@skein-js/server-kit 0.11.3 → 0.12.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/README.md CHANGED
@@ -24,8 +24,9 @@ handler table.
24
24
  `{ deps }` to any adapter. `overrides` swaps in production drivers/auth. See
25
25
  [docs/embedding.md](../../docs/embedding.md).
26
26
  - **LangGraph dev-state import** — `readLanggraphDevState` / `loadSnapshotIntoStore` /
27
- `describeSnapshot`: read an existing `.langgraph_api/` directory and reconstruct skein's own
28
- `DevStateSnapshot`, so adopting skein carries all local state over losslessly.
27
+ `describeSnapshot`, from the **`@skein-js/server-kit/dev` subpath**: read an existing
28
+ `.langgraph_api/` directory and reconstruct skein's own `DevStateSnapshot`, so adopting skein carries
29
+ all local state over losslessly.
29
30
  - **CORS** — `corsFromHttpConfig` / `toCorsOptions` map a `langgraph.json` `http.cors` block to
30
31
  `cors`-style `CorsOptions`; `allowedOrigin` / `corsResponseHeaders` / `applyNodeCors` /
31
32
  `sendNodePreflight` derive CORS headers for the adapters without a CORS middleware of their own
@@ -92,12 +93,39 @@ Pass `overrides` to swap in production drivers or an auth engine while keeping t
92
93
  `DEFAULT_RUN_CONCURRENCY` (10, matching the LangGraph CLI). The environment is validated even when
93
94
  an explicit value is given, so the two sources can't silently disagree. `skein dev`/`start` use it
94
95
  to resolve the number they print in the startup banner.
96
+ - **`startHeapPressureMonitor(options): HeapPressureMonitor`** — samples `v8.getHeapStatistics()` on an
97
+ unref'd interval and warns **once per crossing** (85% of the heap limit, re-arming below 70%), with
98
+ in-flight runs and buffered frames alongside so the line distinguishes too-much-concurrency from a slow
99
+ SSE consumer from a leak. `resolveHeapPressureOptions()` reads `SKEIN_HEAP_WARN_PERCENT` (`0` disables)
100
+ and `SKEIN_HEAP_SAMPLE_MS`. Started by `resolveProtocolRuntime` and stopped with the worker, so no
101
+ adapter has to remember it. Free when no logger is configured — it schedules nothing.
102
+ - **`checkHeapHeadroom(): HeapHeadroom`** — the boot-time counterpart: compares V8's ceiling with the
103
+ container's cgroup limit and warns when the automatic sizing does not fit (see
104
+ [deploy.md](../../docs/deploy.md)).
105
+ - **`describePoolPressure(runConcurrency, poolMax): string | undefined`** — warns when the worker can
106
+ execute more runs at once than the Postgres pool can serve.
107
+ - **`resolveMaxPageSize(explicit?, env?): number`** — the same chain for the store page bound:
108
+ explicit `maxPageSize` → `SKEIN_MAX_PAGE_SIZE` → `DEFAULT_MAX_PAGE_SIZE` (1000). Every driver applies
109
+ it to list/search, including when the caller asks for no limit.
95
110
  - **`loadInMemoryRuntime` / `loadReloadableInMemoryRuntime`** — assemble a `ProtocolDeps` from a
96
111
  `langgraph.json` using in-process drivers. The reloadable variant adds `reloadGraphs` /
