@prisma-next/cli 0.3.0-dev.1 → 0.3.0-pr.73.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -23,20 +23,6 @@ Provide a command-line interface that:
23
23
  - **Help Output Formatting**: Custom styled help output with command trees and formatted descriptions
24
24
  - **Config Management**: Load and validate `prisma-next.config.ts` files using Arktype validation
25
25
 
26
- ### Wiring validation
27
-
28
- The CLI performs **wiring validation** at the composition boundary: it ensures the emitted contract artifacts are compatible with the descriptors wired in `prisma-next.config.ts`.
29
-
30
- This prevents runtime mismatches (for example: a contract that declares extension packs, but a config that doesn’t provide the matching descriptors).
31
-
32
- Commands that enforce wiring validation:
33
- - **`db verify`**
34
- - **`db introspect`** (when a contract is provided)
35
- - **`db sign`**
36
- - **`db init`**
37
-
38
- If you hit a wiring validation error: add the required descriptors to `config.extensionPacks` (matched by descriptor `id`) and re-run the command.
39
-
40
26
  **Note**: Control plane domain actions (database verification, contract emission) are implemented in `@prisma-next/core-control-plane`. The CLI uses the control plane domain actions programmatically but does not define control plane types itself.
41
27
 
42
28
  ## Command Descriptions
