@prisma-next/cli 0.3.0-dev.15 → 0.3.0-dev.162
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/LICENSE +201 -0
- package/README.md +381 -128
- package/dist/cli-errors-BDCYR5ap.mjs +4 -0
- package/dist/cli-errors-Dzs7Oxz7.d.mts +3 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.js +1 -2346
- package/dist/cli.mjs +245 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/client-yYtotiSX.mjs +1063 -0
- package/dist/client-yYtotiSX.mjs.map +1 -0
- package/dist/commands/contract-emit.d.mts +7 -0
- package/dist/commands/contract-emit.d.mts.map +1 -0
- package/dist/commands/contract-emit.mjs +8 -0
- package/dist/commands/contract-infer.d.mts +7 -0
- package/dist/commands/contract-infer.d.mts.map +1 -0
- package/dist/commands/contract-infer.mjs +9 -0
- package/dist/commands/db-init.d.mts +7 -0
- package/dist/commands/db-init.d.mts.map +1 -0
- package/dist/commands/db-init.mjs +125 -0
- package/dist/commands/db-init.mjs.map +1 -0
- package/dist/commands/db-schema.d.mts +7 -0
- package/dist/commands/db-schema.d.mts.map +1 -0
- package/dist/commands/db-schema.mjs +55 -0
- package/dist/commands/db-schema.mjs.map +1 -0
- package/dist/commands/db-sign.d.mts +7 -0
- package/dist/commands/db-sign.d.mts.map +1 -0
- package/dist/commands/db-sign.mjs +136 -0
- package/dist/commands/db-sign.mjs.map +1 -0
- 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 +122 -0
- package/dist/commands/db-update.mjs.map +1 -0
- package/dist/commands/db-verify.d.mts +7 -0
- package/dist/commands/db-verify.d.mts.map +1 -0
- package/dist/commands/db-verify.mjs +322 -0
- package/dist/commands/db-verify.mjs.map +1 -0
- package/dist/commands/migration-apply.d.mts +36 -0
- package/dist/commands/migration-apply.d.mts.map +1 -0
- package/dist/commands/migration-apply.mjs +244 -0
- package/dist/commands/migration-apply.mjs.map +1 -0
- package/dist/commands/migration-new.d.mts +8 -0
- package/dist/commands/migration-new.d.mts.map +1 -0
- package/dist/commands/migration-new.mjs +151 -0
- package/dist/commands/migration-new.mjs.map +1 -0
- package/dist/commands/migration-plan.d.mts +47 -0
- package/dist/commands/migration-plan.d.mts.map +1 -0
- package/dist/commands/migration-plan.mjs +312 -0
- package/dist/commands/migration-plan.mjs.map +1 -0
- package/dist/commands/migration-ref.d.mts +43 -0
- package/dist/commands/migration-ref.d.mts.map +1 -0
- package/dist/commands/migration-ref.mjs +194 -0
- package/dist/commands/migration-ref.mjs.map +1 -0
- package/dist/commands/migration-show.d.mts +28 -0
- package/dist/commands/migration-show.d.mts.map +1 -0
- package/dist/commands/migration-show.mjs +139 -0
- package/dist/commands/migration-show.mjs.map +1 -0
- package/dist/commands/migration-status.d.mts +86 -0
- package/dist/commands/migration-status.d.mts.map +1 -0
- package/dist/commands/migration-status.mjs +8 -0
- package/dist/commands/migration-verify.d.mts +16 -0
- package/dist/commands/migration-verify.d.mts.map +1 -0
- package/dist/commands/migration-verify.mjs +109 -0
- package/dist/commands/migration-verify.mjs.map +1 -0
- package/dist/config-loader-C4VXKl8f.mjs +43 -0
- package/dist/config-loader-C4VXKl8f.mjs.map +1 -0
- package/dist/{config-loader.d.ts → config-loader.d.mts} +8 -3
- package/dist/config-loader.d.mts.map +1 -0
- package/dist/config-loader.mjs +3 -0
- package/dist/contract-emit-Bk_eEDKu.mjs +187 -0
- package/dist/contract-emit-Bk_eEDKu.mjs.map +1 -0
- package/dist/contract-infer-suMDmFSG.mjs +89 -0
- package/dist/contract-infer-suMDmFSG.mjs.map +1 -0
- package/dist/exports/config-types.d.mts +2 -0
- package/dist/exports/config-types.mjs +3 -0
- package/dist/exports/control-api.d.mts +624 -0
- package/dist/exports/control-api.d.mts.map +1 -0
- package/dist/exports/control-api.mjs +109 -0
- package/dist/exports/control-api.mjs.map +1 -0
- package/dist/{load-ts-contract.d.ts → exports/index.d.mts} +12 -7
- package/dist/exports/index.d.mts.map +1 -0
- package/dist/exports/index.mjs +141 -0
- package/dist/exports/index.mjs.map +1 -0
- package/dist/extract-operation-statements-BVlb3jxp.mjs +13 -0
- package/dist/extract-operation-statements-BVlb3jxp.mjs.map +1 -0
- package/dist/extract-sql-ddl-6EVSOThm.mjs +26 -0
- package/dist/extract-sql-ddl-6EVSOThm.mjs.map +1 -0
- package/dist/framework-components-BAsliT4V.mjs +59 -0
- package/dist/framework-components-BAsliT4V.mjs.map +1 -0
- package/dist/inspect-live-schema-HMutsJYh.mjs +91 -0
- package/dist/inspect-live-schema-HMutsJYh.mjs.map +1 -0
- package/dist/migration-command-scaffold-Dg7CKKCg.mjs +105 -0
- package/dist/migration-command-scaffold-Dg7CKKCg.mjs.map +1 -0
- package/dist/migration-status-BqfVmC0w.mjs +1582 -0
- package/dist/migration-status-BqfVmC0w.mjs.map +1 -0
- package/dist/migrations-Bv8oeiY_.mjs +173 -0
- package/dist/migrations-Bv8oeiY_.mjs.map +1 -0
- package/dist/progress-adapter-D4x8SbJa.mjs +43 -0
- package/dist/progress-adapter-D4x8SbJa.mjs.map +1 -0
- package/dist/terminal-ui-N5tR-ob5.mjs +967 -0
- package/dist/terminal-ui-N5tR-ob5.mjs.map +1 -0
- package/dist/verify-WARh5TjK.mjs +385 -0
- package/dist/verify-WARh5TjK.mjs.map +1 -0
- package/package.json +87 -41
- package/src/cli.ts +113 -58
- package/src/commands/contract-emit.ts +237 -144
- package/src/commands/contract-infer-paths.ts +32 -0
- package/src/commands/contract-infer.ts +143 -0
- package/src/commands/db-init.ts +211 -426
- package/src/commands/db-schema.ts +77 -0
- package/src/commands/db-sign.ts +208 -229
- package/src/commands/db-update.ts +236 -0
- package/src/commands/db-verify.ts +504 -184
- package/src/commands/inspect-live-schema.ts +170 -0
- package/src/commands/migration-apply.ts +427 -0
- package/src/commands/migration-new.ts +260 -0
- package/src/commands/migration-plan.ts +519 -0
- package/src/commands/migration-ref.ts +305 -0
- package/src/commands/migration-show.ts +246 -0
- package/src/commands/migration-status.ts +864 -0
- package/src/commands/migration-verify.ts +180 -0
- package/src/config-loader.ts +13 -3
- package/src/control-api/client.ts +428 -46
- package/src/control-api/contract-enrichment.ts +119 -0
- package/src/control-api/errors.ts +9 -0
- package/src/control-api/operations/contract-emit.ts +174 -0
- package/src/control-api/operations/db-init.ts +144 -26
- package/src/control-api/operations/db-update.ts +220 -0
- package/src/control-api/operations/extract-operation-statements.ts +14 -0
- package/src/control-api/operations/extract-sql-ddl.ts +47 -0
- package/src/control-api/operations/migration-apply.ts +191 -0
- package/src/control-api/operations/migration-helpers.ts +49 -0
- package/src/control-api/types.ts +450 -18
- package/src/exports/config-types.ts +4 -3
- package/src/exports/control-api.ts +24 -2
- package/src/load-ts-contract.ts +30 -19
- package/src/utils/cli-errors.ts +14 -8
- package/src/utils/command-helpers.ts +302 -3
- package/src/utils/formatters/emit.ts +67 -0
- package/src/utils/formatters/errors.ts +82 -0
- package/src/utils/formatters/graph-migration-mapper.ts +240 -0
- package/src/utils/formatters/graph-render.ts +1323 -0
- package/src/utils/formatters/graph-types.ts +120 -0
- package/src/utils/formatters/help.ts +380 -0
- package/src/utils/formatters/helpers.ts +28 -0
- package/src/utils/formatters/migrations.ts +346 -0
- package/src/utils/formatters/styled.ts +212 -0
- package/src/utils/formatters/verify.ts +621 -0
- package/src/utils/framework-components.ts +13 -10
- package/src/utils/global-flags.ts +41 -23
- package/src/utils/migration-command-scaffold.ts +184 -0
- package/src/utils/migration-types.ts +12 -0
- package/src/utils/progress-adapter.ts +75 -0
- package/src/utils/result-handler.ts +12 -13
- package/src/utils/shutdown.ts +92 -0
- package/src/utils/suggest-command.ts +31 -0
- package/src/utils/terminal-ui.ts +276 -0
- package/dist/chunk-6EPKRATC.js +0 -91
- package/dist/chunk-6EPKRATC.js.map +0 -1
- package/dist/chunk-DIJPT5TZ.js +0 -967
- package/dist/chunk-DIJPT5TZ.js.map +0 -1
- package/dist/chunk-HWYQOCAJ.js +0 -47
- package/dist/chunk-HWYQOCAJ.js.map +0 -1
- package/dist/chunk-MG7PBERL.js +0 -131
- package/dist/chunk-MG7PBERL.js.map +0 -1
- package/dist/chunk-VI2YETW7.js +0 -38
- package/dist/chunk-VI2YETW7.js.map +0 -1
- package/dist/cli.d.ts +0 -2
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/commands/contract-emit.d.ts +0 -3
- package/dist/commands/contract-emit.d.ts.map +0 -1
- package/dist/commands/contract-emit.js +0 -10
- package/dist/commands/contract-emit.js.map +0 -1
- package/dist/commands/db-init.d.ts +0 -3
- package/dist/commands/db-init.d.ts.map +0 -1
- package/dist/commands/db-init.js +0 -339
- package/dist/commands/db-init.js.map +0 -1
- package/dist/commands/db-introspect.d.ts +0 -3
- package/dist/commands/db-introspect.d.ts.map +0 -1
- package/dist/commands/db-introspect.js +0 -183
- package/dist/commands/db-introspect.js.map +0 -1
- package/dist/commands/db-schema-verify.d.ts +0 -3
- package/dist/commands/db-schema-verify.d.ts.map +0 -1
- package/dist/commands/db-schema-verify.js +0 -161
- package/dist/commands/db-schema-verify.js.map +0 -1
- package/dist/commands/db-sign.d.ts +0 -3
- package/dist/commands/db-sign.d.ts.map +0 -1
- package/dist/commands/db-sign.js +0 -196
- package/dist/commands/db-sign.js.map +0 -1
- package/dist/commands/db-verify.d.ts +0 -3
- package/dist/commands/db-verify.d.ts.map +0 -1
- package/dist/commands/db-verify.js +0 -170
- package/dist/commands/db-verify.js.map +0 -1
- package/dist/config-loader.d.ts.map +0 -1
- package/dist/config-loader.js +0 -7
- package/dist/config-loader.js.map +0 -1
- package/dist/control-api/client.d.ts +0 -13
- package/dist/control-api/client.d.ts.map +0 -1
- package/dist/control-api/operations/db-init.d.ts +0 -27
- package/dist/control-api/operations/db-init.d.ts.map +0 -1
- package/dist/control-api/types.d.ts +0 -203
- package/dist/control-api/types.d.ts.map +0 -1
- package/dist/exports/config-types.d.ts +0 -3
- package/dist/exports/config-types.d.ts.map +0 -1
- package/dist/exports/config-types.js +0 -6
- package/dist/exports/config-types.js.map +0 -1
- package/dist/exports/control-api.d.ts +0 -13
- package/dist/exports/control-api.d.ts.map +0 -1
- package/dist/exports/control-api.js +0 -240
- package/dist/exports/control-api.js.map +0 -1
- package/dist/exports/index.d.ts +0 -4
- package/dist/exports/index.d.ts.map +0 -1
- package/dist/exports/index.js +0 -176
- package/dist/exports/index.js.map +0 -1
- package/dist/load-ts-contract.d.ts.map +0 -1
- package/dist/utils/action.d.ts +0 -16
- package/dist/utils/action.d.ts.map +0 -1
- package/dist/utils/cli-errors.d.ts +0 -7
- package/dist/utils/cli-errors.d.ts.map +0 -1
- package/dist/utils/command-helpers.d.ts +0 -12
- package/dist/utils/command-helpers.d.ts.map +0 -1
- package/dist/utils/framework-components.d.ts +0 -70
- package/dist/utils/framework-components.d.ts.map +0 -1
- package/dist/utils/global-flags.d.ts +0 -25
- package/dist/utils/global-flags.d.ts.map +0 -1
- package/dist/utils/output.d.ts +0 -142
- package/dist/utils/output.d.ts.map +0 -1
- package/dist/utils/result-handler.d.ts +0 -15
- package/dist/utils/result-handler.d.ts.map +0 -1
- package/dist/utils/spinner.d.ts +0 -29
- package/dist/utils/spinner.d.ts.map +0 -1
- package/src/commands/db-introspect.ts +0 -254
- package/src/commands/db-schema-verify.ts +0 -231
- package/src/utils/action.ts +0 -43
- package/src/utils/output.ts +0 -1471
- package/src/utils/spinner.ts +0 -67
|
@@ -1,21 +1,42 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
1
|
+
import type { Contract, ContractMarkerRecord } from '@prisma-next/contract/types';
|
|
2
|
+
import { emit as emitContractArtifacts } from '@prisma-next/emitter';
|
|
3
|
+
import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
|
|
3
4
|
import type {
|
|
4
5
|
ControlDriverInstance,
|
|
5
6
|
ControlFamilyInstance,
|
|
6
|
-
|
|
7
|
+
ControlStack,
|
|
8
|
+
CoreSchemaView,
|
|
7
9
|
SignDatabaseResult,
|
|
8
10
|
VerifyDatabaseResult,
|
|
9
11
|
VerifyDatabaseSchemaResult,
|
|
10
|
-
} from '@prisma-next/
|
|
12
|
+
} from '@prisma-next/framework-components/control';
|
|
13
|
+
import {
|
|
14
|
+
createControlStack,
|
|
15
|
+
hasMigrations,
|
|
16
|
+
hasSchemaView,
|
|
17
|
+
} from '@prisma-next/framework-components/control';
|
|
18
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
19
|
+
import { notOk, ok } from '@prisma-next/utils/result';
|
|
11
20
|
import { assertFrameworkComponentsCompatible } from '../utils/framework-components';
|
|
21
|
+
import { enrichContract } from './contract-enrichment';
|
|
22
|
+
import { ContractValidationError } from './errors';
|
|
12
23
|
import { executeDbInit } from './operations/db-init';
|
|
24
|
+
import { executeDbUpdate } from './operations/db-update';
|
|
25
|
+
import { executeMigrationApply } from './operations/migration-apply';
|
|
13
26
|
import type {
|
|
27
|
+
ControlActionName,
|
|
14
28
|
ControlClient,
|
|
15
29
|
ControlClientOptions,
|
|
16
30
|
DbInitOptions,
|
|
17
31
|
DbInitResult,
|
|
32
|
+
DbUpdateOptions,
|
|
33
|
+
DbUpdateResult,
|
|
34
|
+
EmitOptions,
|
|
35
|
+
EmitResult,
|
|
18
36
|
IntrospectOptions,
|
|
37
|
+
MigrationApplyOptions,
|
|
38
|
+
MigrationApplyResult,
|
|
39
|
+
OnControlProgress,
|
|
19
40
|
SchemaVerifyOptions,
|
|
20
41
|
SignOptions,
|
|
21
42
|
VerifyOptions,
|
|
@@ -41,9 +62,9 @@ export function createControlClient(options: ControlClientOptions): ControlClien
|
|
|
41
62
|
*/
|
|
42
63
|
class ControlClientImpl implements ControlClient {
|
|
43
64
|
private readonly options: ControlClientOptions;
|
|
44
|
-
private stack:
|
|
65
|
+
private stack: ControlStack | null = null;
|
|
45
66
|
private driver: ControlDriverInstance<string, string> | null = null;
|
|
46
|
-
private familyInstance: ControlFamilyInstance<string> | null = null;
|
|
67
|
+
private familyInstance: ControlFamilyInstance<string, unknown> | null = null;
|
|
47
68
|
private frameworkComponents: ReadonlyArray<
|
|
48
69
|
TargetBoundComponentDescriptor<string, string>
|
|
49
70
|
> | null = null;
|
|
@@ -60,15 +81,14 @@ class ControlClientImpl implements ControlClient {
|
|
|
60
81
|
return; // Idempotent
|
|
61
82
|
}
|
|
62
83
|
|
|
63
|
-
|
|
64
|
-
|
|
84
|
+
this.stack = createControlStack({
|
|
85
|
+
family: this.options.family,
|
|
65
86
|
target: this.options.target,
|
|
66
87
|
adapter: this.options.adapter,
|
|
67
88
|
driver: this.options.driver,
|
|
68
89
|
extensionPacks: this.options.extensionPacks,
|
|
69
90
|
});
|
|
70
91
|
|
|
71
|
-
// Create family instance using the stack
|
|
72
92
|
this.familyInstance = this.options.family.create(this.stack);
|
|
73
93
|
|
|
74
94
|
// Validate and type-narrow framework components
|
|
@@ -109,12 +129,8 @@ class ControlClientImpl implements ControlClient {
|
|
|
109
129
|
);
|
|
110
130
|
}
|
|
111
131
|
|
|
112
|
-
// Create driver instance
|
|
113
|
-
// Cast through any since connection type is driver-specific at runtime.
|
|
114
|
-
// The driver descriptor is typed with any for TConnection in ControlClientOptions,
|
|
115
|
-
// but createControlPlaneStack defaults it to string. We bridge this at runtime.
|
|
116
132
|
// biome-ignore lint/suspicious/noExplicitAny: required for runtime connection type flexibility
|
|
117
|
-
this.driver = await this.stack
|
|
133
|
+
this.driver = await this.stack.driver.create(resolvedConnection as any);
|
|
118
134
|
}
|
|
119
135
|
|
|
120
136
|
async close(): Promise<void> {
|
|
@@ -126,7 +142,7 @@ class ControlClientImpl implements ControlClient {
|
|
|
126
142
|
|
|
127
143
|
private async ensureConnected(): Promise<{
|
|
128
144
|
driver: ControlDriverInstance<string, string>;
|
|
129
|
-
familyInstance: ControlFamilyInstance<string>;
|
|
145
|
+
familyInstance: ControlFamilyInstance<string, unknown>;
|
|
130
146
|
frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<string, string>>;
|
|
131
147
|
}> {
|
|
132
148
|
// Auto-init if needed
|
|
@@ -147,83 +163,449 @@ class ControlClientImpl implements ControlClient {
|
|
|
147
163
|
};
|
|
148
164
|
}
|
|
149
165
|
|
|
166
|
+
private async connectWithProgress(
|
|
167
|
+
connection: unknown | undefined,
|
|
168
|
+
action: ControlActionName,
|
|
169
|
+
onProgress?: OnControlProgress,
|
|
170
|
+
): Promise<void> {
|
|
171
|
+
if (connection === undefined) return;
|
|
172
|
+
onProgress?.({
|
|
173
|
+
action,
|
|
174
|
+
kind: 'spanStart',
|
|
175
|
+
spanId: 'connect',
|
|
176
|
+
label: 'Connecting to database...',
|
|
177
|
+
});
|
|
178
|
+
try {
|
|
179
|
+
await this.connect(connection);
|
|
180
|
+
onProgress?.({ action, kind: 'spanEnd', spanId: 'connect', outcome: 'ok' });
|
|
181
|
+
} catch (error) {
|
|
182
|
+
onProgress?.({ action, kind: 'spanEnd', spanId: 'connect', outcome: 'error' });
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
150
187
|
async verify(options: VerifyOptions): Promise<VerifyDatabaseResult> {
|
|
188
|
+
const { onProgress } = options;
|
|
189
|
+
await this.connectWithProgress(options.connection, 'verify', onProgress);
|
|
151
190
|
const { driver, familyInstance } = await this.ensureConnected();
|
|
152
191
|
|
|
153
192
|
// Validate contract using family instance
|
|
154
|
-
|
|
193
|
+
let contract: Contract;
|
|
194
|
+
try {
|
|
195
|
+
contract = familyInstance.validateContract(options.contract);
|
|
196
|
+
} catch (error) {
|
|
197
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
198
|
+
throw new ContractValidationError(message, error);
|
|
199
|
+
}
|
|
155
200
|
|
|
156
|
-
//
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
contractIR,
|
|
163
|
-
expectedTargetId: this.options.target.targetId,
|
|
164
|
-
contractPath: '',
|
|
201
|
+
// Emit verify span
|
|
202
|
+
onProgress?.({
|
|
203
|
+
action: 'verify',
|
|
204
|
+
kind: 'spanStart',
|
|
205
|
+
spanId: 'verify',
|
|
206
|
+
label: 'Verifying database marker...',
|
|
165
207
|
});
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
// Delegate to family instance verify method
|
|
211
|
+
// Note: We pass empty strings for contractPath/configPath since the programmatic
|
|
212
|
+
// API doesn't deal with file paths. The family instance accepts these as optional
|
|
213
|
+
// metadata for error reporting.
|
|
214
|
+
const result = await familyInstance.verify({
|
|
215
|
+
driver,
|
|
216
|
+
contract,
|
|
217
|
+
expectedTargetId: this.options.target.targetId,
|
|
218
|
+
contractPath: '',
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
onProgress?.({
|
|
222
|
+
action: 'verify',
|
|
223
|
+
kind: 'spanEnd',
|
|
224
|
+
spanId: 'verify',
|
|
225
|
+
outcome: result.ok ? 'ok' : 'error',
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
return result;
|
|
229
|
+
} catch (error) {
|
|
230
|
+
onProgress?.({
|
|
231
|
+
action: 'verify',
|
|
232
|
+
kind: 'spanEnd',
|
|
233
|
+
spanId: 'verify',
|
|
234
|
+
outcome: 'error',
|
|
235
|
+
});
|
|
236
|
+
throw error;
|
|
237
|
+
}
|
|
166
238
|
}
|
|
167
239
|
|
|
168
240
|
async schemaVerify(options: SchemaVerifyOptions): Promise<VerifyDatabaseSchemaResult> {
|
|
241
|
+
const { onProgress } = options;
|
|
242
|
+
await this.connectWithProgress(options.connection, 'schemaVerify', onProgress);
|
|
169
243
|
const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
|
|
170
244
|
|
|
171
245
|
// Validate contract using family instance
|
|
172
|
-
|
|
246
|
+
let contract: Contract;
|
|
247
|
+
try {
|
|
248
|
+
contract = familyInstance.validateContract(options.contract);
|
|
249
|
+
} catch (error) {
|
|
250
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
251
|
+
throw new ContractValidationError(message, error);
|
|
252
|
+
}
|
|
173
253
|
|
|
174
|
-
//
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
frameworkComponents,
|
|
254
|
+
// Emit schemaVerify span
|
|
255
|
+
onProgress?.({
|
|
256
|
+
action: 'schemaVerify',
|
|
257
|
+
kind: 'spanStart',
|
|
258
|
+
spanId: 'schemaVerify',
|
|
259
|
+
label: 'Verifying database schema...',
|
|
181
260
|
});
|
|
261
|
+
|
|
262
|
+
try {
|
|
263
|
+
// Delegate to family instance schemaVerify method
|
|
264
|
+
const result = await familyInstance.schemaVerify({
|
|
265
|
+
driver,
|
|
266
|
+
contract,
|
|
267
|
+
strict: options.strict ?? false,
|
|
268
|
+
contractPath: '',
|
|
269
|
+
frameworkComponents,
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
onProgress?.({
|
|
273
|
+
action: 'schemaVerify',
|
|
274
|
+
kind: 'spanEnd',
|
|
275
|
+
spanId: 'schemaVerify',
|
|
276
|
+
outcome: result.ok ? 'ok' : 'error',
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
return result;
|
|
280
|
+
} catch (error) {
|
|
281
|
+
onProgress?.({
|
|
282
|
+
action: 'schemaVerify',
|
|
283
|
+
kind: 'spanEnd',
|
|
284
|
+
spanId: 'schemaVerify',
|
|
285
|
+
outcome: 'error',
|
|
286
|
+
});
|
|
287
|
+
throw error;
|
|
288
|
+
}
|
|
182
289
|
}
|
|
183
290
|
|
|
184
291
|
async sign(options: SignOptions): Promise<SignDatabaseResult> {
|
|
292
|
+
const { onProgress } = options;
|
|
293
|
+
await this.connectWithProgress(options.connection, 'sign', onProgress);
|
|
185
294
|
const { driver, familyInstance } = await this.ensureConnected();
|
|
186
295
|
|
|
187
296
|
// Validate contract using family instance
|
|
188
|
-
|
|
297
|
+
let contract: Contract;
|
|
298
|
+
try {
|
|
299
|
+
contract = familyInstance.validateContract(options.contract);
|
|
300
|
+
} catch (error) {
|
|
301
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
302
|
+
throw new ContractValidationError(message, error);
|
|
303
|
+
}
|
|
189
304
|
|
|
190
|
-
//
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
305
|
+
// Emit sign span
|
|
306
|
+
onProgress?.({
|
|
307
|
+
action: 'sign',
|
|
308
|
+
kind: 'spanStart',
|
|
309
|
+
spanId: 'sign',
|
|
310
|
+
label: 'Signing database...',
|
|
195
311
|
});
|
|
312
|
+
|
|
313
|
+
try {
|
|
314
|
+
// Delegate to family instance sign method
|
|
315
|
+
const result = await familyInstance.sign({
|
|
316
|
+
driver,
|
|
317
|
+
contract,
|
|
318
|
+
contractPath: options.contractPath ?? '',
|
|
319
|
+
...ifDefined('configPath', options.configPath),
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
onProgress?.({
|
|
323
|
+
action: 'sign',
|
|
324
|
+
kind: 'spanEnd',
|
|
325
|
+
spanId: 'sign',
|
|
326
|
+
outcome: 'ok',
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
return result;
|
|
330
|
+
} catch (error) {
|
|
331
|
+
onProgress?.({
|
|
332
|
+
action: 'sign',
|
|
333
|
+
kind: 'spanEnd',
|
|
334
|
+
spanId: 'sign',
|
|
335
|
+
outcome: 'error',
|
|
336
|
+
});
|
|
337
|
+
throw error;
|
|
338
|
+
}
|
|
196
339
|
}
|
|
197
340
|
|
|
198
341
|
async dbInit(options: DbInitOptions): Promise<DbInitResult> {
|
|
342
|
+
const { onProgress } = options;
|
|
343
|
+
await this.connectWithProgress(options.connection, 'dbInit', onProgress);
|
|
199
344
|
const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
|
|
200
345
|
|
|
201
|
-
|
|
202
|
-
if (!this.options.target.migrations) {
|
|
346
|
+
if (!hasMigrations(this.options.target)) {
|
|
203
347
|
throw new Error(`Target "${this.options.target.targetId}" does not support migrations`);
|
|
204
348
|
}
|
|
205
349
|
|
|
206
|
-
|
|
207
|
-
|
|
350
|
+
let contract: Contract;
|
|
351
|
+
try {
|
|
352
|
+
contract = familyInstance.validateContract(options.contract);
|
|
353
|
+
} catch (error) {
|
|
354
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
355
|
+
throw new ContractValidationError(message, error);
|
|
356
|
+
}
|
|
208
357
|
|
|
209
|
-
// Delegate to extracted dbInit operation
|
|
210
358
|
return executeDbInit({
|
|
211
359
|
driver,
|
|
212
360
|
familyInstance,
|
|
213
|
-
|
|
361
|
+
contract,
|
|
362
|
+
mode: options.mode,
|
|
363
|
+
migrations: this.options.target.migrations,
|
|
364
|
+
frameworkComponents,
|
|
365
|
+
...ifDefined('onProgress', onProgress),
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async dbUpdate(options: DbUpdateOptions): Promise<DbUpdateResult> {
|
|
370
|
+
const { onProgress } = options;
|
|
371
|
+
await this.connectWithProgress(options.connection, 'dbUpdate', onProgress);
|
|
372
|
+
const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
|
|
373
|
+
|
|
374
|
+
if (!hasMigrations(this.options.target)) {
|
|
375
|
+
throw new Error(`Target "${this.options.target.targetId}" does not support migrations`);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
let contract: Contract;
|
|
379
|
+
try {
|
|
380
|
+
contract = familyInstance.validateContract(options.contract);
|
|
381
|
+
} catch (error) {
|
|
382
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
383
|
+
throw new ContractValidationError(message, error);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return executeDbUpdate({
|
|
387
|
+
driver,
|
|
388
|
+
familyInstance,
|
|
389
|
+
contract,
|
|
214
390
|
mode: options.mode,
|
|
215
391
|
migrations: this.options.target.migrations,
|
|
216
392
|
frameworkComponents,
|
|
393
|
+
...ifDefined('acceptDataLoss', options.acceptDataLoss),
|
|
394
|
+
...ifDefined('onProgress', onProgress),
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
async readMarker(): Promise<ContractMarkerRecord | null> {
|
|
399
|
+
const { driver, familyInstance } = await this.ensureConnected();
|
|
400
|
+
return familyInstance.readMarker({ driver });
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
async migrationApply(options: MigrationApplyOptions): Promise<MigrationApplyResult> {
|
|
404
|
+
const { onProgress } = options;
|
|
405
|
+
await this.connectWithProgress(options.connection, 'migrationApply', onProgress);
|
|
406
|
+
const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
|
|
407
|
+
|
|
408
|
+
if (!hasMigrations(this.options.target)) {
|
|
409
|
+
throw new Error(`Target "${this.options.target.targetId}" does not support migrations`);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return executeMigrationApply({
|
|
413
|
+
driver,
|
|
414
|
+
familyInstance,
|
|
415
|
+
originHash: options.originHash,
|
|
416
|
+
destinationHash: options.destinationHash,
|
|
417
|
+
pendingMigrations: options.pendingMigrations,
|
|
418
|
+
migrations: this.options.target.migrations,
|
|
419
|
+
frameworkComponents,
|
|
420
|
+
targetId: this.options.target.targetId,
|
|
421
|
+
...(onProgress ? { onProgress } : {}),
|
|
217
422
|
});
|
|
218
423
|
}
|
|
219
424
|
|
|
220
425
|
async introspect(options?: IntrospectOptions): Promise<unknown> {
|
|
426
|
+
const onProgress = options?.onProgress;
|
|
427
|
+
await this.connectWithProgress(options?.connection, 'introspect', onProgress);
|
|
221
428
|
const { driver, familyInstance } = await this.ensureConnected();
|
|
222
429
|
|
|
223
430
|
// TODO: Pass schema option to familyInstance.introspect when schema filtering is implemented
|
|
224
431
|
const _schema = options?.schema;
|
|
225
432
|
void _schema;
|
|
226
433
|
|
|
227
|
-
|
|
434
|
+
// Emit introspect span
|
|
435
|
+
onProgress?.({
|
|
436
|
+
action: 'introspect',
|
|
437
|
+
kind: 'spanStart',
|
|
438
|
+
spanId: 'introspect',
|
|
439
|
+
label: 'Introspecting database schema...',
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
try {
|
|
443
|
+
const result = await familyInstance.introspect({ driver });
|
|
444
|
+
|
|
445
|
+
onProgress?.({
|
|
446
|
+
action: 'introspect',
|
|
447
|
+
kind: 'spanEnd',
|
|
448
|
+
spanId: 'introspect',
|
|
449
|
+
outcome: 'ok',
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
return result;
|
|
453
|
+
} catch (error) {
|
|
454
|
+
onProgress?.({
|
|
455
|
+
action: 'introspect',
|
|
456
|
+
kind: 'spanEnd',
|
|
457
|
+
spanId: 'introspect',
|
|
458
|
+
outcome: 'error',
|
|
459
|
+
});
|
|
460
|
+
throw error;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
toSchemaView(schemaIR: unknown): CoreSchemaView | undefined {
|
|
465
|
+
this.init();
|
|
466
|
+
if (this.familyInstance && hasSchemaView(this.familyInstance)) {
|
|
467
|
+
return this.familyInstance.toSchemaView(schemaIR);
|
|
468
|
+
}
|
|
469
|
+
return undefined;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
async emit(options: EmitOptions): Promise<EmitResult> {
|
|
473
|
+
const { onProgress, contractConfig } = options;
|
|
474
|
+
|
|
475
|
+
// Ensure initialized (creates stack and family instance)
|
|
476
|
+
// emit() does NOT require a database connection
|
|
477
|
+
this.init();
|
|
478
|
+
|
|
479
|
+
if (!this.familyInstance) {
|
|
480
|
+
throw new Error('Family instance was not initialized. This is a bug.');
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
let contractRaw: unknown;
|
|
484
|
+
onProgress?.({
|
|
485
|
+
action: 'emit',
|
|
486
|
+
kind: 'spanStart',
|
|
487
|
+
spanId: 'resolveSource',
|
|
488
|
+
label: 'Resolving contract source...',
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
try {
|
|
492
|
+
const sourceContext = {
|
|
493
|
+
composedExtensionPacks: (this.options.extensionPacks ?? []).map((p) => p.id),
|
|
494
|
+
};
|
|
495
|
+
const providerResult = await contractConfig.sourceProvider(sourceContext);
|
|
496
|
+
if (!providerResult.ok) {
|
|
497
|
+
onProgress?.({
|
|
498
|
+
action: 'emit',
|
|
499
|
+
kind: 'spanEnd',
|
|
500
|
+
spanId: 'resolveSource',
|
|
501
|
+
outcome: 'error',
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
return notOk({
|
|
505
|
+
code: 'CONTRACT_SOURCE_INVALID',
|
|
506
|
+
summary: providerResult.failure.summary,
|
|
507
|
+
why: providerResult.failure.summary,
|
|
508
|
+
meta: providerResult.failure.meta,
|
|
509
|
+
diagnostics: providerResult.failure,
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
contractRaw = providerResult.value;
|
|
513
|
+
|
|
514
|
+
onProgress?.({
|
|
515
|
+
action: 'emit',
|
|
516
|
+
kind: 'spanEnd',
|
|
517
|
+
spanId: 'resolveSource',
|
|
518
|
+
outcome: 'ok',
|
|
519
|
+
});
|
|
520
|
+
} catch (error) {
|
|
521
|
+
onProgress?.({
|
|
522
|
+
action: 'emit',
|
|
523
|
+
kind: 'spanEnd',
|
|
524
|
+
spanId: 'resolveSource',
|
|
525
|
+
outcome: 'error',
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
529
|
+
return notOk({
|
|
530
|
+
code: 'CONTRACT_SOURCE_INVALID',
|
|
531
|
+
summary: 'Failed to resolve contract source',
|
|
532
|
+
why: message,
|
|
533
|
+
diagnostics: {
|
|
534
|
+
summary: 'Contract source provider threw an exception',
|
|
535
|
+
diagnostics: [
|
|
536
|
+
{
|
|
537
|
+
code: 'PROVIDER_THROW',
|
|
538
|
+
message,
|
|
539
|
+
},
|
|
540
|
+
],
|
|
541
|
+
},
|
|
542
|
+
meta: undefined,
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Emit contract
|
|
547
|
+
onProgress?.({
|
|
548
|
+
action: 'emit',
|
|
549
|
+
kind: 'spanStart',
|
|
550
|
+
spanId: 'emit',
|
|
551
|
+
label: 'Emitting contract...',
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
try {
|
|
555
|
+
const enrichedIR = enrichContract(contractRaw as Contract, this.frameworkComponents ?? []);
|
|
556
|
+
|
|
557
|
+
try {
|
|
558
|
+
this.familyInstance.validateContract(enrichedIR);
|
|
559
|
+
} catch (error) {
|
|
560
|
+
onProgress?.({
|
|
561
|
+
action: 'emit',
|
|
562
|
+
kind: 'spanEnd',
|
|
563
|
+
spanId: 'emit',
|
|
564
|
+
outcome: 'error',
|
|
565
|
+
});
|
|
566
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
567
|
+
return notOk({
|
|
568
|
+
code: 'CONTRACT_VALIDATION_FAILED',
|
|
569
|
+
summary: 'Contract validation failed',
|
|
570
|
+
why: message,
|
|
571
|
+
meta: undefined,
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const result = await emitContractArtifacts(
|
|
576
|
+
enrichedIR,
|
|
577
|
+
this.stack!,
|
|
578
|
+
this.options.family.emission,
|
|
579
|
+
);
|
|
580
|
+
|
|
581
|
+
onProgress?.({
|
|
582
|
+
action: 'emit',
|
|
583
|
+
kind: 'spanEnd',
|
|
584
|
+
spanId: 'emit',
|
|
585
|
+
outcome: 'ok',
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
return ok({
|
|
589
|
+
storageHash: result.storageHash,
|
|
590
|
+
...ifDefined('executionHash', result.executionHash),
|
|
591
|
+
profileHash: result.profileHash,
|
|
592
|
+
contractJson: result.contractJson,
|
|
593
|
+
contractDts: result.contractDts,
|
|
594
|
+
});
|
|
595
|
+
} catch (error) {
|
|
596
|
+
onProgress?.({
|
|
597
|
+
action: 'emit',
|
|
598
|
+
kind: 'spanEnd',
|
|
599
|
+
spanId: 'emit',
|
|
600
|
+
outcome: 'error',
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
return notOk({
|
|
604
|
+
code: 'EMIT_FAILED',
|
|
605
|
+
summary: 'Failed to emit contract',
|
|
606
|
+
why: error instanceof Error ? error.message : String(error),
|
|
607
|
+
meta: undefined,
|
|
608
|
+
});
|
|
609
|
+
}
|
|
228
610
|
}
|
|
229
611
|
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { Contract } from '@prisma-next/contract/types';
|
|
2
|
+
import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
|
|
3
|
+
|
|
4
|
+
type CapabilityMatrix = Record<string, Record<string, boolean>>;
|
|
5
|
+
|
|
6
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
7
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function sortDeep(value: unknown): unknown {
|
|
11
|
+
if (Array.isArray(value)) {
|
|
12
|
+
return value.map(sortDeep);
|
|
13
|
+
}
|
|
14
|
+
if (!isPlainObject(value)) {
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
const entries = Object.entries(value).sort(([a], [b]) => a.localeCompare(b));
|
|
18
|
+
const next: Record<string, unknown> = {};
|
|
19
|
+
for (const [key, child] of entries) {
|
|
20
|
+
next[key] = sortDeep(child);
|
|
21
|
+
}
|
|
22
|
+
return next;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function sortDeepTyped<T>(value: T): T {
|
|
26
|
+
return sortDeep(value) as T;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function extractCapabilityMatrix(value: unknown): CapabilityMatrix {
|
|
30
|
+
if (!isPlainObject(value)) return {};
|
|
31
|
+
|
|
32
|
+
const out: CapabilityMatrix = {};
|
|
33
|
+
for (const [namespace, maybeCaps] of Object.entries(value)) {
|
|
34
|
+
if (!isPlainObject(maybeCaps)) continue;
|
|
35
|
+
const caps: Record<string, boolean> = {};
|
|
36
|
+
for (const [key, flag] of Object.entries(maybeCaps)) {
|
|
37
|
+
if (typeof flag === 'boolean') {
|
|
38
|
+
caps[key] = flag;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (Object.keys(caps).length > 0) {
|
|
42
|
+
out[namespace] = caps;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function mergeCapabilities(left: CapabilityMatrix, right: CapabilityMatrix): CapabilityMatrix {
|
|
50
|
+
const next: CapabilityMatrix = { ...left };
|
|
51
|
+
for (const [namespace, capabilities] of Object.entries(right)) {
|
|
52
|
+
next[namespace] = {
|
|
53
|
+
...(left[namespace] ?? {}),
|
|
54
|
+
...capabilities,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return next;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function extractExtensionPackMeta(
|
|
61
|
+
component: TargetBoundComponentDescriptor<string, string>,
|
|
62
|
+
): Record<string, unknown> {
|
|
63
|
+
const { kind, id, version, capabilities, types } = component;
|
|
64
|
+
const base: Record<string, unknown> = {
|
|
65
|
+
kind,
|
|
66
|
+
id,
|
|
67
|
+
familyId: component.familyId,
|
|
68
|
+
targetId: component.targetId,
|
|
69
|
+
version,
|
|
70
|
+
};
|
|
71
|
+
if (capabilities) {
|
|
72
|
+
base['capabilities'] = capabilities;
|
|
73
|
+
}
|
|
74
|
+
if (types) {
|
|
75
|
+
if (types.codecTypes) {
|
|
76
|
+
const { controlPlaneHooks: _, codecInstances: _ci, ...cleanedCodecTypes } = types.codecTypes;
|
|
77
|
+
base['types'] = { ...types, codecTypes: cleanedCodecTypes };
|
|
78
|
+
} else {
|
|
79
|
+
base['types'] = types;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return base;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Enriches a raw contract with framework-derived metadata:
|
|
87
|
+
* capabilities from all component descriptors and extension pack metadata
|
|
88
|
+
* from extension descriptors. Produces deterministically sorted output.
|
|
89
|
+
*/
|
|
90
|
+
export function enrichContract(
|
|
91
|
+
ir: Contract,
|
|
92
|
+
components: ReadonlyArray<TargetBoundComponentDescriptor<string, string>>,
|
|
93
|
+
): Contract {
|
|
94
|
+
let mergedCapabilities = ir.capabilities;
|
|
95
|
+
const extensionPacksMeta: Record<string, unknown> = {};
|
|
96
|
+
|
|
97
|
+
for (const component of components) {
|
|
98
|
+
if (component.capabilities) {
|
|
99
|
+
mergedCapabilities = mergeCapabilities(
|
|
100
|
+
mergedCapabilities,
|
|
101
|
+
extractCapabilityMatrix(component.capabilities),
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
if (component.kind === 'extension') {
|
|
105
|
+
extensionPacksMeta[component.id] = extractExtensionPackMeta(component);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const extensionPacks =
|
|
110
|
+
Object.keys(extensionPacksMeta).length > 0
|
|
111
|
+
? { ...ir.extensionPacks, ...extensionPacksMeta }
|
|
112
|
+
: ir.extensionPacks;
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
...ir,
|
|
116
|
+
capabilities: sortDeepTyped(mergedCapabilities),
|
|
117
|
+
extensionPacks: sortDeepTyped(extensionPacks),
|
|
118
|
+
};
|
|
119
|
+
}
|