@lostgradient/weft 0.11.0 → 0.13.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 (242) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/api.d.ts +2 -0
  3. package/dist/cli/api.js +3 -4
  4. package/dist/cli/generated/operation-catalog.snapshot.json +5167 -0
  5. package/dist/cli/generated/operation-client.generated.d.ts +119 -10
  6. package/dist/cli/generated/operation-client.generated.js +74 -1
  7. package/dist/cli/index.js +1 -0
  8. package/dist/cli/operation-catalog-snapshot.d.ts +11 -2
  9. package/dist/cli/operation-catalog-snapshot.js +14 -5
  10. package/dist/cli/operation-client-runtime.d.ts +29 -0
  11. package/dist/cli-main.js +227 -401
  12. package/dist/client/client-storage.d.ts +24 -0
  13. package/dist/client/client-storage.js +14 -0
  14. package/dist/client/handle-delegation.d.ts +3 -3
  15. package/dist/client/handle-delegation.js +2 -2
  16. package/dist/client/http-client-requests.d.ts +5 -3
  17. package/dist/client/http-client-requests.js +20 -2
  18. package/dist/client/http-client-storage.d.ts +3 -0
  19. package/dist/client/http-client-storage.js +110 -0
  20. package/dist/client/http-client.d.ts +9 -6
  21. package/dist/client/http-client.js +13 -18
  22. package/dist/client/http-operations.d.ts +3 -1
  23. package/dist/client/http-operations.js +84 -2
  24. package/dist/client/http-request.d.ts +35 -6
  25. package/dist/client/http-request.js +31 -10
  26. package/dist/client/in-process-operations.d.ts +2 -2
  27. package/dist/client/in-process-operations.js +2 -2
  28. package/dist/client/index.d.ts +6 -0
  29. package/dist/client/index.js +18 -0
  30. package/dist/client/interface.d.ts +26 -26
  31. package/dist/client/local.d.ts +9 -6
  32. package/dist/client/local.js +9 -4
  33. package/dist/client/search-params.js +10 -6
  34. package/dist/client/start-body.d.ts +3 -1
  35. package/dist/client/start-body.js +9 -0
  36. package/dist/connection.d.ts +8 -0
  37. package/dist/connection.js +41 -17
  38. package/dist/core/bulk-workflow-filter.d.ts +2 -1
  39. package/dist/core/bulk-workflow-filter.js +5 -1
  40. package/dist/core/engine/async-activity-records.d.ts +12 -1
  41. package/dist/core/engine/async-activity-records.js +81 -2
  42. package/dist/core/engine/bulk-operations-purge.js +16 -1
  43. package/dist/core/engine/bulk-operations-shared.js +2 -0
  44. package/dist/core/engine/callback-creators-core.js +2 -1
  45. package/dist/core/engine/callback-creators-schedule.d.ts +2 -1
  46. package/dist/core/engine/callback-creators-schedule.js +3 -3
  47. package/dist/core/engine/checkpoint-reads.js +11 -1
  48. package/dist/core/engine/child-workflow.js +15 -6
  49. package/dist/core/engine/construction.js +3 -0
  50. package/dist/core/engine/disposal.js +2 -0
  51. package/dist/core/engine/engine-internal-types.d.ts +4 -1
  52. package/dist/core/engine/fenced-write.d.ts +5 -3
  53. package/dist/core/engine/finalizer-status.d.ts +10 -0
  54. package/dist/core/engine/finalizer-status.js +82 -0
  55. package/dist/core/engine/index.d.ts +69 -2
  56. package/dist/core/engine/index.js +71 -8
  57. package/dist/core/engine/internals.d.ts +3 -0
  58. package/dist/core/engine/lease-health.d.ts +64 -0
  59. package/dist/core/engine/lease-health.js +0 -0
  60. package/dist/core/engine/lease-manager.d.ts +4 -2
  61. package/dist/core/engine/lease-manager.js +32 -4
  62. package/dist/core/engine/lifecycle/recovered-services.js +0 -2
  63. package/dist/core/engine/lifecycle/shared.d.ts +28 -0
  64. package/dist/core/engine/lifecycle/start-batch.js +12 -0
  65. package/dist/core/engine/lifecycle/start.d.ts +1 -1
  66. package/dist/core/engine/lifecycle/start.js +34 -5
  67. package/dist/core/engine/lifecycle/transition.js +19 -9
  68. package/dist/core/engine/operations-coordination.d.ts +1 -12
  69. package/dist/core/engine/operations-coordination.js +57 -10
  70. package/dist/core/engine/operations-speculate.d.ts +1 -1
  71. package/dist/core/engine/operations-speculate.js +19 -5
  72. package/dist/core/engine/registration.js +14 -0
  73. package/dist/core/engine/schedule-handle.d.ts +4 -4
  74. package/dist/core/engine/schedule-handle.js +2 -2
  75. package/dist/core/engine/schedule-overlap.d.ts +11 -0
  76. package/dist/core/engine/schedule-overlap.js +50 -0
  77. package/dist/core/engine/schedule-run.d.ts +11 -3
  78. package/dist/core/engine/schedule-run.js +27 -4
  79. package/dist/core/engine/schedule-timer.js +10 -1
  80. package/dist/core/engine/schedules.d.ts +5 -4
  81. package/dist/core/engine/schedules.js +62 -56
  82. package/dist/core/engine/state-utilities.js +1 -1
  83. package/dist/core/engine/storage-io.d.ts +12 -0
  84. package/dist/core/engine/storage-io.js +49 -10
  85. package/dist/core/engine/termination/finalizer-claim.d.ts +8 -6
  86. package/dist/core/engine/termination/finalizer-claim.js +2 -1
  87. package/dist/core/engine/termination/finalizer.js +10 -14
  88. package/dist/core/engine/timeline-coordinator-constants.d.ts +3 -0
  89. package/dist/core/engine/timeline-coordinator-constants.js +1 -0
  90. package/dist/core/engine/timeline-coordinator-detail.d.ts +13 -0
  91. package/dist/core/engine/timeline-coordinator-detail.js +76 -0
  92. package/dist/core/engine/validation/schedule-options.d.ts +8 -1
  93. package/dist/core/engine/validation/schedule-options.js +18 -0
  94. package/dist/core/engine/validation/schedule.d.ts +1 -1
  95. package/dist/core/engine/validation/schedule.js +27 -3
  96. package/dist/core/engine/validation.js +66 -2
  97. package/dist/core/engine/workflow-state-stream.d.ts +5 -1
  98. package/dist/core/engine/workflow-state-stream.js +37 -14
  99. package/dist/core/events/schedule-events.d.ts +3 -4
  100. package/dist/core/events/workflow-events.d.ts +2 -1
  101. package/dist/core/list-filter-validation.d.ts +3 -0
  102. package/dist/core/list-filter-validation.js +11 -0
  103. package/dist/core/registry-snapshot.d.ts +32 -0
  104. package/dist/core/registry-snapshot.js +28 -0
  105. package/dist/core/review/index.d.ts +4 -4
  106. package/dist/core/types/activity.d.ts +52 -5
  107. package/dist/core/types/bulk.d.ts +1 -0
  108. package/dist/core/types/definition-schema-to-json.d.ts +11 -0
  109. package/dist/core/types/definition-schema-to-json.js +11 -2
  110. package/dist/core/types/list-options.d.ts +6 -0
  111. package/dist/core/types/options.d.ts +6 -5
  112. package/dist/core/types/reviews.d.ts +2 -2
  113. package/dist/core/types/schedules.d.ts +45 -2
  114. package/dist/core/types/services-resolution.d.ts +5 -5
  115. package/dist/core/types/state.d.ts +64 -0
  116. package/dist/core/types/workflow-log.d.ts +3 -2
  117. package/dist/core/types/workflow-observability.d.ts +50 -0
  118. package/dist/core/types/workflow-observability.js +0 -0
  119. package/dist/core/types/workflow-registry.d.ts +7 -0
  120. package/dist/core/types.d.ts +1 -0
  121. package/dist/core/types.js +1 -0
  122. package/dist/http.js +2 -0
  123. package/dist/index.d.ts +4 -3
  124. package/dist/indexeddb.js +1 -0
  125. package/dist/json-schema.js +3 -3
  126. package/dist/mcp/cli.js +63 -61
  127. package/dist/mcp/http.d.ts +7 -2
  128. package/dist/mcp/session.d.ts +3 -3
  129. package/dist/mcp/session.js +3 -2
  130. package/dist/mcp/stdio.d.ts +7 -2
  131. package/dist/observability/index.js +2 -2
  132. package/dist/runtime/portable.d.ts +33 -0
  133. package/dist/runtime/portable.js +17 -6
  134. package/dist/server/access-policy-metadata.d.ts +92 -0
  135. package/dist/server/access-policy-metadata.js +78 -0
  136. package/dist/server/engine-event-feed-backend.d.ts +22 -3
  137. package/dist/server/fault-to-http.d.ts +3 -7
  138. package/dist/server/fault-to-http.js +2 -34
  139. package/dist/server/fleet-event-feed.d.ts +65 -0
  140. package/dist/server/handler/index.d.ts +11 -2
  141. package/dist/server/handler/index.js +8 -1
  142. package/dist/server/handler/route-dispatch.d.ts +10 -3
  143. package/dist/server/handler/route-dispatch.js +19 -4
  144. package/dist/server/handler.js +1 -56
  145. package/dist/server/index.d.ts +13 -2
  146. package/dist/server/index.js +88 -64
  147. package/dist/server/interactive-operations.js +2 -0
  148. package/dist/server/openapi-error-responses.js +55 -4
  149. package/dist/server/openapi.d.ts +3 -1
  150. package/dist/server/openapi.js +4 -0
  151. package/dist/server/openrpc-document-schema.d.ts +64 -0
  152. package/dist/server/openrpc-document-schema.js +6 -26
  153. package/dist/server/openrpc.d.ts +4 -0
  154. package/dist/server/openrpc.js +9 -33
  155. package/dist/server/operation-fault.d.ts +24 -7
  156. package/dist/server/operation-fault.js +65 -13
  157. package/dist/server/operations/aggregate-workflows.d.ts +6 -0
  158. package/dist/server/operations/aggregate-workflows.js +3 -2
  159. package/dist/server/operations/async-activity.d.ts +14 -0
  160. package/dist/server/operations/async-activity.js +88 -4
  161. package/dist/server/operations/bulk-filter-helpers.d.ts +3 -0
  162. package/dist/server/operations/bulk-filter-helpers.js +27 -3
  163. package/dist/server/operations/bulk-mutate-workflow-tags.d.ts +6 -0
  164. package/dist/server/operations/bulk-signal-workflows.d.ts +6 -0
  165. package/dist/server/operations/get-stream-chunks.js +2 -7
  166. package/dist/server/operations/get-system-lease.d.ts +19 -0
  167. package/dist/server/operations/get-system-lease.js +48 -0
  168. package/dist/server/operations/get-workflow-observability.d.ts +24 -0
  169. package/dist/server/operations/get-workflow-observability.js +80 -0
  170. package/dist/server/operations/get-workflow-result.js +2 -2
  171. package/dist/server/operations/get-workflow.d.ts +2 -2
  172. package/dist/server/operations/list-filter-query-extractor.js +15 -0
  173. package/dist/server/operations/list-workflows.d.ts +6 -0
  174. package/dist/server/operations/list-workflows.js +12 -3
  175. package/dist/server/operations/operation-helpers.d.ts +9 -18
  176. package/dist/server/operations/operation-helpers.js +5 -23
  177. package/dist/server/operations/purge-workflows.d.ts +3 -0
  178. package/dist/server/operations/recover-all.js +2 -1
  179. package/dist/server/operations/single-workflow-tag-mutation.js +2 -2
  180. package/dist/server/operations/sse-stream.js +2 -7
  181. package/dist/server/operations/storage-capabilities.d.ts +34 -0
  182. package/dist/server/operations/storage-capabilities.js +38 -0
  183. package/dist/server/operations/storage.js +8 -17
  184. package/dist/server/operations/stream-workflow-sse.js +3 -9
  185. package/dist/server/operations/submit-review-decision.d.ts +2 -0
  186. package/dist/server/operations/submit-review-decision.js +19 -2
  187. package/dist/server/operations/update-schedule.d.ts +8 -0
  188. package/dist/server/operations/update-schedule.js +70 -7
  189. package/dist/server/operations/workflow-events-sse.js +2 -2
  190. package/dist/server/replay-live-feed-internals.d.ts +19 -0
  191. package/dist/server/replay-live-feed-internals.js +76 -0
  192. package/dist/server/rest-binding.d.ts +7 -6
  193. package/dist/server/rest-bindings.d.ts +2 -13
  194. package/dist/server/rest-bindings.js +19 -10
  195. package/dist/server/runtime/authentication-bridge.d.ts +2 -2
  196. package/dist/server/serve-internals.js +3 -3
  197. package/dist/server/task-queue.d.ts +7 -5
  198. package/dist/server/workflow-event-feed.d.ts +100 -11
  199. package/dist/server/workflow-event-feed.js +1 -140
  200. package/dist/service-worker/index.d.ts +8 -2
  201. package/dist/service-worker/index.js +37 -56
  202. package/dist/service-worker/setup.d.ts +6 -2
  203. package/dist/storage/auto.js +1 -1
  204. package/dist/storage/bun-sql.js +3 -3
  205. package/dist/storage/capabilities.d.ts +1 -1
  206. package/dist/storage/cloudflare-durable-object-sql.d.ts +75 -0
  207. package/dist/storage/cloudflare-durable-object-sql.js +0 -0
  208. package/dist/storage/cloudflare-value-codec.d.ts +67 -0
  209. package/dist/storage/cloudflare-value-codec.js +46 -0
  210. package/dist/storage/cloudflare.d.ts +140 -0
  211. package/dist/storage/cloudflare.js +127 -0
  212. package/dist/storage/compressed-storage.js +1 -1
  213. package/dist/storage/http.js +200 -2
  214. package/dist/storage/index.d.ts +8 -1
  215. package/dist/storage/indexeddb.js +311 -1
  216. package/dist/storage/interface.d.ts +16 -4
  217. package/dist/storage/interface.js +1 -1
  218. package/dist/storage/key-prefixes.d.ts +1 -1
  219. package/dist/storage/key-prefixes.js +4 -0
  220. package/dist/storage/lmdb.js +1 -1
  221. package/dist/storage/memory.js +1 -1
  222. package/dist/storage/neon.js +4 -4
  223. package/dist/storage/node-sqlite.js +3 -3
  224. package/dist/storage/postgres-key-value-queries.d.ts +6 -0
  225. package/dist/storage/postgres-key-value-queries.js +2 -3
  226. package/dist/storage/postgres.js +4 -4
  227. package/dist/storage/resolve.js +1 -1
  228. package/dist/storage/scoped-storage.js +1 -1
  229. package/dist/storage/sql-identifier.d.ts +35 -0
  230. package/dist/storage/sql-identifier.js +5 -0
  231. package/dist/storage/sqlite-key-value-queries.d.ts +11 -6
  232. package/dist/storage/sqlite-key-value-queries.js +3 -3
  233. package/dist/storage/testing.js +1 -1
  234. package/dist/storage/turso.js +2 -2
  235. package/dist/storage/typed-storage.js +138 -2
  236. package/dist/storage/web-extension.js +298 -1
  237. package/dist/testing/index.js +21 -60
  238. package/dist/version.d.ts +1 -1
  239. package/dist/version.js +1 -1
  240. package/dist/web-extension.js +1 -0
  241. package/dist/worker/protocol.js +1 -1
  242. package/package.json +8 -3
