@procedures/client 0.14.0 → 0.14.1

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 (87) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +12 -3
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/adapters/http/server/body.d.ts +17 -0
  5. package/dist/adapters/http/server/body.d.ts.map +1 -0
  6. package/dist/adapters/http/server/body.js +44 -0
  7. package/dist/adapters/http/server/body.js.map +1 -0
  8. package/dist/adapters/http/server/cors.d.ts +24 -1
  9. package/dist/adapters/http/server/cors.d.ts.map +1 -1
  10. package/dist/adapters/http/server/cors.js +33 -1
  11. package/dist/adapters/http/server/cors.js.map +1 -1
  12. package/dist/adapters/http/server/handler.d.ts +3 -1
  13. package/dist/adapters/http/server/handler.d.ts.map +1 -1
  14. package/dist/adapters/http/server/handler.js +4 -34
  15. package/dist/adapters/http/server/handler.js.map +1 -1
  16. package/dist/browser.d.ts +4 -3
  17. package/dist/browser.d.ts.map +1 -1
  18. package/dist/browser.js +4 -1
  19. package/dist/browser.js.map +1 -1
  20. package/dist/events/publisher.d.ts.map +1 -1
  21. package/dist/events/publisher.js +10 -5
  22. package/dist/events/publisher.js.map +1 -1
  23. package/dist/events/rows.d.ts +12 -4
  24. package/dist/events/rows.d.ts.map +1 -1
  25. package/dist/events/rows.js +13 -16
  26. package/dist/events/rows.js.map +1 -1
  27. package/dist/index.d.ts +5 -2
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +8 -2
  30. package/dist/index.js.map +1 -1
  31. package/dist/node.d.ts +10 -1
  32. package/dist/node.d.ts.map +1 -1
  33. package/dist/node.js +11 -0
  34. package/dist/node.js.map +1 -1
  35. package/dist/procedures/collection/admission.d.ts +77 -0
  36. package/dist/procedures/collection/admission.d.ts.map +1 -0
  37. package/dist/procedures/collection/admission.js +204 -0
  38. package/dist/procedures/collection/admission.js.map +1 -0
  39. package/dist/procedures/collection/changes.d.ts +3 -1
  40. package/dist/procedures/collection/changes.d.ts.map +1 -1
  41. package/dist/procedures/collection/changes.js +10 -6
  42. package/dist/procedures/collection/changes.js.map +1 -1
  43. package/dist/procedures/collection/index.d.ts +2 -0
  44. package/dist/procedures/collection/index.d.ts.map +1 -1
  45. package/dist/procedures/collection/index.js +2 -0
  46. package/dist/procedures/collection/index.js.map +1 -1
  47. package/dist/procedures/collection/patch-in-parts.d.ts +15 -0
  48. package/dist/procedures/collection/patch-in-parts.d.ts.map +1 -0
  49. package/dist/procedures/collection/patch-in-parts.js +47 -0
  50. package/dist/procedures/collection/patch-in-parts.js.map +1 -0
  51. package/dist/procedures/collection/procedures.d.ts +8 -0
  52. package/dist/procedures/collection/procedures.d.ts.map +1 -1
  53. package/dist/procedures/collection/procedures.js +54 -77
  54. package/dist/procedures/collection/procedures.js.map +1 -1
  55. package/dist/procedures/index.d.ts +2 -2
  56. package/dist/procedures/index.d.ts.map +1 -1
  57. package/dist/procedures/index.js +3 -1
  58. package/dist/procedures/index.js.map +1 -1
  59. package/dist/procedures/invoke.d.ts +5 -2
  60. package/dist/procedures/invoke.d.ts.map +1 -1
  61. package/dist/procedures/invoke.js +5 -1
  62. package/dist/procedures/invoke.js.map +1 -1
  63. package/dist/procedures/types.d.ts +7 -0
  64. package/dist/procedures/types.d.ts.map +1 -1
  65. package/dist/procedures/types.js.map +1 -1
  66. package/dist/server/index.d.ts +70 -5
  67. package/dist/server/index.d.ts.map +1 -1
  68. package/dist/server/index.js +26 -3
  69. package/dist/server/index.js.map +1 -1
  70. package/package.json +12 -1
  71. package/src/adapters/http/server/body.ts +43 -0
  72. package/src/adapters/http/server/cors.ts +42 -1
  73. package/src/adapters/http/server/handler.ts +7 -32
  74. package/src/browser.ts +9 -1
  75. package/src/events/publisher.ts +8 -5
  76. package/src/events/rows.ts +19 -6
  77. package/src/index.ts +9 -6
  78. package/src/node.ts +13 -1
  79. package/src/procedures/collection/admission.ts +243 -0
  80. package/src/procedures/collection/changes.ts +11 -5
  81. package/src/procedures/collection/index.ts +4 -0
  82. package/src/procedures/collection/patch-in-parts.ts +56 -0
  83. package/src/procedures/collection/procedures.ts +58 -80
  84. package/src/procedures/index.ts +7 -0
  85. package/src/procedures/invoke.ts +7 -2
  86. package/src/procedures/types.ts +8 -0
  87. package/src/server/index.ts +94 -17
