@specverse/engines 6.7.8 → 6.16.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.
Files changed (47) hide show
  1. package/dist/ai/behavior-ai-service.js +2 -2
  2. package/dist/ai/behavior-ai-service.js.map +1 -1
  3. package/dist/inference/core/specly-converter.d.ts.map +1 -1
  4. package/dist/inference/core/specly-converter.js +20 -0
  5. package/dist/inference/core/specly-converter.js.map +1 -1
  6. package/dist/inference/index.d.ts.map +1 -1
  7. package/dist/inference/index.js +72 -22
  8. package/dist/inference/index.js.map +1 -1
  9. package/dist/inference/logical/generators/controller-generator.d.ts.map +1 -1
  10. package/dist/inference/logical/generators/controller-generator.js +26 -4
  11. package/dist/inference/logical/generators/controller-generator.js.map +1 -1
  12. package/dist/libs/instance-factories/applications/templates/generic/backend-package-json-generator.js +22 -5
  13. package/dist/libs/instance-factories/controllers/templates/fastify/routes-generator.js +50 -15
  14. package/dist/libs/instance-factories/controllers/templates/fastify/server-generator.js +26 -6
  15. package/dist/libs/instance-factories/services/postgres-native-services.yaml +90 -0
  16. package/dist/libs/instance-factories/services/templates/_shared/step-matching.js +44 -0
  17. package/dist/libs/instance-factories/services/templates/mongodb-native/controller-generator.js +68 -13
  18. package/dist/libs/instance-factories/services/templates/mongodb-native/step-conventions.js +515 -0
  19. package/dist/libs/instance-factories/services/templates/postgres-native/client-generator.js +165 -0
  20. package/dist/libs/instance-factories/services/templates/postgres-native/controller-generator.js +300 -0
  21. package/dist/libs/instance-factories/services/templates/postgres-native/ddl-generator.js +169 -0
  22. package/dist/libs/instance-factories/services/templates/postgres-native/service-generator.js +65 -0
  23. package/dist/libs/instance-factories/services/templates/postgres-native/step-conventions.js +433 -0
  24. package/dist/libs/instance-factories/services/templates/prisma/ai-behaviors-generator.js +27 -4
  25. package/dist/libs/instance-factories/services/templates/prisma/step-conventions.js +7 -34
  26. package/dist/parser/processors/ExecutableProcessor.d.ts.map +1 -1
  27. package/dist/parser/processors/ExecutableProcessor.js +14 -1
  28. package/dist/parser/processors/ExecutableProcessor.js.map +1 -1
  29. package/dist/realize/index.d.ts.map +1 -1
  30. package/dist/realize/index.js +30 -3
  31. package/dist/realize/index.js.map +1 -1
  32. package/libs/instance-factories/applications/templates/generic/backend-package-json-generator.ts +46 -24
  33. package/libs/instance-factories/controllers/templates/fastify/routes-generator.ts +80 -21
  34. package/libs/instance-factories/controllers/templates/fastify/server-generator.ts +48 -7
  35. package/libs/instance-factories/services/postgres-native-services.yaml +90 -0
  36. package/libs/instance-factories/services/templates/_shared/step-matching.ts +103 -0
  37. package/libs/instance-factories/services/templates/mongodb-native/controller-generator.ts +97 -23
  38. package/libs/instance-factories/services/templates/mongodb-native/step-conventions.ts +691 -0
  39. package/libs/instance-factories/services/templates/postgres-native/__tests__/controller-generator.test.ts +193 -0
  40. package/libs/instance-factories/services/templates/postgres-native/client-generator.ts +178 -0
  41. package/libs/instance-factories/services/templates/postgres-native/controller-generator.ts +372 -0
  42. package/libs/instance-factories/services/templates/postgres-native/ddl-generator.ts +236 -0
  43. package/libs/instance-factories/services/templates/postgres-native/service-generator.ts +84 -0
  44. package/libs/instance-factories/services/templates/postgres-native/step-conventions.ts +539 -0
  45. package/libs/instance-factories/services/templates/prisma/ai-behaviors-generator.ts +61 -7
  46. package/libs/instance-factories/services/templates/prisma/step-conventions.ts +21 -68
  47. package/package.json +4 -3
