@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.
- package/CHANGELOG.md +36 -0
- package/README.md +12 -3
- package/dist/.tsbuildinfo +1 -1
- package/dist/adapters/http/server/body.d.ts +17 -0
- package/dist/adapters/http/server/body.d.ts.map +1 -0
- package/dist/adapters/http/server/body.js +44 -0
- package/dist/adapters/http/server/body.js.map +1 -0
- package/dist/adapters/http/server/cors.d.ts +24 -1
- package/dist/adapters/http/server/cors.d.ts.map +1 -1
- package/dist/adapters/http/server/cors.js +33 -1
- package/dist/adapters/http/server/cors.js.map +1 -1
- package/dist/adapters/http/server/handler.d.ts +3 -1
- package/dist/adapters/http/server/handler.d.ts.map +1 -1
- package/dist/adapters/http/server/handler.js +4 -34
- package/dist/adapters/http/server/handler.js.map +1 -1
- package/dist/browser.d.ts +4 -3
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +4 -1
- package/dist/browser.js.map +1 -1
- package/dist/events/publisher.d.ts.map +1 -1
- package/dist/events/publisher.js +10 -5
- package/dist/events/publisher.js.map +1 -1
- package/dist/events/rows.d.ts +12 -4
- package/dist/events/rows.d.ts.map +1 -1
- package/dist/events/rows.js +13 -16
- package/dist/events/rows.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +10 -1
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +11 -0
- package/dist/node.js.map +1 -1
- package/dist/procedures/collection/admission.d.ts +77 -0
- package/dist/procedures/collection/admission.d.ts.map +1 -0
- package/dist/procedures/collection/admission.js +204 -0
- package/dist/procedures/collection/admission.js.map +1 -0
- package/dist/procedures/collection/changes.d.ts +3 -1
- package/dist/procedures/collection/changes.d.ts.map +1 -1
- package/dist/procedures/collection/changes.js +10 -6
- package/dist/procedures/collection/changes.js.map +1 -1
- package/dist/procedures/collection/index.d.ts +2 -0
- package/dist/procedures/collection/index.d.ts.map +1 -1
- package/dist/procedures/collection/index.js +2 -0
- package/dist/procedures/collection/index.js.map +1 -1
- package/dist/procedures/collection/patch-in-parts.d.ts +15 -0
- package/dist/procedures/collection/patch-in-parts.d.ts.map +1 -0
- package/dist/procedures/collection/patch-in-parts.js +47 -0
- package/dist/procedures/collection/patch-in-parts.js.map +1 -0
- package/dist/procedures/collection/procedures.d.ts +8 -0
- package/dist/procedures/collection/procedures.d.ts.map +1 -1
- package/dist/procedures/collection/procedures.js +54 -77
- package/dist/procedures/collection/procedures.js.map +1 -1
- package/dist/procedures/index.d.ts +2 -2
- package/dist/procedures/index.d.ts.map +1 -1
- package/dist/procedures/index.js +3 -1
- package/dist/procedures/index.js.map +1 -1
- package/dist/procedures/invoke.d.ts +5 -2
- package/dist/procedures/invoke.d.ts.map +1 -1
- package/dist/procedures/invoke.js +5 -1
- package/dist/procedures/invoke.js.map +1 -1
- package/dist/procedures/types.d.ts +7 -0
- package/dist/procedures/types.d.ts.map +1 -1
- package/dist/procedures/types.js.map +1 -1
- package/dist/server/index.d.ts +70 -5
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +26 -3
- package/dist/server/index.js.map +1 -1
- package/package.json +12 -1
- package/src/adapters/http/server/body.ts +43 -0
- package/src/adapters/http/server/cors.ts +42 -1
- package/src/adapters/http/server/handler.ts +7 -32
- package/src/browser.ts +9 -1
- package/src/events/publisher.ts +8 -5
- package/src/events/rows.ts +19 -6
- package/src/index.ts +9 -6
- package/src/node.ts +13 -1
- package/src/procedures/collection/admission.ts +243 -0
- package/src/procedures/collection/changes.ts +11 -5
- package/src/procedures/collection/index.ts +4 -0
- package/src/procedures/collection/patch-in-parts.ts +56 -0
- package/src/procedures/collection/procedures.ts +58 -80
- package/src/procedures/index.ts +7 -0
- package/src/procedures/invoke.ts +7 -2
- package/src/procedures/types.ts +8 -0
- 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,
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
221
|
-
const value = await
|
|
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, () =>
|
|
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, () =>
|
|
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 {
|
|
273
|
-
return
|
|
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 {
|
|
289
|
-
return
|
|
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 {
|
|
305
|
-
if (!
|
|
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
|
|
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 {
|
|
324
|
-
if (
|
|
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 (
|
|
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 (
|
|
344
|
-
const changes = await
|
|
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 (
|
|
358
|
-
const entries = await
|
|
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 {
|
|
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 (
|
|
382
|
-
if (!
|
|
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
|
|
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
|
-
|
|
404
|
-
)
|
|
405
|
-
const output: GetFoundOutput =
|
|
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 {
|
|
422
|
-
return
|
|
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, () =>
|
|
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 {
|
|
454
|
-
const result = await
|
|
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, () =>
|
|
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, () =>
|
|
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
|
];
|
package/src/procedures/index.ts
CHANGED
|
@@ -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,
|
package/src/procedures/invoke.ts
CHANGED
|
@@ -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
|
|
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);
|
package/src/procedures/types.ts
CHANGED
|
@@ -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/server/index.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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>;
|