@unrdf/project-engine 5.0.1 → 26.4.3

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 (39) hide show
  1. package/package.json +6 -5
  2. package/src/golden-structure.mjs +2 -2
  3. package/src/materialize-apply.mjs +2 -2
  4. package/README.md +0 -53
  5. package/src/api-contract-validator.mjs +0 -711
  6. package/src/auto-test-generator.mjs +0 -444
  7. package/src/autonomic-mapek.mjs +0 -511
  8. package/src/capabilities-manifest.mjs +0 -125
  9. package/src/code-complexity-js.mjs +0 -368
  10. package/src/dependency-graph.mjs +0 -276
  11. package/src/doc-drift-checker.mjs +0 -172
  12. package/src/doc-generator.mjs +0 -229
  13. package/src/domain-infer.mjs +0 -966
  14. package/src/drift-snapshot.mjs +0 -775
  15. package/src/file-roles.mjs +0 -94
  16. package/src/fs-scan.mjs +0 -305
  17. package/src/gap-finder.mjs +0 -376
  18. package/src/hotspot-analyzer.mjs +0 -412
  19. package/src/index.mjs +0 -151
  20. package/src/initialize.mjs +0 -957
  21. package/src/lens/project-structure.mjs +0 -74
  22. package/src/mapek-orchestration.mjs +0 -665
  23. package/src/materialize-plan.mjs +0 -422
  24. package/src/materialize.mjs +0 -137
  25. package/src/policy-derivation.mjs +0 -869
  26. package/src/project-config.mjs +0 -142
  27. package/src/project-diff.mjs +0 -28
  28. package/src/project-engine/build-utils.mjs +0 -237
  29. package/src/project-engine/code-analyzer.mjs +0 -248
  30. package/src/project-engine/doc-generator.mjs +0 -407
  31. package/src/project-engine/infrastructure.mjs +0 -213
  32. package/src/project-engine/metrics.mjs +0 -146
  33. package/src/project-model.mjs +0 -111
  34. package/src/project-report.mjs +0 -348
  35. package/src/refactoring-guide.mjs +0 -242
  36. package/src/stack-detect.mjs +0 -102
  37. package/src/stack-linter.mjs +0 -213
  38. package/src/template-infer.mjs +0 -674
  39. package/src/type-auditor.mjs +0 -609
