stitchkit 0.80.0 → 0.81.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.
Files changed (179) hide show
  1. package/dist/agent-runtime/provider-failure.d.ts.map +1 -1
  2. package/dist/agent-runtime-coding-tools.js +1 -1
  3. package/dist/agent-runtime-harness.js +7 -5
  4. package/dist/agent-runtime.js +15 -19
  5. package/dist/application/notification-outbox.d.ts +80 -0
  6. package/dist/application/notification-outbox.d.ts.map +1 -0
  7. package/dist/application/process-lifecycle.d.ts +166 -0
  8. package/dist/application/process-lifecycle.d.ts.map +1 -0
  9. package/dist/application/revision-signal.d.ts +86 -0
  10. package/dist/application/revision-signal.d.ts.map +1 -0
  11. package/dist/application/state-store.d.ts +18 -0
  12. package/dist/application/state-store.d.ts.map +1 -0
  13. package/dist/application.d.ts +4 -0
  14. package/dist/application.d.ts.map +1 -1
  15. package/dist/application.js +725 -1
  16. package/dist/browser/http.d.ts +14 -0
  17. package/dist/browser/http.d.ts.map +1 -1
  18. package/dist/browser/resumable.d.ts +6 -0
  19. package/dist/browser/resumable.d.ts.map +1 -1
  20. package/dist/cli.js +9 -7
  21. package/dist/contract/define.d.ts +27 -4
  22. package/dist/contract/define.d.ts.map +1 -1
  23. package/dist/contract/errors-factory.d.ts +22 -3
  24. package/dist/contract/errors-factory.d.ts.map +1 -1
  25. package/dist/contract/index.d.ts +2 -2
  26. package/dist/contract/index.d.ts.map +1 -1
  27. package/dist/contract/index.js +1 -1
  28. package/dist/geo/index.d.ts +4 -0
  29. package/dist/geo/index.d.ts.map +1 -0
  30. package/dist/geo/maxmind.d.ts +4 -0
  31. package/dist/geo/maxmind.d.ts.map +1 -0
  32. package/dist/geo/resolver.d.ts +9 -0
  33. package/dist/geo/resolver.d.ts.map +1 -0
  34. package/dist/geo/types.d.ts +59 -0
  35. package/dist/geo/types.d.ts.map +1 -0
  36. package/dist/geo.d.ts +3 -0
  37. package/dist/geo.d.ts.map +1 -0
  38. package/dist/geo.js +245 -0
  39. package/dist/{index-9s6z8vgf.js → index-1dd82z59.js} +1 -1
  40. package/dist/{index-vwp69871.js → index-27487ay5.js} +4 -4
  41. package/dist/{index-7ag81v2f.js → index-27gm3d6g.js} +10 -321
  42. package/dist/{index-y01f4223.js → index-2bxcmqkb.js} +127 -43
  43. package/dist/{index-dxesg10d.js → index-3ds42w1w.js} +3 -3
  44. package/dist/index-3xwxfj4z.js +334 -0
  45. package/dist/{index-egaabxkz.js → index-5z8tezpx.js} +2 -2
  46. package/dist/{index-fxz0v7vg.js → index-7evdsyez.js} +1 -83
  47. package/dist/{index-xcetq6j3.js → index-90d7h2cr.js} +32 -13
  48. package/dist/{index-t0z5p5w6.js → index-apn8repm.js} +1 -1
  49. package/dist/{index-758zx2ha.js → index-dggwnnjv.js} +2 -2
  50. package/dist/{index-dwf1f2ap.js → index-f9mb610r.js} +1 -62
  51. package/dist/{index-5mtc969c.js → index-fwbkp2jn.js} +1 -1
  52. package/dist/index-p13mwz16.js +16 -0
  53. package/dist/{index-m9kzax52.js → index-p18f59zy.js} +21 -5
  54. package/dist/{index-hb5pf3s4.js → index-qmgs7y68.js} +1 -1
  55. package/dist/{index-2rwwmr7v.js → index-s4ckmrac.js} +1 -1
  56. package/dist/{index-h4pj6fta.js → index-s8cnx02s.js} +8 -5
  57. package/dist/{index-k9ay6v6d.js → index-vchzar7e.js} +74 -23
  58. package/dist/index-vsbzgd7b.js +84 -0
  59. package/dist/{index-dxjjv399.js → index-xbppf54a.js} +95 -15
  60. package/dist/index-y91ry9h2.js +4 -0
  61. package/dist/index-zcgf3gqf.js +83 -0
  62. package/dist/index.js +17 -13
  63. package/dist/internal/error-message.d.ts +3 -0
  64. package/dist/internal/error-message.d.ts.map +1 -0
  65. package/dist/internal/media-type.d.ts +8 -0
  66. package/dist/internal/media-type.d.ts.map +1 -0
  67. package/dist/internal/route-pattern.d.ts +21 -0
  68. package/dist/internal/route-pattern.d.ts.map +1 -1
  69. package/dist/node.js +9 -6
  70. package/dist/observability/audit.d.ts +28 -1
  71. package/dist/observability/audit.d.ts.map +1 -1
  72. package/dist/observability/bounded-logger.d.ts +20 -0
  73. package/dist/observability/bounded-logger.d.ts.map +1 -0
  74. package/dist/observability/context.d.ts +12 -3
  75. package/dist/observability/context.d.ts.map +1 -1
  76. package/dist/observability/index.d.ts +3 -2
  77. package/dist/observability/index.d.ts.map +1 -1
  78. package/dist/observability/index.js +138 -8
  79. package/dist/observability/sanitize.d.ts +17 -0
  80. package/dist/observability/sanitize.d.ts.map +1 -1
  81. package/dist/primitives.js +1 -1
  82. package/dist/react/query-client.d.ts +33 -0
  83. package/dist/react/query-client.d.ts.map +1 -0
  84. package/dist/react.d.ts +1 -0
  85. package/dist/react.d.ts.map +1 -1
  86. package/dist/react.js +83 -1
  87. package/dist/realtime/registry.d.ts +93 -0
  88. package/dist/realtime/registry.d.ts.map +1 -0
  89. package/dist/release/header.d.ts +9 -0
  90. package/dist/release/header.d.ts.map +1 -0
  91. package/dist/release/marker.d.ts +39 -0
  92. package/dist/release/marker.d.ts.map +1 -0
  93. package/dist/release/socket.d.ts +44 -0
  94. package/dist/release/socket.d.ts.map +1 -0
  95. package/dist/release/watcher.d.ts +66 -0
  96. package/dist/release/watcher.d.ts.map +1 -0
  97. package/dist/release.d.ts +15 -0
  98. package/dist/release.d.ts.map +1 -0
  99. package/dist/release.js +196 -0
  100. package/dist/remote.js +12 -7
  101. package/dist/server/context.d.ts +2 -1
  102. package/dist/server/context.d.ts.map +1 -1
  103. package/dist/server/create.d.ts.map +1 -1
  104. package/dist/server/error-hook.d.ts +17 -16
  105. package/dist/server/error-hook.d.ts.map +1 -1
  106. package/dist/server/file-state-store.d.ts +23 -0
  107. package/dist/server/file-state-store.d.ts.map +1 -0
  108. package/dist/server/implement.d.ts.map +1 -1
  109. package/dist/server/index.d.ts +5 -2
  110. package/dist/server/index.d.ts.map +1 -1
  111. package/dist/server/index.js +469 -11
  112. package/dist/server/middleware/cookies.d.ts +33 -1
  113. package/dist/server/middleware/cookies.d.ts.map +1 -1
  114. package/dist/server/middleware/cors.d.ts +16 -1
  115. package/dist/server/middleware/cors.d.ts.map +1 -1
  116. package/dist/server/openapi.d.ts.map +1 -1
  117. package/dist/server/release-signal.d.ts +35 -0
  118. package/dist/server/release-signal.d.ts.map +1 -0
  119. package/dist/server/router.d.ts.map +1 -1
  120. package/dist/server/types.d.ts +26 -2
  121. package/dist/server/types.d.ts.map +1 -1
  122. package/dist/testing/surface-manifest.d.ts.map +1 -1
  123. package/dist/testing.js +7 -4
  124. package/dist/tool-invoker.js +8 -6
  125. package/dist/tools/download-core.d.ts.map +1 -1
  126. package/dist/tools/remote.d.ts.map +1 -1
  127. package/dist/tools/view-file.d.ts.map +1 -1
  128. package/dist/tools.js +20 -16
  129. package/dist/tracking/attribution.d.ts +51 -0
  130. package/dist/tracking/attribution.d.ts.map +1 -0
  131. package/dist/tracking/beacon.d.ts +14 -0
  132. package/dist/tracking/beacon.d.ts.map +1 -0
  133. package/dist/tracking/clicks.d.ts +46 -0
  134. package/dist/tracking/clicks.d.ts.map +1 -0
  135. package/dist/tracking/client.d.ts +121 -0
  136. package/dist/tracking/client.d.ts.map +1 -0
  137. package/dist/tracking/contract.d.ts +46 -0
  138. package/dist/tracking/contract.d.ts.map +1 -0
  139. package/dist/tracking/delivery.d.ts +24 -0
  140. package/dist/tracking/delivery.d.ts.map +1 -0
  141. package/dist/tracking/host.d.ts +43 -0
  142. package/dist/tracking/host.d.ts.map +1 -0
  143. package/dist/tracking/once-per-page.d.ts +17 -0
  144. package/dist/tracking/once-per-page.d.ts.map +1 -0
  145. package/dist/tracking/outbox-storage-indexeddb.d.ts +12 -0
  146. package/dist/tracking/outbox-storage-indexeddb.d.ts.map +1 -0
  147. package/dist/tracking/outbox-storage-memory.d.ts +12 -0
  148. package/dist/tracking/outbox-storage-memory.d.ts.map +1 -0
  149. package/dist/tracking/outbox.d.ts +98 -0
  150. package/dist/tracking/outbox.d.ts.map +1 -0
  151. package/dist/tracking/schemas.d.ts +183 -0
  152. package/dist/tracking/schemas.d.ts.map +1 -0
  153. package/dist/tracking/scroll.d.ts +22 -0
  154. package/dist/tracking/scroll.d.ts.map +1 -0
  155. package/dist/tracking/sequence-reserve.d.ts +34 -0
  156. package/dist/tracking/sequence-reserve.d.ts.map +1 -0
  157. package/dist/tracking/server/active-interval.d.ts +26 -0
  158. package/dist/tracking/server/active-interval.d.ts.map +1 -0
  159. package/dist/tracking/server/bot.d.ts +9 -0
  160. package/dist/tracking/server/bot.d.ts.map +1 -0
  161. package/dist/tracking/server/disposition.d.ts +55 -0
  162. package/dist/tracking/server/disposition.d.ts.map +1 -0
  163. package/dist/tracking/server/hash.d.ts +13 -0
  164. package/dist/tracking/server/hash.d.ts.map +1 -0
  165. package/dist/tracking/server/presence.d.ts +27 -0
  166. package/dist/tracking/server/presence.d.ts.map +1 -0
  167. package/dist/tracking/server/visit-lease.d.ts +92 -0
  168. package/dist/tracking/server/visit-lease.d.ts.map +1 -0
  169. package/dist/tracking/visible-time.d.ts +38 -0
  170. package/dist/tracking/visible-time.d.ts.map +1 -0
  171. package/dist/tracking-server.d.ts +14 -0
  172. package/dist/tracking-server.d.ts.map +1 -0
  173. package/dist/tracking-server.js +173 -0
  174. package/dist/tracking.d.ts +24 -0
  175. package/dist/tracking.d.ts.map +1 -0
  176. package/dist/tracking.js +1090 -0
  177. package/llms-full.txt +986 -30
  178. package/llms.txt +4 -0
  179. package/package.json +26 -5
