@sedrino/db-schema 0.1.1 → 0.1.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.
- package/README.md +62 -6
- package/dist/cli.js +1904 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +365 -85
- package/dist/index.js +1108 -187
- package/dist/index.js.map +1 -1
- package/docs/cli.md +93 -0
- package/docs/expressions-and-transforms.md +165 -0
- package/docs/index.md +5 -2
- package/docs/migrations.md +183 -3
- package/docs/planning-and-apply.md +200 -0
- package/docs/relations.md +130 -0
- package/docs/schema-document.md +62 -0
- package/package.json +3 -2
- package/src/apply.ts +67 -0
- package/src/cli.ts +105 -7
- package/src/drizzle.ts +348 -1
- package/src/index.ts +38 -1
- package/src/migration.ts +315 -3
- package/src/operations.ts +278 -0
- package/src/planner.ts +7 -190
- package/src/project.ts +157 -1
- package/src/sql-expression.ts +123 -0
- package/src/sqlite.ts +150 -9
- package/src/transforms.ts +94 -0
- package/src/utils.ts +54 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Drizzle Relations
|
|
2
|
+
|
|
3
|
+
`@sedrino/db-schema` can emit Drizzle soft relations alongside generated table definitions.
|
|
4
|
+
|
|
5
|
+
## What is inferred today
|
|
6
|
+
|
|
7
|
+
From a field reference like this:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
t.reference("accountId", {
|
|
11
|
+
references: {
|
|
12
|
+
table: "account",
|
|
13
|
+
field: "accountId",
|
|
14
|
+
onDelete: "cascade",
|
|
15
|
+
},
|
|
16
|
+
}).required();
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
the generator emits:
|
|
20
|
+
|
|
21
|
+
- a forward `r.one.account(...)` relation on the source table
|
|
22
|
+
- a reverse relation on the target table
|
|
23
|
+
- `r.many.sourceTable(...)` by default
|
|
24
|
+
- `r.one.sourceTable(...)` when the foreign key field is unique
|
|
25
|
+
|
|
26
|
+
## One-to-many example
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import {
|
|
30
|
+
compileSchemaToDrizzle,
|
|
31
|
+
compileSchemaToDrizzleRelations,
|
|
32
|
+
createMigration,
|
|
33
|
+
materializeSchema,
|
|
34
|
+
} from "@sedrino/db-schema";
|
|
35
|
+
|
|
36
|
+
const { schema } = materializeSchema({
|
|
37
|
+
migrations: [
|
|
38
|
+
createMigration(
|
|
39
|
+
{
|
|
40
|
+
id: "2026-04-08-001",
|
|
41
|
+
name: "Create account and contact tables",
|
|
42
|
+
},
|
|
43
|
+
(m) => {
|
|
44
|
+
m.createTable("account", (t) => {
|
|
45
|
+
t.id("accountId", { prefix: "acct" });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
m.createTable("contact", (t) => {
|
|
49
|
+
t.id("contactId", { prefix: "ct" });
|
|
50
|
+
t.belongsTo("account", { required: true });
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
),
|
|
54
|
+
],
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const relationsSource = compileSchemaToDrizzleRelations(schema);
|
|
58
|
+
const fullDrizzleSource = compileSchemaToDrizzle(schema);
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Duplicate relations
|
|
62
|
+
|
|
63
|
+
If a table has multiple foreign keys to the same target table, the generator emits Drizzle relation aliases automatically.
|
|
64
|
+
|
|
65
|
+
Example:
|
|
66
|
+
|
|
67
|
+
- `post.authorId -> user.userId`
|
|
68
|
+
- `post.reviewerId -> user.userId`
|
|
69
|
+
|
|
70
|
+
becomes:
|
|
71
|
+
|
|
72
|
+
- `post.author`
|
|
73
|
+
- `post.reviewer`
|
|
74
|
+
- `user.authorPosts`
|
|
75
|
+
- `user.reviewerPosts`
|
|
76
|
+
|
|
77
|
+
with matching Drizzle aliases.
|
|
78
|
+
|
|
79
|
+
## Many-to-many junction tables
|
|
80
|
+
|
|
81
|
+
`createJunctionTable(...)` enables inferred Drizzle `through(...)` relations for simple many-to-many join tables.
|
|
82
|
+
|
|
83
|
+
Example:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import {
|
|
87
|
+
compileSchemaToDrizzleRelations,
|
|
88
|
+
createMigration,
|
|
89
|
+
materializeSchema,
|
|
90
|
+
} from "@sedrino/db-schema";
|
|
91
|
+
|
|
92
|
+
const { schema } = materializeSchema({
|
|
93
|
+
migrations: [
|
|
94
|
+
createMigration(
|
|
95
|
+
{
|
|
96
|
+
id: "2026-04-08-001",
|
|
97
|
+
name: "Create users, groups, and memberships",
|
|
98
|
+
},
|
|
99
|
+
(m) => {
|
|
100
|
+
m.createTable("user", (t) => {
|
|
101
|
+
t.id("userId", { prefix: "usr" });
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
m.createTable("group", (t) => {
|
|
105
|
+
t.id("groupId", { prefix: "grp" });
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
m.createJunctionTable("userGroupMembership", {
|
|
109
|
+
left: { table: "user" },
|
|
110
|
+
right: { table: "group" },
|
|
111
|
+
});
|
|
112
|
+
},
|
|
113
|
+
),
|
|
114
|
+
],
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const relationsSource = compileSchemaToDrizzleRelations(schema);
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
This emits:
|
|
121
|
+
|
|
122
|
+
- direct `r.one.user(...)` / `r.one.group(...)` relations on the junction table
|
|
123
|
+
- a `user.groups` relation using `through(...)`
|
|
124
|
+
- a `group.users` relation using `through(...)`
|
|
125
|
+
|
|
126
|
+
## Current limits
|
|
127
|
+
|
|
128
|
+
- junction-table inference expects a simple 2-column join table
|
|
129
|
+
- composite primary keys are still not modeled directly in the schema document
|
|
130
|
+
- advanced polymorphic `where` relations are not emitted yet
|
package/docs/schema-document.md
CHANGED
|
@@ -18,12 +18,38 @@ type DatabaseSchemaDocument = {
|
|
|
18
18
|
};
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
+
## Important helpers
|
|
22
|
+
|
|
23
|
+
The schema module exports a few small helpers around the document:
|
|
24
|
+
|
|
25
|
+
- `createEmptySchema()`
|
|
26
|
+
- `parseSchemaDocument(...)`
|
|
27
|
+
- `validateSchemaDocument(...)`
|
|
28
|
+
- `assertValidSchemaDocument(...)`
|
|
29
|
+
- `schemaHash(...)`
|
|
30
|
+
- `findTable(...)`
|
|
31
|
+
- `findField(...)`
|
|
32
|
+
|
|
33
|
+
The package also exports the underlying runtime schemas:
|
|
34
|
+
|
|
35
|
+
- `schemaDocumentSchema`
|
|
36
|
+
- `tableSpecSchema`
|
|
37
|
+
- `fieldSpecSchema`
|
|
38
|
+
- `logicalTypeSpecSchema`
|
|
39
|
+
- `storageSpecSchema`
|
|
40
|
+
- `defaultSpecSchema`
|
|
41
|
+
- `fieldReferenceSpecSchema`
|
|
42
|
+
- `indexSpecSchema`
|
|
43
|
+
- `uniqueSpecSchema`
|
|
44
|
+
- `foreignKeyActionSchema`
|
|
45
|
+
|
|
21
46
|
## Important v1 ideas
|
|
22
47
|
|
|
23
48
|
- The schema document is the materialized current-state snapshot.
|
|
24
49
|
- Migrations are still the authored change history.
|
|
25
50
|
- Logical types are higher-level than physical column types.
|
|
26
51
|
- Storage strategies make the SQLite representation explicit.
|
|
52
|
+
- Foreign-key references can also drive generated Drizzle soft relations.
|
|
27
53
|
|
|
28
54
|
## Example
|
|
29
55
|
|
|
@@ -46,3 +72,39 @@ That compiles to:
|
|
|
46
72
|
- runtime semantics: `Temporal.Instant`
|
|
47
73
|
- physical storage: `INTEGER`
|
|
48
74
|
- Drizzle helper: `temporalInstantEpochMs("created_at")`
|
|
75
|
+
|
|
76
|
+
## Validation example
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import {
|
|
80
|
+
assertValidSchemaDocument,
|
|
81
|
+
schemaDocumentSchema,
|
|
82
|
+
schemaHash,
|
|
83
|
+
validateSchemaDocument,
|
|
84
|
+
} from "@sedrino/db-schema";
|
|
85
|
+
|
|
86
|
+
const result = validateSchemaDocument({
|
|
87
|
+
version: 1,
|
|
88
|
+
dialect: "sqlite",
|
|
89
|
+
schemaId: "crm",
|
|
90
|
+
tables: [],
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
if (result.issues.length > 0) {
|
|
94
|
+
throw new Error(`Schema is invalid: ${JSON.stringify(result.issues, null, 2)}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const schema = assertValidSchemaDocument(result.schema);
|
|
98
|
+
const hash = schemaHash(schema);
|
|
99
|
+
const parsedAgain = schemaDocumentSchema.parse(schema);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Lookup example
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { createEmptySchema, findField, findTable } from "@sedrino/db-schema";
|
|
106
|
+
|
|
107
|
+
const schema = createEmptySchema("crm");
|
|
108
|
+
const accountTable = findTable(schema, "account");
|
|
109
|
+
const createdAtField = accountTable ? findField(accountTable, "createdAt") : null;
|
|
110
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sedrino/db-schema",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"./package.json": "./package.json"
|
|
15
15
|
},
|
|
16
16
|
"bin": {
|
|
17
|
-
"sedrino-db": "./
|
|
17
|
+
"sedrino-db": "./dist/cli.js"
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
20
|
"dist",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"clean": "rm -rf dist",
|
|
27
27
|
"build": "bun run clean && tsup",
|
|
28
28
|
"test": "bun test",
|
|
29
|
+
"test:coverage": "bun test --coverage",
|
|
29
30
|
"typecheck": "bunx -p @typescript/native-preview tsc --noEmit",
|
|
30
31
|
"test:all": "bun test && bunx -p @typescript/native-preview tsc --noEmit"
|
|
31
32
|
},
|
package/src/apply.ts
CHANGED
|
@@ -21,6 +21,19 @@ export type ApplyMigrationsResult = {
|
|
|
21
21
|
currentSchemaHash: string;
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
+
export type MigrationStatusResult = {
|
|
25
|
+
localMigrationIds: string[];
|
|
26
|
+
appliedMigrationIds: string[];
|
|
27
|
+
pendingMigrationIds: string[];
|
|
28
|
+
unexpectedDatabaseMigrationIds: string[];
|
|
29
|
+
schemaHash: {
|
|
30
|
+
local: string;
|
|
31
|
+
database: string | null;
|
|
32
|
+
driftDetected: boolean;
|
|
33
|
+
};
|
|
34
|
+
metadataTablesPresent: boolean;
|
|
35
|
+
};
|
|
36
|
+
|
|
24
37
|
export type LibsqlConnectionOptions = {
|
|
25
38
|
url: string;
|
|
26
39
|
authToken?: string;
|
|
@@ -126,6 +139,44 @@ export async function listAppliedMigrations(client: Client) {
|
|
|
126
139
|
}));
|
|
127
140
|
}
|
|
128
141
|
|
|
142
|
+
export async function inspectMigrationStatus(args: {
|
|
143
|
+
client?: Client;
|
|
144
|
+
connection?: LibsqlConnectionOptions;
|
|
145
|
+
migrations: MigrationDefinition[];
|
|
146
|
+
baseSchema?: DatabaseSchemaDocument;
|
|
147
|
+
}) {
|
|
148
|
+
const client = args.client ?? createLibsqlClient(assertConnection(args.connection));
|
|
149
|
+
const metadataTablesPresent = await hasMetadataTables(client);
|
|
150
|
+
const appliedRows = metadataTablesPresent ? await listAppliedMigrations(client) : [];
|
|
151
|
+
const appliedIds = new Set(appliedRows.map((row) => row.migrationId));
|
|
152
|
+
const localMigrationIds = args.migrations.map((migration) => migration.meta.id);
|
|
153
|
+
const pendingMigrationIds = localMigrationIds.filter((id) => !appliedIds.has(id));
|
|
154
|
+
const unexpectedDatabaseMigrationIds = appliedRows
|
|
155
|
+
.map((row) => row.migrationId)
|
|
156
|
+
.filter((id) => !localMigrationIds.includes(id));
|
|
157
|
+
const appliedLocalMigrations = args.migrations.filter((migration) => appliedIds.has(migration.meta.id));
|
|
158
|
+
const expectedCurrent = materializeSchema({
|
|
159
|
+
baseSchema: args.baseSchema,
|
|
160
|
+
migrations: appliedLocalMigrations,
|
|
161
|
+
}).schema;
|
|
162
|
+
const currentState = metadataTablesPresent ? await getSchemaState(client) : null;
|
|
163
|
+
const localSchemaHash = schemaHash(expectedCurrent);
|
|
164
|
+
const databaseSchemaHash = currentState?.schemaHash ?? null;
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
localMigrationIds,
|
|
168
|
+
appliedMigrationIds: appliedRows.map((row) => row.migrationId),
|
|
169
|
+
pendingMigrationIds,
|
|
170
|
+
unexpectedDatabaseMigrationIds,
|
|
171
|
+
schemaHash: {
|
|
172
|
+
local: localSchemaHash,
|
|
173
|
+
database: databaseSchemaHash,
|
|
174
|
+
driftDetected: databaseSchemaHash !== null && databaseSchemaHash !== localSchemaHash,
|
|
175
|
+
},
|
|
176
|
+
metadataTablesPresent,
|
|
177
|
+
} satisfies MigrationStatusResult;
|
|
178
|
+
}
|
|
179
|
+
|
|
129
180
|
export async function getSchemaState(client: Client): Promise<SchemaStateRow | null> {
|
|
130
181
|
const result = await client.execute(
|
|
131
182
|
`SELECT schema_hash, schema_json
|
|
@@ -172,6 +223,22 @@ async function ensureMetadataTables(client: Client) {
|
|
|
172
223
|
);
|
|
173
224
|
}
|
|
174
225
|
|
|
226
|
+
async function hasMetadataTables(client: Client) {
|
|
227
|
+
const result = await client.execute({
|
|
228
|
+
sql: `SELECT name FROM sqlite_master
|
|
229
|
+
WHERE type = 'table' AND name IN (?, ?)`,
|
|
230
|
+
args: [MIGRATIONS_TABLE, STATE_TABLE],
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const names = new Set(
|
|
234
|
+
(result.rows as Array<Record<string, unknown>>)
|
|
235
|
+
.map((row) => getString(row.name))
|
|
236
|
+
.filter((value): value is string => value !== null),
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
return names.has(MIGRATIONS_TABLE) && names.has(STATE_TABLE);
|
|
240
|
+
}
|
|
241
|
+
|
|
175
242
|
async function executePlan(client: Client, plan: PlannedMigration) {
|
|
176
243
|
const appliedAt = Date.now();
|
|
177
244
|
const statements: Array<string | { sql: string; args: SqlValue[] }> = [
|
package/src/cli.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
|
|
3
1
|
import path from "node:path";
|
|
4
2
|
import { env, exit } from "node:process";
|
|
5
3
|
import { compileSchemaToDrizzle } from "./drizzle";
|
|
6
|
-
import { applyMigrations, createLibsqlClient } from "./apply";
|
|
4
|
+
import { applyMigrations, createLibsqlClient, inspectMigrationStatus } from "./apply";
|
|
7
5
|
import {
|
|
6
|
+
createMigrationScaffold,
|
|
8
7
|
materializeProjectMigrations,
|
|
9
8
|
resolveDbProjectLayout,
|
|
9
|
+
validateDbProject,
|
|
10
10
|
writeDrizzleSchema,
|
|
11
11
|
writeSchemaSnapshot,
|
|
12
12
|
} from "./project";
|
|
@@ -29,9 +29,18 @@ async function main() {
|
|
|
29
29
|
case "migrate plan":
|
|
30
30
|
await handleMigratePlan(args);
|
|
31
31
|
return;
|
|
32
|
+
case "migrate create":
|
|
33
|
+
await handleMigrateCreate(args);
|
|
34
|
+
return;
|
|
32
35
|
case "migrate apply":
|
|
33
36
|
await handleMigrateApply(args);
|
|
34
37
|
return;
|
|
38
|
+
case "migrate validate":
|
|
39
|
+
await handleMigrateValidate(args);
|
|
40
|
+
return;
|
|
41
|
+
case "migrate status":
|
|
42
|
+
await handleMigrateStatus(args);
|
|
43
|
+
return;
|
|
35
44
|
case "schema print":
|
|
36
45
|
await handleSchemaPrint(args);
|
|
37
46
|
return;
|
|
@@ -46,9 +55,11 @@ async function main() {
|
|
|
46
55
|
async function handleMigratePlan(args: ParsedArgs) {
|
|
47
56
|
const layout = resolveLayoutFromArgs(args);
|
|
48
57
|
const { schema, plans } = await materializeProjectMigrations(layout);
|
|
58
|
+
const snapshotPath = path.resolve(getStringOption(args, "snapshot") ?? layout.snapshotPath);
|
|
59
|
+
const drizzleOutputPath = path.resolve(getStringOption(args, "drizzle-out") ?? layout.drizzlePath);
|
|
49
60
|
|
|
50
|
-
await writeSchemaSnapshot(schema,
|
|
51
|
-
await writeDrizzleSchema(schema,
|
|
61
|
+
await writeSchemaSnapshot(schema, snapshotPath);
|
|
62
|
+
await writeDrizzleSchema(schema, drizzleOutputPath);
|
|
52
63
|
|
|
53
64
|
const warnings = plans.flatMap((plan) =>
|
|
54
65
|
plan.sql.warnings.map((warning) => `${plan.migrationId}: ${warning}`),
|
|
@@ -56,8 +67,8 @@ async function handleMigratePlan(args: ParsedArgs) {
|
|
|
56
67
|
const statementCount = plans.reduce((total, plan) => total + plan.sql.statements.length, 0);
|
|
57
68
|
|
|
58
69
|
console.log(`Planned ${plans.length} migration(s)`);
|
|
59
|
-
console.log(`Schema snapshot: ${
|
|
60
|
-
console.log(`Drizzle output: ${
|
|
70
|
+
console.log(`Schema snapshot: ${snapshotPath}`);
|
|
71
|
+
console.log(`Drizzle output: ${drizzleOutputPath}`);
|
|
61
72
|
console.log(`SQL statements: ${statementCount}`);
|
|
62
73
|
console.log(`Schema hash: ${plans.at(-1)?.toSchemaHash ?? "schema_00000000"}`);
|
|
63
74
|
|
|
@@ -80,6 +91,21 @@ async function handleMigratePlan(args: ParsedArgs) {
|
|
|
80
91
|
}
|
|
81
92
|
}
|
|
82
93
|
|
|
94
|
+
async function handleMigrateCreate(args: ParsedArgs) {
|
|
95
|
+
const layout = resolveLayoutFromArgs(args);
|
|
96
|
+
const rawName = args.positionals.slice(2).join(" ").trim();
|
|
97
|
+
|
|
98
|
+
if (!rawName) {
|
|
99
|
+
throw new Error("Missing migration name. Usage: sedrino-db migrate create <name> [--dir db]");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const created = await createMigrationScaffold(layout, rawName);
|
|
103
|
+
|
|
104
|
+
console.log(`Created migration: ${created.filePath}`);
|
|
105
|
+
console.log(`Migration id: ${created.migrationId}`);
|
|
106
|
+
console.log(`Migration name: ${created.migrationName}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
83
109
|
async function handleMigrateApply(args: ParsedArgs) {
|
|
84
110
|
const layout = resolveLayoutFromArgs(args);
|
|
85
111
|
const { migrations } = await materializeProjectMigrations(layout);
|
|
@@ -117,6 +143,75 @@ async function handleMigrateApply(args: ParsedArgs) {
|
|
|
117
143
|
}
|
|
118
144
|
}
|
|
119
145
|
|
|
146
|
+
async function handleMigrateValidate(args: ParsedArgs) {
|
|
147
|
+
const layout = resolveLayoutFromArgs(args);
|
|
148
|
+
const result = await validateDbProject(layout);
|
|
149
|
+
|
|
150
|
+
console.log(`Validated ${result.migrations.length} migration(s)`);
|
|
151
|
+
console.log(`Schema hash: ${result.plans.at(-1)?.toSchemaHash ?? "schema_00000000"}`);
|
|
152
|
+
console.log(`Snapshot up to date: ${result.artifacts.snapshotUpToDate ? "yes" : "no"}`);
|
|
153
|
+
console.log(`Drizzle output up to date: ${result.artifacts.drizzleUpToDate ? "yes" : "no"}`);
|
|
154
|
+
|
|
155
|
+
if (result.warnings.length > 0) {
|
|
156
|
+
console.log("");
|
|
157
|
+
console.log("Warnings:");
|
|
158
|
+
for (const warning of result.warnings) console.log(`- ${warning}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const hasIssues =
|
|
162
|
+
result.warnings.length > 0 ||
|
|
163
|
+
!result.artifacts.snapshotUpToDate ||
|
|
164
|
+
!result.artifacts.drizzleUpToDate;
|
|
165
|
+
|
|
166
|
+
if (hasIssues) {
|
|
167
|
+
exit(1);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function handleMigrateStatus(args: ParsedArgs) {
|
|
172
|
+
const layout = resolveLayoutFromArgs(args);
|
|
173
|
+
const { migrations, plans } = await materializeProjectMigrations(layout);
|
|
174
|
+
console.log(`Local migrations: ${migrations.length}`);
|
|
175
|
+
console.log(`Local schema hash: ${plans.at(-1)?.toSchemaHash ?? "schema_00000000"}`);
|
|
176
|
+
|
|
177
|
+
const url = getStringOption(args, "url") ?? env.LIBSQL_URL;
|
|
178
|
+
const authToken = getStringOption(args, "auth-token") ?? env.LIBSQL_AUTH_TOKEN;
|
|
179
|
+
if (!url) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const status = await inspectMigrationStatus({
|
|
184
|
+
client: createLibsqlClient({ url, authToken }),
|
|
185
|
+
migrations,
|
|
186
|
+
baseSchema: undefined,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
console.log(`Metadata tables present: ${status.metadataTablesPresent ? "yes" : "no"}`);
|
|
190
|
+
console.log(`Applied in database: ${status.appliedMigrationIds.length}`);
|
|
191
|
+
console.log(`Pending locally: ${status.pendingMigrationIds.length}`);
|
|
192
|
+
console.log(`Unexpected in database: ${status.unexpectedDatabaseMigrationIds.length}`);
|
|
193
|
+
console.log(
|
|
194
|
+
`Database schema hash: ${status.schemaHash.database ?? (status.metadataTablesPresent ? "missing" : "none")}`,
|
|
195
|
+
);
|
|
196
|
+
console.log(`Drift detected: ${status.schemaHash.driftDetected ? "yes" : "no"}`);
|
|
197
|
+
|
|
198
|
+
if (status.pendingMigrationIds.length > 0) {
|
|
199
|
+
console.log("");
|
|
200
|
+
console.log("Pending:");
|
|
201
|
+
for (const migrationId of status.pendingMigrationIds) console.log(`- ${migrationId}`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (status.unexpectedDatabaseMigrationIds.length > 0) {
|
|
205
|
+
console.log("");
|
|
206
|
+
console.log("Unexpected in database:");
|
|
207
|
+
for (const migrationId of status.unexpectedDatabaseMigrationIds) console.log(`- ${migrationId}`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (status.schemaHash.driftDetected || status.unexpectedDatabaseMigrationIds.length > 0) {
|
|
211
|
+
exit(1);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
120
215
|
async function handleSchemaPrint(args: ParsedArgs) {
|
|
121
216
|
const layout = resolveLayoutFromArgs(args);
|
|
122
217
|
const { schema } = await materializeProjectMigrations(layout);
|
|
@@ -190,8 +285,11 @@ function printHelp() {
|
|
|
190
285
|
console.log(`sedrino-db
|
|
191
286
|
|
|
192
287
|
Usage:
|
|
288
|
+
sedrino-db migrate create <name> [--dir db]
|
|
193
289
|
sedrino-db migrate plan [--dir db] [--sql] [--snapshot path] [--drizzle-out path]
|
|
194
290
|
sedrino-db migrate apply --url <libsql-url> [--auth-token token] [--dir db]
|
|
291
|
+
sedrino-db migrate validate [--dir db]
|
|
292
|
+
sedrino-db migrate status [--dir db] [--url <libsql-url>] [--auth-token token]
|
|
195
293
|
sedrino-db schema print [--dir db]
|
|
196
294
|
sedrino-db schema drizzle [--dir db] [--out path]
|
|
197
295
|
|