@happyvertical/smrt-core 0.45.3 → 0.47.0
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.
- package/AGENTS.md +39 -0
- package/agents/generators.md +0 -1
- package/dist/browser.d.ts +2 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/decorators/compatibility.d.ts +46 -0
- package/dist/decorators/compatibility.d.ts.map +1 -1
- package/dist/decorators/compatibility.js +48 -5
- package/dist/decorators/compatibility.js.map +1 -1
- package/dist/decorators/index.d.ts +119 -1
- package/dist/decorators/index.d.ts.map +1 -1
- package/dist/decorators/index.js +52 -8
- package/dist/decorators/index.js.map +1 -1
- package/dist/embedded-write-queue.d.ts.map +1 -1
- package/dist/embedded-write-queue.js +20 -1
- package/dist/embedded-write-queue.js.map +1 -1
- package/dist/generators/custom-action.d.ts +450 -41
- package/dist/generators/custom-action.d.ts.map +1 -1
- package/dist/generators/custom-action.js +765 -56
- package/dist/generators/custom-action.js.map +1 -1
- package/dist/generators/index.d.ts +1 -3
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +3 -4
- package/dist/generators/mcp.d.ts.map +1 -1
- package/dist/generators/mcp.js +1 -1
- package/dist/generators/mcp.js.map +1 -1
- package/dist/generators/preflight-route.d.ts +12 -10
- package/dist/generators/preflight-route.d.ts.map +1 -1
- package/dist/generators/preflight-route.js +46 -14
- package/dist/generators/preflight-route.js.map +1 -1
- package/dist/generators/rest.d.ts +44 -0
- package/dist/generators/rest.d.ts.map +1 -1
- package/dist/generators/rest.js +84 -9
- package/dist/generators/rest.js.map +1 -1
- package/dist/generators/tenant-gate.d.ts +33 -9
- package/dist/generators/tenant-gate.d.ts.map +1 -1
- package/dist/generators/tenant-gate.js +2 -1
- package/dist/generators/tenant-gate.js.map +1 -1
- package/dist/generators.js +3 -4
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -6
- package/dist/knowledge.d.ts.map +1 -1
- package/dist/knowledge.js +87 -31
- package/dist/knowledge.js.map +1 -1
- package/dist/manifest/static-manifest.d.ts.map +1 -1
- package/dist/manifest/static-manifest.js +125 -53
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.js +1 -1
- package/dist/manifest/store.js.map +1 -1
- package/dist/manifest.json +184 -53
- package/dist/migrations/framework-base-tables.d.ts +148 -0
- package/dist/migrations/framework-base-tables.d.ts.map +1 -0
- package/dist/migrations/framework-base-tables.js +419 -0
- package/dist/migrations/framework-base-tables.js.map +1 -0
- package/dist/migrations/index.d.ts +1 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/migrations/index.js +2 -1
- package/dist/migrations.js +2 -1
- package/dist/postgres-permissions.d.ts +45 -0
- package/dist/postgres-permissions.d.ts.map +1 -0
- package/dist/postgres-permissions.js +424 -0
- package/dist/postgres-permissions.js.map +1 -0
- package/dist/registry/framework-base-classes.d.ts.map +1 -1
- package/dist/registry/framework-base-classes.js +4 -1
- package/dist/registry/framework-base-classes.js.map +1 -1
- package/dist/registry/index.d.ts +1 -1
- package/dist/registry/index.d.ts.map +1 -1
- package/dist/registry/shared-state.d.ts +19 -0
- package/dist/registry/shared-state.d.ts.map +1 -1
- package/dist/registry/shared-state.js +16 -1
- package/dist/registry/shared-state.js.map +1 -1
- package/dist/registry.d.ts +136 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +231 -1
- package/dist/registry.js.map +1 -1
- package/dist/scanner/manifest-generator.d.ts.map +1 -1
- package/dist/scanner/manifest-generator.js +18 -16
- package/dist/scanner/manifest-generator.js.map +1 -1
- package/dist/scanner/types.d.ts +59 -6
- package/dist/scanner/types.d.ts.map +1 -1
- package/dist/scanner/types.js.map +1 -1
- package/dist/scripts/create-wrappers.js +0 -5
- package/dist/smrt-knowledge.json +35 -35
- package/dist/testing/database.d.ts +6 -1
- package/dist/testing/database.d.ts.map +1 -1
- package/dist/testing/database.js +9 -1
- package/dist/testing/database.js.map +1 -1
- package/dist/vite-plugin/api-client-entries.d.ts.map +1 -1
- package/dist/vite-plugin/api-client-entries.js +23 -19
- package/dist/vite-plugin/api-client-entries.js.map +1 -1
- package/dist/vite-plugin/index.d.ts.map +1 -1
- package/dist/vite-plugin/index.js +3 -167
- package/dist/vite-plugin/index.js.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.d.ts +31 -4
- package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.js +137 -71
- package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
- package/dist/vite-plugin/templates/default-ui.ts +22 -1
- package/dist/vite-plugin/web-collections.d.ts.map +1 -1
- package/dist/vite-plugin/web-collections.js +1 -1
- package/dist/vite-plugin/web-collections.js.map +1 -1
- package/package.json +10 -10
- package/dist/generators/cli.d.ts +0 -155
- package/dist/generators/cli.d.ts.map +0 -1
- package/dist/generators/cli.js +0 -473
- package/dist/generators/cli.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"framework-base-tables.js","names":[],"sources":["../../src/migrations/framework-base-tables.ts"],"sourcesContent":["/**\n * One-time remediation for the five orphaned framework-base tables (#2647).\n *\n * #2644 stopped `getAllSchemasAsDefinitions()` from planning a table for\n * SMRT's abstract framework-base classes (`SmrtObject`, `SmrtClass`,\n * `SmrtCollection`, `SmrtHierarchical`, `SmrtPolymorphicAssociation`), but it\n * does not remove the ones a pre-fix `db:migrate` already created. Any\n * deployment that ran `db:migrate` before that fix has all five tables, and\n * the differ now reports them as permanent orphans (#2369's\n * `includeDroppedTables` stays `false` on purpose — see below).\n *\n * This module is a narrow, named remediation, deliberately **not** a change\n * to the general differ: `SchemaDiff.orphan_tables` already reports orphan\n * tables, and `includeDroppedTables` (default `false`, both call sites pass\n * it explicitly) is the opt-in for the executable `DROP TABLE`. That default\n * must stay `false` — flipping it globally would make `db:migrate` drop *any*\n * table absent from the manifest, which for a consumer with hand-managed or\n * legacy tables is unacceptable data loss. Dropping only these five specific,\n * hand-typed names is safe *because* nothing but the retired base-class\n * planning path could ever have created them; a general orphan drop is not.\n *\n * Safety model, in order:\n * 1. {@link FRAMEWORK_BASE_TABLE_NAMES} is a literal, hand-typed list. It is\n * never derived from a differ orphan report or any other dynamic source —\n * doing so would recreate the dangerous global-drop path by another route.\n * 2. {@link planFrameworkBaseTableDrop} is read-only. For every name in the\n * list that exists live, it verifies the table has *only* that table's\n * expected columns — the universal `id`/`slug`/`context`/`created_at`/\n * `updated_at` base for three of the five, extended with the real fields\n * `SmrtHierarchical`/`SmrtPolymorphicAssociation` themselves declare for\n * `smrt_hierarchicals`/`smrt_polymorphic_associations` (see\n * {@link EXPECTED_COLUMN_BUCKETS_BY_TABLE}) — with a plausible type for\n * each, is referenced by no foreign key anywhere in the live database,\n * and is empty. Any live table missing that shape — e.g. a consumer's\n * own, unrelated table that happens to share one of these five names —\n * is reported as unsafe and dropped nothing.\n * 3. {@link dropFrameworkBaseTables} refuses to run against a plan that is\n * not `safe`, locks every PostgreSQL target `IN ACCESS EXCLUSIVE MODE`\n * (a plain `SELECT COUNT(*)` alone would not block a concurrent writer),\n * and then re-verifies shape, type, and emptiness — not just the row\n * count — inside that same bounded transaction immediately before\n * dropping anything. A table rewritten or replaced between planning and\n * execution therefore still stops the whole batch, not just a row-count\n * race. Foreign keys are checked at planning time only. On PostgreSQL, a\n * foreign key added afterward is still caught — its FK enforcement is\n * dependency-based, so `DROP TABLE` refuses when a real dependent\n * exists. This does **not** hold on SQLite/DuckDB: SQLite's `DROP TABLE`\n * only enforces against the rows actually being removed, so an empty\n * parent (exactly this function's precondition) drops cleanly past a\n * real foreign key, and DuckDB's adapter cannot even see the reference\n * at plan time (see {@link qualifyIdentifier}'s doc comment). No data is\n * lost either way — the residual risk on those two engines is a\n * dangling reference, not data loss, confined to a narrow window a\n * one-time operator-run command makes unlikely in practice.\n */\n\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { detectEngine } from '../schema/ddl/index.js';\nimport type { DatabaseEngine } from '../schema/ddl/types.js';\nimport { quoteIdentifier } from '../schema/sql-identifiers.js';\nimport { toSafeInteger } from '../utils/safe-integer.js';\n\n/**\n * The exact five orphaned framework-base tables #2644 stopped planning.\n *\n * NEVER derive this list from `SchemaDiff.orphan_tables` or any other dynamic\n * introspection — that would turn this narrow remediation into the same\n * dangerous global orphan-drop that `includeDroppedTables` deliberately\n * refuses to be. Every entry is a plain string literal, on purpose.\n */\nexport const FRAMEWORK_BASE_TABLE_NAMES = [\n 'smrt_objects',\n 'smrt_classes',\n 'smrt_collections',\n 'smrt_hierarchicals',\n 'smrt_polymorphic_associations',\n] as const;\n\nexport type FrameworkBaseTableName =\n (typeof FRAMEWORK_BASE_TABLE_NAMES)[number];\n\n/**\n * Every one of the five tables carries this universal base — but two of\n * them are not plain aliases of it. `SmrtHierarchical` and\n * `SmrtPolymorphicAssociation` are abstract classes that *extend* the\n * universal shape with their own real fields (true parent-id trees; the\n * meta/role/sort columns generic associations need), exactly like any\n * concrete class extending them would inherit those fields. Verified\n * directly against a genuine pre-#2644 `db:migrate` run (a real installed\n * multi-package consumer, not hand-typed DDL): `smrt_hierarchicals` and\n * `smrt_polymorphic_associations` are generated with the extra columns\n * below every time, on every engine. A per-table expectation is therefore\n * required — treating all five as the plain 5-column baseline would refuse\n * to ever drop these two genuine framework-base tables, reporting their own\n * real columns as \"unexpected\".\n */\nconst UNIVERSAL_BASE_COLUMNS = [\n 'id',\n 'slug',\n 'context',\n 'created_at',\n 'updated_at',\n] as const;\n\n/**\n * Coarse, dialect-tolerant type buckets. A live column with a matching\n * *name* but an unrelated *type* (`id INTEGER`, `context BOOLEAN`, ...) is\n * not a genuine framework-base table — it is a consumer's own table that\n * happens to share every column name — and a name-only check alone cannot\n * see that.\n *\n * Buckets, not exact strings: PostgreSQL may report `TIMESTAMP` or\n * `TIMESTAMPTZ` depending on configuration, DuckDB normalizes `TEXT` to\n * `VARCHAR`, and SQLite's generator emits `DATETIME` — all legitimate for a\n * real framework-base table on their engine. Only a column outside its\n * expected bucket entirely (an integer where text was expected, a boolean,\n * a float) is refused.\n */\ntype ColumnTypeBucket = 'text' | 'uuid' | 'timestamp' | 'integer' | 'other';\n\nfunction classifyColumnType(type: string): ColumnTypeBucket {\n const normalized = type\n .toUpperCase()\n .trim()\n .replace(/\\(\\s*\\d+\\s*\\)/g, '');\n if (/^UUID$/.test(normalized)) return 'uuid';\n if (/^(TEXT|CLOB|STRING|VARCHAR|CHAR)/.test(normalized)) return 'text';\n if (/^(TIMESTAMP|DATETIME|DATE)/.test(normalized)) return 'timestamp';\n if (/^(INTEGER|INT|BIGINT|SMALLINT|TINYINT)$/.test(normalized)) {\n return 'integer';\n }\n return 'other';\n}\n\nconst UNIVERSAL_BASE_BUCKETS: Record<\n (typeof UNIVERSAL_BASE_COLUMNS)[number],\n readonly ColumnTypeBucket[]\n> = {\n id: ['text', 'uuid'],\n slug: ['text'],\n context: ['text'],\n created_at: ['timestamp'],\n updated_at: ['timestamp'],\n};\n\n/**\n * The exact columns (and their expected type buckets) each of the five\n * tables may have — the universal base for three of them, extended for\n * `smrt_hierarchicals` (true parent-id tree: `parent_id`) and\n * `smrt_polymorphic_associations` (generic association: `meta_type`,\n * `meta_id`, `role`, `sort_order`), matching {@link SmrtHierarchical} /\n * {@link SmrtPolymorphicAssociation}'s own real field declarations exactly.\n * Anything more, or anything missing, on any of the five means the live\n * table is not what this remediation expects — most likely a consumer's\n * own unrelated table that happens to share the name — and must be refused\n * rather than guessed about.\n */\nconst EXPECTED_COLUMN_BUCKETS_BY_TABLE: Record<\n FrameworkBaseTableName,\n Readonly<Record<string, readonly ColumnTypeBucket[]>>\n> = {\n smrt_objects: UNIVERSAL_BASE_BUCKETS,\n smrt_classes: UNIVERSAL_BASE_BUCKETS,\n smrt_collections: UNIVERSAL_BASE_BUCKETS,\n smrt_hierarchicals: {\n ...UNIVERSAL_BASE_BUCKETS,\n parent_id: ['text', 'uuid'],\n },\n smrt_polymorphic_associations: {\n ...UNIVERSAL_BASE_BUCKETS,\n meta_type: ['text'],\n meta_id: ['text', 'uuid'],\n role: ['text'],\n sort_order: ['integer'],\n },\n};\n\nconst DEFAULT_POSTGRES_LOCK_TIMEOUT_MS = 30_000;\nconst DEFAULT_POSTGRES_STATEMENT_TIMEOUT_MS = 60_000;\n\n/** Why a target table was refused. A table can carry more than one. */\nexport type FrameworkBaseTableRefusal =\n | { kind: 'not-empty'; rowCount: number }\n | {\n kind: 'unexpected-shape';\n actualColumns: string[];\n missingColumns: string[];\n extraColumns: string[];\n }\n | {\n kind: 'unexpected-column-type';\n mismatches: Array<{\n column: string;\n actualType: string;\n expectedBuckets: string[];\n }>;\n }\n | {\n kind: 'referenced-by-foreign-key';\n references: Array<{ table: string; column: string }>;\n }\n | { kind: 'introspection-unavailable'; reason: string };\n\n/** Plan/refusal findings for one of the five candidate tables. */\nexport interface FrameworkBaseTableReport {\n table: FrameworkBaseTableName;\n /** Whether this table exists in the live database at all. */\n exists: boolean;\n /** `null` when the table does not exist or its row count was not read. */\n rowCount: number | null;\n /** Live index names on this table, enumerated from the schema — never guessed. */\n indexNames: string[];\n /** Empty when this table (if it exists) is safe to drop. */\n refusals: FrameworkBaseTableRefusal[];\n}\n\n/** The full remediation plan: what would be dropped, and why it is or isn't safe. */\nexport interface FrameworkBaseTablesPlan {\n engine: DatabaseEngine;\n /** One entry per {@link FRAMEWORK_BASE_TABLE_NAMES}, in that order. */\n tables: FrameworkBaseTableReport[];\n /** True only when every existing target table has zero refusals. */\n safe: boolean;\n /**\n * The plan for the `--dry-run` preview: `DROP INDEX` for every companion\n * index (enumerated from the live schema, never guessed) followed by\n * `DROP TABLE` for its table, per existing target table. Empty when\n * `safe` is `false` or no target table exists.\n *\n * {@link dropFrameworkBaseTables} does not execute the `DROP INDEX`\n * entries verbatim — see its doc comment for why — so this array is a\n * complete and accurate *forecast* of what a real run does to the\n * database, but is not literally replayed statement-by-statement.\n */\n statements: string[];\n}\n\nexport interface PlanFrameworkBaseTableDropOptions {\n /** Adapter hint when the database URL does not identify the engine. */\n engineHint?: string;\n}\n\nfunction resolveDatabaseUrl(db: DatabaseInterface): string {\n const dbWithConfig = db as DatabaseInterface & { config?: { url?: string } };\n return db.url || dbWithConfig.config?.url || '';\n}\n\n/**\n * Qualify a table or index identifier so execution resolves to exactly the\n * object inspection looked at.\n *\n * `getExistingTableNames()` and `getTableSchema()` both scope PostgreSQL\n * discovery to the `public` schema, but a plain `quoteIdentifier(name)` in a\n * `SELECT`/`DROP` statement resolves through the session's `search_path`\n * instead — which can list another schema before `public`. An empty,\n * unrelated same-named table or index earlier on that path would otherwise\n * satisfy every safety check yet let the DROP hit a different object than\n * the one just verified. SQLite and DuckDB have no equivalent search-path\n * ambiguity for this module's purposes, so only PostgreSQL is schema-qualified.\n *\n * Known limitation, documented rather than fixed: this module (like\n * `differ.ts` and `live-parity.ts` elsewhere in this package) only ever\n * discovers PostgreSQL objects in the `public` schema — multi-schema\n * PostgreSQL deployments are not a supported SMRT configuration anywhere in\n * this package. A table in a *different* schema with a foreign key onto one\n * of these five names is therefore invisible to the `referenced-by-foreign-key`\n * check. It is not, however, an actual data-loss risk: PostgreSQL's own\n * foreign-key enforcement refuses the `DROP TABLE` at execution time\n * (\"cannot drop table ... because other objects depend on it\") inside this\n * module's bounded transaction, so nothing is still dropped — exactly the\n * same fail-safe shape as the documented DuckDB foreign-key gap below, just\n * surfaced as a generic execution error instead of a curated refusal.\n */\nfunction qualifyIdentifier(engine: DatabaseEngine, name: string): string {\n return engine === 'postgres'\n ? `\"public\".${quoteIdentifier(name)}`\n : quoteIdentifier(name);\n}\n\nasync function getExistingTableNames(\n db: DatabaseInterface,\n engine: DatabaseEngine,\n): Promise<Set<string>> {\n const query =\n engine === 'postgres'\n ? `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'`\n : // SQLite and DuckDB both expose the SQLite-compatible `sqlite_master`\n // catalog (differ.ts's `getExistingTables()` verified this against a\n // live DuckDB build; see that comment for detail).\n `SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'`;\n\n const result = await db.query(query);\n const rows = result.rows as { name?: string; table_name?: string }[];\n return new Set(\n rows.map((row) => row.name || row.table_name || '').filter(Boolean),\n );\n}\n\nasync function countRows(\n db: DatabaseInterface,\n engine: DatabaseEngine,\n table: string,\n): Promise<number> {\n const result = await db.query(\n `SELECT COUNT(*) AS row_count FROM ${qualifyIdentifier(engine, table)}`,\n );\n return toSafeInteger(\n result.rows?.[0]?.row_count ?? 0,\n `Framework base-table row count for ${table}`,\n );\n}\n\n/**\n * Render a millisecond timeout as a PostgreSQL interval literal.\n *\n * Mirrors `formatPostgresTimeout` in `migrations/tracker.ts` (#2362) — kept\n * as a tiny local copy rather than an import so this module stays a\n * self-contained, easily audited remediation rather than reaching into the\n * migration tracker's internals for one string helper.\n */\nfunction formatPostgresTimeout(milliseconds: number): string {\n return `${Math.max(0, Math.trunc(milliseconds))}ms`;\n}\n\n/**\n * Column name → declared type, read with a query every engine supports\n * *inside a transaction* — unlike `getTableSchema()`, which\n * `@happyvertical/sql` does not expose on the connection object a\n * `db.transaction()` callback receives (verified directly: `typeof\n * tx.getTableSchema` is `undefined`). Used only for the execution-time\n * re-check in {@link dropFrameworkBaseTables}; planning uses the richer\n * `getTableSchema()` on the ordinary (non-transactional) connection.\n *\n * Returns `null` when the table cannot be described right now — SQLite\n * returns zero rows for an unknown table, DuckDB instead throws a Catalog\n * Error (caught below) — either way meaning \"cannot confirm this is safe\",\n * which must fail closed exactly like a table that no longer exists.\n */\nasync function inspectColumnTypes(\n db: DatabaseInterface,\n engine: DatabaseEngine,\n table: string,\n): Promise<Record<string, string> | null> {\n try {\n if (engine === 'postgres') {\n const result = await db.query(\n `SELECT column_name, data_type FROM information_schema.columns WHERE table_schema = 'public' AND table_name = $1`,\n table,\n );\n const rows = result.rows as { column_name: string; data_type: string }[];\n if (rows.length === 0) return null;\n const columns: Record<string, string> = {};\n for (const row of rows) columns[row.column_name] = row.data_type;\n return columns;\n }\n\n const result = await db.query(\n `PRAGMA table_info(${quoteIdentifier(table)})`,\n );\n const rows = result.rows as { name?: string; type?: string }[];\n if (rows.length === 0) return null;\n const columns: Record<string, string> = {};\n for (const row of rows) {\n if (row.name) columns[row.name] = row.type ?? '';\n }\n return columns;\n } catch {\n return null;\n }\n}\n\n/**\n * Compare a freshly-read column map against `table`'s expected shape/type,\n * returning a human-readable reason it is unsafe, or `null` when it matches\n * exactly. Shared by the execution-time re-check so its comparison logic\n * cannot drift from {@link assessTargets}'s own definitions\n * ({@link EXPECTED_COLUMN_BUCKETS_BY_TABLE}, {@link classifyColumnType}).\n */\nfunction describeColumnMismatch(\n table: FrameworkBaseTableName,\n columns: Record<string, string>,\n): string | null {\n const expectedBuckets = EXPECTED_COLUMN_BUCKETS_BY_TABLE[table];\n const expectedColumns = Object.keys(expectedBuckets);\n const actualColumns = Object.keys(columns).sort();\n const missingColumns = expectedColumns.filter(\n (column) => !(column in columns),\n );\n const extraColumns = actualColumns.filter(\n (column) => !(column in expectedBuckets),\n );\n if (missingColumns.length > 0 || extraColumns.length > 0) {\n return `unexpected column shape (actual columns: ${actualColumns.join(', ')})`;\n }\n\n const typeMismatches = expectedColumns\n .map((column) => {\n const actualType = columns[column] ?? '';\n const bucket = classifyColumnType(actualType);\n return expectedBuckets[column].includes(bucket)\n ? null\n : `${column} is \"${actualType}\"`;\n })\n .filter((entry): entry is string => entry !== null);\n if (typeMismatches.length > 0) {\n return `unexpected column type (${typeMismatches.join(', ')})`;\n }\n\n return null;\n}\n\n/**\n * Read-only assessment: for each of the five target names, is it safe to\n * drop, and why or why not?\n *\n * Used only by {@link planFrameworkBaseTableDrop}'s unlocked preflight, via\n * `getTableSchema()`. {@link dropFrameworkBaseTables}'s own execution-time\n * re-check does **not** call this function or reuse its logic wholesale: it\n * re-verifies shape and type from raw `information_schema.columns` /\n * `PRAGMA table_info` instead, because `getTableSchema()` is not available\n * on the transaction-scoped connection a `db.transaction()` callback\n * receives (see that function's own doc comment for the full rationale,\n * including why foreign keys are deliberately not re-scanned there). Never\n * mutates the database.\n */\nasync function assessTargets(\n db: DatabaseInterface,\n engine: DatabaseEngine,\n): Promise<{ tables: FrameworkBaseTableReport[]; safe: boolean }> {\n if (typeof db.getTableSchema !== 'function') {\n // Fail closed for any target that actually exists: without shape\n // introspection we cannot tell a genuine framework-base table apart\n // from a consumer's own unrelated table of the same name. But existence\n // itself only needs a raw catalog query (`getExistingTableNames()`,\n // which never calls `getTableSchema()`), so a database with none of the\n // five tables present is still a safe, clean no-op even on an adapter\n // this degraded — refusing unconditionally here would report five\n // nonexistent tables as unsafe instead of nothing to do.\n const existingTableNames = await getExistingTableNames(db, engine);\n const tables: FrameworkBaseTableReport[] = FRAMEWORK_BASE_TABLE_NAMES.map(\n (name) =>\n existingTableNames.has(name)\n ? {\n table: name,\n exists: true,\n rowCount: null,\n indexNames: [],\n refusals: [\n {\n kind: 'introspection-unavailable' as const,\n reason:\n 'The configured database adapter cannot describe tables (`getTableSchema` is unavailable), so table shape cannot be verified.',\n },\n ],\n }\n : {\n table: name,\n exists: false,\n rowCount: null,\n indexNames: [],\n refusals: [],\n },\n );\n return {\n tables,\n safe: tables.every((table) => table.refusals.length === 0),\n };\n }\n\n const existingTableNames = await getExistingTableNames(db, engine);\n\n // Build one reverse foreign-key index across every live table, not just\n // the five candidates: a real, unrelated table anywhere in the database\n // could reference one of these names, and only a full-catalog scan catches\n // that (mirrors differ.ts's own full-catalog scan for the #2608 uuid\n // convergence plan).\n //\n // Known gap on DuckDB: `@happyvertical/sql`'s DuckDB adapter never\n // populates `getTableSchema().foreignKeys` (verified directly — even a\n // table-level `FOREIGN KEY (...) REFERENCES ...` constraint comes back\n // empty), so this scan cannot see a real inbound reference there and the\n // plan will report the table safe. DuckDB itself still refuses the DROP\n // TABLE with a catalog error inside the bounded transaction in that case,\n // so nothing unsafe is actually dropped — the refusal just surfaces at\n // execution instead of at planning, with DuckDB's own error text instead\n // of `referenced-by-foreign-key`.\n const inboundForeignKeys = new Map<\n string,\n Array<{ table: string; column: string }>\n >();\n for (const liveTable of existingTableNames) {\n const liveSchema = await db.getTableSchema(liveTable);\n for (const foreignKey of liveSchema?.foreignKeys ?? []) {\n const references =\n inboundForeignKeys.get(foreignKey.referencesTable) ?? [];\n references.push({ table: liveTable, column: foreignKey.column });\n inboundForeignKeys.set(foreignKey.referencesTable, references);\n }\n }\n\n const tables: FrameworkBaseTableReport[] = [];\n let safe = true;\n\n for (const name of FRAMEWORK_BASE_TABLE_NAMES) {\n if (!existingTableNames.has(name)) {\n tables.push({\n table: name,\n exists: false,\n rowCount: null,\n indexNames: [],\n refusals: [],\n });\n continue;\n }\n\n const schema = await db.getTableSchema(name);\n const refusals: FrameworkBaseTableRefusal[] = [];\n\n if (!schema) {\n refusals.push({\n kind: 'introspection-unavailable',\n reason: `getTableSchema(\"${name}\") returned no result even though the table exists.`,\n });\n tables.push({\n table: name,\n exists: true,\n rowCount: null,\n indexNames: [],\n refusals,\n });\n safe = false;\n continue;\n }\n\n const expectedBucketsForTable = EXPECTED_COLUMN_BUCKETS_BY_TABLE[name];\n const expectedColumnsForTable = Object.keys(expectedBucketsForTable);\n const actualColumns = Object.keys(schema.columns).sort();\n const missingColumns = expectedColumnsForTable.filter(\n (column) => !schema.columns[column],\n );\n const extraColumns = actualColumns.filter(\n (column) => !(column in expectedBucketsForTable),\n );\n if (missingColumns.length > 0 || extraColumns.length > 0) {\n refusals.push({\n kind: 'unexpected-shape',\n actualColumns,\n missingColumns,\n extraColumns,\n });\n } else {\n // The column *set* matches exactly — only meaningful to type-check\n // when every expected column is actually present and nothing extra\n // is there to confuse the comparison.\n const mismatches = expectedColumnsForTable\n .map((column) => {\n const actualType = schema.columns[column]?.type ?? '';\n const bucket = classifyColumnType(actualType);\n const expectedBuckets = expectedBucketsForTable[column];\n return expectedBuckets.includes(bucket)\n ? null\n : { column, actualType, expectedBuckets: [...expectedBuckets] };\n })\n .filter(\n (mismatch): mismatch is NonNullable<typeof mismatch> =>\n mismatch !== null,\n );\n if (mismatches.length > 0) {\n refusals.push({ kind: 'unexpected-column-type', mismatches });\n }\n }\n\n const references = inboundForeignKeys.get(name) ?? [];\n if (references.length > 0) {\n refusals.push({ kind: 'referenced-by-foreign-key', references });\n }\n\n const rowCount = await countRows(db, engine, name);\n if (rowCount > 0) {\n refusals.push({ kind: 'not-empty', rowCount });\n }\n\n const indexNames = (schema.indexes ?? []).map((index) => index.name);\n\n if (refusals.length > 0) safe = false;\n\n tables.push({ table: name, exists: true, rowCount, indexNames, refusals });\n }\n\n return { tables, safe };\n}\n\n/**\n * Read-only preflight: is it safe to drop the five framework-base tables,\n * and what exactly would that require?\n *\n * Never mutates the database. Safe to call for `--dry-run` and as the\n * required first half of a real run.\n */\nexport async function planFrameworkBaseTableDrop(\n db: DatabaseInterface,\n options: PlanFrameworkBaseTableDropOptions = {},\n): Promise<FrameworkBaseTablesPlan> {\n const engine = detectEngine(resolveDatabaseUrl(db), options.engineHint);\n const { tables, safe } = await assessTargets(db, engine);\n\n const statements: string[] = [];\n if (safe) {\n for (const table of tables) {\n if (!table.exists) continue;\n for (const indexName of table.indexNames) {\n statements.push(\n `DROP INDEX IF EXISTS ${qualifyIdentifier(engine, indexName)}`,\n );\n }\n statements.push(\n `DROP TABLE IF EXISTS ${qualifyIdentifier(engine, table.table)}`,\n );\n }\n }\n\n return { engine, tables, safe, statements };\n}\n\nexport interface DropFrameworkBaseTablesOptions {\n /** PostgreSQL lock timeout in milliseconds (defaults to 30 seconds). */\n lockTimeout?: number;\n /** PostgreSQL statement timeout in milliseconds (defaults to 60 seconds). */\n statementTimeout?: number;\n}\n\nexport interface DropFrameworkBaseTablesResult {\n droppedTables: string[];\n droppedIndexes: string[];\n}\n\n/**\n * Execute a plan produced by {@link planFrameworkBaseTableDrop}.\n *\n * Refuses outright when the plan is not `safe` — callers must resolve every\n * refusal (by fixing the underlying data, or accepting that a table is not a\n * genuine framework-base table) rather than forcing this function past them.\n *\n * On PostgreSQL the whole batch runs in one transaction bounded by\n * `SET LOCAL lock_timeout` / `SET LOCAL statement_timeout` (#2362), so a\n * batch that queues behind a long-running writer fails fast and rolls back\n * instead of holding locks against every writer. Every target is then locked\n * `IN ACCESS EXCLUSIVE MODE` — a plain `SELECT COUNT(*)` alone only takes an\n * ACCESS SHARE lock, which would let a concurrent writer commit a row (or a\n * concurrent DDL session replace the table entirely) between the check and\n * the DROP; holding the exclusive lock first makes the check-then-drop\n * sequence atomic.\n *\n * Immediately before dropping anything, this re-verifies column shape,\n * column type, and emptiness inside that same transaction — not just the\n * row count `planFrameworkBaseTableDrop()` already checked. This uses raw\n * `information_schema.columns` (PostgreSQL) / `PRAGMA table_info`\n * (SQLite/DuckDB) queries rather than `getTableSchema()`: `@happyvertical/sql`\n * does not expose that richer introspection method on the transaction-scoped\n * connection this callback receives, only `query()`. Foreign keys are\n * deliberately **not** re-scanned here — doing so would need a fresh\n * full-catalog scan on every drop.\n *\n * On PostgreSQL a foreign key that appeared after planning is still caught:\n * its FK enforcement is dependency-based, so `DROP TABLE` itself refuses\n * when a real dependent exists, empty parent or not. **This does not hold\n * on SQLite** — verified directly: `DROP TABLE` there only checks FK\n * enforcement against the rows actually being removed, so a parent with\n * zero rows (exactly the state this function requires) drops cleanly even\n * with a real, enforced foreign key pointing at it, leaving the referencing\n * table with a dangling reference. On SQLite/DuckDB, the plan-time\n * full-catalog scan is therefore the *only* gate against a foreign key on\n * these two engines — narrower than PostgreSQL's, on top of the\n * already-documented DuckDB introspection gap in\n * {@link qualifyIdentifier}'s doc comment where that plan-time scan cannot\n * see the reference at all. No data is lost either way (the target table is\n * verified empty before every drop); the residual risk is a dangling\n * reference in the very narrow window between planning and this\n * transaction, on either engine, or an FK created after planning at all.\n *\n * Only `plan.statements`' `DROP TABLE` entries are executed — the\n * `DROP INDEX` entries are not. An index name is unique per schema\n * (PostgreSQL) / globally (SQLite), so nothing re-verifies it is still the\n * same object between planning and execution the way the table itself now\n * is; re-issuing a stale `DROP INDEX` by name could hit an unrelated index\n * created under that name in the meantime. `DROP TABLE` cascades to every\n * index actually owned by the table on every engine this module supports,\n * resolved fresh from the database's own catalog at drop time — identity-safe\n * by construction, unlike a second name-based statement would be.\n */\nexport async function dropFrameworkBaseTables(\n db: DatabaseInterface,\n plan: FrameworkBaseTablesPlan,\n options: DropFrameworkBaseTablesOptions = {},\n): Promise<DropFrameworkBaseTablesResult> {\n if (!plan.safe) {\n throw new Error(\n 'Refusing to drop framework base tables: the plan reported at least one unsafe table. Re-run planFrameworkBaseTableDrop() and resolve every refusal first — nothing was dropped.',\n );\n }\n\n const targets = plan.tables.filter((table) => table.exists);\n if (targets.length === 0) {\n return { droppedTables: [], droppedIndexes: [] };\n }\n\n if (!db.transaction) {\n throw new Error(\n 'Dropping framework base tables requires a database adapter with transaction support.',\n );\n }\n\n const lockTimeoutMs = options.lockTimeout ?? DEFAULT_POSTGRES_LOCK_TIMEOUT_MS;\n const statementTimeoutMs =\n options.statementTimeout ?? DEFAULT_POSTGRES_STATEMENT_TIMEOUT_MS;\n const isPostgres = plan.engine === 'postgres';\n\n await db.transaction(async (tx) => {\n if (isPostgres) {\n await tx.query(\n `SET LOCAL lock_timeout = '${formatPostgresTimeout(lockTimeoutMs)}'`,\n );\n await tx.query(\n `SET LOCAL statement_timeout = '${formatPostgresTimeout(statementTimeoutMs)}'`,\n );\n }\n\n // On PostgreSQL, a plain `SELECT COUNT(*)` takes only an ACCESS SHARE\n // lock, which does not block a concurrent writer or a concurrent DDL\n // session dropping and recreating the same name as an unrelated table:\n // either could land after this check passes but before the DROP below\n // acquires its own exclusive lock. Acquire ACCESS EXCLUSIVE on every\n // target *before* re-assessing it so the check and the drop are atomic —\n // once held, no other session can read, write, or alter the table until\n // this transaction ends, and `SET LOCAL lock_timeout` above bounds the\n // wait for it exactly as it bounds the DROP itself.\n if (isPostgres) {\n for (const table of targets) {\n await tx.query(\n `LOCK TABLE ${qualifyIdentifier(plan.engine, table.table)} IN ACCESS EXCLUSIVE MODE`,\n );\n }\n }\n\n // Re-verify shape, type, and emptiness inside this transaction (locked,\n // on PostgreSQL) immediately before dropping anything — a table could\n // have been rewritten, or (on PostgreSQL, before the locks above were\n // acquired) dropped and replaced by an unrelated same-named table, since\n // the read-only plan. Trusting the original plan's findings past that\n // point would let the drop proceed against a table the plan never\n // actually verified. See this function's own doc comment for why\n // foreign keys are not re-scanned here.\n for (const target of targets) {\n const liveColumns = await inspectColumnTypes(\n tx,\n plan.engine,\n target.table,\n );\n if (!liveColumns) {\n throw new Error(\n `Refusing to drop \"${target.table}\": it could not be re-verified inside the transaction (it may no longer exist). Nothing was dropped.`,\n );\n }\n\n const mismatch = describeColumnMismatch(target.table, liveColumns);\n if (mismatch) {\n throw new Error(\n `Refusing to drop \"${target.table}\": a fresh check inside the transaction found an ${mismatch}. Nothing was dropped.`,\n );\n }\n\n const rowCount = await countRows(tx, plan.engine, target.table);\n if (rowCount > 0) {\n throw new Error(\n `Refusing to drop \"${target.table}\": it now has ${rowCount} row(s) though it was empty when planned. Nothing was dropped.`,\n );\n }\n }\n\n // Execute only the `DROP TABLE` statements, never the `DROP INDEX`\n // statements `plan.statements` also carries for the dry-run preview.\n // An index name is unique per schema (PostgreSQL) / globally (SQLite),\n // so a live name collision with the one enumerated at plan time\n // necessarily means the object is not the one this re-check just\n // verified — the same \"swapped between planning and execution\" risk\n // the table-side re-check above closes, but for indexes there is no\n // cheap re-verification to run. `DROP TABLE` itself cascades to every\n // index actually owned by the table on every engine this module\n // supports, which is identity-safe by construction: the database\n // resolves \"this table's indexes\" fresh at drop time from its own\n // catalog, never by matching the stale planned name.\n for (const statement of plan.statements) {\n if (statement.startsWith('DROP INDEX')) continue;\n await tx.query(statement);\n }\n });\n\n return {\n droppedTables: targets.map((table) => table.table),\n droppedIndexes: targets.flatMap((table) => table.indexNames),\n };\n}\n"],"mappings":"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"}
|
|
@@ -12,6 +12,7 @@ export { type BackfillRecord, BackfillTableUnavailableError, BackfillTracker, ty
|
|
|
12
12
|
export { computeChecksum, normalizeSQL, shortChecksum, verifyChecksum, } from './checksum.js';
|
|
13
13
|
export type { DiffOptions } from './differ.js';
|
|
14
14
|
export { canonicalizeDefault, generateSchemaDiff, getSQLFromDiff, hasActionableChanges, isAdvisoryOnlyChange, isInfoOnlyChange, isManualOrAdvisoryChange, SchemaComparer, uniqueColumnIndexName, } from './differ.js';
|
|
15
|
+
export { type DropFrameworkBaseTablesOptions, type DropFrameworkBaseTablesResult, dropFrameworkBaseTables, FRAMEWORK_BASE_TABLE_NAMES, type FrameworkBaseTableName, type FrameworkBaseTableRefusal, type FrameworkBaseTableReport, type FrameworkBaseTablesPlan, type PlanFrameworkBaseTableDropOptions, planFrameworkBaseTableDrop, } from './framework-base-tables.js';
|
|
15
16
|
export type { GeneratedMigration, GenerateMigrationOptions, } from './generator.js';
|
|
16
17
|
export { createMigrationDefinition, generateMigrationSequence, generateMigrationTimestamp, MigrationGenerator, } from './generator.js';
|
|
17
18
|
export { buildIntegerWidthStatements, buildIntegerWidthTableStatements, collectIntegerWidthTargets, type IntegerWidthColumnReport, type IntegerWidthColumnState, type IntegerWidthOptions, type IntegerWidthPreflightResult, type IntegerWidthTableReport, type IntegerWidthTarget, type IntegerWidthWidenOptions, type IntegerWidthWidenResult, preflightIntegerWidthWidening, widenIntegerColumnsToBigInt, } from './integer-width.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EACL,KAAK,cAAc,EACnB,6BAA6B,EAC7B,eAAe,EACf,KAAK,sBAAsB,GAC5B,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,eAAe,EACf,YAAY,EACZ,aAAa,EACb,cAAc,GACf,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,EACd,oBAAoB,EACpB,oBAAoB,EACpB,gBAAgB,EAChB,wBAAwB,EACxB,cAAc,EACd,qBAAqB,GACtB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EACL,KAAK,cAAc,EACnB,6BAA6B,EAC7B,eAAe,EACf,KAAK,sBAAsB,GAC5B,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,eAAe,EACf,YAAY,EACZ,aAAa,EACb,cAAc,GACf,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,EACd,oBAAoB,EACpB,oBAAoB,EACpB,gBAAgB,EAChB,wBAAwB,EACxB,cAAc,EACd,qBAAqB,GACtB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,KAAK,8BAA8B,EACnC,KAAK,6BAA6B,EAClC,uBAAuB,EACvB,0BAA0B,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,iCAAiC,EACtC,0BAA0B,GAC3B,MAAM,4BAA4B,CAAC;AACpC,YAAY,EACV,kBAAkB,EAClB,wBAAwB,GACzB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,yBAAyB,EACzB,yBAAyB,EACzB,0BAA0B,EAC1B,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,2BAA2B,EAC3B,gCAAgC,EAChC,0BAA0B,EAC1B,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,EACxB,KAAK,2BAA2B,EAChC,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EACvB,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,6BAA6B,EAC7B,2BAA2B,GAC5B,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,yBAAyB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,wBAAwB,EACxB,KAAK,yBAAyB,EAC9B,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,iBAAiB,EACtB,0BAA0B,EAC1B,+BAA+B,GAChC,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,0BAA0B,EAC1B,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,kBAAkB,EAClB,KAAK,6BAA6B,GACnC,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,KAAK,iCAAiC,EACtC,4BAA4B,EAC5B,0BAA0B,EAC1B,KAAK,8BAA8B,EACnC,uBAAuB,EACvB,wBAAwB,EACxB,2BAA2B,EAC3B,KAAK,sBAAsB,EAC3B,2BAA2B,GAC5B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,wBAAwB,EACxB,uBAAuB,EACvB,gBAAgB,EAChB,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,cAAc,CAAC;AAGtB,YAAY,EACV,sBAAsB,EACtB,wBAAwB,EACxB,UAAU,EACV,cAAc,EACd,iBAAiB,EAEjB,aAAa,EACb,mBAAmB,EACnB,sBAAsB,EACtB,WAAW,EACX,cAAc,EACd,eAAe,EACf,SAAS,EACT,mBAAmB,EACnB,aAAa,EACb,eAAe,EAEf,eAAe,EACf,gBAAgB,EAEhB,uBAAuB,EACvB,mBAAmB,EACnB,qBAAqB,EACrB,eAAe,EACf,YAAY,EACZ,UAAU,EACV,qBAAqB,EACrB,yBAAyB,EACzB,eAAe,GAChB,MAAM,YAAY,CAAC"}
|
package/dist/migrations/index.js
CHANGED
|
@@ -4,8 +4,9 @@ import { SchemaComparer, canonicalizeDefault, generateSchemaDiff, getSQLFromDiff
|
|
|
4
4
|
import { BackfillTableUnavailableError, BackfillTracker } from "./backfill-tracker.js";
|
|
5
5
|
import { buildIntegerWidthStatements, buildIntegerWidthTableStatements, collectIntegerWidthTargets, preflightIntegerWidthWidening, widenIntegerColumnsToBigInt } from "./integer-width.js";
|
|
6
6
|
import { computeChecksum, normalizeSQL, shortChecksum, verifyChecksum } from "./checksum.js";
|
|
7
|
+
import { FRAMEWORK_BASE_TABLE_NAMES, dropFrameworkBaseTables, planFrameworkBaseTableDrop } from "./framework-base-tables.js";
|
|
7
8
|
import { MigrationGenerator, createMigrationDefinition, generateMigrationSequence, generateMigrationTimestamp } from "./generator.js";
|
|
8
9
|
import { MinorUnitsPreflightError, buildMinorUnitsStatements, preflightMinorUnitsRescale, rescaleMoneyColumnsToMinorUnits } from "./minor-units.js";
|
|
9
10
|
import { MigrationTracker, buildConcurrentIndexPlan, extractCreatedIndexName, planPostgresStatements } from "./tracker.js";
|
|
10
11
|
import { getPendingSchemaStatements, migrateSmrtSchemas } from "./orchestrate.js";
|
|
11
|
-
export { BackfillTableUnavailableError, BackfillTracker, MigrationGenerator, MigrationTracker, MinorUnitsPreflightError, SQLITE_REBUILD_TABLE_PREFIX, SchemaComparer, buildConcurrentIndexPlan, buildIntegerWidthStatements, buildIntegerWidthTableStatements, buildMinorUnitsStatements, buildSqliteRebuildStatements, canonicalizeDefault, collectIntegerWidthTargets, computeChecksum, createMigrationDefinition, extractCreatedIndexName, generateMigrationSequence, generateMigrationTimestamp, generateSchemaDiff, getPendingSchemaStatements, getSQLFromDiff, hasActionableChanges, isAdvisoryOnlyChange, isInfoOnlyChange, isManualOrAdvisoryChange, isSqliteRebuildPlaceholder, migrateSmrtSchemas, normalizeSQL, parsePostgresTimeoutMs, planPostgresStatements, planSqliteTableRebuilds, preflightIntegerWidthWidening, preflightMinorUnitsRescale, rescaleMoneyColumnsToMinorUnits, rewriteSqliteCreateTable, shortChecksum, sqliteRebuildPlaceholderSql, uniqueColumnIndexName, verifyChecksum, widenIntegerColumnsToBigInt };
|
|
12
|
+
export { BackfillTableUnavailableError, BackfillTracker, FRAMEWORK_BASE_TABLE_NAMES, MigrationGenerator, MigrationTracker, MinorUnitsPreflightError, SQLITE_REBUILD_TABLE_PREFIX, SchemaComparer, buildConcurrentIndexPlan, buildIntegerWidthStatements, buildIntegerWidthTableStatements, buildMinorUnitsStatements, buildSqliteRebuildStatements, canonicalizeDefault, collectIntegerWidthTargets, computeChecksum, createMigrationDefinition, dropFrameworkBaseTables, extractCreatedIndexName, generateMigrationSequence, generateMigrationTimestamp, generateSchemaDiff, getPendingSchemaStatements, getSQLFromDiff, hasActionableChanges, isAdvisoryOnlyChange, isInfoOnlyChange, isManualOrAdvisoryChange, isSqliteRebuildPlaceholder, migrateSmrtSchemas, normalizeSQL, parsePostgresTimeoutMs, planFrameworkBaseTableDrop, planPostgresStatements, planSqliteTableRebuilds, preflightIntegerWidthWidening, preflightMinorUnitsRescale, rescaleMoneyColumnsToMinorUnits, rewriteSqliteCreateTable, shortChecksum, sqliteRebuildPlaceholderSql, uniqueColumnIndexName, verifyChecksum, widenIntegerColumnsToBigInt };
|
package/dist/migrations.js
CHANGED
|
@@ -4,9 +4,10 @@ import { SchemaComparer, canonicalizeDefault, generateSchemaDiff, getSQLFromDiff
|
|
|
4
4
|
import { BackfillTableUnavailableError, BackfillTracker } from "./migrations/backfill-tracker.js";
|
|
5
5
|
import { buildIntegerWidthStatements, buildIntegerWidthTableStatements, collectIntegerWidthTargets, preflightIntegerWidthWidening, widenIntegerColumnsToBigInt } from "./migrations/integer-width.js";
|
|
6
6
|
import { computeChecksum, normalizeSQL, shortChecksum, verifyChecksum } from "./migrations/checksum.js";
|
|
7
|
+
import { FRAMEWORK_BASE_TABLE_NAMES, dropFrameworkBaseTables, planFrameworkBaseTableDrop } from "./migrations/framework-base-tables.js";
|
|
7
8
|
import { MigrationGenerator, createMigrationDefinition, generateMigrationSequence, generateMigrationTimestamp } from "./migrations/generator.js";
|
|
8
9
|
import { MinorUnitsPreflightError, buildMinorUnitsStatements, preflightMinorUnitsRescale, rescaleMoneyColumnsToMinorUnits } from "./migrations/minor-units.js";
|
|
9
10
|
import { MigrationTracker, buildConcurrentIndexPlan, extractCreatedIndexName, planPostgresStatements } from "./migrations/tracker.js";
|
|
10
11
|
import { getPendingSchemaStatements, migrateSmrtSchemas } from "./migrations/orchestrate.js";
|
|
11
12
|
import "./migrations/index.js";
|
|
12
|
-
export { BackfillTableUnavailableError, BackfillTracker, MigrationGenerator, MigrationTracker, MinorUnitsPreflightError, SQLITE_REBUILD_TABLE_PREFIX, SchemaComparer, buildConcurrentIndexPlan, buildIntegerWidthStatements, buildIntegerWidthTableStatements, buildMinorUnitsStatements, buildSqliteRebuildStatements, canonicalizeDefault, collectIntegerWidthTargets, computeChecksum, createMigrationDefinition, extractCreatedIndexName, generateMigrationSequence, generateMigrationTimestamp, generateSchemaDiff, getPendingSchemaStatements, getSQLFromDiff, hasActionableChanges, isAdvisoryOnlyChange, isInfoOnlyChange, isManualOrAdvisoryChange, isSqliteRebuildPlaceholder, migrateSmrtSchemas, normalizeSQL, parsePostgresTimeoutMs, planPostgresStatements, planSqliteTableRebuilds, preflightIntegerWidthWidening, preflightMinorUnitsRescale, rescaleMoneyColumnsToMinorUnits, rewriteSqliteCreateTable, shortChecksum, sqliteRebuildPlaceholderSql, uniqueColumnIndexName, verifyChecksum, widenIntegerColumnsToBigInt };
|
|
13
|
+
export { BackfillTableUnavailableError, BackfillTracker, FRAMEWORK_BASE_TABLE_NAMES, MigrationGenerator, MigrationTracker, MinorUnitsPreflightError, SQLITE_REBUILD_TABLE_PREFIX, SchemaComparer, buildConcurrentIndexPlan, buildIntegerWidthStatements, buildIntegerWidthTableStatements, buildMinorUnitsStatements, buildSqliteRebuildStatements, canonicalizeDefault, collectIntegerWidthTargets, computeChecksum, createMigrationDefinition, dropFrameworkBaseTables, extractCreatedIndexName, generateMigrationSequence, generateMigrationTimestamp, generateSchemaDiff, getPendingSchemaStatements, getSQLFromDiff, hasActionableChanges, isAdvisoryOnlyChange, isInfoOnlyChange, isManualOrAdvisoryChange, isSqliteRebuildPlaceholder, migrateSmrtSchemas, normalizeSQL, parsePostgresTimeoutMs, planFrameworkBaseTableDrop, planPostgresStatements, planSqliteTableRebuilds, preflightIntegerWidthWidening, preflightMinorUnitsRescale, rescaleMoneyColumnsToMinorUnits, rewriteSqliteCreateTable, shortChecksum, sqliteRebuildPlaceholderSql, uniqueColumnIndexName, verifyChecksum, widenIntegerColumnsToBigInt };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { DatabaseInterface } from '@happyvertical/sql';
|
|
2
|
+
/** PostgreSQL ACLs only; this is not application authorization or RLS. */
|
|
3
|
+
export interface PostgresPermissionContract {
|
|
4
|
+
schema: string;
|
|
5
|
+
/** Defaults affect every future object created by migrationOwner in schema. */
|
|
6
|
+
schemaExclusive: true;
|
|
7
|
+
migrationOwner: string;
|
|
8
|
+
runtimeRole: string;
|
|
9
|
+
managedTables: string[];
|
|
10
|
+
/** Exact zero-argument trigger functions bound only to managed tables. */
|
|
11
|
+
managedTriggerFunctions?: string[];
|
|
12
|
+
monitor?: {
|
|
13
|
+
role: string;
|
|
14
|
+
tables: Record<string, string[]>;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export interface PostgresPermissionDiagnostic {
|
|
18
|
+
code: string;
|
|
19
|
+
severity: 'missing' | 'excessive' | 'unsupported';
|
|
20
|
+
role?: string;
|
|
21
|
+
resource: string;
|
|
22
|
+
message: string;
|
|
23
|
+
}
|
|
24
|
+
export interface PostgresPermissionPlan {
|
|
25
|
+
contract: PostgresPermissionContract;
|
|
26
|
+
diagnostics: PostgresPermissionDiagnostic[];
|
|
27
|
+
statements: string[];
|
|
28
|
+
canApply: boolean;
|
|
29
|
+
fingerprint: string;
|
|
30
|
+
limitations: string[];
|
|
31
|
+
}
|
|
32
|
+
type NormalizedPostgresPermissionContract = PostgresPermissionContract & {
|
|
33
|
+
managedTriggerFunctions: string[];
|
|
34
|
+
};
|
|
35
|
+
type Executor = Pick<DatabaseInterface, 'query'>;
|
|
36
|
+
/** Validate configuration before opening a connection; unknown options fail closed. */
|
|
37
|
+
export declare function validatePostgresPermissionContract(value: unknown): NormalizedPostgresPermissionContract;
|
|
38
|
+
/** Read-only catalog inspection. No application records or credentials are read. */
|
|
39
|
+
export declare function planPostgresPermissions(db: Executor, contract: PostgresPermissionContract): Promise<PostgresPermissionPlan>;
|
|
40
|
+
/** Apply the reviewed contract atomically, rejecting stale catalog snapshots. */
|
|
41
|
+
export declare function applyPostgresPermissions(db: DatabaseInterface, contract: PostgresPermissionContract, options: {
|
|
42
|
+
expectedFingerprint: string;
|
|
43
|
+
}): Promise<PostgresPermissionPlan>;
|
|
44
|
+
export {};
|
|
45
|
+
//# sourceMappingURL=postgres-permissions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres-permissions.d.ts","sourceRoot":"","sources":["../src/postgres-permissions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAS5D,0EAA0E;AAC1E,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,+EAA+E;IAC/E,eAAe,EAAE,IAAI,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,0EAA0E;IAC1E,uBAAuB,CAAC,EAAE,MAAM,EAAE,CAAC;IACnC,OAAO,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;KAAE,CAAC;CAC9D;AAED,MAAM,WAAW,4BAA4B;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,SAAS,GAAG,WAAW,GAAG,aAAa,CAAC;IAClD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,0BAA0B,CAAC;IACrC,WAAW,EAAE,4BAA4B,EAAE,CAAC;IAC5C,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,KAAK,oCAAoC,GAAG,0BAA0B,GAAG;IACvE,uBAAuB,EAAE,MAAM,EAAE,CAAC;CACnC,CAAC;AAEF,KAAK,QAAQ,GAAG,IAAI,CAAC,iBAAiB,EAAE,OAAO,CAAC,CAAC;AAkJjD,uFAAuF;AACvF,wBAAgB,kCAAkC,CAChD,KAAK,EAAE,OAAO,GACb,oCAAoC,CA0FtC;AAu2BD,oFAAoF;AACpF,wBAAsB,uBAAuB,CAC3C,EAAE,EAAE,QAAQ,EACZ,QAAQ,EAAE,0BAA0B,GACnC,OAAO,CAAC,sBAAsB,CAAC,CAEjC;AAED,iFAAiF;AACjF,wBAAsB,wBAAwB,CAC5C,EAAE,EAAE,iBAAiB,EACrB,QAAQ,EAAE,0BAA0B,EACpC,OAAO,EAAE;IAAE,mBAAmB,EAAE,MAAM,CAAA;CAAE,GACvC,OAAO,CAAC,sBAAsB,CAAC,CA8CjC"}
|