@@ -62,8 +62,9 @@ import {
62
62
  createBoundedSinkManager
63
63
  } from "./index-a59da114.js";
64
64
  import {
65
+ backoffDelay,
65
66
  createBackoff
66
- } from "./index-h4pj6fta.js";
67
+ } from "./index-s8cnx02s.js";
67
68
  import {
68
69
  watchKeyString
69
70
  } from "./index-dre2ywck.js";
@@ -77,11 +78,15 @@ import {
77
78
  createBoundedChannel,
78
79
  createCreditWindow
79
80
  } from "./index-7gnkny4z.js";
81
+ import {
82
+ measureSize
83
+ } from "./index-xbppf54a.js";
80
84
  import {
81
85
  argumentsDigest,
82
86
  stableValue
83
87
  } from "./index-a916km3r.js";
84
88
  import"./index-0w9abg87.js";
89
+ import"./index-kzfs85xp.js";
85
90
  import"./index-77fekveh.js";
86
91
  // src/application/admission.ts
87
92
  import { z } from "zod";
@@ -778,6 +783,707 @@ function sqliteKeyspaceBackend(declaration, config) {
778
783
  }
779
784
  };
780
785
  }
786
+ // src/application/notification-outbox.ts
787
+ import { z as z5 } from "zod";
788
+ function notificationOutboxStateSchema(payload) {
789
+ return z5.object({
790
+ schemaVersion: z5.literal(1),
791
+ queue: z5.array(z5.object({
792
+ key: z5.string().min(1).max(512),
793
+ payload,
794
+ createdAt: z5.string().datetime({ offset: true }),
795
+ attempts: z5.number().int().nonnegative(),
796
+ nextAttemptAt: z5.string().datetime({ offset: true }),
797
+ leaseOwner: z5.string().min(1).max(128).nullable(),
798
+ leaseId: z5.string().min(1).max(128).nullable(),
799
+ leaseUntil: z5.string().datetime({ offset: true }).nullable()
800
+ }).strict()),
801
+ receipts: z5.array(z5.object({
802
+ key: z5.string().min(1).max(512),
803
+ completedAt: z5.string().datetime({ offset: true })
804
+ }).strict())
805
+ }).strict();
806
+ }
807
+ var emptyOutboxState = () => ({
808
+ schemaVersion: 1,
809
+ queue: [],
810
+ receipts: []
811
+ });
812
+ var sizeOf = (value) => measureSize(value).responseBytes;
813
+ var DEFAULT_OUTBOX_BACKOFF = { minDelayMs: 1000, maxDelayMs: 60000, jitter: 0 };
814
+ function createNotificationOutbox(config) {
815
+ const schema = notificationOutboxStateSchema(config.payloadSchema);
816
+ const clock = config.clock ?? (() => new Date);
817
+ const ownerId = config.ownerId ?? crypto.randomUUID();
818
+ const pollIntervalMs = z5.number().int().min(10).parse(config.pollIntervalMs ?? 1000);
819
+ const leaseMs = z5.number().int().min(100).parse(config.leaseMs ?? 30000);
820
+ const maxAttempts = z5.number().int().min(1).max(1e4).parse(config.maxAttempts ?? 100);
821
+ const maxQueue = z5.number().int().min(1).max(1e5).parse(config.maxQueue ?? 1000);
822
+ const maxStateBytes = z5.number().int().min(1024).parse(config.maxStateBytes ?? 1024 * 1024);
823
+ const retainReceipts = z5.number().int().min(0).max(1e5).parse(config.retainReceipts ?? 1000);
824
+ const backoffMs = config.backoffMs ?? ((attempt) => backoffDelay(DEFAULT_OUTBOX_BACKOFF, attempt));
825
+ let timer;
826
+ let running = false;
827
+ let interrupted = false;
828
+ let flushTail = Promise.resolve();
829
+ const bounded = (input) => {
830
+ const parsed = schema.parse(input);
831
+ if (parsed.queue.length > maxQueue) {
832
+ throw new Error(`[stitchkit] notification outbox queue limit (${maxQueue}) exceeded`);
833
+ }
834
+ let receipts = parsed.receipts.slice(0, retainReceipts);
835
+ let next = { ...parsed, receipts };
836
+ while (sizeOf(next) > maxStateBytes && receipts.length > 0) {
837
+ receipts = receipts.slice(0, -1);
838
+ next = { ...parsed, receipts };
839
+ }
840
+ if (sizeOf(next) > maxStateBytes) {
841
+ throw new Error(`[stitchkit] notification outbox state limit (${maxStateBytes} bytes) exceeded`);
842
+ }
843
+ return next;
844
+ };
845
+ const parse = (state) => schema.parse(state ?? emptyOutboxState());
846
+ const enqueue = async (input) => {
847
+ const key = z5.string().min(1).max(512).parse(input.key);
848
+ const payload = config.payloadSchema.parse(input.payload);
849
+ const supersedes = new Set(input.supersedes ?? []);
850
+ return config.store.update((current) => {
851
+ const state = parse(current);
852
+ if (state.queue.some((item) => item.key === key) || state.receipts.some((receipt) => receipt.key === key)) {
853
+ return { state, result: false };
854
+ }
855
+ const now = clock().toISOString();
856
+ const queue = [
857
+ ...state.queue.filter((item) => !supersedes.has(item.key)),
858
+ {
859
+ key,
860
+ payload,
861
+ createdAt: now,
862
+ attempts: 0,
863
+ nextAttemptAt: now,
864
+ leaseOwner: null,
865
+ leaseId: null,
866
+ leaseUntil: null
867
+ }
868
+ ];
869
+ const next = bounded({ ...state, queue });
870
+ return { state: next, result: true };
871
+ });
872
+ };
873
+ const claimNext = async () => {
874
+ const now = clock();
875
+ return config.store.update((current) => {
876
+ const state = parse(current);
877
+ const candidate = [...state.queue].sort((left, right) => Date.parse(left.createdAt) - Date.parse(right.createdAt)).find((item) => Date.parse(item.nextAttemptAt) <= now.getTime() && (item.leaseUntil === null || Date.parse(item.leaseUntil) <= now.getTime()));
878
+ if (!candidate)
879
+ return { state, result: null };
880
+ const leaseId = crypto.randomUUID();
881
+ const claimed = {
882
+ ...candidate,
883
+ attempts: candidate.attempts + 1,
884
+ leaseOwner: ownerId,
885
+ leaseId,
886
+ leaseUntil: new Date(now.getTime() + leaseMs).toISOString()
887
+ };
888
+ return {
889
+ state: bounded({
890
+ ...state,
891
+ queue: state.queue.map((item) => item.key === candidate.key ? claimed : item)
892
+ }),
893
+ result: claimed
894
+ };
895
+ });
896
+ };
897
+ const acknowledge = async (claimed) => {
898
+ await config.store.update((current) => {
899
+ const state = parse(current);
900
+ const owns = state.queue.some((item) => item.key === claimed.key && item.leaseId === claimed.leaseId);
901
+ if (!owns)
902
+ return { state, result: undefined };
903
+ const receipts = [
904
+ { key: claimed.key, completedAt: clock().toISOString() },
905
+ ...state.receipts.filter((receipt) => receipt.key !== claimed.key)
906
+ ].slice(0, retainReceipts);
907
+ return {
908
+ state: bounded({
909
+ ...state,
910
+ queue: state.queue.filter((item) => item.key !== claimed.key),
911
+ receipts
912
+ }),
913
+ result: undefined
914
+ };
915
+ });
916
+ };
917
+ const reject = async (claimed, error) => {
918
+ const classification = await config.classify(error);
919
+ return config.store.update((current) => {
920
+ const state = parse(current);
921
+ const live = state.queue.find((item) => item.key === claimed.key && item.leaseId === claimed.leaseId);
922
+ if (!live)
923
+ return { state, result: null };
924
+ const attempts = live.attempts;
925
+ const reason = classification.recipientUnreachable === true ? "recipient-unreachable" : !classification.retryable ? "terminal" : attempts >= maxAttempts ? "attempt-limit" : null;
926
+ if (reason) {
927
+ return {
928
+ state: bounded({
929
+ ...state,
930
+ queue: state.queue.filter((item) => item.key !== live.key)
931
+ }),
932
+ result: { item: { ...live, attempts }, error, reason }
933
+ };
934
+ }
935
+ const rawDelay = backoffMs(attempts);
936
+ const wait = z5.number().finite().nonnegative().max(24 * 60 * 60 * 1000).parse(rawDelay);
937
+ const retry = {
938
+ ...live,
939
+ attempts,
940
+ nextAttemptAt: new Date(clock().getTime() + wait).toISOString(),
941
+ leaseOwner: null,
942
+ leaseId: null,
943
+ leaseUntil: null
944
+ };
945
+ return {
946
+ state: bounded({
947
+ ...state,
948
+ queue: state.queue.map((item) => item.key === live.key ? retry : item)
949
+ }),
950
+ result: null
951
+ };
952
+ });
953
+ };
954
+ const flushPass = async () => {
955
+ let delivered = 0;
956
+ while (!interrupted) {
957
+ const claimed = await claimNext();
958
+ if (!claimed)
959
+ return delivered;
960
+ try {
961
+ await config.send({
962
+ key: claimed.key,
963
+ payload: claimed.payload,
964
+ attempt: claimed.attempts
965
+ });
966
+ await acknowledge(claimed);
967
+ delivered += 1;
968
+ } catch (error) {
969
+ const dropped = await reject(claimed, error);
970
+ if (dropped)
971
+ await config.onDropped?.(dropped);
972
+ }
973
+ }
974
+ return delivered;
975
+ };
976
+ const flush = () => {
977
+ const result = flushTail.then(flushPass, flushPass);
978
+ flushTail = result.then(() => {
979
+ return;
980
+ }, () => {
981
+ return;
982
+ });
983
+ return result;
984
+ };
985
+ const runScheduled = async () => {
986
+ try {
987
+ await flush();
988
+ } catch (error) {
989
+ try {
990
+ await config.onError?.(error);
991
+ } catch {}
992
+ } finally {
993
+ schedule();
994
+ }
995
+ };
996
+ const schedule = () => {
997
+ if (!running)
998
+ return;
999
+ timer = setTimeout(() => {
1000
+ runScheduled();
1001
+ }, pollIntervalMs);
1002
+ };
1003
+ return {
1004
+ enqueue,
1005
+ flush,
1006
+ start() {
1007
+ if (running)
1008
+ return;
1009
+ running = true;
1010
+ interrupted = false;
1011
+ runScheduled();
1012
+ },
1013
+ async stop() {
1014
+ running = false;
1015
+ interrupted = true;
1016
+ if (timer)
1017
+ clearTimeout(timer);
1018
+ timer = undefined;
1019
+ try {
1020
+ await flushTail;
1021
+ } finally {
1022
+ interrupted = false;
1023
+ }
1024
+ },
1025
+ async state() {
1026
+ return parse(await config.store.read());
1027
+ }
1028
+ };
1029
+ }
1030
+ function notificationOutboxResource(outbox, config = {}) {
1031
+ return defineManagedResource({
1032
+ id: config.id ?? "notification-outbox",
1033
+ start() {
1034
+ outbox.start();
1035
+ return { value: outbox };
1036
+ },
1037
+ async close() {
1038
+ await outbox.stop();
1039
+ },
1040
+ async force() {
1041
+ await outbox.stop();
1042
+ }
1043
+ });
1044
+ }
1045
+ // src/application/process-lifecycle.ts
1046
+ import { z as z6 } from "zod";
1047
+ var LifecycleTerminationSchema = z6.enum([
1048
+ "active",
1049
+ "clean",
1050
+ "forced",
1051
+ "hot-reload",
1052
+ "abnormal"
1053
+ ]);
1054
+ var LifecycleRunSchema = z6.object({
1055
+ runId: z6.string().min(1).max(128),
1056
+ pid: z6.number().int().positive(),
1057
+ version: z6.string().min(1).max(256),
1058
+ startedAt: z6.string().datetime({ offset: true }),
1059
+ readyAt: z6.string().datetime({ offset: true }).nullable(),
1060
+ stoppedAt: z6.string().datetime({ offset: true }).nullable(),
1061
+ termination: LifecycleTerminationSchema
1062
+ }).strict();
1063
+ var LifecycleStateSchema = z6.object({
1064
+ schemaVersion: z6.literal(1),
1065
+ runs: z6.array(LifecycleRunSchema)
1066
+ }).strict();
1067
+ var PreviousExitSchema = z6.enum([
1068
+ "first-boot",
1069
+ "hot-reload",
1070
+ "clean",
1071
+ "forced",
1072
+ "handoff",
1073
+ "abnormal"
1074
+ ]);
1075
+ var emptyState = () => ({ schemaVersion: 1, runs: [] });
1076
+ var epoch = (value) => Date.parse(value);
1077
+ var elapsed = (later, earlier) => Math.max(0, epoch(later) - epoch(earlier));
1078
+ function normalize(state, retain) {
1079
+ const parsed = LifecycleStateSchema.parse(state ?? emptyState());
1080
+ const runs = [...parsed.runs];
1081
+ for (let index = runs.length - 1;runs.length > retain && index >= 0; index -= 1) {
1082
+ if (runs[index]?.termination !== "active")
1083
+ runs.splice(index, 1);
1084
+ }
1085
+ return { schemaVersion: 1, runs: runs.slice(0, retain) };
1086
+ }
1087
+ var UNKNOWN_VERSION = "unknown";
1088
+ function isVersionChange(previous, next) {
1089
+ return previous !== UNKNOWN_VERSION && next !== UNKNOWN_VERSION && previous !== next;
1090
+ }
1091
+ function transitionProcessStart(source, input) {
1092
+ const retain = z6.number().int().min(1).max(1000).parse(input.retain ?? 20);
1093
+ const state = normalize(source, 1000);
1094
+ const run = LifecycleRunSchema.parse({
1095
+ runId: input.runId,
1096
+ pid: input.pid,
1097
+ version: input.version,
1098
+ startedAt: input.now,
1099
+ readyAt: null,
1100
+ stoppedAt: null,
1101
+ termination: "active"
1102
+ });
1103
+ if (state.runs.some((candidate) => candidate.runId === run.runId)) {
1104
+ throw new Error(`[stitchkit] lifecycle run id already exists: ${run.runId}`);
1105
+ }
1106
+ const previous = state.runs[0] ?? null;
1107
+ let previousExit = "first-boot";
1108
+ let downtimeMs = null;
1109
+ if (previous?.stoppedAt) {
1110
+ previousExit = previous.termination === "forced" || previous.termination === "abnormal" || previous.termination === "hot-reload" ? previous.termination : "clean";
1111
+ downtimeMs = elapsed(run.startedAt, previous.stoppedAt);
1112
+ } else if (previous) {
1113
+ if (previous.pid === run.pid)
1114
+ previousExit = "hot-reload";
1115
+ else if (isVersionChange(previous.version, run.version))
1116
+ previousExit = "handoff";
1117
+ else
1118
+ previousExit = input.sameVersionOverlap ?? "abnormal";
1119
+ }
1120
+ const runs = state.runs.map((candidate) => {
1121
+ if (candidate.runId !== previous?.runId || candidate.termination !== "active")
1122
+ return candidate;
1123
+ if (previousExit === "hot-reload") {
1124
+ return { ...candidate, stoppedAt: run.startedAt, termination: "hot-reload" };
1125
+ }
1126
+ if (previousExit === "abnormal") {
1127
+ return { ...candidate, stoppedAt: run.startedAt, termination: "abnormal" };
1128
+ }
1129
+ return candidate;
1130
+ });
1131
+ const next = normalize({ schemaVersion: 1, runs: [run, ...runs] }, retain);
1132
+ return {
1133
+ state: next,
1134
+ fact: {
1135
+ type: "started",
1136
+ runId: run.runId,
1137
+ pid: run.pid,
1138
+ version: run.version,
1139
+ startedAt: run.startedAt,
1140
+ previousExit,
1141
+ previousRunId: previous?.runId ?? null,
1142
+ previousPid: previous?.pid ?? null,
1143
+ previousVersion: previous?.version ?? null,
1144
+ versionChanged: previous !== null && isVersionChange(previous.version, run.version),
1145
+ downtimeMs
1146
+ }
1147
+ };
1148
+ }
1149
+ function transitionProcessReady(source, input) {
1150
+ const retain = input.retain ?? 20;
1151
+ const state = normalize(source, retain);
1152
+ const target = state.runs.find((run) => run.runId === input.runId && run.pid === input.pid && run.termination === "active");
1153
+ if (!target) {
1154
+ return {
1155
+ state,
1156
+ fact: {
1157
+ type: "ready",
1158
+ runId: input.runId,
1159
+ pid: input.pid,
1160
+ readyAt: input.now,
1161
+ startupMs: 0,
1162
+ recorded: false
1163
+ }
1164
+ };
1165
+ }
1166
+ const readyAt = target.readyAt ?? input.now;
1167
+ return {
1168
+ state: {
1169
+ schemaVersion: 1,
1170
+ runs: state.runs.map((run) => run.runId === target.runId && run.readyAt === null ? { ...run, readyAt } : run)
1171
+ },
1172
+ fact: {
1173
+ type: "ready",
1174
+ runId: target.runId,
1175
+ pid: target.pid,
1176
+ readyAt,
1177
+ startupMs: elapsed(readyAt, target.startedAt),
1178
+ recorded: target.readyAt === null
1179
+ }
1180
+ };
1181
+ }
1182
+ function transitionProcessShutdown(source, input) {
1183
+ const state = normalize(source, input.retain ?? 20);
1184
+ const target = state.runs.find((run) => run.runId === input.runId && run.pid === input.pid && run.termination === "active");
1185
+ const termination = input.forced ? "forced" : "clean";
1186
+ if (!target) {
1187
+ return {
1188
+ state,
1189
+ fact: {
1190
+ type: "stopped",
1191
+ runId: input.runId,
1192
+ pid: input.pid,
1193
+ stoppedAt: input.now,
1194
+ uptimeMs: null,
1195
+ termination,
1196
+ recorded: false
1197
+ }
1198
+ };
1199
+ }
1200
+ return {
1201
+ state: {
1202
+ schemaVersion: 1,
1203
+ runs: state.runs.map((run) => run.runId === target.runId ? { ...run, stoppedAt: input.now, termination } : run)
1204
+ },
1205
+ fact: {
1206
+ type: "stopped",
1207
+ runId: target.runId,
1208
+ pid: target.pid,
1209
+ stoppedAt: input.now,
1210
+ uptimeMs: elapsed(input.now, target.startedAt),
1211
+ termination,
1212
+ recorded: true
1213
+ }
1214
+ };
1215
+ }
1216
+ function createProcessLifecycleLedger(config) {
1217
+ const clock = config.clock ?? (() => new Date);
1218
+ const pid = z6.number().int().positive().parse(config.pid ?? process.pid);
1219
+ const retain = z6.number().int().min(1).max(1000).parse(config.retain ?? 20);
1220
+ const configuredRunId = config.runId;
1221
+ const nextRunId = typeof configuredRunId === "function" ? configuredRunId : configuredRunId === undefined ? () => crypto.randomUUID() : () => configuredRunId;
1222
+ let runId = nextRunId();
1223
+ let started = false;
1224
+ const listeners = new Set;
1225
+ const publish = (fact) => {
1226
+ for (const listener of listeners) {
1227
+ Promise.resolve().then(() => listener(fact)).catch((error) => config.onSubscriberError?.(error, fact));
1228
+ }
1229
+ };
1230
+ return {
1231
+ async recordStart({ version }) {
1232
+ if (started)
1233
+ runId = nextRunId();
1234
+ const fact = await config.store.update((state) => {
1235
+ const transition = transitionProcessStart(state, {
1236
+ runId,
1237
+ pid,
1238
+ version,
1239
+ now: clock().toISOString(),
1240
+ retain,
1241
+ sameVersionOverlap: config.sameVersionOverlap
1242
+ });
1243
+ return { state: transition.state, result: transition.fact };
1244
+ });
1245
+ started = true;
1246
+ publish(fact);
1247
+ return fact;
1248
+ },
1249
+ async recordReady() {
1250
+ const fact = await config.store.update((state) => {
1251
+ const transition = transitionProcessReady(state, {
1252
+ runId,
1253
+ pid,
1254
+ now: clock().toISOString(),
1255
+ retain
1256
+ });
1257
+ return { state: transition.state, result: transition.fact };
1258
+ });
1259
+ if (fact.recorded)
1260
+ publish(fact);
1261
+ return fact;
1262
+ },
1263
+ async recordShutdown(input) {
1264
+ const fact = await config.store.update((state) => {
1265
+ const transition = transitionProcessShutdown(state, {
1266
+ runId,
1267
+ pid,
1268
+ now: clock().toISOString(),
1269
+ forced: input?.forced,
1270
+ retain
1271
+ });
1272
+ return { state: transition.state, result: transition.fact };
1273
+ });
1274
+ if (fact.recorded)
1275
+ publish(fact);
1276
+ return fact;
1277
+ },
1278
+ async current() {
1279
+ const state = normalize(await config.store.read(), retain);
1280
+ return state.runs.find((run) => run.runId === runId && run.pid === pid) ?? null;
1281
+ },
1282
+ async runs() {
1283
+ return normalize(await config.store.read(), retain).runs;
1284
+ },
1285
+ subscribe(listener) {
1286
+ listeners.add(listener);
1287
+ return () => listeners.delete(listener);
1288
+ }
1289
+ };
1290
+ }
1291
+ function lifecycleLedgerResource(ledger, config) {
1292
+ let stopped = false;
1293
+ let shutdown;
1294
+ const stop = async (forced) => {
1295
+ if (stopped)
1296
+ return;
1297
+ if (!shutdown)
1298
+ shutdown = ledger.recordShutdown({ forced });
1299
+ try {
1300
+ await shutdown;
1301
+ stopped = true;
1302
+ } finally {
1303
+ if (!stopped)
1304
+ shutdown = undefined;
1305
+ }
1306
+ };
1307
+ return defineManagedResource({
1308
+ id: config.id ?? "lifecycle",
1309
+ async start() {
1310
+ stopped = false;
1311
+ shutdown = undefined;
1312
+ await ledger.recordStart({ version: config.version });
1313
+ return { value: ledger };
1314
+ },
1315
+ async activate() {
1316
+ await ledger.recordReady();
1317
+ },
1318
+ async close() {
1319
+ await stop(false);
1320
+ },
1321
+ async force() {
1322
+ await stop(true);
1323
+ }
1324
+ });
1325
+ }
1326
+ // src/application/revision-signal.ts
1327
+ import { z as z7 } from "zod";
1328
+ var NonNegativeSafeIntegerSchema = z7.number().int().nonnegative().safe();
1329
+ var PositiveSafeIntegerSchema2 = z7.number().int().positive().safe();
1330
+ var TimerDelaySchema = z7.number().int().positive().max(2147483647);
1331
+ var RevisionSignalStateSchema = z7.enum(["open", "closed"]);
1332
+ var RevisionSignalWaitOutcomeSchema = z7.enum([
1333
+ "changed",
1334
+ "timed-out",
1335
+ "aborted",
1336
+ "closed",
1337
+ "capacity"
1338
+ ]);
1339
+ var RevisionSignalWaitResultSchema = z7.object({
1340
+ outcome: RevisionSignalWaitOutcomeSchema,
1341
+ revision: NonNegativeSafeIntegerSchema
1342
+ }).strict().readonly();
1343
+ var RevisionSignalAdvanceResultSchema = z7.object({
1344
+ outcome: z7.enum(["advanced", "closed"]),
1345
+ revision: NonNegativeSafeIntegerSchema
1346
+ }).strict().readonly();
1347
+ var RevisionSignalSnapshotSchema = z7.object({
1348
+ state: RevisionSignalStateSchema,
1349
+ revision: NonNegativeSafeIntegerSchema,
1350
+ pending: NonNegativeSafeIntegerSchema,
1351
+ maxWaiters: PositiveSafeIntegerSchema2,
1352
+ advances: NonNegativeSafeIntegerSchema,
1353
+ refusedAdvances: NonNegativeSafeIntegerSchema,
1354
+ waits: NonNegativeSafeIntegerSchema,
1355
+ changed: NonNegativeSafeIntegerSchema,
1356
+ timedOut: NonNegativeSafeIntegerSchema,
1357
+ aborted: NonNegativeSafeIntegerSchema,
1358
+ closedWaits: NonNegativeSafeIntegerSchema,
1359
+ capacityRefusals: NonNegativeSafeIntegerSchema,
1360
+ clockFailures: NonNegativeSafeIntegerSchema
1361
+ }).strict().readonly();
1362
+ var systemClock = {
1363
+ schedule(callback, delayMs) {
1364
+ const timer = setTimeout(callback, delayMs);
1365
+ return { cancel: () => clearTimeout(timer) };
1366
+ }
1367
+ };
1368
+ function createRevisionSignal(config) {
1369
+ const maxWaiters = PositiveSafeIntegerSchema2.parse(config.maxWaiters);
1370
+ const clock = config.clock ?? systemClock;
1371
+ const waiters = new Set;
1372
+ let state = "open";
1373
+ let revision = 0;
1374
+ let advances = 0;
1375
+ let refusedAdvances = 0;
1376
+ let waits = 0;
1377
+ let changed = 0;
1378
+ let timedOut = 0;
1379
+ let aborted = 0;
1380
+ let closedWaits = 0;
1381
+ let capacityRefusals = 0;
1382
+ let clockFailures = 0;
1383
+ const result = (outcome) => {
1384
+ if (outcome === "changed")
1385
+ changed += 1;
1386
+ else if (outcome === "timed-out")
1387
+ timedOut += 1;
1388
+ else if (outcome === "aborted")
1389
+ aborted += 1;
1390
+ else if (outcome === "closed")
1391
+ closedWaits += 1;
1392
+ else
1393
+ capacityRefusals += 1;
1394
+ return RevisionSignalWaitResultSchema.parse({ outcome, revision });
1395
+ };
1396
+ const snapshot = () => RevisionSignalSnapshotSchema.parse({
1397
+ state,
1398
+ revision,
1399
+ pending: waiters.size,
1400
+ maxWaiters,
1401
+ advances,
1402
+ refusedAdvances,
1403
+ waits,
1404
+ changed,
1405
+ timedOut,
1406
+ aborted,
1407
+ closedWaits,
1408
+ capacityRefusals,
1409
+ clockFailures
1410
+ });
1411
+ return {
1412
+ advance() {
1413
+ if (state === "closed") {
1414
+ refusedAdvances += 1;
1415
+ return RevisionSignalAdvanceResultSchema.parse({ outcome: "closed", revision });
1416
+ }
1417
+ if (revision === Number.MAX_SAFE_INTEGER) {
1418
+ throw new RangeError("Revision signal exhausted the safe-integer revision range");
1419
+ }
1420
+ revision += 1;
1421
+ advances += 1;
1422
+ for (const waiter of [...waiters])
1423
+ waiter.settle("changed");
1424
+ return RevisionSignalAdvanceResultSchema.parse({ outcome: "advanced", revision });
1425
+ },
1426
+ wait(after, options = {}) {
1427
+ const parsedAfter = NonNegativeSafeIntegerSchema.parse(after);
1428
+ const timeoutMs = options.timeoutMs === undefined ? undefined : TimerDelaySchema.parse(options.timeoutMs);
1429
+ if (parsedAfter > revision) {
1430
+ throw new RangeError(`Revision signal cannot wait after future revision ${parsedAfter}; current revision is ${revision}`);
1431
+ }
1432
+ waits += 1;
1433
+ if (state === "closed")
1434
+ return Promise.resolve(result("closed"));
1435
+ if (parsedAfter < revision)
1436
+ return Promise.resolve(result("changed"));
1437
+ if (options.signal?.aborted)
1438
+ return Promise.resolve(result("aborted"));
1439
+ if (waiters.size >= maxWaiters)
1440
+ return Promise.resolve(result("capacity"));
1441
+ return new Promise((resolve) => {
1442
+ let settled = false;
1443
+ let timer;
1444
+ const waiter = {
1445
+ settle(outcome) {
1446
+ if (settled)
1447
+ return;
1448
+ settled = true;
1449
+ timer?.cancel();
1450
+ options.signal?.removeEventListener("abort", onAbort);
1451
+ waiters.delete(waiter);
1452
+ resolve(result(outcome));
1453
+ }
1454
+ };
1455
+ function onAbort() {
1456
+ waiter.settle("aborted");
1457
+ }
1458
+ waiters.add(waiter);
1459
+ options.signal?.addEventListener("abort", onAbort, { once: true });
1460
+ if (timeoutMs !== undefined) {
1461
+ try {
1462
+ const scheduled = clock.schedule(() => waiter.settle("timed-out"), timeoutMs);
1463
+ timer = scheduled;
1464
+ if (settled)
1465
+ scheduled.cancel();
1466
+ } catch (error) {
1467
+ settled = true;
1468
+ options.signal?.removeEventListener("abort", onAbort);
1469
+ waiters.delete(waiter);
1470
+ clockFailures += 1;
1471
+ throw error;
1472
+ }
1473
+ }
1474
+ });
1475
+ },
1476
+ close() {
1477
+ if (state === "closed")
1478
+ return snapshot();
1479
+ state = "closed";
1480
+ for (const waiter of [...waiters])
1481
+ waiter.settle("closed");
1482
+ return snapshot();
1483
+ },
1484
+ getSnapshot: snapshot
1485
+ };
1486
+ }
781
1487
  // src/application/server-resource.ts
