@autonoma-ai/sdk 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/chunk-MR56EFU5.js +126 -0
- package/dist/chunk-MR56EFU5.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +142 -0
- package/dist/cli.js.map +1 -0
- package/dist/graph-DpqVvKaD.d.ts +107 -0
- package/dist/graph.d.ts +1 -0
- package/dist/graph.js +9 -0
- package/dist/graph.js.map +1 -0
- package/dist/index.d.ts +97 -0
- package/dist/index.js +510 -0
- package/dist/index.js.map +1 -0
- package/package.json +39 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// src/graph.ts
|
|
2
|
+
function topoSort(nodes, edges) {
|
|
3
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
4
|
+
const adj = /* @__PURE__ */ new Map();
|
|
5
|
+
for (const node of nodes) {
|
|
6
|
+
inDegree.set(node, 0);
|
|
7
|
+
adj.set(node, []);
|
|
8
|
+
}
|
|
9
|
+
const relevantEdges = edges.filter(
|
|
10
|
+
(e) => e.from !== e.to && nodes.includes(e.from) && nodes.includes(e.to)
|
|
11
|
+
);
|
|
12
|
+
for (const edge of relevantEdges) {
|
|
13
|
+
adj.get(edge.to).push(edge.from);
|
|
14
|
+
inDegree.set(edge.from, (inDegree.get(edge.from) ?? 0) + 1);
|
|
15
|
+
}
|
|
16
|
+
const queue = [];
|
|
17
|
+
for (const [node, deg] of inDegree) {
|
|
18
|
+
if (deg === 0) queue.push(node);
|
|
19
|
+
}
|
|
20
|
+
const sorted = [];
|
|
21
|
+
while (queue.length > 0) {
|
|
22
|
+
queue.sort();
|
|
23
|
+
const node = queue.shift();
|
|
24
|
+
sorted.push(node);
|
|
25
|
+
for (const neighbor of adj.get(node) ?? []) {
|
|
26
|
+
const newDeg = (inDegree.get(neighbor) ?? 1) - 1;
|
|
27
|
+
inDegree.set(neighbor, newDeg);
|
|
28
|
+
if (newDeg === 0) queue.push(neighbor);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const unsortedNodes = nodes.filter((n) => !sorted.includes(n));
|
|
32
|
+
const cycles = unsortedNodes.length > 0 ? findSCCs(unsortedNodes, relevantEdges) : [];
|
|
33
|
+
if (cycles.length === 0) return { sorted, cycles };
|
|
34
|
+
const cycleNodeSet = new Set(cycles.flat());
|
|
35
|
+
const stillUnsorted = unsortedNodes.filter((n) => !cycleNodeSet.has(n));
|
|
36
|
+
if (stillUnsorted.length > 0) {
|
|
37
|
+
const inDeg2 = /* @__PURE__ */ new Map();
|
|
38
|
+
const adj2 = /* @__PURE__ */ new Map();
|
|
39
|
+
for (const node of stillUnsorted) {
|
|
40
|
+
inDeg2.set(node, 0);
|
|
41
|
+
adj2.set(node, []);
|
|
42
|
+
}
|
|
43
|
+
const stillSet = new Set(stillUnsorted);
|
|
44
|
+
for (const edge of relevantEdges) {
|
|
45
|
+
if (stillSet.has(edge.from) && stillSet.has(edge.to)) {
|
|
46
|
+
adj2.get(edge.to).push(edge.from);
|
|
47
|
+
inDeg2.set(edge.from, (inDeg2.get(edge.from) ?? 0) + 1);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const queue2 = [];
|
|
51
|
+
for (const [node, deg] of inDeg2) {
|
|
52
|
+
if (deg === 0) queue2.push(node);
|
|
53
|
+
}
|
|
54
|
+
while (queue2.length > 0) {
|
|
55
|
+
queue2.sort();
|
|
56
|
+
const node = queue2.shift();
|
|
57
|
+
sorted.push(node);
|
|
58
|
+
for (const neighbor of adj2.get(node) ?? []) {
|
|
59
|
+
const newDeg = (inDeg2.get(neighbor) ?? 1) - 1;
|
|
60
|
+
inDeg2.set(neighbor, newDeg);
|
|
61
|
+
if (newDeg === 0) queue2.push(neighbor);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return { sorted, cycles };
|
|
66
|
+
}
|
|
67
|
+
function findSCCs(nodes, edges) {
|
|
68
|
+
const adj = /* @__PURE__ */ new Map();
|
|
69
|
+
const nodeSet = new Set(nodes);
|
|
70
|
+
for (const node of nodes) adj.set(node, []);
|
|
71
|
+
for (const edge of edges) {
|
|
72
|
+
if (nodeSet.has(edge.from) && nodeSet.has(edge.to)) {
|
|
73
|
+
adj.get(edge.to).push(edge.from);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
let index = 0;
|
|
77
|
+
const stack = [];
|
|
78
|
+
const onStack = /* @__PURE__ */ new Set();
|
|
79
|
+
const indices = /* @__PURE__ */ new Map();
|
|
80
|
+
const lowlinks = /* @__PURE__ */ new Map();
|
|
81
|
+
const sccs = [];
|
|
82
|
+
function strongConnect(v) {
|
|
83
|
+
indices.set(v, index);
|
|
84
|
+
lowlinks.set(v, index);
|
|
85
|
+
index++;
|
|
86
|
+
stack.push(v);
|
|
87
|
+
onStack.add(v);
|
|
88
|
+
for (const w of adj.get(v) ?? []) {
|
|
89
|
+
if (!indices.has(w)) {
|
|
90
|
+
strongConnect(w);
|
|
91
|
+
lowlinks.set(v, Math.min(lowlinks.get(v), lowlinks.get(w)));
|
|
92
|
+
} else if (onStack.has(w)) {
|
|
93
|
+
lowlinks.set(v, Math.min(lowlinks.get(v), indices.get(w)));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (lowlinks.get(v) === indices.get(v)) {
|
|
97
|
+
const scc = [];
|
|
98
|
+
let w;
|
|
99
|
+
do {
|
|
100
|
+
w = stack.pop();
|
|
101
|
+
onStack.delete(w);
|
|
102
|
+
scc.push(w);
|
|
103
|
+
} while (w !== v);
|
|
104
|
+
if (scc.length > 1) sccs.push(scc);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
for (const node of nodes) {
|
|
108
|
+
if (!indices.has(node)) strongConnect(node);
|
|
109
|
+
}
|
|
110
|
+
return sccs;
|
|
111
|
+
}
|
|
112
|
+
function findDeferrableEdge(cycle, edges) {
|
|
113
|
+
const cycleSet = new Set(cycle);
|
|
114
|
+
for (const edge of edges) {
|
|
115
|
+
if (cycleSet.has(edge.from) && cycleSet.has(edge.to) && edge.from !== edge.to && edge.nullable) {
|
|
116
|
+
return edge;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export {
|
|
123
|
+
topoSort,
|
|
124
|
+
findDeferrableEdge
|
|
125
|
+
};
|
|
126
|
+
//# sourceMappingURL=chunk-MR56EFU5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/graph.ts"],"sourcesContent":["import type { FKEdge } from './types'\n\nexport type { FKEdge }\n\nexport interface TopoSortResult {\n sorted: string[]\n cycles: string[][]\n}\n\n/**\n * Topological sort via Kahn's algorithm.\n * Returns sorted nodes and any strongly connected components (cycles).\n *\n * After detecting cycles, runs a second pass to sort nodes that depend on\n * cycle members (these aren't in cycles themselves but couldn't be sorted\n * while their cycle-member dependencies had non-zero in-degree).\n */\nexport function topoSort(nodes: string[], edges: FKEdge[]): TopoSortResult {\n const inDegree = new Map<string, number>()\n const adj = new Map<string, string[]>()\n\n for (const node of nodes) {\n inDegree.set(node, 0)\n adj.set(node, [])\n }\n\n // Filter to only edges between known nodes, skip self-referential\n const relevantEdges = edges.filter(\n (e) => e.from !== e.to && nodes.includes(e.from) && nodes.includes(e.to),\n )\n\n for (const edge of relevantEdges) {\n // edge.from depends on edge.to (from has the FK pointing to to)\n adj.get(edge.to)!.push(edge.from)\n inDegree.set(edge.from, (inDegree.get(edge.from) ?? 0) + 1)\n }\n\n // First pass: standard Kahn's\n const queue: string[] = []\n for (const [node, deg] of inDegree) {\n if (deg === 0) queue.push(node)\n }\n\n const sorted: string[] = []\n while (queue.length > 0) {\n queue.sort()\n const node = queue.shift()!\n sorted.push(node)\n for (const neighbor of adj.get(node) ?? []) {\n const newDeg = (inDegree.get(neighbor) ?? 1) - 1\n inDegree.set(neighbor, newDeg)\n if (newDeg === 0) queue.push(neighbor)\n }\n }\n\n // Find cycle nodes and their SCCs\n const unsortedNodes = nodes.filter((n) => !sorted.includes(n))\n const cycles = unsortedNodes.length > 0 ? findSCCs(unsortedNodes, relevantEdges) : []\n\n if (cycles.length === 0) return { sorted, cycles }\n\n // Second pass: treat cycle nodes as \"resolved\" and re-run Kahn's\n // for any remaining unsorted nodes that depend on cycle members\n const cycleNodeSet = new Set(cycles.flat())\n const stillUnsorted = unsortedNodes.filter((n) => !cycleNodeSet.has(n))\n\n if (stillUnsorted.length > 0) {\n // Reset in-degrees for unsorted non-cycle nodes, counting only edges\n // from other unsorted non-cycle nodes (cycle deps are resolved)\n const inDeg2 = new Map<string, number>()\n const adj2 = new Map<string, string[]>()\n\n for (const node of stillUnsorted) {\n inDeg2.set(node, 0)\n adj2.set(node, [])\n }\n\n const stillSet = new Set(stillUnsorted)\n for (const edge of relevantEdges) {\n if (stillSet.has(edge.from) && stillSet.has(edge.to)) {\n adj2.get(edge.to)!.push(edge.from)\n inDeg2.set(edge.from, (inDeg2.get(edge.from) ?? 0) + 1)\n }\n }\n\n const queue2: string[] = []\n for (const [node, deg] of inDeg2) {\n if (deg === 0) queue2.push(node)\n }\n\n while (queue2.length > 0) {\n queue2.sort()\n const node = queue2.shift()!\n sorted.push(node)\n for (const neighbor of adj2.get(node) ?? []) {\n const newDeg = (inDeg2.get(neighbor) ?? 1) - 1\n inDeg2.set(neighbor, newDeg)\n if (newDeg === 0) queue2.push(neighbor)\n }\n }\n }\n\n return { sorted, cycles }\n}\n\n/**\n * Tarjan's SCC algorithm to identify exact cycles among remaining nodes.\n */\nfunction findSCCs(nodes: string[], edges: FKEdge[]): string[][] {\n const adj = new Map<string, string[]>()\n const nodeSet = new Set(nodes)\n\n for (const node of nodes) adj.set(node, [])\n for (const edge of edges) {\n if (nodeSet.has(edge.from) && nodeSet.has(edge.to)) {\n adj.get(edge.to)!.push(edge.from)\n }\n }\n\n let index = 0\n const stack: string[] = []\n const onStack = new Set<string>()\n const indices = new Map<string, number>()\n const lowlinks = new Map<string, number>()\n const sccs: string[][] = []\n\n function strongConnect(v: string) {\n indices.set(v, index)\n lowlinks.set(v, index)\n index++\n stack.push(v)\n onStack.add(v)\n\n for (const w of adj.get(v) ?? []) {\n if (!indices.has(w)) {\n strongConnect(w)\n lowlinks.set(v, Math.min(lowlinks.get(v)!, lowlinks.get(w)!))\n } else if (onStack.has(w)) {\n lowlinks.set(v, Math.min(lowlinks.get(v)!, indices.get(w)!))\n }\n }\n\n if (lowlinks.get(v) === indices.get(v)) {\n const scc: string[] = []\n let w: string\n do {\n w = stack.pop()!\n onStack.delete(w)\n scc.push(w)\n } while (w !== v)\n if (scc.length > 1) sccs.push(scc)\n }\n }\n\n for (const node of nodes) {\n if (!indices.has(node)) strongConnect(node)\n }\n\n return sccs\n}\n\n/**\n * Find a nullable FK edge in a cycle that can be deferred.\n * Skips self-referential edges.\n */\nexport function findDeferrableEdge(\n cycle: string[],\n edges: FKEdge[],\n): FKEdge | null {\n const cycleSet = new Set(cycle)\n for (const edge of edges) {\n if (cycleSet.has(edge.from) && cycleSet.has(edge.to) && edge.from !== edge.to && edge.nullable) {\n return edge\n }\n }\n return null\n}\n"],"mappings":"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","names":[]}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { readFile, writeFile } from "fs/promises";
|
|
5
|
+
import { resolve } from "path";
|
|
6
|
+
var HELP = `
|
|
7
|
+
autonoma \u2014 Autonoma SDK CLI
|
|
8
|
+
|
|
9
|
+
Commands:
|
|
10
|
+
autonoma schema convert <dmmf.json> Convert Prisma DMMF to autonoma schema
|
|
11
|
+
|
|
12
|
+
Options:
|
|
13
|
+
--scope-field <name> Scope field name (default: "testRunId")
|
|
14
|
+
--pretty Pretty-print output
|
|
15
|
+
-o, --output <path> Write output to file instead of stdout
|
|
16
|
+
-h, --help Show this help
|
|
17
|
+
`.trim();
|
|
18
|
+
async function main() {
|
|
19
|
+
const args = process.argv.slice(2);
|
|
20
|
+
if (args.length === 0 || args.includes("-h") || args.includes("--help")) {
|
|
21
|
+
console.log(HELP);
|
|
22
|
+
process.exit(0);
|
|
23
|
+
}
|
|
24
|
+
const command = args[0];
|
|
25
|
+
switch (command) {
|
|
26
|
+
case "schema":
|
|
27
|
+
return await cmdSchema(args.slice(1));
|
|
28
|
+
default:
|
|
29
|
+
console.error(`Unknown command: ${command}`);
|
|
30
|
+
console.log(HELP);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function cmdSchema(args) {
|
|
35
|
+
const subcommand = args[0];
|
|
36
|
+
if (subcommand !== "convert") {
|
|
37
|
+
console.error("Usage: autonoma schema convert <dmmf.json> --scope-field <name>");
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
const flags = parseFlags(args.slice(1));
|
|
41
|
+
const positional = flags._positional;
|
|
42
|
+
if (positional.length < 1) {
|
|
43
|
+
console.error("Usage: autonoma schema convert <dmmf.json> --scope-field <name>");
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
const dmmfPath = resolve(positional[0]);
|
|
47
|
+
const scopeField = flags["--scope-field"] ?? "testRunId";
|
|
48
|
+
const dmmf = await readJSON(dmmfPath);
|
|
49
|
+
const schema = convertDMMFToSchema(dmmf, scopeField);
|
|
50
|
+
const json = JSON.stringify(schema, null, 2);
|
|
51
|
+
if (flags["-o"] || flags["--output"]) {
|
|
52
|
+
const outPath = resolve(flags["-o"] ?? flags["--output"]);
|
|
53
|
+
await writeFile(outPath, json + "\n");
|
|
54
|
+
console.error(`Schema written to ${outPath}`);
|
|
55
|
+
console.error(` ${schema.models.length} models, ${schema.edges.length} FK edges, scopeField: "${scopeField}"`);
|
|
56
|
+
} else {
|
|
57
|
+
console.log(json);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function convertDMMFToSchema(dmmf, scopeField) {
|
|
61
|
+
let dmmfModels;
|
|
62
|
+
if (dmmf.datamodel?.models) {
|
|
63
|
+
dmmfModels = dmmf.datamodel.models;
|
|
64
|
+
} else if (Array.isArray(dmmf.models)) {
|
|
65
|
+
dmmfModels = dmmf.models;
|
|
66
|
+
} else {
|
|
67
|
+
dmmfModels = Object.entries(dmmf.models).map(
|
|
68
|
+
([name, model]) => ({ ...model, name })
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
const models = [];
|
|
72
|
+
const edges = [];
|
|
73
|
+
for (const model of dmmfModels) {
|
|
74
|
+
const fields = [];
|
|
75
|
+
for (const field of model.fields) {
|
|
76
|
+
if (field.kind === "object") {
|
|
77
|
+
if (field.relationFromFields?.length) {
|
|
78
|
+
edges.push({
|
|
79
|
+
from: model.name,
|
|
80
|
+
to: field.type,
|
|
81
|
+
localField: field.relationFromFields[0],
|
|
82
|
+
foreignField: field.relationToFields?.[0] ?? "id",
|
|
83
|
+
nullable: !field.isRequired
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (field.kind === "scalar" || field.kind === "enum") {
|
|
89
|
+
fields.push({
|
|
90
|
+
name: field.name,
|
|
91
|
+
type: field.type,
|
|
92
|
+
isRequired: field.isRequired,
|
|
93
|
+
isId: field.isId,
|
|
94
|
+
hasDefault: field.hasDefaultValue
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
models.push({ name: model.name, fields });
|
|
99
|
+
}
|
|
100
|
+
return { models, edges, relations: [], scopeField };
|
|
101
|
+
}
|
|
102
|
+
async function readJSON(path) {
|
|
103
|
+
try {
|
|
104
|
+
const content = await readFile(path, "utf-8");
|
|
105
|
+
return JSON.parse(content);
|
|
106
|
+
} catch (err) {
|
|
107
|
+
if (err.code === "ENOENT") {
|
|
108
|
+
console.error(`File not found: ${path}`);
|
|
109
|
+
} else if (err instanceof SyntaxError) {
|
|
110
|
+
console.error(`Invalid JSON in ${path}: ${err.message}`);
|
|
111
|
+
} else {
|
|
112
|
+
console.error(`Error reading ${path}: ${err}`);
|
|
113
|
+
}
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function parseFlags(args) {
|
|
118
|
+
const result = { _positional: [] };
|
|
119
|
+
let i = 0;
|
|
120
|
+
while (i < args.length) {
|
|
121
|
+
const arg = args[i];
|
|
122
|
+
if (arg.startsWith("-")) {
|
|
123
|
+
const next = args[i + 1];
|
|
124
|
+
if (next && !next.startsWith("-")) {
|
|
125
|
+
result[arg] = next;
|
|
126
|
+
i += 2;
|
|
127
|
+
} else {
|
|
128
|
+
result[arg] = true;
|
|
129
|
+
i++;
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
result._positional.push(arg);
|
|
133
|
+
i++;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
main().catch((err) => {
|
|
139
|
+
console.error("Fatal:", err instanceof Error ? err.message : err);
|
|
140
|
+
process.exit(1);
|
|
141
|
+
});
|
|
142
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { readFile, writeFile } from 'node:fs/promises'\nimport { resolve } from 'node:path'\nimport type { SchemaInfo } from './types'\n\nconst HELP = `\nautonoma — Autonoma SDK CLI\n\nCommands:\n autonoma schema convert <dmmf.json> Convert Prisma DMMF to autonoma schema\n\nOptions:\n --scope-field <name> Scope field name (default: \"testRunId\")\n --pretty Pretty-print output\n -o, --output <path> Write output to file instead of stdout\n -h, --help Show this help\n`.trim()\n\nasync function main() {\n const args = process.argv.slice(2)\n\n if (args.length === 0 || args.includes('-h') || args.includes('--help')) {\n console.log(HELP)\n process.exit(0)\n }\n\n const command = args[0]\n\n switch (command) {\n case 'schema':\n return await cmdSchema(args.slice(1))\n default:\n console.error(`Unknown command: ${command}`)\n console.log(HELP)\n process.exit(1)\n }\n}\n\n// ── schema convert ────────────────────────────────────────────────────────\n\nasync function cmdSchema(args: string[]) {\n const subcommand = args[0]\n\n if (subcommand !== 'convert') {\n console.error('Usage: autonoma schema convert <dmmf.json> --scope-field <name>')\n process.exit(1)\n }\n\n const flags = parseFlags(args.slice(1))\n const positional = flags._positional\n\n if (positional.length < 1) {\n console.error('Usage: autonoma schema convert <dmmf.json> --scope-field <name>')\n process.exit(1)\n }\n\n const dmmfPath = resolve(positional[0]!)\n const scopeField = (flags['--scope-field'] as string) ?? 'testRunId'\n const dmmf = await readJSON<DMMFInput>(dmmfPath)\n\n const schema = convertDMMFToSchema(dmmf, scopeField)\n\n const json = JSON.stringify(schema, null, 2)\n\n if (flags['-o'] || flags['--output']) {\n const outPath = resolve((flags['-o'] ?? flags['--output']) as string)\n await writeFile(outPath, json + '\\n')\n console.error(`Schema written to ${outPath}`)\n console.error(` ${schema.models.length} models, ${schema.edges.length} FK edges, scopeField: \"${scopeField}\"`)\n } else {\n console.log(json)\n }\n}\n\n// ── DMMF conversion ───────────────────────────────────────────────────────\n\ninterface DMMFInput {\n models: Record<string, DMMFModel> | DMMFModel[]\n datamodel?: { models: DMMFModel[] }\n}\n\ninterface DMMFModel {\n name: string\n fields: DMMFField[]\n}\n\ninterface DMMFField {\n name: string\n type: string\n kind: string\n isRequired: boolean\n isId: boolean\n hasDefaultValue: boolean\n relationFromFields?: string[]\n relationToFields?: string[]\n}\n\nfunction convertDMMFToSchema(dmmf: DMMFInput, scopeField: string): SchemaInfo {\n let dmmfModels: DMMFModel[]\n\n if (dmmf.datamodel?.models) {\n dmmfModels = dmmf.datamodel.models\n } else if (Array.isArray(dmmf.models)) {\n dmmfModels = dmmf.models\n } else {\n dmmfModels = Object.entries(dmmf.models).map(\n ([name, model]) => ({ ...model, name }),\n )\n }\n\n const models: SchemaInfo['models'] = []\n const edges: SchemaInfo['edges'] = []\n\n for (const model of dmmfModels) {\n const fields: SchemaInfo['models'][number]['fields'] = []\n\n for (const field of model.fields) {\n if (field.kind === 'object') {\n if (field.relationFromFields?.length) {\n edges.push({\n from: model.name,\n to: field.type,\n localField: field.relationFromFields[0]!,\n foreignField: field.relationToFields?.[0] ?? 'id',\n nullable: !field.isRequired,\n })\n }\n continue\n }\n\n if (field.kind === 'scalar' || field.kind === 'enum') {\n fields.push({\n name: field.name,\n type: field.type,\n isRequired: field.isRequired,\n isId: field.isId,\n hasDefault: field.hasDefaultValue,\n })\n }\n }\n\n models.push({ name: model.name, fields })\n }\n\n return { models, edges, relations: [], scopeField }\n}\n\n// ── Utilities ─────────────────────────────────────────────────────────────\n\nasync function readJSON<T>(path: string): Promise<T> {\n try {\n const content = await readFile(path, 'utf-8')\n return JSON.parse(content) as T\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n console.error(`File not found: ${path}`)\n } else if (err instanceof SyntaxError) {\n console.error(`Invalid JSON in ${path}: ${err.message}`)\n } else {\n console.error(`Error reading ${path}: ${err}`)\n }\n process.exit(1)\n }\n}\n\ninterface ParsedFlags {\n [key: string]: string | boolean | string[]\n _positional: string[]\n}\n\nfunction parseFlags(args: string[]): ParsedFlags {\n const result: ParsedFlags = { _positional: [] }\n let i = 0\n while (i < args.length) {\n const arg = args[i]!\n if (arg.startsWith('-')) {\n const next = args[i + 1]\n if (next && !next.startsWith('-')) {\n result[arg] = next\n i += 2\n } else {\n result[arg] = true\n i++\n }\n } else {\n result._positional.push(arg)\n i++\n }\n }\n return result\n}\n\nmain().catch((err) => {\n console.error('Fatal:', err instanceof Error ? err.message : err)\n process.exit(1)\n})\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/** ORM adapter interface — implemented by @autonoma-ai/sdk-prisma, @autonoma-ai/sdk-drizzle, etc. */
|
|
2
|
+
interface OrmAdapter {
|
|
3
|
+
/** Return schema metadata for discover (models, fields, relationships) */
|
|
4
|
+
getSchema(): SchemaInfo;
|
|
5
|
+
/** Create entities from a resolved spec, return created records keyed by model */
|
|
6
|
+
createEntities(spec: Record<string, ResolvedEntitySpec>, context: CreateContext): Promise<Record<string, Record<string, unknown>[]>>;
|
|
7
|
+
/** Delete all data scoped to a value. Refs are provided for targeted cleanup of un-scoped models. */
|
|
8
|
+
teardown(scopeValue: string, refs?: Record<string, Record<string, unknown>[]>): Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
interface SchemaInfo {
|
|
11
|
+
models: ModelInfo[];
|
|
12
|
+
edges: FKEdge[];
|
|
13
|
+
relations: SchemaRelation[];
|
|
14
|
+
scopeField: string;
|
|
15
|
+
}
|
|
16
|
+
/** Maps a parent's relation field name to the child model and its FK */
|
|
17
|
+
interface SchemaRelation {
|
|
18
|
+
parentModel: string;
|
|
19
|
+
childModel: string;
|
|
20
|
+
parentField: string;
|
|
21
|
+
childField: string;
|
|
22
|
+
}
|
|
23
|
+
interface ModelInfo {
|
|
24
|
+
name: string;
|
|
25
|
+
fields: FieldInfo[];
|
|
26
|
+
}
|
|
27
|
+
interface FieldInfo {
|
|
28
|
+
name: string;
|
|
29
|
+
type: string;
|
|
30
|
+
isRequired: boolean;
|
|
31
|
+
isId: boolean;
|
|
32
|
+
hasDefault: boolean;
|
|
33
|
+
}
|
|
34
|
+
interface FKEdge {
|
|
35
|
+
from: string;
|
|
36
|
+
to: string;
|
|
37
|
+
localField: string;
|
|
38
|
+
foreignField: string;
|
|
39
|
+
nullable: boolean;
|
|
40
|
+
}
|
|
41
|
+
interface ResolvedEntitySpec {
|
|
42
|
+
count: number;
|
|
43
|
+
fields: Record<string, unknown>[];
|
|
44
|
+
batch?: boolean;
|
|
45
|
+
}
|
|
46
|
+
interface CreateContext {
|
|
47
|
+
testRunId: string;
|
|
48
|
+
refs: Record<string, Record<string, unknown>[]>;
|
|
49
|
+
}
|
|
50
|
+
/** Scenario sent inline in the `up` request body */
|
|
51
|
+
interface ScenarioDefinition {
|
|
52
|
+
/** Nested tree: model name → array of node objects with nested children */
|
|
53
|
+
create: Record<string, Record<string, unknown>[]>;
|
|
54
|
+
}
|
|
55
|
+
interface HandlerConfig {
|
|
56
|
+
adapter: OrmAdapter;
|
|
57
|
+
/** Shared secret — known by both you and Autonoma. Used to verify HMAC signatures on incoming requests. */
|
|
58
|
+
sharedSecret: string;
|
|
59
|
+
/** Internal secret — only you know this. Used to sign the refs JWT token. Autonoma never sees it. */
|
|
60
|
+
signingSecret: string;
|
|
61
|
+
allowProduction?: boolean;
|
|
62
|
+
auth?: (user: Record<string, unknown>) => Promise<AuthResult> | AuthResult;
|
|
63
|
+
}
|
|
64
|
+
interface AuthResult {
|
|
65
|
+
token: string;
|
|
66
|
+
[key: string]: unknown;
|
|
67
|
+
}
|
|
68
|
+
interface HandlerRequest {
|
|
69
|
+
body: string;
|
|
70
|
+
headers: Record<string, string>;
|
|
71
|
+
}
|
|
72
|
+
interface HandlerResponse {
|
|
73
|
+
status: number;
|
|
74
|
+
body: Record<string, unknown>;
|
|
75
|
+
}
|
|
76
|
+
interface DiscoverResponse {
|
|
77
|
+
schema: SchemaInfo;
|
|
78
|
+
}
|
|
79
|
+
interface UpResponse {
|
|
80
|
+
auth: AuthResult;
|
|
81
|
+
refs: Record<string, Record<string, unknown>[]>;
|
|
82
|
+
refsToken: string;
|
|
83
|
+
}
|
|
84
|
+
interface DownResponse {
|
|
85
|
+
ok: boolean;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
interface TopoSortResult {
|
|
89
|
+
sorted: string[];
|
|
90
|
+
cycles: string[][];
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Topological sort via Kahn's algorithm.
|
|
94
|
+
* Returns sorted nodes and any strongly connected components (cycles).
|
|
95
|
+
*
|
|
96
|
+
* After detecting cycles, runs a second pass to sort nodes that depend on
|
|
97
|
+
* cycle members (these aren't in cycles themselves but couldn't be sorted
|
|
98
|
+
* while their cycle-member dependencies had non-zero in-degree).
|
|
99
|
+
*/
|
|
100
|
+
declare function topoSort(nodes: string[], edges: FKEdge[]): TopoSortResult;
|
|
101
|
+
/**
|
|
102
|
+
* Find a nullable FK edge in a cycle that can be deferred.
|
|
103
|
+
* Skips self-referential edges.
|
|
104
|
+
*/
|
|
105
|
+
declare function findDeferrableEdge(cycle: string[], edges: FKEdge[]): FKEdge | null;
|
|
106
|
+
|
|
107
|
+
export { type AuthResult as A, type CreateContext as C, type DiscoverResponse as D, type FKEdge as F, type HandlerConfig as H, type ModelInfo as M, type OrmAdapter as O, type ResolvedEntitySpec as R, type SchemaInfo as S, type TopoSortResult as T, type UpResponse as U, type HandlerRequest as a, type HandlerResponse as b, type ScenarioDefinition as c, type DownResponse as d, type FieldInfo as e, type SchemaRelation as f, findDeferrableEdge as g, topoSort as t };
|
package/dist/graph.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { F as FKEdge, T as TopoSortResult, g as findDeferrableEdge, t as topoSort } from './graph-DpqVvKaD.js';
|
package/dist/graph.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { H as HandlerConfig, a as HandlerRequest, b as HandlerResponse, S as SchemaInfo, O as OrmAdapter, c as ScenarioDefinition } from './graph-DpqVvKaD.js';
|
|
2
|
+
export { A as AuthResult, C as CreateContext, D as DiscoverResponse, d as DownResponse, F as FKEdge, e as FieldInfo, M as ModelInfo, R as ResolvedEntitySpec, f as SchemaRelation, U as UpResponse, g as findDeferrableEdge, t as topoSort } from './graph-DpqVvKaD.js';
|
|
3
|
+
|
|
4
|
+
declare function handleRequest(config: HandlerConfig, req: HandlerRequest): Promise<HandlerResponse>;
|
|
5
|
+
|
|
6
|
+
declare function signBody(body: string, secret: string): string;
|
|
7
|
+
declare function verifySignature(body: string, signature: string, secret: string): boolean;
|
|
8
|
+
|
|
9
|
+
interface RefsPayload {
|
|
10
|
+
refs: Record<string, Record<string, unknown>[]>;
|
|
11
|
+
testRunId: string;
|
|
12
|
+
environment: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Sign refs into a JWT-like token (header.payload.signature).
|
|
16
|
+
* Uses HMAC-SHA256 — not a full JWT library to avoid dependencies.
|
|
17
|
+
*/
|
|
18
|
+
declare function signRefs(payload: RefsPayload, secret: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Verify and decode a refs token. Returns the payload or throws.
|
|
21
|
+
*/
|
|
22
|
+
declare function verifyRefs(token: string, secret: string): RefsPayload;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Compute a stable 16-char hex fingerprint of a scenario definition.
|
|
26
|
+
* Uses sha256 of the JSON-serialized spec with sorted keys.
|
|
27
|
+
*/
|
|
28
|
+
declare function fingerprint(value: unknown): string;
|
|
29
|
+
|
|
30
|
+
interface TemplateContext {
|
|
31
|
+
testRunId: string;
|
|
32
|
+
index: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Resolve all {{...}} expressions in a value. Handles strings, objects, and arrays recursively.
|
|
36
|
+
*/
|
|
37
|
+
declare function resolveTemplate(value: unknown, ctx: TemplateContext): unknown;
|
|
38
|
+
|
|
39
|
+
/** A create operation produced by the tree resolver */
|
|
40
|
+
interface CreateOp {
|
|
41
|
+
model: string;
|
|
42
|
+
fields: Record<string, unknown>;
|
|
43
|
+
tempId: string;
|
|
44
|
+
batch: boolean;
|
|
45
|
+
}
|
|
46
|
+
/** Result of resolving a tree scenario */
|
|
47
|
+
interface ResolvedTree {
|
|
48
|
+
ops: CreateOp[];
|
|
49
|
+
aliases: Map<string, string>;
|
|
50
|
+
}
|
|
51
|
+
/** A resolved reference to another node's id */
|
|
52
|
+
interface RefNode {
|
|
53
|
+
_ref: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Resolve a nested scenario tree into an ordered list of create operations.
|
|
57
|
+
*
|
|
58
|
+
* Walks depth-first. Parent-child FKs are wired automatically.
|
|
59
|
+
* Handles both directions:
|
|
60
|
+
* - FK on child (Application.organizationId → Organization): set child FK to parent ID
|
|
61
|
+
* - FK on parent (Member.userId → User): create child first, set parent FK to child ID
|
|
62
|
+
*/
|
|
63
|
+
declare function resolveTree(create: Record<string, Record<string, unknown>[]>, schema: SchemaInfo, testRunId: string): ResolvedTree;
|
|
64
|
+
|
|
65
|
+
interface CheckResult {
|
|
66
|
+
valid: boolean;
|
|
67
|
+
phase: 'up' | 'down' | 'ok';
|
|
68
|
+
errors: CheckError[];
|
|
69
|
+
timing?: {
|
|
70
|
+
upMs: number;
|
|
71
|
+
downMs: number;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
interface CheckError {
|
|
75
|
+
phase: 'up' | 'down';
|
|
76
|
+
message: string;
|
|
77
|
+
fix?: string;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Dry-run a scenario against a real database.
|
|
81
|
+
* Runs the full up → down cycle and returns structured errors.
|
|
82
|
+
*/
|
|
83
|
+
declare function checkScenario(adapter: OrmAdapter, scenario: ScenarioDefinition, options?: {
|
|
84
|
+
sharedSecret?: string;
|
|
85
|
+
signingSecret?: string;
|
|
86
|
+
auth?: HandlerConfig['auth'];
|
|
87
|
+
}): Promise<CheckResult>;
|
|
88
|
+
/**
|
|
89
|
+
* Check multiple scenarios sequentially.
|
|
90
|
+
*/
|
|
91
|
+
declare function checkAllScenarios(adapter: OrmAdapter, scenarios: ScenarioDefinition[], options?: {
|
|
92
|
+
sharedSecret?: string;
|
|
93
|
+
signingSecret?: string;
|
|
94
|
+
auth?: HandlerConfig['auth'];
|
|
95
|
+
}): Promise<CheckResult[]>;
|
|
96
|
+
|
|
97
|
+
export { type CheckError, type CheckResult, type CreateOp, HandlerConfig, HandlerRequest, HandlerResponse, OrmAdapter, type RefNode, type ResolvedTree, ScenarioDefinition, SchemaInfo, type TemplateContext, checkAllScenarios, checkScenario, fingerprint, handleRequest, resolveTemplate, resolveTree, signBody, signRefs, verifyRefs, verifySignature };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
import {
|
|
2
|
+
findDeferrableEdge,
|
|
3
|
+
topoSort
|
|
4
|
+
} from "./chunk-MR56EFU5.js";
|
|
5
|
+
|
|
6
|
+
// src/hmac.ts
|
|
7
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
8
|
+
function signBody(body, secret) {
|
|
9
|
+
return createHmac("sha256", secret).update(body).digest("hex");
|
|
10
|
+
}
|
|
11
|
+
function verifySignature(body, signature, secret) {
|
|
12
|
+
const expected = signBody(body, secret);
|
|
13
|
+
if (expected.length !== signature.length) return false;
|
|
14
|
+
return timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// src/refs.ts
|
|
18
|
+
import { createHmac as createHmac2 } from "crypto";
|
|
19
|
+
function signRefs(payload, secret) {
|
|
20
|
+
const header = base64url({ alg: "HS256", typ: "REFS" });
|
|
21
|
+
const body = base64url(payload);
|
|
22
|
+
const signature = hmac(`${header}.${body}`, secret);
|
|
23
|
+
return `${header}.${body}.${signature}`;
|
|
24
|
+
}
|
|
25
|
+
function verifyRefs(token, secret) {
|
|
26
|
+
const parts = token.split(".");
|
|
27
|
+
if (parts.length !== 3) throw new Error("malformed token");
|
|
28
|
+
const [header, body, signature] = parts;
|
|
29
|
+
const expected = hmac(`${header}.${body}`, secret);
|
|
30
|
+
if (expected !== signature) throw new Error("signature mismatch");
|
|
31
|
+
return JSON.parse(Buffer.from(body, "base64url").toString());
|
|
32
|
+
}
|
|
33
|
+
function base64url(obj) {
|
|
34
|
+
return Buffer.from(JSON.stringify(obj)).toString("base64url");
|
|
35
|
+
}
|
|
36
|
+
function hmac(data, secret) {
|
|
37
|
+
return createHmac2("sha256", secret).update(data).digest("base64url");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// src/template.ts
|
|
41
|
+
var TEMPLATE_RE = /\{\{(.+?)\}\}/g;
|
|
42
|
+
function resolveTemplate(value, ctx) {
|
|
43
|
+
if (typeof value === "string") return resolveString(value, ctx);
|
|
44
|
+
if (Array.isArray(value)) return value.map((v) => resolveTemplate(v, ctx));
|
|
45
|
+
if (value && typeof value === "object") {
|
|
46
|
+
const result = {};
|
|
47
|
+
for (const [k, v] of Object.entries(value)) {
|
|
48
|
+
result[k] = resolveTemplate(v, ctx);
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
function resolveString(str, ctx) {
|
|
55
|
+
const fullMatch = str.match(/^\{\{(.+?)\}\}$/);
|
|
56
|
+
if (fullMatch) {
|
|
57
|
+
return evaluateExpression(fullMatch[1], ctx);
|
|
58
|
+
}
|
|
59
|
+
return str.replace(TEMPLATE_RE, (_, expr) => {
|
|
60
|
+
const val = evaluateExpression(expr, ctx);
|
|
61
|
+
return String(val);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function evaluateExpression(expr, ctx) {
|
|
65
|
+
expr = expr.trim();
|
|
66
|
+
if (expr === "testRunId") return ctx.testRunId;
|
|
67
|
+
if (expr === "index") return ctx.index;
|
|
68
|
+
if (expr === "index1") return ctx.index + 1;
|
|
69
|
+
const cycleMatch = expr.match(/^cycle\(\[(.+)\]\)$/);
|
|
70
|
+
if (cycleMatch) {
|
|
71
|
+
const items = parseArrayLiteral(cycleMatch[1]);
|
|
72
|
+
return items[ctx.index % items.length];
|
|
73
|
+
}
|
|
74
|
+
const pickMatch = expr.match(/^pick\(\[(.+)\]\)$/);
|
|
75
|
+
if (pickMatch) {
|
|
76
|
+
const items = parseArrayLiteral(pickMatch[1]);
|
|
77
|
+
return items[Math.floor(Math.random() * items.length)];
|
|
78
|
+
}
|
|
79
|
+
const randIntMatch = expr.match(/^random\.int\((\d+),\s*(\d+)\)$/);
|
|
80
|
+
if (randIntMatch) {
|
|
81
|
+
const min = parseInt(randIntMatch[1], 10);
|
|
82
|
+
const max = parseInt(randIntMatch[2], 10);
|
|
83
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
84
|
+
}
|
|
85
|
+
const randFloatMatch = expr.match(/^random\.float\((\d+(?:\.\d+)?),\s*(\d+(?:\.\d+)?)\)$/);
|
|
86
|
+
if (randFloatMatch) {
|
|
87
|
+
const min = parseFloat(randFloatMatch[1]);
|
|
88
|
+
const max = parseFloat(randFloatMatch[2]);
|
|
89
|
+
return Math.random() * (max - min) + min;
|
|
90
|
+
}
|
|
91
|
+
if (expr === "now()") return (/* @__PURE__ */ new Date()).toISOString();
|
|
92
|
+
const daysAgoMatch = expr.match(/^daysAgo\((\d+)\)$/);
|
|
93
|
+
if (daysAgoMatch) {
|
|
94
|
+
const d = /* @__PURE__ */ new Date();
|
|
95
|
+
d.setDate(d.getDate() - parseInt(daysAgoMatch[1], 10));
|
|
96
|
+
return d.toISOString();
|
|
97
|
+
}
|
|
98
|
+
throw new Error(`Template error: unknown expression '${expr}'`);
|
|
99
|
+
}
|
|
100
|
+
function parseArrayLiteral(raw) {
|
|
101
|
+
return raw.split(",").map((s) => {
|
|
102
|
+
s = s.trim();
|
|
103
|
+
if (s.startsWith("'") && s.endsWith("'") || s.startsWith('"') && s.endsWith('"')) {
|
|
104
|
+
return s.slice(1, -1);
|
|
105
|
+
}
|
|
106
|
+
return s;
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// src/tree.ts
|
|
111
|
+
var RESERVED_KEYS = /* @__PURE__ */ new Set(["_alias", "_ref", "_count", "_batch"]);
|
|
112
|
+
function resolveTree(create, schema, testRunId) {
|
|
113
|
+
const relationByParentField = /* @__PURE__ */ new Map();
|
|
114
|
+
for (const rel of schema.relations) {
|
|
115
|
+
relationByParentField.set(`${rel.parentModel}.${rel.parentField}`, rel);
|
|
116
|
+
}
|
|
117
|
+
const fkOnParent = /* @__PURE__ */ new Set();
|
|
118
|
+
for (const rel of schema.relations) {
|
|
119
|
+
const edge = schema.edges.find(
|
|
120
|
+
(e) => e.localField === rel.childField && (e.from === rel.parentModel || e.from === rel.childModel)
|
|
121
|
+
);
|
|
122
|
+
if (edge && edge.from === rel.parentModel) {
|
|
123
|
+
fkOnParent.add(`${rel.parentModel}.${rel.parentField}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
127
|
+
const ops = [];
|
|
128
|
+
let tempCounter = 0;
|
|
129
|
+
function makeTempId(model) {
|
|
130
|
+
return `__temp_${model}_${tempCounter++}`;
|
|
131
|
+
}
|
|
132
|
+
function walkNode(modelName, node, parentTempId, parentRelation, parentFkOnParent, index) {
|
|
133
|
+
const fields = {};
|
|
134
|
+
const preChildren = [];
|
|
135
|
+
const postChildren = [];
|
|
136
|
+
const alias = node._alias;
|
|
137
|
+
const tempId = makeTempId(modelName);
|
|
138
|
+
for (const [key, value] of Object.entries(node)) {
|
|
139
|
+
if (RESERVED_KEYS.has(key)) continue;
|
|
140
|
+
const relation = relationByParentField.get(`${modelName}.${key}`);
|
|
141
|
+
if (relation) {
|
|
142
|
+
const isOnParent = fkOnParent.has(`${modelName}.${key}`);
|
|
143
|
+
if (isOnParent) {
|
|
144
|
+
preChildren.push({ relation, value, fkOnParent: true });
|
|
145
|
+
} else {
|
|
146
|
+
postChildren.push({ relation, value, fkOnParent: false });
|
|
147
|
+
}
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (value && typeof value === "object" && "_ref" in value) {
|
|
151
|
+
const refAlias = value._ref;
|
|
152
|
+
const refTempId = aliases.get(refAlias);
|
|
153
|
+
if (!refTempId) {
|
|
154
|
+
throw new Error(`_ref "${refAlias}" not found. Ensure the referenced node has _alias and is created before this one.`);
|
|
155
|
+
}
|
|
156
|
+
fields[key] = refTempId;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
const ctx = { testRunId, index };
|
|
160
|
+
fields[key] = resolveTemplate(value, ctx);
|
|
161
|
+
}
|
|
162
|
+
if (parentRelation && parentTempId && !parentFkOnParent) {
|
|
163
|
+
fields[parentRelation.childField] = parentTempId;
|
|
164
|
+
}
|
|
165
|
+
for (const { relation, value, fkOnParent: isOnParent } of preChildren) {
|
|
166
|
+
if (Array.isArray(value)) {
|
|
167
|
+
for (let i = 0; i < value.length; i++) {
|
|
168
|
+
const childTempId = walkNode(relation.childModel, value[i], tempId, relation, true, i);
|
|
169
|
+
fields[relation.childField] = childTempId;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
ops.push({ model: modelName, fields, tempId, batch: false });
|
|
174
|
+
if (alias) aliases.set(alias, tempId);
|
|
175
|
+
for (const { relation, value } of postChildren) {
|
|
176
|
+
if (Array.isArray(value)) {
|
|
177
|
+
for (let i = 0; i < value.length; i++) {
|
|
178
|
+
walkNode(relation.childModel, value[i], tempId, relation, false, i);
|
|
179
|
+
}
|
|
180
|
+
} else if (value && typeof value === "object" && "_count" in value) {
|
|
181
|
+
const bulk = value;
|
|
182
|
+
const count = bulk._count;
|
|
183
|
+
const isBatch = bulk._batch ?? false;
|
|
184
|
+
for (let i = 0; i < count; i++) {
|
|
185
|
+
const bulkFields = {};
|
|
186
|
+
for (const [k, v] of Object.entries(bulk)) {
|
|
187
|
+
if (k === "_count" || k === "_batch") continue;
|
|
188
|
+
const ctx = { testRunId, index: i };
|
|
189
|
+
bulkFields[k] = resolveTemplate(v, ctx);
|
|
190
|
+
}
|
|
191
|
+
bulkFields[relation.childField] = tempId;
|
|
192
|
+
ops.push({ model: relation.childModel, fields: bulkFields, tempId: makeTempId(relation.childModel), batch: isBatch });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return tempId;
|
|
197
|
+
}
|
|
198
|
+
for (const [modelName, nodes] of Object.entries(create)) {
|
|
199
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
200
|
+
walkNode(modelName, nodes[i], null, null, false, i);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return { ops, aliases };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// src/errors.ts
|
|
207
|
+
var AutonomaError = class extends Error {
|
|
208
|
+
constructor(message, code, status) {
|
|
209
|
+
super(message);
|
|
210
|
+
this.code = code;
|
|
211
|
+
this.status = status;
|
|
212
|
+
this.name = "AutonomaError";
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
var Errors = {
|
|
216
|
+
unknownAction(action) {
|
|
217
|
+
return new AutonomaError(
|
|
218
|
+
`Unknown action: ${action}`,
|
|
219
|
+
"UNKNOWN_ACTION",
|
|
220
|
+
400
|
|
221
|
+
);
|
|
222
|
+
},
|
|
223
|
+
unknownEnvironment(name) {
|
|
224
|
+
return new AutonomaError(
|
|
225
|
+
`Unknown environment: ${name}`,
|
|
226
|
+
"UNKNOWN_ENVIRONMENT",
|
|
227
|
+
400
|
|
228
|
+
);
|
|
229
|
+
},
|
|
230
|
+
invalidSignature() {
|
|
231
|
+
return new AutonomaError(
|
|
232
|
+
"Invalid HMAC signature",
|
|
233
|
+
"INVALID_SIGNATURE",
|
|
234
|
+
401
|
|
235
|
+
);
|
|
236
|
+
},
|
|
237
|
+
invalidRefsToken(reason) {
|
|
238
|
+
return new AutonomaError(
|
|
239
|
+
`Invalid refs token: ${reason}`,
|
|
240
|
+
"INVALID_REFS_TOKEN",
|
|
241
|
+
403
|
|
242
|
+
);
|
|
243
|
+
},
|
|
244
|
+
productionBlocked() {
|
|
245
|
+
return new AutonomaError(
|
|
246
|
+
"Environment factory is disabled in production",
|
|
247
|
+
"PRODUCTION_BLOCKED",
|
|
248
|
+
404
|
|
249
|
+
);
|
|
250
|
+
},
|
|
251
|
+
invalidBody(reason) {
|
|
252
|
+
return new AutonomaError(
|
|
253
|
+
`Invalid request body: ${reason}`,
|
|
254
|
+
"INVALID_BODY",
|
|
255
|
+
400
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
// src/handler.ts
|
|
261
|
+
async function handleRequest(config, req) {
|
|
262
|
+
try {
|
|
263
|
+
if (config.sharedSecret === config.signingSecret) {
|
|
264
|
+
throw new AutonomaError(
|
|
265
|
+
"sharedSecret and signingSecret must be different. The shared secret is known by Autonoma; the signing secret must be private.",
|
|
266
|
+
"SAME_SECRETS",
|
|
267
|
+
500
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
if (!config.allowProduction && process.env.NODE_ENV === "production") {
|
|
271
|
+
throw Errors.productionBlocked();
|
|
272
|
+
}
|
|
273
|
+
const signature = req.headers["x-signature"] ?? req.headers["X-Signature"] ?? "";
|
|
274
|
+
if (!verifySignature(req.body, signature, config.sharedSecret)) {
|
|
275
|
+
throw Errors.invalidSignature();
|
|
276
|
+
}
|
|
277
|
+
let body;
|
|
278
|
+
try {
|
|
279
|
+
body = JSON.parse(req.body);
|
|
280
|
+
} catch {
|
|
281
|
+
throw Errors.invalidBody("invalid JSON");
|
|
282
|
+
}
|
|
283
|
+
const action = body.action;
|
|
284
|
+
if (!action) throw Errors.invalidBody("missing action");
|
|
285
|
+
switch (action) {
|
|
286
|
+
case "discover":
|
|
287
|
+
return handleDiscover(config);
|
|
288
|
+
case "up":
|
|
289
|
+
return await handleUp(config, body);
|
|
290
|
+
case "down":
|
|
291
|
+
return await handleDown(config, body);
|
|
292
|
+
default:
|
|
293
|
+
throw Errors.unknownAction(action);
|
|
294
|
+
}
|
|
295
|
+
} catch (err) {
|
|
296
|
+
if (err instanceof AutonomaError) {
|
|
297
|
+
return { status: err.status, body: { error: err.message, code: err.code } };
|
|
298
|
+
}
|
|
299
|
+
const message = err instanceof Error ? err.message : "Internal error";
|
|
300
|
+
return { status: 500, body: { error: message, code: "INTERNAL_ERROR" } };
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
function handleDiscover(config) {
|
|
304
|
+
const schema = config.adapter.getSchema();
|
|
305
|
+
return { status: 200, body: { schema } };
|
|
306
|
+
}
|
|
307
|
+
async function handleUp(config, body) {
|
|
308
|
+
const create = body.create;
|
|
309
|
+
if (!create) throw Errors.invalidBody('missing "create" in request body');
|
|
310
|
+
const testRunId = body.testRunId ?? crypto.randomUUID();
|
|
311
|
+
const schema = config.adapter.getSchema();
|
|
312
|
+
const tree = resolveTree(create, schema, testRunId);
|
|
313
|
+
const refs = {};
|
|
314
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
315
|
+
let i = 0;
|
|
316
|
+
while (i < tree.ops.length) {
|
|
317
|
+
const op = tree.ops[i];
|
|
318
|
+
const model = op.model;
|
|
319
|
+
const batch = [op];
|
|
320
|
+
while (i + 1 < tree.ops.length && tree.ops[i + 1].model === model && tree.ops[i + 1].batch === op.batch) {
|
|
321
|
+
i++;
|
|
322
|
+
batch.push(tree.ops[i]);
|
|
323
|
+
}
|
|
324
|
+
const resolvedFields = batch.map((b) => {
|
|
325
|
+
const fields = { ...b.fields };
|
|
326
|
+
delete fields.id;
|
|
327
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
328
|
+
if (typeof value === "string" && value.startsWith("__temp_")) {
|
|
329
|
+
const realId = idMap.get(value);
|
|
330
|
+
if (realId) fields[key] = realId;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
const scopeEdge = schema.edges.find(
|
|
334
|
+
(e) => e.from === model && e.localField.toLowerCase() === schema.scopeField.toLowerCase() && e.from !== e.to
|
|
335
|
+
);
|
|
336
|
+
if (scopeEdge && !(scopeEdge.localField in fields)) {
|
|
337
|
+
const scopeVal = detectScopeValue(refs, schema.scopeField);
|
|
338
|
+
if (scopeVal) fields[scopeEdge.localField] = scopeVal;
|
|
339
|
+
}
|
|
340
|
+
return fields;
|
|
341
|
+
});
|
|
342
|
+
const spec = {
|
|
343
|
+
[model]: { count: resolvedFields.length, fields: resolvedFields, batch: op.batch }
|
|
344
|
+
};
|
|
345
|
+
const context = { testRunId, refs };
|
|
346
|
+
const created = await config.adapter.createEntities(spec, context);
|
|
347
|
+
const records = created[model] ?? [];
|
|
348
|
+
if (!refs[model]) refs[model] = [];
|
|
349
|
+
refs[model].push(...records);
|
|
350
|
+
for (let j = 0; j < batch.length; j++) {
|
|
351
|
+
const record = records[j];
|
|
352
|
+
if (record && typeof record.id === "string") {
|
|
353
|
+
idMap.set(batch[j].tempId, record.id);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
i++;
|
|
357
|
+
}
|
|
358
|
+
const scopeValue = detectScopeValue(refs, schema.scopeField) ?? testRunId;
|
|
359
|
+
const firstUser = findFirstUser(refs);
|
|
360
|
+
let auth = { token: "" };
|
|
361
|
+
if (config.auth && firstUser) {
|
|
362
|
+
auth = await config.auth(firstUser);
|
|
363
|
+
}
|
|
364
|
+
const refsToken = signRefs(
|
|
365
|
+
{ refs, testRunId: scopeValue, environment: "" },
|
|
366
|
+
config.signingSecret
|
|
367
|
+
);
|
|
368
|
+
return { status: 200, body: { auth, refs, refsToken } };
|
|
369
|
+
}
|
|
370
|
+
async function handleDown(config, body) {
|
|
371
|
+
const refsToken = body.refsToken;
|
|
372
|
+
if (!refsToken) throw Errors.invalidBody("missing refsToken");
|
|
373
|
+
let payload;
|
|
374
|
+
try {
|
|
375
|
+
payload = verifyRefs(refsToken, config.signingSecret);
|
|
376
|
+
} catch (err) {
|
|
377
|
+
const message = err instanceof Error ? err.message : "invalid token";
|
|
378
|
+
throw Errors.invalidRefsToken(message);
|
|
379
|
+
}
|
|
380
|
+
await config.adapter.teardown(payload.testRunId, payload.refs);
|
|
381
|
+
return { status: 200, body: { ok: true } };
|
|
382
|
+
}
|
|
383
|
+
function findFirstUser(refs) {
|
|
384
|
+
for (const [model, records] of Object.entries(refs)) {
|
|
385
|
+
if (model.toLowerCase() === "user" && records.length > 0) {
|
|
386
|
+
return records[0];
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
function detectScopeValue(refs, scopeField) {
|
|
392
|
+
const scopeLower = scopeField.toLowerCase();
|
|
393
|
+
for (const records of Object.values(refs)) {
|
|
394
|
+
for (const record of records) {
|
|
395
|
+
for (const [key, value] of Object.entries(record)) {
|
|
396
|
+
if (key.toLowerCase() === scopeLower && typeof value === "string") {
|
|
397
|
+
return value;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return null;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// src/fingerprint.ts
|
|
406
|
+
import { createHash } from "crypto";
|
|
407
|
+
function fingerprint(value) {
|
|
408
|
+
const json = JSON.stringify(value, sortReplacer);
|
|
409
|
+
return createHash("sha256").update(json).digest("hex").slice(0, 16);
|
|
410
|
+
}
|
|
411
|
+
function sortReplacer(_key, value) {
|
|
412
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
413
|
+
return Object.keys(value).sort().reduce(
|
|
414
|
+
(sorted, k) => {
|
|
415
|
+
sorted[k] = value[k];
|
|
416
|
+
return sorted;
|
|
417
|
+
},
|
|
418
|
+
{}
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
return value;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// src/check.ts
|
|
425
|
+
async function checkScenario(adapter, scenario, options) {
|
|
426
|
+
const sharedSecret = options?.sharedSecret ?? "autonoma-check-shared";
|
|
427
|
+
const signingSecret = options?.signingSecret ?? "autonoma-check-signing";
|
|
428
|
+
const config = {
|
|
429
|
+
adapter,
|
|
430
|
+
sharedSecret,
|
|
431
|
+
signingSecret,
|
|
432
|
+
auth: options?.auth ?? (async () => ({ token: "check-token" }))
|
|
433
|
+
};
|
|
434
|
+
const upBody = JSON.stringify({ action: "up", create: scenario.create });
|
|
435
|
+
const upReq = {
|
|
436
|
+
body: upBody,
|
|
437
|
+
headers: { "x-signature": signBody(upBody, sharedSecret) }
|
|
438
|
+
};
|
|
439
|
+
const t0 = performance.now();
|
|
440
|
+
const upRes = await handleRequest(config, upReq);
|
|
441
|
+
const upMs = Math.round(performance.now() - t0);
|
|
442
|
+
if (upRes.status !== 200) {
|
|
443
|
+
const errorMsg = upRes.body.error ?? "Unknown error";
|
|
444
|
+
return {
|
|
445
|
+
valid: false,
|
|
446
|
+
phase: "up",
|
|
447
|
+
errors: [{ phase: "up", message: errorMsg, fix: suggestFix(errorMsg) }],
|
|
448
|
+
timing: { upMs, downMs: 0 }
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
const refsToken = upRes.body.refsToken;
|
|
452
|
+
const downBody = JSON.stringify({ action: "down", refsToken });
|
|
453
|
+
const downReq = {
|
|
454
|
+
body: downBody,
|
|
455
|
+
headers: { "x-signature": signBody(downBody, sharedSecret) }
|
|
456
|
+
};
|
|
457
|
+
const t1 = performance.now();
|
|
458
|
+
const downRes = await handleRequest(config, downReq);
|
|
459
|
+
const downMs = Math.round(performance.now() - t1);
|
|
460
|
+
if (downRes.status !== 200) {
|
|
461
|
+
const errorMsg = downRes.body.error ?? "Unknown error";
|
|
462
|
+
return {
|
|
463
|
+
valid: false,
|
|
464
|
+
phase: "down",
|
|
465
|
+
errors: [{ phase: "down", message: errorMsg }],
|
|
466
|
+
timing: { upMs, downMs }
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
return { valid: true, phase: "ok", errors: [], timing: { upMs, downMs } };
|
|
470
|
+
}
|
|
471
|
+
async function checkAllScenarios(adapter, scenarios, options) {
|
|
472
|
+
const results = [];
|
|
473
|
+
for (const scenario of scenarios) {
|
|
474
|
+
results.push(await checkScenario(adapter, scenario, options));
|
|
475
|
+
}
|
|
476
|
+
return results;
|
|
477
|
+
}
|
|
478
|
+
function suggestFix(errorMsg) {
|
|
479
|
+
if (errorMsg.includes("Unique constraint failed")) {
|
|
480
|
+
const match = errorMsg.match(/fields: \(`(.+?)`\)/);
|
|
481
|
+
if (match) return `Unique constraint on (${match[1]}). Add {{testRunId}} or {{index}} to make values unique.`;
|
|
482
|
+
return "Unique constraint violation. Make field values unique across instances.";
|
|
483
|
+
}
|
|
484
|
+
if (errorMsg.includes("Foreign key constraint")) {
|
|
485
|
+
return "A referenced record does not exist. Check that parent entities are nested correctly.";
|
|
486
|
+
}
|
|
487
|
+
if (errorMsg.includes("Unknown argument")) {
|
|
488
|
+
const match = errorMsg.match(/Unknown argument `(\w+)`/);
|
|
489
|
+
if (match) return `Field "${match[1]}" does not exist on this model. Remove it.`;
|
|
490
|
+
}
|
|
491
|
+
if (errorMsg.includes("must not be null")) {
|
|
492
|
+
return "A required field is null. Add it to the node with a value.";
|
|
493
|
+
}
|
|
494
|
+
return "";
|
|
495
|
+
}
|
|
496
|
+
export {
|
|
497
|
+
checkAllScenarios,
|
|
498
|
+
checkScenario,
|
|
499
|
+
findDeferrableEdge,
|
|
500
|
+
fingerprint,
|
|
501
|
+
handleRequest,
|
|
502
|
+
resolveTemplate,
|
|
503
|
+
resolveTree,
|
|
504
|
+
signBody,
|
|
505
|
+
signRefs,
|
|
506
|
+
topoSort,
|
|
507
|
+
verifyRefs,
|
|
508
|
+
verifySignature
|
|
509
|
+
};
|
|
510
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hmac.ts","../src/refs.ts","../src/template.ts","../src/tree.ts","../src/errors.ts","../src/handler.ts","../src/fingerprint.ts","../src/check.ts"],"sourcesContent":["import { createHmac, timingSafeEqual } from 'node:crypto'\n\nexport function signBody(body: string, secret: string): string {\n return createHmac('sha256', secret).update(body).digest('hex')\n}\n\nexport function verifySignature(\n body: string,\n signature: string,\n secret: string,\n): boolean {\n const expected = signBody(body, secret)\n if (expected.length !== signature.length) return false\n return timingSafeEqual(Buffer.from(expected), Buffer.from(signature))\n}\n","import { createHmac } from 'node:crypto'\n\ninterface RefsPayload {\n refs: Record<string, Record<string, unknown>[]>\n testRunId: string\n environment: string\n}\n\n/**\n * Sign refs into a JWT-like token (header.payload.signature).\n * Uses HMAC-SHA256 — not a full JWT library to avoid dependencies.\n */\nexport function signRefs(payload: RefsPayload, secret: string): string {\n const header = base64url({ alg: 'HS256', typ: 'REFS' })\n const body = base64url(payload)\n const signature = hmac(`${header}.${body}`, secret)\n return `${header}.${body}.${signature}`\n}\n\n/**\n * Verify and decode a refs token. Returns the payload or throws.\n */\nexport function verifyRefs(\n token: string,\n secret: string,\n): RefsPayload {\n const parts = token.split('.')\n if (parts.length !== 3) throw new Error('malformed token')\n\n const [header, body, signature] = parts\n const expected = hmac(`${header}.${body}`, secret)\n\n if (expected !== signature) throw new Error('signature mismatch')\n\n return JSON.parse(Buffer.from(body!, 'base64url').toString())\n}\n\nfunction base64url(obj: unknown): string {\n return Buffer.from(JSON.stringify(obj)).toString('base64url')\n}\n\nfunction hmac(data: string, secret: string): string {\n return createHmac('sha256', secret).update(data).digest('base64url')\n}\n","const TEMPLATE_RE = /\\{\\{(.+?)\\}\\}/g\n\nexport interface TemplateContext {\n testRunId: string\n index: number\n}\n\n/**\n * Resolve all {{...}} expressions in a value. Handles strings, objects, and arrays recursively.\n */\nexport function resolveTemplate(value: unknown, ctx: TemplateContext): unknown {\n if (typeof value === 'string') return resolveString(value, ctx)\n if (Array.isArray(value)) return value.map((v) => resolveTemplate(v, ctx))\n if (value && typeof value === 'object') {\n const result: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n result[k] = resolveTemplate(v, ctx)\n }\n return result\n }\n return value\n}\n\nfunction resolveString(str: string, ctx: TemplateContext): unknown {\n // If the entire string is a single expression, return the raw value (preserving type)\n const fullMatch = str.match(/^\\{\\{(.+?)\\}\\}$/)\n if (fullMatch) {\n return evaluateExpression(fullMatch[1]!, ctx)\n }\n\n // Otherwise, interpolate expressions into the string\n return str.replace(TEMPLATE_RE, (_, expr: string) => {\n const val = evaluateExpression(expr, ctx)\n return String(val)\n })\n}\n\nfunction evaluateExpression(expr: string, ctx: TemplateContext): unknown {\n expr = expr.trim()\n\n // Simple variables\n if (expr === 'testRunId') return ctx.testRunId\n if (expr === 'index') return ctx.index\n if (expr === 'index1') return ctx.index + 1\n\n // cycle([...])\n const cycleMatch = expr.match(/^cycle\\(\\[(.+)\\]\\)$/)\n if (cycleMatch) {\n const items = parseArrayLiteral(cycleMatch[1]!)\n return items[ctx.index % items.length]\n }\n\n // pick([...])\n const pickMatch = expr.match(/^pick\\(\\[(.+)\\]\\)$/)\n if (pickMatch) {\n const items = parseArrayLiteral(pickMatch[1]!)\n return items[Math.floor(Math.random() * items.length)]\n }\n\n // random.int(a,b)\n const randIntMatch = expr.match(/^random\\.int\\((\\d+),\\s*(\\d+)\\)$/)\n if (randIntMatch) {\n const min = parseInt(randIntMatch[1]!, 10)\n const max = parseInt(randIntMatch[2]!, 10)\n return Math.floor(Math.random() * (max - min + 1)) + min\n }\n\n // random.float(a,b)\n const randFloatMatch = expr.match(/^random\\.float\\((\\d+(?:\\.\\d+)?),\\s*(\\d+(?:\\.\\d+)?)\\)$/)\n if (randFloatMatch) {\n const min = parseFloat(randFloatMatch[1]!)\n const max = parseFloat(randFloatMatch[2]!)\n return Math.random() * (max - min) + min\n }\n\n // now()\n if (expr === 'now()') return new Date().toISOString()\n\n // daysAgo(n)\n const daysAgoMatch = expr.match(/^daysAgo\\((\\d+)\\)$/)\n if (daysAgoMatch) {\n const d = new Date()\n d.setDate(d.getDate() - parseInt(daysAgoMatch[1]!, 10))\n return d.toISOString()\n }\n\n throw new Error(`Template error: unknown expression '${expr}'`)\n}\n\nfunction parseArrayLiteral(raw: string): string[] {\n return raw.split(',').map((s) => {\n s = s.trim()\n // Strip surrounding quotes\n if ((s.startsWith(\"'\") && s.endsWith(\"'\")) || (s.startsWith('\"') && s.endsWith('\"'))) {\n return s.slice(1, -1)\n }\n return s\n })\n}\n","import type { SchemaInfo, SchemaRelation } from './types'\nimport { resolveTemplate, type TemplateContext } from './template'\n\nconst RESERVED_KEYS = new Set(['_alias', '_ref', '_count', '_batch'])\n\n/** A create operation produced by the tree resolver */\nexport interface CreateOp {\n model: string\n fields: Record<string, unknown>\n tempId: string\n batch: boolean\n}\n\n/** Result of resolving a tree scenario */\nexport interface ResolvedTree {\n ops: CreateOp[]\n aliases: Map<string, string>\n}\n\n/** A resolved reference to another node's id */\nexport interface RefNode {\n _ref: string\n}\n\n/**\n * Resolve a nested scenario tree into an ordered list of create operations.\n *\n * Walks depth-first. Parent-child FKs are wired automatically.\n * Handles both directions:\n * - FK on child (Application.organizationId → Organization): set child FK to parent ID\n * - FK on parent (Member.userId → User): create child first, set parent FK to child ID\n */\nexport function resolveTree(\n create: Record<string, Record<string, unknown>[]>,\n schema: SchemaInfo,\n testRunId: string,\n): ResolvedTree {\n const relationByParentField = new Map<string, SchemaRelation>()\n for (const rel of schema.relations) {\n relationByParentField.set(`${rel.parentModel}.${rel.parentField}`, rel)\n }\n\n // Determine FK direction for each relation:\n // Is childField on the parent model or the child model?\n const fkOnParent = new Set<string>() // key: \"parentModel.parentField\"\n for (const rel of schema.relations) {\n const edge = schema.edges.find(\n (e) => e.localField === rel.childField && (e.from === rel.parentModel || e.from === rel.childModel),\n )\n if (edge && edge.from === rel.parentModel) {\n // FK column is on the parent model → create child first, then set parent FK\n fkOnParent.add(`${rel.parentModel}.${rel.parentField}`)\n }\n }\n\n const aliases = new Map<string, string>()\n const ops: CreateOp[] = []\n let tempCounter = 0\n\n function makeTempId(model: string): string {\n return `__temp_${model}_${tempCounter++}`\n }\n\n function walkNode(\n modelName: string,\n node: Record<string, unknown>,\n parentTempId: string | null,\n parentRelation: SchemaRelation | null,\n parentFkOnParent: boolean,\n index: number,\n ): string {\n const fields: Record<string, unknown> = {}\n const preChildren: Array<{ relation: SchemaRelation; value: unknown; fkOnParent: boolean }> = []\n const postChildren: Array<{ relation: SchemaRelation; value: unknown; fkOnParent: boolean }> = []\n const alias = node._alias as string | undefined\n const tempId = makeTempId(modelName)\n\n for (const [key, value] of Object.entries(node)) {\n if (RESERVED_KEYS.has(key)) continue\n\n const relation = relationByParentField.get(`${modelName}.${key}`)\n if (relation) {\n const isOnParent = fkOnParent.has(`${modelName}.${key}`)\n if (isOnParent) {\n // FK is on this model → need to create the child BEFORE this node\n preChildren.push({ relation, value, fkOnParent: true })\n } else {\n // FK is on the child → create child AFTER this node (normal)\n postChildren.push({ relation, value, fkOnParent: false })\n }\n continue\n }\n\n if (value && typeof value === 'object' && '_ref' in value) {\n const refAlias = (value as RefNode)._ref\n const refTempId = aliases.get(refAlias)\n if (!refTempId) {\n throw new Error(`_ref \"${refAlias}\" not found. Ensure the referenced node has _alias and is created before this one.`)\n }\n fields[key] = refTempId\n continue\n }\n\n const ctx: TemplateContext = { testRunId, index, }\n fields[key] = resolveTemplate(value, ctx)\n }\n\n // Wire FK to parent (if this node is a child and FK is on the child)\n if (parentRelation && parentTempId && !parentFkOnParent) {\n fields[parentRelation.childField] = parentTempId\n }\n\n // Process pre-children: these need to be created BEFORE this node\n // because this node's FK points to them\n for (const { relation, value, fkOnParent: isOnParent } of preChildren) {\n if (Array.isArray(value)) {\n for (let i = 0; i < value.length; i++) {\n const childTempId = walkNode(relation.childModel, value[i] as Record<string, unknown>, tempId, relation, true, i)\n // Set this node's FK to point to the created child\n fields[relation.childField] = childTempId\n }\n }\n }\n\n // Create this node\n ops.push({ model: modelName, fields, tempId, batch: false })\n if (alias) aliases.set(alias, tempId)\n\n // Process post-children: normal case, FK is on the child\n for (const { relation, value } of postChildren) {\n if (Array.isArray(value)) {\n for (let i = 0; i < value.length; i++) {\n walkNode(relation.childModel, value[i] as Record<string, unknown>, tempId, relation, false, i)\n }\n } else if (value && typeof value === 'object' && '_count' in value) {\n const bulk = value as Record<string, unknown>\n const count = bulk._count as number\n const isBatch = (bulk._batch as boolean) ?? false\n\n for (let i = 0; i < count; i++) {\n const bulkFields: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(bulk)) {\n if (k === '_count' || k === '_batch') continue\n const ctx: TemplateContext = { testRunId, index: i, }\n bulkFields[k] = resolveTemplate(v, ctx)\n }\n bulkFields[relation.childField] = tempId\n ops.push({ model: relation.childModel, fields: bulkFields, tempId: makeTempId(relation.childModel), batch: isBatch })\n }\n }\n }\n\n return tempId\n }\n\n for (const [modelName, nodes] of Object.entries(create)) {\n for (let i = 0; i < nodes.length; i++) {\n walkNode(modelName, nodes[i]!, null, null, false, i)\n }\n }\n\n return { ops, aliases }\n}\n","export class AutonomaError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n public readonly status: number,\n ) {\n super(message)\n this.name = 'AutonomaError'\n }\n}\n\nexport const Errors = {\n unknownAction(action: string) {\n return new AutonomaError(\n `Unknown action: ${action}`,\n 'UNKNOWN_ACTION',\n 400,\n )\n },\n unknownEnvironment(name: string) {\n return new AutonomaError(\n `Unknown environment: ${name}`,\n 'UNKNOWN_ENVIRONMENT',\n 400,\n )\n },\n invalidSignature() {\n return new AutonomaError(\n 'Invalid HMAC signature',\n 'INVALID_SIGNATURE',\n 401,\n )\n },\n invalidRefsToken(reason: string) {\n return new AutonomaError(\n `Invalid refs token: ${reason}`,\n 'INVALID_REFS_TOKEN',\n 403,\n )\n },\n productionBlocked() {\n return new AutonomaError(\n 'Environment factory is disabled in production',\n 'PRODUCTION_BLOCKED',\n 404,\n )\n },\n invalidBody(reason: string) {\n return new AutonomaError(\n `Invalid request body: ${reason}`,\n 'INVALID_BODY',\n 400,\n )\n },\n} as const\n","import type {\n HandlerConfig,\n HandlerRequest,\n HandlerResponse,\n CreateContext,\n ResolvedEntitySpec,\n} from './types'\nimport { verifySignature } from './hmac'\nimport { signRefs, verifyRefs } from './refs'\nimport { resolveTree } from './tree'\nimport { AutonomaError, Errors } from './errors'\n\nexport async function handleRequest(\n config: HandlerConfig,\n req: HandlerRequest,\n): Promise<HandlerResponse> {\n try {\n if (config.sharedSecret === config.signingSecret) {\n throw new AutonomaError(\n 'sharedSecret and signingSecret must be different. The shared secret is known by Autonoma; the signing secret must be private.',\n 'SAME_SECRETS',\n 500,\n )\n }\n\n if (!config.allowProduction && process.env.NODE_ENV === 'production') {\n throw Errors.productionBlocked()\n }\n\n const signature = req.headers['x-signature'] ?? req.headers['X-Signature'] ?? ''\n if (!verifySignature(req.body, signature, config.sharedSecret)) {\n throw Errors.invalidSignature()\n }\n\n let body: Record<string, unknown>\n try {\n body = JSON.parse(req.body)\n } catch {\n throw Errors.invalidBody('invalid JSON')\n }\n\n const action = body.action as string\n if (!action) throw Errors.invalidBody('missing action')\n\n switch (action) {\n case 'discover':\n return handleDiscover(config)\n case 'up':\n return await handleUp(config, body)\n case 'down':\n return await handleDown(config, body)\n default:\n throw Errors.unknownAction(action)\n }\n } catch (err) {\n if (err instanceof AutonomaError) {\n return { status: err.status, body: { error: err.message, code: err.code } }\n }\n const message = err instanceof Error ? err.message : 'Internal error'\n return { status: 500, body: { error: message, code: 'INTERNAL_ERROR' } }\n }\n}\n\nfunction handleDiscover(config: HandlerConfig): HandlerResponse {\n const schema = config.adapter.getSchema()\n return { status: 200, body: { schema } }\n}\n\nasync function handleUp(\n config: HandlerConfig,\n body: Record<string, unknown>,\n): Promise<HandlerResponse> {\n const create = body.create as Record<string, Record<string, unknown>[]> | undefined\n if (!create) throw Errors.invalidBody('missing \"create\" in request body')\n\n const testRunId = (body.testRunId as string) ?? crypto.randomUUID()\n const schema = config.adapter.getSchema()\n\n const tree = resolveTree(create, schema, testRunId)\n const refs: Record<string, Record<string, unknown>[]> = {}\n const idMap = new Map<string, string>()\n\n let i = 0\n while (i < tree.ops.length) {\n const op = tree.ops[i]!\n const model = op.model\n\n // Collect consecutive ops for the same model with same batch flag\n const batch: typeof tree.ops = [op]\n while (i + 1 < tree.ops.length && tree.ops[i + 1]!.model === model && tree.ops[i + 1]!.batch === op.batch) {\n i++\n batch.push(tree.ops[i]!)\n }\n\n // Replace temp IDs with real IDs in all fields\n const resolvedFields = batch.map((b) => {\n const fields = { ...b.fields }\n delete fields.id\n for (const [key, value] of Object.entries(fields)) {\n if (typeof value === 'string' && value.startsWith('__temp_')) {\n const realId = idMap.get(value)\n if (realId) fields[key] = realId\n }\n }\n // Inject scope field if applicable\n const scopeEdge = schema.edges.find(\n (e) => e.from === model && e.localField.toLowerCase() === schema.scopeField.toLowerCase() && e.from !== e.to,\n )\n if (scopeEdge && !(scopeEdge.localField in fields)) {\n const scopeVal = detectScopeValue(refs, schema.scopeField)\n if (scopeVal) fields[scopeEdge.localField] = scopeVal\n }\n return fields\n })\n\n const spec: Record<string, ResolvedEntitySpec> = {\n [model]: { count: resolvedFields.length, fields: resolvedFields, batch: op.batch },\n }\n\n const context: CreateContext = { testRunId, refs }\n const created = await config.adapter.createEntities(spec, context)\n const records = created[model] ?? []\n\n if (!refs[model]) refs[model] = []\n refs[model].push(...records)\n\n for (let j = 0; j < batch.length; j++) {\n const record = records[j]\n if (record && typeof record.id === 'string') {\n idMap.set(batch[j]!.tempId, record.id)\n }\n }\n\n i++\n }\n\n const scopeValue = detectScopeValue(refs, schema.scopeField) ?? testRunId\n\n const firstUser = findFirstUser(refs)\n let auth = { token: '' }\n if (config.auth && firstUser) {\n auth = await config.auth(firstUser)\n }\n\n const refsToken = signRefs(\n { refs, testRunId: scopeValue, environment: '' },\n config.signingSecret,\n )\n\n return { status: 200, body: { auth, refs, refsToken } }\n}\n\nasync function handleDown(\n config: HandlerConfig,\n body: Record<string, unknown>,\n): Promise<HandlerResponse> {\n const refsToken = body.refsToken as string\n if (!refsToken) throw Errors.invalidBody('missing refsToken')\n\n let payload: ReturnType<typeof verifyRefs>\n try {\n payload = verifyRefs(refsToken, config.signingSecret)\n } catch (err) {\n const message = err instanceof Error ? err.message : 'invalid token'\n throw Errors.invalidRefsToken(message)\n }\n\n await config.adapter.teardown(payload.testRunId, payload.refs)\n\n return { status: 200, body: { ok: true } }\n}\n\nfunction findFirstUser(\n refs: Record<string, Record<string, unknown>[]>,\n): Record<string, unknown> | null {\n for (const [model, records] of Object.entries(refs)) {\n if (model.toLowerCase() === 'user' && records.length > 0) {\n return records[0]!\n }\n }\n return null\n}\n\nfunction detectScopeValue(\n refs: Record<string, Record<string, unknown>[]>,\n scopeField: string,\n): string | null {\n const scopeLower = scopeField.toLowerCase()\n for (const records of Object.values(refs)) {\n for (const record of records) {\n for (const [key, value] of Object.entries(record)) {\n if (key.toLowerCase() === scopeLower && typeof value === 'string') {\n return value\n }\n }\n }\n }\n return null\n}\n","import { createHash } from 'node:crypto'\n\n/**\n * Compute a stable 16-char hex fingerprint of a scenario definition.\n * Uses sha256 of the JSON-serialized spec with sorted keys.\n */\nexport function fingerprint(value: unknown): string {\n const json = JSON.stringify(value, sortReplacer)\n return createHash('sha256').update(json).digest('hex').slice(0, 16)\n}\n\n/**\n * JSON replacer that sorts object keys for deterministic serialization.\n */\nfunction sortReplacer(_key: string, value: unknown): unknown {\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n return Object.keys(value as Record<string, unknown>)\n .sort()\n .reduce(\n (sorted, k) => {\n sorted[k] = (value as Record<string, unknown>)[k]\n return sorted\n },\n {} as Record<string, unknown>,\n )\n }\n return value\n}\n","import type {\n OrmAdapter,\n ScenarioDefinition,\n HandlerConfig,\n} from './types'\nimport { handleRequest } from './handler'\nimport { signBody } from './hmac'\n\nexport interface CheckResult {\n valid: boolean\n phase: 'up' | 'down' | 'ok'\n errors: CheckError[]\n timing?: { upMs: number; downMs: number }\n}\n\nexport interface CheckError {\n phase: 'up' | 'down'\n message: string\n fix?: string\n}\n\n/**\n * Dry-run a scenario against a real database.\n * Runs the full up → down cycle and returns structured errors.\n */\nexport async function checkScenario(\n adapter: OrmAdapter,\n scenario: ScenarioDefinition,\n options?: { sharedSecret?: string; signingSecret?: string; auth?: HandlerConfig['auth'] },\n): Promise<CheckResult> {\n const sharedSecret = options?.sharedSecret ?? 'autonoma-check-shared'\n const signingSecret = options?.signingSecret ?? 'autonoma-check-signing'\n\n const config: HandlerConfig = {\n adapter,\n sharedSecret,\n signingSecret,\n auth: options?.auth ?? (async () => ({ token: 'check-token' })),\n }\n\n // Up\n const upBody = JSON.stringify({ action: 'up', create: scenario.create })\n const upReq = {\n body: upBody,\n headers: { 'x-signature': signBody(upBody, sharedSecret) },\n }\n\n const t0 = performance.now()\n const upRes = await handleRequest(config, upReq)\n const upMs = Math.round(performance.now() - t0)\n\n if (upRes.status !== 200) {\n const errorMsg = (upRes.body as Record<string, string>).error ?? 'Unknown error'\n return {\n valid: false,\n phase: 'up',\n errors: [{ phase: 'up', message: errorMsg, fix: suggestFix(errorMsg) }],\n timing: { upMs, downMs: 0 },\n }\n }\n\n // Down\n const refsToken = (upRes.body as Record<string, string>).refsToken\n const downBody = JSON.stringify({ action: 'down', refsToken })\n const downReq = {\n body: downBody,\n headers: { 'x-signature': signBody(downBody, sharedSecret) },\n }\n\n const t1 = performance.now()\n const downRes = await handleRequest(config, downReq)\n const downMs = Math.round(performance.now() - t1)\n\n if (downRes.status !== 200) {\n const errorMsg = (downRes.body as Record<string, string>).error ?? 'Unknown error'\n return {\n valid: false,\n phase: 'down',\n errors: [{ phase: 'down', message: errorMsg }],\n timing: { upMs, downMs },\n }\n }\n\n return { valid: true, phase: 'ok', errors: [], timing: { upMs, downMs } }\n}\n\n/**\n * Check multiple scenarios sequentially.\n */\nexport async function checkAllScenarios(\n adapter: OrmAdapter,\n scenarios: ScenarioDefinition[],\n options?: { sharedSecret?: string; signingSecret?: string; auth?: HandlerConfig['auth'] },\n): Promise<CheckResult[]> {\n const results: CheckResult[] = []\n for (const scenario of scenarios) {\n results.push(await checkScenario(adapter, scenario, options))\n }\n return results\n}\n\nfunction suggestFix(errorMsg: string): string {\n if (errorMsg.includes('Unique constraint failed')) {\n const match = errorMsg.match(/fields: \\(`(.+?)`\\)/)\n if (match) return `Unique constraint on (${match[1]}). Add {{testRunId}} or {{index}} to make values unique.`\n return 'Unique constraint violation. Make field values unique across instances.'\n }\n if (errorMsg.includes('Foreign key constraint')) {\n return 'A referenced record does not exist. Check that parent entities are nested correctly.'\n }\n if (errorMsg.includes('Unknown argument')) {\n const match = errorMsg.match(/Unknown argument `(\\w+)`/)\n if (match) return `Field \"${match[1]}\" does not exist on this model. Remove it.`\n }\n if (errorMsg.includes('must not be null')) {\n return 'A required field is null. Add it to the node with a value.'\n }\n return ''\n}\n"],"mappings":"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","names":["createHmac"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@autonoma-ai/sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Autonoma Environment Factory SDK — protocol layer",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./graph": {
|
|
13
|
+
"types": "./dist/graph.d.ts",
|
|
14
|
+
"import": "./dist/graph.js",
|
|
15
|
+
"default": "./dist/graph.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"bin": {
|
|
19
|
+
"autonoma": "./dist/cli.js"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "^22.0.0",
|
|
29
|
+
"typescript": "^5.7.0",
|
|
30
|
+
"vitest": "^3.0.0",
|
|
31
|
+
"tsup": "^8.4.0"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsup",
|
|
35
|
+
"test": "vitest run",
|
|
36
|
+
"test:unit": "vitest run",
|
|
37
|
+
"clean": "rm -rf dist"
|
|
38
|
+
}
|
|
39
|
+
}
|