@stacksjs/database 0.70.36 → 0.70.38
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/dist/auth-tables.d.ts +4 -0
- package/dist/class-seeder.d.ts +16 -0
- package/dist/custom/audits.d.ts +16 -0
- package/dist/custom/errors.d.ts +1 -0
- package/dist/custom/index.d.ts +3 -0
- package/dist/custom/jobs.d.ts +3 -0
- package/dist/database.d.ts +87 -0
- package/dist/defaults.d.ts +48 -0
- package/dist/driver-config.d.ts +120 -0
- package/dist/drivers/defaults/index.d.ts +2 -0
- package/dist/drivers/defaults/passwords.d.ts +4 -0
- package/dist/drivers/defaults/traits.d.ts +33 -0
- package/dist/drivers/dynamodb.d.ts +148 -0
- package/dist/drivers/helpers.d.ts +29 -0
- package/dist/drivers/index.d.ts +13 -0
- package/dist/drivers/mysql.d.ts +7 -0
- package/dist/drivers/postgres.d.ts +7 -0
- package/dist/drivers/sqlite.d.ts +23 -0
- package/dist/index.d.ts +112 -0
- package/dist/index.js +1 -1
- package/dist/migrations.d.ts +35 -0
- package/dist/query-logger.d.ts +26 -0
- package/dist/query-parser.d.ts +4 -0
- package/dist/safe-migrations.d.ts +72 -0
- package/dist/seeder.d.ts +57 -0
- package/dist/sql-helpers.d.ts +31 -0
- package/dist/types.d.ts +66 -0
- package/dist/utils.d.ts +139 -0
- package/dist/validators.d.ts +26 -0
- package/package.json +10 -10
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/*.ts`; an explicit `--class` filters to one.
|
|
2
|
+
*/
|
|
3
|
+
export declare function runClassSeeders(options?: RunOptions): Promise<{ ran: string[], skipped: string[] }>;
|
|
4
|
+
declare interface RunOptions {
|
|
5
|
+
class?: string
|
|
6
|
+
dir?: string
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Base class for class-based seeders. Subclass this and implement
|
|
10
|
+
* `async run()`. Seeders may call `this.call()` to invoke other
|
|
11
|
+
* seeders, mirroring Laravel's nested-seeder pattern.
|
|
12
|
+
*/
|
|
13
|
+
export declare abstract class Seeder {
|
|
14
|
+
abstract run(): Promise<void> | void;
|
|
15
|
+
protected call(other: new () => Seeder): Promise<void>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create the `model_audits` table used by the `useAudit` ORM trait.
|
|
3
|
+
*
|
|
4
|
+
* The trait writes one row per create/update/delete on any model that
|
|
5
|
+
* declares `traits: { useAudit: true }`. The schema is intentionally
|
|
6
|
+
* polymorphic — `auditable_type` + `auditable_id` rather than per-table
|
|
7
|
+
* audit columns — so adding the trait to a new model never requires a
|
|
8
|
+
* follow-up migration.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { createModelAuditsTable } from '@stacksjs/database'
|
|
13
|
+
* await createModelAuditsTable() // idempotent — checks before writing
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export declare function createModelAuditsTable(): Promise<void>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function createErrorsTable(): Promise<void>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { QueryBuilder, QueryBuilderConfig, SupportedDialect } from 'bun-query-builder';
|
|
2
|
+
/**
|
|
3
|
+
* Create a database connection with the given options
|
|
4
|
+
*/
|
|
5
|
+
export declare function createDatabase(options: DatabaseOptions): Database;
|
|
6
|
+
/**
|
|
7
|
+
* Create a SQLite database connection
|
|
8
|
+
*/
|
|
9
|
+
export declare function createSqliteDatabase(database: string, options?: Partial<Omit<DatabaseOptions, 'driver' | 'connection'>>): Database;
|
|
10
|
+
/**
|
|
11
|
+
* Create a PostgreSQL database connection
|
|
12
|
+
*/
|
|
13
|
+
export declare function createPostgresDatabase(connection: DatabaseConnectionConfig, options?: Partial<Omit<DatabaseOptions, 'driver' | 'connection'>>): Database;
|
|
14
|
+
/**
|
|
15
|
+
* Create a MySQL database connection
|
|
16
|
+
*/
|
|
17
|
+
export declare function createMysqlDatabase(connection: DatabaseConnectionConfig, options?: Partial<Omit<DatabaseOptions, 'driver' | 'connection'>>): Database;
|
|
18
|
+
export declare interface DatabaseConnectionConfig {
|
|
19
|
+
database: string
|
|
20
|
+
host?: string
|
|
21
|
+
port?: number
|
|
22
|
+
username?: string
|
|
23
|
+
password?: string
|
|
24
|
+
url?: string
|
|
25
|
+
}
|
|
26
|
+
export declare interface DatabaseOptions {
|
|
27
|
+
driver: SupportedDialect
|
|
28
|
+
connection: DatabaseConnectionConfig
|
|
29
|
+
verbose?: boolean
|
|
30
|
+
timestamps?: {
|
|
31
|
+
createdAt?: string
|
|
32
|
+
updatedAt?: string
|
|
33
|
+
defaultOrderColumn?: string
|
|
34
|
+
}
|
|
35
|
+
softDeletes?: {
|
|
36
|
+
enabled?: boolean
|
|
37
|
+
column?: string
|
|
38
|
+
defaultFilter?: boolean
|
|
39
|
+
}
|
|
40
|
+
hooks?: QueryBuilderConfig['hooks']
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Database class for managing database connections
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* // Initialize with SQLite
|
|
48
|
+
* const db = new Database({
|
|
49
|
+
* driver: 'sqlite',
|
|
50
|
+
* connection: { database: 'database/app.sqlite' }
|
|
51
|
+
* })
|
|
52
|
+
*
|
|
53
|
+
* // Initialize with PostgreSQL
|
|
54
|
+
* const db = new Database({
|
|
55
|
+
* driver: 'postgres',
|
|
56
|
+
* connection: {
|
|
57
|
+
* database: 'myapp',
|
|
58
|
+
* host: 'localhost',
|
|
59
|
+
* port: 5432,
|
|
60
|
+
* username: 'postgres',
|
|
61
|
+
* password: 'secret'
|
|
62
|
+
* }
|
|
63
|
+
* })
|
|
64
|
+
*
|
|
65
|
+
* // Use the query builder
|
|
66
|
+
* const users = await db.query.selectFrom('users').where('active', '=', true).get()
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export declare class Database {
|
|
70
|
+
constructor(options: DatabaseOptions);
|
|
71
|
+
get driver(): SupportedDialect;
|
|
72
|
+
get connection(): DatabaseConnectionConfig;
|
|
73
|
+
get isInitialized(): boolean;
|
|
74
|
+
get query(): QueryBuilder<any>;
|
|
75
|
+
initialize(): void;
|
|
76
|
+
switchDriver(driver: SupportedDialect, connection: DatabaseConnectionConfig): void;
|
|
77
|
+
close(): void;
|
|
78
|
+
static fromConfig(config: {
|
|
79
|
+
default: SupportedDialect
|
|
80
|
+
connections: {
|
|
81
|
+
sqlite?: { database: string }
|
|
82
|
+
mysql?: { name: string, host?: string, port?: number, username?: string, password?: string }
|
|
83
|
+
postgres?: { name: string, host?: string, port?: number, username?: string, password?: string }
|
|
84
|
+
}
|
|
85
|
+
}, env?: string): Database;
|
|
86
|
+
static fromEnv(): Database;
|
|
87
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get default connection config for a given database driver.
|
|
3
|
+
* Reads from the typed env proxy so values are auto-coerced.
|
|
4
|
+
*/
|
|
5
|
+
export declare function getConnectionDefaults(driver: string, envProxy?: Record<string, any>): ConnectionDefaults;
|
|
6
|
+
/**
|
|
7
|
+
* Database Defaults & Constants
|
|
8
|
+
*
|
|
9
|
+
* Centralizes all hardcoded default values for database connections
|
|
10
|
+
* so they are defined once and easily discoverable.
|
|
11
|
+
*/
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// HOST / PORT DEFAULTS
|
|
14
|
+
// ============================================================================
|
|
15
|
+
export declare const DB_HOST_DEFAULT: '127.0.0.1';
|
|
16
|
+
export declare const DB_PORTS: {
|
|
17
|
+
mysql: 3306;
|
|
18
|
+
postgres: 5432;
|
|
19
|
+
sqlite: 0
|
|
20
|
+
};
|
|
21
|
+
export declare const DB_NAMES: {
|
|
22
|
+
default: 'stacks';
|
|
23
|
+
sqlitePath: 'database/stacks.sqlite';
|
|
24
|
+
sqliteTestingPath: 'database/stacks_testing.sqlite'
|
|
25
|
+
};
|
|
26
|
+
export declare const DB_USERS: {
|
|
27
|
+
mysql: 'root';
|
|
28
|
+
postgres: 'postgres';
|
|
29
|
+
sqlite: ''
|
|
30
|
+
};
|
|
31
|
+
export declare const REDIS_DEFAULTS: {
|
|
32
|
+
host: 'localhost';
|
|
33
|
+
port: 6379
|
|
34
|
+
};
|
|
35
|
+
export declare const AWS_DEFAULTS: {
|
|
36
|
+
region: 'us-east-1'
|
|
37
|
+
};
|
|
38
|
+
// ============================================================================
|
|
39
|
+
// CONNECTION CONFIG BUILDERS
|
|
40
|
+
// ============================================================================
|
|
41
|
+
export declare interface ConnectionDefaults {
|
|
42
|
+
database: string
|
|
43
|
+
host?: string
|
|
44
|
+
port?: number
|
|
45
|
+
username?: string
|
|
46
|
+
password?: string
|
|
47
|
+
prefix?: string
|
|
48
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { SupportedDialect } from 'bun-query-builder';
|
|
2
|
+
/**
|
|
3
|
+
* Get the connection string for a given driver and configuration
|
|
4
|
+
*/
|
|
5
|
+
export declare function getConnectionString(driver: SupportedDialect, config: DatabaseConnections[keyof DatabaseConnections]): string;
|
|
6
|
+
/**
|
|
7
|
+
* Validate driver configuration
|
|
8
|
+
*/
|
|
9
|
+
export declare function validateDriverConfig(driver: SupportedDialect, config: DatabaseConnections[keyof DatabaseConnections]): { valid: boolean, errors: string[] };
|
|
10
|
+
/**
|
|
11
|
+
* Merge user configuration with defaults
|
|
12
|
+
*/
|
|
13
|
+
export declare function mergeWithDefaults<T extends keyof DatabaseConnections>(driver: T, config: Partial<DatabaseConnections[T]>): DatabaseConnections[T];
|
|
14
|
+
/**
|
|
15
|
+
* Get the appropriate configuration for a driver from environment variables
|
|
16
|
+
*/
|
|
17
|
+
export declare function getConfigFromEnv(driver: SupportedDialect): DatabaseConnections[keyof DatabaseConnections];
|
|
18
|
+
/**
|
|
19
|
+
* Detect the best available driver based on environment
|
|
20
|
+
*/
|
|
21
|
+
export declare function detectDriver(): SupportedDialect;
|
|
22
|
+
/**
|
|
23
|
+
* Default configuration values for each driver
|
|
24
|
+
*/
|
|
25
|
+
export declare const driverDefaults: {
|
|
26
|
+
sqlite: {
|
|
27
|
+
database: 'database/stacks.sqlite';
|
|
28
|
+
prefix: ''
|
|
29
|
+
};
|
|
30
|
+
mysql: {
|
|
31
|
+
name: 'stacks';
|
|
32
|
+
host: '127.0.0.1';
|
|
33
|
+
port: 3306;
|
|
34
|
+
username: 'root';
|
|
35
|
+
password: '';
|
|
36
|
+
prefix: '';
|
|
37
|
+
charset: 'utf8mb4';
|
|
38
|
+
collation: 'utf8mb4_unicode_ci'
|
|
39
|
+
};
|
|
40
|
+
postgres: {
|
|
41
|
+
name: 'stacks';
|
|
42
|
+
host: '127.0.0.1';
|
|
43
|
+
port: 5432;
|
|
44
|
+
username: 'postgres';
|
|
45
|
+
password: '';
|
|
46
|
+
prefix: '';
|
|
47
|
+
schema: 'public'
|
|
48
|
+
};
|
|
49
|
+
browser: {}
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* SQLite specific configuration
|
|
53
|
+
*/
|
|
54
|
+
export declare interface SqliteConfig {
|
|
55
|
+
database: string
|
|
56
|
+
prefix?: string
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* MySQL specific configuration
|
|
60
|
+
*/
|
|
61
|
+
export declare interface MysqlConfig {
|
|
62
|
+
name: string
|
|
63
|
+
host?: string
|
|
64
|
+
port?: number
|
|
65
|
+
username?: string
|
|
66
|
+
password?: string
|
|
67
|
+
prefix?: string
|
|
68
|
+
charset?: string
|
|
69
|
+
collation?: string
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* PostgreSQL specific configuration
|
|
73
|
+
*/
|
|
74
|
+
export declare interface PostgresConfig {
|
|
75
|
+
name: string
|
|
76
|
+
host?: string
|
|
77
|
+
port?: number
|
|
78
|
+
username?: string
|
|
79
|
+
password?: string
|
|
80
|
+
prefix?: string
|
|
81
|
+
schema?: string
|
|
82
|
+
sslMode?: 'disable' | 'require' | 'verify-ca' | 'verify-full'
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* DynamoDB specific configuration
|
|
86
|
+
*/
|
|
87
|
+
export declare interface DynamoDbConfig {
|
|
88
|
+
key: string
|
|
89
|
+
secret: string
|
|
90
|
+
region?: string
|
|
91
|
+
prefix?: string
|
|
92
|
+
endpoint?: string
|
|
93
|
+
tableName?: string
|
|
94
|
+
singleTable?: {
|
|
95
|
+
enabled?: boolean
|
|
96
|
+
pkAttribute?: string
|
|
97
|
+
skAttribute?: string
|
|
98
|
+
entityTypeAttribute?: string
|
|
99
|
+
keyDelimiter?: string
|
|
100
|
+
gsiCount?: number
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* All database connections configuration
|
|
105
|
+
*/
|
|
106
|
+
export declare interface DatabaseConnections {
|
|
107
|
+
sqlite?: SqliteConfig
|
|
108
|
+
mysql?: MysqlConfig
|
|
109
|
+
postgres?: PostgresConfig
|
|
110
|
+
dynamodb?: DynamoDbConfig
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Full database configuration
|
|
114
|
+
*/
|
|
115
|
+
export declare interface FullDatabaseConfig {
|
|
116
|
+
default: SupportedDialect
|
|
117
|
+
connections: DatabaseConnections
|
|
118
|
+
migrations?: string
|
|
119
|
+
migrationLocks?: string
|
|
120
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// bun-query-builder utilities are used via db.unsafe() for raw SQL
|
|
2
|
+
export declare function getTraitTables(): string[];
|
|
3
|
+
// SQLite/MySQL version
|
|
4
|
+
export declare function createPasskeyMigration(): Promise<void>;
|
|
5
|
+
// PostgreSQL version
|
|
6
|
+
export declare function createPostgresPasskeyMigration(): Promise<void>;
|
|
7
|
+
export declare function createTaggableTable(): Promise<void>;
|
|
8
|
+
export declare function createPostgresTagsTable(): Promise<void>;
|
|
9
|
+
// SQLite/MySQL version
|
|
10
|
+
export declare function createCategorizableTable(): Promise<void>;
|
|
11
|
+
// PostgreSQL version
|
|
12
|
+
export declare function createPostgresCategorizableTable(): Promise<void>;
|
|
13
|
+
// SQLite/MySQL version
|
|
14
|
+
export declare function createCommentablesTable(options?: {
|
|
15
|
+
requiresApproval?: boolean
|
|
16
|
+
reportable?: boolean
|
|
17
|
+
votable?: boolean
|
|
18
|
+
requiresAuth?: boolean
|
|
19
|
+
}): Promise<void>;
|
|
20
|
+
// PostgreSQL version
|
|
21
|
+
export declare function createPostgresCommentsTable(): Promise<void>;
|
|
22
|
+
export declare function dropCommonTables(): Promise<void>;
|
|
23
|
+
export declare function truncateMigrationTables(): Promise<void>;
|
|
24
|
+
export declare function dropMigrationTables(): Promise<void>;
|
|
25
|
+
export declare function createCommentUpvoteMigration(): Promise<void>;
|
|
26
|
+
export declare function createPostgresCommentUpvoteMigration(): Promise<void>;
|
|
27
|
+
export declare function createCommentablesPivotTable(): Promise<void>;
|
|
28
|
+
export declare function createPostgresCommentablesPivotTable(): Promise<void>;
|
|
29
|
+
export declare function createTaggablesTable(): Promise<void>;
|
|
30
|
+
export declare function createPostgresTaggablesTable(): Promise<void>;
|
|
31
|
+
export declare function createQueryLogsTable(): Promise<void>;
|
|
32
|
+
// PostgreSQL version
|
|
33
|
+
export declare function createPostgresQueryLogsTable(): Promise<void>;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type { Model } from '@stacksjs/types';
|
|
2
|
+
/**
|
|
3
|
+
* Marshall a JS object to DynamoDB format
|
|
4
|
+
*/
|
|
5
|
+
declare function marshall(obj: Record<string, any>): Record<string, any>;
|
|
6
|
+
/**
|
|
7
|
+
* Unmarshall a DynamoDB object to JS format
|
|
8
|
+
*/
|
|
9
|
+
declare function unmarshall(obj: Record<string, any>): Record<string, any>;
|
|
10
|
+
/**
|
|
11
|
+
* Generate key pattern for an entity
|
|
12
|
+
*/
|
|
13
|
+
export declare function generateKeyPattern(entityName: string, idField?: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Parse a key pattern and extract values
|
|
16
|
+
*/
|
|
17
|
+
export declare function parseKeyPattern(pattern: string, key: string): Record<string, string>;
|
|
18
|
+
/**
|
|
19
|
+
* Build a key from a pattern and values
|
|
20
|
+
*/
|
|
21
|
+
export declare function buildKey(pattern: string, values: Record<string, string>): string;
|
|
22
|
+
/**
|
|
23
|
+
* Create a new DynamoDB client instance
|
|
24
|
+
*/
|
|
25
|
+
export declare function createDynamo(): DynamoClient;
|
|
26
|
+
/**
|
|
27
|
+
* DynamoDB client singleton
|
|
28
|
+
*/
|
|
29
|
+
export declare const dynamo: DynamoClient;
|
|
30
|
+
/**
|
|
31
|
+
* DynamoDB connection configuration
|
|
32
|
+
*/
|
|
33
|
+
export declare interface DynamoConnectionConfig {
|
|
34
|
+
region: string
|
|
35
|
+
table: string
|
|
36
|
+
endpoint?: string
|
|
37
|
+
credentials?: {
|
|
38
|
+
accessKeyId: string
|
|
39
|
+
secretAccessKey: string
|
|
40
|
+
sessionToken?: string
|
|
41
|
+
}
|
|
42
|
+
pkAttribute?: string
|
|
43
|
+
skAttribute?: string
|
|
44
|
+
entityTypeAttribute?: string
|
|
45
|
+
keyDelimiter?: string
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Entity mapping for single-table design
|
|
49
|
+
*/
|
|
50
|
+
export declare interface SingleTableEntityMapping {
|
|
51
|
+
entityType: string
|
|
52
|
+
pkPattern: string
|
|
53
|
+
skPattern?: string
|
|
54
|
+
gsi1pk?: string
|
|
55
|
+
gsi1sk?: string
|
|
56
|
+
gsi2pk?: string
|
|
57
|
+
gsi2sk?: string
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Sort key builder for fluent API
|
|
61
|
+
*/
|
|
62
|
+
export declare interface SortKeyBuilder {
|
|
63
|
+
equals(value: string): EntityQueryBuilder
|
|
64
|
+
beginsWith(prefix: string): EntityQueryBuilder
|
|
65
|
+
between(start: string, end: string): EntityQueryBuilder
|
|
66
|
+
lt(value: string): EntityQueryBuilder
|
|
67
|
+
lte(value: string): EntityQueryBuilder
|
|
68
|
+
gt(value: string): EntityQueryBuilder
|
|
69
|
+
gte(value: string): EntityQueryBuilder
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Batch write operation
|
|
73
|
+
*/
|
|
74
|
+
export declare interface BatchWriteOperation {
|
|
75
|
+
put?: { entity: string, item: Record<string, any> }
|
|
76
|
+
delete?: { entity: string, pk: string, sk: string }
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Transact write operation
|
|
80
|
+
*/
|
|
81
|
+
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[] }
|
|
84
|
+
delete?: { entity: string, pk: string, sk: string, condition?: string }
|
|
85
|
+
conditionCheck?: { entity: string, pk: string, sk: string, condition: string }
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Query result
|
|
89
|
+
*/
|
|
90
|
+
export declare interface QueryResult<T = any> {
|
|
91
|
+
items: T[]
|
|
92
|
+
count: number
|
|
93
|
+
scannedCount?: number
|
|
94
|
+
lastKey?: Record<string, any>
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Entity-centric query builder for DynamoDB
|
|
98
|
+
*/
|
|
99
|
+
export declare class EntityQueryBuilder<T = any> {
|
|
100
|
+
constructor(client: any, tableName: string, config: { pkAttribute: string, skAttribute: string, entityTypeAttribute: string, keyDelimiter: string });
|
|
101
|
+
entity(entityType: string): this;
|
|
102
|
+
pk(value: string): this;
|
|
103
|
+
get sk(): SortKeyBuilder;
|
|
104
|
+
index(indexName: string): this;
|
|
105
|
+
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;
|
|
109
|
+
limit(count: number): this;
|
|
110
|
+
asc(): this;
|
|
111
|
+
desc(): this;
|
|
112
|
+
consistent(): this;
|
|
113
|
+
startFrom(key: Record<string, any>): this;
|
|
114
|
+
toRequest(): Record<string, any>;
|
|
115
|
+
get(): Promise<T[]>;
|
|
116
|
+
first(): Promise<T | undefined>;
|
|
117
|
+
getAll(): Promise<T[]>;
|
|
118
|
+
count(): Promise<number>;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* DynamoDB client with entity-centric API
|
|
122
|
+
*/
|
|
123
|
+
declare class DynamoClient {
|
|
124
|
+
connection(config: DynamoConnectionConfig): this;
|
|
125
|
+
isConfigured(): boolean;
|
|
126
|
+
setClient(client: any): this;
|
|
127
|
+
getClient(): any;
|
|
128
|
+
registerEntity(mapping: SingleTableEntityMapping): this;
|
|
129
|
+
registerModel(model: Model): this;
|
|
130
|
+
getEntityMapping(entityType: string): SingleTableEntityMapping | undefined;
|
|
131
|
+
entity<T = any>(entityType: string): EntityQueryBuilder<T>;
|
|
132
|
+
batchWrite(operations: BatchWriteOperation[]): Promise<void>;
|
|
133
|
+
transactWrite(operations: TransactWriteOperation[]): Promise<void>;
|
|
134
|
+
put(entity: string, item: Record<string, any>): Promise<void>;
|
|
135
|
+
get<T = any>(pk: string, sk?: string): Promise<T | undefined>;
|
|
136
|
+
delete(pk: string, sk?: string): Promise<void>;
|
|
137
|
+
update(pk: string, sk: string | undefined, updates: Record<string, any>): Promise<void>;
|
|
138
|
+
getTableName(): string;
|
|
139
|
+
getConfig(): {
|
|
140
|
+
tableName: string
|
|
141
|
+
pkAttribute: string
|
|
142
|
+
skAttribute: string
|
|
143
|
+
entityTypeAttribute: string
|
|
144
|
+
keyDelimiter: string
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
// Export marshall/unmarshall utilities
|
|
148
|
+
export { marshall, unmarshall };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Attribute, AttributesElements, Model } from '@stacksjs/types';
|
|
2
|
+
import type { DateValidatorType, EnumValidatorType, NumberValidatorType, StringValidatorType, ValidationType } from '@stacksjs/ts-validation';
|
|
3
|
+
export declare function deleteMigrationFiles(): Promise<void>;
|
|
4
|
+
export declare function deleteFrameworkModels(): Promise<void>;
|
|
5
|
+
export declare function getLastMigrationFields(modelName: string): Promise<AttributesElements>;
|
|
6
|
+
export declare function modelTableName(model: Model | string): Promise<string>;
|
|
7
|
+
export declare function hasTableBeenMigrated(tableName: string): Promise<boolean>;
|
|
8
|
+
export declare function hasMigrationBeenCreated(tableName: string): Promise<boolean>;
|
|
9
|
+
export declare function getExecutedMigrations(): Promise<{ name: string }[]>;
|
|
10
|
+
export declare function prepareTextColumnType(validator: StringValidatorType, driver?: string): string;
|
|
11
|
+
// Add new function for date/time column types
|
|
12
|
+
export declare function prepareDateTimeColumnType(validator: DateValidatorType, driver?: string): string;
|
|
13
|
+
export declare function compareRanges(range1: Range, range2: Range): boolean;
|
|
14
|
+
export declare function checkPivotMigration(dynamicPart: string): Promise<boolean>;
|
|
15
|
+
export declare function pluckChanges(array1: string[], array2: string[]): { added: string[], removed: string[] } | null;
|
|
16
|
+
export declare function arrangeColumns(attributes: AttributesElements | undefined): Array<[string, Attribute]>;
|
|
17
|
+
export declare function isArrayEqual(arr1: (number | undefined)[], arr2: (number | undefined)[]): boolean;
|
|
18
|
+
export declare function findDifferingKeys(obj1: any, obj2: any): { key: string, max: number, min: number }[];
|
|
19
|
+
export declare function fetchTables(): Promise<string[]>;
|
|
20
|
+
export declare function getUpvoteTableName(model: Model, tableName: string): string | undefined;
|
|
21
|
+
export declare function prepareNumberColumnType(validator: NumberValidatorType, driver?: string): string;
|
|
22
|
+
// Add new function for enum column types
|
|
23
|
+
export declare function prepareEnumColumnType(validator: EnumValidatorType, driver?: string): string;
|
|
24
|
+
export declare function mapFieldTypeToColumnType(validator: ValidationType, driver?: string): string;
|
|
25
|
+
export declare function checkIsRequired(rule: string): boolean;
|
|
26
|
+
declare interface Range {
|
|
27
|
+
min: number
|
|
28
|
+
max: number
|
|
29
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drivers — barrel export.
|
|
3
|
+
*
|
|
4
|
+
* Helpers shared across driver implementations live in `./helpers.ts` (NOT
|
|
5
|
+
* inline here) so driver modules can import them without re-importing this
|
|
6
|
+
* barrel and triggering a self-cycle. See `./helpers.ts` for the rationale.
|
|
7
|
+
*/
|
|
8
|
+
export * from './helpers';
|
|
9
|
+
export * from './mysql';
|
|
10
|
+
export * from './postgres';
|
|
11
|
+
export * from './sqlite';
|
|
12
|
+
export * from './defaults/index';
|
|
13
|
+
export * from './dynamodb';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Ok } from '@stacksjs/error-handling';
|
|
2
|
+
export declare function resetMysqlDatabase(): Promise<Ok<string, never>>;
|
|
3
|
+
export declare function dropMysqlTables(): Promise<void>;
|
|
4
|
+
export declare function generateMysqlMigration(modelPath: string): Promise<void>;
|
|
5
|
+
export declare function generateMysqlTraitMigrations(): Promise<void>;
|
|
6
|
+
export declare function createMysqlForeignKeyMigrations(modelPath: string): Promise<void>;
|
|
7
|
+
export declare function createAlterTableMigration(modelPath: string): Promise<void>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Ok } from '@stacksjs/error-handling';
|
|
2
|
+
export declare function dropPostgresTables(): Promise<void>;
|
|
3
|
+
export declare function generatePostgresTraitMigrations(): Promise<void>;
|
|
4
|
+
export declare function resetPostgresDatabase(): Promise<Ok<string, never>>;
|
|
5
|
+
export declare function generatePostgresMigration(modelPath: string): Promise<void>;
|
|
6
|
+
export declare function createPostgresForeignKeyMigrations(modelPath: string): Promise<void>;
|
|
7
|
+
export declare function fetchPostgresTables(): Promise<string[]>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Ok } from '@stacksjs/error-handling';
|
|
2
|
+
export declare function resetSqliteDatabase(): Promise<Ok<string, never>>;
|
|
3
|
+
/**
|
|
4
|
+
* Configure SQLite for the Stacks workload. Idempotent — the pragmas are
|
|
5
|
+
* cheap to re-apply, but each one is a no-op once set so repeated calls
|
|
6
|
+
* during dev hot-reload don't accumulate state.
|
|
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.
|
|
16
|
+
*/
|
|
17
|
+
export declare function configureSqlitePragmas(): Promise<void>;
|
|
18
|
+
export declare function dropSqliteTables(): Promise<void>;
|
|
19
|
+
export declare function fetchSqliteFile(): string;
|
|
20
|
+
export declare function fetchTestSqliteFile(): string;
|
|
21
|
+
export declare function generateSqliteMigration(modelPath: string): Promise<void>;
|
|
22
|
+
export declare function createSqliteForeignKeyMigrations(_modelPath: string): Promise<void>;
|
|
23
|
+
export declare function copyModelFiles(modelPath: string): Promise<void>;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
DatabaseConnectionConfig,
|
|
3
|
+
DatabaseOptions,
|
|
4
|
+
} from './database';
|
|
5
|
+
export type {
|
|
6
|
+
DatabaseConnections,
|
|
7
|
+
DynamoDbConfig,
|
|
8
|
+
FullDatabaseConfig,
|
|
9
|
+
MysqlConfig,
|
|
10
|
+
PostgresConfig,
|
|
11
|
+
SqliteConfig,
|
|
12
|
+
} from './driver-config';
|
|
13
|
+
export type {
|
|
14
|
+
QueryBuilder,
|
|
15
|
+
QueryBuilderConfig,
|
|
16
|
+
Seeder as QueryBuilderSeeder,
|
|
17
|
+
SupportedDialect,
|
|
18
|
+
} from 'bun-query-builder';
|
|
19
|
+
export type {
|
|
20
|
+
DynamoConnectionConfig,
|
|
21
|
+
SingleTableEntityMapping,
|
|
22
|
+
SortKeyBuilder,
|
|
23
|
+
BatchWriteOperation,
|
|
24
|
+
TransactWriteOperation,
|
|
25
|
+
QueryResult,
|
|
26
|
+
} from './drivers/dynamodb';
|
|
27
|
+
/**
|
|
28
|
+
* @stacksjs/database
|
|
29
|
+
*
|
|
30
|
+
* Database module powered by bun-query-builder.
|
|
31
|
+
* Provides database initialization, driver configuration, migrations,
|
|
32
|
+
* seeding, and a fluent query builder interface.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* import { Database, db, createSqliteDatabase } from '@stacksjs/database'
|
|
37
|
+
*
|
|
38
|
+
* // Use the default db instance (configured from environment)
|
|
39
|
+
* const users = await db.selectFrom('users').where('active', '=', true).get()
|
|
40
|
+
*
|
|
41
|
+
* // Or create a custom database instance
|
|
42
|
+
* const customDb = new Database({
|
|
43
|
+
* driver: 'postgres',
|
|
44
|
+
* connection: {
|
|
45
|
+
* database: 'myapp',
|
|
46
|
+
* host: 'localhost',
|
|
47
|
+
* port: 5432,
|
|
48
|
+
* username: 'postgres',
|
|
49
|
+
* password: 'secret'
|
|
50
|
+
* }
|
|
51
|
+
* })
|
|
52
|
+
*
|
|
53
|
+
* // Helper functions for quick setup
|
|
54
|
+
* const sqliteDb = createSqliteDatabase('database/app.sqlite')
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
// Database initialization and management
|
|
58
|
+
export {
|
|
59
|
+
Database,
|
|
60
|
+
createDatabase,
|
|
61
|
+
createMysqlDatabase,
|
|
62
|
+
createPostgresDatabase,
|
|
63
|
+
createSqliteDatabase,
|
|
64
|
+
} from './database';
|
|
65
|
+
// Driver configuration
|
|
66
|
+
export {
|
|
67
|
+
detectDriver,
|
|
68
|
+
driverDefaults,
|
|
69
|
+
getConfigFromEnv,
|
|
70
|
+
getConnectionString,
|
|
71
|
+
mergeWithDefaults,
|
|
72
|
+
validateDriverConfig,
|
|
73
|
+
} from './driver-config';
|
|
74
|
+
// Core database utilities and default instance
|
|
75
|
+
export * from './utils';
|
|
76
|
+
// Types (compatibility layer for Kysely types)
|
|
77
|
+
export * from './types';
|
|
78
|
+
// Migrations
|
|
79
|
+
export * from './migrations';
|
|
80
|
+
// Query logger DI hook (router calls setQueryTracker on init)
|
|
81
|
+
export { setQueryTracker, logQuery } from './query-logger';
|
|
82
|
+
// Class-based seeders (supplements the model-attribute auto-seeder)
|
|
83
|
+
export { Seeder, runClassSeeders } from './class-seeder';
|
|
84
|
+
// Zero-downtime migration helpers
|
|
85
|
+
export { addColumnSafely, backfillInBatches, renameColumnSafely } from './safe-migrations';
|
|
86
|
+
// Seeding
|
|
87
|
+
export * from './seeder';
|
|
88
|
+
// Driver utilities
|
|
89
|
+
export * from './drivers/index';
|
|
90
|
+
// Custom migrations (jobs, errors, etc.)
|
|
91
|
+
export * from './custom/index';
|
|
92
|
+
// Auth tables migration
|
|
93
|
+
export * from './auth-tables';
|
|
94
|
+
// SQL dialect helpers & connection defaults
|
|
95
|
+
export * from './sql-helpers';
|
|
96
|
+
export * from './defaults';
|
|
97
|
+
// Re-export bun-query-builder functions and types
|
|
98
|
+
export {
|
|
99
|
+
createQueryBuilder,
|
|
100
|
+
setConfig,
|
|
101
|
+
} from 'bun-query-builder';
|
|
102
|
+
// DynamoDB entity-centric API
|
|
103
|
+
export {
|
|
104
|
+
createDynamo,
|
|
105
|
+
dynamo,
|
|
106
|
+
EntityQueryBuilder,
|
|
107
|
+
generateKeyPattern,
|
|
108
|
+
parseKeyPattern,
|
|
109
|
+
buildKey,
|
|
110
|
+
marshall,
|
|
111
|
+
unmarshall,
|
|
112
|
+
} from './drivers/dynamodb';
|
package/dist/index.js
CHANGED
|
@@ -133,7 +133,7 @@ Or enable auto-install in your mock.config.ts: { autoInstallLocales: true }`)}th
|
|
|
133
133
|
)`);let K=O.prepare("INSERT OR IGNORE INTO migrations (migration) VALUES (?)");for(let W of X)K.run(W)}finally{O.close()}}}catch(G){$0.debug(`[migration] Could not record dropped migrations as executed: ${G}`)}}async function uE(){let $=d8();if($==="sqlite")return;let Z=l1.connections[$],X=(Z?.name||"stacks").replace(/['"]/g,""),_=Z?.host||"localhost",Y=Z?.port||($==="postgres"?5432:3306),L=Z?.username||($==="postgres"?process.env.USER||"postgres":"root"),z=Z?.password||"",j=$==="postgres"?"postgres":"mysql";try{d9({dialect:$,database:{database:j,host:_,port:Y,username:L,password:z}}),b1();let U=F9();if($==="postgres")try{await U.unsafe(`CREATE DATABASE "${X}"`),$0.info(`Created database "${X}"`)}catch(Q){if(Q?.message?.includes("already exists")||Q?.errno==="42P04")$0.info(`Database "${X}" already exists`);else throw Q}else if($==="mysql")try{await U.unsafe(`CREATE DATABASE IF NOT EXISTS \`${X}\``),$0.info(`Ensured database "${X}" exists`)}catch(Q){if(Q?.message?.includes("database exists"))$0.info(`Database "${X}" already exists`);else throw Q}b1()}catch(U){$0.warn(`Could not auto-create database "${X}": ${U?.message||U}`),$0.info("If the database already exists, this warning can be ignored."),b1()}}async function oV(){let $=Date.now();try{if($0.info("Migrating database..."),await uE(),c5(),d8()==="sqlite")dE();let Z=v6.userModelsPath();return $0.debug(`[migration] Running migrations from: ${Z}`),await S6(Z),$0.success(`Database migration completed in ${Date.now()-$}ms.`),c8("Database migration completed.")}catch(Z){let X=Z instanceof Error?Z.message:String(Z);return $0.error(`[migration] Failed after ${Date.now()-$}ms: ${X}`),$0.info("[migration] Run `./buddy migrate:fresh` to drop and recreate the schema if state is partial."),f5(g5("Migration failed",Z))}}var mE=["oauth_refresh_tokens","oauth_access_tokens","oauth_clients","passkeys","failed_jobs","jobs","notifications","password_reset_tokens"];async function aV(){try{c5();let $=v6.userModelsPath(),Z=d8();return await sE(Z),await M6($,{dialect:Z}),c8("All tables dropped successfully!")}catch($){return f5(g5("Database reset failed",$))}}async function sE($){if($==="mysql")try{await v.unsafe("SET FOREIGN_KEY_CHECKS = 0").execute()}catch(Z){$0.warn(`Could not disable foreign key checks: ${Z instanceof Error?Z.message:String(Z)}`)}if($==="sqlite")try{await v.unsafe("PRAGMA foreign_keys = OFF").execute()}catch(Z){$0.warn(`Could not disable foreign key checks: ${Z instanceof Error?Z.message:String(Z)}`)}for(let Z of mE)try{let X;if($==="postgres")X=`DROP TABLE IF EXISTS "${Z}" CASCADE`;else if($==="mysql")X=`DROP TABLE IF EXISTS \`${Z}\``;else X=`DROP TABLE IF EXISTS "${Z}"`;$0.info(`Dropping framework table: ${Z}`),await v.unsafe(X).execute(),$0.info(`Dropped framework table: ${Z}`)}catch(X){$0.warn(`Could not drop table ${Z}: ${X instanceof Error?X.message:String(X)}`)}if($==="mysql")try{await v.unsafe("SET FOREIGN_KEY_CHECKS = 1").execute()}catch(Z){$0.warn(`Could not re-enable foreign key checks: ${Z instanceof Error?Z.message:String(Z)}`)}if($==="sqlite")try{await v.unsafe("PRAGMA foreign_keys = ON").execute()}catch(Z){$0.warn(`Could not re-enable foreign key checks: ${Z instanceof Error?Z.message:String(Z)}`)}}async function tV(){try{$0.info("Generating migrations..."),c5();let $=d8(),{modelsDir:Z,skip:X}=p4();if(X)return $0.info("No app/Models directory found; using committed framework migrations"),c8("Migrations generated");$0.debug(`[migration] Generating migrations for dialect: ${$}, models: ${Z}`);let _=await A5(Z,{dialect:$});if(_.hasChanges){let Y=lE(_.sqlStatements??[]);if(Y>0)$0.success(`Migrations generated (${Y} file${Y===1?"":"s"})`);else $0.success("Migrations generated")}else $0.info("No changes detected");return c8("Migrations generated")}catch($){return f5(g5("Migration generation failed",$))}}function lE($){if(!$?.length)return 0;let Z=Q8(process.cwd(),"database","migrations");try{H0("fs").mkdirSync(Z,{recursive:!0})}catch{}let X="";try{for(let U of h6(Z).filter((Q)=>Q.endsWith(".sql")))X+=`
|
|
134
134
|
${f4(Q8(Z,U),"utf8")}`}catch{}let _=(U)=>U.replace(/\s+/g," ").trim(),Y=_(X),L=nE($),z=0,j=iE(Z);for(let U of L){let Q=U.statements.filter((O)=>!Y.includes(_(O)));if(Q.length===0)continue;let E=`${String(j).padStart(10,"0")}-${U.label}.sql`,G=Q8(Z,E),F=`${Q.map((O)=>O.trim().replace(/;\s*$/,"")).join(`;
|
|
135
135
|
`)};
|
|
136
|
-
`;g4(G,F),$0.debug(`[migration] Wrote ${E} (${Q.length} stmt${Q.length===1?"":"s"})`),z+=1,j+=1}return z}function nE($){let Z=new Map,X=(_,Y)=>{let L=Z.get(_)??[];L.push(Y),Z.set(_,L)};for(let _ of $){let Y=_.trim();if(!Y)continue;let L=Y.match(/^\s*CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?["`]?(\w+)["`]?/i);if(L){X(`create-${L[1]}-table`,Y);continue}let z=Y.match(/^\s*ALTER\s+TABLE\s+["`]?(\w+)["`]?\s+(?:ADD\s+COLUMN\s+["`]?(\w+)["`]?|DROP\s+COLUMN\s+["`]?(\w+)["`]?|ADD\s+CONSTRAINT)/i);if(z){X(`alter-${z[1]}-${z[2]||z[3]||"constraint"}`,Y);continue}let j=Y.match(/^\s*CREATE\s+(?:UNIQUE\s+)?INDEX\s+(?:IF\s+NOT\s+EXISTS\s+)?["`]?(\w+)["`]?\s+ON\s+["`]?(\w+)["`]?/i);if(j){X(`create-${j[1]}-index-in-${j[2]}`,Y);continue}let U=Y.match(/^\s*DROP\s+TABLE\s+(?:IF\s+EXISTS\s+)?["`]?(\w+)["`]?/i);if(U){X(`drop-${U[1]}-table`,Y);continue}X("auto-misc",Y)}return[...Z.entries()].map(([_,Y])=>({label:_,statements:Y}))}function iE($){let Z=0;try{for(let X of h6($)){let _=X.match(/^(\d+)-/);if(_)Z=Math.max(Z,Number.parseInt(_[1],10))}}catch{}return Z+1}async function eV(){try{$0.info("Generating fresh migrations..."),c5();let $=d8(),{modelsDir:Z,skip:X}=p4();if(X)return $0.info("No app/Models directory found; using committed framework migrations"),c8("Migrations generated");return await A5(Z,{dialect:$,full:!0}),$0.success("Migrations generated"),c8("Migrations generated")}catch($){return f5(g5("Fresh migration generation failed",$))}}import{memoryUsage as aE}from"process";import{config as Z8}from"@stacksjs/config";import{log as n1}from"@stacksjs/logging";function b6($){let Z={normalized:"",type:"OTHER",tables:[]};if(!$)return Z;try{let X=$.trim().toUpperCase();if(X.startsWith("SELECT"))Z.type="SELECT";else if(X.startsWith("INSERT"))Z.type="INSERT";else if(X.startsWith("UPDATE"))Z.type="UPDATE";else if(X.startsWith("DELETE"))Z.type="DELETE";return Z.tables=rE($,Z.type),Z.normalized=oE($),Z}catch{return{normalized:$,type:"OTHER",tables:[]}}}function rE($,Z){let X=[],_=$.toLowerCase();try{let Y=null,L=null,z=null,j=null,U=null,Q=/join\s+([\w.]+)/gi;switch(Z){case"SELECT":if(Y=_.match(/from\s+([\w.]+)/i),Y&&Y[1])X.push(Y[1].replace(/`/g,"").split(".").pop());L=Q.exec(_);while(L!==null){if(L[1])X.push(L[1].replace(/`/g,"").split(".").pop());L=Q.exec(_)}break;case"INSERT":if(z=_.match(/into\s+([\w.]+)/i),z&&z[1])X.push(z[1].replace(/`/g,"").split(".").pop());break;case"UPDATE":if(j=_.match(/update\s+([\w.]+)/i),j&&j[1])X.push(j[1].replace(/`/g,"").split(".").pop());break;case"DELETE":if(U=_.match(/from\s+([\w.]+)/i),U&&U[1])X.push(U[1].replace(/`/g,"").split(".").pop());break}}catch{}return[...new Set(X)].filter(Boolean)}function oE($){try{let Z=$;return Z=Z.replace(/(?<![a-zA-Z_])\b\d+\b(?![a-zA-Z_])/g,"?"),Z=Z.replace(/'([^']|'')*'/g,"?"),Z=Z.replace(/"([^"]|"")*"/g,"?"),Z=Z.replace(/\btrue\b/gi,"?"),Z=Z.replace(/\bfalse\b/gi,"?"),Z=Z.replace(/\bnull\b/gi,"?"),Z=Z.replace(/\s+/g," ").trim(),Z}catch{return $}}await p0();var c4=()=>{};function tE($){c4=$}var f6=!1;async function eE($){if(f6)return;try{let{query:Z,durationMs:X,error:_,bindings:Y}=$O($);try{c4(Z,X,Z8.database?.default||"unknown")}catch{}if(!Z8.database?.queryLogging?.enabled)return;let L=ZO(X,_),z=await XO(Z,X,L,_,Y);if(Z8.database?.queryLogging?.analysis?.enabled&&(L==="slow"||Z8.database.queryLogging.analysis.analyzeAll))await zO(z);f6=!0;try{await OO(z)}finally{f6=!1}if(L!=="completed")n1[L==="failed"?"error":"warn"](`Query ${L}:`,{query:z.query,duration:z.duration,connection:z.connection,..._&&{error:_}})}catch(Z){n1.error("Failed to log query:",Z)}}function $O($){let Z=$.query?.sql||"",X=$.queryDurationMillis||0,_=$.error,Y;if($.query?.parameters)try{Y=JSON.stringify($.query.parameters)}catch{Y="[]"}return{query:Z,durationMs:X,error:_,bindings:Y}}function ZO($,Z){let X=Z8.database?.queryLogging?.slowThreshold||100;if(Z)return"failed";if($>X)return"slow";return"completed"}async function XO($,Z,X,_,Y){let L=Z8.database.default||"unknown",z=b6($).normalized||$,{trace:j,caller:U}=LO();return{query:$,normalized_query:z,duration:Z,connection:L,status:X,error:_?String(_):void 0,executed_at:new Date().toISOString(),bindings:Y,trace:j,...U,memory_usage:aE().heapUsed/1024/1024}}var _O=[/\b(?:sk|pk|rk|api|secret|token|bearer|password|pwd|key)[_-]?[A-Za-z0-9]{12,}/gi,/\bsk_(?:live|test)_[A-Za-z0-9]{20,}/g,/\bAKIA[0-9A-Z]{16}/g,/\b[A-Za-z0-9_-]{40,}\b(?=\s|$|['"])/g];function YO($){let Z=$;for(let X of _O)Z=Z.replace(X,"<redacted>");return Z}function LO(){try{let $=Error("Stack trace capture").stack||"",X=$.split(`
|
|
136
|
+
`;g4(G,F),$0.debug(`[migration] Wrote ${E} (${Q.length} stmt${Q.length===1?"":"s"})`),z+=1,j+=1}return z}function nE($){let Z=new Map,X=(_,Y)=>{let L=Z.get(_)??[];L.push(Y),Z.set(_,L)};for(let _ of $){let Y=_.trim();if(!Y)continue;let L=Y.match(/^\s*CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?["`]?(\w+)["`]?/i);if(L){X(`create-${L[1]}-table`,Y);continue}let z=Y.match(/^\s*ALTER\s+TABLE\s+["`]?(\w+)["`]?\s+(?:ADD\s+COLUMN\s+["`]?(\w+)["`]?|DROP\s+COLUMN\s+["`]?(\w+)["`]?|ADD\s+CONSTRAINT)/i);if(z){X(`alter-${z[1]}-${z[2]||z[3]||"constraint"}`,Y);continue}let j=Y.match(/^\s*CREATE\s+(?:UNIQUE\s+)?INDEX\s+(?:IF\s+NOT\s+EXISTS\s+)?["`]?(\w+)["`]?\s+ON\s+["`]?(\w+)["`]?/i);if(j){X(`create-${j[1]}-index-in-${j[2]}`,Y);continue}let U=Y.match(/^\s*DROP\s+TABLE\s+(?:IF\s+EXISTS\s+)?["`]?(\w+)["`]?/i);if(U){X(`drop-${U[1]}-table`,Y);continue}X("auto-misc",Y)}return[...Z.entries()].map(([_,Y])=>({label:_,statements:Y}))}function iE($){let Z=0;try{for(let X of h6($)){let _=X.match(/^(\d+)-/);if(_?.[1])Z=Math.max(Z,Number.parseInt(_[1],10))}}catch{}return Z+1}async function eV(){try{$0.info("Generating fresh migrations..."),c5();let $=d8(),{modelsDir:Z,skip:X}=p4();if(X)return $0.info("No app/Models directory found; using committed framework migrations"),c8("Migrations generated");return await A5(Z,{dialect:$,full:!0}),$0.success("Migrations generated"),c8("Migrations generated")}catch($){return f5(g5("Fresh migration generation failed",$))}}import{memoryUsage as aE}from"process";import{config as Z8}from"@stacksjs/config";import{log as n1}from"@stacksjs/logging";function b6($){let Z={normalized:"",type:"OTHER",tables:[]};if(!$)return Z;try{let X=$.trim().toUpperCase();if(X.startsWith("SELECT"))Z.type="SELECT";else if(X.startsWith("INSERT"))Z.type="INSERT";else if(X.startsWith("UPDATE"))Z.type="UPDATE";else if(X.startsWith("DELETE"))Z.type="DELETE";return Z.tables=rE($,Z.type),Z.normalized=oE($),Z}catch{return{normalized:$,type:"OTHER",tables:[]}}}function rE($,Z){let X=[],_=$.toLowerCase();try{let Y=null,L=null,z=null,j=null,U=null,Q=/join\s+([\w.]+)/gi;switch(Z){case"SELECT":if(Y=_.match(/from\s+([\w.]+)/i),Y&&Y[1])X.push(Y[1].replace(/`/g,"").split(".").pop());L=Q.exec(_);while(L!==null){if(L[1])X.push(L[1].replace(/`/g,"").split(".").pop());L=Q.exec(_)}break;case"INSERT":if(z=_.match(/into\s+([\w.]+)/i),z&&z[1])X.push(z[1].replace(/`/g,"").split(".").pop());break;case"UPDATE":if(j=_.match(/update\s+([\w.]+)/i),j&&j[1])X.push(j[1].replace(/`/g,"").split(".").pop());break;case"DELETE":if(U=_.match(/from\s+([\w.]+)/i),U&&U[1])X.push(U[1].replace(/`/g,"").split(".").pop());break}}catch{}return[...new Set(X)].filter(Boolean)}function oE($){try{let Z=$;return Z=Z.replace(/(?<![a-zA-Z_])\b\d+\b(?![a-zA-Z_])/g,"?"),Z=Z.replace(/'([^']|'')*'/g,"?"),Z=Z.replace(/"([^"]|"")*"/g,"?"),Z=Z.replace(/\btrue\b/gi,"?"),Z=Z.replace(/\bfalse\b/gi,"?"),Z=Z.replace(/\bnull\b/gi,"?"),Z=Z.replace(/\s+/g," ").trim(),Z}catch{return $}}await p0();var c4=()=>{};function tE($){c4=$}var f6=!1;async function eE($){if(f6)return;try{let{query:Z,durationMs:X,error:_,bindings:Y}=$O($);try{c4(Z,X,Z8.database?.default||"unknown")}catch{}if(!Z8.database?.queryLogging?.enabled)return;let L=ZO(X,_),z=await XO(Z,X,L,_,Y);if(Z8.database?.queryLogging?.analysis?.enabled&&(L==="slow"||Z8.database.queryLogging.analysis.analyzeAll))await zO(z);f6=!0;try{await OO(z)}finally{f6=!1}if(L!=="completed")n1[L==="failed"?"error":"warn"](`Query ${L}:`,{query:z.query,duration:z.duration,connection:z.connection,..._&&{error:_}})}catch(Z){n1.error("Failed to log query:",Z)}}function $O($){let Z=$.query?.sql||"",X=$.queryDurationMillis||0,_=$.error,Y;if($.query?.parameters)try{Y=JSON.stringify($.query.parameters)}catch{Y="[]"}return{query:Z,durationMs:X,error:_,bindings:Y}}function ZO($,Z){let X=Z8.database?.queryLogging?.slowThreshold||100;if(Z)return"failed";if($>X)return"slow";return"completed"}async function XO($,Z,X,_,Y){let L=Z8.database.default||"unknown",z=b6($).normalized||$,{trace:j,caller:U}=LO();return{query:$,normalized_query:z,duration:Z,connection:L,status:X,error:_?String(_):void 0,executed_at:new Date().toISOString(),bindings:Y,trace:j,...U,memory_usage:aE().heapUsed/1024/1024}}var _O=[/\b(?:sk|pk|rk|api|secret|token|bearer|password|pwd|key)[_-]?[A-Za-z0-9]{12,}/gi,/\bsk_(?:live|test)_[A-Za-z0-9]{20,}/g,/\bAKIA[0-9A-Z]{16}/g,/\b[A-Za-z0-9_-]{40,}\b(?=\s|$|['"])/g];function YO($){let Z=$;for(let X of _O)Z=Z.replace(X,"<redacted>");return Z}function LO(){try{let $=Error("Stack trace capture").stack||"",X=$.split(`
|
|
137
137
|
`).slice(1).find((Y)=>!Y.includes("query-logger.ts")),_={};if(X){let Y=X.match(/at (.+?) \(/),L=X.match(/\((.+?):(\d+):(\d+)\)/);if(Y&&Y[1]){let z=Y[1].split(".");_={model:z.length>1?z[0]:void 0,method:z.length>1?z[1]:z[0]}}if(L&&L[1]&&L[2])_={..._,file:L[1],line:Number.parseInt(L[2],10)}}return{trace:YO($),caller:_}}catch{return{trace:"",caller:{}}}}async function zO($){try{let{tables:Z,type:X}=b6($.query);if($.affected_tables=JSON.stringify(Z||[]),X==="SELECT"&&Z8.database?.queryLogging?.analysis?.explainPlan){let Y=await jO($.query);if(Y){if($.explain_plan=Y.plan,$.indexes_used=JSON.stringify(Y.indexesUsed||[]),$.missing_indexes=JSON.stringify(Y.missingIndexes||[]),Z8.database?.queryLogging?.analysis?.suggestions)$.optimization_suggestions=JSON.stringify(EO(Y,$))}}let _=[X];if(Z&&Z.length>0)_.push(...Z.map((Y)=>`table:${Y}`));$.tags=JSON.stringify(_)}catch(Z){n1.debug("Error during query analysis:",Z)}}var UO=new Set(["sqlite","mysql","postgres"]);async function jO($){try{let Z=(await import("@stacksjs/config")).config?.database?.default,X=typeof Z==="string"?Z:"sqlite";if(!UO.has(X))return null;let _;if(X==="mysql")_=`EXPLAIN FORMAT=JSON ${$}`;else if(X==="postgres")_=`EXPLAIN (FORMAT JSON) ${$}`;else _=`EXPLAIN QUERY PLAN ${$}`;let Y=await v.unsafe?.(_);if(!Y)return null;let L=Array.isArray(Y)?Y:Y.rows??[],z=JSON.stringify(L),j=[],U=[];if(X==="sqlite")for(let Q of L){let E=Q?.detail||"",G=E.match(/USING (?:COVERING )?INDEX (\w+)/i);if(G&&G[1])j.push(G[1]);else{let F=E.match(/^SCAN\s+(\w+)/i);if(F&&F[1])U.push(F[1])}}else if(X==="mysql"){let Q=z;for(let E of Q.matchAll(/"key"\s*:\s*"([^"]+)"/g))if(E[1])j.push(E[1]);for(let E of Q.matchAll(/"table_name"\s*:\s*"([^"]+)"[^}]*"access_type"\s*:\s*"ALL"/g))if(E[1])U.push(E[1])}else if(X==="postgres"){let Q=z;for(let E of Q.matchAll(/"Index Name"\s*:\s*"([^"]+)"/g))if(E[1])j.push(E[1]);for(let E of Q.matchAll(/"Node Type"\s*:\s*"Seq Scan"[^}]*"Relation Name"\s*:\s*"([^"]+)"/g))if(E[1])U.push(E[1])}return{plan:z.length>4000?`${z.slice(0,4000)}\u2026`:z,indexesUsed:Array.from(new Set(j)),missingIndexes:Array.from(new Set(U))}}catch(Z){return n1.debug("[query-logger] EXPLAIN failed (non-fatal):",Z),null}}function EO($,Z){let X=[];if($.missingIndexes&&$.missingIndexes.length>0)X.push(`Consider adding index on ${$.missingIndexes.join(", ")}`);if(Z.status==="slow"){if(X.push("Consider optimizing this query to reduce execution time"),Z.query.toLowerCase().includes("select *"))X.push("Specify only needed columns instead of using SELECT *");if(!Z.query.toLowerCase().includes("limit"))X.push("Consider adding LIMIT clause to reduce result set size")}return X}async function OO($){try{await v.insertInto("query_logs").values($).execute()}catch(Z){n1.error("Failed to store query log:",Z)}}import{log as i1}from"@stacksjs/logging";import{path as QO}from"@stacksjs/path";import{fs as d4}from"@stacksjs/storage";class u4{async call($){await new $().run()}}async function GO($={}){let Z=$.dir??QO.projectPath("database/seeders"),X=[],_=[];if(!d4.existsSync(Z))return i1.info(`[seeder] No class seeders directory at ${Z}`),{ran:X,skipped:_};let Y=d4.readdirSync(Z).filter((L)=>L.endsWith(".ts")&&!L.startsWith("_"));for(let L of Y){let z=L.replace(/\.ts$/,"");if($.class&&z!==$.class){_.push(z);continue}try{let j=await import(`${Z}/${L}`),U=j.default??j[z];if(!U){i1.warn(`[seeder] ${L} has no default export`),_.push(z);continue}let Q=new U;if(typeof Q.run!=="function"){i1.warn(`[seeder] ${z} does not implement run()`),_.push(z);continue}i1.info(`[seeder] Running ${z}\u2026`),await Q.run(),X.push(z)}catch(j){i1.error(`[seeder] ${z} failed:`,j),_.push(z)}}return{ran:X,skipped:_}}async function FO($,Z,X,_){let{type:Y,defaultValue:L,notNull:z=!1,batchSize:j=1000}=_,U=$,Q=L===void 0?"":` DEFAULT ${l4(L)}`;if(await u8(U,`ALTER TABLE ${c0(Z)} ADD COLUMN ${c0(X)} ${Y}${Q}`),L!==void 0)await s4($,Z,X,L,j);if(z)await u8(U,`ALTER TABLE ${c0(Z)} ALTER COLUMN ${c0(X)} SET NOT NULL`)}async function u8($,Z){if(typeof $.unsafe==="function")return await $.unsafe(Z)??{};return await u1`${u1.raw(Z)}`.execute($),{}}async function s4($,Z,X,_,Y=1000){let L=$,z=0,j=0;do{let U=`
|
|
138
138
|
UPDATE ${c0(Z)} SET ${c0(X)} = ${l4(_)}
|
|
139
139
|
WHERE ${c0(X)} IS NULL
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Result } from '@stacksjs/error-handling';
|
|
2
|
+
export type { MigrationResult as MigrationResultType };
|
|
3
|
+
/**
|
|
4
|
+
* Run database migrations
|
|
5
|
+
*/
|
|
6
|
+
export declare function runDatabaseMigration(): Promise<Result<string, Error>>;
|
|
7
|
+
/**
|
|
8
|
+
* Reset the database (drop all tables)
|
|
9
|
+
*/
|
|
10
|
+
export declare function resetDatabase(): Promise<Result<string, Error>>;
|
|
11
|
+
/*` definitions to the stored snapshot
|
|
12
|
+
* (`.qb/model-snapshot.<dialect>.json`) via bun-query-builder, then — if
|
|
13
|
+
* there are changes — writes the resulting ALTER/CREATE/DROP statements
|
|
14
|
+
* out to a fresh file in `database/migrations/`. Each statement is
|
|
15
|
+
* grouped by table + DDL verb and lands in its own file using the
|
|
16
|
+
* runner's existing naming convention so it picks them up the same way
|
|
17
|
+
* as a hand-written migration.
|
|
18
|
+
*
|
|
19
|
+
* Without this write step the qb generator stages the diff in memory but
|
|
20
|
+
* the runner never sees it, so model edits silently no-op'd — defeating
|
|
21
|
+
* the "models are the source of truth" promise.
|
|
22
|
+
*/
|
|
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>>;
|
|
28
|
+
/**
|
|
29
|
+
* Migration result type for compatibility
|
|
30
|
+
*/
|
|
31
|
+
export declare interface MigrationResult {
|
|
32
|
+
migrationName: string
|
|
33
|
+
direction: 'Up' | 'Down'
|
|
34
|
+
status: 'Success' | 'Error' | 'NotExecuted'
|
|
35
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare function setQueryTracker(fn: QueryTracker): void;
|
|
2
|
+
/**
|
|
3
|
+
* Process an executed query and store it in the database
|
|
4
|
+
*/
|
|
5
|
+
export declare function logQuery(event: LogEvent): Promise<void>;
|
|
6
|
+
/**
|
|
7
|
+
* Query log event type - compatible with bun-query-builder hooks
|
|
8
|
+
*/
|
|
9
|
+
declare interface LogEvent {
|
|
10
|
+
query?: {
|
|
11
|
+
sql?: string
|
|
12
|
+
parameters?: unknown[]
|
|
13
|
+
}
|
|
14
|
+
queryDurationMillis?: number
|
|
15
|
+
error?: Error | unknown
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Soft dependency on the router's query tracker. Importing it directly
|
|
19
|
+
* creates the cycle `database → router → database` (router uses db
|
|
20
|
+
* helpers transitively via middleware). The DI shape below lets the
|
|
21
|
+
* router register its tracker at module-init time and lets the database
|
|
22
|
+
* package stay leaf-node — runs that don't load the router (CLI tools,
|
|
23
|
+
* cron tasks) silently no-op the tracker call.
|
|
24
|
+
*/
|
|
25
|
+
// eslint-disable-next-line pickier/no-unused-vars
|
|
26
|
+
declare type QueryTracker = (query: string, durationMs?: number, connection?: string) => void;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { db } from './utils';
|
|
2
|
+
/**
|
|
3
|
+
* Add a column to `tableName` without taking a table-level lock long
|
|
4
|
+
* enough to disrupt traffic. The column is created nullable, backfilled
|
|
5
|
+
* in batches, and (if `notNull: true`) the constraint is added at the
|
|
6
|
+
* end.
|
|
7
|
+
*
|
|
8
|
+
* Pre-conditions:
|
|
9
|
+
* - `tableName` exists
|
|
10
|
+
* - `columnName` does NOT already exist (this helper doesn't gracefully
|
|
11
|
+
* handle the rerun case — wrap in `if (!columnExists)` if you need that)
|
|
12
|
+
*
|
|
13
|
+
* Caveats:
|
|
14
|
+
* - SQLite doesn't support adding NOT NULL columns to existing tables
|
|
15
|
+
* without a default; we work around it by always supplying one
|
|
16
|
+
* - Postgres < 11 rewrites the entire table when a default is added;
|
|
17
|
+
* this helper assumes ≥ 11
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* await addColumnSafely(db, 'users', 'email_verified', {
|
|
22
|
+
* type: 'boolean',
|
|
23
|
+
* defaultValue: false,
|
|
24
|
+
* notNull: true,
|
|
25
|
+
* })
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare function addColumnSafely(db: Database, tableName: string, columnName: string, options: AddColumnSafelyOptions): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Back-fill `columnName` with `value` for any row where it's currently
|
|
31
|
+
* NULL. Runs in batches so the UPDATE doesn't lock the entire table.
|
|
32
|
+
*
|
|
33
|
+
* Useful as a standalone helper when you want to backfill an *existing*
|
|
34
|
+
* column (e.g. populating a denormalized count) — `addColumnSafely`
|
|
35
|
+
* uses it internally.
|
|
36
|
+
*/
|
|
37
|
+
export declare function backfillInBatches(db: Database, tableName: string, columnName: string, value: string | number | boolean | null, batchSize?: number): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Rename a column safely on a table that's actively serving traffic.
|
|
40
|
+
*
|
|
41
|
+
* Most database engines DO support `RENAME COLUMN` as a metadata-only
|
|
42
|
+
* operation (no rewrite, no long lock), which means the headline
|
|
43
|
+
* concern is *application-side*: app code reads the old column name,
|
|
44
|
+
* the migration renames it, and the next request 500s.
|
|
45
|
+
*
|
|
46
|
+
* This helper wraps the rename in a multi-step sequence the framework
|
|
47
|
+
* docs can teach as the canonical pattern:
|
|
48
|
+
*
|
|
49
|
+
* 1. Add the new column
|
|
50
|
+
* 2. Backfill from old → new
|
|
51
|
+
* 3. Update writes to dual-write old AND new (app-level, deploy step)
|
|
52
|
+
* 4. Update reads to read from new (app-level, deploy step)
|
|
53
|
+
* 5. Drop the old column (separate migration)
|
|
54
|
+
*
|
|
55
|
+
* For the rare case where the rename *can* happen atomically (small
|
|
56
|
+
* table, no live traffic), pass `{ atomic: true }` and we'll just emit
|
|
57
|
+
* the RENAME COLUMN.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* // Step 1 of the rename sequence — the rest is app-side coordination.
|
|
62
|
+
* await renameColumnSafely(db, 'users', 'name', 'full_name', { type: 'varchar(255)' })
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export declare function renameColumnSafely(db: Database, tableName: string, oldName: string, newName: string, options: { type: string, atomic?: boolean }): Promise<void>;
|
|
66
|
+
declare interface AddColumnSafelyOptions {
|
|
67
|
+
type: string
|
|
68
|
+
defaultValue?: string | number | boolean | null
|
|
69
|
+
notNull?: boolean
|
|
70
|
+
batchSize?: number
|
|
71
|
+
}
|
|
72
|
+
declare type Database = typeof db;
|
package/dist/seeder.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main seeding function
|
|
3
|
+
* Seeds the database using model factory functions
|
|
4
|
+
* Loads models from both framework defaults and user-defined models,
|
|
5
|
+
* with user models taking precedence.
|
|
6
|
+
*/
|
|
7
|
+
export declare function seed(config?: SeederConfig): Promise<SeedSummary>;
|
|
8
|
+
/**
|
|
9
|
+
* Seed a specific model by name
|
|
10
|
+
* Searches both default and user models
|
|
11
|
+
*/
|
|
12
|
+
export declare function seedModel$(modelName: string, options?: { count?: number, fresh?: boolean, verbose?: boolean }): Promise<SeedResult>;
|
|
13
|
+
/**
|
|
14
|
+
* Fresh seed - truncate all tables and reseed
|
|
15
|
+
*/
|
|
16
|
+
export declare function freshSeed(config?: SeederConfig): Promise<SeedSummary>;
|
|
17
|
+
/**
|
|
18
|
+
* Get list of seedable models without seeding
|
|
19
|
+
* Returns models from both default and user directories
|
|
20
|
+
*/
|
|
21
|
+
export declare function listSeedableModels(): Promise<Array<{ name: string, table: string, count: number, source: 'default' | 'user' }>>;
|
|
22
|
+
/**
|
|
23
|
+
* Seeder configuration options
|
|
24
|
+
*/
|
|
25
|
+
export declare interface SeederConfig {
|
|
26
|
+
modelsDir?: string
|
|
27
|
+
defaultCount?: number
|
|
28
|
+
verbose?: boolean
|
|
29
|
+
fresh?: boolean
|
|
30
|
+
only?: string[]
|
|
31
|
+
except?: string[]
|
|
32
|
+
includeDefaults?: boolean
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Result of a single model seeding operation
|
|
36
|
+
*/
|
|
37
|
+
export declare interface SeedResult {
|
|
38
|
+
model: string
|
|
39
|
+
table: string
|
|
40
|
+
count: number
|
|
41
|
+
success: boolean
|
|
42
|
+
error?: string
|
|
43
|
+
duration: number
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Result of the entire seeding operation
|
|
47
|
+
*/
|
|
48
|
+
export declare interface SeedSummary {
|
|
49
|
+
total: number
|
|
50
|
+
successful: number
|
|
51
|
+
failed: number
|
|
52
|
+
results: SeedResult[]
|
|
53
|
+
duration: number
|
|
54
|
+
}
|
|
55
|
+
// Legacy exports for backwards compatibility
|
|
56
|
+
export { seed as runSeeders };
|
|
57
|
+
export { freshSeed as freshWithSeed };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create SQL dialect helpers for a given driver.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { sqlHelpers } from '@stacksjs/database'
|
|
7
|
+
* const sql = sqlHelpers('postgres')
|
|
8
|
+
* await db.unsafe(`SELECT * FROM users WHERE id = ${sql.param(1)}`, [userId])
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
export declare function sqlHelpers(driver: string): SqlDialectHelpers;
|
|
12
|
+
/**
|
|
13
|
+
* SQL Dialect Helpers
|
|
14
|
+
*
|
|
15
|
+
* Cross-database compatibility utilities for PostgreSQL, MySQL, and SQLite.
|
|
16
|
+
* Centralizes the isPostgres/isMysql/now/boolTrue/boolFalse/param helpers
|
|
17
|
+
* that were previously duplicated across tokens.ts, auth-tables.ts, and setup.ts.
|
|
18
|
+
*/
|
|
19
|
+
export declare interface SqlDialectHelpers {
|
|
20
|
+
driver: string
|
|
21
|
+
isPostgres: boolean
|
|
22
|
+
isMysql: boolean
|
|
23
|
+
isSqlite: boolean
|
|
24
|
+
now: string
|
|
25
|
+
boolTrue: string
|
|
26
|
+
boolFalse: string
|
|
27
|
+
autoIncrement: string
|
|
28
|
+
primaryKey: string
|
|
29
|
+
param: (index: number) => string
|
|
30
|
+
params: (...values: unknown[]) => { sql: string, values: unknown[] }
|
|
31
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQL template tag function.
|
|
3
|
+
* Creates parameterized SQL queries from template literals.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* const query = sql`SELECT * FROM users WHERE id = ${userId}`
|
|
8
|
+
* ```
|
|
9
|
+
*/
|
|
10
|
+
export declare function sql(strings: TemplateStringsArray, ...values: unknown[]): Sql;
|
|
11
|
+
/**
|
|
12
|
+
* Type for raw SQL expressions.
|
|
13
|
+
* Used when building dynamic SQL queries.
|
|
14
|
+
*/
|
|
15
|
+
export declare interface RawBuilder<T = unknown> {
|
|
16
|
+
readonly sql: string
|
|
17
|
+
readonly parameters?: unknown[]
|
|
18
|
+
readonly __result?: T
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* SQL template tag type.
|
|
22
|
+
* Used for tagged template literals that produce SQL.
|
|
23
|
+
*/
|
|
24
|
+
export declare interface Sql {
|
|
25
|
+
readonly sql: string
|
|
26
|
+
readonly parameters: unknown[]
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Database types - Compatibility layer
|
|
30
|
+
*
|
|
31
|
+
* These types provide backwards compatibility with code that
|
|
32
|
+
* previously used Kysely types. They work with bun-query-builder's
|
|
33
|
+
* native type system.
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* Marks a column as auto-generated (e.g., auto-increment primary keys).
|
|
37
|
+
* When inserting, this field is optional. When selecting, it's required.
|
|
38
|
+
*/
|
|
39
|
+
export type Generated<T> = T;
|
|
40
|
+
/**
|
|
41
|
+
* Marks a column as always generated (computed columns).
|
|
42
|
+
* This field cannot be inserted or updated directly.
|
|
43
|
+
*/
|
|
44
|
+
export type GeneratedAlways<T> = T;
|
|
45
|
+
/**
|
|
46
|
+
* Utility type for insert operations.
|
|
47
|
+
* Makes Generated fields optional, keeps required fields required.
|
|
48
|
+
*/
|
|
49
|
+
export type Insertable<T> = {
|
|
50
|
+
[K in keyof T]?: T[K]
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Utility type for select operations.
|
|
54
|
+
* All fields are as defined in the table type.
|
|
55
|
+
*/
|
|
56
|
+
export type Selectable<T> = T;
|
|
57
|
+
/**
|
|
58
|
+
* Utility type for update operations.
|
|
59
|
+
* All fields are optional since you may update only some fields.
|
|
60
|
+
*/
|
|
61
|
+
export type Updateable<T> = Partial<T>;
|
|
62
|
+
/**
|
|
63
|
+
* Database type alias for backwards compatibility.
|
|
64
|
+
* Use the query builder from bun-query-builder instead.
|
|
65
|
+
*/
|
|
66
|
+
export type Database = any;
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { createQueryBuilder, setConfig } from 'bun-query-builder';
|
|
2
|
+
import type { DatabaseSchema } from 'bun-query-builder';
|
|
3
|
+
// Function to initialize the config when it's available
|
|
4
|
+
export declare function initializeDbConfig(config: any): void;
|
|
5
|
+
export declare function ensureDatabaseConfigLoaded(): Promise<void>;
|
|
6
|
+
/**
|
|
7
|
+
* Lazy proxy for the query builder - connection is only made when first used.
|
|
8
|
+
* This is the main entry point for database operations.
|
|
9
|
+
*/
|
|
10
|
+
export declare const db: Proxy;
|
|
11
|
+
/**
|
|
12
|
+
* Fluent chain returned by entry-point methods like `selectFrom`/`updateTable`.
|
|
13
|
+
*
|
|
14
|
+
* bun-query-builder marks legacy chain methods (e.g. `selectAll`, `whereILike`,
|
|
15
|
+
* `selectAllRelations`) as optional in its declarations even though they're
|
|
16
|
+
* always present at runtime. Re-typing them here avoids forcing every call
|
|
17
|
+
* site to use `?.()` or `!` on the chain.
|
|
18
|
+
*
|
|
19
|
+
* Returns are typed as `any` deliberately — typing each variant precisely
|
|
20
|
+
* would re-introduce the optional methods, and we already lose strict column
|
|
21
|
+
* typing one step into a chain (the underlying query builder is constructed
|
|
22
|
+
* with no schema). Tests cover the runtime semantics.
|
|
23
|
+
*/
|
|
24
|
+
export declare interface FluentChain {
|
|
25
|
+
where: (...args: any[]) => FluentChain
|
|
26
|
+
whereNull: (...args: any[]) => FluentChain
|
|
27
|
+
whereNotNull: (...args: any[]) => FluentChain
|
|
28
|
+
whereIn: (...args: any[]) => FluentChain
|
|
29
|
+
whereNotIn: (...args: any[]) => FluentChain
|
|
30
|
+
whereLike: (...args: any[]) => FluentChain
|
|
31
|
+
whereNotLike: (...args: any[]) => FluentChain
|
|
32
|
+
whereILike: (...args: any[]) => FluentChain
|
|
33
|
+
whereNotILike: (...args: any[]) => FluentChain
|
|
34
|
+
whereBetween: (...args: any[]) => FluentChain
|
|
35
|
+
whereNotBetween: (...args: any[]) => FluentChain
|
|
36
|
+
whereRaw: (...args: any[]) => FluentChain
|
|
37
|
+
whereColumn: (...args: any[]) => FluentChain
|
|
38
|
+
orWhere: (...args: any[]) => FluentChain
|
|
39
|
+
orWhereNull: (...args: any[]) => FluentChain
|
|
40
|
+
orWhereNotNull: (...args: any[]) => FluentChain
|
|
41
|
+
orWhereIn: (...args: any[]) => FluentChain
|
|
42
|
+
orWhereNotIn: (...args: any[]) => FluentChain
|
|
43
|
+
orWhereLike: (...args: any[]) => FluentChain
|
|
44
|
+
orWhereNotLike: (...args: any[]) => FluentChain
|
|
45
|
+
orWhereILike: (...args: any[]) => FluentChain
|
|
46
|
+
orWhereColumn: (...args: any[]) => FluentChain
|
|
47
|
+
andWhere: (...args: any[]) => FluentChain
|
|
48
|
+
having: (...args: any[]) => FluentChain
|
|
49
|
+
groupBy: (...args: any[]) => FluentChain
|
|
50
|
+
orderBy: (...args: any[]) => FluentChain
|
|
51
|
+
limit: (...args: any[]) => FluentChain
|
|
52
|
+
offset: (...args: any[]) => FluentChain
|
|
53
|
+
select: (...args: any[]) => FluentChain
|
|
54
|
+
selectAll: () => FluentChain
|
|
55
|
+
selectAllRelations: () => FluentChain
|
|
56
|
+
selectRaw: (...args: any[]) => FluentChain
|
|
57
|
+
distinct: () => FluentChain
|
|
58
|
+
distinctOn: (...args: any[]) => FluentChain
|
|
59
|
+
innerJoin: (...args: any[]) => FluentChain
|
|
60
|
+
leftJoin: (...args: any[]) => FluentChain
|
|
61
|
+
rightJoin: (...args: any[]) => FluentChain
|
|
62
|
+
fullJoin: (...args: any[]) => FluentChain
|
|
63
|
+
crossJoin: (...args: any[]) => FluentChain
|
|
64
|
+
with: (...args: any[]) => FluentChain
|
|
65
|
+
union: (...args: any[]) => FluentChain
|
|
66
|
+
unionAll: (...args: any[]) => FluentChain
|
|
67
|
+
values: (...args: any[]) => FluentChain
|
|
68
|
+
set: (...args: any[]) => FluentChain
|
|
69
|
+
returning: (...args: any[]) => FluentChain
|
|
70
|
+
returningAll: () => FluentChain
|
|
71
|
+
onConflict: (...args: any[]) => FluentChain
|
|
72
|
+
onDuplicateKeyUpdate: (...args: any[]) => FluentChain
|
|
73
|
+
onConflictDoNothing: (...args: any[]) => FluentChain
|
|
74
|
+
onDuplicateKeyIgnore: () => FluentChain
|
|
75
|
+
forUpdate: () => FluentChain
|
|
76
|
+
forShare: () => FluentChain
|
|
77
|
+
toSQL: () => string
|
|
78
|
+
execute: () => Promise<any>
|
|
79
|
+
executeTakeFirst: () => Promise<any>
|
|
80
|
+
executeTakeFirstOrThrow: () => Promise<any>
|
|
81
|
+
pluck: (...args: any[]) => Promise<any>
|
|
82
|
+
count: (...args: any[]) => Promise<number>
|
|
83
|
+
sum: (...args: any[]) => Promise<number>
|
|
84
|
+
avg: (...args: any[]) => Promise<number>
|
|
85
|
+
min: (...args: any[]) => Promise<any>
|
|
86
|
+
max: (...args: any[]) => Promise<any>
|
|
87
|
+
exists: () => Promise<boolean>
|
|
88
|
+
doesntExist: () => Promise<boolean>
|
|
89
|
+
[key: string]: any
|
|
90
|
+
}
|
|
91
|
+
// Permissive schema type that accepts any table name with any columns
|
|
92
|
+
// This allows the query builder to work before model types are generated
|
|
93
|
+
declare type AnySchema = DatabaseSchema<any> & Record<string, { columns: Record<string, any>, primaryKey: string }>;
|
|
94
|
+
// The bun-query-builder types `unsafe()` as returning `Promise<any>`, but at
|
|
95
|
+
// runtime it returns a Bun SQL Statement that has `.execute()`. This interface
|
|
96
|
+
// corrects the return type so callers can chain `.execute()` without type errors.
|
|
97
|
+
declare type UnsafeReturn = Promise<any> & { execute: () => Promise<any> }
|
|
98
|
+
/**
|
|
99
|
+
* Top-level surface of the lazy `db` proxy. Methods that return a chainable
|
|
100
|
+
* builder are typed as `FluentChain` to flatten the optional-method noise
|
|
101
|
+
* inherent in bun-query-builder's declarations. Methods that introduce their
|
|
102
|
+
* own generics (`transaction<T>`, etc.) are kept as their original signatures
|
|
103
|
+
* via the underlying QueryBuilder type so call-site inference still works.
|
|
104
|
+
*/
|
|
105
|
+
declare type RawQueryBuilder = ReturnType<typeof createQueryBuilder>;
|
|
106
|
+
declare type GenericPassthroughKeys = | 'transaction'
|
|
107
|
+
| 'savepoint'
|
|
108
|
+
| 'beginDistributed'
|
|
109
|
+
| 'transactional'
|
|
110
|
+
| 'configure'
|
|
111
|
+
| 'reserve'
|
|
112
|
+
| 'commitDistributed'
|
|
113
|
+
| 'rollbackDistributed'
|
|
114
|
+
| 'setTransactionDefaults'
|
|
115
|
+
| 'close'
|
|
116
|
+
| 'listen'
|
|
117
|
+
| 'unlisten'
|
|
118
|
+
| 'notify'
|
|
119
|
+
| 'copyTo'
|
|
120
|
+
| 'copyFrom'
|
|
121
|
+
| 'ping'
|
|
122
|
+
| 'waitForReady'
|
|
123
|
+
| 'count'
|
|
124
|
+
| 'sum'
|
|
125
|
+
| 'avg'
|
|
126
|
+
| 'min'
|
|
127
|
+
| 'max'
|
|
128
|
+
| 'insertOrIgnore'
|
|
129
|
+
| 'insertGetId'
|
|
130
|
+
| 'updateOrInsert'
|
|
131
|
+
| 'upsert'
|
|
132
|
+
| 'create'
|
|
133
|
+
| 'createMany'
|
|
134
|
+
| 'sql'
|
|
135
|
+
| 'raw'
|
|
136
|
+
| 'simple'
|
|
137
|
+
| 'file';
|
|
138
|
+
// Export setConfig if available
|
|
139
|
+
export { setConfig };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { BigintValidatorType, BinaryValidatorType, BlobValidatorType, BooleanValidatorType, DatetimeValidatorType, DateValidatorType, DecimalValidatorType, EnumValidatorType, FloatValidatorType, IntegerValidatorType, JsonValidatorType, NumberValidatorType, SmallintValidatorType, StringValidatorType, TimestampTzValidatorType, TimestampValidatorType, UnixValidatorType, ValidationType } from '@stacksjs/ts-validation';
|
|
2
|
+
export declare function isStringValidator(v: ValidationType): v is StringValidatorType;
|
|
3
|
+
export declare function isNumberValidator(v: ValidationType): v is NumberValidatorType;
|
|
4
|
+
export declare function enumValidator(v: ValidationType): v is EnumValidatorType;
|
|
5
|
+
export declare function isBooleanValidator(v: ValidationType): v is BooleanValidatorType;
|
|
6
|
+
export declare function isDateValidator(v: ValidationType): v is DateValidatorType;
|
|
7
|
+
export declare function isUnixValidator(v: ValidationType): v is UnixValidatorType;
|
|
8
|
+
export declare function isFloatValidator(v: ValidationType): v is FloatValidatorType;
|
|
9
|
+
export declare function isDatetimeValidator(v: ValidationType): v is DatetimeValidatorType;
|
|
10
|
+
export declare function isTimestampValidator(v: ValidationType): v is TimestampValidatorType;
|
|
11
|
+
export declare function isTimestampTzValidator(v: ValidationType): v is TimestampTzValidatorType;
|
|
12
|
+
export declare function isDecimalValidator(v: ValidationType): v is DecimalValidatorType;
|
|
13
|
+
export declare function isSmallintValidator(v: ValidationType): v is SmallintValidatorType;
|
|
14
|
+
export declare function isIntegerValidator(v: ValidationType): v is IntegerValidatorType;
|
|
15
|
+
export declare function isBigintValidator(v: ValidationType): v is BigintValidatorType;
|
|
16
|
+
export declare function isBinaryValidator(v: ValidationType): v is BinaryValidatorType;
|
|
17
|
+
export declare function isBlobValidator(v: ValidationType): v is BlobValidatorType;
|
|
18
|
+
export declare function isJsonValidator(v: ValidationType): v is JsonValidatorType;
|
|
19
|
+
export declare function checkValidator(validator: ValidationType, driver: string): string;
|
|
20
|
+
export declare function prepareNumberColumnType(validator: NumberValidatorType, driver?: string): string;
|
|
21
|
+
// Add new function for enum column types
|
|
22
|
+
export declare function prepareEnumColumnType(validator: EnumValidatorType, driver?: string): string;
|
|
23
|
+
export declare function prepareTextColumnType(validator: StringValidatorType, driver?: string): string;
|
|
24
|
+
// Add new function for date/time column types
|
|
25
|
+
export declare function prepareDateTimeColumnType(validator: DateValidatorType, driver?: string): string;
|
|
26
|
+
export declare function findCharacterLength(validator: ValidationType): number;
|
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.38",
|
|
5
5
|
"description": "The Stacks database integration.",
|
|
6
6
|
"author": "Chris Breuer",
|
|
7
7
|
"contributors": [
|
|
@@ -53,15 +53,15 @@
|
|
|
53
53
|
"bun-query-builder": "^0.1.21"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
-
"@stacksjs/cli": "0.70.
|
|
57
|
-
"@stacksjs/config": "0.70.
|
|
58
|
-
"@stacksjs/logging": "0.70.
|
|
59
|
-
"@stacksjs/router": "0.70.
|
|
56
|
+
"@stacksjs/cli": "0.70.37",
|
|
57
|
+
"@stacksjs/config": "0.70.37",
|
|
58
|
+
"@stacksjs/logging": "0.70.37",
|
|
59
|
+
"@stacksjs/router": "0.70.37",
|
|
60
60
|
"better-dx": "^0.2.12",
|
|
61
|
-
"@stacksjs/path": "0.70.
|
|
62
|
-
"@stacksjs/query-builder": "0.70.
|
|
63
|
-
"@stacksjs/storage": "0.70.
|
|
64
|
-
"@stacksjs/strings": "0.70.
|
|
65
|
-
"@stacksjs/utils": "0.70.
|
|
61
|
+
"@stacksjs/path": "0.70.37",
|
|
62
|
+
"@stacksjs/query-builder": "0.70.37",
|
|
63
|
+
"@stacksjs/storage": "0.70.37",
|
|
64
|
+
"@stacksjs/strings": "0.70.37",
|
|
65
|
+
"@stacksjs/utils": "0.70.37"
|
|
66
66
|
}
|
|
67
67
|
}
|