@prisma-next/family-sql 0.4.0-dev.9 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/control-adapter.d.mts +16 -2
  2. package/dist/control-adapter.d.mts.map +1 -1
  3. package/dist/control.d.mts +2 -2
  4. package/dist/control.d.mts.map +1 -1
  5. package/dist/control.mjs +23 -11
  6. package/dist/control.mjs.map +1 -1
  7. package/dist/migration.d.mts +36 -0
  8. package/dist/migration.d.mts.map +1 -0
  9. package/dist/migration.mjs +35 -0
  10. package/dist/migration.mjs.map +1 -0
  11. package/dist/schema-verify.d.mts +1 -1
  12. package/dist/{types-CH9zsNrU.d.mts → types-C6K4mxDM.d.mts} +33 -3
  13. package/dist/types-C6K4mxDM.d.mts.map +1 -0
  14. package/dist/{verify-DZHtfcmj.mjs → verify-4GshvY4p.mjs} +25 -11
  15. package/dist/verify-4GshvY4p.mjs.map +1 -0
  16. package/dist/verify-sql-schema-Ovz7RXR5.mjs.map +1 -1
  17. package/dist/verify.d.mts.map +1 -1
  18. package/dist/verify.mjs +1 -1
  19. package/package.json +21 -20
  20. package/src/core/control-adapter.ts +21 -1
  21. package/src/core/control-instance.ts +2 -2
  22. package/src/core/migrations/contract-to-schema-ir.ts +45 -5
  23. package/src/core/migrations/types.ts +35 -0
  24. package/src/core/sql-migration.ts +34 -0
  25. package/src/core/verify.ts +50 -32
  26. package/src/exports/control.ts +2 -0
  27. package/src/exports/migration.ts +1 -0
  28. package/dist/operation-descriptors.d.mts +0 -380
  29. package/dist/operation-descriptors.d.mts.map +0 -1
  30. package/dist/operation-descriptors.mjs +0 -294
  31. package/dist/operation-descriptors.mjs.map +0 -1
  32. package/dist/types-CH9zsNrU.d.mts.map +0 -1
  33. package/dist/verify-DZHtfcmj.mjs.map +0 -1
  34. package/src/core/migrations/descriptor-schemas.ts +0 -172
  35. package/src/core/migrations/operation-descriptors.ts +0 -213
  36. package/src/exports/operation-descriptors.ts +0 -52
