@effectstream/node-sdk 0.104.1 → 0.104.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 CHANGED
@@ -64,6 +64,13 @@ instead of jumping to a new tip.
64
64
  The subpaths are thin re-exports - semantics are identical to importing
65
65
  from the underlying packages.
66
66
 
67
+ The re-exported `db/start-pglite` handle uses a bounded, non-destructive
68
+ `close()` by default and an explicit owner-only `close({ force: true })` mode.
69
+ Default close defers PGlite cleanup until the last accepted client socket drains.
70
+ Concurrent calls share the first cleanup promise and the first requested mode
71
+ wins. See the `@effectstream/db` PgLite gateway lifecycle section for the full
72
+ client-ownership, failure, and `0.104.0`→`0.200.1` migration contract.
73
+
67
74
  ## Inside EffectStream
68
75
 
69
76
  A namespace seam, not a runtime piece. Each subpath simply re-exports
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effectstream/node-sdk",
3
- "version": "0.104.1",
3
+ "version": "0.104.3",
4
4
  "license": "(MIT OR Apache-2.0)",
5
5
  "homepage": "https://effectstream.github.io/docs/",
6
6
  "repository": {
@@ -83,18 +83,18 @@
83
83
  }
84
84
  },
85
85
  "dependencies": {
86
- "@effectstream/runtime": "0.104.1",
87
- "@effectstream/sm": "0.104.1",
88
- "@effectstream/sync": "0.104.1",
89
- "@effectstream/db": "0.104.1",
90
- "@effectstream/db-emulator": "0.104.1",
91
- "@effectstream/coroutine": "0.104.1",
92
- "@effectstream/event-server": "0.104.1",
93
- "@effectstream/config": "0.104.1",
94
- "@effectstream/chain-types": "0.104.1",
95
- "@effectstream/precompile": "0.104.1",
96
- "@effectstream/utils": "0.104.1",
97
- "@effectstream/concise": "0.104.1",
86
+ "@effectstream/runtime": "0.104.3",
87
+ "@effectstream/sm": "0.104.3",
88
+ "@effectstream/sync": "0.104.3",
89
+ "@effectstream/db": "0.104.3",
90
+ "@effectstream/db-emulator": "0.104.3",
91
+ "@effectstream/coroutine": "0.104.3",
92
+ "@effectstream/event-server": "0.104.3",
93
+ "@effectstream/config": "0.104.3",
94
+ "@effectstream/chain-types": "0.104.3",
95
+ "@effectstream/precompile": "0.104.3",
96
+ "@effectstream/utils": "0.104.3",
97
+ "@effectstream/concise": "0.104.3",
98
98
  "effection": "^3.5.0"
99
99
  },
100
100
  "description": "Main application node SDK for EffectStream"
@@ -218,7 +218,7 @@ export async function runNode<
218
218
 
