prisma-pglite-bridge 0.5.1 → 0.5.3
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/README.md +22 -16
- package/dist/index.cjs +75 -45
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -5
- package/dist/index.d.mts +8 -5
- package/dist/index.mjs +75 -45
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -15,18 +15,6 @@ pnpm add -D prisma-pglite-bridge @electric-sql/pglite @prisma/adapter-pg pg
|
|
|
15
15
|
The last three are peer dependencies you may already have.
|
|
16
16
|
TypeScript users also need `@types/pg`.
|
|
17
17
|
|
|
18
|
-
### Bridge fs-sync policy
|
|
19
|
-
|
|
20
|
-
The adapter defaults `syncToFs` to `'auto'`:
|
|
21
|
-
|
|
22
|
-
- in-memory PGlite (`new PGlite()` or `memory://...`) resolves to `false`
|
|
23
|
-
- persistent `dataDir` usage resolves to `true`
|
|
24
|
-
|
|
25
|
-
That keeps bridge-heavy test workloads on the lower-memory fast path
|
|
26
|
-
without changing durability defaults for persistent databases.
|
|
27
|
-
If you use a custom `fs`, set `syncToFs` explicitly because the
|
|
28
|
-
adapter cannot infer whether that storage is durable.
|
|
29
|
-
|
|
30
18
|
## Quickstart
|
|
31
19
|
|
|
32
20
|
```typescript
|
|
@@ -77,6 +65,18 @@ it from environment variables, network input, or any value that
|
|
|
77
65
|
crosses a trust boundary, and keep the migrations directory
|
|
78
66
|
writable only by trusted processes.
|
|
79
67
|
|
|
68
|
+
## Bridge fs-sync policy
|
|
69
|
+
|
|
70
|
+
The adapter defaults `syncToFs` to `'auto'`:
|
|
71
|
+
|
|
72
|
+
- in-memory PGlite (`new PGlite()` or `memory://...`) resolves to `false`
|
|
73
|
+
- persistent `dataDir` usage resolves to `true`
|
|
74
|
+
|
|
75
|
+
That keeps bridge-heavy test workloads on the lower-memory fast path
|
|
76
|
+
without changing durability defaults for persistent databases.
|
|
77
|
+
If you use a custom `fs`, set `syncToFs` explicitly because the
|
|
78
|
+
adapter cannot infer whether that storage is durable.
|
|
79
|
+
|
|
80
80
|
## API
|
|
81
81
|
|
|
82
82
|
### `createPgliteAdapter(options)`
|
|
@@ -107,15 +107,20 @@ Returns:
|
|
|
107
107
|
Note: this clears all data including seed data — re-seed after
|
|
108
108
|
reset if needed.
|
|
109
109
|
- `close()` — shuts down the pool. The caller-supplied PGlite
|
|
110
|
-
instance is not closed — you own its lifecycle.
|
|
111
|
-
|
|
112
|
-
|
|
110
|
+
instance is not closed — you own its lifecycle. Recommended in
|
|
111
|
+
explicit test teardown, long-running scripts, and dev servers so
|
|
112
|
+
the pool is released promptly and leak warnings do not fire.
|
|
113
113
|
- `stats()` — returns telemetry when `statsLevel` is `'basic'` or
|
|
114
114
|
`'full'`, else `undefined`. See [Stats collection](#stats-collection).
|
|
115
115
|
- `adapterId` — a unique `symbol` identifying this adapter. Use it
|
|
116
116
|
to filter events from the public
|
|
117
117
|
[diagnostics channels](#diagnostics-channels) when multiple
|
|
118
118
|
adapters share a process.
|
|
119
|
+
- `snapshotDb()` — captures the current DB contents into an internal
|
|
120
|
+
snapshot so later `resetDb()` calls restore to that state instead of
|
|
121
|
+
truncating to empty.
|
|
122
|
+
- `resetSnapshot()` — discards the current snapshot so later
|
|
123
|
+
`resetDb()` calls truncate back to empty again.
|
|
119
124
|
|
|
120
125
|
### `createPool(options)`
|
|
121
126
|
|
|
@@ -137,7 +142,7 @@ Returns `pool` (pg.Pool), `adapterId` (a unique `symbol` for
|
|
|
137
142
|
[diagnostics channel](#diagnostics-channels) filtering), and
|
|
138
143
|
`close()` (which shuts down the pool only — the caller-supplied
|
|
139
144
|
PGlite instance is not closed). Accepts `pglite` (required),
|
|
140
|
-
`max`, and `
|
|
145
|
+
`max`, `adapterId`, and `syncToFs`.
|
|
141
146
|
|
|
142
147
|
### `PGliteBridge`
|
|
143
148
|
|
|
@@ -186,6 +191,7 @@ the in-memory PGlite version:
|
|
|
186
191
|
import { PGlite } from '@electric-sql/pglite';
|
|
187
192
|
import { createPgliteAdapter } from 'prisma-pglite-bridge';
|
|
188
193
|
import { PrismaClient } from '@prisma/client';
|
|
194
|
+
import { beforeEach, vi } from 'vitest';
|
|
189
195
|
|
|
190
196
|
const pglite = new PGlite();
|
|
191
197
|
const { adapter, resetDb } = await createPgliteAdapter({
|
package/dist/index.cjs
CHANGED
|
@@ -585,7 +585,10 @@ var PGliteBridge = class extends node_stream.Duplex {
|
|
|
585
585
|
const message = this.input.consume(len);
|
|
586
586
|
await this.acquireSession();
|
|
587
587
|
await this.pglite.runExclusive(async () => {
|
|
588
|
-
await this.
|
|
588
|
+
await this.streamProtocol(message, {
|
|
589
|
+
detectErrors: false,
|
|
590
|
+
suppressIntermediateRfq: false
|
|
591
|
+
});
|
|
589
592
|
});
|
|
590
593
|
this.phase = "ready";
|
|
591
594
|
}
|
|
@@ -623,7 +626,10 @@ var PGliteBridge = class extends node_stream.Duplex {
|
|
|
623
626
|
await this.flushPipeline();
|
|
624
627
|
continue;
|
|
625
628
|
}
|
|
626
|
-
await this.runWithTiming((detectErrors) => this.
|
|
629
|
+
await this.runWithTiming((detectErrors) => this.streamProtocol(message, {
|
|
630
|
+
detectErrors,
|
|
631
|
+
suppressIntermediateRfq: false
|
|
632
|
+
}));
|
|
627
633
|
}
|
|
628
634
|
}
|
|
629
635
|
/**
|
|
@@ -640,7 +646,10 @@ var PGliteBridge = class extends node_stream.Duplex {
|
|
|
640
646
|
const messages = this.pipeline;
|
|
641
647
|
this.pipeline = [];
|
|
642
648
|
const batch = concat(messages);
|
|
643
|
-
await this.runWithTiming((detectErrors) => this.
|
|
649
|
+
await this.runWithTiming((detectErrors) => this.streamProtocol(batch, {
|
|
650
|
+
detectErrors,
|
|
651
|
+
suppressIntermediateRfq: true
|
|
652
|
+
}));
|
|
644
653
|
}
|
|
645
654
|
/**
|
|
646
655
|
* Acquires the session, runs the op under `pglite.runExclusive`, and
|
|
@@ -694,42 +703,22 @@ var PGliteBridge = class extends node_stream.Duplex {
|
|
|
694
703
|
});
|
|
695
704
|
}
|
|
696
705
|
}
|
|
697
|
-
async runPipelineBatch(batch, detectErrors) {
|
|
698
|
-
let errSeen = false;
|
|
699
|
-
const framer = new BackendMessageFramer({
|
|
700
|
-
suppressIntermediateReadyForQuery: true,
|
|
701
|
-
onChunk: (chunk) => {
|
|
702
|
-
/* c8 ignore next — race-only: tornDown becomes true mid-stream */
|
|
703
|
-
if (!this.tornDown && chunk.length > 0) this.push(chunk);
|
|
704
|
-
},
|
|
705
|
-
onErrorResponse: () => {
|
|
706
|
-
if (detectErrors) errSeen = true;
|
|
707
|
-
},
|
|
708
|
-
onReadyForQuery: (status) => {
|
|
709
|
-
if (this.sessionLock) this.sessionLock.updateStatus(this.bridgeId, status);
|
|
710
|
-
}
|
|
711
|
-
});
|
|
712
|
-
await this.pglite.execProtocolRawStream(batch, {
|
|
713
|
-
syncToFs: this.syncToFs,
|
|
714
|
-
onRawData: (chunk) => {
|
|
715
|
-
/* c8 ignore next — race-only: tornDown becomes true mid-stream */
|
|
716
|
-
if (!this.tornDown) framer.write(chunk);
|
|
717
|
-
}
|
|
718
|
-
});
|
|
719
|
-
framer.flush({ dropHeldReadyForQuery: this.tornDown });
|
|
720
|
-
return !errSeen;
|
|
721
|
-
}
|
|
722
706
|
/**
|
|
723
|
-
* Sends a message to PGlite and pushes response
|
|
724
|
-
* stream as they arrive. Avoids collecting and
|
|
725
|
-
* multi-row responses (e.g., findMany 500 rows
|
|
707
|
+
* Sends a message (or pipelined batch) to PGlite and pushes response
|
|
708
|
+
* chunks directly to the stream as they arrive. Avoids collecting and
|
|
709
|
+
* concatenating for large multi-row responses (e.g., findMany 500 rows
|
|
710
|
+
* = ~503 onRawData chunks).
|
|
711
|
+
*
|
|
712
|
+
* For pipelined Extended Query batches, pass `suppressIntermediateRfq`
|
|
713
|
+
* so only the final ReadyForQuery reaches the client.
|
|
726
714
|
*
|
|
727
715
|
* Must be called inside runExclusive.
|
|
728
716
|
*/
|
|
729
|
-
async
|
|
717
|
+
async streamProtocol(message, options) {
|
|
718
|
+
const { detectErrors, suppressIntermediateRfq } = options;
|
|
730
719
|
let errSeen = false;
|
|
731
720
|
const framer = new BackendMessageFramer({
|
|
732
|
-
suppressIntermediateReadyForQuery:
|
|
721
|
+
suppressIntermediateReadyForQuery: suppressIntermediateRfq,
|
|
733
722
|
onChunk: (chunk) => {
|
|
734
723
|
/* c8 ignore next — race-only: tornDown becomes true mid-stream */
|
|
735
724
|
if (!this.tornDown && chunk.length > 0) this.push(chunk);
|
|
@@ -756,6 +745,49 @@ var PGliteBridge = class extends node_stream.Duplex {
|
|
|
756
745
|
}
|
|
757
746
|
};
|
|
758
747
|
//#endregion
|
|
748
|
+
//#region src/bridge-client.ts
|
|
749
|
+
const bridgeClientOptionsKey = Symbol("bridgeClientOptions");
|
|
750
|
+
var BridgeClient = class extends pg.default.Client {
|
|
751
|
+
querySubmissionChain = Promise.resolve();
|
|
752
|
+
constructor(config) {
|
|
753
|
+
const { [bridgeClientOptionsKey]: bridge, ...clientConfig } = config ?? {};
|
|
754
|
+
if (!bridge) throw new Error("BridgeClient requires bridge options");
|
|
755
|
+
super({
|
|
756
|
+
...clientConfig,
|
|
757
|
+
user: "postgres",
|
|
758
|
+
database: "postgres",
|
|
759
|
+
stream: () => new PGliteBridge(bridge.pglite, bridge.sessionLock, bridge.adapterId, bridge.telemetry, bridge.syncToFs)
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
query(...args) {
|
|
763
|
+
const first = args[0];
|
|
764
|
+
const callSuper = () => super.query.apply(this, args);
|
|
765
|
+
if (first === null || first === void 0) return callSuper();
|
|
766
|
+
if (typeof first.submit === "function") return callSuper();
|
|
767
|
+
const prior = this.querySubmissionChain;
|
|
768
|
+
let signalDone;
|
|
769
|
+
this.querySubmissionChain = new Promise((resolve) => {
|
|
770
|
+
signalDone = resolve;
|
|
771
|
+
});
|
|
772
|
+
const cbIndex = args.findIndex((arg) => typeof arg === "function");
|
|
773
|
+
if (cbIndex !== -1) {
|
|
774
|
+
const origCb = args[cbIndex];
|
|
775
|
+
args[cbIndex] = (err, res) => {
|
|
776
|
+
signalDone();
|
|
777
|
+
origCb(err, res);
|
|
778
|
+
};
|
|
779
|
+
prior.then(callSuper).catch((err) => {
|
|
780
|
+
signalDone();
|
|
781
|
+
origCb(err, void 0);
|
|
782
|
+
});
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
const p = prior.then(callSuper);
|
|
786
|
+
p.then(signalDone, signalDone);
|
|
787
|
+
return p;
|
|
788
|
+
}
|
|
789
|
+
};
|
|
790
|
+
//#endregion
|
|
759
791
|
//#region src/utils/session-lock.ts
|
|
760
792
|
/**
|
|
761
793
|
* Session-level lock for PGlite's single-session model.
|
|
@@ -904,20 +936,18 @@ const createPool$1 = async (options) => {
|
|
|
904
936
|
const syncToFs = resolveSyncToFs(pglite, options.syncToFs);
|
|
905
937
|
await pglite.waitReady;
|
|
906
938
|
const sessionLock = new SessionLock();
|
|
907
|
-
const
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
939
|
+
const poolConfig = {
|
|
940
|
+
Client: BridgeClient,
|
|
941
|
+
max,
|
|
942
|
+
[bridgeClientOptionsKey]: {
|
|
943
|
+
pglite,
|
|
944
|
+
sessionLock,
|
|
945
|
+
adapterId,
|
|
946
|
+
telemetry,
|
|
947
|
+
syncToFs
|
|
915
948
|
}
|
|
916
949
|
};
|
|
917
|
-
const pool = new pg.default.Pool(
|
|
918
|
-
Client,
|
|
919
|
-
max
|
|
920
|
-
});
|
|
950
|
+
const pool = new pg.default.Pool(poolConfig);
|
|
921
951
|
const close = () => pool.end();
|
|
922
952
|
return {
|
|
923
953
|
pool,
|