trickle-backend 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/dist/db/connection.d.ts +3 -0
- package/dist/db/connection.js +16 -0
- package/dist/db/migrations.d.ts +2 -0
- package/dist/db/migrations.js +51 -0
- package/dist/db/queries.d.ts +70 -0
- package/dist/db/queries.js +186 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +10 -0
- package/dist/routes/audit.d.ts +2 -0
- package/dist/routes/audit.js +251 -0
- package/dist/routes/codegen.d.ts +2 -0
- package/dist/routes/codegen.js +224 -0
- package/dist/routes/coverage.d.ts +2 -0
- package/dist/routes/coverage.js +98 -0
- package/dist/routes/dashboard.d.ts +2 -0
- package/dist/routes/dashboard.js +433 -0
- package/dist/routes/diff.d.ts +2 -0
- package/dist/routes/diff.js +181 -0
- package/dist/routes/errors.d.ts +2 -0
- package/dist/routes/errors.js +86 -0
- package/dist/routes/functions.d.ts +2 -0
- package/dist/routes/functions.js +69 -0
- package/dist/routes/ingest.d.ts +2 -0
- package/dist/routes/ingest.js +111 -0
- package/dist/routes/mock.d.ts +2 -0
- package/dist/routes/mock.js +57 -0
- package/dist/routes/search.d.ts +2 -0
- package/dist/routes/search.js +136 -0
- package/dist/routes/tail.d.ts +2 -0
- package/dist/routes/tail.js +11 -0
- package/dist/routes/types.d.ts +2 -0
- package/dist/routes/types.js +97 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +40 -0
- package/dist/services/sse-broker.d.ts +10 -0
- package/dist/services/sse-broker.js +39 -0
- package/dist/services/type-differ.d.ts +2 -0
- package/dist/services/type-differ.js +126 -0
- package/dist/services/type-generator.d.ts +319 -0
- package/dist/services/type-generator.js +3207 -0
- package/dist/types.d.ts +56 -0
- package/dist/types.js +2 -0
- package/package.json +22 -0
- package/src/db/connection.ts +16 -0
- package/src/db/migrations.ts +50 -0
- package/src/db/queries.ts +260 -0
- package/src/index.ts +11 -0
- package/src/routes/audit.ts +283 -0
- package/src/routes/codegen.ts +237 -0
- package/src/routes/coverage.ts +120 -0
- package/src/routes/dashboard.ts +435 -0
- package/src/routes/diff.ts +215 -0
- package/src/routes/errors.ts +91 -0
- package/src/routes/functions.ts +75 -0
- package/src/routes/ingest.ts +139 -0
- package/src/routes/mock.ts +66 -0
- package/src/routes/search.ts +169 -0
- package/src/routes/tail.ts +12 -0
- package/src/routes/types.ts +106 -0
- package/src/server.ts +40 -0
- package/src/services/sse-broker.ts +51 -0
- package/src/services/type-differ.ts +141 -0
- package/src/services/type-generator.ts +3853 -0
- package/src/types.ts +37 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const express_1 = require("express");
|
|
4
|
+
const connection_1 = require("../db/connection");
|
|
5
|
+
const queries_1 = require("../db/queries");
|
|
6
|
+
const type_generator_1 = require("../services/type-generator");
|
|
7
|
+
const router = (0, express_1.Router)();
|
|
8
|
+
function tryParseJson(value) {
|
|
9
|
+
try {
|
|
10
|
+
return JSON.parse(value);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Collect type data for all matching functions.
|
|
18
|
+
*/
|
|
19
|
+
function collectFunctionTypes(opts) {
|
|
20
|
+
const results = [];
|
|
21
|
+
let functionRows;
|
|
22
|
+
if (opts.functionName) {
|
|
23
|
+
functionRows = (0, queries_1.getFunctionByName)(connection_1.db, opts.functionName);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
const listed = (0, queries_1.listFunctions)(connection_1.db, {
|
|
27
|
+
env: opts.env,
|
|
28
|
+
limit: 500,
|
|
29
|
+
});
|
|
30
|
+
functionRows = listed.rows;
|
|
31
|
+
}
|
|
32
|
+
for (const fn of functionRows) {
|
|
33
|
+
const functionId = fn.id;
|
|
34
|
+
const functionName = fn.function_name;
|
|
35
|
+
const moduleName = fn.module;
|
|
36
|
+
const environment = fn.environment || undefined;
|
|
37
|
+
const snapshot = (0, queries_1.getLatestSnapshot)(connection_1.db, functionId, opts.env);
|
|
38
|
+
if (!snapshot)
|
|
39
|
+
continue;
|
|
40
|
+
const argsType = tryParseJson(snapshot.args_type);
|
|
41
|
+
const returnType = tryParseJson(snapshot.return_type);
|
|
42
|
+
if (!argsType || !returnType)
|
|
43
|
+
continue;
|
|
44
|
+
results.push({
|
|
45
|
+
name: functionName,
|
|
46
|
+
argsType,
|
|
47
|
+
returnType,
|
|
48
|
+
module: moduleName,
|
|
49
|
+
env: snapshot.env || environment,
|
|
50
|
+
observedAt: snapshot.observed_at,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return results;
|
|
54
|
+
}
|
|
55
|
+
// GET / — generate types for all (or filtered) functions
|
|
56
|
+
router.get("/", (req, res) => {
|
|
57
|
+
try {
|
|
58
|
+
const { functionName, env, language } = req.query;
|
|
59
|
+
const functions = collectFunctionTypes({
|
|
60
|
+
functionName: functionName,
|
|
61
|
+
env: env,
|
|
62
|
+
});
|
|
63
|
+
if (functions.length === 0) {
|
|
64
|
+
res.json({ types: "// No functions found matching the given filters.\n" });
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const format = req.query.format?.toLowerCase();
|
|
68
|
+
const isPython = language?.toLowerCase() === "python";
|
|
69
|
+
if (format === "snapshot") {
|
|
70
|
+
// Return raw type data as a portable JSON snapshot for `trickle check`
|
|
71
|
+
const snapshot = {
|
|
72
|
+
version: 1,
|
|
73
|
+
createdAt: new Date().toISOString(),
|
|
74
|
+
functions: functions.map((f) => ({
|
|
75
|
+
name: f.name,
|
|
76
|
+
module: f.module,
|
|
77
|
+
env: f.env,
|
|
78
|
+
argsType: f.argsType,
|
|
79
|
+
returnType: f.returnType,
|
|
80
|
+
})),
|
|
81
|
+
};
|
|
82
|
+
res.json(snapshot);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (format === "openapi") {
|
|
86
|
+
const title = req.query.title || undefined;
|
|
87
|
+
const version = req.query.version || undefined;
|
|
88
|
+
const serverUrl = req.query.serverUrl || undefined;
|
|
89
|
+
const spec = (0, type_generator_1.generateOpenApiSpec)(functions, { title, version, serverUrl });
|
|
90
|
+
res.json(spec);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
let types;
|
|
94
|
+
if (format === "handlers") {
|
|
95
|
+
types = (0, type_generator_1.generateHandlerTypes)(functions);
|
|
96
|
+
res.json({ types });
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
else if (format === "zod") {
|
|
100
|
+
types = (0, type_generator_1.generateZodSchemas)(functions);
|
|
101
|
+
res.json({ types });
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
else if (format === "react-query") {
|
|
105
|
+
types = (0, type_generator_1.generateReactQueryHooks)(functions);
|
|
106
|
+
res.json({ types });
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
else if (format === "guards") {
|
|
110
|
+
types = (0, type_generator_1.generateTypeGuards)(functions);
|
|
111
|
+
res.json({ types });
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
else if (format === "middleware") {
|
|
115
|
+
types = (0, type_generator_1.generateMiddleware)(functions);
|
|
116
|
+
res.json({ types });
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
else if (format === "msw") {
|
|
120
|
+
types = (0, type_generator_1.generateMswHandlers)(functions);
|
|
121
|
+
res.json({ types });
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
else if (format === "json-schema") {
|
|
125
|
+
types = (0, type_generator_1.generateJsonSchemas)(functions);
|
|
126
|
+
res.json({ types });
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
else if (format === "swr") {
|
|
130
|
+
types = (0, type_generator_1.generateSwrHooks)(functions);
|
|
131
|
+
res.json({ types });
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
else if (format === "pydantic") {
|
|
135
|
+
types = (0, type_generator_1.generatePydanticModels)(functions);
|
|
136
|
+
res.json({ types });
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
else if (format === "class-validator") {
|
|
140
|
+
types = (0, type_generator_1.generateClassValidatorDtos)(functions);
|
|
141
|
+
res.json({ types });
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
else if (format === "graphql") {
|
|
145
|
+
types = (0, type_generator_1.generateGraphqlSchema)(functions);
|
|
146
|
+
res.json({ types });
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
else if (format === "trpc") {
|
|
150
|
+
types = (0, type_generator_1.generateTrpcRouter)(functions);
|
|
151
|
+
res.json({ types });
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
else if (format === "axios") {
|
|
155
|
+
types = (0, type_generator_1.generateAxiosClient)(functions);
|
|
156
|
+
res.json({ types });
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
else if (format === "annotate") {
|
|
160
|
+
const lang = language?.toLowerCase() === "python" ? "python" : "typescript";
|
|
161
|
+
const annotations = (0, type_generator_1.generateInlineAnnotations)(functions, lang);
|
|
162
|
+
res.json({ annotations });
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
else if (format === "stubs") {
|
|
166
|
+
// Group functions by module and return per-module type stubs
|
|
167
|
+
const byModule = {};
|
|
168
|
+
for (const fn of functions) {
|
|
169
|
+
const mod = fn.module || "_default";
|
|
170
|
+
if (!byModule[mod])
|
|
171
|
+
byModule[mod] = [];
|
|
172
|
+
byModule[mod].push(fn);
|
|
173
|
+
}
|
|
174
|
+
const stubs = {};
|
|
175
|
+
for (const [mod, fns] of Object.entries(byModule)) {
|
|
176
|
+
stubs[mod] = {
|
|
177
|
+
ts: (0, type_generator_1.generateAllTypes)(fns),
|
|
178
|
+
python: (0, type_generator_1.generatePythonTypes)(fns),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
res.json({ stubs });
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
else if (format === "client") {
|
|
185
|
+
types = (0, type_generator_1.generateApiClient)(functions);
|
|
186
|
+
}
|
|
187
|
+
else if (isPython) {
|
|
188
|
+
types = (0, type_generator_1.generatePythonTypes)(functions);
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
types = (0, type_generator_1.generateAllTypes)(functions);
|
|
192
|
+
}
|
|
193
|
+
res.json({ types });
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
console.error("Codegen error:", err);
|
|
197
|
+
res.status(500).json({ error: "Internal server error" });
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
// GET /:functionName — generate types for a specific function
|
|
201
|
+
router.get("/:functionName", (req, res) => {
|
|
202
|
+
try {
|
|
203
|
+
const { functionName } = req.params;
|
|
204
|
+
const { env, language } = req.query;
|
|
205
|
+
const functions = collectFunctionTypes({
|
|
206
|
+
functionName,
|
|
207
|
+
env: env,
|
|
208
|
+
});
|
|
209
|
+
if (functions.length === 0) {
|
|
210
|
+
res.status(404).json({ error: `No function found matching "${functionName}"` });
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const isPython = language?.toLowerCase() === "python";
|
|
214
|
+
const types = isPython
|
|
215
|
+
? (0, type_generator_1.generatePythonTypes)(functions)
|
|
216
|
+
: (0, type_generator_1.generateAllTypes)(functions);
|
|
217
|
+
res.json({ types });
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
console.error("Codegen error:", err);
|
|
221
|
+
res.status(500).json({ error: "Internal server error" });
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
exports.default = router;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const express_1 = require("express");
|
|
4
|
+
const connection_1 = require("../db/connection");
|
|
5
|
+
const router = (0, express_1.Router)();
|
|
6
|
+
/**
|
|
7
|
+
* GET /api/coverage — Type observation coverage report.
|
|
8
|
+
*
|
|
9
|
+
* Returns per-function stats: snapshot count, variant count,
|
|
10
|
+
* freshness, error count, and an overall health score.
|
|
11
|
+
*/
|
|
12
|
+
router.get("/", (req, res) => {
|
|
13
|
+
try {
|
|
14
|
+
const { env, stale_hours } = req.query;
|
|
15
|
+
const staleThresholdHours = stale_hours ? parseInt(stale_hours, 10) : 24;
|
|
16
|
+
// Get all functions
|
|
17
|
+
let functionsQuery = `SELECT * FROM functions`;
|
|
18
|
+
const params = [];
|
|
19
|
+
if (env) {
|
|
20
|
+
functionsQuery += ` WHERE environment = ?`;
|
|
21
|
+
params.push(env);
|
|
22
|
+
}
|
|
23
|
+
functionsQuery += ` ORDER BY last_seen_at DESC`;
|
|
24
|
+
const functions = connection_1.db.prepare(functionsQuery).all(...params);
|
|
25
|
+
// Per-function stats
|
|
26
|
+
const snapshotCountStmt = connection_1.db.prepare(`SELECT COUNT(*) as count FROM type_snapshots WHERE function_id = ?`);
|
|
27
|
+
const variantCountStmt = connection_1.db.prepare(`SELECT COUNT(DISTINCT type_hash) as count FROM type_snapshots WHERE function_id = ?`);
|
|
28
|
+
const errorCountStmt = connection_1.db.prepare(`SELECT COUNT(*) as count FROM errors WHERE function_id = ?`);
|
|
29
|
+
const latestSnapshotStmt = connection_1.db.prepare(`SELECT observed_at FROM type_snapshots WHERE function_id = ? ORDER BY observed_at DESC LIMIT 1`);
|
|
30
|
+
const now = new Date();
|
|
31
|
+
const staleThreshold = new Date(now.getTime() - staleThresholdHours * 60 * 60 * 1000);
|
|
32
|
+
const entries = functions.map((fn) => {
|
|
33
|
+
const snapshots = snapshotCountStmt.get(fn.id).count;
|
|
34
|
+
const variants = variantCountStmt.get(fn.id).count;
|
|
35
|
+
const errors = errorCountStmt.get(fn.id).count;
|
|
36
|
+
const latestRow = latestSnapshotStmt.get(fn.id);
|
|
37
|
+
const lastObserved = latestRow ? latestRow.observed_at : fn.last_seen_at;
|
|
38
|
+
const lastObservedDate = new Date(lastObserved);
|
|
39
|
+
const isStale = lastObservedDate < staleThreshold;
|
|
40
|
+
const hasTypes = snapshots > 0;
|
|
41
|
+
const hasMultipleVariants = variants > 1;
|
|
42
|
+
const hasErrors = errors > 0;
|
|
43
|
+
// Per-function health: 0-100
|
|
44
|
+
let health = 0;
|
|
45
|
+
if (hasTypes)
|
|
46
|
+
health += 60; // Has type observations
|
|
47
|
+
if (!isStale)
|
|
48
|
+
health += 20; // Recently observed
|
|
49
|
+
if (!hasErrors)
|
|
50
|
+
health += 10; // No errors
|
|
51
|
+
if (!hasMultipleVariants)
|
|
52
|
+
health += 10; // Consistent types (single variant)
|
|
53
|
+
return {
|
|
54
|
+
functionName: fn.function_name,
|
|
55
|
+
module: fn.module,
|
|
56
|
+
language: fn.language,
|
|
57
|
+
environment: fn.environment,
|
|
58
|
+
firstSeen: fn.first_seen_at,
|
|
59
|
+
lastObserved,
|
|
60
|
+
snapshots,
|
|
61
|
+
variants,
|
|
62
|
+
errors,
|
|
63
|
+
isStale,
|
|
64
|
+
hasTypes,
|
|
65
|
+
health,
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
// Aggregate stats
|
|
69
|
+
const total = entries.length;
|
|
70
|
+
const withTypes = entries.filter((e) => e.hasTypes).length;
|
|
71
|
+
const staleCount = entries.filter((e) => e.isStale).length;
|
|
72
|
+
const freshCount = entries.filter((e) => !e.isStale).length;
|
|
73
|
+
const withErrors = entries.filter((e) => e.errors > 0).length;
|
|
74
|
+
const withMultipleVariants = entries.filter((e) => e.variants > 1).length;
|
|
75
|
+
const overallHealth = total > 0
|
|
76
|
+
? Math.round(entries.reduce((sum, e) => sum + e.health, 0) / total)
|
|
77
|
+
: 0;
|
|
78
|
+
res.json({
|
|
79
|
+
summary: {
|
|
80
|
+
total,
|
|
81
|
+
withTypes,
|
|
82
|
+
withoutTypes: total - withTypes,
|
|
83
|
+
fresh: freshCount,
|
|
84
|
+
stale: staleCount,
|
|
85
|
+
withErrors,
|
|
86
|
+
withMultipleVariants,
|
|
87
|
+
health: overallHealth,
|
|
88
|
+
staleThresholdHours,
|
|
89
|
+
},
|
|
90
|
+
entries,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
console.error("Coverage error:", err);
|
|
95
|
+
res.status(500).json({ error: "Internal server error" });
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
exports.default = router;
|