@stacksjs/database 0.70.44 → 0.70.53

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.
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Acquire the distributed migration lock for the given dialect.
3
+ * Returns a handle whose `release()` method MUST be called in a
4
+ * `finally` to free the lock — even on error paths.
5
+ *
6
+ * @param dialect - which database driver is being migrated against
7
+ * @param adminDb - the bun-query-builder connection to issue lock SQL
8
+ * through (PG / MySQL). Ignored for SQLite.
9
+ * @param opts.timeoutMs - max time to wait for an existing holder
10
+ * @param opts.sqliteLockPath - override the file path SQLite uses
11
+ *
12
+ * Throws an error if the lock can't be acquired within `timeoutMs`.
13
+ */
14
+ export declare function acquireMigrationLock(dialect: Dialect, adminDb: { unsafe: (sql: string) => Promise<unknown> } | null, opts?: { timeoutMs?: number, sqliteLockPath?: string }): Promise<MigrationLockHandle>;
15
+ /**
16
+ * Returned by `acquireMigrationLock()`. Callers MUST invoke `release`
17
+ * in a finally block; the lock is process-external (file, advisory,
18
+ * or named) so leaking it strands future migration runs.
19
+ */
20
+ export declare interface MigrationLockHandle {
21
+ release: () => Promise<void>
22
+ }
23
+ export type Dialect = 'sqlite' | 'mysql' | 'postgres';
@@ -1,5 +1,35 @@
1
+ import type { MigrationOperation } from '@stacksjs/query-builder';
1
2
  import type { Result } from '@stacksjs/error-handling';
2
3
  export type { MigrationResult as MigrationResultType };
4
+ /**
5
+ * SQLite compatibility preprocessing for migrations.
6
+ *
7
+ * SQLite does not support:
8
+ * - ALTER TABLE ADD CONSTRAINT (foreign keys must be defined at table creation)
9
+ * - CREATE TYPE ... AS ENUM (SQLite has no user-defined types; enum columns
10
+ * are plain TEXT, with the allowed values enforced at the validation layer)
11
+ *
12
+ * Note: CREATE UNIQUE INDEX files are deliberately NOT skipped — the SQLite
13
+ * dialect driver never renders inline UNIQUE in CREATE TABLE, so the
14
+ * standalone index file is the only uniqueness enforcement on SQLite
15
+ * (stacksjs/stacks#1952).
16
+ *
17
+ * Two flavours of "no-op on SQLite" need different handling:
18
+ *
19
+ * - **Skip-and-keep** (`skipMigration`): the file is portable — it would
20
+ * run cleanly on MySQL/Postgres — but doesn't apply to SQLite. Record
21
+ * it as executed in the migrations tracking table so it doesn't replay,
22
+ * but **leave the file on disk** so a future `DB_CONNECTION` flip can
23
+ * pick it up. This is the right path for FK constraint files.
24
+ * (stacksjs/stacks#1916)
25
+ *
26
+ * - **Drop-and-delete** (`deleteMigration`): the file is genuinely dead
27
+ * — a duplicate CREATE TABLE created by `buddy generate:migrations`
28
+ * regenerating against an already-modeled table, or a DROP COLUMN
29
+ * migration whose target column never existed. Removing it keeps the
30
+ * directory clean and prevents future runs from re-discovering it.
31
+ */
32
+ export declare function preprocessSqliteMigrations(): void;
3
33
  /**
4
34
  * Run database migrations
5
35
  */
@@ -8,6 +38,18 @@ export declare function runDatabaseMigration(): Promise<Result<string, Error>>;
8
38
  * Reset the database (drop all tables)
9
39
  */
10
40
  export declare function resetDatabase(): Promise<Result<string, Error>>;
