@lde/pipeline-void 0.31.3 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -72,6 +72,27 @@ Global and domain-specific factories accept `VoidStageOptions` (`transform`) and
72
72
  | `detectVocabularies()` | [`entity-properties.rq`](queries/entity-properties.rq) — Entity properties with automatic `void:vocabulary` detection. Accepts `DetectVocabulariesOptions` with an optional `vocabularies` array to extend the built-in defaults. |
73
73
  | `uriSpaces(uriSpaceMap)` | [`object-uri-space.rq`](queries/object-uri-space.rq) — Object URI namespace linksets, aggregated against a provided URI space map |
74
74
 
75
+ ## Namespace normalization
76
+
77
+ Some vocabularies publish under both `http://` and `https://` variants of the same namespace (notably schema.org), and datasets mix them. Without normalization each variant gets its own `void:classPartition`/`void:propertyPartition`, so consumers see two partitions for one class — which crashed the Dataset Register browser (netwerk-digitaal-erfgoed/dataset-knowledge-graph#334).
78
+
79
+ `schemaOrgPartitionMergePlugin` normalizes `http://schema.org/` to `https://schema.org/` **and** merges the duplicate partition nodes the two variants produced. It is a [`beforeDatasetWrite`](../pipeline) plugin — it runs once over a whole dataset’s output at the pipeline edge, so the analysis queries stay unaware of namespace aliases (see [ADR 7](../../docs/decisions/0007-namespace-merge-as-a-dataset-plugin.md)):
80
+
81
+ ```typescript
82
+ import { voidStages, schemaOrgPartitionMergePlugin } from '@lde/pipeline-void';
83
+ import { Pipeline, provenancePlugin } from '@lde/pipeline';
84
+
85
+ await new Pipeline({
86
+ stages: await voidStages(), // plain, no namespace options
87
+ plugins: [schemaOrgPartitionMergePlugin(), provenancePlugin()],
88
+ // …
89
+ }).run();
90
+ ```
91
+
92
+ Use `namespacePartitionMergePlugin(aliases)` for namespaces other than schema.org. The transform streams — it buffers only partition quads (bounded by the summary), passing everything else straight through. Datasets typically use a single schema.org namespace, so within one dataset there is one variant per class and every count stays exact; a dataset that mixes both namespaces on one property has its `void:distinctObjects` summed (an over-count for shared objects) rather than deduped.
93
+
94
+ > This plugin does more than rename IRIs: rewriting the `void:class` objects alone would still leave two `void:classPartition` nodes for one class. If you only need a blanket namespace rewrite over a dataset’s own quads (not a VoID partition merge) — for example when mapping instance data to an application profile — use the generic [`schemaOrgNormalizationPlugin` / `namespaceNormalizationPlugin`](../pipeline) from `@lde/pipeline` instead.
95
+
75
96
  ## Stage transforms
76
97
 
77
98
  A VoID stage decorates its reader’s output with a `QuadTransform<ReaderContext>` attached as data (see [@lde/pipeline](../pipeline)’s extension model and [ADR 2](../../docs/decisions/0002-unify-pipeline-extension-on-quad-transforms.md)). It runs once per reader call and may fire its own SPARQL queries against the `distribution` in scope — so write it to accept being called more than once: a global stage calls it once over the complete output, a per-class stage with batching enabled once per batch (one class at `batchSize: 1`).
package/dist/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export { Stage, NotSupported } from '@lde/pipeline';
2
- export type { AttachedReader, ReaderContext, QuadTransform, } from '@lde/pipeline';
2
+ export type { AttachedReader, NamespaceAlias, ReaderContext, QuadTransform, } from '@lde/pipeline';
3
3
  export * from './stage.js';
4
+ export * from './partitionMerge.js';
5
+ export * from './partitionIri.js';
4
6
  export * from './vocabularyTransform.js';
5
7
  export * from './uriSpaceTransform.js';
6
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACpD,YAAY,EACV,cAAc,EACd,aAAa,EACb,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,cAAc,YAAY,CAAC;AAC3B,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACpD,YAAY,EACV,cAAc,EACd,cAAc,EACd,aAAa,EACb,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC"}
package/dist/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  export { Stage, NotSupported } from '@lde/pipeline';
2
2
  export * from './stage.js';
3
+ export * from './partitionMerge.js';
4
+ export * from './partitionIri.js';
3
5
  export * from './vocabularyTransform.js';
4
6
  export * from './uriSpaceTransform.js';
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Each partition kind's IRI prefix and the ordered SPARQL string expressions
3
+ * whose MD5 hash keys it. The queries must bind the standard component
4
+ * variables (`?class`, `?p`, `?dt`, `?objectClass`, `?lang`).
5
+ */
6
+ declare const PARTITION_KINDS: {
7
+ readonly class: readonly ["STR(?class)"];
8
+ readonly 'class-property': readonly ["STR(?class)", "STR(?p)"];
9
+ readonly datatype: readonly ["STR(?class)", "STR(?p)", "STR(?dt)"];
10
+ readonly 'object-class': readonly ["STR(?class)", "STR(?p)", "STR(?objectClass)"];
11
+ readonly language: readonly ["STR(?class)", "STR(?p)", "?lang"];
12
+ };
13
+ /** A VoID partition kind; also its IRI prefix. */
14
+ export type PartitionKind = keyof typeof PARTITION_KINDS;
15
+ /**
16
+ * Mint a partition IRI from its canonical component *string values* (the class
17
+ * IRI, property IRI, datatype IRI, or language tag — the `STR()` forms the
18
+ * SPARQL side concatenates). The TypeScript counterpart of the query minting.
19
+ */
20
+ export declare function mintPartitionIri(datasetIri: string, kind: PartitionKind, components: readonly string[]): string;
21
+ /**
22
+ * Replace every `#mint:<kind>#` marker in a query with the SPARQL value
23
+ * expression that mints that kind's partition IRI, e.g.
24
+ * `URI(CONCAT(STR(?dataset), "/.well-known/void#class-", MD5(STR(?class))))`.
25
+ */
26
+ export declare function substituteMintMarkers(query: string): string;
27
+ export {};
28
+ //# sourceMappingURL=partitionIri.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"partitionIri.d.ts","sourceRoot":"","sources":["../src/partitionIri.ts"],"names":[],"mappings":"AAoBA;;;;GAIG;AACH,QAAA,MAAM,eAAe;;;;;;CAOX,CAAC;AAEX,kDAAkD;AAClD,MAAM,MAAM,aAAa,GAAG,MAAM,OAAO,eAAe,CAAC;AAEzD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,aAAa,EACnB,UAAU,EAAE,SAAS,MAAM,EAAE,GAC5B,MAAM,CAGR;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAY3D"}
@@ -0,0 +1,56 @@
1
+ import { createHash } from 'node:crypto';
2
+ /**
3
+ * Single source of truth for the VoID partition-IRI scheme
4
+ * (`<dataset>/.well-known/void#<prefix>-<MD5(component…)>`), shared by two
5
+ * derivations that must agree byte-for-byte:
6
+ *
7
+ * - {@link mintPartitionIri} mints an IRI in TypeScript (used by the
8
+ * partition-merge transform to re-key a partition to its canonical form);
9
+ * - {@link substituteMintMarkers} generates the equivalent SPARQL `BIND` value
10
+ * expression, injected into the analysis queries in place of `#mint:<kind>#`.
11
+ *
12
+ * Because both come from {@link PARTITION_KINDS}, a change to the scheme (a
13
+ * prefix, a component, its order) updates the query minting and the TS minting
14
+ * together — they cannot silently diverge.
15
+ */
16
+ /** The `/.well-known/void#` path under a dataset IRI where partitions live. */
17
+ const WELL_KNOWN_VOID = '/.well-known/void#';
18
+ /**
19
+ * Each partition kind's IRI prefix and the ordered SPARQL string expressions
20
+ * whose MD5 hash keys it. The queries must bind the standard component
21
+ * variables (`?class`, `?p`, `?dt`, `?objectClass`, `?lang`).
22
+ */
23
+ const PARTITION_KINDS = {
24
+ class: ['STR(?class)'],
25
+ 'class-property': ['STR(?class)', 'STR(?p)'],
26
+ datatype: ['STR(?class)', 'STR(?p)', 'STR(?dt)'],
27
+ 'object-class': ['STR(?class)', 'STR(?p)', 'STR(?objectClass)'],
28
+ // ?lang is a plain string literal, so it is hashed without STR().
29
+ language: ['STR(?class)', 'STR(?p)', '?lang'],
30
+ };
31
+ /**
32
+ * Mint a partition IRI from its canonical component *string values* (the class
33
+ * IRI, property IRI, datatype IRI, or language tag — the `STR()` forms the
34
+ * SPARQL side concatenates). The TypeScript counterpart of the query minting.
35
+ */
36
+ export function mintPartitionIri(datasetIri, kind, components) {
37
+ const hash = createHash('md5').update(components.join('')).digest('hex');
38
+ return `${datasetIri}${WELL_KNOWN_VOID}${kind}-${hash}`;
39
+ }
40
+ /**
41
+ * Replace every `#mint:<kind>#` marker in a query with the SPARQL value
42
+ * expression that mints that kind's partition IRI, e.g.
43
+ * `URI(CONCAT(STR(?dataset), "/.well-known/void#class-", MD5(STR(?class))))`.
44
+ */
45
+ export function substituteMintMarkers(query) {
46
+ return query.replace(/#mint:([a-z-]+)#/g, (_match, kind) => {
47
+ const components = PARTITION_KINDS[kind];
48
+ if (components === undefined) {
49
+ throw new Error(`Unknown partition kind in #mint:# marker: ${kind}`);
50
+ }
51
+ const hashed = components.length === 1
52
+ ? components[0]
53
+ : `CONCAT(${components.join(', ')})`;
54
+ return `URI(CONCAT(STR(?dataset), "${WELL_KNOWN_VOID}${kind}-", MD5(${hashed})))`;
55
+ });
56
+ }
@@ -0,0 +1,55 @@
1
+ import { type BeforeDatasetWriteContext, type NamespaceAlias, type PipelinePlugin, type QuadTransform } from '@lde/pipeline';
2
+ /**
3
+ * A {@link QuadTransform} for the {@link PipelinePlugin.beforeDatasetWrite} hook
4
+ * that merges the `void:classPartition` / `void:propertyPartition` subtrees of
5
+ * namespace-alias variants (e.g. `http://schema.org/CreativeWork` and
6
+ * `https://schema.org/CreativeWork`) into one partition per canonical
7
+ * class/property.
8
+ *
9
+ * VoID partitions are keyed by an opaque `MD5(class[, property[, …]])` IRI, so
10
+ * two namespace variants produce two partition nodes that, once the class IRI is
11
+ * canonicalized, describe the same class. Seeing a whole dataset's output at
12
+ * once, this transform re-mints every partition IRI from its **canonical** key
13
+ * components via {@link mintPartitionIri} — the single source of truth the
14
+ * queries' SPARQL minting is also generated from — collapses the duplicates,
15
+ * and sums their numeric measures.
16
+ *
17
+ * It streams: only partition quads are buffered (bounded by the summary — a
18
+ * handful of classes × properties, not the dataset), and every other quad
19
+ * passes straight through.
20
+ *
21
+ * Datasets typically use a single schema.org namespace, so within one dataset
22
+ * there is one variant per class and the transform merely renames and re-keys —
23
+ * `void:distinctObjects` and every count stay exact. A dataset that genuinely
24
+ * mixes both namespaces on the same property collapses the variants by summing,
25
+ * which over-counts shared distinct objects; this is not optimized for.
26
+ *
27
+ * With no aliases configured the transform is a no-op.
28
+ */
29
+ export declare function mergeNamespaceVariants(namespaceAliases: readonly NamespaceAlias[]): QuadTransform<BeforeDatasetWriteContext>;
30
+ /**
31
+ * A {@link PipelinePlugin} that canonicalizes schema.org namespace variants in
32
+ * the VoID output — rewriting `http://schema.org/` to `https://schema.org/` —
33
+ * _and_ merges the duplicate partition nodes the two variants produced. Runs on
34
+ * the whole dataset's output via {@link PipelinePlugin.beforeDatasetWrite}, so
35
+ * the analysis queries stay unaware of namespace aliases.
36
+ *
37
+ * This does more than a plain namespace rewrite: rewriting the `void:class`
38
+ * objects alone would leave two `void:classPartition` nodes for the same class.
39
+ * For a non-VoID, blanket namespace rewrite (e.g. mapping instance data to an
40
+ * application profile), use `schemaOrgNormalizationPlugin` from `@lde/pipeline`.
41
+ */
42
+ export declare function schemaOrgPartitionMergePlugin(): PipelinePlugin;
43
+ /**
44
+ * A {@link PipelinePlugin} that canonicalizes the given namespace aliases in the
45
+ * VoID output and merges the duplicate partition nodes their variants produced.
46
+ * Generic form of {@link schemaOrgPartitionMergePlugin}.
47
+ *
48
+ * Required stages: re-keying a datatype/language/object-class partition walks up
49
+ * its `cp → pp → dp` chain, reading `void:class` and `void:property` that
50
+ * `classPartitions` and `classPropertySubjects` emit. Use this plugin with a
51
+ * stage set that includes both (as {@link voidStages} does); without them a
52
+ * void-ext partition cannot be re-keyed and its alias variants ship unmerged.
53
+ */
54
+ export declare function namespacePartitionMergePlugin(namespaceAliases: readonly NamespaceAlias[]): PipelinePlugin;
55
+ //# sourceMappingURL=partitionMerge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"partitionMerge.d.ts","sourceRoot":"","sources":["../src/partitionMerge.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,yBAAyB,EAC9B,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,eAAe,CAAC;AAqEvB;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,sBAAsB,CACpC,gBAAgB,EAAE,SAAS,cAAc,EAAE,GAC1C,aAAa,CAAC,yBAAyB,CAAC,CAgB1C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,6BAA6B,IAAI,cAAc,CAI9D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,6BAA6B,CAC3C,gBAAgB,EAAE,SAAS,cAAc,EAAE,GAC1C,cAAc,CAKhB"}
@@ -0,0 +1,341 @@
1
+ import { canonicalizeIri, } from '@lde/pipeline';
2
+ import { DataFactory } from 'n3';
3
+ import { mintPartitionIri } from './partitionIri.js';
4
+ const { namedNode, literal, quad } = DataFactory;
5
+ const VOID = 'http://rdfs.org/ns/void#';
6
+ const VOID_EXT = 'http://ldf.fi/void-ext#';
7
+ const XSD_INTEGER = 'http://www.w3.org/2001/XMLSchema#integer';
8
+ const SCHEMA_HTTP = 'http://schema.org/';
9
+ const SCHEMA_HTTPS = 'https://schema.org/';
10
+ const VOID_CLASS = `${VOID}class`;
11
+ const VOID_PROPERTY = `${VOID}property`;
12
+ const VOID_ENTITIES = `${VOID}entities`;
13
+ const VOID_TRIPLES = `${VOID}triples`;
14
+ const VOID_DISTINCT_OBJECTS = `${VOID}distinctObjects`;
15
+ const VOID_CLASS_PARTITION = `${VOID}classPartition`;
16
+ const VOID_PROPERTY_PARTITION = `${VOID}propertyPartition`;
17
+ const VOIDEXT_DATATYPE = `${VOID_EXT}datatype`;
18
+ const VOIDEXT_LANGUAGE = `${VOID_EXT}language`;
19
+ const VOIDEXT_DATATYPE_PARTITION = `${VOID_EXT}datatypePartition`;
20
+ const VOIDEXT_OBJECTCLASS_PARTITION = `${VOID_EXT}objectClassPartition`;
21
+ const VOIDEXT_LANGUAGE_PARTITION = `${VOID_EXT}languagePartition`;
22
+ /**
23
+ * Integer-literal measures summed when partitions collapse.
24
+ * `void:distinctObjects` is included: a dataset that uses a single schema.org
25
+ * namespace has one variant per class, so there is nothing to combine and the
26
+ * count is unchanged; only a dataset that mixes both namespaces on the same
27
+ * property (rare) sums two distinct-object counts — an over-count we accept
28
+ * rather than optimize for.
29
+ */
30
+ const NUMERIC_MEASURES = new Set([
31
+ VOID_ENTITIES,
32
+ VOID_TRIPLES,
33
+ VOID_DISTINCT_OBJECTS,
34
+ ]);
35
+ /** Structural links whose object is a child partition node. */
36
+ const CHILD_LINKS = new Set([
37
+ VOID_CLASS_PARTITION,
38
+ VOID_PROPERTY_PARTITION,
39
+ VOIDEXT_DATATYPE_PARTITION,
40
+ VOIDEXT_OBJECTCLASS_PARTITION,
41
+ VOIDEXT_LANGUAGE_PARTITION,
42
+ ]);
43
+ /**
44
+ * Every predicate that describes a partition. A quad with one of these
45
+ * predicates is buffered for merging; every other quad streams through
46
+ * untouched, so the transform's memory is bounded by the VoID summary, not the
47
+ * dataset.
48
+ */
49
+ const PARTITION_VOCABULARY = new Set([
50
+ VOID_CLASS,
51
+ VOID_PROPERTY,
52
+ VOIDEXT_DATATYPE,
53
+ VOIDEXT_LANGUAGE,
54
+ // Derived, not re-listed: a measure added to NUMERIC_MEASURES must also be
55
+ // buffered here, or it would stream through un-summed.
56
+ ...NUMERIC_MEASURES,
57
+ ...CHILD_LINKS,
58
+ ]);
59
+ /** `void:class` / `void:property` objects are IRIs subject to canonicalization. */
60
+ const CANONICALIZED_OBJECT_PREDICATES = new Set([VOID_CLASS, VOID_PROPERTY]);
61
+ /**
62
+ * A {@link QuadTransform} for the {@link PipelinePlugin.beforeDatasetWrite} hook
63
+ * that merges the `void:classPartition` / `void:propertyPartition` subtrees of
64
+ * namespace-alias variants (e.g. `http://schema.org/CreativeWork` and
65
+ * `https://schema.org/CreativeWork`) into one partition per canonical
66
+ * class/property.
67
+ *
68
+ * VoID partitions are keyed by an opaque `MD5(class[, property[, …]])` IRI, so
69
+ * two namespace variants produce two partition nodes that, once the class IRI is
70
+ * canonicalized, describe the same class. Seeing a whole dataset's output at
71
+ * once, this transform re-mints every partition IRI from its **canonical** key
72
+ * components via {@link mintPartitionIri} — the single source of truth the
73
+ * queries' SPARQL minting is also generated from — collapses the duplicates,
74
+ * and sums their numeric measures.
75
+ *
76
+ * It streams: only partition quads are buffered (bounded by the summary — a
77
+ * handful of classes × properties, not the dataset), and every other quad
78
+ * passes straight through.
79
+ *
80
+ * Datasets typically use a single schema.org namespace, so within one dataset
81
+ * there is one variant per class and the transform merely renames and re-keys —
82
+ * `void:distinctObjects` and every count stay exact. A dataset that genuinely
83
+ * mixes both namespaces on the same property collapses the variants by summing,
84
+ * which over-counts shared distinct objects; this is not optimized for.
85
+ *
86
+ * With no aliases configured the transform is a no-op.
87
+ */
88
+ export function mergeNamespaceVariants(namespaceAliases) {
89
+ if (namespaceAliases.length === 0) {
90
+ return (quads) => quads;
91
+ }
92
+ return async function* (quads, { dataset }) {
93
+ const datasetIri = dataset.iri.toString();
94
+ const partitionQuads = [];
95
+ for await (const q of quads) {
96
+ if (PARTITION_VOCABULARY.has(q.predicate.value)) {
97
+ partitionQuads.push(q);
98
+ }
99
+ else {
100
+ yield q;
101
+ }
102
+ }
103
+ yield* mergeBuffered(partitionQuads, datasetIri, namespaceAliases);
104
+ };
105
+ }
106
+ /**
107
+ * A {@link PipelinePlugin} that canonicalizes schema.org namespace variants in
108
+ * the VoID output — rewriting `http://schema.org/` to `https://schema.org/` —
109
+ * _and_ merges the duplicate partition nodes the two variants produced. Runs on
110
+ * the whole dataset's output via {@link PipelinePlugin.beforeDatasetWrite}, so
111
+ * the analysis queries stay unaware of namespace aliases.
112
+ *
113
+ * This does more than a plain namespace rewrite: rewriting the `void:class`
114
+ * objects alone would leave two `void:classPartition` nodes for the same class.
115
+ * For a non-VoID, blanket namespace rewrite (e.g. mapping instance data to an
116
+ * application profile), use `schemaOrgNormalizationPlugin` from `@lde/pipeline`.
117
+ */
118
+ export function schemaOrgPartitionMergePlugin() {
119
+ return namespacePartitionMergePlugin([
120
+ { canonical: SCHEMA_HTTPS, alias: SCHEMA_HTTP },
121
+ ]);
122
+ }
123
+ /**
124
+ * A {@link PipelinePlugin} that canonicalizes the given namespace aliases in the
125
+ * VoID output and merges the duplicate partition nodes their variants produced.
126
+ * Generic form of {@link schemaOrgPartitionMergePlugin}.
127
+ *
128
+ * Required stages: re-keying a datatype/language/object-class partition walks up
129
+ * its `cp → pp → dp` chain, reading `void:class` and `void:property` that
130
+ * `classPartitions` and `classPropertySubjects` emit. Use this plugin with a
131
+ * stage set that includes both (as {@link voidStages} does); without them a
132
+ * void-ext partition cannot be re-keyed and its alias variants ship unmerged.
133
+ */
134
+ export function namespacePartitionMergePlugin(namespaceAliases) {
135
+ return {
136
+ name: 'void-namespace-partition-merge',
137
+ beforeDatasetWrite: mergeNamespaceVariants(namespaceAliases),
138
+ };
139
+ }
140
+ function* mergeBuffered(buffered, datasetIri, namespaceAliases) {
141
+ const nodes = indexNodes(buffered);
142
+ const remap = buildRemap(nodes, datasetIri, namespaceAliases);
143
+ const measureSums = new Map();
144
+ const emitted = new Set();
145
+ for (const original of buffered) {
146
+ const subject = remapTerm(original.subject, remap);
147
+ let object = remapTerm(original.object, remap);
148
+ // Canonicalize a void:class/void:property object only when its own
149
+ // partition node is being re-keyed. The top-level property partitions from
150
+ // entity-properties.rq are never merged (their parent is the dataset, not a
151
+ // class), so they keep the source namespace — consumers can still see which
152
+ // namespace the dataset actually uses (see ADR 7).
153
+ if (remap.has(original.subject.value)) {
154
+ object = canonicalizeObject(object, original.predicate.value, namespaceAliases);
155
+ }
156
+ const rewritten = quad(subject, original.predicate, object, original.graph);
157
+ if (NUMERIC_MEASURES.has(original.predicate.value)) {
158
+ accumulateMeasure(measureSums, rewritten);
159
+ continue;
160
+ }
161
+ const key = quadKey(rewritten);
162
+ if (!emitted.has(key)) {
163
+ emitted.add(key);
164
+ yield rewritten;
165
+ }
166
+ }
167
+ for (const sum of measureSums.values()) {
168
+ yield reconstructMeasure(sum);
169
+ }
170
+ }
171
+ function indexNodes(buffered) {
172
+ const nodes = new Map();
173
+ const nodeOf = (value) => {
174
+ let node = nodes.get(value);
175
+ if (!node) {
176
+ node = { values: new Map() };
177
+ nodes.set(value, node);
178
+ }
179
+ return node;
180
+ };
181
+ for (const q of buffered) {
182
+ if (CHILD_LINKS.has(q.predicate.value) &&
183
+ q.object.termType === 'NamedNode') {
184
+ nodeOf(q.object.value).incomingLink = {
185
+ predicate: q.predicate.value,
186
+ parent: q.subject.value,
187
+ };
188
+ }
189
+ else {
190
+ nodeOf(q.subject.value).values.set(q.predicate.value, q.object);
191
+ }
192
+ }
193
+ return nodes;
194
+ }
195
+ /**
196
+ * Build a raw-IRI → canonical-IRI map for every partition node whose canonical
197
+ * key differs from its current IRI.
198
+ */
199
+ function buildRemap(nodes, datasetIri, namespaceAliases) {
200
+ const remap = new Map();
201
+ for (const [iri, node] of nodes) {
202
+ const canonicalIri = canonicalPartitionIri(node, nodes, datasetIri, namespaceAliases);
203
+ if (canonicalIri !== undefined && canonicalIri !== iri) {
204
+ remap.set(iri, canonicalIri);
205
+ }
206
+ }
207
+ return remap;
208
+ }
209
+ /**
210
+ * The canonical partition IRI for a node, or `undefined` if the node is not a
211
+ * partition (no incoming structural link). Replicates the queries' minting:
212
+ * `<dataset>/.well-known/void#<prefix>-<MD5(STR(component)…)>`.
213
+ */
214
+ function canonicalPartitionIri(node, nodes, datasetIri, namespaceAliases) {
215
+ const link = node.incomingLink;
216
+ if (link === undefined)
217
+ return undefined;
218
+ const canon = (value) => canonicalizeIri(value, namespaceAliases);
219
+ const classOf = (partition) => nodes.get(partition)?.values.get(VOID_CLASS)?.value;
220
+ switch (link.predicate) {
221
+ case VOID_CLASS_PARTITION: {
222
+ const klass = node.values.get(VOID_CLASS)?.value;
223
+ return klass
224
+ ? mintPartitionIri(datasetIri, 'class', [canon(klass)])
225
+ : undefined;
226
+ }
227
+ case VOID_PROPERTY_PARTITION: {
228
+ const klass = classOf(link.parent);
229
+ const property = node.values.get(VOID_PROPERTY)?.value;
230
+ return klass && property
231
+ ? mintPartitionIri(datasetIri, 'class-property', [
232
+ canon(klass),
233
+ canon(property),
234
+ ])
235
+ : undefined;
236
+ }
237
+ case VOIDEXT_DATATYPE_PARTITION: {
238
+ const [klass, property] = classProperty(link.parent, nodes);
239
+ const datatype = node.values.get(VOIDEXT_DATATYPE)?.value;
240
+ return klass && property && datatype
241
+ ? mintPartitionIri(datasetIri, 'datatype', [
242
+ canon(klass),
243
+ canon(property),
244
+ datatype,
245
+ ])
246
+ : undefined;
247
+ }
248
+ case VOIDEXT_OBJECTCLASS_PARTITION: {
249
+ const [klass, property] = classProperty(link.parent, nodes);
250
+ const objectClass = node.values.get(VOID_CLASS)?.value;
251
+ return klass && property && objectClass
252
+ ? mintPartitionIri(datasetIri, 'object-class', [
253
+ canon(klass),
254
+ canon(property),
255
+ canon(objectClass),
256
+ ])
257
+ : undefined;
258
+ }
259
+ case VOIDEXT_LANGUAGE_PARTITION: {
260
+ const [klass, property] = classProperty(link.parent, nodes);
261
+ const language = node.values.get(VOIDEXT_LANGUAGE)?.value;
262
+ return klass && property && language !== undefined
263
+ ? mintPartitionIri(datasetIri, 'language', [
264
+ canon(klass),
265
+ canon(property),
266
+ language,
267
+ ])
268
+ : undefined;
269
+ }
270
+ default:
271
+ return undefined;
272
+ }
273
+ }
274
+ /** The (class, property) of a property partition, following it up to its class partition. */
275
+ function classProperty(propertyPartition, nodes) {
276
+ const node = nodes.get(propertyPartition);
277
+ const property = node?.values.get(VOID_PROPERTY)?.value;
278
+ const classPartition = node?.incomingLink?.parent;
279
+ const klass = classPartition
280
+ ? nodes.get(classPartition)?.values.get(VOID_CLASS)?.value
281
+ : undefined;
282
+ return [klass, property];
283
+ }
284
+ function remapTerm(term, remap) {
285
+ if (term.termType === 'NamedNode') {
286
+ const canonical = remap.get(term.value);
287
+ if (canonical !== undefined)
288
+ return namedNode(canonical);
289
+ }
290
+ return term;
291
+ }
292
+ function canonicalizeObject(object, predicate, namespaceAliases) {
293
+ if (CANONICALIZED_OBJECT_PREDICATES.has(predicate) &&
294
+ object.termType === 'NamedNode') {
295
+ const canonical = canonicalizeIri(object.value, namespaceAliases);
296
+ if (canonical !== object.value)
297
+ return namedNode(canonical);
298
+ }
299
+ return object;
300
+ }
301
+ function accumulateMeasure(sums, measure) {
302
+ const key = `${measure.subject.value} ${measure.predicate.value} ${measure.graph.value}`;
303
+ const existing = sums.get(key);
304
+ if (existing === undefined) {
305
+ sums.set(key, {
306
+ subject: measure.subject,
307
+ predicate: measure.predicate,
308
+ graph: measure.graph,
309
+ datatype: measure.object.termType === 'Literal'
310
+ ? measure.object.datatype.value
311
+ : XSD_INTEGER,
312
+ total: parseInteger(measure.object),
313
+ });
314
+ }
315
+ else {
316
+ existing.total += parseInteger(measure.object);
317
+ }
318
+ }
319
+ function parseInteger(object) {
320
+ if (object.termType === 'Literal' && /^[+-]?\d+$/.test(object.value)) {
321
+ return BigInt(object.value);
322
+ }
323
+ return 0n;
324
+ }
325
+ function reconstructMeasure(sum) {
326
+ return quad(sum.subject, sum.predicate, literal(sum.total.toString(), namedNode(sum.datatype)), sum.graph);
327
+ }
328
+ function quadKey(q) {
329
+ return [
330
+ q.subject.value,
331
+ q.predicate.value,
332
+ termKey(q.object),
333
+ q.graph.value,
334
+ ].join(' ');
335
+ }
336
+ function termKey(term) {
337
+ if (term.termType === 'Literal') {
338
+ return `"${term.value}"^^${term.datatype.value}@${term.language}`;
339
+ }
340
+ return term.value;
341
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"stage.d.ts","sourceRoot":"","sources":["../src/stage.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,EAKL,KAAK,aAAa,EAElB,KAAK,aAAa,EACnB,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAezC;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;CAiBnB,CAAC;AAEX,sDAAsD;AACtD,MAAM,MAAM,aAAa,GACvB,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,OAAO,gBAAgB,CAAC,CAAC;AAE3D,2EAA2E;AAC3E,MAAM,MAAM,kBAAkB,GAC1B,aAAa,CAAC,aAAa,CAAC,GAC5B,aAAa,CAAC,aAAa,CAAC,EAAE,CAAC;AAEnC;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,kBAAkB,CAAC;CAChC;AAED;;;;;GAKG;AACH,MAAM,WAAW,wBAAyB,SAAQ,gBAAgB;IAChE,oEAAoE;IACpE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iFAAiF;IACjF,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,iBAAkB,SAAQ,IAAI,CAC7C,wBAAwB,EACxB,WAAW,CACZ;IACC,yEAAyE;IACzE,SAAS,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,IAAI,EAAE,CAAC,CAAC;IACjD,mFAAmF;IACnF,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,kBAAkB,CAAC,CAAC,CAAC;CACjE;AAgED,wBAAgB,gBAAgB,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,KAAK,CAAC,CAE3E;AAED,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,KAAK,CAAC,CAE1E;AAOD,wBAAgB,mBAAmB,CACjC,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,KAAK,CAAC,CAKhB;AAED,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,KAAK,CAAC,CAK1E;AAED,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,KAAK,CAAC,CAK1E;AAED,wBAAgB,aAAa,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,KAAK,CAAC,CAKxE;AAED,wBAAgB,YAAY,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,KAAK,CAAC,CAKvE;AAED,wBAAgB,qBAAqB,CACnC,OAAO,CAAC,EAAE,wBAAwB,GACjC,OAAO,CAAC,KAAK,CAAC,CAKhB;AAED,wBAAgB,oBAAoB,CAClC,OAAO,CAAC,EAAE,wBAAwB,GACjC,OAAO,CAAC,KAAK,CAAC,CAKhB;AAED,wBAAgB,cAAc,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,KAAK,CAAC,CAKzE;AAED,wBAAgB,cAAc,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,KAAK,CAAC,CAEzE;AAID,wBAAgB,qBAAqB,CACnC,OAAO,CAAC,EAAE,wBAAwB,GACjC,OAAO,CAAC,KAAK,CAAC,CAKhB;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,CAAC,EAAE,wBAAwB,GACjC,OAAO,CAAC,KAAK,CAAC,CAKhB;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,CAAC,EAAE,wBAAwB,GACjC,OAAO,CAAC,KAAK,CAAC,CAKhB;AAID,wBAAgB,SAAS,CACvB,WAAW,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,IAAI,EAAE,CAAC,EACjD,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,KAAK,CAAC,CAOhB;AAED,MAAM,WAAW,yBAA0B,SAAQ,gBAAgB;IACjE,mFAAmF;IACnF,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAED,wBAAgB,kBAAkB,CAChC,OAAO,CAAC,EAAE,yBAAyB,GAClC,OAAO,CAAC,KAAK,CAAC,CAQhB;AAED;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC9B,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,KAAK,EAAE,CAAC,CAgDlB"}
1
+ {"version":3,"file":"stage.d.ts","sourceRoot":"","sources":["../src/stage.ts"],"names":[],"mappings":"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"}
package/dist/stage.js CHANGED
@@ -4,6 +4,7 @@ import { resolve, dirname } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { withVocabularies, defaultVocabularies, } from './vocabularyTransform.js';
6
6
  import { withUriSpaces } from './uriSpaceTransform.js';
7
+ import { substituteMintMarkers } from './partitionIri.js';
7
8
  const queriesDir = resolve(dirname(fileURLToPath(import.meta.url)), '..', 'queries');
8
9
  /**
9
10
  * Stable names for every VoID stage, equal to the underlying query filename.
@@ -31,7 +32,7 @@ export const VOID_STAGE_NAMES = {
31
32
  objectUriSpace: 'object-uri-space.rq',
32
33
  };
33
34
  async function createVoidStage(filename, options) {
34
- const query = await readQueryFile(resolve(queriesDir, filename));
35
+ const query = substituteMintMarkers(await readQueryFile(resolve(queriesDir, filename)));
35
36
  const reader = {
36
37
  reader: new SparqlConstructReader({ query }),
37
38
  transform: options?.transform,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lde/pipeline-void",
3
- "version": "0.31.3",
3
+ "version": "0.32.0",
4
4
  "description": "VOiD (Vocabulary of Interlinked Datasets) statistical analysis for RDF datasets",
5
5
  "repository": {
6
6
  "url": "git+https://github.com/ldelements/lde.git",
@@ -34,6 +34,6 @@
34
34
  },
35
35
  "peerDependencies": {
36
36
  "@lde/dataset": "^0.7.8",
37
- "@lde/pipeline": "^0.33.3"
37
+ "@lde/pipeline": "^0.34.0"
38
38
  }
39
39
  }
@@ -3,17 +3,17 @@ PREFIX void: <http://rdfs.org/ns/void#>
3
3
  CONSTRUCT {
4
4
  ?dataset a void:Dataset ;
5
5
  void:classPartition ?classPartition .
6
- ?classPartition void:class ?type ;
6
+ ?classPartition void:class ?class ;
7
7
  void:entities ?entities .
8
8
  }
9
9
  WHERE {
10
10
  {
11
- SELECT (COUNT(?type) AS ?entities) ?type {
11
+ SELECT (COUNT(?class) AS ?entities) ?class {
12
12
  #subjectFilter#
13
- ?s a ?type .
13
+ ?s a ?class .
14
14
  }
15
- GROUP BY ?type
15
+ GROUP BY ?class
16
16
  }
17
- BIND(URI(CONCAT(STR(?dataset), "/.well-known/void#class-", MD5(STR(?type)))) AS ?classPartition)
17
+ BIND(#mint:class# AS ?classPartition)
18
18
  }
19
19
  LIMIT 10000
@@ -16,6 +16,6 @@ WHERE {
16
16
  }
17
17
  GROUP BY ?class ?p
18
18
  }
19
- BIND(URI(CONCAT(STR(?dataset), "/.well-known/void#class-property-", MD5(CONCAT(STR(?class), STR(?p))))) AS ?propertyPartition)
19
+ BIND(#mint:class-property# AS ?propertyPartition)
20
20
  }
21
21
  LIMIT 100000
@@ -21,7 +21,7 @@ WHERE {
21
21
  }
22
22
  GROUP BY ?class ?p
23
23
  }
24
- BIND(URI(CONCAT(STR(?dataset), "/.well-known/void#class-", MD5(STR(?class)))) AS ?classPartition)
25
- BIND(URI(CONCAT(STR(?dataset), "/.well-known/void#class-property-", MD5(CONCAT(STR(?class), STR(?p))))) AS ?propertyPartition)
24
+ BIND(#mint:class# AS ?classPartition)
25
+ BIND(#mint:class-property# AS ?propertyPartition)
26
26
  }
27
27
  LIMIT 100000
@@ -22,7 +22,7 @@ WHERE {
22
22
  }
23
23
  GROUP BY ?class ?p ?dt
24
24
  }
25
- BIND(URI(CONCAT(STR(?dataset), "/.well-known/void#class-property-", MD5(CONCAT(STR(?class), STR(?p))))) AS ?propertyPartition)
26
- BIND(URI(CONCAT(STR(?dataset), "/.well-known/void#datatype-", MD5(CONCAT(STR(?class), STR(?p), STR(?dt))))) AS ?datatypePartition)
25
+ BIND(#mint:class-property# AS ?propertyPartition)
26
+ BIND(#mint:datatype# AS ?datatypePartition)
27
27
  }
28
28
  LIMIT 100000
@@ -24,7 +24,7 @@ WHERE {
24
24
  }
25
25
  GROUP BY ?class ?p ?lang
26
26
  }
27
- BIND(URI(CONCAT(STR(?dataset), "/.well-known/void#class-property-", MD5(CONCAT(STR(?class), STR(?p))))) AS ?propertyPartition)
28
- BIND(URI(CONCAT(STR(?dataset), "/.well-known/void#language-", MD5(CONCAT(STR(?class), STR(?p), ?lang)))) AS ?languagePartition)
27
+ BIND(#mint:class-property# AS ?propertyPartition)
28
+ BIND(#mint:language# AS ?languagePartition)
29
29
  }
30
30
  LIMIT 100000
@@ -22,7 +22,7 @@ WHERE {
22
22
  }
23
23
  GROUP BY ?class ?p ?objectClass
24
24
  }
25
- BIND(URI(CONCAT(STR(?dataset), "/.well-known/void#class-property-", MD5(CONCAT(STR(?class), STR(?p))))) AS ?propertyPartition)
26
- BIND(URI(CONCAT(STR(?dataset), "/.well-known/void#object-class-", MD5(CONCAT(STR(?class), STR(?p), STR(?objectClass))))) AS ?objectClassPartition)
25
+ BIND(#mint:class-property# AS ?propertyPartition)
26
+ BIND(#mint:object-class# AS ?objectClassPartition)
27
27
  }
28
28
  LIMIT 100000