@_linked/core 2.25.0 → 2.27.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 (61) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +27 -0
  3. package/lib/esm/index.d.ts +9 -0
  4. package/lib/esm/index.js +8 -0
  5. package/lib/esm/index.js.map +1 -1
  6. package/lib/esm/interfaces/IDataset.d.ts +15 -0
  7. package/lib/esm/interfaces/IDataset.js.map +1 -1
  8. package/lib/esm/live/LiveQueryStore.d.ts +240 -0
  9. package/lib/esm/live/LiveQueryStore.js +729 -0
  10. package/lib/esm/live/LiveQueryStore.js.map +1 -0
  11. package/lib/esm/live/changes.d.ts +30 -0
  12. package/lib/esm/live/changes.js +30 -0
  13. package/lib/esm/live/changes.js.map +1 -0
  14. package/lib/esm/live/keys.d.ts +54 -0
  15. package/lib/esm/live/keys.js +112 -0
  16. package/lib/esm/live/keys.js.map +1 -0
  17. package/lib/esm/live/matcher.d.ts +43 -0
  18. package/lib/esm/live/matcher.js +115 -0
  19. package/lib/esm/live/matcher.js.map +1 -0
  20. package/lib/esm/live/registry.d.ts +24 -0
  21. package/lib/esm/live/registry.js +41 -0
  22. package/lib/esm/live/registry.js.map +1 -0
  23. package/lib/esm/live.d.ts +12 -0
  24. package/lib/esm/live.js +15 -0
  25. package/lib/esm/live.js.map +1 -0
  26. package/lib/esm/paths/normalizePropertyPath.js +3 -2
  27. package/lib/esm/paths/normalizePropertyPath.js.map +1 -1
  28. package/lib/esm/queries/AskBuilder.d.ts +10 -0
  29. package/lib/esm/queries/AskBuilder.js +14 -0
  30. package/lib/esm/queries/AskBuilder.js.map +1 -1
  31. package/lib/esm/queries/CountBuilder.d.ts +10 -0
  32. package/lib/esm/queries/CountBuilder.js +14 -0
  33. package/lib/esm/queries/CountBuilder.js.map +1 -1
  34. package/lib/esm/queries/QueryBuilder.d.ts +10 -0
  35. package/lib/esm/queries/QueryBuilder.js +14 -0
  36. package/lib/esm/queries/QueryBuilder.js.map +1 -1
  37. package/lib/esm/queries/QueryContext.js +9 -2
  38. package/lib/esm/queries/QueryContext.js.map +1 -1
  39. package/lib/esm/queries/queryDependencies.d.ts +58 -0
  40. package/lib/esm/queries/queryDependencies.js +494 -0
  41. package/lib/esm/queries/queryDependencies.js.map +1 -0
  42. package/lib/esm/queries/queryDispatch.d.ts +46 -0
  43. package/lib/esm/queries/queryDispatch.js +88 -3
  44. package/lib/esm/queries/queryDispatch.js.map +1 -1
  45. package/lib/esm/shapes/nodeShapeData.d.ts +16 -0
  46. package/lib/esm/shapes/nodeShapeData.js +41 -1
  47. package/lib/esm/shapes/nodeShapeData.js.map +1 -1
  48. package/lib/esm/shapes/relationShape.d.ts +35 -0
  49. package/lib/esm/shapes/relationShape.js +88 -0
  50. package/lib/esm/shapes/relationShape.js.map +1 -0
  51. package/lib/esm/shapes/validation.js +8 -4
  52. package/lib/esm/shapes/validation.js.map +1 -1
  53. package/lib/esm/sparql/irToAlgebra.js +5 -40
  54. package/lib/esm/sparql/irToAlgebra.js.map +1 -1
  55. package/lib/esm/utils/LinkedStorage.d.ts +6 -0
  56. package/lib/esm/utils/LinkedStorage.js +32 -6
  57. package/lib/esm/utils/LinkedStorage.js.map +1 -1
  58. package/lib/esm/utils/ShapeClass.d.ts +26 -0
  59. package/lib/esm/utils/ShapeClass.js +128 -0
  60. package/lib/esm/utils/ShapeClass.js.map +1 -1
  61. package/package.json +6 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,54 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.27.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#311](https://github.com/linked-fw/core/pull/311) [`f397a2e`](https://github.com/linked-fw/core/commit/f397a2e2dfa915b1fd1a0188323e0575941e61b9) Thanks [@flyon](https://github.com/flyon)! - Live queries: `query.live()` on select, count and ask builders returns a handle that keeps its result current — subscribe, await the first result, refresh, patch, close — backed by a framework-free `LiveQueryStore` in core (`@_linked/core/live`, registered automatically by the package root).
