@procedures/client 0.14.0 → 0.15.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 (136) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/README.md +12 -3
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/adapters/http/client/transport.d.ts.map +1 -1
  5. package/dist/adapters/http/client/transport.js +4 -0
  6. package/dist/adapters/http/client/transport.js.map +1 -1
  7. package/dist/adapters/http/server/body.d.ts +17 -0
  8. package/dist/adapters/http/server/body.d.ts.map +1 -0
  9. package/dist/adapters/http/server/body.js +44 -0
  10. package/dist/adapters/http/server/body.js.map +1 -0
  11. package/dist/adapters/http/server/cors.d.ts +24 -1
  12. package/dist/adapters/http/server/cors.d.ts.map +1 -1
  13. package/dist/adapters/http/server/cors.js +33 -1
  14. package/dist/adapters/http/server/cors.js.map +1 -1
  15. package/dist/adapters/http/server/handler.d.ts +3 -1
  16. package/dist/adapters/http/server/handler.d.ts.map +1 -1
  17. package/dist/adapters/http/server/handler.js +7 -34
  18. package/dist/adapters/http/server/handler.js.map +1 -1
  19. package/dist/adapters/local/client/transport.d.ts.map +1 -1
  20. package/dist/adapters/local/client/transport.js +5 -0
  21. package/dist/adapters/local/client/transport.js.map +1 -1
  22. package/dist/browser.d.ts +4 -3
  23. package/dist/browser.d.ts.map +1 -1
  24. package/dist/browser.js +6 -1
  25. package/dist/browser.js.map +1 -1
  26. package/dist/client/client.js +1 -1
  27. package/dist/client/client.js.map +1 -1
  28. package/dist/client/errors/registry.d.ts.map +1 -1
  29. package/dist/client/errors/registry.js +1 -0
  30. package/dist/client/errors/registry.js.map +1 -1
  31. package/dist/client/types.d.ts +6 -1
  32. package/dist/client/types.d.ts.map +1 -1
  33. package/dist/client/types.js +7 -1
  34. package/dist/client/types.js.map +1 -1
  35. package/dist/events/publisher.d.ts.map +1 -1
  36. package/dist/events/publisher.js +10 -5
  37. package/dist/events/publisher.js.map +1 -1
  38. package/dist/events/rows.d.ts +12 -4
  39. package/dist/events/rows.d.ts.map +1 -1
  40. package/dist/events/rows.js +13 -16
  41. package/dist/events/rows.js.map +1 -1
  42. package/dist/index.d.ts +5 -2
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +8 -2
  45. package/dist/index.js.map +1 -1
  46. package/dist/node.d.ts +10 -1
  47. package/dist/node.d.ts.map +1 -1
  48. package/dist/node.js +11 -0
  49. package/dist/node.js.map +1 -1
  50. package/dist/procedures/collection/admission.d.ts +77 -0
  51. package/dist/procedures/collection/admission.d.ts.map +1 -0
  52. package/dist/procedures/collection/admission.js +204 -0
  53. package/dist/procedures/collection/admission.js.map +1 -0
  54. package/dist/procedures/collection/changes.d.ts +3 -1
  55. package/dist/procedures/collection/changes.d.ts.map +1 -1
  56. package/dist/procedures/collection/changes.js +10 -6
  57. package/dist/procedures/collection/changes.js.map +1 -1
  58. package/dist/procedures/collection/index.d.ts +5 -1
  59. package/dist/procedures/collection/index.d.ts.map +1 -1
  60. package/dist/procedures/collection/index.js +4 -0
  61. package/dist/procedures/collection/index.js.map +1 -1
  62. package/dist/procedures/collection/patch-in-parts.d.ts +15 -0
  63. package/dist/procedures/collection/patch-in-parts.d.ts.map +1 -0
  64. package/dist/procedures/collection/patch-in-parts.js +47 -0
  65. package/dist/procedures/collection/patch-in-parts.js.map +1 -0
  66. package/dist/procedures/collection/precondition.d.ts +111 -0
  67. package/dist/procedures/collection/precondition.d.ts.map +1 -0
  68. package/dist/procedures/collection/precondition.js +153 -0
  69. package/dist/procedures/collection/precondition.js.map +1 -0
  70. package/dist/procedures/collection/procedures.d.ts +8 -0
  71. package/dist/procedures/collection/procedures.d.ts.map +1 -1
  72. package/dist/procedures/collection/procedures.js +66 -76
  73. package/dist/procedures/collection/procedures.js.map +1 -1
  74. package/dist/procedures/collection/query.d.ts +2 -0
  75. package/dist/procedures/collection/query.d.ts.map +1 -1
  76. package/dist/procedures/collection/query.js +3 -0
  77. package/dist/procedures/collection/query.js.map +1 -1
  78. package/dist/procedures/collection/schemas.d.ts +16 -0
  79. package/dist/procedures/collection/schemas.d.ts.map +1 -1
  80. package/dist/procedures/collection/schemas.js +26 -5
  81. package/dist/procedures/collection/schemas.js.map +1 -1
  82. package/dist/procedures/collection/storage.d.ts +34 -6
  83. package/dist/procedures/collection/storage.d.ts.map +1 -1
  84. package/dist/procedures/collection/storage.js +3 -0
  85. package/dist/procedures/collection/storage.js.map +1 -1
  86. package/dist/procedures/index.d.ts +2 -2
  87. package/dist/procedures/index.d.ts.map +1 -1
  88. package/dist/procedures/index.js +3 -1
  89. package/dist/procedures/index.js.map +1 -1
  90. package/dist/procedures/invoke.d.ts +5 -2
  91. package/dist/procedures/invoke.d.ts.map +1 -1
  92. package/dist/procedures/invoke.js +5 -1
  93. package/dist/procedures/invoke.js.map +1 -1
  94. package/dist/procedures/types.d.ts +7 -0
  95. package/dist/procedures/types.d.ts.map +1 -1
  96. package/dist/procedures/types.js.map +1 -1
  97. package/dist/protocol.d.ts +1 -1
  98. package/dist/protocol.d.ts.map +1 -1
  99. package/dist/protocol.js +1 -0
  100. package/dist/protocol.js.map +1 -1
  101. package/dist/server/index.d.ts +70 -5
  102. package/dist/server/index.d.ts.map +1 -1
  103. package/dist/server/index.js +26 -3
  104. package/dist/server/index.js.map +1 -1
  105. package/dist/server/procedure-server.d.ts.map +1 -1
  106. package/dist/server/procedure-server.js +5 -0
  107. package/dist/server/procedure-server.js.map +1 -1
  108. package/package.json +12 -1
  109. package/src/adapters/http/client/transport.ts +3 -0
  110. package/src/adapters/http/server/body.ts +43 -0
  111. package/src/adapters/http/server/cors.ts +42 -1
  112. package/src/adapters/http/server/handler.ts +10 -32
  113. package/src/adapters/local/client/transport.ts +4 -0
  114. package/src/browser.ts +25 -1
  115. package/src/client/client.ts +1 -1
  116. package/src/client/errors/registry.ts +1 -0
  117. package/src/client/types.ts +8 -0
  118. package/src/events/publisher.ts +8 -5
  119. package/src/events/rows.ts +19 -6
  120. package/src/index.ts +9 -6
  121. package/src/node.ts +13 -1
  122. package/src/procedures/collection/admission.ts +243 -0
  123. package/src/procedures/collection/changes.ts +11 -5
  124. package/src/procedures/collection/index.ts +21 -0
  125. package/src/procedures/collection/patch-in-parts.ts +56 -0
  126. package/src/procedures/collection/precondition.ts +180 -0
  127. package/src/procedures/collection/procedures.ts +69 -78
  128. package/src/procedures/collection/query.ts +16 -0
  129. package/src/procedures/collection/schemas.ts +43 -6
  130. package/src/procedures/collection/storage.ts +36 -6
  131. package/src/procedures/index.ts +22 -0
  132. package/src/procedures/invoke.ts +7 -2
  133. package/src/procedures/types.ts +8 -0
  134. package/src/protocol.ts +3 -1
  135. package/src/server/index.ts +94 -17
  136. package/src/server/procedure-server.ts +4 -0