@@ -56,10 +56,12 @@ import {
56
56
  type SetBatchInput,
57
57
  type DeleteBatchInput,
58
58
  } from "./schemas.js";
59
- import { DEFAULT_FIND_LIMIT, applyPatch, queryEntries, type StoragePatch, type StorageQueryOptions, type StorageQueryPolicy } from "./query.js";
59
+ import { DEFAULT_FIND_LIMIT, queryEntries, type StorageQueryOptions, type StorageQueryPolicy } from "./query.js";
60
60
  import type { CollectionStorage, StorageWriteOptions } from "./storage.js";
61
61
  import { collectionChangeTopic, startChangeChain, writeWithChangeEvent } from "./changes.js";
62
62
  import { partitionOfCall, storageOfCall, withPartition, type PartitionInput } from "./partition.js";
63
+ import { admitsRow, admittedStorage, type CollectionAdmission } from "./admission.js";
64
+ import { patchItem } from "./patch-in-parts.js";
63
65
 
64
66
  // =============================================================================
65
67
  // Handler Factory
@@ -122,6 +124,13 @@ export interface CollectionProceduresOptions {
122
124
  * @default true, with the topic `collections.<name>` and no values
123
125
  */
124
126
  changeEvents?: boolean | CollectionChangeEventsOptions | undefined;
127
+ /**
128
+ * The rules of the rows (roadmap CLI-41). `admit` examines each row of each read and of each
129
+ * change event with values. `guard` examines each write before the storage changes, and a
130
+ * refused write gets `FORBIDDEN` (reason `write-not-allowed`). Without it, the procedures check
131
+ * only the partition.
132
+ */
133
+ admission?: CollectionAdmission | undefined;
125
134
  }
126
135
 
127
136
  /** The write options of an input with an origin. */
@@ -129,40 +138,6 @@ function writeOptionsOf(input: { origin?: string }): StorageWriteOptions | undef
129
138
  return input.origin === undefined ? undefined : { origin: input.origin };
130
139
  }
131
140
 
132
- /**
133
- * The patches of a storage without `patch()` that run, for each id. A patch of an id waits for the
134
- * patch before it, so two patches of one process keep each field. A write of another writer
135
- * between the read and the write of a patch is lost: give the storage a `patch()` for one step.
136
- */
137
- const PATCHES = new WeakMap<object, Map<string, Promise<unknown>>>();
138
-
139
- /** This function patches an item of a storage without `patch()`: a read, a merge and a write. */
140
- function patchInParts(storage: CollectionStorage<unknown>, id: string, fields: StoragePatch, options?: StorageWriteOptions): Promise<unknown> {
141
- let running = PATCHES.get(storage);
142
- if (!running) {
143
- running = new Map();
144
- PATCHES.set(storage, running);
145
- }
146
- const queue = running;
147
- const before = queue.get(id) ?? Promise.resolve();
148
- const patch = before.then(async () => {
149
- const current = await storage.get(id);
150
- if (current === undefined) return undefined;
151
- const next = applyPatch(current, fields);
152
- await storage.set(id, next, options);
153
- return next;
154
- });
155
- const settled = patch.then(
156
- () => undefined,
157
- () => undefined,
158
- );
159
- queue.set(id, settled);
160
- void settled.then(() => {
161
- if (queue.get(id) === settled) queue.delete(id);
162
- });
163
- return patch;
164
- }
165
-
166
141
  // =============================================================================
