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