@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 ADDED
@@ -0,0 +1,21 @@
1
+ # MIT License
2
+
3
+ Copyright (c) 2023 Open Web Foundation
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,4 +1,60 @@
1
+ import { sqlHelpers } from './sql-helpers';
2
+ /**
3
+ * Defensive ALTER guaranteeing `users.email_verified_at` — the column
4
+ * `verifyEmail()` writes and the `verified` middleware reads, but which
5
+ * no generated users migration ever creates (stacksjs/stacks#1948).
6
+ * Pure builder so tests can assert per-dialect DDL without a live DB.
7
+ */
8
+ export declare function usersEmailVerifiedAtSql(sql: SqlHelpers): string;
9
+ /**
10
+ * Defensive ALTER guaranteeing `users.password_changed_at` — the column
11
+ * `resetPassword()` stamps and the token-validation paths read to bind a
12
+ * token's validity to the user's credential state (stacksjs/stacks#1957,
13
+ * a #1947 follow-up). No generated users migration creates it, so the
14
+ * same pure-builder + try/catch-swallow pattern as
15
+ * {@link usersEmailVerifiedAtSql} guarantees it from both schema paths.
16
+ */
17
+ export declare function usersPasswordChangedAtSql(sql: SqlHelpers): string;
18
+ /**
19
+ * Defensive ALTER guaranteeing `users.two_factor_secret` and
20
+ * `users.two_factor_enabled` — storage/framework/core/auth/src/
21
+ * authenticator.ts's TOTP helpers (generateTwoFactorSecret,
22
+ * verifyTwoFactorCode) have existed since early in the framework's
23
+ * history, but nothing ever created the columns a caller would persist
24
+ * them to, or the `passkeys`/`webauthn_challenges` tables
25
+ * storage/framework/core/auth/src/passkey.ts's WebAuthn helpers
26
+ * unconditionally query against — every passkey/2FA call site was
27
+ * dead code pointed at tables that never existed on any install,
28
+ * `buddy new` included. Same defensive ALTER + swallow pattern as
29
+ * {@link usersEmailVerifiedAtSql}.
30
+ */
31
+ export declare function usersTwoFactorColumnsSql(sql: SqlHelpers): string[];
32
+ /**
33
+ * Defensive ALTER guaranteeing `users.stripe_id` — the `billable`
34
+ * model trait's methods (createStripeCustomer/createOrGetStripeUser,
35
+ * used by `user.checkout(...)`) read and write this column
36
+ * unconditionally, but it's runtime-mixin-only (createBillableMethods
37
+ * in orm/define-model.ts, gated behind `traits.billable`): nothing in
38
+ * migration codegen ever creates the column itself, on any model, with
39
+ * or without the trait enabled. Every Stripe checkout call site was
40
+ * dead code pointed at a column that never existed, `buddy new`
41
+ * included — same shape as the passkeys/two_factor gap above (see
42
+ * stacksjs/status#1 Phase 9).
43
+ */
44
+ export declare function usersStripeIdSql(): string;
45
+ /**
46
+ * Runs every `users` guarantee-column ALTER (email_verified_at,
47
+ * password_changed_at, two_factor_secret, two_factor_enabled,
48
+ * stripe_id), each independently try/catch-swallowed so one
49
+ * already-existing column (or a not-yet-existing `users` table) never
50
+ * skips the others. Exported so `buddy migrate`/`migrate:fresh` can
51
+ * call it a second time after the numbered model migrations run — see
52
+ * the call site in {@link migrateAuthTables} for why a single call
53
+ * isn't enough.
54
+ */
55
+ export declare function ensureUsersAuthColumns(sql: SqlHelpers, options?: { verbose?: boolean }): Promise<void>;
1
56
  /**
2
57
  * Create all authentication tables
3
58
  */
4
59
  export declare function migrateAuthTables(options?: { verbose?: boolean }): Promise<{ success: boolean, error?: string }>;