167
142
  // Collection Procedure Definitions
168
143
  // =============================================================================
@@ -192,17 +167,20 @@ export function createCollectionProcedures(collectionName: string, options: Coll
192
167
  events === false ? undefined : (events !== true && events.topic) || ((name: string, _ctx: ProcedureContext, partition: string | undefined) => collectionChangeTopic(name, partition));
193
168
  const topicOf = (ctx: ProcedureContext, partition: string | undefined): string | undefined => topicRule?.(collectionName, ctx, partition);
194
169
  const withValues = events !== false && events !== true && events.values === true;
195
- /** A write, and then its change event on the bus of the context. */
170
+ const admission = options.admission;
171
+ /** A write, and then its change event on the bus of the context. The chain of the events belongs to the storage, not to its view. */
196
172
  const changing = <R>(ctx: ProcedureContext, partition: string | undefined, storage: CollectionStorage<unknown>, ids: readonly string[] | "*", origin: string | undefined, write: () => Promise<R>): Promise<R> =>
197
- writeWithChangeEvent(ctx.bus, storage, topicRule ? topicOf(ctx, partition) : undefined, ids, origin, write, withValues);
173
+ writeWithChangeEvent(ctx.bus, storage, topicRule ? topicOf(ctx, partition) : undefined, ids, origin, write, withValues, admission?.admit);
198
174
  /**
199
175
  * The partition and the storage of a call (roadmap CLI-17). The repository of the call has the
200
- * partitions of the collection and their resolver: the tenant comes from the principal.
176
+ * partitions of the collection and their resolver: the tenant comes from the principal. Each read
177
+ * and each write goes through the view, with the rules of the rows (roadmap CLI-41).
201
178
  */
202
- const storageOf = async (input: PartitionInput, ctx: ProcedureContext): Promise<{ storage: CollectionStorage<unknown>; partition: string | undefined }> => {
179
+ const storageOf = async (input: PartitionInput, ctx: ProcedureContext): Promise<{ storage: CollectionStorage<unknown>; view: CollectionStorage<unknown>; partition: string | undefined }> => {
203
180
  const collection = getCollectionFromPath(ctx.path);
204
181
  const partition = await partitionOfCall(ctx, collection, input.partition);
205
- return { storage: storageOfCall<unknown>(ctx, collection, partition), partition };
182
+ const storage = storageOfCall<unknown>(ctx, collection, partition);
183
+ return { storage, view: admittedStorage(storage, admission, ctx), partition };
206
184
  };
207
185
 
208
186
  return [
@@ -217,8 +195,8 @@ export function createCollectionProcedures(collectionName: string, options: Coll
217
195
  tags: ["collections", collectionName, "read"],
218
196
  },
219
197
  handler: async (input: GetInput & PartitionInput, ctx: ProcedureContext) => {
220
- const { storage } = await storageOf(input, ctx);
221
- const value = await storage.get(input.id);
198
+ const { view } = await storageOf(input, ctx);
199
+ const value = await view.get(input.id);
222
200
  if (input.found !== true) return value;
223
201
  // The output with `found` keeps a stored null apart from a missing id, in one call
224
202
  const output: GetFoundOutput = value === undefined ? { found: false } : { found: true, value };
@@ -237,8 +215,8 @@ export function createCollectionProcedures(collectionName: string, options: Coll
237
215
  tags: ["collections", collectionName, "write"],
238
216
  },
239
217
  handler: async (input: SetInput & PartitionInput, ctx: ProcedureContext) => {
240
- const { storage, partition } = await storageOf(input, ctx);
241
- await changing(ctx, partition, storage, [input.id], input.origin, () => storage.set(input.id, input.value, writeOptionsOf(input)));
218
+ const { storage, view, partition } = await storageOf(input, ctx);
219
+ await changing(ctx, partition, storage, [input.id], input.origin, () => view.set(input.id, input.value, writeOptionsOf(input)));
242
220
  },
243
221
  }),
244
222
 
@@ -253,8 +231,8 @@ export function createCollectionProcedures(collectionName: string, options: Coll
253
231
  tags: ["collections", collectionName, "write"],
254
232
  },
255
233
  handler: async (input: DeleteInput & PartitionInput, ctx: ProcedureContext) => {
256
- const { storage, partition } = await storageOf(input, ctx);
257
- return changing(ctx, partition, storage, [input.id], input.origin, () => storage.delete(input.id, writeOptionsOf(input)));
234
+ const { storage, view, partition } = await storageOf(input, ctx);
235
+ return changing(ctx, partition, storage, [input.id], input.origin, () => view.delete(input.id, writeOptionsOf(input)));
258
236
  },
259
237
  }),
