@lostgradient/weft 0.23.1 → 0.24.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 (294) hide show
  1. package/README.md +72 -4
  2. package/dist/cli/codegen-emit-dedup.d.ts +81 -0
  3. package/dist/cli/codegen-emit-dedup.js +45 -0
  4. package/dist/cli/codegen-emit-registry.d.ts +90 -0
  5. package/dist/cli/codegen-emit-registry.js +50 -0
  6. package/dist/cli/codegen-emit.d.ts +26 -39
  7. package/dist/cli/codegen-emit.js +4 -27
  8. package/dist/cli/codegen-validate.d.ts +49 -0
  9. package/dist/cli/codegen-validate.js +186 -0
  10. package/dist/cli/codegen.js +6 -87
  11. package/dist/cli/generated/operation-catalog.snapshot.json +287 -3
  12. package/dist/cli/generated/operation-client.generated.d.ts +324 -10
  13. package/dist/cli/generated/operation-client.generated.js +12 -0
  14. package/dist/cli/output.js +1 -1
  15. package/dist/cli/storage-factory.js +2 -0
  16. package/dist/cli/workflow-commands.js +2 -0
  17. package/dist/cli-main.js +1 -1
  18. package/dist/core/application-payload-digest.d.ts +42 -0
  19. package/dist/core/application-payload-digest.js +76 -0
  20. package/dist/core/application-primitive-abort.d.ts +50 -0
  21. package/dist/core/application-primitive-abort.js +40 -0
  22. package/dist/core/application-primitive-attempt-registry.d.ts +74 -0
  23. package/dist/core/application-primitive-attempt-registry.js +73 -0
  24. package/dist/core/application-primitive-codec.d.ts +36 -0
  25. package/dist/core/application-primitive-codec.js +56 -0
  26. package/dist/core/application-primitive-commit.d.ts +67 -0
  27. package/dist/core/application-primitive-commit.js +66 -0
  28. package/dist/core/application-primitive-guards.d.ts +62 -0
  29. package/dist/core/application-primitive-guards.js +89 -0
  30. package/dist/core/application-primitive-payload.d.ts +29 -0
  31. package/dist/core/application-primitive-payload.js +65 -0
  32. package/dist/core/application-primitive-timing.d.ts +18 -0
  33. package/dist/core/application-primitive-timing.js +22 -0
  34. package/dist/core/atomic-state.js +2 -0
  35. package/dist/core/catalog/codec.d.ts +40 -0
  36. package/dist/core/catalog/codec.js +45 -0
  37. package/dist/core/catalog/errors.d.ts +104 -0
  38. package/dist/core/catalog/errors.js +41 -0
  39. package/dist/core/catalog/index.d.ts +25 -0
  40. package/dist/core/catalog/index.js +15 -0
  41. package/dist/core/catalog/reference-counts.d.ts +105 -0
  42. package/dist/core/catalog/reference-counts.js +28 -0
  43. package/dist/core/catalog/removal.d.ts +52 -0
  44. package/dist/core/catalog/removal.js +14 -0
  45. package/dist/core/catalog/storage-io.d.ts +78 -0
  46. package/dist/core/catalog/storage-io.js +106 -0
  47. package/dist/core/catalog/types.d.ts +108 -0
  48. package/dist/core/catalog/types.js +0 -0
  49. package/dist/core/catalog/workflow-catalog.d.ts +165 -0
  50. package/dist/core/catalog/workflow-catalog.js +198 -0
  51. package/dist/core/compare-codepoint.d.ts +13 -0
  52. package/dist/core/compare-codepoint.js +7 -0
  53. package/dist/core/compression.js +2 -0
  54. package/dist/core/context/attributes.d.ts +1 -1
  55. package/dist/core/context/durable-operations.d.ts +2 -2
  56. package/dist/core/context/index.js +5 -3
  57. package/dist/core/context/state-namespace.d.ts +1 -1
  58. package/dist/core/contract/build.d.ts +64 -0
  59. package/dist/core/contract/build.js +99 -0
  60. package/dist/core/contract/compatibility.d.ts +187 -0
  61. package/dist/core/contract/compatibility.js +37 -0
  62. package/dist/core/contract/failure.d.ts +64 -0
  63. package/dist/core/contract/failure.js +4 -0
  64. package/dist/core/contract/hash.d.ts +92 -0
  65. package/dist/core/contract/hash.js +34 -0
  66. package/dist/core/contract/index.d.ts +21 -0
  67. package/dist/core/contract/index.js +22 -0
  68. package/dist/core/contract/limits.d.ts +74 -0
  69. package/dist/core/contract/limits.js +1 -0
  70. package/dist/core/contract/manifest-parse-schema.d.ts +37 -0
  71. package/dist/core/contract/manifest-parse-schema.js +106 -0
  72. package/dist/core/contract/manifest-parse.d.ts +76 -0
  73. package/dist/core/contract/manifest-parse.js +189 -0
  74. package/dist/core/contract/manifest.d.ts +56 -0
  75. package/dist/core/contract/manifest.js +31 -0
  76. package/dist/core/contract/normalize.d.ts +97 -0
  77. package/dist/core/contract/normalize.js +127 -0
  78. package/dist/core/contract/revision.d.ts +29 -0
  79. package/dist/core/contract/revision.js +5 -0
  80. package/dist/core/contract/types.d.ts +274 -0
  81. package/dist/core/contract/types.js +1 -0
  82. package/dist/core/engine/catalog-activation.d.ts +40 -0
  83. package/dist/core/engine/catalog-activation.js +17 -0
  84. package/dist/core/engine/catalog-events.d.ts +36 -0
  85. package/dist/core/engine/catalog-events.js +15 -0
  86. package/dist/core/engine/catalog-readiness.d.ts +49 -0
  87. package/dist/core/engine/catalog-readiness.js +59 -0
  88. package/dist/core/engine/catalog-removal.d.ts +132 -0
  89. package/dist/core/engine/catalog-removal.js +70 -0
  90. package/dist/core/engine/construction.d.ts +1 -1
  91. package/dist/core/engine/construction.js +4 -2
  92. package/dist/core/engine/disposal.js +9 -0
  93. package/dist/core/engine/engine-workflows-namespace.d.ts +130 -0
  94. package/dist/core/engine/engine-workflows-namespace.js +49 -0
  95. package/dist/core/engine/finalizer-metadata.d.ts +22 -0
  96. package/dist/core/engine/finalizer-metadata.js +12 -0
  97. package/dist/core/engine/handle-result.js +2 -0
  98. package/dist/core/engine/index.d.ts +83 -0
  99. package/dist/core/engine/index.js +61 -0
  100. package/dist/core/engine/internals.d.ts +44 -1
  101. package/dist/core/engine/internals.js +6 -0
  102. package/dist/core/engine/lifecycle/start.js +3 -0
  103. package/dist/core/engine/registration.d.ts +76 -0
  104. package/dist/core/engine/registration.js +12 -6
  105. package/dist/core/engine/source-registration.d.ts +42 -0
  106. package/dist/core/engine/source-registration.js +36 -0
  107. package/dist/core/engine/source-resolution.d.ts +85 -0
  108. package/dist/core/engine/source-resolution.js +119 -0
  109. package/dist/core/engine/validation.js +2 -2
  110. package/dist/core/events/catalog-events.d.ts +117 -0
  111. package/dist/core/events/catalog-events.js +62 -0
  112. package/dist/core/events/event-map.d.ts +6 -0
  113. package/dist/core/events/index.d.ts +1 -0
  114. package/dist/core/events/index.js +1 -0
  115. package/dist/core/mailbox-admission.d.ts +26 -0
  116. package/dist/core/mailbox-admission.js +130 -0
  117. package/dist/core/mailbox-claims.d.ts +251 -0
  118. package/dist/core/mailbox-claims.js +0 -0
  119. package/dist/core/mailbox-codec.d.ts +32 -0
  120. package/dist/core/mailbox-codec.js +221 -0
  121. package/dist/core/mailbox-contract.d.ts +290 -0
  122. package/dist/core/mailbox-contract.js +0 -0
  123. package/dist/core/mailbox-delivery.d.ts +37 -0
  124. package/dist/core/mailbox-delivery.js +193 -0
  125. package/dist/core/mailbox-guards.d.ts +78 -0
  126. package/dist/core/mailbox-guards.js +58 -0
  127. package/dist/core/mailbox-index-codec.d.ts +41 -0
  128. package/dist/core/mailbox-index-codec.js +71 -0
  129. package/dist/core/mailbox-internals.d.ts +156 -0
  130. package/dist/core/mailbox-internals.js +154 -0
  131. package/dist/core/mailbox-maintenance.d.ts +29 -0
  132. package/dist/core/mailbox-maintenance.js +196 -0
  133. package/dist/core/mailbox-settlement.d.ts +69 -0
  134. package/dist/core/mailbox-settlement.js +206 -0
  135. package/dist/core/mailbox-storage.d.ts +117 -0
  136. package/dist/core/mailbox-storage.js +115 -0
  137. package/dist/core/mailbox-transition-helpers.d.ts +70 -0
  138. package/dist/core/mailbox-transition-helpers.js +43 -0
  139. package/dist/core/mailbox-transitions-recovery.d.ts +28 -0
  140. package/dist/core/mailbox-transitions-recovery.js +64 -0
  141. package/dist/core/mailbox-transitions.d.ts +138 -0
  142. package/dist/core/mailbox-transitions.js +180 -0
  143. package/dist/core/mailbox-types.d.ts +406 -0
  144. package/dist/core/mailbox-types.js +16 -0
  145. package/dist/core/mailbox-validation.d.ts +65 -0
  146. package/dist/core/mailbox-validation.js +73 -0
  147. package/dist/core/mailbox-waits.d.ts +50 -0
  148. package/dist/core/mailbox-waits.js +97 -0
  149. package/dist/core/mailbox.d.ts +163 -0
  150. package/dist/core/mailbox.js +211 -0
  151. package/dist/core/outbox-claims.d.ts +307 -0
  152. package/dist/core/outbox-claims.js +0 -0
  153. package/dist/core/outbox-codec.d.ts +24 -0
  154. package/dist/core/outbox-codec.js +257 -0
  155. package/dist/core/outbox-contract.d.ts +346 -0
  156. package/dist/core/outbox-contract.js +0 -0
  157. package/dist/core/outbox-delivery.d.ts +34 -0
  158. package/dist/core/outbox-delivery.js +149 -0
  159. package/dist/core/outbox-drain.d.ts +29 -0
  160. package/dist/core/outbox-drain.js +158 -0
  161. package/dist/core/outbox-enqueue.d.ts +24 -0
  162. package/dist/core/outbox-enqueue.js +115 -0
  163. package/dist/core/outbox-guards.d.ts +76 -0
  164. package/dist/core/outbox-guards.js +60 -0
  165. package/dist/core/outbox-index-codec.d.ts +33 -0
  166. package/dist/core/outbox-index-codec.js +71 -0
  167. package/dist/core/outbox-internals.d.ts +99 -0
  168. package/dist/core/outbox-internals.js +159 -0
  169. package/dist/core/outbox-listing.d.ts +12 -0
  170. package/dist/core/outbox-listing.js +38 -0
  171. package/dist/core/outbox-maintenance.d.ts +28 -0
  172. package/dist/core/outbox-maintenance.js +191 -0
  173. package/dist/core/outbox-operations.d.ts +18 -0
  174. package/dist/core/outbox-operations.js +46 -0
  175. package/dist/core/outbox-runner.d.ts +30 -0
  176. package/dist/core/outbox-runner.js +217 -0
  177. package/dist/core/outbox-settlement.d.ts +57 -0
  178. package/dist/core/outbox-settlement.js +183 -0
  179. package/dist/core/outbox-storage.d.ts +107 -0
  180. package/dist/core/outbox-storage.js +127 -0
  181. package/dist/core/outbox-transition-helpers.d.ts +83 -0
  182. package/dist/core/outbox-transition-helpers.js +63 -0
  183. package/dist/core/outbox-transitions-recovery.d.ts +30 -0
  184. package/dist/core/outbox-transitions-recovery.js +42 -0
  185. package/dist/core/outbox-transitions.d.ts +145 -0
  186. package/dist/core/outbox-transitions.js +236 -0
  187. package/dist/core/outbox-types.d.ts +400 -0
  188. package/dist/core/outbox-types.js +20 -0
  189. package/dist/core/outbox-validation.d.ts +101 -0
  190. package/dist/core/outbox-validation.js +127 -0
  191. package/dist/core/outbox-waits.d.ts +39 -0
  192. package/dist/core/outbox-waits.js +88 -0
  193. package/dist/core/outbox.d.ts +156 -0
  194. package/dist/core/outbox.js +234 -0
  195. package/dist/core/registry-limits.d.ts +49 -0
  196. package/dist/core/registry-limits.js +10 -0
  197. package/dist/core/registry-schema-conversion.d.ts +20 -0
  198. package/dist/core/registry-schema-conversion.js +22 -0
  199. package/dist/core/registry-snapshot.d.ts +91 -42
  200. package/dist/core/registry-snapshot.js +39 -77
  201. package/dist/core/registry-workflow-contract-draft.d.ts +32 -0
  202. package/dist/core/registry-workflow-contract-draft.js +28 -0
  203. package/dist/core/registry-workflow-manifest.d.ts +122 -0
  204. package/dist/core/registry-workflow-manifest.js +92 -0
  205. package/dist/core/source/errors.d.ts +55 -0
  206. package/dist/core/source/errors.js +13 -0
  207. package/dist/core/source/index.d.ts +19 -0
  208. package/dist/core/source/index.js +4 -0
  209. package/dist/core/source/resolvers.d.ts +45 -0
  210. package/dist/core/source/resolvers.js +9 -0
  211. package/dist/core/source/types.d.ts +211 -0
  212. package/dist/core/source/types.js +0 -0
  213. package/dist/core/source/validate.d.ts +91 -0
  214. package/dist/core/source/validate.js +62 -0
  215. package/dist/core/source/workflow-source.d.ts +43 -0
  216. package/dist/core/source/workflow-source.js +16 -0
  217. package/dist/core/types/workflow-registries.d.ts +7 -0
  218. package/dist/core/types/workflow-registry.d.ts +12 -0
  219. package/dist/core/versioning.js +2 -0
  220. package/dist/core/weft-error.d.ts +1 -1
  221. package/dist/core/weft-error.js +9 -1
  222. package/dist/http.js +2 -2
  223. package/dist/index.d.ts +23 -2
  224. package/dist/index.js +50 -0
  225. package/dist/indexeddb.js +1 -1
  226. package/dist/json-schema.js +3 -3
  227. package/dist/mcp/resources.js +2 -0
  228. package/dist/runtime/portable.d.ts +9 -1
  229. package/dist/server/fault-to-json-rpc.js +3 -1
  230. package/dist/server/handler/auth-context-principal.js +4 -0
  231. package/dist/server/json-rpc-http.js +2 -0
  232. package/dist/server/json-rpc-websocket.js +6 -4
  233. package/dist/server/operation-catalog/index.d.ts +1 -1
  234. package/dist/server/operation-catalog/pipeline-helpers.js +2 -0
  235. package/dist/server/operation-catalog/types.d.ts +17 -0
  236. package/dist/server/operation-fault.d.ts +3 -0
  237. package/dist/server/operation-fault.js +3 -1
  238. package/dist/server/operations/activate-workflow-revision.d.ts +30 -0
  239. package/dist/server/operations/activate-workflow-revision.js +69 -0
  240. package/dist/server/operations/fleet-events-subscription.js +1 -1
  241. package/dist/server/operations/fork-workflow.js +1 -1
  242. package/dist/server/operations/get-active-workflow-revision.d.ts +32 -0
  243. package/dist/server/operations/get-active-workflow-revision.js +46 -0
  244. package/dist/server/operations/get-catalog-diagnostics.d.ts +85 -0
  245. package/dist/server/operations/get-catalog-diagnostics.js +57 -0
  246. package/dist/server/operations/get-registry.js +17 -4
  247. package/dist/server/operations/get-workflow-revision.d.ts +25 -0
  248. package/dist/server/operations/get-workflow-revision.js +50 -0
  249. package/dist/server/operations/install-workflow-revision.d.ts +27 -0
  250. package/dist/server/operations/install-workflow-revision.js +46 -0
  251. package/dist/server/operations/list-workflow-revisions.d.ts +20 -0
  252. package/dist/server/operations/list-workflow-revisions.js +40 -0
  253. package/dist/server/operations/start-or-signal-workflow.js +1 -1
  254. package/dist/server/operations/start-workflow.js +1 -1
  255. package/dist/server/operations/static-registrations.js +36 -0
  256. package/dist/server/operations/storage.js +2 -2
  257. package/dist/server/operations/submit-review-decision.js +1 -1
  258. package/dist/server/operations/workflow-catalog-operation-helpers.d.ts +59 -0
  259. package/dist/server/operations/workflow-catalog-operation-helpers.js +135 -0
  260. package/dist/server/runtime/websocket-upgrade.js +2 -0
  261. package/dist/storage/bun-sql.js +49 -6
  262. package/dist/storage/catalog-keys.d.ts +45 -0
  263. package/dist/storage/catalog-keys.js +6 -0
  264. package/dist/storage/compressed-storage.js +1 -1
  265. package/dist/storage/derived-operations.js +1 -1
  266. package/dist/storage/index.d.ts +26 -1
  267. package/dist/storage/interface.d.ts +26 -1
  268. package/dist/storage/interface.js +1 -1
  269. package/dist/storage/key-encoding.d.ts +15 -0
  270. package/dist/storage/key-encoding.js +3 -0
  271. package/dist/storage/key-prefixes.d.ts +1 -1
  272. package/dist/storage/key-prefixes.js +15 -0
  273. package/dist/storage/lmdb.d.ts +26 -1
  274. package/dist/storage/lmdb.js +1 -1
  275. package/dist/storage/mailbox-keys.d.ts +80 -0
  276. package/dist/storage/mailbox-keys.js +14 -0
  277. package/dist/storage/memory.js +1 -1
  278. package/dist/storage/neon.js +2 -2
  279. package/dist/storage/node-sqlite-loader.d.ts +1 -1
  280. package/dist/storage/node-sqlite.js +49 -6
  281. package/dist/storage/outbox-keys.d.ts +77 -0
  282. package/dist/storage/outbox-keys.js +14 -0
  283. package/dist/storage/postgres.js +2 -2
  284. package/dist/storage/resolve.js +1 -1
  285. package/dist/storage/scoped-storage.js +1 -1
  286. package/dist/storage/storage-configuration.d.ts +6 -0
  287. package/dist/storage/testing.js +1 -1
  288. package/dist/storage/turso.js +2 -2
  289. package/dist/version.d.ts +1 -1
  290. package/dist/version.js +1 -1
  291. package/dist/web-extension.js +1 -1
  292. package/dist/worker/manifest/registry-contract-builder.d.ts +21 -3
  293. package/dist/worker/manifest/registry-contract-builder.js +40 -31
  294. package/package.json +1 -1
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Racing an in-flight storage observation against abort signals (WFT-84, WFT-85).
3
+ *
4
+ * Storage has no cancellation contract, so a stalled remote read cannot be
5
+ * cut short. What can be cut short is the caller's wait: the moment any of the
6
+ * signals fires, or the wait's own budget runs out, the observation is
7
+ * reported as aborted and the read is left to settle on its own. Kept to the
8
+ * fewest promise hops, because the deterministic fake-timer tests drain a
9
+ * fixed number of microtask turns between ticks.
10
+ *
11
+ * @module core/application-primitive-abort
12
+ */
13
+ import { WeftError } from './weft-error.ts';
14
+ /** The outcome of racing an in-flight observation against abort signals. */
15
+ export type Raced<T> = {
16
+ readonly aborted: false;
17
+ readonly value: T;
18
+ } | {
19
+ readonly aborted: true;
20
+ readonly reason: unknown;
21
+ };
22
+ /**
23
+ * Thrown by `Mailbox.awaitCleanup()` when a positive budget runs out
24
+ * while the FIRST cleanup-state read is still in flight — there is no
25
+ * observation yet to report as `pending`. Nothing durable changed; the wait is
26
+ * simply over.
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * import { WaitBudgetElapsedError } from '@lostgradient/weft';
31
+ *
32
+ * const error = new WaitBudgetElapsedError();
33
+ * console.log(error.code); // 'WaitBudgetElapsedError'
34
+ * ```
35
+ */
36
+ export declare class WaitBudgetElapsedError extends WeftError<'WaitBudgetElapsedError'> {
37
+ constructor();
38
+ }
39
+ /**
40
+ * Run an observation unless any of the signals is, or becomes, aborted while
41
+ * it is in flight. `undefined` signals are ignored.
42
+ */
43
+ export declare function raceAbort<T>(run: () => Promise<T>, ...signals: readonly (AbortSignal | undefined)[]): Promise<Raced<T>>;
44
+ /**
45
+ * `raceAbort` with a budget: when `budgetMs` is a positive number, a timer of
46
+ * that length also ends the race, with `WaitBudgetElapsedError` as the reason.
47
+ * A bounded wait is bounded by this even while a remote read is stalled; the
48
+ * sleeps between observations are not the only place a budget can run out.
49
+ */
50
+ export declare function raceAbortWithin<T>(run: () => Promise<T>, budgetMs: number | null, ...signals: readonly (AbortSignal | undefined)[]): Promise<Raced<T>>;
@@ -0,0 +1,40 @@
1
+ import { WeftError } from "./weft-error.js";
2
+
3
+ export class WaitBudgetElapsedError extends WeftError {
4
+ constructor() {
5
+ super("WaitBudgetElapsedError", "The wait budget elapsed while a storage observation was still in flight.");
6
+ }
7
+ }
8
+ export function raceAbort(run, ...signals) {
9
+ return raceAbortWithin(run, null, ...signals);
10
+ }
11
+ export function raceAbortWithin(run, budgetMs, ...signals) {
12
+ const live = signals.filter((signal) => signal !== void 0), fired = live.find((signal) => signal.aborted);
13
+ if (fired !== void 0)
14
+ return Promise.resolve({ aborted: !0, reason: fired.reason });
15
+ return new Promise((resolve, reject) => {
16
+ const cleanup = new AbortController, timer = budgetMs === null || budgetMs <= 0 ? null : setTimeout(() => {
17
+ cleanup.abort();
18
+ resolve({ aborted: !0, reason: new WaitBudgetElapsedError });
19
+ }, budgetMs), settle = () => {
20
+ cleanup.abort();
21
+ if (timer !== null)
22
+ clearTimeout(timer);
23
+ };
24
+ for (const signal of live)
25
+ signal.addEventListener("abort", () => {
26
+ settle();
27
+ resolve({ aborted: !0, reason: signal.reason });
28
+ }, { once: !0, signal: cleanup.signal });
29
+ (async () => {
30
+ try {
31
+ const value = await run();
32
+ settle();
33
+ resolve({ aborted: !1, value });
34
+ } catch (error) {
35
+ settle();
36
+ reject(error);
37
+ }
38
+ })();
39
+ });
40
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * The process-local registry of live claim attempts for one scope of an
3
+ * application primitive (WFT-84, WFT-85), and the lease-commit serial that
4
+ * fences reconciliation against it.
5
+ *
6
+ * Registrations live here rather than in durable storage because an
7
+ * `AbortSignal` is process-local by construction; the registry is what lets a
8
+ * cancellation, settlement, or maintenance pass in this process reach a
9
+ * claimant in this process. Another process learns from renewal instead.
10
+ *
11
+ * @module core/application-primitive-attempt-registry
12
+ */
13
+ import type { Storage } from '../storage/interface.ts';
14
+ /**
15
+ * One live attempt in this process: its abort controller plus the callback that
16
+ * forgets it from the handle that claimed it.
17
+ *
18
+ * Carrying the release alongside the controller is what lets a *sibling* handle
19
+ * — one running maintenance, or settling with a token it was handed — release
20
+ * ownership from the handle that actually owns it. Without that, the claiming
21
+ * handle's ownership set leaks one entry per attempt settled elsewhere.
22
+ */
23
+ export type AttemptRegistration = {
24
+ readonly controller: AbortController;
25
+ readonly release: () => void;
26
+ /**
27
+ * The subject (command, delivery) the attempt belongs to, so a caller-supplied
28
+ * token cannot release another subject's attempt.
29
+ */
30
+ readonly subjectId: string;
31
+ /**
32
+ * The process-local lease-commit serial this attempt's lease landed under, or
33
+ * `null` while its compare-and-swap is still in flight. A reconciliation from
34
+ * a durable snapshot releases only attempts that committed BEFORE the
35
+ * snapshot was read: a snapshot cannot speak for a lease that landed after it.
36
+ */
37
+ committedSerial: number | null;
38
+ };
39
+ /**
40
+ * The process-local attempt registry for one scope: a map from attempt token
41
+ * to registration, with a secondary index from subject id to the tokens
42
+ * registered for it, so reconciling one subject costs its own attempts rather
43
+ * than a walk over every live claim in the scope.
44
+ */
45
+ export declare class AttemptRegistry extends Map<string, AttemptRegistration> {
46
+ #private;
47
+ constructor();
48
+ set(attemptToken: string, registration: AttemptRegistration): this;
49
+ delete(attemptToken: string): boolean;
50
+ clear(): void;
51
+ /** The tokens currently registered for one subject, as a snapshot safe to release from. */
52
+ tokensFor(subjectId: string): string[];
53
+ }
54
+ /** Record one more lease committed by this process and return its serial. */
55
+ export declare function nextLeaseCommitSerial(): number;
56
+ /** The serial of the latest lease this process committed; read before a snapshot to fence reconciliation. */
57
+ export declare function leaseCommitSerial(): number;
58
+ /**
59
+ * Acquire the shared attempt-controller registry for one scope in this process.
60
+ * Every acquisition is balanced by {@link releaseAttemptControllerRegistry}
61
+ * from the handle's `dispose()`.
62
+ */
63
+ export declare function attemptControllerRegistry(storage: Storage, primitive: string, namespace: string, scopeId: string): AttemptRegistry;
64
+ /**
65
+ * Release one handle's hold on a scope registry.
66
+ *
67
+ * The scope is forgotten once no handle holds it and no attempt is live in it.
68
+ * A service that creates short-lived handles for many resource ids over one
69
+ * long-lived storage would otherwise retain a map per historical resource. A
70
+ * live attempt owned by a sibling handle keeps the scope until it settles.
71
+ */
72
+ export declare function releaseAttemptControllerRegistry(storage: Storage, primitive: string, namespace: string, scopeId: string): void;
73
+ /** Whether this process still tracks a registry for the scope. Diagnostics and tests. */
74
+ export declare function hasAttemptControllerScope(storage: Storage, primitive: string, namespace: string, scopeId: string): boolean;
@@ -0,0 +1,73 @@
1
+ export class AttemptRegistry extends Map {
2
+ #bySubject;
3
+ constructor() {
4
+ super();
5
+ this.#bySubject = new Map;
6
+ }
7
+ set(attemptToken, registration) {
8
+ super.set(attemptToken, registration);
9
+ let tokens = this.#bySubject.get(registration.subjectId);
10
+ if (tokens === void 0) {
11
+ tokens = new Set;
12
+ this.#bySubject.set(registration.subjectId, tokens);
13
+ }
14
+ tokens.add(attemptToken);
15
+ return this;
16
+ }
17
+ delete(attemptToken) {
18
+ const registration = this.get(attemptToken);
19
+ if (registration === void 0)
20
+ return !1;
21
+ super.delete(attemptToken);
22
+ const tokens = this.#bySubject.get(registration.subjectId);
23
+ tokens?.delete(attemptToken);
24
+ if (tokens?.size === 0)
25
+ this.#bySubject.delete(registration.subjectId);
26
+ return !0;
27
+ }
28
+ clear() {
29
+ super.clear();
30
+ this.#bySubject.clear();
31
+ }
32
+ tokensFor(subjectId) {
33
+ return [...this.#bySubject.get(subjectId) ?? []];
34
+ }
35
+ }
36
+ let localLeaseCommits = 0;
37
+ export function nextLeaseCommitSerial() {
38
+ localLeaseCommits += 1;
39
+ return localLeaseCommits;
40
+ }
41
+ export function leaseCommitSerial() {
42
+ return localLeaseCommits;
43
+ }
44
+ const ATTEMPT_CONTROLLERS_BY_STORAGE = new WeakMap;
45
+ function scopeKey(primitive, namespace, scopeId) {
46
+ return `${primitive}:${encodeURIComponent(namespace)}:${encodeURIComponent(scopeId)}`;
47
+ }
48
+ export function attemptControllerRegistry(storage, primitive, namespace, scopeId) {
49
+ let byScope = ATTEMPT_CONTROLLERS_BY_STORAGE.get(storage);
50
+ if (byScope === void 0) {
51
+ byScope = new Map;
52
+ ATTEMPT_CONTROLLERS_BY_STORAGE.set(storage, byScope);
53
+ }
54
+ const scope = scopeKey(primitive, namespace, scopeId);
55
+ let entry = byScope.get(scope);
56
+ if (entry === void 0) {
57
+ entry = { controllers: new AttemptRegistry, handles: 0 };
58
+ byScope.set(scope, entry);
59
+ }
60
+ entry.handles += 1;
61
+ return entry.controllers;
62
+ }
63
+ export function releaseAttemptControllerRegistry(storage, primitive, namespace, scopeId) {
64
+ const byScope = ATTEMPT_CONTROLLERS_BY_STORAGE.get(storage), scope = scopeKey(primitive, namespace, scopeId), entry = byScope?.get(scope);
65
+ if (byScope === void 0 || entry === void 0)
66
+ return;
67
+ entry.handles = Math.max(0, entry.handles - 1);
68
+ if (entry.handles === 0 && entry.controllers.size === 0)
69
+ byScope.delete(scope);
70
+ }
71
+ export function hasAttemptControllerScope(storage, primitive, namespace, scopeId) {
72
+ return ATTEMPT_CONTROLLERS_BY_STORAGE.get(storage)?.has(scopeKey(primitive, namespace, scopeId)) === !0;
73
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Fail-closed readers shared by the application primitives' codecs (WFT-84, WFT-85).
3
+ *
4
+ * Every reader raises `PersistedDataCorruptError` for the key being decoded
5
+ * rather than coercing a damaged value into something plausible. The record
6
+ * codec and the index codec are built from these.
7
+ *
8
+ * @module core/application-primitive-codec
9
+ */
10
+ export declare function isRecordObject(value: unknown): value is Record<string, unknown>;
11
+ export declare function fail(key: string): never;
12
+ export declare function readString(source: Record<string, unknown>, field: string, key: string): string;
13
+ export declare function readOptionalString(source: Record<string, unknown>, field: string, key: string): string | undefined;
14
+ export declare function readInteger(source: Record<string, unknown>, field: string, key: string): number;
15
+ /**
16
+ * Admission requires these to be positive, so a persisted zero is corruption:
17
+ * a zero visibility timeout would hand out a lease that is reclaimable the
18
+ * instant it is granted, and zero attempts would deliver work that is already
19
+ * exhausted.
20
+ */
21
+ export declare function readPositiveInteger(source: Record<string, unknown>, field: string, key: string): number;
22
+ export declare function readOptionalInteger(source: Record<string, unknown>, field: string, key: string): number | undefined;
23
+ export declare function readVersion(source: Record<string, unknown>, key: string, expected?: number): void;
24
+ /**
25
+ * Rebuild the key a record's own identity names. Key construction percent-encodes
26
+ * each component and throws a raw `URIError` on an unpaired surrogate; a
27
+ * persisted identity that malformed is corruption and must surface as such.
28
+ */
29
+ export declare function ownKey(build: () => string, key: string): string;
30
+ /**
31
+ * A persisted command id is handed straight to key construction by `claim()`,
32
+ * `list()`, the waits, and idempotency lookups; an unpaired surrogate or an
33
+ * oversized value there would escape as a raw `URIError` rather than the
34
+ * corruption it is.
35
+ */
36
+ export declare function readIdentifier(value: unknown, key: string): string;
@@ -0,0 +1,56 @@
1
+ import { MAX_APPLICATION_IDENTITY_BYTES, isWellFormedString } from "./mailbox-guards.js";
2
+ import { MAILBOX_RECORD_VERSION } from "./mailbox-types.js";
3
+ import { PersistedDataCorruptError } from "./persisted-data-incompatible-error.js";
4
+ export function isRecordObject(value) {
5
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6
+ }
7
+ export function fail(key) {
8
+ throw new PersistedDataCorruptError(key);
9
+ }
10
+ export function readString(source, field, key) {
11
+ const value = source[field];
12
+ if (typeof value !== "string" || value.length === 0)
13
+ fail(key);
14
+ return value;
15
+ }
16
+ export function readOptionalString(source, field, key) {
17
+ const value = source[field];
18
+ if (value === void 0)
19
+ return;
20
+ if (typeof value !== "string")
21
+ fail(key);
22
+ return value;
23
+ }
24
+ export function readInteger(source, field, key) {
25
+ const value = source[field];
26
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)
27
+ fail(key);
28
+ return value;
29
+ }
30
+ export function readPositiveInteger(source, field, key) {
31
+ const value = readInteger(source, field, key);
32
+ if (value === 0)
33
+ fail(key);
34
+ return value;
35
+ }
36
+ export function readOptionalInteger(source, field, key) {
37
+ if (source[field] === void 0)
38
+ return;
39
+ return readInteger(source, field, key);
40
+ }
41
+ export function readVersion(source, key, expected = MAILBOX_RECORD_VERSION) {
42
+ if (source.recordVersion !== expected)
43
+ fail(key);
44
+ }
45
+ export function ownKey(build, key) {
46
+ try {
47
+ return build();
48
+ } catch {
49
+ return fail(key);
50
+ }
51
+ }
52
+ export function readIdentifier(value, key) {
53
+ if (typeof value !== "string" || value.length === 0 || !isWellFormedString(value) || new TextEncoder().encode(value).byteLength > MAX_APPLICATION_IDENTITY_BYTES)
54
+ fail(key);
55
+ return value;
56
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Committing one durable state transition of an application primitive,
3
+ * atomically with the fleet event that describes it (WFT-84, WFT-85).
4
+ *
5
+ * A transition is a compare-and-swap plan: the conditions it was decided
6
+ * against and the operations that carry it out. Without an event sink the plan
7
+ * is a plain conditional batch. With one, the sink is asked to append the event
8
+ * under the same conditions and operations, and the FIRST commit through a sink
9
+ * also writes a single-use probe key that is read back afterwards: nothing else
10
+ * ever writes that key, so a concurrent transition on the record between the
11
+ * commit and the read cannot be mistaken for a sink that committed to a
12
+ * different backend.
13
+ *
14
+ * @module core/application-primitive-commit
15
+ */
16
+ import type { BatchOperation, ConditionalBatchCondition, Storage } from '../storage/interface.ts';
17
+ /**
18
+ * What a primitive needs from a fleet event feed: append one event atomically
19
+ * with the transition's conditions and operations. Structural on purpose, so
20
+ * the core never imports the server's feed.
21
+ */
22
+ export type ApplicationEventSink = {
23
+ append(event: {
24
+ readonly kind: string;
25
+ readonly emittedAtMs: number;
26
+ readonly payload: unknown;
27
+ }, transaction: {
28
+ readonly conditions: readonly ConditionalBatchCondition[];
29
+ readonly operations: readonly BatchOperation[];
30
+ }): Promise<unknown>;
31
+ };
32
+ /** One durable state transition, optionally paired with the fleet event that describes it. */
33
+ export type ApplicationCommitPlan = {
34
+ readonly conditions: readonly ConditionalBatchCondition[];
35
+ readonly operations: readonly BatchOperation[];
36
+ readonly event: {
37
+ readonly kind: string;
38
+ readonly payload: unknown;
39
+ } | null;
40
+ readonly now: number;
41
+ /**
42
+ * Where the first commit through an event sink writes its verification
43
+ * probe. Unique per plan, so a concurrent transition on the record can never
44
+ * be mistaken for a sink that committed somewhere else.
45
+ */
46
+ readonly sinkProbeKey: string;
47
+ };
48
+ /**
49
+ * Whether every compare-and-swap condition still matches durable state.
50
+ *
51
+ * Used to classify a failed event-sink append: if the caller's own conditions
52
+ * still hold, the append failed for the feed's own reasons and the error must
53
+ * propagate; if one moved, another actor won the race and the caller retries.
54
+ */
55
+ export declare function conditionsStillHold(storage: Storage, conditions: readonly ConditionalBatchCondition[]): Promise<boolean>;
56
+ /** Byte-for-byte equality of two values, as `conditionalBatch` compares them. */
57
+ export declare function bytesEqual(left: Uint8Array, right: Uint8Array): boolean;
58
+ /**
59
+ * Commit one transition, atomically with its fleet event when a sink is
60
+ * configured.
61
+ *
62
+ * Returns `false` when a compare-and-swap condition was lost, which means
63
+ * another actor transitioned the record first and the caller should re-read and
64
+ * re-decide. Any other failure throws. `subject` names the primitive in the
65
+ * misconfiguration diagnostic ("application mailbox", "application outbox").
66
+ */
67
+ export declare function commitApplicationTransition(storage: Storage, events: ApplicationEventSink | undefined, plan: ApplicationCommitPlan, subject: string): Promise<boolean>;
@@ -0,0 +1,66 @@
1
+ import { storageConditionalBatch } from "../storage/interface.js";
2
+ export async function conditionsStillHold(storage, conditions) {
3
+ for (const condition of conditions) {
4
+ const current = await storage.get(condition.key);
5
+ if (condition.expectedValue === null) {
6
+ if (current !== null)
7
+ return !1;
8
+ continue;
9
+ }
10
+ if (current === null || !bytesEqual(current, condition.expectedValue))
11
+ return !1;
12
+ }
13
+ return !0;
14
+ }
15
+ export function bytesEqual(left, right) {
16
+ if (left.byteLength !== right.byteLength)
17
+ return !1;
18
+ for (let index = 0;index < left.byteLength; index += 1)
19
+ if (left[index] !== right[index])
20
+ return !1;
21
+ return !0;
22
+ }
23
+ export async function commitApplicationTransition(storage, events, plan, subject) {
24
+ if (events === void 0 || plan.event === null)
25
+ return storageConditionalBatch(storage, [...plan.conditions], [...plan.operations]);
26
+ const probe = isSinkVerified(storage, events) ? null : { key: plan.sinkProbeKey, value: new TextEncoder().encode(plan.sinkProbeKey) };
27
+ try {
28
+ await events.append({ kind: plan.event.kind, emittedAtMs: plan.now, payload: plan.event.payload }, {
29
+ conditions: plan.conditions,
30
+ operations: probe === null ? plan.operations : [...plan.operations, { type: "put", key: probe.key, value: probe.value }]
31
+ });
32
+ } catch (error) {
33
+ if (await conditionsStillHold(storage, plan.conditions))
34
+ throw error;
35
+ return !1;
36
+ }
37
+ if (probe !== null)
38
+ await assertSinkCommittedLocally(storage, events, probe, subject);
39
+ return !0;
40
+ }
41
+ const VERIFIED_SINK_BACKENDS = new WeakMap;
42
+ function verifiedBackendsFor(events) {
43
+ let verified = VERIFIED_SINK_BACKENDS.get(events);
44
+ if (verified === void 0) {
45
+ verified = new WeakSet;
46
+ VERIFIED_SINK_BACKENDS.set(events, verified);
47
+ }
48
+ return verified;
49
+ }
50
+ function isSinkVerified(storage, events) {
51
+ return verifiedBackendsFor(events).has(storage);
52
+ }
53
+ async function assertSinkCommittedLocally(storage, events, probe, subject) {
54
+ let stored;
55
+ try {
56
+ stored = await storage.get(probe.key);
57
+ } catch {
58
+ return;
59
+ }
60
+ if (stored === null || !bytesEqual(stored, probe.value))
61
+ throw Error(`The configured ${subject} event sink committed to a different storage backend than the ${subject}. Build the fleet event feed over the same Storage instance the ${subject} uses.`);
62
+ verifiedBackendsFor(events).add(storage);
63
+ try {
64
+ await storage.delete(probe.key);
65
+ } catch {}
66
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Field-level guards shared by the durable application primitives (the
3
+ * command mailbox, WFT-84, and the delivery outbox, WFT-85): identity
4
+ * components, durable JSON metadata, injected clock readings, generated
5
+ * identifiers, derived instants, and wait budgets.
6
+ *
7
+ * Every guard rejects at the boundary that owns the contract rather than letting
8
+ * a bad value reach durable storage — a record the decoder cannot read back
9
+ * would surface much later as corruption on an unrelated read.
10
+ *
11
+ * The guards are produced by a factory bound to the primitive's own validation
12
+ * error class, so a mailbox caller sees `ApplicationCommandValidationError` and
13
+ * an outbox caller sees its own, while the checks themselves are written once.
14
+ *
15
+ * @module core/application-primitive-guards
16
+ */
17
+ import { type JSONValue } from './json.ts';
18
+ /** Maximum bytes in any opaque identity component (namespace, resource, owner, caller, target, kind). */
19
+ export declare const MAX_APPLICATION_IDENTITY_BYTES = 256;
20
+ /** Maximum encoded bytes for durable JSON metadata such as an outcome, progress, or failure details. */
21
+ export declare const MAX_DURABLE_METADATA_BYTES = 65536;
22
+ /**
23
+ * The largest delay `setTimeout` schedules faithfully (a signed 32-bit
24
+ * millisecond count). A larger value is clamped to a tick by the runtime, which
25
+ * would turn a rare poll into a tight loop against durable storage.
26
+ */
27
+ export declare const MAX_TIMER_DELAY_MS = 2147483647;
28
+ /** Default gap between durable polls for the bounded waits. */
29
+ export declare const DEFAULT_WAIT_POLL_INTERVAL_MS = 50;
30
+ /** The UTF-8 length of a string. */
31
+ export declare function byteLengthOf(value: string): number;
32
+ /** Whether a string contains no unpaired surrogate. */
33
+ export declare function isWellFormedString(value: string): boolean;
34
+ /** A validation error class a primitive binds its guards to. */
35
+ export type ApplicationValidationErrorClass = new (message: string, options?: ErrorOptions) => Error;
36
+ /** The guard set produced by {@link createApplicationGuards}. */
37
+ export type ApplicationGuards = {
38
+ readonly requireIdentity: (value: unknown, field: string, maxBytes: number) => string;
39
+ readonly optionalIdentityOf: (value: unknown, field: string, maxBytes: number) => string | undefined;
40
+ readonly requirePositiveInteger: (value: unknown, field: string, maximum: number) => number;
41
+ readonly requireNonNegativeInteger: (value: unknown, field: string, maximum: number) => number;
42
+ readonly validateDurableJSONValue: (value: unknown, field: string) => JSONValue | undefined;
43
+ readonly requireGeneratedIdentifier: (value: string, field: string) => string;
44
+ readonly requireClockInstant: (now: number, source?: string) => number;
45
+ readonly requireDerivedInstant: (instant: number, field: string) => number;
46
+ readonly requireMaintenanceInstant: (now: number) => number;
47
+ readonly requireWaitBudget: (options: {
48
+ readonly timeoutMs?: number | undefined;
49
+ readonly pollIntervalMs?: number | undefined;
50
+ }) => {
51
+ readonly timeoutMs: number;
52
+ readonly pollIntervalMs: number;
53
+ };
54
+ };
55
+ /**
56
+ * Bind the shared guards to one primitive's validation error class.
57
+ *
58
+ * Every guard throws an instance of `ValidationError`, so callers and tests of
59
+ * the mailbox keep matching `ApplicationCommandValidationError` while the
60
+ * outbox matches its own class.
61
+ */
62
+ export declare function createApplicationGuards(ValidationError: ApplicationValidationErrorClass): ApplicationGuards;
@@ -0,0 +1,89 @@
1
+ import { decode, encode } from "./codec.js";
2
+ import { isJSONValue } from "./json.js";
3
+ export const MAX_APPLICATION_IDENTITY_BYTES = 256, MAX_DURABLE_METADATA_BYTES = 65536, MAX_TIMER_DELAY_MS = 2147483647, DEFAULT_WAIT_POLL_INTERVAL_MS = 50;
4
+ export function byteLengthOf(value) {
5
+ return new TextEncoder().encode(value).byteLength;
6
+ }
7
+ export function isWellFormedString(value) {
8
+ return value.isWellFormed();
9
+ }
10
+ export function createApplicationGuards(ValidationError) {
11
+ const requireIdentity = (value, field, maxBytes) => {
12
+ if (typeof value !== "string" || value.length === 0)
13
+ throw new ValidationError(`${field} must be a non-empty string.`);
14
+ if (!isWellFormedString(value))
15
+ throw new ValidationError(`${field} must be well-formed Unicode: it contains an unpaired surrogate that cannot be encoded into a storage key.`);
16
+ if (byteLengthOf(value) > maxBytes)
17
+ throw new ValidationError(`${field} must encode to at most ${maxBytes} bytes.`);
18
+ return value;
19
+ };
20
+ return {
21
+ requireIdentity,
22
+ optionalIdentityOf: (value, field, maxBytes) => {
23
+ if (value === void 0)
24
+ return;
25
+ return requireIdentity(value, field, maxBytes);
26
+ },
27
+ requirePositiveInteger: (value, field, maximum) => {
28
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1 || value > maximum)
29
+ throw new ValidationError(`${field} must be a safe integer between 1 and ${maximum}.`);
30
+ return value;
31
+ },
32
+ requireNonNegativeInteger: (value, field, maximum) => {
33
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0 || value > maximum)
34
+ throw new ValidationError(`${field} must be a safe integer between 0 and ${maximum}.`);
35
+ return value;
36
+ },
37
+ validateDurableJSONValue: (value, field) => {
38
+ if (value === void 0)
39
+ return;
40
+ let snapshot, encoded;
41
+ try {
42
+ encoded = encode(value);
43
+ snapshot = decode(encoded);
44
+ } catch (cause) {
45
+ throw new ValidationError(`${field} is not encodable by the structured-clone codec.`, {
46
+ cause
47
+ });
48
+ }
49
+ if (!isJSONValue(snapshot))
50
+ throw new ValidationError(`${field} must be a JSON-safe value: durable records reject anything the record decoder cannot read back.`);
51
+ if (encoded.byteLength > MAX_DURABLE_METADATA_BYTES)
52
+ throw new ValidationError(`${field} encodes to ${encoded.byteLength} bytes, over the ${MAX_DURABLE_METADATA_BYTES}-byte durable metadata ceiling.`);
53
+ return snapshot;
54
+ },
55
+ requireGeneratedIdentifier: (value, field) => {
56
+ if (typeof value !== "string" || value.length === 0)
57
+ throw new ValidationError(`The configured generateId() returned an empty ${field}; durable keys require a non-empty identifier.`);
58
+ if (!isWellFormedString(value))
59
+ throw new ValidationError(`The configured generateId() returned a ${field} containing an unpaired surrogate.`);
60
+ if (byteLengthOf(value) > MAX_APPLICATION_IDENTITY_BYTES)
61
+ throw new ValidationError(`The configured generateId() returned a ${field} over ${MAX_APPLICATION_IDENTITY_BYTES} bytes.`);
62
+ return value;
63
+ },
64
+ requireClockInstant: (now, source = "now()") => {
65
+ if (typeof now !== "number" || !Number.isSafeInteger(now) || now < 0)
66
+ throw new ValidationError(`The configured ${source} returned ${String(now)}; durable records require a non-negative safe-integer millisecond timestamp.`);
67
+ return now;
68
+ },
69
+ requireDerivedInstant: (instant, field) => {
70
+ if (!Number.isSafeInteger(instant) || instant < 0)
71
+ throw new ValidationError(`${field} computed to ${String(instant)}, outside the safe-integer millisecond range durable records accept.`);
72
+ return instant;
73
+ },
74
+ requireMaintenanceInstant: (now) => {
75
+ if (typeof now !== "number" || !Number.isSafeInteger(now) || now < 0)
76
+ throw new ValidationError("runMaintenance() requires a non-negative safe-integer timestamp in milliseconds.");
77
+ return now;
78
+ },
79
+ requireWaitBudget: (options) => {
80
+ const timeoutMs = options.timeoutMs ?? 0;
81
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 0 || timeoutMs > MAX_TIMER_DELAY_MS)
82
+ throw new ValidationError(`timeoutMs must be a non-negative safe integer of at most ${MAX_TIMER_DELAY_MS} milliseconds, the largest delay a timer can schedule; the in-flight budget is scheduled as one timer.`);
83
+ const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_WAIT_POLL_INTERVAL_MS;
84
+ if (!Number.isSafeInteger(pollIntervalMs) || pollIntervalMs < 1 || pollIntervalMs > MAX_TIMER_DELAY_MS)
85
+ throw new ValidationError(`pollIntervalMs must be a positive safe integer of at most ${MAX_TIMER_DELAY_MS} milliseconds, the largest delay a timer can schedule.`);
86
+ return { timeoutMs, pollIntervalMs };
87
+ }
88
+ };
89
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Payload and causation validation shared by the durable application
3
+ * primitives (the command mailbox, WFT-84, and the delivery outbox, WFT-85).
4
+ *
5
+ * Both primitives carry the same inline-or-reference payload and the same
6
+ * causal metadata, bound to the same content digest, so the checks are written
7
+ * once and bound to each primitive's own validation error class.
8
+ *
9
+ * @module core/application-primitive-payload
10
+ */
11
+ import { type ApplicationValidationErrorClass } from './application-primitive-guards.ts';
12
+ import type { ApplicationCommandCausation, ApplicationCommandPayload } from './mailbox-types.ts';
13
+ /** Maximum bytes in a content-addressed payload reference. */
14
+ export declare const MAX_APPLICATION_PAYLOAD_REFERENCE_BYTES = 2048;
15
+ /** A validated payload with its digest. */
16
+ export type ValidatedPayload = {
17
+ readonly payload: ApplicationCommandPayload;
18
+ readonly digest: string;
19
+ };
20
+ /** The validator set produced by {@link createPayloadValidators}. */
21
+ export type PayloadValidators = {
22
+ readonly validatePayload: (payload: unknown, maxInlinePayloadBytes: number) => Promise<ValidatedPayload>;
23
+ readonly validateCausation: (causation: ApplicationCommandCausation | undefined) => ApplicationCommandCausation | undefined;
24
+ };
25
+ /**
26
+ * Bind the shared payload validators to one primitive's validation error
27
+ * class.
28
+ */
29
+ export declare function createPayloadValidators(ValidationError: ApplicationValidationErrorClass): PayloadValidators;