@ferricstore/ferricstore 0.1.6 → 0.2.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 (211) hide show
  1. package/README.md +232 -9
  2. package/dist/index.cjs +14910 -4799
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +1637 -867
  5. package/dist/index.d.ts +1637 -867
  6. package/dist/index.js +14963 -4862
  7. package/dist/index.js.map +1 -1
  8. package/docs/api/assets/hierarchy.js +1 -1
  9. package/docs/api/assets/navigation.js +1 -1
  10. package/docs/api/assets/search.js +1 -1
  11. package/docs/api/classes/BitmapStore.html +2 -2
  12. package/docs/api/classes/BloomFilterStore.html +4 -2
  13. package/docs/api/classes/ClaimHydrationError.html +41 -0
  14. package/docs/api/classes/ConnectionClosedError.html +40 -0
  15. package/docs/api/classes/CountMinSketchStore.html +3 -2
  16. package/docs/api/classes/CuckooFilterStore.html +3 -2
  17. package/docs/api/classes/FerricStoreClient.html +204 -146
  18. package/docs/api/classes/FerricStoreError.html +6 -3
  19. package/docs/api/classes/FlowAlreadyExistsError.html +6 -3
  20. package/docs/api/classes/FlowBatchError.html +42 -0
  21. package/docs/api/classes/FlowNotFoundError.html +6 -3
  22. package/docs/api/classes/FlowWrongStateError.html +6 -3
  23. package/docs/api/classes/GeoStore.html +4 -2
  24. package/docs/api/classes/HashStore.html +2 -2
  25. package/docs/api/classes/HyperLogLogStore.html +3 -2
  26. package/docs/api/classes/InvalidCommandError.html +6 -3
  27. package/docs/api/classes/KeyValueStore.html +2 -2
  28. package/docs/api/classes/LeaseRenewalError.html +34 -0
  29. package/docs/api/classes/ListStore.html +6 -2
  30. package/docs/api/classes/LockHeldError.html +6 -3
  31. package/docs/api/classes/LockNotOwnedError.html +6 -3
  32. package/docs/api/classes/NativeAdapter.html +4 -2
  33. package/docs/api/classes/OverloadedError.html +6 -4
  34. package/docs/api/classes/Queue.html +2 -2
  35. package/docs/api/classes/QueueClient.html +2 -2
  36. package/docs/api/classes/QueueCompletionError.html +35 -0
  37. package/docs/api/classes/QueueWorker.html +2 -2
  38. package/docs/api/classes/ReconnectingExecutor.html +8 -2
  39. package/docs/api/classes/RequestTimeoutError.html +41 -0
  40. package/docs/api/classes/RerouteError.html +39 -0
  41. package/docs/api/classes/RoutingTopology.html +2 -2
  42. package/docs/api/classes/SetStore.html +4 -2
  43. package/docs/api/classes/SortedSetStore.html +3 -2
  44. package/docs/api/classes/StaleLeaseError.html +6 -3
  45. package/docs/api/classes/StreamStore.html +2 -2
  46. package/docs/api/classes/TDigestStore.html +2 -2
  47. package/docs/api/classes/TopKStore.html +6 -2
  48. package/docs/api/classes/TopologyNativeAdapterPool.html +7 -2
  49. package/docs/api/classes/Workflow.html +4 -2
  50. package/docs/api/classes/WorkflowClient.html +2 -2
  51. package/docs/api/classes/WorkflowContext.html +2 -2
  52. package/docs/api/classes/WorkflowFlowCommands.html +3 -2
  53. package/docs/api/classes/WorkflowWorker.html +2 -2
  54. package/docs/api/functions/classifyServerError.html +1 -1
  55. package/docs/api/functions/complete.html +1 -1
  56. package/docs/api/functions/fail.html +1 -1
  57. package/docs/api/functions/isOutcome.html +1 -0
  58. package/docs/api/functions/isReconnectableClosedConnectionError.html +1 -1
  59. package/docs/api/functions/mapException.html +1 -1
  60. package/docs/api/functions/retry.html +1 -1
  61. package/docs/api/functions/transition.html +1 -1
  62. package/docs/api/hierarchy.html +1 -1
  63. package/docs/api/index.html +162 -12
  64. package/docs/api/interfaces/AdminListOptions.html +4 -0
  65. package/docs/api/interfaces/ApprovalListOptions.html +6 -0
  66. package/docs/api/interfaces/ApprovalRequestOptions.html +11 -0
  67. package/docs/api/interfaces/AttributeQueryOptions.html +5 -0
  68. package/docs/api/interfaces/AutoBatchOptions.html +3 -2
  69. package/docs/api/interfaces/BackpressurePolicy.html +2 -2
  70. package/docs/api/interfaces/BudgetCommitOptions.html +3 -0
  71. package/docs/api/interfaces/BudgetReserveOptions.html +5 -0
  72. package/docs/api/interfaces/CancelOptions.html +12 -6
  73. package/docs/api/interfaces/ChildSpec.html +2 -2
  74. package/docs/api/interfaces/CircuitOpenOptions.html +4 -0
  75. package/docs/api/interfaces/ClaimDueOptions.html +4 -2
  76. package/docs/api/interfaces/ClaimHydrationItem.html +3 -0
  77. package/docs/api/interfaces/ClaimedItem.html +4 -3
  78. package/docs/api/interfaces/CommandExecutor.html +7 -2
  79. package/docs/api/interfaces/CompleteJobsAndClaimJobsResult.html +11 -0
  80. package/docs/api/interfaces/CompleteManyOptions.html +14 -0
  81. package/docs/api/interfaces/CompleteOptions.html +4 -2
  82. package/docs/api/interfaces/CompleteOutcome.html +4 -2
  83. package/docs/api/interfaces/CountMinMergeOptions.html +2 -2
  84. package/docs/api/interfaces/CreateItem.html +3 -2
  85. package/docs/api/interfaces/CreateManyOptions.html +10 -2
  86. package/docs/api/interfaces/CreateOptions.html +6 -2
  87. package/docs/api/interfaces/EffectCompensateOptions.html +7 -0
  88. package/docs/api/interfaces/EffectConfirmOptions.html +7 -0
  89. package/docs/api/interfaces/EffectFailOptions.html +8 -0
  90. package/docs/api/interfaces/EffectReserveOptions.html +8 -0
  91. package/docs/api/interfaces/EffectStatusOptions.html +5 -0
  92. package/docs/api/interfaces/ExecutePipelineOptions.html +6 -2
  93. package/docs/api/interfaces/ExtendLeaseOptions.html +7 -0
  94. package/docs/api/interfaces/FailOptions.html +4 -2
  95. package/docs/api/interfaces/FailOutcome.html +4 -2
  96. package/docs/api/interfaces/FencedItem.html +2 -2
  97. package/docs/api/interfaces/FerricStoreClientFromUrlOptions.html +6 -2
  98. package/docs/api/interfaces/FerricStoreClientOptions.html +6 -2
  99. package/docs/api/interfaces/FetchOrComputeComputeResult.html +9 -0
  100. package/docs/api/interfaces/FetchOrComputeHitResult.html +6 -0
  101. package/docs/api/interfaces/FlowBatchCompletedItem.html +3 -0
  102. package/docs/api/interfaces/FlowMaxActiveFailure.html +3 -0
  103. package/docs/api/interfaces/FlowPolicyOptions.html +6 -2
  104. package/docs/api/interfaces/FlowRecord.html +5 -2
  105. package/docs/api/interfaces/FlowStatePolicy.html +2 -2
  106. package/docs/api/interfaces/FlowStatsOptions.html +6 -0
  107. package/docs/api/interfaces/GeoMember.html +2 -2
  108. package/docs/api/interfaces/GovernanceLedgerOptions.html +6 -0
  109. package/docs/api/interfaces/HistoryOptions.html +16 -0
  110. package/docs/api/interfaces/InvocationCreateOptions.html +2 -2
  111. package/docs/api/interfaces/KeyInfo.html +2 -2
  112. package/docs/api/interfaces/LeaseMutationOptions.html +14 -0
  113. package/docs/api/interfaces/LimitAmountOptions.html +4 -0
  114. package/docs/api/interfaces/LimitLeaseOptions.html +6 -0
  115. package/docs/api/interfaces/LimitListOptions.html +5 -0
  116. package/docs/api/interfaces/LimitReleaseOptions.html +4 -0
  117. package/docs/api/interfaces/MutateOptions.html +4 -2
  118. package/docs/api/interfaces/NamedValueMutation.html +4 -2
  119. package/docs/api/interfaces/NativeAdapterOptions.html +21 -9
  120. package/docs/api/interfaces/NativeClientOptions.html +43 -0
  121. package/docs/api/interfaces/NativeProtocolEvent.html +5 -0
  122. package/docs/api/interfaces/ProtocolCommand.html +2 -2
  123. package/docs/api/interfaces/QueueOptions.html +2 -2
  124. package/docs/api/interfaces/QueueWorkerResult.html +2 -2
  125. package/docs/api/interfaces/RangeLimit.html +2 -2
  126. package/docs/api/interfaces/RateLimitResult.html +2 -2
  127. package/docs/api/interfaces/ReadOptions.html +2 -2
  128. package/docs/api/interfaces/ReclaimOptions.html +4 -2
  129. package/docs/api/interfaces/ReconnectOptions.html +8 -2
  130. package/docs/api/interfaces/RequestContext.html +2 -2
  131. package/docs/api/interfaces/RequestContextOptions.html +2 -2
  132. package/docs/api/interfaces/RetryOptions.html +4 -2
  133. package/docs/api/interfaces/RetryOutcome.html +4 -2
  134. package/docs/api/interfaces/RetryPolicy.html +2 -2
  135. package/docs/api/interfaces/RoutingEndpoint.html +2 -2
  136. package/docs/api/interfaces/RoutingRoute.html +2 -2
  137. package/docs/api/interfaces/RunStepsItem.html +3 -0
  138. package/docs/api/interfaces/RunStepsManyOptions.html +11 -0
  139. package/docs/api/interfaces/ScanOptions.html +4 -2
  140. package/docs/api/interfaces/ScheduleFireDueOptions.html +5 -0
  141. package/docs/api/interfaces/ScheduleListOptions.html +9 -0
  142. package/docs/api/interfaces/ScheduleOptions.html +16 -0
  143. package/docs/api/interfaces/SearchOptions.html +2 -2
  144. package/docs/api/interfaces/StartAndClaimOptions.html +19 -0
  145. package/docs/api/interfaces/StateOptions.html +2 -2
  146. package/docs/api/interfaces/StateRegistration.html +2 -2
  147. package/docs/api/interfaces/StepContinueOptions.html +20 -0
  148. package/docs/api/interfaces/StoreCommandClient.html +3 -2
  149. package/docs/api/interfaces/TDigestCreateOptions.html +2 -2
  150. package/docs/api/interfaces/TDigestMergeOptions.html +2 -2
  151. package/docs/api/interfaces/TopologyNativeAdapterOptions.html +39 -0
  152. package/docs/api/interfaces/TransitionOptions.html +4 -2
  153. package/docs/api/interfaces/TransitionOutcome.html +4 -2
  154. package/docs/api/interfaces/ValueConfig.html +2 -2
  155. package/docs/api/interfaces/WorkerConfig.html +23 -2
  156. package/docs/api/interfaces/WorkflowOptions.html +2 -2
  157. package/docs/api/interfaces/WorkflowWorkerResult.html +2 -2
  158. package/docs/api/interfaces/XReadStream.html +2 -2
  159. package/docs/api/interfaces/ZAddMember.html +2 -2
  160. package/docs/api/media/kv-store.ts +2 -2
  161. package/docs/api/modules.html +1 -1
  162. package/docs/api/types/BackoffKind.html +1 -1
  163. package/docs/api/types/CollectionScanOptions.html +2 -0
  164. package/docs/api/types/Command.html +1 -1
  165. package/docs/api/types/CommandArgument.html +1 -1
  166. package/docs/api/types/ConnectionRequestDisposition.html +2 -0
  167. package/docs/api/types/EndpointPolicy.html +2 -1
  168. package/docs/api/types/ExceptionPolicy.html +1 -1
  169. package/docs/api/types/ExpiryCondition.html +1 -0
  170. package/docs/api/types/FencingToken.html +2 -0
  171. package/docs/api/types/FetchOrComputeFencedResult.html +1 -0
  172. package/docs/api/types/FetchOrComputeResult.html +1 -0
  173. package/docs/api/types/FlowAdminRecord.html +1 -0
  174. package/docs/api/types/FlowStateMode.html +1 -1
  175. package/docs/api/types/FlowStatePolicyLike.html +1 -1
  176. package/docs/api/types/GeoAddOptions.html +2 -0
  177. package/docs/api/types/GetExOptions.html +2 -0
  178. package/docs/api/types/HashScanResult.html +2 -0
  179. package/docs/api/types/IntegerReply.html +2 -0
  180. package/docs/api/types/ManagementPairs.html +1 -1
  181. package/docs/api/types/MaxActiveMs.html +1 -0
  182. package/docs/api/types/Outcome.html +1 -1
  183. package/docs/api/types/QueueBatchHandler.html +1 -1
  184. package/docs/api/types/QueueHandler.html +1 -1
  185. package/docs/api/types/QueueJob.html +1 -1
  186. package/docs/api/types/RequestDisposition.html +1 -0
  187. package/docs/api/types/SearchStateMeta.html +1 -1
  188. package/docs/api/types/SetOptions.html +2 -0
  189. package/docs/api/types/SetScanResult.html +2 -0
  190. package/docs/api/types/SortedSetScanResult.html +2 -0
  191. package/docs/api/types/StateMeta.html +1 -1
  192. package/docs/api/types/StateMetaValue.html +1 -1
  193. package/docs/api/types/TopKReserveOptions.html +1 -0
  194. package/docs/api/types/WorkerProfile.html +1 -1
  195. package/docs/api/types/WorkerRefillStrategy.html +1 -0
  196. package/docs/api/types/WorkflowHandler.html +1 -1
  197. package/docs/api/types/ZAddOptions.html +2 -0
  198. package/docs/api/variables/COMMAND_OPCODES.html +1 -1
  199. package/docs/api/variables/FERRICSTORE_MINIMUM_SERVER_VERSION.html +2 -0
  200. package/docs/api/variables/FERRICSTORE_NATIVE_PROTOCOL_VERSION.html +2 -0
  201. package/docs/api/variables/FERRICSTORE_SDK_VERSION.html +2 -0
  202. package/docs/python-parity.md +12 -6
  203. package/package.json +10 -1
  204. package/docs/api/classes/JsonStore.html +0 -15
  205. package/docs/api/interfaces/FetchOrComputeResult.html +0 -6
  206. package/docs/api/interfaces/GeoAddOptions.html +0 -4
  207. package/docs/api/interfaces/GetExOptions.html +0 -6
  208. package/docs/api/interfaces/JsonSetOptions.html +0 -3
  209. package/docs/api/interfaces/SetOptions.html +0 -9
  210. package/docs/api/interfaces/TopKReserveOptions.html +0 -4
  211. package/docs/api/interfaces/ZAddOptions.html +0 -6
