@skein-js/server-kit 0.13.0 → 0.14.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
@@ -1,5 +1,7 @@
1
1
  # @skein-js/server-kit
2
2
 
3
+ [![npm](https://img.shields.io/npm/v/%40skein-js%2Fserver-kit?logo=npm&color=cb3837)](https://www.npmjs.com/package/@skein-js/server-kit) [![downloads](https://img.shields.io/npm/dm/%40skein-js%2Fserver-kit?color=blue)](https://www.npmjs.com/package/@skein-js/server-kit) [![license](https://img.shields.io/npm/l/%40skein-js%2Fserver-kit?color=green)](../../LICENSE)
4
+
3
5
  > Shared, framework-agnostic building blocks for skein-js HTTP adapters.
4
6
 
5
7
  Part of **[skein-js](../../README.md)** — the open-source alternative to LangGraph Platform for TypeScript: a self-hosted [Agent Protocol](https://github.com/langchain-ai/agent-protocol) server for [LangGraph.js](https://github.com/langchain-ai/langgraphjs), and a drop-in replacement for the LangGraph CLI.
@@ -107,9 +109,16 @@ Pass `overrides` to swap in production drivers or an auth engine while keeping t
107
109
  - **`resolveMaxPageSize(explicit?, env?): number`** — the same chain for the store page bound:
108
110
  explicit `maxPageSize` → `SKEIN_MAX_PAGE_SIZE` → `DEFAULT_MAX_PAGE_SIZE` (1000). Every driver applies
109
111
  it to list/search, including when the caller asks for no limit.
112
+ - **`resolveStoreTtl(raw?)` / `resolveThreadTtl(raw?)`** — map the `langgraph.json` `store.ttl` and
113
+ `checkpointer.ttl` blocks (snake_case, minutes) onto the drivers' camelCase `StoreTtlConfig` /
114
+ `ThreadTtlConfig`; `undefined` when unset, so nothing expires unless asked. Plus the raw input types
115
+ `RawStoreTtl` / `RawThreadTtl`. They live here rather than in `@skein-js/runtime` because **both**
116
+ assembly paths need them and neither owns the other — resolving a block in only one is how a TTL
117
+ came to work under `skein start` and silently do nothing under `skein dev`.
110
118
  - **`loadInMemoryRuntime` / `loadReloadableInMemoryRuntime`** — assemble a `ProtocolDeps` from a
111
- `langgraph.json` using in-process drivers. The reloadable variant adds `reloadGraphs` /
112
- `snapshotState` / `hydrateState` (what powers `skein dev`'s hot reload + cross-restart persistence).
119
+ `langgraph.json` using in-process drivers, reading both TTL blocks itself. The reloadable variant
120
+ adds `reloadGraphs` / `snapshotState` / `hydrateState` (what powers `skein dev`'s hot reload +
121
+ cross-restart persistence).
113
122
  - **`readLanggraphDevState` / `loadSnapshotIntoStore` / `describeSnapshot`** — read an existing
114
123
  `.langgraph_api/` directory and reconstruct a `DevStateSnapshot`, so adopting skein carries local
115
124
  state over losslessly.
@@ -16,7 +16,8 @@ import {
16
16
  filterSkeinRoutes,
17
17
  skeinRoutes
18
18
  } from "@skein-js/agent-protocol";
19
- var ALWAYS_EXPOSED_HEADERS = ["content-location", "x-pagination-total"];
19
+ var ALWAYS_EXPOSED_HEADERS = ["content-location", "x-pagination-total", "idempotent-replay"];
20
+ var ALWAYS_ALLOWED_HEADERS = ["idempotency-key"];
20
21
  function toCorsOptions(config) {
21
22
  const options = {};
22
23
  const allowAll = config.allow_origins?.includes("*") ?? false;
@@ -31,7 +32,13 @@ function toCorsOptions(config) {
31
32
  options.origin = allowAll ? "*" : config.allow_origins;
32
33
  }
33
34
  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_headers !== void 0) {
36
+ const configured = new Set(config.allow_headers.map((header) => header.toLowerCase()));
37
+ options.allowedHeaders = config.allow_headers.length === 0 ? [] : [
38
+ ...config.allow_headers,
39
+ ...ALWAYS_ALLOWED_HEADERS.filter((header) => !configured.has(header))
40
+ ];
41
+ }
35
42
  if (config.allow_credentials !== void 0) options.credentials = config.allow_credentials;
36
43
  if (config.max_age !== void 0) options.maxAge = config.max_age;
37
44
  const exposed = /* @__PURE__ */ new Set([...ALWAYS_EXPOSED_HEADERS, ...config.expose_headers ?? []]);
@@ -64,6 +71,31 @@ function disabledRoutesFromHttpConfig(http) {
64
71
  function routesFromHttpConfig(http) {
65
72
  return filterSkeinRoutes(skeinRoutes, disabledRoutesFromHttpConfig(http));
66
73
  }
74
+ function consoleMountFromHttpConfig(http) {
75
+ if (typeof http !== "object" || http === null) return void 0;
76
+ const value = http["console"];
77
+ if (value === true) return DEFAULT_CONSOLE_MOUNT;
78
+ if (typeof value !== "string") return void 0;
79
+ const trimmed = value.trim();
80
+ if (/^(false|0|off|no)$/i.test(trimmed)) return void 0;
81
+ if (/^(true|1|on|yes)$/i.test(trimmed)) return DEFAULT_CONSOLE_MOUNT;
82
+ if (trimmed === "" || trimmed === "/") return void 0;
83
+ const withLeadingSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
84
+ return withLeadingSlash.replace(/\/+$/, "");
85
+ }
86
+ var DEFAULT_CONSOLE_MOUNT = "/console";
87
+
88
+ // src/idempotency-config.ts
89
+ function resolveIdempotency(raw) {
90
+ if (!raw) return void 0;
91
+ const config = {};
92
+ if (typeof raw.retention_hours === "number") config.retentionHours = raw.retention_hours;
93
+ if (typeof raw.in_flight_minutes === "number") config.inFlightMinutes = raw.in_flight_minutes;
94
+ if (typeof raw.sweep_interval_minutes === "number") {
95
+ config.sweepIntervalMinutes = raw.sweep_interval_minutes;
96
+ }
97
+ return Object.keys(config).length > 0 ? config : void 0;
98
+ }
67
99
 
68
100
  // src/in-memory-deps.ts
69
101
  import { MemorySaver } from "@langchain/langgraph";
@@ -155,6 +187,27 @@ function embedInMemoryGraphs(graphs, overrides = {}) {
155
187
  }
156
188
  var createInMemoryDeps = embedInMemoryGraphs;
157
189
 
190
+ // src/ttl-config.ts
191
+ function resolveThreadTtl(raw) {
192
+ if (!raw) return void 0;
193
+ const ttl = {};
194
+ if (typeof raw.default_ttl === "number") ttl.defaultTtl = raw.default_ttl;
195
+ if (typeof raw.sweep_interval_minutes === "number") {
196
+ ttl.sweepIntervalMinutes = raw.sweep_interval_minutes;
197
+ }
198
+ return Object.keys(ttl).length > 0 ? ttl : void 0;
199
+ }
200
+ function resolveStoreTtl(raw) {
201
+ if (!raw) return void 0;
202
+ const ttl = {};
203
+ if (typeof raw.default_ttl === "number") ttl.defaultTtl = raw.default_ttl;
204
+ if (typeof raw.refresh_on_read === "boolean") ttl.refreshOnRead = raw.refresh_on_read;
205
+ if (typeof raw.sweep_interval_minutes === "number") {
206
+ ttl.sweepIntervalMinutes = raw.sweep_interval_minutes;
207
+ }
208
+ return Object.keys(ttl).length > 0 ? ttl : void 0;
209
+ }
210
+
158
211
  // src/in-memory-runtime.ts
159
212
  function toGraphResolver(graphs) {
160
213
  return {
@@ -185,7 +238,14 @@ async function loadReloadableInMemoryRuntime(configPath, importModule, staticSch
185
238
  load: (graphId) => current.load(graphId),
186
239
  schemas: async (graphId) => await current.schemas(graphId)
187
240
  };
188
- const store = new MemorySkeinStore2({ maxPageSize: resolveMaxPageSize() });
241
+ const threadTtl = resolveThreadTtl(first.config.checkpointer?.ttl);
242
+ const storeTtl = resolveStoreTtl(first.config.store?.ttl);
243
+ const idempotency = resolveIdempotency(first.config.skein?.idempotency);
244
+ const store = new MemorySkeinStore2({
245
+ maxPageSize: resolveMaxPageSize(),
246
+ ...threadTtl ? { threadTtl } : {},
247
+ ...storeTtl ? { ttl: storeTtl } : {}
248
+ });
189
249
  const checkpointer = new MemorySaver2();
190
250
  const deps = {
191
251
  store,
@@ -196,6 +256,10 @@ async function loadReloadableInMemoryRuntime(configPath, importModule, staticSch
196
256
  bus: new MemoryRunEventBus2(resolveMemoryBusLimits()),
197
257
  checkpointer,
198
258
  auth: await loadAuthEngine(first.config.auth, { configDir: first.configDir, importModule }),
259
+ // Present only when configured — its presence is what starts the thread TTL sweeper.
260
+ ...threadTtl ? { threadTtl } : {},
261
+ // Unlike `threadTtl`, absence here means *defaults*, not *off*: the header is honoured either way.
262
+ ...idempotency ? { idempotency } : {},
199
263
  ...extraDeps
200
264
  };
201
265
  return {
@@ -226,12 +290,16 @@ export {
226
290
  corsFromHttpConfig,
227
291
  disabledRoutesFromHttpConfig,
228
292
  routesFromHttpConfig,
293
+ consoleMountFromHttpConfig,
294
+ resolveIdempotency,
229
295
  resolveMaxPageSize,
230
296
  resolveMemoryBusLimits,
231
297
  normalizeEmbeddableGraphs,
232
298
  graphMapToResolver,
233
299
  embedInMemoryGraphs,
234
300
  createInMemoryDeps,
301
+ resolveThreadTtl,
302
+ resolveStoreTtl,
235
303
  loadInMemoryRuntime,
236
304
  loadReloadableInMemoryRuntime
237
305
  };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  loadInMemoryRuntime,
3
3
  loadReloadableInMemoryRuntime
4
- } from "./chunk-QOHYARHD.js";
4
+ } from "./chunk-XDS3YWFL.js";
5
5
  import "./chunk-JUBBCC7R.js";
6
6
  export {
7
7
  loadInMemoryRuntime,
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ProtocolRuntime, RouteBinding, Logger, ProtocolDeps, RunWorkerOptions, CronSchedulerOptions, RunFailureReport, GraphResolver, RouteMatcher, DisabledRouteGroups, ProtocolResponse } from '@skein-js/agent-protocol';
1
+ import { ProtocolRuntime, RouteBinding, Logger, ProtocolDeps, RunWorkerOptions, CronSchedulerOptions, RunFailureReport, IdempotencyConfig, GraphResolver, RouteMatcher, DisabledRouteGroups, ProtocolResponse } from '@skein-js/agent-protocol';
2
2
  export { CompiledGraphFactory, DEFAULT_RUN_CONCURRENCY, DEFAULT_SHUTDOWN_GRACE_MS, GraphResolver, Logger, ProtocolDeps, ResolvedGraph, RunWorkerOptions } from '@skein-js/agent-protocol';
3
3
  import { ModuleImporter, LanggraphJson, GraphSchemas } from '@skein-js/config';
4
4
  import { CorsOptions } from 'cors';
@@ -6,9 +6,9 @@ export { CorsOptions } from 'cors';
6
6
  import { MemoryRunEventBusOptions } from '@skein-js/storage-memory';
7
7
  import { D as DevStateSnapshot } from './dev-BfYYRfEB.js';
8
8
  export { a as DevStateCounts } from './dev-BfYYRfEB.js';
9
+ import { StoreTtlConfig, ThreadTtlConfig } from '@skein-js/core';
9
10
  import { CompiledGraph } from '@langchain/langgraph';
10
11
  import { ServerResponse } from 'node:http';
11
- import '@skein-js/core';
12
12
 
13
13
  interface SkeinRuntimeCommonOptions {
14
14
  /**
@@ -454,6 +454,49 @@ declare function loadReloadableInMemoryRuntime(configPath: string, importModule?
454
454
  */
455
455
  extraDeps?: Partial<Omit<ProtocolDeps, "graphs">>): Promise<ReloadableInMemoryRuntime>;
456
456
 
457
+ /** The raw `checkpointer.ttl` block as `langgraph.json` spells it. */
458
+ interface RawThreadTtl {
459
+ default_ttl?: number;
460
+ strategy?: "delete";
461
+ sweep_interval_minutes?: number;
462
+ }
463
+ /**
464
+ * Map the raw block to {@link ThreadTtlConfig}, or `undefined` when nothing is set — so threads never
465
+ * expire unless asked to, and no sweeper is started.
466
+ *
467
+ * `strategy` is read only to be validated by the config schema: `"delete"` is the only thing an
468
+ * expired thread can become, so there is nothing for the drivers to branch on.
469
+ */
470
+ declare function resolveThreadTtl(raw: RawThreadTtl | undefined): ThreadTtlConfig | undefined;
471
+ /** The raw `store.ttl` block as `langgraph.json` spells it. */
472
+ interface RawStoreTtl {
473
+ default_ttl?: number;
474
+ refresh_on_read?: boolean;
475
+ sweep_interval_minutes?: number;
476
+ }
477
+ /**
478
+ * Map the raw `store.ttl` block to {@link StoreTtlConfig}, or `undefined` when nothing is set — so
479
+ * store items never expire unless asked to.
480
+ */
481
+ declare function resolveStoreTtl(raw: RawStoreTtl | undefined): StoreTtlConfig | undefined;
482
+
483
+ /** The raw `skein.idempotency` block as `langgraph.json` spells it. */
484
+ interface RawIdempotency {
485
+ retention_hours?: number;
486
+ in_flight_minutes?: number;
487
+ sweep_interval_minutes?: number;
488
+ }
489
+ /**
490
+ * Map the raw block to {@link IdempotencyConfig}, or `undefined` when nothing is set.
491
+ *
492
+ * Note what `undefined` means here, because it differs from its neighbours: **defaults, not
493
+ * disabled.** `resolveStoreTtl` returning `undefined` genuinely means items never expire, but a
494
+ * caller who sends `Idempotency-Key` has been promised their retry will not start a second run —
495
+ * there is no configuration under which the header stops being honoured, only one that tunes how
496
+ * long the promise lasts.
497
+ */
498
+ declare function resolveIdempotency(raw: RawIdempotency | undefined): IdempotencyConfig | undefined;
499
+
457
500
  type AnyCompiledGraph = CompiledGraph<any>;
458
501
  /**
459
502
  * A graph you can embed in code: any compiled LangGraph.js graph, or a factory that builds one per run
@@ -531,6 +574,17 @@ declare function disabledRoutesFromHttpConfig(http: unknown): DisabledRouteGroup
531
574
  * nothing is disabled (the same array, not a copy).
532
575
  */
533
576
  declare function routesFromHttpConfig(http: unknown): readonly RouteBinding[];
577
+ /**
578
+ * Where the console should be mounted per a langgraph.json `http.console` block, or `undefined` for
579
+ * "do not serve it".
580
+ *
581
+ * Deliberately opt-in, and deliberately strict about what counts as opting in. The console can read
582
+ * and delete every thread, every stored memory, and every schedule on the server; a config that meant
583
+ * to disable it — a `"false"` left by env substitution, a `0`, a typo — must never be read as
584
+ * enabling it. `skein dev` does not go through here at all: it serves the console by default, because
585
+ * a local dev server is exactly where you want one.
586
+ */
587
+ declare function consoleMountFromHttpConfig(http: unknown): string | undefined;
534
588
 
535
589
  type CorsSetting = true | CorsOptions;
536
590
  /**
@@ -649,4 +703,4 @@ type RuntimeGlobal = typeof globalThis & {
649
703
  /** Detect Node, Bun, or Deno without importing a runtime-specific module. */
650
704
  declare function detectRuntimeCapabilities(scope?: RuntimeGlobal): RuntimeCapabilities;
651
705
 
652
- export { type ConsoleLogLevel, type ConsoleLoggerOptions, type CorsSetting, DEFAULT_HEAP_SAMPLE_MS, DEFAULT_HEAP_WARN_PERCENT, DevStateSnapshot, type EmbeddableGraph, type FileReader, HEAP_REARM_MARGIN_PERCENT, HEAP_REARM_PERCENT, type HeapHeadroom, type HeapPressureContext, type HeapPressureMonitor, type HeapPressureMonitorOptions, type InMemoryRuntimeConfig, type IntervalHandle, type LanggraphCorsConfig, type ReloadableInMemoryRuntime, type ResolvedMemoryBusLimits, type ResolvedProtocolRuntime, type ResolvedRuntimeDeps, type RuntimeCapabilities, type RuntimeMemorySnapshot, type RuntimeSignalSubscription, type SkeinRuntimeCommonOptions, type SkeinRuntimeOptions, allowedOrigin, applyNodeCors, checkHeapHeadroom, corsFromHttpConfig, corsPreflightHeaders, corsResponseHeaders, createConsoleLogger, createInMemoryDeps, describeError, describeHeapHeadroom, describePoolPressure, detectRuntimeCapabilities, disabledRoutesFromHttpConfig, embedInMemoryGraphs, formatLogMeta, graphMapToResolver, joinList, loadInMemoryRuntime, loadReloadableInMemoryRuntime, normalizeEmbeddableGraphs, readContainerMemoryLimit, resolveCronTickMs, resolveHeapPressureOptions, resolveMaxPageSize, resolveMemoryBusLimits, resolveProtocolRuntime, resolveRunConcurrency, resolveRunTimeoutMs, resolveRuntimeDeps, resolveShutdownGraceMs, routeMatcherFor, routesFromHttpConfig, runFailureIdentity, sendNodeError, sendNodePreflight, sendNodeResponse, startHeapPressureMonitor, stripBasePath, toCorsOptions };
706
+ export { type ConsoleLogLevel, type ConsoleLoggerOptions, type CorsSetting, DEFAULT_HEAP_SAMPLE_MS, DEFAULT_HEAP_WARN_PERCENT, DevStateSnapshot, type EmbeddableGraph, type FileReader, HEAP_REARM_MARGIN_PERCENT, HEAP_REARM_PERCENT, type HeapHeadroom, type HeapPressureContext, type HeapPressureMonitor, type HeapPressureMonitorOptions, type InMemoryRuntimeConfig, type IntervalHandle, type LanggraphCorsConfig, type RawIdempotency, type RawStoreTtl, type RawThreadTtl, type ReloadableInMemoryRuntime, type ResolvedMemoryBusLimits, type ResolvedProtocolRuntime, type ResolvedRuntimeDeps, type RuntimeCapabilities, type RuntimeMemorySnapshot, type RuntimeSignalSubscription, type SkeinRuntimeCommonOptions, type SkeinRuntimeOptions, allowedOrigin, applyNodeCors, checkHeapHeadroom, consoleMountFromHttpConfig, corsFromHttpConfig, corsPreflightHeaders, corsResponseHeaders, createConsoleLogger, createInMemoryDeps, describeError, describeHeapHeadroom, describePoolPressure, detectRuntimeCapabilities, disabledRoutesFromHttpConfig, embedInMemoryGraphs, formatLogMeta, graphMapToResolver, joinList, loadInMemoryRuntime, loadReloadableInMemoryRuntime, normalizeEmbeddableGraphs, readContainerMemoryLimit, resolveCronTickMs, resolveHeapPressureOptions, resolveIdempotency, resolveMaxPageSize, resolveMemoryBusLimits, resolveProtocolRuntime, resolveRunConcurrency, resolveRunTimeoutMs, resolveRuntimeDeps, resolveShutdownGraceMs, resolveStoreTtl, resolveThreadTtl, routeMatcherFor, routesFromHttpConfig, runFailureIdentity, sendNodeError, sendNodePreflight, sendNodeResponse, startHeapPressureMonitor, stripBasePath, toCorsOptions };
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import {
2
+ consoleMountFromHttpConfig,
2
3
  corsFromHttpConfig,
3
4
  createInMemoryDeps,
4
5
  disabledRoutesFromHttpConfig,
@@ -9,11 +10,14 @@ import {
9
10
  normalizeEmbeddableGraphs,
10
11
  positiveIntegerFromEnv,
11
12
  requirePositiveInteger,
13
+ resolveIdempotency,
12
14
  resolveMaxPageSize,
13
15
  resolveMemoryBusLimits,
16
+ resolveStoreTtl,
17
+ resolveThreadTtl,
14
18
  routesFromHttpConfig,
15
19
  toCorsOptions
16
- } from "./chunk-QOHYARHD.js";
20
+ } from "./chunk-XDS3YWFL.js";
17
21
  import "./chunk-JUBBCC7R.js";
18
22
 
19
23
  // src/resolve-runtime.ts
@@ -162,7 +166,7 @@ function resolveLogger(option, injected, frameworkLogger) {
162
166
  return option ?? frameworkLogger;
163
167
  }
164
168
  async function resolveRuntimeDeps(options, frameworkLogger) {
165
- const loaded = options.deps ? { deps: options.deps, cors: void 0, routes: skeinRoutes } : await (await import("./in-memory-runtime-32UYJRVJ.js")).loadInMemoryRuntime(options.config, options.importModule);
169
+ const loaded = options.deps ? { deps: options.deps, cors: void 0, routes: skeinRoutes } : await (await import("./in-memory-runtime-BUUSE63C.js")).loadInMemoryRuntime(options.config, options.importModule);
166
170
  const logger = resolveLogger(options.logger, loaded.deps.logger, frameworkLogger);
167
171
  if (logger && !loaded.deps.logger) loaded.deps.logger = logger;
168
172
  return { deps: loaded.deps, cors: loaded.cors, routes: loaded.routes, logger };
@@ -653,6 +657,7 @@ export {
653
657
  allowedOrigin,
654
658
  applyNodeCors,
655
659
  checkHeapHeadroom,
660
+ consoleMountFromHttpConfig,
656
661
  corsFromHttpConfig,
657
662
  corsPreflightHeaders,
658
663
  corsResponseHeaders,
@@ -673,6 +678,7 @@ export {
673
678
  readContainerMemoryLimit,
674
679
  resolveCronTickMs,
675
680
  resolveHeapPressureOptions,
681
+ resolveIdempotency,
676
682
  resolveMaxPageSize,
677
683
  resolveMemoryBusLimits,
678
684
  resolveProtocolRuntime,
@@ -680,6 +686,8 @@ export {
680
686
  resolveRunTimeoutMs,
681
687
  resolveRuntimeDeps,
682
688
  resolveShutdownGraceMs,
689
+ resolveStoreTtl,
690
+ resolveThreadTtl,
683
691
  routeMatcherFor,
684
692
  routesFromHttpConfig,
685
693
  runFailureIdentity,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skein-js/server-kit",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "Shared, framework-agnostic building blocks for skein-js HTTP adapters — in-memory dev runtime, LangGraph dev-state import, and langgraph.json CORS mapping.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Maina Wycliffe <wmmaina7@gmail.com>",
@@ -49,10 +49,10 @@
49
49
  "cors": "^2.8.5",
50
50
  "superjson": "^2.2.6",
51
51
  "zod": "^3.25.76",
52
- "@skein-js/agent-protocol": "0.13.0",
53
- "@skein-js/config": "0.13.0",
54
- "@skein-js/core": "0.13.0",
55
- "@skein-js/storage-memory": "0.13.0"
52
+ "@skein-js/agent-protocol": "0.14.0",
53
+ "@skein-js/config": "0.14.0",
54
+ "@skein-js/core": "0.14.0",
55
+ "@skein-js/storage-memory": "0.14.0"
56
56
  },
57
57
  "peerDependencies": {
58
58
  "@langchain/langgraph": "^1.4.0"