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,86 @@
|
|
|
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 router = (0, express_1.Router)();
|
|
7
|
+
// GET / — list errors with filters
|
|
8
|
+
router.get("/", (req, res) => {
|
|
9
|
+
try {
|
|
10
|
+
const { functionName, env, since, limit, offset } = req.query;
|
|
11
|
+
const result = (0, queries_1.listErrors)(connection_1.db, {
|
|
12
|
+
functionName: functionName,
|
|
13
|
+
env: env,
|
|
14
|
+
since: since,
|
|
15
|
+
limit: limit ? parseInt(limit, 10) : undefined,
|
|
16
|
+
offset: offset ? parseInt(offset, 10) : undefined,
|
|
17
|
+
});
|
|
18
|
+
const parsed = result.rows.map((row) => ({
|
|
19
|
+
...row,
|
|
20
|
+
args_type: tryParseJson(row.args_type),
|
|
21
|
+
return_type: tryParseJson(row.return_type),
|
|
22
|
+
args_snapshot: tryParseJson(row.args_snapshot),
|
|
23
|
+
}));
|
|
24
|
+
res.json({ errors: parsed, total: result.total });
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
console.error("List errors error:", err);
|
|
28
|
+
res.status(500).json({ error: "Internal server error" });
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
// GET /:id — get single error with full context
|
|
32
|
+
router.get("/:id", (req, res) => {
|
|
33
|
+
try {
|
|
34
|
+
const id = parseInt(req.params.id, 10);
|
|
35
|
+
if (isNaN(id)) {
|
|
36
|
+
res.status(400).json({ error: "Invalid error id" });
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const errorRow = (0, queries_1.getError)(connection_1.db, id);
|
|
40
|
+
if (!errorRow) {
|
|
41
|
+
res.status(404).json({ error: "Error not found" });
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
// Find the associated type snapshot if type_hash is available
|
|
45
|
+
let snapshot;
|
|
46
|
+
if (errorRow.type_hash && errorRow.function_id) {
|
|
47
|
+
const stmt = connection_1.db.prepare(`
|
|
48
|
+
SELECT * FROM type_snapshots
|
|
49
|
+
WHERE function_id = ? AND type_hash = ?
|
|
50
|
+
ORDER BY observed_at DESC
|
|
51
|
+
LIMIT 1
|
|
52
|
+
`);
|
|
53
|
+
snapshot = stmt.get(errorRow.function_id, errorRow.type_hash);
|
|
54
|
+
}
|
|
55
|
+
res.json({
|
|
56
|
+
error: {
|
|
57
|
+
...errorRow,
|
|
58
|
+
args_type: tryParseJson(errorRow.args_type),
|
|
59
|
+
return_type: tryParseJson(errorRow.return_type),
|
|
60
|
+
args_snapshot: tryParseJson(errorRow.args_snapshot),
|
|
61
|
+
},
|
|
62
|
+
snapshot: snapshot
|
|
63
|
+
? {
|
|
64
|
+
...snapshot,
|
|
65
|
+
args_type: tryParseJson(snapshot.args_type),
|
|
66
|
+
return_type: tryParseJson(snapshot.return_type),
|
|
67
|
+
}
|
|
68
|
+
: null,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
console.error("Get error error:", err);
|
|
73
|
+
res.status(500).json({ error: "Internal server error" });
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
function tryParseJson(value) {
|
|
77
|
+
if (typeof value !== "string")
|
|
78
|
+
return value;
|
|
79
|
+
try {
|
|
80
|
+
return JSON.parse(value);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
exports.default = router;
|
|
@@ -0,0 +1,69 @@
|
|
|
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 router = (0, express_1.Router)();
|
|
7
|
+
// GET / — list functions
|
|
8
|
+
router.get("/", (req, res) => {
|
|
9
|
+
try {
|
|
10
|
+
const { q, env, language, limit, offset } = req.query;
|
|
11
|
+
const result = (0, queries_1.listFunctions)(connection_1.db, {
|
|
12
|
+
search: q,
|
|
13
|
+
env: env,
|
|
14
|
+
language: language,
|
|
15
|
+
limit: limit ? parseInt(limit, 10) : undefined,
|
|
16
|
+
offset: offset ? parseInt(offset, 10) : undefined,
|
|
17
|
+
});
|
|
18
|
+
res.json({ functions: result.rows, total: result.total });
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
console.error("List functions error:", err);
|
|
22
|
+
res.status(500).json({ error: "Internal server error" });
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
// GET /:id — get single function with latest snapshots
|
|
26
|
+
router.get("/:id", (req, res) => {
|
|
27
|
+
try {
|
|
28
|
+
const id = parseInt(req.params.id, 10);
|
|
29
|
+
if (isNaN(id)) {
|
|
30
|
+
res.status(400).json({ error: "Invalid function id" });
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const func = (0, queries_1.getFunction)(connection_1.db, id);
|
|
34
|
+
if (!func) {
|
|
35
|
+
res.status(404).json({ error: "Function not found" });
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
// Get latest snapshots per known env
|
|
39
|
+
const envStmt = connection_1.db.prepare(`
|
|
40
|
+
SELECT DISTINCT env FROM type_snapshots WHERE function_id = ?
|
|
41
|
+
`);
|
|
42
|
+
const envs = envStmt.all(id).map((r) => r.env);
|
|
43
|
+
const latestSnapshots = {};
|
|
44
|
+
for (const env of envs) {
|
|
45
|
+
const snapshot = (0, queries_1.getLatestSnapshot)(connection_1.db, id, env);
|
|
46
|
+
if (snapshot) {
|
|
47
|
+
latestSnapshots[env] = {
|
|
48
|
+
...snapshot,
|
|
49
|
+
args_type: tryParseJson(snapshot.args_type),
|
|
50
|
+
return_type: tryParseJson(snapshot.return_type),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
res.json({ function: func, latestSnapshots });
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
console.error("Get function error:", err);
|
|
58
|
+
res.status(500).json({ error: "Internal server error" });
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
function tryParseJson(value) {
|
|
62
|
+
try {
|
|
63
|
+
return JSON.parse(value);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
exports.default = router;
|
|
@@ -0,0 +1,111 @@
|
|
|
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 sse_broker_1 = require("../services/sse-broker");
|
|
7
|
+
const router = (0, express_1.Router)();
|
|
8
|
+
function processPayload(payload) {
|
|
9
|
+
const { functionName, module, language, environment, typeHash, argsType, returnType, sampleInput, sampleOutput, error, } = payload;
|
|
10
|
+
const env = environment || "unknown";
|
|
11
|
+
const func = (0, queries_1.upsertFunction)(connection_1.db, { functionName, module, environment: env, language });
|
|
12
|
+
const functionId = func.id;
|
|
13
|
+
let isNewType = false;
|
|
14
|
+
const existingSnapshot = (0, queries_1.findSnapshotByHash)(connection_1.db, functionId, typeHash, env);
|
|
15
|
+
if (!existingSnapshot) {
|
|
16
|
+
(0, queries_1.insertSnapshot)(connection_1.db, {
|
|
17
|
+
functionId,
|
|
18
|
+
typeHash,
|
|
19
|
+
argsType: JSON.stringify(argsType),
|
|
20
|
+
returnType: JSON.stringify(returnType),
|
|
21
|
+
variablesType: null,
|
|
22
|
+
sampleInput: sampleInput !== undefined ? JSON.stringify(sampleInput) : null,
|
|
23
|
+
sampleOutput: sampleOutput !== undefined ? JSON.stringify(sampleOutput) : null,
|
|
24
|
+
env,
|
|
25
|
+
});
|
|
26
|
+
isNewType = true;
|
|
27
|
+
sse_broker_1.sseBroker.broadcast("type:new", {
|
|
28
|
+
functionName,
|
|
29
|
+
module,
|
|
30
|
+
typeHash,
|
|
31
|
+
environment: env,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
let errorRecord;
|
|
35
|
+
if (error) {
|
|
36
|
+
errorRecord = (0, queries_1.insertError)(connection_1.db, {
|
|
37
|
+
functionId,
|
|
38
|
+
errorType: error.type,
|
|
39
|
+
errorMessage: error.message,
|
|
40
|
+
stackTrace: error.stackTrace || null,
|
|
41
|
+
argsType: JSON.stringify(argsType),
|
|
42
|
+
returnType: JSON.stringify(returnType),
|
|
43
|
+
variablesType: null,
|
|
44
|
+
argsSnapshot: error.argsSnapshot !== undefined ? JSON.stringify(error.argsSnapshot) : null,
|
|
45
|
+
typeHash,
|
|
46
|
+
env,
|
|
47
|
+
});
|
|
48
|
+
sse_broker_1.sseBroker.broadcast("error:new", {
|
|
49
|
+
functionName,
|
|
50
|
+
module,
|
|
51
|
+
errorType: error.type,
|
|
52
|
+
errorMessage: error.message,
|
|
53
|
+
environment: env,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return { functionId, isNewType, error: errorRecord };
|
|
57
|
+
}
|
|
58
|
+
// POST / — single payload ingest
|
|
59
|
+
router.post("/", (req, res) => {
|
|
60
|
+
try {
|
|
61
|
+
const payload = req.body;
|
|
62
|
+
if (!payload.functionName || !payload.module || !payload.typeHash) {
|
|
63
|
+
res.status(400).json({ error: "Missing required fields: functionName, module, typeHash" });
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const result = processPayload(payload);
|
|
67
|
+
res.status(200).json({
|
|
68
|
+
ok: true,
|
|
69
|
+
functionId: result.functionId,
|
|
70
|
+
isNewType: result.isNewType,
|
|
71
|
+
errorId: result.error?.id,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
console.error("Ingest error:", err);
|
|
76
|
+
res.status(500).json({ error: "Internal server error" });
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
// POST /batch — batch ingest
|
|
80
|
+
router.post("/batch", (req, res) => {
|
|
81
|
+
try {
|
|
82
|
+
const { payloads } = req.body;
|
|
83
|
+
if (!Array.isArray(payloads) || payloads.length === 0) {
|
|
84
|
+
res.status(400).json({ error: "Expected non-empty payloads array" });
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const results = [];
|
|
88
|
+
const transaction = connection_1.db.transaction(() => {
|
|
89
|
+
for (const payload of payloads) {
|
|
90
|
+
if (!payload.functionName || !payload.module || !payload.typeHash) {
|
|
91
|
+
results.push({ error: "Missing required fields", functionName: payload.functionName });
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const result = processPayload(payload);
|
|
95
|
+
results.push({
|
|
96
|
+
ok: true,
|
|
97
|
+
functionId: result.functionId,
|
|
98
|
+
isNewType: result.isNewType,
|
|
99
|
+
errorId: result.error?.id,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
transaction();
|
|
104
|
+
res.status(200).json({ ok: true, results });
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
console.error("Batch ingest error:", err);
|
|
108
|
+
res.status(500).json({ error: "Internal server error" });
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
exports.default = router;
|
|
@@ -0,0 +1,57 @@
|
|
|
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 router = (0, express_1.Router)();
|
|
7
|
+
function tryParseJson(value) {
|
|
8
|
+
if (typeof value !== "string")
|
|
9
|
+
return value;
|
|
10
|
+
try {
|
|
11
|
+
return JSON.parse(value);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** Route-style function name → { method, path } */
|
|
18
|
+
function parseRouteName(name) {
|
|
19
|
+
const match = name.match(/^(GET|POST|PUT|DELETE|PATCH)\s+(.+)$/i);
|
|
20
|
+
if (!match)
|
|
21
|
+
return null;
|
|
22
|
+
return { method: match[1].toUpperCase(), path: match[2] };
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* GET /api/mock-config
|
|
26
|
+
*
|
|
27
|
+
* Returns all observed routes with their sample data, ready for mock server use.
|
|
28
|
+
*/
|
|
29
|
+
router.get("/", (_req, res) => {
|
|
30
|
+
try {
|
|
31
|
+
const { rows } = (0, queries_1.listFunctions)(connection_1.db, { limit: 500 });
|
|
32
|
+
const routes = [];
|
|
33
|
+
for (const fn of rows) {
|
|
34
|
+
const parsed = parseRouteName(fn.function_name);
|
|
35
|
+
if (!parsed)
|
|
36
|
+
continue; // Skip non-route functions
|
|
37
|
+
const snapshot = (0, queries_1.getLatestSnapshot)(connection_1.db, fn.id);
|
|
38
|
+
if (!snapshot)
|
|
39
|
+
continue;
|
|
40
|
+
routes.push({
|
|
41
|
+
method: parsed.method,
|
|
42
|
+
path: parsed.path,
|
|
43
|
+
functionName: fn.function_name,
|
|
44
|
+
module: fn.module,
|
|
45
|
+
sampleInput: tryParseJson(snapshot.sample_input),
|
|
46
|
+
sampleOutput: tryParseJson(snapshot.sample_output),
|
|
47
|
+
observedAt: snapshot.observed_at,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
res.json({ routes });
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
console.error("Mock config error:", err);
|
|
54
|
+
res.status(500).json({ error: "Internal server error" });
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
exports.default = router;
|
|
@@ -0,0 +1,136 @@
|
|
|
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 router = (0, express_1.Router)();
|
|
7
|
+
function tryParseJson(value) {
|
|
8
|
+
try {
|
|
9
|
+
return JSON.parse(value);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Recursively search a TypeNode tree for fields matching the query.
|
|
17
|
+
* Returns an array of matching field paths.
|
|
18
|
+
*/
|
|
19
|
+
function searchTypeNode(node, query, currentPath, results) {
|
|
20
|
+
const lowerQuery = query.toLowerCase();
|
|
21
|
+
switch (node.kind) {
|
|
22
|
+
case "object": {
|
|
23
|
+
for (const [key, val] of Object.entries(node.properties)) {
|
|
24
|
+
const fieldPath = currentPath ? `${currentPath}.${key}` : key;
|
|
25
|
+
// Check if the field name matches
|
|
26
|
+
if (key.toLowerCase().includes(lowerQuery)) {
|
|
27
|
+
results.push({
|
|
28
|
+
path: fieldPath,
|
|
29
|
+
kind: val.kind,
|
|
30
|
+
typeName: val.kind === "primitive" ? val.name : val.kind,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
// Recurse into the value
|
|
34
|
+
searchTypeNode(val, query, fieldPath, results);
|
|
35
|
+
}
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
case "array":
|
|
39
|
+
searchTypeNode(node.element, query, `${currentPath}[]`, results);
|
|
40
|
+
break;
|
|
41
|
+
case "union":
|
|
42
|
+
for (const member of node.members) {
|
|
43
|
+
searchTypeNode(member, query, currentPath, results);
|
|
44
|
+
}
|
|
45
|
+
break;
|
|
46
|
+
case "tuple":
|
|
47
|
+
for (let i = 0; i < node.elements.length; i++) {
|
|
48
|
+
searchTypeNode(node.elements[i], query, `${currentPath}[${i}]`, results);
|
|
49
|
+
}
|
|
50
|
+
break;
|
|
51
|
+
case "promise":
|
|
52
|
+
searchTypeNode(node.resolved, query, currentPath, results);
|
|
53
|
+
break;
|
|
54
|
+
case "map":
|
|
55
|
+
searchTypeNode(node.key, query, `${currentPath}.key`, results);
|
|
56
|
+
searchTypeNode(node.value, query, `${currentPath}.value`, results);
|
|
57
|
+
break;
|
|
58
|
+
case "set":
|
|
59
|
+
searchTypeNode(node.element, query, `${currentPath}[]`, results);
|
|
60
|
+
break;
|
|
61
|
+
case "primitive": {
|
|
62
|
+
// Match on primitive type name (e.g., searching "number" finds all number fields)
|
|
63
|
+
if (node.name.toLowerCase().includes(lowerQuery) && currentPath) {
|
|
64
|
+
// Only add if not already matched by field name
|
|
65
|
+
const alreadyMatched = results.some((r) => r.path === currentPath);
|
|
66
|
+
if (!alreadyMatched) {
|
|
67
|
+
results.push({
|
|
68
|
+
path: currentPath,
|
|
69
|
+
kind: "primitive",
|
|
70
|
+
typeName: node.name,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// GET / — search across all observed types
|
|
79
|
+
router.get("/", (req, res) => {
|
|
80
|
+
try {
|
|
81
|
+
const query = req.query.q;
|
|
82
|
+
if (!query || query.trim().length === 0) {
|
|
83
|
+
res.status(400).json({ error: "Missing query parameter 'q'" });
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const env = req.query.env;
|
|
87
|
+
const lowerQuery = query.toLowerCase();
|
|
88
|
+
// Get all functions
|
|
89
|
+
const { rows: functionRows } = (0, queries_1.listFunctions)(connection_1.db, { env, limit: 500 });
|
|
90
|
+
const results = [];
|
|
91
|
+
for (const fn of functionRows) {
|
|
92
|
+
const functionId = fn.id;
|
|
93
|
+
const functionName = fn.function_name;
|
|
94
|
+
const moduleName = fn.module;
|
|
95
|
+
const environment = fn.environment || "development";
|
|
96
|
+
const lastSeen = fn.last_seen_at;
|
|
97
|
+
// Check function name match
|
|
98
|
+
const nameMatches = functionName.toLowerCase().includes(lowerQuery);
|
|
99
|
+
// Get latest snapshot and search types
|
|
100
|
+
const snapshot = (0, queries_1.getLatestSnapshot)(connection_1.db, functionId, env);
|
|
101
|
+
const fieldMatches = [];
|
|
102
|
+
if (snapshot) {
|
|
103
|
+
const argsType = tryParseJson(snapshot.args_type);
|
|
104
|
+
const returnType = tryParseJson(snapshot.return_type);
|
|
105
|
+
if (argsType) {
|
|
106
|
+
searchTypeNode(argsType, query, "args", fieldMatches);
|
|
107
|
+
}
|
|
108
|
+
if (returnType) {
|
|
109
|
+
searchTypeNode(returnType, query, "response", fieldMatches);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Include if function name matches or any fields match
|
|
113
|
+
if (nameMatches || fieldMatches.length > 0) {
|
|
114
|
+
results.push({
|
|
115
|
+
functionName,
|
|
116
|
+
module: moduleName,
|
|
117
|
+
environment,
|
|
118
|
+
lastSeen,
|
|
119
|
+
matches: nameMatches && fieldMatches.length === 0
|
|
120
|
+
? [{ path: "(function name)", kind: "name", typeName: undefined }]
|
|
121
|
+
: fieldMatches,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
res.json({
|
|
126
|
+
query,
|
|
127
|
+
total: results.length,
|
|
128
|
+
results,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
console.error("Search error:", err);
|
|
133
|
+
res.status(500).json({ error: "Internal server error" });
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
exports.default = router;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const express_1 = require("express");
|
|
4
|
+
const sse_broker_1 = require("../services/sse-broker");
|
|
5
|
+
const router = (0, express_1.Router)();
|
|
6
|
+
// GET / — SSE endpoint
|
|
7
|
+
router.get("/", (req, res) => {
|
|
8
|
+
const filter = req.query.filter;
|
|
9
|
+
sse_broker_1.sseBroker.addClient(res, filter);
|
|
10
|
+
});
|
|
11
|
+
exports.default = router;
|
|
@@ -0,0 +1,97 @@
|
|
|
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_differ_1 = require("../services/type-differ");
|
|
7
|
+
const router = (0, express_1.Router)();
|
|
8
|
+
// GET /:functionId — get type snapshots for a function
|
|
9
|
+
router.get("/:functionId", (req, res) => {
|
|
10
|
+
try {
|
|
11
|
+
const functionId = parseInt(req.params.functionId, 10);
|
|
12
|
+
if (isNaN(functionId)) {
|
|
13
|
+
res.status(400).json({ error: "Invalid functionId" });
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const { env, limit } = req.query;
|
|
17
|
+
const snapshots = (0, queries_1.listSnapshots)(connection_1.db, {
|
|
18
|
+
functionId,
|
|
19
|
+
env: env,
|
|
20
|
+
limit: limit ? parseInt(limit, 10) : undefined,
|
|
21
|
+
});
|
|
22
|
+
const parsed = snapshots.map((s) => ({
|
|
23
|
+
...s,
|
|
24
|
+
args_type: tryParseJson(s.args_type),
|
|
25
|
+
return_type: tryParseJson(s.return_type),
|
|
26
|
+
sample_input: tryParseJson(s.sample_input),
|
|
27
|
+
sample_output: tryParseJson(s.sample_output),
|
|
28
|
+
}));
|
|
29
|
+
res.json({ snapshots: parsed });
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
console.error("List types error:", err);
|
|
33
|
+
res.status(500).json({ error: "Internal server error" });
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
// GET /:functionId/diff — diff between two snapshots or envs
|
|
37
|
+
router.get("/:functionId/diff", (req, res) => {
|
|
38
|
+
try {
|
|
39
|
+
const functionId = parseInt(req.params.functionId, 10);
|
|
40
|
+
if (isNaN(functionId)) {
|
|
41
|
+
res.status(400).json({ error: "Invalid functionId" });
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const { from, to, fromEnv, toEnv } = req.query;
|
|
45
|
+
let fromSnapshot;
|
|
46
|
+
let toSnapshot;
|
|
47
|
+
if (from && to) {
|
|
48
|
+
// Diff by snapshot IDs
|
|
49
|
+
const stmt = connection_1.db.prepare(`SELECT * FROM type_snapshots WHERE id = ? AND function_id = ?`);
|
|
50
|
+
fromSnapshot = stmt.get(parseInt(from, 10), functionId);
|
|
51
|
+
toSnapshot = stmt.get(parseInt(to, 10), functionId);
|
|
52
|
+
}
|
|
53
|
+
else if (fromEnv && toEnv) {
|
|
54
|
+
// Diff between envs (latest snapshot in each)
|
|
55
|
+
const stmt = connection_1.db.prepare(`
|
|
56
|
+
SELECT * FROM type_snapshots
|
|
57
|
+
WHERE function_id = ? AND env = ?
|
|
58
|
+
ORDER BY observed_at DESC
|
|
59
|
+
LIMIT 1
|
|
60
|
+
`);
|
|
61
|
+
fromSnapshot = stmt.get(functionId, fromEnv);
|
|
62
|
+
toSnapshot = stmt.get(functionId, toEnv);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
res.status(400).json({ error: "Provide 'from' and 'to' snapshot IDs, or 'fromEnv' and 'toEnv'" });
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (!fromSnapshot || !toSnapshot) {
|
|
69
|
+
res.status(404).json({ error: "One or both snapshots not found" });
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const fromArgs = JSON.parse(fromSnapshot.args_type);
|
|
73
|
+
const toArgs = JSON.parse(toSnapshot.args_type);
|
|
74
|
+
const fromReturn = JSON.parse(fromSnapshot.return_type);
|
|
75
|
+
const toReturn = JSON.parse(toSnapshot.return_type);
|
|
76
|
+
const argsDiff = (0, type_differ_1.diffTypes)(fromArgs, toArgs, "args");
|
|
77
|
+
const returnDiff = (0, type_differ_1.diffTypes)(fromReturn, toReturn, "return");
|
|
78
|
+
res.json({
|
|
79
|
+
from: { id: fromSnapshot.id, env: fromSnapshot.env, observed_at: fromSnapshot.observed_at },
|
|
80
|
+
to: { id: toSnapshot.id, env: toSnapshot.env, observed_at: toSnapshot.observed_at },
|
|
81
|
+
diffs: [...argsDiff, ...returnDiff],
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
console.error("Type diff error:", err);
|
|
86
|
+
res.status(500).json({ error: "Internal server error" });
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
function tryParseJson(value) {
|
|
90
|
+
try {
|
|
91
|
+
return JSON.parse(value);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
exports.default = router;
|
package/dist/server.d.ts
ADDED
package/dist/server.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.app = void 0;
|
|
7
|
+
const express_1 = __importDefault(require("express"));
|
|
8
|
+
const cors_1 = __importDefault(require("cors"));
|
|
9
|
+
const ingest_1 = __importDefault(require("./routes/ingest"));
|
|
10
|
+
const functions_1 = __importDefault(require("./routes/functions"));
|
|
11
|
+
const types_1 = __importDefault(require("./routes/types"));
|
|
12
|
+
const errors_1 = __importDefault(require("./routes/errors"));
|
|
13
|
+
const tail_1 = __importDefault(require("./routes/tail"));
|
|
14
|
+
const codegen_1 = __importDefault(require("./routes/codegen"));
|
|
15
|
+
const mock_1 = __importDefault(require("./routes/mock"));
|
|
16
|
+
const diff_1 = __importDefault(require("./routes/diff"));
|
|
17
|
+
const dashboard_1 = __importDefault(require("./routes/dashboard"));
|
|
18
|
+
const coverage_1 = __importDefault(require("./routes/coverage"));
|
|
19
|
+
const audit_1 = __importDefault(require("./routes/audit"));
|
|
20
|
+
const search_1 = __importDefault(require("./routes/search"));
|
|
21
|
+
const app = (0, express_1.default)();
|
|
22
|
+
exports.app = app;
|
|
23
|
+
app.use((0, cors_1.default)());
|
|
24
|
+
app.use(express_1.default.json({ limit: "5mb" }));
|
|
25
|
+
app.use("/api/ingest", ingest_1.default);
|
|
26
|
+
app.use("/api/functions", functions_1.default);
|
|
27
|
+
app.use("/api/types", types_1.default);
|
|
28
|
+
app.use("/api/errors", errors_1.default);
|
|
29
|
+
app.use("/api/tail", tail_1.default);
|
|
30
|
+
app.use("/api/codegen", codegen_1.default);
|
|
31
|
+
app.use("/api/mock-config", mock_1.default);
|
|
32
|
+
app.use("/api/diff", diff_1.default);
|
|
33
|
+
app.use("/dashboard", dashboard_1.default);
|
|
34
|
+
app.use("/api/coverage", coverage_1.default);
|
|
35
|
+
app.use("/api/audit", audit_1.default);
|
|
36
|
+
app.use("/api/search", search_1.default);
|
|
37
|
+
// Health check
|
|
38
|
+
app.get("/api/health", (_req, res) => {
|
|
39
|
+
res.json({ ok: true, timestamp: new Date().toISOString() });
|
|
40
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { EventEmitter } from "events";
|
|
2
|
+
import { Response } from "express";
|
|
3
|
+
declare class SseBroker extends EventEmitter {
|
|
4
|
+
private clients;
|
|
5
|
+
addClient(res: Response, filter?: string): void;
|
|
6
|
+
removeClient(res: Response): void;
|
|
7
|
+
broadcast(event: string, data: unknown): void;
|
|
8
|
+
}
|
|
9
|
+
export declare const sseBroker: SseBroker;
|
|
10
|
+
export {};
|