UNPKG

@cloudflare/actors

Version:

An easier way to build with Cloudflare Durable Objects

121 lines 8.01 kB
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var _SQLSchemaMigrations_instances, _SQLSchemaMigrations__config, _SQLSchemaMigrations__migrations, _SQLSchemaMigrations__lastMigrationMonotonicId, _SQLSchemaMigrations__lastMigrationIDKeyName; /** * SQLSchemaMigrations is a simple class to manage your SQL migrations when using SQLite Durable Objects (DO). * * It accepts a config with the Durable Object `storage`, and a list of migrations. * This list of migrations should cover everything migration ran ever, not just new ones to run. * * Each migration is identified by a monotonically increasing identifier (`idMonotonicInc`). * Once the `runAll()` function has been called at least once, the migrations processed should never * change from that point on. Otherwise, if you change the SQL statement of an already ran migration * that change will not be applied. * * All SQL schema changes should be done with newly added migration entries in the given config, along * with a higher `idMonotonicInc`. * * Running `runAll()` multiple times is safe, and returns early if the migrations array has no changes, * therefore it's recommended to always run it before your Durable Object instance is going to read or * write into the SQLite database, or at least run it once in the constructor of your DO. */ export class SQLSchemaMigrations { constructor(config) { _SQLSchemaMigrations_instances.add(this); _SQLSchemaMigrations__config.set(this, void 0); _SQLSchemaMigrations__migrations.set(this, void 0); _SQLSchemaMigrations__lastMigrationMonotonicId.set(this, -1); __classPrivateFieldSet(this, _SQLSchemaMigrations__config, config, "f"); const migrations = [...config.migrations]; migrations.sort((a, b) => a.idMonotonicInc - b.idMonotonicInc); const idSeen = new Set(); migrations.forEach((m) => { if (m.idMonotonicInc < 0) { throw new Error(`migration ID cannot be negative: ${m.idMonotonicInc}`); } if (idSeen.has(m.idMonotonicInc)) { throw new Error(`duplicate migration ID detected: ${m.idMonotonicInc}`); } idSeen.add(m.idMonotonicInc); }); __classPrivateFieldSet(this, _SQLSchemaMigrations__migrations, migrations, "f"); // TODO Should we load the `_lastMigrationMonotonicId` from storage here? // Without loading it, `hasMigrationsToRun()` will always return true until `runAll()` runs once. // That's not bad per se, since everyone should `runAll()` as soon as they need their storage // to be writeable. // Also, if we want to do this, we cannot do it here, since we need to be within `async` function. :( } /** * This is a quick check based on the in memory tracker of last migration ran, * therefore this always returns `true` until `runAll` runs at least once. * @returns `true` if the `migrations` list provided has not been ran in full yet. */ hasMigrationsToRun() { if (!__classPrivateFieldGet(this, _SQLSchemaMigrations__migrations, "f").length) { return false; } return (__classPrivateFieldGet(this, _SQLSchemaMigrations__lastMigrationMonotonicId, "f") !== __classPrivateFieldGet(this, _SQLSchemaMigrations__migrations, "f")[__classPrivateFieldGet(this, _SQLSchemaMigrations__migrations, "f").length - 1].idMonotonicInc); } /** * Runs all the migrations that haven't already ran. The `idMonotonicInc` of each migration is used * to track which migrations ran or not. New migrations should always have higher `idMonotonicInc` * than older ones! * * @param sqlGen An optional callback function to generate the SQL statement of a given migration at runtime. * If the migration entry already has a valid `sql` statement this callback is NOT called. * @returns The numbers of rows read and written throughout the migration execution. */ async runAll(sqlGen) { const result = { rowsRead: 0, rowsWritten: 0, }; if (!this.hasMigrationsToRun()) { return result; } __classPrivateFieldSet(this, _SQLSchemaMigrations__lastMigrationMonotonicId, (await __classPrivateFieldGet(this, _SQLSchemaMigrations__config, "f").doStorage.get(__classPrivateFieldGet(this, _SQLSchemaMigrations_instances, "m", _SQLSchemaMigrations__lastMigrationIDKeyName).call(this))) ?? -1, "f"); // Skip all the applied ones. let idx = 0, sz = __classPrivateFieldGet(this, _SQLSchemaMigrations__migrations, "f").length; while (idx < sz && __classPrivateFieldGet(this, _SQLSchemaMigrations__migrations, "f")[idx].idMonotonicInc <= __classPrivateFieldGet(this, _SQLSchemaMigrations__lastMigrationMonotonicId, "f")) { idx += 1; } // Make sure we still have migrations to run. if (idx >= sz) { return result; } const doSql = __classPrivateFieldGet(this, _SQLSchemaMigrations__config, "f").doStorage.sql; const migrationsToRun = __classPrivateFieldGet(this, _SQLSchemaMigrations__migrations, "f").slice(idx); await __classPrivateFieldGet(this, _SQLSchemaMigrations__config, "f").doStorage.transaction(async () => { let _lastMigrationMonotonicId = __classPrivateFieldGet(this, _SQLSchemaMigrations__lastMigrationMonotonicId, "f"); migrationsToRun.forEach((migration) => { let query = migration.sql ?? sqlGen?.(migration.idMonotonicInc); if (!query) { throw new Error(`migration with neither 'sql' nor 'sqlGen' provided: ${migration.idMonotonicInc}`); } const cursor = doSql.exec(query); let _ = cursor.toArray(); result.rowsRead += cursor.rowsRead; result.rowsWritten += cursor.rowsWritten; _lastMigrationMonotonicId = migration.idMonotonicInc; }); __classPrivateFieldSet(this, _SQLSchemaMigrations__lastMigrationMonotonicId, _lastMigrationMonotonicId, "f"); await __classPrivateFieldGet(this, _SQLSchemaMigrations__config, "f").doStorage.put(__classPrivateFieldGet(this, _SQLSchemaMigrations_instances, "m", _SQLSchemaMigrations__lastMigrationIDKeyName).call(this), __classPrivateFieldGet(this, _SQLSchemaMigrations__lastMigrationMonotonicId, "f")); }); return result; } } _SQLSchemaMigrations__config = new WeakMap(), _SQLSchemaMigrations__migrations = new WeakMap(), _SQLSchemaMigrations__lastMigrationMonotonicId = new WeakMap(), _SQLSchemaMigrations_instances = new WeakSet(), _SQLSchemaMigrations__lastMigrationIDKeyName = function _SQLSchemaMigrations__lastMigrationIDKeyName() { return "__sql_migrations_lastID"; }; //# sourceMappingURL=sql-schema-migrations.js.map