@quvel-kit/core 1.3.20 → 1.3.21

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/index.d.ts CHANGED
@@ -42,6 +42,7 @@ export * from './utils/error.js';
42
42
  export * from './utils/scripts.js';
43
43
  export * from './utils/assets.js';
44
44
  export * from './utils/pagination.js';
45
+ export * from './orm/index.js';
45
46
  export { defineQuvelBoot } from './boot/quvel.js';
46
47
  export { serviceContainerPlugin } from './stores/plugins/serviceContainer.js';
47
48
  export { defineQuvelModule } from './module.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAUH,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AAGjC,YAAY,EACV,OAAO,IAAI,QAAQ,EACnB,eAAe,EACf,eAAe,EACf,eAAe,EACf,eAAe,GAChB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAGvD,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAG1D,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGtE,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAG7D,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,0BAA0B,CAAC;AACzC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AAGvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AAGtC,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAGlD,OAAO,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAG9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAGtD,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAC9E,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAUH,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AAGjC,YAAY,EACV,OAAO,IAAI,QAAQ,EACnB,eAAe,EACf,eAAe,EACf,eAAe,EACf,eAAe,GAChB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAGvD,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAG1D,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGtE,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAG7D,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,0BAA0B,CAAC;AACzC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AAGvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AAGtC,cAAc,gBAAgB,CAAC;AAG/B,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAGlD,OAAO,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAG9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAGtD,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAC9E,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC"}
package/dist/index.js CHANGED
@@ -53,6 +53,8 @@ export * from './utils/error.js';
53
53
  export * from './utils/scripts.js';
54
54
  export * from './utils/assets.js';
55
55
  export * from './utils/pagination.js';
56
+ // ORM - Active Record pattern for models
57
+ export * from './orm/index.js';
56
58
  // Boot
57
59
  export { defineQuvelBoot } from './boot/quvel.js';
58
60
  // Stores
