bonescript-compiler 0.2.1 → 0.4.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/LICENSE +21 -21
- package/dist/algorithm_catalog.js +166 -166
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +75 -543
- package/dist/cli.js.map +1 -1
- package/dist/commands/check.d.ts +5 -0
- package/dist/commands/check.js +34 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/compile.d.ts +5 -0
- package/dist/commands/compile.js +215 -0
- package/dist/commands/compile.js.map +1 -0
- package/dist/commands/debug.d.ts +5 -0
- package/dist/commands/debug.js +59 -0
- package/dist/commands/debug.js.map +1 -0
- package/dist/commands/diff.d.ts +5 -0
- package/dist/commands/diff.js +125 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/fmt.d.ts +5 -0
- package/dist/commands/fmt.js +49 -0
- package/dist/commands/fmt.js.map +1 -0
- package/dist/commands/init.d.ts +5 -0
- package/dist/commands/init.js +96 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/ir.d.ts +5 -0
- package/dist/commands/ir.js +27 -0
- package/dist/commands/ir.js.map +1 -0
- package/dist/commands/lex.d.ts +5 -0
- package/dist/commands/lex.js +21 -0
- package/dist/commands/lex.js.map +1 -0
- package/dist/commands/parse.d.ts +5 -0
- package/dist/commands/parse.js +30 -0
- package/dist/commands/parse.js.map +1 -0
- package/dist/commands/test.d.ts +5 -0
- package/dist/commands/test.js +61 -0
- package/dist/commands/test.js.map +1 -0
- package/dist/commands/verify_determinism.d.ts +5 -0
- package/dist/commands/verify_determinism.js +64 -0
- package/dist/commands/verify_determinism.js.map +1 -0
- package/dist/commands/watch.d.ts +5 -0
- package/dist/commands/watch.js +50 -0
- package/dist/commands/watch.js.map +1 -0
- package/dist/emit_auth.d.ts +6 -0
- package/dist/emit_auth.js +69 -0
- package/dist/emit_auth.js.map +1 -0
- package/dist/emit_capability.d.ts +13 -0
- package/dist/emit_capability.js +292 -128
- package/dist/emit_capability.js.map +1 -1
- package/dist/emit_composition.js +37 -3
- package/dist/emit_composition.js.map +1 -1
- package/dist/emit_database.d.ts +7 -0
- package/dist/emit_database.js +74 -0
- package/dist/emit_database.js.map +1 -0
- package/dist/emit_deploy.js +162 -162
- package/dist/emit_events.d.ts +1 -0
- package/dist/emit_events.js +342 -275
- package/dist/emit_events.js.map +1 -1
- package/dist/emit_full.js +135 -95
- package/dist/emit_full.js.map +1 -1
- package/dist/emit_index.d.ts +6 -0
- package/dist/emit_index.js +157 -0
- package/dist/emit_index.js.map +1 -0
- package/dist/emit_maintenance.js +249 -249
- package/dist/emit_models.d.ts +12 -0
- package/dist/emit_models.js +171 -0
- package/dist/emit_models.js.map +1 -0
- package/dist/emit_openapi.d.ts +9 -0
- package/dist/emit_openapi.js +308 -0
- package/dist/emit_openapi.js.map +1 -0
- package/dist/emit_package.d.ts +7 -0
- package/dist/emit_package.js +70 -0
- package/dist/emit_package.js.map +1 -0
- package/dist/emit_router.d.ts +12 -0
- package/dist/emit_router.js +390 -0
- package/dist/emit_router.js.map +1 -0
- package/dist/emit_runtime.d.ts +17 -11
- package/dist/emit_runtime.js +29 -686
- package/dist/emit_runtime.js.map +1 -1
- package/dist/emit_sourcemap.js +66 -66
- package/dist/emit_tests.js +37 -0
- package/dist/emit_tests.js.map +1 -1
- package/dist/emitter.js +34 -5
- package/dist/emitter.js.map +1 -1
- package/dist/extension_manager.d.ts +2 -2
- package/dist/extension_manager.js +6 -3
- package/dist/extension_manager.js.map +1 -1
- package/dist/lowering.d.ts +5 -14
- package/dist/lowering.js +47 -417
- package/dist/lowering.js.map +1 -1
- package/dist/lowering_channels.d.ts +11 -0
- package/dist/lowering_channels.js +102 -0
- package/dist/lowering_channels.js.map +1 -0
- package/dist/lowering_entities.d.ts +11 -0
- package/dist/lowering_entities.js +222 -0
- package/dist/lowering_entities.js.map +1 -0
- package/dist/lowering_helpers.d.ts +13 -0
- package/dist/lowering_helpers.js +76 -0
- package/dist/lowering_helpers.js.map +1 -0
- package/dist/module_loader.d.ts +2 -2
- package/dist/module_loader.js +20 -23
- package/dist/module_loader.js.map +1 -1
- package/dist/scaffold.d.ts +2 -2
- package/dist/scaffold.js +316 -319
- package/dist/scaffold.js.map +1 -1
- package/dist/typechecker.js +32 -13
- package/dist/typechecker.js.map +1 -1
- package/dist/verifier.d.ts +5 -0
- package/dist/verifier.js +140 -2
- package/dist/verifier.js.map +1 -1
- package/package.json +62 -52
- package/src/algorithm_catalog.ts +345 -345
- package/src/ast.ts +334 -334
- package/src/cli.ts +98 -624
- package/src/commands/check.ts +33 -0
- package/src/commands/compile.ts +191 -0
- package/src/commands/debug.ts +33 -0
- package/src/commands/diff.ts +108 -0
- package/src/commands/fmt.ts +22 -0
- package/src/commands/init.ts +72 -0
- package/src/commands/ir.ts +23 -0
- package/src/commands/lex.ts +17 -0
- package/src/commands/parse.ts +24 -0
- package/src/commands/test.ts +36 -0
- package/src/commands/verify_determinism.ts +66 -0
- package/src/commands/watch.ts +25 -0
- package/src/emit_auth.ts +67 -0
- package/src/emit_batch.ts +140 -140
- package/src/emit_capability.ts +617 -436
- package/src/emit_composition.ts +229 -196
- package/src/emit_database.ts +75 -0
- package/src/emit_deploy.ts +190 -190
- package/src/emit_events.ts +377 -307
- package/src/emit_extras.ts +240 -240
- package/src/emit_full.ts +351 -309
- package/src/emit_index.ts +161 -0
- package/src/emit_maintenance.ts +459 -459
- package/src/emit_models.ts +176 -0
- package/src/emit_openapi.ts +318 -0
- package/src/emit_package.ts +69 -0
- package/src/emit_router.ts +409 -0
- package/src/emit_runtime.ts +17 -728
- package/src/emit_sourcemap.ts +140 -140
- package/src/emit_tests.ts +246 -205
- package/src/emit_websocket.ts +229 -229
- package/src/emitter.ts +31 -5
- package/src/extension_manager.ts +189 -187
- package/src/formatter.ts +297 -297
- package/src/index.ts +88 -88
- package/src/ir.ts +215 -215
- package/src/lexer.ts +630 -630
- package/src/lowering.ts +142 -556
- package/src/lowering_channels.ts +107 -0
- package/src/lowering_entities.ts +248 -0
- package/src/lowering_helpers.ts +75 -0
- package/src/module_loader.ts +112 -114
- package/src/optimizer.ts +196 -196
- package/src/parse_decls.ts +409 -409
- package/src/parse_decls2.ts +244 -244
- package/src/parse_expr.ts +197 -197
- package/src/parse_types.ts +54 -54
- package/src/parser.ts +1 -1
- package/src/parser_base.ts +57 -57
- package/src/parser_recovery.ts +153 -153
- package/src/scaffold.ts +372 -375
- package/src/solver.ts +330 -330
- package/src/typechecker.ts +30 -15
- package/src/types.ts +122 -122
- package/src/verifier.ts +151 -4
package/src/emit_auth.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BoneScript Auth Emitter
|
|
3
|
+
* Generates auth.ts — JWT middleware with production safety checks.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as IR from "./ir";
|
|
7
|
+
|
|
8
|
+
export function emitAuthMiddleware(_system: IR.IRSystem): string {
|
|
9
|
+
return `// Generated by BoneScript compiler. DO NOT EDIT.
|
|
10
|
+
import { Request, Response, NextFunction } from "express";
|
|
11
|
+
import jwt from "jsonwebtoken";
|
|
12
|
+
|
|
13
|
+
// JWT_SECRET must be set in production. The server will refuse to start without it
|
|
14
|
+
// when NODE_ENV is "production" to prevent accidental use of a weak fallback.
|
|
15
|
+
const JWT_SECRET = (() => {
|
|
16
|
+
const secret = process.env.JWT_SECRET;
|
|
17
|
+
if (!secret) {
|
|
18
|
+
if (process.env.NODE_ENV === "production") {
|
|
19
|
+
console.error("[FATAL] JWT_SECRET environment variable is not set. Refusing to start in production.");
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
console.warn("[WARN] JWT_SECRET is not set. Using insecure default — do not use in production.");
|
|
23
|
+
return "bonescript-dev-secret-do-not-use-in-production";
|
|
24
|
+
}
|
|
25
|
+
if (secret.length < 32) {
|
|
26
|
+
console.warn("[WARN] JWT_SECRET is shorter than 32 characters. Use a longer secret in production.");
|
|
27
|
+
}
|
|
28
|
+
return secret;
|
|
29
|
+
})();
|
|
30
|
+
|
|
31
|
+
export interface AuthContext {
|
|
32
|
+
authenticated: boolean;
|
|
33
|
+
actor_id: string | null;
|
|
34
|
+
trace_id: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function authMiddleware(req: Request, res: Response, next: NextFunction): void {
|
|
38
|
+
const header = req.headers.authorization;
|
|
39
|
+
if (!header || !header.startsWith("Bearer ")) {
|
|
40
|
+
(req as any).auth = { authenticated: false, actor_id: null, trace_id: req.headers["x-trace-id"] as string || "" };
|
|
41
|
+
next();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
const token = header.slice(7);
|
|
46
|
+
const decoded = jwt.verify(token, JWT_SECRET) as { sub: string };
|
|
47
|
+
(req as any).auth = {
|
|
48
|
+
authenticated: true,
|
|
49
|
+
actor_id: decoded.sub,
|
|
50
|
+
trace_id: req.headers["x-trace-id"] as string || "",
|
|
51
|
+
};
|
|
52
|
+
} catch {
|
|
53
|
+
(req as any).auth = { authenticated: false, actor_id: null, trace_id: req.headers["x-trace-id"] as string || "" };
|
|
54
|
+
}
|
|
55
|
+
next();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function requireAuth(req: Request, res: Response, next: NextFunction): void {
|
|
59
|
+
const auth: AuthContext = (req as any).auth;
|
|
60
|
+
if (!auth || !auth.authenticated) {
|
|
61
|
+
res.status(401).json({ error: { code: "UNAUTHORIZED", message: "Authentication required" } });
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
next();
|
|
65
|
+
}
|
|
66
|
+
`;
|
|
67
|
+
}
|
package/src/emit_batch.ts
CHANGED
|
@@ -1,140 +1,140 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* BoneScript Batch Executor Emitter
|
|
3
|
-
* Generates a batch processing module for sync: batch capabilities.
|
|
4
|
-
* Batch capabilities are queued and processed in configurable intervals.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import * as IR from "./ir";
|
|
8
|
-
|
|
9
|
-
export function emitBatchExecutor(system: IR.IRSystem): string {
|
|
10
|
-
// Collect all batch capabilities
|
|
11
|
-
const batchMethods: { modName: string; method: IR.IRMethod }[] = [];
|
|
12
|
-
for (const mod of system.modules) {
|
|
13
|
-
for (const iface of mod.interfaces) {
|
|
14
|
-
for (const method of iface.methods) {
|
|
15
|
-
if (method.sync === "batch") {
|
|
16
|
-
batchMethods.push({ modName: mod.name, method });
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (batchMethods.length === 0) return "";
|
|
23
|
-
|
|
24
|
-
const lines: string[] = [];
|
|
25
|
-
lines.push(`// Generated by BoneScript compiler. DO NOT EDIT.`);
|
|
26
|
-
lines.push(`// Batch executor for sync: batch capabilities.`);
|
|
27
|
-
lines.push(`// Set BATCH_INTERVAL_MS to control flush frequency (default: 5000ms).`);
|
|
28
|
-
lines.push(``);
|
|
29
|
-
lines.push(`import { query } from "./db";`);
|
|
30
|
-
lines.push(`import { logger } from "./logger";`);
|
|
31
|
-
lines.push(`import { counter, histogram } from "./metrics";`);
|
|
32
|
-
lines.push(``);
|
|
33
|
-
|
|
34
|
-
// Batch queue type
|
|
35
|
-
lines.push(`interface BatchItem {`);
|
|
36
|
-
lines.push(` capability: string;`);
|
|
37
|
-
lines.push(` payload: Record<string, unknown>;`);
|
|
38
|
-
lines.push(` enqueuedAt: Date;`);
|
|
39
|
-
lines.push(` resolve: (result: any) => void;`);
|
|
40
|
-
lines.push(` reject: (err: Error) => void;`);
|
|
41
|
-
lines.push(`}`);
|
|
42
|
-
lines.push(``);
|
|
43
|
-
|
|
44
|
-
// Per-capability queues
|
|
45
|
-
lines.push(`const queues: Map<string, BatchItem[]> = new Map([`);
|
|
46
|
-
for (const { modName, method } of batchMethods) {
|
|
47
|
-
lines.push(` ["${modName}.${method.name}", []],`);
|
|
48
|
-
}
|
|
49
|
-
lines.push(`]);`);
|
|
50
|
-
lines.push(``);
|
|
51
|
-
|
|
52
|
-
// Enqueue function
|
|
53
|
-
lines.push(`export function enqueueBatch(capability: string, payload: Record<string, unknown>): Promise<any> {`);
|
|
54
|
-
lines.push(` return new Promise((resolve, reject) => {`);
|
|
55
|
-
lines.push(` const queue = queues.get(capability);`);
|
|
56
|
-
lines.push(` if (!queue) {`);
|
|
57
|
-
lines.push(` reject(new Error(\`Unknown batch capability: \${capability}\`));`);
|
|
58
|
-
lines.push(` return;`);
|
|
59
|
-
lines.push(` }`);
|
|
60
|
-
lines.push(` queue.push({ capability, payload, enqueuedAt: new Date(), resolve, reject });`);
|
|
61
|
-
lines.push(` counter("batch.enqueued", { capability });`);
|
|
62
|
-
lines.push(` });`);
|
|
63
|
-
lines.push(`}`);
|
|
64
|
-
lines.push(``);
|
|
65
|
-
|
|
66
|
-
// Flush function — processes all queued items
|
|
67
|
-
lines.push(`async function flushBatch(capability: string, items: BatchItem[]): Promise<void> {`);
|
|
68
|
-
lines.push(` if (items.length === 0) return;`);
|
|
69
|
-
lines.push(` const start = Date.now();`);
|
|
70
|
-
lines.push(` logger.info("batch_flush_started", { event: capability, metadata: { count: items.length } });`);
|
|
71
|
-
lines.push(``);
|
|
72
|
-
lines.push(` // Process items in a single DB transaction`);
|
|
73
|
-
lines.push(` const { pool } = require("./db");`);
|
|
74
|
-
lines.push(` const client = await pool.connect();`);
|
|
75
|
-
lines.push(` try {`);
|
|
76
|
-
lines.push(` await client.query("BEGIN");`);
|
|
77
|
-
lines.push(` for (const item of items) {`);
|
|
78
|
-
lines.push(` try {`);
|
|
79
|
-
lines.push(` // Execute the batch item`);
|
|
80
|
-
lines.push(` // Each capability's batch handler is registered below`);
|
|
81
|
-
lines.push(` const handler = batchHandlers.get(capability);`);
|
|
82
|
-
lines.push(` if (handler) {`);
|
|
83
|
-
lines.push(` const result = await handler(item.payload, client);`);
|
|
84
|
-
lines.push(` item.resolve(result);`);
|
|
85
|
-
lines.push(` } else {`);
|
|
86
|
-
lines.push(` item.reject(new Error(\`No batch handler for \${capability}\`));`);
|
|
87
|
-
lines.push(` }`);
|
|
88
|
-
lines.push(` } catch (e: any) {`);
|
|
89
|
-
lines.push(` item.reject(e);`);
|
|
90
|
-
lines.push(` }`);
|
|
91
|
-
lines.push(` }`);
|
|
92
|
-
lines.push(` await client.query("COMMIT");`);
|
|
93
|
-
lines.push(` histogram("batch.duration_ms", Date.now() - start, { capability });`);
|
|
94
|
-
lines.push(` counter("batch.processed", { capability, count: String(items.length) });`);
|
|
95
|
-
lines.push(` logger.info("batch_flush_completed", { event: capability, metadata: { count: items.length, duration_ms: Date.now() - start } });`);
|
|
96
|
-
lines.push(` } catch (e: any) {`);
|
|
97
|
-
lines.push(` await client.query("ROLLBACK");`);
|
|
98
|
-
lines.push(` for (const item of items) item.reject(e);`);
|
|
99
|
-
lines.push(` logger.error("batch_flush_failed", { event: capability, metadata: { error: e.message } });`);
|
|
100
|
-
lines.push(` } finally {`);
|
|
101
|
-
lines.push(` client.release();`);
|
|
102
|
-
lines.push(` }`);
|
|
103
|
-
lines.push(`}`);
|
|
104
|
-
lines.push(``);
|
|
105
|
-
|
|
106
|
-
// Batch handler registry
|
|
107
|
-
lines.push(`type BatchHandler = (payload: Record<string, unknown>, client: any) => Promise<any>;`);
|
|
108
|
-
lines.push(`const batchHandlers: Map<string, BatchHandler> = new Map();`);
|
|
109
|
-
lines.push(``);
|
|
110
|
-
lines.push(`export function registerBatchHandler(capability: string, handler: BatchHandler): void {`);
|
|
111
|
-
lines.push(` batchHandlers.set(capability, handler);`);
|
|
112
|
-
lines.push(`}`);
|
|
113
|
-
lines.push(``);
|
|
114
|
-
|
|
115
|
-
// Batch worker — flushes all queues on interval
|
|
116
|
-
lines.push(`export function startBatchWorker(intervalMs: number = parseInt(process.env.BATCH_INTERVAL_MS || "5000")): NodeJS.Timeout {`);
|
|
117
|
-
lines.push(` logger.info("batch_worker_started", { event: "startup", metadata: { interval_ms: intervalMs } });`);
|
|
118
|
-
lines.push(` return setInterval(async () => {`);
|
|
119
|
-
lines.push(` for (const [capability, queue] of queues) {`);
|
|
120
|
-
lines.push(` if (queue.length === 0) continue;`);
|
|
121
|
-
lines.push(` const batch = queue.splice(0, queue.length); // drain queue atomically`);
|
|
122
|
-
lines.push(` await flushBatch(capability, batch).catch(e => {`);
|
|
123
|
-
lines.push(` logger.error("batch_worker_error", { event: capability, metadata: { error: e.message } });`);
|
|
124
|
-
lines.push(` });`);
|
|
125
|
-
lines.push(` }`);
|
|
126
|
-
lines.push(` }, intervalMs);`);
|
|
127
|
-
lines.push(`}`);
|
|
128
|
-
lines.push(``);
|
|
129
|
-
|
|
130
|
-
// Capability-specific stub handlers
|
|
131
|
-
for (const { modName, method } of batchMethods) {
|
|
132
|
-
const key = `${modName}.${method.name}`;
|
|
133
|
-
lines.push(`// Batch handler for ${key}`);
|
|
134
|
-
lines.push(`// Register your implementation:`);
|
|
135
|
-
lines.push(`// registerBatchHandler("${key}", async (payload, client) => { ... });`);
|
|
136
|
-
lines.push(``);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return lines.join("\n");
|
|
140
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* BoneScript Batch Executor Emitter
|
|
3
|
+
* Generates a batch processing module for sync: batch capabilities.
|
|
4
|
+
* Batch capabilities are queued and processed in configurable intervals.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as IR from "./ir";
|
|
8
|
+
|
|
9
|
+
export function emitBatchExecutor(system: IR.IRSystem): string {
|
|
10
|
+
// Collect all batch capabilities
|
|
11
|
+
const batchMethods: { modName: string; method: IR.IRMethod }[] = [];
|
|
12
|
+
for (const mod of system.modules) {
|
|
13
|
+
for (const iface of mod.interfaces) {
|
|
14
|
+
for (const method of iface.methods) {
|
|
15
|
+
if (method.sync === "batch") {
|
|
16
|
+
batchMethods.push({ modName: mod.name, method });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (batchMethods.length === 0) return "";
|
|
23
|
+
|
|
24
|
+
const lines: string[] = [];
|
|
25
|
+
lines.push(`// Generated by BoneScript compiler. DO NOT EDIT.`);
|
|
26
|
+
lines.push(`// Batch executor for sync: batch capabilities.`);
|
|
27
|
+
lines.push(`// Set BATCH_INTERVAL_MS to control flush frequency (default: 5000ms).`);
|
|
28
|
+
lines.push(``);
|
|
29
|
+
lines.push(`import { query } from "./db";`);
|
|
30
|
+
lines.push(`import { logger } from "./logger";`);
|
|
31
|
+
lines.push(`import { counter, histogram } from "./metrics";`);
|
|
32
|
+
lines.push(``);
|
|
33
|
+
|
|
34
|
+
// Batch queue type
|
|
35
|
+
lines.push(`interface BatchItem {`);
|
|
36
|
+
lines.push(` capability: string;`);
|
|
37
|
+
lines.push(` payload: Record<string, unknown>;`);
|
|
38
|
+
lines.push(` enqueuedAt: Date;`);
|
|
39
|
+
lines.push(` resolve: (result: any) => void;`);
|
|
40
|
+
lines.push(` reject: (err: Error) => void;`);
|
|
41
|
+
lines.push(`}`);
|
|
42
|
+
lines.push(``);
|
|
43
|
+
|
|
44
|
+
// Per-capability queues
|
|
45
|
+
lines.push(`const queues: Map<string, BatchItem[]> = new Map([`);
|
|
46
|
+
for (const { modName, method } of batchMethods) {
|
|
47
|
+
lines.push(` ["${modName}.${method.name}", []],`);
|
|
48
|
+
}
|
|
49
|
+
lines.push(`]);`);
|
|
50
|
+
lines.push(``);
|
|
51
|
+
|
|
52
|
+
// Enqueue function
|
|
53
|
+
lines.push(`export function enqueueBatch(capability: string, payload: Record<string, unknown>): Promise<any> {`);
|
|
54
|
+
lines.push(` return new Promise((resolve, reject) => {`);
|
|
55
|
+
lines.push(` const queue = queues.get(capability);`);
|
|
56
|
+
lines.push(` if (!queue) {`);
|
|
57
|
+
lines.push(` reject(new Error(\`Unknown batch capability: \${capability}\`));`);
|
|
58
|
+
lines.push(` return;`);
|
|
59
|
+
lines.push(` }`);
|
|
60
|
+
lines.push(` queue.push({ capability, payload, enqueuedAt: new Date(), resolve, reject });`);
|
|
61
|
+
lines.push(` counter("batch.enqueued", { capability });`);
|
|
62
|
+
lines.push(` });`);
|
|
63
|
+
lines.push(`}`);
|
|
64
|
+
lines.push(``);
|
|
65
|
+
|
|
66
|
+
// Flush function — processes all queued items
|
|
67
|
+
lines.push(`async function flushBatch(capability: string, items: BatchItem[]): Promise<void> {`);
|
|
68
|
+
lines.push(` if (items.length === 0) return;`);
|
|
69
|
+
lines.push(` const start = Date.now();`);
|
|
70
|
+
lines.push(` logger.info("batch_flush_started", { event: capability, metadata: { count: items.length } });`);
|
|
71
|
+
lines.push(``);
|
|
72
|
+
lines.push(` // Process items in a single DB transaction`);
|
|
73
|
+
lines.push(` const { pool } = require("./db");`);
|
|
74
|
+
lines.push(` const client = await pool.connect();`);
|
|
75
|
+
lines.push(` try {`);
|
|
76
|
+
lines.push(` await client.query("BEGIN");`);
|
|
77
|
+
lines.push(` for (const item of items) {`);
|
|
78
|
+
lines.push(` try {`);
|
|
79
|
+
lines.push(` // Execute the batch item`);
|
|
80
|
+
lines.push(` // Each capability's batch handler is registered below`);
|
|
81
|
+
lines.push(` const handler = batchHandlers.get(capability);`);
|
|
82
|
+
lines.push(` if (handler) {`);
|
|
83
|
+
lines.push(` const result = await handler(item.payload, client);`);
|
|
84
|
+
lines.push(` item.resolve(result);`);
|
|
85
|
+
lines.push(` } else {`);
|
|
86
|
+
lines.push(` item.reject(new Error(\`No batch handler for \${capability}\`));`);
|
|
87
|
+
lines.push(` }`);
|
|
88
|
+
lines.push(` } catch (e: any) {`);
|
|
89
|
+
lines.push(` item.reject(e);`);
|
|
90
|
+
lines.push(` }`);
|
|
91
|
+
lines.push(` }`);
|
|
92
|
+
lines.push(` await client.query("COMMIT");`);
|
|
93
|
+
lines.push(` histogram("batch.duration_ms", Date.now() - start, { capability });`);
|
|
94
|
+
lines.push(` counter("batch.processed", { capability, count: String(items.length) });`);
|
|
95
|
+
lines.push(` logger.info("batch_flush_completed", { event: capability, metadata: { count: items.length, duration_ms: Date.now() - start } });`);
|
|
96
|
+
lines.push(` } catch (e: any) {`);
|
|
97
|
+
lines.push(` await client.query("ROLLBACK");`);
|
|
98
|
+
lines.push(` for (const item of items) item.reject(e);`);
|
|
99
|
+
lines.push(` logger.error("batch_flush_failed", { event: capability, metadata: { error: e.message } });`);
|
|
100
|
+
lines.push(` } finally {`);
|
|
101
|
+
lines.push(` client.release();`);
|
|
102
|
+
lines.push(` }`);
|
|
103
|
+
lines.push(`}`);
|
|
104
|
+
lines.push(``);
|
|
105
|
+
|
|
106
|
+
// Batch handler registry
|
|
107
|
+
lines.push(`type BatchHandler = (payload: Record<string, unknown>, client: any) => Promise<any>;`);
|
|
108
|
+
lines.push(`const batchHandlers: Map<string, BatchHandler> = new Map();`);
|
|
109
|
+
lines.push(``);
|
|
110
|
+
lines.push(`export function registerBatchHandler(capability: string, handler: BatchHandler): void {`);
|
|
111
|
+
lines.push(` batchHandlers.set(capability, handler);`);
|
|
112
|
+
lines.push(`}`);
|
|
113
|
+
lines.push(``);
|
|
114
|
+
|
|
115
|
+
// Batch worker — flushes all queues on interval
|
|
116
|
+
lines.push(`export function startBatchWorker(intervalMs: number = parseInt(process.env.BATCH_INTERVAL_MS || "5000")): NodeJS.Timeout {`);
|
|
117
|
+
lines.push(` logger.info("batch_worker_started", { event: "startup", metadata: { interval_ms: intervalMs } });`);
|
|
118
|
+
lines.push(` return setInterval(async () => {`);
|
|
119
|
+
lines.push(` for (const [capability, queue] of queues) {`);
|
|
120
|
+
lines.push(` if (queue.length === 0) continue;`);
|
|
121
|
+
lines.push(` const batch = queue.splice(0, queue.length); // drain queue atomically`);
|
|
122
|
+
lines.push(` await flushBatch(capability, batch).catch(e => {`);
|
|
123
|
+
lines.push(` logger.error("batch_worker_error", { event: capability, metadata: { error: e.message } });`);
|
|
124
|
+
lines.push(` });`);
|
|
125
|
+
lines.push(` }`);
|
|
126
|
+
lines.push(` }, intervalMs);`);
|
|
127
|
+
lines.push(`}`);
|
|
128
|
+
lines.push(``);
|
|
129
|
+
|
|
130
|
+
// Capability-specific stub handlers
|
|
131
|
+
for (const { modName, method } of batchMethods) {
|
|
132
|
+
const key = `${modName}.${method.name}`;
|
|
133
|
+
lines.push(`// Batch handler for ${key}`);
|
|
134
|
+
lines.push(`// Register your implementation:`);
|
|
135
|
+
lines.push(`// registerBatchHandler("${key}", async (payload, client) => { ... });`);
|
|
136
|
+
lines.push(``);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return lines.join("\n");
|
|
140
|
+
}
|