@@ -0,0 +1,24 @@
1
+ import { type BatchOperation, type ConditionalBatchCondition, type ScanOptions, type Storage } from '../storage/interface.ts';
2
+ /**
3
+ * Byte-oriented raw storage surface shared by local and HTTP clients.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * import { Engine, LocalClient, MemoryStorage, type WeftClientStorage } from '@lostgradient/weft';
8
+ *
9
+ * await using engine = new Engine({ storage: new MemoryStorage() });
10
+ * const client = new LocalClient(engine);
11
+ * const storage: WeftClientStorage = client.storage;
12
+ * await storage.put('example:key', new TextEncoder().encode('value'));
13
+ * ```
14
+ */
15
+ export interface WeftClientStorage {
16
+ get(key: string): Promise<Uint8Array | null>;
17
+ put(key: string, value: Uint8Array): Promise<void>;
18
+ delete(key: string): Promise<void>;
19
+ scan(prefix: string, options?: ScanOptions): AsyncIterable<[string, Uint8Array]>;
20
+ batch(operations: BatchOperation[]): Promise<void>;
21
+ conditionalBatch(conditions: ConditionalBatchCondition[], operations: BatchOperation[]): Promise<boolean>;
22
+ }
23
+ /** Adapt an engine-owned storage backend without exposing adapter-only methods. */
24
+ export declare function createLocalClientStorage(storage: Storage): WeftClientStorage;
@@ -0,0 +1,14 @@
1
+ import {
2
+ storageBatch,
3
+ storageConditionalBatch
4
+ } from "../storage/interface.js";
5
+ export function createLocalClientStorage(storage) {
6
+ return {
7
+ get: (key) => storage.get(key),
8
+ put: (key, value) => storage.put(key, value),
9
+ delete: (key) => storage.delete(key),
10
+ scan: (prefix, options) => storage.scan(prefix, options),
11
+ batch: (operations) => storageBatch(storage, operations),
12
+ conditionalBatch: (conditions, operations) => storageConditionalBatch(storage, conditions, operations)
13
+ };
14
+ }
@@ -1,5 +1,5 @@
1
1
  import type { WeftEventMap } from '../core/events.ts';