@@ -0,0 +1,410 @@
1
+ import type { FilterOperator } from './types.js';
2
+ import type { ServiceContainer } from '../container/ServiceContainer.js';
3
+ import type { LengthAwarePaginatorResponse, SimplePaginatorResponse, CursorPaginatorResponse } from '../types/laravel.types.js';
4
+ /**
5
+ * Fluent Query Builder for Models
6
+ *
7
+ * Builds Spatie Query Builder compatible query strings and executes queries.
8
+ *
9
+ * Supports:
10
+ * - Filtering: where(), whereIn(), whereNotNull()
11
+ * - Eager loading: with()
12
+ * - Field selection: fields()
13
+ * - Sorting: sort()
14
+ * - Pagination: page(), perPage(), paginate()
15
+ *
16
+ * @template T - Model class type
17
+ * @template A - API response interface type
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * const users = await User.query()
22
+ * .where('status', 'active')
23
+ * .where('age', '>', 18)
24
+ * .with('posts.comments', 'profile')
25
+ * .fields('users', ['id', 'name', 'email'])
26
+ * .fields('posts', ['id', 'title'])
27
+ * .sort('-created_at', 'name')
28
+ * .paginate(20);
29
+ * ```
30
+ */
31
+ export declare class QueryBuilder<T = any, A = any> {
32
+ private modelClass;
33
+ private container?;
34
+ /**
35
+ * Map of filters to apply
36
+ * Key: field name, Value: operator and value
37
+ */
38
+ private filters;
39
+ /**
40
+ * Set of relationships to eager load
41
+ */
42
+ private includes;
43
+ /**
44
+ * Map of field selections per resource type
45
+ * Key: resource name (e.g., 'users'), Value: array of field names
46
+ */
47
+ private fieldSelections;
48
+ /**
49
+ * Array of sort fields (prefix with - for descending)
50
+ */
51
+ private sortFields;
52
+ /**
53
+ * Page number for pagination
54
+ */
55
+ private pageNumber?;
56
+ /**
57
+ * Items per page
58
+ */
59
+ private perPage?;
60
+ /**
61
+ * Cursor value for cursor pagination
62
+ */
63
+ private cursorValue?;
64
+ /**
65
+ * Constructor
66
+ *
67
+ * @param modelClass - Model class constructor
68
+ * @param container - ServiceContainer instance
69
+ */
70
+ constructor(modelClass: new (attrs: Partial<A>, container?: ServiceContainer) => T, container?: ServiceContainer | undefined);
71
+ /**
72
+ * Get the API service
73
+ * @throws Error if no container provided
74
+ */
75
+ private get api();
76
+ /**
77
+ * Get model configuration
78
+ */
79
+ private get config();
80
+ /**
81
+ * Add a where clause
82
+ *
83
+ * Supports two syntaxes:
84
+ * - where(field, value) - Equality check
85
+ * - where(field, operator, value) - Custom operator
86
+ *
87
+ * @param field - Field name
88
+ * @param operatorOrValue - Operator or value (if using equality)
89
+ * @param value - Value (if using custom operator)
90
+ * @returns this for method chaining
91
+ *
92
+ * @example
93
+ * ```typescript
94
+ * query.where('status', 'active') // status = 'active'
95
+ * query.where('age', '>', 18) // age > 18
96
+ * query.where('name', 'like', '%john%') // name like '%john%'
97
+ * ```
98
+ */
99
+ where(field: string, operatorOrValue: FilterOperator | any, value?: any): this;
100
+ /**
101
+ * Add a whereIn clause (field IN values)
102
+ *
103
+ * @param field - Field name
104
+ * @param values - Array of values
105
+ * @returns this for method chaining
106
+ *
107
+ * @example
108
+ * ```typescript
109
+ * query.whereIn('id', ['1', '2', '3'])
110
+ * ```
111
+ */
112
+ whereIn(field: string, values: any[]): this;
113
+ /**
114
+ * Add a whereNotNull clause
115
+ *
116
+ * @param field - Field name
117
+ * @returns this for method chaining
118
+ *
119
+ * @example
120
+ * ```typescript
121
+ * query.whereNotNull('email_verified_at')
122
+ * ```
123
+ */
124
+ whereNotNull(field: string): this;
125
+ /**
126
+ * Add a whereNull clause
127
+ *
128
+ * @param field - Field name
129
+ * @returns this for method chaining
130
+ *
131
+ * @example
132
+ * ```typescript
133
+ * query.whereNull('deleted_at')
134
+ * ```
135
+ */
136
+ whereNull(field: string): this;
137
+ /**
138
+ * Add eager loading relationships
139
+ *
140
+ * Supports nested relationships with dot notation
141
+ *
142
+ * @param relations - Relationship names (method names on model)
143
+ * @returns this for method chaining
144
+ *
145
+ * @example
146
+ * ```typescript
147
+ * query.with('posts', 'profile')
148
+ * query.with('posts.comments.author')
149
+ * query.with('posts.tags', 'posts.comments')
150
+ * ```
151
+ */
152
+ with(...relations: string[]): this;
153
+ /**
154
+ * Select specific fields for a resource type (sparse fieldsets)
155
+ *
156
+ * @param resource - Resource name (e.g., 'users', 'posts')
157
+ * @param fields - Array of field names to select
158
+ * @returns this for method chaining
159
+ *
160
+ * @example
161
+ * ```typescript
162
+ * query.fields('users', ['id', 'name', 'email'])
163
+ * query.fields('posts', ['id', 'title', 'created_at'])
164
+ * ```
165
+ */
166
+ fields(resource: string, fields: string[]): this;
167
+ /**
168
+ * Add sorting (prefix with - for descending)
169
+ *
170
+ * @param fields - Field names (prefix with '-' for DESC)
171
+ * @returns this for method chaining
172
+ *
173
+ * @example
174
+ * ```typescript
175
+ * query.sort('name') // ASC
176
+ * query.sort('-created_at') // DESC
177
+ * query.sort('-created_at', 'name') // Multiple fields
178
+ * ```
179
+ */
180
+ sort(...fields: string[]): this;
181
+ /**
182
+ * Set page number for pagination
183
+ *
184
+ * @param page - Page number (1-indexed)
185
+ * @returns this for method chaining
186
+ *
187
+ * @example
188
+ * ```typescript
189
+ * query.page(2)
190
+ * ```
191
+ */
192
+ page(page: number): this;
193
+ /**
194
+ * Set items per page
195
+ *
196
+ * @param count - Number of items per page
197
+ * @returns this for method chaining
198
+ *
199
+ * @example
200
+ * ```typescript
201
+ * query.perPage(50)
202
+ * ```
203
+ */
204
+ limit(count: number): this;
205
+ /**
206
+ * Set cursor for cursor pagination
207
+ *
208
+ * @param cursor - Cursor value from previous response
209
+ * @returns this for method chaining
210
+ *
211
+ * @example
212
+ * ```typescript
213
+ * query.cursor(response.meta.next_cursor)
214
+ * ```
215
+ */
216
+ cursor(cursor: string): this;
217
+ /**
218
+ * Build query parameters for Spatie Query Builder
219
+ *
220
+ * Converts fluent API calls into URL query string format:
221
+ * - include=posts.comments,profile
222
+ * - filter[status]=active
223
+ * - filter[age]=>:18
224
+ * - fields[users]=id,name,email
225
+ * - sort=-created_at,name
226
+ * - page=1&per_page=20
227
+ *
228
+ * @returns Record of query parameters for Axios
229
+ */
230
+ private buildParams;
231
+ /**
232
+ * Execute query and get all results
233
+ *
234
+ * @returns Array of model instances
235
+ *
236
+ * @example
237
+ * ```typescript
238
+ * const users = await User.query().where('active', true).get();
239
+ * ```
240
+ */
241
+ get(): Promise<T[]>;
242
+ /**
243
+ * Execute query and get first result
244
+ *
245
+ * @returns First model instance or null if none found
246
+ *
247
+ * @example
248
+ * ```typescript
249
+ * const user = await User.query().where('email', 'john@example.com').first();
250
+ * if (user) {
251
+ * console.log(user.name);
252
+ * }
253
+ * ```
254
+ */
255
+ first(): Promise<T | null>;
256
+ /**
257
+ * Execute query and get first result or fail
258
+ *
259
+ * @returns First model instance
260
+ * @throws Error if no results found
261
+ *
262
+ * @example
263
+ * ```typescript
264
+ * const user = await User.query().where('email', 'john@example.com').firstOrFail();
265
+ * // Throws if user not found
266
+ * ```
267
+ */
268
+ firstOrFail(): Promise<T>;
269
+ /**
270
+ * Execute query with length-aware pagination
271
+ *
272
+ * Returns paginated response with total count and page metadata
273
+ *
274
+ * @param perPageCount - Items per page (default: 15)
275
+ * @returns Paginated response with metadata
276
+ *
277
+ * @example
278
+ * ```typescript
279
+ * const paginated = await User.query().where('active', true).paginate(20);
280
+ * console.log(paginated.data); // Array of User models
281
+ * console.log(paginated.meta.total); // Total count
282
+ * console.log(paginated.meta.current_page); // Current page
283
+ * console.log(paginated.links.next); // Next page URL
284
+ * ```
285
+ */
286
+ paginate(perPageCount?: number): Promise<LengthAwarePaginatorResponse<T>>;
287
+ /**
288
+ * Execute query with simple pagination (no total count)
289
+ *
290
+ * @param perPageCount - Items per page (default: 15)
291
+ * @returns Paginated response without total count
292
+ *
293
+ * @example
294
+ * ```typescript
295
+ * const paginated = await User.query().simplePaginate(20);
296
+ * ```
297
+ */
298
+ simplePaginate(perPageCount?: number): Promise<SimplePaginatorResponse<T>>;
299
+ /**
300
+ * Execute query with cursor pagination
301
+ *
302
+ * @param perPageCount - Items per page (default: 15)
303
+ * @returns Cursor paginated response
304
+ *
305
+ * @example
306
+ * ```typescript
307
+ * const paginated = await User.query().cursorPaginate(20);
308
+ * console.log(paginated.meta.next_cursor);
309
+ * console.log(paginated.meta.prev_cursor);
310
+ * ```
311
+ */
312
+ cursorPaginate(perPageCount?: number): Promise<CursorPaginatorResponse<T>>;
313
+ /**
314
+ * Get count of matching records
315
+ *
316
+ * Note: This makes a paginated request with per_page=1 and reads the total
317
+ *
318
+ * @returns Total count of matching records
319
+ *
320
+ * @example
321
+ * ```typescript
322
+ * const activeUserCount = await User.query().where('active', true).count();
323
+ * ```
324
+ */
325
+ count(): Promise<number>;
326
+ /**
327
+ * Check if any records exist matching the query
328
+ *
329
+ * @returns true if at least one record exists
330
+ *
331
+ * @example
332
+ * ```typescript
333
+ * const hasActiveUsers = await User.query().where('active', true).exists();
334
+ * ```
335
+ */
336
+ exists(): Promise<boolean>;
337
+ /**
338
+ * Hydrate models from API data
339
+ *
340
+ * Converts raw API response objects into model instances
341
+ *
342
+ * @param data - Array of raw API objects
343
+ * @returns Array of model instances
344
+ */
345
+ private hydrate;
346
+ /**
347
+ * Clone the query builder
348
+ *
349
+ * Creates a new instance with the same filters, includes, etc.
350
+ * Useful for creating base queries and extending them
351
+ *
352
+ * @returns Cloned QueryBuilder instance
353
+ *
354
+ * @example
355
+ * ```typescript
356
+ * const baseQuery = User.query().where('active', true);
357
+ * const admins = await baseQuery.clone().where('role', 'admin').get();
358
+ * const users = await baseQuery.clone().where('role', 'user').get();
359
+ * ```
360
+ */
361
+ clone(): QueryBuilder<T, A>;
362
+ /**
363
+ * Execute a callback with a fresh query clone
364
+ *
365
+ * @param callback - Function that modifies the query
366
+ * @returns Result of the callback
367
+ *
368
+ * @example
369
+ * ```typescript
370
+ * const baseQuery = User.query().where('active', true);
371
+ *
372
+ * const admins = await baseQuery.tap(q => q.where('role', 'admin')).get();
373
+ * const users = await baseQuery.tap(q => q.where('role', 'user')).get();
374
+ * ```
375
+ */
376
+ tap<R>(callback: (query: QueryBuilder<T, A>) => R): R;
377
+ /**
378
+ * Apply a callback if condition is true
379
+ *
380
+ * @param condition - Boolean condition
381
+ * @param callback - Function to apply if condition is true
382
+ * @returns this for method chaining
383
+ *
384
+ * @example
385
+ * ```typescript
386
+ * const query = User.query()
387
+ * .when(search, q => q.where('name', 'like', `%${search}%`))
388
+ * .when(role, q => q.where('role', role));
389
+ * ```
390
+ */
391
+ when(condition: boolean | any, callback: (query: this) => void): this;
392
+ /**
393
+ * Dump the current query parameters (for debugging)
394
+ *
395
+ * @returns Query parameters object
396
+ *
397
+ * @example
398
+ * ```typescript
399
+ * const params = User.query()
400
+ * .where('active', true)
401
+ * .with('posts')
402
+ * .dump();
403
+ *
404
+ * console.log(params);
405
+ * // { include: 'posts', 'filter[active]': true }
406
+ * ```
407
+ */
408
+ dump(): Record<string, any>;
409
+ }
410
+ //# sourceMappingURL=QueryBuilder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"QueryBuilder.d.ts","sourceRoot":"","sources":["../../src/orm/QueryBuilder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,569 @@
1
+ /**
2
+ * Fluent Query Builder for Models
3
+ *
4
+ * Builds Spatie Query Builder compatible query strings and executes queries.
5
+ *
6
+ * Supports:
7
+ * - Filtering: where(), whereIn(), whereNotNull()
8
+ * - Eager loading: with()
9
+ * - Field selection: fields()
10
+ * - Sorting: sort()
11
+ * - Pagination: page(), perPage(), paginate()
12
+ *
13
+ * @template T - Model class type
14
+ * @template A - API response interface type
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const users = await User.query()
19
+ * .where('status', 'active')
20
+ * .where('age', '>', 18)
21
+ * .with('posts.comments', 'profile')
22
+ * .fields('users', ['id', 'name', 'email'])
23
+ * .fields('posts', ['id', 'title'])
24
+ * .sort('-created_at', 'name')
25
+ * .paginate(20);
26
+ * ```
27
+ */
28
+ export class QueryBuilder {
29
+ modelClass;
30
+ container;
31
+ /**
32
+ * Map of filters to apply
33
+ * Key: field name, Value: operator and value
34
+ */
35
+ filters = new Map();
36
+ /**
37
+ * Set of relationships to eager load
38
+ */
39
+ includes = new Set();
40
+ /**
41
+ * Map of field selections per resource type
42
+ * Key: resource name (e.g., 'users'), Value: array of field names
43
+ */
44
+ fieldSelections = new Map();
45
+ /**
46
+ * Array of sort fields (prefix with - for descending)
47
+ */
48
+ sortFields = [];
49
+ /**
50
+ * Page number for pagination
51
+ */
52
+ pageNumber;
53
+ /**
54
+ * Items per page
55
+ */
56
+ perPage;
57
+ /**
58
+ * Cursor value for cursor pagination
59
+ */
60
+ cursorValue;
61
+ /**
62
+ * Constructor
63
+ *
64
+ * @param modelClass - Model class constructor
65
+ * @param container - ServiceContainer instance
66
+ */
67
+ constructor(modelClass, container) {
68
+ this.modelClass = modelClass;
69
+ this.container = container;
70
+ }
71
+ /**
72
+ * Get the API service
73
+ * @throws Error if no container provided
74
+ */
75
+ get api() {
76
+ if (!this.container) {
77
+ throw new Error('QueryBuilder requires ServiceContainer. ' +
78
+ 'Pass container to query(): Model.query(container)');
79
+ }
80
+ return this.container.api;
81
+ }
82
+ /**
83
+ * Get model configuration
84
+ */
85
+ get config() {
86
+ return this.modelClass.config;
87
+ }
88
+ /**
89
+ * Add a where clause
90
+ *
91
+ * Supports two syntaxes:
92
+ * - where(field, value) - Equality check
93
+ * - where(field, operator, value) - Custom operator
94
+ *
95
+ * @param field - Field name
96
+ * @param operatorOrValue - Operator or value (if using equality)
97
+ * @param value - Value (if using custom operator)
98
+ * @returns this for method chaining
99
+ *
100
+ * @example
101
+ * ```typescript
102
+ * query.where('status', 'active') // status = 'active'
103
+ * query.where('age', '>', 18) // age > 18
104
+ * query.where('name', 'like', '%john%') // name like '%john%'
105
+ * ```
106
+ */
107
+ where(field, operatorOrValue, value) {
108
+ if (value === undefined) {
109
+ // where(field, value) syntax
110
+ this.filters.set(field, { operator: '=', value: operatorOrValue });
111
+ }
112
+ else {
113
+ // where(field, operator, value) syntax
114
+ this.filters.set(field, { operator: operatorOrValue, value });
115
+ }
116
+ return this;
117
+ }
118
+ /**
119
+ * Add a whereIn clause (field IN values)
120
+ *
121
+ * @param field - Field name
122
+ * @param values - Array of values
123
+ * @returns this for method chaining
124
+ *
125
+ * @example
126
+ * ```typescript
127
+ * query.whereIn('id', ['1', '2', '3'])
128
+ * ```
129
+ */
130
+ whereIn(field, values) {
131
+ this.filters.set(field, { operator: 'in', value: values.join(',') });
132
+ return this;
133
+ }
134
+ /**
135
+ * Add a whereNotNull clause
136
+ *
137
+ * @param field - Field name
138
+ * @returns this for method chaining
139
+ *
140
+ * @example
141
+ * ```typescript
142
+ * query.whereNotNull('email_verified_at')
143
+ * ```
144
+ */
145
+ whereNotNull(field) {
146
+ this.filters.set(field, { operator: '!=', value: 'null' });
147
+ return this;
148
+ }
149
+ /**
150
+ * Add a whereNull clause
151
+ *
152
+ * @param field - Field name
153
+ * @returns this for method chaining
154
+ *
155
+ * @example
156
+ * ```typescript
157
+ * query.whereNull('deleted_at')
158
+ * ```
159
+ */
160
+ whereNull(field) {
161
+ this.filters.set(field, { operator: '=', value: 'null' });
162
+ return this;
163
+ }
164
+ /**
165
+ * Add eager loading relationships
166
+ *
167
+ * Supports nested relationships with dot notation
168
+ *
169
+ * @param relations - Relationship names (method names on model)
170
+ * @returns this for method chaining
171
+ *
172
+ * @example
173
+ * ```typescript
174
+ * query.with('posts', 'profile')
175
+ * query.with('posts.comments.author')
176
+ * query.with('posts.tags', 'posts.comments')
177
+ * ```
178
+ */
179
+ with(...relations) {
180
+ for (const relation of relations) {
181
+ this.includes.add(relation);
182
+ }
183
+ return this;
184
+ }
185
+ /**
186
+ * Select specific fields for a resource type (sparse fieldsets)
187
+ *
188
+ * @param resource - Resource name (e.g., 'users', 'posts')
189
+ * @param fields - Array of field names to select
190
+ * @returns this for method chaining
191
+ *
192
+ * @example
193
+ * ```typescript
194
+ * query.fields('users', ['id', 'name', 'email'])
195
+ * query.fields('posts', ['id', 'title', 'created_at'])
196
+ * ```
197
+ */
198
+ fields(resource, fields) {
199
+ this.fieldSelections.set(resource, fields);
200
+ return this;
201
+ }
202
+ /**
203
+ * Add sorting (prefix with - for descending)
204
+ *
205
+ * @param fields - Field names (prefix with '-' for DESC)
206
+ * @returns this for method chaining
207
+ *
208
+ * @example
209
+ * ```typescript
210
+ * query.sort('name') // ASC
211
+ * query.sort('-created_at') // DESC
212
+ * query.sort('-created_at', 'name') // Multiple fields
213
+ * ```
214
+ */
215
+ sort(...fields) {
216
+ this.sortFields.push(...fields);
217
+ return this;
218
+ }
219
+ /**
220
+ * Set page number for pagination
221
+ *
222
+ * @param page - Page number (1-indexed)
223
+ * @returns this for method chaining
224
+ *
225
+ * @example
226
+ * ```typescript
227
+ * query.page(2)
228
+ * ```
229
+ */
230
+ page(page) {
231
+ this.pageNumber = page;
232
+ return this;
233
+ }
234
+ /**
235
+ * Set items per page
236
+ *
237
+ * @param count - Number of items per page
238
+ * @returns this for method chaining
239
+ *
240
+ * @example
241
+ * ```typescript
242
+ * query.perPage(50)
243
+ * ```
244
+ */
245
+ limit(count) {
246
+ this.perPage = count;
247
+ return this;
248
+ }
249
+ /**
250
+ * Set cursor for cursor pagination
251
+ *
252
+ * @param cursor - Cursor value from previous response
253
+ * @returns this for method chaining
254
+ *
255
+ * @example
256
+ * ```typescript
257
+ * query.cursor(response.meta.next_cursor)
258
+ * ```
259
+ */
260
+ cursor(cursor) {
261
+ this.cursorValue = cursor;
262
+ return this;
263
+ }
264
+ /**
265
+ * Build query parameters for Spatie Query Builder
266
+ *
267
+ * Converts fluent API calls into URL query string format:
268
+ * - include=posts.comments,profile
269
+ * - filter[status]=active
270
+ * - filter[age]=>:18
271
+ * - fields[users]=id,name,email
272
+ * - sort=-created_at,name
273
+ * - page=1&per_page=20
274
+ *
275
+ * @returns Record of query parameters for Axios
276
+ */
277
+ buildParams() {
278
+ const params = {};
279
+ // Include relationships
280
+ if (this.includes.size > 0) {
281
+ params.include = Array.from(this.includes).join(',');
282
+ }
283
+ // Filters
284
+ for (const [field, { operator, value }] of this.filters) {
285
+ if (operator === '=') {
286
+ // Simple equality
287
+ params[`filter[${field}]`] = value;
288
+ }
289
+ else {
290
+ // Custom operator
291
+ // Note: Format depends on your backend's Spatie Query Builder config
292
+ // Default format: filter[field]=operator:value
293
+ params[`filter[${field}]`] = `${operator}:${value}`;
294
+ }
295
+ }
296
+ // Field selections (sparse fieldsets)
297
+ for (const [resource, fields] of this.fieldSelections) {
298
+ params[`fields[${resource}]`] = fields.join(',');
299
+ }
300
+ // Sorting
301
+ if (this.sortFields.length > 0) {
302
+ params.sort = this.sortFields.join(',');
303
+ }
304
+ // Pagination
305
+ if (this.pageNumber !== undefined) {
306
+ params.page = this.pageNumber;
307
+ }
308
+ if (this.perPage !== undefined) {
309
+ params.per_page = this.perPage;
310
+ }
311
+ if (this.cursorValue !== undefined) {
312
+ params.cursor = this.cursorValue;
313
+ }
314
+ return params;
315
+ }
316
+ /**
317
+ * Execute query and get all results
318
+ *
319
+ * @returns Array of model instances
320
+ *
321
+ * @example
322
+ * ```typescript
323
+ * const users = await User.query().where('active', true).get();
324
+ * ```
325
+ */
326
+ async get() {
327
+ const params = this.buildParams();
328
+ const response = await this.api.get(this.config.endpoint, { params });
329
+ return this.hydrate(response.data);
330
+ }
331
+ /**
332
+ * Execute query and get first result
333
+ *
334
+ * @returns First model instance or null if none found
335
+ *
336
+ * @example
337
+ * ```typescript
338
+ * const user = await User.query().where('email', 'john@example.com').first();
339
+ * if (user) {
340
+ * console.log(user.name);
341
+ * }
342
+ * ```
343
+ */
344
+ async first() {
345
+ const params = this.buildParams();
346
+ params.per_page = 1;
347
+ try {
348
+ const response = await this.api.get(this.config.endpoint, { params });
349
+ const models = this.hydrate(response.data);
350
+ return models[0] ?? null;
351
+ }
352
+ catch (error) {
353
+ // If 404 or other error, return null
354
+ return null;
355
+ }
356
+ }
357
+ /**
358
+ * Execute query and get first result or fail
359
+ *
360
+ * @returns First model instance
361
+ * @throws Error if no results found
362
+ *
363
+ * @example
364
+ * ```typescript
365
+ * const user = await User.query().where('email', 'john@example.com').firstOrFail();
366
+ * // Throws if user not found
367
+ * ```
368
+ */
369
+ async firstOrFail() {
370
+ const model = await this.first();
371
+ if (!model) {
372
+ throw new Error(`No ${this.modelClass.name} found matching query`);
373
+ }
374
+ return model;
375
+ }
376
+ /**
377
+ * Execute query with length-aware pagination
378
+ *
379
+ * Returns paginated response with total count and page metadata
380
+ *
381
+ * @param perPageCount - Items per page (default: 15)
382
+ * @returns Paginated response with metadata
383
+ *
384
+ * @example
385
+ * ```typescript
386
+ * const paginated = await User.query().where('active', true).paginate(20);
387
+ * console.log(paginated.data); // Array of User models
388
+ * console.log(paginated.meta.total); // Total count
389
+ * console.log(paginated.meta.current_page); // Current page
390
+ * console.log(paginated.links.next); // Next page URL
391
+ * ```
392
+ */
393
+ async paginate(perPageCount = 15) {
394
+ this.perPage = perPageCount;
395
+ const params = this.buildParams();
396
+ const response = await this.api.get(this.config.endpoint, { params });
397
+ return {
398
+ ...response,
399
+ data: this.hydrate(response.data),
400
+ };
401
+ }
402
+ /**
403
+ * Execute query with simple pagination (no total count)
404
+ *
405
+ * @param perPageCount - Items per page (default: 15)
406
+ * @returns Paginated response without total count
407
+ *
408
+ * @example
409
+ * ```typescript
410
+ * const paginated = await User.query().simplePaginate(20);
411
+ * ```
412
+ */
413
+ async simplePaginate(perPageCount = 15) {
414
+ this.perPage = perPageCount;
415
+ const params = this.buildParams();
416
+ const response = await this.api.get(this.config.endpoint, { params });
417
+ return {
418
+ ...response,
419
+ data: this.hydrate(response.data),
420
+ };
421
+ }
422
+ /**
423
+ * Execute query with cursor pagination
424
+ *
425
+ * @param perPageCount - Items per page (default: 15)
426
+ * @returns Cursor paginated response
427
+ *
428
+ * @example
429
+ * ```typescript
430
+ * const paginated = await User.query().cursorPaginate(20);
431
+ * console.log(paginated.meta.next_cursor);
432
+ * console.log(paginated.meta.prev_cursor);
433
+ * ```
434
+ */
435
+ async cursorPaginate(perPageCount = 15) {
436
+ this.perPage = perPageCount;
437
+ const params = this.buildParams();
438
+ const response = await this.api.get(this.config.endpoint, { params });
439
+ return {
440
+ ...response,
441
+ data: this.hydrate(response.data),
442
+ };
443
+ }
444
+ /**
445
+ * Get count of matching records
446
+ *
447
+ * Note: This makes a paginated request with per_page=1 and reads the total
448
+ *
449
+ * @returns Total count of matching records
450
+ *
451
+ * @example
452
+ * ```typescript
453
+ * const activeUserCount = await User.query().where('active', true).count();
454
+ * ```
455
+ */
456
+ async count() {
457
+ const params = this.buildParams();
458
+ params.per_page = 1;
459
+ const response = await this.api.get(this.config.endpoint, { params });
460
+ return response.meta.total;
461
+ }
462
+ /**
463
+ * Check if any records exist matching the query
464
+ *
465
+ * @returns true if at least one record exists
466
+ *
467
+ * @example
468
+ * ```typescript
469
+ * const hasActiveUsers = await User.query().where('active', true).exists();
470
+ * ```
471
+ */
472
+ async exists() {
473
+ const count = await this.count();
474
+ return count > 0;
475
+ }
476
+ /**
477
+ * Hydrate models from API data
478
+ *
479
+ * Converts raw API response objects into model instances
480
+ *
481
+ * @param data - Array of raw API objects
482
+ * @returns Array of model instances
483
+ */
484
+ hydrate(data) {
485
+ return data.map(attrs => new this.modelClass(attrs, this.container));
486
+ }
487
+ /**
488
+ * Clone the query builder
489
+ *
490
+ * Creates a new instance with the same filters, includes, etc.
491
+ * Useful for creating base queries and extending them
492
+ *
493
+ * @returns Cloned QueryBuilder instance
494
+ *
495
+ * @example
496
+ * ```typescript
497
+ * const baseQuery = User.query().where('active', true);
498
+ * const admins = await baseQuery.clone().where('role', 'admin').get();
499
+ * const users = await baseQuery.clone().where('role', 'user').get();
500
+ * ```
501
+ */
502
+ clone() {
503
+ const cloned = new QueryBuilder(this.modelClass, this.container);
504
+ cloned.filters = new Map(this.filters);
505
+ cloned.includes = new Set(this.includes);
506
+ cloned.fieldSelections = new Map(this.fieldSelections);
507
+ cloned.sortFields = [...this.sortFields];
508
+ cloned.pageNumber = this.pageNumber;
509
+ cloned.perPage = this.perPage;
510
+ cloned.cursorValue = this.cursorValue;
511
+ return cloned;
512
+ }
513
+ /**
514
+ * Execute a callback with a fresh query clone
515
+ *
516
+ * @param callback - Function that modifies the query
517
+ * @returns Result of the callback
518
+ *
519
+ * @example
520
+ * ```typescript
521
+ * const baseQuery = User.query().where('active', true);
522
+ *
523
+ * const admins = await baseQuery.tap(q => q.where('role', 'admin')).get();
524
+ * const users = await baseQuery.tap(q => q.where('role', 'user')).get();
525
+ * ```
526
+ */
527
+ tap(callback) {
528
+ return callback(this.clone());
529
+ }
530
+ /**
531
+ * Apply a callback if condition is true
532
+ *
533
+ * @param condition - Boolean condition
534
+ * @param callback - Function to apply if condition is true
535
+ * @returns this for method chaining
536
+ *
537
+ * @example
538
+ * ```typescript
539
+ * const query = User.query()
540
+ * .when(search, q => q.where('name', 'like', `%${search}%`))
541
+ * .when(role, q => q.where('role', role));
542
+ * ```
543
+ */
544
+ when(condition, callback) {
545
+ if (condition) {
546
+ callback(this);
547
+ }
548
+ return this;
549
+ }
550
+ /**
551
+ * Dump the current query parameters (for debugging)
552
+ *
553
+ * @returns Query parameters object
554
+ *
555
+ * @example
556
+ * ```typescript
557
+ * const params = User.query()
558
+ * .where('active', true)
559
+ * .with('posts')
560
+ * .dump();
561
+ *
562
+ * console.log(params);
563
+ * // { include: 'posts', 'filter[active]': true }
564
+ * ```
565
+ */
566
+ dump() {
567
+ return this.buildParams();
568
+ }
569
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * ORM Module - Front-End Active Record Pattern for Spatie Query Builder
3
+ *
4
+ * Provides QueryBuilder and types for building Active Record models.
5
+ * Apps should create their own base Model class - see app/src/models/BaseModel.ts
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * // In app/src/models/BaseModel.ts
10
+ * import { QueryBuilder, type ModelConfig } from '@quvel-kit/core/orm';
11
+ *
12
+ * export abstract class Model<T, A> {
13
+ * // Your customizable base model
14
+ * }
15
+ * ```
16
+ */
17
+ export { QueryBuilder } from './QueryBuilder.js';
18
+ export type { ModelConfig, FilterOperator, ModelMetadata } from './types.js';
19
+ export { type RelationshipType, type RelationshipDefinition, type RelationshipData, } from './relationships/types.js';
20
+ export type { ServiceContainer } from '../container/ServiceContainer.js';
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/orm/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGjD,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAG7E,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,gBAAgB,GACtB,MAAM,0BAA0B,CAAC;AAGlC,YAAY,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * ORM Module - Front-End Active Record Pattern for Spatie Query Builder
3
+ *
4
+ * Provides QueryBuilder and types for building Active Record models.
5
+ * Apps should create their own base Model class - see app/src/models/BaseModel.ts
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * // In app/src/models/BaseModel.ts
10
+ * import { QueryBuilder, type ModelConfig } from '@quvel-kit/core/orm';
11
+ *
12
+ * export abstract class Model<T, A> {
13
+ * // Your customizable base model
14
+ * }
15
+ * ```
16
+ */
17
+ // Core classes
18
+ export { QueryBuilder } from './QueryBuilder.js';
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Relationship types supported by the ORM
3
+ */
4
+ export type RelationshipType = 'hasMany' | 'belongsTo' | 'hasOne';
5
+ /**
6
+ * Relationship definition returned by relationship methods
7
+ *
8
+ * This interface defines the metadata for a relationship but does NOT
9
+ * implement lazy loading. Relationships must be eager-loaded via the
10
+ * query builder's `with()` method.
11
+ *
12
+ * The generic parameter R is used for type safety at call sites but not
13
+ * in the interface properties themselves.
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * export class User extends Model<User, IUser> {
18
+ * posts() {
19
+ * return this.hasMany(Post, 'user_id');
20
+ * }
21
+ * }
22
+ *
23
+ * // Eager load:
24
+ * const users = await User.query().with('posts').get();
25
+ * const posts = users[0].getRelation<Post[]>('posts');
26
+ * ```
27
+ */
28
+ export interface RelationshipDefinition<R = any> {
29
+ /** Phantom type for type safety - not used at runtime */
30
+ __relationshipType?: R;
31
+ /**
32
+ * Type of relationship
33
+ */
34
+ type: RelationshipType;
35
+ /**
36
+ * Related model class constructor
37
+ */
38
+ related: new (...args: any[]) => any;
39
+ /**
40
+ * Foreign key field name on the related model (hasMany, hasOne)
41
+ * or on this model (belongsTo)
42
+ */
43
+ foreignKey: string;
44
+ /**
45
+ * Local key field name on this model (hasMany, hasOne)
46
+ * Usually the primary key
47
+ */
48
+ localKey?: string;
49
+ /**
50
+ * Owner key field name on the related model (belongsTo)
51
+ * Usually the primary key of the related model
52
+ */
53
+ ownerKey?: string;
54
+ }
55
+ /**
56
+ * Relationship data type helper
57
+ * Unwraps array types for type safety
58
+ */
59
+ export type RelationshipData<R> = R extends (infer U)[] ? U[] : R | null;
60
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/orm/relationships/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,CAAC;AAElE;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,WAAW,sBAAsB,CAAC,CAAC,GAAG,GAAG;IAC7C,yDAAyD;IACzD,kBAAkB,CAAC,EAAE,CAAC,CAAC;IAEvB;;OAEG;IACH,IAAI,EAAE,gBAAgB,CAAC;IAEvB;;OAEG;IACH,OAAO,EAAE,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC;IAErC;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Shared ORM type definitions
3
+ * Prevents circular dependencies between Model and QueryBuilder
4
+ */
5
+ /**
6
+ * Model configuration interface
7
+ * Subclasses must implement this static configuration
8
+ */
9
+ export interface ModelConfig {
10
+ /**
11
+ * API endpoint for this model (e.g., '/api/v1/users', '/users')
12
+ */
13
+ endpoint: string;
14
+ /**
15
+ * Primary key field name
16
+ * @default 'id'
17
+ */
18
+ primaryKey?: string;
19
+ /**
20
+ * Timestamp fields to parse as Date objects
21
+ * @example ['created_at', 'updated_at', 'email_verified_at']
22
+ */
23
+ dates?: string[];
24
+ }
25
+ /**
26
+ * Filter operator types supported by Spatie Query Builder
27
+ */
28
+ export type FilterOperator = '=' | '!=' | '>' | '>=' | '<' | '<=' | 'like' | 'in';
29
+ /**
30
+ * Model metadata stored on each instance
31
+ * Internal state tracking for the Active Record pattern
32
+ */
33
+ export interface ModelMetadata {
34
+ /**
35
+ * Is this a new record (not yet persisted)?
36
+ * New records use POST, existing records use PUT
37
+ */
38
+ isNew: boolean;
39
+ /**
40
+ * Original attributes from API (for dirty checking)
41
+ * Used to determine what changed for optimistic updates
42
+ */
43
+ original: Record<string, any>;
44
+ /**
45
+ * Loaded relationships map
46
+ * Key: relationship name, Value: related model(s)
47
+ */
48
+ relations: Map<string, any>;
49
+ /**
50
+ * Model is currently being saved
51
+ * Prevents concurrent save operations
52
+ */
53
+ isSaving: boolean;
54
+ /**
55
+ * Model is currently being deleted
56
+ * Prevents concurrent delete operations
57
+ */
58
+ isDeleting: boolean;
59
+ }
60
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/orm/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,GAAG,GAAG,IAAI,GAAG,GAAG,GAAG,IAAI,GAAG,GAAG,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAAC;AAElF;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,KAAK,EAAE,OAAO,CAAC;IAEf;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAE9B;;;OAGG;IACH,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAE5B;;;OAGG;IACH,QAAQ,EAAE,OAAO,CAAC;IAElB;;;OAGG;IACH,UAAU,EAAE,OAAO,CAAC;CACrB"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Shared ORM type definitions
3
+ * Prevents circular dependencies between Model and QueryBuilder
4
+ */
5
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quvel-kit/core",
3
- "version": "1.3.20",
3
+ "version": "1.3.21",
4
4
  "description": "Core utilities for Quvel UI",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -39,6 +39,10 @@
39
39
  "import": "./dist/composables/index.js",
40
40
  "types": "./dist/composables/index.d.ts"
41
41
  },
42
+ "./orm": {
43
+ "import": "./dist/orm/index.js",
44
+ "types": "./dist/orm/index.d.ts"
45
+ },
42
46
  "./global": {
43
47
  "types": "./global.d.ts"
44
48
  }