@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
@@ -0,0 +1,320 @@
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
+ * Caching store wrapper.
18
+ *
19
+ * Speeds up read-heavy workloads by serving repeated retrievals from an in-memory cache. The cache stays bounded
20
+ * by size and age, shares concurrent identical retrievals, and stays coherent with writes made through the store, by
21
+ * other clients, or signalled by the backend.
22
+ *
23
+ * @module
24
+ */
25
+ import { isArray, isObject } from "@metreeca/core";
26
+ import { immutable } from "@metreeca/core/values";
27
+ /**
28
+ * Creates a caching store backed by a delegate.
29
+ *
30
+ * Serves repeated retrievals from an in-memory cache, sparing the delegate a round-trip whenever a prior result can
31
+ * be reused. This cuts latency and backend load for read-heavy workloads, while writes and eviction keep cached
32
+ * results coherent and bounded.
33
+ *
34
+ * Results are cached by `entry`, `model`, and the `locale` and `limit` of the {@link Store.lookup lookup} retrieval scope.
35
+ * Templates differing only in member or element order share a cached result.
36
+ *
37
+ * > [!IMPORTANT]
38
+ * > `shape` is not part of the cache key. A retrieval result MUST depend only on `entry`, `model`, `locale` and
39
+ * > `limit`, with `shape` supplying structural metadata that does not affect the returned payload.
40
+ *
41
+ * > [!IMPORTANT]
42
+ * > The scope `locale` is part of the cache key, because it selects which localised content a retrieval returns.
43
+ * > Unlike the model, the locale priority list is order-significant: `["en", "it"]` and `["it", "en"]` are cached
44
+ * > separately. An omitted and an empty locale list share a single cached result, distinct from any explicit list.
45
+ *
46
+ * > [!IMPORTANT]
47
+ * > The scope `limit` is part of the cache key, because it caps the page size of each collection and so changes the
48
+ * > returned payload. An omitted and a `0` limit share a single cached result. The scope `plain` and `depth` are not
49
+ * > part of the key: they only accept or reject a model, and never alter a successful payload.
50
+ *
51
+ * The cache offers three observable guarantees:
52
+ *
53
+ * - **coalescing** — concurrent retrievals for the same key resolve from a single delegate fetch, and a failed
54
+ * retrieval or one finding no resource is never cached, so a later retrieval re-fetches;
55
+ * - **read-after-write coherence** — a retrieval never returns content staler than the latest write made through this
56
+ * store: {@link Store.create create}, {@link Store.update update}, {@link Store.delete delete},
57
+ * {@link Store.insert insert} and {@link Store.remove remove} invalidate matching entries, and a
58
+ * retrieval overlapping an in-progress write to the same entry is served from the delegate without being cached;
59
+ * - **commit coherence** — writes committed through {@link Store.execute execute}, by other clients sharing the
60
+ * delegate, or signalled by the backend invalidate matching entries on commit, for as long as the store stays open
61
+ * ({@link Store.close close} ends further invalidation).
62
+ *
63
+ * The configurable `match` predicate selects which cached entries a write invalidates.
64
+ *
65
+ * > [!IMPORTANT]
66
+ * > **Transaction Isolation** — Inherited from the wrapped {@link Store}.
67
+ *
68
+ * > [!IMPORTANT]
69
+ * > **Mutation Events** — Inherited from the wrapped {@link Store}.
70
+ *
71
+ * @param store - The delegate store to cache
72
+ * @param options - Cache configuration options
73
+ *
74
+ * @returns An immutable store with caching behaviour
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * const cache = createCachingStore(restStore, { size: 1_000, ttl: 60_000 });
79
+ * const product = await cache.lookup({ entry, shape, model });
80
+ * ```
81
+ */
82
+ export function createCachingStore(store, { size = 0, ttl = 0, match = () => true } = {}) {
83
+ // LRU ordering is supplied for free by `Map`'s insertion-order iteration: every hit re-sets the entry,
84
+ // moving it to the tail, so `cache.keys().next().value` is always the least-recently-used key.
85
+ const cache = new Map();
86
+ // Tracks entries with at least one in-flight wrapper-initiated write. Reads of these entries bypass the
87
+ // cache entirely: they still query the delegate, but their result is not stored. This closes the window
88
+ // between a lookup call resolving with a pre-commit value and the reactive observer firing on commit, during
89
+ // which the cache would otherwise hold a stale-but-fresh-looking record.
90
+ const writing = new Map();
91
+ // Reactive invalidation: subscribe to the delegate's mutation events and drop matching records on commit.
92
+ // Covers writes routed through `execute`, writes from other clients sharing the delegate, and backends that
93
+ // emit storage-level events. The subscription is released by `close()` so the delegate does not retain a
94
+ // reference to a discarded wrapper.
95
+ const unsubscribe = store.observe(mutations => {
96
+ Object.keys(mutations).forEach(invalidate);
97
+ });
98
+ return immutable({
99
+ lookup({ entry, shape, model }, opts) {
100
+ return memoise(entry, model, opts, () => store.lookup({ entry, shape, model }, opts));
101
+ },
102
+ create({ entry, shape, model, state }) {
103
+ return write(entry, () => store.create({ entry, shape, model, state }));
104
+ },
105
+ update({ entry, shape, state }) {
106
+ return write(entry, () => store.update({ entry, shape, state }));
107
+ },
108
+ delete({ entry, shape }) {
109
+ return write(entry, () => store.delete({ entry, shape }));
110
+ },
111
+ insert({ entry, shape, state }) {
112
+ return write(entry, () => store.insert({ entry, shape, state }));
113
+ },
114
+ remove({ entry, shape }) {
115
+ return write(entry, () => store.remove({ entry, shape }));
116
+ },
117
+ observe(observer, resources) {
118
+ return store.observe(observer, resources);
119
+ },
120
+ execute(task) {
121
+ return store.execute(task); // task mutations invalidate the cache reactively through events
122
+ },
123
+ close() {
124
+ unsubscribe();
125
+ return store.close();
126
+ }
127
+ });
128
+ /**
129
+ * Retrieve-with-cache core.
130
+ *
131
+ * Three branches:
132
+ *
133
+ * - **bypass** — when `entry` has any wrapper-initiated write in flight, runs `miss()` and returns its
134
+ * promise without storing it, so a pre-commit snapshot cannot be installed as fresh;
135
+ * - **fresh hit** — returns the memoised promise and re-inserts the record to bump its LRU position;
136
+ * - **miss / TTL-expired** — runs `miss()`, stores the pending promise under the canonicalised key so
137
+ * concurrent callers share the in-flight request, and enforces TTL/size bounds via {@link purge}; a
138
+ * fetch resolving to an absent resource or rejecting evicts its record only if an intervening invalidation
139
+ * has not already replaced it.
140
+ *
141
+ * @typeParam V - The retrieved payload, opaque to the cache
142
+ *
143
+ * @param entry - Resource identifier; paired with the canonicalised model and varying scope members to form the
144
+ * cache key
145
+ * @param model - Retrieval model, canonicalised so that equivalent models in different orderings share a key
146
+ * @param vary - Retrieval scope members whose values vary the returned payload, folded into the key: `locale`
147
+ * order-significantly (an omitted or empty list collapsing to a single key) and `limit` because it caps each
148
+ * collection's `#` pagination bound (an omitted or `0` unbounded value collapsing to a single key)
149
+ * @param miss - Fetcher invoked on cache miss, on TTL expiration, or while the entry has an in-flight write
150
+ *
151
+ * @returns The memoised, freshly-fetched, or bypass-fetched promise
152
+ */
153
+ function memoise(entry, model, vary = {}, miss) {
154
+ // Bypass the cache while a write to this entry is in flight: still query the delegate so the caller
155
+ // gets a value, but do not store the result: it is liable to be the pre-commit snapshot that the
156
+ // reactive observer would invalidate moments later
157
+ if (writing.has(entry)) {
158
+ return miss();
159
+ }
160
+ else {
161
+ // Cache key layout: `<entry>\x00<canonical-model>\x00<locale>\x00<limit>`. `canonical` walks the
162
+ // model bottom-up, sorting object keys and (already-canonical) array elements, so models differing only
163
+ // in ordering share a key. The locale segment is serialised verbatim (order preserved, since the priority
164
+ // list is order-significant) with omitted and empty lists collapsing to `[]`. The limit segment is
165
+ // keyed because it caps each collection's `#` and so changes the payload, with omitted and `0` (both
166
+ // unbounded) collapsing to `0`. `\x00` is safe as a separator: IRIs cannot contain it and JSON always
167
+ // escapes it inside string payloads.
168
+ const key = [entry, canonical(model), JSON.stringify(vary.locale ?? []), vary.limit ?? 0].join("\x00");
169
+ const cached = cache.get(key);
170
+ if (cached && !expired(cached.created)) {
171
+ // hit: re-insert to move the entry to the tail of the Map's iteration order (the LRU position),
172
+ // and return the memoised promise; ;(cast) the type-erased `Promise<unknown>` storage lets records
173
+ // with different payload types share one Map
174
+ cache.delete(key);
175
+ cache.set(key, cached);
176
+ return cached.value;
177
+ }
178
+ else {
179
+ // miss (or TTL-expired): drop any stale record, run the fetcher, and memoise the pending promise
180
+ // under the same key so concurrent callers share the in-flight request; an absent resource or a
181
+ // failure evicts its own record once settled, so neither a missing resource nor a transient error
182
+ // is retained
183
+ if (cached) {
184
+ cache.delete(key);
185
+ }
186
+ const value = miss().then(resource => {
187
+ if (resource === undefined) {
188
+ evict();
189
+ }
190
+ return resource;
191
+ }, error => {
192
+ evict();
193
+ throw error;
194
+ });
195
+ // identity-check: only evict if this promise is still the cached one, avoiding dropping a newer
196
+ // record installed after an intervening invalidate
197
+ function evict() {
198
+ if (cache.get(key)?.value === value) {
199
+ cache.delete(key);
200
+ }
201
+ }
202
+ cache.set(key, {
203
+ entry,
204
+ value,
205
+ created: Date.now()
206
+ });
207
+ purge();
208
+ return value;
209
+ }
210
+ }
211
+ }
212
+ /**
213
+ * Serialises a value to a canonical JSON-like string.
214
+ *
215
+ * Children are canonicalised first, then sorted by their already-canonical string form, so arrays of
216
+ * objects are ordered by structural content rather than by the `"[object Object]"` placeholder that
217
+ * `Array.prototype.sort` would otherwise impose. The output collapses values differing only in property
218
+ * or element order to the same string.
219
+ *
220
+ * @param value - Value to serialise
221
+ *
222
+ * @returns A canonical string representation of `value`
223
+ */
224
+ function canonical(value) {
225
+ if (isArray(value)) {
226
+ return `[${value.map(canonical).sort().join(",")}]`;
227
+ }
228
+ else if (isObject(value)) {
229
+ return `{${Object.keys(value).sort().map(k => `${JSON.stringify(k)}:${canonical(value[k])}`).join(",")}}`;
230
+ }
231
+ else {
232
+ // JSON.stringify(undefined) returns undefined, not a string; substitute a distinct sentinel so the
233
+ // signature holds and `undefined` does not collide with the string literal `"undefined"` (which JSON
234
+ // would render with surrounding quotes)
235
+ return value === undefined ? "undefined" : JSON.stringify(value);
236
+ }
237
+ }
238
+ /**
239
+ * Tests whether a record is older than the configured TTL.
240
+ *
241
+ * Returns false when `ttl <= 0`, so the predicate can be reused at both the hit guard and the purge sweep
242
+ * without duplicating the boundary check. The reference timestamp defaults to `Date.now` but may be
243
+ * passed in to share a single snapshot across a batch of comparisons.
244
+ *
245
+ * @param created - Insertion timestamp (ms) of the record under test
246
+ * @param now - Reference timestamp (ms) to compare against; defaults to `Date.now`
247
+ *
248
+ * @returns true if `ttl > 0` and `created` is older than `ttl` milliseconds relative to `now`; false otherwise
249
+ */
250
+ function expired(created, now = Date.now()) {
251
+ return ttl > 0 && now - created > ttl;
252
+ }
253
+ /**
254
+ * Run a wrapper-initiated write under in-flight tracking.
255
+ *
256
+ * Marks `entry` as having an in-flight write, runs the configured pre-commit invalidation, delegates to
257
+ * `op`, and clears the marker once the delegate settles. While the marker is set, retrievals of `entry`
258
+ * bypass the cache so a pre-commit value can never be stored as fresh.
259
+ *
260
+ * @param entry - Resource identifier targeted by the write
261
+ * @param op - Thunk that delegates the write to the wrapped store and returns its promise
262
+ *
263
+ * @returns The promise returned by `op`, settled after the in-flight marker has been cleared
264
+ */
265
+ function write(entry, op) {
266
+ writing.set(entry, (writing.get(entry) ?? 0) + 1);
267
+ invalidate(entry);
268
+ return op().finally(() => {
269
+ const count = (writing.get(entry) ?? 1) - 1;
270
+ if (count <= 0) {
271
+ writing.delete(entry);
272
+ }
273
+ else {
274
+ writing.set(entry, count);
275
+ }
276
+ });
277
+ }
278
+ /**
279
+ * Drop cached records matching a mutated resource.
280
+ *
281
+ * Iterates the cache and removes every record whose stored identifier satisfies the configured {@link match}
282
+ * predicate against the mutated `entry`. Called from both the pre-commit write path on the wrapper and the
283
+ * reactive observer attached to the delegate.
284
+ *
285
+ * @param entry - Identifier of the mutated resource
286
+ */
287
+ function invalidate(entry) {
288
+ for (const [key, record] of cache) {
289
+ if (match(entry, record.entry)) {
290
+ cache.delete(key);
291
+ }
292
+ }
293
+ }
294
+ /**
295
+ * Enforce TTL and size bounds.
296
+ *
297
+ * Runs two sweeps: first, when `ttl > 0`, drops every record older than `ttl` milliseconds; second, when
298
+ * `size > 0` and the cache is overfull, drops the head slice of the Map (least-recently-used first) in
299
+ * a single batch sized to the exact overflow. Invoked after every miss so expired siblings and overfill
300
+ * are cleaned up incrementally, as the cache has no background sweeper.
301
+ */
302
+ function purge() {
303
+ if (ttl > 0) {
304
+ const now = Date.now();
305
+ for (const [key, record] of cache) {
306
+ if (expired(record.created, now)) {
307
+ cache.delete(key);
308
+ }
309
+ }
310
+ }
311
+ // LRU sweep: when over the size bound, take the head slice of Map keys (insertion order = LRU first)
312
+ // and drop them: `cache.size - size` is the exact overflow, so no loop condition needs re-checking
313
+ if (size > 0 && cache.size > size) {
314
+ Array.from(cache.keys())
315
+ .slice(0, cache.size - size)
316
+ .forEach(key => cache.delete(key));
317
+ }
318
+ }
319
+ }
320
+ //# sourceMappingURL=caching.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"caching.js","sourceRoot":"","sources":["../../src/stores/caching.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ import type { Store } from "../index.js";
2
+ /**
3
+ * Manages mutation events, transactional execution, and lifecycle for a connector's {@link StoreClient},
4
+ * exposing it as a full {@link Store}.
5
+ *
6
+ * A connector can implement only the {@link StoreClient} data surface and still offer the full {@link Store}
7
+ * contract. The connector supplies its transaction primitive through `management.execute`, which hands the
8
+ * connector's {@link StoreClient} to every data call. The option is required, since only the connector knows what
9
+ * atomicity its backend can provide. Backend primitives supplied through `management` may also back `observe` or
10
+ * `close`, and the wrapper fills in whatever is missing.
11
+ *
12
+ * Each standalone mutation call and each {@link Store.execute execute} call delivers a single filtered event to each
13
+ * matching {@link StoreObserver observer} once the call resolves. If the call rejects, its pending events are
14
+ * discarded and no observer is notified. Synchronous throws and asynchronous rejections from observers are caught and
15
+ * silently ignored, so one faulty observer cannot break delivery to others.
16
+ *
17
+ * All errors, including {@link !RangeError RangeError}, {@link @metreeca/core!TraceError | TraceError} and
18
+ * {@link @metreeca/http!Problem | Problem}, propagate as promise rejections, in line with the unified {@link Store}
19
+ * error channel.
20
+ *
21
+ * > [!IMPORTANT]
22
+ * > **Transaction Isolation** — Determined by the supplied `execute` option, typically through a backend
23
+ * > transaction primitive: the wrapper adds neither isolation nor atomicity of its own.
24
+ *
25
+ * > [!IMPORTANT]
26
+ * > **Mutation Events** — emits in-process observer events: each standalone mutation and each
27
+ * > {@link Store.execute execute} call delivers a single filtered batch to matching {@link StoreObserver observer}s
28
+ * > on resolve, and none on rejection. Mutations made by other clients reach local observers only if a
29
+ * > backend-bridging `observe` option is supplied through `management`.
30
+ *
31
+ * @param management - {@link Store} management methods backed by the connector: `execute` is required, and the
32
+ * wrapper fills in whatever else is missing
33
+ * @param management.execute - Transaction wrapper applied to every standalone StoreClient call and to the body of
34
+ * each {@link Store.execute execute} call, typically a backend transaction primitive (for example,
35
+ * `graph.execute` for a SPARQL connector) responsible for atomic commit and rollback. A backend with no
36
+ * transaction primitive supplies a wrapper applying the task directly to its StoreClient, and declares on its
37
+ * factory that a failing task neither rolls back nor isolates its mutations. The option MUST call each task it
38
+ * receives with a `scope` StoreClient dedicated to that call, so per-call state, such as a freshly bound graph
39
+ * buffer, never leaks across concurrent invocations; every data call within the transaction is routed through
40
+ * `scope`. Calls made by a user task inside {@link Store.execute execute} do not open further transactions: they
41
+ * share the outer one. The option MUST convert synchronous throws from the task into promise rejections, to
42
+ * preserve the unified Store error channel: lookups are routed through it with no additional guard
43
+ * @param management.observe - Backend registration for storage-level events, such as mutations made by other clients
44
+ * sharing the backend. Each observer is registered both with this option and locally, and a single detach call
45
+ * releases both registrations. The resource filter always arrives as an array of references, or as `undefined`
46
+ * for an unfiltered registration, so the option never has to handle bare references or single-pass iterables.
47
+ * Absent by default
48
+ * @param management.close - Exposed as is on the returned store. Defaults to a resolved no-op
49
+ *
50
+ * @returns An immutable {@link Store} serving data calls through the supplied `management` opts
51
+ */
52
+ export declare function createManagingStore({ observe, execute, close }: {
53
+ readonly observe?: Store["observe"];
54
+ readonly execute: Store["execute"];
55
+ readonly close?: Store["close"];
56
+ }): Store;
57
+ //# sourceMappingURL=managing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"managing.d.ts","sourceRoot":"","sources":["../../src/stores/managing.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAAE,KAAK,EAA8B,MAAM,aAAa,CAAC;AAGrE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,wBAAgB,mBAAmB,CAAC,EAEnC,OAAO,EACP,OAAO,EAEP,KAA+B,EAE/B,EAAE;IAEF,QAAQ,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAA;IACnC,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,CAAA;IAElC,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,CAAA;CAE/B,GAAG,KAAK,CA2HR"}
@@ -0,0 +1,147 @@
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 { some } from "@metreeca/core/arrays";
17
+ import { isNestedIRI } from "@metreeca/core/resource";
18
+ import { immutable } from "@metreeca/core/values";
19
+ /**
20
+ * Manages mutation events, transactional execution, and lifecycle for a connector's {@link StoreClient},
21
+ * exposing it as a full {@link Store}.
22
+ *
23
+ * A connector can implement only the {@link StoreClient} data surface and still offer the full {@link Store}
24
+ * contract. The connector supplies its transaction primitive through `management.execute`, which hands the
25
+ * connector's {@link StoreClient} to every data call. The option is required, since only the connector knows what
26
+ * atomicity its backend can provide. Backend primitives supplied through `management` may also back `observe` or
27
+ * `close`, and the wrapper fills in whatever is missing.
28
+ *
29
+ * Each standalone mutation call and each {@link Store.execute execute} call delivers a single filtered event to each
30
+ * matching {@link StoreObserver observer} once the call resolves. If the call rejects, its pending events are
31
+ * discarded and no observer is notified. Synchronous throws and asynchronous rejections from observers are caught and
32
+ * silently ignored, so one faulty observer cannot break delivery to others.
33
+ *
34
+ * All errors, including {@link !RangeError RangeError}, {@link @metreeca/core!TraceError | TraceError} and
35
+ * {@link @metreeca/http!Problem | Problem}, propagate as promise rejections, in line with the unified {@link Store}
36
+ * error channel.
37
+ *
38
+ * > [!IMPORTANT]
39
+ * > **Transaction Isolation** — Determined by the supplied `execute` option, typically through a backend
40
+ * > transaction primitive: the wrapper adds neither isolation nor atomicity of its own.
41
+ *
42
+ * > [!IMPORTANT]
43
+ * > **Mutation Events** — emits in-process observer events: each standalone mutation and each
44
+ * > {@link Store.execute execute} call delivers a single filtered batch to matching {@link StoreObserver observer}s
45
+ * > on resolve, and none on rejection. Mutations made by other clients reach local observers only if a
46
+ * > backend-bridging `observe` option is supplied through `management`.
47
+ *
48
+ * @param management - {@link Store} management methods backed by the connector: `execute` is required, and the
49
+ * wrapper fills in whatever else is missing
50
+ * @param management.execute - Transaction wrapper applied to every standalone StoreClient call and to the body of
51
+ * each {@link Store.execute execute} call, typically a backend transaction primitive (for example,
52
+ * `graph.execute` for a SPARQL connector) responsible for atomic commit and rollback. A backend with no
53
+ * transaction primitive supplies a wrapper applying the task directly to its StoreClient, and declares on its
54
+ * factory that a failing task neither rolls back nor isolates its mutations. The option MUST call each task it
55
+ * receives with a `scope` StoreClient dedicated to that call, so per-call state, such as a freshly bound graph
56
+ * buffer, never leaks across concurrent invocations; every data call within the transaction is routed through
57
+ * `scope`. Calls made by a user task inside {@link Store.execute execute} do not open further transactions: they
58
+ * share the outer one. The option MUST convert synchronous throws from the task into promise rejections, to
59
+ * preserve the unified Store error channel: lookups are routed through it with no additional guard
60
+ * @param management.observe - Backend registration for storage-level events, such as mutations made by other clients
61
+ * sharing the backend. Each observer is registered both with this option and locally, and a single detach call
62
+ * releases both registrations. The resource filter always arrives as an array of references, or as `undefined`
63
+ * for an unfiltered registration, so the option never has to handle bare references or single-pass iterables.
64
+ * Absent by default
65
+ * @param management.close - Exposed as is on the returned store. Defaults to a resolved no-op
66
+ *
67
+ * @returns An immutable {@link Store} serving data calls through the supplied `management` opts
68
+ */
69
+ export function createManagingStore({ observe, execute, close = () => Promise.resolve() }) {
70
+ const observers = new Map();
71
+ return immutable({
72
+ lookup: (request, opts) => execute(store => store.lookup(request, opts)),
73
+ create: request => notify(({ mutated }) => execute(store => store.create(request).then(mutated))),
74
+ update: request => notify(({ mutated }) => execute(store => store.update(request).then(mutated))),
75
+ delete: request => notify(({ deleted }) => execute(store => store.delete(request).then(deleted))),
76
+ insert: request => notify(({ mutated }) => execute(store => store.insert(request).then(mutated))),
77
+ remove: request => notify(({ deleted }) => execute(store => store.remove(request).then(deleted))),
78
+ observe(observer, resources) {
79
+ // normalise upfront: the filter is drawn from once, so a single-pass iterable is safely
80
+ // shared between the local registration and the delegate
81
+ const filter = resources === undefined ? undefined : some(resources);
82
+ if (filter?.length === 0) {
83
+ return observe?.(observer, []) ?? (() => { });
84
+ }
85
+ else {
86
+ const token = Symbol();
87
+ const detach = observe?.(observer, filter);
88
+ observers.set(token, { observer, resources: filter });
89
+ return () => {
90
+ try {
91
+ detach?.();
92
+ }
93
+ finally {
94
+ observers.delete(token);
95
+ }
96
+ };
97
+ }
98
+ },
99
+ execute(task) {
100
+ return notify(({ mutated, deleted }) => execute(store => task(immutable({
101
+ lookup: (request, opts) => store.lookup(request, opts),
102
+ create: request => store.create(request).then(mutated),
103
+ update: request => store.update(request).then(mutated),
104
+ delete: request => store.delete(request).then(deleted),
105
+ insert: request => store.insert(request).then(mutated),
106
+ remove: request => store.remove(request).then(deleted)
107
+ }))));
108
+ },
109
+ close
110
+ });
111
+ async function notify(task) {
112
+ const mutations = new Map();
113
+ const value = await task({
114
+ mutated: entry => {
115
+ if (entry !== undefined) {
116
+ mutations.set(entry, true);
117
+ }
118
+ return entry;
119
+ },
120
+ deleted: entry => {
121
+ if (entry !== undefined) {
122
+ mutations.set(entry, false);
123
+ }
124
+ return entry;
125
+ }
126
+ });
127
+ if (mutations.size > 0) {
128
+ observers.forEach(({ observer, resources }) => {
129
+ const event = {};
130
+ mutations.forEach((exists, id) => {
131
+ // `resources` is `undefined` for an unfiltered registration (fires for every mutation)
132
+ // and an array (possibly empty) for a filtered one
133
+ if (resources === undefined || resources.some(r => isNestedIRI(r, id))) {
134
+ event[id] = exists;
135
+ }
136
+ });
137
+ if (Object.keys(event).length > 0) {
138
+ Promise.resolve(event)
139
+ .then(observer)
140
+ .catch(() => { });
141
+ }
142
+ });
143
+ }
144
+ return value;
145
+ }
146
+ }
147
+ //# sourceMappingURL=managing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"managing.js","sourceRoot":"","sources":["../../src/stores/managing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAYH,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAE7C,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAOlD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,MAAM,UAAU,mBAAmB,CAAC,EAEnC,OAAO,EACP,OAAO,EAEP,KAAK,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,EAS/B;IAEA,MAAM,SAAS,GAAG,IAAI,GAAG,EAKrB,CAAC;IAEL,OAAO,SAAS,CAAC;QAEhB,MAAM,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAExE,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;QACjG,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;QACjG,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;QAEjG,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;QACjG,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;QAGjG,OAAO,CAAC,QAAQ,EAAE,SAAS;YAE1B,wFAAwF;YACxF,yDAAyD;YAEzD,MAAM,MAAM,GAAG,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAErE,IAAK,MAAM,EAAE,MAAM,KAAK,CAAC,EAAG,CAAC;gBAE5B,OAAO,OAAO,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YAE9C,CAAC;iBAAM,CAAC;gBAEP,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC;gBACvB,MAAM,MAAM,GAAG,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;gBAE3C,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;gBAEtD,OAAO,GAAG,EAAE;oBACX,IAAI,CAAC;wBAAC,MAAM,EAAE,EAAE,CAAC;oBAAC,CAAC;4BAAS,CAAC;wBAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;oBAAC,CAAC;gBACzD,CAAC,CAAC;YAEH,CAAC;QAEF,CAAC;QAED,OAAO,CAAC,IAAI;YAEX,OAAO,MAAM,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC;gBAEvE,MAAM,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC;gBAEtD,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC;gBACtD,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC;gBACtD,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC;gBAEtD,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC;gBACtD,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC;aAEtD,CAAC,CAAC,CAAC,CAAC,CAAC;QAEP,CAAC;QAED,KAAK;KAEL,CAAC,CAAC;IAGH,KAAK,UAAU,MAAM,CAAI,IAKP;QAEjB,MAAM,SAAS,GAAG,IAAI,GAAG,EAAsB,CAAC;QAEhD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC;YAExB,OAAO,EAAE,KAAK,CAAC,EAAE;gBAChB,IAAK,KAAK,KAAK,SAAS,EAAG,CAAC;oBAAC,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;gBAAC,CAAC;gBAC1D,OAAO,KAAK,CAAC;YACd,CAAC;YAED,OAAO,EAAE,KAAK,CAAC,EAAE;gBAChB,IAAK,KAAK,KAAK,SAAS,EAAG,CAAC;oBAAC,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;gBAAC,CAAC;gBAC3D,OAAO,KAAK,CAAC;YACd,CAAC;SAED,CAAC,CAAC;QAEH,IAAK,SAAS,CAAC,IAAI,GAAG,CAAC,EAAG,CAAC;YAE1B,SAAS,CAAC,OAAO,CAAC,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,EAAE;gBAE7C,MAAM,KAAK,GAA+B,EAAE,CAAC;gBAE7C,SAAS,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,EAAE,EAAE;oBAEhC,uFAAuF;oBACvF,mDAAmD;oBAEnD,IAAK,SAAS,KAAK,SAAS,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAG,CAAC;wBAAC,KAAK,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC;oBAAC,CAAC;gBAElG,CAAC,CAAC,CAAC;gBAEH,IAAK,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,EAAG,CAAC;oBAErC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC;yBACpB,IAAI,CAAC,QAAQ,CAAC;yBACd,KAAK,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;gBAEpB,CAAC;YAEF,CAAC,CAAC,CAAC;QAEJ,CAAC;QAED,OAAO,KAAK,CAAC;IAEd,CAAC;AAEF,CAAC"}
package/package.json CHANGED
@@ -1,10 +1,12 @@
1
1
  {
2
2
  "name": "@metreeca/keep",
3
- "version": "0.9.0",
4
- "description": "{TBD}",
3
+ "version": "0.11.0",
4
+ "description": "Core model-driven storage API for @metreeca/keep.",
5
5
  "keywords": [
6
6
  "typescript",
7
- "utilities"
7
+ "json-ld",
8
+ "model-driven",
9
+ "storage"
8
10
  ],
9
11
  "homepage": "https://metreeca.github.io/keep/",
10
12
  "bugs": {
@@ -19,14 +21,32 @@
19
21
  },
20
22
  "repository": {
21
23
  "type": "git",
22
- "url": "git+https://github.com/metreeca/keep.git"
24
+ "url": "git+https://github.com/metreeca/keep.git",
25
+ "directory": "packages/components/keep"
23
26
  },
24
27
  "type": "module",
25
28
  "sideEffects": false,
26
29
  "exports": {
27
- ".": "./dist/index/index.js",
28
- "./stores": "./dist/stores/index.js",
29
- "./states": "./dist/states/index.js"
30
+ ".": {
31
+ "@metreeca/source": "./src/index.ts",
32
+ "default": "./dist/index.js"
33
+ },
34
+ "./validating": {
35
+ "@metreeca/source": "./src/clients/validating.ts",
36
+ "default": "./dist/clients/validating.js"
37
+ },
38
+ "./batching": {
39
+ "@metreeca/source": "./src/clients/batching.ts",
40
+ "default": "./dist/clients/batching.js"
41
+ },
42
+ "./caching": {
43
+ "@metreeca/source": "./src/stores/caching.ts",
44
+ "default": "./dist/stores/caching.js"
45
+ },
46
+ "./managing": {
47
+ "@metreeca/source": "./src/stores/managing.ts",
48
+ "default": "./dist/stores/managing.js"
49
+ }
30
50
  },
31
51
  "files": [
32
52
  "README.md",
@@ -34,24 +54,20 @@
34
54
  "dist"
35
55
  ],
36
56
  "engines": {
37
- "node": ">=22.0.0"
57
+ "node": ">=22.22.0"
38
58
  },
39
59
  "scripts": {
40
60
  "clean": "rm -rf dist docs node_modules",
41
- "setup": "npm install && npm audit fix",
42
- "build": "rm -rf dist docs && tsc -p tsconfig.build.json && npx typedoc",
43
- "check": "npx vitest --typecheck run",
44
- "watch": "npx tsc --watch",
45
- "proof": "npx typedoc && (npx typedoc --watch & npx five-server docs --open)"
61
+ "build": "rm -rf dist && tsc -p tsconfig.build.json",
62
+ "check": "npx vitest --typecheck run"
46
63
  },
47
64
  "dependencies": {
48
- "@metreeca/core": "^0.9.13",
49
- "@metreeca/qest": "^0.9.0",
50
- "@metreeca/blue": "^0.9.0"
65
+ "@metreeca/blue": "^0.12.0",
66
+ "@metreeca/core": "^0.12.0",
67
+ "@metreeca/http": "^0.4.0",
68
+ "@metreeca/qest": "^0.12.0"
51
69
  },
52
70
  "devDependencies": {
53
- "five-server": "^0.4.5",
54
- "typedoc": "^0.28.14",
55
71
  "typescript": "^5.9.3",
56
72
  "vitest": "^4.0.13"
57
73
  }
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index/index.ts"],"names":[],"mappings":""}