41
+ /**
42
+ * Preview the pending migration as a list of structured operations WITHOUT
43
+ * writing any files or advancing the snapshot. The `buddy migrate` command
44
+ * uses this (in the interactive parent process) to gate destructive changes
45
+ * behind confirmation before spawning the non-interactive migrate action.
46
+ */
47
+ export declare function previewPendingMigrations(options?: GenerateMigrationsOptions): Promise<MigrationOperation[]>;
48
+ export declare function generateMigrations(options?: GenerateMigrationsOptions): Promise<Result<string, Error>>;
49
+ /**
50
+ * Generate fresh migrations (full regeneration, ignoring previous state)
51
+ */
52
+ export declare function generateMigrations2(): Promise<Result<string, Error>>;
11
53
  /*` definitions to the stored snapshot
12
54
  * (`.qb/model-snapshot.<dialect>.json`) via bun-query-builder, then — if
13
55
  * there are changes — writes the resulting ALTER/CREATE/DROP statements
@@ -20,11 +62,10 @@ export declare function resetDatabase(): Promise<Result<string, Error>>;
20
62
  * the runner never sees it, so model edits silently no-op'd — defeating
21
63
  * the "models are the source of truth" promise.
22
64
  */
23
- export declare function generateMigrations(): Promise<Result<string, Error>>;
24
- /**
25
- * Generate fresh migrations (full regeneration, ignoring previous state)
26
- */
27
- export declare function generateMigrations2(): Promise<Result<string, Error>>;
65
+ export declare interface GenerateMigrationsOptions {
66
+ applyRenames?: boolean
67
+ fromDb?: boolean
68
+ }
28
69
  /**
29
70
  * Migration result type for compatibility
30
71
  */
