stitchkit 0.70.4 → 0.70.6

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.
Files changed (35) hide show
  1. package/dist/agent-runtime/purge.d.ts +28 -0
  2. package/dist/agent-runtime/purge.d.ts.map +1 -0
  3. package/dist/agent-runtime/runtime.d.ts.map +1 -1
  4. package/dist/agent-runtime/sqlite-purge.d.ts +7 -0
  5. package/dist/agent-runtime/sqlite-purge.d.ts.map +1 -0
  6. package/dist/agent-runtime/sqlite.d.ts.map +1 -1
  7. package/dist/agent-runtime/store-driver.d.ts +2 -0
  8. package/dist/agent-runtime/store-driver.d.ts.map +1 -1
  9. package/dist/agent-runtime/store-purge.d.ts +13 -0
  10. package/dist/agent-runtime/store-purge.d.ts.map +1 -0
  11. package/dist/agent-runtime/store.d.ts +3 -0
  12. package/dist/agent-runtime/store.d.ts.map +1 -1
  13. package/dist/agent-runtime-harness.js +1 -1
  14. package/dist/agent-runtime-sqlite-bun.js +2 -2
  15. package/dist/agent-runtime-sqlite-node.js +2 -2
  16. package/dist/agent-runtime.d.ts +2 -0
  17. package/dist/agent-runtime.d.ts.map +1 -1
  18. package/dist/agent-runtime.js +11 -3
  19. package/dist/application.js +2 -2
  20. package/dist/{index-jw81xr75.js → index-19eet1qx.js} +127 -51
  21. package/dist/{index-7wpgrqa8.js → index-3z73fh2c.js} +93 -7
  22. package/dist/{index-35wcrke5.js → index-by57nwhc.js} +3 -0
  23. package/dist/{index-9sx8tbz2.js → index-cksjz4eg.js} +50 -1
  24. package/dist/{index-c97p90dc.js → index-cx84zg25.js} +82 -24
  25. package/dist/{index-w0445741.js → index-hvftzz91.js} +1 -1
  26. package/dist/node.js +2 -2
  27. package/dist/server/contract-stream.d.ts.map +1 -1
  28. package/dist/server/http-stream-lifetime.d.ts +19 -0
  29. package/dist/server/http-stream-lifetime.d.ts.map +1 -0
  30. package/dist/server/index.js +2 -2
  31. package/dist/server/shutdown.d.ts.map +1 -1
  32. package/dist/server/streaming-route.d.ts.map +1 -1
  33. package/dist/testing.js +2 -2
  34. package/llms-full.txt +87 -5
  35. package/package.json +1 -1
@@ -20,8 +20,11 @@ import {
20
20
  setRequestError
21
21
  } from "./index-w9m1wznn.js";
