graphddb 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,13 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  buildBridgeBundle,
4
- buildManifest
5
- } from "./chunk-H5TUW2WR.js";
4
+ buildManifest,
5
+ buildOperations
6
+ } from "./chunk-N5NQM3SO.js";
6
7
  import {
7
8
  MetadataRegistry,
8
9
  normalizeSelectSpec
9
- } from "./chunk-W3GEJPPV.js";
10
- import "./chunk-MCKGQKYU.js";
10
+ } from "./chunk-F2DI3GTI.js";
11
+ import {
12
+ createDefaultLinter
13
+ } from "./chunk-PDUVTYC5.js";
11
14
 
12
15
  // src/cli.ts
13
16
  import { createRequire } from "module";
@@ -441,6 +444,105 @@ var commandDefinitions = {
441
444
  }
442
445
  }
443
446
  ]
447
+ },
448
+ "generate.docs": {
449
+ "effects": {
450
+ "risk_level": "low",
451
+ "reads": [
452
+ "ts-definitions"
453
+ ],
454
+ "writes": [
455
+ "generated-code"
456
+ ]
457
+ },
458
+ "options": [
459
+ {
460
+ "name": "entry",
461
+ "schema": {
462
+ "type": "string"
463
+ },
464
+ "file": {
465
+ "mode": "read",
466
+ "exists": true,
467
+ "media_type": "application/typescript",
468
+ "encoding": "utf-8"
469
+ }
470
+ },
471
+ {
472
+ "name": "queries",
473
+ "schema": {
474
+ "type": "string"
475
+ },
476
+ "file": {
477
+ "mode": "read",
478
+ "exists": true,
479
+ "media_type": "application/typescript",
480
+ "encoding": "utf-8"
481
+ }
482
+ },
483
+ {
484
+ "name": "out",
485
+ "schema": {
486
+ "type": "string"
487
+ }
488
+ },
489
+ {
490
+ "name": "format",
491
+ "schema": {
492
+ "type": "string",
493
+ "enum": [
494
+ "markdown"
495
+ ],
496
+ "default": "markdown"
497
+ }
498
+ },
499
+ {
500
+ "name": "split",
501
+ "schema": {
502
+ "type": "string",
503
+ "enum": [
504
+ "none",
505
+ "entity"
506
+ ],
507
+ "default": "none"
508
+ }
509
+ },
510
+ {
511
+ "name": "template-dir",
512
+ "schema": {
513
+ "type": "string"
514
+ }
515
+ },
516
+ {
517
+ "name": "helpers",
518
+ "schema": {
519
+ "type": "string"
520
+ }
521
+ },
522
+ {
523
+ "name": "billing-mode",
524
+ "schema": {
525
+ "type": "string",
526
+ "enum": [
527
+ "PAY_PER_REQUEST",
528
+ "PROVISIONED"
529
+ ],
530
+ "default": "PAY_PER_REQUEST"
531
+ }
532
+ },
533
+ {
534
+ "name": "stream",
535
+ "schema": {
536
+ "type": "string",
537
+ "enum": [
538
+ "auto",
539
+ "on",
540
+ "off"
541
+ ],
542
+ "default": "auto"
543
+ }
544
+ }
545
+ ]
444
546
  }
445
547
  };
446
548
  function deriveCommandPolicy(command_id, optionValues) {
@@ -467,8 +569,8 @@ function deriveCommandPolicy(command_id, optionValues) {
467
569
  }
468
570
 
469
571
  // src/generated/contract.ts
470
- 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\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\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";
471
- 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 },\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 }\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 }\n }\n}';
572
+ 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\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\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";
573
+ 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 },\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 }\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 }\n }\n}';
472
574
 
473
575
  // src/generated/program.ts
474
576
  function createProgram(handlers2, version) {
@@ -494,6 +596,15 @@ function createProgram(handlers2, version) {
494
596
  }
495
597
  await handlers2.generateCloudformation(opts, globalOpts);
496
598
  });