package/README.md CHANGED
@@ -16,6 +16,14 @@ npm install @ferricstore/ferricstore
16
16
 
17
17
  Requires Node.js 22.22 or newer. The SDK ships ESM and CommonJS builds and is tested with Node 22, 24, and 26.
18
18
 
19
+ ## Compatibility
20
+
21
+ SDK `0.2.x` requires FerricStore server `0.8.0` or newer. FerricStore 0.8 is a
22
+ breaking beta API contract update, while the native wire protocol remains v1
23
+ (`FSNP` framing and existing opcode numbers are unchanged). Capabilities and
24
+ response-size limits are negotiated per connection from the HELLO-shaped
25
+ startup response rather than inferred from a server version table.
26
+
19
27
  ESM:
20
28
 
21
29
  ```ts
@@ -34,12 +42,20 @@ const { FerricStoreClient, JsonCodec } = require("@ferricstore/ferricstore");
34
42
  docker run -p 6388:6388 \
35
43
  -e FERRICSTORE_PROTECTED_MODE=false \
36
44
  -v ferricstore_data:/data \
37
- ghcr.io/ferricstore/ferricstore:0.7.5
45
+ ghcr.io/ferricstore/ferricstore:0.8.0
38
46
  ```
39
47
 
40
48
  ## Cluster-aware client