2
- import type { QueryDefinition, ScheduleSpec, ScheduleSummary, SearchAttributeValue, SignalDefinition, SignalDeliveryOptions, UpdateDefinition } from '../core/types.ts';
2
+ import type { QueryDefinition, ScheduleSpec, ScheduleSummary, ScheduleUpdateOptions, SearchAttributeValue, SignalDefinition, SignalDeliveryOptions, UpdateDefinition } from '../core/types.ts';
3
3
  import type { WorkflowEventTail } from './event-tail.ts';
4
4
  import type { ClientHandle, ClientScheduleHandle, StartOrSignalOutcome } from './interface.ts';
5
5
  export interface WorkflowHandleDelegationClient {
@@ -62,7 +62,7 @@ export interface ScheduleHandleDelegationClient {
62
62
  pauseSchedule(id: string): Promise<void>;
63
63
  resumeSchedule(id: string): Promise<void>;
64
64
  cancelSchedule(id: string): Promise<void>;
65
- updateSchedule(id: string, newSpec: string | ScheduleSpec): Promise<void>;
65
+ updateSchedule(id: string, newSpec: string | ScheduleSpec, options?: ScheduleUpdateOptions): Promise<void>;
66
66
  getSchedule(id: string): Promise<ScheduleSummary | null>;
67
67
  }
68
68
  export declare abstract class ScheduleHandleDelegation<TClient extends ScheduleHandleDelegationClient = ScheduleHandleDelegationClient> implements ClientScheduleHandle {
@@ -72,7 +72,7 @@ export declare abstract class ScheduleHandleDelegation<TClient extends ScheduleH
72
72
  pause(): Promise<void>;
73
73
  resume(): Promise<void>;
74
74
  cancel(): Promise<void>;
75
- update(newSpec: string | ScheduleSpec): Promise<void>;
75
+ update(newSpec: string | ScheduleSpec, options?: ScheduleUpdateOptions): Promise<void>;
76
76
  describe(): Promise<ScheduleSummary | null>;
77
77
  abstract [Symbol.dispose](): void;
78
78
  }
@@ -65,8 +65,8 @@ export class ScheduleHandleDelegation {
65
65
  async cancel() {
66
66
  return this.client.cancelSchedule(this.id);
67
67
  }
68
- async update(newSpec) {
69
- return this.client.updateSchedule(this.id, newSpec);
68
+ async update(newSpec, options) {
69
+ return this.client.updateSchedule(this.id, newSpec, options);
70
70
  }
71
71
  async describe() {
72
72
  return this.client.getSchedule(this.id);
@@ -1,5 +1,5 @@
1
1
  import type { StoredStreamChunk } from '../core/context.ts';
2
- import type { BulkCancelResult, BulkDeleteResult, BulkRetryFailedResult, BulkSignalResult, BulkTagResult, CoordinatedUpdateResult, ForkOptions, ListFilter, PurgeResult, RetentionOverview, ReviewListEntry, ReviewListFilter, ScheduleSpec, ScheduleSummary, SearchAttributeValue, SignalDeliveryOptions, SubmitReviewOptions, WorkflowReplay, WorkflowState, WorkflowTimelineEntry } from '../core/types.ts';
2
+ import type { BulkCancelResult, BulkDeleteResult, BulkRetryFailedResult, BulkSignalResult, BulkTagResult, CoordinatedUpdateResult, ForkOptions, ListFilter, PendingAsyncActivityListOptions, PendingAsyncActivityPage, PurgeResult, RetentionOverview, ReviewListEntry, ReviewListFilter, ScheduleSpec, ScheduleSummary, ScheduleUpdateOptions, SearchAttributeValue, SignalDeliveryOptions, SubmitReviewOptions, WorkflowReplay, WorkflowState, WorkflowTimelineEntry } from '../core/types.ts';
3
3
  import type { UpdateResult } from './interface.ts';
4
4
  export type HttpClientRequestContext = {
5
5
  readonly baseUrl: string;
@@ -37,6 +37,8 @@ export declare function signalWorkflowRequest(context: HttpClientRequestContext,
37
37
  * workflow with `result`. Mirrors `LocalClient.activity.complete` over HTTP.
38
38
  */
39
39
  export declare function completeAsyncActivityRequest(context: HttpClientRequestContext, token: string, result?: unknown): Promise<void>;
40
+ /** List durable async activities awaiting completion for one workflow. */
41
+ export declare function listPendingAsyncActivitiesRequest(context: HttpClientRequestContext, workflowId: string, options?: PendingAsyncActivityListOptions): Promise<PendingAsyncActivityPage>;
40
42
  /**
41
43
  * Fail a deferred ("async") activity by task token. A live `Error` cannot cross
42
44
  * the wire, so the error is reduced to `message` + `name` — exactly the fields
@@ -59,8 +61,8 @@ export declare function pauseScheduleRequest(context: HttpClientRequestContext,
59
61
  export declare function resumeScheduleRequest(context: HttpClientRequestContext, id: string): Promise<void>;
60
62
  /** Cancel a recurring schedule (`DELETE /v1/schedules/:id`). */
61
63
  export declare function cancelScheduleRequest(context: HttpClientRequestContext, id: string): Promise<void>;
62
- /** Update a schedule's recurrence specification (`PATCH /v1/schedules/:id`). */
63
- export declare function updateScheduleRequest(context: HttpClientRequestContext, id: string, newSpec: string | ScheduleSpec): Promise<void>;
64
+ /** Update a schedule's cadence and mutable options (`PATCH /v1/schedules/:id`). */
65
+ export declare function updateScheduleRequest(context: HttpClientRequestContext, id: string, newSpec: string | ScheduleSpec, options?: ScheduleUpdateOptions): Promise<void>;
64
66
  /**
65
67
  * Query a named read-only accessor on a workflow. A no-input query is a GET;
66
68
  * an input payload is sent as a POST body.
@@ -139,6 +139,15 @@ export async function completeAsyncActivityRequest(context, token, result) {
139
139
  body: JSON.stringify({ token, result })
140
140
  });
141
141
  }
142
+ export async function listPendingAsyncActivitiesRequest(context, workflowId, options) {
143
+ const search = new URLSearchParams;
144
+ if (options?.limit !== void 0)
145
+ search.set("limit", String(options.limit));
146
+ if (options?.cursor !== void 0)
147
+ search.set("cursor", options.cursor);
148
+ const query = search.size > 0 ? `?${search.toString()}` : "";
149
+ return request(context.baseUrl, `/workflows/${encodeURIComponent(workflowId)}/pending-async-activities${query}`, context.headers);
150
+ }
142
151
  export async function failAsyncActivityRequest(context, token, error) {
143
152
  const reduced = { message: error instanceof Error ? error.message : String(error) };
144
153
  if (error instanceof Error)
@@ -176,10 +185,19 @@ export function cancelScheduleRequest(context, id) {
176
185
  method: "DELETE"
177
186
  });
178
187
  }
179
- export function updateScheduleRequest(context, id, newSpec) {
188
+ export function updateScheduleRequest(context, id, newSpec, options) {
189
+ const body = scheduleSpecToWireFields(newSpec);
190
+ if (options?.description !== void 0)
191
+ body.description = options.description;
192
+ if (options?.overlap !== void 0)
193
+ body.overlap = options.overlap;
194
+ if (options?.backfill !== void 0)
195
+ body.backfill = options.backfill;
196
+ if (options?.jitter !== void 0)
197
+ body.jitter = options.jitter;
180
198
  return request(context.baseUrl, `/schedules/${encodeURIComponent(id)}`, context.headers, {
181
199
  method: "PATCH",
182
- body: JSON.stringify(scheduleSpecToWireFields(newSpec))
200
+ body: JSON.stringify(body)
183
201
  });
184
202
  }
185
203
  export async function queryWorkflowRequest(context, id, name, input) {
@@ -0,0 +1,3 @@
1
+ import type { WeftClientStorage } from './client-storage.ts';
2
+ /** Build the raw-storage facade over the six specialized REST bindings. */
3
+ export declare function createHttpClientStorage(baseUrl: string, headers: Record<string, string>): WeftClientStorage;
@@ -0,0 +1,110 @@
1
+ import { decodeBase64ToBytes, encodeBytesToBase64, isRecord } from "../storage/byte-encoding.js";
2
+ import {
3
+ assertStorageBatchOperationCount
4
+ } from "../storage/interface.js";
5
+ import { request, requestResponse } from "./http-request.js";
6
+ const MAX_SCAN_RESPONSE_BYTES = 67108864;
7
+ function encodeOperation(operation) {
8
+ return operation.type === "put" ? { type: "put", key: operation.key, value: encodeBytesToBase64(operation.value) } : { type: "delete", key: operation.key };
9
+ }
10
+ function scanPath(prefix, options) {
11
+ const parameters = new URLSearchParams({ prefix });
12
+ for (const [name, value] of Object.entries(options))
13
+ if (value !== void 0)
14
+ parameters.set(name, String(value));
15
+ return `/storage?${parameters.toString()}`;
16
+ }
17
+ function scanEntry(value) {
18
+ if (!isRecord(value) || typeof value.key !== "string" || typeof value.value !== "string")
19
+ throw Error("HttpClient storage scan response contained an invalid NDJSON entry.");
20
+ return [value.key, decodeBase64ToBytes(value.value)];
21
+ }
22
+ async function releaseScanReader(reader, reachedEndOfStream) {
23
+ if (!reachedEndOfStream)
24
+ try {
25
+ await reader.cancel();
26
+ } catch {}
27
+ reader.releaseLock();
28
+ }
29
+ async function* scanResponse(response) {
30
+ if (response.body === null)
31
+ return;
32
+ const reader = response.body.getReader(), decoder = new TextDecoder;
33
+ let bufferedText = "", bytesRead = 0, reachedEndOfStream = !1;
34
+ try {
35
+ while (!0) {
36
+ const { done, value } = await reader.read();
37
+ if (done) {
38
+ reachedEndOfStream = !0;
39
+ break;
40
+ }
41
+ bytesRead += value.byteLength;
42
+ if (bytesRead > MAX_SCAN_RESPONSE_BYTES)
43
+ throw Error("HttpClient storage scan response exceeded the maximum allowed size.");
44
+ bufferedText += decoder.decode(value, { stream: !0 });
45
+ const lines = bufferedText.split(`
46
+ `);
47
+ bufferedText = lines.pop() ?? "";
48
+ for (const line of lines)
49
+ if (line.trim().length > 0)
50
+ yield scanEntry(JSON.parse(line));
51
+ }
52
+ bufferedText += decoder.decode();
53
+ if (bufferedText.trim().length > 0)
54
+ yield scanEntry(JSON.parse(bufferedText));
55
+ } finally {
56
+ await releaseScanReader(reader, reachedEndOfStream);
57
+ }
58
+ }
59
+ export function createHttpClientStorage(baseUrl, headers) {
60
+ return {
61
+ async get(key) {
62
+ const response = await requestResponse(baseUrl, `/storage/${encodeURIComponent(key)}`, headers, { method: "GET" });
63
+ return response === null ? null : new Uint8Array(await response.arrayBuffer());
64
+ },
65
+ async put(key, value) {
66
+ await requestResponse(baseUrl, `/storage/${encodeURIComponent(key)}`, headers, {
67
+ method: "PUT",
68
+ headers: { "content-type": "application/octet-stream" },
69
+ body: new Blob([value])
70
+ });
71
+ },
72
+ async delete(key) {
73
+ await requestResponse(baseUrl, `/storage/${encodeURIComponent(key)}`, headers, {
74
+ method: "DELETE"
75
+ });
76
+ },
77
+ async* scan(prefix, options = {}) {
78
+ const response = await requestResponse(baseUrl, scanPath(prefix, options), headers, {
79
+ method: "GET",
80
+ headers: { accept: "application/x-ndjson" }
81
+ });
82
+ if (response !== null)
83
+ yield* scanResponse(response);
84
+ },
85
+ async batch(operations) {
86
+ assertStorageBatchOperationCount("batch operations", operations.length);
87
+ await request(baseUrl, "/storage/-/batch", headers, {
88
+ method: "POST",
89
+ body: JSON.stringify({ operations: operations.map(encodeOperation) })
90
+ });
91
+ },
92
+ async conditionalBatch(conditions, operations) {
93
+ assertStorageBatchOperationCount("conditionalBatch conditions", conditions.length);
94
+ assertStorageBatchOperationCount("conditionalBatch operations", operations.length);
95
+ const result = await request(baseUrl, "/storage/-/conditional-batch", headers, {
96
+ method: "POST",
97
+ body: JSON.stringify({
98
+ conditions: conditions.map((condition) => ({
99
+ key: condition.key,
100
+ expectedValue: condition.expectedValue === null ? null : encodeBytesToBase64(condition.expectedValue)
101
+ })),
102
+ operations: operations.map(encodeOperation)
103
+ })
104
+ });
105
+ if (!isRecord(result) || typeof result.applied !== "boolean")
106
+ throw Error('HttpClient storage conditional batch response must include a boolean "applied" field.');
107
+ return result.applied;
108
+ }
109
+ };
110
+ }
@@ -1,6 +1,7 @@
1
- import { type CatalogOperationName, type CatalogOperationTypes, type WeftClient as CatalogOperations } from '../cli/generated/operation-client.generated.ts';
1
+ import { type ClientOperationName, type ClientOperationTypes, type ClientOperations } from '../cli/generated/operation-client.generated.ts';
2
2
  import type { StoredStreamChunk } from '../core/context.ts';
3
- import type { AttributeFilterKey, BulkCancelResult, BulkDeleteResult, BulkRetryFailedResult, BulkSignalResult, BulkTagResult, CoordinatedUpdateResult, ForkOptions, ListFilter, PaginatedResult, PurgeResult, QueryDefinition, RetentionOverview, ReviewListEntry, ReviewListFilter, ScheduleFilter, ScheduleOptions, ScheduleSpec, ScheduleSummary, SearchAttributeValue, SignalDefinition, SignalDeliveryOptions, StartOrSignalSignal, SubmitReviewOptions, TypedListFilter, UpdateDefinition, WorkflowEvent, WorkflowInput, WorkflowOutput, WorkflowRegistry, WorkflowReplay, WorkflowState, WorkflowSummary, WorkflowTimelineEntry } from '../core/types.ts';
3
+ import type { AttributeFilterKey, BulkCancelResult, BulkDeleteResult, BulkRetryFailedResult, BulkSignalResult, BulkTagResult, CoordinatedUpdateResult, ForkOptions, ListFilter, PaginatedResult, PurgeResult, QueryDefinition, RetentionOverview, ReviewListEntry, ReviewListFilter, ScheduleFilter, ScheduleOptions, ScheduleSpec, ScheduleSummary, ScheduleUpdateOptions, SearchAttributeValue, SignalDefinition, SignalDeliveryOptions, StartOrSignalSignal, SubmitReviewOptions, TypedListFilter, UpdateDefinition, WorkflowEvent, WorkflowInput, WorkflowOutput, WorkflowRegistry, WorkflowReplay, WorkflowState, WorkflowSummary, WorkflowTimelineEntry } from '../core/types.ts';
4
+ import type { WeftClientStorage } from './client-storage.ts';
4
5
  import type { WorkflowEventTail } from './event-tail.ts';
5
6
  import { type HttpClientOptions } from './http-request.ts';
6
7
  import type { ClientHandle, ClientScheduleHandle, ClientStartOptions, ClientStartOrSignalOptions, UpdateResult, WeftClient, WeftClientActivity } from './interface.ts';
@@ -49,15 +50,17 @@ export declare class HttpClient implements WeftClient {
49
50
  readonly baseUrl: string;
50
51
  /** @internal Exposed for handle access. */
51
52
  readonly headers: Record<string, string>;
52
- /** Typed low-level accessor for every catalog operation over JSON-RPC. */
53
- readonly operations: CatalogOperations;
53
+ /** Typed low-level accessor over JSON-RPC and generated ordinary REST bindings. */
54
+ readonly operations: ClientOperations;
55
+ /** Raw storage administration over the byte-oriented REST bindings. */
56
+ readonly storage: WeftClientStorage;
54
57
  /**
55
58
  * Out-of-band ("async") activity completion over HTTP. POSTs to
56
59
  * `/v1/activities/{complete,fail}`; mirrors {@link LocalClient}'s `activity`.
57
60
  */
58
61
  readonly activity: WeftClientActivity;
59
62
  constructor(options?: HttpClientOptions);
60
- call<Name extends CatalogOperationName>(name: Name, input: CatalogOperationTypes[Name]['input']): Promise<CatalogOperationTypes[Name]['output']>;
63
+ call<Name extends ClientOperationName>(name: Name, input: ClientOperationTypes[Name]['input']): Promise<ClientOperationTypes[Name]['output']>;
61
64
  start<TName extends KnownWorkflowName>(type: TName, input: WorkflowInput<WorkflowRegistry, TName>, options?: ClientStartOptions): Promise<ClientHandle<WorkflowOutput<WorkflowRegistry, TName>>>;
62
65
  start<TName extends string>(type: UnknownNameWhenRegistryEmpty<TName>, input: unknown, options?: ClientStartOptions): Promise<ClientHandle>;
63
66
  startOrSignal<TName extends KnownWorkflowName>(type: TName, input: WorkflowInput<WorkflowRegistry, TName>, signal: StartOrSignalSignal, options?: ClientStartOrSignalOptions): Promise<ClientHandle<WorkflowOutput<WorkflowRegistry, TName>>>;
@@ -75,7 +78,7 @@ export declare class HttpClient implements WeftClient {
75
78
  pauseSchedule(id: string): Promise<void>;
76
79
  resumeSchedule(id: string): Promise<void>;
77
80
  cancelSchedule(id: string): Promise<void>;
78
- updateSchedule(id: string, newSpec: string | ScheduleSpec): Promise<void>;
81
+ updateSchedule(id: string, newSpec: string | ScheduleSpec, options?: ScheduleUpdateOptions): Promise<void>;
79
82
  signal(id: string, name: SignalDefinition): Promise<void>;
80
83
  signal<TInput>(id: string, name: SignalDefinition<TInput>, payload: TInput, options?: SignalDeliveryOptions): Promise<void>;
81
84
  signal(id: string, name: string, payload?: unknown, options?: SignalDeliveryOptions): Promise<void>;
@@ -1,5 +1,6 @@
1
1
  import {
2
- CATALOG_OPERATION_NAMES
2
+ CLIENT_OPERATION_NAMES,
3
+ CLIENT_REST_OPERATION_BINDINGS
3
4
  } from "../cli/generated/operation-client.generated.js";
4
5
  import { createCatalogWeftClient } from "../cli/operation-client-runtime.js";
5
6
  import { messageName } from "../core/types.js";
@@ -19,6 +20,7 @@ import {
19
20
  getTimelineRequest,
20
21
  getUpdateResultRequest,
21
22
  getWorkflowRequest,
23
+ listPendingAsyncActivitiesRequest,
22
24
  listReviewRequests,
23
25
  pauseScheduleRequest,
24
26
  purgeWorkflowRequests,
@@ -38,27 +40,31 @@ import {
38
40
  untagAllWorkflowRequests,
39
41
  updateScheduleRequest
40
42
  } from "./http-client-requests.js";
43
+ import { createHttpClientStorage } from "./http-client-storage.js";
41
44
  import { HttpHandle } from "./http-handle.js";
42
- import { httpClientCatalogTransport } from "./http-operations.js";
45
+ import { httpClientOperationTransport } from "./http-operations.js";
43
46
  import { request, resolveHttpClientConnection } from "./http-request.js";
44
47
  import { HttpScheduleHandle } from "./http-schedule-handle.js";
45
48
  import { openClientEventSubscription } from "./open-event-subscription.js";
46
49
  import { buildScheduleListSearchParams } from "./schedule-list-search-params.js";
47
50
  import { buildWorkflowListSearchParams } from "./search-params.js";
48
- import { buildStartBody, buildStartOrSignalBody, scheduleSpecToWireFields } from "./start-body.js";
51
+ import { buildScheduleBody, buildStartBody, buildStartOrSignalBody } from "./start-body.js";
49
52
 
50
53
  export class HttpClient {
51
54
  baseUrl;
52
55
  headers;
53
56
  operations;
57
+ storage;
54
58
  activity;
55
59
  #streamOptions;
56
60
  constructor(options = {}) {
57
61
  const connection = resolveHttpClientConnection(options);
58
62
  this.baseUrl = connection.baseUrl;
59
63
  this.headers = connection.headers;
60
- this.operations = createCatalogWeftClient(CATALOG_OPERATION_NAMES, httpClientCatalogTransport(this.baseUrl, this.headers));
64
+ this.operations = createCatalogWeftClient(CLIENT_OPERATION_NAMES, httpClientOperationTransport(this.baseUrl, this.headers, CLIENT_REST_OPERATION_BINDINGS));
65
+ this.storage = createHttpClientStorage(this.baseUrl, this.headers);
61
66
  this.activity = {
67
+ listPending: (workflowId, listOptions) => listPendingAsyncActivitiesRequest(this, workflowId, listOptions),
62
68
  complete: (token, result) => completeAsyncActivityRequest(this, token, result),
63
69
  completeExceptionally: (token, error) => failAsyncActivityRequest(this, token, error)
64
70
  };
@@ -82,18 +88,7 @@ export class HttpClient {
82
88
  return new HttpHandle(response.id, this, response.outcome);
83
89
  }
84
90
  async schedule(type, input, spec, options) {
85
- const body = {
86
- type,
87
- input,
88
- ...scheduleSpecToWireFields(spec)
89
- };
90
- if (options?.id !== void 0)
91
- body.id = options.id;
92
- if (options?.overlap !== void 0)
93
- body.overlap = options.overlap;
94
- if (options?.backfill !== void 0)
95
- body.backfill = options.backfill;
96
- const response = await request(this.baseUrl, "/schedules", this.headers, {
91
+ const body = buildScheduleBody(type, input, spec, options), response = await request(this.baseUrl, "/schedules", this.headers, {
97
92
  method: "POST",
98
93
  body: JSON.stringify(body)
99
94
  });
@@ -133,8 +128,8 @@ export class HttpClient {
133
128
  async cancelSchedule(id) {
134
129
  return cancelScheduleRequest(this, id);
135
130
  }
136
- async updateSchedule(id, newSpec) {
137
- return updateScheduleRequest(this, id, newSpec);
131
+ async updateSchedule(id, newSpec, options) {
132
+ return updateScheduleRequest(this, id, newSpec, options);
138
133
  }
139
134
  async signal(id, nameOrDefinition, payload, options) {
140
135
  await signalWorkflowRequest(this, id, messageName(nameOrDefinition), payload, options);
@@ -10,7 +10,7 @@
10
10
  *
11
11
  * @module client/http-operations
12
12
  */
13
- import type { CatalogTransport } from '../cli/operation-client-runtime.ts';
13
+ import type { CatalogTransport, ClientRestOperationBinding } from '../cli/operation-client-runtime.ts';
14
14
  /**
15
15
  * Build a {@link CatalogTransport} that dispatches catalog operations as
16
16
  * JSON-RPC requests to a remote Weft server at `${baseUrl}/jsonrpc`.
@@ -27,3 +27,5 @@ import type { CatalogTransport } from '../cli/operation-client-runtime.ts';
27
27
  * `HttpClientError` rather than a raw `SyntaxError`.
28
28
  */
29
29
  export declare function httpClientCatalogTransport(baseUrl: string, headers: Record<string, string>): CatalogTransport;
30
+ /** Route generated client operations over JSON-RPC or ordinary REST metadata. */
31
+ export declare function httpClientOperationTransport(baseUrl: string, headers: Record<string, string>, restBindings: Readonly<Record<string, ClientRestOperationBinding>>): CatalogTransport;
@@ -1,11 +1,14 @@
1
1
  import { isFaultCode } from "../core/fault-code.js";
2
- import { HttpClientError } from "./http-request.js";
2
+ import { HttpClientError, request } from "./http-request.js";
3
3
  function isJsonRpcFailure(value) {
4
4
  return typeof value === "object" && value !== null && "error" in value && typeof value.error === "object" && value.error !== null && "message" in value.error && typeof value.error.message === "string";
5
5
  }
6
6
  function isJsonRpcSuccess(value) {
7
7
  return typeof value === "object" && value !== null && "result" in value;
8
8
  }
9
+ function isRecord(value) {
10
+ return typeof value === "object" && value !== null && !Array.isArray(value);
11
+ }
9
12
  export function httpClientCatalogTransport(baseUrl, headers) {
10
13
  const endpoint = `${baseUrl}/jsonrpc`;
11
14
  return async (operationName, input) => {
@@ -27,10 +30,89 @@ export function httpClientCatalogTransport(baseUrl, headers) {
27
30
  }
28
31
  if (isJsonRpcFailure(body)) {
29
32
  const { message, data } = body.error, httpStatus = typeof data?.httpStatus === "number" ? data.httpStatus : response.status, faultCode = isFaultCode(data?.weftCode) ? data.weftCode : void 0;
30
- throw new HttpClientError(httpStatus, message, { faultCode });
33
+ throw new HttpClientError(httpStatus, message, {
34
+ faultCode,
35
+ data: isRecord(data) ? data : void 0
36
+ });
31
37
  }
32
38
  if (isJsonRpcSuccess(body))
33
39
  return body.result;
34
40
  throw new HttpClientError(response.status, `Invalid JSON-RPC response from ${endpoint}`);
35
41
  };
36
42
  }
43
+ function inputRecord(operationName, input) {
44
+ if (isRecord(input))
45
+ return input;
46
+ throw new HttpClientError(400, `Operation ${operationName} input must be an object.`);
47
+ }
48
+ function requiredString(operationName, field, value) {
49
+ if (typeof value === "string" && value.length > 0)
50
+ return value;
51
+ throw new HttpClientError(400, `Operation ${operationName} requires a non-empty string "${field}" field.`);
52
+ }
53
+ function restParameterValue(operationName, field, value) {
54
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean")
55
+ return String(value);
56
+ throw new HttpClientError(400, `Operation ${operationName} requires a string, number, or boolean "${field}" field.`);
57
+ }
58
+ function appendQueryValues(projection, operationName, field, queryParameter, value, repeating) {
59
+ if (repeating && Array.isArray(value)) {
60
+ for (const entry of value)
61
+ projection.searchParameters.append(queryParameter, restParameterValue(operationName, field, entry));
62
+ return;
63
+ }
64
+ projection.searchParameters.set(queryParameter, restParameterValue(operationName, field, value));
65
+ }
66
+ function projectRestInput(projection, operationName, field, source, value) {
67
+ if (source.kind === "path") {
68
+ const pathValue = requiredString(operationName, field, value);
69
+ projection.path = projection.path.replaceAll(`:${source.pathParam}`, encodeURIComponent(pathValue));
70
+ return;
71
+ }
72
+ if (value === void 0)
73
+ return;
74
+ if (source.kind === "query") {
75
+ appendQueryValues(projection, operationName, field, source.queryParam, value, source.repeating === !0);
76
+ return;
77
+ }
78
+ if (source.kind === "header") {
79
+ projection.requestHeaders.set(source.headerName, restParameterValue(operationName, field, value));
80
+ return;
81
+ }
82
+ if (source.kind === "body-field") {
83
+ projection.bodyFields[source.bodyField] = value;
84
+ return;
85
+ }
86
+ projection.directBody = value;
87
+ projection.directBodyMediaType = source.mediaType ?? "application/json";
88
+ }
89
+ async function callRestOperation(baseUrl, headers, operationName, binding, input) {
90
+ const fields = inputRecord(operationName, input), projection = {
91
+ path: binding.path,
92
+ searchParameters: new URLSearchParams,
93
+ requestHeaders: new Headers,
94
+ bodyFields: {},
95
+ directBody: void 0,
96
+ directBodyMediaType: void 0
97
+ };
98
+ for (const [field, source] of Object.entries(binding.inputSources))
99
+ projectRestInput(projection, operationName, field, source, fields[field]);
100
+ const query = projection.searchParameters.toString();
101
+ if (query.length > 0)
102
+ projection.path += `?${query}`;
103
+ const hasBodyFields = Object.keys(projection.bodyFields).length > 0, body = projection.directBody === void 0 ? hasBodyFields ? projection.bodyFields : void 0 : projection.directBody;
104
+ if (body !== void 0)
105
+ projection.requestHeaders.set("content-type", projection.directBodyMediaType ?? "application/json");
106
+ return request(baseUrl, projection.path, headers, {
107
+ method: binding.method,
108
+ ...[...projection.requestHeaders].length === 0 ? {} : { headers: projection.requestHeaders },
109
+ ...body === void 0 ? {} : { body: JSON.stringify(body) }
110
+ });
111
+ }
112
+ export function httpClientOperationTransport(baseUrl, headers, restBindings) {
113
+ const jsonRpc = httpClientCatalogTransport(baseUrl, headers);
114
+ return (operationName, input) => {
115
+ const restBinding = restBindings[operationName];
116
+ return restBinding === void 0 ? jsonRpc(operationName, input) : callRestOperation(baseUrl, headers, operationName, restBinding, input);
117
+ };
118
+ }
@@ -63,11 +63,15 @@ export declare function resolveHttpClientConnection(options: HttpClientOptions):
63
63
  /**
64
64
  * Error thrown when the server returns a non-2xx response.
65
65
  *
66
- * When the server sends a structured fault body (`{ error: { code, message } }`),
67
- * the wire fault `code` is surfaced as {@link HttpClientError.faultCode} and a
68
- * derived {@link HttpClientError.category} so callers can branch programmatically
69
- * instead of string-matching `message`. Both are `undefined` when the body is a
70
- * plain `{ error: string }` or carries no recognized code.
66
+ * When the server sends a fault body with a recognized coarse `code`, the wire
67
+ * value is surfaced as {@link HttpClientError.faultCode} with a derived
68
+ * {@link HttpClientError.category}, so callers can branch without
69
+ * string-matching `message`. Both are `undefined` for the production flat REST
70
+ * body, which carries no coarse code. The fault's typed
71
+ * `data` payload (e.g. `InvalidParams`'s `issues`, `NotFound`/`Conflict`'s
72
+ * `resource`/`identifier`) is surfaced verbatim as {@link HttpClientError.data}
73
+ * when the body carries an audited `data` object; `undefined` otherwise —
74
+ * including for a masked `EngineFailure`, whose flat body carries no `data`.
71
75
  *
72
76
  * @example
73
77
  * ```ts
@@ -78,7 +82,7 @@ export declare function resolveHttpClientConnection(options: HttpClientOptions):
78
82
  * await client.cancel('nonexistent-id');
79
83
  * } catch (err) {
80
84
  * if (err instanceof HttpClientError) {
81
- * if (err.faultCode === 'NotFound') {
85
+ * if (err.status === 404 && err.data?.['resource'] === 'workflow') {
82
86
  * // handle a missing resource specifically
83
87
  * } else if (err.category === 'resource') {
84
88
  * // back off on rate limits and capacity faults
@@ -110,9 +114,34 @@ export declare class HttpClientError extends WeftError<'HttpClientError'> {
110
114
  * carried only the coarse {@link faultCode} (most faults) or no structured body.
111
115
  */
112
116
  readonly weftCode?: WeftErrorCode | undefined;
117
+ /**
118
+ * The fault's wire `data` payload, when the response carried a structured
119
+ * body (`{ error, data }` for production REST, nested `{ error: { data } }`
120
+ * for structured REST responses, or `error.data` for JSON-RPC). Shape is
121
+ * fault-code-dependent — see {@link FaultCode} and the server's
122
+ * `OperationFault` per-code `data` union for what each code carries (e.g.
123
+ * `InvalidParams.data.issues`, `NotFound.data.resource`). `undefined` for
124
+ * plain-string error bodies, bodies with no `data` field, or a `data` field
125
+ * that is not a JSON object.
126
+ *
127
+ * Over JSON-RPC-over-HTTP this is the raw envelope `error.data` verbatim
128
+ * (see `httpClientCatalogTransport` in `http-operations.ts`), so it also
129
+ * carries the envelope's own `weftCode` (the coarse {@link FaultCode}, not
130
+ * a fine-grained {@link WeftErrorCode}) and `httpStatus` keys alongside the
131
+ * per-code payload — those two are not part of the `OperationFault` data
132
+ * union.
133
+ */
134
+ readonly data?: Readonly<Record<string, unknown>> | undefined;
113
135
  constructor(status: number, message: string, options?: {
114
136
  faultCode?: FaultCode | undefined;
115
137
  weftCode?: WeftErrorCode | undefined;
138
+ data?: Readonly<Record<string, unknown>> | undefined;
116
139
  });
117
140
  }
118
141
  export declare function request<T>(baseUrl: string, path: string, baseHeaders: Record<string, string>, options?: RequestInit): Promise<T>;
142
+ /**
143
+ * Execute a REST request with the canonical {@link HttpClientError} shaping
144
+ * while leaving the successful response body available to binary or streaming
145
+ * client surfaces.
146
+ */
147
+ export declare function requestResponse(baseUrl: string, path: string, baseHeaders: Record<string, string>, options?: RequestInit): Promise<Response | null>;
@@ -17,12 +17,14 @@ export class HttpClientError extends WeftError {
17
17
  faultCode;
18
18
  category;
19
19
  weftCode;
20
+ data;
20
21
  constructor(status, message, options) {
21
22
  super("HttpClientError", message);
22
23
  this.status = status;
23
24
  this.faultCode = options?.faultCode;
24
25
  this.category = options?.faultCode === void 0 ? void 0 : failureCategoryForFaultCode(options.faultCode);
25
26
  this.weftCode = options?.weftCode;
27
+ this.data = options?.data;
26
28
  }
27
29
  }
28
30
  function buildRequestHeaders(baseHeaders, options) {
@@ -39,6 +41,14 @@ function buildRequestHeaders(baseHeaders, options) {
39
41
  function isFlatErrorBody(value) {
40
42
  return value !== null && typeof value === "object" && typeof value.error === "string";
41
43
  }
44
+ function parseFlatErrorBody(body) {
45
+ const weftCode = isWeftErrorCode(body.weftCode) ? body.weftCode : void 0, data = isRecord(body.data) ? body.data : void 0;
46
+ return {
47
+ message: body.error,
48
+ ...weftCode === void 0 ? {} : { weftCode },
49
+ ...data === void 0 ? {} : { data }
50
+ };
51
+ }
42
52
  function isStructuredErrorBody(value) {
43
53
  if (value === null || typeof value !== "object")
44
54
  return !1;
@@ -53,31 +63,42 @@ function weftCodeFromData(data) {
53
63
  const candidate = data.weftCode;
54
64
  return isWeftErrorCode(candidate) ? candidate : void 0;
55
65
  }
66
+ function isRecord(value) {
67
+ return typeof value === "object" && value !== null && !Array.isArray(value);
68
+ }
56
69
  async function parseErrorBody(response) {
57
70
  try {
58
71
  const body = await response.json();
59
72
  if (isStructuredErrorBody(body)) {
60
- const { code, message, data } = body.error, weftCode = weftCodeFromData(data), base = weftCode === void 0 ? { message } : { message, weftCode };
73
+ const { code, message, data } = body.error, weftCode = weftCodeFromData(data), base = { message };
74
+ if (weftCode !== void 0)
75
+ base.weftCode = weftCode;
76
+ if (isRecord(data))
77
+ base.data = data;
61
78
  return isFaultCode(code) ? { ...base, faultCode: code } : base;
62
79
  }
63
- if (isFlatErrorBody(body) && body.error) {
64
- const weftCode = isWeftErrorCode(body.weftCode) ? body.weftCode : void 0;
65
- return weftCode === void 0 ? { message: body.error } : { message: body.error, weftCode };
66
- }
80
+ if (isFlatErrorBody(body) && body.error)
81
+ return parseFlatErrorBody(body);
67
82
  return { message: response.statusText };
68
83
  } catch {
69
84
  return { message: response.statusText };
70
85
  }
71
86
  }
72
87
  export async function request(baseUrl, path, baseHeaders, options) {
88
+ const response = await requestResponse(baseUrl, path, baseHeaders, options);
89
+ if (response === null)
90
+ return null;
91
+ if (response.status === 204)
92
+ return;
93
+ return response.json();
94
+ }
95
+ export async function requestResponse(baseUrl, path, baseHeaders, options) {
73
96
  const headers = buildRequestHeaders(baseHeaders, options), response = await fetch(`${baseUrl}/v1${path}`, { ...options, headers });
74
97
  if (response.status === 404 && (!options?.method || options.method === "GET"))
75
98
  return null;
76
99
  if (!response.ok) {
77
- const { message, faultCode, weftCode } = await parseErrorBody(response);
78
- throw new HttpClientError(response.status, message, { faultCode, weftCode });
100
+ const { message, faultCode, weftCode, data } = await parseErrorBody(response);
101
+ throw new HttpClientError(response.status, message, { faultCode, weftCode, data });
79
102
  }
80
- if (response.status === 204)
81
- return;
82
- return response.json();
103
+ return response;
83
104
  }