@metreeca/keep 0.9.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.
Files changed (37) hide show
  1. package/README.md +228 -24
  2. package/dist/clients/batching.core.d.ts +49 -0
  3. package/dist/clients/batching.core.d.ts.map +1 -0
  4. package/dist/clients/batching.core.js +159 -0
  5. package/dist/clients/batching.core.js.map +1 -0
  6. package/dist/clients/batching.d.ts +218 -0
  7. package/dist/clients/batching.d.ts.map +1 -0
  8. package/dist/clients/batching.js +109 -0
  9. package/dist/clients/batching.js.map +1 -0
  10. package/dist/clients/validating.d.ts +41 -0
  11. package/dist/clients/validating.d.ts.map +1 -0
  12. package/dist/clients/validating.js +150 -0
  13. package/dist/clients/validating.js.map +1 -0
  14. package/dist/index.d.ts +464 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/{states/index.js → index.js} +1 -1
  17. package/dist/index.js.map +1 -0
  18. package/dist/stores/caching.d.ts +93 -0
  19. package/dist/stores/caching.d.ts.map +1 -0
  20. package/dist/stores/caching.js +320 -0
  21. package/dist/stores/caching.js.map +1 -0
  22. package/dist/stores/managing.d.ts +57 -0
  23. package/dist/stores/managing.d.ts.map +1 -0
  24. package/dist/stores/managing.js +147 -0
  25. package/dist/stores/managing.js.map +1 -0
  26. package/package.json +34 -18
  27. package/dist/index/index.d.ts +0 -2
  28. package/dist/index/index.d.ts.map +0 -1
  29. package/dist/index/index.js +0 -17
  30. package/dist/index/index.js.map +0 -1
  31. package/dist/states/index.d.ts +0 -2
  32. package/dist/states/index.d.ts.map +0 -1
  33. package/dist/states/index.js.map +0 -1
  34. package/dist/stores/index.d.ts +0 -2
  35. package/dist/stores/index.d.ts.map +0 -1
  36. package/dist/stores/index.js +0 -17
  37. package/dist/stores/index.js.map +0 -1
