@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 +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/orm/QueryBuilder.d.ts +410 -0
- package/dist/orm/QueryBuilder.d.ts.map +1 -0
- package/dist/orm/QueryBuilder.js +569 -0
- package/dist/orm/index.d.ts +21 -0
- package/dist/orm/index.d.ts.map +1 -0
- package/dist/orm/index.js +18 -0
- package/dist/orm/relationships/types.d.ts +60 -0
- package/dist/orm/relationships/types.d.ts.map +1 -0
- package/dist/orm/relationships/types.js +1 -0
- package/dist/orm/types.d.ts +60 -0
- package/dist/orm/types.d.ts.map +1 -0
- package/dist/orm/types.js +5 -0
- package/package.json +5 -1
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';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quvel-kit/core",
|
|
3
|
-
"version": "1.3.
|
|
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
|
}
|