irgen 0.2.2 → 0.3.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.
@@ -0,0 +1,71 @@
1
+ export function validateSemantics(bundle) {
2
+ const messages = [];
3
+ const entities = new Set();
4
+ const components = new Set();
5
+ // DEBUG
6
+ // console.log("Validating bundle:", JSON.stringify(bundle, null, 2));
7
+ // 1. Collect Definitions (Deduplicated for reference checking)
8
+ for (const app of bundle.apps) {
9
+ const a = app;
10
+ // Entities
11
+ if (a.entities) {
12
+ for (const entity of a.entities) {
13
+ if (entities.has(entity.name)) {
14
+ messages.push({ type: "error", message: `Duplicate Entity name: "${entity.name}"` });
15
+ }
16
+ entities.add(entity.name);
17
+ }
18
+ }
19
+ // Shared Components
20
+ if (a.components) {
21
+ for (const comp of a.components) {
22
+ components.add(comp.name);
23
+ }
24
+ }
25
+ }
26
+ // 2. Perform stricter checks for true duplicates (e.g. Page paths)
27
+ for (const app of bundle.apps) {
28
+ const a = app;
29
+ if (a.pages) {
30
+ const pathsInApp = new Set();
31
+ for (const page of a.pages) {
32
+ if (pathsInApp.has(page.path)) {
33
+ messages.push({ type: "warning", message: `Duplicate Page path in app "${a.name}": "${page.path}"` });
34
+ }
35
+ pathsInApp.add(page.path);
36
+ }
37
+ }
38
+ }
39
+ // 3. Validate References
40
+ for (const app of bundle.apps) {
41
+ const a = app;
42
+ // Check Shared Components references
43
+ if (a.components) {
44
+ for (const comp of a.components) {
45
+ // Check entityRef
46
+ if (comp.entityRef && !entities.has(comp.entityRef)) {
47
+ messages.push({
48
+ type: "error",
49
+ message: `Component "${comp.name}" references unknown Entity "${comp.entityRef}"`
50
+ });
51
+ }
52
+ }
53
+ }
54
+ // Check Pages
55
+ if (a.pages) {
56
+ for (const page of a.pages) {
57
+ if (page.components) {
58
+ for (const compCall of page.components) {
59
+ if (!components.has(compCall.name)) {
60
+ messages.push({
61
+ type: "error",
62
+ message: `Page "${page.name}" references unknown Component "${compCall.name}"`
63
+ });
64
+ }
65
+ }
66
+ }
67
+ }
68
+ }
69
+ }
70
+ return messages;
71
+ }
@@ -8,3 +8,4 @@ export declare function emitPaginationAdapter(project: Project, outDir: string,
8
8
  export declare function emitValidationAdapter(project: Project, outDir: string, policies?: any): void;
9
9
  export declare function emitResponseAdapter(project: Project, outDir: string, policies?: any): void;
10
10
  export declare function emitAuthAdapter(project: Project, outDir: string, policies?: any): void;
11
+ export declare function emitHealthAdapter(project: Project, outDir: string, policies?: any): void;
@@ -33,43 +33,34 @@ export function emitIdAdapter(project, outDir, policies) {
33
33
  }
34
34
  }
