@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
package/README.md
ADDED
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
# @linklabjs/core
|
|
2
|
+
|
|
3
|
+
> **The graph is the map. The Trail is the traveler.**
|
|
4
|
+
|
|
5
|
+
> "The Trail defines the path, the history, and the intention.
|
|
6
|
+
> The graph knows the possibilities."
|
|
7
|
+
|
|
8
|
+
LinkLab associates two concepts:
|
|
9
|
+
|
|
10
|
+
- **The compiled graph** — the map: entities, relations, optimal routes
|
|
11
|
+
- **The Trail** — the traveler: navigation, context, history, intention
|
|
12
|
+
|
|
13
|
+
The map knows all paths. The traveler decides where to go — and by traveling, enriches the map.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## The problem LinkLab solves
|
|
18
|
+
|
|
19
|
+
In every application, we write the same SQL joins by hand:
|
|
20
|
+
|
|
21
|
+
```sql
|
|
22
|
+
-- Get all actors in films directed by Nolan
|
|
23
|
+
SELECT people.*
|
|
24
|
+
FROM directors
|
|
25
|
+
INNER JOIN credits ON directors.id = credits.personId AND credits.jobId = 2
|
|
26
|
+
INNER JOIN movies ON credits.movieId = movies.id
|
|
27
|
+
INNER JOIN credits c2 ON movies.id = c2.movieId AND c2.jobId = 1
|
|
28
|
+
INNER JOIN people ON c2.personId = people.id
|
|
29
|
+
WHERE directors.name = 'Nolan'
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
With LinkLab:
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
cinema.directors('Nolan').movies.actors
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
LinkLab generates the SQL, finds the optimal path in the graph, and improves continuously from usage traces.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm install @linklabjs/core
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Quick start
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { Graph } from '@linklabjs/core'
|
|
54
|
+
import compiledGraph from './linklab/netflix/netflix.json'
|
|
55
|
+
import * as dataset from './data'
|
|
56
|
+
|
|
57
|
+
const graph = new Graph(compiledGraph, { dataset })
|
|
58
|
+
const netflix = graph.domain()
|
|
59
|
+
|
|
60
|
+
// Fluent navigation
|
|
61
|
+
const actors = await netflix.movies(278).actors
|
|
62
|
+
const films = await netflix.directors('Nolan').movies
|
|
63
|
+
const colleagues = await netflix.actors('DiCaprio').movies.directors
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The result is a plain JavaScript array — map, filter, sort as usual:
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
const titles = await netflix.directors('Nolan').movies
|
|
70
|
+
.then(films => films.filter(f => f.release_year > 2000))
|
|
71
|
+
.then(films => films.map(f => f.title))
|
|
72
|
+
// ['Interstellar', 'Inception', 'The Dark Knight'...]
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## How it works
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
Your database or JSON files
|
|
81
|
+
↓ linklab build
|
|
82
|
+
{alias}.json (compiled graph — precalculated routes)
|
|
83
|
+
↓ QueryEngine
|
|
84
|
+
SQL generated automatically
|
|
85
|
+
↓ NavigationEngine
|
|
86
|
+
Fluent API: cinema.directors('Nolan').movies.actors
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`linklab build` is a CLI command from `@linklabjs/cli`. It produces the compiled graph that `@linklabjs/core` consumes at runtime.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Semantic views
|
|
94
|
+
|
|
95
|
+
When the same entity appears in multiple roles — actors, directors, writers all being `people` — LinkLab detects this at compile time and generates semantic views automatically:
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
netflix.movies(278).people → everyone (all roles)
|
|
99
|
+
netflix.movies(278).actors → actors only
|
|
100
|
+
netflix.movies(278).director → director only
|
|
101
|
+
netflix.movies(278).writers → writers only
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`people('Christopher Nolan').director` and `directors('Christopher Nolan')` are equivalent — same entity, filtered by role. No separate endpoint to maintain.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## API levels
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
Level 1 cinema.directors('Nolan').movies.actors
|
|
112
|
+
→ semantic facade, transparent, 80% of use cases
|
|
113
|
+
|
|
114
|
+
Level 2 graph.from('Pigalle').to('Alesia').path(Strategy.Shortest)
|
|
115
|
+
→ paths, strategies, Dijkstra
|
|
116
|
+
|
|
117
|
+
Level 3 graph.entities / .relations / .weights
|
|
118
|
+
→ introspection, debug, dashboards
|
|
119
|
+
|
|
120
|
+
Level 4 graph.weight(edge).set(value) / .compile()
|
|
121
|
+
→ metaprogramming, CalibrationJob
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Level 1 — Semantic facade
|
|
127
|
+
|
|
128
|
+
### `new Graph(compiledGraph, options?)` → `Graph`
|
|
129
|
+
|
|
130
|
+
Main entry point. Builds a navigable graph.
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
import { Graph } from '@linklabjs/core'
|
|
134
|
+
|
|
135
|
+
const graph = new Graph(compiledGraph, {
|
|
136
|
+
compiled?: CompiledGraph, // precalculated routes
|
|
137
|
+
dataset?: Record<string, any[]>, // JSON data in memory
|
|
138
|
+
provider?: Provider, // PostgresProvider for real database
|
|
139
|
+
})
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### `graph.domain()` → `DomainProxy`
|
|
143
|
+
|
|
144
|
+
Returns the transparent semantic proxy (Level 1).
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
const cinema = graph.domain()
|
|
148
|
+
|
|
149
|
+
const cast = await cinema.movies(278).people
|
|
150
|
+
const films = await cinema.directors('Nolan').movies
|
|
151
|
+
const found = await cinema.movies({ title: 'Inception' })
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Level 2 — Pathfinding
|
|
157
|
+
|
|
158
|
+
### `graph.from(nodeId)` → `PathBuilder`
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
const builder = graph.from('Pigalle').to('Alesia')
|
|
162
|
+
|
|
163
|
+
builder.paths() // all paths — Shortest by default
|
|
164
|
+
builder.paths(Strategy.Comfort()) // +8 min per transfer
|
|
165
|
+
builder.path() // best path only
|
|
166
|
+
builder.links // subgraph between two nodes
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### `Strategy`
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
import { Strategy } from '@linklabjs/core'
|
|
173
|
+
|
|
174
|
+
Strategy.Shortest() // minimal raw weight (default)
|
|
175
|
+
Strategy.Comfort() // +8 min per transfer
|
|
176
|
+
Strategy.Custom(penalty) // +penalty per transfer
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Level 3 — Introspection
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
graph.entities // GraphNode[] — all nodes
|
|
185
|
+
graph.relations // GraphEdge[] — all edges
|
|
186
|
+
graph.schema // Record<string, string> — node types
|
|
187
|
+
graph.weights // Map<string, number> — current weights
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Fastify plugin — REST + HATEOAS
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
import Fastify from 'fastify'
|
|
196
|
+
import { linklabPlugin } from '@linklabjs/core'
|
|
197
|
+
|
|
198
|
+
const app = Fastify()
|
|
199
|
+
|
|
200
|
+
await app.register(linklabPlugin, {
|
|
201
|
+
graph: compiledGraph,
|
|
202
|
+
prefix: '/api',
|
|
203
|
+
dataLoader: { provider: postgresProvider },
|
|
204
|
+
onEngine: (engine, req) => {
|
|
205
|
+
engine.hooks.on('access.check', async (ctx) => {
|
|
206
|
+
if (!req.user) return { cancelled: true, reason: 'unauthenticated' }
|
|
207
|
+
})
|
|
208
|
+
}
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
// These routes work automatically — no configuration:
|
|
212
|
+
// GET /api/movies
|
|
213
|
+
// GET /api/movies/278
|
|
214
|
+
// GET /api/movies/278/people
|
|
215
|
+
// GET /api/directors/2/movies
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Response includes `_links` generated from the graph:
|
|
219
|
+
|
|
220
|
+
```json
|
|
221
|
+
{
|
|
222
|
+
"id": 504,
|
|
223
|
+
"name": "Tim Robbins",
|
|
224
|
+
"_links": {
|
|
225
|
+
"self": { "href": "/api/movies/278/people/504" },
|
|
226
|
+
"up": { "href": "/api/movies/278" },
|
|
227
|
+
"movies": { "href": "/api/movies/278/people/504/movies" },
|
|
228
|
+
"credits": { "href": "/api/movies/278/people/504/credits" }
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## Low-level API
|
|
236
|
+
|
|
237
|
+
### `QueryEngine`
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
import { QueryEngine } from '@linklabjs/core'
|
|
241
|
+
|
|
242
|
+
const engine = new QueryEngine(compiledGraph)
|
|
243
|
+
|
|
244
|
+
engine.getRoute(from, to) // RouteInfo
|
|
245
|
+
engine.generateSQL(options: QueryOptions) // string — readable SQL
|
|
246
|
+
engine.executeInMemory(options, dataset) // any[] — JSON execution
|
|
247
|
+
engine.generateJSONPipeline(options) // object — debug
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
interface QueryOptions {
|
|
252
|
+
from: string
|
|
253
|
+
to: string
|
|
254
|
+
filters?: Record<string, any> // WHERE conditions
|
|
255
|
+
semantic?: string // semantic view label — ex: 'actor'
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### `PathFinder`
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
import { PathFinder } from '@linklabjs/core'
|
|
263
|
+
|
|
264
|
+
const finder = new PathFinder(graph)
|
|
265
|
+
|
|
266
|
+
finder.findShortestPath(from, to) // PathDetails | null
|
|
267
|
+
finder.findAllPaths(from, to, maxPaths?) // Path[]
|
|
268
|
+
finder.hasPath(from, to) // boolean
|
|
269
|
+
finder.getReachableNodes(from, maxDepth?) // Set<string>
|
|
270
|
+
finder.getPathWeight(path) // number
|
|
271
|
+
finder.getStats() // { nodes, edges, avgDegree }
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### `GraphCompiler`
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
import { GraphCompiler } from '@linklabjs/core'
|
|
278
|
+
|
|
279
|
+
const compiler = new GraphCompiler({
|
|
280
|
+
weightThreshold?: number, // pruning threshold (default: 1000)
|
|
281
|
+
keepFallbacks?: boolean, // keep alternative routes
|
|
282
|
+
maxFallbacks?: number, // max alternatives per route
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
compiler.compile(graph, metrics): CompiledGraph
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## Core types
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
interface GraphNode {
|
|
294
|
+
id: string
|
|
295
|
+
type: string
|
|
296
|
+
label?: string
|
|
297
|
+
[key: string]: any
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
interface GraphEdge {
|
|
301
|
+
from: string
|
|
302
|
+
to: string
|
|
303
|
+
weight: number
|
|
304
|
+
name?: string
|
|
305
|
+
via?: string
|
|
306
|
+
metadata?: Record<string, any>
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
interface CompiledGraph {
|
|
310
|
+
version: string
|
|
311
|
+
compiledAt: string
|
|
312
|
+
nodes: GraphNode[]
|
|
313
|
+
routes: RouteInfo[]
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
interface RouteInfo {
|
|
317
|
+
from: string
|
|
318
|
+
to: string
|
|
319
|
+
semantic?: boolean
|
|
320
|
+
label?: string
|
|
321
|
+
primary: {
|
|
322
|
+
path: string[]
|
|
323
|
+
edges: RouteStep[]
|
|
324
|
+
weight: number
|
|
325
|
+
joins: number
|
|
326
|
+
}
|
|
327
|
+
fallbacks: RouteInfo['primary'][]
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
interface Provider {
|
|
331
|
+
query<T>(sql: string, params?: any[]): Promise<T[]>
|
|
332
|
+
close(): Promise<void>
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## Recommended imports
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
import {
|
|
342
|
+
Graph,
|
|
343
|
+
Strategy,
|
|
344
|
+
PathFinder,
|
|
345
|
+
QueryEngine,
|
|
346
|
+
GraphCompiler,
|
|
347
|
+
NavigationEngine,
|
|
348
|
+
linklabPlugin,
|
|
349
|
+
} from '@linklabjs/core'
|
|
350
|
+
|
|
351
|
+
import type {
|
|
352
|
+
GraphNode,
|
|
353
|
+
GraphEdge,
|
|
354
|
+
CompiledGraph,
|
|
355
|
+
RouteInfo,
|
|
356
|
+
QueryOptions,
|
|
357
|
+
} from '@linklabjs/core'
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## Examples
|
|
363
|
+
|
|
364
|
+
| Example | Source | Demonstrates |
|
|
365
|
+
|---------|--------|-------------|
|
|
366
|
+
| `dvdrental` | PostgreSQL | FK relations, semantic views, full pipeline |
|
|
367
|
+
| `netflix` | JSON | Pivot detection, semantic views (actors/directors/writers) |
|
|
368
|
+
| `cinema` | JSON | Minimal graph, REPL starting point |
|
|
369
|
+
| `metro` | GTFS open data | Dijkstra, real RATP weights, strategies |
|
|
370
|
+
| `musicians` | Manual | Cycles, minHops, via filter |
|
|
371
|
+
|
|
372
|
+
See the [examples](./src/examples) folder.
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## Custom formatters
|
|
377
|
+
|
|
378
|
+
Extend `BaseFormatter` to transform raw navigation results into domain-readable output:
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
import { BaseFormatter } from '@linklabjs/core'
|
|
382
|
+
import type { NavigationPath } from '@linklabjs/core'
|
|
383
|
+
|
|
384
|
+
export class MyFormatter extends BaseFormatter {
|
|
385
|
+
format(path: NavigationPath): string {
|
|
386
|
+
return [
|
|
387
|
+
`Path: ${path.nodes.join(' → ')}`,
|
|
388
|
+
`Weight: ${path.totalWeight}`,
|
|
389
|
+
].join('\n')
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
## Not an ORM
|
|
397
|
+
|
|
398
|
+
LinkLab does not map tables to objects. It does not manage migrations. It does not hide your SQL.
|
|
399
|
+
|
|
400
|
+
It compiles a navigation graph from your existing schema and resolves paths through it. The generated SQL is readable — visible in the REPL and in `QueryEngine.generateSQL()`.
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
- [GitHub](https://github.com/charley-simon/linklab)
|
|
405
|
+
- [Report an issue](https://github.com/charley-simon/linklab/issues)
|
|
406
|
+
|
|
407
|
+
---
|
|
408
|
+
|
|
409
|
+
## License
|
|
410
|
+
|
|
411
|
+
MIT — Charley Simon
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@linklabjs/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "LinkLab core — semantic navigation graph engine",
|
|
6
|
+
"author": "Charley Simon",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"homepage": "https://github.com/charley-simon/linklab",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/charley-simon/linklab.git",
|
|
12
|
+
"directory": "packages/core"
|
|
13
|
+
},
|
|
14
|
+
"keywords": ["linklab", "graph", "navigation", "hateoas", "rest", "api", "semantic", "trail", "typescript"],
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"default": "./dist/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": ["dist", "src"],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "pnpm run clean && tsc -p tsconfig.json",
|
|
26
|
+
"clean": "rimraf --glob dist \"src/**/*.js\" \"src/**/*.d.ts\" \"tests/**/*.js\"",
|
|
27
|
+
"test": "vitest run",
|
|
28
|
+
"test:watch": "vitest",
|
|
29
|
+
"test:coverage": "vitest run --coverage",
|
|
30
|
+
"prepublishOnly": "pnpm run build"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"chalk": "^5.6.2",
|
|
34
|
+
"dotenv": "^17.3.1",
|
|
35
|
+
"fastify": "^5.8.2",
|
|
36
|
+
"fastify-plugin": "^5.1.0",
|
|
37
|
+
"pg": "^8.20.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^25.4.0",
|
|
41
|
+
"@vitest/coverage-v8": "^2.0.0",
|
|
42
|
+
"@vitest/ui": "^2.0.0",
|
|
43
|
+
"rimraf": "^6.1.3",
|
|
44
|
+
"typescript": "^5.9.3",
|
|
45
|
+
"vitest": "^2.0.0"
|
|
46
|
+
},
|
|
47
|
+
"engines": { "node": ">=18" }
|
|
48
|
+
}
|