@@ -0,0 +1,20 @@
1
+ import { sqlHelpers } from './sql-helpers';
2
+ /**
3
+ * `CREATE TABLE IF NOT EXISTS notifications` for the given dialect.
4
+ * Pure (no execution) so the cross-dialect DDL is unit-testable.
5
+ * Columns match the `DatabaseNotification` interface in
6
+ * `notifications/src/drivers/database.ts`.
7
+ */
8
+ export declare function notificationsTableSql(sql: SqlHelpers): string;
9
+ /**
10
+ * `CREATE TABLE IF NOT EXISTS notification_preferences`. The
11
+ * `UNIQUE (user_id, channel, category)` constraint is what makes the
12
+ * preference upsert safe — matches `NotificationPreferenceRow`.
13
+ */
14
+ export declare function notificationPreferencesTableSql(sql: SqlHelpers): string;
15
+ /**
16
+ * Create the notification + notification_preferences tables. Idempotent
17
+ * (`IF NOT EXISTS`), so it's safe to run on every `buddy migrate`.
18
+ */
19
+ export declare function migrateNotificationTables(options?: { verbose?: boolean }): Promise<{ success: boolean, error?: string }>;
20
+ declare type SqlHelpers = ReturnType<typeof sqlHelpers>;
@@ -0,0 +1,17 @@
1
+ import { sqlHelpers } from './sql-helpers';
2
+ /** `roles` table — id + name + guard + timestamps with UNIQUE(name, guard_name). */
3
+ export declare function rolesTableSql(sql: SqlHelpers): string;
4
+ /** `permissions` table — same shape as `roles`. */
5
+ export declare function permissionsTableSql(sql: SqlHelpers): string;
6
+ /** `user_roles` pivot — composite PK makes double-assign a unique violation. */
7
+ export declare function userRolesTableSql(): string;
8
+ /** `user_permissions` pivot. */
9
+ export declare function userPermissionsTableSql(): string;
10
+ /** `role_permissions` pivot. */
11
+ export declare function rolePermissionsTableSql(): string;
12
+ /**
13
+ * Create the 5 RBAC tables. Idempotent (`IF NOT EXISTS`), so it's
14
+ * safe to run on every `buddy migrate`.
15
+ */
16
+ export declare function migrateRbacTables(options?: { verbose?: boolean }): Promise<{ success: boolean, error?: string }>;
17
+ declare type SqlHelpers = ReturnType<typeof sqlHelpers>;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Remove a single `useSeeder` / `seedable` object-property from model
3
+ * source text (stacksjs/stacks#1929). Brace-aware (balances nested
4
+ * `{}` and skips string literals) and conservative: only strips the
5
+ * documented value shapes (`true`, `false`, or a `{ … }` object). For
6
+ * anything else (an identifier, a function call, a spread) it returns
7
+ * `changed: false` so the caller can flag it for manual cleanup
8
+ * instead of risking a mangled file.
9
+ *
10
+ * Exported for unit tests.
11
+ */
12
+ export declare function stripUseSeederTrait(source: string): { source: string, changed: boolean, skipped: boolean };
13
+ /**
14
+ * Walk the configured models directory, find every model whose
15
+ * `traits.useSeeder` is truthy, and write a class-seeder file for it.
16
+ * Returns a structured report so the CLI command can render a summary
17
+ * without re-parsing log lines.
18
+ */
19
+ export declare function scaffoldClassSeedersFromModels(options?: ScaffoldOptions): Promise<ScaffoldResult>;
20
+ /** Pure renderer — exported for unit tests. */
21
+ export declare function renderSeederFile(modelName: string, modelImportPath: string, count: number): string;
22
+ export declare interface ScaffoldOptions {
23
+ modelsDir?: string
24
+ seedersDir?: string
25
+ force?: boolean
26
+ dryRun?: boolean
27
+ }
28
+ export declare interface ScaffoldResult {
29
+ generated: Array<{ model: string, file: string }>
30
+ skipped: Array<{ model: string, file: string, reason: 'already-exists' | 'no-useseeder' }>
31
+ errors: Array<{ model: string, error: string }>
32
+ strippedTrait: Array<{ model: string, file: string }>
33
+ traitStripSkipped: Array<{ model: string, file: string }>
34
+ }
package/dist/seeder.d.ts CHANGED
@@ -1,8 +1,29 @@
1
+ import type { Attribute, Model } from '@stacksjs/types';
2
+ /**
3
+ * Test whether a model name is on the protected list.
4
+ * Exported for downstream tooling (CI lint rules, custom seeders) so the
5
+ * list stays a single source of truth.
6
+ */
7
+ export declare function isProtectedModel(name: string): boolean;
8
+ /**
9
+ * Direct entry point for `factory.generate(Model, opts)` — exported
10
+ * under a distinct name so the new public API in `factory.ts` can call
11
+ * into the same insert path the legacy walker uses without leaking the
12
+ * `SeederModel` type. See stacksjs/stacks#1919.
13
+ */
14
+ export declare function seedModelDirect(model: SeederModel, options: SeederConfig): Promise<SeedResult>;
1
15
  /**
2
16
  * Main seeding function
3
17
  * Seeds the database using model factory functions
4
18
  * Loads models from both framework defaults and user-defined models,
5
19
  * with user models taking precedence.
20
+ *
21
+ * @deprecated stacksjs/stacks#1919 — the model auto-walker is no
22
+ * longer invoked by `./buddy seed`. Migrate each `useSeeder` trait to
23
+ * a class seeder via `./buddy seed:scaffold`, then call
24
+ * `factory.generate(Model, opts)` from inside each seeder. This
25
+ * function remains exported for programmatic back-compat but is
26
+ * scheduled for removal.
6
27
  */
7
28
  export declare function seed(config?: SeederConfig): Promise<SeedSummary>;
8
29
  /**
@@ -19,6 +40,31 @@ export declare function freshSeed(config?: SeederConfig): Promise<SeedSummary>;
19
40
  * Returns models from both default and user directories
20
41
  */
21
42
  export declare function listSeedableModels(): Promise<Array<{ name: string, table: string, count: number, source: 'default' | 'user' }>>;
