orez 0.2.26 → 0.2.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/dist/cf-do/worker.d.ts.map +1 -1
  2. package/dist/cf-do/worker.js +9 -1
  3. package/dist/cf-do/worker.js.map +1 -1
  4. package/dist/pg-proxy-do-backend.d.ts +2 -0
  5. package/dist/pg-proxy-do-backend.d.ts.map +1 -1
  6. package/dist/pg-proxy-do-backend.js +49 -7
  7. package/dist/pg-proxy-do-backend.js.map +1 -1
  8. package/dist/pg-sqlite-compiler/catalog/seed.d.ts +67 -0
  9. package/dist/pg-sqlite-compiler/catalog/seed.d.ts.map +1 -0
  10. package/dist/pg-sqlite-compiler/catalog/seed.js +436 -0
  11. package/dist/pg-sqlite-compiler/catalog/seed.js.map +1 -0
  12. package/dist/pg-sqlite-compiler/index.d.ts +12 -0
  13. package/dist/pg-sqlite-compiler/index.d.ts.map +1 -0
  14. package/dist/pg-sqlite-compiler/index.js +59 -0
  15. package/dist/pg-sqlite-compiler/index.js.map +1 -0
  16. package/dist/pg-sqlite-compiler/passes/ast-utils.d.ts +48 -0
  17. package/dist/pg-sqlite-compiler/passes/ast-utils.d.ts.map +1 -0
  18. package/dist/pg-sqlite-compiler/passes/ast-utils.js +93 -0
  19. package/dist/pg-sqlite-compiler/passes/ast-utils.js.map +1 -0
  20. package/dist/pg-sqlite-compiler/passes/catalog.d.ts +34 -0
  21. package/dist/pg-sqlite-compiler/passes/catalog.d.ts.map +1 -0
  22. package/dist/pg-sqlite-compiler/passes/catalog.js +30 -0
  23. package/dist/pg-sqlite-compiler/passes/catalog.js.map +1 -0
  24. package/dist/pg-sqlite-compiler/passes/datetime.d.ts +21 -0
  25. package/dist/pg-sqlite-compiler/passes/datetime.d.ts.map +1 -0
  26. package/dist/pg-sqlite-compiler/passes/datetime.js +53 -0
  27. package/dist/pg-sqlite-compiler/passes/datetime.js.map +1 -0
  28. package/dist/pg-sqlite-compiler/passes/index.d.ts +21 -0
  29. package/dist/pg-sqlite-compiler/passes/index.d.ts.map +1 -0
  30. package/dist/pg-sqlite-compiler/passes/index.js +39 -0
  31. package/dist/pg-sqlite-compiler/passes/index.js.map +1 -0
  32. package/dist/pg-sqlite-compiler/passes/types.d.ts +41 -0
  33. package/dist/pg-sqlite-compiler/passes/types.d.ts.map +1 -0
  34. package/dist/pg-sqlite-compiler/passes/types.js +103 -0
  35. package/dist/pg-sqlite-compiler/passes/types.js.map +1 -0
  36. package/dist/pg-sqlite-compiler/test/oracle.d.ts +34 -0
  37. package/dist/pg-sqlite-compiler/test/oracle.d.ts.map +1 -0
  38. package/dist/pg-sqlite-compiler/test/oracle.js +204 -0
  39. package/dist/pg-sqlite-compiler/test/oracle.js.map +1 -0
  40. package/dist/pg-sqlite-compiler/types.d.ts +55 -0
  41. package/dist/pg-sqlite-compiler/types.d.ts.map +1 -0
  42. package/dist/pg-sqlite-compiler/types.js +2 -0
  43. package/dist/pg-sqlite-compiler/types.js.map +1 -0
  44. package/package.json +8 -4
  45. package/src/admin/admin-data.test.ts +0 -348
  46. package/src/admin/http-proxy.ts +0 -252
  47. package/src/admin/log-store.ts +0 -192
  48. package/src/admin/server.ts +0 -471
  49. package/src/admin/ui.ts +0 -1322
  50. package/src/bench/proxy-throughput.bench.ts +0 -343
  51. package/src/bench/serial-mutations.bench.ts +0 -270
  52. package/src/browser.ts +0 -203
  53. package/src/cf-do/.wrangler/cache/cf.json +0 -1
  54. package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite +0 -0
  55. package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-shm +0 -0
  56. package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-wal +0 -0
  57. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/0f0f3bdf0abda097eb6f1246db4657d9fc622081362d894d82c1a1ce067b05b6.sqlite +0 -0
  58. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/1ddd3a4a48a11b51658444f5458a1fb175194b1d5b6a5bda20ef3fe3205b900c.sqlite +0 -0
  59. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/204a39120310d37e972c5914cfd71ad55c151bdb9e8ed289a5f8c5b052dd60e4.sqlite +0 -0
  60. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/3835f242df9728adba3d127a238793fd054ed3e51df3f60749ee744c469bf2a2.sqlite +0 -0
  61. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/4aa9c80eb716cf55b8995ccf7afab0b36c683e6da07d7c37a3f9c570136036df.sqlite +0 -0
  62. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/533e2fd1d6ea46e7a9a0017916ef341802d438d72583462755f2c1f8225e9bf2.sqlite +0 -0
  63. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/5ffa1aced1225ecaeac6366f7586aa3de92761cdff8711d81fbd81f248076abd.sqlite +0 -0
  64. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/686c3a9f0d7e59ed2ab607efd4b76d779c97cafeb3818380033bf7c7eb86c819.sqlite +0 -0
  65. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/6e8214e8dcfadd0deb52d64e5e9ca85c6b329ace11193909845995396914c473.sqlite +0 -0
  66. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/78d9ec9ff873d3fe3507ff53c2a6f6dfc408b4268eb0db3f2a146c0678965366.sqlite +0 -0
  67. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/7eff9f0ed7e27ad0d3f9d923de0682fab1928591172c1ba336c5f79a134a5d85.sqlite +0 -0
  68. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/836cda5b995b25867d722ed4f4c2292167e80351a3c6038db626648eb247dd8b.sqlite +0 -0
  69. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/91ef63b112209ab30172763acd8a0935106c248f7f1bcae5545ce37a9f201551.sqlite +0 -0
  70. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/a66ea4293a5f5938bc6d116edfa2522bb85bc37aea3541fbc09c3b613b9b32c0.sqlite +0 -0
  71. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/ceb2ab26b80590840b65651deb6e948d3bf81565c6751f3a58752cf4bf4aecae.sqlite +0 -0
  72. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite +0 -0
  73. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-shm +0 -0
  74. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-wal +0 -0
  75. package/src/cf-do/ARCHITECTURE.md +0 -83
  76. package/src/cf-do/watermark.test.ts +0 -103
  77. package/src/cf-do/watermark.ts +0 -118
  78. package/src/cf-do/worker.ts +0 -1033
  79. package/src/cf-do/wrangler.toml +0 -11
  80. package/src/cf-pglite/README.md +0 -19
  81. package/src/change-tracking.ts +0 -25
  82. package/src/child-process.test.ts +0 -147
  83. package/src/child-process.ts +0 -90
  84. package/src/cli-entry.ts +0 -72
  85. package/src/cli.test.ts +0 -38
  86. package/src/cli.ts +0 -1214
  87. package/src/config.ts +0 -150
  88. package/src/do-sql-tracking.test.ts +0 -19
  89. package/src/do-sql-tracking.ts +0 -19
  90. package/src/index.ts +0 -1215
  91. package/src/integration/integration.test.ts +0 -517
  92. package/src/integration/native-binary.guard.test.ts +0 -13
  93. package/src/integration/native-startup.test.ts +0 -44
  94. package/src/integration/replication-latency.test.ts +0 -428
  95. package/src/integration/restore-live-stress.test.ts +0 -433
  96. package/src/integration/restore-reset.test.ts +0 -400
  97. package/src/integration/restore.test.ts +0 -274
  98. package/src/integration/test-permissions.ts +0 -147
  99. package/src/load-config.ts +0 -46
  100. package/src/log.ts +0 -96
  101. package/src/mutex.ts +0 -47
  102. package/src/pg-proxy-browser.singledb.test.ts +0 -233
  103. package/src/pg-proxy-browser.ts +0 -2022
  104. package/src/pg-proxy-do-backend.test.ts +0 -3890
  105. package/src/pg-proxy-do-backend.ts +0 -7157
  106. package/src/pg-proxy.ts +0 -1087
  107. package/src/pglite-ipc.test.ts +0 -116
  108. package/src/pglite-ipc.ts +0 -266
  109. package/src/pglite-manager.ts +0 -557
  110. package/src/pglite-web-proxy.test.ts +0 -57
  111. package/src/pglite-web-proxy.ts +0 -221
  112. package/src/pglite-web-worker.ts +0 -152
  113. package/src/pglite-worker-thread.ts +0 -253
  114. package/src/port.ts +0 -25
  115. package/src/process-title.ts +0 -9
  116. package/src/recovery.ts +0 -155
  117. package/src/replication/change-tracker.test.ts +0 -357
  118. package/src/replication/change-tracker.ts +0 -279
  119. package/src/replication/handler.test.ts +0 -511
  120. package/src/replication/handler.ts +0 -1190
  121. package/src/replication/pgoutput-encoder.test.ts +0 -697
  122. package/src/replication/pgoutput-encoder.ts +0 -373
  123. package/src/replication/tcp-replication.test.ts +0 -876
  124. package/src/replication/zero-compat.test.ts +0 -1150
  125. package/src/restore-stress.test.ts +0 -188
  126. package/src/s3-local.ts +0 -203
  127. package/src/shim/hooks.mjs +0 -120
  128. package/src/shim/register.mjs +0 -4
  129. package/src/sqlite-mode/apply-mode.ts +0 -224
  130. package/src/sqlite-mode/index.ts +0 -15
  131. package/src/sqlite-mode/native-binary.ts +0 -89
  132. package/src/sqlite-mode/package-resolve.ts +0 -17
  133. package/src/sqlite-mode/resolve-mode.ts +0 -80
  134. package/src/sqlite-mode/shim-template.ts +0 -159
  135. package/src/sqlite-mode/sqlite-mode.test.ts +0 -427
  136. package/src/sqlite-mode/types.ts +0 -30
  137. package/src/vite-plugin.ts +0 -67
  138. package/src/wasm-sqlite.test.ts +0 -537
  139. package/src/worker/browser-admin.ts +0 -52
  140. package/src/worker/browser-build-config.test.ts +0 -71
  141. package/src/worker/browser-build-config.ts +0 -109
  142. package/src/worker/browser-embed-admin.test.ts +0 -75
  143. package/src/worker/browser-embed.ts +0 -345
  144. package/src/worker/cf-patches.ts +0 -384
  145. package/src/worker/embed-integration.test.ts +0 -321
  146. package/src/worker/index.ts +0 -138
  147. package/src/worker/shims/fastify.test.ts +0 -255
  148. package/src/worker/shims/fastify.ts +0 -306
  149. package/src/worker/shims/http-service.test.ts +0 -355
  150. package/src/worker/shims/http-service.ts +0 -293
  151. package/src/worker/shims/node-stub.ts +0 -290
  152. package/src/worker/shims/oxfmt.ts +0 -3
  153. package/src/worker/shims/postgres-browser.ts +0 -59
  154. package/src/worker/shims/postgres-socket.test.ts +0 -576
  155. package/src/worker/shims/postgres-socket.ts +0 -310
  156. package/src/worker/shims/postgres.test.ts +0 -364
  157. package/src/worker/shims/postgres.ts +0 -1454
  158. package/src/worker/shims/sqlite-browser.test.ts +0 -233
  159. package/src/worker/shims/sqlite-browser.ts +0 -175
  160. package/src/worker/shims/sqlite.test.ts +0 -786
  161. package/src/worker/shims/sqlite.ts +0 -978
  162. package/src/worker/shims/stream-browser.ts +0 -15
  163. package/src/worker/shims/ws-browser.test.ts +0 -205
  164. package/src/worker/shims/ws-browser.ts +0 -248
  165. package/src/worker/shims/ws.test.ts +0 -288
  166. package/src/worker/shims/ws.ts +0 -467
  167. package/src/worker/shims/zero-process-env.ts +0 -11
  168. package/src/worker/types.ts +0 -75
  169. package/src/worker/worker-integration.test.ts +0 -223
  170. package/src/worker/worker.test.ts +0 -136
  171. package/src/worker/zero-cache-embed-cf.ts +0 -463
  172. package/src/worker/zero-cache-embed.ts +0 -277
