@prisma-next/cli 0.3.0-dev.53 → 0.3.0-dev.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +24 -0
  2. package/dist/cli.mjs +5 -3
  3. package/dist/cli.mjs.map +1 -1
  4. package/dist/{client-BSZKpZTF.mjs → client-B7f4PZZ1.mjs} +367 -170
  5. package/dist/client-B7f4PZZ1.mjs.map +1 -0
  6. package/dist/commands/contract-emit.d.mts.map +1 -1
  7. package/dist/commands/contract-emit.mjs +7 -6
  8. package/dist/commands/contract-emit.mjs.map +1 -1
  9. package/dist/commands/db-init.d.mts.map +1 -1
  10. package/dist/commands/db-init.mjs +28 -76
  11. package/dist/commands/db-init.mjs.map +1 -1
  12. package/dist/commands/db-introspect.d.mts.map +1 -1
  13. package/dist/commands/db-introspect.mjs +12 -17
  14. package/dist/commands/db-introspect.mjs.map +1 -1
  15. package/dist/commands/db-schema-verify.d.mts.map +1 -1
  16. package/dist/commands/db-schema-verify.mjs +5 -4
  17. package/dist/commands/db-schema-verify.mjs.map +1 -1
  18. package/dist/commands/db-sign.d.mts.map +1 -1
  19. package/dist/commands/db-sign.mjs +6 -5
  20. package/dist/commands/db-sign.mjs.map +1 -1
  21. package/dist/commands/db-update.d.mts +7 -0
  22. package/dist/commands/db-update.d.mts.map +1 -0
  23. package/dist/commands/db-update.mjs +120 -0
  24. package/dist/commands/db-update.mjs.map +1 -0
  25. package/dist/commands/db-verify.d.mts.map +1 -1
  26. package/dist/commands/db-verify.mjs +5 -4
  27. package/dist/commands/db-verify.mjs.map +1 -1
  28. package/dist/{config-loader-BJ8HsEdA.mjs → config-loader-DqKf1qSa.mjs} +1 -1
  29. package/dist/{config-loader-BJ8HsEdA.mjs.map → config-loader-DqKf1qSa.mjs.map} +1 -1
  30. package/dist/config-loader.mjs +1 -1
  31. package/dist/exports/control-api.d.mts +96 -6
  32. package/dist/exports/control-api.d.mts.map +1 -1
  33. package/dist/exports/control-api.mjs +2 -2
  34. package/dist/exports/index.mjs +1 -3
  35. package/dist/exports/index.mjs.map +1 -1
  36. package/dist/migration-command-scaffold-BELw_do2.mjs +95 -0
  37. package/dist/migration-command-scaffold-BELw_do2.mjs.map +1 -0
  38. package/dist/{result-handler-BZPY7HX4.mjs → result-handler-BhmrXIvT.mjs} +63 -13
  39. package/dist/result-handler-BhmrXIvT.mjs.map +1 -0
  40. package/package.json +16 -12
  41. package/src/cli.ts +5 -0
  42. package/src/commands/contract-emit.ts +22 -6
  43. package/src/commands/db-init.ts +89 -197
  44. package/src/commands/db-introspect.ts +4 -8
  45. package/src/commands/db-schema-verify.ts +11 -2
  46. package/src/commands/db-sign.ts +13 -4
  47. package/src/commands/db-update.ts +220 -0
  48. package/src/commands/db-verify.ts +11 -2
  49. package/src/control-api/client.ts +109 -145
  50. package/src/control-api/errors.ts +9 -0
  51. package/src/control-api/operations/db-init.ts +39 -34
  52. package/src/control-api/operations/db-update.ts +221 -0
  53. package/src/control-api/operations/extract-sql-ddl.ts +47 -0
  54. package/src/control-api/operations/migration-helpers.ts +49 -0
  55. package/src/control-api/types.ts +104 -4
  56. package/src/exports/control-api.ts +5 -0
  57. package/src/utils/cli-errors.ts +2 -0
  58. package/src/utils/command-helpers.ts +81 -3
  59. package/src/utils/migration-command-scaffold.ts +189 -0
  60. package/src/utils/output.ts +43 -13
  61. package/dist/client-BSZKpZTF.mjs.map +0 -1
  62. package/dist/result-handler-BZPY7HX4.mjs.map +0 -1