60
+ declare type SqlHelpers = ReturnType<typeof sqlHelpers>;
@@ -1,4 +1,34 @@
1
+ /**
2
+ * Topologically sort seeders by their declared `dependencies`. Ties
3
+ * (and dependency-free seeders) come out in alphabetical order so the
4
+ * result is deterministic across filesystems and runs.
5
+ *
6
+ * Unknown dependency names are dropped from the graph with a warning —
7
+ * they may refer to model-factory seeders that ran earlier in the
8
+ * `buddy seed` pipeline, or be stale references from a rename. The run
9
+ * doesn't fail because of them.
10
+ *
11
+ * Cycles throw with the offending class names in the error message.
12
+ *
13
+ * Exported for testing.
14
+ */
15
+ export declare function topoSortSeeders(seeders: Array<{ name: string, dependencies?: string[] }>): string[];
1
16
  /*.ts`; an explicit `--class` filters to one.
17
+ *
18
+ * Ordering:
19
+ * 1. Files matching `*.ts` (excluding `_*.ts`) are imported.
20
+ * 2. If any seeder declares `dependencies`, the runnable set is
21
+ * topologically sorted (alphabetical tie-break).
22
+ * 3. Otherwise, the alphabetical order from `Array.sort()` wins —
23
+ * cheaper than the topo path and predictable across filesystems.
24
+ *
25
+ * Class-name filtering via `options.class` short-circuits both paths
26
+ * and runs only that one seeder. Cross-seeder dependencies are NOT
27
+ * resolved transitively in that mode — the caller takes responsibility
28
+ * for whatever prereqs are needed.
29
+ *
30
+ * See stacksjs/stacks#1855 for the original report of unsorted FS
31
+ * iteration producing zero-row seed runs.
2
32
  */
3
33
  export declare function runClassSeeders(options?: RunOptions): Promise<{ ran: string[], skipped: string[] }>;
