@guren/server 0.2.0-alpha.7 → 1.0.0-rc.9
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/Application-DtWDHXr1.d.ts +2110 -0
- package/dist/BroadcastManager-AkIWUGJo.d.ts +466 -0
- package/dist/CacheManager-BkvHEOZX.d.ts +244 -0
- package/dist/ConsoleKernel-CqCVrdZs.d.ts +207 -0
- package/dist/EventManager-CmIoLt7r.d.ts +207 -0
- package/dist/Gate-CNkBYf8m.d.ts +268 -0
- package/dist/HealthManager-DUyMIzsZ.d.ts +141 -0
- package/dist/I18nManager-Dtgzsf5n.d.ts +270 -0
- package/dist/LogManager-7mxnkaPM.d.ts +256 -0
- package/dist/MailManager-DpMvYiP9.d.ts +292 -0
- package/dist/Scheduler-BstvSca7.d.ts +469 -0
- package/dist/StorageManager-oZTHqaza.d.ts +337 -0
- package/dist/api-token-JOif2CtG.d.ts +1792 -0
- package/dist/app-key-CsBfRC_Q.d.ts +214 -0
- package/dist/auth/index.d.ts +418 -0
- package/dist/auth/index.js +6742 -0
- package/dist/authorization/index.d.ts +129 -0
- package/dist/authorization/index.js +621 -0
- package/dist/broadcasting/index.d.ts +233 -0
- package/dist/broadcasting/index.js +907 -0
- package/dist/cache/index.d.ts +233 -0
- package/dist/cache/index.js +817 -0
- package/dist/encryption/index.d.ts +222 -0
- package/dist/encryption/index.js +602 -0
- package/dist/events/index.d.ts +155 -0
- package/dist/events/index.js +330 -0
- package/dist/health/index.d.ts +185 -0
- package/dist/health/index.js +379 -0
- package/dist/i18n/index.d.ts +101 -0
- package/dist/i18n/index.js +597 -0
- package/dist/index-9_Jzj5jo.d.ts +7 -0
- package/dist/index.d.ts +2628 -619
- package/dist/index.js +22229 -3116
- package/dist/lambda/index.d.ts +156 -0
- package/dist/lambda/index.js +91 -0
- package/dist/logging/index.d.ts +50 -0
- package/dist/logging/index.js +557 -0
- package/dist/mail/index.d.ts +288 -0
- package/dist/mail/index.js +695 -0
- package/dist/mcp/index.d.ts +139 -0
- package/dist/mcp/index.js +382 -0
- package/dist/notifications/index.d.ts +271 -0
- package/dist/notifications/index.js +741 -0
- package/dist/queue/index.d.ts +423 -0
- package/dist/queue/index.js +958 -0
- package/dist/runtime/index.d.ts +93 -0
- package/dist/runtime/index.js +834 -0
- package/dist/scheduling/index.d.ts +41 -0
- package/dist/scheduling/index.js +836 -0
- package/dist/storage/index.d.ts +196 -0
- package/dist/storage/index.js +832 -0
- package/dist/vite/index.js +203 -3
- package/package.json +93 -6
- package/dist/chunk-FK2XQSBF.js +0 -160
|
@@ -0,0 +1,1792 @@
|
|
|
1
|
+
import { MiddlewareHandler, Context } from 'hono';
|
|
2
|
+
|
|
3
|
+
/** Accessor function: transforms a record field value after reading from DB. */
|
|
4
|
+
type AccessorFn<T = unknown> = (record: PlainObject) => T;
|
|
5
|
+
/** Mutator function: transforms a field value before writing to DB. */
|
|
6
|
+
type MutatorFn<T = unknown> = (value: T, record: PlainObject) => unknown;
|
|
7
|
+
/** Map of field names to accessor functions. */
|
|
8
|
+
type AccessorDefinitions = Record<string, AccessorFn>;
|
|
9
|
+
/** Map of field names to mutator functions. */
|
|
10
|
+
type MutatorDefinitions = Record<string, MutatorFn>;
|
|
11
|
+
|
|
12
|
+
type FieldKey<TRecord extends PlainObject> = keyof TRecord & string;
|
|
13
|
+
/** Comparison operators supported by the QueryBuilder. */
|
|
14
|
+
type WhereOperator = '=' | '!=' | '>' | '<' | '>=' | '<=' | 'like' | 'in' | 'not in' | 'is null' | 'is not null';
|
|
15
|
+
/** A single field-level condition. */
|
|
16
|
+
interface SimpleCondition {
|
|
17
|
+
type: 'simple';
|
|
18
|
+
field: string;
|
|
19
|
+
operator: WhereOperator;
|
|
20
|
+
value: unknown;
|
|
21
|
+
}
|
|
22
|
+
/** A group of conditions joined by AND or OR. */
|
|
23
|
+
interface GroupCondition {
|
|
24
|
+
type: 'group';
|
|
25
|
+
boolean: 'and' | 'or';
|
|
26
|
+
conditions: WhereCondition[];
|
|
27
|
+
}
|
|
28
|
+
/** A where condition - either simple or grouped. */
|
|
29
|
+
type WhereCondition = SimpleCondition | GroupCondition;
|
|
30
|
+
/** Options carried by the QueryBuilder for query execution. */
|
|
31
|
+
interface QueryBuilderOptions {
|
|
32
|
+
orderBy: Array<{
|
|
33
|
+
column: string;
|
|
34
|
+
direction: OrderDirection;
|
|
35
|
+
}>;
|
|
36
|
+
limitValue?: number;
|
|
37
|
+
offsetValue?: number;
|
|
38
|
+
selectFields?: readonly string[];
|
|
39
|
+
trx?: unknown;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Fluent query builder for the Guren ORM.
|
|
43
|
+
*
|
|
44
|
+
* Provides a chainable API for constructing database queries with
|
|
45
|
+
* support for complex where conditions, ordering, pagination, and more.
|
|
46
|
+
*
|
|
47
|
+
* Implements the thenable pattern so it can be directly awaited,
|
|
48
|
+
* resolving to the result of `get()`.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* // Fluent chaining
|
|
52
|
+
* const posts = await Post.where('status', 'published')
|
|
53
|
+
* .where('views', '>', 100)
|
|
54
|
+
* .orderBy('createdAt', 'desc')
|
|
55
|
+
* .limit(10)
|
|
56
|
+
* .get()
|
|
57
|
+
*
|
|
58
|
+
* // Thenable - await directly
|
|
59
|
+
* const active = await User.where('active', true)
|
|
60
|
+
*
|
|
61
|
+
* // Pagination
|
|
62
|
+
* const page = await Post.where('status', 'published').paginate(1, 20)
|
|
63
|
+
*/
|
|
64
|
+
declare class QueryBuilder<TRecord extends PlainObject = PlainObject, TResult extends PlainObject = TRecord> {
|
|
65
|
+
private conditions;
|
|
66
|
+
private options;
|
|
67
|
+
private modelClass;
|
|
68
|
+
private table;
|
|
69
|
+
private adapter;
|
|
70
|
+
private eagerLoad;
|
|
71
|
+
private eagerLoadConstraints;
|
|
72
|
+
constructor(modelClass: typeof Model, options?: {
|
|
73
|
+
trx?: unknown;
|
|
74
|
+
});
|
|
75
|
+
/**
|
|
76
|
+
* Add a where condition (AND).
|
|
77
|
+
*
|
|
78
|
+
* Supports three calling signatures:
|
|
79
|
+
* - `where(field, value)` - equality check
|
|
80
|
+
* - `where(field, operator, value)` - comparison
|
|
81
|
+
* - `where(object)` - multiple equality conditions
|
|
82
|
+
*/
|
|
83
|
+
where<TKey extends FieldKey<TRecord>>(field: TKey, value: TRecord[TKey]): this;
|
|
84
|
+
where<TKey extends FieldKey<TRecord>>(field: TKey, operator: WhereOperator, value: unknown): this;
|
|
85
|
+
where(conditions: Partial<Record<FieldKey<TRecord>, unknown>>): this;
|
|
86
|
+
/**
|
|
87
|
+
* Add an OR where condition.
|
|
88
|
+
*
|
|
89
|
+
* Same overloads as `where()`, but joins with OR logic.
|
|
90
|
+
* Creates an OR group containing the new condition(s).
|
|
91
|
+
*/
|
|
92
|
+
orWhere<TKey extends FieldKey<TRecord>>(field: TKey, value: TRecord[TKey]): this;
|
|
93
|
+
orWhere<TKey extends FieldKey<TRecord>>(field: TKey, operator: WhereOperator, value: unknown): this;
|
|
94
|
+
orWhere(conditions: Partial<Record<FieldKey<TRecord>, unknown>>): this;
|
|
95
|
+
/**
|
|
96
|
+
* Add a WHERE NULL condition.
|
|
97
|
+
* @param field - Column to check for NULL
|
|
98
|
+
*/
|
|
99
|
+
whereNull(field: FieldKey<TRecord>): this;
|
|
100
|
+
/**
|
|
101
|
+
* Add a WHERE NOT NULL condition.
|
|
102
|
+
* @param field - Column to check for NOT NULL
|
|
103
|
+
*/
|
|
104
|
+
whereNotNull(field: FieldKey<TRecord>): this;
|
|
105
|
+
/**
|
|
106
|
+
* Add a WHERE IN condition.
|
|
107
|
+
* @param field - Column to check
|
|
108
|
+
* @param values - Array of values to match against
|
|
109
|
+
*/
|
|
110
|
+
whereIn<TKey extends FieldKey<TRecord>>(field: TKey, values: readonly TRecord[TKey][]): this;
|
|
111
|
+
/**
|
|
112
|
+
* Add a WHERE NOT IN condition.
|
|
113
|
+
* @param field - Column to check
|
|
114
|
+
* @param values - Array of values to exclude
|
|
115
|
+
*/
|
|
116
|
+
whereNotIn<TKey extends FieldKey<TRecord>>(field: TKey, values: readonly TRecord[TKey][]): this;
|
|
117
|
+
/**
|
|
118
|
+
* Add an ORDER BY clause. Can be called multiple times to sort by multiple columns.
|
|
119
|
+
* @param field - Column to sort by
|
|
120
|
+
* @param direction - Sort direction (default: 'asc')
|
|
121
|
+
*/
|
|
122
|
+
orderBy(field: FieldKey<TRecord>, direction?: OrderDirection): this;
|
|
123
|
+
/**
|
|
124
|
+
* Set the maximum number of records to return.
|
|
125
|
+
* @param n - Maximum record count
|
|
126
|
+
*/
|
|
127
|
+
limit(n: number): this;
|
|
128
|
+
/**
|
|
129
|
+
* Set the number of records to skip.
|
|
130
|
+
* @param n - Number of records to skip
|
|
131
|
+
*/
|
|
132
|
+
offset(n: number): this;
|
|
133
|
+
/**
|
|
134
|
+
* Limit the columns returned in the result.
|
|
135
|
+
* @param fields - Column names to select
|
|
136
|
+
*/
|
|
137
|
+
select<TKey extends FieldKey<TRecord>>(...fields: readonly TKey[]): QueryBuilder<TRecord, Pick<TRecord, TKey>>;
|
|
138
|
+
/**
|
|
139
|
+
* Apply a named query scope defined on the model.
|
|
140
|
+
*
|
|
141
|
+
* @param name - The scope name
|
|
142
|
+
* @returns this (for chaining)
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* const results = await Post.where('author', 'John')
|
|
146
|
+
* .scope('published')
|
|
147
|
+
* .scope('popular')
|
|
148
|
+
* .get()
|
|
149
|
+
*/
|
|
150
|
+
scope(name: string): this;
|
|
151
|
+
/**
|
|
152
|
+
* Eager-load relationships on query results.
|
|
153
|
+
*
|
|
154
|
+
* Supports string names, arrays, dot notation for nested relations,
|
|
155
|
+
* and constraint callbacks.
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* // Simple
|
|
159
|
+
* await User.where('active', true).with('posts').get()
|
|
160
|
+
*
|
|
161
|
+
* // Multiple
|
|
162
|
+
* await User.where('active', true).with('posts', 'comments').get()
|
|
163
|
+
*
|
|
164
|
+
* // Nested (dot notation)
|
|
165
|
+
* await User.where('active', true).with('posts.comments').get()
|
|
166
|
+
*/
|
|
167
|
+
with(...relations: (string | Record<string, (q: QueryBuilder<any>) => void>)[]): this;
|
|
168
|
+
/**
|
|
169
|
+
* Execute the query and return all matching records.
|
|
170
|
+
* @returns Array of matching records
|
|
171
|
+
*/
|
|
172
|
+
get(): Promise<TResult[]>;
|
|
173
|
+
/**
|
|
174
|
+
* Execute the query and return the first matching record.
|
|
175
|
+
* @returns The first record or null
|
|
176
|
+
*/
|
|
177
|
+
first(): Promise<TResult | null>;
|
|
178
|
+
/**
|
|
179
|
+
* Execute the query and return the first matching record, or throw.
|
|
180
|
+
* @returns The first record
|
|
181
|
+
* @throws Error if no record matches
|
|
182
|
+
*/
|
|
183
|
+
firstOrFail(): Promise<TResult>;
|
|
184
|
+
/**
|
|
185
|
+
* Count the number of records matching the current conditions.
|
|
186
|
+
* @returns The count of matching records
|
|
187
|
+
*/
|
|
188
|
+
count(): Promise<number>;
|
|
189
|
+
/**
|
|
190
|
+
* Paginate the query results.
|
|
191
|
+
* @param page - Page number (1-based, default: 1)
|
|
192
|
+
* @param perPage - Records per page (default: 15)
|
|
193
|
+
* @returns Paginated result with data and metadata
|
|
194
|
+
*/
|
|
195
|
+
paginate(page?: number, perPage?: number): Promise<PaginatedResult<TResult>>;
|
|
196
|
+
/**
|
|
197
|
+
* Bulk update records matching the current conditions.
|
|
198
|
+
* @param data - Data to set on matching records
|
|
199
|
+
* @returns The updated record (adapter-dependent)
|
|
200
|
+
*/
|
|
201
|
+
update(data: PlainObject): Promise<TRecord>;
|
|
202
|
+
/**
|
|
203
|
+
* Bulk delete records matching the current conditions.
|
|
204
|
+
* @returns Number of deleted records (adapter-dependent)
|
|
205
|
+
*/
|
|
206
|
+
delete(): Promise<number | PlainObject | void>;
|
|
207
|
+
/**
|
|
208
|
+
* Makes QueryBuilder a thenable so it can be directly awaited.
|
|
209
|
+
* Resolves to the result of `get()`.
|
|
210
|
+
*/
|
|
211
|
+
then<TResult1 = TResult[], TResult2 = never>(onfulfilled?: ((value: TResult[]) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
|
|
212
|
+
/**
|
|
213
|
+
* Catch handler for the thenable interface.
|
|
214
|
+
*/
|
|
215
|
+
catch<TCatch = never>(onrejected?: ((reason: unknown) => TCatch | PromiseLike<TCatch>) | null): Promise<TResult[] | TCatch>;
|
|
216
|
+
/** Get the internal conditions (used by adapters). */
|
|
217
|
+
getConditions(): WhereCondition[];
|
|
218
|
+
/** Get the internal query options (used by adapters). */
|
|
219
|
+
getOptions(): QueryBuilderOptions;
|
|
220
|
+
private addSimpleCondition;
|
|
221
|
+
private executeQuery;
|
|
222
|
+
/**
|
|
223
|
+
* Attempt to convert the current conditions to a simple WhereClause
|
|
224
|
+
* for backward compatibility with basic adapters.
|
|
225
|
+
* Returns null if conditions are too complex.
|
|
226
|
+
*/
|
|
227
|
+
private toSimpleWhereClause;
|
|
228
|
+
/**
|
|
229
|
+
* Load eager relations onto fetched results.
|
|
230
|
+
* Supports dot notation for nested relations (e.g., 'posts.comments').
|
|
231
|
+
*/
|
|
232
|
+
private readonly loadEagerRelations;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** A named global scope function. */
|
|
236
|
+
type ScopeFunction = (q: QueryBuilder<any>) => QueryBuilder<any>;
|
|
237
|
+
/**
|
|
238
|
+
* Registry for named global scopes on a model.
|
|
239
|
+
*
|
|
240
|
+
* Supports adding, removing, and applying multiple named scopes.
|
|
241
|
+
* Used internally by Model to manage global query constraints.
|
|
242
|
+
*/
|
|
243
|
+
declare class GlobalScopeRegistry {
|
|
244
|
+
private scopes;
|
|
245
|
+
add(name: string, fn: ScopeFunction): void;
|
|
246
|
+
remove(name: string): void;
|
|
247
|
+
has(name: string): boolean;
|
|
248
|
+
names(): string[];
|
|
249
|
+
/** Apply all scopes (or all except excluded) to a query builder. */
|
|
250
|
+
apply<T extends PlainObject>(builder: QueryBuilder<T>, except?: string[]): QueryBuilder<T>;
|
|
251
|
+
clear(): void;
|
|
252
|
+
get size(): number;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Model lifecycle hook names.
|
|
257
|
+
*
|
|
258
|
+
* "Before" hooks (`creating`, `updating`, `deleting`, `saving`) fire before
|
|
259
|
+
* the database operation. If they return `false`, the operation is aborted.
|
|
260
|
+
*
|
|
261
|
+
* "After" hooks (`created`, `updated`, `deleted`, `saved`) fire after the
|
|
262
|
+
* database operation completes.
|
|
263
|
+
*
|
|
264
|
+
* `saving`/`saved` fire for both create and update operations.
|
|
265
|
+
*/
|
|
266
|
+
type HookName = 'creating' | 'created' | 'updating' | 'updated' | 'deleting' | 'deleted' | 'saving' | 'saved';
|
|
267
|
+
/**
|
|
268
|
+
* Callback signature for model lifecycle hooks.
|
|
269
|
+
*
|
|
270
|
+
* @param data - The record data being operated on
|
|
271
|
+
* @returns void for after-hooks. Returning `false` from a before-hook aborts the operation.
|
|
272
|
+
*/
|
|
273
|
+
type HookCallback<T = Record<string, unknown>> = (data: T) => void | Promise<void> | false | Promise<false>;
|
|
274
|
+
/**
|
|
275
|
+
* Map of hook names to their callbacks.
|
|
276
|
+
* All hooks are optional.
|
|
277
|
+
*/
|
|
278
|
+
type ModelHooks = {
|
|
279
|
+
[K in HookName]?: HookCallback;
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Base interface for model observers.
|
|
284
|
+
*
|
|
285
|
+
* Observers allow extracting lifecycle hook logic into dedicated classes.
|
|
286
|
+
* Each method corresponds to a model lifecycle event and is optional.
|
|
287
|
+
*
|
|
288
|
+
* @example
|
|
289
|
+
* class UserObserver implements ModelObserver {
|
|
290
|
+
* async creating(data) { data.slug = slugify(data.name) }
|
|
291
|
+
* async created(data) { await sendWelcomeEmail(data) }
|
|
292
|
+
* }
|
|
293
|
+
*
|
|
294
|
+
* User.observe(UserObserver)
|
|
295
|
+
*/
|
|
296
|
+
interface ModelObserver {
|
|
297
|
+
creating?(data: PlainObject): void | Promise<void> | false | Promise<false>;
|
|
298
|
+
created?(data: PlainObject): void | Promise<void>;
|
|
299
|
+
updating?(data: PlainObject): void | Promise<void> | false | Promise<false>;
|
|
300
|
+
updated?(data: PlainObject): void | Promise<void>;
|
|
301
|
+
deleting?(data: PlainObject): void | Promise<void> | false | Promise<false>;
|
|
302
|
+
deleted?(data: PlainObject): void | Promise<void>;
|
|
303
|
+
saving?(data: PlainObject): void | Promise<void> | false | Promise<false>;
|
|
304
|
+
saved?(data: PlainObject): void | Promise<void>;
|
|
305
|
+
}
|
|
306
|
+
type ModelObserverConstructor = new () => ModelObserver;
|
|
307
|
+
|
|
308
|
+
/** Generic plain object type used throughout the ORM. */
|
|
309
|
+
type PlainObject = Record<string, unknown>;
|
|
310
|
+
type RelationShape = Record<string, unknown>;
|
|
311
|
+
/** Supported cast types for attribute casting. */
|
|
312
|
+
type CastType = 'json' | 'date' | 'boolean' | 'number' | 'string';
|
|
313
|
+
/**
|
|
314
|
+
* Value type for where clause conditions.
|
|
315
|
+
* Supports single values, arrays (for IN queries), or null.
|
|
316
|
+
*/
|
|
317
|
+
type WhereValue<Value> = Value | readonly Value[] | null;
|
|
318
|
+
/**
|
|
319
|
+
* Where clause for filtering records.
|
|
320
|
+
* Supports equality and IN (array) queries.
|
|
321
|
+
*
|
|
322
|
+
* @example
|
|
323
|
+
* // Single value (equality)
|
|
324
|
+
* { status: 'active' }
|
|
325
|
+
*
|
|
326
|
+
* // Array value (IN query)
|
|
327
|
+
* { id: [1, 2, 3] }
|
|
328
|
+
*
|
|
329
|
+
* // Multiple conditions (AND)
|
|
330
|
+
* { status: 'active', role: 'admin' }
|
|
331
|
+
*/
|
|
332
|
+
type WhereClause<TRecord extends PlainObject = PlainObject> = Partial<{
|
|
333
|
+
[K in keyof TRecord & string]?: WhereValue<TRecord[K]>;
|
|
334
|
+
}>;
|
|
335
|
+
/** Sort direction for ordering queries. */
|
|
336
|
+
type OrderDirection = 'asc' | 'desc';
|
|
337
|
+
/** Normalized order definition with column and direction. */
|
|
338
|
+
type OrderDefinition<TRecord extends PlainObject = PlainObject> = {
|
|
339
|
+
column: keyof TRecord & string;
|
|
340
|
+
direction: OrderDirection;
|
|
341
|
+
};
|
|
342
|
+
/**
|
|
343
|
+
* Flexible order expression input.
|
|
344
|
+
* @example
|
|
345
|
+
* 'createdAt' // Column name (ascending)
|
|
346
|
+
* ['createdAt', 'desc'] // Tuple [column, direction]
|
|
347
|
+
* { column: 'createdAt', direction: 'desc' } // Object form
|
|
348
|
+
*/
|
|
349
|
+
type OrderExpression<TRecord extends PlainObject = PlainObject> = (keyof TRecord & string) | readonly [keyof TRecord & string, OrderDirection] | {
|
|
350
|
+
column: keyof TRecord & string;
|
|
351
|
+
direction?: OrderDirection;
|
|
352
|
+
};
|
|
353
|
+
/** Input for orderBy - single expression or array of expressions. */
|
|
354
|
+
type OrderByInput<TRecord extends PlainObject = PlainObject> = OrderExpression<TRecord> | readonly OrderExpression<TRecord>[];
|
|
355
|
+
/** Normalized array of order definitions. */
|
|
356
|
+
type OrderByClause<TRecord extends PlainObject = PlainObject> = readonly OrderDefinition<TRecord>[];
|
|
357
|
+
/** Options for findMany queries. */
|
|
358
|
+
interface FindManyOptions<TRecord extends PlainObject = PlainObject> {
|
|
359
|
+
/** Filter conditions */
|
|
360
|
+
where?: WhereClause<TRecord>;
|
|
361
|
+
/** Sort order */
|
|
362
|
+
orderBy?: OrderByClause<TRecord>;
|
|
363
|
+
/** Maximum number of records to return */
|
|
364
|
+
limit?: number;
|
|
365
|
+
/** Number of records to skip */
|
|
366
|
+
offset?: number;
|
|
367
|
+
}
|
|
368
|
+
/** Options for paginated queries. */
|
|
369
|
+
interface PaginateOptions<TRecord extends PlainObject = PlainObject> {
|
|
370
|
+
/** Page number (1-based, default: 1) */
|
|
371
|
+
page?: number;
|
|
372
|
+
/** Records per page (default: 15) */
|
|
373
|
+
perPage?: number;
|
|
374
|
+
/** Filter conditions */
|
|
375
|
+
where?: WhereClause<TRecord>;
|
|
376
|
+
/** Sort order */
|
|
377
|
+
orderBy?: OrderByInput<TRecord>;
|
|
378
|
+
}
|
|
379
|
+
/** Pagination metadata returned with paginated results. */
|
|
380
|
+
interface PaginationMeta {
|
|
381
|
+
/** Total number of records matching the query */
|
|
382
|
+
total: number;
|
|
383
|
+
/** Number of records per page */
|
|
384
|
+
perPage: number;
|
|
385
|
+
/** Current page number (1-based) */
|
|
386
|
+
currentPage: number;
|
|
387
|
+
/** Total number of pages */
|
|
388
|
+
totalPages: number;
|
|
389
|
+
/** Whether there are more pages after this one */
|
|
390
|
+
hasMore: boolean;
|
|
391
|
+
/** Index of first record on this page (1-based) */
|
|
392
|
+
from: number;
|
|
393
|
+
/** Index of last record on this page */
|
|
394
|
+
to: number;
|
|
395
|
+
}
|
|
396
|
+
/** Result of a paginated query. */
|
|
397
|
+
interface PaginatedResult<TRecord extends PlainObject = PlainObject> {
|
|
398
|
+
/** Records for the current page */
|
|
399
|
+
data: TRecord[];
|
|
400
|
+
/** Pagination metadata */
|
|
401
|
+
meta: PaginationMeta;
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Interface for ORM adapters that power the Model class.
|
|
405
|
+
* The default adapter is DrizzleAdapter.
|
|
406
|
+
*/
|
|
407
|
+
interface ORMAdapter {
|
|
408
|
+
/** Run operations in a database transaction. */
|
|
409
|
+
transaction?<TResult>(callback: (trx: unknown) => Promise<TResult>): Promise<TResult>;
|
|
410
|
+
/** Find multiple records with optional filtering, ordering, and pagination. */
|
|
411
|
+
findMany<TRecord extends PlainObject = PlainObject>(table: unknown, options?: FindManyOptions<TRecord>, queryOptions?: AdapterQueryOptions): Promise<TRecord[]>;
|
|
412
|
+
/** Find a single record by unique criteria. */
|
|
413
|
+
findUnique<TRecord extends PlainObject = PlainObject>(table: unknown, where: WhereClause<TRecord>, queryOptions?: AdapterQueryOptions): Promise<TRecord | null>;
|
|
414
|
+
/** Create a new record. */
|
|
415
|
+
create<TRecord extends PlainObject = PlainObject>(table: unknown, data: PlainObject, writeOptions?: AdapterQueryOptions): Promise<TRecord>;
|
|
416
|
+
/** Update records matching criteria. */
|
|
417
|
+
update?<TRecord extends PlainObject = PlainObject>(table: unknown, where: WhereClause<TRecord>, data: PlainObject, writeOptions?: AdapterQueryOptions): Promise<TRecord>;
|
|
418
|
+
/** Delete records matching criteria. */
|
|
419
|
+
delete?<TRecord extends PlainObject = PlainObject>(table: unknown, where: WhereClause<TRecord>, writeOptions?: AdapterQueryOptions): Promise<number | PlainObject | void>;
|
|
420
|
+
/** Count records matching criteria. */
|
|
421
|
+
count?<TRecord extends PlainObject = PlainObject>(table: unknown, where?: WhereClause<TRecord>, queryOptions?: AdapterQueryOptions): Promise<number>;
|
|
422
|
+
}
|
|
423
|
+
interface AdapterQueryOptions {
|
|
424
|
+
trx?: unknown;
|
|
425
|
+
}
|
|
426
|
+
type ModelWriteOptions = AdapterQueryOptions;
|
|
427
|
+
type ModelQueryOptions = AdapterQueryOptions;
|
|
428
|
+
type TransactionHandle = NonNullable<AdapterQueryOptions['trx']>;
|
|
429
|
+
type SelectFrom<TDatabase> = TDatabase extends {
|
|
430
|
+
select: (...args: any[]) => infer TSelect;
|
|
431
|
+
} ? TSelect extends {
|
|
432
|
+
from: (...args: any[]) => infer TResult;
|
|
433
|
+
} ? TResult : never : never;
|
|
434
|
+
/**
|
|
435
|
+
* ActiveRecord-style base class for database models.
|
|
436
|
+
*
|
|
437
|
+
* Provides a Laravel Eloquent-like API for database operations including
|
|
438
|
+
* CRUD, querying, pagination, and eager-loading of relationships.
|
|
439
|
+
*
|
|
440
|
+
* @example
|
|
441
|
+
* // Define a model
|
|
442
|
+
* class User extends Model<UserRecord> {
|
|
443
|
+
* static override table = users // Drizzle table
|
|
444
|
+
* static override readonly recordType = {} as UserRecord
|
|
445
|
+
* }
|
|
446
|
+
*
|
|
447
|
+
* // Query records
|
|
448
|
+
* const allUsers = await User.all()
|
|
449
|
+
* const user = await User.find(1)
|
|
450
|
+
* const activeUsers = await User.where({ status: 'active' })
|
|
451
|
+
*
|
|
452
|
+
* // Create and update
|
|
453
|
+
* const newUser = await User.create({ name: 'John', email: 'john@example.com' })
|
|
454
|
+
* await User.update({ id: 1 }, { name: 'Jane' })
|
|
455
|
+
*
|
|
456
|
+
* // Pagination
|
|
457
|
+
* const page = await User.paginate({ page: 1, perPage: 10 })
|
|
458
|
+
*
|
|
459
|
+
* // Relationships
|
|
460
|
+
* const usersWithPosts = await User.with('posts')
|
|
461
|
+
*/
|
|
462
|
+
declare abstract class Model<TRecord extends PlainObject = PlainObject> {
|
|
463
|
+
/** The ORM adapter used for database operations. */
|
|
464
|
+
protected static ormAdapter: ORMAdapter;
|
|
465
|
+
/** The database table (e.g., Drizzle table schema). */
|
|
466
|
+
protected static table: unknown;
|
|
467
|
+
/** Type marker for TypeScript inference. Define as `{} as YourRecordType`. */
|
|
468
|
+
static readonly recordType: unknown;
|
|
469
|
+
/** Type marker for insert/update payload inference. Define as `{} as typeof table.$inferInsert`. */
|
|
470
|
+
static readonly createType: unknown;
|
|
471
|
+
protected static relationDefinitions?: Map<string, RelationDefinition>;
|
|
472
|
+
/** Type marker for relation types. Define relation types here for type inference. */
|
|
473
|
+
static relationTypes: RelationShape;
|
|
474
|
+
/**
|
|
475
|
+
* Named query scopes for reusable query constraints.
|
|
476
|
+
*
|
|
477
|
+
* @example
|
|
478
|
+
* class Post extends Model<PostRecord> {
|
|
479
|
+
* static scopes = {
|
|
480
|
+
* published: (q: QueryBuilder<PostRecord>) => q.where('status', 'published'),
|
|
481
|
+
* popular: (q: QueryBuilder<PostRecord>) => q.where('views', '>', 1000),
|
|
482
|
+
* }
|
|
483
|
+
* }
|
|
484
|
+
* // Usage: Post.scope('published').scope('popular').get()
|
|
485
|
+
*/
|
|
486
|
+
static scopes?: Record<string, (q: QueryBuilder<any>) => QueryBuilder<any>>;
|
|
487
|
+
/**
|
|
488
|
+
* Default scope applied to all queries on this model.
|
|
489
|
+
* Override this to automatically filter queries (e.g., soft deletes).
|
|
490
|
+
*
|
|
491
|
+
* @example
|
|
492
|
+
* static defaultScope = (q: QueryBuilder<any>) => q.whereNull('deletedAt')
|
|
493
|
+
*/
|
|
494
|
+
static defaultScope?: (q: QueryBuilder<any>) => QueryBuilder<any>;
|
|
495
|
+
/**
|
|
496
|
+
* Model lifecycle hooks that fire during create, update, and delete operations.
|
|
497
|
+
*
|
|
498
|
+
* @example
|
|
499
|
+
* class User extends Model<UserRecord> {
|
|
500
|
+
* static hooks: ModelHooks = {
|
|
501
|
+
* creating: async (data) => { data.password = await hash(data.password as string) },
|
|
502
|
+
* created: async (data) => { console.log('User created:', data) },
|
|
503
|
+
* }
|
|
504
|
+
* }
|
|
505
|
+
*/
|
|
506
|
+
static hooks?: ModelHooks;
|
|
507
|
+
/**
|
|
508
|
+
* Attribute casting definitions for automatic type conversion.
|
|
509
|
+
*
|
|
510
|
+
* Casts are applied when reading records from the database and when
|
|
511
|
+
* writing records to the database.
|
|
512
|
+
*
|
|
513
|
+
* @example
|
|
514
|
+
* class Post extends Model<PostRecord> {
|
|
515
|
+
* static casts = {
|
|
516
|
+
* metadata: 'json',
|
|
517
|
+
* publishedAt: 'date',
|
|
518
|
+
* isActive: 'boolean',
|
|
519
|
+
* viewCount: 'number',
|
|
520
|
+
* }
|
|
521
|
+
* }
|
|
522
|
+
*/
|
|
523
|
+
static casts?: Record<string, CastType>;
|
|
524
|
+
/**
|
|
525
|
+
* Whitelist of fields allowed for mass assignment.
|
|
526
|
+
* If set, only these fields will be accepted in `create()` and `update()`.
|
|
527
|
+
*
|
|
528
|
+
* @example
|
|
529
|
+
* class User extends Model<UserRecord> {
|
|
530
|
+
* static fillable = ['name', 'email', 'password']
|
|
531
|
+
* }
|
|
532
|
+
*/
|
|
533
|
+
static fillable?: string[];
|
|
534
|
+
/**
|
|
535
|
+
* Blacklist of fields excluded from mass assignment.
|
|
536
|
+
* Defaults to `['id']`. Ignored if `fillable` is set.
|
|
537
|
+
*
|
|
538
|
+
* @example
|
|
539
|
+
* class Post extends Model<PostRecord> {
|
|
540
|
+
* static guarded = ['id', 'createdAt']
|
|
541
|
+
* }
|
|
542
|
+
*/
|
|
543
|
+
static guarded?: string[];
|
|
544
|
+
/**
|
|
545
|
+
* Accessor functions for computed/virtual attributes.
|
|
546
|
+
* Applied after reading records from the database.
|
|
547
|
+
*
|
|
548
|
+
* @example
|
|
549
|
+
* class User extends Model<UserRecord> {
|
|
550
|
+
* static accessors = {
|
|
551
|
+
* fullName: (record) => `${record.firstName} ${record.lastName}`,
|
|
552
|
+
* }
|
|
553
|
+
* }
|
|
554
|
+
*/
|
|
555
|
+
static accessors?: AccessorDefinitions;
|
|
556
|
+
/**
|
|
557
|
+
* Mutator functions for transforming attributes before persistence.
|
|
558
|
+
*
|
|
559
|
+
* @example
|
|
560
|
+
* class User extends Model<UserRecord> {
|
|
561
|
+
* static mutators = {
|
|
562
|
+
* email: (value) => String(value).toLowerCase(),
|
|
563
|
+
* }
|
|
564
|
+
* }
|
|
565
|
+
*/
|
|
566
|
+
static mutators?: MutatorDefinitions;
|
|
567
|
+
/**
|
|
568
|
+
* Fields to exclude from serialization output.
|
|
569
|
+
*
|
|
570
|
+
* @example
|
|
571
|
+
* class User extends Model<UserRecord> {
|
|
572
|
+
* static hidden = ['passwordHash', 'rememberToken']
|
|
573
|
+
* }
|
|
574
|
+
*/
|
|
575
|
+
static hidden?: string[];
|
|
576
|
+
/**
|
|
577
|
+
* Whitelist of fields to include in serialization output.
|
|
578
|
+
* When set, only these fields appear. Takes precedence over `hidden`.
|
|
579
|
+
*/
|
|
580
|
+
static visible?: string[];
|
|
581
|
+
/**
|
|
582
|
+
* Virtual accessor attributes to include in serialization output.
|
|
583
|
+
*
|
|
584
|
+
* @example
|
|
585
|
+
* class User extends Model<UserRecord> {
|
|
586
|
+
* static appends = ['fullName']
|
|
587
|
+
* static accessors = { fullName: (r) => `${r.firstName} ${r.lastName}` }
|
|
588
|
+
* }
|
|
589
|
+
*/
|
|
590
|
+
static appends?: string[];
|
|
591
|
+
/** Registered model observers. */
|
|
592
|
+
protected static observers?: ModelObserver[];
|
|
593
|
+
/** Named global scopes registry. */
|
|
594
|
+
protected static globalScopeRegistry?: GlobalScopeRegistry;
|
|
595
|
+
/**
|
|
596
|
+
* Set a custom ORM adapter for this model.
|
|
597
|
+
* @param adapter - The adapter to use
|
|
598
|
+
*/
|
|
599
|
+
static useAdapter(adapter: ORMAdapter): void;
|
|
600
|
+
/**
|
|
601
|
+
* Get the current ORM adapter.
|
|
602
|
+
* @returns The configured adapter
|
|
603
|
+
*/
|
|
604
|
+
static getAdapter(): ORMAdapter;
|
|
605
|
+
/**
|
|
606
|
+
* Run operations inside a database transaction.
|
|
607
|
+
*
|
|
608
|
+
* @example
|
|
609
|
+
* await User.transaction(async (trx) => {
|
|
610
|
+
* const user = await User.create({ name: 'John' }, { trx })
|
|
611
|
+
* await Profile.create({ userId: user.id }, { trx })
|
|
612
|
+
* })
|
|
613
|
+
*/
|
|
614
|
+
static transaction<T extends typeof Model, TResult>(this: T, callback: (trx: TransactionHandle, scope: TransactionModelScope<T>) => Promise<TResult>): Promise<TResult>;
|
|
615
|
+
/**
|
|
616
|
+
* Create a transaction-bound model scope that automatically forwards `trx`
|
|
617
|
+
* to query and write operations.
|
|
618
|
+
*
|
|
619
|
+
* @example
|
|
620
|
+
* await User.transaction(async (trx, txUser) => {
|
|
621
|
+
* await txUser.create({ name: 'Shinji' })
|
|
622
|
+
* await txUser.update({ id: 1 }, { name: 'Ikari' })
|
|
623
|
+
* })
|
|
624
|
+
*/
|
|
625
|
+
static inTransaction<T extends typeof Model>(this: T, trx: TransactionHandle): TransactionModelScope<T>;
|
|
626
|
+
/**
|
|
627
|
+
* Register a model observer class.
|
|
628
|
+
*
|
|
629
|
+
* @example
|
|
630
|
+
* User.observe(UserObserver)
|
|
631
|
+
*/
|
|
632
|
+
static observe(ObserverClass: ModelObserverConstructor): void;
|
|
633
|
+
/** Clear all registered observers. */
|
|
634
|
+
static clearObservers(): void;
|
|
635
|
+
protected static getGlobalScopes(): GlobalScopeRegistry;
|
|
636
|
+
/**
|
|
637
|
+
* Register a named global scope.
|
|
638
|
+
*
|
|
639
|
+
* @example
|
|
640
|
+
* User.addGlobalScope('active', (q) => q.where('active', true))
|
|
641
|
+
*/
|
|
642
|
+
static addGlobalScope(name: string, fn: ScopeFunction): void;
|
|
643
|
+
/** Remove a named global scope. */
|
|
644
|
+
static removeGlobalScope(name: string): void;
|
|
645
|
+
/**
|
|
646
|
+
* Start a query excluding specific global scope(s).
|
|
647
|
+
*
|
|
648
|
+
* @example
|
|
649
|
+
* const all = await User.withoutGlobalScope('active').get()
|
|
650
|
+
*/
|
|
651
|
+
static withoutGlobalScope<T extends typeof Model>(this: T, ...names: string[]): QueryBuilder<TRecordFor<T>>;
|
|
652
|
+
/**
|
|
653
|
+
* Start a query with no global scopes applied (also skips defaultScope).
|
|
654
|
+
*
|
|
655
|
+
* @example
|
|
656
|
+
* const all = await User.withoutGlobalScopes().get()
|
|
657
|
+
*/
|
|
658
|
+
static withoutGlobalScopes<T extends typeof Model>(this: T): QueryBuilder<TRecordFor<T>>;
|
|
659
|
+
/**
|
|
660
|
+
* Serialize a record for API/JSON output.
|
|
661
|
+
* Applies hidden/visible filtering, accessors, and appends.
|
|
662
|
+
*
|
|
663
|
+
* @example
|
|
664
|
+
* const json = User.serialize(user)
|
|
665
|
+
*/
|
|
666
|
+
static serialize<T extends typeof Model>(this: T, record: TRecordFor<T>): PlainObject;
|
|
667
|
+
/**
|
|
668
|
+
* Serialize an array of records.
|
|
669
|
+
*
|
|
670
|
+
* @example
|
|
671
|
+
* const json = User.serializeMany(users)
|
|
672
|
+
*/
|
|
673
|
+
static serializeMany<T extends typeof Model>(this: T, records: TRecordFor<T>[]): PlainObject[];
|
|
674
|
+
/**
|
|
675
|
+
* Apply attribute casts to a record read from the database.
|
|
676
|
+
*
|
|
677
|
+
* @param record - The raw record from the database
|
|
678
|
+
* @returns The record with cast attributes applied
|
|
679
|
+
*/
|
|
680
|
+
static applyCasts<T extends PlainObject>(record: T): T;
|
|
681
|
+
/**
|
|
682
|
+
* Apply all read-time transforms: casts then accessors.
|
|
683
|
+
* Used internally after fetching records from the database.
|
|
684
|
+
*/
|
|
685
|
+
protected static applyReadTransforms<T extends PlainObject>(record: T): T;
|
|
686
|
+
/**
|
|
687
|
+
* Filter input data based on mass assignment protection rules.
|
|
688
|
+
*
|
|
689
|
+
* If `fillable` is defined, only fields listed in `fillable` are kept.
|
|
690
|
+
* Otherwise, fields listed in `guarded` (default: `['id']`) are removed.
|
|
691
|
+
*
|
|
692
|
+
* @param data - The input data to filter
|
|
693
|
+
* @returns Filtered data safe for mass assignment
|
|
694
|
+
*/
|
|
695
|
+
static filterFillable(data: PlainObject): PlainObject;
|
|
696
|
+
protected static preparePersistencePayload(data: PlainObject): Promise<PlainObject>;
|
|
697
|
+
protected static getRelationDefinitions(): Map<string, RelationDefinition>;
|
|
698
|
+
/** @internal */
|
|
699
|
+
static getRelationDefinition(name: string): RelationDefinition | undefined;
|
|
700
|
+
static resolveTable(): unknown;
|
|
701
|
+
/**
|
|
702
|
+
* Retrieve all records from the table.
|
|
703
|
+
*
|
|
704
|
+
* @returns Array of all records
|
|
705
|
+
*
|
|
706
|
+
* @example
|
|
707
|
+
* const users = await User.all()
|
|
708
|
+
*/
|
|
709
|
+
static all<T extends typeof Model>(this: T, queryOptions?: ModelQueryOptions): Promise<Array<TRecordFor<T>>>;
|
|
710
|
+
/**
|
|
711
|
+
* Find a record by its primary key.
|
|
712
|
+
*
|
|
713
|
+
* @param id - The primary key value
|
|
714
|
+
* @param key - The primary key column name (default: 'id')
|
|
715
|
+
* @returns The record or null if not found
|
|
716
|
+
*
|
|
717
|
+
* @example
|
|
718
|
+
* const user = await User.find(1)
|
|
719
|
+
* const userByEmail = await User.find('john@example.com', 'email')
|
|
720
|
+
*/
|
|
721
|
+
static find<T extends typeof Model>(this: T, id: TRecordFor<T>[keyof TRecordFor<T> & string], key?: keyof TRecordFor<T> & string, queryOptions?: ModelQueryOptions): Promise<TRecordFor<T> | null>;
|
|
722
|
+
/**
|
|
723
|
+
* Find a record by primary key or throw an error.
|
|
724
|
+
*
|
|
725
|
+
* @param id - The primary key value
|
|
726
|
+
* @param key - The primary key column name (default: 'id')
|
|
727
|
+
* @returns The record
|
|
728
|
+
* @throws Error if record not found
|
|
729
|
+
*
|
|
730
|
+
* @example
|
|
731
|
+
* const user = await User.findOrFail(1) // Throws if not found
|
|
732
|
+
*/
|
|
733
|
+
static findOrFail<T extends typeof Model>(this: T, id: TRecordFor<T>[keyof TRecordFor<T> & string], key?: keyof TRecordFor<T> & string, queryOptions?: ModelQueryOptions): Promise<TRecordFor<T>>;
|
|
734
|
+
/**
|
|
735
|
+
* Find a record by primary key with eager-loaded relations.
|
|
736
|
+
*
|
|
737
|
+
* @param id - The primary key value
|
|
738
|
+
* @param relations - Relation name(s) to eager load
|
|
739
|
+
* @param key - The primary key column name (default: 'id')
|
|
740
|
+
* @returns The record with loaded relations, or null if not found
|
|
741
|
+
*
|
|
742
|
+
* @example
|
|
743
|
+
* const post = await Post.findWith(1, 'author')
|
|
744
|
+
* const post = await Post.findWith(1, ['author', 'tags'])
|
|
745
|
+
*/
|
|
746
|
+
static findWith<T extends typeof Model, K extends RelationKey<T>>(this: T, id: TRecordFor<T>[keyof TRecordFor<T> & string], relations: K | readonly K[], key?: keyof TRecordFor<T> & string): Promise<(TRecordFor<T> & RelationTypePick<T, K | readonly K[]>) | null>;
|
|
747
|
+
/**
|
|
748
|
+
* Find a record by primary key with eager-loaded relations, or throw.
|
|
749
|
+
*
|
|
750
|
+
* @param id - The primary key value
|
|
751
|
+
* @param relations - Relation name(s) to eager load
|
|
752
|
+
* @param key - The primary key column name (default: 'id')
|
|
753
|
+
* @returns The record with loaded relations
|
|
754
|
+
* @throws ModelNotFoundException if record not found
|
|
755
|
+
*
|
|
756
|
+
* @example
|
|
757
|
+
* const post = await Post.findWithOrFail(1, 'author')
|
|
758
|
+
* const post = await Post.findWithOrFail(1, ['author', 'tags'])
|
|
759
|
+
*/
|
|
760
|
+
static findWithOrFail<T extends typeof Model, K extends RelationKey<T>>(this: T, id: TRecordFor<T>[keyof TRecordFor<T> & string], relations: K | readonly K[], key?: keyof TRecordFor<T> & string): Promise<TRecordFor<T> & RelationTypePick<T, K | readonly K[]>>;
|
|
761
|
+
/**
|
|
762
|
+
* Get the first record matching the conditions.
|
|
763
|
+
*
|
|
764
|
+
* @param where - Optional filter conditions
|
|
765
|
+
* @returns The first matching record or null
|
|
766
|
+
*
|
|
767
|
+
* @example
|
|
768
|
+
* const admin = await User.first({ role: 'admin' })
|
|
769
|
+
*/
|
|
770
|
+
static first<T extends typeof Model>(this: T, where?: WhereClauseFor<T>, queryOptions?: ModelQueryOptions): Promise<TRecordFor<T> | null>;
|
|
771
|
+
/**
|
|
772
|
+
* Start a fluent query with where conditions.
|
|
773
|
+
*
|
|
774
|
+
* Returns a QueryBuilder that is thenable, so it can be directly awaited.
|
|
775
|
+
*
|
|
776
|
+
* @example
|
|
777
|
+
* // Object form - multiple equality conditions
|
|
778
|
+
* const activeUsers = await User.where({ status: 'active' })
|
|
779
|
+
*
|
|
780
|
+
* // Field + value - equality
|
|
781
|
+
* const admins = await User.where('role', 'admin')
|
|
782
|
+
*
|
|
783
|
+
* // Field + operator + value - comparison
|
|
784
|
+
* const recent = await Post.where('views', '>', 100)
|
|
785
|
+
*
|
|
786
|
+
* // Fluent chaining
|
|
787
|
+
* const posts = await Post.where('status', 'published')
|
|
788
|
+
* .where('views', '>', 100)
|
|
789
|
+
* .orderBy('createdAt', 'desc')
|
|
790
|
+
* .limit(10)
|
|
791
|
+
* .get()
|
|
792
|
+
*/
|
|
793
|
+
static where<T extends typeof Model>(this: T, conditions: WhereClauseFor<T>): QueryBuilder<TRecordFor<T>>;
|
|
794
|
+
static where<T extends typeof Model>(this: T, field: keyof TRecordFor<T> & string, value: unknown): QueryBuilder<TRecordFor<T>>;
|
|
795
|
+
static where<T extends typeof Model>(this: T, field: keyof TRecordFor<T> & string, operator: WhereOperator, value: unknown): QueryBuilder<TRecordFor<T>>;
|
|
796
|
+
/**
|
|
797
|
+
* Start a fluent query with a WHERE NULL condition.
|
|
798
|
+
* @param field - Column to check for NULL
|
|
799
|
+
*/
|
|
800
|
+
static whereNull<T extends typeof Model>(this: T, field: keyof TRecordFor<T> & string): QueryBuilder<TRecordFor<T>>;
|
|
801
|
+
/**
|
|
802
|
+
* Start a fluent query with a WHERE NOT NULL condition.
|
|
803
|
+
* @param field - Column to check for NOT NULL
|
|
804
|
+
*/
|
|
805
|
+
static whereNotNull<T extends typeof Model>(this: T, field: keyof TRecordFor<T> & string): QueryBuilder<TRecordFor<T>>;
|
|
806
|
+
/**
|
|
807
|
+
* Start a fluent query with a WHERE IN condition.
|
|
808
|
+
* @param field - Column to check
|
|
809
|
+
* @param values - Array of values to match against
|
|
810
|
+
*/
|
|
811
|
+
static whereIn<T extends typeof Model>(this: T, field: keyof TRecordFor<T> & string, values: readonly TRecordFor<T>[keyof TRecordFor<T> & string][]): QueryBuilder<TRecordFor<T>>;
|
|
812
|
+
/**
|
|
813
|
+
* Start a fluent query with a WHERE NOT IN condition.
|
|
814
|
+
* @param field - Column to check
|
|
815
|
+
* @param values - Array of values to exclude
|
|
816
|
+
*/
|
|
817
|
+
static whereNotIn<T extends typeof Model>(this: T, field: keyof TRecordFor<T> & string, values: readonly TRecordFor<T>[keyof TRecordFor<T> & string][]): QueryBuilder<TRecordFor<T>>;
|
|
818
|
+
/**
|
|
819
|
+
* Start a fluent query with a typed field selection.
|
|
820
|
+
*
|
|
821
|
+
* @example
|
|
822
|
+
* const rows = await User.select('id', 'name')
|
|
823
|
+
* const first = await User.select('id').first()
|
|
824
|
+
*/
|
|
825
|
+
static select<T extends typeof Model, Keys extends keyof TRecordFor<T> & string>(this: T, ...fields: readonly Keys[]): QueryBuilder<TRecordFor<T>, Pick<TRecordFor<T>, Keys>>;
|
|
826
|
+
/**
|
|
827
|
+
* Start a new QueryBuilder for fluent query construction.
|
|
828
|
+
*
|
|
829
|
+
* @returns A fresh QueryBuilder instance
|
|
830
|
+
*
|
|
831
|
+
* @example
|
|
832
|
+
* const results = await User.newQuery()
|
|
833
|
+
* .where('status', 'active')
|
|
834
|
+
* .orderBy('name')
|
|
835
|
+
* .limit(10)
|
|
836
|
+
* .get()
|
|
837
|
+
*/
|
|
838
|
+
static newQuery<T extends typeof Model>(this: T, queryOptions?: ModelQueryOptions): QueryBuilder<TRecordFor<T>>;
|
|
839
|
+
/**
|
|
840
|
+
* Create a new QueryBuilder without applying any default scopes.
|
|
841
|
+
* Useful for querying soft-deleted records or bypassing global filters.
|
|
842
|
+
*
|
|
843
|
+
* @returns A fresh QueryBuilder instance with no scopes applied
|
|
844
|
+
*/
|
|
845
|
+
static newQueryWithoutScopes<T extends typeof Model>(this: T, queryOptions?: ModelQueryOptions): QueryBuilder<TRecordFor<T>>;
|
|
846
|
+
/**
|
|
847
|
+
* Apply a named query scope.
|
|
848
|
+
*
|
|
849
|
+
* @param name - The scope name defined in `static scopes`
|
|
850
|
+
* @returns A QueryBuilder with the scope applied
|
|
851
|
+
*
|
|
852
|
+
* @example
|
|
853
|
+
* const published = await Post.scope('published').get()
|
|
854
|
+
* const popularPublished = await Post.scope('published').scope('popular').get()
|
|
855
|
+
*/
|
|
856
|
+
static scope<T extends typeof Model>(this: T, name: string): QueryBuilder<TRecordFor<T>>;
|
|
857
|
+
/**
|
|
858
|
+
* Define a one-to-many relationship.
|
|
859
|
+
*
|
|
860
|
+
* @param name - Relation name (used in `with()` calls)
|
|
861
|
+
* @param related - The related model class
|
|
862
|
+
* @param foreignKey - Foreign key column on the related model
|
|
863
|
+
* @param localKey - Local key column on this model
|
|
864
|
+
*
|
|
865
|
+
* @example
|
|
866
|
+
* class User extends Model<UserRecord> {
|
|
867
|
+
* static {
|
|
868
|
+
* this.hasMany('posts', Post, 'userId', 'id')
|
|
869
|
+
* }
|
|
870
|
+
* }
|
|
871
|
+
* // Later: User.with('posts')
|
|
872
|
+
*/
|
|
873
|
+
static hasMany<This extends typeof Model, Related extends typeof Model, ForeignKey extends keyof TRecordFor<Related> & string, LocalKey extends keyof TRecordFor<This> & string, Name extends RelationKeyOrString<This>>(this: This, name: Name, related: Related | (() => Related | Promise<Related>), foreignKey: ForeignKey, localKey: LocalKey): void;
|
|
874
|
+
/**
|
|
875
|
+
* Define a many-to-one (inverse) relationship.
|
|
876
|
+
*
|
|
877
|
+
* @param name - Relation name (used in `with()` calls)
|
|
878
|
+
* @param related - The related model class
|
|
879
|
+
* @param foreignKey - Foreign key column on this model
|
|
880
|
+
* @param ownerKey - Primary key column on the related model
|
|
881
|
+
*
|
|
882
|
+
* @example
|
|
883
|
+
* class Post extends Model<PostRecord> {
|
|
884
|
+
* static {
|
|
885
|
+
* this.belongsTo('author', User, 'userId', 'id')
|
|
886
|
+
* }
|
|
887
|
+
* }
|
|
888
|
+
* // Later: Post.with('author')
|
|
889
|
+
*/
|
|
890
|
+
static belongsTo<This extends typeof Model, Related extends typeof Model, ForeignKey extends keyof TRecordFor<This> & string, OwnerKey extends keyof TRecordFor<Related> & string, Name extends RelationKeyOrString<This>>(this: This, name: Name, related: Related | (() => Related | Promise<Related>), foreignKey: ForeignKey, ownerKey: OwnerKey): void;
|
|
891
|
+
/**
|
|
892
|
+
* Define a one-to-one relationship.
|
|
893
|
+
*
|
|
894
|
+
* @param name - Relation name (used in `with()` calls)
|
|
895
|
+
* @param related - The related model class
|
|
896
|
+
* @param foreignKey - Foreign key column on the related model
|
|
897
|
+
* @param localKey - Local key column on this model
|
|
898
|
+
*
|
|
899
|
+
* @example
|
|
900
|
+
* class User extends Model<UserRecord> {
|
|
901
|
+
* static {
|
|
902
|
+
* this.hasOne('profile', Profile, 'userId', 'id')
|
|
903
|
+
* }
|
|
904
|
+
* }
|
|
905
|
+
* // Later: User.with('profile')
|
|
906
|
+
*/
|
|
907
|
+
static hasOne<This extends typeof Model, Related extends typeof Model, ForeignKey extends keyof TRecordFor<Related> & string, LocalKey extends keyof TRecordFor<This> & string, Name extends RelationKeyOrString<This>>(this: This, name: Name, related: Related | (() => Related | Promise<Related>), foreignKey: ForeignKey, localKey: LocalKey): void;
|
|
908
|
+
/**
|
|
909
|
+
* Define a many-to-many relationship via a pivot table.
|
|
910
|
+
*
|
|
911
|
+
* @param name - Relation name (used in `with()` calls)
|
|
912
|
+
* @param related - The related model class
|
|
913
|
+
* @param pivotTable - The pivot/junction table (e.g., Drizzle table schema)
|
|
914
|
+
* @param foreignPivotKey - Column on pivot table referencing this model
|
|
915
|
+
* @param relatedPivotKey - Column on pivot table referencing the related model
|
|
916
|
+
* @param parentKey - Local key on this model (default: 'id')
|
|
917
|
+
* @param relatedKey - Local key on the related model (default: 'id')
|
|
918
|
+
*
|
|
919
|
+
* @example
|
|
920
|
+
* class User extends Model<UserRecord> {
|
|
921
|
+
* static {
|
|
922
|
+
* this.belongsToMany('roles', Role, userRoles, 'userId', 'roleId', 'id', 'id')
|
|
923
|
+
* }
|
|
924
|
+
* }
|
|
925
|
+
* // Later: User.with('roles')
|
|
926
|
+
*/
|
|
927
|
+
static belongsToMany<This extends typeof Model, Related extends typeof Model, Name extends RelationKeyOrString<This>>(this: This, name: Name, related: Related | (() => Related | Promise<Related>), pivotTable: unknown, foreignPivotKey: string, relatedPivotKey: string, parentKey?: string, relatedKey?: string): void;
|
|
928
|
+
/**
|
|
929
|
+
* Define a has-many-through relationship.
|
|
930
|
+
*
|
|
931
|
+
* @param name - Relation name (used in `with()` calls)
|
|
932
|
+
* @param related - The final related model class
|
|
933
|
+
* @param through - The intermediate model class
|
|
934
|
+
* @param firstKey - Foreign key on the intermediate model referencing this model
|
|
935
|
+
* @param secondKey - Foreign key on the related model referencing the intermediate model
|
|
936
|
+
* @param localKey - Local key on this model (default: 'id')
|
|
937
|
+
* @param secondLocalKey - Local key on the intermediate model (default: 'id')
|
|
938
|
+
*
|
|
939
|
+
* @example
|
|
940
|
+
* class Country extends Model<CountryRecord> {
|
|
941
|
+
* static {
|
|
942
|
+
* this.hasManyThrough('posts', Post, User, 'countryId', 'userId', 'id', 'id')
|
|
943
|
+
* }
|
|
944
|
+
* }
|
|
945
|
+
* // Later: Country.with('posts')
|
|
946
|
+
*/
|
|
947
|
+
static hasManyThrough<This extends typeof Model, Related extends typeof Model, Through extends typeof Model, Name extends RelationKeyOrString<This>>(this: This, name: Name, related: Related | (() => Related | Promise<Related>), through: Through | (() => Through | Promise<Through>), firstKey: string, secondKey: string, localKey?: string, secondLocalKey?: string): void;
|
|
948
|
+
/**
|
|
949
|
+
* Map of type strings to model classes for polymorphic relationships.
|
|
950
|
+
*
|
|
951
|
+
* @example
|
|
952
|
+
* Model.morphMap = { Post, Video }
|
|
953
|
+
*/
|
|
954
|
+
static morphMap?: Record<string, typeof Model>;
|
|
955
|
+
/**
|
|
956
|
+
* Define a one-to-many polymorphic relationship.
|
|
957
|
+
*
|
|
958
|
+
* @param name - Relation name
|
|
959
|
+
* @param related - The related model class
|
|
960
|
+
* @param morphName - Base name for the type/id columns (e.g. 'commentable' → commentableType + commentableId)
|
|
961
|
+
* @param localKey - Local key on this model (default: 'id')
|
|
962
|
+
*
|
|
963
|
+
* @example
|
|
964
|
+
* Post.morphMany('comments', Comment, 'commentable', 'id')
|
|
965
|
+
*/
|
|
966
|
+
static morphMany<This extends typeof Model, Related extends typeof Model, Name extends RelationKeyOrString<This>>(this: This, name: Name, related: Related | (() => Related | Promise<Related>), morphName: string, localKey?: string): void;
|
|
967
|
+
/**
|
|
968
|
+
* Define the inverse of a polymorphic relationship.
|
|
969
|
+
*
|
|
970
|
+
* @param name - Relation name
|
|
971
|
+
* @param morphName - Base name for the type/id columns
|
|
972
|
+
*
|
|
973
|
+
* @example
|
|
974
|
+
* Comment.morphTo('commentable', 'commentable')
|
|
975
|
+
*/
|
|
976
|
+
static morphTo<This extends typeof Model, Name extends RelationKeyOrString<This>>(this: This, name: Name, morphName: string): void;
|
|
977
|
+
/**
|
|
978
|
+
* Get records sorted by the specified order.
|
|
979
|
+
*
|
|
980
|
+
* @param order - Order expression(s)
|
|
981
|
+
* @param where - Optional filter conditions
|
|
982
|
+
* @returns Sorted array of records
|
|
983
|
+
*
|
|
984
|
+
* @example
|
|
985
|
+
* // Single column ascending
|
|
986
|
+
* await User.orderBy('createdAt')
|
|
987
|
+
*
|
|
988
|
+
* // Single column descending
|
|
989
|
+
* await User.orderBy(['createdAt', 'desc'])
|
|
990
|
+
*
|
|
991
|
+
* // Multiple columns
|
|
992
|
+
* await User.orderBy([['lastName', 'asc'], ['firstName', 'asc']])
|
|
993
|
+
*
|
|
994
|
+
* // With where clause
|
|
995
|
+
* await User.orderBy('name', { status: 'active' })
|
|
996
|
+
*/
|
|
997
|
+
static orderBy<T extends typeof Model>(this: T, order: OrderByInput<TRecordFor<T>>, where?: WhereClauseFor<T>, queryOptions?: ModelQueryOptions): Promise<TRecordFor<T>[]>;
|
|
998
|
+
/**
|
|
999
|
+
* Get paginated records.
|
|
1000
|
+
*
|
|
1001
|
+
* @param options - Pagination options
|
|
1002
|
+
* @returns Paginated result with data and metadata
|
|
1003
|
+
*
|
|
1004
|
+
* @example
|
|
1005
|
+
* const result = await User.paginate({ page: 1, perPage: 10 })
|
|
1006
|
+
* // result.data - Array of users
|
|
1007
|
+
* // result.meta.total - Total count
|
|
1008
|
+
* // result.meta.hasMore - Whether there are more pages
|
|
1009
|
+
*
|
|
1010
|
+
* // With filtering and ordering
|
|
1011
|
+
* await User.paginate({
|
|
1012
|
+
* page: 2,
|
|
1013
|
+
* perPage: 20,
|
|
1014
|
+
* where: { status: 'active' },
|
|
1015
|
+
* orderBy: ['createdAt', 'desc']
|
|
1016
|
+
* })
|
|
1017
|
+
*/
|
|
1018
|
+
static paginate<T extends typeof Model>(this: T, options?: PaginateOptions<TRecordFor<T>>, queryOptions?: ModelQueryOptions): Promise<PaginatedResult<TRecordFor<T>>>;
|
|
1019
|
+
/**
|
|
1020
|
+
* Paginate records with eager-loaded relationships.
|
|
1021
|
+
*
|
|
1022
|
+
* @param relations - Relation name(s) to load
|
|
1023
|
+
* @param options - Pagination options
|
|
1024
|
+
* @returns Paginated result with loaded relationships
|
|
1025
|
+
*
|
|
1026
|
+
* @example
|
|
1027
|
+
* const result = await User.withPaginate('posts', { page: 1, perPage: 10 })
|
|
1028
|
+
* // result.data - Users with their posts loaded
|
|
1029
|
+
* // result.meta - Pagination metadata
|
|
1030
|
+
*/
|
|
1031
|
+
static withPaginate<T extends typeof Model, K extends RelationKey<T>>(this: T, relations: K | readonly K[], options?: PaginateOptions<TRecordFor<T>>): Promise<PaginatedResult<TRecordFor<T> & RelationTypePick<T, K | readonly K[]>>>;
|
|
1032
|
+
/**
|
|
1033
|
+
* Create a new record.
|
|
1034
|
+
*
|
|
1035
|
+
* @param data - Record data to insert
|
|
1036
|
+
* @returns The created record
|
|
1037
|
+
*
|
|
1038
|
+
* @example
|
|
1039
|
+
* const user = await User.create({
|
|
1040
|
+
* name: 'John Doe',
|
|
1041
|
+
* email: 'john@example.com'
|
|
1042
|
+
* })
|
|
1043
|
+
*/
|
|
1044
|
+
static create<T extends typeof Model>(this: T, data: TCreateFor<T>, writeOptions?: ModelWriteOptions): Promise<TRecordFor<T>>;
|
|
1045
|
+
/**
|
|
1046
|
+
* Update records matching the conditions.
|
|
1047
|
+
*
|
|
1048
|
+
* @param where - Filter conditions to identify records
|
|
1049
|
+
* @param data - Data to update
|
|
1050
|
+
* @returns The updated record
|
|
1051
|
+
*
|
|
1052
|
+
* @example
|
|
1053
|
+
* await User.update({ id: 1 }, { name: 'Jane Doe' })
|
|
1054
|
+
*/
|
|
1055
|
+
static update<T extends typeof Model>(this: T, where: WhereClauseFor<T>, data: Partial<TCreateFor<T>>, writeOptions?: ModelWriteOptions): Promise<TRecordFor<T>>;
|
|
1056
|
+
/**
|
|
1057
|
+
* Delete records matching the conditions.
|
|
1058
|
+
*
|
|
1059
|
+
* @param where - Filter conditions to identify records
|
|
1060
|
+
* @returns Number of deleted records or void depending on adapter
|
|
1061
|
+
*
|
|
1062
|
+
* @example
|
|
1063
|
+
* await User.delete({ id: 1 })
|
|
1064
|
+
* await User.delete({ status: 'inactive' })
|
|
1065
|
+
*/
|
|
1066
|
+
static delete<T extends typeof Model>(this: T, where: WhereClauseFor<T>, writeOptions?: ModelWriteOptions): Promise<number | PlainObject | void>;
|
|
1067
|
+
/**
|
|
1068
|
+
* Get a raw Drizzle query builder for complex queries.
|
|
1069
|
+
*
|
|
1070
|
+
* @param db - Optional Drizzle database instance
|
|
1071
|
+
* @returns Drizzle query builder starting with `select().from(table)`
|
|
1072
|
+
*
|
|
1073
|
+
* @example
|
|
1074
|
+
* // Simple query
|
|
1075
|
+
* const users = await User.query(db)
|
|
1076
|
+
* .where(eq(users.status, 'active'))
|
|
1077
|
+
* .limit(10)
|
|
1078
|
+
*
|
|
1079
|
+
* // With joins
|
|
1080
|
+
* const usersWithPosts = await User.query(db)
|
|
1081
|
+
* .leftJoin(posts, eq(users.id, posts.userId))
|
|
1082
|
+
*/
|
|
1083
|
+
static query<TDatabase extends {
|
|
1084
|
+
select: (...args: any[]) => any;
|
|
1085
|
+
} = {
|
|
1086
|
+
select: (...args: any[]) => any;
|
|
1087
|
+
}>(// eslint-disable-line @typescript-eslint/no-explicit-any
|
|
1088
|
+
this: typeof Model, db?: TDatabase): SelectFrom<TDatabase>;
|
|
1089
|
+
/**
|
|
1090
|
+
* Eager-load relationships on records.
|
|
1091
|
+
*
|
|
1092
|
+
* @param relations - Relation name(s) to load
|
|
1093
|
+
* @param where - Optional filter conditions
|
|
1094
|
+
* @returns Records with loaded relationships
|
|
1095
|
+
*
|
|
1096
|
+
* @example
|
|
1097
|
+
* // Single relation
|
|
1098
|
+
* const usersWithPosts = await User.with('posts')
|
|
1099
|
+
*
|
|
1100
|
+
* // Multiple relations
|
|
1101
|
+
* const usersWithAll = await User.with(['posts', 'comments'])
|
|
1102
|
+
*
|
|
1103
|
+
* // With filtering
|
|
1104
|
+
* const activeUsersWithPosts = await User.with('posts', { status: 'active' })
|
|
1105
|
+
*/
|
|
1106
|
+
static with<T extends typeof Model, K extends RelationKey<T>>(this: T, relations: K | readonly K[], where?: WhereClauseFor<T>): Promise<Array<TRecordFor<T> & RelationTypePick<T, K | readonly K[]>>>;
|
|
1107
|
+
/** @internal Used by QueryBuilder for eager loading. */
|
|
1108
|
+
static loadRelationInto<T extends typeof Model>(this: T, records: Array<PlainObject>, relationName: string): Promise<void>;
|
|
1109
|
+
protected static loadHasMany(records: Array<PlainObject>, definition: HasManyRelationDefinition): Promise<void>;
|
|
1110
|
+
protected static loadHasOne(records: Array<PlainObject>, definition: HasOneRelationDefinition): Promise<void>;
|
|
1111
|
+
protected static loadBelongsTo(records: Array<PlainObject>, definition: BelongsToRelationDefinition): Promise<void>;
|
|
1112
|
+
protected static loadBelongsToMany(records: Array<PlainObject>, definition: BelongsToManyRelationDefinition): Promise<void>;
|
|
1113
|
+
protected static loadHasManyThrough(records: Array<PlainObject>, definition: HasManyThroughRelationDefinition): Promise<void>;
|
|
1114
|
+
protected static loadMorphMany(records: Array<PlainObject>, definition: MorphManyRelationDefinition): Promise<void>;
|
|
1115
|
+
protected static loadMorphTo(records: Array<PlainObject>, definition: MorphToRelationDefinition): Promise<void>;
|
|
1116
|
+
}
|
|
1117
|
+
type TRecordFor<T extends typeof Model> = T extends {
|
|
1118
|
+
recordType: infer R;
|
|
1119
|
+
} ? R extends PlainObject ? R : PlainObject : PlainObject;
|
|
1120
|
+
type TCreateFor<T extends typeof Model> = T extends {
|
|
1121
|
+
createType: infer R;
|
|
1122
|
+
} ? R extends PlainObject ? R : PlainObject : PlainObject;
|
|
1123
|
+
type WhereClauseFor<T extends typeof Model> = WhereClause<TRecordFor<T>>;
|
|
1124
|
+
type FieldFor<T extends typeof Model> = keyof TRecordFor<T> & string;
|
|
1125
|
+
type RelationTypesFor<T extends typeof Model> = T extends {
|
|
1126
|
+
relationTypes: infer R;
|
|
1127
|
+
} ? R extends RelationShape ? R : {} : {};
|
|
1128
|
+
type RelationKey<T extends typeof Model> = keyof RelationTypesFor<T> & string;
|
|
1129
|
+
type RelationKeyOrString<T extends typeof Model> = RelationKey<T> extends never ? string : RelationKey<T>;
|
|
1130
|
+
type RelationNameUnion<Names> = Names extends readonly (infer Items)[] ? Items : Names;
|
|
1131
|
+
type RelationTypePick<T extends typeof Model, Names> = RelationNameUnion<Names> extends infer Keys ? Keys extends string ? {
|
|
1132
|
+
[K in Keys & keyof RelationTypesFor<T>]: RelationTypesFor<T>[K];
|
|
1133
|
+
} : {} : {};
|
|
1134
|
+
interface TransactionModelScope<T extends typeof Model> {
|
|
1135
|
+
readonly trx: TransactionHandle;
|
|
1136
|
+
all(): Promise<TRecordFor<T>[]>;
|
|
1137
|
+
find(id: unknown): Promise<TRecordFor<T> | null>;
|
|
1138
|
+
findOrFail(id: unknown): Promise<TRecordFor<T>>;
|
|
1139
|
+
first(where?: WhereClauseFor<T>): Promise<TRecordFor<T> | null>;
|
|
1140
|
+
where(conditions: WhereClauseFor<T>): QueryBuilder<TRecordFor<T>>;
|
|
1141
|
+
where(field: FieldFor<T>, value: unknown): QueryBuilder<TRecordFor<T>>;
|
|
1142
|
+
where(field: FieldFor<T>, operator: WhereOperator, value: unknown): QueryBuilder<TRecordFor<T>>;
|
|
1143
|
+
newQuery(): QueryBuilder<TRecordFor<T>>;
|
|
1144
|
+
create(data: TCreateFor<T>): Promise<TRecordFor<T>>;
|
|
1145
|
+
update(where: WhereClauseFor<T>, data: Partial<TCreateFor<T>>): Promise<TRecordFor<T>>;
|
|
1146
|
+
delete(where: WhereClauseFor<T>): Promise<number | PlainObject | void>;
|
|
1147
|
+
paginate(options?: PaginateOptions<TRecordFor<T>>): Promise<PaginatedResult<TRecordFor<T>>>;
|
|
1148
|
+
}
|
|
1149
|
+
interface BaseRelationDefinition {
|
|
1150
|
+
type: 'hasMany' | 'hasOne' | 'belongsTo' | 'belongsToMany' | 'hasManyThrough' | 'morphMany' | 'morphTo';
|
|
1151
|
+
name: string;
|
|
1152
|
+
related: typeof Model | (() => typeof Model | Promise<typeof Model>);
|
|
1153
|
+
}
|
|
1154
|
+
interface HasManyRelationDefinition extends BaseRelationDefinition {
|
|
1155
|
+
type: 'hasMany';
|
|
1156
|
+
foreignKey: string;
|
|
1157
|
+
localKey: string;
|
|
1158
|
+
}
|
|
1159
|
+
interface HasOneRelationDefinition extends BaseRelationDefinition {
|
|
1160
|
+
type: 'hasOne';
|
|
1161
|
+
foreignKey: string;
|
|
1162
|
+
localKey: string;
|
|
1163
|
+
}
|
|
1164
|
+
interface BelongsToRelationDefinition extends BaseRelationDefinition {
|
|
1165
|
+
type: 'belongsTo';
|
|
1166
|
+
foreignKey: string;
|
|
1167
|
+
ownerKey: string;
|
|
1168
|
+
}
|
|
1169
|
+
interface BelongsToManyRelationDefinition extends BaseRelationDefinition {
|
|
1170
|
+
type: 'belongsToMany';
|
|
1171
|
+
pivotTable: unknown;
|
|
1172
|
+
foreignPivotKey: string;
|
|
1173
|
+
relatedPivotKey: string;
|
|
1174
|
+
parentKey: string;
|
|
1175
|
+
relatedKey: string;
|
|
1176
|
+
}
|
|
1177
|
+
interface HasManyThroughRelationDefinition extends BaseRelationDefinition {
|
|
1178
|
+
type: 'hasManyThrough';
|
|
1179
|
+
through: typeof Model | (() => typeof Model | Promise<typeof Model>);
|
|
1180
|
+
firstKey: string;
|
|
1181
|
+
secondKey: string;
|
|
1182
|
+
localKey: string;
|
|
1183
|
+
secondLocalKey: string;
|
|
1184
|
+
}
|
|
1185
|
+
type RelationDefinition = HasManyRelationDefinition | HasOneRelationDefinition | BelongsToRelationDefinition | BelongsToManyRelationDefinition | HasManyThroughRelationDefinition | MorphManyRelationDefinition | MorphToRelationDefinition;
|
|
1186
|
+
interface MorphManyRelationDefinition extends BaseRelationDefinition {
|
|
1187
|
+
type: 'morphMany';
|
|
1188
|
+
morphName: string;
|
|
1189
|
+
localKey: string;
|
|
1190
|
+
}
|
|
1191
|
+
interface MorphToRelationDefinition {
|
|
1192
|
+
type: 'morphTo';
|
|
1193
|
+
name: string;
|
|
1194
|
+
related: undefined;
|
|
1195
|
+
morphName: string;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
type SessionData = Record<string, unknown>;
|
|
1199
|
+
interface SessionStore {
|
|
1200
|
+
/**
|
|
1201
|
+
* Read a session by its opaque identifier.
|
|
1202
|
+
* Returns undefined when the session does not exist or has expired.
|
|
1203
|
+
*/
|
|
1204
|
+
read(id: string): Promise<SessionData | undefined>;
|
|
1205
|
+
/**
|
|
1206
|
+
* Persist a session using upsert semantics for the given opaque identifier.
|
|
1207
|
+
*/
|
|
1208
|
+
write(id: string, data: SessionData, ttlSeconds: number): Promise<void>;
|
|
1209
|
+
/**
|
|
1210
|
+
* Destroy a session. Implementations must treat repeated calls as safe.
|
|
1211
|
+
*/
|
|
1212
|
+
destroy(id: string): Promise<void>;
|
|
1213
|
+
}
|
|
1214
|
+
declare class MemorySessionStore implements SessionStore {
|
|
1215
|
+
private readonly now;
|
|
1216
|
+
private readonly store;
|
|
1217
|
+
constructor(now?: () => number);
|
|
1218
|
+
read(id: string): Promise<SessionData | undefined>;
|
|
1219
|
+
write(id: string, data: SessionData, ttlSeconds: number): Promise<void>;
|
|
1220
|
+
destroy(id: string): Promise<void>;
|
|
1221
|
+
}
|
|
1222
|
+
interface SessionOptions {
|
|
1223
|
+
cookieName?: string;
|
|
1224
|
+
cookiePath?: string;
|
|
1225
|
+
cookieDomain?: string;
|
|
1226
|
+
cookieSecure?: boolean;
|
|
1227
|
+
cookieSameSite?: 'Strict' | 'Lax' | 'None';
|
|
1228
|
+
cookieHttpOnly?: boolean;
|
|
1229
|
+
cookieMaxAgeSeconds?: number;
|
|
1230
|
+
ttlSeconds?: number;
|
|
1231
|
+
store?: SessionStore;
|
|
1232
|
+
}
|
|
1233
|
+
interface Session {
|
|
1234
|
+
readonly id: string;
|
|
1235
|
+
readonly isNew: boolean;
|
|
1236
|
+
get<T = unknown>(key: string): T | undefined;
|
|
1237
|
+
set<T = unknown>(key: string, value: T): void;
|
|
1238
|
+
has(key: string): boolean;
|
|
1239
|
+
forget(key: string): void;
|
|
1240
|
+
flush(): void;
|
|
1241
|
+
all(): SessionData;
|
|
1242
|
+
regenerate(): void;
|
|
1243
|
+
invalidate(): void;
|
|
1244
|
+
flash(key: string, value: unknown): void;
|
|
1245
|
+
getFlash<T = unknown>(key: string): T | undefined;
|
|
1246
|
+
reflash(): void;
|
|
1247
|
+
keep(...keys: string[]): void;
|
|
1248
|
+
}
|
|
1249
|
+
interface CreateSessionMiddlewareOptions extends SessionOptions {
|
|
1250
|
+
}
|
|
1251
|
+
declare function createSessionMiddleware(options?: CreateSessionMiddlewareOptions): MiddlewareHandler;
|
|
1252
|
+
declare function getSessionFromContext<T extends Session = Session>(ctx: {
|
|
1253
|
+
get: (key: string) => unknown;
|
|
1254
|
+
}): T | undefined;
|
|
1255
|
+
|
|
1256
|
+
type AuthCredentials = Record<string, unknown>;
|
|
1257
|
+
interface Authenticatable {
|
|
1258
|
+
getAuthIdentifier(): unknown;
|
|
1259
|
+
getAuthPassword(): string | null | undefined;
|
|
1260
|
+
getRememberToken?(): string | null | undefined;
|
|
1261
|
+
setRememberToken?(token: string | null): void | Promise<void>;
|
|
1262
|
+
}
|
|
1263
|
+
interface Guard<User = Authenticatable> {
|
|
1264
|
+
check(): Promise<boolean>;
|
|
1265
|
+
guest(): Promise<boolean>;
|
|
1266
|
+
user<T = User>(): Promise<T | null>;
|
|
1267
|
+
id(): Promise<unknown>;
|
|
1268
|
+
login<T = User>(user: T, remember?: boolean): Promise<void>;
|
|
1269
|
+
logout(): Promise<void>;
|
|
1270
|
+
attempt(credentials: AuthCredentials, remember?: boolean): Promise<boolean>;
|
|
1271
|
+
validate(credentials: AuthCredentials): Promise<User | null>;
|
|
1272
|
+
session<T extends Session = Session>(): T | undefined;
|
|
1273
|
+
}
|
|
1274
|
+
interface UserProvider<User = Authenticatable> {
|
|
1275
|
+
retrieveById(identifier: unknown): Promise<User | null>;
|
|
1276
|
+
retrieveByCredentials(credentials: AuthCredentials): Promise<User | null>;
|
|
1277
|
+
validateCredentials(user: User, credentials: AuthCredentials): Promise<boolean>;
|
|
1278
|
+
getId(user: User): unknown;
|
|
1279
|
+
setRememberToken?(user: User, token: string | null): Promise<void> | void;
|
|
1280
|
+
getRememberToken?(user: User): Promise<string | null> | string | null;
|
|
1281
|
+
}
|
|
1282
|
+
interface GuardContext {
|
|
1283
|
+
ctx: Context;
|
|
1284
|
+
session: Session | undefined;
|
|
1285
|
+
manager: AuthManagerContract;
|
|
1286
|
+
}
|
|
1287
|
+
type GuardFactory<User = Authenticatable> = (context: GuardContext) => Guard<User>;
|
|
1288
|
+
interface ProviderFactory<User = Authenticatable> {
|
|
1289
|
+
(manager: AuthManagerContract): UserProvider<User>;
|
|
1290
|
+
}
|
|
1291
|
+
interface AuthManagerOptions {
|
|
1292
|
+
defaultGuard?: string;
|
|
1293
|
+
}
|
|
1294
|
+
interface AttachContextOptions {
|
|
1295
|
+
guard?: string;
|
|
1296
|
+
}
|
|
1297
|
+
interface AuthContext<User = Authenticatable> {
|
|
1298
|
+
check(): Promise<boolean>;
|
|
1299
|
+
guest(): Promise<boolean>;
|
|
1300
|
+
user<T = User>(): Promise<T | null>;
|
|
1301
|
+
userOrFail<T = User>(): Promise<T>;
|
|
1302
|
+
id(): Promise<unknown>;
|
|
1303
|
+
login<T = User>(user: T, remember?: boolean): Promise<void>;
|
|
1304
|
+
attempt(credentials: AuthCredentials, remember?: boolean): Promise<boolean>;
|
|
1305
|
+
logout(): Promise<void>;
|
|
1306
|
+
guard<T = User>(name?: string): Guard<T>;
|
|
1307
|
+
session<T extends Session = Session>(): T | undefined;
|
|
1308
|
+
}
|
|
1309
|
+
interface AuthManagerContract {
|
|
1310
|
+
registerGuard<User = Authenticatable>(name: string, factory: GuardFactory<User>): void;
|
|
1311
|
+
registerProvider<User = Authenticatable>(name: string, factory: ProviderFactory<User>): void;
|
|
1312
|
+
getProvider<User = Authenticatable>(name: string): UserProvider<User>;
|
|
1313
|
+
createGuard<User = Authenticatable>(name: string, context: GuardContext): Guard<User>;
|
|
1314
|
+
guardNames(): string[];
|
|
1315
|
+
setDefaultGuard(name: string): void;
|
|
1316
|
+
getDefaultGuard(): string;
|
|
1317
|
+
createAuthContext(ctx: Context, options?: AttachContextOptions): AuthContext;
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
interface PasswordHasher {
|
|
1321
|
+
hash(plain: string): Promise<string>;
|
|
1322
|
+
verify(hashed: string, plain: string): Promise<boolean>;
|
|
1323
|
+
needsRehash?(hashed: string): boolean;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
declare abstract class BaseUserProvider<User extends Authenticatable = Authenticatable> implements UserProvider<User> {
|
|
1327
|
+
abstract retrieveById(identifier: unknown): Promise<User | null>;
|
|
1328
|
+
abstract retrieveByCredentials(credentials: AuthCredentials): Promise<User | null>;
|
|
1329
|
+
abstract validateCredentials(user: User, credentials: AuthCredentials): Promise<boolean>;
|
|
1330
|
+
abstract getId(user: User): unknown;
|
|
1331
|
+
setRememberToken(user: User, token: string | null): Promise<void>;
|
|
1332
|
+
getRememberToken(user: User): Promise<string | null>;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
interface ModelUserProviderOptions {
|
|
1336
|
+
idColumn?: string;
|
|
1337
|
+
usernameColumn?: string;
|
|
1338
|
+
passwordColumn?: string;
|
|
1339
|
+
rememberTokenColumn?: string;
|
|
1340
|
+
hasher?: PasswordHasher;
|
|
1341
|
+
credentialsPasswordField?: string;
|
|
1342
|
+
}
|
|
1343
|
+
declare class ModelUserProvider<User extends Authenticatable = Authenticatable> extends BaseUserProvider<User> {
|
|
1344
|
+
private readonly model;
|
|
1345
|
+
private readonly idColumn;
|
|
1346
|
+
private readonly usernameColumn;
|
|
1347
|
+
private readonly passwordColumn;
|
|
1348
|
+
private readonly rememberTokenColumn;
|
|
1349
|
+
private readonly hasher;
|
|
1350
|
+
private readonly credentialsPasswordField;
|
|
1351
|
+
constructor(model: typeof Model, options?: ModelUserProviderOptions);
|
|
1352
|
+
private cast;
|
|
1353
|
+
retrieveById(identifier: unknown): Promise<User | null>;
|
|
1354
|
+
retrieveByCredentials(credentials: AuthCredentials): Promise<User | null>;
|
|
1355
|
+
validateCredentials(user: User, credentials: AuthCredentials): Promise<boolean>;
|
|
1356
|
+
getId(user: User): unknown;
|
|
1357
|
+
setRememberToken(user: User, token: string | null): Promise<void>;
|
|
1358
|
+
getRememberToken(user: User): Promise<string | null>;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
declare class AuthManager implements AuthManagerContract {
|
|
1362
|
+
private readonly guards;
|
|
1363
|
+
private readonly providers;
|
|
1364
|
+
private defaultGuard;
|
|
1365
|
+
constructor(options?: AuthManagerOptions);
|
|
1366
|
+
registerGuard<User>(name: string, factory: GuardFactory<User>): void;
|
|
1367
|
+
registerProvider<User>(name: string, factory: ProviderFactory<User>): void;
|
|
1368
|
+
getProvider<User>(name: string): UserProvider<User>;
|
|
1369
|
+
createGuard<User>(name: string, context: GuardContext): Guard<User>;
|
|
1370
|
+
guardNames(): string[];
|
|
1371
|
+
setDefaultGuard(name: string): void;
|
|
1372
|
+
getDefaultGuard(): string;
|
|
1373
|
+
createAuthContext(ctx: Context, options?: AttachContextOptions): AuthContext;
|
|
1374
|
+
attempt(name: string, ctx: Context, credentials: AuthCredentials, remember?: boolean): Promise<boolean>;
|
|
1375
|
+
/**
|
|
1376
|
+
* Shorthand method to register a model-based authentication provider and session guard.
|
|
1377
|
+
* This simplifies the common case of authenticating users via a database model.
|
|
1378
|
+
*
|
|
1379
|
+
* @param model - The model class to use for user authentication
|
|
1380
|
+
* @param options - Options for the ModelUserProvider (partial, with defaults)
|
|
1381
|
+
* @param providerName - Name for the provider (defaults to 'users')
|
|
1382
|
+
* @param guardName - Name for the guard (defaults to 'web')
|
|
1383
|
+
*/
|
|
1384
|
+
useModel(model: typeof Model<PlainObject>, options?: Partial<ModelUserProviderOptions>, providerName?: string, guardName?: string): void;
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
interface OAuthProviderConfig {
|
|
1388
|
+
clientId: string;
|
|
1389
|
+
clientSecret: string;
|
|
1390
|
+
redirectUri: string;
|
|
1391
|
+
authorizeUrl: string;
|
|
1392
|
+
tokenUrl: string;
|
|
1393
|
+
userInfoUrl: string;
|
|
1394
|
+
scopes?: string[];
|
|
1395
|
+
tokenAuthMethod?: 'client_secret_post' | 'client_secret_basic';
|
|
1396
|
+
userInfoMethod?: 'GET' | 'POST';
|
|
1397
|
+
mapProfile?: (raw: Record<string, unknown>, token: OAuthTokenResult) => OAuthUserProfile;
|
|
1398
|
+
}
|
|
1399
|
+
interface OAuthTokenResult {
|
|
1400
|
+
accessToken: string;
|
|
1401
|
+
tokenType?: string;
|
|
1402
|
+
refreshToken?: string;
|
|
1403
|
+
expiresIn?: number;
|
|
1404
|
+
scope?: string;
|
|
1405
|
+
raw: Record<string, unknown>;
|
|
1406
|
+
}
|
|
1407
|
+
interface OAuthUserProfile {
|
|
1408
|
+
id: string;
|
|
1409
|
+
email?: string;
|
|
1410
|
+
name?: string;
|
|
1411
|
+
avatar?: string;
|
|
1412
|
+
token: OAuthTokenResult;
|
|
1413
|
+
raw: Record<string, unknown>;
|
|
1414
|
+
}
|
|
1415
|
+
interface OAuthStatePayload {
|
|
1416
|
+
provider: string;
|
|
1417
|
+
redirectTo?: string;
|
|
1418
|
+
expiresAt: Date;
|
|
1419
|
+
}
|
|
1420
|
+
interface OAuthStateStore {
|
|
1421
|
+
store(stateHash: string, payload: OAuthStatePayload): Promise<void>;
|
|
1422
|
+
find(stateHash: string): Promise<OAuthStatePayload | null>;
|
|
1423
|
+
delete(stateHash: string): Promise<void>;
|
|
1424
|
+
}
|
|
1425
|
+
interface OAuthStateConfig {
|
|
1426
|
+
expiresIn?: number;
|
|
1427
|
+
stateLength?: number;
|
|
1428
|
+
hashAlgorithm?: 'sha256' | 'sha512';
|
|
1429
|
+
}
|
|
1430
|
+
interface OAuthAuthorizeOptions {
|
|
1431
|
+
scope?: string[];
|
|
1432
|
+
redirectTo?: string;
|
|
1433
|
+
state?: string;
|
|
1434
|
+
extraParams?: Record<string, string>;
|
|
1435
|
+
}
|
|
1436
|
+
interface OAuthCallbackPayload {
|
|
1437
|
+
code: string;
|
|
1438
|
+
state: string;
|
|
1439
|
+
}
|
|
1440
|
+
interface OAuthManagerOptions {
|
|
1441
|
+
stateStore?: OAuthStateStore;
|
|
1442
|
+
stateConfig?: OAuthStateConfig;
|
|
1443
|
+
}
|
|
1444
|
+
declare class MemoryOAuthStateStore implements OAuthStateStore {
|
|
1445
|
+
private readonly states;
|
|
1446
|
+
store(stateHash: string, payload: OAuthStatePayload): Promise<void>;
|
|
1447
|
+
find(stateHash: string): Promise<OAuthStatePayload | null>;
|
|
1448
|
+
delete(stateHash: string): Promise<void>;
|
|
1449
|
+
clear(): void;
|
|
1450
|
+
}
|
|
1451
|
+
declare class OAuthManager {
|
|
1452
|
+
private readonly providers;
|
|
1453
|
+
private readonly stateStore;
|
|
1454
|
+
private readonly stateConfig;
|
|
1455
|
+
constructor(options?: OAuthManagerOptions);
|
|
1456
|
+
registerProvider(name: string, config: OAuthProviderConfig): void;
|
|
1457
|
+
providerNames(): string[];
|
|
1458
|
+
getProvider(name: string): OAuthProviderConfig;
|
|
1459
|
+
authorize(providerName: string, options?: OAuthAuthorizeOptions): Promise<{
|
|
1460
|
+
url: string;
|
|
1461
|
+
state: string;
|
|
1462
|
+
expiresAt: Date;
|
|
1463
|
+
}>;
|
|
1464
|
+
user(providerName: string, payload: OAuthCallbackPayload): Promise<OAuthUserProfile>;
|
|
1465
|
+
}
|
|
1466
|
+
declare function createOAuthManager(options?: OAuthManagerOptions): OAuthManager;
|
|
1467
|
+
declare function createOAuthState(provider: string, store: OAuthStateStore, config?: OAuthStateConfig, redirectTo?: string, fixedState?: string): Promise<{
|
|
1468
|
+
state: string;
|
|
1469
|
+
expiresAt: Date;
|
|
1470
|
+
}>;
|
|
1471
|
+
declare function verifyOAuthState(state: string, provider: string, store: OAuthStateStore, config?: OAuthStateConfig): Promise<OAuthStatePayload | null>;
|
|
1472
|
+
declare function buildOAuthAuthorizeUrl(provider: OAuthProviderConfig, state: string, options?: {
|
|
1473
|
+
scope?: string[];
|
|
1474
|
+
extraParams?: Record<string, string>;
|
|
1475
|
+
}): string;
|
|
1476
|
+
declare function exchangeOAuthCode(provider: OAuthProviderConfig, code: string): Promise<OAuthTokenResult>;
|
|
1477
|
+
declare function fetchOAuthUserProfile(provider: OAuthProviderConfig, token: OAuthTokenResult): Promise<OAuthUserProfile>;
|
|
1478
|
+
interface OAuthProviderFactoryInput {
|
|
1479
|
+
clientId: string;
|
|
1480
|
+
clientSecret: string;
|
|
1481
|
+
redirectUri: string;
|
|
1482
|
+
scopes?: string[];
|
|
1483
|
+
}
|
|
1484
|
+
declare function createGitHubOAuthProviderConfig(input: OAuthProviderFactoryInput): OAuthProviderConfig;
|
|
1485
|
+
declare function createGoogleOAuthProviderConfig(input: OAuthProviderFactoryInput): OAuthProviderConfig;
|
|
1486
|
+
declare function createDiscordOAuthProviderConfig(input: OAuthProviderFactoryInput): OAuthProviderConfig;
|
|
1487
|
+
declare function buildOAuthRedirectUrl(baseUrl: string, token: string, email?: string): string;
|
|
1488
|
+
declare function parseOAuthRedirectUrl(url: string): {
|
|
1489
|
+
token: string | null;
|
|
1490
|
+
email: string | null;
|
|
1491
|
+
};
|
|
1492
|
+
|
|
1493
|
+
/**
|
|
1494
|
+
* API token data stored in the backing store.
|
|
1495
|
+
*/
|
|
1496
|
+
interface ApiToken {
|
|
1497
|
+
id: string;
|
|
1498
|
+
name: string;
|
|
1499
|
+
hashedToken: string;
|
|
1500
|
+
userId: string | number;
|
|
1501
|
+
abilities: string[];
|
|
1502
|
+
lastUsedAt: Date | null;
|
|
1503
|
+
expiresAt: Date | null;
|
|
1504
|
+
createdAt: Date;
|
|
1505
|
+
}
|
|
1506
|
+
/**
|
|
1507
|
+
* Store interface for API tokens.
|
|
1508
|
+
* Implement this for database-backed storage.
|
|
1509
|
+
*/
|
|
1510
|
+
interface ApiTokenStore {
|
|
1511
|
+
/**
|
|
1512
|
+
* Store a new API token.
|
|
1513
|
+
*/
|
|
1514
|
+
store(token: ApiToken): Promise<void>;
|
|
1515
|
+
/**
|
|
1516
|
+
* Find a token by its hashed value.
|
|
1517
|
+
*/
|
|
1518
|
+
findByHashedToken(hashedToken: string): Promise<ApiToken | null>;
|
|
1519
|
+
/**
|
|
1520
|
+
* Find all tokens for a user.
|
|
1521
|
+
*/
|
|
1522
|
+
findByUserId(userId: string | number): Promise<ApiToken[]>;
|
|
1523
|
+
/**
|
|
1524
|
+
* Delete a token by its ID.
|
|
1525
|
+
*/
|
|
1526
|
+
delete(id: string): Promise<void>;
|
|
1527
|
+
/**
|
|
1528
|
+
* Delete all tokens for a user.
|
|
1529
|
+
*/
|
|
1530
|
+
deleteForUser(userId: string | number): Promise<void>;
|
|
1531
|
+
/**
|
|
1532
|
+
* Update the last used timestamp.
|
|
1533
|
+
*/
|
|
1534
|
+
updateLastUsed(id: string, timestamp: Date): Promise<void>;
|
|
1535
|
+
}
|
|
1536
|
+
/**
|
|
1537
|
+
* In-memory token store for testing.
|
|
1538
|
+
* Do NOT use in production - tokens will be lost on restart.
|
|
1539
|
+
*/
|
|
1540
|
+
declare class MemoryApiTokenStore implements ApiTokenStore {
|
|
1541
|
+
private tokens;
|
|
1542
|
+
private byHash;
|
|
1543
|
+
store(token: ApiToken): Promise<void>;
|
|
1544
|
+
findByHashedToken(hashedToken: string): Promise<ApiToken | null>;
|
|
1545
|
+
findByUserId(userId: string | number): Promise<ApiToken[]>;
|
|
1546
|
+
delete(id: string): Promise<void>;
|
|
1547
|
+
deleteForUser(userId: string | number): Promise<void>;
|
|
1548
|
+
updateLastUsed(id: string, timestamp: Date): Promise<void>;
|
|
1549
|
+
/**
|
|
1550
|
+
* Clear all tokens (useful for testing).
|
|
1551
|
+
*/
|
|
1552
|
+
clear(): void;
|
|
1553
|
+
/**
|
|
1554
|
+
* Get the number of stored tokens.
|
|
1555
|
+
*/
|
|
1556
|
+
get size(): number;
|
|
1557
|
+
}
|
|
1558
|
+
/**
|
|
1559
|
+
* Configuration for API token creation.
|
|
1560
|
+
*/
|
|
1561
|
+
interface CreateApiTokenOptions {
|
|
1562
|
+
/**
|
|
1563
|
+
* Human-readable name for the token.
|
|
1564
|
+
*/
|
|
1565
|
+
name: string;
|
|
1566
|
+
/**
|
|
1567
|
+
* User ID the token belongs to.
|
|
1568
|
+
*/
|
|
1569
|
+
userId: string | number;
|
|
1570
|
+
/**
|
|
1571
|
+
* Token abilities/scopes.
|
|
1572
|
+
* @default ['*'] (all abilities)
|
|
1573
|
+
*/
|
|
1574
|
+
abilities?: string[];
|
|
1575
|
+
/**
|
|
1576
|
+
* Token expiration time in milliseconds from now.
|
|
1577
|
+
* @default null (never expires)
|
|
1578
|
+
*/
|
|
1579
|
+
expiresIn?: number | null;
|
|
1580
|
+
/**
|
|
1581
|
+
* Token byte length (before encoding).
|
|
1582
|
+
* @default 32
|
|
1583
|
+
*/
|
|
1584
|
+
tokenLength?: number;
|
|
1585
|
+
}
|
|
1586
|
+
/**
|
|
1587
|
+
* Result of creating an API token.
|
|
1588
|
+
*/
|
|
1589
|
+
interface CreateApiTokenResult {
|
|
1590
|
+
/**
|
|
1591
|
+
* The full token to give to the user.
|
|
1592
|
+
* Format: {id}|{plainToken}
|
|
1593
|
+
* This is the ONLY time the plain token is available.
|
|
1594
|
+
*/
|
|
1595
|
+
plainTextToken: string;
|
|
1596
|
+
/**
|
|
1597
|
+
* The token record (without the plain token).
|
|
1598
|
+
*/
|
|
1599
|
+
token: ApiToken;
|
|
1600
|
+
}
|
|
1601
|
+
/**
|
|
1602
|
+
* Create a new API token.
|
|
1603
|
+
*
|
|
1604
|
+
* @example
|
|
1605
|
+
* ```ts
|
|
1606
|
+
* const { plainTextToken, token } = await createApiToken(store, {
|
|
1607
|
+
* name: 'My App Token',
|
|
1608
|
+
* userId: user.id,
|
|
1609
|
+
* abilities: ['read', 'write'],
|
|
1610
|
+
* expiresIn: 30 * 24 * 60 * 60 * 1000, // 30 days
|
|
1611
|
+
* })
|
|
1612
|
+
*
|
|
1613
|
+
* // Return plainTextToken to user - this is the only time it's available
|
|
1614
|
+
* return ctx.json({ token: plainTextToken })
|
|
1615
|
+
* ```
|
|
1616
|
+
*/
|
|
1617
|
+
declare function createApiToken(store: ApiTokenStore, options: CreateApiTokenOptions): Promise<CreateApiTokenResult>;
|
|
1618
|
+
/**
|
|
1619
|
+
* Parse a plain text token into its components.
|
|
1620
|
+
*/
|
|
1621
|
+
declare function parseApiToken(plainTextToken: string): {
|
|
1622
|
+
id: string;
|
|
1623
|
+
token: string;
|
|
1624
|
+
} | null;
|
|
1625
|
+
/**
|
|
1626
|
+
* Verify an API token and return the associated user ID.
|
|
1627
|
+
*
|
|
1628
|
+
* @example
|
|
1629
|
+
* ```ts
|
|
1630
|
+
* const result = await verifyApiToken(plainTextToken, store)
|
|
1631
|
+
*
|
|
1632
|
+
* if (!result) {
|
|
1633
|
+
* return ctx.json({ error: 'Invalid token' }, 401)
|
|
1634
|
+
* }
|
|
1635
|
+
*
|
|
1636
|
+
* console.log(`User ${result.userId} authenticated with abilities:`, result.abilities)
|
|
1637
|
+
* ```
|
|
1638
|
+
*/
|
|
1639
|
+
declare function verifyApiToken(plainTextToken: string, store: ApiTokenStore, options?: {
|
|
1640
|
+
updateLastUsed?: boolean;
|
|
1641
|
+
}): Promise<{
|
|
1642
|
+
token: ApiToken;
|
|
1643
|
+
userId: string | number;
|
|
1644
|
+
abilities: string[];
|
|
1645
|
+
} | null>;
|
|
1646
|
+
/**
|
|
1647
|
+
* Check if a token has a specific ability.
|
|
1648
|
+
*/
|
|
1649
|
+
declare function tokenCan(token: ApiToken | {
|
|
1650
|
+
abilities: string[];
|
|
1651
|
+
}, ability: string): boolean;
|
|
1652
|
+
/**
|
|
1653
|
+
* Check if a token has all specified abilities.
|
|
1654
|
+
*/
|
|
1655
|
+
declare function tokenCanAll(token: ApiToken | {
|
|
1656
|
+
abilities: string[];
|
|
1657
|
+
}, abilities: string[]): boolean;
|
|
1658
|
+
/**
|
|
1659
|
+
* Check if a token has any of the specified abilities.
|
|
1660
|
+
*/
|
|
1661
|
+
declare function tokenCanAny(token: ApiToken | {
|
|
1662
|
+
abilities: string[];
|
|
1663
|
+
}, abilities: string[]): boolean;
|
|
1664
|
+
/**
|
|
1665
|
+
* Revoke (delete) an API token.
|
|
1666
|
+
*
|
|
1667
|
+
* @example
|
|
1668
|
+
* ```ts
|
|
1669
|
+
* await revokeApiToken(tokenId, store)
|
|
1670
|
+
* ```
|
|
1671
|
+
*/
|
|
1672
|
+
declare function revokeApiToken(id: string, store: ApiTokenStore): Promise<void>;
|
|
1673
|
+
/**
|
|
1674
|
+
* Revoke all API tokens for a user.
|
|
1675
|
+
*
|
|
1676
|
+
* @example
|
|
1677
|
+
* ```ts
|
|
1678
|
+
* // Revoke all tokens when user changes password
|
|
1679
|
+
* await revokeAllApiTokens(user.id, store)
|
|
1680
|
+
* ```
|
|
1681
|
+
*/
|
|
1682
|
+
declare function revokeAllApiTokens(userId: string | number, store: ApiTokenStore): Promise<void>;
|
|
1683
|
+
/**
|
|
1684
|
+
* Get all API tokens for a user.
|
|
1685
|
+
*
|
|
1686
|
+
* @example
|
|
1687
|
+
* ```ts
|
|
1688
|
+
* const tokens = await getUserApiTokens(user.id, store)
|
|
1689
|
+
* // Returns tokens without the plain text (only metadata)
|
|
1690
|
+
* ```
|
|
1691
|
+
*/
|
|
1692
|
+
declare function getUserApiTokens(userId: string | number, store: ApiTokenStore): Promise<ApiToken[]>;
|
|
1693
|
+
/**
|
|
1694
|
+
* Context key for storing the authenticated token.
|
|
1695
|
+
*/
|
|
1696
|
+
declare const API_TOKEN_KEY = "guren:api-token";
|
|
1697
|
+
/**
|
|
1698
|
+
* Options for the bearer token middleware.
|
|
1699
|
+
*/
|
|
1700
|
+
interface BearerTokenMiddlewareOptions {
|
|
1701
|
+
/**
|
|
1702
|
+
* Token store implementation.
|
|
1703
|
+
*/
|
|
1704
|
+
store: ApiTokenStore;
|
|
1705
|
+
/**
|
|
1706
|
+
* Function to load the user from the user ID.
|
|
1707
|
+
*/
|
|
1708
|
+
loadUser?: (userId: string | number) => Promise<unknown>;
|
|
1709
|
+
/**
|
|
1710
|
+
* Required abilities for this route.
|
|
1711
|
+
* If not specified, any valid token is accepted.
|
|
1712
|
+
*/
|
|
1713
|
+
abilities?: string[];
|
|
1714
|
+
/**
|
|
1715
|
+
* Custom handler when authentication fails.
|
|
1716
|
+
*/
|
|
1717
|
+
onUnauthorized?: (ctx: Context) => Response | Promise<Response>;
|
|
1718
|
+
/**
|
|
1719
|
+
* Custom handler when token lacks required abilities.
|
|
1720
|
+
*/
|
|
1721
|
+
onForbidden?: (ctx: Context, required: string[]) => Response | Promise<Response>;
|
|
1722
|
+
/**
|
|
1723
|
+
* Header name to extract the token from.
|
|
1724
|
+
* @default 'Authorization'
|
|
1725
|
+
*/
|
|
1726
|
+
headerName?: string;
|
|
1727
|
+
/**
|
|
1728
|
+
* Whether to update the token's lastUsedAt timestamp.
|
|
1729
|
+
* @default true
|
|
1730
|
+
*/
|
|
1731
|
+
updateLastUsed?: boolean;
|
|
1732
|
+
}
|
|
1733
|
+
/**
|
|
1734
|
+
* Create middleware that authenticates requests using Bearer tokens.
|
|
1735
|
+
*
|
|
1736
|
+
* @example
|
|
1737
|
+
* ```ts
|
|
1738
|
+
* // Basic usage
|
|
1739
|
+
* app.use('/api/*', createBearerTokenMiddleware({ store }))
|
|
1740
|
+
*
|
|
1741
|
+
* // With ability requirement
|
|
1742
|
+
* router.delete('/api/posts/:id', [PostController, 'destroy'],
|
|
1743
|
+
* createBearerTokenMiddleware({
|
|
1744
|
+
* store,
|
|
1745
|
+
* abilities: ['posts:delete'],
|
|
1746
|
+
* })
|
|
1747
|
+
* )
|
|
1748
|
+
*
|
|
1749
|
+
* // With user loading
|
|
1750
|
+
* app.use('/api/*', createBearerTokenMiddleware({
|
|
1751
|
+
* store,
|
|
1752
|
+
* loadUser: async (userId) => User.find(userId),
|
|
1753
|
+
* }))
|
|
1754
|
+
* ```
|
|
1755
|
+
*/
|
|
1756
|
+
declare function createBearerTokenMiddleware(options: BearerTokenMiddlewareOptions): MiddlewareHandler;
|
|
1757
|
+
/**
|
|
1758
|
+
* Get the authenticated API token from the request context.
|
|
1759
|
+
*
|
|
1760
|
+
* @example
|
|
1761
|
+
* ```ts
|
|
1762
|
+
* router.get('/api/me', async (ctx) => {
|
|
1763
|
+
* const { token, userId, abilities } = getApiToken(ctx)!
|
|
1764
|
+
* return ctx.json({ userId, tokenName: token.name, abilities })
|
|
1765
|
+
* })
|
|
1766
|
+
* ```
|
|
1767
|
+
*/
|
|
1768
|
+
declare function getApiToken(ctx: Context): {
|
|
1769
|
+
token: ApiToken;
|
|
1770
|
+
userId: string | number;
|
|
1771
|
+
abilities: string[];
|
|
1772
|
+
} | null;
|
|
1773
|
+
/**
|
|
1774
|
+
* Get the authenticated API token from the request context, or throw.
|
|
1775
|
+
*
|
|
1776
|
+
* @throws {AuthenticationException} When no token is present in the context.
|
|
1777
|
+
*
|
|
1778
|
+
* @example
|
|
1779
|
+
* ```ts
|
|
1780
|
+
* router.get('/api/me', async (ctx) => {
|
|
1781
|
+
* const { token, userId, abilities } = getApiTokenOrFail(ctx)
|
|
1782
|
+
* return ctx.json({ userId, tokenName: token.name, abilities })
|
|
1783
|
+
* })
|
|
1784
|
+
* ```
|
|
1785
|
+
*/
|
|
1786
|
+
declare function getApiTokenOrFail(ctx: Context): {
|
|
1787
|
+
token: ApiToken;
|
|
1788
|
+
userId: string | number;
|
|
1789
|
+
abilities: string[];
|
|
1790
|
+
};
|
|
1791
|
+
|
|
1792
|
+
export { parseOAuthRedirectUrl as $, AuthManager as A, BaseUserProvider as B, type CreateSessionMiddlewareOptions as C, type SessionStore as D, buildOAuthAuthorizeUrl as E, buildOAuthRedirectUrl as F, type Guard as G, createApiToken as H, createBearerTokenMiddleware as I, createDiscordOAuthProviderConfig as J, createGitHubOAuthProviderConfig as K, createGoogleOAuthProviderConfig as L, MemoryApiTokenStore as M, createOAuthManager as N, OAuthManager as O, type ProviderFactory as P, createOAuthState as Q, createSessionMiddleware as R, type Session as S, exchangeOAuthCode as T, type UserProvider as U, fetchOAuthUserProfile as V, getApiToken as W, getApiTokenOrFail as X, getSessionFromContext as Y, getUserApiTokens as Z, parseApiToken as _, type AuthContext as a, revokeAllApiTokens as a0, revokeApiToken as a1, tokenCan as a2, tokenCanAll as a3, tokenCanAny as a4, verifyApiToken as a5, verifyOAuthState as a6, type PasswordHasher as a7, type PlainObject as a8, Model as a9, type AttachContextOptions as aa, type AuthManagerContract as ab, type ApiToken as b, API_TOKEN_KEY as c, type ApiTokenStore as d, type AuthCredentials as e, type AuthManagerOptions as f, type Authenticatable as g, type BearerTokenMiddlewareOptions as h, type CreateApiTokenOptions as i, type CreateApiTokenResult as j, type GuardContext as k, type GuardFactory as l, MemoryOAuthStateStore as m, MemorySessionStore as n, ModelUserProvider as o, type OAuthAuthorizeOptions as p, type OAuthCallbackPayload as q, type OAuthManagerOptions as r, type OAuthProviderConfig as s, type OAuthProviderFactoryInput as t, type OAuthStateConfig as u, type OAuthStatePayload as v, type OAuthStateStore as w, type OAuthTokenResult as x, type OAuthUserProfile as y, type SessionData as z };
|