graphddb 0.5.1 → 0.5.3
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/README.md +10 -0
- package/dist/cdc/index.d.ts +37 -0
- package/dist/cdc/index.js +29 -0
- package/dist/{chunk-B3GWIWT6.js → chunk-CCIVET5K.js} +74 -1026
- package/dist/{chunk-EMJHTUA4.js → chunk-M6URQOAW.js} +4 -1
- package/dist/chunk-MMVHOUM4.js +24 -0
- package/dist/chunk-PDUVTYC5.js +992 -0
- package/dist/{chunk-FI63YKK5.js → chunk-Y7XV5QL2.js} +3564 -3549
- package/dist/cli.js +1174 -21
- package/dist/from-change-DQK2Jm9R.d.ts +327 -0
- package/dist/index-CtDBo8Se.d.ts +690 -0
- package/dist/index.d.ts +18 -1113
- package/dist/index.js +33 -41
- package/dist/linter/index.d.ts +126 -0
- package/dist/linter/index.js +36 -0
- package/dist/{types-B9rJ1z3H.d.ts → maintenance-view-adapter-D5t9taTE.d.ts} +304 -182
- package/dist/registry-BD_5Rm5C.d.ts +76 -0
- package/dist/relation-depth-DLkhG0xX.d.ts +36 -0
- package/dist/spec/index.d.ts +4 -0
- package/dist/spec/index.js +54 -0
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +5 -3
- package/docs/doc-sample.md +263 -0
- package/docs/docs-generation.md +183 -0
- package/package.json +49 -3
package/dist/cli.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
buildBridgeBundle,
|
|
4
|
-
buildManifest
|
|
5
|
-
|
|
4
|
+
buildManifest,
|
|
5
|
+
buildOperations
|
|
6
|
+
} from "./chunk-Y7XV5QL2.js";
|
|
6
7
|
import {
|
|
7
8
|
MetadataRegistry,
|
|
8
9
|
normalizeSelectSpec
|
|
9
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-CCIVET5K.js";
|
|
11
|
+
import {
|
|
12
|
+
createDefaultLinter
|
|
13
|
+
} from "./chunk-PDUVTYC5.js";
|
|
10
14
|
|
|
11
15
|
// src/cli.ts
|
|
12
16
|
import { createRequire } from "module";
|
|
@@ -440,6 +444,105 @@ var commandDefinitions = {
|
|
|
440
444
|
}
|
|
441
445
|
}
|
|
442
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
|
+
]
|
|
443
546
|
}
|
|
444
547
|
};
|
|
445
548
|
function deriveCommandPolicy(command_id, optionValues) {
|
|
@@ -466,8 +569,8 @@ function deriveCommandPolicy(command_id, optionValues) {
|
|
|
466
569
|
}
|
|
467
570
|
|
|
468
571
|
// src/generated/contract.ts
|
|
469
|
-
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";
|
|
470
|
-
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}';
|
|
471
574
|
|
|
472
575
|
// src/generated/program.ts
|
|
473
576
|
function createProgram(handlers2, version) {
|
|
@@ -493,6 +596,15 @@ function createProgram(handlers2, version) {
|
|
|
493
596
|
}
|
|
494
597
|
await handlers2.generateCloudformation(opts, globalOpts);
|
|
495
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
|
+
});
|
|
496
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) => {
|
|
497
609
|
if (commands.length === 0 && !opts.all) {
|
|
498
610
|
process.stderr.write(JSON.stringify({ code: "INVALID_ARGS", message: "Specify command IDs or use --all" }) + "\n");
|
|
@@ -510,7 +622,7 @@ function createProgram(handlers2, version) {
|
|
|
510
622
|
type: "cli-contracts/extract",
|
|
511
623
|
extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
512
624
|
specVersion: doc.cli_contracts ?? "0.1.0",
|
|
513
|
-
commands: ["graphddb.generate.python", "graphddb.generate.cloudformation"]
|
|
625
|
+
commands: ["graphddb.generate.python", "graphddb.generate.cloudformation", "graphddb.generate.docs"]
|
|
514
626
|
};
|
|
515
627
|
}
|
|
516
628
|
Object.assign(out, doc);
|
|
@@ -527,7 +639,7 @@ function createProgram(handlers2, version) {
|
|
|
527
639
|
yamlLines.push("extractedAt: " + (/* @__PURE__ */ new Date()).toISOString());
|
|
528
640
|
yamlLines.push("spec_version: " + (doc.cli_contracts ?? "0.1.0"));
|
|
529
641
|
yamlLines.push("commands:");
|
|
530
|
-
for (const id of ["graphddb.generate.python", "graphddb.generate.cloudformation"]) {
|
|
642
|
+
for (const id of ["graphddb.generate.python", "graphddb.generate.cloudformation", "graphddb.generate.docs"]) {
|
|
531
643
|
yamlLines.push(" - " + id);
|
|
532
644
|
}
|
|
533
645
|
}
|
|
@@ -570,7 +682,7 @@ function createProgram(handlers2, version) {
|
|
|
570
682
|
import { mkdirSync, writeFileSync } from "fs";
|
|
571
683
|
import { dirname, relative } from "path";
|
|
572
684
|
import { pathToFileURL } from "url";
|
|
573
|
-
import { isAbsolute, resolve } from "path";
|
|
685
|
+
import { isAbsolute, resolve as resolve2 } from "path";
|
|
574
686
|
|
|
575
687
|
// src/codegen/python.ts
|
|
576
688
|
var PY_HEADER = "# DO NOT EDIT. Generated by GraphDDB.";
|
|
@@ -659,6 +771,9 @@ function collectResultTypes(manifest, entity, select, typeName, out, seen) {
|
|
|
659
771
|
} else {
|
|
660
772
|
fields.push([field, `${itemTypeName} | None`]);
|
|
661
773
|
}
|
|
774
|
+
if (relation.description !== void 0) {
|
|
775
|
+
fieldDescriptions[field] = relation.description;
|
|
776
|
+
}
|
|
662
777
|
continue;
|
|
663
778
|
}
|
|
664
779
|
if (value === true) {
|
|
@@ -790,7 +905,15 @@ function methodParams(def) {
|
|
|
790
905
|
}
|
|
791
906
|
return out;
|
|
792
907
|
}
|
|
793
|
-
function
|
|
908
|
+
function gsiDescriptionForQuery(name, entity, manifest, querySpecs) {
|
|
909
|
+
if (!manifest || !querySpecs) return void 0;
|
|
910
|
+
const spec = querySpecs[name];
|
|
911
|
+
const indexName = spec?.operations[0]?.indexName;
|
|
912
|
+
if (indexName === void 0) return void 0;
|
|
913
|
+
const gsi = manifest.entities[entity]?.gsis.find((g) => g.indexName === indexName);
|
|
914
|
+
return gsi?.description;
|
|
915
|
+
}
|
|
916
|
+
function collectRepositories(queries, commands, manifest, querySpecs) {
|
|
794
917
|
const byEntity = /* @__PURE__ */ new Map();
|
|
795
918
|
const ensure = (entity) => {
|
|
796
919
|
const cls = `${entityBaseName(entity)}Repository`;
|
|
@@ -803,13 +926,14 @@ function collectRepositories(queries, commands) {
|
|
|
803
926
|
};
|
|
804
927
|
for (const name of Object.keys(queries).sort()) {
|
|
805
928
|
const def = queries[name];
|
|
929
|
+
const description = def.description ?? gsiDescriptionForQuery(name, def.entity.name, manifest, querySpecs);
|
|
806
930
|
ensure(def.entity.name).push({
|
|
807
931
|
methodName: toSnakeCase(name),
|
|
808
932
|
params: methodParams(def),
|
|
809
933
|
returnType: `${queryResultTypeName(name)} | None`,
|
|
810
934
|
kind: "query",
|
|
811
935
|
operationId: name,
|
|
812
|
-
...
|
|
936
|
+
...description !== void 0 ? { description } : {}
|
|
813
937
|
});
|
|
814
938
|
}
|
|
815
939
|
for (const name of Object.keys(commands).sort()) {
|
|
@@ -914,8 +1038,8 @@ function renderTransactionsClass(methods) {
|
|
|
914
1038
|
body.join("\n\n")
|
|
915
1039
|
].join("\n");
|
|
916
1040
|
}
|
|
917
|
-
function generateRepositories(queries, commands, transactions = {}) {
|
|
918
|
-
const classes = collectRepositories(queries, commands);
|
|
1041
|
+
function generateRepositories(queries, commands, transactions = {}, manifest, querySpecs) {
|
|
1042
|
+
const classes = collectRepositories(queries, commands, manifest, querySpecs);
|
|
919
1043
|
const txMethods = collectTransactionMethods(transactions);
|
|
920
1044
|
const resultTypes = /* @__PURE__ */ new Set();
|
|
921
1045
|
for (const name of Object.keys(queries)) {
|
|
@@ -1450,6 +1574,662 @@ function generateCdcRegistry(options = {}) {
|
|
|
1450
1574
|
return lines.join("\n");
|
|
1451
1575
|
}
|
|
1452
1576
|
|
|
1577
|
+
// src/codegen/docs-model.ts
|
|
1578
|
+
var AS_CONFIGURED = "as configured at deployment";
|
|
1579
|
+
function fieldTypeLabel(entity, field) {
|
|
1580
|
+
const f = entity.fields[field];
|
|
1581
|
+
if (!f) return "";
|
|
1582
|
+
if (f.format === "datetime") return "datetime";
|
|
1583
|
+
if (f.format === "date") return "date";
|
|
1584
|
+
return f.type;
|
|
1585
|
+
}
|
|
1586
|
+
function collectGsiColumns(manifest) {
|
|
1587
|
+
const names = /* @__PURE__ */ new Set();
|
|
1588
|
+
for (const entity of Object.values(manifest.entities)) {
|
|
1589
|
+
for (const gsi of entity.gsis) names.add(gsi.indexName);
|
|
1590
|
+
}
|
|
1591
|
+
return [...names].sort();
|
|
1592
|
+
}
|
|
1593
|
+
function gsiByName(entity, indexName) {
|
|
1594
|
+
return entity.gsis.find((g) => g.indexName === indexName);
|
|
1595
|
+
}
|
|
1596
|
+
function propertyRole(entity, property) {
|
|
1597
|
+
const key = entity.key;
|
|
1598
|
+
if (key) {
|
|
1599
|
+
if (referencesField(key.pkTemplate, property)) return "PK";
|
|
1600
|
+
if (key.skTemplate && referencesField(key.skTemplate, property)) return "SK";
|
|
1601
|
+
}
|
|
1602
|
+
for (const gsi of entity.gsis) {
|
|
1603
|
+
if (referencesField(gsi.pkTemplate, property)) return `${gsi.indexName} PK`;
|
|
1604
|
+
if (gsi.skTemplate && referencesField(gsi.skTemplate, property)) {
|
|
1605
|
+
return `${gsi.indexName} SK`;
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
if (entity.fields[property]) return "\u2713";
|
|
1609
|
+
return "";
|
|
1610
|
+
}
|
|
1611
|
+
function referencesField(template, field) {
|
|
1612
|
+
return template.includes(`{${field}}`);
|
|
1613
|
+
}
|
|
1614
|
+
function keyLabel(prefix, template) {
|
|
1615
|
+
return template ? `${prefix} = ${template}` : "";
|
|
1616
|
+
}
|
|
1617
|
+
function rootIndex(spec) {
|
|
1618
|
+
const root = spec.operations[0];
|
|
1619
|
+
if (!root) return "";
|
|
1620
|
+
return root.indexName ?? "PK";
|
|
1621
|
+
}
|
|
1622
|
+
function isRelation(spec) {
|
|
1623
|
+
return spec.operations.length > 1;
|
|
1624
|
+
}
|
|
1625
|
+
function usesBatchGet(spec) {
|
|
1626
|
+
return spec.operations.some((op) => op.type === "BatchGetItem");
|
|
1627
|
+
}
|
|
1628
|
+
function usesCursor(spec) {
|
|
1629
|
+
return (spec.cardinality ?? "many") === "many";
|
|
1630
|
+
}
|
|
1631
|
+
function inputShape(spec) {
|
|
1632
|
+
const parts = [];
|
|
1633
|
+
for (const name of Object.keys(spec.params).sort()) {
|
|
1634
|
+
const p = spec.params[name];
|
|
1635
|
+
const opt = p.required ? "" : "?";
|
|
1636
|
+
parts.push(`${name}${opt}: ${p.type}`);
|
|
1637
|
+
}
|
|
1638
|
+
if ("cardinality" in spec && (spec.cardinality ?? "many") === "many") {
|
|
1639
|
+
parts.push("limit?: number", "cursor?: string");
|
|
1640
|
+
}
|
|
1641
|
+
return `{ ${parts.join(", ")} }`;
|
|
1642
|
+
}
|
|
1643
|
+
function opPkTemplate(op) {
|
|
1644
|
+
if (op.indexName) {
|
|
1645
|
+
return op.keyCondition[`${op.indexName}PK`] ?? op.keyCondition["PK"] ?? op.keyCondition["pk"] ?? "";
|
|
1646
|
+
}
|
|
1647
|
+
return op.keyCondition["PK"] ?? op.keyCondition["pk"] ?? "";
|
|
1648
|
+
}
|
|
1649
|
+
function rootEntityName(spec, manifest) {
|
|
1650
|
+
const root = spec.operations[0];
|
|
1651
|
+
if (!root) return "";
|
|
1652
|
+
const pk = opPkTemplate(root);
|
|
1653
|
+
for (const [name, entity] of Object.entries(manifest.entities)) {
|
|
1654
|
+
const cand = root.indexName ? gsiByName(entity, root.indexName)?.pkTemplate : entity.key?.pkTemplate;
|
|
1655
|
+
if (cand && pk && cand === pk) return name;
|
|
1656
|
+
}
|
|
1657
|
+
return "";
|
|
1658
|
+
}
|
|
1659
|
+
function outputShape(spec, entityName) {
|
|
1660
|
+
const many = (spec.cardinality ?? "many") === "many";
|
|
1661
|
+
const base = entityName || "Result";
|
|
1662
|
+
return many ? `Page<${base}>` : base;
|
|
1663
|
+
}
|
|
1664
|
+
function buildResolution(spec, manifest) {
|
|
1665
|
+
const root = spec.operations[0];
|
|
1666
|
+
if (!root) return { resolve: "", diagram: [] };
|
|
1667
|
+
if (spec.operations.length === 1) {
|
|
1668
|
+
const keyLabelStr = rootKeyLabel(root);
|
|
1669
|
+
const proj = root.projection.length > 0 ? `Projection(${root.projection.slice(0, 4).join(", ")})` : "Projection";
|
|
1670
|
+
const resolve3 = `${keyLabelStr} \u2192 ${root.type} \u2192 ${proj}`;
|
|
1671
|
+
const inParam = Object.keys(spec.params).sort()[0] ?? "key";
|
|
1672
|
+
const outEntity = rootEntityName(spec, manifest) || "Result";
|
|
1673
|
+
const diagram2 = [
|
|
1674
|
+
{ label: inParam, edge: "" },
|
|
1675
|
+
{ label: keyLabelStr, edge: "" },
|
|
1676
|
+
{ label: root.type, edge: "" },
|
|
1677
|
+
{ label: outEntity, edge: "" }
|
|
1678
|
+
];
|
|
1679
|
+
return { resolve: resolve3, diagram: diagram2 };
|
|
1680
|
+
}
|
|
1681
|
+
const entityHops = [];
|
|
1682
|
+
const diagram = [];
|
|
1683
|
+
for (let i = 0; i < spec.operations.length; i++) {
|
|
1684
|
+
const op = spec.operations[i];
|
|
1685
|
+
const entity = opEntityName(op, manifest) || op.tableName;
|
|
1686
|
+
entityHops.push(`${op.type}(${entity}${op.indexName ? ` by ${op.indexName}` : ""})`);
|
|
1687
|
+
const edge = i < spec.operations.length - 1 ? spec.operations[i + 1].type === "BatchGetItem" ? "BatchGet" : op.indexName ?? "PK" : "";
|
|
1688
|
+
diagram.push({ label: entity, edge });
|
|
1689
|
+
}
|
|
1690
|
+
return { resolve: entityHops.join(" \u2192 "), diagram };
|
|
1691
|
+
}
|
|
1692
|
+
function rootKeyLabel(op) {
|
|
1693
|
+
const pk = opPkTemplate(op);
|
|
1694
|
+
const prefix = op.indexName ? `${op.indexName} PK` : "PK";
|
|
1695
|
+
return `${prefix} = ${pk}`;
|
|
1696
|
+
}
|
|
1697
|
+
function opEntityName(op, manifest) {
|
|
1698
|
+
const pk = opPkTemplate(op);
|
|
1699
|
+
for (const [name, entity] of Object.entries(manifest.entities)) {
|
|
1700
|
+
const cand = op.indexName ? gsiByName(entity, op.indexName)?.pkTemplate : entity.key?.pkTemplate;
|
|
1701
|
+
if (cand && pk) {
|
|
1702
|
+
if (literalPrefix(cand) === literalPrefix(pk)) return name;
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
return "";
|
|
1706
|
+
}
|
|
1707
|
+
function literalPrefix(template) {
|
|
1708
|
+
const idx = template.indexOf("{");
|
|
1709
|
+
return idx === -1 ? template : template.slice(0, idx);
|
|
1710
|
+
}
|
|
1711
|
+
function commandSignature(_name, spec) {
|
|
1712
|
+
const verb = spec.type === "PutItem" ? "create" : spec.type === "UpdateItem" ? "update" : "delete";
|
|
1713
|
+
return `${spec.entity}.${verb}()`;
|
|
1714
|
+
}
|
|
1715
|
+
function buildMaintainedPaths(registry) {
|
|
1716
|
+
const rows = [];
|
|
1717
|
+
const entities = registry.getAll();
|
|
1718
|
+
const byName = /* @__PURE__ */ new Map();
|
|
1719
|
+
for (const [cls, meta] of entities) byName.set(cls.name, meta);
|
|
1720
|
+
for (const name of [...byName.keys()].sort()) {
|
|
1721
|
+
const meta = byName.get(name);
|
|
1722
|
+
for (const rel of meta.relations) {
|
|
1723
|
+
const row = maintainedRowForRelation(name, rel);
|
|
1724
|
+
if (row) rows.push(row);
|
|
1725
|
+
}
|
|
1726
|
+
for (const agg of meta.aggregates) {
|
|
1727
|
+
const row = maintainedRowForAggregate(name, agg);
|
|
1728
|
+
if (row) rows.push(row);
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
return rows;
|
|
1732
|
+
}
|
|
1733
|
+
function maintainedRowForRelation(ownerName, rel) {
|
|
1734
|
+
const pattern = rel.options?.pattern;
|
|
1735
|
+
if (!pattern) return null;
|
|
1736
|
+
const triggers = rel.options?.write?.maintainedOn ?? [];
|
|
1737
|
+
const source = sourceFromTriggers(triggers) || rel.targetFactory().name;
|
|
1738
|
+
const target = `${ownerName}.${rel.propertyName}`;
|
|
1739
|
+
const update = pattern === "counter" ? "ADD +1 / \u22121 on create / remove" : `project ${source} fields into ${ownerName}`;
|
|
1740
|
+
return { source, target, pattern, update };
|
|
1741
|
+
}
|
|
1742
|
+
function maintainedRowForAggregate(ownerName, agg) {
|
|
1743
|
+
const pattern = agg.options.pattern;
|
|
1744
|
+
if (!pattern) return null;
|
|
1745
|
+
const triggers = agg.options.write?.maintainedOn ?? [];
|
|
1746
|
+
const source = sourceFromTriggers(triggers) || agg.targetFactory().name;
|
|
1747
|
+
const target = `${ownerName}.${agg.propertyName}`;
|
|
1748
|
+
const update = pattern === "counter" ? "ADD +1 / \u22121 on create / remove" : `project ${source} into ${ownerName}`;
|
|
1749
|
+
return { source, target, pattern, update };
|
|
1750
|
+
}
|
|
1751
|
+
function sourceFromTriggers(triggers) {
|
|
1752
|
+
for (const t of triggers) {
|
|
1753
|
+
const dot = t.indexOf(".");
|
|
1754
|
+
if (dot > 0) return t.slice(0, dot);
|
|
1755
|
+
}
|
|
1756
|
+
return "";
|
|
1757
|
+
}
|
|
1758
|
+
function buildCdcEntries(registry) {
|
|
1759
|
+
const names = [];
|
|
1760
|
+
for (const [cls, meta] of registry.getAll()) {
|
|
1761
|
+
if (meta.cdcProjected) names.push(cls.name);
|
|
1762
|
+
}
|
|
1763
|
+
return names.sort().map((model) => ({ model }));
|
|
1764
|
+
}
|
|
1765
|
+
function buildCfnResources(manifest, streamsEnabled) {
|
|
1766
|
+
const rows = [
|
|
1767
|
+
{ resource: "AWS::DynamoDB::Table", description: "Physical table" }
|
|
1768
|
+
];
|
|
1769
|
+
const gsiCols = collectGsiColumns(manifest);
|
|
1770
|
+
if (gsiCols.length > 0) {
|
|
1771
|
+
rows.push({
|
|
1772
|
+
resource: "GlobalSecondaryIndexes",
|
|
1773
|
+
description: `GSI definitions (${gsiCols.join(", ")})`
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1776
|
+
rows.push({
|
|
1777
|
+
resource: "StreamSpecification",
|
|
1778
|
+
description: streamsEnabled === void 0 ? AS_CONFIGURED : streamsEnabled ? "NEW_AND_OLD_IMAGES" : "disabled"
|
|
1779
|
+
});
|
|
1780
|
+
const hasTtl = Object.values(manifest.entities).some((e) => e.ttlAttribute);
|
|
1781
|
+
if (hasTtl) {
|
|
1782
|
+
rows.push({
|
|
1783
|
+
resource: "TimeToLiveSpecification",
|
|
1784
|
+
description: "TTL (when a TTL attribute is declared)"
|
|
1785
|
+
});
|
|
1786
|
+
}
|
|
1787
|
+
rows.push(
|
|
1788
|
+
{ resource: "PointInTimeRecovery", description: "PITR" },
|
|
1789
|
+
{ resource: "Auto Scaling", description: "Optional (PROVISIONED mode)" }
|
|
1790
|
+
);
|
|
1791
|
+
return rows;
|
|
1792
|
+
}
|
|
1793
|
+
function buildDocModel(manifest, operations, registry = MetadataRegistry, options = {}) {
|
|
1794
|
+
const entityNames = Object.keys(manifest.entities).sort();
|
|
1795
|
+
const table = entityNames.length > 0 ? manifest.entities[entityNames[0]].table : "";
|
|
1796
|
+
const title = options.title ?? `${table} Table Specification`;
|
|
1797
|
+
const cfnFile = options.cfnFile ?? "cloudformation.yaml";
|
|
1798
|
+
const overview = [
|
|
1799
|
+
{ item: "Table", value: `\`${table}\`` },
|
|
1800
|
+
{
|
|
1801
|
+
item: "Billing Mode",
|
|
1802
|
+
value: options.billingMode ?? "PAY_PER_REQUEST"
|
|
1803
|
+
},
|
|
1804
|
+
{
|
|
1805
|
+
item: "Streams",
|
|
1806
|
+
value: options.streamsEnabled === void 0 ? AS_CONFIGURED : options.streamsEnabled ? "NEW_AND_OLD_IMAGES" : "Disabled"
|
|
1807
|
+
},
|
|
1808
|
+
{
|
|
1809
|
+
item: "TTL",
|
|
1810
|
+
value: Object.values(manifest.entities).some((e) => e.ttlAttribute) ? "Enabled" : "Disabled"
|
|
1811
|
+
},
|
|
1812
|
+
{ item: "PITR", value: AS_CONFIGURED },
|
|
1813
|
+
{ item: "Table Class", value: AS_CONFIGURED },
|
|
1814
|
+
{ item: "Encryption", value: AS_CONFIGURED },
|
|
1815
|
+
{ item: "CloudFormation", value: `\`${cfnFile}\`` }
|
|
1816
|
+
];
|
|
1817
|
+
const relationEdges = [];
|
|
1818
|
+
for (const name of entityNames) {
|
|
1819
|
+
const entity = manifest.entities[name];
|
|
1820
|
+
for (const propName of Object.keys(entity.relations).sort()) {
|
|
1821
|
+
const rel = entity.relations[propName];
|
|
1822
|
+
relationEdges.push({
|
|
1823
|
+
source: name,
|
|
1824
|
+
target: rel.target,
|
|
1825
|
+
label: rel.type
|
|
1826
|
+
});
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
const gsiColumns = collectGsiColumns(manifest);
|
|
1830
|
+
const keySchema = entityNames.map((name) => {
|
|
1831
|
+
const entity = manifest.entities[name];
|
|
1832
|
+
return {
|
|
1833
|
+
model: name,
|
|
1834
|
+
pk: entity.key?.pkTemplate ?? "",
|
|
1835
|
+
sk: entity.key?.skTemplate ?? "",
|
|
1836
|
+
gsis: gsiColumns.map((col) => {
|
|
1837
|
+
const g = gsiByName(entity, col);
|
|
1838
|
+
return { pk: g?.pkTemplate ?? "", sk: g?.skTemplate ?? "" };
|
|
1839
|
+
})
|
|
1840
|
+
};
|
|
1841
|
+
});
|
|
1842
|
+
const propertyOrder = [];
|
|
1843
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1844
|
+
for (const name of entityNames) {
|
|
1845
|
+
const entity = manifest.entities[name];
|
|
1846
|
+
for (const field of Object.keys(entity.fields).sort()) {
|
|
1847
|
+
if (!seen.has(field)) {
|
|
1848
|
+
seen.add(field);
|
|
1849
|
+
propertyOrder.push(field);
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
const propertyMatrix = propertyOrder.map((property) => {
|
|
1854
|
+
let type = "";
|
|
1855
|
+
let description = "";
|
|
1856
|
+
for (const name of entityNames) {
|
|
1857
|
+
const entity = manifest.entities[name];
|
|
1858
|
+
const field = entity.fields[property];
|
|
1859
|
+
if (!field) continue;
|
|
1860
|
+
if (!type) type = fieldTypeLabel(entity, property);
|
|
1861
|
+
if (!description && field.description) description = field.description;
|
|
1862
|
+
}
|
|
1863
|
+
const cells = entityNames.map(
|
|
1864
|
+
(name) => propertyRole(manifest.entities[name], property)
|
|
1865
|
+
);
|
|
1866
|
+
return { property, type, description, cells };
|
|
1867
|
+
});
|
|
1868
|
+
const propertyMatrixHasDescriptions = propertyMatrix.some(
|
|
1869
|
+
(row) => row.description !== ""
|
|
1870
|
+
);
|
|
1871
|
+
const maintainedPaths = buildMaintainedPaths(registry);
|
|
1872
|
+
const entities = entityNames.map((name) => {
|
|
1873
|
+
const entity = manifest.entities[name];
|
|
1874
|
+
const relations = Object.keys(entity.relations).sort().map((propName) => {
|
|
1875
|
+
const rel = entity.relations[propName];
|
|
1876
|
+
return { property: propName, target: rel.target, type: rel.type };
|
|
1877
|
+
});
|
|
1878
|
+
const maintained = maintainedPaths.filter((row) => row.target.startsWith(`${name}.`)).map((row) => ({
|
|
1879
|
+
field: row.target.slice(name.length + 1),
|
|
1880
|
+
pattern: row.pattern
|
|
1881
|
+
}));
|
|
1882
|
+
return {
|
|
1883
|
+
name,
|
|
1884
|
+
description: entity.description ?? "",
|
|
1885
|
+
pk: keyLabel("PK", entity.key?.pkTemplate ?? null),
|
|
1886
|
+
sk: keyLabel("SK", entity.key?.skTemplate ?? null),
|
|
1887
|
+
gsis: entity.gsis.map((g) => ({
|
|
1888
|
+
indexName: g.indexName,
|
|
1889
|
+
pk: g.pkTemplate,
|
|
1890
|
+
sk: g.skTemplate ?? "",
|
|
1891
|
+
unique: g.unique,
|
|
1892
|
+
description: g.description ?? ""
|
|
1893
|
+
})),
|
|
1894
|
+
relations,
|
|
1895
|
+
maintained,
|
|
1896
|
+
fields: Object.keys(entity.fields).sort().map((field) => ({
|
|
1897
|
+
field,
|
|
1898
|
+
type: fieldTypeLabel(entity, field),
|
|
1899
|
+
description: entity.fields[field].description ?? ""
|
|
1900
|
+
})),
|
|
1901
|
+
hasFieldDescriptions: Object.values(entity.fields).some(
|
|
1902
|
+
(f) => (f.description ?? "") !== ""
|
|
1903
|
+
)
|
|
1904
|
+
};
|
|
1905
|
+
});
|
|
1906
|
+
const accessPatterns = [];
|
|
1907
|
+
const accessMatrix = [];
|
|
1908
|
+
for (const name of Object.keys(operations.queries).sort()) {
|
|
1909
|
+
const spec = operations.queries[name];
|
|
1910
|
+
const entityName = rootEntityName(spec, manifest);
|
|
1911
|
+
const { resolve: resolve3, diagram } = buildResolution(spec, manifest);
|
|
1912
|
+
const relation = isRelation(spec);
|
|
1913
|
+
const batchGet = usesBatchGet(spec);
|
|
1914
|
+
const cursor = usesCursor(spec);
|
|
1915
|
+
const index = rootIndex(spec);
|
|
1916
|
+
const signature = `${entityName || "Query"}.${name}(${Object.keys(spec.params).sort().join(", ")})`;
|
|
1917
|
+
accessPatterns.push({
|
|
1918
|
+
signature,
|
|
1919
|
+
description: spec.description ?? "",
|
|
1920
|
+
index,
|
|
1921
|
+
input: inputShape(spec),
|
|
1922
|
+
output: outputShape(spec, entityName),
|
|
1923
|
+
resolve: resolve3,
|
|
1924
|
+
relation,
|
|
1925
|
+
batchGet,
|
|
1926
|
+
cursor,
|
|
1927
|
+
diagram
|
|
1928
|
+
});
|
|
1929
|
+
accessMatrix.push({ query: signature, index, relation, batchGet, cursor });
|
|
1930
|
+
}
|
|
1931
|
+
const mutationContracts = [];
|
|
1932
|
+
for (const name of Object.keys(operations.commands).sort()) {
|
|
1933
|
+
const spec = operations.commands[name];
|
|
1934
|
+
const verb = spec.type === "PutItem" ? "TransactWrite \u2192 " + spec.entity + " item" : spec.type === "UpdateItem" ? "TransactWrite \u2192 " + spec.entity + " item" : "TransactWrite \u2192 delete " + spec.entity + " item";
|
|
1935
|
+
mutationContracts.push({
|
|
1936
|
+
signature: commandSignature(name, spec),
|
|
1937
|
+
description: spec.description ?? "",
|
|
1938
|
+
input: inputShape(spec),
|
|
1939
|
+
output: spec.entity,
|
|
1940
|
+
resolve: verb
|
|
1941
|
+
});
|
|
1942
|
+
}
|
|
1943
|
+
const cdcEntries = buildCdcEntries(registry);
|
|
1944
|
+
const cfnResources = buildCfnResources(manifest, options.streamsEnabled);
|
|
1945
|
+
return {
|
|
1946
|
+
table,
|
|
1947
|
+
title,
|
|
1948
|
+
overview,
|
|
1949
|
+
entityNames,
|
|
1950
|
+
relationEdges,
|
|
1951
|
+
gsiColumns,
|
|
1952
|
+
keySchema,
|
|
1953
|
+
propertyMatrix,
|
|
1954
|
+
propertyMatrixHasDescriptions,
|
|
1955
|
+
entities,
|
|
1956
|
+
accessPatterns,
|
|
1957
|
+
accessMatrix,
|
|
1958
|
+
mutationContracts,
|
|
1959
|
+
maintainedPaths,
|
|
1960
|
+
cdcEntries,
|
|
1961
|
+
cfnResources,
|
|
1962
|
+
cfnFile
|
|
1963
|
+
};
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
// src/codegen/docs.ts
|
|
1967
|
+
import { existsSync, readFileSync } from "fs";
|
|
1968
|
+
import { resolve } from "path";
|
|
1969
|
+
import Handlebars from "handlebars";
|
|
1970
|
+
|
|
1971
|
+
// src/codegen/docs-templates.ts
|
|
1972
|
+
var PARTIAL_NAMES = [
|
|
1973
|
+
"index",
|
|
1974
|
+
"table-overview",
|
|
1975
|
+
"er-diagram",
|
|
1976
|
+
"key-schema",
|
|
1977
|
+
"property-matrix",
|
|
1978
|
+
"entity",
|
|
1979
|
+
"access-pattern",
|
|
1980
|
+
"maintained",
|
|
1981
|
+
"cdc",
|
|
1982
|
+
"cloudformation"
|
|
1983
|
+
];
|
|
1984
|
+
var INDEX = `# {{title}}
|
|
1985
|
+
|
|
1986
|
+
> Generated by \`graphddb generate docs\`
|
|
1987
|
+
|
|
1988
|
+
---
|
|
1989
|
+
|
|
1990
|
+
{{> table-overview }}
|
|
1991
|
+
|
|
1992
|
+
---
|
|
1993
|
+
|
|
1994
|
+
{{> er-diagram }}
|
|
1995
|
+
{{> key-schema }}
|
|
1996
|
+
|
|
1997
|
+
---
|
|
1998
|
+
|
|
1999
|
+
{{> property-matrix }}
|
|
2000
|
+
|
|
2001
|
+
---
|
|
2002
|
+
|
|
2003
|
+
## Entity Details
|
|
2004
|
+
|
|
2005
|
+
{{#each entities}}
|
|
2006
|
+
{{> entity this }}
|
|
2007
|
+
{{/each}}
|
|
2008
|
+
{{#if accessMatrix.length}}---
|
|
2009
|
+
|
|
2010
|
+
## Access Patterns
|
|
2011
|
+
|
|
2012
|
+
Each query resolves to a physical operation, an index (PK or GSI), and a projection.
|
|
2013
|
+
The matrix summarizes; the contracts below give the input/output and execution for each.
|
|
2014
|
+
|
|
2015
|
+
| Query | Index | Relation | BatchGet | Cursor |
|
|
2016
|
+
|------|------|------|------|------|
|
|
2017
|
+
{{#each accessMatrix}}| \`{{query}}\` | {{index}} | {{#if relation}}\u2713{{/if}} | {{#if batchGet}}\u2713{{/if}} | {{#if cursor}}\u2713{{/if}} |
|
|
2018
|
+
{{/each}}
|
|
2019
|
+
|
|
2020
|
+
{{#each accessPatterns}}
|
|
2021
|
+
{{> access-pattern this }}
|
|
2022
|
+
{{/each}}
|
|
2023
|
+
{{/if}}
|
|
2024
|
+
{{#if mutationContracts.length}}---
|
|
2025
|
+
|
|
2026
|
+
## Mutation Contracts
|
|
2027
|
+
|
|
2028
|
+
{{#each mutationContracts}}
|
|
2029
|
+
### \`{{signature}}\`
|
|
2030
|
+
{{#if description}}
|
|
2031
|
+
|
|
2032
|
+
{{description}}
|
|
2033
|
+
{{/if}}
|
|
2034
|
+
|
|
2035
|
+
\`\`\`yaml
|
|
2036
|
+
Input: {{input}}
|
|
2037
|
+
Output: {{output}}
|
|
2038
|
+
Resolve: {{resolve}}
|
|
2039
|
+
\`\`\`
|
|
2040
|
+
|
|
2041
|
+
{{/each}}
|
|
2042
|
+
{{/if}}
|
|
2043
|
+
{{> maintained }}
|
|
2044
|
+
{{> cdc }}
|
|
2045
|
+
---
|
|
2046
|
+
|
|
2047
|
+
{{> cloudformation }}
|
|
2048
|
+
`;
|
|
2049
|
+
var TABLE_OVERVIEW = `## Table Overview
|
|
2050
|
+
|
|
2051
|
+
| Item | Value |
|
|
2052
|
+
|------|-------|
|
|
2053
|
+
{{#each overview}}| {{item}} | {{value}} |
|
|
2054
|
+
{{/each}}`;
|
|
2055
|
+
var ER_DIAGRAM = `{{#if relationEdges.length}}## Entity Relationship Diagram
|
|
2056
|
+
|
|
2057
|
+
\`\`\`mermaid
|
|
2058
|
+
graph TD
|
|
2059
|
+
{{#each relationEdges}} {{mermaidId source}} -->|{{label}}| {{mermaidId target}}
|
|
2060
|
+
{{/each}}\`\`\`
|
|
2061
|
+
|
|
2062
|
+
---
|
|
2063
|
+
|
|
2064
|
+
{{/if}}`;
|
|
2065
|
+
var KEY_SCHEMA = `## Physical Key Schema
|
|
2066
|
+
|
|
2067
|
+
| Model | PK | SK |{{#each gsiColumns}} {{this}} PK | {{this}} SK |{{/each}}
|
|
2068
|
+
|------|------|------|{{#each gsiColumns}}------|------|{{/each}}
|
|
2069
|
+
{{#each keySchema}}| {{model}} | {{codeOrEmpty pk}} | {{codeOrEmpty sk}} |{{#each gsis}} {{codeOrEmpty pk}} | {{codeOrEmpty sk}} |{{/each}}
|
|
2070
|
+
{{/each}}`;
|
|
2071
|
+
var PROPERTY_MATRIX = `## Property Matrix
|
|
2072
|
+
|
|
2073
|
+
| Property | Type |{{#if propertyMatrixHasDescriptions}} Description |{{/if}}{{#each entityNames}} {{this}} |{{/each}}
|
|
2074
|
+
|----------|------|{{#if propertyMatrixHasDescriptions}}-------------|{{/if}}{{#each entityNames}}------|{{/each}}
|
|
2075
|
+
{{#each propertyMatrix}}| {{property}} | {{type}} |{{#if ../propertyMatrixHasDescriptions}} {{description}} |{{/if}}{{#each cells}} {{this}} |{{/each}}
|
|
2076
|
+
{{/each}}
|
|
2077
|
+
|
|
2078
|
+
Legend: **PK** primary partition key \xB7 **SK** primary sort key \xB7 GSI keys \xB7 **\u2713** normal attribute`;
|
|
2079
|
+
var ENTITY = `### {{name}}
|
|
2080
|
+
{{#if description}}
|
|
2081
|
+
|
|
2082
|
+
{{description}}
|
|
2083
|
+
{{/if}}
|
|
2084
|
+
|
|
2085
|
+
- **Key**: \`{{pk}}\`{{#if sk}} / \`{{sk}}\`{{/if}}
|
|
2086
|
+
{{#each gsis}}- **{{indexName}}**: \`PK = {{pk}}\`{{#if sk}} / \`SK = {{sk}}\`{{/if}}{{#if unique}} (unique){{/if}}
|
|
2087
|
+
{{/each}}
|
|
2088
|
+
{{#if relations.length}}- **Relations**: {{#each relations}}\`{{property}}\` \u2192 {{target}} ({{type}}){{#unless @last}}, {{/unless}}{{/each}}
|
|
2089
|
+
{{/if}}
|
|
2090
|
+
{{#if maintained.length}}- **Maintained**: {{#each maintained}}\`{{field}}\` ({{pattern}}){{#unless @last}}, {{/unless}}{{/each}}
|
|
2091
|
+
{{/if}}
|
|
2092
|
+
|
|
2093
|
+
| Field | Type |{{#if hasFieldDescriptions}} Description |{{/if}}
|
|
2094
|
+
|------|------|{{#if hasFieldDescriptions}}------|{{/if}}
|
|
2095
|
+
{{#each fields}}| {{field}} | {{type}} |{{#if ../hasFieldDescriptions}} {{description}} |{{/if}}
|
|
2096
|
+
{{/each}}
|
|
2097
|
+
`;
|
|
2098
|
+
var ACCESS_PATTERN = `### \`{{signature}}\`
|
|
2099
|
+
{{#if description}}
|
|
2100
|
+
|
|
2101
|
+
{{description}}
|
|
2102
|
+
{{/if}}
|
|
2103
|
+
|
|
2104
|
+
\`\`\`yaml
|
|
2105
|
+
Input: {{input}}
|
|
2106
|
+
Output: {{output}}
|
|
2107
|
+
Resolve: {{resolve}}
|
|
2108
|
+
\`\`\`
|
|
2109
|
+
|
|
2110
|
+
\`\`\`mermaid
|
|
2111
|
+
flowchart LR
|
|
2112
|
+
{{#each diagram}}{{mermaidId label}}["{{label}}"]{{#unless @last}} {{#if edge}}-- "{{edge}}" {{/if}}--> {{/unless}}{{/each}}
|
|
2113
|
+
\`\`\`
|
|
2114
|
+
|
|
2115
|
+
`;
|
|
2116
|
+
var MAINTAINED = `{{#if maintainedPaths.length}}---
|
|
2117
|
+
|
|
2118
|
+
## Maintained Access Paths
|
|
2119
|
+
|
|
2120
|
+
Write-side projections kept in sync automatically, within the source write's transaction
|
|
2121
|
+
(or via the CDC stream for \`updateMode: 'stream'\`).
|
|
2122
|
+
|
|
2123
|
+
| Source | Target | Pattern | Update |
|
|
2124
|
+
|---------|--------|--------|--------|
|
|
2125
|
+
{{#each maintainedPaths}}| {{source}} | {{target}} | {{pattern}} | {{update}} |
|
|
2126
|
+
{{/each}}
|
|
2127
|
+
|
|
2128
|
+
\`\`\`mermaid
|
|
2129
|
+
flowchart LR
|
|
2130
|
+
{{#each maintainedPaths}} {{mermaidId source}} -- "{{pattern}}" --> {{mermaidId target}}
|
|
2131
|
+
{{/each}}\`\`\`
|
|
2132
|
+
|
|
2133
|
+
{{/if}}`;
|
|
2134
|
+
var CDC = `{{#if cdcEntries.length}}---
|
|
2135
|
+
|
|
2136
|
+
## CDC
|
|
2137
|
+
|
|
2138
|
+
\`@cdcProjected\` models expose \`fromChange\` / \`subscribe\` to parse the change stream into
|
|
2139
|
+
typed records. Delivery and idempotency are the consumer's responsibility.
|
|
2140
|
+
|
|
2141
|
+
{{#each cdcEntries}}
|
|
2142
|
+
\`\`\`yaml
|
|
2143
|
+
Contract: {{model}}.subscribe
|
|
2144
|
+
Input: ChangeEvent<{{model}}>
|
|
2145
|
+
Output: [{{model}} | null, {{model}} | null] # [oldRecord, newRecord]
|
|
2146
|
+
\`\`\`
|
|
2147
|
+
|
|
2148
|
+
\`\`\`mermaid
|
|
2149
|
+
flowchart LR
|
|
2150
|
+
Mutate --> Stream["DynamoDB Stream"] --> CE["ChangeEvent"]
|
|
2151
|
+
CE --> FC["{{model}}.fromChange()"] --> Sub["subscribe()"] --> Consumer
|
|
2152
|
+
\`\`\`
|
|
2153
|
+
|
|
2154
|
+
{{/each}}
|
|
2155
|
+
{{/if}}`;
|
|
2156
|
+
var CLOUDFORMATION = `## CloudFormation
|
|
2157
|
+
|
|
2158
|
+
Generated by \`graphddb generate cloudformation\` (see \`{{cfnFile}}\`).
|
|
2159
|
+
|
|
2160
|
+
| Resource | Description |
|
|
2161
|
+
|------|------|
|
|
2162
|
+
{{#each cfnResources}}| \`{{resource}}\` | {{description}} |
|
|
2163
|
+
{{/each}}`;
|
|
2164
|
+
var BUNDLED_PARTIALS = {
|
|
2165
|
+
index: INDEX,
|
|
2166
|
+
"table-overview": TABLE_OVERVIEW,
|
|
2167
|
+
"er-diagram": ER_DIAGRAM,
|
|
2168
|
+
"key-schema": KEY_SCHEMA,
|
|
2169
|
+
"property-matrix": PROPERTY_MATRIX,
|
|
2170
|
+
entity: ENTITY,
|
|
2171
|
+
"access-pattern": ACCESS_PATTERN,
|
|
2172
|
+
maintained: MAINTAINED,
|
|
2173
|
+
cdc: CDC,
|
|
2174
|
+
cloudformation: CLOUDFORMATION
|
|
2175
|
+
};
|
|
2176
|
+
|
|
2177
|
+
// src/codegen/docs.ts
|
|
2178
|
+
var DOCS_INDEX_FILE = "index.md";
|
|
2179
|
+
function registerBuiltinHelpers(hb) {
|
|
2180
|
+
hb.registerHelper("eq", (a, b) => a === b);
|
|
2181
|
+
hb.registerHelper(
|
|
2182
|
+
"join",
|
|
2183
|
+
(list, sep) => Array.isArray(list) ? list.join(typeof sep === "string" ? sep : ", ") : ""
|
|
2184
|
+
);
|
|
2185
|
+
hb.registerHelper("pascal", (value) => {
|
|
2186
|
+
const s = String(value ?? "");
|
|
2187
|
+
return s.split(/[^A-Za-z0-9]+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
2188
|
+
});
|
|
2189
|
+
hb.registerHelper("mermaidId", (value) => {
|
|
2190
|
+
const s = String(value ?? "");
|
|
2191
|
+
const id = s.replace(/[^A-Za-z0-9_]/g, "_").replace(/_+/g, "_");
|
|
2192
|
+
return /^[0-9]/.test(id) ? `n_${id}` : id || "n";
|
|
2193
|
+
});
|
|
2194
|
+
hb.registerHelper("codeOrEmpty", (value) => {
|
|
2195
|
+
const s = String(value ?? "");
|
|
2196
|
+
return s ? new hb.SafeString(`\`${s}\``) : "";
|
|
2197
|
+
});
|
|
2198
|
+
}
|
|
2199
|
+
function partialSource(name, templateDir) {
|
|
2200
|
+
if (templateDir) {
|
|
2201
|
+
const candidate = resolve(templateDir, `${name}.hbs`);
|
|
2202
|
+
if (existsSync(candidate)) {
|
|
2203
|
+
return readFileSync(candidate, "utf8");
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
return BUNDLED_PARTIALS[name];
|
|
2207
|
+
}
|
|
2208
|
+
function buildEnvironment(options) {
|
|
2209
|
+
const hb = Handlebars.create();
|
|
2210
|
+
registerBuiltinHelpers(hb);
|
|
2211
|
+
if (options.helpers) {
|
|
2212
|
+
for (const [name, fn] of Object.entries(options.helpers)) {
|
|
2213
|
+
hb.registerHelper(name, fn);
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
for (const name of PARTIAL_NAMES) {
|
|
2217
|
+
hb.registerPartial(
|
|
2218
|
+
name,
|
|
2219
|
+
hb.compile(partialSource(name, options.templateDir), { noEscape: true })
|
|
2220
|
+
);
|
|
2221
|
+
}
|
|
2222
|
+
return hb;
|
|
2223
|
+
}
|
|
2224
|
+
function generateDocs(docModel, options = {}) {
|
|
2225
|
+
const hb = buildEnvironment(options);
|
|
2226
|
+
const index = hb.partials["index"];
|
|
2227
|
+
const template = typeof index === "function" ? index : hb.compile(index, { noEscape: true });
|
|
2228
|
+
let out = template(docModel);
|
|
2229
|
+
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";
|
|
2230
|
+
return { [DOCS_INDEX_FILE]: out };
|
|
2231
|
+
}
|
|
2232
|
+
|
|
1453
2233
|
// src/codegen/index.ts
|
|
1454
2234
|
var OUTPUT_FILES = [
|
|
1455
2235
|
"manifest.json",
|
|
@@ -1471,18 +2251,250 @@ function renderBundle(queries = {}, commands = {}, registry = MetadataRegistry,
|
|
|
1471
2251
|
"manifest.json": JSON.stringify(bundle.manifest, null, 2) + "\n",
|
|
1472
2252
|
"operations.json": JSON.stringify(bundle.operations, null, 2) + "\n",
|
|
1473
2253
|
"types.py": generateTypes(bundle.manifest, queries, txSpecs, options),
|
|
1474
|
-
"repositories.py": generateRepositories(
|
|
2254
|
+
"repositories.py": generateRepositories(
|
|
2255
|
+
queries,
|
|
2256
|
+
commands,
|
|
2257
|
+
txSpecs,
|
|
2258
|
+
bundle.manifest,
|
|
2259
|
+
bundle.operations.queries
|
|
2260
|
+
),
|
|
1475
2261
|
"__init__.py": generateInit(queries, commands, txSpecs)
|
|
1476
2262
|
};
|
|
1477
2263
|
}
|
|
1478
2264
|
|
|
2265
|
+
// src/codegen/jsdoc.ts
|
|
2266
|
+
async function loadTypescript() {
|
|
2267
|
+
try {
|
|
2268
|
+
const mod = await import("typescript");
|
|
2269
|
+
return mod.default ?? mod;
|
|
2270
|
+
} catch {
|
|
2271
|
+
return void 0;
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
var warnedTypescriptMissing = false;
|
|
2275
|
+
var TYPESCRIPT_MISSING_WARNING = "warning: JSDoc description extraction skipped \u2014 'typescript' is not installed. Install typescript to enable it, or use explicit `description` fields.\n";
|
|
2276
|
+
function docCommentFor(ts, node) {
|
|
2277
|
+
const jsDoc = node.jsDoc;
|
|
2278
|
+
if (!jsDoc || jsDoc.length === 0) return void 0;
|
|
2279
|
+
const last = jsDoc[jsDoc.length - 1];
|
|
2280
|
+
const comment = last.comment;
|
|
2281
|
+
if (typeof comment === "string") {
|
|
2282
|
+
const t = comment.trim();
|
|
2283
|
+
return t === "" ? void 0 : t;
|
|
2284
|
+
}
|
|
2285
|
+
if (Array.isArray(comment)) {
|
|
2286
|
+
const joined = ts.getTextOfJSDocComment(comment);
|
|
2287
|
+
if (joined) {
|
|
2288
|
+
const t = joined.trim();
|
|
2289
|
+
return t === "" ? void 0 : t;
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
return void 0;
|
|
2293
|
+
}
|
|
2294
|
+
function hasModelDecorator(ts, node) {
|
|
2295
|
+
const decorators = ts.getDecorators?.(node) ?? [];
|
|
2296
|
+
for (const dec of decorators) {
|
|
2297
|
+
const expr = dec.expression;
|
|
2298
|
+
const callee = ts.isCallExpression(expr) ? expr.expression : expr;
|
|
2299
|
+
if (ts.isIdentifier(callee) && callee.text === "model") return true;
|
|
2300
|
+
}
|
|
2301
|
+
return false;
|
|
2302
|
+
}
|
|
2303
|
+
function gsiIndexNameOf(ts, init) {
|
|
2304
|
+
if (!init || !ts.isCallExpression(init)) return void 0;
|
|
2305
|
+
const callee = init.expression;
|
|
2306
|
+
if (!ts.isIdentifier(callee) || callee.text !== "gsi") return void 0;
|
|
2307
|
+
const first = init.arguments[0];
|
|
2308
|
+
if (first && ts.isStringLiteralLike(first)) return first.text;
|
|
2309
|
+
return void 0;
|
|
2310
|
+
}
|
|
2311
|
+
function collectClassDoc(ts, node) {
|
|
2312
|
+
const members = /* @__PURE__ */ new Map();
|
|
2313
|
+
const gsis = /* @__PURE__ */ new Map();
|
|
2314
|
+
for (const member of node.members) {
|
|
2315
|
+
if (ts.isPropertyDeclaration(member) && member.name) {
|
|
2316
|
+
const doc = docCommentFor(ts, member);
|
|
2317
|
+
if (doc === void 0) continue;
|
|
2318
|
+
const gsiName = gsiIndexNameOf(ts, member.initializer);
|
|
2319
|
+
if (gsiName !== void 0) {
|
|
2320
|
+
gsis.set(gsiName, doc);
|
|
2321
|
+
} else if (ts.isIdentifier(member.name) || ts.isStringLiteralLike(member.name)) {
|
|
2322
|
+
members.set(member.name.text, doc);
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
return { own: docCommentFor(ts, node), members, gsis };
|
|
2327
|
+
}
|
|
2328
|
+
function collectDefinitionDocs(ts, obj, into, ambiguous) {
|
|
2329
|
+
for (const prop of obj.properties) {
|
|
2330
|
+
if (!ts.isPropertyAssignment(prop)) continue;
|
|
2331
|
+
if (!ts.isIdentifier(prop.name) && !ts.isStringLiteralLike(prop.name)) continue;
|
|
2332
|
+
const key = prop.name.text;
|
|
2333
|
+
if (into.has(key) || ambiguous.has(key)) {
|
|
2334
|
+
into.delete(key);
|
|
2335
|
+
ambiguous.add(key);
|
|
2336
|
+
continue;
|
|
2337
|
+
}
|
|
2338
|
+
const params = /* @__PURE__ */ new Map();
|
|
2339
|
+
collectParamDocs(ts, prop.initializer, params);
|
|
2340
|
+
into.set(key, { own: docCommentFor(ts, prop), params });
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
function collectParamDocs(ts, node, into) {
|
|
2344
|
+
const visit = (n) => {
|
|
2345
|
+
if (ts.isPropertyAssignment(n)) {
|
|
2346
|
+
const isParamCall = ts.isCallExpression(n.initializer) && ts.isPropertyAccessExpression(n.initializer.expression) && ts.isIdentifier(n.initializer.expression.expression) && n.initializer.expression.expression.text === "param";
|
|
2347
|
+
if (isParamCall && (ts.isIdentifier(n.name) || ts.isStringLiteralLike(n.name))) {
|
|
2348
|
+
const doc = docCommentFor(ts, n);
|
|
2349
|
+
if (doc !== void 0) into.set(n.name.text, doc);
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
ts.forEachChild(n, visit);
|
|
2353
|
+
};
|
|
2354
|
+
ts.forEachChild(node, visit);
|
|
2355
|
+
}
|
|
2356
|
+
function extract(ts, files) {
|
|
2357
|
+
const program = ts.createProgram(files, {
|
|
2358
|
+
// Parse-only, permissive options: we never emit and never type-check the
|
|
2359
|
+
// world — we only walk the AST of the requested files. `allowJs` lets a `.js`
|
|
2360
|
+
// definition module (pre-compiled input) be parsed too.
|
|
2361
|
+
allowJs: true,
|
|
2362
|
+
noResolve: true,
|
|
2363
|
+
noLib: true,
|
|
2364
|
+
skipLibCheck: true,
|
|
2365
|
+
types: [],
|
|
2366
|
+
// The definition modules use TC39 standard decorators (repo tsconfig has
|
|
2367
|
+
// `experimentalDecorators: false`); parsing is decorator-flavour agnostic for
|
|
2368
|
+
// AST purposes, but keep it aligned.
|
|
2369
|
+
experimentalDecorators: false
|
|
2370
|
+
});
|
|
2371
|
+
const classes = /* @__PURE__ */ new Map();
|
|
2372
|
+
const ambiguousClassNames = /* @__PURE__ */ new Set();
|
|
2373
|
+
const definitions = /* @__PURE__ */ new Map();
|
|
2374
|
+
const ambiguousDefinitionKeys = /* @__PURE__ */ new Set();
|
|
2375
|
+
const fileSet = new Set(files.map((f) => normalize(f)));
|
|
2376
|
+
for (const sf of program.getSourceFiles()) {
|
|
2377
|
+
if (!fileSet.has(normalize(sf.fileName))) continue;
|
|
2378
|
+
const visit = (node) => {
|
|
2379
|
+
if (ts.isClassDeclaration(node) && node.name && hasModelDecorator(ts, node)) {
|
|
2380
|
+
const name = node.name.text;
|
|
2381
|
+
if (classes.has(name) || ambiguousClassNames.has(name)) {
|
|
2382
|
+
classes.delete(name);
|
|
2383
|
+
ambiguousClassNames.add(name);
|
|
2384
|
+
} else {
|
|
2385
|
+
classes.set(name, collectClassDoc(ts, node));
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2388
|
+
if (ts.isCallExpression(node)) {
|
|
2389
|
+
const callee = node.expression;
|
|
2390
|
+
if (ts.isIdentifier(callee) && (callee.text === "defineQueries" || callee.text === "defineCommands")) {
|
|
2391
|
+
const arg = node.arguments[0];
|
|
2392
|
+
if (arg && ts.isObjectLiteralExpression(arg)) {
|
|
2393
|
+
collectDefinitionDocs(ts, arg, definitions, ambiguousDefinitionKeys);
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
ts.forEachChild(node, visit);
|
|
2398
|
+
};
|
|
2399
|
+
visit(sf);
|
|
2400
|
+
}
|
|
2401
|
+
return {
|
|
2402
|
+
classes,
|
|
2403
|
+
ambiguousClassNames,
|
|
2404
|
+
definitions,
|
|
2405
|
+
ambiguousDefinitionKeys
|
|
2406
|
+
};
|
|
2407
|
+
}
|
|
2408
|
+
function normalize(p) {
|
|
2409
|
+
return p.replace(/\\/g, "/");
|
|
2410
|
+
}
|
|
2411
|
+
function mergeIntoRegistry(extracted, registry) {
|
|
2412
|
+
const countByName = /* @__PURE__ */ new Map();
|
|
2413
|
+
for (const [cls] of registry.getAll()) {
|
|
2414
|
+
countByName.set(cls.name, (countByName.get(cls.name) ?? 0) + 1);
|
|
2415
|
+
}
|
|
2416
|
+
for (const [cls, meta] of registry.getAll()) {
|
|
2417
|
+
const name = cls.name;
|
|
2418
|
+
if (extracted.ambiguousClassNames.has(name)) continue;
|
|
2419
|
+
if ((countByName.get(name) ?? 0) !== 1) continue;
|
|
2420
|
+
const classDoc = extracted.classes.get(name);
|
|
2421
|
+
if (!classDoc) continue;
|
|
2422
|
+
applyClassDoc(classDoc, meta);
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
function applyClassDoc(classDoc, meta) {
|
|
2426
|
+
if (meta.description === void 0 && classDoc.own !== void 0) {
|
|
2427
|
+
meta.description = classDoc.own;
|
|
2428
|
+
}
|
|
2429
|
+
for (const field of meta.fields) {
|
|
2430
|
+
const doc = classDoc.members.get(field.propertyName);
|
|
2431
|
+
if (doc === void 0) continue;
|
|
2432
|
+
if (field.options?.description !== void 0) continue;
|
|
2433
|
+
field.options = { ...field.options ?? {}, description: doc };
|
|
2434
|
+
}
|
|
2435
|
+
for (const rel of meta.relations) {
|
|
2436
|
+
const doc = classDoc.members.get(rel.propertyName);
|
|
2437
|
+
if (doc === void 0) continue;
|
|
2438
|
+
if (rel.options?.description !== void 0) continue;
|
|
2439
|
+
rel.options = { ...rel.options ?? {}, description: doc };
|
|
2440
|
+
}
|
|
2441
|
+
for (const gsi of meta.gsiDefinitions) {
|
|
2442
|
+
const doc = classDoc.gsis.get(gsi.indexName);
|
|
2443
|
+
if (doc === void 0) continue;
|
|
2444
|
+
if (gsi.description !== void 0) continue;
|
|
2445
|
+
gsi.description = doc;
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
function mergeIntoDefinitions(extracted, map) {
|
|
2449
|
+
for (const [key, def] of Object.entries(map)) {
|
|
2450
|
+
if (extracted.ambiguousDefinitionKeys.has(key)) continue;
|
|
2451
|
+
const defDoc = extracted.definitions.get(key);
|
|
2452
|
+
if (!defDoc) continue;
|
|
2453
|
+
if (def.description === void 0 && defDoc.own !== void 0) {
|
|
2454
|
+
def.description = defDoc.own;
|
|
2455
|
+
}
|
|
2456
|
+
for (const [paramName, descriptor] of Object.entries(def.params)) {
|
|
2457
|
+
const doc = defDoc.params.get(paramName);
|
|
2458
|
+
if (doc === void 0) continue;
|
|
2459
|
+
if (descriptor.description !== void 0) continue;
|
|
2460
|
+
descriptor.description = doc;
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
async function applyJsDocDescriptions(sources, queries, commands, registry = MetadataRegistry, opts = {}) {
|
|
2465
|
+
const ts = await (opts.loadTs ?? loadTypescript)();
|
|
2466
|
+
if (!ts) {
|
|
2467
|
+
if (!warnedTypescriptMissing) {
|
|
2468
|
+
warnedTypescriptMissing = true;
|
|
2469
|
+
(opts.warn ?? ((m) => void process.stderr.write(m)))(
|
|
2470
|
+
TYPESCRIPT_MISSING_WARNING
|
|
2471
|
+
);
|
|
2472
|
+
}
|
|
2473
|
+
return false;
|
|
2474
|
+
}
|
|
2475
|
+
try {
|
|
2476
|
+
const files = [
|
|
2477
|
+
sources.entry,
|
|
2478
|
+
...sources.queries ? [sources.queries] : [],
|
|
2479
|
+
...sources.commands ? [sources.commands] : []
|
|
2480
|
+
].filter((f, i, a) => a.indexOf(f) === i);
|
|
2481
|
+
const extracted = extract(ts, files);
|
|
2482
|
+
mergeIntoRegistry(extracted, registry);
|
|
2483
|
+
mergeIntoDefinitions(extracted, queries);
|
|
2484
|
+
mergeIntoDefinitions(extracted, commands);
|
|
2485
|
+
return true;
|
|
2486
|
+
} catch {
|
|
2487
|
+
return false;
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2490
|
+
|
|
1479
2491
|
// src/cli/handlers.ts
|
|
1480
2492
|
function fail(code, message, exitCode) {
|
|
1481
2493
|
process.stderr.write(JSON.stringify({ code, message }) + "\n");
|
|
1482
2494
|
process.exit(exitCode);
|
|
1483
2495
|
}
|
|
1484
2496
|
function toUrl(file) {
|
|
1485
|
-
const abs = isAbsolute(file) ? file :
|
|
2497
|
+
const abs = isAbsolute(file) ? file : resolve2(process.cwd(), file);
|
|
1486
2498
|
return pathToFileURL(abs).href;
|
|
1487
2499
|
}
|
|
1488
2500
|
var tsxRegistered = false;
|
|
@@ -1504,6 +2516,12 @@ async function importModule(file) {
|
|
|
1504
2516
|
if (/\.[mc]?ts$/.test(file)) await ensureTsx();
|
|
1505
2517
|
return await import(toUrl(file));
|
|
1506
2518
|
}
|
|
2519
|
+
async function importModelEntry(entry) {
|
|
2520
|
+
await importModule(entry);
|
|
2521
|
+
if (MetadataRegistry.linter === null) {
|
|
2522
|
+
MetadataRegistry.linter = createDefaultLinter();
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
1507
2525
|
async function loadDefinitionMap(file, exportName) {
|
|
1508
2526
|
const mod = await importModule(file);
|
|
1509
2527
|
const value = mod[exportName];
|
|
@@ -1537,7 +2555,7 @@ var handlers = {
|
|
|
1537
2555
|
if (!entry) fail("INVALID_ARGS", "Missing required option --entry.", 2);
|
|
1538
2556
|
if (!out) fail("INVALID_ARGS", "Missing required option --out.", 2);
|
|
1539
2557
|
try {
|
|
1540
|
-
await
|
|
2558
|
+
await importModelEntry(entry);
|
|
1541
2559
|
const queries = await loadDefinitionMap(queriesPath ?? entry, "queries");
|
|
1542
2560
|
const commands = await loadDefinitionMap(commandsPath ?? entry, "commands");
|
|
1543
2561
|
const transactions = await loadOptionalMap(
|
|
@@ -1552,24 +2570,34 @@ var handlers = {
|
|
|
1552
2570
|
contextsPath ?? contractsPath ?? entry,
|
|
1553
2571
|
"contexts"
|
|
1554
2572
|
);
|
|
2573
|
+
const abs = (p) => isAbsolute(p) ? p : resolve2(process.cwd(), p);
|
|
2574
|
+
await applyJsDocDescriptions(
|
|
2575
|
+
{
|
|
2576
|
+
entry: abs(entry),
|
|
2577
|
+
queries: abs(queriesPath ?? entry),
|
|
2578
|
+
commands: abs(commandsPath ?? entry)
|
|
2579
|
+
},
|
|
2580
|
+
queries,
|
|
2581
|
+
commands
|
|
2582
|
+
);
|
|
1555
2583
|
const files = renderBundle(queries, commands, void 0, transactions, {
|
|
1556
2584
|
dataclass: Boolean(dataclass),
|
|
1557
2585
|
contracts: { contracts, contexts }
|
|
1558
2586
|
});
|
|
1559
2587
|
void dataclass;
|
|
1560
|
-
const outDir = isAbsolute(out) ? out :
|
|
2588
|
+
const outDir = isAbsolute(out) ? out : resolve2(process.cwd(), out);
|
|
1561
2589
|
mkdirSync(outDir, { recursive: true });
|
|
1562
2590
|
for (const name of OUTPUT_FILES) {
|
|
1563
|
-
writeFileSync(
|
|
2591
|
+
writeFileSync(resolve2(outDir, name), files[name], "utf8");
|
|
1564
2592
|
}
|
|
1565
2593
|
const emittedFiles = [...OUTPUT_FILES];
|
|
1566
2594
|
if (cdcProjectedModelNames().length > 0) {
|
|
1567
|
-
const entryAbs = isAbsolute(entry) ? entry :
|
|
2595
|
+
const entryAbs = isAbsolute(entry) ? entry : resolve2(process.cwd(), entry);
|
|
1568
2596
|
let modelsModule = relative(outDir, entryAbs).replace(/\\/g, "/").replace(/\.[mc]?ts$/, "");
|
|
1569
2597
|
if (!modelsModule.startsWith(".")) modelsModule = `./${modelsModule}`;
|
|
1570
2598
|
const cdcRegistryFile = "cdc-registry.ts";
|
|
1571
2599
|
writeFileSync(
|
|
1572
|
-
|
|
2600
|
+
resolve2(outDir, cdcRegistryFile),
|
|
1573
2601
|
generateCdcRegistry({ modelsModule }),
|
|
1574
2602
|
"utf8"
|
|
1575
2603
|
);
|
|
@@ -1723,7 +2751,7 @@ var handlers = {
|
|
|
1723
2751
|
tblClass = tableClass;
|
|
1724
2752
|
}
|
|
1725
2753
|
try {
|
|
1726
|
-
await
|
|
2754
|
+
await importModelEntry(entry);
|
|
1727
2755
|
const manifest = buildManifest();
|
|
1728
2756
|
const streamMaintenanceDetected = streamMode === "auto" ? detectStreamMaintenance() : false;
|
|
1729
2757
|
const template = emitCloudformation(manifest, {
|
|
@@ -1746,7 +2774,7 @@ var handlers = {
|
|
|
1746
2774
|
autoscaleTarget: autoscaleTargetNum
|
|
1747
2775
|
});
|
|
1748
2776
|
if (out) {
|
|
1749
|
-
const outFile = isAbsolute(out) ? out :
|
|
2777
|
+
const outFile = isAbsolute(out) ? out : resolve2(process.cwd(), out);
|
|
1750
2778
|
mkdirSync(dirname(outFile), { recursive: true });
|
|
1751
2779
|
writeFileSync(outFile, template, "utf8");
|
|
1752
2780
|
process.stdout.write(
|
|
@@ -1763,6 +2791,131 @@ var handlers = {
|
|
|
1763
2791
|
const message = err instanceof Error ? err.message : String(err);
|
|
1764
2792
|
fail("GENERATION_FAILED", message, 1);
|
|
1765
2793
|
}
|
|
2794
|
+
},
|
|
2795
|
+
/**
|
|
2796
|
+
* `generate docs` (issue #183).
|
|
2797
|
+
*
|
|
2798
|
+
* Loads the entry model module (side-effecting import registers the entities)
|
|
2799
|
+
* plus the optional query / command definitions, builds the serializable
|
|
2800
|
+
* manifest + operations, then the normalized {@link buildDocModel} view model,
|
|
2801
|
+
* and renders the model-spec Markdown (Mermaid fences) via the Handlebars
|
|
2802
|
+
* generator to a single `index.md` in `--out`. `--template-dir` overrides
|
|
2803
|
+
* bundled partials per-name and `--helpers` registers extra Handlebars helpers.
|
|
2804
|
+
* Mirrors `generatePython`'s tsx-based entry loading and JSON error / exit shape.
|
|
2805
|
+
*
|
|
2806
|
+
* The Table Overview billing / streams are resolved from the CFn-shared flags
|
|
2807
|
+
* (`--billing-mode` / `--stream`, `stream auto` detecting `updateMode:'stream'`
|
|
2808
|
+
* maintenance like the CFn generator); deploy-time-only items with no flag
|
|
2809
|
+
* render "as configured at deployment" in the DocModel.
|
|
2810
|
+
*/
|
|
2811
|
+
async generateDocs(options) {
|
|
2812
|
+
const {
|
|
2813
|
+
entry,
|
|
2814
|
+
queries: queriesPath,
|
|
2815
|
+
out,
|
|
2816
|
+
format,
|
|
2817
|
+
split,
|
|
2818
|
+
templateDir,
|
|
2819
|
+
helpers: helpersPath,
|
|
2820
|
+
billingMode,
|
|
2821
|
+
stream
|
|
2822
|
+
} = options;
|
|
2823
|
+
if (!entry) fail("INVALID_ARGS", "Missing required option --entry.", 2);
|
|
2824
|
+
if (!out) fail("INVALID_ARGS", "Missing required option --out.", 2);
|
|
2825
|
+
if (format !== void 0 && format !== "markdown") {
|
|
2826
|
+
fail("INVALID_ARGS", `Invalid --format '${format}'. Use markdown.`, 2);
|
|
2827
|
+
}
|
|
2828
|
+
if (split !== void 0 && split !== "none" && split !== "entity") {
|
|
2829
|
+
fail("INVALID_ARGS", `Invalid --split '${split}'. Use none or entity.`, 2);
|
|
2830
|
+
}
|
|
2831
|
+
const outIsFile = out.toLowerCase().endsWith(".md");
|
|
2832
|
+
if (split === "entity" && outIsFile) {
|
|
2833
|
+
fail(
|
|
2834
|
+
"INVALID_ARGS",
|
|
2835
|
+
`--split entity writes multiple files and needs a directory --out, not a .md file ('${out}').`,
|
|
2836
|
+
2
|
|
2837
|
+
);
|
|
2838
|
+
}
|
|
2839
|
+
if (split === "entity") {
|
|
2840
|
+
fail(
|
|
2841
|
+
"INVALID_ARGS",
|
|
2842
|
+
`--split entity is not yet supported. Use --split none (a single index.md).`,
|
|
2843
|
+
2
|
|
2844
|
+
);
|
|
2845
|
+
}
|
|
2846
|
+
let billing = "PAY_PER_REQUEST";
|
|
2847
|
+
if (billingMode !== void 0) {
|
|
2848
|
+
if (billingMode !== "PAY_PER_REQUEST" && billingMode !== "PROVISIONED") {
|
|
2849
|
+
fail(
|
|
2850
|
+
"INVALID_ARGS",
|
|
2851
|
+
`Invalid --billing-mode '${billingMode}'. Use PAY_PER_REQUEST or PROVISIONED.`,
|
|
2852
|
+
2
|
|
2853
|
+
);
|
|
2854
|
+
}
|
|
2855
|
+
billing = billingMode;
|
|
2856
|
+
}
|
|
2857
|
+
let streamMode = "auto";
|
|
2858
|
+
if (stream !== void 0) {
|
|
2859
|
+
if (stream !== "auto" && stream !== "on" && stream !== "off") {
|
|
2860
|
+
fail("INVALID_ARGS", `Invalid --stream '${stream}'. Use auto, on, or off.`, 2);
|
|
2861
|
+
}
|
|
2862
|
+
streamMode = stream;
|
|
2863
|
+
}
|
|
2864
|
+
try {
|
|
2865
|
+
await importModelEntry(entry);
|
|
2866
|
+
const queries = await loadOptionalMap(queriesPath ?? entry, "queries");
|
|
2867
|
+
const commands = await loadOptionalMap(queriesPath ?? entry, "commands");
|
|
2868
|
+
const manifest = buildManifest();
|
|
2869
|
+
const operations = buildOperations(
|
|
2870
|
+
queries,
|
|
2871
|
+
commands
|
|
2872
|
+
);
|
|
2873
|
+
const streamsEnabled = streamMode === "on" ? true : streamMode === "off" ? false : detectStreamMaintenance();
|
|
2874
|
+
const docModel = buildDocModel(manifest, operations, MetadataRegistry, {
|
|
2875
|
+
billingMode: billing,
|
|
2876
|
+
streamsEnabled
|
|
2877
|
+
});
|
|
2878
|
+
let helpers;
|
|
2879
|
+
if (helpersPath) {
|
|
2880
|
+
const mod = await importModule(helpersPath);
|
|
2881
|
+
const exported = mod.helpers ?? mod.default;
|
|
2882
|
+
if (!exported || typeof exported !== "object") {
|
|
2883
|
+
fail(
|
|
2884
|
+
"INVALID_ARGS",
|
|
2885
|
+
`Module '${helpersPath}' must export a 'helpers' (or default) object mapping helper names to functions.`,
|
|
2886
|
+
2
|
|
2887
|
+
);
|
|
2888
|
+
}
|
|
2889
|
+
helpers = exported;
|
|
2890
|
+
}
|
|
2891
|
+
const files = generateDocs(docModel, {
|
|
2892
|
+
templateDir: templateDir ? isAbsolute(templateDir) ? templateDir : resolve2(process.cwd(), templateDir) : void 0,
|
|
2893
|
+
helpers
|
|
2894
|
+
});
|
|
2895
|
+
const outAbs = isAbsolute(out) ? out : resolve2(process.cwd(), out);
|
|
2896
|
+
const emitted = [];
|
|
2897
|
+
if (outIsFile) {
|
|
2898
|
+
const [contents] = Object.values(files);
|
|
2899
|
+
mkdirSync(dirname(outAbs), { recursive: true });
|
|
2900
|
+
writeFileSync(outAbs, contents, "utf8");
|
|
2901
|
+
emitted.push(out);
|
|
2902
|
+
process.stdout.write(
|
|
2903
|
+
JSON.stringify({ status: "ok", outDir: dirname(out), files: emitted }) + "\n"
|
|
2904
|
+
);
|
|
2905
|
+
} else {
|
|
2906
|
+
mkdirSync(outAbs, { recursive: true });
|
|
2907
|
+
for (const [name, contents] of Object.entries(files)) {
|
|
2908
|
+
writeFileSync(resolve2(outAbs, name), contents, "utf8");
|
|
2909
|
+
emitted.push(name);
|
|
2910
|
+
}
|
|
2911
|
+
process.stdout.write(
|
|
2912
|
+
JSON.stringify({ status: "ok", outDir: out, files: emitted }) + "\n"
|
|
2913
|
+
);
|
|
2914
|
+
}
|
|
2915
|
+
} catch (err) {
|
|
2916
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2917
|
+
fail("GENERATION_FAILED", message, 1);
|
|
2918
|
+
}
|
|
1766
2919
|
}
|
|
1767
2920
|
};
|
|
1768
2921
|
|