@@ -31,6 +31,22 @@
31
31
  * regular expressions and the limit. The default policy does not permit `$regex`.
32
32
  */
33
33
 
34
+ // The precondition of the collection writes is a part of the wire contract too (roadmap CLI-16).
35
+ // This entry gives it to the storage packages: the module has no imports.
36
+ export {
37
+ MAX_EXPECTED_VERSION_LENGTH,
38
+ PRECONDITION_NOT_SUPPORTED,
39
+ VERSION_CONFLICTS_METADATA_KEY,
40
+ VersionConflictError,
41
+ asVersionConflictError,
42
+ isExpectedVersion,
43
+ preconditionNotSupported,
44
+ versionConflict,
45
+ versionConflicts,
46
+ versionConflictsOf,
47
+ } from "./precondition.js";
48
+ export type { CurrentItem, VersionConflict, VersionOf } from "./precondition.js";
49
+
34
50
  //
35
51
  // ═══ The types ═══
36
52
  //
@@ -16,6 +16,7 @@ import {
16
16
  type StorageSelector,
17
17
  type StorageSort,
18
18
  } from "./query.js";
19
+ import { MAX_EXPECTED_VERSION_LENGTH, isExpectedVersion } from "./precondition.js";
19
20
 
20
21
  // =============================================================================
