@lostgradient/weft 0.23.0 → 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 (295) 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-loader.js +2 -2
  281. package/dist/storage/node-sqlite.js +51 -8
  282. package/dist/storage/outbox-keys.d.ts +77 -0
  283. package/dist/storage/outbox-keys.js +14 -0
  284. package/dist/storage/postgres.js +2 -2
  285. package/dist/storage/resolve.js +1 -1
  286. package/dist/storage/scoped-storage.js +1 -1
  287. package/dist/storage/storage-configuration.d.ts +6 -0
  288. package/dist/storage/testing.js +1 -1
  289. package/dist/storage/turso.js +2 -2
  290. package/dist/version.d.ts +1 -1
  291. package/dist/version.js +1 -1
  292. package/dist/web-extension.js +1 -1
  293. package/dist/worker/manifest/registry-contract-builder.d.ts +21 -3
  294. package/dist/worker/manifest/registry-contract-builder.js +40 -31
  295. package/package.json +1 -1
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Bounds and input validation for the durable application delivery outbox
3
+ * (WFT-85): construction options, offered deliveries, and adapter outcomes.
4
+ *
5
+ * Every bound here exists so a hostile or buggy caller — or a misbehaving
6
+ * transport — cannot grow durable storage without limit, push an unbounded
7
+ * string into a storage key, or write a record the decoder rejects. The outbox
8
+ * validates at the boundary, before any write, so a rejected input leaves no
9
+ * trace.
10
+ *
11
+ * @module core/outbox-validation
12
+ */
13
+ import type { JSONValue } from './json.ts';
14
+ import type { ApplicationDeliveryInput, OutboxOptions } from './outbox-contract.ts';
15
+ import type { ApplicationDeliveryFailure, ApplicationDeliveryPayload, ApplicationDeliveryUnknownOutcomePolicy } from './outbox-types.ts';
16
+ /** Outbox defaults resolved once at construction. */
17
+ export type ResolvedOutboxPolicy = Readonly<{
18
+ namespace: string;
19
+ ownerId: string;
20
+ maxBacklog: number;
21
+ visibilityTimeoutMs: number;
22
+ attemptTimeoutMs: number;
23
+ maxAttempts: number;
24
+ retryBackoffMs: number;
25
+ maxRetryBackoffMs: number;
26
+ terminalRetentionMs: number;
27
+ maxInlinePayloadBytes: number;
28
+ maintenanceBatchSize: number;
29
+ unknownOutcomePolicy: ApplicationDeliveryUnknownOutcomePolicy;
30
+ backgroundTasks: 'automatic' | 'manual';
31
+ maintenanceIntervalMs: number;
32
+ }>;
33
+ /**
34
+ * Resolve and range-check the outbox construction options.
35
+ *
36
+ * `attemptTimeoutMs` and `maintenanceIntervalMs` are each scheduled as one
37
+ * timer, so they are bounded by the largest delay a timer honours rather than
38
+ * by the general duration ceiling: a larger value would be clamped to a tick by
39
+ * the runtime, and every attempt would abort at once.
40
+ *
41
+ * @throws {ApplicationDeliveryValidationError} When any option is out of range.
42
+ */
43
+ export declare function resolveOutboxPolicy(options: OutboxOptions): ResolvedOutboxPolicy;
44
+ /** A validated delivery input with its digest and effective per-delivery policy resolved. */
45
+ export type ValidatedDeliveryInput = Readonly<{
46
+ destinationRef: string;
47
+ credentialRef?: string | undefined;
48
+ kind: string;
49
+ payload: ApplicationDeliveryPayload;
50
+ payloadDigest: string;
51
+ payloadMediaType?: string | undefined;
52
+ payloadSchema?: string | undefined;
53
+ idempotencyKey?: string | undefined;
54
+ externalIdempotencyKey?: string | undefined;
55
+ unknownOutcomePolicy: ApplicationDeliveryUnknownOutcomePolicy;
56
+ causation?: ApplicationDeliveryInput['causation'] | undefined;
57
+ availableAfterMs: number;
58
+ maxAttempts: number;
59
+ visibilityTimeoutMs: number;
60
+ attemptTimeoutMs: number;
61
+ }>;
62
+ /**
63
+ * Validate one delivery offered for enqueue and resolve its effective policy.
64
+ *
65
+ * The unknown-outcome policy and the external idempotency evidence are
66
+ * validated together here, so a record with `retry-with-idempotency` and no
67
+ * `externalIdempotencyKey` can never exist: recovery never has to decide what
68
+ * to do with a retry it cannot make safely.
69
+ *
70
+ * @throws {ApplicationDeliveryValidationError} When any field is missing,
71
+ * oversized, out of range, or the policy lacks its evidence.
72
+ */
73
+ export declare function validateDeliveryInput(input: ApplicationDeliveryInput, policy: ResolvedOutboxPolicy): Promise<ValidatedDeliveryInput>;
74
+ /** A transport outcome with every caller-supplied field validated and snapshotted. */
75
+ export type ValidatedOutcome = {
76
+ readonly status: 'acknowledged';
77
+ readonly evidence: JSONValue | undefined;
78
+ } | {
79
+ readonly status: 'retryable';
80
+ readonly failure: ApplicationDeliveryFailure;
81
+ readonly retryAfterMs: number | undefined;
82
+ } | {
83
+ readonly status: 'rejected';
84
+ readonly failure: ApplicationDeliveryFailure;
85
+ } | {
86
+ readonly status: 'unknown';
87
+ readonly failure: {
88
+ readonly reason: 'unknown-outcome';
89
+ readonly message?: string | undefined;
90
+ };
91
+ };
92
+ /**
93
+ * Validate what a transport reported before it becomes durable evidence.
94
+ *
95
+ * A malformed outcome — not an object, an unknown status, `NaN` for
96
+ * `retryAfterMs`, a `Map` as evidence — is not a caller mistake the outbox can
97
+ * refuse: the send may already have happened. It is therefore mapped to
98
+ * `unknown` with a diagnostic message, so the delivery follows the unknown-
99
+ * outcome policy instead of being retried on the strength of nothing.
100
+ */
101
+ export declare function validateOutcome(outcome: unknown): ValidatedOutcome;
@@ -0,0 +1,127 @@
1
+ import { createPayloadValidators } from "./application-primitive-payload.js";
2
+ import {
3
+ ApplicationDeliveryValidationError,
4
+ MAX_APPLICATION_DELIVERY_ATTEMPTS,
5
+ MAX_APPLICATION_DELIVERY_IDEMPOTENCY_KEY_BYTES,
6
+ MAX_APPLICATION_IDENTITY_BYTES,
7
+ MAX_DELIVERY_FAILURE_MESSAGE_BYTES,
8
+ MAX_OUTBOX_BACKLOG,
9
+ MAX_TIMER_DELAY_MS,
10
+ optionalIdentityOf,
11
+ requireIdentity,
12
+ requireNonNegativeInteger,
13
+ requirePositiveInteger,
14
+ validateDurableJSONValue,
15
+ validateFailureEvidence
16
+ } from "./outbox-guards.js";
17
+ const { validatePayload, validateCausation } = createPayloadValidators(ApplicationDeliveryValidationError), UNKNOWN_OUTCOME_POLICIES = new Set([
18
+ "park",
19
+ "dead-letter",
20
+ "retry-with-idempotency"
21
+ ]), ONE_DAY_MS = 86400000, MAX_DURATION_MS = 31536000000;
22
+ function requirePolicy(value, field) {
23
+ if (typeof value !== "string" || !UNKNOWN_OUTCOME_POLICIES.has(value))
24
+ throw new ApplicationDeliveryValidationError(`${field} must be one of ${[...UNKNOWN_OUTCOME_POLICIES].join(", ")}.`);
25
+ return value;
26
+ }
27
+ export function resolveOutboxPolicy(options) {
28
+ const backgroundTasks = options.backgroundTasks ?? "manual";
29
+ if (backgroundTasks !== "manual" && backgroundTasks !== "automatic")
30
+ throw new ApplicationDeliveryValidationError("backgroundTasks must be 'manual' or 'automatic'.");
31
+ return {
32
+ namespace: requireIdentity(options.namespace, "namespace", MAX_APPLICATION_IDENTITY_BYTES),
33
+ ownerId: requireIdentity(options.ownerId, "ownerId", MAX_APPLICATION_IDENTITY_BYTES),
34
+ ...resolveBounds(options),
35
+ unknownOutcomePolicy: requirePolicy(options.unknownOutcomePolicy ?? "park", "unknownOutcomePolicy"),
36
+ backgroundTasks
37
+ };
38
+ }
39
+ function positive(value, fallback, field, maximum) {
40
+ return requirePositiveInteger(value ?? fallback, field, maximum);
41
+ }
42
+ function resolveBounds(options) {
43
+ return {
44
+ maxBacklog: positive(options.maxBacklog, 1000, "maxBacklog", MAX_OUTBOX_BACKLOG),
45
+ visibilityTimeoutMs: positive(options.visibilityTimeoutMs, 30000, "visibilityTimeoutMs", MAX_DURATION_MS),
46
+ attemptTimeoutMs: positive(options.attemptTimeoutMs, 300000, "attemptTimeoutMs", MAX_TIMER_DELAY_MS),
47
+ maxAttempts: positive(options.maxAttempts, 3, "maxAttempts", MAX_APPLICATION_DELIVERY_ATTEMPTS),
48
+ retryBackoffMs: positive(options.retryBackoffMs, 1000, "retryBackoffMs", MAX_DURATION_MS),
49
+ maxRetryBackoffMs: positive(options.maxRetryBackoffMs, 60000, "maxRetryBackoffMs", MAX_DURATION_MS),
50
+ terminalRetentionMs: positive(options.terminalRetentionMs, ONE_DAY_MS, "terminalRetentionMs", MAX_DURATION_MS),
51
+ maxInlinePayloadBytes: positive(options.maxInlinePayloadBytes, 262144, "maxInlinePayloadBytes", 67108864),
52
+ maintenanceBatchSize: positive(options.maintenanceBatchSize, 500, "maintenanceBatchSize", 1e4),
53
+ maintenanceIntervalMs: positive(options.maintenanceIntervalMs, 1000, "maintenanceIntervalMs", MAX_TIMER_DELAY_MS)
54
+ };
55
+ }
56
+ export async function validateDeliveryInput(input, policy) {
57
+ if (typeof input !== "object" || input === null)
58
+ throw new ApplicationDeliveryValidationError("delivery must be an object.");
59
+ const { payload, digest } = await validatePayload(input.payload, policy.maxInlinePayloadBytes), unknownOutcomePolicy = requirePolicy(input.unknownOutcomePolicy ?? policy.unknownOutcomePolicy, "unknownOutcomePolicy"), externalIdempotencyKey = optionalIdentityOf(input.externalIdempotencyKey, "externalIdempotencyKey", MAX_APPLICATION_DELIVERY_IDEMPOTENCY_KEY_BYTES);
60
+ if (unknownOutcomePolicy === "retry-with-idempotency" && externalIdempotencyKey === void 0)
61
+ throw new ApplicationDeliveryValidationError("unknownOutcomePolicy 'retry-with-idempotency' requires externalIdempotencyKey: without stable external idempotency evidence a retry after an unknown outcome could duplicate the effect.");
62
+ return {
63
+ destinationRef: requireIdentity(input.destinationRef, "destinationRef", MAX_APPLICATION_IDENTITY_BYTES),
64
+ credentialRef: optionalIdentityOf(input.credentialRef, "credentialRef", MAX_APPLICATION_IDENTITY_BYTES),
65
+ kind: requireIdentity(input.kind, "kind", MAX_APPLICATION_IDENTITY_BYTES),
66
+ payload,
67
+ payloadDigest: digest,
68
+ payloadMediaType: optionalIdentityOf(input.payloadMediaType, "payloadMediaType", MAX_APPLICATION_IDENTITY_BYTES),
69
+ payloadSchema: optionalIdentityOf(input.payloadSchema, "payloadSchema", MAX_APPLICATION_IDENTITY_BYTES),
70
+ idempotencyKey: optionalIdentityOf(input.idempotencyKey, "idempotencyKey", MAX_APPLICATION_DELIVERY_IDEMPOTENCY_KEY_BYTES),
71
+ externalIdempotencyKey,
72
+ unknownOutcomePolicy,
73
+ causation: validateCausation(input.causation),
74
+ availableAfterMs: requireNonNegativeInteger(input.availableAfterMs ?? 0, "availableAfterMs", MAX_DURATION_MS),
75
+ maxAttempts: requirePositiveInteger(input.maxAttempts ?? policy.maxAttempts, "maxAttempts", MAX_APPLICATION_DELIVERY_ATTEMPTS),
76
+ visibilityTimeoutMs: requirePositiveInteger(input.visibilityTimeoutMs ?? policy.visibilityTimeoutMs, "visibilityTimeoutMs", MAX_DURATION_MS),
77
+ attemptTimeoutMs: requirePositiveInteger(input.attemptTimeoutMs ?? policy.attemptTimeoutMs, "attemptTimeoutMs", MAX_TIMER_DELAY_MS)
78
+ };
79
+ }
80
+ export function validateOutcome(outcome) {
81
+ try {
82
+ return readOutcome(outcome);
83
+ } catch (error) {
84
+ if (error instanceof ApplicationDeliveryValidationError)
85
+ return {
86
+ status: "unknown",
87
+ failure: {
88
+ reason: "unknown-outcome",
89
+ message: `The transport adapter returned a malformed outcome: ${error.message}`
90
+ }
91
+ };
92
+ throw error;
93
+ }
94
+ }
95
+ function readOutcome(outcome) {
96
+ if (typeof outcome !== "object" || outcome === null || !("status" in outcome))
97
+ throw new ApplicationDeliveryValidationError("outcome must be an object with a status.");
98
+ const candidate = outcome;
99
+ switch (candidate.status) {
100
+ case "acknowledged":
101
+ return {
102
+ status: "acknowledged",
103
+ evidence: validateDurableJSONValue(candidate.evidence, "evidence")
104
+ };
105
+ case "retryable":
106
+ return {
107
+ status: "retryable",
108
+ failure: validateFailureEvidence("retryable", candidate.message, candidate.details),
109
+ retryAfterMs: candidate.retryAfterMs === void 0 ? void 0 : requireNonNegativeInteger(candidate.retryAfterMs, "retryAfterMs", MAX_DURATION_MS)
110
+ };
111
+ case "rejected":
112
+ return {
113
+ status: "rejected",
114
+ failure: validateFailureEvidence("application", candidate.message, candidate.details)
115
+ };
116
+ case "unknown":
117
+ return {
118
+ status: "unknown",
119
+ failure: {
120
+ reason: "unknown-outcome",
121
+ message: optionalIdentityOf(candidate.message, "message", MAX_DELIVERY_FAILURE_MESSAGE_BYTES)
122
+ }
123
+ };
124
+ default:
125
+ throw new ApplicationDeliveryValidationError("outcome.status must be 'acknowledged', 'retryable', 'rejected', or 'unknown'.");
126
+ }
127
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The abortable, bounded waits the application delivery outbox exposes
3
+ * (WFT-85): waiting for due work, and waiting for a cancelled delivery's
4
+ * attempt to settle.
5
+ *
6
+ * Both are polling waits, for the same reason the mailbox's are: another
7
+ * process's enqueue is visible only in durable storage. Every wait is bounded
8
+ * by a caller-supplied deadline, respects a caller-supplied `AbortSignal`, and
9
+ * unwinds cleanly on disposal. Waiting never claims, starts, or advances work.
10
+ *
11
+ * @module core/outbox-waits
12
+ */
13
+ import { delayUnlessAborted } from './application-primitive-timing.ts';
14
+ import type { ApplicationDeliveryCleanupResult, OutboxWaitOptions } from './outbox-contract.ts';
15
+ import type { OutboxRuntime } from './outbox-internals.ts';
16
+ export { delayUnlessAborted };
17
+ /**
18
+ * Whether a delivery is claimable right now: the earliest genuine due entry
19
+ * is at or before the clock. Orphaned entries are looked past, bounded, since
20
+ * a wait mutates nothing and cannot make progress through them.
21
+ */
22
+ export declare function hasDueWork(runtime: OutboxRuntime): Promise<boolean>;
23
+ /**
24
+ * Wait, bounded and abortably, until this outbox has a delivery due.
25
+ * Returns `true` when one is claimable and `false` when the wait was aborted,
26
+ * the outbox disposed, or the timeout elapsed first.
27
+ */
28
+ export declare function waitForDueWork(runtime: OutboxRuntime, options?: OutboxWaitOptions): Promise<boolean>;
29
+ /**
30
+ * Wait, bounded, for a cancelled delivery's attempt to settle. A `pending`
31
+ * result means this outbox stopped waiting, never that the transport stopped.
32
+ * The semantics match the mailbox's `awaitCleanup`.
33
+ */
34
+ export declare function waitForCleanup(runtime: OutboxRuntime, options: {
35
+ readonly deliveryId: string;
36
+ readonly timeoutMs: number;
37
+ readonly signal?: AbortSignal | undefined;
38
+ readonly pollIntervalMs?: number | undefined;
39
+ }): Promise<ApplicationDeliveryCleanupResult>;
@@ -0,0 +1,88 @@
1
+ import { WaitBudgetElapsedError, raceAbortWithin } from "./application-primitive-abort.js";
2
+ import { delayUnlessAborted } from "./application-primitive-timing.js";
3
+ import { DUE_HEAD_LOOKAHEAD } from "./outbox-delivery.js";
4
+ import { requireClockInstant, requireDerivedInstant, requireWaitBudget } from "./outbox-guards.js";
5
+ import { readCleanupState } from "./outbox-settlement.js";
6
+ import { loadDelivery, loadDueHead } from "./outbox-storage.js";
7
+ import { isApplicationDeliveryWaiting } from "./outbox-types.js";
8
+
9
+ export { delayUnlessAborted };
10
+ export async function hasDueWork(runtime) {
11
+ const entries = await loadDueHead(runtime.storage, runtime.keys, DUE_HEAD_LOOKAHEAD);
12
+ for (const entry of entries) {
13
+ const loaded = await loadDelivery(runtime.storage, runtime.keys, entry.deliveryId);
14
+ if (loaded === null || !isApplicationDeliveryWaiting(loaded.record))
15
+ continue;
16
+ if (entry.key !== runtime.keys.due(loaded.record.availableAt, loaded.record.deliveryId))
17
+ continue;
18
+ return runtime.now() >= loaded.record.availableAt;
19
+ }
20
+ return !1;
21
+ }
22
+ function budgetSpent(timeoutMs, deadline, now) {
23
+ return timeoutMs > 0 && now >= deadline;
24
+ }
25
+ function budgetFor(timeoutMs, deadline, now) {
26
+ if (timeoutMs === 0)
27
+ return null;
28
+ const remaining = deadline - now;
29
+ return remaining > 0 ? remaining : null;
30
+ }
31
+ function isAborted(disposal, signal) {
32
+ return disposal.aborted || signal?.aborted === !0;
33
+ }
34
+ export async function waitForDueWork(runtime, options) {
35
+ const { timeoutMs, pollIntervalMs } = requireWaitBudget(options ?? {}), deadline = requireDerivedInstant(requireClockInstant(runtime.now()) + timeoutMs, "deadline"), disposal = runtime.disposal;
36
+ let remaining = 0;
37
+ do {
38
+ const observed = await observeDueWork(runtime, options?.signal, timeoutMs, deadline);
39
+ if (observed === null)
40
+ return !1;
41
+ if (observed)
42
+ return timeoutMs === 0 || runtime.now() <= deadline;
43
+ remaining = deadline - runtime.now();
44
+ if (remaining <= 0)
45
+ return !1;
46
+ } while (await delayUnlessAborted(Math.min(pollIntervalMs, remaining), disposal, options?.signal));
47
+ return !1;
48
+ }
49
+ async function observeDueWork(runtime, signal, timeoutMs, deadline) {
50
+ if (budgetSpent(timeoutMs, deadline, runtime.now()))
51
+ return null;
52
+ const observed = await raceAbortWithin(() => hasDueWork(runtime), budgetFor(timeoutMs, deadline, runtime.now()), runtime.disposal, signal);
53
+ if (observed.aborted || isAborted(runtime.disposal, signal))
54
+ return null;
55
+ return observed.value;
56
+ }
57
+ export async function waitForCleanup(runtime, options) {
58
+ const { timeoutMs, pollIntervalMs } = requireWaitBudget(options), deadline = requireDerivedInstant(requireClockInstant(runtime.now()) + timeoutMs, "deadline"), disposal = runtime.disposal, first = await readUnlessAborted(runtime, options.deliveryId, options.signal, budgetFor(timeoutMs, deadline, runtime.now()));
59
+ if (first === null)
60
+ throw new WaitBudgetElapsedError;
61
+ let latest = first;
62
+ if (latest.status === "pending" && latest.receipt.terminalAt !== void 0)
63
+ return latest;
64
+ while (latest.status === "pending") {
65
+ const remaining = deadline - runtime.now();
66
+ if (remaining <= 0)
67
+ break;
68
+ if (!await delayUnlessAborted(Math.min(pollIntervalMs, remaining), disposal, options.signal)) {
69
+ options.signal?.throwIfAborted();
70
+ break;
71
+ }
72
+ if (budgetSpent(timeoutMs, deadline, runtime.now()))
73
+ break;
74
+ const next = await readUnlessAborted(runtime, options.deliveryId, options.signal, budgetFor(timeoutMs, deadline, runtime.now()));
75
+ if (next === null)
76
+ break;
77
+ latest = next;
78
+ }
79
+ return latest;
80
+ }
81
+ async function readUnlessAborted(runtime, deliveryId, signal, budgetMs) {
82
+ const raced = await raceAbortWithin(() => readCleanupState(runtime, deliveryId), budgetMs, runtime.disposal, signal);
83
+ if (!raced.aborted)
84
+ return raced.value;
85
+ if (raced.reason instanceof WaitBudgetElapsedError)
86
+ return null;
87
+ throw raced.reason;
88
+ }
@@ -0,0 +1,156 @@
1
+ /**
2
+ * The durable application delivery outbox (WFT-85).
3
+ *
4
+ * An outbox is a storage-backed, at-least-once delivery queue scoped to one
5
+ * opaque `(namespace, ownerId)` pair. Enqueue returns a durable receipt before
6
+ * any transport attempt begins; attempts are fenced so two workers can never
7
+ * both hold a valid claim; the send is durably marked `attempting` before the
8
+ * transport is called, so a lost result is never mistaken for a safe retry;
9
+ * cancellation is durable before it reaches anyone; and every transition
10
+ * commits atomically with its fleet event when an event sink is configured.
11
+ *
12
+ * What it does not do: manufacture exactly-once external effects. A transport
13
+ * write completing is evidence the adapter reports; only the durable
14
+ * disposition the outbox commits on it moves a delivery. An unknown outcome is
15
+ * retried only when the delivery carries external idempotency evidence and its
16
+ * policy allows it; otherwise it is parked or dead-lettered, never duplicated.
17
+ *
18
+ * @module core/outbox
19
+ */
20
+ import type { Storage } from '../storage/interface.ts';
21
+ import type { JSONValue } from './json.ts';
22
+ import type { ApplicationDeliveryAdmission, ApplicationDeliveryCancellationResult, ApplicationDeliveryCleanupResult, ApplicationDeliveryHeartbeatResult, ApplicationDeliveryInput, ApplicationDeliveryOperatorResult, ApplicationDeliveryOutcome, ApplicationDeliveryReceipt, ApplicationDeliverySettleResult, OutboxCapacity, OutboxClaimResult, OutboxDeliverResult, OutboxDrainReport, OutboxListOptions, OutboxMaintenanceReport, OutboxOptions, OutboxWaitOptions } from './outbox-contract.ts';
23
+ /**
24
+ * A durable, at-least-once application delivery outbox.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * import { Outbox, MemoryStorage } from '@lostgradient/weft';
29
+ *
30
+ * await using storage = new MemoryStorage();
31
+ * using outbox = new Outbox({
32
+ * storage,
33
+ * namespace: 'bureau',
34
+ * ownerId: 'agent-7',
35
+ * adapter: { async send() { return { status: 'acknowledged' }; } },
36
+ * });
37
+ *
38
+ * const admission = await outbox.enqueue({
39
+ * destinationRef: 'webhook:orders',
40
+ * kind: 'order.shipped',
41
+ * payload: { form: 'inline', value: { orderId: 42 } },
42
+ * idempotencyKey: 'order-42-shipped',
43
+ * });
44
+ * console.log(admission.status); // 'enqueued'
45
+ *
46
+ * const delivered = await outbox.deliverNext();
47
+ * console.log(delivered.status === 'settled' && delivered.receipt.state); // 'acknowledged'
48
+ * ```
49
+ */
50
+ export declare class Outbox {
51
+ #private;
52
+ constructor(options: OutboxOptions);
53
+ /** The opaque application namespace this outbox is scoped to. */
54
+ get namespace(): string;
55
+ /** The opaque owner identifier this outbox is scoped to. */
56
+ get ownerId(): string;
57
+ /** The durable backend every transition compares and swaps against. */
58
+ get storage(): Storage;
59
+ /**
60
+ * Offer a delivery. An exact retry of the same idempotency identity returns
61
+ * the original receipt; a reused key with a different destination, kind, or
62
+ * payload returns a conflict; a full backlog is rejected before any write.
63
+ */
64
+ enqueue(delivery: ApplicationDeliveryInput): Promise<ApplicationDeliveryAdmission>;
65
+ /** Read one delivery's immutable receipt, or `null` when it is unknown or retired. */
66
+ receipt(deliveryId: string): Promise<ApplicationDeliveryReceipt | null>;
67
+ /** List receipts in enqueue order, bounded and non-consuming. */
68
+ list(options?: OutboxListOptions): Promise<ApplicationDeliveryReceipt[]>;
69
+ /** Current backlog accounting. Counts only. */
70
+ capacity(): Promise<OutboxCapacity>;
71
+ /**
72
+ * Lease the earliest due delivery to one attempt, for a host that drives the
73
+ * transport itself. Call `beginAttempt()` before sending and `settle()`
74
+ * after; heartbeat in between.
75
+ */
76
+ claim(options?: {
77
+ readonly signal?: AbortSignal | undefined;
78
+ }): Promise<OutboxClaimResult>;
79
+ /** Durably mark the current attempt as about to call the transport. */
80
+ beginAttempt(options: {
81
+ readonly deliveryId: string;
82
+ readonly attemptToken: string;
83
+ }): Promise<ApplicationDeliverySettleResult>;
84
+ /** Record liveness and extend visibility, clamped to the fixed attempt deadline. */
85
+ heartbeat(options: {
86
+ readonly deliveryId: string;
87
+ readonly attemptToken: string;
88
+ readonly transportActivity?: JSONValue | undefined;
89
+ }): Promise<ApplicationDeliveryHeartbeatResult>;
90
+ /**
91
+ * Settle the current attempt on what the transport reported. A malformed
92
+ * outcome is treated as `unknown`, never as a retry.
93
+ */
94
+ settle(options: {
95
+ readonly deliveryId: string;
96
+ readonly attemptToken: string;
97
+ readonly outcome: ApplicationDeliveryOutcome;
98
+ }): Promise<ApplicationDeliverySettleResult>;
99
+ /** Claim, begin, send through the configured adapter, and settle one delivery. */
100
+ deliverNext(options?: {
101
+ readonly signal?: AbortSignal | undefined;
102
+ }): Promise<OutboxDeliverResult>;
103
+ /**
104
+ * Deliver everything due within a bounded budget, running maintenance
105
+ * between rounds. Reports counts only; `pending` is what the durable header
106
+ * still holds open when the drain stops.
107
+ */
108
+ drain(options: {
109
+ readonly timeoutMs: number;
110
+ readonly signal?: AbortSignal | undefined;
111
+ readonly pollIntervalMs?: number | undefined;
112
+ }): Promise<OutboxDrainReport>;
113
+ /** Durably request cancellation and abort an in-process attempt. */
114
+ requestCancellation(options: {
115
+ readonly deliveryId: string;
116
+ readonly reason?: string | undefined;
117
+ }): Promise<ApplicationDeliveryCancellationResult>;
118
+ /** Read whether a cancelled delivery's attempt has finished. Non-consuming. */
119
+ cleanupState(deliveryId: string): Promise<ApplicationDeliveryCleanupResult>;
120
+ /** Wait, bounded, for a cancelled delivery's attempt to settle. */
121
+ awaitCleanup(options: {
122
+ readonly deliveryId: string;
123
+ readonly timeoutMs: number;
124
+ readonly signal?: AbortSignal | undefined;
125
+ readonly pollIntervalMs?: number | undefined;
126
+ }): Promise<ApplicationDeliveryCleanupResult>;
127
+ /** Return a parked, dead-lettered, or rejected delivery to the queue with at least one more attempt: an unspent budget is kept, a spent one is raised by one. */
128
+ retry(options: {
129
+ readonly deliveryId: string;
130
+ }): Promise<ApplicationDeliveryOperatorResult>;
131
+ /** Close a parked `unknown-outcome` delivery as dead-lettered. */
132
+ deadLetter(options: {
133
+ readonly deliveryId: string;
134
+ readonly reason?: string | undefined;
135
+ }): Promise<ApplicationDeliveryOperatorResult>;
136
+ /**
137
+ * Wait, bounded and abortably, until a delivery is due. `timeoutMs` defaults
138
+ * to `0`: one check, no wait.
139
+ */
140
+ waitForDue(options?: OutboxWaitOptions): Promise<boolean>;
141
+ /**
142
+ * Run one bounded maintenance pass: recover lapsed leases and retire
143
+ * terminal receipts past retention. Under `backgroundTasks: 'manual'` this is
144
+ * the only thing that advances time-driven recovery.
145
+ */
146
+ runMaintenance(now?: number): Promise<OutboxMaintenanceReport>;
147
+ /**
148
+ * Release every process-local resource: the maintenance timer, in-flight
149
+ * waits, and every attempt-scoped signal this handle holds. Disposal never
150
+ * deletes durable work; a claim this process held stays leased until it
151
+ * lapses and maintenance recovers it.
152
+ */
153
+ dispose(): void;
154
+ /** `using`-compatible disposal. */
155
+ [Symbol.dispose](): void;
156
+ }