orez 0.1.43 → 0.1.44
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/admin/http-proxy.d.ts.map +1 -1
- package/dist/admin/http-proxy.js +3 -1
- package/dist/admin/http-proxy.js.map +1 -1
- package/dist/admin/log-store.d.ts.map +1 -1
- package/dist/admin/log-store.js +5 -1
- package/dist/admin/log-store.js.map +1 -1
- package/dist/admin/server.d.ts.map +1 -1
- package/dist/admin/server.js +25 -25
- package/dist/admin/server.js.map +1 -1
- package/dist/browser.d.ts +54 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +110 -0
- package/dist/browser.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/pg-proxy-browser.d.ts +26 -0
- package/dist/pg-proxy-browser.d.ts.map +1 -0
- package/dist/pg-proxy-browser.js +1460 -0
- package/dist/pg-proxy-browser.js.map +1 -0
- package/dist/pg-proxy.d.ts.map +1 -1
- package/dist/pg-proxy.js +48 -34
- package/dist/pg-proxy.js.map +1 -1
- package/dist/pglite-ipc.d.ts.map +1 -1
- package/dist/pglite-ipc.js +3 -2
- package/dist/pglite-ipc.js.map +1 -1
- package/dist/pglite-manager.d.ts.map +1 -1
- package/dist/pglite-manager.js +33 -85
- package/dist/pglite-manager.js.map +1 -1
- package/dist/pglite-web-proxy.d.ts +38 -0
- package/dist/pglite-web-proxy.d.ts.map +1 -0
- package/dist/pglite-web-proxy.js +155 -0
- package/dist/pglite-web-proxy.js.map +1 -0
- package/dist/pglite-web-worker.d.ts +24 -0
- package/dist/pglite-web-worker.d.ts.map +1 -0
- package/dist/pglite-web-worker.js +119 -0
- package/dist/pglite-web-worker.js.map +1 -0
- package/dist/recovery.js +2 -2
- package/dist/recovery.js.map +1 -1
- package/dist/replication/change-tracker.js +9 -9
- package/dist/replication/change-tracker.js.map +1 -1
- package/dist/replication/handler.d.ts.map +1 -1
- package/dist/replication/handler.js +34 -26
- package/dist/replication/handler.js.map +1 -1
- package/dist/worker/browser-build-config.d.ts.map +1 -1
- package/dist/worker/browser-build-config.js +5 -2
- package/dist/worker/browser-build-config.js.map +1 -1
- package/dist/worker/browser-embed.d.ts.map +1 -1
- package/dist/worker/browser-embed.js +31 -26
- package/dist/worker/browser-embed.js.map +1 -1
- package/dist/worker/shims/fastify.d.ts +1 -0
- package/dist/worker/shims/fastify.d.ts.map +1 -1
- package/dist/worker/shims/fastify.js +31 -20
- package/dist/worker/shims/fastify.js.map +1 -1
- package/dist/worker/shims/postgres-browser.d.ts +12 -0
- package/dist/worker/shims/postgres-browser.d.ts.map +1 -0
- package/dist/worker/shims/postgres-browser.js +52 -0
- package/dist/worker/shims/postgres-browser.js.map +1 -0
- package/dist/worker/shims/postgres-socket.d.ts +83 -0
- package/dist/worker/shims/postgres-socket.d.ts.map +1 -0
- package/dist/worker/shims/postgres-socket.js +278 -0
- package/dist/worker/shims/postgres-socket.js.map +1 -0
- package/dist/worker/shims/postgres.d.ts.map +1 -1
- package/dist/worker/shims/postgres.js +18 -9
- package/dist/worker/shims/postgres.js.map +1 -1
- package/dist/worker/shims/stream-browser.d.ts +5 -4
- package/dist/worker/shims/stream-browser.d.ts.map +1 -1
- package/dist/worker/shims/stream-browser.js +7 -6
- package/dist/worker/shims/stream-browser.js.map +1 -1
- package/dist/worker/shims/ws-browser.d.ts.map +1 -1
- package/dist/worker/shims/ws-browser.js +43 -21
- package/dist/worker/shims/ws-browser.js.map +1 -1
- package/dist/worker/shims/ws.d.ts.map +1 -1
- package/dist/worker/shims/ws.js +81 -17
- package/dist/worker/shims/ws.js.map +1 -1
- package/package.json +11 -58
- package/src/admin/http-proxy.ts +4 -1
- package/src/admin/log-store.ts +5 -1
- package/src/admin/server.ts +26 -25
- package/src/browser.ts +195 -0
- package/src/cli.ts +1 -1
- package/src/index.ts +5 -2
- package/src/integration/integration.test.ts +1 -1
- package/src/integration/restore-live-stress.test.ts +2 -2
- package/src/pg-proxy-browser.ts +1673 -0
- package/src/pg-proxy.ts +48 -40
- package/src/pglite-ipc.ts +3 -2
- package/src/pglite-manager.ts +45 -107
- package/src/pglite-web-proxy.ts +180 -0
- package/src/pglite-web-worker.ts +132 -0
- package/src/recovery.ts +2 -2
- package/src/replication/change-tracker.test.ts +1 -1
- package/src/replication/change-tracker.ts +9 -9
- package/src/replication/handler.ts +37 -26
- package/src/worker/browser-build-config.test.ts +1 -1
- package/src/worker/browser-build-config.ts +5 -2
- package/src/worker/browser-embed.ts +33 -30
- package/src/worker/shims/fastify.ts +37 -24
- package/src/worker/shims/postgres-browser.ts +59 -0
- package/src/worker/shims/postgres-socket.test.ts +576 -0
- package/src/worker/shims/postgres-socket.ts +310 -0
- package/src/worker/shims/postgres.ts +30 -15
- package/src/worker/shims/stream-browser.ts +15 -0
- package/src/worker/shims/ws-browser.ts +38 -20
- package/src/worker/shims/ws.ts +76 -21
|
@@ -0,0 +1,310 @@
|
|
|
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, [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
|
+
}
|
|
@@ -701,15 +701,22 @@ async function executeQuery(
|
|
|
701
701
|
if (intercepted) return intercepted
|
|
702
702
|
}
|
|
703
703
|
|
|
704
|
-
// strip FK constraints
|
|
705
|
-
//
|
|
706
|
-
//
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
704
|
+
// strip FK constraints — PGlite doesn't support cross-schema FKs,
|
|
705
|
+
// and browser single-process mode doesn't need FK enforcement.
|
|
706
|
+
// covers CREATE TABLE inline FKs and ALTER TABLE ADD CONSTRAINT FKs.
|
|
707
|
+
if (/FOREIGN\s+KEY/i.test(text)) {
|
|
708
|
+
if (/CREATE\s+TABLE/i.test(text)) {
|
|
709
|
+
text = text.replace(
|
|
710
|
+
/,?\s*(?:CONSTRAINT\s+\w+\s+)?FOREIGN\s+KEY\s*\([^)]*\)\s*REFERENCES\s+[^,(]+(?:\s*\([^)]*\))?(?:\s+ON\s+(?:DELETE|UPDATE)\s+(?:CASCADE|SET\s+NULL|SET\s+DEFAULT|RESTRICT|NO\s+ACTION))*(?:\s+DEFERRABLE[^,)]*)?/gi,
|
|
711
|
+
''
|
|
712
|
+
)
|
|
713
|
+
}
|
|
714
|
+
if (/ALTER\s+TABLE/i.test(text) && /ADD\s+CONSTRAINT/i.test(text)) {
|
|
715
|
+
text = text.replace(
|
|
716
|
+
/ALTER\s+TABLE\s+[^\s]+\s+ADD\s+CONSTRAINT\s+[^\s]+\s*\n?\s*FOREIGN\s+KEY\s*\([^)]*\)\s*\n?\s*REFERENCES\s+[^;]+/gi,
|
|
717
|
+
'SELECT 1'
|
|
718
|
+
)
|
|
719
|
+
}
|
|
713
720
|
}
|
|
714
721
|
|
|
715
722
|
const isMulti = hasMultipleStatements(text)
|
|
@@ -1147,12 +1154,20 @@ export function createPostgresShim(pglite: PGlite, opts?: PostgresShimOptions) {
|
|
|
1147
1154
|
return createCopyPendingQuery(queryString, pglite)
|
|
1148
1155
|
}
|
|
1149
1156
|
|
|
1150
|
-
// strip FK constraints
|
|
1151
|
-
if (/FOREIGN\s+KEY/i.test(queryString)
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1157
|
+
// strip FK constraints (see executeQuery for why)
|
|
1158
|
+
if (/FOREIGN\s+KEY/i.test(queryString)) {
|
|
1159
|
+
if (/CREATE\s+TABLE/i.test(queryString)) {
|
|
1160
|
+
queryString = queryString.replace(
|
|
1161
|
+
/,?\s*(?:CONSTRAINT\s+\w+\s+)?FOREIGN\s+KEY\s*\([^)]*\)\s*REFERENCES\s+[^,(]+(?:\s*\([^)]*\))?(?:\s+ON\s+(?:DELETE|UPDATE)\s+(?:CASCADE|SET\s+NULL|SET\s+DEFAULT|RESTRICT|NO\s+ACTION))*(?:\s+DEFERRABLE[^,)]*)?/gi,
|
|
1162
|
+
''
|
|
1163
|
+
)
|
|
1164
|
+
}
|
|
1165
|
+
if (/ALTER\s+TABLE/i.test(queryString) && /ADD\s+CONSTRAINT/i.test(queryString)) {
|
|
1166
|
+
queryString = queryString.replace(
|
|
1167
|
+
/ALTER\s+TABLE\s+[^\s]+\s+ADD\s+CONSTRAINT\s+[^\s]+\s*\n?\s*FOREIGN\s+KEY\s*\([^)]*\)\s*\n?\s*REFERENCES\s+[^;]+/gi,
|
|
1168
|
+
'SELECT 1'
|
|
1169
|
+
)
|
|
1170
|
+
}
|
|
1156
1171
|
}
|
|
1157
1172
|
|
|
1158
1173
|
const serializedParams = (params ?? []).map(serializeParam)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stream shim — re-exports readable-stream with missing Node.js stream functions.
|
|
3
|
+
*
|
|
4
|
+
* readable-stream/stream-browserify don't include getDefaultHighWaterMark
|
|
5
|
+
* which zero-cache uses (added in Node.js 18+).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// @ts-expect-error — readable-stream is CJS
|
|
9
|
+
export * from 'readable-stream'
|
|
10
|
+
// @ts-expect-error — readable-stream is CJS
|
|
11
|
+
export { default } from 'readable-stream'
|
|
12
|
+
|
|
13
|
+
export function getDefaultHighWaterMark(objectMode: boolean): number {
|
|
14
|
+
return objectMode ? 16 : 16 * 1024
|
|
15
|
+
}
|
|
@@ -47,37 +47,56 @@ interface WsCompatible {
|
|
|
47
47
|
* listener — which would silently drop messages.
|
|
48
48
|
*/
|
|
49
49
|
export function messagePortToWs(port: MessagePort): WsCompatible {
|
|
50
|
-
|
|
50
|
+
// separate listener sets for on() vs addEventListener() to match real ws behavior.
|
|
51
|
+
// in the ws package, on() is EventEmitter-style and addEventListener() is DOM-style.
|
|
52
|
+
// createWebSocketStream uses ws.on('message') exclusively for inbound data.
|
|
53
|
+
// streamOut uses ws.addEventListener('message') for ack handling.
|
|
54
|
+
// if they share the same set, ack messages reach both handlers and
|
|
55
|
+
// handleMessage tries to parse acks as protocol messages, closing the connection.
|
|
56
|
+
const onListeners = new Map<string, Set<(event: any) => void>>()
|
|
57
|
+
const domListeners = new Map<string, Set<(event: any) => void>>()
|
|
51
58
|
let closed = false
|
|
52
59
|
|
|
53
|
-
// buffer messages until a 'message' listener is registered
|
|
60
|
+
// buffer messages until a 'message' listener is registered (either kind)
|
|
54
61
|
const pendingMessages: any[] = []
|
|
55
62
|
|
|
56
|
-
function
|
|
57
|
-
if (!
|
|
58
|
-
|
|
63
|
+
function addOnListener(type: string, handler: (event: any) => void) {
|
|
64
|
+
if (!onListeners.has(type)) onListeners.set(type, new Set())
|
|
65
|
+
onListeners.get(type)!.add(handler)
|
|
66
|
+
if (type === 'message' && pendingMessages.length > 0) {
|
|
67
|
+
const queued = pendingMessages.splice(0)
|
|
68
|
+
for (const event of queued) handler(event)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function removeOnListener(type: string, handler: (event: any) => void) {
|
|
73
|
+
onListeners.get(type)?.delete(handler)
|
|
74
|
+
}
|
|
59
75
|
|
|
60
|
-
|
|
76
|
+
function addDomListener(type: string, handler: (event: any) => void) {
|
|
77
|
+
if (!domListeners.has(type)) domListeners.set(type, new Set())
|
|
78
|
+
domListeners.get(type)!.add(handler)
|
|
61
79
|
if (type === 'message' && pendingMessages.length > 0) {
|
|
62
80
|
const queued = pendingMessages.splice(0)
|
|
63
81
|
for (const event of queued) handler(event)
|
|
64
82
|
}
|
|
65
83
|
}
|
|
66
84
|
|
|
67
|
-
function
|
|
68
|
-
|
|
85
|
+
function removeDomListener(type: string, handler: (event: any) => void) {
|
|
86
|
+
domListeners.get(type)?.delete(handler)
|
|
69
87
|
}
|
|
70
88
|
|
|
71
89
|
function emit(type: string, event: any) {
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
90
|
+
const onHandlers = onListeners.get(type)
|
|
91
|
+
const domHandlers = domListeners.get(type)
|
|
92
|
+
const hasAny =
|
|
93
|
+
(onHandlers && onHandlers.size > 0) || (domHandlers && domHandlers.size > 0)
|
|
94
|
+
if (!hasAny) {
|
|
95
|
+
if (type === 'message') pendingMessages.push(event)
|
|
78
96
|
return
|
|
79
97
|
}
|
|
80
|
-
for (const h of
|
|
98
|
+
if (onHandlers) for (const h of onHandlers) h(event)
|
|
99
|
+
if (domHandlers) for (const h of domHandlers) h(event)
|
|
81
100
|
}
|
|
82
101
|
|
|
83
102
|
// forward port messages → ws 'message' events
|
|
@@ -102,7 +121,6 @@ export function messagePortToWs(port: MessagePort): WsCompatible {
|
|
|
102
121
|
|
|
103
122
|
send(data: string | ArrayBuffer | ArrayBufferView) {
|
|
104
123
|
if (closed) return
|
|
105
|
-
// MessagePort uses postMessage (structured clone)
|
|
106
124
|
port.postMessage(data)
|
|
107
125
|
},
|
|
108
126
|
|
|
@@ -113,10 +131,10 @@ export function messagePortToWs(port: MessagePort): WsCompatible {
|
|
|
113
131
|
emit('close', { code: code ?? 1000, reason: '', wasClean: true })
|
|
114
132
|
},
|
|
115
133
|
|
|
116
|
-
addEventListener:
|
|
117
|
-
removeEventListener:
|
|
118
|
-
on:
|
|
119
|
-
off:
|
|
134
|
+
addEventListener: addDomListener,
|
|
135
|
+
removeEventListener: removeDomListener,
|
|
136
|
+
on: addOnListener,
|
|
137
|
+
off: removeOnListener,
|
|
120
138
|
}
|
|
121
139
|
}
|
|
122
140
|
|
package/src/worker/shims/ws.ts
CHANGED
|
@@ -63,8 +63,10 @@ class WebSocket extends EventEmitter {
|
|
|
63
63
|
|
|
64
64
|
if (isInProcess) {
|
|
65
65
|
// in-process: connect via fastify server's handoff mechanism
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
// try all registered fastify instances via tryHandoff, stop at first match
|
|
67
|
+
const instances: any[] = (globalThis as any).__orez_fastify_instances || []
|
|
68
|
+
const fallbackInstance = (globalThis as any).__orez_fastify_instance
|
|
69
|
+
if (instances.length > 0 || fallbackInstance?.server) {
|
|
68
70
|
// create paired message channels for bidirectional communication
|
|
69
71
|
// the client-side WS (this) and serverWs are cross-linked so
|
|
70
72
|
// ping/pong, messages, and close propagate between them
|
|
@@ -74,7 +76,8 @@ class WebSocket extends EventEmitter {
|
|
|
74
76
|
_listeners: {} as Record<string, Function[]>,
|
|
75
77
|
send: (data: string | ArrayBuffer) => {
|
|
76
78
|
// deliver to client side
|
|
77
|
-
|
|
79
|
+
// wrap in event object — zero-cache handleMessage reads event.data
|
|
80
|
+
queueMicrotask(() => clientSide.emit('message', { data }))
|
|
78
81
|
},
|
|
79
82
|
close: (code?: number, reason?: string) => {
|
|
80
83
|
serverWs.readyState = 3
|
|
@@ -98,10 +101,13 @@ class WebSocket extends EventEmitter {
|
|
|
98
101
|
},
|
|
99
102
|
}
|
|
100
103
|
|
|
104
|
+
// client-side internal ws — forwards send() to server,
|
|
105
|
+
// receives messages from server via addEventListener.
|
|
106
|
+
// addEventListener MUST work because WebSocket#setupListeners registers here.
|
|
107
|
+
const clientWsListeners: Record<string, Function[]> = {}
|
|
101
108
|
this.#ws = {
|
|
102
109
|
accept: () => {},
|
|
103
110
|
send: (data: string | ArrayBuffer) => {
|
|
104
|
-
// deliver to server side
|
|
105
111
|
const handlers = serverWs._listeners['message'] || []
|
|
106
112
|
for (const h of handlers) h({ data })
|
|
107
113
|
},
|
|
@@ -109,27 +115,49 @@ class WebSocket extends EventEmitter {
|
|
|
109
115
|
const handlers = serverWs._listeners['close'] || []
|
|
110
116
|
for (const h of handlers) h({ code, reason })
|
|
111
117
|
},
|
|
112
|
-
addEventListener: () => {
|
|
113
|
-
|
|
118
|
+
addEventListener: (type: string, handler: Function) => {
|
|
119
|
+
if (!clientWsListeners[type]) clientWsListeners[type] = []
|
|
120
|
+
clientWsListeners[type].push(handler)
|
|
121
|
+
},
|
|
122
|
+
removeEventListener: (type: string, handler: Function) => {
|
|
123
|
+
const arr = clientWsListeners[type]
|
|
124
|
+
if (arr) {
|
|
125
|
+
const idx = arr.indexOf(handler)
|
|
126
|
+
if (idx >= 0) arr.splice(idx, 1)
|
|
127
|
+
}
|
|
128
|
+
},
|
|
114
129
|
get readyState() {
|
|
115
130
|
return 1
|
|
116
131
|
},
|
|
117
132
|
} as CFWebSocket
|
|
118
133
|
|
|
119
|
-
//
|
|
134
|
+
// wire server → client: when server sends data, deliver to client's
|
|
135
|
+
// addEventListener handlers AND emit on the WebSocket instance
|
|
136
|
+
const origServerSend = serverWs.send
|
|
137
|
+
serverWs.send = (data: string | ArrayBuffer) => {
|
|
138
|
+
const handlers = clientWsListeners['message'] || []
|
|
139
|
+
for (const h of handlers) h({ data })
|
|
140
|
+
queueMicrotask(() => clientSide.emit('message', { data }))
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// try handoff against all fastify instances, stop at first match
|
|
120
144
|
const path = parsedUrl.pathname + parsedUrl.search
|
|
145
|
+
const handoffMsg = {
|
|
146
|
+
message: { url: path, headers: {}, method: 'GET' },
|
|
147
|
+
head: new Uint8Array(0),
|
|
148
|
+
}
|
|
121
149
|
queueMicrotask(() => {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
serverWs
|
|
132
|
-
|
|
150
|
+
let handled = false
|
|
151
|
+
for (const inst of instances) {
|
|
152
|
+
if (inst?.tryHandoff?.(handoffMsg, serverWs)) {
|
|
153
|
+
handled = true
|
|
154
|
+
break
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// fallback: if no instance handled it and we have a fallback, emit directly
|
|
158
|
+
if (!handled && fallbackInstance?.server) {
|
|
159
|
+
fallbackInstance.server.emit('message', ['handoff', handoffMsg], serverWs)
|
|
160
|
+
}
|
|
133
161
|
this.emit('open')
|
|
134
162
|
})
|
|
135
163
|
} else {
|
|
@@ -228,13 +256,40 @@ class WebSocket extends EventEmitter {
|
|
|
228
256
|
}
|
|
229
257
|
|
|
230
258
|
// standard EventTarget-style addEventListener (used by Connection)
|
|
259
|
+
// for 'message' events, wrap the handler so EventEmitter-style (data, isBinary)
|
|
260
|
+
// args get converted to DOM-style { data } events. streamOut uses
|
|
261
|
+
// addEventListener('message', ({data}) => ...) which needs DOM-style events.
|
|
262
|
+
#adapterMap = new WeakMap<Function, Function>()
|
|
263
|
+
|
|
231
264
|
addEventListener(type: string, handler: (event: any) => void): void {
|
|
232
|
-
|
|
233
|
-
|
|
265
|
+
if (type === 'message') {
|
|
266
|
+
const wrapper = (data: any, isBinary?: boolean) => {
|
|
267
|
+
// if already a DOM-style event object with .data, pass through
|
|
268
|
+
if (data && typeof data === 'object' && 'data' in data) {
|
|
269
|
+
handler(data)
|
|
270
|
+
} else {
|
|
271
|
+
handler({ data, isBinary })
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
this.#adapterMap.set(handler, wrapper)
|
|
275
|
+
this.on(type, wrapper)
|
|
276
|
+
} else {
|
|
277
|
+
this.on(type, handler)
|
|
278
|
+
}
|
|
234
279
|
}
|
|
235
280
|
|
|
236
281
|
removeEventListener(type: string, handler: (event: any) => void): void {
|
|
237
|
-
|
|
282
|
+
if (type === 'message') {
|
|
283
|
+
const wrapper = this.#adapterMap.get(handler)
|
|
284
|
+
if (wrapper) {
|
|
285
|
+
this.off(type, wrapper as any)
|
|
286
|
+
this.#adapterMap.delete(handler)
|
|
287
|
+
} else {
|
|
288
|
+
this.off(type, handler)
|
|
289
|
+
}
|
|
290
|
+
} else {
|
|
291
|
+
this.off(type, handler)
|
|
292
|
+
}
|
|
238
293
|
}
|
|
239
294
|
|
|
240
295
|
#setupListeners(): void {
|