@procedures/client 0.8.0 → 0.9.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.
- package/CHANGELOG.md +78 -0
- package/README.md +95 -8
- package/dist/.tsbuildinfo +1 -1
- package/dist/adapters/http/client/transport.d.ts +4 -0
- package/dist/adapters/http/client/transport.d.ts.map +1 -1
- package/dist/adapters/http/client/transport.js +11 -1
- package/dist/adapters/http/client/transport.js.map +1 -1
- package/dist/adapters/http/client/types.d.ts +19 -0
- package/dist/adapters/http/client/types.d.ts.map +1 -1
- package/dist/adapters/http/server/cors.d.ts +64 -0
- package/dist/adapters/http/server/cors.d.ts.map +1 -0
- package/dist/adapters/http/server/cors.js +113 -0
- package/dist/adapters/http/server/cors.js.map +1 -0
- package/dist/adapters/http/server/handler.d.ts +136 -0
- package/dist/adapters/http/server/handler.d.ts.map +1 -0
- package/dist/adapters/http/server/handler.js +495 -0
- package/dist/adapters/http/server/handler.js.map +1 -0
- package/dist/adapters/http/server/strategies.d.ts +14 -5
- package/dist/adapters/http/server/strategies.d.ts.map +1 -1
- package/dist/adapters/http/server/strategies.js.map +1 -1
- package/dist/adapters/http/server/transport.d.ts +30 -46
- package/dist/adapters/http/server/transport.d.ts.map +1 -1
- package/dist/adapters/http/server/transport.js +48 -432
- package/dist/adapters/http/server/transport.js.map +1 -1
- package/dist/adapters/http/server/types.d.ts +13 -0
- package/dist/adapters/http/server/types.d.ts.map +1 -1
- package/dist/adapters/websocket/client/transport.d.ts.map +1 -1
- package/dist/adapters/websocket/client/transport.js +4 -12
- package/dist/adapters/websocket/client/transport.js.map +1 -1
- package/dist/adapters/websocket/server/transport.d.ts +46 -1
- package/dist/adapters/websocket/server/transport.d.ts.map +1 -1
- package/dist/adapters/websocket/server/transport.js +152 -15
- package/dist/adapters/websocket/server/transport.js.map +1 -1
- package/dist/adapters/websocket/server/types.d.ts +10 -3
- package/dist/adapters/websocket/server/types.d.ts.map +1 -1
- package/dist/adapters/websocket/server/upgrade-router.d.ts +28 -0
- package/dist/adapters/websocket/server/upgrade-router.d.ts.map +1 -0
- package/dist/adapters/websocket/server/upgrade-router.js +62 -0
- package/dist/adapters/websocket/server/upgrade-router.js.map +1 -0
- package/dist/adapters/websocket/server/upgrade.d.ts +68 -0
- package/dist/adapters/websocket/server/upgrade.d.ts.map +1 -0
- package/dist/adapters/websocket/server/upgrade.js +49 -0
- package/dist/adapters/websocket/server/upgrade.js.map +1 -0
- package/dist/adapters/websocket/shared/rpc.d.ts +2 -0
- package/dist/adapters/websocket/shared/rpc.d.ts.map +1 -1
- package/dist/adapters/websocket/shared/rpc.js +8 -2
- package/dist/adapters/websocket/shared/rpc.js.map +1 -1
- package/dist/browser.d.ts +12 -4
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +13 -2
- package/dist/browser.js.map +1 -1
- package/dist/client/client.d.ts +43 -0
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +69 -1
- package/dist/client/client.js.map +1 -1
- package/dist/events/publisher.d.ts +25 -1
- package/dist/events/publisher.d.ts.map +1 -1
- package/dist/events/publisher.js +66 -9
- package/dist/events/publisher.js.map +1 -1
- package/dist/events/rows.d.ts +31 -0
- package/dist/events/rows.d.ts.map +1 -0
- package/dist/events/rows.js +39 -0
- package/dist/events/rows.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +14 -0
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +9 -0
- package/dist/node.js.map +1 -1
- package/dist/procedures/capability.d.ts +6 -0
- package/dist/procedures/capability.d.ts.map +1 -1
- package/dist/procedures/capability.js +25 -0
- package/dist/procedures/capability.js.map +1 -1
- package/dist/procedures/collection/changes.d.ts +94 -0
- package/dist/procedures/collection/changes.d.ts.map +1 -0
- package/dist/procedures/collection/changes.js +182 -0
- package/dist/procedures/collection/changes.js.map +1 -0
- package/dist/procedures/collection/index.d.ts +10 -3
- package/dist/procedures/collection/index.d.ts.map +1 -1
- package/dist/procedures/collection/index.js +8 -1
- package/dist/procedures/collection/index.js.map +1 -1
- package/dist/procedures/collection/partition.d.ts +67 -0
- package/dist/procedures/collection/partition.d.ts.map +1 -0
- package/dist/procedures/collection/partition.js +82 -0
- package/dist/procedures/collection/partition.js.map +1 -0
- package/dist/procedures/collection/procedures.d.ts +39 -2
- package/dist/procedures/collection/procedures.d.ts.map +1 -1
- package/dist/procedures/collection/procedures.js +147 -44
- package/dist/procedures/collection/procedures.js.map +1 -1
- package/dist/procedures/collection/query.d.ts +246 -0
- package/dist/procedures/collection/query.d.ts.map +1 -0
- package/dist/procedures/collection/query.js +854 -0
- package/dist/procedures/collection/query.js.map +1 -0
- package/dist/procedures/collection/schemas.d.ts +50 -0
- package/dist/procedures/collection/schemas.d.ts.map +1 -1
- package/dist/procedures/collection/schemas.js +144 -6
- package/dist/procedures/collection/schemas.js.map +1 -1
- package/dist/procedures/collection/storage.d.ts +58 -6
- package/dist/procedures/collection/storage.d.ts.map +1 -1
- package/dist/procedures/collection/storage.js +23 -0
- package/dist/procedures/collection/storage.js.map +1 -1
- package/dist/procedures/elicit.d.ts +190 -0
- package/dist/procedures/elicit.d.ts.map +1 -0
- package/dist/procedures/elicit.js +127 -0
- package/dist/procedures/elicit.js.map +1 -0
- package/dist/procedures/hints.d.ts +43 -0
- package/dist/procedures/hints.d.ts.map +1 -0
- package/dist/procedures/hints.js +74 -0
- package/dist/procedures/hints.js.map +1 -0
- package/dist/procedures/index.d.ts +9 -2
- package/dist/procedures/index.d.ts.map +1 -1
- package/dist/procedures/index.js +5 -1
- package/dist/procedures/index.js.map +1 -1
- package/dist/procedures/invoke.d.ts +24 -2
- package/dist/procedures/invoke.d.ts.map +1 -1
- package/dist/procedures/invoke.js +14 -1
- package/dist/procedures/invoke.js.map +1 -1
- package/dist/procedures/progress.d.ts +24 -0
- package/dist/procedures/progress.d.ts.map +1 -0
- package/dist/procedures/progress.js +11 -0
- package/dist/procedures/progress.js.map +1 -0
- package/dist/procedures/registry.d.ts.map +1 -1
- package/dist/procedures/registry.js +5 -0
- package/dist/procedures/registry.js.map +1 -1
- package/dist/procedures/types.d.ts +55 -0
- package/dist/procedures/types.d.ts.map +1 -1
- package/dist/procedures/types.js.map +1 -1
- package/dist/procedures/untrusted.d.ts +28 -0
- package/dist/procedures/untrusted.d.ts.map +1 -0
- package/dist/procedures/untrusted.js +32 -0
- package/dist/procedures/untrusted.js.map +1 -0
- package/dist/server/browser-guard.d.ts +12 -2
- package/dist/server/browser-guard.d.ts.map +1 -1
- package/dist/server/browser-guard.js +43 -6
- package/dist/server/browser-guard.js.map +1 -1
- package/dist/server/host-policy.d.ts +10 -1
- package/dist/server/host-policy.d.ts.map +1 -1
- package/dist/server/host-policy.js +5 -0
- package/dist/server/host-policy.js.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/procedure-server.d.ts +68 -7
- package/dist/server/procedure-server.d.ts.map +1 -1
- package/dist/server/procedure-server.js +124 -24
- package/dist/server/procedure-server.js.map +1 -1
- package/dist/server/request-auth.d.ts +51 -4
- package/dist/server/request-auth.d.ts.map +1 -1
- package/dist/server/request-auth.js +116 -5
- package/dist/server/request-auth.js.map +1 -1
- package/dist/server/types.d.ts +73 -5
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/types.js.map +1 -1
- package/package.json +7 -3
- package/src/adapters/http/client/transport.ts +11 -1
- package/src/adapters/http/client/types.ts +22 -0
- package/src/adapters/http/server/cors.ts +144 -0
- package/src/adapters/http/server/handler.ts +624 -0
- package/src/adapters/http/server/strategies.ts +16 -6
- package/src/adapters/http/server/transport.ts +55 -478
- package/src/adapters/http/server/types.ts +15 -0
- package/src/adapters/websocket/client/transport.ts +4 -12
- package/src/adapters/websocket/server/transport.ts +166 -16
- package/src/adapters/websocket/server/types.ts +11 -3
- package/src/adapters/websocket/server/upgrade-router.ts +77 -0
- package/src/adapters/websocket/server/upgrade.ts +88 -0
- package/src/adapters/websocket/shared/rpc.ts +9 -2
- package/src/browser.ts +85 -2
- package/src/client/client.ts +83 -1
- package/src/events/publisher.ts +75 -11
- package/src/events/rows.ts +48 -0
- package/src/index.ts +1 -1
- package/src/node.ts +21 -0
- package/src/procedures/capability.ts +21 -0
- package/src/procedures/collection/changes.ts +245 -0
- package/src/procedures/collection/index.ts +57 -1
- package/src/procedures/collection/partition.ts +123 -0
- package/src/procedures/collection/procedures.ts +206 -51
- package/src/procedures/collection/query.ts +1009 -0
- package/src/procedures/collection/schemas.ts +196 -7
- package/src/procedures/collection/storage.ts +78 -6
- package/src/procedures/elicit.ts +320 -0
- package/src/procedures/hints.ts +91 -0
- package/src/procedures/index.ts +83 -2
- package/src/procedures/invoke.ts +33 -2
- package/src/procedures/progress.ts +25 -0
- package/src/procedures/registry.ts +4 -0
- package/src/procedures/types.ts +65 -0
- package/src/procedures/untrusted.ts +50 -0
- package/src/server/browser-guard.ts +48 -6
- package/src/server/host-policy.ts +13 -1
- package/src/server/index.ts +3 -0
- package/src/server/procedure-server.ts +147 -26
- package/src/server/request-auth.ts +128 -6
- package/src/server/types.ts +82 -5
|
@@ -34,6 +34,12 @@ import {
|
|
|
34
34
|
setBatchOutputSchema,
|
|
35
35
|
deleteBatchInputSchema,
|
|
36
36
|
deleteBatchOutputSchema,
|
|
37
|
+
findInputSchemaOf,
|
|
38
|
+
findOutputSchema,
|
|
39
|
+
patchInputSchema,
|
|
40
|
+
patchOutputSchema,
|
|
41
|
+
type FindInput,
|
|
42
|
+
type PatchInput,
|
|
37
43
|
type GetInput,
|
|
38
44
|
type GetFoundOutput,
|
|
39
45
|
type SetInput,
|
|
@@ -50,6 +56,10 @@ import {
|
|
|
50
56
|
type SetBatchInput,
|
|
51
57
|
type DeleteBatchInput,
|
|
52
58
|
} from "./schemas.js";
|
|
59
|
+
import { DEFAULT_FIND_LIMIT, applyPatch, queryEntries, type StoragePatch, type StorageQueryOptions, type StorageQueryPolicy } from "./query.js";
|
|
60
|
+
import type { CollectionStorage, StorageWriteOptions } from "./storage.js";
|
|
61
|
+
import { collectionChangeTopic, startChangeChain, writeWithChangeEvent } from "./changes.js";
|
|
62
|
+
import { partitionOfCall, storageOfCall, withPartition, type PartitionInput } from "./partition.js";
|
|
53
63
|
|
|
54
64
|
// =============================================================================
|
|
55
65
|
// Handler Factory
|
|
@@ -66,15 +76,91 @@ function getCollectionFromPath(path: string[]): string {
|
|
|
66
76
|
return path[1]!;
|
|
67
77
|
}
|
|
68
78
|
|
|
79
|
+
/** The options of a query from the input of `find`. */
|
|
80
|
+
function queryOptionsOf(input: FindInput, policy: StorageQueryPolicy): StorageQueryOptions {
|
|
81
|
+
// A find without a limit gets the maxLimit of the policy (roadmap CLI-14)
|
|
82
|
+
const limit = input.limit ?? (Number.isFinite(policy.maxLimit) ? policy.maxLimit : undefined);
|
|
83
|
+
return {
|
|
84
|
+
...(input.sort !== undefined && { sort: input.sort }),
|
|
85
|
+
...(input.skip !== undefined && { skip: input.skip }),
|
|
86
|
+
...(limit !== undefined && { limit }),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** The options of the change events of the collection procedures (roadmap CLI-15). */
|
|
91
|
+
export interface CollectionChangeEventsOptions {
|
|
92
|
+
/**
|
|
93
|
+
* The topic of the change events of a call, or undefined for no event. It gets the partition of
|
|
94
|
+
* the call (roadmap CLI-17).
|
|
95
|
+
* @default `collectionChangeTopic(name, partition)`: `collections.<name>`, or `collections.<name>.<partition>`
|
|
96
|
+
*/
|
|
97
|
+
topic?: ((collection: string, ctx: ProcedureContext, partition: string | undefined) => string | undefined) | undefined;
|
|
98
|
+
/**
|
|
99
|
+
* True: the events also carry the values of the changed items. Each row `{ id, value }` passes
|
|
100
|
+
* the output filter of the host (`admitItem`) for each subscriber, and a refused row stays out of
|
|
101
|
+
* that event: its id goes to `withheld`. Without it, the events carry only the ids and the
|
|
102
|
+
* versions, and a subscriber reads the values with `changes`.
|
|
103
|
+
* @default false
|
|
104
|
+
*/
|
|
105
|
+
values?: boolean | undefined;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** The options of the collection procedures. */
|
|
109
|
+
export interface CollectionProceduresOptions {
|
|
110
|
+
/**
|
|
111
|
+
* The query policy of `find`: the operators, and the limits of a selector and of the result. A
|
|
112
|
+
* selector or a limit that the policy does not permit is a `VALIDATION_ERROR`. The policy also
|
|
113
|
+
* goes to `query()` of the storage. `$regex` is off unless the policy has it in `operators`.
|
|
114
|
+
* @default DEFAULT_QUERY_POLICY, with `maxLimit` DEFAULT_FIND_LIMIT (1000)
|
|
115
|
+
*/
|
|
116
|
+
queryPolicy?: StorageQueryPolicy | undefined;
|
|
117
|
+
/**
|
|
118
|
+
* The change events of the writes (roadmap CLI-15). Each write publishes a
|
|
119
|
+
* `CollectionChangeEvent` on the bus of its context, while a subscriber listens to the topic.
|
|
120
|
+
* The event has the ids and the versions, and the values only with `values`. False: the
|
|
121
|
+
* procedures publish no event.
|
|
122
|
+
* @default true, with the topic `collections.<name>` and no values
|
|
123
|
+
*/
|
|
124
|
+
changeEvents?: boolean | CollectionChangeEventsOptions | undefined;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** The write options of an input with an origin. */
|
|
128
|
+
function writeOptionsOf(input: { origin?: string }): StorageWriteOptions | undefined {
|
|
129
|
+
return input.origin === undefined ? undefined : { origin: input.origin };
|
|
130
|
+
}
|
|
131
|
+
|
|
69
132
|
/**
|
|
70
|
-
*
|
|
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.
|
|
71
136
|
*/
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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);
|
|
75
145
|
}
|
|
76
|
-
const
|
|
77
|
-
|
|
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;
|
|
78
164
|
}
|
|
79
165
|
|
|
80
166
|
// =============================================================================
|
|
@@ -85,6 +171,7 @@ function getStorage<T>(ctx: ProcedureContext): ReturnType<NonNullable<ProcedureC
|
|
|
85
171
|
* Create collection procedures for a specific collection name.
|
|
86
172
|
*
|
|
87
173
|
* @param collectionName - Name of the collection
|
|
174
|
+
* @param options - The query policy of `find`
|
|
88
175
|
* @returns Array of procedure definitions
|
|
89
176
|
*
|
|
90
177
|
* @example
|
|
@@ -93,24 +180,44 @@ function getStorage<T>(ctx: ProcedureContext): ReturnType<NonNullable<ProcedureC
|
|
|
93
180
|
* registry.registerAll(userProcedures);
|
|
94
181
|
* ```
|
|
95
182
|
*/
|
|
96
|
-
export function createCollectionProcedures(collectionName: string): AnyProcedure[] {
|
|
183
|
+
export function createCollectionProcedures(collectionName: string, options: CollectionProceduresOptions = {}): AnyProcedure[] {
|
|
97
184
|
const basePath = ["collections", collectionName];
|
|
185
|
+
// A find of a caller gets a limit: DEFAULT_FIND_LIMIT, unless the policy gives its own maxLimit
|
|
186
|
+
const queryPolicy: StorageQueryPolicy = { ...options.queryPolicy, maxLimit: options.queryPolicy?.maxLimit ?? DEFAULT_FIND_LIMIT };
|
|
98
187
|
// Each collection is a resource of its own (CAPABILITIES-2026-10): a capability can permit one
|
|
99
188
|
const resource = `collection:${collectionName}`;
|
|
189
|
+
// The topic of the change events of a call (roadmap CLI-15), with its partition (roadmap CLI-17)
|
|
190
|
+
const events = options.changeEvents ?? true;
|
|
191
|
+
const topicRule: ((collection: string, ctx: ProcedureContext, partition: string | undefined) => string | undefined) | undefined =
|
|
192
|
+
events === false ? undefined : (events !== true && events.topic) || ((name: string, _ctx: ProcedureContext, partition: string | undefined) => collectionChangeTopic(name, partition));
|
|
193
|
+
const topicOf = (ctx: ProcedureContext, partition: string | undefined): string | undefined => topicRule?.(collectionName, ctx, partition);
|
|
194
|
+
const withValues = events !== false && events !== true && events.values === true;
|
|
195
|
+
/** A write, and then its change event on the bus of the context. */
|
|
196
|
+
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);
|
|
198
|
+
/**
|
|
199
|
+
* 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.
|
|
201
|
+
*/
|
|
202
|
+
const storageOf = async (input: PartitionInput, ctx: ProcedureContext): Promise<{ storage: CollectionStorage<unknown>; partition: string | undefined }> => {
|
|
203
|
+
const collection = getCollectionFromPath(ctx.path);
|
|
204
|
+
const partition = await partitionOfCall(ctx, collection, input.partition);
|
|
205
|
+
return { storage: storageOfCall<unknown>(ctx, collection, partition), partition };
|
|
206
|
+
};
|
|
100
207
|
|
|
101
208
|
return [
|
|
102
209
|
// Get
|
|
103
210
|
defineProcedure({
|
|
104
211
|
path: [...basePath, "get"],
|
|
105
|
-
input: getInputSchema,
|
|
212
|
+
input: withPartition(getInputSchema),
|
|
106
213
|
output: getOutputSchema,
|
|
107
214
|
metadata: {
|
|
108
215
|
effects: { reads: [resource], writes: [] },
|
|
109
216
|
description: `Get item from ${collectionName} by ID`,
|
|
110
217
|
tags: ["collections", collectionName, "read"],
|
|
111
218
|
},
|
|
112
|
-
handler: async (input: GetInput, ctx
|
|
113
|
-
const storage =
|
|
219
|
+
handler: async (input: GetInput & PartitionInput, ctx: ProcedureContext) => {
|
|
220
|
+
const { storage } = await storageOf(input, ctx);
|
|
114
221
|
const value = await storage.get(input.id);
|
|
115
222
|
if (input.found !== true) return value;
|
|
116
223
|
// The output with `found` keeps a stored null apart from a missing id, in one call
|
|
@@ -122,47 +229,47 @@ export function createCollectionProcedures(collectionName: string): AnyProcedure
|
|
|
122
229
|
// Set
|
|
123
230
|
defineProcedure({
|
|
124
231
|
path: [...basePath, "set"],
|
|
125
|
-
input: setInputSchema,
|
|
232
|
+
input: withPartition(setInputSchema),
|
|
126
233
|
output: setOutputSchema,
|
|
127
234
|
metadata: {
|
|
128
235
|
effects: { reads: [resource], writes: [resource], idempotent: true },
|
|
129
236
|
description: `Set item in ${collectionName}`,
|
|
130
237
|
tags: ["collections", collectionName, "write"],
|
|
131
238
|
},
|
|
132
|
-
handler: async (input: SetInput, ctx: ProcedureContext) => {
|
|
133
|
-
const storage =
|
|
134
|
-
await storage.set(input.id, input.value);
|
|
239
|
+
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)));
|
|
135
242
|
},
|
|
136
243
|
}),
|
|
137
244
|
|
|
138
245
|
// Delete
|
|
139
246
|
defineProcedure({
|
|
140
247
|
path: [...basePath, "delete"],
|
|
141
|
-
input: deleteInputSchema,
|
|
248
|
+
input: withPartition(deleteInputSchema),
|
|
142
249
|
output: deleteOutputSchema,
|
|
143
250
|
metadata: {
|
|
144
251
|
effects: { reads: [resource], writes: [resource], idempotent: true },
|
|
145
252
|
description: `Delete item from ${collectionName}`,
|
|
146
253
|
tags: ["collections", collectionName, "write"],
|
|
147
254
|
},
|
|
148
|
-
handler: async (input: DeleteInput, ctx: ProcedureContext) => {
|
|
149
|
-
const storage =
|
|
150
|
-
return storage.delete(input.id);
|
|
255
|
+
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)));
|
|
151
258
|
},
|
|
152
259
|
}),
|
|
153
260
|
|
|
154
261
|
// Has
|
|
155
262
|
defineProcedure({
|
|
156
263
|
path: [...basePath, "has"],
|
|
157
|
-
input: hasInputSchema,
|
|
264
|
+
input: withPartition(hasInputSchema),
|
|
158
265
|
output: hasOutputSchema,
|
|
159
266
|
metadata: {
|
|
160
267
|
effects: { reads: [resource], writes: [] },
|
|
161
268
|
description: `Check if item exists in ${collectionName}`,
|
|
162
269
|
tags: ["collections", collectionName, "read"],
|
|
163
270
|
},
|
|
164
|
-
handler: async (input: HasInput, ctx: ProcedureContext) => {
|
|
165
|
-
const storage =
|
|
271
|
+
handler: async (input: HasInput & PartitionInput, ctx: ProcedureContext) => {
|
|
272
|
+
const { storage } = await storageOf(input, ctx);
|
|
166
273
|
return storage.has(input.id);
|
|
167
274
|
},
|
|
168
275
|
}),
|
|
@@ -170,15 +277,15 @@ export function createCollectionProcedures(collectionName: string): AnyProcedure
|
|
|
170
277
|
// GetAll
|
|
171
278
|
defineProcedure({
|
|
172
279
|
path: [...basePath, "getAll"],
|
|
173
|
-
input: getAllInputSchema,
|
|
280
|
+
input: withPartition(getAllInputSchema),
|
|
174
281
|
output: getAllOutputSchema,
|
|
175
282
|
metadata: {
|
|
176
283
|
effects: { reads: [resource], writes: [] },
|
|
177
284
|
description: `Get all items from ${collectionName}`,
|
|
178
285
|
tags: ["collections", collectionName, "read"],
|
|
179
286
|
},
|
|
180
|
-
handler: async (
|
|
181
|
-
const storage =
|
|
287
|
+
handler: async (input: GetAllInput & PartitionInput, ctx: ProcedureContext) => {
|
|
288
|
+
const { storage } = await storageOf(input, ctx);
|
|
182
289
|
return storage.getAll();
|
|
183
290
|
},
|
|
184
291
|
}),
|
|
@@ -186,15 +293,15 @@ export function createCollectionProcedures(collectionName: string): AnyProcedure
|
|
|
186
293
|
// Entries: each item with its id, for a sync (roadmap R2)
|
|
187
294
|
defineProcedure({
|
|
188
295
|
path: [...basePath, "entries"],
|
|
189
|
-
input: entriesInputSchema,
|
|
296
|
+
input: withPartition(entriesInputSchema),
|
|
190
297
|
output: entriesOutputSchema,
|
|
191
298
|
metadata: {
|
|
192
299
|
effects: { reads: [resource], writes: [] },
|
|
193
300
|
description: `Get all items of ${collectionName} with their ids`,
|
|
194
301
|
tags: ["collections", collectionName, "read"],
|
|
195
302
|
},
|
|
196
|
-
handler: async (
|
|
197
|
-
const storage =
|
|
303
|
+
handler: async (input: EntriesInput & PartitionInput, ctx: ProcedureContext) => {
|
|
304
|
+
const { storage } = await storageOf(input, ctx);
|
|
198
305
|
if (!storage.getEntries) {
|
|
199
306
|
throw new Error(`The storage of ${collectionName} cannot give the ids of its items`);
|
|
200
307
|
}
|
|
@@ -205,15 +312,15 @@ export function createCollectionProcedures(collectionName: string): AnyProcedure
|
|
|
205
312
|
// Ids: the ids of the items without the values, for a full sync
|
|
206
313
|
defineProcedure({
|
|
207
314
|
path: [...basePath, "ids"],
|
|
208
|
-
input: idsInputSchema,
|
|
315
|
+
input: withPartition(idsInputSchema),
|
|
209
316
|
output: idsOutputSchema,
|
|
210
317
|
metadata: {
|
|
211
318
|
effects: { reads: [resource], writes: [] },
|
|
212
319
|
description: `Get the ids of the items of ${collectionName}, without the values`,
|
|
213
320
|
tags: ["collections", collectionName, "read"],
|
|
214
321
|
},
|
|
215
|
-
handler: async (
|
|
216
|
-
const storage =
|
|
322
|
+
handler: async (input: IdsInput & PartitionInput, ctx: ProcedureContext) => {
|
|
323
|
+
const { storage } = await storageOf(input, ctx);
|
|
217
324
|
if (storage.getIds) return storage.getIds();
|
|
218
325
|
// A storage without getIds() gives its entries: the values stay on the server
|
|
219
326
|
if (storage.getEntries) return (await storage.getEntries()).map(([id]) => id);
|
|
@@ -224,17 +331,20 @@ export function createCollectionProcedures(collectionName: string): AnyProcedure
|
|
|
224
331
|
// Changes: the items that changed after a version, for a delta sync (roadmap R28)
|
|
225
332
|
defineProcedure({
|
|
226
333
|
path: [...basePath, "changes"],
|
|
227
|
-
input: changesInputSchema,
|
|
334
|
+
input: withPartition(changesInputSchema),
|
|
228
335
|
output: changesOutputSchema,
|
|
229
336
|
metadata: {
|
|
230
337
|
effects: { reads: [resource], writes: [] },
|
|
231
338
|
description: `Get the items of ${collectionName} that changed after a version`,
|
|
232
339
|
tags: ["collections", collectionName, "read"],
|
|
233
340
|
},
|
|
234
|
-
handler: async (input: ChangesInput, ctx: ProcedureContext): Promise<ChangesOutput> => {
|
|
235
|
-
const storage =
|
|
341
|
+
handler: async (input: ChangesInput & PartitionInput, ctx: ProcedureContext): Promise<ChangesOutput> => {
|
|
342
|
+
const { storage, partition } = await storageOf(input, ctx);
|
|
236
343
|
if (storage.getChangesSince) {
|
|
237
344
|
const changes = await storage.getChangesSince(input.since);
|
|
345
|
+
// The chain of the change events starts at this version: the caller then applies the
|
|
346
|
+
// next event without a read (roadmap CLI-15)
|
|
347
|
+
if (topicRule) startChangeChain(ctx.bus, storage, topicOf(ctx, partition), changes.version);
|
|
238
348
|
return {
|
|
239
349
|
version: changes.version,
|
|
240
350
|
full: changes.full,
|
|
@@ -252,18 +362,63 @@ export function createCollectionProcedures(collectionName: string): AnyProcedure
|
|
|
252
362
|
},
|
|
253
363
|
}),
|
|
254
364
|
|
|
365
|
+
// Find: the items that match a JSON selector, with an order, a skip and a limit (roadmap CLI-14)
|
|
366
|
+
defineProcedure({
|
|
367
|
+
path: [...basePath, "find"],
|
|
368
|
+
input: withPartition(findInputSchemaOf(queryPolicy)),
|
|
369
|
+
output: findOutputSchema,
|
|
370
|
+
metadata: {
|
|
371
|
+
effects: { reads: [resource], writes: [] },
|
|
372
|
+
description: `Find the items of ${collectionName} that match a selector, with their ids, an order, a skip and a limit`,
|
|
373
|
+
tags: ["collections", collectionName, "read", "query"],
|
|
374
|
+
},
|
|
375
|
+
handler: async (input: FindInput & PartitionInput, ctx: ProcedureContext) => {
|
|
376
|
+
const { storage } = await storageOf(input, ctx);
|
|
377
|
+
const selector = input.selector ?? {};
|
|
378
|
+
const query = queryOptionsOf(input, queryPolicy);
|
|
379
|
+
// The storage runs the selector itself when it can: a database does not read each item. It
|
|
380
|
+
// 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) {
|
|
383
|
+
throw new Error(`The storage of ${collectionName} cannot run a selector or give the ids of its items`);
|
|
384
|
+
}
|
|
385
|
+
return queryEntries(await storage.getEntries(), selector, query, queryPolicy).map(([id, value]) => ({ id, value }));
|
|
386
|
+
},
|
|
387
|
+
}),
|
|
388
|
+
|
|
389
|
+
// Patch: merge fields into one item, a JSON Merge Patch (roadmap CLI-14)
|
|
390
|
+
defineProcedure({
|
|
391
|
+
path: [...basePath, "patch"],
|
|
392
|
+
input: withPartition(patchInputSchema),
|
|
393
|
+
output: patchOutputSchema,
|
|
394
|
+
metadata: {
|
|
395
|
+
effects: { reads: [resource], writes: [resource], idempotent: true },
|
|
396
|
+
description: `Merge fields into one item of ${collectionName}: a JSON Merge Patch, or the operations $set and $unset`,
|
|
397
|
+
tags: ["collections", collectionName, "write"],
|
|
398
|
+
},
|
|
399
|
+
handler: async (input: PatchInput & PartitionInput, ctx: ProcedureContext) => {
|
|
400
|
+
const { storage, partition } = await storageOf(input, ctx);
|
|
401
|
+
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 };
|
|
406
|
+
return output;
|
|
407
|
+
},
|
|
408
|
+
}),
|
|
409
|
+
|
|
255
410
|
// Size
|
|
256
411
|
defineProcedure({
|
|
257
412
|
path: [...basePath, "size"],
|
|
258
|
-
input: sizeInputSchema,
|
|
413
|
+
input: withPartition(sizeInputSchema),
|
|
259
414
|
output: sizeOutputSchema,
|
|
260
415
|
metadata: {
|
|
261
416
|
effects: { reads: [resource], writes: [] },
|
|
262
417
|
description: `Get number of items in ${collectionName}`,
|
|
263
418
|
tags: ["collections", collectionName, "read"],
|
|
264
419
|
},
|
|
265
|
-
handler: async (
|
|
266
|
-
const storage =
|
|
420
|
+
handler: async (input: SizeInput & PartitionInput, ctx: ProcedureContext) => {
|
|
421
|
+
const { storage } = await storageOf(input, ctx);
|
|
267
422
|
return storage.size();
|
|
268
423
|
},
|
|
269
424
|
}),
|
|
@@ -271,31 +426,31 @@ export function createCollectionProcedures(collectionName: string): AnyProcedure
|
|
|
271
426
|
// Clear
|
|
272
427
|
defineProcedure({
|
|
273
428
|
path: [...basePath, "clear"],
|
|
274
|
-
input: clearInputSchema,
|
|
429
|
+
input: withPartition(clearInputSchema),
|
|
275
430
|
output: clearOutputSchema,
|
|
276
431
|
metadata: {
|
|
277
432
|
effects: { reads: [resource], writes: [resource], idempotent: true },
|
|
278
433
|
description: `Clear all items from ${collectionName}`,
|
|
279
434
|
tags: ["collections", collectionName, "write"],
|
|
280
435
|
},
|
|
281
|
-
handler: async (
|
|
282
|
-
const storage =
|
|
283
|
-
await storage.clear();
|
|
436
|
+
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)));
|
|
284
439
|
},
|
|
285
440
|
}),
|
|
286
441
|
|
|
287
442
|
// GetBatch
|
|
288
443
|
defineProcedure({
|
|
289
444
|
path: [...basePath, "getBatch"],
|
|
290
|
-
input: getBatchInputSchema,
|
|
445
|
+
input: withPartition(getBatchInputSchema),
|
|
291
446
|
output: getBatchOutputSchema,
|
|
292
447
|
metadata: {
|
|
293
448
|
effects: { reads: [resource], writes: [] },
|
|
294
449
|
description: `Get multiple items from ${collectionName}`,
|
|
295
450
|
tags: ["collections", collectionName, "read", "batch"],
|
|
296
451
|
},
|
|
297
|
-
handler: async (input: GetBatchInput, ctx: ProcedureContext) => {
|
|
298
|
-
const storage =
|
|
452
|
+
handler: async (input: GetBatchInput & PartitionInput, ctx: ProcedureContext) => {
|
|
453
|
+
const { storage } = await storageOf(input, ctx);
|
|
299
454
|
const result = await storage.getBatch(input.ids);
|
|
300
455
|
// Convert Map to plain object
|
|
301
456
|
return Object.fromEntries(result);
|
|
@@ -305,36 +460,36 @@ export function createCollectionProcedures(collectionName: string): AnyProcedure
|
|
|
305
460
|
// SetBatch
|
|
306
461
|
defineProcedure({
|
|
307
462
|
path: [...basePath, "setBatch"],
|
|
308
|
-
input: setBatchInputSchema,
|
|
463
|
+
input: withPartition(setBatchInputSchema),
|
|
309
464
|
output: setBatchOutputSchema,
|
|
310
465
|
metadata: {
|
|
311
466
|
effects: { reads: [resource], writes: [resource], idempotent: true },
|
|
312
467
|
description: `Set multiple items in ${collectionName}`,
|
|
313
468
|
tags: ["collections", collectionName, "write", "batch"],
|
|
314
469
|
},
|
|
315
|
-
handler: async (input: SetBatchInput, ctx: ProcedureContext) => {
|
|
316
|
-
const storage =
|
|
470
|
+
handler: async (input: SetBatchInput & PartitionInput, ctx: ProcedureContext) => {
|
|
471
|
+
const { storage, partition } = await storageOf(input, ctx);
|
|
317
472
|
const items: Array<[string, unknown]> = input.items.map((item) => [
|
|
318
473
|
item.id,
|
|
319
474
|
item.value,
|
|
320
475
|
]);
|
|
321
|
-
await storage.setBatch(items);
|
|
476
|
+
await changing(ctx, partition, storage, items.map(([id]) => id), input.origin, () => storage.setBatch(items, writeOptionsOf(input)));
|
|
322
477
|
},
|
|
323
478
|
}),
|
|
324
479
|
|
|
325
480
|
// DeleteBatch
|
|
326
481
|
defineProcedure({
|
|
327
482
|
path: [...basePath, "deleteBatch"],
|
|
328
|
-
input: deleteBatchInputSchema,
|
|
483
|
+
input: withPartition(deleteBatchInputSchema),
|
|
329
484
|
output: deleteBatchOutputSchema,
|
|
330
485
|
metadata: {
|
|
331
486
|
effects: { reads: [resource], writes: [resource], idempotent: true },
|
|
332
487
|
description: `Delete multiple items from ${collectionName}`,
|
|
333
488
|
tags: ["collections", collectionName, "write", "batch"],
|
|
334
489
|
},
|
|
335
|
-
handler: async (input: DeleteBatchInput, ctx: ProcedureContext) => {
|
|
336
|
-
const storage =
|
|
337
|
-
return storage.deleteBatch(input.ids);
|
|
490
|
+
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)));
|
|
338
493
|
},
|
|
339
494
|
}),
|
|
340
495
|
];
|