@linklabjs/core 0.1.0
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/README.md +411 -0
- package/package.json +48 -0
- package/src/api/DomainNode.ts +1433 -0
- package/src/api/Graph.ts +271 -0
- package/src/api/PathBuilder.ts +247 -0
- package/src/api/index.ts +15 -0
- package/src/api/loadGraph.ts +207 -0
- package/src/api/test-api.ts +153 -0
- package/src/api/test-domain.ts +119 -0
- package/src/api/types.ts +88 -0
- package/src/config/synonyms.json +28 -0
- package/src/core/EventBus.ts +187 -0
- package/src/core/GraphEvents.ts +153 -0
- package/src/core/PathFinder.ts +283 -0
- package/src/formatters/BaseFormatter.ts +17 -0
- package/src/graph/GraphAssembler.ts +50 -0
- package/src/graph/GraphCompiler.ts +412 -0
- package/src/graph/GraphExtractor.ts +191 -0
- package/src/graph/GraphOptimizer.ts +404 -0
- package/src/graph/GraphTrainer.ts +247 -0
- package/src/http/LinkBuilder.ts +244 -0
- package/src/http/TrailRequest.ts +48 -0
- package/src/http/example-netflix.ts +59 -0
- package/src/http/hateoas/README.md +87 -0
- package/src/http/index.ts +33 -0
- package/src/http/plugin.ts +360 -0
- package/src/index.ts +121 -0
- package/src/instrumentation/TelemetryShim.ts +172 -0
- package/src/navigation/NavigationEngine.ts +441 -0
- package/src/navigation/Resolver.ts +134 -0
- package/src/navigation/Scheduler.ts +136 -0
- package/src/navigation/Trail.ts +252 -0
- package/src/navigation/TrailParser.ts +207 -0
- package/src/navigation/index.ts +11 -0
- package/src/providers/MockProvider.ts +68 -0
- package/src/providers/PostgresProvider.ts +187 -0
- package/src/runtime/CompiledGraphEngine.ts +274 -0
- package/src/runtime/DataLoader.ts +236 -0
- package/src/runtime/Engine.ts +163 -0
- package/src/runtime/QueryEngine.ts +222 -0
- package/src/scenarios/test-metro-paris/config.json +6 -0
- package/src/scenarios/test-metro-paris/graph.json +16325 -0
- package/src/scenarios/test-metro-paris/queries.ts +152 -0
- package/src/scenarios/test-metro-paris/stack.json +1 -0
- package/src/scenarios/test-musicians/config.json +10 -0
- package/src/scenarios/test-musicians/graph.json +20 -0
- package/src/scenarios/test-musicians/stack.json +1 -0
- package/src/scenarios/test-netflix/MIGRATION.md +23 -0
- package/src/scenarios/test-netflix/README.md +138 -0
- package/src/scenarios/test-netflix/actions.ts +92 -0
- package/src/scenarios/test-netflix/config.json +6 -0
- package/src/scenarios/test-netflix/data/categories.json +1 -0
- package/src/scenarios/test-netflix/data/companies.json +1 -0
- package/src/scenarios/test-netflix/data/credits.json +19797 -0
- package/src/scenarios/test-netflix/data/departments.json +18 -0
- package/src/scenarios/test-netflix/data/jobs.json +142 -0
- package/src/scenarios/test-netflix/data/movies.json +3497 -0
- package/src/scenarios/test-netflix/data/people.json +1 -0
- package/src/scenarios/test-netflix/data/synonyms.json +8 -0
- package/src/scenarios/test-netflix/data/users.json +70 -0
- package/src/scenarios/test-netflix/graph.json +1017 -0
- package/src/scenarios/test-netflix/queries.ts +159 -0
- package/src/scenarios/test-netflix/stack.json +14 -0
- package/src/schema/GraphBuilder.ts +106 -0
- package/src/schema/JsonSchemaExtractor.ts +107 -0
- package/src/schema/SchemaAnalyzer.ts +175 -0
- package/src/schema/SchemaExtractor.ts +102 -0
- package/src/schema/SynonymResolver.ts +143 -0
- package/src/scripts/dictionary.json +796 -0
- package/src/scripts/graph.json +664 -0
- package/src/scripts/regenerate.ts +248 -0
- package/src/types/index.ts +506 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SynonymResolver — Résolution de noms de tables par convention et synonymes
|
|
3
|
+
*
|
|
4
|
+
* Utilisé par :
|
|
5
|
+
* JsonSchemaExtractor — résolution FK par convention de nommage
|
|
6
|
+
* SchemaAnalyzer — détection FK implicites (store_id → store)
|
|
7
|
+
* GraphBuilder — à venir
|
|
8
|
+
*
|
|
9
|
+
* Sources de synonymes (fusionnées dans l'ordre) :
|
|
10
|
+
* 1. config/synonyms.json — irréguliers universels (livré avec LinkLab)
|
|
11
|
+
* 2. <projectPath>/synonyms.json — spécifiques au projet (optionnel)
|
|
12
|
+
*
|
|
13
|
+
* Stratégies de résolution dans l'ordre :
|
|
14
|
+
* 1. Correspondance directe prefix === tableName
|
|
15
|
+
* 2. Synonyme explicite synonyms[prefix] === tableName
|
|
16
|
+
* 3. Pluriel régulier +s prefix + 's'
|
|
17
|
+
* 4. Pluriel en -ies category → categories
|
|
18
|
+
* 5. Pluriel en -es address → addresses
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import fs from 'fs'
|
|
22
|
+
import path from 'path'
|
|
23
|
+
|
|
24
|
+
export class SynonymResolver {
|
|
25
|
+
|
|
26
|
+
private synonyms: Record<string, string>
|
|
27
|
+
|
|
28
|
+
constructor(
|
|
29
|
+
private configPath: string = path.join(process.cwd(), 'config'),
|
|
30
|
+
private projectPath?: string
|
|
31
|
+
) {
|
|
32
|
+
this.synonyms = this.load()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ─── Chargement ────────────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
private load(): Record<string, string> {
|
|
38
|
+
const universalPath = path.join(this.configPath, 'synonyms.json')
|
|
39
|
+
const projectPath = this.projectPath
|
|
40
|
+
? path.join(this.projectPath, 'synonyms.json')
|
|
41
|
+
: null
|
|
42
|
+
|
|
43
|
+
let universal: Record<string, string> = {}
|
|
44
|
+
let project: Record<string, string> = {}
|
|
45
|
+
|
|
46
|
+
if (fs.existsSync(universalPath)) {
|
|
47
|
+
universal = this.filter(JSON.parse(fs.readFileSync(universalPath, 'utf-8')))
|
|
48
|
+
} else {
|
|
49
|
+
console.warn(` ⚠️ SynonymResolver — config/synonyms.json introuvable : ${universalPath}`)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (projectPath && fs.existsSync(projectPath)) {
|
|
53
|
+
project = this.filter(JSON.parse(fs.readFileSync(projectPath, 'utf-8')))
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const merged = { ...universal, ...project }
|
|
57
|
+
|
|
58
|
+
const counts = [
|
|
59
|
+
`universels: ${Object.keys(universal).length}`,
|
|
60
|
+
Object.keys(project).length ? `projet: ${Object.keys(project).length}` : null,
|
|
61
|
+
`total: ${Object.keys(merged).length}`
|
|
62
|
+
].filter(Boolean).join(', ')
|
|
63
|
+
|
|
64
|
+
console.log(` 📖 Synonymes (${counts})`)
|
|
65
|
+
return merged
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Filtre les clés de commentaire (_comment, etc.) */
|
|
69
|
+
private filter(raw: Record<string, string>): Record<string, string> {
|
|
70
|
+
return Object.fromEntries(
|
|
71
|
+
Object.entries(raw).filter(([k]) => !k.startsWith('_'))
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ─── Résolution ────────────────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Résout un préfixe vers un nom de table existant.
|
|
79
|
+
* Retourne null si aucune table ne correspond.
|
|
80
|
+
*
|
|
81
|
+
* @param prefix Préfixe extrait du nom de colonne (ex: "person" depuis "personId")
|
|
82
|
+
* @param tableNames Liste des noms de tables disponibles
|
|
83
|
+
*/
|
|
84
|
+
resolve(prefix: string, tableNames: string[]): string | null {
|
|
85
|
+
const lc = (s: string) => s.toLowerCase()
|
|
86
|
+
const p = prefix.toLowerCase()
|
|
87
|
+
|
|
88
|
+
const pluralIes = p.endsWith('y') ? p.slice(0, -1) + 'ies' : null
|
|
89
|
+
const pluralEs = p.endsWith('s') || p.endsWith('x') || p.endsWith('z')
|
|
90
|
+
|| p.endsWith('ch') || p.endsWith('sh')
|
|
91
|
+
? p + 'es' : null
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
// 1. Correspondance directe
|
|
95
|
+
tableNames.find(t => lc(t) === p) ??
|
|
96
|
+
// 2. Synonyme explicite
|
|
97
|
+
tableNames.find(t => lc(t) === lc(this.synonyms[p] ?? '__none__')) ??
|
|
98
|
+
// 3. Pluriel régulier +s
|
|
99
|
+
tableNames.find(t => lc(t) === p + 's') ??
|
|
100
|
+
// 4. Pluriel en -ies (category → categories)
|
|
101
|
+
(pluralIes ? tableNames.find(t => lc(t) === pluralIes) : null) ??
|
|
102
|
+
// 5. Pluriel en -es (address → addresses)
|
|
103
|
+
(pluralEs ? tableNames.find(t => lc(t) === pluralEs) : null) ??
|
|
104
|
+
null
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Extrait le préfixe d'un nom de colonne FK.
|
|
110
|
+
* Gère les conventions camelCase et snake_case.
|
|
111
|
+
*
|
|
112
|
+
* Exemples :
|
|
113
|
+
* personId → person
|
|
114
|
+
* person_id → person
|
|
115
|
+
* movieId → movie
|
|
116
|
+
* manager_staff_id → manager_staff (FK complexe — peut ne pas résoudre)
|
|
117
|
+
*/
|
|
118
|
+
extractPrefix(columnName: string): string {
|
|
119
|
+
return columnName
|
|
120
|
+
.replace(/Id$/, '')
|
|
121
|
+
.replace(/_id$/i, '')
|
|
122
|
+
.toLowerCase()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Résout directement depuis un nom de colonne FK.
|
|
127
|
+
* Combine extractPrefix + resolve.
|
|
128
|
+
*/
|
|
129
|
+
resolveColumn(columnName: string, tableNames: string[]): string | null {
|
|
130
|
+
const prefix = this.extractPrefix(columnName)
|
|
131
|
+
return this.resolve(prefix, tableNames)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ─── Inspection ────────────────────────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
getSynonyms(): Record<string, string> {
|
|
137
|
+
return { ...this.synonyms }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
has(prefix: string): boolean {
|
|
141
|
+
return prefix.toLowerCase() in this.synonyms
|
|
142
|
+
}
|
|
143
|
+
}
|