@_linked/core 2.33.0 → 2.35.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 (88) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +26 -0
  3. package/lib/esm/index.d.ts +2 -1
  4. package/lib/esm/index.js +2 -0
  5. package/lib/esm/index.js.map +1 -1
  6. package/lib/esm/live/LiveQueryStore.d.ts +22 -0
  7. package/lib/esm/live/LiveQueryStore.js +86 -19
  8. package/lib/esm/live/LiveQueryStore.js.map +1 -1
  9. package/lib/esm/live/changes.d.ts +9 -1
  10. package/lib/esm/live/changes.js +7 -2
  11. package/lib/esm/live/changes.js.map +1 -1
  12. package/lib/esm/live/keys.d.ts +6 -0
  13. package/lib/esm/live/keys.js +10 -1
  14. package/lib/esm/live/keys.js.map +1 -1
  15. package/lib/esm/live/matcher.d.ts +4 -1
  16. package/lib/esm/live/matcher.js +10 -5
  17. package/lib/esm/live/matcher.js.map +1 -1
  18. package/lib/esm/queries/AskBuilder.d.ts +6 -0
  19. package/lib/esm/queries/AskBuilder.js +14 -0
  20. package/lib/esm/queries/AskBuilder.js.map +1 -1
  21. package/lib/esm/queries/CountBuilder.d.ts +6 -0
  22. package/lib/esm/queries/CountBuilder.js +14 -0
  23. package/lib/esm/queries/CountBuilder.js.map +1 -1
  24. package/lib/esm/queries/CreateBuilder.d.ts +4 -0
  25. package/lib/esm/queries/CreateBuilder.js +15 -1
  26. package/lib/esm/queries/CreateBuilder.js.map +1 -1
  27. package/lib/esm/queries/DeleteBuilder.d.ts +4 -0
  28. package/lib/esm/queries/DeleteBuilder.js +15 -1
  29. package/lib/esm/queries/DeleteBuilder.js.map +1 -1
  30. package/lib/esm/queries/FieldSet.d.ts +10 -0
  31. package/lib/esm/queries/FieldSet.js +85 -18
  32. package/lib/esm/queries/FieldSet.js.map +1 -1
  33. package/lib/esm/queries/IRLower.js +3 -2
  34. package/lib/esm/queries/IRLower.js.map +1 -1
  35. package/lib/esm/queries/IRMutation.js +15 -14
  36. package/lib/esm/queries/IRMutation.js.map +1 -1
  37. package/lib/esm/queries/MutationSerialization.js +2 -2
  38. package/lib/esm/queries/MutationSerialization.js.map +1 -1
  39. package/lib/esm/queries/PropertyPath.js +2 -2
  40. package/lib/esm/queries/PropertyPath.js.map +1 -1
  41. package/lib/esm/queries/QueryBuilder.d.ts +7 -0
  42. package/lib/esm/queries/QueryBuilder.js +40 -3
  43. package/lib/esm/queries/QueryBuilder.js.map +1 -1
  44. package/lib/esm/queries/SelectQuery.js +2 -2
  45. package/lib/esm/queries/SelectQuery.js.map +1 -1
  46. package/lib/esm/queries/UpdateBuilder.d.ts +4 -0
  47. package/lib/esm/queries/UpdateBuilder.js +15 -1
  48. package/lib/esm/queries/UpdateBuilder.js.map +1 -1
  49. package/lib/esm/queries/fromJSON.d.ts +12 -1
  50. package/lib/esm/queries/fromJSON.js +5 -1
  51. package/lib/esm/queries/fromJSON.js.map +1 -1
  52. package/lib/esm/queries/lower.js +12 -5
  53. package/lib/esm/queries/lower.js.map +1 -1
  54. package/lib/esm/queries/queryDependencies.js +14 -10
  55. package/lib/esm/queries/queryDependencies.js.map +1 -1
  56. package/lib/esm/queries/resolveShape.js +14 -2
  57. package/lib/esm/queries/resolveShape.js.map +1 -1
  58. package/lib/esm/shapes/Shape.js +2 -1
  59. package/lib/esm/shapes/Shape.js.map +1 -1
  60. package/lib/esm/shapes/nodeShapeData.js +7 -10
  61. package/lib/esm/shapes/nodeShapeData.js.map +1 -1
  62. package/lib/esm/shapes/registerRuntimeShape.js +3 -1
  63. package/lib/esm/shapes/registerRuntimeShape.js.map +1 -1
  64. package/lib/esm/shapes/syncShapes.d.ts +6 -2
  65. package/lib/esm/shapes/syncShapes.js +16 -9
  66. package/lib/esm/shapes/syncShapes.js.map +1 -1
  67. package/lib/esm/shapes/validation.js +2 -2
  68. package/lib/esm/shapes/validation.js.map +1 -1
  69. package/lib/esm/sparql/irToAlgebra.js +117 -52
  70. package/lib/esm/sparql/irToAlgebra.js.map +1 -1
  71. package/lib/esm/sparql/ownedLinkCheck.js +6 -1
  72. package/lib/esm/sparql/ownedLinkCheck.js.map +1 -1
  73. package/lib/esm/utils/LinkedStorage.d.ts +7 -0
  74. package/lib/esm/utils/LinkedStorage.js +39 -12
  75. package/lib/esm/utils/LinkedStorage.js.map +1 -1
  76. package/lib/esm/utils/ShapeCatalog.d.ts +57 -0
  77. package/lib/esm/utils/ShapeCatalog.js +86 -0
  78. package/lib/esm/utils/ShapeCatalog.js.map +1 -0
  79. package/lib/esm/utils/ShapeClass.d.ts +73 -8
  80. package/lib/esm/utils/ShapeClass.js +245 -66
  81. package/lib/esm/utils/ShapeClass.js.map +1 -1
  82. package/lib/esm/utils/loadStores.d.ts +4 -0
  83. package/lib/esm/utils/loadStores.js +24 -1
  84. package/lib/esm/utils/loadStores.js.map +1 -1
  85. package/lib/esm/utils/registryCache.d.ts +15 -0
  86. package/lib/esm/utils/registryCache.js +50 -0
  87. package/lib/esm/utils/registryCache.js.map +1 -0
  88. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  import { findPropertyShapeById } from '../shapes/nodeShapeData.js';
2
- import { getSubShapes, getTargetClassId } from '../utils/ShapeClass.js';
2
+ import { catalogOf, getActiveShapeCatalog, getSubShapes, getTargetClassId, withShapeCatalog, } from '../utils/ShapeClass.js';
3
3
  import { rdf } from '../ontologies/rdf.js';
4
4
  import { assertSafeIri } from './sparqlUtils.js';
5
5
  import { isBareNodeReference } from './irToAlgebra.js';
@@ -77,6 +77,11 @@ function walk(data, cache, out) {
77
77
  }
78
78
  /** Every owned link in a create, update, upsert or update-where mutation. Pure; no I/O. */