41
49
 
42
- For a single node, use `fromUrl`. For a FerricStore cluster, pass multiple seed URLs. The SDK fetches the server `SHARDS` topology, routes keyed commands to the current shard leader, and refuses learned hosts outside the seed-host trust set by default.
50
+ For a single node, use `fromUrl`. For a FerricStore cluster, pass multiple seed URLs. The SDK fetches the server `SHARDS` 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 `await` directly.
51
+
52
+ Cross-shard pipelines are grouped into one native pipeline per leader/lane and
53
+ merged back into caller order. Decomposable multi-key commands (`MGET`,
54
+ `EXISTS`, `DEL`, `UNLINK`, and `FLOW.VALUE.MGET`) use the same parallel shard
55
+ fan-out; atomic multi-key commands are never split client-side. Pass
56
+ `{ ordered: true }` as the second argument to `client.pipeline()` when later
57
+ commands depend on earlier ones and the transport may need an individual or
58
+ cross-route fallback.
43
59
 
44
60
  ```ts
45
61
  const flow = await FerricStoreClient.fromUrls(
@@ -53,6 +69,8 @@ const flow = await FerricStoreClient.fromUrls(
53
69
  nativeOptions: {
54
70
  // Use "any" only inside a trusted private network.
55
71
  endpointPolicy: "seed_hosts",
72
+ // Bound client-side route fan-out; freed slots refill immediately.
73
+ topologyConcurrency: 16,
56
74
  warmConnections: true
57
75
  }
58
76
  }
@@ -62,6 +80,86 @@ await flow.refreshTopology();
62
80
  console.log(await flow.route("tenant-a:order-1"));
63
81
  ```