@@ -1,4 +1,4 @@
1
- import { CliStructuredError, errorConfigValidation, errorContractConfigMissing as errorContractConfigMissing$1, errorContractValidationFailed, errorDatabaseConnectionRequired, errorDriverRequired, errorFileNotFound, errorHashMismatch, errorJsonFormatNotSupported, errorMarkerMissing, errorMigrationPlanningFailed, errorRuntime, errorTargetMigrationNotSupported, errorTargetMismatch, errorUnexpected as errorUnexpected$1 } from "@prisma-next/core-control-plane/errors";
1
+ import { CliStructuredError, errorConfigValidation, errorContractConfigMissing as errorContractConfigMissing$1, errorContractValidationFailed, errorDatabaseConnectionRequired, errorDestructiveChanges, errorDriverRequired, errorFileNotFound, errorHashMismatch, errorJsonFormatNotSupported, errorMarkerMissing, errorMigrationPlanningFailed, errorRunnerFailed, errorRuntime, errorTargetMigrationNotSupported, errorTargetMismatch, errorUnexpected as errorUnexpected$1 } from "@prisma-next/core-control-plane/errors";
2
2
  import { notOk, ok } from "@prisma-next/utils/result";
3
3
  import { createControlPlaneStack } from "@prisma-next/core-control-plane/stack";
4
4
  import { ifDefined } from "@prisma-next/utils/defined";
@@ -57,6 +57,81 @@ function assertFrameworkComponentsCompatible(expectedFamilyId, expectedTargetId,
57
57
  return frameworkComponents;
58
58
  }
59
59
 
60
+ //#endregion
61
+ //#region src/control-api/errors.ts
62
+ var ContractValidationError = class extends Error {
63
+ cause;
64
+ constructor(message, cause) {
65
+ super(message);
66
+ this.name = "ContractValidationError";
67
+ this.cause = cause;
68
+ }
69
+ };
70
+
71
+ //#endregion
72
+ //#region src/control-api/operations/extract-sql-ddl.ts
73
+ function isDdlStatement(sqlStatement) {
74
+ const trimmed = sqlStatement.trim().toLowerCase();
75
+ return trimmed.startsWith("create ") || trimmed.startsWith("alter ") || trimmed.startsWith("drop ");
76
+ }
77
+ function hasExecuteSteps(operation) {
78
+ const candidate = operation;
79
+ if (!("execute" in candidate) || !Array.isArray(candidate["execute"])) return false;
80
+ return candidate["execute"].every((step) => typeof step === "object" && step !== null && "sql" in step);
81
+ }
82
+ /**
83
+ * Extracts a best-effort SQL DDL preview for CLI plan output.
84
+ * This helper is presentation-only and is never used to decide migration correctness.
85
+ */
86
+ function extractSqlDdl(operations) {
87
+ const statements = [];
88
+ for (const operation of operations) {
89
+ if (!hasExecuteSteps(operation)) continue;
90
+ for (const step of operation.execute) if (typeof step.sql === "string" && isDdlStatement(step.sql)) statements.push(step.sql.trim());
91
+ }
92
+ return statements;
93
+ }
94
+
95
+ //#endregion
96
+ //#region src/control-api/operations/migration-helpers.ts
97
+ /**
98
+ * Strips operation objects to their public shape (id, label, operationClass).
99
+ * Used at the API boundary to avoid leaking internal fields (precheck, execute, postcheck, etc.).
100
+ */
101
+ function stripOperations(operations) {
102
+ return operations.map((op) => ({
103
+ id: op.id,
104
+ label: op.label,
105
+ operationClass: op.operationClass
106
+ }));
107
+ }
108
+ /**
109
+ * Creates per-operation progress callbacks for the runner.
110
+ * Returns undefined when no onProgress callback is provided.
111
+ */
112
+ function createOperationCallbacks(onProgress, action, parentSpanId) {
113
+ if (!onProgress) return;
114
+ return {
115
+ onOperationStart: (op) => {
116
+ onProgress({
117
+ action,
118
+ kind: "spanStart",
119
+ spanId: `operation:${op.id}`,
120
+ parentSpanId,
121
+ label: op.label
122
+ });
123
+ },
124
+ onOperationComplete: (op) => {
125
+ onProgress({
126
+ action,
127
+ kind: "spanEnd",
128
+ spanId: `operation:${op.id}`,
129
+ outcome: "ok"
130
+ });
131
+ }
132
+ };
133
+ }
134
+
60
135
  //#endregion
