@ferricstore/ferricstore 0.11.10 → 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 (54) hide show
  1. package/README.md +32 -3
  2. package/dist/durability-DlDCsdlo.d.cts +16 -0
  3. package/dist/durability-DplL0SbW.d.ts +16 -0
  4. package/dist/index.cjs +13 -2
  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 +13 -2
  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/functions/httpCommandDisposition.html +1 -1
  48. package/docs/api/index.html +44 -24
  49. package/docs/api/media/agent-frameworks.md +159 -0
  50. package/docs/api/media/langgraph.ts +23 -0
  51. package/docs/api/media/openai-agents-session.ts +13 -0
  52. package/docs/api/types/HTTPCommandDisposition.html +1 -1
  53. package/docs/api/variables/FERRICSTORE_SDK_VERSION.html +1 -1
  54. package/package.json +42 -2
@@ -19,30 +19,38 @@
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.10</code> requires FerricStore server <code>0.11.4</code> or newer. With
24
- FerricStore 0.11.10 it negotiates compact Stream mode 34 for homogeneous auto-ID
31
+ <p>TypeScript SDK <code>0.12.0</code> requires FerricStore server <code>0.11.4</code> or newer. With
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.
27
35
  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.10@sha256:3af390b7429ea3fea2983938eb7adcdd3e8005d06c67473f769f29ebd48e8ab3</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
@@ -84,6 +92,16 @@ are not auto-coalesced. Redirects intentionally retain authentication and custom
84
92
  headers across origins, so configure only endpoints and redirect targets you
85
93
  trust. Use <code>ferric://</code> or <code>ferrics://</code> whenever connection-local behavior is
86
94
  required.</p>
95
+ <p>Run the complete HTTP-compatible integration surface through a real TLS
96
+ listener with ACL authentication using:</p>
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>
98
+ </code><button type="button">Copy</button></pre>
99
+
100
+ <p>The runner creates a private CA, verifies that unauthenticated access and a
101
+ restricted user's forbidden <code>SET</code> are rejected, and sets
102
+ <code>FERRICSTORE_USERNAME</code>, <code>FERRICSTORE_PASSWORD</code>, and <code>FERRICSTORE_CA_FILE</code> for
103
+ the tests. Native-only subscriptions, topology, and session controls stay in
104
+ the native integration jobs.</p>
87
105
  <h2 id="cluster-aware-client" class="tsd-anchor-link">Cluster-aware client<a href="#cluster-aware-client" 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>
88
106
  <p>For a single node, use <code>fromUrl</code>. For a FerricStore cluster, pass multiple seed URLs. The SDK fetches the server <code>SHARDS</code> topology, routes keyed commands to the current shard leader, and refuses learned hosts outside the seed-host trust set by default. The creation promise resolves only after startup and authentication succeed; cluster creation also waits for the initial topology, so connection failures reject the corresponding <code>await</code> directly.</p>
89
107
  <p>Cross-shard pipelines are grouped into one native pipeline per leader/lane and
@@ -93,7 +111,7 @@ fan-out; atomic multi-key commands are never split client-side. Pass
93
111
  <code>{ ordered: true }</code> as the second argument to <code>client.pipeline()</code> when later
94
112
  commands depend on earlier ones and the transport may need an individual or
95
113
  cross-route fallback.</p>
96
- <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>
97
115
  </code><button type="button">Copy</button></pre>
98
116
 
99
117
  <p>Learned topology endpoints are checked before connection. The default
@@ -150,7 +168,7 @@ subscriptions instead.</p>
150
168
  <p>The default integration suite targets one local development server. Real HA,
151
169
  TLS, and authentication deployments can be verified with the opt-in deployment
152
170
  suite:</p>
153
- <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>
154
172
  </code><button type="button">Copy</button></pre>
155
173
 
156
174
  <p>The HA fixture must advertise at least two reachable leader endpoints. The auth
@@ -160,7 +178,7 @@ rejects its plaintext listener. HA TLS/auth options are available through
160
178
  <code>FERRICSTORE_HA_TLS_CA_FILE</code>, <code>FERRICSTORE_HA_TLS_SERVERNAME</code>,
161
179
  <code>FERRICSTORE_HA_USERNAME</code>, and <code>FERRICSTORE_HA_PASSWORD</code>.</p>
162
180
  <p>You can also keep one primary URL and add seeds:</p>
163
- <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>
164
182
  </code><button type="button">Copy</button></pre>
165
183
 
166
184
  <p>Native connections honor the flow-control windows advertised by the
@@ -182,11 +200,11 @@ bounded client queue. Set <code>nativeOptions.maxQueuedWriteBytes</code> to cont
182
200
  queue (default 64 MiB, or <code>0</code> to reject subsequent writes immediately). Healthy