64
82
 
83
+ Learned topology endpoints are checked before connection. The default
84
+ `"seed_hosts"` policy permits exact seed endpoints plus `trustedHosts`;
85
+ `"none"` permits exact seed endpoints only. Use `"any"` only when every
86
+ server-advertised endpoint is already inside a trusted network boundary.
87
+ All HA seed URLs must use the same `ferric://` or `ferrics://` transport;
88
+ `tlsOptions` configures a secure transport but does not change a URL's scheme.
89
+ Each seed connection uses only the credentials embedded in its own URL. The
90
+ first complete seed credential pair is reused for learned cluster endpoints,
91
+ but never overrides another seed URL, even when that seed is first reached
92
+ through learned topology. Duplicate seed endpoints with conflicting effective
93
+ credentials are rejected. Explicit `nativeOptions.username` and `password`
94
+ remain an intentional cluster-wide override.
95
+
96
+ The option types follow ownership: `NativeAdapterOptions` contains direct
97
+ connection settings, `TopologyNativeAdapterOptions` adds learned-endpoint
98
+ policy, and `NativeClientOptions` adds reconnect/HA seed selection. Passing a
99
+ higher-layer option to `NativeAdapter.fromUrl()` is rejected instead of being
100
+ silently ignored.
101
+
102
+ `topologyConcurrency` defaults to 16 and bounds client-side per-route work such
103
+ as cross-shard fan-out, split pipelines, warm-up, and shutdown. It uses
104
+ continuous slot filling: when one route finishes, the next waiting route starts
105
+ without waiting for the rest of the current group. This setting is local to one
106
+ client process or pod; it does not configure FerricStore server concurrency.
107
+
108
+ Automatic reconnect retries only an operation rejected before it could be
109
+ written. If a connection closes while a request is in flight, the SDK surfaces
110
+ that error because the server may already have applied the command; it does not
111
+ replay an uncertain mutation. A later request reconnects normally. Use
112
+ FerricFlow fencing or command-level idempotency when the caller needs safe
113
+ application retries. `ConnectionClosedError.requestDisposition` exposes this
114
+ decision as `"unsent"` or `"possibly_sent"` when the transport can classify it;
115
+ unclassified failures are treated conservatively as possibly sent.
116
+ `RequestTimeoutError.requestDisposition` provides the same retry-safety signal:
117
+ timeouts while waiting for a local flow-control or write-queue slot are
118
+ `"unsent"`, while a request whose frame entered the socket is
119
+ `"possibly_sent"`. Do not automatically retry a possibly-sent mutation.
120
+ `autoReconnect` accepts `maxRetries`, `baseDelayMs`, `maxDelayMs`, and
121
+ `jitterPct`; backoff is applied only after a reconnect attempt itself fails.
122
+
123
+ Topology-aware clients retry one routed command or one physical fused pipeline
124
+ after a successful topology refresh only when the server's typed reroute error
125
+ explicitly reports both `retryable: true` and `safe_to_retry: true`. Producer
126
+ backpressure follows the same flags and honors `retry_after_ms` within the
127
+ configured delay cap. Split or scattered pipelines and uncertain connection
128
+ failures are never replayed.
129
+
130
+ Connection-local state mutations (`AUTH`, `CLIENT SETNAME`, `QUIT`, `RESET`,
131
+ and related native controls) are rejected on reconnecting and topology clients
132
+ because they cannot be applied atomically to every current and future socket.
133
+ Configure `nativeOptions.username`, `password`, `clientName`, and `events` when
134
+ creating those clients, and use `close()` for shutdown. A directly managed
135
+ single `NativeAdapter` retains connection-local command semantics. Native
136
+ `CLIENT TRACKING` and `CLIENT CACHING` are unsupported; use native event
137
+ subscriptions instead.
138
+
139
+ ### Deployment integration tests
140
+
141
+ The default integration suite targets one local development server. Real HA,
142
+ TLS, and authentication deployments can be verified with the opt-in deployment
143
+ suite:
144
+
145
+ ```bash
146
+ FERRICSTORE_HA_URLS=ferric://fs0:6388,ferric://fs1:6388 npm run test:integration:deployment
147
+
148
+ FERRICSTORE_TLS_URL=ferrics://fs0:6389 \
149
+ FERRICSTORE_TLS_CA_FILE=/path/to/ca.pem \
150
+ npm run test:integration:deployment
151
+
152
+ FERRICSTORE_AUTH_URL=ferric://app:secret@fs0:6388 \
153
+ npm run test:integration:deployment
154
+ ```
155
+
156
+ The HA fixture must advertise at least two reachable leader endpoints. The auth
157
+ fixture must require credentials even for the default user. Set
158
+ `FERRICSTORE_TLS_PLAINTEXT_URL` as well to verify that a TLS-only deployment
159
+ rejects its plaintext listener. HA TLS/auth options are available through
160
+ `FERRICSTORE_HA_TLS_CA_FILE`, `FERRICSTORE_HA_TLS_SERVERNAME`,
161
+ `FERRICSTORE_HA_USERNAME`, and `FERRICSTORE_HA_PASSWORD`.
162
+
65
163
  You can also keep one primary URL and add seeds:
66
164
 
67
165
  ```ts
@@ -73,6 +171,27 @@ const flow = await FerricStoreClient.fromUrl("ferric://fs0.example.com:6388", {
73
171
  });
74
172
  ```
75
173
 
174
+ Native connections honor the flow-control windows advertised by the
175
+ HELLO-shaped `STARTUP` response and
176
+ `WINDOW_UPDATE`. Available data-request slots are refilled immediately as
177
+ responses finish; waiting requests are scheduled fairly across protocol lanes.
178
+ The adapter also caps automatic lanes and same-lane work to the advertised lane
179
+ queue, and caps ordered pipeline chunks and outbound frame bodies to the limits
180
+ negotiated during startup. Compact response codecs and aggregate response sizes
181
+ are enabled only when advertised for that connection.
182
+ Set `nativeOptions.maxQueuedRequests` to bound the local waiter queue (default
183
+ `65_536`, or `0` to reject immediately when all advertised slots are occupied).
184
+ Queue waiting counts toward `nativeOptions.timeoutMs`.
185
+
186
+ Control requests do not consume server data credits. Set
187
+ `nativeOptions.maxPendingControlRequests` to bound correlated control requests
188
+ awaiting responses on each connection (default `4_096`).
189
+
190
+ If Node reports socket backpressure, later encoded frames wait for `drain` in a
191
+ bounded client queue. Set `nativeOptions.maxQueuedWriteBytes` to control that
192
+ queue (default 64 MiB, or `0` to reject subsequent writes immediately). Healthy
193
+ socket writes still go directly to `socket.write` without entering the queue.
194
+
76
195
  ## Durable Queue
