@iamkirbki/database-handler-core 2.0.0

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.
@@ -0,0 +1,549 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import Table from "@core/Table";
11
+ /**
12
+ * **Model** - Abstract base class for database models providing a fluent ORM-like interface.
13
+ *
14
+ * This abstract class serves as the foundation for all database models in the application,
15
+ * providing a complete CRUD (Create, Read, Update, Delete) interface with a chainable query
16
+ * builder pattern. It abstracts away the complexity of direct database operations while
17
+ * maintaining type safety through TypeScript generics.
18
+ *
19
+ * ### Key Features:
20
+ * - **Type-Safe Operations**: Fully typed CRUD operations using TypeScript generics
21
+ * - **Fluent Interface**: Chainable query methods for intuitive query building
22
+ * - **Automatic Table Mapping**: Table name derived from class name
23
+ * - **Zero Configuration**: Minimal setup required for basic operations
24
+ * - **Consistent API**: Uniform interface across all models
25
+ *
26
+ * ### Architecture:
27
+ * The Model class acts as a bridge between your application code and the underlying
28
+ * Table/Record infrastructure. It maintains internal state for query parameters and
29
+ * provides a clean, high-level API that feels natural for application developers.
30
+ *
31
+ * ### Type Parameter:
32
+ * @template T - The shape of your model data. Must extend `{ id: string }` to ensure
33
+ * all models have a primary key. This generic type flows through all
34
+ * operations, providing compile-time type checking.
35
+ *
36
+ * @example Basic Model Definition
37
+ * ```typescript
38
+ * import Model from './abstract/Model';
39
+ *
40
+ * interface UserData {
41
+ * id: string;
42
+ * name: string;
43
+ * email: string;
44
+ * createdAt: string;
45
+ * }
46
+ *
47
+ * export default class User extends Model<UserData> {
48
+ * // Add custom methods here if needed
49
+ * public findByEmail(email: string): UserData | undefined {
50
+ * return this.where({ email }).get();
51
+ * }
52
+ * }
53
+ * ```
54
+ *
55
+ * @example CRUD Operations
56
+ * ```typescript
57
+ * const db = new Database('./app.db');
58
+ * const user = new User(db);
59
+ *
60
+ * // CREATE - Insert new record
61
+ * const newUser = user.create({
62
+ * id: '1',
63
+ * name: 'John Doe',
64
+ * email: 'john@example.com',
65
+ * createdAt: new Date().toISOString()
66
+ * });
67
+ *
68
+ * // READ - Fetch single record
69
+ * const foundUser = user.where({ id: '1' }).get();
70
+ * console.log(foundUser?.name); // "John Doe"
71
+ *
72
+ * // READ - Fetch all records
73
+ * const allUsers = user.all();
74
+ * console.log(allUsers.length); // Number of users
75
+ *
76
+ * // UPDATE - Modify existing record
77
+ * user.where({ id: '1' }).update({
78
+ * id: '1',
79
+ * name: 'John Updated',
80
+ * email: 'john@example.com',
81
+ * createdAt: new Date().toISOString()
82
+ * });
83
+ *
84
+ * // DELETE - Remove record
85
+ * user.where({ id: '1' }).delete();
86
+ * ```
87
+ *
88
+ * @example Advanced Query Patterns
89
+ * ```typescript
90
+ * // Find user by email
91
+ * const user = userModel.where({ email: 'jane@example.com' }).get();
92
+ *
93
+ * // Update user by email
94
+ * userModel
95
+ * .where({ email: 'jane@example.com' })
96
+ * .update({ id: '2', name: 'Jane Smith', email: 'jane@example.com', createdAt: '...' });
97
+ *
98
+ * // Delete user by name
99
+ * userModel.where({ name: 'Bob Wilson' }).delete();
100
+ *
101
+ * // Check if user exists
102
+ * const exists = userModel.where({ id: '123' }).get() !== undefined;
103
+ * ```
104
+ *
105
+ * @example Custom Model Methods
106
+ * ```typescript
107
+ * export default class User extends Model<UserData> {
108
+ * // Find active users
109
+ * public findActive(): UserData[] {
110
+ * return this.all().filter(user => user.status === 'active');
111
+ * }
112
+ *
113
+ * // Soft delete by setting status
114
+ * public softDelete(id: string): void {
115
+ * const user = this.where({ id }).get();
116
+ * if (user) {
117
+ * this.where({ id }).update({ ...user, status: 'deleted' });
118
+ * }
119
+ * }
120
+ *
121
+ * // Count total users
122
+ * public count(): number {
123
+ * return this.all().length;
124
+ * }
125
+ * }
126
+ * ```
127
+ *
128
+ * ### Best Practices:
129
+ * 1. **Always extend Model**: Never instantiate Model directly - it's abstract
130
+ * 2. **Define clear interfaces**: Create TypeScript interfaces for your data shapes
131
+ * 3. **Use where() before mutations**: Always call where() before update() or delete()
132
+ * 4. **Handle undefined**: get() can return undefined - check before using
133
+ * 5. **Keep models focused**: Add domain logic, but avoid bloated models
134
+ * 6. **Reset state**: Model maintains query state - create new instances for independent queries
135
+ *
136
+ * ### Limitations:
137
+ * - Query parameters persist between operations on the same instance
138
+ * - Complex queries (joins, aggregations) may require custom methods
139
+ * - Batch operations should be implemented in subclasses if needed
140
+ * - No built-in validation - implement in subclasses or use separate validators
141
+ *
142
+ * @abstract
143
+ * @since 1.0.0
144
+ * @see Table - Underlying table management class
145
+ * @see Record - Individual record manipulation class
146
+ */
147
+ export default class Model {
148
+ /**
149
+ * Protected constructor - use static create() factory instead
150
+ *
151
+ * @param table - Table instance for database operations
152
+ */
153
+ constructor(table) {
154
+ /**
155
+ * Current query parameters for filtering operations.
156
+ *
157
+ * This object stores the WHERE clause conditions set via the where() method.
158
+ * It persists across method calls on the same instance, allowing for chainable
159
+ * query building. Reset by calling where() with new parameters or creating
160
+ * a new model instance.
161
+ *
162
+ * @private
163
+ * @example
164
+ * ```typescript
165
+ * // Internal state after: user.where({ id: '1' })
166
+ * // QueryParams = { id: '1' }
167
+ * ```
168
+ */
169
+ this.QueryParams = {};
170
+ this.Table = table;
171
+ }
172
+ /**
173
+ * Create a new Model instance (async factory method).
174
+ *
175
+ * Creates a new model connected to the specified database. The table name is
176
+ * automatically derived from the class name (via this.constructor.name), so a
177
+ * class named `User` will interact with a table named `User`.
178
+ *
179
+ * **Important**: Ensure the table exists in the database before creating a model
180
+ * instance. The Model class does not create tables automatically.
181
+ *
182
+ * @param adapter - Database adapter instance to use for all operations
183
+ * @returns Promise resolving to the model instance
184
+ *
185
+ * @example
186
+ * ```typescript
187
+ * import { createDatabase } from '@handler/better-sqlite3';
188
+ * import User from './models/User';
189
+ *
190
+ * const db = createDatabase('./app.db');
191
+ * await db.exec(`CREATE TABLE IF NOT EXISTS User (
192
+ * id TEXT PRIMARY KEY,
193
+ * name TEXT NOT NULL,
194
+ * email TEXT UNIQUE
195
+ * )`);
196
+ *
197
+ * const userModel = await User.create(db);
198
+ * ```
199
+ */
200
+ static create(adapter) {
201
+ return __awaiter(this, void 0, void 0, function* () {
202
+ const table = yield Table.create(this.name, adapter);
203
+ return new this(table);
204
+ });
205
+ }
206
+ /**
207
+ * Get the Record instance for the current query parameters.
208
+ *
209
+ * This method provides access to the Record instance matching the current
210
+ * QueryParams. It's used internally by get(), update(), and delete() methods.
211
+ * Returns undefined if no matching record exists.
212
+ *
213
+ * @private
214
+ * @returns Promise resolving to the matching Record instance or undefined
215
+ */
216
+ RecordGet() {
217
+ return __awaiter(this, void 0, void 0, function* () {
218
+ return yield this.Table.Record({ where: this.QueryParams });
219
+ });
220
+ }
221
+ /**
222
+ * Retrieve a single record matching the current query parameters.
223
+ *
224
+ * Returns the data for the first record that matches the WHERE conditions set
225
+ * by where(). If no where() was called, behavior is undefined (typically returns
226
+ * the first record, but this is not guaranteed). Returns undefined if no matching
227
+ * record exists.
228
+ *
229
+ * **Best Practice**: Always call where() before get() to ensure predictable results.
230
+ *
231
+ * @returns Promise resolving to the matching record's data, or undefined if not found
232
+ *
233
+ * @example
234
+ * ```typescript
235
+ * // Find user by ID
236
+ * const user = await userModel.where({ id: '123' }).get();
237
+ * if (user) {
238
+ * console.log(user.name);
239
+ * } else {
240
+ * console.log('User not found');
241
+ * }
242
+ *
243
+ * // Find user by email
244
+ * const user = await userModel.where({ email: 'john@example.com' }).get();
245
+ *
246
+ * // Check existence
247
+ * const exists = await userModel.where({ id: '456' }).get() !== undefined;
248
+ * ```
249
+ */
250
+ get() {
251
+ return __awaiter(this, void 0, void 0, function* () {
252
+ const record = yield this.RecordGet();
253
+ return record === null || record === void 0 ? void 0 : record.values;
254
+ });
255
+ }
256
+ /**
257
+ * Retrieve all records from the table.
258
+ *
259
+ * Returns an array containing the data for every record in the table. This method
260
+ * ignores any where() conditions - it always returns the complete table contents.
261
+ * The array will be empty if the table has no records.
262
+ *
263
+ * **Performance Note**: For large tables, consider implementing pagination or
264
+ * filtering in a custom method to avoid loading excessive data into memory.
265
+ *
266
+ * @returns Promise resolving to array of all records in the table
267
+ *
268
+ * @example
269
+ * ```typescript
270
+ * // Get all users
271
+ * const allUsers = await userModel.all();
272
+ * console.log(`Total users: ${allUsers.length}`);
273
+ *
274
+ * // Iterate over all records
275
+ * allUsers.forEach(user => {
276
+ * console.log(`${user.name} - ${user.email}`);
277
+ * });
278
+ *
279
+ * // Filter in memory
280
+ * const activeUsers = (await userModel.all()).filter(u => u.status === 'active');
281
+ *
282
+ * // Map to simpler structure
283
+ * const userNames = (await userModel.all()).map(u => u.name);
284
+ * ```
285
+ */
286
+ all() {
287
+ return __awaiter(this, void 0, void 0, function* () {
288
+ const records = yield this.Table.Records();
289
+ return records.map(record => record.values);
290
+ });
291
+ }
292
+ /**
293
+ * Set WHERE clause conditions for subsequent operations.
294
+ *
295
+ * This method configures the query parameters that will be used by get(), update(),
296
+ * and delete() operations. It follows a fluent interface pattern, returning the
297
+ * model instance to allow method chaining.
298
+ *
299
+ * The parameters are stored internally and persist until where() is called again
300
+ * with different parameters or a new model instance is created.
301
+ *
302
+ * **Important**: Calling where() replaces any previous query parameters - it does
303
+ * not merge them. For AND conditions with multiple fields, pass all conditions in
304
+ * a single where() call.
305
+ *
306
+ * @param QueryParameters - Object mapping column names to their expected values.
307
+ * All conditions are combined with AND logic.
308
+ * @returns The model instance for method chaining
309
+ *
310
+ * @example Basic Usage
311
+ * ```typescript
312
+ * // Single condition
313
+ * const user = userModel.where({ id: '123' }).get();
314
+ *
315
+ * // Multiple conditions (AND logic)
316
+ * const user = userModel.where({
317
+ * email: 'john@example.com',
318
+ * status: 'active'
319
+ * }).get();
320
+ * ```
321
+ *
322
+ * @example Chaining Pattern
323
+ * ```typescript
324
+ * // Chain where() with other operations
325
+ * userModel
326
+ * .where({ id: '123' })
327
+ * .update({ id: '123', name: 'Updated Name', email: 'new@email.com' });
328
+ *
329
+ * userModel
330
+ * .where({ status: 'inactive' })
331
+ * .delete();
332
+ * ```
333
+ *
334
+ * @example State Persistence
335
+ * ```typescript
336
+ * // Query parameters persist
337
+ * const model = new User(db);
338
+ * model.where({ id: '123' });
339
+ * const user1 = model.get(); // Uses id: '123'
340
+ * const user2 = model.get(); // Still uses id: '123'
341
+ *
342
+ * // Reset with new where() call
343
+ * model.where({ id: '456' });
344
+ * const user3 = model.get(); // Now uses id: '456'
345
+ * ```
346
+ */
347
+ where(QueryParameters) {
348
+ this.QueryParams = QueryParameters;
349
+ return this;
350
+ }
351
+ /**
352
+ * Insert a new record into the table.
353
+ *
354
+ * Creates a new database record with the provided data. The data object must
355
+ * include all required fields as defined by your table schema. Returns a Record
356
+ * instance wrapping the newly created data, or undefined if the insert fails.
357
+ *
358
+ * **Note**: This method does not use the where() query parameters - it always
359
+ * inserts a new record. Duplicate IDs or constraint violations will throw errors.
360
+ *
361
+ * @param data - Complete record data to insert. Must satisfy type T constraints.
362
+ * @returns Promise resolving to Record instance wrapping the created data, or undefined on failure
363
+ * @throws Database errors on constraint violations (duplicate IDs, etc.)
364
+ *
365
+ * @example Basic Insert
366
+ * ```typescript
367
+ * const newUser = await userModel.create({
368
+ * id: '123',
369
+ * name: 'John Doe',
370
+ * email: 'john@example.com',
371
+ * createdAt: new Date().toISOString()
372
+ * });
373
+ *
374
+ * if (newUser) {
375
+ * console.log('User created:', newUser.values);
376
+ * }
377
+ * ```
378
+ *
379
+ * @example Handling Duplicates
380
+ * ```typescript
381
+ * try {
382
+ * const user = await userModel.create({
383
+ * id: 'existing-id',
384
+ * name: 'Test',
385
+ * email: 'test@example.com'
386
+ * });
387
+ * } catch (error) {
388
+ * console.error('Failed to create user:', error.message);
389
+ * // Handle duplicate ID or constraint violation
390
+ * }
391
+ * ```
392
+ *
393
+ * @example Batch Insert
394
+ * ```typescript
395
+ * const usersData = [
396
+ * { id: '1', name: 'User 1', email: 'user1@example.com' },
397
+ * { id: '2', name: 'User 2', email: 'user2@example.com' },
398
+ * { id: '3', name: 'User 3', email: 'user3@example.com' }
399
+ * ];
400
+ *
401
+ * const createdUsers = await Promise.all(usersData.map(data => userModel.create(data)));
402
+ * console.log(`Created ${createdUsers.filter(u => u).length} users`);
403
+ * ```
404
+ */
405
+ create(data) {
406
+ return __awaiter(this, void 0, void 0, function* () {
407
+ return yield this.Table.Insert(data);
408
+ });
409
+ }
410
+ /**
411
+ * Update the record matching the current query parameters.
412
+ *
413
+ * Modifies the database record that matches the WHERE conditions set by where().
414
+ * The entire record is replaced with the new data - this is not a partial update.
415
+ * If no record matches the query parameters, this method does nothing silently.
416
+ *
417
+ * **Important**: Always call where() before update() to specify which record to modify.
418
+ * Without where(), the behavior is undefined and may update an arbitrary record.
419
+ *
420
+ * @param data - Complete replacement data for the record. Must satisfy type T.
421
+ * @returns void - No return value. Check with get() if you need confirmation.
422
+ *
423
+ * @example Basic Update
424
+ * ```typescript
425
+ * // Update user by ID
426
+ * userModel.where({ id: '123' }).update({
427
+ * id: '123',
428
+ * name: 'Updated Name',
429
+ * email: 'updated@example.com',
430
+ * createdAt: '2024-01-01T00:00:00Z'
431
+ * });
432
+ * ```
433
+ *
434
+ * @example Update with Verification
435
+ * ```typescript
436
+ * // Get current data
437
+ * const user = userModel.where({ id: '123' }).get();
438
+ *
439
+ * if (user) {
440
+ * // Modify specific field
441
+ * userModel.where({ id: '123' }).update({
442
+ * ...user,
443
+ * name: 'New Name'
444
+ * });
445
+ *
446
+ * // Verify update
447
+ * const updated = userModel.where({ id: '123' }).get();
448
+ * console.log('Updated successfully:', updated?.name === 'New Name');
449
+ * }
450
+ * ```
451
+ *
452
+ * @example Conditional Update
453
+ * ```typescript
454
+ * // Update by email instead of ID
455
+ * const user = userModel.where({ email: 'old@example.com' }).get();
456
+ *
457
+ * if (user) {
458
+ * userModel.where({ email: 'old@example.com' }).update({
459
+ * ...user,
460
+ * email: 'new@example.com'
461
+ * });
462
+ * }
463
+ * ```
464
+ */
465
+ update(data) {
466
+ return __awaiter(this, void 0, void 0, function* () {
467
+ const record = yield this.RecordGet();
468
+ if (record) {
469
+ yield record.Update(data);
470
+ }
471
+ });
472
+ }
473
+ /**
474
+ * Delete the record matching the current query parameters.
475
+ *
476
+ * Permanently removes the database record that matches the WHERE conditions set
477
+ * by where(). This operation cannot be undone. If no record matches the query
478
+ * parameters, this method does nothing silently.
479
+ *
480
+ * **Important**: Always call where() before delete() to specify which record to remove.
481
+ * Without where(), the behavior is undefined and may delete an arbitrary record.
482
+ *
483
+ * **Warning**: This is a permanent deletion. Consider implementing soft deletes
484
+ * (status flags) in your models if you need to recover deleted records.
485
+ *
486
+ * @returns Promise that resolves when deletion is complete
487
+ *
488
+ * @example Basic Deletion
489
+ * ```typescript
490
+ * // Delete user by ID
491
+ * await userModel.where({ id: '123' }).delete();
492
+ *
493
+ * // Verify deletion
494
+ * const deleted = await userModel.where({ id: '123' }).get();
495
+ * console.log('Deleted:', deleted === undefined); // true
496
+ * ```
497
+ *
498
+ * @example Safe Deletion with Confirmation
499
+ * ```typescript
500
+ * // Check before deleting
501
+ * const user = await userModel.where({ id: '123' }).get();
502
+ *
503
+ * if (user) {
504
+ * console.log(`Deleting user: ${user.name}`);
505
+ * await userModel.where({ id: '123' }).delete();
506
+ * console.log('User deleted successfully');
507
+ * } else {
508
+ * console.log('User not found');
509
+ * }
510
+ * ```
511
+ *
512
+ * @example Batch Deletion
513
+ * ```typescript
514
+ * // Delete all inactive users
515
+ * const inactiveUsers = (await userModel.all()).filter(u => u.status === 'inactive');
516
+ *
517
+ * for (const user of inactiveUsers) {
518
+ * await userModel.where({ id: user.id }).delete();
519
+ * }
520
+ *
521
+ * console.log(`Deleted ${inactiveUsers.length} inactive users`);
522
+ * ```
523
+ *
524
+ * @example Soft Delete Alternative
525
+ * ```typescript
526
+ * // Instead of permanent deletion, mark as deleted
527
+ * export default class User extends Model<UserData> {
528
+ * public async softDelete(id: string): Promise<void> {
529
+ * const user = await this.where({ id }).get();
530
+ * if (user) {
531
+ * await this.where({ id }).update({
532
+ * ...user,
533
+ * status: 'deleted',
534
+ * deletedAt: new Date().toISOString()
535
+ * });
536
+ * }
537
+ * }
538
+ * }
539
+ * ```
540
+ */
541
+ delete() {
542
+ return __awaiter(this, void 0, void 0, function* () {
543
+ const record = yield this.RecordGet();
544
+ if (record) {
545
+ yield record.Delete();
546
+ }
547
+ });
548
+ }
549
+ }
@@ -0,0 +1,6 @@
1
+ import Model from "./Model";
2
+ export default class User extends Model {
3
+ someMethod() {
4
+ // console.log("This is a method in the User model.");
5
+ }
6
+ }