graphddb 0.7.4 → 0.7.5

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 (2) hide show
  1. package/dist/cli.js +2736 -642
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -5,8 +5,7 @@ import {
5
5
  buildOperations
6
6
  } from "./chunk-QOA5MSMD.js";
7
7
  import {
8
- MetadataRegistry,
9
- normalizeSelectSpec
8
+ MetadataRegistry
10
9
  } from "./chunk-F2DI3GTI.js";
11
10
  import {
12
11
  createDefaultLinter
@@ -305,7 +304,7 @@ var commandDefinitions = {
305
304
  }
306
305
  ]
307
306
  },
308
- "generate.cloudformation": {
307
+ "generate.php": {
309
308
  "effects": {
310
309
  "risk_level": "low",
311
310
  "reads": [
@@ -329,123 +328,92 @@ var commandDefinitions = {
329
328
  }
330
329
  },
331
330
  {
332
- "name": "out",
331
+ "name": "queries",
333
332
  "schema": {
334
333
  "type": "string"
334
+ },
335
+ "file": {
336
+ "mode": "read",
337
+ "exists": true,
338
+ "media_type": "application/typescript",
339
+ "encoding": "utf-8"
335
340
  }
336
341
  },
337
342
  {
338
- "name": "format",
339
- "schema": {
340
- "type": "string",
341
- "enum": [
342
- "yaml",
343
- "json"
344
- ],
345
- "default": "yaml"
346
- }
347
- },
348
- {
349
- "name": "gsi-projection",
350
- "schema": {
351
- "type": "string",
352
- "enum": [
353
- "ALL",
354
- "KEYS_ONLY"
355
- ],
356
- "default": "ALL"
357
- }
358
- },
359
- {
360
- "name": "stream",
343
+ "name": "commands",
361
344
  "schema": {
362
- "type": "string",
363
- "enum": [
364
- "auto",
365
- "on",
366
- "off"
367
- ],
368
- "default": "auto"
345
+ "type": "string"
346
+ },
347
+ "file": {
348
+ "mode": "read",
349
+ "exists": true,
350
+ "media_type": "application/typescript",
351
+ "encoding": "utf-8"
369
352
  }
370
353
  },
371
354
  {
372
- "name": "billing-mode",
355
+ "name": "transactions",
373
356
  "schema": {
374
- "type": "string",
375
- "enum": [
376
- "PAY_PER_REQUEST",
377
- "PROVISIONED"
378
- ],
379
- "default": "PAY_PER_REQUEST"
357
+ "type": "string"
358
+ },
359
+ "file": {
360
+ "mode": "read",
361
+ "exists": true,
362
+ "media_type": "application/typescript",
363
+ "encoding": "utf-8"
380
364
  }
381
365
  },
382
366
  {
383
- "name": "rcu",
367
+ "name": "contracts",
384
368
  "schema": {
385
- "type": "integer",
386
- "minimum": 1
369
+ "type": "string"
370
+ },
371
+ "file": {
372
+ "mode": "read",
373
+ "exists": true,
374
+ "media_type": "application/typescript",
375
+ "encoding": "utf-8"
387
376
  }
388
377
  },
389
378
  {
390
- "name": "wcu",
379
+ "name": "contexts",
391
380
  "schema": {
392
- "type": "integer",
393
- "minimum": 1
381
+ "type": "string"
382
+ },
383
+ "file": {
384
+ "mode": "read",
385
+ "exists": true,
386
+ "media_type": "application/typescript",
387
+ "encoding": "utf-8"
394
388
  }
395
389
  },
396
390
  {
397
- "name": "autoscale",
391
+ "name": "typed",
398
392
  "schema": {
399
393
  "type": "boolean"
400
394
  }
401
395
  },
402
396
  {
403
- "name": "autoscale-min",
404
- "schema": {
405
- "type": "integer",
406
- "minimum": 1
407
- }
408
- },
409
- {
410
- "name": "autoscale-max",
411
- "schema": {
412
- "type": "integer",
413
- "minimum": 1
414
- }
415
- },
416
- {
417
- "name": "autoscale-target",
418
- "schema": {
419
- "type": "integer",
420
- "minimum": 1,
421
- "maximum": 100
422
- }
423
- },
424
- {
425
- "name": "pitr",
397
+ "name": "template-dir",
426
398
  "schema": {
427
- "type": "boolean"
399
+ "type": "string"
428
400
  }
429
401
  },
430
402
  {
431
- "name": "table-class",
403
+ "name": "helpers",
432
404
  "schema": {
433
- "type": "string",
434
- "enum": [
435
- "STANDARD",
436
- "STANDARD_INFREQUENT_ACCESS"
437
- ]
405
+ "type": "string"
438
406
  }
439
407
  },
440
408
  {
441
- "name": "sse",
409
+ "name": "out",
442
410
  "schema": {
443
- "type": "boolean"
411
+ "type": "string"
444
412
  }
445
413
  }
446
414
  ]
447
415
  },
448
- "generate.docs": {
416
+ "generate.rust": {
449
417
  "effects": {
450
418
  "risk_level": "low",
451
419
  "reads": [
@@ -481,133 +449,491 @@ var commandDefinitions = {
481
449
  }
482
450
  },
483
451
  {
484
- "name": "out",
452
+ "name": "commands",
485
453
  "schema": {
486
454
  "type": "string"
455
+ },
456
+ "file": {
457
+ "mode": "read",
458
+ "exists": true,
459
+ "media_type": "application/typescript",
460
+ "encoding": "utf-8"
487
461
  }
488
462
  },
489
463
  {
490
- "name": "format",
464
+ "name": "transactions",
491
465
  "schema": {
492
- "type": "string",
493
- "enum": [
494
- "markdown"
495
- ],
496
- "default": "markdown"
466
+ "type": "string"
467
+ },
468
+ "file": {
469
+ "mode": "read",
470
+ "exists": true,
471
+ "media_type": "application/typescript",
472
+ "encoding": "utf-8"
497
473
  }
498
474
  },
499
475
  {
500
- "name": "split",
476
+ "name": "contracts",
501
477
  "schema": {
502
- "type": "string",
503
- "enum": [
504
- "none",
505
- "entity"
506
- ],
507
- "default": "none"
478
+ "type": "string"
479
+ },
480
+ "file": {
481
+ "mode": "read",
482
+ "exists": true,
483
+ "media_type": "application/typescript",
484
+ "encoding": "utf-8"
508
485
  }
509
486
  },
510
487
  {
511
- "name": "template-dir",
488
+ "name": "contexts",
512
489
  "schema": {
513
490
  "type": "string"
491
+ },
492
+ "file": {
493
+ "mode": "read",
494
+ "exists": true,
495
+ "media_type": "application/typescript",
496
+ "encoding": "utf-8"
514
497
  }
515
498
  },
516
499
  {
517
- "name": "helpers",
500
+ "name": "typed",
501
+ "schema": {
502
+ "type": "boolean"
503
+ }
504
+ },
505
+ {
506
+ "name": "template-dir",
518
507
  "schema": {
519
508
  "type": "string"
520
509
  }
521
510
  },
522
511
  {
523
- "name": "billing-mode",
512
+ "name": "helpers",
524
513
  "schema": {
525
- "type": "string",
526
- "enum": [
527
- "PAY_PER_REQUEST",
528
- "PROVISIONED"
529
- ],
530
- "default": "PAY_PER_REQUEST"
514
+ "type": "string"
531
515
  }
532
516
  },
533
517
  {
534
- "name": "stream",
518
+ "name": "out",
535
519
  "schema": {
536
- "type": "string",
537
- "enum": [
538
- "auto",
539
- "on",
540
- "off"
541
- ],
542
- "default": "auto"
520
+ "type": "string"
543
521
  }
544
522
  }
545
523
  ]
546
524
  },
547
- "transform.prepared": {
525
+ "generate.go": {
548
526
  "effects": {
549
- "risk_level": "medium",
527
+ "risk_level": "low",
550
528
  "reads": [
551
- "ts-app-sources"
529
+ "ts-definitions"
552
530
  ],
553
531
  "writes": [
554
- "ts-app-sources"
532
+ "generated-code"
555
533
  ]
556
534
  },
557
535
  "options": [
558
536
  {
559
- "name": "src",
537
+ "name": "entry",
560
538
  "schema": {
561
539
  "type": "string"
562
540
  },
563
541
  "file": {
564
542
  "mode": "read",
565
- "exists": true
543
+ "exists": true,
544
+ "media_type": "application/typescript",
545
+ "encoding": "utf-8"
566
546
  }
567
547
  },
568
548
  {
569
- "name": "write",
549
+ "name": "queries",
570
550
  "schema": {
571
- "type": "boolean"
551
+ "type": "string"
552
+ },
553
+ "file": {
554
+ "mode": "read",
555
+ "exists": true,
556
+ "media_type": "application/typescript",
557
+ "encoding": "utf-8"
572
558
  }
573
559
  },
574
560
  {
575
- "name": "aot",
561
+ "name": "commands",
576
562
  "schema": {
577
563
  "type": "string"
564
+ },
565
+ "file": {
566
+ "mode": "read",
567
+ "exists": true,
568
+ "media_type": "application/typescript",
569
+ "encoding": "utf-8"
578
570
  }
579
- }
580
- ]
581
- }
582
- };
583
- function deriveCommandPolicy(command_id, optionValues) {
584
- const def = commandDefinitions[command_id];
585
- if (!def) throw new Error(`Unknown command: ${command_id}`);
586
- const cmdDef = def;
587
- const options = {};
588
- const active_options = [];
589
- for (const optDef of cmdDef.options ?? []) {
590
- const value = optionValues[optDef.name];
591
- const specified = optDef.name in optionValues && value !== void 0;
592
- options[optDef.name] = { value, specified, definition: optDef };
593
- if (isOptionActive(optDef, value, specified)) {
594
- active_options.push(optDef.name);
595
- }
596
- }
597
- const policy = derivePolicy({
598
- command_id,
599
- command_effects: cmdDef.effects,
600
- options,
601
- env: cmdDef.env
602
- });
603
- return { command: command_id, active_options, policy };
604
- }
605
-
606
- // src/generated/contract.ts
607
- var CONTRACT_YAML = "cli_contracts: 0.1.0\n\ninfo:\n title: GraphDDB CLI\n version: 0.1.0\n description: >-\n Contract for the graphddb code-generation CLI. The CLI reads TypeScript\n GraphDDB model / query / command definitions (the single source of truth)\n and emits Python bindings (manifest.json, operations.json, types.py,\n repositories.py, __init__.py) for the Python bridge runtime.\n license:\n name: MIT\n contact:\n name: foo-log-inc\n url: https://github.com/foo-log-inc/graphddb\n\nartifact_slots:\n ts-definitions:\n description: >-\n TypeScript GraphDDB definition modules (entity models, defineQueries,\n defineCommands) used as the single source of truth.\n direction: read\n generated-code:\n description: >-\n Generated Python bindings and JSON specs written to the output directory\n (manifest.json, operations.json, types.py, repositories.py, __init__.py).\n direction: write\n ts-app-sources:\n description: >-\n Application TypeScript sources containing `DDBModel.prepare` call sites.\n Read by the prepared-statement build lint; rewritten in place by\n `transform prepared --write` (hoist normalization, issue #206).\n direction: write\n\ncommand_sets:\n graphddb:\n summary: Graph data modeling on DynamoDB \u2014 code generation CLI.\n x-stdin: >-\n No command reads from stdin. All inputs are provided via file options.\n\n commands:\n # \u2500\u2500 generate python \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n generate.python:\n summary: Generate Python bindings from TypeScript GraphDDB definitions.\n description: >-\n Loads the TypeScript entity models, query definitions, and command\n definitions, builds the serializable manifest / operations bundle, and\n writes the Python bridge bindings (manifest.json, operations.json,\n types.py, repositories.py, __init__.py) to the output directory.\n Output is deterministic for a given set of inputs.\n usage:\n - graphddb generate python --entry models.ts --queries queries.ts --commands commands.ts --out generated/\n - graphddb generate python --entry definitions.ts --out generated/\n\n effects:\n risk_level: low\n reads:\n - ts-definitions\n writes:\n - generated-code\n\n x-agent:\n recommended_before_use:\n - \"Ensure the TypeScript definitions type-check and lint cleanly.\"\n\n options:\n - name: entry\n aliases: [e]\n required: true\n description: >-\n Entry module that registers the entity models (side-effecting\n import). May also export queries / commands.\n value_name: file\n schema:\n type: string\n file:\n mode: read\n exists: true\n media_type: application/typescript\n encoding: utf-8\n\n - name: queries\n aliases: [q]\n description: >-\n Module exporting `queries` (a defineQueries map). Defaults to the\n entry module when omitted.\n value_name: file\n schema:\n type: string\n file:\n mode: read\n exists: true\n media_type: application/typescript\n encoding: utf-8\n\n - name: commands\n aliases: [c]\n description: >-\n Module exporting `commands` (a defineCommands map). Defaults to the\n entry module when omitted.\n value_name: file\n schema:\n type: string\n file:\n mode: read\n exists: true\n media_type: application/typescript\n encoding: utf-8\n\n - name: transactions\n aliases: [t]\n description: >-\n Module exporting `transactions` (a defineTransactions map).\n Defaults to the commands module (or the entry module) when omitted.\n Optional \u2014 a project with no declarative transactions omits it.\n value_name: file\n schema:\n type: string\n file:\n mode: read\n exists: true\n media_type: application/typescript\n encoding: utf-8\n\n - name: contracts\n description: >-\n Module exporting `contracts` (a map of publicQueryModel /\n publicCommandModel results \u2014 the CQRS Contract layer). Defaults to\n the entry module when omitted. Optional \u2014 a project with no\n contracts omits it, and the output gains no `contracts` content.\n value_name: file\n schema:\n type: string\n file:\n mode: read\n exists: true\n media_type: application/typescript\n encoding: utf-8\n\n - name: contexts\n description: >-\n Module exporting `contexts` (context-ownership: context name \u2192 the\n Model / Contract names that belong to it). Defaults to the\n contracts module (or the entry module) when omitted. Optional \u2014\n consumed by the boundary lint to tell own from foreign.\n value_name: file\n schema:\n type: string\n file:\n mode: read\n exists: true\n media_type: application/typescript\n encoding: utf-8\n\n - name: dataclass\n description: >-\n Emit Python result / element types as @dataclass classes instead\n of TypedDict (decimal / Optional / Connection mapped accordingly).\n schema:\n type: boolean\n\n - name: out\n aliases: [o]\n required: true\n description: Output directory for the generated Python bindings.\n value_name: dir\n schema:\n type: string\n\n exits:\n '0':\n description: Generation succeeded.\n stdout:\n format: json\n schema:\n $ref: '#/components/schemas/GenerateResult'\n files:\n - path: '{options.out}/manifest.json'\n required: true\n media_type: application/json\n description: Entity / table / key / relation metadata.\n - path: '{options.out}/operations.json'\n required: true\n media_type: application/json\n description: Per-query / per-command execution specs.\n - path: '{options.out}/types.py'\n required: true\n media_type: text/x-python\n description: TypedDict result types derived from query selects.\n - path: '{options.out}/repositories.py'\n required: true\n media_type: text/x-python\n description: Per-entity repository classes (sync methods).\n - path: '{options.out}/__init__.py'\n required: true\n media_type: text/x-python\n description: Package re-exports for the generated bindings.\n\n '1':\n description: Unexpected error.\n stderr:\n format: json\n schema:\n $ref: '#/components/schemas/Error'\n\n '2':\n description: Invalid arguments (missing/unreadable input or output).\n stderr:\n format: json\n schema:\n $ref: '#/components/schemas/Error'\n\n # \u2500\u2500 generate cloudformation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n generate.cloudformation:\n summary: >-\n Generate an AWS CloudFormation template (DynamoDB table + GSIs) from\n TypeScript GraphDDB definitions.\n description: >-\n Loads the TypeScript entity models and builds the serializable\n manifest, then emits an AWS CloudFormation template describing the\n DynamoDB table(s) the single-table design maps onto: one\n `AWS::DynamoDB::Table` per physical table, with the base key schema\n (`PK` / `SK`), the unioned Global Secondary Indexes\n (`<indexName>PK` / `<indexName>SK`), `PAY_PER_REQUEST` billing, a\n `TableName` parameter (default = physical name), and Outputs\n (table name + ARN). Output is deterministic and, by default, YAML.\n usage:\n - graphddb generate cloudformation --entry models.ts --out table.yaml\n - graphddb generate cloudformation --entry models.ts --format json\n - graphddb generate cloudformation --entry models.ts --gsi-projection KEYS_ONLY --out table.yaml\n - graphddb generate cloudformation --entry models.ts --stream on --out table.yaml\n - graphddb generate cloudformation --entry models.ts --pitr --sse --table-class STANDARD_INFREQUENT_ACCESS --out table.yaml\n - graphddb generate cloudformation --entry models.ts --billing-mode PROVISIONED --rcu 10 --wcu 5 --out table.yaml\n - graphddb generate cloudformation --entry models.ts --billing-mode PROVISIONED --autoscale --autoscale-min 5 --autoscale-max 100 --autoscale-target 70 --out table.yaml\n\n effects:\n risk_level: low\n reads:\n - ts-definitions\n writes:\n - generated-code\n\n x-agent:\n recommended_before_use:\n - \"Ensure the TypeScript definitions type-check and lint cleanly.\"\n\n options:\n - name: entry\n aliases: [e]\n required: true\n description: >-\n Entry module that registers the entity models (side-effecting\n import).\n value_name: file\n schema:\n type: string\n file:\n mode: read\n exists: true\n media_type: application/typescript\n encoding: utf-8\n\n - name: out\n aliases: [o]\n description: >-\n Output file for the generated CloudFormation template. Written to\n stdout when omitted.\n value_name: file\n schema:\n type: string\n\n - name: format\n aliases: [f]\n description: Output serialization format (yaml or json).\n value_name: format\n schema:\n type: string\n enum: [yaml, json]\n default: yaml\n\n - name: gsi-projection\n description: >-\n GSI ProjectionType. ALL (default) projects every attribute;\n KEYS_ONLY projects only the key attributes.\n value_name: projection\n schema:\n type: string\n enum: [ALL, KEYS_ONLY]\n default: ALL\n\n - name: stream\n description: >-\n DynamoDB Streams (StreamSpecification NEW_AND_OLD_IMAGES) emission.\n auto (default) emits a stream only when the model set uses\n stream-based maintenance (updateMode:'stream'); on always emits;\n off never emits. When enabled, a StreamArn Output is added too.\n value_name: mode\n schema:\n type: string\n enum: [auto, on, off]\n default: auto\n\n - name: billing-mode\n description: >-\n DynamoDB billing mode. PAY_PER_REQUEST (default) is on-demand\n (no provisioned throughput). PROVISIONED emits BillingMode:\n PROVISIONED with ProvisionedThroughput (see --rcu / --wcu) on the\n table and every GSI, and unlocks Application Auto Scaling\n (--autoscale).\n value_name: mode\n schema:\n type: string\n enum: [PAY_PER_REQUEST, PROVISIONED]\n default: PAY_PER_REQUEST\n\n - name: rcu\n description: >-\n Provisioned ReadCapacityUnits (a positive integer) applied to the\n table and every GSI. Only valid with --billing-mode PROVISIONED\n (rejected with PAY_PER_REQUEST). Defaults to 5 when omitted.\n value_name: units\n schema:\n type: integer\n minimum: 1\n\n - name: wcu\n description: >-\n Provisioned WriteCapacityUnits (a positive integer) applied to the\n table and every GSI. Only valid with --billing-mode PROVISIONED\n (rejected with PAY_PER_REQUEST). Defaults to 5 when omitted.\n value_name: units\n schema:\n type: integer\n minimum: 1\n\n - name: autoscale\n description: >-\n Enable Application Auto Scaling. Emits an\n AWS::ApplicationAutoScaling::ScalableTarget +\n AWS::ApplicationAutoScaling::ScalingPolicy pair for the table read\n and write capacity AND for each GSI read and write capacity. Only\n valid with --billing-mode PROVISIONED (autoscaling is invalid on\n on-demand and is rejected). Off by default \u2014 no autoscaling\n resources are emitted unless this flag is passed.\n schema:\n type: boolean\n\n - name: autoscale-min\n description: >-\n Auto Scaling minimum capacity (MinCapacity) for every scalable\n target. Must be <= --autoscale-max. Only meaningful with\n --autoscale. Defaults to 5 when omitted.\n value_name: units\n schema:\n type: integer\n minimum: 1\n\n - name: autoscale-max\n description: >-\n Auto Scaling maximum capacity (MaxCapacity) for every scalable\n target. Must be >= --autoscale-min. Only meaningful with\n --autoscale. Defaults to 100 when omitted.\n value_name: units\n schema:\n type: integer\n minimum: 1\n\n - name: autoscale-target\n description: >-\n Auto Scaling target utilization percentage (TargetValue of the\n TargetTrackingScaling policy). Only meaningful with --autoscale.\n Defaults to 70 when omitted.\n value_name: percent\n schema:\n type: integer\n minimum: 1\n maximum: 100\n\n - name: pitr\n description: >-\n Enable point-in-time recovery (PointInTimeRecoverySpecification\n PointInTimeRecoveryEnabled:true). Off by default \u2014 the property is\n omitted entirely unless this flag is passed.\n schema:\n type: boolean\n\n - name: table-class\n description: >-\n DynamoDB table class. Omitted by default (AWS defaults to\n STANDARD); pass STANDARD or STANDARD_INFREQUENT_ACCESS to emit\n TableClass.\n value_name: class\n schema:\n type: string\n enum: [STANDARD, STANDARD_INFREQUENT_ACCESS]\n\n - name: sse\n description: >-\n Enable server-side encryption with the AWS-owned/managed key\n (SSESpecification SSEEnabled:true). Off by default \u2014 the property is\n omitted entirely unless this flag is passed.\n schema:\n type: boolean\n\n exits:\n '0':\n description: Generation succeeded.\n stdout:\n format: json\n schema:\n $ref: '#/components/schemas/GenerateResult'\n\n '1':\n description: Unexpected error.\n stderr:\n format: json\n schema:\n $ref: '#/components/schemas/Error'\n\n '2':\n description: Invalid arguments (missing/unreadable input or output).\n stderr:\n format: json\n schema:\n $ref: '#/components/schemas/Error'\n\n # \u2500\u2500 generate docs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n generate.docs:\n summary: >-\n Generate model-specification documentation (Markdown + Mermaid) from\n TypeScript GraphDDB definitions.\n description: >-\n Loads the TypeScript entity models (and, optionally, query / command\n definitions), builds the serializable manifest / operations bundle, then\n renders human-facing model documentation \u2014 Table Overview, Entity\n Relationship Diagram, Physical Key Schema, Property Matrix, Entity\n Details, Access Patterns, Mutation Contracts, Maintained Access Paths,\n CDC, and CloudFormation summary \u2014 as Markdown with Mermaid diagrams. The\n layout is defined by bundled Handlebars templates users can override with\n --template-dir (per-partial) and extend with --helpers. Output is\n deterministic for a given set of inputs.\n usage:\n - graphddb generate docs --entry models.ts --out doc.md\n - graphddb generate docs --entry models.ts --queries definitions.ts --out doc.md\n - graphddb generate docs --entry models.ts --out docs/\n - graphddb generate docs --entry models.ts --out docs/ --template-dir doc-templates/ --helpers doc-helpers.js\n\n effects:\n risk_level: low\n reads:\n - ts-definitions\n writes:\n - generated-code\n\n x-agent:\n recommended_before_use:\n - \"Ensure the TypeScript definitions type-check and lint cleanly.\"\n\n options:\n - name: entry\n aliases: [e]\n required: true\n description: >-\n Entry module that registers the entity models (side-effecting\n import). May also export queries / commands.\n value_name: file\n schema:\n type: string\n file:\n mode: read\n exists: true\n media_type: application/typescript\n encoding: utf-8\n\n - name: queries\n aliases: [q]\n description: >-\n Module exporting `queries` / `commands` (defineQueries /\n defineCommands maps) used for the Access Patterns / Mutation\n Contracts sections. Defaults to the entry module when omitted.\n value_name: file\n schema:\n type: string\n file:\n mode: read\n exists: true\n media_type: application/typescript\n encoding: utf-8\n\n - name: out\n aliases: [o]\n required: true\n description: >-\n Output path for the generated documentation. A file path ending in\n .md writes the single Markdown file at exactly that path (creating\n parent directories); any other value is treated as a directory and\n the file is written as <dir>/index.md.\n value_name: path\n schema:\n type: string\n\n - name: format\n aliases: [f]\n description: Output format. Only markdown is supported today.\n value_name: format\n schema:\n type: string\n enum: [markdown]\n default: markdown\n\n - name: split\n description: >-\n Output granularity. none (default) emits a single index.md; entity\n is reserved for a future per-entity split.\n value_name: mode\n schema:\n type: string\n enum: [none, entity]\n default: none\n\n - name: template-dir\n description: >-\n Directory of override <partial>.hbs templates. Each same-named\n partial (index / table-overview / er-diagram / key-schema /\n property-matrix / entity / access-pattern / maintained / cdc /\n cloudformation) overrides the bundled one; absent partials fall back\n to the bundled default.\n value_name: dir\n schema:\n type: string\n\n - name: helpers\n description: >-\n JS module whose default (or named `helpers`) export is a map of extra\n Handlebars helpers to register alongside the built-in set (eq / join\n / pascal / mermaidId / codeOrEmpty).\n value_name: file\n schema:\n type: string\n\n - name: billing-mode\n description: >-\n DynamoDB billing mode used in the Table Overview (shared with\n generate cloudformation). PAY_PER_REQUEST (default) or PROVISIONED.\n value_name: mode\n schema:\n type: string\n enum: [PAY_PER_REQUEST, PROVISIONED]\n default: PAY_PER_REQUEST\n\n - name: stream\n description: >-\n DynamoDB Streams state used in the Table Overview (shared with\n generate cloudformation). auto (default) detects stream-based\n maintenance (updateMode:'stream'); on always enabled; off never.\n value_name: mode\n schema:\n type: string\n enum: [auto, on, off]\n default: auto\n\n exits:\n '0':\n description: Generation succeeded.\n stdout:\n format: json\n schema:\n $ref: '#/components/schemas/GenerateResult'\n\n '1':\n description: Unexpected error.\n stderr:\n format: json\n schema:\n $ref: '#/components/schemas/Error'\n\n '2':\n description: Invalid arguments (missing/unreadable input or output).\n stderr:\n format: json\n schema:\n $ref: '#/components/schemas/Error'\n\n # \u2500\u2500 transform prepared \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n transform.prepared:\n summary: >-\n Lint and hoist-normalize `DDBModel.prepare` call sites (compile-time\n prepared statements, issue #206).\n description: >-\n A source-to-source build pass over application TypeScript files. It\n statically enforces the prepared-statement no-runtime-capture rule\n (a prepare body may reference only its `$` placeholder and\n module-static bindings; capturing a per-call runtime value \u2014 directly\n or via a helper call \u2014 is a loud error), and normalizes every\n verified INLINE `DDBModel.prepare(fn)` call site into a module-scope\n prepared slot so the plan compiles once per module and each call is\n a plain `.execute` (zero per-op planning / hashing \u2014 placement\n independent). Without --write it is a pure check (the build lint):\n violations exit non-zero and hoistable inline call sites are\n reported as notes. With --write, verified inline call sites are\n rewritten in place; files with violations are never rewritten.\n Idempotent: re-running on transformed output changes nothing.\n With --aot <artifact> (issue #208), the pass additionally compiles\n every verified prepare body at BUILD time into a static prepared-plan\n artifact (a generated TypeScript module): with --write it writes the\n artifact and rewrites each call site to load its static plan\n (`loadPreparedPlan(document, id, body)` \u2014 the body stays in source as\n the plan's compile source and fallback); without --write it is the\n DRIFT CHECK \u2014 it recompiles every plan and exits non-zero when the\n artifact is missing or differs (CI gate: model declarations changed\n without regenerating the artifact).\n usage:\n - graphddb transform prepared --src src/\n - graphddb transform prepared --src src/ --write\n - graphddb transform prepared --src src/app/handlers.ts --write\n - graphddb transform prepared --src src/ --aot src/graphddb.prepared.ts --write\n - graphddb transform prepared --src src/ --aot src/graphddb.prepared.ts\n\n effects:\n risk_level: medium\n reads:\n - ts-app-sources\n writes:\n - ts-app-sources\n\n x-agent:\n recommended_before_use:\n - \"Commit or stash pending changes first when using --write (it rewrites sources in place).\"\n\n options:\n - name: src\n aliases: [s]\n required: true\n description: >-\n A TypeScript source file, or a directory scanned recursively for\n .ts/.tsx/.mts/.cts files (node_modules, dist, hidden directories,\n and .d.ts files are skipped).\n value_name: path\n schema:\n type: string\n file:\n mode: read\n exists: true\n\n - name: write\n aliases: [w]\n description: >-\n Rewrite verified inline call sites in place (hoist\n normalization). Without it the command only checks and reports.\n schema:\n type: boolean\n\n - name: aot\n aliases: [a]\n description: >-\n Static prepared-plan artifact module path (issue #208). Enables\n build-time (AOT) compilation of every verified prepare body:\n with --write the artifact module is (re)generated and call sites\n are rewritten to load their static plan; without --write the\n pass recompiles and compares (drift check \u2014 a missing or\n differing artifact exits 1).\n value_name: artifact\n schema:\n type: string\n\n exits:\n '0':\n description: >-\n Check passed / transform applied. No no-runtime-capture\n violations were found.\n stdout:\n format: json\n schema:\n $ref: '#/components/schemas/TransformPreparedResult'\n\n '1':\n description: >-\n No-runtime-capture violations found, or --aot drift detected\n (reported in the result on stdout), or an unexpected error\n (reported on stderr).\n stdout:\n format: json\n schema:\n $ref: '#/components/schemas/TransformPreparedResult'\n\n '2':\n description: Invalid arguments (missing/unreadable input).\n stderr:\n format: json\n schema:\n $ref: '#/components/schemas/Error'\n\ncomponents:\n schemas:\n GenerateResult:\n type: object\n required: [status, outDir, files]\n properties:\n status:\n type: string\n enum: [ok]\n outDir:\n type: string\n description: Output directory the files were written to.\n files:\n type: array\n description: Paths of the generated files (relative to outDir).\n items:\n type: string\n Error:\n type: object\n required: [code, message]\n properties:\n code:\n type: string\n message:\n type: string\n TransformPreparedResult:\n type: object\n required: [status, files, hoisted, moduleScope, alreadyHoisted, errors, diagnostics]\n properties:\n status:\n type: string\n enum: [ok, violations, drift]\n files:\n type: array\n description: Source files scanned.\n items:\n type: string\n changedFiles:\n type: array\n description: Files rewritten in place (--write only).\n items:\n type: string\n hoisted:\n type: integer\n description: Inline call sites normalized (or normalizable in check mode).\n moduleScope:\n type: integer\n description: Call sites already evaluated once per module load (left as-is).\n alreadyHoisted:\n type: integer\n description: Call sites already normalized by a previous run.\n errors:\n type: integer\n description: No-runtime-capture violations found.\n artifact:\n type: string\n description: The static prepared-plan artifact path (--aot only).\n plans:\n type: integer\n description: Prepared plans compiled into the artifact (--aot only).\n drift:\n type: object\n description: >-\n Artifact drift detail (--aot check mode only; present when status\n is drift).\n properties:\n missing:\n type: boolean\n description: The artifact module does not exist.\n added:\n type: array\n items:\n type: string\n removed:\n type: array\n items:\n type: string\n changed:\n type: array\n items:\n type: string\n diagnostics:\n type: array\n description: Per-position lint diagnostics (errors and notes).\n items:\n type: object\n required: [code, category, message, file, line, column]\n properties:\n code:\n type: string\n category:\n type: string\n enum: [error, note]\n message:\n type: string\n file:\n type: string\n line:\n type: integer\n column:\n type: integer\n";
608
- var CONTRACT_JSON_STR = '{\n "cli_contracts": "0.1.0",\n "info": {\n "title": "GraphDDB CLI",\n "version": "0.1.0",\n "description": "Contract for the graphddb code-generation CLI. The CLI reads TypeScript GraphDDB model / query / command definitions (the single source of truth) and emits Python bindings (manifest.json, operations.json, types.py, repositories.py, __init__.py) for the Python bridge runtime.",\n "license": {\n "name": "MIT"\n },\n "contact": {\n "name": "foo-log-inc",\n "url": "https://github.com/foo-log-inc/graphddb"\n }\n },\n "artifact_slots": {\n "ts-definitions": {\n "description": "TypeScript GraphDDB definition modules (entity models, defineQueries, defineCommands) used as the single source of truth.",\n "direction": "read"\n },\n "generated-code": {\n "description": "Generated Python bindings and JSON specs written to the output directory (manifest.json, operations.json, types.py, repositories.py, __init__.py).",\n "direction": "write"\n },\n "ts-app-sources": {\n "description": "Application TypeScript sources containing `DDBModel.prepare` call sites. Read by the prepared-statement build lint; rewritten in place by `transform prepared --write` (hoist normalization, issue #206).",\n "direction": "write"\n }\n },\n "command_sets": {\n "graphddb": {\n "summary": "Graph data modeling on DynamoDB \u2014 code generation CLI.",\n "x-stdin": "No command reads from stdin. All inputs are provided via file options.",\n "commands": {\n "generate.python": {\n "summary": "Generate Python bindings from TypeScript GraphDDB definitions.",\n "description": "Loads the TypeScript entity models, query definitions, and command definitions, builds the serializable manifest / operations bundle, and writes the Python bridge bindings (manifest.json, operations.json, types.py, repositories.py, __init__.py) to the output directory. Output is deterministic for a given set of inputs.",\n "usage": [\n "graphddb generate python --entry models.ts --queries queries.ts --commands commands.ts --out generated/",\n "graphddb generate python --entry definitions.ts --out generated/"\n ],\n "effects": {\n "risk_level": "low",\n "reads": [\n "ts-definitions"\n ],\n "writes": [\n "generated-code"\n ]\n },\n "x-agent": {\n "recommended_before_use": [\n "Ensure the TypeScript definitions type-check and lint cleanly."\n ]\n },\n "options": [\n {\n "name": "entry",\n "aliases": [\n "e"\n ],\n "required": true,\n "description": "Entry module that registers the entity models (side-effecting import). May also export queries / commands.",\n "value_name": "file",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true,\n "media_type": "application/typescript",\n "encoding": "utf-8"\n }\n },\n {\n "name": "queries",\n "aliases": [\n "q"\n ],\n "description": "Module exporting `queries` (a defineQueries map). Defaults to the entry module when omitted.",\n "value_name": "file",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true,\n "media_type": "application/typescript",\n "encoding": "utf-8"\n }\n },\n {\n "name": "commands",\n "aliases": [\n "c"\n ],\n "description": "Module exporting `commands` (a defineCommands map). Defaults to the entry module when omitted.",\n "value_name": "file",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true,\n "media_type": "application/typescript",\n "encoding": "utf-8"\n }\n },\n {\n "name": "transactions",\n "aliases": [\n "t"\n ],\n "description": "Module exporting `transactions` (a defineTransactions map). Defaults to the commands module (or the entry module) when omitted. Optional \u2014 a project with no declarative transactions omits it.",\n "value_name": "file",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true,\n "media_type": "application/typescript",\n "encoding": "utf-8"\n }\n },\n {\n "name": "contracts",\n "description": "Module exporting `contracts` (a map of publicQueryModel / publicCommandModel results \u2014 the CQRS Contract layer). Defaults to the entry module when omitted. Optional \u2014 a project with no contracts omits it, and the output gains no `contracts` content.",\n "value_name": "file",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true,\n "media_type": "application/typescript",\n "encoding": "utf-8"\n }\n },\n {\n "name": "contexts",\n "description": "Module exporting `contexts` (context-ownership: context name \u2192 the Model / Contract names that belong to it). Defaults to the contracts module (or the entry module) when omitted. Optional \u2014 consumed by the boundary lint to tell own from foreign.",\n "value_name": "file",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true,\n "media_type": "application/typescript",\n "encoding": "utf-8"\n }\n },\n {\n "name": "dataclass",\n "description": "Emit Python result / element types as @dataclass classes instead of TypedDict (decimal / Optional / Connection mapped accordingly).",\n "schema": {\n "type": "boolean"\n }\n },\n {\n "name": "out",\n "aliases": [\n "o"\n ],\n "required": true,\n "description": "Output directory for the generated Python bindings.",\n "value_name": "dir",\n "schema": {\n "type": "string"\n }\n }\n ],\n "exits": {\n "0": {\n "description": "Generation succeeded.",\n "stdout": {\n "format": "json",\n "schema": {\n "$ref": "#/components/schemas/GenerateResult"\n }\n },\n "files": [\n {\n "path": "{options.out}/manifest.json",\n "required": true,\n "media_type": "application/json",\n "description": "Entity / table / key / relation metadata."\n },\n {\n "path": "{options.out}/operations.json",\n "required": true,\n "media_type": "application/json",\n "description": "Per-query / per-command execution specs."\n },\n {\n "path": "{options.out}/types.py",\n "required": true,\n "media_type": "text/x-python",\n "description": "TypedDict result types derived from query selects."\n },\n {\n "path": "{options.out}/repositories.py",\n "required": true,\n "media_type": "text/x-python",\n "description": "Per-entity repository classes (sync methods)."\n },\n {\n "path": "{options.out}/__init__.py",\n "required": true,\n "media_type": "text/x-python",\n "description": "Package re-exports for the generated bindings."\n }\n ]\n },\n "1": {\n "description": "Unexpected error.",\n "stderr": {\n "format": "json",\n "schema": {\n "$ref": "#/components/schemas/Error"\n }\n }\n },\n "2": {\n "description": "Invalid arguments (missing/unreadable input or output).",\n "stderr": {\n "format": "json",\n "schema": {\n "$ref": "#/components/schemas/Error"\n }\n }\n }\n }\n },\n "generate.cloudformation": {\n "summary": "Generate an AWS CloudFormation template (DynamoDB table + GSIs) from TypeScript GraphDDB definitions.",\n "description": "Loads the TypeScript entity models and builds the serializable manifest, then emits an AWS CloudFormation template describing the DynamoDB table(s) the single-table design maps onto: one `AWS::DynamoDB::Table` per physical table, with the base key schema (`PK` / `SK`), the unioned Global Secondary Indexes (`<indexName>PK` / `<indexName>SK`), `PAY_PER_REQUEST` billing, a `TableName` parameter (default = physical name), and Outputs (table name + ARN). Output is deterministic and, by default, YAML.",\n "usage": [\n "graphddb generate cloudformation --entry models.ts --out table.yaml",\n "graphddb generate cloudformation --entry models.ts --format json",\n "graphddb generate cloudformation --entry models.ts --gsi-projection KEYS_ONLY --out table.yaml",\n "graphddb generate cloudformation --entry models.ts --stream on --out table.yaml",\n "graphddb generate cloudformation --entry models.ts --pitr --sse --table-class STANDARD_INFREQUENT_ACCESS --out table.yaml",\n "graphddb generate cloudformation --entry models.ts --billing-mode PROVISIONED --rcu 10 --wcu 5 --out table.yaml",\n "graphddb generate cloudformation --entry models.ts --billing-mode PROVISIONED --autoscale --autoscale-min 5 --autoscale-max 100 --autoscale-target 70 --out table.yaml"\n ],\n "effects": {\n "risk_level": "low",\n "reads": [\n "ts-definitions"\n ],\n "writes": [\n "generated-code"\n ]\n },\n "x-agent": {\n "recommended_before_use": [\n "Ensure the TypeScript definitions type-check and lint cleanly."\n ]\n },\n "options": [\n {\n "name": "entry",\n "aliases": [\n "e"\n ],\n "required": true,\n "description": "Entry module that registers the entity models (side-effecting import).",\n "value_name": "file",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true,\n "media_type": "application/typescript",\n "encoding": "utf-8"\n }\n },\n {\n "name": "out",\n "aliases": [\n "o"\n ],\n "description": "Output file for the generated CloudFormation template. Written to stdout when omitted.",\n "value_name": "file",\n "schema": {\n "type": "string"\n }\n },\n {\n "name": "format",\n "aliases": [\n "f"\n ],\n "description": "Output serialization format (yaml or json).",\n "value_name": "format",\n "schema": {\n "type": "string",\n "enum": [\n "yaml",\n "json"\n ],\n "default": "yaml"\n }\n },\n {\n "name": "gsi-projection",\n "description": "GSI ProjectionType. ALL (default) projects every attribute; KEYS_ONLY projects only the key attributes.",\n "value_name": "projection",\n "schema": {\n "type": "string",\n "enum": [\n "ALL",\n "KEYS_ONLY"\n ],\n "default": "ALL"\n }\n },\n {\n "name": "stream",\n "description": "DynamoDB Streams (StreamSpecification NEW_AND_OLD_IMAGES) emission. auto (default) emits a stream only when the model set uses stream-based maintenance (updateMode:\'stream\'); on always emits; off never emits. When enabled, a StreamArn Output is added too.",\n "value_name": "mode",\n "schema": {\n "type": "string",\n "enum": [\n "auto",\n "on",\n "off"\n ],\n "default": "auto"\n }\n },\n {\n "name": "billing-mode",\n "description": "DynamoDB billing mode. PAY_PER_REQUEST (default) is on-demand (no provisioned throughput). PROVISIONED emits BillingMode: PROVISIONED with ProvisionedThroughput (see --rcu / --wcu) on the table and every GSI, and unlocks Application Auto Scaling (--autoscale).",\n "value_name": "mode",\n "schema": {\n "type": "string",\n "enum": [\n "PAY_PER_REQUEST",\n "PROVISIONED"\n ],\n "default": "PAY_PER_REQUEST"\n }\n },\n {\n "name": "rcu",\n "description": "Provisioned ReadCapacityUnits (a positive integer) applied to the table and every GSI. Only valid with --billing-mode PROVISIONED (rejected with PAY_PER_REQUEST). Defaults to 5 when omitted.",\n "value_name": "units",\n "schema": {\n "type": "integer",\n "minimum": 1\n }\n },\n {\n "name": "wcu",\n "description": "Provisioned WriteCapacityUnits (a positive integer) applied to the table and every GSI. Only valid with --billing-mode PROVISIONED (rejected with PAY_PER_REQUEST). Defaults to 5 when omitted.",\n "value_name": "units",\n "schema": {\n "type": "integer",\n "minimum": 1\n }\n },\n {\n "name": "autoscale",\n "description": "Enable Application Auto Scaling. Emits an AWS::ApplicationAutoScaling::ScalableTarget + AWS::ApplicationAutoScaling::ScalingPolicy pair for the table read and write capacity AND for each GSI read and write capacity. Only valid with --billing-mode PROVISIONED (autoscaling is invalid on on-demand and is rejected). Off by default \u2014 no autoscaling resources are emitted unless this flag is passed.",\n "schema": {\n "type": "boolean"\n }\n },\n {\n "name": "autoscale-min",\n "description": "Auto Scaling minimum capacity (MinCapacity) for every scalable target. Must be <= --autoscale-max. Only meaningful with --autoscale. Defaults to 5 when omitted.",\n "value_name": "units",\n "schema": {\n "type": "integer",\n "minimum": 1\n }\n },\n {\n "name": "autoscale-max",\n "description": "Auto Scaling maximum capacity (MaxCapacity) for every scalable target. Must be >= --autoscale-min. Only meaningful with --autoscale. Defaults to 100 when omitted.",\n "value_name": "units",\n "schema": {\n "type": "integer",\n "minimum": 1\n }\n },\n {\n "name": "autoscale-target",\n "description": "Auto Scaling target utilization percentage (TargetValue of the TargetTrackingScaling policy). Only meaningful with --autoscale. Defaults to 70 when omitted.",\n "value_name": "percent",\n "schema": {\n "type": "integer",\n "minimum": 1,\n "maximum": 100\n }\n },\n {\n "name": "pitr",\n "description": "Enable point-in-time recovery (PointInTimeRecoverySpecification PointInTimeRecoveryEnabled:true). Off by default \u2014 the property is omitted entirely unless this flag is passed.",\n "schema": {\n "type": "boolean"\n }\n },\n {\n "name": "table-class",\n "description": "DynamoDB table class. Omitted by default (AWS defaults to STANDARD); pass STANDARD or STANDARD_INFREQUENT_ACCESS to emit TableClass.",\n "value_name": "class",\n "schema": {\n "type": "string",\n "enum": [\n "STANDARD",\n "STANDARD_INFREQUENT_ACCESS"\n ]\n }\n },\n {\n "name": "sse",\n "description": "Enable server-side encryption with the AWS-owned/managed key (SSESpecification SSEEnabled:true). Off by default \u2014 the property is omitted entirely unless this flag is passed.",\n "schema": {\n "type": "boolean"\n }\n }\n ],\n "exits": {\n "0": {\n "description": "Generation succeeded.",\n "stdout": {\n "format": "json",\n "schema": {\n "$ref": "#/components/schemas/GenerateResult"\n }\n }\n },\n "1": {\n "description": "Unexpected error.",\n "stderr": {\n "format": "json",\n "schema": {\n "$ref": "#/components/schemas/Error"\n }\n }\n },\n "2": {\n "description": "Invalid arguments (missing/unreadable input or output).",\n "stderr": {\n "format": "json",\n "schema": {\n "$ref": "#/components/schemas/Error"\n }\n }\n }\n }\n },\n "generate.docs": {\n "summary": "Generate model-specification documentation (Markdown + Mermaid) from TypeScript GraphDDB definitions.",\n "description": "Loads the TypeScript entity models (and, optionally, query / command definitions), builds the serializable manifest / operations bundle, then renders human-facing model documentation \u2014 Table Overview, Entity Relationship Diagram, Physical Key Schema, Property Matrix, Entity Details, Access Patterns, Mutation Contracts, Maintained Access Paths, CDC, and CloudFormation summary \u2014 as Markdown with Mermaid diagrams. The layout is defined by bundled Handlebars templates users can override with --template-dir (per-partial) and extend with --helpers. Output is deterministic for a given set of inputs.",\n "usage": [\n "graphddb generate docs --entry models.ts --out doc.md",\n "graphddb generate docs --entry models.ts --queries definitions.ts --out doc.md",\n "graphddb generate docs --entry models.ts --out docs/",\n "graphddb generate docs --entry models.ts --out docs/ --template-dir doc-templates/ --helpers doc-helpers.js"\n ],\n "effects": {\n "risk_level": "low",\n "reads": [\n "ts-definitions"\n ],\n "writes": [\n "generated-code"\n ]\n },\n "x-agent": {\n "recommended_before_use": [\n "Ensure the TypeScript definitions type-check and lint cleanly."\n ]\n },\n "options": [\n {\n "name": "entry",\n "aliases": [\n "e"\n ],\n "required": true,\n "description": "Entry module that registers the entity models (side-effecting import). May also export queries / commands.",\n "value_name": "file",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true,\n "media_type": "application/typescript",\n "encoding": "utf-8"\n }\n },\n {\n "name": "queries",\n "aliases": [\n "q"\n ],\n "description": "Module exporting `queries` / `commands` (defineQueries / defineCommands maps) used for the Access Patterns / Mutation Contracts sections. Defaults to the entry module when omitted.",\n "value_name": "file",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true,\n "media_type": "application/typescript",\n "encoding": "utf-8"\n }\n },\n {\n "name": "out",\n "aliases": [\n "o"\n ],\n "required": true,\n "description": "Output path for the generated documentation. A file path ending in .md writes the single Markdown file at exactly that path (creating parent directories); any other value is treated as a directory and the file is written as <dir>/index.md.",\n "value_name": "path",\n "schema": {\n "type": "string"\n }\n },\n {\n "name": "format",\n "aliases": [\n "f"\n ],\n "description": "Output format. Only markdown is supported today.",\n "value_name": "format",\n "schema": {\n "type": "string",\n "enum": [\n "markdown"\n ],\n "default": "markdown"\n }\n },\n {\n "name": "split",\n "description": "Output granularity. none (default) emits a single index.md; entity is reserved for a future per-entity split.",\n "value_name": "mode",\n "schema": {\n "type": "string",\n "enum": [\n "none",\n "entity"\n ],\n "default": "none"\n }\n },\n {\n "name": "template-dir",\n "description": "Directory of override <partial>.hbs templates. Each same-named partial (index / table-overview / er-diagram / key-schema / property-matrix / entity / access-pattern / maintained / cdc / cloudformation) overrides the bundled one; absent partials fall back to the bundled default.",\n "value_name": "dir",\n "schema": {\n "type": "string"\n }\n },\n {\n "name": "helpers",\n "description": "JS module whose default (or named `helpers`) export is a map of extra Handlebars helpers to register alongside the built-in set (eq / join / pascal / mermaidId / codeOrEmpty).",\n "value_name": "file",\n "schema": {\n "type": "string"\n }\n },\n {\n "name": "billing-mode",\n "description": "DynamoDB billing mode used in the Table Overview (shared with generate cloudformation). PAY_PER_REQUEST (default) or PROVISIONED.",\n "value_name": "mode",\n "schema": {\n "type": "string",\n "enum": [\n "PAY_PER_REQUEST",\n "PROVISIONED"\n ],\n "default": "PAY_PER_REQUEST"\n }\n },\n {\n "name": "stream",\n "description": "DynamoDB Streams state used in the Table Overview (shared with generate cloudformation). auto (default) detects stream-based maintenance (updateMode:\'stream\'); on always enabled; off never.",\n "value_name": "mode",\n "schema": {\n "type": "string",\n "enum": [\n "auto",\n "on",\n "off"\n ],\n "default": "auto"\n }\n }\n ],\n "exits": {\n "0": {\n "description": "Generation succeeded.",\n "stdout": {\n "format": "json",\n "schema": {\n "$ref": "#/components/schemas/GenerateResult"\n }\n }\n },\n "1": {\n "description": "Unexpected error.",\n "stderr": {\n "format": "json",\n "schema": {\n "$ref": "#/components/schemas/Error"\n }\n }\n },\n "2": {\n "description": "Invalid arguments (missing/unreadable input or output).",\n "stderr": {\n "format": "json",\n "schema": {\n "$ref": "#/components/schemas/Error"\n }\n }\n }\n }\n },\n "transform.prepared": {\n "summary": "Lint and hoist-normalize `DDBModel.prepare` call sites (compile-time prepared statements, issue #206).",\n "description": "A source-to-source build pass over application TypeScript files. It statically enforces the prepared-statement no-runtime-capture rule (a prepare body may reference only its `$` placeholder and module-static bindings; capturing a per-call runtime value \u2014 directly or via a helper call \u2014 is a loud error), and normalizes every verified INLINE `DDBModel.prepare(fn)` call site into a module-scope prepared slot so the plan compiles once per module and each call is a plain `.execute` (zero per-op planning / hashing \u2014 placement independent). Without --write it is a pure check (the build lint): violations exit non-zero and hoistable inline call sites are reported as notes. With --write, verified inline call sites are rewritten in place; files with violations are never rewritten. Idempotent: re-running on transformed output changes nothing. With --aot <artifact> (issue #208), the pass additionally compiles every verified prepare body at BUILD time into a static prepared-plan artifact (a generated TypeScript module): with --write it writes the artifact and rewrites each call site to load its static plan (`loadPreparedPlan(document, id, body)` \u2014 the body stays in source as the plan\'s compile source and fallback); without --write it is the DRIFT CHECK \u2014 it recompiles every plan and exits non-zero when the artifact is missing or differs (CI gate: model declarations changed without regenerating the artifact).",\n "usage": [\n "graphddb transform prepared --src src/",\n "graphddb transform prepared --src src/ --write",\n "graphddb transform prepared --src src/app/handlers.ts --write",\n "graphddb transform prepared --src src/ --aot src/graphddb.prepared.ts --write",\n "graphddb transform prepared --src src/ --aot src/graphddb.prepared.ts"\n ],\n "effects": {\n "risk_level": "medium",\n "reads": [\n "ts-app-sources"\n ],\n "writes": [\n "ts-app-sources"\n ]\n },\n "x-agent": {\n "recommended_before_use": [\n "Commit or stash pending changes first when using --write (it rewrites sources in place)."\n ]\n },\n "options": [\n {\n "name": "src",\n "aliases": [\n "s"\n ],\n "required": true,\n "description": "A TypeScript source file, or a directory scanned recursively for .ts/.tsx/.mts/.cts files (node_modules, dist, hidden directories, and .d.ts files are skipped).",\n "value_name": "path",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true\n }\n },\n {\n "name": "write",\n "aliases": [\n "w"\n ],\n "description": "Rewrite verified inline call sites in place (hoist normalization). Without it the command only checks and reports.",\n "schema": {\n "type": "boolean"\n }\n },\n {\n "name": "aot",\n "aliases": [\n "a"\n ],\n "description": "Static prepared-plan artifact module path (issue #208). Enables build-time (AOT) compilation of every verified prepare body: with --write the artifact module is (re)generated and call sites are rewritten to load their static plan; without --write the pass recompiles and compares (drift check \u2014 a missing or differing artifact exits 1).",\n "value_name": "artifact",\n "schema": {\n "type": "string"\n }\n }\n ],\n "exits": {\n "0": {\n "description": "Check passed / transform applied. No no-runtime-capture violations were found.",\n "stdout": {\n "format": "json",\n "schema": {\n "$ref": "#/components/schemas/TransformPreparedResult"\n }\n }\n },\n "1": {\n "description": "No-runtime-capture violations found, or --aot drift detected (reported in the result on stdout), or an unexpected error (reported on stderr).",\n "stdout": {\n "format": "json",\n "schema": {\n "$ref": "#/components/schemas/TransformPreparedResult"\n }\n }\n },\n "2": {\n "description": "Invalid arguments (missing/unreadable input).",\n "stderr": {\n "format": "json",\n "schema": {\n "$ref": "#/components/schemas/Error"\n }\n }\n }\n }\n }\n }\n }\n },\n "components": {\n "schemas": {\n "GenerateResult": {\n "type": "object",\n "required": [\n "status",\n "outDir",\n "files"\n ],\n "properties": {\n "status": {\n "type": "string",\n "enum": [\n "ok"\n ]\n },\n "outDir": {\n "type": "string",\n "description": "Output directory the files were written to."\n },\n "files": {\n "type": "array",\n "description": "Paths of the generated files (relative to outDir).",\n "items": {\n "type": "string"\n }\n }\n }\n },\n "Error": {\n "type": "object",\n "required": [\n "code",\n "message"\n ],\n "properties": {\n "code": {\n "type": "string"\n },\n "message": {\n "type": "string"\n }\n }\n },\n "TransformPreparedResult": {\n "type": "object",\n "required": [\n "status",\n "files",\n "hoisted",\n "moduleScope",\n "alreadyHoisted",\n "errors",\n "diagnostics"\n ],\n "properties": {\n "status": {\n "type": "string",\n "enum": [\n "ok",\n "violations",\n "drift"\n ]\n },\n "files": {\n "type": "array",\n "description": "Source files scanned.",\n "items": {\n "type": "string"\n }\n },\n "changedFiles": {\n "type": "array",\n "description": "Files rewritten in place (--write only).",\n "items": {\n "type": "string"\n }\n },\n "hoisted": {\n "type": "integer",\n "description": "Inline call sites normalized (or normalizable in check mode)."\n },\n "moduleScope": {\n "type": "integer",\n "description": "Call sites already evaluated once per module load (left as-is)."\n },\n "alreadyHoisted": {\n "type": "integer",\n "description": "Call sites already normalized by a previous run."\n },\n "errors": {\n "type": "integer",\n "description": "No-runtime-capture violations found."\n },\n "artifact": {\n "type": "string",\n "description": "The static prepared-plan artifact path (--aot only)."\n },\n "plans": {\n "type": "integer",\n "description": "Prepared plans compiled into the artifact (--aot only)."\n },\n "drift": {\n "type": "object",\n "description": "Artifact drift detail (--aot check mode only; present when status is drift).",\n "properties": {\n "missing": {\n "type": "boolean",\n "description": "The artifact module does not exist."\n },\n "added": {\n "type": "array",\n "items": {\n "type": "string"\n }\n },\n "removed": {\n "type": "array",\n "items": {\n "type": "string"\n }\n },\n "changed": {\n "type": "array",\n "items": {\n "type": "string"\n }\n }\n }\n },\n "diagnostics": {\n "type": "array",\n "description": "Per-position lint diagnostics (errors and notes).",\n "items": {\n "type": "object",\n "required": [\n "code",\n "category",\n "message",\n "file",\n "line",\n "column"\n ],\n "properties": {\n "code": {\n "type": "string"\n },\n "category": {\n "type": "string",\n "enum": [\n "error",\n "note"\n ]\n },\n "message": {\n "type": "string"\n },\n "file": {\n "type": "string"\n },\n "line": {\n "type": "integer"\n },\n "column": {\n "type": "integer"\n }\n }\n }\n }\n }\n }\n }\n }\n}';
609
-
610
- // src/generated/program.ts
571
+ },
572
+ {
573
+ "name": "transactions",
574
+ "schema": {
575
+ "type": "string"
576
+ },
577
+ "file": {
578
+ "mode": "read",
579
+ "exists": true,
580
+ "media_type": "application/typescript",
581
+ "encoding": "utf-8"
582
+ }
583
+ },
584
+ {
585
+ "name": "contracts",
586
+ "schema": {
587
+ "type": "string"
588
+ },
589
+ "file": {
590
+ "mode": "read",
591
+ "exists": true,
592
+ "media_type": "application/typescript",
593
+ "encoding": "utf-8"
594
+ }
595
+ },
596
+ {
597
+ "name": "contexts",
598
+ "schema": {
599
+ "type": "string"
600
+ },
601
+ "file": {
602
+ "mode": "read",
603
+ "exists": true,
604
+ "media_type": "application/typescript",
605
+ "encoding": "utf-8"
606
+ }
607
+ },
608
+ {
609
+ "name": "typed",
610
+ "schema": {
611
+ "type": "boolean"
612
+ }
613
+ },
614
+ {
615
+ "name": "template-dir",
616
+ "schema": {
617
+ "type": "string"
618
+ }
619
+ },
620
+ {
621
+ "name": "helpers",
622
+ "schema": {
623
+ "type": "string"
624
+ }
625
+ },
626
+ {
627
+ "name": "out",
628
+ "schema": {
629
+ "type": "string"
630
+ }
631
+ }
632
+ ]
633
+ },
634
+ "generate.cloudformation": {
635
+ "effects": {
636
+ "risk_level": "low",
637
+ "reads": [
638
+ "ts-definitions"
639
+ ],
640
+ "writes": [
641
+ "generated-code"
642
+ ]
643
+ },
644
+ "options": [
645
+ {
646
+ "name": "entry",
647
+ "schema": {
648
+ "type": "string"
649
+ },
650
+ "file": {
651
+ "mode": "read",
652
+ "exists": true,
653
+ "media_type": "application/typescript",
654
+ "encoding": "utf-8"
655
+ }
656
+ },
657
+ {
658
+ "name": "out",
659
+ "schema": {
660
+ "type": "string"
661
+ }
662
+ },
663
+ {
664
+ "name": "format",
665
+ "schema": {
666
+ "type": "string",
667
+ "enum": [
668
+ "yaml",
669
+ "json"
670
+ ],
671
+ "default": "yaml"
672
+ }
673
+ },
674
+ {
675
+ "name": "gsi-projection",
676
+ "schema": {
677
+ "type": "string",
678
+ "enum": [
679
+ "ALL",
680
+ "KEYS_ONLY"
681
+ ],
682
+ "default": "ALL"
683
+ }
684
+ },
685
+ {
686
+ "name": "stream",
687
+ "schema": {
688
+ "type": "string",
689
+ "enum": [
690
+ "auto",
691
+ "on",
692
+ "off"
693
+ ],
694
+ "default": "auto"
695
+ }
696
+ },
697
+ {
698
+ "name": "billing-mode",
699
+ "schema": {
700
+ "type": "string",
701
+ "enum": [
702
+ "PAY_PER_REQUEST",
703
+ "PROVISIONED"
704
+ ],
705
+ "default": "PAY_PER_REQUEST"
706
+ }
707
+ },
708
+ {
709
+ "name": "rcu",
710
+ "schema": {
711
+ "type": "integer",
712
+ "minimum": 1
713
+ }
714
+ },
715
+ {
716
+ "name": "wcu",
717
+ "schema": {
718
+ "type": "integer",
719
+ "minimum": 1
720
+ }
721
+ },
722
+ {
723
+ "name": "autoscale",
724
+ "schema": {
725
+ "type": "boolean"
726
+ }
727
+ },
728
+ {
729
+ "name": "autoscale-min",
730
+ "schema": {
731
+ "type": "integer",
732
+ "minimum": 1
733
+ }
734
+ },
735
+ {
736
+ "name": "autoscale-max",
737
+ "schema": {
738
+ "type": "integer",
739
+ "minimum": 1
740
+ }
741
+ },
742
+ {
743
+ "name": "autoscale-target",
744
+ "schema": {
745
+ "type": "integer",
746
+ "minimum": 1,
747
+ "maximum": 100
748
+ }
749
+ },
750
+ {
751
+ "name": "pitr",
752
+ "schema": {
753
+ "type": "boolean"
754
+ }
755
+ },
756
+ {
757
+ "name": "table-class",
758
+ "schema": {
759
+ "type": "string",
760
+ "enum": [
761
+ "STANDARD",
762
+ "STANDARD_INFREQUENT_ACCESS"
763
+ ]
764
+ }
765
+ },
766
+ {
767
+ "name": "sse",
768
+ "schema": {
769
+ "type": "boolean"
770
+ }
771
+ }
772
+ ]
773
+ },
774
+ "generate.docs": {
775
+ "effects": {
776
+ "risk_level": "low",
777
+ "reads": [
778
+ "ts-definitions"
779
+ ],
780
+ "writes": [
781
+ "generated-code"
782
+ ]
783
+ },
784
+ "options": [
785
+ {
786
+ "name": "entry",
787
+ "schema": {
788
+ "type": "string"
789
+ },
790
+ "file": {
791
+ "mode": "read",
792
+ "exists": true,
793
+ "media_type": "application/typescript",
794
+ "encoding": "utf-8"
795
+ }
796
+ },
797
+ {
798
+ "name": "queries",
799
+ "schema": {
800
+ "type": "string"
801
+ },
802
+ "file": {
803
+ "mode": "read",
804
+ "exists": true,
805
+ "media_type": "application/typescript",
806
+ "encoding": "utf-8"
807
+ }
808
+ },
809
+ {
810
+ "name": "out",
811
+ "schema": {
812
+ "type": "string"
813
+ }
814
+ },
815
+ {
816
+ "name": "format",
817
+ "schema": {
818
+ "type": "string",
819
+ "enum": [
820
+ "markdown"
821
+ ],
822
+ "default": "markdown"
823
+ }
824
+ },
825
+ {
826
+ "name": "split",
827
+ "schema": {
828
+ "type": "string",
829
+ "enum": [
830
+ "none",
831
+ "entity"
832
+ ],
833
+ "default": "none"
834
+ }
835
+ },
836
+ {
837
+ "name": "template-dir",
838
+ "schema": {
839
+ "type": "string"
840
+ }
841
+ },
842
+ {
843
+ "name": "helpers",
844
+ "schema": {
845
+ "type": "string"
846
+ }
847
+ },
848
+ {
849
+ "name": "billing-mode",
850
+ "schema": {
851
+ "type": "string",
852
+ "enum": [
853
+ "PAY_PER_REQUEST",
854
+ "PROVISIONED"
855
+ ],
856
+ "default": "PAY_PER_REQUEST"
857
+ }
858
+ },
859
+ {
860
+ "name": "stream",
861
+ "schema": {
862
+ "type": "string",
863
+ "enum": [
864
+ "auto",
865
+ "on",
866
+ "off"
867
+ ],
868
+ "default": "auto"
869
+ }
870
+ }
871
+ ]
872
+ },
873
+ "transform.prepared": {
874
+ "effects": {
875
+ "risk_level": "medium",
876
+ "reads": [
877
+ "ts-app-sources"
878
+ ],
879
+ "writes": [
880
+ "ts-app-sources"
881
+ ]
882
+ },
883
+ "options": [
884
+ {
885
+ "name": "src",
886
+ "schema": {
887
+ "type": "string"
888
+ },
889
+ "file": {
890
+ "mode": "read",
891
+ "exists": true
892
+ }
893
+ },
894
+ {
895
+ "name": "write",
896
+ "schema": {
897
+ "type": "boolean"
898
+ }
899
+ },
900
+ {
901
+ "name": "aot",
902
+ "schema": {
903
+ "type": "string"
904
+ }
905
+ }
906
+ ]
907
+ }
908
+ };
909
+ function deriveCommandPolicy(command_id, optionValues) {
910
+ const def = commandDefinitions[command_id];
911
+ if (!def) throw new Error(`Unknown command: ${command_id}`);
912
+ const cmdDef = def;
913
+ const options = {};
914
+ const active_options = [];
915
+ for (const optDef of cmdDef.options ?? []) {
916
+ const value = optionValues[optDef.name];
917
+ const specified = optDef.name in optionValues && value !== void 0;
918
+ options[optDef.name] = { value, specified, definition: optDef };
919
+ if (isOptionActive(optDef, value, specified)) {
920
+ active_options.push(optDef.name);
921
+ }
922
+ }
923
+ const policy = derivePolicy({
924
+ command_id,
925
+ command_effects: cmdDef.effects,
926
+ options,
927
+ env: cmdDef.env
928
+ });
929
+ return { command: command_id, active_options, policy };
930
+ }
931
+
932
+ // src/generated/contract.ts
933
+ var CONTRACT_YAML = "cli_contracts: 0.1.0\n\ninfo:\n title: GraphDDB CLI\n version: 0.1.0\n description: >-\n Contract for the graphddb code-generation CLI. The CLI reads TypeScript\n GraphDDB model / query / command definitions (the single source of truth)\n and emits Python bindings (manifest.json, operations.json, types.py,\n repositories.py, __init__.py) for the Python bridge runtime.\n license:\n name: MIT\n contact:\n name: foo-log-inc\n url: https://github.com/foo-log-inc/graphddb\n\nartifact_slots:\n ts-definitions:\n description: >-\n TypeScript GraphDDB definition modules (entity models, defineQueries,\n defineCommands) used as the single source of truth.\n direction: read\n generated-code:\n description: >-\n Generated Python bindings and JSON specs written to the output directory\n (manifest.json, operations.json, types.py, repositories.py, __init__.py).\n direction: write\n ts-app-sources:\n description: >-\n Application TypeScript sources containing `DDBModel.prepare` call sites.\n Read by the prepared-statement build lint; rewritten in place by\n `transform prepared --write` (hoist normalization, issue #206).\n direction: write\n\ncommand_sets:\n graphddb:\n summary: Graph data modeling on DynamoDB \u2014 code generation CLI.\n x-stdin: >-\n No command reads from stdin. All inputs are provided via file options.\n\n commands:\n # \u2500\u2500 generate python \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n generate.python:\n summary: Generate Python bindings from TypeScript GraphDDB definitions.\n description: >-\n Loads the TypeScript entity models, query definitions, and command\n definitions, builds the serializable manifest / operations bundle, and\n writes the Python bridge bindings (manifest.json, operations.json,\n types.py, repositories.py, __init__.py) to the output directory.\n Output is deterministic for a given set of inputs.\n usage:\n - graphddb generate python --entry models.ts --queries queries.ts --commands commands.ts --out generated/\n - graphddb generate python --entry definitions.ts --out generated/\n\n effects:\n risk_level: low\n reads:\n - ts-definitions\n writes:\n - generated-code\n\n x-agent:\n recommended_before_use:\n - \"Ensure the TypeScript definitions type-check and lint cleanly.\"\n\n options:\n - name: entry\n aliases: [e]\n required: true\n description: >-\n Entry module that registers the entity models (side-effecting\n import). May also export queries / commands.\n value_name: file\n schema:\n type: string\n file:\n mode: read\n exists: true\n media_type: application/typescript\n encoding: utf-8\n\n - name: queries\n aliases: [q]\n description: >-\n Module exporting `queries` (a defineQueries map). Defaults to the\n entry module when omitted.\n value_name: file\n schema:\n type: string\n file:\n mode: read\n exists: true\n media_type: application/typescript\n encoding: utf-8\n\n - name: commands\n aliases: [c]\n description: >-\n Module exporting `commands` (a defineCommands map). Defaults to the\n entry module when omitted.\n value_name: file\n schema:\n type: string\n file:\n mode: read\n exists: true\n media_type: application/typescript\n encoding: utf-8\n\n - name: transactions\n aliases: [t]\n description: >-\n Module exporting `transactions` (a defineTransactions map).\n Defaults to the commands module (or the entry module) when omitted.\n Optional \u2014 a project with no declarative transactions omits it.\n value_name: file\n schema:\n type: string\n file:\n mode: read\n exists: true\n media_type: application/typescript\n encoding: utf-8\n\n - name: contracts\n description: >-\n Module exporting `contracts` (a map of publicQueryModel /\n publicCommandModel results \u2014 the CQRS Contract layer). Defaults to\n the entry module when omitted. Optional \u2014 a project with no\n contracts omits it, and the output gains no `contracts` content.\n value_name: file\n schema:\n type: string\n file:\n mode: read\n exists: true\n media_type: application/typescript\n encoding: utf-8\n\n - name: contexts\n description: >-\n Module exporting `contexts` (context-ownership: context name \u2192 the\n Model / Contract names that belong to it). Defaults to the\n contracts module (or the entry module) when omitted. Optional \u2014\n consumed by the boundary lint to tell own from foreign.\n value_name: file\n schema:\n type: string\n file:\n mode: read\n exists: true\n media_type: application/typescript\n encoding: utf-8\n\n - name: dataclass\n description: >-\n Emit Python result / element types as @dataclass classes instead\n of TypedDict (decimal / Optional / Connection mapped accordingly).\n schema:\n type: boolean\n\n - name: out\n aliases: [o]\n required: true\n description: Output directory for the generated Python bindings.\n value_name: dir\n schema:\n type: string\n\n exits:\n '0':\n description: Generation succeeded.\n stdout:\n format: json\n schema:\n $ref: '#/components/schemas/GenerateResult'\n files:\n - path: '{options.out}/manifest.json'\n required: true\n media_type: application/json\n description: Entity / table / key / relation metadata.\n - path: '{options.out}/operations.json'\n required: true\n media_type: application/json\n description: Per-query / per-command execution specs.\n - path: '{options.out}/types.py'\n required: true\n media_type: text/x-python\n description: TypedDict result types derived from query selects.\n - path: '{options.out}/repositories.py'\n required: true\n media_type: text/x-python\n description: Per-entity repository classes (sync methods).\n - path: '{options.out}/__init__.py'\n required: true\n media_type: text/x-python\n description: Package re-exports for the generated bindings.\n\n '1':\n description: Unexpected error.\n stderr:\n format: json\n schema:\n $ref: '#/components/schemas/Error'\n\n '2':\n description: Invalid arguments (missing/unreadable input or output).\n stderr:\n format: json\n schema:\n $ref: '#/components/schemas/Error'\n\n # \u2500\u2500 generate php \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n generate.php:\n summary: >-\n Generate typed PHP bindings (repositories + result DTOs) from\n TypeScript GraphDDB definitions.\n description: >-\n Loads the TypeScript entity models, query definitions, and command\n definitions, builds the serializable manifest / operations bundle, and\n writes typed PHP bindings \u2014 one `<Entity>Repository` per entity plus a\n result DTO (and `<Item>Connection`) per query result shape \u2014 into the\n output directory. Each repository wraps the PHP `GraphDDBRuntime`; query\n methods return a hydrated `?<Result>` typed object, command methods\n return void, both calling the runtime by the original operation id. The\n layout is defined by bundled Handlebars templates users can override\n with --template-dir (per-partial) and extend with --helpers; type\n resolution always lives in code (the templates customize layout, not\n types). Output is deterministic for a given set of inputs. The `--typed`\n flag selects the typed-binding output (the only mode today; reserved for\n a future raw-binding mode).\n usage:\n - graphddb generate php --typed --entry models.ts --queries queries.ts --commands commands.ts --out generated/\n - graphddb generate php --typed --entry definitions.ts --out generated/\n - graphddb generate php --typed --entry definitions.ts --out generated/ --template-dir php-templates/ --helpers php-helpers.js\n\n effects:\n risk_level: low\n reads:\n - ts-definitions\n writes:\n - generated-code\n\n x-agent:\n recommended_before_use:\n - \"Ensure the TypeScript definitions type-check and lint cleanly.\"\n\n options:\n - name: entry\n aliases: [e]\n required: true\n description: >-\n Entry module that registers the entity models (side-effecting\n import). May also export queries / commands.\n value_name: file\n schema:\n type: string\n file:\n mode: read\n exists: true\n media_type: application/typescript\n encoding: utf-8\n\n - name: queries\n aliases: [q]\n description: >-\n Module exporting `queries` (a defineQueries map). Defaults to the\n entry module when omitted.\n value_name: file\n schema:\n type: string\n file:\n mode: read\n exists: true\n media_type: application/typescript\n encoding: utf-8\n\n - name: commands\n aliases: [c]\n description: >-\n Module exporting `commands` (a defineCommands map). Defaults to the\n entry module when omitted.\n value_name: file\n schema:\n type: string\n file:\n mode: read\n exists: true\n media_type: application/typescript\n encoding: utf-8\n\n - name: transactions\n aliases: [t]\n description: >-\n Module exporting `transactions` (a defineTransactions map).\n Defaults to the commands module (or the entry module) when omitted.\n Optional \u2014 a project with no declarative transactions omits it.\n value_name: file\n schema:\n type: string\n file:\n mode: read\n exists: true\n media_type: application/typescript\n encoding: utf-8\n\n - name: contracts\n description: >-\n Module exporting `contracts` (the CQRS Contract layer). Defaults to\n the entry module when omitted. Optional \u2014 contract-internal\n operations are excluded from the generated binding surface.\n value_name: file\n schema:\n type: string\n file:\n mode: read\n exists: true\n media_type: application/typescript\n encoding: utf-8\n\n - name: contexts\n description: >-\n Module exporting `contexts` (context-ownership). Defaults to the\n contracts module (or the entry module) when omitted. Optional.\n value_name: file\n schema:\n type: string\n file:\n mode: read\n exists: true\n media_type: application/typescript\n encoding: utf-8\n\n - name: typed\n description: >-\n Emit the typed-binding output (repositories + result DTOs). The only\n output mode today; reserved to distinguish a future raw-binding mode.\n schema:\n type: boolean\n\n - name: template-dir\n description: >-\n Directory of override <partial>.hbs templates. Each same-named\n partial (file / dto-class / connection-class / repo-class /\n repo-method / tx-class / tx-method) overrides the bundled one;\n absent partials fall back to the bundled default. Layout only \u2014 type\n resolution always lives in code.\n value_name: dir\n schema:\n type: string\n\n - name: helpers\n description: >-\n JS module whose default (or named `helpers`) export is a map of\n extra Handlebars helpers to register alongside the built-in set.\n value_name: file\n schema:\n type: string\n\n - name: out\n aliases: [o]\n required: true\n description: Output directory for the generated PHP bindings.\n value_name: dir\n schema:\n type: string\n\n exits:\n '0':\n description: Generation succeeded.\n stdout:\n format: json\n schema:\n $ref: '#/components/schemas/GenerateResult'\n\n '1':\n description: Unexpected error.\n stderr:\n format: json\n schema:\n $ref: '#/components/schemas/Error'\n\n '2':\n description: Invalid arguments (missing/unreadable input or output).\n stderr:\n format: json\n schema:\n $ref: '#/components/schemas/Error'\n\n # \u2500\u2500 generate rust \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n generate.rust:\n summary: >-\n Generate typed Rust bindings (repositories + serde result structs) from\n TypeScript GraphDDB definitions.\n description: >-\n Loads the TypeScript entity models, query definitions, and command\n definitions, builds the serializable manifest / operations bundle, and\n writes a single `bindings.rs` module of typed Rust bindings \u2014 a\n serde-`Deserialize` result struct (and `<Item>Connection`) per query\n result shape plus one `<Entity>Repository` per entity \u2014 into the output\n directory. Each repository holds an `Arc<GraphDDBRuntime>`; query methods\n return `Result<Option<T>, GraphDDBError>` (the deserialized typed\n result), command methods return `Result<(), GraphDDBError>`, both calling\n the runtime by the original operation id. The layout is defined by\n bundled Handlebars templates users can override with --template-dir\n (per-partial) and extend with --helpers; type resolution always lives in\n code (the templates customize layout, not types). Output is deterministic\n for a given set of inputs. The `--typed` flag selects the typed-binding\n output (the only mode today; reserved for a future raw-binding mode).\n usage:\n - graphddb generate rust --typed --entry models.ts --queries queries.ts --commands commands.ts --out generated/\n - graphddb generate rust --typed --entry definitions.ts --out generated/\n - graphddb generate rust --typed --entry definitions.ts --out generated/ --template-dir rust-templates/ --helpers rust-helpers.js\n\n effects:\n risk_level: low\n reads:\n - ts-definitions\n writes:\n - generated-code\n\n x-agent:\n recommended_before_use:\n - \"Ensure the TypeScript definitions type-check and lint cleanly.\"\n\n options:\n - name: entry\n aliases: [e]\n required: true\n description: >-\n Entry module that registers the entity models (side-effecting\n import). May also export queries / commands.\n value_name: file\n schema:\n type: string\n file:\n mode: read\n exists: true\n media_type: application/typescript\n encoding: utf-8\n\n - name: queries\n aliases: [q]\n description: >-\n Module exporting `queries` (a defineQueries map). Defaults to the\n entry module when omitted.\n value_name: file\n schema:\n type: string\n file:\n mode: read\n exists: true\n media_type: application/typescript\n encoding: utf-8\n\n - name: commands\n aliases: [c]\n description: >-\n Module exporting `commands` (a defineCommands map). Defaults to the\n entry module when omitted.\n value_name: file\n schema:\n type: string\n file:\n mode: read\n exists: true\n media_type: application/typescript\n encoding: utf-8\n\n - name: transactions\n aliases: [t]\n description: >-\n Module exporting `transactions` (a defineTransactions map).\n Defaults to the commands module (or the entry module) when omitted.\n Optional \u2014 a project with no declarative transactions omits it.\n value_name: file\n schema:\n type: string\n file:\n mode: read\n exists: true\n media_type: application/typescript\n encoding: utf-8\n\n - name: contracts\n description: >-\n Module exporting `contracts` (the CQRS Contract layer). Defaults to\n the entry module when omitted. Optional \u2014 contract-internal\n operations are excluded from the generated binding surface.\n value_name: file\n schema:\n type: string\n file:\n mode: read\n exists: true\n media_type: application/typescript\n encoding: utf-8\n\n - name: contexts\n description: >-\n Module exporting `contexts` (context-ownership). Defaults to the\n contracts module (or the entry module) when omitted. Optional.\n value_name: file\n schema:\n type: string\n file:\n mode: read\n exists: true\n media_type: application/typescript\n encoding: utf-8\n\n - name: typed\n description: >-\n Emit the typed-binding output (repositories + result structs). The\n only output mode today; reserved to distinguish a future raw-binding\n mode.\n schema:\n type: boolean\n\n - name: template-dir\n description: >-\n Directory of override <partial>.hbs templates. Each same-named\n partial (file / result-struct / connection-struct / repo-struct /\n repo-method / tx-struct / tx-method) overrides the bundled one;\n absent partials fall back to the bundled default. Layout only \u2014 type\n resolution always lives in code.\n value_name: dir\n schema:\n type: string\n\n - name: helpers\n description: >-\n JS module whose default (or named `helpers`) export is a map of\n extra Handlebars helpers to register alongside the built-in set.\n value_name: file\n schema:\n type: string\n\n - name: out\n aliases: [o]\n required: true\n description: Output directory for the generated Rust bindings.\n value_name: dir\n schema:\n type: string\n\n exits:\n '0':\n description: Generation succeeded.\n stdout:\n format: json\n schema:\n $ref: '#/components/schemas/GenerateResult'\n\n '1':\n description: Unexpected error.\n stderr:\n format: json\n schema:\n $ref: '#/components/schemas/Error'\n\n '2':\n description: Invalid arguments (missing/unreadable input or output).\n stderr:\n format: json\n schema:\n $ref: '#/components/schemas/Error'\n\n # \u2500\u2500 generate go \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n generate.go:\n summary: >-\n Generate typed Go bindings (repositories + json result structs) from\n TypeScript GraphDDB definitions.\n description: >-\n Loads the TypeScript entity models, query definitions, and command\n definitions, builds the serializable manifest / operations bundle, and\n writes a single `bindings.go` package of typed Go bindings \u2014 a json result\n struct (and `<Item>Connection`) per query result shape plus one\n `<Entity>Repository` per entity \u2014 into the output directory. Each\n repository holds a `*graphddb_runtime.GraphDDBRuntime`; query methods\n return `(*T, error)` (the deserialized typed result; a nil pointer for no\n match), command methods return `error`, both calling the runtime by the\n original operation id. Numbers are precision-strict (`json.Number`), so a\n large integer round-trips losslessly. The layout is defined by bundled\n Handlebars templates users can override with --template-dir (per-partial)\n and extend with --helpers; type resolution always lives in code (the\n templates customize layout, not types). Output is deterministic for a\n given set of inputs and gofmt-normalized. The `--typed` flag selects the\n typed-binding output (the only mode today; reserved for a future\n raw-binding mode).\n usage:\n - graphddb generate go --typed --entry models.ts --queries queries.ts --commands commands.ts --out generated/\n - graphddb generate go --typed --entry definitions.ts --out generated/\n - graphddb generate go --typed --entry definitions.ts --out generated/ --template-dir go-templates/ --helpers go-helpers.js\n\n effects:\n risk_level: low\n reads:\n - ts-definitions\n writes:\n - generated-code\n\n x-agent:\n recommended_before_use:\n - \"Ensure the TypeScript definitions type-check and lint cleanly.\"\n\n options:\n - name: entry\n aliases: [e]\n required: true\n description: >-\n Entry module that registers the entity models (side-effecting\n import). May also export queries / commands.\n value_name: file\n schema:\n type: string\n file:\n mode: read\n exists: true\n media_type: application/typescript\n encoding: utf-8\n\n - name: queries\n aliases: [q]\n description: >-\n Module exporting `queries` (a defineQueries map). Defaults to the\n entry module when omitted.\n value_name: file\n schema:\n type: string\n file:\n mode: read\n exists: true\n media_type: application/typescript\n encoding: utf-8\n\n - name: commands\n aliases: [c]\n description: >-\n Module exporting `commands` (a defineCommands map). Defaults to the\n entry module when omitted.\n value_name: file\n schema:\n type: string\n file:\n mode: read\n exists: true\n media_type: application/typescript\n encoding: utf-8\n\n - name: transactions\n aliases: [t]\n description: >-\n Module exporting `transactions` (a defineTransactions map).\n Defaults to the commands module (or the entry module) when omitted.\n Optional \u2014 a project with no declarative transactions omits it.\n value_name: file\n schema:\n type: string\n file:\n mode: read\n exists: true\n media_type: application/typescript\n encoding: utf-8\n\n - name: contracts\n description: >-\n Module exporting `contracts` (the CQRS Contract layer). Defaults to\n the entry module when omitted. Optional \u2014 contract-internal\n operations are excluded from the generated binding surface.\n value_name: file\n schema:\n type: string\n file:\n mode: read\n exists: true\n media_type: application/typescript\n encoding: utf-8\n\n - name: contexts\n description: >-\n Module exporting `contexts` (context-ownership). Defaults to the\n contracts module (or the entry module) when omitted. Optional.\n value_name: file\n schema:\n type: string\n file:\n mode: read\n exists: true\n media_type: application/typescript\n encoding: utf-8\n\n - name: typed\n description: >-\n Emit the typed-binding output (repositories + result structs). The\n only output mode today; reserved to distinguish a future raw-binding\n mode.\n schema:\n type: boolean\n\n - name: template-dir\n description: >-\n Directory of override <partial>.hbs templates. Each same-named\n partial (file / result-struct / connection-struct / repo-struct /\n repo-method / tx-struct / tx-method) overrides the bundled one;\n absent partials fall back to the bundled default. Layout only \u2014 type\n resolution always lives in code.\n value_name: dir\n schema:\n type: string\n\n - name: helpers\n description: >-\n JS module whose default (or named `helpers`) export is a map of\n extra Handlebars helpers to register alongside the built-in set.\n value_name: file\n schema:\n type: string\n\n - name: out\n aliases: [o]\n required: true\n description: Output directory for the generated Go bindings.\n value_name: dir\n schema:\n type: string\n\n exits:\n '0':\n description: Generation succeeded.\n stdout:\n format: json\n schema:\n $ref: '#/components/schemas/GenerateResult'\n\n '1':\n description: Unexpected error.\n stderr:\n format: json\n schema:\n $ref: '#/components/schemas/Error'\n\n '2':\n description: Invalid arguments (missing/unreadable input or output).\n stderr:\n format: json\n schema:\n $ref: '#/components/schemas/Error'\n\n # \u2500\u2500 generate cloudformation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n generate.cloudformation:\n summary: >-\n Generate an AWS CloudFormation template (DynamoDB table + GSIs) from\n TypeScript GraphDDB definitions.\n description: >-\n Loads the TypeScript entity models and builds the serializable\n manifest, then emits an AWS CloudFormation template describing the\n DynamoDB table(s) the single-table design maps onto: one\n `AWS::DynamoDB::Table` per physical table, with the base key schema\n (`PK` / `SK`), the unioned Global Secondary Indexes\n (`<indexName>PK` / `<indexName>SK`), `PAY_PER_REQUEST` billing, a\n `TableName` parameter (default = physical name), and Outputs\n (table name + ARN). Output is deterministic and, by default, YAML.\n usage:\n - graphddb generate cloudformation --entry models.ts --out table.yaml\n - graphddb generate cloudformation --entry models.ts --format json\n - graphddb generate cloudformation --entry models.ts --gsi-projection KEYS_ONLY --out table.yaml\n - graphddb generate cloudformation --entry models.ts --stream on --out table.yaml\n - graphddb generate cloudformation --entry models.ts --pitr --sse --table-class STANDARD_INFREQUENT_ACCESS --out table.yaml\n - graphddb generate cloudformation --entry models.ts --billing-mode PROVISIONED --rcu 10 --wcu 5 --out table.yaml\n - graphddb generate cloudformation --entry models.ts --billing-mode PROVISIONED --autoscale --autoscale-min 5 --autoscale-max 100 --autoscale-target 70 --out table.yaml\n\n effects:\n risk_level: low\n reads:\n - ts-definitions\n writes:\n - generated-code\n\n x-agent:\n recommended_before_use:\n - \"Ensure the TypeScript definitions type-check and lint cleanly.\"\n\n options:\n - name: entry\n aliases: [e]\n required: true\n description: >-\n Entry module that registers the entity models (side-effecting\n import).\n value_name: file\n schema:\n type: string\n file:\n mode: read\n exists: true\n media_type: application/typescript\n encoding: utf-8\n\n - name: out\n aliases: [o]\n description: >-\n Output file for the generated CloudFormation template. Written to\n stdout when omitted.\n value_name: file\n schema:\n type: string\n\n - name: format\n aliases: [f]\n description: Output serialization format (yaml or json).\n value_name: format\n schema:\n type: string\n enum: [yaml, json]\n default: yaml\n\n - name: gsi-projection\n description: >-\n GSI ProjectionType. ALL (default) projects every attribute;\n KEYS_ONLY projects only the key attributes.\n value_name: projection\n schema:\n type: string\n enum: [ALL, KEYS_ONLY]\n default: ALL\n\n - name: stream\n description: >-\n DynamoDB Streams (StreamSpecification NEW_AND_OLD_IMAGES) emission.\n auto (default) emits a stream only when the model set uses\n stream-based maintenance (updateMode:'stream'); on always emits;\n off never emits. When enabled, a StreamArn Output is added too.\n value_name: mode\n schema:\n type: string\n enum: [auto, on, off]\n default: auto\n\n - name: billing-mode\n description: >-\n DynamoDB billing mode. PAY_PER_REQUEST (default) is on-demand\n (no provisioned throughput). PROVISIONED emits BillingMode:\n PROVISIONED with ProvisionedThroughput (see --rcu / --wcu) on the\n table and every GSI, and unlocks Application Auto Scaling\n (--autoscale).\n value_name: mode\n schema:\n type: string\n enum: [PAY_PER_REQUEST, PROVISIONED]\n default: PAY_PER_REQUEST\n\n - name: rcu\n description: >-\n Provisioned ReadCapacityUnits (a positive integer) applied to the\n table and every GSI. Only valid with --billing-mode PROVISIONED\n (rejected with PAY_PER_REQUEST). Defaults to 5 when omitted.\n value_name: units\n schema:\n type: integer\n minimum: 1\n\n - name: wcu\n description: >-\n Provisioned WriteCapacityUnits (a positive integer) applied to the\n table and every GSI. Only valid with --billing-mode PROVISIONED\n (rejected with PAY_PER_REQUEST). Defaults to 5 when omitted.\n value_name: units\n schema:\n type: integer\n minimum: 1\n\n - name: autoscale\n description: >-\n Enable Application Auto Scaling. Emits an\n AWS::ApplicationAutoScaling::ScalableTarget +\n AWS::ApplicationAutoScaling::ScalingPolicy pair for the table read\n and write capacity AND for each GSI read and write capacity. Only\n valid with --billing-mode PROVISIONED (autoscaling is invalid on\n on-demand and is rejected). Off by default \u2014 no autoscaling\n resources are emitted unless this flag is passed.\n schema:\n type: boolean\n\n - name: autoscale-min\n description: >-\n Auto Scaling minimum capacity (MinCapacity) for every scalable\n target. Must be <= --autoscale-max. Only meaningful with\n --autoscale. Defaults to 5 when omitted.\n value_name: units\n schema:\n type: integer\n minimum: 1\n\n - name: autoscale-max\n description: >-\n Auto Scaling maximum capacity (MaxCapacity) for every scalable\n target. Must be >= --autoscale-min. Only meaningful with\n --autoscale. Defaults to 100 when omitted.\n value_name: units\n schema:\n type: integer\n minimum: 1\n\n - name: autoscale-target\n description: >-\n Auto Scaling target utilization percentage (TargetValue of the\n TargetTrackingScaling policy). Only meaningful with --autoscale.\n Defaults to 70 when omitted.\n value_name: percent\n schema:\n type: integer\n minimum: 1\n maximum: 100\n\n - name: pitr\n description: >-\n Enable point-in-time recovery (PointInTimeRecoverySpecification\n PointInTimeRecoveryEnabled:true). Off by default \u2014 the property is\n omitted entirely unless this flag is passed.\n schema:\n type: boolean\n\n - name: table-class\n description: >-\n DynamoDB table class. Omitted by default (AWS defaults to\n STANDARD); pass STANDARD or STANDARD_INFREQUENT_ACCESS to emit\n TableClass.\n value_name: class\n schema:\n type: string\n enum: [STANDARD, STANDARD_INFREQUENT_ACCESS]\n\n - name: sse\n description: >-\n Enable server-side encryption with the AWS-owned/managed key\n (SSESpecification SSEEnabled:true). Off by default \u2014 the property is\n omitted entirely unless this flag is passed.\n schema:\n type: boolean\n\n exits:\n '0':\n description: Generation succeeded.\n stdout:\n format: json\n schema:\n $ref: '#/components/schemas/GenerateResult'\n\n '1':\n description: Unexpected error.\n stderr:\n format: json\n schema:\n $ref: '#/components/schemas/Error'\n\n '2':\n description: Invalid arguments (missing/unreadable input or output).\n stderr:\n format: json\n schema:\n $ref: '#/components/schemas/Error'\n\n # \u2500\u2500 generate docs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n generate.docs:\n summary: >-\n Generate model-specification documentation (Markdown + Mermaid) from\n TypeScript GraphDDB definitions.\n description: >-\n Loads the TypeScript entity models (and, optionally, query / command\n definitions), builds the serializable manifest / operations bundle, then\n renders human-facing model documentation \u2014 Table Overview, Entity\n Relationship Diagram, Physical Key Schema, Property Matrix, Entity\n Details, Access Patterns, Mutation Contracts, Maintained Access Paths,\n CDC, and CloudFormation summary \u2014 as Markdown with Mermaid diagrams. The\n layout is defined by bundled Handlebars templates users can override with\n --template-dir (per-partial) and extend with --helpers. Output is\n deterministic for a given set of inputs.\n usage:\n - graphddb generate docs --entry models.ts --out doc.md\n - graphddb generate docs --entry models.ts --queries definitions.ts --out doc.md\n - graphddb generate docs --entry models.ts --out docs/\n - graphddb generate docs --entry models.ts --out docs/ --template-dir doc-templates/ --helpers doc-helpers.js\n\n effects:\n risk_level: low\n reads:\n - ts-definitions\n writes:\n - generated-code\n\n x-agent:\n recommended_before_use:\n - \"Ensure the TypeScript definitions type-check and lint cleanly.\"\n\n options:\n - name: entry\n aliases: [e]\n required: true\n description: >-\n Entry module that registers the entity models (side-effecting\n import). May also export queries / commands.\n value_name: file\n schema:\n type: string\n file:\n mode: read\n exists: true\n media_type: application/typescript\n encoding: utf-8\n\n - name: queries\n aliases: [q]\n description: >-\n Module exporting `queries` / `commands` (defineQueries /\n defineCommands maps) used for the Access Patterns / Mutation\n Contracts sections. Defaults to the entry module when omitted.\n value_name: file\n schema:\n type: string\n file:\n mode: read\n exists: true\n media_type: application/typescript\n encoding: utf-8\n\n - name: out\n aliases: [o]\n required: true\n description: >-\n Output path for the generated documentation. A file path ending in\n .md writes the single Markdown file at exactly that path (creating\n parent directories); any other value is treated as a directory and\n the file is written as <dir>/index.md.\n value_name: path\n schema:\n type: string\n\n - name: format\n aliases: [f]\n description: Output format. Only markdown is supported today.\n value_name: format\n schema:\n type: string\n enum: [markdown]\n default: markdown\n\n - name: split\n description: >-\n Output granularity. none (default) emits a single index.md; entity\n is reserved for a future per-entity split.\n value_name: mode\n schema:\n type: string\n enum: [none, entity]\n default: none\n\n - name: template-dir\n description: >-\n Directory of override <partial>.hbs templates. Each same-named\n partial (index / table-overview / er-diagram / key-schema /\n property-matrix / entity / access-pattern / maintained / cdc /\n cloudformation) overrides the bundled one; absent partials fall back\n to the bundled default.\n value_name: dir\n schema:\n type: string\n\n - name: helpers\n description: >-\n JS module whose default (or named `helpers`) export is a map of extra\n Handlebars helpers to register alongside the built-in set (eq / join\n / pascal / mermaidId / codeOrEmpty).\n value_name: file\n schema:\n type: string\n\n - name: billing-mode\n description: >-\n DynamoDB billing mode used in the Table Overview (shared with\n generate cloudformation). PAY_PER_REQUEST (default) or PROVISIONED.\n value_name: mode\n schema:\n type: string\n enum: [PAY_PER_REQUEST, PROVISIONED]\n default: PAY_PER_REQUEST\n\n - name: stream\n description: >-\n DynamoDB Streams state used in the Table Overview (shared with\n generate cloudformation). auto (default) detects stream-based\n maintenance (updateMode:'stream'); on always enabled; off never.\n value_name: mode\n schema:\n type: string\n enum: [auto, on, off]\n default: auto\n\n exits:\n '0':\n description: Generation succeeded.\n stdout:\n format: json\n schema:\n $ref: '#/components/schemas/GenerateResult'\n\n '1':\n description: Unexpected error.\n stderr:\n format: json\n schema:\n $ref: '#/components/schemas/Error'\n\n '2':\n description: Invalid arguments (missing/unreadable input or output).\n stderr:\n format: json\n schema:\n $ref: '#/components/schemas/Error'\n\n # \u2500\u2500 transform prepared \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n transform.prepared:\n summary: >-\n Lint and hoist-normalize `DDBModel.prepare` call sites (compile-time\n prepared statements, issue #206).\n description: >-\n A source-to-source build pass over application TypeScript files. It\n statically enforces the prepared-statement no-runtime-capture rule\n (a prepare body may reference only its `$` placeholder and\n module-static bindings; capturing a per-call runtime value \u2014 directly\n or via a helper call \u2014 is a loud error), and normalizes every\n verified INLINE `DDBModel.prepare(fn)` call site into a module-scope\n prepared slot so the plan compiles once per module and each call is\n a plain `.execute` (zero per-op planning / hashing \u2014 placement\n independent). Without --write it is a pure check (the build lint):\n violations exit non-zero and hoistable inline call sites are\n reported as notes. With --write, verified inline call sites are\n rewritten in place; files with violations are never rewritten.\n Idempotent: re-running on transformed output changes nothing.\n With --aot <artifact> (issue #208), the pass additionally compiles\n every verified prepare body at BUILD time into a static prepared-plan\n artifact (a generated TypeScript module): with --write it writes the\n artifact and rewrites each call site to load its static plan\n (`loadPreparedPlan(document, id, body)` \u2014 the body stays in source as\n the plan's compile source and fallback); without --write it is the\n DRIFT CHECK \u2014 it recompiles every plan and exits non-zero when the\n artifact is missing or differs (CI gate: model declarations changed\n without regenerating the artifact).\n usage:\n - graphddb transform prepared --src src/\n - graphddb transform prepared --src src/ --write\n - graphddb transform prepared --src src/app/handlers.ts --write\n - graphddb transform prepared --src src/ --aot src/graphddb.prepared.ts --write\n - graphddb transform prepared --src src/ --aot src/graphddb.prepared.ts\n\n effects:\n risk_level: medium\n reads:\n - ts-app-sources\n writes:\n - ts-app-sources\n\n x-agent:\n recommended_before_use:\n - \"Commit or stash pending changes first when using --write (it rewrites sources in place).\"\n\n options:\n - name: src\n aliases: [s]\n required: true\n description: >-\n A TypeScript source file, or a directory scanned recursively for\n .ts/.tsx/.mts/.cts files (node_modules, dist, hidden directories,\n and .d.ts files are skipped).\n value_name: path\n schema:\n type: string\n file:\n mode: read\n exists: true\n\n - name: write\n aliases: [w]\n description: >-\n Rewrite verified inline call sites in place (hoist\n normalization). Without it the command only checks and reports.\n schema:\n type: boolean\n\n - name: aot\n aliases: [a]\n description: >-\n Static prepared-plan artifact module path (issue #208). Enables\n build-time (AOT) compilation of every verified prepare body:\n with --write the artifact module is (re)generated and call sites\n are rewritten to load their static plan; without --write the\n pass recompiles and compares (drift check \u2014 a missing or\n differing artifact exits 1).\n value_name: artifact\n schema:\n type: string\n\n exits:\n '0':\n description: >-\n Check passed / transform applied. No no-runtime-capture\n violations were found.\n stdout:\n format: json\n schema:\n $ref: '#/components/schemas/TransformPreparedResult'\n\n '1':\n description: >-\n No-runtime-capture violations found, or --aot drift detected\n (reported in the result on stdout), or an unexpected error\n (reported on stderr).\n stdout:\n format: json\n schema:\n $ref: '#/components/schemas/TransformPreparedResult'\n\n '2':\n description: Invalid arguments (missing/unreadable input).\n stderr:\n format: json\n schema:\n $ref: '#/components/schemas/Error'\n\ncomponents:\n schemas:\n GenerateResult:\n type: object\n required: [status, outDir, files]\n properties:\n status:\n type: string\n enum: [ok]\n outDir:\n type: string\n description: Output directory the files were written to.\n files:\n type: array\n description: Paths of the generated files (relative to outDir).\n items:\n type: string\n Error:\n type: object\n required: [code, message]\n properties:\n code:\n type: string\n message:\n type: string\n TransformPreparedResult:\n type: object\n required: [status, files, hoisted, moduleScope, alreadyHoisted, errors, diagnostics]\n properties:\n status:\n type: string\n enum: [ok, violations, drift]\n files:\n type: array\n description: Source files scanned.\n items:\n type: string\n changedFiles:\n type: array\n description: Files rewritten in place (--write only).\n items:\n type: string\n hoisted:\n type: integer\n description: Inline call sites normalized (or normalizable in check mode).\n moduleScope:\n type: integer\n description: Call sites already evaluated once per module load (left as-is).\n alreadyHoisted:\n type: integer\n description: Call sites already normalized by a previous run.\n errors:\n type: integer\n description: No-runtime-capture violations found.\n artifact:\n type: string\n description: The static prepared-plan artifact path (--aot only).\n plans:\n type: integer\n description: Prepared plans compiled into the artifact (--aot only).\n drift:\n type: object\n description: >-\n Artifact drift detail (--aot check mode only; present when status\n is drift).\n properties:\n missing:\n type: boolean\n description: The artifact module does not exist.\n added:\n type: array\n items:\n type: string\n removed:\n type: array\n items:\n type: string\n changed:\n type: array\n items:\n type: string\n diagnostics:\n type: array\n description: Per-position lint diagnostics (errors and notes).\n items:\n type: object\n required: [code, category, message, file, line, column]\n properties:\n code:\n type: string\n category:\n type: string\n enum: [error, note]\n message:\n type: string\n file:\n type: string\n line:\n type: integer\n column:\n type: integer\n";
934
+ var CONTRACT_JSON_STR = '{\n "cli_contracts": "0.1.0",\n "info": {\n "title": "GraphDDB CLI",\n "version": "0.1.0",\n "description": "Contract for the graphddb code-generation CLI. The CLI reads TypeScript GraphDDB model / query / command definitions (the single source of truth) and emits Python bindings (manifest.json, operations.json, types.py, repositories.py, __init__.py) for the Python bridge runtime.",\n "license": {\n "name": "MIT"\n },\n "contact": {\n "name": "foo-log-inc",\n "url": "https://github.com/foo-log-inc/graphddb"\n }\n },\n "artifact_slots": {\n "ts-definitions": {\n "description": "TypeScript GraphDDB definition modules (entity models, defineQueries, defineCommands) used as the single source of truth.",\n "direction": "read"\n },\n "generated-code": {\n "description": "Generated Python bindings and JSON specs written to the output directory (manifest.json, operations.json, types.py, repositories.py, __init__.py).",\n "direction": "write"\n },\n "ts-app-sources": {\n "description": "Application TypeScript sources containing `DDBModel.prepare` call sites. Read by the prepared-statement build lint; rewritten in place by `transform prepared --write` (hoist normalization, issue #206).",\n "direction": "write"\n }\n },\n "command_sets": {\n "graphddb": {\n "summary": "Graph data modeling on DynamoDB \u2014 code generation CLI.",\n "x-stdin": "No command reads from stdin. All inputs are provided via file options.",\n "commands": {\n "generate.python": {\n "summary": "Generate Python bindings from TypeScript GraphDDB definitions.",\n "description": "Loads the TypeScript entity models, query definitions, and command definitions, builds the serializable manifest / operations bundle, and writes the Python bridge bindings (manifest.json, operations.json, types.py, repositories.py, __init__.py) to the output directory. Output is deterministic for a given set of inputs.",\n "usage": [\n "graphddb generate python --entry models.ts --queries queries.ts --commands commands.ts --out generated/",\n "graphddb generate python --entry definitions.ts --out generated/"\n ],\n "effects": {\n "risk_level": "low",\n "reads": [\n "ts-definitions"\n ],\n "writes": [\n "generated-code"\n ]\n },\n "x-agent": {\n "recommended_before_use": [\n "Ensure the TypeScript definitions type-check and lint cleanly."\n ]\n },\n "options": [\n {\n "name": "entry",\n "aliases": [\n "e"\n ],\n "required": true,\n "description": "Entry module that registers the entity models (side-effecting import). May also export queries / commands.",\n "value_name": "file",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true,\n "media_type": "application/typescript",\n "encoding": "utf-8"\n }\n },\n {\n "name": "queries",\n "aliases": [\n "q"\n ],\n "description": "Module exporting `queries` (a defineQueries map). Defaults to the entry module when omitted.",\n "value_name": "file",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true,\n "media_type": "application/typescript",\n "encoding": "utf-8"\n }\n },\n {\n "name": "commands",\n "aliases": [\n "c"\n ],\n "description": "Module exporting `commands` (a defineCommands map). Defaults to the entry module when omitted.",\n "value_name": "file",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true,\n "media_type": "application/typescript",\n "encoding": "utf-8"\n }\n },\n {\n "name": "transactions",\n "aliases": [\n "t"\n ],\n "description": "Module exporting `transactions` (a defineTransactions map). Defaults to the commands module (or the entry module) when omitted. Optional \u2014 a project with no declarative transactions omits it.",\n "value_name": "file",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true,\n "media_type": "application/typescript",\n "encoding": "utf-8"\n }\n },\n {\n "name": "contracts",\n "description": "Module exporting `contracts` (a map of publicQueryModel / publicCommandModel results \u2014 the CQRS Contract layer). Defaults to the entry module when omitted. Optional \u2014 a project with no contracts omits it, and the output gains no `contracts` content.",\n "value_name": "file",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true,\n "media_type": "application/typescript",\n "encoding": "utf-8"\n }\n },\n {\n "name": "contexts",\n "description": "Module exporting `contexts` (context-ownership: context name \u2192 the Model / Contract names that belong to it). Defaults to the contracts module (or the entry module) when omitted. Optional \u2014 consumed by the boundary lint to tell own from foreign.",\n "value_name": "file",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true,\n "media_type": "application/typescript",\n "encoding": "utf-8"\n }\n },\n {\n "name": "dataclass",\n "description": "Emit Python result / element types as @dataclass classes instead of TypedDict (decimal / Optional / Connection mapped accordingly).",\n "schema": {\n "type": "boolean"\n }\n },\n {\n "name": "out",\n "aliases": [\n "o"\n ],\n "required": true,\n "description": "Output directory for the generated Python bindings.",\n "value_name": "dir",\n "schema": {\n "type": "string"\n }\n }\n ],\n "exits": {\n "0": {\n "description": "Generation succeeded.",\n "stdout": {\n "format": "json",\n "schema": {\n "$ref": "#/components/schemas/GenerateResult"\n }\n },\n "files": [\n {\n "path": "{options.out}/manifest.json",\n "required": true,\n "media_type": "application/json",\n "description": "Entity / table / key / relation metadata."\n },\n {\n "path": "{options.out}/operations.json",\n "required": true,\n "media_type": "application/json",\n "description": "Per-query / per-command execution specs."\n },\n {\n "path": "{options.out}/types.py",\n "required": true,\n "media_type": "text/x-python",\n "description": "TypedDict result types derived from query selects."\n },\n {\n "path": "{options.out}/repositories.py",\n "required": true,\n "media_type": "text/x-python",\n "description": "Per-entity repository classes (sync methods)."\n },\n {\n "path": "{options.out}/__init__.py",\n "required": true,\n "media_type": "text/x-python",\n "description": "Package re-exports for the generated bindings."\n }\n ]\n },\n "1": {\n "description": "Unexpected error.",\n "stderr": {\n "format": "json",\n "schema": {\n "$ref": "#/components/schemas/Error"\n }\n }\n },\n "2": {\n "description": "Invalid arguments (missing/unreadable input or output).",\n "stderr": {\n "format": "json",\n "schema": {\n "$ref": "#/components/schemas/Error"\n }\n }\n }\n }\n },\n "generate.php": {\n "summary": "Generate typed PHP bindings (repositories + result DTOs) from TypeScript GraphDDB definitions.",\n "description": "Loads the TypeScript entity models, query definitions, and command definitions, builds the serializable manifest / operations bundle, and writes typed PHP bindings \u2014 one `<Entity>Repository` per entity plus a result DTO (and `<Item>Connection`) per query result shape \u2014 into the output directory. Each repository wraps the PHP `GraphDDBRuntime`; query methods return a hydrated `?<Result>` typed object, command methods return void, both calling the runtime by the original operation id. The layout is defined by bundled Handlebars templates users can override with --template-dir (per-partial) and extend with --helpers; type resolution always lives in code (the templates customize layout, not types). Output is deterministic for a given set of inputs. The `--typed` flag selects the typed-binding output (the only mode today; reserved for a future raw-binding mode).",\n "usage": [\n "graphddb generate php --typed --entry models.ts --queries queries.ts --commands commands.ts --out generated/",\n "graphddb generate php --typed --entry definitions.ts --out generated/",\n "graphddb generate php --typed --entry definitions.ts --out generated/ --template-dir php-templates/ --helpers php-helpers.js"\n ],\n "effects": {\n "risk_level": "low",\n "reads": [\n "ts-definitions"\n ],\n "writes": [\n "generated-code"\n ]\n },\n "x-agent": {\n "recommended_before_use": [\n "Ensure the TypeScript definitions type-check and lint cleanly."\n ]\n },\n "options": [\n {\n "name": "entry",\n "aliases": [\n "e"\n ],\n "required": true,\n "description": "Entry module that registers the entity models (side-effecting import). May also export queries / commands.",\n "value_name": "file",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true,\n "media_type": "application/typescript",\n "encoding": "utf-8"\n }\n },\n {\n "name": "queries",\n "aliases": [\n "q"\n ],\n "description": "Module exporting `queries` (a defineQueries map). Defaults to the entry module when omitted.",\n "value_name": "file",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true,\n "media_type": "application/typescript",\n "encoding": "utf-8"\n }\n },\n {\n "name": "commands",\n "aliases": [\n "c"\n ],\n "description": "Module exporting `commands` (a defineCommands map). Defaults to the entry module when omitted.",\n "value_name": "file",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true,\n "media_type": "application/typescript",\n "encoding": "utf-8"\n }\n },\n {\n "name": "transactions",\n "aliases": [\n "t"\n ],\n "description": "Module exporting `transactions` (a defineTransactions map). Defaults to the commands module (or the entry module) when omitted. Optional \u2014 a project with no declarative transactions omits it.",\n "value_name": "file",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true,\n "media_type": "application/typescript",\n "encoding": "utf-8"\n }\n },\n {\n "name": "contracts",\n "description": "Module exporting `contracts` (the CQRS Contract layer). Defaults to the entry module when omitted. Optional \u2014 contract-internal operations are excluded from the generated binding surface.",\n "value_name": "file",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true,\n "media_type": "application/typescript",\n "encoding": "utf-8"\n }\n },\n {\n "name": "contexts",\n "description": "Module exporting `contexts` (context-ownership). Defaults to the contracts module (or the entry module) when omitted. Optional.",\n "value_name": "file",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true,\n "media_type": "application/typescript",\n "encoding": "utf-8"\n }\n },\n {\n "name": "typed",\n "description": "Emit the typed-binding output (repositories + result DTOs). The only output mode today; reserved to distinguish a future raw-binding mode.",\n "schema": {\n "type": "boolean"\n }\n },\n {\n "name": "template-dir",\n "description": "Directory of override <partial>.hbs templates. Each same-named partial (file / dto-class / connection-class / repo-class / repo-method / tx-class / tx-method) overrides the bundled one; absent partials fall back to the bundled default. Layout only \u2014 type resolution always lives in code.",\n "value_name": "dir",\n "schema": {\n "type": "string"\n }\n },\n {\n "name": "helpers",\n "description": "JS module whose default (or named `helpers`) export is a map of extra Handlebars helpers to register alongside the built-in set.",\n "value_name": "file",\n "schema": {\n "type": "string"\n }\n },\n {\n "name": "out",\n "aliases": [\n "o"\n ],\n "required": true,\n "description": "Output directory for the generated PHP bindings.",\n "value_name": "dir",\n "schema": {\n "type": "string"\n }\n }\n ],\n "exits": {\n "0": {\n "description": "Generation succeeded.",\n "stdout": {\n "format": "json",\n "schema": {\n "$ref": "#/components/schemas/GenerateResult"\n }\n }\n },\n "1": {\n "description": "Unexpected error.",\n "stderr": {\n "format": "json",\n "schema": {\n "$ref": "#/components/schemas/Error"\n }\n }\n },\n "2": {\n "description": "Invalid arguments (missing/unreadable input or output).",\n "stderr": {\n "format": "json",\n "schema": {\n "$ref": "#/components/schemas/Error"\n }\n }\n }\n }\n },\n "generate.rust": {\n "summary": "Generate typed Rust bindings (repositories + serde result structs) from TypeScript GraphDDB definitions.",\n "description": "Loads the TypeScript entity models, query definitions, and command definitions, builds the serializable manifest / operations bundle, and writes a single `bindings.rs` module of typed Rust bindings \u2014 a serde-`Deserialize` result struct (and `<Item>Connection`) per query result shape plus one `<Entity>Repository` per entity \u2014 into the output directory. Each repository holds an `Arc<GraphDDBRuntime>`; query methods return `Result<Option<T>, GraphDDBError>` (the deserialized typed result), command methods return `Result<(), GraphDDBError>`, both calling the runtime by the original operation id. The layout is defined by bundled Handlebars templates users can override with --template-dir (per-partial) and extend with --helpers; type resolution always lives in code (the templates customize layout, not types). Output is deterministic for a given set of inputs. The `--typed` flag selects the typed-binding output (the only mode today; reserved for a future raw-binding mode).",\n "usage": [\n "graphddb generate rust --typed --entry models.ts --queries queries.ts --commands commands.ts --out generated/",\n "graphddb generate rust --typed --entry definitions.ts --out generated/",\n "graphddb generate rust --typed --entry definitions.ts --out generated/ --template-dir rust-templates/ --helpers rust-helpers.js"\n ],\n "effects": {\n "risk_level": "low",\n "reads": [\n "ts-definitions"\n ],\n "writes": [\n "generated-code"\n ]\n },\n "x-agent": {\n "recommended_before_use": [\n "Ensure the TypeScript definitions type-check and lint cleanly."\n ]\n },\n "options": [\n {\n "name": "entry",\n "aliases": [\n "e"\n ],\n "required": true,\n "description": "Entry module that registers the entity models (side-effecting import). May also export queries / commands.",\n "value_name": "file",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true,\n "media_type": "application/typescript",\n "encoding": "utf-8"\n }\n },\n {\n "name": "queries",\n "aliases": [\n "q"\n ],\n "description": "Module exporting `queries` (a defineQueries map). Defaults to the entry module when omitted.",\n "value_name": "file",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true,\n "media_type": "application/typescript",\n "encoding": "utf-8"\n }\n },\n {\n "name": "commands",\n "aliases": [\n "c"\n ],\n "description": "Module exporting `commands` (a defineCommands map). Defaults to the entry module when omitted.",\n "value_name": "file",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true,\n "media_type": "application/typescript",\n "encoding": "utf-8"\n }\n },\n {\n "name": "transactions",\n "aliases": [\n "t"\n ],\n "description": "Module exporting `transactions` (a defineTransactions map). Defaults to the commands module (or the entry module) when omitted. Optional \u2014 a project with no declarative transactions omits it.",\n "value_name": "file",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true,\n "media_type": "application/typescript",\n "encoding": "utf-8"\n }\n },\n {\n "name": "contracts",\n "description": "Module exporting `contracts` (the CQRS Contract layer). Defaults to the entry module when omitted. Optional \u2014 contract-internal operations are excluded from the generated binding surface.",\n "value_name": "file",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true,\n "media_type": "application/typescript",\n "encoding": "utf-8"\n }\n },\n {\n "name": "contexts",\n "description": "Module exporting `contexts` (context-ownership). Defaults to the contracts module (or the entry module) when omitted. Optional.",\n "value_name": "file",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true,\n "media_type": "application/typescript",\n "encoding": "utf-8"\n }\n },\n {\n "name": "typed",\n "description": "Emit the typed-binding output (repositories + result structs). The only output mode today; reserved to distinguish a future raw-binding mode.",\n "schema": {\n "type": "boolean"\n }\n },\n {\n "name": "template-dir",\n "description": "Directory of override <partial>.hbs templates. Each same-named partial (file / result-struct / connection-struct / repo-struct / repo-method / tx-struct / tx-method) overrides the bundled one; absent partials fall back to the bundled default. Layout only \u2014 type resolution always lives in code.",\n "value_name": "dir",\n "schema": {\n "type": "string"\n }\n },\n {\n "name": "helpers",\n "description": "JS module whose default (or named `helpers`) export is a map of extra Handlebars helpers to register alongside the built-in set.",\n "value_name": "file",\n "schema": {\n "type": "string"\n }\n },\n {\n "name": "out",\n "aliases": [\n "o"\n ],\n "required": true,\n "description": "Output directory for the generated Rust bindings.",\n "value_name": "dir",\n "schema": {\n "type": "string"\n }\n }\n ],\n "exits": {\n "0": {\n "description": "Generation succeeded.",\n "stdout": {\n "format": "json",\n "schema": {\n "$ref": "#/components/schemas/GenerateResult"\n }\n }\n },\n "1": {\n "description": "Unexpected error.",\n "stderr": {\n "format": "json",\n "schema": {\n "$ref": "#/components/schemas/Error"\n }\n }\n },\n "2": {\n "description": "Invalid arguments (missing/unreadable input or output).",\n "stderr": {\n "format": "json",\n "schema": {\n "$ref": "#/components/schemas/Error"\n }\n }\n }\n }\n },\n "generate.go": {\n "summary": "Generate typed Go bindings (repositories + json result structs) from TypeScript GraphDDB definitions.",\n "description": "Loads the TypeScript entity models, query definitions, and command definitions, builds the serializable manifest / operations bundle, and writes a single `bindings.go` package of typed Go bindings \u2014 a json result struct (and `<Item>Connection`) per query result shape plus one `<Entity>Repository` per entity \u2014 into the output directory. Each repository holds a `*graphddb_runtime.GraphDDBRuntime`; query methods return `(*T, error)` (the deserialized typed result; a nil pointer for no match), command methods return `error`, both calling the runtime by the original operation id. Numbers are precision-strict (`json.Number`), so a large integer round-trips losslessly. The layout is defined by bundled Handlebars templates users can override with --template-dir (per-partial) and extend with --helpers; type resolution always lives in code (the templates customize layout, not types). Output is deterministic for a given set of inputs and gofmt-normalized. The `--typed` flag selects the typed-binding output (the only mode today; reserved for a future raw-binding mode).",\n "usage": [\n "graphddb generate go --typed --entry models.ts --queries queries.ts --commands commands.ts --out generated/",\n "graphddb generate go --typed --entry definitions.ts --out generated/",\n "graphddb generate go --typed --entry definitions.ts --out generated/ --template-dir go-templates/ --helpers go-helpers.js"\n ],\n "effects": {\n "risk_level": "low",\n "reads": [\n "ts-definitions"\n ],\n "writes": [\n "generated-code"\n ]\n },\n "x-agent": {\n "recommended_before_use": [\n "Ensure the TypeScript definitions type-check and lint cleanly."\n ]\n },\n "options": [\n {\n "name": "entry",\n "aliases": [\n "e"\n ],\n "required": true,\n "description": "Entry module that registers the entity models (side-effecting import). May also export queries / commands.",\n "value_name": "file",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true,\n "media_type": "application/typescript",\n "encoding": "utf-8"\n }\n },\n {\n "name": "queries",\n "aliases": [\n "q"\n ],\n "description": "Module exporting `queries` (a defineQueries map). Defaults to the entry module when omitted.",\n "value_name": "file",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true,\n "media_type": "application/typescript",\n "encoding": "utf-8"\n }\n },\n {\n "name": "commands",\n "aliases": [\n "c"\n ],\n "description": "Module exporting `commands` (a defineCommands map). Defaults to the entry module when omitted.",\n "value_name": "file",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true,\n "media_type": "application/typescript",\n "encoding": "utf-8"\n }\n },\n {\n "name": "transactions",\n "aliases": [\n "t"\n ],\n "description": "Module exporting `transactions` (a defineTransactions map). Defaults to the commands module (or the entry module) when omitted. Optional \u2014 a project with no declarative transactions omits it.",\n "value_name": "file",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true,\n "media_type": "application/typescript",\n "encoding": "utf-8"\n }\n },\n {\n "name": "contracts",\n "description": "Module exporting `contracts` (the CQRS Contract layer). Defaults to the entry module when omitted. Optional \u2014 contract-internal operations are excluded from the generated binding surface.",\n "value_name": "file",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true,\n "media_type": "application/typescript",\n "encoding": "utf-8"\n }\n },\n {\n "name": "contexts",\n "description": "Module exporting `contexts` (context-ownership). Defaults to the contracts module (or the entry module) when omitted. Optional.",\n "value_name": "file",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true,\n "media_type": "application/typescript",\n "encoding": "utf-8"\n }\n },\n {\n "name": "typed",\n "description": "Emit the typed-binding output (repositories + result structs). The only output mode today; reserved to distinguish a future raw-binding mode.",\n "schema": {\n "type": "boolean"\n }\n },\n {\n "name": "template-dir",\n "description": "Directory of override <partial>.hbs templates. Each same-named partial (file / result-struct / connection-struct / repo-struct / repo-method / tx-struct / tx-method) overrides the bundled one; absent partials fall back to the bundled default. Layout only \u2014 type resolution always lives in code.",\n "value_name": "dir",\n "schema": {\n "type": "string"\n }\n },\n {\n "name": "helpers",\n "description": "JS module whose default (or named `helpers`) export is a map of extra Handlebars helpers to register alongside the built-in set.",\n "value_name": "file",\n "schema": {\n "type": "string"\n }\n },\n {\n "name": "out",\n "aliases": [\n "o"\n ],\n "required": true,\n "description": "Output directory for the generated Go bindings.",\n "value_name": "dir",\n "schema": {\n "type": "string"\n }\n }\n ],\n "exits": {\n "0": {\n "description": "Generation succeeded.",\n "stdout": {\n "format": "json",\n "schema": {\n "$ref": "#/components/schemas/GenerateResult"\n }\n }\n },\n "1": {\n "description": "Unexpected error.",\n "stderr": {\n "format": "json",\n "schema": {\n "$ref": "#/components/schemas/Error"\n }\n }\n },\n "2": {\n "description": "Invalid arguments (missing/unreadable input or output).",\n "stderr": {\n "format": "json",\n "schema": {\n "$ref": "#/components/schemas/Error"\n }\n }\n }\n }\n },\n "generate.cloudformation": {\n "summary": "Generate an AWS CloudFormation template (DynamoDB table + GSIs) from TypeScript GraphDDB definitions.",\n "description": "Loads the TypeScript entity models and builds the serializable manifest, then emits an AWS CloudFormation template describing the DynamoDB table(s) the single-table design maps onto: one `AWS::DynamoDB::Table` per physical table, with the base key schema (`PK` / `SK`), the unioned Global Secondary Indexes (`<indexName>PK` / `<indexName>SK`), `PAY_PER_REQUEST` billing, a `TableName` parameter (default = physical name), and Outputs (table name + ARN). Output is deterministic and, by default, YAML.",\n "usage": [\n "graphddb generate cloudformation --entry models.ts --out table.yaml",\n "graphddb generate cloudformation --entry models.ts --format json",\n "graphddb generate cloudformation --entry models.ts --gsi-projection KEYS_ONLY --out table.yaml",\n "graphddb generate cloudformation --entry models.ts --stream on --out table.yaml",\n "graphddb generate cloudformation --entry models.ts --pitr --sse --table-class STANDARD_INFREQUENT_ACCESS --out table.yaml",\n "graphddb generate cloudformation --entry models.ts --billing-mode PROVISIONED --rcu 10 --wcu 5 --out table.yaml",\n "graphddb generate cloudformation --entry models.ts --billing-mode PROVISIONED --autoscale --autoscale-min 5 --autoscale-max 100 --autoscale-target 70 --out table.yaml"\n ],\n "effects": {\n "risk_level": "low",\n "reads": [\n "ts-definitions"\n ],\n "writes": [\n "generated-code"\n ]\n },\n "x-agent": {\n "recommended_before_use": [\n "Ensure the TypeScript definitions type-check and lint cleanly."\n ]\n },\n "options": [\n {\n "name": "entry",\n "aliases": [\n "e"\n ],\n "required": true,\n "description": "Entry module that registers the entity models (side-effecting import).",\n "value_name": "file",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true,\n "media_type": "application/typescript",\n "encoding": "utf-8"\n }\n },\n {\n "name": "out",\n "aliases": [\n "o"\n ],\n "description": "Output file for the generated CloudFormation template. Written to stdout when omitted.",\n "value_name": "file",\n "schema": {\n "type": "string"\n }\n },\n {\n "name": "format",\n "aliases": [\n "f"\n ],\n "description": "Output serialization format (yaml or json).",\n "value_name": "format",\n "schema": {\n "type": "string",\n "enum": [\n "yaml",\n "json"\n ],\n "default": "yaml"\n }\n },\n {\n "name": "gsi-projection",\n "description": "GSI ProjectionType. ALL (default) projects every attribute; KEYS_ONLY projects only the key attributes.",\n "value_name": "projection",\n "schema": {\n "type": "string",\n "enum": [\n "ALL",\n "KEYS_ONLY"\n ],\n "default": "ALL"\n }\n },\n {\n "name": "stream",\n "description": "DynamoDB Streams (StreamSpecification NEW_AND_OLD_IMAGES) emission. auto (default) emits a stream only when the model set uses stream-based maintenance (updateMode:\'stream\'); on always emits; off never emits. When enabled, a StreamArn Output is added too.",\n "value_name": "mode",\n "schema": {\n "type": "string",\n "enum": [\n "auto",\n "on",\n "off"\n ],\n "default": "auto"\n }\n },\n {\n "name": "billing-mode",\n "description": "DynamoDB billing mode. PAY_PER_REQUEST (default) is on-demand (no provisioned throughput). PROVISIONED emits BillingMode: PROVISIONED with ProvisionedThroughput (see --rcu / --wcu) on the table and every GSI, and unlocks Application Auto Scaling (--autoscale).",\n "value_name": "mode",\n "schema": {\n "type": "string",\n "enum": [\n "PAY_PER_REQUEST",\n "PROVISIONED"\n ],\n "default": "PAY_PER_REQUEST"\n }\n },\n {\n "name": "rcu",\n "description": "Provisioned ReadCapacityUnits (a positive integer) applied to the table and every GSI. Only valid with --billing-mode PROVISIONED (rejected with PAY_PER_REQUEST). Defaults to 5 when omitted.",\n "value_name": "units",\n "schema": {\n "type": "integer",\n "minimum": 1\n }\n },\n {\n "name": "wcu",\n "description": "Provisioned WriteCapacityUnits (a positive integer) applied to the table and every GSI. Only valid with --billing-mode PROVISIONED (rejected with PAY_PER_REQUEST). Defaults to 5 when omitted.",\n "value_name": "units",\n "schema": {\n "type": "integer",\n "minimum": 1\n }\n },\n {\n "name": "autoscale",\n "description": "Enable Application Auto Scaling. Emits an AWS::ApplicationAutoScaling::ScalableTarget + AWS::ApplicationAutoScaling::ScalingPolicy pair for the table read and write capacity AND for each GSI read and write capacity. Only valid with --billing-mode PROVISIONED (autoscaling is invalid on on-demand and is rejected). Off by default \u2014 no autoscaling resources are emitted unless this flag is passed.",\n "schema": {\n "type": "boolean"\n }\n },\n {\n "name": "autoscale-min",\n "description": "Auto Scaling minimum capacity (MinCapacity) for every scalable target. Must be <= --autoscale-max. Only meaningful with --autoscale. Defaults to 5 when omitted.",\n "value_name": "units",\n "schema": {\n "type": "integer",\n "minimum": 1\n }\n },\n {\n "name": "autoscale-max",\n "description": "Auto Scaling maximum capacity (MaxCapacity) for every scalable target. Must be >= --autoscale-min. Only meaningful with --autoscale. Defaults to 100 when omitted.",\n "value_name": "units",\n "schema": {\n "type": "integer",\n "minimum": 1\n }\n },\n {\n "name": "autoscale-target",\n "description": "Auto Scaling target utilization percentage (TargetValue of the TargetTrackingScaling policy). Only meaningful with --autoscale. Defaults to 70 when omitted.",\n "value_name": "percent",\n "schema": {\n "type": "integer",\n "minimum": 1,\n "maximum": 100\n }\n },\n {\n "name": "pitr",\n "description": "Enable point-in-time recovery (PointInTimeRecoverySpecification PointInTimeRecoveryEnabled:true). Off by default \u2014 the property is omitted entirely unless this flag is passed.",\n "schema": {\n "type": "boolean"\n }\n },\n {\n "name": "table-class",\n "description": "DynamoDB table class. Omitted by default (AWS defaults to STANDARD); pass STANDARD or STANDARD_INFREQUENT_ACCESS to emit TableClass.",\n "value_name": "class",\n "schema": {\n "type": "string",\n "enum": [\n "STANDARD",\n "STANDARD_INFREQUENT_ACCESS"\n ]\n }\n },\n {\n "name": "sse",\n "description": "Enable server-side encryption with the AWS-owned/managed key (SSESpecification SSEEnabled:true). Off by default \u2014 the property is omitted entirely unless this flag is passed.",\n "schema": {\n "type": "boolean"\n }\n }\n ],\n "exits": {\n "0": {\n "description": "Generation succeeded.",\n "stdout": {\n "format": "json",\n "schema": {\n "$ref": "#/components/schemas/GenerateResult"\n }\n }\n },\n "1": {\n "description": "Unexpected error.",\n "stderr": {\n "format": "json",\n "schema": {\n "$ref": "#/components/schemas/Error"\n }\n }\n },\n "2": {\n "description": "Invalid arguments (missing/unreadable input or output).",\n "stderr": {\n "format": "json",\n "schema": {\n "$ref": "#/components/schemas/Error"\n }\n }\n }\n }\n },\n "generate.docs": {\n "summary": "Generate model-specification documentation (Markdown + Mermaid) from TypeScript GraphDDB definitions.",\n "description": "Loads the TypeScript entity models (and, optionally, query / command definitions), builds the serializable manifest / operations bundle, then renders human-facing model documentation \u2014 Table Overview, Entity Relationship Diagram, Physical Key Schema, Property Matrix, Entity Details, Access Patterns, Mutation Contracts, Maintained Access Paths, CDC, and CloudFormation summary \u2014 as Markdown with Mermaid diagrams. The layout is defined by bundled Handlebars templates users can override with --template-dir (per-partial) and extend with --helpers. Output is deterministic for a given set of inputs.",\n "usage": [\n "graphddb generate docs --entry models.ts --out doc.md",\n "graphddb generate docs --entry models.ts --queries definitions.ts --out doc.md",\n "graphddb generate docs --entry models.ts --out docs/",\n "graphddb generate docs --entry models.ts --out docs/ --template-dir doc-templates/ --helpers doc-helpers.js"\n ],\n "effects": {\n "risk_level": "low",\n "reads": [\n "ts-definitions"\n ],\n "writes": [\n "generated-code"\n ]\n },\n "x-agent": {\n "recommended_before_use": [\n "Ensure the TypeScript definitions type-check and lint cleanly."\n ]\n },\n "options": [\n {\n "name": "entry",\n "aliases": [\n "e"\n ],\n "required": true,\n "description": "Entry module that registers the entity models (side-effecting import). May also export queries / commands.",\n "value_name": "file",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true,\n "media_type": "application/typescript",\n "encoding": "utf-8"\n }\n },\n {\n "name": "queries",\n "aliases": [\n "q"\n ],\n "description": "Module exporting `queries` / `commands` (defineQueries / defineCommands maps) used for the Access Patterns / Mutation Contracts sections. Defaults to the entry module when omitted.",\n "value_name": "file",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true,\n "media_type": "application/typescript",\n "encoding": "utf-8"\n }\n },\n {\n "name": "out",\n "aliases": [\n "o"\n ],\n "required": true,\n "description": "Output path for the generated documentation. A file path ending in .md writes the single Markdown file at exactly that path (creating parent directories); any other value is treated as a directory and the file is written as <dir>/index.md.",\n "value_name": "path",\n "schema": {\n "type": "string"\n }\n },\n {\n "name": "format",\n "aliases": [\n "f"\n ],\n "description": "Output format. Only markdown is supported today.",\n "value_name": "format",\n "schema": {\n "type": "string",\n "enum": [\n "markdown"\n ],\n "default": "markdown"\n }\n },\n {\n "name": "split",\n "description": "Output granularity. none (default) emits a single index.md; entity is reserved for a future per-entity split.",\n "value_name": "mode",\n "schema": {\n "type": "string",\n "enum": [\n "none",\n "entity"\n ],\n "default": "none"\n }\n },\n {\n "name": "template-dir",\n "description": "Directory of override <partial>.hbs templates. Each same-named partial (index / table-overview / er-diagram / key-schema / property-matrix / entity / access-pattern / maintained / cdc / cloudformation) overrides the bundled one; absent partials fall back to the bundled default.",\n "value_name": "dir",\n "schema": {\n "type": "string"\n }\n },\n {\n "name": "helpers",\n "description": "JS module whose default (or named `helpers`) export is a map of extra Handlebars helpers to register alongside the built-in set (eq / join / pascal / mermaidId / codeOrEmpty).",\n "value_name": "file",\n "schema": {\n "type": "string"\n }\n },\n {\n "name": "billing-mode",\n "description": "DynamoDB billing mode used in the Table Overview (shared with generate cloudformation). PAY_PER_REQUEST (default) or PROVISIONED.",\n "value_name": "mode",\n "schema": {\n "type": "string",\n "enum": [\n "PAY_PER_REQUEST",\n "PROVISIONED"\n ],\n "default": "PAY_PER_REQUEST"\n }\n },\n {\n "name": "stream",\n "description": "DynamoDB Streams state used in the Table Overview (shared with generate cloudformation). auto (default) detects stream-based maintenance (updateMode:\'stream\'); on always enabled; off never.",\n "value_name": "mode",\n "schema": {\n "type": "string",\n "enum": [\n "auto",\n "on",\n "off"\n ],\n "default": "auto"\n }\n }\n ],\n "exits": {\n "0": {\n "description": "Generation succeeded.",\n "stdout": {\n "format": "json",\n "schema": {\n "$ref": "#/components/schemas/GenerateResult"\n }\n }\n },\n "1": {\n "description": "Unexpected error.",\n "stderr": {\n "format": "json",\n "schema": {\n "$ref": "#/components/schemas/Error"\n }\n }\n },\n "2": {\n "description": "Invalid arguments (missing/unreadable input or output).",\n "stderr": {\n "format": "json",\n "schema": {\n "$ref": "#/components/schemas/Error"\n }\n }\n }\n }\n },\n "transform.prepared": {\n "summary": "Lint and hoist-normalize `DDBModel.prepare` call sites (compile-time prepared statements, issue #206).",\n "description": "A source-to-source build pass over application TypeScript files. It statically enforces the prepared-statement no-runtime-capture rule (a prepare body may reference only its `$` placeholder and module-static bindings; capturing a per-call runtime value \u2014 directly or via a helper call \u2014 is a loud error), and normalizes every verified INLINE `DDBModel.prepare(fn)` call site into a module-scope prepared slot so the plan compiles once per module and each call is a plain `.execute` (zero per-op planning / hashing \u2014 placement independent). Without --write it is a pure check (the build lint): violations exit non-zero and hoistable inline call sites are reported as notes. With --write, verified inline call sites are rewritten in place; files with violations are never rewritten. Idempotent: re-running on transformed output changes nothing. With --aot <artifact> (issue #208), the pass additionally compiles every verified prepare body at BUILD time into a static prepared-plan artifact (a generated TypeScript module): with --write it writes the artifact and rewrites each call site to load its static plan (`loadPreparedPlan(document, id, body)` \u2014 the body stays in source as the plan\'s compile source and fallback); without --write it is the DRIFT CHECK \u2014 it recompiles every plan and exits non-zero when the artifact is missing or differs (CI gate: model declarations changed without regenerating the artifact).",\n "usage": [\n "graphddb transform prepared --src src/",\n "graphddb transform prepared --src src/ --write",\n "graphddb transform prepared --src src/app/handlers.ts --write",\n "graphddb transform prepared --src src/ --aot src/graphddb.prepared.ts --write",\n "graphddb transform prepared --src src/ --aot src/graphddb.prepared.ts"\n ],\n "effects": {\n "risk_level": "medium",\n "reads": [\n "ts-app-sources"\n ],\n "writes": [\n "ts-app-sources"\n ]\n },\n "x-agent": {\n "recommended_before_use": [\n "Commit or stash pending changes first when using --write (it rewrites sources in place)."\n ]\n },\n "options": [\n {\n "name": "src",\n "aliases": [\n "s"\n ],\n "required": true,\n "description": "A TypeScript source file, or a directory scanned recursively for .ts/.tsx/.mts/.cts files (node_modules, dist, hidden directories, and .d.ts files are skipped).",\n "value_name": "path",\n "schema": {\n "type": "string"\n },\n "file": {\n "mode": "read",\n "exists": true\n }\n },\n {\n "name": "write",\n "aliases": [\n "w"\n ],\n "description": "Rewrite verified inline call sites in place (hoist normalization). Without it the command only checks and reports.",\n "schema": {\n "type": "boolean"\n }\n },\n {\n "name": "aot",\n "aliases": [\n "a"\n ],\n "description": "Static prepared-plan artifact module path (issue #208). Enables build-time (AOT) compilation of every verified prepare body: with --write the artifact module is (re)generated and call sites are rewritten to load their static plan; without --write the pass recompiles and compares (drift check \u2014 a missing or differing artifact exits 1).",\n "value_name": "artifact",\n "schema": {\n "type": "string"\n }\n }\n ],\n "exits": {\n "0": {\n "description": "Check passed / transform applied. No no-runtime-capture violations were found.",\n "stdout": {\n "format": "json",\n "schema": {\n "$ref": "#/components/schemas/TransformPreparedResult"\n }\n }\n },\n "1": {\n "description": "No-runtime-capture violations found, or --aot drift detected (reported in the result on stdout), or an unexpected error (reported on stderr).",\n "stdout": {\n "format": "json",\n "schema": {\n "$ref": "#/components/schemas/TransformPreparedResult"\n }\n }\n },\n "2": {\n "description": "Invalid arguments (missing/unreadable input).",\n "stderr": {\n "format": "json",\n "schema": {\n "$ref": "#/components/schemas/Error"\n }\n }\n }\n }\n }\n }\n }\n },\n "components": {\n "schemas": {\n "GenerateResult": {\n "type": "object",\n "required": [\n "status",\n "outDir",\n "files"\n ],\n "properties": {\n "status": {\n "type": "string",\n "enum": [\n "ok"\n ]\n },\n "outDir": {\n "type": "string",\n "description": "Output directory the files were written to."\n },\n "files": {\n "type": "array",\n "description": "Paths of the generated files (relative to outDir).",\n "items": {\n "type": "string"\n }\n }\n }\n },\n "Error": {\n "type": "object",\n "required": [\n "code",\n "message"\n ],\n "properties": {\n "code": {\n "type": "string"\n },\n "message": {\n "type": "string"\n }\n }\n },\n "TransformPreparedResult": {\n "type": "object",\n "required": [\n "status",\n "files",\n "hoisted",\n "moduleScope",\n "alreadyHoisted",\n "errors",\n "diagnostics"\n ],\n "properties": {\n "status": {\n "type": "string",\n "enum": [\n "ok",\n "violations",\n "drift"\n ]\n },\n "files": {\n "type": "array",\n "description": "Source files scanned.",\n "items": {\n "type": "string"\n }\n },\n "changedFiles": {\n "type": "array",\n "description": "Files rewritten in place (--write only).",\n "items": {\n "type": "string"\n }\n },\n "hoisted": {\n "type": "integer",\n "description": "Inline call sites normalized (or normalizable in check mode)."\n },\n "moduleScope": {\n "type": "integer",\n "description": "Call sites already evaluated once per module load (left as-is)."\n },\n "alreadyHoisted": {\n "type": "integer",\n "description": "Call sites already normalized by a previous run."\n },\n "errors": {\n "type": "integer",\n "description": "No-runtime-capture violations found."\n },\n "artifact": {\n "type": "string",\n "description": "The static prepared-plan artifact path (--aot only)."\n },\n "plans": {\n "type": "integer",\n "description": "Prepared plans compiled into the artifact (--aot only)."\n },\n "drift": {\n "type": "object",\n "description": "Artifact drift detail (--aot check mode only; present when status is drift).",\n "properties": {\n "missing": {\n "type": "boolean",\n "description": "The artifact module does not exist."\n },\n "added": {\n "type": "array",\n "items": {\n "type": "string"\n }\n },\n "removed": {\n "type": "array",\n "items": {\n "type": "string"\n }\n },\n "changed": {\n "type": "array",\n "items": {\n "type": "string"\n }\n }\n }\n },\n "diagnostics": {\n "type": "array",\n "description": "Per-position lint diagnostics (errors and notes).",\n "items": {\n "type": "object",\n "required": [\n "code",\n "category",\n "message",\n "file",\n "line",\n "column"\n ],\n "properties": {\n "code": {\n "type": "string"\n },\n "category": {\n "type": "string",\n "enum": [\n "error",\n "note"\n ]\n },\n "message": {\n "type": "string"\n },\n "file": {\n "type": "string"\n },\n "line": {\n "type": "integer"\n },\n "column": {\n "type": "integer"\n }\n }\n }\n }\n }\n }\n }\n }\n}';
935
+
936
+ // src/generated/program.ts
611
937
  function createProgram(handlers2, version) {
612
938
  const program = new Command();
613
939
  program.name("graphddb").version(version, "-V, --version").description("Graph data modeling on DynamoDB \u2014 code generation CLI.");
@@ -622,6 +948,33 @@ function createProgram(handlers2, version) {
622
948
  }
623
949
  await handlers2.generatePython(opts, globalOpts);
624
950
  });