260
238
 
@@ -269,8 +247,8 @@ export function createCollectionProcedures(collectionName: string, options: Coll
269
247
  tags: ["collections", collectionName, "read"],
270
248
  },
271
249
  handler: async (input: HasInput & PartitionInput, ctx: ProcedureContext) => {
272
- const { storage } = await storageOf(input, ctx);
273
- return storage.has(input.id);
250
+ const { view } = await storageOf(input, ctx);
251
+ return view.has(input.id);
274
252
  },
275
253
  }),
276
254
 
@@ -285,8 +263,8 @@ export function createCollectionProcedures(collectionName: string, options: Coll
285
263
  tags: ["collections", collectionName, "read"],
286
264
  },
287
265
  handler: async (input: GetAllInput & PartitionInput, ctx: ProcedureContext) => {
288
- const { storage } = await storageOf(input, ctx);
289
- return storage.getAll();
266
+ const { view } = await storageOf(input, ctx);
267
+ return view.getAll();
290
268
  },
291
269
  }),
292
270
 
@@ -301,11 +279,11 @@ export function createCollectionProcedures(collectionName: string, options: Coll
301
279
  tags: ["collections", collectionName, "read"],
302
280
  },
303
281
  handler: async (input: EntriesInput & PartitionInput, ctx: ProcedureContext) => {
304
- const { storage } = await storageOf(input, ctx);
305
- if (!storage.getEntries) {
282
+ const { view } = await storageOf(input, ctx);
283
+ if (!view.getEntries) {
306
284
  throw new Error(`The storage of ${collectionName} cannot give the ids of its items`);
307
285
  }
308
- return (await storage.getEntries()).map(([id, value]) => ({ id, value }));
286
+ return (await view.getEntries()).map(([id, value]) => ({ id, value }));
309
287
  },
310
288
  }),
311
289
 
@@ -320,10 +298,10 @@ export function createCollectionProcedures(collectionName: string, options: Coll
320
298
  tags: ["collections", collectionName, "read"],
321
299
  },
322
300
  handler: async (input: IdsInput & PartitionInput, ctx: ProcedureContext) => {
323
- const { storage } = await storageOf(input, ctx);
324
- if (storage.getIds) return storage.getIds();
301
+ const { view } = await storageOf(input, ctx);
302
+ if (view.getIds) return view.getIds();
325
303
  // A storage without getIds() gives its entries: the values stay on the server
326
- if (storage.getEntries) return (await storage.getEntries()).map(([id]) => id);
304
+ if (view.getEntries) return (await view.getEntries()).map(([id]) => id);
327
305
  throw new Error(`The storage of ${collectionName} cannot give the ids of its items`);
328
306
  },
329
307
  }),
@@ -339,9 +317,9 @@ export function createCollectionProcedures(collectionName: string, options: Coll
339
317
  tags: ["collections", collectionName, "read"],
340
318
  },
