@ferricstore/ferricstore 0.11.11 → 0.12.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 (52) hide show
  1. package/README.md +16 -1
  2. package/dist/durability-DlDCsdlo.d.cts +16 -0
  3. package/dist/durability-DplL0SbW.d.ts +16 -0
  4. package/dist/index.cjs +1 -1
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.d.cts +5 -162
  7. package/dist/index.d.ts +5 -162
  8. package/dist/index.js +1 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/internal-lEEDZpPH.d.cts +4 -0
  11. package/dist/internal-lEEDZpPH.d.ts +4 -0
  12. package/dist/langgraph.cjs +1278 -0
  13. package/dist/langgraph.cjs.map +1 -0
  14. package/dist/langgraph.d.cts +148 -0
  15. package/dist/langgraph.d.ts +148 -0
  16. package/dist/langgraph.js +1252 -0
  17. package/dist/langgraph.js.map +1 -0
  18. package/dist/openai-agents.cjs +580 -0
  19. package/dist/openai-agents.cjs.map +1 -0
  20. package/dist/openai-agents.d.cts +44 -0
  21. package/dist/openai-agents.d.ts +44 -0
  22. package/dist/openai-agents.js +555 -0
  23. package/dist/openai-agents.js.map +1 -0
  24. package/dist/outcomes-BbFDp3AH.d.ts +160 -0
  25. package/dist/outcomes-DmBwnq0Y.d.cts +160 -0
  26. package/docs/agent-frameworks.md +159 -0
  27. package/docs/api/assets/highlight.css +12 -12
  28. package/docs/api/classes/ClaimHydrationError.html +2 -2
  29. package/docs/api/classes/ConnectionClosedError.html +2 -2
  30. package/docs/api/classes/FerricStoreError.html +2 -2
  31. package/docs/api/classes/FlowAlreadyExistsError.html +2 -2
  32. package/docs/api/classes/FlowBatchError.html +2 -2
  33. package/docs/api/classes/FlowNotFoundError.html +2 -2
  34. package/docs/api/classes/FlowQueryError.html +2 -2
  35. package/docs/api/classes/FlowWrongStateError.html +2 -2
  36. package/docs/api/classes/HTTPTransportError.html +2 -2
  37. package/docs/api/classes/InvalidCommandError.html +2 -2
  38. package/docs/api/classes/LeaseRenewalError.html +2 -2
  39. package/docs/api/classes/LockHeldError.html +2 -2
  40. package/docs/api/classes/LockNotOwnedError.html +2 -2
  41. package/docs/api/classes/OverloadedError.html +2 -2
  42. package/docs/api/classes/QueueCompletionError.html +2 -2
  43. package/docs/api/classes/RequestTimeoutError.html +2 -2
  44. package/docs/api/classes/RerouteError.html +2 -2
  45. package/docs/api/classes/StaleLeaseError.html +2 -2
  46. package/docs/api/classes/StalePolicyGenerationError.html +2 -2
  47. package/docs/api/index.html +34 -24
  48. package/docs/api/media/agent-frameworks.md +159 -0
  49. package/docs/api/media/langgraph.ts +23 -0
  50. package/docs/api/media/openai-agents-session.ts +13 -0
  51. package/docs/api/variables/FERRICSTORE_SDK_VERSION.html +1 -1
  52. package/package.json +41 -2
@@ -19,8 +19,16 @@
19
19
  </code><button type="button">Copy</button></pre>
20
20
 
21
21
  <p>Requires Node.js 22.22 or newer. The SDK ships ESM and CommonJS builds and is tested with Node 22, 24, and 26.</p>
22
+ <p>Agent framework adapters are optional:</p>
23
+ <pre><code class="bash"><span class="hl-0">npm</span><span class="hl-1"> </span><span class="hl-2">install</span><span class="hl-1"> </span><span class="hl-2">@ferricstore/ferricstore</span><span class="hl-1"> </span><span class="hl-2">@langchain/langgraph</span><span class="hl-1"> </span><span class="hl-2">@langchain/core</span><br/><span class="hl-3"># or</span><br/><span class="hl-0">npm</span><span class="hl-1"> </span><span class="hl-2">install</span><span class="hl-1"> </span><span class="hl-2">@ferricstore/ferricstore</span><span class="hl-1"> </span><span class="hl-2">@openai/agents</span>
24
+ </code><button type="button">Copy</button></pre>
25
+
26
+ <p>Use <code>@ferricstore/ferricstore/langgraph</code> for a LangGraph.js checkpointer,
27
+ long-term <code>BaseStore</code>, and FerricFlow handler bridge. Use
28
+ <code>@ferricstore/ferricstore/openai-agents</code> for an atomic, idempotent OpenAI
29
+ Agents SDK <code>Session</code>. See <a href="media/agent-frameworks.md">docs/agent-frameworks.md</a>.</p>
22
30
  <h2 id="compatibility" class="tsd-anchor-link">Compatibility<a href="#compatibility" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h2>
23
- <p>TypeScript SDK <code>0.11.11</code> requires FerricStore server <code>0.11.4</code> or newer. With
31
+ <p>TypeScript SDK <code>0.12.0</code> requires FerricStore server <code>0.11.4</code> or newer. With
24
32
  FerricStore 0.11.11 it negotiates compact Stream mode 34 for homogeneous auto-ID
25
33
  <code>XADD</code> pipelines and compact Pub/Sub mode 35 for homogeneous <code>PUBLISH</code>
26
34
  pipelines. Native wire protocol v1 and the generic fallback are unchanged.
@@ -28,21 +36,21 @@ Capabilities and response-size limits are negotiated
28
36
  per connection from the HELLO-shaped startup response rather than inferred from
29
37
  a server version table.</p>
30
38
  <p>ESM:</p>
31
- <pre><code class="ts"><span class="hl-3">import</span><span class="hl-1"> { </span><span class="hl-4">FerricStoreClient</span><span class="hl-1">, </span><span class="hl-4">JsonCodec</span><span class="hl-1"> } </span><span class="hl-3">from</span><span class="hl-1"> </span><span class="hl-2">&quot;@ferricstore/ferricstore&quot;</span><span class="hl-1">;</span>
39
+ <pre><code class="ts"><span class="hl-4">import</span><span class="hl-1"> { </span><span class="hl-5">FerricStoreClient</span><span class="hl-1">, </span><span class="hl-5">JsonCodec</span><span class="hl-1"> } </span><span class="hl-4">from</span><span class="hl-1"> </span><span class="hl-2">&quot;@ferricstore/ferricstore&quot;</span><span class="hl-1">;</span>
32
40
  </code><button type="button">Copy</button></pre>
33
41
 
34
42
  <p>CommonJS:</p>
35
- <pre><code class="js"><span class="hl-5">const</span><span class="hl-1"> { </span><span class="hl-6">FerricStoreClient</span><span class="hl-1">, </span><span class="hl-6">JsonCodec</span><span class="hl-1"> } = </span><span class="hl-0">require</span><span class="hl-1">(</span><span class="hl-2">&quot;@ferricstore/ferricstore&quot;</span><span class="hl-1">);</span>
43
+ <pre><code class="js"><span class="hl-6">const</span><span class="hl-1"> { </span><span class="hl-7">FerricStoreClient</span><span class="hl-1">, </span><span class="hl-7">JsonCodec</span><span class="hl-1"> } = </span><span class="hl-0">require</span><span class="hl-1">(</span><span class="hl-2">&quot;@ferricstore/ferricstore&quot;</span><span class="hl-1">);</span>
36
44
  </code><button type="button">Copy</button></pre>
37
45
 
38
46
  <h2 id="run-ferricstore-locally" class="tsd-anchor-link">Run FerricStore Locally<a href="#run-ferricstore-locally" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h2>
39
- <pre><code class="bash"><span class="hl-0">docker</span><span class="hl-1"> </span><span class="hl-2">run</span><span class="hl-1"> </span><span class="hl-5">-p</span><span class="hl-1"> </span><span class="hl-2">6388:6388</span><span class="hl-1"> </span><span class="hl-7">\</span><br/><span class="hl-1"> </span><span class="hl-5">-e</span><span class="hl-1"> </span><span class="hl-2">FERRICSTORE_PROTECTED_MODE=</span><span class="hl-5">false</span><span class="hl-1"> </span><span class="hl-7">\</span><br/><span class="hl-1"> </span><span class="hl-5">-v</span><span class="hl-1"> </span><span class="hl-2">ferricstore_data:/data</span><span class="hl-1"> </span><span class="hl-7">\</span><br/><span class="hl-1"> </span><span class="hl-2">quay.io/ferricstore/ferricstore:0.11.11@sha256:d9f488539f0d6c1a513d2315e7a9c2947cc795b393f3774c9de8ba5e5b5c21b5</span>
47
+ <pre><code class="bash"><span class="hl-0">docker</span><span class="hl-1"> </span><span class="hl-2">run</span><span class="hl-1"> </span><span class="hl-6">-p</span><span class="hl-1"> </span><span class="hl-2">6388:6388</span><span class="hl-1"> </span><span class="hl-8">\</span><br/><span class="hl-1"> </span><span class="hl-6">-e</span><span class="hl-1"> </span><span class="hl-2">FERRICSTORE_PROTECTED_MODE=</span><span class="hl-6">false</span><span class="hl-1"> </span><span class="hl-8">\</span><br/><span class="hl-1"> </span><span class="hl-6">-v</span><span class="hl-1"> </span><span class="hl-2">ferricstore_data:/data</span><span class="hl-1"> </span><span class="hl-8">\</span><br/><span class="hl-1"> </span><span class="hl-2">quay.io/ferricstore/ferricstore:0.11.11@sha256:d9f488539f0d6c1a513d2315e7a9c2947cc795b393f3774c9de8ba5e5b5c21b5</span>
40
48
  </code><button type="button">Copy</button></pre>
41
49
 
42
50
  <h2 id="query-durable-runs" class="tsd-anchor-link">Query durable runs<a href="#query-durable-runs" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h2>
43
51
  <p>Use parameterized FQL for bounded, partition-scoped reads. Cursors are opaque
44
52
  and must be reused with the same query and parameters.</p>