97
112
  `snapshotState` / `hydrateState` (what powers `skein dev`'s hot reload + cross-restart persistence).
98
113
  - **`readLanggraphDevState` / `loadSnapshotIntoStore` / `describeSnapshot`** — read an existing
99
114
  `.langgraph_api/` directory and reconstruct a `DevStateSnapshot`, so adopting skein carries local
100
115
  state over losslessly.
116
+
117
+ Imported from **`@skein-js/server-kit/dev`**, not the package root:
118
+
119
+ ```ts
120
+ import { readLanggraphDevState } from "@skein-js/server-kit/dev";
121
+ ```
122
+
123
+ They carry `superjson` and `node:fs/promises`, and only `skein dev` / `skein import` call them, so
124
+ keeping them on the root barrel put a filesystem snapshot deserializer into every adapter's module
125
+ graph. There is deliberately no root re-export — a re-export is still a static import. The
126
+ `DevStateCounts` _type_ remains exported from the root, since `export type` is erased. See
127
+ [docs/bundling.md](../../docs/bundling.md).
128
+
101
129
  - **CORS** — `corsFromHttpConfig` / `toCorsOptions` map a `langgraph.json` `http.cors` block to
102
130
  `cors`-style options; `allowedOrigin` / `corsResponseHeaders` / `applyNodeCors` / `sendNodePreflight`
103
131
  derive CORS headers for adapters without a CORS middleware of their own.
@@ -0,0 +1,56 @@
1
+ // src/dev-persistence.ts
2
+ var toBase64 = (bytes) => Buffer.from(bytes).toString("base64");
3
+ var fromBase64 = (text) => new Uint8Array(Buffer.from(text, "base64"));
4
+ var mapValues = (record, fn) => Object.fromEntries(Object.entries(record).map(([key, value]) => [key, fn(value)]));
5
+ function snapshotCheckpointer(saver) {
6
+ return {
7
+ storage: mapValues(
8
+ saver.storage,
9
+ (namespaces) => mapValues(
10
+ namespaces,
11
+ (checkpoints) => mapValues(checkpoints, ([checkpoint, metadata, parentId]) => [
12
+ toBase64(checkpoint),
13
+ toBase64(metadata),
14
+ parentId
15
+ ])
16
+ )
17
+ ),
18
+ writes: mapValues(
19
+ saver.writes,
20
+ (taskWrites) => mapValues(taskWrites, ([taskId, channel, blob]) => [
21
+ taskId,
22
+ channel,
23
+ toBase64(blob)
24
+ ])
25
+ )
26
+ };
27
+ }
28
+ function hydrateCheckpointer(saver, snapshot) {
29
+ saver.storage = mapValues(
30
+ snapshot.storage,
31
+ (namespaces) => mapValues(
32
+ namespaces,
33
+ (checkpoints) => mapValues(
34
+ checkpoints,
35
+ ([checkpoint, metadata, parentId]) => [
36
+ fromBase64(checkpoint),
37
+ fromBase64(metadata),
38
+ parentId
39
+ ]
40
+ )
41
+ )
42
+ );
43
+ saver.writes = mapValues(
44
+ snapshot.writes,
45
+ (taskWrites) => mapValues(taskWrites, ([taskId, channel, blob]) => [
46
+ taskId,
47
+ channel,
48
+ fromBase64(blob)
49
+ ])
50
+ );
51
+ }
52
+
53
+ export {
54
+ snapshotCheckpointer,
55
+ hydrateCheckpointer
56
+ };
@@ -0,0 +1,237 @@
1
+ import {
2
+ hydrateCheckpointer,
3
+ snapshotCheckpointer
4
+ } from "./chunk-JUBBCC7R.js";
5
+
6
+ // src/in-memory-runtime.ts
7
+ import { MemorySaver as MemorySaver2 } from "@langchain/langgraph";
8
+ import {
9
+ loadAuthEngine,
10
+ loadConfig
11
+ } from "@skein-js/config";
12
+ import { MemoryRunEventBus as MemoryRunEventBus2, MemoryRunQueue as MemoryRunQueue2, MemorySkeinStore as MemorySkeinStore2 } from "@skein-js/storage-memory";
13
+
14
+ // src/cors-config.ts
15
+ import {
16
+ filterSkeinRoutes,
17
+ skeinRoutes
18
+ } from "@skein-js/agent-protocol";
19
+ var ALWAYS_EXPOSED_HEADERS = ["content-location", "x-pagination-total"];
20
+ function toCorsOptions(config) {
21
+ const options = {};
22
+ const allowAll = config.allow_origins?.includes("*") ?? false;
23
+ if (config.allow_origin_regex !== void 0) {
24
+ const pattern = new RegExp(`^(?:${config.allow_origin_regex})$`);
25
+ const listed = allowAll ? void 0 : new Set(config.allow_origins);
26
+ options.origin = (origin, callback) => callback(
27
+ null,
28
+ origin !== void 0 && (allowAll || pattern.test(origin) || (listed?.has(origin) ?? false))
29
+ );
30
+ } else if (config.allow_origins !== void 0) {
31
+ options.origin = allowAll ? "*" : config.allow_origins;
32
+ }
33
+ if (config.allow_methods !== void 0) options.methods = config.allow_methods;
34
+ if (config.allow_headers !== void 0) options.allowedHeaders = config.allow_headers;
35
+ if (config.allow_credentials !== void 0) options.credentials = config.allow_credentials;
36
+ if (config.max_age !== void 0) options.maxAge = config.max_age;
37
+ const exposed = /* @__PURE__ */ new Set([...ALWAYS_EXPOSED_HEADERS, ...config.expose_headers ?? []]);
38
+ options.exposedHeaders = [...exposed];
39
+ return options;
40
+ }
41
+ function corsFromHttpConfig(http) {
42
+ if (typeof http !== "object" || http === null) return void 0;
43
+ const cors = http.cors;
44
+ if (typeof cors !== "object" || cors === null) return void 0;
45
+ return toCorsOptions(cors);
46
+ }
47
+ var DISABLE_FLAGS = {
48
+ disable_assistants: "assistants",
49
+ disable_threads: "threads",
50
+ disable_runs: "runs",
51
+ disable_crons: "crons",
52
+ disable_store: "store",
53
+ disable_meta: "meta"
54
+ };
55
+ function disabledRoutesFromHttpConfig(http) {
56
+ if (typeof http !== "object" || http === null) return {};
57
+ const flags = http;
58
+ const disabled = {};
59
+ for (const [flag, group] of Object.entries(DISABLE_FLAGS)) {
60
+ if (flags[flag] === true) disabled[group] = true;
61
+ }
62
+ return disabled;
63
+ }
64
+ function routesFromHttpConfig(http) {
65
+ return filterSkeinRoutes(skeinRoutes, disabledRoutesFromHttpConfig(http));
66
+ }
67
+
68
+ // src/in-memory-deps.ts
69
+ import { MemorySaver } from "@langchain/langgraph";
70
+ import { MemoryRunEventBus, MemoryRunQueue, MemorySkeinStore } from "@skein-js/storage-memory";
71
+
72
+ // src/max-page-size.ts
73
+ import { DEFAULT_MAX_PAGE_SIZE } from "@skein-js/core";
74
+
75
+ // src/env-numbers.ts
76
+ import { SkeinConfigError } from "@skein-js/config/errors";
77
+ function requirePositiveInteger(source, raw) {
78
+ const parsed = Number(raw);
79
+ if (!Number.isSafeInteger(parsed) || parsed <= 0) {
80
+ throw new SkeinConfigError(`${source} must be a positive integer (got "${String(raw)}").`);
81
+ }
82
+ return parsed;
83
+ }
84
+ function positiveIntegerFromEnv(names, env) {
85
+ for (const name of names) {
86
+ const raw = env[name];
87
+ if (raw === void 0 || raw.trim() === "") continue;
88
+ return requirePositiveInteger(name, raw);
89
+ }
90
+ return void 0;
91
+ }
92
+
93
+ // src/max-page-size.ts
94
+ var MAX_PAGE_SIZE_ENV_VARS = ["SKEIN_MAX_PAGE_SIZE"];
95
+ function resolveMaxPageSize(explicit, env = process.env) {
96
+ const fromEnv = positiveIntegerFromEnv(MAX_PAGE_SIZE_ENV_VARS, env);
97
+ if (explicit === void 0) return fromEnv ?? DEFAULT_MAX_PAGE_SIZE;
98
+ return requirePositiveInteger("store.maxPageSize", explicit);
99
+ }
100
+
101
+ // src/memory-bus-limits.ts
102
+ import {
103
+ DEFAULT_MEMORY_BUS_MAX_FRAMES_PER_RUN,
104
+ DEFAULT_MEMORY_BUS_MAX_RETAINED_RUNS
105
+ } from "@skein-js/storage-memory";
106
+ var MAX_RETAINED_RUNS_ENV = "SKEIN_MEMORY_BUS_MAX_RETAINED_RUNS";
107
+ var MAX_FRAMES_PER_RUN_ENV = "SKEIN_MEMORY_BUS_MAX_FRAMES_PER_RUN";
108
+ function resolveMemoryBusLimits(explicit = {}, env = process.env) {
109
+ const retainedFromEnv = positiveIntegerFromEnv([MAX_RETAINED_RUNS_ENV], env);
110
+ const framesFromEnv = positiveIntegerFromEnv([MAX_FRAMES_PER_RUN_ENV], env);
111
+ return {
112
+ maxRetainedRuns: explicit.maxRetainedRuns === void 0 ? retainedFromEnv ?? DEFAULT_MEMORY_BUS_MAX_RETAINED_RUNS : requirePositiveInteger("maxRetainedRuns", explicit.maxRetainedRuns),
113
+ maxFramesPerRun: explicit.maxFramesPerRun === void 0 ? framesFromEnv ?? DEFAULT_MEMORY_BUS_MAX_FRAMES_PER_RUN : requirePositiveInteger("maxFramesPerRun", explicit.maxFramesPerRun)
114
+ };
115
+ }
116
+
117
+ // src/in-memory-deps.ts
118
+ function isGraphResolver(graphs) {
119
+ const candidate = graphs;
120
+ return Array.isArray(candidate.ids) && typeof candidate.load === "function";
121
+ }
122
+ function normalizeEmbeddableGraphs(graphs) {
123
+ return isGraphResolver(graphs) ? graphs : graphMapToResolver(graphs);
124
+ }
125
+ function graphMapToResolver(graphs) {
126
+ const ids = Object.keys(graphs);
127
+ return {
128
+ ids,
129
+ load: async (graphId) => {
130
+ const graph = graphs[graphId];
131
+ if (graph == null) {
132
+ const known = ids.join(", ") || "none";
133
+ throw new Error(
134
+ ids.includes(graphId) ? `Graph "${graphId}" resolved to ${String(graph)} \u2014 check its export/factory (known: ${known}).` : `Unknown graph "${graphId}" (known: ${known}).`
135
+ );
136
+ }
137
+ return graph;
138
+ },
139
+ schemas: async (graphId) => ({ [graphId]: { graph_id: graphId } })
140
+ };
141
+ }
142
+ function embedInMemoryGraphs(graphs, overrides = {}) {
143
+ return {
144
+ // `maxPageSize` resolved for the same reason as the bus limits below: an embedded host's
145
+ // `SKEIN_MAX_PAGE_SIZE` has to reach the driver that applies it.
146
+ store: new MemorySkeinStore({ maxPageSize: resolveMaxPageSize() }),
147
+ graphs: normalizeEmbeddableGraphs(graphs),
148
+ queue: new MemoryRunQueue(),
149
+ // Resolved rather than defaulted, so `SKEIN_MEMORY_BUS_*` reaches an embedded host too — this is
150
+ // the driver set a long-lived single-process deployment actually runs on.
151
+ bus: new MemoryRunEventBus(resolveMemoryBusLimits()),
152
+ checkpointer: new MemorySaver(),
153
+ ...overrides
154
+ };
155
+ }
156
+ var createInMemoryDeps = embedInMemoryGraphs;
157
+
158
+ // src/in-memory-runtime.ts
159
+ function toGraphResolver(graphs) {
160
+ return {
161
+ ids: graphs.ids,
162
+ load: (graphId) => graphs.load(graphId),
163
+ schemas: async (graphId) => await graphs.schemas(graphId)
164
+ };
165
+ }
166
+ async function loadInMemoryRuntime(configPath, importModule, staticSchemas) {
167
+ const { graphs, config, configDir } = await loadConfig({
168
+ configPath,
169
+ importModule,
170
+ staticSchemas
171
+ });
172
+ const deps = embedInMemoryGraphs(toGraphResolver(graphs));
173
+ deps.auth = await loadAuthEngine(config.auth, { configDir, importModule });
174
+ return {
175
+ deps,
176
+ cors: corsFromHttpConfig(config.http),
177
+ routes: routesFromHttpConfig(config.http)
178
+ };
179
+ }
180
+ async function loadReloadableInMemoryRuntime(configPath, importModule, staticSchemas, extraDeps) {
181
+ const first = await loadConfig({ configPath, importModule, staticSchemas });
182
+ let current = first.graphs;
183
+ const graphs = {
184
+ ids: first.graphs.ids,
185
+ load: (graphId) => current.load(graphId),
186
+ schemas: async (graphId) => await current.schemas(graphId)
187
+ };
188
+ const store = new MemorySkeinStore2({ maxPageSize: resolveMaxPageSize() });
189
+ const checkpointer = new MemorySaver2();
190
+ const deps = {
191
+ store,
192
+ graphs,
193
+ queue: new MemoryRunQueue2(),
194
+ // This is the path `skein start` and `skein up` take by default, so it is the longest-lived
195
+ // memory bus skein runs — the bounds have to be resolved here too, not just on the embed paths.
196
+ bus: new MemoryRunEventBus2(resolveMemoryBusLimits()),
197
+ checkpointer,
198
+ auth: await loadAuthEngine(first.config.auth, { configDir: first.configDir, importModule }),
199
+ ...extraDeps
200
+ };
201
+ return {
202
+ deps,
203
+ cors: corsFromHttpConfig(first.config.http),
204
+ routes: routesFromHttpConfig(first.config.http),
205
+ config: first.config,
206
+ configDir: first.configDir,
207
+ reloadGraphs: async () => {
208
+ current = (await loadConfig({ configPath, importModule, staticSchemas })).graphs;
209
+ },
210
+ snapshotState: () => ({
211
+ version: 1,
212
+ store: store.snapshot(),
213
+ checkpoints: snapshotCheckpointer(checkpointer)
214
+ }),
215
+ hydrateState: (snapshot) => {
216
+ store.hydrate(snapshot.store);
217
+ hydrateCheckpointer(checkpointer, snapshot.checkpoints);
218
+ }
219
+ };
220
+ }
221
+
222
+ export {
223
+ requirePositiveInteger,
224
+ positiveIntegerFromEnv,
225
+ toCorsOptions,
226
+ corsFromHttpConfig,
227
+ disabledRoutesFromHttpConfig,
228
+ routesFromHttpConfig,
229
+ resolveMaxPageSize,
230
+ resolveMemoryBusLimits,
231
+ normalizeEmbeddableGraphs,
232
+ graphMapToResolver,
233
+ embedInMemoryGraphs,
234
+ createInMemoryDeps,
235
+ loadInMemoryRuntime,
236
+ loadReloadableInMemoryRuntime
237
+ };
@@ -0,0 +1,46 @@
1
+ import { BaseCheckpointSaver } from '@langchain/langgraph';
2
+ import { SkeinStore } from '@skein-js/core';
3
+ import { MemoryStoreSnapshot } from '@skein-js/storage-memory';
4
+
5
+ /** The checkpoint tuple `[checkpoint, metadata, parentId?]` with the blobs base64-encoded. */
6
+ type SerializedCheckpointTuple = [string, string, string | undefined];
7
+ /** `MemorySaver.storage` (thread → namespace → checkpointId → tuple) with blobs base64-encoded. */
8
+ type SerializedCheckpointStorage = Record<string, Record<string, Record<string, SerializedCheckpointTuple>>>;
9
+ /** `MemorySaver.writes` (key → taskId+idx → `[taskId, channel, blob]`) with the blob base64-encoded. */
10
+ type SerializedCheckpointWrites = Record<string, Record<string, [string, string, string]>>;
11
+ interface CheckpointerSnapshot {
12
+ storage: SerializedCheckpointStorage;
13
+ writes: SerializedCheckpointWrites;
14
+ }
15
+ /** A JSON-serializable snapshot of the whole in-memory dev runtime. */
16
+ interface DevStateSnapshot {
17
+ version: 1;
18
+ store: MemoryStoreSnapshot;
19
+ checkpoints: CheckpointerSnapshot;
20
+ }
21
+
22
+ /**
23
+ * Read a LangGraph `.langgraph_api/` directory and reconstruct skein's `DevStateSnapshot`.
24
+ * Returns `null` when the directory holds none of the expected files (nothing to import).
25
+ */
26
+ declare function readLanggraphDevState(langgraphApiDir: string): Promise<DevStateSnapshot | null>;
27
+ /** Row/checkpoint counts in a snapshot, for CLI + log summaries. */
28
+ interface DevStateCounts {
29
+ assistants: number;
30
+ threads: number;
31
+ runs: number;
32
+ items: number;
33
+ /** Threads that carry graph checkpoint history. */
34
+ checkpointedThreads: number;
35
+ }
36
+ declare function describeSnapshot(snapshot: DevStateSnapshot): DevStateCounts;
37
+ /**
38
+ * Load a `DevStateSnapshot` into a live store + checkpointer — the sink for importing into a real
39
+ * skein deployment (e.g. Postgres). Resource rows go through the driver's `restore` (ids +
40
+ * timestamps preserved, `ON CONFLICT DO NOTHING` so re-runs are safe); checkpoints — graph state +
41
+ * full history — are copied via the public checkpointer API. Throws if the store can't bulk-restore
42
+ * (both first-party drivers can); a custom driver must implement `restore` to be an import target.
43
+ */
44
+ declare function loadSnapshotIntoStore(snapshot: DevStateSnapshot, store: SkeinStore, checkpointer: BaseCheckpointSaver): Promise<void>;
45
+
46
+ export { type DevStateSnapshot as D, type DevStateCounts as a, describeSnapshot as d, loadSnapshotIntoStore as l, readLanggraphDevState as r };
package/dist/dev.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ export { a as DevStateCounts, d as describeSnapshot, l as loadSnapshotIntoStore, r as readLanggraphDevState } from './dev-BfYYRfEB.js';
2
+ import '@langchain/langgraph';
3
+ import '@skein-js/core';
4
+ import '@skein-js/storage-memory';