@metreeca/keep 0.9.0 → 0.10.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 +221 -24
- package/dist/clients/batching.core.d.ts +29 -0
- package/dist/clients/batching.core.d.ts.map +1 -0
- package/dist/clients/batching.core.js +114 -0
- package/dist/clients/batching.core.js.map +1 -0
- package/dist/clients/batching.d.ts +223 -0
- package/dist/clients/batching.d.ts.map +1 -0
- package/dist/clients/batching.js +105 -0
- package/dist/clients/batching.js.map +1 -0
- package/dist/clients/validating.d.ts +38 -0
- package/dist/clients/validating.d.ts.map +1 -0
- package/dist/clients/validating.js +128 -0
- package/dist/clients/validating.js.map +1 -0
- package/dist/index.d.ts +358 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/{states/index.js → index.js} +1 -1
- package/dist/index.js.map +1 -0
- package/dist/stores/caching.d.ts +96 -0
- package/dist/stores/caching.d.ts.map +1 -0
- package/dist/stores/caching.js +311 -0
- package/dist/stores/caching.js.map +1 -0
- package/dist/stores/managing.d.ts +55 -0
- package/dist/stores/managing.d.ts.map +1 -0
- package/dist/stores/managing.js +159 -0
- package/dist/stores/managing.js.map +1 -0
- package/package.json +33 -17
- package/dist/index/index.d.ts +0 -2
- package/dist/index/index.d.ts.map +0 -1
- package/dist/index/index.js +0 -17
- package/dist/index/index.js.map +0 -1
- package/dist/states/index.d.ts +0 -2
- package/dist/states/index.d.ts.map +0 -1
- package/dist/states/index.js.map +0 -1
- package/dist/stores/index.d.ts +0 -2
- package/dist/stores/index.d.ts.map +0 -1
- package/dist/stores/index.js +0 -17
- package/dist/stores/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -2,9 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@metreeca/keep)
|
|
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
|
-
|
|
7
|
+
Defines the backend-agnostic `StoreClient` and `Store` interfaces for persisting and retrieving linked data resources as
|
|
8
|
+
shape-validated states and query projections. Resource shapes are defined using
|
|
9
|
+
[@metreeca/blue](https://github.com/metreeca/blue); resource states and retrieval templates follow the data and query
|
|
10
|
+
models defined by [@metreeca/qest](https://github.com/metreeca/qest). Actual storage is delegated to backend
|
|
11
|
+
[connector packages](https://github.com/metreeca/keep#installation).
|
|
8
12
|
|
|
9
13
|
# Installation
|
|
10
14
|
|
|
@@ -12,37 +16,230 @@
|
|
|
12
16
|
npm install @metreeca/keep
|
|
13
17
|
```
|
|
14
18
|
|
|
15
|
-
> [!WARNING]
|
|
16
|
-
>
|
|
17
|
-
> TypeScript consumers must use `"moduleResolution": "nodenext"/"node16"/"bundler"` in `tsconfig.json`.
|
|
18
|
-
> The legacy `"node"` resolver is not supported.
|
|
19
|
-
|
|
20
19
|
# Usage
|
|
21
20
|
|
|
22
21
|
> [!NOTE]
|
|
23
22
|
>
|
|
24
|
-
> This section introduces essential concepts; for complete coverage, see the
|
|
25
|
-
>
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
> This section introduces essential concepts; for complete coverage, see the
|
|
24
|
+
> [API reference](https://metreeca.github.io/keep/modules/_metreeca_keep.html).
|
|
25
|
+
|
|
26
|
+
| Module | Description |
|
|
27
|
+
|-----------------------------------------|--------------------------|
|
|
28
|
+
| [@metreeca/keep][keep] | Model-driven storage API |
|
|
29
|
+
| **Stores** | |
|
|
30
|
+
| [@metreeca/keep/caching][caching] | Caching store wrapper |
|
|
31
|
+
| [@metreeca/keep/managing][managing] | Managing store wrapper |
|
|
32
|
+
| **Store Clients** | |
|
|
33
|
+
| [@metreeca/keep/validating][validating] | Validating store wrapper |
|
|
34
|
+
| [@metreeca/keep/batching][batching] | Batching store client |
|
|
35
|
+
|
|
36
|
+
[keep]: https://metreeca.github.io/keep/modules/_metreeca_keep.html
|
|
37
|
+
|
|
38
|
+
[validating]: https://metreeca.github.io/keep/modules/_metreeca_keep.validating.html
|
|
39
|
+
|
|
40
|
+
[batching]: https://metreeca.github.io/keep/modules/_metreeca_keep.batching.html
|
|
41
|
+
|
|
42
|
+
[caching]: https://metreeca.github.io/keep/modules/_metreeca_keep.caching.html
|
|
43
|
+
|
|
44
|
+
[managing]: https://metreeca.github.io/keep/modules/_metreeca_keep.managing.html
|
|
45
|
+
|
|
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`. `model` and `state` are validated against the supplied shape; validation failures
|
|
48
|
+
reject with a `TraceError`. Network, storage, and other processing errors reject with a structured `Problem`.
|
|
49
|
+
|
|
50
|
+
## Retrieving Resources
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
// single resource retrieval
|
|
54
|
+
|
|
55
|
+
const product = await store.lookup({
|
|
56
|
+
entry: "http://example.com/products/1",
|
|
57
|
+
shape: ProductShape,
|
|
58
|
+
model: {
|
|
59
|
+
name: "",
|
|
60
|
+
price: 0,
|
|
61
|
+
vendor: {
|
|
62
|
+
id: "",
|
|
63
|
+
name: ""
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// collection retrieval with filtering, ordering, and pagination
|
|
69
|
+
|
|
70
|
+
const catalog = await store.lookup({
|
|
71
|
+
entry: "http://example.com/products/",
|
|
72
|
+
shape: ProductShape,
|
|
73
|
+
model: {
|
|
74
|
+
products: [{
|
|
75
|
+
id: "",
|
|
76
|
+
name: "",
|
|
77
|
+
price: 0,
|
|
78
|
+
">=price": 50, // price ≥ 50
|
|
79
|
+
"~name": "widget", // name contains "widget"
|
|
80
|
+
"^price": 1, // sort by price ascending
|
|
81
|
+
"@": 0, // offset
|
|
82
|
+
"#": 25 // limit
|
|
83
|
+
}]
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
```
|
|
29
87
|
|
|
30
|
-
|
|
88
|
+
## Creating and Updating Resources
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
await store.create({
|
|
92
|
+
entry: "http://example.com/products/42", shape: ProductShape, state: {
|
|
93
|
+
id: "http://example.com/products/42",
|
|
94
|
+
name: "Widget",
|
|
95
|
+
price: 29.99,
|
|
96
|
+
vendor: "http://example.com/vendors/acme"
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
await store.update({
|
|
101
|
+
entry: "http://example.com/products/42", shape: ProductShape, state: {
|
|
102
|
+
id: "http://example.com/products/42",
|
|
103
|
+
name: "Widget",
|
|
104
|
+
price: 39.99,
|
|
105
|
+
vendor: "http://example.com/vendors/acme"
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
```
|
|
31
109
|
|
|
32
|
-
|
|
110
|
+
## Deleting Resources
|
|
33
111
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
112
|
+
```typescript
|
|
113
|
+
await store.delete({ entry: "http://example.com/products/42", shape: ProductShape });
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Loading Data
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
// unconditionally upsert (create or replace)
|
|
120
|
+
|
|
121
|
+
await store.insert({
|
|
122
|
+
entry: "http://example.com/products/42", shape: ProductShape, state: {
|
|
123
|
+
id: "http://example.com/products/42",
|
|
124
|
+
name: "Widget",
|
|
125
|
+
price: 39.99,
|
|
126
|
+
vendor: "http://example.com/vendors/acme"
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// unconditionally remove (silently succeeds if absent)
|
|
131
|
+
|
|
132
|
+
await store.remove({ entry: "http://example.com/products/42", shape: ProductShape });
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Observing Mutations
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
const unsubscribe = store.observe(mutations => {
|
|
139
|
+
for (const [id, exists] of Object.entries(mutations)) {
|
|
140
|
+
console.log(exists ? "upserted" : "removed", id);
|
|
141
|
+
}
|
|
142
|
+
}, "http://example.com/products/");
|
|
143
|
+
|
|
144
|
+
unsubscribe(); // stop receiving events
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The second argument scopes the observer to the given resources and their descendants; omit it to receive all mutations,
|
|
148
|
+
pass an empty array to detach the observer.
|
|
149
|
+
|
|
150
|
+
## Executing Transactions
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
await store.execute(async store => {
|
|
154
|
+
await store.create({ entry: product.id, shape: ProductShape, state: product });
|
|
155
|
+
await store.update({ entry: inventory.id, shape: InventoryShape, state: inventory });
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Cross-backend isolation semantics, concurrency models, and the buffer-and-flush emulation pattern are documented in the
|
|
160
|
+
[Transaction Design](https://metreeca.github.io/keep/documents/_metreeca_keep.Transaction_Design.html) companion
|
|
161
|
+
document.
|
|
162
|
+
|
|
163
|
+
# Implementing Connectors
|
|
164
|
+
|
|
165
|
+
A backend connector implements the bare `StoreClient` interface against its native primitives, then wraps it with the
|
|
166
|
+
`createValidatingStore` helper from
|
|
167
|
+
[@metreeca/keep/validating](https://metreeca.github.io/keep/modules/_metreeca_keep.validating.html) for shape-driven
|
|
168
|
+
input validation and `createManagingStore` from
|
|
169
|
+
[@metreeca/keep/managing](https://metreeca.github.io/keep/modules/_metreeca_keep.managing.html) for mutation events,
|
|
170
|
+
transactional execution, and lifecycle:
|
|
37
171
|
|
|
38
|
-
|
|
172
|
+
```typescript
|
|
173
|
+
import type { StoreClient, Store } from "@metreeca/keep";
|
|
174
|
+
import { createManagingStore } from "@metreeca/keep/managing";
|
|
175
|
+
import { createValidatingStore } from "@metreeca/keep/validating";
|
|
176
|
+
|
|
177
|
+
function createMyStore(): Store {
|
|
178
|
+
|
|
179
|
+
const backend: StoreClient = {
|
|
180
|
+
|
|
181
|
+
lookup({ entry, shape, model }) { /* query the backend, return matching data or undefined */ },
|
|
182
|
+
|
|
183
|
+
create({ entry, shape, state }) { /* create if absent, return entry or undefined */ },
|
|
184
|
+
update({ entry, shape, state }) { /* update if present, return entry or undefined */ },
|
|
185
|
+
delete({ entry, shape }) { /* delete if present, return entry or undefined */ },
|
|
186
|
+
|
|
187
|
+
insert({ entry, shape, state }) { /* upsert unconditionally, return entry */ },
|
|
188
|
+
remove({ entry, shape }) { /* remove unconditionally, return entry */ }
|
|
189
|
+
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
return createManagingStore(createValidatingStore(backend, { trusted: true }), {
|
|
193
|
+
|
|
194
|
+
execute: task => { /* run task within a backend transaction */ },
|
|
195
|
+
close: () => { /* release connections */ }
|
|
196
|
+
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Pass `trusted: true` to `createValidatingStore` when the backend is trusted to deliver shape-conforming data, so
|
|
203
|
+
`lookup` responses skip the redundant outbound validation pass. Supply `execute` to `createManagingStore` to route every
|
|
204
|
+
standalone call and the entire `execute` task body through the backend's transaction primitive; omit it for
|
|
205
|
+
non-transactional backends and the wrapper degrades to per-call atomicity. `close` defaults to a no-op.
|
|
206
|
+
|
|
207
|
+
All errors reach the caller as promise rejections through the unified Store error channel — `RangeError` for malformed
|
|
208
|
+
entries, `TraceError` for shape validation failures, `Problem` for network, storage, or other processing failures.
|
|
209
|
+
Connectors MUST preserve this contract: convert backend-specific exceptions into `Problem` rejections and let validation
|
|
210
|
+
rejections propagate untouched.
|
|
211
|
+
|
|
212
|
+
Each connector MUST document its supported transaction isolation level and mutation event scope per the conventions
|
|
213
|
+
defined in the [Transaction Design](https://metreeca.github.io/keep/documents/_metreeca_keep.Transaction_Design.html)
|
|
214
|
+
companion document.
|
|
215
|
+
|
|
216
|
+
## Testing
|
|
217
|
+
|
|
218
|
+
Use [@metreeca/keep-suite](https://github.com/metreeca/keep/tree/main/packages/keep-suite) to run the full conformance
|
|
219
|
+
suite against your connector:
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
import { testStore } from "@metreeca/keep-suite";
|
|
223
|
+
import { describe } from "vitest";
|
|
224
|
+
|
|
225
|
+
describe("my-store", () => testStore({
|
|
226
|
+
|
|
227
|
+
build: () => createMyStore(), // create a store with schema but no data
|
|
228
|
+
|
|
229
|
+
contains: id => { /* true if the resource has any stored data */ },
|
|
230
|
+
includes: (resource, shape) => { /* true if every fact described by `resource` is present */ },
|
|
231
|
+
excludes: (resource, shape) => { /* true if every fact described by `resource` is absent */ },
|
|
232
|
+
|
|
233
|
+
populate: () => { /* clear and reload the sample dataset */ },
|
|
234
|
+
generate: (sample, shape) => { /* insert an isolated copy of `sample` with a unique id */ },
|
|
235
|
+
|
|
236
|
+
close: () => { /* optional: release store resources after all tests */ }
|
|
237
|
+
|
|
238
|
+
}));
|
|
239
|
+
```
|
|
39
240
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
| @metreeca/qest | Data types for client-driven, queryable REST/JSON APIs |
|
|
43
|
-
| @metreeca/blue _(upcoming)_ | Shape-based validation for resources, patches, and queries |
|
|
44
|
-
| **@metreeca/keep** | Shape-driven storage framework with pluggable adapters |
|
|
45
|
-
| @metreeca/gate _(upcoming)_ | Shape-driven REST/JSON API publishing |
|
|
241
|
+
Filter to a subset of sub-suites with the `match` option (for example, `["Retrieve", "PersistCreate"]`); see the
|
|
242
|
+
[API reference](https://metreeca.github.io/keep/modules/_metreeca_keep-suite.html) for the full list.
|
|
46
243
|
|
|
47
244
|
# Support
|
|
48
245
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Broker, Detect, Handler, Lookup, Modify, Select } from "./batching.js";
|
|
2
|
+
/**
|
|
3
|
+
* Creates a batching request {@link Broker}.
|
|
4
|
+
*
|
|
5
|
+
* Backs {@link createBatchingStore}: returns a broker that queues each submitted request by type and
|
|
6
|
+
* drives the queues to completion against the supplied handlers. The first submission kicks a drain
|
|
7
|
+
* loop that runs all four handlers concurrently, round by round, until every queue is quiescent;
|
|
8
|
+
* requests issued mid-round, whether forwarded by a handler or freshly submitted, are served on a
|
|
9
|
+
* later round, so handlers must not assume any ordering between handlers.
|
|
10
|
+
*
|
|
11
|
+
* Handler errors poison the run: every still-pending {@link Deferred} is rejected with the
|
|
12
|
+
* originating error, so callers see a deterministic failure rather than a hang.
|
|
13
|
+
*
|
|
14
|
+
* @param handlers The batch handlers, one per request type. Each settles every {@link Deferred} in
|
|
15
|
+
* its batch and may issue nested {@link Broker.lookup} / {@link Broker.select} calls while
|
|
16
|
+
* processing; its returned promise must settle only after every owned request, including
|
|
17
|
+
* transitively nested ones, has settled.
|
|
18
|
+
*
|
|
19
|
+
* @returns An immutable {@link Broker} surface for enqueuing requests and receiving their
|
|
20
|
+
* materialised results; each call returns a promise that settles when the relevant batch
|
|
21
|
+
* completes
|
|
22
|
+
*/
|
|
23
|
+
export declare function createBroker(handlers: {
|
|
24
|
+
detect: Handler<Detect>;
|
|
25
|
+
lookup: Handler<Lookup>;
|
|
26
|
+
select: Handler<Select>;
|
|
27
|
+
modify: Handler<Modify>;
|
|
28
|
+
}): Broker;
|
|
29
|
+
//# sourceMappingURL=batching.core.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batching.core.d.ts","sourceRoot":"","sources":["../../src/clients/batching.core.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,MAAM,EAAY,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAW,MAAM,EAAE,MAAM,eAAe,CAAC;AAExG;;;;;;;;;;;;;;;;;;;;GAoBG;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,CAgIT"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright © 2025-2026 Metreeca srl
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import { immutable } from "@metreeca/core/structures";
|
|
17
|
+
/**
|
|
18
|
+
* Creates a batching request {@link Broker}.
|
|
19
|
+
*
|
|
20
|
+
* Backs {@link createBatchingStore}: returns a broker that queues each submitted request by type and
|
|
21
|
+
* drives the queues to completion against the supplied handlers. The first submission kicks a drain
|
|
22
|
+
* loop that runs all four handlers concurrently, round by round, until every queue is quiescent;
|
|
23
|
+
* requests issued mid-round, whether forwarded by a handler or freshly submitted, are served on a
|
|
24
|
+
* later round, so handlers must not assume any ordering between handlers.
|
|
25
|
+
*
|
|
26
|
+
* Handler errors poison the run: every still-pending {@link Deferred} is rejected with the
|
|
27
|
+
* originating error, so callers see a deterministic failure rather than a hang.
|
|
28
|
+
*
|
|
29
|
+
* @param handlers The batch handlers, one per request type. Each settles every {@link Deferred} in
|
|
30
|
+
* its batch and may issue nested {@link Broker.lookup} / {@link Broker.select} calls while
|
|
31
|
+
* processing; its returned promise must settle only after every owned request, including
|
|
32
|
+
* transitively nested ones, has settled.
|
|
33
|
+
*
|
|
34
|
+
* @returns An immutable {@link Broker} surface for enqueuing requests and receiving their
|
|
35
|
+
* materialised results; each call returns a promise that settles when the relevant batch
|
|
36
|
+
* completes
|
|
37
|
+
*/
|
|
38
|
+
export function createBroker(handlers) {
|
|
39
|
+
const queues = Object.fromEntries(Object.entries(handlers).map(([key, handler]) => {
|
|
40
|
+
const items = [];
|
|
41
|
+
return [key, {
|
|
42
|
+
items,
|
|
43
|
+
drain: async () => {
|
|
44
|
+
if (items.length > 0) {
|
|
45
|
+
const batch = items.slice();
|
|
46
|
+
await handler(batch, broker); // ;(cast) erased to the handler's union parameter
|
|
47
|
+
items.splice(0, batch.length);
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
purge: error => {
|
|
51
|
+
items.forEach(deferred => deferred.reject(error));
|
|
52
|
+
items.length = 0;
|
|
53
|
+
}
|
|
54
|
+
}];
|
|
55
|
+
}));
|
|
56
|
+
// Inner surface handed to the handlers: nested calls enqueue without kicking a drain.
|
|
57
|
+
const broker = immutable({
|
|
58
|
+
detect(request) {
|
|
59
|
+
return new Promise((resolve, reject) => queues.detect.items.push({ request, resolve, reject }));
|
|
60
|
+
},
|
|
61
|
+
lookup(request) {
|
|
62
|
+
return new Promise((resolve, reject) => queues.lookup.items.push({ request, resolve, reject }));
|
|
63
|
+
},
|
|
64
|
+
select(request) {
|
|
65
|
+
return new Promise((resolve, reject) => queues.select.items.push({ request, resolve, reject }));
|
|
66
|
+
},
|
|
67
|
+
modify(request) {
|
|
68
|
+
return new Promise((resolve, reject) => queues.modify.items.push({ request, resolve, reject }));
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
// Public surface: every call enqueues through the inner broker and kicks a drain via `process`.
|
|
72
|
+
return immutable({
|
|
73
|
+
detect(request) {
|
|
74
|
+
return process(() => broker.detect(request));
|
|
75
|
+
},
|
|
76
|
+
lookup(request) {
|
|
77
|
+
return process(() => broker.lookup(request));
|
|
78
|
+
},
|
|
79
|
+
select(request) {
|
|
80
|
+
return process(() => broker.select(request));
|
|
81
|
+
},
|
|
82
|
+
modify(request) {
|
|
83
|
+
return process(() => broker.modify(request));
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
// Kicks a drain only when every queue is idle: a running drain keeps its snapshot queued, so
|
|
87
|
+
// "all empty" means "no drain in flight". The kick is a microtask, so a synchronous burst of
|
|
88
|
+
// submissions coalesces into one drain. The check runs before `submit` enqueues, or it would
|
|
89
|
+
// always see the request it just pushed.
|
|
90
|
+
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
|
+
if (Object.values(queues).every(queue => queue.items.length === 0)) {
|
|
94
|
+
Promise.resolve().then(async () => {
|
|
95
|
+
// Drain in rounds until every queue is quiescent, all four handlers concurrently per
|
|
96
|
+
// round. A handler keeps its snapshot in the queue across the `await` (so the idle-check
|
|
97
|
+
// still sees work and won't kick a second drain) and removes exactly it on success;
|
|
98
|
+
// anything pushed mid-round is served next round. A throw leaves the snapshot queued to purge.
|
|
99
|
+
try {
|
|
100
|
+
while (Object.values(queues).some(queue => queue.items.length > 0)) {
|
|
101
|
+
await Promise.all(Object.values(queues).map(queue => queue.drain()));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
// A handler throw poisons the run: purge every queue so pending callers reject
|
|
106
|
+
// instead of hanging; already-settled entries no-op.
|
|
107
|
+
Object.values(queues).forEach(queue => queue.purge(error));
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
return submit();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=batching.core.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batching.core.js","sourceRoot":"","sources":["../../src/clients/batching.core.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Batching store client.
|
|
3
|
+
*
|
|
4
|
+
* Builds a {@link StoreClient} that coalesces concurrent requests of the same kind into a single
|
|
5
|
+
* batch, so a connector can serve many requests with one backend round-trip. A connector author
|
|
6
|
+
* supplies one {@link Handler} per request type to {@link createBatchingStore}; the returned client
|
|
7
|
+
* routes every call through a {@link Broker} that queues like-typed requests and hands each handler
|
|
8
|
+
* the whole batch it accumulated.
|
|
9
|
+
*
|
|
10
|
+
* Four request types partition the work, each with its own handler:
|
|
11
|
+
*
|
|
12
|
+
* - **`detect`** — probes whether each {@link Detect} entry exists in the store
|
|
13
|
+
* - **`lookup`** — materialises each {@link Lookup} against its `model`; multi-valued slots reached
|
|
14
|
+
* by the model may be delegated to the `select` handler via {@link Broker.select}
|
|
15
|
+
* - **`select`** — materialises each {@link Select} collection (see {@link Query} for the admitted
|
|
16
|
+
* forms); element resources reached by the query may be delegated to the `lookup` handler via
|
|
17
|
+
* {@link Broker.lookup}
|
|
18
|
+
* - **`modify`** — creates, updates, or deletes each {@link Modify} target
|
|
19
|
+
*
|
|
20
|
+
* Retrieval is driven by the requested `model` and `query`, not by the stored graph: `lookup` and
|
|
21
|
+
* `select` hand work back and forth, `lookup` spawning a `select` for each multi-valued slot its
|
|
22
|
+
* model reaches and `select` spawning a `lookup` for each element resource its query reaches. The
|
|
23
|
+
* handover recurses through this alternation and bottoms out where the `model` or `query` stops
|
|
24
|
+
* nesting, so each request descends exactly as deep as it asks for, however deep the underlying
|
|
25
|
+
* graph runs.
|
|
26
|
+
*
|
|
27
|
+
* Handlers run concurrently and forward nested requests to one another through the {@link Broker}, so
|
|
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.
|
|
31
|
+
*
|
|
32
|
+
* @module
|
|
33
|
+
*/
|
|
34
|
+
import type { Property, ResourceShape } from "@metreeca/blue/resource";
|
|
35
|
+
import type { Lazy } from "@metreeca/core";
|
|
36
|
+
import type { Tag } from "@metreeca/core/language";
|
|
37
|
+
import type { Reference, Resource } from "@metreeca/qest/resource";
|
|
38
|
+
import type { Instance, Query, Template } from "@metreeca/qest/template";
|
|
39
|
+
import type { StoreClient } from "../index.js";
|
|
40
|
+
/**
|
|
41
|
+
* Requests accepted by {@link Broker}.
|
|
42
|
+
*/
|
|
43
|
+
export type Request = Detect | Lookup | Select | Modify;
|
|
44
|
+
/**
|
|
45
|
+
* Result of running a {@link Request}, narrowed by request variant:
|
|
46
|
+
*
|
|
47
|
+
* - a {@link Detect} resolves to a `boolean` existence flag
|
|
48
|
+
* - a {@link Lookup} resolves to its materialised resource {@link Instance}
|
|
49
|
+
* - a {@link Select} resolves to its materialised collection {@link Instance}
|
|
50
|
+
* - a {@link Modify} resolves to its mutated entry's {@link Reference}
|
|
51
|
+
*
|
|
52
|
+
* @typeParam R The request whose result type is selected
|
|
53
|
+
*/
|
|
54
|
+
export type Response<R extends Request> = R extends Select<infer T> ? Instance<T> : R extends Lookup<infer T> ? Instance<T> : R extends Modify ? Reference : R extends Detect ? boolean : never;
|
|
55
|
+
/**
|
|
56
|
+
* Resource existence request.
|
|
57
|
+
*
|
|
58
|
+
* Carries the `entry` to probe; resolves to whether that entry exists in the store.
|
|
59
|
+
*/
|
|
60
|
+
export type Detect = {
|
|
61
|
+
readonly entry: Reference;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Resource retrieval request.
|
|
65
|
+
*
|
|
66
|
+
* Carries a single resource (`entry` of `shape`), the `model` against which to materialise it, and
|
|
67
|
+
* the `locale` priority driving language negotiation for its localised content (§6.2). Resolves to
|
|
68
|
+
* the materialised resource.
|
|
69
|
+
*
|
|
70
|
+
* @typeParam T The resource model selecting the result shape
|
|
71
|
+
*/
|
|
72
|
+
export type Lookup<T extends Template = Template> = {
|
|
73
|
+
readonly entry: Reference;
|
|
74
|
+
readonly shape: Lazy<ResourceShape>;
|
|
75
|
+
readonly model: T;
|
|
76
|
+
readonly locale: readonly Tag[];
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Collection retrieval request.
|
|
80
|
+
*
|
|
81
|
+
* Carries a single multi-valued property (`field` on `entry` of `shape`), the `query` describing the
|
|
82
|
+
* shape of its values (one of the {@link Query} arms), and the `locale` priority driving language
|
|
83
|
+
* negotiation for its localised content (§6.2). Resolves to the materialised collection.
|
|
84
|
+
*
|
|
85
|
+
* @typeParam T The collection query selecting the result shape
|
|
86
|
+
*/
|
|
87
|
+
export type Select<T extends Query = Query> = {
|
|
88
|
+
readonly entry: Reference;
|
|
89
|
+
readonly shape: Lazy<ResourceShape>;
|
|
90
|
+
readonly field: Property;
|
|
91
|
+
readonly query: T;
|
|
92
|
+
readonly locale: readonly Tag[];
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Resource mutation request.
|
|
96
|
+
*
|
|
97
|
+
* Carries the `entry` to mutate (of `shape`) and the target `state` to persist: a present `state`
|
|
98
|
+
* creates or updates the entry, an omitted `state` deletes it. Resolves to the mutated entry's
|
|
99
|
+
* {@link Reference}.
|
|
100
|
+
*/
|
|
101
|
+
export type Modify = {
|
|
102
|
+
readonly entry: Reference;
|
|
103
|
+
readonly shape: Lazy<ResourceShape>;
|
|
104
|
+
readonly state?: Resource;
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* Batch handler for a single request type.
|
|
108
|
+
*
|
|
109
|
+
* Supplied by the connector author to {@link createBatchingStore}, one per request type. Receives the
|
|
110
|
+
* whole batch the {@link Broker} has accumulated for this type, together with the broker for issuing
|
|
111
|
+
* nested requests, and settles each {@link Deferred} in the batch with its result or error. The
|
|
112
|
+
* returned promise must resolve only once every request the handler owns has settled, including any
|
|
113
|
+
* nested requests issued through the broker.
|
|
114
|
+
*
|
|
115
|
+
* The broker never dispatches an empty batch, so the handler need not guard against one.
|
|
116
|
+
*
|
|
117
|
+
* @typeParam R The request variant handled
|
|
118
|
+
*/
|
|
119
|
+
export type Handler<R extends Request> = {
|
|
120
|
+
(batch: readonly Deferred<R>[], broker: Broker): Promise<void>;
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* A queued request paired with its promise-resolution hooks.
|
|
124
|
+
*
|
|
125
|
+
* Each batch a {@link Handler} receives is an array of `Deferred` entries. The handler reads each
|
|
126
|
+
* `request`, then calls `resolve` with its result (typed by {@link Response | Response<R>}) or
|
|
127
|
+
* `reject` with an error, once the backend round-trip and any nested requests issued through the
|
|
128
|
+
* {@link Broker} have settled.
|
|
129
|
+
*
|
|
130
|
+
* @typeParam R The queued request variant: a {@link Detect} probe, a {@link Lookup} resource fetch,
|
|
131
|
+
* a {@link Select} collection fetch, or a {@link Modify} mutation
|
|
132
|
+
*/
|
|
133
|
+
export type Deferred<R extends Request> = {
|
|
134
|
+
readonly request: R;
|
|
135
|
+
resolve(value: Response<R>): void;
|
|
136
|
+
reject(error: unknown): void;
|
|
137
|
+
};
|
|
138
|
+
/**
|
|
139
|
+
* Request-submission surface over the batching {@link Handler | handlers}.
|
|
140
|
+
*
|
|
141
|
+
* Brokers each store request (detection, lookup, selection, modification) to its handler: a call
|
|
142
|
+
* enqueues the request and returns a promise that settles with the request's result. Held both by
|
|
143
|
+
* outside callers, as the entry point, and by handlers, to delegate nested work to a sibling
|
|
144
|
+
* handler.
|
|
145
|
+
*/
|
|
146
|
+
export type Broker = {
|
|
147
|
+
/**
|
|
148
|
+
* Enqueue a {@link Detect} request.
|
|
149
|
+
*
|
|
150
|
+
* @param request The entry to probe
|
|
151
|
+
*
|
|
152
|
+
* @returns A promise resolving to `true` when the request's entry exists in the store; `false`
|
|
153
|
+
* otherwise
|
|
154
|
+
*/
|
|
155
|
+
detect(request: Detect): Promise<boolean>;
|
|
156
|
+
/**
|
|
157
|
+
* Enqueue a {@link Lookup} request.
|
|
158
|
+
*
|
|
159
|
+
* Carries no existence precondition: an entry with no stored state resolves to an empty instance of
|
|
160
|
+
* the request's `model` rather than rejecting.
|
|
161
|
+
*
|
|
162
|
+
* @param request The resource and model to materialise
|
|
163
|
+
*
|
|
164
|
+
* @returns A promise resolving to the materialised {@link Instance} of the request's `model` (the
|
|
165
|
+
* resource value), settled once the owning batch and any nested promises needed to assemble its
|
|
166
|
+
* value have resolved
|
|
167
|
+
*/
|
|
168
|
+
lookup<T extends Template>(request: Lookup<T>): Promise<Instance<T>>;
|
|
169
|
+
/**
|
|
170
|
+
* Enqueue a {@link Select} request.
|
|
171
|
+
*
|
|
172
|
+
* Carries no existence precondition: an entry with no matching members resolves to an empty
|
|
173
|
+
* collection rather than rejecting.
|
|
174
|
+
*
|
|
175
|
+
* @param request The property and query to materialise
|
|
176
|
+
*
|
|
177
|
+
* @returns A promise resolving to the materialised {@link Instance} of the request's `query` (one
|
|
178
|
+
* of the tuple-wrapped {@link Query} arms), settled once the owning batch and any nested promises
|
|
179
|
+
* needed to assemble its value have resolved
|
|
180
|
+
*/
|
|
181
|
+
select<T extends Query>(request: Select<T>): Promise<Instance<T>>;
|
|
182
|
+
/**
|
|
183
|
+
* Enqueue a {@link Modify} request.
|
|
184
|
+
*
|
|
185
|
+
* Mutates unconditionally, with no existence precondition: a present `state` upserts the entry,
|
|
186
|
+
* creating it when absent; an omitted `state` deletes it leniently, resolving as a no-op when
|
|
187
|
+
* absent.
|
|
188
|
+
*
|
|
189
|
+
* @param request The entry and target state to persist
|
|
190
|
+
*
|
|
191
|
+
* @returns A promise resolving to the mutated entry's {@link Reference}, settled once the owning
|
|
192
|
+
* batch has completed
|
|
193
|
+
*/
|
|
194
|
+
modify(request: Modify): Promise<Reference>;
|
|
195
|
+
};
|
|
196
|
+
/**
|
|
197
|
+
* Creates a batching store client backed by a set of request handlers.
|
|
198
|
+
*
|
|
199
|
+
* Routes every {@link StoreClient} call through a {@link Broker} that coalesces concurrent requests
|
|
200
|
+
* into batches and dispatches each batch through the matching `handlers` entry. Adds conditional CRUD
|
|
201
|
+
* semantics on top of the handlers: existence is probed through the `detect` handler before a create,
|
|
202
|
+
* update, or delete is delegated to `modify`, sparing each handler that bookkeeping.
|
|
203
|
+
*
|
|
204
|
+
* The `modify` handler is additionally wrapped so the batching layer, not the storage handler,
|
|
205
|
+
* enforces the §4.1 state-`id` constraint: a request whose `state` carries an `id` other than its
|
|
206
|
+
* `entry` is rejected on its own before dispatch, and a backend failure is settled as a rejection
|
|
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.
|
|
209
|
+
*
|
|
210
|
+
* @param handlers The batch handlers, one per request type, dispatching detect, lookup, select, and
|
|
211
|
+
* modify requests
|
|
212
|
+
*
|
|
213
|
+
* @returns An immutable {@link StoreClient} that batches its requests through the broker
|
|
214
|
+
*
|
|
215
|
+
* @throws {RangeError} If a create or update `state` carries an `id` that differs from its `entry`
|
|
216
|
+
*/
|
|
217
|
+
export declare function createBatchingStore(handlers: {
|
|
218
|
+
detect: Handler<Detect>;
|
|
219
|
+
lookup: Handler<Lookup>;
|
|
220
|
+
select: Handler<Select>;
|
|
221
|
+
modify: Handler<Modify>;
|
|
222
|
+
}): StoreClient;
|
|
223
|
+
//# sourceMappingURL=batching.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batching.d.ts","sourceRoot":"","sources":["../../src/clients/batching.ts"],"names":[],"mappings":"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"}
|