├── .eslintrc.json
├── .github
└── workflows
│ └── test.yml
├── CHANGELOG.md
├── README.md
├── UNLICENSE
├── cf
├── polyfills.js
├── src
│ ├── bytes.js
│ ├── connection.js
│ ├── errors.js
│ ├── index.js
│ ├── large.js
│ ├── query.js
│ ├── queue.js
│ ├── result.js
│ ├── subscribe.js
│ └── types.js
└── test.js
├── cjs
├── package.json
├── src
│ ├── bytes.js
│ ├── connection.js
│ ├── errors.js
│ ├── index.js
│ ├── large.js
│ ├── query.js
│ ├── queue.js
│ ├── result.js
│ ├── subscribe.js
│ └── types.js
└── tests
│ ├── bootstrap.js
│ ├── copy.csv
│ ├── index.js
│ ├── pg_hba.conf
│ ├── select-param.sql
│ ├── select.sql
│ └── test.js
├── demo.gif
├── deno
├── README.md
├── mod.js
├── package.json
├── polyfills.js
├── src
│ ├── bytes.js
│ ├── connection.js
│ ├── errors.js
│ ├── index.js
│ ├── large.js
│ ├── query.js
│ ├── queue.js
│ ├── result.js
│ ├── subscribe.js
│ └── types.js
├── tests
│ ├── bootstrap.js
│ ├── copy.csv
│ ├── index.js
│ ├── pg_hba.conf
│ ├── select-param.sql
│ ├── select.sql
│ └── test.js
└── types
│ └── index.d.ts
├── package.json
├── postgresjs.svg
├── src
├── bytes.js
├── connection.js
├── errors.js
├── index.js
├── large.js
├── query.js
├── queue.js
├── result.js
├── subscribe.js
└── types.js
├── tests
├── bootstrap.js
├── copy.csv
├── index.js
├── pg_hba.conf
├── select-param.sql
├── select.sql
└── test.js
├── transpile.cf.js
├── transpile.cjs
├── transpile.deno.js
└── types
├── index.d.ts
├── package.json
└── tsconfig.json
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "env": {
4 | "es2020": true,
5 | "node": true
6 | },
7 | "parserOptions": {
8 | "ecmaVersion": 2020,
9 | "sourceType": "module"
10 | },
11 | "rules": {
12 | "comma-dangle": 2,
13 | "no-cond-assign": 2,
14 | "no-console": 1,
15 | "no-constant-condition": 2,
16 | "no-control-regex": 2,
17 | "no-debugger": 2,
18 | "no-dupe-args": 2,
19 | "no-dupe-keys": 2,
20 | "no-duplicate-case": 2,
21 | "no-empty": 2,
22 | "no-empty-character-class": 2,
23 | "no-ex-assign": 2,
24 | "no-extra-boolean-cast": 2,
25 | "no-extra-semi": 2,
26 | "no-func-assign": 2,
27 | "no-inner-declarations": 2,
28 | "no-invalid-regexp": 2,
29 | "no-irregular-whitespace": 2,
30 | "no-negated-in-lhs": 0,
31 | "no-obj-calls": 2,
32 | "no-regex-spaces": 2,
33 | "no-sparse-arrays": 2,
34 | "no-unexpected-multiline": 2,
35 | "no-unreachable": 2,
36 | "use-isnan": 2,
37 | "valid-typeof": 2,
38 | "accessor-pairs": 2,
39 | "array-callback-return": 0,
40 | "consistent-return": 0,
41 | "curly": [
42 | 2,
43 | "multi-or-nest",
44 | "consistent"
45 | ],
46 | "default-case": 2,
47 | "dot-location": [
48 | 2,
49 | "property"
50 | ],
51 | "dot-notation": [
52 | 2,
53 | {
54 | "allowPattern": "^[a-z]+(_[a-z]+)+$"
55 | }
56 | ],
57 | "eqeqeq": [
58 | "error",
59 | "always",
60 | {
61 | "null": "ignore"
62 | }
63 | ],
64 | "no-alert": 2,
65 | "no-caller": 2,
66 | "no-case-declarations": 2,
67 | "no-div-regex": 2,
68 | "no-else-return": 2,
69 | "no-empty-function": 2,
70 | "no-empty-pattern": 2,
71 | "no-eq-null": 0,
72 | "no-eval": 2,
73 | "no-extend-native": 2,
74 | "no-extra-bind": 2,
75 | "no-extra-label": 2,
76 | "no-fallthrough": 2,
77 | "no-floating-decimal": 2,
78 | "no-implicit-coercion": 0,
79 | "no-implicit-globals": 2,
80 | "no-implied-eval": 2,
81 | "no-invalid-this": 2,
82 | "no-iterator": 2,
83 | "no-labels": 2,
84 | "no-lone-blocks": 2,
85 | "no-loop-func": 2,
86 | "no-magic-numbers": 0,
87 | "no-multi-spaces": [
88 | 2,
89 | {
90 | "ignoreEOLComments": true,
91 | "exceptions": {
92 | "Array": true,
93 | "Property": true,
94 | "VariableDeclarator": true,
95 | "ImportDeclaration": true,
96 | "TernaryExpressions": true,
97 | "Comments": true
98 | }
99 | }
100 | ],
101 | "no-multi-str": 2,
102 | "no-native-reassign": 2,
103 | "no-new": 2,
104 | "no-new-func": 2,
105 | "no-new-wrappers": 2,
106 | "no-octal": 2,
107 | "no-octal-escape": 2,
108 | "no-param-reassign": 0,
109 | "no-proto": 2,
110 | "no-redeclare": 2,
111 | "no-return-assign": 0,
112 | "no-script-url": 2,
113 | "no-self-assign": 2,
114 | "no-self-compare": 2,
115 | "no-sequences": 0,
116 | "no-throw-literal": 2,
117 | "no-unmodified-loop-condition": 2,
118 | "no-unused-expressions": 0,
119 | "no-unused-labels": 2,
120 | "no-useless-call": 2,
121 | "no-useless-concat": 2,
122 | "no-useless-escape": 2,
123 | "no-void": 2,
124 | "no-with": 2,
125 | "wrap-iife": 2,
126 | "no-delete-var": 2,
127 | "no-label-var": 2,
128 | "no-restricted-globals": 2,
129 | "no-shadow": 0,
130 | "no-shadow-restricted-names": 2,
131 | "no-undef": 2,
132 | "no-undef-init": 2,
133 | "no-unused-vars": 2,
134 | "no-use-before-define": [
135 | 2,
136 | {
137 | "functions": false,
138 | "variables": false
139 | }
140 | ],
141 | "callback-return": 0,
142 | "global-require": 2,
143 | "handle-callback-err": 2,
144 | "no-mixed-requires": 2,
145 | "no-new-require": 2,
146 | "no-path-concat": 2,
147 | "no-process-env": 2,
148 | "no-process-exit": 2,
149 | "array-bracket-spacing": [
150 | 2,
151 | "never"
152 | ],
153 | "block-spacing": [
154 | 2,
155 | "always"
156 | ],
157 | "brace-style": [
158 | 2,
159 | "1tbs",
160 | {
161 | "allowSingleLine": true
162 | }
163 | ],
164 | "camelcase": 0,
165 | "comma-spacing": 2,
166 | "comma-style": [
167 | 2,
168 | "first",
169 | {
170 | "exceptions": {
171 | "ArrayExpression": true,
172 | "ObjectExpression": true
173 | }
174 | }
175 | ],
176 | "consistent-this": [
177 | 2,
178 | "self"
179 | ],
180 | "eol-last": 2,
181 | "indent": [
182 | 2,
183 | 2,
184 | {
185 | "MemberExpression": "off",
186 | "flatTernaryExpressions": true,
187 | "VariableDeclarator": {
188 | "const": 2
189 | },
190 | "FunctionExpression": {
191 | "parameters": "first"
192 | },
193 | "CallExpression": {
194 | "arguments": "off"
195 | },
196 | "ArrayExpression": "first",
197 | "ObjectExpression": "first"
198 | }
199 | ],
200 | "key-spacing": [
201 | 0,
202 | {
203 | "beforeColon": false,
204 | "afterColon": true,
205 | "mode": "minimum"
206 | }
207 | ],
208 | "keyword-spacing": 2,
209 | "linebreak-style": 2,
210 | "lines-around-comment": 0,
211 | "max-depth": [
212 | 2,
213 | 5
214 | ],
215 | "max-len": [
216 | 2,
217 | 150
218 | ],
219 | "max-nested-callbacks": [
220 | 2,
221 | 5
222 | ],
223 | "max-params": [
224 | 2,
225 | 5
226 | ],
227 | "max-statements-per-line": 0,
228 | "new-cap": [
229 | 2,
230 | {
231 | "capIsNew": false
232 | }
233 | ],
234 | "new-parens": 2,
235 | "newline-after-var": 0,
236 | "newline-before-return": 0,
237 | "no-array-constructor": 2,
238 | "no-bitwise": 0,
239 | "no-continue": 2,
240 | "no-lonely-if": 2,
241 | "no-mixed-spaces-and-tabs": 2,
242 | "no-negated-condition": 0,
243 | "no-new-object": 2,
244 | "no-spaced-func": 2,
245 | "no-trailing-spaces": 1,
246 | "no-unneeded-ternary": 2,
247 | "no-whitespace-before-property": 2,
248 | "object-curly-spacing": [
249 | 2,
250 | "always"
251 | ],
252 | "one-var-declaration-per-line": [
253 | 2,
254 | "always"
255 | ],
256 | "quote-props": [
257 | 2,
258 | "as-needed"
259 | ],
260 | "quotes": [
261 | 2,
262 | "single"
263 | ],
264 | "semi": [
265 | 2,
266 | "never"
267 | ],
268 | "space-before-blocks": 2,
269 | "space-before-function-paren": [
270 | 2,
271 | "never"
272 | ],
273 | "space-infix-ops": 2,
274 | "space-unary-ops": 2,
275 | "spaced-comment": 2,
276 | "arrow-spacing": 2,
277 | "constructor-super": 2,
278 | "no-class-assign": 2,
279 | "no-confusing-arrow": 0,
280 | "no-const-assign": 2,
281 | "no-dupe-class-members": 2,
282 | "no-duplicate-imports": 2,
283 | "no-new-symbol": 2,
284 | "no-this-before-super": 2,
285 | "no-useless-constructor": 2,
286 | "no-var": 2,
287 | "object-shorthand": 0,
288 | "prefer-arrow-callback": 0,
289 | "prefer-const": 2,
290 | "prefer-rest-params": 0,
291 | "prefer-spread": 0
292 | }
293 | }
294 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: test
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | test:
7 | name: Node v${{ matrix.node }} on PostgreSQL v${{ matrix.postgres }}
8 | strategy:
9 | fail-fast: false
10 | matrix:
11 | node: ['12', '14', '16', '18', '20', '21', '22', '23', '24']
12 | postgres: ['12', '13', '14', '15', '16', '17']
13 | runs-on: ubuntu-latest
14 | services:
15 | postgres:
16 | image: postgres:${{ matrix.postgres }}
17 | env:
18 | POSTGRES_USER: postgres
19 | POSTGRES_HOST_AUTH_METHOD: trust
20 | ports:
21 | - 5433:5432
22 | options: >-
23 | --health-cmd pg_isready
24 | --health-interval 10s
25 | --health-timeout 5s
26 | --health-retries 5
27 | steps:
28 | - uses: actions/checkout@v4
29 | - run: |
30 | date
31 | sudo apt purge postgresql-16
32 | sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
33 | wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
34 | sudo apt-get update
35 | sudo apt-get -y install "postgresql-${{ matrix.postgres }}"
36 | sudo cp ./tests/pg_hba.conf /etc/postgresql/${{ matrix.postgres }}/main/pg_hba.conf
37 | sudo sed -i 's/.*wal_level.*/wal_level = logical/' /etc/postgresql/${{ matrix.postgres }}/main/postgresql.conf
38 | sudo sed -i 's/.*max_prepared_transactions.*/max_prepared_transactions = 100/' /etc/postgresql/${{ matrix.postgres }}/main/postgresql.conf
39 | sudo sed -i 's/.*ssl = .*/ssl = on/' /etc/postgresql/${{ matrix.postgres }}/main/postgresql.conf
40 | openssl req -new -x509 -nodes -days 365 -text -subj "/CN=localhost" -extensions v3_req -config <(cat /etc/ssl/openssl.cnf <(printf "\n[v3_req]\nbasicConstraints=critical,CA:TRUE\nkeyUsage=nonRepudiation,digitalSignature,keyEncipherment\nsubjectAltName=DNS:localhost")) -keyout server.key -out server.crt
41 | sudo cp server.key /etc/postgresql/${{ matrix.postgres }}/main/server.key
42 | sudo cp server.crt /etc/postgresql/${{ matrix.postgres }}/main/server.crt
43 | sudo chmod og-rwx /etc/postgresql/${{ matrix.postgres }}/main/server.key
44 | sudo systemctl start postgresql.service
45 | sudo systemctl status postgresql.service
46 | pg_isready
47 | sudo -u postgres psql -c "SHOW hba_file;"
48 | - uses: denoland/setup-deno@v1
49 | with:
50 | deno-version: v1.x
51 | - uses: actions/setup-node@v4
52 | with:
53 | node-version: ${{ matrix.node }}
54 | - run: npm test
55 | env:
56 | PGUSER: postgres
57 | PGSOCKET: /var/run/postgresql
58 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## v3.2.4 - 25 May 2022
4 | - Allow setting keep_alive: false bee62f3
5 | - Fix support for null in arrays - fixes #371 b04c853
6 |
7 | ## v3.2.3 - 23 May 2022
8 | - Fix Only use setKeepAlive in Deno if available 28fbbaf
9 | - Fix wrong helper match on multiple occurances 02f3854
10 |
11 | #### Typescript related
12 | - Fix Deno assertRejects compatibility (#365) 0f0af92
13 | - Fix include missing boolean type in JSONValue union (#373) 1817387
14 |
15 | ## v3.2.2 - 15 May 2022
16 | - Properly handle errors thrown on commit 99ddae4
17 |
18 | ## v3.2.1 - 15 May 2022
19 | - Exclude target_session_attrs from connection obj 43f1442
20 |
21 | ## v3.2.0 - 15 May 2022
22 | - Add `sslmode=verify-full` support e67da29
23 | - Add support for array of fragments 342bf55
24 | - Add uri decode of host in url - fixes #346 1adc113
25 | - Add passing of rest url params to connection (ootb support cockroach urls) 41ed84f
26 | - Fix Deno partial writes 452a30d
27 | - Fix `as` dynamic helper 3300c40
28 | - Fix some nested fragments usage 9bfa902
29 | - Fix missing columns on `Result` when using simple protocol - fixes #350 1e2e298
30 | - Fix fragments in transactions - fixes #333 75914c7
31 |
32 | #### Typescript related
33 | - Upgrade/fix types (#357) 1e6d312
34 | - Add optional `onlisten` callback to `listen()` on TypeScript (#360) 6b749b2
35 | - Add implicit custom type inference (#361) 28512bf
36 | - Fix and improve sql() helper types (#338) c1de3d8
37 | - Fix update query type def for `.writable()` and `.readable()` to return promises (#347) 51269ce
38 | - Add bigint to typescript Serializable - fixes #330 f1e41c3
39 |
40 | ## v3.1.0 - 22 Apr 2022
41 | - Add close method to close but not end connections forever 94fea8f
42 | - Add .values() method to return rows as arrays of values 56873c2
43 | - Support transform.undefined - fixes #314 eab71e5
44 | - Support nested fragments values and dynamics - fixes #326 86445ca
45 | - Fix deno close sequence f76af24
46 | - Fix subscribe reconnect and add onsubscribe method - fixes #315 5097345
47 | - Deno ts fix - fixes #327 50403a1
48 |
49 | ## v3.0.6 - 19 Apr 2022
50 | - Properly close connections in Deno cbc6a75
51 | - Only write end message if socket is open 13950af
52 | - Improve query cancellation 01c2c68
53 | - Use monotonically increasing time for timeout - fixes #316 9d7a21d
54 | - Add support for dynamic columns with `returning` - fixes #317 04644c0
55 | - Fix type errors in TypeScript deno projects (#313) 822fb21
56 | - Execute forEach instantly 44e9fbe
57 |
58 | ## v3.0.5 - 6 Apr 2022
59 | - Fix transaction execution timing 28bb0b3
60 | - Add optional onlisten function to listen 1dc2fd2
61 | - Fix dynamic in helper after insert #305 4d63a59
62 |
63 | ## v3.0.4 - 5 Apr 2022
64 | - Ensure drain only dequeues if ready - fixes #303 2e5f017
65 |
66 | ## v3.0.3 - 4 Apr 2022
67 | - Run tests with github actions b536d0d
68 | - Add custom socket option - fixes #284 5413f0c
69 | - Fix sql function overload type inference (#294) 3c4e90a
70 | - Update deno std to 0.132 and enable last tests 50762d4
71 | - Send proper client-encoding - Fixes #288 e5b8554
72 |
73 | ## v3.0.2 - 31 Mar 2022
74 | - Fix BigInt handling 36a70df
75 | - Fix unsubscribing (#300) b6c597f
76 | - Parse update properly with identity full - Fixes #296 3ed11e7
77 |
78 | ## v3.0.1 - 30 Mar 2022
79 | - Improve connection queue handling + fix leak cee1a57
80 | - Use publications option - fixes #295 b5ceecc
81 | - Throw proper query error if destroyed e148a0a
82 | - Transaction rejects with rethrown error - fixes #289 f7c8ae6
83 | - Only create origin stacktrace for tagged and debug - fixes #290 a782edf
84 | - Include types and readme in deno release - fixes #287 9068820
85 | - Disable fetch_types for Subscribe options 72e0cdb
86 | - Update TypeScript types with v3 changes (#293) db05836
87 |
88 | ## v3.0.0 - 24 Mar 2022
89 | This is a complete rewrite to better support all the features that I was trying to get into v2. There are a few breaking changes from v2 beta, which some (myself included) was using in production, so I'm skipping a stable v2 release and going straight to v3.
90 |
91 | Here are some of the new things available, but check the updated docs.
92 | - Dynamic query builder based on raw sql
93 | - Realtime subscribe to db changes through logical replication
94 | - Multi-host support for High Availability setups
95 | - Postgres input parameter types from `ParameterDescription`
96 | - Deno support
97 | - Cursors as async iterators
98 | - `.describe()` to only get query input types and column definitions
99 | - Support for Large Objects
100 | - `max_lifetime` for connections
101 | - Cancellation of requests
102 | - Converted to ESM (with CJS support)
103 | - Typescript support (Credit @minigugus)
104 |
105 | ### Breaking changes from v2 -> v3
106 | - Cursors are always called with `Result` arrays (previously cursor 1 would return a row object, where > 1 would return an array of rows)
107 | - `.writable()` and `.readable()` is now async (returns a Promise that resolves to the stream)
108 | - Queries now returns a lazy promise instead of being executed immediately. This means the query won't be sent until awaited (.then, .catch, .finally is called) or until `.execute()` is manually called.
109 | - `.stream()` is renamed to `.forEach`
110 | - Returned results are now it's own `Result` class extending `Array` instead of an Array with extra properties (actually shouldn't be breaking unless you're doing something funny)
111 | - Parameters are now cast using the types returned from Postgres ParameterDescription with a fallback to the previously inferred types
112 | - Only tested with node v12 and up
113 | - Implicit array value to multiple parameter expansion removed (use sql([...]) instead)
114 |
115 | ### Breaking changes from v1 -> v2 (v2 never moved on from beta)
116 | - All identifiers from `sql()` in queries are now always quoted
117 | - Undefined parameters are no longer allowed
118 | - Rename timeout option to `idle_timeout`
119 | - Default to 10 connections instead of number of CPUs
120 | - Numbers that cannot be safely cast to JS Number are returned as string. This happens for eg, `select count(*)` because `count()` returns a 64 bit integer (int8), so if you know your `count()` won't be too big for a js number just cast in your query to int4 like `select count(*)::int`
121 |
122 | ## v1.0.2 - 21 Jan 2020
123 |
124 | - Fix standard postgres user env var (#20) cce5ad7
125 | - Ensure url or options is not falsy bc549b0
126 | - Add support for dynamic password b2ab9fb
127 | - Fix hiding pass from options 3f76b98
128 |
129 |
130 | ## v1.0.1 - 3 Jan 2020
131 |
132 | - Fix #3 url without db and trailing slash 45d4233
133 | - Fix stream promise - resolve with correct result 730df2c
134 | - Fix return value of unsafe query with multiple statements 748f198
135 | - Fix destroy before connected f682ca1
136 | - Fix params usage for file() call without options e4f12a4
137 | - Various Performance improvements
138 |
139 | ## v1.0.0 - 22 Dec 2019
140 |
141 | - Initial release
142 |
--------------------------------------------------------------------------------
/UNLICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/cf/polyfills.js:
--------------------------------------------------------------------------------
1 | import { EventEmitter } from 'node:events'
2 | import { Buffer } from 'node:buffer'
3 |
4 | const Crypto = globalThis.crypto
5 |
6 | let ids = 1
7 | const tasks = new Set()
8 |
9 | const v4Seg = '(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'
10 | const v4Str = `(${v4Seg}[.]){3}${v4Seg}`
11 | const IPv4Reg = new RegExp(`^${v4Str}$`)
12 |
13 | const v6Seg = '(?:[0-9a-fA-F]{1,4})'
14 | const IPv6Reg = new RegExp(
15 | '^(' +
16 | `(?:${v6Seg}:){7}(?:${v6Seg}|:)|` +
17 | `(?:${v6Seg}:){6}(?:${v4Str}|:${v6Seg}|:)|` +
18 | `(?:${v6Seg}:){5}(?::${v4Str}|(:${v6Seg}){1,2}|:)|` +
19 | `(?:${v6Seg}:){4}(?:(:${v6Seg}){0,1}:${v4Str}|(:${v6Seg}){1,3}|:)|` +
20 | `(?:${v6Seg}:){3}(?:(:${v6Seg}){0,2}:${v4Str}|(:${v6Seg}){1,4}|:)|` +
21 | `(?:${v6Seg}:){2}(?:(:${v6Seg}){0,3}:${v4Str}|(:${v6Seg}){1,5}|:)|` +
22 | `(?:${v6Seg}:){1}(?:(:${v6Seg}){0,4}:${v4Str}|(:${v6Seg}){1,6}|:)|` +
23 | `(?::((?::${v6Seg}){0,5}:${v4Str}|(?::${v6Seg}){1,7}|:))` +
24 | ')(%[0-9a-zA-Z-.:]{1,})?$'
25 | )
26 |
27 | const textEncoder = new TextEncoder()
28 | export const crypto = {
29 | randomBytes: l => Crypto.getRandomValues(Buffer.alloc(l)),
30 | pbkdf2Sync: async(password, salt, iterations, keylen) =>
31 | Crypto.subtle.deriveBits(
32 | {
33 | name: 'PBKDF2',
34 | hash: 'SHA-256',
35 | salt,
36 | iterations
37 | },
38 | await Crypto.subtle.importKey(
39 | 'raw',
40 | textEncoder.encode(password),
41 | 'PBKDF2',
42 | false,
43 | ['deriveBits']
44 | ),
45 | keylen * 8,
46 | ['deriveBits']
47 | ),
48 | createHash: type => ({
49 | update: x => ({
50 | digest: encoding => {
51 | if (!(x instanceof Uint8Array)) {
52 | x = textEncoder.encode(x)
53 | }
54 | let prom
55 | if (type === 'sha256') {
56 | prom = Crypto.subtle.digest('SHA-256', x)
57 | } else if (type === 'md5') {
58 | prom = Crypto.subtle.digest('md5', x)
59 | } else {
60 | throw Error('createHash only supports sha256 or md5 in this environment, not ${type}.')
61 | }
62 | if (encoding === 'hex') {
63 | return prom.then((arrayBuf) => Buffer.from(arrayBuf).toString('hex'))
64 | } else if (encoding) {
65 | throw Error(`createHash only supports hex encoding or unencoded in this environment, not ${encoding}`)
66 | } else {
67 | return prom
68 | }
69 | }
70 | })
71 | }),
72 | createHmac: (type, key) => ({
73 | update: x => ({
74 | digest: async() =>
75 | Buffer.from(
76 | await Crypto.subtle.sign(
77 | 'HMAC',
78 | await Crypto.subtle.importKey('raw', key, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']),
79 | textEncoder.encode(x)
80 | )
81 | )
82 | })
83 | })
84 | }
85 |
86 | export const performance = globalThis.performance
87 |
88 | export const process = {
89 | env: {}
90 | }
91 |
92 | export const os = {
93 | userInfo() {
94 | return { username: 'postgres' }
95 | }
96 | }
97 |
98 | export const fs = {
99 | readFile() {
100 | throw new Error('Reading files not supported on CloudFlare')
101 | }
102 | }
103 |
104 | export const net = {
105 | isIP: (x) => IPv4Reg.test(x) ? 4 : IPv6Reg.test(x) ? 6 : 0,
106 | Socket
107 | }
108 |
109 | export { setImmediate, clearImmediate }
110 |
111 | export const tls = {
112 | connect({ socket: tcp, servername }) {
113 | tcp.writer.releaseLock()
114 | tcp.reader.releaseLock()
115 | tcp.readyState = 'upgrading'
116 | tcp.raw = tcp.raw.startTls({ servername })
117 | tcp.raw.closed.then(
118 | () => tcp.emit('close'),
119 | (e) => tcp.emit('error', e)
120 | )
121 | tcp.writer = tcp.raw.writable.getWriter()
122 | tcp.reader = tcp.raw.readable.getReader()
123 |
124 | tcp.writer.ready.then(() => {
125 | tcp.read()
126 | tcp.readyState = 'upgrade'
127 | })
128 | return tcp
129 | }
130 | }
131 |
132 | function Socket() {
133 | const tcp = Object.assign(new EventEmitter(), {
134 | readyState: 'open',
135 | raw: null,
136 | writer: null,
137 | reader: null,
138 | connect,
139 | write,
140 | end,
141 | destroy,
142 | read
143 | })
144 |
145 | return tcp
146 |
147 | async function connect(port, host) {
148 | try {
149 | tcp.readyState = 'opening'
150 | const { connect } = await import('cloudflare:sockets')
151 | tcp.raw = connect(host + ':' + port, tcp.ssl ? { secureTransport: 'starttls' } : {})
152 | tcp.raw.closed.then(
153 | () => {
154 | tcp.readyState !== 'upgrade'
155 | ? close()
156 | : ((tcp.readyState = 'open'), tcp.emit('secureConnect'))
157 | },
158 | (e) => tcp.emit('error', e)
159 | )
160 | tcp.writer = tcp.raw.writable.getWriter()
161 | tcp.reader = tcp.raw.readable.getReader()
162 |
163 | tcp.ssl ? readFirst() : read()
164 | tcp.writer.ready.then(() => {
165 | tcp.readyState = 'open'
166 | tcp.emit('connect')
167 | })
168 | } catch (err) {
169 | error(err)
170 | }
171 | }
172 |
173 | function close() {
174 | if (tcp.readyState === 'closed')
175 | return
176 |
177 | tcp.readyState = 'closed'
178 | tcp.emit('close')
179 | }
180 |
181 | function write(data, cb) {
182 | tcp.writer.write(data).then(cb, error)
183 | return true
184 | }
185 |
186 | function end(data) {
187 | return data
188 | ? tcp.write(data, () => tcp.raw.close())
189 | : tcp.raw.close()
190 | }
191 |
192 | function destroy() {
193 | tcp.destroyed = true
194 | tcp.end()
195 | }
196 |
197 | async function read() {
198 | try {
199 | let done
200 | , value
201 | while (({ done, value } = await tcp.reader.read(), !done))
202 | tcp.emit('data', Buffer.from(value))
203 | } catch (err) {
204 | error(err)
205 | }
206 | }
207 |
208 | async function readFirst() {
209 | const { value } = await tcp.reader.read()
210 | tcp.emit('data', Buffer.from(value))
211 | }
212 |
213 | function error(err) {
214 | tcp.emit('error', err)
215 | tcp.emit('close')
216 | }
217 | }
218 |
219 | function setImmediate(fn) {
220 | const id = ids++
221 | tasks.add(id)
222 | queueMicrotask(() => {
223 | if (tasks.has(id)) {
224 | fn()
225 | tasks.delete(id)
226 | }
227 | })
228 | return id
229 | }
230 |
231 | function clearImmediate(id) {
232 | tasks.delete(id)
233 | }
234 |
--------------------------------------------------------------------------------
/cf/src/bytes.js:
--------------------------------------------------------------------------------
1 | import { Buffer } from 'node:buffer'
2 | const size = 256
3 | let buffer = Buffer.allocUnsafe(size)
4 |
5 | const messages = 'BCcDdEFfHPpQSX'.split('').reduce((acc, x) => {
6 | const v = x.charCodeAt(0)
7 | acc[x] = () => {
8 | buffer[0] = v
9 | b.i = 5
10 | return b
11 | }
12 | return acc
13 | }, {})
14 |
15 | const b = Object.assign(reset, messages, {
16 | N: String.fromCharCode(0),
17 | i: 0,
18 | inc(x) {
19 | b.i += x
20 | return b
21 | },
22 | str(x) {
23 | const length = Buffer.byteLength(x)
24 | fit(length)
25 | b.i += buffer.write(x, b.i, length, 'utf8')
26 | return b
27 | },
28 | i16(x) {
29 | fit(2)
30 | buffer.writeUInt16BE(x, b.i)
31 | b.i += 2
32 | return b
33 | },
34 | i32(x, i) {
35 | if (i || i === 0) {
36 | buffer.writeUInt32BE(x, i)
37 | return b
38 | }
39 | fit(4)
40 | buffer.writeUInt32BE(x, b.i)
41 | b.i += 4
42 | return b
43 | },
44 | z(x) {
45 | fit(x)
46 | buffer.fill(0, b.i, b.i + x)
47 | b.i += x
48 | return b
49 | },
50 | raw(x) {
51 | buffer = Buffer.concat([buffer.subarray(0, b.i), x])
52 | b.i = buffer.length
53 | return b
54 | },
55 | end(at = 1) {
56 | buffer.writeUInt32BE(b.i - at, at)
57 | const out = buffer.subarray(0, b.i)
58 | b.i = 0
59 | buffer = Buffer.allocUnsafe(size)
60 | return out
61 | }
62 | })
63 |
64 | export default b
65 |
66 | function fit(x) {
67 | if (buffer.length - b.i < x) {
68 | const prev = buffer
69 | , length = prev.length
70 |
71 | buffer = Buffer.allocUnsafe(length + (length >> 1) + x)
72 | prev.copy(buffer)
73 | }
74 | }
75 |
76 | function reset() {
77 | b.i = 0
78 | return b
79 | }
80 |
--------------------------------------------------------------------------------
/cf/src/errors.js:
--------------------------------------------------------------------------------
1 | export class PostgresError extends Error {
2 | constructor(x) {
3 | super(x.message)
4 | this.name = this.constructor.name
5 | Object.assign(this, x)
6 | }
7 | }
8 |
9 | export const Errors = {
10 | connection,
11 | postgres,
12 | generic,
13 | notSupported
14 | }
15 |
16 | function connection(x, options, socket) {
17 | const { host, port } = socket || options
18 | const error = Object.assign(
19 | new Error(('write ' + x + ' ' + (options.path || (host + ':' + port)))),
20 | {
21 | code: x,
22 | errno: x,
23 | address: options.path || host
24 | }, options.path ? {} : { port: port }
25 | )
26 | Error.captureStackTrace(error, connection)
27 | return error
28 | }
29 |
30 | function postgres(x) {
31 | const error = new PostgresError(x)
32 | Error.captureStackTrace(error, postgres)
33 | return error
34 | }
35 |
36 | function generic(code, message) {
37 | const error = Object.assign(new Error(code + ': ' + message), { code })
38 | Error.captureStackTrace(error, generic)
39 | return error
40 | }
41 |
42 | /* c8 ignore next 10 */
43 | function notSupported(x) {
44 | const error = Object.assign(
45 | new Error(x + ' (B) is not supported'),
46 | {
47 | code: 'MESSAGE_NOT_SUPPORTED',
48 | name: x
49 | }
50 | )
51 | Error.captureStackTrace(error, notSupported)
52 | return error
53 | }
54 |
--------------------------------------------------------------------------------
/cf/src/large.js:
--------------------------------------------------------------------------------
1 | import Stream from 'node:stream'
2 |
3 | export default function largeObject(sql, oid, mode = 0x00020000 | 0x00040000) {
4 | return new Promise(async(resolve, reject) => {
5 | await sql.begin(async sql => {
6 | let finish
7 | !oid && ([{ oid }] = await sql`select lo_creat(-1) as oid`)
8 | const [{ fd }] = await sql`select lo_open(${ oid }, ${ mode }) as fd`
9 |
10 | const lo = {
11 | writable,
12 | readable,
13 | close : () => sql`select lo_close(${ fd })`.then(finish),
14 | tell : () => sql`select lo_tell64(${ fd })`,
15 | read : (x) => sql`select loread(${ fd }, ${ x }) as data`,
16 | write : (x) => sql`select lowrite(${ fd }, ${ x })`,
17 | truncate : (x) => sql`select lo_truncate64(${ fd }, ${ x })`,
18 | seek : (x, whence = 0) => sql`select lo_lseek64(${ fd }, ${ x }, ${ whence })`,
19 | size : () => sql`
20 | select
21 | lo_lseek64(${ fd }, location, 0) as position,
22 | seek.size
23 | from (
24 | select
25 | lo_lseek64($1, 0, 2) as size,
26 | tell.location
27 | from (select lo_tell64($1) as location) tell
28 | ) seek
29 | `
30 | }
31 |
32 | resolve(lo)
33 |
34 | return new Promise(async r => finish = r)
35 |
36 | async function readable({
37 | highWaterMark = 2048 * 8,
38 | start = 0,
39 | end = Infinity
40 | } = {}) {
41 | let max = end - start
42 | start && await lo.seek(start)
43 | return new Stream.Readable({
44 | highWaterMark,
45 | async read(size) {
46 | const l = size > max ? size - max : size
47 | max -= size
48 | const [{ data }] = await lo.read(l)
49 | this.push(data)
50 | if (data.length < size)
51 | this.push(null)
52 | }
53 | })
54 | }
55 |
56 | async function writable({
57 | highWaterMark = 2048 * 8,
58 | start = 0
59 | } = {}) {
60 | start && await lo.seek(start)
61 | return new Stream.Writable({
62 | highWaterMark,
63 | write(chunk, encoding, callback) {
64 | lo.write(chunk).then(() => callback(), callback)
65 | }
66 | })
67 | }
68 | }).catch(reject)
69 | })
70 | }
71 |
--------------------------------------------------------------------------------
/cf/src/query.js:
--------------------------------------------------------------------------------
1 | const originCache = new Map()
2 | , originStackCache = new Map()
3 | , originError = Symbol('OriginError')
4 |
5 | export const CLOSE = {}
6 | export class Query extends Promise {
7 | constructor(strings, args, handler, canceller, options = {}) {
8 | let resolve
9 | , reject
10 |
11 | super((a, b) => {
12 | resolve = a
13 | reject = b
14 | })
15 |
16 | this.tagged = Array.isArray(strings.raw)
17 | this.strings = strings
18 | this.args = args
19 | this.handler = handler
20 | this.canceller = canceller
21 | this.options = options
22 |
23 | this.state = null
24 | this.statement = null
25 |
26 | this.resolve = x => (this.active = false, resolve(x))
27 | this.reject = x => (this.active = false, reject(x))
28 |
29 | this.active = false
30 | this.cancelled = null
31 | this.executed = false
32 | this.signature = ''
33 |
34 | this[originError] = this.handler.debug
35 | ? new Error()
36 | : this.tagged && cachedError(this.strings)
37 | }
38 |
39 | get origin() {
40 | return (this.handler.debug
41 | ? this[originError].stack
42 | : this.tagged && originStackCache.has(this.strings)
43 | ? originStackCache.get(this.strings)
44 | : originStackCache.set(this.strings, this[originError].stack).get(this.strings)
45 | ) || ''
46 | }
47 |
48 | static get [Symbol.species]() {
49 | return Promise
50 | }
51 |
52 | cancel() {
53 | return this.canceller && (this.canceller(this), this.canceller = null)
54 | }
55 |
56 | simple() {
57 | this.options.simple = true
58 | this.options.prepare = false
59 | return this
60 | }
61 |
62 | async readable() {
63 | this.simple()
64 | this.streaming = true
65 | return this
66 | }
67 |
68 | async writable() {
69 | this.simple()
70 | this.streaming = true
71 | return this
72 | }
73 |
74 | cursor(rows = 1, fn) {
75 | this.options.simple = false
76 | if (typeof rows === 'function') {
77 | fn = rows
78 | rows = 1
79 | }
80 |
81 | this.cursorRows = rows
82 |
83 | if (typeof fn === 'function')
84 | return (this.cursorFn = fn, this)
85 |
86 | let prev
87 | return {
88 | [Symbol.asyncIterator]: () => ({
89 | next: () => {
90 | if (this.executed && !this.active)
91 | return { done: true }
92 |
93 | prev && prev()
94 | const promise = new Promise((resolve, reject) => {
95 | this.cursorFn = value => {
96 | resolve({ value, done: false })
97 | return new Promise(r => prev = r)
98 | }
99 | this.resolve = () => (this.active = false, resolve({ done: true }))
100 | this.reject = x => (this.active = false, reject(x))
101 | })
102 | this.execute()
103 | return promise
104 | },
105 | return() {
106 | prev && prev(CLOSE)
107 | return { done: true }
108 | }
109 | })
110 | }
111 | }
112 |
113 | describe() {
114 | this.options.simple = false
115 | this.onlyDescribe = this.options.prepare = true
116 | return this
117 | }
118 |
119 | stream() {
120 | throw new Error('.stream has been renamed to .forEach')
121 | }
122 |
123 | forEach(fn) {
124 | this.forEachFn = fn
125 | this.handle()
126 | return this
127 | }
128 |
129 | raw() {
130 | this.isRaw = true
131 | return this
132 | }
133 |
134 | values() {
135 | this.isRaw = 'values'
136 | return this
137 | }
138 |
139 | async handle() {
140 | !this.executed && (this.executed = true) && await 1 && this.handler(this)
141 | }
142 |
143 | execute() {
144 | this.handle()
145 | return this
146 | }
147 |
148 | then() {
149 | this.handle()
150 | return super.then.apply(this, arguments)
151 | }
152 |
153 | catch() {
154 | this.handle()
155 | return super.catch.apply(this, arguments)
156 | }
157 |
158 | finally() {
159 | this.handle()
160 | return super.finally.apply(this, arguments)
161 | }
162 | }
163 |
164 | function cachedError(xs) {
165 | if (originCache.has(xs))
166 | return originCache.get(xs)
167 |
168 | const x = Error.stackTraceLimit
169 | Error.stackTraceLimit = 4
170 | originCache.set(xs, new Error())
171 | Error.stackTraceLimit = x
172 | return originCache.get(xs)
173 | }
174 |
--------------------------------------------------------------------------------
/cf/src/queue.js:
--------------------------------------------------------------------------------
1 | export default Queue
2 |
3 | function Queue(initial = []) {
4 | let xs = initial.slice()
5 | let index = 0
6 |
7 | return {
8 | get length() {
9 | return xs.length - index
10 | },
11 | remove: (x) => {
12 | const index = xs.indexOf(x)
13 | return index === -1
14 | ? null
15 | : (xs.splice(index, 1), x)
16 | },
17 | push: (x) => (xs.push(x), x),
18 | shift: () => {
19 | const out = xs[index++]
20 |
21 | if (index === xs.length) {
22 | index = 0
23 | xs = []
24 | } else {
25 | xs[index - 1] = undefined
26 | }
27 |
28 | return out
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/cf/src/result.js:
--------------------------------------------------------------------------------
1 | export default class Result extends Array {
2 | constructor() {
3 | super()
4 | Object.defineProperties(this, {
5 | count: { value: null, writable: true },
6 | state: { value: null, writable: true },
7 | command: { value: null, writable: true },
8 | columns: { value: null, writable: true },
9 | statement: { value: null, writable: true }
10 | })
11 | }
12 |
13 | static get [Symbol.species]() {
14 | return Array
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/cf/src/subscribe.js:
--------------------------------------------------------------------------------
1 | import { Buffer } from 'node:buffer'
2 | const noop = () => { /* noop */ }
3 |
4 | export default function Subscribe(postgres, options) {
5 | const subscribers = new Map()
6 | , slot = 'postgresjs_' + Math.random().toString(36).slice(2)
7 | , state = {}
8 |
9 | let connection
10 | , stream
11 | , ended = false
12 |
13 | const sql = subscribe.sql = postgres({
14 | ...options,
15 | transform: { column: {}, value: {}, row: {} },
16 | max: 1,
17 | fetch_types: false,
18 | idle_timeout: null,
19 | max_lifetime: null,
20 | connection: {
21 | ...options.connection,
22 | replication: 'database'
23 | },
24 | onclose: async function() {
25 | if (ended)
26 | return
27 | stream = null
28 | state.pid = state.secret = undefined
29 | connected(await init(sql, slot, options.publications))
30 | subscribers.forEach(event => event.forEach(({ onsubscribe }) => onsubscribe()))
31 | },
32 | no_subscribe: true
33 | })
34 |
35 | const end = sql.end
36 | , close = sql.close
37 |
38 | sql.end = async() => {
39 | ended = true
40 | stream && (await new Promise(r => (stream.once('close', r), stream.end())))
41 | return end()
42 | }
43 |
44 | sql.close = async() => {
45 | stream && (await new Promise(r => (stream.once('close', r), stream.end())))
46 | return close()
47 | }
48 |
49 | return subscribe
50 |
51 | async function subscribe(event, fn, onsubscribe = noop, onerror = noop) {
52 | event = parseEvent(event)
53 |
54 | if (!connection)
55 | connection = init(sql, slot, options.publications)
56 |
57 | const subscriber = { fn, onsubscribe }
58 | const fns = subscribers.has(event)
59 | ? subscribers.get(event).add(subscriber)
60 | : subscribers.set(event, new Set([subscriber])).get(event)
61 |
62 | const unsubscribe = () => {
63 | fns.delete(subscriber)
64 | fns.size === 0 && subscribers.delete(event)
65 | }
66 |
67 | return connection.then(x => {
68 | connected(x)
69 | onsubscribe()
70 | stream && stream.on('error', onerror)
71 | return { unsubscribe, state, sql }
72 | })
73 | }
74 |
75 | function connected(x) {
76 | stream = x.stream
77 | state.pid = x.state.pid
78 | state.secret = x.state.secret
79 | }
80 |
81 | async function init(sql, slot, publications) {
82 | if (!publications)
83 | throw new Error('Missing publication names')
84 |
85 | const xs = await sql.unsafe(
86 | `CREATE_REPLICATION_SLOT ${ slot } TEMPORARY LOGICAL pgoutput NOEXPORT_SNAPSHOT`
87 | )
88 |
89 | const [x] = xs
90 |
91 | const stream = await sql.unsafe(
92 | `START_REPLICATION SLOT ${ slot } LOGICAL ${
93 | x.consistent_point
94 | } (proto_version '1', publication_names '${ publications }')`
95 | ).writable()
96 |
97 | const state = {
98 | lsn: Buffer.concat(x.consistent_point.split('/').map(x => Buffer.from(('00000000' + x).slice(-8), 'hex')))
99 | }
100 |
101 | stream.on('data', data)
102 | stream.on('error', error)
103 | stream.on('close', sql.close)
104 |
105 | return { stream, state: xs.state }
106 |
107 | function error(e) {
108 | console.error('Unexpected error during logical streaming - reconnecting', e) // eslint-disable-line
109 | }
110 |
111 | function data(x) {
112 | if (x[0] === 0x77) {
113 | parse(x.subarray(25), state, sql.options.parsers, handle, options.transform)
114 | } else if (x[0] === 0x6b && x[17]) {
115 | state.lsn = x.subarray(1, 9)
116 | pong()
117 | }
118 | }
119 |
120 | function handle(a, b) {
121 | const path = b.relation.schema + '.' + b.relation.table
122 | call('*', a, b)
123 | call('*:' + path, a, b)
124 | b.relation.keys.length && call('*:' + path + '=' + b.relation.keys.map(x => a[x.name]), a, b)
125 | call(b.command, a, b)
126 | call(b.command + ':' + path, a, b)
127 | b.relation.keys.length && call(b.command + ':' + path + '=' + b.relation.keys.map(x => a[x.name]), a, b)
128 | }
129 |
130 | function pong() {
131 | const x = Buffer.alloc(34)
132 | x[0] = 'r'.charCodeAt(0)
133 | x.fill(state.lsn, 1)
134 | x.writeBigInt64BE(BigInt(Date.now() - Date.UTC(2000, 0, 1)) * BigInt(1000), 25)
135 | stream.write(x)
136 | }
137 | }
138 |
139 | function call(x, a, b) {
140 | subscribers.has(x) && subscribers.get(x).forEach(({ fn }) => fn(a, b, x))
141 | }
142 | }
143 |
144 | function Time(x) {
145 | return new Date(Date.UTC(2000, 0, 1) + Number(x / BigInt(1000)))
146 | }
147 |
148 | function parse(x, state, parsers, handle, transform) {
149 | const char = (acc, [k, v]) => (acc[k.charCodeAt(0)] = v, acc)
150 |
151 | Object.entries({
152 | R: x => { // Relation
153 | let i = 1
154 | const r = state[x.readUInt32BE(i)] = {
155 | schema: x.toString('utf8', i += 4, i = x.indexOf(0, i)) || 'pg_catalog',
156 | table: x.toString('utf8', i + 1, i = x.indexOf(0, i + 1)),
157 | columns: Array(x.readUInt16BE(i += 2)),
158 | keys: []
159 | }
160 | i += 2
161 |
162 | let columnIndex = 0
163 | , column
164 |
165 | while (i < x.length) {
166 | column = r.columns[columnIndex++] = {
167 | key: x[i++],
168 | name: transform.column.from
169 | ? transform.column.from(x.toString('utf8', i, i = x.indexOf(0, i)))
170 | : x.toString('utf8', i, i = x.indexOf(0, i)),
171 | type: x.readUInt32BE(i += 1),
172 | parser: parsers[x.readUInt32BE(i)],
173 | atttypmod: x.readUInt32BE(i += 4)
174 | }
175 |
176 | column.key && r.keys.push(column)
177 | i += 4
178 | }
179 | },
180 | Y: () => { /* noop */ }, // Type
181 | O: () => { /* noop */ }, // Origin
182 | B: x => { // Begin
183 | state.date = Time(x.readBigInt64BE(9))
184 | state.lsn = x.subarray(1, 9)
185 | },
186 | I: x => { // Insert
187 | let i = 1
188 | const relation = state[x.readUInt32BE(i)]
189 | const { row } = tuples(x, relation.columns, i += 7, transform)
190 |
191 | handle(row, {
192 | command: 'insert',
193 | relation
194 | })
195 | },
196 | D: x => { // Delete
197 | let i = 1
198 | const relation = state[x.readUInt32BE(i)]
199 | i += 4
200 | const key = x[i] === 75
201 | handle(key || x[i] === 79
202 | ? tuples(x, relation.columns, i += 3, transform).row
203 | : null
204 | , {
205 | command: 'delete',
206 | relation,
207 | key
208 | })
209 | },
210 | U: x => { // Update
211 | let i = 1
212 | const relation = state[x.readUInt32BE(i)]
213 | i += 4
214 | const key = x[i] === 75
215 | const xs = key || x[i] === 79
216 | ? tuples(x, relation.columns, i += 3, transform)
217 | : null
218 |
219 | xs && (i = xs.i)
220 |
221 | const { row } = tuples(x, relation.columns, i + 3, transform)
222 |
223 | handle(row, {
224 | command: 'update',
225 | relation,
226 | key,
227 | old: xs && xs.row
228 | })
229 | },
230 | T: () => { /* noop */ }, // Truncate,
231 | C: () => { /* noop */ } // Commit
232 | }).reduce(char, {})[x[0]](x)
233 | }
234 |
235 | function tuples(x, columns, xi, transform) {
236 | let type
237 | , column
238 | , value
239 |
240 | const row = transform.raw ? new Array(columns.length) : {}
241 | for (let i = 0; i < columns.length; i++) {
242 | type = x[xi++]
243 | column = columns[i]
244 | value = type === 110 // n
245 | ? null
246 | : type === 117 // u
247 | ? undefined
248 | : column.parser === undefined
249 | ? x.toString('utf8', xi + 4, xi += 4 + x.readUInt32BE(xi))
250 | : column.parser.array === true
251 | ? column.parser(x.toString('utf8', xi + 5, xi += 4 + x.readUInt32BE(xi)))
252 | : column.parser(x.toString('utf8', xi + 4, xi += 4 + x.readUInt32BE(xi)))
253 |
254 | transform.raw
255 | ? (row[i] = transform.raw === true
256 | ? value
257 | : transform.value.from ? transform.value.from(value, column) : value)
258 | : (row[column.name] = transform.value.from
259 | ? transform.value.from(value, column)
260 | : value
261 | )
262 | }
263 |
264 | return { i: xi, row: transform.row.from ? transform.row.from(row) : row }
265 | }
266 |
267 | function parseEvent(x) {
268 | const xs = x.match(/^(\*|insert|update|delete)?:?([^.]+?\.?[^=]+)?=?(.+)?/i) || []
269 |
270 | if (!xs)
271 | throw new Error('Malformed subscribe pattern: ' + x)
272 |
273 | const [, command, path, key] = xs
274 |
275 | return (command || '*')
276 | + (path ? ':' + (path.indexOf('.') === -1 ? 'public.' + path : path) : '')
277 | + (key ? '=' + key : '')
278 | }
279 |
--------------------------------------------------------------------------------
/cf/src/types.js:
--------------------------------------------------------------------------------
1 | import { Buffer } from 'node:buffer'
2 | import { Query } from './query.js'
3 | import { Errors } from './errors.js'
4 |
5 | export const types = {
6 | string: {
7 | to: 25,
8 | from: null, // defaults to string
9 | serialize: x => '' + x
10 | },
11 | number: {
12 | to: 0,
13 | from: [21, 23, 26, 700, 701],
14 | serialize: x => '' + x,
15 | parse: x => +x
16 | },
17 | json: {
18 | to: 114,
19 | from: [114, 3802],
20 | serialize: x => JSON.stringify(x),
21 | parse: x => JSON.parse(x)
22 | },
23 | boolean: {
24 | to: 16,
25 | from: 16,
26 | serialize: x => x === true ? 't' : 'f',
27 | parse: x => x === 't'
28 | },
29 | date: {
30 | to: 1184,
31 | from: [1082, 1114, 1184],
32 | serialize: x => (x instanceof Date ? x : new Date(x)).toISOString(),
33 | parse: x => new Date(x)
34 | },
35 | bytea: {
36 | to: 17,
37 | from: 17,
38 | serialize: x => '\\x' + Buffer.from(x).toString('hex'),
39 | parse: x => Buffer.from(x.slice(2), 'hex')
40 | }
41 | }
42 |
43 | class NotTagged { then() { notTagged() } catch() { notTagged() } finally() { notTagged() }}
44 |
45 | export class Identifier extends NotTagged {
46 | constructor(value) {
47 | super()
48 | this.value = escapeIdentifier(value)
49 | }
50 | }
51 |
52 | export class Parameter extends NotTagged {
53 | constructor(value, type, array) {
54 | super()
55 | this.value = value
56 | this.type = type
57 | this.array = array
58 | }
59 | }
60 |
61 | export class Builder extends NotTagged {
62 | constructor(first, rest) {
63 | super()
64 | this.first = first
65 | this.rest = rest
66 | }
67 |
68 | build(before, parameters, types, options) {
69 | const keyword = builders.map(([x, fn]) => ({ fn, i: before.search(x) })).sort((a, b) => a.i - b.i).pop()
70 | return keyword.i === -1
71 | ? escapeIdentifiers(this.first, options)
72 | : keyword.fn(this.first, this.rest, parameters, types, options)
73 | }
74 | }
75 |
76 | export function handleValue(x, parameters, types, options) {
77 | let value = x instanceof Parameter ? x.value : x
78 | if (value === undefined) {
79 | x instanceof Parameter
80 | ? x.value = options.transform.undefined
81 | : value = x = options.transform.undefined
82 |
83 | if (value === undefined)
84 | throw Errors.generic('UNDEFINED_VALUE', 'Undefined values are not allowed')
85 | }
86 |
87 | return '$' + (types.push(
88 | x instanceof Parameter
89 | ? (parameters.push(x.value), x.array
90 | ? x.array[x.type || inferType(x.value)] || x.type || firstIsString(x.value)
91 | : x.type
92 | )
93 | : (parameters.push(x), inferType(x))
94 | ))
95 | }
96 |
97 | const defaultHandlers = typeHandlers(types)
98 |
99 | export function stringify(q, string, value, parameters, types, options) { // eslint-disable-line
100 | for (let i = 1; i < q.strings.length; i++) {
101 | string += (stringifyValue(string, value, parameters, types, options)) + q.strings[i]
102 | value = q.args[i]
103 | }
104 |
105 | return string
106 | }
107 |
108 | function stringifyValue(string, value, parameters, types, o) {
109 | return (
110 | value instanceof Builder ? value.build(string, parameters, types, o) :
111 | value instanceof Query ? fragment(value, parameters, types, o) :
112 | value instanceof Identifier ? value.value :
113 | value && value[0] instanceof Query ? value.reduce((acc, x) => acc + ' ' + fragment(x, parameters, types, o), '') :
114 | handleValue(value, parameters, types, o)
115 | )
116 | }
117 |
118 | function fragment(q, parameters, types, options) {
119 | q.fragment = true
120 | return stringify(q, q.strings[0], q.args[0], parameters, types, options)
121 | }
122 |
123 | function valuesBuilder(first, parameters, types, columns, options) {
124 | return first.map(row =>
125 | '(' + columns.map(column =>
126 | stringifyValue('values', row[column], parameters, types, options)
127 | ).join(',') + ')'
128 | ).join(',')
129 | }
130 |
131 | function values(first, rest, parameters, types, options) {
132 | const multi = Array.isArray(first[0])
133 | const columns = rest.length ? rest.flat() : Object.keys(multi ? first[0] : first)
134 | return valuesBuilder(multi ? first : [first], parameters, types, columns, options)
135 | }
136 |
137 | function select(first, rest, parameters, types, options) {
138 | typeof first === 'string' && (first = [first].concat(rest))
139 | if (Array.isArray(first))
140 | return escapeIdentifiers(first, options)
141 |
142 | let value
143 | const columns = rest.length ? rest.flat() : Object.keys(first)
144 | return columns.map(x => {
145 | value = first[x]
146 | return (
147 | value instanceof Query ? fragment(value, parameters, types, options) :
148 | value instanceof Identifier ? value.value :
149 | handleValue(value, parameters, types, options)
150 | ) + ' as ' + escapeIdentifier(options.transform.column.to ? options.transform.column.to(x) : x)
151 | }).join(',')
152 | }
153 |
154 | const builders = Object.entries({
155 | values,
156 | in: (...xs) => {
157 | const x = values(...xs)
158 | return x === '()' ? '(null)' : x
159 | },
160 | select,
161 | as: select,
162 | returning: select,
163 | '\\(': select,
164 |
165 | update(first, rest, parameters, types, options) {
166 | return (rest.length ? rest.flat() : Object.keys(first)).map(x =>
167 | escapeIdentifier(options.transform.column.to ? options.transform.column.to(x) : x) +
168 | '=' + stringifyValue('values', first[x], parameters, types, options)
169 | )
170 | },
171 |
172 | insert(first, rest, parameters, types, options) {
173 | const columns = rest.length ? rest.flat() : Object.keys(Array.isArray(first) ? first[0] : first)
174 | return '(' + escapeIdentifiers(columns, options) + ')values' +
175 | valuesBuilder(Array.isArray(first) ? first : [first], parameters, types, columns, options)
176 | }
177 | }).map(([x, fn]) => ([new RegExp('((?:^|[\\s(])' + x + '(?:$|[\\s(]))(?![\\s\\S]*\\1)', 'i'), fn]))
178 |
179 | function notTagged() {
180 | throw Errors.generic('NOT_TAGGED_CALL', 'Query not called as a tagged template literal')
181 | }
182 |
183 | export const serializers = defaultHandlers.serializers
184 | export const parsers = defaultHandlers.parsers
185 |
186 | export const END = {}
187 |
188 | function firstIsString(x) {
189 | if (Array.isArray(x))
190 | return firstIsString(x[0])
191 | return typeof x === 'string' ? 1009 : 0
192 | }
193 |
194 | export const mergeUserTypes = function(types) {
195 | const user = typeHandlers(types || {})
196 | return {
197 | serializers: Object.assign({}, serializers, user.serializers),
198 | parsers: Object.assign({}, parsers, user.parsers)
199 | }
200 | }
201 |
202 | function typeHandlers(types) {
203 | return Object.keys(types).reduce((acc, k) => {
204 | types[k].from && [].concat(types[k].from).forEach(x => acc.parsers[x] = types[k].parse)
205 | if (types[k].serialize) {
206 | acc.serializers[types[k].to] = types[k].serialize
207 | types[k].from && [].concat(types[k].from).forEach(x => acc.serializers[x] = types[k].serialize)
208 | }
209 | return acc
210 | }, { parsers: {}, serializers: {} })
211 | }
212 |
213 | function escapeIdentifiers(xs, { transform: { column } }) {
214 | return xs.map(x => escapeIdentifier(column.to ? column.to(x) : x)).join(',')
215 | }
216 |
217 | export const escapeIdentifier = function escape(str) {
218 | return '"' + str.replace(/"/g, '""').replace(/\./g, '"."') + '"'
219 | }
220 |
221 | export const inferType = function inferType(x) {
222 | return (
223 | x instanceof Parameter ? x.type :
224 | x instanceof Date ? 1184 :
225 | x instanceof Uint8Array ? 17 :
226 | (x === true || x === false) ? 16 :
227 | typeof x === 'bigint' ? 20 :
228 | Array.isArray(x) ? inferType(x[0]) :
229 | 0
230 | )
231 | }
232 |
233 | const escapeBackslash = /\\/g
234 | const escapeQuote = /"/g
235 |
236 | function arrayEscape(x) {
237 | return x
238 | .replace(escapeBackslash, '\\\\')
239 | .replace(escapeQuote, '\\"')
240 | }
241 |
242 | export const arraySerializer = function arraySerializer(xs, serializer, options, typarray) {
243 | if (Array.isArray(xs) === false)
244 | return xs
245 |
246 | if (!xs.length)
247 | return '{}'
248 |
249 | const first = xs[0]
250 | // Only _box (1020) has the ';' delimiter for arrays, all other types use the ',' delimiter
251 | const delimiter = typarray === 1020 ? ';' : ','
252 |
253 | if (Array.isArray(first) && !first.type)
254 | return '{' + xs.map(x => arraySerializer(x, serializer, options, typarray)).join(delimiter) + '}'
255 |
256 | return '{' + xs.map(x => {
257 | if (x === undefined) {
258 | x = options.transform.undefined
259 | if (x === undefined)
260 | throw Errors.generic('UNDEFINED_VALUE', 'Undefined values are not allowed')
261 | }
262 |
263 | return x === null
264 | ? 'null'
265 | : '"' + arrayEscape(serializer ? serializer(x.type ? x.value : x) : '' + x) + '"'
266 | }).join(delimiter) + '}'
267 | }
268 |
269 | const arrayParserState = {
270 | i: 0,
271 | char: null,
272 | str: '',
273 | quoted: false,
274 | last: 0
275 | }
276 |
277 | export const arrayParser = function arrayParser(x, parser, typarray) {
278 | arrayParserState.i = arrayParserState.last = 0
279 | return arrayParserLoop(arrayParserState, x, parser, typarray)
280 | }
281 |
282 | function arrayParserLoop(s, x, parser, typarray) {
283 | const xs = []
284 | // Only _box (1020) has the ';' delimiter for arrays, all other types use the ',' delimiter
285 | const delimiter = typarray === 1020 ? ';' : ','
286 | for (; s.i < x.length; s.i++) {
287 | s.char = x[s.i]
288 | if (s.quoted) {
289 | if (s.char === '\\') {
290 | s.str += x[++s.i]
291 | } else if (s.char === '"') {
292 | xs.push(parser ? parser(s.str) : s.str)
293 | s.str = ''
294 | s.quoted = x[s.i + 1] === '"'
295 | s.last = s.i + 2
296 | } else {
297 | s.str += s.char
298 | }
299 | } else if (s.char === '"') {
300 | s.quoted = true
301 | } else if (s.char === '{') {
302 | s.last = ++s.i
303 | xs.push(arrayParserLoop(s, x, parser, typarray))
304 | } else if (s.char === '}') {
305 | s.quoted = false
306 | s.last < s.i && xs.push(parser ? parser(x.slice(s.last, s.i)) : x.slice(s.last, s.i))
307 | s.last = s.i + 1
308 | break
309 | } else if (s.char === delimiter && s.p !== '}' && s.p !== '"') {
310 | xs.push(parser ? parser(x.slice(s.last, s.i)) : x.slice(s.last, s.i))
311 | s.last = s.i + 1
312 | }
313 | s.p = s.char
314 | }
315 | s.last < s.i && xs.push(parser ? parser(x.slice(s.last, s.i + 1)) : x.slice(s.last, s.i + 1))
316 | return xs
317 | }
318 |
319 | export const toCamel = x => {
320 | let str = x[0]
321 | for (let i = 1; i < x.length; i++)
322 | str += x[i] === '_' ? x[++i].toUpperCase() : x[i]
323 | return str
324 | }
325 |
326 | export const toPascal = x => {
327 | let str = x[0].toUpperCase()
328 | for (let i = 1; i < x.length; i++)
329 | str += x[i] === '_' ? x[++i].toUpperCase() : x[i]
330 | return str
331 | }
332 |
333 | export const toKebab = x => x.replace(/_/g, '-')
334 |
335 | export const fromCamel = x => x.replace(/([A-Z])/g, '_$1').toLowerCase()
336 | export const fromPascal = x => (x.slice(0, 1) + x.slice(1).replace(/([A-Z])/g, '_$1')).toLowerCase()
337 | export const fromKebab = x => x.replace(/-/g, '_')
338 |
339 | function createJsonTransform(fn) {
340 | return function jsonTransform(x, column) {
341 | return typeof x === 'object' && x !== null && (column.type === 114 || column.type === 3802)
342 | ? Array.isArray(x)
343 | ? x.map(x => jsonTransform(x, column))
344 | : Object.entries(x).reduce((acc, [k, v]) => Object.assign(acc, { [fn(k)]: jsonTransform(v, column) }), {})
345 | : x
346 | }
347 | }
348 |
349 | toCamel.column = { from: toCamel }
350 | toCamel.value = { from: createJsonTransform(toCamel) }
351 | fromCamel.column = { to: fromCamel }
352 |
353 | export const camel = { ...toCamel }
354 | camel.column.to = fromCamel
355 |
356 | toPascal.column = { from: toPascal }
357 | toPascal.value = { from: createJsonTransform(toPascal) }
358 | fromPascal.column = { to: fromPascal }
359 |
360 | export const pascal = { ...toPascal }
361 | pascal.column.to = fromPascal
362 |
363 | toKebab.column = { from: toKebab }
364 | toKebab.value = { from: createJsonTransform(toKebab) }
365 | fromKebab.column = { to: fromKebab }
366 |
367 | export const kebab = { ...toKebab }
368 | kebab.column.to = fromKebab
369 |
--------------------------------------------------------------------------------
/cf/test.js:
--------------------------------------------------------------------------------
1 | // Add your database url and run this file with the below two commands to test pages and workers
2 | // npx wrangler@latest pages dev ./cf --script-path test.js --compatibility-date=2023-06-20 --log-level=debug --compatibility-flag=nodejs_compat
3 | // npx wrangler@latest dev ./cf/test.js --compatibility-date=2023-06-20 --log-level=debug --compatibility-flag=nodejs_compat
4 |
5 | import postgres from './src/index.js'
6 | const DATABASE_URL = ''
7 |
8 | export default {
9 | async fetch() {
10 | const sql = postgres(DATABASE_URL)
11 | const rows = await sql`SELECT table_name FROM information_schema.columns`
12 | return new Response(rows.map((e) => e.table_name).join('\n'))
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/cjs/package.json:
--------------------------------------------------------------------------------
1 | {"type":"commonjs"}
--------------------------------------------------------------------------------
/cjs/src/bytes.js:
--------------------------------------------------------------------------------
1 | const size = 256
2 | let buffer = Buffer.allocUnsafe(size)
3 |
4 | const messages = 'BCcDdEFfHPpQSX'.split('').reduce((acc, x) => {
5 | const v = x.charCodeAt(0)
6 | acc[x] = () => {
7 | buffer[0] = v
8 | b.i = 5
9 | return b
10 | }
11 | return acc
12 | }, {})
13 |
14 | const b = Object.assign(reset, messages, {
15 | N: String.fromCharCode(0),
16 | i: 0,
17 | inc(x) {
18 | b.i += x
19 | return b
20 | },
21 | str(x) {
22 | const length = Buffer.byteLength(x)
23 | fit(length)
24 | b.i += buffer.write(x, b.i, length, 'utf8')
25 | return b
26 | },
27 | i16(x) {
28 | fit(2)
29 | buffer.writeUInt16BE(x, b.i)
30 | b.i += 2
31 | return b
32 | },
33 | i32(x, i) {
34 | if (i || i === 0) {
35 | buffer.writeUInt32BE(x, i)
36 | return b
37 | }
38 | fit(4)
39 | buffer.writeUInt32BE(x, b.i)
40 | b.i += 4
41 | return b
42 | },
43 | z(x) {
44 | fit(x)
45 | buffer.fill(0, b.i, b.i + x)
46 | b.i += x
47 | return b
48 | },
49 | raw(x) {
50 | buffer = Buffer.concat([buffer.subarray(0, b.i), x])
51 | b.i = buffer.length
52 | return b
53 | },
54 | end(at = 1) {
55 | buffer.writeUInt32BE(b.i - at, at)
56 | const out = buffer.subarray(0, b.i)
57 | b.i = 0
58 | buffer = Buffer.allocUnsafe(size)
59 | return out
60 | }
61 | })
62 |
63 | module.exports = b
64 |
65 | function fit(x) {
66 | if (buffer.length - b.i < x) {
67 | const prev = buffer
68 | , length = prev.length
69 |
70 | buffer = Buffer.allocUnsafe(length + (length >> 1) + x)
71 | prev.copy(buffer)
72 | }
73 | }
74 |
75 | function reset() {
76 | b.i = 0
77 | return b
78 | }
79 |
--------------------------------------------------------------------------------
/cjs/src/errors.js:
--------------------------------------------------------------------------------
1 | const PostgresError = module.exports.PostgresError = class PostgresError extends Error {
2 | constructor(x) {
3 | super(x.message)
4 | this.name = this.constructor.name
5 | Object.assign(this, x)
6 | }
7 | }
8 |
9 | const Errors = module.exports.Errors = {
10 | connection,
11 | postgres,
12 | generic,
13 | notSupported
14 | }
15 |
16 | function connection(x, options, socket) {
17 | const { host, port } = socket || options
18 | const error = Object.assign(
19 | new Error(('write ' + x + ' ' + (options.path || (host + ':' + port)))),
20 | {
21 | code: x,
22 | errno: x,
23 | address: options.path || host
24 | }, options.path ? {} : { port: port }
25 | )
26 | Error.captureStackTrace(error, connection)
27 | return error
28 | }
29 |
30 | function postgres(x) {
31 | const error = new PostgresError(x)
32 | Error.captureStackTrace(error, postgres)
33 | return error
34 | }
35 |
36 | function generic(code, message) {
37 | const error = Object.assign(new Error(code + ': ' + message), { code })
38 | Error.captureStackTrace(error, generic)
39 | return error
40 | }
41 |
42 | /* c8 ignore next 10 */
43 | function notSupported(x) {
44 | const error = Object.assign(
45 | new Error(x + ' (B) is not supported'),
46 | {
47 | code: 'MESSAGE_NOT_SUPPORTED',
48 | name: x
49 | }
50 | )
51 | Error.captureStackTrace(error, notSupported)
52 | return error
53 | }
54 |
--------------------------------------------------------------------------------
/cjs/src/large.js:
--------------------------------------------------------------------------------
1 | const Stream = require('stream')
2 |
3 | module.exports = largeObject;function largeObject(sql, oid, mode = 0x00020000 | 0x00040000) {
4 | return new Promise(async(resolve, reject) => {
5 | await sql.begin(async sql => {
6 | let finish
7 | !oid && ([{ oid }] = await sql`select lo_creat(-1) as oid`)
8 | const [{ fd }] = await sql`select lo_open(${ oid }, ${ mode }) as fd`
9 |
10 | const lo = {
11 | writable,
12 | readable,
13 | close : () => sql`select lo_close(${ fd })`.then(finish),
14 | tell : () => sql`select lo_tell64(${ fd })`,
15 | read : (x) => sql`select loread(${ fd }, ${ x }) as data`,
16 | write : (x) => sql`select lowrite(${ fd }, ${ x })`,
17 | truncate : (x) => sql`select lo_truncate64(${ fd }, ${ x })`,
18 | seek : (x, whence = 0) => sql`select lo_lseek64(${ fd }, ${ x }, ${ whence })`,
19 | size : () => sql`
20 | select
21 | lo_lseek64(${ fd }, location, 0) as position,
22 | seek.size
23 | from (
24 | select
25 | lo_lseek64($1, 0, 2) as size,
26 | tell.location
27 | from (select lo_tell64($1) as location) tell
28 | ) seek
29 | `
30 | }
31 |
32 | resolve(lo)
33 |
34 | return new Promise(async r => finish = r)
35 |
36 | async function readable({
37 | highWaterMark = 2048 * 8,
38 | start = 0,
39 | end = Infinity
40 | } = {}) {
41 | let max = end - start
42 | start && await lo.seek(start)
43 | return new Stream.Readable({
44 | highWaterMark,
45 | async read(size) {
46 | const l = size > max ? size - max : size
47 | max -= size
48 | const [{ data }] = await lo.read(l)
49 | this.push(data)
50 | if (data.length < size)
51 | this.push(null)
52 | }
53 | })
54 | }
55 |
56 | async function writable({
57 | highWaterMark = 2048 * 8,
58 | start = 0
59 | } = {}) {
60 | start && await lo.seek(start)
61 | return new Stream.Writable({
62 | highWaterMark,
63 | write(chunk, encoding, callback) {
64 | lo.write(chunk).then(() => callback(), callback)
65 | }
66 | })
67 | }
68 | }).catch(reject)
69 | })
70 | }
71 |
--------------------------------------------------------------------------------
/cjs/src/query.js:
--------------------------------------------------------------------------------
1 | const originCache = new Map()
2 | , originStackCache = new Map()
3 | , originError = Symbol('OriginError')
4 |
5 | const CLOSE = module.exports.CLOSE = {}
6 | const Query = module.exports.Query = class Query extends Promise {
7 | constructor(strings, args, handler, canceller, options = {}) {
8 | let resolve
9 | , reject
10 |
11 | super((a, b) => {
12 | resolve = a
13 | reject = b
14 | })
15 |
16 | this.tagged = Array.isArray(strings.raw)
17 | this.strings = strings
18 | this.args = args
19 | this.handler = handler
20 | this.canceller = canceller
21 | this.options = options
22 |
23 | this.state = null
24 | this.statement = null
25 |
26 | this.resolve = x => (this.active = false, resolve(x))
27 | this.reject = x => (this.active = false, reject(x))
28 |
29 | this.active = false
30 | this.cancelled = null
31 | this.executed = false
32 | this.signature = ''
33 |
34 | this[originError] = this.handler.debug
35 | ? new Error()
36 | : this.tagged && cachedError(this.strings)
37 | }
38 |
39 | get origin() {
40 | return (this.handler.debug
41 | ? this[originError].stack
42 | : this.tagged && originStackCache.has(this.strings)
43 | ? originStackCache.get(this.strings)
44 | : originStackCache.set(this.strings, this[originError].stack).get(this.strings)
45 | ) || ''
46 | }
47 |
48 | static get [Symbol.species]() {
49 | return Promise
50 | }
51 |
52 | cancel() {
53 | return this.canceller && (this.canceller(this), this.canceller = null)
54 | }
55 |
56 | simple() {
57 | this.options.simple = true
58 | this.options.prepare = false
59 | return this
60 | }
61 |
62 | async readable() {
63 | this.simple()
64 | this.streaming = true
65 | return this
66 | }
67 |
68 | async writable() {
69 | this.simple()
70 | this.streaming = true
71 | return this
72 | }
73 |
74 | cursor(rows = 1, fn) {
75 | this.options.simple = false
76 | if (typeof rows === 'function') {
77 | fn = rows
78 | rows = 1
79 | }
80 |
81 | this.cursorRows = rows
82 |
83 | if (typeof fn === 'function')
84 | return (this.cursorFn = fn, this)
85 |
86 | let prev
87 | return {
88 | [Symbol.asyncIterator]: () => ({
89 | next: () => {
90 | if (this.executed && !this.active)
91 | return { done: true }
92 |
93 | prev && prev()
94 | const promise = new Promise((resolve, reject) => {
95 | this.cursorFn = value => {
96 | resolve({ value, done: false })
97 | return new Promise(r => prev = r)
98 | }
99 | this.resolve = () => (this.active = false, resolve({ done: true }))
100 | this.reject = x => (this.active = false, reject(x))
101 | })
102 | this.execute()
103 | return promise
104 | },
105 | return() {
106 | prev && prev(CLOSE)
107 | return { done: true }
108 | }
109 | })
110 | }
111 | }
112 |
113 | describe() {
114 | this.options.simple = false
115 | this.onlyDescribe = this.options.prepare = true
116 | return this
117 | }
118 |
119 | stream() {
120 | throw new Error('.stream has been renamed to .forEach')
121 | }
122 |
123 | forEach(fn) {
124 | this.forEachFn = fn
125 | this.handle()
126 | return this
127 | }
128 |
129 | raw() {
130 | this.isRaw = true
131 | return this
132 | }
133 |
134 | values() {
135 | this.isRaw = 'values'
136 | return this
137 | }
138 |
139 | async handle() {
140 | !this.executed && (this.executed = true) && await 1 && this.handler(this)
141 | }
142 |
143 | execute() {
144 | this.handle()
145 | return this
146 | }
147 |
148 | then() {
149 | this.handle()
150 | return super.then.apply(this, arguments)
151 | }
152 |
153 | catch() {
154 | this.handle()
155 | return super.catch.apply(this, arguments)
156 | }
157 |
158 | finally() {
159 | this.handle()
160 | return super.finally.apply(this, arguments)
161 | }
162 | }
163 |
164 | function cachedError(xs) {
165 | if (originCache.has(xs))
166 | return originCache.get(xs)
167 |
168 | const x = Error.stackTraceLimit
169 | Error.stackTraceLimit = 4
170 | originCache.set(xs, new Error())
171 | Error.stackTraceLimit = x
172 | return originCache.get(xs)
173 | }
174 |
--------------------------------------------------------------------------------
/cjs/src/queue.js:
--------------------------------------------------------------------------------
1 | module.exports = Queue
2 |
3 | function Queue(initial = []) {
4 | let xs = initial.slice()
5 | let index = 0
6 |
7 | return {
8 | get length() {
9 | return xs.length - index
10 | },
11 | remove: (x) => {
12 | const index = xs.indexOf(x)
13 | return index === -1
14 | ? null
15 | : (xs.splice(index, 1), x)
16 | },
17 | push: (x) => (xs.push(x), x),
18 | shift: () => {
19 | const out = xs[index++]
20 |
21 | if (index === xs.length) {
22 | index = 0
23 | xs = []
24 | } else {
25 | xs[index - 1] = undefined
26 | }
27 |
28 | return out
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/cjs/src/result.js:
--------------------------------------------------------------------------------
1 | module.exports = class Result extends Array {
2 | constructor() {
3 | super()
4 | Object.defineProperties(this, {
5 | count: { value: null, writable: true },
6 | state: { value: null, writable: true },
7 | command: { value: null, writable: true },
8 | columns: { value: null, writable: true },
9 | statement: { value: null, writable: true }
10 | })
11 | }
12 |
13 | static get [Symbol.species]() {
14 | return Array
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/cjs/src/subscribe.js:
--------------------------------------------------------------------------------
1 | const noop = () => { /* noop */ }
2 |
3 | module.exports = Subscribe;function Subscribe(postgres, options) {
4 | const subscribers = new Map()
5 | , slot = 'postgresjs_' + Math.random().toString(36).slice(2)
6 | , state = {}
7 |
8 | let connection
9 | , stream
10 | , ended = false
11 |
12 | const sql = subscribe.sql = postgres({
13 | ...options,
14 | transform: { column: {}, value: {}, row: {} },
15 | max: 1,
16 | fetch_types: false,
17 | idle_timeout: null,
18 | max_lifetime: null,
19 | connection: {
20 | ...options.connection,
21 | replication: 'database'
22 | },
23 | onclose: async function() {
24 | if (ended)
25 | return
26 | stream = null
27 | state.pid = state.secret = undefined
28 | connected(await init(sql, slot, options.publications))
29 | subscribers.forEach(event => event.forEach(({ onsubscribe }) => onsubscribe()))
30 | },
31 | no_subscribe: true
32 | })
33 |
34 | const end = sql.end
35 | , close = sql.close
36 |
37 | sql.end = async() => {
38 | ended = true
39 | stream && (await new Promise(r => (stream.once('close', r), stream.end())))
40 | return end()
41 | }
42 |
43 | sql.close = async() => {
44 | stream && (await new Promise(r => (stream.once('close', r), stream.end())))
45 | return close()
46 | }
47 |
48 | return subscribe
49 |
50 | async function subscribe(event, fn, onsubscribe = noop, onerror = noop) {
51 | event = parseEvent(event)
52 |
53 | if (!connection)
54 | connection = init(sql, slot, options.publications)
55 |
56 | const subscriber = { fn, onsubscribe }
57 | const fns = subscribers.has(event)
58 | ? subscribers.get(event).add(subscriber)
59 | : subscribers.set(event, new Set([subscriber])).get(event)
60 |
61 | const unsubscribe = () => {
62 | fns.delete(subscriber)
63 | fns.size === 0 && subscribers.delete(event)
64 | }
65 |
66 | return connection.then(x => {
67 | connected(x)
68 | onsubscribe()
69 | stream && stream.on('error', onerror)
70 | return { unsubscribe, state, sql }
71 | })
72 | }
73 |
74 | function connected(x) {
75 | stream = x.stream
76 | state.pid = x.state.pid
77 | state.secret = x.state.secret
78 | }
79 |
80 | async function init(sql, slot, publications) {
81 | if (!publications)
82 | throw new Error('Missing publication names')
83 |
84 | const xs = await sql.unsafe(
85 | `CREATE_REPLICATION_SLOT ${ slot } TEMPORARY LOGICAL pgoutput NOEXPORT_SNAPSHOT`
86 | )
87 |
88 | const [x] = xs
89 |
90 | const stream = await sql.unsafe(
91 | `START_REPLICATION SLOT ${ slot } LOGICAL ${
92 | x.consistent_point
93 | } (proto_version '1', publication_names '${ publications }')`
94 | ).writable()
95 |
96 | const state = {
97 | lsn: Buffer.concat(x.consistent_point.split('/').map(x => Buffer.from(('00000000' + x).slice(-8), 'hex')))
98 | }
99 |
100 | stream.on('data', data)
101 | stream.on('error', error)
102 | stream.on('close', sql.close)
103 |
104 | return { stream, state: xs.state }
105 |
106 | function error(e) {
107 | console.error('Unexpected error during logical streaming - reconnecting', e) // eslint-disable-line
108 | }
109 |
110 | function data(x) {
111 | if (x[0] === 0x77) {
112 | parse(x.subarray(25), state, sql.options.parsers, handle, options.transform)
113 | } else if (x[0] === 0x6b && x[17]) {
114 | state.lsn = x.subarray(1, 9)
115 | pong()
116 | }
117 | }
118 |
119 | function handle(a, b) {
120 | const path = b.relation.schema + '.' + b.relation.table
121 | call('*', a, b)
122 | call('*:' + path, a, b)
123 | b.relation.keys.length && call('*:' + path + '=' + b.relation.keys.map(x => a[x.name]), a, b)
124 | call(b.command, a, b)
125 | call(b.command + ':' + path, a, b)
126 | b.relation.keys.length && call(b.command + ':' + path + '=' + b.relation.keys.map(x => a[x.name]), a, b)
127 | }
128 |
129 | function pong() {
130 | const x = Buffer.alloc(34)
131 | x[0] = 'r'.charCodeAt(0)
132 | x.fill(state.lsn, 1)
133 | x.writeBigInt64BE(BigInt(Date.now() - Date.UTC(2000, 0, 1)) * BigInt(1000), 25)
134 | stream.write(x)
135 | }
136 | }
137 |
138 | function call(x, a, b) {
139 | subscribers.has(x) && subscribers.get(x).forEach(({ fn }) => fn(a, b, x))
140 | }
141 | }
142 |
143 | function Time(x) {
144 | return new Date(Date.UTC(2000, 0, 1) + Number(x / BigInt(1000)))
145 | }
146 |
147 | function parse(x, state, parsers, handle, transform) {
148 | const char = (acc, [k, v]) => (acc[k.charCodeAt(0)] = v, acc)
149 |
150 | Object.entries({
151 | R: x => { // Relation
152 | let i = 1
153 | const r = state[x.readUInt32BE(i)] = {
154 | schema: x.toString('utf8', i += 4, i = x.indexOf(0, i)) || 'pg_catalog',
155 | table: x.toString('utf8', i + 1, i = x.indexOf(0, i + 1)),
156 | columns: Array(x.readUInt16BE(i += 2)),
157 | keys: []
158 | }
159 | i += 2
160 |
161 | let columnIndex = 0
162 | , column
163 |
164 | while (i < x.length) {
165 | column = r.columns[columnIndex++] = {
166 | key: x[i++],
167 | name: transform.column.from
168 | ? transform.column.from(x.toString('utf8', i, i = x.indexOf(0, i)))
169 | : x.toString('utf8', i, i = x.indexOf(0, i)),
170 | type: x.readUInt32BE(i += 1),
171 | parser: parsers[x.readUInt32BE(i)],
172 | atttypmod: x.readUInt32BE(i += 4)
173 | }
174 |
175 | column.key && r.keys.push(column)
176 | i += 4
177 | }
178 | },
179 | Y: () => { /* noop */ }, // Type
180 | O: () => { /* noop */ }, // Origin
181 | B: x => { // Begin
182 | state.date = Time(x.readBigInt64BE(9))
183 | state.lsn = x.subarray(1, 9)
184 | },
185 | I: x => { // Insert
186 | let i = 1
187 | const relation = state[x.readUInt32BE(i)]
188 | const { row } = tuples(x, relation.columns, i += 7, transform)
189 |
190 | handle(row, {
191 | command: 'insert',
192 | relation
193 | })
194 | },
195 | D: x => { // Delete
196 | let i = 1
197 | const relation = state[x.readUInt32BE(i)]
198 | i += 4
199 | const key = x[i] === 75
200 | handle(key || x[i] === 79
201 | ? tuples(x, relation.columns, i += 3, transform).row
202 | : null
203 | , {
204 | command: 'delete',
205 | relation,
206 | key
207 | })
208 | },
209 | U: x => { // Update
210 | let i = 1
211 | const relation = state[x.readUInt32BE(i)]
212 | i += 4
213 | const key = x[i] === 75
214 | const xs = key || x[i] === 79
215 | ? tuples(x, relation.columns, i += 3, transform)
216 | : null
217 |
218 | xs && (i = xs.i)
219 |
220 | const { row } = tuples(x, relation.columns, i + 3, transform)
221 |
222 | handle(row, {
223 | command: 'update',
224 | relation,
225 | key,
226 | old: xs && xs.row
227 | })
228 | },
229 | T: () => { /* noop */ }, // Truncate,
230 | C: () => { /* noop */ } // Commit
231 | }).reduce(char, {})[x[0]](x)
232 | }
233 |
234 | function tuples(x, columns, xi, transform) {
235 | let type
236 | , column
237 | , value
238 |
239 | const row = transform.raw ? new Array(columns.length) : {}
240 | for (let i = 0; i < columns.length; i++) {
241 | type = x[xi++]
242 | column = columns[i]
243 | value = type === 110 // n
244 | ? null
245 | : type === 117 // u
246 | ? undefined
247 | : column.parser === undefined
248 | ? x.toString('utf8', xi + 4, xi += 4 + x.readUInt32BE(xi))
249 | : column.parser.array === true
250 | ? column.parser(x.toString('utf8', xi + 5, xi += 4 + x.readUInt32BE(xi)))
251 | : column.parser(x.toString('utf8', xi + 4, xi += 4 + x.readUInt32BE(xi)))
252 |
253 | transform.raw
254 | ? (row[i] = transform.raw === true
255 | ? value
256 | : transform.value.from ? transform.value.from(value, column) : value)
257 | : (row[column.name] = transform.value.from
258 | ? transform.value.from(value, column)
259 | : value
260 | )
261 | }
262 |
263 | return { i: xi, row: transform.row.from ? transform.row.from(row) : row }
264 | }
265 |
266 | function parseEvent(x) {
267 | const xs = x.match(/^(\*|insert|update|delete)?:?([^.]+?\.?[^=]+)?=?(.+)?/i) || []
268 |
269 | if (!xs)
270 | throw new Error('Malformed subscribe pattern: ' + x)
271 |
272 | const [, command, path, key] = xs
273 |
274 | return (command || '*')
275 | + (path ? ':' + (path.indexOf('.') === -1 ? 'public.' + path : path) : '')
276 | + (key ? '=' + key : '')
277 | }
278 |
--------------------------------------------------------------------------------
/cjs/src/types.js:
--------------------------------------------------------------------------------
1 | const { Query } = require('./query.js')
2 | const { Errors } = require('./errors.js')
3 |
4 | const types = module.exports.types = {
5 | string: {
6 | to: 25,
7 | from: null, // defaults to string
8 | serialize: x => '' + x
9 | },
10 | number: {
11 | to: 0,
12 | from: [21, 23, 26, 700, 701],
13 | serialize: x => '' + x,
14 | parse: x => +x
15 | },
16 | json: {
17 | to: 114,
18 | from: [114, 3802],
19 | serialize: x => JSON.stringify(x),
20 | parse: x => JSON.parse(x)
21 | },
22 | boolean: {
23 | to: 16,
24 | from: 16,
25 | serialize: x => x === true ? 't' : 'f',
26 | parse: x => x === 't'
27 | },
28 | date: {
29 | to: 1184,
30 | from: [1082, 1114, 1184],
31 | serialize: x => (x instanceof Date ? x : new Date(x)).toISOString(),
32 | parse: x => new Date(x)
33 | },
34 | bytea: {
35 | to: 17,
36 | from: 17,
37 | serialize: x => '\\x' + Buffer.from(x).toString('hex'),
38 | parse: x => Buffer.from(x.slice(2), 'hex')
39 | }
40 | }
41 |
42 | class NotTagged { then() { notTagged() } catch() { notTagged() } finally() { notTagged() }}
43 |
44 | const Identifier = module.exports.Identifier = class Identifier extends NotTagged {
45 | constructor(value) {
46 | super()
47 | this.value = escapeIdentifier(value)
48 | }
49 | }
50 |
51 | const Parameter = module.exports.Parameter = class Parameter extends NotTagged {
52 | constructor(value, type, array) {
53 | super()
54 | this.value = value
55 | this.type = type
56 | this.array = array
57 | }
58 | }
59 |
60 | const Builder = module.exports.Builder = class Builder extends NotTagged {
61 | constructor(first, rest) {
62 | super()
63 | this.first = first
64 | this.rest = rest
65 | }
66 |
67 | build(before, parameters, types, options) {
68 | const keyword = builders.map(([x, fn]) => ({ fn, i: before.search(x) })).sort((a, b) => a.i - b.i).pop()
69 | return keyword.i === -1
70 | ? escapeIdentifiers(this.first, options)
71 | : keyword.fn(this.first, this.rest, parameters, types, options)
72 | }
73 | }
74 |
75 | module.exports.handleValue = handleValue;function handleValue(x, parameters, types, options) {
76 | let value = x instanceof Parameter ? x.value : x
77 | if (value === undefined) {
78 | x instanceof Parameter
79 | ? x.value = options.transform.undefined
80 | : value = x = options.transform.undefined
81 |
82 | if (value === undefined)
83 | throw Errors.generic('UNDEFINED_VALUE', 'Undefined values are not allowed')
84 | }
85 |
86 | return '$' + (types.push(
87 | x instanceof Parameter
88 | ? (parameters.push(x.value), x.array
89 | ? x.array[x.type || inferType(x.value)] || x.type || firstIsString(x.value)
90 | : x.type
91 | )
92 | : (parameters.push(x), inferType(x))
93 | ))
94 | }
95 |
96 | const defaultHandlers = typeHandlers(types)
97 |
98 | module.exports.stringify = stringify;function stringify(q, string, value, parameters, types, options) { // eslint-disable-line
99 | for (let i = 1; i < q.strings.length; i++) {
100 | string += (stringifyValue(string, value, parameters, types, options)) + q.strings[i]
101 | value = q.args[i]
102 | }
103 |
104 | return string
105 | }
106 |
107 | function stringifyValue(string, value, parameters, types, o) {
108 | return (
109 | value instanceof Builder ? value.build(string, parameters, types, o) :
110 | value instanceof Query ? fragment(value, parameters, types, o) :
111 | value instanceof Identifier ? value.value :
112 | value && value[0] instanceof Query ? value.reduce((acc, x) => acc + ' ' + fragment(x, parameters, types, o), '') :
113 | handleValue(value, parameters, types, o)
114 | )
115 | }
116 |
117 | function fragment(q, parameters, types, options) {
118 | q.fragment = true
119 | return stringify(q, q.strings[0], q.args[0], parameters, types, options)
120 | }
121 |
122 | function valuesBuilder(first, parameters, types, columns, options) {
123 | return first.map(row =>
124 | '(' + columns.map(column =>
125 | stringifyValue('values', row[column], parameters, types, options)
126 | ).join(',') + ')'
127 | ).join(',')
128 | }
129 |
130 | function values(first, rest, parameters, types, options) {
131 | const multi = Array.isArray(first[0])
132 | const columns = rest.length ? rest.flat() : Object.keys(multi ? first[0] : first)
133 | return valuesBuilder(multi ? first : [first], parameters, types, columns, options)
134 | }
135 |
136 | function select(first, rest, parameters, types, options) {
137 | typeof first === 'string' && (first = [first].concat(rest))
138 | if (Array.isArray(first))
139 | return escapeIdentifiers(first, options)
140 |
141 | let value
142 | const columns = rest.length ? rest.flat() : Object.keys(first)
143 | return columns.map(x => {
144 | value = first[x]
145 | return (
146 | value instanceof Query ? fragment(value, parameters, types, options) :
147 | value instanceof Identifier ? value.value :
148 | handleValue(value, parameters, types, options)
149 | ) + ' as ' + escapeIdentifier(options.transform.column.to ? options.transform.column.to(x) : x)
150 | }).join(',')
151 | }
152 |
153 | const builders = Object.entries({
154 | values,
155 | in: (...xs) => {
156 | const x = values(...xs)
157 | return x === '()' ? '(null)' : x
158 | },
159 | select,
160 | as: select,
161 | returning: select,
162 | '\\(': select,
163 |
164 | update(first, rest, parameters, types, options) {
165 | return (rest.length ? rest.flat() : Object.keys(first)).map(x =>
166 | escapeIdentifier(options.transform.column.to ? options.transform.column.to(x) : x) +
167 | '=' + stringifyValue('values', first[x], parameters, types, options)
168 | )
169 | },
170 |
171 | insert(first, rest, parameters, types, options) {
172 | const columns = rest.length ? rest.flat() : Object.keys(Array.isArray(first) ? first[0] : first)
173 | return '(' + escapeIdentifiers(columns, options) + ')values' +
174 | valuesBuilder(Array.isArray(first) ? first : [first], parameters, types, columns, options)
175 | }
176 | }).map(([x, fn]) => ([new RegExp('((?:^|[\\s(])' + x + '(?:$|[\\s(]))(?![\\s\\S]*\\1)', 'i'), fn]))
177 |
178 | function notTagged() {
179 | throw Errors.generic('NOT_TAGGED_CALL', 'Query not called as a tagged template literal')
180 | }
181 |
182 | const serializers = module.exports.serializers = defaultHandlers.serializers
183 | const parsers = module.exports.parsers = defaultHandlers.parsers
184 |
185 | const END = module.exports.END = {}
186 |
187 | function firstIsString(x) {
188 | if (Array.isArray(x))
189 | return firstIsString(x[0])
190 | return typeof x === 'string' ? 1009 : 0
191 | }
192 |
193 | const mergeUserTypes = module.exports.mergeUserTypes = function(types) {
194 | const user = typeHandlers(types || {})
195 | return {
196 | serializers: Object.assign({}, serializers, user.serializers),
197 | parsers: Object.assign({}, parsers, user.parsers)
198 | }
199 | }
200 |
201 | function typeHandlers(types) {
202 | return Object.keys(types).reduce((acc, k) => {
203 | types[k].from && [].concat(types[k].from).forEach(x => acc.parsers[x] = types[k].parse)
204 | if (types[k].serialize) {
205 | acc.serializers[types[k].to] = types[k].serialize
206 | types[k].from && [].concat(types[k].from).forEach(x => acc.serializers[x] = types[k].serialize)
207 | }
208 | return acc
209 | }, { parsers: {}, serializers: {} })
210 | }
211 |
212 | function escapeIdentifiers(xs, { transform: { column } }) {
213 | return xs.map(x => escapeIdentifier(column.to ? column.to(x) : x)).join(',')
214 | }
215 |
216 | const escapeIdentifier = module.exports.escapeIdentifier = function escape(str) {
217 | return '"' + str.replace(/"/g, '""').replace(/\./g, '"."') + '"'
218 | }
219 |
220 | const inferType = module.exports.inferType = function inferType(x) {
221 | return (
222 | x instanceof Parameter ? x.type :
223 | x instanceof Date ? 1184 :
224 | x instanceof Uint8Array ? 17 :
225 | (x === true || x === false) ? 16 :
226 | typeof x === 'bigint' ? 20 :
227 | Array.isArray(x) ? inferType(x[0]) :
228 | 0
229 | )
230 | }
231 |
232 | const escapeBackslash = /\\/g
233 | const escapeQuote = /"/g
234 |
235 | function arrayEscape(x) {
236 | return x
237 | .replace(escapeBackslash, '\\\\')
238 | .replace(escapeQuote, '\\"')
239 | }
240 |
241 | const arraySerializer = module.exports.arraySerializer = function arraySerializer(xs, serializer, options, typarray) {
242 | if (Array.isArray(xs) === false)
243 | return xs
244 |
245 | if (!xs.length)
246 | return '{}'
247 |
248 | const first = xs[0]
249 | // Only _box (1020) has the ';' delimiter for arrays, all other types use the ',' delimiter
250 | const delimiter = typarray === 1020 ? ';' : ','
251 |
252 | if (Array.isArray(first) && !first.type)
253 | return '{' + xs.map(x => arraySerializer(x, serializer, options, typarray)).join(delimiter) + '}'
254 |
255 | return '{' + xs.map(x => {
256 | if (x === undefined) {
257 | x = options.transform.undefined
258 | if (x === undefined)
259 | throw Errors.generic('UNDEFINED_VALUE', 'Undefined values are not allowed')
260 | }
261 |
262 | return x === null
263 | ? 'null'
264 | : '"' + arrayEscape(serializer ? serializer(x.type ? x.value : x) : '' + x) + '"'
265 | }).join(delimiter) + '}'
266 | }
267 |
268 | const arrayParserState = {
269 | i: 0,
270 | char: null,
271 | str: '',
272 | quoted: false,
273 | last: 0
274 | }
275 |
276 | const arrayParser = module.exports.arrayParser = function arrayParser(x, parser, typarray) {
277 | arrayParserState.i = arrayParserState.last = 0
278 | return arrayParserLoop(arrayParserState, x, parser, typarray)
279 | }
280 |
281 | function arrayParserLoop(s, x, parser, typarray) {
282 | const xs = []
283 | // Only _box (1020) has the ';' delimiter for arrays, all other types use the ',' delimiter
284 | const delimiter = typarray === 1020 ? ';' : ','
285 | for (; s.i < x.length; s.i++) {
286 | s.char = x[s.i]
287 | if (s.quoted) {
288 | if (s.char === '\\') {
289 | s.str += x[++s.i]
290 | } else if (s.char === '"') {
291 | xs.push(parser ? parser(s.str) : s.str)
292 | s.str = ''
293 | s.quoted = x[s.i + 1] === '"'
294 | s.last = s.i + 2
295 | } else {
296 | s.str += s.char
297 | }
298 | } else if (s.char === '"') {
299 | s.quoted = true
300 | } else if (s.char === '{') {
301 | s.last = ++s.i
302 | xs.push(arrayParserLoop(s, x, parser, typarray))
303 | } else if (s.char === '}') {
304 | s.quoted = false
305 | s.last < s.i && xs.push(parser ? parser(x.slice(s.last, s.i)) : x.slice(s.last, s.i))
306 | s.last = s.i + 1
307 | break
308 | } else if (s.char === delimiter && s.p !== '}' && s.p !== '"') {
309 | xs.push(parser ? parser(x.slice(s.last, s.i)) : x.slice(s.last, s.i))
310 | s.last = s.i + 1
311 | }
312 | s.p = s.char
313 | }
314 | s.last < s.i && xs.push(parser ? parser(x.slice(s.last, s.i + 1)) : x.slice(s.last, s.i + 1))
315 | return xs
316 | }
317 |
318 | const toCamel = module.exports.toCamel = x => {
319 | let str = x[0]
320 | for (let i = 1; i < x.length; i++)
321 | str += x[i] === '_' ? x[++i].toUpperCase() : x[i]
322 | return str
323 | }
324 |
325 | const toPascal = module.exports.toPascal = x => {
326 | let str = x[0].toUpperCase()
327 | for (let i = 1; i < x.length; i++)
328 | str += x[i] === '_' ? x[++i].toUpperCase() : x[i]
329 | return str
330 | }
331 |
332 | const toKebab = module.exports.toKebab = x => x.replace(/_/g, '-')
333 |
334 | const fromCamel = module.exports.fromCamel = x => x.replace(/([A-Z])/g, '_$1').toLowerCase()
335 | const fromPascal = module.exports.fromPascal = x => (x.slice(0, 1) + x.slice(1).replace(/([A-Z])/g, '_$1')).toLowerCase()
336 | const fromKebab = module.exports.fromKebab = x => x.replace(/-/g, '_')
337 |
338 | function createJsonTransform(fn) {
339 | return function jsonTransform(x, column) {
340 | return typeof x === 'object' && x !== null && (column.type === 114 || column.type === 3802)
341 | ? Array.isArray(x)
342 | ? x.map(x => jsonTransform(x, column))
343 | : Object.entries(x).reduce((acc, [k, v]) => Object.assign(acc, { [fn(k)]: jsonTransform(v, column) }), {})
344 | : x
345 | }
346 | }
347 |
348 | toCamel.column = { from: toCamel }
349 | toCamel.value = { from: createJsonTransform(toCamel) }
350 | fromCamel.column = { to: fromCamel }
351 |
352 | const camel = module.exports.camel = { ...toCamel }
353 | camel.column.to = fromCamel
354 |
355 | toPascal.column = { from: toPascal }
356 | toPascal.value = { from: createJsonTransform(toPascal) }
357 | fromPascal.column = { to: fromPascal }
358 |
359 | const pascal = module.exports.pascal = { ...toPascal }
360 | pascal.column.to = fromPascal
361 |
362 | toKebab.column = { from: toKebab }
363 | toKebab.value = { from: createJsonTransform(toKebab) }
364 | fromKebab.column = { to: fromKebab }
365 |
366 | const kebab = module.exports.kebab = { ...toKebab }
367 | kebab.column.to = fromKebab
368 |
--------------------------------------------------------------------------------
/cjs/tests/bootstrap.js:
--------------------------------------------------------------------------------
1 | const { spawnSync } = require('child_process')
2 |
3 | exec('dropdb', ['postgres_js_test'])
4 |
5 | exec('psql', ['-c', 'alter system set ssl=on'])
6 | exec('psql', ['-c', 'drop user postgres_js_test'])
7 | exec('psql', ['-c', 'create user postgres_js_test'])
8 | exec('psql', ['-c', 'alter system set password_encryption=md5'])
9 | exec('psql', ['-c', 'select pg_reload_conf()'])
10 | exec('psql', ['-c', 'drop user if exists postgres_js_test_md5'])
11 | exec('psql', ['-c', 'create user postgres_js_test_md5 with password \'postgres_js_test_md5\''])
12 | exec('psql', ['-c', 'alter system set password_encryption=\'scram-sha-256\''])
13 | exec('psql', ['-c', 'select pg_reload_conf()'])
14 | exec('psql', ['-c', 'drop user if exists postgres_js_test_scram'])
15 | exec('psql', ['-c', 'create user postgres_js_test_scram with password \'postgres_js_test_scram\''])
16 |
17 | exec('createdb', ['postgres_js_test'])
18 | exec('psql', ['-c', 'grant all on database postgres_js_test to postgres_js_test'])
19 | exec('psql', ['-c', 'alter database postgres_js_test owner to postgres_js_test'])
20 |
21 | module.exports.exec = exec;function exec(cmd, args) {
22 | const { stderr } = spawnSync(cmd, args, { stdio: 'pipe', encoding: 'utf8' })
23 | if (stderr && !stderr.includes('already exists') && !stderr.includes('does not exist'))
24 | throw stderr
25 | }
26 |
27 | async function execAsync(cmd, args) { // eslint-disable-line
28 | let stderr = ''
29 | const cp = await spawn(cmd, args, { stdio: 'pipe', encoding: 'utf8' }) // eslint-disable-line
30 | cp.stderr.on('data', x => stderr += x)
31 | await new Promise(x => cp.on('exit', x))
32 | if (stderr && !stderr.includes('already exists') && !stderr.includes('does not exist'))
33 | throw new Error(stderr)
34 | }
35 |
--------------------------------------------------------------------------------
/cjs/tests/copy.csv:
--------------------------------------------------------------------------------
1 | 1 2 3
2 | 4 5 6
3 |
--------------------------------------------------------------------------------
/cjs/tests/pg_hba.conf:
--------------------------------------------------------------------------------
1 | local all all trust
2 | host all postgres samehost trust
3 | host postgres_js_test postgres_js_test samehost trust
4 | host postgres_js_test postgres_js_test_md5 samehost md5
5 | host postgres_js_test postgres_js_test_scram samehost scram-sha-256
6 |
--------------------------------------------------------------------------------
/cjs/tests/select-param.sql:
--------------------------------------------------------------------------------
1 | select $1 as x
2 |
--------------------------------------------------------------------------------
/cjs/tests/select.sql:
--------------------------------------------------------------------------------
1 | select 1 as x
2 |
--------------------------------------------------------------------------------
/cjs/tests/test.js:
--------------------------------------------------------------------------------
1 | /* eslint no-console: 0 */
2 |
3 | const util = require('util')
4 |
5 | let done = 0
6 | let only = false
7 | let ignored = 0
8 | let failed = false
9 | let promise = Promise.resolve()
10 | const tests = {}
11 | , ignore = {}
12 |
13 | const nt = module.exports.nt = () => ignored++
14 | const ot = module.exports.ot = (...rest) => (only = true, test(true, ...rest))
15 | const t = module.exports.t = (...rest) => test(false, ...rest)
16 | t.timeout = 5
17 |
18 | async function test(o, name, options, fn) {
19 | typeof options !== 'object' && (fn = options, options = {})
20 | const line = new Error().stack.split('\n')[3].match(':([0-9]+):')[1]
21 |
22 | await 1
23 |
24 | if (only && !o)
25 | return
26 |
27 | tests[line] = { fn, line, name }
28 | promise = promise.then(() => Promise.race([
29 | new Promise((resolve, reject) =>
30 | fn.timer = setTimeout(() => reject('Timed out'), (options.timeout || t.timeout) * 1000)
31 | ),
32 | failed
33 | ? (ignored++, ignore)
34 | : fn()
35 | ]))
36 | .then(async x => {
37 | clearTimeout(fn.timer)
38 | if (x === ignore)
39 | return
40 |
41 | if (!Array.isArray(x))
42 | throw new Error('Test should return result array')
43 |
44 | const [expected, got] = await Promise.all(x)
45 | if (expected !== got) {
46 | failed = true
47 | throw new Error(util.inspect(expected) + ' != ' + util.inspect(got))
48 | }
49 |
50 | tests[line].succeeded = true
51 | process.stdout.write('✅')
52 | })
53 | .catch(err => {
54 | tests[line].failed = failed = true
55 | tests[line].error = err instanceof Error ? err : new Error(util.inspect(err))
56 | })
57 | .then(() => {
58 | ++done === Object.keys(tests).length && exit()
59 | })
60 | }
61 |
62 | function exit() {
63 | let success = true
64 | Object.values(tests).every((x) => {
65 | if (x.succeeded)
66 | return true
67 |
68 | success = false
69 | x.cleanup
70 | ? console.error('⛔️', x.name + ' at line', x.line, 'cleanup failed', '\n', util.inspect(x.cleanup))
71 | : console.error('⛔️', x.name + ' at line', x.line, x.failed
72 | ? 'failed'
73 | : 'never finished', x.error ? '\n' + util.inspect(x.error) : ''
74 | )
75 | })
76 |
77 | only
78 | ? console.error('⚠️', 'Not all tests were run')
79 | : ignored
80 | ? console.error('⚠️', ignored, 'ignored test' + (ignored === 1 ? '' : 's', '\n'))
81 | : success
82 | ? console.log('🎉')
83 | : console.error('⚠️', 'Not good')
84 |
85 | !process.exitCode && (!success || only || ignored) && (process.exitCode = 1)
86 | }
87 |
88 |
--------------------------------------------------------------------------------
/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/porsager/postgres/9b92b65da6a5121545581a6dd5de859c2a70177f/demo.gif
--------------------------------------------------------------------------------
/deno/mod.js:
--------------------------------------------------------------------------------
1 | // @deno-types="./types/index.d.ts"
2 | export { default } from './src/index.js'
3 |
--------------------------------------------------------------------------------
/deno/package.json:
--------------------------------------------------------------------------------
1 | {"type":"commonjs"}
--------------------------------------------------------------------------------
/deno/polyfills.js:
--------------------------------------------------------------------------------
1 | /* global Deno */
2 |
3 | import { Buffer } from 'https://deno.land/std@0.132.0/node/buffer.ts'
4 | import { isIP } from 'https://deno.land/std@0.132.0/node/net.ts'
5 |
6 | const events = () => ({ data: [], error: [], drain: [], connect: [], secureConnect: [], close: [] })
7 |
8 | class Socket {
9 | constructor() {
10 | return createSocket()
11 | }
12 | }
13 |
14 | function createSocket() {
15 | let paused
16 | , resume
17 | , keepAlive
18 |
19 | const socket = {
20 | error,
21 | success,
22 | readyState: 'open',
23 | setKeepAlive: x => {
24 | keepAlive = x
25 | socket.raw && socket.raw.setKeepAlive && socket.raw.setKeepAlive(x)
26 | },
27 | connect: (port, hostname) => {
28 | socket.raw = null
29 | socket.readyState = 'connecting'
30 | typeof port === 'string'
31 | ? Deno.connect({ transport: 'unix', path: socket.path = port }).then(success, error)
32 | : Deno.connect({ transport: 'tcp', port: socket.port = port, hostname: socket.hostname = hostname || 'localhost' }).then(success, error) // eslint-disable-line
33 | return socket
34 | },
35 | pause: () => {
36 | paused = new Promise(r => resume = r)
37 | },
38 | resume: () => {
39 | resume && resume()
40 | paused = null
41 | },
42 | isPaused: () => !!paused,
43 | removeAllListeners: () => socket.events = events(),
44 | events: events(),
45 | raw: null,
46 | on: (x, fn) => socket.events[x].push(fn),
47 | once: (x, fn) => {
48 | if (x === 'data')
49 | socket.break = true
50 | const e = socket.events[x]
51 | e.push(once)
52 | once.once = fn
53 | function once(...args) {
54 | fn(...args)
55 | e.indexOf(once) > -1 && e.splice(e.indexOf(once), 1)
56 | }
57 | },
58 | removeListener: (x, fn) => {
59 | socket.events[x] = socket.events[x].filter(x => x !== fn && x.once !== fn)
60 | },
61 | write: (x, cb) => {
62 | socket.raw.write(x).then(l => {
63 | l < x.length
64 | ? socket.write(x.slice(l), cb)
65 | : (cb && cb(null))
66 | }).catch(err => {
67 | cb && cb()
68 | call(socket.events.error, err)
69 | })
70 | return false
71 | },
72 | destroy: () => close(),
73 | end: (x) => {
74 | x && socket.write(x)
75 | close()
76 | }
77 | }
78 |
79 | return socket
80 |
81 | async function success(raw) {
82 | if (socket.readyState !== 'connecting')
83 | return raw.close()
84 |
85 | const encrypted = socket.encrypted
86 | socket.raw = raw
87 | keepAlive != null && raw.setKeepAlive && raw.setKeepAlive(keepAlive)
88 | socket.readyState = 'open'
89 | socket.encrypted
90 | ? call(socket.events.secureConnect)
91 | : call(socket.events.connect)
92 |
93 | const b = new Uint8Array(1024)
94 | let result
95 |
96 | try {
97 | while ((result = socket.readyState === 'open' && await raw.read(b))) {
98 | call(socket.events.data, Buffer.from(b.subarray(0, result)))
99 | if (!encrypted && socket.break && (socket.break = false, b[0] === 83))
100 | return socket.break = false
101 | paused && await paused
102 | }
103 | } catch (e) {
104 | if (e instanceof Deno.errors.BadResource === false)
105 | error(e)
106 | }
107 |
108 | if (!socket.encrypted || encrypted)
109 | closed()
110 | }
111 |
112 | function close() {
113 | try {
114 | socket.raw && socket.raw.close()
115 | } catch (e) {
116 | if (e instanceof Deno.errors.BadResource === false)
117 | call(socket.events.error, e)
118 | }
119 | }
120 |
121 | function closed() {
122 | if (socket.readyState === 'closed')
123 | return
124 |
125 | socket.break = socket.encrypted = false
126 | socket.readyState = 'closed'
127 | call(socket.events.close)
128 | }
129 |
130 | function error(err) {
131 | call(socket.events.error, err)
132 | socket.raw
133 | ? close()
134 | : closed()
135 | }
136 |
137 | function call(xs, x) {
138 | xs.slice().forEach(fn => fn(x))
139 | }
140 | }
141 |
142 | export const net = {
143 | isIP,
144 | createServer() {
145 | const server = {
146 | address() {
147 | return { port: 9876 }
148 | },
149 | async listen() {
150 | server.raw = Deno.listen({ port: 9876, transport: 'tcp' })
151 | for await (const conn of server.raw)
152 | setTimeout(() => conn.close(), 500)
153 | },
154 | close() {
155 | server.raw.close()
156 | }
157 | }
158 | return server
159 | },
160 | Socket
161 | }
162 |
163 | export const tls = {
164 | connect({ socket, ...options }) {
165 | socket.encrypted = true
166 | socket.readyState = 'connecting'
167 | Deno.startTls(socket.raw, { hostname: socket.hostname, ...options })
168 | .then(socket.success, socket.error)
169 | socket.raw = null
170 | return socket
171 | }
172 | }
173 |
174 | let ids = 1
175 | const tasks = new Set()
176 | export const setImmediate = fn => {
177 | const id = ids++
178 | tasks.add(id)
179 | queueMicrotask(() => {
180 | if (tasks.has(id)) {
181 | fn()
182 | tasks.delete(id)
183 | }
184 | })
185 | return id
186 | }
187 |
188 | export const clearImmediate = id => tasks.delete(id)
189 |
190 |
--------------------------------------------------------------------------------
/deno/src/bytes.js:
--------------------------------------------------------------------------------
1 | import { Buffer } from 'https://deno.land/std@0.132.0/node/buffer.ts'
2 | const size = 256
3 | let buffer = Buffer.allocUnsafe(size)
4 |
5 | const messages = 'BCcDdEFfHPpQSX'.split('').reduce((acc, x) => {
6 | const v = x.charCodeAt(0)
7 | acc[x] = () => {
8 | buffer[0] = v
9 | b.i = 5
10 | return b
11 | }
12 | return acc
13 | }, {})
14 |
15 | const b = Object.assign(reset, messages, {
16 | N: String.fromCharCode(0),
17 | i: 0,
18 | inc(x) {
19 | b.i += x
20 | return b
21 | },
22 | str(x) {
23 | const length = Buffer.byteLength(x)
24 | fit(length)
25 | b.i += buffer.write(x, b.i, length, 'utf8')
26 | return b
27 | },
28 | i16(x) {
29 | fit(2)
30 | buffer.writeUInt16BE(x, b.i)
31 | b.i += 2
32 | return b
33 | },
34 | i32(x, i) {
35 | if (i || i === 0) {
36 | buffer.writeUInt32BE(x, i)
37 | return b
38 | }
39 | fit(4)
40 | buffer.writeUInt32BE(x, b.i)
41 | b.i += 4
42 | return b
43 | },
44 | z(x) {
45 | fit(x)
46 | buffer.fill(0, b.i, b.i + x)
47 | b.i += x
48 | return b
49 | },
50 | raw(x) {
51 | buffer = Buffer.concat([buffer.subarray(0, b.i), x])
52 | b.i = buffer.length
53 | return b
54 | },
55 | end(at = 1) {
56 | buffer.writeUInt32BE(b.i - at, at)
57 | const out = buffer.subarray(0, b.i)
58 | b.i = 0
59 | buffer = Buffer.allocUnsafe(size)
60 | return out
61 | }
62 | })
63 |
64 | export default b
65 |
66 | function fit(x) {
67 | if (buffer.length - b.i < x) {
68 | const prev = buffer
69 | , length = prev.length
70 |
71 | buffer = Buffer.allocUnsafe(length + (length >> 1) + x)
72 | prev.copy(buffer)
73 | }
74 | }
75 |
76 | function reset() {
77 | b.i = 0
78 | return b
79 | }
80 |
--------------------------------------------------------------------------------
/deno/src/errors.js:
--------------------------------------------------------------------------------
1 | export class PostgresError extends Error {
2 | constructor(x) {
3 | super(x.message)
4 | this.name = this.constructor.name
5 | Object.assign(this, x)
6 | }
7 | }
8 |
9 | export const Errors = {
10 | connection,
11 | postgres,
12 | generic,
13 | notSupported
14 | }
15 |
16 | function connection(x, options, socket) {
17 | const { host, port } = socket || options
18 | const error = Object.assign(
19 | new Error(('write ' + x + ' ' + (options.path || (host + ':' + port)))),
20 | {
21 | code: x,
22 | errno: x,
23 | address: options.path || host
24 | }, options.path ? {} : { port: port }
25 | )
26 | Error.captureStackTrace(error, connection)
27 | return error
28 | }
29 |
30 | function postgres(x) {
31 | const error = new PostgresError(x)
32 | Error.captureStackTrace(error, postgres)
33 | return error
34 | }
35 |
36 | function generic(code, message) {
37 | const error = Object.assign(new Error(code + ': ' + message), { code })
38 | Error.captureStackTrace(error, generic)
39 | return error
40 | }
41 |
42 | /* c8 ignore next 10 */
43 | function notSupported(x) {
44 | const error = Object.assign(
45 | new Error(x + ' (B) is not supported'),
46 | {
47 | code: 'MESSAGE_NOT_SUPPORTED',
48 | name: x
49 | }
50 | )
51 | Error.captureStackTrace(error, notSupported)
52 | return error
53 | }
54 |
--------------------------------------------------------------------------------
/deno/src/large.js:
--------------------------------------------------------------------------------
1 | import Stream from 'https://deno.land/std@0.132.0/node/stream.ts'
2 |
3 | export default function largeObject(sql, oid, mode = 0x00020000 | 0x00040000) {
4 | return new Promise(async(resolve, reject) => {
5 | await sql.begin(async sql => {
6 | let finish
7 | !oid && ([{ oid }] = await sql`select lo_creat(-1) as oid`)
8 | const [{ fd }] = await sql`select lo_open(${ oid }, ${ mode }) as fd`
9 |
10 | const lo = {
11 | writable,
12 | readable,
13 | close : () => sql`select lo_close(${ fd })`.then(finish),
14 | tell : () => sql`select lo_tell64(${ fd })`,
15 | read : (x) => sql`select loread(${ fd }, ${ x }) as data`,
16 | write : (x) => sql`select lowrite(${ fd }, ${ x })`,
17 | truncate : (x) => sql`select lo_truncate64(${ fd }, ${ x })`,
18 | seek : (x, whence = 0) => sql`select lo_lseek64(${ fd }, ${ x }, ${ whence })`,
19 | size : () => sql`
20 | select
21 | lo_lseek64(${ fd }, location, 0) as position,
22 | seek.size
23 | from (
24 | select
25 | lo_lseek64($1, 0, 2) as size,
26 | tell.location
27 | from (select lo_tell64($1) as location) tell
28 | ) seek
29 | `
30 | }
31 |
32 | resolve(lo)
33 |
34 | return new Promise(async r => finish = r)
35 |
36 | async function readable({
37 | highWaterMark = 2048 * 8,
38 | start = 0,
39 | end = Infinity
40 | } = {}) {
41 | let max = end - start
42 | start && await lo.seek(start)
43 | return new Stream.Readable({
44 | highWaterMark,
45 | async read(size) {
46 | const l = size > max ? size - max : size
47 | max -= size
48 | const [{ data }] = await lo.read(l)
49 | this.push(data)
50 | if (data.length < size)
51 | this.push(null)
52 | }
53 | })
54 | }
55 |
56 | async function writable({
57 | highWaterMark = 2048 * 8,
58 | start = 0
59 | } = {}) {
60 | start && await lo.seek(start)
61 | return new Stream.Writable({
62 | highWaterMark,
63 | write(chunk, encoding, callback) {
64 | lo.write(chunk).then(() => callback(), callback)
65 | }
66 | })
67 | }
68 | }).catch(reject)
69 | })
70 | }
71 |
--------------------------------------------------------------------------------
/deno/src/query.js:
--------------------------------------------------------------------------------
1 | const originCache = new Map()
2 | , originStackCache = new Map()
3 | , originError = Symbol('OriginError')
4 |
5 | export const CLOSE = {}
6 | export class Query extends Promise {
7 | constructor(strings, args, handler, canceller, options = {}) {
8 | let resolve
9 | , reject
10 |
11 | super((a, b) => {
12 | resolve = a
13 | reject = b
14 | })
15 |
16 | this.tagged = Array.isArray(strings.raw)
17 | this.strings = strings
18 | this.args = args
19 | this.handler = handler
20 | this.canceller = canceller
21 | this.options = options
22 |
23 | this.state = null
24 | this.statement = null
25 |
26 | this.resolve = x => (this.active = false, resolve(x))
27 | this.reject = x => (this.active = false, reject(x))
28 |
29 | this.active = false
30 | this.cancelled = null
31 | this.executed = false
32 | this.signature = ''
33 |
34 | this[originError] = this.handler.debug
35 | ? new Error()
36 | : this.tagged && cachedError(this.strings)
37 | }
38 |
39 | get origin() {
40 | return (this.handler.debug
41 | ? this[originError].stack
42 | : this.tagged && originStackCache.has(this.strings)
43 | ? originStackCache.get(this.strings)
44 | : originStackCache.set(this.strings, this[originError].stack).get(this.strings)
45 | ) || ''
46 | }
47 |
48 | static get [Symbol.species]() {
49 | return Promise
50 | }
51 |
52 | cancel() {
53 | return this.canceller && (this.canceller(this), this.canceller = null)
54 | }
55 |
56 | simple() {
57 | this.options.simple = true
58 | this.options.prepare = false
59 | return this
60 | }
61 |
62 | async readable() {
63 | this.simple()
64 | this.streaming = true
65 | return this
66 | }
67 |
68 | async writable() {
69 | this.simple()
70 | this.streaming = true
71 | return this
72 | }
73 |
74 | cursor(rows = 1, fn) {
75 | this.options.simple = false
76 | if (typeof rows === 'function') {
77 | fn = rows
78 | rows = 1
79 | }
80 |
81 | this.cursorRows = rows
82 |
83 | if (typeof fn === 'function')
84 | return (this.cursorFn = fn, this)
85 |
86 | let prev
87 | return {
88 | [Symbol.asyncIterator]: () => ({
89 | next: () => {
90 | if (this.executed && !this.active)
91 | return { done: true }
92 |
93 | prev && prev()
94 | const promise = new Promise((resolve, reject) => {
95 | this.cursorFn = value => {
96 | resolve({ value, done: false })
97 | return new Promise(r => prev = r)
98 | }
99 | this.resolve = () => (this.active = false, resolve({ done: true }))
100 | this.reject = x => (this.active = false, reject(x))
101 | })
102 | this.execute()
103 | return promise
104 | },
105 | return() {
106 | prev && prev(CLOSE)
107 | return { done: true }
108 | }
109 | })
110 | }
111 | }
112 |
113 | describe() {
114 | this.options.simple = false
115 | this.onlyDescribe = this.options.prepare = true
116 | return this
117 | }
118 |
119 | stream() {
120 | throw new Error('.stream has been renamed to .forEach')
121 | }
122 |
123 | forEach(fn) {
124 | this.forEachFn = fn
125 | this.handle()
126 | return this
127 | }
128 |
129 | raw() {
130 | this.isRaw = true
131 | return this
132 | }
133 |
134 | values() {
135 | this.isRaw = 'values'
136 | return this
137 | }
138 |
139 | async handle() {
140 | !this.executed && (this.executed = true) && await 1 && this.handler(this)
141 | }
142 |
143 | execute() {
144 | this.handle()
145 | return this
146 | }
147 |
148 | then() {
149 | this.handle()
150 | return super.then.apply(this, arguments)
151 | }
152 |
153 | catch() {
154 | this.handle()
155 | return super.catch.apply(this, arguments)
156 | }
157 |
158 | finally() {
159 | this.handle()
160 | return super.finally.apply(this, arguments)
161 | }
162 | }
163 |
164 | function cachedError(xs) {
165 | if (originCache.has(xs))
166 | return originCache.get(xs)
167 |
168 | const x = Error.stackTraceLimit
169 | Error.stackTraceLimit = 4
170 | originCache.set(xs, new Error())
171 | Error.stackTraceLimit = x
172 | return originCache.get(xs)
173 | }
174 |
--------------------------------------------------------------------------------
/deno/src/queue.js:
--------------------------------------------------------------------------------
1 | export default Queue
2 |
3 | function Queue(initial = []) {
4 | let xs = initial.slice()
5 | let index = 0
6 |
7 | return {
8 | get length() {
9 | return xs.length - index
10 | },
11 | remove: (x) => {
12 | const index = xs.indexOf(x)
13 | return index === -1
14 | ? null
15 | : (xs.splice(index, 1), x)
16 | },
17 | push: (x) => (xs.push(x), x),
18 | shift: () => {
19 | const out = xs[index++]
20 |
21 | if (index === xs.length) {
22 | index = 0
23 | xs = []
24 | } else {
25 | xs[index - 1] = undefined
26 | }
27 |
28 | return out
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/deno/src/result.js:
--------------------------------------------------------------------------------
1 | export default class Result extends Array {
2 | constructor() {
3 | super()
4 | Object.defineProperties(this, {
5 | count: { value: null, writable: true },
6 | state: { value: null, writable: true },
7 | command: { value: null, writable: true },
8 | columns: { value: null, writable: true },
9 | statement: { value: null, writable: true }
10 | })
11 | }
12 |
13 | static get [Symbol.species]() {
14 | return Array
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/deno/src/subscribe.js:
--------------------------------------------------------------------------------
1 | import { Buffer } from 'https://deno.land/std@0.132.0/node/buffer.ts'
2 | const noop = () => { /* noop */ }
3 |
4 | export default function Subscribe(postgres, options) {
5 | const subscribers = new Map()
6 | , slot = 'postgresjs_' + Math.random().toString(36).slice(2)
7 | , state = {}
8 |
9 | let connection
10 | , stream
11 | , ended = false
12 |
13 | const sql = subscribe.sql = postgres({
14 | ...options,
15 | transform: { column: {}, value: {}, row: {} },
16 | max: 1,
17 | fetch_types: false,
18 | idle_timeout: null,
19 | max_lifetime: null,
20 | connection: {
21 | ...options.connection,
22 | replication: 'database'
23 | },
24 | onclose: async function() {
25 | if (ended)
26 | return
27 | stream = null
28 | state.pid = state.secret = undefined
29 | connected(await init(sql, slot, options.publications))
30 | subscribers.forEach(event => event.forEach(({ onsubscribe }) => onsubscribe()))
31 | },
32 | no_subscribe: true
33 | })
34 |
35 | const end = sql.end
36 | , close = sql.close
37 |
38 | sql.end = async() => {
39 | ended = true
40 | stream && (await new Promise(r => (stream.once('close', r), stream.end())))
41 | return end()
42 | }
43 |
44 | sql.close = async() => {
45 | stream && (await new Promise(r => (stream.once('close', r), stream.end())))
46 | return close()
47 | }
48 |
49 | return subscribe
50 |
51 | async function subscribe(event, fn, onsubscribe = noop, onerror = noop) {
52 | event = parseEvent(event)
53 |
54 | if (!connection)
55 | connection = init(sql, slot, options.publications)
56 |
57 | const subscriber = { fn, onsubscribe }
58 | const fns = subscribers.has(event)
59 | ? subscribers.get(event).add(subscriber)
60 | : subscribers.set(event, new Set([subscriber])).get(event)
61 |
62 | const unsubscribe = () => {
63 | fns.delete(subscriber)
64 | fns.size === 0 && subscribers.delete(event)
65 | }
66 |
67 | return connection.then(x => {
68 | connected(x)
69 | onsubscribe()
70 | stream && stream.on('error', onerror)
71 | return { unsubscribe, state, sql }
72 | })
73 | }
74 |
75 | function connected(x) {
76 | stream = x.stream
77 | state.pid = x.state.pid
78 | state.secret = x.state.secret
79 | }
80 |
81 | async function init(sql, slot, publications) {
82 | if (!publications)
83 | throw new Error('Missing publication names')
84 |
85 | const xs = await sql.unsafe(
86 | `CREATE_REPLICATION_SLOT ${ slot } TEMPORARY LOGICAL pgoutput NOEXPORT_SNAPSHOT`
87 | )
88 |
89 | const [x] = xs
90 |
91 | const stream = await sql.unsafe(
92 | `START_REPLICATION SLOT ${ slot } LOGICAL ${
93 | x.consistent_point
94 | } (proto_version '1', publication_names '${ publications }')`
95 | ).writable()
96 |
97 | const state = {
98 | lsn: Buffer.concat(x.consistent_point.split('/').map(x => Buffer.from(('00000000' + x).slice(-8), 'hex')))
99 | }
100 |
101 | stream.on('data', data)
102 | stream.on('error', error)
103 | stream.on('close', sql.close)
104 |
105 | return { stream, state: xs.state }
106 |
107 | function error(e) {
108 | console.error('Unexpected error during logical streaming - reconnecting', e) // eslint-disable-line
109 | }
110 |
111 | function data(x) {
112 | if (x[0] === 0x77) {
113 | parse(x.subarray(25), state, sql.options.parsers, handle, options.transform)
114 | } else if (x[0] === 0x6b && x[17]) {
115 | state.lsn = x.subarray(1, 9)
116 | pong()
117 | }
118 | }
119 |
120 | function handle(a, b) {
121 | const path = b.relation.schema + '.' + b.relation.table
122 | call('*', a, b)
123 | call('*:' + path, a, b)
124 | b.relation.keys.length && call('*:' + path + '=' + b.relation.keys.map(x => a[x.name]), a, b)
125 | call(b.command, a, b)
126 | call(b.command + ':' + path, a, b)
127 | b.relation.keys.length && call(b.command + ':' + path + '=' + b.relation.keys.map(x => a[x.name]), a, b)
128 | }
129 |
130 | function pong() {
131 | const x = Buffer.alloc(34)
132 | x[0] = 'r'.charCodeAt(0)
133 | x.fill(state.lsn, 1)
134 | x.writeBigInt64BE(BigInt(Date.now() - Date.UTC(2000, 0, 1)) * BigInt(1000), 25)
135 | stream.write(x)
136 | }
137 | }
138 |
139 | function call(x, a, b) {
140 | subscribers.has(x) && subscribers.get(x).forEach(({ fn }) => fn(a, b, x))
141 | }
142 | }
143 |
144 | function Time(x) {
145 | return new Date(Date.UTC(2000, 0, 1) + Number(x / BigInt(1000)))
146 | }
147 |
148 | function parse(x, state, parsers, handle, transform) {
149 | const char = (acc, [k, v]) => (acc[k.charCodeAt(0)] = v, acc)
150 |
151 | Object.entries({
152 | R: x => { // Relation
153 | let i = 1
154 | const r = state[x.readUInt32BE(i)] = {
155 | schema: x.toString('utf8', i += 4, i = x.indexOf(0, i)) || 'pg_catalog',
156 | table: x.toString('utf8', i + 1, i = x.indexOf(0, i + 1)),
157 | columns: Array(x.readUInt16BE(i += 2)),
158 | keys: []
159 | }
160 | i += 2
161 |
162 | let columnIndex = 0
163 | , column
164 |
165 | while (i < x.length) {
166 | column = r.columns[columnIndex++] = {
167 | key: x[i++],
168 | name: transform.column.from
169 | ? transform.column.from(x.toString('utf8', i, i = x.indexOf(0, i)))
170 | : x.toString('utf8', i, i = x.indexOf(0, i)),
171 | type: x.readUInt32BE(i += 1),
172 | parser: parsers[x.readUInt32BE(i)],
173 | atttypmod: x.readUInt32BE(i += 4)
174 | }
175 |
176 | column.key && r.keys.push(column)
177 | i += 4
178 | }
179 | },
180 | Y: () => { /* noop */ }, // Type
181 | O: () => { /* noop */ }, // Origin
182 | B: x => { // Begin
183 | state.date = Time(x.readBigInt64BE(9))
184 | state.lsn = x.subarray(1, 9)
185 | },
186 | I: x => { // Insert
187 | let i = 1
188 | const relation = state[x.readUInt32BE(i)]
189 | const { row } = tuples(x, relation.columns, i += 7, transform)
190 |
191 | handle(row, {
192 | command: 'insert',
193 | relation
194 | })
195 | },
196 | D: x => { // Delete
197 | let i = 1
198 | const relation = state[x.readUInt32BE(i)]
199 | i += 4
200 | const key = x[i] === 75
201 | handle(key || x[i] === 79
202 | ? tuples(x, relation.columns, i += 3, transform).row
203 | : null
204 | , {
205 | command: 'delete',
206 | relation,
207 | key
208 | })
209 | },
210 | U: x => { // Update
211 | let i = 1
212 | const relation = state[x.readUInt32BE(i)]
213 | i += 4
214 | const key = x[i] === 75
215 | const xs = key || x[i] === 79
216 | ? tuples(x, relation.columns, i += 3, transform)
217 | : null
218 |
219 | xs && (i = xs.i)
220 |
221 | const { row } = tuples(x, relation.columns, i + 3, transform)
222 |
223 | handle(row, {
224 | command: 'update',
225 | relation,
226 | key,
227 | old: xs && xs.row
228 | })
229 | },
230 | T: () => { /* noop */ }, // Truncate,
231 | C: () => { /* noop */ } // Commit
232 | }).reduce(char, {})[x[0]](x)
233 | }
234 |
235 | function tuples(x, columns, xi, transform) {
236 | let type
237 | , column
238 | , value
239 |
240 | const row = transform.raw ? new Array(columns.length) : {}
241 | for (let i = 0; i < columns.length; i++) {
242 | type = x[xi++]
243 | column = columns[i]
244 | value = type === 110 // n
245 | ? null
246 | : type === 117 // u
247 | ? undefined
248 | : column.parser === undefined
249 | ? x.toString('utf8', xi + 4, xi += 4 + x.readUInt32BE(xi))
250 | : column.parser.array === true
251 | ? column.parser(x.toString('utf8', xi + 5, xi += 4 + x.readUInt32BE(xi)))
252 | : column.parser(x.toString('utf8', xi + 4, xi += 4 + x.readUInt32BE(xi)))
253 |
254 | transform.raw
255 | ? (row[i] = transform.raw === true
256 | ? value
257 | : transform.value.from ? transform.value.from(value, column) : value)
258 | : (row[column.name] = transform.value.from
259 | ? transform.value.from(value, column)
260 | : value
261 | )
262 | }
263 |
264 | return { i: xi, row: transform.row.from ? transform.row.from(row) : row }
265 | }
266 |
267 | function parseEvent(x) {
268 | const xs = x.match(/^(\*|insert|update|delete)?:?([^.]+?\.?[^=]+)?=?(.+)?/i) || []
269 |
270 | if (!xs)
271 | throw new Error('Malformed subscribe pattern: ' + x)
272 |
273 | const [, command, path, key] = xs
274 |
275 | return (command || '*')
276 | + (path ? ':' + (path.indexOf('.') === -1 ? 'public.' + path : path) : '')
277 | + (key ? '=' + key : '')
278 | }
279 |
--------------------------------------------------------------------------------
/deno/src/types.js:
--------------------------------------------------------------------------------
1 | import { Buffer } from 'https://deno.land/std@0.132.0/node/buffer.ts'
2 | import { Query } from './query.js'
3 | import { Errors } from './errors.js'
4 |
5 | export const types = {
6 | string: {
7 | to: 25,
8 | from: null, // defaults to string
9 | serialize: x => '' + x
10 | },
11 | number: {
12 | to: 0,
13 | from: [21, 23, 26, 700, 701],
14 | serialize: x => '' + x,
15 | parse: x => +x
16 | },
17 | json: {
18 | to: 114,
19 | from: [114, 3802],
20 | serialize: x => JSON.stringify(x),
21 | parse: x => JSON.parse(x)
22 | },
23 | boolean: {
24 | to: 16,
25 | from: 16,
26 | serialize: x => x === true ? 't' : 'f',
27 | parse: x => x === 't'
28 | },
29 | date: {
30 | to: 1184,
31 | from: [1082, 1114, 1184],
32 | serialize: x => (x instanceof Date ? x : new Date(x)).toISOString(),
33 | parse: x => new Date(x)
34 | },
35 | bytea: {
36 | to: 17,
37 | from: 17,
38 | serialize: x => '\\x' + Buffer.from(x).toString('hex'),
39 | parse: x => Buffer.from(x.slice(2), 'hex')
40 | }
41 | }
42 |
43 | class NotTagged { then() { notTagged() } catch() { notTagged() } finally() { notTagged() }}
44 |
45 | export class Identifier extends NotTagged {
46 | constructor(value) {
47 | super()
48 | this.value = escapeIdentifier(value)
49 | }
50 | }
51 |
52 | export class Parameter extends NotTagged {
53 | constructor(value, type, array) {
54 | super()
55 | this.value = value
56 | this.type = type
57 | this.array = array
58 | }
59 | }
60 |
61 | export class Builder extends NotTagged {
62 | constructor(first, rest) {
63 | super()
64 | this.first = first
65 | this.rest = rest
66 | }
67 |
68 | build(before, parameters, types, options) {
69 | const keyword = builders.map(([x, fn]) => ({ fn, i: before.search(x) })).sort((a, b) => a.i - b.i).pop()
70 | return keyword.i === -1
71 | ? escapeIdentifiers(this.first, options)
72 | : keyword.fn(this.first, this.rest, parameters, types, options)
73 | }
74 | }
75 |
76 | export function handleValue(x, parameters, types, options) {
77 | let value = x instanceof Parameter ? x.value : x
78 | if (value === undefined) {
79 | x instanceof Parameter
80 | ? x.value = options.transform.undefined
81 | : value = x = options.transform.undefined
82 |
83 | if (value === undefined)
84 | throw Errors.generic('UNDEFINED_VALUE', 'Undefined values are not allowed')
85 | }
86 |
87 | return '$' + (types.push(
88 | x instanceof Parameter
89 | ? (parameters.push(x.value), x.array
90 | ? x.array[x.type || inferType(x.value)] || x.type || firstIsString(x.value)
91 | : x.type
92 | )
93 | : (parameters.push(x), inferType(x))
94 | ))
95 | }
96 |
97 | const defaultHandlers = typeHandlers(types)
98 |
99 | export function stringify(q, string, value, parameters, types, options) { // eslint-disable-line
100 | for (let i = 1; i < q.strings.length; i++) {
101 | string += (stringifyValue(string, value, parameters, types, options)) + q.strings[i]
102 | value = q.args[i]
103 | }
104 |
105 | return string
106 | }
107 |
108 | function stringifyValue(string, value, parameters, types, o) {
109 | return (
110 | value instanceof Builder ? value.build(string, parameters, types, o) :
111 | value instanceof Query ? fragment(value, parameters, types, o) :
112 | value instanceof Identifier ? value.value :
113 | value && value[0] instanceof Query ? value.reduce((acc, x) => acc + ' ' + fragment(x, parameters, types, o), '') :
114 | handleValue(value, parameters, types, o)
115 | )
116 | }
117 |
118 | function fragment(q, parameters, types, options) {
119 | q.fragment = true
120 | return stringify(q, q.strings[0], q.args[0], parameters, types, options)
121 | }
122 |
123 | function valuesBuilder(first, parameters, types, columns, options) {
124 | return first.map(row =>
125 | '(' + columns.map(column =>
126 | stringifyValue('values', row[column], parameters, types, options)
127 | ).join(',') + ')'
128 | ).join(',')
129 | }
130 |
131 | function values(first, rest, parameters, types, options) {
132 | const multi = Array.isArray(first[0])
133 | const columns = rest.length ? rest.flat() : Object.keys(multi ? first[0] : first)
134 | return valuesBuilder(multi ? first : [first], parameters, types, columns, options)
135 | }
136 |
137 | function select(first, rest, parameters, types, options) {
138 | typeof first === 'string' && (first = [first].concat(rest))
139 | if (Array.isArray(first))
140 | return escapeIdentifiers(first, options)
141 |
142 | let value
143 | const columns = rest.length ? rest.flat() : Object.keys(first)
144 | return columns.map(x => {
145 | value = first[x]
146 | return (
147 | value instanceof Query ? fragment(value, parameters, types, options) :
148 | value instanceof Identifier ? value.value :
149 | handleValue(value, parameters, types, options)
150 | ) + ' as ' + escapeIdentifier(options.transform.column.to ? options.transform.column.to(x) : x)
151 | }).join(',')
152 | }
153 |
154 | const builders = Object.entries({
155 | values,
156 | in: (...xs) => {
157 | const x = values(...xs)
158 | return x === '()' ? '(null)' : x
159 | },
160 | select,
161 | as: select,
162 | returning: select,
163 | '\\(': select,
164 |
165 | update(first, rest, parameters, types, options) {
166 | return (rest.length ? rest.flat() : Object.keys(first)).map(x =>
167 | escapeIdentifier(options.transform.column.to ? options.transform.column.to(x) : x) +
168 | '=' + stringifyValue('values', first[x], parameters, types, options)
169 | )
170 | },
171 |
172 | insert(first, rest, parameters, types, options) {
173 | const columns = rest.length ? rest.flat() : Object.keys(Array.isArray(first) ? first[0] : first)
174 | return '(' + escapeIdentifiers(columns, options) + ')values' +
175 | valuesBuilder(Array.isArray(first) ? first : [first], parameters, types, columns, options)
176 | }
177 | }).map(([x, fn]) => ([new RegExp('((?:^|[\\s(])' + x + '(?:$|[\\s(]))(?![\\s\\S]*\\1)', 'i'), fn]))
178 |
179 | function notTagged() {
180 | throw Errors.generic('NOT_TAGGED_CALL', 'Query not called as a tagged template literal')
181 | }
182 |
183 | export const serializers = defaultHandlers.serializers
184 | export const parsers = defaultHandlers.parsers
185 |
186 | export const END = {}
187 |
188 | function firstIsString(x) {
189 | if (Array.isArray(x))
190 | return firstIsString(x[0])
191 | return typeof x === 'string' ? 1009 : 0
192 | }
193 |
194 | export const mergeUserTypes = function(types) {
195 | const user = typeHandlers(types || {})
196 | return {
197 | serializers: Object.assign({}, serializers, user.serializers),
198 | parsers: Object.assign({}, parsers, user.parsers)
199 | }
200 | }
201 |
202 | function typeHandlers(types) {
203 | return Object.keys(types).reduce((acc, k) => {
204 | types[k].from && [].concat(types[k].from).forEach(x => acc.parsers[x] = types[k].parse)
205 | if (types[k].serialize) {
206 | acc.serializers[types[k].to] = types[k].serialize
207 | types[k].from && [].concat(types[k].from).forEach(x => acc.serializers[x] = types[k].serialize)
208 | }
209 | return acc
210 | }, { parsers: {}, serializers: {} })
211 | }
212 |
213 | function escapeIdentifiers(xs, { transform: { column } }) {
214 | return xs.map(x => escapeIdentifier(column.to ? column.to(x) : x)).join(',')
215 | }
216 |
217 | export const escapeIdentifier = function escape(str) {
218 | return '"' + str.replace(/"/g, '""').replace(/\./g, '"."') + '"'
219 | }
220 |
221 | export const inferType = function inferType(x) {
222 | return (
223 | x instanceof Parameter ? x.type :
224 | x instanceof Date ? 1184 :
225 | x instanceof Uint8Array ? 17 :
226 | (x === true || x === false) ? 16 :
227 | typeof x === 'bigint' ? 20 :
228 | Array.isArray(x) ? inferType(x[0]) :
229 | 0
230 | )
231 | }
232 |
233 | const escapeBackslash = /\\/g
234 | const escapeQuote = /"/g
235 |
236 | function arrayEscape(x) {
237 | return x
238 | .replace(escapeBackslash, '\\\\')
239 | .replace(escapeQuote, '\\"')
240 | }
241 |
242 | export const arraySerializer = function arraySerializer(xs, serializer, options, typarray) {
243 | if (Array.isArray(xs) === false)
244 | return xs
245 |
246 | if (!xs.length)
247 | return '{}'
248 |
249 | const first = xs[0]
250 | // Only _box (1020) has the ';' delimiter for arrays, all other types use the ',' delimiter
251 | const delimiter = typarray === 1020 ? ';' : ','
252 |
253 | if (Array.isArray(first) && !first.type)
254 | return '{' + xs.map(x => arraySerializer(x, serializer, options, typarray)).join(delimiter) + '}'
255 |
256 | return '{' + xs.map(x => {
257 | if (x === undefined) {
258 | x = options.transform.undefined
259 | if (x === undefined)
260 | throw Errors.generic('UNDEFINED_VALUE', 'Undefined values are not allowed')
261 | }
262 |
263 | return x === null
264 | ? 'null'
265 | : '"' + arrayEscape(serializer ? serializer(x.type ? x.value : x) : '' + x) + '"'
266 | }).join(delimiter) + '}'
267 | }
268 |
269 | const arrayParserState = {
270 | i: 0,
271 | char: null,
272 | str: '',
273 | quoted: false,
274 | last: 0
275 | }
276 |
277 | export const arrayParser = function arrayParser(x, parser, typarray) {
278 | arrayParserState.i = arrayParserState.last = 0
279 | return arrayParserLoop(arrayParserState, x, parser, typarray)
280 | }
281 |
282 | function arrayParserLoop(s, x, parser, typarray) {
283 | const xs = []
284 | // Only _box (1020) has the ';' delimiter for arrays, all other types use the ',' delimiter
285 | const delimiter = typarray === 1020 ? ';' : ','
286 | for (; s.i < x.length; s.i++) {
287 | s.char = x[s.i]
288 | if (s.quoted) {
289 | if (s.char === '\\') {
290 | s.str += x[++s.i]
291 | } else if (s.char === '"') {
292 | xs.push(parser ? parser(s.str) : s.str)
293 | s.str = ''
294 | s.quoted = x[s.i + 1] === '"'
295 | s.last = s.i + 2
296 | } else {
297 | s.str += s.char
298 | }
299 | } else if (s.char === '"') {
300 | s.quoted = true
301 | } else if (s.char === '{') {
302 | s.last = ++s.i
303 | xs.push(arrayParserLoop(s, x, parser, typarray))
304 | } else if (s.char === '}') {
305 | s.quoted = false
306 | s.last < s.i && xs.push(parser ? parser(x.slice(s.last, s.i)) : x.slice(s.last, s.i))
307 | s.last = s.i + 1
308 | break
309 | } else if (s.char === delimiter && s.p !== '}' && s.p !== '"') {
310 | xs.push(parser ? parser(x.slice(s.last, s.i)) : x.slice(s.last, s.i))
311 | s.last = s.i + 1
312 | }
313 | s.p = s.char
314 | }
315 | s.last < s.i && xs.push(parser ? parser(x.slice(s.last, s.i + 1)) : x.slice(s.last, s.i + 1))
316 | return xs
317 | }
318 |
319 | export const toCamel = x => {
320 | let str = x[0]
321 | for (let i = 1; i < x.length; i++)
322 | str += x[i] === '_' ? x[++i].toUpperCase() : x[i]
323 | return str
324 | }
325 |
326 | export const toPascal = x => {
327 | let str = x[0].toUpperCase()
328 | for (let i = 1; i < x.length; i++)
329 | str += x[i] === '_' ? x[++i].toUpperCase() : x[i]
330 | return str
331 | }
332 |
333 | export const toKebab = x => x.replace(/_/g, '-')
334 |
335 | export const fromCamel = x => x.replace(/([A-Z])/g, '_$1').toLowerCase()
336 | export const fromPascal = x => (x.slice(0, 1) + x.slice(1).replace(/([A-Z])/g, '_$1')).toLowerCase()
337 | export const fromKebab = x => x.replace(/-/g, '_')
338 |
339 | function createJsonTransform(fn) {
340 | return function jsonTransform(x, column) {
341 | return typeof x === 'object' && x !== null && (column.type === 114 || column.type === 3802)
342 | ? Array.isArray(x)
343 | ? x.map(x => jsonTransform(x, column))
344 | : Object.entries(x).reduce((acc, [k, v]) => Object.assign(acc, { [fn(k)]: jsonTransform(v, column) }), {})
345 | : x
346 | }
347 | }
348 |
349 | toCamel.column = { from: toCamel }
350 | toCamel.value = { from: createJsonTransform(toCamel) }
351 | fromCamel.column = { to: fromCamel }
352 |
353 | export const camel = { ...toCamel }
354 | camel.column.to = fromCamel
355 |
356 | toPascal.column = { from: toPascal }
357 | toPascal.value = { from: createJsonTransform(toPascal) }
358 | fromPascal.column = { to: fromPascal }
359 |
360 | export const pascal = { ...toPascal }
361 | pascal.column.to = fromPascal
362 |
363 | toKebab.column = { from: toKebab }
364 | toKebab.value = { from: createJsonTransform(toKebab) }
365 | fromKebab.column = { to: fromKebab }
366 |
367 | export const kebab = { ...toKebab }
368 | kebab.column.to = fromKebab
369 |
--------------------------------------------------------------------------------
/deno/tests/bootstrap.js:
--------------------------------------------------------------------------------
1 | import { spawn } from 'https://deno.land/std@0.132.0/node/child_process.ts'
2 |
3 | await exec('dropdb', ['postgres_js_test'])
4 |
5 | await exec('psql', ['-c', 'alter system set ssl=on'])
6 | await exec('psql', ['-c', 'drop user postgres_js_test'])
7 | await exec('psql', ['-c', 'create user postgres_js_test'])
8 | await exec('psql', ['-c', 'alter system set password_encryption=md5'])
9 | await exec('psql', ['-c', 'select pg_reload_conf()'])
10 | await exec('psql', ['-c', 'drop user if exists postgres_js_test_md5'])
11 | await exec('psql', ['-c', 'create user postgres_js_test_md5 with password \'postgres_js_test_md5\''])
12 | await exec('psql', ['-c', 'alter system set password_encryption=\'scram-sha-256\''])
13 | await exec('psql', ['-c', 'select pg_reload_conf()'])
14 | await exec('psql', ['-c', 'drop user if exists postgres_js_test_scram'])
15 | await exec('psql', ['-c', 'create user postgres_js_test_scram with password \'postgres_js_test_scram\''])
16 |
17 | await exec('createdb', ['postgres_js_test'])
18 | await exec('psql', ['-c', 'grant all on database postgres_js_test to postgres_js_test'])
19 | await exec('psql', ['-c', 'alter database postgres_js_test owner to postgres_js_test'])
20 |
21 | function ignore(cmd, args) {
22 | const { stderr } = spawnSync(cmd, args, { stdio: 'pipe', encoding: 'utf8' })
23 | if (stderr && !stderr.includes('already exists') && !stderr.includes('does not exist'))
24 | throw stderr
25 | }
26 |
27 | export async function exec(cmd, args) { // eslint-disable-line
28 | let stderr = ''
29 | const cp = await spawn(cmd, args, { stdio: 'pipe', encoding: 'utf8' }) // eslint-disable-line
30 | cp.stderr.on('data', x => stderr += x)
31 | await new Promise(x => cp.on('exit', x))
32 | if (stderr && !stderr.includes('already exists') && !stderr.includes('does not exist'))
33 | throw new Error(stderr)
34 | }
35 |
--------------------------------------------------------------------------------
/deno/tests/copy.csv:
--------------------------------------------------------------------------------
1 | 1 2 3
2 | 4 5 6
3 |
--------------------------------------------------------------------------------
/deno/tests/pg_hba.conf:
--------------------------------------------------------------------------------
1 | local all all trust
2 | host all postgres samehost trust
3 | host postgres_js_test postgres_js_test samehost trust
4 | host postgres_js_test postgres_js_test_md5 samehost md5
5 | host postgres_js_test postgres_js_test_scram samehost scram-sha-256
6 |
--------------------------------------------------------------------------------
/deno/tests/select-param.sql:
--------------------------------------------------------------------------------
1 | select $1 as x
2 |
--------------------------------------------------------------------------------
/deno/tests/select.sql:
--------------------------------------------------------------------------------
1 | select 1 as x
2 |
--------------------------------------------------------------------------------
/deno/tests/test.js:
--------------------------------------------------------------------------------
1 | import process from 'https://deno.land/std@0.132.0/node/process.ts'
2 | /* eslint no-console: 0 */
3 |
4 | import util from 'https://deno.land/std@0.132.0/node/util.ts'
5 |
6 | let done = 0
7 | let only = false
8 | let ignored = 0
9 | let failed = false
10 | let promise = Promise.resolve()
11 | const tests = {}
12 | , ignore = {}
13 |
14 | export const nt = () => ignored++
15 | export const ot = (...rest) => (only = true, test(true, ...rest))
16 | export const t = (...rest) => test(false, ...rest)
17 | t.timeout = 5
18 |
19 | async function test(o, name, options, fn) {
20 | typeof options !== 'object' && (fn = options, options = {})
21 | const line = new Error().stack.split('\n')[3].match(':([0-9]+):')[1]
22 |
23 | await 1
24 |
25 | if (only && !o)
26 | return
27 |
28 | tests[line] = { fn, line, name }
29 | promise = promise.then(() => Promise.race([
30 | new Promise((resolve, reject) =>
31 | fn.timer = setTimeout(() => reject('Timed out'), (options.timeout || t.timeout) * 1000)
32 | ),
33 | failed
34 | ? (ignored++, ignore)
35 | : fn()
36 | ]))
37 | .then(async x => {
38 | clearTimeout(fn.timer)
39 | if (x === ignore)
40 | return
41 |
42 | if (!Array.isArray(x))
43 | throw new Error('Test should return result array')
44 |
45 | const [expected, got] = await Promise.all(x)
46 | if (expected !== got) {
47 | failed = true
48 | throw new Error(util.inspect(expected) + ' != ' + util.inspect(got))
49 | }
50 |
51 | tests[line].succeeded = true
52 | process.stdout.write('✅')
53 | })
54 | .catch(err => {
55 | tests[line].failed = failed = true
56 | tests[line].error = err instanceof Error ? err : new Error(util.inspect(err))
57 | })
58 | .then(() => {
59 | ++done === Object.keys(tests).length && exit()
60 | })
61 | }
62 |
63 | function exit() {
64 | let success = true
65 | Object.values(tests).every((x) => {
66 | if (x.succeeded)
67 | return true
68 |
69 | success = false
70 | x.cleanup
71 | ? console.error('⛔️', x.name + ' at line', x.line, 'cleanup failed', '\n', util.inspect(x.cleanup))
72 | : console.error('⛔️', x.name + ' at line', x.line, x.failed
73 | ? 'failed'
74 | : 'never finished', x.error ? '\n' + util.inspect(x.error) : ''
75 | )
76 | })
77 |
78 | only
79 | ? console.error('⚠️', 'Not all tests were run')
80 | : ignored
81 | ? console.error('⚠️', ignored, 'ignored test' + (ignored === 1 ? '' : 's', '\n'))
82 | : success
83 | ? console.log('🎉')
84 | : console.error('⚠️', 'Not good')
85 |
86 | !process.exitCode && (!success || only || ignored) && (process.exitCode = 1)
87 | }
88 |
89 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "postgres",
3 | "version": "3.4.7",
4 | "description": "Fastest full featured PostgreSQL client for Node.js",
5 | "type": "module",
6 | "module": "src/index.js",
7 | "main": "cjs/src/index.js",
8 | "exports": {
9 | "types": "./types/index.d.ts",
10 | "bun": "./src/index.js",
11 | "workerd": "./cf/src/index.js",
12 | "import": "./src/index.js",
13 | "default": "./cjs/src/index.js"
14 | },
15 | "types": "types/index.d.ts",
16 | "typings": "types/index.d.ts",
17 | "engines": {
18 | "node": ">=12"
19 | },
20 | "scripts": {
21 | "build": "npm run build:cjs && npm run build:deno && npm run build:cf",
22 | "build:cjs": "node transpile.cjs",
23 | "build:deno": "node transpile.deno.js",
24 | "build:cf": "node transpile.cf.js",
25 | "test": "npm run test:esm && npm run test:cjs && npm run test:deno",
26 | "test:esm": "node tests/index.js",
27 | "test:cjs": "npm run build:cjs && cd cjs/tests && node index.js && cd ../../",
28 | "test:deno": "npm run build:deno && cd deno/tests && deno run --no-lock --allow-all --unsafely-ignore-certificate-errors index.js && cd ../../",
29 | "lint": "eslint src && eslint tests",
30 | "prepare": "npm run build",
31 | "prepublishOnly": "npm run lint"
32 | },
33 | "files": [
34 | "/cf/src",
35 | "/cf/polyfills.js",
36 | "/cjs/src",
37 | "/cjs/package.json",
38 | "/src",
39 | "/types"
40 | ],
41 | "author": "Rasmus Porsager (https://www.porsager.com)",
42 | "funding": {
43 | "type": "individual",
44 | "url": "https://github.com/sponsors/porsager"
45 | },
46 | "license": "Unlicense",
47 | "repository": "porsager/postgres",
48 | "homepage": "https://github.com/porsager/postgres",
49 | "bugs": "https://github.com/porsager/postgres/issues",
50 | "keywords": [
51 | "driver",
52 | "postgresql",
53 | "postgres.js",
54 | "postgres",
55 | "postrges",
56 | "postgre",
57 | "client",
58 | "sql",
59 | "db",
60 | "pg",
61 | "database"
62 | ]
63 | }
64 |
--------------------------------------------------------------------------------
/postgresjs.svg:
--------------------------------------------------------------------------------
1 |
18 |
--------------------------------------------------------------------------------
/src/bytes.js:
--------------------------------------------------------------------------------
1 | const size = 256
2 | let buffer = Buffer.allocUnsafe(size)
3 |
4 | const messages = 'BCcDdEFfHPpQSX'.split('').reduce((acc, x) => {
5 | const v = x.charCodeAt(0)
6 | acc[x] = () => {
7 | buffer[0] = v
8 | b.i = 5
9 | return b
10 | }
11 | return acc
12 | }, {})
13 |
14 | const b = Object.assign(reset, messages, {
15 | N: String.fromCharCode(0),
16 | i: 0,
17 | inc(x) {
18 | b.i += x
19 | return b
20 | },
21 | str(x) {
22 | const length = Buffer.byteLength(x)
23 | fit(length)
24 | b.i += buffer.write(x, b.i, length, 'utf8')
25 | return b
26 | },
27 | i16(x) {
28 | fit(2)
29 | buffer.writeUInt16BE(x, b.i)
30 | b.i += 2
31 | return b
32 | },
33 | i32(x, i) {
34 | if (i || i === 0) {
35 | buffer.writeUInt32BE(x, i)
36 | return b
37 | }
38 | fit(4)
39 | buffer.writeUInt32BE(x, b.i)
40 | b.i += 4
41 | return b
42 | },
43 | z(x) {
44 | fit(x)
45 | buffer.fill(0, b.i, b.i + x)
46 | b.i += x
47 | return b
48 | },
49 | raw(x) {
50 | buffer = Buffer.concat([buffer.subarray(0, b.i), x])
51 | b.i = buffer.length
52 | return b
53 | },
54 | end(at = 1) {
55 | buffer.writeUInt32BE(b.i - at, at)
56 | const out = buffer.subarray(0, b.i)
57 | b.i = 0
58 | buffer = Buffer.allocUnsafe(size)
59 | return out
60 | }
61 | })
62 |
63 | export default b
64 |
65 | function fit(x) {
66 | if (buffer.length - b.i < x) {
67 | const prev = buffer
68 | , length = prev.length
69 |
70 | buffer = Buffer.allocUnsafe(length + (length >> 1) + x)
71 | prev.copy(buffer)
72 | }
73 | }
74 |
75 | function reset() {
76 | b.i = 0
77 | return b
78 | }
79 |
--------------------------------------------------------------------------------
/src/errors.js:
--------------------------------------------------------------------------------
1 | export class PostgresError extends Error {
2 | constructor(x) {
3 | super(x.message)
4 | this.name = this.constructor.name
5 | Object.assign(this, x)
6 | }
7 | }
8 |
9 | export const Errors = {
10 | connection,
11 | postgres,
12 | generic,
13 | notSupported
14 | }
15 |
16 | function connection(x, options, socket) {
17 | const { host, port } = socket || options
18 | const error = Object.assign(
19 | new Error(('write ' + x + ' ' + (options.path || (host + ':' + port)))),
20 | {
21 | code: x,
22 | errno: x,
23 | address: options.path || host
24 | }, options.path ? {} : { port: port }
25 | )
26 | Error.captureStackTrace(error, connection)
27 | return error
28 | }
29 |
30 | function postgres(x) {
31 | const error = new PostgresError(x)
32 | Error.captureStackTrace(error, postgres)
33 | return error
34 | }
35 |
36 | function generic(code, message) {
37 | const error = Object.assign(new Error(code + ': ' + message), { code })
38 | Error.captureStackTrace(error, generic)
39 | return error
40 | }
41 |
42 | /* c8 ignore next 10 */
43 | function notSupported(x) {
44 | const error = Object.assign(
45 | new Error(x + ' (B) is not supported'),
46 | {
47 | code: 'MESSAGE_NOT_SUPPORTED',
48 | name: x
49 | }
50 | )
51 | Error.captureStackTrace(error, notSupported)
52 | return error
53 | }
54 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import os from 'os'
2 | import fs from 'fs'
3 |
4 | import {
5 | mergeUserTypes,
6 | inferType,
7 | Parameter,
8 | Identifier,
9 | Builder,
10 | toPascal,
11 | pascal,
12 | toCamel,
13 | camel,
14 | toKebab,
15 | kebab,
16 | fromPascal,
17 | fromCamel,
18 | fromKebab
19 | } from './types.js'
20 |
21 | import Connection from './connection.js'
22 | import { Query, CLOSE } from './query.js'
23 | import Queue from './queue.js'
24 | import { Errors, PostgresError } from './errors.js'
25 | import Subscribe from './subscribe.js'
26 | import largeObject from './large.js'
27 |
28 | Object.assign(Postgres, {
29 | PostgresError,
30 | toPascal,
31 | pascal,
32 | toCamel,
33 | camel,
34 | toKebab,
35 | kebab,
36 | fromPascal,
37 | fromCamel,
38 | fromKebab,
39 | BigInt: {
40 | to: 20,
41 | from: [20],
42 | parse: x => BigInt(x), // eslint-disable-line
43 | serialize: x => x.toString()
44 | }
45 | })
46 |
47 | export default Postgres
48 |
49 | function Postgres(a, b) {
50 | const options = parseOptions(a, b)
51 | , subscribe = options.no_subscribe || Subscribe(Postgres, { ...options })
52 |
53 | let ending = false
54 |
55 | const queries = Queue()
56 | , connecting = Queue()
57 | , reserved = Queue()
58 | , closed = Queue()
59 | , ended = Queue()
60 | , open = Queue()
61 | , busy = Queue()
62 | , full = Queue()
63 | , queues = { connecting, reserved, closed, ended, open, busy, full }
64 |
65 | const connections = [...Array(options.max)].map(() => Connection(options, queues, { onopen, onend, onclose }))
66 |
67 | const sql = Sql(handler)
68 |
69 | Object.assign(sql, {
70 | get parameters() { return options.parameters },
71 | largeObject: largeObject.bind(null, sql),
72 | subscribe,
73 | CLOSE,
74 | END: CLOSE,
75 | PostgresError,
76 | options,
77 | reserve,
78 | listen,
79 | begin,
80 | close,
81 | end
82 | })
83 |
84 | return sql
85 |
86 | function Sql(handler) {
87 | handler.debug = options.debug
88 |
89 | Object.entries(options.types).reduce((acc, [name, type]) => {
90 | acc[name] = (x) => new Parameter(x, type.to)
91 | return acc
92 | }, typed)
93 |
94 | Object.assign(sql, {
95 | types: typed,
96 | typed,
97 | unsafe,
98 | notify,
99 | array,
100 | json,
101 | file
102 | })
103 |
104 | return sql
105 |
106 | function typed(value, type) {
107 | return new Parameter(value, type)
108 | }
109 |
110 | function sql(strings, ...args) {
111 | const query = strings && Array.isArray(strings.raw)
112 | ? new Query(strings, args, handler, cancel)
113 | : typeof strings === 'string' && !args.length
114 | ? new Identifier(options.transform.column.to ? options.transform.column.to(strings) : strings)
115 | : new Builder(strings, args)
116 | return query
117 | }
118 |
119 | function unsafe(string, args = [], options = {}) {
120 | arguments.length === 2 && !Array.isArray(args) && (options = args, args = [])
121 | const query = new Query([string], args, handler, cancel, {
122 | prepare: false,
123 | ...options,
124 | simple: 'simple' in options ? options.simple : args.length === 0
125 | })
126 | return query
127 | }
128 |
129 | function file(path, args = [], options = {}) {
130 | arguments.length === 2 && !Array.isArray(args) && (options = args, args = [])
131 | const query = new Query([], args, (query) => {
132 | fs.readFile(path, 'utf8', (err, string) => {
133 | if (err)
134 | return query.reject(err)
135 |
136 | query.strings = [string]
137 | handler(query)
138 | })
139 | }, cancel, {
140 | ...options,
141 | simple: 'simple' in options ? options.simple : args.length === 0
142 | })
143 | return query
144 | }
145 | }
146 |
147 | async function listen(name, fn, onlisten) {
148 | const listener = { fn, onlisten }
149 |
150 | const sql = listen.sql || (listen.sql = Postgres({
151 | ...options,
152 | max: 1,
153 | idle_timeout: null,
154 | max_lifetime: null,
155 | fetch_types: false,
156 | onclose() {
157 | Object.entries(listen.channels).forEach(([name, { listeners }]) => {
158 | delete listen.channels[name]
159 | Promise.all(listeners.map(l => listen(name, l.fn, l.onlisten).catch(() => { /* noop */ })))
160 | })
161 | },
162 | onnotify(c, x) {
163 | c in listen.channels && listen.channels[c].listeners.forEach(l => l.fn(x))
164 | }
165 | }))
166 |
167 | const channels = listen.channels || (listen.channels = {})
168 | , exists = name in channels
169 |
170 | if (exists) {
171 | channels[name].listeners.push(listener)
172 | const result = await channels[name].result
173 | listener.onlisten && listener.onlisten()
174 | return { state: result.state, unlisten }
175 | }
176 |
177 | channels[name] = { result: sql`listen ${
178 | sql.unsafe('"' + name.replace(/"/g, '""') + '"')
179 | }`, listeners: [listener] }
180 | const result = await channels[name].result
181 | listener.onlisten && listener.onlisten()
182 | return { state: result.state, unlisten }
183 |
184 | async function unlisten() {
185 | if (name in channels === false)
186 | return
187 |
188 | channels[name].listeners = channels[name].listeners.filter(x => x !== listener)
189 | if (channels[name].listeners.length)
190 | return
191 |
192 | delete channels[name]
193 | return sql`unlisten ${
194 | sql.unsafe('"' + name.replace(/"/g, '""') + '"')
195 | }`
196 | }
197 | }
198 |
199 | async function notify(channel, payload) {
200 | return await sql`select pg_notify(${ channel }, ${ '' + payload })`
201 | }
202 |
203 | async function reserve() {
204 | const queue = Queue()
205 | const c = open.length
206 | ? open.shift()
207 | : await new Promise((resolve, reject) => {
208 | const query = { reserve: resolve, reject }
209 | queries.push(query)
210 | closed.length && connect(closed.shift(), query)
211 | })
212 |
213 | move(c, reserved)
214 | c.reserved = () => queue.length
215 | ? c.execute(queue.shift())
216 | : move(c, reserved)
217 | c.reserved.release = true
218 |
219 | const sql = Sql(handler)
220 | sql.release = () => {
221 | c.reserved = null
222 | onopen(c)
223 | }
224 |
225 | return sql
226 |
227 | function handler(q) {
228 | c.queue === full
229 | ? queue.push(q)
230 | : c.execute(q) || move(c, full)
231 | }
232 | }
233 |
234 | async function begin(options, fn) {
235 | !fn && (fn = options, options = '')
236 | const queries = Queue()
237 | let savepoints = 0
238 | , connection
239 | , prepare = null
240 |
241 | try {
242 | await sql.unsafe('begin ' + options.replace(/[^a-z ]/ig, ''), [], { onexecute }).execute()
243 | return await Promise.race([
244 | scope(connection, fn),
245 | new Promise((_, reject) => connection.onclose = reject)
246 | ])
247 | } catch (error) {
248 | throw error
249 | }
250 |
251 | async function scope(c, fn, name) {
252 | const sql = Sql(handler)
253 | sql.savepoint = savepoint
254 | sql.prepare = x => prepare = x.replace(/[^a-z0-9$-_. ]/gi)
255 | let uncaughtError
256 | , result
257 |
258 | name && await sql`savepoint ${ sql(name) }`
259 | try {
260 | result = await new Promise((resolve, reject) => {
261 | const x = fn(sql)
262 | Promise.resolve(Array.isArray(x) ? Promise.all(x) : x).then(resolve, reject)
263 | })
264 |
265 | if (uncaughtError)
266 | throw uncaughtError
267 | } catch (e) {
268 | await (name
269 | ? sql`rollback to ${ sql(name) }`
270 | : sql`rollback`
271 | )
272 | throw e instanceof PostgresError && e.code === '25P02' && uncaughtError || e
273 | }
274 |
275 | if (!name) {
276 | prepare
277 | ? await sql`prepare transaction '${ sql.unsafe(prepare) }'`
278 | : await sql`commit`
279 | }
280 |
281 | return result
282 |
283 | function savepoint(name, fn) {
284 | if (name && Array.isArray(name.raw))
285 | return savepoint(sql => sql.apply(sql, arguments))
286 |
287 | arguments.length === 1 && (fn = name, name = null)
288 | return scope(c, fn, 's' + savepoints++ + (name ? '_' + name : ''))
289 | }
290 |
291 | function handler(q) {
292 | q.catch(e => uncaughtError || (uncaughtError = e))
293 | c.queue === full
294 | ? queries.push(q)
295 | : c.execute(q) || move(c, full)
296 | }
297 | }
298 |
299 | function onexecute(c) {
300 | connection = c
301 | move(c, reserved)
302 | c.reserved = () => queries.length
303 | ? c.execute(queries.shift())
304 | : move(c, reserved)
305 | }
306 | }
307 |
308 | function move(c, queue) {
309 | c.queue.remove(c)
310 | queue.push(c)
311 | c.queue = queue
312 | queue === open
313 | ? c.idleTimer.start()
314 | : c.idleTimer.cancel()
315 | return c
316 | }
317 |
318 | function json(x) {
319 | return new Parameter(x, 3802)
320 | }
321 |
322 | function array(x, type) {
323 | if (!Array.isArray(x))
324 | return array(Array.from(arguments))
325 |
326 | return new Parameter(x, type || (x.length ? inferType(x) || 25 : 0), options.shared.typeArrayMap)
327 | }
328 |
329 | function handler(query) {
330 | if (ending)
331 | return query.reject(Errors.connection('CONNECTION_ENDED', options, options))
332 |
333 | if (open.length)
334 | return go(open.shift(), query)
335 |
336 | if (closed.length)
337 | return connect(closed.shift(), query)
338 |
339 | busy.length
340 | ? go(busy.shift(), query)
341 | : queries.push(query)
342 | }
343 |
344 | function go(c, query) {
345 | return c.execute(query)
346 | ? move(c, busy)
347 | : move(c, full)
348 | }
349 |
350 | function cancel(query) {
351 | return new Promise((resolve, reject) => {
352 | query.state
353 | ? query.active
354 | ? Connection(options).cancel(query.state, resolve, reject)
355 | : query.cancelled = { resolve, reject }
356 | : (
357 | queries.remove(query),
358 | query.cancelled = true,
359 | query.reject(Errors.generic('57014', 'canceling statement due to user request')),
360 | resolve()
361 | )
362 | })
363 | }
364 |
365 | async function end({ timeout = null } = {}) {
366 | if (ending)
367 | return ending
368 |
369 | await 1
370 | let timer
371 | return ending = Promise.race([
372 | new Promise(r => timeout !== null && (timer = setTimeout(destroy, timeout * 1000, r))),
373 | Promise.all(connections.map(c => c.end()).concat(
374 | listen.sql ? listen.sql.end({ timeout: 0 }) : [],
375 | subscribe.sql ? subscribe.sql.end({ timeout: 0 }) : []
376 | ))
377 | ]).then(() => clearTimeout(timer))
378 | }
379 |
380 | async function close() {
381 | await Promise.all(connections.map(c => c.end()))
382 | }
383 |
384 | async function destroy(resolve) {
385 | await Promise.all(connections.map(c => c.terminate()))
386 | while (queries.length)
387 | queries.shift().reject(Errors.connection('CONNECTION_DESTROYED', options))
388 | resolve()
389 | }
390 |
391 | function connect(c, query) {
392 | move(c, connecting)
393 | c.connect(query)
394 | return c
395 | }
396 |
397 | function onend(c) {
398 | move(c, ended)
399 | }
400 |
401 | function onopen(c) {
402 | if (queries.length === 0)
403 | return move(c, open)
404 |
405 | let max = Math.ceil(queries.length / (connecting.length + 1))
406 | , ready = true
407 |
408 | while (ready && queries.length && max-- > 0) {
409 | const query = queries.shift()
410 | if (query.reserve)
411 | return query.reserve(c)
412 |
413 | ready = c.execute(query)
414 | }
415 |
416 | ready
417 | ? move(c, busy)
418 | : move(c, full)
419 | }
420 |
421 | function onclose(c, e) {
422 | move(c, closed)
423 | c.reserved = null
424 | c.onclose && (c.onclose(e), c.onclose = null)
425 | options.onclose && options.onclose(c.id)
426 | queries.length && connect(c, queries.shift())
427 | }
428 | }
429 |
430 | function parseOptions(a, b) {
431 | if (a && a.shared)
432 | return a
433 |
434 | const env = process.env // eslint-disable-line
435 | , o = (!a || typeof a === 'string' ? b : a) || {}
436 | , { url, multihost } = parseUrl(a)
437 | , query = [...url.searchParams].reduce((a, [b, c]) => (a[b] = c, a), {})
438 | , host = o.hostname || o.host || multihost || url.hostname || env.PGHOST || 'localhost'
439 | , port = o.port || url.port || env.PGPORT || 5432
440 | , user = o.user || o.username || url.username || env.PGUSERNAME || env.PGUSER || osUsername()
441 |
442 | o.no_prepare && (o.prepare = false)
443 | query.sslmode && (query.ssl = query.sslmode, delete query.sslmode)
444 | 'timeout' in o && (console.log('The timeout option is deprecated, use idle_timeout instead'), o.idle_timeout = o.timeout) // eslint-disable-line
445 | query.sslrootcert === 'system' && (query.ssl = 'verify-full')
446 |
447 | const ints = ['idle_timeout', 'connect_timeout', 'max_lifetime', 'max_pipeline', 'backoff', 'keep_alive']
448 | const defaults = {
449 | max : 10,
450 | ssl : false,
451 | idle_timeout : null,
452 | connect_timeout : 30,
453 | max_lifetime : max_lifetime,
454 | max_pipeline : 100,
455 | backoff : backoff,
456 | keep_alive : 60,
457 | prepare : true,
458 | debug : false,
459 | fetch_types : true,
460 | publications : 'alltables',
461 | target_session_attrs: null
462 | }
463 |
464 | return {
465 | host : Array.isArray(host) ? host : host.split(',').map(x => x.split(':')[0]),
466 | port : Array.isArray(port) ? port : host.split(',').map(x => parseInt(x.split(':')[1] || port)),
467 | path : o.path || host.indexOf('/') > -1 && host + '/.s.PGSQL.' + port,
468 | database : o.database || o.db || (url.pathname || '').slice(1) || env.PGDATABASE || user,
469 | user : user,
470 | pass : o.pass || o.password || url.password || env.PGPASSWORD || '',
471 | ...Object.entries(defaults).reduce(
472 | (acc, [k, d]) => {
473 | const value = k in o ? o[k] : k in query
474 | ? (query[k] === 'disable' || query[k] === 'false' ? false : query[k])
475 | : env['PG' + k.toUpperCase()] || d
476 | acc[k] = typeof value === 'string' && ints.includes(k)
477 | ? +value
478 | : value
479 | return acc
480 | },
481 | {}
482 | ),
483 | connection : {
484 | application_name: env.PGAPPNAME || 'postgres.js',
485 | ...o.connection,
486 | ...Object.entries(query).reduce((acc, [k, v]) => (k in defaults || (acc[k] = v), acc), {})
487 | },
488 | types : o.types || {},
489 | target_session_attrs: tsa(o, url, env),
490 | onnotice : o.onnotice,
491 | onnotify : o.onnotify,
492 | onclose : o.onclose,
493 | onparameter : o.onparameter,
494 | socket : o.socket,
495 | transform : parseTransform(o.transform || { undefined: undefined }),
496 | parameters : {},
497 | shared : { retries: 0, typeArrayMap: {} },
498 | ...mergeUserTypes(o.types)
499 | }
500 | }
501 |
502 | function tsa(o, url, env) {
503 | const x = o.target_session_attrs || url.searchParams.get('target_session_attrs') || env.PGTARGETSESSIONATTRS
504 | if (!x || ['read-write', 'read-only', 'primary', 'standby', 'prefer-standby'].includes(x))
505 | return x
506 |
507 | throw new Error('target_session_attrs ' + x + ' is not supported')
508 | }
509 |
510 | function backoff(retries) {
511 | return (0.5 + Math.random() / 2) * Math.min(3 ** retries / 100, 20)
512 | }
513 |
514 | function max_lifetime() {
515 | return 60 * (30 + Math.random() * 30)
516 | }
517 |
518 | function parseTransform(x) {
519 | return {
520 | undefined: x.undefined,
521 | column: {
522 | from: typeof x.column === 'function' ? x.column : x.column && x.column.from,
523 | to: x.column && x.column.to
524 | },
525 | value: {
526 | from: typeof x.value === 'function' ? x.value : x.value && x.value.from,
527 | to: x.value && x.value.to
528 | },
529 | row: {
530 | from: typeof x.row === 'function' ? x.row : x.row && x.row.from,
531 | to: x.row && x.row.to
532 | }
533 | }
534 | }
535 |
536 | function parseUrl(url) {
537 | if (!url || typeof url !== 'string')
538 | return { url: { searchParams: new Map() } }
539 |
540 | let host = url
541 | host = host.slice(host.indexOf('://') + 3).split(/[?/]/)[0]
542 | host = decodeURIComponent(host.slice(host.indexOf('@') + 1))
543 |
544 | const urlObj = new URL(url.replace(host, host.split(',')[0]))
545 |
546 | return {
547 | url: {
548 | username: decodeURIComponent(urlObj.username),
549 | password: decodeURIComponent(urlObj.password),
550 | host: urlObj.host,
551 | hostname: urlObj.hostname,
552 | port: urlObj.port,
553 | pathname: urlObj.pathname,
554 | searchParams: urlObj.searchParams
555 | },
556 | multihost: host.indexOf(',') > -1 && host
557 | }
558 | }
559 |
560 | function osUsername() {
561 | try {
562 | return os.userInfo().username // eslint-disable-line
563 | } catch (_) {
564 | return process.env.USERNAME || process.env.USER || process.env.LOGNAME // eslint-disable-line
565 | }
566 | }
567 |
--------------------------------------------------------------------------------
/src/large.js:
--------------------------------------------------------------------------------
1 | import Stream from 'stream'
2 |
3 | export default function largeObject(sql, oid, mode = 0x00020000 | 0x00040000) {
4 | return new Promise(async(resolve, reject) => {
5 | await sql.begin(async sql => {
6 | let finish
7 | !oid && ([{ oid }] = await sql`select lo_creat(-1) as oid`)
8 | const [{ fd }] = await sql`select lo_open(${ oid }, ${ mode }) as fd`
9 |
10 | const lo = {
11 | writable,
12 | readable,
13 | close : () => sql`select lo_close(${ fd })`.then(finish),
14 | tell : () => sql`select lo_tell64(${ fd })`,
15 | read : (x) => sql`select loread(${ fd }, ${ x }) as data`,
16 | write : (x) => sql`select lowrite(${ fd }, ${ x })`,
17 | truncate : (x) => sql`select lo_truncate64(${ fd }, ${ x })`,
18 | seek : (x, whence = 0) => sql`select lo_lseek64(${ fd }, ${ x }, ${ whence })`,
19 | size : () => sql`
20 | select
21 | lo_lseek64(${ fd }, location, 0) as position,
22 | seek.size
23 | from (
24 | select
25 | lo_lseek64($1, 0, 2) as size,
26 | tell.location
27 | from (select lo_tell64($1) as location) tell
28 | ) seek
29 | `
30 | }
31 |
32 | resolve(lo)
33 |
34 | return new Promise(async r => finish = r)
35 |
36 | async function readable({
37 | highWaterMark = 2048 * 8,
38 | start = 0,
39 | end = Infinity
40 | } = {}) {
41 | let max = end - start
42 | start && await lo.seek(start)
43 | return new Stream.Readable({
44 | highWaterMark,
45 | async read(size) {
46 | const l = size > max ? size - max : size
47 | max -= size
48 | const [{ data }] = await lo.read(l)
49 | this.push(data)
50 | if (data.length < size)
51 | this.push(null)
52 | }
53 | })
54 | }
55 |
56 | async function writable({
57 | highWaterMark = 2048 * 8,
58 | start = 0
59 | } = {}) {
60 | start && await lo.seek(start)
61 | return new Stream.Writable({
62 | highWaterMark,
63 | write(chunk, encoding, callback) {
64 | lo.write(chunk).then(() => callback(), callback)
65 | }
66 | })
67 | }
68 | }).catch(reject)
69 | })
70 | }
71 |
--------------------------------------------------------------------------------
/src/query.js:
--------------------------------------------------------------------------------
1 | const originCache = new Map()
2 | , originStackCache = new Map()
3 | , originError = Symbol('OriginError')
4 |
5 | export const CLOSE = {}
6 | export class Query extends Promise {
7 | constructor(strings, args, handler, canceller, options = {}) {
8 | let resolve
9 | , reject
10 |
11 | super((a, b) => {
12 | resolve = a
13 | reject = b
14 | })
15 |
16 | this.tagged = Array.isArray(strings.raw)
17 | this.strings = strings
18 | this.args = args
19 | this.handler = handler
20 | this.canceller = canceller
21 | this.options = options
22 |
23 | this.state = null
24 | this.statement = null
25 |
26 | this.resolve = x => (this.active = false, resolve(x))
27 | this.reject = x => (this.active = false, reject(x))
28 |
29 | this.active = false
30 | this.cancelled = null
31 | this.executed = false
32 | this.signature = ''
33 |
34 | this[originError] = this.handler.debug
35 | ? new Error()
36 | : this.tagged && cachedError(this.strings)
37 | }
38 |
39 | get origin() {
40 | return (this.handler.debug
41 | ? this[originError].stack
42 | : this.tagged && originStackCache.has(this.strings)
43 | ? originStackCache.get(this.strings)
44 | : originStackCache.set(this.strings, this[originError].stack).get(this.strings)
45 | ) || ''
46 | }
47 |
48 | static get [Symbol.species]() {
49 | return Promise
50 | }
51 |
52 | cancel() {
53 | return this.canceller && (this.canceller(this), this.canceller = null)
54 | }
55 |
56 | simple() {
57 | this.options.simple = true
58 | this.options.prepare = false
59 | return this
60 | }
61 |
62 | async readable() {
63 | this.simple()
64 | this.streaming = true
65 | return this
66 | }
67 |
68 | async writable() {
69 | this.simple()
70 | this.streaming = true
71 | return this
72 | }
73 |
74 | cursor(rows = 1, fn) {
75 | this.options.simple = false
76 | if (typeof rows === 'function') {
77 | fn = rows
78 | rows = 1
79 | }
80 |
81 | this.cursorRows = rows
82 |
83 | if (typeof fn === 'function')
84 | return (this.cursorFn = fn, this)
85 |
86 | let prev
87 | return {
88 | [Symbol.asyncIterator]: () => ({
89 | next: () => {
90 | if (this.executed && !this.active)
91 | return { done: true }
92 |
93 | prev && prev()
94 | const promise = new Promise((resolve, reject) => {
95 | this.cursorFn = value => {
96 | resolve({ value, done: false })
97 | return new Promise(r => prev = r)
98 | }
99 | this.resolve = () => (this.active = false, resolve({ done: true }))
100 | this.reject = x => (this.active = false, reject(x))
101 | })
102 | this.execute()
103 | return promise
104 | },
105 | return() {
106 | prev && prev(CLOSE)
107 | return { done: true }
108 | }
109 | })
110 | }
111 | }
112 |
113 | describe() {
114 | this.options.simple = false
115 | this.onlyDescribe = this.options.prepare = true
116 | return this
117 | }
118 |
119 | stream() {
120 | throw new Error('.stream has been renamed to .forEach')
121 | }
122 |
123 | forEach(fn) {
124 | this.forEachFn = fn
125 | this.handle()
126 | return this
127 | }
128 |
129 | raw() {
130 | this.isRaw = true
131 | return this
132 | }
133 |
134 | values() {
135 | this.isRaw = 'values'
136 | return this
137 | }
138 |
139 | async handle() {
140 | !this.executed && (this.executed = true) && await 1 && this.handler(this)
141 | }
142 |
143 | execute() {
144 | this.handle()
145 | return this
146 | }
147 |
148 | then() {
149 | this.handle()
150 | return super.then.apply(this, arguments)
151 | }
152 |
153 | catch() {
154 | this.handle()
155 | return super.catch.apply(this, arguments)
156 | }
157 |
158 | finally() {
159 | this.handle()
160 | return super.finally.apply(this, arguments)
161 | }
162 | }
163 |
164 | function cachedError(xs) {
165 | if (originCache.has(xs))
166 | return originCache.get(xs)
167 |
168 | const x = Error.stackTraceLimit
169 | Error.stackTraceLimit = 4
170 | originCache.set(xs, new Error())
171 | Error.stackTraceLimit = x
172 | return originCache.get(xs)
173 | }
174 |
--------------------------------------------------------------------------------
/src/queue.js:
--------------------------------------------------------------------------------
1 | export default Queue
2 |
3 | function Queue(initial = []) {
4 | let xs = initial.slice()
5 | let index = 0
6 |
7 | return {
8 | get length() {
9 | return xs.length - index
10 | },
11 | remove: (x) => {
12 | const index = xs.indexOf(x)
13 | return index === -1
14 | ? null
15 | : (xs.splice(index, 1), x)
16 | },
17 | push: (x) => (xs.push(x), x),
18 | shift: () => {
19 | const out = xs[index++]
20 |
21 | if (index === xs.length) {
22 | index = 0
23 | xs = []
24 | } else {
25 | xs[index - 1] = undefined
26 | }
27 |
28 | return out
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/result.js:
--------------------------------------------------------------------------------
1 | export default class Result extends Array {
2 | constructor() {
3 | super()
4 | Object.defineProperties(this, {
5 | count: { value: null, writable: true },
6 | state: { value: null, writable: true },
7 | command: { value: null, writable: true },
8 | columns: { value: null, writable: true },
9 | statement: { value: null, writable: true }
10 | })
11 | }
12 |
13 | static get [Symbol.species]() {
14 | return Array
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/subscribe.js:
--------------------------------------------------------------------------------
1 | const noop = () => { /* noop */ }
2 |
3 | export default function Subscribe(postgres, options) {
4 | const subscribers = new Map()
5 | , slot = 'postgresjs_' + Math.random().toString(36).slice(2)
6 | , state = {}
7 |
8 | let connection
9 | , stream
10 | , ended = false
11 |
12 | const sql = subscribe.sql = postgres({
13 | ...options,
14 | transform: { column: {}, value: {}, row: {} },
15 | max: 1,
16 | fetch_types: false,
17 | idle_timeout: null,
18 | max_lifetime: null,
19 | connection: {
20 | ...options.connection,
21 | replication: 'database'
22 | },
23 | onclose: async function() {
24 | if (ended)
25 | return
26 | stream = null
27 | state.pid = state.secret = undefined
28 | connected(await init(sql, slot, options.publications))
29 | subscribers.forEach(event => event.forEach(({ onsubscribe }) => onsubscribe()))
30 | },
31 | no_subscribe: true
32 | })
33 |
34 | const end = sql.end
35 | , close = sql.close
36 |
37 | sql.end = async() => {
38 | ended = true
39 | stream && (await new Promise(r => (stream.once('close', r), stream.end())))
40 | return end()
41 | }
42 |
43 | sql.close = async() => {
44 | stream && (await new Promise(r => (stream.once('close', r), stream.end())))
45 | return close()
46 | }
47 |
48 | return subscribe
49 |
50 | async function subscribe(event, fn, onsubscribe = noop, onerror = noop) {
51 | event = parseEvent(event)
52 |
53 | if (!connection)
54 | connection = init(sql, slot, options.publications)
55 |
56 | const subscriber = { fn, onsubscribe }
57 | const fns = subscribers.has(event)
58 | ? subscribers.get(event).add(subscriber)
59 | : subscribers.set(event, new Set([subscriber])).get(event)
60 |
61 | const unsubscribe = () => {
62 | fns.delete(subscriber)
63 | fns.size === 0 && subscribers.delete(event)
64 | }
65 |
66 | return connection.then(x => {
67 | connected(x)
68 | onsubscribe()
69 | stream && stream.on('error', onerror)
70 | return { unsubscribe, state, sql }
71 | })
72 | }
73 |
74 | function connected(x) {
75 | stream = x.stream
76 | state.pid = x.state.pid
77 | state.secret = x.state.secret
78 | }
79 |
80 | async function init(sql, slot, publications) {
81 | if (!publications)
82 | throw new Error('Missing publication names')
83 |
84 | const xs = await sql.unsafe(
85 | `CREATE_REPLICATION_SLOT ${ slot } TEMPORARY LOGICAL pgoutput NOEXPORT_SNAPSHOT`
86 | )
87 |
88 | const [x] = xs
89 |
90 | const stream = await sql.unsafe(
91 | `START_REPLICATION SLOT ${ slot } LOGICAL ${
92 | x.consistent_point
93 | } (proto_version '1', publication_names '${ publications }')`
94 | ).writable()
95 |
96 | const state = {
97 | lsn: Buffer.concat(x.consistent_point.split('/').map(x => Buffer.from(('00000000' + x).slice(-8), 'hex')))
98 | }
99 |
100 | stream.on('data', data)
101 | stream.on('error', error)
102 | stream.on('close', sql.close)
103 |
104 | return { stream, state: xs.state }
105 |
106 | function error(e) {
107 | console.error('Unexpected error during logical streaming - reconnecting', e) // eslint-disable-line
108 | }
109 |
110 | function data(x) {
111 | if (x[0] === 0x77) {
112 | parse(x.subarray(25), state, sql.options.parsers, handle, options.transform)
113 | } else if (x[0] === 0x6b && x[17]) {
114 | state.lsn = x.subarray(1, 9)
115 | pong()
116 | }
117 | }
118 |
119 | function handle(a, b) {
120 | const path = b.relation.schema + '.' + b.relation.table
121 | call('*', a, b)
122 | call('*:' + path, a, b)
123 | b.relation.keys.length && call('*:' + path + '=' + b.relation.keys.map(x => a[x.name]), a, b)
124 | call(b.command, a, b)
125 | call(b.command + ':' + path, a, b)
126 | b.relation.keys.length && call(b.command + ':' + path + '=' + b.relation.keys.map(x => a[x.name]), a, b)
127 | }
128 |
129 | function pong() {
130 | const x = Buffer.alloc(34)
131 | x[0] = 'r'.charCodeAt(0)
132 | x.fill(state.lsn, 1)
133 | x.writeBigInt64BE(BigInt(Date.now() - Date.UTC(2000, 0, 1)) * BigInt(1000), 25)
134 | stream.write(x)
135 | }
136 | }
137 |
138 | function call(x, a, b) {
139 | subscribers.has(x) && subscribers.get(x).forEach(({ fn }) => fn(a, b, x))
140 | }
141 | }
142 |
143 | function Time(x) {
144 | return new Date(Date.UTC(2000, 0, 1) + Number(x / BigInt(1000)))
145 | }
146 |
147 | function parse(x, state, parsers, handle, transform) {
148 | const char = (acc, [k, v]) => (acc[k.charCodeAt(0)] = v, acc)
149 |
150 | Object.entries({
151 | R: x => { // Relation
152 | let i = 1
153 | const r = state[x.readUInt32BE(i)] = {
154 | schema: x.toString('utf8', i += 4, i = x.indexOf(0, i)) || 'pg_catalog',
155 | table: x.toString('utf8', i + 1, i = x.indexOf(0, i + 1)),
156 | columns: Array(x.readUInt16BE(i += 2)),
157 | keys: []
158 | }
159 | i += 2
160 |
161 | let columnIndex = 0
162 | , column
163 |
164 | while (i < x.length) {
165 | column = r.columns[columnIndex++] = {
166 | key: x[i++],
167 | name: transform.column.from
168 | ? transform.column.from(x.toString('utf8', i, i = x.indexOf(0, i)))
169 | : x.toString('utf8', i, i = x.indexOf(0, i)),
170 | type: x.readUInt32BE(i += 1),
171 | parser: parsers[x.readUInt32BE(i)],
172 | atttypmod: x.readUInt32BE(i += 4)
173 | }
174 |
175 | column.key && r.keys.push(column)
176 | i += 4
177 | }
178 | },
179 | Y: () => { /* noop */ }, // Type
180 | O: () => { /* noop */ }, // Origin
181 | B: x => { // Begin
182 | state.date = Time(x.readBigInt64BE(9))
183 | state.lsn = x.subarray(1, 9)
184 | },
185 | I: x => { // Insert
186 | let i = 1
187 | const relation = state[x.readUInt32BE(i)]
188 | const { row } = tuples(x, relation.columns, i += 7, transform)
189 |
190 | handle(row, {
191 | command: 'insert',
192 | relation
193 | })
194 | },
195 | D: x => { // Delete
196 | let i = 1
197 | const relation = state[x.readUInt32BE(i)]
198 | i += 4
199 | const key = x[i] === 75
200 | handle(key || x[i] === 79
201 | ? tuples(x, relation.columns, i += 3, transform).row
202 | : null
203 | , {
204 | command: 'delete',
205 | relation,
206 | key
207 | })
208 | },
209 | U: x => { // Update
210 | let i = 1
211 | const relation = state[x.readUInt32BE(i)]
212 | i += 4
213 | const key = x[i] === 75
214 | const xs = key || x[i] === 79
215 | ? tuples(x, relation.columns, i += 3, transform)
216 | : null
217 |
218 | xs && (i = xs.i)
219 |
220 | const { row } = tuples(x, relation.columns, i + 3, transform)
221 |
222 | handle(row, {
223 | command: 'update',
224 | relation,
225 | key,
226 | old: xs && xs.row
227 | })
228 | },
229 | T: () => { /* noop */ }, // Truncate,
230 | C: () => { /* noop */ } // Commit
231 | }).reduce(char, {})[x[0]](x)
232 | }
233 |
234 | function tuples(x, columns, xi, transform) {
235 | let type
236 | , column
237 | , value
238 |
239 | const row = transform.raw ? new Array(columns.length) : {}
240 | for (let i = 0; i < columns.length; i++) {
241 | type = x[xi++]
242 | column = columns[i]
243 | value = type === 110 // n
244 | ? null
245 | : type === 117 // u
246 | ? undefined
247 | : column.parser === undefined
248 | ? x.toString('utf8', xi + 4, xi += 4 + x.readUInt32BE(xi))
249 | : column.parser.array === true
250 | ? column.parser(x.toString('utf8', xi + 5, xi += 4 + x.readUInt32BE(xi)))
251 | : column.parser(x.toString('utf8', xi + 4, xi += 4 + x.readUInt32BE(xi)))
252 |
253 | transform.raw
254 | ? (row[i] = transform.raw === true
255 | ? value
256 | : transform.value.from ? transform.value.from(value, column) : value)
257 | : (row[column.name] = transform.value.from
258 | ? transform.value.from(value, column)
259 | : value
260 | )
261 | }
262 |
263 | return { i: xi, row: transform.row.from ? transform.row.from(row) : row }
264 | }
265 |
266 | function parseEvent(x) {
267 | const xs = x.match(/^(\*|insert|update|delete)?:?([^.]+?\.?[^=]+)?=?(.+)?/i) || []
268 |
269 | if (!xs)
270 | throw new Error('Malformed subscribe pattern: ' + x)
271 |
272 | const [, command, path, key] = xs
273 |
274 | return (command || '*')
275 | + (path ? ':' + (path.indexOf('.') === -1 ? 'public.' + path : path) : '')
276 | + (key ? '=' + key : '')
277 | }
278 |
--------------------------------------------------------------------------------
/src/types.js:
--------------------------------------------------------------------------------
1 | import { Query } from './query.js'
2 | import { Errors } from './errors.js'
3 |
4 | export const types = {
5 | string: {
6 | to: 25,
7 | from: null, // defaults to string
8 | serialize: x => '' + x
9 | },
10 | number: {
11 | to: 0,
12 | from: [21, 23, 26, 700, 701],
13 | serialize: x => '' + x,
14 | parse: x => +x
15 | },
16 | json: {
17 | to: 114,
18 | from: [114, 3802],
19 | serialize: x => JSON.stringify(x),
20 | parse: x => JSON.parse(x)
21 | },
22 | boolean: {
23 | to: 16,
24 | from: 16,
25 | serialize: x => x === true ? 't' : 'f',
26 | parse: x => x === 't'
27 | },
28 | date: {
29 | to: 1184,
30 | from: [1082, 1114, 1184],
31 | serialize: x => (x instanceof Date ? x : new Date(x)).toISOString(),
32 | parse: x => new Date(x)
33 | },
34 | bytea: {
35 | to: 17,
36 | from: 17,
37 | serialize: x => '\\x' + Buffer.from(x).toString('hex'),
38 | parse: x => Buffer.from(x.slice(2), 'hex')
39 | }
40 | }
41 |
42 | class NotTagged { then() { notTagged() } catch() { notTagged() } finally() { notTagged() }}
43 |
44 | export class Identifier extends NotTagged {
45 | constructor(value) {
46 | super()
47 | this.value = escapeIdentifier(value)
48 | }
49 | }
50 |
51 | export class Parameter extends NotTagged {
52 | constructor(value, type, array) {
53 | super()
54 | this.value = value
55 | this.type = type
56 | this.array = array
57 | }
58 | }
59 |
60 | export class Builder extends NotTagged {
61 | constructor(first, rest) {
62 | super()
63 | this.first = first
64 | this.rest = rest
65 | }
66 |
67 | build(before, parameters, types, options) {
68 | const keyword = builders.map(([x, fn]) => ({ fn, i: before.search(x) })).sort((a, b) => a.i - b.i).pop()
69 | return keyword.i === -1
70 | ? escapeIdentifiers(this.first, options)
71 | : keyword.fn(this.first, this.rest, parameters, types, options)
72 | }
73 | }
74 |
75 | export function handleValue(x, parameters, types, options) {
76 | let value = x instanceof Parameter ? x.value : x
77 | if (value === undefined) {
78 | x instanceof Parameter
79 | ? x.value = options.transform.undefined
80 | : value = x = options.transform.undefined
81 |
82 | if (value === undefined)
83 | throw Errors.generic('UNDEFINED_VALUE', 'Undefined values are not allowed')
84 | }
85 |
86 | return '$' + (types.push(
87 | x instanceof Parameter
88 | ? (parameters.push(x.value), x.array
89 | ? x.array[x.type || inferType(x.value)] || x.type || firstIsString(x.value)
90 | : x.type
91 | )
92 | : (parameters.push(x), inferType(x))
93 | ))
94 | }
95 |
96 | const defaultHandlers = typeHandlers(types)
97 |
98 | export function stringify(q, string, value, parameters, types, options) { // eslint-disable-line
99 | for (let i = 1; i < q.strings.length; i++) {
100 | string += (stringifyValue(string, value, parameters, types, options)) + q.strings[i]
101 | value = q.args[i]
102 | }
103 |
104 | return string
105 | }
106 |
107 | function stringifyValue(string, value, parameters, types, o) {
108 | return (
109 | value instanceof Builder ? value.build(string, parameters, types, o) :
110 | value instanceof Query ? fragment(value, parameters, types, o) :
111 | value instanceof Identifier ? value.value :
112 | value && value[0] instanceof Query ? value.reduce((acc, x) => acc + ' ' + fragment(x, parameters, types, o), '') :
113 | handleValue(value, parameters, types, o)
114 | )
115 | }
116 |
117 | function fragment(q, parameters, types, options) {
118 | q.fragment = true
119 | return stringify(q, q.strings[0], q.args[0], parameters, types, options)
120 | }
121 |
122 | function valuesBuilder(first, parameters, types, columns, options) {
123 | return first.map(row =>
124 | '(' + columns.map(column =>
125 | stringifyValue('values', row[column], parameters, types, options)
126 | ).join(',') + ')'
127 | ).join(',')
128 | }
129 |
130 | function values(first, rest, parameters, types, options) {
131 | const multi = Array.isArray(first[0])
132 | const columns = rest.length ? rest.flat() : Object.keys(multi ? first[0] : first)
133 | return valuesBuilder(multi ? first : [first], parameters, types, columns, options)
134 | }
135 |
136 | function select(first, rest, parameters, types, options) {
137 | typeof first === 'string' && (first = [first].concat(rest))
138 | if (Array.isArray(first))
139 | return escapeIdentifiers(first, options)
140 |
141 | let value
142 | const columns = rest.length ? rest.flat() : Object.keys(first)
143 | return columns.map(x => {
144 | value = first[x]
145 | return (
146 | value instanceof Query ? fragment(value, parameters, types, options) :
147 | value instanceof Identifier ? value.value :
148 | handleValue(value, parameters, types, options)
149 | ) + ' as ' + escapeIdentifier(options.transform.column.to ? options.transform.column.to(x) : x)
150 | }).join(',')
151 | }
152 |
153 | const builders = Object.entries({
154 | values,
155 | in: (...xs) => {
156 | const x = values(...xs)
157 | return x === '()' ? '(null)' : x
158 | },
159 | select,
160 | as: select,
161 | returning: select,
162 | '\\(': select,
163 |
164 | update(first, rest, parameters, types, options) {
165 | return (rest.length ? rest.flat() : Object.keys(first)).map(x =>
166 | escapeIdentifier(options.transform.column.to ? options.transform.column.to(x) : x) +
167 | '=' + stringifyValue('values', first[x], parameters, types, options)
168 | )
169 | },
170 |
171 | insert(first, rest, parameters, types, options) {
172 | const columns = rest.length ? rest.flat() : Object.keys(Array.isArray(first) ? first[0] : first)
173 | return '(' + escapeIdentifiers(columns, options) + ')values' +
174 | valuesBuilder(Array.isArray(first) ? first : [first], parameters, types, columns, options)
175 | }
176 | }).map(([x, fn]) => ([new RegExp('((?:^|[\\s(])' + x + '(?:$|[\\s(]))(?![\\s\\S]*\\1)', 'i'), fn]))
177 |
178 | function notTagged() {
179 | throw Errors.generic('NOT_TAGGED_CALL', 'Query not called as a tagged template literal')
180 | }
181 |
182 | export const serializers = defaultHandlers.serializers
183 | export const parsers = defaultHandlers.parsers
184 |
185 | export const END = {}
186 |
187 | function firstIsString(x) {
188 | if (Array.isArray(x))
189 | return firstIsString(x[0])
190 | return typeof x === 'string' ? 1009 : 0
191 | }
192 |
193 | export const mergeUserTypes = function(types) {
194 | const user = typeHandlers(types || {})
195 | return {
196 | serializers: Object.assign({}, serializers, user.serializers),
197 | parsers: Object.assign({}, parsers, user.parsers)
198 | }
199 | }
200 |
201 | function typeHandlers(types) {
202 | return Object.keys(types).reduce((acc, k) => {
203 | types[k].from && [].concat(types[k].from).forEach(x => acc.parsers[x] = types[k].parse)
204 | if (types[k].serialize) {
205 | acc.serializers[types[k].to] = types[k].serialize
206 | types[k].from && [].concat(types[k].from).forEach(x => acc.serializers[x] = types[k].serialize)
207 | }
208 | return acc
209 | }, { parsers: {}, serializers: {} })
210 | }
211 |
212 | function escapeIdentifiers(xs, { transform: { column } }) {
213 | return xs.map(x => escapeIdentifier(column.to ? column.to(x) : x)).join(',')
214 | }
215 |
216 | export const escapeIdentifier = function escape(str) {
217 | return '"' + str.replace(/"/g, '""').replace(/\./g, '"."') + '"'
218 | }
219 |
220 | export const inferType = function inferType(x) {
221 | return (
222 | x instanceof Parameter ? x.type :
223 | x instanceof Date ? 1184 :
224 | x instanceof Uint8Array ? 17 :
225 | (x === true || x === false) ? 16 :
226 | typeof x === 'bigint' ? 20 :
227 | Array.isArray(x) ? inferType(x[0]) :
228 | 0
229 | )
230 | }
231 |
232 | const escapeBackslash = /\\/g
233 | const escapeQuote = /"/g
234 |
235 | function arrayEscape(x) {
236 | return x
237 | .replace(escapeBackslash, '\\\\')
238 | .replace(escapeQuote, '\\"')
239 | }
240 |
241 | export const arraySerializer = function arraySerializer(xs, serializer, options, typarray) {
242 | if (Array.isArray(xs) === false)
243 | return xs
244 |
245 | if (!xs.length)
246 | return '{}'
247 |
248 | const first = xs[0]
249 | // Only _box (1020) has the ';' delimiter for arrays, all other types use the ',' delimiter
250 | const delimiter = typarray === 1020 ? ';' : ','
251 |
252 | if (Array.isArray(first) && !first.type)
253 | return '{' + xs.map(x => arraySerializer(x, serializer, options, typarray)).join(delimiter) + '}'
254 |
255 | return '{' + xs.map(x => {
256 | if (x === undefined) {
257 | x = options.transform.undefined
258 | if (x === undefined)
259 | throw Errors.generic('UNDEFINED_VALUE', 'Undefined values are not allowed')
260 | }
261 |
262 | return x === null
263 | ? 'null'
264 | : '"' + arrayEscape(serializer ? serializer(x.type ? x.value : x) : '' + x) + '"'
265 | }).join(delimiter) + '}'
266 | }
267 |
268 | const arrayParserState = {
269 | i: 0,
270 | char: null,
271 | str: '',
272 | quoted: false,
273 | last: 0
274 | }
275 |
276 | export const arrayParser = function arrayParser(x, parser, typarray) {
277 | arrayParserState.i = arrayParserState.last = 0
278 | return arrayParserLoop(arrayParserState, x, parser, typarray)
279 | }
280 |
281 | function arrayParserLoop(s, x, parser, typarray) {
282 | const xs = []
283 | // Only _box (1020) has the ';' delimiter for arrays, all other types use the ',' delimiter
284 | const delimiter = typarray === 1020 ? ';' : ','
285 | for (; s.i < x.length; s.i++) {
286 | s.char = x[s.i]
287 | if (s.quoted) {
288 | if (s.char === '\\') {
289 | s.str += x[++s.i]
290 | } else if (s.char === '"') {
291 | xs.push(parser ? parser(s.str) : s.str)
292 | s.str = ''
293 | s.quoted = x[s.i + 1] === '"'
294 | s.last = s.i + 2
295 | } else {
296 | s.str += s.char
297 | }
298 | } else if (s.char === '"') {
299 | s.quoted = true
300 | } else if (s.char === '{') {
301 | s.last = ++s.i
302 | xs.push(arrayParserLoop(s, x, parser, typarray))
303 | } else if (s.char === '}') {
304 | s.quoted = false
305 | s.last < s.i && xs.push(parser ? parser(x.slice(s.last, s.i)) : x.slice(s.last, s.i))
306 | s.last = s.i + 1
307 | break
308 | } else if (s.char === delimiter && s.p !== '}' && s.p !== '"') {
309 | xs.push(parser ? parser(x.slice(s.last, s.i)) : x.slice(s.last, s.i))
310 | s.last = s.i + 1
311 | }
312 | s.p = s.char
313 | }
314 | s.last < s.i && xs.push(parser ? parser(x.slice(s.last, s.i + 1)) : x.slice(s.last, s.i + 1))
315 | return xs
316 | }
317 |
318 | export const toCamel = x => {
319 | let str = x[0]
320 | for (let i = 1; i < x.length; i++)
321 | str += x[i] === '_' ? x[++i].toUpperCase() : x[i]
322 | return str
323 | }
324 |
325 | export const toPascal = x => {
326 | let str = x[0].toUpperCase()
327 | for (let i = 1; i < x.length; i++)
328 | str += x[i] === '_' ? x[++i].toUpperCase() : x[i]
329 | return str
330 | }
331 |
332 | export const toKebab = x => x.replace(/_/g, '-')
333 |
334 | export const fromCamel = x => x.replace(/([A-Z])/g, '_$1').toLowerCase()
335 | export const fromPascal = x => (x.slice(0, 1) + x.slice(1).replace(/([A-Z])/g, '_$1')).toLowerCase()
336 | export const fromKebab = x => x.replace(/-/g, '_')
337 |
338 | function createJsonTransform(fn) {
339 | return function jsonTransform(x, column) {
340 | return typeof x === 'object' && x !== null && (column.type === 114 || column.type === 3802)
341 | ? Array.isArray(x)
342 | ? x.map(x => jsonTransform(x, column))
343 | : Object.entries(x).reduce((acc, [k, v]) => Object.assign(acc, { [fn(k)]: jsonTransform(v, column) }), {})
344 | : x
345 | }
346 | }
347 |
348 | toCamel.column = { from: toCamel }
349 | toCamel.value = { from: createJsonTransform(toCamel) }
350 | fromCamel.column = { to: fromCamel }
351 |
352 | export const camel = { ...toCamel }
353 | camel.column.to = fromCamel
354 |
355 | toPascal.column = { from: toPascal }
356 | toPascal.value = { from: createJsonTransform(toPascal) }
357 | fromPascal.column = { to: fromPascal }
358 |
359 | export const pascal = { ...toPascal }
360 | pascal.column.to = fromPascal
361 |
362 | toKebab.column = { from: toKebab }
363 | toKebab.value = { from: createJsonTransform(toKebab) }
364 | fromKebab.column = { to: fromKebab }
365 |
366 | export const kebab = { ...toKebab }
367 | kebab.column.to = fromKebab
368 |
--------------------------------------------------------------------------------
/tests/bootstrap.js:
--------------------------------------------------------------------------------
1 | import { spawnSync } from 'child_process'
2 |
3 | exec('dropdb', ['postgres_js_test'])
4 |
5 | exec('psql', ['-c', 'alter system set ssl=on'])
6 | exec('psql', ['-c', 'drop user postgres_js_test'])
7 | exec('psql', ['-c', 'create user postgres_js_test'])
8 | exec('psql', ['-c', 'alter system set password_encryption=md5'])
9 | exec('psql', ['-c', 'select pg_reload_conf()'])
10 | exec('psql', ['-c', 'drop user if exists postgres_js_test_md5'])
11 | exec('psql', ['-c', 'create user postgres_js_test_md5 with password \'postgres_js_test_md5\''])
12 | exec('psql', ['-c', 'alter system set password_encryption=\'scram-sha-256\''])
13 | exec('psql', ['-c', 'select pg_reload_conf()'])
14 | exec('psql', ['-c', 'drop user if exists postgres_js_test_scram'])
15 | exec('psql', ['-c', 'create user postgres_js_test_scram with password \'postgres_js_test_scram\''])
16 |
17 | exec('createdb', ['postgres_js_test'])
18 | exec('psql', ['-c', 'grant all on database postgres_js_test to postgres_js_test'])
19 | exec('psql', ['-c', 'alter database postgres_js_test owner to postgres_js_test'])
20 |
21 | export function exec(cmd, args) {
22 | const { stderr } = spawnSync(cmd, args, { stdio: 'pipe', encoding: 'utf8' })
23 | if (stderr && !stderr.includes('already exists') && !stderr.includes('does not exist'))
24 | throw stderr
25 | }
26 |
27 | async function execAsync(cmd, args) { // eslint-disable-line
28 | let stderr = ''
29 | const cp = await spawn(cmd, args, { stdio: 'pipe', encoding: 'utf8' }) // eslint-disable-line
30 | cp.stderr.on('data', x => stderr += x)
31 | await new Promise(x => cp.on('exit', x))
32 | if (stderr && !stderr.includes('already exists') && !stderr.includes('does not exist'))
33 | throw new Error(stderr)
34 | }
35 |
--------------------------------------------------------------------------------
/tests/copy.csv:
--------------------------------------------------------------------------------
1 | 1 2 3
2 | 4 5 6
3 |
--------------------------------------------------------------------------------
/tests/pg_hba.conf:
--------------------------------------------------------------------------------
1 | local all all trust
2 | host all postgres samehost trust
3 | host postgres_js_test postgres_js_test samehost trust
4 | host postgres_js_test postgres_js_test_md5 samehost md5
5 | host postgres_js_test postgres_js_test_scram samehost scram-sha-256
6 |
--------------------------------------------------------------------------------
/tests/select-param.sql:
--------------------------------------------------------------------------------
1 | select $1 as x
2 |
--------------------------------------------------------------------------------
/tests/select.sql:
--------------------------------------------------------------------------------
1 | select 1 as x
2 |
--------------------------------------------------------------------------------
/tests/test.js:
--------------------------------------------------------------------------------
1 | /* eslint no-console: 0 */
2 |
3 | import util from 'util'
4 |
5 | let done = 0
6 | let only = false
7 | let ignored = 0
8 | let failed = false
9 | let promise = Promise.resolve()
10 | const tests = {}
11 | , ignore = {}
12 |
13 | export const nt = () => ignored++
14 | export const ot = (...rest) => (only = true, test(true, ...rest))
15 | export const t = (...rest) => test(false, ...rest)
16 | t.timeout = 5
17 |
18 | async function test(o, name, options, fn) {
19 | typeof options !== 'object' && (fn = options, options = {})
20 | const line = new Error().stack.split('\n')[3].match(':([0-9]+):')[1]
21 |
22 | await 1
23 |
24 | if (only && !o)
25 | return
26 |
27 | tests[line] = { fn, line, name }
28 | promise = promise.then(() => Promise.race([
29 | new Promise((resolve, reject) =>
30 | fn.timer = setTimeout(() => reject('Timed out'), (options.timeout || t.timeout) * 1000)
31 | ),
32 | failed
33 | ? (ignored++, ignore)
34 | : fn()
35 | ]))
36 | .then(async x => {
37 | clearTimeout(fn.timer)
38 | if (x === ignore)
39 | return
40 |
41 | if (!Array.isArray(x))
42 | throw new Error('Test should return result array')
43 |
44 | const [expected, got] = await Promise.all(x)
45 | if (expected !== got) {
46 | failed = true
47 | throw new Error(util.inspect(expected) + ' != ' + util.inspect(got))
48 | }
49 |
50 | tests[line].succeeded = true
51 | process.stdout.write('✅')
52 | })
53 | .catch(err => {
54 | tests[line].failed = failed = true
55 | tests[line].error = err instanceof Error ? err : new Error(util.inspect(err))
56 | })
57 | .then(() => {
58 | ++done === Object.keys(tests).length && exit()
59 | })
60 | }
61 |
62 | function exit() {
63 | let success = true
64 | Object.values(tests).every((x) => {
65 | if (x.succeeded)
66 | return true
67 |
68 | success = false
69 | x.cleanup
70 | ? console.error('⛔️', x.name + ' at line', x.line, 'cleanup failed', '\n', util.inspect(x.cleanup))
71 | : console.error('⛔️', x.name + ' at line', x.line, x.failed
72 | ? 'failed'
73 | : 'never finished', x.error ? '\n' + util.inspect(x.error) : ''
74 | )
75 | })
76 |
77 | only
78 | ? console.error('⚠️', 'Not all tests were run')
79 | : ignored
80 | ? console.error('⚠️', ignored, 'ignored test' + (ignored === 1 ? '' : 's', '\n'))
81 | : success
82 | ? console.log('🎉')
83 | : console.error('⚠️', 'Not good')
84 |
85 | !process.exitCode && (!success || only || ignored) && (process.exitCode = 1)
86 | }
87 |
88 |
--------------------------------------------------------------------------------
/transpile.cf.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import path from 'path'
3 |
4 | const empty = x => fs.readdirSync(x).forEach(f => fs.unlinkSync(path.join(x, f)))
5 | , ensureEmpty = x => !fs.existsSync(x) ? fs.mkdirSync(x) : empty(x)
6 | , root = 'cf'
7 | , src = path.join(root, 'src')
8 |
9 | ensureEmpty(src)
10 |
11 | fs.readdirSync('src').forEach(name =>
12 | fs.writeFileSync(
13 | path.join(src, name),
14 | transpile(fs.readFileSync(path.join('src', name), 'utf8'), name, 'src')
15 | )
16 | )
17 |
18 | function transpile(x) {
19 | const timers = x.includes('setImmediate')
20 | ? 'import { setImmediate, clearImmediate } from \'../polyfills.js\'\n'
21 | : ''
22 |
23 | const process = x.includes('process.')
24 | ? 'import { process } from \'../polyfills.js\'\n'
25 | : ''
26 |
27 | const buffer = x.includes('Buffer')
28 | ? 'import { Buffer } from \'node:buffer\'\n'
29 | : ''
30 |
31 | return process + buffer + timers + x
32 | .replace('import net from \'net\'', 'import { net } from \'../polyfills.js\'')
33 | .replace('import tls from \'tls\'', 'import { tls } from \'../polyfills.js\'')
34 | .replace('import crypto from \'crypto\'', 'import { crypto } from \'../polyfills.js\'')
35 | .replace('import os from \'os\'', 'import { os } from \'../polyfills.js\'')
36 | .replace('import fs from \'fs\'', 'import { fs } from \'../polyfills.js\'')
37 | .replace('import { performance } from \'perf_hooks\'', 'import { performance } from \'../polyfills.js\'')
38 | .replace(/ from '([a-z_]+)'/g, ' from \'node:$1\'')
39 | }
40 |
--------------------------------------------------------------------------------
/transpile.cjs:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | , path = require('path')
3 |
4 | const empty = x => fs.readdirSync(x).forEach(f => fs.unlinkSync(path.join(x, f)))
5 | , ensureEmpty = x => !fs.existsSync(x) ? fs.mkdirSync(x) : empty(x)
6 | , root = 'cjs'
7 | , src = path.join(root, 'src')
8 | , tests = path.join(root, 'tests')
9 |
10 | !fs.existsSync(root) && fs.mkdirSync(root)
11 | ensureEmpty(src)
12 | ensureEmpty(tests)
13 |
14 | fs.readdirSync('src').forEach(name =>
15 | fs.writeFileSync(
16 | path.join(src, name),
17 | transpile(fs.readFileSync(path.join('src', name), 'utf8'))
18 | )
19 | )
20 |
21 | fs.readdirSync('tests').forEach(name =>
22 | fs.writeFileSync(
23 | path.join(tests, name),
24 | name.endsWith('.js')
25 | ? transpile(fs.readFileSync(path.join('tests', name), 'utf8'))
26 | : fs.readFileSync(path.join('tests', name), 'utf8')
27 | )
28 | )
29 |
30 | fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify({ type: 'commonjs' }))
31 |
32 | function transpile(x) {
33 | return x.replace(/export default function ([^(]+)/, 'module.exports = $1;function $1')
34 | .replace(/export class ([a-z0-9_$]+)/gi, 'const $1 = module.exports.$1 = class $1')
35 | .replace(/export default /, 'module.exports = ')
36 | .replace(/export {/g, 'module.exports = {')
37 | .replace(/export const ([a-z0-9_$]+)/gi, 'const $1 = module.exports.$1')
38 | .replace(/export function ([a-z0-9_$]+)/gi, 'module.exports.$1 = $1;function $1')
39 | .replace(/import {([^{}]*?)} from (['"].*?['"])/gi, 'const {$1} = require($2)')
40 | .replace(/import (.*?) from (['"].*?['"])/gi, 'const $1 = require($2)')
41 | .replace(/import (['"].*?['"])/gi, 'require($1)')
42 | .replace('new URL(x, import.meta.url)', 'require("path").join(__dirname, x)')
43 | }
44 |
--------------------------------------------------------------------------------
/transpile.deno.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import path from 'path'
3 |
4 | const std = 'https://deno.land/std@0.132.0/'
5 | , empty = x => fs.readdirSync(x).forEach(f => fs.unlinkSync(path.join(x, f)))
6 | , ensureEmpty = x => !fs.existsSync(x) ? fs.mkdirSync(x) : empty(x)
7 | , root = 'deno'
8 | , src = path.join(root, 'src')
9 | , types = path.join(root, 'types')
10 | , tests = path.join(root, 'tests')
11 |
12 | ensureEmpty(src)
13 | ensureEmpty(types)
14 | ensureEmpty(tests)
15 |
16 | fs.writeFileSync(
17 | path.join(types, 'index.d.ts'),
18 | transpile(fs.readFileSync(path.join('types', 'index.d.ts'), 'utf8'), 'index.d.ts', 'types')
19 | )
20 |
21 | fs.writeFileSync(
22 | path.join(root, 'README.md'),
23 | fs.readFileSync('README.md', 'utf8')
24 | .replace(/### Installation(\n.*){4}/, '')
25 | .replace(
26 | 'import postgres from \'postgres\'',
27 | 'import postgres from \'https://deno.land/x/postgresjs/mod.js\''
28 | )
29 | )
30 |
31 | fs.readdirSync('src').forEach(name =>
32 | fs.writeFileSync(
33 | path.join(src, name),
34 | transpile(fs.readFileSync(path.join('src', name), 'utf8'), name, 'src')
35 | )
36 | )
37 |
38 | fs.readdirSync('tests').forEach(name =>
39 | fs.writeFileSync(
40 | path.join(tests, name),
41 | name.endsWith('.js')
42 | ? transpile(fs.readFileSync(path.join('tests', name), 'utf8'), name, 'tests')
43 | : fs.readFileSync(path.join('tests', name), 'utf8')
44 | )
45 | )
46 |
47 | fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify({ type: 'commonjs' }))
48 |
49 | function transpile(x, name, folder) {
50 | if (folder === 'tests') {
51 | if (name === 'bootstrap.js') {
52 | x = x.replace('export function exec(', 'function ignore(')
53 | .replace('async function execAsync(', 'export async function exec(')
54 | .replace(/\nexec\(/g, '\nawait exec(')
55 | .replace('{ spawnSync }', '{ spawn }')
56 | }
57 | if (name === 'index.js')
58 | x += '\n;globalThis.addEventListener("unload", () => Deno.exit(process.exitCode))'
59 | }
60 |
61 | const buffer = x.includes('Buffer')
62 | ? 'import { Buffer } from \'' + std + 'node/buffer.ts\'\n'
63 | : ''
64 |
65 | const process = x.includes('process.')
66 | ? 'import process from \'' + std + 'node/process.ts\'\n'
67 | : ''
68 |
69 | const timers = x.includes('setImmediate')
70 | ? 'import { setImmediate, clearImmediate } from \'../polyfills.js\'\n'
71 | : ''
72 |
73 | const hmac = x.includes('createHmac')
74 | ? 'import { HmacSha256 } from \'' + std + 'hash/sha256.ts\'\n'
75 | : ''
76 |
77 | return hmac + buffer + process + timers + x
78 | .replace(
79 | 'crypto.createHmac(\'sha256\', key).update(x).digest()',
80 | 'Buffer.from(new HmacSha256(key).update(x).digest())'
81 | )
82 | .replace(
83 | 'query.writable.push({ chunk, callback })',
84 | '(query.writable.push({ chunk }), callback())'
85 | )
86 | .replace('socket.setKeepAlive(true, 1000 * keep_alive)', 'socket.setKeepAlive(true)')
87 | .replace('node:stream', std + 'node/stream.ts')
88 | .replace('import net from \'net\'', 'import { net } from \'../polyfills.js\'')
89 | .replace('import tls from \'tls\'', 'import { tls } from \'../polyfills.js\'')
90 | .replace('import { performance } from \'perf_hooks\'', '')
91 | .replace(/ from '([a-z_]+)'/g, ' from \'' + std + 'node/$1.ts\'')
92 | }
93 |
--------------------------------------------------------------------------------
/types/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "devDependencies": {
3 | "@types/node": "^16"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/types/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": [
4 | "ES2015"
5 | ],
6 | "types": [
7 | "node"
8 | ],
9 | "esModuleInterop": true,
10 | "strict": true,
11 | "noImplicitAny": true,
12 | "exactOptionalPropertyTypes": true
13 | }
14 | }
--------------------------------------------------------------------------------