43
+ /**
44
+ * Models that touch live auth state and are unsafe to auto-seed on an
45
+ * already-populated database (stacksjs/stacks#1852).
46
+ *
47
+ * The motivating incident: a userland `app/Models/OauthClient.ts` shipped
48
+ * with the default `useSeeder: { count: 10 }` trait. Every `./buddy seed`
49
+ * re-rolled the `oauth_clients` table — including the row at id=1, the
50
+ * Personal Access Client whose `secret` is part of the encryption key
51
+ * used to derive each issued access token's `encryptedId`. With the
52
+ * secret rotated, every previously-issued token failed validation at
53
+ * `decrypt(encryptedId, clientSecret)`, surfacing as a generic
54
+ * "Unauthorized. Invalid token." 401 with no log line indicating what
55
+ * actually happened.
56
+ *
57
+ * Models on this list are skipped by default. They are seeded when:
58
+ *
59
+ * - `fresh: true` is passed (the seeder truncates first; live tokens
60
+ * are gone anyway, so re-rolling the PAC secret is harmless), OR
61
+ * - `allowProtected: true` is passed (explicit opt-in escape hatch
62
+ * surfaced as `./buddy seed --allow-protected`).
63
+ *
64
+ * The list is conservative: any model whose rows participate in token
65
+ * issuance / validation / refresh belongs here.
66
+ */
67
+ export declare const PROTECTED_MODELS: readonly string[];
22
68
  /**
23
69
  * Seeder configuration options
24
70
  */
@@ -30,6 +76,7 @@ export declare interface SeederConfig {
30
76
  only?: string[]
31
77
  except?: string[]
32
78
  includeDefaults?: boolean
79
+ allowProtected?: boolean
33
80
  }
34
81
  /**
35
82
  * Result of a single model seeding operation
@@ -52,6 +99,18 @@ export declare interface SeedSummary {
52
99
  results: SeedResult[]
53
100
  duration: number
54
101
  }
102
+ /**
103
+ * Parsed model with seeding information
104
+ */
105
+ export declare interface SeederModel {
106
+ name: string
107
+ table: string
108
+ count: number
109
+ fixtures: Array<Record<string, unknown>>
110
+ attributes: Record<string, Attribute>
111
+ model: Model
112
+ filePath: string
113
+ }
55
114
  // Legacy exports for backwards compatibility
56
115
  export { seed as runSeeders };
57
116
  export { freshSeed as freshWithSeed };
@@ -26,6 +26,8 @@ export declare interface SqlDialectHelpers {
26
26
  boolFalse: string
27
27
  autoIncrement: string
28
28
  primaryKey: string
29
+ pkColumn: string
30
+ nullableTimestamp: string
29
31
  param: (index: number) => string
30
32
  params: (...values: unknown[]) => { sql: string, values: unknown[] }
31
33
  }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * True if the current async context is inside an active transaction
