From 60e4a76fa872e429975e336827f8c0a51394110f Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Fri, 4 Dec 2020 17:27:33 +0000 Subject: [PATCH] [Flight] Add rudimentary PG binding (#20372) * [Flight] Add rudimentary PG binding * Use nested Maps for parameters * Inline and fix Flow --- packages/react-pg/README.md | 12 +++ packages/react-pg/index.browser.js | 12 +++ packages/react-pg/index.js | 12 +++ packages/react-pg/index.node.js | 12 +++ packages/react-pg/npm/index.browser.js | 7 ++ packages/react-pg/npm/index.js | 3 + packages/react-pg/npm/index.node.js | 7 ++ packages/react-pg/package.json | 27 +++++++ packages/react-pg/src/ReactPostgres.js | 107 +++++++++++++++++++++++++ scripts/flow/environment.js | 14 ++++ scripts/rollup/bundles.js | 18 +++++ 11 files changed, 231 insertions(+) create mode 100644 packages/react-pg/README.md create mode 100644 packages/react-pg/index.browser.js create mode 100644 packages/react-pg/index.js create mode 100644 packages/react-pg/index.node.js create mode 100644 packages/react-pg/npm/index.browser.js create mode 100644 packages/react-pg/npm/index.js create mode 100644 packages/react-pg/npm/index.node.js create mode 100644 packages/react-pg/package.json create mode 100644 packages/react-pg/src/ReactPostgres.js diff --git a/packages/react-pg/README.md b/packages/react-pg/README.md new file mode 100644 index 0000000000000..cbcc8fb09a7fe --- /dev/null +++ b/packages/react-pg/README.md @@ -0,0 +1,12 @@ +# react-pg + +This package is meant to be used alongside yet-to-be-released, experimental React features. It's unlikely to be useful in any other context. + +**Do not use in a real application.** We're publishing this early for +demonstration purposes. + +**Use it at your own risk.** + +# No, Really, It Is Unstable + +The API ~~may~~ will change wildly between versions. diff --git a/packages/react-pg/index.browser.js b/packages/react-pg/index.browser.js new file mode 100644 index 0000000000000..444c63ec765ff --- /dev/null +++ b/packages/react-pg/index.browser.js @@ -0,0 +1,12 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +throw new Error( + 'This entry point is not yet supported in the browser environment', +); diff --git a/packages/react-pg/index.js b/packages/react-pg/index.js new file mode 100644 index 0000000000000..ceb2071c4f055 --- /dev/null +++ b/packages/react-pg/index.js @@ -0,0 +1,12 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +'use strict'; + +export * from './index.node'; diff --git a/packages/react-pg/index.node.js b/packages/react-pg/index.node.js new file mode 100644 index 0000000000000..71ee93be80aa8 --- /dev/null +++ b/packages/react-pg/index.node.js @@ -0,0 +1,12 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +'use strict'; + +export * from './src/ReactPostgres'; diff --git a/packages/react-pg/npm/index.browser.js b/packages/react-pg/npm/index.browser.js new file mode 100644 index 0000000000000..6e11ea9c992e1 --- /dev/null +++ b/packages/react-pg/npm/index.browser.js @@ -0,0 +1,7 @@ +'use strict'; + +if (process.env.NODE_ENV === 'production') { + module.exports = require('./cjs/react-pg.browser.production.min.js'); +} else { + module.exports = require('./cjs/react-pg.browser.development.js'); +} diff --git a/packages/react-pg/npm/index.js b/packages/react-pg/npm/index.js new file mode 100644 index 0000000000000..ee510df2ad686 --- /dev/null +++ b/packages/react-pg/npm/index.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = require('./index.node'); diff --git a/packages/react-pg/npm/index.node.js b/packages/react-pg/npm/index.node.js new file mode 100644 index 0000000000000..a351531e3c902 --- /dev/null +++ b/packages/react-pg/npm/index.node.js @@ -0,0 +1,7 @@ +'use strict'; + +if (process.env.NODE_ENV === 'production') { + module.exports = require('./cjs/react-pg.node.production.min.js'); +} else { + module.exports = require('./cjs/react-pg.node.development.js'); +} diff --git a/packages/react-pg/package.json b/packages/react-pg/package.json new file mode 100644 index 0000000000000..9f8575414eb45 --- /dev/null +++ b/packages/react-pg/package.json @@ -0,0 +1,27 @@ +{ + "private": true, + "name": "react-pg", + "description": "React bindings for PostgreSQL", + "version": "0.0.0", + "repository": { + "type" : "git", + "url" : "https://github.com/facebook/react.git", + "directory": "packages/react-pg" + }, + "files": [ + "LICENSE", + "README.md", + "build-info.json", + "index.js", + "index.node.js", + "index.browser.js", + "cjs/" + ], + "peerDependencies": { + "react": "^17.0.0", + "pg": "*" + }, + "browser": { + "./index.js": "./index.browser.js" + } +} diff --git a/packages/react-pg/src/ReactPostgres.js b/packages/react-pg/src/ReactPostgres.js new file mode 100644 index 0000000000000..fc4eae302a320 --- /dev/null +++ b/packages/react-pg/src/ReactPostgres.js @@ -0,0 +1,107 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import type {Wakeable} from 'shared/ReactTypes'; + +import {unstable_getCacheForType} from 'react'; +import {Pool as PostgresPool} from 'pg'; +import {prepareValue} from 'pg/lib/utils'; + +const Pending = 0; +const Resolved = 1; +const Rejected = 2; + +type PendingResult = {| + status: 0, + value: Wakeable, +|}; + +type ResolvedResult = {| + status: 1, + value: mixed, +|}; + +type RejectedResult = {| + status: 2, + value: mixed, +|}; + +type Result = PendingResult | ResolvedResult | RejectedResult; + +function toResult(thenable): Result { + const result: Result = { + status: Pending, + value: thenable, + }; + thenable.then( + value => { + if (result.status === Pending) { + const resolvedResult = ((result: any): ResolvedResult); + resolvedResult.status = Resolved; + resolvedResult.value = value; + } + }, + err => { + if (result.status === Pending) { + const rejectedResult = ((result: any): RejectedResult); + rejectedResult.status = Rejected; + rejectedResult.value = err; + } + }, + ); + return result; +} + +function readResult(result: Result) { + if (result.status === Resolved) { + return result.value; + } else { + throw result.value; + } +} + +export function Pool(options: mixed) { + this.pool = new PostgresPool(options); + // Unique function per instance because it's used for cache identity. + this.createResultMap = function() { + return new Map(); + }; +} + +Pool.prototype.query = function(query: string, values?: Array) { + const pool = this.pool; + const outerMap = unstable_getCacheForType(this.createResultMap); + + let innerMap: Map = outerMap; + let key = query; + if (values != null) { + // If we have parameters, each becomes as a nesting layer for Maps. + // We want to find (or create as needed) the innermost Map, and return that. + for (let i = 0; i < values.length; i++) { + let nextMap = innerMap.get(key); + if (nextMap === undefined) { + nextMap = new Map(); + innerMap.set(key, nextMap); + } + innerMap = nextMap; + // Postgres bindings convert everything to strings: + // https://node-postgres.com/features/queries#parameterized-query + // We reuse their algorithm instead of reimplementing. + key = prepareValue(values[i]); + } + } + + let entry: Result | void = innerMap.get(key); + if (!entry) { + const thenable = pool.query(query, values); + entry = toResult(thenable); + innerMap.set(key, entry); + } + return readResult(entry); +}; diff --git a/scripts/flow/environment.js b/scripts/flow/environment.js index 3efe41ff344d2..65d7c81da974b 100644 --- a/scripts/flow/environment.js +++ b/scripts/flow/environment.js @@ -69,3 +69,17 @@ declare module 'EventListener' { declare function __webpack_chunk_load__(id: string): Promise; declare function __webpack_require__(id: string): any; + +declare module 'pg' { + declare var Pool: ( + options: mixed, + ) => { + query: (query: string, values?: Array) => void, + }; +} + +declare module 'pg/lib/utils' { + declare module.exports: { + prepareValue(val: any): mixed, + }; +} diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 37fdafa3408a1..2ee1ab74a402c 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -153,6 +153,24 @@ const bundles = [ externals: ['react', 'http', 'https'], }, + /******* React PG Browser (experimental, new) *******/ + { + bundleTypes: [NODE_DEV, NODE_PROD], + moduleType: ISOMORPHIC, + entry: 'react-pg/index.browser', + global: 'ReactPostgres', + externals: [], + }, + + /******* React PG Node (experimental, new) *******/ + { + bundleTypes: [NODE_DEV, NODE_PROD], + moduleType: ISOMORPHIC, + entry: 'react-pg/index.node', + global: 'ReactPostgres', + externals: ['react', 'pg'], + }, + /******* React DOM *******/ { bundleTypes: [