package/package.json CHANGED
@@ -1,35 +1,36 @@
1
1
  {
2
2
  "name": "@prisma-next/family-sql",
3
- "version": "0.4.0-dev.9",
3
+ "version": "0.4.2",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "SQL family descriptor for Prisma Next",
7
7
  "dependencies": {
8
8
  "arktype": "^2.0.0",
9
- "@prisma-next/emitter": "0.4.0-dev.9",
10
- "@prisma-next/framework-components": "0.4.0-dev.9",
11
- "@prisma-next/contract": "0.4.0-dev.9",
12
- "@prisma-next/operations": "0.4.0-dev.9",
13
- "@prisma-next/runtime-executor": "0.4.0-dev.9",
14
- "@prisma-next/sql-contract": "0.4.0-dev.9",
15
- "@prisma-next/sql-contract-ts": "0.4.0-dev.9",
16
- "@prisma-next/sql-contract-emitter": "0.4.0-dev.9",
17
- "@prisma-next/sql-relational-core": "0.4.0-dev.9",
18
- "@prisma-next/sql-runtime": "0.4.0-dev.9",
19
- "@prisma-next/sql-schema-ir": "0.4.0-dev.9",
20
- "@prisma-next/utils": "0.4.0-dev.9",
21
- "@prisma-next/sql-operations": "0.4.0-dev.9"
9
+ "@prisma-next/contract": "0.4.2",
10
+ "@prisma-next/migration-tools": "0.4.2",
11
+ "@prisma-next/emitter": "0.4.2",
12
+ "@prisma-next/operations": "0.4.2",
13
+ "@prisma-next/sql-contract-emitter": "0.4.2",
14
+ "@prisma-next/sql-contract": "0.4.2",
15
+ "@prisma-next/framework-components": "0.4.2",
16
+ "@prisma-next/sql-contract-ts": "0.4.2",
17
+ "@prisma-next/runtime-executor": "0.4.2",
18
+ "@prisma-next/sql-operations": "0.4.2",
19
+ "@prisma-next/sql-relational-core": "0.4.2",
20
+ "@prisma-next/sql-runtime": "0.4.2",
21
+ "@prisma-next/utils": "0.4.2",
22
+ "@prisma-next/sql-schema-ir": "0.4.2"
22
23
  },
23
24
  "devDependencies": {
24
25
  "tsdown": "0.18.4",
25
26
  "typescript": "5.9.3",
26
27
  "vitest": "4.0.17",
27
- "@prisma-next/driver-postgres": "0.4.0-dev.9",
28
- "@prisma-next/psl-parser": "0.4.0-dev.9",
29
- "@prisma-next/sql-contract-psl": "0.4.0-dev.9",
30
- "@prisma-next/test-utils": "0.0.1",
28
+ "@prisma-next/psl-parser": "0.4.2",
29
+ "@prisma-next/driver-postgres": "0.4.2",
30
+ "@prisma-next/sql-contract-psl": "0.4.2",
31
31
  "@prisma-next/tsconfig": "0.0.0",
32
- "@prisma-next/tsdown": "0.0.0"
32
+ "@prisma-next/tsdown": "0.0.0",
33
+ "@prisma-next/test-utils": "0.0.1"
33
34
  },
34
35
  "files": [
35
36
  "dist",
@@ -38,7 +39,7 @@
38
39
  "exports": {
39
40
  "./control": "./dist/control.mjs",
40
41
  "./control-adapter": "./dist/control-adapter.mjs",
41
- "./operation-descriptors": "./dist/operation-descriptors.mjs",
42
+ "./migration": "./dist/migration.mjs",
42
43
  "./pack": "./dist/pack.mjs",
43
44
  "./runtime": "./dist/runtime.mjs",
44
45
  "./schema-verify": "./dist/schema-verify.mjs",
@@ -1,7 +1,13 @@
1
1
  import type {
2
2
  ControlAdapterInstance,
3
3
  ControlDriverInstance,
4
+ ControlStack,
4
5
  } from '@prisma-next/framework-components/control';
6
+ import type {
7
+ AnyQueryAst,
8
+ LoweredStatement,
9
+ LowererContext,
10
+ } from '@prisma-next/sql-relational-core/ast';
5
11
  import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types';
6
12
  import type { DefaultNormalizer, NativeTypeNormalizer } from './schema-verify/verify-sql-schema';
7
13
 
@@ -44,6 +50,17 @@ export interface SqlControlAdapter<TTarget extends string = string>
44
50
  * before comparison with contract native types during schema verification.
45
51
  */
46
52
  readonly normalizeNativeType?: NativeTypeNormalizer;
53
+
54
+ /**
55
+ * Lower a SQL query AST into a target-flavored `{ sql, params }` payload.
56
+ *
57
+ * Migration tooling (e.g. the `dataTransform` operation) needs to materialize
58
+ * SQL at emit/plan time without instantiating the runtime adapter. The control
59
+ * adapter's `lower` is byte-equivalent to the runtime adapter's `lower` for the
60
+ * same AST and contract, ensuring planned SQL matches what the runtime would
61
+ * emit.
62
+ */
63
+ lower(ast: AnyQueryAst, context: LowererContext<unknown>): LoweredStatement;
47
64
  }
48
65
 
49
66
  /**
@@ -55,6 +72,9 @@ export interface SqlControlAdapter<TTarget extends string = string>
55
72
  export interface SqlControlAdapterDescriptor<TTarget extends string = string> {
56
73
  /**
57
74
  * Creates a SQL control adapter instance for control-plane operations.
75
+ *
76
+ * Receives the assembled `ControlStack` so adapters can read aggregated
77
+ * metadata (codec lookup, extension contributions) when materializing.
58
78
  */
59
- create(): SqlControlAdapter<TTarget>;
79
+ create(stack: ControlStack<'sql', TTarget>): SqlControlAdapter<TTarget>;
60
80
  }
@@ -435,7 +435,7 @@ export function createSqlFamilyInstance<TTargetId extends string>(
435
435
 
436
436
  const contract = sqlValidateContract<Contract<SqlStorage>>(contractInput, emptyCodecLookup);
437
437
 
438
- const controlAdapter = adapter.create();
438
+ const controlAdapter = adapter.create(stack);
439
439
  if (!isSqlControlAdapter(controlAdapter)) {
440
440
  throw new Error('Adapter does not implement SqlControlAdapter.introspect()');
441
441
  }
@@ -559,7 +559,7 @@ export function createSqlFamilyInstance<TTargetId extends string>(
559
559
  }): Promise<SqlSchemaIR> {
560
560
  const { driver, contract } = options;
561
561
 
562
- const controlAdapter = adapter.create();
562
+ const controlAdapter = adapter.create(stack);
563
563
  if (!isSqlControlAdapter(controlAdapter)) {
564
564
  throw new Error('Adapter does not implement SqlControlAdapter.introspect()');
565
565
  }
@@ -7,6 +7,7 @@ import type {
7
7
  SqlStorage,
8
8
  StorageColumn,
9
9
  StorageTable,
10
+ StorageTypeInstance,
10
11
  UniqueConstraint,
11
12
  } from '@prisma-next/sql-contract/types';
12
13
  import { defaultIndexName } from '@prisma-next/sql-schema-ir/naming';
@@ -53,16 +54,26 @@ export type DefaultRenderer = (def: ColumnDefault, column: StorageColumn) => str
53
54
  function convertColumn(
54
55
  name: string,
55
56
  column: StorageColumn,
57
+ storageTypes: Record<string, StorageTypeInstance>,
56
58
  expandNativeType: NativeTypeExpander | undefined,
57
59
  renderDefault: DefaultRenderer | undefined,
58
60
  ): SqlColumnIR {
61
+ // Resolve `typeRef` so columns that delegate their `nativeType`/`codecId`/
62
+ // `typeParams` to a named `storage.types` entry expand the same way as
63
+ // columns that inline those fields. Without this resolution, a
64
+ // `typeRef`-based column like `post.embedding → Embedding1536` would
65
+ // render as the bare `"vector"` (dropping the `length` parameter), while
66
+ // `verify-sql-schema.ts`'s `renderExpectedNativeType` resolves the
67
+ // typeRef and produces `"vector(1536)"` — making diffs on the same
68
+ // contract falsely report a `type_mismatch`.
69
+ const resolved = resolveColumnTypeMetadata(column, storageTypes);
59
70
  const nativeType = expandNativeType
60
71
  ? expandNativeType({
61
- nativeType: column.nativeType,
62
- codecId: column.codecId,
63
- ...ifDefined('typeParams', column.typeParams),
72
+ nativeType: resolved.nativeType,
73
+ codecId: resolved.codecId,
74
+ ...ifDefined('typeParams', resolved.typeParams),
64
75
  })
65
- : column.nativeType;
76
+ : resolved.nativeType;
66
77
  return {
67
78
  name,
68
79
  nativeType,
@@ -74,6 +85,26 @@ function convertColumn(
74
85
  };
75
86
  }
76
87
 
88
+ function resolveColumnTypeMetadata(
89
+ column: StorageColumn,
90
+ storageTypes: Record<string, StorageTypeInstance>,
91
+ ): Pick<StorageColumn, 'codecId' | 'nativeType' | 'typeParams'> {
92
+ if (!column.typeRef) {
93
+ return column;
94
+ }
95
+ const referenced = storageTypes[column.typeRef];
96
+ if (!referenced) {
97
+ throw new Error(
98
+ `Column references storage type "${column.typeRef}" but it is not defined in storage.types.`,
99
+ );
100
+ }
101
+ return {
102
+ codecId: referenced.codecId,
103
+ nativeType: referenced.nativeType,
104
+ typeParams: referenced.typeParams,
105
+ };
106
+ }
107
+
77
108
  function convertUnique(unique: UniqueConstraint): SqlUniqueIR {
78
109
  return {
79
110
  columns: unique.columns,
@@ -101,12 +132,19 @@ function convertForeignKey(fk: ForeignKey): SqlForeignKeyIR {
101
132
  function convertTable(
102
133
  name: string,
103
134
  table: StorageTable,
135
+ storageTypes: Record<string, StorageTypeInstance>,
104
136
  expandNativeType: NativeTypeExpander | undefined,
105
137
  renderDefault: DefaultRenderer | undefined,
106
138
  ): SqlTableIR {
107
139
  const columns: Record<string, SqlColumnIR> = {};
108
140
  for (const [colName, colDef] of Object.entries(table.columns)) {
109
- columns[colName] = convertColumn(colName, colDef, expandNativeType, renderDefault);
141
+ columns[colName] = convertColumn(
142
+ colName,
143
+ colDef,
144
+ storageTypes,
145
+ expandNativeType,
146
+ renderDefault,
147
+ );
110
148
  }
111
149
 
112
150
  const satisfiedIndexColumns = new Set([
@@ -217,11 +255,13 @@ export function contractToSchemaIR(
217
255
  }
218
256
 
219
257
  const storage = contract.storage;
258
+ const storageTypes = storage.types ?? {};
220
259
  const tables: Record<string, SqlTableIR> = {};
221
260
  for (const [tableName, tableDef] of Object.entries(storage.tables)) {
222
261
  tables[tableName] = convertTable(
223
262
  tableName,
224
263
  tableDef,
264
+ storageTypes,
225
265
  options.expandNativeType,
226
266
  options.renderDefault,
227
267
  );
@@ -4,6 +4,7 @@ import type {
4
4
  ControlAdapterDescriptor,
5
5
  ControlDriverInstance,
6
6
  ControlExtensionDescriptor,
7
+ DataTransformOperation,
7
8
  MigratableTargetDescriptor,
8
9
  MigrationOperationPolicy,
9
10
  MigrationPlan,
@@ -141,6 +142,18 @@ export interface SqlMigrationPlanOperationStep {
141
142
  readonly meta?: AnyRecord;
142
143
  }
143
144
 
145
+ /**
146
+ * Minimal shape every SQL-family target must conform to for its per-operation
147
+ * `target.details` payload. Each SQL operation addresses a named database
148
+ * object in some schema; targets (Postgres, MySQL, SQLite, …) extend this
149
+ * shape with their own fields (e.g. Postgres adds `objectType` and optional
150
+ * `table`).
151
+ */
152
+ export interface SqlPlanTargetDetails {
153
+ readonly schema: string;
154
+ readonly name: string;
155
+ }
156
+
144
157
  export interface SqlMigrationPlanOperationTarget<TTargetDetails> {
145
158
  readonly id: string;
146
159
  readonly details?: TTargetDetails;
@@ -155,6 +168,19 @@ export interface SqlMigrationPlanOperation<TTargetDetails> extends MigrationPlan
155
168
  readonly meta?: AnyRecord;
156
169
  }
157
170
 
171
+ /**
172
+ * Union of all operation shapes a SQL-family migration may emit: schema-facing
173
+ * `SqlMigrationPlanOperation`s and family-agnostic `DataTransformOperation`s.
174
+ *
175
+ * Mirrors `AnyMongoMigrationOperation` in shape — the runner already handles
176
+ * both branches via `isDataTransformOperation`, and authored `migration.ts`
177
+ * files must be able to intermix `dataTransform(endContract, …)` calls with
178
+ * DDL factory calls (e.g. `setNotNull(…)`) in a single `operations` array.
179
+ */
180
+ export type AnySqlMigrationOperation<TTargetDetails> =
181
+ | SqlMigrationPlanOperation<TTargetDetails>
182
+ | DataTransformOperation;
183
+
158
184
  export interface SqlMigrationPlanContractInfo {
159
185
  readonly storageHash: string;
160
186
  readonly profileHash?: string;
@@ -216,6 +242,15 @@ export interface SqlMigrationPlannerPlanOptions {
216
242
  readonly schema: SqlSchemaIR;
217
243
  readonly policy: MigrationOperationPolicy;
218
244
  readonly schemaName?: string;
245
+ /**
246
+ * The "from" contract (state the planner assumes the database starts at).
247
+ * Only `migration plan` supplies this; `db update` / `db init` reconcile
248
+ * against the live schema with no old contract. Strategies that need
249
+ * from/to column-shape comparisons (unsafe type change, nullability
250
+ * tightening) use this to decide whether to emit `dataTransform`
251
+ * placeholders.
252
+ */
253
+ readonly fromContract?: Contract<SqlStorage> | null;
219
254
  /**
220
255
  * Active framework components participating in this composition.
221
256
  * SQL targets can interpret this list to derive database dependencies.
@@ -0,0 +1,34 @@
1
+ import { Migration } from '@prisma-next/migration-tools/migration';
2
+ import type { AnySqlMigrationOperation, SqlPlanTargetDetails } from './migrations/types';
3
+
4
+ /**
5
+ * Family-owned base class for SQL migrations.
6
+ *
7
+ * Parameterized on the target-details shape because SQL-family targets
8
+ * (Postgres, MySQL, SQLite, …) each carry their own `target.details` payload
9
+ * on `SqlMigrationPlanOperation`. The type parameter is narrowed to
10
+ * `SqlPlanTargetDetails` so every target-specific shape must at minimum
11
+ * identify the object being targeted (schema + name); concrete targets
12
+ * extend the shape with their own fields.
13
+ *
14
+ * Each concrete target-side subclass (e.g. Postgres's
15
+ * `TypeScriptRenderablePostgresMigration`) fixes `targetId` to its own
16
+ * target-id string literal, since SQL can't hardcode a single `targetId`:
17
+ * `targetId` is a target-level identity, not a family-level one, and
18
+ * belongs on the subclass.
19
+ *
20
+ * `familyId` is intentionally not declared here. The SQL family has no
21
+ * family-scoped runtime identity today — consumers reach the family via
22
+ * target descriptors rather than by family-id lookup, so adding one would
23
+ * be purely decorative. Introducing it later is a non-breaking superset.
24
+ *
25
+ * The operation type parameter is `AnySqlMigrationOperation<TDetails>` — the
26
+ * union of DDL-shaped `SqlMigrationPlanOperation` and `DataTransformOperation`
27
+ * — so subclasses can return a mix of schema operations (e.g. `setNotNull`,
28
+ * `addColumn`) and data-transform operations (e.g. `dataTransform`). Mirrors
29
+ * `MongoMigration`'s parameterization on `AnyMongoMigrationOperation`.
30
+ */
31
+ export abstract class SqlMigration<
32
+ TDetails extends SqlPlanTargetDetails,
33
+ TTargetId extends string = string,
34
+ > extends Migration<AnySqlMigrationOperation<TDetails>, 'sql', TTargetId> {}
@@ -97,6 +97,27 @@ export function readMarkerSql(): { readonly sql: string; readonly params: readon
97
97
  };
98
98
  }
99
99
 
100
+ /**
101
+ * Returns the SQL statement that probes for the existence of the marker table.
102
+ * Uses the SQL-standard `information_schema.tables` view so the query succeeds
103
+ * (returning zero rows) when the table has not been created yet — avoiding a
104
+ * `relation does not exist` error. Some Postgres wire-protocol implementations
105
+ * (e.g. PGlite's TCP proxy) do not fully recover from an extended-protocol
106
+ * parse error, so we probe first instead of relying on an error signal.
107
+ * @internal - Used internally by readMarker().
108
+ */
109
+ export function markerTableExistsSql(): {
110
+ readonly sql: string;
111
+ readonly params: readonly unknown[];
112
+ } {
113
+ return {
114
+ sql: `select 1
115
+ from information_schema.tables
116
+ where table_schema = $1 and table_name = $2`,
117
+ params: ['prisma_contract', 'marker'],
118
+ };
119
+ }
120
+
100
121
  /**
101
122
  * Reads the contract marker from the database using the provided driver.
102
123
  * Returns the parsed marker record or null if no marker is found.
@@ -108,41 +129,38 @@ export function readMarkerSql(): { readonly sql: string; readonly params: readon
108
129
  export async function readMarker(
109
130
  driver: ControlDriverInstance<'sql', string>,
110
131
  ): Promise<ContractMarkerRecord | null> {
111
- const markerStatement = readMarkerSql();
112
-
113
- try {
114
- const queryResult = await driver.query<{
115
- core_hash: string;
116
- profile_hash: string;
117
- contract_json: unknown | null;
118
- canonical_version: number | null;
119
- updated_at: Date | string;
120
- app_tag: string | null;
121
- meta: unknown | null;
122
- }>(markerStatement.sql, markerStatement.params);
123
-
124
- if (queryResult.rows.length === 0) {
125
- return null;
126
- }
132
+ // Probe for the marker table first so that a fresh database (no
133
+ // `prisma_contract` schema) returns null cleanly instead of surfacing a
134
+ // `relation does not exist` error. This keeps the control connection in a
135
+ // predictable state for driver implementations that are sensitive to
136
+ // extended-protocol parse errors.
137
+ const existsStatement = markerTableExistsSql();
138
+ const existsResult = await driver.query(existsStatement.sql, existsStatement.params);
139
+ if (existsResult.rows.length === 0) {
140
+ return null;
141
+ }
127
142
 
128
- const markerRow = queryResult.rows[0];
129
- if (!markerRow) {
130
- // If rows array has length > 0 but first element is undefined, this is an unexpected result structure
131
- throw new Error('Database query returned unexpected result structure');
132
- }
143
+ const markerStatement = readMarkerSql();
144
+ const queryResult = await driver.query<{
145
+ core_hash: string;
146
+ profile_hash: string;
147
+ contract_json: unknown | null;
148
+ canonical_version: number | null;
149
+ updated_at: Date | string;
150
+ app_tag: string | null;
151
+ meta: unknown | null;
152
+ }>(markerStatement.sql, markerStatement.params);
153
+
154
+ if (queryResult.rows.length === 0) {
155
+ return null;
156
+ }
133
157
 
134
- return parseContractMarkerRow(markerRow);
135
- } catch (error) {
136
- // Handle case where marker table doesn't exist yet (empty database)
137
- // PostgreSQL error code 42P01 = undefined_table
138
- if (
139
- error instanceof Error &&
140
- (error.message.includes('does not exist') || (error as { code?: string }).code === '42P01')
141
- ) {
142
- return null;
143
- }
144
- throw error;
158
+ const markerRow = queryResult.rows[0];
159
+ if (!markerRow) {
160
+ throw new Error('Database query returned unexpected result structure');
145
161
  }
162
+
163
+ return parseContractMarkerRow(markerRow);
146
164
  }
147
165
 
148
166
  /**
@@ -33,6 +33,7 @@ export {
33
33
  } from '../core/migrations/plan-helpers';
34
34
  export { INIT_ADDITIVE_POLICY } from '../core/migrations/policies';
35
35
  export type {
36
+ AnySqlMigrationOperation,
36
37
  CodecControlHooks,
37
38
  ComponentDatabaseDependencies,
38
39
  ComponentDatabaseDependency,
@@ -62,6 +63,7 @@ export type {
62
63
  SqlPlannerFailureResult,
63
64
  SqlPlannerResult,
64
65
  SqlPlannerSuccessResult,
66
+ SqlPlanTargetDetails,
65
67
  StorageTypePlanResult,
66
68
  } from '../core/migrations/types';
67
69
  export { collectInitDependencies, isDatabaseDependencyProvider } from '../core/migrations/types';
@@ -0,0 +1 @@
1
+ export { SqlMigration as Migration } from '../core/sql-migration';