341
319
  handler: async (input: ChangesInput & PartitionInput, ctx: ProcedureContext): Promise<ChangesOutput> => {
342
- const { storage, partition } = await storageOf(input, ctx);
343
- if (storage.getChangesSince) {
344
- const changes = await storage.getChangesSince(input.since);
320
+ const { storage, view, partition } = await storageOf(input, ctx);
321
+ if (view.getChangesSince) {
322
+ const changes = await view.getChangesSince(input.since);
345
323
  // The chain of the change events starts at this version: the caller then applies the
346
324
  // next event without a read (roadmap CLI-15)
347
325
  if (topicRule) startChangeChain(ctx.bus, storage, topicOf(ctx, partition), changes.version);
@@ -354,8 +332,8 @@ export function createCollectionProcedures(collectionName: string, options: Coll
354
332
  }
355
333
  // A storage without a change log gives all its items. The empty version asks for all
356
334
  // items again at the next call.
357
- if (storage.getEntries) {
358
- const entries = await storage.getEntries();
335
+ if (view.getEntries) {
336
+ const entries = await view.getEntries();
359
337
  return { version: "", full: true, changed: entries.map(([id, value]) => ({ id, value })), deleted: [] };
360
338
  }
361
339
  throw new Error(`The storage of ${collectionName} cannot give its changes or the ids of its items`);
@@ -373,16 +351,16 @@ export function createCollectionProcedures(collectionName: string, options: Coll
373
351
  tags: ["collections", collectionName, "read", "query"],
374
352
  },
375
353
  handler: async (input: FindInput & PartitionInput, ctx: ProcedureContext) => {
376
- const { storage } = await storageOf(input, ctx);
354
+ const { view } = await storageOf(input, ctx);
377
355
  const selector = input.selector ?? {};
378
356
  const query = queryOptionsOf(input, queryPolicy);
379
357
  // The storage runs the selector itself when it can: a database does not read each item. It
380
358
  // gets the policy of the host, so it permits what the procedure permits.
381
- if (storage.query) return (await storage.query(selector, query, queryPolicy)).map(([id, value]) => ({ id, value }));
382
- if (!storage.getEntries) {
359
+ if (view.query) return (await view.query(selector, query, queryPolicy)).map(([id, value]) => ({ id, value }));
360
+ if (!view.getEntries) {
383
361
  throw new Error(`The storage of ${collectionName} cannot run a selector or give the ids of its items`);
384
362
  }
385
- return queryEntries(await storage.getEntries(), selector, query, queryPolicy).map(([id, value]) => ({ id, value }));
363
+ return queryEntries(await view.getEntries(), selector, query, queryPolicy).map(([id, value]) => ({ id, value }));
386
364
  },
387
365
  }),
388
366
 
@@ -397,12 +375,12 @@ export function createCollectionProcedures(collectionName: string, options: Coll
397
375
  tags: ["collections", collectionName, "write"],
398
376
  },
399
377
  handler: async (input: PatchInput & PartitionInput, ctx: ProcedureContext) => {
400
- const { storage, partition } = await storageOf(input, ctx);
378
+ const { storage, view, partition } = await storageOf(input, ctx);
401
379
  const write = writeOptionsOf(input);
402
- const value = await changing(ctx, partition, storage, [input.id], input.origin, () =>
403
- storage.patch ? storage.patch(input.id, input.fields, write) : patchInParts(storage, input.id, input.fields, write),
404
- );
405
- const output: GetFoundOutput = value === undefined ? { found: false } : { found: true, value };
380
+ const value = await changing(ctx, partition, storage, [input.id], input.origin, () => patchItem(view, input.id, input.fields, write));
381
+ if (value === undefined) return { found: false } satisfies GetFoundOutput;
382
+ // The new item is a row too: a caller gets it only when the rule of the rows admits it (roadmap CLI-41)
383
+ const output: GetFoundOutput = (await admitsRow(admission, ctx, input.id, value)) ? { found: true, value } : { found: true };
406
384
  return output;
407
385
  },
408
386
  }),
@@ -418,8 +396,8 @@ export function createCollectionProcedures(collectionName: string, options: Coll
418
396
  tags: ["collections", collectionName, "read"],
419
397
  },
420
398
  handler: async (input: SizeInput & PartitionInput, ctx: ProcedureContext) => {
421
- const { storage } = await storageOf(input, ctx);
422
- return storage.size();
399
+ const { view } = await storageOf(input, ctx);
400
+ return view.size();
423
401
  },
424
402
  }),