45
- <pre><code class="ts"><span class="hl-5">const</span><span class="hl-1"> </span><span class="hl-6">client</span><span class="hl-1"> = </span><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">FerricStoreClient</span><span class="hl-1">.</span><span class="hl-0">fromUrl</span><span class="hl-1">(</span><span class="hl-2">&quot;ferric://127.0.0.1:6388&quot;</span><span class="hl-1">);</span><br/><span class="hl-5">const</span><span class="hl-1"> </span><span class="hl-6">query</span><span class="hl-1"> = </span><span class="hl-2">`FROM runs</span><br/><span class="hl-2">WHERE partition_key = @partition AND type = @type AND state = @state</span><br/><span class="hl-2">ORDER BY updated_at_ms ASC LIMIT 25 RETURN RECORDS`</span><span class="hl-1">;</span><br/><span class="hl-5">const</span><span class="hl-1"> </span><span class="hl-6">params</span><span class="hl-1"> = { </span><span class="hl-4">partition:</span><span class="hl-1"> </span><span class="hl-2">&quot;partition-a&quot;</span><span class="hl-1">, </span><span class="hl-4">type:</span><span class="hl-1"> </span><span class="hl-2">&quot;invoice&quot;</span><span class="hl-1">, </span><span class="hl-4">state:</span><span class="hl-1"> </span><span class="hl-2">&quot;queued&quot;</span><span class="hl-1"> };</span><br/><br/><span class="hl-5">const</span><span class="hl-1"> </span><span class="hl-6">result</span><span class="hl-1"> = </span><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">client</span><span class="hl-1">.</span><span class="hl-0">query</span><span class="hl-1">(</span><span class="hl-4">query</span><span class="hl-1">, </span><span class="hl-4">params</span><span class="hl-1">);</span><br/><span class="hl-5">const</span><span class="hl-1"> </span><span class="hl-6">plan</span><span class="hl-1"> = </span><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">client</span><span class="hl-1">.</span><span class="hl-0">explain</span><span class="hl-1">(</span><span class="hl-4">query</span><span class="hl-1">, </span><span class="hl-4">params</span><span class="hl-1">);</span><br/><span class="hl-5">const</span><span class="hl-1"> </span><span class="hl-6">indexes</span><span class="hl-1"> = </span><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">client</span><span class="hl-1">.</span><span class="hl-0">queryIndexes</span><span class="hl-1">();</span>
53
+ <pre><code class="ts"><span class="hl-6">const</span><span class="hl-1"> </span><span class="hl-7">client</span><span class="hl-1"> = </span><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">FerricStoreClient</span><span class="hl-1">.</span><span class="hl-0">fromUrl</span><span class="hl-1">(</span><span class="hl-2">&quot;ferric://127.0.0.1:6388&quot;</span><span class="hl-1">);</span><br/><span class="hl-6">const</span><span class="hl-1"> </span><span class="hl-7">query</span><span class="hl-1"> = </span><span class="hl-2">`FROM runs</span><br/><span class="hl-2">WHERE partition_key = @partition AND type = @type AND state = @state</span><br/><span class="hl-2">ORDER BY updated_at_ms ASC LIMIT 25 RETURN RECORDS`</span><span class="hl-1">;</span><br/><span class="hl-6">const</span><span class="hl-1"> </span><span class="hl-7">params</span><span class="hl-1"> = { </span><span class="hl-5">partition:</span><span class="hl-1"> </span><span class="hl-2">&quot;partition-a&quot;</span><span class="hl-1">, </span><span class="hl-5">type:</span><span class="hl-1"> </span><span class="hl-2">&quot;invoice&quot;</span><span class="hl-1">, </span><span class="hl-5">state:</span><span class="hl-1"> </span><span class="hl-2">&quot;queued&quot;</span><span class="hl-1"> };</span><br/><br/><span class="hl-6">const</span><span class="hl-1"> </span><span class="hl-7">result</span><span class="hl-1"> = </span><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">client</span><span class="hl-1">.</span><span class="hl-0">query</span><span class="hl-1">(</span><span class="hl-5">query</span><span class="hl-1">, </span><span class="hl-5">params</span><span class="hl-1">);</span><br/><span class="hl-6">const</span><span class="hl-1"> </span><span class="hl-7">plan</span><span class="hl-1"> = </span><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">client</span><span class="hl-1">.</span><span class="hl-0">explain</span><span class="hl-1">(</span><span class="hl-5">query</span><span class="hl-1">, </span><span class="hl-5">params</span><span class="hl-1">);</span><br/><span class="hl-6">const</span><span class="hl-1"> </span><span class="hl-7">indexes</span><span class="hl-1"> = </span><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">client</span><span class="hl-1">.</span><span class="hl-0">queryIndexes</span><span class="hl-1">();</span>
46
54
  </code><button type="button">Copy</button></pre>
47
55
 
48
56
  <p>Each index reports <code>coveringFields</code>, which identifies the built-in and dynamic
@@ -57,14 +65,14 @@ complete public record. Projection runs after authorization, authoritative
57
65
  recheck, ordering, and cursor calculation: it reduces retained result data,
58
66
  encoding, network, and client decoding work, but not index scans or hydration.</p>
59
67
  <p>Use the source-aware builder to avoid hand-quoting result selectors:</p>
60
- <pre><code class="ts"><span class="hl-5">const</span><span class="hl-1"> </span><span class="hl-6">projected</span><span class="hl-1"> = </span><span class="hl-0">projectFlowQuery</span><span class="hl-1">(</span><br/><span class="hl-1"> </span><span class="hl-2">&quot;FROM runs WHERE partition_key = @partition AND run_id = @run&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">&quot;record&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">FlowProjection</span><span class="hl-1">.</span><span class="hl-4">run</span><span class="hl-1">.</span><span class="hl-4">id</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">FlowProjection</span><span class="hl-1">.</span><span class="hl-4">run</span><span class="hl-1">.</span><span class="hl-4">state</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">FlowProjection</span><span class="hl-1">.</span><span class="hl-4">run</span><span class="hl-1">.</span><span class="hl-0">attribute</span><span class="hl-1">(</span><span class="hl-2">&quot;customer&quot;</span><span class="hl-1">)</span><br/><span class="hl-1">);</span><br/><span class="hl-5">const</span><span class="hl-1"> </span><span class="hl-6">result</span><span class="hl-1"> = </span><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">client</span><span class="hl-1">.</span><span class="hl-0">query</span><span class="hl-1">(</span><span class="hl-4">projected</span><span class="hl-1">, { </span><span class="hl-4">partition:</span><span class="hl-1"> </span><span class="hl-2">&quot;partition-a&quot;</span><span class="hl-1">, </span><span class="hl-4">run:</span><span class="hl-1"> </span><span class="hl-2">&quot;run-1&quot;</span><span class="hl-1"> });</span>
68
+ <pre><code class="ts"><span class="hl-6">const</span><span class="hl-1"> </span><span class="hl-7">projected</span><span class="hl-1"> = </span><span class="hl-0">projectFlowQuery</span><span class="hl-1">(</span><br/><span class="hl-1"> </span><span class="hl-2">&quot;FROM runs WHERE partition_key = @partition AND run_id = @run&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">&quot;record&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">FlowProjection</span><span class="hl-1">.</span><span class="hl-5">run</span><span class="hl-1">.</span><span class="hl-5">id</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">FlowProjection</span><span class="hl-1">.</span><span class="hl-5">run</span><span class="hl-1">.</span><span class="hl-5">state</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">FlowProjection</span><span class="hl-1">.</span><span class="hl-5">run</span><span class="hl-1">.</span><span class="hl-0">attribute</span><span class="hl-1">(</span><span class="hl-2">&quot;customer&quot;</span><span class="hl-1">)</span><br/><span class="hl-1">);</span><br/><span class="hl-6">const</span><span class="hl-1"> </span><span class="hl-7">result</span><span class="hl-1"> = </span><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">client</span><span class="hl-1">.</span><span class="hl-0">query</span><span class="hl-1">(</span><span class="hl-5">projected</span><span class="hl-1">, { </span><span class="hl-5">partition:</span><span class="hl-1"> </span><span class="hl-2">&quot;partition-a&quot;</span><span class="hl-1">, </span><span class="hl-5">run:</span><span class="hl-1"> </span><span class="hl-2">&quot;run-1&quot;</span><span class="hl-1"> });</span>
61
69
  </code><button type="button">Copy</button></pre>
62
70
 
63
71
  <h2 id="http-transport" class="tsd-anchor-link">HTTP transport<a href="#http-transport" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h2>
64
72
  <p><code>fromUrl</code> accepts <code>http://</code> and <code>https://</code> without changing the command API.
65
73
  HTTP/1.1 uses a persistent keep-alive pool. Set <code>http2: true</code> to use one
66
74
  multiplexed HTTP/2 session per origin:</p>
67
- <pre><code class="ts"><span class="hl-5">const</span><span class="hl-1"> </span><span class="hl-6">client</span><span class="hl-1"> = </span><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">FerricStoreClient</span><span class="hl-1">.</span><span class="hl-0">fromUrl</span><span class="hl-1">(</span><br/><span class="hl-1"> </span><span class="hl-2">&quot;https://ferricstore-http.example.com&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-4">httpOptions:</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-4">username:</span><span class="hl-1"> </span><span class="hl-2">&quot;default&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">password</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">http2:</span><span class="hl-1"> </span><span class="hl-5">true</span><br/><span class="hl-1"> }</span><br/><span class="hl-1"> }</span><br/><span class="hl-1">);</span><br/><br/><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">client</span><span class="hl-1">.</span><span class="hl-0">ping</span><span class="hl-1">();</span>
75
+ <pre><code class="ts"><span class="hl-6">const</span><span class="hl-1"> </span><span class="hl-7">client</span><span class="hl-1"> = </span><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">FerricStoreClient</span><span class="hl-1">.</span><span class="hl-0">fromUrl</span><span class="hl-1">(</span><br/><span class="hl-1"> </span><span class="hl-2">&quot;https://ferricstore-http.example.com&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-5">httpOptions:</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-5">username:</span><span class="hl-1"> </span><span class="hl-2">&quot;default&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">password</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">http2:</span><span class="hl-1"> </span><span class="hl-6">true</span><br/><span class="hl-1"> }</span><br/><span class="hl-1"> }</span><br/><span class="hl-1">);</span><br/><br/><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">client</span><span class="hl-1">.</span><span class="hl-0">ping</span><span class="hl-1">();</span>
68
76
  </code><button type="button">Copy</button></pre>
69
77
 
70
78
  <p>Use <code>bearerToken</code> for Bearer authentication. Basic username/password
@@ -86,7 +94,7 @@ trust. Use <code>ferric://</code> or <code>ferrics://</code> whenever connection
86
94
  required.</p>
87
95
  <p>Run the complete HTTP-compatible integration surface through a real TLS
88
96
  listener with ACL authentication using:</p>
89
- <pre><code class="bash"><span class="hl-4">FERRICSTORE_IMAGE</span><span class="hl-1">=</span><span class="hl-2">quay.io/ferricstore/ferricstore:0.11.11@sha256:d9f488539f0d6c1a513d2315e7a9c2947cc795b393f3774c9de8ba5e5b5c21b5</span><span class="hl-1"> </span><span class="hl-0">\</span><br/><span class="hl-1"> </span><span class="hl-2">npm</span><span class="hl-1"> </span><span class="hl-2">run</span><span class="hl-1"> </span><span class="hl-2">test:integration:http</span>
97
+ <pre><code class="bash"><span class="hl-5">FERRICSTORE_IMAGE</span><span class="hl-1">=</span><span class="hl-2">quay.io/ferricstore/ferricstore:0.11.11@sha256:d9f488539f0d6c1a513d2315e7a9c2947cc795b393f3774c9de8ba5e5b5c21b5</span><span class="hl-1"> </span><span class="hl-0">\</span><br/><span class="hl-1"> </span><span class="hl-2">npm</span><span class="hl-1"> </span><span class="hl-2">run</span><span class="hl-1"> </span><span class="hl-2">test:integration:http</span>
90
98
  </code><button type="button">Copy</button></pre>
91
99
 
92
100
  <p>The runner creates a private CA, verifies that unauthenticated access and a
@@ -103,7 +111,7 @@ fan-out; atomic multi-key commands are never split client-side. Pass
103
111
  <code>{ ordered: true }</code> as the second argument to <code>client.pipeline()</code> when later
104
112
  commands depend on earlier ones and the transport may need an individual or
105
113
  cross-route fallback.</p>