219
219
  task = run(function* () {
220
220
  yield* ensure(function* () {
221
- yield* call(() => ownedDatabase.close());
221
+ yield* call(() => ownedDatabase.close({ force: true }));
222
222
  });
223
223
  yield* init();
224
224
  yield* withEffectstreamStaticConfig(staticConfig, function* () {
@@ -252,7 +252,7 @@ export async function runNode<
252
252
  process.off("SIGINT", signalHandler);
253
253
  process.off("SIGTERM", signalHandler);
254
254
  }
255
- if (!task && database) await database.close();
255
+ if (!task && database) await database.close({ force: true });
256
256
  restoreEnvironment();
257
257
  }
258
258
  }
@@ -0,0 +1,50 @@
1
+ import pg from "../../../db/node_modules/pg";
2
+ import net from "node:net";
3
+ import {
4
+ midnightContract,
5
+ pglite,
6
+ runNode,
7
+ } from "../../src/single-file.ts";
8
+
9
+ const originalPoolEnd = pg.Pool.prototype.end;
10
+ const originalServerClose = net.Server.prototype.close;
11
+ pg.Pool.prototype.end = async function () {
12
+ console.info("SINGLE_FILE_POOL_END");
13
+ return await originalPoolEnd.call(this);
14
+ };
15
+ net.Server.prototype.close = function (...args: Parameters<net.Server["close"]>) {
16
+ const address = this.address();
17
+ if (address && typeof address !== "string") {
18
+ console.info(`SINGLE_FILE_SERVER_CLOSE:${address.port}`);
19
+ }
20
+ return originalServerClose.apply(this, args);
21
+ };
22
+
23
+ const initialSignalListeners = process.listenerCount("SIGTERM");
24
+ const signalPoll = setInterval(() => {
25
+ if (process.listenerCount("SIGTERM") > initialSignalListeners) {
26
+ clearInterval(signalPoll);
27
+ console.info("SINGLE_FILE_SEND_SIGTERM");
28
+ process.kill(process.pid, "SIGTERM");
29
+ }
30
+ }, 10);
31
+
32
+ await runNode({
33
+ appName: "single-file-sigterm-fixture",
34
+ apiPort: 18999,
35
+ database: pglite({ port: 0 }),
36
+ sources: {
37
+ counter: midnightContract({
38
+ network: "preview",
39
+ address: "a".repeat(64),
40
+ startBlockHeight: 0,
41
+ ledger: { round: "uint128" },
42
+ indexer: "http://127.0.0.1:18998/graphql",
43
+ }),
44
+ },
45
+ transitions: {
46
+ counter: () => {},
47
+ },
48
+ });
49
+
50
+ console.info("SINGLE_FILE_EXIT_ZERO");
@@ -0,0 +1,46 @@
1
+ import { expect, test } from "bun:test";
2
+ import net from "node:net";
3
+
4
+ async function expectPortClosed(port: number): Promise<void> {
5
+ await expect(new Promise<void>((resolve, reject) => {
6
+ const socket = net.connect({ host: "127.0.0.1", port });
7
+ socket.once("connect", () => {
8
+ socket.destroy();
9
+ resolve();
10
+ });
11
+ socket.once("error", reject);
12
+ })).rejects.toMatchObject({ code: "ECONNREFUSED" });
13
+ }
14
+
15
+ test("single-file SIGTERM drains the runtime pool before explicit forced PGlite close", async () => {
16
+ const child = Bun.spawn([
17
+ process.execPath,
18
+ new URL("./fixtures/single-file-sigterm.ts", import.meta.url).pathname,
19
+ ], {
20
+ cwd: new URL("../../../..", import.meta.url).pathname,
21
+ stdout: "pipe",
22
+ stderr: "pipe",
23
+ });
24
+
25
+ const [stdout, stderr, exitCode] = await Promise.all([
26
+ new Response(child.stdout).text(),
27
+ new Response(child.stderr).text(),
28
+ child.exited,
29
+ ]);
30
+ const output = `${stdout}\n${stderr}`;
31
+
32
+ expect(exitCode, output).toBe(0);
33
+ expect(output).toContain("SINGLE_FILE_SEND_SIGTERM");
34
+ expect(output).toContain("SINGLE_FILE_POOL_END");
35
+ expect(output).toContain("SINGLE_FILE_EXIT_ZERO");
36
+
37
+ const match = output.match(/database: server listening on port (\d+)/);
38
+ if (!match) throw new Error(`No reported PGlite port in output:\n${output}`);
39
+ const port = Number(match[1]);
40
+ const gatewayCloseMarker = `SINGLE_FILE_SERVER_CLOSE:${port}`;
41
+ expect(output).toContain(gatewayCloseMarker);
42
+ expect(output.indexOf("SINGLE_FILE_POOL_END")).toBeLessThan(
43
+ output.indexOf(gatewayCloseMarker),
44
+ );
45
+ await expectPortClosed(port);
46
+ }, 30_000);