3
+ * scope. Queue dispatch (and other side-effect emitters) read this
4
+ * to decide between immediate execution and buffering.
5
+ */
6
+ export declare function isInTransaction(): boolean;
7
+ /**
8
+ * Enqueue a callback to fire after the surrounding transaction
9
+ * commits. Returns:
10
+ * - `true` — buffered; caller should NOT execute the side-effect now
11
+ * - `false` — no active transaction; caller should execute immediately
12
+ *
13
+ * This is the low-level primitive. Higher-level facades (queue
14
+ * dispatch, mailer send, event emit) wrap it with their own
15
+ * "respect transaction context unless overridden" logic.
16
+ */
17
+ export declare function enqueueAfterCommit(callback: AfterCommitCallback): boolean;
18
+ /**
19
+ * Run `fn` inside a transaction scope. Returns whatever `fn`
20
+ * returns. On success, fires every buffered after-commit callback
21
+ * in insertion order. On error, discards them — the transaction
22
+ * rolled back so the side-effects shouldn't happen.
23
+ *
24
+ * Used by `@stacksjs/orm`'s `transaction()` wrapper to thread the
25
+ * scope through user code. Apps don't call this directly.
26
+ *
27
+ * Nested calls reuse the outer scope rather than nesting — flush
28
+ * happens on the OUTERMOST commit, matching the savepoint
29
+ * semantics of every relational database. The depth counter is
30
+ * tracked so the outer call knows when it owns the flush.
31
+ */
32
+ export declare function runInTransactionScope<T>(fn: () => Promise<T>, options?: { onError?: (err: unknown, index: number) => void }): Promise<T>;
33
+ /**
34
+ * Test-only escape hatch — manually flush the current scope's
35
+ * buffered callbacks without ending the transaction. Production
36
+ * code never needs this; tests use it to assert intermediate
37
+ * state. Returns the number of callbacks fired.
38
+ */
39
+ export declare function __flushAfterCommitNow(): Promise<number>;
40
+ /**
41
+ * Test-only escape hatch — peek at the number of buffered
42
+ * callbacks without firing them. Returns 0 outside a scope.
43
+ */
44
+ export declare function __pendingAfterCommitCount(): number;
45
+ /**
46
+ * One buffered side-effect waiting for the surrounding transaction
47
+ * to commit. Errors thrown during flush are NOT re-thrown — the
48
+ * transaction itself already committed, so failing the whole flow
49
+ * after-the-fact would corrupt the caller's mental model. Errors
50
+ * are logged via `onError` if the scope supplied one.
51
+ */
52
+ declare type AfterCommitCallback = () => Promise<void> | void;
package/dist/types.d.ts CHANGED
@@ -25,6 +25,80 @@ export declare interface Sql {
25
25
  readonly sql: string
26
26
  readonly parameters: unknown[]
27
27
  }
28
+ /**
29
+ * Reference to a column for use inside an expression. Returned by
30
+ * {@link StacksExpressionBuilder.ref} and accepted everywhere a value
31
+ * or column is expected (e.g. inside `sql\`\${ref} > 0\`\`).
32
+ *
33
+ * The shape matches `sql.ref()`'s return so a raw fragment from either
34
+ * source is interoperable.
35
+ */
36
+ export declare interface ColumnRef {
37
+ readonly raw: string
38
+ }
39
+ /**
40
+ * Fluent aggregate-function builder accessible via
41
+ * `eb.fn.count(...)`, `eb.fn.sum(...)`, etc. The chained `.as(name)`
42
+ * names the resulting column in the projection; `.filterWhere(...)`
43
+ * scopes the aggregate to a sub-population (`COUNT(*) FILTER (WHERE
44
+ * status = 'success')` style).
45
+ */
46
+ export declare interface AggregateExpression {
47
+ as: (alias: string) => AggregateExpression
48
+ filterWhere: (column: string, op: ExpressionOperator | string, value: unknown) => AggregateExpression
49
+ }
50
+ /**
51
+ * Aggregate-function accessor exposed on the expression builder.
52
+ *
53
+ * Covers the call sites in commerce today (`count`, `sum`, `avg`,
54
+ * `min`, `max`). Other Kysely-side aggregates (`countAll`,
55
+ * `coalesce`, etc.) can be added here as call sites surface; we
56
+ * deliberately don't widen to "everything Kysely exposes" because
57
+ * that surface keeps growing and an `any`-typed escape hatch always
58
+ * exists (`eb.fn as any).newThing(...)`) if a one-off bypass is
59
+ * genuinely needed.
60
+ */
61
+ export declare interface ExpressionFunctions {
62
+ count: (column: string) => AggregateExpression
63
+ sum: (column: string) => AggregateExpression
64
+ avg: (column: string) => AggregateExpression
65
+ min: (column: string) => AggregateExpression
66
+ max: (column: string) => AggregateExpression
67
+ }
68
+ /**
69
+ * Minimal typed expression-builder surface for sub-query / inline-
70
+ * expression callbacks (stacksjs/stacks#1892, T-2 from #1875).
71
+ *
72
+ * Background: the framework's commerce module passed `(eb: any) => …`
73
+ * to `.where()` / `.select()` callbacks across 80+ sites. The `any`
74
+ * escape meant typos like `eb.compare(...)` (no such method — it's
75
+ * `cmpr`) only surfaced at runtime, and any later rename in
76
+ * bun-query-builder couldn't break here at type-check time.
77
+ *
78
+ * This interface declares the methods commerce actually uses today —
79
+ * `or`, `cmpr`, `ref`, `raw`, plus the `fn` aggregate accessor. It
80
+ * intentionally does NOT claim to be the full Kysely
81
+ * `ExpressionBuilder<DB, TB>` type:
82
+ *
83
+ * - Stacks's `Database` is still typed as `any` (no generated
84
+ * schema map yet) so the table-aware narrowing Kysely offers
85
+ * can't be expressed here yet.
86
+ * - bun-query-builder doesn't currently re-export its internal
87
+ * `ExpressionBuilder` type, so we can't alias to the canonical
88
+ * shape upstream.
89
+ *
90
+ * When either of those changes upstream, swap this interface's
91
+ * implementation in one place rather than re-typing every call site.
92
+ */
93
+ export declare interface StacksExpressionBuilder {
94
+ or: (expressions: ReadonlyArray<unknown>) => unknown
95
+ and?: (expressions: ReadonlyArray<unknown>) => unknown
96
+ cmpr: (left: unknown, op: ExpressionOperator, right: unknown) => unknown
97
+ ref: (column: string) => ColumnRef
98
+ raw: (value: string) => ColumnRef
99
+ fn: ExpressionFunctions
100
+ readonly [extra: string]: unknown
101
+ }
28
102
  /**
29
103
  * Database types - Compatibility layer
30
104
  *
@@ -64,3 +138,11 @@ export type Updateable<T> = Partial<T>;
64
138
  * Use the query builder from bun-query-builder instead.
65
139
  */