106
- <pre><code class="ts"><span class="hl-5">const</span><span class="hl-1"> </span><span class="hl-6">flow</span><span class="hl-1"> = </span><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">FerricStoreClient</span><span class="hl-1">.</span><span class="hl-0">fromUrls</span><span class="hl-1">(</span><br/><span class="hl-1"> [</span><br/><span class="hl-1"> </span><span class="hl-2">&quot;ferric://fs0.example.com:6388&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">&quot;ferric://fs1.example.com:6388&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">&quot;ferric://fs2.example.com:6388&quot;</span><br/><span class="hl-1"> ],</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-4">codec:</span><span class="hl-1"> </span><span class="hl-5">new</span><span class="hl-1"> </span><span class="hl-0">JsonCodec</span><span class="hl-1">(),</span><br/><span class="hl-1"> </span><span class="hl-4">nativeOptions:</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-8">// Use &quot;any&quot; only inside a trusted private network.</span><br/><span class="hl-1"> </span><span class="hl-4">endpointPolicy:</span><span class="hl-1"> </span><span class="hl-2">&quot;seed_hosts&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-8">// Bound client-side route fan-out; freed slots refill immediately.</span><br/><span class="hl-1"> </span><span class="hl-4">topologyConcurrency:</span><span class="hl-1"> </span><span class="hl-9">16</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">warmConnections:</span><span class="hl-1"> </span><span class="hl-5">true</span><br/><span class="hl-1"> }</span><br/><span class="hl-1"> }</span><br/><span class="hl-1">);</span><br/><br/><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">flow</span><span class="hl-1">.</span><span class="hl-0">refreshTopology</span><span class="hl-1">();</span><br/><span class="hl-4">console</span><span class="hl-1">.</span><span class="hl-0">log</span><span class="hl-1">(</span><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">flow</span><span class="hl-1">.</span><span class="hl-0">route</span><span class="hl-1">(</span><span class="hl-2">&quot;tenant-a:order-1&quot;</span><span class="hl-1">));</span>
114
+ <pre><code class="ts"><span class="hl-6">const</span><span class="hl-1"> </span><span class="hl-7">flow</span><span class="hl-1"> = </span><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">FerricStoreClient</span><span class="hl-1">.</span><span class="hl-0">fromUrls</span><span class="hl-1">(</span><br/><span class="hl-1"> [</span><br/><span class="hl-1"> </span><span class="hl-2">&quot;ferric://fs0.example.com:6388&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">&quot;ferric://fs1.example.com:6388&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">&quot;ferric://fs2.example.com:6388&quot;</span><br/><span class="hl-1"> ],</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-5">codec:</span><span class="hl-1"> </span><span class="hl-6">new</span><span class="hl-1"> </span><span class="hl-0">JsonCodec</span><span class="hl-1">(),</span><br/><span class="hl-1"> </span><span class="hl-5">nativeOptions:</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-3">// Use &quot;any&quot; only inside a trusted private network.</span><br/><span class="hl-1"> </span><span class="hl-5">endpointPolicy:</span><span class="hl-1"> </span><span class="hl-2">&quot;seed_hosts&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-3">// Bound client-side route fan-out; freed slots refill immediately.</span><br/><span class="hl-1"> </span><span class="hl-5">topologyConcurrency:</span><span class="hl-1"> </span><span class="hl-9">16</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">warmConnections:</span><span class="hl-1"> </span><span class="hl-6">true</span><br/><span class="hl-1"> }</span><br/><span class="hl-1"> }</span><br/><span class="hl-1">);</span><br/><br/><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">flow</span><span class="hl-1">.</span><span class="hl-0">refreshTopology</span><span class="hl-1">();</span><br/><span class="hl-5">console</span><span class="hl-1">.</span><span class="hl-0">log</span><span class="hl-1">(</span><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">flow</span><span class="hl-1">.</span><span class="hl-0">route</span><span class="hl-1">(</span><span class="hl-2">&quot;tenant-a:order-1&quot;</span><span class="hl-1">));</span>
107
115
  </code><button type="button">Copy</button></pre>
108
116
 
109
117
  <p>Learned topology endpoints are checked before connection. The default
@@ -160,7 +168,7 @@ subscriptions instead.</p>
160
168
  <p>The default integration suite targets one local development server. Real HA,
161
169
  TLS, and authentication deployments can be verified with the opt-in deployment
162
170
  suite:</p>
163
- <pre><code class="bash"><span class="hl-4">FERRICSTORE_HA_URLS</span><span class="hl-1">=</span><span class="hl-2">ferric://fs0:6388,ferric://fs1:6388</span><span class="hl-1"> </span><span class="hl-0">npm</span><span class="hl-1"> </span><span class="hl-2">run</span><span class="hl-1"> </span><span class="hl-2">test:integration:deployment</span><br/><br/><span class="hl-4">FERRICSTORE_TLS_URL</span><span class="hl-1">=</span><span class="hl-2">ferrics://fs0:6389</span><span class="hl-1"> </span><span class="hl-0">\</span><br/><span class="hl-1">FERRICSTORE_TLS_CA_FILE=/path/to/ca.pem </span><span class="hl-7">\</span><br/><span class="hl-1">npm </span><span class="hl-2">run</span><span class="hl-1"> </span><span class="hl-2">test:integration:deployment</span><br/><br/><span class="hl-4">FERRICSTORE_AUTH_URL</span><span class="hl-1">=</span><span class="hl-2">ferric://app:secret@fs0:6388</span><span class="hl-1"> </span><span class="hl-0">\</span><br/><span class="hl-1">npm </span><span class="hl-2">run</span><span class="hl-1"> </span><span class="hl-2">test:integration:deployment</span>
171
+ <pre><code class="bash"><span class="hl-5">FERRICSTORE_HA_URLS</span><span class="hl-1">=</span><span class="hl-2">ferric://fs0:6388,ferric://fs1:6388</span><span class="hl-1"> </span><span class="hl-0">npm</span><span class="hl-1"> </span><span class="hl-2">run</span><span class="hl-1"> </span><span class="hl-2">test:integration:deployment</span><br/><br/><span class="hl-5">FERRICSTORE_TLS_URL</span><span class="hl-1">=</span><span class="hl-2">ferrics://fs0:6389</span><span class="hl-1"> </span><span class="hl-0">\</span><br/><span class="hl-1">FERRICSTORE_TLS_CA_FILE=/path/to/ca.pem </span><span class="hl-8">\</span><br/><span class="hl-1">npm </span><span class="hl-2">run</span><span class="hl-1"> </span><span class="hl-2">test:integration:deployment</span><br/><br/><span class="hl-5">FERRICSTORE_AUTH_URL</span><span class="hl-1">=</span><span class="hl-2">ferric://app:secret@fs0:6388</span><span class="hl-1"> </span><span class="hl-0">\</span><br/><span class="hl-1">npm </span><span class="hl-2">run</span><span class="hl-1"> </span><span class="hl-2">test:integration:deployment</span>
164
172
  </code><button type="button">Copy</button></pre>
165
173
 
166
174
  <p>The HA fixture must advertise at least two reachable leader endpoints. The auth
@@ -170,7 +178,7 @@ rejects its plaintext listener. HA TLS/auth options are available through
170
178
  <code>FERRICSTORE_HA_TLS_CA_FILE</code>, <code>FERRICSTORE_HA_TLS_SERVERNAME</code>,
171
179
  <code>FERRICSTORE_HA_USERNAME</code>, and <code>FERRICSTORE_HA_PASSWORD</code>.</p>
172
180
  <p>You can also keep one primary URL and add seeds:</p>
