@metreeca/keep 0.10.0 → 0.11.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/README.md +49 -42
- package/dist/clients/batching.core.d.ts +30 -10
- package/dist/clients/batching.core.d.ts.map +1 -1
- package/dist/clients/batching.core.js +74 -29
- package/dist/clients/batching.core.js.map +1 -1
- package/dist/clients/batching.d.ts +84 -89
- package/dist/clients/batching.d.ts.map +1 -1
- package/dist/clients/batching.js +36 -32
- package/dist/clients/batching.js.map +1 -1
- package/dist/clients/validating.d.ts +25 -22
- package/dist/clients/validating.d.ts.map +1 -1
- package/dist/clients/validating.js +70 -48
- package/dist/clients/validating.js.map +1 -1
- package/dist/index.d.ts +285 -179
- package/dist/index.d.ts.map +1 -1
- package/dist/stores/caching.d.ts +17 -20
- package/dist/stores/caching.d.ts.map +1 -1
- package/dist/stores/caching.js +60 -51
- package/dist/stores/caching.js.map +1 -1
- package/dist/stores/managing.d.ts +44 -42
- package/dist/stores/managing.d.ts.map +1 -1
- package/dist/stores/managing.js +58 -70
- package/dist/stores/managing.js.map +1 -1
- package/package.json +6 -6
|
@@ -1,61 +1,58 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Batching store client.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* batch,
|
|
6
|
-
*
|
|
7
|
-
* routes every call through a {@link Broker} that queues like-typed requests and hands each handler
|
|
8
|
-
* the whole batch it accumulated.
|
|
4
|
+
* Lets a connector serve many concurrent store requests with few backend round-trips. Requests of the same kind are
|
|
5
|
+
* coalesced into a single batch, and the connector supplies one batch {@link Handler} per kind. The resulting
|
|
6
|
+
* {@link StoreClient} adds conditional CRUD semantics on top, so handlers apply each request unconditionally.
|
|
9
7
|
*
|
|
10
|
-
* Four request
|
|
8
|
+
* Four request kinds partition the work, each with its own handler:
|
|
11
9
|
*
|
|
12
10
|
* - **`detect`** — probes whether each {@link Detect} entry exists in the store
|
|
13
|
-
* - **`
|
|
14
|
-
* by the model may be
|
|
15
|
-
* - **`select`** — materialises each {@link Select} collection
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* a handler must not assume any ordering between handlers, and must await every nested promise it
|
|
29
|
-
* issues before settling the request that depends on it. Correctness rests on this data dependency
|
|
30
|
-
* alone, not on handler scheduling.
|
|
11
|
+
* - **`detail`** — materialises each {@link Detail} resource against its `model`; multi-valued members reached
|
|
12
|
+
* by the model may be handed to the `select` handler through {@link Broker.select}
|
|
13
|
+
* - **`select`** — materialises each {@link Select} collection against its `query`; element resources reached
|
|
14
|
+
* by the query may be handed to the `detail` handler through {@link Broker.detail}
|
|
15
|
+
* - **`modify`** — creates, updates, or deletes each {@link Modify} target, or links it to a collection
|
|
16
|
+
*
|
|
17
|
+
* Retrieval is driven by the requested `model` and `query`, not by the stored graph. A `detail` handler issues a
|
|
18
|
+
* `select` for each multi-valued member its model reaches, and a `select` handler issues a `detail` for each element
|
|
19
|
+
* resource its query reaches. Each request thus descends exactly as deep as its `model` or `query` nests, however
|
|
20
|
+
* deep the underlying graph runs.
|
|
21
|
+
*
|
|
22
|
+
* > [!IMPORTANT]
|
|
23
|
+
* > Handlers run concurrently and exchange nested requests through the {@link Broker}. A handler must not assume any
|
|
24
|
+
* > ordering between handlers, and must await every nested request it issues before settling the request that
|
|
25
|
+
* > depends on it.
|
|
31
26
|
*
|
|
32
27
|
* @module
|
|
33
28
|
*/
|
|
34
29
|
import type { Property, ResourceShape } from "@metreeca/blue/resource";
|
|
35
|
-
import type
|
|
30
|
+
import { type Match } from "@metreeca/blue/value";
|
|
31
|
+
import { type Lazy } from "@metreeca/core";
|
|
36
32
|
import type { Tag } from "@metreeca/core/language";
|
|
37
|
-
import type {
|
|
38
|
-
import type {
|
|
33
|
+
import type { Query, Slot, Template } from "@metreeca/qest/model";
|
|
34
|
+
import type { Reference, Resource, Value } from "@metreeca/qest/state";
|
|
39
35
|
import type { StoreClient } from "../index.js";
|
|
40
36
|
/**
|
|
41
37
|
* Requests accepted by {@link Broker}.
|
|
42
38
|
*/
|
|
43
|
-
export type Request = Detect |
|
|
39
|
+
export type Request = Detect | Detail | Select | Modify;
|
|
44
40
|
/**
|
|
45
41
|
* Result of running a {@link Request}, narrowed by request variant:
|
|
46
42
|
*
|
|
47
43
|
* - a {@link Detect} resolves to a `boolean` existence flag
|
|
48
|
-
* - a {@link
|
|
49
|
-
* - a {@link Select} resolves to
|
|
44
|
+
* - a {@link Detail} resolves to the materialised resource, typed by its shape and model
|
|
45
|
+
* - a {@link Select} resolves to the {@link Value | values} its collection holds, untyped since its shape is
|
|
46
|
+
* carried as a value rather than as a type parameter
|
|
50
47
|
* - a {@link Modify} resolves to its mutated entry's {@link Reference}
|
|
51
48
|
*
|
|
52
49
|
* @typeParam R The request whose result type is selected
|
|
53
50
|
*/
|
|
54
|
-
export type Response<R extends Request> = R extends Select
|
|
51
|
+
export type Response<R extends Request> = R extends Select ? readonly Value[] : R extends Detail<infer T, infer S> ? Match<S, T> : R extends Modify ? Reference : R extends Detect ? boolean : never;
|
|
55
52
|
/**
|
|
56
53
|
* Resource existence request.
|
|
57
54
|
*
|
|
58
|
-
* Carries the `entry` to probe
|
|
55
|
+
* Carries the `entry` to probe, and resolves to whether that entry exists in the store.
|
|
59
56
|
*/
|
|
60
57
|
export type Detect = {
|
|
61
58
|
readonly entry: Reference;
|
|
@@ -63,28 +60,28 @@ export type Detect = {
|
|
|
63
60
|
/**
|
|
64
61
|
* Resource retrieval request.
|
|
65
62
|
*
|
|
66
|
-
* Carries a single resource (`entry` of `shape`), the `model`
|
|
67
|
-
*
|
|
68
|
-
* the materialised resource.
|
|
63
|
+
* Carries a single resource (`entry` of `shape`), the `model` to materialise it against, and the `locale` priority
|
|
64
|
+
* for coalescing its localised content (QEST §6.2). Resolves to the materialised resource.
|
|
69
65
|
*
|
|
70
|
-
* @typeParam T The resource model selecting the result
|
|
66
|
+
* @typeParam T The resource model selecting the members of the result
|
|
67
|
+
* @typeParam S The shape describing the resource
|
|
71
68
|
*/
|
|
72
|
-
export type
|
|
69
|
+
export type Detail<T extends Template = Template, S extends Lazy<ResourceShape> = Lazy<ResourceShape>> = {
|
|
73
70
|
readonly entry: Reference;
|
|
74
|
-
readonly shape:
|
|
71
|
+
readonly shape: S;
|
|
75
72
|
readonly model: T;
|
|
76
73
|
readonly locale: readonly Tag[];
|
|
77
74
|
};
|
|
78
75
|
/**
|
|
79
76
|
* Collection retrieval request.
|
|
80
77
|
*
|
|
81
|
-
* Carries a single multi-valued property (`field` on `entry` of `shape`), the `query` describing the
|
|
82
|
-
*
|
|
83
|
-
*
|
|
78
|
+
* Carries a single multi-valued property (`field` on `entry` of `shape`), the `query` describing its values, and the
|
|
79
|
+
* `locale` priority for coalescing its localised content (QEST §6.2). Resolves to the materialised collection: items
|
|
80
|
+
* for a template query, rows for a projection.
|
|
84
81
|
*
|
|
85
|
-
* @typeParam T The collection query selecting the result
|
|
82
|
+
* @typeParam T The collection query selecting the form of the result
|
|
86
83
|
*/
|
|
87
|
-
export type Select<T extends Query = Query
|
|
84
|
+
export type Select<T extends Query<Slot> = Query<Slot>> = {
|
|
88
85
|
readonly entry: Reference;
|
|
89
86
|
readonly shape: Lazy<ResourceShape>;
|
|
90
87
|
readonly field: Property;
|
|
@@ -94,25 +91,29 @@ export type Select<T extends Query = Query> = {
|
|
|
94
91
|
/**
|
|
95
92
|
* Resource mutation request.
|
|
96
93
|
*
|
|
97
|
-
* Carries the `entry` to mutate (of `shape`) and the target `state` to persist
|
|
98
|
-
*
|
|
99
|
-
* {@link Reference}
|
|
94
|
+
* Carries the `entry` to mutate (of `shape`) and the target `state` to persist. A present `state` creates or updates
|
|
95
|
+
* the entry, and an omitted `state` deletes it. A request carrying a `link` instead adds `item` to the collection
|
|
96
|
+
* held by `entry` under `property`, leaving the rest of the entry unchanged. Resolves to the {@link Reference} of the
|
|
97
|
+
* mutated entry.
|
|
100
98
|
*/
|
|
101
99
|
export type Modify = {
|
|
102
100
|
readonly entry: Reference;
|
|
103
101
|
readonly shape: Lazy<ResourceShape>;
|
|
104
102
|
readonly state?: Resource;
|
|
103
|
+
readonly link?: {
|
|
104
|
+
readonly property: Property;
|
|
105
|
+
readonly item: Reference;
|
|
106
|
+
};
|
|
105
107
|
};
|
|
106
108
|
/**
|
|
107
109
|
* Batch handler for a single request type.
|
|
108
110
|
*
|
|
109
|
-
* Supplied by the connector
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* nested requests issued through the broker.
|
|
111
|
+
* Supplied by the connector to {@link createBatchingStore}, one per request type. A handler receives every request of
|
|
112
|
+
* its type accumulated since the previous batch, together with a {@link Broker} for issuing nested requests. It
|
|
113
|
+
* settles each {@link Deferred} in the batch with its result or error. The returned promise must resolve only once
|
|
114
|
+
* every request in the batch has settled, including any nested requests issued through the broker.
|
|
114
115
|
*
|
|
115
|
-
*
|
|
116
|
+
* Batches are never empty, so handlers need not guard against an empty one.
|
|
116
117
|
*
|
|
117
118
|
* @typeParam R The request variant handled
|
|
118
119
|
*/
|
|
@@ -122,12 +123,11 @@ export type Handler<R extends Request> = {
|
|
|
122
123
|
/**
|
|
123
124
|
* A queued request paired with its promise-resolution hooks.
|
|
124
125
|
*
|
|
125
|
-
* Each batch a {@link Handler} receives is an array of `Deferred` entries.
|
|
126
|
-
* `request
|
|
127
|
-
*
|
|
128
|
-
* {@link Broker} have settled.
|
|
126
|
+
* Each batch a {@link Handler} receives is an array of `Deferred` entries. For each entry, the handler reads the
|
|
127
|
+
* `request` and settles it, calling `resolve` with its result (typed by {@link Response | Response<R>}) or `reject`
|
|
128
|
+
* with an error.
|
|
129
129
|
*
|
|
130
|
-
* @typeParam R The queued request variant: a {@link Detect} probe, a {@link
|
|
130
|
+
* @typeParam R The queued request variant: a {@link Detect} probe, a {@link Detail} resource fetch,
|
|
131
131
|
* a {@link Select} collection fetch, or a {@link Modify} mutation
|
|
132
132
|
*/
|
|
133
133
|
export type Deferred<R extends Request> = {
|
|
@@ -138,10 +138,9 @@ export type Deferred<R extends Request> = {
|
|
|
138
138
|
/**
|
|
139
139
|
* Request-submission surface over the batching {@link Handler | handlers}.
|
|
140
140
|
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
* handler.
|
|
141
|
+
* Routes each request (detection, detail, selection, modification) to the handler for its type. Each call queues the
|
|
142
|
+
* request for the next batch and returns a promise that settles with its result. Handlers receive a broker to hand
|
|
143
|
+
* nested work to a sibling handler.
|
|
145
144
|
*/
|
|
146
145
|
export type Broker = {
|
|
147
146
|
/**
|
|
@@ -154,18 +153,17 @@ export type Broker = {
|
|
|
154
153
|
*/
|
|
155
154
|
detect(request: Detect): Promise<boolean>;
|
|
156
155
|
/**
|
|
157
|
-
* Enqueue a {@link
|
|
156
|
+
* Enqueue a {@link Detail} request.
|
|
158
157
|
*
|
|
159
158
|
* Carries no existence precondition: an entry with no stored state resolves to an empty instance of
|
|
160
159
|
* the request's `model` rather than rejecting.
|
|
161
160
|
*
|
|
162
161
|
* @param request The resource and model to materialise
|
|
163
162
|
*
|
|
164
|
-
* @returns A promise resolving to the materialised
|
|
165
|
-
*
|
|
166
|
-
* value have resolved
|
|
163
|
+
* @returns A promise resolving to the resource materialised against the request's `model`, settled once the
|
|
164
|
+
* owning batch and any nested requests needed to assemble it have resolved
|
|
167
165
|
*/
|
|
168
|
-
|
|
166
|
+
detail<S extends Lazy<ResourceShape>, T extends Template>(request: Detail<T, S>): Promise<Match<S, T>>;
|
|
169
167
|
/**
|
|
170
168
|
* Enqueue a {@link Select} request.
|
|
171
169
|
*
|
|
@@ -174,21 +172,20 @@ export type Broker = {
|
|
|
174
172
|
*
|
|
175
173
|
* @param request The property and query to materialise
|
|
176
174
|
*
|
|
177
|
-
* @returns A promise resolving to the
|
|
178
|
-
*
|
|
179
|
-
*
|
|
175
|
+
* @returns A promise resolving to the values the collection holds as the request's `query` narrows them,
|
|
176
|
+
* items for a template and rows for a projection, settled once the owning batch and any nested requests
|
|
177
|
+
* needed to assemble them have resolved
|
|
180
178
|
*/
|
|
181
|
-
select<T extends Query
|
|
179
|
+
select<T extends Query<Slot>>(request: Select<T>): Promise<readonly Value[]>;
|
|
182
180
|
/**
|
|
183
181
|
* Enqueue a {@link Modify} request.
|
|
184
182
|
*
|
|
185
|
-
* Mutates unconditionally, with no existence precondition
|
|
186
|
-
*
|
|
187
|
-
* absent.
|
|
183
|
+
* Mutates unconditionally, with no existence precondition. A present `state` upserts the entry, creating it if
|
|
184
|
+
* absent. An omitted `state` deletes the entry, and is a no-op if the entry is absent.
|
|
188
185
|
*
|
|
189
|
-
* @param request The entry and target state to persist
|
|
186
|
+
* @param request The entry and target state to persist, or the collection link to add
|
|
190
187
|
*
|
|
191
|
-
* @returns A promise resolving to the
|
|
188
|
+
* @returns A promise resolving to the {@link Reference} of the mutated entry, settled once the owning
|
|
192
189
|
* batch has completed
|
|
193
190
|
*/
|
|
194
191
|
modify(request: Modify): Promise<Reference>;
|
|
@@ -196,27 +193,25 @@ export type Broker = {
|
|
|
196
193
|
/**
|
|
197
194
|
* Creates a batching store client backed by a set of request handlers.
|
|
198
195
|
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
196
|
+
* Coalesces concurrent {@link StoreClient} calls into batches, each served by the matching `handlers` entry. The
|
|
197
|
+
* client adds conditional CRUD semantics on top of the handlers: `create`, `update` and `delete` probe for existence
|
|
198
|
+
* through the `detect` handler before handing the mutation to `modify`. `create` mints the identifier of the new
|
|
199
|
+
* resource, if its state names none, and links it to the collecting resource.
|
|
203
200
|
*
|
|
204
|
-
* The `modify` handler
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
* across the surviving batch rather than surfaced as a handler throw. The wrapped handler thus always
|
|
208
|
-
* receives a non-empty batch of pre-validated requests to apply unconditionally.
|
|
201
|
+
* The `modify` handler receives only requests whose `state` carries no `id` other than their `entry`. A request
|
|
202
|
+
* failing this check is rejected on its own, and the rest of the batch still applies. If the `modify` handler throws,
|
|
203
|
+
* the error rejects every request in its batch.
|
|
209
204
|
*
|
|
210
|
-
* @param handlers The batch handlers, one per request type
|
|
211
|
-
* modify requests
|
|
205
|
+
* @param handlers The batch handlers, one per request type
|
|
212
206
|
*
|
|
213
|
-
* @returns An immutable {@link StoreClient}
|
|
207
|
+
* @returns An immutable {@link StoreClient} serving its requests in batches through `handlers`
|
|
214
208
|
*
|
|
215
|
-
* @throws {RangeError} If
|
|
209
|
+
* @throws {@link !RangeError RangeError} If an update or insert `state` carries an `id` that differs from its `entry`,
|
|
210
|
+
* or if a create `state` member filling an identifier slot is not a single path segment
|
|
216
211
|
*/
|
|
217
212
|
export declare function createBatchingStore(handlers: {
|
|
218
213
|
detect: Handler<Detect>;
|
|
219
|
-
|
|
214
|
+
detail: Handler<Detail>;
|
|
220
215
|
select: Handler<Select>;
|
|
221
216
|
modify: Handler<Modify>;
|
|
222
217
|
}): StoreClient;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"batching.d.ts","sourceRoot":"","sources":["../../src/clients/batching.ts"],"names":[],"mappings":"AAgBA
|
|
1
|
+
{"version":3,"file":"batching.d.ts","sourceRoot":"","sources":["../../src/clients/batching.ts"],"names":[],"mappings":"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"}
|
package/dist/clients/batching.js
CHANGED
|
@@ -13,37 +13,36 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
16
|
+
import { blueprint, collection } from "@metreeca/blue/value";
|
|
17
|
+
import { isString } from "@metreeca/core";
|
|
18
|
+
import { immutable } from "@metreeca/core/values";
|
|
19
|
+
import { createBroker, mint } from "./batching.core.js";
|
|
19
20
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
20
21
|
/**
|
|
21
22
|
* Creates a batching store client backed by a set of request handlers.
|
|
22
23
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
24
|
+
* Coalesces concurrent {@link StoreClient} calls into batches, each served by the matching `handlers` entry. The
|
|
25
|
+
* client adds conditional CRUD semantics on top of the handlers: `create`, `update` and `delete` probe for existence
|
|
26
|
+
* through the `detect` handler before handing the mutation to `modify`. `create` mints the identifier of the new
|
|
27
|
+
* resource, if its state names none, and links it to the collecting resource.
|
|
27
28
|
*
|
|
28
|
-
* The `modify` handler
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* across the surviving batch rather than surfaced as a handler throw. The wrapped handler thus always
|
|
32
|
-
* receives a non-empty batch of pre-validated requests to apply unconditionally.
|
|
29
|
+
* The `modify` handler receives only requests whose `state` carries no `id` other than their `entry`. A request
|
|
30
|
+
* failing this check is rejected on its own, and the rest of the batch still applies. If the `modify` handler throws,
|
|
31
|
+
* the error rejects every request in its batch.
|
|
33
32
|
*
|
|
34
|
-
* @param handlers The batch handlers, one per request type
|
|
35
|
-
* modify requests
|
|
33
|
+
* @param handlers The batch handlers, one per request type
|
|
36
34
|
*
|
|
37
|
-
* @returns An immutable {@link StoreClient}
|
|
35
|
+
* @returns An immutable {@link StoreClient} serving its requests in batches through `handlers`
|
|
38
36
|
*
|
|
39
|
-
* @throws {RangeError} If
|
|
37
|
+
* @throws {@link !RangeError RangeError} If an update or insert `state` carries an `id` that differs from its `entry`,
|
|
38
|
+
* or if a create `state` member filling an identifier slot is not a single path segment
|
|
40
39
|
*/
|
|
41
40
|
export function createBatchingStore(handlers) {
|
|
42
41
|
const broker = createBroker({
|
|
43
42
|
...handlers,
|
|
44
43
|
async modify(batch, broker) {
|
|
45
44
|
const items = batch.filter(({ request: { entry, state }, reject }) => {
|
|
46
|
-
// reject each request whose state id contradicts its entry
|
|
45
|
+
// reject each request whose state id contradicts its entry on its own, so the
|
|
47
46
|
// rest of the batch still applies and the check stays out of storage-specific handlers
|
|
48
47
|
const matching = state?.id === undefined || state.id === entry;
|
|
49
48
|
if (!matching) {
|
|
@@ -65,25 +64,30 @@ export function createBatchingStore(handlers) {
|
|
|
65
64
|
});
|
|
66
65
|
return immutable({
|
|
67
66
|
async lookup({ entry, shape, model }, { locale = ["und"] } = {}) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
return eager(shape).virtual || await broker.detect({ entry })
|
|
71
|
-
? broker.lookup({ entry, shape, model, locale })
|
|
67
|
+
return await broker.detect({ entry })
|
|
68
|
+
? broker.detail({ entry, shape, model, locale })
|
|
72
69
|
: undefined;
|
|
73
70
|
},
|
|
74
|
-
async create({ entry, shape, state }) {
|
|
75
|
-
// the
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
71
|
+
async create({ entry, shape, model, state }) {
|
|
72
|
+
// the new resource is stored under the identifier its state names, or under one minted for it off the
|
|
73
|
+
// blueprint of the collection; either way it is the resource probed for existence and written, not the
|
|
74
|
+
// collection
|
|
75
|
+
const target = collection(shape, model);
|
|
76
|
+
const plan = blueprint(shape, model);
|
|
77
|
+
const { id } = state;
|
|
78
|
+
const child = isString(id) ? id : mint(entry, plan, state);
|
|
79
|
+
// a collection a plain property holds is linked to its new member; one exposed through
|
|
80
|
+
// a foreign property is a view over the member's own link and reaches it on its own
|
|
81
|
+
return await broker.detect({ entry: child }) ? undefined
|
|
82
|
+
: broker.modify({ entry: child, shape: plan, state }).then(created => target.foreign
|
|
83
|
+
? created
|
|
84
|
+
: broker.modify({ entry, shape, link: { property: target, item: created } }).then(() => created));
|
|
82
85
|
},
|
|
83
86
|
async update({ entry, shape, state }) {
|
|
84
|
-
// the entry identifies the target: a state carrying a different id contradicts it
|
|
85
|
-
|
|
86
|
-
|
|
87
|
+
// the entry identifies the target: a state carrying a different id contradicts it
|
|
88
|
+
const { id } = state;
|
|
89
|
+
if (id !== undefined && id !== entry) {
|
|
90
|
+
throw new RangeError(`mismatched state id <${String(id)}> for entry <${entry}>`);
|
|
87
91
|
}
|
|
88
92
|
return await broker.detect({ entry })
|
|
89
93
|
? broker.modify({ entry, shape, state })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"batching.js","sourceRoot":"","sources":["../../src/clients/batching.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;
|
|
1
|
+
{"version":3,"file":"batching.js","sourceRoot":"","sources":["../../src/clients/batching.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,37 +2,40 @@ import type { StoreClient } from "../index.js";
|
|
|
2
2
|
/**
|
|
3
3
|
* Creates a validating store backed by a delegate.
|
|
4
4
|
*
|
|
5
|
-
* Each method validates
|
|
5
|
+
* Each method validates its inputs against the supplied shape before forwarding the request to `store`:
|
|
6
6
|
*
|
|
7
|
-
* - `entry`
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* - `
|
|
11
|
-
* {@link
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* `
|
|
16
|
-
*
|
|
17
|
-
*
|
|
7
|
+
* - `entry` must be an absolute IRI with no query string and no fragment; otherwise the request is rejected with a
|
|
8
|
+
* {@link !RangeError RangeError}
|
|
9
|
+
* - `model` is validated as a {@link @metreeca/blue!validate | template} for {@link StoreClient.lookup lookup},
|
|
10
|
+
* within the query-complexity bounds (`plain`, `depth`, `limit`) of the caller's
|
|
11
|
+
* {@link StoreScope | retrieval scope}, and as a collection slice for {@link StoreClient.create create}
|
|
12
|
+
* - `state.id`, if present, must be nested under `entry` for {@link StoreClient.create create}, and equal to `entry`
|
|
13
|
+
* for {@link StoreClient.update update} and {@link StoreClient.insert insert}; otherwise the request is rejected
|
|
14
|
+
* with a {@link !RangeError RangeError}
|
|
15
|
+
* - `state` is validated as a resource with captive expansion capped at depth `0`, so inline captive batches are
|
|
16
|
+
* rejected; {@link StoreClient.create create} validates it against the shape of the collected resources, and
|
|
17
|
+
* {@link StoreClient.update update} and {@link StoreClient.insert insert} against the supplied shape
|
|
18
18
|
*
|
|
19
|
-
* The `trusted`
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* pass `trusted: true` to skip the redundant pass.
|
|
19
|
+
* The `trusted` option controls whether {@link StoreClient.lookup lookup} responses are re-validated against the
|
|
20
|
+
* shape, as narrowed by the caller's `model`. It defaults to `false`, the safe choice for connectors whose source may
|
|
21
|
+
* deliver non-conforming data, such as a remote REST endpoint. Local stores that compute results themselves can pass
|
|
22
|
+
* `trusted: true` to skip the redundant check.
|
|
24
23
|
*
|
|
25
|
-
* Validation failures surface as {@link @metreeca/core!TraceError | TraceError} or
|
|
26
|
-
* unified {@link Store} error channel.
|
|
24
|
+
* Validation failures surface as {@link @metreeca/core!TraceError | TraceError} or {@link !RangeError RangeError}
|
|
25
|
+
* rejections, in line with the unified {@link Store} error channel.
|
|
27
26
|
*
|
|
28
|
-
* @param store - Inner StoreClient
|
|
27
|
+
* @param store - Inner StoreClient receiving the validated requests
|
|
29
28
|
* @param options - Optional validation options
|
|
30
|
-
* @param options.trusted - When `true`, skips re-validation of the {@link StoreClient.lookup lookup}
|
|
31
|
-
* response against the shape narrowed by the caller's `model`; defaults to `false`
|
|
32
29
|
*
|
|
33
30
|
* @returns An immutable {@link StoreClient} wrapping `store` with input validation
|
|
34
31
|
*/
|
|
35
32
|
export declare function createValidatingStore(store: StoreClient, { trusted }?: {
|
|
33
|
+
/**
|
|
34
|
+
* Whether to skip re-validation of {@link StoreClient.lookup lookup} responses against the shape, as narrowed by
|
|
35
|
+
* the caller's `model`.
|
|
36
|
+
*
|
|
37
|
+
* @defaultValue `false`, treating the wrapped store as untrusted
|
|
38
|
+
*/
|
|
36
39
|
readonly trusted?: boolean;
|
|
37
40
|
}): StoreClient;
|
|
38
41
|
//# sourceMappingURL=validating.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validating.d.ts","sourceRoot":"","sources":["../../src/clients/validating.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"validating.d.ts","sourceRoot":"","sources":["../../src/clients/validating.ts"],"names":[],"mappings":"AAkCA,OAAO,KAAK,EAAS,WAAW,EAAc,MAAM,aAAa,CAAC;AAGlE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,WAAW,EAAE,EAEzD,OAAe,EAEf,GAAE;IAEF;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAErB,GAAG,WAAW,CAkJnB"}
|