66
140
  export type Database = any;
141
+ /**
142
+ * Comparison operator accepted by {@link StacksExpressionBuilder.cmpr}
143
+ * and friends. Mirrors the standard SQL operators the underlying
144
+ * Kysely-style builder supports.
145
+ */
146
+ export type ExpressionOperator = | '=' | '!=' | '<>' | '<' | '<=' | '>' | '>='
147
+ | 'in' | 'not in' | 'is' | 'is not'
148
+ | 'like' | 'not like' | 'ilike' | 'not ilike';
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Walk every model file (user + framework defaults) and return the
3
+ * full list of declared unique constraints — both single-column
4
+ * `unique: true` attributes and `unique: true` composite indexes.
5
+ */
6
+ export declare function getDeclaredUniques(): Promise<DeclaredUnique[]>;
7
+ /**
8
+ * Query the live database for every UNIQUE index, returning a
9
+ * normalised `{ table, name, columns }` shape. Dialect-aware: PRAGMA
10
+ * on SQLite, information_schema / pg_index on MySQL / PostgreSQL.
11
+ *
12
+ * The optional `dialect` parameter is the test seam — production code
13
+ * leaves it undefined so it's derived from `DB_CONNECTION`.
14
+ */
15
+ export declare function getLiveUniqueIndexes(dialect?: Dialect): Promise<LiveUniqueIndex[]>;
16
+ /**
17
+ * Diff declared unique constraints against live UNIQUE indexes. A
18
+ * declared entry is satisfied iff some live unique index on the same
19
+ * table has an equal column SET (sorted, case-insensitive). Declared
20
+ * entries whose table is absent from the live DB go to `skippedTables`
21
+ * (feature-gated commerce/CMS models legitimately have no table yet)
22
+ * rather than `missing`.
23
+ */
24
+ export declare function auditUniqueIndexes(dialect?: Dialect): Promise<UniqueAuditResult>;
25
+ // stacksjs/stacks#1952 — Unique-index drift audit. Compares each
26
+ // model's declared uniqueness (`unique: true` attributes and
27
+ // `unique: true` composite indexes) against the UNIQUE indexes that
28
+ // actually exist in the live database.
29
+ //
30
+ // This is the `buddy doctor` companion to the migrate-side self-heal
31
+ // (#1952): `buddy migrate` already re-queues missing unique-index
32
+ // migrations, but that only fires when you run migrate, doesn't help
33
+ // apps scaffolded during the stub era (whose own repos carry
34
+ // `SELECT 1;` stub migrations), and hard-fails mid-migrate on
35
+ // pre-existing duplicate rows instead of reporting first. This audit
36
+ // surfaces the drift read-only, before you migrate.
37
+ //
38
+ // Matching is by COLUMN SET, never by index name — the generator
39
+ // emits names like `users_users_email_unique` while migration
40
+ // filenames say `users_email_unique`, so names are not stable.
41
+ export declare interface DeclaredUnique {
42
+ table: string
43
+ columns: string[]
44
+ model: string
45
+ source: 'attribute' | 'index'
46
+ indexName?: string
47
+ }
48
+ export declare interface LiveUniqueIndex {
49
+ table: string
50
+ name: string
51
+ columns: string[]
52
+ }
53
+ export declare interface UniqueAuditResult {
54
+ supported: boolean
55
+ declared: DeclaredUnique[]
56
+ live: LiveUniqueIndex[]
57
+ missing: DeclaredUnique[]
58
+ skippedTables: string[]
59
+ }
60
+ declare type Dialect = 'sqlite' | 'mysql' | 'postgres' | 'other';
package/dist/utils.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { createQueryBuilder, setConfig } from 'bun-query-builder';
2
- import type { DatabaseSchema } from 'bun-query-builder';
1
+ import { createQueryBuilder, setConfig } from '@stacksjs/query-builder';
2
+ import type { DatabaseSchema } from '@stacksjs/query-builder';
3
+ export declare function acquireDbConfigLock(): Promise<() => void>;
3
4
  // Function to initialize the config when it's available