77
196
 
78
197
  ```ts
@@ -89,7 +208,12 @@ await emails.enqueue("email-1", {
89
208
  payload: { template: "welcome", userId: "user-1" }
90
209
  });
91
210
 
92
- await emails.worker({ batchSize: 100, worker: "email-worker-1" }).run(async (job) => {
211
+ await emails.enqueueMany([{ id: "email-2", payload: { template: "receipt" } }], {
212
+ autoPartitionBatchSize: 1_000,
213
+ autoPartitionConcurrency: 8
214
+ });
215
+
216
+ await emails.worker({ batchSize: 100, concurrency: 16, worker: "email-worker-1" }).run(async (job) => {
93
217
  console.log(job.id, job.payload);
94
218
  return { sent: true };
95
219
  });
@@ -126,6 +250,7 @@ await order.start("order-1", {
126
250
 
127
251
  await order.worker({
128
252
  batchSize: 50,
253
+ concurrency: 8,
129
254
  states: ["created", "charged"],
130
255
  worker: "order-worker-1"
131
256
  }).run();
@@ -140,6 +265,49 @@ Handlers return explicit durable outcomes:
140
265
 
141
266
  FerricFlow does not replay TypeScript handler code. Workers claim a durable state, run normal code, then write the next state through the FerricFlow API.
142
267
 
268
+ Per-job `run()` workers cap every claim to currently available concurrency. They continuously refill slots by default, so if five of ten jobs finish and their terminal writes are acknowledged, one client-side `claim(limit: 5)` can start five replacements while the other five continue. Queue completions produced in the same event-loop turn remain batched. A slot stays occupied through its `complete`, `retry`, or `fail` acknowledgement; the worker never exceeds its local concurrency limit.
269
+
270
+ Full-record claims across multiple states are returned by the claim command itself, including requested payloads and named values. `FLOW.GET` hydration is retained only for compatibility with a server that unexpectedly returns legacy compact tuples. That fallback preserves result order and is bounded to 16 concurrent reads by default; set `legacyClaimHydrationConcurrency` on the client to tune it. If a fallback read fails, `ClaimHydrationError.claimed` contains every already-leased job, while `hydratedItems` contains the indexed records that finished successfully; `failedIndex` and `cause` identify the first observed failure. Set `jobOnly: true` when compact claim metadata is sufficient; compact state metadata is decoded directly from the claim response without per-job `FLOW.GET` calls.
271
+
272
+ Use `refillStrategy: "wave"` to wait for the entire current claim to settle before claiming again. `refillDelayMs` adds a small coalescing window before a continuous refill; the default `0` still coalesces completions for one event-loop turn. Batchable queue completions and same-route replacement claims share one ordered native pipeline by default; set `fuseCompleteClaim: false` to keep them as separate requests. `runOnce()` and batch-handler APIs remain finite and wave-oriented. These controls are local to one worker instance or pod: two pods configured with `concurrency: 10` can execute up to twenty jobs collectively.
273
+
274
+ Worker claim and terminal-write batches use the client's `flowManyBatchLimit`,
275
+ which defaults to FerricStore's standard 1,000-item limit and should match the
276
+ server's `flow_max_batch_items` setting. Higher handler concurrency remains
277
+ supported and is filled through multiple bounded claims. `completeAsyncDepth`
278
+ is normalized to a finite, non-negative integer; non-finite values use the safe
279
+ worker-mode default. If an asynchronous completion fails after earlier writes
280
+ succeed, `QueueCompletionError.completed` reports every successful completion
281
+ drained by the same call, regardless of where the failed completion appeared.
282
+
283
+ When a worker combines `blockMs` with an `AbortSignal`, native long polls are
284
+ bounded by `abortPollMs` (default `1_000`) so shutdown is observed without
285
+ abandoning an in-flight claim that may already have leased work. Finite server
286
+ blocking time is added to the transport timeout rather than consuming it.
287
+
288
+ Unpartitioned `enqueueMany` calls group items in linear time, preserve caller
289
+ result order, keep chunks for the same auto-partition sequential, and dispatch
290
+ different partitions with bounded concurrency. The defaults above match the
291
+ server's standard 1,000-item Flow batch limit while avoiding unbounded requests.
292
+ Explicit and mixed-partition independent batches use the same hard request cap;
293
+ all Flow many mutations split larger inputs only when `independent: true`.
294
+ `independent: false` is never silently split and rejects oversized inputs before
295
+ dispatch. Set `flowManyBatchLimit` on the client when the server uses a custom
296
+ `flow_max_batch_items` value. If a later independent chunk fails,
297
+ `FlowBatchError.completedItems` reports the exact input indices and values whose
298
+ results were already confirmed; the original failure remains available as
299
+ `cause`.
300
+
301
+ Workers renew active leases every half lease by default and stop renewal before the fenced terminal write. Set `leaseRenewal: false` only when the handler is guaranteed to finish comfortably inside `leaseMs`; use `leaseRenewIntervalMs` to override the renewal interval.
302
+
303
+ `ctx.valueMany(names)` deduplicates shared references and fetches every missing referenced value with one `FLOW.VALUE.MGET`. Inline and locally cached values do not consume network work, and a stored JSON `null` remains distinct from a missing reference.
304
+
305
+ Flow fencing tokens remain `number` values while safe and are returned as
306
+ `bigint` once they exceed JavaScript's safe integer range. Pass the token back
307
+ unchanged; all fenced mutation APIs accept the exported `FencingToken` type,
308
+ and native compact claim and batch paths preserve its signed 64-bit value
309
+ exactly.
310
+
143
311
  ## Low-Level Flow Commands
144
312
 
145
313
  ```ts