35
35
  export function emitLoggerAdapter(project, outDir, policies) {
36
- const impl = policies?.loggerImpl ?? policies?.core?.loggerImpl ?? "console";
36
+ const logging = policies?.logging ?? { enabled: true, level: "info", format: "json", redact: [] };
37
+ const impl = policies?.loggerImpl ?? policies?.core?.loggerImpl; // Fallback for legacy
37
38
  const sf = project.createSourceFile(path.join(outDir, "lib", "logger.ts"), "", { overwrite: true });
38
- sf.addStatements([`// Generated: logger adapter (${impl})`]);
39
- if (impl === "console") {
39
+ sf.addStatements([`// Generated: logger adapter`]);
40
+ if (logging.enabled === false) {
40
41
  sf.addStatements([
41
42
  `export const logger = {`,
42
- ` info: (...args: any[]) => console.info(...args),`,
43
- ` warn: (...args: any[]) => console.warn(...args),`,
44
- ` error: (...args: any[]) => console.error(...args),`,
45
- ` debug: (...args: any[]) => console.debug(...args),`,
43
+ ` info: (..._args: any[]) => {},`,
44
+ ` warn: (..._args: any[]) => {},`,
45
+ ` error: (..._args: any[]) => {},`,
46
+ ` debug: (..._args: any[]) => {},`,
47
+ ` child: (_bindings: any) => logger,`,
46
48
  `};`,
47
- ``,
48
- ]);
49
- }
50
- else if (impl === "pino" || impl === "winston") {
51
- sf.addStatements([
52
- `// ${impl} adapter: please add ${impl} as a dependency in the generated project`,
53
- `export const logger = {`,
54
- ` info: (...args: any[]) => console.info("[logger:${impl}]", ...args),`,
55
- ` warn: (...args: any[]) => console.warn("[logger:${impl}]", ...args),`,
56
- ` error: (...args: any[]) => console.error("[logger:${impl}]", ...args),`,
57
- ` debug: (...args: any[]) => console.debug("[logger:${impl}]", ...args),`,
58
- `};`,
59
- ``,
60
- ]);
61
- }
62
- else {
63
- sf.addStatements([
64
- `export const logger = {`,
65
- ` info: (..._args: any[]) => { throw new Error("logger implementation not provided"); },`,
66
- ` warn: (..._args: any[]) => { throw new Error("logger implementation not provided"); },`,
67
- ` error: (..._args: any[]) => { throw new Error("logger implementation not provided"); },`,
68
- ` debug: (..._args: any[]) => { throw new Error("logger implementation not provided"); },`,
69
- `};`,
70
- ``,
71
49
  ]);
50
+ return;
72
51
  }
52
+ // Preference: New logging policy > legacy core.loggerImpl
53
+ // If user explicitly asked for 'console' in legacy, we respect it IF new policy isn't set (but new policy has defaults...)
54
+ // Actually, let's just make 'pino' the standard for now if enabled.
55
+ sf.addStatements([
56
+ `import pino from "pino";`,
57
+ ``,
58
+ `export const logger = pino({`,
59
+ ` level: ${JSON.stringify(logging.level || "info")},`,
60
+ ...(logging.format === "pretty" ? [` transport: { target: "pino-pretty" },`] : []),
61
+ ...(logging.redact && logging.redact.length > 0 ? [` redact: ${JSON.stringify(logging.redact)},`] : []),
62
+ `});`,
63
+ ]);
73
64
  }
74
65
  export function emitContextAdapter(project, outDir, policies) {
75
66
  const sf = project.createSourceFile(path.join(outDir, "lib", "context.ts"), "", { overwrite: true });
@@ -371,3 +362,44 @@ export function emitAuthAdapter(project, outDir, policies) {
371
362
  `export function isAuthEnabled() { return JWT_ENABLED; }`,
372
363
  ]);
373
364
  }
365
+ export function emitHealthAdapter(project, outDir, policies) {
366
+ const health = policies?.health ?? { enabled: true, endpoint: "/health", metrics: { enabled: false, endpoint: "/metrics" } };
367
+ if (!health.enabled)
368
+ return;
369
+ const sf = project.createSourceFile(path.join(outDir, "lib", "health.ts"), "", { overwrite: true });
370
+ sf.addStatements([
371
+ `// Generated: health check and metrics adapter`,
372
+ `import type { Request, Response } from "express";`,
373
+ `import { logger } from "./logger";`,
374
+ ``,
375
+ `export async function healthCheck(req: Request, res: Response) {`,
376
+ ` // TODO: Add database connection check if DB is enabled`,
377
+ ` const status = {`,
378
+ ` status: "ok",`,
379
+ ` uptime: process.uptime(),`,
380
+ ` timestamp: new Date().toISOString(),`,
381
+ ` };`,
382
+ ` res.json(status);`,
383
+ `}`,
384
+ ``,
385
+ ]);
386
+ if (health.metrics?.enabled) {
387
+ sf.addStatements([
388
+ `import client from "prom-client";`,
389
+ ``,
390
+ `// Initialize metrics`,
391
+ `const collectDefaultMetrics = client.collectDefaultMetrics;`,
392
+ `collectDefaultMetrics();`,
393
+ ``,
394
+ `export async function metricsCheck(req: Request, res: Response) {`,
395
+ ` try {`,
396
+ ` res.set("Content-Type", client.register.contentType);`,
397
+ ` res.end(await client.register.metrics());`,
398
+ ` } catch (err) {`,
399
+ ` logger.error("Metrics error", err);`,
400
+ ` res.status(500).end(err);`,
401
+ ` }`,
402
+ `}`,
403
+ ]);
404
+ }
405
+ }