@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,65 @@
1
+ import { computePayloadDigest, PayloadDigestError } from "./application-payload-digest.js";
2
+ import {
3
+ createApplicationGuards,
4
+ MAX_APPLICATION_IDENTITY_BYTES
5
+ } from "./application-primitive-guards.js";
6
+ import { decode, encode } from "./codec.js";
7
+ const HEX_DIGEST_PATTERN = /^[0-9a-f]{64}$/;
8
+ export const MAX_APPLICATION_PAYLOAD_REFERENCE_BYTES = 2048;
9
+ export function createPayloadValidators(ValidationError) {
10
+ const { requireIdentity, optionalIdentityOf, requireNonNegativeInteger } = createApplicationGuards(ValidationError), validateInlinePayload = async (payload, maxInlinePayloadBytes) => {
11
+ const value = Reflect.get(payload, "value");
12
+ let encoded;
13
+ try {
14
+ encoded = encode(value);
15
+ } catch (cause) {
16
+ throw new ValidationError("payload.value is not encodable by the structured-clone codec.", {
17
+ cause
18
+ });
19
+ }
20
+ if (encoded.byteLength > maxInlinePayloadBytes)
21
+ throw new ValidationError(`payload.value encodes to ${encoded.byteLength} bytes, over the ${maxInlinePayloadBytes}-byte inline ceiling. Store it behind a content-addressed reference instead.`);
22
+ const snapshot = decode(encoded);
23
+ try {
24
+ return {
25
+ payload: { form: "inline", value: snapshot },
26
+ digest: await computePayloadDigest(snapshot)
27
+ };
28
+ } catch (cause) {
29
+ if (cause instanceof PayloadDigestError)
30
+ throw new ValidationError(`payload.value cannot be digested: ${cause.message}`, { cause });
31
+ throw cause;
32
+ }
33
+ }, validateReferencePayload = (payload) => {
34
+ const reference = requireIdentity(Reflect.get(payload, "reference"), "payload.reference", MAX_APPLICATION_PAYLOAD_REFERENCE_BYTES), digest = Reflect.get(payload, "digest");
35
+ if (typeof digest !== "string" || !HEX_DIGEST_PATTERN.test(digest))
36
+ throw new ValidationError("payload.digest must be a 64-character lowercase hexadecimal SHA-256 digest. A reference payload has no other way to bind idempotency to payload identity.");
37
+ const rawByteLength = Reflect.get(payload, "byteLength");
38
+ if (rawByteLength === void 0)
39
+ return { payload: { form: "reference", reference, digest }, digest };
40
+ const referencedBytes = requireNonNegativeInteger(rawByteLength, "payload.byteLength", Number.MAX_SAFE_INTEGER);
41
+ return {
42
+ payload: { form: "reference", reference, digest, byteLength: referencedBytes },
43
+ digest
44
+ };
45
+ };
46
+ return { validatePayload: async (payload, maxInlinePayloadBytes) => {
47
+ if (typeof payload !== "object" || payload === null || !("form" in payload))
48
+ throw new ValidationError("payload must be an inline or reference payload object.");
49
+ const form = Reflect.get(payload, "form");
50
+ if (form === "inline")
51
+ return validateInlinePayload(payload, maxInlinePayloadBytes);
52
+ if (form !== "reference")
53
+ throw new ValidationError("payload.form must be 'inline' or 'reference'.");
54
+ return validateReferencePayload(payload);
55
+ }, validateCausation: (causation) => {
56
+ if (causation === void 0)
57
+ return;
58
+ if (typeof causation !== "object" || causation === null)
59
+ throw new ValidationError("causation must be an object when present.");
60
+ const correlationId = optionalIdentityOf(causation.correlationId, "causation.correlationId", MAX_APPLICATION_IDENTITY_BYTES), causationId = optionalIdentityOf(causation.causationId, "causation.causationId", MAX_APPLICATION_IDENTITY_BYTES), traceparent = optionalIdentityOf(causation.traceparent, "causation.traceparent", MAX_APPLICATION_IDENTITY_BYTES);
61
+ if (correlationId === void 0 && causationId === void 0 && traceparent === void 0)
62
+ return;
63
+ return { correlationId, causationId, traceparent };
64
+ } };
65
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Timing helpers shared by the durable application primitives (the command
3
+ * mailbox, WFT-84, and the delivery outbox, WFT-85): the deterministic retry
4
+ * backoff every reschedule uses, and the abortable sleep every polling wait
5
+ * uses.
6
+ *
7
+ * @module core/application-primitive-timing
8
+ */
9
+ /** Deterministic exponential backoff. No jitter, so redelivery timing is testable. */
10
+ export declare function computeRetryBackoffMs(attempt: number, baseMs: number, maximumMs: number): number;
11
+ /**
12
+ * Sleep, resolving `false` when the wait was aborted or the primitive disposed
13
+ * and `true` when the interval actually elapsed.
14
+ *
15
+ * The listeners are removed on every path, including the timer path, so a
16
+ * long-lived primitive does not accumulate abort listeners once per poll.
17
+ */
18
+ export declare function delayUnlessAborted(milliseconds: number, disposal: AbortSignal, signal?: AbortSignal): Promise<boolean>;
@@ -0,0 +1,22 @@
1
+ export function computeRetryBackoffMs(attempt, baseMs, maximumMs) {
2
+ const raw = baseMs * 2 ** Math.max(0, attempt - 1);
3
+ return Math.min(Number.isFinite(raw) ? raw : maximumMs, maximumMs);
4
+ }
5
+ export function delayUnlessAborted(milliseconds, disposal, signal) {
6
+ if (signal?.aborted === !0 || disposal.aborted)
7
+ return Promise.resolve(!1);
8
+ return new Promise((resolve) => {
9
+ const settle = (elapsed) => {
10
+ clearTimeout(timer);
11
+ signal?.removeEventListener("abort", onAbort);
12
+ disposal.removeEventListener("abort", onAbort);
13
+ resolve(elapsed);
14
+ }, onAbort = () => {
15
+ settle(!1);
16
+ }, timer = setTimeout(() => {
17
+ settle(!0);
18
+ }, milliseconds);
19
+ signal?.addEventListener("abort", onAbort, { once: !0 });
20
+ disposal.addEventListener("abort", onAbort, { once: !0 });
21
+ });
22
+ }
@@ -32,6 +32,8 @@ export function atomicStateDataKey(scope, key) {
32
32
  return KEYS.stateExecution(scope.ownerWorkflowId, key);
33
33
  case "workflow":
34
34
  return KEYS.stateWorkflow(scope.workflowType, key);
35
+ default:
36
+ throw Error(`Unknown atomic state scope type: ${JSON.stringify(scope)}`);
35
37
  }
36
38
  }
