@vibeorm/migrate 1.3.1 → 2.0.0-alpha.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 (61) hide show
  1. package/README.md +33 -66
  2. package/dist/ddl/drop-order.d.ts +25 -0
  3. package/dist/ddl/drop-order.d.ts.map +1 -0
  4. package/dist/ddl/mysql.d.ts +46 -0
  5. package/dist/ddl/mysql.d.ts.map +1 -0
  6. package/dist/ddl/postgres.d.ts +24 -0
  7. package/dist/ddl/postgres.d.ts.map +1 -0
  8. package/dist/ddl/render.d.ts +25 -0
  9. package/dist/ddl/render.d.ts.map +1 -0
  10. package/dist/ddl/sqlite.d.ts +62 -0
  11. package/dist/ddl/sqlite.d.ts.map +1 -0
  12. package/dist/differ.d.ts +33 -0
  13. package/dist/differ.d.ts.map +1 -0
  14. package/dist/down.d.ts +55 -0
  15. package/dist/down.d.ts.map +1 -0
  16. package/dist/enum-values.d.ts +19 -0
  17. package/dist/enum-values.d.ts.map +1 -0
  18. package/dist/execute.d.ts +41 -0
  19. package/dist/execute.d.ts.map +1 -0
  20. package/dist/extensions.d.ts +74 -0
  21. package/dist/extensions.d.ts.map +1 -0
  22. package/dist/index.d.ts +26 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +3858 -0
  25. package/dist/index.js.map +31 -0
  26. package/dist/introspect/mysql.d.ts +31 -0
  27. package/dist/introspect/mysql.d.ts.map +1 -0
  28. package/dist/introspect/postgres.d.ts +25 -0
  29. package/dist/introspect/postgres.d.ts.map +1 -0
  30. package/dist/introspect/shared.d.ts +76 -0
  31. package/dist/introspect/shared.d.ts.map +1 -0
  32. package/dist/introspect/sqlite.d.ts +53 -0
  33. package/dist/introspect/sqlite.d.ts.map +1 -0
  34. package/dist/mysql-types.d.ts +66 -0
  35. package/dist/mysql-types.d.ts.map +1 -0
  36. package/dist/normalize.d.ts +115 -0
  37. package/dist/normalize.d.ts.map +1 -0
  38. package/dist/postgres-types.d.ts +95 -0
  39. package/dist/postgres-types.d.ts.map +1 -0
  40. package/dist/push.d.ts +45 -0
  41. package/dist/push.d.ts.map +1 -0
  42. package/dist/relations.d.ts +40 -0
  43. package/dist/relations.d.ts.map +1 -0
  44. package/dist/runner.d.ts +120 -0
  45. package/dist/runner.d.ts.map +1 -0
  46. package/dist/sqlite-types.d.ts +87 -0
  47. package/dist/sqlite-types.d.ts.map +1 -0
  48. package/dist/types.d.ts +217 -0
  49. package/dist/types.d.ts.map +1 -0
  50. package/package.json +33 -23
  51. package/src/cascade-actions.ts +0 -88
  52. package/src/ddl-builder.ts +0 -415
  53. package/src/index.ts +0 -41
  54. package/src/introspector.ts +0 -684
  55. package/src/migration-runner.ts +0 -127
  56. package/src/relation-utils.ts +0 -79
  57. package/src/schema-differ.ts +0 -865
  58. package/src/schema-printer.ts +0 -259
  59. package/src/snapshot.ts +0 -141
  60. package/src/sql-utils.ts +0 -45
  61. package/src/types.ts +0 -13