@@ -116,7 +102,7 @@ prisma-next db verify [--db <url>] [--config <path>] [--json] [-v] [-q] [--times
116
102
  ```
117
103
 
118
104
  Options:
119
- - `--db <url>`: Database connection string (optional; defaults to `config.db.url` if set)
105
+ - `--db <url>`: Database connection string (optional, falls back to `config.db.url` or `DATABASE_URL` environment variable)
120
106
  - `--config <path>`: Optional. Path to `prisma-next.config.ts` (defaults to `./prisma-next.config.ts` if present)
121
107
  - `--json`: Output as JSON object
122
108
  - `-q, --quiet`: Quiet mode (errors only)
@@ -173,7 +159,7 @@ export default defineConfig({
173
159
 
174
160
  1. **Load Contract**: Reads the emitted `contract.json` from `config.contract.output`
175
161
  2. **Connect to Database**: Uses `config.driver.create(url)` to create a driver
176
- 3. **Create Family Instance**: Calls `config.family.create()` with target, adapter, driver, and `extensionPacks` (passed as `extensions`) to create a family instance
162
+ 3. **Create Family Instance**: Calls `config.family.create()` with target, adapter, driver, and extensions to create a family instance
177
163
  4. **Verify**: Calls `familyInstance.verify()` which:
178
164
  - Reads the contract marker from the database
179
165
  - Compares marker presence: Returns `PN-RTM-3001` if marker is missing
@@ -270,7 +256,7 @@ prisma-next db introspect [--db <url>] [--config <path>] [--json] [-v] [-q] [--t
270
256
  ```
271
257
 
272
258
  Options:
273
- - `--db <url>`: Database connection string (optional; defaults to `config.db.url` if set)
259
+ - `--db <url>`: Database connection string (optional, falls back to `config.db.url` or `DATABASE_URL` environment variable)
274
260
  - `--config <path>`: Optional. Path to `prisma-next.config.ts` (defaults to `./prisma-next.config.ts` if present)
275
261
  - `--json`: Output as JSON object
276
262
  - `-q, --quiet`: Quiet mode (errors only)
@@ -320,7 +306,7 @@ export default defineConfig({
320
306
  **Introspection Process:**
321
307
 
322
308
  1. **Connect to Database**: Uses `config.driver.create(url)` to create a driver
323
- 2. **Create Family Instance**: Calls `config.family.create()` with target, adapter, driver, and `extensionPacks` (passed as `extensions`) to create a family instance
309
+ 2. **Create Family Instance**: Calls `config.family.create()` with target, adapter, driver, and extensions to create a family instance
324
310
  3. **Introspect**: Calls `familyInstance.introspect()` which:
325
311
  - Queries the database catalog to discover schema structure
326
312
  - Returns a family-specific schema IR (e.g., `SqlSchemaIR` for SQL family)
@@ -387,7 +373,7 @@ sql schema (tables: 2)
387
373
 
388
374
  **Error Codes:**
389
375
  - `PN-CLI-4010`: Missing driver in config — provide a driver descriptor
390
- - `PN-CLI-4005`: Missing database URL — provide `--db <url>` or set `db.url` in config
376
+ - `PN-CLI-4011`: Missing database URL — provide `--db` flag or `config.db.url` or `DATABASE_URL` environment variable
391
377
 
392
378
  **Family Requirements:**
393
379
 
@@ -421,7 +407,7 @@ prisma-next db sign [--db <url>] [--config <path>] [--json] [-v] [-q] [--timesta
421
407
  ```
422
408
 
423
409
  Options:
424
- - `--db <url>`: Database connection string (optional; defaults to `config.db.url` if set)
410
+ - `--db <url>`: Database connection string (optional, falls back to `config.db.url` or `DATABASE_URL` environment variable)
425
411
  - `--config <path>`: Optional. Path to `prisma-next.config.ts` (defaults to `./prisma-next.config.ts` if present)
426
412
  - `--json`: Output as JSON object
427
413
  - `-q, --quiet`: Quiet mode (errors only)
@@ -478,7 +464,7 @@ export default defineConfig({
478
464
 
479
465
  1. **Load Contract**: Reads the emitted `contract.json` from `config.contract.output`
480
466
  2. **Connect to Database**: Uses `config.driver.create(url)` to create a driver
481
- 3. **Create Family Instance**: Calls `config.family.create()` with target, adapter, driver, and `extensionPacks` (passed as `extensions`) to create a family instance
467
+ 3. **Create Family Instance**: Calls `config.family.create()` with target, adapter, driver, and extensions to create a family instance
482
468
  4. **Schema Verification (Precondition)**: Calls `familyInstance.schemaVerify()` to verify the database schema matches the contract:
483
469
  - If verification fails: Prints schema verification output and exits with code 1 (marker is not written)
484
470
  - If verification passes: Proceeds to marker signing
@@ -584,7 +570,7 @@ For updated markers:
584
570
 
585
571
  **Error Codes:**
586
572
  - `PN-CLI-4010`: Missing driver in config — provide a driver descriptor
587
- - `PN-CLI-4005`: Missing database URL — provide `--db <url>` or set `db.url` in config
573
+ - `PN-CLI-4011`: Missing database URL — provide `--db` flag or `config.db.url` or `DATABASE_URL` environment variable
588
574
  - Exit code 1: Schema verification failed — database schema does not match contract (marker is not written)
589
575
 
590
576
  **Relationship to Other Commands:**
@@ -623,7 +609,7 @@ The SQL family provides this via `@prisma-next/family-sql/control`. The `sign()`
623
609
 
624
610
  ### `prisma-next db init`
625
611
 
626
- Initialize a database schema from the contract. This command plans and applies **additive-only** operations (create missing tables/columns/constraints/indexes) until the database satisfies the contract, then writes the contract marker.
612
+ Initialize a database schema from the contract. This command generates a migration plan to bring an empty database in sync with the contract and executes it.
627
613
 
628
614
  **Command:**
629
615
  ```bash
@@ -631,10 +617,10 @@ prisma-next db init [--db <url>] [--config <path>] [--plan] [--json] [-v] [-q] [
631
617
  ```
632
618
 
633
619
  Options:
634
- - `--db <url>`: Database connection string (optional; defaults to `config.db.url` if set)
620
+ - `--db <url>`: Database connection string (optional, falls back to `config.db.url` or `DATABASE_URL` environment variable)
635
621
  - `--config <path>`: Optional. Path to `prisma-next.config.ts` (defaults to `./prisma-next.config.ts` if present)
636
622
  - `--plan`: Only show the migration plan, do not apply it
637
- - `--json [format]`: Output as JSON (`object` only; `ndjson` is not supported for this command)
623
+ - `--json`: Output as JSON object
638
624
  - `-q, --quiet`: Quiet mode (errors only)
639
625
  - `-v, --verbose`: Verbose output (debug info, timings)
640
626
  - `-vv, --trace`: Trace output (deep internals, stack traces)
@@ -689,56 +675,51 @@ export default defineConfig({
689
675
 
690
676
  1. **Load Contract**: Reads the emitted `contract.json` from `config.contract.output`
691
677
  2. **Connect to Database**: Uses `config.driver.create(url)` to create a driver
692
- 3. **Create Family Instance**: Calls `config.family.create()` with target, adapter, driver, and `extensionPacks` (passed as `extensions`)
678
+ 3. **Create Family Instance**: Calls `config.family.create()` with target, adapter, driver, and extensions
693
679
  4. **Introspect Schema**: Calls `familyInstance.introspect()` to get the current database schema IR
694
- 5. **Validate wiring**: Ensures the contract is compatible with the CLI config:
695
- - `contract.targetFamily` matches `config.family.familyId`
696
- - `contract.target` matches `config.target.targetId`
697
- - `contract.extensionPacks` (if present) are provided by `config.extensionPacks` (matched by descriptor `id`)
698
- 6. **Create Planner/Runner**: Uses `config.target.migrations.createPlanner()` and `config.target.migrations.createRunner()`
699
- 7. **Plan Migration**: Calls `planner.plan()` with the contract IR, schema IR, additive-only policy, and `frameworkComponents` (the active target/adapter/extension descriptors)
680
+ 5. **Create Planner/Runner**: Asks the target to construct a `MigrationPlanner` and `MigrationRunner`
681
+ 6. **Plan Migration**: Calls `planner.plan()` with the contract IR, schema IR, and additive-only policy
700
682
  - On conflict: Returns a structured failure with conflict list
701
683
  - On success: Returns a migration plan with operations
702
- 8. **Apply Migration** (if not `--plan`):
684
+ 7. **Apply Migration** (if not `--plan`):
703
685
  - Calls `runner.execute()` to apply the plan
686
+ - Runner executes pre/post checks for each operation
704
687
  - After execution, verifies schema matches contract
705
- - Writes contract marker (and records a ledger entry via the target runner)
688
+ - Writes contract marker and ledger entry
706
689
 
707
690
  **Output Format (TTY - Plan Mode):**
708
691
 
709
692
  ```
710
- prisma-next db init ➜ Bootstrap a database to match the current contract
693
+ prisma-next db init ➜ Initialize database schema from contract
711
694
  config: prisma-next.config.ts
712
695
  contract: src/prisma/contract.json
713
- mode: plan (dry run)
714
-
715
- ✔ Planned 4 operation(s)
716
-
717
- ├─ Create table user [additive]
718
- ├─ Add unique constraint user_email_key on user [additive]
719
- ├─ Create index user_email_idx on user [additive]
720
- └─ Add foreign key post_userId_fkey on post [additive]
696
+ database: localhost:5432/mydb
721
697
 
722
- Destination hash: sha256:abc123...
698
+ Migration Plan (4 operations)
699
+ ├─ Create extension "plpgsql"
700
+ ├─ Create table "user"
701
+ │ ├─ id: int4 (not null, primary key)
702
+ │ └─ email: text (not null)
703
+ └─ Create unique index "user_email_key"
723
704
 
724
- This is a dry run. No changes were applied.
725
- Run without --plan to apply changes.
705
+ Run without --plan to apply this migration.
726
706
  ```
727
707
 
728
708
  **Output Format (TTY - Apply Mode):**
729
709
 
730
710
  ```
731
- prisma-next db init ➜ Bootstrap a database to match the current contract
711
+ prisma-next db init ➜ Initialize database schema from contract
732
712
  config: prisma-next.config.ts
733
713
  contract: src/prisma/contract.json
714
+ database: localhost:5432/mydb
734
715
 
735
- Applying migration plan and verifying schema...
736
- Create table user...
737
- Add unique constraint user_email_key on user...
738
- Create index user_email_idx on user...
739
- Add foreign key post_userId_fkey on post...
740
- Applied 4 operation(s)
741
- Marker written: sha256:abc123...
716
+ Applied 4 migration operations
717
+ ├─ Created extension "plpgsql"
718
+ ├─ Created table "user"
719
+ └─ Created unique index "user_email_key"
720
+ Contract marker written
721
+ coreHash: sha256:abc123...
722
+ Ledger entry recorded
742
723
  ```
743
724
 
744
725
  **Output Format (JSON):**
@@ -748,41 +729,33 @@ Applying migration plan and verifying schema...
748
729
  "ok": true,
749
730
  "mode": "apply",
750
731
  "plan": {
751
- "targetId": "postgres",
752
- "destination": {
753
- "coreHash": "sha256:abc123..."
754
- },
755
- "operations": [
756
- {
757
- "id": "table.user",
758
- "label": "Create table user",
759
- "operationClass": "additive"
760
- }
761
- ]
732
+ "operations": [...],
733
+ "conflicts": []
762
734
  },
763
735
  "execution": {
764
- "operationsPlanned": 4,
765
- "operationsExecuted": 4
766
- },
767
- "marker": {
768
- "coreHash": "sha256:abc123..."
736
+ "operations": [...],
737
+ "marker": {
738
+ "coreHash": "sha256:abc123...",
739
+ "profileHash": "sha256:def456..."
740
+ },
741
+ "ledger": {
742
+ "id": "ledger-entry-uuid",
743
+ "createdAt": "2025-01-01T00:00:00Z"
744
+ }
769
745
  }
770
746
  }
771
747
  ```
772
748
 
773
749
  **Error Codes:**
774
- - `PN-CLI-4004`: Contract file not found
775
- - `PN-CLI-4005`: Missing database URL
776
- - `PN-CLI-4008`: Unsupported JSON format (`--json ndjson` is rejected for `db init`)
777
- - `PN-CLI-4010`: Missing driver in config
778
- - `PN-CLI-4020`: Migration planning failed (conflicts)
779
- - `PN-CLI-4021`: Target does not support migrations
780
- - `PN-RTM-3000`: Runtime error (includes marker mismatch failures)
781
-
782
- **Behavior Notes:**
783
-
784
- - If the database already has a marker that matches the destination contract, `db init` succeeds as a noop (0 operations planned/executed).
785
- - If the database has a marker that does **not** match the destination contract, `db init` fails (including in `--plan` mode). Use `db init` for bootstrapping; use your migration workflow to reconcile existing databases.
750
+ - `PN-CLI-4010`: Missing driver in config — provide a driver descriptor
751
+ - `PN-CLI-4011`: Missing database URL — provide `--db` flag or `config.db.url` or `DATABASE_URL` environment variable
752
+ - `PN-CLI-4011`: Migration planning failed due to conflicts (exit code 1)
753
+ - `PN-CLI-4012`: Target does not support migrations
754
+
755
+ **Current Limitations (v1):**
756
+ - Only supports empty databases (no existing tables)
757
+ - Non-empty databases result in a planning failure with conflict details
758
+ - Future `db update` command will support additive changes to existing schemas
786
759
 
787
760
  **Config File (`prisma-next.config.ts`):**
788
761
 
@@ -91,4 +91,4 @@ export {
91
91
  assertFrameworkComponentsCompatible,
92
92
  assertContractRequirementsSatisfied
93
93
  };
94
- //# sourceMappingURL=chunk-ZKYEJROM.js.map
94
+ //# sourceMappingURL=chunk-C7QQMZ3I.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils/framework-components.ts"],"sourcesContent":["import {\n checkContractComponentRequirements,\n type TargetBoundComponentDescriptor,\n} from '@prisma-next/contract/framework-components';\nimport type { ContractIR } from '@prisma-next/contract/ir';\nimport type {\n ControlAdapterDescriptor,\n ControlExtensionDescriptor,\n ControlFamilyDescriptor,\n ControlTargetDescriptor,\n} from '@prisma-next/core-control-plane/types';\nimport { errorConfigValidation, errorContractMissingExtensionPacks } from './cli-errors';\n\n/**\n * Asserts that all framework components are compatible with the expected family and target.\n *\n * This function validates that each component in the framework components array:\n * - Has kind 'target', 'adapter', 'extension', or 'driver'\n * - Has familyId matching expectedFamilyId\n * - Has targetId matching expectedTargetId\n *\n * This validation happens at the CLI composition boundary, before passing components\n * to typed planner/runner instances. It fills the gap between runtime validation\n * (via `validateConfig()`) and compile-time type enforcement.\n *\n * @param expectedFamilyId - The expected family ID (e.g., 'sql')\n * @param expectedTargetId - The expected target ID (e.g., 'postgres')\n * @param frameworkComponents - Array of framework components to validate\n * @returns The same array typed as TargetBoundComponentDescriptor\n * @throws CliStructuredError if any component is incompatible\n *\n * @example\n * ```ts\n * const config = await loadConfig();\n * const frameworkComponents = [config.target, config.adapter, ...(config.extensionPacks ?? [])];\n *\n * // Validate and type-narrow components before passing to planner\n * const typedComponents = assertFrameworkComponentsCompatible(\n * config.family.familyId,\n * config.target.targetId,\n * frameworkComponents\n * );\n *\n * const planner = target.migrations.createPlanner(familyInstance);\n * planner.plan({ contract, schema, policy, frameworkComponents: typedComponents });\n * ```\n */\nexport function assertFrameworkComponentsCompatible<\n TFamilyId extends string,\n TTargetId extends string,\n>(\n expectedFamilyId: TFamilyId,\n expectedTargetId: TTargetId,\n frameworkComponents: ReadonlyArray<unknown>,\n): ReadonlyArray<TargetBoundComponentDescriptor<TFamilyId, TTargetId>> {\n for (let i = 0; i < frameworkComponents.length; i++) {\n const component = frameworkComponents[i];\n\n // Check that component is an object\n if (typeof component !== 'object' || component === null) {\n throw errorConfigValidation('frameworkComponents[]', {\n why: `Framework component at index ${i} must be an object`,\n });\n }\n\n const record = component as Record<string, unknown>;\n\n // Check kind\n if (!Object.hasOwn(record, 'kind')) {\n throw errorConfigValidation('frameworkComponents[].kind', {\n why: `Framework component at index ${i} must have 'kind' property`,\n });\n }\n\n const kind = record['kind'];\n if (kind !== 'target' && kind !== 'adapter' && kind !== 'extension' && kind !== 'driver') {\n throw errorConfigValidation('frameworkComponents[].kind', {\n why: `Framework component at index ${i} has invalid kind '${String(kind)}' (must be 'target', 'adapter', 'extension', or 'driver')`,\n });\n }\n\n // Check familyId\n if (!Object.hasOwn(record, 'familyId')) {\n throw errorConfigValidation('frameworkComponents[].familyId', {\n why: `Framework component at index ${i} (kind: ${String(kind)}) must have 'familyId' property`,\n });\n }\n\n const familyId = record['familyId'];\n if (familyId !== expectedFamilyId) {\n throw errorConfigValidation('frameworkComponents[].familyId', {\n why: `Framework component at index ${i} (kind: ${String(kind)}) has familyId '${String(familyId)}' but expected '${expectedFamilyId}'`,\n });\n }\n\n // Check targetId\n if (!Object.hasOwn(record, 'targetId')) {\n throw errorConfigValidation('frameworkComponents[].targetId', {\n why: `Framework component at index ${i} (kind: ${String(kind)}) must have 'targetId' property`,\n });\n }\n\n const targetId = record['targetId'];\n if (targetId !== expectedTargetId) {\n throw errorConfigValidation('frameworkComponents[].targetId', {\n why: `Framework component at index ${i} (kind: ${String(kind)}) has targetId '${String(targetId)}' but expected '${expectedTargetId}'`,\n });\n }\n }\n\n // Type assertion is safe because we've validated all components above\n return frameworkComponents as ReadonlyArray<TargetBoundComponentDescriptor<TFamilyId, TTargetId>>;\n}\n\n/**\n * Validates that a contract is compatible with the configured family, target, adapter,\n * and extension packs. Throws on family/target mismatches or missing extension packs.\n *\n * This check ensures the emitted contract matches the CLI config before running\n * commands that depend on the contract (e.g., db verify, db sign).\n *\n * @param contract - The contract IR to validate (must include targetFamily, target, extensionPacks).\n * @param family - The configured family descriptor.\n * @param target - The configured target descriptor.\n * @param adapter - The configured adapter descriptor.\n * @param extensionPacks - Optional array of extension descriptors provided by the config.\n *\n * @throws {CliStructuredError} errorConfigValidation when contract.targetFamily or contract.target\n * doesn't match the configured family/target.\n * @throws {CliStructuredError} errorContractMissingExtensionPacks when the contract requires\n * extension packs that are not provided in the config (includes all missing packs in error.meta).\n *\n * @example\n * ```ts\n * import { assertContractRequirementsSatisfied } from './framework-components';\n *\n * const config = await loadConfig();\n * const contractIR = await loadContractJson(config.contract.output);\n *\n * // Throws if contract is incompatible with config\n * assertContractRequirementsSatisfied({\n * contract: contractIR,\n * family: config.family,\n * target: config.target,\n * adapter: config.adapter,\n * extensionPacks: config.extensionPacks,\n * });\n * ```\n */\nexport function assertContractRequirementsSatisfied<\n TFamilyId extends string,\n TTargetId extends string,\n>({\n contract,\n family,\n target,\n adapter,\n extensionPacks,\n}: {\n readonly contract: Pick<ContractIR, 'targetFamily' | 'target' | 'extensionPacks'>;\n readonly family: ControlFamilyDescriptor<TFamilyId>;\n readonly target: ControlTargetDescriptor<TFamilyId, TTargetId>;\n readonly adapter: ControlAdapterDescriptor<TFamilyId, TTargetId>;\n readonly extensionPacks?: readonly ControlExtensionDescriptor<TFamilyId, TTargetId>[] | undefined;\n}): void {\n const providedComponentIds = new Set<string>([target.id, adapter.id]);\n for (const extension of extensionPacks ?? []) {\n providedComponentIds.add(extension.id);\n }\n\n const result = checkContractComponentRequirements({\n contract,\n expectedTargetFamily: family.familyId,\n expectedTargetId: target.targetId,\n providedComponentIds,\n });\n\n if (result.familyMismatch) {\n throw errorConfigValidation('contract.targetFamily', {\n why: `Contract was emitted for family '${result.familyMismatch.actual}' but CLI config is wired to '${result.familyMismatch.expected}'.`,\n });\n }\n\n if (result.targetMismatch) {\n throw errorConfigValidation('contract.target', {\n why: `Contract target '${result.targetMismatch.actual}' does not match CLI target '${result.targetMismatch.expected}'.`,\n });\n }\n\n if (result.missingExtensionPackIds.length > 0) {\n throw errorContractMissingExtensionPacks({\n missingExtensionPacks: result.missingExtensionPackIds,\n providedComponentIds: [...providedComponentIds],\n });\n }\n}\n"],"mappings":";;;;;;AAAA;AAAA,EACE;AAAA,OAEK;AA4CA,SAAS,oCAId,kBACA,kBACA,qBACqE;AACrE,WAAS,IAAI,GAAG,IAAI,oBAAoB,QAAQ,KAAK;AACnD,UAAM,YAAY,oBAAoB,CAAC;AAGvC,QAAI,OAAO,cAAc,YAAY,cAAc,MAAM;AACvD,YAAM,sBAAsB,yBAAyB;AAAA,QACnD,KAAK,gCAAgC,CAAC;AAAA,MACxC,CAAC;AAAA,IACH;AAEA,UAAM,SAAS;AAGf,QAAI,CAAC,OAAO,OAAO,QAAQ,MAAM,GAAG;AAClC,YAAM,sBAAsB,8BAA8B;AAAA,QACxD,KAAK,gCAAgC,CAAC;AAAA,MACxC,CAAC;AAAA,IACH;AAEA,UAAM,OAAO,OAAO,MAAM;AAC1B,QAAI,SAAS,YAAY,SAAS,aAAa,SAAS,eAAe,SAAS,UAAU;AACxF,YAAM,sBAAsB,8BAA8B;AAAA,QACxD,KAAK,gCAAgC,CAAC,sBAAsB,OAAO,IAAI,CAAC;AAAA,MAC1E,CAAC;AAAA,IACH;AAGA,QAAI,CAAC,OAAO,OAAO,QAAQ,UAAU,GAAG;AACtC,YAAM,sBAAsB,kCAAkC;AAAA,QAC5D,KAAK,gCAAgC,CAAC,WAAW,OAAO,IAAI,CAAC;AAAA,MAC/D,CAAC;AAAA,IACH;AAEA,UAAM,WAAW,OAAO,UAAU;AAClC,QAAI,aAAa,kBAAkB;AACjC,YAAM,sBAAsB,kCAAkC;AAAA,QAC5D,KAAK,gCAAgC,CAAC,WAAW,OAAO,IAAI,CAAC,mBAAmB,OAAO,QAAQ,CAAC,mBAAmB,gBAAgB;AAAA,MACrI,CAAC;AAAA,IACH;AAGA,QAAI,CAAC,OAAO,OAAO,QAAQ,UAAU,GAAG;AACtC,YAAM,sBAAsB,kCAAkC;AAAA,QAC5D,KAAK,gCAAgC,CAAC,WAAW,OAAO,IAAI,CAAC;AAAA,MAC/D,CAAC;AAAA,IACH;AAEA,UAAM,WAAW,OAAO,UAAU;AAClC,QAAI,aAAa,kBAAkB;AACjC,YAAM,sBAAsB,kCAAkC;AAAA,QAC5D,KAAK,gCAAgC,CAAC,WAAW,OAAO,IAAI,CAAC,mBAAmB,OAAO,QAAQ,CAAC,mBAAmB,gBAAgB;AAAA,MACrI,CAAC;AAAA,IACH;AAAA,EACF;AAGA,SAAO;AACT;AAqCO,SAAS,oCAGd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMS;AACP,QAAM,uBAAuB,oBAAI,IAAY,CAAC,OAAO,IAAI,QAAQ,EAAE,CAAC;AACpE,aAAW,aAAa,kBAAkB,CAAC,GAAG;AAC5C,yBAAqB,IAAI,UAAU,EAAE;AAAA,EACvC;AAEA,QAAM,SAAS,mCAAmC;AAAA,IAChD;AAAA,IACA,sBAAsB,OAAO;AAAA,IAC7B,kBAAkB,OAAO;AAAA,IACzB;AAAA,EACF,CAAC;AAED,MAAI,OAAO,gBAAgB;AACzB,UAAM,sBAAsB,yBAAyB;AAAA,MACnD,KAAK,oCAAoC,OAAO,eAAe,MAAM,iCAAiC,OAAO,eAAe,QAAQ;AAAA,IACtI,CAAC;AAAA,EACH;AAEA,MAAI,OAAO,gBAAgB;AACzB,UAAM,sBAAsB,mBAAmB;AAAA,MAC7C,KAAK,oBAAoB,OAAO,eAAe,MAAM,gCAAgC,OAAO,eAAe,QAAQ;AAAA,IACrH,CAAC;AAAA,EACH;AAEA,MAAI,OAAO,wBAAwB,SAAS,GAAG;AAC7C,UAAM,mCAAmC;AAAA,MACvC,uBAAuB,OAAO;AAAA,MAC9B,sBAAsB,CAAC,GAAG,oBAAoB;AAAA,IAChD,CAAC;AAAA,EACH;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/utils/framework-components.ts"],"sourcesContent":["import {\n checkContractComponentRequirements,\n type TargetBoundComponentDescriptor,\n} from '@prisma-next/contract/framework-components';\nimport type { ContractIR } from '@prisma-next/contract/ir';\nimport type {\n ControlAdapterDescriptor,\n ControlExtensionDescriptor,\n ControlFamilyDescriptor,\n ControlTargetDescriptor,\n} from '@prisma-next/core-control-plane/types';\nimport { errorConfigValidation, errorContractMissingExtensionPacks } from './cli-errors';\n\n/**\n * Asserts that all framework components are compatible with the expected family and target.\n *\n * This function validates that each component in the framework components array:\n * - Has kind 'target', 'adapter', 'extension', or 'driver'\n * - Has familyId matching expectedFamilyId\n * - Has targetId matching expectedTargetId\n *\n * This validation happens at the CLI composition boundary, before passing components\n * to typed planner/runner instances. It fills the gap between runtime validation\n * (via `validateConfig()`) and compile-time type enforcement.\n *\n * @param expectedFamilyId - The expected family ID (e.g., 'sql')\n * @param expectedTargetId - The expected target ID (e.g., 'postgres')\n * @param frameworkComponents - Array of framework components to validate\n * @returns The same array typed as TargetBoundComponentDescriptor\n * @throws CliStructuredError if any component is incompatible\n *\n * @example\n * ```ts\n * const config = await loadConfig();\n * const frameworkComponents = [config.target, config.adapter, ...(config.extensionPacks ?? [])];\n *\n * // Validate and type-narrow components before passing to planner\n * const typedComponents = assertFrameworkComponentsCompatible(\n * config.family.familyId,\n * config.target.targetId,\n * frameworkComponents\n * );\n *\n * const planner = target.migrations.createPlanner(familyInstance);\n * planner.plan({ contract, schema, policy, frameworkComponents: typedComponents });\n * ```\n */\nexport function assertFrameworkComponentsCompatible<\n TFamilyId extends string,\n TTargetId extends string,\n>(\n expectedFamilyId: TFamilyId,\n expectedTargetId: TTargetId,\n frameworkComponents: ReadonlyArray<unknown>,\n): ReadonlyArray<TargetBoundComponentDescriptor<TFamilyId, TTargetId>> {\n for (let i = 0; i < frameworkComponents.length; i++) {\n const component = frameworkComponents[i];\n\n // Check that component is an object\n if (typeof component !== 'object' || component === null) {\n throw errorConfigValidation('frameworkComponents[]', {\n why: `Framework component at index ${i} must be an object`,\n });\n }\n\n const record = component as Record<string, unknown>;\n\n // Check kind\n if (!Object.hasOwn(record, 'kind')) {\n throw errorConfigValidation('frameworkComponents[].kind', {\n why: `Framework component at index ${i} must have 'kind' property`,\n });\n }\n\n const kind = record['kind'];\n if (kind !== 'target' && kind !== 'adapter' && kind !== 'extension' && kind !== 'driver') {\n throw errorConfigValidation('frameworkComponents[].kind', {\n why: `Framework component at index ${i} has invalid kind '${String(kind)}' (must be 'target', 'adapter', 'extension', or 'driver')`,\n });\n }\n\n // Check familyId\n if (!Object.hasOwn(record, 'familyId')) {\n throw errorConfigValidation('frameworkComponents[].familyId', {\n why: `Framework component at index ${i} (kind: ${String(kind)}) must have 'familyId' property`,\n });\n }\n\n const familyId = record['familyId'];\n if (familyId !== expectedFamilyId) {\n throw errorConfigValidation('frameworkComponents[].familyId', {\n why: `Framework component at index ${i} (kind: ${String(kind)}) has familyId '${String(familyId)}' but expected '${expectedFamilyId}'`,\n });\n }\n\n // Check targetId\n if (!Object.hasOwn(record, 'targetId')) {\n throw errorConfigValidation('frameworkComponents[].targetId', {\n why: `Framework component at index ${i} (kind: ${String(kind)}) must have 'targetId' property`,\n });\n }\n\n const targetId = record['targetId'];\n if (targetId !== expectedTargetId) {\n throw errorConfigValidation('frameworkComponents[].targetId', {\n why: `Framework component at index ${i} (kind: ${String(kind)}) has targetId '${String(targetId)}' but expected '${expectedTargetId}'`,\n });\n }\n }\n\n // Type assertion is safe because we've validated all components above\n return frameworkComponents as ReadonlyArray<TargetBoundComponentDescriptor<TFamilyId, TTargetId>>;\n}\n\n/**\n * Validates that a contract is compatible with the configured family, target, adapter,\n * and extension packs. Throws on family/target mismatches or missing extension packs.\n *\n * This check ensures the emitted contract matches the CLI config before running\n * commands that depend on the contract (e.g., db verify, db sign).\n *\n * @param contract - The contract IR to validate (must include targetFamily, target, extensionPacks).\n * @param family - The configured family descriptor.\n * @param target - The configured target descriptor.\n * @param adapter - The configured adapter descriptor.\n * @param extensionPacks - Optional array of extension descriptors provided by the config.\n *\n * @throws {CliStructuredError} errorConfigValidation when contract.targetFamily or contract.target\n * doesn't match the configured family/target.\n * @throws {CliStructuredError} errorContractMissingExtensionPacks when the contract requires\n * extension packs that are not provided in the config (includes all missing packs in error.meta).\n *\n * @example\n * ```ts\n * import { assertContractRequirementsSatisfied } from './framework-components';\n *\n * const config = await loadConfig();\n * const contractIR = await loadContractJson(config.contract.output);\n *\n * // Throws if contract is incompatible with config\n * assertContractRequirementsSatisfied({\n * contract: contractIR,\n * family: config.family,\n * target: config.target,\n * adapter: config.adapter,\n * extensionPacks: config.extensions,\n * });\n * ```\n */\nexport function assertContractRequirementsSatisfied<\n TFamilyId extends string,\n TTargetId extends string,\n>({\n contract,\n family,\n target,\n adapter,\n extensionPacks,\n}: {\n readonly contract: Pick<ContractIR, 'targetFamily' | 'target' | 'extensionPacks'>;\n readonly family: ControlFamilyDescriptor<TFamilyId>;\n readonly target: ControlTargetDescriptor<TFamilyId, TTargetId>;\n readonly adapter: ControlAdapterDescriptor<TFamilyId, TTargetId>;\n readonly extensionPacks?: readonly ControlExtensionDescriptor<TFamilyId, TTargetId>[] | undefined;\n}): void {\n const providedComponentIds = new Set<string>([target.id, adapter.id]);\n for (const extension of extensionPacks ?? []) {\n providedComponentIds.add(extension.id);\n }\n\n const result = checkContractComponentRequirements({\n contract,\n expectedTargetFamily: family.familyId,\n expectedTargetId: target.targetId,\n providedComponentIds,\n });\n\n if (result.familyMismatch) {\n throw errorConfigValidation('contract.targetFamily', {\n why: `Contract was emitted for family '${result.familyMismatch.actual}' but CLI config is wired to '${result.familyMismatch.expected}'.`,\n });\n }\n\n if (result.targetMismatch) {\n throw errorConfigValidation('contract.target', {\n why: `Contract target '${result.targetMismatch.actual}' does not match CLI target '${result.targetMismatch.expected}'.`,\n });\n }\n\n if (result.missingExtensionPackIds.length > 0) {\n throw errorContractMissingExtensionPacks({\n missingExtensionPacks: result.missingExtensionPackIds,\n providedComponentIds: [...providedComponentIds],\n });\n }\n}\n"],"mappings":";;;;;;AAAA;AAAA,EACE;AAAA,OAEK;AA4CA,SAAS,oCAId,kBACA,kBACA,qBACqE;AACrE,WAAS,IAAI,GAAG,IAAI,oBAAoB,QAAQ,KAAK;AACnD,UAAM,YAAY,oBAAoB,CAAC;AAGvC,QAAI,OAAO,cAAc,YAAY,cAAc,MAAM;AACvD,YAAM,sBAAsB,yBAAyB;AAAA,QACnD,KAAK,gCAAgC,CAAC;AAAA,MACxC,CAAC;AAAA,IACH;AAEA,UAAM,SAAS;AAGf,QAAI,CAAC,OAAO,OAAO,QAAQ,MAAM,GAAG;AAClC,YAAM,sBAAsB,8BAA8B;AAAA,QACxD,KAAK,gCAAgC,CAAC;AAAA,MACxC,CAAC;AAAA,IACH;AAEA,UAAM,OAAO,OAAO,MAAM;AAC1B,QAAI,SAAS,YAAY,SAAS,aAAa,SAAS,eAAe,SAAS,UAAU;AACxF,YAAM,sBAAsB,8BAA8B;AAAA,QACxD,KAAK,gCAAgC,CAAC,sBAAsB,OAAO,IAAI,CAAC;AAAA,MAC1E,CAAC;AAAA,IACH;AAGA,QAAI,CAAC,OAAO,OAAO,QAAQ,UAAU,GAAG;AACtC,YAAM,sBAAsB,kCAAkC;AAAA,QAC5D,KAAK,gCAAgC,CAAC,WAAW,OAAO,IAAI,CAAC;AAAA,MAC/D,CAAC;AAAA,IACH;AAEA,UAAM,WAAW,OAAO,UAAU;AAClC,QAAI,aAAa,kBAAkB;AACjC,YAAM,sBAAsB,kCAAkC;AAAA,QAC5D,KAAK,gCAAgC,CAAC,WAAW,OAAO,IAAI,CAAC,mBAAmB,OAAO,QAAQ,CAAC,mBAAmB,gBAAgB;AAAA,MACrI,CAAC;AAAA,IACH;AAGA,QAAI,CAAC,OAAO,OAAO,QAAQ,UAAU,GAAG;AACtC,YAAM,sBAAsB,kCAAkC;AAAA,QAC5D,KAAK,gCAAgC,CAAC,WAAW,OAAO,IAAI,CAAC;AAAA,MAC/D,CAAC;AAAA,IACH;AAEA,UAAM,WAAW,OAAO,UAAU;AAClC,QAAI,aAAa,kBAAkB;AACjC,YAAM,sBAAsB,kCAAkC;AAAA,QAC5D,KAAK,gCAAgC,CAAC,WAAW,OAAO,IAAI,CAAC,mBAAmB,OAAO,QAAQ,CAAC,mBAAmB,gBAAgB;AAAA,MACrI,CAAC;AAAA,IACH;AAAA,EACF;AAGA,SAAO;AACT;AAqCO,SAAS,oCAGd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMS;AACP,QAAM,uBAAuB,oBAAI,IAAY,CAAC,OAAO,IAAI,QAAQ,EAAE,CAAC;AACpE,aAAW,aAAa,kBAAkB,CAAC,GAAG;AAC5C,yBAAqB,IAAI,UAAU,EAAE;AAAA,EACvC;AAEA,QAAM,SAAS,mCAAmC;AAAA,IAChD;AAAA,IACA,sBAAsB,OAAO;AAAA,IAC7B,kBAAkB,OAAO;AAAA,IACzB;AAAA,EACF,CAAC;AAED,MAAI,OAAO,gBAAgB;AACzB,UAAM,sBAAsB,yBAAyB;AAAA,MACnD,KAAK,oCAAoC,OAAO,eAAe,MAAM,iCAAiC,OAAO,eAAe,QAAQ;AAAA,IACtI,CAAC;AAAA,EACH;AAEA,MAAI,OAAO,gBAAgB;AACzB,UAAM,sBAAsB,mBAAmB;AAAA,MAC7C,KAAK,oBAAoB,OAAO,eAAe,MAAM,gCAAgC,OAAO,eAAe,QAAQ;AAAA,IACrH,CAAC;AAAA,EACH;AAEA,MAAI,OAAO,wBAAwB,SAAS,GAAG;AAC7C,UAAM,mCAAmC;AAAA,MACvC,uBAAuB,OAAO;AAAA,MAC9B,sBAAsB,CAAC,GAAG,oBAAoB;AAAA,IAChD,CAAC;AAAA,EACH;AACF;","names":[]}