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