@@ -1,259 +0,0 @@
1
- /**
2
- * Schema Printer — Schema IR → .prisma text
3
- *
4
- * Converts a Schema IR back into Prisma schema language text.
5
- * Used by `db pull` to generate a .prisma file from the database.
6
- */
7
-
8
- import type {
9
- Schema,
10
- Model,
11
- Field,
12
- ScalarField,
13
- EnumField,
14
- RelationField,
15
- Enum,
16
- DefaultValue,
17
- CascadeAction,
18
- } from "@vibeorm/parser";
19
- import { resolveOnDelete, resolveOnUpdate } from "./cascade-actions.ts";
20
-
21
- // ─── Helpers ──────────────────────────────────────────────────────
22
-
23
- function indent(params: { text: string; level?: number }): string {
24
- const spaces = " ".repeat(params.level ?? 1);
25
- return `${spaces}${params.text}`;
26
- }
27
-
28
- function formatDefault(params: { def: DefaultValue }): string {
29
- const { def } = params;
30
- switch (def.kind) {
31
- case "autoincrement":
32
- return "@default(autoincrement())";
33
- case "now":
34
- return "@default(now())";
35
- case "uuid":
36
- return "@default(uuid())";
37
- case "cuid":
38
- return "@default(cuid())";
39
- case "nanoid":
40
- return "@default(nanoid())";
41
- case "ulid":
42
- return "@default(ulid())";
43
- case "dbgenerated":
44
- return `@default(dbgenerated("${def.value}"))`;
45
- case "literal": {
46
- const val = def.value;
47
- if (typeof val === "string") return `@default("${val}")`;
48
- if (typeof val === "boolean") return `@default(${val})`;
49
- return `@default(${val})`;
50
- }
51
- default:
52
- return "";
53
- }
54
- }
55
-
56
- // ─── Field Printers ───────────────────────────────────────────────
57
-
58
- function printScalarField(params: { field: ScalarField }): string {
59
- const { field } = params;
60
- const parts: string[] = [field.name];
61
-
62
- // Type
63
- let typeName = field.prismaType;
64
- if (field.isList) typeName += "[]";
65
- else if (!field.isRequired) typeName += "?";
66
- parts.push(typeName);
67
-
68
- // Attributes
69
- if (field.isId) parts.push("@id");
70
- if (field.isUnique) parts.push("@unique");
71
- if (field.default) parts.push(formatDefault({ def: field.default }));
72
- if (field.isUpdatedAt) parts.push("@updatedAt");
73
- if (field.dbName !== field.name) parts.push(`@map("${field.dbName}")`);
74
- if (field.nativeType) parts.push(`@db.${field.nativeType}`);
75
-
76
- return indent({ text: parts.join(" ") });
77
- }
78
-
79
- function printEnumField(params: { field: EnumField }): string {
80
- const { field } = params;
81
- const parts: string[] = [field.name];
82
-
83
- let typeName = field.enumName;
84
- if (field.isList) typeName += "[]";
85
- else if (!field.isRequired) typeName += "?";
86
- parts.push(typeName);
87
-
88
- if (field.isId) parts.push("@id");
89
- if (field.isUnique) parts.push("@unique");
90
- if (field.default) parts.push(formatDefault({ def: field.default }));
91
- if (field.dbName !== field.name) parts.push(`@map("${field.dbName}")`);
92
-
93
- return indent({ text: parts.join(" ") });
94
- }
95
-
96
- function printRelationField(params: { field: RelationField }): string {
97
- const { field } = params;
98
- const parts: string[] = [field.name];
99
-
100
- // Type
101
- let typeName = field.relatedModel;
102
- if (field.isList) typeName += "[]";
103
- else if (!field.isRequired) typeName += "?";
104
- parts.push(typeName);
105
-
106
- // @relation with fields/references if this side owns the FK
107
- if (field.relation.isForeignKey && field.relation.fields.length > 0) {
108
- const fieldsStr = field.relation.fields.map((f: string) => f).join(", ");
109
- const refsStr = field.relation.references.map((f: string) => f).join(", ");
110
-
111
- // Only emit cascade actions when they differ from the Prisma default for
112
- // this relation's optionality. Keeps `db pull` output minimal and matches
113
- // what users would have hand-written.
114
- const explicitOnDelete = nonDefaultOnDelete({ field });
115
- const explicitOnUpdate = nonDefaultOnUpdate({ field });
116
-
117
- const segments: string[] = [];
118
- if (field.relation.name) segments.push(`"${field.relation.name}"`);
119
- segments.push(`fields: [${fieldsStr}]`);
120
- segments.push(`references: [${refsStr}]`);
121
- if (explicitOnDelete) segments.push(`onDelete: ${explicitOnDelete}`);
122
- if (explicitOnUpdate) segments.push(`onUpdate: ${explicitOnUpdate}`);
123
-
124
- parts.push(`@relation(${segments.join(", ")})`);
125
- } else if (field.relation.name) {
126
- parts.push(`@relation("${field.relation.name}")`);
127
- }
128
-
129
- return indent({ text: parts.join(" ") });
130
- }
131
-
132
- /**
133
- * Returns the onDelete action only if it differs from the Prisma default for
134
- * this relation's optionality. Returns `undefined` when the action is the
135
- * default (in which case we omit it from the printed `@relation(...)`).
136
- */
137
- function nonDefaultOnDelete(params: { field: RelationField }): CascadeAction | undefined {
138
- const { field } = params;
139
- if (field.relation.onDelete === undefined) return undefined;
140
- const effectiveDefault = resolveOnDelete({
141
- explicit: undefined,
142
- isRequired: field.isRequired,
143
- });
144
- return field.relation.onDelete === effectiveDefault ? undefined : field.relation.onDelete;
145
- }
146
-
147
- function nonDefaultOnUpdate(params: { field: RelationField }): CascadeAction | undefined {
148
- const { field } = params;
149
- if (field.relation.onUpdate === undefined) return undefined;
150
- const effectiveDefault = resolveOnUpdate({ explicit: undefined });
151
- return field.relation.onUpdate === effectiveDefault ? undefined : field.relation.onUpdate;
152
- }
153
-
154
- function printField(params: { field: Field }): string {
155
- switch (params.field.kind) {
156
- case "scalar":
157
- return printScalarField({ field: params.field });
158
- case "enum":
159
- return printEnumField({ field: params.field });
160
- case "relation":
161
- return printRelationField({ field: params.field });
162
- }
163
- }
164
-
165
- // ─── Model Printer ────────────────────────────────────────────────
166
-
167
- function printModel(params: { model: Model }): string {
168
- const { model } = params;
169
- const lines: string[] = [];
170
-
171
- lines.push(`model ${model.name} {`);
172
-
173
- // Fields
174
- for (const field of model.fields) {
175
- lines.push(printField({ field }));
176
- }
177
-
178
- // Block-level attributes
179
- if (model.dbName !== model.name) {
180
- lines.push("");
181
- lines.push(indent({ text: `@@map("${model.dbName}")` }));
182
- }
183
-
184
- // Composite primary key
185
- if (model.primaryKey.isComposite) {
186
- const pkFields = model.primaryKey.fields.join(", ");
187
- lines.push("");
188
- lines.push(indent({ text: `@@id([${pkFields}])` }));
189
- }
190
-
191
- // Multi-column unique constraints
192
- for (const uc of model.uniqueConstraints) {
193
- if (uc.fields.length > 1) {
194
- const fields = uc.fields.join(", ");
195
- lines.push(indent({ text: `@@unique([${fields}])` }));
196
- }
197
- }
198
-
199
- // Indexes
200
- for (const idx of model.indexes) {
201
- const fields = idx.fields.join(", ");
202
- lines.push(indent({ text: `@@index([${fields}])` }));
203
- }
204
-
205
- lines.push("}");
206
- return lines.join("\n");
207
- }
208
-
209
- // ─── Enum Printer ─────────────────────────────────────────────────
210
-
211
- function printEnum(params: { enumDef: Enum }): string {
212
- const { enumDef } = params;
213
- const lines: string[] = [];
214
-
215
- lines.push(`enum ${enumDef.name} {`);
216
-
217
- for (const val of enumDef.values) {
218
- let line = indent({ text: val.name });
219
- if (val.dbName && val.dbName !== val.name) {
220
- line += ` @map("${val.dbName}")`;
221
- }
222
- lines.push(line);
223
- }
224
-
225
- if (enumDef.dbName && enumDef.dbName !== enumDef.name) {
226
- lines.push("");
227
- lines.push(indent({ text: `@@map("${enumDef.dbName}")` }));
228
- }
229
-
230
- lines.push("}");
231
- return lines.join("\n");
232
- }
233
-
234
- // ─── Main Entry Point ─────────────────────────────────────────────
235
-
236
- export function printSchema(params: { schema: Schema }): string {
237
- const { schema } = params;
238
- const sections: string[] = [];
239
-
240
- // Datasource block
241
- sections.push(
242
- `datasource db {\n` +
243
- ` provider = "postgresql"\n` +
244
- ` url = env("DATABASE_URL")\n` +
245
- `}`
246
- );
247
-
248
- // Enums
249
- for (const enumDef of schema.enums) {
250
- sections.push(printEnum({ enumDef }));
251
- }
252
-
253
- // Models
254
- for (const model of schema.models) {
255
- sections.push(printModel({ model }));
256
- }
257
-
258
- return sections.join("\n\n") + "\n";
259
- }
package/src/snapshot.ts DELETED
@@ -1,141 +0,0 @@
1
- /**
2
- * Snapshot Management — Schema IR → JSON + Journal
3
- *
4
- * Handles reading/writing JSON snapshots of the Schema IR
5
- * and maintaining the migration journal.
6
- */
7
-
8
- import type { Schema } from "@vibeorm/parser";
9
- import { validateSchema, formatValidationErrors } from "@vibeorm/parser";
10
- import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from "fs";
11
- import { join } from "path";
12
-
13
- // ─── Types ────────────────────────────────────────────────────────
14
-
15
- export type JournalEntry = {
16
- idx: number;
17
- timestamp: string;
18
- name: string;
19
- checksum: string;
20
- };
21
-
22
- export type Journal = {
23
- version: number;
24
- entries: JournalEntry[];
25
- };
26
-
27
- // ─── Snapshot I/O ─────────────────────────────────────────────────
28
-
29
- export function saveSnapshot(params: {
30
- schema: Schema;
31
- migrationsDir: string;
32
- timestamp: string;
33
- }): void {
34
- const { schema, migrationsDir, timestamp } = params;
35
- const metaDir = join(migrationsDir, "meta");
36
-
37
- if (!existsSync(metaDir)) {
38
- mkdirSync(metaDir, { recursive: true });
39
- }
40
-
41
- const snapshotPath = join(metaDir, `${timestamp}_snapshot.json`);
42
- writeFileSync(snapshotPath, JSON.stringify(schema, null, 2), "utf-8");
43
- }
44
-
45
- export function loadLatestSnapshot(params: { migrationsDir: string }): Schema | null {
46
- const { migrationsDir } = params;
47
- const metaDir = join(migrationsDir, "meta");
48
-
49
- if (!existsSync(metaDir)) return null;
50
-
51
- const files = readdirSync(metaDir)
52
- .filter((f: string) => f.endsWith("_snapshot.json"))
53
- .sort();
54
-
55
- if (files.length === 0) return null;
56
-
57
- const latestFile = files[files.length - 1]!;
58
- const content = readFileSync(join(metaDir, latestFile), "utf-8");
59
- const schema = JSON.parse(content) as Schema;
60
-
61
- // Validate deserialized snapshot for integrity
62
- const validation = validateSchema({ schema });
63
- if (!validation.valid) {
64
- console.warn(`Warning: Snapshot "${latestFile}" has validation issues:`);
65
- console.warn(formatValidationErrors({ result: validation }));
66
- }
67
-
68
- return schema;
69
- }
70
-
71
- export function loadSnapshot(params: {
72
- migrationsDir: string;
73
- timestamp: string;
74
- }): Schema | null {
75
- const { migrationsDir, timestamp } = params;
76
- const snapshotPath = join(migrationsDir, "meta", `${timestamp}_snapshot.json`);
77
-
78
- if (!existsSync(snapshotPath)) return null;
79
-
80
- const content = readFileSync(snapshotPath, "utf-8");
81
- const schema = JSON.parse(content) as Schema;
82
-
83
- // Validate deserialized snapshot for integrity
84
- const validation = validateSchema({ schema });
85
- if (!validation.valid) {
86
- console.warn(`Warning: Snapshot "${timestamp}" has validation issues:`);
87
- console.warn(formatValidationErrors({ result: validation }));
88
- }
89
-
90
- return schema;
91
- }
92
-
93
- // ─── Journal I/O ──────────────────────────────────────────────────
94
-
95
- export function loadJournal(params: { migrationsDir: string }): Journal {
96
- const { migrationsDir } = params;
97
- const journalPath = join(migrationsDir, "meta", "_journal.json");
98
-
99
- if (!existsSync(journalPath)) {
100
- return { version: 1, entries: [] };
101
- }
102
-
103
- const content = readFileSync(journalPath, "utf-8");
104
- return JSON.parse(content) as Journal;
105
- }
106
-
107
- export function saveJournal(params: {
108
- migrationsDir: string;
109
- journal: Journal;
110
- }): void {
111
- const { migrationsDir, journal } = params;
112
- const metaDir = join(migrationsDir, "meta");
113
-
114
- if (!existsSync(metaDir)) {
115
- mkdirSync(metaDir, { recursive: true });
116
- }
117
-
118
- const journalPath = join(metaDir, "_journal.json");
119
- writeFileSync(journalPath, JSON.stringify(journal, null, 2), "utf-8");
120
- }
121
-
122
- // ─── Timestamp Generation ─────────────────────────────────────────
123
-
124
- export function generateTimestamp(): string {
125
- const now = new Date();
126
- const year = now.getFullYear();
127
- const month = String(now.getMonth() + 1).padStart(2, "0");
128
- const day = String(now.getDate()).padStart(2, "0");
129
- const hours = String(now.getHours()).padStart(2, "0");
130
- const minutes = String(now.getMinutes()).padStart(2, "0");
131
- const seconds = String(now.getSeconds()).padStart(2, "0");
132
- return `${year}${month}${day}${hours}${minutes}${seconds}`;
133
- }
134
-
135
- // ─── Checksum ─────────────────────────────────────────────────────
136
-
137
- export function computeChecksum(params: { content: string }): string {
138
- const hasher = new Bun.CryptoHasher("sha256");
139
- hasher.update(params.content);
140
- return `sha256:${hasher.digest("hex")}`;
141
- }
package/src/sql-utils.ts DELETED
@@ -1,45 +0,0 @@
1
- /**
2
- * SQL Utility Functions
3
- *
4
- * Shared SQL parsing/splitting utilities used by migration runner and CLI.
5
- */
6
-
7
- /**
8
- * Split a SQL string into individual statements, handling DO $$ blocks correctly.
9
- * DO $$ ... END $$; blocks contain semicolons that are NOT statement terminators.
10
- */
11
- export function splitSqlStatements(params: { sql: string }): string[] {
12
- const { sql } = params;
13
- const statements: string[] = [];
14
- let current = "";
15
- let inDollarBlock = false;
16
-
17
- const lines = sql.split("\n");
18
- for (const line of lines) {
19
- const trimmed = line.trim();
20
-
21
- if (trimmed.startsWith("DO $$") || trimmed.startsWith("DO $")) {
22
- inDollarBlock = true;
23
- }
24
-
25
- current += (current ? "\n" : "") + line;
26
-
27
- if (inDollarBlock && trimmed.startsWith("END $$;")) {
28
- inDollarBlock = false;
29
- statements.push(current.trim());
30
- current = "";
31
- continue;
32
- }
33
-
34
- if (!inDollarBlock && trimmed.endsWith(";")) {
35
- const stmt = current.trim();
36
- if (stmt) statements.push(stmt);
37
- current = "";
38
- }
39
- }
40
-
41
- const remainder = current.trim();
42
- if (remainder) statements.push(remainder);
43
-
44
- return statements.filter((s) => s.length > 0);
45
- }
package/src/types.ts DELETED
@@ -1,13 +0,0 @@
1
- /**
2
- * Shared types for the @vibeorm/migrate package.
3
- */
4
-
5
- /**
6
- * Generic SQL executor function.
7
- * Accepts a SQL string and optional parameter values, returns rows.
8
- * This decouples all modules from bun:sql for testability.
9
- */
10
- export type SqlExecutor = (params: {
11
- text: string;
12
- values?: unknown[];
13
- }) => Promise<Record<string, unknown>[]>;