@stacksjs/database 0.72.22 → 0.72.23

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.
Files changed (3) hide show
  1. package/dist/utils.d.ts +183 -111
  2. package/dist/utils.js +1 -1
  3. package/package.json +10 -10
package/dist/utils.d.ts CHANGED
@@ -4,9 +4,7 @@ export declare function acquireDbConfigLock(): Promise<() => void>;
4
4
  // Function to initialize the config when it's available
5
5
  export declare function initializeDbConfig(config: any): void;
6
6
  export declare function createDatabaseQueryHooks(dispatch: (event: DatabaseQueryLogEvent) => void | Promise<void>): QueryHooks;
7
- declare function ensureConfigLoaded(): Promise<void>;
8
7
  export declare function ensureDatabaseConfigLoaded(): Promise<void>;
9
- declare function getDb(): ReturnType<typeof createQueryBuilder>;
10
8
  /**
11
9
  * Discard every cached database client after the underlying query-builder
12
10
  * connection is reset.
@@ -19,24 +17,46 @@ declare function getDb(): ReturnType<typeof createQueryBuilder>;
19
17
  */
20
18
  export declare function resetDatabaseConnection(): void;
21
19
  /**
22
- * The builder a read should use right now.
20
+ * Fluent chain returned by entry-point methods like `selectFrom`/`updateTable`.
23
21
  *
24
- * Falls back to the primary whenever routing is not permitted — no
25
- * replicas configured, auto-routing off, inside a transaction, or after a
26
- * write in this async context. See `./replicas` for why each of those
27
- * carve-outs exists.
22
+ * bun-query-builder marks legacy chain methods (e.g. `selectAll`, `whereILike`,
23
+ * `selectAllRelations`) as optional in its declarations even though they're
24
+ * always present at runtime. Re-typing them here avoids forcing every call
25
+ * site to use `?.()` or `!` on the chain.
26
+ *
27
+ * **The chain carries its row type.** `TRow` comes from the augmented
28
+ * `DatabaseSchema` that `buddy generate:db-types` writes, so
29
+ * `db.selectFrom('users').executeTakeFirst()` answers a `users` row rather than
30
+ * `any` - and `select(['id', 'handle'])` narrows it to those two columns.
31
+ *
32
+ * Every terminal used to be `Promise<any>`, and the cost was not theoretical:
33
+ * an application on top of this ends up annotating every result `any` to say
34
+ * what it already knows, which is a thousand places the compiler has been told
35
+ * to stop looking. A row it cannot type is `Record<string, unknown>` instead -
36
+ * still a value the caller has to narrow, but one narrowing it is checked.
37
+ *
38
+ * A join or an aliased select list widens the row to
39
+ * `Record<string, unknown>`: the shape then depends on the aliases rather than
40
+ * on any one table, and claiming otherwise would be worse than not knowing.
28
41
  */
29
- declare function getReadDb(): ReturnType<typeof createQueryBuilder>;
30
42
  /**
31
- * Explicitly replica-routed handle: `db.read.selectFrom('users')`.
43
+ * State the shape of rows this package knows and the query cannot.
44
+ *
45
+ * A raw query answers `Record<string, unknown>` when the table is not in the
46
+ * generated `DatabaseSchema` - which is always true *inside* the framework,
47
+ * because an application's schema does not exist at framework build time. The
48
+ * package that ships the model does know the shape, and this is where that
49
+ * knowledge is written down: once, at the boundary, named and greppable.
32
50
  *
33
- * Unlike automatic routing this ignores `reads.autoRoute` asking for
34
- * `db.read` IS the statement that this particular query tolerates a stale
35
- * result. It still respects the transaction carve-out, because a read
36
- * inside a transaction must see that transaction's own writes no matter
37
- * how it was requested.
51
+ * It is an assertion, and deliberately an obvious one. What it replaces is a
52
+ * `Promise<any>` that spread the same claim silently through every caller.
53
+ *
54
+ * Application code should not need it: `buddy generate:db-types` gives `db` the
55
+ * real column types, and a row that arrives typed does not want asserting.
38
56
  */
39
- declare function getExplicitReadDb(): ReturnType<typeof createQueryBuilder>;
57
+ export declare function asRows<TRow>(rows: ReadonlyArray<Record<string, unknown>>): TRow[];
58
+ /** The single-row form of {@link asRows}. */
59
+ export declare function asRow<TRow>(row: Record<string, unknown> | undefined): TRow | undefined;
40
60
  /**
41
61
  * Update bun-query-builder configuration
42
62
  */
@@ -64,15 +84,11 @@ export declare const RAW_QUERY_SOFT_DELETE_CONFIG: {
64
84
  column: 'deleted_at';
65
85
  defaultFilter: true
66
86
  };
67
- /** Statements that mutate, for the read-your-writes tracking in `./replicas`. */
68
- declare const WRITE_ENTRY_POINTS: Set<any>;
69
- /** Reads that are candidates for replica routing. */
70
- declare const READ_ENTRY_POINTS: Set<any>;
71
87
  /**
72
88
  * Lazy proxy for the query builder - connection is only made when first used.
73
89
  * This is the main entry point for database operations.
74
90
  */
75
- export declare const db: Proxy;
91
+ export declare const db: Db;
76
92
  /**
77
93
  * Replica-routed handle exposed as `db.read`.
78
94
  *
@@ -80,7 +96,7 @@ export declare const db: Proxy;
80
96
  * available behind it (`db.read.selectFrom(...).where(...)`) without
81
97
  * re-declaring every chain entry point.
82
98
  */
