@type32/tauri-sqlite-orm 0.2.15 → 0.4.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.
Files changed (43) hide show
  1. package/dist/aggregates.d.ts +12 -0
  2. package/dist/aggregates.js +9 -0
  3. package/dist/builders/delete.d.ts +23 -0
  4. package/dist/builders/delete.js +73 -0
  5. package/dist/builders/index.d.ts +7 -0
  6. package/dist/builders/index.js +7 -0
  7. package/dist/builders/insert.d.ts +31 -0
  8. package/dist/builders/insert.js +141 -0
  9. package/dist/builders/query-base.d.ts +1 -0
  10. package/dist/builders/query-base.js +1 -0
  11. package/dist/builders/relations.d.ts +11 -0
  12. package/dist/builders/relations.js +1 -0
  13. package/dist/builders/select.d.ts +54 -0
  14. package/dist/builders/select.js +427 -0
  15. package/dist/builders/update.d.ts +30 -0
  16. package/dist/builders/update.js +124 -0
  17. package/dist/builders/with.d.ts +17 -0
  18. package/dist/builders/with.js +34 -0
  19. package/dist/column-helpers.d.ts +22 -0
  20. package/dist/column-helpers.js +17 -0
  21. package/dist/dialect.d.ts +21 -0
  22. package/dist/dialect.js +67 -0
  23. package/dist/errors.d.ts +30 -0
  24. package/dist/errors.js +66 -0
  25. package/dist/index.d.mts +150 -4
  26. package/dist/index.d.ts +150 -4
  27. package/dist/index.js +270 -108
  28. package/dist/index.mjs +263 -104
  29. package/dist/operators.d.ts +30 -0
  30. package/dist/operators.js +84 -0
  31. package/dist/orm.d.ts +180 -0
  32. package/dist/orm.js +556 -0
  33. package/dist/relational-types.d.ts +87 -0
  34. package/dist/relational-types.js +1 -0
  35. package/dist/relations-v2.d.ts +77 -0
  36. package/dist/relations-v2.js +157 -0
  37. package/dist/serialization.d.ts +14 -0
  38. package/dist/serialization.js +135 -0
  39. package/dist/subquery.d.ts +5 -0
  40. package/dist/subquery.js +6 -0
  41. package/dist/types.d.ts +58 -0
  42. package/dist/types.js +1 -0
  43. package/package.json +2 -1
