@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
|
@@ -0,0 +1,105 @@
|
|
|
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 { immutable } from "@metreeca/core/structures";
|
|
18
|
+
import { createBroker } from "./batching.core.js";
|
|
19
|
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
20
|
+
/**
|
|
21
|
+
* Creates a batching store client backed by a set of request handlers.
|
|
22
|
+
*
|
|
23
|
+
* Routes every {@link StoreClient} call through a {@link Broker} that coalesces concurrent requests
|
|
24
|
+
* into batches and dispatches each batch through the matching `handlers` entry. Adds conditional CRUD
|
|
25
|
+
* semantics on top of the handlers: existence is probed through the `detect` handler before a create,
|
|
26
|
+
* update, or delete is delegated to `modify`, sparing each handler that bookkeeping.
|
|
27
|
+
*
|
|
28
|
+
* The `modify` handler is additionally wrapped so the batching layer, not the storage handler,
|
|
29
|
+
* enforces the §4.1 state-`id` constraint: a request whose `state` carries an `id` other than its
|
|
30
|
+
* `entry` is rejected on its own before dispatch, and a backend failure is settled as a rejection
|
|
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.
|
|
33
|
+
*
|
|
34
|
+
* @param handlers The batch handlers, one per request type, dispatching detect, lookup, select, and
|
|
35
|
+
* modify requests
|
|
36
|
+
*
|
|
37
|
+
* @returns An immutable {@link StoreClient} that batches its requests through the broker
|
|
38
|
+
*
|
|
39
|
+
* @throws {RangeError} If a create or update `state` carries an `id` that differs from its `entry`
|
|
40
|
+
*/
|
|
41
|
+
export function createBatchingStore(handlers) {
|
|
42
|
+
const broker = createBroker({
|
|
43
|
+
...handlers,
|
|
44
|
+
async modify(batch, broker) {
|
|
45
|
+
const items = batch.filter(({ request: { entry, state }, reject }) => {
|
|
46
|
+
// reject each request whose state id contradicts its entry (§4.1) on its own, so the
|
|
47
|
+
// rest of the batch still applies and the check stays out of storage-specific handlers
|
|
48
|
+
const matching = state?.id === undefined || state.id === entry;
|
|
49
|
+
if (!matching) {
|
|
50
|
+
reject(new RangeError(`mismatched state id <${String(state?.id)}> for entry <${entry}>`));
|
|
51
|
+
}
|
|
52
|
+
return matching;
|
|
53
|
+
});
|
|
54
|
+
if (items.length > 0) {
|
|
55
|
+
// on backend failure, reject the surviving batch rather than throw: the broker
|
|
56
|
+
// treats a handler throw as a run-wide fault and propagates it to sibling queues
|
|
57
|
+
try {
|
|
58
|
+
await handlers.modify(items, broker);
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
items.forEach(({ reject }) => reject(error));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
return immutable({
|
|
67
|
+
async lookup({ entry, shape, model }, { locale = ["und"] } = {}) {
|
|
68
|
+
// virtual resources have no stored state of their own: their members are derived from
|
|
69
|
+
// selection constraints rather than from stored content, so the existence probe is skipped.
|
|
70
|
+
return eager(shape).virtual || await broker.detect({ entry })
|
|
71
|
+
? broker.lookup({ entry, shape, model, locale })
|
|
72
|
+
: undefined;
|
|
73
|
+
},
|
|
74
|
+
async create({ entry, shape, state }) {
|
|
75
|
+
// the entry identifies the target: a state carrying a different id contradicts it (§4.1)
|
|
76
|
+
if (state.id !== undefined && state.id !== entry) {
|
|
77
|
+
throw new RangeError(`mismatched state id <${String(state.id)}> for entry <${entry}>`);
|
|
78
|
+
}
|
|
79
|
+
return await broker.detect({ entry })
|
|
80
|
+
? undefined
|
|
81
|
+
: broker.modify({ entry, shape, state });
|
|
82
|
+
},
|
|
83
|
+
async update({ entry, shape, state }) {
|
|
84
|
+
// the entry identifies the target: a state carrying a different id contradicts it (§4.1)
|
|
85
|
+
if (state.id !== undefined && state.id !== entry) {
|
|
86
|
+
throw new RangeError(`mismatched state id <${String(state.id)}> for entry <${entry}>`);
|
|
87
|
+
}
|
|
88
|
+
return await broker.detect({ entry })
|
|
89
|
+
? broker.modify({ entry, shape, state })
|
|
90
|
+
: undefined;
|
|
91
|
+
},
|
|
92
|
+
async delete({ entry, shape }) {
|
|
93
|
+
return await broker.detect({ entry })
|
|
94
|
+
? broker.modify({ entry, shape })
|
|
95
|
+
: undefined;
|
|
96
|
+
},
|
|
97
|
+
insert(insert) {
|
|
98
|
+
return broker.modify(insert);
|
|
99
|
+
},
|
|
100
|
+
remove(remove) {
|
|
101
|
+
return broker.modify(remove);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=batching.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batching.js","sourceRoot":"","sources":["../../src/clients/batching.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { StoreClient } from "../index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Creates a validating store backed by a delegate.
|
|
4
|
+
*
|
|
5
|
+
* Each method validates the relevant inputs against the supplied shape before delegating to `store`:
|
|
6
|
+
*
|
|
7
|
+
* - `entry` is checked for absolute-IRI form (no query string, no fragment) — failures rejected as `RangeError`
|
|
8
|
+
* - `state.id`, when present, is checked for equality with `entry` — mismatches rejected as `RangeError`, since
|
|
9
|
+
* the entry identifies the target and a contradicting payload identity is a caller error
|
|
10
|
+
* - `model` is validated as a {@link @metreeca/blue!validate | template} for
|
|
11
|
+
* {@link StoreClient.lookup lookup}, with the `lookup` `plain`/`depth`/`limit` opts forwarded as
|
|
12
|
+
* query-complexity caps
|
|
13
|
+
* - `state` is validated as a resource for {@link StoreClient.create create}/
|
|
14
|
+
* {@link StoreClient.update update}/{@link StoreClient.insert insert}; `create` and
|
|
15
|
+
* `update` always cap captive expansion at depth `0` (inline captive batches rejected), while `insert` honours its
|
|
16
|
+
* `depth` opt
|
|
17
|
+
* (omitted leaves expansion unbounded)
|
|
18
|
+
*
|
|
19
|
+
* The `trusted` opt controls whether {@link StoreClient.lookup lookup} responses are re-validated
|
|
20
|
+
* against the shape narrowed by the caller's `model` template before surfacing. Defaults to `false`
|
|
21
|
+
* — connectors whose backing source isn't trusted to deliver shape-conforming data (for example, a
|
|
22
|
+
* remote REST endpoint) get the safe default. Local stores that compute results themselves should
|
|
23
|
+
* pass `trusted: true` to skip the redundant pass.
|
|
24
|
+
*
|
|
25
|
+
* Validation failures surface as {@link @metreeca/core!TraceError | TraceError} or `RangeError` rejections per the
|
|
26
|
+
* unified {@link Store} error channel.
|
|
27
|
+
*
|
|
28
|
+
* @param store - Inner StoreClient to delegate to after validation
|
|
29
|
+
* @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
|
+
*
|
|
33
|
+
* @returns An immutable {@link StoreClient} wrapping `store` with input validation
|
|
34
|
+
*/
|
|
35
|
+
export declare function createValidatingStore(store: StoreClient, { trusted }?: {
|
|
36
|
+
readonly trusted?: boolean;
|
|
37
|
+
}): StoreClient;
|
|
38
|
+
//# sourceMappingURL=validating.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validating.d.ts","sourceRoot":"","sources":["../../src/clients/validating.ts"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAS,WAAW,EAAE,MAAM,aAAa,CAAC;AAGtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,WAAW,EAAE,EAEzD,OAAe,EAEf,GAAE;IAEF,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAErB,GAAG,WAAW,CA+GnB"}
|
|
@@ -0,0 +1,128 @@
|
|
|
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
|
+
/**
|
|
17
|
+
* Validating store wrapper.
|
|
18
|
+
*
|
|
19
|
+
* Wraps a plain {@link StoreClient} with shape-driven validation of entries, models, and states
|
|
20
|
+
* before delegating, and optional re-validation of retrieval responses.
|
|
21
|
+
*
|
|
22
|
+
* @module
|
|
23
|
+
*/
|
|
24
|
+
import { validate } from "@metreeca/blue";
|
|
25
|
+
import { immutable } from "@metreeca/core/structures";
|
|
26
|
+
import { TraceError } from "@metreeca/core/trace";
|
|
27
|
+
import { isReference } from "@metreeca/qest/resource";
|
|
28
|
+
/**
|
|
29
|
+
* Creates a validating store backed by a delegate.
|
|
30
|
+
*
|
|
31
|
+
* Each method validates the relevant inputs against the supplied shape before delegating to `store`:
|
|
32
|
+
*
|
|
33
|
+
* - `entry` is checked for absolute-IRI form (no query string, no fragment) — failures rejected as `RangeError`
|
|
34
|
+
* - `state.id`, when present, is checked for equality with `entry` — mismatches rejected as `RangeError`, since
|
|
35
|
+
* the entry identifies the target and a contradicting payload identity is a caller error
|
|
36
|
+
* - `model` is validated as a {@link @metreeca/blue!validate | template} for
|
|
37
|
+
* {@link StoreClient.lookup lookup}, with the `lookup` `plain`/`depth`/`limit` opts forwarded as
|
|
38
|
+
* query-complexity caps
|
|
39
|
+
* - `state` is validated as a resource for {@link StoreClient.create create}/
|
|
40
|
+
* {@link StoreClient.update update}/{@link StoreClient.insert insert}; `create` and
|
|
41
|
+
* `update` always cap captive expansion at depth `0` (inline captive batches rejected), while `insert` honours its
|
|
42
|
+
* `depth` opt
|
|
43
|
+
* (omitted leaves expansion unbounded)
|
|
44
|
+
*
|
|
45
|
+
* The `trusted` opt controls whether {@link StoreClient.lookup lookup} responses are re-validated
|
|
46
|
+
* against the shape narrowed by the caller's `model` template before surfacing. Defaults to `false`
|
|
47
|
+
* — connectors whose backing source isn't trusted to deliver shape-conforming data (for example, a
|
|
48
|
+
* remote REST endpoint) get the safe default. Local stores that compute results themselves should
|
|
49
|
+
* pass `trusted: true` to skip the redundant pass.
|
|
50
|
+
*
|
|
51
|
+
* Validation failures surface as {@link @metreeca/core!TraceError | TraceError} or `RangeError` rejections per the
|
|
52
|
+
* unified {@link Store} error channel.
|
|
53
|
+
*
|
|
54
|
+
* @param store - Inner StoreClient to delegate to after validation
|
|
55
|
+
* @param options - Optional validation options
|
|
56
|
+
* @param options.trusted - When `true`, skips re-validation of the {@link StoreClient.lookup lookup}
|
|
57
|
+
* response against the shape narrowed by the caller's `model`; defaults to `false`
|
|
58
|
+
*
|
|
59
|
+
* @returns An immutable {@link StoreClient} wrapping `store` with input validation
|
|
60
|
+
*/
|
|
61
|
+
export function createValidatingStore(store, { trusted = false } = {}) {
|
|
62
|
+
return immutable({
|
|
63
|
+
async lookup({ entry, shape, model }, opts) {
|
|
64
|
+
return store.lookup({
|
|
65
|
+
entry: assertEntry(entry),
|
|
66
|
+
shape,
|
|
67
|
+
model: validate(model, { ...opts, shape, model: true })({
|
|
68
|
+
value: value => value,
|
|
69
|
+
trace: trace => { throw new TraceError("invalid model", trace ?? []); }
|
|
70
|
+
})
|
|
71
|
+
}, opts).then(result => {
|
|
72
|
+
return trusted || result === undefined ? immutable(result) : validate(result, { shape, model })({
|
|
73
|
+
value: value => value,
|
|
74
|
+
trace: trace => { throw new TraceError("invalid response", trace ?? []); }
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
},
|
|
78
|
+
async create({ entry, shape, state }) {
|
|
79
|
+
return store.create({
|
|
80
|
+
entry: assertEntry(entry),
|
|
81
|
+
shape,
|
|
82
|
+
state: validate(state, { shape, depth: 0 })({
|
|
83
|
+
value: value => value,
|
|
84
|
+
trace: trace => { throw new TraceError("invalid state", trace ?? []); }
|
|
85
|
+
})
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
async update({ entry, shape, state }) {
|
|
89
|
+
return store.update({
|
|
90
|
+
entry: assertEntry(entry),
|
|
91
|
+
shape,
|
|
92
|
+
state: validate(state, { shape, depth: 0 })({
|
|
93
|
+
value: value => value,
|
|
94
|
+
trace: trace => { throw new TraceError("invalid state", trace ?? []); }
|
|
95
|
+
})
|
|
96
|
+
});
|
|
97
|
+
},
|
|
98
|
+
async delete({ entry, shape }) {
|
|
99
|
+
return store.delete({ entry: assertEntry(entry), shape });
|
|
100
|
+
},
|
|
101
|
+
async insert({ entry, shape, state }, opts) {
|
|
102
|
+
return store.insert({
|
|
103
|
+
entry: assertEntry(entry),
|
|
104
|
+
shape,
|
|
105
|
+
state: validate(state, { ...opts, shape })({
|
|
106
|
+
value: value => value,
|
|
107
|
+
trace: trace => { throw new TraceError("invalid state", trace ?? []); }
|
|
108
|
+
})
|
|
109
|
+
}, opts);
|
|
110
|
+
},
|
|
111
|
+
async remove({ entry, shape }) {
|
|
112
|
+
return store.remove({
|
|
113
|
+
entry: assertEntry(entry),
|
|
114
|
+
shape
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
function assertEntry(entry) {
|
|
119
|
+
if (!isReference(entry)) {
|
|
120
|
+
throw new RangeError(`expected absolute IRI <${entry}>`);
|
|
121
|
+
}
|
|
122
|
+
if (entry.includes("?") || entry.includes("#")) {
|
|
123
|
+
throw new RangeError(`unexpected query or fragment in <${entry}>`);
|
|
124
|
+
}
|
|
125
|
+
return entry;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=validating.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validating.js","sourceRoot":"","sources":["../../src/clients/validating.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import type { ResourceShape } from "@metreeca/blue/resource";
|
|
2
|
+
import type { Lazy } from "@metreeca/core";
|
|
3
|
+
import type { Tag } from "@metreeca/core/language";
|
|
4
|
+
import { type Reference, Resource } from "@metreeca/qest/resource";
|
|
5
|
+
import { Instance, Template } from "@metreeca/qest/template";
|
|
6
|
+
/**
|
|
7
|
+
* Model-driven resource store.
|
|
8
|
+
*
|
|
9
|
+
* Extends {@link StoreClient} with management facilities — mutation events, transactional execution, and lifecycle —
|
|
10
|
+
* to form the store surface produced by factories like
|
|
11
|
+
* {@link createManagingStore}. The data surface is factored into a standalone
|
|
12
|
+
* {@link StoreClient} so it can be implemented and passed around on its own (for example, the inner client handed to
|
|
13
|
+
* {@link Store.execute execute} carries no `execute` of its own), while consumers of a store reach both
|
|
14
|
+
* surfaces through the same object.
|
|
15
|
+
*
|
|
16
|
+
* Data methods on a store — {@link StoreClient.lookup lookup}, {@link StoreClient.create create},
|
|
17
|
+
* {@link StoreClient.update update}, {@link StoreClient.delete delete}, {@link StoreClient.insert insert}, and
|
|
18
|
+
* {@link StoreClient.remove remove} — are individually atomic even when executed outside
|
|
19
|
+
* {@link Store.execute execute}, regardless of the number of server round-trips they may internally require.
|
|
20
|
+
*
|
|
21
|
+
* > [!IMPORTANT]
|
|
22
|
+
* > Implementations provide best-effort transaction isolation, targeting snapshot isolation where the backend
|
|
23
|
+
* > supports it and degrading gracefully to the maximum level achievable by the underlying storage, down to no
|
|
24
|
+
* > isolation at all. Each implementation **must** document its supported isolation level. Implementations not
|
|
25
|
+
* > supporting atomic updates natively **must** emulate them by buffering and deferring mutations until commit time.
|
|
26
|
+
*
|
|
27
|
+
* > [!IMPORTANT]
|
|
28
|
+
* > Implementations provide best-effort mutation event signalling, targeting notification for all mutations on the
|
|
29
|
+
* > backing storage layer and degrading gracefully to signalling only events generated by calls to the mutation
|
|
30
|
+
* > methods on the store. Each implementation **must** document its supported event scope.
|
|
31
|
+
*/
|
|
32
|
+
export interface Store extends StoreClient {
|
|
33
|
+
/**
|
|
34
|
+
* Observe mutation events.
|
|
35
|
+
*
|
|
36
|
+
* Each call mints an independent registration; multiple registrations of the same observer coexist and
|
|
37
|
+
* fire independently — the observer is invoked once per matching registration per mutation batch. The
|
|
38
|
+
* returned handle detaches **only** that registration; calling it more than once is a no-op and other
|
|
39
|
+
* registrations of the same observer are unaffected. Detachment is the only supported way to stop
|
|
40
|
+
* receiving events — repeated `observe` calls do not replace any prior registration.
|
|
41
|
+
*
|
|
42
|
+
* `resources` is the filter for the registration:
|
|
43
|
+
*
|
|
44
|
+
* - `undefined` (omitted) — no filter; the observer fires for every mutation
|
|
45
|
+
* - a single {@link Reference} — fires for mutations to that resource and its
|
|
46
|
+
* {@link @metreeca/core!isNestedIRI | descendants}
|
|
47
|
+
* - an array of references — fires for mutations matching any element and its descendants
|
|
48
|
+
* - an empty array — ignored; no registration is created and the returned handle is a no-op
|
|
49
|
+
*
|
|
50
|
+
* > [!NOTE]
|
|
51
|
+
* > The observer receives only resource identifiers and an existence flag — not the mutated state.
|
|
52
|
+
* > This keeps event payloads small even when many resources are mutated within a single transaction,
|
|
53
|
+
* > and lets each observer fetch whatever data envelope it needs via {@link StoreClient.lookup lookup}.
|
|
54
|
+
*
|
|
55
|
+
* @param observer - Mutation observer invoked with each batch of matching mutations
|
|
56
|
+
* @param resources - Filter (single reference, array of references, or omitted for no filter)
|
|
57
|
+
*
|
|
58
|
+
* @returns A function that detaches **this** registration
|
|
59
|
+
*
|
|
60
|
+
* @throws `Error` if the store has been {@link Store.close closed}
|
|
61
|
+
*/
|
|
62
|
+
observe(observer: StoreObserver, resources?: Reference | readonly Reference[]): () => void;
|
|
63
|
+
/**
|
|
64
|
+
* Execute a task within a store transaction.
|
|
65
|
+
*
|
|
66
|
+
* All operations performed during the task are executed atomically. If the task completes successfully, all
|
|
67
|
+
* mutations are committed and {@link Store.observe registered observers} receive a single mutation event
|
|
68
|
+
* containing all affected resources. If the task throws or rejects — including logic errors (`RangeError`,
|
|
69
|
+
* {@link TraceError}) raised by inner {@link StoreClient} calls — no mutations are executed, no events are
|
|
70
|
+
* notified, and the error is propagated to the caller as a promise rejection.
|
|
71
|
+
*
|
|
72
|
+
* The task is handled its own {@link StoreClient} for the transaction. Operations performed through it belong to
|
|
73
|
+
* the transaction and commit together, kept separate from any other `execute` running at the same time.
|
|
74
|
+
*
|
|
75
|
+
* > [!WARNING]
|
|
76
|
+
* > `execute` is not re-entrant: transaction boundaries are flat, so a task cannot open a nested transaction
|
|
77
|
+
* > and compositions must share a single outer call site.
|
|
78
|
+
*
|
|
79
|
+
* > [!WARNING]
|
|
80
|
+
* > The task MUST NOT retain or use the {@link StoreClient} it receives after `execute` settles: implementations
|
|
81
|
+
* > may back it with transaction-scoped state (buffered mutations, a bound backend scope) that is flushed or
|
|
82
|
+
* > discarded on completion, so any later call has undefined behaviour.
|
|
83
|
+
*
|
|
84
|
+
* > [!WARNING]
|
|
85
|
+
* > **Atomicity is mandatory; isolation is best-effort.** The task's mutations and their events always commit
|
|
86
|
+
* > all-or-nothing. `SNAPSHOT` is the suggested read-isolation level; each implementation declares the level it
|
|
87
|
+
* > actually provides. An implementation with neither native transactions nor `SNAPSHOT` isolation MUST buffer
|
|
88
|
+
* > the mutations and events, commit them as one batch on success, and drop the buffer on failure (a synchronous
|
|
89
|
+
* > throw or a rejected promise).
|
|
90
|
+
*
|
|
91
|
+
* @typeParam V - Return type of the task
|
|
92
|
+
*
|
|
93
|
+
* @param task - Async or sync function performing store operations within the transaction
|
|
94
|
+
*
|
|
95
|
+
* @returns A promise resolving to the value returned by `task`; rejects with any error raised or
|
|
96
|
+
* propagated by `task`, including a `RangeError`/{@link TraceError} from inner Store validation or a
|
|
97
|
+
* {@link Problem} from a transactional, network, storage, or other processing failure
|
|
98
|
+
*
|
|
99
|
+
* @throws `Error` if the store has been {@link Store.close closed}
|
|
100
|
+
*/
|
|
101
|
+
execute<V>(task: (store: StoreClient) => V | Promise<V>): Promise<V>;
|
|
102
|
+
/**
|
|
103
|
+
* Release resources held by this store.
|
|
104
|
+
*
|
|
105
|
+
* Frees underlying resources such as database connections or file handles. Calling `close` on an
|
|
106
|
+
* already-closed store has no effect; implementations with no resources to release MAY return a
|
|
107
|
+
* resolved no-op.
|
|
108
|
+
*
|
|
109
|
+
* @returns A promise resolving when all resources have been released; rejects with a {@link Problem} if a
|
|
110
|
+
* clean-up error occurs
|
|
111
|
+
*/
|
|
112
|
+
close(): Promise<void>;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Model-driven resource CRUD operations.
|
|
116
|
+
*
|
|
117
|
+
* Persists and retrieves linked data resources as shape-validated states and query projections.
|
|
118
|
+
*/
|
|
119
|
+
export interface StoreClient {
|
|
120
|
+
/**
|
|
121
|
+
* Retrieve a resource.
|
|
122
|
+
*
|
|
123
|
+
* The result is shaped by the `model` {@link Template}: plain identifier properties are resolved from the
|
|
124
|
+
* shape's {@link Instance}\<T\> type, while computed bindings are derived from the template value.
|
|
125
|
+
*
|
|
126
|
+
* > [!NOTE]
|
|
127
|
+
* > `shape` and `model` are kept distinct so that a single `shape` can serve many retrieval templates —
|
|
128
|
+
* > for example, a server wiring one `shape` at startup and accepting any admissible `model` decoded
|
|
129
|
+
* > from the client request on each call. Callers wanting the shape's own model as template MUST pass
|
|
130
|
+
* > it explicitly via {@link @metreeca/blue/value!model}.
|
|
131
|
+
*
|
|
132
|
+
* > [!CAUTION]
|
|
133
|
+
* > By default, `model` templates support the full query language, including aggregate transforms and nested
|
|
134
|
+
* > expansion. When exposing retrieval to untrusted clients, restrict query complexity as required by setting
|
|
135
|
+
* > `plain` to `true`, `depth` to `0` or a positive value, and/or `limit` to a maximum result set size.
|
|
136
|
+
*
|
|
137
|
+
* @typeParam T - The retrieval template type
|
|
138
|
+
*
|
|
139
|
+
* @param request - Retrieval specifications
|
|
140
|
+
* @param request.entry - Absolute identifier of the resource to be retrieved
|
|
141
|
+
* @param request.shape - Resource shape driving the operation
|
|
142
|
+
* @param request.model - Retrieval template defining the data envelope
|
|
143
|
+
* @param opts - Optional retrieval options
|
|
144
|
+
* @param opts.locale - {@link Tag} priority list driving language negotiation for localised content; entries are
|
|
145
|
+
* matched in order of preference against the language tags available for each localised value. Implementations
|
|
146
|
+
* default this to `["und"]` when omitted
|
|
147
|
+
* @param opts.plain - When `true`, rejects `model` templates carrying aggregate transforms (`count`, `sum`, `min`,
|
|
148
|
+
* `max`, `avg`); defaults to `false`, admitting the full query language
|
|
149
|
+
* @param opts.depth - Maximum depth admitted for nested `model` expansion and query probe paths, each nesting
|
|
150
|
+
* level or path segment counting against the budget; `0` rejects any nested template while still accepting IRI
|
|
151
|
+
* references; omission leaves expansion unbounded
|
|
152
|
+
* @param opts.limit - Maximum value admitted for the `#` pagination constraint in `model` selections; a positive
|
|
153
|
+
* value caps the result set, rejecting any `#` exceeding it or set to `0` (unbounded) and injecting itself as a
|
|
154
|
+
* default where `#` is absent; omission, like `0`, leaves result sets unbounded
|
|
155
|
+
*
|
|
156
|
+
* @returns A promise resolving to an immutable copy of the resource data matching the specified model,
|
|
157
|
+
* or to `undefined` if the resource is not present in the store; rejects with a `RangeError` if `entry`
|
|
158
|
+
* is not an absolute IRI, a {@link TraceError} if `model` doesn't {@link validate} against the shape,
|
|
159
|
+
* or a {@link Problem} on network, storage, or other processing errors
|
|
160
|
+
*
|
|
161
|
+
* @throws `Error` if the store has been {@link Store.close closed}
|
|
162
|
+
*/
|
|
163
|
+
lookup<T extends Template>(request: {
|
|
164
|
+
readonly entry: Reference;
|
|
165
|
+
readonly shape: Lazy<ResourceShape>;
|
|
166
|
+
readonly model: T;
|
|
167
|
+
}, opts?: {
|
|
168
|
+
locale?: readonly Tag[];
|
|
169
|
+
plain?: boolean;
|
|
170
|
+
depth?: number;
|
|
171
|
+
limit?: number;
|
|
172
|
+
}): Promise<undefined | Instance<T>>;
|
|
173
|
+
/**
|
|
174
|
+
* Create a resource.
|
|
175
|
+
*
|
|
176
|
+
* Stores the resource's own data if the resource doesn't already exist. Specific reference kinds are handled
|
|
177
|
+
* as follows:
|
|
178
|
+
*
|
|
179
|
+
* - {@link ResourceShape | embedded references} — cascades recursively with the same semantics
|
|
180
|
+
* - {@link @metreeca/blue/reference!ReferenceConstraints.captive | captive references} — accepted only as bare
|
|
181
|
+
* IRI references; inline captive batches are rejected, as `state` is always validated at depth `0`. Use
|
|
182
|
+
* {@link StoreClient.insert insert} to embed a captive tree in a single batch
|
|
183
|
+
* - {@link @metreeca/blue/reference!ReferenceConstraints.foreign | foreign references} — skipped, as their
|
|
184
|
+
* data is owned by the defining resource
|
|
185
|
+
*
|
|
186
|
+
* @param request - Creation specifications
|
|
187
|
+
* @param request.entry - Absolute identifier of the target resource
|
|
188
|
+
* @param request.shape - Resource shape driving the operation
|
|
189
|
+
* @param request.state - Initial property values for the new resource
|
|
190
|
+
*
|
|
191
|
+
* @returns A promise resolving to the `entry` {@link Reference} of the created resource, or to `undefined`
|
|
192
|
+
* if the resource already exists; rejects with a `RangeError` if `entry` is not an absolute IRI or if
|
|
193
|
+
* `state` carries an `id` differing from `entry`, a {@link TraceError} if `state` doesn't {@link validate}
|
|
194
|
+
* against the shape, or a {@link Problem} on network, storage, or other processing errors
|
|
195
|
+
*
|
|
196
|
+
* @throws `Error` if the store has been {@link Store.close closed}
|
|
197
|
+
*
|
|
198
|
+
* @see {@link StoreClient.insert insert} for unconditional insertion
|
|
199
|
+
*/
|
|
200
|
+
create(request: {
|
|
201
|
+
readonly entry: Reference;
|
|
202
|
+
readonly shape: Lazy<ResourceShape>;
|
|
203
|
+
readonly state: Resource;
|
|
204
|
+
}): Promise<undefined | Reference>;
|
|
205
|
+
/**
|
|
206
|
+
* Update a resource.
|
|
207
|
+
*
|
|
208
|
+
* Replaces the resource's own data if the resource already exists, fully removing any previously existing
|
|
209
|
+
* embedded data. Specific reference kinds are handled as follows:
|
|
210
|
+
*
|
|
211
|
+
* - {@link ResourceShape | embedded references} — cascades recursively with the same semantics
|
|
212
|
+
* - {@link @metreeca/blue/reference!ReferenceConstraints.captive | captive references} — accepted only as bare
|
|
213
|
+
* IRI references; inline captive batches are rejected, as `state` is always validated at depth `0`. Use
|
|
214
|
+
* {@link StoreClient.insert insert} to embed a captive tree in a single batch
|
|
215
|
+
* - {@link @metreeca/blue/reference!ReferenceConstraints.foreign | foreign references} — skipped, as their
|
|
216
|
+
* data is owned by the defining resource
|
|
217
|
+
*
|
|
218
|
+
* @param request - Update specifications
|
|
219
|
+
* @param request.entry - Absolute identifier of the target resource
|
|
220
|
+
* @param request.shape - Resource shape driving the operation
|
|
221
|
+
* @param request.state - Complete replacement state for the resource
|
|
222
|
+
*
|
|
223
|
+
* @returns A promise resolving to the `entry` {@link Reference} of the updated resource, or to `undefined`
|
|
224
|
+
* if the resource doesn't exist; rejects with a `RangeError` if `entry` is not an absolute IRI or if
|
|
225
|
+
* `state` carries an `id` differing from `entry`, a {@link TraceError} if `state` doesn't {@link validate}
|
|
226
|
+
* against the shape, or a {@link Problem} on network, storage, or other processing errors
|
|
227
|
+
*
|
|
228
|
+
* @throws `Error` if the store has been {@link Store.close closed}
|
|
229
|
+
*
|
|
230
|
+
* @see {@link StoreClient.insert insert} for unconditional insertion
|
|
231
|
+
*/
|
|
232
|
+
update(request: {
|
|
233
|
+
readonly entry: Reference;
|
|
234
|
+
readonly shape: Lazy<ResourceShape>;
|
|
235
|
+
readonly state: Resource;
|
|
236
|
+
}): Promise<undefined | Reference>;
|
|
237
|
+
/**
|
|
238
|
+
* Delete a resource.
|
|
239
|
+
*
|
|
240
|
+
* Removes the resource's own data and clears references to it from other resources, if the resource exists.
|
|
241
|
+
* Specific reference kinds are handled as follows:
|
|
242
|
+
*
|
|
243
|
+
* - {@link ResourceShape | embedded references} — cascades recursively with the same semantics
|
|
244
|
+
* - {@link @metreeca/blue/reference!ReferenceConstraints.captive | captive references} — cascade-deleted with
|
|
245
|
+
* the same semantics
|
|
246
|
+
*
|
|
247
|
+
* @param request - Deletion specifications
|
|
248
|
+
* @param request.entry - Absolute identifier of the target resource
|
|
249
|
+
* @param request.shape - Resource shape driving the operation
|
|
250
|
+
*
|
|
251
|
+
* @returns A promise resolving to the `entry` {@link Reference} of the deleted resource, or to `undefined`
|
|
252
|
+
* if the resource doesn't exist; rejects with a `RangeError` if `entry` is not an absolute IRI, or a
|
|
253
|
+
* {@link Problem} on network, storage, or other processing errors
|
|
254
|
+
*
|
|
255
|
+
* @throws `Error` if the store has been {@link Store.close closed}
|
|
256
|
+
*
|
|
257
|
+
* @see {@link StoreClient.remove remove} for unconditional removal
|
|
258
|
+
*/
|
|
259
|
+
delete(request: {
|
|
260
|
+
readonly entry: Reference;
|
|
261
|
+
readonly shape: Lazy<ResourceShape>;
|
|
262
|
+
}): Promise<undefined | Reference>;
|
|
263
|
+
/**
|
|
264
|
+
* Insert a resource.
|
|
265
|
+
*
|
|
266
|
+
* Unconditionally inserts or replaces the resource's own data, fully removing any previously existing embedded
|
|
267
|
+
* data. Specific reference kinds are handled as follows:
|
|
268
|
+
*
|
|
269
|
+
* - {@link ResourceShape | embedded references} — cascades recursively with the same semantics
|
|
270
|
+
* - {@link @metreeca/blue/reference!ReferenceConstraints.captive | captive references} — accepted as bare IRI
|
|
271
|
+
* references or, up to `opts.depth` nesting levels, as inline batches creating or updating the captive tree
|
|
272
|
+
* - {@link @metreeca/blue/reference!ReferenceConstraints.foreign | foreign references} — skipped, as their
|
|
273
|
+
* data is owned by the defining resource
|
|
274
|
+
*
|
|
275
|
+
* > [!CAUTION]
|
|
276
|
+
* > By default, resources accept captive reference expansion to unbounded depth. To enforce a strict insertion
|
|
277
|
+
* > process that admits only bare references, set `opts.depth` to `0` to reject all expansion; set it to a positive
|
|
278
|
+
* > value to cap the nesting depth admitted.
|
|
279
|
+
*
|
|
280
|
+
* @param request - Insertion specifications
|
|
281
|
+
* @param request.entry - Absolute identifier of the target resource
|
|
282
|
+
* @param request.shape - Resource shape driving the operation
|
|
283
|
+
* @param request.state - Complete resource state to be inserted
|
|
284
|
+
* @param opts - Optional insertion options
|
|
285
|
+
* @param opts.depth - Maximum nesting depth for expanding `captive` reference values as inline target resource
|
|
286
|
+
* states, each expansion level counting against the budget; `0` rejects all expansion, accepting bare IRI
|
|
287
|
+
* references only; omission leaves expansion unbounded
|
|
288
|
+
*
|
|
289
|
+
* @returns A promise resolving to the `entry` {@link Reference} of the inserted resource; rejects with a
|
|
290
|
+
* `RangeError` if `entry` is not an absolute IRI or if `state` carries an `id` differing from `entry`, a
|
|
291
|
+
* {@link TraceError} if `state` doesn't {@link validate} against the shape, or a {@link Problem} on
|
|
292
|
+
* network, storage, or other processing errors
|
|
293
|
+
*
|
|
294
|
+
* @throws `Error` if the store has been {@link Store.close closed}
|
|
295
|
+
*
|
|
296
|
+
* @see {@link StoreClient.create create} for conditional creation
|
|
297
|
+
* @see {@link StoreClient.update update} for conditional replacement
|
|
298
|
+
*/
|
|
299
|
+
insert(request: {
|
|
300
|
+
readonly entry: Reference;
|
|
301
|
+
readonly shape: Lazy<ResourceShape>;
|
|
302
|
+
readonly state: Resource;
|
|
303
|
+
}, opts?: {
|
|
304
|
+
readonly depth?: number;
|
|
305
|
+
}): Promise<Reference>;
|
|
306
|
+
/**
|
|
307
|
+
* Remove a resource.
|
|
308
|
+
*
|
|
309
|
+
* Unconditionally removes the resource's own data and clears references to it from other resources. Specific
|
|
310
|
+
* reference kinds are handled as follows:
|
|
311
|
+
*
|
|
312
|
+
* - {@link ResourceShape | embedded references} — cascades recursively with the same semantics
|
|
313
|
+
* - {@link @metreeca/blue/reference!ReferenceConstraints.captive | captive references} — cascade-removed with
|
|
314
|
+
* the same semantics
|
|
315
|
+
*
|
|
316
|
+
* @param request - Removal specifications
|
|
317
|
+
* @param request.entry - Absolute identifier of the target resource
|
|
318
|
+
* @param request.shape - Resource shape driving the operation
|
|
319
|
+
*
|
|
320
|
+
* @returns A promise resolving to the `entry` {@link Reference} of the removed resource; rejects with a
|
|
321
|
+
* `RangeError` if `entry` is not an absolute IRI, or a {@link Problem} on network, storage, or other
|
|
322
|
+
* processing errors
|
|
323
|
+
*
|
|
324
|
+
* @throws `Error` if the store has been {@link Store.close closed}
|
|
325
|
+
*
|
|
326
|
+
* @see {@link StoreClient.delete delete} for conditional removal
|
|
327
|
+
*/
|
|
328
|
+
remove(request: {
|
|
329
|
+
readonly entry: Reference;
|
|
330
|
+
readonly shape: Lazy<ResourceShape>;
|
|
331
|
+
}): Promise<Reference>;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Store mutation observer.
|
|
335
|
+
*
|
|
336
|
+
* Receives batched mutation events. Registered via {@link Store.observe}.
|
|
337
|
+
*/
|
|
338
|
+
export interface StoreObserver {
|
|
339
|
+
/**
|
|
340
|
+
* Receive a batch of mutation events.
|
|
341
|
+
*
|
|
342
|
+
* Each entry maps a mutated resource id to an existence flag: `true` if the resource was
|
|
343
|
+
* {@link StoreClient.create created}, {@link StoreClient.update updated}, or {@link StoreClient.insert inserted};
|
|
344
|
+
* `false` if it was {@link StoreClient.delete deleted} or {@link StoreClient.remove removed}. When the same
|
|
345
|
+
* resource is mutated multiple times within a batch, last-write-wins semantics apply.
|
|
346
|
+
*
|
|
347
|
+
* > [!NOTE]
|
|
348
|
+
* > Observers may be sync or async. Both synchronous throws and asynchronous rejections are caught and
|
|
349
|
+
* > silently ignored so that one faulty observer cannot break delivery to others.
|
|
350
|
+
*
|
|
351
|
+
* @param mutations - Record mapping resource identifiers to existence flags
|
|
352
|
+
* (`true` for upserted, `false` for removed)
|
|
353
|
+
*/
|
|
354
|
+
(mutations: {
|
|
355
|
+
readonly [entry: Reference]: boolean;
|
|
356
|
+
}): void | Promise<void>;
|
|
357
|
+
}
|
|
358
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|