79
79
  export function collectOwnedLinks(ir) {
80
+ // Resolves the rules in the catalog the IR was lowered in.
81
+ const catalog = catalogOf(ir);
82
+ if (catalog !== undefined && catalog !== getActiveShapeCatalog()) {
83
+ return withShapeCatalog(catalog, () => collectOwnedLinks(ir));
84
+ }
80
85
  const out = [];
81
86
  walk(ir.data, new Map(), out);
82
87
  return out;
@@ -1 +1 @@
1
- {"version":3,"file":"ownedLinkCheck.js","sourceRoot":"","sources":["../../../src/sparql/ownedLinkCheck.ts"],"names":[],"mappings":"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","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/**\n * The class check for nodes linked by `{id}` into a `contains` property.\n *\n * An owner deletes what it contains: on replace, on set-remove and when the owner itself is\n * deleted. Linking an existing node into a `contains` slot is allowed (upload, then attach),\n * so a node of the wrong kind — a Person linked into an `image` slot whose value shape is\n * ImageObject — would later be deleted with it. Nothing validates `sh:class` on write, so the\n * SPARQL dataset checks it here, before the update is sent.\n *\n * A field is checked when it owns its value by the same rule the replace delete applies: the\n * property itself is `contains`, declares a value shape and has a simple path. A plain property\n * on a predicate another shape declares `contains` owns nothing and is not checked. A node is\n * refused only when the store holds at least one `rdf:type` for it and none of them is accepted;\n * an id that does not exist yet, or has no type, is allowed (`rdf:nil`, a node another request\n * creates, an untyped IRI). Collecting the links is a pure walk over the lowered IR; the dataset sends\n * one SELECT for all distinct ids, and none when there is nothing to check.\n */\nimport type {\n IRCreateMutation,\n IRFieldValue,\n IRNodeData,\n IRUpdateMutation,\n IRUpdateWhereMutation,\n IRUpsertMutation,\n} from '../queries/IntermediateRepresentation.js';\nimport {findPropertyShapeById, type PropertyShapeData} from '../shapes/nodeShapeData.js';\nimport {getSubShapes, getTargetClassId} from '../utils/ShapeClass.js';\nimport {rdf} from '../ontologies/rdf.js';\nimport {assertSafeIri} from './sparqlUtils.js';\nimport {isBareNodeReference} from './irToAlgebra.js';\nimport {getSimplePathId} from '../paths/normalizePropertyPath.js';\nimport {isSparqlSelectResults, type SparqlQueryResults} from './resultMapping.js';\n\n/** One bare `{id}` linked into a `contains` field that declares a value shape. */\nexport type OwnedLink = {\n id: string;\n /** Property-shape id. */\n property: string;\n /** Property label, for the message. */\n label: string;\n /** The node shape that declares the property. */\n shape: string;\n /** The value shape's targetClass — the class the message names. */\n expectedClass: string;\n /** `expectedClass`, every registered sub-shape's targetClass, and `sh:class` if declared. */\n acceptedClasses: string[];\n};\n\ntype OwnedLinkMutation =\n | IRCreateMutation\n | IRUpdateMutation\n | IRUpsertMutation\n | IRUpdateWhereMutation;\n\ntype OwnedRule = Omit<OwnedLink, 'id'>;\n\nconst RDF_NIL = rdf.nil.id;\nconst RDF_TYPE = rdf.type.id;\n\nfunction isNodeData(value: unknown): value is IRNodeData {\n return !!value && typeof value === 'object' && 'shape' in value && 'fields' in value;\n}\n\n/**\n * The values a field writes: the items of an array overwrite, the `add` items of a set\n * modification (`remove` items unlink, they never link), or the single value.\n */\nfunction writtenValues(value: IRFieldValue): unknown[] {\n if (Array.isArray(value)) return value;\n if (\n value &&\n typeof value === 'object' &&\n !(value instanceof Date) &&\n !('id' in value) &&\n !('shape' in value) &&\n ('add' in value || 'remove' in value)\n ) {\n return (value as {add?: IRFieldValue[]}).add ?? [];\n }\n return [value];\n}\n\n/** The check for a property, or null when the property is not checked. */\nfunction ownedRuleFor(\n propertyId: string,\n fallbackShape: string,\n cache: Map<string, OwnedRule | null>,\n): OwnedRule | null {\n if (cache.has(propertyId)) return cache.get(propertyId) ?? null;\n const rule = buildRule(findPropertyShapeById(propertyId), fallbackShape);\n cache.set(propertyId, rule);\n return rule;\n}\n\nfunction buildRule(ps: PropertyShapeData | undefined, fallbackShape: string): OwnedRule | null {\n if (!ps?.contains || !ps.valueShape || !getSimplePathId(ps.path)) return null;\n const expectedClass = getTargetClassId(ps.valueShape.id);\n if (!expectedClass) return null;\n const accepted = new Set<string>([expectedClass]);\n for (const sub of getSubShapes(ps.valueShape.id)) {\n const subClass = getTargetClassId(sub);\n if (subClass) accepted.add(subClass);\n }\n if (ps.class?.id) accepted.add(ps.class.id);\n return {\n property: ps.id,\n label: ps.label,\n shape: ps.parentNodeShape?.id ?? fallbackShape,\n expectedClass,\n acceptedClasses: [...accepted],\n };\n}\n\nfunction walk(data: IRNodeData, cache: Map<string, OwnedRule | null>, out: OwnedLink[]): void {\n for (const field of data.fields) {\n const rule = ownedRuleFor(field.property, data.shape, cache);\n for (const item of writtenValues(field.value)) {\n if (isNodeData(item)) {\n // A nested create is not a link — the mutation writes its type itself — but its\n // own fields can link.\n walk(item, cache, out);\n } else if (rule && isBareNodeReference(item) && item.id !== RDF_NIL) {\n out.push({id: item.id, ...rule});\n }\n }\n }\n}\n\n/** Every owned link in a create, update, upsert or update-where mutation. Pure; no I/O. */\nexport function collectOwnedLinks(ir: OwnedLinkMutation): OwnedLink[] {\n const out: OwnedLink[] = [];\n walk(ir.data, new Map(), out);\n return out;\n}\n\n/** `SELECT ?id ?type WHERE { VALUES ?id { … } ?id rdf:type ?type }` for the distinct ids. */\nexport function ownedLinkTypesQuery(ids: string[]): string {\n const distinct = [...new Set(ids)];\n for (const id of distinct) assertSafeIri(id);\n const values = distinct.map((id) => `<${id}>`).join(' ');\n return `SELECT ?id ?type WHERE { VALUES ?id { ${values} } ?id <${RDF_TYPE}> ?type }`;\n}\n\n/** The `rdf:type`s per id from the answer to {@link ownedLinkTypesQuery}. */\nexport function ownedLinkTypesFromResults(\n json: SparqlQueryResults,\n): Map<string, Set<string>> {\n if (!isSparqlSelectResults(json)) {\n throw new Error(\n 'Expected a SPARQL SELECT result set for the owned-link type check, got an ASK response.',\n );\n }\n const types = new Map<string, Set<string>>();\n for (const binding of json.results.bindings) {\n const id = binding.id?.value;\n const type = binding.type?.value;\n if (!id || !type) continue;\n let set = types.get(id);\n if (!set) types.set(id, (set = new Set()));\n set.add(type);\n }\n return types;\n}\n\n/**\n * Throws when a linked id is typed and none of its types is accepted. Every violation is\n * listed in the one error. An id with no types is allowed.\n */\nexport function assertOwnedLinkTypes(\n links: OwnedLink[],\n typesById: ReadonlyMap<string, ReadonlySet<string>>,\n): void {\n const messages: string[] = [];\n const seen = new Set<string>();\n for (const link of links) {\n const types = typesById.get(link.id);\n if (!types || types.size === 0) continue;\n if (link.acceptedClasses.some((c) => types.has(c))) continue;\n const key = `${link.property} ${link.id}`;\n if (seen.has(key)) continue;\n seen.add(key);\n const typed = [...types].map((t) => `<${t}>`).join(', ');\n const others = link.acceptedClasses.filter((c) => c !== link.expectedClass);\n const alsoAccepted = others.length\n ? ` (or of ${others.map((c) => `<${c}>`).join(', ')}, a registered sub-shape or sh:class)`\n : '';\n messages.push(\n `Cannot link <${link.id}> into the contains property \"${link.label}\" of <${link.shape}>: ` +\n `it is typed ${typed}, but \"${link.label}\" only owns instances of ` +\n `<${link.expectedClass}>${alsoAccepted}. An owner deletes what it contains when the ` +\n 'value is replaced or removed, or when the owner is deleted.',\n );\n }\n if (messages.length > 0) throw new Error(messages.join('\\n'));\n}\n"]}
1
+ {"version":3,"file":"ownedLinkCheck.js","sourceRoot":"","sources":["../../../src/sparql/ownedLinkCheck.ts"],"names":[],"mappings":"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","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/**\n * The class check for nodes linked by `{id}` into a `contains` property.\n *\n * An owner deletes what it contains: on replace, on set-remove and when the owner itself is\n * deleted. Linking an existing node into a `contains` slot is allowed (upload, then attach),\n * so a node of the wrong kind — a Person linked into an `image` slot whose value shape is\n * ImageObject — would later be deleted with it. Nothing validates `sh:class` on write, so the\n * SPARQL dataset checks it here, before the update is sent.\n *\n * A field is checked when it owns its value by the same rule the replace delete applies: the\n * property itself is `contains`, declares a value shape and has a simple path. A plain property\n * on a predicate another shape declares `contains` owns nothing and is not checked. A node is\n * refused only when the store holds at least one `rdf:type` for it and none of them is accepted;\n * an id that does not exist yet, or has no type, is allowed (`rdf:nil`, a node another request\n * creates, an untyped IRI). Collecting the links is a pure walk over the lowered IR; the dataset sends\n * one SELECT for all distinct ids, and none when there is nothing to check.\n */\nimport type {\n IRCreateMutation,\n IRFieldValue,\n IRNodeData,\n IRUpdateMutation,\n IRUpdateWhereMutation,\n IRUpsertMutation,\n} from '../queries/IntermediateRepresentation.js';\nimport {findPropertyShapeById, type PropertyShapeData} from '../shapes/nodeShapeData.js';\nimport {\n catalogOf,\n getActiveShapeCatalog,\n getSubShapes,\n getTargetClassId,\n withShapeCatalog,\n} from '../utils/ShapeClass.js';\nimport {rdf} from '../ontologies/rdf.js';\nimport {assertSafeIri} from './sparqlUtils.js';\nimport {isBareNodeReference} from './irToAlgebra.js';\nimport {getSimplePathId} from '../paths/normalizePropertyPath.js';\nimport {isSparqlSelectResults, type SparqlQueryResults} from './resultMapping.js';\n\n/** One bare `{id}` linked into a `contains` field that declares a value shape. */\nexport type OwnedLink = {\n id: string;\n /** Property-shape id. */\n property: string;\n /** Property label, for the message. */\n label: string;\n /** The node shape that declares the property. */\n shape: string;\n /** The value shape's targetClass — the class the message names. */\n expectedClass: string;\n /** `expectedClass`, every registered sub-shape's targetClass, and `sh:class` if declared. */\n acceptedClasses: string[];\n};\n\ntype OwnedLinkMutation =\n | IRCreateMutation\n | IRUpdateMutation\n | IRUpsertMutation\n | IRUpdateWhereMutation;\n\ntype OwnedRule = Omit<OwnedLink, 'id'>;\n\nconst RDF_NIL = rdf.nil.id;\nconst RDF_TYPE = rdf.type.id;\n\nfunction isNodeData(value: unknown): value is IRNodeData {\n return !!value && typeof value === 'object' && 'shape' in value && 'fields' in value;\n}\n\n/**\n * The values a field writes: the items of an array overwrite, the `add` items of a set\n * modification (`remove` items unlink, they never link), or the single value.\n */\nfunction writtenValues(value: IRFieldValue): unknown[] {\n if (Array.isArray(value)) return value;\n if (\n value &&\n typeof value === 'object' &&\n !(value instanceof Date) &&\n !('id' in value) &&\n !('shape' in value) &&\n ('add' in value || 'remove' in value)\n ) {\n return (value as {add?: IRFieldValue[]}).add ?? [];\n }\n return [value];\n}\n\n/** The check for a property, or null when the property is not checked. */\nfunction ownedRuleFor(\n propertyId: string,\n fallbackShape: string,\n cache: Map<string, OwnedRule | null>,\n): OwnedRule | null {\n if (cache.has(propertyId)) return cache.get(propertyId) ?? null;\n const rule = buildRule(findPropertyShapeById(propertyId), fallbackShape);\n cache.set(propertyId, rule);\n return rule;\n}\n\nfunction buildRule(ps: PropertyShapeData | undefined, fallbackShape: string): OwnedRule | null {\n if (!ps?.contains || !ps.valueShape || !getSimplePathId(ps.path)) return null;\n const expectedClass = getTargetClassId(ps.valueShape.id);\n if (!expectedClass) return null;\n const accepted = new Set<string>([expectedClass]);\n for (const sub of getSubShapes(ps.valueShape.id)) {\n const subClass = getTargetClassId(sub);\n if (subClass) accepted.add(subClass);\n }\n if (ps.class?.id) accepted.add(ps.class.id);\n return {\n property: ps.id,\n label: ps.label,\n shape: ps.parentNodeShape?.id ?? fallbackShape,\n expectedClass,\n acceptedClasses: [...accepted],\n };\n}\n\nfunction walk(data: IRNodeData, cache: Map<string, OwnedRule | null>, out: OwnedLink[]): void {\n for (const field of data.fields) {\n const rule = ownedRuleFor(field.property, data.shape, cache);\n for (const item of writtenValues(field.value)) {\n if (isNodeData(item)) {\n // A nested create is not a link — the mutation writes its type itself — but its\n // own fields can link.\n walk(item, cache, out);\n } else if (rule && isBareNodeReference(item) && item.id !== RDF_NIL) {\n out.push({id: item.id, ...rule});\n }\n }\n }\n}\n\n/** Every owned link in a create, update, upsert or update-where mutation. Pure; no I/O. */\nexport function collectOwnedLinks(ir: OwnedLinkMutation): OwnedLink[] {\n // Resolves the rules in the catalog the IR was lowered in.\n const catalog = catalogOf(ir);\n if (catalog !== undefined && catalog !== getActiveShapeCatalog()) {\n return withShapeCatalog(catalog, () => collectOwnedLinks(ir));\n }\n const out: OwnedLink[] = [];\n walk(ir.data, new Map(), out);\n return out;\n}\n\n/** `SELECT ?id ?type WHERE { VALUES ?id { … } ?id rdf:type ?type }` for the distinct ids. */\nexport function ownedLinkTypesQuery(ids: string[]): string {\n const distinct = [...new Set(ids)];\n for (const id of distinct) assertSafeIri(id);\n const values = distinct.map((id) => `<${id}>`).join(' ');\n return `SELECT ?id ?type WHERE { VALUES ?id { ${values} } ?id <${RDF_TYPE}> ?type }`;\n}\n\n/** The `rdf:type`s per id from the answer to {@link ownedLinkTypesQuery}. */\nexport function ownedLinkTypesFromResults(\n json: SparqlQueryResults,\n): Map<string, Set<string>> {\n if (!isSparqlSelectResults(json)) {\n throw new Error(\n 'Expected a SPARQL SELECT result set for the owned-link type check, got an ASK response.',\n );\n }\n const types = new Map<string, Set<string>>();\n for (const binding of json.results.bindings) {\n const id = binding.id?.value;\n const type = binding.type?.value;\n if (!id || !type) continue;\n let set = types.get(id);\n if (!set) types.set(id, (set = new Set()));\n set.add(type);\n }\n return types;\n}\n\n/**\n * Throws when a linked id is typed and none of its types is accepted. Every violation is\n * listed in the one error. An id with no types is allowed.\n */\nexport function assertOwnedLinkTypes(\n links: OwnedLink[],\n typesById: ReadonlyMap<string, ReadonlySet<string>>,\n): void {\n const messages: string[] = [];\n const seen = new Set<string>();\n for (const link of links) {\n const types = typesById.get(link.id);\n if (!types || types.size === 0) continue;\n if (link.acceptedClasses.some((c) => types.has(c))) continue;\n const key = `${link.property} ${link.id}`;\n if (seen.has(key)) continue;\n seen.add(key);\n const typed = [...types].map((t) => `<${t}>`).join(', ');\n const others = link.acceptedClasses.filter((c) => c !== link.expectedClass);\n const alsoAccepted = others.length\n ? ` (or of ${others.map((c) => `<${c}>`).join(', ')}, a registered sub-shape or sh:class)`\n : '';\n messages.push(\n `Cannot link <${link.id}> into the contains property \"${link.label}\" of <${link.shape}>: ` +\n `it is typed ${typed}, but \"${link.label}\" only owns instances of ` +\n `<${link.expectedClass}>${alsoAccepted}. An owner deletes what it contains when the ` +\n 'value is replaced or removed, or when the owner is deleted.',\n );\n }\n if (messages.length > 0) throw new Error(messages.join('\\n'));\n}\n"]}
@@ -66,6 +66,13 @@ export declare abstract class LinkedStorage {
66
66
  * reached after an identity lookup has already failed.
67
67
  */
68
68
  private static findPinByShapeUri;
69
+ /**
70
+ * The dataset a query routes to, resolved in the catalog the query captured. Only the
71
+ * routing runs in that catalog: the caller then calls the dataset in the scope it was
72
+ * called in, so a router's own queries (CN's `BranchDataFork` lookups) never run inside
73
+ * a project's catalog. The dataset re-enters from the query or its IR where it lowers.
74
+ */
75
+ private static resolveDatasetForQuery;
69
76
  private static resolveDatasetForQueryShape;
70
77
  /**
71
78
  * Route a select query — **and a count**, which is a select.
@@ -10,7 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  var _a, _b, _c;
11
11
  import { CoreSet } from '../collections/CoreSet.js';
12
12
  import { setQueryDispatch, resolveExistence } from '../queries/queryDispatch.js';
13
- import { getShapeClass } from './ShapeClass.js';
13
+ import { catalogOf, getActiveShapeCatalog, getShapeClass, outsideShapeCatalog, withShapeCatalog, } from './ShapeClass.js';
14
14
  // plan-011 — count physical evaluations of THIS module on the one shared
15
15
  // global object. With the single-loader fix there should be exactly one copy;
16
16
  // the single-instance guard in backend.ts reports this count if storage config
@@ -211,6 +211,19 @@ export class LinkedStorage {
211
211
  }
212
212
  return undefined;
213
213
  }
214
+ /**
215
+ * The dataset a query routes to, resolved in the catalog the query captured. Only the
216
+ * routing runs in that catalog: the caller then calls the dataset in the scope it was
217
+ * called in, so a router's own queries (CN's `BranchDataFork` lookups) never run inside
218
+ * a project's catalog. The dataset re-enters from the query or its IR where it lowers.
219
+ */
220
+ static resolveDatasetForQuery(query) {
221
+ const catalog = catalogOf(query);
222
+ if (catalog !== getActiveShapeCatalog()) {
223
+ return withShapeCatalog(catalog, () => this.resolveDatasetForQuery(query));
224
+ }
225
+ return this.resolveDatasetForQueryShape(query.shape);
226
+ }
214
227
  static resolveDatasetForQueryShape(shape) {
215
228
  if (!shape) {
216
229
  return routingState.defaultDataset;
@@ -247,11 +260,14 @@ export class LinkedStorage {
247
260
  if (!(query === null || query === void 0 ? void 0 : query.shape)) {
248
261
  return Promise.reject(new Error('Invalid select query passed to LinkedStorage.selectQuery(): missing shape.'));
249
262
  }
250
- const dataset = this.resolveDatasetForQueryShape(query.shape);
263
+ const dataset = this.resolveDatasetForQuery(query);
251
264
  if (!(dataset === null || dataset === void 0 ? void 0 : dataset.selectQuery)) {
252
265
  return Promise.reject(new Error('No query dataset configured. Call LinkedStorage.setDefaultDataset().'));
253
266
  }
254
- return dataset.selectQuery(query);
267
+ // Called outside any catalog: a router's own queries never run in the query's catalog.
268
+ return (getActiveShapeCatalog() === undefined
269
+ ? dataset.selectQuery(query)
270
+ : outsideShapeCatalog(() => dataset.selectQuery(query)));
255
271
  }
256
272
  /**
257
273
  * Route an ask query.
@@ -277,11 +293,13 @@ export class LinkedStorage {
277
293
  if (!(query === null || query === void 0 ? void 0 : query.shape)) {
278
294
  return this.askAnyDataset(query);
279
295
  }
280
- const dataset = this.resolveDatasetForQueryShape(query.shape);
296
+ const dataset = this.resolveDatasetForQuery(query);
281
297
  if (!dataset) {
282
298
  throw new Error('No query dataset configured. Call LinkedStorage.setDefaultDataset().');
283
299
  }
284
- return resolveExistence(dataset, query);
300
+ return getActiveShapeCatalog() === undefined
301
+ ? resolveExistence(dataset, query)
302
+ : outsideShapeCatalog(() => resolveExistence(dataset, query));
285
303
  });
286
304
  }
287
305
  /**
@@ -300,7 +318,10 @@ export class LinkedStorage {
300
318
  throw new Error('No query dataset configured. Call LinkedStorage.setDefaultDataset().');
301
319
  }
302
320
  for (const dataset of datasets) {
303
- if (yield resolveExistence(dataset, query))
321
+ const exists = getActiveShapeCatalog() === undefined
322
+ ? resolveExistence(dataset, query)
323
+ : outsideShapeCatalog(() => resolveExistence(dataset, query));
324
+ if (yield exists)
304
325
  return true;
305
326
  }
306
327
  return false;
@@ -310,31 +331,37 @@ export class LinkedStorage {
310
331
  if (!(query === null || query === void 0 ? void 0 : query.shape)) {
311
332
  return Promise.reject(new Error('Invalid update query passed to LinkedStorage.updateQuery(): missing shape.'));
312
333
  }
313
- const dataset = this.resolveDatasetForQueryShape(query.shape);
334
+ const dataset = this.resolveDatasetForQuery(query);
314
335
  if (!(dataset === null || dataset === void 0 ? void 0 : dataset.updateQuery)) {
315
336
  return Promise.reject(new Error('No update handler configured on the query dataset.'));
316
337
  }
317
- return dataset.updateQuery(query);
338
+ return (getActiveShapeCatalog() === undefined
339
+ ? dataset.updateQuery(query)
340
+ : outsideShapeCatalog(() => dataset.updateQuery(query)));
318
341
  }
319
342
  static createQuery(query) {
320
343
  if (!(query === null || query === void 0 ? void 0 : query.shape)) {
321
344
  return Promise.reject(new Error('Invalid create query passed to LinkedStorage.createQuery(): missing shape.'));
322
345
  }
323
- const dataset = this.resolveDatasetForQueryShape(query.shape);
346
+ const dataset = this.resolveDatasetForQuery(query);
324
347
  if (!(dataset === null || dataset === void 0 ? void 0 : dataset.createQuery)) {
325
348
  return Promise.reject(new Error('No create handler configured on the query dataset.'));
326
349
  }
327
- return dataset.createQuery(query);
350
+ return (getActiveShapeCatalog() === undefined
351
+ ? dataset.createQuery(query)
352
+ : outsideShapeCatalog(() => dataset.createQuery(query)));
328
353
  }
329
354
  static deleteQuery(query) {
330
355
  if (!(query === null || query === void 0 ? void 0 : query.shape)) {
331
356
  return Promise.reject(new Error('Invalid delete query passed to LinkedStorage.deleteQuery(): missing shape.'));
332
357
  }
333
- const dataset = this.resolveDatasetForQueryShape(query.shape);
358
+ const dataset = this.resolveDatasetForQuery(query);
334
359
  if (!(dataset === null || dataset === void 0 ? void 0 : dataset.deleteQuery)) {
335
360
  return Promise.reject(new Error('No delete handler configured on the query dataset.'));
336
361
  }
337
- return dataset.deleteQuery(query);
362
+ return getActiveShapeCatalog() === undefined
363
+ ? dataset.deleteQuery(query)
364
+ : outsideShapeCatalog(() => dataset.deleteQuery(query));
338
365
  }
339
366
  }
340
367
  //# sourceMappingURL=LinkedStorage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"LinkedStorage.js","sourceRoot":"","sources":["../../../src/utils/LinkedStorage.ts"],"names":[],"mappings":"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","sourcesContent":["import {CoreSet} from '../collections/CoreSet.js';\nimport type {IDataset} from '../interfaces/IDataset.js';\nimport 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 {setQueryDispatch, resolveExistence} from '../queries/queryDispatch.js';\nimport {getShapeClass} from './ShapeClass.js';\nimport type {NodeShapeData} from '../shapes/SHACL.js';\n\n// plan-011 — count physical evaluations of THIS module on the one shared\n// global object. With the single-loader fix there should be exactly one copy;\n// the single-instance guard in backend.ts reports this count if storage config\n// ever lands on a different copy. Deliberately avoids Date/Math.random\n// (unavailable / non-deterministic per repo constraints).\nconst linkedStorageGlobal: any =\n typeof globalThis !== 'undefined' ? globalThis : ({} as any);\nlinkedStorageGlobal.__linkedStorageInstanceCount =\n (linkedStorageGlobal.__linkedStorageInstanceCount ?? 0) + 1;\n\n/**\n * Primary routing layer (arch-04 §The IDataset abstraction).\n *\n * Resolves an incoming Linked Query to the IDataset that should handle it,\n * based on the query's target shape. Composite IDatasets (gateways, routers,\n * forwarders, resolvers) plug in here just like storage IDatasets do.\n *\n * The \"Dataset\" naming aligns with the IDataset contract. Earlier \"Store\"\n * names were renamed in phase-1 — see docs/plans/002-phase-1-create-user-project-flow.md.\n */\n/**\n * The routing table lives on the shared global, beside the shape registries.\n *\n * It has to, for the same reason they do: a module can evaluate more than once in a\n * process, and a class static then exists once per copy. Leaving these two here while\n * the registries were shared was worse than either extreme — a second copy answered\n * `isInitialised()` with false and `getDatasetForShapeClass()` with the default, while\n * reporting a fully populated shape registry.\n */\nconst routingState: {\n defaultDataset?: IDataset;\n shapeToDataset: Map<Function, IDataset>;\n listeners?: Set<() => void>;\n} = (linkedStorageGlobal.__linkedStorageRouting ??= {\n defaultDataset: undefined,\n shapeToDataset: new Map(),\n listeners: new Set(),\n});\n// An older copy of this module may have created the record without listeners.\nroutingState.listeners ??= new Set();\n\n/** Tell routing-change listeners that the set of datasets may have changed. */\nfunction notifyRoutingChanged(): void {\n for (const listener of [...routingState.listeners!]) {\n try {\n listener();\n } catch (err) {\n console.error('[linked] routing-change listener failed', err);\n }\n }\n}\n\n/** A shape class's IRI, or undefined for anything that is not a registered shape. */\nconst shapeUriOf = (shapeClass?: Function | null): string | undefined => {\n const uri = (shapeClass as {shape?: {id?: unknown}} | null | undefined)?.shape?.id;\n return typeof uri === 'string' && uri ? uri : undefined;\n};\n\nexport abstract class LinkedStorage {\n\n /** plan-011 — how many physical copies of this module have evaluated. */\n static getLoadedInstanceCount(): number {\n return linkedStorageGlobal.__linkedStorageInstanceCount ?? 0;\n }\n\n static isInitialised() {\n return !!routingState.defaultDataset;\n }\n\n /** The catch-all IDataset for shapes with no explicit mapping. */\n static getDefaultDataset() {\n return routingState.defaultDataset;\n }\n\n /** Set the default IDataset (catch-all for shapes with no explicit mapping). */\n static setDefaultDataset(dataset: IDataset) {\n routingState.defaultDataset = dataset;\n if (routingState.defaultDataset?.init) {\n routingState.defaultDataset.init();\n }\n setQueryDispatch({\n selectQuery: (q) => this.selectQuery(q),\n askQuery: (q) => this.askQuery(q),\n createQuery: (q) => this.createQuery(q),\n updateQuery: (q) => this.updateQuery(q),\n deleteQuery: (q) => this.deleteQuery(q),\n });\n notifyRoutingChanged();\n }\n\n /**\n * Be told whenever the set of datasets changes (`setDefaultDataset`,\n * `setDatasetForShapes`, `unsetDatasetForShape`). The live-query store uses\n * this to pick up a dataset's change feed. Returns the unsubscribe function.\n */\n static onRoutingChanged(listener: () => void): () => void {\n routingState.listeners!.add(listener);\n return () => routingState.listeners!.delete(listener);\n }\n\n /** Pin one or more shape classes to a specific IDataset implementer. */\n static setDatasetForShapes(dataset: IDataset, ...shapeClasses: Function[]) {\n shapeClasses.forEach((shapeClass) => {\n // A shape's IRI is its identity, so two classes claiming the same IRI are the\n // same shape and must not route to different datasets. That situation is a\n // duplicate-registration bug elsewhere; say so here rather than resolving it by\n // Map insertion order, which is silent and arbitrary.\n const uri = shapeUriOf(shapeClass);\n if (uri) {\n for (const [pinned, existing] of routingState.shapeToDataset) {\n if (pinned !== shapeClass && shapeUriOf(pinned) === uri && existing !== dataset) {\n console.warn(\n `[linked] Two different classes are pinned to '${uri}' with different ` +\n `datasets. They are the same shape by identity, so one of these pins ` +\n `will be ignored. This usually means the shape class was registered twice.`,\n );\n }\n }\n }\n routingState.shapeToDataset.set(shapeClass, dataset);\n });\n notifyRoutingChanged();\n }\n\n /**\n * Remove the pin for a shape, by class or by IRI.\n *\n * Removes **every** entry sharing that IRI, not just the one class object handed in.\n * Deleting by class identity alone is not enough: resolution falls back to matching\n * on IRI, so another class claiming the same shape would silently resurrect the pin\n * the caller believed they had removed.\n */\n static unsetDatasetForShape(shape: Function | string): void {\n const uri = typeof shape === 'string' ? shape : shapeUriOf(shape);\n routingState.shapeToDataset.delete(shape as Function);\n if (uri) {\n for (const pinned of [...routingState.shapeToDataset.keys()]) {\n if (shapeUriOf(pinned) === uri) {\n routingState.shapeToDataset.delete(pinned);\n }\n }\n }\n notifyRoutingChanged();\n }\n\n /** Every IDataset known to the primary router (default + all pinned). */\n static getDatasets(): CoreSet<IDataset> {\n const datasets = new CoreSet<IDataset>();\n if (routingState.defaultDataset) {\n datasets.add(routingState.defaultDataset);\n }\n routingState.shapeToDataset.forEach((dataset) => datasets.add(dataset));\n return datasets;\n }\n\n /**\n * The live shape→IDataset map — not a copy.\n *\n * Mutating it works but is not the supported way to remove a pin: deleting a key\n * here removes one class object, and resolution falls back to matching on shape IRI,\n * so another class claiming the same shape resurrects the pin. Use\n * `unsetDatasetForShape`, which removes every entry for that identity.\n */\n static getShapeToDatasetMap(): Map<Function, IDataset> {\n return routingState.shapeToDataset;\n }\n\n /** Resolve the IDataset for a given shape class. Walks the prototype chain. */\n static getDatasetForShapeClass(shapeClass?: Function | null): IDataset | undefined {\n let current: Function | null = shapeClass ?? null;\n while (typeof current === 'function') {\n const dataset = routingState.shapeToDataset.get(current);\n if (dataset) {\n return dataset;\n }\n const parent = Object.getPrototypeOf(current);\n if (parent === Function.prototype || parent === null) break;\n current = parent;\n }\n // Only once the identity walk has failed. `static shape` is inherited, so running\n // this at every level of the chain repeated the same scan for the same IRI.\n return this.findPinByShapeUri(shapeClass) ?? routingState.defaultDataset;\n }\n\n /**\n * The same pins, matched on shape IRI rather than on class identity.\n *\n * A class object is only a usable key while exactly one copy of the module\n * that declares it has evaluated, and in a built app there is not: the\n * backend runs from `lib/`, while `linked.backend.storage` is loaded from the\n * app root and imports the app's shapes from `src/`. The pin lands on one\n * copy of a shape class and the query resolves the other. The two are `!==`,\n * the lookup misses, and the query silently falls through to the default\n * dataset — which is how a shape that IS pinned reaches the app-data router\n * and fails there. Dev loads both halves from `src`, so it only breaks in\n * production.\n *\n * A shape's IRI does not have that problem: it is the shape's identity, it is\n * what the query already carries on the wire, and every copy derives the same\n * one. So it is the key that survives duplication.\n *\n * Deliberately a scan of the one map rather than a second map kept alongside it:\n * one source of truth, over a map holding a few dozen entries, on a path only\n * reached after an identity lookup has already failed.\n */\n private static findPinByShapeUri(shapeClass?: Function | null): IDataset | undefined {\n const uri = shapeUriOf(shapeClass);\n if (!uri) return undefined;\n for (const [pinned, dataset] of routingState.shapeToDataset) {\n if (shapeUriOf(pinned) === uri) {\n return dataset;\n }\n }\n return undefined;\n }\n\n private static resolveDatasetForQueryShape(\n shape?: string | Function | NodeShapeData | null,\n ): IDataset | undefined {\n if (!shape) {\n return routingState.defaultDataset;\n }\n if (typeof shape === 'function') {\n return this.getDatasetForShapeClass(shape);\n }\n if (typeof shape === 'string') {\n const shapeClass = getShapeClass(shape);\n return this.getDatasetForShapeClass(shapeClass);\n }\n // NodeShapeData (the closed query's `shape` accessor) — resolve via its IRI.\n if (typeof shape === 'object' && 'id' in shape) {\n const shapeClass = getShapeClass((shape as {id: string}).id);\n return this.getDatasetForShapeClass(shapeClass);\n }\n return routingState.defaultDataset;\n }\n\n /**\n * Route a select query — **and a count**, which is a select.\n *\n * A count is not a separate query form (an ask is: `ASK WHERE { … }`); it is\n * `SELECT (COUNT(DISTINCT ?s) AS ?count) WHERE { … }`, routed by the same shape to\n * the same dataset and answered over the same channel. So it needs no arm of its\n * own here — which is the point: when it had one, this router was missing it, and\n * `.count()` failed on every path that went through the router while passing\n * against a store held directly.\n *\n * The count contract — a real, non-negative integer, a failure never flattened\n * into `0` — belongs to `resolveCount`, which the builder goes through. None of it\n * is re-implemented here, exactly as none of `resolveExistence` is.\n */\n static selectQuery<ResultType>(\n query: SelectQuery | CountQuery,\n ): Promise<ResultType> {\n if (!query?.shape) {\n return Promise.reject(\n new Error(\n 'Invalid select query passed to LinkedStorage.selectQuery(): missing shape.',\n ),\n );\n }\n const dataset = this.resolveDatasetForQueryShape(query.shape);\n if (!dataset?.selectQuery) {\n return Promise.reject(\n new Error('No query dataset configured. Call LinkedStorage.setDefaultDataset().'),\n );\n }\n return dataset.selectQuery(query) as Promise<ResultType>;\n }\n\n /**\n * Route an ask query.\n *\n * A **shaped** ask routes like every other query — by its shape, to one dataset.\n *\n * A **shapeless** ask (\"does a node with this IRI exist at all\") has no shape,\n * and therefore no routing key. Asking only the default dataset would answer\n * `false` for a node that exists in a pinned one — a wrong answer, quietly. So a\n * router must ask every dataset it knows and OR the results. That is cheap\n * precisely because the answers are booleans: the fan-out short-circuits on the\n * first `true`, where the same sweep for rows could not.\n *\n * Any other router implementing `IDataset` inherits this obligation: a shapeless\n * ask means \"anywhere I can reach\", not \"in my default store\".\n *\n * The contract itself — a real boolean, errors never flattened to `false` —\n * belongs to `resolveExistence`, which the builder also goes through. None of it\n * is re-implemented here.\n */\n static async askQuery(query: AskQuery): Promise<boolean> {\n if (!query?.shape) {\n return this.askAnyDataset(query);\n }\n const dataset = this.resolveDatasetForQueryShape(query.shape);\n if (!dataset) {\n throw new Error(\n 'No query dataset configured. Call LinkedStorage.setDefaultDataset().',\n );\n }\n return resolveExistence(dataset, query);\n }\n\n /**\n * Fan a shapeless ask out across every known dataset, resolving `true` as soon\n * as one answers `true`.\n *\n * Sequential rather than parallel: the common case is a single dataset, and an\n * early `true` should not have already cost a query against every other store.\n * A failure from any dataset propagates — an unreachable store makes the answer\n * unknown, and \"unknown\" is not `false`.\n */\n private static async askAnyDataset(query: AskQuery): Promise<boolean> {\n const datasets = this.getDatasets();\n if (datasets.size === 0) {\n throw new Error(\n 'No query dataset configured. Call LinkedStorage.setDefaultDataset().',\n );\n }\n for (const dataset of datasets) {\n if (await resolveExistence(dataset, query)) return true;\n }\n return false;\n }\n\n static updateQuery<ResponseType>(query: UpdateQuery): Promise<ResponseType> {\n if (!query?.shape) {\n return Promise.reject(\n new Error(\n 'Invalid update query passed to LinkedStorage.updateQuery(): missing shape.',\n ),\n );\n }\n const dataset = this.resolveDatasetForQueryShape(query.shape);\n if (!dataset?.updateQuery) {\n return Promise.reject(\n new Error('No update handler configured on the query dataset.'),\n );\n }\n return dataset.updateQuery(query) as Promise<ResponseType>;\n }\n\n static createQuery<ResponseType>(query: CreateQuery): Promise<ResponseType> {\n if (!query?.shape) {\n return Promise.reject(\n new Error(\n 'Invalid create query passed to LinkedStorage.createQuery(): missing shape.',\n ),\n );\n }\n const dataset = this.resolveDatasetForQueryShape(query.shape);\n if (!dataset?.createQuery) {\n return Promise.reject(\n new Error('No create handler configured on the query dataset.'),\n );\n }\n return dataset.createQuery(query) as Promise<ResponseType>;\n }\n\n static deleteQuery(query: DeleteQuery): Promise<DeleteResponse> {\n if (!query?.shape) {\n return Promise.reject(\n new Error(\n 'Invalid delete query passed to LinkedStorage.deleteQuery(): missing shape.',\n ),\n );\n }\n const dataset = this.resolveDatasetForQueryShape(query.shape);\n if (!dataset?.deleteQuery) {\n return Promise.reject(\n new Error('No delete handler configured on the query dataset.'),\n );\n }\n return dataset.deleteQuery(query);\n }\n}\n"]}
1
+ {"version":3,"file":"LinkedStorage.js","sourceRoot":"","sources":["../../../src/utils/LinkedStorage.ts"],"names":[],"mappings":"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","sourcesContent":["import {CoreSet} from '../collections/CoreSet.js';\nimport type {IDataset} from '../interfaces/IDataset.js';\nimport 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 {setQueryDispatch, resolveExistence} from '../queries/queryDispatch.js';\nimport {\n catalogOf,\n getActiveShapeCatalog,\n getShapeClass,\n outsideShapeCatalog,\n withShapeCatalog,\n} from './ShapeClass.js';\nimport type {NodeShapeData} from '../shapes/SHACL.js';\n\n// plan-011 — count physical evaluations of THIS module on the one shared\n// global object. With the single-loader fix there should be exactly one copy;\n// the single-instance guard in backend.ts reports this count if storage config\n// ever lands on a different copy. Deliberately avoids Date/Math.random\n// (unavailable / non-deterministic per repo constraints).\nconst linkedStorageGlobal: any =\n typeof globalThis !== 'undefined' ? globalThis : ({} as any);\nlinkedStorageGlobal.__linkedStorageInstanceCount =\n (linkedStorageGlobal.__linkedStorageInstanceCount ?? 0) + 1;\n\n/**\n * Primary routing layer (arch-04 §The IDataset abstraction).\n *\n * Resolves an incoming Linked Query to the IDataset that should handle it,\n * based on the query's target shape. Composite IDatasets (gateways, routers,\n * forwarders, resolvers) plug in here just like storage IDatasets do.\n *\n * The \"Dataset\" naming aligns with the IDataset contract. Earlier \"Store\"\n * names were renamed in phase-1 — see docs/plans/002-phase-1-create-user-project-flow.md.\n */\n/**\n * The routing table lives on the shared global, beside the shape registries.\n *\n * It has to, for the same reason they do: a module can evaluate more than once in a\n * process, and a class static then exists once per copy. Leaving these two here while\n * the registries were shared was worse than either extreme — a second copy answered\n * `isInitialised()` with false and `getDatasetForShapeClass()` with the default, while\n * reporting a fully populated shape registry.\n */\nconst routingState: {\n defaultDataset?: IDataset;\n shapeToDataset: Map<Function, IDataset>;\n listeners?: Set<() => void>;\n} = (linkedStorageGlobal.__linkedStorageRouting ??= {\n defaultDataset: undefined,\n shapeToDataset: new Map(),\n listeners: new Set(),\n});\n// An older copy of this module may have created the record without listeners.\nroutingState.listeners ??= new Set();\n\n/** Tell routing-change listeners that the set of datasets may have changed. */\nfunction notifyRoutingChanged(): void {\n for (const listener of [...routingState.listeners!]) {\n try {\n listener();\n } catch (err) {\n console.error('[linked] routing-change listener failed', err);\n }\n }\n}\n\n/** A shape class's IRI, or undefined for anything that is not a registered shape. */\nconst shapeUriOf = (shapeClass?: Function | null): string | undefined => {\n const uri = (shapeClass as {shape?: {id?: unknown}} | null | undefined)?.shape?.id;\n return typeof uri === 'string' && uri ? uri : undefined;\n};\n\nexport abstract class LinkedStorage {\n\n /** plan-011 — how many physical copies of this module have evaluated. */\n static getLoadedInstanceCount(): number {\n return linkedStorageGlobal.__linkedStorageInstanceCount ?? 0;\n }\n\n static isInitialised() {\n return !!routingState.defaultDataset;\n }\n\n /** The catch-all IDataset for shapes with no explicit mapping. */\n static getDefaultDataset() {\n return routingState.defaultDataset;\n }\n\n /** Set the default IDataset (catch-all for shapes with no explicit mapping). */\n static setDefaultDataset(dataset: IDataset) {\n routingState.defaultDataset = dataset;\n if (routingState.defaultDataset?.init) {\n routingState.defaultDataset.init();\n }\n setQueryDispatch({\n selectQuery: (q) => this.selectQuery(q),\n askQuery: (q) => this.askQuery(q),\n createQuery: (q) => this.createQuery(q),\n updateQuery: (q) => this.updateQuery(q),\n deleteQuery: (q) => this.deleteQuery(q),\n });\n notifyRoutingChanged();\n }\n\n /**\n * Be told whenever the set of datasets changes (`setDefaultDataset`,\n * `setDatasetForShapes`, `unsetDatasetForShape`). The live-query store uses\n * this to pick up a dataset's change feed. Returns the unsubscribe function.\n */\n static onRoutingChanged(listener: () => void): () => void {\n routingState.listeners!.add(listener);\n return () => routingState.listeners!.delete(listener);\n }\n\n /** Pin one or more shape classes to a specific IDataset implementer. */\n static setDatasetForShapes(dataset: IDataset, ...shapeClasses: Function[]) {\n shapeClasses.forEach((shapeClass) => {\n // A shape's IRI is its identity, so two classes claiming the same IRI are the\n // same shape and must not route to different datasets. That situation is a\n // duplicate-registration bug elsewhere; say so here rather than resolving it by\n // Map insertion order, which is silent and arbitrary.\n const uri = shapeUriOf(shapeClass);\n if (uri) {\n for (const [pinned, existing] of routingState.shapeToDataset) {\n if (pinned !== shapeClass && shapeUriOf(pinned) === uri && existing !== dataset) {\n console.warn(\n `[linked] Two different classes are pinned to '${uri}' with different ` +\n `datasets. They are the same shape by identity, so one of these pins ` +\n `will be ignored. This usually means the shape class was registered twice.`,\n );\n }\n }\n }\n routingState.shapeToDataset.set(shapeClass, dataset);\n });\n notifyRoutingChanged();\n }\n\n /**\n * Remove the pin for a shape, by class or by IRI.\n *\n * Removes **every** entry sharing that IRI, not just the one class object handed in.\n * Deleting by class identity alone is not enough: resolution falls back to matching\n * on IRI, so another class claiming the same shape would silently resurrect the pin\n * the caller believed they had removed.\n */\n static unsetDatasetForShape(shape: Function | string): void {\n const uri = typeof shape === 'string' ? shape : shapeUriOf(shape);\n routingState.shapeToDataset.delete(shape as Function);\n if (uri) {\n for (const pinned of [...routingState.shapeToDataset.keys()]) {\n if (shapeUriOf(pinned) === uri) {\n routingState.shapeToDataset.delete(pinned);\n }\n }\n }\n notifyRoutingChanged();\n }\n\n /** Every IDataset known to the primary router (default + all pinned). */\n static getDatasets(): CoreSet<IDataset> {\n const datasets = new CoreSet<IDataset>();\n if (routingState.defaultDataset) {\n datasets.add(routingState.defaultDataset);\n }\n routingState.shapeToDataset.forEach((dataset) => datasets.add(dataset));\n return datasets;\n }\n\n /**\n * The live shape→IDataset map — not a copy.\n *\n * Mutating it works but is not the supported way to remove a pin: deleting a key\n * here removes one class object, and resolution falls back to matching on shape IRI,\n * so another class claiming the same shape resurrects the pin. Use\n * `unsetDatasetForShape`, which removes every entry for that identity.\n */\n static getShapeToDatasetMap(): Map<Function, IDataset> {\n return routingState.shapeToDataset;\n }\n\n /** Resolve the IDataset for a given shape class. Walks the prototype chain. */\n static getDatasetForShapeClass(shapeClass?: Function | null): IDataset | undefined {\n let current: Function | null = shapeClass ?? null;\n while (typeof current === 'function') {\n const dataset = routingState.shapeToDataset.get(current);\n if (dataset) {\n return dataset;\n }\n const parent = Object.getPrototypeOf(current);\n if (parent === Function.prototype || parent === null) break;\n current = parent;\n }\n // Only once the identity walk has failed. `static shape` is inherited, so running\n // this at every level of the chain repeated the same scan for the same IRI.\n return this.findPinByShapeUri(shapeClass) ?? routingState.defaultDataset;\n }\n\n /**\n * The same pins, matched on shape IRI rather than on class identity.\n *\n * A class object is only a usable key while exactly one copy of the module\n * that declares it has evaluated, and in a built app there is not: the\n * backend runs from `lib/`, while `linked.backend.storage` is loaded from the\n * app root and imports the app's shapes from `src/`. The pin lands on one\n * copy of a shape class and the query resolves the other. The two are `!==`,\n * the lookup misses, and the query silently falls through to the default\n * dataset — which is how a shape that IS pinned reaches the app-data router\n * and fails there. Dev loads both halves from `src`, so it only breaks in\n * production.\n *\n * A shape's IRI does not have that problem: it is the shape's identity, it is\n * what the query already carries on the wire, and every copy derives the same\n * one. So it is the key that survives duplication.\n *\n * Deliberately a scan of the one map rather than a second map kept alongside it:\n * one source of truth, over a map holding a few dozen entries, on a path only\n * reached after an identity lookup has already failed.\n */\n private static findPinByShapeUri(shapeClass?: Function | null): IDataset | undefined {\n const uri = shapeUriOf(shapeClass);\n if (!uri) return undefined;\n for (const [pinned, dataset] of routingState.shapeToDataset) {\n if (shapeUriOf(pinned) === uri) {\n return dataset;\n }\n }\n return undefined;\n }\n\n /**\n * The dataset a query routes to, resolved in the catalog the query captured. Only the\n * routing runs in that catalog: the caller then calls the dataset in the scope it was\n * called in, so a router's own queries (CN's `BranchDataFork` lookups) never run inside\n * a project's catalog. The dataset re-enters from the query or its IR where it lowers.\n */\n private static resolveDatasetForQuery(query: {shape?: unknown}): IDataset | undefined {\n const catalog = catalogOf(query);\n if (catalog !== getActiveShapeCatalog()) {\n return withShapeCatalog(catalog, () => this.resolveDatasetForQuery(query));\n }\n return this.resolveDatasetForQueryShape(\n query.shape as string | Function | NodeShapeData | null | undefined,\n );\n }\n\n private static resolveDatasetForQueryShape(\n shape?: string | Function | NodeShapeData | null,\n ): IDataset | undefined {\n if (!shape) {\n return routingState.defaultDataset;\n }\n if (typeof shape === 'function') {\n return this.getDatasetForShapeClass(shape);\n }\n if (typeof shape === 'string') {\n const shapeClass = getShapeClass(shape);\n return this.getDatasetForShapeClass(shapeClass);\n }\n // NodeShapeData (the closed query's `shape` accessor) — resolve via its IRI.\n if (typeof shape === 'object' && 'id' in shape) {\n const shapeClass = getShapeClass((shape as {id: string}).id);\n return this.getDatasetForShapeClass(shapeClass);\n }\n return routingState.defaultDataset;\n }\n\n /**\n * Route a select query — **and a count**, which is a select.\n *\n * A count is not a separate query form (an ask is: `ASK WHERE { … }`); it is\n * `SELECT (COUNT(DISTINCT ?s) AS ?count) WHERE { … }`, routed by the same shape to\n * the same dataset and answered over the same channel. So it needs no arm of its\n * own here — which is the point: when it had one, this router was missing it, and\n * `.count()` failed on every path that went through the router while passing\n * against a store held directly.\n *\n * The count contract — a real, non-negative integer, a failure never flattened\n * into `0` — belongs to `resolveCount`, which the builder goes through. None of it\n * is re-implemented here, exactly as none of `resolveExistence` is.\n */\n static selectQuery<ResultType>(\n query: SelectQuery | CountQuery,\n ): Promise<ResultType> {\n if (!query?.shape) {\n return Promise.reject(\n new Error(\n 'Invalid select query passed to LinkedStorage.selectQuery(): missing shape.',\n ),\n );\n }\n const dataset = this.resolveDatasetForQuery(query);\n if (!dataset?.selectQuery) {\n return Promise.reject(\n new Error('No query dataset configured. Call LinkedStorage.setDefaultDataset().'),\n );\n }\n // Called outside any catalog: a router's own queries never run in the query's catalog.\n return (getActiveShapeCatalog() === undefined\n ? dataset.selectQuery(query)\n : outsideShapeCatalog(() => dataset.selectQuery(query))) as Promise<ResultType>;\n }\n\n /**\n * Route an ask query.\n *\n * A **shaped** ask routes like every other query — by its shape, to one dataset.\n *\n * A **shapeless** ask (\"does a node with this IRI exist at all\") has no shape,\n * and therefore no routing key. Asking only the default dataset would answer\n * `false` for a node that exists in a pinned one — a wrong answer, quietly. So a\n * router must ask every dataset it knows and OR the results. That is cheap\n * precisely because the answers are booleans: the fan-out short-circuits on the\n * first `true`, where the same sweep for rows could not.\n *\n * Any other router implementing `IDataset` inherits this obligation: a shapeless\n * ask means \"anywhere I can reach\", not \"in my default store\".\n *\n * The contract itself — a real boolean, errors never flattened to `false` —\n * belongs to `resolveExistence`, which the builder also goes through. None of it\n * is re-implemented here.\n */\n static async askQuery(query: AskQuery): Promise<boolean> {\n if (!query?.shape) {\n return this.askAnyDataset(query);\n }\n const dataset = this.resolveDatasetForQuery(query);\n if (!dataset) {\n throw new Error(\n 'No query dataset configured. Call LinkedStorage.setDefaultDataset().',\n );\n }\n return getActiveShapeCatalog() === undefined\n ? resolveExistence(dataset, query)\n : outsideShapeCatalog(() => resolveExistence(dataset, query));\n }\n\n /**\n * Fan a shapeless ask out across every known dataset, resolving `true` as soon\n * as one answers `true`.\n *\n * Sequential rather than parallel: the common case is a single dataset, and an\n * early `true` should not have already cost a query against every other store.\n * A failure from any dataset propagates — an unreachable store makes the answer\n * unknown, and \"unknown\" is not `false`.\n */\n private static async askAnyDataset(query: AskQuery): Promise<boolean> {\n const datasets = this.getDatasets();\n if (datasets.size === 0) {\n throw new Error(\n 'No query dataset configured. Call LinkedStorage.setDefaultDataset().',\n );\n }\n for (const dataset of datasets) {\n const exists = getActiveShapeCatalog() === undefined\n ? resolveExistence(dataset, query)\n : outsideShapeCatalog(() => resolveExistence(dataset, query));\n if (await exists) return true;\n }\n return false;\n }\n\n static updateQuery<ResponseType>(query: UpdateQuery): Promise<ResponseType> {\n if (!query?.shape) {\n return Promise.reject(\n new Error(\n 'Invalid update query passed to LinkedStorage.updateQuery(): missing shape.',\n ),\n );\n }\n const dataset = this.resolveDatasetForQuery(query);\n if (!dataset?.updateQuery) {\n return Promise.reject(\n new Error('No update handler configured on the query dataset.'),\n );\n }\n return (getActiveShapeCatalog() === undefined\n ? dataset.updateQuery(query)\n : outsideShapeCatalog(() => dataset.updateQuery(query))) as Promise<ResponseType>;\n }\n\n static createQuery<ResponseType>(query: CreateQuery): Promise<ResponseType> {\n if (!query?.shape) {\n return Promise.reject(\n new Error(\n 'Invalid create query passed to LinkedStorage.createQuery(): missing shape.',\n ),\n );\n }\n const dataset = this.resolveDatasetForQuery(query);\n if (!dataset?.createQuery) {\n return Promise.reject(\n new Error('No create handler configured on the query dataset.'),\n );\n }\n return (getActiveShapeCatalog() === undefined\n ? dataset.createQuery(query)\n : outsideShapeCatalog(() => dataset.createQuery(query))) as Promise<ResponseType>;\n }\n\n static deleteQuery(query: DeleteQuery): Promise<DeleteResponse> {\n if (!query?.shape) {\n return Promise.reject(\n new Error(\n 'Invalid delete query passed to LinkedStorage.deleteQuery(): missing shape.',\n ),\n );\n }\n const dataset = this.resolveDatasetForQuery(query);\n if (!dataset?.deleteQuery) {\n return Promise.reject(\n new Error('No delete handler configured on the query dataset.'),\n );\n }\n return getActiveShapeCatalog() === undefined\n ? dataset.deleteQuery(query)\n : outsideShapeCatalog(() => dataset.deleteQuery(query));\n }\n}\n"]}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * A set of shapes a query resolves against without registering them.
3
+ *
4
+ * The global registry is process-wide and last-wins: two projects (or two branches of
5
+ * one) that hold the same shape IRI with different properties cannot both be registered.
6
+ * A `ShapeCatalog` is an immutable object holding one such set; `withShapeCatalog(catalog,
7
+ * fn)` makes it the shapes every registry accessor reads while `fn` runs. Builders, field
8
+ * sets and IRs capture the catalog they were made in and re-enter it wherever they are
9
+ * picked up later, so a query built in a scope lowers, compiles and live-tracks against
10
+ * that catalog after the scope has closed.
11
+ *
12
+ * The catalog is never serialized: DSL-JSON carries IRIs only, and each side of a wire
13
+ * supplies its own catalog (`fromJSON(json, {catalog})`).
14
+ *
15
+ * See docs/architecture/runtime-instances.md.
16
+ */
17
+ import type { Shape } from '../shapes/Shape.js';
18
+ import type { NodeShapeData } from '../shapes/nodeShapeData.js';
19
+ import { type NodeShapeWire } from '../shapes/nodeShapeWire.js';
20
+ import '../shapes/PathNode.js';
21
+ export { withShapeCatalog, getActiveShapeCatalog, catalogOf, shapeCatalogKey, } from './ShapeClass.js';
22
+ /** The IRIs of core's six built-in shapes, which every catalog holds. */
23
+ export declare function builtInShapeIris(): string[];
24
+ export declare class ShapeCatalog {
25
+ /** For log lines only, e.g. `"<projectId>#<branch>"`. */
26
+ readonly name?: string;
27
+ /**
28
+ * Every shape the catalog holds, its own and core's built-ins, by IRI. Read by the
29
+ * registry accessors. A plain field rather than a private one, so a catalog built by
30
+ * one copy of core is readable by another.
31
+ * @internal
32
+ */
33
+ readonly nodeShapes: ReadonlyMap<string, NodeShapeData>;
34
+ /** Core's built-in classes by IRI — the only classes a catalog holds. @internal */
35
+ readonly shapeClasses: ReadonlyMap<string, typeof Shape>;
36
+ private constructor();
37
+ /**
38
+ * Build a catalog from shape metadata or its wire form. Wires are converted as
39
+ * `registerRuntimeShape` converts them (`fromWire`, the reserved-label warning). Unlike the
40
+ * global registry, a row for an IRI that has a compiled class elsewhere is kept: inside a
41
+ * catalog the data is the authority.
42
+ *
43
+ * Core's six built-in shapes always come from core's code, with their classes. `syncShapes`
44
+ * writes them into datasets too, but a row for one of their IRIs is ignored here: the
45
+ * metamodel (its cascades, its routing pins) must not depend on how faithfully a catalog was
46
+ * read, or on which core version last synced the dataset.
47
+ */
48
+ static from(shapes: Iterable<NodeShapeData | NodeShapeWire> | Record<string, NodeShapeData | NodeShapeWire>, options?: {
49
+ name?: string;
50
+ }): ShapeCatalog;
51
+ /** How many shapes the catalog holds, core's built-ins included. */
52
+ get size(): number;
53
+ get(iri: string): NodeShapeData | undefined;
54
+ has(iri: string): boolean;
55
+ /** Every shape the catalog holds, core's built-ins included. */
56
+ values(): IterableIterator<NodeShapeData>;
57
+ }
@@ -0,0 +1,86 @@
1
+ /*
2
+ * This Source Code Form is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
+ */
6
+ import { fromWire, isNodeShapeWire } from '../shapes/nodeShapeWire.js';
7
+ import { getNodeShapeUri } from '../shapes/SHACL.js';
8
+ import { warnOnReservedPropertyLabel } from '../queries/reservedQueryNames.js';
9
+ import { getRegisteredShapeClass } from './ShapeClass.js';
10
+ // Registers PathNode and PathList (PathNode imports PathList), two of the built-ins.
11
+ import '../shapes/PathNode.js';
12
+ export { withShapeCatalog, getActiveShapeCatalog, catalogOf, shapeCatalogKey, } from './ShapeClass.js';
13
+ /**
14
+ * Core's own shapes, which every catalog holds from core's code, with their classes.
15
+ *
16
+ * `syncShapes` writes them into a dataset like any package's shapes, but a catalog ignores
17
+ * those rows: inside a catalog the built-ins resolve from code. Data-only shapes depend
18
+ * on them: a property shape's `type` points at `Shape`, and the shape metamodel itself
19
+ * (`NodeShape`, `PropertyShape`, their `sh:in` lists and complex paths) is how a shape
20
+ * sync reads and writes shapes.
21
+ */
22
+ const BUILT_IN_NAMES = ['Shape', 'NodeShape', 'PropertyShape', 'List', 'PathNode', 'PathList'];
23
+ /** The IRIs of core's six built-in shapes, which every catalog holds. */
24
+ export function builtInShapeIris() {
25
+ return BUILT_IN_NAMES.map((name) => getNodeShapeUri('@_linked/core', name));
26
+ }
27
+ export class ShapeCatalog {
28
+ constructor(nodeShapes, shapeClasses, name) {
29
+ this.nodeShapes = nodeShapes;
30
+ this.shapeClasses = shapeClasses;
31
+ if (name !== undefined)
32
+ this.name = name;
33
+ Object.freeze(this);
34
+ }
35
+ /**
36
+ * Build a catalog from shape metadata or its wire form. Wires are converted as
37
+ * `registerRuntimeShape` converts them (`fromWire`, the reserved-label warning). Unlike the
38
+ * global registry, a row for an IRI that has a compiled class elsewhere is kept: inside a
39
+ * catalog the data is the authority.
40
+ *
41
+ * Core's six built-in shapes always come from core's code, with their classes. `syncShapes`
42
+ * writes them into datasets too, but a row for one of their IRIs is ignored here: the
43
+ * metamodel (its cascades, its routing pins) must not depend on how faithfully a catalog was
44
+ * read, or on which core version last synced the dataset.
45
+ */
46
+ static from(shapes, options) {
47
+ var _a;
48
+ const list = shapes && typeof shapes[Symbol.iterator] === 'function'
49
+ ? shapes
50
+ : Object.values(shapes);
51
+ const nodeShapes = new Map();
52
+ const shapeClasses = new Map();
53
+ for (const iri of builtInShapeIris()) {
54
+ const shapeClass = getRegisteredShapeClass(iri);
55
+ if (!(shapeClass === null || shapeClass === void 0 ? void 0 : shapeClass.shape))
56
+ continue;
57
+ shapeClasses.set(iri, shapeClass);
58
+ nodeShapes.set(iri, shapeClass.shape);
59
+ }
60
+ for (const shape of list) {
61
+ if (!(shape === null || shape === void 0 ? void 0 : shape.id) || shapeClasses.has(shape.id))
62
+ continue;
63
+ const data = isNodeShapeWire(shape) ? fromWire(shape) : shape;
64
+ for (const ps of (_a = data.propertyShapes) !== null && _a !== void 0 ? _a : []) {
65
+ warnOnReservedPropertyLabel(ps.label, data.label, data.id);
66
+ }
67
+ nodeShapes.set(data.id, data);
68
+ }
69
+ return new ShapeCatalog(nodeShapes, shapeClasses, options === null || options === void 0 ? void 0 : options.name);
70
+ }
71
+ /** How many shapes the catalog holds, core's built-ins included. */
72
+ get size() {
73
+ return this.nodeShapes.size;
74
+ }
75
+ get(iri) {
76
+ return this.nodeShapes.get(iri);
77
+ }
78
+ has(iri) {
79
+ return this.nodeShapes.has(iri);
80
+ }
81
+ /** Every shape the catalog holds, core's built-ins included. */
82
+ values() {
83
+ return this.nodeShapes.values();
84
+ }
85
+ }
86
+ //# sourceMappingURL=ShapeCatalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ShapeCatalog.js","sourceRoot":"","sources":["../../../src/utils/ShapeCatalog.ts"],"names":[],"mappings":"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","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\n/**\n * A set of shapes a query resolves against without registering them.\n *\n * The global registry is process-wide and last-wins: two projects (or two branches of\n * one) that hold the same shape IRI with different properties cannot both be registered.\n * A `ShapeCatalog` is an immutable object holding one such set; `withShapeCatalog(catalog,\n * fn)` makes it the shapes every registry accessor reads while `fn` runs. Builders, field\n * sets and IRs capture the catalog they were made in and re-enter it wherever they are\n * picked up later, so a query built in a scope lowers, compiles and live-tracks against\n * that catalog after the scope has closed.\n *\n * The catalog is never serialized: DSL-JSON carries IRIs only, and each side of a wire\n * supplies its own catalog (`fromJSON(json, {catalog})`).\n *\n * See docs/architecture/runtime-instances.md.\n */\n\nimport type {Shape} from '../shapes/Shape.js';\nimport type {NodeShapeData} from '../shapes/nodeShapeData.js';\nimport {fromWire, isNodeShapeWire, type NodeShapeWire} from '../shapes/nodeShapeWire.js';\nimport {getNodeShapeUri} from '../shapes/SHACL.js';\nimport {warnOnReservedPropertyLabel} from '../queries/reservedQueryNames.js';\nimport {getRegisteredShapeClass} from './ShapeClass.js';\n// Registers PathNode and PathList (PathNode imports PathList), two of the built-ins.\nimport '../shapes/PathNode.js';\n\nexport {\n withShapeCatalog,\n getActiveShapeCatalog,\n catalogOf,\n shapeCatalogKey,\n} from './ShapeClass.js';\n\n/**\n * Core's own shapes, which every catalog holds from core's code, with their classes.\n *\n * `syncShapes` writes them into a dataset like any package's shapes, but a catalog ignores\n * those rows: inside a catalog the built-ins resolve from code. Data-only shapes depend\n * on them: a property shape's `type` points at `Shape`, and the shape metamodel itself\n * (`NodeShape`, `PropertyShape`, their `sh:in` lists and complex paths) is how a shape\n * sync reads and writes shapes.\n */\nconst BUILT_IN_NAMES = ['Shape', 'NodeShape', 'PropertyShape', 'List', 'PathNode', 'PathList'];\n\n/** The IRIs of core's six built-in shapes, which every catalog holds. */\nexport function builtInShapeIris(): string[] {\n return BUILT_IN_NAMES.map((name) => getNodeShapeUri('@_linked/core', name));\n}\n\nexport class ShapeCatalog {\n /** For log lines only, e.g. `\"<projectId>#<branch>\"`. */\n readonly name?: string;\n /**\n * Every shape the catalog holds, its own and core's built-ins, by IRI. Read by the\n * registry accessors. A plain field rather than a private one, so a catalog built by\n * one copy of core is readable by another.\n * @internal\n */\n readonly nodeShapes: ReadonlyMap<string, NodeShapeData>;\n /** Core's built-in classes by IRI — the only classes a catalog holds. @internal */\n readonly shapeClasses: ReadonlyMap<string, typeof Shape>;\n\n private constructor(\n nodeShapes: Map<string, NodeShapeData>,\n shapeClasses: Map<string, typeof Shape>,\n name?: string,\n ) {\n this.nodeShapes = nodeShapes;\n this.shapeClasses = shapeClasses;\n if (name !== undefined) this.name = name;\n Object.freeze(this);\n }\n\n /**\n * Build a catalog from shape metadata or its wire form. Wires are converted as\n * `registerRuntimeShape` converts them (`fromWire`, the reserved-label warning). Unlike the\n * global registry, a row for an IRI that has a compiled class elsewhere is kept: inside a\n * catalog the data is the authority.\n *\n * Core's six built-in shapes always come from core's code, with their classes. `syncShapes`\n * writes them into datasets too, but a row for one of their IRIs is ignored here: the\n * metamodel (its cascades, its routing pins) must not depend on how faithfully a catalog was\n * read, or on which core version last synced the dataset.\n */\n static from(\n shapes:\n | Iterable<NodeShapeData | NodeShapeWire>\n | Record<string, NodeShapeData | NodeShapeWire>,\n options?: {name?: string},\n ): ShapeCatalog {\n const list: Iterable<NodeShapeData | NodeShapeWire> =\n shapes && typeof (shapes as Iterable<unknown>)[Symbol.iterator] === 'function'\n ? (shapes as Iterable<NodeShapeData | NodeShapeWire>)\n : Object.values(shapes as Record<string, NodeShapeData | NodeShapeWire>);\n\n const nodeShapes = new Map<string, NodeShapeData>();\n const shapeClasses = new Map<string, typeof Shape>();\n for (const iri of builtInShapeIris()) {\n const shapeClass = getRegisteredShapeClass(iri);\n if (!shapeClass?.shape) continue;\n shapeClasses.set(iri, shapeClass);\n nodeShapes.set(iri, shapeClass.shape);\n }\n for (const shape of list) {\n if (!shape?.id || shapeClasses.has(shape.id)) continue;\n const data = isNodeShapeWire(shape) ? fromWire(shape) : (shape as NodeShapeData);\n for (const ps of data.propertyShapes ?? []) {\n warnOnReservedPropertyLabel(ps.label, data.label, data.id);\n }\n nodeShapes.set(data.id, data);\n }\n return new ShapeCatalog(nodeShapes, shapeClasses, options?.name);\n }\n\n /** How many shapes the catalog holds, core's built-ins included. */\n get size(): number {\n return this.nodeShapes.size;\n }\n\n get(iri: string): NodeShapeData | undefined {\n return this.nodeShapes.get(iri);\n }\n\n has(iri: string): boolean {\n return this.nodeShapes.has(iri);\n }\n\n /** Every shape the catalog holds, core's built-ins included. */\n values(): IterableIterator<NodeShapeData> {\n return this.nodeShapes.values() as IterableIterator<NodeShapeData>;\n }\n}\n"]}
@@ -3,8 +3,65 @@ import { type NodeShapeData, type PropertyShapeData } from '../shapes/nodeShapeD
3
3
  import type { NodeShapeWire } from '../shapes/nodeShapeWire.js';
4
4
  import type { ICoreIterable } from '../interfaces/ICoreIterable.js';
5
5
  import type { NodeReferenceValue } from './NodeReference.js';
6
+ import type { ShapeCatalog } from './ShapeCatalog.js';
7
+ import { RegistryCache } from './registryCache.js';
8
+ export { RegistryCache };
6
9
  /** How many distinct copies of this module are loaded. One is expected. */
7
10
  export declare function getShapeRegistryInstanceCount(): number;
11
+ /**
12
+ * Run `fn` with `catalog` as the shapes every registry accessor reads; `undefined` means
13
+ * the global registry (explicitly — it does not inherit an outer catalog).
14
+ *
15
+ * Synchronous on purpose: it works the same in the browser and in Node, with no
16
+ * AsyncLocalStorage. Scopes nest, the previous catalog is restored in `finally`, and fn's
17
+ * value is returned unchanged. A promise returned by fn is not awaited, so its
18
+ * continuation runs outside the scope. That is why builders and IRs capture their catalog
19
+ * and every place that picks one up later re-enters it (see docs/architecture/runtime-instances.md).
20
+ */
21
+ export declare function withShapeCatalog<T>(catalog: ShapeCatalog | undefined, fn: () => T): T;
22
+ /**
23
+ * Run `fn` with no catalog active, without the promise warning: for core's own calls into a
24
+ * dataset, whose asynchronous continuation is meant to run outside any catalog. @internal
25
+ */
26
+ export declare function outsideShapeCatalog<T>(fn: () => T): T;
27
+ /** The catalog of the current `withShapeCatalog` scope, or undefined for the global registry. */
28
+ export declare function getActiveShapeCatalog(): ShapeCatalog | undefined;
29
+ /**
30
+ * The catalog a builder, a FieldSet or an IR captured — undefined means the global registry.
31
+ * Reads the object's `catalog` field, which builders keep as a getter and IRs as a
32
+ * non-enumerable property, so neither appears in `toJSON` or a JSON round trip.
33
+ */
34
+ export declare function catalogOf(queryOrIr: object | null | undefined): ShapeCatalog | undefined;
35
+ /**
36
+ * Record `catalog` on an IR object as a non-enumerable `catalog` field, so the code that
37
+ * compiles it later (often after an `await`) re-enters the catalog it was lowered in.
38
+ * Nothing is written for the global registry: an IR without the field compiles globally.
39
+ * @internal
40
+ */
41
+ export declare function attachCatalog<T extends object>(ir: T, catalog?: ShapeCatalog | undefined): T;
42
+ /** 0 for the global registry, else a process-unique id for the catalog. */
43
+ export declare function shapeCatalogKey(catalog: ShapeCatalog | undefined): number;
44
+ /**
45
+ * The catalog a builder made from `shape` captures.
46
+ *
47
+ * Inside a scope: the active catalog when the root is an IRI (`shape` undefined) or a
48
+ * shape the catalog holds; otherwise none. A class the active catalog does not hold is
49
+ * authored code (CN's own shapes, built by CN code while a project scope is active) and
50
+ * must lower against the global registry. Outside a scope: the catalog that built the
51
+ * adapter class, if it is one, else none.
52
+ * @internal
53
+ */
54
+ export declare function captureCatalog(shape?: {
55
+ shape?: {
56
+ id?: string;
57
+ };
58
+ } | Function | null): ShapeCatalog | undefined;
59
+ /**
60
+ * Throw when a registry write is attempted inside a catalog scope. A catalog is
61
+ * immutable, and a write meant for it would land in the global registry instead.
62
+ * @internal
63
+ */
64
+ export declare function assertNoActiveCatalog(): void;
8
65
  /**
9
66
  * The current registration version — a monotonic counter, bumped on every write.
10
67
  *
@@ -31,15 +88,11 @@ export declare function registerNodeShape(nodeShape: NodeShapeData): void;
31
88
  export declare function getNodeShape(nodeShape: NodeReferenceValue | {
32
89
  id: string;
33
90
  } | string): NodeShapeData | undefined;
34
- /** Every registered shape's metadata, keyed by node-shape IRI. */
35
- export declare function getAllNodeShapes(): ReadonlyMap<string, NodeShapeData>;
36
91
  /**
37
- * A constructor for a shape known only as data, created on first use.
38
- *
39
- * Returns undefined when the IRI is not registered at all. Prefer `getShapeClass` when
40
- * you specifically need an authored class; use this when you need *something*
41
- * constructor-shaped to drive the query builders.
92
+ * Every registered shape's metadata, keyed by node-shape IRI. Inside a catalog scope:
93
+ * exactly the catalog's shapes plus core's built-ins.
42
94
  */
95
+ export declare function getAllNodeShapes(): ReadonlyMap<string, NodeShapeData>;
43
96
  export declare function getOrCreateShapeAdapter(nodeShape: NodeShapeData | string): typeof Shape | undefined;
44
97
  /** Anything that can identify a shape: its metadata, its class, or its IRI. */
45
98
  export type ShapeLike = NodeShapeData | typeof Shape | Function | string;
@@ -115,11 +168,23 @@ export declare function addNodeShapeToShapeClass(nodeShape: NodeShapeData, shape
115
168
  export declare function resolveShapeConstructor(nodeShape: NodeReferenceValue | {
116
169
  id: string;
117
170
  } | string): ShapeConstructor | undefined;
171
+ /**
172
+ * The constructor the query layer resolves an IRI to. In the global registry it is
173
+ * `getShapeClass` (authored classes only, as always). Inside a catalog, where every shape
174
+ * but core's built-ins is data, it is the catalog's constructor for any shape it holds.
175
+ * @internal
176
+ */
177
+ export declare function getQueryShapeClass(nodeShape: NodeReferenceValue | {
178
+ id: string;
179
+ } | string): ShapeConstructor | undefined;
118
180
  export declare function getShapeClass(nodeShape: NodeReferenceValue | {
119
181
  id: string;
120
182
  } | string): ShapeConstructor | undefined;
183
+ /** The class registered globally for an IRI, whatever catalog is active. @internal */
184
+ export declare function getRegisteredShapeClass(id: string): typeof Shape | undefined;
121
185
  /**
122
- * Returns all registered shape classes (keyed by NodeShape URI).
186
+ * Returns all registered shape classes (keyed by NodeShape URI). Inside a catalog scope:
187
+ * core's built-in classes only — a catalog holds no other class.
123
188
  */
124
189
  export declare function getAllShapeClasses(): Map<string, typeof Shape>;
125
190
  /**