package/dist/orm.js ADDED
@@ -0,0 +1,556 @@
1
+ import { Kysely, sql as kyselySql } from 'kysely';
2
+ import { TauriDialect } from './dialect';
3
+ import { SelectQueryBuilder, InsertQueryBuilder, UpdateQueryBuilder, DeleteQueryBuilder, WithQueryBuilder, } from './builders';
4
+ // Column class
5
+ export class SQLiteColumn {
6
+ type;
7
+ options;
8
+ _;
9
+ constructor(name, type, options = {}) {
10
+ this.type = type;
11
+ this.options = options;
12
+ this._ = {
13
+ name,
14
+ dataType: type,
15
+ mode: (options.mode || 'default'),
16
+ notNull: (options.notNull ?? false),
17
+ hasDefault: (options.default !== undefined || options.$defaultFn !== undefined),
18
+ autoincrement: (options.autoincrement ?? false),
19
+ enum: options.enum,
20
+ customType: undefined,
21
+ };
22
+ }
23
+ notNull() {
24
+ return new SQLiteColumn(this._.name, this.type, { ...this.options, notNull: true, mode: this._.mode });
25
+ }
26
+ default(value) {
27
+ return new SQLiteColumn(this._.name, this.type, { ...this.options, default: value, mode: this._.mode });
28
+ }
29
+ $defaultFn(fn) {
30
+ return new SQLiteColumn(this._.name, this.type, { ...this.options, $defaultFn: fn, mode: this._.mode });
31
+ }
32
+ primaryKey() {
33
+ return new SQLiteColumn(this._.name, this.type, { ...this.options, primaryKey: true, notNull: true, mode: this._.mode });
34
+ }
35
+ autoincrement() {
36
+ return new SQLiteColumn(this._.name, this.type, { ...this.options, autoincrement: true, mode: this._.mode });
37
+ }
38
+ unique() {
39
+ return new SQLiteColumn(this._.name, this.type, { ...this.options, unique: true, mode: this._.mode });
40
+ }
41
+ references(ref, column, options) {
42
+ const columnKey = typeof column === 'string' ? column : column._.name;
43
+ const columnObj = typeof column === 'string' ? ref._.columns[column] : column;
44
+ return new SQLiteColumn(this._.name, this.type, {
45
+ ...this.options,
46
+ references: {
47
+ table: ref,
48
+ column: columnObj,
49
+ onDelete: options?.onDelete,
50
+ onUpdate: options?.onUpdate,
51
+ },
52
+ mode: this._.mode
53
+ });
54
+ }
55
+ $onUpdateFn(fn) {
56
+ return new SQLiteColumn(this._.name, this.type, { ...this.options, $onUpdateFn: fn, mode: this._.mode });
57
+ }
58
+ $type() {
59
+ return this;
60
+ }
61
+ as(alias) {
62
+ // This is a placeholder for alias functionality
63
+ return this;
64
+ }
65
+ }
66
+ export class Table {
67
+ _;
68
+ relations = {};
69
+ constructor(name, columns) {
70
+ this._ = {
71
+ name,
72
+ columns,
73
+ };
74
+ }
75
+ }
76
+ export const sqliteTable = (tableName, columns) => {
77
+ return new Table(tableName, columns);
78
+ };
79
+ export const asc = (column) => kyselySql `${kyselySql.ref(column._.name)} ASC`;
80
+ export const desc = (column) => kyselySql `${kyselySql.ref(column._.name)} DESC`;
81
+ // Main ORM Class
82
+ export class TauriORM {
83
+ db;
84
+ tables = new Map();
85
+ kysely;
86
+ constructor(db, schema = undefined) {
87
+ this.db = db;
88
+ this.kysely = new Kysely({ dialect: new TauriDialect(db) });
89
+ if (schema) {
90
+ for (const [, value] of Object.entries(schema)) {
91
+ if (value instanceof Table) {
92
+ this.tables.set(value._.name, value);
93
+ }
94
+ }
95
+ }
96
+ }
97
+ buildColumnDefinition(col, forAlterTable = false) {
98
+ let sql = `${col._.name} ${col.type}`;
99
+ if (col.options.primaryKey && !forAlterTable) {
100
+ sql += ' PRIMARY KEY';
101
+ if (col._.autoincrement) {
102
+ sql += ' AUTOINCREMENT';
103
+ }
104
+ }
105
+ if (col._.notNull)
106
+ sql += ' NOT NULL';
107
+ if (col.options.unique)
108
+ sql += ' UNIQUE';
109
+ if (col.options.default !== undefined) {
110
+ const value = col.options.default;
111
+ sql += ` DEFAULT ${typeof value === 'string' ? `'${value.replace(/'/g, "''")}'` : value}`;
112
+ }
113
+ if (col.options.references) {
114
+ sql += ` REFERENCES ${col.options.references.table._.name}(${col.options.references.column._.name})`;
115
+ if (col.options.references.onDelete) {
116
+ sql += ` ON DELETE ${col.options.references.onDelete.toUpperCase()}`;
117
+ }
118
+ if (col.options.references.onUpdate) {
119
+ sql += ` ON UPDATE ${col.options.references.onUpdate.toUpperCase()}`;
120
+ }
121
+ }
122
+ return sql;
123
+ }
124
+ async checkMigration() {
125
+ const dbTables = await this.db.select(`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'`);
126
+ const dbTableNames = new Set(dbTables.map((t) => t.name));
127
+ const schemaTableNames = new Set(Array.from(this.tables.keys()));
128
+ const changes = {
129
+ tablesToCreate: [],
130
+ tablesToRecreate: [],
131
+ tablesToDrop: [],
132
+ columnsToAdd: [],
133
+ };
134
+ // Check tables to create
135
+ for (const tableName of schemaTableNames) {
136
+ if (!dbTableNames.has(tableName)) {
137
+ changes.tablesToCreate.push(tableName);
138
+ }
139
+ }
140
+ // Check tables to drop
141
+ for (const tableName of dbTableNames) {
142
+ if (!schemaTableNames.has(tableName)) {
143
+ changes.tablesToDrop.push(tableName);
144
+ }
145
+ }
146
+ // Check for table recreation and column additions
147
+ for (const table of this.tables.values()) {
148
+ const tableName = table._.name;
149
+ if (!dbTableNames.has(tableName))
150
+ continue;
151
+ const existingTableInfo = await this.db.select(`PRAGMA table_info('${tableName}')`);
152
+ const existingIndexes = await this.db.select(`PRAGMA index_list('${tableName}')`);
153
+ const uniqueColumns = new Set();
154
+ for (const index of existingIndexes) {
155
+ if (index.unique === 1 && index.origin === 'u') {
156
+ const indexInfo = await this.db.select(`PRAGMA index_info('${index.name}')`);
157
+ if (indexInfo.length === 1) {
158
+ uniqueColumns.add(indexInfo[0].name);
159
+ }
160
+ }
161
+ }
162
+ const existingColumns = new Map(existingTableInfo.map(c => [c.name, c]));
163
+ const schemaColumns = table._.columns;
164
+ let needsRecreate = false;
165
+ for (const [colName, column] of Object.entries(schemaColumns)) {
166
+ const dbColName = column._.name;
167
+ const existing = existingColumns.get(dbColName);
168
+ if (!existing) {
169
+ if (!this.canAddColumnWithAlter(column)) {
170
+ needsRecreate = true;
171
+ break;
172
+ }
173
+ changes.columnsToAdd.push({ table: tableName, column: dbColName });
174
+ }
175
+ else {
176
+ const hasUniqueInDB = uniqueColumns.has(dbColName);
177
+ const wantsUnique = !!column.options.unique;
178
+ if (hasUniqueInDB !== wantsUnique || this.hasColumnDefinitionChanged(column, existing)) {
179
+ needsRecreate = true;
180
+ break;
181
+ }
182
+ }
183
+ }
184
+ // Check for removed columns (existingCol is DB name)
185
+ for (const existingCol of existingColumns.keys()) {
186
+ const schemaHasCol = Object.values(schemaColumns).some((c) => c._.name === existingCol);
187
+ if (!schemaHasCol) {
188
+ needsRecreate = true;
189
+ break;
190
+ }
191
+ }
192
+ if (needsRecreate) {
193
+ changes.tablesToRecreate.push(tableName);
194
+ }
195
+ }
196
+ const safe = changes.tablesToRecreate.length === 0 && changes.tablesToDrop.length === 0;
197
+ return { safe, changes };
198
+ }
199
+ async migrate(options) {
200
+ if (options?.dryRun) {
201
+ const check = await this.checkMigration();
202
+ console.log('[Tauri-ORM] Migration Preview (Dry Run):');
203
+ console.log(' Tables to create:', check.changes.tablesToCreate);
204
+ console.log(' Tables to recreate (DESTRUCTIVE):', check.changes.tablesToRecreate);
205
+ console.log(' Tables to drop:', check.changes.tablesToDrop);
206
+ console.log(' Columns to add:', check.changes.columnsToAdd);
207
+ console.log(' Safe migration:', check.safe);
208
+ return;
209
+ }
210
+ const dbTables = await this.db.select(`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'`);
211
+ const dbTableNames = new Set(dbTables.map((t) => t.name));
212
+ const schemaTableNames = new Set(Array.from(this.tables.keys()));
213
+ // Create/update tables
214
+ for (const table of this.tables.values()) {
215
+ const tableName = table._.name;
216
+ const tableExists = dbTableNames.has(tableName);
217
+ if (!tableExists) {
218
+ // Table does not exist, create it
219
+ const columnsSql = Object.values(table._.columns)
220
+ .map((col) => this.buildColumnDefinition(col))
221
+ .join(', ');
222
+ const createSql = `CREATE TABLE ${tableName} (${columnsSql})`;
223
+ await this.db.execute(createSql);
224
+ }
225
+ else {
226
+ // Table exists, check for schema changes
227
+ const existingTableInfo = await this.db.select(`PRAGMA table_info('${tableName}')`);
228
+ // Get existing UNIQUE constraints from indexes
229
+ const existingIndexes = await this.db.select(`PRAGMA index_list('${tableName}')`);
230
+ const uniqueColumns = new Set();
231
+ for (const index of existingIndexes) {
232
+ if (index.unique === 1 && index.origin === 'u') {
233
+ const indexInfo = await this.db.select(`PRAGMA index_info('${index.name}')`);
234
+ if (indexInfo.length === 1) {
235
+ uniqueColumns.add(indexInfo[0].name);
236
+ }
237
+ }
238
+ }
239
+ const existingColumns = new Map(existingTableInfo.map(c => [c.name, c]));
240
+ const schemaColumns = table._.columns;
241
+ // Check if we need to recreate the table (column definition changes)
242
+ let needsRecreate = false;
243
+ const columnsToAdd = [];
244
+ for (const [colName, column] of Object.entries(schemaColumns)) {
245
+ const dbColName = column._.name;
246
+ const existing = existingColumns.get(dbColName);
247
+ if (!existing) {
248
+ // New column - check if it can be added with ALTER TABLE
249
+ if (this.canAddColumnWithAlter(column)) {
250
+ columnsToAdd.push(column);
251
+ }
252
+ else {
253
+ needsRecreate = true;
254
+ break;
255
+ }
256
+ }
257
+ else {
258
+ // Existing column - check if definition changed
259
+ const hasUniqueInDB = uniqueColumns.has(dbColName);
260
+ const wantsUnique = !!column.options.unique;
261
+ if (hasUniqueInDB !== wantsUnique) {
262
+ needsRecreate = true;
263
+ break;
264
+ }
265
+ if (this.hasColumnDefinitionChanged(column, existing)) {
266
+ needsRecreate = true;
267
+ break;
268
+ }
269
+ }
270
+ }
271
+ // Check for removed columns (existingCol is DB name)
272
+ if (options?.performDestructiveActions) {
273
+ for (const existingCol of existingColumns.keys()) {
274
+ const schemaHasCol = Object.values(schemaColumns).some((c) => c._.name === existingCol);
275
+ if (!schemaHasCol) {
276
+ needsRecreate = true;
277
+ break;
278
+ }
279
+ }
280
+ }
281
+ if (needsRecreate) {
282
+ // Recreate table with new schema
283
+ await this.recreateTable(tableName, table);
284
+ }
285
+ else if (columnsToAdd.length > 0) {
286
+ // Just add new columns with ALTER TABLE
287
+ for (const column of columnsToAdd) {
288
+ const columnSql = this.buildColumnDefinition(column, true);
289
+ await this.db.execute(`ALTER TABLE ${tableName} ADD COLUMN ${columnSql}`);
290
+ }
291
+ }
292
+ }
293
+ }
294
+ // Drop extra tables if destructive actions are enabled
295
+ if (options?.performDestructiveActions) {
296
+ for (const tableName of dbTableNames) {
297
+ if (!schemaTableNames.has(tableName)) {
298
+ await this.dropTable(tableName);
299
+ }
300
+ }
301
+ }
302
+ }
303
+ canAddColumnWithAlter(column) {
304
+ // SQLite ALTER TABLE ADD COLUMN has limitations:
305
+ // - Cannot add PRIMARY KEY
306
+ // - Cannot add UNIQUE (without using a workaround)
307
+ // - Can add NOT NULL only if column has a DEFAULT value
308
+ if (column.options.primaryKey)
309
+ return false;
310
+ if (column.options.unique)
311
+ return false;
312
+ if (column._.notNull && column.options.default === undefined && !column.options.$defaultFn)
313
+ return false;
314
+ return true;
315
+ }
316
+ hasColumnDefinitionChanged(column, existing) {
317
+ // Check if column type changed (normalize to uppercase)
318
+ if (column.type.toUpperCase() !== existing.type.toUpperCase())
319
+ return true;
320
+ // Check if NOT NULL changed
321
+ if (column._.notNull !== (existing.notnull === 1))
322
+ return true;
323
+ // Check if PRIMARY KEY changed
324
+ if (!!column.options.primaryKey !== (existing.pk === 1))
325
+ return true;
326
+ // Check if default value changed
327
+ const hasDefault = column.options.default !== undefined;
328
+ const existingHasDefault = existing.dflt_value !== null;
329
+ if (hasDefault !== existingHasDefault)
330
+ return true;
331
+ // Check UNIQUE constraint (requires checking indexes)
332
+ // For now, we'll check UNIQUE separately if needed
333
+ return false;
334
+ }
335
+ async recreateTable(tableName, table) {
336
+ const tempTableName = `${tableName}_new_${Date.now()}`;
337
+ // Create new table with updated schema
338
+ const columnsSql = Object.values(table._.columns)
339
+ .map((col) => this.buildColumnDefinition(col))
340
+ .join(', ');
341
+ await this.db.execute(`CREATE TABLE ${tempTableName} (${columnsSql})`);
342
+ // Copy data from old table (only columns that exist in both)
343
+ const oldColumns = await this.db.select(`PRAGMA table_info('${tableName}')`);
344
+ const oldColumnNames = oldColumns.map(c => c.name);
345
+ const newColumnNames = Object.values(table._.columns).map(c => c._.name);
346
+ const commonColumns = oldColumnNames.filter(name => newColumnNames.includes(name));
347
+ if (commonColumns.length > 0) {
348
+ const columnsList = commonColumns.join(', ');
349
+ await this.db.execute(`INSERT INTO ${tempTableName} (${columnsList}) SELECT ${columnsList} FROM ${tableName}`);
350
+ }
351
+ // Drop old table and rename new table
352
+ await this.db.execute(`DROP TABLE ${tableName}`);
353
+ await this.db.execute(`ALTER TABLE ${tempTableName} RENAME TO ${tableName}`);
354
+ }
355
+ select(table, columns) {
356
+ const internalTable = this.tables.get(table._.name);
357
+ if (!internalTable) {
358
+ console.warn(`[Tauri-ORM] Table "${table._.name}" was not passed in the schema to the ORM constructor. Relations will not be available.`);
359
+ return new SelectQueryBuilder(this.kysely, table, columns);
360
+ }
361
+ return new SelectQueryBuilder(this.kysely, internalTable, columns);
362
+ }
363
+ insert(table) {
364
+ return new InsertQueryBuilder(this.kysely, table);
365
+ }
366
+ update(table) {
367
+ return new UpdateQueryBuilder(this.kysely, table);
368
+ }
369
+ delete(table) {
370
+ return new DeleteQueryBuilder(this.kysely, table);
371
+ }
372
+ async upsert(table, data, conflictTarget) {
373
+ const columns = conflictTarget.map(col => table._.columns[col]);
374
+ return this.insert(table)
375
+ .values(data)
376
+ .onConflictDoUpdate({
377
+ target: columns.length === 1 ? columns[0] : columns,
378
+ set: data
379
+ })
380
+ .execute();
381
+ }
382
+ $with(alias) {
383
+ const withBuilder = new WithQueryBuilder(this.kysely);
384
+ return {
385
+ as: (query) => {
386
+ withBuilder.with(alias, query);
387
+ return withBuilder;
388
+ },
389
+ };
390
+ }
391
+ async transaction(callback) {
392
+ await this.db.execute('BEGIN');
393
+ try {
394
+ const result = await callback(this);
395
+ await this.db.execute('COMMIT');
396
+ return result;
397
+ }
398
+ catch (e) {
399
+ await this.db.execute('ROLLBACK');
400
+ throw e;
401
+ }
402
+ }
403
+ rollback() {
404
+ throw new Error('Transaction rolled back');
405
+ }
406
+ // --- Schema detection / signature ---
407
+ async ensureSchemaMeta() {
408
+ await this.db.execute(`CREATE TABLE IF NOT EXISTS _schema_meta
409
+ (
410
+ key
411
+ TEXT
412
+ PRIMARY
413
+ KEY,
414
+ value
415
+ TEXT
416
+ NOT
417
+ NULL
418
+ )`);
419
+ }
420
+ async getSchemaMeta(key) {
421
+ await this.ensureSchemaMeta();
422
+ const rows = await this.db.select(`SELECT value
423
+ FROM _schema_meta
424
+ WHERE key = ?`, [key]);
425
+ return rows?.[0]?.value ?? null;
426
+ }
427
+ async setSchemaMeta(key, value) {
428
+ await this.ensureSchemaMeta();
429
+ await this.db.execute(`INSERT INTO _schema_meta(key, value)
430
+ VALUES (?, ?) ON CONFLICT(key) DO
431
+ UPDATE
432
+ SET value = excluded.value`, [key, value]);
433
+ }
434
+ normalizeColumn(col) {
435
+ return {
436
+ name: col._.name,
437
+ type: col.type,
438
+ pk: !!col.options.primaryKey,
439
+ ai: !!col._.autoincrement,
440
+ nn: !!col._.notNull,
441
+ unique: !!col.options.unique,
442
+ dv: col.options.default && typeof col.options.default === 'object' && col.options.default.raw
443
+ ? { raw: col.options.default.raw }
444
+ : col.options.default ?? null,
445
+ hasDefaultFn: col.options.$defaultFn !== undefined,
446
+ hasOnUpdateFn: col.options.$onUpdateFn !== undefined,
447
+ onDelete: col.options.references?.onDelete ?? null,
448
+ onUpdate: col.options.references?.onUpdate ?? null,
449
+ };
450
+ }
451
+ computeModelSignature() {
452
+ const entries = Array.from(this.tables.values()).map((tbl) => {
453
+ const cols = Object.values(tbl._.columns)
454
+ .map((c) => this.normalizeColumn(c))
455
+ .sort((a, b) => a.name.localeCompare(b.name));
456
+ return { table: tbl._.name, columns: cols };
457
+ });
458
+ entries.sort((a, b) => a.table.localeCompare(b.table));
459
+ return JSON.stringify(entries);
460
+ }
461
+ getSchemaSignature() {
462
+ return this.computeModelSignature();
463
+ }
464
+ async isSchemaDirty() {
465
+ const sig = this.computeModelSignature();
466
+ const stored = await this.getSchemaMeta('schema_signature');
467
+ return { dirty: sig !== stored, current: sig, stored };
468
+ }
469
+ async migrateIfDirty() {
470
+ const status = await this.isSchemaDirty();
471
+ if (status.dirty) {
472
+ await this.migrate();
473
+ await this.setSchemaMeta('schema_signature', this.computeModelSignature());
474
+ return true;
475
+ }
476
+ return false;
477
+ }
478
+ async doesTableExist(tableName) {
479
+ const result = await this.db.select(`SELECT name FROM sqlite_master WHERE type='table' AND name=?`, [tableName]);
480
+ return result.length > 0;
481
+ }
482
+ async dropTable(tableName) {
483
+ await this.db.execute(`DROP TABLE IF EXISTS ${tableName}`);
484
+ }
485
+ async doesColumnExist(tableName, columnName) {
486
+ const result = await this.db.select(`PRAGMA table_info('${tableName}')`);
487
+ return result.some((col) => col.name === columnName);
488
+ }
489
+ async renameTable(from, to) {
490
+ await this.db.execute(`ALTER TABLE ${from} RENAME TO ${to}`);
491
+ }
492
+ async dropColumn(tableName, columnName) {
493
+ await this.db.execute(`ALTER TABLE ${tableName} DROP COLUMN ${columnName}`);
494
+ }
495
+ async renameColumn(tableName, from, to) {
496
+ await this.db.execute(`ALTER TABLE ${tableName} RENAME COLUMN ${from} TO ${to}`);
497
+ }
498
+ }
499
+ // Relations
500
+ export class Relation {
501
+ foreignTable;
502
+ constructor(foreignTable) {
503
+ this.foreignTable = foreignTable;
504
+ }
505
+ }
506
+ export class OneRelation extends Relation {
507
+ config;
508
+ constructor(foreignTable, config) {
509
+ super(foreignTable);
510
+ this.config = config;
511
+ }
512
+ }
513
+ export class ManyRelation extends Relation {
514
+ config;
515
+ constructor(foreignTable, config) {
516
+ super(foreignTable);
517
+ this.config = config;
518
+ }
519
+ }
520
+ export const relations = (table, relationsCallback) => {
521
+ const builtRelations = relationsCallback({
522
+ one: (foreignTable, config) => {
523
+ return new OneRelation(foreignTable, config);
524
+ },
525
+ many: (foreignTable) => {
526
+ return new ManyRelation(foreignTable);
527
+ },
528
+ });
529
+ for (const [name, relation] of Object.entries(builtRelations)) {
530
+ if (relation instanceof OneRelation) {
531
+ table.relations[name] = {
532
+ type: 'one',
533
+ foreignTable: relation.foreignTable,
534
+ fields: relation.config?.fields,
535
+ references: relation.config?.references,
536
+ optional: relation.config?.optional,
537
+ alias: relation.config?.alias,
538
+ };
539
+ }
540
+ else if (relation instanceof ManyRelation) {
541
+ table.relations[name] = {
542
+ type: 'many',
543
+ foreignTable: relation.foreignTable,
544
+ };
545
+ }
546
+ }
547
+ return builtRelations;
548
+ };
549
+ // Helper functions
550
+ export const getTableColumns = (table) => {
551
+ return table._.columns;
552
+ };
553
+ export const alias = (table, alias) => {
554
+ // This is a placeholder for alias functionality
555
+ return table;
556
+ };
@@ -0,0 +1,87 @@
1
+ import type { AnyTable, InferSelectModel } from './types';
2
+ import type { OneRelation, ManyRelation } from './orm';
3
+ /** Maps relations() return type to typed relation configs with foreign table preserved */
4
+ export type InferRelationsMap<R extends Record<string, OneRelation | ManyRelation>> = {
5
+ [K in keyof R]: R[K] extends OneRelation<infer T> ? R[K] extends {
6
+ config?: {
7
+ optional?: infer O;
8
+ };
9
+ } ? {
10
+ type: 'one';
11
+ foreignTable: T;
12
+ optional: O;
13
+ } : {
14
+ type: 'one';
15
+ foreignTable: T;
16
+ optional?: true;
17
+ } : R[K] extends ManyRelation<infer T> ? {
18
+ type: 'many';
19
+ foreignTable: T;
20
+ } : never;
21
+ };
22
+ /** With/include object shape - matches NestedInclude from select builder */
23
+ type WithShape = boolean | {
24
+ columns?: string[] | Record<string, boolean>;
25
+ with?: Record<string, WithShape>;
26
+ };
27
+ /** Map of table name -> relations() return type, for nested with support */
28
+ type RelationsByTable = Record<string, Record<string, OneRelation | ManyRelation>>;
29
+ /** Get relations for a table from the all-relations map */
30
+ type GetRelationsForTable<TTable extends AnyTable, TAllRelations extends RelationsByTable> = TAllRelations[TTable['_']['name']] extends Record<string, OneRelation | ManyRelation> ? InferRelationsMap<TAllRelations[TTable['_']['name']]> : Record<string, never>;
31
+ /** Infer nested relation fields when TWith has a nested `with` */
32
+ type InferNestedFields<TForeignTable extends AnyTable, TWith extends WithShape, TAllRelations extends RelationsByTable> = TWith extends {
33
+ with?: infer TW;
34
+ } ? TW extends Record<string, WithShape> ? InferRelationFields<TForeignTable, GetRelationsForTable<TForeignTable, TAllRelations>, TAllRelations, TW> : unknown : unknown;
35
+ /** Recursively build relation fields from a with object */
36
+ type InferRelationFields<TTable extends AnyTable, TRelationsMap extends Record<string, {
37
+ type: 'one' | 'many';
38
+ foreignTable: AnyTable;
39
+ }>, TAllRelations extends RelationsByTable, TWith extends Record<string, WithShape>> = {
40
+ [K in keyof TWith & keyof TRelationsMap]: TWith[K] extends false | undefined ? never : TRelationsMap[K] extends {
41
+ type: 'one';
42
+ foreignTable: infer T;
43
+ optional?: infer O;
44
+ } ? T extends AnyTable ? ([O] extends [false] ? InferSelectModel<T> & InferNestedFields<T, TWith[K], TAllRelations> : (InferSelectModel<T> & InferNestedFields<T, TWith[K], TAllRelations>) | null) : never : TRelationsMap[K] extends {
45
+ type: 'many';
46
+ foreignTable: infer T;
47
+ } ? T extends AnyTable ? (InferSelectModel<T> & InferNestedFields<T, TWith[K], TAllRelations>)[] : never : never;
48
+ };
49
+ /**
50
+ * Infer the result type of a select query that includes relations via `.include(with)`.
51
+ *
52
+ * Use this to type variables that hold results from relational queries, e.g.:
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * export type User = InferSelectModel<typeof schema.user>
57
+ *
58
+ * const withRelationalObject = { sessions: true, accounts: true } as const
59
+ * export type UserWithRelations = InferRelationalSelectModel<
60
+ * typeof schema.user,
61
+ * typeof schema.userRelations,
62
+ * typeof withRelationalObject
63
+ * >
64
+ * ```
65
+ *
66
+ * For nested includes, pass a map of table name -> relations as the fourth parameter:
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * const withNested = { sessions: { with: { user: true } } } as const
71
+ * type UserWithSessionsAndUser = InferRelationalSelectModel<
72
+ * typeof schema.user,
73
+ * typeof schema.userRelations,
74
+ * typeof withNested,
75
+ * { user: typeof userRelations; session: typeof sessionRelations }
76
+ * >
77
+ * ```
78
+ *
79
+ * @param TTable - The table type (e.g. typeof schema.user)
80
+ * @param TRelations - The relations for this table (e.g. typeof schema.userRelations)
81
+ * @param TWith - The with/include object shape (use `as const` for literal inference)
82
+ * @param TAllRelations - Optional map of table name -> relations for nested `with` support
83
+ */
84
+ export type InferRelationalSelectModel<TTable extends AnyTable, TRelations extends Record<string, OneRelation | ManyRelation>, TWith extends Record<string, WithShape>, TAllRelations extends RelationsByTable = {
85
+ [K in TTable['_']['name']]: TRelations;
86
+ }> = InferSelectModel<TTable> & InferRelationFields<TTable, InferRelationsMap<TRelations>, TAllRelations, TWith>;
87
+ export {};
@@ -0,0 +1 @@
1
+ export {};