782
1488
  function managedServerResource(config) {
783
1489
  let shutdownPromise;
@@ -1081,17 +1787,26 @@ function readErrorCode(error) {
1081
1787
  }
1082
1788
  export {
1083
1789
  watchKey,
1790
+ transitionProcessStart,
1791
+ transitionProcessShutdown,
1792
+ transitionProcessReady,
1084
1793
  sqliteKeyspaceBackend,
1085
1794
  readDiagnosticJournalLockDiagnosis,
1086
1795
  projectApplicationStatus,
1087
1796
  openKeyspace,
1797
+ notificationOutboxStateSchema,
1798
+ notificationOutboxResource,
1088
1799
  memoryKeyspaceBackend,
1089
1800
  managedServerResource,
1090
1801
  managedResourceDependencyId,
1802
+ lifecycleLedgerResource,
1091
1803
  keyspaceResource,
1092
1804
  defineManagedResource,
1093
1805
  defineKeyspace,
1094
1806
  createWatchHub,
1807
+ createRevisionSignal,
1808
+ createProcessLifecycleLedger,
1809
+ createNotificationOutbox,
1095
1810
  createManagedSchedule,
1096
1811
  createDecisionPipeline,
1097
1812
  createCreditWindow,
@@ -1104,6 +1819,12 @@ export {
1104
1819
  createApplication,
1105
1820
  createActivityProjection,
1106
1821
  applicationLifecycleEvent,
1822
+ RevisionSignalWaitResultSchema,
1823
+ RevisionSignalWaitOutcomeSchema,
1824
+ RevisionSignalStateSchema,
1825
+ RevisionSignalSnapshotSchema,
1826
+ RevisionSignalAdvanceResultSchema,
1827
+ PreviousExitSchema,
1107
1828
  PolicyDecisionSchema,
1108
1829
  ManagedScheduleStatusSchema,
1109
1830
  ManagedScheduleOverlapSchema,
@@ -1111,6 +1832,9 @@ export {
1111
1832
  ManagedScheduleDescriptorSchema,
1112
1833
  ManagedResourceStateSchema,
1113
1834
  ManagedResourceSnapshotSchema,
1835
+ LifecycleTerminationSchema,
1836
+ LifecycleStateSchema,
1837
+ LifecycleRunSchema,
1114
1838
  DiagnosticJournalWaitResultSchema,
1115
1839
  DiagnosticJournalSubmitResultSchema,
1116
1840
  DiagnosticJournalStatusSchema,