22
22
  import {
23
- ShutdownOptionsSchema
24
- } from "./index-7wpgrqa8.js";
23
+ ShutdownOptionsSchema,
24
+ isStreamCancellation,
25
+ ownHttpStream,
26
+ settleStreamCleanup
27
+ } from "./index-3z73fh2c.js";
25
28
  import {
26
29
  errorCode,
27
30
  normalizeError,
@@ -598,8 +601,28 @@ function streamingRoute(options) {
598
601
  handler: async (request, context) => {
599
602
  applyIdleTimeout(context.server, request, idleTimeoutSeconds);
600
603
  const departed = new AbortController;
601
- const iterable = await options.source(request, { ...context, signal: departed.signal });
602
- const iterator = iterable[Symbol.asyncIterator]();
604
+ const settled = Promise.withResolvers();
605
+ settled.promise.catch(() => {
606
+ return;
607
+ });
608
+ const pumped = Promise.withResolvers();
609
+ pumped.promise.catch(() => {
610
+ return;
611
+ });
612
+ let cancel = () => departed.abort();
613
+ ownHttpStream(request, { cancel: () => cancel(), settled: settled.promise });
614
+ let iterator;
615
+ try {
616
+ const iterable = await options.source(request, {
617
+ ...context,
618
+ signal: departed.signal
619
+ });
620
+ iterator = iterable[Symbol.asyncIterator]();
621
+ } catch (error) {
622
+ departed.abort();
623
+ settled.reject(error);
624
+ throw error;
625
+ }
603
626
  const encoder2 = new TextEncoder;
604
627
  let heartbeat = null;
605
628
  let closed = false;
@@ -613,15 +636,22 @@ function streamingRoute(options) {
613
636
  if (closed)
614
637
  return;
615
638
  closed = true;
639
+ stopHeartbeat();
640
+ signalDemand();
641
+ request.signal.removeEventListener("abort", onRequestAbort);
642
+ departed.abort();
643
+ const hook = Promise.withResolvers();
616
644
  try {
617
645
  options.onClose?.();
618
- stopHeartbeat();
619
- signalDemand();
620
- departed.abort();
621
- Promise.resolve(iterator.return?.(undefined)).catch(() => {
622
- return;
623
- });
624
- } catch {}
646
+ hook.resolve();
647
+ } catch (error) {
648
+ hook.reject(error);
649
+ }
650
+ settleStreamCleanup([
651
+ hook.promise,
652
+ pumped.promise,
653
+ Promise.resolve().then(() => iterator.return?.(undefined))
654
+ ]).then(() => settled.resolve(), (error) => settled.reject(error));
625
655
  };
626
656
  let demand = null;
627
657
  const signalDemand = () => {
@@ -629,6 +659,7 @@ function streamingRoute(options) {
629
659
  demand = null;
630
660
  resolve?.();
631
661
  };
662
+ const onRequestAbort = () => cancel();
632
663
  const stream = new ReadableStream({
633
664
  start(controller) {
634
665
  const send = (text) => {
@@ -647,15 +678,16 @@ function streamingRoute(options) {
647
678
  controller.close();
648
679
  } catch {}
649
680
  };
650
- if (request.signal.aborted) {
681
+ cancel = () => {
651
682
  release();
652
683
  finish();
684
+ };
685
+ if (request.signal.aborted || departed.signal.aborted) {
686
+ pumped.resolve();
687
+ cancel();
653
688
  return;
654
689
  }
655
- request.signal.addEventListener("abort", () => {
656
- release();
657
- finish();
658
- });
690
+ request.signal.addEventListener("abort", onRequestAbort, { once: true });
659
691
  send(framing.keepAlive);
660
692
  heartbeat = setInterval(() => {
661
693
  if ((controller.desiredSize ?? 1) > 0)
@@ -696,9 +728,13 @@ function streamingRoute(options) {
696
728
  if (framing.done)
697
729
  send(framing.done);
698
730
  } catch (error) {
699
- if (!closed)
731
+ if (closed) {
732
+ if (!isStreamCancellation(error, departed.signal))
733
+ pumped.reject(error);
734
+ } else
700
735
  send(framing.frame(normalizeError(error).toJSON()));
701
736
  } finally {
737
+ pumped.resolve();
702
738
  release();
703
739
  finish();
704
740
  }
@@ -890,9 +926,32 @@ function waitForAbort(signal) {
890
926
  function contractStreamResponse(request, context, source, descriptor, operationAbort) {
891
927
  const format = descriptor.format ?? "ndjson";
892
928
  const maxFrameBytes = descriptor.maxFrameBytes ?? DEFAULT_CONTRACT_STREAM_FRAME_BYTES;
929
+ const iterator = source[Symbol.asyncIterator]();
930
+ let pendingNext;
931
+ const settled = Promise.withResolvers();
932
+ settled.promise.catch(() => {
933
+ return;
934
+ });
893
935
  let lifetime;
894
936
  let lifetimeExpired = false;
895
- if (descriptor.lifetimeMs !== undefined) {
937
+ let closing = false;
938
+ const closeSource = (cancelled = operationAbort.signal.aborted) => {
939
+ if (closing)
940
+ return;
941
+ closing = true;
942
+ if (lifetime !== undefined)
943
+ clearTimeout(lifetime);
944
+ operationAbort.abort();
945
+ settleStreamCleanup([
946
+ Promise.resolve(pendingNext).catch((error) => {
947
+ if (cancelled && !isStreamCancellation(error, operationAbort.signal))
948
+ throw error;
949
+ }),
950
+ Promise.resolve().then(() => iterator.return?.(undefined))
951
+ ]).then(() => settled.resolve(), (error) => settled.reject(error));
952
+ };
953
+ ownHttpStream(request, { cancel: () => closeSource(true), settled: settled.promise });
954
+ if (descriptor.lifetimeMs !== undefined && !closing) {
896
955
  lifetime = setTimeout(() => {
897
956
  lifetimeExpired = true;
898
957
  operationAbort.abort(new Error("Stream lifetime expired"));
@@ -900,12 +959,14 @@ function contractStreamResponse(request, context, source, descriptor, operationA
900
959
  lifetime.unref?.();
901
960
  }
902
961
  const frames = async function* () {
903
- const iterator = source[Symbol.asyncIterator]();
962
+ if (closing)
963
+ return;
904
964
  const aborted = waitForAbort(operationAbort.signal);
905
965
  let terminalSeen = descriptor.terminal === undefined;
906
966
  try {
907
967
  for (;; ) {
908
- const next = await Promise.race([iterator.next(), aborted]);
968
+ pendingNext = Promise.resolve(iterator.next());
969
+ const next = await Promise.race([pendingNext, aborted]);
909
970
  if (next.done)
910
971
  break;
911
972
  const parsed = descriptor.item.safeParse(next.value);
@@ -951,10 +1012,7 @@ function contractStreamResponse(request, context, source, descriptor, operationA
951
1012
  } finally {
952
1013
  if (lifetime !== undefined)
953
1014
  clearTimeout(lifetime);
954
- operationAbort.abort();
955
- Promise.resolve(iterator.return?.(undefined)).catch(() => {
956
- return;
957
- });
1015
+ closeSource();
958
1016
  }
959
1017
  };
960
1018
  const route = streamingRoute({
@@ -8,7 +8,7 @@ import {
8
8
  } from "./index-8eywc9zv.js";
9
9
  import {
10
10
  ShutdownOptionsSchema
11
- } from "./index-7wpgrqa8.js";
11
+ } from "./index-3z73fh2c.js";
12
12
  import {
13
13
  AppError
14
14
  } from "./index-0w9abg87.js";
package/dist/node.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  createHandler,
6
6
  createSocketIOServer,
7
7
  createUnixClientTransport
8
- } from "./index-c97p90dc.js";
8
+ } from "./index-cx84zg25.js";
9
9
  import {
10
10
  createImplement,
11
11
  createImplementRegistry,
@@ -23,7 +23,7 @@ import {
23
23
  ShutdownStateSchema,
24
24
  ShutdownStatusSchema,
25
25
  createServerLifecycle
26
- } from "./index-7wpgrqa8.js";
26
+ } from "./index-3z73fh2c.js";
27
27
  import"./index-nt1mp8km.js";
28
28
  import"./index-vj3vvpaa.js";
29
29
  import {
@@ -1 +1 @@
1
- {"version":3,"file":"contract-stream.d.ts","sourceRoot":"","sources":["../../src/server/contract-stream.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,wBAAwB,EAC9B,MAAM,aAAa,CAAC;AAGrB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAiB/C,gEAAgE;AAChE,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,eAAe,EACxB,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,EAC9B,UAAU,EAAE,wBAAwB,EACpC,cAAc,EAAE,eAAe,GAC9B,OAAO,CAAC,QAAQ,CAAC,CA6FnB"}
1
+ {"version":3,"file":"contract-stream.d.ts","sourceRoot":"","sources":["../../src/server/contract-stream.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,wBAAwB,EAC9B,MAAM,aAAa,CAAC;AAQrB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAiB/C,gEAAgE;AAChE,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,eAAe,EACxB,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,EAC9B,UAAU,EAAE,wBAAwB,EACpC,cAAc,EAAE,eAAe,GAC9B,OAAO,CAAC,QAAQ,CAAC,CAoHnB"}
@@ -0,0 +1,19 @@
1
+ /** Request identity stays native: cloning it would break runtime timeout/upgrade APIs. */
2
+ interface StreamLifetime {
3
+ cancel(): void;
4
+ settled: Promise<void>;
5
+ }
6
+ /** Abort-aware I/O may reject with the signal reason or wrap it as its cause. */
7
+ export declare function isStreamCancellation(error: unknown, signal: AbortSignal): boolean;
8
+ /** Internal bridge between Fetch-clean streaming routes and their managed server. */
9
+ export declare function ownHttpStream(request: Request, stream: StreamLifetime): void;
10
+ export declare function settleStreamCleanup(operations: readonly Promise<unknown>[]): Promise<void>;
11
+ export declare function createHttpStreamTracker(): {
12
+ bind(request: Request, onComplete: () => void): () => void;
13
+ readonly pendingRequests: number;
14
+ cancel(): void;
15
+ assertClean: () => void;
16
+ drain(): Promise<void>;
17
+ };
18
+ export {};
19
+ //# sourceMappingURL=http-stream-lifetime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-stream-lifetime.d.ts","sourceRoot":"","sources":["../../src/server/http-stream-lifetime.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,UAAU,cAAc;IACtB,MAAM,IAAI,IAAI,CAAC;IACf,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAQD,iFAAiF;AACjF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAMjF;AAED,qFAAqF;AACrF,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,GAAG,IAAI,CAE5E;AAED,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,SAAS,OAAO,CAAC,OAAO,CAAC,EAAE,GACtC,OAAO,CAAC,IAAI,CAAC,CAOf;AAED,wBAAgB,uBAAuB;kBAWrB,OAAO,cAAc,MAAM,IAAI,GAAG,MAAM,IAAI;8BAkCnC,MAAM;;;;EAehC"}
@@ -13,7 +13,7 @@ import {
13
13
  sseRoute,
14
14
  streamingRoute,
15
15
  webSocketLane
16
- } from "../index-c97p90dc.js";
16
+ } from "../index-cx84zg25.js";
17
17
  import {
18
18
  composeAuthHooks,
19
19
  createAuthHook,
@@ -60,7 +60,7 @@ import {
60
60
  ShutdownStateSchema,
61
61
  ShutdownStatusSchema,
62
62
  createServerLifecycle
63
- } from "../index-7wpgrqa8.js";
63
+ } from "../index-3z73fh2c.js";
64
64
  import {
65
65
  jsonSchemaFields,
66
66
  toJsonSchema
@@ -1 +1 @@
1
- {"version":3,"file":"shutdown.d.ts","sourceRoot":"","sources":["../../src/server/shutdown.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C,eAAO,MAAM,mBAAmB;;;;;;;EAO9B,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,eAAO,MAAM,qBAAqB;;;;;;iBAehC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;iBAM/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;iBAY/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE,6EAA6E;AAC7E,MAAM,WAAW,mBAAmB,CAAC,QAAQ;IAC3C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;CAC9D;AAED,MAAM,WAAW,eAAe;IAC9B,aAAa,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/C,eAAe,IAAI,MAAM,CAAC;IAC1B,iBAAiB,IAAI,MAAM,CAAC;IAC5B,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IACrC,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAED,UAAU,eAAe;IACvB,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,YAAY,CAAC,OAAO,CAAC,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;IAC1E,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;CAC9D;AAoFD,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,eAAe,GAAG,eAAe,CAiKxF"}
1
+ {"version":3,"file":"shutdown.d.ts","sourceRoot":"","sources":["../../src/server/shutdown.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C,eAAO,MAAM,mBAAmB;;;;;;;EAO9B,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,eAAO,MAAM,qBAAqB;;;;;;iBAehC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;iBAM/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;iBAY/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE,6EAA6E;AAC7E,MAAM,WAAW,mBAAmB,CAAC,QAAQ;IAC3C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;CAC9D;AAED,MAAM,WAAW,eAAe;IAC9B,aAAa,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/C,eAAe,IAAI,MAAM,CAAC;IAC1B,iBAAiB,IAAI,MAAM,CAAC;IAC5B,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IACrC,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAED,UAAU,eAAe;IACvB,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,YAAY,CAAC,OAAO,CAAC,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;IAC1E,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;CAC9D;AAoFD,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,eAAe,GAAG,eAAe,CA4KxF"}
@@ -1 +1 @@
1
- {"version":3,"file":"streaming-route.d.ts","sourceRoot":"","sources":["../../src/server/streaming-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAEzD,uFAAuF;AACvF,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,KAAK,CAAC;AAE/C;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,OAAQ,CAAC;AAEjD,MAAM,WAAW,qBAAqB,CAAC,OAAO,GAAG,OAAO;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,gDAAgD;IAChD,MAAM,CAAC,EAAE,UAAU,GAAG,KAAK,CAAC;IAC5B,sCAAsC;IACtC,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;;OAUG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;;;OAOG;IACH,MAAM,EAAE,CACN,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,sBAAsB,CAAC,OAAO,CAAC,KACrC,aAAa,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC;IAC9D,8EAA8E;IAC9E,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,WAAW,sBAAsB,CAAC,OAAO,GAAG,OAAO,CAAE,SAAQ,eAAe,CAAC,OAAO,CAAC;IACzF,4EAA4E;IAC5E,MAAM,EAAE,WAAW,CAAC;CACrB;AAiGD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,GAAG,OAAO,EAC9C,OAAO,EAAE,qBAAqB,CAAC,OAAO,CAAC,GACtC,QAAQ,CAAC,OAAO,CAAC,CAiNnB;AAED,uFAAuF;AACvF,wBAAgB,WAAW,CAAC,OAAO,GAAG,OAAO,EAC3C,OAAO,EAAE,IAAI,CAAC,qBAAqB,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,GACtD,QAAQ,CAAC,OAAO,CAAC,CAEnB;AAED,gFAAgF;AAChF,wBAAgB,QAAQ,CAAC,OAAO,GAAG,OAAO,EACxC,OAAO,EAAE,IAAI,CAAC,qBAAqB,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,GACtD,QAAQ,CAAC,OAAO,CAAC,CAEnB"}
1
+ {"version":3,"file":"streaming-route.d.ts","sourceRoot":"","sources":["../../src/server/streaming-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAOrD,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAEzD,uFAAuF;AACvF,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,KAAK,CAAC;AAE/C;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,OAAQ,CAAC;AAEjD,MAAM,WAAW,qBAAqB,CAAC,OAAO,GAAG,OAAO;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,gDAAgD;IAChD,MAAM,CAAC,EAAE,UAAU,GAAG,KAAK,CAAC;IAC5B,sCAAsC;IACtC,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;;OAUG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;;;OAOG;IACH,MAAM,EAAE,CACN,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,sBAAsB,CAAC,OAAO,CAAC,KACrC,aAAa,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC;IAC9D,8EAA8E;IAC9E,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,WAAW,sBAAsB,CAAC,OAAO,GAAG,OAAO,CAAE,SAAQ,eAAe,CAAC,OAAO,CAAC;IACzF,4EAA4E;IAC5E,MAAM,EAAE,WAAW,CAAC;CACrB;AAiGD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,GAAG,OAAO,EAC9C,OAAO,EAAE,qBAAqB,CAAC,OAAO,CAAC,GACtC,QAAQ,CAAC,OAAO,CAAC,CA4OnB;AAED,uFAAuF;AACvF,wBAAgB,WAAW,CAAC,OAAO,GAAG,OAAO,EAC3C,OAAO,EAAE,IAAI,CAAC,qBAAqB,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,GACtD,QAAQ,CAAC,OAAO,CAAC,CAEnB;AAED,gFAAgF;AAChF,wBAAgB,QAAQ,CAAC,OAAO,GAAG,OAAO,EACxC,OAAO,EAAE,IAAI,CAAC,qBAAqB,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,GACtD,QAAQ,CAAC,OAAO,CAAC,CAEnB"}
package/dist/testing.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  } from "./index-x1th9s8c.js";
5
5
  import {
6
6
  createApplication
7
- } from "./index-w0445741.js";
7
+ } from "./index-hvftzz91.js";
8
8
  import"./index-2k4yrqkc.js";
9
9
  import"./index-8eywc9zv.js";
10
10
  import {
@@ -12,7 +12,7 @@ import {
12
12
  RealtimeRequestInvalidAcknowledgementError,
13
13
  RealtimeRequestTimeoutError
14
14
  } from "./index-7b188kmz.js";
15
- import"./index-7wpgrqa8.js";
15
+ import"./index-3z73fh2c.js";
16
16
  import {
17
17
  mcpProjectionCandidate,
18
18
  prepareProjectedMcpTools,
package/llms-full.txt CHANGED
@@ -960,8 +960,11 @@ const result = await server.shutdown({
960
960
  })
961
961
  ```
962
962
 
963
- The first call closes HTTP and Socket.IO admission, then gives admitted HTTP/application work the
964
- full `gracePeriodMs` budget. Once that work drains, `realtimeCloseTimeoutMs` (default `1_000`)
963
+ The first call closes HTTP and Socket.IO admission and cancels owned contract streams and
964
+ `streamingRoute` / `ndjsonRoute` / `sseRoute` sources. Their supplied `signal` is the lifetime:
965
+ waiting producers must honour it, and their iterator cleanup must finish. No separate application
966
+ abort controller or copied shutdown loop is required. Admitted finite HTTP/application work and
967
+ stream cleanup share the full `gracePeriodMs` budget. Once that work drains, `realtimeCloseTimeoutMs` (default `1_000`)
965
968
  bounds WebSocket close handshakes inside the same outer deadline. Any upgraded sockets still open
966
969
  at that boundary are terminated without shortening HTTP grace, and graceful runtime shutdown
967
970
  continues. If the outer grace budget or external signal forces destructive teardown,
@@ -970,11 +973,18 @@ the first options win. New
970
973
  ordinary HTTP work receives `503`, `Retry-After` and `Connection: close` outside
971
974
  `wrapFetch`. `result.outcome` is `clean` or `forced`; a forced result preserves
972
975
  the pending snapshot and reason while final pending counters describe the
973
- post-close transport state. `forcedWebSockets` counts both sockets terminated at the dedicated
976
+ post-close transport state and any still-owned stream cleanup. A streaming request increments
977
+ `completedRequests` only after its source cleanup settles, not when its `Response` is returned.
978
+ `abortedRequests` counts requests pending at outer force, not subscriptions closed cooperatively
979
+ during grace. `forcedWebSockets` counts both sockets terminated at the dedicated
974
980
  realtime bound and sockets terminated by outer force; `pendingWebSocketsAtForce` is only the latter
975
981
  snapshot, so a clean result can truthfully report a bounded realtime termination. A graceful phase error still runs forced cleanup and
976
982
  then rejects with the original error; a forced transport that cannot confirm
977
- completion before `forceTimeoutMs` rejects instead of reporting a false zero.
983
+ completion before `forceTimeoutMs` rejects instead of reporting a false zero. An uncooperative
984
+ stream stays pending even if its connection is gone; a cleanup error rejects shutdown.
985
+ Standalone `createHandler` and arbitrary raw `Response` bodies have no managed source ownership:
986
+ their embedding host/producer owns cancellation. Request identity must be retained by `wrapFetch`
987
+ when forwarding to owned streaming routes; it is also required by native timeout/upgrade APIs.
978
988
  `runtime` is a diagnostics escape hatch, not a second canonical stop path.
979
989
 
980
990
  ### Trusted HTTPS in development
@@ -4538,6 +4548,61 @@ boundary, validate with the exported schema, and write only the current
4538
4548
  version. Core deliberately does not guess an application's database migration
4539
4549
  or silently accept an unknown future version.
4540
4550
 
4551
+ ### Purging a conversation
4552
+
4553
+ ```ts
4554
+ import { purgeAgentConversation } from 'stitchkit/agent-runtime';
4555
+
4556
+ const result = await purgeAgentConversation(store, {
4557
+ conversationId,
4558
+ expectedVersion: snapshot.version, // optional stale-intent protection
4559
+ });
4560
+ ```
4561
+
4562
+ The optional `AgentRuntimeStore.purgeConversation` capability is implemented by the memory and
4563
+ initialized Bun/Node SQLite stores. The helper returns `unsupported` for stores without it; it
4564
+ never pretends deletion succeeded. Other outcomes are `purged`, `already_purged`,
4565
+ `active` (with `runIds`), or `conflict` (with `actualVersion`). Storage failures reject.
4566
+
4567
+ Purge refuses queued, running and interrupt-requested runs. It does not interrupt, force-abandon
4568
+ or wait for a provider. Authorize the destructive request in the host, close its conversation
4569
+ ingress/attachments, then settle runs through normal interruption or explicitly justified recovery.
4570
+ Retry purge after they are terminal. An optional expected version must match the current snapshot;
4571
+ a version conflict requires a fresh deletion decision, not blind retry with a new version.
4572
+
4573
+ Successful purge atomically removes messages (including compacted inactive rows), all runs and
4574
+ retained terminal assistants, admissions/idempotency receipts, conversation heads and their owned
4575
+ indexes. It retains only a permanent conversation-ID tombstone. Purging an unknown ID reserves it
4576
+ as well, so a submit paused before admission cannot create it later. Repeating the call returns
4577
+ `already_purged`, regardless of the original expected version. A lost response is safely retried
4578
+ with the same ID; **new chats require a new ID**.
4579
+
4580
+ Every later runtime mutation rejects with `AgentConversationPurgedError`, including fresh and
4581
+ duplicate admissions, checkpoints, recovery and compaction. Existing controller leases grant no
4582
+ exception. Existing reads return empty snapshots, absent runs and empty message pages; purged
4583
+ conversations disappear from the SQLite catalog and recovery scans. Snapshots retain their
4584
+ existing empty `version: 0` shape; use the purge result, not an empty snapshot, as deletion evidence.
4585
+
4586
+ SQLite commits the tombstone and all deletions in one `BEGIN IMMEDIATE` transaction. Failure before
4587
+ commit rolls everything back; reopen/recovery cannot restore committed deleted records. Same-thread
4588
+ connection contention rejects promptly without effects; retry after the competing operation settles.
4589
+ Initialization adds the tombstone table and write guards to schema v1 transactionally, without
4590
+ changing application tables or `user_version`. Guards also fence older writers. `initialize: false`
4591
+ on an original v1 database does not add purge capability; open an initialized writer first.
4592
+
4593
+ Consumer-owned model selections, projections, attachment files, delivery/outbox records and event
4594
+ logs remain outside this boundary. Retain an authorized cleanup intent plus any needed opaque file
4595
+ references in consumer storage, purge first, then retry idempotent consumer cleanup. Invalidate local
4596
+ caches/subscriptions so an old delivered event cannot repopulate a UI. The library never traverses
4597
+ paths, revokes remote artifacts or claims a cross-store transaction. Nor does logical purge securely
4598
+ wipe database free pages, WAL or backups. Memory tombstones last only for that store instance.
4599
+
4600
+ Custom normalized drivers may opt in through `AgentConversationPurgeDriver` on `driver.conversations`.
4601
+ Their transaction must serialize `isPurged`, head/active-run checks, `remove`, and **all** mutations
4602
+ for the same ID, including absent-ID admission. An optimistic head CAS alone is insufficient. `remove`
4603
+ must delete every owned payload/index and preserve the tombstone; errors must roll back the entire
4604
+ transaction. Drivers without this guarantee must leave the capability absent. See ADR 0138.
4605
+
4541
4606
  ## Events and reconnect
4542
4607
 
4543
4608
  `publish` receives event classes with different guarantees:
@@ -5219,6 +5284,12 @@ try {
5219
5284
  `release()` is idempotent. Admission and counter increment are atomic, so work
5220
5285
  cannot slip between the shutdown check and drain accounting.
5221
5286
 
5287
+ `managedServerResource` already owns contract/streaming-route response lifetimes. It cancels
5288
+ their supplied signals at admission close and awaits source cleanup before dependent resources
5289
+ close. Do not add application admission leases or a second cancellation registry for those
5290
+ streams. A source that ignores cancellation or fails its cleanup prevents a clean shutdown;
5291
+ the existing grace and force budgets still bound the result.
5292
+
5222
5293
  ### Bounded operation admission
5223
5294
 
5224
5295
  Compose `createBoundedAdmission` when accepted work also competes for a finite
@@ -11943,7 +12014,7 @@ Also re-exports the error helpers from `stitchkit/contract`.
11943
12014
  | `BunServerHandle` | _type_ | managed Bun handle (`url`, `port`, `runtime`, `status`, `shutdown`) |
11944
12015
  | `ManagedServerHandle` | _type_ | shared lifecycle shape generic over the runtime escape hatch |
11945
12016
  | `ShutdownOptionsSchema` / `ShutdownOptions` | schema / _type_ | HTTP/application grace, a separate WebSocket close-handshake bound, bounded forced-completion timeout, retry hint and optional external abort signal |
11946
- | `ShutdownStatusSchema` / `ShutdownStatus` | schema / _type_ | live state and request/WebSocket counters |
12017
+ | `ShutdownStatusSchema` / `ShutdownStatus` | schema / _type_ | live state and request/WebSocket counters; owned streams remain pending through source cleanup |
11947
12018
  | `ShutdownResultSchema` / `ShutdownResult` | schema / _type_ | clean/forced result with final counters, outer-force snapshots and `forcedWebSockets` including bounded realtime terminations |
11948
12019
  | `ShutdownStateSchema` / `ShutdownState` | schema / _type_ | managed lifecycle state machine |
11949
12020
  | `ServiceDef` | _type_ | the result of `implement` |
@@ -12302,6 +12373,11 @@ Server-only optional application runtime. See the
12302
12373
  | `AgentHistoryMutationSchema` | schema | typed canonical message mutation applied inside the winning state transaction |
12303
12374
  | `RecoverAgentRunSchema` | schema | explicit abandon/requeue recovery decision; acquired runs require replay-safe evidence |
12304
12375
  | `createMemoryAgentRuntimeStore` | function | process-local reference adapter, not production durability |
12376
+ | `purgeAgentConversation` | function | dispatch optional atomic deletion; returns `unsupported`, `active`, `conflict`, `purged` or `already_purged` |
12377
+ | `AgentConversationPurgeInputSchema` / `AgentConversationPurgeInput` | schema / _type_ | conversation ID and optional expected snapshot version |
12378
+ | `AgentConversationPurgeResultSchema` / `AgentConversationPurgeResult` | schema / _type_ | typed deletion/refusal outcomes; active refusal includes run IDs |
12379
+ | `AgentConversationPurgedError` | class | runtime mutation rejected because its conversation ID is permanently purged |
12380
+ | `AgentConversationPurgeDriver` | _type_ | optional `driver.conversations` capability: serialized tombstone read and atomic removal of all owned records |
12305
12381
  | `projectAgentHistory` | function | asynchronously project canonical records and resolved multimodal files into provider-valid AI SDK messages |
12306
12382
  | `defineModelRegistry` | function | typed language-model descriptors, capabilities and provider construction |
12307
12383
  | `AgentModelCatalogSchema` / `AgentModelCatalog` | schema / _type_ | provider-neutral complete/partial model catalog with separately sourced popularity, metrics, prices and observation time |
@@ -12403,6 +12479,12 @@ neither grows with the length of the conversation, and neither needs anything ne
12403
12479
  conversation — that is what the store's reducer validates against, and what the runtime builds a
12404
12480
  prompt from (→ ADR 0112).
12405
12481
 
12482
+ `AgentRuntimeStore.purgeConversation` is optional; `purgeAgentConversation(store, input)` explicitly
12483
+ handles unsupported stores. Official memory and initialized SQLite stores refuse active runs,
12484
+ remove every owned payload and reserve the conversation ID permanently. Empty snapshots are not
12485
+ deletion receipts. Consumer metadata, files, event logs and UI cache invalidation remain outside
12486
+ the transaction. See [purging a conversation](../guide/agent-runtime.md#purging-a-conversation).
12487
+
12406
12488
  Store command/result exports are `AcceptInputAndAssignRun`, `AcceptInputAndAssignRunSchema`,
12407
12489
  `AcquireAgentRun`, `AcquireAgentRunSchema`, `CheckpointRunAssistant`,
12408
12490
  `CheckpointRunAssistantSchema`, `CommitRunTerminal`, `CommitRunTerminalSchema`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitchkit",
3
- "version": "0.70.4",
3
+ "version": "0.70.6",
4
4
  "description": "Contract-first backend framework — one defineContract() into an HTTP API, MCP tools, AI-agent tools and a typed client. Bun and Node.",
5
5
  "keywords": [
6
6
  "bun",