prisma-pglite-bridge 1.0.0 → 1.1.0
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/index.cjs +145 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -4
- package/dist/index.d.mts +8 -4
- package/dist/index.mjs +145 -40
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -109,6 +109,38 @@ const EQP_MESSAGES = new Set([
|
|
|
109
109
|
const MAX_BACKEND_MESSAGE_LENGTH = 1073741824;
|
|
110
110
|
//#endregion
|
|
111
111
|
//#region src/duplex/row-description.ts
|
|
112
|
+
const readUInt32BE = (buf, offset) => {
|
|
113
|
+
/* c8 ignore start — callers guard fixed-width reads */
|
|
114
|
+
const b1 = buf[offset] ?? 0;
|
|
115
|
+
const b2 = buf[offset + 1] ?? 0;
|
|
116
|
+
const b3 = buf[offset + 2] ?? 0;
|
|
117
|
+
const b4 = buf[offset + 3] ?? 0;
|
|
118
|
+
/* c8 ignore stop */
|
|
119
|
+
return (b1 << 24 | b2 << 16 | b3 << 8 | b4) >>> 0;
|
|
120
|
+
};
|
|
121
|
+
const readUInt16BE = (buf, offset) => {
|
|
122
|
+
/* c8 ignore start — callers guard fixed-width reads */
|
|
123
|
+
const b1 = buf[offset] ?? 0;
|
|
124
|
+
const b2 = buf[offset + 1] ?? 0;
|
|
125
|
+
/* c8 ignore stop */
|
|
126
|
+
return b1 << 8 | b2;
|
|
127
|
+
};
|
|
128
|
+
const rowDescriptionNeedsRewrite = (buf, start = 0, end = buf.length) => {
|
|
129
|
+
if (end - start < 7) return false;
|
|
130
|
+
const fieldCount = readUInt16BE(buf, start + 5);
|
|
131
|
+
let p = start + 7;
|
|
132
|
+
for (let i = 0; i < fieldCount; i++) {
|
|
133
|
+
while (p < end && buf[p] !== 0) p++;
|
|
134
|
+
p++;
|
|
135
|
+
/* c8 ignore next — defense-in-depth: framer caller passes a complete frame */
|
|
136
|
+
if (p + 18 > end) return false;
|
|
137
|
+
const tableOID = readUInt32BE(buf, p);
|
|
138
|
+
p += 6;
|
|
139
|
+
if (readUInt32BE(buf, p) === 18 && tableOID !== 0 && tableOID < 16384) return true;
|
|
140
|
+
p += 12;
|
|
141
|
+
}
|
|
142
|
+
return false;
|
|
143
|
+
};
|
|
112
144
|
/**
|
|
113
145
|
* Widens any field whose dataTypeOID is 18 ("char") to oid 25 (text) — but
|
|
114
146
|
* only when the field originates from a pg_catalog relation (tableOID is
|
|
@@ -121,6 +153,7 @@ const MAX_BACKEND_MESSAGE_LENGTH = 1073741824;
|
|
|
121
153
|
* fixed-size in place.
|
|
122
154
|
*/
|
|
123
155
|
const rewriteRowDescriptionInPlace = (buf) => {
|
|
156
|
+
if (!rowDescriptionNeedsRewrite(buf)) return;
|
|
124
157
|
const fieldCount = buf.readInt16BE(5);
|
|
125
158
|
let p = 7;
|
|
126
159
|
for (let i = 0; i < fieldCount; i++) {
|
|
@@ -213,7 +246,7 @@ var BackendMessageFramer = class {
|
|
|
213
246
|
this.emitReadyForQuery();
|
|
214
247
|
this.rfqBytesRead = 0;
|
|
215
248
|
}
|
|
216
|
-
} else if (msgType === 84) {
|
|
249
|
+
} else if (msgType === 84) if (rowDescriptionNeedsRewrite(chunk, offset, offset + totalLen)) {
|
|
217
250
|
flushPassthrough(offset);
|
|
218
251
|
if (this.suppressIntermediateReadyForQuery && this.rfqBytesRead === 6) this.dropHeldReadyForQuery();
|
|
219
252
|
this.emitRewrittenRowDescription(Buffer.from(chunk.subarray(offset, offset + totalLen)));
|
|
@@ -221,6 +254,10 @@ var BackendMessageFramer = class {
|
|
|
221
254
|
if (this.suppressIntermediateReadyForQuery && this.rfqBytesRead === 6) this.dropHeldReadyForQuery();
|
|
222
255
|
if (passthroughStart < 0) passthroughStart = offset;
|
|
223
256
|
}
|
|
257
|
+
else {
|
|
258
|
+
if (this.suppressIntermediateReadyForQuery && this.rfqBytesRead === 6) this.dropHeldReadyForQuery();
|
|
259
|
+
if (passthroughStart < 0) passthroughStart = offset;
|
|
260
|
+
}
|
|
224
261
|
offset += totalLen;
|
|
225
262
|
continue;
|
|
226
263
|
}
|
|
@@ -502,6 +539,15 @@ var FrontendMessageBuffer = class {
|
|
|
502
539
|
* after each sub-message (PGlite's single-user mode). These are stripped,
|
|
503
540
|
* keeping only the final ReadyForQuery after Sync.
|
|
504
541
|
*/
|
|
542
|
+
const TERMINATE_MESSAGE = new Uint8Array([
|
|
543
|
+
88,
|
|
544
|
+
0,
|
|
545
|
+
0,
|
|
546
|
+
0,
|
|
547
|
+
4
|
|
548
|
+
]);
|
|
549
|
+
const PROTOCOL_CLEANUP_RAW_BYTES = 8 * 1024 * 1024;
|
|
550
|
+
const PROTOCOL_CLEANUP_CALLS = 32;
|
|
505
551
|
/**
|
|
506
552
|
* Duplex stream that bridges `pg.Client` to an in-process PGlite instance.
|
|
507
553
|
*
|
|
@@ -553,6 +599,9 @@ var PGliteDuplex = class extends node_stream.Duplex {
|
|
|
553
599
|
/** Memoized rollback so concurrent teardown paths (e.g., `_final` then
|
|
554
600
|
* `_destroy`) don't issue duplicate `ROLLBACK` statements. */
|
|
555
601
|
rollbackPromise;
|
|
602
|
+
pendingProtocolCleanupBytes = 0;
|
|
603
|
+
pendingProtocolCleanupCalls = 0;
|
|
604
|
+
protocolCleanupUnsupported = false;
|
|
556
605
|
/** Resolves once the stream has fully torn down (post-`_final` rollback,
|
|
557
606
|
* post-`_destroy`). Single-shot, mirroring the `'close'` event. */
|
|
558
607
|
onClose;
|
|
@@ -766,20 +815,38 @@ var PGliteDuplex = class extends node_stream.Duplex {
|
|
|
766
815
|
let batch;
|
|
767
816
|
/* c8 ignore next 3 — flushPipeline only runs after Sync is appended */
|
|
768
817
|
if (messages.length === 1) batch = messages[0] ?? new Uint8Array(0);
|
|
769
|
-
else
|
|
770
|
-
const total = messages.reduce((sum, p) => sum + p.length, 0);
|
|
771
|
-
batch = new Uint8Array(total);
|
|
772
|
-
let offset = 0;
|
|
773
|
-
for (const part of messages) {
|
|
774
|
-
batch.set(part, offset);
|
|
775
|
-
offset += part.length;
|
|
776
|
-
}
|
|
777
|
-
}
|
|
818
|
+
else batch = this.tryContiguousPipelineBatch(messages) ?? this.concatPipeline(messages);
|
|
778
819
|
await this.runWithTiming((detectErrors) => this.streamProtocol(batch, {
|
|
779
820
|
detectErrors,
|
|
780
821
|
suppressIntermediateRfq: true
|
|
781
822
|
}));
|
|
782
823
|
}
|
|
824
|
+
tryContiguousPipelineBatch(messages) {
|
|
825
|
+
const first = messages[0];
|
|
826
|
+
/* c8 ignore next — caller only passes non-empty pipelines */
|
|
827
|
+
if (first === void 0) return void 0;
|
|
828
|
+
const buffer = first.buffer;
|
|
829
|
+
const start = first.byteOffset;
|
|
830
|
+
let end = start + first.byteLength;
|
|
831
|
+
for (let i = 1; i < messages.length; i++) {
|
|
832
|
+
const part = messages[i];
|
|
833
|
+
/* c8 ignore next — pipeline array has no holes */
|
|
834
|
+
if (part === void 0) return void 0;
|
|
835
|
+
if (part.buffer !== buffer || part.byteOffset !== end) return;
|
|
836
|
+
end += part.byteLength;
|
|
837
|
+
}
|
|
838
|
+
return new Uint8Array(buffer, start, end - start);
|
|
839
|
+
}
|
|
840
|
+
concatPipeline(messages) {
|
|
841
|
+
const total = messages.reduce((sum, p) => sum + p.length, 0);
|
|
842
|
+
const batch = new Uint8Array(total);
|
|
843
|
+
let offset = 0;
|
|
844
|
+
for (const part of messages) {
|
|
845
|
+
batch.set(part, offset);
|
|
846
|
+
offset += part.length;
|
|
847
|
+
}
|
|
848
|
+
return batch;
|
|
849
|
+
}
|
|
783
850
|
/**
|
|
784
851
|
* Acquires the session, runs the op under `pglite.runExclusive`, and
|
|
785
852
|
* updates internal stats and/or publishes diagnostics events when enabled.
|
|
@@ -835,10 +902,8 @@ var PGliteDuplex = class extends node_stream.Duplex {
|
|
|
835
902
|
}
|
|
836
903
|
}
|
|
837
904
|
/**
|
|
838
|
-
* Sends a message (or pipelined batch) to PGlite and pushes
|
|
839
|
-
*
|
|
840
|
-
* concatenating for large multi-row responses (e.g., findMany 500 rows
|
|
841
|
-
* = ~503 onRawData chunks).
|
|
905
|
+
* Sends a message (or pipelined batch) to PGlite and pushes the raw protocol
|
|
906
|
+
* response to the stream.
|
|
842
907
|
*
|
|
843
908
|
* For pipelined Extended Query batches, pass `suppressIntermediateRfq`
|
|
844
909
|
* so only the final ReadyForQuery reaches the client.
|
|
@@ -863,15 +928,47 @@ var PGliteDuplex = class extends node_stream.Duplex {
|
|
|
863
928
|
}
|
|
864
929
|
});
|
|
865
930
|
await waitPGliteReady(this.pglite, this.timeout);
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
931
|
+
let rawBytes = 0;
|
|
932
|
+
let streamFailed = false;
|
|
933
|
+
try {
|
|
934
|
+
await this.pglite.execProtocolRawStream(message, {
|
|
935
|
+
syncToFs: this.syncToFs,
|
|
936
|
+
onRawData: (chunk) => {
|
|
937
|
+
rawBytes += chunk.byteLength;
|
|
938
|
+
framer.write(chunk);
|
|
939
|
+
}
|
|
940
|
+
});
|
|
941
|
+
} catch (err) {
|
|
942
|
+
streamFailed = true;
|
|
943
|
+
throw err;
|
|
944
|
+
} finally {
|
|
945
|
+
this.pendingProtocolCleanupBytes += rawBytes;
|
|
946
|
+
this.pendingProtocolCleanupCalls++;
|
|
947
|
+
if (!this.protocolCleanupUnsupported && (streamFailed || this.pendingProtocolCleanupBytes >= PROTOCOL_CLEANUP_RAW_BYTES || this.pendingProtocolCleanupCalls >= PROTOCOL_CLEANUP_CALLS)) {
|
|
948
|
+
await this.clearPGliteProtocolMessages();
|
|
949
|
+
this.pendingProtocolCleanupBytes = 0;
|
|
950
|
+
this.pendingProtocolCleanupCalls = 0;
|
|
870
951
|
}
|
|
871
|
-
}
|
|
952
|
+
}
|
|
872
953
|
framer.flush({ dropHeldReadyForQuery: this.tornDown });
|
|
873
954
|
return !errSeen;
|
|
874
955
|
}
|
|
956
|
+
async clearPGliteProtocolMessages() {
|
|
957
|
+
if (this.protocolCleanupUnsupported) return;
|
|
958
|
+
const { execProtocolStream } = this.pglite;
|
|
959
|
+
if (typeof execProtocolStream !== "function") {
|
|
960
|
+
this.protocolCleanupUnsupported = true;
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
try {
|
|
964
|
+
await execProtocolStream.call(this.pglite, TERMINATE_MESSAGE, {
|
|
965
|
+
syncToFs: false,
|
|
966
|
+
throwOnError: false
|
|
967
|
+
});
|
|
968
|
+
} catch {
|
|
969
|
+
this.protocolCleanupUnsupported = true;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
875
972
|
acquireSession() {
|
|
876
973
|
return this.sessionLock?.acquire(this.duplexId);
|
|
877
974
|
}
|
|
@@ -1109,7 +1206,7 @@ const wrapTypesWithFastArrayParsers = (types) => {
|
|
|
1109
1206
|
//#endregion
|
|
1110
1207
|
//#region src/pool/pg-bridge-client.ts
|
|
1111
1208
|
var PgBridgeClient = class PgBridgeClient extends pg.default.Client {
|
|
1112
|
-
querySubmissionChain
|
|
1209
|
+
querySubmissionChain;
|
|
1113
1210
|
static OptionsKey = Symbol("PgBridgeClientOptions");
|
|
1114
1211
|
constructor(config) {
|
|
1115
1212
|
const { [PgBridgeClient.OptionsKey]: bridge, ...clientConfig } = config ?? {};
|
|
@@ -1123,33 +1220,41 @@ var PgBridgeClient = class PgBridgeClient extends pg.default.Client {
|
|
|
1123
1220
|
}
|
|
1124
1221
|
query(...args) {
|
|
1125
1222
|
const first = args[0];
|
|
1126
|
-
const
|
|
1127
|
-
|
|
1128
|
-
if (typeof first.submit === "function") return callSuper();
|
|
1129
|
-
if (isObject(first) && isTypesLike(first.types)) args[0] = {
|
|
1130
|
-
...first,
|
|
1131
|
-
types: wrapTypesWithFastArrayParsers(first.types)
|
|
1223
|
+
const submit = () => {
|
|
1224
|
+
return super.query.apply(this, args);
|
|
1132
1225
|
};
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
this.querySubmissionChain = new Promise((resolve) => {
|
|
1136
|
-
signalDone = resolve;
|
|
1137
|
-
});
|
|
1226
|
+
if (first === null || first === void 0) return submit();
|
|
1227
|
+
if (typeof first.submit === "function") return submit();
|
|
1138
1228
|
const cbIndex = args.findIndex((arg) => typeof arg === "function");
|
|
1139
1229
|
if (cbIndex !== -1) {
|
|
1140
1230
|
const origCb = args[cbIndex];
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
signalDone();
|
|
1231
|
+
const promiseArgs = args.slice();
|
|
1232
|
+
promiseArgs.splice(cbIndex, 1);
|
|
1233
|
+
try {
|
|
1234
|
+
this.query(...promiseArgs).then((res) => origCb(null, res), (err) => origCb(err, void 0));
|
|
1235
|
+
} catch (err) {
|
|
1147
1236
|
origCb(err, void 0);
|
|
1148
|
-
}
|
|
1237
|
+
}
|
|
1149
1238
|
return;
|
|
1150
1239
|
}
|
|
1151
|
-
|
|
1152
|
-
|
|
1240
|
+
if (isObject(first) && isTypesLike(first.types)) args[0] = {
|
|
1241
|
+
...first,
|
|
1242
|
+
types: wrapTypesWithFastArrayParsers(first.types)
|
|
1243
|
+
};
|
|
1244
|
+
const prior = this.querySubmissionChain;
|
|
1245
|
+
let p;
|
|
1246
|
+
if (prior === void 0) try {
|
|
1247
|
+
p = submit();
|
|
1248
|
+
} catch (err) {
|
|
1249
|
+
return Promise.reject(err);
|
|
1250
|
+
}
|
|
1251
|
+
else p = prior.then(submit);
|
|
1252
|
+
let done;
|
|
1253
|
+
const clearChain = () => {
|
|
1254
|
+
if (this.querySubmissionChain === done) this.querySubmissionChain = void 0;
|
|
1255
|
+
};
|
|
1256
|
+
done = p.then(clearChain, clearChain);
|
|
1257
|
+
this.querySubmissionChain = done;
|
|
1153
1258
|
return p;
|
|
1154
1259
|
}
|
|
1155
1260
|
};
|