@metaobjectsdev/migrate-ts 0.5.0-rc.1
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 +189 -0
- package/README.md +73 -0
- package/dist/diff/index.d.ts +30 -0
- package/dist/diff/index.d.ts.map +1 -0
- package/dist/diff/index.js +226 -0
- package/dist/diff/index.js.map +1 -0
- package/dist/diff/rename-heuristic.d.ts +23 -0
- package/dist/diff/rename-heuristic.d.ts.map +1 -0
- package/dist/diff/rename-heuristic.js +236 -0
- package/dist/diff/rename-heuristic.js.map +1 -0
- package/dist/diff/status.d.ts +8 -0
- package/dist/diff/status.d.ts.map +1 -0
- package/dist/diff/status.js +53 -0
- package/dist/diff/status.js.map +1 -0
- package/dist/emit/index.d.ts +17 -0
- package/dist/emit/index.d.ts.map +1 -0
- package/dist/emit/index.js +18 -0
- package/dist/emit/index.js.map +1 -0
- package/dist/emit/postgres.d.ts +3 -0
- package/dist/emit/postgres.d.ts.map +1 -0
- package/dist/emit/postgres.js +181 -0
- package/dist/emit/postgres.js.map +1 -0
- package/dist/emit/sqlite.d.ts +3 -0
- package/dist/emit/sqlite.d.ts.map +1 -0
- package/dist/emit/sqlite.js +302 -0
- package/dist/emit/sqlite.js.map +1 -0
- package/dist/errors.d.ts +8 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +54 -0
- package/dist/errors.js.map +1 -0
- package/dist/expected-schema.d.ts +15 -0
- package/dist/expected-schema.d.ts.map +1 -0
- package/dist/expected-schema.js +243 -0
- package/dist/expected-schema.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/introspect/index.d.ts +6 -0
- package/dist/introspect/index.d.ts.map +1 -0
- package/dist/introspect/index.js +11 -0
- package/dist/introspect/index.js.map +1 -0
- package/dist/introspect/postgres.d.ts +57 -0
- package/dist/introspect/postgres.d.ts.map +1 -0
- package/dist/introspect/postgres.js +339 -0
- package/dist/introspect/postgres.js.map +1 -0
- package/dist/introspect/sqlite.d.ts +4 -0
- package/dist/introspect/sqlite.d.ts.map +1 -0
- package/dist/introspect/sqlite.js +192 -0
- package/dist/introspect/sqlite.js.map +1 -0
- package/dist/source-aware-diff.d.ts +20 -0
- package/dist/source-aware-diff.d.ts.map +1 -0
- package/dist/source-aware-diff.js +24 -0
- package/dist/source-aware-diff.js.map +1 -0
- package/dist/sql-type.d.ts +45 -0
- package/dist/sql-type.d.ts.map +1 -0
- package/dist/sql-type.js +76 -0
- package/dist/sql-type.js.map +1 -0
- package/dist/types.d.ts +223 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/view-ddl-postgres.d.ts +4 -0
- package/dist/view-ddl-postgres.d.ts.map +1 -0
- package/dist/view-ddl-postgres.js +13 -0
- package/dist/view-ddl-postgres.js.map +1 -0
- package/dist/view-ddl-sqlite.d.ts +3 -0
- package/dist/view-ddl-sqlite.d.ts.map +1 -0
- package/dist/view-ddl-sqlite.js +7 -0
- package/dist/view-ddl-sqlite.js.map +1 -0
- package/dist/view-diff.d.ts +13 -0
- package/dist/view-diff.d.ts.map +1 -0
- package/dist/view-diff.js +42 -0
- package/dist/view-diff.js.map +1 -0
- package/dist/write-migration.d.ts +19 -0
- package/dist/write-migration.d.ts.map +1 -0
- package/dist/write-migration.js +34 -0
- package/dist/write-migration.js.map +1 -0
- package/package.json +50 -0
- package/src/diff/index.ts +294 -0
- package/src/diff/rename-heuristic.ts +265 -0
- package/src/diff/status.ts +55 -0
- package/src/emit/index.ts +38 -0
- package/src/emit/postgres.ts +189 -0
- package/src/emit/sqlite.ts +322 -0
- package/src/errors.ts +58 -0
- package/src/expected-schema.ts +326 -0
- package/src/index.ts +49 -0
- package/src/introspect/index.ts +14 -0
- package/src/introspect/postgres.ts +428 -0
- package/src/introspect/sqlite.ts +216 -0
- package/src/source-aware-diff.ts +49 -0
- package/src/sql-type.ts +91 -0
- package/src/types.ts +174 -0
- package/src/view-ddl-postgres.ts +15 -0
- package/src/view-ddl-sqlite.ts +7 -0
- package/src/view-diff.ts +55 -0
- package/src/write-migration.ts +64 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import type { MetaData, MetaObject, MetaReferenceIdentity, MetaRoot } from "@metaobjectsdev/metadata";
|
|
2
|
+
import {
|
|
3
|
+
TYPE_OBJECT,
|
|
4
|
+
TYPE_SOURCE,
|
|
5
|
+
TYPE_VALIDATOR,
|
|
6
|
+
SOURCE_SUBTYPE_DB_VIEW,
|
|
7
|
+
VALIDATOR_SUBTYPE_REQUIRED,
|
|
8
|
+
IDENTITY_ATTR_FIELDS,
|
|
9
|
+
IDENTITY_ATTR_GENERATION,
|
|
10
|
+
IDENTITY_ATTR_UNIQUE,
|
|
11
|
+
FIELD_ATTR_REQUIRED,
|
|
12
|
+
FIELD_ATTR_DEFAULT,
|
|
13
|
+
FIELD_ATTR_UNIQUE,
|
|
14
|
+
FIELD_SUBTYPE_STRING,
|
|
15
|
+
FIELD_SUBTYPE_INT,
|
|
16
|
+
FIELD_SUBTYPE_LONG,
|
|
17
|
+
FIELD_SUBTYPE_SHORT,
|
|
18
|
+
FIELD_SUBTYPE_BYTE,
|
|
19
|
+
FIELD_SUBTYPE_DOUBLE,
|
|
20
|
+
FIELD_SUBTYPE_FLOAT,
|
|
21
|
+
FIELD_SUBTYPE_DECIMAL,
|
|
22
|
+
FIELD_SUBTYPE_BOOLEAN,
|
|
23
|
+
FIELD_SUBTYPE_CURRENCY,
|
|
24
|
+
FIELD_SUBTYPE_DATE,
|
|
25
|
+
FIELD_SUBTYPE_TIME,
|
|
26
|
+
FIELD_SUBTYPE_TIMESTAMP,
|
|
27
|
+
FIELD_SUBTYPE_OBJECT,
|
|
28
|
+
FIELD_SUBTYPE_CLASS,
|
|
29
|
+
resolveTableName, resolveColumnName, resolveTableSchema,
|
|
30
|
+
} from "@metaobjectsdev/metadata";
|
|
31
|
+
import type { SqlType } from "./sql-type.js";
|
|
32
|
+
import type {
|
|
33
|
+
SchemaSnapshot, TableDescriptor, ColumnDescriptor, IndexDescriptor, FkDescriptor,
|
|
34
|
+
} from "./types.js";
|
|
35
|
+
|
|
36
|
+
export interface BuildExpectedSchemaOptions {
|
|
37
|
+
/**
|
|
38
|
+
* If set, normalize column SqlTypes for the target dialect so the diff
|
|
39
|
+
* matches what introspection will see. For sqlite this collapses
|
|
40
|
+
* boolean → integer{64} and timestamp/date/time → text, since sqlite
|
|
41
|
+
* has no native boolean/timestamp affinity and Drizzle's
|
|
42
|
+
* `integer(..., {mode:"boolean"})` / `text("ts")` patterns produce
|
|
43
|
+
* INTEGER / TEXT in the actual DB.
|
|
44
|
+
*/
|
|
45
|
+
dialect?: "sqlite" | "postgres";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function buildExpectedSchema(
|
|
49
|
+
root: MetaData,
|
|
50
|
+
opts?: BuildExpectedSchemaOptions,
|
|
51
|
+
): SchemaSnapshot {
|
|
52
|
+
// Pass 1: collect entities + their resolved table names.
|
|
53
|
+
// Skip:
|
|
54
|
+
// - abstract objects (e.g., BaseEntity)
|
|
55
|
+
// - value objects (no table backing)
|
|
56
|
+
// - projections (source.dbView — handled by the view-diff pipeline, not table diff)
|
|
57
|
+
const entities: { entity: MetaObject; tableName: string }[] = [];
|
|
58
|
+
for (const child of root.ownChildren()) {
|
|
59
|
+
if (child.type !== TYPE_OBJECT) continue;
|
|
60
|
+
if (child.isAbstract) continue;
|
|
61
|
+
if (child.subType === "value") continue;
|
|
62
|
+
const hasViewSource = child.ownChildren().some(
|
|
63
|
+
(c) => c.type === TYPE_SOURCE && c.subType === SOURCE_SUBTYPE_DB_VIEW,
|
|
64
|
+
);
|
|
65
|
+
if (hasViewSource) continue;
|
|
66
|
+
entities.push({ entity: child as MetaObject, tableName: resolveTableName(child) });
|
|
67
|
+
}
|
|
68
|
+
const entityToTable = new Map(entities.map((e) => [e.entity.name, e.tableName]));
|
|
69
|
+
const resolveTargetTable = (entityName: string) => entityToTable.get(entityName);
|
|
70
|
+
|
|
71
|
+
// Pass 2: build full descriptors with FK resolution.
|
|
72
|
+
// Schema is resolved here (not stored in Pass 1) to avoid exactOptionalPropertyTypes
|
|
73
|
+
// issues with `string | undefined` vs `schema?: string`.
|
|
74
|
+
const tables: TableDescriptor[] = entities.map(({ entity, tableName }) => {
|
|
75
|
+
const t = buildTable(entity, tableName, resolveTargetTable, root as MetaRoot);
|
|
76
|
+
const schema = resolveTableSchema(entity);
|
|
77
|
+
if (schema !== undefined) t.schema = schema;
|
|
78
|
+
return t;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Pass 3: dialect-specific SqlType normalization.
|
|
82
|
+
if (opts?.dialect === "sqlite") {
|
|
83
|
+
for (const table of tables) {
|
|
84
|
+
for (const col of table.columns) {
|
|
85
|
+
col.sqlType = normalizeForSqlite(col.sqlType);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Dialect validation: SQLite has no schema concept; reject any non-default @schema.
|
|
91
|
+
if (opts?.dialect === "sqlite") {
|
|
92
|
+
for (const table of tables) {
|
|
93
|
+
if (table.schema !== undefined) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
`sqlite does not support DB schemas; entity-table "${table.name}" declares @schema "${table.schema}"`,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return { tables, views: [] };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Normalize a canonical SqlType for what sqlite introspection will actually see.
|
|
106
|
+
* sqlite stores all integers (including booleans) as INTEGER, and uses TEXT for
|
|
107
|
+
* date/time/timestamp affinities by default.
|
|
108
|
+
*/
|
|
109
|
+
function normalizeForSqlite(sqlType: SqlType): SqlType {
|
|
110
|
+
switch (sqlType.kind) {
|
|
111
|
+
case "boolean":
|
|
112
|
+
return { kind: "integer", bits: 64 };
|
|
113
|
+
case "timestamp":
|
|
114
|
+
case "date":
|
|
115
|
+
return { kind: "text" };
|
|
116
|
+
case "integer":
|
|
117
|
+
// SQLite stores every INTEGER as a 64-bit value and Drizzle's int() emits
|
|
118
|
+
// plain "INTEGER" regardless of source bit-width. Collapse 32 → 64 so the
|
|
119
|
+
// expected snapshot matches what introspection sees.
|
|
120
|
+
return { kind: "integer", bits: 64 };
|
|
121
|
+
default:
|
|
122
|
+
return sqlType;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function buildTable(
|
|
127
|
+
entity: MetaObject,
|
|
128
|
+
tableName: string,
|
|
129
|
+
resolveTargetTable: (entityName: string) => string | undefined,
|
|
130
|
+
root: MetaRoot,
|
|
131
|
+
): TableDescriptor {
|
|
132
|
+
// Use effective accessors so inherited fields/identities (from `extends:` /
|
|
133
|
+
// abstract bases like BaseEntity) are included.
|
|
134
|
+
const pkIdentity = entity.primaryIdentity();
|
|
135
|
+
|
|
136
|
+
const pkJsNames = pkIdentity ? readIdentityFields(pkIdentity) : [];
|
|
137
|
+
const pkGeneration = pkIdentity
|
|
138
|
+
? (pkIdentity.ownAttr(IDENTITY_ATTR_GENERATION) as string | undefined)
|
|
139
|
+
: undefined;
|
|
140
|
+
|
|
141
|
+
const primaryKey = pkJsNames.map((jsName) => {
|
|
142
|
+
const field = findField(entity, jsName);
|
|
143
|
+
return field ? resolveColumnName(field) : toSnake(jsName);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const columns: ColumnDescriptor[] = [];
|
|
147
|
+
for (const field of entity.fields()) {
|
|
148
|
+
const isPk = pkJsNames.includes(field.name);
|
|
149
|
+
columns.push(buildColumn(field, isPk, isPk ? pkGeneration : undefined));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
name: tableName,
|
|
154
|
+
columns,
|
|
155
|
+
indexes: buildSecondaryIndexes(entity, tableName),
|
|
156
|
+
foreignKeys: buildForeignKeys(entity, tableName, resolveTargetTable, root),
|
|
157
|
+
primaryKey,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function buildSecondaryIndexes(entity: MetaObject, tableName: string): IndexDescriptor[] {
|
|
162
|
+
const indexes: IndexDescriptor[] = [];
|
|
163
|
+
|
|
164
|
+
// (a) Implicit unique indexes from @unique fields. Drizzle auto-creates these
|
|
165
|
+
// on the DB side using the convention `<table>_<column>_unique` whenever a
|
|
166
|
+
// column has `.unique()`. We mirror them in the expected schema so the diff
|
|
167
|
+
// doesn't see them as drop-only on the actual side.
|
|
168
|
+
for (const field of entity.fields()) {
|
|
169
|
+
if (field.ownAttr(FIELD_ATTR_UNIQUE) !== true) continue;
|
|
170
|
+
const colName = resolveColumnName(field);
|
|
171
|
+
indexes.push({
|
|
172
|
+
name: `${tableName}_${colName}_unique`,
|
|
173
|
+
columns: [colName],
|
|
174
|
+
unique: true,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// (b) Explicit secondary identities — unique-by-default, opt out with @unique: false.
|
|
179
|
+
// Drizzle emits the index using the identity's @name attr directly (no table
|
|
180
|
+
// prefix), so the expected name must match.
|
|
181
|
+
for (const identity of entity.secondaryIdentities()) {
|
|
182
|
+
const fieldNames = readIdentityFields(identity);
|
|
183
|
+
if (fieldNames.length === 0) continue;
|
|
184
|
+
const cols = fieldNames.map((jsName) => {
|
|
185
|
+
const field = findField(entity, jsName);
|
|
186
|
+
return field ? resolveColumnName(field) : toSnake(jsName);
|
|
187
|
+
});
|
|
188
|
+
const uniqueAttr = identity.ownAttr(IDENTITY_ATTR_UNIQUE);
|
|
189
|
+
indexes.push({
|
|
190
|
+
name: identity.name,
|
|
191
|
+
columns: cols,
|
|
192
|
+
unique: uniqueAttr !== false,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
return indexes;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function buildForeignKeys(
|
|
199
|
+
entity: MetaObject,
|
|
200
|
+
tableName: string,
|
|
201
|
+
resolveTargetTable: (entityName: string) => string | undefined,
|
|
202
|
+
root: MetaRoot,
|
|
203
|
+
): FkDescriptor[] {
|
|
204
|
+
const fks: FkDescriptor[] = [];
|
|
205
|
+
for (const refChild of entity.referenceIdentities()) {
|
|
206
|
+
// @enforce: false → logical-only reference; not a physical FK constraint.
|
|
207
|
+
if (!refChild.enforce) continue;
|
|
208
|
+
const targetEntity = refChild.targetEntity;
|
|
209
|
+
if (targetEntity === undefined) continue;
|
|
210
|
+
const refTable = resolveTargetTable(targetEntity);
|
|
211
|
+
if (!refTable) continue;
|
|
212
|
+
|
|
213
|
+
const fkFieldJsNames = readIdentityFields(refChild);
|
|
214
|
+
if (fkFieldJsNames.length === 0) continue;
|
|
215
|
+
|
|
216
|
+
const fkCols = fkFieldJsNames.map((jsName) => {
|
|
217
|
+
const fkField = findField(entity, jsName);
|
|
218
|
+
return fkField ? resolveColumnName(fkField) : toSnake(jsName);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// Target columns: prefer explicit multi-field dotted form, else delegate
|
|
222
|
+
// to MetaReferenceIdentity.resolvedTargetPkField (single field → target's
|
|
223
|
+
// primary identity → "id" fallback).
|
|
224
|
+
const explicitTargetFields = refChild.targetFields;
|
|
225
|
+
const refColumns = explicitTargetFields.length > 1
|
|
226
|
+
? explicitTargetFields.map(toSnake)
|
|
227
|
+
: [toSnake(refChild.resolvedTargetPkField(root) ?? "id")];
|
|
228
|
+
|
|
229
|
+
fks.push({
|
|
230
|
+
name: `${tableName}_${fkCols[0]}_fk`,
|
|
231
|
+
columns: fkCols,
|
|
232
|
+
refTable,
|
|
233
|
+
refColumns,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
return fks;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const EXPR_DEFAULT_PATTERNS = [
|
|
240
|
+
/^current_timestamp$/i,
|
|
241
|
+
/^now\(\)$/i,
|
|
242
|
+
/^current_date$/i,
|
|
243
|
+
/^current_time$/i,
|
|
244
|
+
/\(\)/, // anything function-like
|
|
245
|
+
];
|
|
246
|
+
|
|
247
|
+
function buildColumn(
|
|
248
|
+
field: MetaData,
|
|
249
|
+
isPk: boolean,
|
|
250
|
+
pkGeneration: string | undefined,
|
|
251
|
+
): ColumnDescriptor {
|
|
252
|
+
const requiredAttr = field.ownAttr(FIELD_ATTR_REQUIRED);
|
|
253
|
+
// BaseEntity-style metadata expresses required via a validator.required child;
|
|
254
|
+
// direct attr form is the alternative. Either signals NOT NULL.
|
|
255
|
+
const hasRequiredValidator = field.ownChildren().some(
|
|
256
|
+
(c) => c.type === TYPE_VALIDATOR && c.subType === VALIDATOR_SUBTYPE_REQUIRED,
|
|
257
|
+
);
|
|
258
|
+
const isRequired = requiredAttr === true || requiredAttr === "true" || hasRequiredValidator;
|
|
259
|
+
const defaultRaw = field.ownAttr(FIELD_ATTR_DEFAULT);
|
|
260
|
+
|
|
261
|
+
const col: ColumnDescriptor = {
|
|
262
|
+
name: resolveColumnName(field),
|
|
263
|
+
sqlType: subtypeToSqlType(field.subType),
|
|
264
|
+
nullable: !isPk && !isRequired,
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
if (typeof defaultRaw === "string" && defaultRaw.length > 0) {
|
|
268
|
+
const isExpr = EXPR_DEFAULT_PATTERNS.some((re) => re.test(defaultRaw));
|
|
269
|
+
col.default = { kind: isExpr ? "expr" : "literal", value: defaultRaw };
|
|
270
|
+
} else if (typeof defaultRaw === "boolean" || typeof defaultRaw === "number") {
|
|
271
|
+
col.default = { kind: "literal", value: String(defaultRaw) };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (isPk && (pkGeneration === "increment" || pkGeneration === "uuid")) {
|
|
275
|
+
col.identity = pkGeneration;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return col;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function subtypeToSqlType(subType: string): SqlType {
|
|
282
|
+
switch (subType) {
|
|
283
|
+
case FIELD_SUBTYPE_STRING: return { kind: "text" };
|
|
284
|
+
case FIELD_SUBTYPE_INT:
|
|
285
|
+
case FIELD_SUBTYPE_SHORT:
|
|
286
|
+
case FIELD_SUBTYPE_BYTE: return { kind: "integer", bits: 32 };
|
|
287
|
+
case FIELD_SUBTYPE_LONG:
|
|
288
|
+
case FIELD_SUBTYPE_CURRENCY: return { kind: "integer", bits: 64 };
|
|
289
|
+
case FIELD_SUBTYPE_DOUBLE:
|
|
290
|
+
case FIELD_SUBTYPE_FLOAT: return { kind: "real" };
|
|
291
|
+
case FIELD_SUBTYPE_DECIMAL: return { kind: "numeric" };
|
|
292
|
+
case FIELD_SUBTYPE_BOOLEAN: return { kind: "boolean" };
|
|
293
|
+
case FIELD_SUBTYPE_DATE: return { kind: "date" };
|
|
294
|
+
case FIELD_SUBTYPE_TIME: return { kind: "text" }; // SQL TIME rare; coerce to text
|
|
295
|
+
case FIELD_SUBTYPE_TIMESTAMP: return { kind: "timestamp", withTimezone: false };
|
|
296
|
+
case FIELD_SUBTYPE_OBJECT:
|
|
297
|
+
case FIELD_SUBTYPE_CLASS: return { kind: "json" };
|
|
298
|
+
default: return { kind: "text" }; // unknown → text fallback
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function readIdentityFields(identity: MetaData): string[] {
|
|
303
|
+
const raw = identity.ownAttr(IDENTITY_ATTR_FIELDS);
|
|
304
|
+
if (Array.isArray(raw)) {
|
|
305
|
+
return raw.map(String).filter((s) => s.length > 0);
|
|
306
|
+
}
|
|
307
|
+
if (typeof raw === "string") {
|
|
308
|
+
// Fallback: comma-separated string form (defensive; canonical form is array)
|
|
309
|
+
return raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
310
|
+
}
|
|
311
|
+
return [];
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function findField(entity: MetaObject, name: string): MetaData | undefined {
|
|
315
|
+
for (const field of entity.fields()) {
|
|
316
|
+
if (field.name === name) return field;
|
|
317
|
+
}
|
|
318
|
+
return undefined;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function toSnake(s: string): string {
|
|
322
|
+
return s
|
|
323
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2")
|
|
324
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
325
|
+
.toLowerCase();
|
|
326
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Public API surface for @metaobjectsdev/migrate-ts v0.1.0
|
|
2
|
+
//
|
|
3
|
+
// Architecture: pure pipeline — buildExpectedSchema(metadata) +
|
|
4
|
+
// introspect(db, dialect) → SchemaSnapshot; diff(expected, actual, opts)
|
|
5
|
+
// → Change[]; emit(changes, opts) → { up, down }; writeMigration(...)
|
|
6
|
+
// writes the pair to disk.
|
|
7
|
+
//
|
|
8
|
+
// See docs/specs/2026-05-11-v0.2-sp4-migrate-ts-design.md.
|
|
9
|
+
|
|
10
|
+
// Pipeline functions
|
|
11
|
+
export { buildExpectedSchema } from "./expected-schema.js";
|
|
12
|
+
export { introspect, introspectPostgres, introspectSqlite } from "./introspect/index.js";
|
|
13
|
+
export { diff } from "./diff/index.js";
|
|
14
|
+
export { emit } from "./emit/index.js";
|
|
15
|
+
export { writeMigration } from "./write-migration.js";
|
|
16
|
+
|
|
17
|
+
// Errors
|
|
18
|
+
export { BlockedChangesError } from "./errors.js";
|
|
19
|
+
|
|
20
|
+
// SqlType helpers (rarely needed but useful for advanced consumers)
|
|
21
|
+
export { isWidening, sqlTypeEquals } from "./sql-type.js";
|
|
22
|
+
|
|
23
|
+
// Types
|
|
24
|
+
export type { SqlType } from "./sql-type.js";
|
|
25
|
+
export type {
|
|
26
|
+
SchemaSnapshot, SnapshotMeta,
|
|
27
|
+
TableDescriptor, ColumnDescriptor, IndexDescriptor, FkDescriptor, ColumnDefault,
|
|
28
|
+
ViewDescriptor, FkAction,
|
|
29
|
+
Change, ChangeKind, ChangeStatus,
|
|
30
|
+
AllowOptions, AmbiguousChange, AmbiguousResolution, AmbiguousCallback,
|
|
31
|
+
DiffResult, EmitResult, Dialect,
|
|
32
|
+
} from "./types.js";
|
|
33
|
+
export type { DiffArgs } from "./diff/index.js";
|
|
34
|
+
export type { EmitOptions } from "./emit/index.js";
|
|
35
|
+
export type { WriteMigrationOptions, WriteMigrationResult } from "./write-migration.js";
|
|
36
|
+
|
|
37
|
+
// View diff + dialect emitters
|
|
38
|
+
export { classifyViewDiff } from "./view-diff.js";
|
|
39
|
+
export type { ViewShape, ViewDiffClass, ViewMigrationOpts } from "./view-diff.js";
|
|
40
|
+
export { emitPostgresViewMigration } from "./view-ddl-postgres.js";
|
|
41
|
+
export { emitSqliteViewMigration } from "./view-ddl-sqlite.js";
|
|
42
|
+
|
|
43
|
+
// View migrations orchestrator
|
|
44
|
+
export {
|
|
45
|
+
computeViewMigrations,
|
|
46
|
+
type ViewMigrationInput,
|
|
47
|
+
type ViewMigrationsOpts,
|
|
48
|
+
type ViewMigrationsResult,
|
|
49
|
+
} from "./source-aware-diff.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Kysely } from "kysely";
|
|
2
|
+
import type { Dialect, SchemaSnapshot } from "../types.js";
|
|
3
|
+
import { introspectPostgres } from "./postgres.js";
|
|
4
|
+
import { introspectSqlite } from "./sqlite.js";
|
|
5
|
+
|
|
6
|
+
export { introspectPostgres } from "./postgres.js";
|
|
7
|
+
export { introspectSqlite } from "./sqlite.js";
|
|
8
|
+
|
|
9
|
+
export async function introspect(db: Kysely<Record<string, unknown>>, dialect: Dialect): Promise<SchemaSnapshot> {
|
|
10
|
+
switch (dialect) {
|
|
11
|
+
case "postgres": return introspectPostgres(db);
|
|
12
|
+
case "sqlite": return introspectSqlite(db);
|
|
13
|
+
}
|
|
14
|
+
}
|