599
+ __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) => {
600
+ const globalOpts = cmd.optsWithGlobals();
601
+ if (globalOpts.introspect) {
602
+ const policy = deriveCommandPolicy("generate.docs", opts);
603
+ console.log(JSON.stringify(policy, null, 2));
604
+ return;
605
+ }
606
+ await handlers2.generateDocs(opts, globalOpts);
607
+ });
497
608
  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) => {
498
609
  if (commands.length === 0 && !opts.all) {
499
610
  process.stderr.write(JSON.stringify({ code: "INVALID_ARGS", message: "Specify command IDs or use --all" }) + "\n");
@@ -511,7 +622,7 @@ function createProgram(handlers2, version) {
511
622
  type: "cli-contracts/extract",
512
623
  extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
513
624
  specVersion: doc.cli_contracts ?? "0.1.0",
514
- commands: ["graphddb.generate.python", "graphddb.generate.cloudformation"]
625
+ commands: ["graphddb.generate.python", "graphddb.generate.cloudformation", "graphddb.generate.docs"]
515
626
  };
516
627
  }
517
628
  Object.assign(out, doc);
@@ -528,7 +639,7 @@ function createProgram(handlers2, version) {
528
639
  yamlLines.push("extractedAt: " + (/* @__PURE__ */ new Date()).toISOString());
529
640
  yamlLines.push("spec_version: " + (doc.cli_contracts ?? "0.1.0"));
530
641
  yamlLines.push("commands:");
531
- for (const id of ["graphddb.generate.python", "graphddb.generate.cloudformation"]) {
642
+ for (const id of ["graphddb.generate.python", "graphddb.generate.cloudformation", "graphddb.generate.docs"]) {
532
643
  yamlLines.push(" - " + id);
533
644
  }
534
645
  }
@@ -571,7 +682,7 @@ function createProgram(handlers2, version) {
571
682
  import { mkdirSync, writeFileSync } from "fs";
572
683
  import { dirname, relative } from "path";
573
684
  import { pathToFileURL } from "url";
574
- import { isAbsolute, resolve } from "path";
685
+ import { isAbsolute, resolve as resolve2 } from "path";
575
686
 
576
687
  // src/codegen/python.ts
577
688
  var PY_HEADER = "# DO NOT EDIT. Generated by GraphDDB.";
@@ -633,6 +744,11 @@ function collectResultTypes(manifest, entity, select, typeName, out, seen) {
633
744
  const value = select[field];
634
745
  const relation = meta?.relations[field];
635
746
  if (relation) {
747
+ if (relation.type === "refs") {
748
+ throw new Error(
749
+ `Relation '${field}' on '${relation.target}' is a 'refs' relation (issue #197): it is resolvable only through the TS in-process runtime, not the Python runtime (its parent-list BatchGet fan-out has no static-spec / Python representation). Remove '${field}' from selects compiled to generated Python types.`
750
+ );
751
+ }
636
752
  const spec = normalizeSelectSpec(value);
637
753
  const childSelect = spec.select ?? {};
638
754
  const itemTypeName = `${typeName}${toPascalCase(field)}Item`;
@@ -1463,6 +1579,662 @@ function generateCdcRegistry(options = {}) {
1463
1579
  return lines.join("\n");
1464
1580
  }
1465
1581
 
1582
+ // src/codegen/docs-model.ts
1583
+ var AS_CONFIGURED = "as configured at deployment";
1584
+ function fieldTypeLabel(entity, field) {
1585
+ const f = entity.fields[field];
1586
+ if (!f) return "";
1587
+ if (f.format === "datetime") return "datetime";
1588
+ if (f.format === "date") return "date";
1589
+ return f.type;
1590
+ }
1591
+ function collectGsiColumns(manifest) {
1592
+ const names = /* @__PURE__ */ new Set();
1593
+ for (const entity of Object.values(manifest.entities)) {
1594
+ for (const gsi of entity.gsis) names.add(gsi.indexName);
1595
+ }
1596
+ return [...names].sort();
1597
+ }
1598
+ function gsiByName(entity, indexName) {
1599
+ return entity.gsis.find((g) => g.indexName === indexName);
1600
+ }
1601
+ function propertyRole(entity, property) {
1602
+ const key = entity.key;
1603
+ if (key) {
1604
+ if (referencesField(key.pkTemplate, property)) return "PK";
1605
+ if (key.skTemplate && referencesField(key.skTemplate, property)) return "SK";
1606
+ }
1607
+ for (const gsi of entity.gsis) {
1608
+ if (referencesField(gsi.pkTemplate, property)) return `${gsi.indexName} PK`;
1609
+ if (gsi.skTemplate && referencesField(gsi.skTemplate, property)) {
1610
+ return `${gsi.indexName} SK`;
1611
+ }
1612
+ }
1613
+ if (entity.fields[property]) return "\u2713";
1614
+ return "";
1615
+ }
1616
+ function referencesField(template, field) {
1617
+ return template.includes(`{${field}}`);
1618
+ }
1619
+ function keyLabel(prefix, template) {
1620
+ return template ? `${prefix} = ${template}` : "";
1621
+ }
1622
+ function rootIndex(spec) {
1623
+ const root = spec.operations[0];
1624
+ if (!root) return "";
1625
+ return root.indexName ?? "PK";
1626
+ }
1627
+ function isRelation(spec) {
1628
+ return spec.operations.length > 1;
1629
+ }
1630
+ function usesBatchGet(spec) {
1631
+ return spec.operations.some((op) => op.type === "BatchGetItem");
1632
+ }
1633
+ function usesCursor(spec) {
1634
+ return (spec.cardinality ?? "many") === "many";
1635
+ }
1636
+ function inputShape(spec) {
1637
+ const parts = [];
1638
+ for (const name of Object.keys(spec.params).sort()) {
1639
+ const p = spec.params[name];
1640
+ const opt = p.required ? "" : "?";
1641
+ parts.push(`${name}${opt}: ${p.type}`);
1642
+ }
1643
+ if ("cardinality" in spec && (spec.cardinality ?? "many") === "many") {
1644
+ parts.push("limit?: number", "cursor?: string");
1645
+ }
1646
+ return `{ ${parts.join(", ")} }`;
1647
+ }
1648
+ function opPkTemplate(op) {
1649
+ if (op.indexName) {
1650
+ return op.keyCondition[`${op.indexName}PK`] ?? op.keyCondition["PK"] ?? op.keyCondition["pk"] ?? "";
1651
+ }
1652
+ return op.keyCondition["PK"] ?? op.keyCondition["pk"] ?? "";
1653
+ }
1654
+ function rootEntityName(spec, manifest) {
1655
+ const root = spec.operations[0];
1656
+ if (!root) return "";
1657
+ const pk = opPkTemplate(root);
1658
+ for (const [name, entity] of Object.entries(manifest.entities)) {
1659
+ const cand = root.indexName ? gsiByName(entity, root.indexName)?.pkTemplate : entity.key?.pkTemplate;
1660
+ if (cand && pk && cand === pk) return name;
1661
+ }
1662
+ return "";
1663
+ }
1664
+ function outputShape(spec, entityName) {
1665
+ const many = (spec.cardinality ?? "many") === "many";
1666
+ const base = entityName || "Result";
1667
+ return many ? `Page<${base}>` : base;
1668
+ }
1669
+ function buildResolution(spec, manifest) {
1670
+ const root = spec.operations[0];
1671
+ if (!root) return { resolve: "", diagram: [] };
1672
+ if (spec.operations.length === 1) {
1673
+ const keyLabelStr = rootKeyLabel(root);
1674
+ const proj = root.projection.length > 0 ? `Projection(${root.projection.slice(0, 4).join(", ")})` : "Projection";
1675
+ const resolve3 = `${keyLabelStr} \u2192 ${root.type} \u2192 ${proj}`;
1676
+ const inParam = Object.keys(spec.params).sort()[0] ?? "key";
1677
+ const outEntity = rootEntityName(spec, manifest) || "Result";
1678
+ const diagram2 = [
1679
+ { label: inParam, edge: "" },
1680
+ { label: keyLabelStr, edge: "" },
1681
+ { label: root.type, edge: "" },
1682
+ { label: outEntity, edge: "" }
1683
+ ];
1684
+ return { resolve: resolve3, diagram: diagram2 };
1685
+ }
1686
+ const entityHops = [];
1687
+ const diagram = [];
1688
+ for (let i = 0; i < spec.operations.length; i++) {
1689
+ const op = spec.operations[i];
1690
+ const entity = opEntityName(op, manifest) || op.tableName;
1691
+ entityHops.push(`${op.type}(${entity}${op.indexName ? ` by ${op.indexName}` : ""})`);
1692
+ const edge = i < spec.operations.length - 1 ? spec.operations[i + 1].type === "BatchGetItem" ? "BatchGet" : op.indexName ?? "PK" : "";
1693
+ diagram.push({ label: entity, edge });
1694
+ }
1695
+ return { resolve: entityHops.join(" \u2192 "), diagram };
1696
+ }
1697
+ function rootKeyLabel(op) {
1698
+ const pk = opPkTemplate(op);
1699
+ const prefix = op.indexName ? `${op.indexName} PK` : "PK";
1700
+ return `${prefix} = ${pk}`;
1701
+ }
1702
+ function opEntityName(op, manifest) {
1703
+ const pk = opPkTemplate(op);
1704
+ for (const [name, entity] of Object.entries(manifest.entities)) {
1705
+ const cand = op.indexName ? gsiByName(entity, op.indexName)?.pkTemplate : entity.key?.pkTemplate;
1706
+ if (cand && pk) {
1707
+ if (literalPrefix(cand) === literalPrefix(pk)) return name;
1708
+ }
1709
+ }
1710
+ return "";
1711
+ }
1712
+ function literalPrefix(template) {
1713
+ const idx = template.indexOf("{");
1714
+ return idx === -1 ? template : template.slice(0, idx);
1715
+ }
1716
+ function commandSignature(_name, spec) {
1717
+ const verb = spec.type === "PutItem" ? "create" : spec.type === "UpdateItem" ? "update" : "delete";
1718
+ return `${spec.entity}.${verb}()`;
1719
+ }
1720
+ function buildMaintainedPaths(registry) {
1721
+ const rows = [];
1722
+ const entities = registry.getAll();
1723
+ const byName = /* @__PURE__ */ new Map();
1724
+ for (const [cls, meta] of entities) byName.set(cls.name, meta);
1725
+ for (const name of [...byName.keys()].sort()) {
1726
+ const meta = byName.get(name);
1727
+ for (const rel of meta.relations) {
1728
+ const row = maintainedRowForRelation(name, rel);
1729
+ if (row) rows.push(row);
1730
+ }
1731
+ for (const agg of meta.aggregates) {
1732
+ const row = maintainedRowForAggregate(name, agg);
1733
+ if (row) rows.push(row);
1734
+ }
1735
+ }
1736
+ return rows;
1737
+ }
1738
+ function maintainedRowForRelation(ownerName, rel) {
1739
+ const pattern = rel.options?.pattern;
1740
+ if (!pattern) return null;
1741
+ const triggers = rel.options?.write?.maintainedOn ?? [];
1742
+ const source = sourceFromTriggers(triggers) || rel.targetFactory().name;
1743
+ const target = `${ownerName}.${rel.propertyName}`;
1744
+ const update = pattern === "counter" ? "ADD +1 / \u22121 on create / remove" : `project ${source} fields into ${ownerName}`;
1745
+ return { source, target, pattern, update };
1746
+ }
1747
+ function maintainedRowForAggregate(ownerName, agg) {
1748
+ const pattern = agg.options.pattern;
1749
+ if (!pattern) return null;
1750
+ const triggers = agg.options.write?.maintainedOn ?? [];
1751
+ const source = sourceFromTriggers(triggers) || agg.targetFactory().name;
1752
+ const target = `${ownerName}.${agg.propertyName}`;
1753
+ const update = pattern === "counter" ? "ADD +1 / \u22121 on create / remove" : `project ${source} into ${ownerName}`;
1754
+ return { source, target, pattern, update };
1755
+ }
1756
+ function sourceFromTriggers(triggers) {
1757
+ for (const t of triggers) {
1758
+ const dot = t.indexOf(".");
1759
+ if (dot > 0) return t.slice(0, dot);
1760
+ }
1761
+ return "";
1762
+ }
1763
+ function buildCdcEntries(registry) {
1764
+ const names = [];
1765
+ for (const [cls, meta] of registry.getAll()) {
1766
+ if (meta.cdcProjected) names.push(cls.name);
1767
+ }
1768
+ return names.sort().map((model) => ({ model }));
1769
+ }
1770
+ function buildCfnResources(manifest, streamsEnabled) {
1771
+ const rows = [
1772
+ { resource: "AWS::DynamoDB::Table", description: "Physical table" }
1773
+ ];
1774
+ const gsiCols = collectGsiColumns(manifest);
1775
+ if (gsiCols.length > 0) {
1776
+ rows.push({
1777
+ resource: "GlobalSecondaryIndexes",
1778
+ description: `GSI definitions (${gsiCols.join(", ")})`
1779
+ });
1780
+ }
1781
+ rows.push({
1782
+ resource: "StreamSpecification",
1783
+ description: streamsEnabled === void 0 ? AS_CONFIGURED : streamsEnabled ? "NEW_AND_OLD_IMAGES" : "disabled"
1784
+ });
1785
+ const hasTtl = Object.values(manifest.entities).some((e) => e.ttlAttribute);
1786
+ if (hasTtl) {
1787
+ rows.push({
1788
+ resource: "TimeToLiveSpecification",
1789
+ description: "TTL (when a TTL attribute is declared)"
1790
+ });
1791
+ }
1792
+ rows.push(
1793
+ { resource: "PointInTimeRecovery", description: "PITR" },
1794
+ { resource: "Auto Scaling", description: "Optional (PROVISIONED mode)" }
1795
+ );
1796
+ return rows;
1797
+ }
1798
+ function buildDocModel(manifest, operations, registry = MetadataRegistry, options = {}) {
1799
+ const entityNames = Object.keys(manifest.entities).sort();
1800
+ const table = entityNames.length > 0 ? manifest.entities[entityNames[0]].table : "";
1801
+ const title = options.title ?? `${table} Table Specification`;
1802
+ const cfnFile = options.cfnFile ?? "cloudformation.yaml";
1803
+ const overview = [
1804
+ { item: "Table", value: `\`${table}\`` },
1805
+ {
1806
+ item: "Billing Mode",
1807
+ value: options.billingMode ?? "PAY_PER_REQUEST"
1808
+ },
1809
+ {
1810
+ item: "Streams",
1811
+ value: options.streamsEnabled === void 0 ? AS_CONFIGURED : options.streamsEnabled ? "NEW_AND_OLD_IMAGES" : "Disabled"
1812
+ },
1813
+ {
1814
+ item: "TTL",
1815
+ value: Object.values(manifest.entities).some((e) => e.ttlAttribute) ? "Enabled" : "Disabled"
1816
+ },
1817
+ { item: "PITR", value: AS_CONFIGURED },
1818
+ { item: "Table Class", value: AS_CONFIGURED },
1819
+ { item: "Encryption", value: AS_CONFIGURED },
1820
+ { item: "CloudFormation", value: `\`${cfnFile}\`` }
1821
+ ];
1822
+ const relationEdges = [];
1823
+ for (const name of entityNames) {
1824
+ const entity = manifest.entities[name];
1825
+ for (const propName of Object.keys(entity.relations).sort()) {
1826
+ const rel = entity.relations[propName];
1827
+ relationEdges.push({
1828
+ source: name,
1829
+ target: rel.target,
1830
+ label: rel.type
1831
+ });
1832
+ }
1833
+ }
1834
+ const gsiColumns = collectGsiColumns(manifest);
1835
+ const keySchema = entityNames.map((name) => {
1836
+ const entity = manifest.entities[name];
1837
+ return {
1838
+ model: name,
1839
+ pk: entity.key?.pkTemplate ?? "",
1840
+ sk: entity.key?.skTemplate ?? "",
1841
+ gsis: gsiColumns.map((col) => {
1842
+ const g = gsiByName(entity, col);
1843
+ return { pk: g?.pkTemplate ?? "", sk: g?.skTemplate ?? "" };
1844
+ })
1845
+ };
1846
+ });
1847
+ const propertyOrder = [];
1848
+ const seen = /* @__PURE__ */ new Set();
1849
+ for (const name of entityNames) {
1850
+ const entity = manifest.entities[name];
1851
+ for (const field of Object.keys(entity.fields).sort()) {
1852
+ if (!seen.has(field)) {
1853
+ seen.add(field);
1854
+ propertyOrder.push(field);
1855
+ }
1856
+ }
1857
+ }
1858
+ const propertyMatrix = propertyOrder.map((property) => {
1859
+ let type = "";
1860
+ let description = "";
1861
+ for (const name of entityNames) {
1862
+ const entity = manifest.entities[name];
1863
+ const field = entity.fields[property];
1864
+ if (!field) continue;
1865
+ if (!type) type = fieldTypeLabel(entity, property);
1866
+ if (!description && field.description) description = field.description;
1867
+ }
1868
+ const cells = entityNames.map(
1869
+ (name) => propertyRole(manifest.entities[name], property)
1870
+ );
1871
+ return { property, type, description, cells };
1872
+ });
1873
+ const propertyMatrixHasDescriptions = propertyMatrix.some(
1874
+ (row) => row.description !== ""
1875
+ );
1876
+ const maintainedPaths = buildMaintainedPaths(registry);
1877
+ const entities = entityNames.map((name) => {
1878
+ const entity = manifest.entities[name];
1879
+ const relations = Object.keys(entity.relations).sort().map((propName) => {
1880
+ const rel = entity.relations[propName];
1881
+ return { property: propName, target: rel.target, type: rel.type };
1882
+ });
1883
+ const maintained = maintainedPaths.filter((row) => row.target.startsWith(`${name}.`)).map((row) => ({
1884
+ field: row.target.slice(name.length + 1),
1885
+ pattern: row.pattern
1886
+ }));
1887
+ return {
1888
+ name,
1889
+ description: entity.description ?? "",
1890
+ pk: keyLabel("PK", entity.key?.pkTemplate ?? null),
1891
+ sk: keyLabel("SK", entity.key?.skTemplate ?? null),
1892
+ gsis: entity.gsis.map((g) => ({
1893
+ indexName: g.indexName,
1894
+ pk: g.pkTemplate,
1895
+ sk: g.skTemplate ?? "",
1896
+ unique: g.unique,
1897
+ description: g.description ?? ""
1898
+ })),
1899
+ relations,
1900
+ maintained,
1901
+ fields: Object.keys(entity.fields).sort().map((field) => ({
1902
+ field,
1903
+ type: fieldTypeLabel(entity, field),
1904
+ description: entity.fields[field].description ?? ""
1905
+ })),
1906
+ hasFieldDescriptions: Object.values(entity.fields).some(
1907
+ (f) => (f.description ?? "") !== ""
1908
+ )
1909
+ };
1910
+ });
1911
+ const accessPatterns = [];
1912
+ const accessMatrix = [];
1913
+ for (const name of Object.keys(operations.queries).sort()) {
1914
+ const spec = operations.queries[name];
1915
+ const entityName = rootEntityName(spec, manifest);
1916
+ const { resolve: resolve3, diagram } = buildResolution(spec, manifest);
1917
+ const relation = isRelation(spec);
1918
+ const batchGet = usesBatchGet(spec);
1919
+ const cursor = usesCursor(spec);
1920
+ const index = rootIndex(spec);
1921
+ const signature = `${entityName || "Query"}.${name}(${Object.keys(spec.params).sort().join(", ")})`;
1922
+ accessPatterns.push({
1923
+ signature,
1924
+ description: spec.description ?? "",
1925
+ index,
1926
+ input: inputShape(spec),
1927
+ output: outputShape(spec, entityName),
1928
+ resolve: resolve3,
1929
+ relation,
1930
+ batchGet,
1931
+ cursor,
1932
+ diagram
1933
+ });
1934
+ accessMatrix.push({ query: signature, index, relation, batchGet, cursor });
1935
+ }
1936
+ const mutationContracts = [];
1937
+ for (const name of Object.keys(operations.commands).sort()) {
1938
+ const spec = operations.commands[name];
1939
+ const verb = spec.type === "PutItem" ? "TransactWrite \u2192 " + spec.entity + " item" : spec.type === "UpdateItem" ? "TransactWrite \u2192 " + spec.entity + " item" : "TransactWrite \u2192 delete " + spec.entity + " item";
1940
+ mutationContracts.push({
1941
+ signature: commandSignature(name, spec),
1942
+ description: spec.description ?? "",
1943
+ input: inputShape(spec),
1944
+ output: spec.entity,
1945
+ resolve: verb
1946
+ });
1947
+ }
1948
+ const cdcEntries = buildCdcEntries(registry);
1949
+ const cfnResources = buildCfnResources(manifest, options.streamsEnabled);
1950
+ return {
1951
+ table,
1952
+ title,
1953
+ overview,
1954
+ entityNames,
1955
+ relationEdges,
1956
+ gsiColumns,
1957
+ keySchema,
1958
+ propertyMatrix,
1959
+ propertyMatrixHasDescriptions,
1960
+ entities,
1961
+ accessPatterns,
1962
+ accessMatrix,
1963
+ mutationContracts,
1964
+ maintainedPaths,
1965
+ cdcEntries,
1966
+ cfnResources,
1967
+ cfnFile
1968
+ };
1969
+ }
1970
+
1971
+ // src/codegen/docs.ts
1972
+ import { existsSync, readFileSync } from "fs";
1973
+ import { resolve } from "path";
1974
+ import Handlebars from "handlebars";
1975
+
1976
+ // src/codegen/docs-templates.ts
1977
+ var PARTIAL_NAMES = [
1978
+ "index",
1979
+ "table-overview",
1980
+ "er-diagram",
1981
+ "key-schema",
1982
+ "property-matrix",
1983
+ "entity",
1984
+ "access-pattern",
1985
+ "maintained",
1986
+ "cdc",
1987
+ "cloudformation"
1988
+ ];
1989
+ var INDEX = `# {{title}}
1990
+
1991
+ > Generated by \`graphddb generate docs\`
1992
+
1993
+ ---
1994
+
1995
+ {{> table-overview }}
1996
+
1997
+ ---
1998
+
1999
+ {{> er-diagram }}
2000
+ {{> key-schema }}
2001
+
2002
+ ---
2003
+
2004
+ {{> property-matrix }}
2005
+
2006
+ ---
2007
+
2008
+ ## Entity Details
2009
+
2010
+ {{#each entities}}
2011
+ {{> entity this }}
2012
+ {{/each}}
2013
+ {{#if accessMatrix.length}}---
2014
+
2015
+ ## Access Patterns
2016
+
2017
+ Each query resolves to a physical operation, an index (PK or GSI), and a projection.
2018
+ The matrix summarizes; the contracts below give the input/output and execution for each.
2019
+
2020
+ | Query | Index | Relation | BatchGet | Cursor |
2021
+ |------|------|------|------|------|
2022
+ {{#each accessMatrix}}| \`{{query}}\` | {{index}} | {{#if relation}}\u2713{{/if}} | {{#if batchGet}}\u2713{{/if}} | {{#if cursor}}\u2713{{/if}} |
2023
+ {{/each}}
2024
+
2025
+ {{#each accessPatterns}}
2026
+ {{> access-pattern this }}
2027
+ {{/each}}
2028
+ {{/if}}
2029
+ {{#if mutationContracts.length}}---
2030
+
2031
+ ## Mutation Contracts
2032
+
2033
+ {{#each mutationContracts}}
2034
+ ### \`{{signature}}\`
2035
+ {{#if description}}
2036
+
2037
+ {{description}}
2038
+ {{/if}}
2039
+
2040
+ \`\`\`yaml
2041
+ Input: {{input}}
2042
+ Output: {{output}}
2043
+ Resolve: {{resolve}}
2044
+ \`\`\`
2045
+
2046
+ {{/each}}
2047
+ {{/if}}
2048
+ {{> maintained }}
2049
+ {{> cdc }}
2050
+ ---
2051
+
2052
+ {{> cloudformation }}
2053
+ `;
2054
+ var TABLE_OVERVIEW = `## Table Overview
2055
+
2056
+ | Item | Value |
2057
+ |------|-------|
2058
+ {{#each overview}}| {{item}} | {{value}} |
2059
+ {{/each}}`;
2060
+ var ER_DIAGRAM = `{{#if relationEdges.length}}## Entity Relationship Diagram
2061
+
2062
+ \`\`\`mermaid
2063
+ graph TD
2064
+ {{#each relationEdges}} {{mermaidId source}} -->|{{label}}| {{mermaidId target}}
2065
+ {{/each}}\`\`\`
2066
+
2067
+ ---
2068
+
2069
+ {{/if}}`;
2070
+ var KEY_SCHEMA = `## Physical Key Schema
2071
+
2072
+ | Model | PK | SK |{{#each gsiColumns}} {{this}} PK | {{this}} SK |{{/each}}
2073
+ |------|------|------|{{#each gsiColumns}}------|------|{{/each}}
2074
+ {{#each keySchema}}| {{model}} | {{codeOrEmpty pk}} | {{codeOrEmpty sk}} |{{#each gsis}} {{codeOrEmpty pk}} | {{codeOrEmpty sk}} |{{/each}}
2075
+ {{/each}}`;
2076
+ var PROPERTY_MATRIX = `## Property Matrix
2077
+
2078
+ | Property | Type |{{#if propertyMatrixHasDescriptions}} Description |{{/if}}{{#each entityNames}} {{this}} |{{/each}}
2079
+ |----------|------|{{#if propertyMatrixHasDescriptions}}-------------|{{/if}}{{#each entityNames}}------|{{/each}}
2080
+ {{#each propertyMatrix}}| {{property}} | {{type}} |{{#if ../propertyMatrixHasDescriptions}} {{description}} |{{/if}}{{#each cells}} {{this}} |{{/each}}
2081
+ {{/each}}
2082
+
2083
+ Legend: **PK** primary partition key \xB7 **SK** primary sort key \xB7 GSI keys \xB7 **\u2713** normal attribute`;
2084
+ var ENTITY = `### {{name}}
2085
+ {{#if description}}
2086
+
2087
+ {{description}}
2088
+ {{/if}}
2089
+
2090
+ - **Key**: \`{{pk}}\`{{#if sk}} / \`{{sk}}\`{{/if}}
2091
+ {{#each gsis}}- **{{indexName}}**: \`PK = {{pk}}\`{{#if sk}} / \`SK = {{sk}}\`{{/if}}{{#if unique}} (unique){{/if}}
2092
+ {{/each}}
2093
+ {{#if relations.length}}- **Relations**: {{#each relations}}\`{{property}}\` \u2192 {{target}} ({{type}}){{#unless @last}}, {{/unless}}{{/each}}
2094
+ {{/if}}
2095
+ {{#if maintained.length}}- **Maintained**: {{#each maintained}}\`{{field}}\` ({{pattern}}){{#unless @last}}, {{/unless}}{{/each}}
2096
+ {{/if}}
2097
+
2098
+ | Field | Type |{{#if hasFieldDescriptions}} Description |{{/if}}
2099
+ |------|------|{{#if hasFieldDescriptions}}------|{{/if}}
2100
+ {{#each fields}}| {{field}} | {{type}} |{{#if ../hasFieldDescriptions}} {{description}} |{{/if}}
2101
+ {{/each}}
2102
+ `;
2103
+ var ACCESS_PATTERN = `### \`{{signature}}\`
2104
+ {{#if description}}
2105
+
2106
+ {{description}}
2107
+ {{/if}}
2108
+
2109
+ \`\`\`yaml
2110
+ Input: {{input}}
2111
+ Output: {{output}}
2112
+ Resolve: {{resolve}}
2113
+ \`\`\`
2114
+
2115
+ \`\`\`mermaid
2116
+ flowchart LR
2117
+ {{#each diagram}}{{mermaidId label}}["{{label}}"]{{#unless @last}} {{#if edge}}-- "{{edge}}" {{/if}}--> {{/unless}}{{/each}}
2118
+ \`\`\`
2119
+
2120
+ `;
2121
+ var MAINTAINED = `{{#if maintainedPaths.length}}---
2122
+
2123
+ ## Maintained Access Paths
2124
+
2125
+ Write-side projections kept in sync automatically, within the source write's transaction
2126
+ (or via the CDC stream for \`updateMode: 'stream'\`).
2127
+
2128
+ | Source | Target | Pattern | Update |
2129
+ |---------|--------|--------|--------|
2130
+ {{#each maintainedPaths}}| {{source}} | {{target}} | {{pattern}} | {{update}} |
2131
+ {{/each}}
2132
+
2133
+ \`\`\`mermaid
2134
+ flowchart LR
2135
+ {{#each maintainedPaths}} {{mermaidId source}} -- "{{pattern}}" --> {{mermaidId target}}
2136
+ {{/each}}\`\`\`
2137
+
2138
+ {{/if}}`;
2139
+ var CDC = `{{#if cdcEntries.length}}---
2140
+
2141
+ ## CDC
2142
+
2143
+ \`@cdcProjected\` models expose \`fromChange\` / \`subscribe\` to parse the change stream into
2144
+ typed records. Delivery and idempotency are the consumer's responsibility.
2145
+
2146
+ {{#each cdcEntries}}
2147
+ \`\`\`yaml
2148
+ Contract: {{model}}.subscribe
2149
+ Input: ChangeEvent<{{model}}>
2150
+ Output: [{{model}} | null, {{model}} | null] # [oldRecord, newRecord]
2151
+ \`\`\`
2152
+
2153
+ \`\`\`mermaid
2154
+ flowchart LR
2155
+ Mutate --> Stream["DynamoDB Stream"] --> CE["ChangeEvent"]
2156
+ CE --> FC["{{model}}.fromChange()"] --> Sub["subscribe()"] --> Consumer
2157
+ \`\`\`
2158
+
2159
+ {{/each}}
2160
+ {{/if}}`;
2161
+ var CLOUDFORMATION = `## CloudFormation
2162
+
2163
+ Generated by \`graphddb generate cloudformation\` (see \`{{cfnFile}}\`).
2164
+
2165
+ | Resource | Description |
2166
+ |------|------|
2167
+ {{#each cfnResources}}| \`{{resource}}\` | {{description}} |
2168
+ {{/each}}`;
2169
+ var BUNDLED_PARTIALS = {
2170
+ index: INDEX,
2171
+ "table-overview": TABLE_OVERVIEW,
2172
+ "er-diagram": ER_DIAGRAM,
2173
+ "key-schema": KEY_SCHEMA,
2174
+ "property-matrix": PROPERTY_MATRIX,
2175
+ entity: ENTITY,
2176
+ "access-pattern": ACCESS_PATTERN,
2177
+ maintained: MAINTAINED,
2178
+ cdc: CDC,
2179
+ cloudformation: CLOUDFORMATION
2180
+ };
2181
+
2182
+ // src/codegen/docs.ts
2183
+ var DOCS_INDEX_FILE = "index.md";
2184
+ function registerBuiltinHelpers(hb) {
2185
+ hb.registerHelper("eq", (a, b) => a === b);
2186
+ hb.registerHelper(
2187
+ "join",
2188
+ (list, sep) => Array.isArray(list) ? list.join(typeof sep === "string" ? sep : ", ") : ""
2189
+ );
2190
+ hb.registerHelper("pascal", (value) => {
2191
+ const s = String(value ?? "");
2192
+ return s.split(/[^A-Za-z0-9]+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
2193
+ });
2194
+ hb.registerHelper("mermaidId", (value) => {
2195
+ const s = String(value ?? "");
2196
+ const id = s.replace(/[^A-Za-z0-9_]/g, "_").replace(/_+/g, "_");
2197
+ return /^[0-9]/.test(id) ? `n_${id}` : id || "n";
2198
+ });
2199
+ hb.registerHelper("codeOrEmpty", (value) => {
2200
+ const s = String(value ?? "");
2201
+ return s ? new hb.SafeString(`\`${s}\``) : "";
2202
+ });
2203
+ }
2204
+ function partialSource(name, templateDir) {
2205
+ if (templateDir) {
2206
+ const candidate = resolve(templateDir, `${name}.hbs`);
2207
+ if (existsSync(candidate)) {
2208
+ return readFileSync(candidate, "utf8");
2209
+ }
2210
+ }
2211
+ return BUNDLED_PARTIALS[name];
2212
+ }
2213
+ function buildEnvironment(options) {
2214
+ const hb = Handlebars.create();
2215
+ registerBuiltinHelpers(hb);
2216
+ if (options.helpers) {
2217
+ for (const [name, fn] of Object.entries(options.helpers)) {
2218
+ hb.registerHelper(name, fn);
2219
+ }
2220
+ }
2221
+ for (const name of PARTIAL_NAMES) {
2222
+ hb.registerPartial(
2223
+ name,
2224
+ hb.compile(partialSource(name, options.templateDir), { noEscape: true })
2225
+ );
2226
+ }
2227
+ return hb;
2228
+ }
2229
+ function generateDocs(docModel, options = {}) {
2230
+ const hb = buildEnvironment(options);
2231
+ const index = hb.partials["index"];
2232
+ const template = typeof index === "function" ? index : hb.compile(index, { noEscape: true });
2233
+ let out = template(docModel);
2234
+ out = out.replace(/([^\n])\n(---\n)/g, "$1\n\n$2").replace(/(\n---)\n([^\n])/g, "$1\n\n$2").replace(/([^\n])\n(#{1,6} )/g, "$1\n\n$2").replace(/\n{3,}/g, "\n\n").replace(/\s*$/, "") + "\n";
2235
+ return { [DOCS_INDEX_FILE]: out };
2236
+ }
2237
+
1466
2238
  // src/codegen/index.ts
1467
2239
  var OUTPUT_FILES = [
1468
2240
  "manifest.json",
@@ -1498,7 +2270,7 @@ function renderBundle(queries = {}, commands = {}, registry = MetadataRegistry,
1498
2270
  // src/codegen/jsdoc.ts
1499
2271
  async function loadTypescript() {
1500
2272
  try {
1501
- const mod = await import("./typescript-ZUQEBJRV.js");
2273
+ const mod = await import("typescript");
1502
2274
  return mod.default ?? mod;
1503
2275
  } catch {
1504
2276
  return void 0;
@@ -1727,7 +2499,7 @@ function fail(code, message, exitCode) {
1727
2499
  process.exit(exitCode);
1728
2500
  }
1729
2501
  function toUrl(file) {
1730
- const abs = isAbsolute(file) ? file : resolve(process.cwd(), file);
2502
+ const abs = isAbsolute(file) ? file : resolve2(process.cwd(), file);
1731
2503
  return pathToFileURL(abs).href;
1732
2504
  }
1733
2505
  var tsxRegistered = false;
@@ -1749,6 +2521,12 @@ async function importModule(file) {
1749
2521
  if (/\.[mc]?ts$/.test(file)) await ensureTsx();
1750
2522
  return await import(toUrl(file));
1751
2523
  }
2524
+ async function importModelEntry(entry) {
2525
+ await importModule(entry);
2526
+ if (MetadataRegistry.linter === null) {
2527
+ MetadataRegistry.linter = createDefaultLinter();
2528
+ }
2529
+ }
1752
2530
  async function loadDefinitionMap(file, exportName) {
1753
2531
  const mod = await importModule(file);
1754
2532
  const value = mod[exportName];
@@ -1782,7 +2560,7 @@ var handlers = {
1782
2560
  if (!entry) fail("INVALID_ARGS", "Missing required option --entry.", 2);
1783
2561
  if (!out) fail("INVALID_ARGS", "Missing required option --out.", 2);
1784
2562
  try {
1785
- await importModule(entry);
2563
+ await importModelEntry(entry);
1786
2564
  const queries = await loadDefinitionMap(queriesPath ?? entry, "queries");
1787
2565
  const commands = await loadDefinitionMap(commandsPath ?? entry, "commands");
1788
2566
  const transactions = await loadOptionalMap(
@@ -1797,7 +2575,7 @@ var handlers = {
1797
2575
  contextsPath ?? contractsPath ?? entry,
1798
2576
  "contexts"
1799
2577
  );
1800
- const abs = (p) => isAbsolute(p) ? p : resolve(process.cwd(), p);
2578
+ const abs = (p) => isAbsolute(p) ? p : resolve2(process.cwd(), p);
1801
2579
  await applyJsDocDescriptions(
1802
2580
  {
1803
2581
  entry: abs(entry),
@@ -1812,19 +2590,19 @@ var handlers = {
1812
2590
  contracts: { contracts, contexts }
1813
2591
  });
1814
2592
  void dataclass;
1815
- const outDir = isAbsolute(out) ? out : resolve(process.cwd(), out);
2593
+ const outDir = isAbsolute(out) ? out : resolve2(process.cwd(), out);
1816
2594
  mkdirSync(outDir, { recursive: true });
1817
2595
  for (const name of OUTPUT_FILES) {
1818
- writeFileSync(resolve(outDir, name), files[name], "utf8");
2596
+ writeFileSync(resolve2(outDir, name), files[name], "utf8");
1819
2597
  }
1820
2598
  const emittedFiles = [...OUTPUT_FILES];
1821
2599
  if (cdcProjectedModelNames().length > 0) {
1822
- const entryAbs = isAbsolute(entry) ? entry : resolve(process.cwd(), entry);
2600
+ const entryAbs = isAbsolute(entry) ? entry : resolve2(process.cwd(), entry);
1823
2601
  let modelsModule = relative(outDir, entryAbs).replace(/\\/g, "/").replace(/\.[mc]?ts$/, "");
1824
2602
  if (!modelsModule.startsWith(".")) modelsModule = `./${modelsModule}`;
1825
2603
  const cdcRegistryFile = "cdc-registry.ts";
1826
2604
  writeFileSync(
1827
- resolve(outDir, cdcRegistryFile),
2605
+ resolve2(outDir, cdcRegistryFile),
1828
2606
  generateCdcRegistry({ modelsModule }),
1829
2607
  "utf8"
1830
2608
  );
@@ -1978,7 +2756,7 @@ var handlers = {
1978
2756
  tblClass = tableClass;
1979
2757
  }
1980
2758
  try {
1981
- await importModule(entry);
2759
+ await importModelEntry(entry);
1982
2760
  const manifest = buildManifest();
1983
2761
  const streamMaintenanceDetected = streamMode === "auto" ? detectStreamMaintenance() : false;
1984
2762
  const template = emitCloudformation(manifest, {
@@ -2001,7 +2779,7 @@ var handlers = {
2001
2779
  autoscaleTarget: autoscaleTargetNum
2002
2780
  });
2003
2781
  if (out) {
2004
- const outFile = isAbsolute(out) ? out : resolve(process.cwd(), out);
2782
+ const outFile = isAbsolute(out) ? out : resolve2(process.cwd(), out);
2005
2783
  mkdirSync(dirname(outFile), { recursive: true });
2006
2784
  writeFileSync(outFile, template, "utf8");
2007
2785
  process.stdout.write(
@@ -2018,6 +2796,131 @@ var handlers = {
2018
2796
  const message = err instanceof Error ? err.message : String(err);
2019
2797
  fail("GENERATION_FAILED", message, 1);
2020
2798
  }
2799
+ },
2800
+ /**
2801
+ * `generate docs` (issue #183).
2802
+ *
2803
+ * Loads the entry model module (side-effecting import registers the entities)
2804
+ * plus the optional query / command definitions, builds the serializable
2805
+ * manifest + operations, then the normalized {@link buildDocModel} view model,
2806
+ * and renders the model-spec Markdown (Mermaid fences) via the Handlebars
2807
+ * generator to a single `index.md` in `--out`. `--template-dir` overrides
2808
+ * bundled partials per-name and `--helpers` registers extra Handlebars helpers.
2809
+ * Mirrors `generatePython`'s tsx-based entry loading and JSON error / exit shape.
2810
+ *
2811
+ * The Table Overview billing / streams are resolved from the CFn-shared flags
2812
+ * (`--billing-mode` / `--stream`, `stream auto` detecting `updateMode:'stream'`
2813
+ * maintenance like the CFn generator); deploy-time-only items with no flag
2814
+ * render "as configured at deployment" in the DocModel.
2815
+ */
2816
+ async generateDocs(options) {
2817
+ const {
2818
+ entry,
2819
+ queries: queriesPath,
2820
+ out,
2821
+ format,
2822
+ split,
2823
+ templateDir,
2824
+ helpers: helpersPath,
2825
+ billingMode,
2826
+ stream
2827
+ } = options;
2828
+ if (!entry) fail("INVALID_ARGS", "Missing required option --entry.", 2);
2829
+ if (!out) fail("INVALID_ARGS", "Missing required option --out.", 2);
2830
+ if (format !== void 0 && format !== "markdown") {
2831
+ fail("INVALID_ARGS", `Invalid --format '${format}'. Use markdown.`, 2);
2832
+ }
2833
+ if (split !== void 0 && split !== "none" && split !== "entity") {
2834
+ fail("INVALID_ARGS", `Invalid --split '${split}'. Use none or entity.`, 2);
2835
+ }
2836
+ const outIsFile = out.toLowerCase().endsWith(".md");
2837
+ if (split === "entity" && outIsFile) {
2838
+ fail(
2839
+ "INVALID_ARGS",
2840
+ `--split entity writes multiple files and needs a directory --out, not a .md file ('${out}').`,
2841
+ 2
2842
+ );
2843
+ }
2844
+ if (split === "entity") {
2845
+ fail(
2846
+ "INVALID_ARGS",
2847
+ `--split entity is not yet supported. Use --split none (a single index.md).`,
2848
+ 2
2849
+ );
2850
+ }
2851
+ let billing = "PAY_PER_REQUEST";
2852
+ if (billingMode !== void 0) {
2853
+ if (billingMode !== "PAY_PER_REQUEST" && billingMode !== "PROVISIONED") {
2854
+ fail(
2855
+ "INVALID_ARGS",
2856
+ `Invalid --billing-mode '${billingMode}'. Use PAY_PER_REQUEST or PROVISIONED.`,
2857
+ 2
2858
+ );
2859
+ }
2860
+ billing = billingMode;
2861
+ }
2862
+ let streamMode = "auto";
2863
+ if (stream !== void 0) {
2864
+ if (stream !== "auto" && stream !== "on" && stream !== "off") {
2865
+ fail("INVALID_ARGS", `Invalid --stream '${stream}'. Use auto, on, or off.`, 2);
2866
+ }
2867
+ streamMode = stream;
2868
+ }
2869
+ try {
2870
+ await importModelEntry(entry);
2871
+ const queries = await loadOptionalMap(queriesPath ?? entry, "queries");
2872
+ const commands = await loadOptionalMap(queriesPath ?? entry, "commands");
2873
+ const manifest = buildManifest();
2874
+ const operations = buildOperations(
2875
+ queries,
2876
+ commands
2877
+ );
2878
+ const streamsEnabled = streamMode === "on" ? true : streamMode === "off" ? false : detectStreamMaintenance();
2879
+ const docModel = buildDocModel(manifest, operations, MetadataRegistry, {
2880
+ billingMode: billing,
2881
+ streamsEnabled
2882
+ });
2883
+ let helpers;
2884
+ if (helpersPath) {
2885
+ const mod = await importModule(helpersPath);
2886
+ const exported = mod.helpers ?? mod.default;
2887
+ if (!exported || typeof exported !== "object") {
2888
+ fail(
2889
+ "INVALID_ARGS",
2890
+ `Module '${helpersPath}' must export a 'helpers' (or default) object mapping helper names to functions.`,
2891
+ 2
2892
+ );
2893
+ }
2894
+ helpers = exported;
2895
+ }
2896
+ const files = generateDocs(docModel, {
2897
+ templateDir: templateDir ? isAbsolute(templateDir) ? templateDir : resolve2(process.cwd(), templateDir) : void 0,
2898
+ helpers
2899
+ });
2900
+ const outAbs = isAbsolute(out) ? out : resolve2(process.cwd(), out);
2901
+ const emitted = [];
2902
+ if (outIsFile) {
2903
+ const [contents] = Object.values(files);
2904
+ mkdirSync(dirname(outAbs), { recursive: true });
2905
+ writeFileSync(outAbs, contents, "utf8");
2906
+ emitted.push(out);
2907
+ process.stdout.write(
2908
+ JSON.stringify({ status: "ok", outDir: dirname(out), files: emitted }) + "\n"
2909
+ );
2910
+ } else {
2911
+ mkdirSync(outAbs, { recursive: true });
2912
+ for (const [name, contents] of Object.entries(files)) {
2913
+ writeFileSync(resolve2(outAbs, name), contents, "utf8");
2914
+ emitted.push(name);
2915
+ }
2916
+ process.stdout.write(
2917
+ JSON.stringify({ status: "ok", outDir: out, files: emitted }) + "\n"
2918
+ );
2919
+ }
2920
+ } catch (err) {
2921
+ const message = err instanceof Error ? err.message : String(err);
2922
+ fail("GENERATION_FAILED", message, 1);
2923
+ }
2021
2924
  }
2022
2925
  };
2023
2926