@cloudflare/actors
Version:
An easier way to build with Cloudflare Durable Objects
121 lines • 8.01 kB
JavaScript
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