@unrdf/kgn 5.0.1 → 26.4.2

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 (59) hide show
  1. package/dist/index.mjs +9207 -0
  2. package/package.json +33 -28
  3. package/src/base/filter-templates.js +7 -1
  4. package/src/base/index.js +15 -10
  5. package/src/base/injection-targets.js +6 -0
  6. package/src/base/macro-templates.js +6 -0
  7. package/src/base/shacl-templates.js +6 -0
  8. package/src/base/template-base.js +7 -1
  9. package/src/core/attestor.js +50 -1
  10. package/src/core/filters.js +134 -1
  11. package/src/core/index.js +8 -1
  12. package/src/core/kgen-engine.js +49 -1
  13. package/src/core/parser.js +52 -1
  14. package/src/core/post-processor.js +7 -1
  15. package/src/core/renderer.js +67 -1
  16. package/src/doc-generator/mdx-generator.mjs +1 -1
  17. package/src/doc-generator/nav-generator.mjs +1 -1
  18. package/src/doc-generator/rdf-builder.mjs +2 -2
  19. package/src/engine/index.js +9 -0
  20. package/src/engine/pipeline.js +7 -1
  21. package/src/engine/renderer.js +18 -3
  22. package/src/engine/template-engine.js +12 -3
  23. package/src/filters/array.js +14 -6
  24. package/src/filters/index.js +165 -17
  25. package/src/filters/rdf.js +3 -3
  26. package/src/{index.js → index.mjs} +46 -0
  27. package/src/index.test.mjs +40 -0
  28. package/src/inheritance/index.js +19 -1
  29. package/src/injection/atomic-writer.js +22 -1
  30. package/src/injection/idempotency-manager.js +33 -0
  31. package/src/injection/injection-engine.js +46 -1
  32. package/src/injection/integration.js +3 -3
  33. package/src/injection/modes/index.js +30 -0
  34. package/src/injection/rollback-manager.js +26 -2
  35. package/src/injection/target-resolver.js +48 -3
  36. package/src/injection/tests/injection-engine.test.js +3 -3
  37. package/src/injection/tests/integration.test.js +2 -1
  38. package/src/injection/tests/run-tests.js +3 -0
  39. package/src/injection/validation-engine.js +71 -5
  40. package/src/linter/determinism-linter.js +20 -5
  41. package/src/linter/determinism.js +8 -2
  42. package/src/linter/index.js +3 -1
  43. package/src/linter/test-doubles.js +151 -4
  44. package/src/parser/frontmatter.js +6 -0
  45. package/src/parser/variables.js +7 -1
  46. package/src/rdf/filters.js +393 -0
  47. package/src/rdf/index.js +444 -0
  48. package/src/renderer/deterministic.js +6 -0
  49. package/src/renderer/index.js +3 -1
  50. package/src/templates/rdf/DELIVERY-SUMMARY.md +266 -0
  51. package/src/templates/rdf/README.md +595 -0
  52. package/src/templates/rdf/dataset.njk +83 -0
  53. package/src/templates/rdf/index.js +106 -0
  54. package/src/templates/rdf/jsonld-context.njk +63 -0
  55. package/src/templates/rdf/ontology.njk +107 -0
  56. package/src/templates/rdf/schema.njk +79 -0
  57. package/src/templates/rdf/shapes.njk +89 -0
  58. package/src/templates/rdf/sparql-queries.njk +70 -0
  59. package/src/templates/rdf/vocabulary.njk +79 -0