37
39
  function assertValidAtomicStateKey(key) {
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Encoding, decoding, and structural validation for durable catalog records.
3
+ *
4
+ * The active-pointer record (`catalog-active:<name>`) is the only catalog
5
+ * value whose bytes are read back and trusted for control-flow decisions
6
+ * (the CAS retry loop, generation comparisons) — so unlike the manifest
7
+ * entry (validated end-to-end by {@link parseWorkflowRevisionManifest}),
8
+ * it gets its own small structural validator here rather than round-tripping
9
+ * through a JSON-Schema-shaped parser.
10
+ *
11
+ * @module core/catalog/codec
12
+ */
13
+ import type { WorkflowRevisionManifest } from '../contract/types.ts';
14
+ import type { WorkflowCatalogActivePointer } from './types.ts';
15
+ /** Encode an active-pointer record for durable storage. */
16
+ export declare function encodeActivePointer(pointer: WorkflowCatalogActivePointer): Uint8Array;
17
+ /**
18
+ * Decode and structurally validate an active-pointer record read from
19
+ * durable storage. Returns `null` when the bytes do not decode as a valid
20
+ * `{ revision, generation, activatedAt }` record — the caller
21
+ * (`restoreWorkflowCatalog`) fails closed on `null` rather than treating it
22
+ * as absent, matching the codebase's fail-closed convention for corrupted
23
+ * durable singleton records (`ownership-mode-marker.ts`,
24
+ * `EngineLeaseCorruptedError`).
25
+ */
26
+ export declare function decodeActivePointer(bytes: Uint8Array): WorkflowCatalogActivePointer | null;
27
+ /**
28
+ * Deep-equality comparison between two workflow revision manifests, used by
29
+ * `WorkflowCatalog.install()` to decide whether a re-install for an existing
30
+ * `(name, revision)` key is a byte-identical no-op or a genuine metadata
31
+ * conflict. Compares each manifest's top-level identity scalars alongside
32
+ * its `contract` field's existing canonical serialization
33
+ * ({@link canonicalWorkflowContractJson}, `core/contract/normalize.ts`) —
34
+ * reusing that canonicalizer rather than a fresh `JSON.stringify` keeps this
35
+ * comparison immune to incidental object-key insertion-order differences
36
+ * inside `contract`, the same guarantee `contractHash()`/`revision` already
37
+ * rely on. A caller-supplied `revision` string that changed independently of
38
+ * contract content is also treated as a conflict, not a silent no-op.
39
+ */
40
+ export declare function manifestsAreByteIdentical(a: WorkflowRevisionManifest, b: WorkflowRevisionManifest): boolean;
@@ -0,0 +1,45 @@
1
+ import { decode, encode } from "../codec.js";
2
+ import { canonicalWorkflowContractJson } from "../contract/normalize.js";
3
+ export function encodeActivePointer(pointer) {
4
+ return encode(pointer);
5
+ }
6
+ export function decodeActivePointer(bytes) {
7
+ let decoded;
8
+ try {
9
+ decoded = decode(bytes);
10
+ } catch {
11
+ return null;
12
+ }
13
+ if (typeof decoded !== "object" || decoded === null)
14
+ return null;
15
+ return validateActivePointerRecord(decoded);
16
+ }
17
+ function validateActivePointerRecord(record) {
18
+ const { revision, generation, activatedAt } = record;
19
+ if (!isNonEmptyString(revision))
20
+ return null;
21
+ if (!isPositiveSafeInteger(generation))
22
+ return null;
23
+ if (typeof activatedAt !== "number" || !Number.isFinite(activatedAt))
24
+ return null;
25
+ return { revision, generation, activatedAt };
26
+ }
27
+ function isNonEmptyString(value) {
28
+ return typeof value === "string" && value.length > 0;
29
+ }
30
+ function isPositiveSafeInteger(value) {
31
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 1;
32
+ }
33
+ export function manifestsAreByteIdentical(a, b) {
34
+ return canonicalManifestJson(a) === canonicalManifestJson(b);
35
+ }
36
+ function canonicalManifestJson(manifest) {
37
+ return JSON.stringify({
38
+ manifestVersion: manifest.manifestVersion,
39
+ name: manifest.name,
40
+ workflowVersion: manifest.workflowVersion,
41
+ revision: manifest.revision,
42
+ contractHash: manifest.contractHash,
43
+ contract: canonicalWorkflowContractJson(manifest.contract)
44
+ });
45
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Error classes thrown by the durable workflow catalog (WFT-9/WFT-10) and
3
+ * its public promotion, `engine.workflows` (WFT-11).
4
+ *
5
+ * `WorkflowCatalogConflictError` and `WorkflowRevisionNotInstalledError` are
6
+ * public — part of `WeftErrorCode` and re-exported from `src/index.ts` —
7
+ * because `engine.workflows.install()`/`activate()` can throw either of them
8
+ * directly at a caller who holds no other way to reach the catalog.
9
+ * `WorkflowCatalogActivationConflictError` stays internal: it is reachable
10
+ * only through `engine.register()`'s unconditional `activateRegistered`
11
+ * path, which every public entry point already awaits via
12
+ * `ensureWorkflowCatalogReady` rather than calling directly, so no public
13
+ * caller ever observes it as a typed error to catch.
14
+ *
15
+ * @module core/catalog/errors
16
+ */
17
+ import { WeftError } from '../weft-error.ts';
18
+ /**
19
+ * Thrown by {@link import('./workflow-catalog.ts').WorkflowCatalog.install}
20
+ * when a caller attempts to install a `(name, revision)` pair that already
21
+ * has a durably- or in-memory-installed entry with different manifest bytes.
22
+ * A byte-identical reinstall for the same key is a no-op, not an error — see
23
+ * `install()`'s JSDoc.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * import { Engine, WorkflowCatalogConflictError } from '@lostgradient/weft';
28
+ *
29
+ * const engine = new Engine();
30
+ * try {
31
+ * throw new WorkflowCatalogConflictError('checkout', 'r1');
32
+ * } catch (error) {
33
+ * if (error instanceof WorkflowCatalogConflictError) {
34
+ * console.error(error.workflowName, error.revision);
35
+ * }
36
+ * }
37
+ * void engine;
38
+ * ```
39
+ */
40
+ export declare class WorkflowCatalogConflictError extends WeftError<'WorkflowCatalogConflictError'> {
41
+ readonly workflowName: string;
42
+ readonly revision: string;
43
+ constructor(workflowName: string, revision: string);
44
+ }
45
+ /**
46
+ * Thrown by
47
+ * {@link import('./workflow-catalog.ts').WorkflowCatalog.activateRegistered}
48
+ * after its bounded 5-attempt CAS retry loop exhausts without successfully
49
+ * committing the active pointer — mirrors `AtomicStateConflictError`'s
50
+ * exhaustion contract.
51
+ */
52
+ export declare class WorkflowCatalogActivationConflictError extends WeftError<'WorkflowCatalogActivationConflictError'> {
53
+ readonly workflowName: string;
54
+ readonly attempts: number;
55
+ constructor(workflowName: string, attempts: number);
56
+ }
57
+ /**
58
+ * Thrown by `engine.workflows.activate()` when `(workflowName, revision)`
59
+ * has never been installed — distinct from
60
+ * `WorkflowCatalog.activateCandidate`'s own `applied: false` refusal
61
+ * variants, which all assume the candidate IS installed and instead
62
+ * describe why activating it was refused.
63
+ *
64
+ * @example
65
+ * ```ts
66
+ * import { Engine, WorkflowRevisionNotInstalledError } from '@lostgradient/weft';
67
+ *
68
+ * const engine = new Engine();
69
+ * try {
70
+ * await engine.workflows.activate('checkout', 'unknown-revision');
71
+ * } catch (error) {
72
+ * if (error instanceof WorkflowRevisionNotInstalledError) {
73
+ * console.error(error.workflowName, error.revision);
74
+ * }
75
+ * }
76
+ * ```
77
+ */
78
+ export declare class WorkflowRevisionNotInstalledError extends WeftError<'WorkflowRevisionNotInstalledError'> {
79
+ readonly workflowName: string;
80
+ readonly revision: string;
81
+ constructor(workflowName: string, revision: string);
82
+ }
83
+ /**
84
+ * Thrown by
85
+ * {@link import('./workflow-catalog.ts').WorkflowCatalog.activateCandidate}
86
+ * when the durable active pointer for `name` names a revision whose catalog
87
+ * entry cannot be resolved — neither this process's in-memory cache nor a
88
+ * durable read-through finds it. This should be unreachable in practice:
89
+ * both activation entry points (`install()`-then-activate) always durably
90
+ * install a revision before ever pointing the active pointer at it, so a
91
+ * durably-active revision missing its own entry is a genuine storage
92
+ * inconsistency, not a race any caller can retry past. Fail-closed rather
93
+ * than silently skipping the compatibility check `activateCandidate`
94
+ * exists to enforce — see `#refuseIncompatibleCandidate`'s JSDoc. Stays
95
+ * internal (not re-exported from `src/index.ts`) like
96
+ * {@link WorkflowCatalogActivationConflictError}: no code path lets a public
97
+ * caller observe it as a typed error to catch, only as a generic
98
+ * `EngineFailure`-wrapped operation fault.
99
+ */
100
+ export declare class WorkflowCatalogActiveEntryMissingError extends WeftError<'WorkflowCatalogActiveEntryMissingError'> {
101
+ readonly workflowName: string;
102
+ readonly revision: string;
103
+ constructor(workflowName: string, revision: string);
104
+ }
@@ -0,0 +1,41 @@
1
+ import { WeftError } from "../weft-error.js";
2
+
3
+ export class WorkflowCatalogConflictError extends WeftError {
4
+ workflowName;
5
+ revision;
6
+ constructor(workflowName, revision) {
7
+ super("WorkflowCatalogConflictError", `Workflow catalog entry "${workflowName}" revision "${revision}" is already installed with ` + "different contract metadata. A revision identity must be immutable once installed \u2014 " + "register a workflow whose contract content differs under a new revision instead of reusing this one.");
8
+ this.workflowName = workflowName;
9
+ this.revision = revision;
10
+ }
11
+ }
12
+
13
+ export class WorkflowCatalogActivationConflictError extends WeftError {
14
+ workflowName;
15
+ attempts;
16
+ constructor(workflowName, attempts) {
17
+ super("WorkflowCatalogActivationConflictError", `Workflow catalog activation conflict: failed to activate "${workflowName}" after ${String(attempts)} attempts due to sustained concurrent writers.`);
18
+ this.workflowName = workflowName;
19
+ this.attempts = attempts;
20
+ }
21
+ }
22
+
23
+ export class WorkflowRevisionNotInstalledError extends WeftError {
24
+ workflowName;
25
+ revision;
26
+ constructor(workflowName, revision) {
27
+ super("WorkflowRevisionNotInstalledError", `Workflow "${workflowName}" has no installed revision "${revision}" \u2014 install it via ` + "engine.workflows.install() before activating it.");
28
+ this.workflowName = workflowName;
29
+ this.revision = revision;
30
+ }
31
+ }
32
+
33
+ export class WorkflowCatalogActiveEntryMissingError extends WeftError {
34
+ workflowName;
35
+ revision;
36
+ constructor(workflowName, revision) {
37
+ super("WorkflowCatalogActiveEntryMissingError", `Workflow catalog inconsistency: "${workflowName}" is durably active at revision "${revision}", but that revision's catalog entry cannot be resolved (neither in-memory nor durably). Refusing to activate a candidate without being able to check compatibility against the currently active revision.`);
38
+ this.workflowName = workflowName;
39
+ this.revision = revision;
40
+ }
41
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Barrel for the durable workflow catalog (WFT-9/WFT-10, extended with
3
+ * reference accounting and removal in WFT-12).
4
+ *
5
+ * `WorkflowCatalog` itself and `WorkflowCatalogEntry` (which can carry a
6
+ * live `RegisteredWorkflowDefinition` function reference) stay
7
+ * package-internal — `core/engine/catalog-readiness.ts`,
8
+ * `core/engine/catalog-activation.ts`, `core/engine/catalog-removal.ts`, and
9
+ * `core/registry-snapshot.ts` are their consumers. Two different public
10
+ * surfaces are built on top: WFT-11's `engine.workflows`
11
+ * (`core/engine/engine-workflows-namespace.ts`), which is why
12
+ * `WorkflowCatalogConflictError`, `WorkflowRevisionNotInstalledError`,
13
+ * `WorkflowCatalogActivationResult`, `WorkflowCatalogActivePointer`, and
14
+ * `WorkflowRevisionRecord` ARE re-exported from `src/index.ts`; and WFT-12's
15
+ * `removeWorkflowRevision`/`getWorkflowRevisionDiagnostics`
16
+ * (`core/engine/catalog-removal.ts`).
17
+ *
18
+ * @module core/catalog
19
+ */
20
+ export { WorkflowCatalogActivationConflictError, WorkflowCatalogActiveEntryMissingError, WorkflowCatalogConflictError, WorkflowRevisionNotInstalledError, } from './errors.ts';
21
+ export { decrementNestedRevisionCount, incrementNestedRevisionCount, readNestedRevisionCount, totalWorkflowRevisionReferences, type WorkflowRevisionReferenceCounts, } from './reference-counts.ts';
22
+ export { removeCatalogEntry, type WorkflowCatalogRemovalOutcome } from './removal.ts';
23
+ export { restoreWorkflowCatalog, type RestoredWorkflowCatalogState } from './storage-io.ts';
24
+ export type { WorkflowCatalogActivationResult, WorkflowCatalogActivePointer, WorkflowCatalogEntry, WorkflowRevisionRecord, } from './types.ts';
25
+ export { WorkflowCatalog, type ActivateCandidateOptions } from './workflow-catalog.ts';
@@ -0,0 +1,15 @@
1
+ export {
2
+ WorkflowCatalogActivationConflictError,
3
+ WorkflowCatalogActiveEntryMissingError,
4
+ WorkflowCatalogConflictError,
5
+ WorkflowRevisionNotInstalledError
6
+ } from "./errors.js";
7
+ export {
8
+ decrementNestedRevisionCount,
9
+ incrementNestedRevisionCount,
10
+ readNestedRevisionCount,
11
+ totalWorkflowRevisionReferences
12
+ } from "./reference-counts.js";
13
+ export { removeCatalogEntry } from "./removal.js";
14
+ export { restoreWorkflowCatalog } from "./storage-io.js";
15
+ export { WorkflowCatalog } from "./workflow-catalog.js";
@@ -0,0 +1,105 @@
1
+ /**
2
+ * `WorkflowRevisionReferenceCounts` — the bounded accounting interface a
3
+ * `(name, revision)` removal decision is gated on (WFT-12).
4
+ *
5
+ * Seven fields, all always present so a consumer never has to special-case
6
+ * an "unknown" reference kind. Two are wired to real in-process signals this
7
+ * batch; the remaining five stay structurally present but always `0` until
8
+ * run-level revision pinning lands (WFT-17) gives them something real to
9
+ * count — see each field's own doc for its specific WFT-17 dependency. This
10
+ * mirrors `workflow-catalog.ts`'s own precedent of describing a forward
11
+ * dependency in prose rather than leaving a `TODO`/`FIXME` marker.
12
+ *
13
+ * Keyed by structured `(name, revision)` throughout — nested
14
+ * `Map<string, Map<string, number>>`, never a delimiter-joined string — so a
15
+ * workflow `name` or `revision` equal to `'__proto__'`/`'toString'` or
16
+ * containing a colon is always handled correctly, matching
17
+ * `WorkflowCatalog`'s own `#entries` convention.
18
+ *
19
+ * @module core/catalog/reference-counts
20
+ */
21
+ /**
22
+ * Every reference kind a `(name, revision)` removal decision considers.
23
+ * Removal is refused whenever {@link totalWorkflowRevisionReferences} is
24
+ * nonzero for the target revision.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * import type { WorkflowRevisionReferenceCounts } from '@lostgradient/weft';
29
+ *
30
+ * function summarize(counts: WorkflowRevisionReferenceCounts): string {
31
+ * return `${counts.registeredDefinitions} registered, ${counts.inFlightStarts} in flight`;
32
+ * }
33
+ * void summarize;
34
+ * ```
35
+ */
36
+ export type WorkflowRevisionReferenceCounts = Readonly<{
37
+ /**
38
+ * `1` when this process's own `engine.register()`-drain path most
39
+ * recently activated exactly this revision for this name, `0` otherwise.
40
+ * Wired now, from `EngineInternals.registeredCatalogRevisions`.
41
+ */
42
+ registeredDefinitions: number;
43
+ /**
44
+ * Count of this process's own in-flight `startWorkflow` calls reserved
45
+ * against this revision (from `EngineInternals.inFlightStartsByRevision`).
46
+ * Wired now; the reservation lives inside `lifecycle/start.ts`'s single
47
+ * `startWorkflow` choke point itself, so every caller that funnels through
48
+ * it is already counted — `engine.start()`/`engine.startOrSignal()`'s
49
+ * create path, and `ctx.startChild()` too, since
50
+ * `createChildWorkflowOperationCallbacks` (`callback-creators-bundles.ts`)
51
+ * calls the very same `startWorkflow`. There is no separate bulk
52
+ * `startBatch()` entry point to funnel — `buildStartBatchOperations` is
53
+ * internal plumbing already inside this same `startWorkflow` call,
54
+ * building one start's own storage-write batch, not a distinct
55
+ * multi-start API.
56
+ */
57
+ inFlightStarts: number;
58
+ /**
59
+ * Non-terminal runs whose `WorkflowState` pins exactly this revision.
60
+ * Always `0` until run-level revision pinning (WFT-17) gives a run's
61
+ * persisted state a `revision` field to count against.
62
+ */
63
+ nonTerminalRuns: number;
64
+ /**
65
+ * Schedules pinned to exactly this revision. Always `0` until WFT-17
66
+ * introduces schedule-level revision pinning.
67
+ */
68
+ pinnedSchedules: number;
69
+ /**
70
+ * Queued dispatches (delayed starts, retries) targeting exactly this
71
+ * revision. Always `0` until WFT-17 threads revision identity through the
72
+ * dispatch ledger.
73
+ */
74
+ pendingDispatches: number;
75
+ /**
76
+ * Active execution realms (remote worker sessions) currently running
77
+ * exactly this revision. Always `0` until WFT-17 gives a realm's
78
+ * advertised contract a revision this accounting can compare against.
79
+ */
80
+ activeExecutionRealms: number;
81
+ /**
82
+ * Retained recovery records (crash-recovery checkpoints, dead letters)
83
+ * referencing exactly this revision. Always `0` until WFT-17 threads
84
+ * revision identity through those retained records.
85
+ */
86
+ retainedRecoveryRecords: number;
87
+ }>;
88
+ /** Sum every field of `counts` — nonzero means removal must be refused. */
89
+ export declare function totalWorkflowRevisionReferences(counts: WorkflowRevisionReferenceCounts): number;
90
+ /**
91
+ * Increment the `(name, revision)` count in a nested
92
+ * `Map<string, Map<string, number>>`, creating the inner map and the entry
93
+ * as needed.
94
+ */
95
+ export declare function incrementNestedRevisionCount(counts: Map<string, Map<string, number>>, name: string, revision: string): void;
96
+ /**
97
+ * Decrement the `(name, revision)` count in a nested
98
+ * `Map<string, Map<string, number>>`. Removes the revision's own entry once
99
+ * it reaches `0` (rather than leaving a stale `0` behind) and removes the
100
+ * name's inner map once it is empty. A decrement against an absent
101
+ * `(name, revision)` is a no-op — never goes negative.
102
+ */
103
+ export declare function decrementNestedRevisionCount(counts: Map<string, Map<string, number>>, name: string, revision: string): void;
104
+ /** Read the `(name, revision)` count from a nested map, `0` when absent. */
105
+ export declare function readNestedRevisionCount(counts: Map<string, Map<string, number>>, name: string, revision: string): number;
@@ -0,0 +1,28 @@
1
+ export function totalWorkflowRevisionReferences(counts) {
2
+ return counts.registeredDefinitions + counts.inFlightStarts + counts.nonTerminalRuns + counts.pinnedSchedules + counts.pendingDispatches + counts.activeExecutionRealms + counts.retainedRecoveryRecords;
3
+ }
4
+ export function incrementNestedRevisionCount(counts, name, revision) {
5
+ let byRevision = counts.get(name);
6
+ if (byRevision === void 0) {
7
+ byRevision = new Map;
8
+ counts.set(name, byRevision);
9
+ }
10
+ byRevision.set(revision, (byRevision.get(revision) ?? 0) + 1);
11
+ }
12
+ export function decrementNestedRevisionCount(counts, name, revision) {
13
+ const byRevision = counts.get(name);
14
+ if (byRevision === void 0)
15
+ return;
16
+ const current = byRevision.get(revision);
17
+ if (current === void 0)
18
+ return;
19
+ if (current <= 1) {
20
+ byRevision.delete(revision);
21
+ if (byRevision.size === 0)
22
+ counts.delete(name);
23
+ } else
24
+ byRevision.set(revision, current - 1);
25
+ }
26
+ export function readNestedRevisionCount(counts, name, revision) {
27
+ return counts.get(name)?.get(revision) ?? 0;
28
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * The mechanical CAS-delete primitive `WorkflowCatalog.remove()` delegates
3
+ * to. Split into its own module to protect `workflow-catalog.ts`'s size
4
+ * against the repository's 500-line implementation-file ceiling — mirrors
5
+ * how `storage-io.ts`/`codec.ts` already sit alongside `workflow-catalog.ts`
6
+ * rather than inline in it.
7
+ *
8
+ * Fences the delete on BOTH the exact entry bytes read AND the exact active
9
+ * pointer bytes read, in one `conditionalBatch` — not entry bytes alone.
10
+ * Between an in-memory "is this the active revision" check and the delete,
11
+ * a concurrent `activateCandidate`/`activateRegistered` call (this process
12
+ * or another sharing the same durable store) could make the target revision
13
+ * active; fencing the active-pointer key too means that race loses the CAS
14
+ * and surfaces as `'conflict'` (the caller re-decides) instead of silently
15
+ * deleting a revision that became active a moment before the delete landed.
16
+ *
17
+ * Single-shot — no retry loop, matching `activateCandidate`'s own
18
+ * no-retry-caller-decides precedent, since a removal decision (unlike
19
+ * `activateRegistered`'s unconditional retry) already depends on reference
20
+ * counts computed by the caller and should not blindly re-attempt against
21
+ * possibly-stale counts.
22
+ *
23
+ * @module core/catalog/removal
24
+ */
25
+ import { type Storage } from '../../storage/interface.ts';
26
+ /** Outcome of {@link removeCatalogEntry}. */
27
+ export type WorkflowCatalogRemovalOutcome = Readonly<{
28
+ outcome: 'removed';
29
+ }> | Readonly<{
30
+ outcome: 'not-found';
31
+ }> | Readonly<{
32
+ outcome: 'active';
33
+ activeRevision: string;
34
+ }> | Readonly<{
35
+ outcome: 'conflict';
36
+ }>;
37
+ /**
38
+ * Durably delete the installed-revision record for `(name, revision)`.
39
+ *
40
+ * Reads the current `catalog-entry:<name>:<revision>` bytes; `null` is
41
+ * `'not-found'` (a no-op — the caller may already believe the revision is
42
+ * gone). Reads the current `catalog-active:<name>` bytes and refuses with
43
+ * `'active'` when they decode to exactly this revision — a structural
44
+ * invariant independent of reference counts, since every future or
45
+ * resuming run resolves the active pointer, not a specific installed
46
+ * entry. Otherwise deletes the entry key via a `conditionalBatch` CAS'd on
47
+ * BOTH the exact entry bytes AND the exact active-pointer bytes read above
48
+ * (present as a no-op `put`-free precondition when active-pointer bytes
49
+ * are `null` — never activated). A CAS loss (either key changed
50
+ * concurrently) surfaces as `'conflict'`.
51
+ */
52
+ export declare function removeCatalogEntry(storage: Storage, name: string, revision: string): Promise<WorkflowCatalogRemovalOutcome>;
@@ -0,0 +1,14 @@
1
+ import { KEYS, storageConditionalBatch } from "../../storage/interface.js";
2
+ import { decodeActivePointer } from "./codec.js";
3
+ export async function removeCatalogEntry(storage, name, revision) {
4
+ const entryKey = KEYS.catalogEntry(name, revision), entryBytes = await storage.get(entryKey);
5
+ if (entryBytes === null)
6
+ return { outcome: "not-found" };
7
+ const activeKey = KEYS.catalogActive(name), activeBytes = await storage.get(activeKey), activePointer = activeBytes === null ? null : decodeActivePointer(activeBytes);
8
+ if (activePointer !== null && activePointer.revision === revision)
9
+ return { outcome: "active", activeRevision: activePointer.revision };
10
+ return await storageConditionalBatch(storage, [
11
+ { key: entryKey, expectedValue: entryBytes },
12
+ { key: activeKey, expectedValue: activeBytes }
13
+ ], [{ type: "delete", key: entryKey }]) ? { outcome: "removed" } : { outcome: "conflict" };
14
+ }