bonescript-compiler 0.5.5 → 0.5.7
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/cli.js +17 -1
- package/dist/cli.js.map +1 -1
- package/dist/emit_admin.d.ts +7 -0
- package/dist/emit_admin.js +130 -0
- package/dist/emit_admin.js.map +1 -0
- package/dist/emit_audit.d.ts +7 -0
- package/dist/emit_audit.js +89 -0
- package/dist/emit_audit.js.map +1 -0
- package/dist/emit_full.js +22 -0
- package/dist/emit_full.js.map +1 -1
- package/dist/emit_openapi.d.ts +7 -0
- package/dist/emit_openapi.js +333 -0
- package/dist/emit_openapi.js.map +1 -0
- package/dist/emit_postman.d.ts +6 -0
- package/dist/emit_postman.js +126 -0
- package/dist/emit_postman.js.map +1 -0
- package/dist/emit_runtime.js +30 -6
- package/dist/emit_runtime.js.map +1 -1
- package/dist/emit_sdk.d.ts +7 -0
- package/dist/emit_sdk.js +162 -0
- package/dist/emit_sdk.js.map +1 -0
- package/dist/emit_seed.d.ts +6 -0
- package/dist/emit_seed.js +88 -0
- package/dist/emit_seed.js.map +1 -0
- package/dist/emit_zod.d.ts +7 -0
- package/dist/emit_zod.js +115 -0
- package/dist/emit_zod.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +19 -1
- package/dist/index.js.map +1 -1
- package/dist/lowering.js +5 -2
- package/dist/lowering.js.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +14 -1
- package/src/emit_admin.ts +131 -0
- package/src/emit_audit.ts +112 -0
- package/src/emit_full.ts +29 -0
- package/src/emit_openapi.ts +344 -0
- package/src/emit_postman.ts +145 -0
- package/src/emit_runtime.ts +31 -6
- package/src/emit_sdk.ts +195 -0
- package/src/emit_seed.ts +91 -0
- package/src/emit_zod.ts +111 -0
- package/src/index.ts +9 -0
- package/src/lowering.ts +5 -2
package/src/emit_sdk.ts
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BoneScript TypeScript SDK Emitter
|
|
3
|
+
* Generates a typed fetch client SDK from an IRSystem.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as IR from "./ir";
|
|
7
|
+
|
|
8
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
function toSnakeCase(s: string): string {
|
|
11
|
+
return s.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function toCamelCase(s: string): string {
|
|
15
|
+
return s.replace(/_([a-z])/g, (_: string, c: string) => c.toUpperCase());
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function toPascalCase(s: string): string {
|
|
19
|
+
const c = toCamelCase(s);
|
|
20
|
+
return c.charAt(0).toUpperCase() + c.slice(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function irTypeToTs(irType: string): string {
|
|
24
|
+
if (irType === "string") return "string";
|
|
25
|
+
if (irType === "uint" || irType === "int") return "number";
|
|
26
|
+
if (irType === "float") return "number";
|
|
27
|
+
if (irType === "bool") return "boolean";
|
|
28
|
+
if (irType === "timestamp") return "string";
|
|
29
|
+
if (irType === "uuid") return "string";
|
|
30
|
+
if (irType === "bytes") return "string";
|
|
31
|
+
if (irType === "json") return "unknown";
|
|
32
|
+
const listMatch = irType.match(/^list<(.+)>$/);
|
|
33
|
+
if (listMatch) return irTypeToTs(listMatch[1]) + "[]";
|
|
34
|
+
const setMatch = irType.match(/^set<(.+)>$/);
|
|
35
|
+
if (setMatch) return irTypeToTs(setMatch[1]) + "[]";
|
|
36
|
+
const optMatch = irType.match(/^optional<(.+)>$/);
|
|
37
|
+
if (optMatch) return irTypeToTs(optMatch[1]) + " | null";
|
|
38
|
+
return "unknown";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ─── SDK generator ────────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
export function emitTypescriptSdk(system: IR.IRSystem): string {
|
|
44
|
+
const lines: string[] = [];
|
|
45
|
+
const systemPascal = toPascalCase(system.name.replace(/[^a-zA-Z0-9_]/g, "_"));
|
|
46
|
+
|
|
47
|
+
lines.push("// Generated by BoneScript compiler. Import this file into your frontend.");
|
|
48
|
+
lines.push(`// System: ${system.name} v${system.version}`);
|
|
49
|
+
lines.push("");
|
|
50
|
+
|
|
51
|
+
// Collect all models for interface generation
|
|
52
|
+
const allModels: { mod: IR.IRModule; model: IR.IRModel }[] = [];
|
|
53
|
+
for (const mod of system.modules) {
|
|
54
|
+
if (mod.kind === "api_service" && mod.models.length > 0) {
|
|
55
|
+
for (const model of mod.models) {
|
|
56
|
+
allModels.push({ mod, model });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Emit TypeScript interfaces
|
|
62
|
+
for (const { model } of allModels) {
|
|
63
|
+
const typeName = toPascalCase(model.name);
|
|
64
|
+
lines.push(`export interface ${typeName} {`);
|
|
65
|
+
for (const field of model.fields) {
|
|
66
|
+
const nullable = field.nullable ? " | null" : "";
|
|
67
|
+
lines.push(` ${field.name}: ${irTypeToTs(field.type)}${nullable};`);
|
|
68
|
+
}
|
|
69
|
+
lines.push("}");
|
|
70
|
+
lines.push("");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Emit client class
|
|
74
|
+
lines.push(`export class ${systemPascal}Client {`);
|
|
75
|
+
lines.push(
|
|
76
|
+
` constructor(private baseUrl: string, private getToken?: () => string | null) {}`
|
|
77
|
+
);
|
|
78
|
+
lines.push("");
|
|
79
|
+
|
|
80
|
+
// Private request helper
|
|
81
|
+
lines.push(
|
|
82
|
+
` private async request<T>(method: string, path: string, body?: unknown): Promise<T> {`
|
|
83
|
+
);
|
|
84
|
+
lines.push(` const headers: Record<string, string> = {`);
|
|
85
|
+
lines.push(` "Content-Type": "application/json",`);
|
|
86
|
+
lines.push(` };`);
|
|
87
|
+
lines.push(` if (this.getToken) {`);
|
|
88
|
+
lines.push(` const token = this.getToken();`);
|
|
89
|
+
lines.push(` if (token) headers["Authorization"] = "Bearer " + token;`);
|
|
90
|
+
lines.push(` }`);
|
|
91
|
+
lines.push(` const res = await fetch(this.baseUrl + path, {`);
|
|
92
|
+
lines.push(` method,`);
|
|
93
|
+
lines.push(` headers,`);
|
|
94
|
+
lines.push(` body: body !== undefined ? JSON.stringify(body) : undefined,`);
|
|
95
|
+
lines.push(` });`);
|
|
96
|
+
lines.push(` if (!res.ok) {`);
|
|
97
|
+
lines.push(
|
|
98
|
+
` const text = await res.text().catch(() => res.statusText);`
|
|
99
|
+
);
|
|
100
|
+
lines.push(` throw new Error(\`HTTP \${res.status}: \${text}\`);`);
|
|
101
|
+
lines.push(` }`);
|
|
102
|
+
lines.push(` if (res.status === 204) return undefined as unknown as T;`);
|
|
103
|
+
lines.push(` return res.json() as Promise<T>;`);
|
|
104
|
+
lines.push(` }`);
|
|
105
|
+
lines.push("");
|
|
106
|
+
|
|
107
|
+
// Per-entity methods
|
|
108
|
+
for (const { mod, model } of allModels) {
|
|
109
|
+
const entity = toPascalCase(model.name);
|
|
110
|
+
const tableName = toSnakeCase(model.name);
|
|
111
|
+
const basePath = `/${tableName}s`;
|
|
112
|
+
|
|
113
|
+
lines.push(` // ─── ${entity} ───`);
|
|
114
|
+
lines.push("");
|
|
115
|
+
|
|
116
|
+
lines.push(
|
|
117
|
+
` list${entity}(page = 1, pageSize = 50): Promise<{ items: ${entity}[]; total: number; page: number; page_size: number }> {`
|
|
118
|
+
);
|
|
119
|
+
lines.push(
|
|
120
|
+
` return this.request("GET", \`${basePath}?page=\${page}&page_size=\${pageSize}\`);`
|
|
121
|
+
);
|
|
122
|
+
lines.push(` }`);
|
|
123
|
+
lines.push("");
|
|
124
|
+
|
|
125
|
+
lines.push(` get${entity}(id: string): Promise<${entity}> {`);
|
|
126
|
+
lines.push(` return this.request("GET", \`${basePath}/\${id}\`);`);
|
|
127
|
+
lines.push(` }`);
|
|
128
|
+
lines.push("");
|
|
129
|
+
|
|
130
|
+
lines.push(
|
|
131
|
+
` create${entity}(data: Partial<${entity}>): Promise<${entity}> {`
|
|
132
|
+
);
|
|
133
|
+
lines.push(` return this.request("POST", "${basePath}", data);`);
|
|
134
|
+
lines.push(` }`);
|
|
135
|
+
lines.push("");
|
|
136
|
+
|
|
137
|
+
lines.push(
|
|
138
|
+
` update${entity}(id: string, data: Partial<${entity}>): Promise<${entity}> {`
|
|
139
|
+
);
|
|
140
|
+
lines.push(` return this.request("PUT", \`${basePath}/\${id}\`, data);`);
|
|
141
|
+
lines.push(` }`);
|
|
142
|
+
lines.push("");
|
|
143
|
+
|
|
144
|
+
lines.push(` delete${entity}(id: string): Promise<void> {`);
|
|
145
|
+
lines.push(` return this.request("DELETE", \`${basePath}/\${id}\`);`);
|
|
146
|
+
lines.push(` }`);
|
|
147
|
+
lines.push("");
|
|
148
|
+
|
|
149
|
+
// Capability methods
|
|
150
|
+
const crudNames = new Set(["create", "read", "update", "delete", "list"]);
|
|
151
|
+
const allMethods: IR.IRMethod[] = mod.interfaces.flatMap((i) => i.methods);
|
|
152
|
+
const capabilityMethods = allMethods.filter(
|
|
153
|
+
(m) => !crudNames.has(m.name.toLowerCase())
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
for (const method of capabilityMethods) {
|
|
157
|
+
const methodCamel = toCamelCase(method.name);
|
|
158
|
+
const dashName = toSnakeCase(method.name).replace(/_/g, "-");
|
|
159
|
+
lines.push(
|
|
160
|
+
` ${methodCamel}(data: Record<string, unknown>): Promise<{ ok: boolean; action: string }> {`
|
|
161
|
+
);
|
|
162
|
+
lines.push(
|
|
163
|
+
` return this.request("POST", "${basePath}/${dashName}", data);`
|
|
164
|
+
);
|
|
165
|
+
lines.push(` }`);
|
|
166
|
+
lines.push("");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
lines.push("}");
|
|
171
|
+
lines.push("");
|
|
172
|
+
|
|
173
|
+
return lines.join("\n");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function emitSdkPackageJson(system: IR.IRSystem): string {
|
|
177
|
+
const pkgName = toSnakeCase(system.name).replace(/_/g, "-") + "-sdk";
|
|
178
|
+
return JSON.stringify(
|
|
179
|
+
{
|
|
180
|
+
name: pkgName,
|
|
181
|
+
version: system.version,
|
|
182
|
+
description: `TypeScript SDK for ${system.name} — generated by BoneScript compiler`,
|
|
183
|
+
main: "client.js",
|
|
184
|
+
types: "client.d.ts",
|
|
185
|
+
scripts: {
|
|
186
|
+
build: "tsc",
|
|
187
|
+
},
|
|
188
|
+
devDependencies: {
|
|
189
|
+
typescript: "5.3.3",
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
null,
|
|
193
|
+
2
|
|
194
|
+
);
|
|
195
|
+
}
|
package/src/emit_seed.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BoneScript Seed File Emitter
|
|
3
|
+
* Generates a TypeScript seed script from an IRSystem.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as IR from "./ir";
|
|
7
|
+
|
|
8
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
function toSnakeCase(s: string): string {
|
|
11
|
+
return s.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function seedValue(irType: string, fieldName: string, i: number): string {
|
|
15
|
+
if (irType === "string") return `"sample_${fieldName}_${i + 1}"`;
|
|
16
|
+
if (irType === "uint" || irType === "int") return String(i + 1);
|
|
17
|
+
if (irType === "float") return String((i + 1) * 1.5);
|
|
18
|
+
if (irType === "bool") return i % 2 === 0 ? "true" : "false";
|
|
19
|
+
if (irType === "uuid") return "uuid()";
|
|
20
|
+
if (irType === "timestamp") return "new Date().toISOString()";
|
|
21
|
+
if (irType === "bytes") return `"sample_${fieldName}_${i + 1}"`;
|
|
22
|
+
if (irType === "json") return "JSON.stringify({})";
|
|
23
|
+
const listMatch = irType.match(/^list<(.+)>$/);
|
|
24
|
+
if (listMatch) return "JSON.stringify([])";
|
|
25
|
+
const setMatch = irType.match(/^set<(.+)>$/);
|
|
26
|
+
if (setMatch) return "JSON.stringify([])";
|
|
27
|
+
const optMatch = irType.match(/^optional<(.+)>$/);
|
|
28
|
+
if (optMatch) return "null";
|
|
29
|
+
return `"sample_${fieldName}_${i + 1}"`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
export function emitSeedFile(system: IR.IRSystem): string {
|
|
35
|
+
const lines: string[] = [];
|
|
36
|
+
|
|
37
|
+
lines.push(`// Generated by BoneScript compiler.`);
|
|
38
|
+
lines.push(`// Seed script for: ${system.name} v${system.version}`);
|
|
39
|
+
lines.push(`// Run: npx ts-node src/seed.ts`);
|
|
40
|
+
lines.push("");
|
|
41
|
+
lines.push(`import { query, pool } from "./db";`);
|
|
42
|
+
lines.push(`import { v4 as uuid } from "uuid";`);
|
|
43
|
+
lines.push("");
|
|
44
|
+
|
|
45
|
+
const seedModels: { mod: IR.IRModule; model: IR.IRModel }[] = [];
|
|
46
|
+
for (const mod of system.modules) {
|
|
47
|
+
if (mod.kind === "api_service" || mod.kind === "data_store") {
|
|
48
|
+
for (const model of mod.models) {
|
|
49
|
+
seedModels.push({ mod, model });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
lines.push(`async function main(): Promise<void> {`);
|
|
55
|
+
lines.push(` console.log("Seeding ${system.name}...");`);
|
|
56
|
+
lines.push("");
|
|
57
|
+
|
|
58
|
+
for (const { model } of seedModels) {
|
|
59
|
+
const tableName = toSnakeCase(model.name) + "s";
|
|
60
|
+
lines.push(` // Seed ${model.name}`);
|
|
61
|
+
lines.push(` console.log(" Seeding ${tableName}...");`);
|
|
62
|
+
|
|
63
|
+
for (let i = 0; i < 3; i++) {
|
|
64
|
+
const fieldNames = model.fields.map((f) => f.name);
|
|
65
|
+
const fieldValues = model.fields.map((f) =>
|
|
66
|
+
seedValue(f.type, f.name, i)
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
lines.push(` await query(`);
|
|
70
|
+
lines.push(
|
|
71
|
+
` \`INSERT INTO ${tableName} (${fieldNames.join(", ")}) VALUES (${fieldNames.map((_, idx) => `$${idx + 1}`).join(", ")}) ON CONFLICT DO NOTHING\`,`
|
|
72
|
+
);
|
|
73
|
+
lines.push(` [${fieldValues.join(", ")}]`);
|
|
74
|
+
lines.push(` );`);
|
|
75
|
+
}
|
|
76
|
+
lines.push("");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
lines.push(` console.log("Seed complete.");`);
|
|
80
|
+
lines.push(`}`);
|
|
81
|
+
lines.push("");
|
|
82
|
+
lines.push(`main()`);
|
|
83
|
+
lines.push(` .catch((err) => {`);
|
|
84
|
+
lines.push(` console.error("Seed failed:", err);`);
|
|
85
|
+
lines.push(` process.exit(1);`);
|
|
86
|
+
lines.push(` })`);
|
|
87
|
+
lines.push(` .finally(() => pool.end());`);
|
|
88
|
+
lines.push("");
|
|
89
|
+
|
|
90
|
+
return lines.join("\n");
|
|
91
|
+
}
|
package/src/emit_zod.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BoneScript Zod Schema Emitter
|
|
3
|
+
* Generates Zod v3 validation schemas from an IRSystem.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as IR from "./ir";
|
|
7
|
+
|
|
8
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
function toPascalCase(s: string): string {
|
|
11
|
+
return s
|
|
12
|
+
.replace(/_([a-z])/g, (_: string, c: string) => c.toUpperCase())
|
|
13
|
+
.replace(/^([a-z])/, (_: string, c: string) => c.toUpperCase());
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function irTypeToZod(irType: string): string {
|
|
17
|
+
if (irType === "string") return "z.string()";
|
|
18
|
+
if (irType === "uint") return "z.number().int().nonnegative()";
|
|
19
|
+
if (irType === "int") return "z.number().int()";
|
|
20
|
+
if (irType === "float") return "z.number()";
|
|
21
|
+
if (irType === "bool") return "z.boolean()";
|
|
22
|
+
if (irType === "timestamp") return "z.string().datetime()";
|
|
23
|
+
if (irType === "uuid") return "z.string().uuid()";
|
|
24
|
+
if (irType === "bytes") return "z.string()";
|
|
25
|
+
if (irType === "json") return "z.unknown()";
|
|
26
|
+
const listMatch = irType.match(/^list<(.+)>$/);
|
|
27
|
+
if (listMatch) return `z.array(${irTypeToZod(listMatch[1])})`;
|
|
28
|
+
const setMatch = irType.match(/^set<(.+)>$/);
|
|
29
|
+
if (setMatch) return `z.array(${irTypeToZod(setMatch[1])})`;
|
|
30
|
+
const optMatch = irType.match(/^optional<(.+)>$/);
|
|
31
|
+
if (optMatch) return `${irTypeToZod(optMatch[1])}.nullable()`;
|
|
32
|
+
return "z.unknown()";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function applyConstraints(
|
|
36
|
+
baseZod: string,
|
|
37
|
+
fieldName: string,
|
|
38
|
+
constraints: IR.IRModelConstraint[]
|
|
39
|
+
): string {
|
|
40
|
+
let result = baseZod;
|
|
41
|
+
for (const c of constraints) {
|
|
42
|
+
if (c.target !== fieldName) continue;
|
|
43
|
+
if (c.kind === "range") {
|
|
44
|
+
if (c.params.min !== undefined) result += `.min(${c.params.min})`;
|
|
45
|
+
if (c.params.max !== undefined) result += `.max(${c.params.max})`;
|
|
46
|
+
} else if (c.kind === "enum") {
|
|
47
|
+
const vals = c.params.values as string[];
|
|
48
|
+
if (vals && vals.length > 0) {
|
|
49
|
+
result = `z.enum([${vals.map((v) => JSON.stringify(v)).join(", ")}])`;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
export function emitZodSchemas(system: IR.IRSystem): string {
|
|
59
|
+
const lines: string[] = [];
|
|
60
|
+
|
|
61
|
+
lines.push(`// Generated by BoneScript compiler.`);
|
|
62
|
+
lines.push(`import { z } from "zod";`);
|
|
63
|
+
lines.push("");
|
|
64
|
+
|
|
65
|
+
// Model schemas
|
|
66
|
+
for (const mod of system.modules) {
|
|
67
|
+
for (const model of mod.models) {
|
|
68
|
+
const schemaName = toPascalCase(model.name) + "Schema";
|
|
69
|
+
const typeName = toPascalCase(model.name);
|
|
70
|
+
|
|
71
|
+
lines.push(`export const ${schemaName} = z.object({`);
|
|
72
|
+
for (const field of model.fields) {
|
|
73
|
+
let zodType = irTypeToZod(field.type);
|
|
74
|
+
zodType = applyConstraints(zodType, field.name, model.constraints);
|
|
75
|
+
if (field.nullable) {
|
|
76
|
+
zodType = zodType + ".nullable()";
|
|
77
|
+
}
|
|
78
|
+
lines.push(` ${field.name}: ${zodType},`);
|
|
79
|
+
}
|
|
80
|
+
lines.push(`});`);
|
|
81
|
+
lines.push(`export type ${typeName} = z.infer<typeof ${schemaName}>;`);
|
|
82
|
+
lines.push("");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Capability input schemas
|
|
87
|
+
for (const mod of system.modules) {
|
|
88
|
+
for (const iface of mod.interfaces) {
|
|
89
|
+
for (const method of iface.methods) {
|
|
90
|
+
if (method.input.length === 0) continue;
|
|
91
|
+
const modPascal = toPascalCase(mod.name);
|
|
92
|
+
const methodPascal = toPascalCase(method.name);
|
|
93
|
+
const schemaName = `${modPascal}${methodPascal}InputSchema`;
|
|
94
|
+
|
|
95
|
+
lines.push(`export const ${schemaName} = z.object({`);
|
|
96
|
+
for (const field of method.input) {
|
|
97
|
+
const zodType = irTypeToZod(field.type);
|
|
98
|
+
lines.push(` ${field.name}: ${zodType},`);
|
|
99
|
+
}
|
|
100
|
+
lines.push(`});`);
|
|
101
|
+
lines.push("");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return lines.join("\n");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function emitZodPackageAdditions(): string {
|
|
110
|
+
return JSON.stringify({ zod: "3.22.4" }, null, 2);
|
|
111
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -40,6 +40,15 @@ export { lookupAlgorithm, listAlgorithms, listByCategory } from "./algorithm_cat
|
|
|
40
40
|
// Extension system
|
|
41
41
|
export { mergeWithExisting, extractImplementations, validateExtensions } from "./extension_manager";
|
|
42
42
|
|
|
43
|
+
// New emitters
|
|
44
|
+
export { emitOpenApiSpec, emitOpenApiJson } from "./emit_openapi";
|
|
45
|
+
export { emitTypescriptSdk, emitSdkPackageJson } from "./emit_sdk";
|
|
46
|
+
export { emitZodSchemas } from "./emit_zod";
|
|
47
|
+
export { emitPostmanCollection } from "./emit_postman";
|
|
48
|
+
export { emitSeedFile } from "./emit_seed";
|
|
49
|
+
export { emitAuditSchema, emitAuditMiddleware } from "./emit_audit";
|
|
50
|
+
export { emitAdminPanel } from "./emit_admin";
|
|
51
|
+
|
|
43
52
|
/**
|
|
44
53
|
* Convenience function: compile a .bone source string to files.
|
|
45
54
|
*/
|
package/src/lowering.ts
CHANGED
|
@@ -118,7 +118,7 @@ export class Lowering {
|
|
|
118
118
|
const relatedCaps = capabilities.filter(c =>
|
|
119
119
|
c.params.some(p => p.type.kind === "EntityRefType" && p.type.name === entity.name)
|
|
120
120
|
);
|
|
121
|
-
modules.push(this.lowerEntity(entity, relatedCaps, stores));
|
|
121
|
+
modules.push(this.lowerEntity(entity, relatedCaps, stores, policies));
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
// Lower channels → realtime_service modules
|
|
@@ -233,7 +233,7 @@ export class Lowering {
|
|
|
233
233
|
|
|
234
234
|
// ââ€Âۉâ€Âۉâ€Â€ Entity Lowering ââ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Âۉâ€Â€
|
|
235
235
|
|
|
236
|
-
private lowerEntity(entity: AST.EntityDeclNode, capabilities: AST.CapabilityDeclNode[], stores: AST.StoreDeclNode[]): IR.IRModule {
|
|
236
|
+
private lowerEntity(entity: AST.EntityDeclNode, capabilities: AST.CapabilityDeclNode[], stores: AST.StoreDeclNode[], policies: AST.PolicyDeclNode[] = []): IR.IRModule {
|
|
237
237
|
const moduleId = makeId(this.systemName, "api_service", `${entity.name}Service`);
|
|
238
238
|
|
|
239
239
|
// Build model from entity fields + ontology entailments
|
|
@@ -380,6 +380,9 @@ export class Lowering {
|
|
|
380
380
|
config: {
|
|
381
381
|
authenticated: entity.auth !== null && entity.auth !== "none",
|
|
382
382
|
auth_method: entity.auth || "none",
|
|
383
|
+
audit: policies.some(p => p.audit === true),
|
|
384
|
+
rate_limit: policies.length > 0 && policies[0].rateLimit ? policies[0].rateLimit.count : 0,
|
|
385
|
+
rate_limit_window_ms: policies.length > 0 && policies[0].rateLimit ? (parseDurationMs(String(policies[0].rateLimit.per)) || 60000) : 60000,
|
|
383
386
|
},
|
|
384
387
|
};
|
|
385
388
|
}
|