@hawiah/core 1.0.2
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/LICENSE +21 -0
- package/README.md +102 -0
- package/dist/Hawiah.d.ts +348 -0
- package/dist/Hawiah.js +709 -0
- package/dist/Hawiah.js.map +1 -0
- package/dist/drivers/MemoryDriver.d.ts +17 -0
- package/dist/drivers/MemoryDriver.js +81 -0
- package/dist/drivers/MemoryDriver.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces/IDriver.d.ts +68 -0
- package/dist/interfaces/IDriver.js +3 -0
- package/dist/interfaces/IDriver.js.map +1 -0
- package/package.json +51 -0
package/dist/Hawiah.js
ADDED
|
@@ -0,0 +1,709 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Hawiah = void 0;
|
|
7
|
+
const dataloader_1 = __importDefault(require("dataloader"));
|
|
8
|
+
/**
|
|
9
|
+
* Hawiah: A lightweight, schema-less database abstraction layer.
|
|
10
|
+
* Designed to be friendly and easy to use.
|
|
11
|
+
*/
|
|
12
|
+
class Hawiah {
|
|
13
|
+
driver;
|
|
14
|
+
isConnected = false;
|
|
15
|
+
relations = new Map();
|
|
16
|
+
loaders = new Map();
|
|
17
|
+
/**
|
|
18
|
+
* Creates a new Hawiah instance.
|
|
19
|
+
* @param options - Configuration options
|
|
20
|
+
* @param options.driver - The database driver to use
|
|
21
|
+
*/
|
|
22
|
+
constructor(options) {
|
|
23
|
+
this.driver = options.driver;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Connects to the database.
|
|
27
|
+
*/
|
|
28
|
+
async connect() {
|
|
29
|
+
if (this.isConnected) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
await this.driver.connect();
|
|
33
|
+
this.isConnected = true;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Disconnects from the database.
|
|
37
|
+
*/
|
|
38
|
+
async disconnect() {
|
|
39
|
+
if (!this.isConnected) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
await this.driver.disconnect();
|
|
43
|
+
this.isConnected = false;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Adds a new record to the database.
|
|
47
|
+
* @param data - The data to add
|
|
48
|
+
* @returns The added record
|
|
49
|
+
*/
|
|
50
|
+
async insert(data) {
|
|
51
|
+
this.ensureConnected();
|
|
52
|
+
return await this.driver.set(data);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Inserts multiple records into the database.
|
|
56
|
+
* @param dataArray - Array of data to insert
|
|
57
|
+
* @returns Array of inserted records
|
|
58
|
+
*/
|
|
59
|
+
async insertMany(dataArray) {
|
|
60
|
+
this.ensureConnected();
|
|
61
|
+
const results = [];
|
|
62
|
+
for (const data of dataArray) {
|
|
63
|
+
const result = await this.driver.set(data);
|
|
64
|
+
results.push(result);
|
|
65
|
+
}
|
|
66
|
+
return results;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Gets records matching the query.
|
|
70
|
+
* @param query - The filter condition (default: all)
|
|
71
|
+
* @param limit - Optional maximum number of records to return
|
|
72
|
+
* @returns Array of matching records
|
|
73
|
+
*/
|
|
74
|
+
async get(query = {}, limit) {
|
|
75
|
+
this.ensureConnected();
|
|
76
|
+
const results = await this.driver.get(query);
|
|
77
|
+
if (limit && limit > 0) {
|
|
78
|
+
return results.slice(0, limit);
|
|
79
|
+
}
|
|
80
|
+
return results;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Gets a single record matching the query.
|
|
84
|
+
* @param query - The filter condition
|
|
85
|
+
* @returns The first matching record or null
|
|
86
|
+
*/
|
|
87
|
+
async getOne(query) {
|
|
88
|
+
this.ensureConnected();
|
|
89
|
+
return await this.driver.getOne(query);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Gets all records in the database.
|
|
93
|
+
* @returns Array of all records
|
|
94
|
+
*/
|
|
95
|
+
async getAll() {
|
|
96
|
+
this.ensureConnected();
|
|
97
|
+
return await this.driver.get({});
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Gets records matching any of the provided queries.
|
|
101
|
+
* @param queries - Array of filter conditions
|
|
102
|
+
* @returns Combined array of matching records
|
|
103
|
+
*/
|
|
104
|
+
async getMany(queries) {
|
|
105
|
+
this.ensureConnected();
|
|
106
|
+
const results = [];
|
|
107
|
+
for (const query of queries) {
|
|
108
|
+
const data = await this.driver.get(query);
|
|
109
|
+
results.push(...data);
|
|
110
|
+
}
|
|
111
|
+
return results;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Updates records matching the query.
|
|
115
|
+
* @param query - The filter condition
|
|
116
|
+
* @param data - The data to update
|
|
117
|
+
* @returns Number of updated records
|
|
118
|
+
*/
|
|
119
|
+
async update(query, data) {
|
|
120
|
+
this.ensureConnected();
|
|
121
|
+
return await this.driver.update(query, data);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Updates a single record matching the query.
|
|
125
|
+
* @param query - The filter condition
|
|
126
|
+
* @param data - The data to update
|
|
127
|
+
* @returns True if a record was updated
|
|
128
|
+
*/
|
|
129
|
+
async updateOne(query, data) {
|
|
130
|
+
this.ensureConnected();
|
|
131
|
+
const count = await this.driver.update(query, data);
|
|
132
|
+
return count > 0;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Saves a record (adds if new, updates if exists).
|
|
136
|
+
* @param query - The filter condition to check existence
|
|
137
|
+
* @param data - The data to add or update
|
|
138
|
+
* @returns The saved record
|
|
139
|
+
*/
|
|
140
|
+
async save(query, data) {
|
|
141
|
+
this.ensureConnected();
|
|
142
|
+
const existing = await this.driver.getOne(query);
|
|
143
|
+
if (existing) {
|
|
144
|
+
await this.driver.update(query, data);
|
|
145
|
+
return { ...existing, ...data };
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
return await this.driver.set({ ...query, ...data });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Removes records matching the query.
|
|
153
|
+
* @param query - The filter condition
|
|
154
|
+
* @returns Number of removed records
|
|
155
|
+
*/
|
|
156
|
+
async remove(query) {
|
|
157
|
+
this.ensureConnected();
|
|
158
|
+
return await this.driver.delete(query);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Removes a single record matching the query.
|
|
162
|
+
* @param query - The filter condition
|
|
163
|
+
* @returns True if a record was removed
|
|
164
|
+
*/
|
|
165
|
+
async removeOne(query) {
|
|
166
|
+
this.ensureConnected();
|
|
167
|
+
const count = await this.driver.delete(query);
|
|
168
|
+
return count > 0;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Clears all records from the database.
|
|
172
|
+
* @returns Number of removed records
|
|
173
|
+
*/
|
|
174
|
+
async clear() {
|
|
175
|
+
this.ensureConnected();
|
|
176
|
+
return await this.driver.delete({});
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Checks if records exist matching the query.
|
|
180
|
+
* @param query - The filter condition
|
|
181
|
+
* @returns True if exists
|
|
182
|
+
*/
|
|
183
|
+
async has(query) {
|
|
184
|
+
this.ensureConnected();
|
|
185
|
+
return await this.driver.exists(query);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Counts records matching the query.
|
|
189
|
+
* @param query - The filter condition
|
|
190
|
+
* @returns Count of records
|
|
191
|
+
*/
|
|
192
|
+
async count(query = {}) {
|
|
193
|
+
this.ensureConnected();
|
|
194
|
+
return await this.driver.count(query);
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Counts records where a field matches a value.
|
|
198
|
+
* @param field - The field name
|
|
199
|
+
* @param value - The value to match
|
|
200
|
+
* @returns Count of records
|
|
201
|
+
*/
|
|
202
|
+
async countBy(field, value) {
|
|
203
|
+
this.ensureConnected();
|
|
204
|
+
const query = { [field]: value };
|
|
205
|
+
return await this.driver.count(query);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Gets a record by its ID.
|
|
209
|
+
* @param id - The record ID to search for
|
|
210
|
+
* @returns The matching record or null if not found
|
|
211
|
+
*/
|
|
212
|
+
async getById(id) {
|
|
213
|
+
this.ensureConnected();
|
|
214
|
+
return await this.driver.getOne({ _id: id });
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Updates a record by its ID.
|
|
218
|
+
* @param id - The record ID to update
|
|
219
|
+
* @param data - The data to update
|
|
220
|
+
* @returns True if the record was updated
|
|
221
|
+
*/
|
|
222
|
+
async updateById(id, data) {
|
|
223
|
+
this.ensureConnected();
|
|
224
|
+
const count = await this.driver.update({ _id: id }, data);
|
|
225
|
+
return count > 0;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Removes a record by its ID.
|
|
229
|
+
* @param id - The record ID to remove
|
|
230
|
+
* @returns True if the record was removed
|
|
231
|
+
*/
|
|
232
|
+
async removeById(id) {
|
|
233
|
+
this.ensureConnected();
|
|
234
|
+
const count = await this.driver.delete({ _id: id });
|
|
235
|
+
return count > 0;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Checks if a record exists by its ID.
|
|
239
|
+
* @param id - The record ID to check
|
|
240
|
+
* @returns True if the record exists
|
|
241
|
+
*/
|
|
242
|
+
async hasId(id) {
|
|
243
|
+
this.ensureConnected();
|
|
244
|
+
return await this.driver.exists({ _id: id });
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Gets records where a field matches a value.
|
|
248
|
+
* @param field - The field name to match
|
|
249
|
+
* @param value - The value to match
|
|
250
|
+
* @returns Array of matching records
|
|
251
|
+
*/
|
|
252
|
+
async getBy(field, value) {
|
|
253
|
+
this.ensureConnected();
|
|
254
|
+
const query = { [field]: value };
|
|
255
|
+
return await this.driver.get(query);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Checks if a record exists where a field matches a value.
|
|
259
|
+
* @param field - The field name to check
|
|
260
|
+
* @param value - The value to match
|
|
261
|
+
* @returns True if a matching record exists
|
|
262
|
+
*/
|
|
263
|
+
async hasBy(field, value) {
|
|
264
|
+
this.ensureConnected();
|
|
265
|
+
const query = { [field]: value };
|
|
266
|
+
return await this.driver.exists(query);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Sorts the results based on a field.
|
|
270
|
+
* @param query - The filter condition
|
|
271
|
+
* @param field - The field to sort by
|
|
272
|
+
* @param direction - 'asc' or 'desc'
|
|
273
|
+
* @returns Sorted array of records
|
|
274
|
+
*/
|
|
275
|
+
async sort(query, field, direction = 'asc') {
|
|
276
|
+
this.ensureConnected();
|
|
277
|
+
const results = await this.driver.get(query);
|
|
278
|
+
return results.sort((a, b) => {
|
|
279
|
+
if (a[field] < b[field])
|
|
280
|
+
return direction === 'asc' ? -1 : 1;
|
|
281
|
+
if (a[field] > b[field])
|
|
282
|
+
return direction === 'asc' ? 1 : -1;
|
|
283
|
+
return 0;
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Selects specific fields from the results.
|
|
288
|
+
* @param query - The filter condition
|
|
289
|
+
* @param fields - Array of field names to select
|
|
290
|
+
* @returns Array of records with only selected fields
|
|
291
|
+
*/
|
|
292
|
+
async select(query, fields) {
|
|
293
|
+
this.ensureConnected();
|
|
294
|
+
const results = await this.driver.get(query);
|
|
295
|
+
return results.map(record => {
|
|
296
|
+
const selected = {};
|
|
297
|
+
fields.forEach(field => {
|
|
298
|
+
if (field in record) {
|
|
299
|
+
selected[field] = record[field];
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
return selected;
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Retrieves unique values for a specific field.
|
|
307
|
+
* @param field - The field to get unique values for
|
|
308
|
+
* @param query - Optional filter condition
|
|
309
|
+
* @returns Array of unique values
|
|
310
|
+
*/
|
|
311
|
+
async unique(field, query = {}) {
|
|
312
|
+
this.ensureConnected();
|
|
313
|
+
const results = await this.driver.get(query);
|
|
314
|
+
const values = results.map(record => record[field]);
|
|
315
|
+
return [...new Set(values)];
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Groups records by a specific field.
|
|
319
|
+
* @param field - The field to group by
|
|
320
|
+
* @param query - Optional filter condition
|
|
321
|
+
* @returns Object where keys are group values and values are arrays of records
|
|
322
|
+
*/
|
|
323
|
+
async group(field, query = {}) {
|
|
324
|
+
this.ensureConnected();
|
|
325
|
+
const results = await this.driver.get(query);
|
|
326
|
+
return results.reduce((groups, record) => {
|
|
327
|
+
const key = String(record[field]);
|
|
328
|
+
if (!groups[key]) {
|
|
329
|
+
groups[key] = [];
|
|
330
|
+
}
|
|
331
|
+
groups[key].push(record);
|
|
332
|
+
return groups;
|
|
333
|
+
}, {});
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Paginates results.
|
|
337
|
+
* @param query - The filter condition
|
|
338
|
+
* @param page - Page number (1-based)
|
|
339
|
+
* @param pageSize - Records per page
|
|
340
|
+
* @returns Pagination result object
|
|
341
|
+
*/
|
|
342
|
+
async paginate(query, page = 1, pageSize = 10) {
|
|
343
|
+
this.ensureConnected();
|
|
344
|
+
const allResults = await this.driver.get(query);
|
|
345
|
+
const total = allResults.length;
|
|
346
|
+
const totalPages = Math.ceil(total / pageSize);
|
|
347
|
+
const startIndex = (page - 1) * pageSize;
|
|
348
|
+
const endIndex = startIndex + pageSize;
|
|
349
|
+
const data = allResults.slice(startIndex, endIndex);
|
|
350
|
+
return {
|
|
351
|
+
data,
|
|
352
|
+
page,
|
|
353
|
+
pageSize,
|
|
354
|
+
total,
|
|
355
|
+
totalPages
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Calculates the sum of a numeric field.
|
|
360
|
+
* @param field - The field to sum
|
|
361
|
+
* @param query - Optional filter condition
|
|
362
|
+
* @returns The sum of all values in the field
|
|
363
|
+
*/
|
|
364
|
+
async sum(field, query = {}) {
|
|
365
|
+
this.ensureConnected();
|
|
366
|
+
const results = await this.driver.get(query);
|
|
367
|
+
return results.reduce((sum, record) => sum + (Number(record[field]) || 0), 0);
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Increments a numeric field by a specified amount.
|
|
371
|
+
* @param query - The filter condition to find the record
|
|
372
|
+
* @param field - The field to increment
|
|
373
|
+
* @param amount - The amount to increment by (default: 1)
|
|
374
|
+
* @returns The new value after incrementing
|
|
375
|
+
* @throws Error if record is not found
|
|
376
|
+
*/
|
|
377
|
+
async increment(query, field, amount = 1) {
|
|
378
|
+
this.ensureConnected();
|
|
379
|
+
const record = await this.driver.getOne(query);
|
|
380
|
+
if (!record) {
|
|
381
|
+
throw new Error('Record not found');
|
|
382
|
+
}
|
|
383
|
+
const currentValue = Number(record[field]) || 0;
|
|
384
|
+
const newValue = currentValue + amount;
|
|
385
|
+
await this.driver.update(query, { [field]: newValue });
|
|
386
|
+
return newValue;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Decrements a numeric field by a specified amount.
|
|
390
|
+
* @param query - The filter condition to find the record
|
|
391
|
+
* @param field - The field to decrement
|
|
392
|
+
* @param amount - The amount to decrement by (default: 1)
|
|
393
|
+
* @returns The new value after decrementing
|
|
394
|
+
* @throws Error if record is not found
|
|
395
|
+
*/
|
|
396
|
+
async decrement(query, field, amount = 1) {
|
|
397
|
+
return await this.increment(query, field, -amount);
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Pushes a value to the end of an array field.
|
|
401
|
+
* @param query - The filter condition to find records
|
|
402
|
+
* @param field - The array field to push to
|
|
403
|
+
* @param value - The value to push
|
|
404
|
+
* @returns Number of records updated
|
|
405
|
+
*/
|
|
406
|
+
async push(query, field, value) {
|
|
407
|
+
this.ensureConnected();
|
|
408
|
+
const records = await this.driver.get(query);
|
|
409
|
+
let count = 0;
|
|
410
|
+
for (const record of records) {
|
|
411
|
+
if (!Array.isArray(record[field])) {
|
|
412
|
+
record[field] = [];
|
|
413
|
+
}
|
|
414
|
+
record[field].push(value);
|
|
415
|
+
await this.driver.update(query, record);
|
|
416
|
+
count++;
|
|
417
|
+
}
|
|
418
|
+
return count;
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Removes all occurrences of a value from an array field.
|
|
422
|
+
* @param query - The filter condition to find records
|
|
423
|
+
* @param field - The array field to pull from
|
|
424
|
+
* @param value - The value to remove
|
|
425
|
+
* @returns Number of records updated
|
|
426
|
+
*/
|
|
427
|
+
async pull(query, field, value) {
|
|
428
|
+
this.ensureConnected();
|
|
429
|
+
const records = await this.driver.get(query);
|
|
430
|
+
let count = 0;
|
|
431
|
+
for (const record of records) {
|
|
432
|
+
if (Array.isArray(record[field])) {
|
|
433
|
+
const initialLength = record[field].length;
|
|
434
|
+
record[field] = record[field].filter((item) => JSON.stringify(item) !== JSON.stringify(value));
|
|
435
|
+
if (record[field].length !== initialLength) {
|
|
436
|
+
await this.driver.update(query, record);
|
|
437
|
+
count++;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return count;
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Removes the first element from an array field.
|
|
445
|
+
* @param query - The filter condition to find records
|
|
446
|
+
* @param field - The array field to shift
|
|
447
|
+
* @returns Number of records updated
|
|
448
|
+
*/
|
|
449
|
+
async shift(query, field) {
|
|
450
|
+
this.ensureConnected();
|
|
451
|
+
const records = await this.driver.get(query);
|
|
452
|
+
let count = 0;
|
|
453
|
+
for (const record of records) {
|
|
454
|
+
if (Array.isArray(record[field]) && record[field].length > 0) {
|
|
455
|
+
record[field].shift();
|
|
456
|
+
await this.driver.update(query, record);
|
|
457
|
+
count++;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return count;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Adds a value to the beginning of an array field.
|
|
464
|
+
* @param query - The filter condition to find records
|
|
465
|
+
* @param field - The array field to unshift to
|
|
466
|
+
* @param value - The value to add
|
|
467
|
+
* @returns Number of records updated
|
|
468
|
+
*/
|
|
469
|
+
async unshift(query, field, value) {
|
|
470
|
+
this.ensureConnected();
|
|
471
|
+
const records = await this.driver.get(query);
|
|
472
|
+
let count = 0;
|
|
473
|
+
for (const record of records) {
|
|
474
|
+
if (!Array.isArray(record[field])) {
|
|
475
|
+
record[field] = [];
|
|
476
|
+
}
|
|
477
|
+
record[field].unshift(value);
|
|
478
|
+
await this.driver.update(query, record);
|
|
479
|
+
count++;
|
|
480
|
+
}
|
|
481
|
+
return count;
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Removes the last element from an array field.
|
|
485
|
+
* @param query - The filter condition to find records
|
|
486
|
+
* @param field - The array field to pop from
|
|
487
|
+
* @returns Number of records updated
|
|
488
|
+
*/
|
|
489
|
+
async pop(query, field) {
|
|
490
|
+
this.ensureConnected();
|
|
491
|
+
const records = await this.driver.get(query);
|
|
492
|
+
let count = 0;
|
|
493
|
+
for (const record of records) {
|
|
494
|
+
if (Array.isArray(record[field]) && record[field].length > 0) {
|
|
495
|
+
record[field].pop();
|
|
496
|
+
await this.driver.update(query, record);
|
|
497
|
+
count++;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return count;
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Removes a field from matching records.
|
|
504
|
+
* @param query - The filter condition to find records
|
|
505
|
+
* @param field - The field name to remove
|
|
506
|
+
* @returns Number of records updated
|
|
507
|
+
*/
|
|
508
|
+
async unset(query, field) {
|
|
509
|
+
this.ensureConnected();
|
|
510
|
+
const records = await this.driver.get(query);
|
|
511
|
+
let count = 0;
|
|
512
|
+
for (const record of records) {
|
|
513
|
+
if (field in record) {
|
|
514
|
+
delete record[field];
|
|
515
|
+
await this.driver.update(query, record);
|
|
516
|
+
count++;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return count;
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Renames a field in matching records.
|
|
523
|
+
* @param query - The filter condition to find records
|
|
524
|
+
* @param oldField - The current field name
|
|
525
|
+
* @param newField - The new field name
|
|
526
|
+
* @returns Number of records updated
|
|
527
|
+
*/
|
|
528
|
+
async rename(query, oldField, newField) {
|
|
529
|
+
this.ensureConnected();
|
|
530
|
+
const records = await this.driver.get(query);
|
|
531
|
+
let count = 0;
|
|
532
|
+
for (const record of records) {
|
|
533
|
+
if (oldField in record) {
|
|
534
|
+
record[newField] = record[oldField];
|
|
535
|
+
delete record[oldField];
|
|
536
|
+
await this.driver.update(query, record);
|
|
537
|
+
count++;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return count;
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Gets the first record in the database.
|
|
544
|
+
* @returns The first record or null if empty
|
|
545
|
+
*/
|
|
546
|
+
async first() {
|
|
547
|
+
this.ensureConnected();
|
|
548
|
+
const results = await this.driver.get({});
|
|
549
|
+
return results.length > 0 ? results[0] : null;
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Gets the last record in the database.
|
|
553
|
+
* @returns The last record or null if empty
|
|
554
|
+
*/
|
|
555
|
+
async last() {
|
|
556
|
+
this.ensureConnected();
|
|
557
|
+
const results = await this.driver.get({});
|
|
558
|
+
return results.length > 0 ? results[results.length - 1] : null;
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Checks if the database is empty.
|
|
562
|
+
* @returns True if no records exist
|
|
563
|
+
*/
|
|
564
|
+
async isEmpty() {
|
|
565
|
+
this.ensureConnected();
|
|
566
|
+
const count = await this.driver.count({});
|
|
567
|
+
return count === 0;
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Gets random records from the database.
|
|
571
|
+
* @param sampleSize - Number of random records to return (default: 1)
|
|
572
|
+
* @returns Array of random records
|
|
573
|
+
*/
|
|
574
|
+
async random(sampleSize = 1) {
|
|
575
|
+
this.ensureConnected();
|
|
576
|
+
const results = await this.driver.get({});
|
|
577
|
+
const shuffled = results.sort(() => 0.5 - Math.random());
|
|
578
|
+
return shuffled.slice(0, sampleSize);
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Ensures the database is connected before executing operations.
|
|
582
|
+
* @throws Error if database is not connected
|
|
583
|
+
* @private
|
|
584
|
+
*/
|
|
585
|
+
ensureConnected() {
|
|
586
|
+
if (!this.isConnected) {
|
|
587
|
+
throw new Error('Database not connected. Call connect() first.');
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Gets the underlying database driver.
|
|
592
|
+
* @returns The database driver instance
|
|
593
|
+
*/
|
|
594
|
+
getDriver() {
|
|
595
|
+
return this.driver;
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Checks if the database connection is active.
|
|
599
|
+
* @returns True if connected
|
|
600
|
+
*/
|
|
601
|
+
isActive() {
|
|
602
|
+
return this.isConnected;
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Define a relationship with another Hawiah instance
|
|
606
|
+
* @param name - Relationship name
|
|
607
|
+
* @param target - Target Hawiah instance
|
|
608
|
+
* @param localKey - Local field name
|
|
609
|
+
* @param foreignKey - Foreign field name
|
|
610
|
+
* @param type - Relationship type ('one' or 'many')
|
|
611
|
+
*/
|
|
612
|
+
relation(name, target, localKey, foreignKey, type = 'many') {
|
|
613
|
+
this.relations.set(name, { target, localKey, foreignKey, type });
|
|
614
|
+
return this;
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Get records with populated relationships
|
|
618
|
+
* @param query - Query filter
|
|
619
|
+
* @param relations - Relationship names to populate
|
|
620
|
+
*/
|
|
621
|
+
async getWith(query = {}, ...relations) {
|
|
622
|
+
this.ensureConnected();
|
|
623
|
+
const records = await this.driver.get(query);
|
|
624
|
+
if (relations.length === 0 || records.length === 0) {
|
|
625
|
+
return records;
|
|
626
|
+
}
|
|
627
|
+
for (const relationName of relations) {
|
|
628
|
+
await this.loadRelation(records, relationName);
|
|
629
|
+
}
|
|
630
|
+
return records;
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Get one record with populated relationships
|
|
634
|
+
* @param query - Query filter
|
|
635
|
+
* @param relations - Relationship names to populate
|
|
636
|
+
*/
|
|
637
|
+
async getOneWith(query, ...relations) {
|
|
638
|
+
this.ensureConnected();
|
|
639
|
+
const record = await this.driver.getOne(query);
|
|
640
|
+
if (!record || relations.length === 0) {
|
|
641
|
+
return record;
|
|
642
|
+
}
|
|
643
|
+
for (const relationName of relations) {
|
|
644
|
+
await this.loadRelation([record], relationName);
|
|
645
|
+
}
|
|
646
|
+
return record;
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Load a relationship for records using DataLoader batching
|
|
650
|
+
*/
|
|
651
|
+
async loadRelation(records, relationName) {
|
|
652
|
+
const relation = this.relations.get(relationName);
|
|
653
|
+
if (!relation) {
|
|
654
|
+
throw new Error(`Relation "${relationName}" not defined`);
|
|
655
|
+
}
|
|
656
|
+
const { target, localKey, foreignKey, type } = relation;
|
|
657
|
+
let loader = this.loaders.get(relationName);
|
|
658
|
+
if (!loader) {
|
|
659
|
+
loader = new dataloader_1.default(async (keys) => {
|
|
660
|
+
const allRecords = await target.get({});
|
|
661
|
+
if (type === 'one') {
|
|
662
|
+
const map = new Map();
|
|
663
|
+
allRecords.forEach(record => {
|
|
664
|
+
if (keys.includes(record[foreignKey])) {
|
|
665
|
+
map.set(record[foreignKey], record);
|
|
666
|
+
}
|
|
667
|
+
});
|
|
668
|
+
return keys.map(key => map.get(key) || null);
|
|
669
|
+
}
|
|
670
|
+
else {
|
|
671
|
+
const map = new Map();
|
|
672
|
+
allRecords.forEach(record => {
|
|
673
|
+
if (keys.includes(record[foreignKey])) {
|
|
674
|
+
if (!map.has(record[foreignKey])) {
|
|
675
|
+
map.set(record[foreignKey], []);
|
|
676
|
+
}
|
|
677
|
+
map.get(record[foreignKey]).push(record);
|
|
678
|
+
}
|
|
679
|
+
});
|
|
680
|
+
return keys.map(key => map.get(key) || []);
|
|
681
|
+
}
|
|
682
|
+
}, { cache: true });
|
|
683
|
+
this.loaders.set(relationName, loader);
|
|
684
|
+
}
|
|
685
|
+
const keys = records.map(r => r[localKey]).filter(k => k != null);
|
|
686
|
+
if (keys.length === 0)
|
|
687
|
+
return;
|
|
688
|
+
const results = await loader.loadMany(keys);
|
|
689
|
+
let resultIndex = 0;
|
|
690
|
+
records.forEach(record => {
|
|
691
|
+
if (record[localKey] != null) {
|
|
692
|
+
record[relationName] = results[resultIndex];
|
|
693
|
+
resultIndex++;
|
|
694
|
+
}
|
|
695
|
+
else {
|
|
696
|
+
record[relationName] = type === 'many' ? [] : null;
|
|
697
|
+
}
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Clear relationship cache
|
|
702
|
+
*/
|
|
703
|
+
clearCache() {
|
|
704
|
+
this.loaders.forEach(loader => loader.clearAll());
|
|
705
|
+
this.loaders.clear();
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
exports.Hawiah = Hawiah;
|
|
709
|
+
//# sourceMappingURL=Hawiah.js.map
|