83
- export declare const readDb: Proxy;
99
+ export declare const readDb: Omit<Db, 'read'>;
84
100
  export declare interface DatabaseQueryLogEvent {
85
101
  query: {
86
102
  sql: string
@@ -89,91 +105,79 @@ export declare interface DatabaseQueryLogEvent {
89
105
  queryDurationMillis: number
90
106
  error?: unknown
91
107
  }
92
- /**
93
- * Fluent chain returned by entry-point methods like `selectFrom`/`updateTable`.
94
- *
95
- * bun-query-builder marks legacy chain methods (e.g. `selectAll`, `whereILike`,
96
- * `selectAllRelations`) as optional in its declarations even though they're
97
- * always present at runtime. Re-typing them here avoids forcing every call
98
- * site to use `?.()` or `!` on the chain.
99
- *
100
- * Returns are typed as `any` deliberately — typing each variant precisely
101
- * would re-introduce the optional methods, and we already lose strict column
102
- * typing one step into a chain (the underlying query builder is constructed
103
- * with no schema). Tests cover the runtime semantics.
104
- */
105
- export declare interface FluentChain {
106
- where(callback: (eb: import('./types').StacksExpressionBuilder) => unknown): FluentChain
107
- where(...args: any[]): FluentChain
108
- whereNull: (...args: any[]) => FluentChain
109
- whereNotNull: (...args: any[]) => FluentChain
110
- whereIn: (...args: any[]) => FluentChain
111
- whereNotIn: (...args: any[]) => FluentChain
112
- whereLike: (...args: any[]) => FluentChain
113
- whereNotLike: (...args: any[]) => FluentChain
114
- whereILike: (...args: any[]) => FluentChain
115
- whereNotILike: (...args: any[]) => FluentChain
116
- whereBetween: (...args: any[]) => FluentChain
117
- whereNotBetween: (...args: any[]) => FluentChain
118
- whereRaw: (...args: any[]) => FluentChain
119
- whereColumn: (...args: any[]) => FluentChain
120
- orWhere: (...args: any[]) => FluentChain
121
- orWhereNull: (...args: any[]) => FluentChain
122
- orWhereNotNull: (...args: any[]) => FluentChain
123
- orWhereIn: (...args: any[]) => FluentChain
124
- orWhereNotIn: (...args: any[]) => FluentChain
125
- orWhereLike: (...args: any[]) => FluentChain
126
- orWhereNotLike: (...args: any[]) => FluentChain
127
- orWhereILike: (...args: any[]) => FluentChain
128
- orWhereColumn: (...args: any[]) => FluentChain
129
- andWhere: (...args: any[]) => FluentChain
130
- whereAny: (...args: any[]) => FluentChain
131
- whereAll: (...args: any[]) => FluentChain
132
- whereNone: (...args: any[]) => FluentChain
133
- having: (...args: any[]) => FluentChain
134
- groupBy: (...args: any[]) => FluentChain
135
- orderBy: (...args: any[]) => FluentChain
136
- limit: (...args: any[]) => FluentChain
137
- offset: (...args: any[]) => FluentChain
138
- select(selection: ((eb: import('./types').StacksExpressionBuilder) => unknown) | ReadonlyArray<string | ((eb: import('./types').StacksExpressionBuilder) => unknown) | unknown>): FluentChain
139
- select(...args: any[]): FluentChain
140
- selectAll: () => FluentChain
141
- selectAllRelations: () => FluentChain
142
- selectRaw: (...args: any[]) => FluentChain
143
- distinct: () => FluentChain
144
- distinctOn: (...args: any[]) => FluentChain
145
- innerJoin: (...args: any[]) => FluentChain
146
- leftJoin: (...args: any[]) => FluentChain
147
- rightJoin: (...args: any[]) => FluentChain
148
- fullJoin: (...args: any[]) => FluentChain
149
- crossJoin: (...args: any[]) => FluentChain
150
- with: (...args: any[]) => FluentChain
151
- union: (...args: any[]) => FluentChain
152
- unionAll: (...args: any[]) => FluentChain
153
- values: (...args: any[]) => FluentChain
154
- set: (...args: any[]) => FluentChain
155
- returning: (...args: any[]) => FluentChain
156
- returningAll: () => FluentChain
157
- onConflict: (...args: any[]) => FluentChain
158
- onDuplicateKeyUpdate: (...args: any[]) => FluentChain
159
- onConflictDoNothing: (...args: any[]) => FluentChain
160
- onDuplicateKeyIgnore: () => FluentChain
161
- forUpdate: () => FluentChain
162
- forShare: () => FluentChain
108
+ export declare interface BaseFluentChain<TRow = Record<string, unknown>, TKind extends ChainKind = 'select'> {
109
+ where(callback: (eb: import('./types').StacksExpressionBuilder) => unknown): FluentChain<TRow, TKind>
110
+ where(...args: unknown[]): FluentChain<TRow, TKind>
111
+ whereNull: (...args: unknown[]) => FluentChain<TRow, TKind>
112
+ whereNotNull: (...args: unknown[]) => FluentChain<TRow, TKind>
113
+ whereIn: (...args: unknown[]) => FluentChain<TRow, TKind>
114
+ whereNotIn: (...args: unknown[]) => FluentChain<TRow, TKind>
115
+ whereLike: (...args: unknown[]) => FluentChain<TRow, TKind>
116
+ whereNotLike: (...args: unknown[]) => FluentChain<TRow, TKind>
117
+ whereILike: (...args: unknown[]) => FluentChain<TRow, TKind>
118
+ whereNotILike: (...args: unknown[]) => FluentChain<TRow, TKind>
119
+ whereBetween: (...args: unknown[]) => FluentChain<TRow, TKind>
120
+ whereNotBetween: (...args: unknown[]) => FluentChain<TRow, TKind>
121
+ whereRaw: (...args: unknown[]) => FluentChain<TRow, TKind>
122
+ whereColumn: (...args: unknown[]) => FluentChain<TRow, TKind>
123
+ orWhere: (...args: unknown[]) => FluentChain<TRow, TKind>
124
+ orWhereNull: (...args: unknown[]) => FluentChain<TRow, TKind>
125
+ orWhereNotNull: (...args: unknown[]) => FluentChain<TRow, TKind>
126
+ orWhereIn: (...args: unknown[]) => FluentChain<TRow, TKind>
127
+ orWhereNotIn: (...args: unknown[]) => FluentChain<TRow, TKind>
128
+ orWhereLike: (...args: unknown[]) => FluentChain<TRow, TKind>
129
+ orWhereNotLike: (...args: unknown[]) => FluentChain<TRow, TKind>
130
+ orWhereILike: (...args: unknown[]) => FluentChain<TRow, TKind>
131
+ orWhereColumn: (...args: unknown[]) => FluentChain<TRow, TKind>
132
+ andWhere: (...args: unknown[]) => FluentChain<TRow, TKind>
133
+ whereAny: (...args: unknown[]) => FluentChain<TRow, TKind>
134
+ whereAll: (...args: unknown[]) => FluentChain<TRow, TKind>
135
+ whereNone: (...args: unknown[]) => FluentChain<TRow, TKind>
136
+ having: (...args: unknown[]) => FluentChain<TRow, TKind>
137
+ groupBy: (...args: unknown[]) => FluentChain<TRow, TKind>
138
+ orderBy: (...args: unknown[]) => FluentChain<TRow, TKind>
139
+ limit: (...args: unknown[]) => FluentChain<TRow, TKind>
140
+ offset: (...args: unknown[]) => FluentChain<TRow, TKind>
141
+ select<K extends KnownKeys<TRow> & string>(columns: readonly K[]): FluentChain<Pick<TRow, K>, TKind>
142
+ select(selection: ((eb: import('./types').StacksExpressionBuilder) => unknown) | ReadonlyArray<string | ((eb: import('./types').StacksExpressionBuilder) => unknown) | unknown>): FluentChain<Record<string, unknown>, TKind>
143
+ select(...args: unknown[]): FluentChain<Record<string, unknown>, TKind>
144
+ selectAll: () => FluentChain<TRow, TKind>
145
+ selectAllRelations: () => FluentChain<Record<string, unknown>, TKind>
146
+ selectRaw: (...args: unknown[]) => FluentChain<Record<string, unknown>, TKind>
147
+ distinct: () => FluentChain<TRow, TKind>
148
+ distinctOn: (...args: unknown[]) => FluentChain<TRow, TKind>
149
+ innerJoin: (...args: unknown[]) => FluentChain<Record<string, unknown>, TKind>
150
+ leftJoin: (...args: unknown[]) => FluentChain<Record<string, unknown>, TKind>
151
+ rightJoin: (...args: unknown[]) => FluentChain<Record<string, unknown>, TKind>
152
+ fullJoin: (...args: unknown[]) => FluentChain<Record<string, unknown>, TKind>
153
+ crossJoin: (...args: unknown[]) => FluentChain<Record<string, unknown>, TKind>
154
+ with: (...args: unknown[]) => FluentChain<Record<string, unknown>, TKind>
155
+ union: (...args: unknown[]) => FluentChain<Record<string, unknown>, TKind>
156
+ unionAll: (...args: unknown[]) => FluentChain<Record<string, unknown>, TKind>
157
+ values: (values: Partial<TRow> | ReadonlyArray<Partial<TRow>> | object | readonly object[]) => FluentChain<TRow, TKind>
158
+ set: (values: Partial<TRow> | object) => FluentChain<TRow, TKind>
159
+ returning<K extends KnownKeys<TRow> & string>(columns: readonly K[]): FluentChain<Pick<TRow, K>, 'returning'>
160
+ returning(...args: unknown[]): FluentChain<Record<string, unknown>, 'returning'>
161
+ returningAll: () => FluentChain<TRow, 'returning'>
162
+ onConflict: (...args: unknown[]) => FluentChain<TRow, TKind>
163
+ onDuplicateKeyUpdate: (...args: unknown[]) => FluentChain<TRow, TKind>
164
+ onConflictDoNothing: (...args: unknown[]) => FluentChain<TRow, TKind>
165
+ onDuplicateKeyIgnore: () => FluentChain<TRow, TKind>
166
+ forUpdate: () => FluentChain<TRow, TKind>
167
+ forShare: () => FluentChain<TRow, TKind>
163
168
  toSQL: () => string
164
- execute: () => Promise<any>
165
- executeTakeFirst: () => Promise<any>
166
- executeTakeFirstOrThrow: () => Promise<any>
167
- pluck: (...args: any[]) => Promise<any>
168
- count: (...args: any[]) => Promise<number>
169
- sum: (...args: any[]) => Promise<number>
170
- avg: (...args: any[]) => Promise<number>
171
- min: (...args: any[]) => Promise<any>
172
- max: (...args: any[]) => Promise<any>
169
+ execute: () => Promise<ResultOf<TRow, TKind>>
170
+ executeTakeFirst: () => Promise<FirstOf<TRow, TKind>>
171
+ executeTakeFirstOrThrow: () => Promise<NonNullable<FirstOf<TRow, TKind>>>
172
+ pluck: (...args: unknown[]) => Promise<unknown[]>
173
+ count: (...args: unknown[]) => Promise<number>
174
+ sum: (...args: unknown[]) => Promise<number>
175
+ avg: (...args: unknown[]) => Promise<number>
176
+ min: (...args: unknown[]) => Promise<unknown>
177
+ max: (...args: unknown[]) => Promise<unknown>
173
178
  exists: () => Promise<boolean>
174
179
  doesntExist: () => Promise<boolean>
175
- $call: (callback: (query: FluentChain) => FluentChain) => FluentChain
176
- [key: string]: any
180
+ $call: (callback: (query: FluentChain<TRow, TKind>) => FluentChain<TRow, TKind>) => FluentChain<TRow, TKind>
177
181
  }
178
182
  /*.ts` and
179
183
  * emits `database/types.d.ts` containing:
@@ -197,20 +201,75 @@ export declare interface FluentChain {
197
201
  export declare interface DatabaseSchema {}
198
202
  declare interface Db extends Pick<Required<RawQueryBuilder>, GenericPassthroughKeys> {
199
203
  fn: import('./types').ExpressionFunctions
200
- selectFrom: (table: TableName) => FluentChain
201
- insertInto: (table: TableName) => FluentChain
202
- updateTable: (table: TableName) => FluentChain
203
- deleteFrom: (table: TableName) => FluentChain
204
- table: (table: TableName) => FluentChain
205
- selectFromSub: (sub: any, alias: string) => FluentChain
206
- select: (table: TableName, ...columns: string[]) => FluentChain
207
- unsafe: (query: string, params?: any[]) => UnsafeReturn
204
+ selectFrom: <T extends TableName>(table: T) => FluentChain<RowOf<T>, 'select'>
205
+ insertInto: <T extends TableName>(table: T) => FluentChain<RowOf<T>, 'insert'>
206
+ updateTable: <T extends TableName>(table: T) => FluentChain<RowOf<T>, 'update'>
207
+ deleteFrom: <T extends TableName>(table: T) => FluentChain<RowOf<T>, 'delete'>
208
+ table: <T extends TableName>(table: T) => FluentChain<RowOf<T>, 'select'>
209
+ selectFromSub: (sub: unknown, alias: string) => FluentChain<Record<string, unknown>>
210
+ select: <T extends TableName>(table: T, ...columns: string[]) => FluentChain<Record<string, unknown>>
211
+ unsafe: (query: string, params?: unknown[]) => UnsafeReturn
208
212
  read: Omit<Db, 'read'>
209
213
  }
210
214
  // The bun-query-builder types `unsafe()` as returning `Promise<any>`, but at
211
215
  // runtime it returns a Bun SQL Statement that has `.execute()`. This interface
212
216
  // corrects the return type so callers can chain `.execute()` without type errors.
213
217
  declare type UnsafeReturn = Promise<any> & { execute: () => Promise<any> }
218
+ /**
219
+ * The keys a row type actually declares, or `never` for a loose record.
220
+ *
221
+ * `keyof Record<string, unknown>` is `string`, so a narrowing overload written
222
+ * against it will happily accept *anything* as a column - including
223
+ * `'menu_items.id as id'`, which then becomes a property name in the result
224
+ * type. That is worse than not narrowing: the row type looks specific and every
225
+ * key in it is fiction.
226
+ */
227
+ export type KnownKeys<T> = string extends keyof T ? never : keyof T;
228
+ /**
229
+ * Which verb started a chain, so its terminals can answer the right thing.
230
+ *
231
+ * `returning` is its own kind rather than a flag on the others: a mutation with
232
+ * `RETURNING` answers rows, and that is the difference between reading
233
+ * `rows[0].id` and reading a count.
234
+ */
235
+ export type ChainKind = 'select' | 'insert' | 'update' | 'delete' | 'returning';
236
+ /** What `execute()` resolves to for each verb. */
237
+ export type ResultOf<TRow, TKind extends ChainKind> = TKind extends 'select' | 'returning'
238
+ ? TRow[]
239
+ : number;
240
+ /** What `executeTakeFirst()` resolves to for each verb. */
241
+ export type FirstOf<TRow, TKind extends ChainKind> = TKind extends 'select' | 'returning' | 'insert'
242
+ ? TRow | undefined
243
+ : TKind extends 'update'
244
+ ? { numUpdatedRows?: number }
245
+ : { numDeletedRows?: number }
246
+ /** `created_at` -> `CreatedAt`, for the dynamic helper names below. */
247
+ declare type SnakeToPascal<S extends string> = S extends `${infer Head}_${infer Tail}`
248
+ ? `${Capitalize<Head>}${SnakeToPascal<Tail>}`
249
+ : Capitalize<S>;
250
+ /**
251
+ * The dynamic `where<Column>` helpers bun-query-builder generates.
252
+ *
253
+ * Derived from the row type rather than allowed by an index signature. The
254
+ * index signature that used to be here (`[key: string]: any`) made every
255
+ * misspelling legal and every result `any`: `whereHndle('a')` compiled, and so
256
+ * did reading a property that does not exist.
257
+ */
258
+ export type DynamicWhereMethods<TRow, TKind extends ChainKind = 'select'> = {
259
+ [K in keyof TRow & string as `where${SnakeToPascal<K>}`]: (value: TRow[K]) => FluentChain<TRow, TKind>
260
+ } & {
261
+ [K in keyof TRow & string as `orWhere${SnakeToPascal<K>}`]: (value: TRow[K]) => FluentChain<TRow, TKind>
262
+ } & {
263
+ [K in keyof TRow & string as `andWhere${SnakeToPascal<K>}`]: (value: TRow[K]) => FluentChain<TRow, TKind>
264
+ }
265
+ /**
266
+ * A chain over one table's rows: the methods, plus the generated helpers.
267
+ *
268
+ * A type alias rather than an interface because the helper half is a mapped
269
+ * type over `TRow`, and an interface can only extend members TypeScript knows
270
+ * statically.
271
+ */
272
+ export type FluentChain<TRow = Record<string, unknown>, TKind extends ChainKind = 'select'> = BaseFluentChain<TRow, TKind> & DynamicWhereMethods<TRow, TKind>;
214
273
  /**
215
274
  * Top-level surface of the lazy `db` proxy. Methods that return a chainable
216
275
  * builder are typed as `FluentChain` to flatten the optional-method noise
@@ -262,6 +321,19 @@ declare type GenericPassthroughKeys = | 'transaction'
262
321
  */
263
322
  // eslint-disable-next-line ts/no-empty-object-type
264
323
  export type TableName = (keyof DatabaseSchema & string) | (string & {});
324
+ /**
325
+ * The row type of a registered table, or an unknown-valued record.
326
+ *
327
+ * A table the generated `DatabaseSchema` knows answers its own columns. One it
328
+ * does not - an app that has never run `buddy generate:db-types`, or a table
329
+ * that lives outside a model - answers `Record<string, unknown>`: still a value
330
+ * the caller narrows, but narrowing it is checked rather than waved through.
331
+ */
332
+ export type RowOf<T extends TableName> = T extends keyof DatabaseSchema
333
+ ? DatabaseSchema[T] extends { columns: infer C }
334
+ ? C
335
+ : DatabaseSchema[T]
336
+ : Record<string, unknown>;
265
337
  // SQLite bootstrap pragmas (stacksjs/stacks#1951) now live in
266
338
  // @stacksjs/query-builder — the one chokepoint every framework
267
339
  // query-builder instance is created through — so EVERY fresh sqlite
package/dist/utils.js CHANGED
@@ -1 +1 @@
1
- import{AsyncLocalStorage}from"node:async_hooks";import{createQueryBuilder,registerPersistentQueryHooks,resetConnection as resetQueryBuilderConnection,setConfig}from"@stacksjs/query-builder";import{SQL}from"bun";import{env as envVars}from"@stacksjs/env";import{getConnectionDefaults}from"./defaults";import{isMysqlWire,isVitessSharded,toQueryBuilderDialect}from"./dialect";import{relativeMigrationDirectory,resolveMigrationDirectory}from"./migration-path";import{contextInTransaction,markContextWrote,resolveReplicaConnection,selectReplica,shouldRouteToReplica,withTransactionContext}from"./replicas";import{aggregateFunctions}from"./types";const sqliteDefaults=getConnectionDefaults("sqlite",envVars),mysqlDefaults=getConnectionDefaults("mysql",envVars),postgresDefaults=getConnectionDefaults("postgres",envVars);let appEnv=envVars.APP_ENV||"local",dbDriver=envVars.DB_CONNECTION||"sqlite",dbConfig={connections:{sqlite:{database:sqliteDefaults.database,prefix:""},mysql:{name:mysqlDefaults.database,host:mysqlDefaults.host,username:mysqlDefaults.username,password:mysqlDefaults.password,port:mysqlDefaults.port,prefix:""},singlestore:{name:mysqlDefaults.database,host:mysqlDefaults.host,username:mysqlDefaults.username,password:mysqlDefaults.password,port:mysqlDefaults.port,prefix:""},vitess:{name:mysqlDefaults.database,host:mysqlDefaults.host,username:mysqlDefaults.username,password:mysqlDefaults.password,port:15306,prefix:"",sharded:isVitessSharded()},postgres:{name:postgresDefaults.database,host:postgresDefaults.host,username:postgresDefaults.username,password:postgresDefaults.password,port:postgresDefaults.port,prefix:""}}},dbConfigLockTail=Promise.resolve();export function acquireDbConfigLock(){let release=()=>{};const held=new Promise((resolve)=>{release=resolve}),acquired=dbConfigLockTail.then(()=>release);dbConfigLockTail=dbConfigLockTail.then(()=>held);return acquired}export function initializeDbConfig(config){if(config?.app?.env)appEnv=config.app.env;if(config?.database?.default)dbDriver=config.database.default;if(config?.database)dbConfig=config.database;updateQueryBuilderConfig();_dbInstance=null;_replicaInstances=new Map}function getEnv(){return appEnv}function getDriver(){return dbDriver}function getDatabaseConfig(){return dbConfig}function getDialect(){return toQueryBuilderDialect(getDriver())}function getDbConfig(){const driver=getDriver(),database=getDatabaseConfig(),env=getEnv();if(driver==="sqlite"){const defaultName=env!=="testing"?"database/stacks.sqlite":"database/stacks_testing.sqlite";return{database:database.connections?.sqlite?.database??defaultName}}if(driver==="mysql")return{database:database.connections?.mysql?.name||"stacks",host:database.connections?.mysql?.host??"127.0.0.1",username:database.connections?.mysql?.username??"root",password:database.connections?.mysql?.password??"",port:database.connections?.mysql?.port??3306};if(driver==="singlestore")return{database:database.connections?.singlestore?.name||"stacks",host:database.connections?.singlestore?.host??"127.0.0.1",username:database.connections?.singlestore?.username??"root",password:database.connections?.singlestore?.password??"",port:database.connections?.singlestore?.port??3306};if(driver==="vitess")return{database:database.connections?.vitess?.name||"stacks",host:database.connections?.vitess?.host??"127.0.0.1",username:database.connections?.vitess?.username??"root",password:database.connections?.vitess?.password??"",port:database.connections?.vitess?.port??15306};if(driver==="postgres"){const dbName=database.connections?.postgres?.name??"stacks";return{database:env==="testing"?`${dbName}_testing`:dbName,host:database.connections?.postgres?.host??"127.0.0.1",username:database.connections?.postgres?.username??"",password:database.connections?.postgres?.password??"",port:database.connections?.postgres?.port??5432}}return{database:":memory:"}}export const QB_SNAPSHOT_DIR="storage/framework/database",RAW_QUERY_SOFT_DELETE_CONFIG={enabled:!1,column:"deleted_at",defaultFilter:!0};export function createDatabaseQueryHooks(dispatch){function forward(event){try{Promise.resolve(dispatch(event)).catch(()=>{})}catch{}}return{onQueryEnd:(event)=>forward({query:{sql:event.sql,parameters:event.params},queryDurationMillis:event.durationMs}),onQueryError:(event)=>forward({query:{sql:event.sql,parameters:event.params},queryDurationMillis:event.durationMs,error:event.error})}}function forwardDatabaseQuery(event){import("./query-logger").then(({logQuery})=>logQuery(event)).catch(()=>{})}registerPersistentQueryHooks(createDatabaseQueryHooks(forwardDatabaseQuery));function getPoolConfig(){const driver=getDriver();if(driver==="sqlite")return;return getDatabaseConfig().connections?.[driver]?.pool}function getReplicas(){const driver=getDriver();if(driver==="sqlite")return[];return getDatabaseConfig().connections?.[driver]?.replicas??[]}function getReadPolicy(){return getDatabaseConfig().reads??{}}function toBunPoolOptions(pool){if(!pool)return{};const options={};if(pool.max!==void 0)options.max=pool.max;if(pool.idleTimeoutMs!==void 0)options.idleTimeout=Math.round(pool.idleTimeoutMs/1000);if(pool.acquireTimeoutMs!==void 0)options.connectionTimeout=Math.round(pool.acquireTimeoutMs/1000);if(pool.maxLifetimeMs!==void 0)options.maxLifetime=Math.round(pool.maxLifetimeMs/1000);return options}function updateQueryBuilderConfig(){const dialect=getDialect(),dbConfigForQb=getDbConfig(),pool=getPoolConfig();setConfig({dialect,vitess:{sharded:isVitessSharded(dbConfig.connections.vitess?.sharded)},database:pool?{...dbConfigForQb,pool}:dbConfigForQb,verbose:getEnv()!=="production",snapshotDir:QB_SNAPSHOT_DIR,migrationDir:relativeMigrationDirectory(resolveMigrationDirectory(toQueryBuilderDialect(dialect),{snapshotDir:QB_SNAPSHOT_DIR})),timestamps:{createdAt:"created_at",updatedAt:"updated_at",defaultOrderColumn:"created_at"},softDeletes:RAW_QUERY_SOFT_DELETE_CONFIG})}updateQueryBuilderConfig();let _dbInstance=null,_configInitPromise=null;function ensureConfigLoaded(){if(!_configInitPromise)_configInitPromise=(async()=>{try{const{config,overridesReady}=await import("@stacksjs/config");await overridesReady;if(config){initializeDbConfig(config);_dbInstance=null}}catch{}})();return _configInitPromise}export async function ensureDatabaseConfigLoaded(){await ensureConfigLoaded()}export{applySqlitePragmas,SQLITE_BOOTSTRAP_PRAGMAS}from"@stacksjs/query-builder";const sqliteTxOwner=new AsyncLocalStorage;let sqliteTxTail=Promise.resolve();function serializeSqliteTransaction(run){if(sqliteTxOwner.getStore())return run();const result=sqliteTxTail.then(()=>sqliteTxOwner.run(!0,run));sqliteTxTail=result.then(()=>{return},()=>{return});return result}function applySqliteTransactionSerialization(instance){const original=instance.transaction.bind(instance);instance.transaction=(...args)=>serializeSqliteTransaction(()=>original(...args))}function applyTransactionRoutingContext(instance){const original=instance.transaction.bind(instance);instance.transaction=(...args)=>withTransactionContext(()=>original(...args))}function getDb(){if(!_dbInstance){updateQueryBuilderConfig();_dbInstance=createQueryBuilder();if(getDialect()==="sqlite")applySqliteTransactionSerialization(_dbInstance);applyTransactionRoutingContext(_dbInstance)}return _dbInstance}let _replicaInstances=new Map;export function resetDatabaseConnection(){resetQueryBuilderConnection();_dbInstance=null;_replicaInstances=new Map}function getReplicaDb(replica){const primary=getDbConfig(),resolved=resolveReplicaConnection(replica,primary),key=`${resolved.host}:${resolved.port??""}`,cached=_replicaInstances.get(key);if(cached)return cached;const scheme=isMysqlWire(getDriver())?"mysql":"postgres",auth=resolved.username?`${encodeURIComponent(resolved.username)}:${encodeURIComponent(resolved.password??"")}@`:"",url=`${scheme}://${auth}${resolved.host}:${resolved.port}/${resolved.database}`,sql=new SQL({url,...toBunPoolOptions(getPoolConfig())}),instance=createQueryBuilder({sql});_replicaInstances.set(key,instance);return instance}function getReadDb(){const replicas=getReplicas(),policy=getReadPolicy();if(!shouldRouteToReplica({policy,replicas}))return getDb();const replica=selectReplica(replicas,policy.strategy);return replica?getReplicaDb(replica):getDb()}function getExplicitReadDb(){const replicas=getReplicas();if(!replicas.length||contextInTransaction())return getDb();const replica=selectReplica(replicas,getReadPolicy().strategy);return replica?getReplicaDb(replica):getDb()}const WRITE_ENTRY_POINTS=new Set(["insertInto","updateTable","deleteFrom","create","createMany","insertOrIgnore","insertGetId","updateOrInsert","upsert"]),READ_ENTRY_POINTS=new Set(["selectFrom","selectFromSub","select"]);ensureConfigLoaded();export const db=new Proxy({},{get(_target,prop){if(prop==="fn")return aggregateFunctions;if(prop==="read")return readDb;if(typeof prop==="string"&&WRITE_ENTRY_POINTS.has(prop))markContextWrote();const instance=typeof prop==="string"&&READ_ENTRY_POINTS.has(prop)?getReadDb():getDb(),value=instance[prop];if(typeof value==="function")return value.bind(instance);return value}}),readDb=new Proxy({},{get(_target,prop){if(prop==="fn")return aggregateFunctions;const instance=getExplicitReadDb(),value=instance[prop];if(typeof value==="function")return value.bind(instance);return value}});export{setConfig};
1
+ import{AsyncLocalStorage}from"node:async_hooks";import{createQueryBuilder,registerPersistentQueryHooks,resetConnection as resetQueryBuilderConnection,setConfig}from"@stacksjs/query-builder";import{SQL}from"bun";import{env as envVars}from"@stacksjs/env";import{getConnectionDefaults}from"./defaults";import{isMysqlWire,isVitessSharded,toQueryBuilderDialect}from"./dialect";import{relativeMigrationDirectory,resolveMigrationDirectory}from"./migration-path";import{contextInTransaction,markContextWrote,resolveReplicaConnection,selectReplica,shouldRouteToReplica,withTransactionContext}from"./replicas";import{aggregateFunctions}from"./types";const sqliteDefaults=getConnectionDefaults("sqlite",envVars),mysqlDefaults=getConnectionDefaults("mysql",envVars),postgresDefaults=getConnectionDefaults("postgres",envVars);let appEnv=envVars.APP_ENV||"local",dbDriver=envVars.DB_CONNECTION||"sqlite",dbConfig={connections:{sqlite:{database:sqliteDefaults.database,prefix:""},mysql:{name:mysqlDefaults.database,host:mysqlDefaults.host,username:mysqlDefaults.username,password:mysqlDefaults.password,port:mysqlDefaults.port,prefix:""},singlestore:{name:mysqlDefaults.database,host:mysqlDefaults.host,username:mysqlDefaults.username,password:mysqlDefaults.password,port:mysqlDefaults.port,prefix:""},vitess:{name:mysqlDefaults.database,host:mysqlDefaults.host,username:mysqlDefaults.username,password:mysqlDefaults.password,port:15306,prefix:"",sharded:isVitessSharded()},postgres:{name:postgresDefaults.database,host:postgresDefaults.host,username:postgresDefaults.username,password:postgresDefaults.password,port:postgresDefaults.port,prefix:""}}},dbConfigLockTail=Promise.resolve();export function acquireDbConfigLock(){let release=()=>{};const held=new Promise((resolve)=>{release=resolve}),acquired=dbConfigLockTail.then(()=>release);dbConfigLockTail=dbConfigLockTail.then(()=>held);return acquired}export function initializeDbConfig(config){if(config?.app?.env)appEnv=config.app.env;if(config?.database?.default)dbDriver=config.database.default;if(config?.database)dbConfig=config.database;updateQueryBuilderConfig();_dbInstance=null;_replicaInstances=new Map}function getEnv(){return appEnv}function getDriver(){return dbDriver}function getDatabaseConfig(){return dbConfig}function getDialect(){return toQueryBuilderDialect(getDriver())}function getDbConfig(){const driver=getDriver(),database=getDatabaseConfig(),env=getEnv();if(driver==="sqlite"){const defaultName=env!=="testing"?"database/stacks.sqlite":"database/stacks_testing.sqlite";return{database:database.connections?.sqlite?.database??defaultName}}if(driver==="mysql")return{database:database.connections?.mysql?.name||"stacks",host:database.connections?.mysql?.host??"127.0.0.1",username:database.connections?.mysql?.username??"root",password:database.connections?.mysql?.password??"",port:database.connections?.mysql?.port??3306};if(driver==="singlestore")return{database:database.connections?.singlestore?.name||"stacks",host:database.connections?.singlestore?.host??"127.0.0.1",username:database.connections?.singlestore?.username??"root",password:database.connections?.singlestore?.password??"",port:database.connections?.singlestore?.port??3306};if(driver==="vitess")return{database:database.connections?.vitess?.name||"stacks",host:database.connections?.vitess?.host??"127.0.0.1",username:database.connections?.vitess?.username??"root",password:database.connections?.vitess?.password??"",port:database.connections?.vitess?.port??15306};if(driver==="postgres"){const dbName=database.connections?.postgres?.name??"stacks";return{database:env==="testing"?`${dbName}_testing`:dbName,host:database.connections?.postgres?.host??"127.0.0.1",username:database.connections?.postgres?.username??"",password:database.connections?.postgres?.password??"",port:database.connections?.postgres?.port??5432}}return{database:":memory:"}}export const QB_SNAPSHOT_DIR="storage/framework/database",RAW_QUERY_SOFT_DELETE_CONFIG={enabled:!1,column:"deleted_at",defaultFilter:!0};export function createDatabaseQueryHooks(dispatch){function forward(event){try{Promise.resolve(dispatch(event)).catch(()=>{})}catch{}}return{onQueryEnd:(event)=>forward({query:{sql:event.sql,parameters:event.params},queryDurationMillis:event.durationMs}),onQueryError:(event)=>forward({query:{sql:event.sql,parameters:event.params},queryDurationMillis:event.durationMs,error:event.error})}}function forwardDatabaseQuery(event){import("./query-logger").then(({logQuery})=>logQuery(event)).catch(()=>{})}registerPersistentQueryHooks(createDatabaseQueryHooks(forwardDatabaseQuery));function getPoolConfig(){const driver=getDriver();if(driver==="sqlite")return;return getDatabaseConfig().connections?.[driver]?.pool}function getReplicas(){const driver=getDriver();if(driver==="sqlite")return[];return getDatabaseConfig().connections?.[driver]?.replicas??[]}function getReadPolicy(){return getDatabaseConfig().reads??{}}function toBunPoolOptions(pool){if(!pool)return{};const options={};if(pool.max!==void 0)options.max=pool.max;if(pool.idleTimeoutMs!==void 0)options.idleTimeout=Math.round(pool.idleTimeoutMs/1000);if(pool.acquireTimeoutMs!==void 0)options.connectionTimeout=Math.round(pool.acquireTimeoutMs/1000);if(pool.maxLifetimeMs!==void 0)options.maxLifetime=Math.round(pool.maxLifetimeMs/1000);return options}function updateQueryBuilderConfig(){const dialect=getDialect(),dbConfigForQb=getDbConfig(),pool=getPoolConfig();setConfig({dialect,vitess:{sharded:isVitessSharded(dbConfig.connections.vitess?.sharded)},database:pool?{...dbConfigForQb,pool}:dbConfigForQb,verbose:getEnv()!=="production",snapshotDir:QB_SNAPSHOT_DIR,migrationDir:relativeMigrationDirectory(resolveMigrationDirectory(toQueryBuilderDialect(dialect),{snapshotDir:QB_SNAPSHOT_DIR})),timestamps:{createdAt:"created_at",updatedAt:"updated_at",defaultOrderColumn:"created_at"},softDeletes:RAW_QUERY_SOFT_DELETE_CONFIG})}updateQueryBuilderConfig();let _dbInstance=null,_configInitPromise=null;function ensureConfigLoaded(){if(!_configInitPromise)_configInitPromise=(async()=>{try{const{config,overridesReady}=await import("@stacksjs/config");await overridesReady;if(config){initializeDbConfig(config);_dbInstance=null}}catch{}})();return _configInitPromise}export async function ensureDatabaseConfigLoaded(){await ensureConfigLoaded()}export{applySqlitePragmas,SQLITE_BOOTSTRAP_PRAGMAS}from"@stacksjs/query-builder";const sqliteTxOwner=new AsyncLocalStorage;let sqliteTxTail=Promise.resolve();function serializeSqliteTransaction(run){if(sqliteTxOwner.getStore())return run();const result=sqliteTxTail.then(()=>sqliteTxOwner.run(!0,run));sqliteTxTail=result.then(()=>{return},()=>{return});return result}function applySqliteTransactionSerialization(instance){const original=instance.transaction.bind(instance);instance.transaction=(...args)=>serializeSqliteTransaction(()=>original(...args))}function applyTransactionRoutingContext(instance){const original=instance.transaction.bind(instance);instance.transaction=(...args)=>withTransactionContext(()=>original(...args))}function getDb(){if(!_dbInstance){updateQueryBuilderConfig();_dbInstance=createQueryBuilder();if(getDialect()==="sqlite")applySqliteTransactionSerialization(_dbInstance);applyTransactionRoutingContext(_dbInstance)}return _dbInstance}let _replicaInstances=new Map;export function resetDatabaseConnection(){resetQueryBuilderConnection();_dbInstance=null;_replicaInstances=new Map}function getReplicaDb(replica){const primary=getDbConfig(),resolved=resolveReplicaConnection(replica,primary),key=`${resolved.host}:${resolved.port??""}`,cached=_replicaInstances.get(key);if(cached)return cached;const scheme=isMysqlWire(getDriver())?"mysql":"postgres",auth=resolved.username?`${encodeURIComponent(resolved.username)}:${encodeURIComponent(resolved.password??"")}@`:"",url=`${scheme}://${auth}${resolved.host}:${resolved.port}/${resolved.database}`,sql=new SQL({url,...toBunPoolOptions(getPoolConfig())}),instance=createQueryBuilder({sql});_replicaInstances.set(key,instance);return instance}function getReadDb(){const replicas=getReplicas(),policy=getReadPolicy();if(!shouldRouteToReplica({policy,replicas}))return getDb();const replica=selectReplica(replicas,policy.strategy);return replica?getReplicaDb(replica):getDb()}function getExplicitReadDb(){const replicas=getReplicas();if(!replicas.length||contextInTransaction())return getDb();const replica=selectReplica(replicas,getReadPolicy().strategy);return replica?getReplicaDb(replica):getDb()}const WRITE_ENTRY_POINTS=new Set(["insertInto","updateTable","deleteFrom","create","createMany","insertOrIgnore","insertGetId","updateOrInsert","upsert"]),READ_ENTRY_POINTS=new Set(["selectFrom","selectFromSub","select"]);ensureConfigLoaded();export function asRows(rows){return rows}export function asRow(row){return row}export const db=new Proxy({},{get(_target,prop){if(prop==="fn")return aggregateFunctions;if(prop==="read")return readDb;if(typeof prop==="string"&&WRITE_ENTRY_POINTS.has(prop))markContextWrote();const instance=typeof prop==="string"&&READ_ENTRY_POINTS.has(prop)?getReadDb():getDb(),value=instance[prop];if(typeof value==="function")return value.bind(instance);return value}}),readDb=new Proxy({},{get(_target,prop){if(prop==="fn")return aggregateFunctions;const instance=getExplicitReadDb(),value=instance[prop];if(typeof value==="function")return value.bind(instance);return value}});export{setConfig};
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/database",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.72.22",
5
+ "version": "0.72.23",
6
6
  "description": "The Stacks database integration.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -65,15 +65,15 @@
65
65
  "dynamodb-tooling": "^0.3.2"
66
66
  },
67
67
  "devDependencies": {
68
- "@stacksjs/cli": "0.72.22",
69
- "@stacksjs/config": "0.72.22",
70
- "@stacksjs/logging": "0.72.22",
71
- "@stacksjs/router": "0.72.22",
68
+ "@stacksjs/cli": "0.72.23",
69
+ "@stacksjs/config": "0.72.23",
70
+ "@stacksjs/logging": "0.72.23",
71
+ "@stacksjs/router": "0.72.23",
72
72
  "better-dx": "^0.2.23",
73
- "@stacksjs/path": "0.72.22",
74
- "@stacksjs/query-builder": "0.72.22",
75
- "@stacksjs/storage": "0.72.22",
76
- "@stacksjs/strings": "0.72.22",
77
- "@stacksjs/utils": "0.72.22"
73
+ "@stacksjs/path": "0.72.23",
74
+ "@stacksjs/query-builder": "0.72.23",
75
+ "@stacksjs/storage": "0.72.23",
76
+ "@stacksjs/strings": "0.72.23",
77
+ "@stacksjs/utils": "0.72.23"
78
78
  }
79
79
  }