[Flight] Add rudimentary PG binding (#20372)
* [Flight] Add rudimentary PG binding * Use nested Maps for parameters * Inline and fix Flow
Dan Abramov committed
Dec 4, 2020 at 17:27 UTC
60e4a76fa872e429975e336827f8c0a51394110f
11 files changed
+231
packages/react-pg/README.md
new
+12
@@ -0,0 +1,12 @@
1
+# react-pg
2
+
3
+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.
4
+
5
+**Do not use in a real application.** We're publishing this early for
6
+demonstration purposes.
7
+
8
+**Use it at your own risk.**
9
+
10
+# No, Really, It Is Unstable
11
+
12
+The API ~~may~~ will change wildly between versions.
packages/react-pg/index.browser.js
new
+12
@@ -0,0 +1,12 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+throw new Error(
11
+ 'This entry point is not yet supported in the browser environment',
12
+);
packages/react-pg/index.js
new
+12
@@ -0,0 +1,12 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+'use strict';
11
+
12
+export * from './index.node';
packages/react-pg/index.node.js
new
+12
@@ -0,0 +1,12 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+'use strict';
11
+
12
+export * from './src/ReactPostgres';
packages/react-pg/npm/index.browser.js
new
+7
@@ -0,0 +1,7 @@
1
+'use strict';
2
+
3
+if (process.env.NODE_ENV === 'production') {
4
+ module.exports = require('./cjs/react-pg.browser.production.min.js');
5
+} else {
6
+ module.exports = require('./cjs/react-pg.browser.development.js');
7
+}
packages/react-pg/npm/index.js
new
+3
@@ -0,0 +1,3 @@
1
+'use strict';
2
+
3
+module.exports = require('./index.node');
packages/react-pg/npm/index.node.js
new
+7
@@ -0,0 +1,7 @@
1
+'use strict';
2
+
3
+if (process.env.NODE_ENV === 'production') {
4
+ module.exports = require('./cjs/react-pg.node.production.min.js');
5
+} else {
6
+ module.exports = require('./cjs/react-pg.node.development.js');
7
+}
packages/react-pg/package.json
new
+27
@@ -0,0 +1,27 @@
1
+{
2
+ "private": true,
3
+ "name": "react-pg",
4
+ "description": "React bindings for PostgreSQL",
5
+ "version": "0.0.0",
6
+ "repository": {
7
+ "type" : "git",
8
+ "url" : "https://github.com/facebook/react.git",
9
+ "directory": "packages/react-pg"
10
+ },
11
+ "files": [
12
+ "LICENSE",
13
+ "README.md",
14
+ "build-info.json",
15
+ "index.js",
16
+ "index.node.js",
17
+ "index.browser.js",
18
+ "cjs/"
19
+ ],
20
+ "peerDependencies": {
21
+ "react": "^17.0.0",
22
+ "pg": "*"
23
+ },
24
+ "browser": {
25
+ "./index.js": "./index.browser.js"
26
+ }
27
+}
packages/react-pg/src/ReactPostgres.js
new
+107
@@ -0,0 +1,107 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import type {Wakeable} from 'shared/ReactTypes';
11
+
12
+import {unstable_getCacheForType} from 'react';
13
+import {Pool as PostgresPool} from 'pg';
14
+import {prepareValue} from 'pg/lib/utils';
15
+
16
+const Pending = 0;
17
+const Resolved = 1;
18
+const Rejected = 2;
19
+
20
+type PendingResult = {|
21
+ status: 0,
22
+ value: Wakeable,
23
+|};
24
+
25
+type ResolvedResult = {|
26
+ status: 1,
27
+ value: mixed,
28
+|};
29
+
30
+type RejectedResult = {|
31
+ status: 2,
32
+ value: mixed,
33
+|};
34
+
35
+type Result = PendingResult | ResolvedResult | RejectedResult;
36
+
37
+function toResult(thenable): Result {
38
+ const result: Result = {
39
+ status: Pending,
40
+ value: thenable,
41
+ };
42
+ thenable.then(
43
+ value => {
44
+ if (result.status === Pending) {
45
+ const resolvedResult = ((result: any): ResolvedResult);
46
+ resolvedResult.status = Resolved;
47
+ resolvedResult.value = value;
48
+ }
49
+ },
50
+ err => {
51
+ if (result.status === Pending) {
52
+ const rejectedResult = ((result: any): RejectedResult);
53
+ rejectedResult.status = Rejected;
54
+ rejectedResult.value = err;
55
+ }
56
+ },
57
+ );
58
+ return result;
59
+}
60
+
61
+function readResult(result: Result) {
62
+ if (result.status === Resolved) {
63
+ return result.value;
64
+ } else {
65
+ throw result.value;
66
+ }
67
+}
68
+
69
+export function Pool(options: mixed) {
70
+ this.pool = new PostgresPool(options);
71
+ // Unique function per instance because it's used for cache identity.
72
+ this.createResultMap = function() {
73
+ return new Map();
74
+ };
75
+}
76
+
77
+Pool.prototype.query = function(query: string, values?: Array<mixed>) {
78
+ const pool = this.pool;
79
+ const outerMap = unstable_getCacheForType(this.createResultMap);
80
+
81
+ let innerMap: Map<any, any> = outerMap;
82
+ let key = query;
83
+ if (values != null) {
84
+ // If we have parameters, each becomes as a nesting layer for Maps.
85
+ // We want to find (or create as needed) the innermost Map, and return that.
86
+ for (let i = 0; i < values.length; i++) {
87
+ let nextMap = innerMap.get(key);
88
+ if (nextMap === undefined) {
89
+ nextMap = new Map();
90
+ innerMap.set(key, nextMap);
91
+ }
92
+ innerMap = nextMap;
93
+ // Postgres bindings convert everything to strings:
94
+ // https://node-postgres.com/features/queries#parameterized-query
95
+ // We reuse their algorithm instead of reimplementing.
96
+ key = prepareValue(values[i]);
97
+ }
98
+ }
99
+
100
+ let entry: Result | void = innerMap.get(key);
101
+ if (!entry) {
102
+ const thenable = pool.query(query, values);
103
+ entry = toResult(thenable);
104
+ innerMap.set(key, entry);
105
+ }
106
+ return readResult(entry);
107
+};
scripts/flow/environment.js
+14
@@ -69,3 +69,17 @@ declare module 'EventListener' {
69
70
declare function __webpack_chunk_load__(id: string): Promise<mixed>;
71
declare function __webpack_require__(id: string): any;
72
+
73
+declare module 'pg' {
74
+ declare var Pool: (
75
+ options: mixed,
76
+ ) => {
77
+ query: (query: string, values?: Array<mixed>) => void,
78
+ };
79
+}
80
+
81
+declare module 'pg/lib/utils' {
82
+ declare module.exports: {
83
+ prepareValue(val: any): mixed,
84
+ };
85
+}
scripts/rollup/bundles.js
+18
@@ -153,6 +153,24 @@ const bundles = [
153
externals: ['react', 'http', 'https'],
154
},
155
156
+ /******* React PG Browser (experimental, new) *******/
157
+ {
158
+ bundleTypes: [NODE_DEV, NODE_PROD],
159
+ moduleType: ISOMORPHIC,
160
+ entry: 'react-pg/index.browser',
161
+ global: 'ReactPostgres',
162
+ externals: [],
163
+ },
164
+
165
+ /******* React PG Node (experimental, new) *******/
166
+ {
167
+ bundleTypes: [NODE_DEV, NODE_PROD],
168
+ moduleType: ISOMORPHIC,
169
+ entry: 'react-pg/index.node',
170
+ global: 'ReactPostgres',
171
+ externals: ['react', 'pg'],
172
+ },
173
+
174
/******* React DOM *******/
175
{
176
bundleTypes: [