easy-ping 0.8.0 → 0.9.0
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 +18 -12
- package/dist/adapter-C3uFRpEN.d.cts +10 -0
- package/dist/adapter-DoCRPi9I.d.ts +10 -0
- package/dist/adapters/mysql.cjs +7 -4
- package/dist/adapters/mysql.cjs.map +1 -1
- package/dist/adapters/mysql.d.cts +3 -2
- package/dist/adapters/mysql.d.ts +3 -2
- package/dist/adapters/mysql.js +1 -1
- package/dist/adapters/postgres.js +3 -3
- package/dist/adapters/sqlite.cjs +8 -5
- package/dist/adapters/sqlite.cjs.map +1 -1
- package/dist/adapters/sqlite.d.cts +3 -2
- package/dist/adapters/sqlite.d.ts +3 -2
- package/dist/adapters/sqlite.js +1 -1
- package/dist/chunk-EROWLUL4.js +411 -0
- package/dist/chunk-EROWLUL4.js.map +1 -0
- package/dist/{chunk-HY47TQMY.js → chunk-HSB5T3LD.js} +11 -8
- package/dist/chunk-HSB5T3LD.js.map +1 -0
- package/dist/{chunk-M4R77BJD.js → chunk-UM2L26PV.js} +10 -7
- package/dist/chunk-UM2L26PV.js.map +1 -0
- package/dist/dialect-CvBJSydR.d.cts +10 -0
- package/dist/dialect-CvBJSydR.d.ts +10 -0
- package/dist/plugins/digests.cjs +19 -12
- package/dist/plugins/digests.cjs.map +1 -1
- package/dist/plugins/digests.js +5 -12
- package/dist/plugins/digests.js.map +1 -1
- package/dist/schema.cjs +334 -123
- package/dist/schema.cjs.map +1 -1
- package/dist/schema.d.cts +26 -1
- package/dist/schema.d.ts +26 -1
- package/dist/schema.js +3 -3
- package/package.json +1 -1
- package/dist/adapter-B7R4rXtJ.d.cts +0 -17
- package/dist/adapter-B7R4rXtJ.d.ts +0 -17
- package/dist/chunk-HY47TQMY.js.map +0 -1
- package/dist/chunk-M4R77BJD.js.map +0 -1
- package/dist/chunk-XIDVLYNQ.js +0 -209
- package/dist/chunk-XIDVLYNQ.js.map +0 -1
package/dist/schema.cjs
CHANGED
|
@@ -60,6 +60,173 @@ var coreSchema = {
|
|
|
60
60
|
}
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
+
// src/schema/render-mysql.ts
|
|
64
|
+
var quote = (value) => `\`${value.split("`").join("``")}\``;
|
|
65
|
+
function stringType(field, table, spec) {
|
|
66
|
+
const keys = [
|
|
67
|
+
...table.primaryKey ? [table.primaryKey] : [],
|
|
68
|
+
...(table.indexes ?? []).map((index) => index.on)
|
|
69
|
+
].filter((columns) => columns.includes(field));
|
|
70
|
+
if (keys.some((columns) => columns.length > 1)) return "VARCHAR(255)";
|
|
71
|
+
if (keys.length > 0) return "VARCHAR(768)";
|
|
72
|
+
if (spec.default !== void 0) return "VARCHAR(255)";
|
|
73
|
+
return "TEXT";
|
|
74
|
+
}
|
|
75
|
+
function columnType(field, table, spec) {
|
|
76
|
+
switch (spec.type) {
|
|
77
|
+
case "string":
|
|
78
|
+
return stringType(field, table, spec);
|
|
79
|
+
case "number":
|
|
80
|
+
return "INT";
|
|
81
|
+
case "boolean":
|
|
82
|
+
return "TINYINT(1)";
|
|
83
|
+
case "date":
|
|
84
|
+
return "DATETIME(3)";
|
|
85
|
+
case "json":
|
|
86
|
+
return "JSON";
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
var defaultClause = (spec) => {
|
|
90
|
+
if (spec.defaultNow) return " DEFAULT CURRENT_TIMESTAMP(3)";
|
|
91
|
+
if (spec.default === void 0) return "";
|
|
92
|
+
if (typeof spec.default === "string") return ` DEFAULT '${spec.default.replace(/'/g, "''")}'`;
|
|
93
|
+
if (typeof spec.default === "boolean") return ` DEFAULT ${spec.default ? 1 : 0}`;
|
|
94
|
+
return ` DEFAULT ${spec.default}`;
|
|
95
|
+
};
|
|
96
|
+
function mysqlColumnDefinition(field, table, spec) {
|
|
97
|
+
const nullability = spec.required ? " NOT NULL" : "";
|
|
98
|
+
return `${quote(toSnakeCase(field))} ${columnType(field, table, spec)}${nullability}${defaultClause(spec)}`;
|
|
99
|
+
}
|
|
100
|
+
function renderMysqlDdl(schema, prefix = "") {
|
|
101
|
+
const statements = [];
|
|
102
|
+
for (const table of Object.values(schema)) {
|
|
103
|
+
const lines = Object.entries(table.fields).map(
|
|
104
|
+
([field, spec]) => ` ${mysqlColumnDefinition(field, table, spec)}`
|
|
105
|
+
);
|
|
106
|
+
if (table.primaryKey?.length) {
|
|
107
|
+
lines.push(
|
|
108
|
+
` PRIMARY KEY (${table.primaryKey.map((f) => quote(toSnakeCase(f))).join(", ")})`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
for (const index of table.indexes ?? []) {
|
|
112
|
+
const name = quote(prefix + (index.name ?? `${table.tableName}_${index.on.join("_")}_idx`));
|
|
113
|
+
const columns = index.on.map((f) => quote(toSnakeCase(f))).join(", ");
|
|
114
|
+
lines.push(` ${index.unique ? "UNIQUE " : ""}INDEX ${name} (${columns})`);
|
|
115
|
+
}
|
|
116
|
+
statements.push(
|
|
117
|
+
`CREATE TABLE IF NOT EXISTS ${quote(prefix + table.tableName)} (
|
|
118
|
+
${lines.join(",\n")}
|
|
119
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
return statements;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// src/schema/migrate-mysql.ts
|
|
126
|
+
var MYSQL_INTROSPECT_COLUMNS_SQL = `
|
|
127
|
+
SELECT TABLE_NAME AS table_name, COLUMN_NAME AS column_name, DATA_TYPE AS data_type
|
|
128
|
+
FROM information_schema.COLUMNS
|
|
129
|
+
WHERE TABLE_SCHEMA = DATABASE()
|
|
130
|
+
`;
|
|
131
|
+
var MYSQL_INTROSPECT_INDEXES_SQL = `
|
|
132
|
+
SELECT DISTINCT TABLE_NAME AS table_name, INDEX_NAME AS index_name
|
|
133
|
+
FROM information_schema.STATISTICS
|
|
134
|
+
WHERE TABLE_SCHEMA = DATABASE()
|
|
135
|
+
`;
|
|
136
|
+
var LIVE_TYPE = {
|
|
137
|
+
string: ["varchar", "char", "text", "tinytext", "mediumtext", "longtext"],
|
|
138
|
+
number: ["int", "integer", "bigint", "smallint", "mediumint", "tinyint"],
|
|
139
|
+
boolean: ["tinyint", "bit", "boolean"],
|
|
140
|
+
date: ["datetime", "timestamp"],
|
|
141
|
+
json: ["json", "longtext"]
|
|
142
|
+
};
|
|
143
|
+
var quote2 = (value) => `\`${value.split("`").join("``")}\``;
|
|
144
|
+
var indexName = (table, declaration) => declaration.name ?? `${table.tableName}_${declaration.on.join("_")}_idx`;
|
|
145
|
+
var lower = (value) => String(value).toLowerCase();
|
|
146
|
+
async function planMysqlMigration(query, schema, prefix = "") {
|
|
147
|
+
const [columnRows, indexRows] = await Promise.all([
|
|
148
|
+
query(MYSQL_INTROSPECT_COLUMNS_SQL, []),
|
|
149
|
+
query(MYSQL_INTROSPECT_INDEXES_SQL, [])
|
|
150
|
+
]);
|
|
151
|
+
const columns = /* @__PURE__ */ new Map();
|
|
152
|
+
for (const row of columnRows.rows) {
|
|
153
|
+
const table = lower(row.table_name ?? row.TABLE_NAME);
|
|
154
|
+
const byColumn = columns.get(table) ?? /* @__PURE__ */ new Map();
|
|
155
|
+
byColumn.set(lower(row.column_name ?? row.COLUMN_NAME), lower(row.data_type ?? row.DATA_TYPE));
|
|
156
|
+
columns.set(table, byColumn);
|
|
157
|
+
}
|
|
158
|
+
const indexes = new Set(
|
|
159
|
+
indexRows.rows.map(
|
|
160
|
+
(row) => `${lower(row.table_name ?? row.TABLE_NAME)}.${lower(row.index_name ?? row.INDEX_NAME)}`
|
|
161
|
+
)
|
|
162
|
+
);
|
|
163
|
+
const statements = [];
|
|
164
|
+
const unsupported = [];
|
|
165
|
+
const createdTables = [];
|
|
166
|
+
for (const table of Object.values(schema)) {
|
|
167
|
+
const qualified = prefix + table.tableName;
|
|
168
|
+
const existing = columns.get(qualified.toLowerCase());
|
|
169
|
+
if (!existing || existing.size === 0) {
|
|
170
|
+
createdTables.push(qualified);
|
|
171
|
+
statements.push(...renderMysqlDdl({ [table.tableName]: table }, prefix));
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
for (const [field, spec] of Object.entries(table.fields)) {
|
|
175
|
+
const column = toSnakeCase(field);
|
|
176
|
+
const found = existing.get(column.toLowerCase());
|
|
177
|
+
if (found === void 0) {
|
|
178
|
+
const canFill = spec.defaultNow || spec.default !== void 0;
|
|
179
|
+
const definition = mysqlColumnDefinition(field, table, {
|
|
180
|
+
...spec,
|
|
181
|
+
// A required column with nothing to fill existing rows with is added nullable.
|
|
182
|
+
required: Boolean(spec.required && canFill)
|
|
183
|
+
});
|
|
184
|
+
statements.push(`ALTER TABLE ${quote2(qualified)} ADD COLUMN ${definition}`);
|
|
185
|
+
if (spec.required && !canFill) {
|
|
186
|
+
unsupported.push(
|
|
187
|
+
`${qualified}.${column} is declared required but has no default; it was added nullable. Backfill it, then ALTER TABLE ... MODIFY ... NOT NULL yourself.`
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (!LIVE_TYPE[spec.type].includes(found)) {
|
|
193
|
+
unsupported.push(
|
|
194
|
+
`${qualified}.${column} is ${found} but ${spec.type} is declared. Changing a column type can lose data \u2014 do it yourself.`
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
for (const declaration of table.indexes ?? []) {
|
|
199
|
+
const name = prefix + indexName(table, declaration);
|
|
200
|
+
if (indexes.has(`${qualified.toLowerCase()}.${name.toLowerCase()}`)) continue;
|
|
201
|
+
const textColumn = declaration.on.find((field) => {
|
|
202
|
+
const live = existing.get(toSnakeCase(field).toLowerCase());
|
|
203
|
+
return live?.endsWith("text") ?? false;
|
|
204
|
+
});
|
|
205
|
+
if (textColumn) {
|
|
206
|
+
unsupported.push(
|
|
207
|
+
`${qualified}: index ${name} needs ${toSnakeCase(textColumn)} as VARCHAR, but it is TEXT, which MySQL cannot index without a prefix length. Change the column type, then rerun.`
|
|
208
|
+
);
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
const cols = declaration.on.map((field) => quote2(toSnakeCase(field))).join(", ");
|
|
212
|
+
statements.push(
|
|
213
|
+
`CREATE ${declaration.unique ? "UNIQUE " : ""}INDEX ${quote2(name)} ON ${quote2(qualified)} (${cols})`
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
for (const column of existing.keys()) {
|
|
217
|
+
const declared = Object.keys(table.fields).some(
|
|
218
|
+
(field) => toSnakeCase(field).toLowerCase() === column
|
|
219
|
+
);
|
|
220
|
+
if (!declared) {
|
|
221
|
+
unsupported.push(
|
|
222
|
+
`${qualified}.${column} exists but is not declared. Left alone \u2014 drop it yourself if it is ours and no longer needed.`
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return { statements, unsupported, createdTables };
|
|
228
|
+
}
|
|
229
|
+
|
|
63
230
|
// src/schema/render-sql.ts
|
|
64
231
|
var SQL_TYPE = {
|
|
65
232
|
string: "text",
|
|
@@ -68,8 +235,8 @@ var SQL_TYPE = {
|
|
|
68
235
|
date: "timestamptz",
|
|
69
236
|
json: "jsonb"
|
|
70
237
|
};
|
|
71
|
-
var
|
|
72
|
-
var
|
|
238
|
+
var quote3 = (value) => `"${value.replace(/"/g, '""')}"`;
|
|
239
|
+
var defaultClause2 = (field) => {
|
|
73
240
|
if (field.defaultNow) return " DEFAULT now()";
|
|
74
241
|
if (field.default === void 0) return "";
|
|
75
242
|
if (typeof field.default === "string") return ` DEFAULT '${field.default.replace(/'/g, "''")}'`;
|
|
@@ -78,25 +245,25 @@ var defaultClause = (field) => {
|
|
|
78
245
|
function renderPostgresDdl(schema, prefix = "") {
|
|
79
246
|
const statements = [];
|
|
80
247
|
for (const table of Object.values(schema)) {
|
|
81
|
-
const name =
|
|
248
|
+
const name = quote3(prefix + table.tableName);
|
|
82
249
|
const columns = Object.entries(table.fields).map(([field, spec]) => {
|
|
83
250
|
const nullability = spec.required ? " NOT NULL" : "";
|
|
84
|
-
return ` ${
|
|
251
|
+
return ` ${quote3(toSnakeCase(field))} ${SQL_TYPE[spec.type]}${nullability}${defaultClause2(spec)}`;
|
|
85
252
|
});
|
|
86
253
|
if (table.primaryKey?.length) {
|
|
87
|
-
const key = table.primaryKey.map((field) =>
|
|
254
|
+
const key = table.primaryKey.map((field) => quote3(toSnakeCase(field))).join(", ");
|
|
88
255
|
columns.push(` PRIMARY KEY (${key})`);
|
|
89
256
|
}
|
|
90
257
|
statements.push(`CREATE TABLE IF NOT EXISTS ${name} (
|
|
91
258
|
${columns.join(",\n")}
|
|
92
259
|
)`);
|
|
93
260
|
for (const declaration of table.indexes ?? []) {
|
|
94
|
-
const
|
|
261
|
+
const indexName3 = quote3(
|
|
95
262
|
prefix + (declaration.name ?? `${table.tableName}_${declaration.on.join("_")}_idx`)
|
|
96
263
|
);
|
|
97
|
-
const columns2 = declaration.on.map((field) =>
|
|
264
|
+
const columns2 = declaration.on.map((field) => quote3(toSnakeCase(field))).join(", ");
|
|
98
265
|
const unique = declaration.unique ? "UNIQUE " : "";
|
|
99
|
-
statements.push(`CREATE ${unique}INDEX IF NOT EXISTS ${
|
|
266
|
+
statements.push(`CREATE ${unique}INDEX IF NOT EXISTS ${indexName3} ON ${name} (${columns2})`);
|
|
100
267
|
}
|
|
101
268
|
}
|
|
102
269
|
return statements;
|
|
@@ -110,21 +277,21 @@ var SQL_TYPE2 = {
|
|
|
110
277
|
date: "timestamptz",
|
|
111
278
|
json: "jsonb"
|
|
112
279
|
};
|
|
113
|
-
var
|
|
280
|
+
var LIVE_TYPE2 = {
|
|
114
281
|
string: ["text", "character varying", "character"],
|
|
115
282
|
number: ["integer", "bigint", "smallint"],
|
|
116
283
|
boolean: ["boolean"],
|
|
117
284
|
date: ["timestamp with time zone", "timestamp without time zone"],
|
|
118
285
|
json: ["jsonb", "json"]
|
|
119
286
|
};
|
|
120
|
-
var
|
|
121
|
-
var
|
|
287
|
+
var quote4 = (value) => `"${value.replace(/"/g, '""')}"`;
|
|
288
|
+
var defaultClause3 = (field) => {
|
|
122
289
|
if (field.defaultNow) return " DEFAULT now()";
|
|
123
290
|
if (field.default === void 0) return "";
|
|
124
291
|
if (typeof field.default === "string") return ` DEFAULT '${field.default.replace(/'/g, "''")}'`;
|
|
125
292
|
return ` DEFAULT ${field.default}`;
|
|
126
293
|
};
|
|
127
|
-
var
|
|
294
|
+
var indexName2 = (table, declaration) => declaration.name ?? `${table.tableName}_${declaration.on.join("_")}_idx`;
|
|
128
295
|
function normaliseColumn(row) {
|
|
129
296
|
if ("table" in row) return row;
|
|
130
297
|
return {
|
|
@@ -166,7 +333,7 @@ async function planPostgresMigration(introspect, schema, prefix = "") {
|
|
|
166
333
|
const canFill = spec.defaultNow || spec.default !== void 0;
|
|
167
334
|
const nullability = spec.required && canFill ? " NOT NULL" : "";
|
|
168
335
|
statements.push(
|
|
169
|
-
`ALTER TABLE ${
|
|
336
|
+
`ALTER TABLE ${quote4(qualified)} ADD COLUMN IF NOT EXISTS ${quote4(column)} ${SQL_TYPE2[spec.type]}${nullability}${defaultClause3(spec)}`
|
|
170
337
|
);
|
|
171
338
|
if (spec.required && !canFill) {
|
|
172
339
|
unsupported.push(
|
|
@@ -175,17 +342,17 @@ async function planPostgresMigration(introspect, schema, prefix = "") {
|
|
|
175
342
|
}
|
|
176
343
|
continue;
|
|
177
344
|
}
|
|
178
|
-
if (!
|
|
345
|
+
if (!LIVE_TYPE2[spec.type].includes(found.type)) {
|
|
179
346
|
unsupported.push(
|
|
180
347
|
`${qualified}.${column} is ${found.type} but ${spec.type} is declared (${SQL_TYPE2[spec.type]}). Changing a column type can lose data \u2014 do it yourself.`
|
|
181
348
|
);
|
|
182
349
|
}
|
|
183
350
|
}
|
|
184
351
|
for (const declaration of table.indexes ?? []) {
|
|
185
|
-
const name =
|
|
186
|
-
const columns = declaration.on.map((field) =>
|
|
352
|
+
const name = quote4(prefix + indexName2(table, declaration));
|
|
353
|
+
const columns = declaration.on.map((field) => quote4(toSnakeCase(field))).join(", ");
|
|
187
354
|
statements.push(
|
|
188
|
-
`CREATE ${declaration.unique ? "UNIQUE " : ""}INDEX IF NOT EXISTS ${name} ON ${
|
|
355
|
+
`CREATE ${declaration.unique ? "UNIQUE " : ""}INDEX IF NOT EXISTS ${name} ON ${quote4(qualified)} (${columns})`
|
|
189
356
|
);
|
|
190
357
|
}
|
|
191
358
|
for (const column of existing.keys()) {
|
|
@@ -200,6 +367,151 @@ async function planPostgresMigration(introspect, schema, prefix = "") {
|
|
|
200
367
|
return { statements, unsupported, createdTables };
|
|
201
368
|
}
|
|
202
369
|
|
|
370
|
+
// src/schema/render-sqlite.ts
|
|
371
|
+
var SQLITE_TYPE = {
|
|
372
|
+
string: "TEXT",
|
|
373
|
+
number: "INTEGER",
|
|
374
|
+
boolean: "INTEGER",
|
|
375
|
+
// ISO-8601 with milliseconds and a trailing Z, the same 24 characters
|
|
376
|
+
// `Date#toISOString` writes, so `<=` on the text is `<=` on the instant.
|
|
377
|
+
date: "TEXT",
|
|
378
|
+
json: "TEXT"
|
|
379
|
+
};
|
|
380
|
+
var quote5 = (value) => `"${value.split('"').join('""')}"`;
|
|
381
|
+
var defaultClause4 = (spec) => {
|
|
382
|
+
if (spec.defaultNow) return " DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))";
|
|
383
|
+
if (spec.default === void 0) return "";
|
|
384
|
+
if (typeof spec.default === "string") return ` DEFAULT '${spec.default.replace(/'/g, "''")}'`;
|
|
385
|
+
if (typeof spec.default === "boolean") return ` DEFAULT ${spec.default ? 1 : 0}`;
|
|
386
|
+
return ` DEFAULT ${spec.default}`;
|
|
387
|
+
};
|
|
388
|
+
function sqliteColumnDefinition(field, spec) {
|
|
389
|
+
const nullability = spec.required ? " NOT NULL" : "";
|
|
390
|
+
return `${quote5(toSnakeCase(field))} ${SQLITE_TYPE[spec.type]}${nullability}${defaultClause4(spec)}`;
|
|
391
|
+
}
|
|
392
|
+
function renderSqliteDdl(schema, prefix = "") {
|
|
393
|
+
const statements = [];
|
|
394
|
+
for (const table of Object.values(schema)) {
|
|
395
|
+
const name = quote5(prefix + table.tableName);
|
|
396
|
+
const columns = Object.entries(table.fields).map(
|
|
397
|
+
([field, spec]) => ` ${sqliteColumnDefinition(field, spec)}`
|
|
398
|
+
);
|
|
399
|
+
if (table.primaryKey?.length) {
|
|
400
|
+
columns.push(
|
|
401
|
+
` PRIMARY KEY (${table.primaryKey.map((f) => quote5(toSnakeCase(f))).join(", ")})`
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
statements.push(`CREATE TABLE IF NOT EXISTS ${name} (
|
|
405
|
+
${columns.join(",\n")}
|
|
406
|
+
)`);
|
|
407
|
+
for (const index of table.indexes ?? []) {
|
|
408
|
+
const indexName3 = quote5(
|
|
409
|
+
prefix + (index.name ?? `${table.tableName}_${index.on.join("_")}_idx`)
|
|
410
|
+
);
|
|
411
|
+
const cols = index.on.map((f) => quote5(toSnakeCase(f))).join(", ");
|
|
412
|
+
statements.push(
|
|
413
|
+
`CREATE ${index.unique ? "UNIQUE " : ""}INDEX IF NOT EXISTS ${indexName3} ON ${name} (${cols})`
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return statements;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// src/schema/migrate-sqlite.ts
|
|
421
|
+
var SQLITE_INTROSPECT_SQL = `
|
|
422
|
+
SELECT m.name AS table_name, p.name AS column_name, p.type AS data_type
|
|
423
|
+
FROM sqlite_master AS m
|
|
424
|
+
JOIN pragma_table_info(m.name) AS p
|
|
425
|
+
WHERE m.type = 'table' AND m.name NOT LIKE 'sqlite_%'
|
|
426
|
+
`;
|
|
427
|
+
var quote6 = (value) => `"${value.split('"').join('""')}"`;
|
|
428
|
+
var REBUILD_SUFFIX = "__easy_ping_rebuild";
|
|
429
|
+
async function planSqliteMigration(query, schema, prefix = "") {
|
|
430
|
+
const { rows } = await query(SQLITE_INTROSPECT_SQL, []);
|
|
431
|
+
const live = /* @__PURE__ */ new Map();
|
|
432
|
+
for (const row of rows) {
|
|
433
|
+
const table = String(row.table_name);
|
|
434
|
+
const columns = live.get(table) ?? /* @__PURE__ */ new Map();
|
|
435
|
+
columns.set(String(row.column_name), String(row.data_type).toUpperCase());
|
|
436
|
+
live.set(table, columns);
|
|
437
|
+
}
|
|
438
|
+
const statements = [];
|
|
439
|
+
const unsupported = [];
|
|
440
|
+
const createdTables = [];
|
|
441
|
+
for (const table of Object.values(schema)) {
|
|
442
|
+
const qualified = prefix + table.tableName;
|
|
443
|
+
const existing = live.get(qualified);
|
|
444
|
+
if (!existing || existing.size === 0) {
|
|
445
|
+
createdTables.push(qualified);
|
|
446
|
+
statements.push(...renderSqliteDdl({ [table.tableName]: table }, prefix));
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
const missing = Object.entries(table.fields).filter(
|
|
450
|
+
([field]) => !existing.has(toSnakeCase(field))
|
|
451
|
+
);
|
|
452
|
+
const relaxed = {
|
|
453
|
+
...table,
|
|
454
|
+
fields: Object.fromEntries(
|
|
455
|
+
Object.entries(table.fields).map(([field, spec]) => {
|
|
456
|
+
const unfillable = !existing.has(toSnakeCase(field)) && spec.required && !spec.defaultNow && spec.default === void 0;
|
|
457
|
+
if (unfillable) {
|
|
458
|
+
unsupported.push(
|
|
459
|
+
`${qualified}.${toSnakeCase(field)} is declared required but has no default; it was added nullable. Backfill it; SQLite cannot add NOT NULL afterwards without a rebuild.`
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
return [field, unfillable ? { ...spec, required: false } : spec];
|
|
463
|
+
})
|
|
464
|
+
)
|
|
465
|
+
};
|
|
466
|
+
if (missing.some(([, spec]) => spec.defaultNow)) {
|
|
467
|
+
statements.push(...rebuild(relaxed, prefix, existing));
|
|
468
|
+
} else {
|
|
469
|
+
for (const [field] of missing) {
|
|
470
|
+
const spec = relaxed.fields[field];
|
|
471
|
+
if (!spec) continue;
|
|
472
|
+
statements.push(
|
|
473
|
+
`ALTER TABLE ${quote6(qualified)} ADD COLUMN ${sqliteColumnDefinition(field, spec)}`
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
statements.push(...renderSqliteDdl({ [table.tableName]: table }, prefix).slice(1));
|
|
477
|
+
}
|
|
478
|
+
for (const [field, spec] of Object.entries(table.fields)) {
|
|
479
|
+
const found = existing.get(toSnakeCase(field));
|
|
480
|
+
if (found !== void 0 && found !== SQLITE_TYPE[spec.type]) {
|
|
481
|
+
unsupported.push(
|
|
482
|
+
`${qualified}.${toSnakeCase(field)} is ${found || "untyped"} but ${SQLITE_TYPE[spec.type]} is declared. Changing a column type needs a table rebuild \u2014 do it yourself.`
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
for (const column of existing.keys()) {
|
|
487
|
+
const declared = Object.keys(table.fields).some((field) => toSnakeCase(field) === column);
|
|
488
|
+
if (!declared) {
|
|
489
|
+
unsupported.push(
|
|
490
|
+
`${qualified}.${column} exists but is not declared. Left alone \u2014 drop it yourself if it is ours and no longer needed.`
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return { statements, unsupported, createdTables };
|
|
496
|
+
}
|
|
497
|
+
function rebuild(table, prefix, existing) {
|
|
498
|
+
const qualified = prefix + table.tableName;
|
|
499
|
+
const temp = qualified + REBUILD_SUFFIX;
|
|
500
|
+
const [createTemp = ""] = renderSqliteDdl(
|
|
501
|
+
{ [table.tableName]: { ...table, tableName: table.tableName + REBUILD_SUFFIX, indexes: [] } },
|
|
502
|
+
prefix
|
|
503
|
+
);
|
|
504
|
+
const shared = Object.keys(table.fields).map(toSnakeCase).filter((column) => existing.has(column)).map(quote6).join(", ");
|
|
505
|
+
return [
|
|
506
|
+
`DROP TABLE IF EXISTS ${quote6(temp)}`,
|
|
507
|
+
createTemp,
|
|
508
|
+
`INSERT INTO ${quote6(temp)} (${shared}) SELECT ${shared} FROM ${quote6(qualified)}`,
|
|
509
|
+
`DROP TABLE ${quote6(qualified)}`,
|
|
510
|
+
`ALTER TABLE ${quote6(temp)} RENAME TO ${quote6(qualified)}`,
|
|
511
|
+
...renderSqliteDdl({ [table.tableName]: table }, prefix).slice(1)
|
|
512
|
+
];
|
|
513
|
+
}
|
|
514
|
+
|
|
203
515
|
// src/schema/render-drizzle.ts
|
|
204
516
|
var COLUMN_BUILDER = {
|
|
205
517
|
string: "text",
|
|
@@ -264,115 +576,14 @@ ${body}
|
|
|
264
576
|
`;
|
|
265
577
|
}
|
|
266
578
|
|
|
267
|
-
// src/schema/render-mysql.ts
|
|
268
|
-
var quote3 = (value) => `\`${value.split("`").join("``")}\``;
|
|
269
|
-
function stringType(field, table, spec) {
|
|
270
|
-
const keys = [
|
|
271
|
-
...table.primaryKey ? [table.primaryKey] : [],
|
|
272
|
-
...(table.indexes ?? []).map((index) => index.on)
|
|
273
|
-
].filter((columns) => columns.includes(field));
|
|
274
|
-
if (keys.some((columns) => columns.length > 1)) return "VARCHAR(255)";
|
|
275
|
-
if (keys.length > 0) return "VARCHAR(768)";
|
|
276
|
-
if (spec.default !== void 0) return "VARCHAR(255)";
|
|
277
|
-
return "TEXT";
|
|
278
|
-
}
|
|
279
|
-
function columnType(field, table, spec) {
|
|
280
|
-
switch (spec.type) {
|
|
281
|
-
case "string":
|
|
282
|
-
return stringType(field, table, spec);
|
|
283
|
-
case "number":
|
|
284
|
-
return "INT";
|
|
285
|
-
case "boolean":
|
|
286
|
-
return "TINYINT(1)";
|
|
287
|
-
case "date":
|
|
288
|
-
return "DATETIME(3)";
|
|
289
|
-
case "json":
|
|
290
|
-
return "JSON";
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
var defaultClause3 = (spec) => {
|
|
294
|
-
if (spec.defaultNow) return " DEFAULT CURRENT_TIMESTAMP(3)";
|
|
295
|
-
if (spec.default === void 0) return "";
|
|
296
|
-
if (typeof spec.default === "string") return ` DEFAULT '${spec.default.replace(/'/g, "''")}'`;
|
|
297
|
-
if (typeof spec.default === "boolean") return ` DEFAULT ${spec.default ? 1 : 0}`;
|
|
298
|
-
return ` DEFAULT ${spec.default}`;
|
|
299
|
-
};
|
|
300
|
-
function renderMysqlDdl(schema, prefix = "") {
|
|
301
|
-
const statements = [];
|
|
302
|
-
for (const table of Object.values(schema)) {
|
|
303
|
-
const lines = Object.entries(table.fields).map(([field, spec]) => {
|
|
304
|
-
const nullability = spec.required ? " NOT NULL" : "";
|
|
305
|
-
return ` ${quote3(toSnakeCase(field))} ${columnType(field, table, spec)}${nullability}${defaultClause3(spec)}`;
|
|
306
|
-
});
|
|
307
|
-
if (table.primaryKey?.length) {
|
|
308
|
-
lines.push(
|
|
309
|
-
` PRIMARY KEY (${table.primaryKey.map((f) => quote3(toSnakeCase(f))).join(", ")})`
|
|
310
|
-
);
|
|
311
|
-
}
|
|
312
|
-
for (const index of table.indexes ?? []) {
|
|
313
|
-
const name = quote3(prefix + (index.name ?? `${table.tableName}_${index.on.join("_")}_idx`));
|
|
314
|
-
const columns = index.on.map((f) => quote3(toSnakeCase(f))).join(", ");
|
|
315
|
-
lines.push(` ${index.unique ? "UNIQUE " : ""}INDEX ${name} (${columns})`);
|
|
316
|
-
}
|
|
317
|
-
statements.push(
|
|
318
|
-
`CREATE TABLE IF NOT EXISTS ${quote3(prefix + table.tableName)} (
|
|
319
|
-
${lines.join(",\n")}
|
|
320
|
-
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`
|
|
321
|
-
);
|
|
322
|
-
}
|
|
323
|
-
return statements;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// src/schema/render-sqlite.ts
|
|
327
|
-
var SQL_TYPE3 = {
|
|
328
|
-
string: "TEXT",
|
|
329
|
-
number: "INTEGER",
|
|
330
|
-
boolean: "INTEGER",
|
|
331
|
-
// ISO-8601 with milliseconds and a trailing Z, the same 24 characters
|
|
332
|
-
// `Date#toISOString` writes, so `<=` on the text is `<=` on the instant.
|
|
333
|
-
date: "TEXT",
|
|
334
|
-
json: "TEXT"
|
|
335
|
-
};
|
|
336
|
-
var quote4 = (value) => `"${value.split('"').join('""')}"`;
|
|
337
|
-
var defaultClause4 = (spec) => {
|
|
338
|
-
if (spec.defaultNow) return " DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))";
|
|
339
|
-
if (spec.default === void 0) return "";
|
|
340
|
-
if (typeof spec.default === "string") return ` DEFAULT '${spec.default.replace(/'/g, "''")}'`;
|
|
341
|
-
if (typeof spec.default === "boolean") return ` DEFAULT ${spec.default ? 1 : 0}`;
|
|
342
|
-
return ` DEFAULT ${spec.default}`;
|
|
343
|
-
};
|
|
344
|
-
function renderSqliteDdl(schema, prefix = "") {
|
|
345
|
-
const statements = [];
|
|
346
|
-
for (const table of Object.values(schema)) {
|
|
347
|
-
const name = quote4(prefix + table.tableName);
|
|
348
|
-
const columns = Object.entries(table.fields).map(([field, spec]) => {
|
|
349
|
-
const nullability = spec.required ? " NOT NULL" : "";
|
|
350
|
-
return ` ${quote4(toSnakeCase(field))} ${SQL_TYPE3[spec.type]}${nullability}${defaultClause4(spec)}`;
|
|
351
|
-
});
|
|
352
|
-
if (table.primaryKey?.length) {
|
|
353
|
-
columns.push(
|
|
354
|
-
` PRIMARY KEY (${table.primaryKey.map((f) => quote4(toSnakeCase(f))).join(", ")})`
|
|
355
|
-
);
|
|
356
|
-
}
|
|
357
|
-
statements.push(`CREATE TABLE IF NOT EXISTS ${name} (
|
|
358
|
-
${columns.join(",\n")}
|
|
359
|
-
)`);
|
|
360
|
-
for (const index of table.indexes ?? []) {
|
|
361
|
-
const indexName2 = quote4(
|
|
362
|
-
prefix + (index.name ?? `${table.tableName}_${index.on.join("_")}_idx`)
|
|
363
|
-
);
|
|
364
|
-
const cols = index.on.map((f) => quote4(toSnakeCase(f))).join(", ");
|
|
365
|
-
statements.push(
|
|
366
|
-
`CREATE ${index.unique ? "UNIQUE " : ""}INDEX IF NOT EXISTS ${indexName2} ON ${name} (${cols})`
|
|
367
|
-
);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
return statements;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
579
|
exports.INTROSPECT_SQL = INTROSPECT_SQL;
|
|
580
|
+
exports.MYSQL_INTROSPECT_COLUMNS_SQL = MYSQL_INTROSPECT_COLUMNS_SQL;
|
|
581
|
+
exports.MYSQL_INTROSPECT_INDEXES_SQL = MYSQL_INTROSPECT_INDEXES_SQL;
|
|
582
|
+
exports.SQLITE_INTROSPECT_SQL = SQLITE_INTROSPECT_SQL;
|
|
374
583
|
exports.coreSchema = coreSchema;
|
|
584
|
+
exports.planMysqlMigration = planMysqlMigration;
|
|
375
585
|
exports.planPostgresMigration = planPostgresMigration;
|
|
586
|
+
exports.planSqliteMigration = planSqliteMigration;
|
|
376
587
|
exports.renderDrizzleSchema = renderDrizzleSchema;
|
|
377
588
|
exports.renderMysqlDdl = renderMysqlDdl;
|
|
378
589
|
exports.renderPostgresDdl = renderPostgresDdl;
|