@geekmidas/cli 10.0.0-alpha.87 → 10.0.0-alpha.88

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.
@@ -1 +1 @@
1
- {"version":3,"file":"databases-BY-pEImp.cjs","names":["readdir","extname","join","pathToFileURL","stat","readFile","join","migrationTargets","MIGRATIONS_ROOT","canonicalId","readdir","DATABASE_FOLDERS","provideKey","require","createRequire","pathToFileURL"],"sources":["../src/migrate/provider.ts","../src/migrate/databases.ts"],"sourcesContent":["/**\n * One construct's migrations and seeds, read from its folders.\n *\n * Two kinds of file, told apart by extension rather than by configuration: a\n * script exports `up` (and may export `down`) — or `seed(db, { stage })`, in\n * `seeds/` — and a\n * `.sql` file is run whole. The second is what makes a folder independent of\n * the client the app uses — Better Auth's schema is SQL whatever the\n * application queries with.\n */\n\nimport { readdir, readFile, stat } from 'node:fs/promises';\nimport { extname, join } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type { Kysely, sql } from 'kysely';\nimport type { Migration, MigrationProvider } from 'kysely/migration';\n\nconst SCRIPTS = new Set(['.ts', '.mts', '.js', '.mjs']);\n\n/** Two files in one folder that would be one migration. */\nexport class DuplicateMigration extends Error {\n\tconstructor(\n\t\treadonly folder: string,\n\t\treadonly migration: string,\n\t) {\n\t\tsuper(\n\t\t\t`${folder} has two migrations named '${migration}' — a script and a .sql file ` +\n\t\t\t\t`with the same name. A migration is recorded by name, so only one of ` +\n\t\t\t\t`them could ever run; rename or remove the other.`,\n\t\t);\n\t\tthis.name = 'DuplicateMigration';\n\t}\n}\n\n/** A script in a migrations folder that exports no `up`. */\nexport class MigrationHasNoUp extends Error {\n\tconstructor(readonly file: string) {\n\t\tsuper(\n\t\t\t`${file} is in a migrations folder but exports no \\`up\\` function. ` +\n\t\t\t\t`Export \\`async function up(db: Kysely<unknown>)\\`, or move the file ` +\n\t\t\t\t`out of the folder if it is not a migration.`,\n\t\t);\n\t\tthis.name = 'MigrationHasNoUp';\n\t}\n}\n\n/** A script in a seeds folder that exports no `seed`. */\nexport class SeedHasNoSeed extends Error {\n\tconstructor(readonly file: string) {\n\t\tsuper(\n\t\t\t`${file} is in a seeds folder but exports no \\`seed\\` function. ` +\n\t\t\t\t`Export \\`async function seed(db, { stage })\\`, or move the ` +\n\t\t\t\t`file out of the folder if it is not a seed.`,\n\t\t);\n\t\tthis.name = 'SeedHasNoSeed';\n\t}\n}\n\n/**\n * What a seed is handed beside its database.\n *\n * Seeds run on every stage, production included, so a seed that should write\n * something only somewhere — a demo tenant on `development`, never on\n * `production` — decides by the stage it is given.\n */\nexport interface SeedContext {\n\t/** The stage being seeded: the local one, `test`, or a deployed one. */\n\tstage: string;\n}\n\n/** One seed: what it is called, and what it does. */\nexport interface Seed {\n\tname: string;\n\trun: (db: Kysely<unknown>, context: SeedContext) => Promise<void>;\n}\n\n/**\n * The migration files in a folder, by name, without reading them.\n *\n * Also what `gkm dev` compares against a construct's history to report what\n * is pending, which is why it is separate from loading them.\n */\nexport async function migrationFiles(\n\tfolder: string,\n): Promise<Map<string, string>> {\n\tconst entries = await readdir(folder, { withFileTypes: true }).catch(\n\t\t(error: NodeJS.ErrnoException) => {\n\t\t\tif (error.code === 'ENOENT') return [];\n\t\t\tthrow error;\n\t\t},\n\t);\n\n\tconst files = new Map<string, string>();\n\tfor (const entry of entries) {\n\t\tif (!entry.isFile() || !isMigration(entry.name)) continue;\n\n\t\tconst name = entry.name.slice(0, -extname(entry.name).length);\n\t\tif (files.has(name)) throw new DuplicateMigration(folder, name);\n\n\t\tfiles.set(name, join(folder, entry.name));\n\t}\n\n\treturn files;\n}\n\n/** A Kysely provider over one construct's folder. */\nexport function folderProvider(\n\tfolder: string,\n\traw: typeof sql,\n): MigrationProvider {\n\treturn {\n\t\tasync getMigrations() {\n\t\t\tconst migrations: Record<string, Migration> = {};\n\n\t\t\tfor (const [name, file] of await migrationFiles(folder)) {\n\t\t\t\tmigrations[name] =\n\t\t\t\t\textname(file) === '.sql'\n\t\t\t\t\t\t? await sqlMigration(file, raw)\n\t\t\t\t\t\t: await scriptMigration(file);\n\t\t\t}\n\n\t\t\treturn migrations;\n\t\t},\n\t};\n}\n\n/**\n * The seeds in a folder, loaded, in name order.\n *\n * Every one runs on every pass — there is no history to consult — so the order\n * is the only thing between two seeds where one reads what another writes.\n * Name them to sort that way.\n */\nexport async function loadSeeds(\n\tfolder: string,\n\traw: typeof sql,\n): Promise<Seed[]> {\n\tconst files = [...(await migrationFiles(folder))].sort(([a], [b]) =>\n\t\ta < b ? -1 : a > b ? 1 : 0,\n\t);\n\n\tconst seeds: Seed[] = [];\n\tfor (const [name, file] of files) {\n\t\tseeds.push({\n\t\t\tname,\n\t\t\trun:\n\t\t\t\textname(file) === '.sql'\n\t\t\t\t\t? (await sqlMigration(file, raw)).up\n\t\t\t\t\t: await scriptSeed(file),\n\t\t});\n\t}\n\n\treturn seeds;\n}\n\n/**\n * A seed script, imported as it is on disk now.\n *\n * Keyed by its modification time: a seed is run again after it is edited, and\n * a process that has imported it once would otherwise run the old one.\n */\nasync function scriptSeed(file: string): Promise<Seed['run']> {\n\tconst url = pathToFileURL(file);\n\turl.searchParams.set('mtime', String((await stat(file)).mtimeMs));\n\tconst module = (await import(url.href)) as { seed?: unknown };\n\tif (typeof module.seed !== 'function') throw new SeedHasNoSeed(file);\n\n\treturn module.seed as Seed['run'];\n}\n\nfunction isMigration(file: string): boolean {\n\tif (file.endsWith('.d.ts')) return false;\n\tif (/\\.(spec|test)\\.[cm]?[jt]s$/.test(file)) return false;\n\n\tconst extension = extname(file);\n\treturn extension === '.sql' || SCRIPTS.has(extension);\n}\n\nasync function scriptMigration(file: string): Promise<Migration> {\n\tconst module = (await import(pathToFileURL(file).href)) as Partial<Migration>;\n\tif (typeof module.up !== 'function') throw new MigrationHasNoUp(file);\n\n\treturn {\n\t\tup: module.up,\n\t\t...(typeof module.down === 'function' ? { down: module.down } : {}),\n\t};\n}\n\n/**\n * A `.sql` file, run whole.\n *\n * One query rather than split on `;`: with no parameters, Postgres takes\n * several statements in one message, and splitting by hand breaks on the first\n * `;` inside a string or a function body. There is no `down` — a generated\n * schema is rolled back by a migration that says how, not by guessing.\n */\nasync function sqlMigration(file: string, raw: typeof sql): Promise<Migration> {\n\tconst statements = await readFile(file, 'utf-8');\n\n\treturn {\n\t\tup: async (db: Kysely<unknown>) => {\n\t\t\tawait raw.raw(statements).execute(db);\n\t\t},\n\t};\n}\n","/**\n * Bring each construct's schema up to its migrations folder.\n *\n * Which constructs, in what order, from which folder, is `migrationTargets`'\n * answer and nobody else's. What is decided here is only how one target is\n * run: as its owner, in its own schema, with a history of its own.\n */\n\nimport { readdir } from 'node:fs/promises';\nimport { createRequire } from 'node:module';\nimport { join } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport {\n\ttype ConstructManifest,\n\tcanonicalId,\n\tDATABASE_FOLDERS,\n\tMIGRATIONS_ROOT,\n\ttype MigrationTarget,\n\tmigrationTargets,\n\tprovideKey,\n} from '@geekmidas/manifest';\nimport type { Kysely } from 'kysely';\nimport type { Migrator } from 'kysely/migration';\nimport pg from 'pg';\nimport type { ConstructSource } from '../reconcile/discover';\nimport { folderProvider, loadSeeds, migrationFiles } from './provider';\n\nexport interface DatabasesOptions {\n\t/** The project root — where `db/` lives. */\n\troot: string;\n\tmanifest: ConstructManifest;\n\t/**\n\t * Where each construct was declared. A construct's migrations run with the\n\t * Kysely its own file resolves, which is the one its migrations import.\n\t */\n\tsources?: Readonly<Record<string, ConstructSource>>;\n\t/** The resolved environment: each construct's owner URL is read from it. */\n\tenv: Readonly<Record<string, string | undefined>>;\n\t/** One construct, by name. All of them when absent. */\n\tonly?: string;\n}\n\nexport interface MigrationRun {\n\ttarget: MigrationTarget;\n\t/** The migrations this run applied, in order. */\n\tapplied: string[];\n}\n\nexport interface SeedOptions extends DatabasesOptions {\n\t/** The stage being seeded, handed to every seed. */\n\tstage: string;\n}\n\nexport interface SeedRun {\n\ttarget: MigrationTarget;\n\t/** The seeds this run ran, in order — every one, every time. */\n\tseeded: string[];\n}\n\n/** The seeds a run would run for one construct, read without connecting. */\nexport interface PlannedSeeds {\n\ttarget: MigrationTarget;\n\t/** In the order they would run. */\n\tseeds: string[];\n}\n\nexport interface PendingMigrations {\n\ttarget: MigrationTarget;\n\tpending: string[];\n}\n\n/** A folder under `db/` that names no construct. */\nexport class UnknownMigrationFolder extends Error {\n\tconstructor(\n\t\treadonly folder: string,\n\t\treadonly known: readonly string[],\n\t) {\n\t\tsuper(\n\t\t\t`${folder} names no database construct, so its migrations would never ` +\n\t\t\t\t`run. ${known.length > 0 ? `The folders that do: ${known.join(', ')}.` : 'This project declares no database.'} ` +\n\t\t\t\t`Rename the folder after the construct it belongs to.`,\n\t\t);\n\t\tthis.name = 'UnknownMigrationFolder';\n\t}\n}\n\n/** A construct was asked for by a name that takes no migrations. */\nexport class NoSuchMigrationTarget extends Error {\n\tconstructor(\n\t\treadonly construct: string,\n\t\treadonly known: readonly string[],\n\t) {\n\t\tsuper(\n\t\t\t`'${construct}' is not a database construct. ` +\n\t\t\t\t(known.length > 0\n\t\t\t\t\t? `The ones with migrations: ${known.join(', ')}.`\n\t\t\t\t\t: 'This project declares no database.'),\n\t\t);\n\t\tthis.name = 'NoSuchMigrationTarget';\n\t}\n}\n\n/**\n * A migration left directly in a construct's folder, where nothing reads it.\n *\n * Migrations moved into `migrations/` so seeds could sit beside them. A file\n * still at the old level would otherwise never run, and nothing would say so.\n */\nexport class MigrationsOutsideFolder extends Error {\n\tconstructor(\n\t\treadonly folder: string,\n\t\treadonly files: readonly string[],\n\t) {\n\t\tsuper(\n\t\t\t`${folder} has ${files.join(', ')} directly inside it, where nothing ` +\n\t\t\t\t`runs them. Migrations live in ${folder}/migrations/ and seeds in ` +\n\t\t\t\t`${folder}/seeds/ — move ${files.length === 1 ? 'it' : 'them'} there.`,\n\t\t);\n\t\tthis.name = 'MigrationsOutsideFolder';\n\t}\n}\n\n/** A folder inside a construct's folder that is neither `migrations` nor `seeds`. */\nexport class UnknownDatabaseFolder extends Error {\n\tconstructor(readonly folder: string) {\n\t\tsuper(\n\t\t\t`${folder} is neither migrations/ nor seeds/, so nothing in it would ` +\n\t\t\t\t`ever run. A construct's folder holds those two; rename or remove it.`,\n\t\t);\n\t\tthis.name = 'UnknownDatabaseFolder';\n\t}\n}\n\n/** A seed failed; its own writes are rolled back, the ones before it stay. */\nexport class SeedFailed extends Error {\n\tconstructor(\n\t\treadonly construct: string,\n\t\treadonly seed: string,\n\t\toverride readonly cause: unknown,\n\t) {\n\t\tsuper(\n\t\t\t`${construct}: seed '${seed}' failed — ` +\n\t\t\t\t`${cause instanceof Error ? cause.message : String(cause)}. ` +\n\t\t\t\t`Its writes were rolled back; fix it and run it again.`,\n\t\t);\n\t\tthis.name = 'SeedFailed';\n\t}\n}\n\n/** A construct with roles has no owner URL to migrate with. */\nexport class NoOwnerCredential extends Error {\n\tconstructor(\n\t\treadonly construct: string,\n\t\treadonly key: string,\n\t) {\n\t\tsuper(\n\t\t\t`${construct} has no ${key} to migrate with. Migrations run as the owner ` +\n\t\t\t\t`role — the runtime one cannot create a table — and nothing ` +\n\t\t\t\t`resolved its URL. Is the stage reconciled?`,\n\t\t);\n\t\tthis.name = 'NoOwnerCredential';\n\t}\n}\n\n/** A migration failed; the ones before it in the run stay applied. */\nexport class MigrationFailed extends Error {\n\tconstructor(\n\t\treadonly construct: string,\n\t\treadonly migration: string | undefined,\n\t\toverride readonly cause: unknown,\n\t) {\n\t\tsuper(\n\t\t\t`${construct}: migration ${migration ? `'${migration}' ` : ''}failed — ` +\n\t\t\t\t`${cause instanceof Error ? cause.message : String(cause)}`,\n\t\t);\n\t\tthis.name = 'MigrationFailed';\n\t}\n}\n\n/** The project's Kysely could not be found from where a database is declared. */\nexport class KyselyNotFound extends Error {\n\tconstructor(readonly from: string) {\n\t\tsuper(\n\t\t\t`Could not resolve 'kysely' from ${from}. Migrations run with the ` +\n\t\t\t\t`project's own Kysely — install it where the database is declared.`,\n\t\t);\n\t\tthis.name = 'KyselyNotFound';\n\t}\n}\n\n/** Apply every pending migration, construct by construct, parents first. */\nexport async function migrateDatabases(\n\toptions: DatabasesOptions,\n): Promise<MigrationRun[]> {\n\tconst runs: MigrationRun[] = [];\n\n\tfor (const target of await targetsFor(options)) {\n\t\t// No folder, or an empty one, is nothing to do — and no reason to\n\t\t// connect, which a construct nobody has written a migration for yet\n\t\t// should not need.\n\t\tconst files = await migrationFiles(join(options.root, target.migrations));\n\t\tif (files.size === 0) {\n\t\t\truns.push({ target, applied: [] });\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst applied = await withMigrator(options, target, async (migrator) => {\n\t\t\tconst { error, results = [] } = await migrator.migrateToLatest();\n\t\t\tif (error) {\n\t\t\t\tconst failed = results.find((result) => result.status === 'Error');\n\t\t\t\tthrow new MigrationFailed(target.id, failed?.migrationName, error);\n\t\t\t}\n\n\t\t\treturn results\n\t\t\t\t.filter((result) => result.status === 'Success')\n\t\t\t\t.map((result) => result.migrationName);\n\t\t});\n\n\t\truns.push({ target, applied });\n\t}\n\n\treturn runs;\n}\n\n/**\n * Run every construct's seeds, parents first — each one, every time, on every\n * stage, production included. Each is handed the stage, so a seed decides\n * what belongs where.\n *\n * Seeds are reference data kept up to date by being run again, so there is no\n * history: a seed is an upsert, and a changed one is applied by the next run.\n * Each runs in its own transaction, as the construct's owner, so a failure\n * leaves nothing half-written. Assumes the schema is migrated —\n * {@link migrateAndSeed} is what makes that true.\n */\nexport async function seedDatabases(options: SeedOptions): Promise<SeedRun[]> {\n\tconst runs: SeedRun[] = [];\n\n\tfor (const target of await targetsFor(options)) {\n\t\tconst folder = join(options.root, target.seeds);\n\t\tif ((await migrationFiles(folder)).size === 0) {\n\t\t\truns.push({ target, seeded: [] });\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst seeded = await withOwner(options, target, async (db, kysely) => {\n\t\t\tconst names: string[] = [];\n\t\t\tfor (const seed of await loadSeeds(folder, kysely.sql)) {\n\t\t\t\ttry {\n\t\t\t\t\tawait db\n\t\t\t\t\t\t.transaction()\n\t\t\t\t\t\t.execute((trx) => seed.run(trx, { stage: options.stage }));\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthrow new SeedFailed(target.id, seed.name, error);\n\t\t\t\t}\n\t\t\t\tnames.push(seed.name);\n\t\t\t}\n\t\t\treturn names;\n\t\t});\n\n\t\truns.push({ target, seeded });\n\t}\n\n\treturn runs;\n}\n\n/**\n * The seeds {@link seedDatabases} would run, construct by construct, parents\n * first, in the order it would run them — read from the folders alone, with\n * no connection. What a dry run lists. Constructs with none are left out.\n */\nexport async function plannedSeeds(\n\toptions: Pick<DatabasesOptions, 'root' | 'manifest' | 'only'>,\n): Promise<PlannedSeeds[]> {\n\tconst planned: PlannedSeeds[] = [];\n\tfor (const target of await targetsFor(options)) {\n\t\tconst files = await migrationFiles(join(options.root, target.seeds));\n\t\tif (files.size === 0) continue;\n\t\tplanned.push({ target, seeds: [...files.keys()].sort() });\n\t}\n\treturn planned;\n}\n\n/**\n * Migrate, then seed: a seed only ever runs against the schema it was written\n * for.\n *\n * With `only`, the constructs it lives in are migrated too — a tenant's seed\n * cannot run before the database that owns its schema exists — and only it is\n * seeded.\n */\nexport async function migrateAndSeed(options: SeedOptions): Promise<{\n\tmigrations: MigrationRun[];\n\tseeds: SeedRun[];\n}> {\n\tconst migrations: MigrationRun[] = [];\n\tfor (const target of await withParents(options)) {\n\t\tmigrations.push(\n\t\t\t...(await migrateDatabases({ ...options, only: target.id })),\n\t\t);\n\t}\n\n\treturn { migrations, seeds: await seedDatabases(options) };\n}\n\n/** The targets `only` names, after the ones it lives in; all without it. */\nasync function withParents(\n\toptions: DatabasesOptions,\n): Promise<MigrationTarget[]> {\n\tconst chosen = await targetsFor(options);\n\tif (!options.only) return chosen;\n\n\tconst all = migrationTargets(options.manifest);\n\tconst ids = new Set<string>();\n\tfor (const target of chosen) {\n\t\tlet current: MigrationTarget | undefined = target;\n\t\twhile (current) {\n\t\t\tids.add(current.id);\n\t\t\tconst parent: string | undefined = current.of;\n\t\t\tcurrent = parent ? all.find((t) => t.id === parent) : undefined;\n\t\t}\n\t}\n\n\treturn all.filter((target) => ids.has(target.id));\n}\n\n/**\n * What each construct's folder has that its history does not — without\n * applying any of it. What `gkm dev` reports.\n */\nexport async function pendingMigrations(\n\toptions: DatabasesOptions,\n): Promise<PendingMigrations[]> {\n\tconst found: PendingMigrations[] = [];\n\n\tfor (const target of await targetsFor(options)) {\n\t\tconst files = await migrationFiles(join(options.root, target.migrations));\n\t\tif (files.size === 0) continue;\n\n\t\tconst pending = await withMigrator(options, target, async (migrator) =>\n\t\t\t(await migrator.getMigrations())\n\t\t\t\t.filter((migration) => migration.executedAt === undefined)\n\t\t\t\t.map((migration) => migration.name),\n\t\t);\n\n\t\tif (pending.length > 0) found.push({ target, pending });\n\t}\n\n\treturn found;\n}\n\n/**\n * The targets this run covers, with every folder under `db/` accounted for.\n *\n * A folder that names no construct is an error rather than something to skip:\n * a typo in its name is otherwise a migration that silently never runs.\n */\nasync function targetsFor(\n\toptions: Pick<DatabasesOptions, 'root' | 'manifest' | 'only'>,\n): Promise<MigrationTarget[]> {\n\tconst targets = migrationTargets(options.manifest);\n\tconst folders = targets.map((target) => target.folder);\n\n\tfor (const folder of await foldersIn(join(options.root, MIGRATIONS_ROOT))) {\n\t\tconst relative = `${MIGRATIONS_ROOT}/${folder}`;\n\t\tif (!folders.includes(relative)) {\n\t\t\tthrow new UnknownMigrationFolder(relative, folders);\n\t\t}\n\t\tawait checkLayout(options.root, relative);\n\t}\n\n\tif (!options.only) return targets;\n\n\tconst id = canonicalId(options.only);\n\tconst chosen = targets.filter((target) => target.id === id);\n\tif (chosen.length === 0) {\n\t\tthrow new NoSuchMigrationTarget(\n\t\t\toptions.only,\n\t\t\ttargets.map((target) => target.id),\n\t\t);\n\t}\n\n\treturn chosen;\n}\n\n/**\n * A construct's folder holds `migrations/` and `seeds/`, and nothing else —\n * anything beside them is a file or folder nothing would ever run.\n */\nasync function checkLayout(root: string, folder: string): Promise<void> {\n\tconst entries = await readdir(join(root, folder), { withFileTypes: true });\n\n\tconst loose = entries\n\t\t.filter((entry) => entry.isFile() && !entry.name.startsWith('.'))\n\t\t.map((entry) => entry.name);\n\tif (loose.length > 0) throw new MigrationsOutsideFolder(folder, loose);\n\n\tfor (const entry of entries) {\n\t\tif (\n\t\t\tentry.isDirectory() &&\n\t\t\t!(DATABASE_FOLDERS as readonly string[]).includes(entry.name)\n\t\t) {\n\t\t\tthrow new UnknownDatabaseFolder(`${folder}/${entry.name}`);\n\t\t}\n\t}\n}\n\nasync function foldersIn(dir: string): Promise<string[]> {\n\tconst entries = await readdir(dir, { withFileTypes: true }).catch(\n\t\t(error: NodeJS.ErrnoException) => {\n\t\t\tif (error.code === 'ENOENT') return [];\n\t\t\tthrow error;\n\t\t},\n\t);\n\n\treturn entries\n\t\t.filter((entry) => entry.isDirectory())\n\t\t.map((entry) => entry.name);\n}\n\n/**\n * A migrator for one target, connected as its owner.\n *\n * No `migrationTableSchema`: the owner role's `search_path` is pinned to the\n * construct's schema, so the history lands there — `app.kysely_migration` for\n * the database, `auth_database.kysely_migration` for its tenant — and two\n * constructs in one Postgres never share one.\n *\n * Unordered migrations are allowed. Files are timestamped, so a branch merged\n * after another's migration ran carries an older one; refusing it would make\n * every such merge a manual repair.\n */\nasync function withMigrator<T>(\n\toptions: DatabasesOptions,\n\ttarget: MigrationTarget,\n\trun: (migrator: Migrator) => Promise<T>,\n): Promise<T> {\n\treturn withOwner(options, target, (db, { Migrator, sql }) =>\n\t\trun(\n\t\t\tnew Migrator({\n\t\t\t\tdb,\n\t\t\t\tprovider: folderProvider(join(options.root, target.migrations), sql),\n\t\t\t\tallowUnorderedMigrations: true,\n\t\t\t}),\n\t\t),\n\t);\n}\n\n/**\n * One target's database, connected as its owner and closed afterwards — what\n * migrations and seeds both run as.\n */\nasync function withOwner<T>(\n\toptions: DatabasesOptions,\n\ttarget: MigrationTarget,\n\trun: (\n\t\tdb: Kysely<unknown>,\n\t\tkysely: Awaited<ReturnType<typeof kyselyFrom>>,\n\t) => Promise<T>,\n): Promise<T> {\n\tconst from =\n\t\toptions.sources?.[target.id]?.file ??\n\t\t(target.of ? options.sources?.[target.of]?.file : undefined) ??\n\t\tjoin(options.root, 'package.json');\n\tconst kysely = await kyselyFrom(from);\n\n\tconst db: Kysely<unknown> = new kysely.Kysely<unknown>({\n\t\tdialect: new kysely.PostgresDialect({\n\t\t\tpool: new pg.Pool(poolConfig(ownerUrl(options, target))),\n\t\t}),\n\t});\n\n\ttry {\n\t\treturn await run(db, kysely);\n\t} finally {\n\t\tawait db.destroy();\n\t}\n}\n\n/**\n * The owner's URL, with no fallback to the runtime one.\n *\n * The runtime role can create nothing, so falling back would only move the\n * failure to the first `CREATE TABLE` and make it read like a permissions\n * bug. The one exception is a database declared `roles: false`, where there is\n * no owner — both URLs are the master credential by design.\n */\nfunction ownerUrl(options: DatabasesOptions, target: MigrationTarget): string {\n\tconst key = provideKey(target.id, 'ownerUrl');\n\tconst owner = options.env[key];\n\tif (owner) return owner;\n\n\tconst database = options.manifest[target.of ?? target.id];\n\tif (database?.kind === 'database' && database.roles === false) {\n\t\tconst url = options.env[provideKey(target.id, 'url')];\n\t\tif (url) return url;\n\t}\n\n\tthrow new NoOwnerCredential(target.id, key);\n}\n\n/**\n * Pool settings for a URL, with `?search_path=` as the startup option Postgres\n * reads. Only a `roles: false` URL carries it — a role pins its own.\n */\nfunction poolConfig(url: string): pg.PoolConfig {\n\tconst parsed = new URL(url);\n\tconst searchPath = parsed.searchParams.get('search_path');\n\tif (!searchPath) return { connectionString: url };\n\n\tparsed.searchParams.delete('search_path');\n\treturn {\n\t\tconnectionString: parsed.toString(),\n\t\toptions: `-c search_path=${searchPath}`,\n\t};\n}\n\n/** Kysely as the project resolves it from `from`. */\nasync function kyselyFrom(from: string) {\n\tconst require = createRequire(from);\n\n\ttry {\n\t\tconst [main, migration] = await Promise.all([\n\t\t\timport(pathToFileURL(require.resolve('kysely')).href),\n\t\t\timport(pathToFileURL(require.resolve('kysely/migration')).href),\n\t\t]);\n\n\t\treturn {\n\t\t\tKysely: main.Kysely as typeof import('kysely').Kysely,\n\t\t\tPostgresDialect:\n\t\t\t\tmain.PostgresDialect as typeof import('kysely').PostgresDialect,\n\t\t\tsql: main.sql as typeof import('kysely').sql,\n\t\t\tMigrator:\n\t\t\t\tmigration.Migrator as typeof import('kysely/migration').Migrator,\n\t\t};\n\t} catch {\n\t\tthrow new KyselyNotFound(from);\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"databases-BY-pEImp.cjs","names":["readdir","extname","join","pathToFileURL","stat","readFile","join","migrationTargets","MIGRATIONS_ROOT","canonicalId","readdir","DATABASE_FOLDERS","provideKey","require","createRequire","pathToFileURL"],"sources":["../src/migrate/provider.ts","../src/migrate/databases.ts"],"sourcesContent":["/**\n * One construct's migrations and seeds, read from its folders.\n *\n * Two kinds of file, told apart by extension rather than by configuration: a\n * script exports `up` (and may export `down`) — or `seed(db, { stage })`, in\n * `seeds/` — and a\n * `.sql` file is run whole. The second is what makes a folder independent of\n * the client the app uses — Better Auth's schema is SQL whatever the\n * application queries with.\n */\n\nimport { readdir, readFile, stat } from 'node:fs/promises';\nimport { extname, join } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type { SeedContext } from '@geekmidas/constructs';\nimport type { Kysely, sql } from 'kysely';\nimport type { Migration, MigrationProvider } from 'kysely/migration';\n\nconst SCRIPTS = new Set(['.ts', '.mts', '.js', '.mjs']);\n\n/** Two files in one folder that would be one migration. */\nexport class DuplicateMigration extends Error {\n\tconstructor(\n\t\treadonly folder: string,\n\t\treadonly migration: string,\n\t) {\n\t\tsuper(\n\t\t\t`${folder} has two migrations named '${migration}' — a script and a .sql file ` +\n\t\t\t\t`with the same name. A migration is recorded by name, so only one of ` +\n\t\t\t\t`them could ever run; rename or remove the other.`,\n\t\t);\n\t\tthis.name = 'DuplicateMigration';\n\t}\n}\n\n/** A script in a migrations folder that exports no `up`. */\nexport class MigrationHasNoUp extends Error {\n\tconstructor(readonly file: string) {\n\t\tsuper(\n\t\t\t`${file} is in a migrations folder but exports no \\`up\\` function. ` +\n\t\t\t\t`Export \\`async function up(db: Kysely<unknown>)\\`, or move the file ` +\n\t\t\t\t`out of the folder if it is not a migration.`,\n\t\t);\n\t\tthis.name = 'MigrationHasNoUp';\n\t}\n}\n\n/** A script in a seeds folder that exports no `seed`. */\nexport class SeedHasNoSeed extends Error {\n\tconstructor(readonly file: string) {\n\t\tsuper(\n\t\t\t`${file} is in a seeds folder but exports no \\`seed\\` function. ` +\n\t\t\t\t`Export \\`async function seed(db, { stage })\\`, or move the ` +\n\t\t\t\t`file out of the folder if it is not a seed.`,\n\t\t);\n\t\tthis.name = 'SeedHasNoSeed';\n\t}\n}\n\n/**\n * What a seed is handed beside its database — `@geekmidas/constructs`'\n * `SeedContext`, whose `stage` is the union of the declared stages once\n * `.gkm/stages.d.ts` is generated.\n */\nexport type { SeedContext };\n\n/** One seed: what it is called, and what it does. */\nexport interface Seed {\n\tname: string;\n\trun: (db: Kysely<unknown>, context: SeedContext) => Promise<void>;\n}\n\n/**\n * The migration files in a folder, by name, without reading them.\n *\n * Also what `gkm dev` compares against a construct's history to report what\n * is pending, which is why it is separate from loading them.\n */\nexport async function migrationFiles(\n\tfolder: string,\n): Promise<Map<string, string>> {\n\tconst entries = await readdir(folder, { withFileTypes: true }).catch(\n\t\t(error: NodeJS.ErrnoException) => {\n\t\t\tif (error.code === 'ENOENT') return [];\n\t\t\tthrow error;\n\t\t},\n\t);\n\n\tconst files = new Map<string, string>();\n\tfor (const entry of entries) {\n\t\tif (!entry.isFile() || !isMigration(entry.name)) continue;\n\n\t\tconst name = entry.name.slice(0, -extname(entry.name).length);\n\t\tif (files.has(name)) throw new DuplicateMigration(folder, name);\n\n\t\tfiles.set(name, join(folder, entry.name));\n\t}\n\n\treturn files;\n}\n\n/** A Kysely provider over one construct's folder. */\nexport function folderProvider(\n\tfolder: string,\n\traw: typeof sql,\n): MigrationProvider {\n\treturn {\n\t\tasync getMigrations() {\n\t\t\tconst migrations: Record<string, Migration> = {};\n\n\t\t\tfor (const [name, file] of await migrationFiles(folder)) {\n\t\t\t\tmigrations[name] =\n\t\t\t\t\textname(file) === '.sql'\n\t\t\t\t\t\t? await sqlMigration(file, raw)\n\t\t\t\t\t\t: await scriptMigration(file);\n\t\t\t}\n\n\t\t\treturn migrations;\n\t\t},\n\t};\n}\n\n/**\n * The seeds in a folder, loaded, in name order.\n *\n * Every one runs on every pass — there is no history to consult — so the order\n * is the only thing between two seeds where one reads what another writes.\n * Name them to sort that way.\n */\nexport async function loadSeeds(\n\tfolder: string,\n\traw: typeof sql,\n): Promise<Seed[]> {\n\tconst files = [...(await migrationFiles(folder))].sort(([a], [b]) =>\n\t\ta < b ? -1 : a > b ? 1 : 0,\n\t);\n\n\tconst seeds: Seed[] = [];\n\tfor (const [name, file] of files) {\n\t\tseeds.push({\n\t\t\tname,\n\t\t\trun:\n\t\t\t\textname(file) === '.sql'\n\t\t\t\t\t? (await sqlMigration(file, raw)).up\n\t\t\t\t\t: await scriptSeed(file),\n\t\t});\n\t}\n\n\treturn seeds;\n}\n\n/**\n * A seed script, imported as it is on disk now.\n *\n * Keyed by its modification time: a seed is run again after it is edited, and\n * a process that has imported it once would otherwise run the old one.\n */\nasync function scriptSeed(file: string): Promise<Seed['run']> {\n\tconst url = pathToFileURL(file);\n\turl.searchParams.set('mtime', String((await stat(file)).mtimeMs));\n\tconst module = (await import(url.href)) as { seed?: unknown };\n\tif (typeof module.seed !== 'function') throw new SeedHasNoSeed(file);\n\n\treturn module.seed as Seed['run'];\n}\n\nfunction isMigration(file: string): boolean {\n\tif (file.endsWith('.d.ts')) return false;\n\tif (/\\.(spec|test)\\.[cm]?[jt]s$/.test(file)) return false;\n\n\tconst extension = extname(file);\n\treturn extension === '.sql' || SCRIPTS.has(extension);\n}\n\nasync function scriptMigration(file: string): Promise<Migration> {\n\tconst module = (await import(pathToFileURL(file).href)) as Partial<Migration>;\n\tif (typeof module.up !== 'function') throw new MigrationHasNoUp(file);\n\n\treturn {\n\t\tup: module.up,\n\t\t...(typeof module.down === 'function' ? { down: module.down } : {}),\n\t};\n}\n\n/**\n * A `.sql` file, run whole.\n *\n * One query rather than split on `;`: with no parameters, Postgres takes\n * several statements in one message, and splitting by hand breaks on the first\n * `;` inside a string or a function body. There is no `down` — a generated\n * schema is rolled back by a migration that says how, not by guessing.\n */\nasync function sqlMigration(file: string, raw: typeof sql): Promise<Migration> {\n\tconst statements = await readFile(file, 'utf-8');\n\n\treturn {\n\t\tup: async (db: Kysely<unknown>) => {\n\t\t\tawait raw.raw(statements).execute(db);\n\t\t},\n\t};\n}\n","/**\n * Bring each construct's schema up to its migrations folder.\n *\n * Which constructs, in what order, from which folder, is `migrationTargets`'\n * answer and nobody else's. What is decided here is only how one target is\n * run: as its owner, in its own schema, with a history of its own.\n */\n\nimport { readdir } from 'node:fs/promises';\nimport { createRequire } from 'node:module';\nimport { join } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport {\n\ttype ConstructManifest,\n\tcanonicalId,\n\tDATABASE_FOLDERS,\n\tMIGRATIONS_ROOT,\n\ttype MigrationTarget,\n\tmigrationTargets,\n\tprovideKey,\n} from '@geekmidas/manifest';\nimport type { Kysely } from 'kysely';\nimport type { Migrator } from 'kysely/migration';\nimport pg from 'pg';\nimport type { ConstructSource } from '../reconcile/discover';\nimport { folderProvider, loadSeeds, migrationFiles } from './provider';\n\nexport interface DatabasesOptions {\n\t/** The project root — where `db/` lives. */\n\troot: string;\n\tmanifest: ConstructManifest;\n\t/**\n\t * Where each construct was declared. A construct's migrations run with the\n\t * Kysely its own file resolves, which is the one its migrations import.\n\t */\n\tsources?: Readonly<Record<string, ConstructSource>>;\n\t/** The resolved environment: each construct's owner URL is read from it. */\n\tenv: Readonly<Record<string, string | undefined>>;\n\t/** One construct, by name. All of them when absent. */\n\tonly?: string;\n}\n\nexport interface MigrationRun {\n\ttarget: MigrationTarget;\n\t/** The migrations this run applied, in order. */\n\tapplied: string[];\n}\n\nexport interface SeedOptions extends DatabasesOptions {\n\t/** The stage being seeded, handed to every seed. */\n\tstage: string;\n}\n\nexport interface SeedRun {\n\ttarget: MigrationTarget;\n\t/** The seeds this run ran, in order — every one, every time. */\n\tseeded: string[];\n}\n\n/** The seeds a run would run for one construct, read without connecting. */\nexport interface PlannedSeeds {\n\ttarget: MigrationTarget;\n\t/** In the order they would run. */\n\tseeds: string[];\n}\n\nexport interface PendingMigrations {\n\ttarget: MigrationTarget;\n\tpending: string[];\n}\n\n/** A folder under `db/` that names no construct. */\nexport class UnknownMigrationFolder extends Error {\n\tconstructor(\n\t\treadonly folder: string,\n\t\treadonly known: readonly string[],\n\t) {\n\t\tsuper(\n\t\t\t`${folder} names no database construct, so its migrations would never ` +\n\t\t\t\t`run. ${known.length > 0 ? `The folders that do: ${known.join(', ')}.` : 'This project declares no database.'} ` +\n\t\t\t\t`Rename the folder after the construct it belongs to.`,\n\t\t);\n\t\tthis.name = 'UnknownMigrationFolder';\n\t}\n}\n\n/** A construct was asked for by a name that takes no migrations. */\nexport class NoSuchMigrationTarget extends Error {\n\tconstructor(\n\t\treadonly construct: string,\n\t\treadonly known: readonly string[],\n\t) {\n\t\tsuper(\n\t\t\t`'${construct}' is not a database construct. ` +\n\t\t\t\t(known.length > 0\n\t\t\t\t\t? `The ones with migrations: ${known.join(', ')}.`\n\t\t\t\t\t: 'This project declares no database.'),\n\t\t);\n\t\tthis.name = 'NoSuchMigrationTarget';\n\t}\n}\n\n/**\n * A migration left directly in a construct's folder, where nothing reads it.\n *\n * Migrations moved into `migrations/` so seeds could sit beside them. A file\n * still at the old level would otherwise never run, and nothing would say so.\n */\nexport class MigrationsOutsideFolder extends Error {\n\tconstructor(\n\t\treadonly folder: string,\n\t\treadonly files: readonly string[],\n\t) {\n\t\tsuper(\n\t\t\t`${folder} has ${files.join(', ')} directly inside it, where nothing ` +\n\t\t\t\t`runs them. Migrations live in ${folder}/migrations/ and seeds in ` +\n\t\t\t\t`${folder}/seeds/ — move ${files.length === 1 ? 'it' : 'them'} there.`,\n\t\t);\n\t\tthis.name = 'MigrationsOutsideFolder';\n\t}\n}\n\n/** A folder inside a construct's folder that is neither `migrations` nor `seeds`. */\nexport class UnknownDatabaseFolder extends Error {\n\tconstructor(readonly folder: string) {\n\t\tsuper(\n\t\t\t`${folder} is neither migrations/ nor seeds/, so nothing in it would ` +\n\t\t\t\t`ever run. A construct's folder holds those two; rename or remove it.`,\n\t\t);\n\t\tthis.name = 'UnknownDatabaseFolder';\n\t}\n}\n\n/** A seed failed; its own writes are rolled back, the ones before it stay. */\nexport class SeedFailed extends Error {\n\tconstructor(\n\t\treadonly construct: string,\n\t\treadonly seed: string,\n\t\toverride readonly cause: unknown,\n\t) {\n\t\tsuper(\n\t\t\t`${construct}: seed '${seed}' failed — ` +\n\t\t\t\t`${cause instanceof Error ? cause.message : String(cause)}. ` +\n\t\t\t\t`Its writes were rolled back; fix it and run it again.`,\n\t\t);\n\t\tthis.name = 'SeedFailed';\n\t}\n}\n\n/** A construct with roles has no owner URL to migrate with. */\nexport class NoOwnerCredential extends Error {\n\tconstructor(\n\t\treadonly construct: string,\n\t\treadonly key: string,\n\t) {\n\t\tsuper(\n\t\t\t`${construct} has no ${key} to migrate with. Migrations run as the owner ` +\n\t\t\t\t`role — the runtime one cannot create a table — and nothing ` +\n\t\t\t\t`resolved its URL. Is the stage reconciled?`,\n\t\t);\n\t\tthis.name = 'NoOwnerCredential';\n\t}\n}\n\n/** A migration failed; the ones before it in the run stay applied. */\nexport class MigrationFailed extends Error {\n\tconstructor(\n\t\treadonly construct: string,\n\t\treadonly migration: string | undefined,\n\t\toverride readonly cause: unknown,\n\t) {\n\t\tsuper(\n\t\t\t`${construct}: migration ${migration ? `'${migration}' ` : ''}failed — ` +\n\t\t\t\t`${cause instanceof Error ? cause.message : String(cause)}`,\n\t\t);\n\t\tthis.name = 'MigrationFailed';\n\t}\n}\n\n/** The project's Kysely could not be found from where a database is declared. */\nexport class KyselyNotFound extends Error {\n\tconstructor(readonly from: string) {\n\t\tsuper(\n\t\t\t`Could not resolve 'kysely' from ${from}. Migrations run with the ` +\n\t\t\t\t`project's own Kysely — install it where the database is declared.`,\n\t\t);\n\t\tthis.name = 'KyselyNotFound';\n\t}\n}\n\n/** Apply every pending migration, construct by construct, parents first. */\nexport async function migrateDatabases(\n\toptions: DatabasesOptions,\n): Promise<MigrationRun[]> {\n\tconst runs: MigrationRun[] = [];\n\n\tfor (const target of await targetsFor(options)) {\n\t\t// No folder, or an empty one, is nothing to do — and no reason to\n\t\t// connect, which a construct nobody has written a migration for yet\n\t\t// should not need.\n\t\tconst files = await migrationFiles(join(options.root, target.migrations));\n\t\tif (files.size === 0) {\n\t\t\truns.push({ target, applied: [] });\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst applied = await withMigrator(options, target, async (migrator) => {\n\t\t\tconst { error, results = [] } = await migrator.migrateToLatest();\n\t\t\tif (error) {\n\t\t\t\tconst failed = results.find((result) => result.status === 'Error');\n\t\t\t\tthrow new MigrationFailed(target.id, failed?.migrationName, error);\n\t\t\t}\n\n\t\t\treturn results\n\t\t\t\t.filter((result) => result.status === 'Success')\n\t\t\t\t.map((result) => result.migrationName);\n\t\t});\n\n\t\truns.push({ target, applied });\n\t}\n\n\treturn runs;\n}\n\n/**\n * Run every construct's seeds, parents first — each one, every time, on every\n * stage, production included. Each is handed the stage, so a seed decides\n * what belongs where.\n *\n * Seeds are reference data kept up to date by being run again, so there is no\n * history: a seed is an upsert, and a changed one is applied by the next run.\n * Each runs in its own transaction, as the construct's owner, so a failure\n * leaves nothing half-written. Assumes the schema is migrated —\n * {@link migrateAndSeed} is what makes that true.\n */\nexport async function seedDatabases(options: SeedOptions): Promise<SeedRun[]> {\n\tconst runs: SeedRun[] = [];\n\n\tfor (const target of await targetsFor(options)) {\n\t\tconst folder = join(options.root, target.seeds);\n\t\tif ((await migrationFiles(folder)).size === 0) {\n\t\t\truns.push({ target, seeded: [] });\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst seeded = await withOwner(options, target, async (db, kysely) => {\n\t\t\tconst names: string[] = [];\n\t\t\tfor (const seed of await loadSeeds(folder, kysely.sql)) {\n\t\t\t\ttry {\n\t\t\t\t\tawait db\n\t\t\t\t\t\t.transaction()\n\t\t\t\t\t\t.execute((trx) => seed.run(trx, { stage: options.stage }));\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthrow new SeedFailed(target.id, seed.name, error);\n\t\t\t\t}\n\t\t\t\tnames.push(seed.name);\n\t\t\t}\n\t\t\treturn names;\n\t\t});\n\n\t\truns.push({ target, seeded });\n\t}\n\n\treturn runs;\n}\n\n/**\n * The seeds {@link seedDatabases} would run, construct by construct, parents\n * first, in the order it would run them — read from the folders alone, with\n * no connection. What a dry run lists. Constructs with none are left out.\n */\nexport async function plannedSeeds(\n\toptions: Pick<DatabasesOptions, 'root' | 'manifest' | 'only'>,\n): Promise<PlannedSeeds[]> {\n\tconst planned: PlannedSeeds[] = [];\n\tfor (const target of await targetsFor(options)) {\n\t\tconst files = await migrationFiles(join(options.root, target.seeds));\n\t\tif (files.size === 0) continue;\n\t\tplanned.push({ target, seeds: [...files.keys()].sort() });\n\t}\n\treturn planned;\n}\n\n/**\n * Migrate, then seed: a seed only ever runs against the schema it was written\n * for.\n *\n * With `only`, the constructs it lives in are migrated too — a tenant's seed\n * cannot run before the database that owns its schema exists — and only it is\n * seeded.\n */\nexport async function migrateAndSeed(options: SeedOptions): Promise<{\n\tmigrations: MigrationRun[];\n\tseeds: SeedRun[];\n}> {\n\tconst migrations: MigrationRun[] = [];\n\tfor (const target of await withParents(options)) {\n\t\tmigrations.push(\n\t\t\t...(await migrateDatabases({ ...options, only: target.id })),\n\t\t);\n\t}\n\n\treturn { migrations, seeds: await seedDatabases(options) };\n}\n\n/** The targets `only` names, after the ones it lives in; all without it. */\nasync function withParents(\n\toptions: DatabasesOptions,\n): Promise<MigrationTarget[]> {\n\tconst chosen = await targetsFor(options);\n\tif (!options.only) return chosen;\n\n\tconst all = migrationTargets(options.manifest);\n\tconst ids = new Set<string>();\n\tfor (const target of chosen) {\n\t\tlet current: MigrationTarget | undefined = target;\n\t\twhile (current) {\n\t\t\tids.add(current.id);\n\t\t\tconst parent: string | undefined = current.of;\n\t\t\tcurrent = parent ? all.find((t) => t.id === parent) : undefined;\n\t\t}\n\t}\n\n\treturn all.filter((target) => ids.has(target.id));\n}\n\n/**\n * What each construct's folder has that its history does not — without\n * applying any of it. What `gkm dev` reports.\n */\nexport async function pendingMigrations(\n\toptions: DatabasesOptions,\n): Promise<PendingMigrations[]> {\n\tconst found: PendingMigrations[] = [];\n\n\tfor (const target of await targetsFor(options)) {\n\t\tconst files = await migrationFiles(join(options.root, target.migrations));\n\t\tif (files.size === 0) continue;\n\n\t\tconst pending = await withMigrator(options, target, async (migrator) =>\n\t\t\t(await migrator.getMigrations())\n\t\t\t\t.filter((migration) => migration.executedAt === undefined)\n\t\t\t\t.map((migration) => migration.name),\n\t\t);\n\n\t\tif (pending.length > 0) found.push({ target, pending });\n\t}\n\n\treturn found;\n}\n\n/**\n * The targets this run covers, with every folder under `db/` accounted for.\n *\n * A folder that names no construct is an error rather than something to skip:\n * a typo in its name is otherwise a migration that silently never runs.\n */\nasync function targetsFor(\n\toptions: Pick<DatabasesOptions, 'root' | 'manifest' | 'only'>,\n): Promise<MigrationTarget[]> {\n\tconst targets = migrationTargets(options.manifest);\n\tconst folders = targets.map((target) => target.folder);\n\n\tfor (const folder of await foldersIn(join(options.root, MIGRATIONS_ROOT))) {\n\t\tconst relative = `${MIGRATIONS_ROOT}/${folder}`;\n\t\tif (!folders.includes(relative)) {\n\t\t\tthrow new UnknownMigrationFolder(relative, folders);\n\t\t}\n\t\tawait checkLayout(options.root, relative);\n\t}\n\n\tif (!options.only) return targets;\n\n\tconst id = canonicalId(options.only);\n\tconst chosen = targets.filter((target) => target.id === id);\n\tif (chosen.length === 0) {\n\t\tthrow new NoSuchMigrationTarget(\n\t\t\toptions.only,\n\t\t\ttargets.map((target) => target.id),\n\t\t);\n\t}\n\n\treturn chosen;\n}\n\n/**\n * A construct's folder holds `migrations/` and `seeds/`, and nothing else —\n * anything beside them is a file or folder nothing would ever run.\n */\nasync function checkLayout(root: string, folder: string): Promise<void> {\n\tconst entries = await readdir(join(root, folder), { withFileTypes: true });\n\n\tconst loose = entries\n\t\t.filter((entry) => entry.isFile() && !entry.name.startsWith('.'))\n\t\t.map((entry) => entry.name);\n\tif (loose.length > 0) throw new MigrationsOutsideFolder(folder, loose);\n\n\tfor (const entry of entries) {\n\t\tif (\n\t\t\tentry.isDirectory() &&\n\t\t\t!(DATABASE_FOLDERS as readonly string[]).includes(entry.name)\n\t\t) {\n\t\t\tthrow new UnknownDatabaseFolder(`${folder}/${entry.name}`);\n\t\t}\n\t}\n}\n\nasync function foldersIn(dir: string): Promise<string[]> {\n\tconst entries = await readdir(dir, { withFileTypes: true }).catch(\n\t\t(error: NodeJS.ErrnoException) => {\n\t\t\tif (error.code === 'ENOENT') return [];\n\t\t\tthrow error;\n\t\t},\n\t);\n\n\treturn entries\n\t\t.filter((entry) => entry.isDirectory())\n\t\t.map((entry) => entry.name);\n}\n\n/**\n * A migrator for one target, connected as its owner.\n *\n * No `migrationTableSchema`: the owner role's `search_path` is pinned to the\n * construct's schema, so the history lands there — `app.kysely_migration` for\n * the database, `auth_database.kysely_migration` for its tenant — and two\n * constructs in one Postgres never share one.\n *\n * Unordered migrations are allowed. Files are timestamped, so a branch merged\n * after another's migration ran carries an older one; refusing it would make\n * every such merge a manual repair.\n */\nasync function withMigrator<T>(\n\toptions: DatabasesOptions,\n\ttarget: MigrationTarget,\n\trun: (migrator: Migrator) => Promise<T>,\n): Promise<T> {\n\treturn withOwner(options, target, (db, { Migrator, sql }) =>\n\t\trun(\n\t\t\tnew Migrator({\n\t\t\t\tdb,\n\t\t\t\tprovider: folderProvider(join(options.root, target.migrations), sql),\n\t\t\t\tallowUnorderedMigrations: true,\n\t\t\t}),\n\t\t),\n\t);\n}\n\n/**\n * One target's database, connected as its owner and closed afterwards — what\n * migrations and seeds both run as.\n */\nasync function withOwner<T>(\n\toptions: DatabasesOptions,\n\ttarget: MigrationTarget,\n\trun: (\n\t\tdb: Kysely<unknown>,\n\t\tkysely: Awaited<ReturnType<typeof kyselyFrom>>,\n\t) => Promise<T>,\n): Promise<T> {\n\tconst from =\n\t\toptions.sources?.[target.id]?.file ??\n\t\t(target.of ? options.sources?.[target.of]?.file : undefined) ??\n\t\tjoin(options.root, 'package.json');\n\tconst kysely = await kyselyFrom(from);\n\n\tconst db: Kysely<unknown> = new kysely.Kysely<unknown>({\n\t\tdialect: new kysely.PostgresDialect({\n\t\t\tpool: new pg.Pool(poolConfig(ownerUrl(options, target))),\n\t\t}),\n\t});\n\n\ttry {\n\t\treturn await run(db, kysely);\n\t} finally {\n\t\tawait db.destroy();\n\t}\n}\n\n/**\n * The owner's URL, with no fallback to the runtime one.\n *\n * The runtime role can create nothing, so falling back would only move the\n * failure to the first `CREATE TABLE` and make it read like a permissions\n * bug. The one exception is a database declared `roles: false`, where there is\n * no owner — both URLs are the master credential by design.\n */\nfunction ownerUrl(options: DatabasesOptions, target: MigrationTarget): string {\n\tconst key = provideKey(target.id, 'ownerUrl');\n\tconst owner = options.env[key];\n\tif (owner) return owner;\n\n\tconst database = options.manifest[target.of ?? target.id];\n\tif (database?.kind === 'database' && database.roles === false) {\n\t\tconst url = options.env[provideKey(target.id, 'url')];\n\t\tif (url) return url;\n\t}\n\n\tthrow new NoOwnerCredential(target.id, key);\n}\n\n/**\n * Pool settings for a URL, with `?search_path=` as the startup option Postgres\n * reads. Only a `roles: false` URL carries it — a role pins its own.\n */\nfunction poolConfig(url: string): pg.PoolConfig {\n\tconst parsed = new URL(url);\n\tconst searchPath = parsed.searchParams.get('search_path');\n\tif (!searchPath) return { connectionString: url };\n\n\tparsed.searchParams.delete('search_path');\n\treturn {\n\t\tconnectionString: parsed.toString(),\n\t\toptions: `-c search_path=${searchPath}`,\n\t};\n}\n\n/** Kysely as the project resolves it from `from`. */\nasync function kyselyFrom(from: string) {\n\tconst require = createRequire(from);\n\n\ttry {\n\t\tconst [main, migration] = await Promise.all([\n\t\t\timport(pathToFileURL(require.resolve('kysely')).href),\n\t\t\timport(pathToFileURL(require.resolve('kysely/migration')).href),\n\t\t]);\n\n\t\treturn {\n\t\t\tKysely: main.Kysely as typeof import('kysely').Kysely,\n\t\t\tPostgresDialect:\n\t\t\t\tmain.PostgresDialect as typeof import('kysely').PostgresDialect,\n\t\t\tsql: main.sql as typeof import('kysely').sql,\n\t\t\tMigrator:\n\t\t\t\tmigration.Migrator as typeof import('kysely/migration').Migrator,\n\t\t};\n\t} catch {\n\t\tthrow new KyselyNotFound(from);\n\t}\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"databases-CzszQV0N.mjs","names":[],"sources":["../src/migrate/provider.ts","../src/migrate/databases.ts"],"sourcesContent":["/**\n * One construct's migrations and seeds, read from its folders.\n *\n * Two kinds of file, told apart by extension rather than by configuration: a\n * script exports `up` (and may export `down`) — or `seed(db, { stage })`, in\n * `seeds/` — and a\n * `.sql` file is run whole. The second is what makes a folder independent of\n * the client the app uses — Better Auth's schema is SQL whatever the\n * application queries with.\n */\n\nimport { readdir, readFile, stat } from 'node:fs/promises';\nimport { extname, join } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type { Kysely, sql } from 'kysely';\nimport type { Migration, MigrationProvider } from 'kysely/migration';\n\nconst SCRIPTS = new Set(['.ts', '.mts', '.js', '.mjs']);\n\n/** Two files in one folder that would be one migration. */\nexport class DuplicateMigration extends Error {\n\tconstructor(\n\t\treadonly folder: string,\n\t\treadonly migration: string,\n\t) {\n\t\tsuper(\n\t\t\t`${folder} has two migrations named '${migration}' — a script and a .sql file ` +\n\t\t\t\t`with the same name. A migration is recorded by name, so only one of ` +\n\t\t\t\t`them could ever run; rename or remove the other.`,\n\t\t);\n\t\tthis.name = 'DuplicateMigration';\n\t}\n}\n\n/** A script in a migrations folder that exports no `up`. */\nexport class MigrationHasNoUp extends Error {\n\tconstructor(readonly file: string) {\n\t\tsuper(\n\t\t\t`${file} is in a migrations folder but exports no \\`up\\` function. ` +\n\t\t\t\t`Export \\`async function up(db: Kysely<unknown>)\\`, or move the file ` +\n\t\t\t\t`out of the folder if it is not a migration.`,\n\t\t);\n\t\tthis.name = 'MigrationHasNoUp';\n\t}\n}\n\n/** A script in a seeds folder that exports no `seed`. */\nexport class SeedHasNoSeed extends Error {\n\tconstructor(readonly file: string) {\n\t\tsuper(\n\t\t\t`${file} is in a seeds folder but exports no \\`seed\\` function. ` +\n\t\t\t\t`Export \\`async function seed(db, { stage })\\`, or move the ` +\n\t\t\t\t`file out of the folder if it is not a seed.`,\n\t\t);\n\t\tthis.name = 'SeedHasNoSeed';\n\t}\n}\n\n/**\n * What a seed is handed beside its database.\n *\n * Seeds run on every stage, production included, so a seed that should write\n * something only somewhere — a demo tenant on `development`, never on\n * `production` — decides by the stage it is given.\n */\nexport interface SeedContext {\n\t/** The stage being seeded: the local one, `test`, or a deployed one. */\n\tstage: string;\n}\n\n/** One seed: what it is called, and what it does. */\nexport interface Seed {\n\tname: string;\n\trun: (db: Kysely<unknown>, context: SeedContext) => Promise<void>;\n}\n\n/**\n * The migration files in a folder, by name, without reading them.\n *\n * Also what `gkm dev` compares against a construct's history to report what\n * is pending, which is why it is separate from loading them.\n */\nexport async function migrationFiles(\n\tfolder: string,\n): Promise<Map<string, string>> {\n\tconst entries = await readdir(folder, { withFileTypes: true }).catch(\n\t\t(error: NodeJS.ErrnoException) => {\n\t\t\tif (error.code === 'ENOENT') return [];\n\t\t\tthrow error;\n\t\t},\n\t);\n\n\tconst files = new Map<string, string>();\n\tfor (const entry of entries) {\n\t\tif (!entry.isFile() || !isMigration(entry.name)) continue;\n\n\t\tconst name = entry.name.slice(0, -extname(entry.name).length);\n\t\tif (files.has(name)) throw new DuplicateMigration(folder, name);\n\n\t\tfiles.set(name, join(folder, entry.name));\n\t}\n\n\treturn files;\n}\n\n/** A Kysely provider over one construct's folder. */\nexport function folderProvider(\n\tfolder: string,\n\traw: typeof sql,\n): MigrationProvider {\n\treturn {\n\t\tasync getMigrations() {\n\t\t\tconst migrations: Record<string, Migration> = {};\n\n\t\t\tfor (const [name, file] of await migrationFiles(folder)) {\n\t\t\t\tmigrations[name] =\n\t\t\t\t\textname(file) === '.sql'\n\t\t\t\t\t\t? await sqlMigration(file, raw)\n\t\t\t\t\t\t: await scriptMigration(file);\n\t\t\t}\n\n\t\t\treturn migrations;\n\t\t},\n\t};\n}\n\n/**\n * The seeds in a folder, loaded, in name order.\n *\n * Every one runs on every pass — there is no history to consult — so the order\n * is the only thing between two seeds where one reads what another writes.\n * Name them to sort that way.\n */\nexport async function loadSeeds(\n\tfolder: string,\n\traw: typeof sql,\n): Promise<Seed[]> {\n\tconst files = [...(await migrationFiles(folder))].sort(([a], [b]) =>\n\t\ta < b ? -1 : a > b ? 1 : 0,\n\t);\n\n\tconst seeds: Seed[] = [];\n\tfor (const [name, file] of files) {\n\t\tseeds.push({\n\t\t\tname,\n\t\t\trun:\n\t\t\t\textname(file) === '.sql'\n\t\t\t\t\t? (await sqlMigration(file, raw)).up\n\t\t\t\t\t: await scriptSeed(file),\n\t\t});\n\t}\n\n\treturn seeds;\n}\n\n/**\n * A seed script, imported as it is on disk now.\n *\n * Keyed by its modification time: a seed is run again after it is edited, and\n * a process that has imported it once would otherwise run the old one.\n */\nasync function scriptSeed(file: string): Promise<Seed['run']> {\n\tconst url = pathToFileURL(file);\n\turl.searchParams.set('mtime', String((await stat(file)).mtimeMs));\n\tconst module = (await import(url.href)) as { seed?: unknown };\n\tif (typeof module.seed !== 'function') throw new SeedHasNoSeed(file);\n\n\treturn module.seed as Seed['run'];\n}\n\nfunction isMigration(file: string): boolean {\n\tif (file.endsWith('.d.ts')) return false;\n\tif (/\\.(spec|test)\\.[cm]?[jt]s$/.test(file)) return false;\n\n\tconst extension = extname(file);\n\treturn extension === '.sql' || SCRIPTS.has(extension);\n}\n\nasync function scriptMigration(file: string): Promise<Migration> {\n\tconst module = (await import(pathToFileURL(file).href)) as Partial<Migration>;\n\tif (typeof module.up !== 'function') throw new MigrationHasNoUp(file);\n\n\treturn {\n\t\tup: module.up,\n\t\t...(typeof module.down === 'function' ? { down: module.down } : {}),\n\t};\n}\n\n/**\n * A `.sql` file, run whole.\n *\n * One query rather than split on `;`: with no parameters, Postgres takes\n * several statements in one message, and splitting by hand breaks on the first\n * `;` inside a string or a function body. There is no `down` — a generated\n * schema is rolled back by a migration that says how, not by guessing.\n */\nasync function sqlMigration(file: string, raw: typeof sql): Promise<Migration> {\n\tconst statements = await readFile(file, 'utf-8');\n\n\treturn {\n\t\tup: async (db: Kysely<unknown>) => {\n\t\t\tawait raw.raw(statements).execute(db);\n\t\t},\n\t};\n}\n","/**\n * Bring each construct's schema up to its migrations folder.\n *\n * Which constructs, in what order, from which folder, is `migrationTargets`'\n * answer and nobody else's. What is decided here is only how one target is\n * run: as its owner, in its own schema, with a history of its own.\n */\n\nimport { readdir } from 'node:fs/promises';\nimport { createRequire } from 'node:module';\nimport { join } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport {\n\ttype ConstructManifest,\n\tcanonicalId,\n\tDATABASE_FOLDERS,\n\tMIGRATIONS_ROOT,\n\ttype MigrationTarget,\n\tmigrationTargets,\n\tprovideKey,\n} from '@geekmidas/manifest';\nimport type { Kysely } from 'kysely';\nimport type { Migrator } from 'kysely/migration';\nimport pg from 'pg';\nimport type { ConstructSource } from '../reconcile/discover';\nimport { folderProvider, loadSeeds, migrationFiles } from './provider';\n\nexport interface DatabasesOptions {\n\t/** The project root — where `db/` lives. */\n\troot: string;\n\tmanifest: ConstructManifest;\n\t/**\n\t * Where each construct was declared. A construct's migrations run with the\n\t * Kysely its own file resolves, which is the one its migrations import.\n\t */\n\tsources?: Readonly<Record<string, ConstructSource>>;\n\t/** The resolved environment: each construct's owner URL is read from it. */\n\tenv: Readonly<Record<string, string | undefined>>;\n\t/** One construct, by name. All of them when absent. */\n\tonly?: string;\n}\n\nexport interface MigrationRun {\n\ttarget: MigrationTarget;\n\t/** The migrations this run applied, in order. */\n\tapplied: string[];\n}\n\nexport interface SeedOptions extends DatabasesOptions {\n\t/** The stage being seeded, handed to every seed. */\n\tstage: string;\n}\n\nexport interface SeedRun {\n\ttarget: MigrationTarget;\n\t/** The seeds this run ran, in order — every one, every time. */\n\tseeded: string[];\n}\n\n/** The seeds a run would run for one construct, read without connecting. */\nexport interface PlannedSeeds {\n\ttarget: MigrationTarget;\n\t/** In the order they would run. */\n\tseeds: string[];\n}\n\nexport interface PendingMigrations {\n\ttarget: MigrationTarget;\n\tpending: string[];\n}\n\n/** A folder under `db/` that names no construct. */\nexport class UnknownMigrationFolder extends Error {\n\tconstructor(\n\t\treadonly folder: string,\n\t\treadonly known: readonly string[],\n\t) {\n\t\tsuper(\n\t\t\t`${folder} names no database construct, so its migrations would never ` +\n\t\t\t\t`run. ${known.length > 0 ? `The folders that do: ${known.join(', ')}.` : 'This project declares no database.'} ` +\n\t\t\t\t`Rename the folder after the construct it belongs to.`,\n\t\t);\n\t\tthis.name = 'UnknownMigrationFolder';\n\t}\n}\n\n/** A construct was asked for by a name that takes no migrations. */\nexport class NoSuchMigrationTarget extends Error {\n\tconstructor(\n\t\treadonly construct: string,\n\t\treadonly known: readonly string[],\n\t) {\n\t\tsuper(\n\t\t\t`'${construct}' is not a database construct. ` +\n\t\t\t\t(known.length > 0\n\t\t\t\t\t? `The ones with migrations: ${known.join(', ')}.`\n\t\t\t\t\t: 'This project declares no database.'),\n\t\t);\n\t\tthis.name = 'NoSuchMigrationTarget';\n\t}\n}\n\n/**\n * A migration left directly in a construct's folder, where nothing reads it.\n *\n * Migrations moved into `migrations/` so seeds could sit beside them. A file\n * still at the old level would otherwise never run, and nothing would say so.\n */\nexport class MigrationsOutsideFolder extends Error {\n\tconstructor(\n\t\treadonly folder: string,\n\t\treadonly files: readonly string[],\n\t) {\n\t\tsuper(\n\t\t\t`${folder} has ${files.join(', ')} directly inside it, where nothing ` +\n\t\t\t\t`runs them. Migrations live in ${folder}/migrations/ and seeds in ` +\n\t\t\t\t`${folder}/seeds/ — move ${files.length === 1 ? 'it' : 'them'} there.`,\n\t\t);\n\t\tthis.name = 'MigrationsOutsideFolder';\n\t}\n}\n\n/** A folder inside a construct's folder that is neither `migrations` nor `seeds`. */\nexport class UnknownDatabaseFolder extends Error {\n\tconstructor(readonly folder: string) {\n\t\tsuper(\n\t\t\t`${folder} is neither migrations/ nor seeds/, so nothing in it would ` +\n\t\t\t\t`ever run. A construct's folder holds those two; rename or remove it.`,\n\t\t);\n\t\tthis.name = 'UnknownDatabaseFolder';\n\t}\n}\n\n/** A seed failed; its own writes are rolled back, the ones before it stay. */\nexport class SeedFailed extends Error {\n\tconstructor(\n\t\treadonly construct: string,\n\t\treadonly seed: string,\n\t\toverride readonly cause: unknown,\n\t) {\n\t\tsuper(\n\t\t\t`${construct}: seed '${seed}' failed — ` +\n\t\t\t\t`${cause instanceof Error ? cause.message : String(cause)}. ` +\n\t\t\t\t`Its writes were rolled back; fix it and run it again.`,\n\t\t);\n\t\tthis.name = 'SeedFailed';\n\t}\n}\n\n/** A construct with roles has no owner URL to migrate with. */\nexport class NoOwnerCredential extends Error {\n\tconstructor(\n\t\treadonly construct: string,\n\t\treadonly key: string,\n\t) {\n\t\tsuper(\n\t\t\t`${construct} has no ${key} to migrate with. Migrations run as the owner ` +\n\t\t\t\t`role — the runtime one cannot create a table — and nothing ` +\n\t\t\t\t`resolved its URL. Is the stage reconciled?`,\n\t\t);\n\t\tthis.name = 'NoOwnerCredential';\n\t}\n}\n\n/** A migration failed; the ones before it in the run stay applied. */\nexport class MigrationFailed extends Error {\n\tconstructor(\n\t\treadonly construct: string,\n\t\treadonly migration: string | undefined,\n\t\toverride readonly cause: unknown,\n\t) {\n\t\tsuper(\n\t\t\t`${construct}: migration ${migration ? `'${migration}' ` : ''}failed — ` +\n\t\t\t\t`${cause instanceof Error ? cause.message : String(cause)}`,\n\t\t);\n\t\tthis.name = 'MigrationFailed';\n\t}\n}\n\n/** The project's Kysely could not be found from where a database is declared. */\nexport class KyselyNotFound extends Error {\n\tconstructor(readonly from: string) {\n\t\tsuper(\n\t\t\t`Could not resolve 'kysely' from ${from}. Migrations run with the ` +\n\t\t\t\t`project's own Kysely — install it where the database is declared.`,\n\t\t);\n\t\tthis.name = 'KyselyNotFound';\n\t}\n}\n\n/** Apply every pending migration, construct by construct, parents first. */\nexport async function migrateDatabases(\n\toptions: DatabasesOptions,\n): Promise<MigrationRun[]> {\n\tconst runs: MigrationRun[] = [];\n\n\tfor (const target of await targetsFor(options)) {\n\t\t// No folder, or an empty one, is nothing to do — and no reason to\n\t\t// connect, which a construct nobody has written a migration for yet\n\t\t// should not need.\n\t\tconst files = await migrationFiles(join(options.root, target.migrations));\n\t\tif (files.size === 0) {\n\t\t\truns.push({ target, applied: [] });\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst applied = await withMigrator(options, target, async (migrator) => {\n\t\t\tconst { error, results = [] } = await migrator.migrateToLatest();\n\t\t\tif (error) {\n\t\t\t\tconst failed = results.find((result) => result.status === 'Error');\n\t\t\t\tthrow new MigrationFailed(target.id, failed?.migrationName, error);\n\t\t\t}\n\n\t\t\treturn results\n\t\t\t\t.filter((result) => result.status === 'Success')\n\t\t\t\t.map((result) => result.migrationName);\n\t\t});\n\n\t\truns.push({ target, applied });\n\t}\n\n\treturn runs;\n}\n\n/**\n * Run every construct's seeds, parents first — each one, every time, on every\n * stage, production included. Each is handed the stage, so a seed decides\n * what belongs where.\n *\n * Seeds are reference data kept up to date by being run again, so there is no\n * history: a seed is an upsert, and a changed one is applied by the next run.\n * Each runs in its own transaction, as the construct's owner, so a failure\n * leaves nothing half-written. Assumes the schema is migrated —\n * {@link migrateAndSeed} is what makes that true.\n */\nexport async function seedDatabases(options: SeedOptions): Promise<SeedRun[]> {\n\tconst runs: SeedRun[] = [];\n\n\tfor (const target of await targetsFor(options)) {\n\t\tconst folder = join(options.root, target.seeds);\n\t\tif ((await migrationFiles(folder)).size === 0) {\n\t\t\truns.push({ target, seeded: [] });\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst seeded = await withOwner(options, target, async (db, kysely) => {\n\t\t\tconst names: string[] = [];\n\t\t\tfor (const seed of await loadSeeds(folder, kysely.sql)) {\n\t\t\t\ttry {\n\t\t\t\t\tawait db\n\t\t\t\t\t\t.transaction()\n\t\t\t\t\t\t.execute((trx) => seed.run(trx, { stage: options.stage }));\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthrow new SeedFailed(target.id, seed.name, error);\n\t\t\t\t}\n\t\t\t\tnames.push(seed.name);\n\t\t\t}\n\t\t\treturn names;\n\t\t});\n\n\t\truns.push({ target, seeded });\n\t}\n\n\treturn runs;\n}\n\n/**\n * The seeds {@link seedDatabases} would run, construct by construct, parents\n * first, in the order it would run them — read from the folders alone, with\n * no connection. What a dry run lists. Constructs with none are left out.\n */\nexport async function plannedSeeds(\n\toptions: Pick<DatabasesOptions, 'root' | 'manifest' | 'only'>,\n): Promise<PlannedSeeds[]> {\n\tconst planned: PlannedSeeds[] = [];\n\tfor (const target of await targetsFor(options)) {\n\t\tconst files = await migrationFiles(join(options.root, target.seeds));\n\t\tif (files.size === 0) continue;\n\t\tplanned.push({ target, seeds: [...files.keys()].sort() });\n\t}\n\treturn planned;\n}\n\n/**\n * Migrate, then seed: a seed only ever runs against the schema it was written\n * for.\n *\n * With `only`, the constructs it lives in are migrated too — a tenant's seed\n * cannot run before the database that owns its schema exists — and only it is\n * seeded.\n */\nexport async function migrateAndSeed(options: SeedOptions): Promise<{\n\tmigrations: MigrationRun[];\n\tseeds: SeedRun[];\n}> {\n\tconst migrations: MigrationRun[] = [];\n\tfor (const target of await withParents(options)) {\n\t\tmigrations.push(\n\t\t\t...(await migrateDatabases({ ...options, only: target.id })),\n\t\t);\n\t}\n\n\treturn { migrations, seeds: await seedDatabases(options) };\n}\n\n/** The targets `only` names, after the ones it lives in; all without it. */\nasync function withParents(\n\toptions: DatabasesOptions,\n): Promise<MigrationTarget[]> {\n\tconst chosen = await targetsFor(options);\n\tif (!options.only) return chosen;\n\n\tconst all = migrationTargets(options.manifest);\n\tconst ids = new Set<string>();\n\tfor (const target of chosen) {\n\t\tlet current: MigrationTarget | undefined = target;\n\t\twhile (current) {\n\t\t\tids.add(current.id);\n\t\t\tconst parent: string | undefined = current.of;\n\t\t\tcurrent = parent ? all.find((t) => t.id === parent) : undefined;\n\t\t}\n\t}\n\n\treturn all.filter((target) => ids.has(target.id));\n}\n\n/**\n * What each construct's folder has that its history does not — without\n * applying any of it. What `gkm dev` reports.\n */\nexport async function pendingMigrations(\n\toptions: DatabasesOptions,\n): Promise<PendingMigrations[]> {\n\tconst found: PendingMigrations[] = [];\n\n\tfor (const target of await targetsFor(options)) {\n\t\tconst files = await migrationFiles(join(options.root, target.migrations));\n\t\tif (files.size === 0) continue;\n\n\t\tconst pending = await withMigrator(options, target, async (migrator) =>\n\t\t\t(await migrator.getMigrations())\n\t\t\t\t.filter((migration) => migration.executedAt === undefined)\n\t\t\t\t.map((migration) => migration.name),\n\t\t);\n\n\t\tif (pending.length > 0) found.push({ target, pending });\n\t}\n\n\treturn found;\n}\n\n/**\n * The targets this run covers, with every folder under `db/` accounted for.\n *\n * A folder that names no construct is an error rather than something to skip:\n * a typo in its name is otherwise a migration that silently never runs.\n */\nasync function targetsFor(\n\toptions: Pick<DatabasesOptions, 'root' | 'manifest' | 'only'>,\n): Promise<MigrationTarget[]> {\n\tconst targets = migrationTargets(options.manifest);\n\tconst folders = targets.map((target) => target.folder);\n\n\tfor (const folder of await foldersIn(join(options.root, MIGRATIONS_ROOT))) {\n\t\tconst relative = `${MIGRATIONS_ROOT}/${folder}`;\n\t\tif (!folders.includes(relative)) {\n\t\t\tthrow new UnknownMigrationFolder(relative, folders);\n\t\t}\n\t\tawait checkLayout(options.root, relative);\n\t}\n\n\tif (!options.only) return targets;\n\n\tconst id = canonicalId(options.only);\n\tconst chosen = targets.filter((target) => target.id === id);\n\tif (chosen.length === 0) {\n\t\tthrow new NoSuchMigrationTarget(\n\t\t\toptions.only,\n\t\t\ttargets.map((target) => target.id),\n\t\t);\n\t}\n\n\treturn chosen;\n}\n\n/**\n * A construct's folder holds `migrations/` and `seeds/`, and nothing else —\n * anything beside them is a file or folder nothing would ever run.\n */\nasync function checkLayout(root: string, folder: string): Promise<void> {\n\tconst entries = await readdir(join(root, folder), { withFileTypes: true });\n\n\tconst loose = entries\n\t\t.filter((entry) => entry.isFile() && !entry.name.startsWith('.'))\n\t\t.map((entry) => entry.name);\n\tif (loose.length > 0) throw new MigrationsOutsideFolder(folder, loose);\n\n\tfor (const entry of entries) {\n\t\tif (\n\t\t\tentry.isDirectory() &&\n\t\t\t!(DATABASE_FOLDERS as readonly string[]).includes(entry.name)\n\t\t) {\n\t\t\tthrow new UnknownDatabaseFolder(`${folder}/${entry.name}`);\n\t\t}\n\t}\n}\n\nasync function foldersIn(dir: string): Promise<string[]> {\n\tconst entries = await readdir(dir, { withFileTypes: true }).catch(\n\t\t(error: NodeJS.ErrnoException) => {\n\t\t\tif (error.code === 'ENOENT') return [];\n\t\t\tthrow error;\n\t\t},\n\t);\n\n\treturn entries\n\t\t.filter((entry) => entry.isDirectory())\n\t\t.map((entry) => entry.name);\n}\n\n/**\n * A migrator for one target, connected as its owner.\n *\n * No `migrationTableSchema`: the owner role's `search_path` is pinned to the\n * construct's schema, so the history lands there — `app.kysely_migration` for\n * the database, `auth_database.kysely_migration` for its tenant — and two\n * constructs in one Postgres never share one.\n *\n * Unordered migrations are allowed. Files are timestamped, so a branch merged\n * after another's migration ran carries an older one; refusing it would make\n * every such merge a manual repair.\n */\nasync function withMigrator<T>(\n\toptions: DatabasesOptions,\n\ttarget: MigrationTarget,\n\trun: (migrator: Migrator) => Promise<T>,\n): Promise<T> {\n\treturn withOwner(options, target, (db, { Migrator, sql }) =>\n\t\trun(\n\t\t\tnew Migrator({\n\t\t\t\tdb,\n\t\t\t\tprovider: folderProvider(join(options.root, target.migrations), sql),\n\t\t\t\tallowUnorderedMigrations: true,\n\t\t\t}),\n\t\t),\n\t);\n}\n\n/**\n * One target's database, connected as its owner and closed afterwards — what\n * migrations and seeds both run as.\n */\nasync function withOwner<T>(\n\toptions: DatabasesOptions,\n\ttarget: MigrationTarget,\n\trun: (\n\t\tdb: Kysely<unknown>,\n\t\tkysely: Awaited<ReturnType<typeof kyselyFrom>>,\n\t) => Promise<T>,\n): Promise<T> {\n\tconst from =\n\t\toptions.sources?.[target.id]?.file ??\n\t\t(target.of ? options.sources?.[target.of]?.file : undefined) ??\n\t\tjoin(options.root, 'package.json');\n\tconst kysely = await kyselyFrom(from);\n\n\tconst db: Kysely<unknown> = new kysely.Kysely<unknown>({\n\t\tdialect: new kysely.PostgresDialect({\n\t\t\tpool: new pg.Pool(poolConfig(ownerUrl(options, target))),\n\t\t}),\n\t});\n\n\ttry {\n\t\treturn await run(db, kysely);\n\t} finally {\n\t\tawait db.destroy();\n\t}\n}\n\n/**\n * The owner's URL, with no fallback to the runtime one.\n *\n * The runtime role can create nothing, so falling back would only move the\n * failure to the first `CREATE TABLE` and make it read like a permissions\n * bug. The one exception is a database declared `roles: false`, where there is\n * no owner — both URLs are the master credential by design.\n */\nfunction ownerUrl(options: DatabasesOptions, target: MigrationTarget): string {\n\tconst key = provideKey(target.id, 'ownerUrl');\n\tconst owner = options.env[key];\n\tif (owner) return owner;\n\n\tconst database = options.manifest[target.of ?? target.id];\n\tif (database?.kind === 'database' && database.roles === false) {\n\t\tconst url = options.env[provideKey(target.id, 'url')];\n\t\tif (url) return url;\n\t}\n\n\tthrow new NoOwnerCredential(target.id, key);\n}\n\n/**\n * Pool settings for a URL, with `?search_path=` as the startup option Postgres\n * reads. Only a `roles: false` URL carries it — a role pins its own.\n */\nfunction poolConfig(url: string): pg.PoolConfig {\n\tconst parsed = new URL(url);\n\tconst searchPath = parsed.searchParams.get('search_path');\n\tif (!searchPath) return { connectionString: url };\n\n\tparsed.searchParams.delete('search_path');\n\treturn {\n\t\tconnectionString: parsed.toString(),\n\t\toptions: `-c search_path=${searchPath}`,\n\t};\n}\n\n/** Kysely as the project resolves it from `from`. */\nasync function kyselyFrom(from: string) {\n\tconst require = createRequire(from);\n\n\ttry {\n\t\tconst [main, migration] = await Promise.all([\n\t\t\timport(pathToFileURL(require.resolve('kysely')).href),\n\t\t\timport(pathToFileURL(require.resolve('kysely/migration')).href),\n\t\t]);\n\n\t\treturn {\n\t\t\tKysely: main.Kysely as typeof import('kysely').Kysely,\n\t\t\tPostgresDialect:\n\t\t\t\tmain.PostgresDialect as typeof import('kysely').PostgresDialect,\n\t\t\tsql: main.sql as typeof import('kysely').sql,\n\t\t\tMigrator:\n\t\t\t\tmigration.Migrator as typeof import('kysely/migration').Migrator,\n\t\t};\n\t} catch {\n\t\tthrow new KyselyNotFound(from);\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"databases-CzszQV0N.mjs","names":[],"sources":["../src/migrate/provider.ts","../src/migrate/databases.ts"],"sourcesContent":["/**\n * One construct's migrations and seeds, read from its folders.\n *\n * Two kinds of file, told apart by extension rather than by configuration: a\n * script exports `up` (and may export `down`) — or `seed(db, { stage })`, in\n * `seeds/` — and a\n * `.sql` file is run whole. The second is what makes a folder independent of\n * the client the app uses — Better Auth's schema is SQL whatever the\n * application queries with.\n */\n\nimport { readdir, readFile, stat } from 'node:fs/promises';\nimport { extname, join } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type { SeedContext } from '@geekmidas/constructs';\nimport type { Kysely, sql } from 'kysely';\nimport type { Migration, MigrationProvider } from 'kysely/migration';\n\nconst SCRIPTS = new Set(['.ts', '.mts', '.js', '.mjs']);\n\n/** Two files in one folder that would be one migration. */\nexport class DuplicateMigration extends Error {\n\tconstructor(\n\t\treadonly folder: string,\n\t\treadonly migration: string,\n\t) {\n\t\tsuper(\n\t\t\t`${folder} has two migrations named '${migration}' — a script and a .sql file ` +\n\t\t\t\t`with the same name. A migration is recorded by name, so only one of ` +\n\t\t\t\t`them could ever run; rename or remove the other.`,\n\t\t);\n\t\tthis.name = 'DuplicateMigration';\n\t}\n}\n\n/** A script in a migrations folder that exports no `up`. */\nexport class MigrationHasNoUp extends Error {\n\tconstructor(readonly file: string) {\n\t\tsuper(\n\t\t\t`${file} is in a migrations folder but exports no \\`up\\` function. ` +\n\t\t\t\t`Export \\`async function up(db: Kysely<unknown>)\\`, or move the file ` +\n\t\t\t\t`out of the folder if it is not a migration.`,\n\t\t);\n\t\tthis.name = 'MigrationHasNoUp';\n\t}\n}\n\n/** A script in a seeds folder that exports no `seed`. */\nexport class SeedHasNoSeed extends Error {\n\tconstructor(readonly file: string) {\n\t\tsuper(\n\t\t\t`${file} is in a seeds folder but exports no \\`seed\\` function. ` +\n\t\t\t\t`Export \\`async function seed(db, { stage })\\`, or move the ` +\n\t\t\t\t`file out of the folder if it is not a seed.`,\n\t\t);\n\t\tthis.name = 'SeedHasNoSeed';\n\t}\n}\n\n/**\n * What a seed is handed beside its database — `@geekmidas/constructs`'\n * `SeedContext`, whose `stage` is the union of the declared stages once\n * `.gkm/stages.d.ts` is generated.\n */\nexport type { SeedContext };\n\n/** One seed: what it is called, and what it does. */\nexport interface Seed {\n\tname: string;\n\trun: (db: Kysely<unknown>, context: SeedContext) => Promise<void>;\n}\n\n/**\n * The migration files in a folder, by name, without reading them.\n *\n * Also what `gkm dev` compares against a construct's history to report what\n * is pending, which is why it is separate from loading them.\n */\nexport async function migrationFiles(\n\tfolder: string,\n): Promise<Map<string, string>> {\n\tconst entries = await readdir(folder, { withFileTypes: true }).catch(\n\t\t(error: NodeJS.ErrnoException) => {\n\t\t\tif (error.code === 'ENOENT') return [];\n\t\t\tthrow error;\n\t\t},\n\t);\n\n\tconst files = new Map<string, string>();\n\tfor (const entry of entries) {\n\t\tif (!entry.isFile() || !isMigration(entry.name)) continue;\n\n\t\tconst name = entry.name.slice(0, -extname(entry.name).length);\n\t\tif (files.has(name)) throw new DuplicateMigration(folder, name);\n\n\t\tfiles.set(name, join(folder, entry.name));\n\t}\n\n\treturn files;\n}\n\n/** A Kysely provider over one construct's folder. */\nexport function folderProvider(\n\tfolder: string,\n\traw: typeof sql,\n): MigrationProvider {\n\treturn {\n\t\tasync getMigrations() {\n\t\t\tconst migrations: Record<string, Migration> = {};\n\n\t\t\tfor (const [name, file] of await migrationFiles(folder)) {\n\t\t\t\tmigrations[name] =\n\t\t\t\t\textname(file) === '.sql'\n\t\t\t\t\t\t? await sqlMigration(file, raw)\n\t\t\t\t\t\t: await scriptMigration(file);\n\t\t\t}\n\n\t\t\treturn migrations;\n\t\t},\n\t};\n}\n\n/**\n * The seeds in a folder, loaded, in name order.\n *\n * Every one runs on every pass — there is no history to consult — so the order\n * is the only thing between two seeds where one reads what another writes.\n * Name them to sort that way.\n */\nexport async function loadSeeds(\n\tfolder: string,\n\traw: typeof sql,\n): Promise<Seed[]> {\n\tconst files = [...(await migrationFiles(folder))].sort(([a], [b]) =>\n\t\ta < b ? -1 : a > b ? 1 : 0,\n\t);\n\n\tconst seeds: Seed[] = [];\n\tfor (const [name, file] of files) {\n\t\tseeds.push({\n\t\t\tname,\n\t\t\trun:\n\t\t\t\textname(file) === '.sql'\n\t\t\t\t\t? (await sqlMigration(file, raw)).up\n\t\t\t\t\t: await scriptSeed(file),\n\t\t});\n\t}\n\n\treturn seeds;\n}\n\n/**\n * A seed script, imported as it is on disk now.\n *\n * Keyed by its modification time: a seed is run again after it is edited, and\n * a process that has imported it once would otherwise run the old one.\n */\nasync function scriptSeed(file: string): Promise<Seed['run']> {\n\tconst url = pathToFileURL(file);\n\turl.searchParams.set('mtime', String((await stat(file)).mtimeMs));\n\tconst module = (await import(url.href)) as { seed?: unknown };\n\tif (typeof module.seed !== 'function') throw new SeedHasNoSeed(file);\n\n\treturn module.seed as Seed['run'];\n}\n\nfunction isMigration(file: string): boolean {\n\tif (file.endsWith('.d.ts')) return false;\n\tif (/\\.(spec|test)\\.[cm]?[jt]s$/.test(file)) return false;\n\n\tconst extension = extname(file);\n\treturn extension === '.sql' || SCRIPTS.has(extension);\n}\n\nasync function scriptMigration(file: string): Promise<Migration> {\n\tconst module = (await import(pathToFileURL(file).href)) as Partial<Migration>;\n\tif (typeof module.up !== 'function') throw new MigrationHasNoUp(file);\n\n\treturn {\n\t\tup: module.up,\n\t\t...(typeof module.down === 'function' ? { down: module.down } : {}),\n\t};\n}\n\n/**\n * A `.sql` file, run whole.\n *\n * One query rather than split on `;`: with no parameters, Postgres takes\n * several statements in one message, and splitting by hand breaks on the first\n * `;` inside a string or a function body. There is no `down` — a generated\n * schema is rolled back by a migration that says how, not by guessing.\n */\nasync function sqlMigration(file: string, raw: typeof sql): Promise<Migration> {\n\tconst statements = await readFile(file, 'utf-8');\n\n\treturn {\n\t\tup: async (db: Kysely<unknown>) => {\n\t\t\tawait raw.raw(statements).execute(db);\n\t\t},\n\t};\n}\n","/**\n * Bring each construct's schema up to its migrations folder.\n *\n * Which constructs, in what order, from which folder, is `migrationTargets`'\n * answer and nobody else's. What is decided here is only how one target is\n * run: as its owner, in its own schema, with a history of its own.\n */\n\nimport { readdir } from 'node:fs/promises';\nimport { createRequire } from 'node:module';\nimport { join } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport {\n\ttype ConstructManifest,\n\tcanonicalId,\n\tDATABASE_FOLDERS,\n\tMIGRATIONS_ROOT,\n\ttype MigrationTarget,\n\tmigrationTargets,\n\tprovideKey,\n} from '@geekmidas/manifest';\nimport type { Kysely } from 'kysely';\nimport type { Migrator } from 'kysely/migration';\nimport pg from 'pg';\nimport type { ConstructSource } from '../reconcile/discover';\nimport { folderProvider, loadSeeds, migrationFiles } from './provider';\n\nexport interface DatabasesOptions {\n\t/** The project root — where `db/` lives. */\n\troot: string;\n\tmanifest: ConstructManifest;\n\t/**\n\t * Where each construct was declared. A construct's migrations run with the\n\t * Kysely its own file resolves, which is the one its migrations import.\n\t */\n\tsources?: Readonly<Record<string, ConstructSource>>;\n\t/** The resolved environment: each construct's owner URL is read from it. */\n\tenv: Readonly<Record<string, string | undefined>>;\n\t/** One construct, by name. All of them when absent. */\n\tonly?: string;\n}\n\nexport interface MigrationRun {\n\ttarget: MigrationTarget;\n\t/** The migrations this run applied, in order. */\n\tapplied: string[];\n}\n\nexport interface SeedOptions extends DatabasesOptions {\n\t/** The stage being seeded, handed to every seed. */\n\tstage: string;\n}\n\nexport interface SeedRun {\n\ttarget: MigrationTarget;\n\t/** The seeds this run ran, in order — every one, every time. */\n\tseeded: string[];\n}\n\n/** The seeds a run would run for one construct, read without connecting. */\nexport interface PlannedSeeds {\n\ttarget: MigrationTarget;\n\t/** In the order they would run. */\n\tseeds: string[];\n}\n\nexport interface PendingMigrations {\n\ttarget: MigrationTarget;\n\tpending: string[];\n}\n\n/** A folder under `db/` that names no construct. */\nexport class UnknownMigrationFolder extends Error {\n\tconstructor(\n\t\treadonly folder: string,\n\t\treadonly known: readonly string[],\n\t) {\n\t\tsuper(\n\t\t\t`${folder} names no database construct, so its migrations would never ` +\n\t\t\t\t`run. ${known.length > 0 ? `The folders that do: ${known.join(', ')}.` : 'This project declares no database.'} ` +\n\t\t\t\t`Rename the folder after the construct it belongs to.`,\n\t\t);\n\t\tthis.name = 'UnknownMigrationFolder';\n\t}\n}\n\n/** A construct was asked for by a name that takes no migrations. */\nexport class NoSuchMigrationTarget extends Error {\n\tconstructor(\n\t\treadonly construct: string,\n\t\treadonly known: readonly string[],\n\t) {\n\t\tsuper(\n\t\t\t`'${construct}' is not a database construct. ` +\n\t\t\t\t(known.length > 0\n\t\t\t\t\t? `The ones with migrations: ${known.join(', ')}.`\n\t\t\t\t\t: 'This project declares no database.'),\n\t\t);\n\t\tthis.name = 'NoSuchMigrationTarget';\n\t}\n}\n\n/**\n * A migration left directly in a construct's folder, where nothing reads it.\n *\n * Migrations moved into `migrations/` so seeds could sit beside them. A file\n * still at the old level would otherwise never run, and nothing would say so.\n */\nexport class MigrationsOutsideFolder extends Error {\n\tconstructor(\n\t\treadonly folder: string,\n\t\treadonly files: readonly string[],\n\t) {\n\t\tsuper(\n\t\t\t`${folder} has ${files.join(', ')} directly inside it, where nothing ` +\n\t\t\t\t`runs them. Migrations live in ${folder}/migrations/ and seeds in ` +\n\t\t\t\t`${folder}/seeds/ — move ${files.length === 1 ? 'it' : 'them'} there.`,\n\t\t);\n\t\tthis.name = 'MigrationsOutsideFolder';\n\t}\n}\n\n/** A folder inside a construct's folder that is neither `migrations` nor `seeds`. */\nexport class UnknownDatabaseFolder extends Error {\n\tconstructor(readonly folder: string) {\n\t\tsuper(\n\t\t\t`${folder} is neither migrations/ nor seeds/, so nothing in it would ` +\n\t\t\t\t`ever run. A construct's folder holds those two; rename or remove it.`,\n\t\t);\n\t\tthis.name = 'UnknownDatabaseFolder';\n\t}\n}\n\n/** A seed failed; its own writes are rolled back, the ones before it stay. */\nexport class SeedFailed extends Error {\n\tconstructor(\n\t\treadonly construct: string,\n\t\treadonly seed: string,\n\t\toverride readonly cause: unknown,\n\t) {\n\t\tsuper(\n\t\t\t`${construct}: seed '${seed}' failed — ` +\n\t\t\t\t`${cause instanceof Error ? cause.message : String(cause)}. ` +\n\t\t\t\t`Its writes were rolled back; fix it and run it again.`,\n\t\t);\n\t\tthis.name = 'SeedFailed';\n\t}\n}\n\n/** A construct with roles has no owner URL to migrate with. */\nexport class NoOwnerCredential extends Error {\n\tconstructor(\n\t\treadonly construct: string,\n\t\treadonly key: string,\n\t) {\n\t\tsuper(\n\t\t\t`${construct} has no ${key} to migrate with. Migrations run as the owner ` +\n\t\t\t\t`role — the runtime one cannot create a table — and nothing ` +\n\t\t\t\t`resolved its URL. Is the stage reconciled?`,\n\t\t);\n\t\tthis.name = 'NoOwnerCredential';\n\t}\n}\n\n/** A migration failed; the ones before it in the run stay applied. */\nexport class MigrationFailed extends Error {\n\tconstructor(\n\t\treadonly construct: string,\n\t\treadonly migration: string | undefined,\n\t\toverride readonly cause: unknown,\n\t) {\n\t\tsuper(\n\t\t\t`${construct}: migration ${migration ? `'${migration}' ` : ''}failed — ` +\n\t\t\t\t`${cause instanceof Error ? cause.message : String(cause)}`,\n\t\t);\n\t\tthis.name = 'MigrationFailed';\n\t}\n}\n\n/** The project's Kysely could not be found from where a database is declared. */\nexport class KyselyNotFound extends Error {\n\tconstructor(readonly from: string) {\n\t\tsuper(\n\t\t\t`Could not resolve 'kysely' from ${from}. Migrations run with the ` +\n\t\t\t\t`project's own Kysely — install it where the database is declared.`,\n\t\t);\n\t\tthis.name = 'KyselyNotFound';\n\t}\n}\n\n/** Apply every pending migration, construct by construct, parents first. */\nexport async function migrateDatabases(\n\toptions: DatabasesOptions,\n): Promise<MigrationRun[]> {\n\tconst runs: MigrationRun[] = [];\n\n\tfor (const target of await targetsFor(options)) {\n\t\t// No folder, or an empty one, is nothing to do — and no reason to\n\t\t// connect, which a construct nobody has written a migration for yet\n\t\t// should not need.\n\t\tconst files = await migrationFiles(join(options.root, target.migrations));\n\t\tif (files.size === 0) {\n\t\t\truns.push({ target, applied: [] });\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst applied = await withMigrator(options, target, async (migrator) => {\n\t\t\tconst { error, results = [] } = await migrator.migrateToLatest();\n\t\t\tif (error) {\n\t\t\t\tconst failed = results.find((result) => result.status === 'Error');\n\t\t\t\tthrow new MigrationFailed(target.id, failed?.migrationName, error);\n\t\t\t}\n\n\t\t\treturn results\n\t\t\t\t.filter((result) => result.status === 'Success')\n\t\t\t\t.map((result) => result.migrationName);\n\t\t});\n\n\t\truns.push({ target, applied });\n\t}\n\n\treturn runs;\n}\n\n/**\n * Run every construct's seeds, parents first — each one, every time, on every\n * stage, production included. Each is handed the stage, so a seed decides\n * what belongs where.\n *\n * Seeds are reference data kept up to date by being run again, so there is no\n * history: a seed is an upsert, and a changed one is applied by the next run.\n * Each runs in its own transaction, as the construct's owner, so a failure\n * leaves nothing half-written. Assumes the schema is migrated —\n * {@link migrateAndSeed} is what makes that true.\n */\nexport async function seedDatabases(options: SeedOptions): Promise<SeedRun[]> {\n\tconst runs: SeedRun[] = [];\n\n\tfor (const target of await targetsFor(options)) {\n\t\tconst folder = join(options.root, target.seeds);\n\t\tif ((await migrationFiles(folder)).size === 0) {\n\t\t\truns.push({ target, seeded: [] });\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst seeded = await withOwner(options, target, async (db, kysely) => {\n\t\t\tconst names: string[] = [];\n\t\t\tfor (const seed of await loadSeeds(folder, kysely.sql)) {\n\t\t\t\ttry {\n\t\t\t\t\tawait db\n\t\t\t\t\t\t.transaction()\n\t\t\t\t\t\t.execute((trx) => seed.run(trx, { stage: options.stage }));\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthrow new SeedFailed(target.id, seed.name, error);\n\t\t\t\t}\n\t\t\t\tnames.push(seed.name);\n\t\t\t}\n\t\t\treturn names;\n\t\t});\n\n\t\truns.push({ target, seeded });\n\t}\n\n\treturn runs;\n}\n\n/**\n * The seeds {@link seedDatabases} would run, construct by construct, parents\n * first, in the order it would run them — read from the folders alone, with\n * no connection. What a dry run lists. Constructs with none are left out.\n */\nexport async function plannedSeeds(\n\toptions: Pick<DatabasesOptions, 'root' | 'manifest' | 'only'>,\n): Promise<PlannedSeeds[]> {\n\tconst planned: PlannedSeeds[] = [];\n\tfor (const target of await targetsFor(options)) {\n\t\tconst files = await migrationFiles(join(options.root, target.seeds));\n\t\tif (files.size === 0) continue;\n\t\tplanned.push({ target, seeds: [...files.keys()].sort() });\n\t}\n\treturn planned;\n}\n\n/**\n * Migrate, then seed: a seed only ever runs against the schema it was written\n * for.\n *\n * With `only`, the constructs it lives in are migrated too — a tenant's seed\n * cannot run before the database that owns its schema exists — and only it is\n * seeded.\n */\nexport async function migrateAndSeed(options: SeedOptions): Promise<{\n\tmigrations: MigrationRun[];\n\tseeds: SeedRun[];\n}> {\n\tconst migrations: MigrationRun[] = [];\n\tfor (const target of await withParents(options)) {\n\t\tmigrations.push(\n\t\t\t...(await migrateDatabases({ ...options, only: target.id })),\n\t\t);\n\t}\n\n\treturn { migrations, seeds: await seedDatabases(options) };\n}\n\n/** The targets `only` names, after the ones it lives in; all without it. */\nasync function withParents(\n\toptions: DatabasesOptions,\n): Promise<MigrationTarget[]> {\n\tconst chosen = await targetsFor(options);\n\tif (!options.only) return chosen;\n\n\tconst all = migrationTargets(options.manifest);\n\tconst ids = new Set<string>();\n\tfor (const target of chosen) {\n\t\tlet current: MigrationTarget | undefined = target;\n\t\twhile (current) {\n\t\t\tids.add(current.id);\n\t\t\tconst parent: string | undefined = current.of;\n\t\t\tcurrent = parent ? all.find((t) => t.id === parent) : undefined;\n\t\t}\n\t}\n\n\treturn all.filter((target) => ids.has(target.id));\n}\n\n/**\n * What each construct's folder has that its history does not — without\n * applying any of it. What `gkm dev` reports.\n */\nexport async function pendingMigrations(\n\toptions: DatabasesOptions,\n): Promise<PendingMigrations[]> {\n\tconst found: PendingMigrations[] = [];\n\n\tfor (const target of await targetsFor(options)) {\n\t\tconst files = await migrationFiles(join(options.root, target.migrations));\n\t\tif (files.size === 0) continue;\n\n\t\tconst pending = await withMigrator(options, target, async (migrator) =>\n\t\t\t(await migrator.getMigrations())\n\t\t\t\t.filter((migration) => migration.executedAt === undefined)\n\t\t\t\t.map((migration) => migration.name),\n\t\t);\n\n\t\tif (pending.length > 0) found.push({ target, pending });\n\t}\n\n\treturn found;\n}\n\n/**\n * The targets this run covers, with every folder under `db/` accounted for.\n *\n * A folder that names no construct is an error rather than something to skip:\n * a typo in its name is otherwise a migration that silently never runs.\n */\nasync function targetsFor(\n\toptions: Pick<DatabasesOptions, 'root' | 'manifest' | 'only'>,\n): Promise<MigrationTarget[]> {\n\tconst targets = migrationTargets(options.manifest);\n\tconst folders = targets.map((target) => target.folder);\n\n\tfor (const folder of await foldersIn(join(options.root, MIGRATIONS_ROOT))) {\n\t\tconst relative = `${MIGRATIONS_ROOT}/${folder}`;\n\t\tif (!folders.includes(relative)) {\n\t\t\tthrow new UnknownMigrationFolder(relative, folders);\n\t\t}\n\t\tawait checkLayout(options.root, relative);\n\t}\n\n\tif (!options.only) return targets;\n\n\tconst id = canonicalId(options.only);\n\tconst chosen = targets.filter((target) => target.id === id);\n\tif (chosen.length === 0) {\n\t\tthrow new NoSuchMigrationTarget(\n\t\t\toptions.only,\n\t\t\ttargets.map((target) => target.id),\n\t\t);\n\t}\n\n\treturn chosen;\n}\n\n/**\n * A construct's folder holds `migrations/` and `seeds/`, and nothing else —\n * anything beside them is a file or folder nothing would ever run.\n */\nasync function checkLayout(root: string, folder: string): Promise<void> {\n\tconst entries = await readdir(join(root, folder), { withFileTypes: true });\n\n\tconst loose = entries\n\t\t.filter((entry) => entry.isFile() && !entry.name.startsWith('.'))\n\t\t.map((entry) => entry.name);\n\tif (loose.length > 0) throw new MigrationsOutsideFolder(folder, loose);\n\n\tfor (const entry of entries) {\n\t\tif (\n\t\t\tentry.isDirectory() &&\n\t\t\t!(DATABASE_FOLDERS as readonly string[]).includes(entry.name)\n\t\t) {\n\t\t\tthrow new UnknownDatabaseFolder(`${folder}/${entry.name}`);\n\t\t}\n\t}\n}\n\nasync function foldersIn(dir: string): Promise<string[]> {\n\tconst entries = await readdir(dir, { withFileTypes: true }).catch(\n\t\t(error: NodeJS.ErrnoException) => {\n\t\t\tif (error.code === 'ENOENT') return [];\n\t\t\tthrow error;\n\t\t},\n\t);\n\n\treturn entries\n\t\t.filter((entry) => entry.isDirectory())\n\t\t.map((entry) => entry.name);\n}\n\n/**\n * A migrator for one target, connected as its owner.\n *\n * No `migrationTableSchema`: the owner role's `search_path` is pinned to the\n * construct's schema, so the history lands there — `app.kysely_migration` for\n * the database, `auth_database.kysely_migration` for its tenant — and two\n * constructs in one Postgres never share one.\n *\n * Unordered migrations are allowed. Files are timestamped, so a branch merged\n * after another's migration ran carries an older one; refusing it would make\n * every such merge a manual repair.\n */\nasync function withMigrator<T>(\n\toptions: DatabasesOptions,\n\ttarget: MigrationTarget,\n\trun: (migrator: Migrator) => Promise<T>,\n): Promise<T> {\n\treturn withOwner(options, target, (db, { Migrator, sql }) =>\n\t\trun(\n\t\t\tnew Migrator({\n\t\t\t\tdb,\n\t\t\t\tprovider: folderProvider(join(options.root, target.migrations), sql),\n\t\t\t\tallowUnorderedMigrations: true,\n\t\t\t}),\n\t\t),\n\t);\n}\n\n/**\n * One target's database, connected as its owner and closed afterwards — what\n * migrations and seeds both run as.\n */\nasync function withOwner<T>(\n\toptions: DatabasesOptions,\n\ttarget: MigrationTarget,\n\trun: (\n\t\tdb: Kysely<unknown>,\n\t\tkysely: Awaited<ReturnType<typeof kyselyFrom>>,\n\t) => Promise<T>,\n): Promise<T> {\n\tconst from =\n\t\toptions.sources?.[target.id]?.file ??\n\t\t(target.of ? options.sources?.[target.of]?.file : undefined) ??\n\t\tjoin(options.root, 'package.json');\n\tconst kysely = await kyselyFrom(from);\n\n\tconst db: Kysely<unknown> = new kysely.Kysely<unknown>({\n\t\tdialect: new kysely.PostgresDialect({\n\t\t\tpool: new pg.Pool(poolConfig(ownerUrl(options, target))),\n\t\t}),\n\t});\n\n\ttry {\n\t\treturn await run(db, kysely);\n\t} finally {\n\t\tawait db.destroy();\n\t}\n}\n\n/**\n * The owner's URL, with no fallback to the runtime one.\n *\n * The runtime role can create nothing, so falling back would only move the\n * failure to the first `CREATE TABLE` and make it read like a permissions\n * bug. The one exception is a database declared `roles: false`, where there is\n * no owner — both URLs are the master credential by design.\n */\nfunction ownerUrl(options: DatabasesOptions, target: MigrationTarget): string {\n\tconst key = provideKey(target.id, 'ownerUrl');\n\tconst owner = options.env[key];\n\tif (owner) return owner;\n\n\tconst database = options.manifest[target.of ?? target.id];\n\tif (database?.kind === 'database' && database.roles === false) {\n\t\tconst url = options.env[provideKey(target.id, 'url')];\n\t\tif (url) return url;\n\t}\n\n\tthrow new NoOwnerCredential(target.id, key);\n}\n\n/**\n * Pool settings for a URL, with `?search_path=` as the startup option Postgres\n * reads. Only a `roles: false` URL carries it — a role pins its own.\n */\nfunction poolConfig(url: string): pg.PoolConfig {\n\tconst parsed = new URL(url);\n\tconst searchPath = parsed.searchParams.get('search_path');\n\tif (!searchPath) return { connectionString: url };\n\n\tparsed.searchParams.delete('search_path');\n\treturn {\n\t\tconnectionString: parsed.toString(),\n\t\toptions: `-c search_path=${searchPath}`,\n\t};\n}\n\n/** Kysely as the project resolves it from `from`. */\nasync function kyselyFrom(from: string) {\n\tconst require = createRequire(from);\n\n\ttry {\n\t\tconst [main, migration] = await Promise.all([\n\t\t\timport(pathToFileURL(require.resolve('kysely')).href),\n\t\t\timport(pathToFileURL(require.resolve('kysely/migration')).href),\n\t\t]);\n\n\t\treturn {\n\t\t\tKysely: main.Kysely as typeof import('kysely').Kysely,\n\t\t\tPostgresDialect:\n\t\t\t\tmain.PostgresDialect as typeof import('kysely').PostgresDialect,\n\t\t\tsql: main.sql as typeof import('kysely').sql,\n\t\t\tMigrator:\n\t\t\t\tmigration.Migrator as typeof import('kysely/migration').Migrator,\n\t\t};\n\t} catch {\n\t\tthrow new KyselyNotFound(from);\n\t}\n}\n"],"mappings":"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"}
package/dist/index.cjs CHANGED
@@ -53,7 +53,7 @@ crypto = require_rolldown_runtime.__toESM(crypto, 1);
53
53
  let _geekmidas_constructs_functions = require("@geekmidas/constructs/functions");
54
54
  let _geekmidas_constructs_topic = require("@geekmidas/constructs/topic");
55
55
  //#region package.json
56
- var version = "10.0.0-alpha.87";
56
+ var version = "10.0.0-alpha.88";
57
57
  //#endregion
58
58
  //#region src/prompt.ts
59
59
  /**
@@ -499,6 +499,54 @@ var UnsupportedPlatform = class extends Error {
499
499
  }
500
500
  };
501
501
  //#endregion
502
+ //#region src/workspace/stageTypes.ts
503
+ /**
504
+ * `.gkm/stages.d.ts`: the declared stages, as types application code can see.
505
+ *
506
+ * Constructs and app code cannot import `gkm.config.ts`, so the stage names it
507
+ * declares were plain strings everywhere else. This writes them into
508
+ * `@geekmidas/constructs`' `Stages` interface, which `LocalStage`,
509
+ * `DeployedStage` and `AnyStage` read — an `ExternalApi` url keyed `prodution`
510
+ * becomes a type error. Without the file those types are `string`.
511
+ *
512
+ * Written by `gkm dev`, `gkm build` and `gkm test --prepare`, beside the
513
+ * generated clients; a project's tsconfig `include`s it.
514
+ */
515
+ /** Where the declaration is written, relative to the workspace root. */
516
+ const STAGE_TYPES_FILE = ".gkm/stages.d.ts";
517
+ /** A union of string literal types: `'staging' | 'prod'`, or `never`. */
518
+ function union(names) {
519
+ return names.length ? names.map((name) => `'${name}'`).join(" | ") : "never";
520
+ }
521
+ /** The declaration for a stages block. */
522
+ function stageTypesDeclaration(stages) {
523
+ return `// Generated by gkm from the \`stages\` in gkm.config.ts — do not edit.
524
+ // \`gkm dev\`, \`gkm build\` and \`gkm test --prepare\` rewrite it.
525
+ export {};
526
+
527
+ declare module '@geekmidas/constructs' {
528
+ interface Stages {
529
+ local: ${union([stages.local])};
530
+ deployed: ${union(stages.deployed)};
531
+ }
532
+ }
533
+ `;
534
+ }
535
+ /**
536
+ * Write `.gkm/stages.d.ts` under the workspace root, and return its path — or
537
+ * nothing when there are no stages to write. Left untouched when unchanged, so
538
+ * an editor's watcher does not re-check the project on every start.
539
+ */
540
+ async function writeStageTypes(root, stages) {
541
+ if (!stages) return void 0;
542
+ const path = (0, node_path.join)(root, STAGE_TYPES_FILE);
543
+ const content = stageTypesDeclaration(stages);
544
+ if (await (0, node_fs_promises.readFile)(path, "utf8").catch(() => void 0) === content) return path;
545
+ await (0, node_fs_promises.mkdir)((0, node_path.join)(root, ".gkm"), { recursive: true });
546
+ await (0, node_fs_promises.writeFile)(path, content);
547
+ return path;
548
+ }
549
+ //#endregion
502
550
  //#region src/dev/credentials.ts
503
551
  /**
504
552
  * `gkm dev:credentials` — where to sign in to each local service, and as
@@ -1812,8 +1860,10 @@ async function devCommand(options) {
1812
1860
  if (!appConfig) {
1813
1861
  logger$5.log("🔎 Reading gkm.config.ts and the constructs");
1814
1862
  const { workspace: everything } = await require_config.loadWorkspaceConfig();
1863
+ await writeStageTypes(everything.root, everything.stages);
1815
1864
  return workspaceDevCommand(everything, options);
1816
1865
  }
1866
+ await writeStageTypes(appConfig.workspace.root, appConfig.workspace.stages);
1817
1867
  const config = appConfig.gkmConfig;
1818
1868
  const appRoot = appConfig.appRoot;
1819
1869
  const secretsRoot = appConfig.workspaceRoot;
@@ -3144,6 +3194,7 @@ var UnknownCacheBackend = class extends Error {
3144
3194
  };
3145
3195
  async function buildCommand(options) {
3146
3196
  const loadedConfig = await require_config.loadWorkspaceConfig();
3197
+ await writeStageTypes(loadedConfig.workspace.root, loadedConfig.workspace.stages);
3147
3198
  if (loadedConfig.type === "workspace") {
3148
3199
  const cwd = (0, node_path.resolve)(process.cwd());
3149
3200
  const workspaceRoot = (0, node_path.resolve)(loadedConfig.workspace.root);
@@ -5225,25 +5276,25 @@ function loadPackageJson() {
5225
5276
  * Run: pnpm --filter @geekmidas/cli sync-versions
5226
5277
  */
5227
5278
  const GEEKMIDAS_VERSIONS = {
5228
- "@geekmidas/audit": "~10.0.0-alpha.87",
5229
- "@geekmidas/auth": "~10.0.0-alpha.87",
5230
- "@geekmidas/cache": "~10.0.0-alpha.87",
5231
- "@geekmidas/client": "~10.0.0-alpha.87",
5232
- "@geekmidas/cloud": "~10.0.0-alpha.87",
5233
- "@geekmidas/constructs": "~10.0.0-alpha.87",
5234
- "@geekmidas/db": "~10.0.0-alpha.87",
5235
- "@geekmidas/emailkit": "~10.0.0-alpha.87",
5236
- "@geekmidas/envkit": "~10.0.0-alpha.87",
5237
- "@geekmidas/errors": "~10.0.0-alpha.87",
5238
- "@geekmidas/events": "~10.0.0-alpha.87",
5239
- "@geekmidas/logger": "~10.0.0-alpha.87",
5240
- "@geekmidas/manifest": "~10.0.0-alpha.87",
5241
- "@geekmidas/rate-limit": "~10.0.0-alpha.87",
5242
- "@geekmidas/schema": "~10.0.0-alpha.87",
5243
- "@geekmidas/services": "~10.0.0-alpha.87",
5244
- "@geekmidas/storage": "~10.0.0-alpha.87",
5245
- "@geekmidas/telescope": "~10.0.0-alpha.87",
5246
- "@geekmidas/testkit": "~10.0.0-alpha.87",
5279
+ "@geekmidas/audit": "~10.0.0-alpha.88",
5280
+ "@geekmidas/auth": "~10.0.0-alpha.88",
5281
+ "@geekmidas/cache": "~10.0.0-alpha.88",
5282
+ "@geekmidas/client": "~10.0.0-alpha.88",
5283
+ "@geekmidas/cloud": "~10.0.0-alpha.88",
5284
+ "@geekmidas/constructs": "~10.0.0-alpha.88",
5285
+ "@geekmidas/db": "~10.0.0-alpha.88",
5286
+ "@geekmidas/emailkit": "~10.0.0-alpha.88",
5287
+ "@geekmidas/envkit": "~10.0.0-alpha.88",
5288
+ "@geekmidas/errors": "~10.0.0-alpha.88",
5289
+ "@geekmidas/events": "~10.0.0-alpha.88",
5290
+ "@geekmidas/logger": "~10.0.0-alpha.88",
5291
+ "@geekmidas/manifest": "~10.0.0-alpha.88",
5292
+ "@geekmidas/rate-limit": "~10.0.0-alpha.88",
5293
+ "@geekmidas/schema": "~10.0.0-alpha.88",
5294
+ "@geekmidas/services": "~10.0.0-alpha.88",
5295
+ "@geekmidas/storage": "~10.0.0-alpha.88",
5296
+ "@geekmidas/telescope": "~10.0.0-alpha.88",
5297
+ "@geekmidas/testkit": "~10.0.0-alpha.88",
5247
5298
  "@geekmidas/cli": `~${loadPackageJson().version}`
5248
5299
  };
5249
5300
  //#endregion
@@ -5294,7 +5345,7 @@ function generateAuthAppFiles(options) {
5294
5345
  [`@${options.name}/*`]: ["../../packages/*/src"]
5295
5346
  }
5296
5347
  },
5297
- include: ["src/**/*.ts"],
5348
+ include: ["src/**/*.ts", "../../.gkm/stages.d.ts"],
5298
5349
  exclude: ["node_modules", "dist"]
5299
5350
  };
5300
5351
  return [
@@ -5405,7 +5456,7 @@ export default defineConfig({${stagesBlock(options.stages)}${options.monorepo ?
5405
5456
  gkmConfig += `
5406
5457
  });
5407
5458
  `;
5408
- const tsConfig = options.monorepo ? workspaceApiTsConfig(options.name) : {
5459
+ const tsConfig = options.monorepo ? workspaceApiTsConfig(options.name, ".gkm/stages.d.ts") : {
5409
5460
  compilerOptions: {
5410
5461
  target: "ES2022",
5411
5462
  module: "NodeNext",
@@ -5420,7 +5471,7 @@ export default defineConfig({${stagesBlock(options.stages)}${options.monorepo ?
5420
5471
  allowImportingTsExtensions: true,
5421
5472
  paths: { "~/*": ["./src/*"] }
5422
5473
  },
5423
- include: ["src/**/*.ts"],
5474
+ include: ["src/**/*.ts", ".gkm/stages.d.ts"],
5424
5475
  exclude: ["node_modules", "dist"]
5425
5476
  };
5426
5477
  if (options.monorepo) {
@@ -5535,7 +5586,7 @@ export default defineConfig({${stagesBlock(options.stages)}${options.monorepo ?
5535
5586
  * the root constructs, so in the other `@<name>/constructs/api.ts` fell through
5536
5587
  * to the `packages/` mapping and nothing could load the surface.
5537
5588
  */
5538
- function workspaceApiTsConfig(name) {
5589
+ function workspaceApiTsConfig(name, stageTypes) {
5539
5590
  return {
5540
5591
  extends: "../../tsconfig.json",
5541
5592
  compilerOptions: {
@@ -5547,12 +5598,12 @@ function workspaceApiTsConfig(name) {
5547
5598
  [`@${name}/*`]: ["../../packages/*/src"]
5548
5599
  }
5549
5600
  },
5550
- include: ["src/**/*.ts"],
5601
+ include: ["src/**/*.ts", stageTypes],
5551
5602
  exclude: ["node_modules", "dist"]
5552
5603
  };
5553
5604
  }
5554
5605
  function generateSingleAppConfigFiles(options, _template, _helpers) {
5555
- const tsConfig = workspaceApiTsConfig(options.name);
5606
+ const tsConfig = workspaceApiTsConfig(options.name, "../../.gkm/stages.d.ts");
5556
5607
  const files = [{
5557
5608
  path: "tsconfig.json",
5558
5609
  content: `${JSON.stringify(tsConfig, null, 2)}\n`
@@ -5644,7 +5695,7 @@ const COMMIT = /^[0-9a-f]{40}$/;
5644
5695
  * The `uses:` value for the stages action, with a comment saying which CLI
5645
5696
  * release the pinned commit is.
5646
5697
  */
5647
- function stagesActionUses(commit = "8bf34e377e5def9821eacaea3971d6daf6c91748", version = cliVersion()) {
5698
+ function stagesActionUses(commit = "5bd5a7422c74b30cb3c285d0717da882171c103a", version = cliVersion()) {
5648
5699
  return commit && COMMIT.test(commit) ? `${STAGES_ACTION}@${commit} # @geekmidas/cli ${version}` : `${STAGES_ACTION}@main # pin to a commit SHA: see the action's README`;
5649
5700
  }
5650
5701
  //#endregion
@@ -7404,6 +7455,7 @@ ${isSst ? "\n# SST\n.sst/\n" : ""}`;
7404
7455
  forceConsistentCasingInFileNames: true,
7405
7456
  resolveJsonModule: true
7406
7457
  },
7458
+ include: ["**/*", ".gkm/stages.d.ts"],
7407
7459
  exclude: [
7408
7460
  "node_modules",
7409
7461
  "dist",
@@ -11266,6 +11318,7 @@ async function testCommand(options = {}) {
11266
11318
  const preloadPath = (0, node_path.join)(gkmDir, "test-credentials-preload.ts");
11267
11319
  await require_seeds.createCredentialsPreload(preloadPath, result.secretsJsonPath);
11268
11320
  const workspace = await require_config.loadWorkspaceConfig(cwd).then((loaded) => loaded.workspace).catch(() => void 0);
11321
+ if (workspace) await writeStageTypes(workspace.root, workspace.stages);
11269
11322
  const manifestPath = workspace ? await require_ready.writeTestHarness({
11270
11323
  root: workspace.root,
11271
11324
  targets: [workspace.root, ...Object.values(workspace.apps).map((app) => (0, node_path.isAbsolute)(app.path) ? app.path : (0, node_path.join)(workspace.root, app.path))],