@@ -0,0 +1,444 @@
1
+ /**
2
+ * @file RDF-Aware Template System Integration
3
+ * @module @unrdf/kgn/rdf
4
+ * @description Integration layer between @unrdf/kgn and @unrdf/core for RDF-aware templating
5
+ */
6
+
7
+ import { z } from 'zod';
8
+ import nunjucks from 'nunjucks';
9
+ import {
10
+ createStore,
11
+ namedNode,
12
+ literal as createLiteral,
13
+ blankNode as createBlankNode,
14
+ quad,
15
+ executeQuery,
16
+ COMMON_PREFIXES,
17
+ } from '@unrdf/core';
18
+ import { rdfTemplateFilters } from './filters.js';
19
+
20
+ /**
21
+ * Schema for RDF template engine configuration
22
+ */
23
+ const RdfTemplateEngineConfigSchema = z.object({
24
+ autoescape: z.boolean().optional(),
25
+ throwOnUndefined: z.boolean().optional(),
26
+ trimBlocks: z.boolean().optional(),
27
+ lstripBlocks: z.boolean().optional(),
28
+ prefixes: z.record(z.string()).optional(),
29
+ enableStore: z.boolean().optional(),
30
+ storeOptions: z.any().optional(),
31
+ });
32
+
33
+ /**
34
+ * Schema for RDF template rendering context
35
+ */
36
+ const RdfRenderContextSchema = z.object({
37
+ data: z.any(),
38
+ prefixes: z.record(z.string()).optional(),
39
+ store: z.any().optional(),
40
+ baseUri: z.string().url().optional(),
41
+ });
42
+
43
+ /**
44
+ * Schema for SPARQL template generation
45
+ */
46
+ const SparqlTemplateSchema = z.object({
47
+ prefixes: z.record(z.string()).optional(),
48
+ where: z.array(z.object({
49
+ subject: z.string(),
50
+ predicate: z.string(),
51
+ object: z.string(),
52
+ })).min(1),
53
+ select: z.array(z.string()).optional(),
54
+ distinct: z.boolean().optional(),
55
+ limit: z.number().int().positive().optional(),
56
+ offset: z.number().int().nonnegative().optional(),
57
+ orderBy: z.array(z.object({
58
+ variable: z.string(),
59
+ direction: z.enum(['ASC', 'DESC']).optional(),
60
+ })).optional(),
61
+ });
62
+
63
+ /**
64
+ * RDF-aware template engine
65
+ * Extends Nunjucks with RDF-specific filters and functions
66
+ */
67
+ export class RdfTemplateEngine {
68
+ /**
69
+ * Create RDF template engine
70
+ * @param {Object} config - Engine configuration
71
+ */
72
+ constructor(config = {}) {
73
+ this.config = RdfTemplateEngineConfigSchema.parse(config);
74
+
75
+ // Apply defaults
76
+ const {
77
+ autoescape = false,
78
+ throwOnUndefined = false,
79
+ trimBlocks = true,
80
+ lstripBlocks = true,
81
+ prefixes = {},
82
+ enableStore = true,
83
+ storeOptions,
84
+ } = this.config;
85
+
86
+ // Create Nunjucks environment
87
+ this.env = new nunjucks.Environment(null, {
88
+ autoescape,
89
+ throwOnUndefined,
90
+ trimBlocks,
91
+ lstripBlocks,
92
+ });
93
+
94
+ // Initialize RDF store if enabled
95
+ this.store = enableStore ? createStore(storeOptions) : null;
96
+
97
+ // Merge common prefixes with custom prefixes
98
+ this.prefixes = { ...COMMON_PREFIXES, ...prefixes };
99
+
100
+ // Register RDF filters
101
+ this._registerFilters();
102
+
103
+ // Register RDF functions
104
+ this._registerGlobals();
105
+ }
106
+
107
+ /**
108
+ * Register RDF template filters
109
+ * @private
110
+ */
111
+ _registerFilters() {
112
+ // Register all RDF template filters
113
+ for (const [name, filter] of Object.entries(rdfTemplateFilters)) {
114
+ this.env.addFilter(name, filter);
115
+ }
116
+
117
+ // Additional utility filters
118
+ this.env.addFilter('expandUri', (curie) => this._expandUri(curie));
119
+ this.env.addFilter('contractUri', (uri) => this._contractUri(uri));
120
+ this.env.addFilter('toQuad', (triple) => this._toQuad(triple));
121
+ }
122
+
123
+ /**
124
+ * Register global RDF functions
125
+ * @private
126
+ */
127
+ _registerGlobals() {
128
+ // Add RDF term constructors as globals
129
+ this.env.addGlobal('namedNode', (uri) => namedNode(uri));
130
+ this.env.addGlobal('literal', (value, langOrDatatype) => {
131
+ if (!langOrDatatype) {
132
+ return createLiteral(value);
133
+ }
134
+ // Check if it's a language tag
135
+ if (/^[a-z]{2,3}(-[A-Z]{2})?$/.test(langOrDatatype)) {
136
+ return createLiteral(value, langOrDatatype);
137
+ }
138
+ // Treat as datatype
139
+ return createLiteral(value, null, namedNode(langOrDatatype));
140
+ });
141
+ this.env.addGlobal('blankNode', (id) => createBlankNode(id));
142
+ this.env.addGlobal('quad', (s, p, o, g) => quad(s, p, o, g));
143
+
144
+ // Add prefix expansion
145
+ this.env.addGlobal('expand', (curie) => this._expandUri(curie));
146
+ this.env.addGlobal('contract', (uri) => this._contractUri(uri));
147
+
148
+ // Add SPARQL query builder
149
+ this.env.addGlobal('sparqlQuery', (spec) => this._buildSparqlQuery(spec));
150
+
151
+ // Add store access (if enabled)
152
+ if (this.store) {
153
+ this.env.addGlobal('queryStore', async (query) => {
154
+ return await executeQuery(this.store, query);
155
+ });
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Expand CURIE to full URI
161
+ * @private
162
+ * @param {string} curie - CURIE (prefix:localName)
163
+ * @returns {string} Full URI
164
+ */
165
+ _expandUri(curie) {
166
+ if (!curie || typeof curie !== 'string') {
167
+ return '';
168
+ }
169
+
170
+ const colonIndex = curie.indexOf(':');
171
+ if (colonIndex === -1) {
172
+ return curie;
173
+ }
174
+
175
+ const prefix = curie.substring(0, colonIndex);
176
+ const localName = curie.substring(colonIndex + 1);
177
+
178
+ if (this.prefixes[prefix]) {
179
+ return this.prefixes[prefix] + localName;
180
+ }
181
+
182
+ return curie;
183
+ }
184
+
185
+ /**
186
+ * Contract URI to CURIE
187
+ * @private
188
+ * @param {string} uri - Full URI
189
+ * @returns {string} CURIE or original URI
190
+ */
191
+ _contractUri(uri) {
192
+ if (!uri || typeof uri !== 'string') {
193
+ return '';
194
+ }
195
+
196
+ for (const [prefix, namespace] of Object.entries(this.prefixes)) {
197
+ if (uri.startsWith(namespace)) {
198
+ const localName = uri.substring(namespace.length);
199
+ return `${prefix}:${localName}`;
200
+ }
201
+ }
202
+
203
+ return uri;
204
+ }
205
+
206
+ /**
207
+ * Convert triple object to quad
208
+ * @private
209
+ * @param {Object} triple - Triple object
210
+ * @returns {Object} Quad object
211
+ */
212
+ _toQuad(triple) {
213
+ if (!triple || typeof triple !== 'object') {
214
+ return null;
215
+ }
216
+
217
+ const subject = this._createTerm(triple.subject || triple.s);
218
+ const predicate = this._createTerm(triple.predicate || triple.p);
219
+ const object = this._createTerm(triple.object || triple.o);
220
+
221
+ return quad(subject, predicate, object);
222
+ }
223
+
224
+ /**
225
+ * Create RDF term from string or object
226
+ * @private
227
+ * @param {string|Object} term - Term specification
228
+ * @returns {Object} RDF term
229
+ */
230
+ _createTerm(term) {
231
+ if (!term) {
232
+ return null;
233
+ }
234
+
235
+ if (typeof term === 'object' && term.termType) {
236
+ return term;
237
+ }
238
+
239
+ if (typeof term === 'string') {
240
+ // Blank node
241
+ if (term.startsWith('_:')) {
242
+ return createBlankNode(term.substring(2));
243
+ }
244
+ // URI
245
+ if (term.startsWith('http://') || term.startsWith('https://')) {
246
+ return namedNode(term);
247
+ }
248
+ // CURIE
249
+ if (term.includes(':')) {
250
+ return namedNode(this._expandUri(term));
251
+ }
252
+ // Literal
253
+ return createLiteral(term);
254
+ }
255
+
256
+ return createLiteral(String(term));
257
+ }
258
+
259
+ /**
260
+ * Build SPARQL query from template specification
261
+ * @private
262
+ * @param {Object} spec - Query specification
263
+ * @returns {string} SPARQL query
264
+ */
265
+ _buildSparqlQuery(spec) {
266
+ const validated = SparqlTemplateSchema.parse(spec);
267
+
268
+ // Apply defaults
269
+ const {
270
+ prefixes = {},
271
+ where,
272
+ select,
273
+ distinct = false,
274
+ limit,
275
+ offset = 0,
276
+ orderBy,
277
+ } = validated;
278
+
279
+ const lines = [];
280
+
281
+ // Prefixes
282
+ const queryPrefixes = { ...this.prefixes, ...prefixes };
283
+ for (const [prefix, uri] of Object.entries(queryPrefixes)) {
284
+ lines.push(`PREFIX ${prefix}: <${uri}>`);
285
+ }
286
+ lines.push('');
287
+
288
+ // SELECT clause
289
+ const selectVars = select || ['*'];
290
+ const distinctStr = distinct ? 'DISTINCT ' : '';
291
+ lines.push(`SELECT ${distinctStr}${selectVars.join(' ')}`);
292
+
293
+ // WHERE clause
294
+ lines.push('WHERE {');
295
+ for (const pattern of where) {
296
+ lines.push(` ${pattern.subject} ${pattern.predicate} ${pattern.object} .`);
297
+ }
298
+ lines.push('}');
299
+
300
+ // ORDER BY
301
+ if (orderBy && orderBy.length > 0) {
302
+ const orderClauses = orderBy.map(
303
+ (o) => `${o.direction || 'ASC'}(${o.variable})`
304
+ );
305
+ lines.push(`ORDER BY ${orderClauses.join(' ')}`);
306
+ }
307
+
308
+ // LIMIT and OFFSET
309
+ if (limit) {
310
+ lines.push(`LIMIT ${limit}`);
311
+ }
312
+ if (offset > 0) {
313
+ lines.push(`OFFSET ${offset}`);
314
+ }
315
+
316
+ return lines.join('\n');
317
+ }
318
+
319
+ /**
320
+ * Render template with RDF context
321
+ * @param {string} template - Template string
322
+ * @param {Object} context - Rendering context
323
+ * @returns {string} Rendered output
324
+ */
325
+ render(template, context = {}) {
326
+ const validated = RdfRenderContextSchema.parse({
327
+ data: context,
328
+ prefixes: context.prefixes,
329
+ store: context.store || this.store,
330
+ baseUri: context.baseUri,
331
+ });
332
+
333
+ // Merge context with RDF utilities
334
+ const renderContext = {
335
+ ...validated.data,
336
+ prefixes: { ...this.prefixes, ...validated.prefixes },
337
+ store: validated.store,
338
+ baseUri: validated.baseUri,
339
+ };
340
+
341
+ return this.env.renderString(template, renderContext);
342
+ }
343
+
344
+ /**
345
+ * Render template from file
346
+ * @param {string} templatePath - Path to template file
347
+ * @param {Object} context - Rendering context
348
+ * @returns {string} Rendered output
349
+ */
350
+ renderFile(templatePath, context = {}) {
351
+ const validated = RdfRenderContextSchema.parse({
352
+ data: context,
353
+ prefixes: context.prefixes,
354
+ store: context.store || this.store,
355
+ baseUri: context.baseUri,
356
+ });
357
+
358
+ const renderContext = {
359
+ ...validated.data,
360
+ prefixes: { ...this.prefixes, ...validated.prefixes },
361
+ store: validated.store,
362
+ baseUri: validated.baseUri,
363
+ };
364
+
365
+ return this.env.render(templatePath, renderContext);
366
+ }
367
+
368
+ /**
369
+ * Add custom prefix mapping
370
+ * @param {string} prefix - Prefix name
371
+ * @param {string} uri - Namespace URI
372
+ */
373
+ addPrefix(prefix, uri) {
374
+ z.string().min(1).parse(prefix);
375
+ z.string().url().parse(uri);
376
+
377
+ this.prefixes[prefix] = uri;
378
+ }
379
+
380
+ /**
381
+ * Get RDF store instance
382
+ * @returns {Object|null} RDF store or null if disabled
383
+ */
384
+ getStore() {
385
+ return this.store;
386
+ }
387
+
388
+ /**
389
+ * Get Nunjucks environment
390
+ * @returns {Object} Nunjucks environment
391
+ */
392
+ getEnvironment() {
393
+ return this.env;
394
+ }
395
+ }
396
+
397
+ /**
398
+ * Create RDF template engine instance
399
+ * @param {Object} config - Engine configuration
400
+ * @returns {RdfTemplateEngine} RDF template engine
401
+ */
402
+ export function createRdfTemplateEngine(config = {}) {
403
+ return new RdfTemplateEngine(config);
404
+ }
405
+
406
+ /**
407
+ * Render RDF template (convenience function)
408
+ * @param {string} template - Template string
409
+ * @param {Object} context - Rendering context
410
+ * @param {Object} config - Engine configuration
411
+ * @returns {string} Rendered output
412
+ */
413
+ export function renderRdfTemplate(template, context = {}, config = {}) {
414
+ const engine = new RdfTemplateEngine(config);
415
+ return engine.render(template, context);
416
+ }
417
+
418
+ /**
419
+ * Export RDF template filters
420
+ */
421
+ export {
422
+ rdfTemplateFilters,
423
+ toTurtle,
424
+ toSparql,
425
+ rdfPrefix,
426
+ blankNode,
427
+ literal,
428
+ } from './filters.js';
429
+
430
+ /**
431
+ * Export schemas for external validation
432
+ */
433
+ export {
434
+ RdfTemplateEngineConfigSchema,
435
+ RdfRenderContextSchema,
436
+ SparqlTemplateSchema,
437
+ };
438
+
439
+ export default {
440
+ RdfTemplateEngine,
441
+ createRdfTemplateEngine,
442
+ renderRdfTemplate,
443
+ rdfTemplateFilters,
444
+ };
@@ -5,7 +5,13 @@
5
5
 
6
6
  import crypto from 'crypto';
7
7
 
8
+ /**
9
+ *
10
+ */
8
11
  export class DeterministicRenderer {
12
+ /**
13
+ *
14
+ */
9
15
  constructor(options = {}) {
10
16
  this.staticBuildTime = options.staticBuildTime || '2024-01-01T00:00:00.000Z';
11
17
  this.blockNonDeterministic = options.blockNonDeterministic !== false;
@@ -2,5 +2,7 @@
2
2
  * Renderer Module Exports
3
3
  */
4
4
 
5
- export { DeterministicRenderer } from './deterministic.js';
5
+ import { DeterministicRenderer } from './deterministic.js';
6
+
7
+ export { DeterministicRenderer };
6
8
  export default { DeterministicRenderer };