@quvel-kit/core 1.3.21 → 1.3.22
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 +1 -0
- package/dist/orm/Model.d.ts +469 -0
- package/dist/orm/Model.d.ts.map +1 -0
- package/dist/orm/Model.js +648 -0
- package/dist/orm/index.d.ts +14 -6
- package/dist/orm/index.d.ts.map +1 -1
- package/dist/orm/index.js +14 -6
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -43,6 +43,7 @@ export * from './utils/scripts.js';
|
|
|
43
43
|
export * from './utils/assets.js';
|
|
44
44
|
export * from './utils/pagination.js';
|
|
45
45
|
export * from './orm/index.js';
|
|
46
|
+
export { Model } from './orm/Model.js';
|
|
46
47
|
export { defineQuvelBoot } from './boot/quvel.js';
|
|
47
48
|
export { serviceContainerPlugin } from './stores/plugins/serviceContainer.js';
|
|
48
49
|
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,cAAc,gBAAgB,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;AAC/B,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAGvC,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
|
@@ -55,6 +55,7 @@ export * from './utils/assets.js';
|
|
|
55
55
|
export * from './utils/pagination.js';
|
|
56
56
|
// ORM - Active Record pattern for models
|
|
57
57
|
export * from './orm/index.js';
|
|
58
|
+
export { Model } from './orm/Model.js';
|
|
58
59
|
// Boot
|
|
59
60
|
export { defineQuvelBoot } from './boot/quvel.js';
|
|
60
61
|
// Stores
|
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
import type { ServiceContainer, ApiService } from '@quvel-kit/core';
|
|
2
|
+
import type { ModelConfig, RelationshipDefinition } from '@quvel-kit/core/orm';
|
|
3
|
+
import type { QueryBuilder } from '@quvel-kit/core/orm';
|
|
4
|
+
/**
|
|
5
|
+
* Abstract Base Model Class
|
|
6
|
+
*
|
|
7
|
+
* Provides Active Record pattern for front-end models with:
|
|
8
|
+
* - Instance methods: save(), delete(), refresh(), isDirty()
|
|
9
|
+
* - Static query builder: User.query().where(...).get()
|
|
10
|
+
* - Relationship definitions: hasMany(), belongsTo(), hasOne()
|
|
11
|
+
* - Integration with ServiceContainer for API access
|
|
12
|
+
* - Full TypeScript type safety
|
|
13
|
+
* - SSR compatibility
|
|
14
|
+
*
|
|
15
|
+
* @template T - The model class itself (for proper return types)
|
|
16
|
+
* @template A - The API response interface (IUser, IPost, etc.)
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* export interface IUser {
|
|
21
|
+
* id: string;
|
|
22
|
+
* name: string;
|
|
23
|
+
* email: string;
|
|
24
|
+
* }
|
|
25
|
+
*
|
|
26
|
+
* export class User extends Model<User, IUser> implements IUser {
|
|
27
|
+
* static config: ModelConfig = {
|
|
28
|
+
* endpoint: '/api/v1/users',
|
|
29
|
+
* dates: ['created_at', 'updated_at']
|
|
30
|
+
* };
|
|
31
|
+
*
|
|
32
|
+
* id!: string;
|
|
33
|
+
* name!: string;
|
|
34
|
+
* email!: string;
|
|
35
|
+
*
|
|
36
|
+
* // Relationship definitions
|
|
37
|
+
* posts() {
|
|
38
|
+
* return this.hasMany(Post, 'user_id');
|
|
39
|
+
* }
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export declare abstract class Model<T extends Model<T, A>, A = any> {
|
|
44
|
+
/**
|
|
45
|
+
* Model configuration - MUST be overridden by subclasses
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* static config: ModelConfig = {
|
|
50
|
+
* endpoint: '/api/v1/users',
|
|
51
|
+
* primaryKey: 'id',
|
|
52
|
+
* dates: ['created_at', 'updated_at']
|
|
53
|
+
* };
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
static config: ModelConfig;
|
|
57
|
+
/**
|
|
58
|
+
* Global service container for all models
|
|
59
|
+
* Set once in boot file via Model.setContainer($quvel)
|
|
60
|
+
* @private
|
|
61
|
+
*/
|
|
62
|
+
private static _globalContainer?;
|
|
63
|
+
/**
|
|
64
|
+
* Set global service container for all models
|
|
65
|
+
*
|
|
66
|
+
* Should be called once in your boot file to enable automatic container injection.
|
|
67
|
+
*
|
|
68
|
+
* @param container - ServiceContainer instance
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```typescript
|
|
72
|
+
* // In boot file
|
|
73
|
+
* import { Model } from '@quvel-kit/core/orm';
|
|
74
|
+
*
|
|
75
|
+
* export default boot(({ app }) => {
|
|
76
|
+
* const container = app.config.globalProperties.$quvel;
|
|
77
|
+
* Model.setContainer(container);
|
|
78
|
+
* });
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
static setContainer(container: ServiceContainer): void;
|
|
82
|
+
/**
|
|
83
|
+
* Get the global service container
|
|
84
|
+
* @returns ServiceContainer or undefined if not set
|
|
85
|
+
*/
|
|
86
|
+
static getContainer(): ServiceContainer | undefined;
|
|
87
|
+
/**
|
|
88
|
+
* Service container instance
|
|
89
|
+
* Provides access to ApiService and other services
|
|
90
|
+
*/
|
|
91
|
+
protected $quvel: ServiceContainer;
|
|
92
|
+
/**
|
|
93
|
+
* Metadata for internal state tracking
|
|
94
|
+
* @private
|
|
95
|
+
*/
|
|
96
|
+
private $meta;
|
|
97
|
+
/**
|
|
98
|
+
* Constructor - accepts raw API data and optional container
|
|
99
|
+
*
|
|
100
|
+
* @param attributes - Raw attributes from API response
|
|
101
|
+
* @param container - ServiceContainer instance (auto-injected in most cases)
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```typescript
|
|
105
|
+
* // From API response
|
|
106
|
+
* const user = new User({ id: '1', name: 'John', email: 'john@example.com' });
|
|
107
|
+
*
|
|
108
|
+
* // With container (in stores)
|
|
109
|
+
* const user = new User(apiData, this.$quvel);
|
|
110
|
+
*
|
|
111
|
+
* // New unsaved record
|
|
112
|
+
* const user = new User({ name: 'Jane', email: 'jane@example.com' }, $quvel);
|
|
113
|
+
* await user.save(); // POST /api/v1/users
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
constructor(attributes?: Partial<A>, container?: ServiceContainer);
|
|
117
|
+
/**
|
|
118
|
+
* Fill model with attributes
|
|
119
|
+
* Handles property assignment and date parsing
|
|
120
|
+
*
|
|
121
|
+
* @param attributes - Attributes to assign to the model
|
|
122
|
+
*/
|
|
123
|
+
protected fill(attributes: Partial<A> | Record<string, any>): void;
|
|
124
|
+
/**
|
|
125
|
+
* Get the API service instance
|
|
126
|
+
* @throws Error if model not initialized with ServiceContainer
|
|
127
|
+
*/
|
|
128
|
+
protected get api(): ApiService;
|
|
129
|
+
/**
|
|
130
|
+
* Get model configuration
|
|
131
|
+
*/
|
|
132
|
+
protected get config(): ModelConfig;
|
|
133
|
+
/**
|
|
134
|
+
* Get primary key value from object or current instance
|
|
135
|
+
*
|
|
136
|
+
* @param obj - Object to get primary key from (defaults to this)
|
|
137
|
+
* @returns Primary key value or undefined
|
|
138
|
+
*/
|
|
139
|
+
protected getPrimaryKeyValue(obj?: any): any;
|
|
140
|
+
/**
|
|
141
|
+
* Get the resource URL for this model instance
|
|
142
|
+
* @example '/api/v1/users/123'
|
|
143
|
+
* @throws Error if model is unsaved (no primary key)
|
|
144
|
+
*/
|
|
145
|
+
protected getResourceUrl(): string;
|
|
146
|
+
/**
|
|
147
|
+
* Create a new query builder for this model
|
|
148
|
+
*
|
|
149
|
+
* @param container - Optional ServiceContainer (uses global if available)
|
|
150
|
+
* @returns QueryBuilder instance for fluent API
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```typescript
|
|
154
|
+
* const users = await User.query()
|
|
155
|
+
* .where('status', 'active')
|
|
156
|
+
* .with('posts', 'profile')
|
|
157
|
+
* .get();
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
static query<T extends Model<T, A>, A = any>(this: new (attrs: Partial<A>) => T): QueryBuilder<T, A>;
|
|
161
|
+
/**
|
|
162
|
+
* Find a model by primary key
|
|
163
|
+
*
|
|
164
|
+
* @param id - Primary key value
|
|
165
|
+
* @param container - Optional ServiceContainer
|
|
166
|
+
* @returns Model instance or null if not found
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* ```typescript
|
|
170
|
+
* const user = await User.find('123');
|
|
171
|
+
* if (user) {
|
|
172
|
+
* console.log(user.name);
|
|
173
|
+
* }
|
|
174
|
+
* ```
|
|
175
|
+
*/
|
|
176
|
+
static find<T extends Model<T, A>, A = any>(this: new (attrs: Partial<A>, container?: ServiceContainer) => T, id: string | number, container?: ServiceContainer): Promise<T | null>;
|
|
177
|
+
/**
|
|
178
|
+
* Find a model by primary key or throw an error
|
|
179
|
+
*
|
|
180
|
+
* @param id - Primary key value
|
|
181
|
+
* @param container - Optional ServiceContainer
|
|
182
|
+
* @returns Model instance
|
|
183
|
+
* @throws Error if not found
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* ```typescript
|
|
187
|
+
* const user = await User.findOrFail('123');
|
|
188
|
+
* // Throws if user doesn't exist
|
|
189
|
+
* ```
|
|
190
|
+
*/
|
|
191
|
+
static findOrFail<T extends Model<T, A>, A = any>(this: new (attrs: Partial<A>, container?: ServiceContainer) => T, id: string | number, container?: ServiceContainer): Promise<T>;
|
|
192
|
+
/**
|
|
193
|
+
* Save the model (create or update)
|
|
194
|
+
*
|
|
195
|
+
* - New models (no primary key): POST to endpoint
|
|
196
|
+
* - Existing models: PUT to resource URL
|
|
197
|
+
*
|
|
198
|
+
* @returns The saved model instance
|
|
199
|
+
* @throws Error if already saving or if API request fails
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* ```typescript
|
|
203
|
+
* // Create new
|
|
204
|
+
* const user = new User({ name: 'John', email: 'john@example.com' }, $quvel);
|
|
205
|
+
* await user.save(); // POST /api/v1/users
|
|
206
|
+
*
|
|
207
|
+
* // Update existing
|
|
208
|
+
* user.name = 'Jane';
|
|
209
|
+
* await user.save(); // PUT /api/v1/users/123
|
|
210
|
+
* ```
|
|
211
|
+
*/
|
|
212
|
+
save(): Promise<T>;
|
|
213
|
+
/**
|
|
214
|
+
* Delete the model from the server
|
|
215
|
+
*
|
|
216
|
+
* @throws Error if model is unsaved or already being deleted
|
|
217
|
+
*
|
|
218
|
+
* @example
|
|
219
|
+
* ```typescript
|
|
220
|
+
* await user.delete(); // DELETE /api/v1/users/123
|
|
221
|
+
* ```
|
|
222
|
+
*/
|
|
223
|
+
delete(): Promise<void>;
|
|
224
|
+
/**
|
|
225
|
+
* Refresh the model from the API
|
|
226
|
+
* Re-fetches the current state from the server
|
|
227
|
+
*
|
|
228
|
+
* @returns The refreshed model instance
|
|
229
|
+
* @throws Error if model is unsaved
|
|
230
|
+
*
|
|
231
|
+
* @example
|
|
232
|
+
* ```typescript
|
|
233
|
+
* await user.refresh(); // GET /api/v1/users/123
|
|
234
|
+
* console.log(user.name); // Updated from server
|
|
235
|
+
* ```
|
|
236
|
+
*/
|
|
237
|
+
refresh(): Promise<T>;
|
|
238
|
+
/**
|
|
239
|
+
* Check if model has unsaved changes (dirty checking)
|
|
240
|
+
*
|
|
241
|
+
* Compares current attributes with original attributes from API
|
|
242
|
+
*
|
|
243
|
+
* @returns true if there are unsaved changes
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* ```typescript
|
|
247
|
+
* const user = await User.find('123');
|
|
248
|
+
* user.name = 'Changed';
|
|
249
|
+
* console.log(user.isDirty()); // true
|
|
250
|
+
* await user.save();
|
|
251
|
+
* console.log(user.isDirty()); // false
|
|
252
|
+
* ```
|
|
253
|
+
*/
|
|
254
|
+
isDirty(): boolean;
|
|
255
|
+
/**
|
|
256
|
+
* Check if a specific attribute has changed
|
|
257
|
+
*
|
|
258
|
+
* @param key - Attribute name to check
|
|
259
|
+
* @returns true if the attribute has changed
|
|
260
|
+
*
|
|
261
|
+
* @example
|
|
262
|
+
* ```typescript
|
|
263
|
+
* user.name = 'New Name';
|
|
264
|
+
* console.log(user.isDirtyAttribute('name')); // true
|
|
265
|
+
* console.log(user.isDirtyAttribute('email')); // false
|
|
266
|
+
* ```
|
|
267
|
+
*/
|
|
268
|
+
isDirtyAttribute(key: keyof A): boolean;
|
|
269
|
+
/**
|
|
270
|
+
* Get model attributes as plain object
|
|
271
|
+
* Excludes metadata, methods, and internal properties
|
|
272
|
+
*
|
|
273
|
+
* @returns Plain object of model attributes
|
|
274
|
+
*
|
|
275
|
+
* @example
|
|
276
|
+
* ```typescript
|
|
277
|
+
* const attrs = user.getAttributes();
|
|
278
|
+
* console.log(attrs); // { id: '123', name: 'John', email: 'john@example.com' }
|
|
279
|
+
* ```
|
|
280
|
+
*/
|
|
281
|
+
getAttributes(): Record<string, any>;
|
|
282
|
+
/**
|
|
283
|
+
* Get original attribute value (before changes)
|
|
284
|
+
*
|
|
285
|
+
* @param key - Attribute name
|
|
286
|
+
* @returns Original value from API
|
|
287
|
+
*
|
|
288
|
+
* @example
|
|
289
|
+
* ```typescript
|
|
290
|
+
* const user = await User.find('123');
|
|
291
|
+
* console.log(user.name); // 'John'
|
|
292
|
+
* user.name = 'Jane';
|
|
293
|
+
* console.log(user.getOriginal('name')); // 'John'
|
|
294
|
+
* ```
|
|
295
|
+
*/
|
|
296
|
+
getOriginal<K extends keyof A>(key: K): A[K] | undefined;
|
|
297
|
+
/**
|
|
298
|
+
* Reset model to original state (undo changes)
|
|
299
|
+
*
|
|
300
|
+
* @example
|
|
301
|
+
* ```typescript
|
|
302
|
+
* user.name = 'Changed';
|
|
303
|
+
* user.reset();
|
|
304
|
+
* console.log(user.name); // Back to original value
|
|
305
|
+
* ```
|
|
306
|
+
*/
|
|
307
|
+
reset(): void;
|
|
308
|
+
/**
|
|
309
|
+
* Get a loaded relationship
|
|
310
|
+
*
|
|
311
|
+
* @param name - Relationship name (method name on model)
|
|
312
|
+
* @returns Related model(s) or undefined if not loaded
|
|
313
|
+
*
|
|
314
|
+
* @example
|
|
315
|
+
* ```typescript
|
|
316
|
+
* const users = await User.query().with('posts').get();
|
|
317
|
+
* const posts = users[0].getRelation<Post[]>('posts');
|
|
318
|
+
* ```
|
|
319
|
+
*/
|
|
320
|
+
getRelation<R = any>(name: string): R | undefined;
|
|
321
|
+
/**
|
|
322
|
+
* Set a loaded relationship
|
|
323
|
+
* Used internally by QueryBuilder when hydrating relationships
|
|
324
|
+
*
|
|
325
|
+
* @param name - Relationship name
|
|
326
|
+
* @param value - Related model(s)
|
|
327
|
+
*/
|
|
328
|
+
setRelation<R = any>(name: string, value: R): void;
|
|
329
|
+
/**
|
|
330
|
+
* Check if a relationship is loaded
|
|
331
|
+
*
|
|
332
|
+
* @param name - Relationship name
|
|
333
|
+
* @returns true if relationship is loaded
|
|
334
|
+
*
|
|
335
|
+
* @example
|
|
336
|
+
* ```typescript
|
|
337
|
+
* if (user.hasRelation('posts')) {
|
|
338
|
+
* const posts = user.getRelation<Post[]>('posts');
|
|
339
|
+
* }
|
|
340
|
+
* ```
|
|
341
|
+
*/
|
|
342
|
+
hasRelation(name: string): boolean;
|
|
343
|
+
/**
|
|
344
|
+
* Define a hasMany relationship
|
|
345
|
+
*
|
|
346
|
+
* **Note**: This only defines the relationship metadata.
|
|
347
|
+
* Actual loading must be done via query builder's `with()` method.
|
|
348
|
+
*
|
|
349
|
+
* @param related - Related model class
|
|
350
|
+
* @param foreignKey - Foreign key on related model
|
|
351
|
+
* @param localKey - Local key on this model (default: primary key)
|
|
352
|
+
* @returns Relationship definition
|
|
353
|
+
*
|
|
354
|
+
* @example
|
|
355
|
+
* ```typescript
|
|
356
|
+
* export class User extends Model<User, IUser> {
|
|
357
|
+
* posts() {
|
|
358
|
+
* return this.hasMany(Post, 'user_id');
|
|
359
|
+
* }
|
|
360
|
+
* }
|
|
361
|
+
*
|
|
362
|
+
* // Usage
|
|
363
|
+
* const users = await User.query().with('posts').get();
|
|
364
|
+
* const posts = users[0].getRelation<Post[]>('posts');
|
|
365
|
+
* ```
|
|
366
|
+
*/
|
|
367
|
+
protected hasMany<R extends Model<R, any>>(related: new (...args: any[]) => R, foreignKey: string, localKey?: string): RelationshipDefinition<R[]>;
|
|
368
|
+
/**
|
|
369
|
+
* Define a belongsTo relationship
|
|
370
|
+
*
|
|
371
|
+
* **Note**: This only defines the relationship metadata.
|
|
372
|
+
* Actual loading must be done via query builder's `with()` method.
|
|
373
|
+
*
|
|
374
|
+
* @param related - Related model class
|
|
375
|
+
* @param foreignKey - Foreign key on this model
|
|
376
|
+
* @param ownerKey - Owner key on related model (default: 'id')
|
|
377
|
+
* @returns Relationship definition
|
|
378
|
+
*
|
|
379
|
+
* @example
|
|
380
|
+
* ```typescript
|
|
381
|
+
* export class Post extends Model<Post, IPost> {
|
|
382
|
+
* user() {
|
|
383
|
+
* return this.belongsTo(User, 'user_id');
|
|
384
|
+
* }
|
|
385
|
+
* }
|
|
386
|
+
*
|
|
387
|
+
* // Usage
|
|
388
|
+
* const posts = await Post.query().with('user').get();
|
|
389
|
+
* const user = posts[0].getRelation<User>('user');
|
|
390
|
+
* ```
|
|
391
|
+
*/
|
|
392
|
+
protected belongsTo<R extends Model<R, any>>(related: new (...args: any[]) => R, foreignKey: string, ownerKey?: string): RelationshipDefinition<R | null>;
|
|
393
|
+
/**
|
|
394
|
+
* Define a hasOne relationship
|
|
395
|
+
*
|
|
396
|
+
* **Note**: This only defines the relationship metadata.
|
|
397
|
+
* Actual loading must be done via query builder's `with()` method.
|
|
398
|
+
*
|
|
399
|
+
* @param related - Related model class
|
|
400
|
+
* @param foreignKey - Foreign key on related model
|
|
401
|
+
* @param localKey - Local key on this model (default: primary key)
|
|
402
|
+
* @returns Relationship definition
|
|
403
|
+
*
|
|
404
|
+
* @example
|
|
405
|
+
* ```typescript
|
|
406
|
+
* export class User extends Model<User, IUser> {
|
|
407
|
+
* profile() {
|
|
408
|
+
* return this.hasOne(Profile, 'user_id');
|
|
409
|
+
* }
|
|
410
|
+
* }
|
|
411
|
+
*
|
|
412
|
+
* // Usage
|
|
413
|
+
* const users = await User.query().with('profile').get();
|
|
414
|
+
* const profile = users[0].getRelation<Profile>('profile');
|
|
415
|
+
* ```
|
|
416
|
+
*/
|
|
417
|
+
protected hasOne<R extends Model<R, any>>(related: new (...args: any[]) => R, foreignKey: string, localKey?: string): RelationshipDefinition<R | null>;
|
|
418
|
+
/**
|
|
419
|
+
* Factory method - creates instance from API data
|
|
420
|
+
* Provides backward compatibility with existing `fromApi` pattern
|
|
421
|
+
*
|
|
422
|
+
* @param data - API response data
|
|
423
|
+
* @param container - Optional ServiceContainer
|
|
424
|
+
* @returns Model instance
|
|
425
|
+
*
|
|
426
|
+
* @example
|
|
427
|
+
* ```typescript
|
|
428
|
+
* // Old pattern (still works)
|
|
429
|
+
* const user = User.fromApi(apiData);
|
|
430
|
+
*
|
|
431
|
+
* // New pattern
|
|
432
|
+
* const user = new User(apiData, $quvel);
|
|
433
|
+
* ```
|
|
434
|
+
*/
|
|
435
|
+
static fromApi<T extends Model<T, A>, A = any>(this: new (attrs: Partial<A>, container?: ServiceContainer) => T, data: A, container?: ServiceContainer): T;
|
|
436
|
+
/**
|
|
437
|
+
* Serialize model to JSON
|
|
438
|
+
* Useful for API responses, logging, etc.
|
|
439
|
+
*
|
|
440
|
+
* @returns Plain object representation
|
|
441
|
+
*
|
|
442
|
+
* @example
|
|
443
|
+
* ```typescript
|
|
444
|
+
* console.log(JSON.stringify(user.toJSON()));
|
|
445
|
+
* ```
|
|
446
|
+
*/
|
|
447
|
+
toJSON(): Record<string, any>;
|
|
448
|
+
/**
|
|
449
|
+
* Check if this is a new (unsaved) model
|
|
450
|
+
*
|
|
451
|
+
* @returns true if model has not been saved to the server
|
|
452
|
+
*
|
|
453
|
+
* @example
|
|
454
|
+
* ```typescript
|
|
455
|
+
* const user = new User({ name: 'John' });
|
|
456
|
+
* console.log(user.isNew()); // true
|
|
457
|
+
* await user.save();
|
|
458
|
+
* console.log(user.isNew()); // false
|
|
459
|
+
* ```
|
|
460
|
+
*/
|
|
461
|
+
isNew(): boolean;
|
|
462
|
+
/**
|
|
463
|
+
* Check if this model exists on the server
|
|
464
|
+
*
|
|
465
|
+
* @returns true if model has been saved
|
|
466
|
+
*/
|
|
467
|
+
exists(): boolean;
|
|
468
|
+
}
|
|
469
|
+
//# sourceMappingURL=Model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Model.d.ts","sourceRoot":"","sources":["../../src/orm/Model.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,648 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abstract Base Model Class
|
|
3
|
+
*
|
|
4
|
+
* Provides Active Record pattern for front-end models with:
|
|
5
|
+
* - Instance methods: save(), delete(), refresh(), isDirty()
|
|
6
|
+
* - Static query builder: User.query().where(...).get()
|
|
7
|
+
* - Relationship definitions: hasMany(), belongsTo(), hasOne()
|
|
8
|
+
* - Integration with ServiceContainer for API access
|
|
9
|
+
* - Full TypeScript type safety
|
|
10
|
+
* - SSR compatibility
|
|
11
|
+
*
|
|
12
|
+
* @template T - The model class itself (for proper return types)
|
|
13
|
+
* @template A - The API response interface (IUser, IPost, etc.)
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* export interface IUser {
|
|
18
|
+
* id: string;
|
|
19
|
+
* name: string;
|
|
20
|
+
* email: string;
|
|
21
|
+
* }
|
|
22
|
+
*
|
|
23
|
+
* export class User extends Model<User, IUser> implements IUser {
|
|
24
|
+
* static config: ModelConfig = {
|
|
25
|
+
* endpoint: '/api/v1/users',
|
|
26
|
+
* dates: ['created_at', 'updated_at']
|
|
27
|
+
* };
|
|
28
|
+
*
|
|
29
|
+
* id!: string;
|
|
30
|
+
* name!: string;
|
|
31
|
+
* email!: string;
|
|
32
|
+
*
|
|
33
|
+
* // Relationship definitions
|
|
34
|
+
* posts() {
|
|
35
|
+
* return this.hasMany(Post, 'user_id');
|
|
36
|
+
* }
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export class Model {
|
|
41
|
+
/**
|
|
42
|
+
* Model configuration - MUST be overridden by subclasses
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* static config: ModelConfig = {
|
|
47
|
+
* endpoint: '/api/v1/users',
|
|
48
|
+
* primaryKey: 'id',
|
|
49
|
+
* dates: ['created_at', 'updated_at']
|
|
50
|
+
* };
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
static config;
|
|
54
|
+
/**
|
|
55
|
+
* Global service container for all models
|
|
56
|
+
* Set once in boot file via Model.setContainer($quvel)
|
|
57
|
+
* @private
|
|
58
|
+
*/
|
|
59
|
+
static _globalContainer;
|
|
60
|
+
/**
|
|
61
|
+
* Set global service container for all models
|
|
62
|
+
*
|
|
63
|
+
* Should be called once in your boot file to enable automatic container injection.
|
|
64
|
+
*
|
|
65
|
+
* @param container - ServiceContainer instance
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```typescript
|
|
69
|
+
* // In boot file
|
|
70
|
+
* import { Model } from '@quvel-kit/core/orm';
|
|
71
|
+
*
|
|
72
|
+
* export default boot(({ app }) => {
|
|
73
|
+
* const container = app.config.globalProperties.$quvel;
|
|
74
|
+
* Model.setContainer(container);
|
|
75
|
+
* });
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
static setContainer(container) {
|
|
79
|
+
Model._globalContainer = container;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Get the global service container
|
|
83
|
+
* @returns ServiceContainer or undefined if not set
|
|
84
|
+
*/
|
|
85
|
+
static getContainer() {
|
|
86
|
+
return Model._globalContainer;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Service container instance
|
|
90
|
+
* Provides access to ApiService and other services
|
|
91
|
+
*/
|
|
92
|
+
$quvel;
|
|
93
|
+
/**
|
|
94
|
+
* Metadata for internal state tracking
|
|
95
|
+
* @private
|
|
96
|
+
*/
|
|
97
|
+
$meta;
|
|
98
|
+
/**
|
|
99
|
+
* Constructor - accepts raw API data and optional container
|
|
100
|
+
*
|
|
101
|
+
* @param attributes - Raw attributes from API response
|
|
102
|
+
* @param container - ServiceContainer instance (auto-injected in most cases)
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```typescript
|
|
106
|
+
* // From API response
|
|
107
|
+
* const user = new User({ id: '1', name: 'John', email: 'john@example.com' });
|
|
108
|
+
*
|
|
109
|
+
* // With container (in stores)
|
|
110
|
+
* const user = new User(apiData, this.$quvel);
|
|
111
|
+
*
|
|
112
|
+
* // New unsaved record
|
|
113
|
+
* const user = new User({ name: 'Jane', email: 'jane@example.com' }, $quvel);
|
|
114
|
+
* await user.save(); // POST /api/v1/users
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
constructor(attributes = {}, container) {
|
|
118
|
+
// Initialize metadata
|
|
119
|
+
this.$meta = {
|
|
120
|
+
isNew: !this.getPrimaryKeyValue(attributes),
|
|
121
|
+
original: { ...attributes },
|
|
122
|
+
relations: new Map(),
|
|
123
|
+
isSaving: false,
|
|
124
|
+
isDeleting: false,
|
|
125
|
+
};
|
|
126
|
+
// Assign attributes to model
|
|
127
|
+
this.fill(attributes);
|
|
128
|
+
// Container will be injected later if not provided
|
|
129
|
+
if (container) {
|
|
130
|
+
this.$quvel = container;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Fill model with attributes
|
|
135
|
+
* Handles property assignment and date parsing
|
|
136
|
+
*
|
|
137
|
+
* @param attributes - Attributes to assign to the model
|
|
138
|
+
*/
|
|
139
|
+
fill(attributes) {
|
|
140
|
+
Object.assign(this, attributes);
|
|
141
|
+
// Parse date fields
|
|
142
|
+
const config = this.constructor.config;
|
|
143
|
+
if (config.dates) {
|
|
144
|
+
for (const field of config.dates) {
|
|
145
|
+
if (field in attributes && typeof attributes[field] === 'string') {
|
|
146
|
+
this[field] = new Date(attributes[field]);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Get the API service instance
|
|
153
|
+
* @throws Error if model not initialized with ServiceContainer
|
|
154
|
+
*/
|
|
155
|
+
get api() {
|
|
156
|
+
if (!this.$quvel) {
|
|
157
|
+
throw new Error(`${this.constructor.name} not initialized with ServiceContainer. ` +
|
|
158
|
+
`Pass container to constructor: new ${this.constructor.name}(data, container)`);
|
|
159
|
+
}
|
|
160
|
+
return this.$quvel.api;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Get model configuration
|
|
164
|
+
*/
|
|
165
|
+
get config() {
|
|
166
|
+
return this.constructor.config;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Get primary key value from object or current instance
|
|
170
|
+
*
|
|
171
|
+
* @param obj - Object to get primary key from (defaults to this)
|
|
172
|
+
* @returns Primary key value or undefined
|
|
173
|
+
*/
|
|
174
|
+
getPrimaryKeyValue(obj) {
|
|
175
|
+
const target = obj ?? this;
|
|
176
|
+
const key = this.config.primaryKey ?? 'id';
|
|
177
|
+
return target[key];
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Get the resource URL for this model instance
|
|
181
|
+
* @example '/api/v1/users/123'
|
|
182
|
+
* @throws Error if model is unsaved (no primary key)
|
|
183
|
+
*/
|
|
184
|
+
getResourceUrl() {
|
|
185
|
+
const pk = this.getPrimaryKeyValue();
|
|
186
|
+
if (!pk) {
|
|
187
|
+
throw new Error(`Cannot get resource URL for unsaved ${this.constructor.name}`);
|
|
188
|
+
}
|
|
189
|
+
return `${this.config.endpoint}/${pk}`;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Create a new query builder for this model
|
|
193
|
+
*
|
|
194
|
+
* @param container - Optional ServiceContainer (uses global if available)
|
|
195
|
+
* @returns QueryBuilder instance for fluent API
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* ```typescript
|
|
199
|
+
* const users = await User.query()
|
|
200
|
+
* .where('status', 'active')
|
|
201
|
+
* .with('posts', 'profile')
|
|
202
|
+
* .get();
|
|
203
|
+
* ```
|
|
204
|
+
*/
|
|
205
|
+
static query() {
|
|
206
|
+
// Dynamic import to avoid circular dependency
|
|
207
|
+
const { QueryBuilder } = require('@quvel-kit/core/orm');
|
|
208
|
+
return new QueryBuilder(this, Model._globalContainer);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Find a model by primary key
|
|
212
|
+
*
|
|
213
|
+
* @param id - Primary key value
|
|
214
|
+
* @param container - Optional ServiceContainer
|
|
215
|
+
* @returns Model instance or null if not found
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* ```typescript
|
|
219
|
+
* const user = await User.find('123');
|
|
220
|
+
* if (user) {
|
|
221
|
+
* console.log(user.name);
|
|
222
|
+
* }
|
|
223
|
+
* ```
|
|
224
|
+
*/
|
|
225
|
+
static async find(id, container) {
|
|
226
|
+
const pkField = this.config.primaryKey ?? 'id';
|
|
227
|
+
return this.query(container)
|
|
228
|
+
.where(pkField, id)
|
|
229
|
+
.first();
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Find a model by primary key or throw an error
|
|
233
|
+
*
|
|
234
|
+
* @param id - Primary key value
|
|
235
|
+
* @param container - Optional ServiceContainer
|
|
236
|
+
* @returns Model instance
|
|
237
|
+
* @throws Error if not found
|
|
238
|
+
*
|
|
239
|
+
* @example
|
|
240
|
+
* ```typescript
|
|
241
|
+
* const user = await User.findOrFail('123');
|
|
242
|
+
* // Throws if user doesn't exist
|
|
243
|
+
* ```
|
|
244
|
+
*/
|
|
245
|
+
static async findOrFail(id, container) {
|
|
246
|
+
const model = await this.find(id, container);
|
|
247
|
+
if (!model) {
|
|
248
|
+
throw new Error(`${this.name} with id ${id} not found`);
|
|
249
|
+
}
|
|
250
|
+
return model;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Save the model (create or update)
|
|
254
|
+
*
|
|
255
|
+
* - New models (no primary key): POST to endpoint
|
|
256
|
+
* - Existing models: PUT to resource URL
|
|
257
|
+
*
|
|
258
|
+
* @returns The saved model instance
|
|
259
|
+
* @throws Error if already saving or if API request fails
|
|
260
|
+
*
|
|
261
|
+
* @example
|
|
262
|
+
* ```typescript
|
|
263
|
+
* // Create new
|
|
264
|
+
* const user = new User({ name: 'John', email: 'john@example.com' }, $quvel);
|
|
265
|
+
* await user.save(); // POST /api/v1/users
|
|
266
|
+
*
|
|
267
|
+
* // Update existing
|
|
268
|
+
* user.name = 'Jane';
|
|
269
|
+
* await user.save(); // PUT /api/v1/users/123
|
|
270
|
+
* ```
|
|
271
|
+
*/
|
|
272
|
+
async save() {
|
|
273
|
+
if (this.$meta.isSaving) {
|
|
274
|
+
throw new Error(`${this.constructor.name} is already being saved`);
|
|
275
|
+
}
|
|
276
|
+
this.$meta.isSaving = true;
|
|
277
|
+
try {
|
|
278
|
+
const attributes = this.getAttributes();
|
|
279
|
+
if (this.$meta.isNew) {
|
|
280
|
+
// Create new record via POST
|
|
281
|
+
const response = await this.api.post(this.config.endpoint, attributes);
|
|
282
|
+
const data = (response && typeof response === 'object' && 'data' in response)
|
|
283
|
+
? response.data
|
|
284
|
+
: response;
|
|
285
|
+
this.fill(data);
|
|
286
|
+
this.$meta.isNew = false;
|
|
287
|
+
this.$meta.original = { ...data };
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
// Update existing record via PUT
|
|
291
|
+
const response = await this.api.put(this.getResourceUrl(), attributes);
|
|
292
|
+
const data = (response && typeof response === 'object' && 'data' in response)
|
|
293
|
+
? response.data
|
|
294
|
+
: response;
|
|
295
|
+
this.fill(data);
|
|
296
|
+
this.$meta.original = { ...data };
|
|
297
|
+
}
|
|
298
|
+
return this;
|
|
299
|
+
}
|
|
300
|
+
finally {
|
|
301
|
+
this.$meta.isSaving = false;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Delete the model from the server
|
|
306
|
+
*
|
|
307
|
+
* @throws Error if model is unsaved or already being deleted
|
|
308
|
+
*
|
|
309
|
+
* @example
|
|
310
|
+
* ```typescript
|
|
311
|
+
* await user.delete(); // DELETE /api/v1/users/123
|
|
312
|
+
* ```
|
|
313
|
+
*/
|
|
314
|
+
async delete() {
|
|
315
|
+
if (this.$meta.isNew) {
|
|
316
|
+
throw new Error(`Cannot delete unsaved ${this.constructor.name}`);
|
|
317
|
+
}
|
|
318
|
+
if (this.$meta.isDeleting) {
|
|
319
|
+
throw new Error(`${this.constructor.name} is already being deleted`);
|
|
320
|
+
}
|
|
321
|
+
this.$meta.isDeleting = true;
|
|
322
|
+
try {
|
|
323
|
+
await this.api.delete(this.getResourceUrl());
|
|
324
|
+
}
|
|
325
|
+
finally {
|
|
326
|
+
this.$meta.isDeleting = false;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Refresh the model from the API
|
|
331
|
+
* Re-fetches the current state from the server
|
|
332
|
+
*
|
|
333
|
+
* @returns The refreshed model instance
|
|
334
|
+
* @throws Error if model is unsaved
|
|
335
|
+
*
|
|
336
|
+
* @example
|
|
337
|
+
* ```typescript
|
|
338
|
+
* await user.refresh(); // GET /api/v1/users/123
|
|
339
|
+
* console.log(user.name); // Updated from server
|
|
340
|
+
* ```
|
|
341
|
+
*/
|
|
342
|
+
async refresh() {
|
|
343
|
+
if (this.$meta.isNew) {
|
|
344
|
+
throw new Error(`Cannot refresh unsaved ${this.constructor.name}`);
|
|
345
|
+
}
|
|
346
|
+
const response = await this.api.get(this.getResourceUrl());
|
|
347
|
+
const data = (response && typeof response === 'object' && 'data' in response)
|
|
348
|
+
? response.data
|
|
349
|
+
: response;
|
|
350
|
+
this.fill(data);
|
|
351
|
+
this.$meta.original = { ...data };
|
|
352
|
+
return this;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Check if model has unsaved changes (dirty checking)
|
|
356
|
+
*
|
|
357
|
+
* Compares current attributes with original attributes from API
|
|
358
|
+
*
|
|
359
|
+
* @returns true if there are unsaved changes
|
|
360
|
+
*
|
|
361
|
+
* @example
|
|
362
|
+
* ```typescript
|
|
363
|
+
* const user = await User.find('123');
|
|
364
|
+
* user.name = 'Changed';
|
|
365
|
+
* console.log(user.isDirty()); // true
|
|
366
|
+
* await user.save();
|
|
367
|
+
* console.log(user.isDirty()); // false
|
|
368
|
+
* ```
|
|
369
|
+
*/
|
|
370
|
+
isDirty() {
|
|
371
|
+
const current = this.getAttributes();
|
|
372
|
+
const original = this.$meta.original;
|
|
373
|
+
for (const key in current) {
|
|
374
|
+
if (current[key] !== original[key]) {
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Check if a specific attribute has changed
|
|
382
|
+
*
|
|
383
|
+
* @param key - Attribute name to check
|
|
384
|
+
* @returns true if the attribute has changed
|
|
385
|
+
*
|
|
386
|
+
* @example
|
|
387
|
+
* ```typescript
|
|
388
|
+
* user.name = 'New Name';
|
|
389
|
+
* console.log(user.isDirtyAttribute('name')); // true
|
|
390
|
+
* console.log(user.isDirtyAttribute('email')); // false
|
|
391
|
+
* ```
|
|
392
|
+
*/
|
|
393
|
+
isDirtyAttribute(key) {
|
|
394
|
+
const current = this.getAttributes();
|
|
395
|
+
const original = this.$meta.original;
|
|
396
|
+
return current[key] !== original[key];
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Get model attributes as plain object
|
|
400
|
+
* Excludes metadata, methods, and internal properties
|
|
401
|
+
*
|
|
402
|
+
* @returns Plain object of model attributes
|
|
403
|
+
*
|
|
404
|
+
* @example
|
|
405
|
+
* ```typescript
|
|
406
|
+
* const attrs = user.getAttributes();
|
|
407
|
+
* console.log(attrs); // { id: '123', name: 'John', email: 'john@example.com' }
|
|
408
|
+
* ```
|
|
409
|
+
*/
|
|
410
|
+
getAttributes() {
|
|
411
|
+
const attrs = {};
|
|
412
|
+
for (const key in this) {
|
|
413
|
+
// Skip internal properties, methods, and container
|
|
414
|
+
if (key !== '$quvel' &&
|
|
415
|
+
key !== '$meta' &&
|
|
416
|
+
typeof this[key] !== 'function' &&
|
|
417
|
+
!key.startsWith('$')) {
|
|
418
|
+
attrs[key] = this[key];
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
return attrs;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Get original attribute value (before changes)
|
|
425
|
+
*
|
|
426
|
+
* @param key - Attribute name
|
|
427
|
+
* @returns Original value from API
|
|
428
|
+
*
|
|
429
|
+
* @example
|
|
430
|
+
* ```typescript
|
|
431
|
+
* const user = await User.find('123');
|
|
432
|
+
* console.log(user.name); // 'John'
|
|
433
|
+
* user.name = 'Jane';
|
|
434
|
+
* console.log(user.getOriginal('name')); // 'John'
|
|
435
|
+
* ```
|
|
436
|
+
*/
|
|
437
|
+
getOriginal(key) {
|
|
438
|
+
return this.$meta.original[key];
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Reset model to original state (undo changes)
|
|
442
|
+
*
|
|
443
|
+
* @example
|
|
444
|
+
* ```typescript
|
|
445
|
+
* user.name = 'Changed';
|
|
446
|
+
* user.reset();
|
|
447
|
+
* console.log(user.name); // Back to original value
|
|
448
|
+
* ```
|
|
449
|
+
*/
|
|
450
|
+
reset() {
|
|
451
|
+
this.fill(this.$meta.original);
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Get a loaded relationship
|
|
455
|
+
*
|
|
456
|
+
* @param name - Relationship name (method name on model)
|
|
457
|
+
* @returns Related model(s) or undefined if not loaded
|
|
458
|
+
*
|
|
459
|
+
* @example
|
|
460
|
+
* ```typescript
|
|
461
|
+
* const users = await User.query().with('posts').get();
|
|
462
|
+
* const posts = users[0].getRelation<Post[]>('posts');
|
|
463
|
+
* ```
|
|
464
|
+
*/
|
|
465
|
+
getRelation(name) {
|
|
466
|
+
return this.$meta.relations.get(name);
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Set a loaded relationship
|
|
470
|
+
* Used internally by QueryBuilder when hydrating relationships
|
|
471
|
+
*
|
|
472
|
+
* @param name - Relationship name
|
|
473
|
+
* @param value - Related model(s)
|
|
474
|
+
*/
|
|
475
|
+
setRelation(name, value) {
|
|
476
|
+
this.$meta.relations.set(name, value);
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Check if a relationship is loaded
|
|
480
|
+
*
|
|
481
|
+
* @param name - Relationship name
|
|
482
|
+
* @returns true if relationship is loaded
|
|
483
|
+
*
|
|
484
|
+
* @example
|
|
485
|
+
* ```typescript
|
|
486
|
+
* if (user.hasRelation('posts')) {
|
|
487
|
+
* const posts = user.getRelation<Post[]>('posts');
|
|
488
|
+
* }
|
|
489
|
+
* ```
|
|
490
|
+
*/
|
|
491
|
+
hasRelation(name) {
|
|
492
|
+
return this.$meta.relations.has(name);
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Define a hasMany relationship
|
|
496
|
+
*
|
|
497
|
+
* **Note**: This only defines the relationship metadata.
|
|
498
|
+
* Actual loading must be done via query builder's `with()` method.
|
|
499
|
+
*
|
|
500
|
+
* @param related - Related model class
|
|
501
|
+
* @param foreignKey - Foreign key on related model
|
|
502
|
+
* @param localKey - Local key on this model (default: primary key)
|
|
503
|
+
* @returns Relationship definition
|
|
504
|
+
*
|
|
505
|
+
* @example
|
|
506
|
+
* ```typescript
|
|
507
|
+
* export class User extends Model<User, IUser> {
|
|
508
|
+
* posts() {
|
|
509
|
+
* return this.hasMany(Post, 'user_id');
|
|
510
|
+
* }
|
|
511
|
+
* }
|
|
512
|
+
*
|
|
513
|
+
* // Usage
|
|
514
|
+
* const users = await User.query().with('posts').get();
|
|
515
|
+
* const posts = users[0].getRelation<Post[]>('posts');
|
|
516
|
+
* ```
|
|
517
|
+
*/
|
|
518
|
+
hasMany(related, foreignKey, localKey) {
|
|
519
|
+
return {
|
|
520
|
+
type: 'hasMany',
|
|
521
|
+
related,
|
|
522
|
+
foreignKey,
|
|
523
|
+
localKey: localKey ?? (this.config.primaryKey ?? 'id'),
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Define a belongsTo relationship
|
|
528
|
+
*
|
|
529
|
+
* **Note**: This only defines the relationship metadata.
|
|
530
|
+
* Actual loading must be done via query builder's `with()` method.
|
|
531
|
+
*
|
|
532
|
+
* @param related - Related model class
|
|
533
|
+
* @param foreignKey - Foreign key on this model
|
|
534
|
+
* @param ownerKey - Owner key on related model (default: 'id')
|
|
535
|
+
* @returns Relationship definition
|
|
536
|
+
*
|
|
537
|
+
* @example
|
|
538
|
+
* ```typescript
|
|
539
|
+
* export class Post extends Model<Post, IPost> {
|
|
540
|
+
* user() {
|
|
541
|
+
* return this.belongsTo(User, 'user_id');
|
|
542
|
+
* }
|
|
543
|
+
* }
|
|
544
|
+
*
|
|
545
|
+
* // Usage
|
|
546
|
+
* const posts = await Post.query().with('user').get();
|
|
547
|
+
* const user = posts[0].getRelation<User>('user');
|
|
548
|
+
* ```
|
|
549
|
+
*/
|
|
550
|
+
belongsTo(related, foreignKey, ownerKey) {
|
|
551
|
+
return {
|
|
552
|
+
type: 'belongsTo',
|
|
553
|
+
related,
|
|
554
|
+
foreignKey,
|
|
555
|
+
ownerKey: ownerKey ?? 'id',
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Define a hasOne relationship
|
|
560
|
+
*
|
|
561
|
+
* **Note**: This only defines the relationship metadata.
|
|
562
|
+
* Actual loading must be done via query builder's `with()` method.
|
|
563
|
+
*
|
|
564
|
+
* @param related - Related model class
|
|
565
|
+
* @param foreignKey - Foreign key on related model
|
|
566
|
+
* @param localKey - Local key on this model (default: primary key)
|
|
567
|
+
* @returns Relationship definition
|
|
568
|
+
*
|
|
569
|
+
* @example
|
|
570
|
+
* ```typescript
|
|
571
|
+
* export class User extends Model<User, IUser> {
|
|
572
|
+
* profile() {
|
|
573
|
+
* return this.hasOne(Profile, 'user_id');
|
|
574
|
+
* }
|
|
575
|
+
* }
|
|
576
|
+
*
|
|
577
|
+
* // Usage
|
|
578
|
+
* const users = await User.query().with('profile').get();
|
|
579
|
+
* const profile = users[0].getRelation<Profile>('profile');
|
|
580
|
+
* ```
|
|
581
|
+
*/
|
|
582
|
+
hasOne(related, foreignKey, localKey) {
|
|
583
|
+
return {
|
|
584
|
+
type: 'hasOne',
|
|
585
|
+
related,
|
|
586
|
+
foreignKey,
|
|
587
|
+
localKey: localKey ?? (this.config.primaryKey ?? 'id'),
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Factory method - creates instance from API data
|
|
592
|
+
* Provides backward compatibility with existing `fromApi` pattern
|
|
593
|
+
*
|
|
594
|
+
* @param data - API response data
|
|
595
|
+
* @param container - Optional ServiceContainer
|
|
596
|
+
* @returns Model instance
|
|
597
|
+
*
|
|
598
|
+
* @example
|
|
599
|
+
* ```typescript
|
|
600
|
+
* // Old pattern (still works)
|
|
601
|
+
* const user = User.fromApi(apiData);
|
|
602
|
+
*
|
|
603
|
+
* // New pattern
|
|
604
|
+
* const user = new User(apiData, $quvel);
|
|
605
|
+
* ```
|
|
606
|
+
*/
|
|
607
|
+
static fromApi(data, container) {
|
|
608
|
+
return new this(data, container);
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Serialize model to JSON
|
|
612
|
+
* Useful for API responses, logging, etc.
|
|
613
|
+
*
|
|
614
|
+
* @returns Plain object representation
|
|
615
|
+
*
|
|
616
|
+
* @example
|
|
617
|
+
* ```typescript
|
|
618
|
+
* console.log(JSON.stringify(user.toJSON()));
|
|
619
|
+
* ```
|
|
620
|
+
*/
|
|
621
|
+
toJSON() {
|
|
622
|
+
return this.getAttributes();
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Check if this is a new (unsaved) model
|
|
626
|
+
*
|
|
627
|
+
* @returns true if model has not been saved to the server
|
|
628
|
+
*
|
|
629
|
+
* @example
|
|
630
|
+
* ```typescript
|
|
631
|
+
* const user = new User({ name: 'John' });
|
|
632
|
+
* console.log(user.isNew()); // true
|
|
633
|
+
* await user.save();
|
|
634
|
+
* console.log(user.isNew()); // false
|
|
635
|
+
* ```
|
|
636
|
+
*/
|
|
637
|
+
isNew() {
|
|
638
|
+
return this.$meta.isNew;
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* Check if this model exists on the server
|
|
642
|
+
*
|
|
643
|
+
* @returns true if model has been saved
|
|
644
|
+
*/
|
|
645
|
+
exists() {
|
|
646
|
+
return !this.$meta.isNew;
|
|
647
|
+
}
|
|
648
|
+
}
|
package/dist/orm/index.d.ts
CHANGED
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ORM Module - Front-End Active Record Pattern for Spatie Query Builder
|
|
3
3
|
*
|
|
4
|
-
* Provides
|
|
5
|
-
*
|
|
4
|
+
* Provides complete Active Record implementation with QueryBuilder.
|
|
5
|
+
* Advanced users can create intermediate base models or copy Model code into their project.
|
|
6
6
|
*
|
|
7
7
|
* @example
|
|
8
8
|
* ```typescript
|
|
9
|
-
*
|
|
10
|
-
* import { QueryBuilder, type ModelConfig } from '@quvel-kit/core/orm';
|
|
9
|
+
* import { Model, type ModelConfig } from '@quvel-kit/core/orm';
|
|
11
10
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* // Direct usage (recommended for most projects)
|
|
12
|
+
* export class User extends Model<User, IUser> implements IUser {
|
|
13
|
+
* static override config: ModelConfig = { endpoint: '/api/v1/users' };
|
|
14
|
+
* id!: string;
|
|
15
|
+
* name!: string;
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
* // Optional: Intermediate base for app-wide customization
|
|
19
|
+
* export abstract class AppModel<T, A> extends Model<T, A> {
|
|
20
|
+
* myCustomMethod() { /* ... *\/ }
|
|
14
21
|
* }
|
|
15
22
|
* ```
|
|
16
23
|
*/
|
|
24
|
+
export { Model } from './Model.js';
|
|
17
25
|
export { QueryBuilder } from './QueryBuilder.js';
|
|
18
26
|
export type { ModelConfig, FilterOperator, ModelMetadata } from './types.js';
|
|
19
27
|
export { type RelationshipType, type RelationshipDefinition, type RelationshipData, } from './relationships/types.js';
|
package/dist/orm/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/orm/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/orm/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAGH,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,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"}
|
package/dist/orm/index.js
CHANGED
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ORM Module - Front-End Active Record Pattern for Spatie Query Builder
|
|
3
3
|
*
|
|
4
|
-
* Provides
|
|
5
|
-
*
|
|
4
|
+
* Provides complete Active Record implementation with QueryBuilder.
|
|
5
|
+
* Advanced users can create intermediate base models or copy Model code into their project.
|
|
6
6
|
*
|
|
7
7
|
* @example
|
|
8
8
|
* ```typescript
|
|
9
|
-
*
|
|
10
|
-
* import { QueryBuilder, type ModelConfig } from '@quvel-kit/core/orm';
|
|
9
|
+
* import { Model, type ModelConfig } from '@quvel-kit/core/orm';
|
|
11
10
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* // Direct usage (recommended for most projects)
|
|
12
|
+
* export class User extends Model<User, IUser> implements IUser {
|
|
13
|
+
* static override config: ModelConfig = { endpoint: '/api/v1/users' };
|
|
14
|
+
* id!: string;
|
|
15
|
+
* name!: string;
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
* // Optional: Intermediate base for app-wide customization
|
|
19
|
+
* export abstract class AppModel<T, A> extends Model<T, A> {
|
|
20
|
+
* myCustomMethod() { /* ... *\/ }
|
|
14
21
|
* }
|
|
15
22
|
* ```
|
|
16
23
|
*/
|
|
17
24
|
// Core classes
|
|
25
|
+
export { Model } from './Model.js';
|
|
18
26
|
export { QueryBuilder } from './QueryBuilder.js';
|