4
5
  export declare function initializeDbConfig(config: any): void;
5
6
  export declare function ensureDatabaseConfigLoaded(): Promise<void>;
@@ -88,6 +89,26 @@ export declare interface FluentChain {
88
89
  doesntExist: () => Promise<boolean>
89
90
  [key: string]: any
90
91
  }
92
+ /*.ts` and
93
+ * emits `database/types.d.ts` containing:
94
+ *
95
+ * ```ts
96
+ * declare module '@stacksjs/database' {
97
+ * interface DatabaseSchema {
98
+ * court_houses: { columns: { id: number; name: string; ... } }
99
+ * judges: { columns: { id: number; name: string; court_id: number; ... } }
100
+ * }
101
+ * }
102
+ * ```
103
+ *
104
+ * Once that file is loaded into the TS project, `db.selectFrom('co|')`
105
+ * autocompletes to known table names. Apps without a generated file
106
+ * still compile — the `(string & {})` branch on `TableName` keeps the
107
+ * type as a literal-union+escape-hatch, so any string is accepted
108
+ * but known keys are surfaced first by the language server.
109
+ */
110
+ // eslint-disable-next-line ts/no-empty-object-type
111
+ export declare interface DatabaseSchema {}
91
112
  // Permissive schema type that accepts any table name with any columns
92
113
  // This allows the query builder to work before model types are generated
93
114
  declare type AnySchema = DatabaseSchema<any> & Record<string, { columns: Record<string, any>, primaryKey: string }>;
@@ -135,5 +156,23 @@ declare type GenericPassthroughKeys = | 'transaction'
135
156
  | 'raw'
136
157
  | 'simple'
137
158
  | 'file';