@@ -0,0 +1,90 @@
1
+ name: PostgresNativeDriver
2
+ version: "1.0.0"
3
+ category: service
4
+ description: "Business logic services using the native node-postgres (pg) driver — raw SQL, no ORM layer"
5
+
6
+ metadata:
7
+ author: "SpecVerse Team"
8
+ license: "MIT"
9
+ tags: [services, business-logic, postgres, pg, native-driver, controllers]
10
+
11
+ compatibility:
12
+ specverse: ">=5.0.0"
13
+ node: ">=18.0.0"
14
+
15
+ # Same shape as MongoDBNativeDriver — the pg pool IS the data layer; no ORM
16
+ # layer to swap in independently. This single factory therefore provides
17
+ # both the orm.* capabilities and the service-layer capabilities.
18
+ capabilities:
19
+ provides:
20
+ - "orm.schema" # Emits the pg pool client + DDL (the connection IS the schema layer)
21
+ - "orm.client"
22
+ - "orm.postgres.native"
23
+ - "service.controller"
24
+ - "service.business"
25
+ - "service.crud"
26
+ requires:
27
+ - "storage.database.relational"
28
+
29
+ technology:
30
+ runtime: "node"
31
+ language: "typescript"
32
+ orm: "postgres-native"
33
+ version: "^8.11.0"
34
+
35
+ dependencies:
36
+ runtime:
37
+ - name: "pg"
38
+ version: "^8.11.0"
39
+
40
+ dev:
41
+ - name: "@types/pg"
42
+ version: "^8.11.0"
43
+ - name: "@types/node"
44
+ version: "^20.8.0"
45
+ - name: "typescript"
46
+ version: "^5.2.0"
47
+
48
+ codeTemplates:
49
+ # `schema` slot emits the pg pool singleton + a co-located CREATE TABLE
50
+ # script (schema.sql) so users can bootstrap a database without pulling
51
+ # in an external migrator. The pool generator owns the runtime
52
+ # connection; the DDL generator owns the static SQL.
53
+ schema:
54
+ engine: typescript
55
+ generator: "libs/instance-factories/services/templates/postgres-native/client-generator.ts"
56
+ outputPattern: "{backendDir}/src/db/pgClient.ts"
57
+
58
+ ddl:
59
+ engine: typescript
60
+ generator: "libs/instance-factories/services/templates/postgres-native/ddl-generator.ts"
61
+ outputPattern: "{backendDir}/src/db/schema.sql"
62
+
63
+ controllers:
64
+ engine: typescript
65
+ generator: "libs/instance-factories/services/templates/postgres-native/controller-generator.ts"
66
+ outputPattern: "{backendDir}/src/controllers/{model}Controller.ts"
67
+
68
+ services:
69
+ engine: typescript
70
+ generator: "libs/instance-factories/services/templates/postgres-native/service-generator.ts"
71
+ outputPattern: "{backendDir}/src/services/{service}.ts"
72
+
73
+ configuration:
74
+ outputStructure: "monorepo"
75
+ backendDir: "backend"
76
+ tableNaming: "lowercase-pluralized" # User → users, OrderItem → orderitems
77
+ validation: true
78
+ eventPublishing: true
79
+ errorHandling: "throw"
80
+
81
+ requirements:
82
+ dependencies:
83
+ npm:
84
+ dependencies:
85
+ "pg": "^8.11.0"
86
+ environment:
87
+ - name: "POSTGRES_URL"
88
+ description: "PostgreSQL connection string (e.g. postgres://user:pass@localhost:5432/myapp)"
89
+ required: true
90
+ configuration: {}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Shared step-matching machinery — common helpers + the canonical
3
+ * `matchAgainstConventions` walker that any ORM-specific step-conventions
4
+ * library can compose with.
5
+ *
6
+ * Each ORM library (prisma, mongodb-native) exports its own
7
+ * `STEP_CONVENTIONS` array of `{name, pattern, generateCall}` items
8
+ * describing what code to emit for matched patterns. The matcher itself
9
+ * is identical across libraries: walk the conventions in order, first
10
+ * match wins; if nothing matches, compute the AI-fallback shape
11
+ * (functionName + inputs + resultVar) deterministically.
12
+ *
13
+ * Extracted from prisma/step-conventions.ts and mongodb-native/step-conventions.ts
14
+ * so future ORM libraries can drop in just the conventions array
15
+ * without re-implementing matching logic. (#43K-D)
16
+ */
17
+
18
+ /** Camel-case a step's text into a TS identifier. Identical algorithm
19
+ * across all ORM libraries — function names emitted in `aiBehaviors.<X>(...)`
20
+ * calls must agree with the function declarations the AI-behaviors-generator
21
+ * emits. Both sides go through this. */
22
+ export function toMethod(words: string): string {
23
+ const cleaned = words.trim().replace(/[^A-Za-z0-9\s]+/g, ' ');
24
+ const camel = cleaned.replace(/\s+(.)/g, (_, c) => c.toUpperCase()).replace(/^\w/, (c) => c.toLowerCase());
25
+ const safe = camel.replace(/[^A-Za-z0-9_$]/g, '');
26
+ return safe || 'unnamedStep';
27
+ }
28
+
29
+ export function toVar(name: string): string {
30
+ return name.charAt(0).toLowerCase() + name.slice(1);
31
+ }
32
+
33
+ export interface SharedStepContext {
34
+ modelName: string;
35
+ serviceName: string;
36
+ operationName: string;
37
+ stepNum: number;
38
+ parameterNames?: string[];
39
+ declaredVars?: Set<string>;
40
+ resultName?: string;
41
+ }
42
+
43
+ export interface SharedConvention<C extends SharedStepContext = SharedStepContext> {
44
+ name: string;
45
+ pattern: RegExp;
46
+ /** Emit inline code for this step. Return empty string to signal
47
+ * "regex matched but I can't safely emit" (caller falls through to AI). */
48
+ generateCall: (match: RegExpMatchArray, ctx: C) => string;
49
+ /** Optional helper-method body that some libraries (e.g. prisma's
50
+ * `check {condition}` convention) emit alongside the call site. */
51
+ generateMethod?: (match: RegExpMatchArray, ctx: C) => string;
52
+ }
53
+
54
+ export interface MatchResult {
55
+ matched: boolean;
56
+ call: string;
57
+ helperMethod?: string;
58
+ functionName?: string;
59
+ inputs?: string[];
60
+ resultVar?: string;
61
+ }
62
+
63
+ /** Walk conventions in order. First match wins (with empty-string fallthrough).
64
+ * Unmatched: compute the AI-delegate call site shape so the controller and
65
+ * the AI-behaviors-generator agree on function name + inputs + resultVar. */
66
+ export function matchAgainstConventions<C extends SharedStepContext>(
67
+ step: string,
68
+ ctx: C,
69
+ conventions: ReadonlyArray<SharedConvention<C>>,
70
+ aiArgsExpr: (inputs: string[], paramNames: string[]) => string,
71
+ ): MatchResult {
72
+ for (const convention of conventions) {
73
+ const m = step.match(convention.pattern);
74
+ if (m) {
75
+ const call = convention.generateCall(m, ctx);
76
+ if (call) {
77
+ return {
78
+ matched: true,
79
+ call,
80
+ helperMethod: convention.generateMethod?.(m, ctx),
81
+ };
82
+ }
83
+ }
84
+ }
85
+
86
+ // AI fallback shape — same across all ORMs.
87
+ const functionName = toMethod(step);
88
+ const declared = Array.from(ctx.declaredVars || []);
89
+ const paramNames = ctx.parameterNames || [];
90
+ const inputs = [...paramNames, ...declared];
91
+ const resultVar = ctx.resultName || `step${ctx.stepNum}Result`;
92
+ if (ctx.declaredVars) ctx.declaredVars.add(resultVar);
93
+ const inputObj = aiArgsExpr(inputs, paramNames);
94
+
95
+ return {
96
+ matched: false,
97
+ call: ` // Step ${ctx.stepNum}: ${step} [AI-generated — pure transform]
98
+ const ${resultVar} = await aiBehaviors.${functionName}(${inputObj});`,
99
+ functionName,
100
+ inputs,
101
+ resultVar,
102
+ };
103
+ }
@@ -26,7 +26,7 @@ import type { TemplateContext } from '@specverse/types';
26
26
  import { buildTransitionMap, isAutoField } from '@specverse/types/spec-rules';
27
27
 
28
28
  export default function generateMongoNativeController(context: TemplateContext): string {
29
- const { controller, model } = context;
29
+ const { controller, model, models } = context as any;
30
30
  if (!controller) throw new Error('Controller is required in template context');
31
31
  if (!model) throw new Error('Model is required for controller generation');
32
32
 
@@ -36,7 +36,17 @@ export default function generateMongoNativeController(context: TemplateContext):
36
36
  const collection = collectionName(model);
37
37
  const curedOps = controller.cured || {};
38
38
 
39
- const customActions = generateCustomActions(controller);
39
+ // Build a name → ModelSpec registry once so step-conventions can read
40
+ // attribute defaults (level: 1, totalResources: '', etc.) and FK targets
41
+ // when emitting create/insertMany code (#43K-A).
42
+ const modelRegistry: Record<string, any> = {};
43
+ if (Array.isArray(models)) {
44
+ for (const m of models) if (m?.name) modelRegistry[m.name] = m;
45
+ } else if (models && typeof models === 'object') {
46
+ Object.assign(modelRegistry, models);
47
+ }
48
+
49
+ const customActions = generateCustomActions(controller, modelRegistry);
40
50
 
41
51
  const validate = generateValidateMethod(model, modelName);
42
52
  const create = curedOps.create ? generateCreateMethod(model, modelName, modelVar, collection) : '';
@@ -55,7 +65,8 @@ export default function generateMongoNativeController(context: TemplateContext):
55
65
  */
56
66
  import { ObjectId, type Filter, type Document } from 'mongodb';
57
67
  import { getCollection } from '../db/mongoClient.js';
58
- ${hasEventPublishing ? `import { eventBus } from '../events/eventBus.js';` : ''}
68
+ ${hasEventPublishing || customActions.needsAiBehaviors ? `import { eventBus } from '../events/eventBus.js';` : ''}
69
+ ${customActions.needsAiBehaviors ? `import * as aiBehaviors from '../behaviors/${controllerName}.ai.js';` : ''}
59
70
 
60
71
  const COLLECTION_NAME = '${collection}';
61
72
 
@@ -275,29 +286,96 @@ function generateDeleteMethod(modelName: string, modelVar: string, collection: s
275
286
 
276
287
  interface CustomActionsResult {
277
288
  code: string;
289
+ needsAiBehaviors: boolean;
278
290
  }
279
291
 
280
292
  /**
281
- * Custom actions emit TODO stubs that throw "not implemented".
293
+ * Custom actions emit a per-step body using `matchMongoStep`. The same
294
+ * matcher is passed to the AI-behaviors-generator (via realize/index.ts)
295
+ * so both sides accumulate the same `declaredVars` set and produce
296
+ * matching function names + inputs for unmatched steps.
282
297
  *
283
- * Why not delegate to `aiBehaviors.<actionName>`? Because the AI-behaviors
284
- * generator only emits functions for STEPS that didn't match a convention
285
- * pattern not for actions themselves. So `aiBehaviors.rotate` would not
286
- * exist even when an action named `rotate` is declared on the controller.
287
- *
288
- * Real implementation of action bodies is deferred to the MongoDB-native
289
- * step-conventions library (#43F follow-up — mirror Prisma's
290
- * step-conventions.ts but emit native-driver collection calls).
298
+ * Per step:
299
+ * - matched emit conventional native-driver code inline
300
+ * - unmatched emit `const stepNResult = await aiBehaviors.<funcName>({...});`
301
+ * (the aiBehaviors function comes from the controller's `*.ai.ts`
302
+ * file, generated by AI-behaviors-generator using the SAME matcher).
291
303
  */
292
- function generateCustomActions(controller: any): CustomActionsResult {
304
+ import { matchMongoStep, type MongoStepContext } from './step-conventions.js';
305
+
306
+ function generateCustomActions(controller: any, modelRegistry: Record<string, any> = {}): CustomActionsResult {
293
307
  if (!controller.actions || Object.keys(controller.actions).length === 0) {
294
- return { code: '' };
308
+ return { code: '', needsAiBehaviors: false };
295
309
  }
310
+ const CRUD_NAMES = new Set(['create', 'retrieve', 'retrieveAll', 'update', 'evolve', 'delete', 'validate']);
311
+ const modelName = controller.model || (controller.name || '').replace(/Controller$/, '') || 'Model';
312
+ const collectionName = modelName.toLowerCase() + 's';
296
313
  const out: string[] = [];
314
+ let needsAiBehaviors = false;
297
315
  for (const [actionName, action] of Object.entries<any>(controller.actions)) {
298
- const stepsHeader = (action.steps && action.steps.length > 0)
299
- ? action.steps.map((s: any) => ` * - ${typeof s === 'string' ? s : (s.action || JSON.stringify(s))}`).join('\n')
316
+ if (CRUD_NAMES.has(actionName)) {
317
+ // Surface the collision so spec authors aren't silently losing
318
+ // a declared action. Going forward this could escalate to an
319
+ // error or auto-rename; for now a warning keeps existing specs
320
+ // building while making the dropped behaviour visible.
321
+ console.warn(
322
+ `⚠️ ${controller.name || 'Controller'}.${actionName} — behaviour-derived action collides with the auto-generated CURVED \`${actionName}\` op. Dropped to avoid TS2393 duplicate-implementation. Rename the behaviour (e.g. \`${actionName}Soft\` / \`hardDelete\`) if you need the custom logic.`
323
+ );
324
+ continue;
325
+ }
326
+ const steps: any[] = Array.isArray(action.steps) ? action.steps : [];
327
+ const stepsHeader = steps.length > 0
328
+ ? steps.map((s: any) => ` * - ${typeof s === 'string' ? s : (s.action || JSON.stringify(s))}`).join('\n')
300
329
  : ' * (no spec steps declared)';
330
+
331
+ const declaredVars = new Set<string>();
332
+ const stepBodies: string[] = [];
333
+ let usesArgs = false;
334
+ let actionRefersToAi = false;
335
+ steps.forEach((rawStep: any, i: number) => {
336
+ const stepText = typeof rawStep === 'string' ? rawStep : (rawStep?.step || rawStep?.action);
337
+ if (typeof stepText !== 'string') {
338
+ stepBodies.push(` // Step ${i + 1}: (non-string step ignored)`);
339
+ return;
340
+ }
341
+ const ctx: MongoStepContext = {
342
+ modelName,
343
+ collectionName,
344
+ serviceName: controller.name || 'Controller',
345
+ operationName: actionName,
346
+ stepNum: i + 1,
347
+ parameterNames: Object.keys(action.parameters || {}),
348
+ declaredVars,
349
+ models: modelRegistry,
350
+ };
351
+ const result = matchMongoStep(stepText, ctx);
352
+ stepBodies.push(result.call);
353
+ if (/\bargs\./.test(result.call)) usesArgs = true;
354
+ if (!result.matched) actionRefersToAi = true;
355
+ });
356
+
357
+ if (actionRefersToAi) needsAiBehaviors = true;
358
+ const argsParam = usesArgs ? 'args: any = {}' : '_args: any = {}';
359
+ let combined = stepBodies.join('\n\n');
360
+ // Drop the `const stepNResult =` declaration when no later step
361
+ // references the result. Strict tsc's noUnusedLocals applies to
362
+ // locals regardless of underscore prefix, so the only safe fix is
363
+ // to omit the assignment entirely. The await still fires.
364
+ const stepResultRe = /const\s+(step\d+Result)\s*=/g;
365
+ let mres: RegExpExecArray | null;
366
+ const declared: string[] = [];
367
+ while ((mres = stepResultRe.exec(combined)) !== null) declared.push(mres[1]);
368
+ for (const name of declared) {
369
+ const refCount = (combined.match(new RegExp(`\\b${name}\\b`, 'g')) || []).length;
370
+ if (refCount <= 1) {
371
+ // Only the declaration itself — drop the binding, keep the await.
372
+ combined = combined.replace(new RegExp(`const\\s+${name}\\s*=\\s*`), '');
373
+ }
374
+ }
375
+ const body = steps.length > 0
376
+ ? combined + `\n return { success: true };`
377
+ : ` throw new Error('${controller.name || 'Controller'}.${actionName} is not implemented');`;
378
+
301
379
  out.push(`
302
380
  /**
303
381
  * ${actionName}
@@ -306,14 +384,10 @@ function generateCustomActions(controller: any): CustomActionsResult {
306
384
  * Spec steps:
307
385
  ${stepsHeader}
308
386
  */
309
- public async ${actionName}(_args: any = {}): Promise<any> {
310
- // TODO (#43F): translate spec steps into native MongoDB driver calls
311
- // via a mongodb-native step-conventions library (mirror of the prisma
312
- // one). For now this is a stub so realize completes and the action
313
- // surface is callable for parity tests.
314
- throw new Error('${controller.name}.${actionName} is not implemented');
387
+ public async ${actionName}(${argsParam}): Promise<any> {
388
+ ${body}
315
389
  }
316
390
  `);
317
391
  }
318
- return { code: out.join('\n') };
392
+ return { code: out.join('\n'), needsAiBehaviors };
319
393
  }