orez 0.2.27 → 0.2.30

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 (157) hide show
  1. package/dist/cf-do/worker.d.ts +3 -0
  2. package/dist/cf-do/worker.d.ts.map +1 -1
  3. package/dist/cf-do/worker.js +37 -15
  4. package/dist/cf-do/worker.js.map +1 -1
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +8 -0
  7. package/dist/index.js.map +1 -1
  8. package/package.json +3 -4
  9. package/src/admin/admin-data.test.ts +0 -348
  10. package/src/admin/http-proxy.ts +0 -252
  11. package/src/admin/log-store.ts +0 -192
  12. package/src/admin/server.ts +0 -471
  13. package/src/admin/ui.ts +0 -1322
  14. package/src/bench/proxy-throughput.bench.ts +0 -343
  15. package/src/bench/serial-mutations.bench.ts +0 -270
  16. package/src/browser.ts +0 -203
  17. package/src/cf-do/.wrangler/cache/cf.json +0 -1
  18. package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite +0 -0
  19. package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-shm +0 -0
  20. package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-wal +0 -0
  21. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/0ffaabee41a60e04dd0eb7db3073f0a40139e6a97ccd26823967acb652b89a7b.sqlite +0 -0
  22. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite +0 -0
  23. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-shm +0 -0
  24. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-wal +0 -0
  25. package/src/cf-do/.wrangler/tmp/bundle-0z4CpE/middleware-insertion-facade.js +0 -11
  26. package/src/cf-do/.wrangler/tmp/bundle-0z4CpE/middleware-loader.entry.ts +0 -134
  27. package/src/cf-do/.wrangler/tmp/bundle-vYmw0E/middleware-insertion-facade.js +0 -11
  28. package/src/cf-do/.wrangler/tmp/bundle-vYmw0E/middleware-loader.entry.ts +0 -134
  29. package/src/cf-do/.wrangler/tmp/dev-cbILNo/worker.js +0 -1059
  30. package/src/cf-do/.wrangler/tmp/dev-cbILNo/worker.js.map +0 -8
  31. package/src/cf-do/.wrangler/tmp/dev-qbho19/worker.js +0 -1059
  32. package/src/cf-do/.wrangler/tmp/dev-qbho19/worker.js.map +0 -8
  33. package/src/cf-do/ARCHITECTURE.md +0 -93
  34. package/src/cf-do/CHAT_E2E.md +0 -213
  35. package/src/cf-do/watermark.test.ts +0 -103
  36. package/src/cf-do/watermark.ts +0 -118
  37. package/src/cf-do/worker.ts +0 -1041
  38. package/src/cf-do/wrangler.toml +0 -11
  39. package/src/cf-pglite/README.md +0 -19
  40. package/src/change-tracking.ts +0 -25
  41. package/src/child-process.test.ts +0 -147
  42. package/src/child-process.ts +0 -90
  43. package/src/cli-entry.ts +0 -72
  44. package/src/cli.test.ts +0 -40
  45. package/src/cli.ts +0 -1214
  46. package/src/config.ts +0 -150
  47. package/src/do-sql-tracking.test.ts +0 -19
  48. package/src/do-sql-tracking.ts +0 -19
  49. package/src/index.ts +0 -1215
  50. package/src/integration/integration.test.ts +0 -517
  51. package/src/integration/native-binary.guard.test.ts +0 -13
  52. package/src/integration/native-startup.test.ts +0 -44
  53. package/src/integration/replication-latency.test.ts +0 -428
  54. package/src/integration/restore-live-stress.test.ts +0 -433
  55. package/src/integration/restore-reset.test.ts +0 -400
  56. package/src/integration/restore.test.ts +0 -274
  57. package/src/integration/test-permissions.ts +0 -147
  58. package/src/load-config.ts +0 -46
  59. package/src/log.ts +0 -96
  60. package/src/mutex.ts +0 -47
  61. package/src/pg-proxy-browser.singledb.test.ts +0 -233
  62. package/src/pg-proxy-browser.ts +0 -2022
  63. package/src/pg-proxy-do-backend.test.ts +0 -3890
  64. package/src/pg-proxy-do-backend.ts +0 -7191
  65. package/src/pg-proxy.ts +0 -1087
  66. package/src/pg-sqlite-compiler/README.md +0 -53
  67. package/src/pg-sqlite-compiler/catalog/seed.ts +0 -524
  68. package/src/pg-sqlite-compiler/fixtures/pgsqlite/arithmetic.json +0 -307
  69. package/src/pg-sqlite-compiler/fixtures/pgsqlite/array.json +0 -377
  70. package/src/pg-sqlite-compiler/fixtures/pgsqlite/cast.json +0 -12
  71. package/src/pg-sqlite-compiler/fixtures/pgsqlite/catalog.json +0 -447
  72. package/src/pg-sqlite-compiler/fixtures/pgsqlite/create-table.json +0 -32
  73. package/src/pg-sqlite-compiler/fixtures/pgsqlite/datetime.json +0 -397
  74. package/src/pg-sqlite-compiler/fixtures/pgsqlite/enum.json +0 -337
  75. package/src/pg-sqlite-compiler/fixtures/pgsqlite/insert.json +0 -337
  76. package/src/pg-sqlite-compiler/fixtures/pgsqlite/json.json +0 -537
  77. package/src/pg-sqlite-compiler/fixtures/pgsqlite/misc.json +0 -1837
  78. package/src/pg-sqlite-compiler/index.ts +0 -73
  79. package/src/pg-sqlite-compiler/integration.test.ts +0 -136
  80. package/src/pg-sqlite-compiler/passes/ast-utils.ts +0 -113
  81. package/src/pg-sqlite-compiler/passes/catalog.ts +0 -65
  82. package/src/pg-sqlite-compiler/passes/datetime.ts +0 -74
  83. package/src/pg-sqlite-compiler/passes/index.ts +0 -49
  84. package/src/pg-sqlite-compiler/passes/types.ts +0 -156
  85. package/src/pg-sqlite-compiler/smoke.test.ts +0 -69
  86. package/src/pg-sqlite-compiler/test/catalog.test.ts +0 -171
  87. package/src/pg-sqlite-compiler/test/corpus.test.ts +0 -161
  88. package/src/pg-sqlite-compiler/test/datetime.oracle.test.ts +0 -102
  89. package/src/pg-sqlite-compiler/test/oracle.ts +0 -237
  90. package/src/pg-sqlite-compiler/test/types.test.ts +0 -109
  91. package/src/pg-sqlite-compiler/types.ts +0 -63
  92. package/src/pglite-ipc.test.ts +0 -116
  93. package/src/pglite-ipc.ts +0 -266
  94. package/src/pglite-manager.ts +0 -557
  95. package/src/pglite-web-proxy.test.ts +0 -57
  96. package/src/pglite-web-proxy.ts +0 -221
  97. package/src/pglite-web-worker.ts +0 -152
  98. package/src/pglite-worker-thread.ts +0 -253
  99. package/src/port.ts +0 -25
  100. package/src/process-title.ts +0 -9
  101. package/src/recovery.ts +0 -155
  102. package/src/replication/change-tracker.test.ts +0 -357
  103. package/src/replication/change-tracker.ts +0 -279
  104. package/src/replication/handler.test.ts +0 -511
  105. package/src/replication/handler.ts +0 -1190
  106. package/src/replication/pgoutput-encoder.test.ts +0 -697
  107. package/src/replication/pgoutput-encoder.ts +0 -373
  108. package/src/replication/tcp-replication.test.ts +0 -876
  109. package/src/replication/zero-compat.test.ts +0 -1150
  110. package/src/restore-stress.test.ts +0 -188
  111. package/src/s3-local.ts +0 -203
  112. package/src/shim/hooks.mjs +0 -120
  113. package/src/shim/register.mjs +0 -4
  114. package/src/sqlite-mode/apply-mode.ts +0 -224
  115. package/src/sqlite-mode/index.ts +0 -15
  116. package/src/sqlite-mode/native-binary.ts +0 -89
  117. package/src/sqlite-mode/package-resolve.ts +0 -17
  118. package/src/sqlite-mode/resolve-mode.ts +0 -80
  119. package/src/sqlite-mode/shim-template.ts +0 -159
  120. package/src/sqlite-mode/sqlite-mode.test.ts +0 -427
  121. package/src/sqlite-mode/types.ts +0 -30
  122. package/src/vite-plugin.ts +0 -67
  123. package/src/wasm-sqlite.test.ts +0 -537
  124. package/src/worker/browser-admin.ts +0 -52
  125. package/src/worker/browser-build-config.test.ts +0 -71
  126. package/src/worker/browser-build-config.ts +0 -109
  127. package/src/worker/browser-embed-admin.test.ts +0 -75
  128. package/src/worker/browser-embed.ts +0 -345
  129. package/src/worker/cf-patches.ts +0 -384
  130. package/src/worker/embed-integration.test.ts +0 -321
  131. package/src/worker/index.ts +0 -138
  132. package/src/worker/shims/fastify.test.ts +0 -255
  133. package/src/worker/shims/fastify.ts +0 -306
  134. package/src/worker/shims/http-service.test.ts +0 -355
  135. package/src/worker/shims/http-service.ts +0 -293
  136. package/src/worker/shims/node-stub.ts +0 -290
  137. package/src/worker/shims/oxfmt.ts +0 -3
  138. package/src/worker/shims/postgres-browser.ts +0 -59
  139. package/src/worker/shims/postgres-socket.test.ts +0 -576
  140. package/src/worker/shims/postgres-socket.ts +0 -310
  141. package/src/worker/shims/postgres.test.ts +0 -364
  142. package/src/worker/shims/postgres.ts +0 -1454
  143. package/src/worker/shims/sqlite-browser.test.ts +0 -233
  144. package/src/worker/shims/sqlite-browser.ts +0 -175
  145. package/src/worker/shims/sqlite.test.ts +0 -786
  146. package/src/worker/shims/sqlite.ts +0 -978
  147. package/src/worker/shims/stream-browser.ts +0 -15
  148. package/src/worker/shims/ws-browser.test.ts +0 -205
  149. package/src/worker/shims/ws-browser.ts +0 -248
  150. package/src/worker/shims/ws.test.ts +0 -288
  151. package/src/worker/shims/ws.ts +0 -467
  152. package/src/worker/shims/zero-process-env.ts +0 -11
  153. package/src/worker/types.ts +0 -75
  154. package/src/worker/worker-integration.test.ts +0 -223
  155. package/src/worker/worker.test.ts +0 -136
  156. package/src/worker/zero-cache-embed-cf.ts +0 -463
  157. 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
- })