locality-idb 0.4.0 → 0.5.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.
package/dist/index.cjs ADDED
@@ -0,0 +1,741 @@
1
+
2
+ //#region node_modules/.pnpm/nhb-toolbox@4.28.66/node_modules/nhb-toolbox/dist/esm/guards/primitives.js
3
+ function isNumber(value) {
4
+ return typeof value === "number" && Number.isFinite(value);
5
+ }
6
+ function isString(value) {
7
+ return typeof value === "string";
8
+ }
9
+ function isInteger(value) {
10
+ return isNumber(value) && Number.isInteger(value);
11
+ }
12
+ function isPositiveInteger(value) {
13
+ return isInteger(value) && value > 0;
14
+ }
15
+ function isBoolean(value) {
16
+ return typeof value === "boolean";
17
+ }
18
+ function isNonEmptyString(value) {
19
+ return isString(value) && value?.length > 0;
20
+ }
21
+
22
+ //#endregion
23
+ //#region node_modules/.pnpm/nhb-toolbox@4.28.66/node_modules/nhb-toolbox/dist/esm/guards/non-primitives.js
24
+ function isArray(value) {
25
+ return Array.isArray(value);
26
+ }
27
+ function isValidArray(value) {
28
+ return Array.isArray(value) && value?.length > 0;
29
+ }
30
+ function isObject(value) {
31
+ return value !== null && typeof value === "object" && !isArray(value);
32
+ }
33
+ function isNotEmptyObject(value) {
34
+ return isObject(value) && Object.keys(value)?.length > 0;
35
+ }
36
+ function isArrayOfType(value, typeCheck) {
37
+ return isArray(value) && value?.every(typeCheck);
38
+ }
39
+
40
+ //#endregion
41
+ //#region node_modules/.pnpm/nhb-toolbox@4.28.66/node_modules/nhb-toolbox/dist/esm/array/sort.js
42
+ function naturalSort(a, b, options) {
43
+ const { caseInsensitive = true, localeAware = false } = options || {};
44
+ const _createChunks = (str) => {
45
+ const chunks = [];
46
+ let current = "";
47
+ let isNumeric = false;
48
+ for (const char of str) {
49
+ const charIsNum = !Number.isNaN(Number(char));
50
+ if (current?.length === 0) {
51
+ current = char;
52
+ isNumeric = charIsNum;
53
+ continue;
54
+ }
55
+ if (charIsNum === isNumeric) current += char;
56
+ else {
57
+ chunks?.push(isNumeric ? Number(current) : current);
58
+ current = char;
59
+ isNumeric = charIsNum;
60
+ }
61
+ }
62
+ if (current?.length > 0) chunks?.push(isNumeric ? Number(current) : current);
63
+ return chunks;
64
+ };
65
+ const aChunks = _createChunks(a);
66
+ const bChunks = _createChunks(b);
67
+ for (let i = 0; i < Math.min(aChunks?.length, bChunks?.length); i++) {
68
+ let aChunk = aChunks[i];
69
+ let bChunk = bChunks[i];
70
+ if (caseInsensitive && typeof aChunk === "string" && typeof bChunk === "string") {
71
+ aChunk = aChunk?.toLowerCase();
72
+ bChunk = bChunk?.toLowerCase();
73
+ }
74
+ if (typeof aChunk !== typeof bChunk) return typeof aChunk === "string" ? 1 : -1;
75
+ if (aChunk !== bChunk) {
76
+ if (typeof aChunk === "number" && typeof bChunk === "number") return aChunk - bChunk;
77
+ if (typeof aChunk === "string" && typeof bChunk === "string") {
78
+ if (localeAware) {
79
+ const cmp = aChunk.localeCompare(bChunk, void 0, { sensitivity: caseInsensitive ? "accent" : "variant" });
80
+ if (cmp !== 0) return cmp;
81
+ }
82
+ return aChunk < bChunk ? -1 : 1;
83
+ }
84
+ }
85
+ }
86
+ return aChunks?.length - bChunks?.length;
87
+ }
88
+ function sortAnArray(array, options) {
89
+ if (!isValidArray(array)) return array;
90
+ if (isArrayOfType(array, isString)) return [...array].sort((a, b) => options?.sortOrder === "desc" ? naturalSort(b, a) : naturalSort(a, b));
91
+ if (isArrayOfType(array, isNumber)) return [...array].sort((a, b) => options?.sortOrder === "desc" ? b - a : a - b);
92
+ if (isArrayOfType(array, isBoolean)) return [...array].sort((a, b) => options?.sortOrder === "desc" ? Number(b) - Number(a) : Number(a) - Number(b));
93
+ if (isArrayOfType(array, isObject) && options && "sortByField" in options) return [...array].sort((a, b) => {
94
+ const _getKeyValue = (obj, path) => {
95
+ return path.split(".").reduce((acc, key) => acc?.[key], obj);
96
+ };
97
+ const keyA = _getKeyValue(a, options?.sortByField);
98
+ const keyB = _getKeyValue(b, options?.sortByField);
99
+ if (keyA == null || keyB == null) return keyA == null ? 1 : -1;
100
+ if (isString(keyA) && isString(keyB)) return options?.sortOrder === "desc" ? naturalSort(keyB, keyA) : naturalSort(keyA, keyB);
101
+ if (isNumber(keyA) && isNumber(keyB)) return options?.sortOrder === "desc" ? keyB - keyA : keyA - keyB;
102
+ if (isBoolean(keyA) && isBoolean(keyB)) return options?.sortOrder === "desc" ? Number(keyB) - Number(keyA) : Number(keyA) - Number(keyB);
103
+ return 0;
104
+ });
105
+ return [...array];
106
+ }
107
+
108
+ //#endregion
109
+ //#region src/core.ts
110
+ /** Symbol key for column column data type */
111
+ const ColumnType = Symbol("ColumnType");
112
+ /** Symbol key for primary key marker */
113
+ const IsPrimaryKey = Symbol("IsPrimaryKey");
114
+ /** Symbol key for auto increment marker */
115
+ const IsAutoInc = Symbol("IsAutoInc");
116
+ /** Symbol key for optional marker */
117
+ const IsOptional = Symbol("IsOptional");
118
+ /** Symbol key for indexed marker */
119
+ const IsIndexed = Symbol("IsIndexed");
120
+ /** Symbol key for unique marker */
121
+ const IsUnique = Symbol("IsUnique");
122
+ /** Symbol key for default value */
123
+ const DefaultValue = Symbol("DefaultValue");
124
+ /** @class Represents a column definition. */
125
+ var Column = class {
126
+ constructor(type) {
127
+ this[ColumnType] = type;
128
+ }
129
+ /** @instance Marks column as primary key */
130
+ pk() {
131
+ this[IsPrimaryKey] = true;
132
+ return this;
133
+ }
134
+ /** @instance Marks column as unique */
135
+ unique() {
136
+ this[IsIndexed] = true;
137
+ this[IsUnique] = true;
138
+ return this;
139
+ }
140
+ /** @instance Enables auto increment - only available for numeric columns */
141
+ auto() {
142
+ const colType = this[ColumnType];
143
+ if (!isNonEmptyString(colType) || ![
144
+ "int",
145
+ "integer",
146
+ "float",
147
+ "number"
148
+ ].includes(colType.toLowerCase())) throw new Error(`auto() can only be used with integer columns, got: ${colType}`);
149
+ this[IsAutoInc] = true;
150
+ return this;
151
+ }
152
+ /** @instance Marks column as indexed */
153
+ index() {
154
+ this[IsIndexed] = true;
155
+ return this;
156
+ }
157
+ /** @instance Sets default value for the column */
158
+ default(value) {
159
+ this[DefaultValue] = value;
160
+ return this;
161
+ }
162
+ /** @instance Marks column as optional */
163
+ optional() {
164
+ this[IsOptional] = true;
165
+ return this;
166
+ }
167
+ };
168
+ /** @class Represents a table. */
169
+ var Table = class {
170
+ name;
171
+ columns;
172
+ constructor(name, columns) {
173
+ this.name = name;
174
+ this.columns = columns;
175
+ }
176
+ };
177
+
178
+ //#endregion
179
+ //#region src/factory.ts
180
+ /**
181
+ * * Opens an IndexedDB database with the specified stores.
182
+ * @param name Database name
183
+ * @param stores Array of store configurations
184
+ * @param version Database version (default is `1`)
185
+ * @returns Promise that resolves to the opened {@link IDBDatabase} instance.
186
+ */
187
+ function openDBWithStores(name, stores, version = 1) {
188
+ return new Promise((resolve, reject) => {
189
+ if (!window.indexedDB) throw new Error("IndexedDb is not supported in this environment or browser!");
190
+ const request = window.indexedDB.open(name, version);
191
+ request.onupgradeneeded = (event) => {
192
+ const db = event.target.result;
193
+ for (const store of stores) if (!db.objectStoreNames.contains(store.name)) db.createObjectStore(store.name, {
194
+ keyPath: store.keyPath,
195
+ autoIncrement: store.autoIncrement
196
+ });
197
+ };
198
+ request.onsuccess = () => resolve(request.result);
199
+ request.onerror = () => reject(request.error);
200
+ });
201
+ }
202
+
203
+ //#endregion
204
+ //#region src/helpers.ts
205
+ /** Ensure UUID variant is RFC4122 compliant */
206
+ function _hexVariant(hex) {
207
+ return (parseInt(hex, 16) & 63 | 128).toString(16).padStart(2, "0");
208
+ }
209
+ /** Convert a hex string to UUID format */
210
+ function _formatUUID(h, v, up) {
211
+ const part3 = String(v) + h.slice(13, 16);
212
+ const part4 = _hexVariant(h.slice(16, 18)) + h.slice(18, 20);
213
+ const formatted = [
214
+ h.slice(0, 8),
215
+ h.slice(8, 12),
216
+ part3,
217
+ part4,
218
+ h.slice(20, 32)
219
+ ].join("-");
220
+ return up ? formatted.toUpperCase() : formatted;
221
+ }
222
+
223
+ //#endregion
224
+ //#region src/utils.ts
225
+ /**
226
+ * * Generate a random UUID v4 string
227
+ * @param uppercase Whether to return the UUID in uppercase format. Default is `false`.
228
+ * @returns UUID v4 string
229
+ */
230
+ function uuidV4(uppercase = false) {
231
+ const bytes = new Uint8Array(16);
232
+ for (let i = 0; i < 16; i++) bytes[i] = Math.floor(Math.random() * 256);
233
+ let hex = "";
234
+ for (let i = 0; i < 16; i++) hex += bytes[i].toString(16).padStart(2, "0");
235
+ return _formatUUID(hex, 4, uppercase);
236
+ }
237
+ /**
238
+ * * Get current timestamp in ISO 8601 format
239
+ * @param date Optional Date object to format. Defaults to current {@link Date new Date()}
240
+ * @return Timestamp string in ISO 8601 format
241
+ */
242
+ function getTimestamp(date = /* @__PURE__ */ new Date()) {
243
+ return date.toISOString();
244
+ }
245
+
246
+ //#endregion
247
+ //#region src/query.ts
248
+ /** Symbol for type extraction (exists only in type system) */
249
+ const Selected = Symbol("Selected");
250
+ /** Symbol to indicate if insert data is array */
251
+ const IsArray = Symbol("IsArray");
252
+ /**
253
+ * @class Select query builder.
254
+ */
255
+ var SelectQuery = class {
256
+ #table;
257
+ #readyPromise;
258
+ #dbGetter;
259
+ #whereCondition;
260
+ #orderByKey;
261
+ #orderByDir = "asc";
262
+ #limitCount;
263
+ constructor(table, dbGetter, readyPromise) {
264
+ this.#table = table;
265
+ this.#dbGetter = dbGetter;
266
+ this.#readyPromise = readyPromise;
267
+ }
268
+ /**
269
+ * @instance Select or exclude specific columns
270
+ * @param cols Columns to select or exclude
271
+ */
272
+ select(cols) {
273
+ this[Selected] = cols;
274
+ return this;
275
+ }
276
+ /**
277
+ * @instance Filter rows based on predicate function
278
+ * @param predicate Filtering function
279
+ */
280
+ where(predicate) {
281
+ this.#whereCondition = predicate;
282
+ return this;
283
+ }
284
+ /**
285
+ * @instance Order results by specified key and direction
286
+ * @param key Key to order by
287
+ * @param dir Direction: 'asc' | 'desc' (default: 'asc')
288
+ */
289
+ orderBy(key, dir = "asc") {
290
+ this.#orderByKey = key;
291
+ this.#orderByDir = dir;
292
+ return this;
293
+ }
294
+ /**
295
+ * @instance Limit number of results
296
+ * @param count Maximum number of results to return
297
+ */
298
+ limit(count) {
299
+ this.#limitCount = count;
300
+ return this;
301
+ }
302
+ /** Projects a row based on selected fields */
303
+ #projectRow(row) {
304
+ if (!isNotEmptyObject(this[Selected])) return row;
305
+ const projected = {};
306
+ const selectionEntries = Object.entries(this[Selected]);
307
+ const selectionKeys = new Set(Object.keys(this[Selected]));
308
+ if (selectionEntries.some(([, value]) => value === true)) {
309
+ for (const [key, value] of selectionEntries) if (value === true) projected[key] = row[key];
310
+ } else for (const key of Object.keys(row)) if (!selectionKeys.has(key) || this[Selected][key] !== false) projected[key] = row[key];
311
+ return projected;
312
+ }
313
+ async all() {
314
+ await this.#readyPromise;
315
+ return new Promise((resolve, reject) => {
316
+ const request = this.#dbGetter().transaction(this.#table, "readonly").objectStore(this.#table).getAll();
317
+ request.onsuccess = () => {
318
+ let results = request.result;
319
+ if (this.#whereCondition) results = results.filter(this.#whereCondition);
320
+ if (this.#orderByKey) results = sortAnArray(results, {
321
+ sortOrder: this.#orderByDir,
322
+ sortByField: this.#orderByKey
323
+ });
324
+ if (this.#limitCount) results = results.slice(0, this.#limitCount);
325
+ resolve(results.map((row) => this.#projectRow(row)));
326
+ };
327
+ request.onerror = () => reject(request.error);
328
+ });
329
+ }
330
+ async first() {
331
+ await this.#readyPromise;
332
+ return new Promise((resolve, reject) => {
333
+ const request = this.#dbGetter().transaction(this.#table, "readonly").objectStore(this.#table).getAll();
334
+ request.onsuccess = () => {
335
+ let results = request.result;
336
+ if (this.#whereCondition) results = results.filter(this.#whereCondition);
337
+ if (results.length > 0) resolve(this.#projectRow(results[0]));
338
+ else resolve(null);
339
+ };
340
+ request.onerror = () => reject(request.error);
341
+ });
342
+ }
343
+ };
344
+ /** @class Insert query builder. */
345
+ var InsertQuery = class {
346
+ #table;
347
+ #dbGetter;
348
+ #readyPromise;
349
+ #dataToInsert = [];
350
+ #columns;
351
+ constructor(table, dbGetter, readyPromise, columns) {
352
+ this.#table = table;
353
+ this.#dbGetter = dbGetter;
354
+ this.#readyPromise = readyPromise;
355
+ this.#columns = columns;
356
+ }
357
+ /**
358
+ * @instance Sets the data to be inserted
359
+ * @param data Data object or array of data objects to insert
360
+ */
361
+ values(data) {
362
+ this[IsArray] = Array.isArray(data);
363
+ this.#dataToInsert = this[IsArray] ? data : [data];
364
+ return this;
365
+ }
366
+ /**
367
+ * @instance Executes the insert query
368
+ * @returns Inserted record(s)
369
+ */
370
+ async run() {
371
+ await this.#readyPromise;
372
+ return new Promise((resolve, reject) => {
373
+ const transaction = this.#dbGetter().transaction(this.#table, "readwrite");
374
+ const store = transaction.objectStore(this.#table);
375
+ const insertedDocs = [];
376
+ const promises = this.#dataToInsert.map((data) => {
377
+ return new Promise((res, rej) => {
378
+ const updated = { ...data };
379
+ if (this.#columns) Object.entries(this.#columns).forEach(([fieldName, column]) => {
380
+ const defaultValue = column[DefaultValue];
381
+ if (!(fieldName in updated) && defaultValue !== void 0) updated[fieldName] = defaultValue;
382
+ const columnType = column[ColumnType];
383
+ if (columnType === "uuid" && !(fieldName in updated)) updated[fieldName] = uuidV4();
384
+ if (columnType === "timestamp" && !(fieldName in updated)) updated[fieldName] = getTimestamp();
385
+ });
386
+ const request = store.add(updated);
387
+ request.onsuccess = () => {
388
+ const key = request.result;
389
+ const getRequest = store.get(key);
390
+ getRequest.onsuccess = () => {
391
+ insertedDocs.push(getRequest.result);
392
+ res();
393
+ };
394
+ getRequest.onerror = () => rej(getRequest.error);
395
+ };
396
+ request.onerror = () => rej(request.error);
397
+ });
398
+ });
399
+ transaction.oncomplete = () => Promise.all(promises).then(() => this[IsArray] === true ? resolve(insertedDocs) : resolve(insertedDocs[0])).catch(reject);
400
+ transaction.onerror = () => reject(transaction.error);
401
+ });
402
+ }
403
+ };
404
+ /** @class Update query builder. */
405
+ var UpdateQuery = class {
406
+ #table;
407
+ #dbGetter;
408
+ #readyPromise;
409
+ #dataToUpdate;
410
+ #whereCondition;
411
+ constructor(table, dbGetter, readyPromise) {
412
+ this.#table = table;
413
+ this.#dbGetter = dbGetter;
414
+ this.#readyPromise = readyPromise;
415
+ }
416
+ /**
417
+ * @instance Sets the data to be updated
418
+ * @param values Values to update
419
+ */
420
+ set(values) {
421
+ this.#dataToUpdate = values;
422
+ return this;
423
+ }
424
+ /**
425
+ * @instance Filter rows to update
426
+ * @param predicate Filtering function
427
+ */
428
+ where(predicate) {
429
+ this.#whereCondition = predicate;
430
+ return this;
431
+ }
432
+ /**
433
+ * @instance Executes the update query
434
+ * @returns Number of records updated
435
+ */
436
+ async run() {
437
+ await this.#readyPromise;
438
+ if (!isNotEmptyObject(this.#dataToUpdate)) throw new Error("No values set for update!");
439
+ return new Promise((resolve, reject) => {
440
+ const store = this.#dbGetter().transaction(this.#table, "readwrite").objectStore(this.#table);
441
+ const request = store.getAll();
442
+ let updateCount = 0;
443
+ request.onsuccess = () => {
444
+ let rows = request.result;
445
+ if (this.#whereCondition) rows = rows.filter(this.#whereCondition);
446
+ const updatePromises = rows.map((row) => {
447
+ return new Promise((res, rej) => {
448
+ const updatedRow = {
449
+ ...row,
450
+ ...this.#dataToUpdate
451
+ };
452
+ const putRequest = store.put(updatedRow);
453
+ putRequest.onsuccess = () => {
454
+ updateCount++;
455
+ res();
456
+ };
457
+ putRequest.onerror = () => rej(putRequest.error);
458
+ });
459
+ });
460
+ Promise.all(updatePromises).then(() => resolve(updateCount)).catch(reject);
461
+ };
462
+ request.onerror = () => reject(request.error);
463
+ });
464
+ }
465
+ };
466
+ /** @class Delete query builder. */
467
+ var DeleteQuery = class {
468
+ #table;
469
+ #dbGetter;
470
+ #readyPromise;
471
+ #keyField;
472
+ #whereCondition;
473
+ constructor(table, dbGetter, readyPromise, keyField) {
474
+ this.#table = table;
475
+ this.#dbGetter = dbGetter;
476
+ this.#readyPromise = readyPromise;
477
+ this.#keyField = keyField;
478
+ }
479
+ /**
480
+ * @instance Filter rows to delete
481
+ * @param predicate Filtering function
482
+ */
483
+ where(predicate) {
484
+ this.#whereCondition = predicate;
485
+ return this;
486
+ }
487
+ /**
488
+ * @instance Executes the delete query
489
+ * @returns Number of records deleted
490
+ */
491
+ async run() {
492
+ await this.#readyPromise;
493
+ return new Promise((resolve, reject) => {
494
+ const store = this.#dbGetter().transaction(this.#table, "readwrite").objectStore(this.#table);
495
+ const request = store.getAll();
496
+ let deleteCount = 0;
497
+ request.onsuccess = () => {
498
+ let rows = request.result;
499
+ if (this.#whereCondition) rows = rows.filter(this.#whereCondition);
500
+ const deletePromises = rows.map((row) => {
501
+ return new Promise((res, rej) => {
502
+ const key = row[this.#keyField];
503
+ const delRequest = store.delete(key);
504
+ delRequest.onsuccess = () => {
505
+ deleteCount++;
506
+ res();
507
+ };
508
+ delRequest.onerror = () => rej(delRequest.error);
509
+ });
510
+ });
511
+ Promise.all(deletePromises).then(() => resolve(deleteCount)).catch(reject);
512
+ };
513
+ request.onerror = () => reject(request.error);
514
+ });
515
+ }
516
+ };
517
+
518
+ //#endregion
519
+ //#region src/client.ts
520
+ /**
521
+ * @class `Locality` class for {@link IndexedDB} interactions.
522
+ *
523
+ * @example
524
+ * import { column, defineSchema, Locality } from 'locality-idb';
525
+ *
526
+ * const schema = defineSchema({
527
+ * users: {
528
+ * id: column.int().pk().auto(),
529
+ * name: column.text(),
530
+ * email: column.text().unique(),
531
+ * },
532
+ * });
533
+ *
534
+ * const db = new Locality({
535
+ * dbName: 'my-database',
536
+ * version: 1,
537
+ * schema,
538
+ * });
539
+ *
540
+ * // Optional
541
+ * await db.ready();
542
+ *
543
+ * // Insert a new user
544
+ * const inserted = await db.insert('users').values({ name: 'Alice', email: 'alice@wonderland.mad' }).run();
545
+ *
546
+ * // Get all users
547
+ * const allUsers = await db.from('users').all();
548
+ *
549
+ * // Select users
550
+ * const allAlices = await db.from('users').where((user) => user.email.includes('alice')).all();
551
+ *
552
+ * // Update a user
553
+ * const updated = await db.update('users').set({ name: 'Alice Liddell' }).where((user) => user.id === 1).run();
554
+ *
555
+ * // Delete a user
556
+ * const deleted = await db.delete('users').where((user) => user.id === 1).run();
557
+ */
558
+ var Locality = class {
559
+ #name;
560
+ #schema;
561
+ #version;
562
+ #db;
563
+ #readyPromise;
564
+ constructor(config) {
565
+ this.#name = config.dbName;
566
+ this.#schema = config.schema;
567
+ this.#version = config.version;
568
+ const store = this.#buildStoresConfig();
569
+ this.#readyPromise = openDBWithStores(this.#name, store, this.#version).then((db) => {
570
+ this.#db = db;
571
+ });
572
+ }
573
+ /** Build store configurations from schema. */
574
+ #buildStoresConfig() {
575
+ return Object.entries(this.#schema).map(([tableName, table]) => {
576
+ const columns = table.columns;
577
+ const autoInc = Object.values(columns).find((col) => col[IsPrimaryKey])?.[IsAutoInc] || false;
578
+ return {
579
+ name: tableName,
580
+ keyPath: Object.entries(columns).find(([_, col]) => col[IsPrimaryKey])?.[0],
581
+ autoIncrement: autoInc
582
+ };
583
+ });
584
+ }
585
+ /** @instance Waits for database initialization to complete. */
586
+ async ready() {
587
+ return this.#readyPromise;
588
+ }
589
+ /**
590
+ * @instance Select records from a table.
591
+ * @param table Table name.
592
+ * @returns
593
+ */
594
+ from(table) {
595
+ return new SelectQuery(table, () => this.#db, this.#readyPromise);
596
+ }
597
+ /**
598
+ * @instance Insert records into a table.
599
+ * @param table Table name.
600
+ */
601
+ insert(table) {
602
+ return new InsertQuery(table, () => this.#db, this.#readyPromise, this.#schema[table].columns);
603
+ }
604
+ /**
605
+ * @instance Update records in a table.
606
+ * @param table Table name.
607
+ */
608
+ update(table) {
609
+ return new UpdateQuery(table, () => this.#db, this.#readyPromise);
610
+ }
611
+ /**
612
+ * @instance Delete records from a table.
613
+ * @param table Table name.
614
+ */
615
+ delete(table) {
616
+ const columns = this.#schema[table].columns;
617
+ const keyField = Object.entries(columns).find(([_, col]) => col[IsPrimaryKey])?.[0];
618
+ return new DeleteQuery(table, () => this.#db, this.#readyPromise, keyField);
619
+ }
620
+ };
621
+
622
+ //#endregion
623
+ //#region src/schema.ts
624
+ /**
625
+ * * Defines a database schema from a given schema definition.
626
+ * @param schema An object defining the schema, where each key is a table name and each value is a record of {@link column} definitions.
627
+ * @returns An object mapping each table name to its corresponding {@link Table} instance.
628
+ *
629
+ * @example
630
+ * const schema = defineSchema({
631
+ * users: {
632
+ * id: column.int().pk().auto(),
633
+ * name: column.varchar(255).unique(),
634
+ * createdAt: column.timestamp(),
635
+ * isActive: column.bool().default(true),
636
+ * },
637
+ * posts: {
638
+ * id: column.int().pk().auto(),
639
+ * userId: column.int().index(),
640
+ * title: column.varchar(255),
641
+ * content: column.text(),
642
+ * createdAt: column.timestamp(),
643
+ * },
644
+ * });
645
+ *
646
+ * // Infer types:
647
+ *
648
+ * type User = InferSelectType<typeof schema.users>;
649
+ * type InsertUser = InferInsertType<typeof schema.users>;
650
+ * type UpdateUser = InferUpdateType<typeof schema.users>;
651
+ *
652
+ * type Post = InferSelectType<typeof schema.posts>;
653
+ * type InsertPost = InferInsertType<typeof schema.posts>;
654
+ * type UpdatePost = InferUpdateType<typeof schema.posts>;
655
+ */
656
+ function defineSchema(schema) {
657
+ const result = {};
658
+ for (const [tableName, columns] of Object.entries(schema)) result[tableName] = new Table(tableName, columns);
659
+ return result;
660
+ }
661
+ /**
662
+ * * Factory function to create a new {@link Table} instance.
663
+ * @param name The name of the table.
664
+ * @param columns An object defining the columns of the table using {@link column} definitions.
665
+ * @returns A new {@link Table} instance representing the table schema.
666
+ *
667
+ * @example
668
+ * const userTable = table('users', {
669
+ * id: column.int().pk().auto(),
670
+ * name: column.varchar(255).unique(),
671
+ * createdAt: column.timestamp(),
672
+ * isActive: column.bool().default(true),
673
+ * });
674
+ */
675
+ function table(name, columns) {
676
+ return new Table(name, columns);
677
+ }
678
+ /**
679
+ * * Column factory with various column types.
680
+ *
681
+ * @remarks
682
+ * - `char` and `varchar` accept an optional length parameter.
683
+ * - `object`, `array`, `list`, `tuple`, `set`, and `map` are generic and can be typed.
684
+ * - `custom` can be used for any custom data type.
685
+ * - Each column can be further configured using methods like `pk()`, `unique()`, `auto()`, `index()`, `default()`, and `optional()`.
686
+ * - Example usage is provided below:
687
+ *
688
+ * @example
689
+ * const idColumn = column.int().pk().auto();
690
+ * const nameColumn = column.varchar(255).unique();
691
+ * const createdAtColumn = column.timestamp();
692
+ * const isActiveColumn = column.bool().default(true);
693
+ *
694
+ * // Define a table schema
695
+ * const userTable = table('users', {
696
+ * id: idColumn,
697
+ * name: nameColumn,
698
+ * createdAt: createdAtColumn,
699
+ * isActive: isActiveColumn,
700
+ * });
701
+ *
702
+ * // Define a database schema
703
+ * const schema = defineSchema({
704
+ * users: {
705
+ * id: idColumn,
706
+ * name: nameColumn,
707
+ * createdAt: createdAtColumn,
708
+ * isActive: isActiveColumn,
709
+ * },
710
+ * });
711
+ */
712
+ const column = {
713
+ int: () => new Column("int"),
714
+ float: () => new Column("float"),
715
+ number: () => new Column("number"),
716
+ bigint: () => new Column("bigint"),
717
+ text: () => new Column("text"),
718
+ string: () => new Column("string"),
719
+ char: (l) => new Column(`char(${isPositiveInteger(l) ? l : "0"})`),
720
+ varchar: (l) => new Column(`varchar(${isPositiveInteger(l) ? l : "0"})`),
721
+ uuid: () => new Column("uuid"),
722
+ timestamp: () => new Column("timestamp"),
723
+ bool: () => new Column("bool"),
724
+ date: () => new Column("date"),
725
+ object: () => new Column(`object`),
726
+ array: () => new Column(`array`),
727
+ list: () => new Column(`list`),
728
+ tuple: () => new Column(`tuple`),
729
+ set: () => new Column(`set`),
730
+ map: () => new Column(`map`),
731
+ custom: () => new Column(`custom`)
732
+ };
733
+
734
+ //#endregion
735
+ exports.Locality = Locality;
736
+ exports.column = column;
737
+ exports.defineSchema = defineSchema;
738
+ exports.getTimestamp = getTimestamp;
739
+ exports.openDBWithStores = openDBWithStores;
740
+ exports.table = table;
741
+ exports.uuidV4 = uuidV4;