@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.
- package/dist/index.mjs +9207 -0
- package/package.json +33 -28
- package/src/base/filter-templates.js +7 -1
- package/src/base/index.js +15 -10
- package/src/base/injection-targets.js +6 -0
- package/src/base/macro-templates.js +6 -0
- package/src/base/shacl-templates.js +6 -0
- package/src/base/template-base.js +7 -1
- package/src/core/attestor.js +50 -1
- package/src/core/filters.js +134 -1
- package/src/core/index.js +8 -1
- package/src/core/kgen-engine.js +49 -1
- package/src/core/parser.js +52 -1
- package/src/core/post-processor.js +7 -1
- package/src/core/renderer.js +67 -1
- package/src/doc-generator/mdx-generator.mjs +1 -1
- package/src/doc-generator/nav-generator.mjs +1 -1
- package/src/doc-generator/rdf-builder.mjs +2 -2
- package/src/engine/index.js +9 -0
- package/src/engine/pipeline.js +7 -1
- package/src/engine/renderer.js +18 -3
- package/src/engine/template-engine.js +12 -3
- package/src/filters/array.js +14 -6
- package/src/filters/index.js +165 -17
- package/src/filters/rdf.js +3 -3
- package/src/{index.js → index.mjs} +46 -0
- package/src/index.test.mjs +40 -0
- package/src/inheritance/index.js +19 -1
- package/src/injection/atomic-writer.js +22 -1
- package/src/injection/idempotency-manager.js +33 -0
- package/src/injection/injection-engine.js +46 -1
- package/src/injection/integration.js +3 -3
- package/src/injection/modes/index.js +30 -0
- package/src/injection/rollback-manager.js +26 -2
- package/src/injection/target-resolver.js +48 -3
- package/src/injection/tests/injection-engine.test.js +3 -3
- package/src/injection/tests/integration.test.js +2 -1
- package/src/injection/tests/run-tests.js +3 -0
- package/src/injection/validation-engine.js +71 -5
- package/src/linter/determinism-linter.js +20 -5
- package/src/linter/determinism.js +8 -2
- package/src/linter/index.js +3 -1
- package/src/linter/test-doubles.js +151 -4
- package/src/parser/frontmatter.js +6 -0
- package/src/parser/variables.js +7 -1
- package/src/rdf/filters.js +393 -0
- package/src/rdf/index.js +444 -0
- package/src/renderer/deterministic.js +6 -0
- package/src/renderer/index.js +3 -1
- package/src/templates/rdf/DELIVERY-SUMMARY.md +266 -0
- package/src/templates/rdf/README.md +595 -0
- package/src/templates/rdf/dataset.njk +83 -0
- package/src/templates/rdf/index.js +106 -0
- package/src/templates/rdf/jsonld-context.njk +63 -0
- package/src/templates/rdf/ontology.njk +107 -0
- package/src/templates/rdf/schema.njk +79 -0
- package/src/templates/rdf/shapes.njk +89 -0
- package/src/templates/rdf/sparql-queries.njk +70 -0
- package/src/templates/rdf/vocabulary.njk +79 -0
package/src/filters/array.js
CHANGED
|
@@ -71,13 +71,21 @@ export const sort = (arr, key = null) => {
|
|
|
71
71
|
};
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
|
-
* Reverse array
|
|
75
|
-
* @param {any}
|
|
76
|
-
* @returns {Array} Reversed array
|
|
74
|
+
* Reverse array or string
|
|
75
|
+
* @param {any} value - Input array or string
|
|
76
|
+
* @returns {Array|string} Reversed array or string
|
|
77
77
|
*/
|
|
78
|
-
export const reverse = (
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
export const reverse = (value) => {
|
|
79
|
+
// Handle strings
|
|
80
|
+
if (typeof value === 'string') {
|
|
81
|
+
return value.split('').reverse().join('');
|
|
82
|
+
}
|
|
83
|
+
// Handle arrays
|
|
84
|
+
if (Array.isArray(value)) {
|
|
85
|
+
return [...value].reverse();
|
|
86
|
+
}
|
|
87
|
+
// Return other types unchanged
|
|
88
|
+
return value;
|
|
81
89
|
};
|
|
82
90
|
|
|
83
91
|
/**
|
package/src/filters/index.js
CHANGED
|
@@ -29,6 +29,16 @@ export function createCustomFilters(options = {}) {
|
|
|
29
29
|
...dataFilters,
|
|
30
30
|
...rdfFilters,
|
|
31
31
|
|
|
32
|
+
// Smart reverse filter that works with both strings and arrays
|
|
33
|
+
reverse: input => {
|
|
34
|
+
if (Array.isArray(input)) {
|
|
35
|
+
return [...input].reverse();
|
|
36
|
+
}
|
|
37
|
+
// Treat as string
|
|
38
|
+
if (input === null || input === undefined) return '';
|
|
39
|
+
return String(input).split('').reverse().join('');
|
|
40
|
+
},
|
|
41
|
+
|
|
32
42
|
// Enhanced deterministic filters that override or augment the base filters
|
|
33
43
|
|
|
34
44
|
// Deterministic date/time formatting
|
|
@@ -61,9 +71,10 @@ export function createCustomFilters(options = {}) {
|
|
|
61
71
|
const d = new Date(date);
|
|
62
72
|
if (isNaN(d.getTime())) return '';
|
|
63
73
|
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
const
|
|
74
|
+
// Use UTC time to match test expectations
|
|
75
|
+
const hours = d.getUTCHours().toString().padStart(2, '0');
|
|
76
|
+
const minutes = d.getUTCMinutes().toString().padStart(2, '0');
|
|
77
|
+
const seconds = d.getUTCSeconds().toString().padStart(2, '0');
|
|
67
78
|
|
|
68
79
|
switch (format) {
|
|
69
80
|
case 'HH:mm:ss':
|
|
@@ -75,6 +86,48 @@ export function createCustomFilters(options = {}) {
|
|
|
75
86
|
}
|
|
76
87
|
},
|
|
77
88
|
|
|
89
|
+
// Date arithmetic - add days to a date
|
|
90
|
+
dateAdd: (date, amount, unit = 'day') => {
|
|
91
|
+
const d = new Date(date);
|
|
92
|
+
if (isNaN(d.getTime())) return '';
|
|
93
|
+
|
|
94
|
+
switch (unit.toLowerCase()) {
|
|
95
|
+
case 'day':
|
|
96
|
+
case 'days':
|
|
97
|
+
d.setUTCDate(d.getUTCDate() + amount);
|
|
98
|
+
break;
|
|
99
|
+
case 'month':
|
|
100
|
+
case 'months':
|
|
101
|
+
d.setUTCMonth(d.getUTCMonth() + amount);
|
|
102
|
+
break;
|
|
103
|
+
case 'year':
|
|
104
|
+
case 'years':
|
|
105
|
+
d.setUTCFullYear(d.getUTCFullYear() + amount);
|
|
106
|
+
break;
|
|
107
|
+
case 'hour':
|
|
108
|
+
case 'hours':
|
|
109
|
+
d.setUTCHours(d.getUTCHours() + amount);
|
|
110
|
+
break;
|
|
111
|
+
case 'minute':
|
|
112
|
+
case 'minutes':
|
|
113
|
+
d.setUTCMinutes(d.getUTCMinutes() + amount);
|
|
114
|
+
break;
|
|
115
|
+
case 'second':
|
|
116
|
+
case 'seconds':
|
|
117
|
+
d.setUTCSeconds(d.getUTCSeconds() + amount);
|
|
118
|
+
break;
|
|
119
|
+
default:
|
|
120
|
+
return '';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return d.toISOString();
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
// Date arithmetic - subtract days from a date
|
|
127
|
+
dateSub: (date, amount, unit = 'day') => {
|
|
128
|
+
return allFilters.dateAdd(date, -amount, unit);
|
|
129
|
+
},
|
|
130
|
+
|
|
78
131
|
// Deterministic timestamp
|
|
79
132
|
timestamp: () => {
|
|
80
133
|
if (deterministicMode) {
|
|
@@ -93,7 +146,7 @@ export function createCustomFilters(options = {}) {
|
|
|
93
146
|
},
|
|
94
147
|
|
|
95
148
|
// File and path utilities
|
|
96
|
-
filename:
|
|
149
|
+
filename: filePath => {
|
|
97
150
|
if (!filePath) return '';
|
|
98
151
|
return filePath.split('/').pop().split('\\').pop();
|
|
99
152
|
},
|
|
@@ -107,7 +160,7 @@ export function createCustomFilters(options = {}) {
|
|
|
107
160
|
return dotIndex > 0 ? name.slice(0, dotIndex) : name;
|
|
108
161
|
},
|
|
109
162
|
|
|
110
|
-
dirname:
|
|
163
|
+
dirname: filePath => {
|
|
111
164
|
if (!filePath) return '';
|
|
112
165
|
const parts = filePath.split('/');
|
|
113
166
|
if (parts.length === 1) {
|
|
@@ -118,20 +171,104 @@ export function createCustomFilters(options = {}) {
|
|
|
118
171
|
return parts.slice(0, -1).join('/');
|
|
119
172
|
},
|
|
120
173
|
|
|
174
|
+
// Date arithmetic filters
|
|
175
|
+
dateAdd: (dateStr, amount, unit = 'day') => {
|
|
176
|
+
const date = new Date(dateStr);
|
|
177
|
+
switch (unit) {
|
|
178
|
+
case 'day':
|
|
179
|
+
date.setDate(date.getDate() + amount);
|
|
180
|
+
break;
|
|
181
|
+
case 'month':
|
|
182
|
+
date.setMonth(date.getMonth() + amount);
|
|
183
|
+
break;
|
|
184
|
+
case 'year':
|
|
185
|
+
date.setFullYear(date.getFullYear() + amount);
|
|
186
|
+
break;
|
|
187
|
+
default:
|
|
188
|
+
throw new Error(`Unknown unit: ${unit}`);
|
|
189
|
+
}
|
|
190
|
+
return date.toISOString();
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
dateSub: (dateStr, amount, unit = 'day') => {
|
|
194
|
+
const date = new Date(dateStr);
|
|
195
|
+
switch (unit) {
|
|
196
|
+
case 'day':
|
|
197
|
+
date.setDate(date.getDate() - amount);
|
|
198
|
+
break;
|
|
199
|
+
case 'month':
|
|
200
|
+
date.setMonth(date.getMonth() - amount);
|
|
201
|
+
break;
|
|
202
|
+
case 'year':
|
|
203
|
+
date.setFullYear(date.getFullYear() - amount);
|
|
204
|
+
break;
|
|
205
|
+
default:
|
|
206
|
+
throw new Error(`Unknown unit: ${unit}`);
|
|
207
|
+
}
|
|
208
|
+
return date.toISOString();
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
// Path utility filters
|
|
212
|
+
resolve: (base, relative) => {
|
|
213
|
+
// Simple path resolution (normalize and join)
|
|
214
|
+
const baseParts = base.split('/').filter(p => p);
|
|
215
|
+
const relativeParts = relative.split('/').filter(p => p);
|
|
216
|
+
|
|
217
|
+
for (const part of relativeParts) {
|
|
218
|
+
if (part === '..') {
|
|
219
|
+
baseParts.pop();
|
|
220
|
+
} else if (part !== '.') {
|
|
221
|
+
baseParts.push(part);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return '/' + baseParts.join('/');
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
relative: (from, to) => {
|
|
229
|
+
// Simple relative path calculation
|
|
230
|
+
const fromParts = from.split('/').filter(p => p);
|
|
231
|
+
const toParts = to.split('/').filter(p => p);
|
|
232
|
+
|
|
233
|
+
// Find common base
|
|
234
|
+
let i = 0;
|
|
235
|
+
while (i < fromParts.length && i < toParts.length && fromParts[i] === toParts[i]) {
|
|
236
|
+
i++;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Go up from 'from' directory
|
|
240
|
+
const upCount = fromParts.length - i;
|
|
241
|
+
const upParts = Array(upCount).fill('..');
|
|
242
|
+
|
|
243
|
+
// Then append remaining 'to' parts
|
|
244
|
+
const remainingParts = toParts.slice(i);
|
|
245
|
+
|
|
246
|
+
return [...upParts, ...remainingParts].join('/');
|
|
247
|
+
},
|
|
248
|
+
|
|
121
249
|
// Code generation helpers
|
|
122
250
|
comment: (text, style = '//') => {
|
|
123
251
|
if (!text) return '';
|
|
124
252
|
switch (style) {
|
|
125
253
|
case '//':
|
|
126
|
-
return text
|
|
254
|
+
return text
|
|
255
|
+
.split('\n')
|
|
256
|
+
.map(line => `// ${line}`)
|
|
257
|
+
.join('\n');
|
|
127
258
|
case '#':
|
|
128
|
-
return text
|
|
259
|
+
return text
|
|
260
|
+
.split('\n')
|
|
261
|
+
.map(line => `# ${line}`)
|
|
262
|
+
.join('\n');
|
|
129
263
|
case '/*':
|
|
130
264
|
return `/* ${text} */`;
|
|
131
265
|
case '<!--':
|
|
132
266
|
return `<!-- ${text} -->`;
|
|
133
267
|
default:
|
|
134
|
-
return text
|
|
268
|
+
return text
|
|
269
|
+
.split('\n')
|
|
270
|
+
.map(line => `${style} ${line}`)
|
|
271
|
+
.join('\n');
|
|
135
272
|
}
|
|
136
273
|
},
|
|
137
274
|
|
|
@@ -146,21 +283,27 @@ export function createCustomFilters(options = {}) {
|
|
|
146
283
|
// Determinism blockers - these will throw in deterministic mode
|
|
147
284
|
now: () => {
|
|
148
285
|
if (deterministicMode) {
|
|
149
|
-
throw new Error(
|
|
286
|
+
throw new Error(
|
|
287
|
+
'Filter "now" is not allowed in deterministic mode. Use "timestamp" instead.'
|
|
288
|
+
);
|
|
150
289
|
}
|
|
151
290
|
return new Date().toISOString();
|
|
152
291
|
},
|
|
153
292
|
|
|
154
293
|
random: () => {
|
|
155
294
|
if (deterministicMode) {
|
|
156
|
-
throw new Error(
|
|
295
|
+
throw new Error(
|
|
296
|
+
'Filter "random" is not allowed in deterministic mode. Use "hash" for consistent randomness.'
|
|
297
|
+
);
|
|
157
298
|
}
|
|
158
299
|
return Math.random();
|
|
159
300
|
},
|
|
160
301
|
|
|
161
302
|
uuid: () => {
|
|
162
303
|
if (deterministicMode) {
|
|
163
|
-
throw new Error(
|
|
304
|
+
throw new Error(
|
|
305
|
+
'Filter "uuid" is not allowed in deterministic mode. Use "hash" for consistent IDs.'
|
|
306
|
+
);
|
|
164
307
|
}
|
|
165
308
|
return crypto.randomUUID();
|
|
166
309
|
},
|
|
@@ -175,7 +318,9 @@ export function createCustomFilters(options = {}) {
|
|
|
175
318
|
|
|
176
319
|
// Get headers from first object
|
|
177
320
|
if (headers && data.length > 0 && typeof data[0] === 'object') {
|
|
178
|
-
const headerRow = Object.keys(data[0])
|
|
321
|
+
const headerRow = Object.keys(data[0])
|
|
322
|
+
.map(h => `${quote}${h}${quote}`)
|
|
323
|
+
.join(delimiter);
|
|
179
324
|
lines.push(headerRow);
|
|
180
325
|
}
|
|
181
326
|
|
|
@@ -214,9 +359,12 @@ export function createCustomFilters(options = {}) {
|
|
|
214
359
|
// Separator row
|
|
215
360
|
const separators = headers.map(() => {
|
|
216
361
|
switch (align) {
|
|
217
|
-
case 'center':
|
|
218
|
-
|
|
219
|
-
|
|
362
|
+
case 'center':
|
|
363
|
+
return ':---:';
|
|
364
|
+
case 'right':
|
|
365
|
+
return '---:';
|
|
366
|
+
default:
|
|
367
|
+
return '---';
|
|
220
368
|
}
|
|
221
369
|
});
|
|
222
370
|
lines.push('| ' + separators.join(' | ') + ' |');
|
|
@@ -229,7 +377,7 @@ export function createCustomFilters(options = {}) {
|
|
|
229
377
|
}
|
|
230
378
|
|
|
231
379
|
return lines.join('\n');
|
|
232
|
-
}
|
|
380
|
+
},
|
|
233
381
|
};
|
|
234
382
|
|
|
235
383
|
return allFilters;
|
|
@@ -267,4 +415,4 @@ export { textFilters, arrayFilters, dataFilters, rdfFilters };
|
|
|
267
415
|
/**
|
|
268
416
|
* Export default function
|
|
269
417
|
*/
|
|
270
|
-
export default createCustomFilters;
|
|
418
|
+
export default createCustomFilters;
|
package/src/filters/rdf.js
CHANGED
|
@@ -79,7 +79,7 @@ export const contract = (uri, prefixes = {}) => {
|
|
|
79
79
|
* @param {any} dataset - Mock dataset (ignored in stub)
|
|
80
80
|
* @returns {Array} Mock query results
|
|
81
81
|
*/
|
|
82
|
-
export const sparql = async (query,
|
|
82
|
+
export const sparql = async (query, _dataset = null) => {
|
|
83
83
|
if (!query || typeof query !== 'string') return [];
|
|
84
84
|
|
|
85
85
|
// Return mock results for known queries
|
|
@@ -227,7 +227,7 @@ export const rdfDatatype = (value, datatype = 'string') => {
|
|
|
227
227
|
* @param {any} shape - SHACL shape
|
|
228
228
|
* @returns {Object} Mock validation result
|
|
229
229
|
*/
|
|
230
|
-
export const shaclValidate = (
|
|
230
|
+
export const shaclValidate = (_data, _shape) => {
|
|
231
231
|
return {
|
|
232
232
|
conforms: true,
|
|
233
233
|
results: [],
|
|
@@ -241,7 +241,7 @@ export const shaclValidate = (data, shape) => {
|
|
|
241
241
|
* @param {string} reasoner - Reasoner type
|
|
242
242
|
* @returns {any} Original graph (no inference applied)
|
|
243
243
|
*/
|
|
244
|
-
export const infer = (graph,
|
|
244
|
+
export const infer = (graph, _reasoner = 'rdfs') => {
|
|
245
245
|
return graph; // No-op for stub
|
|
246
246
|
};
|
|
247
247
|
|
|
@@ -11,6 +11,13 @@
|
|
|
11
11
|
* - Marker-based targeting and rollback capabilities (NEW)
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
// Import classes/functions for local use
|
|
15
|
+
import { TemplateEngine, EnhancedTemplateEngine } from './engine/template-engine.js';
|
|
16
|
+
import { TemplateInheritanceEngine } from './inheritance/index.js';
|
|
17
|
+
import { enhanceKgenWithInjection } from './injection/integration.js';
|
|
18
|
+
import { initializeInjection } from './injection/api.js';
|
|
19
|
+
|
|
20
|
+
// Re-export for external use
|
|
14
21
|
export { TemplateEngine, EnhancedTemplateEngine } from './engine/template-engine.js';
|
|
15
22
|
export { TemplateInheritanceEngine } from './inheritance/index.js';
|
|
16
23
|
export { createCustomFilters } from './filters/index.js';
|
|
@@ -75,6 +82,9 @@ export {
|
|
|
75
82
|
} from './injection/constants.js';
|
|
76
83
|
|
|
77
84
|
// Enhanced engine factory with inheritance and injection support
|
|
85
|
+
/**
|
|
86
|
+
*
|
|
87
|
+
*/
|
|
78
88
|
export function createEngine(options = {}) {
|
|
79
89
|
// Use EnhancedTemplateEngine by default for inheritance support
|
|
80
90
|
const engineClass = options.useBasicEngine ? TemplateEngine : EnhancedTemplateEngine;
|
|
@@ -89,11 +99,17 @@ export function createEngine(options = {}) {
|
|
|
89
99
|
}
|
|
90
100
|
|
|
91
101
|
// Inheritance-specific engine factory
|
|
102
|
+
/**
|
|
103
|
+
*
|
|
104
|
+
*/
|
|
92
105
|
export function createInheritanceEngine(options = {}) {
|
|
93
106
|
return new TemplateInheritanceEngine(options);
|
|
94
107
|
}
|
|
95
108
|
|
|
96
109
|
// Enhanced template engine factory (inheritance + all features)
|
|
110
|
+
/**
|
|
111
|
+
*
|
|
112
|
+
*/
|
|
97
113
|
export function createEnhancedEngine(options = {}) {
|
|
98
114
|
return new EnhancedTemplateEngine({
|
|
99
115
|
enableInheritance: true,
|
|
@@ -104,6 +120,36 @@ export function createEnhancedEngine(options = {}) {
|
|
|
104
120
|
}
|
|
105
121
|
|
|
106
122
|
// Injection-specific engine factory
|
|
123
|
+
/**
|
|
124
|
+
*
|
|
125
|
+
*/
|
|
107
126
|
export function createInjectionEngine(injectionConfig = {}) {
|
|
108
127
|
return initializeInjection(injectionConfig);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// RDF TEMPLATE SYSTEM EXPORTS (NEW)
|
|
131
|
+
// RDF-aware template engine with @unrdf/core integration
|
|
132
|
+
export {
|
|
133
|
+
RdfTemplateEngine,
|
|
134
|
+
createRdfTemplateEngine,
|
|
135
|
+
renderRdfTemplate,
|
|
136
|
+
RdfTemplateEngineConfigSchema,
|
|
137
|
+
RdfRenderContextSchema,
|
|
138
|
+
SparqlTemplateSchema,
|
|
139
|
+
} from './rdf/index.js';
|
|
140
|
+
|
|
141
|
+
// RDF template filters
|
|
142
|
+
export {
|
|
143
|
+
rdfTemplateFilters,
|
|
144
|
+
toTurtle,
|
|
145
|
+
toSparql,
|
|
146
|
+
rdfPrefix,
|
|
147
|
+
blankNode,
|
|
148
|
+
literal,
|
|
149
|
+
} from './rdf/filters.js';
|
|
150
|
+
|
|
151
|
+
// RDF engine factory
|
|
152
|
+
export async function createRdfEngine(config = {}) {
|
|
153
|
+
const { RdfTemplateEngine } = await import('./rdf/index.js');
|
|
154
|
+
return new RdfTemplateEngine(config);
|
|
109
155
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file KGN package basic tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
import {
|
|
7
|
+
createEngine,
|
|
8
|
+
createInheritanceEngine,
|
|
9
|
+
createEnhancedEngine,
|
|
10
|
+
TemplateEngine,
|
|
11
|
+
EnhancedTemplateEngine,
|
|
12
|
+
} from './index.mjs';
|
|
13
|
+
|
|
14
|
+
describe('@unrdf/kgn', () => {
|
|
15
|
+
it('should export main engine classes', () => {
|
|
16
|
+
expect(TemplateEngine).toBeDefined();
|
|
17
|
+
expect(EnhancedTemplateEngine).toBeDefined();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should create basic engine', () => {
|
|
21
|
+
const engine = createEngine({ useBasicEngine: true });
|
|
22
|
+
expect(engine).toBeDefined();
|
|
23
|
+
expect(engine).toBeInstanceOf(TemplateEngine);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should create enhanced engine by default', () => {
|
|
27
|
+
const engine = createEngine();
|
|
28
|
+
expect(engine).toBeDefined();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should create inheritance engine', () => {
|
|
32
|
+
const engine = createInheritanceEngine();
|
|
33
|
+
expect(engine).toBeDefined();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should create enhanced engine with options', () => {
|
|
37
|
+
const engine = createEnhancedEngine({ enableCache: false });
|
|
38
|
+
expect(engine).toBeDefined();
|
|
39
|
+
});
|
|
40
|
+
});
|
package/src/inheritance/index.js
CHANGED
|
@@ -2,12 +2,21 @@
|
|
|
2
2
|
* Template Inheritance System - Placeholder for future implementation
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
5
8
|
export class TemplateInheritanceEngine {
|
|
9
|
+
/**
|
|
10
|
+
*
|
|
11
|
+
*/
|
|
6
12
|
constructor(options = {}) {
|
|
7
13
|
this.options = options;
|
|
8
14
|
}
|
|
9
15
|
|
|
10
|
-
|
|
16
|
+
/**
|
|
17
|
+
*
|
|
18
|
+
*/
|
|
19
|
+
async processTemplate(templatePath, _context = {}) {
|
|
11
20
|
// Basic implementation for build compatibility
|
|
12
21
|
return {
|
|
13
22
|
templatePath,
|
|
@@ -18,6 +27,9 @@ export class TemplateInheritanceEngine {
|
|
|
18
27
|
};
|
|
19
28
|
}
|
|
20
29
|
|
|
30
|
+
/**
|
|
31
|
+
*
|
|
32
|
+
*/
|
|
21
33
|
getStats() {
|
|
22
34
|
return {
|
|
23
35
|
templatesProcessed: 0,
|
|
@@ -28,10 +40,16 @@ export class TemplateInheritanceEngine {
|
|
|
28
40
|
};
|
|
29
41
|
}
|
|
30
42
|
|
|
43
|
+
/**
|
|
44
|
+
*
|
|
45
|
+
*/
|
|
31
46
|
async clearCache() {
|
|
32
47
|
// No-op for now
|
|
33
48
|
}
|
|
34
49
|
|
|
50
|
+
/**
|
|
51
|
+
*
|
|
52
|
+
*/
|
|
35
53
|
getConfig() {
|
|
36
54
|
return this.options;
|
|
37
55
|
}
|
|
@@ -10,9 +10,15 @@ import { join, dirname, basename, extname } from 'path';
|
|
|
10
10
|
import { createHash } from 'crypto';
|
|
11
11
|
import { pipeline } from 'stream/promises';
|
|
12
12
|
|
|
13
|
-
import { ERROR_CODES, CHECKSUM_ALGORITHMS, LOCK_CONFIG } from './constants.js';
|
|
13
|
+
import { ERROR_CODES as _ERROR_CODES, CHECKSUM_ALGORITHMS, LOCK_CONFIG } from './constants.js';
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
*
|
|
17
|
+
*/
|
|
15
18
|
export class AtomicWriter {
|
|
19
|
+
/**
|
|
20
|
+
*
|
|
21
|
+
*/
|
|
16
22
|
constructor(config = {}) {
|
|
17
23
|
this.config = config;
|
|
18
24
|
this.activeLocks = new Map();
|
|
@@ -118,6 +124,9 @@ export class AtomicWriter {
|
|
|
118
124
|
* Private Methods
|
|
119
125
|
*/
|
|
120
126
|
|
|
127
|
+
/**
|
|
128
|
+
*
|
|
129
|
+
*/
|
|
121
130
|
async _acquireLock(filePath) {
|
|
122
131
|
const lockId = `${filePath}-${Date.now()}-${Math.random()}`;
|
|
123
132
|
const startTime = Date.now();
|
|
@@ -154,6 +163,9 @@ export class AtomicWriter {
|
|
|
154
163
|
throw new Error(`Failed to acquire lock for ${filePath} within timeout`);
|
|
155
164
|
}
|
|
156
165
|
|
|
166
|
+
/**
|
|
167
|
+
*
|
|
168
|
+
*/
|
|
157
169
|
async _releaseLock(lockId) {
|
|
158
170
|
const lockFile = this.activeLocks.get(lockId);
|
|
159
171
|
if (lockFile) {
|
|
@@ -166,6 +178,9 @@ export class AtomicWriter {
|
|
|
166
178
|
}
|
|
167
179
|
}
|
|
168
180
|
|
|
181
|
+
/**
|
|
182
|
+
*
|
|
183
|
+
*/
|
|
169
184
|
async _createBackup(filePath, operationId) {
|
|
170
185
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
171
186
|
const ext = extname(filePath);
|
|
@@ -182,6 +197,9 @@ export class AtomicWriter {
|
|
|
182
197
|
return backupPath;
|
|
183
198
|
}
|
|
184
199
|
|
|
200
|
+
/**
|
|
201
|
+
*
|
|
202
|
+
*/
|
|
185
203
|
async _createTempFile(filePath) {
|
|
186
204
|
const dir = dirname(filePath);
|
|
187
205
|
const base = basename(filePath);
|
|
@@ -189,6 +207,9 @@ export class AtomicWriter {
|
|
|
189
207
|
return join(dir, tempName);
|
|
190
208
|
}
|
|
191
209
|
|
|
210
|
+
/**
|
|
211
|
+
*
|
|
212
|
+
*/
|
|
192
213
|
async _calculateChecksum(filePath, algorithm = CHECKSUM_ALGORITHMS.SHA256) {
|
|
193
214
|
const hash = createHash(algorithm);
|
|
194
215
|
const stream = createReadStream(filePath);
|
|
@@ -10,7 +10,13 @@ import { createHash } from 'crypto';
|
|
|
10
10
|
|
|
11
11
|
import { SKIP_IF_LOGIC, CHECKSUM_ALGORITHMS } from './constants.js';
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
*
|
|
15
|
+
*/
|
|
13
16
|
export class IdempotencyManager {
|
|
17
|
+
/**
|
|
18
|
+
*
|
|
19
|
+
*/
|
|
14
20
|
constructor(config = {}) {
|
|
15
21
|
this.config = config;
|
|
16
22
|
this.contentCache = new Map();
|
|
@@ -103,6 +109,9 @@ export class IdempotencyManager {
|
|
|
103
109
|
* Private Methods
|
|
104
110
|
*/
|
|
105
111
|
|
|
112
|
+
/**
|
|
113
|
+
*
|
|
114
|
+
*/
|
|
106
115
|
async _evaluateSingleCondition(condition, target, content, variables) {
|
|
107
116
|
// Built-in conditions
|
|
108
117
|
if (condition === 'file_exists') {
|
|
@@ -155,6 +164,9 @@ export class IdempotencyManager {
|
|
|
155
164
|
};
|
|
156
165
|
}
|
|
157
166
|
|
|
167
|
+
/**
|
|
168
|
+
*
|
|
169
|
+
*/
|
|
158
170
|
async _evaluateMultipleConditions(conditions, target, content, variables) {
|
|
159
171
|
const logic = target.skipIfLogic || SKIP_IF_LOGIC.OR;
|
|
160
172
|
const results = [];
|
|
@@ -181,6 +193,9 @@ export class IdempotencyManager {
|
|
|
181
193
|
}
|
|
182
194
|
}
|
|
183
195
|
|
|
196
|
+
/**
|
|
197
|
+
*
|
|
198
|
+
*/
|
|
184
199
|
async _evaluateObjectCondition(conditionObj, target, content, variables) {
|
|
185
200
|
// Complex object-based conditions
|
|
186
201
|
const { pattern, exists, custom, hash } = conditionObj;
|
|
@@ -218,12 +233,18 @@ export class IdempotencyManager {
|
|
|
218
233
|
return { skip: false, reason: 'Invalid object condition' };
|
|
219
234
|
}
|
|
220
235
|
|
|
236
|
+
/**
|
|
237
|
+
*
|
|
238
|
+
*/
|
|
221
239
|
_interpolateVariables(template, variables) {
|
|
222
240
|
return template.replace(/\{\{(\w+)\}\}/g, (match, variable) => {
|
|
223
241
|
return variables[variable] || match;
|
|
224
242
|
});
|
|
225
243
|
}
|
|
226
244
|
|
|
245
|
+
/**
|
|
246
|
+
*
|
|
247
|
+
*/
|
|
227
248
|
async _getFileContent(filePath) {
|
|
228
249
|
const cacheKey = filePath;
|
|
229
250
|
|
|
@@ -244,6 +265,9 @@ export class IdempotencyManager {
|
|
|
244
265
|
}
|
|
245
266
|
}
|
|
246
267
|
|
|
268
|
+
/**
|
|
269
|
+
*
|
|
270
|
+
*/
|
|
247
271
|
async _fileExists(filePath) {
|
|
248
272
|
try {
|
|
249
273
|
await fs.access(filePath);
|
|
@@ -253,6 +277,9 @@ export class IdempotencyManager {
|
|
|
253
277
|
}
|
|
254
278
|
}
|
|
255
279
|
|
|
280
|
+
/**
|
|
281
|
+
*
|
|
282
|
+
*/
|
|
256
283
|
async _getFileSize(filePath) {
|
|
257
284
|
try {
|
|
258
285
|
const stats = await fs.stat(filePath);
|
|
@@ -265,6 +292,9 @@ export class IdempotencyManager {
|
|
|
265
292
|
}
|
|
266
293
|
}
|
|
267
294
|
|
|
295
|
+
/**
|
|
296
|
+
*
|
|
297
|
+
*/
|
|
268
298
|
async _getLineCount(filePath) {
|
|
269
299
|
try {
|
|
270
300
|
const content = await this._getFileContent(filePath);
|
|
@@ -274,6 +304,9 @@ export class IdempotencyManager {
|
|
|
274
304
|
}
|
|
275
305
|
}
|
|
276
306
|
|
|
307
|
+
/**
|
|
308
|
+
*
|
|
309
|
+
*/
|
|
277
310
|
async _getExistingContentHash(target) {
|
|
278
311
|
try {
|
|
279
312
|
const content = await this._getFileContent(target.resolvedPath);
|