@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.
- package/LICENSE +21 -0
- package/README.md +210 -0
- package/package.json +90 -0
- package/src/MIGRATION_COMPLETE.md +186 -0
- package/src/PORT-MAP.md +302 -0
- package/src/base/filter-templates.js +479 -0
- package/src/base/index.js +92 -0
- package/src/base/injection-targets.js +583 -0
- package/src/base/macro-templates.js +298 -0
- package/src/base/macro-templates.js.bak +461 -0
- package/src/base/shacl-templates.js +617 -0
- package/src/base/template-base.js +388 -0
- package/src/core/attestor.js +381 -0
- package/src/core/filters.js +518 -0
- package/src/core/index.js +21 -0
- package/src/core/kgen-engine.js +372 -0
- package/src/core/parser.js +447 -0
- package/src/core/post-processor.js +313 -0
- package/src/core/renderer.js +469 -0
- package/src/doc-generator/cli.mjs +122 -0
- package/src/doc-generator/index.mjs +28 -0
- package/src/doc-generator/mdx-generator.mjs +71 -0
- package/src/doc-generator/nav-generator.mjs +136 -0
- package/src/doc-generator/parser.mjs +291 -0
- package/src/doc-generator/rdf-builder.mjs +306 -0
- package/src/doc-generator/scanner.mjs +189 -0
- package/src/engine/index.js +42 -0
- package/src/engine/pipeline.js +448 -0
- package/src/engine/renderer.js +604 -0
- package/src/engine/template-engine.js +566 -0
- package/src/filters/array.js +436 -0
- package/src/filters/data.js +479 -0
- package/src/filters/index.js +270 -0
- package/src/filters/rdf.js +264 -0
- package/src/filters/text.js +369 -0
- package/src/index.js +109 -0
- package/src/inheritance/index.js +40 -0
- package/src/injection/api.js +260 -0
- package/src/injection/atomic-writer.js +327 -0
- package/src/injection/constants.js +136 -0
- package/src/injection/idempotency-manager.js +295 -0
- package/src/injection/index.js +28 -0
- package/src/injection/injection-engine.js +378 -0
- package/src/injection/integration.js +339 -0
- package/src/injection/modes/index.js +341 -0
- package/src/injection/rollback-manager.js +373 -0
- package/src/injection/target-resolver.js +323 -0
- package/src/injection/tests/atomic-writer.test.js +382 -0
- package/src/injection/tests/injection-engine.test.js +611 -0
- package/src/injection/tests/integration.test.js +392 -0
- package/src/injection/tests/run-tests.js +283 -0
- package/src/injection/validation-engine.js +547 -0
- package/src/linter/determinism-linter.js +473 -0
- package/src/linter/determinism.js +410 -0
- package/src/linter/index.js +6 -0
- package/src/linter/test-doubles.js +475 -0
- package/src/parser/frontmatter.js +228 -0
- package/src/parser/variables.js +344 -0
- package/src/renderer/deterministic.js +245 -0
- package/src/renderer/index.js +6 -0
- package/src/templates/latex/academic-paper.njk +186 -0
- package/src/templates/latex/index.js +104 -0
- package/src/templates/nextjs/app-page.njk +66 -0
- package/src/templates/nextjs/index.js +80 -0
- package/src/templates/office/docx/document.njk +368 -0
- package/src/templates/office/index.js +79 -0
- package/src/templates/office/word-report.njk +129 -0
- package/src/utils/template-utils.js +426 -0
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KGEN Filter System - Deterministic template filters
|
|
3
|
+
*
|
|
4
|
+
* Implements all v1 Lock specification filters:
|
|
5
|
+
* - Text: upper, lower, trim, replace, split, join, slice
|
|
6
|
+
* - Data: default, unique, sort, groupby, map, sum, count
|
|
7
|
+
* - Format: json, md, csv
|
|
8
|
+
* - RDF: prefix, expand, sparql
|
|
9
|
+
* - Validation: shaclReport
|
|
10
|
+
* - CAS: casDigest, attestRef
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import crypto from 'crypto';
|
|
14
|
+
|
|
15
|
+
export class KGenFilters {
|
|
16
|
+
constructor(options = {}) {
|
|
17
|
+
this.options = {
|
|
18
|
+
deterministicMode: options.deterministicMode !== false,
|
|
19
|
+
strictMode: options.strictMode !== false,
|
|
20
|
+
...options
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
this.filters = new Map();
|
|
24
|
+
this.registerCoreFilters();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Register a filter function
|
|
29
|
+
*/
|
|
30
|
+
register(name, filterFunction, options = {}) {
|
|
31
|
+
if (typeof filterFunction !== 'function') {
|
|
32
|
+
throw new Error(`Filter '${name}' must be a function`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
this.filters.set(name, {
|
|
36
|
+
function: filterFunction,
|
|
37
|
+
deterministic: options.deterministic !== false,
|
|
38
|
+
description: options.description || '',
|
|
39
|
+
category: options.category || 'custom'
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Apply filter to value
|
|
45
|
+
*/
|
|
46
|
+
apply(filterName, value, ...args) {
|
|
47
|
+
const filter = this.filters.get(filterName);
|
|
48
|
+
|
|
49
|
+
if (!filter) {
|
|
50
|
+
if (this.options.strictMode) {
|
|
51
|
+
throw new Error(`Unknown filter: ${filterName}`);
|
|
52
|
+
}
|
|
53
|
+
return value; // Return original value if filter not found
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Check deterministic mode compliance
|
|
57
|
+
if (this.options.deterministicMode && !filter.deterministic) {
|
|
58
|
+
throw new Error(`Filter '${filterName}' is not deterministic and cannot be used in deterministic mode`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
return filter.function(value, ...args);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (this.options.strictMode) {
|
|
65
|
+
throw new Error(`Filter '${filterName}' failed: ${error.message}`);
|
|
66
|
+
}
|
|
67
|
+
return value; // Return original value on error
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Check if filter exists
|
|
73
|
+
*/
|
|
74
|
+
has(filterName) {
|
|
75
|
+
return this.filters.has(filterName);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get filter count
|
|
80
|
+
*/
|
|
81
|
+
getFilterCount() {
|
|
82
|
+
return this.filters.size;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Register all core filters
|
|
87
|
+
*/
|
|
88
|
+
registerCoreFilters() {
|
|
89
|
+
this.registerTextFilters();
|
|
90
|
+
this.registerDataFilters();
|
|
91
|
+
this.registerFormatFilters();
|
|
92
|
+
this.registerRDFFilters();
|
|
93
|
+
this.registerValidationFilters();
|
|
94
|
+
this.registerCASFilters();
|
|
95
|
+
this.registerUtilityFilters();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Text processing filters
|
|
100
|
+
*/
|
|
101
|
+
registerTextFilters() {
|
|
102
|
+
this.register('upper', (str) => String(str || '').toUpperCase(), {
|
|
103
|
+
category: 'text',
|
|
104
|
+
description: 'Convert string to uppercase'
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
this.register('lower', (str) => String(str || '').toLowerCase(), {
|
|
108
|
+
category: 'text',
|
|
109
|
+
description: 'Convert string to lowercase'
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
this.register('trim', (str) => String(str || '').trim(), {
|
|
113
|
+
category: 'text',
|
|
114
|
+
description: 'Remove leading and trailing whitespace'
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
this.register('replace', (str, search, replace = '') => {
|
|
118
|
+
const searchRegex = typeof search === 'string' ?
|
|
119
|
+
new RegExp(search.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g') :
|
|
120
|
+
search;
|
|
121
|
+
return String(str || '').replace(searchRegex, replace);
|
|
122
|
+
}, {
|
|
123
|
+
category: 'text',
|
|
124
|
+
description: 'Replace occurrences of search string'
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
this.register('split', (str, separator = '') => {
|
|
128
|
+
return String(str || '').split(separator);
|
|
129
|
+
}, {
|
|
130
|
+
category: 'text',
|
|
131
|
+
description: 'Split string into array'
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
this.register('join', (arr, separator = '') => {
|
|
135
|
+
if (!Array.isArray(arr)) return String(arr || '');
|
|
136
|
+
return arr.join(separator);
|
|
137
|
+
}, {
|
|
138
|
+
category: 'text',
|
|
139
|
+
description: 'Join array elements into string'
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
this.register('slice', (str, start = 0, end) => {
|
|
143
|
+
const s = String(str || '');
|
|
144
|
+
return end !== undefined ? s.slice(start, end) : s.slice(start);
|
|
145
|
+
}, {
|
|
146
|
+
category: 'text',
|
|
147
|
+
description: 'Extract substring by position'
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Data processing filters
|
|
153
|
+
*/
|
|
154
|
+
registerDataFilters() {
|
|
155
|
+
this.register('default', (value, defaultValue = '') => {
|
|
156
|
+
return (value === null || value === undefined || value === '') ? defaultValue : value;
|
|
157
|
+
}, {
|
|
158
|
+
category: 'data',
|
|
159
|
+
description: 'Provide default value if original is empty'
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
this.register('unique', (arr) => {
|
|
163
|
+
if (!Array.isArray(arr)) return arr;
|
|
164
|
+
return [...new Set(arr)];
|
|
165
|
+
}, {
|
|
166
|
+
category: 'data',
|
|
167
|
+
description: 'Remove duplicate values from array'
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
this.register('sort', (arr, key) => {
|
|
171
|
+
if (!Array.isArray(arr)) return arr;
|
|
172
|
+
|
|
173
|
+
return [...arr].sort((a, b) => {
|
|
174
|
+
let aVal, bVal;
|
|
175
|
+
|
|
176
|
+
if (key) {
|
|
177
|
+
aVal = typeof a === 'object' ? a[key] : a;
|
|
178
|
+
bVal = typeof b === 'object' ? b[key] : b;
|
|
179
|
+
} else {
|
|
180
|
+
aVal = a;
|
|
181
|
+
bVal = b;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Handle null/undefined consistently
|
|
185
|
+
if (aVal == null && bVal == null) return 0;
|
|
186
|
+
if (aVal == null) return -1;
|
|
187
|
+
if (bVal == null) return 1;
|
|
188
|
+
|
|
189
|
+
// Stable sort for determinism
|
|
190
|
+
return aVal > bVal ? 1 : aVal < bVal ? -1 : 0;
|
|
191
|
+
});
|
|
192
|
+
}, {
|
|
193
|
+
category: 'data',
|
|
194
|
+
description: 'Sort array by value or key'
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
this.register('groupby', (arr, key) => {
|
|
198
|
+
if (!Array.isArray(arr)) return {};
|
|
199
|
+
|
|
200
|
+
const groups = {};
|
|
201
|
+
arr.forEach(item => {
|
|
202
|
+
const groupKey = typeof item === 'object' && item !== null ?
|
|
203
|
+
item[key] : String(item);
|
|
204
|
+
|
|
205
|
+
if (!groups[groupKey]) {
|
|
206
|
+
groups[groupKey] = [];
|
|
207
|
+
}
|
|
208
|
+
groups[groupKey].push(item);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
return groups;
|
|
212
|
+
}, {
|
|
213
|
+
category: 'data',
|
|
214
|
+
description: 'Group array elements by key'
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
this.register('map', (arr, key) => {
|
|
218
|
+
if (!Array.isArray(arr)) return arr;
|
|
219
|
+
|
|
220
|
+
return arr.map(item => {
|
|
221
|
+
if (typeof item === 'object' && item !== null) {
|
|
222
|
+
return item[key];
|
|
223
|
+
}
|
|
224
|
+
return item;
|
|
225
|
+
});
|
|
226
|
+
}, {
|
|
227
|
+
category: 'data',
|
|
228
|
+
description: 'Extract values by key from array of objects'
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
this.register('sum', (arr, key) => {
|
|
232
|
+
if (!Array.isArray(arr)) return 0;
|
|
233
|
+
|
|
234
|
+
return arr.reduce((sum, item) => {
|
|
235
|
+
let val;
|
|
236
|
+
if (key && typeof item === 'object' && item !== null) {
|
|
237
|
+
val = item[key];
|
|
238
|
+
} else {
|
|
239
|
+
val = item;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const num = Number(val);
|
|
243
|
+
return sum + (isNaN(num) ? 0 : num);
|
|
244
|
+
}, 0);
|
|
245
|
+
}, {
|
|
246
|
+
category: 'data',
|
|
247
|
+
description: 'Sum numeric values in array'
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
this.register('count', (arr) => {
|
|
251
|
+
if (Array.isArray(arr)) return arr.length;
|
|
252
|
+
if (typeof arr === 'object' && arr !== null) return Object.keys(arr).length;
|
|
253
|
+
return 0;
|
|
254
|
+
}, {
|
|
255
|
+
category: 'data',
|
|
256
|
+
description: 'Count elements in array or object'
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Format output filters
|
|
262
|
+
*/
|
|
263
|
+
registerFormatFilters() {
|
|
264
|
+
this.register('json', (obj, indent) => {
|
|
265
|
+
try {
|
|
266
|
+
const indentValue = indent ? (typeof indent === 'number' ? indent : 2) : 0;
|
|
267
|
+
return JSON.stringify(obj, null, indentValue);
|
|
268
|
+
} catch (error) {
|
|
269
|
+
return '{}';
|
|
270
|
+
}
|
|
271
|
+
}, {
|
|
272
|
+
category: 'format',
|
|
273
|
+
description: 'Convert object to JSON string'
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
this.register('md', (str) => {
|
|
277
|
+
// Basic markdown escaping for safety
|
|
278
|
+
return String(str || '').replace(/([*_`\\])/g, '\\$1');
|
|
279
|
+
}, {
|
|
280
|
+
category: 'format',
|
|
281
|
+
description: 'Escape markdown special characters'
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
this.register('csv', (arr, delimiter = ',') => {
|
|
285
|
+
if (!Array.isArray(arr)) return '';
|
|
286
|
+
|
|
287
|
+
return arr.map(item => {
|
|
288
|
+
if (typeof item === 'object' && item !== null) {
|
|
289
|
+
return JSON.stringify(item).replace(/"/g, '""');
|
|
290
|
+
}
|
|
291
|
+
const str = String(item);
|
|
292
|
+
return str.includes(delimiter) ? `"${str.replace(/"/g, '""')}"` : str;
|
|
293
|
+
}).join(delimiter);
|
|
294
|
+
}, {
|
|
295
|
+
category: 'format',
|
|
296
|
+
description: 'Convert array to CSV format'
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* RDF and semantic web filters
|
|
302
|
+
*/
|
|
303
|
+
registerRDFFilters() {
|
|
304
|
+
this.register('prefix', (uri, prefixes = {}) => {
|
|
305
|
+
if (!uri || typeof uri !== 'string') return uri;
|
|
306
|
+
|
|
307
|
+
// Find matching prefix
|
|
308
|
+
for (const [prefix, namespace] of Object.entries(prefixes)) {
|
|
309
|
+
if (uri.startsWith(namespace)) {
|
|
310
|
+
return uri.replace(namespace, `${prefix}:`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return uri;
|
|
315
|
+
}, {
|
|
316
|
+
category: 'rdf',
|
|
317
|
+
description: 'Convert full URI to prefixed form'
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
this.register('expand', (prefixedUri, prefixes = {}) => {
|
|
321
|
+
if (!prefixedUri || typeof prefixedUri !== 'string') return prefixedUri;
|
|
322
|
+
|
|
323
|
+
const colonIndex = prefixedUri.indexOf(':');
|
|
324
|
+
if (colonIndex === -1) return prefixedUri;
|
|
325
|
+
|
|
326
|
+
const prefix = prefixedUri.substring(0, colonIndex);
|
|
327
|
+
const suffix = prefixedUri.substring(colonIndex + 1);
|
|
328
|
+
|
|
329
|
+
if (prefixes[prefix]) {
|
|
330
|
+
return prefixes[prefix] + suffix;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return prefixedUri;
|
|
334
|
+
}, {
|
|
335
|
+
category: 'rdf',
|
|
336
|
+
description: 'Expand prefixed URI to full form'
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
this.register('sparql', (query, params = {}) => {
|
|
340
|
+
if (!query || typeof query !== 'string') return '';
|
|
341
|
+
|
|
342
|
+
let processedQuery = query;
|
|
343
|
+
|
|
344
|
+
// Simple parameter substitution for deterministic queries
|
|
345
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
346
|
+
const placeholder = new RegExp(`\\$\\{${key}\\}`, 'g');
|
|
347
|
+
processedQuery = processedQuery.replace(placeholder, String(value));
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
return processedQuery;
|
|
351
|
+
}, {
|
|
352
|
+
category: 'rdf',
|
|
353
|
+
description: 'Process SPARQL query with parameters'
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Validation filters
|
|
359
|
+
*/
|
|
360
|
+
registerValidationFilters() {
|
|
361
|
+
this.register('shaclReport', (data, shaclShapes = {}) => {
|
|
362
|
+
// Simplified SHACL validation for deterministic behavior
|
|
363
|
+
const report = {
|
|
364
|
+
conforms: true,
|
|
365
|
+
results: [],
|
|
366
|
+
timestamp: this.options.deterministicMode ? '2024-01-01T00:00:00.000Z' : new Date().toISOString()
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
if (!data || typeof data !== 'object') {
|
|
370
|
+
report.conforms = false;
|
|
371
|
+
report.results.push({
|
|
372
|
+
severity: 'Violation',
|
|
373
|
+
message: 'Invalid data format for SHACL validation'
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// TODO: Implement full SHACL validation logic
|
|
378
|
+
// For now, return basic conformance report
|
|
379
|
+
|
|
380
|
+
return report;
|
|
381
|
+
}, {
|
|
382
|
+
category: 'validation',
|
|
383
|
+
description: 'Generate SHACL validation report'
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Content Addressable Storage (CAS) filters
|
|
389
|
+
*/
|
|
390
|
+
registerCASFilters() {
|
|
391
|
+
this.register('casDigest', (content, algorithm = 'sha256') => {
|
|
392
|
+
try {
|
|
393
|
+
const hash = crypto.createHash(algorithm);
|
|
394
|
+
hash.update(String(content || ''), 'utf8');
|
|
395
|
+
return hash.digest('hex');
|
|
396
|
+
} catch (error) {
|
|
397
|
+
if (this.options.strictMode) {
|
|
398
|
+
throw new Error(`CAS digest failed: ${error.message}`);
|
|
399
|
+
}
|
|
400
|
+
return '';
|
|
401
|
+
}
|
|
402
|
+
}, {
|
|
403
|
+
category: 'cas',
|
|
404
|
+
description: 'Generate content-addressable digest'
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
this.register('attestRef', (content, options = {}) => {
|
|
408
|
+
const digest = this.apply('casDigest', content, options.algorithm || 'sha256');
|
|
409
|
+
const timestamp = this.options.deterministicMode ?
|
|
410
|
+
'2024-01-01T00:00:00.000Z' :
|
|
411
|
+
new Date().toISOString();
|
|
412
|
+
|
|
413
|
+
return {
|
|
414
|
+
digest,
|
|
415
|
+
algorithm: options.algorithm || 'sha256',
|
|
416
|
+
timestamp,
|
|
417
|
+
attestor: options.attestor || 'kgen-templates',
|
|
418
|
+
version: '1.0.0'
|
|
419
|
+
};
|
|
420
|
+
}, {
|
|
421
|
+
category: 'cas',
|
|
422
|
+
description: 'Generate attestation reference for content'
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Utility filters
|
|
428
|
+
*/
|
|
429
|
+
registerUtilityFilters() {
|
|
430
|
+
// Non-deterministic filters that throw in deterministic mode
|
|
431
|
+
this.register('now', () => {
|
|
432
|
+
if (this.options.deterministicMode) {
|
|
433
|
+
throw new Error('Filter "now" is not allowed in deterministic mode');
|
|
434
|
+
}
|
|
435
|
+
return new Date().toISOString();
|
|
436
|
+
}, {
|
|
437
|
+
category: 'utility',
|
|
438
|
+
deterministic: false,
|
|
439
|
+
description: 'Get current timestamp (non-deterministic)'
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
this.register('random', () => {
|
|
443
|
+
if (this.options.deterministicMode) {
|
|
444
|
+
throw new Error('Filter "random" is not allowed in deterministic mode');
|
|
445
|
+
}
|
|
446
|
+
return Math.random();
|
|
447
|
+
}, {
|
|
448
|
+
category: 'utility',
|
|
449
|
+
deterministic: false,
|
|
450
|
+
description: 'Generate random number (non-deterministic)'
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
this.register('uuid', () => {
|
|
454
|
+
if (this.options.deterministicMode) {
|
|
455
|
+
throw new Error('Filter "uuid" is not allowed in deterministic mode');
|
|
456
|
+
}
|
|
457
|
+
return crypto.randomUUID();
|
|
458
|
+
}, {
|
|
459
|
+
category: 'utility',
|
|
460
|
+
deterministic: false,
|
|
461
|
+
description: 'Generate UUID (non-deterministic)'
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Get all filters by category
|
|
467
|
+
*/
|
|
468
|
+
getFiltersByCategory(category) {
|
|
469
|
+
const result = {};
|
|
470
|
+
|
|
471
|
+
for (const [name, filter] of this.filters) {
|
|
472
|
+
if (filter.category === category) {
|
|
473
|
+
result[name] = {
|
|
474
|
+
description: filter.description,
|
|
475
|
+
deterministic: filter.deterministic
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return result;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* List all filter names
|
|
485
|
+
*/
|
|
486
|
+
listFilters() {
|
|
487
|
+
return Array.from(this.filters.keys()).sort();
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Get filter statistics
|
|
492
|
+
*/
|
|
493
|
+
getStats() {
|
|
494
|
+
const categories = {};
|
|
495
|
+
let deterministicCount = 0;
|
|
496
|
+
|
|
497
|
+
for (const [name, filter] of this.filters) {
|
|
498
|
+
if (!categories[filter.category]) {
|
|
499
|
+
categories[filter.category] = 0;
|
|
500
|
+
}
|
|
501
|
+
categories[filter.category]++;
|
|
502
|
+
|
|
503
|
+
if (filter.deterministic) {
|
|
504
|
+
deterministicCount++;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
return {
|
|
509
|
+
totalFilters: this.filters.size,
|
|
510
|
+
deterministicFilters: deterministicCount,
|
|
511
|
+
nonDeterministicFilters: this.filters.size - deterministicCount,
|
|
512
|
+
categories,
|
|
513
|
+
deterministicMode: this.options.deterministicMode
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
export default KGenFilters;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KGEN Core Template Engine - Native implementation without nunjucks
|
|
3
|
+
*
|
|
4
|
+
* Pipeline: plan → render → post → attest
|
|
5
|
+
* Features: Deterministic rendering, custom filters, attestation support
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export { KGenTemplateEngine } from './kgen-engine.js';
|
|
9
|
+
export { KGenParser } from './parser.js';
|
|
10
|
+
export { KGenFilters } from './filters.js';
|
|
11
|
+
export { KGenRenderer } from './renderer.js';
|
|
12
|
+
export { KGenPostProcessor } from './post-processor.js';
|
|
13
|
+
export { KGenAttestor } from './attestor.js';
|
|
14
|
+
|
|
15
|
+
// Convenience factory function
|
|
16
|
+
export function createKGenEngine(options = {}) {
|
|
17
|
+
return new KGenTemplateEngine(options);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Default export
|
|
21
|
+
export { KGenTemplateEngine as default };
|