8
+
9
+ - Automatic refetching: every local mutation (including `exec(target)`) is observed through the new `subscribeQueryDispatch(listener)`; a dataset may report changes made elsewhere through the optional `IDataset.subscribeChanges(listener)` (and `authoritativeChanges` to defer refetches to its own confirmation); application code can `publishChange(event)`; `invalidate(Shape | iri | {id} | query)` is the manual lever. `LinkedStorage.onRoutingChanged(listener)` lets the store pick up feeds of datasets registered later.
10
+ - Two helpers describe what queries read and mutations write in predicate IRIs: `queryDependencies(query)` (`narrow`, `filter`, `hidden`, `shapes`, `unbound`) and `mutationEffects(mutation, result?)` (`op`, `shape`, `props`, `ids?`, `membership`). The matcher uses them to refetch by node id where it can, template-wide where it must, scoped by shape.
11
+ - `LinkedStorage.setDefaultDataset` / `setDatasetForShapes` / `unsetDatasetForShape` now notify `onRoutingChanged` listeners; the live store uses this to reset its cache on a storage change. `QueryDispatchEvent` carries the explicit `target` of an `exec(target)` call. Errors thrown by query-context listeners are now isolated (logged, not propagated).
12
+ - `findPropertyShapeById` is now exported from `shapes/nodeShapeData` (moved from the SPARQL layer; behaviour unchanged).
13
+
14
+ All additions are backwards compatible; `await query` and the mutation API are unchanged.
15
+
16
+ ## 2.26.0
17
+
18
+ ### Minor Changes
19
+
20
+ - [#308](https://github.com/linked-fw/core/pull/308) [`5f27582`](https://github.com/linked-fw/core/commit/5f27582cb3f341927a70a3c06279764f35cf4e4c) Thanks [@flyon](https://github.com/flyon)! - Add helpers for relation properties declared with `sh:class` alone, so callers can find the shape a relation's values go through.
21
+
22
+ New in `@_linked/core/utils/ShapeClass`:
23
+
24
+ - `getTargetClassId(shape, shapes?)`: the class a shape's instances are typed with. It returns the shape's own `targetClass`, or else the first one it finds through `extends`, or `undefined`. It accepts a shape IRI, a `{id}`, shape data or wire metadata, or a shape class, and it never throws.
25
+ - `getShapesForTargetClass(classIri, shapes?)`: the least specific shapes whose own or inherited `targetClass` is exactly `classIri`, sorted by id. A shape that extends another match is left out. If an `extends` cycle would leave out every match, all matches are kept.
26
+
27
+ New module `@_linked/core/shapes/relationShape`:
28
+
29
+ - `isRelation(property)`: true when the property has `sh:node`, has `sh:class`, or has an IRI or blank-node `sh:nodeKind`.
30
+ - `rangeClassOf(property, shapes?)`: the declared `sh:class`, or else the `targetClass` of the property's `sh:node` shape.
31
+ - `resolveRelationShape(property, shapes?)`: returns `{shapeId?, candidates, source: 'node' | 'class' | 'none'}`. A declared `sh:node` always wins. Otherwise the shapes from `getShapesForTargetClass` are the candidates, and the first one is used. If more than one unrelated root qualifies, it logs one warning per class and candidate set.
32
+
33
+ Pass `shapes` (for example, an app's own shape catalog) to resolve among that set. Without it, lookups use the shape registry.
34
+
35
+ ```ts
36
+ import {getTargetClassId, getShapesForTargetClass} from '@_linked/core/utils/ShapeClass';
37
+ import {isRelation, resolveRelationShape} from '@_linked/core/shapes/relationShape';
38
+
39
+ getTargetClassId('https://example.org/shapes/Employee'); // inherited through extends
40
+ getShapesForTargetClass('https://schema.org/Person', catalog); // the root shapes only
41
+
42
+ if (isRelation(property)) {
43
+ const {shapeId, source} = resolveRelationShape(property, catalog);
44
+ // shapeId is undefined when no shape targets the class
45
+ }
46
+ ```
47
+
48
+ A relation never upgrades itself to a sub-shape. Suppose `Person` and `Employee extends Person` both target `schema:Person`. A `sh:class schema:Person` relation then resolves to `Person`, with no warning. To use `Employee`, declare `sh:node`.
49
+
50
+ **Behaviour change:** validation now treats a property with `sh:class` as node-valued, the same way it treats one with `sh:node`. Creates and updates are validated, so a write that passes a bare literal (such as a string) for a `sh:class` property is now rejected. Pass a reference (`{id}`) instead.
51
+
3
52
  ## 2.25.0
4
53
 
5
54
  ### Minor Changes
package/README.md CHANGED
@@ -42,6 +42,7 @@ npm run setup
42
42
  - **[DSL-JSON — the Linked query wire format](./documentation/dsl-json.md)** — the canonical, standardized query structure.
43
43
  - [Intermediate Representation (IR)](./documentation/intermediate-representation.md) — the internal algebra the SPARQL dataset lowers to.
44
44
  - [SPARQL Algebra Layer](./documentation/sparql-algebra.md)
45
+ - [Live queries](./documentation/live-queries.md) — `query.live()`, change sources, and what triggers a refetch.
45
46
 
46
47
  ## How Linked works — from shapes to query results
47
48
 
@@ -1132,6 +1133,32 @@ yet — it travels as a `{$ctx}` marker and is resolved wherever the query is fi
1132
1133
  `FieldSet` serializes the same way (`FieldSet.for(Person, ['name','knows']).toJSON()` /
1133
1134
  `FieldSet.fromJSON(json)`).
1134
1135
 
1136
+ ## Live queries
1137
+
1138
+ A live query keeps its result current: subscribe once, and whenever something changes that the query
1139
+ can have read — a local mutation, a change reported by a dataset's feed, or one you publish yourself —
1140
+ it fetches again and notifies its listeners. The store is framework-free and lives in core; UI
1141
+ bindings such as `@_linked/react` sit on top of it. Full guide: **[documentation/live-queries.md](./documentation/live-queries.md)**.
1142
+
1143
+ ```typescript
1144
+ const live = Team.select((t) => [t.name, t.members.size()]).for({id: teamId}).live();
1145
+ const first = await live; // first data; the handle stays live
1146
+ live.subscribe((state) => render(state.data)); // every later result
1147
+ live.close();
1148
+
1149
+ Team.select().toCount().live((s) => setTotal(s.data)); // listener shorthand; counts and asks go live too
1150
+
1151
+ await Team.update({members: {add: [{id: personId}]}}).for({id: teamId});
1152
+ // → every live query that reads this team's members refetches; unrelated ones do not.
1153
+ ```
1154
+
1155
+ - `queryDependencies(query)` and `mutationEffects(mutation, result?)` describe, in predicate IRIs, what a
1156
+ query reads and what a mutation writes; the store matches the two (by node id where it can, template-wide
1157
+ where it must, scoped by shape).
1158
+ - `subscribeQueryDispatch(listener)` observes every query that runs through the dispatch, `exec(target)` included.
1159
+ - A dataset can report changes made elsewhere through the optional `IDataset.subscribeChanges(listener)`;
1160
+ `publishChange(event)` does the same from application code; `invalidate(Shape | {id} | query)` is the manual lever.
1161
+
1135
1162
  ## TODO
1136
1163
 
1137
1164
  - Allow `preloadFor` to accept another query (not just a component).
@@ -1,4 +1,5 @@
1
1
  import './shapes/index.js';
2
+ import './live.js';
2
3
  export { syncShapes, syncShape } from './shapes/syncShapes.js';
3
4
  export { rdfList } from './shapes/List.js';
4
5
  export { serializePathToNodeData } from './shapes/serializePathToNodeData.js';
@@ -12,6 +13,8 @@ export type { LowerableQuery } from './queries/lower.js';
12
13
  export { fromJSON } from './queries/fromJSON.js';
13
14
  export type { QueryJSON } from './queries/fromJSON.js';
14
15
  export { getQueryContext, setQueryContext, subscribeQueryContext, PendingQueryContext, UnresolvedContextError, } from './queries/QueryContext.js';
16
+ export { subscribeQueryDispatch } from './queries/queryDispatch.js';
17
+ export type { QueryDispatchEvent, QueryDispatchListener } from './queries/queryDispatch.js';
15
18
  export { CONTEXT_REF_KEY, encodeContextRef, isContextRefJSON, resolveContextId, } from './queries/ContextRef.js';
16
19
  export type { ContextRefJSON } from './queries/ContextRef.js';
17
20
  export { PropertyPath, walkPropertyPath } from './queries/PropertyPath.js';
@@ -36,3 +39,9 @@ export type { LinkedComponentInterface, QueryComponentLike } from './queries/Sel
36
39
  export { encodeNodeData, encodeValue, } from './queries/MutationSerialization.js';
37
40
  export { decodeNodeData, lowerMutationJSON, } from './queries/lowerMutationJSON.js';
38
41
  export type { MutationJSON, CreateMutationJSON, UpdateMutationJSON, DeleteMutationJSON, MutationValueJSON, MutationNodeDataJSON, } from './queries/MutationSerialization.js';
42
+ export { queryDependencies, mutationEffects } from './queries/queryDependencies.js';
43
+ export type { QueryDependencies, MutationEffects } from './queries/queryDependencies.js';
44
+ export { findPropertyShapeById } from './shapes/nodeShapeData.js';
45
+ export { LiveQueryStore, getLiveQueryStore, resetLiveQueryStore, publishChange, invalidate } from './live/LiveQueryStore.js';
46
+ export type { ChangeEvent } from './live/changes.js';
47
+ export type { LiveQuery, LiveState, LiveStatus, LiveListener, LiveQueryOptions, LiveQueryStoreOptions, } from './live/LiveQueryStore.js';
package/lib/esm/index.js CHANGED
@@ -5,6 +5,8 @@
5
5
  */
6
6
  // Registers every shape core defines (side effects only).
7
7
  import './shapes/index.js';
8
+ // Registers the live-query store so `query.live()` works (side effects only).
9
+ import './live.js';
8
10
  export { syncShapes, syncShape } from './shapes/syncShapes.js';
9
11
  export { rdfList } from './shapes/List.js';
10
12
  export { serializePathToNodeData } from './shapes/serializePathToNodeData.js';
@@ -18,6 +20,7 @@ export { SelectBuilder, QueryBuilder } from './queries/QueryBuilder.js';
18
20
  export { lower } from './queries/lower.js';
19
21
  export { fromJSON } from './queries/fromJSON.js';
20
22
  export { getQueryContext, setQueryContext, subscribeQueryContext, PendingQueryContext, UnresolvedContextError, } from './queries/QueryContext.js';
23
+ export { subscribeQueryDispatch } from './queries/queryDispatch.js';
21
24
  export { CONTEXT_REF_KEY, encodeContextRef, isContextRefJSON, resolveContextId, } from './queries/ContextRef.js';
22
25
  export { PropertyPath, walkPropertyPath } from './queries/PropertyPath.js';
23
26
  // Phase 3a — FieldSet
@@ -34,4 +37,9 @@ export { Expr } from './expressions/Expr.js';
34
37
  // Mutation DSL-JSON serialization (create/update/delete to/from JSON)
35
38
  export { encodeNodeData, encodeValue, } from './queries/MutationSerialization.js';
36
39
  export { decodeNodeData, lowerMutationJSON, } from './queries/lowerMutationJSON.js';
40
+ // Live-query dependency helpers — what a query reads and a mutation writes, as predicates
41
+ export { queryDependencies, mutationEffects } from './queries/queryDependencies.js';
42
+ export { findPropertyShapeById } from './shapes/nodeShapeData.js';
43
+ // Live queries — see documentation/live-queries.md
44
+ export { LiveQueryStore, getLiveQueryStore, resetLiveQueryStore, publishChange, invalidate } from './live/LiveQueryStore.js';
37
45
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,0DAA0D;AAC1D,OAAO,mBAAmB,CAAC;AAC3B,OAAO,EAAC,UAAU,EAAE,SAAS,EAAC,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAC,OAAO,EAAC,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAC,uBAAuB,EAAC,MAAM,qCAAqC,CAAC;AAC5E,sEAAsE;AACtE,OAAO,EAAC,KAAK,EAAC,MAAM,mBAAmB,CAAC;AACxC,kEAAkE;AAClE,OAAO,EAAC,QAAQ,EAAE,WAAW,EAAE,oBAAoB,EAAC,MAAM,wBAAwB,CAAC;AAQnF,OAAO,EAAC,aAAa,EAAC,MAAM,0BAA0B,CAAC;AACvD,2CAA2C;AAC3C,OAAO,EAAC,aAAa,EAAE,YAAY,EAAC,MAAM,2BAA2B,CAAC;AACtE,OAAO,EAAC,KAAK,EAAC,MAAM,oBAAoB,CAAC;AAEzC,OAAO,EAAC,QAAQ,EAAC,MAAM,uBAAuB,CAAC;AAE/C,OAAO,EACL,eAAe,EACf,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAC,YAAY,EAAE,gBAAgB,EAAC,MAAM,2BAA2B,CAAC;AAEzE,sBAAsB;AACtB,OAAO,EAAC,QAAQ,EAAC,MAAM,uBAAuB,CAAC;AAM/C,+BAA+B;AAC/B,OAAO,EAAC,UAAU,EAAE,UAAU,EAAC,MAAM,yBAAyB,CAAC;AAG/D,OAAO,EAAC,YAAY,EAAE,YAAY,EAAC,MAAM,2BAA2B,CAAC;AAGrE,OAAO,EAAC,aAAa,EAAC,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAC,aAAa,EAAC,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAC,aAAa,EAAC,MAAM,4BAA4B,CAAC;AAGzD,8CAA8C;AAC9C,OAAO,EAAC,cAAc,EAAC,MAAM,iCAAiC,CAAC;AAE/D,OAAO,EAAC,IAAI,EAAC,MAAM,uBAAuB,CAAC;AAe3C,sEAAsE;AACtE,OAAO,EACL,cAAc,EACd,WAAW,GACZ,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACL,cAAc,EACd,iBAAiB,GAClB,MAAM,gCAAgC,CAAC","sourcesContent":["/*\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at https://mozilla.org/MPL/2.0/.\n */\n// Registers every shape core defines (side effects only).\nimport './shapes/index.js';\nexport {syncShapes, syncShape} from './shapes/syncShapes.js';\nexport {rdfList} from './shapes/List.js';\nexport {serializePathToNodeData} from './shapes/serializePathToNodeData.js';\n// Core public API surface (documented as root imports in the README).\nexport {Shape} from './shapes/Shape.js';\n// SHACL-aligned validation of plain data objects against a shape.\nexport {validate, assertValid, ShapeValidationError} from './shapes/validation.js';\nexport type {\n ValidationReport,\n ValidationResult,\n ValidationMode,\n ValidateOptions,\n ValidatableShape,\n} from './shapes/validation.js';\nexport {LinkedStorage} from './utils/LinkedStorage.js';\n// New dynamic query building API (Phase 2)\nexport {SelectBuilder, QueryBuilder} from './queries/QueryBuilder.js';\nexport {lower} from './queries/lower.js';\nexport type {LowerableQuery} from './queries/lower.js';\nexport {fromJSON} from './queries/fromJSON.js';\nexport type {QueryJSON} from './queries/fromJSON.js';\nexport {\n getQueryContext,\n setQueryContext,\n subscribeQueryContext,\n PendingQueryContext,\n UnresolvedContextError,\n} from './queries/QueryContext.js';\nexport {\n CONTEXT_REF_KEY,\n encodeContextRef,\n isContextRefJSON,\n resolveContextId,\n} from './queries/ContextRef.js';\nexport type {ContextRefJSON} from './queries/ContextRef.js';\nexport {PropertyPath, walkPropertyPath} from './queries/PropertyPath.js';\n\n// Phase 3a — FieldSet\nexport {FieldSet} from './queries/FieldSet.js';\nexport type {FieldSetEntry, FieldSetInput, FieldSetJSON, FieldSetFieldJSON} from './queries/FieldSet.js';\n\n// Phase 4 — Serialization types\nexport type {QueryBuilderJSON} from './queries/QueryBuilder.js';\n\n// Phase 3b — Mutation builders\nexport {AskBuilder, isAskQuery} from './queries/AskBuilder.js';\nexport type {AskSpec} from './queries/AskBuilder.js';\nexport type {AskQuery, AskQueryJSON, RawAskInput} from './queries/AskQuery.js';\nexport {CountBuilder, isCountQuery} from './queries/CountBuilder.js';\nexport type {CountSpec} from './queries/CountBuilder.js';\nexport type {CountQuery, CountQueryJSON, RawCountInput} from './queries/CountQuery.js';\nexport {CreateBuilder} from './queries/CreateBuilder.js';\nexport {UpdateBuilder} from './queries/UpdateBuilder.js';\nexport {DeleteBuilder} from './queries/DeleteBuilder.js';\nexport type {DeleteId} from './queries/DeleteBuilder.js';\n\n// Expressions — computed fields and functions\nexport {ExpressionNode} from './expressions/ExpressionNode.js';\nexport type {ExpressionInput, PropertyRefMap} from './expressions/ExpressionNode.js';\nexport {Expr} from './expressions/Expr.js';\nexport type {\n ExpressionUpdateProxy,\n ExpressionUpdateResult,\n BaseExpressionMethods,\n NumericExpressionMethods,\n StringExpressionMethods,\n DateExpressionMethods,\n BooleanExpressionMethods,\n} from './expressions/ExpressionMethods.js';\n\n// Phase 5 — Component query integration\nexport type {LinkedComponentInterface, QueryComponentLike} from './queries/SelectQuery.js';\n\n\n// Mutation DSL-JSON serialization (create/update/delete to/from JSON)\nexport {\n encodeNodeData,\n encodeValue,\n} from './queries/MutationSerialization.js';\nexport {\n decodeNodeData,\n lowerMutationJSON,\n} from './queries/lowerMutationJSON.js';\nexport type {\n MutationJSON,\n CreateMutationJSON,\n UpdateMutationJSON,\n DeleteMutationJSON,\n MutationValueJSON,\n MutationNodeDataJSON,\n} from './queries/MutationSerialization.js';\n\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,0DAA0D;AAC1D,OAAO,mBAAmB,CAAC;AAC3B,8EAA8E;AAC9E,OAAO,WAAW,CAAC;AACnB,OAAO,EAAC,UAAU,EAAE,SAAS,EAAC,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAC,OAAO,EAAC,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAC,uBAAuB,EAAC,MAAM,qCAAqC,CAAC;AAC5E,sEAAsE;AACtE,OAAO,EAAC,KAAK,EAAC,MAAM,mBAAmB,CAAC;AACxC,kEAAkE;AAClE,OAAO,EAAC,QAAQ,EAAE,WAAW,EAAE,oBAAoB,EAAC,MAAM,wBAAwB,CAAC;AAQnF,OAAO,EAAC,aAAa,EAAC,MAAM,0BAA0B,CAAC;AACvD,2CAA2C;AAC3C,OAAO,EAAC,aAAa,EAAE,YAAY,EAAC,MAAM,2BAA2B,CAAC;AACtE,OAAO,EAAC,KAAK,EAAC,MAAM,oBAAoB,CAAC;AAEzC,OAAO,EAAC,QAAQ,EAAC,MAAM,uBAAuB,CAAC;AAE/C,OAAO,EACL,eAAe,EACf,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAC,sBAAsB,EAAC,MAAM,4BAA4B,CAAC;AAElE,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAC,YAAY,EAAE,gBAAgB,EAAC,MAAM,2BAA2B,CAAC;AAEzE,sBAAsB;AACtB,OAAO,EAAC,QAAQ,EAAC,MAAM,uBAAuB,CAAC;AAM/C,+BAA+B;AAC/B,OAAO,EAAC,UAAU,EAAE,UAAU,EAAC,MAAM,yBAAyB,CAAC;AAG/D,OAAO,EAAC,YAAY,EAAE,YAAY,EAAC,MAAM,2BAA2B,CAAC;AAGrE,OAAO,EAAC,aAAa,EAAC,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAC,aAAa,EAAC,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAC,aAAa,EAAC,MAAM,4BAA4B,CAAC;AAGzD,8CAA8C;AAC9C,OAAO,EAAC,cAAc,EAAC,MAAM,iCAAiC,CAAC;AAE/D,OAAO,EAAC,IAAI,EAAC,MAAM,uBAAuB,CAAC;AAe3C,sEAAsE;AACtE,OAAO,EACL,cAAc,EACd,WAAW,GACZ,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACL,cAAc,EACd,iBAAiB,GAClB,MAAM,gCAAgC,CAAC;AAWxC,0FAA0F;AAC1F,OAAO,EAAC,iBAAiB,EAAE,eAAe,EAAC,MAAM,gCAAgC,CAAC;AAElF,OAAO,EAAC,qBAAqB,EAAC,MAAM,2BAA2B,CAAC;AAEhE,mDAAmD;AACnD,OAAO,EAAC,cAAc,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,aAAa,EAAE,UAAU,EAAC,MAAM,0BAA0B,CAAC","sourcesContent":["/*\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at https://mozilla.org/MPL/2.0/.\n */\n// Registers every shape core defines (side effects only).\nimport './shapes/index.js';\n// Registers the live-query store so `query.live()` works (side effects only).\nimport './live.js';\nexport {syncShapes, syncShape} from './shapes/syncShapes.js';\nexport {rdfList} from './shapes/List.js';\nexport {serializePathToNodeData} from './shapes/serializePathToNodeData.js';\n// Core public API surface (documented as root imports in the README).\nexport {Shape} from './shapes/Shape.js';\n// SHACL-aligned validation of plain data objects against a shape.\nexport {validate, assertValid, ShapeValidationError} from './shapes/validation.js';\nexport type {\n ValidationReport,\n ValidationResult,\n ValidationMode,\n ValidateOptions,\n ValidatableShape,\n} from './shapes/validation.js';\nexport {LinkedStorage} from './utils/LinkedStorage.js';\n// New dynamic query building API (Phase 2)\nexport {SelectBuilder, QueryBuilder} from './queries/QueryBuilder.js';\nexport {lower} from './queries/lower.js';\nexport type {LowerableQuery} from './queries/lower.js';\nexport {fromJSON} from './queries/fromJSON.js';\nexport type {QueryJSON} from './queries/fromJSON.js';\nexport {\n getQueryContext,\n setQueryContext,\n subscribeQueryContext,\n PendingQueryContext,\n UnresolvedContextError,\n} from './queries/QueryContext.js';\nexport {subscribeQueryDispatch} from './queries/queryDispatch.js';\nexport type {QueryDispatchEvent, QueryDispatchListener} from './queries/queryDispatch.js';\nexport {\n CONTEXT_REF_KEY,\n encodeContextRef,\n isContextRefJSON,\n resolveContextId,\n} from './queries/ContextRef.js';\nexport type {ContextRefJSON} from './queries/ContextRef.js';\nexport {PropertyPath, walkPropertyPath} from './queries/PropertyPath.js';\n\n// Phase 3a — FieldSet\nexport {FieldSet} from './queries/FieldSet.js';\nexport type {FieldSetEntry, FieldSetInput, FieldSetJSON, FieldSetFieldJSON} from './queries/FieldSet.js';\n\n// Phase 4 — Serialization types\nexport type {QueryBuilderJSON} from './queries/QueryBuilder.js';\n\n// Phase 3b — Mutation builders\nexport {AskBuilder, isAskQuery} from './queries/AskBuilder.js';\nexport type {AskSpec} from './queries/AskBuilder.js';\nexport type {AskQuery, AskQueryJSON, RawAskInput} from './queries/AskQuery.js';\nexport {CountBuilder, isCountQuery} from './queries/CountBuilder.js';\nexport type {CountSpec} from './queries/CountBuilder.js';\nexport type {CountQuery, CountQueryJSON, RawCountInput} from './queries/CountQuery.js';\nexport {CreateBuilder} from './queries/CreateBuilder.js';\nexport {UpdateBuilder} from './queries/UpdateBuilder.js';\nexport {DeleteBuilder} from './queries/DeleteBuilder.js';\nexport type {DeleteId} from './queries/DeleteBuilder.js';\n\n// Expressions — computed fields and functions\nexport {ExpressionNode} from './expressions/ExpressionNode.js';\nexport type {ExpressionInput, PropertyRefMap} from './expressions/ExpressionNode.js';\nexport {Expr} from './expressions/Expr.js';\nexport type {\n ExpressionUpdateProxy,\n ExpressionUpdateResult,\n BaseExpressionMethods,\n NumericExpressionMethods,\n StringExpressionMethods,\n DateExpressionMethods,\n BooleanExpressionMethods,\n} from './expressions/ExpressionMethods.js';\n\n// Phase 5 — Component query integration\nexport type {LinkedComponentInterface, QueryComponentLike} from './queries/SelectQuery.js';\n\n\n// Mutation DSL-JSON serialization (create/update/delete to/from JSON)\nexport {\n encodeNodeData,\n encodeValue,\n} from './queries/MutationSerialization.js';\nexport {\n decodeNodeData,\n lowerMutationJSON,\n} from './queries/lowerMutationJSON.js';\nexport type {\n MutationJSON,\n CreateMutationJSON,\n UpdateMutationJSON,\n DeleteMutationJSON,\n MutationValueJSON,\n MutationNodeDataJSON,\n} from './queries/MutationSerialization.js';\n\n\n// Live-query dependency helpers — what a query reads and a mutation writes, as predicates\nexport {queryDependencies, mutationEffects} from './queries/queryDependencies.js';\nexport type {QueryDependencies, MutationEffects} from './queries/queryDependencies.js';\nexport {findPropertyShapeById} from './shapes/nodeShapeData.js';\n\n// Live queries — see documentation/live-queries.md\nexport {LiveQueryStore, getLiveQueryStore, resetLiveQueryStore, publishChange, invalidate} from './live/LiveQueryStore.js';\nexport type {ChangeEvent} from './live/changes.js';\nexport type {\n LiveQuery,\n LiveState,\n LiveStatus,\n LiveListener,\n LiveQueryOptions,\n LiveQueryStoreOptions,\n} from './live/LiveQueryStore.js';\n"]}
@@ -5,6 +5,7 @@ import type { CreateQuery } from '../queries/CreateQuery.js';
5
5
  import type { UpdateQuery } from '../queries/UpdateQuery.js';
6
6
  import type { DeleteQuery, DeleteResponse } from '../queries/DeleteQuery.js';
7
7
  import type { SelectResult, CreateResult, UpdateResult } from '../queries/IntermediateRepresentation.js';
8
+ import type { ChangeEvent } from '../live/changes.js';
8
9
  /**
9
10
  * Universal dataset interface. Every dataset in the Linked framework accepts
10
11
  * Linked Queries as input. The implementing class decides how to handle them —
@@ -74,4 +75,18 @@ export interface IDataset {
74
75
  updateQuery?(query: UpdateQuery): Promise<UpdateResult>;
75
76
  createQuery?(query: CreateQuery): Promise<CreateResult>;
76
77
  deleteQuery?(query: DeleteQuery): Promise<DeleteResponse>;
78
+ /**
79
+ * Optional change feed. A dataset that learns about changes made elsewhere
80
+ * (another client, a server-side job) reports them here as
81
+ * {@link ChangeEvent}s — the mutation's DSL-JSON plus its result, or
82
+ * precomputed effects — and the live-query store refetches whatever those
83
+ * changes can have touched. Returns the unsubscribe function.
84
+ */
85
+ subscribeChanges?(listener: (event: ChangeEvent) => void): () => void;
86
+ /**
87
+ * When `true`, a local mutation routed to this dataset does not trigger
88
+ * refetches by itself; the store waits for the dataset's own change event
89
+ * (the server's confirmation). Only meaningful with `subscribeChanges`.
90
+ */
91
+ readonly authoritativeChanges?: boolean;
77
92
  }
@@ -1 +1 @@
1
- {"version":3,"file":"IDataset.js","sourceRoot":"","sources":["../../../src/interfaces/IDataset.ts"],"names":[],"mappings":"","sourcesContent":["import type {SelectQuery} from '../queries/SelectQuery.js';\nimport type {AskQuery} from '../queries/AskQuery.js';\nimport type {CountQuery} from '../queries/CountQuery.js';\nimport type {CreateQuery} from '../queries/CreateQuery.js';\nimport type {UpdateQuery} from '../queries/UpdateQuery.js';\nimport type {DeleteQuery, DeleteResponse} from '../queries/DeleteQuery.js';\nimport type {\n SelectResult,\n CreateResult,\n UpdateResult,\n} from '../queries/IntermediateRepresentation.js';\n\n/**\n * Universal dataset interface. Every dataset in the Linked framework accepts\n * Linked Queries as input. The implementing class decides how to handle them —\n * compiling to SPARQL for Fuseki, forwarding as-is to a Host Agent API, etc.\n *\n * Each method receives the live, closed (read-only) query object — the builder\n * viewed through its `*Query` interface — and returns the result. A store reads\n * `query.toJSON()` to forward it, or `lower(query)` to get canonical IR. The\n * calling layer (LinkedStorage via queryDispatch) threads the precise DSL-level\n * TypeScript result type back to the caller.\n */\nexport interface IDataset {\n /**\n * Prepares the store to be used.\n */\n init?(): Promise<any>;\n\n /**\n * Answer a select query — **and a count query**, which arrives here too.\n *\n * A count is not a query form of its own the way an ask is. An ask is\n * `ASK WHERE { … }`; a count is `SELECT (COUNT(DISTINCT ?s) AS ?count) WHERE { … }`\n * — a select with an aggregate projection, over the same transport, answered with\n * the same result-set response. Giving it a method of its own meant every router\n * had to grow an arm for it, and any that did not silently could not count.\n *\n * So a store that wants to answer counts branches on the lowered IR:\n * `lower(query).kind === 'count'` (see {@link CountQuery} and `SparqlDataset`).\n * A store that does not is no worse off than before — it will fail loudly at the\n * dispatch (`resolveCount`), which refuses anything but a non-negative integer.\n * There is deliberately **no path in this package that rewrites a count as a row\n * query** and measures the array: that would hide an unbounded read behind a call\n * that looks cheap.\n *\n * A count must be answered with a real, non-negative integer, and must reject on\n * failure: reporting an unreachable store as `0` renders an empty table that is\n * indistinguishable from real data, which is the failure mode this API was built\n * to remove.\n */\n selectQuery(query: SelectQuery | CountQuery): Promise<SelectResult | number>;\n /**\n * Answer an ask query — a boolean, not a result set.\n *\n * **Required.** An {@link AskQuery} carries a pattern and nothing else: no\n * projection, no sorting, no pagination. A SPARQL-backed store emits\n * `ASK WHERE { … }`; another backend answers it however it can.\n *\n * It is required rather than optional, and this package contains **no path that\n * rewrites an ask as a select**. A store that has no boolean primitive decides\n * for itself how to answer — that decision belongs to the store, and defaulting\n * it here would hide it.\n *\n * `query.shape` is optional: absent means no `rdf:type` constraint at all\n * (\"does a node with this IRI exist\"), which lowers to `ASK { <iri> ?p ?o }`.\n *\n * Must resolve to a real boolean — a non-boolean is rejected, not coerced,\n * since a truthy value would silently read as \"exists\". Must reject on\n * failure: reporting an unreachable store as `false` is the failure mode this\n * API was built to remove.\n */\n askQuery(query: AskQuery): Promise<boolean>;\n /**\n * Receives update AND upsert mutations — `lower(query)` yields `kind: 'update'`,\n * `'update_where'` or `'upsert'`. An implementation that does not handle `'upsert'`\n * should throw rather than fall through to the update path: an upsert lowered as a\n * plain update writes an untyped node and reports success.\n */\n updateQuery?(query: UpdateQuery): Promise<UpdateResult>;\n createQuery?(query: CreateQuery): Promise<CreateResult>;\n deleteQuery?(query: DeleteQuery): Promise<DeleteResponse>;\n}\n"]}
1
+ {"version":3,"file":"IDataset.js","sourceRoot":"","sources":["../../../src/interfaces/IDataset.ts"],"names":[],"mappings":"","sourcesContent":["import type {SelectQuery} from '../queries/SelectQuery.js';\nimport type {AskQuery} from '../queries/AskQuery.js';\nimport type {CountQuery} from '../queries/CountQuery.js';\nimport type {CreateQuery} from '../queries/CreateQuery.js';\nimport type {UpdateQuery} from '../queries/UpdateQuery.js';\nimport type {DeleteQuery, DeleteResponse} from '../queries/DeleteQuery.js';\nimport type {\n SelectResult,\n CreateResult,\n UpdateResult,\n} from '../queries/IntermediateRepresentation.js';\nimport type {ChangeEvent} from '../live/changes.js';\n\n/**\n * Universal dataset interface. Every dataset in the Linked framework accepts\n * Linked Queries as input. The implementing class decides how to handle them —\n * compiling to SPARQL for Fuseki, forwarding as-is to a Host Agent API, etc.\n *\n * Each method receives the live, closed (read-only) query object — the builder\n * viewed through its `*Query` interface — and returns the result. A store reads\n * `query.toJSON()` to forward it, or `lower(query)` to get canonical IR. The\n * calling layer (LinkedStorage via queryDispatch) threads the precise DSL-level\n * TypeScript result type back to the caller.\n */\nexport interface IDataset {\n /**\n * Prepares the store to be used.\n */\n init?(): Promise<any>;\n\n /**\n * Answer a select query — **and a count query**, which arrives here too.\n *\n * A count is not a query form of its own the way an ask is. An ask is\n * `ASK WHERE { … }`; a count is `SELECT (COUNT(DISTINCT ?s) AS ?count) WHERE { … }`\n * — a select with an aggregate projection, over the same transport, answered with\n * the same result-set response. Giving it a method of its own meant every router\n * had to grow an arm for it, and any that did not silently could not count.\n *\n * So a store that wants to answer counts branches on the lowered IR:\n * `lower(query).kind === 'count'` (see {@link CountQuery} and `SparqlDataset`).\n * A store that does not is no worse off than before — it will fail loudly at the\n * dispatch (`resolveCount`), which refuses anything but a non-negative integer.\n * There is deliberately **no path in this package that rewrites a count as a row\n * query** and measures the array: that would hide an unbounded read behind a call\n * that looks cheap.\n *\n * A count must be answered with a real, non-negative integer, and must reject on\n * failure: reporting an unreachable store as `0` renders an empty table that is\n * indistinguishable from real data, which is the failure mode this API was built\n * to remove.\n */\n selectQuery(query: SelectQuery | CountQuery): Promise<SelectResult | number>;\n /**\n * Answer an ask query — a boolean, not a result set.\n *\n * **Required.** An {@link AskQuery} carries a pattern and nothing else: no\n * projection, no sorting, no pagination. A SPARQL-backed store emits\n * `ASK WHERE { … }`; another backend answers it however it can.\n *\n * It is required rather than optional, and this package contains **no path that\n * rewrites an ask as a select**. A store that has no boolean primitive decides\n * for itself how to answer — that decision belongs to the store, and defaulting\n * it here would hide it.\n *\n * `query.shape` is optional: absent means no `rdf:type` constraint at all\n * (\"does a node with this IRI exist\"), which lowers to `ASK { <iri> ?p ?o }`.\n *\n * Must resolve to a real boolean — a non-boolean is rejected, not coerced,\n * since a truthy value would silently read as \"exists\". Must reject on\n * failure: reporting an unreachable store as `false` is the failure mode this\n * API was built to remove.\n */\n askQuery(query: AskQuery): Promise<boolean>;\n /**\n * Receives update AND upsert mutations — `lower(query)` yields `kind: 'update'`,\n * `'update_where'` or `'upsert'`. An implementation that does not handle `'upsert'`\n * should throw rather than fall through to the update path: an upsert lowered as a\n * plain update writes an untyped node and reports success.\n */\n updateQuery?(query: UpdateQuery): Promise<UpdateResult>;\n createQuery?(query: CreateQuery): Promise<CreateResult>;\n deleteQuery?(query: DeleteQuery): Promise<DeleteResponse>;\n\n /**\n * Optional change feed. A dataset that learns about changes made elsewhere\n * (another client, a server-side job) reports them here as\n * {@link ChangeEvent}s — the mutation's DSL-JSON plus its result, or\n * precomputed effects — and the live-query store refetches whatever those\n * changes can have touched. Returns the unsubscribe function.\n */\n subscribeChanges?(listener: (event: ChangeEvent) => void): () => void;\n /**\n * When `true`, a local mutation routed to this dataset does not trigger\n * refetches by itself; the store waits for the dataset's own change event\n * (the server's confirmation). Only meaningful with `subscribeChanges`.\n */\n readonly authoritativeChanges?: boolean;\n}\n"]}
@@ -0,0 +1,240 @@
1
+ import { type MutationEffects, type QueryDependencies } from '../queries/queryDependencies.js';
2
+ import type { ShapeConstructor } from '../shapes/Shape.js';
3
+ import { type ChangeEvent, type ChangeOrigin } from './changes.js';
4
+ import { type InstanceParams, type LiveBuilder, type LiveQueryKind } from './keys.js';
5
+ export type LiveStatus = 'pending' | 'loading' | 'success' | 'error';
6
+ /**
7
+ * What a live query currently knows.
8
+ *
9
+ * - `pending`: nothing requested yet (no storage configured, or the subject is
10
+ * a query context that has not been set).
11
+ * - `loading`: first request in flight, no data yet.
12
+ * - `success` / `error`: last request settled. On error the previous `data`,
13
+ * if any, is kept so a screen does not go blank over a transient failure.
14
+ * - `refreshing`: a request is in flight while `data` is present.
15
+ * - `notFound`: a single-subject query answered `null`.
16
+ *
17
+ * The object is replaced (never mutated) on every change, so it is safe as a
18
+ * `useSyncExternalStore` snapshot.
19
+ */
20
+ export type LiveState<R = unknown> = {
21
+ status: LiveStatus;
22
+ data?: R;
23
+ error?: Error;
24
+ notFound: boolean;
25
+ refreshing: boolean;
26
+ };
27
+ export type LiveListener<R = unknown> = (state: LiveState<R>) => void;
28
+ export type LiveQueryOptions = {
29
+ /** Metadata for the template registry (`prepare()`), not identity. */
30
+ name?: string;
31
+ /** `false` opts this template — every query with this exact template — out of automatic invalidation. Default `true`. */
32
+ reactive?: boolean;
33
+ /** A pinned template survives having no instances (component definitions, `prepare()`). */
34
+ pinned?: boolean;
35
+ };
36
+ /**
37
+ * The handle returned by `query.live()`.
38
+ *
39
+ * It is `PromiseLike`: `await live` resolves with the first successful `data`
40
+ * (or rejects with the first error) and the handle stays live afterwards. It
41
+ * waits for as long as storage is not configured or a pending query context is
42
+ * not set. Because the handle is thenable, do not `return` it from an `async`
43
+ * function — the promise machinery would unwrap it. `subscribe(cb)` follows
44
+ * the Svelte store contract (returns the unsubscribe function). `close()`
45
+ * detaches every listener added through this handle; the handle can be used
46
+ * again afterwards, which is what React's StrictMode needs.
47
+ */
48
+ export interface LiveQuery<R = unknown> extends PromiseLike<R> {
49
+ readonly state: LiveState<R>;
50
+ /** Identity of the underlying instance (template key + params). */
51
+ readonly key: string;
52
+ subscribe(listener: LiveListener<R>): () => void;
53
+ /** Refetch now, keeping the current data until the response lands. */
54
+ refresh(): Promise<void>;
55
+ /**
56
+ * Local edit of the cached data, no request; overwritten by the next refetch
57
+ * and visible to every subscriber of the same query. An object merges into
58
+ * object data; an array or a function replaces.
59
+ */
60
+ patch(partial: Partial<R> | ((current: R) => R)): void;
61
+ /** Detach every listener added through this handle. */
62
+ close(): void;
63
+ }
64
+ export type Template = {
65
+ key: string;
66
+ kind: LiveQueryKind;
67
+ name?: string;
68
+ json: Record<string, unknown>;
69
+ /** Subject-stripped builder, used for dependency analysis only. */
70
+ builder: LiveBuilder;
71
+ shapeIri?: string;
72
+ reactive: boolean;
73
+ pinned: boolean;
74
+ deps?: QueryDependencies;
75
+ /** `deps` could not be computed (a query context in `where` is unset); recomputed on the next use. */
76
+ depsProvisional?: boolean;
77
+ instances: Map<string, Instance>;
78
+ };
79
+ export type Instance = {
80
+ key: string;
81
+ template: Template;
82
+ params: InstanceParams;
83
+ /** The bound builder this instance executes (what `.live()` was called on). */
84
+ builder: LiveBuilder;
85
+ state: LiveState;
86
+ /** Sequence of the latest issued request; a response carrying an older sequence is dropped. */
87
+ seq: number;
88
+ inflight: boolean;
89
+ /** Invalidated while a request was in flight: run one more request when it settles. */
90
+ staleWhileInflight: boolean;
91
+ /** Every node id the last result mentions, plus the subject(s). */
92
+ ids: Set<string>;
93
+ listeners: Set<LiveListener>;
94
+ /** Removed from its template (GC, routing change); a handle that still holds it re-resolves on use. */
95
+ dropped: boolean;
96
+ /** Set when a query context re-keyed this instance: handles follow it to the new one. */
97
+ movedTo?: Instance;
98
+ gcTimer?: ReturnType<typeof setTimeout>;
99
+ };
100
+ export type LiveQueryStoreOptions = {
101
+ /** Grace period before an instance with no listeners is dropped. */
102
+ gcMs: number;
103
+ /** Window in which a remote echo of a local change (or the reverse) folds into one refetch. */
104
+ echoMs: number;
105
+ };
106
+ /**
107
+ * Holds every live query in the runtime: templates (what is read) and
108
+ * instances (what it is applied to, and the data). Framework-free; UI layers
109
+ * subscribe to instances. One store per runtime, registered on `globalThis`
110
+ * (see `registry.ts`).
111
+ */
112
+ export declare class LiveQueryStore {
113
+ readonly options: LiveQueryStoreOptions;
114
+ /** @internal */
115
+ readonly _templates: Map<string, Template>;
116
+ /** @internal Indexes read by the change matcher. */
117
+ readonly templatesByProp: Map<string, Set<Template>>;
118
+ /** @internal */
119
+ readonly templatesByShape: Map<string, Set<Template>>;
120
+ /** @internal */
121
+ readonly instancesById: Map<string, Set<Instance>>;
122
+ private readonly _disposers;
123
+ private readonly _feeds;
124
+ private readonly _recentChanges;
125
+ private _pendingRefetch;
126
+ private _flushScheduled;
127
+ constructor();
128
+ /** Register (or find) the template of `query`. Options merge into an existing template. */
129
+ template(query: LiveBuilder, opts?: LiveQueryOptions): Template;
130
+ private _template;
131
+ /** The registered templates (for database tuning, devtools). */
132
+ templates(): ReadonlyArray<{
133
+ key: string;
134
+ kind: LiveQueryKind;
135
+ name?: string;
136
+ json: Record<string, unknown>;
137
+ }>;
138
+ /** Compute the dependencies of every template now (they are otherwise computed on first use). */
139
+ prepare(): void;
140
+ /**
141
+ * The watch set of a template, computed once. When a query context used in
142
+ * `where` is not set yet, lowering throws; the template then gets provisional
143
+ * dependencies (its shape only) and is recomputed on its next use, which the
144
+ * context change triggers.
145
+ */
146
+ depsOf(template: Template): QueryDependencies;
147
+ private _index;
148
+ private _unindexTemplate;
149
+ /** @internal Get or create the instance of `template` for `params`, executing `builder`. */
150
+ instance(template: Template, params: InstanceParams, builder: LiveBuilder): Instance;
151
+ /**
152
+ * Subscribe to `query`. The first listener on an instance starts its fetch;
153
+ * identical queries share one instance and one request. A handle created
154
+ * without a listener holds nothing: its instance is released after the grace
155
+ * period unless something subscribes.
156
+ */
157
+ subscribe<R = unknown>(query: LiveBuilder, listener?: LiveListener<R>, opts?: LiveQueryOptions): LiveQuery<R>;
158
+ /** @internal The live instance for a possibly dropped one (same template, params and builder). */
159
+ _resolve(inst: Instance): Instance;
160
+ /** @internal */
161
+ _attach(inst: Instance, listener: LiveListener): void;
162
+ /** @internal */
163
+ _detach(inst: Instance, listener: LiveListener): void;
164
+ /** Refetch an instance, keeping its data on screen. Resolves when that request settles. */
165
+ refresh(inst: Instance): Promise<void>;
166
+ /**
167
+ * Local edit of an instance's data. Never touches the store. A patched
168
+ * instance counts as loaded (`success`), so `await live` resolves with it.
169
+ */
170
+ patch(inst: Instance, partial: unknown): void;
171
+ /**
172
+ * Refetch every instance matching `target`: a shape class or shape IRI
173
+ * (everything that scans or traverses it, as after a delete), a node
174
+ * reference (`{id}`), a query (its template), or a template. Never folded
175
+ * with other change events.
176
+ */
177
+ invalidate(target: ShapeConstructor<any> | string | {
178
+ id: string;
179
+ } | LiveBuilder | Template): void;
180
+ /** @internal Refetch a set of instances, folding in-flight ones into one follow-up request. */
181
+ _refetchAll(instances: Iterable<Instance>): void;
182
+ /**
183
+ * Tell the store that data changed. Every source ends here: local mutations
184
+ * (automatically, origin `local`), dataset change feeds (`feed`), application
185
+ * code via `publishChange()` (`app`) and `invalidate()` (`manual`).
186
+ *
187
+ * A local change and its remote confirmation carry identical effects; when
188
+ * they arrive within `options.echoMs` of each other they refetch once. Two
189
+ * events of the same origin are never folded — two writes to the same node
190
+ * in quick succession are two changes — and manual invalidations never are.
191
+ */
192
+ publish(event: ChangeEvent, origin?: ChangeOrigin): void;
193
+ private _scheduleFlush;
194
+ /**
195
+ * Local mutations. Select and ask events are ignored: the store's own fetches
196
+ * run through the same dispatch and must not feed back into it.
197
+ */
198
+ private _onDispatch;
199
+ /** Subscribe to the change feeds of the datasets currently routed; drop feeds of datasets that left. */
200
+ private _scanDatasets;
201
+ /**
202
+ * Storage changed (a dataset was set, pinned or unpinned): what is cached may
203
+ * come from a store that no longer answers, so instances nobody watches are
204
+ * dropped and watched ones fetch again. Also refreshes the feed subscriptions.
205
+ */
206
+ private _onRoutingChanged;
207
+ /** Drop every template and instance (tests). Subscriptions to sources stay. */
208
+ reset(): void;
209
+ /** @internal */
210
+ dispose(): void;
211
+ private _fetch;
212
+ private _applyResult;
213
+ private _collectIds;
214
+ private _setState;
215
+ /**
216
+ * A query context changed. An instance whose *subject* is that context is
217
+ * re-keyed to the new id (the builder resolves the context lazily, so it is
218
+ * reused); an instance that references it in `where` just fetches again.
219
+ */
220
+ private _onContextChange;
221
+ /** Remove an instance from its template (and the template when it is empty and not pinned). */
222
+ private _dropInstance;
223
+ private _scheduleGc;
224
+ private _cancelGc;
225
+ }
226
+ /** Every string value under an `id` key, at any depth. */
227
+ export declare function collectIds(value: unknown, into: Set<string>): Set<string>;
228
+ /** Deep equality over the plain-data results the store holds (objects, arrays, dates, primitives). */
229
+ export declare function deepEqual(a: unknown, b: unknown): boolean;
230
+ /** The runtime's live-query store (registered on `globalThis` when this module loads). */
231
+ export declare function getLiveQueryStore(): LiveQueryStore;
232
+ /** Publish a change from application code (your own transport, a server push). */
233
+ export declare function publishChange(event: ChangeEvent): void;
234
+ /** Refetch every live query that `target` can have affected. See {@link LiveQueryStore.invalidate}. */
235
+ export declare function invalidate(target: ShapeConstructor<any> | string | {
236
+ id: string;
237
+ } | LiveBuilder | Template): void;
238
+ export type { ChangeEvent, ChangeOrigin, MutationEffects };
239
+ /** Replace the store with a fresh one (tests). */
240
+ export declare function resetLiveQueryStore(): LiveQueryStore;