@prisma-next/cli 0.3.0-dev.52 → 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.
- package/README.md +24 -0
- package/dist/cli.mjs +5 -3
- package/dist/cli.mjs.map +1 -1
- package/dist/{client-BSZKpZTF.mjs → client-B7f4PZZ1.mjs} +367 -170
- package/dist/client-B7f4PZZ1.mjs.map +1 -0
- package/dist/commands/contract-emit.d.mts.map +1 -1
- package/dist/commands/contract-emit.mjs +7 -6
- package/dist/commands/contract-emit.mjs.map +1 -1
- package/dist/commands/db-init.d.mts.map +1 -1
- package/dist/commands/db-init.mjs +28 -76
- package/dist/commands/db-init.mjs.map +1 -1
- package/dist/commands/db-introspect.d.mts.map +1 -1
- package/dist/commands/db-introspect.mjs +12 -17
- package/dist/commands/db-introspect.mjs.map +1 -1
- package/dist/commands/db-schema-verify.d.mts.map +1 -1
- package/dist/commands/db-schema-verify.mjs +5 -4
- package/dist/commands/db-schema-verify.mjs.map +1 -1
- package/dist/commands/db-sign.d.mts.map +1 -1
- package/dist/commands/db-sign.mjs +6 -5
- package/dist/commands/db-sign.mjs.map +1 -1
- package/dist/commands/db-update.d.mts +7 -0
- package/dist/commands/db-update.d.mts.map +1 -0
- package/dist/commands/db-update.mjs +120 -0
- package/dist/commands/db-update.mjs.map +1 -0
- package/dist/commands/db-verify.d.mts.map +1 -1
- package/dist/commands/db-verify.mjs +5 -4
- package/dist/commands/db-verify.mjs.map +1 -1
- package/dist/{config-loader-BJ8HsEdA.mjs → config-loader-DqKf1qSa.mjs} +1 -1
- package/dist/{config-loader-BJ8HsEdA.mjs.map → config-loader-DqKf1qSa.mjs.map} +1 -1
- package/dist/config-loader.mjs +1 -1
- package/dist/exports/control-api.d.mts +96 -6
- package/dist/exports/control-api.d.mts.map +1 -1
- package/dist/exports/control-api.mjs +2 -2
- package/dist/exports/index.mjs +1 -3
- package/dist/exports/index.mjs.map +1 -1
- package/dist/migration-command-scaffold-BELw_do2.mjs +95 -0
- package/dist/migration-command-scaffold-BELw_do2.mjs.map +1 -0
- package/dist/{result-handler-BZPY7HX4.mjs → result-handler-BhmrXIvT.mjs} +63 -13
- package/dist/result-handler-BhmrXIvT.mjs.map +1 -0
- package/package.json +16 -12
- package/src/cli.ts +5 -0
- package/src/commands/contract-emit.ts +22 -6
- package/src/commands/db-init.ts +89 -197
- package/src/commands/db-introspect.ts +4 -8
- package/src/commands/db-schema-verify.ts +11 -2
- package/src/commands/db-sign.ts +13 -4
- package/src/commands/db-update.ts +220 -0
- package/src/commands/db-verify.ts +11 -2
- package/src/control-api/client.ts +109 -145
- package/src/control-api/errors.ts +9 -0
- package/src/control-api/operations/db-init.ts +39 -34
- package/src/control-api/operations/db-update.ts +221 -0
- package/src/control-api/operations/extract-sql-ddl.ts +47 -0
- package/src/control-api/operations/migration-helpers.ts +49 -0
- package/src/control-api/types.ts +104 -4
- package/src/exports/control-api.ts +5 -0
- package/src/utils/cli-errors.ts +2 -0
- package/src/utils/command-helpers.ts +81 -3
- package/src/utils/migration-command-scaffold.ts +189 -0
- package/src/utils/output.ts +43 -13
- package/dist/client-BSZKpZTF.mjs.map +0 -1
- package/dist/result-handler-BZPY7HX4.mjs.map +0 -1
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
2
|
|
|
3
|
+
const longDescriptions = new WeakMap<Command, string>();
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* Sets both short and long descriptions for a command.
|
|
5
7
|
* The short description is used in command trees and headers.
|
|
@@ -12,8 +14,7 @@ export function setCommandDescriptions(
|
|
|
12
14
|
): Command {
|
|
13
15
|
command.description(shortDescription);
|
|
14
16
|
if (longDescription) {
|
|
15
|
-
|
|
16
|
-
(command as Command & { _longDescription?: string })._longDescription = longDescription;
|
|
17
|
+
longDescriptions.set(command, longDescription);
|
|
17
18
|
}
|
|
18
19
|
return command;
|
|
19
20
|
}
|
|
@@ -22,5 +23,82 @@ export function setCommandDescriptions(
|
|
|
22
23
|
* Gets the long description from a command if it was set via setCommandDescriptions.
|
|
23
24
|
*/
|
|
24
25
|
export function getLongDescription(command: Command): string | undefined {
|
|
25
|
-
return (command
|
|
26
|
+
return longDescriptions.get(command);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Shared CLI options interface for migration commands (db init, db update).
|
|
31
|
+
* These are the Commander.js parsed options common to both commands.
|
|
32
|
+
*/
|
|
33
|
+
export interface MigrationCommandOptions {
|
|
34
|
+
readonly db?: string;
|
|
35
|
+
readonly config?: string;
|
|
36
|
+
readonly plan?: boolean;
|
|
37
|
+
readonly json?: string | boolean;
|
|
38
|
+
readonly quiet?: boolean;
|
|
39
|
+
readonly q?: boolean;
|
|
40
|
+
readonly verbose?: boolean;
|
|
41
|
+
readonly v?: boolean;
|
|
42
|
+
readonly vv?: boolean;
|
|
43
|
+
readonly trace?: boolean;
|
|
44
|
+
readonly timestamps?: boolean;
|
|
45
|
+
readonly color?: boolean;
|
|
46
|
+
readonly 'no-color'?: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Masks credentials in a database connection URL.
|
|
51
|
+
* Handles standard URLs (username + password + query params) and libpq-style key=value strings.
|
|
52
|
+
*/
|
|
53
|
+
export function maskConnectionUrl(url: string): string {
|
|
54
|
+
try {
|
|
55
|
+
const parsed = new URL(url);
|
|
56
|
+
if (parsed.username) {
|
|
57
|
+
parsed.username = '****';
|
|
58
|
+
}
|
|
59
|
+
if (parsed.password) {
|
|
60
|
+
parsed.password = '****';
|
|
61
|
+
}
|
|
62
|
+
// Also mask password in query parameters (e.g., ?password=secret, ?sslpassword=secret)
|
|
63
|
+
for (const key of [...parsed.searchParams.keys()]) {
|
|
64
|
+
if (/password/i.test(key)) {
|
|
65
|
+
parsed.searchParams.set(key, '****');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return parsed.toString();
|
|
69
|
+
} catch {
|
|
70
|
+
// Fallback for libpq-style key=value connection strings (e.g., "host=localhost password=secret user=admin")
|
|
71
|
+
return url
|
|
72
|
+
.replace(/password\s*=\s*\S+/gi, 'password=****')
|
|
73
|
+
.replace(/user\s*=\s*\S+/gi, 'user=****');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Strips raw connection URL fragments from an error message to prevent credential leakage.
|
|
79
|
+
* Call this before surfacing driver errors to the user.
|
|
80
|
+
*/
|
|
81
|
+
export function sanitizeErrorMessage(message: string, connectionUrl?: string): string {
|
|
82
|
+
if (!connectionUrl) {
|
|
83
|
+
return message;
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
const parsed = new URL(connectionUrl);
|
|
87
|
+
// Replace the full URL (with and without trailing slash)
|
|
88
|
+
let sanitized = message;
|
|
89
|
+
sanitized = sanitized.replaceAll(connectionUrl, maskConnectionUrl(connectionUrl));
|
|
90
|
+
// Also replace the password and username individually if they appear
|
|
91
|
+
if (parsed.password) {
|
|
92
|
+
sanitized = sanitized.replaceAll(parsed.password, '****');
|
|
93
|
+
}
|
|
94
|
+
if (parsed.username) {
|
|
95
|
+
sanitized = sanitized.replaceAll(parsed.username, '****');
|
|
96
|
+
}
|
|
97
|
+
return sanitized;
|
|
98
|
+
} catch {
|
|
99
|
+
// For libpq-style strings, mask password and user values in the message
|
|
100
|
+
return message
|
|
101
|
+
.replace(/password\s*=\s*\S+/gi, 'password=****')
|
|
102
|
+
.replace(/user\s*=\s*\S+/gi, 'user=****');
|
|
103
|
+
}
|
|
26
104
|
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { relative, resolve } from 'node:path';
|
|
3
|
+
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
4
|
+
import type { Command } from 'commander';
|
|
5
|
+
import { loadConfig } from '../config-loader';
|
|
6
|
+
import { createControlClient } from '../control-api/client';
|
|
7
|
+
import type { ControlClient } from '../control-api/types';
|
|
8
|
+
import {
|
|
9
|
+
type CliStructuredError,
|
|
10
|
+
errorContractValidationFailed,
|
|
11
|
+
errorDatabaseConnectionRequired,
|
|
12
|
+
errorDriverRequired,
|
|
13
|
+
errorFileNotFound,
|
|
14
|
+
errorTargetMigrationNotSupported,
|
|
15
|
+
errorUnexpected,
|
|
16
|
+
} from './cli-errors';
|
|
17
|
+
import { maskConnectionUrl } from './command-helpers';
|
|
18
|
+
import type { GlobalFlags } from './global-flags';
|
|
19
|
+
import { formatStyledHeader } from './output';
|
|
20
|
+
import { createProgressAdapter } from './progress-adapter';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Resolved context for a migration command.
|
|
24
|
+
* Contains everything needed to invoke a control-api operation.
|
|
25
|
+
*/
|
|
26
|
+
export interface MigrationContext {
|
|
27
|
+
readonly client: ControlClient;
|
|
28
|
+
readonly contractJson: Record<string, unknown>;
|
|
29
|
+
readonly dbConnection: unknown;
|
|
30
|
+
readonly onProgress: ReturnType<typeof createProgressAdapter>;
|
|
31
|
+
readonly configPath: string;
|
|
32
|
+
readonly contractPath: string;
|
|
33
|
+
readonly contractPathAbsolute: string;
|
|
34
|
+
readonly config: Awaited<ReturnType<typeof loadConfig>>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Command-specific configuration for the shared scaffold.
|
|
39
|
+
*/
|
|
40
|
+
export interface MigrationCommandDescriptor {
|
|
41
|
+
readonly commandName: string;
|
|
42
|
+
readonly description: string;
|
|
43
|
+
readonly url: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Prepares the shared context for migration commands (db init, db update).
|
|
48
|
+
*
|
|
49
|
+
* Handles: config loading, contract file reading, JSON parsing, connection resolution,
|
|
50
|
+
* driver/migration-support validation, client creation, and header output.
|
|
51
|
+
*
|
|
52
|
+
* Returns a Result with either the resolved context or a structured error.
|
|
53
|
+
*/
|
|
54
|
+
export async function prepareMigrationContext(
|
|
55
|
+
options: { readonly db?: string; readonly config?: string; readonly plan?: boolean },
|
|
56
|
+
flags: GlobalFlags,
|
|
57
|
+
descriptor: MigrationCommandDescriptor,
|
|
58
|
+
): Promise<Result<MigrationContext, CliStructuredError>> {
|
|
59
|
+
// Load config
|
|
60
|
+
const config = await loadConfig(options.config);
|
|
61
|
+
const configPath = options.config
|
|
62
|
+
? relative(process.cwd(), resolve(options.config))
|
|
63
|
+
: 'prisma-next.config.ts';
|
|
64
|
+
const contractPathAbsolute = config.contract?.output
|
|
65
|
+
? resolve(config.contract.output)
|
|
66
|
+
: resolve('src/prisma/contract.json');
|
|
67
|
+
const contractPath = relative(process.cwd(), contractPathAbsolute);
|
|
68
|
+
|
|
69
|
+
// Output header
|
|
70
|
+
if (flags.json !== 'object' && !flags.quiet) {
|
|
71
|
+
const details: Array<{ label: string; value: string }> = [
|
|
72
|
+
{ label: 'config', value: configPath },
|
|
73
|
+
{ label: 'contract', value: contractPath },
|
|
74
|
+
];
|
|
75
|
+
if (options.db) {
|
|
76
|
+
details.push({ label: 'database', value: maskConnectionUrl(options.db) });
|
|
77
|
+
}
|
|
78
|
+
if (options.plan) {
|
|
79
|
+
details.push({ label: 'mode', value: 'plan (dry run)' });
|
|
80
|
+
}
|
|
81
|
+
const header = formatStyledHeader({
|
|
82
|
+
command: descriptor.commandName,
|
|
83
|
+
description: descriptor.description,
|
|
84
|
+
url: descriptor.url,
|
|
85
|
+
details,
|
|
86
|
+
flags,
|
|
87
|
+
});
|
|
88
|
+
console.log(header);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Load contract file
|
|
92
|
+
let contractJsonContent: string;
|
|
93
|
+
try {
|
|
94
|
+
contractJsonContent = await readFile(contractPathAbsolute, 'utf-8');
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {
|
|
97
|
+
return notOk(
|
|
98
|
+
errorFileNotFound(contractPathAbsolute, {
|
|
99
|
+
why: `Contract file not found at ${contractPathAbsolute}`,
|
|
100
|
+
fix: `Run \`prisma-next contract emit\` to generate ${contractPath}, or update \`config.contract.output\` in ${configPath}`,
|
|
101
|
+
}),
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
return notOk(
|
|
105
|
+
errorUnexpected(error instanceof Error ? error.message : String(error), {
|
|
106
|
+
why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`,
|
|
107
|
+
}),
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Parse contract JSON
|
|
112
|
+
let contractJson: Record<string, unknown>;
|
|
113
|
+
try {
|
|
114
|
+
contractJson = JSON.parse(contractJsonContent) as Record<string, unknown>;
|
|
115
|
+
} catch (error) {
|
|
116
|
+
return notOk(
|
|
117
|
+
errorContractValidationFailed(
|
|
118
|
+
`Contract JSON is invalid: ${error instanceof Error ? error.message : String(error)}`,
|
|
119
|
+
{ where: { path: contractPathAbsolute } },
|
|
120
|
+
),
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Resolve database connection (--db flag or config.db.connection)
|
|
125
|
+
const dbConnection = options.db ?? config.db?.connection;
|
|
126
|
+
if (!dbConnection) {
|
|
127
|
+
return notOk(
|
|
128
|
+
errorDatabaseConnectionRequired({
|
|
129
|
+
why: `Database connection is required for ${descriptor.commandName} (set db.connection in ${configPath}, or pass --db <url>)`,
|
|
130
|
+
}),
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Check for driver
|
|
135
|
+
if (!config.driver) {
|
|
136
|
+
return notOk(
|
|
137
|
+
errorDriverRequired({ why: `Config.driver is required for ${descriptor.commandName}` }),
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Check target supports migrations
|
|
142
|
+
if (!config.target.migrations) {
|
|
143
|
+
return notOk(
|
|
144
|
+
errorTargetMigrationNotSupported({
|
|
145
|
+
why: `Target "${config.target.id}" does not support migrations`,
|
|
146
|
+
}),
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Create control client
|
|
151
|
+
const client = createControlClient({
|
|
152
|
+
family: config.family,
|
|
153
|
+
target: config.target,
|
|
154
|
+
adapter: config.adapter,
|
|
155
|
+
driver: config.driver,
|
|
156
|
+
extensionPacks: config.extensionPacks ?? [],
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Create progress adapter
|
|
160
|
+
const onProgress = createProgressAdapter({ flags });
|
|
161
|
+
|
|
162
|
+
return ok({
|
|
163
|
+
client,
|
|
164
|
+
contractJson,
|
|
165
|
+
dbConnection,
|
|
166
|
+
onProgress,
|
|
167
|
+
configPath,
|
|
168
|
+
contractPath,
|
|
169
|
+
contractPathAbsolute,
|
|
170
|
+
config,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Registers the shared CLI options for migration commands (db init, db update).
|
|
176
|
+
*/
|
|
177
|
+
export function addMigrationCommandOptions(command: Command): Command {
|
|
178
|
+
return command
|
|
179
|
+
.option('--db <url>', 'Database connection string')
|
|
180
|
+
.option('--config <path>', 'Path to prisma-next.config.ts')
|
|
181
|
+
.option('--plan', 'Preview planned operations without applying', false)
|
|
182
|
+
.option('--json [format]', 'Output as JSON (object)', false)
|
|
183
|
+
.option('-q, --quiet', 'Quiet mode: errors only')
|
|
184
|
+
.option('-v, --verbose', 'Verbose output: debug info, timings')
|
|
185
|
+
.option('-vv, --trace', 'Trace output: deep internals, stack traces')
|
|
186
|
+
.option('--timestamps', 'Add timestamps to output')
|
|
187
|
+
.option('--color', 'Force color output')
|
|
188
|
+
.option('--no-color', 'Disable color output');
|
|
189
|
+
}
|
package/src/utils/output.ts
CHANGED
|
@@ -808,16 +808,16 @@ export function formatSignJson(result: SignDatabaseResult): string {
|
|
|
808
808
|
}
|
|
809
809
|
|
|
810
810
|
// ============================================================================
|
|
811
|
-
//
|
|
811
|
+
// Migration Command Output Formatters (shared by db init and db update)
|
|
812
812
|
// ============================================================================
|
|
813
813
|
|
|
814
814
|
/**
|
|
815
|
-
*
|
|
815
|
+
* Shared CLI output type for migration commands (db init, db update).
|
|
816
816
|
*/
|
|
817
|
-
export interface
|
|
818
|
-
readonly ok:
|
|
817
|
+
export interface MigrationCommandResult {
|
|
818
|
+
readonly ok: true;
|
|
819
819
|
readonly mode: 'plan' | 'apply';
|
|
820
|
-
readonly plan
|
|
820
|
+
readonly plan: {
|
|
821
821
|
readonly targetId: string;
|
|
822
822
|
readonly destination: {
|
|
823
823
|
readonly storageHash: string;
|
|
@@ -828,6 +828,7 @@ export interface DbInitResult {
|
|
|
828
828
|
readonly label: string;
|
|
829
829
|
readonly operationClass: string;
|
|
830
830
|
}[];
|
|
831
|
+
readonly sql?: readonly string[];
|
|
831
832
|
};
|
|
832
833
|
readonly execution?: {
|
|
833
834
|
readonly operationsPlanned: number;
|
|
@@ -844,9 +845,12 @@ export interface DbInitResult {
|
|
|
844
845
|
}
|
|
845
846
|
|
|
846
847
|
/**
|
|
847
|
-
* Formats human-readable output for db init plan mode.
|
|
848
|
+
* Formats human-readable output for migration commands (db init, db update) in plan mode.
|
|
848
849
|
*/
|
|
849
|
-
export function
|
|
850
|
+
export function formatMigrationPlanOutput(
|
|
851
|
+
result: MigrationCommandResult,
|
|
852
|
+
flags: GlobalFlags,
|
|
853
|
+
): string {
|
|
850
854
|
if (flags.quiet) {
|
|
851
855
|
return '';
|
|
852
856
|
}
|
|
@@ -882,6 +886,24 @@ export function formatDbInitPlanOutput(result: DbInitResult, flags: GlobalFlags)
|
|
|
882
886
|
);
|
|
883
887
|
}
|
|
884
888
|
|
|
889
|
+
// SQL DDL preview (SQL family only)
|
|
890
|
+
const planSql = result.plan?.sql;
|
|
891
|
+
if (planSql) {
|
|
892
|
+
lines.push(`${prefix}`);
|
|
893
|
+
lines.push(`${prefix}${formatDimText('DDL preview')}`);
|
|
894
|
+
if (planSql.length === 0) {
|
|
895
|
+
lines.push(`${prefix}${formatDimText('No DDL operations.')}`);
|
|
896
|
+
} else {
|
|
897
|
+
lines.push(`${prefix}`);
|
|
898
|
+
for (const statement of planSql) {
|
|
899
|
+
const trimmed = statement.trim();
|
|
900
|
+
if (!trimmed) continue;
|
|
901
|
+
const line = trimmed.endsWith(';') ? trimmed : `${trimmed};`;
|
|
902
|
+
lines.push(`${prefix}${line}`);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
885
907
|
// Timings in verbose mode
|
|
886
908
|
if (isVerbose(flags, 1)) {
|
|
887
909
|
lines.push(`${prefix}${formatDimText(`Total time: ${result.timings.total}ms`)}`);
|
|
@@ -896,9 +918,12 @@ export function formatDbInitPlanOutput(result: DbInitResult, flags: GlobalFlags)
|
|
|
896
918
|
}
|
|
897
919
|
|
|
898
920
|
/**
|
|
899
|
-
* Formats human-readable output for db init apply mode.
|
|
921
|
+
* Formats human-readable output for migration commands (db init, db update) in apply mode.
|
|
900
922
|
*/
|
|
901
|
-
export function
|
|
923
|
+
export function formatMigrationApplyOutput(
|
|
924
|
+
result: MigrationCommandResult,
|
|
925
|
+
flags: GlobalFlags,
|
|
926
|
+
): string {
|
|
902
927
|
if (flags.quiet) {
|
|
903
928
|
return '';
|
|
904
929
|
}
|
|
@@ -912,11 +937,15 @@ export function formatDbInitApplyOutput(result: DbInitResult, flags: GlobalFlags
|
|
|
912
937
|
if (result.ok) {
|
|
913
938
|
// Success summary
|
|
914
939
|
const executed = result.execution?.operationsExecuted ?? 0;
|
|
915
|
-
|
|
940
|
+
if (executed === 0) {
|
|
941
|
+
lines.push(`${prefix}${formatGreen('✔')} Database already matches contract`);
|
|
942
|
+
} else {
|
|
943
|
+
lines.push(`${prefix}${formatGreen('✔')} Applied ${executed} operation(s)`);
|
|
944
|
+
}
|
|
916
945
|
|
|
917
946
|
// Marker info
|
|
918
947
|
if (result.marker) {
|
|
919
|
-
lines.push(`${prefix}${formatDimText(`
|
|
948
|
+
lines.push(`${prefix}${formatDimText(` Signature: ${result.marker.storageHash}`)}`);
|
|
920
949
|
if (result.marker.profileHash) {
|
|
921
950
|
lines.push(`${prefix}${formatDimText(` Profile hash: ${result.marker.profileHash}`)}`);
|
|
922
951
|
}
|
|
@@ -932,9 +961,9 @@ export function formatDbInitApplyOutput(result: DbInitResult, flags: GlobalFlags
|
|
|
932
961
|
}
|
|
933
962
|
|
|
934
963
|
/**
|
|
935
|
-
* Formats JSON output for db init
|
|
964
|
+
* Formats JSON output for migration commands (db init, db update).
|
|
936
965
|
*/
|
|
937
|
-
export function
|
|
966
|
+
export function formatMigrationJson(result: MigrationCommandResult): string {
|
|
938
967
|
return JSON.stringify(result, null, 2);
|
|
939
968
|
}
|
|
940
969
|
|
|
@@ -1247,6 +1276,7 @@ function getCommandDocsUrl(commandPath: string): string | undefined {
|
|
|
1247
1276
|
const docsMap: Record<string, string> = {
|
|
1248
1277
|
'contract emit': 'https://pris.ly/contract-emit',
|
|
1249
1278
|
'db verify': 'https://pris.ly/db-verify',
|
|
1279
|
+
'db update': 'https://pris.ly/db-update',
|
|
1250
1280
|
};
|
|
1251
1281
|
return docsMap[commandPath];
|
|
1252
1282
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"client-BSZKpZTF.mjs","names":["plannerResult: MigrationPlannerResult","migrationPlan: MigrationPlan","runnerResult: MigrationRunnerResult","contractRaw: unknown"],"sources":["../src/utils/framework-components.ts","../src/control-api/operations/db-init.ts","../src/control-api/client.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 { ControlPlaneStack } 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 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 stack - The control plane stack (target, adapter, driver, extensionPacks).\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 * const stack = createControlPlaneStack({ target: config.target, adapter: config.adapter, ... });\n *\n * // Throws if contract is incompatible with config\n * assertContractRequirementsSatisfied({ contract: contractIR, stack });\n * ```\n */\nexport function assertContractRequirementsSatisfied<\n TFamilyId extends string,\n TTargetId extends string,\n>({\n contract,\n stack,\n}: {\n readonly contract: Pick<ContractIR, 'targetFamily' | 'target' | 'extensionPacks'>;\n readonly stack: ControlPlaneStack<TFamilyId, TTargetId>;\n}): void {\n const providedComponentIds = new Set<string>([stack.target.id, stack.adapter.id]);\n for (const extension of stack.extensionPacks) {\n providedComponentIds.add(extension.id);\n }\n\n const result = checkContractComponentRequirements({\n contract,\n expectedTargetFamily: stack.target.familyId,\n expectedTargetId: stack.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","import type { TargetBoundComponentDescriptor } from '@prisma-next/contract/framework-components';\nimport type { ContractIR } from '@prisma-next/contract/ir';\nimport type {\n ControlDriverInstance,\n ControlFamilyInstance,\n MigrationPlan,\n MigrationPlannerResult,\n MigrationPlanOperation,\n MigrationRunnerResult,\n TargetMigrationsCapability,\n} from '@prisma-next/core-control-plane/types';\nimport { notOk, ok } from '@prisma-next/utils/result';\nimport type { DbInitResult, DbInitSuccess, OnControlProgress } from '../types';\n\n/**\n * Options for executing dbInit operation.\n */\nexport interface ExecuteDbInitOptions<TFamilyId extends string, TTargetId extends string> {\n readonly driver: ControlDriverInstance<TFamilyId, TTargetId>;\n readonly familyInstance: ControlFamilyInstance<TFamilyId>;\n readonly contractIR: ContractIR;\n readonly mode: 'plan' | 'apply';\n readonly migrations: TargetMigrationsCapability<\n TFamilyId,\n TTargetId,\n ControlFamilyInstance<TFamilyId>\n >;\n readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<TFamilyId, TTargetId>>;\n /** Optional progress callback for observing operation progress */\n readonly onProgress?: OnControlProgress;\n}\n\n/**\n * Executes the dbInit operation.\n *\n * This is the core logic extracted from the CLI command, without any file I/O,\n * process.exit(), or console output. It uses the Result pattern to return\n * success or failure details.\n *\n * @param options - The options for executing dbInit\n * @returns Result with DbInitSuccess on success, DbInitFailure on failure\n */\nexport async function executeDbInit<TFamilyId extends string, TTargetId extends string>(\n options: ExecuteDbInitOptions<TFamilyId, TTargetId>,\n): Promise<DbInitResult> {\n const { driver, familyInstance, contractIR, mode, migrations, frameworkComponents, onProgress } =\n options;\n\n // Create planner and runner from target migrations capability\n const planner = migrations.createPlanner(familyInstance);\n const runner = migrations.createRunner(familyInstance);\n\n // Introspect live schema\n const introspectSpanId = 'introspect';\n onProgress?.({\n action: 'dbInit',\n kind: 'spanStart',\n spanId: introspectSpanId,\n label: 'Introspecting database schema',\n });\n const schemaIR = await familyInstance.introspect({ driver });\n onProgress?.({\n action: 'dbInit',\n kind: 'spanEnd',\n spanId: introspectSpanId,\n outcome: 'ok',\n });\n\n // Policy for init mode (additive only)\n const policy = { allowedOperationClasses: ['additive'] as const };\n\n // Plan migration\n const planSpanId = 'plan';\n onProgress?.({\n action: 'dbInit',\n kind: 'spanStart',\n spanId: planSpanId,\n label: 'Planning migration',\n });\n const plannerResult: MigrationPlannerResult = await planner.plan({\n contract: contractIR,\n schema: schemaIR,\n policy,\n frameworkComponents,\n });\n\n if (plannerResult.kind === 'failure') {\n onProgress?.({\n action: 'dbInit',\n kind: 'spanEnd',\n spanId: planSpanId,\n outcome: 'error',\n });\n return notOk({\n code: 'PLANNING_FAILED' as const,\n summary: 'Migration planning failed due to conflicts',\n conflicts: plannerResult.conflicts,\n why: undefined,\n meta: undefined,\n });\n }\n\n const migrationPlan: MigrationPlan = plannerResult.plan;\n onProgress?.({\n action: 'dbInit',\n kind: 'spanEnd',\n spanId: planSpanId,\n outcome: 'ok',\n });\n\n // Check for existing marker - handle idempotency and mismatch errors\n const checkMarkerSpanId = 'checkMarker';\n onProgress?.({\n action: 'dbInit',\n kind: 'spanStart',\n spanId: checkMarkerSpanId,\n label: 'Checking contract marker',\n });\n const existingMarker = await familyInstance.readMarker({ driver });\n if (existingMarker) {\n const markerMatchesDestination =\n existingMarker.storageHash === migrationPlan.destination.storageHash &&\n (!migrationPlan.destination.profileHash ||\n existingMarker.profileHash === migrationPlan.destination.profileHash);\n\n if (markerMatchesDestination) {\n // Already at destination - return success with no operations\n onProgress?.({\n action: 'dbInit',\n kind: 'spanEnd',\n spanId: checkMarkerSpanId,\n outcome: 'skipped',\n });\n const result: DbInitSuccess = {\n mode,\n plan: { operations: [] },\n ...(mode === 'apply'\n ? {\n execution: { operationsPlanned: 0, operationsExecuted: 0 },\n marker: {\n storageHash: existingMarker.storageHash,\n profileHash: existingMarker.profileHash,\n },\n }\n : {}),\n summary: 'Database already at target contract state',\n };\n return ok(result);\n }\n\n // Marker exists but doesn't match destination - fail\n onProgress?.({\n action: 'dbInit',\n kind: 'spanEnd',\n spanId: checkMarkerSpanId,\n outcome: 'error',\n });\n return notOk({\n code: 'MARKER_ORIGIN_MISMATCH' as const,\n summary: 'Existing contract marker does not match plan destination',\n marker: {\n storageHash: existingMarker.storageHash,\n profileHash: existingMarker.profileHash,\n },\n destination: {\n storageHash: migrationPlan.destination.storageHash,\n profileHash: migrationPlan.destination.profileHash,\n },\n why: undefined,\n conflicts: undefined,\n meta: undefined,\n });\n }\n\n onProgress?.({\n action: 'dbInit',\n kind: 'spanEnd',\n spanId: checkMarkerSpanId,\n outcome: 'ok',\n });\n\n // Plan mode - don't execute\n if (mode === 'plan') {\n const result: DbInitSuccess = {\n mode: 'plan',\n plan: { operations: migrationPlan.operations },\n summary: `Planned ${migrationPlan.operations.length} operation(s)`,\n };\n return ok(result);\n }\n\n // Apply mode - execute runner\n const applySpanId = 'apply';\n onProgress?.({\n action: 'dbInit',\n kind: 'spanStart',\n spanId: applySpanId,\n label: 'Applying migration plan',\n });\n\n const callbacks = onProgress\n ? {\n onOperationStart: (op: MigrationPlanOperation) => {\n onProgress({\n action: 'dbInit',\n kind: 'spanStart',\n spanId: `operation:${op.id}`,\n parentSpanId: applySpanId,\n label: op.label,\n });\n },\n onOperationComplete: (op: MigrationPlanOperation) => {\n onProgress({\n action: 'dbInit',\n kind: 'spanEnd',\n spanId: `operation:${op.id}`,\n outcome: 'ok',\n });\n },\n }\n : undefined;\n\n const runnerResult: MigrationRunnerResult = await runner.execute({\n plan: migrationPlan,\n driver,\n destinationContract: contractIR,\n policy,\n ...(callbacks ? { callbacks } : {}),\n // db init plans and applies back-to-back from a fresh introspection, so per-operation\n // pre/postchecks and the idempotency probe are usually redundant overhead. We still\n // enforce marker/origin compatibility and a full schema verification after apply.\n executionChecks: {\n prechecks: false,\n postchecks: false,\n idempotencyChecks: false,\n },\n frameworkComponents,\n });\n\n if (!runnerResult.ok) {\n onProgress?.({\n action: 'dbInit',\n kind: 'spanEnd',\n spanId: applySpanId,\n outcome: 'error',\n });\n return notOk({\n code: 'RUNNER_FAILED' as const,\n summary: runnerResult.failure.summary,\n why: runnerResult.failure.why,\n meta: runnerResult.failure.meta,\n conflicts: undefined,\n });\n }\n\n const execution = runnerResult.value;\n\n onProgress?.({\n action: 'dbInit',\n kind: 'spanEnd',\n spanId: applySpanId,\n outcome: 'ok',\n });\n\n const result: DbInitSuccess = {\n mode: 'apply',\n plan: { operations: migrationPlan.operations },\n execution: {\n operationsPlanned: execution.operationsPlanned,\n operationsExecuted: execution.operationsExecuted,\n },\n marker: migrationPlan.destination.profileHash\n ? {\n storageHash: migrationPlan.destination.storageHash,\n profileHash: migrationPlan.destination.profileHash,\n }\n : { storageHash: migrationPlan.destination.storageHash },\n summary: `Applied ${execution.operationsExecuted} operation(s), marker written`,\n };\n return ok(result);\n}\n","import type { TargetBoundComponentDescriptor } from '@prisma-next/contract/framework-components';\nimport type { CoreSchemaView } from '@prisma-next/core-control-plane/schema-view';\nimport { createControlPlaneStack } from '@prisma-next/core-control-plane/stack';\nimport type {\n ControlDriverInstance,\n ControlFamilyInstance,\n ControlPlaneStack,\n SignDatabaseResult,\n VerifyDatabaseResult,\n VerifyDatabaseSchemaResult,\n} from '@prisma-next/core-control-plane/types';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { notOk, ok } from '@prisma-next/utils/result';\nimport { assertFrameworkComponentsCompatible } from '../utils/framework-components';\nimport { executeDbInit } from './operations/db-init';\nimport type {\n ControlClient,\n ControlClientOptions,\n DbInitOptions,\n DbInitResult,\n EmitOptions,\n EmitResult,\n IntrospectOptions,\n SchemaVerifyOptions,\n SignOptions,\n VerifyOptions,\n} from './types';\n\n/**\n * Creates a programmatic control client for Prisma Next operations.\n *\n * The client accepts framework component descriptors at creation time,\n * manages driver lifecycle via connect()/close(), and exposes domain\n * operations that delegate to the existing family instance methods.\n *\n * @see {@link ControlClient} for the client interface\n * @see README.md \"Programmatic Control API\" section for usage examples\n */\nexport function createControlClient(options: ControlClientOptions): ControlClient {\n return new ControlClientImpl(options);\n}\n\n/**\n * Implementation of ControlClient.\n * Manages initialization and connection state, delegates operations to family instance.\n */\nclass ControlClientImpl implements ControlClient {\n private readonly options: ControlClientOptions;\n private stack: ControlPlaneStack<string, string> | null = null;\n private driver: ControlDriverInstance<string, string> | null = null;\n private familyInstance: ControlFamilyInstance<string> | null = null;\n private frameworkComponents: ReadonlyArray<\n TargetBoundComponentDescriptor<string, string>\n > | null = null;\n private initialized = false;\n private readonly defaultConnection: unknown;\n\n constructor(options: ControlClientOptions) {\n this.options = options;\n this.defaultConnection = options.connection;\n }\n\n init(): void {\n if (this.initialized) {\n return; // Idempotent\n }\n\n // Create the control plane stack\n this.stack = createControlPlaneStack({\n target: this.options.target,\n adapter: this.options.adapter,\n driver: this.options.driver,\n extensionPacks: this.options.extensionPacks,\n });\n\n // Create family instance using the stack\n this.familyInstance = this.options.family.create(this.stack);\n\n // Validate and type-narrow framework components\n const rawComponents = [\n this.options.target,\n this.options.adapter,\n ...(this.options.extensionPacks ?? []),\n ];\n this.frameworkComponents = assertFrameworkComponentsCompatible(\n this.options.family.familyId,\n this.options.target.targetId,\n rawComponents,\n );\n\n this.initialized = true;\n }\n\n async connect(connection?: unknown): Promise<void> {\n // Auto-init if needed\n this.init();\n\n if (this.driver) {\n throw new Error('Already connected. Call close() before reconnecting.');\n }\n\n // Resolve connection: argument > default from options\n const resolvedConnection = connection ?? this.defaultConnection;\n if (resolvedConnection === undefined) {\n throw new Error(\n 'No connection provided. Pass a connection to connect() or provide a default connection when creating the client.',\n );\n }\n\n // Check for driver descriptor\n if (!this.stack?.driver) {\n throw new Error(\n 'Driver is not configured. Pass a driver descriptor when creating the control client to enable database operations.',\n );\n }\n\n // Create driver instance\n // Cast through any since connection type is driver-specific at runtime.\n // The driver descriptor is typed with any for TConnection in ControlClientOptions,\n // but createControlPlaneStack defaults it to string. We bridge this at runtime.\n // biome-ignore lint/suspicious/noExplicitAny: required for runtime connection type flexibility\n this.driver = await this.stack?.driver.create(resolvedConnection as any);\n }\n\n async close(): Promise<void> {\n if (this.driver) {\n await this.driver.close();\n this.driver = null;\n }\n }\n\n private async ensureConnected(): Promise<{\n driver: ControlDriverInstance<string, string>;\n familyInstance: ControlFamilyInstance<string>;\n frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<string, string>>;\n }> {\n // Auto-init if needed\n this.init();\n\n // Auto-connect if not connected and default connection is available\n if (!this.driver && this.defaultConnection !== undefined) {\n await this.connect(this.defaultConnection);\n }\n\n if (!this.driver || !this.familyInstance || !this.frameworkComponents) {\n throw new Error('Not connected. Call connect(connection) first.');\n }\n return {\n driver: this.driver,\n familyInstance: this.familyInstance,\n frameworkComponents: this.frameworkComponents,\n };\n }\n\n async verify(options: VerifyOptions): Promise<VerifyDatabaseResult> {\n const { onProgress } = options;\n\n // Connect with progress span if connection provided\n if (options.connection !== undefined) {\n onProgress?.({\n action: 'verify',\n kind: 'spanStart',\n spanId: 'connect',\n label: 'Connecting to database...',\n });\n try {\n await this.connect(options.connection);\n onProgress?.({\n action: 'verify',\n kind: 'spanEnd',\n spanId: 'connect',\n outcome: 'ok',\n });\n } catch (error) {\n onProgress?.({\n action: 'verify',\n kind: 'spanEnd',\n spanId: 'connect',\n outcome: 'error',\n });\n throw error;\n }\n }\n\n const { driver, familyInstance } = await this.ensureConnected();\n\n // Validate contract using family instance\n const contractIR = familyInstance.validateContractIR(options.contractIR);\n\n // Emit verify span\n onProgress?.({\n action: 'verify',\n kind: 'spanStart',\n spanId: 'verify',\n label: 'Verifying contract marker...',\n });\n\n try {\n // Delegate to family instance verify method\n // Note: We pass empty strings for contractPath/configPath since the programmatic\n // API doesn't deal with file paths. The family instance accepts these as optional\n // metadata for error reporting.\n const result = await familyInstance.verify({\n driver,\n contractIR,\n expectedTargetId: this.options.target.targetId,\n contractPath: '',\n });\n\n onProgress?.({\n action: 'verify',\n kind: 'spanEnd',\n spanId: 'verify',\n outcome: result.ok ? 'ok' : 'error',\n });\n\n return result;\n } catch (error) {\n onProgress?.({\n action: 'verify',\n kind: 'spanEnd',\n spanId: 'verify',\n outcome: 'error',\n });\n throw error;\n }\n }\n\n async schemaVerify(options: SchemaVerifyOptions): Promise<VerifyDatabaseSchemaResult> {\n const { onProgress } = options;\n\n // Connect with progress span if connection provided\n if (options.connection !== undefined) {\n onProgress?.({\n action: 'schemaVerify',\n kind: 'spanStart',\n spanId: 'connect',\n label: 'Connecting to database...',\n });\n try {\n await this.connect(options.connection);\n onProgress?.({\n action: 'schemaVerify',\n kind: 'spanEnd',\n spanId: 'connect',\n outcome: 'ok',\n });\n } catch (error) {\n onProgress?.({\n action: 'schemaVerify',\n kind: 'spanEnd',\n spanId: 'connect',\n outcome: 'error',\n });\n throw error;\n }\n }\n\n const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();\n\n // Validate contract using family instance\n const contractIR = familyInstance.validateContractIR(options.contractIR);\n\n // Emit schemaVerify span\n onProgress?.({\n action: 'schemaVerify',\n kind: 'spanStart',\n spanId: 'schemaVerify',\n label: 'Verifying database schema...',\n });\n\n try {\n // Delegate to family instance schemaVerify method\n const result = await familyInstance.schemaVerify({\n driver,\n contractIR,\n strict: options.strict ?? false,\n contractPath: '',\n frameworkComponents,\n });\n\n onProgress?.({\n action: 'schemaVerify',\n kind: 'spanEnd',\n spanId: 'schemaVerify',\n outcome: result.ok ? 'ok' : 'error',\n });\n\n return result;\n } catch (error) {\n onProgress?.({\n action: 'schemaVerify',\n kind: 'spanEnd',\n spanId: 'schemaVerify',\n outcome: 'error',\n });\n throw error;\n }\n }\n\n async sign(options: SignOptions): Promise<SignDatabaseResult> {\n const { onProgress } = options;\n\n // Connect with progress span if connection provided\n if (options.connection !== undefined) {\n onProgress?.({\n action: 'sign',\n kind: 'spanStart',\n spanId: 'connect',\n label: 'Connecting to database...',\n });\n try {\n await this.connect(options.connection);\n onProgress?.({\n action: 'sign',\n kind: 'spanEnd',\n spanId: 'connect',\n outcome: 'ok',\n });\n } catch (error) {\n onProgress?.({\n action: 'sign',\n kind: 'spanEnd',\n spanId: 'connect',\n outcome: 'error',\n });\n throw error;\n }\n }\n\n const { driver, familyInstance } = await this.ensureConnected();\n\n // Validate contract using family instance\n const contractIR = familyInstance.validateContractIR(options.contractIR);\n\n // Emit sign span\n onProgress?.({\n action: 'sign',\n kind: 'spanStart',\n spanId: 'sign',\n label: 'Signing database...',\n });\n\n try {\n // Delegate to family instance sign method\n const result = await familyInstance.sign({\n driver,\n contractIR,\n contractPath: options.contractPath ?? '',\n ...ifDefined('configPath', options.configPath),\n });\n\n onProgress?.({\n action: 'sign',\n kind: 'spanEnd',\n spanId: 'sign',\n outcome: 'ok',\n });\n\n return result;\n } catch (error) {\n onProgress?.({\n action: 'sign',\n kind: 'spanEnd',\n spanId: 'sign',\n outcome: 'error',\n });\n throw error;\n }\n }\n\n async dbInit(options: DbInitOptions): Promise<DbInitResult> {\n const { onProgress } = options;\n\n // Connect with progress span if connection provided\n if (options.connection !== undefined) {\n onProgress?.({\n action: 'dbInit',\n kind: 'spanStart',\n spanId: 'connect',\n label: 'Connecting to database...',\n });\n try {\n await this.connect(options.connection);\n onProgress?.({\n action: 'dbInit',\n kind: 'spanEnd',\n spanId: 'connect',\n outcome: 'ok',\n });\n } catch (error) {\n onProgress?.({\n action: 'dbInit',\n kind: 'spanEnd',\n spanId: 'connect',\n outcome: 'error',\n });\n throw error;\n }\n }\n\n const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();\n\n // Check target supports migrations\n if (!this.options.target.migrations) {\n throw new Error(`Target \"${this.options.target.targetId}\" does not support migrations`);\n }\n\n // Validate contract using family instance\n const contractIR = familyInstance.validateContractIR(options.contractIR);\n\n // Delegate to extracted dbInit operation\n return executeDbInit({\n driver,\n familyInstance,\n contractIR,\n mode: options.mode,\n migrations: this.options.target.migrations,\n frameworkComponents,\n ...(onProgress ? { onProgress } : {}),\n });\n }\n\n async introspect(options?: IntrospectOptions): Promise<unknown> {\n const onProgress = options?.onProgress;\n\n // Connect with progress span if connection provided\n if (options?.connection !== undefined) {\n onProgress?.({\n action: 'introspect',\n kind: 'spanStart',\n spanId: 'connect',\n label: 'Connecting to database...',\n });\n try {\n await this.connect(options.connection);\n onProgress?.({\n action: 'introspect',\n kind: 'spanEnd',\n spanId: 'connect',\n outcome: 'ok',\n });\n } catch (error) {\n onProgress?.({\n action: 'introspect',\n kind: 'spanEnd',\n spanId: 'connect',\n outcome: 'error',\n });\n throw error;\n }\n }\n\n const { driver, familyInstance } = await this.ensureConnected();\n\n // TODO: Pass schema option to familyInstance.introspect when schema filtering is implemented\n const _schema = options?.schema;\n void _schema;\n\n // Emit introspect span\n onProgress?.({\n action: 'introspect',\n kind: 'spanStart',\n spanId: 'introspect',\n label: 'Introspecting database schema...',\n });\n\n try {\n const result = await familyInstance.introspect({ driver });\n\n onProgress?.({\n action: 'introspect',\n kind: 'spanEnd',\n spanId: 'introspect',\n outcome: 'ok',\n });\n\n return result;\n } catch (error) {\n onProgress?.({\n action: 'introspect',\n kind: 'spanEnd',\n spanId: 'introspect',\n outcome: 'error',\n });\n throw error;\n }\n }\n\n toSchemaView(schemaIR: unknown): CoreSchemaView | undefined {\n this.init();\n if (this.familyInstance?.toSchemaView) {\n return this.familyInstance.toSchemaView(schemaIR);\n }\n return undefined;\n }\n\n async emit(options: EmitOptions): Promise<EmitResult> {\n const { onProgress, contractConfig } = options;\n\n // Ensure initialized (creates stack and family instance)\n // emit() does NOT require a database connection\n this.init();\n\n if (!this.familyInstance) {\n throw new Error('Family instance was not initialized. This is a bug.');\n }\n\n let contractRaw: unknown;\n onProgress?.({\n action: 'emit',\n kind: 'spanStart',\n spanId: 'resolveSource',\n label: 'Resolving contract source...',\n });\n\n try {\n const providerResult = await contractConfig.sourceProvider();\n if (!providerResult.ok) {\n onProgress?.({\n action: 'emit',\n kind: 'spanEnd',\n spanId: 'resolveSource',\n outcome: 'error',\n });\n\n return notOk({\n code: 'CONTRACT_SOURCE_INVALID',\n summary: providerResult.failure.summary,\n why: providerResult.failure.summary,\n meta: providerResult.failure.meta,\n diagnostics: providerResult.failure,\n });\n }\n contractRaw = providerResult.value;\n\n onProgress?.({\n action: 'emit',\n kind: 'spanEnd',\n spanId: 'resolveSource',\n outcome: 'ok',\n });\n } catch (error) {\n onProgress?.({\n action: 'emit',\n kind: 'spanEnd',\n spanId: 'resolveSource',\n outcome: 'error',\n });\n\n const message = error instanceof Error ? error.message : String(error);\n return notOk({\n code: 'CONTRACT_SOURCE_INVALID',\n summary: 'Failed to resolve contract source',\n why: message,\n diagnostics: {\n summary: 'Contract source provider threw an exception',\n diagnostics: [\n {\n code: 'PROVIDER_THROW',\n message,\n },\n ],\n },\n meta: undefined,\n });\n }\n\n // Emit contract\n onProgress?.({\n action: 'emit',\n kind: 'spanStart',\n spanId: 'emit',\n label: 'Emitting contract...',\n });\n\n try {\n const emitResult = await this.familyInstance.emitContract({ contractIR: contractRaw });\n\n onProgress?.({\n action: 'emit',\n kind: 'spanEnd',\n spanId: 'emit',\n outcome: 'ok',\n });\n\n return ok({\n storageHash: emitResult.storageHash,\n ...ifDefined('executionHash', emitResult.executionHash),\n profileHash: emitResult.profileHash,\n contractJson: emitResult.contractJson,\n contractDts: emitResult.contractDts,\n });\n } catch (error) {\n onProgress?.({\n action: 'emit',\n kind: 'spanEnd',\n spanId: 'emit',\n outcome: 'error',\n });\n\n return notOk({\n code: 'EMIT_FAILED',\n summary: 'Failed to emit contract',\n why: error instanceof Error ? error.message : String(error),\n meta: undefined,\n });\n }\n }\n}\n"],"mappings":"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"}
|