EdgeAIG's picture
download
raw
8.96 kB
var __rewriteRelativeImportExtension = this && this.__rewriteRelativeImportExtension || function (path, preserveJsx) {
if (typeof path === "string" && /^\.\.?\//.test(path)) {
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : d + ext + "." + cm.toLowerCase() + "js";
});
}
return path;
};
/**
* Runs SQL migrations with `SqlClient`.
*
* A migrator loads numbered migration effects, records completed ids in a
* migrations table, and runs only pending migrations in a transaction. It
* creates the table when needed, detects duplicate ids, treats concurrent runs
* as locked, and can dump the schema after successful migrations.
*
* @since 4.0.0
*/
import * as Arr from "../../Array.js";
import * as Data from "../../Data.js";
import * as Effect from "../../Effect.js";
import { FileSystem } from "../../FileSystem.js";
import { pipe } from "../../Function.js";
import * as Option from "../../Option.js";
import * as Order from "../../Order.js";
import * as Client from "./SqlClient.js";
/**
* Error raised while loading, validating, locking, or running SQL migrations.
*
* @category errors
* @since 4.0.0
*/
export class MigrationError extends /*#__PURE__*/Data.TaggedError("MigrationError") {}
/**
* Creates a migrator that ensures the migrations table exists, runs pending
* migrations in a transaction, and optionally dumps the schema after successful
* migrations.
*
* @category constructors
* @since 4.0.0
*/
export const make = ({
dumpSchema = () => Effect.void
}) => ({
loader,
schemaDirectory,
table = "effect_sql_migrations"
}) => Effect.gen(function* () {
const sql = yield* Client.SqlClient;
const ensureMigrationsTable = sql.onDialectOrElse({
mssql: () => sql`IF OBJECT_ID(N'${sql.literal(table)}', N'U') IS NULL
CREATE TABLE ${sql(table)} (
migration_id INT NOT NULL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
created_at DATETIME NOT NULL DEFAULT GETDATE()
)`,
mysql: () => sql`CREATE TABLE IF NOT EXISTS ${sql(table)} (
migration_id INTEGER UNSIGNED NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
name VARCHAR(255) NOT NULL,
PRIMARY KEY (migration_id)
)`,
pg: () => Effect.catch(sql`select ${table}::regclass`, () => sql`CREATE TABLE ${sql(table)} (
migration_id integer primary key,
created_at timestamp with time zone not null default now(),
name text not null
)`),
orElse: () => sql`CREATE TABLE IF NOT EXISTS ${sql(table)} (
migration_id integer PRIMARY KEY NOT NULL,
created_at datetime NOT NULL DEFAULT current_timestamp,
name VARCHAR(255) NOT NULL
)`
});
const insertMigrations = rows => sql`INSERT INTO ${sql(table)} ${sql.insert(rows.map(([migration_id, name]) => ({
migration_id,
name
})))}`.withoutTransform;
const latestMigration = Effect.map(sql`SELECT migration_id, name, created_at FROM ${sql(table)} ORDER BY migration_id DESC`.withoutTransform, _ => Option.map(Option.fromNullishOr(_[0]), ({
created_at,
migration_id,
name
}) => ({
id: migration_id,
name,
createdAt: created_at
})));
const loadMigration = ([id, name, load]) => Effect.catchDefect(load, _ => Effect.fail(new MigrationError({
kind: "ImportError",
message: `Could not import migration "${id}_${name}"\n\n${_}`
}))).pipe(Effect.flatMap(_ => Effect.isEffect(_) ? Effect.succeed(_) : _.default ? Effect.succeed(_.default?.default ?? _.default) : Effect.fail(new MigrationError({
kind: "ImportError",
message: `Default export not found for migration "${id}_${name}"`
}))), Effect.filterOrFail(Effect.isEffect, () => new MigrationError({
kind: "ImportError",
message: `Default export was not an Effect for migration "${id}_${name}"`
})));
const runMigration = (id, name, effect) => Effect.catch(effect, error => Effect.die(new MigrationError({
cause: error,
kind: "Failed",
message: `Migration "${id}_${name}" failed`
})));
// === run
const run = Effect.gen(function* () {
yield* sql.onDialectOrElse({
pg: () => sql`LOCK TABLE ${sql(table)} IN ACCESS EXCLUSIVE MODE`,
orElse: () => Effect.void
});
const [latestMigrationId, current] = yield* Effect.all([Effect.map(latestMigration, Option.match({
onNone: () => 0,
onSome: _ => _.id
})), loader]);
if (new Set(current.map(([id]) => id)).size !== current.length) {
return yield* new MigrationError({
kind: "Duplicates",
message: "Found duplicate migration id's"
});
}
const required = [];
for (const resolved of current) {
const [currentId, currentName] = resolved;
if (currentId <= latestMigrationId) {
continue;
}
required.push([currentId, currentName, yield* loadMigration(resolved)]);
}
if (required.length > 0) {
yield* pipe(insertMigrations(required.map(([id, name]) => [id, name])), Effect.mapError(error => isConstraintConflict(error) ? new MigrationError({
kind: "Locked",
message: "Migrations already running"
}) : error));
}
yield* Effect.forEach(required, ([id, name, effect]) => Effect.logDebug(`Running migration`).pipe(Effect.flatMap(() => runMigration(id, name, effect)), Effect.annotateLogs("migration_id", String(id)), Effect.annotateLogs("migration_name", name), Effect.withSpan(`Migrator ${id}_${name}`)), {
discard: true
});
yield* pipe(latestMigration, Effect.flatMap(Option.match({
onNone: () => Effect.logDebug(`Migrations complete`),
onSome: _ => Effect.logDebug(`Migrations complete`).pipe(Effect.annotateLogs("latest_migration_id", _.id.toString()), Effect.annotateLogs("latest_migration_name", _.name))
})));
return required.map(([id, name]) => [id, name]);
});
yield* ensureMigrationsTable;
const completed = yield* pipe(sql.withTransaction(run), Effect.catchTag("MigrationError", _ => _.kind === "Locked" ? Effect.as(Effect.logDebug(_.message), []) : Effect.fail(_)));
if (schemaDirectory && completed.length > 0) {
yield* dumpSchema(`${schemaDirectory}/_schema.sql`, table).pipe(Effect.catchCause(cause => Effect.logInfo("Could not dump schema", cause)));
}
return completed;
});
const migrationOrder = /*#__PURE__*/Order.make(([a], [b]) => Order.Number(a, b));
const isConstraintConflict = error => error.reason._tag === "ConstraintError" || error.reason._tag === "UniqueViolation";
/**
* Creates a migration loader from a glob record of dynamic import functions,
* parsing files named `<id>_<name>.js`, `<id>_<name>.ts`,
* `<id>_<name>.mjs`, or `<id>_<name>.mts` and sorting migrations by id.
*
* @category loaders
* @since 4.0.0
*/
export const fromGlob = migrations => pipe(Object.keys(migrations), Arr.flatMapNullishOr(_ => _.match(/^(?:.*\/)?(\d+)_([^.]+)\.(js|ts|mjs|mts)$/)), Arr.map(([key, id, name]) => [Number(id), name, Effect.promise(() => migrations[key]())]), Arr.sort(migrationOrder), Effect.succeed);
/**
* Creates a migration loader from a Babel-style glob record, parsing keys such
* as `_<id>_<name>Js`, `_<id>_<name>Ts`, `_<id>_<name>Mjs`, or
* `_<id>_<name>Mts` and sorting migrations by id.
*
* @category loaders
* @since 4.0.0
*/
export const fromBabelGlob = migrations => pipe(Object.keys(migrations), Arr.flatMapNullishOr(_ => _.match(/^_(\d+)_([^.]+?)(Js|Ts|Mjs|Mts)?$/)), Arr.map(([key, id, name]) => [Number(id), name, Effect.succeed(migrations[key])]), Arr.sort(migrationOrder), Effect.succeed);
/**
* Creates a migration loader from a record of migration effects keyed by
* `<id>_<name>`, sorted by migration id.
*
* @category loaders
* @since 4.0.0
*/
export const fromRecord = migrations => pipe(Object.keys(migrations), Arr.flatMapNullishOr(_ => _.match(/^(\d+)_(.+)$/)), Arr.map(([key, id, name]) => [Number(id), name, Effect.succeed(migrations[key])]), Arr.sort(migrationOrder), Effect.succeed);
/**
* Creates a migration loader that reads a directory with `FileSystem`, imports
* files named `<id>_<name>.js`, `<id>_<name>.ts`,
* `<id>_<name>.mjs`, or `<id>_<name>.mts`, and sorts migrations by id.
*
* @category loaders
* @since 4.0.0
*/
export const fromFileSystem = /*#__PURE__*/Effect.fnUntraced(function* (directory) {
const Fs = yield* FileSystem;
const files = yield* Effect.mapError(Fs.readDirectory(directory), cause => new MigrationError({
kind: "Failed",
cause,
message: "Failed to read migrations directory"
}));
return files.map(file => Option.fromNullishOr(file.match(/^(?:.*\/)?(\d+)_([^.]+)\.(js|ts|mjs|mts)$/))).flatMap(Option.match({
onNone: () => [],
onSome: ([basename, id, name]) => [[Number(id), name, Effect.promise(() => import(__rewriteRelativeImportExtension(/* @vite-ignore */
/* webpackIgnore: true */
`${directory}/${basename}`)))]]
})).sort(([a], [b]) => a - b);
});
//# sourceMappingURL=Migrator.js.map

Xet Storage Details

Size:
8.96 kB
·
Xet hash:
dc027d4b9518b6a2179647d6cd7bfe99079fddd929b721e8aabb938ebd244f9b

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.