@unrdf/kgn 5.0.1

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 (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +210 -0
  3. package/package.json +90 -0
  4. package/src/MIGRATION_COMPLETE.md +186 -0
  5. package/src/PORT-MAP.md +302 -0
  6. package/src/base/filter-templates.js +479 -0
  7. package/src/base/index.js +92 -0
  8. package/src/base/injection-targets.js +583 -0
  9. package/src/base/macro-templates.js +298 -0
  10. package/src/base/macro-templates.js.bak +461 -0
  11. package/src/base/shacl-templates.js +617 -0
  12. package/src/base/template-base.js +388 -0
  13. package/src/core/attestor.js +381 -0
  14. package/src/core/filters.js +518 -0
  15. package/src/core/index.js +21 -0
  16. package/src/core/kgen-engine.js +372 -0
  17. package/src/core/parser.js +447 -0
  18. package/src/core/post-processor.js +313 -0
  19. package/src/core/renderer.js +469 -0
  20. package/src/doc-generator/cli.mjs +122 -0
  21. package/src/doc-generator/index.mjs +28 -0
  22. package/src/doc-generator/mdx-generator.mjs +71 -0
  23. package/src/doc-generator/nav-generator.mjs +136 -0
  24. package/src/doc-generator/parser.mjs +291 -0
  25. package/src/doc-generator/rdf-builder.mjs +306 -0
  26. package/src/doc-generator/scanner.mjs +189 -0
  27. package/src/engine/index.js +42 -0
  28. package/src/engine/pipeline.js +448 -0
  29. package/src/engine/renderer.js +604 -0
  30. package/src/engine/template-engine.js +566 -0
  31. package/src/filters/array.js +436 -0
  32. package/src/filters/data.js +479 -0
  33. package/src/filters/index.js +270 -0
  34. package/src/filters/rdf.js +264 -0
  35. package/src/filters/text.js +369 -0
  36. package/src/index.js +109 -0
  37. package/src/inheritance/index.js +40 -0
  38. package/src/injection/api.js +260 -0
  39. package/src/injection/atomic-writer.js +327 -0
  40. package/src/injection/constants.js +136 -0
  41. package/src/injection/idempotency-manager.js +295 -0
  42. package/src/injection/index.js +28 -0
  43. package/src/injection/injection-engine.js +378 -0
  44. package/src/injection/integration.js +339 -0
  45. package/src/injection/modes/index.js +341 -0
  46. package/src/injection/rollback-manager.js +373 -0
  47. package/src/injection/target-resolver.js +323 -0
  48. package/src/injection/tests/atomic-writer.test.js +382 -0
  49. package/src/injection/tests/injection-engine.test.js +611 -0
  50. package/src/injection/tests/integration.test.js +392 -0
  51. package/src/injection/tests/run-tests.js +283 -0
  52. package/src/injection/validation-engine.js +547 -0
  53. package/src/linter/determinism-linter.js +473 -0
  54. package/src/linter/determinism.js +410 -0
  55. package/src/linter/index.js +6 -0
  56. package/src/linter/test-doubles.js +475 -0
  57. package/src/parser/frontmatter.js +228 -0
  58. package/src/parser/variables.js +344 -0
  59. package/src/renderer/deterministic.js +245 -0
  60. package/src/renderer/index.js +6 -0
  61. package/src/templates/latex/academic-paper.njk +186 -0
  62. package/src/templates/latex/index.js +104 -0
  63. package/src/templates/nextjs/app-page.njk +66 -0
  64. package/src/templates/nextjs/index.js +80 -0
  65. package/src/templates/office/docx/document.njk +368 -0
  66. package/src/templates/office/index.js +79 -0
  67. package/src/templates/office/word-report.njk +129 -0
  68. package/src/utils/template-utils.js +426 -0
@@ -0,0 +1,270 @@
1
+ /**
2
+ * KGEN Template Filters - Complete Filter Library
3
+ *
4
+ * Migrated essential filters from unjucks with 80/20 principle:
5
+ * - Text processing (upper, lower, camelCase, etc.)
6
+ * - Array manipulation (join, sort, unique, etc.)
7
+ * - Data handling (json, default, type checking)
8
+ * - RDF/SPARQL support (mocked for deterministic testing)
9
+ *
10
+ * All filters provide deterministic, reproducible output
11
+ */
12
+
13
+ import crypto from 'crypto';
14
+ import { textFilters } from './text.js';
15
+ import { arrayFilters } from './array.js';
16
+ import { dataFilters } from './data.js';
17
+ import { rdfFilters } from './rdf.js';
18
+
19
+ /**
20
+ * Create custom filters with deterministic operations
21
+ */
22
+ export function createCustomFilters(options = {}) {
23
+ const { deterministicMode = false, staticBuildTime = '2024-01-01T00:00:00.000Z' } = options;
24
+
25
+ // Merge all filter collections
26
+ const allFilters = {
27
+ ...textFilters,
28
+ ...arrayFilters,
29
+ ...dataFilters,
30
+ ...rdfFilters,
31
+
32
+ // Enhanced deterministic filters that override or augment the base filters
33
+
34
+ // Deterministic date/time formatting
35
+ formatDate: (date, format = 'YYYY-MM-DD') => {
36
+ if (deterministicMode) {
37
+ // Use static build time for deterministic rendering
38
+ return staticBuildTime.split('T')[0]; // Returns '2024-01-01'
39
+ }
40
+
41
+ const d = new Date(date);
42
+ if (isNaN(d.getTime())) return '';
43
+
44
+ switch (format) {
45
+ case 'YYYY-MM-DD':
46
+ return d.toISOString().split('T')[0];
47
+ case 'MM/DD/YYYY':
48
+ return `${(d.getMonth() + 1).toString().padStart(2, '0')}/${d.getDate().toString().padStart(2, '0')}/${d.getFullYear()}`;
49
+ case 'DD/MM/YYYY':
50
+ return `${d.getDate().toString().padStart(2, '0')}/${(d.getMonth() + 1).toString().padStart(2, '0')}/${d.getFullYear()}`;
51
+ default:
52
+ return d.toISOString().split('T')[0];
53
+ }
54
+ },
55
+
56
+ formatTime: (date, format = 'HH:mm:ss') => {
57
+ if (deterministicMode) {
58
+ return '00:00:00';
59
+ }
60
+
61
+ const d = new Date(date);
62
+ if (isNaN(d.getTime())) return '';
63
+
64
+ const hours = d.getHours().toString().padStart(2, '0');
65
+ const minutes = d.getMinutes().toString().padStart(2, '0');
66
+ const seconds = d.getSeconds().toString().padStart(2, '0');
67
+
68
+ switch (format) {
69
+ case 'HH:mm:ss':
70
+ return `${hours}:${minutes}:${seconds}`;
71
+ case 'HH:mm':
72
+ return `${hours}:${minutes}`;
73
+ default:
74
+ return `${hours}:${minutes}:${seconds}`;
75
+ }
76
+ },
77
+
78
+ // Deterministic timestamp
79
+ timestamp: () => {
80
+ if (deterministicMode) {
81
+ return staticBuildTime;
82
+ }
83
+ return new Date().toISOString();
84
+ },
85
+
86
+ // Content hashing for deterministic IDs
87
+ hash: (content, algorithm = 'sha256') => {
88
+ return crypto.createHash(algorithm).update(String(content)).digest('hex');
89
+ },
90
+
91
+ shortHash: (content, length = 8) => {
92
+ return crypto.createHash('sha256').update(String(content)).digest('hex').substring(0, length);
93
+ },
94
+
95
+ // File and path utilities
96
+ filename: (filePath) => {
97
+ if (!filePath) return '';
98
+ return filePath.split('/').pop().split('\\').pop();
99
+ },
100
+
101
+ basename: (filePath, ext) => {
102
+ const name = filePath.split('/').pop().split('\\').pop();
103
+ if (ext && name.endsWith(ext)) {
104
+ return name.slice(0, -ext.length);
105
+ }
106
+ const dotIndex = name.lastIndexOf('.');
107
+ return dotIndex > 0 ? name.slice(0, dotIndex) : name;
108
+ },
109
+
110
+ dirname: (filePath) => {
111
+ if (!filePath) return '';
112
+ const parts = filePath.split('/');
113
+ if (parts.length === 1) {
114
+ // Try Windows separators
115
+ const winParts = filePath.split('\\');
116
+ return winParts.length > 1 ? winParts.slice(0, -1).join('\\') : '';
117
+ }
118
+ return parts.slice(0, -1).join('/');
119
+ },
120
+
121
+ // Code generation helpers
122
+ comment: (text, style = '//') => {
123
+ if (!text) return '';
124
+ switch (style) {
125
+ case '//':
126
+ return text.split('\n').map(line => `// ${line}`).join('\n');
127
+ case '#':
128
+ return text.split('\n').map(line => `# ${line}`).join('\n');
129
+ case '/*':
130
+ return `/* ${text} */`;
131
+ case '<!--':
132
+ return `<!-- ${text} -->`;
133
+ default:
134
+ return text.split('\n').map(line => `${style} ${line}`).join('\n');
135
+ }
136
+ },
137
+
138
+ // Validation and safety filters
139
+ required: (value, message = 'Value is required') => {
140
+ if (value === null || value === undefined || value === '') {
141
+ throw new Error(message);
142
+ }
143
+ return value;
144
+ },
145
+
146
+ // Determinism blockers - these will throw in deterministic mode
147
+ now: () => {
148
+ if (deterministicMode) {
149
+ throw new Error('Filter "now" is not allowed in deterministic mode. Use "timestamp" instead.');
150
+ }
151
+ return new Date().toISOString();
152
+ },
153
+
154
+ random: () => {
155
+ if (deterministicMode) {
156
+ throw new Error('Filter "random" is not allowed in deterministic mode. Use "hash" for consistent randomness.');
157
+ }
158
+ return Math.random();
159
+ },
160
+
161
+ uuid: () => {
162
+ if (deterministicMode) {
163
+ throw new Error('Filter "uuid" is not allowed in deterministic mode. Use "hash" for consistent IDs.');
164
+ }
165
+ return crypto.randomUUID();
166
+ },
167
+
168
+ // CSV conversion filters
169
+ csv: (data, options = {}) => {
170
+ if (!Array.isArray(data)) return '';
171
+ if (data.length === 0) return '';
172
+
173
+ const { delimiter = ',', quote = '"', headers = true } = options;
174
+ const lines = [];
175
+
176
+ // Get headers from first object
177
+ if (headers && data.length > 0 && typeof data[0] === 'object') {
178
+ const headerRow = Object.keys(data[0]).map(h => `${quote}${h}${quote}`).join(delimiter);
179
+ lines.push(headerRow);
180
+ }
181
+
182
+ // Add data rows
183
+ for (const row of data) {
184
+ if (typeof row === 'object' && row !== null) {
185
+ const values = Object.values(row).map(v => {
186
+ const str = String(v || '');
187
+ return str.includes(delimiter) || str.includes(quote) || str.includes('\n')
188
+ ? `${quote}${str.replace(new RegExp(quote, 'g'), quote + quote)}${quote}`
189
+ : str;
190
+ });
191
+ lines.push(values.join(delimiter));
192
+ } else {
193
+ lines.push(String(row));
194
+ }
195
+ }
196
+
197
+ return lines.join('\n');
198
+ },
199
+
200
+ // Markdown table filter
201
+ markdown: (data, options = {}) => {
202
+ if (!Array.isArray(data)) return '';
203
+ if (data.length === 0) return '';
204
+
205
+ const { align = 'left' } = options;
206
+ const lines = [];
207
+
208
+ if (typeof data[0] === 'object' && data[0] !== null) {
209
+ const headers = Object.keys(data[0]);
210
+
211
+ // Header row
212
+ lines.push('| ' + headers.join(' | ') + ' |');
213
+
214
+ // Separator row
215
+ const separators = headers.map(() => {
216
+ switch (align) {
217
+ case 'center': return ':---:';
218
+ case 'right': return '---:';
219
+ default: return '---';
220
+ }
221
+ });
222
+ lines.push('| ' + separators.join(' | ') + ' |');
223
+
224
+ // Data rows
225
+ for (const row of data) {
226
+ const values = headers.map(h => String(row[h] || '').replace(/\|/g, '\\|'));
227
+ lines.push('| ' + values.join(' | ') + ' |');
228
+ }
229
+ }
230
+
231
+ return lines.join('\n');
232
+ }
233
+ };
234
+
235
+ return allFilters;
236
+ }
237
+
238
+ /**
239
+ * Register all filters with Nunjucks environment
240
+ * @param {Object} env - Nunjucks environment
241
+ * @param {Object} options - Filter options
242
+ */
243
+ export function registerFilters(env, options = {}) {
244
+ const filters = createCustomFilters(options);
245
+
246
+ for (const [name, filter] of Object.entries(filters)) {
247
+ env.addFilter(name, filter);
248
+ }
249
+
250
+ return env;
251
+ }
252
+
253
+ /**
254
+ * Get all available filter names
255
+ * @param {Object} options - Filter options
256
+ * @returns {Array} Array of filter names
257
+ */
258
+ export function getFilterNames(options = {}) {
259
+ return Object.keys(createCustomFilters(options));
260
+ }
261
+
262
+ /**
263
+ * Export individual filter collections
264
+ */
265
+ export { textFilters, arrayFilters, dataFilters, rdfFilters };
266
+
267
+ /**
268
+ * Export default function
269
+ */
270
+ export default createCustomFilters;
@@ -0,0 +1,264 @@
1
+ /**
2
+ * KGEN RDF/SPARQL Filters - P0 Essential Semantic Web Processing
3
+ *
4
+ * Mock implementations for testing and London BDD approach
5
+ * These stubs provide deterministic behavior for template generation
6
+ */
7
+
8
+ /**
9
+ * Mock RDF prefixes for expansion
10
+ */
11
+ const DEFAULT_PREFIXES = {
12
+ 'rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
13
+ 'rdfs': 'http://www.w3.org/2000/01/rdf-schema#',
14
+ 'owl': 'http://www.w3.org/2002/07/owl#',
15
+ 'xsd': 'http://www.w3.org/2001/XMLSchema#',
16
+ 'dc': 'http://purl.org/dc/elements/1.1/',
17
+ 'foaf': 'http://xmlns.com/foaf/0.1/',
18
+ 'skos': 'http://www.w3.org/2004/02/skos/core#',
19
+ 'ex': 'http://example.org/',
20
+ 'schema': 'https://schema.org/'
21
+ };
22
+
23
+ /**
24
+ * Mock SPARQL dataset for deterministic results
25
+ */
26
+ const MOCK_SPARQL_RESULTS = {
27
+ 'SELECT ?s ?p ?o WHERE { ?s ?p ?o }': [
28
+ { s: 'ex:Subject1', p: 'rdf:type', o: 'ex:Class1' },
29
+ { s: 'ex:Subject2', p: 'rdfs:label', o: '"Example Label"' }
30
+ ],
31
+ 'SELECT * WHERE { ?s rdf:type ?type }': [
32
+ { s: 'ex:Instance1', type: 'ex:Person' },
33
+ { s: 'ex:Instance2', type: 'ex:Organization' }
34
+ ]
35
+ };
36
+
37
+ /**
38
+ * Expand CURIE (Compact URI) to full URI
39
+ * @param {string} curie - CURIE in format 'prefix:localName'
40
+ * @param {Object} prefixes - Custom prefix mappings
41
+ * @returns {string} Expanded URI
42
+ */
43
+ export const expand = (curie, prefixes = {}) => {
44
+ if (!curie || typeof curie !== 'string') return '';
45
+
46
+ const allPrefixes = { ...DEFAULT_PREFIXES, ...prefixes };
47
+ const [prefix, localName] = curie.split(':', 2);
48
+
49
+ if (localName === undefined) return curie; // No prefix found
50
+ if (!allPrefixes[prefix]) return curie; // Unknown prefix
51
+
52
+ return allPrefixes[prefix] + localName;
53
+ };
54
+
55
+ /**
56
+ * Contract full URI to CURIE if possible
57
+ * @param {string} uri - Full URI
58
+ * @param {Object} prefixes - Custom prefix mappings
59
+ * @returns {string} CURIE or original URI
60
+ */
61
+ export const contract = (uri, prefixes = {}) => {
62
+ if (!uri || typeof uri !== 'string') return '';
63
+
64
+ const allPrefixes = { ...DEFAULT_PREFIXES, ...prefixes };
65
+
66
+ for (const [prefix, namespace] of Object.entries(allPrefixes)) {
67
+ if (uri.startsWith(namespace)) {
68
+ const localName = uri.substring(namespace.length);
69
+ return `${prefix}:${localName}`;
70
+ }
71
+ }
72
+
73
+ return uri;
74
+ };
75
+
76
+ /**
77
+ * Execute mock SPARQL query (deterministic results)
78
+ * @param {string} query - SPARQL query string
79
+ * @param {any} dataset - Mock dataset (ignored in stub)
80
+ * @returns {Array} Mock query results
81
+ */
82
+ export const sparql = async (query, dataset = null) => {
83
+ if (!query || typeof query !== 'string') return [];
84
+
85
+ // Return mock results for known queries
86
+ const normalizedQuery = query.trim().replace(/\s+/g, ' ');
87
+
88
+ if (MOCK_SPARQL_RESULTS[normalizedQuery]) {
89
+ return MOCK_SPARQL_RESULTS[normalizedQuery];
90
+ }
91
+
92
+ // Generate deterministic mock results based on query hash
93
+ const queryHash = query.split('').reduce((hash, char) => {
94
+ hash = ((hash << 5) - hash) + char.charCodeAt(0);
95
+ return hash & hash;
96
+ }, 0);
97
+
98
+ const numResults = Math.abs(queryHash) % 5 + 1; // 1-5 results
99
+ const results = [];
100
+
101
+ for (let i = 0; i < numResults; i++) {
102
+ results.push({
103
+ s: `ex:MockSubject${i + 1}`,
104
+ p: `ex:mockProperty${i + 1}`,
105
+ o: `"Mock Object ${i + 1}"`
106
+ });
107
+ }
108
+
109
+ return results;
110
+ };
111
+
112
+ /**
113
+ * Create RDF literal with optional language tag or datatype
114
+ * @param {any} value - Literal value
115
+ * @param {string} langOrType - Language tag (e.g., 'en') or datatype CURIE
116
+ * @returns {string} RDF literal string
117
+ */
118
+ export const rdfLiteral = (value, langOrType = null) => {
119
+ if (value === null || value === undefined) return '""';
120
+
121
+ const literal = `"${String(value).replace(/"/g, '\\"')}"`;
122
+
123
+ if (!langOrType) return literal;
124
+
125
+ // Check if it's a language tag (2-3 letter code)
126
+ if (/^[a-z]{2,3}(-[A-Z]{2})?$/.test(langOrType)) {
127
+ return `${literal}@${langOrType}`;
128
+ }
129
+
130
+ // Treat as datatype
131
+ return `${literal}^^${langOrType}`;
132
+ };
133
+
134
+ /**
135
+ * Create RDF resource (URI reference)
136
+ * @param {string} uri - URI string
137
+ * @returns {string} URI wrapped in angle brackets
138
+ */
139
+ export const rdfResource = (uri) => {
140
+ if (!uri || typeof uri !== 'string') return '<>';
141
+
142
+ // If already a CURIE or angle-bracketed URI, return as-is
143
+ if (uri.includes(':') && !uri.startsWith('http')) return uri;
144
+ if (uri.startsWith('<') && uri.endsWith('>')) return uri;
145
+
146
+ return `<${uri}>`;
147
+ };
148
+
149
+ /**
150
+ * Escape string for Turtle/N3 syntax
151
+ * @param {string} str - String to escape
152
+ * @returns {string} Turtle-escaped string
153
+ */
154
+ export const turtleEscape = (str) => {
155
+ if (!str || typeof str !== 'string') return '';
156
+
157
+ return str
158
+ .replace(/\\/g, '\\\\')
159
+ .replace(/"/g, '\\"')
160
+ .replace(/\n/g, '\\n')
161
+ .replace(/\r/g, '\\r')
162
+ .replace(/\t/g, '\\t');
163
+ };
164
+
165
+ /**
166
+ * Create SPARQL variable
167
+ * @param {string} name - Variable name
168
+ * @returns {string} SPARQL variable with ? prefix
169
+ */
170
+ export const sparqlVar = (name) => {
171
+ if (!name || typeof name !== 'string') return '?var';
172
+
173
+ // Remove any existing ? or $ prefix
174
+ const cleanName = name.replace(/^[?$]/, '');
175
+
176
+ // Ensure valid SPARQL variable name (alphanumeric + underscore)
177
+ const validName = cleanName.replace(/[^a-zA-Z0-9_]/g, '_');
178
+
179
+ return `?${validName}`;
180
+ };
181
+
182
+ /**
183
+ * Create RDF list (collection)
184
+ * @param {Array} items - Array items
185
+ * @returns {string} RDF list syntax
186
+ */
187
+ export const rdfList = (items) => {
188
+ if (!Array.isArray(items) || items.length === 0) {
189
+ return 'rdf:nil';
190
+ }
191
+
192
+ return `( ${items.join(' ')} )`;
193
+ };
194
+
195
+ /**
196
+ * Create blank node identifier
197
+ * @param {string} id - Optional identifier
198
+ * @returns {string} Blank node with _: prefix
199
+ */
200
+ export const blankNode = (id = null) => {
201
+ if (id && typeof id === 'string') {
202
+ const cleanId = id.replace(/[^a-zA-Z0-9_]/g, '_');
203
+ return `_:${cleanId}`;
204
+ }
205
+
206
+ // Generate deterministic blank node ID
207
+ const timestamp = new Date('2024-01-01T00:00:00.000Z').getTime();
208
+ return `_:blank${timestamp % 10000}`;
209
+ };
210
+
211
+ /**
212
+ * Add RDF datatype to literal
213
+ * @param {any} value - Value to type
214
+ * @param {string} datatype - XSD datatype (without prefix)
215
+ * @returns {string} Typed literal
216
+ */
217
+ export const rdfDatatype = (value, datatype = 'string') => {
218
+ if (value === null || value === undefined) return '""';
219
+
220
+ const literal = `"${String(value)}"`;
221
+ return `${literal}^^xsd:${datatype}`;
222
+ };
223
+
224
+ /**
225
+ * Mock SHACL validation (always passes in stub)
226
+ * @param {any} data - Data to validate
227
+ * @param {any} shape - SHACL shape
228
+ * @returns {Object} Mock validation result
229
+ */
230
+ export const shaclValidate = (data, shape) => {
231
+ return {
232
+ conforms: true,
233
+ results: [],
234
+ message: 'Mock SHACL validation passed'
235
+ };
236
+ };
237
+
238
+ /**
239
+ * Mock reasoning/inference (no-op in stub)
240
+ * @param {any} graph - RDF graph
241
+ * @param {string} reasoner - Reasoner type
242
+ * @returns {any} Original graph (no inference applied)
243
+ */
244
+ export const infer = (graph, reasoner = 'rdfs') => {
245
+ return graph; // No-op for stub
246
+ };
247
+
248
+ // Collection of all RDF filters for easy import
249
+ export const rdfFilters = {
250
+ expand,
251
+ contract,
252
+ sparql,
253
+ rdfLiteral,
254
+ rdfResource,
255
+ turtleEscape,
256
+ sparqlVar,
257
+ rdfList,
258
+ blankNode,
259
+ rdfDatatype,
260
+ shaclValidate,
261
+ infer
262
+ };
263
+
264
+ export default rdfFilters;