@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
package/README.md
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
Core model-driven storage API for the [@metreeca/keep](https://github.com/metreeca/keep) linked data storage framework.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
[@metreeca/blue](https://github.com/metreeca/blue)
|
|
10
|
-
models
|
|
7
|
+
Provides one backend-agnostic API for persisting and retrieving linked data resources, validated against their models
|
|
8
|
+
and retrieved through client-defined templates and queries. Resource models are defined with
|
|
9
|
+
[@metreeca/blue](https://github.com/metreeca/blue), and resource states and retrieval templates follow the data and
|
|
10
|
+
query models of [@metreeca/qest](https://github.com/metreeca/qest). Actual storage is delegated to backend
|
|
11
11
|
[connector packages](https://github.com/metreeca/keep#installation).
|
|
12
12
|
|
|
13
13
|
# Installation
|
|
@@ -43,9 +43,11 @@ npm install @metreeca/keep
|
|
|
43
43
|
|
|
44
44
|
[managing]: https://metreeca.github.io/keep/modules/_metreeca_keep.managing.html
|
|
45
45
|
|
|
46
|
-
All store methods accept an `entry` that MUST be an absolute IRI without query string or fragment
|
|
47
|
-
entries reject with `RangeError`. `
|
|
48
|
-
|
|
46
|
+
All store methods accept an `entry` that MUST be an absolute IRI without query string or fragment: non-conforming
|
|
47
|
+
entries reject with `RangeError`. `create` takes as `entry` the resource holding the collection, and as `model` a slice
|
|
48
|
+
naming the multi-valued property that holds it. An `id` stated in a `create` state MUST be nested under `entry`.
|
|
49
|
+
`model` and `state` are validated against the supplied shape, and validation failures reject with a `TraceError`.
|
|
50
|
+
Network, storage, and other processing errors reject with a structured `Problem`.
|
|
49
51
|
|
|
50
52
|
## Retrieving Resources
|
|
51
53
|
|
|
@@ -56,31 +58,31 @@ const product = await store.lookup({
|
|
|
56
58
|
entry: "http://example.com/products/1",
|
|
57
59
|
shape: ProductShape,
|
|
58
60
|
model: {
|
|
59
|
-
name:
|
|
60
|
-
price:
|
|
61
|
+
name: {},
|
|
62
|
+
price: {},
|
|
61
63
|
vendor: {
|
|
62
|
-
id:
|
|
63
|
-
name:
|
|
64
|
+
id: {},
|
|
65
|
+
name: {}
|
|
64
66
|
}
|
|
65
67
|
}
|
|
66
68
|
});
|
|
67
69
|
|
|
68
70
|
// collection retrieval with filtering, ordering, and pagination
|
|
69
71
|
|
|
70
|
-
const
|
|
72
|
+
const catalogue = await store.lookup({
|
|
71
73
|
entry: "http://example.com/products/",
|
|
72
|
-
shape:
|
|
74
|
+
shape: CatalogueShape,
|
|
73
75
|
model: {
|
|
74
|
-
products:
|
|
75
|
-
id:
|
|
76
|
-
name:
|
|
77
|
-
price:
|
|
76
|
+
products: {
|
|
77
|
+
id: {},
|
|
78
|
+
name: {},
|
|
79
|
+
price: {},
|
|
78
80
|
">=price": 50, // price ≥ 50
|
|
79
81
|
"~name": "widget", // name contains "widget"
|
|
80
82
|
"^price": 1, // sort by price ascending
|
|
81
83
|
"@": 0, // offset
|
|
82
84
|
"#": 25 // limit
|
|
83
|
-
}
|
|
85
|
+
}
|
|
84
86
|
}
|
|
85
87
|
});
|
|
86
88
|
```
|
|
@@ -88,9 +90,10 @@ const catalog = await store.lookup({
|
|
|
88
90
|
## Creating and Updating Resources
|
|
89
91
|
|
|
90
92
|
```typescript
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
93
|
+
// creation within the collection holding the new resource, its identifier minted by the store unless stated
|
|
94
|
+
|
|
95
|
+
const product = await store.create({
|
|
96
|
+
entry: "http://example.com/products/", shape: CatalogueShape, model: { products: {} }, state: {
|
|
94
97
|
name: "Widget",
|
|
95
98
|
price: 29.99,
|
|
96
99
|
vendor: "http://example.com/vendors/acme"
|
|
@@ -144,21 +147,21 @@ const unsubscribe = store.observe(mutations => {
|
|
|
144
147
|
unsubscribe(); // stop receiving events
|
|
145
148
|
```
|
|
146
149
|
|
|
147
|
-
The second argument scopes the observer to the given resources and their descendants
|
|
148
|
-
pass an empty
|
|
150
|
+
The second argument scopes the observer to the given resources and their descendants: pass a single reference or any
|
|
151
|
+
collection of them, omit it to receive all mutations, or pass an empty collection to skip registration altogether.
|
|
149
152
|
|
|
150
153
|
## Executing Transactions
|
|
151
154
|
|
|
152
155
|
```typescript
|
|
153
156
|
await store.execute(async store => {
|
|
154
|
-
await store.create({ entry:
|
|
157
|
+
await store.create({ entry: catalogue, shape: CatalogueShape, model: { products: {} }, state: product });
|
|
155
158
|
await store.update({ entry: inventory.id, shape: InventoryShape, state: inventory });
|
|
156
159
|
});
|
|
157
160
|
```
|
|
158
161
|
|
|
159
|
-
Cross-backend isolation semantics, concurrency models, and the buffer-and-flush emulation pattern are
|
|
160
|
-
[Transaction Design](https://metreeca.github.io/keep/documents/_metreeca_keep.Transaction_Design.html)
|
|
161
|
-
document.
|
|
162
|
+
Cross-backend atomicity and isolation semantics, concurrency models, and the buffer-and-flush emulation pattern are
|
|
163
|
+
documented in the [Transaction Design](https://metreeca.github.io/keep/documents/_metreeca_keep.Transaction_Design.html)
|
|
164
|
+
companion document.
|
|
162
165
|
|
|
163
166
|
# Implementing Connectors
|
|
164
167
|
|
|
@@ -178,9 +181,9 @@ function createMyStore(): Store {
|
|
|
178
181
|
|
|
179
182
|
const backend: StoreClient = {
|
|
180
183
|
|
|
181
|
-
lookup({ entry, shape, model }) { /* query the backend, return matching data or undefined */ },
|
|
184
|
+
lookup({ entry, shape, model }, scope) { /* query the backend, return matching data or undefined */ },
|
|
182
185
|
|
|
183
|
-
create({ entry, shape, state }) { /* create
|
|
186
|
+
create({ entry, shape, model, state }) { /* create under the collection, return the new entry or undefined */ },
|
|
184
187
|
update({ entry, shape, state }) { /* update if present, return entry or undefined */ },
|
|
185
188
|
delete({ entry, shape }) { /* delete if present, return entry or undefined */ },
|
|
186
189
|
|
|
@@ -189,9 +192,11 @@ function createMyStore(): Store {
|
|
|
189
192
|
|
|
190
193
|
};
|
|
191
194
|
|
|
192
|
-
|
|
195
|
+
const client = createValidatingStore(backend, { trusted: true });
|
|
196
|
+
|
|
197
|
+
return createManagingStore({
|
|
193
198
|
|
|
194
|
-
execute: task => { /* run task within a backend transaction */ },
|
|
199
|
+
execute: task => { /* run task(client) within a backend transaction */ },
|
|
195
200
|
close: () => { /* release connections */ }
|
|
196
201
|
|
|
197
202
|
});
|
|
@@ -200,11 +205,12 @@ function createMyStore(): Store {
|
|
|
200
205
|
```
|
|
201
206
|
|
|
202
207
|
Pass `trusted: true` to `createValidatingStore` when the backend is trusted to deliver shape-conforming data, so
|
|
203
|
-
`lookup` responses skip the redundant
|
|
204
|
-
standalone call and the entire `execute` task body
|
|
205
|
-
|
|
208
|
+
`lookup` responses skip the redundant validation pass. The `execute` option of `createManagingStore` is required: it runs
|
|
209
|
+
every standalone call and the entire `execute` task body within the backend's transaction primitive, and MUST call the
|
|
210
|
+
task with a store client dedicated to that transaction. A non-transactional backend supplies an `execute` applying the
|
|
211
|
+
task directly to the client, and documents that a failing task does not roll back. `close` defaults to a no-op.
|
|
206
212
|
|
|
207
|
-
All errors reach the caller as promise rejections through the unified
|
|
213
|
+
All errors reach the caller as promise rejections through the unified store error channel: `RangeError` for malformed
|
|
208
214
|
entries, `TraceError` for shape validation failures, `Problem` for network, storage, or other processing failures.
|
|
209
215
|
Connectors MUST preserve this contract: convert backend-specific exceptions into `Problem` rejections and let validation
|
|
210
216
|
rejections propagate untouched.
|
|
@@ -215,8 +221,8 @@ companion document.
|
|
|
215
221
|
|
|
216
222
|
## Testing
|
|
217
223
|
|
|
218
|
-
Use [@metreeca/keep-suite](https://github.com/metreeca/keep/tree/main/packages/keep-suite) to run the full
|
|
219
|
-
suite against your connector:
|
|
224
|
+
Use [@metreeca/keep-suite](https://github.com/metreeca/keep/tree/main/packages/components/keep-suite) to run the full
|
|
225
|
+
conformance suite against your connector:
|
|
220
226
|
|
|
221
227
|
```typescript
|
|
222
228
|
import { testStore } from "@metreeca/keep-suite";
|
|
@@ -224,11 +230,11 @@ import { describe } from "vitest";
|
|
|
224
230
|
|
|
225
231
|
describe("my-store", () => testStore({
|
|
226
232
|
|
|
227
|
-
|
|
233
|
+
open: () => createMyStore(), // open a store with schema but no data
|
|
228
234
|
|
|
229
|
-
contains:
|
|
230
|
-
includes: (
|
|
231
|
-
excludes: (
|
|
235
|
+
contains: entry => { /* true if the resource has any stored data */ },
|
|
236
|
+
includes: (probe, shape) => { /* true if every fact described by `probe` is present */ },
|
|
237
|
+
excludes: (probe, shape) => { /* true if every fact described by `probe` is absent */ },
|
|
232
238
|
|
|
233
239
|
populate: () => { /* clear and reload the sample dataset */ },
|
|
234
240
|
generate: (sample, shape) => { /* insert an isolated copy of `sample` with a unique id */ },
|
|
@@ -238,7 +244,8 @@ describe("my-store", () => testStore({
|
|
|
238
244
|
}));
|
|
239
245
|
```
|
|
240
246
|
|
|
241
|
-
|
|
247
|
+
Narrow the run to a subset of sub-suites or tests with the `target` and `ignore` options (for example,
|
|
248
|
+
`target: ["Retrieve", "PersistCreate"]`); see the
|
|
242
249
|
[API reference](https://metreeca.github.io/keep/modules/_metreeca_keep-suite.html) for the full list.
|
|
243
250
|
|
|
244
251
|
# Support
|
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ResourceShape } from "@metreeca/blue/resource";
|
|
2
|
+
import { type Lazy } from "@metreeca/core";
|
|
3
|
+
import type { Reference, Resource } from "@metreeca/qest/state";
|
|
4
|
+
import type { Broker, Detail, Detect, Handler, Modify, Select } from "./batching.js";
|
|
2
5
|
/**
|
|
3
6
|
* Creates a batching request {@link Broker}.
|
|
4
7
|
*
|
|
5
|
-
* Backs {@link createBatchingStore}:
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* later round, so handlers must not assume any ordering between handlers.
|
|
8
|
+
* Backs {@link createBatchingStore}: the broker queues each submitted request by type and serves the queues in rounds
|
|
9
|
+
* against the supplied handlers, running all four concurrently, until every queue is empty. Requests issued
|
|
10
|
+
* mid-round, whether forwarded by a handler or freshly submitted, are served in a later round, so handlers must not
|
|
11
|
+
* assume any ordering between handlers.
|
|
10
12
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
+
* A handler throw fails the whole run: every pending {@link Deferred} is rejected with the originating error, so
|
|
14
|
+
* callers see a deterministic failure rather than a hang.
|
|
13
15
|
*
|
|
14
16
|
* @param handlers The batch handlers, one per request type. Each settles every {@link Deferred} in
|
|
15
|
-
* its batch and may issue nested {@link Broker.
|
|
17
|
+
* its batch and may issue nested {@link Broker.detail} / {@link Broker.select} calls while
|
|
16
18
|
* processing; its returned promise must settle only after every owned request, including
|
|
17
19
|
* transitively nested ones, has settled.
|
|
18
20
|
*
|
|
@@ -22,8 +24,26 @@ import type { Broker, Detect, Handler, Lookup, Modify, Select } from "./batching
|
|
|
22
24
|
*/
|
|
23
25
|
export declare function createBroker(handlers: {
|
|
24
26
|
detect: Handler<Detect>;
|
|
25
|
-
|
|
27
|
+
detail: Handler<Detail>;
|
|
26
28
|
select: Handler<Select>;
|
|
27
29
|
modify: Handler<Modify>;
|
|
28
30
|
}): Broker;
|
|
31
|
+
/**
|
|
32
|
+
* Mints the identifier of a resource created under a collection.
|
|
33
|
+
*
|
|
34
|
+
* Returns an identifier nested under `entry`, matching the identifier {@link ResourceShape.pattern | pattern} declared
|
|
35
|
+
* by the shape, if any. Each `{name}` slot is filled with the like-named member of `state`, or with an opaque segment
|
|
36
|
+
* if the state has no such member. A trailing `/*` slot is always filled with an opaque segment. A root-relative
|
|
37
|
+
* pattern is resolved against `entry`. If the shape declares no pattern, the identifier is an opaque segment under
|
|
38
|
+
* `entry`.
|
|
39
|
+
*
|
|
40
|
+
* @param entry The absolute identifier of the resource collecting the new one
|
|
41
|
+
* @param shape The shape describing the new resource, possibly deferred to break definition cycles
|
|
42
|
+
* @param state The initial state of the new resource
|
|
43
|
+
*
|
|
44
|
+
* @returns The absolute identifier of the new resource
|
|
45
|
+
*
|
|
46
|
+
* @throws {@link !RangeError RangeError} If a member filling a slot is not a single non-empty path segment
|
|
47
|
+
*/
|
|
48
|
+
export declare function mint(entry: Reference, shape: Lazy<ResourceShape>, state: Resource): Reference;
|
|
29
49
|
//# sourceMappingURL=batching.core.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"batching.core.d.ts","sourceRoot":"","sources":["../../src/clients/batching.core.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"batching.core.d.ts","sourceRoot":"","sources":["../../src/clients/batching.core.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAE7D,OAAO,EAA8C,KAAK,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAIvF,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAS,MAAM,sBAAsB,CAAC;AACvE,OAAO,KAAK,EAAE,MAAM,EAAY,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAW,MAAM,EAAE,MAAM,eAAe,CAAC;AAYxG;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE;IAEtC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;IACvB,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;IACvB,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;IACvB,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;CAEvB,GAAG,MAAM,CA+IT;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,CAAC,aAAa,CAAC,EAAE,KAAK,EAAE,QAAQ,GAAG,SAAS,CAwB7F"}
|
|
@@ -13,21 +13,29 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
import {
|
|
16
|
+
import { eager } from "@metreeca/blue/value";
|
|
17
|
+
import { error, isNumber, isString } from "@metreeca/core";
|
|
18
|
+
import { resolve } from "@metreeca/core/resource";
|
|
19
|
+
import { immutable } from "@metreeca/core/values";
|
|
20
|
+
/**
|
|
21
|
+
* The form a value read off a state has to take to stand as a path segment: non-empty, with no delimiter or
|
|
22
|
+
* whitespace.
|
|
23
|
+
*/
|
|
24
|
+
const SegmentFormat = /^[^\s/?#]+$/;
|
|
25
|
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
17
26
|
/**
|
|
18
27
|
* Creates a batching request {@link Broker}.
|
|
19
28
|
*
|
|
20
|
-
* Backs {@link createBatchingStore}:
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* later round, so handlers must not assume any ordering between handlers.
|
|
29
|
+
* Backs {@link createBatchingStore}: the broker queues each submitted request by type and serves the queues in rounds
|
|
30
|
+
* against the supplied handlers, running all four concurrently, until every queue is empty. Requests issued
|
|
31
|
+
* mid-round, whether forwarded by a handler or freshly submitted, are served in a later round, so handlers must not
|
|
32
|
+
* assume any ordering between handlers.
|
|
25
33
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
34
|
+
* A handler throw fails the whole run: every pending {@link Deferred} is rejected with the originating error, so
|
|
35
|
+
* callers see a deterministic failure rather than a hang.
|
|
28
36
|
*
|
|
29
37
|
* @param handlers The batch handlers, one per request type. Each settles every {@link Deferred} in
|
|
30
|
-
* its batch and may issue nested {@link Broker.
|
|
38
|
+
* its batch and may issue nested {@link Broker.detail} / {@link Broker.select} calls while
|
|
31
39
|
* processing; its returned promise must settle only after every owned request, including
|
|
32
40
|
* transitively nested ones, has settled.
|
|
33
41
|
*
|
|
@@ -40,12 +48,16 @@ export function createBroker(handlers) {
|
|
|
40
48
|
const items = [];
|
|
41
49
|
return [key, {
|
|
42
50
|
items,
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
// snapshots the queued requests and hands them to the handler, keeping them queued until the round
|
|
52
|
+
// is settled by the drain loop, which removes exactly the snapshot in the same step it re-checks the
|
|
53
|
+
// queues: a queue emptied any earlier would let a submission landing in between kick a second drain
|
|
54
|
+
// over the same snapshot, whose own removal would then drop whatever was queued behind it
|
|
55
|
+
round: () => {
|
|
56
|
+
const batch = items.slice();
|
|
57
|
+
return {
|
|
58
|
+
done: batch.length > 0 ? handler(batch, broker) : Promise.resolve(),
|
|
59
|
+
settle: () => { items.splice(0, batch.length); }
|
|
60
|
+
};
|
|
49
61
|
},
|
|
50
62
|
purge: error => {
|
|
51
63
|
items.forEach(deferred => deferred.reject(error));
|
|
@@ -58,8 +70,8 @@ export function createBroker(handlers) {
|
|
|
58
70
|
detect(request) {
|
|
59
71
|
return new Promise((resolve, reject) => queues.detect.items.push({ request, resolve, reject }));
|
|
60
72
|
},
|
|
61
|
-
|
|
62
|
-
return new Promise((resolve, reject) => queues.
|
|
73
|
+
detail(request) {
|
|
74
|
+
return new Promise((resolve, reject) => queues.detail.items.push({ request, resolve, reject }));
|
|
63
75
|
},
|
|
64
76
|
select(request) {
|
|
65
77
|
return new Promise((resolve, reject) => queues.select.items.push({ request, resolve, reject }));
|
|
@@ -73,8 +85,8 @@ export function createBroker(handlers) {
|
|
|
73
85
|
detect(request) {
|
|
74
86
|
return process(() => broker.detect(request));
|
|
75
87
|
},
|
|
76
|
-
|
|
77
|
-
return process(() => broker.
|
|
88
|
+
detail(request) {
|
|
89
|
+
return process(() => broker.detail(request));
|
|
78
90
|
},
|
|
79
91
|
select(request) {
|
|
80
92
|
return process(() => broker.select(request));
|
|
@@ -83,22 +95,23 @@ export function createBroker(handlers) {
|
|
|
83
95
|
return process(() => broker.modify(request));
|
|
84
96
|
}
|
|
85
97
|
});
|
|
86
|
-
// Kicks a drain only when every queue is idle: a running drain keeps its
|
|
87
|
-
// "all empty" means "no drain in flight". The kick is a microtask,
|
|
88
|
-
// submissions coalesces into one drain. The check runs before `submit`
|
|
89
|
-
// always see the request it just pushed.
|
|
98
|
+
// Kicks a drain only when every queue is idle: a running drain keeps its snapshots queued until the
|
|
99
|
+
// step that re-checks the queues, so "all empty" means "no drain in flight". The kick is a microtask,
|
|
100
|
+
// so a synchronous burst of submissions coalesces into one drain. The check runs before `submit`
|
|
101
|
+
// enqueues, or it would always see the request it just pushed.
|
|
90
102
|
function process(submit) {
|
|
91
|
-
// a running drain keeps its snapshot in the queues until the round ends, so any non-empty
|
|
92
|
-
// queue means a drain is already in flight; all-empty is the only "no drain running" state
|
|
93
103
|
if (Object.values(queues).every(queue => queue.items.length === 0)) {
|
|
94
104
|
Promise.resolve().then(async () => {
|
|
95
105
|
// Drain in rounds until every queue is quiescent, all four handlers concurrently per
|
|
96
|
-
// round.
|
|
97
|
-
//
|
|
98
|
-
// anything pushed mid-round is served next round. A throw leaves
|
|
106
|
+
// round. Every snapshot stays queued across the `await` and is removed in the same
|
|
107
|
+
// synchronous step as the next idle check, so no submission can ever find the queues empty
|
|
108
|
+
// while the loop is alive; anything pushed mid-round is served next round. A throw leaves
|
|
109
|
+
// the snapshots queued to purge.
|
|
99
110
|
try {
|
|
100
111
|
while (Object.values(queues).some(queue => queue.items.length > 0)) {
|
|
101
|
-
|
|
112
|
+
const rounds = Object.values(queues).map(queue => queue.round());
|
|
113
|
+
await Promise.all(rounds.map(round => round.done));
|
|
114
|
+
rounds.forEach(round => round.settle());
|
|
102
115
|
}
|
|
103
116
|
}
|
|
104
117
|
catch (error) {
|
|
@@ -111,4 +124,36 @@ export function createBroker(handlers) {
|
|
|
111
124
|
return submit();
|
|
112
125
|
}
|
|
113
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Mints the identifier of a resource created under a collection.
|
|
129
|
+
*
|
|
130
|
+
* Returns an identifier nested under `entry`, matching the identifier {@link ResourceShape.pattern | pattern} declared
|
|
131
|
+
* by the shape, if any. Each `{name}` slot is filled with the like-named member of `state`, or with an opaque segment
|
|
132
|
+
* if the state has no such member. A trailing `/*` slot is always filled with an opaque segment. A root-relative
|
|
133
|
+
* pattern is resolved against `entry`. If the shape declares no pattern, the identifier is an opaque segment under
|
|
134
|
+
* `entry`.
|
|
135
|
+
*
|
|
136
|
+
* @param entry The absolute identifier of the resource collecting the new one
|
|
137
|
+
* @param shape The shape describing the new resource, possibly deferred to break definition cycles
|
|
138
|
+
* @param state The initial state of the new resource
|
|
139
|
+
*
|
|
140
|
+
* @returns The absolute identifier of the new resource
|
|
141
|
+
*
|
|
142
|
+
* @throws {@link !RangeError RangeError} If a member filling a slot is not a single non-empty path segment
|
|
143
|
+
*/
|
|
144
|
+
export function mint(entry, shape, state) {
|
|
145
|
+
const { pattern } = eager(shape);
|
|
146
|
+
return pattern === undefined
|
|
147
|
+
? resolve(entry.endsWith("/") ? entry : `${entry}/`, crypto.randomUUID())
|
|
148
|
+
: resolve(entry, pattern.replace(/\{(\w*)}|(?<=\/)\*$/g, (_, name) => name === undefined ? crypto.randomUUID() : segment(name)));
|
|
149
|
+
/**
|
|
150
|
+
* Reads a pattern slot off the like-named member of the state, or fills it with an opaque segment.
|
|
151
|
+
*/
|
|
152
|
+
function segment(name) {
|
|
153
|
+
const value = state[name];
|
|
154
|
+
return value === undefined ? crypto.randomUUID()
|
|
155
|
+
: (isString(value) || isNumber(value)) && SegmentFormat.test(String(value)) ? String(value)
|
|
156
|
+
: error(new RangeError(`illegal identifier segment <${String(value)}> for slot <${name}>`));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
114
159
|
//# sourceMappingURL=batching.core.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"batching.core.js","sourceRoot":"","sources":["../../src/clients/batching.core.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"batching.core.js","sourceRoot":"","sources":["../../src/clients/batching.core.ts"],"names":[],"mappings":"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"}
|