package/README.md CHANGED
@@ -2,9 +2,13 @@
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/@metreeca/keep)](https://www.npmjs.com/package/@metreeca/keep)
4
4
 
5
- {TBD: one-line description}
5
+ Core model-driven storage API for the [@metreeca/keep](https://github.com/metreeca/keep) linked data storage framework.
6
6
 
7
- {TBD: value proposition and key capabilities}
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
+ [connector packages](https://github.com/metreeca/keep#installation).
8
12
 
9
13
  # Installation
10
14
 
@@ -12,37 +16,237 @@
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 API reference:
25
- >
26
- > | Module | Description |
27
- > |--------|-------------|
28
- > | {TBD} | {TBD} |
23
+ > This section introduces essential concepts; for complete coverage, see the
24
+ > [API reference](https://metreeca.github.io/keep/modules/_metreeca_keep.html).
29
25
 
30
- {TBD: usage overview and examples}
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 |
31
35
 
32
- # Integrated Ecosystem
36
+ [keep]: https://metreeca.github.io/keep/modules/_metreeca_keep.html
33
37
 
34
- > [!IMPORTANT]
35
- >
36
- > **@metreeca/keep** defines {TBD}; applications are free to {TBD}.
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`. `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`.
51
+
52
+ ## Retrieving Resources
53
+
54
+ ```typescript
55
+ // single resource retrieval
56
+
57
+ const product = await store.lookup({
58
+ entry: "http://example.com/products/1",
59
+ shape: ProductShape,
60
+ model: {
61
+ name: {},
62
+ price: {},
63
+ vendor: {
64
+ id: {},
65
+ name: {}
66
+ }
67
+ }
68
+ });
69
+
70
+ // collection retrieval with filtering, ordering, and pagination
71
+
72
+ const catalogue = await store.lookup({
73
+ entry: "http://example.com/products/",
74
+ shape: CatalogueShape,
75
+ model: {
76
+ products: {
77
+ id: {},
78
+ name: {},
79
+ price: {},
80
+ ">=price": 50, // price ≥ 50
81
+ "~name": "widget", // name contains "widget"
82
+ "^price": 1, // sort by price ascending
83
+ "@": 0, // offset
84
+ "#": 25 // limit
85
+ }
86
+ }
87
+ });
88
+ ```
89
+
90
+ ## Creating and Updating Resources
91
+
92
+ ```typescript
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: {
97
+ name: "Widget",
98
+ price: 29.99,
99
+ vendor: "http://example.com/vendors/acme"
100
+ }
101
+ });
102
+
103
+ await store.update({
104
+ entry: "http://example.com/products/42", shape: ProductShape, state: {
105
+ id: "http://example.com/products/42",
106
+ name: "Widget",
107
+ price: 39.99,
108
+ vendor: "http://example.com/vendors/acme"
109
+ }
110
+ });
111
+ ```
112
+
113
+ ## Deleting Resources
114
+
115
+ ```typescript
116
+ await store.delete({ entry: "http://example.com/products/42", shape: ProductShape });
117
+ ```
118
+
119
+ ## Loading Data
120
+
121
+ ```typescript
122
+ // unconditionally upsert (create or replace)
123
+
124
+ await store.insert({
125
+ entry: "http://example.com/products/42", shape: ProductShape, state: {
126
+ id: "http://example.com/products/42",
127
+ name: "Widget",
128
+ price: 39.99,
129
+ vendor: "http://example.com/vendors/acme"
130
+ }
131
+ });
132
+
133
+ // unconditionally remove (silently succeeds if absent)
134
+
135
+ await store.remove({ entry: "http://example.com/products/42", shape: ProductShape });
136
+ ```
137
+
138
+ ## Observing Mutations
139
+
140
+ ```typescript
141
+ const unsubscribe = store.observe(mutations => {
142
+ for (const [id, exists] of Object.entries(mutations)) {
143
+ console.log(exists ? "upserted" : "removed", id);
144
+ }
145
+ }, "http://example.com/products/");
146
+
147
+ unsubscribe(); // stop receiving events
148
+ ```
37
149
 
38
- **@metreeca/keep** is part of an integrated ecosystem for rapid application development:
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.
152
+
153
+ ## Executing Transactions
154
+
155
+ ```typescript
156
+ await store.execute(async store => {
157
+ await store.create({ entry: catalogue, shape: CatalogueShape, model: { products: {} }, state: product });
158
+ await store.update({ entry: inventory.id, shape: InventoryShape, state: inventory });
159
+ });
160
+ ```
161
+
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.
165
+
166
+ # Implementing Connectors
167
+
168
+ A backend connector implements the bare `StoreClient` interface against its native primitives, then wraps it with the
169
+ `createValidatingStore` helper from
170
+ [@metreeca/keep/validating](https://metreeca.github.io/keep/modules/_metreeca_keep.validating.html) for shape-driven
171
+ input validation and `createManagingStore` from
172
+ [@metreeca/keep/managing](https://metreeca.github.io/keep/modules/_metreeca_keep.managing.html) for mutation events,
173
+ transactional execution, and lifecycle:
174
+
175
+ ```typescript
176
+ import type { StoreClient, Store } from "@metreeca/keep";
177
+ import { createManagingStore } from "@metreeca/keep/managing";
178
+ import { createValidatingStore } from "@metreeca/keep/validating";
179
+
180
+ function createMyStore(): Store {
181
+
182
+ const backend: StoreClient = {
183
+
184
+ lookup({ entry, shape, model }, scope) { /* query the backend, return matching data or undefined */ },
185
+
186
+ create({ entry, shape, model, state }) { /* create under the collection, return the new entry or undefined */ },
187
+ update({ entry, shape, state }) { /* update if present, return entry or undefined */ },
188
+ delete({ entry, shape }) { /* delete if present, return entry or undefined */ },
189
+
190
+ insert({ entry, shape, state }) { /* upsert unconditionally, return entry */ },
191
+ remove({ entry, shape }) { /* remove unconditionally, return entry */ }
192
+
193
+ };
194
+
195
+ const client = createValidatingStore(backend, { trusted: true });
196
+
197
+ return createManagingStore({
198
+
199
+ execute: task => { /* run task(client) within a backend transaction */ },
200
+ close: () => { /* release connections */ }
201
+
202
+ });
203
+
204
+ }
205
+ ```
206
+
207
+ Pass `trusted: true` to `createValidatingStore` when the backend is trusted to deliver shape-conforming data, so
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.
212
+
213
+ All errors reach the caller as promise rejections through the unified store error channel: `RangeError` for malformed
214
+ entries, `TraceError` for shape validation failures, `Problem` for network, storage, or other processing failures.
215
+ Connectors MUST preserve this contract: convert backend-specific exceptions into `Problem` rejections and let validation
216
+ rejections propagate untouched.
217
+
218
+ Each connector MUST document its supported transaction isolation level and mutation event scope per the conventions
219
+ defined in the [Transaction Design](https://metreeca.github.io/keep/documents/_metreeca_keep.Transaction_Design.html)
220
+ companion document.
221
+
222
+ ## Testing
223
+
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:
226
+
227
+ ```typescript
228
+ import { testStore } from "@metreeca/keep-suite";
229
+ import { describe } from "vitest";
230
+
231
+ describe("my-store", () => testStore({
232
+
233
+ open: () => createMyStore(), // open a store with schema but no data
234
+
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 */ },
238
+
239
+ populate: () => { /* clear and reload the sample dataset */ },
240
+ generate: (sample, shape) => { /* insert an isolated copy of `sample` with a unique id */ },
241
+
242
+ close: () => { /* optional: release store resources after all tests */ }
243
+
244
+ }));
245
+ ```
39
246
 
40
- | Package | Description |
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 |
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
249
+ [API reference](https://metreeca.github.io/keep/modules/_metreeca_keep-suite.html) for the full list.
46
250
 
47
251
  # Support
48
252
 
@@ -0,0 +1,49 @@
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";
5
+ /**
6
+ * Creates a batching request {@link Broker}.
7
+ *
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.
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.
15
+ *
16
+ * @param handlers The batch handlers, one per request type. Each settles every {@link Deferred} in
17
+ * its batch and may issue nested {@link Broker.detail} / {@link Broker.select} calls while
18
+ * processing; its returned promise must settle only after every owned request, including
19
+ * transitively nested ones, has settled.
20
+ *
21
+ * @returns An immutable {@link Broker} surface for enqueuing requests and receiving their
22
+ * materialised results; each call returns a promise that settles when the relevant batch
23
+ * completes
24
+ */
25
+ export declare function createBroker(handlers: {
26
+ detect: Handler<Detect>;
27
+ detail: Handler<Detail>;
28
+ select: Handler<Select>;
29
+ modify: Handler<Modify>;
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;
49
+ //# 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":"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"}
@@ -0,0 +1,159 @@
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 { 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
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
26
+ /**
27
+ * Creates a batching request {@link Broker}.
28
+ *
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.
33
+ *
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.
36
+ *
37
+ * @param handlers The batch handlers, one per request type. Each settles every {@link Deferred} in
38
+ * its batch and may issue nested {@link Broker.detail} / {@link Broker.select} calls while
39
+ * processing; its returned promise must settle only after every owned request, including
40
+ * transitively nested ones, has settled.
41
+ *
42
+ * @returns An immutable {@link Broker} surface for enqueuing requests and receiving their
43
+ * materialised results; each call returns a promise that settles when the relevant batch
44
+ * completes
45
+ */
46
+ export function createBroker(handlers) {
47
+ const queues = Object.fromEntries(Object.entries(handlers).map(([key, handler]) => {
48
+ const items = [];
49
+ return [key, {
50
+ items,
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
+ };
61
+ },
62
+ purge: error => {
63
+ items.forEach(deferred => deferred.reject(error));
64
+ items.length = 0;
65
+ }
66
+ }];
67
+ }));
68
+ // Inner surface handed to the handlers: nested calls enqueue without kicking a drain.
69
+ const broker = immutable({
70
+ detect(request) {
71
+ return new Promise((resolve, reject) => queues.detect.items.push({ request, resolve, reject }));
72
+ },
73
+ detail(request) {
74
+ return new Promise((resolve, reject) => queues.detail.items.push({ request, resolve, reject }));
75
+ },
76
+ select(request) {
77
+ return new Promise((resolve, reject) => queues.select.items.push({ request, resolve, reject }));
78
+ },
79
+ modify(request) {
80
+ return new Promise((resolve, reject) => queues.modify.items.push({ request, resolve, reject }));
81
+ }
82
+ });
83
+ // Public surface: every call enqueues through the inner broker and kicks a drain via `process`.
84
+ return immutable({
85
+ detect(request) {
86
+ return process(() => broker.detect(request));
87
+ },
88
+ detail(request) {
89
+ return process(() => broker.detail(request));
90
+ },
91
+ select(request) {
92
+ return process(() => broker.select(request));
93
+ },
94
+ modify(request) {
95
+ return process(() => broker.modify(request));
96
+ }
97
+ });
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.
102
+ function process(submit) {
103
+ if (Object.values(queues).every(queue => queue.items.length === 0)) {
104
+ Promise.resolve().then(async () => {
105
+ // Drain in rounds until every queue is quiescent, all four handlers concurrently per
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.
110
+ try {
111
+ while (Object.values(queues).some(queue => queue.items.length > 0)) {
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());
115
+ }
116
+ }
117
+ catch (error) {
118
+ // A handler throw poisons the run: purge every queue so pending callers reject
119
+ // instead of hanging; already-settled entries no-op.
120
+ Object.values(queues).forEach(queue => queue.purge(error));
121
+ }
122
+ });
123
+ }
124
+ return submit();
125
+ }
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
+ }
159
+ //# 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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"}