21
22
  // Generic Schema Builder
@@ -83,6 +84,25 @@ function withOriginOf<I extends object>(data: Record<string, unknown>, input: I)
83
84
  return (isString(origin) ? { ...input, origin } : input) as I & { origin?: string };
84
85
  }
85
86
 
87
+ /** The message of an expected version that is not valid (roadmap CLI-16). */
88
+ const EXPECTED_VERSION_MESSAGE = `expectedVersion must be a string of at most ${MAX_EXPECTED_VERSION_LENGTH} characters, or null`;
89
+
90
+ /**
91
+ * The error of the field `expectedVersion` of a write input (roadmap CLI-16), or undefined. The
92
+ * field is absent, a string of at most 1024 characters, or `null`.
93
+ */
94
+ function expectedVersionError(data: Record<string, unknown>, path: (string | number)[] = []): ZodErrorLike | undefined {
95
+ if (!("expectedVersion" in data) || data["expectedVersion"] === undefined || isExpectedVersion(data["expectedVersion"])) return undefined;
96
+ const message = path.length === 0 ? EXPECTED_VERSION_MESSAGE : `${path.join(".")}.${EXPECTED_VERSION_MESSAGE}`;
97
+ return { message, errors: [{ path: [...path, "expectedVersion"], message }] };
98
+ }
99
+
100
+ /** The input with the expected version of the data, when the data has one. */
101
+ function withExpectedVersionOf<I extends object>(data: Record<string, unknown>, input: I): I & { expectedVersion?: string | null } {
102
+ const expected = data["expectedVersion"];
103
+ return (isExpectedVersion(expected) ? { ...input, expectedVersion: expected } : input) as I & { expectedVersion?: string | null };
104
+ }
105
+
86
106
  // =============================================================================
87
107
  // Common Schemas
88
108
  // =============================================================================
@@ -217,6 +237,11 @@ export interface SetInput {
217
237
  value: unknown;
218
238
  /** The origin of the write, for the observers of the storage and the change event (roadmap CLI-15). */
219
239
  origin?: string;
240
+ /**
241
+ * The version that the write expects for the current item (roadmap CLI-16). `null` expects no
242
+ * item. A conflict changes nothing and answers `VERSION_CONFLICT` with the current item.
243
+ */
244
+ expectedVersion?: string | null;
220
245
  }
221
246
 
222
247
  export const setInputSchema : ZodLike<SetInput> = createSchema<SetInput>((data) => {
@@ -247,9 +272,9 @@ export const setInputSchema : ZodLike<SetInput> = createSchema<SetInput>((data)
247
272
  },
248
273
  };
249
274
  }
250
- const error = originError(data);
275
+ const error = originError(data) ?? expectedVersionError(data);
251
276
  if (error) return { success: false, error };
252
- return { success: true, data: withOriginOf(data, { id: data["id"], value: data["value"] }) };
277
+ return { success: true, data: withExpectedVersionOf(data, withOriginOf(data, { id: data["id"], value: data["value"] })) };
253
278
  });
254
279
 
255
280
  /**
@@ -270,6 +295,11 @@ export interface DeleteInput {
270
295
  id: string;
271
296
  /** The origin of the write, for the observers of the storage and the change event (roadmap CLI-15). */
272
297
  origin?: string;