61
136
  //#region src/control-api/operations/db-init.ts
62
137
  /**
@@ -128,7 +203,7 @@ async function executeDbInit(options) {
128
203
  action: "dbInit",
129
204
  kind: "spanStart",
130
205
  spanId: checkMarkerSpanId,
131
- label: "Checking contract marker"
206
+ label: "Checking database signature"
132
207
  });
133
208
  const existingMarker = await familyInstance.readMarker({ driver });
134
209
  if (existingMarker) {
@@ -142,16 +217,18 @@ async function executeDbInit(options) {
142
217
  return ok({
143
218
  mode,
144
219
  plan: { operations: [] },
145
- ...mode === "apply" ? {
146
- execution: {
147
- operationsPlanned: 0,
148
- operationsExecuted: 0
149
- },
150
- marker: {
151
- storageHash: existingMarker.storageHash,
152
- profileHash: existingMarker.profileHash
153
- }
154
- } : {},
220
+ destination: {
221
+ storageHash: migrationPlan.destination.storageHash,
222
+ ...ifDefined("profileHash", migrationPlan.destination.profileHash)
223
+ },
224
+ ...ifDefined("execution", mode === "apply" ? {
225
+ operationsPlanned: 0,
226
+ operationsExecuted: 0
227
+ } : void 0),
228
+ ...ifDefined("marker", mode === "apply" ? {
229
+ storageHash: existingMarker.storageHash,
230
+ profileHash: existingMarker.profileHash
231
+ } : void 0),
155
232
  summary: "Database already at target contract state"
156
233
  });
157
234
  }
@@ -183,11 +260,21 @@ async function executeDbInit(options) {
183
260
  spanId: checkMarkerSpanId,
184
261
  outcome: "ok"
185
262
  });
186
- if (mode === "plan") return ok({
187
- mode: "plan",
188
- plan: { operations: migrationPlan.operations },
189
- summary: `Planned ${migrationPlan.operations.length} operation(s)`
190
- });
263
+ if (mode === "plan") {
264
+ const planSql = familyInstance.familyId === "sql" ? extractSqlDdl(migrationPlan.operations) : void 0;
265
+ return ok({
266
+ mode: "plan",
267
+ plan: {
268
+ operations: stripOperations(migrationPlan.operations),
269
+ ...ifDefined("sql", planSql)
270
+ },
271
+ destination: {
272
+ storageHash: migrationPlan.destination.storageHash,
273
+ ...ifDefined("profileHash", migrationPlan.destination.profileHash)
274
+ },
275
+ summary: `Planned ${migrationPlan.operations.length} operation(s)`
276
+ });
277
+ }
191
278
  const applySpanId = "apply";
192
279
  onProgress?.({
193
280
  action: "dbInit",
@@ -195,31 +282,13 @@ async function executeDbInit(options) {
195
282
  spanId: applySpanId,
196
283
  label: "Applying migration plan"
197
284
  });
198
- const callbacks = onProgress ? {
199
- onOperationStart: (op) => {
200
- onProgress({
201
- action: "dbInit",
202
- kind: "spanStart",
203
- spanId: `operation:${op.id}`,
204
- parentSpanId: applySpanId,
205
- label: op.label
206
- });
207
- },
208
- onOperationComplete: (op) => {
209
- onProgress({
210
- action: "dbInit",
211
- kind: "spanEnd",
212
- spanId: `operation:${op.id}`,
213
- outcome: "ok"
214
- });
215
- }
216
- } : void 0;
285
+ const callbacks = createOperationCallbacks(onProgress, "dbInit", applySpanId);
217
286
  const runnerResult = await runner.execute({
218
287
  plan: migrationPlan,
219
288
  driver,
220
289
  destinationContract: contractIR,
221
290
  policy,
222
- ...callbacks ? { callbacks } : {},
291
+ ...ifDefined("callbacks", callbacks),
223
292
  executionChecks: {
224
293
  prechecks: false,
225
294
  postchecks: false,
@@ -251,7 +320,170 @@ async function executeDbInit(options) {
251
320
  });
252
321
  return ok({
253
322
  mode: "apply",
254
- plan: { operations: migrationPlan.operations },
323
+ plan: { operations: stripOperations(migrationPlan.operations) },
324
+ destination: {
325
+ storageHash: migrationPlan.destination.storageHash,
326
+ ...ifDefined("profileHash", migrationPlan.destination.profileHash)
327
+ },
328
+ execution: {
329
+ operationsPlanned: execution.operationsPlanned,
330
+ operationsExecuted: execution.operationsExecuted
331
+ },
332
+ marker: migrationPlan.destination.profileHash ? {
333
+ storageHash: migrationPlan.destination.storageHash,
334
+ profileHash: migrationPlan.destination.profileHash
335
+ } : { storageHash: migrationPlan.destination.storageHash },
336
+ summary: `Applied ${execution.operationsExecuted} operation(s), database signed`
337
+ });
338
+ }
339
+
340
+ //#endregion
341
+ //#region src/control-api/operations/db-update.ts
342
+ const DB_UPDATE_POLICY = { allowedOperationClasses: [
343
+ "additive",
344
+ "widening",
345
+ "destructive"
346
+ ] };
347
+ /**
348
+ * Executes the db update operation: introspect → plan → (optionally) apply → marker.
349
+ *
350
+ * db update is a pure reconciliation command: it introspects the live schema, plans the diff
351
+ * to the destination contract, and applies operations. The marker is bookkeeping only — written
352
+ * after apply so that `verify` and `db init` can reference it, but never read or validated
353
+ * by db update itself. The runner creates the marker table if it does not exist.
354
+ */
355
+ async function executeDbUpdate(options) {
356
+ const { driver, familyInstance, contractIR, mode, migrations, frameworkComponents, onProgress } = options;
357
+ const planner = migrations.createPlanner(familyInstance);
358
+ const runner = migrations.createRunner(familyInstance);
359
+ const introspectSpanId = "introspect";
360
+ onProgress?.({
361
+ action: "dbUpdate",
362
+ kind: "spanStart",
363
+ spanId: introspectSpanId,
364
+ label: "Introspecting database schema"
365
+ });
366
+ const schemaIR = await familyInstance.introspect({ driver });
367
+ onProgress?.({
368
+ action: "dbUpdate",
369
+ kind: "spanEnd",
370
+ spanId: introspectSpanId,
371
+ outcome: "ok"
372
+ });
373
+ const policy = DB_UPDATE_POLICY;
374
+ const planSpanId = "plan";
375
+ onProgress?.({
376
+ action: "dbUpdate",
377
+ kind: "spanStart",
378
+ spanId: planSpanId,
379
+ label: "Planning migration"
380
+ });
381
+ const plannerResult = await planner.plan({
382
+ contract: contractIR,
383
+ schema: schemaIR,
384
+ policy,
385
+ frameworkComponents
386
+ });
387
+ if (plannerResult.kind === "failure") {
388
+ onProgress?.({
389
+ action: "dbUpdate",
390
+ kind: "spanEnd",
391
+ spanId: planSpanId,
392
+ outcome: "error"
393
+ });
394
+ return notOk({
395
+ code: "PLANNING_FAILED",
396
+ summary: "Migration planning failed due to conflicts",
397
+ conflicts: plannerResult.conflicts,
398
+ why: void 0,
399
+ meta: void 0
400
+ });
401
+ }
402
+ onProgress?.({
403
+ action: "dbUpdate",
404
+ kind: "spanEnd",
405
+ spanId: planSpanId,
406
+ outcome: "ok"
407
+ });
408
+ const migrationPlan = plannerResult.plan;
409
+ if (mode === "plan") {
410
+ const planSql = familyInstance.familyId === "sql" ? extractSqlDdl(migrationPlan.operations) : void 0;
411
+ return ok({
412
+ mode: "plan",
413
+ plan: {
414
+ operations: stripOperations(migrationPlan.operations),
415
+ ...planSql !== void 0 ? { sql: planSql } : {}
416
+ },
417
+ destination: {
418
+ storageHash: migrationPlan.destination.storageHash,
419
+ ...ifDefined("profileHash", migrationPlan.destination.profileHash)
420
+ },
421
+ summary: `Planned ${migrationPlan.operations.length} operation(s)`
422
+ });
423
+ }
424
+ if (!options.acceptDataLoss) {
425
+ const destructiveOps = migrationPlan.operations.filter((op) => op.operationClass === "destructive").map((op) => ({
426
+ id: op.id,
427
+ label: op.label
428
+ }));
429
+ if (destructiveOps.length > 0) return notOk({
430
+ code: "DESTRUCTIVE_CHANGES",
431
+ summary: `Planned ${destructiveOps.length} destructive operation(s) that require confirmation`,
432
+ why: "Use --plan to preview destructive operations, then re-run with --accept-data-loss to apply",
433
+ conflicts: void 0,
434
+ meta: { destructiveOperations: destructiveOps }
435
+ });
436
+ }
437
+ const applySpanId = "apply";
438
+ onProgress?.({
439
+ action: "dbUpdate",
440
+ kind: "spanStart",
441
+ spanId: applySpanId,
442
+ label: "Applying migration plan"
443
+ });
444
+ const callbacks = createOperationCallbacks(onProgress, "dbUpdate", applySpanId);
445
+ const runnerResult = await runner.execute({
446
+ plan: migrationPlan,
447
+ driver,
448
+ destinationContract: contractIR,
449
+ policy,
450
+ ...callbacks ? { callbacks } : {},
451
+ executionChecks: {
452
+ prechecks: false,
453
+ postchecks: false,
454
+ idempotencyChecks: false
455
+ },
456
+ frameworkComponents
457
+ });
458
+ if (!runnerResult.ok) {
459
+ onProgress?.({
460
+ action: "dbUpdate",
461
+ kind: "spanEnd",
462
+ spanId: applySpanId,
463
+ outcome: "error"
464
+ });
465
+ return notOk({
466
+ code: "RUNNER_FAILED",
467
+ summary: runnerResult.failure.summary,
468
+ why: runnerResult.failure.why,
469
+ meta: runnerResult.failure.meta,
470
+ conflicts: void 0
471
+ });
472
+ }
473
+ const execution = runnerResult.value;
474
+ onProgress?.({
475
+ action: "dbUpdate",
476
+ kind: "spanEnd",
477
+ spanId: applySpanId,
478
+ outcome: "ok"
479
+ });
480
+ return ok({
481
+ mode: "apply",
482
+ plan: { operations: stripOperations(migrationPlan.operations) },
483
+ destination: {
484
+ storageHash: migrationPlan.destination.storageHash,
485
+ ...ifDefined("profileHash", migrationPlan.destination.profileHash)
486
+ },
255
487
  execution: {
256
488
  operationsPlanned: execution.operationsPlanned,
257
489
  operationsExecuted: execution.operationsExecuted
@@ -260,7 +492,7 @@ async function executeDbInit(options) {
260
492
  storageHash: migrationPlan.destination.storageHash,
261
493
  profileHash: migrationPlan.destination.profileHash
262
494
  } : { storageHash: migrationPlan.destination.storageHash },
263
- summary: `Applied ${execution.operationsExecuted} operation(s), marker written`
495
+ summary: execution.operationsExecuted === 0 ? "Database already matches contract, signature updated" : `Applied ${execution.operationsExecuted} operation(s), signature updated`
264
496
  });
265
497
  }
266
498
 
@@ -336,40 +568,47 @@ var ControlClientImpl = class {
336
568
  frameworkComponents: this.frameworkComponents
337
569
  };
338
570
  }