425
403
 
@@ -434,8 +412,8 @@ export function createCollectionProcedures(collectionName: string, options: Coll
434
412
  tags: ["collections", collectionName, "write"],
435
413
  },
436
414
  handler: async (input: ClearInput & PartitionInput, ctx: ProcedureContext) => {
437
- const { storage, partition } = await storageOf(input, ctx);
438
- await changing(ctx, partition, storage, "*", input.origin, () => storage.clear(writeOptionsOf(input)));
415
+ const { storage, view, partition } = await storageOf(input, ctx);
416
+ await changing(ctx, partition, storage, "*", input.origin, () => view.clear(writeOptionsOf(input)));
439
417
  },
440
418
  }),
441
419
 
@@ -450,8 +428,8 @@ export function createCollectionProcedures(collectionName: string, options: Coll
450
428
  tags: ["collections", collectionName, "read", "batch"],
451
429
  },
452
430
  handler: async (input: GetBatchInput & PartitionInput, ctx: ProcedureContext) => {
453
- const { storage } = await storageOf(input, ctx);
454
- const result = await storage.getBatch(input.ids);
431
+ const { view } = await storageOf(input, ctx);
432
+ const result = await view.getBatch(input.ids);
455
433
  // Convert Map to plain object
456
434
  return Object.fromEntries(result);
457
435
  },
@@ -468,12 +446,12 @@ export function createCollectionProcedures(collectionName: string, options: Coll
468
446
  tags: ["collections", collectionName, "write", "batch"],
469
447
  },
470
448
  handler: async (input: SetBatchInput & PartitionInput, ctx: ProcedureContext) => {
471
- const { storage, partition } = await storageOf(input, ctx);
449
+ const { storage, view, partition } = await storageOf(input, ctx);
472
450
  const items: Array<[string, unknown]> = input.items.map((item) => [
473
451
  item.id,
474
452
  item.value,
475
453
  ]);
476
- await changing(ctx, partition, storage, items.map(([id]) => id), input.origin, () => storage.setBatch(items, writeOptionsOf(input)));
454
+ await changing(ctx, partition, storage, items.map(([id]) => id), input.origin, () => view.setBatch(items, writeOptionsOf(input)));
477
455
  },
478
456
  }),
479
457
 
@@ -488,8 +466,8 @@ export function createCollectionProcedures(collectionName: string, options: Coll
488
466
  tags: ["collections", collectionName, "write", "batch"],
489
467
  },
490
468
  handler: async (input: DeleteBatchInput & PartitionInput, ctx: ProcedureContext) => {
491
- const { storage, partition } = await storageOf(input, ctx);
492
- return changing(ctx, partition, storage, input.ids, input.origin, () => storage.deleteBatch(input.ids, writeOptionsOf(input)));
469
+ const { storage, view, partition } = await storageOf(input, ctx);
470
+ return changing(ctx, partition, storage, input.ids, input.origin, () => view.deleteBatch(input.ids, writeOptionsOf(input)));
493
471
  },
494
472
  }),
495
473
  ];
@@ -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,
@@ -152,6 +155,10 @@ export type {
152
155
  CollectionProceduresOptions,
153
156
  CollectionChangeEventsOptions,
154
157
  CollectionChangeEvent,
158
+ CollectionAdmission,
159
+ CollectionRow,
160
+ CollectionWrite,
161
+ CollectionWriteGuard,
155
162
  CollectionPartitions,
156
163
  PartitionInput,
157
164
  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
  /**
@@ -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>;