173
- <pre><code class="ts"><span class="hl-5">const</span><span class="hl-1"> </span><span class="hl-6">flow</span><span class="hl-1"> = </span><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">FerricStoreClient</span><span class="hl-1">.</span><span class="hl-0">fromUrl</span><span class="hl-1">(</span><span class="hl-2">&quot;ferric://fs0.example.com:6388&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-4">nativeOptions:</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-4">haRouting:</span><span class="hl-1"> </span><span class="hl-5">true</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">seeds:</span><span class="hl-1"> [</span><span class="hl-2">&quot;ferric://fs1.example.com:6388&quot;</span><span class="hl-1">, </span><span class="hl-2">&quot;ferric://fs2.example.com:6388&quot;</span><span class="hl-1">]</span><br/><span class="hl-1"> }</span><br/><span class="hl-1">});</span>
181
+ <pre><code class="ts"><span class="hl-6">const</span><span class="hl-1"> </span><span class="hl-7">flow</span><span class="hl-1"> = </span><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">FerricStoreClient</span><span class="hl-1">.</span><span class="hl-0">fromUrl</span><span class="hl-1">(</span><span class="hl-2">&quot;ferric://fs0.example.com:6388&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-5">nativeOptions:</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-5">haRouting:</span><span class="hl-1"> </span><span class="hl-6">true</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">seeds:</span><span class="hl-1"> [</span><span class="hl-2">&quot;ferric://fs1.example.com:6388&quot;</span><span class="hl-1">, </span><span class="hl-2">&quot;ferric://fs2.example.com:6388&quot;</span><span class="hl-1">]</span><br/><span class="hl-1"> }</span><br/><span class="hl-1">});</span>
174
182
  </code><button type="button">Copy</button></pre>
175
183
 
176
184
  <p>Native connections honor the flow-control windows advertised by the
@@ -192,11 +200,11 @@ bounded client queue. Set <code>nativeOptions.maxQueuedWriteBytes</code> to cont
192
200
  queue (default 64 MiB, or <code>0</code> to reject subsequent writes immediately). Healthy
193
201
  socket writes still go directly to <code>socket.write</code> without entering the queue.</p>
194
202
  <h2 id="durable-queue" class="tsd-anchor-link">Durable Queue<a href="#durable-queue" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h2>
195
- <pre><code class="ts"><span class="hl-3">import</span><span class="hl-1"> { </span><span class="hl-4">FerricStoreClient</span><span class="hl-1">, </span><span class="hl-4">JsonCodec</span><span class="hl-1">, </span><span class="hl-4">QueueClient</span><span class="hl-1"> } </span><span class="hl-3">from</span><span class="hl-1"> </span><span class="hl-2">&quot;@ferricstore/ferricstore&quot;</span><span class="hl-1">;</span><br/><br/><span class="hl-5">const</span><span class="hl-1"> </span><span class="hl-6">flow</span><span class="hl-1"> = </span><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">FerricStoreClient</span><span class="hl-1">.</span><span class="hl-0">fromUrl</span><span class="hl-1">(</span><span class="hl-2">&quot;ferric://127.0.0.1:6388&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-4">codec:</span><span class="hl-1"> </span><span class="hl-5">new</span><span class="hl-1"> </span><span class="hl-0">JsonCodec</span><span class="hl-1">()</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-5">const</span><span class="hl-1"> </span><span class="hl-6">emails</span><span class="hl-1"> = </span><span class="hl-5">new</span><span class="hl-1"> </span><span class="hl-0">QueueClient</span><span class="hl-1">(</span><span class="hl-4">flow</span><span class="hl-1">).</span><span class="hl-0">queue</span><span class="hl-1">(</span><span class="hl-2">&quot;email&quot;</span><span class="hl-1">);</span><br/><br/><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">emails</span><span class="hl-1">.</span><span class="hl-0">enqueue</span><span class="hl-1">(</span><span class="hl-2">&quot;email-1&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-4">idempotent:</span><span class="hl-1"> </span><span class="hl-5">true</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">payload:</span><span class="hl-1"> { </span><span class="hl-4">template:</span><span class="hl-1"> </span><span class="hl-2">&quot;welcome&quot;</span><span class="hl-1">, </span><span class="hl-4">userId:</span><span class="hl-1"> </span><span class="hl-2">&quot;user-1&quot;</span><span class="hl-1"> }</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">emails</span><span class="hl-1">.</span><span class="hl-0">enqueueMany</span><span class="hl-1">([{ </span><span class="hl-4">id:</span><span class="hl-1"> </span><span class="hl-2">&quot;email-2&quot;</span><span class="hl-1">, </span><span class="hl-4">payload:</span><span class="hl-1"> { </span><span class="hl-4">template:</span><span class="hl-1"> </span><span class="hl-2">&quot;receipt&quot;</span><span class="hl-1"> } }], {</span><br/><span class="hl-1"> </span><span class="hl-4">autoPartitionBatchSize:</span><span class="hl-1"> </span><span class="hl-9">1_000</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">autoPartitionConcurrency:</span><span class="hl-1"> </span><span class="hl-9">8</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">emails</span><span class="hl-1">.</span><span class="hl-0">worker</span><span class="hl-1">({ </span><span class="hl-4">batchSize:</span><span class="hl-1"> </span><span class="hl-9">100</span><span class="hl-1">, </span><span class="hl-4">concurrency:</span><span class="hl-1"> </span><span class="hl-9">16</span><span class="hl-1">, </span><span class="hl-4">worker:</span><span class="hl-1"> </span><span class="hl-2">&quot;email-worker-1&quot;</span><span class="hl-1"> }).</span><span class="hl-0">run</span><span class="hl-1">(</span><span class="hl-5">async</span><span class="hl-1"> (</span><span class="hl-4">job</span><span class="hl-1">) </span><span class="hl-5">=&gt;</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-4">console</span><span class="hl-1">.</span><span class="hl-0">log</span><span class="hl-1">(</span><span class="hl-4">job</span><span class="hl-1">.</span><span class="hl-4">id</span><span class="hl-1">, </span><span class="hl-4">job</span><span class="hl-1">.</span><span class="hl-4">payload</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-3">return</span><span class="hl-1"> { </span><span class="hl-4">sent:</span><span class="hl-1"> </span><span class="hl-5">true</span><span class="hl-1"> };</span><br/><span class="hl-1">});</span>
203
+ <pre><code class="ts"><span class="hl-4">import</span><span class="hl-1"> { </span><span class="hl-5">FerricStoreClient</span><span class="hl-1">, </span><span class="hl-5">JsonCodec</span><span class="hl-1">, </span><span class="hl-5">QueueClient</span><span class="hl-1"> } </span><span class="hl-4">from</span><span class="hl-1"> </span><span class="hl-2">&quot;@ferricstore/ferricstore&quot;</span><span class="hl-1">;</span><br/><br/><span class="hl-6">const</span><span class="hl-1"> </span><span class="hl-7">flow</span><span class="hl-1"> = </span><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">FerricStoreClient</span><span class="hl-1">.</span><span class="hl-0">fromUrl</span><span class="hl-1">(</span><span class="hl-2">&quot;ferric://127.0.0.1:6388&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-5">codec:</span><span class="hl-1"> </span><span class="hl-6">new</span><span class="hl-1"> </span><span class="hl-0">JsonCodec</span><span class="hl-1">()</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-6">const</span><span class="hl-1"> </span><span class="hl-7">emails</span><span class="hl-1"> = </span><span class="hl-6">new</span><span class="hl-1"> </span><span class="hl-0">QueueClient</span><span class="hl-1">(</span><span class="hl-5">flow</span><span class="hl-1">).</span><span class="hl-0">queue</span><span class="hl-1">(</span><span class="hl-2">&quot;email&quot;</span><span class="hl-1">);</span><br/><br/><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">emails</span><span class="hl-1">.</span><span class="hl-0">enqueue</span><span class="hl-1">(</span><span class="hl-2">&quot;email-1&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-5">idempotent:</span><span class="hl-1"> </span><span class="hl-6">true</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">payload:</span><span class="hl-1"> { </span><span class="hl-5">template:</span><span class="hl-1"> </span><span class="hl-2">&quot;welcome&quot;</span><span class="hl-1">, </span><span class="hl-5">userId:</span><span class="hl-1"> </span><span class="hl-2">&quot;user-1&quot;</span><span class="hl-1"> }</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">emails</span><span class="hl-1">.</span><span class="hl-0">enqueueMany</span><span class="hl-1">([{ </span><span class="hl-5">id:</span><span class="hl-1"> </span><span class="hl-2">&quot;email-2&quot;</span><span class="hl-1">, </span><span class="hl-5">payload:</span><span class="hl-1"> { </span><span class="hl-5">template:</span><span class="hl-1"> </span><span class="hl-2">&quot;receipt&quot;</span><span class="hl-1"> } }], {</span><br/><span class="hl-1"> </span><span class="hl-5">autoPartitionBatchSize:</span><span class="hl-1"> </span><span class="hl-9">1_000</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">autoPartitionConcurrency:</span><span class="hl-1"> </span><span class="hl-9">8</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">emails</span><span class="hl-1">.</span><span class="hl-0">worker</span><span class="hl-1">({ </span><span class="hl-5">batchSize:</span><span class="hl-1"> </span><span class="hl-9">100</span><span class="hl-1">, </span><span class="hl-5">concurrency:</span><span class="hl-1"> </span><span class="hl-9">16</span><span class="hl-1">, </span><span class="hl-5">worker:</span><span class="hl-1"> </span><span class="hl-2">&quot;email-worker-1&quot;</span><span class="hl-1"> }).</span><span class="hl-0">run</span><span class="hl-1">(</span><span class="hl-6">async</span><span class="hl-1"> (</span><span class="hl-5">job</span><span class="hl-1">) </span><span class="hl-6">=&gt;</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-5">console</span><span class="hl-1">.</span><span class="hl-0">log</span><span class="hl-1">(</span><span class="hl-5">job</span><span class="hl-1">.</span><span class="hl-5">id</span><span class="hl-1">, </span><span class="hl-5">job</span><span class="hl-1">.</span><span class="hl-5">payload</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-4">return</span><span class="hl-1"> { </span><span class="hl-5">sent:</span><span class="hl-1"> </span><span class="hl-6">true</span><span class="hl-1"> };</span><br/><span class="hl-1">});</span>
196
204
  </code><button type="button">Copy</button></pre>
197
205
 
198
206
  <h2 id="explicit-workflow" class="tsd-anchor-link">Explicit Workflow<a href="#explicit-workflow" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h2>
199
- <pre><code class="ts"><span class="hl-3">import</span><span class="hl-1"> { </span><span class="hl-4">FerricStoreClient</span><span class="hl-1">, </span><span class="hl-4">JsonCodec</span><span class="hl-1">, </span><span class="hl-4">WorkflowClient</span><span class="hl-1">, </span><span class="hl-4">complete</span><span class="hl-1">, </span><span class="hl-4">transition</span><span class="hl-1"> } </span><span class="hl-3">from</span><span class="hl-1"> </span><span class="hl-2">&quot;@ferricstore/ferricstore&quot;</span><span class="hl-1">;</span><br/><br/><span class="hl-5">const</span><span class="hl-1"> </span><span class="hl-6">flow</span><span class="hl-1"> = </span><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">FerricStoreClient</span><span class="hl-1">.</span><span class="hl-0">fromUrl</span><span class="hl-1">(</span><span class="hl-2">&quot;ferric://127.0.0.1:6388&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-4">codec:</span><span class="hl-1"> </span><span class="hl-5">new</span><span class="hl-1"> </span><span class="hl-0">JsonCodec</span><span class="hl-1">()</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-5">const</span><span class="hl-1"> </span><span class="hl-6">order</span><span class="hl-1"> = </span><span class="hl-5">new</span><span class="hl-1"> </span><span class="hl-0">WorkflowClient</span><span class="hl-1">(</span><span class="hl-4">flow</span><span class="hl-1">).</span><span class="hl-0">workflow</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-4">initialState:</span><span class="hl-1"> </span><span class="hl-2">&quot;created&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">type:</span><span class="hl-1"> </span><span class="hl-2">&quot;order&quot;</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-4">order</span><span class="hl-1">.</span><span class="hl-0">state</span><span class="hl-1">(</span><span class="hl-2">&quot;created&quot;</span><span class="hl-1">, </span><span class="hl-5">async</span><span class="hl-1"> (</span><span class="hl-4">ctx</span><span class="hl-1">) </span><span class="hl-5">=&gt;</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-0">chargeCard</span><span class="hl-1">(</span><span class="hl-4">ctx</span><span class="hl-1">.</span><span class="hl-4">payload</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-3">return</span><span class="hl-1"> </span><span class="hl-0">transition</span><span class="hl-1">(</span><span class="hl-2">&quot;charged&quot;</span><span class="hl-1">);</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-4">order</span><span class="hl-1">.</span><span class="hl-0">state</span><span class="hl-1">(</span><span class="hl-2">&quot;charged&quot;</span><span class="hl-1">, </span><span class="hl-5">async</span><span class="hl-1"> (</span><span class="hl-4">ctx</span><span class="hl-1">) </span><span class="hl-5">=&gt;</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-0">sendReceipt</span><span class="hl-1">(</span><span class="hl-4">ctx</span><span class="hl-1">.</span><span class="hl-4">id</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-3">return</span><span class="hl-1"> </span><span class="hl-0">complete</span><span class="hl-1">({ </span><span class="hl-4">result:</span><span class="hl-1"> { </span><span class="hl-4">ok:</span><span class="hl-1"> </span><span class="hl-5">true</span><span class="hl-1"> } });</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">order</span><span class="hl-1">.</span><span class="hl-0">start</span><span class="hl-1">(</span><span class="hl-2">&quot;order-1&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-4">idempotent:</span><span class="hl-1"> </span><span class="hl-5">true</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">payload:</span><span class="hl-1"> { </span><span class="hl-4">amount:</span><span class="hl-1"> </span><span class="hl-9">42</span><span class="hl-1">, </span><span class="hl-4">userId:</span><span class="hl-1"> </span><span class="hl-2">&quot;user-1&quot;</span><span class="hl-1"> }</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">order</span><span class="hl-1">.</span><span class="hl-0">worker</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-4">batchSize:</span><span class="hl-1"> </span><span class="hl-9">50</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">concurrency:</span><span class="hl-1"> </span><span class="hl-9">8</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">states:</span><span class="hl-1"> [</span><span class="hl-2">&quot;created&quot;</span><span class="hl-1">, </span><span class="hl-2">&quot;charged&quot;</span><span class="hl-1">],</span><br/><span class="hl-1"> </span><span class="hl-4">worker:</span><span class="hl-1"> </span><span class="hl-2">&quot;order-worker-1&quot;</span><br/><span class="hl-1">}).</span><span class="hl-0">run</span><span class="hl-1">();</span>
207
+ <pre><code class="ts"><span class="hl-4">import</span><span class="hl-1"> { </span><span class="hl-5">FerricStoreClient</span><span class="hl-1">, </span><span class="hl-5">JsonCodec</span><span class="hl-1">, </span><span class="hl-5">WorkflowClient</span><span class="hl-1">, </span><span class="hl-5">complete</span><span class="hl-1">, </span><span class="hl-5">transition</span><span class="hl-1"> } </span><span class="hl-4">from</span><span class="hl-1"> </span><span class="hl-2">&quot;@ferricstore/ferricstore&quot;</span><span class="hl-1">;</span><br/><br/><span class="hl-6">const</span><span class="hl-1"> </span><span class="hl-7">flow</span><span class="hl-1"> = </span><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">FerricStoreClient</span><span class="hl-1">.</span><span class="hl-0">fromUrl</span><span class="hl-1">(</span><span class="hl-2">&quot;ferric://127.0.0.1:6388&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-5">codec:</span><span class="hl-1"> </span><span class="hl-6">new</span><span class="hl-1"> </span><span class="hl-0">JsonCodec</span><span class="hl-1">()</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-6">const</span><span class="hl-1"> </span><span class="hl-7">order</span><span class="hl-1"> = </span><span class="hl-6">new</span><span class="hl-1"> </span><span class="hl-0">WorkflowClient</span><span class="hl-1">(</span><span class="hl-5">flow</span><span class="hl-1">).</span><span class="hl-0">workflow</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-5">initialState:</span><span class="hl-1"> </span><span class="hl-2">&quot;created&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">type:</span><span class="hl-1"> </span><span class="hl-2">&quot;order&quot;</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-5">order</span><span class="hl-1">.</span><span class="hl-0">state</span><span class="hl-1">(</span><span class="hl-2">&quot;created&quot;</span><span class="hl-1">, </span><span class="hl-6">async</span><span class="hl-1"> (</span><span class="hl-5">ctx</span><span class="hl-1">) </span><span class="hl-6">=&gt;</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-0">chargeCard</span><span class="hl-1">(</span><span class="hl-5">ctx</span><span class="hl-1">.</span><span class="hl-5">payload</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-4">return</span><span class="hl-1"> </span><span class="hl-0">transition</span><span class="hl-1">(</span><span class="hl-2">&quot;charged&quot;</span><span class="hl-1">);</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-5">order</span><span class="hl-1">.</span><span class="hl-0">state</span><span class="hl-1">(</span><span class="hl-2">&quot;charged&quot;</span><span class="hl-1">, </span><span class="hl-6">async</span><span class="hl-1"> (</span><span class="hl-5">ctx</span><span class="hl-1">) </span><span class="hl-6">=&gt;</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-0">sendReceipt</span><span class="hl-1">(</span><span class="hl-5">ctx</span><span class="hl-1">.</span><span class="hl-5">id</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-4">return</span><span class="hl-1"> </span><span class="hl-0">complete</span><span class="hl-1">({ </span><span class="hl-5">result:</span><span class="hl-1"> { </span><span class="hl-5">ok:</span><span class="hl-1"> </span><span class="hl-6">true</span><span class="hl-1"> } });</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">order</span><span class="hl-1">.</span><span class="hl-0">start</span><span class="hl-1">(</span><span class="hl-2">&quot;order-1&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-5">idempotent:</span><span class="hl-1"> </span><span class="hl-6">true</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">payload:</span><span class="hl-1"> { </span><span class="hl-5">amount:</span><span class="hl-1"> </span><span class="hl-9">42</span><span class="hl-1">, </span><span class="hl-5">userId:</span><span class="hl-1"> </span><span class="hl-2">&quot;user-1&quot;</span><span class="hl-1"> }</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">order</span><span class="hl-1">.</span><span class="hl-0">worker</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-5">batchSize:</span><span class="hl-1"> </span><span class="hl-9">50</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">concurrency:</span><span class="hl-1"> </span><span class="hl-9">8</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">states:</span><span class="hl-1"> [</span><span class="hl-2">&quot;created&quot;</span><span class="hl-1">, </span><span class="hl-2">&quot;charged&quot;</span><span class="hl-1">],</span><br/><span class="hl-1"> </span><span class="hl-5">worker:</span><span class="hl-1"> </span><span class="hl-2">&quot;order-worker-1&quot;</span><br/><span class="hl-1">}).</span><span class="hl-0">run</span><span class="hl-1">();</span>
200
208
  </code><button type="button">Copy</button></pre>
201
209
 
202
210
  <p>Handlers return explicit durable outcomes:</p>
@@ -242,12 +250,12 @@ unchanged; all fenced mutation APIs accept the exported <code>FencingToken</code
242
250
  and native compact claim and batch paths preserve its signed 64-bit value
243
251
  exactly.</p>
244
252
  <h2 id="low-level-flow-commands" class="tsd-anchor-link">Low-Level Flow Commands<a href="#low-level-flow-commands" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h2>
245
- <pre><code class="ts"><span class="hl-5">const</span><span class="hl-1"> </span><span class="hl-6">flow</span><span class="hl-1"> = </span><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">FerricStoreClient</span><span class="hl-1">.</span><span class="hl-0">fromUrl</span><span class="hl-1">(</span><span class="hl-2">&quot;ferric://127.0.0.1:6388&quot;</span><span class="hl-1">);</span><br/><br/><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">flow</span><span class="hl-1">.</span><span class="hl-0">create</span><span class="hl-1">(</span><span class="hl-2">&quot;order-1&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-4">type:</span><span class="hl-1"> </span><span class="hl-2">&quot;order&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">state:</span><span class="hl-1"> </span><span class="hl-2">&quot;created&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">payload:</span><span class="hl-1"> </span><span class="hl-4">Buffer</span><span class="hl-1">.</span><span class="hl-0">from</span><span class="hl-1">(</span><span class="hl-2">&quot;order payload&quot;</span><span class="hl-1">),</span><br/><span class="hl-1"> </span><span class="hl-4">idempotent:</span><span class="hl-1"> </span><span class="hl-5">true</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-5">const</span><span class="hl-1"> </span><span class="hl-6">jobs</span><span class="hl-1"> = </span><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">flow</span><span class="hl-1">.</span><span class="hl-0">claimDue</span><span class="hl-1">(</span><span class="hl-2">&quot;order&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-4">state:</span><span class="hl-1"> </span><span class="hl-2">&quot;created&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">worker:</span><span class="hl-1"> </span><span class="hl-2">&quot;worker-1&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">leaseMs:</span><span class="hl-1"> </span><span class="hl-9">30_000</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">limit:</span><span class="hl-1"> </span><span class="hl-9">10</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">payload:</span><span class="hl-1"> </span><span class="hl-5">true</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-3">for</span><span class="hl-1"> (</span><span class="hl-5">const</span><span class="hl-1"> </span><span class="hl-6">job</span><span class="hl-1"> </span><span class="hl-5">of</span><span class="hl-1"> </span><span class="hl-4">jobs</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">flow</span><span class="hl-1">.</span><span class="hl-0">transition</span><span class="hl-1">(</span><span class="hl-4">job</span><span class="hl-1">.</span><span class="hl-4">id</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-4">fromState:</span><span class="hl-1"> </span><span class="hl-4">job</span><span class="hl-1">.</span><span class="hl-4">state</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">toState:</span><span class="hl-1"> </span><span class="hl-2">&quot;charged&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">leaseToken:</span><span class="hl-1"> </span><span class="hl-4">job</span><span class="hl-1">.</span><span class="hl-4">leaseToken</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">fencingToken:</span><span class="hl-1"> </span><span class="hl-4">job</span><span class="hl-1">.</span><span class="hl-4">fencingToken</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">partitionKey:</span><span class="hl-1"> </span><span class="hl-4">job</span><span class="hl-1">.</span><span class="hl-4">partitionKey</span><br/><span class="hl-1"> });</span><br/><span class="hl-1">}</span>
253
+ <pre><code class="ts"><span class="hl-6">const</span><span class="hl-1"> </span><span class="hl-7">flow</span><span class="hl-1"> = </span><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">FerricStoreClient</span><span class="hl-1">.</span><span class="hl-0">fromUrl</span><span class="hl-1">(</span><span class="hl-2">&quot;ferric://127.0.0.1:6388&quot;</span><span class="hl-1">);</span><br/><br/><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">flow</span><span class="hl-1">.</span><span class="hl-0">create</span><span class="hl-1">(</span><span class="hl-2">&quot;order-1&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-5">type:</span><span class="hl-1"> </span><span class="hl-2">&quot;order&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">state:</span><span class="hl-1"> </span><span class="hl-2">&quot;created&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">payload:</span><span class="hl-1"> </span><span class="hl-5">Buffer</span><span class="hl-1">.</span><span class="hl-0">from</span><span class="hl-1">(</span><span class="hl-2">&quot;order payload&quot;</span><span class="hl-1">),</span><br/><span class="hl-1"> </span><span class="hl-5">idempotent:</span><span class="hl-1"> </span><span class="hl-6">true</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-6">const</span><span class="hl-1"> </span><span class="hl-7">jobs</span><span class="hl-1"> = </span><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">flow</span><span class="hl-1">.</span><span class="hl-0">claimDue</span><span class="hl-1">(</span><span class="hl-2">&quot;order&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-5">state:</span><span class="hl-1"> </span><span class="hl-2">&quot;created&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">worker:</span><span class="hl-1"> </span><span class="hl-2">&quot;worker-1&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">leaseMs:</span><span class="hl-1"> </span><span class="hl-9">30_000</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">limit:</span><span class="hl-1"> </span><span class="hl-9">10</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">payload:</span><span class="hl-1"> </span><span class="hl-6">true</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-4">for</span><span class="hl-1"> (</span><span class="hl-6">const</span><span class="hl-1"> </span><span class="hl-7">job</span><span class="hl-1"> </span><span class="hl-6">of</span><span class="hl-1"> </span><span class="hl-5">jobs</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">flow</span><span class="hl-1">.</span><span class="hl-0">transition</span><span class="hl-1">(</span><span class="hl-5">job</span><span class="hl-1">.</span><span class="hl-5">id</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-5">fromState:</span><span class="hl-1"> </span><span class="hl-5">job</span><span class="hl-1">.</span><span class="hl-5">state</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">toState:</span><span class="hl-1"> </span><span class="hl-2">&quot;charged&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">leaseToken:</span><span class="hl-1"> </span><span class="hl-5">job</span><span class="hl-1">.</span><span class="hl-5">leaseToken</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">fencingToken:</span><span class="hl-1"> </span><span class="hl-5">job</span><span class="hl-1">.</span><span class="hl-5">fencingToken</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">partitionKey:</span><span class="hl-1"> </span><span class="hl-5">job</span><span class="hl-1">.</span><span class="hl-5">partitionKey</span><br/><span class="hl-1"> });</span><br/><span class="hl-1">}</span>
246
254
  </code><button type="button">Copy</button></pre>
247
255
 
248
256
  <p>Flow attributes can be returned without hydrating each record and can be
249
257
  updated atomically with the fenced state mutation:</p>
250
- <pre><code class="ts"><span class="hl-5">const</span><span class="hl-1"> </span><span class="hl-6">attributed</span><span class="hl-1"> = </span><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">flow</span><span class="hl-1">.</span><span class="hl-0">claimDue</span><span class="hl-1">(</span><span class="hl-2">&quot;order&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-4">includeAttributes:</span><span class="hl-1"> </span><span class="hl-5">true</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">jobOnly:</span><span class="hl-1"> </span><span class="hl-5">true</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">state:</span><span class="hl-1"> </span><span class="hl-2">&quot;created&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">worker:</span><span class="hl-1"> </span><span class="hl-2">&quot;worker-1&quot;</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">flow</span><span class="hl-1">.</span><span class="hl-0">transition</span><span class="hl-1">(</span><span class="hl-4">attributed</span><span class="hl-1">[</span><span class="hl-9">0</span><span class="hl-1">]!.</span><span class="hl-4">id</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-4">attributesDelete:</span><span class="hl-1"> [</span><span class="hl-2">&quot;temporary&quot;</span><span class="hl-1">],</span><br/><span class="hl-1"> </span><span class="hl-4">attributesMerge:</span><span class="hl-1"> { </span><span class="hl-4">processor:</span><span class="hl-1"> </span><span class="hl-2">&quot;payments-v2&quot;</span><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-4">fencingToken:</span><span class="hl-1"> </span><span class="hl-4">attributed</span><span class="hl-1">[</span><span class="hl-9">0</span><span class="hl-1">]!.</span><span class="hl-4">fencingToken</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">fromState:</span><span class="hl-1"> </span><span class="hl-2">&quot;created&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">leaseToken:</span><span class="hl-1"> </span><span class="hl-4">attributed</span><span class="hl-1">[</span><span class="hl-9">0</span><span class="hl-1">]!.</span><span class="hl-4">leaseToken</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">toState:</span><span class="hl-1"> </span><span class="hl-2">&quot;charged&quot;</span><br/><span class="hl-1">});</span>
258
+ <pre><code class="ts"><span class="hl-6">const</span><span class="hl-1"> </span><span class="hl-7">attributed</span><span class="hl-1"> = </span><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">flow</span><span class="hl-1">.</span><span class="hl-0">claimDue</span><span class="hl-1">(</span><span class="hl-2">&quot;order&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-5">includeAttributes:</span><span class="hl-1"> </span><span class="hl-6">true</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">jobOnly:</span><span class="hl-1"> </span><span class="hl-6">true</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">state:</span><span class="hl-1"> </span><span class="hl-2">&quot;created&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">worker:</span><span class="hl-1"> </span><span class="hl-2">&quot;worker-1&quot;</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">flow</span><span class="hl-1">.</span><span class="hl-0">transition</span><span class="hl-1">(</span><span class="hl-5">attributed</span><span class="hl-1">[</span><span class="hl-9">0</span><span class="hl-1">]!.</span><span class="hl-5">id</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-5">attributesDelete:</span><span class="hl-1"> [</span><span class="hl-2">&quot;temporary&quot;</span><span class="hl-1">],</span><br/><span class="hl-1"> </span><span class="hl-5">attributesMerge:</span><span class="hl-1"> { </span><span class="hl-5">processor:</span><span class="hl-1"> </span><span class="hl-2">&quot;payments-v2&quot;</span><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-5">fencingToken:</span><span class="hl-1"> </span><span class="hl-5">attributed</span><span class="hl-1">[</span><span class="hl-9">0</span><span class="hl-1">]!.</span><span class="hl-5">fencingToken</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">fromState:</span><span class="hl-1"> </span><span class="hl-2">&quot;created&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">leaseToken:</span><span class="hl-1"> </span><span class="hl-5">attributed</span><span class="hl-1">[</span><span class="hl-9">0</span><span class="hl-1">]!.</span><span class="hl-5">leaseToken</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">toState:</span><span class="hl-1"> </span><span class="hl-2">&quot;charged&quot;</span><br/><span class="hl-1">});</span>
251
259
  </code><button type="button">Copy</button></pre>
252
260
 
253
261
  <p>The low-level client also exposes the fused <code>startAndClaim</code>, <code>stepContinue</code>,
@@ -255,13 +263,13 @@ and <code>runStepsMany</code> operations, schedule administration, Flow statisti
255
263
  attribute queries, effects, approvals, circuits, budgets, and distributed
256
264
  limits. History supports the complete server filter surface, including event,
257
265
  time, and version bounds plus cold/consistent reads and payload hydration:</p>
258
- <pre><code class="ts"><span class="hl-5">const</span><span class="hl-1"> </span><span class="hl-6">events</span><span class="hl-1"> = </span><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">flow</span><span class="hl-1">.</span><span class="hl-0">history</span><span class="hl-1">(</span><span class="hl-2">&quot;order-1&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-4">consistentProjection:</span><span class="hl-1"> </span><span class="hl-5">true</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">fromVersion:</span><span class="hl-1"> </span><span class="hl-9">2</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">includeCold:</span><span class="hl-1"> </span><span class="hl-5">true</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">payloadMaxBytes:</span><span class="hl-1"> </span><span class="hl-9">64_000</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">toVersion:</span><span class="hl-1"> </span><span class="hl-9">8</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">values:</span><span class="hl-1"> </span><span class="hl-5">true</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">flow</span><span class="hl-1">.</span><span class="hl-0">scheduleCreate</span><span class="hl-1">(</span><span class="hl-2">&quot;orders-every-five-minutes&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-4">cron:</span><span class="hl-1"> </span><span class="hl-2">&quot;*/5 * * * *&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">kind:</span><span class="hl-1"> </span><span class="hl-2">&quot;cron&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">target:</span><span class="hl-1"> { </span><span class="hl-4">state:</span><span class="hl-1"> </span><span class="hl-2">&quot;created&quot;</span><span class="hl-1">, </span><span class="hl-4">type:</span><span class="hl-1"> </span><span class="hl-2">&quot;order&quot;</span><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-4">timezone:</span><span class="hl-1"> </span><span class="hl-2">&quot;UTC&quot;</span><br/><span class="hl-1">});</span>
266
+ <pre><code class="ts"><span class="hl-6">const</span><span class="hl-1"> </span><span class="hl-7">events</span><span class="hl-1"> = </span><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">flow</span><span class="hl-1">.</span><span class="hl-0">history</span><span class="hl-1">(</span><span class="hl-2">&quot;order-1&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-5">consistentProjection:</span><span class="hl-1"> </span><span class="hl-6">true</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">fromVersion:</span><span class="hl-1"> </span><span class="hl-9">2</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">includeCold:</span><span class="hl-1"> </span><span class="hl-6">true</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">payloadMaxBytes:</span><span class="hl-1"> </span><span class="hl-9">64_000</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">toVersion:</span><span class="hl-1"> </span><span class="hl-9">8</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">values:</span><span class="hl-1"> </span><span class="hl-6">true</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">flow</span><span class="hl-1">.</span><span class="hl-0">scheduleCreate</span><span class="hl-1">(</span><span class="hl-2">&quot;orders-every-five-minutes&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-5">cron:</span><span class="hl-1"> </span><span class="hl-2">&quot;*/5 * * * *&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">kind:</span><span class="hl-1"> </span><span class="hl-2">&quot;cron&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">target:</span><span class="hl-1"> { </span><span class="hl-5">state:</span><span class="hl-1"> </span><span class="hl-2">&quot;created&quot;</span><span class="hl-1">, </span><span class="hl-5">type:</span><span class="hl-1"> </span><span class="hl-2">&quot;order&quot;</span><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-5">timezone:</span><span class="hl-1"> </span><span class="hl-2">&quot;UTC&quot;</span><br/><span class="hl-1">});</span>
259
267
  </code><button type="button">Copy</button></pre>
260
268
 
261
269
  <p>Overdue interval schedules use bounded <code>fire_once</code> catch-up. Recovery creates
262
270
  one target, coalesces additional elapsed periods in constant time, and sets the
263
271
  next run one full interval after recovery:</p>
264
- <pre><code class="ts"><span class="hl-5">const</span><span class="hl-1"> </span><span class="hl-6">schedule</span><span class="hl-1"> = </span><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">flow</span><span class="hl-1">.</span><span class="hl-0">scheduleCreate</span><span class="hl-1">(</span><span class="hl-2">&quot;billing-sweep&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-4">catchupPolicy:</span><span class="hl-1"> </span><span class="hl-2">&quot;fire_once&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">everyMs:</span><span class="hl-1"> </span><span class="hl-9">60_000</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">kind:</span><span class="hl-1"> </span><span class="hl-2">&quot;interval&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">overlapPolicy:</span><span class="hl-1"> </span><span class="hl-2">&quot;queue_after_previous&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">target:</span><span class="hl-1"> { </span><span class="hl-4">id_prefix:</span><span class="hl-1"> </span><span class="hl-2">&quot;billing-sweep&quot;</span><span class="hl-1">, </span><span class="hl-4">type:</span><span class="hl-1"> </span><span class="hl-2">&quot;billing&quot;</span><span class="hl-1"> }</span><br/><span class="hl-1">});</span>
272
+ <pre><code class="ts"><span class="hl-6">const</span><span class="hl-1"> </span><span class="hl-7">schedule</span><span class="hl-1"> = </span><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">flow</span><span class="hl-1">.</span><span class="hl-0">scheduleCreate</span><span class="hl-1">(</span><span class="hl-2">&quot;billing-sweep&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-5">catchupPolicy:</span><span class="hl-1"> </span><span class="hl-2">&quot;fire_once&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">everyMs:</span><span class="hl-1"> </span><span class="hl-9">60_000</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">kind:</span><span class="hl-1"> </span><span class="hl-2">&quot;interval&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">overlapPolicy:</span><span class="hl-1"> </span><span class="hl-2">&quot;queue_after_previous&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">target:</span><span class="hl-1"> { </span><span class="hl-5">id_prefix:</span><span class="hl-1"> </span><span class="hl-2">&quot;billing-sweep&quot;</span><span class="hl-1">, </span><span class="hl-5">type:</span><span class="hl-1"> </span><span class="hl-2">&quot;billing&quot;</span><span class="hl-1"> }</span><br/><span class="hl-1">});</span>
265
273
  </code><button type="button">Copy</button></pre>
266
274
 
267
275
  <p><code>ScheduleRecord</code> exposes the complete recurrence configuration through
@@ -288,7 +296,7 @@ When planning fails, <code>state</code> is <code>&quot;failed&quot;</code>, <cod
288
296
  <code>&quot;planning_failed&quot;</code>, and <code>last_planning_error</code> contains the actionable error.
289
297
  <code>scheduleDelete()</code> resolves to <code>undefined</code> only after an <code>OK</code> server reply.</p>
290
298
  <p>FIFO Flow state policy is opt-in per state:</p>
291
- <pre><code class="ts"><span class="hl-5">const</span><span class="hl-1"> </span><span class="hl-6">policy</span><span class="hl-1"> = </span><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">flow</span><span class="hl-1">.</span><span class="hl-0">installPolicy</span><span class="hl-1">(</span><span class="hl-2">&quot;email&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-4">states:</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-4">queued:</span><span class="hl-1"> { </span><span class="hl-4">mode:</span><span class="hl-1"> </span><span class="hl-2">&quot;fifo&quot;</span><span class="hl-1"> }</span><br/><span class="hl-1"> }</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-8">// Direct writes deep-patch by default. Fence concurrent editors with generation CAS.</span><br/><span class="hl-5">const</span><span class="hl-1"> </span><span class="hl-6">updated</span><span class="hl-1"> = </span><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">flow</span><span class="hl-1">.</span><span class="hl-0">installPolicy</span><span class="hl-1">(</span><span class="hl-2">&quot;email&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-4">expectedGeneration:</span><span class="hl-1"> </span><span class="hl-4">policy</span><span class="hl-1">.</span><span class="hl-4">generation</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">maxActiveMs:</span><span class="hl-1"> </span><span class="hl-9">300_000</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-8">// Full replacement is explicit on the client API.</span><br/><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">flow</span><span class="hl-1">.</span><span class="hl-0">installPolicy</span><span class="hl-1">(</span><span class="hl-2">&quot;email&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-4">expectedGeneration:</span><span class="hl-1"> </span><span class="hl-4">updated</span><span class="hl-1">.</span><span class="hl-4">generation</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">replace:</span><span class="hl-1"> </span><span class="hl-5">true</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">states:</span><span class="hl-1"> { </span><span class="hl-4">queued:</span><span class="hl-1"> { </span><span class="hl-4">mode:</span><span class="hl-1"> </span><span class="hl-2">&quot;fifo&quot;</span><span class="hl-1"> } }</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">flow</span><span class="hl-1">.</span><span class="hl-0">create</span><span class="hl-1">(</span><span class="hl-2">&quot;email-3&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-4">partitionKey:</span><span class="hl-1"> </span><span class="hl-2">&quot;tenant-a:email&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">payload:</span><span class="hl-1"> </span><span class="hl-4">Buffer</span><span class="hl-1">.</span><span class="hl-0">from</span><span class="hl-1">(</span><span class="hl-2">&quot;welcome&quot;</span><span class="hl-1">),</span><br/><span class="hl-1"> </span><span class="hl-4">state:</span><span class="hl-1"> </span><span class="hl-2">&quot;queued&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">type:</span><span class="hl-1"> </span><span class="hl-2">&quot;email&quot;</span><br/><span class="hl-1">});</span>
299
+ <pre><code class="ts"><span class="hl-6">const</span><span class="hl-1"> </span><span class="hl-7">policy</span><span class="hl-1"> = </span><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">flow</span><span class="hl-1">.</span><span class="hl-0">installPolicy</span><span class="hl-1">(</span><span class="hl-2">&quot;email&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-5">states:</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-5">queued:</span><span class="hl-1"> { </span><span class="hl-5">mode:</span><span class="hl-1"> </span><span class="hl-2">&quot;fifo&quot;</span><span class="hl-1"> }</span><br/><span class="hl-1"> }</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-3">// Direct writes deep-patch by default. Fence concurrent editors with generation CAS.</span><br/><span class="hl-6">const</span><span class="hl-1"> </span><span class="hl-7">updated</span><span class="hl-1"> = </span><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">flow</span><span class="hl-1">.</span><span class="hl-0">installPolicy</span><span class="hl-1">(</span><span class="hl-2">&quot;email&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-5">expectedGeneration:</span><span class="hl-1"> </span><span class="hl-5">policy</span><span class="hl-1">.</span><span class="hl-5">generation</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">maxActiveMs:</span><span class="hl-1"> </span><span class="hl-9">300_000</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-3">// Full replacement is explicit on the client API.</span><br/><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">flow</span><span class="hl-1">.</span><span class="hl-0">installPolicy</span><span class="hl-1">(</span><span class="hl-2">&quot;email&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-5">expectedGeneration:</span><span class="hl-1"> </span><span class="hl-5">updated</span><span class="hl-1">.</span><span class="hl-5">generation</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">replace:</span><span class="hl-1"> </span><span class="hl-6">true</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">states:</span><span class="hl-1"> { </span><span class="hl-5">queued:</span><span class="hl-1"> { </span><span class="hl-5">mode:</span><span class="hl-1"> </span><span class="hl-2">&quot;fifo&quot;</span><span class="hl-1"> } }</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">flow</span><span class="hl-1">.</span><span class="hl-0">create</span><span class="hl-1">(</span><span class="hl-2">&quot;email-3&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-5">partitionKey:</span><span class="hl-1"> </span><span class="hl-2">&quot;tenant-a:email&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">payload:</span><span class="hl-1"> </span><span class="hl-5">Buffer</span><span class="hl-1">.</span><span class="hl-0">from</span><span class="hl-1">(</span><span class="hl-2">&quot;welcome&quot;</span><span class="hl-1">),</span><br/><span class="hl-1"> </span><span class="hl-5">state:</span><span class="hl-1"> </span><span class="hl-2">&quot;queued&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">type:</span><span class="hl-1"> </span><span class="hl-2">&quot;email&quot;</span><br/><span class="hl-1">});</span>
292
300
  </code><button type="button">Copy</button></pre>
293
301
 
294
302
  <p>FIFO states require a <code>partitionKey</code>; priority is for parallel states.
@@ -298,7 +306,7 @@ FIFO ordering is enforced by the server per <code>(type, state, partitionKey)</c
298
306
  concurrency remains available across different partitions.</p>
299
307
  <h2 id="ferricstore-kv-and-data-structures" class="tsd-anchor-link">FerricStore KV And Data Structures<a href="#ferricstore-kv-and-data-structures" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h2>
300
308
  <p>The same client exposes typed helpers for FerricStore's Redis-compatible store commands:</p>
301
- <pre><code class="ts"><span class="hl-5">const</span><span class="hl-1"> </span><span class="hl-6">client</span><span class="hl-1"> = </span><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">FerricStoreClient</span><span class="hl-1">.</span><span class="hl-0">fromUrl</span><span class="hl-1">(</span><span class="hl-2">&quot;ferric://127.0.0.1:6388&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-4">codec:</span><span class="hl-1"> </span><span class="hl-5">new</span><span class="hl-1"> </span><span class="hl-0">JsonCodec</span><span class="hl-1">()</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">client</span><span class="hl-1">.</span><span class="hl-4">kv</span><span class="hl-1">.</span><span class="hl-0">set</span><span class="hl-1">(</span><span class="hl-2">&quot;user:1&quot;</span><span class="hl-1">, { </span><span class="hl-4">name:</span><span class="hl-1"> </span><span class="hl-2">&quot;Ada&quot;</span><span class="hl-1"> }, { </span><span class="hl-4">px:</span><span class="hl-1"> </span><span class="hl-9">60_000</span><span class="hl-1"> });</span><br/><span class="hl-5">const</span><span class="hl-1"> </span><span class="hl-6">user</span><span class="hl-1"> = </span><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">client</span><span class="hl-1">.</span><span class="hl-4">kv</span><span class="hl-1">.</span><span class="hl-0">get</span><span class="hl-1">(</span><span class="hl-2">&quot;user:1&quot;</span><span class="hl-1">);</span><br/><br/><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">client</span><span class="hl-1">.</span><span class="hl-4">hash</span><span class="hl-1">.</span><span class="hl-0">hset</span><span class="hl-1">(</span><span class="hl-2">&quot;user:1:profile&quot;</span><span class="hl-1">, { </span><span class="hl-4">email:</span><span class="hl-1"> </span><span class="hl-2">&quot;ada@example.com&quot;</span><span class="hl-1"> });</span><br/><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">client</span><span class="hl-1">.</span><span class="hl-4">lists</span><span class="hl-1">.</span><span class="hl-0">lpush</span><span class="hl-1">(</span><span class="hl-2">&quot;jobs&quot;</span><span class="hl-1">, { </span><span class="hl-4">id:</span><span class="hl-1"> </span><span class="hl-2">&quot;job-1&quot;</span><span class="hl-1"> });</span><br/><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">client</span><span class="hl-1">.</span><span class="hl-4">sets</span><span class="hl-1">.</span><span class="hl-0">sadd</span><span class="hl-1">(</span><span class="hl-2">&quot;seen-users&quot;</span><span class="hl-1">, </span><span class="hl-2">&quot;user:1&quot;</span><span class="hl-1">);</span><br/><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">client</span><span class="hl-1">.</span><span class="hl-4">zset</span><span class="hl-1">.</span><span class="hl-0">zadd</span><span class="hl-1">(</span><span class="hl-2">&quot;leaderboard&quot;</span><span class="hl-1">, [{ </span><span class="hl-4">score:</span><span class="hl-1"> </span><span class="hl-9">42</span><span class="hl-1">, </span><span class="hl-4">member:</span><span class="hl-1"> </span><span class="hl-2">&quot;user:1&quot;</span><span class="hl-1"> }]);</span><br/><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">client</span><span class="hl-1">.</span><span class="hl-4">stream</span><span class="hl-1">.</span><span class="hl-0">xadd</span><span class="hl-1">(</span><span class="hl-2">&quot;events&quot;</span><span class="hl-1">, </span><span class="hl-2">&quot;*&quot;</span><span class="hl-1">, { </span><span class="hl-4">type:</span><span class="hl-1"> </span><span class="hl-2">&quot;created&quot;</span><span class="hl-1">, </span><span class="hl-4">id:</span><span class="hl-1"> </span><span class="hl-2">&quot;user:1&quot;</span><span class="hl-1"> });</span><br/><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">client</span><span class="hl-1">.</span><span class="hl-4">bloom</span><span class="hl-1">.</span><span class="hl-0">add</span><span class="hl-1">(</span><span class="hl-2">&quot;seen-filter&quot;</span><span class="hl-1">, </span><span class="hl-2">&quot;user:1&quot;</span><span class="hl-1">);</span>
309
+ <pre><code class="ts"><span class="hl-6">const</span><span class="hl-1"> </span><span class="hl-7">client</span><span class="hl-1"> = </span><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">FerricStoreClient</span><span class="hl-1">.</span><span class="hl-0">fromUrl</span><span class="hl-1">(</span><span class="hl-2">&quot;ferric://127.0.0.1:6388&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-5">codec:</span><span class="hl-1"> </span><span class="hl-6">new</span><span class="hl-1"> </span><span class="hl-0">JsonCodec</span><span class="hl-1">()</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">client</span><span class="hl-1">.</span><span class="hl-5">kv</span><span class="hl-1">.</span><span class="hl-0">set</span><span class="hl-1">(</span><span class="hl-2">&quot;user:1&quot;</span><span class="hl-1">, { </span><span class="hl-5">name:</span><span class="hl-1"> </span><span class="hl-2">&quot;Ada&quot;</span><span class="hl-1"> }, { </span><span class="hl-5">px:</span><span class="hl-1"> </span><span class="hl-9">60_000</span><span class="hl-1"> });</span><br/><span class="hl-6">const</span><span class="hl-1"> </span><span class="hl-7">user</span><span class="hl-1"> = </span><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">client</span><span class="hl-1">.</span><span class="hl-5">kv</span><span class="hl-1">.</span><span class="hl-0">get</span><span class="hl-1">(</span><span class="hl-2">&quot;user:1&quot;</span><span class="hl-1">);</span><br/><br/><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">client</span><span class="hl-1">.</span><span class="hl-5">hash</span><span class="hl-1">.</span><span class="hl-0">hset</span><span class="hl-1">(</span><span class="hl-2">&quot;user:1:profile&quot;</span><span class="hl-1">, { </span><span class="hl-5">email:</span><span class="hl-1"> </span><span class="hl-2">&quot;ada@example.com&quot;</span><span class="hl-1"> });</span><br/><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">client</span><span class="hl-1">.</span><span class="hl-5">lists</span><span class="hl-1">.</span><span class="hl-0">lpush</span><span class="hl-1">(</span><span class="hl-2">&quot;jobs&quot;</span><span class="hl-1">, { </span><span class="hl-5">id:</span><span class="hl-1"> </span><span class="hl-2">&quot;job-1&quot;</span><span class="hl-1"> });</span><br/><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">client</span><span class="hl-1">.</span><span class="hl-5">sets</span><span class="hl-1">.</span><span class="hl-0">sadd</span><span class="hl-1">(</span><span class="hl-2">&quot;seen-users&quot;</span><span class="hl-1">, </span><span class="hl-2">&quot;user:1&quot;</span><span class="hl-1">);</span><br/><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">client</span><span class="hl-1">.</span><span class="hl-5">zset</span><span class="hl-1">.</span><span class="hl-0">zadd</span><span class="hl-1">(</span><span class="hl-2">&quot;leaderboard&quot;</span><span class="hl-1">, [{ </span><span class="hl-5">score:</span><span class="hl-1"> </span><span class="hl-9">42</span><span class="hl-1">, </span><span class="hl-5">member:</span><span class="hl-1"> </span><span class="hl-2">&quot;user:1&quot;</span><span class="hl-1"> }]);</span><br/><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">client</span><span class="hl-1">.</span><span class="hl-5">stream</span><span class="hl-1">.</span><span class="hl-0">xadd</span><span class="hl-1">(</span><span class="hl-2">&quot;events&quot;</span><span class="hl-1">, </span><span class="hl-2">&quot;*&quot;</span><span class="hl-1">, { </span><span class="hl-5">type:</span><span class="hl-1"> </span><span class="hl-2">&quot;created&quot;</span><span class="hl-1">, </span><span class="hl-5">id:</span><span class="hl-1"> </span><span class="hl-2">&quot;user:1&quot;</span><span class="hl-1"> });</span><br/><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">client</span><span class="hl-1">.</span><span class="hl-5">bloom</span><span class="hl-1">.</span><span class="hl-0">add</span><span class="hl-1">(</span><span class="hl-2">&quot;seen-filter&quot;</span><span class="hl-1">, </span><span class="hl-2">&quot;user:1&quot;</span><span class="hl-1">);</span>
302
310
  </code><button type="button">Copy</button></pre>
303
311
 
304
312
  <p>Large unambiguous scalar batches accept an array without spreading, for example
@@ -328,12 +336,12 @@ apply mutations.</p>
328
336
  <h2 id="auto-batching" class="tsd-anchor-link">Auto-Batching<a href="#auto-batching" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h2>
329
337
  <p>The default client is latency-first: each SDK call sends its own native request.</p>
330
338
  <p>For high-throughput services that issue many independent calls concurrently, enable SDK auto-batching:</p>
331
- <pre><code class="ts"><span class="hl-5">const</span><span class="hl-1"> </span><span class="hl-6">client</span><span class="hl-1"> = </span><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">FerricStoreClient</span><span class="hl-1">.</span><span class="hl-0">fromUrl</span><span class="hl-1">(</span><span class="hl-2">&quot;ferric://127.0.0.1:6388&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-4">autoBatch:</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-4">enabled:</span><span class="hl-1"> </span><span class="hl-5">true</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">maxCommands:</span><span class="hl-1"> </span><span class="hl-9">512</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-4">maxDelayMs:</span><span class="hl-1"> </span><span class="hl-9">0</span><br/><span class="hl-1"> }</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-10">Promise</span><span class="hl-1">.</span><span class="hl-0">all</span><span class="hl-1">([</span><br/><span class="hl-1"> </span><span class="hl-4">client</span><span class="hl-1">.</span><span class="hl-4">kv</span><span class="hl-1">.</span><span class="hl-0">set</span><span class="hl-1">(</span><span class="hl-2">&quot;a&quot;</span><span class="hl-1">, </span><span class="hl-2">&quot;1&quot;</span><span class="hl-1">),</span><br/><span class="hl-1"> </span><span class="hl-4">client</span><span class="hl-1">.</span><span class="hl-4">kv</span><span class="hl-1">.</span><span class="hl-0">set</span><span class="hl-1">(</span><span class="hl-2">&quot;b&quot;</span><span class="hl-1">, </span><span class="hl-2">&quot;2&quot;</span><span class="hl-1">),</span><br/><span class="hl-1"> </span><span class="hl-4">client</span><span class="hl-1">.</span><span class="hl-4">hash</span><span class="hl-1">.</span><span class="hl-0">hset</span><span class="hl-1">(</span><span class="hl-2">&quot;user:1&quot;</span><span class="hl-1">, { </span><span class="hl-4">email:</span><span class="hl-1"> </span><span class="hl-2">&quot;ada@example.com&quot;</span><span class="hl-1"> })</span><br/><span class="hl-1">]);</span>
339
+ <pre><code class="ts"><span class="hl-6">const</span><span class="hl-1"> </span><span class="hl-7">client</span><span class="hl-1"> = </span><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">FerricStoreClient</span><span class="hl-1">.</span><span class="hl-0">fromUrl</span><span class="hl-1">(</span><span class="hl-2">&quot;ferric://127.0.0.1:6388&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-5">autoBatch:</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-5">enabled:</span><span class="hl-1"> </span><span class="hl-6">true</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">maxCommands:</span><span class="hl-1"> </span><span class="hl-9">512</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-5">maxDelayMs:</span><span class="hl-1"> </span><span class="hl-9">0</span><br/><span class="hl-1"> }</span><br/><span class="hl-1">});</span><br/><br/><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-10">Promise</span><span class="hl-1">.</span><span class="hl-0">all</span><span class="hl-1">([</span><br/><span class="hl-1"> </span><span class="hl-5">client</span><span class="hl-1">.</span><span class="hl-5">kv</span><span class="hl-1">.</span><span class="hl-0">set</span><span class="hl-1">(</span><span class="hl-2">&quot;a&quot;</span><span class="hl-1">, </span><span class="hl-2">&quot;1&quot;</span><span class="hl-1">),</span><br/><span class="hl-1"> </span><span class="hl-5">client</span><span class="hl-1">.</span><span class="hl-5">kv</span><span class="hl-1">.</span><span class="hl-0">set</span><span class="hl-1">(</span><span class="hl-2">&quot;b&quot;</span><span class="hl-1">, </span><span class="hl-2">&quot;2&quot;</span><span class="hl-1">),</span><br/><span class="hl-1"> </span><span class="hl-5">client</span><span class="hl-1">.</span><span class="hl-5">hash</span><span class="hl-1">.</span><span class="hl-0">hset</span><span class="hl-1">(</span><span class="hl-2">&quot;user:1&quot;</span><span class="hl-1">, { </span><span class="hl-5">email:</span><span class="hl-1"> </span><span class="hl-2">&quot;ada@example.com&quot;</span><span class="hl-1"> })</span><br/><span class="hl-1">]);</span>
332
340
  </code><button type="button">Copy</button></pre>
333
341
 
334
342
  <p>Auto-batching groups eligible concurrent commands into native <code>PIPELINE</code> frames and resolves each original promise independently. Across frames and individual-request fallbacks, same-key write dependencies retain invocation order, while read-only and disjoint-key work remains concurrent. Commands whose direct native representation uses a custom binary body are safely wrapped as typed <code>COMMAND_EXEC</code> pipeline items, preserving one pipeline request instead of issuing each command separately. Blocking/session and control commands such as <code>FLOW.CLAIM_DUE</code>, <code>BLPOP</code>, <code>XREAD</code>, <code>AUTH</code>, <code>PING</code>, <code>OPTIONS</code>, and <code>QUIT</code> bypass auto-batching. Explicit pipelines issue unsupported or connection-blocking items individually; blocking fallbacks and state-changing controls are sequenced with dependent data commands. Other fallbacks remain concurrent unless <code>client.pipeline(commands, { ordered: true })</code> is requested. Individual fallbacks continuously refill a bounded pool instead of starting every request at once; the default limit is 64 and <code>fallbackConcurrency</code> on the pipeline options can tune it per call. Native pipeline paths are unchanged. Reconnecting and topology executors reject connection-local mutations before dispatch, and an uncertain native pipeline is never replayed automatically.</p>
335
343
  <p>Queue workers are latency-first by default. For high-throughput queue workers, use one profile flag:</p>
336
- <pre><code class="ts"><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">emails</span><span class="hl-1">.</span><span class="hl-0">worker</span><span class="hl-1">({ </span><span class="hl-4">profile:</span><span class="hl-1"> </span><span class="hl-2">&quot;throughput&quot;</span><span class="hl-1">, </span><span class="hl-4">concurrency:</span><span class="hl-1"> </span><span class="hl-9">32</span><span class="hl-1"> }).</span><span class="hl-0">run</span><span class="hl-1">(</span><span class="hl-5">async</span><span class="hl-1"> (</span><span class="hl-4">job</span><span class="hl-1">) </span><span class="hl-5">=&gt;</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-0">sendEmail</span><span class="hl-1">(</span><span class="hl-4">job</span><span class="hl-1">.</span><span class="hl-4">id</span><span class="hl-1">);</span><br/><span class="hl-1">});</span>
344
+ <pre><code class="ts"><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">emails</span><span class="hl-1">.</span><span class="hl-0">worker</span><span class="hl-1">({ </span><span class="hl-5">profile:</span><span class="hl-1"> </span><span class="hl-2">&quot;throughput&quot;</span><span class="hl-1">, </span><span class="hl-5">concurrency:</span><span class="hl-1"> </span><span class="hl-9">32</span><span class="hl-1"> }).</span><span class="hl-0">run</span><span class="hl-1">(</span><span class="hl-6">async</span><span class="hl-1"> (</span><span class="hl-5">job</span><span class="hl-1">) </span><span class="hl-6">=&gt;</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-0">sendEmail</span><span class="hl-1">(</span><span class="hl-5">job</span><span class="hl-1">.</span><span class="hl-5">id</span><span class="hl-1">);</span><br/><span class="hl-1">});</span>
337
345
  </code><button type="button">Copy</button></pre>
338
346
 
339
347
  <p>The throughput profile uses compact claims, a larger batch ceiling, and concurrent completion batching. Per-job claim credit follows currently available <code>concurrency</code> (or its <code>workers</code> alias), capped by <code>batchSize</code>; explicit worker options override profile defaults.</p>
@@ -346,10 +354,12 @@ apply mutations.</p>
346
354
  <li><a href="media/signals.ts">signals.ts</a></li>
347
355
  <li><a href="media/value-refs.ts">value-refs.ts</a></li>
348
356
  <li><a href="media/kv-store.ts">kv-store.ts</a></li>
357
+ <li><a href="media/langgraph.ts">langgraph.ts</a></li>
358
+ <li><a href="media/openai-agents-session.ts">openai-agents-session.ts</a></li>
349
359
  </ul>
350
360
  <h2 id="codecs" class="tsd-anchor-link">Codecs<a href="#codecs" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h2>
351
361
  <p><code>RawCodec</code> is the default and works with <code>Buffer</code>, <code>Uint8Array</code>, and strings. Use <code>JsonCodec</code> for language-neutral structured payloads and results.</p>
352
- <pre><code class="ts"><span class="hl-5">const</span><span class="hl-1"> </span><span class="hl-6">flow</span><span class="hl-1"> = </span><span class="hl-3">await</span><span class="hl-1"> </span><span class="hl-4">FerricStoreClient</span><span class="hl-1">.</span><span class="hl-0">fromUrl</span><span class="hl-1">(</span><span class="hl-2">&quot;ferric://127.0.0.1:6388&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-4">codec:</span><span class="hl-1"> </span><span class="hl-5">new</span><span class="hl-1"> </span><span class="hl-0">JsonCodec</span><span class="hl-1">()</span><br/><span class="hl-1">});</span>
362
+ <pre><code class="ts"><span class="hl-6">const</span><span class="hl-1"> </span><span class="hl-7">flow</span><span class="hl-1"> = </span><span class="hl-4">await</span><span class="hl-1"> </span><span class="hl-5">FerricStoreClient</span><span class="hl-1">.</span><span class="hl-0">fromUrl</span><span class="hl-1">(</span><span class="hl-2">&quot;ferric://127.0.0.1:6388&quot;</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-5">codec:</span><span class="hl-1"> </span><span class="hl-6">new</span><span class="hl-1"> </span><span class="hl-0">JsonCodec</span><span class="hl-1">()</span><br/><span class="hl-1">});</span>
353
363
  </code><button type="button">Copy</button></pre>
354
364
 
355
365
  <h2 id="design-notes" class="tsd-anchor-link">Design Notes<a href="#design-notes" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h2>
@@ -367,13 +377,13 @@ apply mutations.</p>
367
377
 
368
378
  <p><code>npm run check</code> runs strict TypeScript, ESLint, Vitest, and the package build.</p>
369
379
  <p>Use Docker Compose for local integration testing:</p>
370
- <pre><code class="bash"><span class="hl-0">npm</span><span class="hl-1"> </span><span class="hl-2">run</span><span class="hl-1"> </span><span class="hl-2">integration:up</span><br/><span class="hl-4">FERRICSTORE_INTEGRATION</span><span class="hl-1">=</span><span class="hl-2">1</span><span class="hl-1"> </span><span class="hl-0">npm</span><span class="hl-1"> </span><span class="hl-2">run</span><span class="hl-1"> </span><span class="hl-2">test:integration</span><br/><span class="hl-0">npm</span><span class="hl-1"> </span><span class="hl-2">run</span><span class="hl-1"> </span><span class="hl-2">integration:down</span>
380
+ <pre><code class="bash"><span class="hl-0">npm</span><span class="hl-1"> </span><span class="hl-2">run</span><span class="hl-1"> </span><span class="hl-2">integration:up</span><br/><span class="hl-5">FERRICSTORE_INTEGRATION</span><span class="hl-1">=</span><span class="hl-2">1</span><span class="hl-1"> </span><span class="hl-0">npm</span><span class="hl-1"> </span><span class="hl-2">run</span><span class="hl-1"> </span><span class="hl-2">test:integration</span><br/><span class="hl-0">npm</span><span class="hl-1"> </span><span class="hl-2">run</span><span class="hl-1"> </span><span class="hl-2">integration:down</span>
371
381
  </code><button type="button">Copy</button></pre>
372
382
 
373
383
  <p>Benchmark raw FQL and the record convenience layer against a live server. The default
374
384
  comparison interleaves both paths and fails if either performs more than one <code>FLOW.QUERY</code>
375
385
  or any <code>FLOW.GET</code> hydration per operation:</p>
376
- <pre><code class="bash"><span class="hl-0">npm</span><span class="hl-1"> </span><span class="hl-2">run</span><span class="hl-1"> </span><span class="hl-2">build</span><br/><span class="hl-0">npm</span><span class="hl-1"> </span><span class="hl-2">run</span><span class="hl-1"> </span><span class="hl-2">bench:flow-query</span><span class="hl-1"> </span><span class="hl-5">--</span><span class="hl-1"> </span><span class="hl-5">--requests</span><span class="hl-1"> </span><span class="hl-9">500</span><span class="hl-1"> </span><span class="hl-5">--concurrency</span><span class="hl-1"> </span><span class="hl-9">2</span><span class="hl-1"> </span><span class="hl-5">--rows</span><span class="hl-1"> </span><span class="hl-9">100</span><span class="hl-1"> </span><span class="hl-5">--pretty</span>
386
+ <pre><code class="bash"><span class="hl-0">npm</span><span class="hl-1"> </span><span class="hl-2">run</span><span class="hl-1"> </span><span class="hl-2">build</span><br/><span class="hl-0">npm</span><span class="hl-1"> </span><span class="hl-2">run</span><span class="hl-1"> </span><span class="hl-2">bench:flow-query</span><span class="hl-1"> </span><span class="hl-6">--</span><span class="hl-1"> </span><span class="hl-6">--requests</span><span class="hl-1"> </span><span class="hl-9">500</span><span class="hl-1"> </span><span class="hl-6">--concurrency</span><span class="hl-1"> </span><span class="hl-9">2</span><span class="hl-1"> </span><span class="hl-6">--rows</span><span class="hl-1"> </span><span class="hl-9">100</span><span class="hl-1"> </span><span class="hl-6">--pretty</span>
377
387
  </code><button type="button">Copy</button></pre>
378
388
 
379
389
  <p>Generate API docs with:</p>