951
+ __cmd_generate.command("php").description("Generate typed PHP bindings (repositories + result DTOs) from TypeScript GraphDDB definitions.").option("-e, --entry <file>", "Entry module that registers the entity models (side-effecting import). May also export queries / commands.").option("-q, --queries <file>", "Module exporting `queries` (a defineQueries map). Defaults to the entry module when omitted.").option("-c, --commands <file>", "Module exporting `commands` (a defineCommands map). Defaults to the entry module when omitted.").option("-t, --transactions <file>", "Module exporting `transactions` (a defineTransactions map). Defaults to the commands module (or the entry module) when omitted. Optional \u2014 a project with no declarative transactions omits it.").option("--contracts <file>", "Module exporting `contracts` (the CQRS Contract layer). Defaults to the entry module when omitted. Optional \u2014 contract-internal operations are excluded from the generated binding surface.").option("--contexts <file>", "Module exporting `contexts` (context-ownership). Defaults to the contracts module (or the entry module) when omitted. Optional.").option("--typed", "Emit the typed-binding output (repositories + result DTOs). The only output mode today; reserved to distinguish a future raw-binding mode.").option("--template-dir <dir>", "Directory of override <partial>.hbs templates. Each same-named partial (file / dto-class / connection-class / repo-class / repo-method / tx-class / tx-method) overrides the bundled one; absent partials fall back to the bundled default. Layout only \u2014 type resolution always lives in code.").option("--helpers <file>", "JS module whose default (or named `helpers`) export is a map of extra Handlebars helpers to register alongside the built-in set.").option("-o, --out <dir>", "Output directory for the generated PHP bindings.").action(async (opts, cmd) => {
952
+ const globalOpts = cmd.optsWithGlobals();
953
+ if (globalOpts.introspect) {
954
+ const policy = deriveCommandPolicy("generate.php", opts);
955
+ console.log(JSON.stringify(policy, null, 2));
956
+ return;
957
+ }
958
+ await handlers2.generatePhp(opts, globalOpts);
959
+ });
960
+ __cmd_generate.command("rust").description("Generate typed Rust bindings (repositories + serde result structs) from TypeScript GraphDDB definitions.").option("-e, --entry <file>", "Entry module that registers the entity models (side-effecting import). May also export queries / commands.").option("-q, --queries <file>", "Module exporting `queries` (a defineQueries map). Defaults to the entry module when omitted.").option("-c, --commands <file>", "Module exporting `commands` (a defineCommands map). Defaults to the entry module when omitted.").option("-t, --transactions <file>", "Module exporting `transactions` (a defineTransactions map). Defaults to the commands module (or the entry module) when omitted. Optional \u2014 a project with no declarative transactions omits it.").option("--contracts <file>", "Module exporting `contracts` (the CQRS Contract layer). Defaults to the entry module when omitted. Optional \u2014 contract-internal operations are excluded from the generated binding surface.").option("--contexts <file>", "Module exporting `contexts` (context-ownership). Defaults to the contracts module (or the entry module) when omitted. Optional.").option("--typed", "Emit the typed-binding output (repositories + result structs). The only output mode today; reserved to distinguish a future raw-binding mode.").option("--template-dir <dir>", "Directory of override <partial>.hbs templates. Each same-named partial (file / result-struct / connection-struct / repo-struct / repo-method / tx-struct / tx-method) overrides the bundled one; absent partials fall back to the bundled default. Layout only \u2014 type resolution always lives in code.").option("--helpers <file>", "JS module whose default (or named `helpers`) export is a map of extra Handlebars helpers to register alongside the built-in set.").option("-o, --out <dir>", "Output directory for the generated Rust bindings.").action(async (opts, cmd) => {
961
+ const globalOpts = cmd.optsWithGlobals();
962
+ if (globalOpts.introspect) {
963
+ const policy = deriveCommandPolicy("generate.rust", opts);
964
+ console.log(JSON.stringify(policy, null, 2));
965
+ return;
966
+ }
967
+ await handlers2.generateRust(opts, globalOpts);
968
+ });
969
+ __cmd_generate.command("go").description("Generate typed Go bindings (repositories + json result structs) from TypeScript GraphDDB definitions.").option("-e, --entry <file>", "Entry module that registers the entity models (side-effecting import). May also export queries / commands.").option("-q, --queries <file>", "Module exporting `queries` (a defineQueries map). Defaults to the entry module when omitted.").option("-c, --commands <file>", "Module exporting `commands` (a defineCommands map). Defaults to the entry module when omitted.").option("-t, --transactions <file>", "Module exporting `transactions` (a defineTransactions map). Defaults to the commands module (or the entry module) when omitted. Optional \u2014 a project with no declarative transactions omits it.").option("--contracts <file>", "Module exporting `contracts` (the CQRS Contract layer). Defaults to the entry module when omitted. Optional \u2014 contract-internal operations are excluded from the generated binding surface.").option("--contexts <file>", "Module exporting `contexts` (context-ownership). Defaults to the contracts module (or the entry module) when omitted. Optional.").option("--typed", "Emit the typed-binding output (repositories + result structs). The only output mode today; reserved to distinguish a future raw-binding mode.").option("--template-dir <dir>", "Directory of override <partial>.hbs templates. Each same-named partial (file / result-struct / connection-struct / repo-struct / repo-method / tx-struct / tx-method) overrides the bundled one; absent partials fall back to the bundled default. Layout only \u2014 type resolution always lives in code.").option("--helpers <file>", "JS module whose default (or named `helpers`) export is a map of extra Handlebars helpers to register alongside the built-in set.").option("-o, --out <dir>", "Output directory for the generated Go bindings.").action(async (opts, cmd) => {
970
+ const globalOpts = cmd.optsWithGlobals();
971
+ if (globalOpts.introspect) {
972
+ const policy = deriveCommandPolicy("generate.go", opts);
973
+ console.log(JSON.stringify(policy, null, 2));
974
+ return;
975
+ }
976
+ await handlers2.generateGo(opts, globalOpts);
977
+ });
625
978
  __cmd_generate.command("cloudformation").description("Generate an AWS CloudFormation template (DynamoDB table + GSIs) from TypeScript GraphDDB definitions.").option("-e, --entry <file>", "Entry module that registers the entity models (side-effecting import).").option("-o, --out <file>", "Output file for the generated CloudFormation template. Written to stdout when omitted.").option("-f, --format <format>", "Output serialization format (yaml or json).", "yaml").option("--gsi-projection <projection>", "GSI ProjectionType. ALL (default) projects every attribute; KEYS_ONLY projects only the key attributes.", "ALL").option("--stream <mode>", "DynamoDB Streams (StreamSpecification NEW_AND_OLD_IMAGES) emission. auto (default) emits a stream only when the model set uses stream-based maintenance (updateMode:'stream'); on always emits; off never emits. When enabled, a StreamArn Output is added too.", "auto").option("--billing-mode <mode>", "DynamoDB billing mode. PAY_PER_REQUEST (default) is on-demand (no provisioned throughput). PROVISIONED emits BillingMode: PROVISIONED with ProvisionedThroughput (see --rcu / --wcu) on the table and every GSI, and unlocks Application Auto Scaling (--autoscale).", "PAY_PER_REQUEST").option("--rcu <units>", "Provisioned ReadCapacityUnits (a positive integer) applied to the table and every GSI. Only valid with --billing-mode PROVISIONED (rejected with PAY_PER_REQUEST). Defaults to 5 when omitted.").option("--wcu <units>", "Provisioned WriteCapacityUnits (a positive integer) applied to the table and every GSI. Only valid with --billing-mode PROVISIONED (rejected with PAY_PER_REQUEST). Defaults to 5 when omitted.").option("--autoscale", "Enable Application Auto Scaling. Emits an AWS::ApplicationAutoScaling::ScalableTarget + AWS::ApplicationAutoScaling::ScalingPolicy pair for the table read and write capacity AND for each GSI read and write capacity. Only valid with --billing-mode PROVISIONED (autoscaling is invalid on on-demand and is rejected). Off by default \u2014 no autoscaling resources are emitted unless this flag is passed.").option("--autoscale-min <units>", "Auto Scaling minimum capacity (MinCapacity) for every scalable target. Must be <= --autoscale-max. Only meaningful with --autoscale. Defaults to 5 when omitted.").option("--autoscale-max <units>", "Auto Scaling maximum capacity (MaxCapacity) for every scalable target. Must be >= --autoscale-min. Only meaningful with --autoscale. Defaults to 100 when omitted.").option("--autoscale-target <percent>", "Auto Scaling target utilization percentage (TargetValue of the TargetTrackingScaling policy). Only meaningful with --autoscale. Defaults to 70 when omitted.").option("--pitr", "Enable point-in-time recovery (PointInTimeRecoverySpecification PointInTimeRecoveryEnabled:true). Off by default \u2014 the property is omitted entirely unless this flag is passed.").option("--table-class <class>", "DynamoDB table class. Omitted by default (AWS defaults to STANDARD); pass STANDARD or STANDARD_INFREQUENT_ACCESS to emit TableClass.").option("--sse", "Enable server-side encryption with the AWS-owned/managed key (SSESpecification SSEEnabled:true). Off by default \u2014 the property is omitted entirely unless this flag is passed.").action(async (opts, cmd) => {
626
979
  const globalOpts = cmd.optsWithGlobals();
627
980
  if (globalOpts.introspect) {
@@ -631,511 +984,1942 @@ function createProgram(handlers2, version) {
631
984
  }
632
985
  await handlers2.generateCloudformation(opts, globalOpts);
633
986
  });
634
- __cmd_generate.command("docs").description("Generate model-specification documentation (Markdown + Mermaid) from TypeScript GraphDDB definitions.").option("-e, --entry <file>", "Entry module that registers the entity models (side-effecting import). May also export queries / commands.").option("-q, --queries <file>", "Module exporting `queries` / `commands` (defineQueries / defineCommands maps) used for the Access Patterns / Mutation Contracts sections. Defaults to the entry module when omitted.").option("-o, --out <path>", "Output path for the generated documentation. A file path ending in .md writes the single Markdown file at exactly that path (creating parent directories); any other value is treated as a directory and the file is written as <dir>/index.md.").option("-f, --format <format>", "Output format. Only markdown is supported today.", "markdown").option("--split <mode>", "Output granularity. none (default) emits a single index.md; entity is reserved for a future per-entity split.", "none").option("--template-dir <dir>", "Directory of override <partial>.hbs templates. Each same-named partial (index / table-overview / er-diagram / key-schema / property-matrix / entity / access-pattern / maintained / cdc / cloudformation) overrides the bundled one; absent partials fall back to the bundled default.").option("--helpers <file>", "JS module whose default (or named `helpers`) export is a map of extra Handlebars helpers to register alongside the built-in set (eq / join / pascal / mermaidId / codeOrEmpty).").option("--billing-mode <mode>", "DynamoDB billing mode used in the Table Overview (shared with generate cloudformation). PAY_PER_REQUEST (default) or PROVISIONED.", "PAY_PER_REQUEST").option("--stream <mode>", "DynamoDB Streams state used in the Table Overview (shared with generate cloudformation). auto (default) detects stream-based maintenance (updateMode:'stream'); on always enabled; off never.", "auto").action(async (opts, cmd) => {
635
- const globalOpts = cmd.optsWithGlobals();
636
- if (globalOpts.introspect) {
637
- const policy = deriveCommandPolicy("generate.docs", opts);
638
- console.log(JSON.stringify(policy, null, 2));
639
- return;
987
+ __cmd_generate.command("docs").description("Generate model-specification documentation (Markdown + Mermaid) from TypeScript GraphDDB definitions.").option("-e, --entry <file>", "Entry module that registers the entity models (side-effecting import). May also export queries / commands.").option("-q, --queries <file>", "Module exporting `queries` / `commands` (defineQueries / defineCommands maps) used for the Access Patterns / Mutation Contracts sections. Defaults to the entry module when omitted.").option("-o, --out <path>", "Output path for the generated documentation. A file path ending in .md writes the single Markdown file at exactly that path (creating parent directories); any other value is treated as a directory and the file is written as <dir>/index.md.").option("-f, --format <format>", "Output format. Only markdown is supported today.", "markdown").option("--split <mode>", "Output granularity. none (default) emits a single index.md; entity is reserved for a future per-entity split.", "none").option("--template-dir <dir>", "Directory of override <partial>.hbs templates. Each same-named partial (index / table-overview / er-diagram / key-schema / property-matrix / entity / access-pattern / maintained / cdc / cloudformation) overrides the bundled one; absent partials fall back to the bundled default.").option("--helpers <file>", "JS module whose default (or named `helpers`) export is a map of extra Handlebars helpers to register alongside the built-in set (eq / join / pascal / mermaidId / codeOrEmpty).").option("--billing-mode <mode>", "DynamoDB billing mode used in the Table Overview (shared with generate cloudformation). PAY_PER_REQUEST (default) or PROVISIONED.", "PAY_PER_REQUEST").option("--stream <mode>", "DynamoDB Streams state used in the Table Overview (shared with generate cloudformation). auto (default) detects stream-based maintenance (updateMode:'stream'); on always enabled; off never.", "auto").action(async (opts, cmd) => {
988
+ const globalOpts = cmd.optsWithGlobals();
989
+ if (globalOpts.introspect) {
990
+ const policy = deriveCommandPolicy("generate.docs", opts);
991
+ console.log(JSON.stringify(policy, null, 2));
992
+ return;
993
+ }
994
+ await handlers2.generateDocs(opts, globalOpts);
995
+ });
996
+ const __cmd_transform = program.command("transform");
997
+ __cmd_transform.command("prepared").description("Lint and hoist-normalize `DDBModel.prepare` call sites (compile-time prepared statements, issue #206).").option("-s, --src <path>", "A TypeScript source file, or a directory scanned recursively for .ts/.tsx/.mts/.cts files (node_modules, dist, hidden directories, and .d.ts files are skipped).").option("-w, --write", "Rewrite verified inline call sites in place (hoist normalization). Without it the command only checks and reports.").option("-a, --aot <artifact>", "Static prepared-plan artifact module path (issue #208). Enables build-time (AOT) compilation of every verified prepare body: with --write the artifact module is (re)generated and call sites are rewritten to load their static plan; without --write the pass recompiles and compares (drift check \u2014 a missing or differing artifact exits 1).").action(async (opts, cmd) => {
998
+ const globalOpts = cmd.optsWithGlobals();
999
+ if (globalOpts.introspect) {
1000
+ const policy = deriveCommandPolicy("transform.prepared", opts);
1001
+ console.log(JSON.stringify(policy, null, 2));
1002
+ return;
1003
+ }
1004
+ await handlers2.transformPrepared(opts, globalOpts);
1005
+ });
1006
+ program.command("extract").description("Extract contract specification for this CLI tool.").argument("[commands...]", "Command IDs to extract. Use dot notation.").option("-a, --all", "Extract all commands.", false).option("--include-meta", "Include extraction metadata.", true).option("-F, --format <format>", "Output format (yaml or json).", "yaml").action(async (commands, opts, cmd) => {
1007
+ if (commands.length === 0 && !opts.all) {
1008
+ process.stderr.write(JSON.stringify({ code: "INVALID_ARGS", message: "Specify command IDs or use --all" }) + "\n");
1009
+ process.exit(2);
1010
+ }
1011
+ const format = opts.format || "yaml";
1012
+ const doc = JSON.parse(CONTRACT_JSON_STR);
1013
+ const cmdIds = opts.all ? [] : commands;
1014
+ if (cmdIds.length === 0) {
1015
+ if (format === "json") {
1016
+ const out = {};
1017
+ if (opts.includeMeta) {
1018
+ out._meta = {
1019
+ source: "embedded",
1020
+ type: "cli-contracts/extract",
1021
+ extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
1022
+ specVersion: doc.cli_contracts ?? "0.1.0",
1023
+ commands: ["graphddb.generate.python", "graphddb.generate.php", "graphddb.generate.rust", "graphddb.generate.go", "graphddb.generate.cloudformation", "graphddb.generate.docs", "graphddb.transform.prepared"]
1024
+ };
1025
+ }
1026
+ Object.assign(out, doc);
1027
+ process.stdout.write(JSON.stringify(out, null, 2) + "\n");
1028
+ } else {
1029
+ const yamlLines = [];
1030
+ yamlLines.push("# graphddb extract");
1031
+ yamlLines.push("# source: embedded");
1032
+ yamlLines.push("# type: cli-contracts/command-extract");
1033
+ if (opts.includeMeta) {
1034
+ yamlLines.push("---");
1035
+ yamlLines.push("source: embedded");
1036
+ yamlLines.push("type: cli-contracts/command-extract");
1037
+ yamlLines.push("extractedAt: " + (/* @__PURE__ */ new Date()).toISOString());
1038
+ yamlLines.push("spec_version: " + (doc.cli_contracts ?? "0.1.0"));
1039
+ yamlLines.push("commands:");
1040
+ for (const id of ["graphddb.generate.python", "graphddb.generate.php", "graphddb.generate.rust", "graphddb.generate.go", "graphddb.generate.cloudformation", "graphddb.generate.docs", "graphddb.transform.prepared"]) {
1041
+ yamlLines.push(" - " + id);
1042
+ }
1043
+ }
1044
+ yamlLines.push("---");
1045
+ yamlLines.push(CONTRACT_YAML);
1046
+ process.stdout.write(yamlLines.join("\n") + "\n");
1047
+ }
1048
+ } else {
1049
+ const filtered = {
1050
+ cli_contracts: doc.cli_contracts,
1051
+ info: doc.info,
1052
+ command_sets: {}
1053
+ };
1054
+ const fcs = filtered.command_sets;
1055
+ for (const [setId, cs] of Object.entries(doc.command_sets ?? {})) {
1056
+ const cmds = cs.commands;
1057
+ if (!cmds) continue;
1058
+ const matched = {};
1059
+ for (const [cmdId, cmdDef] of Object.entries(cmds)) {
1060
+ const fullId = setId + "." + cmdId;
1061
+ if (cmdIds.some((id) => id === cmdId || id === fullId || cmdId.startsWith(id + "."))) {
1062
+ matched[cmdId] = cmdDef;
1063
+ }
1064
+ }
1065
+ if (Object.keys(matched).length > 0) {
1066
+ const setCopy = { ...cs };
1067
+ setCopy.commands = matched;
1068
+ fcs[setId] = setCopy;
1069
+ }
1070
+ }
1071
+ if (doc.components) filtered.components = doc.components;
1072
+ process.stdout.write(JSON.stringify(filtered, null, 2) + "\n");
1073
+ }
1074
+ process.exit(0);
1075
+ });
1076
+ return program;
1077
+ }
1078
+
1079
+ // src/cli/handlers.ts
1080
+ import { spawnSync } from "child_process";
1081
+ import { mkdirSync, readFileSync as readFileSync3, readdirSync, statSync, writeFileSync } from "fs";
1082
+ import { dirname, relative } from "path";
1083
+ import { pathToFileURL } from "url";
1084
+ import { isAbsolute, resolve as resolve3 } from "path";
1085
+
1086
+ // src/codegen/binding-model.ts
1087
+ function toPascalCase(name) {
1088
+ return name.replace(/[_-]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/\s+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
1089
+ }
1090
+ function entityBaseName(entity) {
1091
+ return entity.replace(/Model$/, "");
1092
+ }
1093
+ function queryResultTypeName(queryName) {
1094
+ return `${toPascalCase(queryName)}Result`;
1095
+ }
1096
+ function txElementTypeName(txName, paramName) {
1097
+ return `${toPascalCase(txName)}${toPascalCase(paramName)}Item`;
1098
+ }
1099
+ function relationSegments(resultPath) {
1100
+ if (resultPath === "$") return [];
1101
+ const parts = resultPath.replace(/^\$\.?/, "").split(".");
1102
+ return parts.filter((p) => p !== "items");
1103
+ }
1104
+ function insertOperation(root, op, manifest) {
1105
+ const segments = relationSegments(op.resultPath);
1106
+ let node = root;
1107
+ let parent = root;
1108
+ let entity = root.entity;
1109
+ for (const prop of segments) {
1110
+ parent = node;
1111
+ const relation = manifest.entities[entity]?.relations[prop];
1112
+ const target = relation?.target ?? entity;
1113
+ let child = node.children.get(prop);
1114
+ if (!child) {
1115
+ child = {
1116
+ entity: target,
1117
+ projection: [],
1118
+ children: /* @__PURE__ */ new Map(),
1119
+ implicitFields: /* @__PURE__ */ new Set()
1120
+ };
1121
+ node.children.set(prop, child);
1122
+ }
1123
+ node = child;
1124
+ entity = target;
1125
+ }
1126
+ node.projection = op.projection;
1127
+ if (op.sourceList?.implicit) {
1128
+ parent.implicitFields.add(op.sourceList.from);
1129
+ }
1130
+ }
1131
+ function emitResultClasses(node, typeName, manifest, out, seen) {
1132
+ const meta = manifest.entities[node.entity];
1133
+ const fields = [];
1134
+ const relationProps = new Set(node.children.keys());
1135
+ const scalarSet = new Set(
1136
+ node.projection.filter(
1137
+ (f) => !relationProps.has(f) && !node.implicitFields.has(f)
1138
+ )
1139
+ );
1140
+ const allKeys = [.../* @__PURE__ */ new Set([...scalarSet, ...relationProps])].sort();
1141
+ for (const key of allKeys) {
1142
+ if (relationProps.has(key)) {
1143
+ const child = node.children.get(key);
1144
+ const relation = meta?.relations[key];
1145
+ const itemTypeName = `${typeName}${toPascalCase(key)}Item`;
1146
+ emitResultClasses(child, itemTypeName, manifest, out, seen);
1147
+ if (relation?.type === "hasMany" || relation?.type === "refs") {
1148
+ const connName = `${itemTypeName}Connection`;
1149
+ if (!seen.has(connName)) {
1150
+ seen.add(connName);
1151
+ out.push({
1152
+ name: connName,
1153
+ isConnection: true,
1154
+ connectionItemType: itemTypeName,
1155
+ fields: [
1156
+ { name: "items", shape: "optional-item", itemType: itemTypeName },
1157
+ { name: "cursor", shape: "scalar", scalarType: "string" }
1158
+ ]
1159
+ });
1160
+ }
1161
+ fields.push({
1162
+ name: key,
1163
+ shape: "connection",
1164
+ itemType: itemTypeName,
1165
+ ...relation?.description !== void 0 ? { description: relation.description } : {}
1166
+ });
1167
+ } else {
1168
+ fields.push({
1169
+ name: key,
1170
+ shape: "optional-item",
1171
+ itemType: itemTypeName,
1172
+ ...relation?.description !== void 0 ? { description: relation.description } : {}
1173
+ });
1174
+ }
1175
+ continue;
1176
+ }
1177
+ const manifestField = meta?.fields[key];
1178
+ fields.push({
1179
+ name: key,
1180
+ shape: "scalar",
1181
+ scalarType: manifestField?.type ?? "string",
1182
+ ...manifestField?.description !== void 0 ? { description: manifestField.description } : {}
1183
+ });
1184
+ }
1185
+ if (!seen.has(typeName)) {
1186
+ seen.add(typeName);
1187
+ out.push({
1188
+ name: typeName,
1189
+ isConnection: false,
1190
+ fields,
1191
+ ...meta?.description !== void 0 ? { description: meta.description } : {}
1192
+ });
1193
+ }
1194
+ }
1195
+ function buildParams(params, elementTypeName) {
1196
+ const out = [];
1197
+ for (const name of Object.keys(params).sort()) {
1198
+ const p = params[name];
1199
+ const param = {
1200
+ name,
1201
+ paramType: p.type,
1202
+ required: p.required,
1203
+ ...p.literals !== void 0 ? { literals: p.literals } : {},
1204
+ ...p.type === "array" && elementTypeName !== void 0 ? { elementType: elementTypeName(name) } : {},
1205
+ ...p.description !== void 0 ? { description: p.description } : {}
1206
+ };
1207
+ out.push(param);
1208
+ }
1209
+ return out;
1210
+ }
1211
+ function emitTransactionElementTypes(transactions, out, seen) {
1212
+ for (const txName of Object.keys(transactions).sort()) {
1213
+ const spec = transactions[txName];
1214
+ for (const paramName of Object.keys(spec.params).sort()) {
1215
+ const p = spec.params[paramName];
1216
+ if (p.type !== "array" || !p.element) continue;
1217
+ const name = txElementTypeName(txName, paramName);
1218
+ if (seen.has(name)) continue;
1219
+ seen.add(name);
1220
+ const element = p.element;
1221
+ const fields = Object.keys(element).sort().map((field) => {
1222
+ const ep = element[field];
1223
+ return elementParamField(field, ep);
1224
+ });
1225
+ out.push({ name, isConnection: false, fields });
1226
+ }
1227
+ }
1228
+ }
1229
+ function elementParamField(name, p) {
1230
+ const scalarType = p.type === "number" ? "number" : "string";
1231
+ return {
1232
+ name,
1233
+ shape: "scalar",
1234
+ scalarType,
1235
+ isParamScalar: true,
1236
+ ...p.literals !== void 0 ? { literals: p.literals } : {}
1237
+ };
1238
+ }
1239
+ function buildBindingModel(manifest, operations, options) {
1240
+ const queries = operations.queries;
1241
+ const commands = operations.commands;
1242
+ const transactions = operations.transactions ?? {};
1243
+ const queryEntityMap = new Map(options.queries.map((q) => [q.name, q.entity]));
1244
+ const queryNames = [...queryEntityMap.keys()].sort();
1245
+ const commandNames = new Set(options.commands);
1246
+ const entities = Object.keys(manifest.entities).sort().map((name) => {
1247
+ const meta = manifest.entities[name];
1248
+ return {
1249
+ name,
1250
+ baseName: entityBaseName(name),
1251
+ ...meta.description !== void 0 ? { description: meta.description } : {}
1252
+ };
1253
+ });
1254
+ const resultClasses = [];
1255
+ const seen = /* @__PURE__ */ new Set();
1256
+ for (const queryName of queryNames) {
1257
+ const spec = queries[queryName];
1258
+ if (!spec) continue;
1259
+ const rootEntity = queryEntityMap.get(queryName);
1260
+ const root = {
1261
+ entity: rootEntity,
1262
+ projection: [],
1263
+ children: /* @__PURE__ */ new Map(),
1264
+ implicitFields: /* @__PURE__ */ new Set()
1265
+ };
1266
+ for (const op of spec.operations) insertOperation(root, op, manifest);
1267
+ emitResultClasses(root, queryResultTypeName(queryName), manifest, resultClasses, seen);
1268
+ }
1269
+ emitTransactionElementTypes(transactions, resultClasses, seen);
1270
+ const byEntity = /* @__PURE__ */ new Map();
1271
+ const ensure = (entity) => {
1272
+ const className = `${entityBaseName(entity)}Repository`;
1273
+ let bucket = byEntity.get(className);
1274
+ if (!bucket) {
1275
+ bucket = { entity: entityBaseName(entity), methods: [] };
1276
+ byEntity.set(className, bucket);
1277
+ }
1278
+ return bucket.methods;
1279
+ };
1280
+ for (const name of queryNames) {
1281
+ const spec = queries[name];
1282
+ if (!spec) continue;
1283
+ const entity = queryEntityMap.get(name);
1284
+ const description = queryDescription(name, entity, spec, manifest);
1285
+ ensure(entity).push({
1286
+ operationId: name,
1287
+ kind: "query",
1288
+ params: buildParams(spec.params),
1289
+ resultType: queryResultTypeName(name),
1290
+ ...description !== void 0 ? { description } : {}
1291
+ });
1292
+ }
1293
+ for (const name of [...commandNames].sort()) {
1294
+ const spec = commands[name];
1295
+ if (!spec) continue;
1296
+ ensure(spec.entity).push({
1297
+ operationId: name,
1298
+ kind: "command",
1299
+ params: buildParams(spec.params),
1300
+ ...spec.description !== void 0 ? { description: spec.description } : {}
1301
+ });
1302
+ }
1303
+ const repositories = [...byEntity.keys()].sort().map((className) => {
1304
+ const bucket = byEntity.get(className);
1305
+ return {
1306
+ name: className,
1307
+ entity: bucket.entity,
1308
+ methods: bucket.methods.slice().sort((a, b) => a.operationId.localeCompare(b.operationId))
1309
+ };
1310
+ });
1311
+ const txMethods = Object.keys(transactions).sort().map((txName) => ({
1312
+ transactionId: txName,
1313
+ params: buildParams(
1314
+ transactions[txName].params,
1315
+ (p) => txElementTypeName(txName, p)
1316
+ )
1317
+ }));
1318
+ return {
1319
+ entities,
1320
+ resultClasses,
1321
+ repositories,
1322
+ transactions: txMethods
1323
+ };
1324
+ }
1325
+ function queryDescription(name, entity, spec, manifest) {
1326
+ if (spec.description !== void 0) return spec.description;
1327
+ const indexName = spec.operations[0]?.indexName;
1328
+ if (indexName === void 0) return void 0;
1329
+ const gsi = manifest.entities[entity]?.gsis.find((g) => g.indexName === indexName);
1330
+ return gsi?.description;
1331
+ }
1332
+
1333
+ // src/codegen/binding-engine.ts
1334
+ import { existsSync, readFileSync } from "fs";
1335
+ import { resolve } from "path";
1336
+ import Handlebars from "handlebars";
1337
+ function registerBuiltinBindingHelpers(hb) {
1338
+ hb.registerHelper("eq", (a, b) => a === b);
1339
+ hb.registerHelper(
1340
+ "join",
1341
+ (list, sep) => Array.isArray(list) ? list.join(typeof sep === "string" ? sep : ", ") : ""
1342
+ );
1343
+ hb.registerHelper("pascal", (value) => pascalCase(String(value ?? "")));
1344
+ hb.registerHelper("snake", (value) => snakeCase(String(value ?? "")));
1345
+ hb.registerHelper("camel", (value) => {
1346
+ const p = pascalCase(String(value ?? ""));
1347
+ return p.charAt(0).toLowerCase() + p.slice(1);
1348
+ });
1349
+ hb.registerHelper("indent", (text, n) => {
1350
+ const pad = " ".repeat(typeof n === "number" ? n : 4);
1351
+ const s = String(text ?? "");
1352
+ return new hb.SafeString(
1353
+ s.split("\n").map((line) => line.length > 0 ? pad + line : line).join("\n")
1354
+ );
1355
+ });
1356
+ }
1357
+ function pascalCase(s) {
1358
+ return s.replace(/[_-]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/\s+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
1359
+ }
1360
+ function snakeCase(s) {
1361
+ return s.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[-\s.]+/g, "_").toLowerCase();
1362
+ }
1363
+ function partialSource(name, bundled, templateDir) {
1364
+ if (templateDir) {
1365
+ const candidate = resolve(templateDir, `${name}.hbs`);
1366
+ if (existsSync(candidate)) return readFileSync(candidate, "utf8");
1367
+ }
1368
+ const source = bundled[name];
1369
+ if (source === void 0) {
1370
+ throw new Error(`No bundled partial named "${name}" and no override found.`);
1371
+ }
1372
+ return source;
1373
+ }
1374
+ function buildBindingEnvironment(bundled, options = {}) {
1375
+ const hb = Handlebars.create();
1376
+ registerBuiltinBindingHelpers(hb);
1377
+ if (options.helpers) {
1378
+ for (const [name, fn] of Object.entries(options.helpers)) {
1379
+ hb.registerHelper(name, fn);
640
1380
  }
641
- await handlers2.generateDocs(opts, globalOpts);
642
- });
643
- const __cmd_transform = program.command("transform");
644
- __cmd_transform.command("prepared").description("Lint and hoist-normalize `DDBModel.prepare` call sites (compile-time prepared statements, issue #206).").option("-s, --src <path>", "A TypeScript source file, or a directory scanned recursively for .ts/.tsx/.mts/.cts files (node_modules, dist, hidden directories, and .d.ts files are skipped).").option("-w, --write", "Rewrite verified inline call sites in place (hoist normalization). Without it the command only checks and reports.").option("-a, --aot <artifact>", "Static prepared-plan artifact module path (issue #208). Enables build-time (AOT) compilation of every verified prepare body: with --write the artifact module is (re)generated and call sites are rewritten to load their static plan; without --write the pass recompiles and compares (drift check \u2014 a missing or differing artifact exits 1).").action(async (opts, cmd) => {
645
- const globalOpts = cmd.optsWithGlobals();
646
- if (globalOpts.introspect) {
647
- const policy = deriveCommandPolicy("transform.prepared", opts);
648
- console.log(JSON.stringify(policy, null, 2));
649
- return;
1381
+ }
1382
+ for (const name of Object.keys(bundled)) {
1383
+ hb.registerPartial(
1384
+ name,
1385
+ hb.compile(partialSource(name, bundled, options.templateDir), {
1386
+ noEscape: true
1387
+ })
1388
+ );
1389
+ }
1390
+ return hb;
1391
+ }
1392
+ function renderBinding(entry, context, bundled, options = {}) {
1393
+ const hb = buildBindingEnvironment(bundled, options);
1394
+ const template = hb.partials[entry];
1395
+ if (template === void 0) {
1396
+ throw new Error(`No entry partial named "${entry}".`);
1397
+ }
1398
+ const compiled = typeof template === "function" ? template : hb.compile(template, { noEscape: true });
1399
+ return compiled(context);
1400
+ }
1401
+
1402
+ // src/codegen/binding-view.ts
1403
+ function resolveField(field, mapper) {
1404
+ let type;
1405
+ let optional = false;
1406
+ switch (field.shape) {
1407
+ case "connection":
1408
+ type = mapper.connection(field.itemType);
1409
+ break;
1410
+ case "optional-item":
1411
+ type = mapper.optional(field.itemType);
1412
+ optional = true;
1413
+ break;
1414
+ case "scalar":
1415
+ default:
1416
+ type = field.isParamScalar ? mapper.paramScalar(
1417
+ field.scalarType === "number" ? "number" : field.literals ? "literal" : "string",
1418
+ field.literals
1419
+ ) : mapper.scalar(field.scalarType ?? "string");
1420
+ break;
1421
+ }
1422
+ return {
1423
+ name: field.name,
1424
+ type,
1425
+ optional,
1426
+ ...field.description !== void 0 ? { description: field.description } : {}
1427
+ };
1428
+ }
1429
+ function resolveClass(cls, mapper) {
1430
+ if (cls.isConnection && cls.connectionItemType) {
1431
+ const items = {
1432
+ name: "items",
1433
+ type: mapper.list(cls.connectionItemType),
1434
+ optional: false
1435
+ };
1436
+ const cursor = {
1437
+ name: "cursor",
1438
+ type: mapper.optional(mapper.scalar("string")),
1439
+ optional: true
1440
+ };
1441
+ return {
1442
+ name: cls.name,
1443
+ isConnection: true,
1444
+ fields: [items, cursor],
1445
+ ...cls.description !== void 0 ? { description: cls.description } : {}
1446
+ };
1447
+ }
1448
+ return {
1449
+ name: cls.name,
1450
+ isConnection: false,
1451
+ fields: cls.fields.map((f) => resolveField(f, mapper)),
1452
+ ...cls.description !== void 0 ? { description: cls.description } : {}
1453
+ };
1454
+ }
1455
+ function resolveParam(param, mapper) {
1456
+ let type;
1457
+ if (param.paramType === "array" && param.elementType) {
1458
+ type = mapper.list(param.elementType);
1459
+ } else {
1460
+ type = mapper.paramScalar(param.paramType, param.literals);
1461
+ }
1462
+ return {
1463
+ name: param.name,
1464
+ type,
1465
+ required: param.required,
1466
+ ...param.description !== void 0 ? { description: param.description } : {}
1467
+ };
1468
+ }
1469
+ function resolveMethod(method, mapper) {
1470
+ return {
1471
+ operationId: method.operationId,
1472
+ kind: method.kind,
1473
+ params: method.params.map((p) => resolveParam(p, mapper)),
1474
+ ...method.kind === "query" && method.resultType !== void 0 ? { returnType: mapper.optional(method.resultType) } : {},
1475
+ ...method.description !== void 0 ? { description: method.description } : {}
1476
+ };
1477
+ }
1478
+ function resolveBindingView(model, mapper) {
1479
+ return {
1480
+ language: mapper.language,
1481
+ entities: model.entities,
1482
+ resultClasses: model.resultClasses.map((c) => resolveClass(c, mapper)),
1483
+ repositories: model.repositories.map((repo) => ({
1484
+ name: repo.name,
1485
+ entity: repo.entity,
1486
+ methods: repo.methods.map((m) => resolveMethod(m, mapper))
1487
+ })),
1488
+ transactions: model.transactions.map((tx) => ({
1489
+ transactionId: tx.transactionId,
1490
+ params: tx.params.map((p) => resolveParam(p, mapper))
1491
+ }))
1492
+ };
1493
+ }
1494
+
1495
+ // src/codegen/python-mapper.ts
1496
+ var PythonTypeMapper = {
1497
+ language: "python",
1498
+ scalar(type) {
1499
+ switch (type) {
1500
+ case "number":
1501
+ case "numberSet":
1502
+ return "float";
1503
+ case "boolean":
1504
+ return "bool";
1505
+ case "binary":
1506
+ return "bytes";
1507
+ case "list":
1508
+ return "list";
1509
+ case "map":
1510
+ return "dict";
1511
+ case "stringSet":
1512
+ case "string":
1513
+ default:
1514
+ return "str";
650
1515
  }
651
- await handlers2.transformPrepared(opts, globalOpts);
652
- });
653
- program.command("extract").description("Extract contract specification for this CLI tool.").argument("[commands...]", "Command IDs to extract. Use dot notation.").option("-a, --all", "Extract all commands.", false).option("--include-meta", "Include extraction metadata.", true).option("-F, --format <format>", "Output format (yaml or json).", "yaml").action(async (commands, opts, cmd) => {
654
- if (commands.length === 0 && !opts.all) {
655
- process.stderr.write(JSON.stringify({ code: "INVALID_ARGS", message: "Specify command IDs or use --all" }) + "\n");
656
- process.exit(2);
1516
+ },
1517
+ paramScalar(kind, literals) {
1518
+ if (kind === "literal" && literals && literals.length > 0) {
1519
+ const lits = literals.map((l) => typeof l === "string" ? JSON.stringify(l) : String(l)).join(", ");
1520
+ return `Literal[${lits}]`;
657
1521
  }
658
- const format = opts.format || "yaml";
659
- const doc = JSON.parse(CONTRACT_JSON_STR);
660
- const cmdIds = opts.all ? [] : commands;
661
- if (cmdIds.length === 0) {
662
- if (format === "json") {
663
- const out = {};
664
- if (opts.includeMeta) {
665
- out._meta = {
666
- source: "embedded",
667
- type: "cli-contracts/extract",
668
- extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
669
- specVersion: doc.cli_contracts ?? "0.1.0",
670
- commands: ["graphddb.generate.python", "graphddb.generate.cloudformation", "graphddb.generate.docs", "graphddb.transform.prepared"]
671
- };
672
- }
673
- Object.assign(out, doc);
674
- process.stdout.write(JSON.stringify(out, null, 2) + "\n");
675
- } else {
676
- const yamlLines = [];
677
- yamlLines.push("# graphddb extract");
678
- yamlLines.push("# source: embedded");
679
- yamlLines.push("# type: cli-contracts/command-extract");
680
- if (opts.includeMeta) {
681
- yamlLines.push("---");
682
- yamlLines.push("source: embedded");
683
- yamlLines.push("type: cli-contracts/command-extract");
684
- yamlLines.push("extractedAt: " + (/* @__PURE__ */ new Date()).toISOString());
685
- yamlLines.push("spec_version: " + (doc.cli_contracts ?? "0.1.0"));
686
- yamlLines.push("commands:");
687
- for (const id of ["graphddb.generate.python", "graphddb.generate.cloudformation", "graphddb.generate.docs", "graphddb.transform.prepared"]) {
688
- yamlLines.push(" - " + id);
689
- }
690
- }
691
- yamlLines.push("---");
692
- yamlLines.push(CONTRACT_YAML);
693
- process.stdout.write(yamlLines.join("\n") + "\n");
694
- }
695
- } else {
696
- const filtered = {
697
- cli_contracts: doc.cli_contracts,
698
- info: doc.info,
699
- command_sets: {}
700
- };
701
- const fcs = filtered.command_sets;
702
- for (const [setId, cs] of Object.entries(doc.command_sets ?? {})) {
703
- const cmds = cs.commands;
704
- if (!cmds) continue;
705
- const matched = {};
706
- for (const [cmdId, cmdDef] of Object.entries(cmds)) {
707
- const fullId = setId + "." + cmdId;
708
- if (cmdIds.some((id) => id === cmdId || id === fullId || cmdId.startsWith(id + "."))) {
709
- matched[cmdId] = cmdDef;
710
- }
711
- }
712
- if (Object.keys(matched).length > 0) {
713
- const setCopy = { ...cs };
714
- setCopy.commands = matched;
715
- fcs[setId] = setCopy;
716
- }
1522
+ return kind === "number" ? "float" : "str";
1523
+ },
1524
+ connection(itemType) {
1525
+ return `${itemType}Connection`;
1526
+ },
1527
+ optional(inner) {
1528
+ return `${inner} | None`;
1529
+ },
1530
+ list(itemType) {
1531
+ return `list[${itemType}]`;
1532
+ }
1533
+ };
1534
+
1535
+ // src/codegen/python-templates.ts
1536
+ var TYPE_CLASS = `{{#if isDataclass}}@dataclass
1537
+ class {{name}}:{{else}}class {{name}}(TypedDict):{{/if}}{{#if docstring}}
1538
+ """{{pyDoc docstring}}"""{{/if}}{{#unless fields.length}}{{#unless docstring}}
1539
+ pass{{/unless}}{{/unless}}{{#each fields}}
1540
+ {{name}}: {{type}}{{default}}{{#if comment}} # {{pyComment comment}}{{/if}}{{/each}}`;
1541
+ var TYPES = `{{header}}
1542
+
1543
+ from __future__ import annotations
1544
+
1545
+ {{#if isDataclass}}from dataclasses import dataclass
1546
+ {{#if usesLiteral}}from typing import Literal
1547
+ {{/if}}{{else}}{{#if usesLiteral}}from typing import Literal, TypedDict
1548
+ {{else}}from typing import TypedDict
1549
+ {{/if}}{{/if}}
1550
+
1551
+ {{body}}
1552
+ `;
1553
+ var REPO_METHOD = ` def {{methodName}}(self{{#if signature}}, {{signature}}{{/if}}) -> {{returnType}}:{{#if description}}
1554
+ """{{pyDoc description}}"""{{/if}}
1555
+ {{#if isQuery}} return self._runtime.execute_query(
1556
+ query_id="{{operationId}}",
1557
+ params={{paramsDict}},
1558
+ ){{else}} self._runtime.execute_command(
1559
+ command_id="{{operationId}}",
1560
+ params={{paramsDict}},
1561
+ ){{/if}}`;
1562
+ var REPO_CLASS = `class {{name}}:
1563
+ def __init__(self, runtime: GraphDDBRuntime) -> None:
1564
+ self._runtime = runtime
1565
+
1566
+ {{#if methods.length}}{{#each methods}}{{> repo-method this}}{{#unless @last}}
1567
+
1568
+ {{/unless}}{{/each}}{{else}} pass{{/if}}`;
1569
+ var TX_METHOD = ` def {{methodName}}(self{{#if signature}}, {{signature}}{{/if}}) -> None:
1570
+ self._runtime.execute_transaction(
1571
+ transaction_id="{{transactionId}}",
1572
+ params={{paramsDict}},
1573
+ )`;
1574
+ var TX_CLASS = `class TransactionsRepository:
1575
+ def __init__(self, runtime: GraphDDBRuntime) -> None:
1576
+ self._runtime = runtime
1577
+
1578
+ {{#if methods.length}}{{#each methods}}{{> tx-method this}}{{#unless @last}}
1579
+
1580
+ {{/unless}}{{/each}}{{else}} pass{{/if}}`;
1581
+ var REPOSITORIES = `{{header}}
1582
+
1583
+ from __future__ import annotations
1584
+
1585
+ {{#if usesLiteral}}from typing import Literal
1586
+
1587
+ {{/if}}from graphddb_runtime import GraphDDBRuntime
1588
+ {{importBlock}}
1589
+ {{body}}
1590
+ `;
1591
+ var INIT = `{{header}}
1592
+
1593
+ from __future__ import annotations
1594
+ {{importBlock}}
1595
+ __all__ = [
1596
+ {{#each all}} "{{this}}",
1597
+ {{/each}}]
1598
+ `;
1599
+ var BUNDLED_PYTHON_PARTIALS = {
1600
+ types: TYPES,
1601
+ "type-class": TYPE_CLASS,
1602
+ repositories: REPOSITORIES,
1603
+ "repo-class": REPO_CLASS,
1604
+ "repo-method": REPO_METHOD,
1605
+ "tx-class": TX_CLASS,
1606
+ "tx-method": TX_METHOD,
1607
+ init: INIT
1608
+ };
1609
+
1610
+ // src/codegen/python-binding.ts
1611
+ var PY_HEADER = "# DO NOT EDIT. Generated by GraphDDB.";
1612
+ function toSnakeCase(name) {
1613
+ return name.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[-\s.]+/g, "_").toLowerCase();
1614
+ }
1615
+ function pyDocLine(text) {
1616
+ return text.replace(/\\/g, "\\\\").replace(/"""/g, '\\"\\"\\"').replace(/\r?\n/g, " ").trim();
1617
+ }
1618
+ function pyCommentLine(text) {
1619
+ return text.replace(/\r?\n/g, " ").trim();
1620
+ }
1621
+ var PY_HELPERS = {
1622
+ pyDoc: (text) => pyDocLine(String(text ?? "")),
1623
+ pyComment: (text) => pyCommentLine(String(text ?? ""))
1624
+ };
1625
+ function signatureOf(params) {
1626
+ return params.map((p) => `${toSnakeCase(p.name)}: ${p.type}`).join(", ");
1627
+ }
1628
+ function paramsDictOf(params) {
1629
+ const entries = params.map((p) => `"${p.name}": ${toSnakeCase(p.name)}`).join(", ");
1630
+ return entries ? `{${entries}}` : "{}";
1631
+ }
1632
+ function classContext(cls, isDataclass) {
1633
+ const fields = cls.fields.map((f) => ({
1634
+ name: f.name,
1635
+ type: f.type,
1636
+ default: isDataclass && f.optional ? " = None" : "",
1637
+ ...f.description !== void 0 ? { comment: f.description } : {}
1638
+ }));
1639
+ const ordered = isDataclass ? [...fields.filter((f) => f.default === ""), ...fields.filter((f) => f.default !== "")] : fields;
1640
+ return {
1641
+ name: cls.name,
1642
+ isDataclass,
1643
+ ...cls.description !== void 0 ? { docstring: cls.description } : {},
1644
+ fields: ordered
1645
+ };
1646
+ }
1647
+ function methodContext(m) {
1648
+ return {
1649
+ methodName: toSnakeCase(m.operationId),
1650
+ signature: signatureOf(m.params),
1651
+ returnType: m.returnType ?? "None",
1652
+ isQuery: m.kind === "query",
1653
+ operationId: m.operationId,
1654
+ paramsDict: paramsDictOf(m.params),
1655
+ ...m.description !== void 0 ? { description: m.description } : {}
1656
+ };
1657
+ }
1658
+ function txMethodContext(m) {
1659
+ return {
1660
+ methodName: toSnakeCase(m.transactionId),
1661
+ signature: signatureOf(m.params),
1662
+ transactionId: m.transactionId,
1663
+ paramsDict: paramsDictOf(m.params)
1664
+ };
1665
+ }
1666
+ function classesUseLiteral(classes) {
1667
+ return classes.some((c) => c.fields.some((f) => f.type.includes("Literal[")));
1668
+ }
1669
+ function methodsUseLiteral(view) {
1670
+ const paramLiteral = (p) => p.type.startsWith("Literal[");
1671
+ return view.repositories.some((r) => r.methods.some((m) => m.params.some(paramLiteral))) || view.transactions.some((t) => t.params.some(paramLiteral));
1672
+ }
1673
+ function repositoryImportTypes(view) {
1674
+ const names = /* @__PURE__ */ new Set();
1675
+ for (const repo of view.repositories) {
1676
+ for (const m of repo.methods) {
1677
+ if (m.kind === "query" && m.returnType) {
1678
+ names.add(m.returnType.replace(/ \| None$/, ""));
717
1679
  }
718
- if (doc.components) filtered.components = doc.components;
719
- process.stdout.write(JSON.stringify(filtered, null, 2) + "\n");
720
1680
  }
721
- process.exit(0);
722
- });
723
- return program;
1681
+ }
1682
+ for (const tx of view.transactions) {
1683
+ for (const p of tx.params) {
1684
+ const match = /^list\[(.+)\]$/.exec(p.type);
1685
+ if (match) names.add(match[1]);
1686
+ }
1687
+ }
1688
+ return [...names].sort();
1689
+ }
1690
+ function engineOptions(options) {
1691
+ return {
1692
+ ...options.templateDir !== void 0 ? { templateDir: options.templateDir } : {},
1693
+ helpers: { ...PY_HELPERS, ...options.helpers ?? {} }
1694
+ };
1695
+ }
1696
+ function render(entry, context, options) {
1697
+ return renderBinding(entry, context, BUNDLED_PYTHON_PARTIALS, options);
1698
+ }
1699
+ function renderTypesFile(view, options) {
1700
+ const isDataclass = Boolean(options.dataclass);
1701
+ const engine = engineOptions(options);
1702
+ const blocks = view.resultClasses.map(
1703
+ (c) => render("type-class", classContext(c, isDataclass), engine)
1704
+ );
1705
+ const body = blocks.length === 0 ? "# (no query result types)" : blocks.join("\n\n\n");
1706
+ const context = {
1707
+ header: PY_HEADER,
1708
+ isDataclass,
1709
+ usesLiteral: classesUseLiteral(view.resultClasses),
1710
+ body
1711
+ };
1712
+ return render("types", context, engine);
1713
+ }
1714
+ function renderRepositoriesFile(view, options) {
1715
+ const engine = engineOptions(options);
1716
+ const blocks = view.repositories.map(
1717
+ (repo) => render(
1718
+ "repo-class",
1719
+ { name: repo.name, methods: repo.methods.map(methodContext) },
1720
+ engine
1721
+ )
1722
+ );
1723
+ if (view.transactions.length > 0) {
1724
+ blocks.push(
1725
+ render(
1726
+ "tx-class",
1727
+ { methods: view.transactions.map(txMethodContext) },
1728
+ engine
1729
+ )
1730
+ );
1731
+ }
1732
+ const importTypes = repositoryImportTypes(view);
1733
+ const importBlock = importTypes.length === 0 ? "" : `from .types import (
1734
+ ${importTypes.map((t) => ` ${t},`).join("\n")}
1735
+ )
1736
+ `;
1737
+ const body = blocks.length === 0 ? "# (no repositories)" : blocks.join("\n\n\n");
1738
+ const context = {
1739
+ header: PY_HEADER,
1740
+ usesLiteral: methodsUseLiteral(view),
1741
+ importBlock,
1742
+ body
1743
+ };
1744
+ return render("repositories", context, engine);
1745
+ }
1746
+ function repositoryClassNamesOf(view) {
1747
+ const names = view.repositories.map((r) => r.name);
1748
+ if (view.transactions.length > 0) names.push("TransactionsRepository");
1749
+ return names;
1750
+ }
1751
+ function renderInitFile(view, options) {
1752
+ const resultTypes = view.repositories.flatMap((r) => r.methods).filter((m) => m.kind === "query" && m.returnType).sort((a, b) => a.operationId < b.operationId ? -1 : a.operationId > b.operationId ? 1 : 0).map((m) => m.returnType.replace(/ \| None$/, ""));
1753
+ const repos = repositoryClassNamesOf(view);
1754
+ const all = [...resultTypes, ...repos].sort();
1755
+ const importLines = [""];
1756
+ if (resultTypes.length > 0) {
1757
+ importLines.push("from .types import (");
1758
+ for (const t of resultTypes) importLines.push(` ${t},`);
1759
+ importLines.push(")");
1760
+ }
1761
+ if (repos.length > 0) {
1762
+ importLines.push("from .repositories import (");
1763
+ for (const r of repos) importLines.push(` ${r},`);
1764
+ importLines.push(")");
1765
+ }
1766
+ importLines.push("");
1767
+ const context = {
1768
+ header: PY_HEADER,
1769
+ all,
1770
+ importBlock: importLines.join("\n")
1771
+ };
1772
+ return render("init", context, engineOptions(options));
1773
+ }
1774
+ function bindingSurface(queries, commands) {
1775
+ return {
1776
+ queries: Object.keys(queries).map((name) => ({
1777
+ name,
1778
+ entity: queries[name].entity.name
1779
+ })),
1780
+ commands: Object.keys(commands)
1781
+ };
1782
+ }
1783
+ function renderPythonBinding(manifest, operations, surface, options = {}) {
1784
+ const model = buildBindingModel(manifest, operations, surface);
1785
+ const view = resolveBindingView(model, PythonTypeMapper);
1786
+ return {
1787
+ "types.py": renderTypesFile(view, options),
1788
+ "repositories.py": renderRepositoriesFile(view, options),
1789
+ "__init__.py": renderInitFile(view, options)
1790
+ };
1791
+ }
1792
+
1793
+ // src/codegen/php-mapper.ts
1794
+ var PHP_NUMBER = "int|float";
1795
+ var PhpTypeMapper = {
1796
+ language: "php",
1797
+ scalar(type) {
1798
+ switch (type) {
1799
+ case "number":
1800
+ case "numberSet":
1801
+ return PHP_NUMBER;
1802
+ case "boolean":
1803
+ return "bool";
1804
+ case "binary":
1805
+ return "string";
1806
+ case "list":
1807
+ case "map":
1808
+ return "array";
1809
+ case "stringSet":
1810
+ case "string":
1811
+ default:
1812
+ return "string";
1813
+ }
1814
+ },
1815
+ paramScalar(kind) {
1816
+ return kind === "number" ? PHP_NUMBER : "string";
1817
+ },
1818
+ connection(itemType) {
1819
+ return `${itemType}Connection`;
1820
+ },
1821
+ optional(inner) {
1822
+ if (inner.includes("|")) return `${inner}|null`;
1823
+ return `?${inner}`;
1824
+ },
1825
+ list(itemType) {
1826
+ void itemType;
1827
+ return "array";
1828
+ }
1829
+ };
1830
+
1831
+ // src/codegen/php-templates.ts
1832
+ var FILE = `{{header}}
1833
+
1834
+ declare(strict_types=1);
1835
+
1836
+ namespace {{namespace}};
1837
+ {{#if uses}}
1838
+
1839
+ {{#each uses}}use {{this}};
1840
+ {{/each}}{{/if}}
1841
+ {{body}}
1842
+ `;
1843
+ var DTO_CLASS = `/**
1844
+ * {{docSummary}}
1845
+ */
1846
+ final class {{name}}
1847
+ {
1848
+ public function __construct(
1849
+ {{#each fields}} public readonly {{phpType}} \${{name}},
1850
+ {{/each}} ) {
1851
+ }
1852
+
1853
+ /**
1854
+ * Hydrate this DTO from the runtime's associative-array result.
1855
+ *
1856
+ * @param array<string,mixed> $data
1857
+ */
1858
+ public static function fromArray(array $data): self
1859
+ {
1860
+ return new self(
1861
+ {{#each fields}} {{name}}: {{hydrateExpr}},
1862
+ {{/each}} );
1863
+ }
1864
+ }`;
1865
+ var CONNECTION_CLASS = `/**
1866
+ * A paged connection of {{itemType}} items with an optional forward cursor.
1867
+ */
1868
+ final class {{name}}
1869
+ {
1870
+ /**
1871
+ * @param list<{{itemType}}> $items
1872
+ */
1873
+ public function __construct(
1874
+ public readonly array $items,
1875
+ public readonly ?string $cursor,
1876
+ ) {
1877
+ }
1878
+
1879
+ /**
1880
+ * @param array<string,mixed> $data
1881
+ */
1882
+ public static function fromArray(array $data): self
1883
+ {
1884
+ return new self(
1885
+ items: array_map(
1886
+ static fn (array $item): {{itemType}} => {{itemType}}::fromArray($item),
1887
+ $data['items'] ?? [],
1888
+ ),
1889
+ cursor: $data['cursor'] ?? null,
1890
+ );
1891
+ }
1892
+ }`;
1893
+ var REPO_METHOD2 = `{{docblock}} public function {{methodName}}({{signature}}): {{returnType}}
1894
+ {
1895
+ {{#if isQuery}} $result = $this->runtime->executeQuery('{{operationId}}', {{paramsArray}});
1896
+ return {{hydrateResult}};
1897
+ {{else}} $this->runtime->executeCommand('{{operationId}}', {{paramsArray}});
1898
+ {{/if}} }`;
1899
+ var REPO_CLASS2 = `/**
1900
+ * Typed repository for the {{entity}} entity, wrapping a {@see GraphDDBRuntime}.
1901
+ */
1902
+ final class {{name}}
1903
+ {
1904
+ public function __construct(
1905
+ private readonly GraphDDBRuntime $runtime,
1906
+ ) {
1907
+ }
1908
+ {{#if body}}
1909
+
1910
+ {{body}}
1911
+ {{/if}}
1912
+ }`;
1913
+ var TX_METHOD2 = `{{docblock}} public function {{methodName}}({{signature}}): void
1914
+ {
1915
+ $this->runtime->executeTransaction('{{transactionId}}', {{paramsArray}});
1916
+ }`;
1917
+ var TX_CLASS2 = `/**
1918
+ * Typed repository for declarative transactions, wrapping a {@see GraphDDBRuntime}.
1919
+ */
1920
+ final class {{name}}
1921
+ {
1922
+ public function __construct(
1923
+ private readonly GraphDDBRuntime $runtime,
1924
+ ) {
1925
+ }
1926
+ {{#if body}}
1927
+
1928
+ {{body}}
1929
+ {{/if}}
1930
+ }`;
1931
+ var BUNDLED_PHP_PARTIALS = {
1932
+ file: FILE,
1933
+ "dto-class": DTO_CLASS,
1934
+ "connection-class": CONNECTION_CLASS,
1935
+ "repo-class": REPO_CLASS2,
1936
+ "repo-method": REPO_METHOD2,
1937
+ "tx-class": TX_CLASS2,
1938
+ "tx-method": TX_METHOD2
1939
+ };
1940
+
1941
+ // src/codegen/php-binding.ts
1942
+ var PHP_HEADER = "<?php\n\n// DO NOT EDIT. Generated by GraphDDB.";
1943
+ var PHP_NAMESPACE = "GraphDDB\\Generated";
1944
+ var RUNTIME_FQCN = "GraphDDB\\Runtime\\Runtime";
1945
+ var RUNTIME_SHORT = "GraphDDBRuntime";
1946
+ function toCamelCase(name) {
1947
+ const pascal4 = name.replace(/[_\-.]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/\s+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
1948
+ return pascal4.charAt(0).toLowerCase() + pascal4.slice(1);
1949
+ }
1950
+ function docLine(text) {
1951
+ return text.replace(/\r?\n/g, " ").trim();
1952
+ }
1953
+ function phpLiteral(value) {
1954
+ if (typeof value === "number") return String(value);
1955
+ return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
1956
+ }
1957
+ var PHP_HELPERS = {
1958
+ // `phpDoc text` — collapse a description to one docblock line.
1959
+ phpDoc: (text) => docLine(String(text ?? ""))
1960
+ };
1961
+ function paramDocType(param, model) {
1962
+ if (model.literals && model.literals.length > 0) {
1963
+ return model.literals.map(phpLiteral).join("|");
1964
+ }
1965
+ if (model.elementType) {
1966
+ return `list<${model.elementType}>`;
1967
+ }
1968
+ return param.type;
724
1969
  }
725
-
726
- // src/cli/handlers.ts
727
- import { mkdirSync, readFileSync as readFileSync2, readdirSync, statSync, writeFileSync } from "fs";
728
- import { dirname, relative } from "path";
729
- import { pathToFileURL } from "url";
730
- import { isAbsolute, resolve as resolve2 } from "path";
731
-
732
- // src/codegen/python.ts
733
- var PY_HEADER = "# DO NOT EDIT. Generated by GraphDDB.";
734
- function toPascalCase(name) {
735
- return name.replace(/[_-]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/\s+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
1970
+ function signatureOf2(params) {
1971
+ return params.map((p) => `${p.type} $${toCamelCase(p.name)}`).join(", ");
736
1972
  }
737
- function toSnakeCase(name) {
738
- return name.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[-\s.]+/g, "_").toLowerCase();
1973
+ function paramsArrayOf(params) {
1974
+ const entries = params.map((p) => `'${p.name}' => $${toCamelCase(p.name)}`).join(", ");
1975
+ return entries ? `[${entries}]` : "[]";
739
1976
  }
740
- function entityBaseName(entity) {
741
- return entity.replace(/Model$/, "");
1977
+ function docblockOf(method, metas, description) {
1978
+ const lines = [];
1979
+ if (description) lines.push(docLine(description));
1980
+ if (description && (method.params.length > 0 || method.returnType)) lines.push("");
1981
+ method.params.forEach((p, i) => {
1982
+ const meta = metas[i];
1983
+ const docType = paramDocType(p, meta);
1984
+ const desc = p.description ? ` ${docLine(p.description)}` : "";
1985
+ lines.push(`@param ${docType} $${toCamelCase(p.name)}${desc}`);
1986
+ });
1987
+ if (method.kind === "query" && method.returnType) {
1988
+ lines.push(`@return ${method.returnType}`);
1989
+ }
1990
+ return renderDocblock(lines);
742
1991
  }
743
- function pyScalarType(type) {
744
- switch (type) {
745
- case "number":
746
- case "numberSet":
747
- return "float";
748
- case "boolean":
749
- return "bool";
750
- case "binary":
751
- return "bytes";
752
- case "list":
753
- return "list";
754
- case "map":
755
- return "dict";
756
- case "stringSet":
757
- case "string":
758
- default:
759
- return "str";
1992
+ function txDocblockOf(method, metas) {
1993
+ const lines = [];
1994
+ method.params.forEach((p, i) => {
1995
+ const meta = metas[i];
1996
+ const docType = paramDocType(p, meta);
1997
+ lines.push(`@param ${docType} $${toCamelCase(p.name)}`);
1998
+ });
1999
+ return renderDocblock(lines);
2000
+ }
2001
+ function renderDocblock(lines) {
2002
+ const body = lines.length > 0 ? lines : [""];
2003
+ const out = [" /**"];
2004
+ for (const line of body) out.push(line === "" ? " *" : ` * ${line}`);
2005
+ out.push(" */");
2006
+ return out.map((l) => `${l}
2007
+ `).join("");
2008
+ }
2009
+ function hydrateFieldExpr(field) {
2010
+ const key = `$data['${field.name}']`;
2011
+ if (!field.optional && /Connection$/.test(field.type) && field.type !== "string") {
2012
+ return `${field.type}::fromArray(${key} ?? [])`;
2013
+ }
2014
+ if (field.optional && field.type.startsWith("?")) {
2015
+ const item = field.type.slice(1);
2016
+ return `isset(${key}) && ${key} !== null ? ${item}::fromArray(${key}) : null`;
760
2017
  }
2018
+ return `${key} ?? null`;
761
2019
  }
762
- function pyParamType(spec) {
763
- if (spec.type === "literal" && spec.literals && spec.literals.length > 0) {
764
- const lits = spec.literals.map((l) => typeof l === "string" ? JSON.stringify(l) : String(l)).join(", ");
765
- return `Literal[${lits}]`;
2020
+ function dtoFieldContexts(cls) {
2021
+ return cls.fields.map((f) => ({
2022
+ name: f.name,
2023
+ phpType: f.type,
2024
+ hydrateExpr: hydrateFieldExpr(f)
2025
+ }));
2026
+ }
2027
+ function dtoUses(cls, allClassNames) {
2028
+ const referenced = /* @__PURE__ */ new Set();
2029
+ const add = (typeName) => {
2030
+ if (allClassNames.has(typeName)) referenced.add(typeName);
2031
+ };
2032
+ if (cls.isConnection) {
2033
+ const item = connectionItemType(cls);
2034
+ if (item) add(item);
2035
+ } else {
2036
+ for (const f of cls.fields) {
2037
+ if (/Connection$/.test(f.type)) add(f.type);
2038
+ else if (f.type.startsWith("?")) add(f.type.slice(1));
2039
+ }
766
2040
  }
767
- return spec.type === "number" ? "float" : "str";
2041
+ return [...referenced].filter((n) => n !== cls.name).sort().map((n) => `${PHP_NAMESPACE}\\${n}`);
768
2042
  }
769
- function txElementTypeName(txName, paramName) {
770
- return `${toPascalCase(txName)}${toPascalCase(paramName)}Item`;
2043
+ function connectionItemType(cls) {
2044
+ if (!cls.isConnection) return void 0;
2045
+ return cls.name.replace(/Connection$/, "");
2046
+ }
2047
+ function hydrateResultExpr(returnType) {
2048
+ const result = returnType.startsWith("?") ? returnType.slice(1) : returnType;
2049
+ return `$result === null ? null : ${result}::fromArray($result)`;
2050
+ }
2051
+ function methodContext2(method, metas, description) {
2052
+ return {
2053
+ methodName: toCamelCase(method.operationId),
2054
+ signature: signatureOf2(method.params),
2055
+ returnType: method.returnType ?? "void",
2056
+ isQuery: method.kind === "query",
2057
+ operationId: method.operationId,
2058
+ paramsArray: paramsArrayOf(method.params),
2059
+ docblock: docblockOf(method, metas, description),
2060
+ hydrateResult: method.returnType ? hydrateResultExpr(method.returnType) : ""
2061
+ };
2062
+ }
2063
+ function txMethodContext2(method, metas) {
2064
+ return {
2065
+ methodName: toCamelCase(method.transactionId),
2066
+ signature: signatureOf2(method.params),
2067
+ transactionId: method.transactionId,
2068
+ paramsArray: paramsArrayOf(method.params),
2069
+ docblock: txDocblockOf(method, metas)
2070
+ };
2071
+ }
2072
+ function engineOptions2(options) {
2073
+ return {
2074
+ ...options.templateDir !== void 0 ? { templateDir: options.templateDir } : {},
2075
+ helpers: { ...PHP_HELPERS, ...options.helpers ?? {} }
2076
+ };
2077
+ }
2078
+ function render2(entry, context, options) {
2079
+ return renderBinding(entry, context, BUNDLED_PHP_PARTIALS, options);
2080
+ }
2081
+ function renderFile(body, uses, options) {
2082
+ return render2(
2083
+ "file",
2084
+ {
2085
+ header: PHP_HEADER,
2086
+ namespace: PHP_NAMESPACE,
2087
+ uses,
2088
+ body
2089
+ },
2090
+ options
2091
+ );
771
2092
  }
772
- function pyTransactionParamType(txName, paramName, spec) {
773
- if (spec.type === "array") {
774
- return `list[${txElementTypeName(txName, paramName)}]`;
2093
+ function buildParamMetaIndex(operations, surface) {
2094
+ const index = /* @__PURE__ */ new Map();
2095
+ const record = (opId, params) => {
2096
+ const inner = /* @__PURE__ */ new Map();
2097
+ for (const [name, p] of Object.entries(params)) {
2098
+ inner.set(name, {
2099
+ ...p.literals !== void 0 ? { literals: p.literals } : {},
2100
+ ...p.type === "array" ? { elementType: `${pascal(opId)}${pascal(name)}Item` } : {}
2101
+ });
2102
+ }
2103
+ index.set(opId, inner);
2104
+ };
2105
+ const querySet = new Set(surface.queries.map((q) => q.name));
2106
+ const commandSet = new Set(surface.commands);
2107
+ for (const [name, spec] of Object.entries(operations.queries)) {
2108
+ if (querySet.has(name)) record(name, spec.params ?? {});
2109
+ }
2110
+ for (const [name, spec] of Object.entries(operations.commands)) {
2111
+ if (commandSet.has(name)) record(name, spec.params ?? {});
775
2112
  }
776
- return pyParamType(spec);
2113
+ for (const [name, spec] of Object.entries(operations.transactions ?? {})) {
2114
+ record(name, spec.params ?? {});
2115
+ }
2116
+ return index;
777
2117
  }
778
- function pyDocLine(text) {
779
- return text.replace(/\\/g, "\\\\").replace(/"""/g, '\\"\\"\\"').replace(/\r?\n/g, " ").trim();
2118
+ function pascal(name) {
2119
+ return name.replace(/[_-]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/\s+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
780
2120
  }
781
- function pyCommentLine(text) {
782
- return text.replace(/\r?\n/g, " ").trim();
2121
+ function metasFor(index, opId, params) {
2122
+ const inner = index.get(opId) ?? /* @__PURE__ */ new Map();
2123
+ return params.map((p) => inner.get(p.name) ?? {});
783
2124
  }
784
- function collectResultTypes(manifest, entity, select, typeName, out, seen) {
785
- const meta = manifest.entities[entity];
786
- const fields = [];
787
- const fieldDescriptions = {};
788
- for (const field of Object.keys(select).sort()) {
789
- const value = select[field];
790
- const relation = meta?.relations[field];
791
- if (relation) {
792
- const spec = normalizeSelectSpec(value);
793
- const childSelect = spec.select ?? {};
794
- const itemTypeName = `${typeName}${toPascalCase(field)}Item`;
795
- collectResultTypes(
796
- manifest,
797
- relation.target,
798
- childSelect,
799
- itemTypeName,
800
- out,
801
- seen
2125
+ function methodDescription(method) {
2126
+ return method.description;
2127
+ }
2128
+ function renderPhpFiles(view, index, options) {
2129
+ const engine = engineOptions2(options);
2130
+ const files = {};
2131
+ const allClassNames = new Set(view.resultClasses.map((c) => c.name));
2132
+ for (const cls of view.resultClasses) {
2133
+ const uses = dtoUses(cls, allClassNames);
2134
+ let body;
2135
+ if (cls.isConnection) {
2136
+ body = render2(
2137
+ "connection-class",
2138
+ { name: cls.name, itemType: connectionItemType(cls) },
2139
+ engine
802
2140
  );
803
- if (relation.type === "hasMany" || relation.type === "refs") {
804
- const connName = `${itemTypeName}Connection`;
805
- if (!seen.has(connName)) {
806
- seen.add(connName);
807
- out.push({
808
- name: connName,
809
- fields: [
810
- ["items", `list[${itemTypeName}]`],
811
- ["cursor", "str | None"]
812
- ]
813
- });
814
- }
815
- fields.push([field, connName]);
816
- } else {
817
- fields.push([field, `${itemTypeName} | None`]);
818
- }
819
- if (relation.description !== void 0) {
820
- fieldDescriptions[field] = relation.description;
2141
+ } else {
2142
+ body = render2(
2143
+ "dto-class",
2144
+ {
2145
+ name: cls.name,
2146
+ docSummary: cls.description ? docLine(cls.description) : cls.name,
2147
+ fields: dtoFieldContexts(cls)
2148
+ },
2149
+ engine
2150
+ );
2151
+ }
2152
+ files[`${cls.name}.php`] = renderFile(body, uses, engine);
2153
+ }
2154
+ for (const repo of view.repositories) {
2155
+ const methodBlocks = repo.methods.map(
2156
+ (m) => render2(
2157
+ "repo-method",
2158
+ methodContext2(m, metasFor(index, m.operationId, m.params), methodDescription(m)),
2159
+ engine
2160
+ )
2161
+ );
2162
+ const body = render2(
2163
+ "repo-class",
2164
+ { name: repo.name, entity: repo.entity, body: methodBlocks.join("\n\n") },
2165
+ engine
2166
+ );
2167
+ const resultTypes = /* @__PURE__ */ new Set();
2168
+ for (const m of repo.methods) {
2169
+ if (m.kind === "query" && m.returnType) {
2170
+ resultTypes.add(m.returnType.startsWith("?") ? m.returnType.slice(1) : m.returnType);
821
2171
  }
822
- continue;
823
2172
  }
824
- if (value === true) {
825
- const manifestField = meta?.fields[field];
826
- const fieldType = manifestField?.type ?? "string";
827
- fields.push([field, pyScalarType(fieldType)]);
828
- if (manifestField?.description !== void 0) {
829
- fieldDescriptions[field] = manifestField.description;
2173
+ const uses = [
2174
+ `${RUNTIME_FQCN} as ${RUNTIME_SHORT}`,
2175
+ ...[...resultTypes].sort().map((t) => `${PHP_NAMESPACE}\\${t}`)
2176
+ ];
2177
+ files[`${repo.name}.php`] = renderFile(body, uses, engine);
2178
+ }
2179
+ if (view.transactions.length > 0) {
2180
+ const methodBlocks = view.transactions.map(
2181
+ (t) => render2("tx-method", txMethodContext2(t, metasFor(index, t.transactionId, t.params)), engine)
2182
+ );
2183
+ const body = render2(
2184
+ "tx-class",
2185
+ { name: "TransactionsRepository", body: methodBlocks.join("\n\n") },
2186
+ engine
2187
+ );
2188
+ const elementTypes = /* @__PURE__ */ new Set();
2189
+ for (const t of view.transactions) {
2190
+ for (const p of t.params) {
2191
+ const meta = index.get(t.transactionId)?.get(p.name);
2192
+ if (meta?.elementType) elementTypes.add(meta.elementType);
830
2193
  }
831
2194
  }
2195
+ const uses = [
2196
+ `${RUNTIME_FQCN} as ${RUNTIME_SHORT}`,
2197
+ ...[...elementTypes].sort().map((t) => `${PHP_NAMESPACE}\\${t}`)
2198
+ ];
2199
+ files["TransactionsRepository.php"] = renderFile(body, uses, engine);
832
2200
  }
833
- if (!seen.has(typeName)) {
834
- seen.add(typeName);
835
- out.push({
836
- name: typeName,
837
- fields,
838
- ...Object.keys(fieldDescriptions).length > 0 ? { fieldDescriptions } : {},
839
- // The entity's own description (issue #154) becomes the result class docstring.
840
- ...meta?.description !== void 0 ? { docstring: meta.description } : {}
841
- });
2201
+ return files;
2202
+ }
2203
+ function renderPhpBinding(manifest, operations, surface, options = {}) {
2204
+ const model = buildBindingModel(manifest, operations, surface);
2205
+ const view = resolveBindingView(model, PhpTypeMapper);
2206
+ const index = buildParamMetaIndex(operations, surface);
2207
+ return renderPhpFiles(view, index, options);
2208
+ }
2209
+
2210
+ // src/codegen/rust-mapper.ts
2211
+ var RUST_NUMBER = "serde_json::Number";
2212
+ var RustTypeMapper = {
2213
+ language: "rust",
2214
+ scalar(type) {
2215
+ switch (type) {
2216
+ case "number":
2217
+ case "numberSet":
2218
+ return RUST_NUMBER;
2219
+ case "boolean":
2220
+ return "bool";
2221
+ case "binary":
2222
+ return "String";
2223
+ case "list":
2224
+ case "map":
2225
+ return "serde_json::Value";
2226
+ case "stringSet":
2227
+ case "string":
2228
+ default:
2229
+ return "String";
2230
+ }
2231
+ },
2232
+ paramScalar(kind) {
2233
+ return kind === "number" ? RUST_NUMBER : "String";
2234
+ },
2235
+ connection(itemType) {
2236
+ return `${itemType}Connection`;
2237
+ },
2238
+ optional(inner) {
2239
+ return `Option<${inner}>`;
2240
+ },
2241
+ list(itemType) {
2242
+ return `Vec<${itemType}>`;
842
2243
  }
2244
+ };
2245
+
2246
+ // src/codegen/rust-templates.ts
2247
+ var FILE2 = `{{header}}
2248
+
2249
+ {{uses}}
2250
+
2251
+ {{body}}
2252
+ `;
2253
+ var RESULT_STRUCT = `{{docComment}}#[derive(Debug, Clone, Deserialize, Serialize)]
2254
+ pub struct {{name}} {
2255
+ {{#each fields}}{{docComment}}{{attr}} pub {{name}}: {{rustType}},
2256
+ {{/each~}}
2257
+ }`;
2258
+ var CONNECTION_STRUCT = `/// A paged connection of {{itemType}} items with an optional forward cursor.
2259
+ #[derive(Debug, Clone, Default, Deserialize, Serialize)]
2260
+ pub struct {{name}} {
2261
+ /// The page of items.
2262
+ #[serde(default)]
2263
+ pub items: Vec<{{itemType}}>,
2264
+ /// The forward cursor for the next page, when more items remain.
2265
+ #[serde(default)]
2266
+ pub cursor: Option<String>,
2267
+ }`;
2268
+ var REPO_METHOD3 = `{{docComment}} pub async fn {{methodName}}(&self{{#if signature}}, {{signature}}{{/if}}) -> {{returnType}} {
2269
+ {{paramsStmts}}{{#if isQuery}} let value = self.runtime.execute_query("{{operationId}}", &params).await?;
2270
+ if value.is_null() {
2271
+ return Ok(None);
2272
+ }
2273
+ let typed = serde_json::from_value(value)
2274
+ .map_err(|e| GraphDDBError::hydration(format!("cannot deserialize {{operationId}} result: {e}")))?;
2275
+ Ok(Some(typed))
2276
+ {{else}} self.runtime.execute_command("{{operationId}}", &params).await?;
2277
+ Ok(())
2278
+ {{/if}} }`;
2279
+ var REPO_STRUCT = `/// Typed repository for the {{entity}} entity, wrapping a \`GraphDDBRuntime\`.
2280
+ #[derive(Clone)]
2281
+ pub struct {{name}} {
2282
+ runtime: Arc<GraphDDBRuntime>,
843
2283
  }
844
- function selectOf(def) {
845
- return def.select ?? {};
2284
+
2285
+ impl {{name}} {
2286
+ /// Construct the repository over a shared runtime.
2287
+ pub fn new(runtime: Arc<GraphDDBRuntime>) -> Self {
2288
+ Self { runtime }
2289
+ }
2290
+ {{#if body}}
2291
+
2292
+ {{body}}
2293
+ {{/if~}}
2294
+ }`;
2295
+ var TX_METHOD3 = `{{docComment}} pub async fn {{methodName}}(&self{{#if signature}}, {{signature}}{{/if}}) -> Result<(), GraphDDBError> {
2296
+ {{paramsStmts}} self.runtime.execute_transaction("{{transactionId}}", &params).await?;
2297
+ Ok(())
2298
+ }`;
2299
+ var TX_STRUCT = `/// Typed repository for declarative transactions, wrapping a \`GraphDDBRuntime\`.
2300
+ #[derive(Clone)]
2301
+ pub struct {{name}} {
2302
+ runtime: Arc<GraphDDBRuntime>,
846
2303
  }
847
- function queryResultTypeName(queryName) {
848
- return `${toPascalCase(queryName)}Result`;
2304
+
2305
+ impl {{name}} {
2306
+ /// Construct the repository over a shared runtime.
2307
+ pub fn new(runtime: Arc<GraphDDBRuntime>) -> Self {
2308
+ Self { runtime }
2309
+ }
2310
+ {{#if body}}
2311
+
2312
+ {{body}}
2313
+ {{/if~}}
2314
+ }`;
2315
+ var BUNDLED_RUST_PARTIALS = {
2316
+ file: FILE2,
2317
+ "result-struct": RESULT_STRUCT,
2318
+ "connection-struct": CONNECTION_STRUCT,
2319
+ "repo-struct": REPO_STRUCT,
2320
+ "repo-method": REPO_METHOD3,
2321
+ "tx-struct": TX_STRUCT,
2322
+ "tx-method": TX_METHOD3
2323
+ };
2324
+
2325
+ // src/codegen/rust-binding.ts
2326
+ var RUST_HEADER = "// DO NOT EDIT. Generated by GraphDDB.";
2327
+ var RUST_USES = [
2328
+ "use graphddb_runtime::{GraphDDBError, GraphDDBRuntime};",
2329
+ "use serde::{Deserialize, Serialize};",
2330
+ "use serde_json::json;",
2331
+ "use std::sync::Arc;"
2332
+ ];
2333
+ var RUST_FILE_NAME = "bindings.rs";
2334
+ function toSnakeCase2(name) {
2335
+ return name.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[-\s.]+/g, "_").toLowerCase();
849
2336
  }
850
- function classDocstringLines(cls) {
851
- return cls.docstring !== void 0 ? [` """${pyDocLine(cls.docstring)}"""`] : [];
2337
+ function docLine2(text) {
2338
+ return text.replace(/\r?\n/g, " ").trim();
2339
+ }
2340
+ function renderDocComment(lines, indent) {
2341
+ if (lines.length === 0) return "";
2342
+ return lines.map((l) => l === "" ? `${indent}///
2343
+ ` : `${indent}/// ${l}
2344
+ `).join("");
852
2345
  }
853
- function withFieldComment(cls, name, line) {
854
- const desc = cls.fieldDescriptions?.[name];
855
- return desc !== void 0 ? `${line} # ${pyCommentLine(desc)}` : line;
2346
+ function rustLiteral(value) {
2347
+ return typeof value === "number" ? `\`${value}\`` : `\`${value}\``;
2348
+ }
2349
+ var RUST_HELPERS = {
2350
+ // `rustDoc text` — collapse a description to one doc-comment line.
2351
+ rustDoc: (text) => docLine2(String(text ?? ""))
2352
+ };
2353
+ function pascal2(name) {
2354
+ return name.replace(/[_-]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/\s+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
856
2355
  }
857
- function renderTypedDict(cls) {
858
- const doc = classDocstringLines(cls);
859
- if (cls.fields.length === 0) {
860
- return doc.length > 0 ? `class ${cls.name}(TypedDict):
861
- ${doc.join("\n")}` : `class ${cls.name}(TypedDict):
862
- pass`;
2356
+ function buildParamMetaIndex2(operations, surface) {
2357
+ const index = /* @__PURE__ */ new Map();
2358
+ const record = (opId, params) => {
2359
+ const inner = /* @__PURE__ */ new Map();
2360
+ for (const [name, p] of Object.entries(params)) {
2361
+ inner.set(name, {
2362
+ ...p.literals !== void 0 ? { literals: p.literals } : {},
2363
+ ...p.type === "array" ? { elementType: `${pascal2(opId)}${pascal2(name)}Item` } : {}
2364
+ });
2365
+ }
2366
+ index.set(opId, inner);
2367
+ };
2368
+ const querySet = new Set(surface.queries.map((q) => q.name));
2369
+ const commandSet = new Set(surface.commands);
2370
+ for (const [name, spec] of Object.entries(operations.queries)) {
2371
+ if (querySet.has(name)) record(name, spec.params ?? {});
863
2372
  }
864
- const lines = cls.fields.map(
865
- ([name, type]) => withFieldComment(cls, name, ` ${name}: ${type}`)
866
- );
867
- return `class ${cls.name}(TypedDict):
868
- ${[...doc, ...lines].join("\n")}`;
869
- }
870
- function renderDataclass(cls) {
871
- const doc = classDocstringLines(cls);
872
- if (cls.fields.length === 0) {
873
- return doc.length > 0 ? `@dataclass
874
- class ${cls.name}:
875
- ${doc.join("\n")}` : `@dataclass
876
- class ${cls.name}:
877
- pass`;
878
- }
879
- const lines = cls.fields.map(
880
- ([name, type]) => type.endsWith("| None") ? withFieldComment(cls, name, ` ${name}: ${type} = None`) : withFieldComment(cls, name, ` ${name}: ${type}`)
881
- );
882
- const isOptional = (l) => /:\s.*\| None\s*=\sNone(\s+#.*)?$/.test(l);
883
- const required = lines.filter((l) => !isOptional(l));
884
- const optional = lines.filter(isOptional);
885
- return `@dataclass
886
- class ${cls.name}:
887
- ${[...doc, ...required, ...optional].join("\n")}`;
888
- }
889
- function collectTransactionElementTypes(transactions, out, seen) {
890
- for (const txName of Object.keys(transactions).sort()) {
891
- const spec = transactions[txName];
892
- for (const paramName of Object.keys(spec.params).sort()) {
893
- const p = spec.params[paramName];
894
- if (p.type !== "array" || !p.element) continue;
895
- const name = txElementTypeName(txName, paramName);
896
- if (seen.has(name)) continue;
897
- seen.add(name);
898
- const fields = [];
899
- for (const field of Object.keys(p.element).sort()) {
900
- fields.push([field, pyParamType(p.element[field])]);
901
- }
902
- out.push({ name, fields });
2373
+ for (const [name, spec] of Object.entries(operations.commands)) {
2374
+ if (commandSet.has(name)) record(name, spec.params ?? {});
2375
+ }
2376
+ for (const [name, spec] of Object.entries(operations.transactions ?? {})) {
2377
+ record(name, spec.params ?? {});
2378
+ }
2379
+ return index;
2380
+ }
2381
+ function metasFor2(index, opId, params) {
2382
+ const inner = index.get(opId) ?? /* @__PURE__ */ new Map();
2383
+ return params.map((p) => inner.get(p.name) ?? {});
2384
+ }
2385
+ function fieldContext(field) {
2386
+ const rustName = toSnakeCase2(field.name);
2387
+ const attrs = [];
2388
+ if (rustName !== field.name) {
2389
+ attrs.push(` #[serde(rename = "${field.name}")]`);
2390
+ }
2391
+ if (field.optional || /Connection$/.test(field.type)) {
2392
+ attrs.push(" #[serde(default)]");
2393
+ }
2394
+ const attr = attrs.length > 0 ? `${attrs.join("\n")}
2395
+ ` : "";
2396
+ const docComment = field.description ? renderDocComment([docLine2(field.description)], " ") : "";
2397
+ return { name: rustName, rustType: field.type, attr, docComment };
2398
+ }
2399
+ function connectionItemType2(cls) {
2400
+ if (!cls.isConnection) return void 0;
2401
+ return cls.name.replace(/Connection$/, "");
2402
+ }
2403
+ function signatureOf3(params) {
2404
+ return params.map((p) => `${toSnakeCase2(p.name)}: ${p.type}`).join(", ");
2405
+ }
2406
+ function paramsStmtsOf(params) {
2407
+ if (params.length === 0) {
2408
+ return " let params = serde_json::Map::new();\n";
2409
+ }
2410
+ const lines = [" let mut params = serde_json::Map::new();"];
2411
+ for (const p of params) {
2412
+ lines.push(
2413
+ ` params.insert("${p.name}".to_string(), json!(${toSnakeCase2(p.name)}));`
2414
+ );
2415
+ }
2416
+ return lines.map((l) => `${l}
2417
+ `).join("");
2418
+ }
2419
+ function methodDocComment(method, metas) {
2420
+ const lines = [];
2421
+ if (method.description) lines.push(docLine2(method.description));
2422
+ const literalNotes = [];
2423
+ method.params.forEach((p, i) => {
2424
+ const meta = metas[i];
2425
+ if (meta.literals && meta.literals.length > 0) {
2426
+ literalNotes.push(
2427
+ `\`${toSnakeCase2(p.name)}\` must be one of: ${meta.literals.map(rustLiteral).join(", ")}.`
2428
+ );
903
2429
  }
2430
+ });
2431
+ if (literalNotes.length > 0) {
2432
+ if (lines.length > 0) lines.push("");
2433
+ lines.push(...literalNotes);
904
2434
  }
2435
+ return renderDocComment(lines, " ");
905
2436
  }
906
- function generateTypes(manifest, queries, transactions = {}, options = {}) {
907
- const classes = [];
908
- const seen = /* @__PURE__ */ new Set();
909
- for (const queryName of Object.keys(queries).sort()) {
910
- const def = queries[queryName];
911
- collectResultTypes(
912
- manifest,
913
- def.entity.name,
914
- selectOf(def),
915
- queryResultTypeName(queryName),
916
- classes,
917
- seen
2437
+ function txMethodDocComment(method, metas) {
2438
+ const lines = [];
2439
+ method.params.forEach((p, i) => {
2440
+ const meta = metas[i];
2441
+ if (meta.literals && meta.literals.length > 0) {
2442
+ lines.push(
2443
+ `\`${toSnakeCase2(p.name)}\` must be one of: ${meta.literals.map(rustLiteral).join(", ")}.`
2444
+ );
2445
+ }
2446
+ });
2447
+ return renderDocComment(lines, " ");
2448
+ }
2449
+ function returnTypeOf(method) {
2450
+ if (method.kind === "query" && method.returnType) {
2451
+ return `Result<${method.returnType}, GraphDDBError>`;
2452
+ }
2453
+ return "Result<(), GraphDDBError>";
2454
+ }
2455
+ function methodContext3(method, metas) {
2456
+ return {
2457
+ methodName: toSnakeCase2(method.operationId),
2458
+ signature: signatureOf3(method.params),
2459
+ returnType: returnTypeOf(method),
2460
+ isQuery: method.kind === "query",
2461
+ operationId: method.operationId,
2462
+ paramsStmts: paramsStmtsOf(method.params),
2463
+ docComment: methodDocComment(method, metas)
2464
+ };
2465
+ }
2466
+ function txMethodContext3(method, metas) {
2467
+ return {
2468
+ methodName: toSnakeCase2(method.transactionId),
2469
+ signature: signatureOf3(method.params),
2470
+ transactionId: method.transactionId,
2471
+ paramsStmts: paramsStmtsOf(method.params),
2472
+ docComment: txMethodDocComment(method, metas)
2473
+ };
2474
+ }
2475
+ function engineOptions3(options) {
2476
+ return {
2477
+ ...options.templateDir !== void 0 ? { templateDir: options.templateDir } : {},
2478
+ helpers: { ...RUST_HELPERS, ...options.helpers ?? {} }
2479
+ };
2480
+ }
2481
+ function render3(entry, context, options) {
2482
+ return renderBinding(entry, context, BUNDLED_RUST_PARTIALS, options);
2483
+ }
2484
+ function renderBody(view, index, engine) {
2485
+ const blocks = [];
2486
+ for (const cls of view.resultClasses) {
2487
+ if (cls.isConnection) {
2488
+ blocks.push(
2489
+ render3(
2490
+ "connection-struct",
2491
+ { name: cls.name, itemType: connectionItemType2(cls) },
2492
+ engine
2493
+ )
2494
+ );
2495
+ } else {
2496
+ const docComment = cls.description ? renderDocComment([docLine2(cls.description)], "") : "";
2497
+ blocks.push(
2498
+ render3(
2499
+ "result-struct",
2500
+ { name: cls.name, docComment, fields: cls.fields.map(fieldContext) },
2501
+ engine
2502
+ )
2503
+ );
2504
+ }
2505
+ }
2506
+ for (const repo of view.repositories) {
2507
+ const methodBlocks = repo.methods.map(
2508
+ (m) => render3(
2509
+ "repo-method",
2510
+ methodContext3(m, metasFor2(index, m.operationId, m.params)),
2511
+ engine
2512
+ )
2513
+ );
2514
+ blocks.push(
2515
+ render3(
2516
+ "repo-struct",
2517
+ { name: repo.name, entity: repo.entity, body: methodBlocks.join("\n\n") },
2518
+ engine
2519
+ )
918
2520
  );
919
2521
  }
920
- collectTransactionElementTypes(transactions, classes, seen);
921
- const useDataclass = Boolean(options.dataclass);
922
- const render = useDataclass ? renderDataclass : renderTypedDict;
923
- const body = classes.length === 0 ? "# (no query result types)" : classes.map(render).join("\n\n\n");
924
- const usesLiteral = classes.some(
925
- (c) => c.fields.some(([, type]) => type.includes("Literal["))
926
- );
927
- const lines = [PY_HEADER, "", "from __future__ import annotations", ""];
928
- if (useDataclass) {
929
- lines.push("from dataclasses import dataclass");
930
- if (usesLiteral) lines.push("from typing import Literal");
931
- } else {
932
- lines.push(
933
- usesLiteral ? "from typing import Literal, TypedDict" : "from typing import TypedDict"
2522
+ if (view.transactions.length > 0) {
2523
+ const methodBlocks = view.transactions.map(
2524
+ (t) => render3(
2525
+ "tx-method",
2526
+ txMethodContext3(t, metasFor2(index, t.transactionId, t.params)),
2527
+ engine
2528
+ )
2529
+ );
2530
+ blocks.push(
2531
+ render3(
2532
+ "tx-struct",
2533
+ { name: "TransactionsRepository", body: methodBlocks.join("\n\n") },
2534
+ engine
2535
+ )
934
2536
  );
935
2537
  }
936
- lines.push("", "", body, "");
937
- return lines.join("\n");
2538
+ return blocks.join("\n\n");
938
2539
  }
939
- function methodParams(def) {
940
- const params = def.params;
941
- const out = [];
942
- for (const name of Object.keys(params).sort()) {
943
- const d = params[name];
944
- const spec = d.literals === void 0 ? { type: d.kind, required: d.required } : {
945
- type: d.kind,
946
- required: d.required,
947
- literals: d.literals
948
- };
949
- out.push({ argName: toSnakeCase(name), pyType: pyParamType(spec), originalName: name });
2540
+ function renderRustBinding(manifest, operations, surface, options = {}) {
2541
+ const model = buildBindingModel(manifest, operations, surface);
2542
+ const view = resolveBindingView(model, RustTypeMapper);
2543
+ const index = buildParamMetaIndex2(operations, surface);
2544
+ const engine = engineOptions3(options);
2545
+ const body = renderBody(view, index, engine);
2546
+ const file = render3(
2547
+ "file",
2548
+ { header: RUST_HEADER, uses: RUST_USES.join("\n"), body },
2549
+ engine
2550
+ );
2551
+ return { [RUST_FILE_NAME]: file };
2552
+ }
2553
+
2554
+ // src/codegen/go-mapper.ts
2555
+ var GO_NUMBER = "json.Number";
2556
+ var GoTypeMapper = {
2557
+ language: "go",
2558
+ scalar(type) {
2559
+ switch (type) {
2560
+ case "number":
2561
+ case "numberSet":
2562
+ return GO_NUMBER;
2563
+ case "boolean":
2564
+ return "bool";
2565
+ case "binary":
2566
+ return "[]byte";
2567
+ case "list":
2568
+ return "[]any";
2569
+ case "map":
2570
+ return "map[string]any";
2571
+ case "stringSet":
2572
+ case "string":
2573
+ default:
2574
+ return "string";
2575
+ }
2576
+ },
2577
+ paramScalar(kind) {
2578
+ return kind === "number" ? GO_NUMBER : "string";
2579
+ },
2580
+ connection(itemType) {
2581
+ return `${itemType}Connection`;
2582
+ },
2583
+ optional(inner) {
2584
+ return inner.startsWith("*") ? inner : `*${inner}`;
2585
+ },
2586
+ list(itemType) {
2587
+ return `[]${itemType}`;
950
2588
  }
951
- return out;
2589
+ };
2590
+
2591
+ // src/codegen/go-templates.ts
2592
+ var FILE3 = `{{header}}
2593
+
2594
+ package {{package}}
2595
+
2596
+ {{imports}}
2597
+
2598
+ {{decodeHelper}}
2599
+
2600
+ {{body}}
2601
+ `;
2602
+ var RESULT_STRUCT2 = `{{docComment}}type {{name}} struct {
2603
+ {{#each fields}}{{docComment}} {{name}} {{goType}} {{tag}}
2604
+ {{/each~}}
2605
+ }`;
2606
+ var CONNECTION_STRUCT2 = `// {{name}} is a paged connection of {{itemType}} items with an optional forward cursor.
2607
+ type {{name}} struct {
2608
+ // Items is the page of items.
2609
+ Items []{{itemType}} \`json:"items"\`
2610
+ // Cursor is the forward cursor for the next page, when more items remain.
2611
+ Cursor *string \`json:"cursor"\`
2612
+ }`;
2613
+ var REPO_METHOD4 = `{{docComment}}func (r *{{repoName}}) {{methodName}}(ctx context.Context{{#if signature}}, {{signature}}{{/if}}) {{returnType}} {
2614
+ {{paramsStmts}}{{#if isQuery}} raw, err := r.runtime.ExecuteQuery(ctx, "{{operationId}}", params, nil)
2615
+ if err != nil {
2616
+ return nil, err
2617
+ }
2618
+ if raw == nil {
2619
+ return nil, nil
2620
+ }
2621
+ var typed {{resultType}}
2622
+ if err := decodeResult(raw, &typed); err != nil {
2623
+ return nil, err
2624
+ }
2625
+ return &typed, nil
2626
+ {{else}} return r.runtime.ExecuteCommand(ctx, "{{operationId}}", params, nil)
2627
+ {{/if}}
2628
+ }`;
2629
+ var REPO_STRUCT2 = `// {{name}} is a typed repository for the {{entity}} entity, wrapping a *graphddb_runtime.GraphDDBRuntime.
2630
+ type {{name}} struct {
2631
+ runtime *graphddb_runtime.GraphDDBRuntime
952
2632
  }
953
- function gsiDescriptionForQuery(name, entity, manifest, querySpecs) {
954
- if (!manifest || !querySpecs) return void 0;
955
- const spec = querySpecs[name];
956
- const indexName = spec?.operations[0]?.indexName;
957
- if (indexName === void 0) return void 0;
958
- const gsi = manifest.entities[entity]?.gsis.find((g) => g.indexName === indexName);
959
- return gsi?.description;
2633
+
2634
+ // New{{name}} constructs the repository over a shared runtime.
2635
+ func New{{name}}(runtime *graphddb_runtime.GraphDDBRuntime) *{{name}} {
2636
+ return &{{name}}{runtime: runtime}
2637
+ }{{#if body}}
2638
+
2639
+ {{body}}{{/if}}`;
2640
+ var TX_METHOD4 = `{{docComment}}func (r *{{repoName}}) {{methodName}}(ctx context.Context{{#if signature}}, {{signature}}{{/if}}) error {
2641
+ {{paramsStmts}} return r.runtime.ExecuteTransaction(ctx, "{{transactionId}}", params, nil)
2642
+ }`;
2643
+ var TX_STRUCT2 = `// {{name}} is a typed repository for declarative transactions, wrapping a *graphddb_runtime.GraphDDBRuntime.
2644
+ type {{name}} struct {
2645
+ runtime *graphddb_runtime.GraphDDBRuntime
960
2646
  }
961
- function collectRepositories(queries, commands, manifest, querySpecs) {
962
- const byEntity = /* @__PURE__ */ new Map();
963
- const ensure = (entity) => {
964
- const cls = `${entityBaseName(entity)}Repository`;
965
- let methods = byEntity.get(cls);
966
- if (!methods) {
967
- methods = [];
968
- byEntity.set(cls, methods);
2647
+
2648
+ // New{{name}} constructs the repository over a shared runtime.
2649
+ func New{{name}}(runtime *graphddb_runtime.GraphDDBRuntime) *{{name}} {
2650
+ return &{{name}}{runtime: runtime}
2651
+ }{{#if body}}
2652
+
2653
+ {{body}}{{/if}}`;
2654
+ var BUNDLED_GO_PARTIALS = {
2655
+ file: FILE3,
2656
+ "result-struct": RESULT_STRUCT2,
2657
+ "connection-struct": CONNECTION_STRUCT2,
2658
+ "repo-struct": REPO_STRUCT2,
2659
+ "repo-method": REPO_METHOD4,
2660
+ "tx-struct": TX_STRUCT2,
2661
+ "tx-method": TX_METHOD4
2662
+ };
2663
+
2664
+ // src/codegen/go-binding.ts
2665
+ var GO_HEADER = "// DO NOT EDIT. Generated by GraphDDB.";
2666
+ var GO_PACKAGE = "bindings";
2667
+ var GO_FILE_NAME = "bindings.go";
2668
+ var GO_RUNTIME_IMPORT = "github.com/foo-log-inc/graphddb/go/graphddb_runtime";
2669
+ var GO_IMPORTS = `import (
2670
+ "bytes"
2671
+ "context"
2672
+ "encoding/json"
2673
+
2674
+ "${GO_RUNTIME_IMPORT}"
2675
+ )`;
2676
+ var GO_DECODE_HELPER = `// decodeResult converts a runtime result (an insertion-ordered map whose numbers
2677
+ // are json.Number) into the typed struct out, preserving exact numeric precision.
2678
+ // It marshals the value and decodes with UseNumber so a json.Number field receives
2679
+ // the original numeric string, never a lossy float64.
2680
+ func decodeResult(raw any, out any) error {
2681
+ data, err := json.Marshal(raw)
2682
+ if err != nil {
2683
+ return err
2684
+ }
2685
+ dec := json.NewDecoder(bytes.NewReader(data))
2686
+ dec.UseNumber()
2687
+ return dec.Decode(out)
2688
+ }`;
2689
+ function words(name) {
2690
+ return name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[-_.\s]+/g, " ").trim().split(/\s+/).filter(Boolean).map((w) => w.toLowerCase());
2691
+ }
2692
+ var INITIALISMS = /* @__PURE__ */ new Set([
2693
+ "id",
2694
+ "url",
2695
+ "uri",
2696
+ "api",
2697
+ "http",
2698
+ "https",
2699
+ "json",
2700
+ "html",
2701
+ "xml",
2702
+ "sql",
2703
+ "uuid",
2704
+ "ttl",
2705
+ "cpu",
2706
+ "ip",
2707
+ "db",
2708
+ "ui",
2709
+ "ascii"
2710
+ ]);
2711
+ function capWord(w) {
2712
+ if (INITIALISMS.has(w)) return w.toUpperCase();
2713
+ return w.charAt(0).toUpperCase() + w.slice(1);
2714
+ }
2715
+ function toExported(name) {
2716
+ return words(name).map(capWord).join("");
2717
+ }
2718
+ function toUnexported(name) {
2719
+ const ws = words(name);
2720
+ if (ws.length === 0) return name;
2721
+ const [first, ...rest] = ws;
2722
+ return first + rest.map(capWord).join("");
2723
+ }
2724
+ function docLine3(text) {
2725
+ return text.replace(/\r?\n/g, " ").trim();
2726
+ }
2727
+ function renderDocComment2(lines, indent) {
2728
+ if (lines.length === 0) return "";
2729
+ return lines.map((l) => l === "" ? `${indent}//
2730
+ ` : `${indent}// ${l}
2731
+ `).join("");
2732
+ }
2733
+ var GO_HELPERS = {
2734
+ // `goDoc text` — collapse a description to one doc-comment line.
2735
+ goDoc: (text) => docLine3(String(text ?? ""))
2736
+ };
2737
+ function pascal3(name) {
2738
+ return name.replace(/[_-]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/\s+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
2739
+ }
2740
+ function buildParamMetaIndex3(operations, surface) {
2741
+ const index = /* @__PURE__ */ new Map();
2742
+ const record = (opId, params) => {
2743
+ const inner = /* @__PURE__ */ new Map();
2744
+ for (const [name, p] of Object.entries(params)) {
2745
+ inner.set(name, {
2746
+ ...p.literals !== void 0 ? { literals: p.literals } : {},
2747
+ ...p.type === "array" ? { elementType: `${pascal3(opId)}${pascal3(name)}Item` } : {}
2748
+ });
969
2749
  }
970
- return methods;
2750
+ index.set(opId, inner);
971
2751
  };
972
- for (const name of Object.keys(queries).sort()) {
973
- const def = queries[name];
974
- const description = def.description ?? gsiDescriptionForQuery(name, def.entity.name, manifest, querySpecs);
975
- ensure(def.entity.name).push({
976
- methodName: toSnakeCase(name),
977
- params: methodParams(def),
978
- returnType: `${queryResultTypeName(name)} | None`,
979
- kind: "query",
980
- operationId: name,
981
- ...description !== void 0 ? { description } : {}
982
- });
2752
+ const querySet = new Set(surface.queries.map((q) => q.name));
2753
+ const commandSet = new Set(surface.commands);
2754
+ for (const [name, spec] of Object.entries(operations.queries)) {
2755
+ if (querySet.has(name)) record(name, spec.params ?? {});
983
2756
  }
984
- for (const name of Object.keys(commands).sort()) {
985
- const def = commands[name];
986
- ensure(def.entity.name).push({
987
- methodName: toSnakeCase(name),
988
- params: methodParams(def),
989
- returnType: "None",
990
- kind: "command",
991
- operationId: name,
992
- ...def.description !== void 0 ? { description: def.description } : {}
993
- });
2757
+ for (const [name, spec] of Object.entries(operations.commands)) {
2758
+ if (commandSet.has(name)) record(name, spec.params ?? {});
994
2759
  }
995
- return [...byEntity.keys()].sort().map((className) => ({
996
- className,
997
- methods: byEntity.get(className).slice().sort((a, b) => a.methodName.localeCompare(b.methodName))
998
- }));
2760
+ for (const [name, spec] of Object.entries(operations.transactions ?? {})) {
2761
+ record(name, spec.params ?? {});
2762
+ }
2763
+ return index;
999
2764
  }
1000
- function renderMethod(m) {
1001
- const sig = m.params.map((p) => `${p.argName}: ${p.pyType}`).join(", ");
1002
- const head = ` def ${m.methodName}(self${sig ? ", " + sig : ""}) -> ${m.returnType}:`;
1003
- const doc = m.description !== void 0 ? [` """${pyDocLine(m.description)}"""`] : [];
1004
- const paramsDict = m.params.map((p) => `"${p.originalName}": ${p.argName}`).join(", ");
1005
- const paramsArg = paramsDict ? `{${paramsDict}}` : "{}";
1006
- if (m.kind === "query") {
1007
- return [
1008
- head,
1009
- ...doc,
1010
- ` return self._runtime.execute_query(`,
1011
- ` query_id="${m.operationId}",`,
1012
- ` params=${paramsArg},`,
1013
- ` )`
1014
- ].join("\n");
1015
- }
1016
- return [
1017
- head,
1018
- ...doc,
1019
- ` self._runtime.execute_command(`,
1020
- ` command_id="${m.operationId}",`,
1021
- ` params=${paramsArg},`,
1022
- ` )`
1023
- ].join("\n");
1024
- }
1025
- function renderRepoClass(cls) {
1026
- const methods = cls.methods.length === 0 ? [" pass"] : cls.methods.map(renderMethod);
1027
- return [
1028
- `class ${cls.className}:`,
1029
- ` def __init__(self, runtime: GraphDDBRuntime) -> None:`,
1030
- ` self._runtime = runtime`,
1031
- "",
1032
- methods.join("\n\n")
1033
- ].join("\n");
1034
- }
1035
- function transactionMethodParams(txName, spec) {
1036
- const params = [];
1037
- const elementTypes = [];
1038
- for (const name of Object.keys(spec.params).sort()) {
1039
- const p = spec.params[name];
1040
- if (p.type === "array") elementTypes.push(txElementTypeName(txName, name));
1041
- params.push({
1042
- argName: toSnakeCase(name),
1043
- pyType: pyTransactionParamType(txName, name, p),
1044
- originalName: name
1045
- });
2765
+ function metasFor3(index, opId, params) {
2766
+ const inner = index.get(opId) ?? /* @__PURE__ */ new Map();
2767
+ return params.map((p) => inner.get(p.name) ?? {});
2768
+ }
2769
+ function fieldContext2(field) {
2770
+ const goName = toExported(field.name);
2771
+ const omit = field.optional || field.type.startsWith("*") || /Connection$/.test(field.type);
2772
+ const tag = `\`json:"${field.name}${omit ? ",omitempty" : ""}"\``;
2773
+ const docComment = field.description ? renderDocComment2([`${goName} ${docLine3(field.description)}`], " ") : "";
2774
+ return { name: goName, goType: field.type, tag, docComment };
2775
+ }
2776
+ function connectionItemType3(cls) {
2777
+ if (!cls.isConnection) return void 0;
2778
+ return cls.name.replace(/Connection$/, "");
2779
+ }
2780
+ function signatureOf4(params) {
2781
+ return params.map((p) => `${toUnexported(p.name)} ${p.type}`).join(", ");
2782
+ }
2783
+ function paramsStmtsOf2(params) {
2784
+ if (params.length === 0) {
2785
+ return " params := graphddb_runtime.Params{}\n";
1046
2786
  }
1047
- return { params, elementTypes };
2787
+ const lines = [" params := graphddb_runtime.Params{"];
2788
+ for (const p of params) {
2789
+ lines.push(` "${p.name}": ${toUnexported(p.name)},`);
2790
+ }
2791
+ lines.push(" }");
2792
+ return lines.map((l) => `${l}
2793
+ `).join("");
1048
2794
  }
1049
- function collectTransactionMethods(transactions) {
1050
- return Object.keys(transactions).sort().map((name) => {
1051
- const { params, elementTypes } = transactionMethodParams(
1052
- name,
1053
- transactions[name]
1054
- );
1055
- return {
1056
- methodName: toSnakeCase(name),
1057
- params,
1058
- transactionId: name,
1059
- elementTypes
1060
- };
2795
+ function goLiteral(value) {
2796
+ return typeof value === "number" ? String(value) : `"${value}"`;
2797
+ }
2798
+ function methodDocComment2(methodName, description, params, metas) {
2799
+ const lines = [];
2800
+ if (description) lines.push(`${methodName} ${docLine3(description)}`);
2801
+ params.forEach((p, i) => {
2802
+ const meta = metas[i];
2803
+ if (meta.literals && meta.literals.length > 0) {
2804
+ lines.push(
2805
+ `${toUnexported(p.name)} must be one of: ${meta.literals.map(goLiteral).join(", ")}.`
2806
+ );
2807
+ }
1061
2808
  });
2809
+ return renderDocComment2(lines, "");
1062
2810
  }
1063
- function renderTxMethod(m) {
1064
- const sig = m.params.map((p) => `${p.argName}: ${p.pyType}`).join(", ");
1065
- const head = ` def ${m.methodName}(self${sig ? ", " + sig : ""}) -> None:`;
1066
- const paramsDict = m.params.map((p) => `"${p.originalName}": ${p.argName}`).join(", ");
1067
- const paramsArg = paramsDict ? `{${paramsDict}}` : "{}";
1068
- return [
1069
- head,
1070
- ` self._runtime.execute_transaction(`,
1071
- ` transaction_id="${m.transactionId}",`,
1072
- ` params=${paramsArg},`,
1073
- ` )`
1074
- ].join("\n");
1075
- }
1076
- function renderTransactionsClass(methods) {
1077
- const body = methods.length === 0 ? [" pass"] : methods.map(renderTxMethod);
1078
- return [
1079
- `class TransactionsRepository:`,
1080
- ` def __init__(self, runtime: GraphDDBRuntime) -> None:`,
1081
- ` self._runtime = runtime`,
1082
- "",
1083
- body.join("\n\n")
1084
- ].join("\n");
1085
- }
1086
- function generateRepositories(queries, commands, transactions = {}, manifest, querySpecs) {
1087
- const classes = collectRepositories(queries, commands, manifest, querySpecs);
1088
- const txMethods = collectTransactionMethods(transactions);
1089
- const resultTypes = /* @__PURE__ */ new Set();
1090
- for (const name of Object.keys(queries)) {
1091
- resultTypes.add(queryResultTypeName(name));
1092
- }
1093
- const elementTypes = /* @__PURE__ */ new Set();
1094
- for (const m of txMethods) for (const t of m.elementTypes) elementTypes.add(t);
1095
- const sortedTypes = [...resultTypes, ...elementTypes].sort();
1096
- const importTypes = sortedTypes.length === 0 ? "" : `from .types import (
1097
- ${sortedTypes.map((t) => ` ${t},`).join("\n")}
1098
- )
1099
- `;
1100
- const repoBodies = classes.map(renderRepoClass);
1101
- if (txMethods.length > 0) repoBodies.push(renderTransactionsClass(txMethods));
1102
- const body = repoBodies.length === 0 ? "# (no repositories)" : repoBodies.join("\n\n\n");
1103
- const usesLiteral = classes.some(
1104
- (c) => c.methods.some((m) => m.params.some((p) => p.pyType.startsWith("Literal[")))
1105
- );
1106
- const lines = [PY_HEADER, "", "from __future__ import annotations", ""];
1107
- if (usesLiteral) lines.push("from typing import Literal", "");
1108
- lines.push("from graphddb_runtime import GraphDDBRuntime");
1109
- if (importTypes) lines.push(importTypes.replace(/\n$/, ""));
1110
- lines.push("", body, "");
1111
- return lines.join("\n");
2811
+ function methodContext4(repoName, method, metas) {
2812
+ const methodName = toExported(method.operationId);
2813
+ const isQuery = method.kind === "query";
2814
+ const resultType = isQuery && method.returnType ? method.returnType.replace(/^\*/, "") : "";
2815
+ const returnType = isQuery && method.returnType ? `(${method.returnType}, error)` : "error";
2816
+ return {
2817
+ repoName,
2818
+ methodName,
2819
+ signature: signatureOf4(method.params),
2820
+ returnType,
2821
+ resultType,
2822
+ isQuery,
2823
+ operationId: method.operationId,
2824
+ paramsStmts: paramsStmtsOf2(method.params),
2825
+ docComment: methodDocComment2(methodName, method.description, method.params, metas)
2826
+ };
2827
+ }
2828
+ function txMethodContext4(repoName, method, metas) {
2829
+ const methodName = toExported(method.transactionId);
2830
+ return {
2831
+ repoName,
2832
+ methodName,
2833
+ signature: signatureOf4(method.params),
2834
+ transactionId: method.transactionId,
2835
+ paramsStmts: paramsStmtsOf2(method.params),
2836
+ docComment: methodDocComment2(methodName, void 0, method.params, metas)
2837
+ };
2838
+ }
2839
+ function engineOptions4(options) {
2840
+ return {
2841
+ ...options.templateDir !== void 0 ? { templateDir: options.templateDir } : {},
2842
+ helpers: { ...GO_HELPERS, ...options.helpers ?? {} }
2843
+ };
1112
2844
  }
1113
- function repositoryClassNames(queries, commands, transactions = {}) {
1114
- const names = collectRepositories(queries, commands).map((c) => c.className);
1115
- if (Object.keys(transactions).length > 0) names.push("TransactionsRepository");
1116
- return names;
2845
+ function render4(entry, context, options) {
2846
+ return renderBinding(entry, context, BUNDLED_GO_PARTIALS, options);
1117
2847
  }
1118
- function generateInit(queries, commands, transactions = {}) {
1119
- const repos = repositoryClassNames(queries, commands, transactions);
1120
- const resultTypes = Object.keys(queries).sort().map(queryResultTypeName);
1121
- const lines = [PY_HEADER, "", "from __future__ import annotations", ""];
1122
- if (resultTypes.length > 0) {
1123
- lines.push(`from .types import (`);
1124
- for (const t of resultTypes) lines.push(` ${t},`);
1125
- lines.push(`)`);
2848
+ function renderBody2(view, index, engine) {
2849
+ const blocks = [];
2850
+ for (const cls of view.resultClasses) {
2851
+ if (cls.isConnection) {
2852
+ blocks.push(
2853
+ render4(
2854
+ "connection-struct",
2855
+ { name: cls.name, itemType: connectionItemType3(cls) },
2856
+ engine
2857
+ )
2858
+ );
2859
+ } else {
2860
+ const docComment = cls.description ? renderDocComment2([`${cls.name} ${docLine3(cls.description)}`], "") : "";
2861
+ blocks.push(
2862
+ render4(
2863
+ "result-struct",
2864
+ { name: cls.name, docComment, fields: cls.fields.map(fieldContext2) },
2865
+ engine
2866
+ )
2867
+ );
2868
+ }
1126
2869
  }
1127
- if (repos.length > 0) {
1128
- lines.push(`from .repositories import (`);
1129
- for (const r of repos) lines.push(` ${r},`);
1130
- lines.push(`)`);
2870
+ for (const repo of view.repositories) {
2871
+ const methodBlocks = repo.methods.map(
2872
+ (m) => render4(
2873
+ "repo-method",
2874
+ methodContext4(repo.name, m, metasFor3(index, m.operationId, m.params)),
2875
+ engine
2876
+ )
2877
+ );
2878
+ blocks.push(
2879
+ render4(
2880
+ "repo-struct",
2881
+ { name: repo.name, entity: repo.entity, body: methodBlocks.join("\n\n") },
2882
+ engine
2883
+ )
2884
+ );
1131
2885
  }
1132
- const all = [...resultTypes, ...repos].sort();
1133
- lines.push("");
1134
- lines.push("__all__ = [");
1135
- for (const name of all) lines.push(` "${name}",`);
1136
- lines.push("]");
1137
- lines.push("");
1138
- return lines.join("\n");
2886
+ if (view.transactions.length > 0) {
2887
+ const txRepoName = "TransactionsRepository";
2888
+ const methodBlocks = view.transactions.map(
2889
+ (t) => render4(
2890
+ "tx-method",
2891
+ txMethodContext4(txRepoName, t, metasFor3(index, t.transactionId, t.params)),
2892
+ engine
2893
+ )
2894
+ );
2895
+ blocks.push(
2896
+ render4(
2897
+ "tx-struct",
2898
+ { name: txRepoName, body: methodBlocks.join("\n\n") },
2899
+ engine
2900
+ )
2901
+ );
2902
+ }
2903
+ return blocks.join("\n\n");
2904
+ }
2905
+ function renderGoBinding(manifest, operations, surface, options = {}) {
2906
+ const model = buildBindingModel(manifest, operations, surface);
2907
+ const view = resolveBindingView(model, GoTypeMapper);
2908
+ const index = buildParamMetaIndex3(operations, surface);
2909
+ const engine = engineOptions4(options);
2910
+ const body = renderBody2(view, index, engine);
2911
+ const file = render4(
2912
+ "file",
2913
+ {
2914
+ header: GO_HEADER,
2915
+ package: GO_PACKAGE,
2916
+ imports: GO_IMPORTS,
2917
+ decodeHelper: GO_DECODE_HELPER,
2918
+ body
2919
+ },
2920
+ engine
2921
+ );
2922
+ return { [GO_FILE_NAME]: file };
1139
2923
  }
1140
2924
 
1141
2925
  // src/codegen/cloudformation.ts
@@ -1712,7 +3496,7 @@ function buildResolution(spec, manifest) {
1712
3496
  if (spec.operations.length === 1) {
1713
3497
  const keyLabelStr = rootKeyLabel(root);
1714
3498
  const proj = root.projection.length > 0 ? `Projection(${root.projection.slice(0, 4).join(", ")})` : "Projection";
1715
- const resolve3 = `${keyLabelStr} \u2192 ${root.type} \u2192 ${proj}`;
3499
+ const resolve4 = `${keyLabelStr} \u2192 ${root.type} \u2192 ${proj}`;
1716
3500
  const inParam = Object.keys(spec.params).sort()[0] ?? "key";
1717
3501
  const outEntity = rootEntityName(spec, manifest) || "Result";
1718
3502
  const diagram2 = [
@@ -1721,7 +3505,7 @@ function buildResolution(spec, manifest) {
1721
3505
  { label: root.type, edge: "" },
1722
3506
  { label: outEntity, edge: "" }
1723
3507
  ];
1724
- return { resolve: resolve3, diagram: diagram2 };
3508
+ return { resolve: resolve4, diagram: diagram2 };
1725
3509
  }
1726
3510
  const entityHops = [];
1727
3511
  const diagram = [];
@@ -1953,7 +3737,7 @@ function buildDocModel(manifest, operations, registry = MetadataRegistry, option
1953
3737
  for (const name of Object.keys(operations.queries).sort()) {
1954
3738
  const spec = operations.queries[name];
1955
3739
  const entityName = rootEntityName(spec, manifest);
1956
- const { resolve: resolve3, diagram } = buildResolution(spec, manifest);
3740
+ const { resolve: resolve4, diagram } = buildResolution(spec, manifest);
1957
3741
  const relation = isRelation(spec);
1958
3742
  const batchGet = usesBatchGet(spec);
1959
3743
  const cursor = usesCursor(spec);
@@ -1965,7 +3749,7 @@ function buildDocModel(manifest, operations, registry = MetadataRegistry, option
1965
3749
  index,
1966
3750
  input: inputShape(spec),
1967
3751
  output: outputShape(spec, entityName),
1968
- resolve: resolve3,
3752
+ resolve: resolve4,
1969
3753
  relation,
1970
3754
  batchGet,
1971
3755
  cursor,
@@ -2009,9 +3793,9 @@ function buildDocModel(manifest, operations, registry = MetadataRegistry, option
2009
3793
  }
2010
3794
 
2011
3795
  // src/codegen/docs.ts
2012
- import { existsSync, readFileSync } from "fs";
2013
- import { resolve } from "path";
2014
- import Handlebars from "handlebars";
3796
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
3797
+ import { resolve as resolve2 } from "path";
3798
+ import Handlebars2 from "handlebars";
2015
3799
 
2016
3800
  // src/codegen/docs-templates.ts
2017
3801
  var PARTIAL_NAMES = [
@@ -2241,17 +4025,17 @@ function registerBuiltinHelpers(hb) {
2241
4025
  return s ? new hb.SafeString(`\`${s}\``) : "";
2242
4026
  });
2243
4027
  }
2244
- function partialSource(name, templateDir) {
4028
+ function partialSource2(name, templateDir) {
2245
4029
  if (templateDir) {
2246
- const candidate = resolve(templateDir, `${name}.hbs`);
2247
- if (existsSync(candidate)) {
2248
- return readFileSync(candidate, "utf8");
4030
+ const candidate = resolve2(templateDir, `${name}.hbs`);
4031
+ if (existsSync2(candidate)) {
4032
+ return readFileSync2(candidate, "utf8");
2249
4033
  }
2250
4034
  }
2251
4035
  return BUNDLED_PARTIALS[name];
2252
4036
  }
2253
4037
  function buildEnvironment(options) {
2254
- const hb = Handlebars.create();
4038
+ const hb = Handlebars2.create();
2255
4039
  registerBuiltinHelpers(hb);
2256
4040
  if (options.helpers) {
2257
4041
  for (const [name, fn] of Object.entries(options.helpers)) {
@@ -2261,7 +4045,7 @@ function buildEnvironment(options) {
2261
4045
  for (const name of PARTIAL_NAMES) {
2262
4046
  hb.registerPartial(
2263
4047
  name,
2264
- hb.compile(partialSource(name, options.templateDir), { noEscape: true })
4048
+ hb.compile(partialSource2(name, options.templateDir), { noEscape: true })
2265
4049
  );
2266
4050
  }
2267
4051
  return hb;
@@ -2291,19 +4075,22 @@ function renderBundle(queries = {}, commands = {}, registry = MetadataRegistry,
2291
4075
  transactions,
2292
4076
  options.contracts ?? {}
2293
4077
  );
2294
- const txSpecs = bundle.operations.transactions ?? {};
4078
+ const py = renderPythonBinding(
4079
+ bundle.manifest,
4080
+ bundle.operations,
4081
+ bindingSurface(queries, commands),
4082
+ {
4083
+ dataclass: options.dataclass,
4084
+ ...options.templateDir !== void 0 ? { templateDir: options.templateDir } : {},
4085
+ ...options.helpers !== void 0 ? { helpers: options.helpers } : {}
4086
+ }
4087
+ );
2295
4088
  return {
2296
4089
  "manifest.json": JSON.stringify(bundle.manifest, null, 2) + "\n",
2297
4090
  "operations.json": JSON.stringify(bundle.operations, null, 2) + "\n",
2298
- "types.py": generateTypes(bundle.manifest, queries, txSpecs, options),
2299
- "repositories.py": generateRepositories(
2300
- queries,
2301
- commands,
2302
- txSpecs,
2303
- bundle.manifest,
2304
- bundle.operations.queries
2305
- ),
2306
- "__init__.py": generateInit(queries, commands, txSpecs)
4091
+ "types.py": py["types.py"],
4092
+ "repositories.py": py["repositories.py"],
4093
+ "__init__.py": py["__init__.py"]
2307
4094
  };
2308
4095
  }
2309
4096
 
@@ -2538,8 +4325,22 @@ function fail(code, message, exitCode) {
2538
4325
  process.stderr.write(JSON.stringify({ code, message }) + "\n");
2539
4326
  process.exit(exitCode);
2540
4327
  }
4328
+ function rustfmtInPlace(files) {
4329
+ if (files.length === 0) return;
4330
+ try {
4331
+ spawnSync("rustfmt", ["--edition", "2021", ...files], { stdio: "ignore" });
4332
+ } catch {
4333
+ }
4334
+ }
4335
+ function gofmtInPlace(files) {
4336
+ if (files.length === 0) return;
4337
+ try {
4338
+ spawnSync("gofmt", ["-w", ...files], { stdio: "ignore" });
4339
+ } catch {
4340
+ }
4341
+ }
2541
4342
  function toUrl(file) {
2542
- const abs = isAbsolute(file) ? file : resolve2(process.cwd(), file);
4343
+ const abs = isAbsolute(file) ? file : resolve3(process.cwd(), file);
2543
4344
  return pathToFileURL(abs).href;
2544
4345
  }
2545
4346
  var tsxRegistered = false;
@@ -2589,7 +4390,7 @@ async function transformPreparedAot(files, aotPath, write, transform) {
2589
4390
  const { unlinkSync } = await import("fs");
2590
4391
  const { basename, join, relative: rel, sep } = await import("path");
2591
4392
  const posix = (p) => p.split(sep).join("/");
2592
- const artifactAbs = isAbsolute(aotPath) ? aotPath : resolve2(process.cwd(), aotPath);
4393
+ const artifactAbs = isAbsolute(aotPath) ? aotPath : resolve3(process.cwd(), aotPath);
2593
4394
  const spec = await import("./spec/index.js");
2594
4395
  try {
2595
4396
  const diagnostics = [];
@@ -2600,7 +4401,7 @@ async function transformPreparedAot(files, aotPath, write, transform) {
2600
4401
  if (file === artifactAbs) continue;
2601
4402
  const relPath = posix(rel(process.cwd(), file) || file);
2602
4403
  scanned.push(relPath);
2603
- const source = readFileSync2(file, "utf8");
4404
+ const source = readFileSync3(file, "utf8");
2604
4405
  if (!source.includes(".prepare(") && !source.includes(`${transform.AOT_LOADER_LOCAL}(`)) {
2605
4406
  continue;
2606
4407
  }
@@ -2671,7 +4472,7 @@ async function transformPreparedAot(files, aotPath, write, transform) {
2671
4472
  mkdirSync(dirname(artifactAbs), { recursive: true });
2672
4473
  const existing = (() => {
2673
4474
  try {
2674
- return readFileSync2(artifactAbs, "utf8");
4475
+ return readFileSync3(artifactAbs, "utf8");
2675
4476
  } catch {
2676
4477
  return void 0;
2677
4478
  }
@@ -2785,7 +4586,7 @@ var handlers = {
2785
4586
  contextsPath ?? contractsPath ?? entry,
2786
4587
  "contexts"
2787
4588
  );
2788
- const abs = (p) => isAbsolute(p) ? p : resolve2(process.cwd(), p);
4589
+ const abs = (p) => isAbsolute(p) ? p : resolve3(process.cwd(), p);
2789
4590
  await applyJsDocDescriptions(
2790
4591
  {
2791
4592
  entry: abs(entry),
@@ -2800,19 +4601,19 @@ var handlers = {
2800
4601
  contracts: { contracts, contexts }
2801
4602
  });
2802
4603
  void dataclass;
2803
- const outDir = isAbsolute(out) ? out : resolve2(process.cwd(), out);
4604
+ const outDir = isAbsolute(out) ? out : resolve3(process.cwd(), out);
2804
4605
  mkdirSync(outDir, { recursive: true });
2805
4606
  for (const name of OUTPUT_FILES) {
2806
- writeFileSync(resolve2(outDir, name), files[name], "utf8");
4607
+ writeFileSync(resolve3(outDir, name), files[name], "utf8");
2807
4608
  }
2808
4609
  const emittedFiles = [...OUTPUT_FILES];
2809
4610
  if (cdcProjectedModelNames().length > 0) {
2810
- const entryAbs = isAbsolute(entry) ? entry : resolve2(process.cwd(), entry);
4611
+ const entryAbs = isAbsolute(entry) ? entry : resolve3(process.cwd(), entry);
2811
4612
  let modelsModule = relative(outDir, entryAbs).replace(/\\/g, "/").replace(/\.[mc]?ts$/, "");
2812
4613
  if (!modelsModule.startsWith(".")) modelsModule = `./${modelsModule}`;
2813
4614
  const cdcRegistryFile = "cdc-registry.ts";
2814
4615
  writeFileSync(
2815
- resolve2(outDir, cdcRegistryFile),
4616
+ resolve3(outDir, cdcRegistryFile),
2816
4617
  generateCdcRegistry({ modelsModule }),
2817
4618
  "utf8"
2818
4619
  );
@@ -2830,6 +4631,299 @@ var handlers = {
2830
4631
  fail("GENERATION_FAILED", message, 1);
2831
4632
  }
2832
4633
  },
4634
+ /**
4635
+ * `generate php --typed` (issue #219, Epic #216 — typed-binding codegen).
4636
+ *
4637
+ * Loads the TypeScript definitions exactly like `generate python`, builds the
4638
+ * SAME serializable manifest / operations bundle (so the JSON the PHP runtime
4639
+ * consumes is identical), then renders typed PHP bindings — one
4640
+ * `<Entity>Repository` per entity plus a result DTO / `<Item>Connection` per
4641
+ * query result shape — through the template-driven foundation restricted to the
4642
+ * public binding surface (contract-internal ops excluded). `--template-dir`
4643
+ * (per-partial layout override) and `--helpers` are threaded straight through,
4644
+ * matching the Python / docs customization surface.
4645
+ */
4646
+ async generatePhp(options) {
4647
+ const {
4648
+ entry,
4649
+ queries: queriesPath,
4650
+ commands: commandsPath,
4651
+ transactions: transactionsPath,
4652
+ contracts: contractsPath,
4653
+ contexts: contextsPath,
4654
+ typed,
4655
+ templateDir,
4656
+ helpers: helpersPath,
4657
+ out
4658
+ } = options;
4659
+ if (!entry) fail("INVALID_ARGS", "Missing required option --entry.", 2);
4660
+ if (!out) fail("INVALID_ARGS", "Missing required option --out.", 2);
4661
+ try {
4662
+ await importModelEntry(entry);
4663
+ const queries = await loadDefinitionMap(queriesPath ?? entry, "queries");
4664
+ const commands = await loadDefinitionMap(commandsPath ?? entry, "commands");
4665
+ const transactions = await loadOptionalMap(
4666
+ transactionsPath ?? commandsPath ?? entry,
4667
+ "transactions"
4668
+ );
4669
+ const contracts = await loadOptionalMap(
4670
+ contractsPath ?? entry,
4671
+ "contracts"
4672
+ );
4673
+ const contexts = await loadOptionalMap(
4674
+ contextsPath ?? contractsPath ?? entry,
4675
+ "contexts"
4676
+ );
4677
+ const abs = (p) => isAbsolute(p) ? p : resolve3(process.cwd(), p);
4678
+ await applyJsDocDescriptions(
4679
+ {
4680
+ entry: abs(entry),
4681
+ queries: abs(queriesPath ?? entry),
4682
+ commands: abs(commandsPath ?? entry)
4683
+ },
4684
+ queries,
4685
+ commands
4686
+ );
4687
+ const bundle = buildBridgeBundle(queries, commands, MetadataRegistry, transactions, {
4688
+ contracts,
4689
+ contexts
4690
+ });
4691
+ let helpers;
4692
+ if (helpersPath) {
4693
+ const mod = await importModule(helpersPath);
4694
+ const exported = mod.helpers ?? mod.default;
4695
+ if (!exported || typeof exported !== "object") {
4696
+ fail(
4697
+ "INVALID_ARGS",
4698
+ `Module '${helpersPath}' must export a 'helpers' (or default) object mapping helper names to functions.`,
4699
+ 2
4700
+ );
4701
+ }
4702
+ helpers = exported;
4703
+ }
4704
+ const files = renderPhpBinding(
4705
+ bundle.manifest,
4706
+ bundle.operations,
4707
+ bindingSurface(queries, commands),
4708
+ {
4709
+ ...templateDir !== void 0 ? { templateDir: isAbsolute(templateDir) ? templateDir : resolve3(process.cwd(), templateDir) } : {},
4710
+ ...helpers !== void 0 ? { helpers } : {}
4711
+ }
4712
+ );
4713
+ void typed;
4714
+ const outDir = isAbsolute(out) ? out : resolve3(process.cwd(), out);
4715
+ mkdirSync(outDir, { recursive: true });
4716
+ const emittedFiles = Object.keys(files).sort();
4717
+ for (const name of emittedFiles) {
4718
+ writeFileSync(resolve3(outDir, name), files[name], "utf8");
4719
+ }
4720
+ process.stdout.write(
4721
+ JSON.stringify({
4722
+ status: "ok",
4723
+ outDir: out,
4724
+ files: emittedFiles
4725
+ }) + "\n"
4726
+ );
4727
+ } catch (err) {
4728
+ const message = err instanceof Error ? err.message : String(err);
4729
+ fail("GENERATION_FAILED", message, 1);
4730
+ }
4731
+ },
4732
+ /**
4733
+ * `generate rust --typed` (issue #220, Epic #216 — Rust typed bindings).
4734
+ *
4735
+ * Mirrors `generatePhp` one-to-one: loads the TypeScript definition modules,
4736
+ * builds the SAME manifest / operations bundle the Python / PHP paths produce,
4737
+ * and renders the typed Rust bindings (a single `bindings.rs` module of serde
4738
+ * result structs + `<Entity>Repository` wrappers over the Rust
4739
+ * `GraphDDBRuntime`). Supports `--template-dir` (per-partial layout override)
4740
+ * and `--helpers` (extra Handlebars helpers), both layout-only.
4741
+ */
4742
+ async generateRust(options) {
4743
+ const {
4744
+ entry,
4745
+ queries: queriesPath,
4746
+ commands: commandsPath,
4747
+ transactions: transactionsPath,
4748
+ contracts: contractsPath,
4749
+ contexts: contextsPath,
4750
+ typed,
4751
+ templateDir,
4752
+ helpers: helpersPath,
4753
+ out
4754
+ } = options;
4755
+ if (!entry) fail("INVALID_ARGS", "Missing required option --entry.", 2);
4756
+ if (!out) fail("INVALID_ARGS", "Missing required option --out.", 2);
4757
+ try {
4758
+ await importModelEntry(entry);
4759
+ const queries = await loadDefinitionMap(queriesPath ?? entry, "queries");
4760
+ const commands = await loadDefinitionMap(commandsPath ?? entry, "commands");
4761
+ const transactions = await loadOptionalMap(
4762
+ transactionsPath ?? commandsPath ?? entry,
4763
+ "transactions"
4764
+ );
4765
+ const contracts = await loadOptionalMap(
4766
+ contractsPath ?? entry,
4767
+ "contracts"
4768
+ );
4769
+ const contexts = await loadOptionalMap(
4770
+ contextsPath ?? contractsPath ?? entry,
4771
+ "contexts"
4772
+ );
4773
+ const abs = (p) => isAbsolute(p) ? p : resolve3(process.cwd(), p);
4774
+ await applyJsDocDescriptions(
4775
+ {
4776
+ entry: abs(entry),
4777
+ queries: abs(queriesPath ?? entry),
4778
+ commands: abs(commandsPath ?? entry)
4779
+ },
4780
+ queries,
4781
+ commands
4782
+ );
4783
+ const bundle = buildBridgeBundle(queries, commands, MetadataRegistry, transactions, {
4784
+ contracts,
4785
+ contexts
4786
+ });
4787
+ let helpers;
4788
+ if (helpersPath) {
4789
+ const mod = await importModule(helpersPath);
4790
+ const exported = mod.helpers ?? mod.default;
4791
+ if (!exported || typeof exported !== "object") {
4792
+ fail(
4793
+ "INVALID_ARGS",
4794
+ `Module '${helpersPath}' must export a 'helpers' (or default) object mapping helper names to functions.`,
4795
+ 2
4796
+ );
4797
+ }
4798
+ helpers = exported;
4799
+ }
4800
+ const files = renderRustBinding(
4801
+ bundle.manifest,
4802
+ bundle.operations,
4803
+ bindingSurface(queries, commands),
4804
+ {
4805
+ ...templateDir !== void 0 ? { templateDir: isAbsolute(templateDir) ? templateDir : resolve3(process.cwd(), templateDir) } : {},
4806
+ ...helpers !== void 0 ? { helpers } : {}
4807
+ }
4808
+ );
4809
+ void typed;
4810
+ const outDir = isAbsolute(out) ? out : resolve3(process.cwd(), out);
4811
+ mkdirSync(outDir, { recursive: true });
4812
+ const emittedFiles = Object.keys(files).sort();
4813
+ for (const name of emittedFiles) {
4814
+ writeFileSync(resolve3(outDir, name), files[name], "utf8");
4815
+ }
4816
+ rustfmtInPlace(emittedFiles.map((name) => resolve3(outDir, name)));
4817
+ process.stdout.write(
4818
+ JSON.stringify({
4819
+ status: "ok",
4820
+ outDir: out,
4821
+ files: emittedFiles
4822
+ }) + "\n"
4823
+ );
4824
+ } catch (err) {
4825
+ const message = err instanceof Error ? err.message : String(err);
4826
+ fail("GENERATION_FAILED", message, 1);
4827
+ }
4828
+ },
4829
+ /**
4830
+ * `generate go --typed` (issue #221, Epic #216 — Go typed bindings).
4831
+ *
4832
+ * Mirrors `generateRust` one-to-one: loads the TypeScript definition modules,
4833
+ * builds the SAME manifest / operations bundle the Python / PHP / Rust paths
4834
+ * produce, and renders the typed Go bindings (a single `bindings.go` package of
4835
+ * json result structs + `<Entity>Repository` wrappers over the Go
4836
+ * `GraphDDBRuntime`). Supports `--template-dir` (per-partial layout override)
4837
+ * and `--helpers` (extra Handlebars helpers), both layout-only. The emitted file
4838
+ * is gofmt-normalized so it passes `gofmt -l` / `go vet` as-is.
4839
+ */
4840
+ async generateGo(options) {
4841
+ const {
4842
+ entry,
4843
+ queries: queriesPath,
4844
+ commands: commandsPath,
4845
+ transactions: transactionsPath,
4846
+ contracts: contractsPath,
4847
+ contexts: contextsPath,
4848
+ typed,
4849
+ templateDir,
4850
+ helpers: helpersPath,
4851
+ out
4852
+ } = options;
4853
+ if (!entry) fail("INVALID_ARGS", "Missing required option --entry.", 2);
4854
+ if (!out) fail("INVALID_ARGS", "Missing required option --out.", 2);
4855
+ try {
4856
+ await importModelEntry(entry);
4857
+ const queries = await loadDefinitionMap(queriesPath ?? entry, "queries");
4858
+ const commands = await loadDefinitionMap(commandsPath ?? entry, "commands");
4859
+ const transactions = await loadOptionalMap(
4860
+ transactionsPath ?? commandsPath ?? entry,
4861
+ "transactions"
4862
+ );
4863
+ const contracts = await loadOptionalMap(
4864
+ contractsPath ?? entry,
4865
+ "contracts"
4866
+ );
4867
+ const contexts = await loadOptionalMap(
4868
+ contextsPath ?? contractsPath ?? entry,
4869
+ "contexts"
4870
+ );
4871
+ const abs = (p) => isAbsolute(p) ? p : resolve3(process.cwd(), p);
4872
+ await applyJsDocDescriptions(
4873
+ {
4874
+ entry: abs(entry),
4875
+ queries: abs(queriesPath ?? entry),
4876
+ commands: abs(commandsPath ?? entry)
4877
+ },
4878
+ queries,
4879
+ commands
4880
+ );
4881
+ const bundle = buildBridgeBundle(queries, commands, MetadataRegistry, transactions, {
4882
+ contracts,
4883
+ contexts
4884
+ });
4885
+ let helpers;
4886
+ if (helpersPath) {
4887
+ const mod = await importModule(helpersPath);
4888
+ const exported = mod.helpers ?? mod.default;
4889
+ if (!exported || typeof exported !== "object") {
4890
+ fail(
4891
+ "INVALID_ARGS",
4892
+ `Module '${helpersPath}' must export a 'helpers' (or default) object mapping helper names to functions.`,
4893
+ 2
4894
+ );
4895
+ }
4896
+ helpers = exported;
4897
+ }
4898
+ const files = renderGoBinding(
4899
+ bundle.manifest,
4900
+ bundle.operations,
4901
+ bindingSurface(queries, commands),
4902
+ {
4903
+ ...templateDir !== void 0 ? { templateDir: isAbsolute(templateDir) ? templateDir : resolve3(process.cwd(), templateDir) } : {},
4904
+ ...helpers !== void 0 ? { helpers } : {}
4905
+ }
4906
+ );
4907
+ void typed;
4908
+ const outDir = isAbsolute(out) ? out : resolve3(process.cwd(), out);
4909
+ mkdirSync(outDir, { recursive: true });
4910
+ const emittedFiles = Object.keys(files).sort();
4911
+ for (const name of emittedFiles) {
4912
+ writeFileSync(resolve3(outDir, name), files[name], "utf8");
4913
+ }
4914
+ gofmtInPlace(emittedFiles.map((name) => resolve3(outDir, name)));
4915
+ process.stdout.write(
4916
+ JSON.stringify({
4917
+ status: "ok",
4918
+ outDir: out,
4919
+ files: emittedFiles
4920
+ }) + "\n"
4921
+ );
4922
+ } catch (err) {
4923
+ const message = err instanceof Error ? err.message : String(err);
4924
+ fail("GENERATION_FAILED", message, 1);
4925
+ }
4926
+ },
2833
4927
  /**
2834
4928
  * `generate cloudformation` (issue #168, Epic #167 — CFn generator C1).
2835
4929
  *
@@ -2989,7 +5083,7 @@ var handlers = {
2989
5083
  autoscaleTarget: autoscaleTargetNum
2990
5084
  });
2991
5085
  if (out) {
2992
- const outFile = isAbsolute(out) ? out : resolve2(process.cwd(), out);
5086
+ const outFile = isAbsolute(out) ? out : resolve3(process.cwd(), out);
2993
5087
  mkdirSync(dirname(outFile), { recursive: true });
2994
5088
  writeFileSync(outFile, template, "utf8");
2995
5089
  process.stdout.write(
@@ -3104,10 +5198,10 @@ var handlers = {
3104
5198
  helpers = exported;
3105
5199
  }
3106
5200
  const files = generateDocs(docModel, {
3107
- templateDir: templateDir ? isAbsolute(templateDir) ? templateDir : resolve2(process.cwd(), templateDir) : void 0,
5201
+ templateDir: templateDir ? isAbsolute(templateDir) ? templateDir : resolve3(process.cwd(), templateDir) : void 0,
3108
5202
  helpers
3109
5203
  });
3110
- const outAbs = isAbsolute(out) ? out : resolve2(process.cwd(), out);
5204
+ const outAbs = isAbsolute(out) ? out : resolve3(process.cwd(), out);
3111
5205
  const emitted = [];
3112
5206
  if (outIsFile) {
3113
5207
  const [contents] = Object.values(files);
@@ -3120,7 +5214,7 @@ var handlers = {
3120
5214
  } else {
3121
5215
  mkdirSync(outAbs, { recursive: true });
3122
5216
  for (const [name, contents] of Object.entries(files)) {
3123
- writeFileSync(resolve2(outAbs, name), contents, "utf8");
5217
+ writeFileSync(resolve3(outAbs, name), contents, "utf8");
3124
5218
  emitted.push(name);
3125
5219
  }
3126
5220
  process.stdout.write(
@@ -3152,7 +5246,7 @@ var handlers = {
3152
5246
  async transformPrepared(options) {
3153
5247
  const { src, write, aot } = options;
3154
5248
  if (!src) fail("INVALID_ARGS", "Missing required option --src.", 2);
3155
- const srcAbs = isAbsolute(src) ? src : resolve2(process.cwd(), src);
5249
+ const srcAbs = isAbsolute(src) ? src : resolve3(process.cwd(), src);
3156
5250
  let rootIsFile;
3157
5251
  let rootIsDir;
3158
5252
  try {
@@ -3170,7 +5264,7 @@ var handlers = {
3170
5264
  const walk = (dir) => {
3171
5265
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
3172
5266
  if (entry.name.startsWith(".")) continue;
3173
- const full = resolve2(dir, entry.name);
5267
+ const full = resolve3(dir, entry.name);
3174
5268
  if (entry.isDirectory()) {
3175
5269
  if (entry.name === "node_modules" || entry.name === "dist") continue;
3176
5270
  walk(full);
@@ -3210,7 +5304,7 @@ var handlers = {
3210
5304
  for (const file of files) {
3211
5305
  const rel = relative(process.cwd(), file) || file;
3212
5306
  scanned.push(rel);
3213
- const source = readFileSync2(file, "utf8");
5307
+ const source = readFileSync3(file, "utf8");
3214
5308
  if (!source.includes(".prepare(")) continue;
3215
5309
  const result = transform.transformPreparedSource(rel, source, { mode });
3216
5310
  diagnostics.push(...result.diagnostics);