@@ -171,6 +339,51 @@ for (const job of jobs) {
171
339
  }
172
340
  ```
173
341
 
342
+ Flow attributes can be returned without hydrating each record and can be
343
+ updated atomically with the fenced state mutation:
344
+
345
+ ```ts
346
+ const attributed = await flow.claimDue("order", {
347
+ includeAttributes: true,
348
+ jobOnly: true,
349
+ state: "created",
350
+ worker: "worker-1"
351
+ });
352
+
353
+ await flow.transition(attributed[0]!.id, {
354
+ attributesDelete: ["temporary"],
355
+ attributesMerge: { processor: "payments-v2" },
356
+ fencingToken: attributed[0]!.fencingToken,
357
+ fromState: "created",
358
+ leaseToken: attributed[0]!.leaseToken,
359
+ toState: "charged"
360
+ });
361
+ ```
362
+
363
+ The low-level client also exposes the fused `startAndClaim`, `stepContinue`,
364
+ and `runStepsMany` operations, schedule administration, Flow statistics and
365
+ attribute queries, effects, approvals, circuits, budgets, and distributed
366
+ limits. History supports the complete server filter surface, including event,
367
+ time, and version bounds plus cold/consistent reads and payload hydration:
368
+
369
+ ```ts
370
+ const events = await flow.history("order-1", {
371
+ consistentProjection: true,
372
+ fromVersion: 2,
373
+ includeCold: true,
374
+ payloadMaxBytes: 64_000,
375
+ toVersion: 8,
376
+ values: true
377
+ });
378
+
379
+ await flow.scheduleCreate("orders-every-five-minutes", {
380
+ cron: "*/5 * * * *",
381
+ kind: "cron",
382
+ target: { state: "created", type: "order" },
383
+ timezone: "UTC"
384
+ });
385
+ ```
386
+
174
387
  FIFO Flow state policy is opt-in per state:
175
388
 
176
389
  ```ts
