@prisma-next/cli 0.3.0-dev.53 → 0.3.0-dev.55
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 +14 -10
- 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
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
2
|
+
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { ContractValidationError } from '../control-api/errors';
|
|
5
|
+
import type { DbUpdateFailure } from '../control-api/types';
|
|
6
|
+
import {
|
|
7
|
+
CliStructuredError,
|
|
8
|
+
errorContractValidationFailed,
|
|
9
|
+
errorDestructiveChanges,
|
|
10
|
+
errorJsonFormatNotSupported,
|
|
11
|
+
errorMigrationPlanningFailed,
|
|
12
|
+
errorRunnerFailed,
|
|
13
|
+
errorUnexpected,
|
|
14
|
+
} from '../utils/cli-errors';
|
|
15
|
+
import type { MigrationCommandOptions } from '../utils/command-helpers';
|
|
16
|
+
import { sanitizeErrorMessage, setCommandDescriptions } from '../utils/command-helpers';
|
|
17
|
+
import { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';
|
|
18
|
+
import {
|
|
19
|
+
addMigrationCommandOptions,
|
|
20
|
+
prepareMigrationContext,
|
|
21
|
+
} from '../utils/migration-command-scaffold';
|
|
22
|
+
import {
|
|
23
|
+
formatCommandHelp,
|
|
24
|
+
formatMigrationApplyOutput,
|
|
25
|
+
formatMigrationJson,
|
|
26
|
+
formatMigrationPlanOutput,
|
|
27
|
+
type MigrationCommandResult,
|
|
28
|
+
} from '../utils/output';
|
|
29
|
+
import { handleResult } from '../utils/result-handler';
|
|
30
|
+
|
|
31
|
+
type DbUpdateOptions = MigrationCommandOptions & {
|
|
32
|
+
readonly acceptDataLoss?: boolean;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Maps a DbUpdateFailure to a CliStructuredError for consistent error handling.
|
|
37
|
+
*/
|
|
38
|
+
function mapDbUpdateFailure(failure: DbUpdateFailure): CliStructuredError {
|
|
39
|
+
if (failure.code === 'PLANNING_FAILED') {
|
|
40
|
+
return errorMigrationPlanningFailed({ conflicts: failure.conflicts ?? [] });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (failure.code === 'RUNNER_FAILED') {
|
|
44
|
+
return errorRunnerFailed(failure.summary, {
|
|
45
|
+
why: failure.why ?? 'Migration runner failed',
|
|
46
|
+
fix: 'Inspect the reported conflict, reconcile schema drift if needed, then re-run `prisma-next db update`',
|
|
47
|
+
...ifDefined('meta', failure.meta),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (failure.code === 'DESTRUCTIVE_CHANGES') {
|
|
52
|
+
return errorDestructiveChanges(failure.summary, {
|
|
53
|
+
...ifDefined('why', failure.why),
|
|
54
|
+
fix: 'Use `prisma-next db update --plan` to preview, then re-run with `--accept-data-loss` to apply destructive changes',
|
|
55
|
+
...ifDefined('meta', failure.meta),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const exhaustive: never = failure.code;
|
|
60
|
+
throw new Error(`Unhandled DbUpdateFailure code: ${exhaustive}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Executes the db update command and returns a structured Result.
|
|
65
|
+
*/
|
|
66
|
+
async function executeDbUpdateCommand(
|
|
67
|
+
options: DbUpdateOptions,
|
|
68
|
+
flags: GlobalFlags,
|
|
69
|
+
startTime: number,
|
|
70
|
+
): Promise<Result<MigrationCommandResult, CliStructuredError>> {
|
|
71
|
+
// Prepare shared migration context (config, contract, connection, client)
|
|
72
|
+
const ctxResult = await prepareMigrationContext(options, flags, {
|
|
73
|
+
commandName: 'db update',
|
|
74
|
+
description: 'Update your database schema to match your contract',
|
|
75
|
+
url: 'https://pris.ly/db-update',
|
|
76
|
+
});
|
|
77
|
+
if (!ctxResult.ok) {
|
|
78
|
+
return ctxResult;
|
|
79
|
+
}
|
|
80
|
+
const { client, contractJson, dbConnection, onProgress, contractPathAbsolute } = ctxResult.value;
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
// Call dbUpdate with connection and progress callback
|
|
84
|
+
const result = await client.dbUpdate({
|
|
85
|
+
contractIR: contractJson,
|
|
86
|
+
mode: options.plan ? 'plan' : 'apply',
|
|
87
|
+
connection: dbConnection,
|
|
88
|
+
...(options.acceptDataLoss ? { acceptDataLoss: true } : {}),
|
|
89
|
+
onProgress,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Handle failures by mapping to CLI structured error
|
|
93
|
+
if (!result.ok) {
|
|
94
|
+
return notOk(mapDbUpdateFailure(result.failure));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Convert success result to CLI output format
|
|
98
|
+
const dbUpdateResult: MigrationCommandResult = {
|
|
99
|
+
ok: true,
|
|
100
|
+
mode: result.value.mode,
|
|
101
|
+
plan: {
|
|
102
|
+
targetId: ctxResult.value.config.target.targetId,
|
|
103
|
+
destination: {
|
|
104
|
+
storageHash: result.value.destination.storageHash,
|
|
105
|
+
...ifDefined('profileHash', result.value.destination.profileHash),
|
|
106
|
+
},
|
|
107
|
+
operations: result.value.plan.operations.map((op) => ({
|
|
108
|
+
id: op.id,
|
|
109
|
+
label: op.label,
|
|
110
|
+
operationClass: op.operationClass,
|
|
111
|
+
})),
|
|
112
|
+
...ifDefined('sql', result.value.plan.sql),
|
|
113
|
+
},
|
|
114
|
+
...ifDefined(
|
|
115
|
+
'execution',
|
|
116
|
+
result.value.execution
|
|
117
|
+
? {
|
|
118
|
+
operationsPlanned: result.value.execution.operationsPlanned,
|
|
119
|
+
operationsExecuted: result.value.execution.operationsExecuted,
|
|
120
|
+
}
|
|
121
|
+
: undefined,
|
|
122
|
+
),
|
|
123
|
+
...ifDefined(
|
|
124
|
+
'marker',
|
|
125
|
+
result.value.marker
|
|
126
|
+
? {
|
|
127
|
+
storageHash: result.value.marker.storageHash,
|
|
128
|
+
...ifDefined('profileHash', result.value.marker.profileHash),
|
|
129
|
+
}
|
|
130
|
+
: undefined,
|
|
131
|
+
),
|
|
132
|
+
summary: result.value.summary,
|
|
133
|
+
timings: { total: Date.now() - startTime },
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
return ok(dbUpdateResult);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
if (CliStructuredError.is(error)) {
|
|
139
|
+
return notOk(error);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (error instanceof ContractValidationError) {
|
|
143
|
+
return notOk(
|
|
144
|
+
errorContractValidationFailed(`Contract validation failed: ${error.message}`, {
|
|
145
|
+
where: { path: contractPathAbsolute },
|
|
146
|
+
}),
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const rawMessage = error instanceof Error ? error.message : String(error);
|
|
151
|
+
const safeMessage = sanitizeErrorMessage(
|
|
152
|
+
rawMessage,
|
|
153
|
+
typeof dbConnection === 'string' ? dbConnection : undefined,
|
|
154
|
+
);
|
|
155
|
+
return notOk(
|
|
156
|
+
errorUnexpected(safeMessage, {
|
|
157
|
+
why: `Unexpected error during db update: ${safeMessage}`,
|
|
158
|
+
}),
|
|
159
|
+
);
|
|
160
|
+
} finally {
|
|
161
|
+
await client.close();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function createDbUpdateCommand(): Command {
|
|
166
|
+
const command = new Command('update');
|
|
167
|
+
setCommandDescriptions(
|
|
168
|
+
command,
|
|
169
|
+
'Update your database schema to match your contract',
|
|
170
|
+
'Compares your database schema to the emitted contract and applies the necessary\n' +
|
|
171
|
+
'changes. Works on any database, whether or not it has been initialized with `db init`.\n' +
|
|
172
|
+
'Use --plan to preview operations before applying.',
|
|
173
|
+
);
|
|
174
|
+
addMigrationCommandOptions(command);
|
|
175
|
+
command.option(
|
|
176
|
+
'--accept-data-loss',
|
|
177
|
+
'Confirm destructive operations (required when plan includes drops or type changes)',
|
|
178
|
+
false,
|
|
179
|
+
);
|
|
180
|
+
command.configureHelp({
|
|
181
|
+
formatHelp: (cmd) => {
|
|
182
|
+
const flags = parseGlobalFlags({});
|
|
183
|
+
return formatCommandHelp({ command: cmd, flags });
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
command.action(async (options: DbUpdateOptions) => {
|
|
187
|
+
const flags = parseGlobalFlags(options);
|
|
188
|
+
const startTime = Date.now();
|
|
189
|
+
|
|
190
|
+
if (flags.json === 'ndjson') {
|
|
191
|
+
const result = notOk(
|
|
192
|
+
errorJsonFormatNotSupported({
|
|
193
|
+
command: 'db update',
|
|
194
|
+
format: 'ndjson',
|
|
195
|
+
supportedFormats: ['object'],
|
|
196
|
+
}),
|
|
197
|
+
);
|
|
198
|
+
const exitCode = handleResult(result, flags);
|
|
199
|
+
process.exit(exitCode);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const result = await executeDbUpdateCommand(options, flags, startTime);
|
|
203
|
+
const exitCode = handleResult(result, flags, (dbUpdateResult) => {
|
|
204
|
+
if (flags.json === 'object') {
|
|
205
|
+
console.log(formatMigrationJson(dbUpdateResult));
|
|
206
|
+
} else {
|
|
207
|
+
const output =
|
|
208
|
+
dbUpdateResult.mode === 'plan'
|
|
209
|
+
? formatMigrationPlanOutput(dbUpdateResult, flags)
|
|
210
|
+
: formatMigrationApplyOutput(dbUpdateResult, flags);
|
|
211
|
+
if (output) {
|
|
212
|
+
console.log(output);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
process.exit(exitCode);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
return command;
|
|
220
|
+
}
|
|
@@ -6,6 +6,7 @@ import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
|
6
6
|
import { Command } from 'commander';
|
|
7
7
|
import { loadConfig } from '../config-loader';
|
|
8
8
|
import { createControlClient } from '../control-api/client';
|
|
9
|
+
import { ContractValidationError } from '../control-api/errors';
|
|
9
10
|
import {
|
|
10
11
|
CliStructuredError,
|
|
11
12
|
errorContractValidationFailed,
|
|
@@ -19,7 +20,7 @@ import {
|
|
|
19
20
|
errorTargetMismatch,
|
|
20
21
|
errorUnexpected,
|
|
21
22
|
} from '../utils/cli-errors';
|
|
22
|
-
import { setCommandDescriptions } from '../utils/command-helpers';
|
|
23
|
+
import { maskConnectionUrl, setCommandDescriptions } from '../utils/command-helpers';
|
|
23
24
|
import { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';
|
|
24
25
|
import {
|
|
25
26
|
formatCommandHelp,
|
|
@@ -94,7 +95,7 @@ async function executeDbVerifyCommand(
|
|
|
94
95
|
{ label: 'contract', value: contractPath },
|
|
95
96
|
];
|
|
96
97
|
if (options.db) {
|
|
97
|
-
details.push({ label: 'database', value: options.db });
|
|
98
|
+
details.push({ label: 'database', value: maskConnectionUrl(options.db) });
|
|
98
99
|
}
|
|
99
100
|
const header = formatStyledHeader({
|
|
100
101
|
command: 'db verify',
|
|
@@ -189,6 +190,14 @@ async function executeDbVerifyCommand(
|
|
|
189
190
|
return notOk(error);
|
|
190
191
|
}
|
|
191
192
|
|
|
193
|
+
if (error instanceof ContractValidationError) {
|
|
194
|
+
return notOk(
|
|
195
|
+
errorContractValidationFailed(`Contract validation failed: ${error.message}`, {
|
|
196
|
+
where: { path: contractPathAbsolute },
|
|
197
|
+
}),
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
192
201
|
// Wrap unexpected errors
|
|
193
202
|
return notOk(
|
|
194
203
|
errorUnexpected(error instanceof Error ? error.message : String(error), {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { TargetBoundComponentDescriptor } from '@prisma-next/contract/framework-components';
|
|
2
|
+
import type { ContractIR } from '@prisma-next/contract/ir';
|
|
2
3
|
import type { CoreSchemaView } from '@prisma-next/core-control-plane/schema-view';
|
|
3
4
|
import { createControlPlaneStack } from '@prisma-next/core-control-plane/stack';
|
|
4
5
|
import type {
|
|
@@ -12,15 +13,21 @@ import type {
|
|
|
12
13
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
13
14
|
import { notOk, ok } from '@prisma-next/utils/result';
|
|
14
15
|
import { assertFrameworkComponentsCompatible } from '../utils/framework-components';
|
|
16
|
+
import { ContractValidationError } from './errors';
|
|
15
17
|
import { executeDbInit } from './operations/db-init';
|
|
18
|
+
import { executeDbUpdate } from './operations/db-update';
|
|
16
19
|
import type {
|
|
20
|
+
ControlActionName,
|
|
17
21
|
ControlClient,
|
|
18
22
|
ControlClientOptions,
|
|
19
23
|
DbInitOptions,
|
|
20
24
|
DbInitResult,
|
|
25
|
+
DbUpdateOptions,
|
|
26
|
+
DbUpdateResult,
|
|
21
27
|
EmitOptions,
|
|
22
28
|
EmitResult,
|
|
23
29
|
IntrospectOptions,
|
|
30
|
+
OnControlProgress,
|
|
24
31
|
SchemaVerifyOptions,
|
|
25
32
|
SignOptions,
|
|
26
33
|
VerifyOptions,
|
|
@@ -152,47 +159,47 @@ class ControlClientImpl implements ControlClient {
|
|
|
152
159
|
};
|
|
153
160
|
}
|
|
154
161
|
|
|
155
|
-
async
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
});
|
|
174
|
-
} catch (error) {
|
|
175
|
-
onProgress?.({
|
|
176
|
-
action: 'verify',
|
|
177
|
-
kind: 'spanEnd',
|
|
178
|
-
spanId: 'connect',
|
|
179
|
-
outcome: 'error',
|
|
180
|
-
});
|
|
181
|
-
throw error;
|
|
182
|
-
}
|
|
162
|
+
private async connectWithProgress(
|
|
163
|
+
connection: unknown | undefined,
|
|
164
|
+
action: ControlActionName,
|
|
165
|
+
onProgress?: OnControlProgress,
|
|
166
|
+
): Promise<void> {
|
|
167
|
+
if (connection === undefined) return;
|
|
168
|
+
onProgress?.({
|
|
169
|
+
action,
|
|
170
|
+
kind: 'spanStart',
|
|
171
|
+
spanId: 'connect',
|
|
172
|
+
label: 'Connecting to database...',
|
|
173
|
+
});
|
|
174
|
+
try {
|
|
175
|
+
await this.connect(connection);
|
|
176
|
+
onProgress?.({ action, kind: 'spanEnd', spanId: 'connect', outcome: 'ok' });
|
|
177
|
+
} catch (error) {
|
|
178
|
+
onProgress?.({ action, kind: 'spanEnd', spanId: 'connect', outcome: 'error' });
|
|
179
|
+
throw error;
|
|
183
180
|
}
|
|
181
|
+
}
|
|
184
182
|
|
|
183
|
+
async verify(options: VerifyOptions): Promise<VerifyDatabaseResult> {
|
|
184
|
+
const { onProgress } = options;
|
|
185
|
+
await this.connectWithProgress(options.connection, 'verify', onProgress);
|
|
185
186
|
const { driver, familyInstance } = await this.ensureConnected();
|
|
186
187
|
|
|
187
188
|
// Validate contract using family instance
|
|
188
|
-
|
|
189
|
+
let contractIR: ContractIR;
|
|
190
|
+
try {
|
|
191
|
+
contractIR = familyInstance.validateContractIR(options.contractIR);
|
|
192
|
+
} catch (error) {
|
|
193
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
194
|
+
throw new ContractValidationError(message, error);
|
|
195
|
+
}
|
|
189
196
|
|
|
190
197
|
// Emit verify span
|
|
191
198
|
onProgress?.({
|
|
192
199
|
action: 'verify',
|
|
193
200
|
kind: 'spanStart',
|
|
194
201
|
spanId: 'verify',
|
|
195
|
-
label: 'Verifying
|
|
202
|
+
label: 'Verifying database signature...',
|
|
196
203
|
});
|
|
197
204
|
|
|
198
205
|
try {
|
|
@@ -228,38 +235,17 @@ class ControlClientImpl implements ControlClient {
|
|
|
228
235
|
|
|
229
236
|
async schemaVerify(options: SchemaVerifyOptions): Promise<VerifyDatabaseSchemaResult> {
|
|
230
237
|
const { onProgress } = options;
|
|
231
|
-
|
|
232
|
-
// Connect with progress span if connection provided
|
|
233
|
-
if (options.connection !== undefined) {
|
|
234
|
-
onProgress?.({
|
|
235
|
-
action: 'schemaVerify',
|
|
236
|
-
kind: 'spanStart',
|
|
237
|
-
spanId: 'connect',
|
|
238
|
-
label: 'Connecting to database...',
|
|
239
|
-
});
|
|
240
|
-
try {
|
|
241
|
-
await this.connect(options.connection);
|
|
242
|
-
onProgress?.({
|
|
243
|
-
action: 'schemaVerify',
|
|
244
|
-
kind: 'spanEnd',
|
|
245
|
-
spanId: 'connect',
|
|
246
|
-
outcome: 'ok',
|
|
247
|
-
});
|
|
248
|
-
} catch (error) {
|
|
249
|
-
onProgress?.({
|
|
250
|
-
action: 'schemaVerify',
|
|
251
|
-
kind: 'spanEnd',
|
|
252
|
-
spanId: 'connect',
|
|
253
|
-
outcome: 'error',
|
|
254
|
-
});
|
|
255
|
-
throw error;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
238
|
+
await this.connectWithProgress(options.connection, 'schemaVerify', onProgress);
|
|
259
239
|
const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
|
|
260
240
|
|
|
261
241
|
// Validate contract using family instance
|
|
262
|
-
|
|
242
|
+
let contractIR: ContractIR;
|
|
243
|
+
try {
|
|
244
|
+
contractIR = familyInstance.validateContractIR(options.contractIR);
|
|
245
|
+
} catch (error) {
|
|
246
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
247
|
+
throw new ContractValidationError(message, error);
|
|
248
|
+
}
|
|
263
249
|
|
|
264
250
|
// Emit schemaVerify span
|
|
265
251
|
onProgress?.({
|
|
@@ -300,38 +286,17 @@ class ControlClientImpl implements ControlClient {
|
|
|
300
286
|
|
|
301
287
|
async sign(options: SignOptions): Promise<SignDatabaseResult> {
|
|
302
288
|
const { onProgress } = options;
|
|
303
|
-
|
|
304
|
-
// Connect with progress span if connection provided
|
|
305
|
-
if (options.connection !== undefined) {
|
|
306
|
-
onProgress?.({
|
|
307
|
-
action: 'sign',
|
|
308
|
-
kind: 'spanStart',
|
|
309
|
-
spanId: 'connect',
|
|
310
|
-
label: 'Connecting to database...',
|
|
311
|
-
});
|
|
312
|
-
try {
|
|
313
|
-
await this.connect(options.connection);
|
|
314
|
-
onProgress?.({
|
|
315
|
-
action: 'sign',
|
|
316
|
-
kind: 'spanEnd',
|
|
317
|
-
spanId: 'connect',
|
|
318
|
-
outcome: 'ok',
|
|
319
|
-
});
|
|
320
|
-
} catch (error) {
|
|
321
|
-
onProgress?.({
|
|
322
|
-
action: 'sign',
|
|
323
|
-
kind: 'spanEnd',
|
|
324
|
-
spanId: 'connect',
|
|
325
|
-
outcome: 'error',
|
|
326
|
-
});
|
|
327
|
-
throw error;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
289
|
+
await this.connectWithProgress(options.connection, 'sign', onProgress);
|
|
331
290
|
const { driver, familyInstance } = await this.ensureConnected();
|
|
332
291
|
|
|
333
292
|
// Validate contract using family instance
|
|
334
|
-
|
|
293
|
+
let contractIR: ContractIR;
|
|
294
|
+
try {
|
|
295
|
+
contractIR = familyInstance.validateContractIR(options.contractIR);
|
|
296
|
+
} catch (error) {
|
|
297
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
298
|
+
throw new ContractValidationError(message, error);
|
|
299
|
+
}
|
|
335
300
|
|
|
336
301
|
// Emit sign span
|
|
337
302
|
onProgress?.({
|
|
@@ -371,34 +336,7 @@ class ControlClientImpl implements ControlClient {
|
|
|
371
336
|
|
|
372
337
|
async dbInit(options: DbInitOptions): Promise<DbInitResult> {
|
|
373
338
|
const { onProgress } = options;
|
|
374
|
-
|
|
375
|
-
// Connect with progress span if connection provided
|
|
376
|
-
if (options.connection !== undefined) {
|
|
377
|
-
onProgress?.({
|
|
378
|
-
action: 'dbInit',
|
|
379
|
-
kind: 'spanStart',
|
|
380
|
-
spanId: 'connect',
|
|
381
|
-
label: 'Connecting to database...',
|
|
382
|
-
});
|
|
383
|
-
try {
|
|
384
|
-
await this.connect(options.connection);
|
|
385
|
-
onProgress?.({
|
|
386
|
-
action: 'dbInit',
|
|
387
|
-
kind: 'spanEnd',
|
|
388
|
-
spanId: 'connect',
|
|
389
|
-
outcome: 'ok',
|
|
390
|
-
});
|
|
391
|
-
} catch (error) {
|
|
392
|
-
onProgress?.({
|
|
393
|
-
action: 'dbInit',
|
|
394
|
-
kind: 'spanEnd',
|
|
395
|
-
spanId: 'connect',
|
|
396
|
-
outcome: 'error',
|
|
397
|
-
});
|
|
398
|
-
throw error;
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
339
|
+
await this.connectWithProgress(options.connection, 'dbInit', onProgress);
|
|
402
340
|
const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
|
|
403
341
|
|
|
404
342
|
// Check target supports migrations
|
|
@@ -407,7 +345,13 @@ class ControlClientImpl implements ControlClient {
|
|
|
407
345
|
}
|
|
408
346
|
|
|
409
347
|
// Validate contract using family instance
|
|
410
|
-
|
|
348
|
+
let contractIR: ContractIR;
|
|
349
|
+
try {
|
|
350
|
+
contractIR = familyInstance.validateContractIR(options.contractIR);
|
|
351
|
+
} catch (error) {
|
|
352
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
353
|
+
throw new ContractValidationError(message, error);
|
|
354
|
+
}
|
|
411
355
|
|
|
412
356
|
// Delegate to extracted dbInit operation
|
|
413
357
|
return executeDbInit({
|
|
@@ -417,40 +361,42 @@ class ControlClientImpl implements ControlClient {
|
|
|
417
361
|
mode: options.mode,
|
|
418
362
|
migrations: this.options.target.migrations,
|
|
419
363
|
frameworkComponents,
|
|
420
|
-
...(onProgress
|
|
364
|
+
...ifDefined('onProgress', onProgress),
|
|
421
365
|
});
|
|
422
366
|
}
|
|
423
367
|
|
|
424
|
-
async
|
|
425
|
-
const onProgress = options
|
|
368
|
+
async dbUpdate(options: DbUpdateOptions): Promise<DbUpdateResult> {
|
|
369
|
+
const { onProgress } = options;
|
|
370
|
+
await this.connectWithProgress(options.connection, 'dbUpdate', onProgress);
|
|
371
|
+
const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
|
|
426
372
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
onProgress?.({
|
|
430
|
-
action: 'introspect',
|
|
431
|
-
kind: 'spanStart',
|
|
432
|
-
spanId: 'connect',
|
|
433
|
-
label: 'Connecting to database...',
|
|
434
|
-
});
|
|
435
|
-
try {
|
|
436
|
-
await this.connect(options.connection);
|
|
437
|
-
onProgress?.({
|
|
438
|
-
action: 'introspect',
|
|
439
|
-
kind: 'spanEnd',
|
|
440
|
-
spanId: 'connect',
|
|
441
|
-
outcome: 'ok',
|
|
442
|
-
});
|
|
443
|
-
} catch (error) {
|
|
444
|
-
onProgress?.({
|
|
445
|
-
action: 'introspect',
|
|
446
|
-
kind: 'spanEnd',
|
|
447
|
-
spanId: 'connect',
|
|
448
|
-
outcome: 'error',
|
|
449
|
-
});
|
|
450
|
-
throw error;
|
|
451
|
-
}
|
|
373
|
+
if (!this.options.target.migrations) {
|
|
374
|
+
throw new Error(`Target "${this.options.target.targetId}" does not support migrations`);
|
|
452
375
|
}
|
|
453
376
|
|
|
377
|
+
let contractIR: ContractIR;
|
|
378
|
+
try {
|
|
379
|
+
contractIR = familyInstance.validateContractIR(options.contractIR);
|
|
380
|
+
} catch (error) {
|
|
381
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
382
|
+
throw new ContractValidationError(message, error);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return executeDbUpdate({
|
|
386
|
+
driver,
|
|
387
|
+
familyInstance,
|
|
388
|
+
contractIR,
|
|
389
|
+
mode: options.mode,
|
|
390
|
+
migrations: this.options.target.migrations,
|
|
391
|
+
frameworkComponents,
|
|
392
|
+
...ifDefined('acceptDataLoss', options.acceptDataLoss),
|
|
393
|
+
...ifDefined('onProgress', onProgress),
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
async introspect(options?: IntrospectOptions): Promise<unknown> {
|
|
398
|
+
const onProgress = options?.onProgress;
|
|
399
|
+
await this.connectWithProgress(options?.connection, 'introspect', onProgress);
|
|
454
400
|
const { driver, familyInstance } = await this.ensureConnected();
|
|
455
401
|
|
|
456
402
|
// TODO: Pass schema option to familyInstance.introspect when schema filtering is implemented
|
|
@@ -575,6 +521,24 @@ class ControlClientImpl implements ControlClient {
|
|
|
575
521
|
});
|
|
576
522
|
|
|
577
523
|
try {
|
|
524
|
+
try {
|
|
525
|
+
this.familyInstance.validateContractIR(contractRaw);
|
|
526
|
+
} catch (error) {
|
|
527
|
+
onProgress?.({
|
|
528
|
+
action: 'emit',
|
|
529
|
+
kind: 'spanEnd',
|
|
530
|
+
spanId: 'emit',
|
|
531
|
+
outcome: 'error',
|
|
532
|
+
});
|
|
533
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
534
|
+
return notOk({
|
|
535
|
+
code: 'CONTRACT_VALIDATION_FAILED',
|
|
536
|
+
summary: 'Contract validation failed',
|
|
537
|
+
why: message,
|
|
538
|
+
meta: undefined,
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
|
|
578
542
|
const emitResult = await this.familyInstance.emitContract({ contractIR: contractRaw });
|
|
579
543
|
|
|
580
544
|
onProgress?.({
|