339
- async verify(options) {
340
- const { onProgress } = options;
341
- if (options.connection !== void 0) {
571
+ async connectWithProgress(connection, action, onProgress) {
572
+ if (connection === void 0) return;
573
+ onProgress?.({
574
+ action,
575
+ kind: "spanStart",
576
+ spanId: "connect",
577
+ label: "Connecting to database..."
578
+ });
579
+ try {
580
+ await this.connect(connection);
342
581
  onProgress?.({
343
- action: "verify",
344
- kind: "spanStart",
582
+ action,
583
+ kind: "spanEnd",
345
584
  spanId: "connect",
346
- label: "Connecting to database..."
585
+ outcome: "ok"
347
586
  });
348
- try {
349
- await this.connect(options.connection);
350
- onProgress?.({
351
- action: "verify",
352
- kind: "spanEnd",
353
- spanId: "connect",
354
- outcome: "ok"
355
- });
356
- } catch (error) {
357
- onProgress?.({
358
- action: "verify",
359
- kind: "spanEnd",
360
- spanId: "connect",
361
- outcome: "error"
362
- });
363
- throw error;
364
- }
587
+ } catch (error) {
588
+ onProgress?.({
589
+ action,
590
+ kind: "spanEnd",
591
+ spanId: "connect",
592
+ outcome: "error"
593
+ });
594
+ throw error;
365
595
  }
596
+ }
597
+ async verify(options) {
598
+ const { onProgress } = options;
599
+ await this.connectWithProgress(options.connection, "verify", onProgress);
366
600
  const { driver, familyInstance } = await this.ensureConnected();
367
- const contractIR = familyInstance.validateContractIR(options.contractIR);
601
+ let contractIR;
602
+ try {
603
+ contractIR = familyInstance.validateContractIR(options.contractIR);
604
+ } catch (error) {
605
+ throw new ContractValidationError(error instanceof Error ? error.message : String(error), error);
606
+ }
368
607
  onProgress?.({
369
608
  action: "verify",
370
609
  kind: "spanStart",
371
610
  spanId: "verify",
372
- label: "Verifying contract marker..."
611
+ label: "Verifying database signature..."
373
612
  });
374
613
  try {
375
614
  const result = await familyInstance.verify({
@@ -397,33 +636,14 @@ var ControlClientImpl = class {
397
636
  }
398
637
  async schemaVerify(options) {
399
638
  const { onProgress } = options;
400
- if (options.connection !== void 0) {
401
- onProgress?.({
402
- action: "schemaVerify",
403
- kind: "spanStart",
404
- spanId: "connect",
405
- label: "Connecting to database..."
406
- });
407
- try {
408
- await this.connect(options.connection);
409
- onProgress?.({
410
- action: "schemaVerify",
411
- kind: "spanEnd",
412
- spanId: "connect",
413
- outcome: "ok"
414
- });
415
- } catch (error) {
416
- onProgress?.({
417
- action: "schemaVerify",
418
- kind: "spanEnd",
419
- spanId: "connect",
420
- outcome: "error"
421
- });
422
- throw error;
423
- }
424
- }
639
+ await this.connectWithProgress(options.connection, "schemaVerify", onProgress);
425
640
  const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
426
- const contractIR = familyInstance.validateContractIR(options.contractIR);
641
+ let contractIR;
642
+ try {
643
+ contractIR = familyInstance.validateContractIR(options.contractIR);
644
+ } catch (error) {
645
+ throw new ContractValidationError(error instanceof Error ? error.message : String(error), error);
646
+ }
427
647
  onProgress?.({
428
648
  action: "schemaVerify",
429
649
  kind: "spanStart",
@@ -457,33 +677,14 @@ var ControlClientImpl = class {
457
677
  }
458
678
  async sign(options) {
459
679
  const { onProgress } = options;
460
- if (options.connection !== void 0) {
461
- onProgress?.({
462
- action: "sign",
463
- kind: "spanStart",
464
- spanId: "connect",
465
- label: "Connecting to database..."
466
- });
467
- try {
468
- await this.connect(options.connection);
469
- onProgress?.({
470
- action: "sign",
471
- kind: "spanEnd",
472
- spanId: "connect",
473
- outcome: "ok"
474
- });
475
- } catch (error) {
476
- onProgress?.({
477
- action: "sign",
478
- kind: "spanEnd",
479
- spanId: "connect",
480
- outcome: "error"
481
- });
482
- throw error;
483
- }
484
- }
680
+ await this.connectWithProgress(options.connection, "sign", onProgress);
485
681
  const { driver, familyInstance } = await this.ensureConnected();
486
- const contractIR = familyInstance.validateContractIR(options.contractIR);
682
+ let contractIR;
683
+ try {
684
+ contractIR = familyInstance.validateContractIR(options.contractIR);
685
+ } catch (error) {
686
+ throw new ContractValidationError(error instanceof Error ? error.message : String(error), error);
687
+ }
487
688
  onProgress?.({
488
689
  action: "sign",
489
690
  kind: "spanStart",
@@ -516,70 +717,50 @@ var ControlClientImpl = class {
516
717
  }
517
718
  async dbInit(options) {
518
719
  const { onProgress } = options;
519
- if (options.connection !== void 0) {
520
- onProgress?.({
521
- action: "dbInit",
522
- kind: "spanStart",
523
- spanId: "connect",
524
- label: "Connecting to database..."
525
- });
526
- try {
527
- await this.connect(options.connection);
528
- onProgress?.({
529
- action: "dbInit",
530
- kind: "spanEnd",
531
- spanId: "connect",
532
- outcome: "ok"
533
- });
534
- } catch (error) {
535
- onProgress?.({
536
- action: "dbInit",
537
- kind: "spanEnd",
538
- spanId: "connect",
539
- outcome: "error"
540
- });
541
- throw error;
542
- }
543
- }
720
+ await this.connectWithProgress(options.connection, "dbInit", onProgress);
544
721
  const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
545
722
  if (!this.options.target.migrations) throw new Error(`Target "${this.options.target.targetId}" does not support migrations`);
723
+ let contractIR;
724
+ try {
725
+ contractIR = familyInstance.validateContractIR(options.contractIR);
726
+ } catch (error) {
727
+ throw new ContractValidationError(error instanceof Error ? error.message : String(error), error);
728
+ }
546
729
  return executeDbInit({
547
730
  driver,
548
731
  familyInstance,
549
- contractIR: familyInstance.validateContractIR(options.contractIR),
732
+ contractIR,
733
+ mode: options.mode,
734
+ migrations: this.options.target.migrations,
735
+ frameworkComponents,
736
+ ...ifDefined("onProgress", onProgress)
737
+ });
738
+ }
739
+ async dbUpdate(options) {
740
+ const { onProgress } = options;
741
+ await this.connectWithProgress(options.connection, "dbUpdate", onProgress);
742
+ const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
743
+ if (!this.options.target.migrations) throw new Error(`Target "${this.options.target.targetId}" does not support migrations`);
744
+ let contractIR;
745
+ try {
746
+ contractIR = familyInstance.validateContractIR(options.contractIR);
747
+ } catch (error) {
748
+ throw new ContractValidationError(error instanceof Error ? error.message : String(error), error);
749
+ }
750
+ return executeDbUpdate({
751
+ driver,
752
+ familyInstance,
753
+ contractIR,
550
754
  mode: options.mode,
551
755
  migrations: this.options.target.migrations,
552
756
  frameworkComponents,
553
- ...onProgress ? { onProgress } : {}
757
+ ...ifDefined("acceptDataLoss", options.acceptDataLoss),
758
+ ...ifDefined("onProgress", onProgress)
554
759
  });
555
760
  }
556
761
  async introspect(options) {
557
762
  const onProgress = options?.onProgress;
558
- if (options?.connection !== void 0) {
559
- onProgress?.({
560
- action: "introspect",
561
- kind: "spanStart",
562
- spanId: "connect",
563
- label: "Connecting to database..."
564
- });
565
- try {
566
- await this.connect(options.connection);
567
- onProgress?.({
568
- action: "introspect",
569
- kind: "spanEnd",
570
- spanId: "connect",
571
- outcome: "ok"
572
- });
573
- } catch (error) {
574
- onProgress?.({
575
- action: "introspect",
576
- kind: "spanEnd",
577
- spanId: "connect",
578
- outcome: "error"
579
- });
580
- throw error;
581
- }
582
- }
763
+ await this.connectWithProgress(options?.connection, "introspect", onProgress);
583
764
  const { driver, familyInstance } = await this.ensureConnected();
584
765
  options?.schema;
585
766
  onProgress?.({
@@ -675,6 +856,22 @@ var ControlClientImpl = class {
675
856
  label: "Emitting contract..."
676
857
  });
677
858
  try {
859
+ try {
860
+ this.familyInstance.validateContractIR(contractRaw);
861
+ } catch (error) {
862
+ onProgress?.({
863
+ action: "emit",
864
+ kind: "spanEnd",
865
+ spanId: "emit",
866
+ outcome: "error"
867
+ });
868
+ return notOk({
869
+ code: "CONTRACT_VALIDATION_FAILED",
870
+ summary: "Contract validation failed",
871
+ why: error instanceof Error ? error.message : String(error),
872
+ meta: void 0
873
+ });
874
+ }
678
875
  const emitResult = await this.familyInstance.emitContract({ contractIR: contractRaw });
679
876
  onProgress?.({
680
877
  action: "emit",
@@ -707,5 +904,5 @@ var ControlClientImpl = class {
707
904
  };
708
905
 
709
906
  //#endregion
710
- export { errorDatabaseConnectionRequired as a, errorHashMismatch as c, errorMigrationPlanningFailed as d, errorRuntime as f, errorUnexpected$1 as h, errorContractValidationFailed as i, errorJsonFormatNotSupported as l, errorTargetMismatch as m, CliStructuredError as n, errorDriverRequired as o, errorTargetMigrationNotSupported as p, errorContractConfigMissing$1 as r, errorFileNotFound as s, createControlClient as t, errorMarkerMissing as u };
711
- //# sourceMappingURL=client-BSZKpZTF.mjs.map
907
+ export { errorTargetMismatch as _, errorContractValidationFailed as a, errorDriverRequired as c, errorJsonFormatNotSupported as d, errorMarkerMissing as f, errorTargetMigrationNotSupported as g, errorRuntime as h, errorContractConfigMissing$1 as i, errorFileNotFound as l, errorRunnerFailed as m, ContractValidationError as n, errorDatabaseConnectionRequired as o, errorMigrationPlanningFailed as p, CliStructuredError as r, errorDestructiveChanges as s, createControlClient as t, errorHashMismatch as u, errorUnexpected$1 as v };
908
+ //# sourceMappingURL=client-B7f4PZZ1.mjs.map