@@ -1,310 +0,0 @@
1
- /**
2
- * MessagePort-backed socket for the postgres npm package.
3
- *
4
- * the postgres package (porsager/postgres) accepts a custom socket factory
5
- * via options.socket. this provides a net.Socket-compatible object backed
6
- * by a MessagePort that connects to pg-proxy-browser.
7
- *
8
- * usage:
9
- * import postgres from 'postgres'
10
- * import { createSocketFactory } from 'orez/worker/shims/postgres-socket'
11
- *
12
- * const sql = postgres({
13
- * socket: createSocketFactory(proxyPort),
14
- * // ... other options
15
- * })
16
- *
17
- * this replaces the postgres.ts shim entirely — the real postgres package
18
- * speaks wire protocol to pg-proxy-browser, just like orez-node speaks
19
- * wire protocol to pg-proxy over TCP.
20
- */
21
-
22
- import { Buffer } from 'buffer'
23
- import { EventEmitter } from 'events'
24
-
25
- /**
26
- * create a socket factory for the postgres npm package.
27
- * each call to the factory creates a new MessageChannel,
28
- * gives one port to the proxy via connectFn, and returns
29
- * a Socket-like object backed by the other port.
30
- */
31
- export function createSocketFactory(connectFn: (port: MessagePort) => void) {
32
- return () => new MessagePortSocket(connectFn)
33
- }
34
-
35
- /**
36
- * net.Socket-compatible object backed by MessagePort.
37
- * implements the full interface that the postgres package uses,
38
- * plus reasonable net.Socket spec compliance for other consumers.
39
- */
40
- class MessagePortSocket extends EventEmitter {
41
- private port: MessagePort | null = null
42
- private channel: MessageChannel | null = null
43
- private _destroyed = false
44
- private _ended = false
45
- private _readyState: 'opening' | 'open' | 'closed' = 'opening'
46
-
47
- // pause/resume buffering for COPY protocol backpressure
48
- private _paused = false
49
- private _pauseBuffer: Buffer[] = []
50
-
51
- // timeout tracking
52
- private _timeoutMs = 0
53
- private _timeoutTimer: ReturnType<typeof setTimeout> | null = null
54
-
55
- // net.Socket compat properties
56
- writable = true
57
- readable = true
58
- bytesRead = 0
59
- bytesWritten = 0
60
-
61
- // postgres may write these on native sockets (skipped for custom, but allow assignment)
62
- ssl?: boolean
63
- host?: string
64
- // port is already used by MessagePort field, use _pgPort for postgres assignment
65
- // actually postgres only writes these for non-custom sockets, so we just need
66
- // the property to be settable without error
67
-
68
- constructor(private connectFn: (port: MessagePort) => void) {
69
- super()
70
- this.channel = new MessageChannel()
71
- this.port = this.channel.port1
72
-
73
- // give server port to pg-proxy-browser
74
- this.connectFn(this.channel.port2)
75
-
76
- // forward incoming data from proxy — wrap as Buffer (postgres needs readUInt32BE etc.)
77
- this.port.onmessage = (ev: MessageEvent) => {
78
- if (this._destroyed) return
79
-
80
- let buf: Buffer | null = null
81
- if (ev.data instanceof ArrayBuffer) {
82
- buf = Buffer.from(new Uint8Array(ev.data))
83
- } else if (ev.data instanceof Uint8Array) {
84
- buf = Buffer.from(ev.data)
85
- }
86
-
87
- if (!buf) return
88
-
89
- this.bytesRead += buf.length
90
- this._resetTimeout()
91
-
92
- if (this._paused) {
93
- this._pauseBuffer.push(buf)
94
- return
95
- }
96
-
97
- this.emit('data', buf)
98
- }
99
-
100
- this.port.start()
101
-
102
- // transition to open and fire connect event async
103
- // for custom sockets postgres calls connected() directly (skips socket.on('connect')),
104
- // but we emit for generic socket compat
105
- queueMicrotask(() => {
106
- if (!this._destroyed) {
107
- this._readyState = 'open'
108
- this.emit('connect')
109
- this.emit('ready')
110
- }
111
- })
112
- }
113
-
114
- get destroyed() {
115
- return this._destroyed
116
- }
117
-
118
- get readyState(): string {
119
- return this._readyState
120
- }
121
-
122
- get connecting() {
123
- return this._readyState === 'opening'
124
- }
125
-
126
- get pending() {
127
- return this._readyState === 'opening'
128
- }
129
-
130
- // postgres calls socket.write(chunk, fn) — returns boolean for backpressure
131
- write(
132
- data: Uint8Array | Buffer | string,
133
- encoding?: any,
134
- callback?: Function
135
- ): boolean {
136
- if (this._destroyed || !this.port) {
137
- if (typeof encoding === 'function') encoding()
138
- else if (typeof callback === 'function') callback()
139
- return false
140
- }
141
-
142
- const bytes: Uint8Array =
143
- typeof data === 'string'
144
- ? Buffer.from(data)
145
- : data instanceof Uint8Array
146
- ? data
147
- : Buffer.from(data)
148
-
149
- // copy before transfer — postgres may reference the buffer after write
150
- const copy = new Uint8Array(bytes.length)
151
- copy.set(bytes)
152
-
153
- try {
154
- this.port.postMessage(copy.buffer)
155
- } catch (err) {
156
- queueMicrotask(() => this.emit('error', err))
157
- if (typeof encoding === 'function') encoding()
158
- else if (typeof callback === 'function') callback()
159
- return false
160
- }
161
-
162
- this.bytesWritten += bytes.length
163
- this._resetTimeout()
164
-
165
- if (typeof encoding === 'function') encoding()
166
- else if (typeof callback === 'function') callback()
167
-
168
- return true
169
- }
170
-
171
- // postgres calls socket.end(terminateMsg) in terminate() then
172
- // registers socket.once('close', resolve). defer destroy so the
173
- // 'close' listener is registered before the event fires.
174
- end(data?: any, encoding?: any, callback?: Function) {
175
- if (typeof data === 'function') {
176
- callback = data
177
- data = undefined
178
- encoding = undefined
179
- } else if (typeof encoding === 'function') {
180
- callback = encoding
181
- encoding = undefined
182
- }
183
-
184
- if (data != null) this.write(data, encoding)
185
- this._ended = true
186
-
187
- // defer destroy to next microtask — terminate() calls socket.end(X_msg)
188
- // then line 408 of connection.js does socket.once('close', resolve).
189
- // synchronous destroy would fire 'close' before that listener exists.
190
- queueMicrotask(() => this.destroy())
191
-
192
- if (typeof callback === 'function') callback()
193
- }
194
-
195
- destroy(err?: Error) {
196
- if (this._destroyed) return this
197
- this._destroyed = true
198
- this._readyState = 'closed'
199
- this.writable = false
200
- this.readable = false
201
-
202
- // clear timeout
203
- if (this._timeoutTimer) {
204
- clearTimeout(this._timeoutTimer)
205
- this._timeoutTimer = null
206
- }
207
-
208
- // clear pause buffer
209
- this._pauseBuffer.length = 0
210
-
211
- if (this.port) {
212
- // delay port.close() to allow pending messages (like Terminate/X)
213
- // to be delivered. closing immediately after postMessage loses
214
- // the message, preventing the proxy from releasing its mutex.
215
- const p = this.port
216
- this.port = null
217
- setTimeout(() => p.close(), 50)
218
- }
219
-
220
- if (err) {
221
- this.emit('error', err)
222
- }
223
- this.emit('end')
224
- this.emit('close', !!err)
225
- return this
226
- }
227
-
228
- // flow control — MessagePort doesn't natively support pause/resume,
229
- // so we buffer incoming messages when paused and flush on resume.
230
- // the postgres COPY protocol relies on this (CopyData calls socket.pause()
231
- // when stream.push() returns false).
232
- pause() {
233
- this._paused = true
234
- return this
235
- }
236
-
237
- resume() {
238
- this._paused = false
239
- // flush buffered messages — exit if data handler re-pauses
240
- while (this._pauseBuffer.length && !this._paused) {
241
- this.emit('data', this._pauseBuffer.shift()!)
242
- }
243
- return this
244
- }
245
-
246
- // timeout — emit 'timeout' after ms of inactivity (no reads or writes).
247
- // postgres calls socket.setKeepAlive conditionally but doesn't use
248
- // setTimeout on custom sockets. still useful for detecting hung connections.
249
- setTimeout(ms: number, cb?: Function) {
250
- this._timeoutMs = ms
251
- if (this._timeoutTimer) {
252
- clearTimeout(this._timeoutTimer)
253
- this._timeoutTimer = null
254
- }
255
- if (cb) this.once('timeout', cb as (...args: any[]) => void)
256
- if (ms > 0) this._resetTimeout()
257
- return this
258
- }
259
-
260
- private _resetTimeout() {
261
- if (this._timeoutTimer) clearTimeout(this._timeoutTimer)
262
- if (this._timeoutMs > 0 && !this._destroyed) {
263
- this._timeoutTimer = globalThis.setTimeout(
264
- () => this.emit('timeout'),
265
- this._timeoutMs
266
- )
267
- }
268
- }
269
-
270
- // no-ops — these configure TCP-level behavior that doesn't apply to MessagePort
271
- setKeepAlive() {
272
- return this
273
- }
274
- setNoDelay() {
275
- return this
276
- }
277
- ref() {
278
- return this
279
- }
280
- unref() {
281
- return this
282
- }
283
- cork() {}
284
- uncork() {}
285
-
286
- // postgres skips connect() for custom sockets, but defensive for generic use
287
- connect() {
288
- return this
289
- }
290
-
291
- // net.Socket address info stubs
292
- address() {
293
- return { address: '127.0.0.1', family: 'IPv4', port: 0 }
294
- }
295
- get remoteAddress() {
296
- return '127.0.0.1'
297
- }
298
- get remotePort() {
299
- return 0
300
- }
301
- get remoteFamily() {
302
- return 'IPv4'
303
- }
304
- get localAddress() {
305
- return '127.0.0.1'
306
- }
307
- get localPort() {
308
- return 0
309
- }
310
- }
@@ -1,364 +0,0 @@
1
- import { PGlite } from '@electric-sql/pglite'
2
- import { describe, it, expect, beforeEach, afterEach } from 'vitest'
3
-
4
- import { createPostgresShim, PostgresError } from './postgres.js'
5
-
6
- describe('postgres shim', () => {
7
- let pglite: PGlite
8
- let sql: ReturnType<typeof createPostgresShim>
9
-
10
- beforeEach(async () => {
11
- pglite = new PGlite()
12
- await pglite.waitReady
13
- sql = createPostgresShim(pglite)
14
-
15
- // set up test table
16
- await sql.unsafe(`
17
- CREATE TABLE test_users (
18
- id SERIAL PRIMARY KEY,
19
- name TEXT NOT NULL,
20
- email TEXT,
21
- active BOOLEAN DEFAULT true,
22
- metadata JSONB,
23
- score NUMERIC
24
- )
25
- `)
26
- })
27
-
28
- afterEach(async () => {
29
- await pglite.close()
30
- })
31
-
32
- // -- tagged template queries --
33
-
34
- describe('tagged template queries', () => {
35
- it('basic select', async () => {
36
- await sql.unsafe(
37
- `INSERT INTO test_users (name, email) VALUES ('alice', 'alice@test.com')`
38
- )
39
- const rows = await sql`SELECT * FROM test_users WHERE name = ${'alice'}`
40
- expect(rows).toHaveLength(1)
41
- expect(rows[0].name).toBe('alice')
42
- expect(rows[0].email).toBe('alice@test.com')
43
- })
44
-
45
- it('multiple parameters', async () => {
46
- await sql.unsafe(
47
- `INSERT INTO test_users (name, email) VALUES ('bob', 'bob@test.com')`
48
- )
49
- await sql.unsafe(
50
- `INSERT INTO test_users (name, email) VALUES ('carol', 'carol@test.com')`
51
- )
52
- const rows =
53
- await sql`SELECT * FROM test_users WHERE name = ${'bob'} OR email = ${'carol@test.com'}`
54
- expect(rows).toHaveLength(2)
55
- })
56
-
57
- it('empty result set', async () => {
58
- const rows = await sql`SELECT * FROM test_users WHERE name = ${'nobody'}`
59
- expect(rows).toHaveLength(0)
60
- expect(rows.length).toBe(0)
61
- })
62
-
63
- it('null parameter', async () => {
64
- await sql`INSERT INTO test_users (name, email) VALUES (${'dave'}, ${null})`
65
- const rows = await sql`SELECT * FROM test_users WHERE name = ${'dave'}`
66
- expect(rows).toHaveLength(1)
67
- expect(rows[0].email).toBeNull()
68
- })
69
-
70
- it('boolean parameter', async () => {
71
- await sql`INSERT INTO test_users (name, active) VALUES (${'eve'}, ${false})`
72
- const rows = await sql`SELECT * FROM test_users WHERE name = ${'eve'}`
73
- expect(rows[0].active).toBe(false)
74
- })
75
-
76
- it('json parameter', async () => {
77
- const meta = { role: 'admin', tags: ['a', 'b'] }
78
- await sql`INSERT INTO test_users (name, metadata) VALUES (${'frank'}, ${meta})`
79
- const rows = await sql`SELECT * FROM test_users WHERE name = ${'frank'}`
80
- expect(rows[0].metadata).toEqual(meta)
81
- })
82
-
83
- it('bigint parameter', async () => {
84
- // bigint gets serialized to string, numeric column can hold it
85
- const big = BigInt('99999999999999999')
86
- await sql`INSERT INTO test_users (name, score) VALUES (${'grace'}, ${big})`
87
- const rows = await sql`SELECT * FROM test_users WHERE name = ${'grace'}`
88
- expect(rows[0].score).toBe('99999999999999999')
89
- })
90
- })
91
-
92
- // -- result format --
93
-
94
- describe('result format', () => {
95
- it('array-like with indexed access', async () => {
96
- await sql.unsafe(`INSERT INTO test_users (name) VALUES ('a'), ('b'), ('c')`)
97
- const rows = await sql`SELECT name FROM test_users ORDER BY name`
98
- expect(rows.length).toBe(3)
99
- expect(rows[0].name).toBe('a')
100
- expect(rows[1].name).toBe('b')
101
- expect(rows[2].name).toBe('c')
102
- })
103
-
104
- it('iterable', async () => {
105
- await sql.unsafe(`INSERT INTO test_users (name) VALUES ('x'), ('y')`)
106
- const rows = await sql`SELECT name FROM test_users ORDER BY name`
107
- const names: string[] = []
108
- for (const row of rows) {
109
- names.push(row.name)
110
- }
111
- expect(names).toEqual(['x', 'y'])
112
- })
113
-
114
- it('destructurable', async () => {
115
- await sql.unsafe(
116
- `INSERT INTO test_users (name, email) VALUES ('zara', 'z@test.com')`
117
- )
118
- const [{ name, email }] =
119
- await sql`SELECT name, email FROM test_users WHERE name = ${'zara'}`
120
- expect(name).toBe('zara')
121
- expect(email).toBe('z@test.com')
122
- })
123
-
124
- it('has count metadata', async () => {
125
- await sql.unsafe(`INSERT INTO test_users (name) VALUES ('a'), ('b')`)
126
- const rows = await sql`SELECT * FROM test_users`
127
- expect(rows.count).toBeGreaterThanOrEqual(2)
128
- })
129
-
130
- it('has command metadata', async () => {
131
- const rows = await sql`SELECT 1 as val`
132
- expect(rows.command).toBe('SELECT')
133
- })
134
-
135
- it('has columns metadata', async () => {
136
- await sql.unsafe(`INSERT INTO test_users (name) VALUES ('test')`)
137
- const rows = await sql`SELECT name, email FROM test_users`
138
- expect(rows.columns).toHaveLength(2)
139
- expect(rows.columns[0].name).toBe('name')
140
- expect(rows.columns[1].name).toBe('email')
141
- })
142
- })
143
-
144
- // -- unsafe queries --
145
-
146
- describe('unsafe queries', () => {
147
- it('DDL without params', async () => {
148
- await sql.unsafe(`CREATE TABLE unsafe_test (id INT)`)
149
- const rows =
150
- await sql`SELECT table_name FROM information_schema.tables WHERE table_name = 'unsafe_test'`
151
- expect(rows).toHaveLength(1)
152
- })
153
-
154
- it('query with params', async () => {
155
- await sql.unsafe(`INSERT INTO test_users (name) VALUES ('unsafe_user')`)
156
- const rows = await sql.unsafe('SELECT * FROM test_users WHERE name = $1', [
157
- 'unsafe_user',
158
- ])
159
- expect(rows).toHaveLength(1)
160
- expect(rows[0].name).toBe('unsafe_user')
161
- })
162
-
163
- it('multi-statement', async () => {
164
- // pglite.query handles single statements; multi-statement DDL
165
- // typically uses exec. unsafe forwards to query which handles most cases.
166
- const result = await sql.unsafe(`SELECT 1 as a`)
167
- expect(result[0].a).toBe(1)
168
- })
169
- })
170
-
171
- // -- transactions --
172
-
173
- describe('transactions', () => {
174
- it('commit on success', async () => {
175
- await sql.begin(async (tx) => {
176
- await tx`INSERT INTO test_users (name) VALUES (${'tx_user'})`
177
- })
178
- const rows = await sql`SELECT * FROM test_users WHERE name = ${'tx_user'}`
179
- expect(rows).toHaveLength(1)
180
- })
181
-
182
- it('rollback on error', async () => {
183
- await expect(
184
- sql.begin(async (tx) => {
185
- await tx`INSERT INTO test_users (name) VALUES (${'rollback_user'})`
186
- throw new Error('intentional rollback')
187
- })
188
- ).rejects.toThrow('intentional rollback')
189
-
190
- const rows = await sql`SELECT * FROM test_users WHERE name = ${'rollback_user'}`
191
- expect(rows).toHaveLength(0)
192
- })
193
-
194
- it('with isolation level string (ignored but accepted)', async () => {
195
- await sql.begin('serializable', async (tx) => {
196
- await tx`INSERT INTO test_users (name) VALUES (${'iso_user'})`
197
- })
198
- const rows = await sql`SELECT * FROM test_users WHERE name = ${'iso_user'}`
199
- expect(rows).toHaveLength(1)
200
- })
201
-
202
- it('tx.unsafe works', async () => {
203
- await sql.begin(async (tx) => {
204
- await tx.unsafe(`INSERT INTO test_users (name) VALUES ('tx_unsafe')`)
205
- })
206
- const rows = await sql`SELECT * FROM test_users WHERE name = ${'tx_unsafe'}`
207
- expect(rows).toHaveLength(1)
208
- })
209
-
210
- it('nested queries in transaction', async () => {
211
- const result = await sql.begin(async (tx) => {
212
- await tx`INSERT INTO test_users (name) VALUES (${'nested_a'})`
213
- await tx`INSERT INTO test_users (name) VALUES (${'nested_b'})`
214
- return tx`SELECT name FROM test_users WHERE name LIKE 'nested_%' ORDER BY name`
215
- })
216
- expect(result).toHaveLength(2)
217
- expect(result[0].name).toBe('nested_a')
218
- expect(result[1].name).toBe('nested_b')
219
- })
220
- })
221
-
222
- // -- PostgresError --
223
-
224
- describe('PostgresError', () => {
225
- it('has correct .code property', () => {
226
- const err = new PostgresError({ message: 'duplicate key', code: '23505' })
227
- expect(err.code).toBe('23505')
228
- expect(err.message).toBe('duplicate key')
229
- expect(err.name).toBe('PostgresError')
230
- expect(err).toBeInstanceOf(Error)
231
- expect(err).toBeInstanceOf(PostgresError)
232
- })
233
-
234
- it('has all standard fields', () => {
235
- const err = new PostgresError({
236
- message: 'test',
237
- code: '42P01',
238
- severity: 'ERROR',
239
- detail: 'some detail',
240
- hint: 'some hint',
241
- schema_name: 'public',
242
- table_name: 'users',
243
- })
244
- expect(err.severity).toBe('ERROR')
245
- expect(err.detail).toBe('some detail')
246
- expect(err.hint).toBe('some hint')
247
- expect(err.schema_name).toBe('public')
248
- expect(err.table_name).toBe('users')
249
- })
250
- })
251
-
252
- // -- identifier escaping --
253
-
254
- describe('identifier escaping', () => {
255
- it('sql(string) returns escaped identifier usable in templates', async () => {
256
- const tableName = 'test_users'
257
- const colName = 'name'
258
- await sql.unsafe(`INSERT INTO test_users (name) VALUES ('ident_test')`)
259
- const rows =
260
- await sql`SELECT ${sql(colName)} FROM ${sql(tableName)} WHERE name = ${'ident_test'}`
261
- expect(rows).toHaveLength(1)
262
- expect(rows[0].name).toBe('ident_test')
263
- })
264
-
265
- it('escapes quotes in identifiers', () => {
266
- const ident = sql('my"table')
267
- expect(ident.value).toBe('"my""table"')
268
- })
269
-
270
- it('escapes dots as schema separators', () => {
271
- const ident = sql('public.users')
272
- expect(ident.value).toBe('"public"."users"')
273
- })
274
- })
275
-
276
- // -- options and metadata --
277
-
278
- describe('options and metadata', () => {
279
- it('sql.options has expected shape', () => {
280
- expect(sql.options).toBeDefined()
281
- expect(sql.options.host).toEqual(['localhost'])
282
- expect(sql.options.port).toEqual([5432])
283
- expect(sql.options.database).toBe('pglite')
284
- expect(sql.options.max).toBe(1)
285
- expect(typeof sql.options.fetch_types).toBe('boolean')
286
- })
287
-
288
- it('sql.PostgresError is the error class', () => {
289
- expect(sql.PostgresError).toBe(PostgresError)
290
- })
291
-
292
- it('sql.end() resolves without error', async () => {
293
- await expect(sql.end()).resolves.toBeUndefined()
294
- })
295
- })
296
-
297
- // -- simple() modifier --
298
-
299
- describe('query modifiers', () => {
300
- it('.simple() returns the same pending query', async () => {
301
- await sql.unsafe(`INSERT INTO test_users (name) VALUES ('simple_test')`)
302
- const rows =
303
- await sql`SELECT * FROM test_users WHERE name = ${'simple_test'}`.simple()
304
- expect(rows).toHaveLength(1)
305
- })
306
- })
307
-
308
- // -- error propagation --
309
-
310
- describe('error propagation', () => {
311
- it('propagates SQL errors from tagged template', async () => {
312
- await expect(sql`SELECT * FROM nonexistent_table`).rejects.toThrow()
313
- })
314
-
315
- it('propagates SQL errors from unsafe', async () => {
316
- await expect(sql.unsafe('SELECT * FROM nonexistent_table')).rejects.toThrow()
317
- })
318
- })
319
-
320
- // -- multi-statement queries --
321
-
322
- describe('multi-statement DDL', () => {
323
- it('handles multi-statement via unsafe()', async () => {
324
- await sql.unsafe(`
325
- CREATE SCHEMA IF NOT EXISTS test_schema;
326
- CREATE TABLE IF NOT EXISTS test_schema.items (
327
- id TEXT PRIMARY KEY,
328
- value TEXT
329
- )
330
- `)
331
- // verify schema and table were created
332
- const result =
333
- await sql`SELECT table_name FROM information_schema.tables WHERE table_schema = 'test_schema'`
334
- expect(result.length).toBeGreaterThan(0)
335
- })
336
-
337
- it('handles multi-statement via tagged template', async () => {
338
- await sql`
339
- CREATE SCHEMA IF NOT EXISTS test_schema2;
340
- CREATE TABLE IF NOT EXISTS test_schema2.things (
341
- id TEXT PRIMARY KEY,
342
- name TEXT
343
- )
344
- `
345
- const result =
346
- await sql`SELECT table_name FROM information_schema.tables WHERE table_schema = 'test_schema2'`
347
- expect(result.length).toBeGreaterThan(0)
348
- })
349
-
350
- it('handles multi-statement with quoted identifiers containing special chars', async () => {
351
- await sql.unsafe(`
352
- CREATE SCHEMA IF NOT EXISTS "zero_0/cvr";
353
- CREATE TABLE IF NOT EXISTS "zero_0/cvr"."clients" (
354
- "clientGroupID" TEXT NOT NULL,
355
- "clientID" TEXT NOT NULL,
356
- PRIMARY KEY ("clientGroupID", "clientID")
357
- )
358
- `)
359
- const result =
360
- await sql`SELECT table_name FROM information_schema.tables WHERE table_schema = 'zero_0/cvr'`
361
- expect(result.length).toBeGreaterThan(0)
362
- })
363
- })
364
- })