@effectstream/db 0.200.2 → 0.200.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effectstream/db",
3
- "version": "0.200.2",
3
+ "version": "0.200.5",
4
4
  "license": "(MIT OR Apache-2.0)",
5
5
  "homepage": "https://effectstream.github.io/docs/",
6
6
  "repository": {
@@ -21,11 +21,11 @@
21
21
  "./version": "./migrations/system-version.ts"
22
22
  },
23
23
  "dependencies": {
24
- "@effectstream/coroutine": "0.200.2",
25
- "@effectstream/log": "0.200.2",
26
- "@effectstream/runtime": "0.200.2",
27
- "@effectstream/sync": "0.200.2",
28
- "@effectstream/utils": "0.200.2",
24
+ "@effectstream/coroutine": "0.200.5",
25
+ "@effectstream/log": "0.200.5",
26
+ "@effectstream/runtime": "0.200.5",
27
+ "@effectstream/sync": "0.200.5",
28
+ "@effectstream/utils": "0.200.5",
29
29
  "@electric-sql/pglite": "^0.3.14",
30
30
  "@pgtyped/runtime": "2.4.2",
31
31
  "@pgtyped/parser": "2.4.2",
@@ -15,12 +15,25 @@ const connection = (handle: PgliteHandle) => ({
15
15
  connectionTimeoutMillis: 500,
16
16
  });
17
17
 
18
- async function connect(handle: PgliteHandle): Promise<Client> {
19
- const client = new Client(connection(handle));
18
+ type PositiveClientFactory = (config: ReturnType<typeof connection>) => Client;
19
+
20
+ const createPositiveClient: PositiveClientFactory = (config) => new Client(config);
21
+
22
+ async function connect(
23
+ handle: PgliteHandle,
24
+ clientFactory: PositiveClientFactory = createPositiveClient,
25
+ ): Promise<Client> {
26
+ await handle.db.waitReady;
27
+ const client = clientFactory(connection(handle));
20
28
  await client.connect();
21
29
  return client;
22
30
  }
23
31
 
32
+ const controlledHandle = (waitReady: Promise<unknown>, port = 5432): PgliteHandle => ({
33
+ port,
34
+ db: { waitReady },
35
+ }) as unknown as PgliteHandle;
36
+
24
37
  async function expectRefused(handle: PgliteHandle): Promise<void> {
25
38
  const probe = new Client(connection(handle));
26
39
  await expect(probe.connect()).rejects.toMatchObject({ code: "ECONNREFUSED" });
@@ -49,6 +62,91 @@ async function waitFor(
49
62
  }
50
63
  }
51
64
 
65
+ test("positive client waits for readiness before construction", async () => {
66
+ const order: string[] = [];
67
+ let releaseReadiness!: () => void;
68
+ const readinessGate = new Promise<void>((resolve) => {
69
+ releaseReadiness = resolve;
70
+ });
71
+ const waitReady = readinessGate.then(() => {
72
+ order.push("readiness");
73
+ });
74
+ const fakeClient = {
75
+ connect: async () => {
76
+ order.push("connect");
77
+ },
78
+ } as unknown as Client;
79
+ let observedConfig: ReturnType<typeof connection> | undefined;
80
+ const clientFactory: PositiveClientFactory = (config) => {
81
+ order.push("construction");
82
+ observedConfig = config;
83
+ return fakeClient;
84
+ };
85
+
86
+ let connectionAttempt: Promise<Client> | undefined;
87
+ try {
88
+ const startedAt = Date.now();
89
+ connectionAttempt = connect(controlledHandle(waitReady), clientFactory);
90
+ await Bun.sleep(600);
91
+
92
+ expect(Date.now() - startedAt).toBeGreaterThanOrEqual(600);
93
+ expect(order).toEqual([]);
94
+ expect(observedConfig).toBeUndefined();
95
+
96
+ releaseReadiness();
97
+ expect(await connectionAttempt).toBe(fakeClient);
98
+ expect(order).toEqual(["readiness", "construction", "connect"]);
99
+ expect(observedConfig?.connectionTimeoutMillis).toBe(500);
100
+ } finally {
101
+ releaseReadiness();
102
+ await connectionAttempt?.catch(() => {});
103
+ }
104
+ });
105
+
106
+ test("positive client propagates readiness rejection before construction", async () => {
107
+ const readinessError = new Error("controlled readiness rejection");
108
+ const waitReady = Promise.reject(readinessError);
109
+ void waitReady.catch(() => {});
110
+ let constructionCount = 0;
111
+ let connectCount = 0;
112
+ const clientFactory: PositiveClientFactory = () => {
113
+ constructionCount += 1;
114
+ return {
115
+ connect: async () => {
116
+ connectCount += 1;
117
+ },
118
+ } as unknown as Client;
119
+ };
120
+
121
+ await expect(connect(controlledHandle(waitReady), clientFactory)).rejects.toBe(readinessError);
122
+ expect(constructionCount).toBe(0);
123
+ expect(connectCount).toBe(0);
124
+ });
125
+
126
+ test("positive client preserves the 500 ms post-readiness handshake timeout", async () => {
127
+ const acceptedSockets: net.Socket[] = [];
128
+ const stalledServer = net.createServer((socket) => {
129
+ acceptedSockets.push(socket);
130
+ });
131
+ await new Promise<void>((resolve) => stalledServer.listen(0, "127.0.0.1", resolve));
132
+ const address = stalledServer.address();
133
+ if (!address || typeof address === "string") {
134
+ throw new Error("Unable to allocate a controlled loopback port.");
135
+ }
136
+
137
+ try {
138
+ await expect(connect(controlledHandle(Promise.resolve(), address.port))).rejects.toMatchObject({
139
+ message: "timeout expired",
140
+ });
141
+ expect(acceptedSockets).toHaveLength(1);
142
+ } finally {
143
+ for (const socket of acceptedSockets) socket.destroy();
144
+ await new Promise<void>((resolve, reject) => {
145
+ stalledServer.close((error) => error ? reject(error) : resolve());
146
+ });
147
+ }
148
+ });
149
+
52
150
  test("default and forced close are bounded without clients", async () => {
53
151
  const graceful = await startPglite(0);
54
152
  const forced = await startPglite(0);