298
+ /**
299
+ * The version that the delete expects for the current item (roadmap CLI-16). `null` expects no
300
+ * item. A conflict changes nothing and answers `VERSION_CONFLICT` with the current item.
301
+ */
302
+ expectedVersion?: string | null;
273
303
  }
274
304
 
275
305
  export const deleteInputSchema : ZodLike<DeleteInput> = createSchema<DeleteInput>((data) => {
@@ -291,9 +321,9 @@ export const deleteInputSchema : ZodLike<DeleteInput> = createSchema<DeleteInput
291
321
  },
292
322
  };
293
323
  }
294
- const error = originError(data);
324
+ const error = originError(data) ?? expectedVersionError(data);
295
325
  if (error) return { success: false, error };
296
- return { success: true, data: withOriginOf(data, { id: data["id"] }) };
326
+ return { success: true, data: withExpectedVersionOf(data, withOriginOf(data, { id: data["id"] })) };
297
327
  });
298
328
 
299
329
  /**
@@ -810,7 +840,12 @@ export const getBatchOutputSchema : ZodLike<Record<string, unknown>> = createSch
810
840
  * Input for setBatch operation.
811
841
  */
812
842
  export interface SetBatchInput {
813
- items: Array<{ id: string; value: unknown }>;
843
+ /**
844
+ * The items. An item can have `expectedVersion`: the version that the write expects for the
845
+ * current item of its id (roadmap CLI-16). When one item has a conflict, the batch changes no
846
+ * item and answers `VERSION_CONFLICT`.
847
+ */
848
+ items: Array<{ id: string; value: unknown; expectedVersion?: string | null }>;
814
849
  /** The origin of the write, for the observers of the storage and the change event (roadmap CLI-15). */
815
850
  origin?: string;
816
851
  }
@@ -845,13 +880,15 @@ export const setBatchInputSchema : ZodLike<SetBatchInput> = createSchema<SetBatc
845
880
  },
846
881
  };
847
882
  }
883
+ const versionError = expectedVersionError(item, ["items", i]);
884
+ if (versionError) return { success: false, error: versionError };
848
885
  }
849
886
  const error = originError(data);
850
887
  if (error) return { success: false, error };
851
888
  return {
852
889
  success: true,
853
890
  data: withOriginOf(data, {
854
- items: data["items"].map((item: any) => ({ id: item["id"], value: item["value"] })),
891
+ items: data["items"].map((item: any) => withExpectedVersionOf(item, { id: item["id"], value: item["value"] })),
855
892
  }),
856
893
  };
857
894
  });
@@ -20,6 +20,9 @@
20
20
  * - `query()` gives the result of `queryEntries()` of `@procedures/collections` for the entries.
21
21
  * - `observe()` gives one batch for each write that changes the storage, before the write resolves.
22
22
  * - `patch()` merges the fields into the item in one step.
23
+ * - A write with `expectedVersion` (`set`, `delete`) or `expectedVersions` (`setBatch`) tests the
24
+ * version of the current item and writes in one step. A conflict changes nothing and rejects
25
+ * with `VersionConflictError` (roadmap CLI-16).
23
26
  */
24
27
 
25
28
  import type { StoragePatch, StorageQueryOptions, StorageQueryPolicy, StorageSelector } from "./query.js";
