@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.
Files changed (72) hide show
  1. package/README.md +411 -0
  2. package/package.json +48 -0
  3. package/src/api/DomainNode.ts +1433 -0
  4. package/src/api/Graph.ts +271 -0
  5. package/src/api/PathBuilder.ts +247 -0
  6. package/src/api/index.ts +15 -0
  7. package/src/api/loadGraph.ts +207 -0
  8. package/src/api/test-api.ts +153 -0
  9. package/src/api/test-domain.ts +119 -0
  10. package/src/api/types.ts +88 -0
  11. package/src/config/synonyms.json +28 -0
  12. package/src/core/EventBus.ts +187 -0
  13. package/src/core/GraphEvents.ts +153 -0
  14. package/src/core/PathFinder.ts +283 -0
  15. package/src/formatters/BaseFormatter.ts +17 -0
  16. package/src/graph/GraphAssembler.ts +50 -0
  17. package/src/graph/GraphCompiler.ts +412 -0
  18. package/src/graph/GraphExtractor.ts +191 -0
  19. package/src/graph/GraphOptimizer.ts +404 -0
  20. package/src/graph/GraphTrainer.ts +247 -0
  21. package/src/http/LinkBuilder.ts +244 -0
  22. package/src/http/TrailRequest.ts +48 -0
  23. package/src/http/example-netflix.ts +59 -0
  24. package/src/http/hateoas/README.md +87 -0
  25. package/src/http/index.ts +33 -0
  26. package/src/http/plugin.ts +360 -0
  27. package/src/index.ts +121 -0
  28. package/src/instrumentation/TelemetryShim.ts +172 -0
  29. package/src/navigation/NavigationEngine.ts +441 -0
  30. package/src/navigation/Resolver.ts +134 -0
  31. package/src/navigation/Scheduler.ts +136 -0
  32. package/src/navigation/Trail.ts +252 -0
  33. package/src/navigation/TrailParser.ts +207 -0
  34. package/src/navigation/index.ts +11 -0
  35. package/src/providers/MockProvider.ts +68 -0
  36. package/src/providers/PostgresProvider.ts +187 -0
  37. package/src/runtime/CompiledGraphEngine.ts +274 -0
  38. package/src/runtime/DataLoader.ts +236 -0
  39. package/src/runtime/Engine.ts +163 -0
  40. package/src/runtime/QueryEngine.ts +222 -0
  41. package/src/scenarios/test-metro-paris/config.json +6 -0
  42. package/src/scenarios/test-metro-paris/graph.json +16325 -0
  43. package/src/scenarios/test-metro-paris/queries.ts +152 -0
  44. package/src/scenarios/test-metro-paris/stack.json +1 -0
  45. package/src/scenarios/test-musicians/config.json +10 -0
  46. package/src/scenarios/test-musicians/graph.json +20 -0
  47. package/src/scenarios/test-musicians/stack.json +1 -0
  48. package/src/scenarios/test-netflix/MIGRATION.md +23 -0
  49. package/src/scenarios/test-netflix/README.md +138 -0
  50. package/src/scenarios/test-netflix/actions.ts +92 -0
  51. package/src/scenarios/test-netflix/config.json +6 -0
  52. package/src/scenarios/test-netflix/data/categories.json +1 -0
  53. package/src/scenarios/test-netflix/data/companies.json +1 -0
  54. package/src/scenarios/test-netflix/data/credits.json +19797 -0
  55. package/src/scenarios/test-netflix/data/departments.json +18 -0
  56. package/src/scenarios/test-netflix/data/jobs.json +142 -0
  57. package/src/scenarios/test-netflix/data/movies.json +3497 -0
  58. package/src/scenarios/test-netflix/data/people.json +1 -0
  59. package/src/scenarios/test-netflix/data/synonyms.json +8 -0
  60. package/src/scenarios/test-netflix/data/users.json +70 -0
  61. package/src/scenarios/test-netflix/graph.json +1017 -0
  62. package/src/scenarios/test-netflix/queries.ts +159 -0
  63. package/src/scenarios/test-netflix/stack.json +14 -0
  64. package/src/schema/GraphBuilder.ts +106 -0
  65. package/src/schema/JsonSchemaExtractor.ts +107 -0
  66. package/src/schema/SchemaAnalyzer.ts +175 -0
  67. package/src/schema/SchemaExtractor.ts +102 -0
  68. package/src/schema/SynonymResolver.ts +143 -0
  69. package/src/scripts/dictionary.json +796 -0
  70. package/src/scripts/graph.json +664 -0
  71. package/src/scripts/regenerate.ts +248 -0
  72. 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
+ }