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.
- package/dist/cf-do/worker.d.ts +3 -0
- package/dist/cf-do/worker.d.ts.map +1 -1
- package/dist/cf-do/worker.js +37 -15
- package/dist/cf-do/worker.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/package.json +3 -4
- package/src/admin/admin-data.test.ts +0 -348
- package/src/admin/http-proxy.ts +0 -252
- package/src/admin/log-store.ts +0 -192
- package/src/admin/server.ts +0 -471
- package/src/admin/ui.ts +0 -1322
- package/src/bench/proxy-throughput.bench.ts +0 -343
- package/src/bench/serial-mutations.bench.ts +0 -270
- package/src/browser.ts +0 -203
- package/src/cf-do/.wrangler/cache/cf.json +0 -1
- package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-shm +0 -0
- package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-wal +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/0ffaabee41a60e04dd0eb7db3073f0a40139e6a97ccd26823967acb652b89a7b.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-shm +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-wal +0 -0
- package/src/cf-do/.wrangler/tmp/bundle-0z4CpE/middleware-insertion-facade.js +0 -11
- package/src/cf-do/.wrangler/tmp/bundle-0z4CpE/middleware-loader.entry.ts +0 -134
- package/src/cf-do/.wrangler/tmp/bundle-vYmw0E/middleware-insertion-facade.js +0 -11
- package/src/cf-do/.wrangler/tmp/bundle-vYmw0E/middleware-loader.entry.ts +0 -134
- package/src/cf-do/.wrangler/tmp/dev-cbILNo/worker.js +0 -1059
- package/src/cf-do/.wrangler/tmp/dev-cbILNo/worker.js.map +0 -8
- package/src/cf-do/.wrangler/tmp/dev-qbho19/worker.js +0 -1059
- package/src/cf-do/.wrangler/tmp/dev-qbho19/worker.js.map +0 -8
- package/src/cf-do/ARCHITECTURE.md +0 -93
- package/src/cf-do/CHAT_E2E.md +0 -213
- package/src/cf-do/watermark.test.ts +0 -103
- package/src/cf-do/watermark.ts +0 -118
- package/src/cf-do/worker.ts +0 -1041
- package/src/cf-do/wrangler.toml +0 -11
- package/src/cf-pglite/README.md +0 -19
- package/src/change-tracking.ts +0 -25
- package/src/child-process.test.ts +0 -147
- package/src/child-process.ts +0 -90
- package/src/cli-entry.ts +0 -72
- package/src/cli.test.ts +0 -40
- package/src/cli.ts +0 -1214
- package/src/config.ts +0 -150
- package/src/do-sql-tracking.test.ts +0 -19
- package/src/do-sql-tracking.ts +0 -19
- package/src/index.ts +0 -1215
- package/src/integration/integration.test.ts +0 -517
- package/src/integration/native-binary.guard.test.ts +0 -13
- package/src/integration/native-startup.test.ts +0 -44
- package/src/integration/replication-latency.test.ts +0 -428
- package/src/integration/restore-live-stress.test.ts +0 -433
- package/src/integration/restore-reset.test.ts +0 -400
- package/src/integration/restore.test.ts +0 -274
- package/src/integration/test-permissions.ts +0 -147
- package/src/load-config.ts +0 -46
- package/src/log.ts +0 -96
- package/src/mutex.ts +0 -47
- package/src/pg-proxy-browser.singledb.test.ts +0 -233
- package/src/pg-proxy-browser.ts +0 -2022
- package/src/pg-proxy-do-backend.test.ts +0 -3890
- package/src/pg-proxy-do-backend.ts +0 -7191
- package/src/pg-proxy.ts +0 -1087
- package/src/pg-sqlite-compiler/README.md +0 -53
- package/src/pg-sqlite-compiler/catalog/seed.ts +0 -524
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/arithmetic.json +0 -307
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/array.json +0 -377
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/cast.json +0 -12
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/catalog.json +0 -447
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/create-table.json +0 -32
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/datetime.json +0 -397
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/enum.json +0 -337
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/insert.json +0 -337
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/json.json +0 -537
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/misc.json +0 -1837
- package/src/pg-sqlite-compiler/index.ts +0 -73
- package/src/pg-sqlite-compiler/integration.test.ts +0 -136
- package/src/pg-sqlite-compiler/passes/ast-utils.ts +0 -113
- package/src/pg-sqlite-compiler/passes/catalog.ts +0 -65
- package/src/pg-sqlite-compiler/passes/datetime.ts +0 -74
- package/src/pg-sqlite-compiler/passes/index.ts +0 -49
- package/src/pg-sqlite-compiler/passes/types.ts +0 -156
- package/src/pg-sqlite-compiler/smoke.test.ts +0 -69
- package/src/pg-sqlite-compiler/test/catalog.test.ts +0 -171
- package/src/pg-sqlite-compiler/test/corpus.test.ts +0 -161
- package/src/pg-sqlite-compiler/test/datetime.oracle.test.ts +0 -102
- package/src/pg-sqlite-compiler/test/oracle.ts +0 -237
- package/src/pg-sqlite-compiler/test/types.test.ts +0 -109
- package/src/pg-sqlite-compiler/types.ts +0 -63
- package/src/pglite-ipc.test.ts +0 -116
- package/src/pglite-ipc.ts +0 -266
- package/src/pglite-manager.ts +0 -557
- package/src/pglite-web-proxy.test.ts +0 -57
- package/src/pglite-web-proxy.ts +0 -221
- package/src/pglite-web-worker.ts +0 -152
- package/src/pglite-worker-thread.ts +0 -253
- package/src/port.ts +0 -25
- package/src/process-title.ts +0 -9
- package/src/recovery.ts +0 -155
- package/src/replication/change-tracker.test.ts +0 -357
- package/src/replication/change-tracker.ts +0 -279
- package/src/replication/handler.test.ts +0 -511
- package/src/replication/handler.ts +0 -1190
- package/src/replication/pgoutput-encoder.test.ts +0 -697
- package/src/replication/pgoutput-encoder.ts +0 -373
- package/src/replication/tcp-replication.test.ts +0 -876
- package/src/replication/zero-compat.test.ts +0 -1150
- package/src/restore-stress.test.ts +0 -188
- package/src/s3-local.ts +0 -203
- package/src/shim/hooks.mjs +0 -120
- package/src/shim/register.mjs +0 -4
- package/src/sqlite-mode/apply-mode.ts +0 -224
- package/src/sqlite-mode/index.ts +0 -15
- package/src/sqlite-mode/native-binary.ts +0 -89
- package/src/sqlite-mode/package-resolve.ts +0 -17
- package/src/sqlite-mode/resolve-mode.ts +0 -80
- package/src/sqlite-mode/shim-template.ts +0 -159
- package/src/sqlite-mode/sqlite-mode.test.ts +0 -427
- package/src/sqlite-mode/types.ts +0 -30
- package/src/vite-plugin.ts +0 -67
- package/src/wasm-sqlite.test.ts +0 -537
- package/src/worker/browser-admin.ts +0 -52
- package/src/worker/browser-build-config.test.ts +0 -71
- package/src/worker/browser-build-config.ts +0 -109
- package/src/worker/browser-embed-admin.test.ts +0 -75
- package/src/worker/browser-embed.ts +0 -345
- package/src/worker/cf-patches.ts +0 -384
- package/src/worker/embed-integration.test.ts +0 -321
- package/src/worker/index.ts +0 -138
- package/src/worker/shims/fastify.test.ts +0 -255
- package/src/worker/shims/fastify.ts +0 -306
- package/src/worker/shims/http-service.test.ts +0 -355
- package/src/worker/shims/http-service.ts +0 -293
- package/src/worker/shims/node-stub.ts +0 -290
- package/src/worker/shims/oxfmt.ts +0 -3
- package/src/worker/shims/postgres-browser.ts +0 -59
- package/src/worker/shims/postgres-socket.test.ts +0 -576
- package/src/worker/shims/postgres-socket.ts +0 -310
- package/src/worker/shims/postgres.test.ts +0 -364
- package/src/worker/shims/postgres.ts +0 -1454
- package/src/worker/shims/sqlite-browser.test.ts +0 -233
- package/src/worker/shims/sqlite-browser.ts +0 -175
- package/src/worker/shims/sqlite.test.ts +0 -786
- package/src/worker/shims/sqlite.ts +0 -978
- package/src/worker/shims/stream-browser.ts +0 -15
- package/src/worker/shims/ws-browser.test.ts +0 -205
- package/src/worker/shims/ws-browser.ts +0 -248
- package/src/worker/shims/ws.test.ts +0 -288
- package/src/worker/shims/ws.ts +0 -467
- package/src/worker/shims/zero-process-env.ts +0 -11
- package/src/worker/types.ts +0 -75
- package/src/worker/worker-integration.test.ts +0 -223
- package/src/worker/worker.test.ts +0 -136
- package/src/worker/zero-cache-embed-cf.ts +0 -463
- 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
|
-
})
|