@@ -76,10 +79,13 @@ export interface CollectionStorage<T> {
76
79
  has(id: string): Promise<boolean>;
77
80
  /** The number of items. */
78
81
  size(): Promise<number>;
79
- /** Set the item of an id. The options give the origin of the write to the observers. */
80
- set(id: string, value: T, options?: StorageWriteOptions): Promise<void>;
81
- /** Delete the item of an id. True when it existed. */
82
- delete(id: string, options?: StorageWriteOptions): Promise<boolean>;
82
+ /**
83
+ * Set the item of an id. The options give the origin of the write to the observers, and the
84
+ * expected version of the current item (roadmap CLI-16).
85
+ */
86
+ set(id: string, value: T, options?: StorageItemWriteOptions): Promise<void>;
87
+ /** Delete the item of an id. True when it existed. The options can expect a version, as for `set`. */
88
+ delete(id: string, options?: StorageItemWriteOptions): Promise<boolean>;
83
89
  /** Delete every item. */
84
90
  clear(options?: StorageWriteOptions): Promise<void>;
85
91
  /**
@@ -88,8 +94,8 @@ export interface CollectionStorage<T> {
88
94
  * procedure `patch` uses it. A storage without it gets a read and a write of the item.
89
95
  */
90
96
  patch?(id: string, fields: StoragePatch, options?: StorageWriteOptions): Promise<T | undefined>;
91
- /** Set several items. */
92
- setBatch(items: Array<[string, T]>, options?: StorageWriteOptions): Promise<void>;
97
+ /** Set several items. The options can expect a version for each id (roadmap CLI-16). */
98
+ setBatch(items: Array<[string, T]>, options?: StorageBatchWriteOptions): Promise<void>;
93
99
  /** Delete several items. The number of items that existed. */
94
100
  deleteBatch(ids: string[], options?: StorageWriteOptions): Promise<number>;
95
101
  /** The items of several ids. An id with no item is not in the map. */
@@ -114,6 +120,30 @@ export interface StorageWriteOptions {
114
120
  readonly origin?: string | undefined;
115
121
  }
116
122
 
123
+ /**
124
+ * The options of `set` and `delete`: the origin, and the precondition of the write (roadmap
125
+ * CLI-16). `precondition.ts` gives the rules.
126
+ */
127
+ export interface StorageItemWriteOptions extends StorageWriteOptions {
128
+ /**
129
+ * The version that the write expects for the current item, from `versionOf` of the storage.
130
+ * `null` expects no item. When the current item has another version, the write changes nothing
131
+ * and rejects with `VersionConflictError`. A storage without `versionOf` rejects it with a
132
+ * `TypeError`. Absent: no precondition.
133
+ */
134
+ readonly expectedVersion?: string | null | undefined;
135
+ }
136
+
137
+ /** The options of `setBatch`: the origin, and the precondition of each id (roadmap CLI-16). */
138
+ export interface StorageBatchWriteOptions extends StorageWriteOptions {
139
+ /**
140
+ * The version that the write expects for the current item of each id, as for `expectedVersion`.
141
+ * When one id has a conflict, the batch changes no item. An id without an entry has no
142
+ * precondition.
143
+ */
144
+ readonly expectedVersions?: ReadonlyArray<readonly [string, string | null]> | undefined;
145
+ }
146
+
117
147
  /** A change of `observe()`: an id got a value. */
118
148
  export interface StorageSetChange<T> {
119
149
  readonly type: "set";
@@ -133,6 +133,9 @@ export {
133
133
  PARTITION_PATTERN,
134
134
  collectionTopicPartition,
135
135
  collectionTopicTarget,
136
+ // The rules of the rows of a collection (roadmap CLI-41)
137
+ admittedStorage,
138
+ admitsRow,
136
139
  DEFAULT_FIND_LIMIT,
137
140
  DEFAULT_QUERY_OPERATORS,
138
141
  DEFAULT_QUERY_POLICY,
@@ -147,11 +150,30 @@ export {
147
150
  queryEntries,
148
151
  queryOptionsProblems,
149
152
  selectorProblems,
153
+ MAX_EXPECTED_VERSION_LENGTH,
154
+ PRECONDITION_NOT_SUPPORTED,
155
+ VERSION_CONFLICTS_METADATA_KEY,
156
+ VersionConflictError,
157
+ asVersionConflictError,
158
+ isExpectedVersion,
159
+ preconditionNotSupported,
160
+ versionConflict,
161
+ versionConflicts,
162
+ versionConflictsOf,
150
163
  } from "./collection/index.js";
151
164
  export type {
165
+ CurrentItem,
166
+ VersionConflict,
167
+ VersionOf,
168
+ StorageItemWriteOptions,
169
+ StorageBatchWriteOptions,
152
170
  CollectionProceduresOptions,
153
171
  CollectionChangeEventsOptions,
154
172
  CollectionChangeEvent,
173
+ CollectionAdmission,
174
+ CollectionRow,
175
+ CollectionWrite,
176
+ CollectionWriteGuard,
155
177
  CollectionPartitions,
156
178
  PartitionInput,
157
179
  PartitionResolver,
@@ -76,8 +76,10 @@ export type InvocationErrorCode =
76
76
  * subscription (`documentation/EVENTS.md`).
77
77
  * - `partition-not-allowed`: the partition resolver of the collection procedures refuses the
78
78
  * partition of a call (roadmap CLI-17).
79
+ * - `write-not-allowed`: the guard of a collection (`admission.guard`) refuses a write (roadmap
80
+ * CLI-41).
79
81
  */
80
- export type ForbiddenReason = CapabilityRefusalReason | "outside-root" | "host-not-allowed" | "topic-not-allowed" | "partition-not-allowed";
82
+ export type ForbiddenReason = CapabilityRefusalReason | "outside-root" | "host-not-allowed" | "topic-not-allowed" | "partition-not-allowed" | "write-not-allowed";
81
83
 
82
84
  /** An error of the invocation itself. An error that the handler throws passes through unchanged. */
83
85
  export class InvocationError extends Error {
@@ -87,7 +89,8 @@ export class InvocationError extends Error {
87
89
  readonly retryable: boolean = false;
88
90
  /**
89
91
  * The reason of a `FORBIDDEN` error: `effects-undeclared`, `effects-exceeded`, `clearance`,
90
- * `deadline`, `outside-root`, `host-not-allowed`, `topic-not-allowed` or `partition-not-allowed`.
92
+ * `deadline`, `outside-root`, `host-not-allowed`, `topic-not-allowed`, `partition-not-allowed` or
93
+ * `write-not-allowed`.
91
94
  */
92
95
  readonly reason: ForbiddenReason | undefined;
93
96
 
@@ -550,6 +553,8 @@ export function createProcedureContext(
550
553
  if (options.principal !== undefined) context.principal = options.principal;
551
554
  // The procedure of the parent invocation made this call (roadmap R33: the root of the files)
552
555
  if (invocation?.parent) context.caller = invocation.parent.procedure;
556
+ // The rules of the rows of a collection get the invocation of the call (roadmap CLI-41)
557
+ if (invocation) context.invocation = invocation;
553
558
  // The output filter of the host, for the data that a handler sends later (roadmap CLI-15)
554
559
  const admitItem = options.admitItem;
555
560
  if (invocation && invocation.depth === 0 && admitItem) context.admit = async (item) => admitItem(item, invocation);
@@ -14,6 +14,7 @@ import type { CollectionStorage } from "./collection/storage.js";
14
14
  import type { EventBus } from "../events/types.js";
15
15
  import type { HydrationEvent } from "./ref.js";
16
16
  import type { Principal } from "../server/types.js";
17
+ import type { Invocation } from "./invoke.js";
17
18
 
18
19
  // =============================================================================
19
20
  // Procedure Path
@@ -419,6 +420,13 @@ export interface ProcedureContext {
419
420
  * examines each row with it. Absent for a nested call, or when the host has no filter.
420
421
  */
421
422
  admit?: ((item: unknown) => Promise<boolean>) | undefined;
423
+
424
+ /**
425
+ * The invocation of this call: the procedure, the path, the principal and the capability
426
+ * (roadmap CLI-41). A rule with the shape of `AdmitItem` gets it, for example the rules of the
427
+ * rows of a collection. Absent for a context without an invocation.
428
+ */
429
+ invocation?: Invocation | undefined;
422
430
  }
423
431
 
424
432
  /**
package/src/protocol.ts CHANGED
@@ -48,7 +48,8 @@ export type ProtocolErrorCode =
48
48
  | "CONNECTION_CLOSED"
49
49
  | "CONNECTION_NOT_FOUND"
50
50
  | "TOPIC_NOT_FOUND"
51
- | "TOPIC_CLEARED";
51
+ | "TOPIC_CLEARED"
52
+ | "VERSION_CONFLICT";
52
53
 
53
54
  /**
54
55
  * The error codes of the protocol, on each face (HTTP, WebSocket, MCP, local). An error that a
@@ -80,6 +81,7 @@ export const ERROR_CODES: Readonly<Record<ProtocolErrorCode, ErrorCodeInfo>> = {
80
81
  CONNECTION_NOT_FOUND: { http: 404, retryable: false, meaning: "The server has no connection with this id" },
81
82
  TOPIC_NOT_FOUND: { http: 404, retryable: false, meaning: "The topic rule of the host has no topic with this name" },
82
83
  TOPIC_CLEARED: { http: 410, retryable: false, meaning: "The host cleared a topic of the subscription: clear() of its bus removed the listener" },
84
+ VERSION_CONFLICT: { http: 409, retryable: false, meaning: "The current item does not have the version that the write expects. The metadata key conflicts has the current items" },
83
85
  };
84
86
 
85
87
  /** True when a code is an error code of the protocol. */
@@ -11,9 +11,13 @@
11
11
  * - Handler registry with pattern matching
12
12
  * - Graceful shutdown
13
13
  *
14
+ * The server transports have the entry `@procedures/client/node` (CLI-6). This entry still
15
+ * exports them as deprecated aliases. The next MINOR removes the aliases.
16
+ *
14
17
  * @example
15
18
  * ```typescript
16
- * import { Server, HttpServerTransport } from "@common/server";
19
+ * import { Server } from "@procedures/client/server";
20
+ * import { HttpServerTransport } from "@procedures/client/node";
17
21
  *
18
22
  * const server = new Server();
19
23
  *
@@ -34,6 +38,19 @@
34
38
  * ```
35
39
  */
36
40
 
41
+ import { HttpServerTransport as NodeHttpServerTransport } from "../adapters/http/server/transport.js";
42
+ import type {
43
+ HttpServerTransportOptions as NodeHttpServerTransportOptions,
44
+ HttpUrlStrategy as NodeHttpUrlStrategy,
45
+ } from "../adapters/http/server/types.js";
46
+ import { WebSocketServerTransport as NodeWebSocketServerTransport } from "../adapters/websocket/server/transport.js";
47
+ import type {
48
+ WebSocketServerTransportOptions as NodeWebSocketServerTransportOptions,
49
+ WebSocketAuthHandler as NodeWebSocketAuthHandler,
50
+ WebSocketConnectionHandler as NodeWebSocketConnectionHandler,
51
+ WebSocketMessage as NodeWebSocketMessage,
52
+ } from "../adapters/websocket/server/types.js";
53
+
37
54
  export { Server } from "./server.js";
38
55
  export type { ServerOptions } from "./server.js";
39
56
 
@@ -59,19 +76,79 @@ export {
59
76
  type ServerTransport,
60
77
  } from "./types.js";
61
78
 
62
- export {
63
- HttpServerTransport,
64
- defaultServerUrlStrategy,
65
- rpcServerUrlStrategy,
66
- } from "../adapters/http/server/index.js";
67
- export type {
68
- HttpServerTransportOptions,
69
- HttpUrlStrategy,
70
- } from "../adapters/http/server/index.js";
71
- export { WebSocketServerTransport } from "../adapters/websocket/server/index.js";
72
- export type {
73
- WebSocketServerTransportOptions,
74
- WebSocketAuthHandler,
75
- WebSocketConnectionHandler,
76
- WebSocketMessage,
77
- } from "../adapters/websocket/server/index.js";
79
+ export { defaultServerUrlStrategy, rpcServerUrlStrategy } from "../adapters/http/server/strategies.js";
80
+
81
+ // ============================================================================
82
+ // The server transports: deprecated here (CLI-6 step 1)
83
+ // ============================================================================
84
+ // Their entry is `@procedures/client/node`. This entry and the root (which re-exports these
85
+ // names from this module) give them through deprecated aliases until the next MINOR, so an
86
+ // editor marks each old import. Each alias is the same class or type as the export of ./node.
87
+
88
+ /**
89
+ * The HTTP server transport of Express, a shim over `procedureHandler`.
90
+ *
91
+ * @deprecated Import it from `@procedures/client/node`. The root and `@procedures/client/server` stop exporting it in the next MINOR (CLI-6).
92
+ */
93
+ export const HttpServerTransport: typeof NodeHttpServerTransport = NodeHttpServerTransport;
94
+ /**
95
+ * The HTTP server transport of Express, a shim over `procedureHandler`.
96
+ *
97
+ * @deprecated Import it from `@procedures/client/node`. The root and `@procedures/client/server` stop exporting it in the next MINOR (CLI-6).
98
+ */
99
+ export type HttpServerTransport = NodeHttpServerTransport;
100
+
101
+ /**
102
+ * The options of `HttpServerTransport`.
103
+ *
104
+ * @deprecated Import it from `@procedures/client/node`. The root and `@procedures/client/server` stop exporting it in the next MINOR (CLI-6).
105
+ */
106
+ export type HttpServerTransportOptions = NodeHttpServerTransportOptions;
107
+
108
+ /**
109
+ * The URL strategy of `HttpServerTransport`: it gives the method of an Express request.
110
+ *
111
+ * @deprecated Import it from `@procedures/client/node`. The root and `@procedures/client/server` stop exporting it in the next MINOR (CLI-6).
112
+ */
113
+ export type HttpUrlStrategy = NodeHttpUrlStrategy;
114
+
115
+ /**
116
+ * The WebSocket server transport, on the `ws` package.
117
+ *
118
+ * @deprecated Import it from `@procedures/client/node`. The root and `@procedures/client/server` stop exporting it in the next MINOR (CLI-6).
119
+ */
120
+ export const WebSocketServerTransport: typeof NodeWebSocketServerTransport = NodeWebSocketServerTransport;
121
+ /**
122
+ * The WebSocket server transport, on the `ws` package.
123
+ *
124
+ * @deprecated Import it from `@procedures/client/node`. The root and `@procedures/client/server` stop exporting it in the next MINOR (CLI-6).
125
+ */
126
+ export type WebSocketServerTransport = NodeWebSocketServerTransport;
127
+
128
+ /**
129
+ * The options of `WebSocketServerTransport`.
130
+ *
131
+ * @deprecated Import it from `@procedures/client/node`. The root and `@procedures/client/server` stop exporting it in the next MINOR (CLI-6).
132
+ */
133
+ export type WebSocketServerTransportOptions = NodeWebSocketServerTransportOptions;
134
+
135
+ /**
136
+ * The authentication handler of `WebSocketServerTransport`, for each upgrade request.
137
+ *
138
+ * @deprecated Import it from `@procedures/client/node`. The root and `@procedures/client/server` stop exporting it in the next MINOR (CLI-6).
139
+ */
140
+ export type WebSocketAuthHandler = NodeWebSocketAuthHandler;
141
+
142
+ /**
143
+ * The connection handler of `WebSocketServerTransport`, for each new connection.
144
+ *
145
+ * @deprecated Import it from `@procedures/client/node`. The root and `@procedures/client/server` stop exporting it in the next MINOR (CLI-6).
146
+ */
147
+ export type WebSocketConnectionHandler = NodeWebSocketConnectionHandler;
148
+
149
+ /**
150
+ * The RPC message of `WebSocketServerTransport`.
151
+ *
152
+ * @deprecated Import it from `@procedures/client/node`. The root and `@procedures/client/server` stop exporting it in the next MINOR (CLI-6).
153
+ */
154
+ export type WebSocketMessage<TReq = unknown> = NodeWebSocketMessage<TReq>;
@@ -20,6 +20,7 @@ import { createCollectionProcedures } from "../procedures/collection/procedures.
20
20
  import { InvocationError, invokeProcedure, type AdmitItem, type Interceptor, type Invocation } from "../procedures/invoke.js";
21
21
  import type { Capability, ClearanceOrder, ProcedureContext, ResourcesOf } from "../procedures/types.js";
22
22
  import type { CollectionPartitions } from "../procedures/collection/partition.js";
23
+ import { VERSION_CONFLICTS_METADATA_KEY, versionConflictsOf } from "../procedures/collection/precondition.js";
23
24
  import { TIMEOUT_METADATA_KEY, effectsOf, joinNarrowing, narrowCapability } from "../procedures/capability.js";
24
25
  import { IdempotencyCache, idempotencyKeyOf, isRepeatable, type IdempotencyOptions } from "../procedures/idempotency.js";
25
26
  import { optionalMs, type Duration } from "../duration.js";
@@ -335,6 +336,9 @@ export function errorResponse(id: string, error: unknown): ServerResponse<unknow
335
336
  const retryAfterMs = (error as { retryAfterMs?: unknown } | undefined)?.retryAfterMs;
336
337
  const metadata: Record<string, unknown> = reason ? { reason } : {};
337
338
  if (typeof retryAfterMs === "number" && Number.isFinite(retryAfterMs) && retryAfterMs >= 0) metadata["retry-after"] = retryAfterMs;
339
+ // A VERSION_CONFLICT error tells the current item of each conflict (roadmap CLI-16)
340
+ const conflicts = versionConflictsOf(error);
341
+ if (conflicts !== undefined) metadata[VERSION_CONFLICTS_METADATA_KEY] = conflicts;
338
342
  const code = errorCodeOf(error);
339
343
  return {
340
344
  id,