183
201
  socket writes still go directly to <code>socket.write</code> without entering the queue.</p>
184
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>
185
- <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>
186
204
  </code><button type="button">Copy</button></pre>
187
205
 
188
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>
189
- <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>
190
208
  </code><button type="button">Copy</button></pre>
191
209
 
192
210
  <p>Handlers return explicit durable outcomes:</p>
@@ -232,12 +250,12 @@ unchanged; all fenced mutation APIs accept the exported <code>FencingToken</code
232
250
  and native compact claim and batch paths preserve its signed 64-bit value
233
251
  exactly.</p>
234
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>
235
- <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>
236
254
  </code><button type="button">Copy</button></pre>
237
255
 
238
256
  <p>Flow attributes can be returned without hydrating each record and can be
239
257
  updated atomically with the fenced state mutation:</p>
240
- <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>
241
259
  </code><button type="button">Copy</button></pre>
242
260
 
243
261
  <p>The low-level client also exposes the fused <code>startAndClaim</code>, <code>stepContinue</code>,
@@ -245,13 +263,13 @@ and <code>runStepsMany</code> operations, schedule administration, Flow statisti
245
263
  attribute queries, effects, approvals, circuits, budgets, and distributed
246
264
  limits. History supports the complete server filter surface, including event,
247
265
  time, and version bounds plus cold/consistent reads and payload hydration:</p>
248
- <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>
249
267
  </code><button type="button">Copy</button></pre>
250
268
 
251
269
  <p>Overdue interval schedules use bounded <code>fire_once</code> catch-up. Recovery creates
252
270
  one target, coalesces additional elapsed periods in constant time, and sets the
253
271
  next run one full interval after recovery:</p>
254
- <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>
255
273
  </code><button type="button">Copy</button></pre>
256
274
 
257
275
  <p><code>ScheduleRecord</code> exposes the complete recurrence configuration through
@@ -278,7 +296,7 @@ When planning fails, <code>state</code> is <code>&quot;failed&quot;</code>, <cod
278
296
  <code>&quot;planning_failed&quot;</code>, and <code>last_planning_error</code> contains the actionable error.
279
297
  <code>scheduleDelete()</code> resolves to <code>undefined</code> only after an <code>OK</code> server reply.</p>
280
298
  <p>FIFO Flow state policy is opt-in per state:</p>
281
- <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>
282
300
  </code><button type="button">Copy</button></pre>
283
301
 
284
302
  <p>FIFO states require a <code>partitionKey</code>; priority is for parallel states.
@@ -288,7 +306,7 @@ FIFO ordering is enforced by the server per <code>(type, state, partitionKey)</c
288
306
  concurrency remains available across different partitions.</p>
289
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>
290
308
  <p>The same client exposes typed helpers for FerricStore's Redis-compatible store commands:</p>
291
- <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>
292
310
  </code><button type="button">Copy</button></pre>
293
311
 
294
312
  <p>Large unambiguous scalar batches accept an array without spreading, for example
@@ -318,12 +336,12 @@ apply mutations.</p>
318
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>
319
337
  <p>The default client is latency-first: each SDK call sends its own native request.</p>
320
338
  <p>For high-throughput services that issue many independent calls concurrently, enable SDK auto-batching:</p>
321
- <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>
322
340
  </code><button type="button">Copy</button></pre>
323
341
 
324
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>
325
343
  <p>Queue workers are latency-first by default. For high-throughput queue workers, use one profile flag:</p>
326
- <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>
327
345
  </code><button type="button">Copy</button></pre>
328
346
 
329
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>
@@ -336,10 +354,12 @@ apply mutations.</p>
336
354
  <li><a href="media/signals.ts">signals.ts</a></li>
337
355
  <li><a href="media/value-refs.ts">value-refs.ts</a></li>
338
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>
339
359
  </ul>
340
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>
341
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>
342
- <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>
343
363
  </code><button type="button">Copy</button></pre>
344
364
 
345
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>
@@ -357,13 +377,13 @@ apply mutations.</p>
357
377
 
358
378
  <p><code>npm run check</code> runs strict TypeScript, ESLint, Vitest, and the package build.</p>
359
379
  <p>Use Docker Compose for local integration testing:</p>
360
- <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>
361
381
  </code><button type="button">Copy</button></pre>
362
382
 
363
383
  <p>Benchmark raw FQL and the record convenience layer against a live server. The default
364
384
  comparison interleaves both paths and fails if either performs more than one <code>FLOW.QUERY</code>
365
385
  or any <code>FLOW.GET</code> hydration per operation:</p>
366
- <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>
367
387
  </code><button type="button">Copy</button></pre>
368
388
 
369
389
  <p>Generate API docs with:</p>