@@ -1,674 +0,0 @@
1
- /**
2
- * @file Pattern induction - learns generator templates from existing project code
3
- * @module project-engine/template-infer
4
- */
5
-
6
- import { UnrdfDataFactory as DataFactory } from '@unrdf/core/rdf/n3-justified-only';
7
- import { createStore } from '@unrdf/oxigraph'; // TODO: Replace with Oxigraph Store
8
- import { z } from 'zod';
9
-
10
- const { namedNode, literal } = DataFactory;
11
-
12
- /* ========================================================================= */
13
- /* Zod Schemas */
14
- /* ========================================================================= */
15
-
16
- const InferOptionsSchema = z.object({
17
- fsStore: z.any().refine(val => val && typeof val.getQuads === 'function', {
18
- message: 'fsStore must be an RDF store with getQuads method',
19
- }),
20
- domainStore: z.any().nullable().optional(),
21
- stackProfile: z
22
- .object({
23
- uiFramework: z.string().nullable().optional(),
24
- webFramework: z.string().nullable().optional(),
25
- testFramework: z.string().nullable().optional(),
26
- language: z.string().nullable().optional(),
27
- })
28
- .optional(),
29
- });
30
-
31
- export const TemplateSchema = z.object({
32
- id: z.string(),
33
- kind: z.string(),
34
- outputPattern: z.string(),
35
- variables: z.array(z.string()),
36
- invariants: z.array(z.string()),
37
- variantCount: z.number(),
38
- examples: z.array(z.string()),
39
- });
40
-
41
- export const InferSummarySchema = z.object({
42
- templateCount: z.number(),
43
- byKind: z.record(z.string(), z.number()),
44
- });
45
-
46
- /* ========================================================================= */
47
- /* Namespaces */
48
- /* ========================================================================= */
49
-
50
- const NS = {
51
- rdf: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
52
- rdfs: 'http://www.w3.org/2000/01/rdf-schema#',
53
- xsd: 'http://www.w3.org/2001/XMLSchema#',
54
- gen: 'http://example.org/unrdf/generator#',
55
- dom: 'http://example.org/unrdf/domain#',
56
- unproj: 'http://example.org/unrdf/project#',
57
- fs: 'http://example.org/unrdf/filesystem#',
58
- };
59
-
60
- /* ========================================================================= */
61
- /* File Family Patterns */
62
- /* ========================================================================= */
63
-
64
- /**
65
- * File family detection patterns
66
- * Each pattern defines how to identify and extract templates from file groups
67
- */
68
- const FILE_FAMILY_PATTERNS = [
69
- {
70
- kind: 'Component',
71
- patterns: [
72
- /^src\/features\/([^/]+)\/([A-Z][a-zA-Z]+)(Component|View|Page)?\.(tsx?|jsx?)$/,
73
- /^src\/components\/([^/]+)\/(index|[A-Z][a-zA-Z]+)\.(tsx?|jsx?)$/,
74
- /^components\/([^/]+)\/(index|[A-Z][a-zA-Z]+)\.(tsx?|jsx?)$/,
75
- ],
76
- outputTemplate: 'src/features/{{entity}}/{{Entity}}{{suffix}}.{{ext}}',
77
- extractVars: (match, _path) => ({
78
- entity: match[1],
79
- Entity: capitalize(match[1]),
80
- suffix: match[3] || '',
81
- ext: match[4] || 'tsx',
82
- }),
83
- },
84
- {
85
- kind: 'Page',
86
- patterns: [
87
- /^src\/app\/([^/]+)\/page\.(tsx?|jsx?)$/,
88
- /^src\/pages\/([^/]+)\/(index|page)\.(tsx?|jsx?)$/,
89
- /^pages\/([^/]+)\/(index|page)\.(tsx?|jsx?)$/,
90
- /^app\/([^/]+)\/page\.(tsx?|jsx?)$/,
91
- ],
92
- outputTemplate: 'src/app/{{route}}/page.{{ext}}',
93
- extractVars: (match, _path) => ({
94
- route: match[1],
95
- ext: match[2] || 'tsx',
96
- }),
97
- },
98
- {
99
- kind: 'Route',
100
- patterns: [
101
- /^src\/app\/([^/]+)\/route\.(tsx?|ts)$/,
102
- /^src\/routes\/([^/]+)\.(tsx?|ts)$/,
103
- /^app\/api\/([^/]+)\/route\.(tsx?|ts)$/,
104
- ],
105
- outputTemplate: 'src/app/{{route}}/route.{{ext}}',
106
- extractVars: (match, _path) => ({
107
- route: match[1],
108
- ext: match[2] || 'ts',
109
- }),
110
- },
111
- {
112
- kind: 'Test',
113
- patterns: [
114
- /^src\/([^/]+)\/([^/]+)\.(test|spec)\.(tsx?|jsx?)$/,
115
- /^test\/([^/]+)\/([^/]+)\.(test|spec)\.(tsx?|jsx?)$/,
116
- /^__tests__\/([^/]+)\.(test|spec)\.(tsx?|jsx?)$/,
117
- /^([^/]+)\.(test|spec)\.(tsx?|jsx?)$/,
118
- ],
119
- outputTemplate: 'test/{{module}}/{{name}}.{{testType}}.{{ext}}',
120
- extractVars: (match, _path) => ({
121
- module: match[1] || 'unit',
122
- name: match[2] || match[1],
123
- testType: match[3] || 'test',
124
- ext: match[4] || 'ts',
125
- }),
126
- },
127
- {
128
- kind: 'Api',
129
- patterns: [
130
- /^src\/api\/([^/]+)\/(route|handler|controller)\.(tsx?|ts)$/,
131
- /^api\/([^/]+)\.(tsx?|ts)$/,
132
- /^src\/server\/([^/]+)\.(tsx?|ts)$/,
133
- ],
134
- outputTemplate: 'src/api/{{endpoint}}/route.{{ext}}',
135
- extractVars: (match, _path) => ({
136
- endpoint: match[1],
137
- ext: match[3] || match[2] || 'ts',
138
- }),
139
- },
140
- {
141
- kind: 'Hook',
142
- patterns: [
143
- /^src\/hooks\/(use[A-Z][a-zA-Z]+)\.(tsx?|ts)$/,
144
- /^hooks\/(use[A-Z][a-zA-Z]+)\.(tsx?|ts)$/,
145
- /^src\/features\/([^/]+)\/hooks\/(use[A-Z][a-zA-Z]+)\.(tsx?|ts)$/,
146
- ],
147
- outputTemplate: 'src/hooks/{{hookName}}.{{ext}}',
148
- extractVars: (match, _path) => ({
149
- hookName: match[1] || match[2],
150
- feature: match[1] && match[2] ? match[1] : null,
151
- ext: match[2] || match[3] || 'ts',
152
- }),
153
- },
154
- {
155
- kind: 'Service',
156
- patterns: [
157
- /^src\/services\/([^/]+)\.(service|client)\.(tsx?|ts)$/,
158
- /^src\/lib\/([^/]+)\.(tsx?|ts)$/,
159
- /^lib\/([^/]+)\.(tsx?|ts)$/,
160
- ],
161
- outputTemplate: 'src/services/{{serviceName}}.service.{{ext}}',
162
- extractVars: (match, _path) => ({
163
- serviceName: match[1],
164
- ext: match[3] || match[2] || 'ts',
165
- }),
166
- },
167
- {
168
- kind: 'Schema',
169
- patterns: [
170
- /^src\/schemas?\/([^/]+)\.(schema|types?)\.(tsx?|ts)$/,
171
- /^src\/types\/([^/]+)\.(tsx?|ts)$/,
172
- /^types\/([^/]+)\.(tsx?|ts)$/,
173
- ],
174
- outputTemplate: 'src/schemas/{{schemaName}}.schema.{{ext}}',
175
- extractVars: (match, _path) => ({
176
- schemaName: match[1],
177
- ext: match[3] || match[2] || 'ts',
178
- }),
179
- },
180
- {
181
- kind: 'Doc',
182
- patterns: [
183
- /^docs?\/([^/]+)\.(md|mdx)$/,
184
- /^src\/docs?\/([^/]+)\.(md|mdx)$/,
185
- /^([A-Z][A-Z_]+)\.(md|mdx)$/,
186
- ],
187
- outputTemplate: 'docs/{{docName}}.{{ext}}',
188
- extractVars: (match, _path) => ({
189
- docName: match[1],
190
- ext: match[2] || 'md',
191
- }),
192
- },
193
- {
194
- kind: 'Config',
195
- patterns: [
196
- /^\.?([a-z]+)rc\.(json|yaml|yml|js|cjs|mjs)$/,
197
- /^([a-z]+)\.config\.(tsx?|ts|js|cjs|mjs)$/,
198
- /^config\/([^/]+)\.(json|yaml|yml)$/,
199
- ],
200
- outputTemplate: '{{configName}}.config.{{ext}}',
201
- extractVars: (match, _path) => ({
202
- configName: match[1],
203
- ext: match[2] || 'js',
204
- }),
205
- },
206
- ];
207
-
208
- /* ========================================================================= */
209
- /* Helper Functions */
210
- /* ========================================================================= */
211
-
212
- /**
213
- * Capitalize first letter
214
- * @param {string} str
215
- * @returns {string}
216
- */
217
- function capitalize(str) {
218
- if (!str) return '';
219
- return str.charAt(0).toUpperCase() + str.slice(1);
220
- }
221
-
222
- /**
223
- * Convert camelCase to kebab-case
224
- * @param {string} str
225
- * @returns {string}
226
- */
227
- function _toKebabCase(str) {
228
- return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
229
- }
230
-
231
- /**
232
- * Extract file paths from fs store
233
- * @param {Store} fsStore
234
- * @returns {string[]}
235
- */
236
- function extractFilePathsFromStore(fsStore) {
237
- const paths = [];
238
-
239
- // Validate fsStore has getQuads method
240
- if (!fsStore || typeof fsStore.getQuads !== 'function') {
241
- return paths;
242
- }
243
-
244
- try {
245
- const quads = fsStore.getQuads(null, namedNode(`${NS.fs}relativePath`), null);
246
-
247
- for (const quad of quads) {
248
- paths.push(quad.object.value);
249
- }
250
- } catch (e) {
251
- // Handle stores that don't support getQuads properly
252
- return paths;
253
- }
254
-
255
- return paths;
256
- }
257
-
258
- /**
259
- * Group files by family pattern
260
- * @param {string[]} paths
261
- * @returns {Map<string, {pattern: Object, matches: Array<{path: string, match: RegExpMatchArray, vars: Object}>}>}
262
- */
263
- function groupFilesByFamily(paths) {
264
- const groups = new Map();
265
-
266
- for (const pattern of FILE_FAMILY_PATTERNS) {
267
- groups.set(pattern.kind, {
268
- pattern,
269
- matches: [],
270
- });
271
- }
272
-
273
- for (const path of paths) {
274
- for (const familyPattern of FILE_FAMILY_PATTERNS) {
275
- for (const regex of familyPattern.patterns) {
276
- const match = path.match(regex);
277
- if (match) {
278
- const vars = familyPattern.extractVars(match, path);
279
- const group = groups.get(familyPattern.kind);
280
- group.matches.push({ path, match, vars });
281
- break;
282
- }
283
- }
284
- }
285
- }
286
-
287
- return groups;
288
- }
289
-
290
- /**
291
- * Extract invariants from a group of similar files
292
- * @param {Array<{path: string, match: RegExpMatchArray, vars: Object}>} matches
293
- * @returns {string[]}
294
- */
295
- function extractInvariants(matches) {
296
- if (matches.length < 2) return [];
297
-
298
- const invariants = [];
299
-
300
- // Find common path prefixes
301
- const prefixes = matches.map(m => {
302
- const parts = m.path.split('/');
303
- return parts.slice(0, -1).join('/');
304
- });
305
- const commonPrefix = findCommonPrefix(prefixes);
306
- if (commonPrefix) {
307
- invariants.push(`path_prefix:${commonPrefix}`);
308
- }
309
-
310
- // Find common extensions
311
- const extensions = matches.map(m => {
312
- const parts = m.path.split('.');
313
- return parts[parts.length - 1];
314
- });
315
- const uniqueExts = [...new Set(extensions)];
316
- if (uniqueExts.length === 1) {
317
- invariants.push(`extension:${uniqueExts[0]}`);
318
- }
319
-
320
- // Find common naming patterns
321
- const baseNames = matches.map(m => {
322
- const parts = m.path.split('/');
323
- const fileName = parts[parts.length - 1];
324
- return fileName.split('.')[0];
325
- });
326
- const namingPattern = detectNamingPattern(baseNames);
327
- if (namingPattern) {
328
- invariants.push(`naming:${namingPattern}`);
329
- }
330
-
331
- return invariants;
332
- }
333
-
334
- /**
335
- * Find common prefix among strings
336
- * @param {string[]} strings
337
- * @returns {string}
338
- */
339
- function findCommonPrefix(strings) {
340
- if (strings.length === 0) return '';
341
- if (strings.length === 1) return strings[0];
342
-
343
- let prefix = strings[0];
344
- for (let i = 1; i < strings.length; i++) {
345
- while (!strings[i].startsWith(prefix) && prefix.length > 0) {
346
- prefix = prefix.slice(0, -1);
347
- }
348
- }
349
- return prefix;
350
- }
351
-
352
- /**
353
- * Detect naming pattern from base names
354
- * @param {string[]} names
355
- * @returns {string|null}
356
- */
357
- function detectNamingPattern(names) {
358
- const patterns = {
359
- PascalCase: /^[A-Z][a-zA-Z0-9]*$/,
360
- camelCase: /^[a-z][a-zA-Z0-9]*$/,
361
- 'kebab-case': /^[a-z][a-z0-9-]*$/,
362
- snake_case: /^[a-z][a-z0-9_]*$/,
363
- index: /^index$/,
364
- };
365
-
366
- for (const [name, regex] of Object.entries(patterns)) {
367
- if (names.every(n => regex.test(n))) {
368
- return name;
369
- }
370
- }
371
-
372
- return null;
373
- }
374
-
375
- /**
376
- * Extract variable names from output pattern
377
- * @param {string} pattern
378
- * @returns {string[]}
379
- */
380
- function extractVariablesFromPattern(pattern) {
381
- const matches = pattern.match(/\{\{(\w+)\}\}/g) || [];
382
- return matches.map(m => m.replace(/\{\{|\}\}/g, ''));
383
- }
384
-
385
- /**
386
- * Generate template ID
387
- * @param {string} kind
388
- * @param {number} index
389
- * @returns {string}
390
- */
391
- function generateTemplateId(kind, index) {
392
- return `${kind}Template${index > 0 ? index + 1 : ''}`;
393
- }
394
-
395
- /* ========================================================================= */
396
- /* RDF Generation */
397
- /* ========================================================================= */
398
-
399
- /**
400
- * Add template triples to store
401
- * @param {Store} store
402
- * @param {Object} template
403
- */
404
- function addTemplateToStore(store, template) {
405
- const templateIri = namedNode(`${NS.gen}${template.id}`);
406
-
407
- // rdf:type gen:Template
408
- store.addQuad(templateIri, namedNode(`${NS.rdf}type`), namedNode(`${NS.gen}Template`));
409
-
410
- // gen:templateKind
411
- store.addQuad(templateIri, namedNode(`${NS.gen}templateKind`), literal(template.kind));
412
-
413
- // gen:outputPattern
414
- store.addQuad(templateIri, namedNode(`${NS.gen}outputPattern`), literal(template.outputPattern));
415
-
416
- // gen:variantCount
417
- store.addQuad(
418
- templateIri,
419
- namedNode(`${NS.gen}variantCount`),
420
- literal(template.variantCount, namedNode(`${NS.xsd}integer`))
421
- );
422
-
423
- // gen:variable (multiple)
424
- for (const variable of template.variables) {
425
- store.addQuad(templateIri, namedNode(`${NS.gen}variable`), literal(variable));
426
- }
427
-
428
- // gen:invariant (multiple)
429
- for (const invariant of template.invariants) {
430
- store.addQuad(templateIri, namedNode(`${NS.gen}invariant`), literal(invariant));
431
- }
432
-
433
- // gen:example (multiple, max 3)
434
- for (const example of template.examples.slice(0, 3)) {
435
- store.addQuad(templateIri, namedNode(`${NS.gen}example`), literal(example));
436
- }
437
-
438
- // gen:producesRole
439
- store.addQuad(
440
- templateIri,
441
- namedNode(`${NS.gen}producesRole`),
442
- namedNode(`${NS.unproj}${template.kind}`)
443
- );
444
- }
445
-
446
- /* ========================================================================= */
447
- /* Public API */
448
- /* ========================================================================= */
449
-
450
- /**
451
- * Infer generator templates from existing project code
452
- *
453
- * Scans project files, groups them by family, extracts patterns,
454
- * and returns RDF triples describing the inferred templates.
455
- *
456
- * @param {Object} fsStore - Filesystem store with project files
457
- * @param {Object} [domainStore] - Optional domain ontology store
458
- * @param {Object} [stackProfile] - Optional stack detection info
459
- * @returns {{store: Store, summary: {templateCount: number, byKind: Record<string, number>}}}
460
- *
461
- * @example
462
- * const { store, summary } = inferTemplatesFromProject(fsStore)
463
- * console.log(`Found ${summary.templateCount} templates`)
464
- * // store contains RDF triples like:
465
- * // gen:UserViewTemplate rdf:type gen:Template
466
- * // gen:UserViewTemplate gen:outputPattern "src/features/{{entity}}/{{Entity}}Page.tsx"
467
- */
468
- export function inferTemplatesFromProject(fsStore, domainStore, stackProfile) {
469
- const validated = InferOptionsSchema.parse({
470
- fsStore,
471
- domainStore,
472
- stackProfile,
473
- });
474
-
475
- const store = createStore();
476
- const summary = {
477
- templateCount: 0,
478
- byKind: {},
479
- };
480
-
481
- // Extract file paths from fsStore
482
- const paths = extractFilePathsFromStore(validated.fsStore);
483
-
484
- // Group files by family pattern
485
- const groups = groupFilesByFamily(paths);
486
-
487
- // Process each family group
488
- for (const [kind, group] of groups) {
489
- const { pattern, matches } = group;
490
-
491
- // Skip families with too few matches (need >= 2 for pattern detection)
492
- if (matches.length < 2) continue;
493
-
494
- // Extract invariants from the matched files
495
- const invariants = extractInvariants(matches);
496
-
497
- // Extract variables from the output pattern
498
- const variables = extractVariablesFromPattern(pattern.outputTemplate);
499
-
500
- // Create template
501
- const templateId = generateTemplateId(kind, 0);
502
- const template = {
503
- id: templateId,
504
- kind,
505
- outputPattern: pattern.outputTemplate,
506
- variables,
507
- invariants,
508
- variantCount: matches.length,
509
- examples: matches.map(m => m.path),
510
- };
511
-
512
- // Add to store
513
- addTemplateToStore(store, template);
514
-
515
- // Update summary
516
- summary.templateCount++;
517
- summary.byKind[kind] = (summary.byKind[kind] || 0) + 1;
518
- }
519
-
520
- // Validate summary
521
- InferSummarySchema.parse(summary);
522
-
523
- return { store, summary };
524
- }
525
-
526
- /**
527
- * Infer templates with domain entity binding
528
- *
529
- * Enhanced version that also attempts to bind templates to domain entities
530
- * from the domain ontology store.
531
- *
532
- * @param {Object} fsStore - Filesystem store with project files
533
- * @param {Object} domainStore - Domain ontology store with entities
534
- * @param {Object} [stackProfile] - Optional stack detection info
535
- * @returns {{store: Store, summary: {templateCount: number, byKind: Record<string, number>, boundEntities: number}}}
536
- */
537
- export function inferTemplatesWithDomainBinding(fsStore, domainStore, stackProfile) {
538
- const { store, summary } = inferTemplatesFromProject(fsStore, domainStore, stackProfile);
539
-
540
- let boundEntities = 0;
541
-
542
- // Try to bind templates to domain entities
543
- if (domainStore) {
544
- try {
545
- // Get domain entities (classes)
546
- const entityQuads = domainStore.getQuads(
547
- null,
548
- namedNode(`${NS.rdf}type`),
549
- namedNode(`${NS.rdfs}Class`)
550
- );
551
-
552
- const entities = entityQuads.map(q => q.subject.value);
553
-
554
- // Get all templates from store
555
- const templateQuads = store.getQuads(
556
- null,
557
- namedNode(`${NS.rdf}type`),
558
- namedNode(`${NS.gen}Template`)
559
- );
560
-
561
- // For each template, try to find matching entities
562
- for (const templateQuad of templateQuads) {
563
- const templateIri = templateQuad.subject;
564
-
565
- // Get examples for this template
566
- const exampleQuads = store.getQuads(templateIri, namedNode(`${NS.gen}example`), null);
567
-
568
- for (const exampleQuad of exampleQuads) {
569
- const examplePath = exampleQuad.object.value;
570
-
571
- // Try to match entity names in the path
572
- for (const entityIri of entities) {
573
- const entityName = entityIri.split(/[#/]/).pop();
574
- if (entityName && examplePath.toLowerCase().includes(entityName.toLowerCase())) {
575
- // Add binding
576
- store.addQuad(templateIri, namedNode(`${NS.gen}targetsClass`), namedNode(entityIri));
577
- boundEntities++;
578
- break;
579
- }
580
- }
581
- }
582
- }
583
- } catch (e) {
584
- // Ignore binding errors, templates still work
585
- }
586
- }
587
-
588
- return {
589
- store,
590
- summary: {
591
- ...summary,
592
- boundEntities,
593
- },
594
- };
595
- }
596
-
597
- /**
598
- * Get templates by kind from store
599
- *
600
- * @param {Store} store - Template store
601
- * @param {string} kind - Template kind (Component, Page, Test, etc.)
602
- * @returns {Array<{iri: string, outputPattern: string, variantCount: number}>}
603
- */
604
- export function getTemplatesByKind(store, kind) {
605
- const templates = [];
606
-
607
- const templateQuads = store.getQuads(null, namedNode(`${NS.gen}templateKind`), literal(kind));
608
-
609
- for (const quad of templateQuads) {
610
- const templateIri = quad.subject.value;
611
-
612
- // Get output pattern
613
- const patternQuads = store.getQuads(quad.subject, namedNode(`${NS.gen}outputPattern`), null);
614
- const outputPattern = patternQuads[0]?.object.value || '';
615
-
616
- // Get variant count
617
- const countQuads = store.getQuads(quad.subject, namedNode(`${NS.gen}variantCount`), null);
618
- const variantCount = parseInt(countQuads[0]?.object.value || '0', 10);
619
-
620
- templates.push({
621
- iri: templateIri,
622
- outputPattern,
623
- variantCount,
624
- });
625
- }
626
-
627
- return templates;
628
- }
629
-
630
- /**
631
- * Serialize templates to a plain object for debugging/export
632
- *
633
- * @param {Store} store - Template store
634
- * @returns {Object[]}
635
- */
636
- export function serializeTemplates(store) {
637
- const templates = [];
638
-
639
- const templateQuads = store.getQuads(
640
- null,
641
- namedNode(`${NS.rdf}type`),
642
- namedNode(`${NS.gen}Template`)
643
- );
644
-
645
- for (const quad of templateQuads) {
646
- const iri = quad.subject;
647
-
648
- const template = {
649
- id: iri.value.split('#').pop(),
650
- iri: iri.value,
651
- };
652
-
653
- // Get all properties
654
- const propQuads = store.getQuads(iri, null, null);
655
-
656
- for (const pq of propQuads) {
657
- const propName = pq.predicate.value.split('#').pop();
658
- const value = pq.object.value;
659
-
660
- if (propName === 'type') continue;
661
-
662
- if (['variable', 'invariant', 'example'].includes(propName)) {
663
- if (!template[propName]) template[propName] = [];
664
- template[propName].push(value);
665
- } else {
666
- template[propName] = value;
667
- }
668
- }
669
-
670
- templates.push(template);
671
- }
672
-
673
- return templates;
674
- }