History

Use Schema.Struct for parsing

Griffin Evans committed 6 months ago

Commit 66420b81 parent 4c9c3eb1 file · +42 −37Browse files

Unified · Split

apps/local/src/server/executor.ts+42−37
@@ -1,7 +1,7 @@
1import { Database } from "bun:sqlite";1import { Database } from "bun:sqlite";
2import { drizzle } from "drizzle-orm/bun-sqlite";2import { drizzle } from "drizzle-orm/bun-sqlite";
3import { migrate } from "drizzle-orm/bun-sqlite/migrator";3import { migrate } from "drizzle-orm/bun-sqlite/migrator";
4import { Context, Data, Effect, Layer, ManagedRuntime } from "effect";4import { Context, Data, Effect, Layer, ManagedRuntime, Schema } from "effect";
5import { createHash } from "node:crypto";5import { createHash } from "node:crypto";
6import * as fs from "node:fs";6import * as fs from "node:fs";
7import { homedir, tmpdir } from "node:os";7import { homedir, tmpdir } from "node:os";
@@ -165,21 +165,25 @@ export const readAppliedDrizzleMigrationHashes = (sqlite: Database): ReadonlyArr · 157 unmodified lines
165 .map((row) => row.hash);165 .map((row) => row.hash);
166};166};
167 167
168interface DrizzleJournal {168const DrizzleJournal = Schema.Struct({
169 readonly entries: ReadonlyArray<{169 entries: Schema.Array(
170 readonly idx: number;170 Schema.Struct({
171 readonly tag: string;171 idx: Schema.Number,
172 }>;172 tag: Schema.String,
173}173 }),
174 ),
175});
176
177const decodeDrizzleJournal = Schema.decodeUnknownSync(Schema.fromJsonString(DrizzleJournal));
174 178
175export const readBundledDrizzleMigrationHashes = (179export const readBundledDrizzleMigrationHashes = (
176 migrationsFolder: string,180 migrationsFolder: string,
177): ReadonlyArray<string> => {181): ReadonlyArray<string> => {
178 // Keep this in sync with drizzle-orm/src/migrator.ts: Drizzle hashes the raw182 // Keep this in sync with drizzle-orm/src/migrator.ts: Drizzle hashes the raw
179 // migration file contents before splitting on statement breakpoints.183 // migration file contents before splitting on statement breakpoints.
180 const journal = JSON.parse(184 const journal = decodeDrizzleJournal(
181 fs.readFileSync(join(migrationsFolder, "meta", "_journal.json")).toString(),185 fs.readFileSync(join(migrationsFolder, "meta", "_journal.json")).toString(),
182 ) as DrizzleJournal;186 );
183 187
184 return [...journal.entries]188 return [...journal.entries]
185 .sort((left, right) => left.idx - right.idx)189 .sort((left, right) => left.idx - right.idx)
@@ -207,36 +211,37 @@ export const checkDrizzleMigrationCompatibility = (input: { · 21 unmodified lines
207 readonly sqlite: Database;211 readonly sqlite: Database;
208 readonly dbPath: string;212 readonly dbPath: string;
209 readonly migrationsFolder: string;213 readonly migrationsFolder: string;
210}): void => {214}): Effect.Effect<void, LocalDatabaseSchemaTooNew | LocalDatabaseMigrationHistoryMismatch> =>
211 // Before running migrations, ensure the DB history is a prefix of the215 Effect.gen(function* () {
212 // migrations bundled with this binary. This catches newer or divergent schemas216 // Before running migrations, ensure the DB history is a prefix of the
213 // before startup reaches arbitrary schema-dependent queries.217 // migrations bundled with this binary. This catches newer or divergent schemas
214 if (!drizzleMigrationsTableExists(input.sqlite)) return;218 // before startup reaches arbitrary schema-dependent queries.
215 219 if (!drizzleMigrationsTableExists(input.sqlite)) return;
216 const applied = readAppliedDrizzleMigrationHashes(input.sqlite);220
217 const bundled = readBundledDrizzleMigrationHashes(input.migrationsFolder);221 const applied = readAppliedDrizzleMigrationHashes(input.sqlite);
218 222 const bundled = readBundledDrizzleMigrationHashes(input.migrationsFolder);
219 if (applied.length > bundled.length) {223
220 throw new LocalDatabaseSchemaTooNew({224 if (applied.length > bundled.length) {
221 message: schemaTooNewMessage(input.dbPath),225 return yield* new LocalDatabaseSchemaTooNew({
222 dbPath: input.dbPath,226 message: schemaTooNewMessage(input.dbPath),
223 appliedMigrationCount: applied.length,
224 knownMigrationCount: bundled.length,
225 });
226 }
227
228 for (let index = 0; index < applied.length; index += 1) {
229 if (applied[index] !== bundled[index]) {
230 throw new LocalDatabaseMigrationHistoryMismatch({
231 message: migrationHistoryMismatchMessage(input.dbPath),
232 dbPath: input.dbPath,227 dbPath: input.dbPath,
233 migrationIndex: index,228 appliedMigrationCount: applied.length,
234 appliedHash: applied[index],229 knownMigrationCount: bundled.length,
235 knownHash: bundled[index],
236 });230 });
237 }231 }
238 }232
239};233 for (let index = 0; index < applied.length; index += 1) {
234 if (applied[index] !== bundled[index]) {
235 return yield* new LocalDatabaseMigrationHistoryMismatch({
236 message: migrationHistoryMismatchMessage(input.dbPath),
237 dbPath: input.dbPath,
238 migrationIndex: index,
239 appliedHash: applied[index],
240 knownHash: bundled[index],
241 });
242 }
243 }
244 });
240 245
241const createLocalExecutorLayer = () => {246const createLocalExecutorLayer = () => {
242 const { path: dbPath, legacySecrets } = resolveDbPath();247 const { path: dbPath, legacySecrets } = resolveDbPath();
@@ -247,7 +252,7 @@ const createLocalExecutorLayer = () => { · 4 unmodified lines
247 Effect.sync(() => new Database(dbPath)),252 Effect.sync(() => new Database(dbPath)),
248 (conn) => Effect.sync(() => conn.close()),253 (conn) => Effect.sync(() => conn.close()),
249 );254 );
250 checkDrizzleMigrationCompatibility({255 yield* checkDrizzleMigrationCompatibility({
251 sqlite,256 sqlite,
252 dbPath,257 dbPath,
253 migrationsFolder: MIGRATIONS_FOLDER,258 migrationsFolder: MIGRATIONS_FOLDER,