@@ -207,10 +420,16 @@ await client.lists.lpush("jobs", { id: "job-1" });
207
420
  await client.sets.sadd("seen-users", "user:1");
208
421
  await client.zset.zadd("leaderboard", [{ score: 42, member: "user:1" }]);
209
422
  await client.stream.xadd("events", "*", { type: "created", id: "user:1" });
210
- await client.json.set("user:1:json", "$", { name: "Ada" });
211
423
  await client.bloom.add("seen-filter", "user:1");
212
424
  ```
213
425
 
426
+ Large unambiguous scalar batches accept an array without spreading, for example
427
+ `client.kv.del(keys)` and `client.tdigest.add(key, values)`. Codec-backed APIs,
428
+ where an array may itself be one stored value, expose explicit methods such as
429
+ `lpushMany`, `saddMany`, `zremMany`, `maddMany`, and `queryMany`. These forms
430
+ avoid JavaScript's variadic-call limit and build the command in one linear pass;
431
+ the existing rest-argument forms retain their original meaning.
432
+
214
433
  Available store helpers:
215
434
 
216
435
  - `client.kv` — strings, key expiry, key management, scans.
@@ -223,9 +442,13 @@ Available store helpers:
223
442
  - `client.hyperloglog` — HyperLogLog commands.
224
443
  - `client.geo` — geospatial commands.
225
444
  - `client.bloom`, `client.cuckoo`, `client.cms`, `client.topk`, `client.tdigest` — probabilistic data structures.
226
- - `client.json` — RedisJSON-compatible JSON commands.
227
445
 
228
- For connection-mode commands such as raw subscription flows or transactions, use `client.command(...)` directly so protocol behavior stays explicit.
446
+ `JsonCodec` serializes ordinary FerricStore values as JSON; FerricStore does not expose RedisJSON `JSON.*` commands.
447
+
448
+ Transactions and raw subscription flows require an exclusive pinned connection
449
+ session, which the multiplexed native client does not currently expose. The SDK
450
+ rejects those commands before dispatch so a failed transaction cannot partially
451
+ apply mutations.
229
452
 
230
453
  ## Auto-Batching
231
454
 
@@ -249,17 +472,17 @@ await Promise.all([
249
472
  ]);
250
473
  ```
251
474
 
252
- Auto-batching groups eligible concurrent commands into native `PIPELINE` frames and resolves each original promise independently. Blocking/session commands such as `FLOW.CLAIM_DUE`, `AUTH`, `QUIT`, `SUBSCRIBE`, and client-control commands bypass auto-batching.
475
+ Auto-batching groups eligible concurrent commands into native `PIPELINE` 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 `COMMAND_EXEC` pipeline items, preserving one pipeline request instead of issuing each command separately. Blocking/session and control commands such as `FLOW.CLAIM_DUE`, `BLPOP`, `XREAD`, `AUTH`, `PING`, `OPTIONS`, and `QUIT` 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 `client.pipeline(commands, { ordered: true })` is requested. Individual fallbacks continuously refill a bounded pool instead of starting every request at once; the default limit is 64 and `fallbackConcurrency` 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.
253
476
 
254
477
  Queue workers are latency-first by default. For high-throughput queue workers, use one profile flag:
255
478
 
256
479
  ```ts
257
- await emails.worker({ profile: "throughput" }).run(async (job) => {
480
+ await emails.worker({ profile: "throughput", concurrency: 32 }).run(async (job) => {
258
481
  await sendEmail(job.id);
259
482
  });
260
483
  ```
261
484
 
262
- The throughput profile uses compact claims, larger claim batches, and async completion batching. Explicit worker options still override the profile.
485
+ The throughput profile uses compact claims, a larger batch ceiling, and concurrent completion batching. Per-job claim credit follows currently available `concurrency` (or its `workers` alias), capped by `batchSize`; explicit worker options override profile defaults.
263
486
 
264
487
  ## Examples
265
488