@stacksjs/database 0.70.88 → 0.70.91
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 +60 -0
- package/dist/auth-tables.js +220 -0
- package/dist/class-seeder.d.ts +65 -0
- package/dist/class-seeder.js +116 -0
- package/dist/column.d.ts +17 -0
- package/dist/column.js +26 -0
- package/dist/custom/audits.d.ts +16 -0
- package/dist/custom/audits.js +57 -0
- package/dist/custom/errors.d.ts +1 -0
- package/dist/custom/errors.js +48 -0
- package/dist/custom/index.d.ts +3 -0
- package/dist/custom/index.js +3 -0
- package/dist/custom/jobs.d.ts +3 -0
- package/dist/custom/jobs.js +449 -0
- package/dist/database.d.ts +89 -0
- package/dist/database.js +178 -0
- package/dist/defaults.d.ts +48 -0
- package/dist/defaults.js +48 -0
- package/dist/driver-config.d.ts +149 -0
- package/dist/driver-config.js +144 -0
- package/dist/drivers/defaults/index.d.ts +2 -0
- package/dist/drivers/defaults/index.js +2 -0
- package/dist/drivers/defaults/passwords.d.ts +4 -0
- package/dist/drivers/defaults/passwords.js +106 -0
- package/dist/drivers/defaults/traits.d.ts +33 -0
- package/dist/drivers/defaults/traits.js +1125 -0
- package/dist/drivers/dynamodb.d.ts +200 -0
- package/dist/drivers/dynamodb.js +607 -0
- package/dist/drivers/helpers.d.ts +35 -0
- package/dist/drivers/helpers.js +206 -0
- package/dist/drivers/index.d.ts +16 -0
- package/dist/drivers/index.js +9 -0
- package/dist/drivers/mysql.d.ts +7 -0
- package/dist/drivers/mysql.js +322 -0
- package/dist/drivers/postgres.d.ts +7 -0
- package/dist/drivers/postgres.js +411 -0
- package/dist/drivers/sqlite.d.ts +20 -0
- package/dist/drivers/sqlite.js +397 -0
- package/dist/factory.d.ts +41 -0
- package/dist/factory.js +51 -0
- package/dist/fk-audit.d.ts +101 -0
- package/dist/fk-audit.js +181 -0
- package/dist/index.d.ts +149 -0
- package/dist/index.js +55 -0
- package/dist/migration-lock.d.ts +23 -0
- package/dist/migration-lock.js +143 -0
- package/dist/migrations.d.ts +76 -0
- package/dist/migrations.js +549 -0
- package/dist/notification-tables.d.ts +20 -0
- package/dist/notification-tables.js +54 -0
- package/dist/query-logger.d.ts +26 -0
- package/dist/query-logger.js +213 -0
- package/dist/query-parser.d.ts +4 -0
- package/dist/query-parser.js +93 -0
- package/dist/rbac-tables.d.ts +17 -0
- package/dist/rbac-tables.js +84 -0
- package/dist/safe-migrations.d.ts +72 -0
- package/dist/safe-migrations.js +59 -0
- package/dist/schema.d.ts +4 -0
- package/dist/schema.js +10 -0
- package/dist/seed-scaffold.d.ts +34 -0
- package/dist/seed-scaffold.js +144 -0
- package/dist/seeder.d.ts +116 -0
- package/dist/seeder.js +363 -0
- package/dist/sql-helpers.d.ts +33 -0
- package/dist/sql-helpers.js +24 -0
- package/dist/table.d.ts +7 -0
- package/dist/table.js +26 -0
- package/dist/tools/setup.d.ts +1 -0
- package/dist/tools/setup.js +6 -0
- package/dist/transaction-context.d.ts +52 -0
- package/dist/transaction-context.js +62 -0
- package/dist/types.d.ts +151 -0
- package/dist/types.js +23 -0
- package/dist/unique-audit.d.ts +60 -0
- package/dist/unique-audit.js +174 -0
- package/dist/utils.d.ts +189 -0
- package/dist/utils.js +163 -0
- package/dist/uuid-columns.d.ts +22 -0
- package/dist/uuid-columns.js +68 -0
- package/dist/validators.d.ts +26 -0
- package/dist/validators.js +122 -0
- package/package.json +11 -11
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { createQueryBuilder, setConfig } from '@stacksjs/query-builder';
|
|
2
|
+
export declare function acquireDbConfigLock(): Promise<() => void>;
|
|
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
|
+
declare function getDb(): ReturnType<typeof createQueryBuilder>;
|
|
7
|
+
/**
|
|
8
|
+
* Lazy proxy for the query builder - connection is only made when first used.
|
|
9
|
+
* This is the main entry point for database operations.
|
|
10
|
+
*/
|
|
11
|
+
export declare const db: Proxy;
|
|
12
|
+
/**
|
|
13
|
+
* Fluent chain returned by entry-point methods like `selectFrom`/`updateTable`.
|
|
14
|
+
*
|
|
15
|
+
* bun-query-builder marks legacy chain methods (e.g. `selectAll`, `whereILike`,
|
|
16
|
+
* `selectAllRelations`) as optional in its declarations even though they're
|
|
17
|
+
* always present at runtime. Re-typing them here avoids forcing every call
|
|
18
|
+
* site to use `?.()` or `!` on the chain.
|
|
19
|
+
*
|
|
20
|
+
* Returns are typed as `any` deliberately — typing each variant precisely
|
|
21
|
+
* would re-introduce the optional methods, and we already lose strict column
|
|
22
|
+
* typing one step into a chain (the underlying query builder is constructed
|
|
23
|
+
* with no schema). Tests cover the runtime semantics.
|
|
24
|
+
*/
|
|
25
|
+
export declare interface FluentChain {
|
|
26
|
+
where(callback: (eb: import('./types').StacksExpressionBuilder) => unknown): FluentChain
|
|
27
|
+
where(...args: any[]): FluentChain
|
|
28
|
+
whereNull: (...args: any[]) => FluentChain
|
|
29
|
+
whereNotNull: (...args: any[]) => FluentChain
|
|
30
|
+
whereIn: (...args: any[]) => FluentChain
|
|
31
|
+
whereNotIn: (...args: any[]) => FluentChain
|
|
32
|
+
whereLike: (...args: any[]) => FluentChain
|
|
33
|
+
whereNotLike: (...args: any[]) => FluentChain
|
|
34
|
+
whereILike: (...args: any[]) => FluentChain
|
|
35
|
+
whereNotILike: (...args: any[]) => FluentChain
|
|
36
|
+
whereBetween: (...args: any[]) => FluentChain
|
|
37
|
+
whereNotBetween: (...args: any[]) => FluentChain
|
|
38
|
+
whereRaw: (...args: any[]) => FluentChain
|
|
39
|
+
whereColumn: (...args: any[]) => FluentChain
|
|
40
|
+
orWhere: (...args: any[]) => FluentChain
|
|
41
|
+
orWhereNull: (...args: any[]) => FluentChain
|
|
42
|
+
orWhereNotNull: (...args: any[]) => FluentChain
|
|
43
|
+
orWhereIn: (...args: any[]) => FluentChain
|
|
44
|
+
orWhereNotIn: (...args: any[]) => FluentChain
|
|
45
|
+
orWhereLike: (...args: any[]) => FluentChain
|
|
46
|
+
orWhereNotLike: (...args: any[]) => FluentChain
|
|
47
|
+
orWhereILike: (...args: any[]) => FluentChain
|
|
48
|
+
orWhereColumn: (...args: any[]) => FluentChain
|
|
49
|
+
andWhere: (...args: any[]) => FluentChain
|
|
50
|
+
having: (...args: any[]) => FluentChain
|
|
51
|
+
groupBy: (...args: any[]) => FluentChain
|
|
52
|
+
orderBy: (...args: any[]) => FluentChain
|
|
53
|
+
limit: (...args: any[]) => FluentChain
|
|
54
|
+
offset: (...args: any[]) => FluentChain
|
|
55
|
+
select(selection: ((eb: import('./types').StacksExpressionBuilder) => unknown) | ReadonlyArray<string | ((eb: import('./types').StacksExpressionBuilder) => unknown) | unknown>): FluentChain
|
|
56
|
+
select(...args: any[]): FluentChain
|
|
57
|
+
selectAll: () => FluentChain
|
|
58
|
+
selectAllRelations: () => FluentChain
|
|
59
|
+
selectRaw: (...args: any[]) => FluentChain
|
|
60
|
+
distinct: () => FluentChain
|
|
61
|
+
distinctOn: (...args: any[]) => FluentChain
|
|
62
|
+
innerJoin: (...args: any[]) => FluentChain
|
|
63
|
+
leftJoin: (...args: any[]) => FluentChain
|
|
64
|
+
rightJoin: (...args: any[]) => FluentChain
|
|
65
|
+
fullJoin: (...args: any[]) => FluentChain
|
|
66
|
+
crossJoin: (...args: any[]) => FluentChain
|
|
67
|
+
with: (...args: any[]) => FluentChain
|
|
68
|
+
union: (...args: any[]) => FluentChain
|
|
69
|
+
unionAll: (...args: any[]) => FluentChain
|
|
70
|
+
values: (...args: any[]) => FluentChain
|
|
71
|
+
set: (...args: any[]) => FluentChain
|
|
72
|
+
returning: (...args: any[]) => FluentChain
|
|
73
|
+
returningAll: () => FluentChain
|
|
74
|
+
onConflict: (...args: any[]) => FluentChain
|
|
75
|
+
onDuplicateKeyUpdate: (...args: any[]) => FluentChain
|
|
76
|
+
onConflictDoNothing: (...args: any[]) => FluentChain
|
|
77
|
+
onDuplicateKeyIgnore: () => FluentChain
|
|
78
|
+
forUpdate: () => FluentChain
|
|
79
|
+
forShare: () => FluentChain
|
|
80
|
+
toSQL: () => string
|
|
81
|
+
execute: () => Promise<any>
|
|
82
|
+
executeTakeFirst: () => Promise<any>
|
|
83
|
+
executeTakeFirstOrThrow: () => Promise<any>
|
|
84
|
+
pluck: (...args: any[]) => Promise<any>
|
|
85
|
+
count: (...args: any[]) => Promise<number>
|
|
86
|
+
sum: (...args: any[]) => Promise<number>
|
|
87
|
+
avg: (...args: any[]) => Promise<number>
|
|
88
|
+
min: (...args: any[]) => Promise<any>
|
|
89
|
+
max: (...args: any[]) => Promise<any>
|
|
90
|
+
exists: () => Promise<boolean>
|
|
91
|
+
doesntExist: () => Promise<boolean>
|
|
92
|
+
$call: (callback: (query: FluentChain) => FluentChain) => FluentChain
|
|
93
|
+
[key: string]: any
|
|
94
|
+
}
|
|
95
|
+
/*.ts` and
|
|
96
|
+
* emits `database/types.d.ts` containing:
|
|
97
|
+
*
|
|
98
|
+
* ```ts
|
|
99
|
+
* declare module '@stacksjs/database' {
|
|
100
|
+
* interface DatabaseSchema {
|
|
101
|
+
* court_houses: { columns: { id: number; name: string; ... } }
|
|
102
|
+
* judges: { columns: { id: number; name: string; court_id: number; ... } }
|
|
103
|
+
* }
|
|
104
|
+
* }
|
|
105
|
+
* ```
|
|
106
|
+
*
|
|
107
|
+
* Once that file is loaded into the TS project, `db.selectFrom('co|')`
|
|
108
|
+
* autocompletes to known table names. Apps without a generated file
|
|
109
|
+
* still compile — the `(string & {})` branch on `TableName` keeps the
|
|
110
|
+
* type as a literal-union+escape-hatch, so any string is accepted
|
|
111
|
+
* but known keys are surfaced first by the language server.
|
|
112
|
+
*/
|
|
113
|
+
// eslint-disable-next-line ts/no-empty-object-type
|
|
114
|
+
export declare interface DatabaseSchema {}
|
|
115
|
+
declare interface Db extends Pick<Required<RawQueryBuilder>, GenericPassthroughKeys> {
|
|
116
|
+
fn: import('./types').ExpressionFunctions
|
|
117
|
+
selectFrom: (table: TableName) => FluentChain
|
|
118
|
+
insertInto: (table: TableName) => FluentChain
|
|
119
|
+
updateTable: (table: TableName) => FluentChain
|
|
120
|
+
deleteFrom: (table: TableName) => FluentChain
|
|
121
|
+
table: (table: TableName) => FluentChain
|
|
122
|
+
selectFromSub: (sub: any, alias: string) => FluentChain
|
|
123
|
+
select: (table: TableName, ...columns: string[]) => FluentChain
|
|
124
|
+
unsafe: (query: string, params?: any[]) => UnsafeReturn
|
|
125
|
+
}
|
|
126
|
+
// The bun-query-builder types `unsafe()` as returning `Promise<any>`, but at
|
|
127
|
+
// runtime it returns a Bun SQL Statement that has `.execute()`. This interface
|
|
128
|
+
// corrects the return type so callers can chain `.execute()` without type errors.
|
|
129
|
+
declare type UnsafeReturn = Promise<any> & { execute: () => Promise<any> }
|
|
130
|
+
/**
|
|
131
|
+
* Top-level surface of the lazy `db` proxy. Methods that return a chainable
|
|
132
|
+
* builder are typed as `FluentChain` to flatten the optional-method noise
|
|
133
|
+
* inherent in bun-query-builder's declarations. Methods that introduce their
|
|
134
|
+
* own generics (`transaction<T>`, etc.) are kept as their original signatures
|
|
135
|
+
* via the underlying QueryBuilder type so call-site inference still works.
|
|
136
|
+
*/
|
|
137
|
+
declare type RawQueryBuilder = ReturnType<typeof createQueryBuilder>;
|
|
138
|
+
declare type GenericPassthroughKeys = | 'transaction'
|
|
139
|
+
| 'savepoint'
|
|
140
|
+
| 'beginDistributed'
|
|
141
|
+
| 'transactional'
|
|
142
|
+
| 'configure'
|
|
143
|
+
| 'reserve'
|
|
144
|
+
| 'commitDistributed'
|
|
145
|
+
| 'rollbackDistributed'
|
|
146
|
+
| 'setTransactionDefaults'
|
|
147
|
+
| 'close'
|
|
148
|
+
| 'listen'
|
|
149
|
+
| 'unlisten'
|
|
150
|
+
| 'notify'
|
|
151
|
+
| 'copyTo'
|
|
152
|
+
| 'copyFrom'
|
|
153
|
+
| 'ping'
|
|
154
|
+
| 'waitForReady'
|
|
155
|
+
| 'count'
|
|
156
|
+
| 'sum'
|
|
157
|
+
| 'avg'
|
|
158
|
+
| 'min'
|
|
159
|
+
| 'max'
|
|
160
|
+
| 'insertOrIgnore'
|
|
161
|
+
| 'insertGetId'
|
|
162
|
+
| 'updateOrInsert'
|
|
163
|
+
| 'upsert'
|
|
164
|
+
| 'create'
|
|
165
|
+
| 'createMany'
|
|
166
|
+
| 'sql'
|
|
167
|
+
| 'raw'
|
|
168
|
+
| 'simple'
|
|
169
|
+
| 'file';
|
|
170
|
+
/**
|
|
171
|
+
* Accept either a registered table name (from augmented
|
|
172
|
+
* `DatabaseSchema`) for autocomplete, or any other string for apps
|
|
173
|
+
* that haven't generated types yet / tables not in a model file.
|
|
174
|
+
*
|
|
175
|
+
* The `(string & {})` branch prevents TS from collapsing the union
|
|
176
|
+
* back to `string` and losing the autocomplete narrowing — a
|
|
177
|
+
* well-documented LiteralUnion trick.
|
|
178
|
+
*/
|
|
179
|
+
// eslint-disable-next-line ts/no-empty-object-type
|
|
180
|
+
export type TableName = (keyof DatabaseSchema & string) | (string & {});
|
|
181
|
+
// SQLite bootstrap pragmas (stacksjs/stacks#1951) now live in
|
|
182
|
+
// @stacksjs/query-builder — the one chokepoint every framework
|
|
183
|
+
// query-builder instance is created through — so EVERY fresh sqlite
|
|
184
|
+
// connection gets `foreign_keys = ON`, including builders created outside
|
|
185
|
+
// this module (e.g. the ORM auto-CRUD routes). Re-exported here for
|
|
186
|
+
// backwards compatibility with existing imports.
|
|
187
|
+
export { applySqlitePragmas, SQLITE_BOOTSTRAP_PRAGMAS } from '@stacksjs/query-builder';
|
|
188
|
+
// Export setConfig if available
|
|
189
|
+
export { setConfig };
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { createQueryBuilder, setConfig } from "@stacksjs/query-builder";
|
|
3
|
+
import { env as envVars } from "@stacksjs/env";
|
|
4
|
+
import { getConnectionDefaults } from "./defaults";
|
|
5
|
+
const sqliteDefaults = getConnectionDefaults("sqlite", envVars), mysqlDefaults = getConnectionDefaults("mysql", envVars), postgresDefaults = getConnectionDefaults("postgres", envVars);
|
|
6
|
+
let appEnv = envVars.APP_ENV || "local", dbDriver = envVars.DB_CONNECTION || "sqlite", dbConfig = {
|
|
7
|
+
connections: {
|
|
8
|
+
sqlite: { database: sqliteDefaults.database, prefix: "" },
|
|
9
|
+
mysql: { name: mysqlDefaults.database, host: mysqlDefaults.host, username: mysqlDefaults.username, password: mysqlDefaults.password, port: mysqlDefaults.port, prefix: "" },
|
|
10
|
+
singlestore: { name: mysqlDefaults.database, host: mysqlDefaults.host, username: mysqlDefaults.username, password: mysqlDefaults.password, port: mysqlDefaults.port, prefix: "" },
|
|
11
|
+
postgres: { name: postgresDefaults.database, host: postgresDefaults.host, username: postgresDefaults.username, password: postgresDefaults.password, port: postgresDefaults.port, prefix: "" }
|
|
12
|
+
}
|
|
13
|
+
}, dbConfigLockTail = Promise.resolve();
|
|
14
|
+
export function acquireDbConfigLock() {
|
|
15
|
+
let release = () => {};
|
|
16
|
+
const held = new Promise((resolve) => {
|
|
17
|
+
release = resolve;
|
|
18
|
+
}), acquired = dbConfigLockTail.then(() => release);
|
|
19
|
+
dbConfigLockTail = dbConfigLockTail.then(() => held);
|
|
20
|
+
return acquired;
|
|
21
|
+
}
|
|
22
|
+
export function initializeDbConfig(config) {
|
|
23
|
+
if (config?.app?.env)
|
|
24
|
+
appEnv = config.app.env;
|
|
25
|
+
if (config?.database?.default)
|
|
26
|
+
dbDriver = config.database.default;
|
|
27
|
+
if (config?.database)
|
|
28
|
+
dbConfig = config.database;
|
|
29
|
+
updateQueryBuilderConfig();
|
|
30
|
+
_dbInstance = null;
|
|
31
|
+
}
|
|
32
|
+
function getEnv() {
|
|
33
|
+
return appEnv;
|
|
34
|
+
}
|
|
35
|
+
function getDriver() {
|
|
36
|
+
return dbDriver;
|
|
37
|
+
}
|
|
38
|
+
function getDatabaseConfig() {
|
|
39
|
+
return dbConfig;
|
|
40
|
+
}
|
|
41
|
+
function getDialect() {
|
|
42
|
+
const driver = getDriver();
|
|
43
|
+
if (driver === "sqlite")
|
|
44
|
+
return "sqlite";
|
|
45
|
+
if (driver === "mysql")
|
|
46
|
+
return "mysql";
|
|
47
|
+
if (driver === "singlestore")
|
|
48
|
+
return "singlestore";
|
|
49
|
+
if (driver === "postgres")
|
|
50
|
+
return "postgres";
|
|
51
|
+
return "sqlite";
|
|
52
|
+
}
|
|
53
|
+
function getDbConfig() {
|
|
54
|
+
const driver = getDriver(), database = getDatabaseConfig(), env = getEnv();
|
|
55
|
+
if (driver === "sqlite") {
|
|
56
|
+
const defaultName = env !== "testing" ? "database/stacks.sqlite" : "database/stacks_testing.sqlite";
|
|
57
|
+
return {
|
|
58
|
+
database: database.connections?.sqlite?.database ?? defaultName
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
if (driver === "mysql")
|
|
62
|
+
return {
|
|
63
|
+
database: database.connections?.mysql?.name || "stacks",
|
|
64
|
+
host: database.connections?.mysql?.host ?? "127.0.0.1",
|
|
65
|
+
username: database.connections?.mysql?.username ?? "root",
|
|
66
|
+
password: database.connections?.mysql?.password ?? "",
|
|
67
|
+
port: database.connections?.mysql?.port ?? 3306
|
|
68
|
+
};
|
|
69
|
+
if (driver === "singlestore")
|
|
70
|
+
return {
|
|
71
|
+
database: database.connections?.singlestore?.name || "stacks",
|
|
72
|
+
host: database.connections?.singlestore?.host ?? "127.0.0.1",
|
|
73
|
+
username: database.connections?.singlestore?.username ?? "root",
|
|
74
|
+
password: database.connections?.singlestore?.password ?? "",
|
|
75
|
+
port: database.connections?.singlestore?.port ?? 3306
|
|
76
|
+
};
|
|
77
|
+
if (driver === "postgres") {
|
|
78
|
+
const dbName = database.connections?.postgres?.name ?? "stacks";
|
|
79
|
+
return {
|
|
80
|
+
database: env === "testing" ? `${dbName}_testing` : dbName,
|
|
81
|
+
host: database.connections?.postgres?.host ?? "127.0.0.1",
|
|
82
|
+
username: database.connections?.postgres?.username ?? "",
|
|
83
|
+
password: database.connections?.postgres?.password ?? "",
|
|
84
|
+
port: database.connections?.postgres?.port ?? 5432
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
return { database: ":memory:" };
|
|
88
|
+
}
|
|
89
|
+
function updateQueryBuilderConfig() {
|
|
90
|
+
const dialect = getDialect(), dbConfigForQb = getDbConfig();
|
|
91
|
+
setConfig({
|
|
92
|
+
dialect,
|
|
93
|
+
database: dbConfigForQb,
|
|
94
|
+
verbose: getEnv() !== "production",
|
|
95
|
+
timestamps: {
|
|
96
|
+
createdAt: "created_at",
|
|
97
|
+
updatedAt: "updated_at",
|
|
98
|
+
defaultOrderColumn: "created_at"
|
|
99
|
+
},
|
|
100
|
+
softDeletes: {
|
|
101
|
+
enabled: !0,
|
|
102
|
+
column: "deleted_at",
|
|
103
|
+
defaultFilter: !0
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
updateQueryBuilderConfig();
|
|
108
|
+
let _dbInstance = null, _configInitPromise = null;
|
|
109
|
+
function ensureConfigLoaded() {
|
|
110
|
+
if (!_configInitPromise)
|
|
111
|
+
_configInitPromise = (async () => {
|
|
112
|
+
try {
|
|
113
|
+
const { config, overridesReady } = await import("@stacksjs/config");
|
|
114
|
+
await overridesReady;
|
|
115
|
+
if (config) {
|
|
116
|
+
initializeDbConfig(config);
|
|
117
|
+
_dbInstance = null;
|
|
118
|
+
}
|
|
119
|
+
} catch {}
|
|
120
|
+
})();
|
|
121
|
+
return _configInitPromise;
|
|
122
|
+
}
|
|
123
|
+
export async function ensureDatabaseConfigLoaded() {
|
|
124
|
+
await ensureConfigLoaded();
|
|
125
|
+
}
|
|
126
|
+
export { applySqlitePragmas, SQLITE_BOOTSTRAP_PRAGMAS } from "@stacksjs/query-builder";
|
|
127
|
+
const sqliteTxOwner = new AsyncLocalStorage;
|
|
128
|
+
let sqliteTxTail = Promise.resolve();
|
|
129
|
+
function serializeSqliteTransaction(run) {
|
|
130
|
+
if (sqliteTxOwner.getStore())
|
|
131
|
+
return run();
|
|
132
|
+
const result = sqliteTxTail.then(() => sqliteTxOwner.run(!0, run));
|
|
133
|
+
sqliteTxTail = result.then(() => {
|
|
134
|
+
return;
|
|
135
|
+
}, () => {
|
|
136
|
+
return;
|
|
137
|
+
});
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
function applySqliteTransactionSerialization(instance) {
|
|
141
|
+
const original = instance.transaction.bind(instance);
|
|
142
|
+
instance.transaction = (...args) => serializeSqliteTransaction(() => original(...args));
|
|
143
|
+
}
|
|
144
|
+
function getDb() {
|
|
145
|
+
if (!_dbInstance) {
|
|
146
|
+
updateQueryBuilderConfig();
|
|
147
|
+
_dbInstance = createQueryBuilder();
|
|
148
|
+
if (getDialect() === "sqlite")
|
|
149
|
+
applySqliteTransactionSerialization(_dbInstance);
|
|
150
|
+
}
|
|
151
|
+
return _dbInstance;
|
|
152
|
+
}
|
|
153
|
+
ensureConfigLoaded();
|
|
154
|
+
export const db = new Proxy({}, {
|
|
155
|
+
get(_target, prop) {
|
|
156
|
+
const instance = getDb(), value = instance[prop];
|
|
157
|
+
if (typeof value === "function")
|
|
158
|
+
return value.bind(instance);
|
|
159
|
+
return value;
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
export { setConfig };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { sqlHelpers } from './sql-helpers';
|
|
2
|
+
/** Pure builder so tests can assert per-dialect DDL without a live DB. */
|
|
3
|
+
export declare function uuidColumnSql(table: string, sql: SqlHelpers): string;
|
|
4
|
+
/**
|
|
5
|
+
* Resolve every table backing a model with `useUuid: true`, across both
|
|
6
|
+
* userland (`app/Models`) and framework-default (`defaults/app/Models`)
|
|
7
|
+
* model directories. Exported so tests (and `doctor`-style diagnostics) can
|
|
8
|
+
* inspect the resolved set without touching a live database.
|
|
9
|
+
*/
|
|
10
|
+
export declare function findUuidTables(): Promise<string[]>;
|
|
11
|
+
/**
|
|
12
|
+
* Guarantee-ALTER `uuid` onto every table whose model declares
|
|
13
|
+
* `useUuid: true`, independently try/catch-swallowed per table so one
|
|
14
|
+
* already-having-the-column (or not-yet-existing) table never skips the
|
|
15
|
+
* rest. Exported so `buddy migrate`/`migrate:fresh` can call it after model
|
|
16
|
+
* migrations run, same pattern as {@link ensureUsersAuthColumns} — see the
|
|
17
|
+
* call sites in buddy/src/commands/migrate.ts.
|
|
18
|
+
*/
|
|
19
|
+
export declare function ensureUuidColumns(sql: SqlHelpers, options?: { verbose?: boolean }): Promise<void>;
|
|
20
|
+
/** Convenience wrapper resolving dialect helpers from `DB_CONNECTION`, for call sites that don't already have a `SqlHelpers` instance. */
|
|
21
|
+
export declare function ensureUuidColumnsForCurrentDriver(options?: { verbose?: boolean }): Promise<void>;
|
|
22
|
+
declare type SqlHelpers = ReturnType<typeof sqlHelpers>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
import { log } from "@stacksjs/logging";
|
|
3
|
+
import { path } from "@stacksjs/path";
|
|
4
|
+
import { getModelName, getTableName } from "@stacksjs/orm";
|
|
5
|
+
import { fs } from "@stacksjs/storage";
|
|
6
|
+
import { db } from "./utils";
|
|
7
|
+
import { sqlHelpers } from "./sql-helpers";
|
|
8
|
+
function getDbDriver() {
|
|
9
|
+
return process.env.DB_CONNECTION || "sqlite";
|
|
10
|
+
}
|
|
11
|
+
function uuidColumnType(sql) {
|
|
12
|
+
if (sql.isPostgres)
|
|
13
|
+
return "UUID";
|
|
14
|
+
if (sql.isMysql)
|
|
15
|
+
return "VARCHAR(255)";
|
|
16
|
+
return "TEXT";
|
|
17
|
+
}
|
|
18
|
+
export function uuidColumnSql(table, sql) {
|
|
19
|
+
return `ALTER TABLE ${table} ADD COLUMN uuid ${uuidColumnType(sql)}`;
|
|
20
|
+
}
|
|
21
|
+
async function loadModelsFrom(dir) {
|
|
22
|
+
const out = [];
|
|
23
|
+
if (!fs.existsSync(dir))
|
|
24
|
+
return out;
|
|
25
|
+
const entries = fs.readdirSync(dir, { withFileTypes: !0 });
|
|
26
|
+
for (const entry of entries) {
|
|
27
|
+
const fullPath = path.join(dir, entry.name);
|
|
28
|
+
if (entry.isDirectory()) {
|
|
29
|
+
out.push(...await loadModelsFrom(fullPath));
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (!entry.name.endsWith(".ts"))
|
|
33
|
+
continue;
|
|
34
|
+
if (entry.name.startsWith("_") || entry.name.startsWith("index"))
|
|
35
|
+
continue;
|
|
36
|
+
try {
|
|
37
|
+
const imported = (await import(fullPath)).default;
|
|
38
|
+
if (imported?.name || imported?.table)
|
|
39
|
+
out.push({ filePath: fullPath, model: imported });
|
|
40
|
+
} catch {}
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
export async function findUuidTables() {
|
|
45
|
+
const dirs = [path.userModelsPath(), path.frameworkPath("defaults/app/Models")], tables = new Set;
|
|
46
|
+
for (const dir of dirs)
|
|
47
|
+
for (const { filePath, model } of await loadModelsFrom(dir)) {
|
|
48
|
+
if (!model.traits?.useUuid)
|
|
49
|
+
continue;
|
|
50
|
+
tables.add(getTableName(model, filePath));
|
|
51
|
+
}
|
|
52
|
+
return [...tables];
|
|
53
|
+
}
|
|
54
|
+
export async function ensureUuidColumns(sql, options = {}) {
|
|
55
|
+
const tables = await findUuidTables();
|
|
56
|
+
for (const table of tables)
|
|
57
|
+
try {
|
|
58
|
+
await db.unsafe(uuidColumnSql(table, sql)).execute();
|
|
59
|
+
if (options.verbose)
|
|
60
|
+
log.debug(`[uuid-columns] Added uuid column to ${table}`);
|
|
61
|
+
} catch {
|
|
62
|
+
if (options.verbose)
|
|
63
|
+
log.debug(`[uuid-columns] Skipped (already applied or ${table} missing): uuid column`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export async function ensureUuidColumnsForCurrentDriver(options = {}) {
|
|
67
|
+
await ensureUuidColumns(sqlHelpers(getDbDriver()), options);
|
|
68
|
+
}
|
|
@@ -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;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
export function isStringValidator(v) {
|
|
2
|
+
return v.name === "string";
|
|
3
|
+
}
|
|
4
|
+
export function isNumberValidator(v) {
|
|
5
|
+
return v.name === "number";
|
|
6
|
+
}
|
|
7
|
+
export function enumValidator(v) {
|
|
8
|
+
return v.name === "enum";
|
|
9
|
+
}
|
|
10
|
+
export function isBooleanValidator(v) {
|
|
11
|
+
return v.name === "boolean";
|
|
12
|
+
}
|
|
13
|
+
export function isDateValidator(v) {
|
|
14
|
+
return v.name === "date";
|
|
15
|
+
}
|
|
16
|
+
export function isUnixValidator(v) {
|
|
17
|
+
return v.name === "unix";
|
|
18
|
+
}
|
|
19
|
+
export function isFloatValidator(v) {
|
|
20
|
+
return v.name === "float";
|
|
21
|
+
}
|
|
22
|
+
export function isDatetimeValidator(v) {
|
|
23
|
+
return v.name === "datetime";
|
|
24
|
+
}
|
|
25
|
+
export function isTimestampValidator(v) {
|
|
26
|
+
return v.name === "timestamp";
|
|
27
|
+
}
|
|
28
|
+
export function isTimestampTzValidator(v) {
|
|
29
|
+
return v.name === "timestampTz";
|
|
30
|
+
}
|
|
31
|
+
export function isDecimalValidator(v) {
|
|
32
|
+
return v.name === "decimal";
|
|
33
|
+
}
|
|
34
|
+
export function isSmallintValidator(v) {
|
|
35
|
+
return v.name === "smallint";
|
|
36
|
+
}
|
|
37
|
+
export function isIntegerValidator(v) {
|
|
38
|
+
return v.name === "integer";
|
|
39
|
+
}
|
|
40
|
+
export function isBigintValidator(v) {
|
|
41
|
+
return v.name === "bigint";
|
|
42
|
+
}
|
|
43
|
+
export function isBinaryValidator(v) {
|
|
44
|
+
return v.name === "binary";
|
|
45
|
+
}
|
|
46
|
+
export function isBlobValidator(v) {
|
|
47
|
+
return v.name === "blob";
|
|
48
|
+
}
|
|
49
|
+
export function isJsonValidator(v) {
|
|
50
|
+
return v.name === "json";
|
|
51
|
+
}
|
|
52
|
+
export function checkValidator(validator, driver) {
|
|
53
|
+
if (enumValidator(validator))
|
|
54
|
+
return prepareEnumColumnType(validator, driver);
|
|
55
|
+
if (isStringValidator(validator))
|
|
56
|
+
return prepareTextColumnType(validator, driver);
|
|
57
|
+
if (isNumberValidator(validator))
|
|
58
|
+
return prepareNumberColumnType(validator, driver);
|
|
59
|
+
if (isBooleanValidator(validator))
|
|
60
|
+
return "'boolean'";
|
|
61
|
+
if (isDateValidator(validator))
|
|
62
|
+
return "'date'";
|
|
63
|
+
if (isDatetimeValidator(validator))
|
|
64
|
+
return "'datetime'";
|
|
65
|
+
if (isUnixValidator(validator))
|
|
66
|
+
return "'bigint'";
|
|
67
|
+
if (isTimestampValidator(validator))
|
|
68
|
+
return "'timestamp'";
|
|
69
|
+
if (isTimestampTzValidator(validator))
|
|
70
|
+
return "'timestamp'";
|
|
71
|
+
if (isFloatValidator(validator))
|
|
72
|
+
return "'float'";
|
|
73
|
+
if (isSmallintValidator(validator))
|
|
74
|
+
return "'smallint'";
|
|
75
|
+
if (isDecimalValidator(validator))
|
|
76
|
+
return "'decimal'";
|
|
77
|
+
if (isIntegerValidator(validator))
|
|
78
|
+
return "'integer'";
|
|
79
|
+
if (isBigintValidator(validator))
|
|
80
|
+
return "'bigint'";
|
|
81
|
+
if (isBinaryValidator(validator))
|
|
82
|
+
return "'binary'";
|
|
83
|
+
return "";
|
|
84
|
+
}
|
|
85
|
+
export function prepareNumberColumnType(validator, driver = "mysql") {
|
|
86
|
+
if (driver === "sqlite")
|
|
87
|
+
return "'integer'";
|
|
88
|
+
if ("getRules" in validator) {
|
|
89
|
+
const minRule = validator.getRules().find((rule) => rule.name === "min"), maxRule = validator.getRules().find((rule) => rule.name === "max"), min = minRule?.params?.min ?? -2147483648, max = maxRule?.params?.max ?? 2147483647;
|
|
90
|
+
return min >= -2147483648 && max <= 2147483647 ? "'integer'" : "'bigint'";
|
|
91
|
+
}
|
|
92
|
+
return "'integer'";
|
|
93
|
+
}
|
|
94
|
+
export function prepareEnumColumnType(validator, driver = "mysql") {
|
|
95
|
+
const allowedValues = validator.getAllowedValues();
|
|
96
|
+
if (!allowedValues)
|
|
97
|
+
throw Error("Enum rule found but no allowedValues defined");
|
|
98
|
+
const enumStructure = allowedValues.map((value) => `'${value}'`).join(", ");
|
|
99
|
+
if (driver === "sqlite")
|
|
100
|
+
return "'text'";
|
|
101
|
+
return `sql\`enum(${enumStructure})\``;
|
|
102
|
+
}
|
|
103
|
+
export function prepareTextColumnType(validator, driver = "mysql") {
|
|
104
|
+
if (driver === "sqlite")
|
|
105
|
+
return "'text'";
|
|
106
|
+
return `'varchar(${findCharacterLength(validator)})'`;
|
|
107
|
+
}
|
|
108
|
+
export function prepareDateTimeColumnType(validator, driver = "mysql") {
|
|
109
|
+
if (driver === "sqlite")
|
|
110
|
+
return "'text'";
|
|
111
|
+
const name = validator.name;
|
|
112
|
+
if (name === "unix")
|
|
113
|
+
return "'bigint'";
|
|
114
|
+
return name || "date";
|
|
115
|
+
}
|
|
116
|
+
export function findCharacterLength(validator) {
|
|
117
|
+
if ("getRules" in validator) {
|
|
118
|
+
const maxLengthRule = validator.getRules().find((rule) => rule.name === "max");
|
|
119
|
+
return maxLengthRule?.params?.length || maxLengthRule?.params?.max || 255;
|
|
120
|
+
}
|
|
121
|
+
return 255;
|
|
122
|
+
}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/database",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.70.
|
|
5
|
+
"version": "0.70.91",
|
|
6
6
|
"description": "The Stacks database integration.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -55,18 +55,18 @@
|
|
|
55
55
|
"prepublishOnly": "bun run build"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"bun-query-builder": "^0.1.
|
|
58
|
+
"bun-query-builder": "^0.1.50"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
|
-
"@stacksjs/cli": "0.70.
|
|
62
|
-
"@stacksjs/config": "0.70.
|
|
63
|
-
"@stacksjs/logging": "0.70.
|
|
64
|
-
"@stacksjs/router": "0.70.
|
|
61
|
+
"@stacksjs/cli": "0.70.91",
|
|
62
|
+
"@stacksjs/config": "0.70.91",
|
|
63
|
+
"@stacksjs/logging": "0.70.91",
|
|
64
|
+
"@stacksjs/router": "0.70.91",
|
|
65
65
|
"better-dx": "^0.2.16",
|
|
66
|
-
"@stacksjs/path": "0.70.
|
|
67
|
-
"@stacksjs/query-builder": "0.70.
|
|
68
|
-
"@stacksjs/storage": "0.70.
|
|
69
|
-
"@stacksjs/strings": "0.70.
|
|
70
|
-
"@stacksjs/utils": "0.70.
|
|
66
|
+
"@stacksjs/path": "0.70.91",
|
|
67
|
+
"@stacksjs/query-builder": "0.70.91",
|
|
68
|
+
"@stacksjs/storage": "0.70.91",
|
|
69
|
+
"@stacksjs/strings": "0.70.91",
|
|
70
|
+
"@stacksjs/utils": "0.70.91"
|
|
71
71
|
}
|
|
72
72
|
}
|