@linklabjs/core 0.1.0 → 0.1.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/dist/api/DomainNode.d.ts +154 -0
- package/dist/api/DomainNode.d.ts.map +1 -0
- package/dist/api/DomainNode.js +1157 -0
- package/dist/api/DomainNode.js.map +1 -0
- package/dist/api/Graph.d.ts +117 -0
- package/dist/api/Graph.d.ts.map +1 -0
- package/dist/api/Graph.js +212 -0
- package/dist/api/Graph.js.map +1 -0
- package/dist/api/PathBuilder.d.ts +76 -0
- package/dist/api/PathBuilder.d.ts.map +1 -0
- package/dist/api/PathBuilder.js +182 -0
- package/dist/api/PathBuilder.js.map +1 -0
- package/dist/api/index.d.ts +8 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +7 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/loadGraph.d.ts +57 -0
- package/dist/api/loadGraph.d.ts.map +1 -0
- package/dist/api/loadGraph.js +153 -0
- package/dist/api/loadGraph.js.map +1 -0
- package/dist/api/test-api.d.ts +9 -0
- package/dist/api/test-api.d.ts.map +1 -0
- package/dist/api/test-api.js +133 -0
- package/dist/api/test-api.js.map +1 -0
- package/dist/api/test-domain.d.ts +13 -0
- package/dist/api/test-domain.d.ts.map +1 -0
- package/dist/api/test-domain.js +105 -0
- package/dist/api/test-domain.js.map +1 -0
- package/dist/api/types.d.ts +69 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +22 -0
- package/dist/api/types.js.map +1 -0
- package/dist/config/synonyms.json +25 -0
- package/dist/core/EventBus.d.ts +56 -0
- package/dist/core/EventBus.d.ts.map +1 -0
- package/dist/core/EventBus.js +147 -0
- package/dist/core/EventBus.js.map +1 -0
- package/dist/core/GraphEvents.d.ts +118 -0
- package/dist/core/GraphEvents.d.ts.map +1 -0
- package/dist/core/GraphEvents.js +23 -0
- package/dist/core/GraphEvents.js.map +1 -0
- package/dist/core/PathFinder.d.ts +43 -0
- package/dist/core/PathFinder.d.ts.map +1 -0
- package/dist/core/PathFinder.js +264 -0
- package/dist/core/PathFinder.js.map +1 -0
- package/dist/formatters/BaseFormatter.d.ts +15 -0
- package/dist/formatters/BaseFormatter.d.ts.map +1 -0
- package/dist/formatters/BaseFormatter.js +9 -0
- package/dist/formatters/BaseFormatter.js.map +1 -0
- package/dist/graph/GraphAssembler.d.ts +14 -0
- package/dist/graph/GraphAssembler.d.ts.map +1 -0
- package/dist/graph/GraphAssembler.js +44 -0
- package/dist/graph/GraphAssembler.js.map +1 -0
- package/dist/graph/GraphCompiler.d.ts +37 -0
- package/dist/graph/GraphCompiler.d.ts.map +1 -0
- package/dist/graph/GraphCompiler.js +355 -0
- package/dist/graph/GraphCompiler.js.map +1 -0
- package/dist/graph/GraphExtractor.d.ts +21 -0
- package/dist/graph/GraphExtractor.d.ts.map +1 -0
- package/dist/graph/GraphExtractor.js +145 -0
- package/dist/graph/GraphExtractor.js.map +1 -0
- package/dist/graph/GraphOptimizer.d.ts +104 -0
- package/dist/graph/GraphOptimizer.d.ts.map +1 -0
- package/dist/graph/GraphOptimizer.js +306 -0
- package/dist/graph/GraphOptimizer.js.map +1 -0
- package/dist/graph/GraphTrainer.d.ts +52 -0
- package/dist/graph/GraphTrainer.d.ts.map +1 -0
- package/dist/graph/GraphTrainer.js +188 -0
- package/dist/graph/GraphTrainer.js.map +1 -0
- package/dist/http/LinkBuilder.d.ts +82 -0
- package/dist/http/LinkBuilder.d.ts.map +1 -0
- package/dist/http/LinkBuilder.js +190 -0
- package/dist/http/LinkBuilder.js.map +1 -0
- package/dist/http/TrailRequest.d.ts +39 -0
- package/dist/http/TrailRequest.d.ts.map +1 -0
- package/dist/http/TrailRequest.js +22 -0
- package/dist/http/TrailRequest.js.map +1 -0
- package/dist/http/example-netflix.d.ts +6 -0
- package/dist/http/example-netflix.d.ts.map +1 -0
- package/dist/http/example-netflix.js +52 -0
- package/dist/http/example-netflix.js.map +1 -0
- package/dist/http/index.d.ts +32 -0
- package/dist/http/index.d.ts.map +1 -0
- package/dist/http/index.js +27 -0
- package/dist/http/index.js.map +1 -0
- package/dist/http/plugin.d.ts +110 -0
- package/dist/http/plugin.d.ts.map +1 -0
- package/dist/http/plugin.js +217 -0
- package/dist/http/plugin.js.map +1 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +71 -0
- package/dist/index.js.map +1 -0
- package/dist/instrumentation/TelemetryShim.d.ts +114 -0
- package/dist/instrumentation/TelemetryShim.d.ts.map +1 -0
- package/dist/instrumentation/TelemetryShim.js +107 -0
- package/dist/instrumentation/TelemetryShim.js.map +1 -0
- package/dist/navigation/NavigationEngine.d.ts +69 -0
- package/dist/navigation/NavigationEngine.d.ts.map +1 -0
- package/dist/navigation/NavigationEngine.js +361 -0
- package/dist/navigation/NavigationEngine.js.map +1 -0
- package/dist/navigation/Resolver.d.ts +35 -0
- package/dist/navigation/Resolver.d.ts.map +1 -0
- package/dist/navigation/Resolver.js +113 -0
- package/dist/navigation/Resolver.js.map +1 -0
- package/dist/navigation/Scheduler.d.ts +36 -0
- package/dist/navigation/Scheduler.d.ts.map +1 -0
- package/dist/navigation/Scheduler.js +107 -0
- package/dist/navigation/Scheduler.js.map +1 -0
- package/dist/navigation/Trail.d.ts +129 -0
- package/dist/navigation/Trail.d.ts.map +1 -0
- package/dist/navigation/Trail.js +202 -0
- package/dist/navigation/Trail.js.map +1 -0
- package/dist/navigation/TrailParser.d.ts +96 -0
- package/dist/navigation/TrailParser.d.ts.map +1 -0
- package/dist/navigation/TrailParser.js +180 -0
- package/dist/navigation/TrailParser.js.map +1 -0
- package/dist/navigation/index.d.ts +10 -0
- package/dist/navigation/index.d.ts.map +1 -0
- package/dist/navigation/index.js +9 -0
- package/dist/navigation/index.js.map +1 -0
- package/dist/providers/MockProvider.d.ts +29 -0
- package/dist/providers/MockProvider.d.ts.map +1 -0
- package/dist/providers/MockProvider.js +55 -0
- package/dist/providers/MockProvider.js.map +1 -0
- package/dist/providers/PostgresProvider.d.ts +46 -0
- package/dist/providers/PostgresProvider.d.ts.map +1 -0
- package/dist/providers/PostgresProvider.js +152 -0
- package/dist/providers/PostgresProvider.js.map +1 -0
- package/dist/runtime/CompiledGraphEngine.d.ts +74 -0
- package/dist/runtime/CompiledGraphEngine.d.ts.map +1 -0
- package/dist/runtime/CompiledGraphEngine.js +211 -0
- package/dist/runtime/CompiledGraphEngine.js.map +1 -0
- package/dist/runtime/DataLoader.d.ts +90 -0
- package/dist/runtime/DataLoader.d.ts.map +1 -0
- package/dist/runtime/DataLoader.js +178 -0
- package/dist/runtime/DataLoader.js.map +1 -0
- package/dist/runtime/Engine.d.ts +36 -0
- package/dist/runtime/Engine.d.ts.map +1 -0
- package/dist/runtime/Engine.js +128 -0
- package/dist/runtime/Engine.js.map +1 -0
- package/dist/runtime/QueryEngine.d.ts +80 -0
- package/dist/runtime/QueryEngine.d.ts.map +1 -0
- package/dist/runtime/QueryEngine.js +188 -0
- package/dist/runtime/QueryEngine.js.map +1 -0
- package/dist/scenarios/test-metro-paris/config.json +6 -0
- package/dist/scenarios/test-metro-paris/graph.json +16325 -0
- package/dist/scenarios/test-metro-paris/queries.d.ts +22 -0
- package/dist/scenarios/test-metro-paris/queries.d.ts.map +1 -0
- package/dist/scenarios/test-metro-paris/queries.js +128 -0
- package/dist/scenarios/test-metro-paris/queries.js.map +1 -0
- package/dist/scenarios/test-metro-paris/stack.json +1 -0
- package/dist/scenarios/test-musicians/config.json +10 -0
- package/dist/scenarios/test-musicians/graph.json +20 -0
- package/dist/scenarios/test-musicians/stack.json +1 -0
- package/dist/scenarios/test-netflix/actions.d.ts +14 -0
- package/dist/scenarios/test-netflix/actions.d.ts.map +1 -0
- package/dist/scenarios/test-netflix/actions.js +86 -0
- package/dist/scenarios/test-netflix/actions.js.map +1 -0
- package/dist/scenarios/test-netflix/config.json +6 -0
- package/dist/scenarios/test-netflix/data/categories.json +1 -0
- package/dist/scenarios/test-netflix/data/companies.json +1 -0
- package/dist/scenarios/test-netflix/data/credits.json +19797 -0
- package/dist/scenarios/test-netflix/data/departments.json +18 -0
- package/dist/scenarios/test-netflix/data/jobs.json +142 -0
- package/dist/scenarios/test-netflix/data/movies.json +3497 -0
- package/dist/scenarios/test-netflix/data/people.json +1 -0
- package/dist/scenarios/test-netflix/data/synonyms.json +7 -0
- package/dist/scenarios/test-netflix/data/users.json +70 -0
- package/dist/scenarios/test-netflix/graph.json +1017 -0
- package/dist/scenarios/test-netflix/queries.d.ts +29 -0
- package/dist/scenarios/test-netflix/queries.d.ts.map +1 -0
- package/dist/scenarios/test-netflix/queries.js +134 -0
- package/dist/scenarios/test-netflix/queries.js.map +1 -0
- package/dist/scenarios/test-netflix/stack.json +14 -0
- package/dist/schema/GraphBuilder.d.ts +9 -0
- package/dist/schema/GraphBuilder.d.ts.map +1 -0
- package/dist/schema/GraphBuilder.js +90 -0
- package/dist/schema/GraphBuilder.js.map +1 -0
- package/dist/schema/JsonSchemaExtractor.d.ts +21 -0
- package/dist/schema/JsonSchemaExtractor.d.ts.map +1 -0
- package/dist/schema/JsonSchemaExtractor.js +88 -0
- package/dist/schema/JsonSchemaExtractor.js.map +1 -0
- package/dist/schema/SchemaAnalyzer.d.ts +41 -0
- package/dist/schema/SchemaAnalyzer.d.ts.map +1 -0
- package/dist/schema/SchemaAnalyzer.js +144 -0
- package/dist/schema/SchemaAnalyzer.js.map +1 -0
- package/dist/schema/SchemaExtractor.d.ts +10 -0
- package/dist/schema/SchemaExtractor.d.ts.map +1 -0
- package/dist/schema/SchemaExtractor.js +90 -0
- package/dist/schema/SchemaExtractor.js.map +1 -0
- package/dist/schema/SynonymResolver.d.ts +55 -0
- package/dist/schema/SynonymResolver.d.ts.map +1 -0
- package/dist/schema/SynonymResolver.js +121 -0
- package/dist/schema/SynonymResolver.js.map +1 -0
- package/dist/scripts/dictionary.json +796 -0
- package/dist/scripts/graph.json +664 -0
- package/dist/scripts/regenerate.d.ts +23 -0
- package/dist/scripts/regenerate.d.ts.map +1 -0
- package/dist/scripts/regenerate.js +206 -0
- package/dist/scripts/regenerate.js.map +1 -0
- package/dist/types/index.d.ts +394 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +21 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LinkBuilder — Génère les liens HATEOAS depuis le graphe
|
|
3
|
+
*
|
|
4
|
+
* Logique pure, sans dépendance à Fastify.
|
|
5
|
+
* Prend un Trail + un Graph, retourne des liens navigables.
|
|
6
|
+
*
|
|
7
|
+
* Trois catégories de liens générés automatiquement :
|
|
8
|
+
*
|
|
9
|
+
* self — l'URL courante (Trail sérialisé)
|
|
10
|
+
* up — le parent (Trail sans la dernière frame)
|
|
11
|
+
* relations — toutes les arêtes sortantes du nœud courant
|
|
12
|
+
*
|
|
13
|
+
* Les liens émergent du graphe — le dev ne configure rien.
|
|
14
|
+
*/
|
|
15
|
+
import { TrailParser } from '../navigation/TrailParser.js';
|
|
16
|
+
// ── LinkBuilder ───────────────────────────────────────────────
|
|
17
|
+
export class LinkBuilder {
|
|
18
|
+
graph;
|
|
19
|
+
options;
|
|
20
|
+
constructor(graph, options = {}) {
|
|
21
|
+
this.graph = graph;
|
|
22
|
+
this.options = {
|
|
23
|
+
prefix: options.prefix ?? '',
|
|
24
|
+
includeReverse: options.includeReverse ?? false,
|
|
25
|
+
exclude: options.exclude ?? [],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Génère les liens HATEOAS pour un Trail donné.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* const builder = new LinkBuilder(graph, { prefix: '/api' })
|
|
34
|
+
* const links = builder.from(trail)
|
|
35
|
+
* // {
|
|
36
|
+
* // self: { href: '/api/people/Nolan/movies' },
|
|
37
|
+
* // up: { href: '/api/people/Nolan' },
|
|
38
|
+
* // actors: { href: '/api/people/Nolan/movies/{id}/actors', templated: true },
|
|
39
|
+
* // ratings: { href: '/api/people/Nolan/movies/{id}/ratings', templated: true }
|
|
40
|
+
* // }
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
from(trail) {
|
|
44
|
+
const currentPath = this.prefix(TrailParser.toPath(trail));
|
|
45
|
+
const links = {
|
|
46
|
+
self: {
|
|
47
|
+
href: currentPath,
|
|
48
|
+
method: 'GET',
|
|
49
|
+
rel: 'self',
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
// ── Frame courante ────────────────────────────────────────
|
|
53
|
+
const current = trail.current;
|
|
54
|
+
// ── Lien "up" — parent dans le Trail ou collection ─────────
|
|
55
|
+
// depth = 1 avec id : up vers la collection (ex: /movies/278 → up: /movies)
|
|
56
|
+
// depth > 1 : up vers l'entité parente (ex: /movies/278/people → up: /movies/278)
|
|
57
|
+
if (trail.depth === 1 && current?.id !== undefined) {
|
|
58
|
+
links.up = {
|
|
59
|
+
href: this.prefix('/' + current.entity),
|
|
60
|
+
method: 'GET',
|
|
61
|
+
rel: 'up',
|
|
62
|
+
title: `Collection ${current.entity}`,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
else if (trail.depth > 1) {
|
|
66
|
+
const parentTrail = trail.slice(trail.depth - 1);
|
|
67
|
+
links.up = {
|
|
68
|
+
href: this.prefix(TrailParser.toPath(parentTrail)),
|
|
69
|
+
method: 'GET',
|
|
70
|
+
rel: 'up',
|
|
71
|
+
title: `Retour vers ${parentTrail.current?.entity}`,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
// ── Liens sortants — arêtes depuis le nœud courant ────────
|
|
75
|
+
if (!current)
|
|
76
|
+
return links;
|
|
77
|
+
const outgoing = this.getOutgoingEdges(current.entity);
|
|
78
|
+
// Grouper les arêtes par entité cible (.to)
|
|
79
|
+
// Plusieurs arêtes peuvent pointer vers la même entité (ex: actor, director, writer → people)
|
|
80
|
+
// On génère un seul lien par entité cible, avec l'arête de poids minimal
|
|
81
|
+
const byTarget = new Map();
|
|
82
|
+
for (const edge of outgoing) {
|
|
83
|
+
// Ignorer les relations exclues
|
|
84
|
+
if (this.options.exclude.includes(edge.name ?? ''))
|
|
85
|
+
continue;
|
|
86
|
+
if (this.options.exclude.includes(edge.to))
|
|
87
|
+
continue;
|
|
88
|
+
const edgeLabel = edge.label ?? edge.name ?? '';
|
|
89
|
+
// Préférer une edge avec un label significatif (pas 'unknow', pas vide)
|
|
90
|
+
const isSignificant = edgeLabel && edgeLabel !== 'unknow';
|
|
91
|
+
const existing = byTarget.get(edge.to);
|
|
92
|
+
if (!existing) {
|
|
93
|
+
byTarget.set(edge.to, edge);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
const existingLabel = existing.label ?? existing.name ?? '';
|
|
97
|
+
const existingSignificant = existingLabel && existingLabel !== 'unknow';
|
|
98
|
+
// Remplacer si : l'actuelle est insignifiante ET la nouvelle est significative,
|
|
99
|
+
// ou les deux sont significatives et la nouvelle est moins lourde
|
|
100
|
+
if ((!existingSignificant && isSignificant) ||
|
|
101
|
+
(existingSignificant && isSignificant && edge.weight < existing.weight)) {
|
|
102
|
+
byTarget.set(edge.to, edge);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
for (const [targetEntity, edge] of byTarget) {
|
|
107
|
+
// L'URL utilise l'entité cible comme segment — pas le nom de l'arête
|
|
108
|
+
// /api/movies/278/people (pas /api/movies/278/actor)
|
|
109
|
+
const href = current.id !== undefined
|
|
110
|
+
? this.prefix(TrailParser.toPath(trail) + '/' + targetEntity)
|
|
111
|
+
: this.prefix(TrailParser.toPath(trail) + '/{id}/' + targetEntity);
|
|
112
|
+
const templated = current.id === undefined || href.includes('{id}');
|
|
113
|
+
links[targetEntity] = {
|
|
114
|
+
href,
|
|
115
|
+
method: 'GET',
|
|
116
|
+
templated: templated || undefined,
|
|
117
|
+
rel: targetEntity,
|
|
118
|
+
title: this.buildTitle(trail, edge),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return links;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Génère les liens pour une collection de résultats.
|
|
125
|
+
* Chaque item reçoit ses propres liens self + relations.
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```typescript
|
|
129
|
+
* // GET /people/Nolan/movies → liste de films
|
|
130
|
+
* const itemLinks = builder.forItems(trail, movies, 'id')
|
|
131
|
+
* // itemLinks[0] = { self: { href: '/people/Nolan/movies/1' }, actors: {...} }
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
forItems(trail, items, idField = 'id') {
|
|
135
|
+
return items.map(item => {
|
|
136
|
+
const id = item[idField];
|
|
137
|
+
if (id === undefined)
|
|
138
|
+
return this.from(trail);
|
|
139
|
+
// Construire un Trail avec l'id de l'item
|
|
140
|
+
const itemTrail = trail.clone();
|
|
141
|
+
const last = itemTrail.current;
|
|
142
|
+
if (last) {
|
|
143
|
+
// Remplacer la dernière frame avec l'id
|
|
144
|
+
itemTrail.pop();
|
|
145
|
+
itemTrail.push({ ...last, id, state: 'RESOLVED' });
|
|
146
|
+
}
|
|
147
|
+
return this.from(itemTrail);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Vérifie si une relation existe depuis un nœud donné.
|
|
152
|
+
* Utile pour les hooks d'access.check.
|
|
153
|
+
*/
|
|
154
|
+
hasRelation(fromEntity, relation) {
|
|
155
|
+
return this.graph.edges.some(e => e.from === fromEntity && (e.name === relation || e.to === relation));
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Retourne toutes les entités accessibles depuis un nœud.
|
|
159
|
+
*/
|
|
160
|
+
reachableFrom(entity) {
|
|
161
|
+
return this.getOutgoingEdges(entity).map(e => e.name ?? e.to);
|
|
162
|
+
}
|
|
163
|
+
// ── Privé ──────────────────────────────────────────────────
|
|
164
|
+
getOutgoingEdges(entity) {
|
|
165
|
+
return this.graph.edges
|
|
166
|
+
.filter(e => e.from === entity)
|
|
167
|
+
.sort((a, b) => b.weight - a.weight); // les plus utilisées en premier
|
|
168
|
+
}
|
|
169
|
+
prefix(path) {
|
|
170
|
+
if (!this.options.prefix)
|
|
171
|
+
return path;
|
|
172
|
+
return this.options.prefix.replace(/\/$/, '') + path;
|
|
173
|
+
}
|
|
174
|
+
buildTitle(trail, edge) {
|
|
175
|
+
const current = trail.current;
|
|
176
|
+
// Résoudre un label lisible pour l'edge :
|
|
177
|
+
// 1. edge.name explicite et non générique (ex: 'LIST_OF_CREDITS', 'actor')
|
|
178
|
+
// 2. edge.to comme fallback neutre (ex: 'people', 'movies')
|
|
179
|
+
const rawName = edge.name ?? '';
|
|
180
|
+
const isGeneric = !rawName || rawName === 'unknow' || rawName === 'unknow_in';
|
|
181
|
+
const edgeLabel = isGeneric ? edge.to : rawName;
|
|
182
|
+
if (!current)
|
|
183
|
+
return edgeLabel;
|
|
184
|
+
const from = current.id !== undefined
|
|
185
|
+
? `${current.entity}(${current.id})`
|
|
186
|
+
: current.entity;
|
|
187
|
+
return `${edgeLabel} de ${from}`;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=LinkBuilder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LinkBuilder.js","sourceRoot":"","sources":["../../src/http/LinkBuilder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TrailRequest — Augmentation du type Request Fastify
|
|
3
|
+
*
|
|
4
|
+
* Ajoute `request.trail` et `request.linkBuilder`
|
|
5
|
+
* sur chaque requête décorée par le plugin LinkLab.
|
|
6
|
+
*
|
|
7
|
+
* Usage :
|
|
8
|
+
* ```typescript
|
|
9
|
+
* fastify.get('/*', async (req, reply) => {
|
|
10
|
+
* const trail = req.trail // Trail parsé depuis l'URL
|
|
11
|
+
* const links = req.linkBuilder // LinkBuilder prêt à l'emploi
|
|
12
|
+
* })
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
import type { FastifyRequest } from 'fastify';
|
|
16
|
+
import type { Trail } from '../navigation/Trail.js';
|
|
17
|
+
import type { LinkBuilder } from './LinkBuilder.js';
|
|
18
|
+
/**
|
|
19
|
+
* Déclaration d'augmentation du module Fastify.
|
|
20
|
+
* TypeScript merge automatiquement avec FastifyRequest.
|
|
21
|
+
*/
|
|
22
|
+
declare module 'fastify' {
|
|
23
|
+
interface FastifyRequest {
|
|
24
|
+
/** Trail parsé depuis l'URL de la requête */
|
|
25
|
+
trail: Trail;
|
|
26
|
+
/** LinkBuilder configuré avec le graphe de l'instance */
|
|
27
|
+
linkBuilder: LinkBuilder;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Extrait le contexte utilisateur depuis une requête Fastify.
|
|
32
|
+
* Extensible par le dev via les options du plugin.
|
|
33
|
+
*/
|
|
34
|
+
export type UserContextExtractor = (req: FastifyRequest) => Promise<Record<string, any>> | Record<string, any>;
|
|
35
|
+
/**
|
|
36
|
+
* Extracteur par défaut — lit req.user si présent (JWT/session)
|
|
37
|
+
*/
|
|
38
|
+
export declare const defaultUserExtractor: UserContextExtractor;
|
|
39
|
+
//# sourceMappingURL=TrailRequest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TrailRequest.d.ts","sourceRoot":"","sources":["../../src/http/TrailRequest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAC7C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAe,wBAAwB,CAAA;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAS,kBAAkB,CAAA;AAEtD;;;GAGG;AACH,OAAO,QAAQ,SAAS,CAAC;IACvB,UAAU,cAAc;QACtB,6CAA6C;QAC7C,KAAK,EAAE,KAAK,CAAA;QAEZ,yDAAyD;QACzD,WAAW,EAAE,WAAW,CAAA;KACzB;CACF;AAED;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAAG,CACjC,GAAG,EAAE,cAAc,KAChB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;AAEvD;;GAEG;AACH,eAAO,MAAM,oBAAoB,EAAE,oBAGlC,CAAA"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TrailRequest — Augmentation du type Request Fastify
|
|
3
|
+
*
|
|
4
|
+
* Ajoute `request.trail` et `request.linkBuilder`
|
|
5
|
+
* sur chaque requête décorée par le plugin LinkLab.
|
|
6
|
+
*
|
|
7
|
+
* Usage :
|
|
8
|
+
* ```typescript
|
|
9
|
+
* fastify.get('/*', async (req, reply) => {
|
|
10
|
+
* const trail = req.trail // Trail parsé depuis l'URL
|
|
11
|
+
* const links = req.linkBuilder // LinkBuilder prêt à l'emploi
|
|
12
|
+
* })
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Extracteur par défaut — lit req.user si présent (JWT/session)
|
|
17
|
+
*/
|
|
18
|
+
export const defaultUserExtractor = (req) => {
|
|
19
|
+
const r = req;
|
|
20
|
+
return r.user ?? r.session?.user ?? {};
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=TrailRequest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TrailRequest.js","sourceRoot":"","sources":["../../src/http/TrailRequest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA4BH;;GAEG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAyB,CAAC,GAAG,EAAE,EAAE;IAChE,MAAM,CAAC,GAAG,GAAU,CAAA;IACpB,OAAO,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,OAAO,EAAE,IAAI,IAAI,EAAE,CAAA;AACxC,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"example-netflix.d.ts","sourceRoot":"","sources":["../../src/http/example-netflix.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exemple d'intégration Netflix
|
|
3
|
+
* Fichier à titre d'exemple uniquement, non exporté.
|
|
4
|
+
*/
|
|
5
|
+
import { createRequire } from 'module';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import Fastify from 'fastify';
|
|
9
|
+
import { linklabPlugin } from './index.js';
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const dataDir = path.join(__dirname, '../examples/netflix/data');
|
|
13
|
+
const compiledGraph = require('../examples/netflix/compiled-graph.json');
|
|
14
|
+
const movies = require(path.join(dataDir, 'movies.json'));
|
|
15
|
+
const people = require(path.join(dataDir, 'people.json'));
|
|
16
|
+
const credits = require(path.join(dataDir, 'credits.json'));
|
|
17
|
+
const categories = require(path.join(dataDir, 'categories.json'));
|
|
18
|
+
const departments = require(path.join(dataDir, 'departments.json'));
|
|
19
|
+
const jobs = require(path.join(dataDir, 'jobs.json'));
|
|
20
|
+
const fastify = Fastify({ logger: true });
|
|
21
|
+
await fastify.register(linklabPlugin, {
|
|
22
|
+
graph: compiledGraph,
|
|
23
|
+
prefix: '/api',
|
|
24
|
+
global: { domain: 'netflix', version: 'v1' },
|
|
25
|
+
dataLoader: {
|
|
26
|
+
dataset: { movies, people, credits, categories, departments, jobs }
|
|
27
|
+
},
|
|
28
|
+
extractUser: async (req) => {
|
|
29
|
+
const auth = req.headers.authorization;
|
|
30
|
+
if (!auth)
|
|
31
|
+
return {};
|
|
32
|
+
return { userId: 'u_123', subscription: 'premium', locale: 'fr-FR' };
|
|
33
|
+
},
|
|
34
|
+
onEngine: (engine, req) => {
|
|
35
|
+
const accessHandler = async (ctx) => {
|
|
36
|
+
const protected_ = ['movies', 'series', 'episodes'];
|
|
37
|
+
if (protected_.includes(ctx.node) && !ctx.trail?.user?.subscription) {
|
|
38
|
+
return { cancelled: true, reason: 'subscription_required' };
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
};
|
|
42
|
+
engine.hooks.on('access.check', accessHandler);
|
|
43
|
+
engine.events.on('traversal.complete', ({ routeUsed, durationMs }) => {
|
|
44
|
+
fastify.log.info({ routeUsed, durationMs }, 'traversal');
|
|
45
|
+
});
|
|
46
|
+
engine.errors.on('route.notfound', ({ from, to }) => {
|
|
47
|
+
fastify.log.warn({ from, to }, 'route not found');
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
await fastify.listen({ port: 3000 });
|
|
52
|
+
//# sourceMappingURL=example-netflix.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"example-netflix.js","sourceRoot":"","sources":["../../src/http/example-netflix.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* http — Exports du module HTTP LinkLab
|
|
3
|
+
*
|
|
4
|
+
* Point d'entrée unique pour le plugin Fastify et
|
|
5
|
+
* les utilitaires HTTP associés.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { linklabPlugin, LinkBuilder, TrailParser } from '@linklab/http'
|
|
10
|
+
*
|
|
11
|
+
* await fastify.register(linklabPlugin, {
|
|
12
|
+
* graph: compiledGraph,
|
|
13
|
+
* prefix: '/api',
|
|
14
|
+
* onEngine: (engine, req) => {
|
|
15
|
+
* engine.hooks.on('access.check', async (ctx) => {
|
|
16
|
+
* if (!ctx.trail.user.userId) {
|
|
17
|
+
* return { cancelled: true, reason: 'unauthenticated' }
|
|
18
|
+
* }
|
|
19
|
+
* })
|
|
20
|
+
* }
|
|
21
|
+
* })
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export { linklabPlugin } from './plugin.js';
|
|
25
|
+
export { LinkBuilder } from './LinkBuilder.js';
|
|
26
|
+
export { DataLoader } from '../runtime/DataLoader.js';
|
|
27
|
+
export type { LinklabPluginOptions } from './plugin.js';
|
|
28
|
+
export type { TrailResponse, ResponseMeta } from './plugin.js';
|
|
29
|
+
export type { HateoasLink, HateoasLinks, LinkBuilderOptions } from './LinkBuilder.js';
|
|
30
|
+
export type { UserContextExtractor } from './TrailRequest.js';
|
|
31
|
+
export type { DataLoaderOptions } from '../runtime/DataLoader.js';
|
|
32
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/http/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAgB,aAAa,CAAA;AACrD,OAAO,EAAE,WAAW,EAAE,MAAkB,kBAAkB,CAAA;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAmB,0BAA0B,CAAA;AAElE,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AACvD,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC9D,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACrF,YAAY,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAC7D,YAAY,EAAE,iBAAiB,EAAE,MAAO,0BAA0B,CAAA"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* http — Exports du module HTTP LinkLab
|
|
3
|
+
*
|
|
4
|
+
* Point d'entrée unique pour le plugin Fastify et
|
|
5
|
+
* les utilitaires HTTP associés.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { linklabPlugin, LinkBuilder, TrailParser } from '@linklab/http'
|
|
10
|
+
*
|
|
11
|
+
* await fastify.register(linklabPlugin, {
|
|
12
|
+
* graph: compiledGraph,
|
|
13
|
+
* prefix: '/api',
|
|
14
|
+
* onEngine: (engine, req) => {
|
|
15
|
+
* engine.hooks.on('access.check', async (ctx) => {
|
|
16
|
+
* if (!ctx.trail.user.userId) {
|
|
17
|
+
* return { cancelled: true, reason: 'unauthenticated' }
|
|
18
|
+
* }
|
|
19
|
+
* })
|
|
20
|
+
* }
|
|
21
|
+
* })
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export { linklabPlugin } from './plugin.js';
|
|
25
|
+
export { LinkBuilder } from './LinkBuilder.js';
|
|
26
|
+
export { DataLoader } from '../runtime/DataLoader.js';
|
|
27
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/http/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAgB,aAAa,CAAA;AACrD,OAAO,EAAE,WAAW,EAAE,MAAkB,kBAAkB,CAAA;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAmB,0BAA0B,CAAA"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LinkLab Fastify Plugin
|
|
3
|
+
*
|
|
4
|
+
* Transforme automatiquement chaque requête HTTP en Trail,
|
|
5
|
+
* résout la navigation via le graphe, et retourne une réponse
|
|
6
|
+
* HATEOAS Level 3 — liens générés automatiquement depuis le graphe.
|
|
7
|
+
*
|
|
8
|
+
* Usage minimal :
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import Fastify from 'fastify'
|
|
11
|
+
* import { linklabPlugin } from '@linklab/http'
|
|
12
|
+
*
|
|
13
|
+
* const fastify = Fastify()
|
|
14
|
+
* await fastify.register(linklabPlugin, {
|
|
15
|
+
* graph: compiledGraph,
|
|
16
|
+
* prefix: '/api'
|
|
17
|
+
* })
|
|
18
|
+
* await fastify.listen({ port: 3000 })
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* Toutes ces routes fonctionnent sans configuration supplémentaire :
|
|
22
|
+
* GET /api/people
|
|
23
|
+
* GET /api/people/Nolan
|
|
24
|
+
* GET /api/people/Nolan/movies
|
|
25
|
+
* GET /api/people/Nolan/movies/1/actors
|
|
26
|
+
*
|
|
27
|
+
* Hooks disponibles sur chaque requête :
|
|
28
|
+
* ```typescript
|
|
29
|
+
* fastify.register(linklabPlugin, {
|
|
30
|
+
* graph,
|
|
31
|
+
* onEngine: (engine, req) => {
|
|
32
|
+
* engine.hooks.on('access.check', async (ctx) => {
|
|
33
|
+
* if (!ctx.trail.user.userId) {
|
|
34
|
+
* return { cancelled: true, reason: 'unauthenticated' }
|
|
35
|
+
* }
|
|
36
|
+
* })
|
|
37
|
+
* }
|
|
38
|
+
* })
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
import type { FastifyPluginAsync, FastifyRequest } from 'fastify';
|
|
42
|
+
import type { Graph, CompiledGraph } from '../types/index.js';
|
|
43
|
+
import { Trail } from '../navigation/Trail.js';
|
|
44
|
+
import { NavigationEngine } from '../navigation/NavigationEngine.js';
|
|
45
|
+
import { type DataLoaderOptions } from '../runtime/DataLoader.js';
|
|
46
|
+
import { type UserContextExtractor } from './TrailRequest.js';
|
|
47
|
+
export interface LinklabPluginOptions {
|
|
48
|
+
/** Le graphe sémantique — navigation, LinkBuilder, Resolver */
|
|
49
|
+
graph: Graph;
|
|
50
|
+
/** Le graphe compilé — routes SQL optimales pour DataLoader/QueryEngine */
|
|
51
|
+
compiledGraph?: CompiledGraph;
|
|
52
|
+
/** Préfixe URL — ex: '/api' ou '/api/v1' */
|
|
53
|
+
prefix?: string;
|
|
54
|
+
/** Contexte global injecté dans chaque Trail */
|
|
55
|
+
global?: Record<string, any>;
|
|
56
|
+
/**
|
|
57
|
+
* Extracteur de contexte utilisateur.
|
|
58
|
+
* Par défaut : lit req.user ou req.session.user
|
|
59
|
+
*/
|
|
60
|
+
extractUser?: UserContextExtractor;
|
|
61
|
+
/**
|
|
62
|
+
* Hook appelé après création du moteur, avant résolution.
|
|
63
|
+
* C'est ici que Netflix branche sa logique métier.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```typescript
|
|
67
|
+
* onEngine: (engine, req) => {
|
|
68
|
+
* engine.hooks.on('access.check', async (ctx) => {
|
|
69
|
+
* if (!ctx.trail.user.subscription) {
|
|
70
|
+
* return { cancelled: true, reason: 'subscription_required' }
|
|
71
|
+
* }
|
|
72
|
+
* })
|
|
73
|
+
* }
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
onEngine?: (engine: NavigationEngine, req: FastifyRequest) => void | Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* Options du DataLoader — source de données réelles.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```typescript
|
|
82
|
+
* // Mode JSON (Netflix mock / tests)
|
|
83
|
+
* dataLoader: { dataset: { movies, people, credits } }
|
|
84
|
+
*
|
|
85
|
+
* // Mode SQL (PostgreSQL)
|
|
86
|
+
* dataLoader: { provider: postgresProvider }
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
dataLoader?: DataLoaderOptions;
|
|
90
|
+
/**
|
|
91
|
+
* Transforme les données avant envoi.
|
|
92
|
+
* Utile pour la pagination, la sérialisation custom, etc.
|
|
93
|
+
*/
|
|
94
|
+
transformData?: (data: any, trail: Trail) => any;
|
|
95
|
+
}
|
|
96
|
+
export interface TrailResponse {
|
|
97
|
+
data: any;
|
|
98
|
+
_links: Record<string, any>;
|
|
99
|
+
_trail: string;
|
|
100
|
+
_meta: ResponseMeta;
|
|
101
|
+
}
|
|
102
|
+
export interface ResponseMeta {
|
|
103
|
+
entity: string;
|
|
104
|
+
depth: number;
|
|
105
|
+
resolved: number;
|
|
106
|
+
timing: number;
|
|
107
|
+
count?: number;
|
|
108
|
+
}
|
|
109
|
+
export declare const linklabPlugin: FastifyPluginAsync<LinklabPluginOptions>;
|
|
110
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/http/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAClB,cAAc,EAEf,MAAM,SAAS,CAAA;AAGhB,OAAO,KAAK,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAC7D,OAAO,EAAE,KAAK,EAAE,MAAuB,wBAAwB,CAAA;AAE/D,OAAO,EAAE,gBAAgB,EAAE,MAAY,mCAAmC,CAAA;AAE1E,OAAO,EAAc,KAAK,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAC7E,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,mBAAmB,CAAA;AAI1B,MAAM,WAAW,oBAAoB;IACnC,+DAA+D;IAC/D,KAAK,EAAE,KAAK,CAAA;IAEZ,2EAA2E;IAC3E,aAAa,CAAC,EAAE,aAAa,CAAA;IAE7B,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf,gDAAgD;IAChD,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAE5B;;;OAGG;IACH,WAAW,CAAC,EAAE,oBAAoB,CAAA;IAElC;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,gBAAgB,EAAE,GAAG,EAAE,cAAc,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAElF;;;;;;;;;;;OAWG;IACH,UAAU,CAAC,EAAE,iBAAiB,CAAA;IAE9B;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,KAAK,GAAG,CAAA;CACjD;AAID,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAK,GAAG,CAAA;IACZ,MAAM,EAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC5B,MAAM,EAAG,MAAM,CAAA;IACf,KAAK,EAAI,YAAY,CAAA;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAI,MAAM,CAAA;IAChB,KAAK,EAAK,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAI,MAAM,CAAA;IAChB,KAAK,CAAC,EAAI,MAAM,CAAA;CACjB;AA+ND,eAAO,MAAM,aAAa,0CAGxB,CAAA"}
|