159
+ /**
160
+ * Accept either a registered table name (from augmented
161
+ * `DatabaseSchema`) for autocomplete, or any other string for apps
162
+ * that haven't generated types yet / tables not in a model file.
163
+ *
164
+ * The `(string & {})` branch prevents TS from collapsing the union
165
+ * back to `string` and losing the autocomplete narrowing — a
166
+ * well-documented LiteralUnion trick.
167
+ */
168
+ // eslint-disable-next-line ts/no-empty-object-type
169
+ export type TableName = (keyof DatabaseSchema & string) | (string & {});
170
+ // SQLite bootstrap pragmas (stacksjs/stacks#1951) now live in
171
+ // @stacksjs/query-builder — the one chokepoint every framework
172
+ // query-builder instance is created through — so EVERY fresh sqlite
173
+ // connection gets `foreign_keys = ON`, including builders created outside
174
+ // this module (e.g. the ORM auto-CRUD routes). Re-exported here for
175
+ // backwards compatibility with existing imports.
176
+ export { applySqlitePragmas, SQLITE_BOOTSTRAP_PRAGMAS } from '@stacksjs/query-builder';
138
177
  // Export setConfig if available
139
178
  export { setConfig };
@@ -0,0 +1,22 @@
1
+ import { sqlHelpers } from './sql-helpers';
2
+ /** Pure builder so tests can assert per-dialect DDL without a live DB. */
3
+ export declare function uuidColumnSql(table: string, sql: SqlHelpers): string;
4
+ /**
5
+ * Resolve every table backing a model with `useUuid: true`, across both
6
+ * userland (`app/Models`) and framework-default (`defaults/app/Models`)
7
+ * model directories. Exported so tests (and `doctor`-style diagnostics) can
8
+ * inspect the resolved set without touching a live database.
9
+ */
10
+ export declare function findUuidTables(): Promise<string[]>;
11
+ /**
12
+ * Guarantee-ALTER `uuid` onto every table whose model declares
13
+ * `useUuid: true`, independently try/catch-swallowed per table so one
14
+ * already-having-the-column (or not-yet-existing) table never skips the
15
+ * rest. Exported so `buddy migrate`/`migrate:fresh` can call it after model
16
+ * migrations run, same pattern as {@link ensureUsersAuthColumns} — see the
17
+ * call sites in buddy/src/commands/migrate.ts.
18
+ */
19
+ export declare function ensureUuidColumns(sql: SqlHelpers, options?: { verbose?: boolean }): Promise<void>;
20
+ /** Convenience wrapper resolving dialect helpers from `DB_CONNECTION`, for call sites that don't already have a `SqlHelpers` instance. */
21
+ export declare function ensureUuidColumnsForCurrentDriver(options?: { verbose?: boolean }): Promise<void>;
22
+ declare type SqlHelpers = ReturnType<typeof sqlHelpers>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/database",
3
3
  "type": "module",
4
- "version": "0.70.44",
4
+ "version": "0.70.53",
5
5
  "description": "The Stacks database integration.",
6
6
  "author": "Chris Breuer",
7
7
  "contributors": [
@@ -50,18 +50,18 @@
50
50
  "prepublishOnly": "bun run build"
51
51
  },
52
52
  "dependencies": {
53
- "bun-query-builder": "^0.1.21"
53
+ "bun-query-builder": "^0.1.38"
54
54
  },
55
55
  "devDependencies": {
56
- "@stacksjs/cli": "^0.70.44",
57
- "@stacksjs/config": "^0.70.44",
58
- "@stacksjs/logging": "^0.70.44",
59
- "@stacksjs/router": "^0.70.44",
56
+ "@stacksjs/cli": "0.70.53",
57
+ "@stacksjs/config": "0.70.53",
58
+ "@stacksjs/logging": "0.70.53",
59
+ "@stacksjs/router": "0.70.53",
60
60
  "better-dx": "^0.2.12",
61
- "@stacksjs/path": "^0.70.44",
62
- "@stacksjs/query-builder": "^0.70.44",
63
- "@stacksjs/storage": "^0.70.44",
64
- "@stacksjs/strings": "^0.70.44",
65
- "@stacksjs/utils": "^0.70.44"
61
+ "@stacksjs/path": "0.70.53",
62
+ "@stacksjs/query-builder": "0.70.53",
63
+ "@stacksjs/storage": "0.70.53",
64
+ "@stacksjs/strings": "0.70.53",
65
+ "@stacksjs/utils": "0.70.53"
66
66
  }
67
67
  }