4
34
  declare interface RunOptions {
@@ -9,8 +39,27 @@ declare interface RunOptions {
9
39
  * Base class for class-based seeders. Subclass this and implement
10
40
  * `async run()`. Seeders may call `this.call()` to invoke other
11
41
  * seeders, mirroring Laravel's nested-seeder pattern.
42
+ *
43
+ * Cross-seeder ordering can be declared explicitly via `dependencies`:
44
+ *
45
+ * ```ts
46
+ * export default class JudgeSeeder extends Seeder {
47
+ * dependencies = ['CourtHouseSeeder']
48
+ * async run() { ... }
49
+ * }
50
+ * ```
51
+ *
52
+ * The class name of each dependency is matched against the class names
53
+ * `runClassSeeders` discovered in the seeders directory. Unknown
54
+ * dependency names are warned about but don't fail the run (they may
55
+ * refer to a model-factory seeder run earlier in `buddy seed`).
56
+ *
57
+ * When no `dependencies` are declared, seeders run in alphabetical
58
+ * order — predictable across filesystems and good enough for projects
59
+ * that name seeders by data flow (CourtHouse → Judge → Review).
12
60
  */
13
61
  export declare abstract class Seeder {
62
+ dependencies?: string[];
14
63
  abstract run(): Promise<void> | void;
15
64
  protected call(other: new () => Seeder): Promise<void>;
16
65
  }
@@ -1,4 +1,4 @@
1
- import type { QueryBuilder, QueryBuilderConfig, SupportedDialect } from 'bun-query-builder';
1
+ import type { QueryBuilder, QueryBuilderConfig, SupportedDialect } from '@stacksjs/query-builder';
2
2
  /**
3
3
  * Create a database connection with the given options
4
4
  */
@@ -74,12 +74,13 @@ export declare class Database {
74
74
  get query(): QueryBuilder<any>;
75
75
  initialize(): void;
76
76
  switchDriver(driver: SupportedDialect, connection: DatabaseConnectionConfig): void;
77
- close(): void;
77
+ close(): Promise<void>;
78
78
  static fromConfig(config: {
79
79
  default: SupportedDialect
80
80
  connections: {
81
81
  sqlite?: { database: string }
82
82
  mysql?: { name: string, host?: string, port?: number, username?: string, password?: string }
83
+ singlestore?: { name: string, host?: string, port?: number, username?: string, password?: string }
83
84
  postgres?: { name: string, host?: string, port?: number, username?: string, password?: string }
84
85
  }
85
86
  }, env?: string): Database;
@@ -1,4 +1,4 @@
1
- import type { SupportedDialect } from 'bun-query-builder';
1
+ import type { SupportedDialect } from '@stacksjs/query-builder';
2
2
  /**
3
3
  * Get the connection string for a given driver and configuration
4
4
  */
@@ -36,6 +36,16 @@ export declare const driverDefaults: {
36
36
  prefix: '';
37
37
  charset: 'utf8mb4';
38
38
  collation: 'utf8mb4_unicode_ci'
39
+ };
40
+ singlestore: {
41
+ name: 'stacks';
42
+ host: '127.0.0.1';
43
+ port: 3306;
44
+ username: 'root';
45
+ password: '';
46
+ prefix: '';
47
+ charset: 'utf8mb4';
48
+ ssl: false
39
49
  };
40
50
  postgres: {
41
51
  name: 'stacks';
@@ -68,6 +78,24 @@ export declare interface MysqlConfig {
68
78
  charset?: string
69
79
  collation?: string
70
80
  }
81
+ /**
82
+ * SingleStore specific configuration.
83
+ *
84
+ * SingleStore (formerly MemSQL) speaks the MySQL wire protocol, so it shares
85
+ * MySQL's connection shape. It diverges only in DDL (distributed tables with
86
+ * SHARD KEY / SORT KEY, no foreign keys) — handled by the migration generator,
87
+ * not by the connection layer.
88
+ */
89
+ export declare interface SinglestoreConfig {
90
+ name: string
91
+ host?: string
92
+ port?: number
93
+ username?: string
94
+ password?: string
95
+ prefix?: string
96
+ charset?: string
97
+ ssl?: boolean
98
+ }
71
99
  /**
72
100
  * PostgreSQL specific configuration
73
101
  */
@@ -106,6 +134,7 @@ export declare interface DynamoDbConfig {
106
134
  export declare interface DatabaseConnections {
107
135
  sqlite?: SqliteConfig
108
136
  mysql?: MysqlConfig
137
+ singlestore?: SinglestoreConfig
109
138
  postgres?: PostgresConfig
110
139
  dynamodb?: DynamoDbConfig
111
140
  }
@@ -2,11 +2,11 @@ import type { Model } from '@stacksjs/types';
2
2
  /**
3
3
  * Marshall a JS object to DynamoDB format
4
4
  */
5
- declare function marshall(obj: Record<string, any>): Record<string, any>;
5
+ declare function marshall(obj: Record<string, unknown>): DynamoItem;
6
6
  /**
7
7
  * Unmarshall a DynamoDB object to JS format
8
8
  */
9
- declare function unmarshall(obj: Record<string, any>): Record<string, any>;
9
+ declare function unmarshall(obj: DynamoItem): Record<string, unknown>;
10
10
  /**
11
11
  * Generate key pattern for an entity
12
12
  */
@@ -27,6 +27,32 @@ export declare function createDynamo(): DynamoClient;
27
27
  * DynamoDB client singleton
28
28
  */
29
29
  export declare const dynamo: DynamoClient;
30
+ /**
31
+ * Minimal AWS-SDK `DynamoDBClient` shape — just the methods this
32
+ * driver calls. Typed locally so callers wiring their own SDK
33
+ * client into `setClient()` get a clear compile-time contract
34
+ * instead of a runtime "function not found" surprise.
35
+ */
36
+ export declare interface DynamoSdkClient {
37
+ query: (req: Record<string, unknown>) => Promise<{
38
+ Items?: DynamoItem[]
39
+ Count?: number
40
+ ScannedCount?: number
41
+ LastEvaluatedKey?: DynamoItem
42
+ }>
43
+ scan: (req: Record<string, unknown>) => Promise<{
44
+ Items?: DynamoItem[]
45
+ Count?: number
46
+ ScannedCount?: number
47
+ LastEvaluatedKey?: DynamoItem
48
+ }>
49
+ getItem: (req: Record<string, unknown>) => Promise<{ Item?: DynamoItem }>
50
+ putItem: (req: Record<string, unknown>) => Promise<unknown>
51
+ deleteItem: (req: Record<string, unknown>) => Promise<unknown>
52
+ updateItem: (req: Record<string, unknown>) => Promise<unknown>
53
+ batchWriteItem: (req: Record<string, unknown>) => Promise<unknown>
54
+ transactWriteItems: (req: Record<string, unknown>) => Promise<unknown>
55
+ }
30
56
  /**
31
57
  * DynamoDB connection configuration
32
58
  */
@@ -72,46 +98,72 @@ export declare interface SortKeyBuilder {
72
98
  * Batch write operation
73
99
  */
74
100
  export declare interface BatchWriteOperation {
75
- put?: { entity: string, item: Record<string, any> }
101
+ put?: { entity: string, item: Record<string, unknown> }
76
102
  delete?: { entity: string, pk: string, sk: string }
77
103
  }
78
104
  /**
79
105
  * Transact write operation
80
106
  */
81
107
  export declare interface TransactWriteOperation {
82
- put?: { entity: string, item: Record<string, any>, condition?: string }
83
- update?: { entity: string, pk: string, sk?: string, set?: Record<string, any>, add?: Record<string, number>, remove?: string[] }
108
+ put?: { entity: string, item: Record<string, unknown>, condition?: string }
109
+ update?: { entity: string, pk: string, sk?: string, set?: Record<string, unknown>, add?: Record<string, number>, remove?: string[] }
84
110
  delete?: { entity: string, pk: string, sk: string, condition?: string }
85
111
  conditionCheck?: { entity: string, pk: string, sk: string, condition: string }
86
112
  }
87
113
  /**
88
114
  * Query result
89
115
  */
90
- export declare interface QueryResult<T = any> {
116
+ export declare interface QueryResult<T = unknown> {
91
117
  items: T[]
92
118
  count: number
93
119
  scannedCount?: number
94
- lastKey?: Record<string, any>
120
+ lastKey?: Record<string, unknown>
95
121
  }
122
+ /**
123
+ * DynamoDB AttributeValue discriminated union (stacksjs/stacks#1894 T-8).
124
+ *
125
+ * Mirrors the wire format AWS SDK's `DynamoDBClient` produces and
126
+ * consumes, declared locally so this package doesn't take a hard
127
+ * dependency on `@aws-sdk/client-dynamodb` just for its types. Each
128
+ * concrete variant carries its own typed payload; consumers narrow
129
+ * via `'S' in attr` / etc.
130
+ *
131
+ * Why not just import from AWS SDK: the framework's DynamoDB driver
132
+ * ships as an optional peer integration. Pulling AWS SDK as a type-
133
+ * level dep would force every Stacks app to install it whether or
134
+ * not they use the driver.
135
+ */
136
+ export type DynamoAttributeValue = | { S: string }
137
+ | { N: string }
138
+ | { B: Uint8Array | string }
139
+ | { BOOL: boolean }
140
+ | { NULL: true }
141
+ | { L: DynamoAttributeValue[] }
142
+ | { M: Record<string, DynamoAttributeValue> }
143
+ | { SS: string[] }
144
+ | { NS: string[] }
145
+ | { BS: Array<Uint8Array | string> }
146
+ /** Marshalled item — the on-the-wire representation. */
147
+ export type DynamoItem = Record<string, DynamoAttributeValue>;
96
148
  /**
97
149
  * Entity-centric query builder for DynamoDB
98
150
  */
99
- export declare class EntityQueryBuilder<T = any> {
100
- constructor(client: any, tableName: string, config: { pkAttribute: string, skAttribute: string, entityTypeAttribute: string, keyDelimiter: string });
151
+ export declare class EntityQueryBuilder<T = unknown> {
152
+ constructor(client: DynamoSdkClient | undefined, tableName: string, config: { pkAttribute: string, skAttribute: string, entityTypeAttribute: string, keyDelimiter: string });
101
153
  entity(entityType: string): this;
102
154
  pk(value: string): this;
103
155
  get sk(): SortKeyBuilder;
104
156
  index(indexName: string): this;
105
157
  project(...attributes: string[]): this;
106
- filter(attribute: string, operator: string, value?: any): this;
107
- where(attribute: string, value: any): this;
108
- whereIn(attribute: string, values: any[]): this;
158
+ filter(attribute: string, operator: string, value?: unknown): this;
159
+ where(attribute: string, value: unknown): this;
160
+ whereIn(attribute: string, values: unknown[]): this;
109
161
  limit(count: number): this;
110
162
  asc(): this;
111
163
  desc(): this;
112
164
  consistent(): this;
113
- startFrom(key: Record<string, any>): this;
114
- toRequest(): Record<string, any>;
165
+ startFrom(key: Record<string, unknown>): this;
166
+ toRequest(): Record<string, unknown>;
115
167
  get(): Promise<T[]>;
116
168
  first(): Promise<T | undefined>;
117
169
  getAll(): Promise<T[]>;
@@ -123,18 +175,18 @@ export declare class EntityQueryBuilder<T = any> {
123
175
  declare class DynamoClient {
124
176
  connection(config: DynamoConnectionConfig): this;
125
177
  isConfigured(): boolean;
126
- setClient(client: any): this;
127
- getClient(): any;
178
+ setClient(client: DynamoSdkClient): this;
179
+ getClient(): DynamoSdkClient | undefined;
128
180
  registerEntity(mapping: SingleTableEntityMapping): this;
129
181
  registerModel(model: Model): this;
130
182
  getEntityMapping(entityType: string): SingleTableEntityMapping | undefined;
131
183
  entity<T = any>(entityType: string): EntityQueryBuilder<T>;
132
184
  batchWrite(operations: BatchWriteOperation[]): Promise<void>;
133
185
  transactWrite(operations: TransactWriteOperation[]): Promise<void>;
134
- put(entity: string, item: Record<string, any>): Promise<void>;
186
+ put(entity: string, item: Record<string, unknown>): Promise<void>;
135
187
  get<T = any>(pk: string, sk?: string): Promise<T | undefined>;
136
188
  delete(pk: string, sk?: string): Promise<void>;
137
- update(pk: string, sk: string | undefined, updates: Record<string, any>): Promise<void>;
189
+ update(pk: string, sk: string | undefined, updates: Record<string, unknown>): Promise<void>;
138
190
  getTableName(): string;
139
191
  getConfig(): {
140
192
  tableName: string
@@ -18,6 +18,12 @@ export declare function isArrayEqual(arr1: (number | undefined)[], arr2: (number
18
18
  export declare function findDifferingKeys(obj1: any, obj2: any): { key: string, max: number, min: number }[];
19
19
  export declare function fetchTables(): Promise<string[]>;
20
20
  export declare function getUpvoteTableName(model: Model, tableName: string): string | undefined;
21
+ /**
22
+ * Foreign-key column of the likes pivot. MUST mirror the runtime default in
23
+ * orm/src/traits/likeable.ts (`${tableName.replace(/s$/, '')}_id`) — a pivot
24
+ * generated with any other column name is a table `like()` cannot write to.
25
+ */
26
+ export declare function getLikeableForeignKey(model: Model, tableName: string): string;
21
27
  export declare function prepareNumberColumnType(validator: NumberValidatorType, driver?: string): string;
22
28
  // Add new function for enum column types
23
29
  export declare function prepareEnumColumnType(validator: EnumValidatorType, driver?: string): string;
@@ -3,5 +3,5 @@ export declare function resetMysqlDatabase(): Promise<Ok<string, never>>;
3
3
  export declare function dropMysqlTables(): Promise<void>;
4
4
  export declare function generateMysqlMigration(modelPath: string): Promise<void>;
5
5
  export declare function generateMysqlTraitMigrations(): Promise<void>;
6
- export declare function createMysqlForeignKeyMigrations(modelPath: string): Promise<void>;
7
6
  export declare function createAlterTableMigration(modelPath: string): Promise<void>;
7
+ export declare function generateIndexCreationSQL(tableName: string, index: { name: string, columns: string[], unique?: boolean, where?: string }): string;
@@ -3,5 +3,5 @@ export declare function dropPostgresTables(): Promise<void>;
3
3
  export declare function generatePostgresTraitMigrations(): Promise<void>;
4
4
  export declare function resetPostgresDatabase(): Promise<Ok<string, never>>;
5
5
  export declare function generatePostgresMigration(modelPath: string): Promise<void>;
6
- export declare function createPostgresForeignKeyMigrations(modelPath: string): Promise<void>;
6
+ export declare function generateIndexCreationSQL(tableName: string, index: { name: string, columns: string[], unique?: boolean, where?: string }): string;
7
7
  export declare function fetchPostgresTables(): Promise<string[]>;
@@ -5,19 +5,16 @@ export declare function resetSqliteDatabase(): Promise<Ok<string, never>>;
5
5
  * cheap to re-apply, but each one is a no-op once set so repeated calls
6
6
  * during dev hot-reload don't accumulate state.
7
7
  *
8
- * - WAL journaling: lets readers and a single writer proceed in parallel
9
- * instead of serializing every transaction. Critical for dev where the
10
- * API server, queue worker, and dashboard all hit the same file.
11
- * - foreign_keys=ON: SQLite ships with FK enforcement OFF by default.
12
- * Without this, FK constraint violations are silently ignored — broken
13
- * relations land in the DB and fail later, far from the original write.
14
- * - busy_timeout: backs off when the file is locked by another writer
15
- * instead of failing the whole query immediately.
8
+ * Connection bootstrap now applies SQLITE_BOOTSTRAP_PRAGMAS automatically
9
+ * inside @stacksjs/query-builder's wrapped `createQueryBuilder`
10
+ * (stacksjs/stacks#1951); this export remains for explicit re-application.
11
+ * See the shared list in @stacksjs/query-builder for what each pragma does
12
+ * and why it must be set per connection.
16
13
  */
17
14
  export declare function configureSqlitePragmas(): Promise<void>;
18
15
  export declare function dropSqliteTables(): Promise<void>;
19
16
  export declare function fetchSqliteFile(): string;
20
17
  export declare function fetchTestSqliteFile(): string;
21
18
  export declare function generateSqliteMigration(modelPath: string): Promise<void>;
22
- export declare function createSqliteForeignKeyMigrations(_modelPath: string): Promise<void>;
23
19
  export declare function copyModelFiles(modelPath: string): Promise<void>;
20
+ export declare function generateIndexCreationSQL(tableName: string, index: { name: string, columns: string[], unique?: boolean, where?: string }): string;
@@ -0,0 +1,41 @@
1
+ import type { Attribute, Model } from '@stacksjs/types';
2
+ import type { SeedResult } from './seeder';
3
+ /**
4
+ * Build the internal `SeederModel`-shaped payload that `seedModelDirect`
5
+ * expects from a public-API call. Pure function — exported separately
6
+ * so tests can assert the override-precedence rules without touching
7
+ * the database.
8
+ *
9
+ * Precedence (lowest → highest): per-attribute `factory` output →
10
+ * global `options.with` → per-row `options.rows[i]`. All keys are
11
+ * snake-cased before insert so callers can use the model's camelCase
12
+ * attribute names without thinking about column naming.
13
+ */
14
+ export declare function buildSeederPayload(modelInput: unknown, options?: GenerateOptions): {
15
+ name: string
16
+ table: string
17
+ count: number
18
+ fixtures: Array<Record<string, unknown>>
19
+ attributes: Record<string, Attribute>
20
+ model: Model
21
+ };
22
+ /**
23
+ * Generate factory rows for a model and insert them. Designed to be
24
+ * called from a class seeder.
25
+ *
26
+ * Honours the model's per-attribute `factory: faker => …` declarations
27
+ * — exactly the same code path as the legacy auto-walker — but without
28
+ * the implicit "every model with `useSeeder` fires on every run"
29
+ * coupling. See stacksjs/stacks#1919 for the rationale.
30
+ */
31
+ export declare function generate(modelInput: unknown, options?: GenerateOptions): Promise<SeedResult>;
32
+ export declare const factory: {
33
+
34
+ };
35
+ export declare interface GenerateOptions {
36
+ count?: number
37
+ fresh?: boolean
38
+ verbose?: boolean
39
+ with?: Record<string, unknown>
40
+ rows?: Array<Record<string, unknown>>
41
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Glob, but resilient to "directory doesn't exist yet" (the userland
3
+ * `app/Models/` directory in particular often doesn't exist when the
4
+ * audit runs in framework tests or scaffolded apps that haven't
5
+ * created any models yet). Avoids the Bun Glob ENOENT.
6
+ */
7
+ export declare function safeGlob(pattern: string): string[];
8
+ /**
9
+ * Walk every model file (user + framework defaults) and return the
10
+ * full list of declared `belongsTo` foreign keys.
11
+ *
12
+ * Convention: `Comment` with `belongsTo: ['Post']` implies the FK
13
+ * `comments.post_id → posts.id`. Same shape the migration generator
14
+ * uses, so we keep it consistent here. Other relationship types
15
+ * (`hasOne`, `hasMany`, `belongsToMany` through tables) imply FKs in
16
+ * the *other* direction or in pivot tables — we only check
17
+ * `belongsTo` here for the simplest, highest-signal audit.
18
+ */
19
+ export declare function getDeclaredFKs(): Promise<DeclaredFK[]>;
20
+ /**
21
+ * Query the live database for every foreign key constraint, returning
22
+ * a normalised shape. Dialect-aware: PRAGMA on SQLite,
23
+ * information_schema on MySQL / PostgreSQL.
24
+ */
25
+ export declare function getLiveFKs(): Promise<LiveFK[]>;
26
+ /**
27
+ * Diff declared FKs against live FKs. Returns the declared FKs that
28
+ * have no matching row in the live database — these are the silent
29
+ * "FK should be enforcing referential integrity but isn't" cases that
30
+ * motivated this audit.
31
+ *
32
+ * Match key is `fromTable.fromColumn → toTable.toColumn` (case-
33
+ * insensitive). Extra live FKs (present in DB but not in any model)
34
+ * are not reported — those usually come from manual migrations or
35
+ * external tooling, both legitimate.
36
+ */
37
+ export declare function auditForeignKeys(): Promise<FkAuditResult>;
38
+ /**
39
+ * Scan the live database for rows whose foreign key references a
40
+ * parent row that doesn't exist. SQLite's `PRAGMA foreign_key_check`
41
+ * does this regardless of the `foreign_keys` pragma state and excludes
42
+ * NULL-FK rows. The violating column is resolved by joining the
43
+ * reported `fkid` against `PRAGMA foreign_key_list(table)`.
44
+ */
45
+ export declare function findFkOrphans(dialect?: 'sqlite' | 'mysql' | 'postgres' | 'other'): Promise<FkOrphanReport>;
46
+ // stacksjs/stacks#1916 — Foreign-key audit. Compares each model's
47
+ // declared `belongsTo` relationships against the FKs that actually
48
+ // exist in the live database. Drives `buddy doctor`'s FK integrity
49
+ // check, surfaces the "you flipped DB_CONNECTION but the FKs didn't
50
+ // follow" failure mode that motivated #1915 and #1916.
51
+ //
52
+ // Two halves:
53
+ //
54
+ // 1. Declared FKs: walk model files, look at `belongsTo`, compute
55
+ // the implied FK shape `{ fromTable, fromColumn, toTable,
56
+ // toColumn }`. Convention is `<related>_id` → `<related>.id`,
57
+ // same as the migration generator.
58
+ //
59
+ // 2. Live FKs: query the live database. SQLite via
60
+ // `PRAGMA foreign_key_list("…")`, MySQL/Postgres via
61
+ // `information_schema.key_column_usage`.
62
+ export declare interface DeclaredFK {
63
+ fromTable: string
64
+ fromColumn: string
65
+ toTable: string
66
+ toColumn: string
67
+ model: string
68
+ }
69
+ export declare interface LiveFK {
70
+ fromTable: string
71
+ fromColumn: string
72
+ toTable: string
73
+ toColumn: string
74
+ }
75
+ export declare interface FkAuditResult {
76
+ declared: DeclaredFK[]
77
+ live: LiveFK[]
78
+ missing: DeclaredFK[]
79
+ }
80
+ // stacksjs/stacks#1951 — FK orphan detection. FK enforcement flipped
81
+ // ON (utils.ts bootstrap pragmas) against databases that were written
82
+ // while `foreign_keys = OFF`, so legacy rows can reference parents that
83
+ // no longer exist. Those orphans silently turn previously-working
84
+ // deletes/inserts into runtime FK failures. This READ-ONLY scan finds
85
+ // them so `buddy doctor` can report before they bite.
86
+ //
87
+ // SQLite-specific: MySQL/Postgres enforce FKs natively, so the
88
+ // FK-off-legacy-data failure mode can't arise there — we degrade to
89
+ // `supported: false` rather than run an expensive per-FK anti-join.
90
+ export declare interface FkOrphan {
91
+ table: string
92
+ column: string
93
+ parent: string
94
+ count: number
95
+ sampleRowids: number[]
96
+ }
97
+ export declare interface FkOrphanReport {
98
+ supported: boolean
99
+ total: number
100
+ orphans: FkOrphan[]
101
+ }
package/dist/index.d.ts CHANGED
@@ -10,12 +10,16 @@ export type {
10
10
  PostgresConfig,
11
11
  SqliteConfig,
12
12
  } from './driver-config';
13
+ export type { GenerateOptions } from './factory';
14
+ export type { ScaffoldOptions, ScaffoldResult } from './seed-scaffold';
15
+ export type { DeclaredFK, FkAuditResult, FkOrphan, FkOrphanReport, LiveFK } from './fk-audit';
16
+ export type { DeclaredUnique, LiveUniqueIndex, UniqueAuditResult } from './unique-audit';
13
17
  export type {
14
18
  QueryBuilder,
15
19
  QueryBuilderConfig,
16
20
  Seeder as QueryBuilderSeeder,
17
21
  SupportedDialect,
18
- } from 'bun-query-builder';
22
+ } from '@stacksjs/query-builder';
19
23
  export type {
20
24
  DynamoConnectionConfig,
21
25
  SingleTableEntityMapping,
@@ -85,20 +89,53 @@ export { Seeder, runClassSeeders } from './class-seeder';
85
89
  export { addColumnSafely, backfillInBatches, renameColumnSafely } from './safe-migrations';
86
90
  // Seeding
87
91
  export * from './seeder';
92
+ // stacksjs/stacks#1919 — public factory API. The canonical replacement
93
+ // for the legacy `useSeeder` trait + auto-walker. Class seeders call
94
+ // `factory.generate(Model, opts)` explicitly so there's one
95
+ // orchestration layer per table, no double-fire on tables that have
96
+ // both a `useSeeder` trait and a class seeder file.
97
+ export { factory, generate as factoryGenerate } from './factory';
98
+ // `buddy seed:scaffold` codemod — generates class-seeder files for
99
+ // every model with a `useSeeder` trait, easing the migration off the
100
+ // auto-walker.
101
+ export { scaffoldClassSeedersFromModels, renderSeederFile } from './seed-scaffold';
88
102
  // Driver utilities
89
103
  export * from './drivers/index';
90
104
  // Custom migrations (jobs, errors, etc.)
91
105
  export * from './custom/index';
92
106
  // Auth tables migration
93
107
  export * from './auth-tables';
108
+ // uuid column guarantee for `useUuid` models (stacksjs/status#1 Phase 9)
109
+ export * from './uuid-columns';
110
+ // Notification tables migration (stacksjs/stacks#1937)
111
+ export { migrateNotificationTables } from './notification-tables';
112
+ // RBAC tables migration (stacksjs/stacks#1941 Phase A)
113
+ export { migrateRbacTables } from './rbac-tables';
94
114
  // SQL dialect helpers & connection defaults
95
115
  export * from './sql-helpers';
96
116
  export * from './defaults';
117
+ // Foreign-key audit (stacksjs/stacks#1916) — compare declared
118
+ // `belongsTo` relationships against live FKs.
119
+ export { auditForeignKeys, findFkOrphans, getDeclaredFKs, getLiveFKs } from './fk-audit';
120
+ // Unique-index drift audit (stacksjs/stacks#1952) — compare declared
121
+ // `unique: true` attributes / indexes against live UNIQUE indexes.
122
+ export { auditUniqueIndexes, getDeclaredUniques, getLiveUniqueIndexes } from './unique-audit';
123
+ // Transaction context: AsyncLocalStorage-based scope so side-effect
124
+ // emitters (queue dispatch, mailer send) can buffer themselves
125
+ // until the surrounding `db.transaction(...)` commits
126
+ // (stacksjs/stacks#1882).
127
+ export {
128
+ __flushAfterCommitNow,
129
+ __pendingAfterCommitCount,
130
+ enqueueAfterCommit,
131
+ isInTransaction,
132
+ runInTransactionScope,
133
+ } from './transaction-context';
97
134
  // Re-export bun-query-builder functions and types
98
135
  export {
99
136
  createQueryBuilder,
100
137
  setConfig,
101
- } from 'bun-query-builder';
138
+ } from '@stacksjs/query-builder';
102
139
  // DynamoDB entity-centric API
103
140
  export {
104
141
  createDynamo,