@stacksjs/database 0.70.45 → 0.70.54
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/LICENSE.md +21 -0
- package/dist/auth-tables.d.ts +56 -0
- package/dist/class-seeder.d.ts +49 -0
- package/dist/database.d.ts +3 -2
- package/dist/driver-config.d.ts +30 -1
- package/dist/drivers/dynamodb.d.ts +70 -18
- package/dist/drivers/helpers.d.ts +6 -0
- package/dist/drivers/mysql.d.ts +1 -1
- package/dist/drivers/postgres.d.ts +1 -1
- package/dist/drivers/sqlite.d.ts +6 -9
- package/dist/factory.d.ts +41 -0
- package/dist/fk-audit.d.ts +101 -0
- package/dist/index.d.ts +39 -2
- package/dist/index.js +1140 -1104
- package/dist/migration-lock.d.ts +23 -0
- package/dist/migrations.d.ts +46 -5
- package/dist/notification-tables.d.ts +20 -0
- package/dist/rbac-tables.d.ts +17 -0
- package/dist/seed-scaffold.d.ts +34 -0
- package/dist/seeder.d.ts +59 -0
- package/dist/sql-helpers.d.ts +2 -0
- package/dist/transaction-context.d.ts +52 -0
- package/dist/types.d.ts +82 -0
- package/dist/unique-audit.d.ts +60 -0
- package/dist/utils.d.ts +41 -2
- package/dist/uuid-columns.d.ts +22 -0
- package/package.json +11 -11
|
@@ -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';
|
package/dist/migrations.d.ts
CHANGED
|
@@ -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
|
|
24
|
-
|
|
25
|
-
|
|
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 };
|
package/dist/sql-helpers.d.ts
CHANGED
|
@@ -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 '
|
|
2
|
-
import type { DatabaseSchema } from '
|
|
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.
|
|
4
|
+
"version": "0.70.54",
|
|
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.
|
|
53
|
+
"bun-query-builder": "^0.1.38"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
-
"@stacksjs/cli": "
|
|
57
|
-
"@stacksjs/config": "
|
|
58
|
-
"@stacksjs/logging": "
|
|
59
|
-
"@stacksjs/router": "
|
|
56
|
+
"@stacksjs/cli": "0.70.54",
|
|
57
|
+
"@stacksjs/config": "0.70.54",
|
|
58
|
+
"@stacksjs/logging": "0.70.54",
|
|
59
|
+
"@stacksjs/router": "0.70.54",
|
|
60
60
|
"better-dx": "^0.2.12",
|
|
61
|
-
"@stacksjs/path": "
|
|
62
|
-
"@stacksjs/query-builder": "
|
|
63
|
-
"@stacksjs/storage": "
|
|
64
|
-
"@stacksjs/strings": "
|
|
65
|
-
"@stacksjs/utils": "
|
|
61
|
+
"@stacksjs/path": "0.70.54",
|
|
62
|
+
"@stacksjs/query-builder": "0.70.54",
|
|
63
|
+
"@stacksjs/storage": "0.70.54",
|
|
64
|
+
"@stacksjs/strings": "0.70.54",
|
|
65
|
+
"@stacksjs/utils": "0.70.54"
|
|
66
66
|
}
|
|
67
67
|
}
|