linkgress-orm 0.0.1

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 (147) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +196 -0
  3. package/dist/database/database-client.interface.d.ts +45 -0
  4. package/dist/database/database-client.interface.d.ts.map +1 -0
  5. package/dist/database/database-client.interface.js +20 -0
  6. package/dist/database/database-client.interface.js.map +1 -0
  7. package/dist/database/index.d.ts +5 -0
  8. package/dist/database/index.d.ts.map +1 -0
  9. package/dist/database/index.js +10 -0
  10. package/dist/database/index.js.map +1 -0
  11. package/dist/database/pg-client.d.ts +30 -0
  12. package/dist/database/pg-client.d.ts.map +1 -0
  13. package/dist/database/pg-client.js +76 -0
  14. package/dist/database/pg-client.js.map +1 -0
  15. package/dist/database/postgres-client.d.ts +44 -0
  16. package/dist/database/postgres-client.d.ts.map +1 -0
  17. package/dist/database/postgres-client.js +111 -0
  18. package/dist/database/postgres-client.js.map +1 -0
  19. package/dist/database/types.d.ts +200 -0
  20. package/dist/database/types.d.ts.map +1 -0
  21. package/dist/database/types.js +8 -0
  22. package/dist/database/types.js.map +1 -0
  23. package/dist/entity/base-entity.d.ts +21 -0
  24. package/dist/entity/base-entity.d.ts.map +1 -0
  25. package/dist/entity/base-entity.js +27 -0
  26. package/dist/entity/base-entity.js.map +1 -0
  27. package/dist/entity/db-column.d.ts +61 -0
  28. package/dist/entity/db-column.d.ts.map +1 -0
  29. package/dist/entity/db-column.js +35 -0
  30. package/dist/entity/db-column.js.map +1 -0
  31. package/dist/entity/db-context.d.ts +665 -0
  32. package/dist/entity/db-context.d.ts.map +1 -0
  33. package/dist/entity/db-context.js +1463 -0
  34. package/dist/entity/db-context.js.map +1 -0
  35. package/dist/entity/entity-base.d.ts +76 -0
  36. package/dist/entity/entity-base.d.ts.map +1 -0
  37. package/dist/entity/entity-base.js +42 -0
  38. package/dist/entity/entity-base.js.map +1 -0
  39. package/dist/entity/entity-builder.d.ts +171 -0
  40. package/dist/entity/entity-builder.d.ts.map +1 -0
  41. package/dist/entity/entity-builder.js +376 -0
  42. package/dist/entity/entity-builder.js.map +1 -0
  43. package/dist/entity/model-config.d.ts +18 -0
  44. package/dist/entity/model-config.d.ts.map +1 -0
  45. package/dist/entity/model-config.js +157 -0
  46. package/dist/entity/model-config.js.map +1 -0
  47. package/dist/index.d.ts +27 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +142 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/migration/db-schema-manager.d.ts +228 -0
  52. package/dist/migration/db-schema-manager.d.ts.map +1 -0
  53. package/dist/migration/db-schema-manager.js +1055 -0
  54. package/dist/migration/db-schema-manager.js.map +1 -0
  55. package/dist/migration/enum-migrator.d.ts +29 -0
  56. package/dist/migration/enum-migrator.d.ts.map +1 -0
  57. package/dist/migration/enum-migrator.js +137 -0
  58. package/dist/migration/enum-migrator.js.map +1 -0
  59. package/dist/query/collection-strategy.factory.d.ts +16 -0
  60. package/dist/query/collection-strategy.factory.d.ts.map +1 -0
  61. package/dist/query/collection-strategy.factory.js +37 -0
  62. package/dist/query/collection-strategy.factory.js.map +1 -0
  63. package/dist/query/collection-strategy.interface.d.ts +146 -0
  64. package/dist/query/collection-strategy.interface.d.ts.map +1 -0
  65. package/dist/query/collection-strategy.interface.js +3 -0
  66. package/dist/query/collection-strategy.interface.js.map +1 -0
  67. package/dist/query/conditions.d.ts +222 -0
  68. package/dist/query/conditions.d.ts.map +1 -0
  69. package/dist/query/conditions.js +446 -0
  70. package/dist/query/conditions.js.map +1 -0
  71. package/dist/query/cte-builder.d.ts +95 -0
  72. package/dist/query/cte-builder.d.ts.map +1 -0
  73. package/dist/query/cte-builder.js +172 -0
  74. package/dist/query/cte-builder.js.map +1 -0
  75. package/dist/query/grouped-query.d.ts +186 -0
  76. package/dist/query/grouped-query.d.ts.map +1 -0
  77. package/dist/query/grouped-query.js +588 -0
  78. package/dist/query/grouped-query.js.map +1 -0
  79. package/dist/query/join-builder.d.ts +106 -0
  80. package/dist/query/join-builder.d.ts.map +1 -0
  81. package/dist/query/join-builder.js +275 -0
  82. package/dist/query/join-builder.js.map +1 -0
  83. package/dist/query/query-builder.d.ts +543 -0
  84. package/dist/query/query-builder.d.ts.map +1 -0
  85. package/dist/query/query-builder.js +2649 -0
  86. package/dist/query/query-builder.js.map +1 -0
  87. package/dist/query/strategies/jsonb-collection-strategy.d.ts +51 -0
  88. package/dist/query/strategies/jsonb-collection-strategy.d.ts.map +1 -0
  89. package/dist/query/strategies/jsonb-collection-strategy.js +210 -0
  90. package/dist/query/strategies/jsonb-collection-strategy.js.map +1 -0
  91. package/dist/query/strategies/temptable-collection-strategy.d.ts +95 -0
  92. package/dist/query/strategies/temptable-collection-strategy.d.ts.map +1 -0
  93. package/dist/query/strategies/temptable-collection-strategy.js +456 -0
  94. package/dist/query/strategies/temptable-collection-strategy.js.map +1 -0
  95. package/dist/query/subquery.d.ts +152 -0
  96. package/dist/query/subquery.d.ts.map +1 -0
  97. package/dist/query/subquery.js +206 -0
  98. package/dist/query/subquery.js.map +1 -0
  99. package/dist/schema/column-builder.d.ts +127 -0
  100. package/dist/schema/column-builder.d.ts.map +1 -0
  101. package/dist/schema/column-builder.js +184 -0
  102. package/dist/schema/column-builder.js.map +1 -0
  103. package/dist/schema/inference.d.ts +26 -0
  104. package/dist/schema/inference.d.ts.map +1 -0
  105. package/dist/schema/inference.js +3 -0
  106. package/dist/schema/inference.js.map +1 -0
  107. package/dist/schema/navigation.d.ts +215 -0
  108. package/dist/schema/navigation.d.ts.map +1 -0
  109. package/dist/schema/navigation.js +233 -0
  110. package/dist/schema/navigation.js.map +1 -0
  111. package/dist/schema/row-type.d.ts +26 -0
  112. package/dist/schema/row-type.d.ts.map +1 -0
  113. package/dist/schema/row-type.js +3 -0
  114. package/dist/schema/row-type.js.map +1 -0
  115. package/dist/schema/sequence-builder.d.ts +87 -0
  116. package/dist/schema/sequence-builder.d.ts.map +1 -0
  117. package/dist/schema/sequence-builder.js +123 -0
  118. package/dist/schema/sequence-builder.js.map +1 -0
  119. package/dist/schema/table-builder.d.ts +122 -0
  120. package/dist/schema/table-builder.d.ts.map +1 -0
  121. package/dist/schema/table-builder.js +132 -0
  122. package/dist/schema/table-builder.js.map +1 -0
  123. package/dist/schema/typed-schema.d.ts +22 -0
  124. package/dist/schema/typed-schema.d.ts.map +1 -0
  125. package/dist/schema/typed-schema.js +28 -0
  126. package/dist/schema/typed-schema.js.map +1 -0
  127. package/dist/types/column-types.d.ts +20 -0
  128. package/dist/types/column-types.d.ts.map +1 -0
  129. package/dist/types/column-types.js +14 -0
  130. package/dist/types/column-types.js.map +1 -0
  131. package/dist/types/custom-types.d.ts +85 -0
  132. package/dist/types/custom-types.d.ts.map +1 -0
  133. package/dist/types/custom-types.js +132 -0
  134. package/dist/types/custom-types.js.map +1 -0
  135. package/dist/types/enum-builder.d.ts +31 -0
  136. package/dist/types/enum-builder.d.ts.map +1 -0
  137. package/dist/types/enum-builder.js +46 -0
  138. package/dist/types/enum-builder.js.map +1 -0
  139. package/dist/types/metadata.d.ts +67 -0
  140. package/dist/types/metadata.d.ts.map +1 -0
  141. package/dist/types/metadata.js +57 -0
  142. package/dist/types/metadata.js.map +1 -0
  143. package/dist/types/type-mapper.d.ts +49 -0
  144. package/dist/types/type-mapper.d.ts.map +1 -0
  145. package/dist/types/type-mapper.js +49 -0
  146. package/dist/types/type-mapper.js.map +1 -0
  147. package/package.json +77 -0
@@ -0,0 +1,1463 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DatabaseContext = exports.DbEntityTable = exports.EntityInsertBuilder = exports.DataContext = exports.TableAccessor = exports.InsertBuilder = exports.QueryExecutor = void 0;
4
+ const entity_base_1 = require("./entity-base");
5
+ const model_config_1 = require("./model-config");
6
+ const conditions_1 = require("../query/conditions");
7
+ const query_builder_1 = require("../query/query-builder");
8
+ const db_schema_manager_1 = require("../migration/db-schema-manager");
9
+ const sequence_builder_1 = require("../schema/sequence-builder");
10
+ /**
11
+ * Query executor with optional logging
12
+ */
13
+ class QueryExecutor {
14
+ constructor(client, options = {}) {
15
+ this.client = client;
16
+ this.options = options;
17
+ }
18
+ async query(sql, params) {
19
+ const logger = this.options.logger || console.log;
20
+ const startTime = this.options.logExecutionTime ? performance.now() : 0;
21
+ if (this.options.logQueries) {
22
+ logger(`\n[SQL Query]`);
23
+ logger(sql.trim());
24
+ if (this.options.logParameters && params && params.length > 0) {
25
+ logger(`[Parameters] ${JSON.stringify(params)}`);
26
+ }
27
+ }
28
+ try {
29
+ const result = await this.client.query(sql, params);
30
+ if (this.options.logExecutionTime) {
31
+ const duration = (performance.now() - startTime).toFixed(2);
32
+ logger(`[Execution Time] ${duration}ms`);
33
+ }
34
+ return result;
35
+ }
36
+ catch (error) {
37
+ if (this.options.logQueries) {
38
+ logger(`[SQL Error] ${error instanceof Error ? error.message : String(error)}`);
39
+ }
40
+ throw error;
41
+ }
42
+ }
43
+ /**
44
+ * Execute a multi-statement query using the simple protocol (no parameters)
45
+ * Only available for clients that support it (e.g., PostgresClient)
46
+ */
47
+ async querySimple(sql) {
48
+ const logger = this.options.logger || console.log;
49
+ const startTime = this.options.logExecutionTime ? performance.now() : 0;
50
+ if (this.options.logQueries) {
51
+ logger(`\n[SQL Query - Multi-Statement]`);
52
+ logger(sql.trim());
53
+ }
54
+ try {
55
+ // Check if client has querySimple method
56
+ if ('querySimple' in this.client && typeof this.client.querySimple === 'function') {
57
+ const result = await this.client.querySimple(sql);
58
+ if (this.options.logExecutionTime) {
59
+ const duration = (performance.now() - startTime).toFixed(2);
60
+ logger(`[Execution Time] ${duration}ms`);
61
+ }
62
+ return result;
63
+ }
64
+ else {
65
+ // Fallback to regular query
66
+ const result = await this.client.query(sql, []);
67
+ if (this.options.logExecutionTime) {
68
+ const duration = (performance.now() - startTime).toFixed(2);
69
+ logger(`[Execution Time] ${duration}ms`);
70
+ }
71
+ return result;
72
+ }
73
+ }
74
+ catch (error) {
75
+ if (this.options.logQueries) {
76
+ logger(`[SQL Error] ${error instanceof Error ? error.message : String(error)}`);
77
+ }
78
+ throw error;
79
+ }
80
+ }
81
+ /**
82
+ * Execute a multi-statement query and return ALL result sets
83
+ * Only available for PostgresClient
84
+ */
85
+ async querySimpleMulti(sql) {
86
+ const logger = this.options.logger || console.log;
87
+ const startTime = this.options.logExecutionTime ? performance.now() : 0;
88
+ if (this.options.logQueries) {
89
+ logger(`\n[SQL Query - Fully Optimized Multi-Statement]`);
90
+ logger(sql.trim());
91
+ }
92
+ try {
93
+ // Check if client has querySimpleMulti method
94
+ if ('querySimpleMulti' in this.client && typeof this.client.querySimpleMulti === 'function') {
95
+ const results = await this.client.querySimpleMulti(sql);
96
+ if (this.options.logExecutionTime) {
97
+ const duration = (performance.now() - startTime).toFixed(2);
98
+ logger(`[Execution Time] ${duration}ms`);
99
+ }
100
+ return results;
101
+ }
102
+ else {
103
+ throw new Error('querySimpleMulti not supported by this client');
104
+ }
105
+ }
106
+ catch (error) {
107
+ if (this.options.logQueries) {
108
+ logger(`[SQL Error] ${error instanceof Error ? error.message : String(error)}`);
109
+ }
110
+ throw error;
111
+ }
112
+ }
113
+ }
114
+ exports.QueryExecutor = QueryExecutor;
115
+ /**
116
+ * Insert builder for upsert operations
117
+ */
118
+ class InsertBuilder {
119
+ constructor(schema, client, executor) {
120
+ this.schema = schema;
121
+ this.client = client;
122
+ this.executor = executor;
123
+ this.dataArray = [];
124
+ this.conflictAction = 'nothing';
125
+ this.overridingSystemValue = false;
126
+ }
127
+ /**
128
+ * Get qualified table name with schema prefix if specified
129
+ */
130
+ getQualifiedTableName() {
131
+ return this.schema.schema
132
+ ? `"${this.schema.schema}"."${this.schema.name}"`
133
+ : `"${this.schema.name}"`;
134
+ }
135
+ /**
136
+ * Set the values to insert (single row or multiple rows)
137
+ */
138
+ values(data) {
139
+ this.dataArray = Array.isArray(data) ? data : [data];
140
+ return this;
141
+ }
142
+ /**
143
+ * Specify conflict target (columns or constraint name)
144
+ */
145
+ onConflict(target) {
146
+ if (Array.isArray(target)) {
147
+ this.conflictTarget = { columns: target };
148
+ }
149
+ else {
150
+ this.conflictTarget = target;
151
+ }
152
+ return this;
153
+ }
154
+ /**
155
+ * Do nothing on conflict
156
+ */
157
+ doNothing() {
158
+ this.conflictAction = 'nothing';
159
+ return this;
160
+ }
161
+ /**
162
+ * Update on conflict (upsert)
163
+ */
164
+ doUpdate(options) {
165
+ this.conflictAction = 'update';
166
+ if (options?.set) {
167
+ this.updateColumns = Object.keys(options.set);
168
+ }
169
+ if (options?.updateColumns) {
170
+ this.updateColumns = options.updateColumns;
171
+ }
172
+ if (options?.updateColumnFilter) {
173
+ this.updateColumnFilter = options.updateColumnFilter;
174
+ }
175
+ if (options?.where) {
176
+ this.setWhereClause = options.where;
177
+ }
178
+ return this;
179
+ }
180
+ /**
181
+ * Set target WHERE clause for ON CONFLICT
182
+ */
183
+ targetWhere(where) {
184
+ this.targetWhereClause = where;
185
+ return this;
186
+ }
187
+ /**
188
+ * Enable OVERRIDING SYSTEM VALUE
189
+ */
190
+ setOverridingSystemValue(value = true) {
191
+ this.overridingSystemValue = value;
192
+ return this;
193
+ }
194
+ /**
195
+ * Execute the insert/upsert
196
+ */
197
+ async execute() {
198
+ if (this.dataArray.length === 0) {
199
+ return [];
200
+ }
201
+ // Extract all unique column names from all data objects
202
+ const columnSet = new Set();
203
+ for (const data of this.dataArray) {
204
+ for (const key of Object.keys(data)) {
205
+ const column = this.schema.columns[key];
206
+ if (column) {
207
+ const config = column.build();
208
+ if (!config.autoIncrement) {
209
+ columnSet.add(key);
210
+ }
211
+ }
212
+ }
213
+ }
214
+ const columns = Array.from(columnSet);
215
+ const values = [];
216
+ const valuePlaceholders = [];
217
+ let paramIndex = 1;
218
+ // Build placeholders for each row
219
+ for (const data of this.dataArray) {
220
+ const rowPlaceholders = [];
221
+ for (const key of columns) {
222
+ const value = data[key];
223
+ const column = this.schema.columns[key];
224
+ const config = column.build();
225
+ // Apply toDriver mapper if present
226
+ const mappedValue = config.mapper
227
+ ? config.mapper.toDriver(value !== undefined ? value : null)
228
+ : (value !== undefined ? value : null);
229
+ values.push(mappedValue);
230
+ rowPlaceholders.push(`$${paramIndex++}`);
231
+ }
232
+ valuePlaceholders.push(`(${rowPlaceholders.join(', ')})`);
233
+ }
234
+ const columnNames = columns.map(key => {
235
+ const column = this.schema.columns[key];
236
+ const config = column.build();
237
+ return `"${config.name}"`;
238
+ });
239
+ const returningColumns = Object.entries(this.schema.columns)
240
+ .map(([_, col]) => `"${col.build().name}"`)
241
+ .join(', ');
242
+ const qualifiedTableName = this.getQualifiedTableName();
243
+ let sql = `INSERT INTO ${qualifiedTableName} (${columnNames.join(', ')})`;
244
+ // Add OVERRIDING SYSTEM VALUE if specified
245
+ if (this.overridingSystemValue) {
246
+ sql += '\n OVERRIDING SYSTEM VALUE';
247
+ }
248
+ sql += `\n VALUES ${valuePlaceholders.join(', ')}`;
249
+ // Add conflict handling
250
+ if (this.conflictTarget || this.conflictAction) {
251
+ sql += '\n ON CONFLICT';
252
+ if (this.conflictTarget) {
253
+ if (this.conflictTarget.columns) {
254
+ const conflictCols = this.conflictTarget.columns.map(c => `"${c}"`).join(', ');
255
+ sql += ` (${conflictCols})`;
256
+ }
257
+ else if (this.conflictTarget.constraint) {
258
+ sql += ` ON CONSTRAINT ${this.conflictTarget.constraint}`;
259
+ }
260
+ }
261
+ // Add target WHERE clause
262
+ if (this.targetWhereClause) {
263
+ sql += ` WHERE ${this.targetWhereClause}`;
264
+ }
265
+ if (this.conflictAction === 'nothing') {
266
+ sql += ' DO NOTHING';
267
+ }
268
+ else if (this.conflictAction === 'update') {
269
+ sql += ' DO UPDATE SET ';
270
+ // Determine which columns to update
271
+ let columnsToUpdate;
272
+ if (this.updateColumns) {
273
+ // Use explicitly specified columns
274
+ columnsToUpdate = this.updateColumns;
275
+ }
276
+ else if (this.updateColumnFilter) {
277
+ // Use filter function
278
+ columnsToUpdate = columns.filter(this.updateColumnFilter);
279
+ }
280
+ else {
281
+ // Update all non-primary key columns
282
+ columnsToUpdate = columns.filter(key => {
283
+ const column = this.schema.columns[key];
284
+ const config = column.build();
285
+ return !config.primaryKey;
286
+ });
287
+ }
288
+ const updateParts = columnsToUpdate.map(col => {
289
+ const column = this.schema.columns[col];
290
+ const config = column.build();
291
+ return `"${config.name}" = EXCLUDED."${config.name}"`;
292
+ });
293
+ sql += updateParts.join(', ');
294
+ if (this.setWhereClause) {
295
+ sql += ` WHERE ${this.setWhereClause}`;
296
+ }
297
+ }
298
+ }
299
+ sql += `\n RETURNING ${returningColumns}`;
300
+ const result = this.executor
301
+ ? await this.executor.query(sql, values)
302
+ : await this.client.query(sql, values);
303
+ return result.rows;
304
+ }
305
+ }
306
+ exports.InsertBuilder = InsertBuilder;
307
+ /**
308
+ * Table accessor with query methods
309
+ */
310
+ class TableAccessor {
311
+ constructor(tableBuilder, client, schemaRegistry, executor, collectionStrategy) {
312
+ this.tableBuilder = tableBuilder;
313
+ this.client = client;
314
+ this.schemaRegistry = schemaRegistry;
315
+ this.executor = executor;
316
+ this.collectionStrategy = collectionStrategy;
317
+ this.schema = tableBuilder.build();
318
+ }
319
+ /**
320
+ * Configure query options for the current query chain
321
+ * Returns a new TableAccessor instance with the specified options
322
+ *
323
+ * @example
324
+ * ```typescript
325
+ * const results = await db.users
326
+ * .withQueryOptions({ logQueries: true, collectionStrategy: 'temptable' })
327
+ * .select(u => ({ id: u.id, name: u.username }))
328
+ * .toList();
329
+ * ```
330
+ */
331
+ withQueryOptions(options) {
332
+ // Merge options with existing collectionStrategy
333
+ const mergedStrategy = options.collectionStrategy ?? this.collectionStrategy;
334
+ // Create new executor if logging options are provided
335
+ let newExecutor = this.executor;
336
+ if (options.logQueries || options.logExecutionTime) {
337
+ newExecutor = new QueryExecutor(this.client, {
338
+ ...options,
339
+ collectionStrategy: mergedStrategy,
340
+ });
341
+ }
342
+ // Return new instance with updated options
343
+ return new TableAccessor(this.tableBuilder, this.client, this.schemaRegistry, newExecutor, mergedStrategy);
344
+ }
345
+ /**
346
+ * Start a select query with automatic type inference
347
+ */
348
+ select(selector) {
349
+ return new query_builder_1.SelectQueryBuilder(this.schema, this.client, selector, undefined, undefined, undefined, undefined, this.executor, undefined, undefined, undefined, this.schemaRegistry, undefined, this.collectionStrategy);
350
+ }
351
+ /**
352
+ * Add WHERE condition before select
353
+ */
354
+ where(condition) {
355
+ const qb = new query_builder_1.QueryBuilder(this.schema, this.client, undefined, undefined, undefined, undefined, this.executor, undefined, undefined, this.collectionStrategy);
356
+ return qb.where(condition);
357
+ }
358
+ /**
359
+ * Left join with another table and selector
360
+ */
361
+ leftJoin(rightTable, condition, selector, alias) {
362
+ const qb = new query_builder_1.QueryBuilder(this.schema, this.client, undefined, undefined, undefined, undefined, this.executor, undefined, undefined, this.collectionStrategy);
363
+ return qb.leftJoin(rightTable, condition, selector, alias);
364
+ }
365
+ /**
366
+ * Inner join with another table or subquery and selector
367
+ */
368
+ innerJoin(rightTable, condition, selector, alias) {
369
+ const qb = new query_builder_1.QueryBuilder(this.schema, this.client, undefined, undefined, undefined, undefined, this.executor, undefined, undefined, this.collectionStrategy);
370
+ return qb.innerJoin(rightTable, condition, selector, alias);
371
+ }
372
+ /**
373
+ * Get table schema (internal use for joins)
374
+ */
375
+ _getSchema() {
376
+ return this.schema;
377
+ }
378
+ /**
379
+ * Get table schema
380
+ */
381
+ getSchema() {
382
+ return this.schema;
383
+ }
384
+ /**
385
+ * Get table name
386
+ */
387
+ getTableName() {
388
+ return this.schema.name;
389
+ }
390
+ /**
391
+ * Get qualified table name with schema prefix if specified
392
+ */
393
+ getQualifiedTableName() {
394
+ return this.schema.schema
395
+ ? `"${this.schema.schema}"."${this.schema.name}"`
396
+ : `"${this.schema.name}"`;
397
+ }
398
+ /**
399
+ * Insert a row
400
+ */
401
+ async insert(data) {
402
+ const columns = [];
403
+ const values = [];
404
+ const placeholders = [];
405
+ let paramIndex = 1;
406
+ for (const [key, value] of Object.entries(data)) {
407
+ const column = this.schema.columns[key];
408
+ if (column) {
409
+ const config = column.build();
410
+ if (!config.autoIncrement) {
411
+ columns.push(`"${config.name}"`);
412
+ // Apply toDriver mapper if present
413
+ const mappedValue = config.mapper
414
+ ? config.mapper.toDriver(value)
415
+ : value;
416
+ values.push(mappedValue);
417
+ placeholders.push(`$${paramIndex++}`);
418
+ }
419
+ }
420
+ }
421
+ const returningColumns = Object.entries(this.schema.columns)
422
+ .map(([_, col]) => `"${col.build().name}"`)
423
+ .join(', ');
424
+ const qualifiedTableName = this.getQualifiedTableName();
425
+ const sql = `
426
+ INSERT INTO ${qualifiedTableName} (${columns.join(', ')})
427
+ VALUES (${placeholders.join(', ')})
428
+ RETURNING ${returningColumns}
429
+ `;
430
+ const result = this.executor
431
+ ? await this.executor.query(sql, values)
432
+ : await this.client.query(sql, values);
433
+ return result.rows[0];
434
+ }
435
+ /**
436
+ * Bulk insert with advanced configuration
437
+ */
438
+ async insertBulk(value, insertConfig) {
439
+ const dataArray = Array.isArray(value) ? value : [value];
440
+ if (dataArray.length === 0) {
441
+ return [];
442
+ }
443
+ // Calculate chunk size based on max rows per batch
444
+ let chunkSize = insertConfig?.chunkSize;
445
+ if (chunkSize == null && dataArray.length > 0) {
446
+ const POSTGRES_MAX_PARAMS = 65535; // PostgreSQL parameter limit
447
+ const columnCount = Object.keys(dataArray[0]).length;
448
+ const maxRowsPerBatch = Math.floor(POSTGRES_MAX_PARAMS / columnCount);
449
+ chunkSize = Math.floor(maxRowsPerBatch * 0.6); // Use 60% of max to be safe
450
+ }
451
+ // Check if we need to chunk
452
+ if (chunkSize && dataArray.length > chunkSize) {
453
+ const results = [];
454
+ for (let i = 0; i < dataArray.length; i += chunkSize) {
455
+ const chunk = dataArray.slice(i, i + chunkSize);
456
+ const chunkResults = await this.insertBulkSingle(chunk, insertConfig);
457
+ results.push(...chunkResults);
458
+ }
459
+ return results;
460
+ }
461
+ else {
462
+ return this.insertBulkSingle(dataArray, insertConfig);
463
+ }
464
+ }
465
+ /**
466
+ * Insert a single chunk (internal method)
467
+ */
468
+ async insertBulkSingle(dataArray, insertConfig) {
469
+ if (dataArray.length === 0) {
470
+ return [];
471
+ }
472
+ // Extract all unique column names from all data objects
473
+ const columnSet = new Set();
474
+ for (const data of dataArray) {
475
+ for (const key of Object.keys(data)) {
476
+ const column = this.schema.columns[key];
477
+ if (column) {
478
+ const config = column.build();
479
+ if (!config.autoIncrement || insertConfig?.overridingSystemValue) {
480
+ columnSet.add(key);
481
+ }
482
+ }
483
+ }
484
+ }
485
+ const columns = Array.from(columnSet);
486
+ const values = [];
487
+ const valuePlaceholders = [];
488
+ let paramIndex = 1;
489
+ // Build placeholders for each row
490
+ for (const data of dataArray) {
491
+ const rowPlaceholders = [];
492
+ for (const key of columns) {
493
+ const value = data[key];
494
+ const column = this.schema.columns[key];
495
+ const config = column.build();
496
+ // Apply toDriver mapper if present
497
+ const mappedValue = config.mapper
498
+ ? config.mapper.toDriver(value !== undefined ? value : null)
499
+ : (value !== undefined ? value : null);
500
+ values.push(mappedValue);
501
+ rowPlaceholders.push(`$${paramIndex++}`);
502
+ }
503
+ valuePlaceholders.push(`(${rowPlaceholders.join(', ')})`);
504
+ }
505
+ const columnNames = columns.map(key => {
506
+ const column = this.schema.columns[key];
507
+ const config = column.build();
508
+ return `"${config.name}"`;
509
+ });
510
+ const returningColumns = Object.entries(this.schema.columns)
511
+ .map(([_, col]) => `"${col.build().name}"`)
512
+ .join(', ');
513
+ const qualifiedTableName = this.getQualifiedTableName();
514
+ let sql = `INSERT INTO ${qualifiedTableName} (${columnNames.join(', ')})`;
515
+ if (insertConfig?.overridingSystemValue) {
516
+ sql += '\n OVERRIDING SYSTEM VALUE';
517
+ }
518
+ sql += `\n VALUES ${valuePlaceholders.join(', ')}
519
+ RETURNING ${returningColumns}`;
520
+ const result = this.executor
521
+ ? await this.executor.query(sql, values)
522
+ : await this.client.query(sql, values);
523
+ return result.rows;
524
+ }
525
+ /**
526
+ * Upsert with advanced configuration
527
+ */
528
+ async upsertBulk(values, config) {
529
+ if (values.length === 0) {
530
+ return [];
531
+ }
532
+ const referenceItem = config?.referenceItem || values[0];
533
+ // Determine primary keys
534
+ let primaryKeys = [];
535
+ if (config?.primaryKey) {
536
+ primaryKeys = Array.isArray(config.primaryKey) ? config.primaryKey : [config.primaryKey];
537
+ }
538
+ else {
539
+ // Auto-detect from schema
540
+ for (const [key, colBuilder] of Object.entries(this.schema.columns)) {
541
+ const colConfig = colBuilder.build();
542
+ if (colConfig.primaryKey) {
543
+ primaryKeys.push(key);
544
+ }
545
+ }
546
+ }
547
+ // Auto-detect overridingSystemValue
548
+ let overridingSystemValue = config?.overridingSystemValue;
549
+ if (overridingSystemValue == null) {
550
+ for (const key of Object.keys(referenceItem)) {
551
+ const column = this.schema.columns[key];
552
+ if (column) {
553
+ const colConfig = column.build();
554
+ if (colConfig.primaryKey && colConfig.autoIncrement) {
555
+ overridingSystemValue = true;
556
+ break;
557
+ }
558
+ }
559
+ }
560
+ }
561
+ // Determine which columns to update
562
+ let updateColumnFilter = config?.updateColumnFilter;
563
+ if (updateColumnFilter == null && config?.updateColumns) {
564
+ const updateColSet = new Set(config.updateColumns);
565
+ updateColumnFilter = (colId) => updateColSet.has(colId);
566
+ }
567
+ if (updateColumnFilter == null) {
568
+ updateColumnFilter = (colId) => !primaryKeys.includes(colId);
569
+ }
570
+ // Calculate chunk size based on max rows per batch
571
+ let chunkSize = config?.chunkSize;
572
+ if (chunkSize == null && values.length > 0) {
573
+ const POSTGRES_MAX_PARAMS = 65535;
574
+ const columnCount = Object.keys(values[0]).length;
575
+ const maxRowsPerBatch = Math.floor(POSTGRES_MAX_PARAMS / columnCount);
576
+ chunkSize = Math.floor(maxRowsPerBatch * 0.6); // Use 60% of max to be safe
577
+ }
578
+ // Check if we need to chunk
579
+ if (chunkSize && values.length > chunkSize) {
580
+ const results = [];
581
+ for (let i = 0; i < values.length; i += chunkSize) {
582
+ const chunk = values.slice(i, i + chunkSize);
583
+ const chunkResults = await this.upsertBulkSingle(chunk, primaryKeys, updateColumnFilter, overridingSystemValue || false, config?.targetWhere, config?.setWhere);
584
+ results.push(...chunkResults);
585
+ }
586
+ return results;
587
+ }
588
+ else {
589
+ return this.upsertBulkSingle(values, primaryKeys, updateColumnFilter, overridingSystemValue || false, config?.targetWhere, config?.setWhere);
590
+ }
591
+ }
592
+ /**
593
+ * Upsert a single chunk (internal method)
594
+ */
595
+ async upsertBulkSingle(values, primaryKeys, updateColumnFilter, overridingSystemValue, targetWhere, setWhere) {
596
+ const builder = new InsertBuilder(this.schema, this.client, this.executor);
597
+ builder.values(values);
598
+ builder.setOverridingSystemValue(overridingSystemValue);
599
+ builder.onConflict({ columns: primaryKeys });
600
+ builder.doUpdate({ updateColumnFilter, where: setWhere });
601
+ if (targetWhere) {
602
+ builder.targetWhere(targetWhere);
603
+ }
604
+ return builder.execute();
605
+ }
606
+ /**
607
+ * Bulk insert multiple rows (simple version, kept for compatibility)
608
+ */
609
+ async insertMany(dataArray) {
610
+ if (dataArray.length === 0) {
611
+ return [];
612
+ }
613
+ // Extract all unique column names from all data objects
614
+ const columnSet = new Set();
615
+ for (const data of dataArray) {
616
+ for (const key of Object.keys(data)) {
617
+ const column = this.schema.columns[key];
618
+ if (column) {
619
+ const config = column.build();
620
+ if (!config.autoIncrement) {
621
+ columnSet.add(key);
622
+ }
623
+ }
624
+ }
625
+ }
626
+ const columns = Array.from(columnSet);
627
+ const values = [];
628
+ const valuePlaceholders = [];
629
+ let paramIndex = 1;
630
+ // Build placeholders for each row
631
+ for (const data of dataArray) {
632
+ const rowPlaceholders = [];
633
+ for (const key of columns) {
634
+ const value = data[key];
635
+ const column = this.schema.columns[key];
636
+ const config = column.build();
637
+ // Apply toDriver mapper if present
638
+ const mappedValue = config.mapper
639
+ ? config.mapper.toDriver(value !== undefined ? value : null)
640
+ : (value !== undefined ? value : null);
641
+ values.push(mappedValue);
642
+ rowPlaceholders.push(`$${paramIndex++}`);
643
+ }
644
+ valuePlaceholders.push(`(${rowPlaceholders.join(', ')})`);
645
+ }
646
+ const columnNames = columns.map(key => {
647
+ const column = this.schema.columns[key];
648
+ const config = column.build();
649
+ return `"${config.name}"`;
650
+ });
651
+ const returningColumns = Object.entries(this.schema.columns)
652
+ .map(([_, col]) => `"${col.build().name}"`)
653
+ .join(', ');
654
+ const qualifiedTableName = this.getQualifiedTableName();
655
+ const sql = `
656
+ INSERT INTO ${qualifiedTableName} (${columnNames.join(', ')})
657
+ VALUES ${valuePlaceholders.join(', ')}
658
+ RETURNING ${returningColumns}
659
+ `;
660
+ const result = this.executor
661
+ ? await this.executor.query(sql, values)
662
+ : await this.client.query(sql, values);
663
+ return result.rows;
664
+ }
665
+ /**
666
+ * Insert with conflict resolution (upsert)
667
+ */
668
+ onConflictDoNothing() {
669
+ return new InsertBuilder(this.schema, this.client, this.executor);
670
+ }
671
+ /**
672
+ * Insert with conflict resolution (upsert) - start building the upsert query
673
+ */
674
+ values(data) {
675
+ const builder = new InsertBuilder(this.schema, this.client, this.executor);
676
+ return builder.values(data);
677
+ }
678
+ /**
679
+ * Update rows
680
+ */
681
+ async update(id, data) {
682
+ const setClauses = [];
683
+ const values = [];
684
+ let paramIndex = 1;
685
+ // Find primary key
686
+ let pkColumnName;
687
+ for (const [key, col] of Object.entries(this.schema.columns)) {
688
+ const config = col.build();
689
+ if (config.primaryKey) {
690
+ pkColumnName = config.name;
691
+ break;
692
+ }
693
+ }
694
+ if (!pkColumnName) {
695
+ throw new Error(`Table ${this.schema.name} has no primary key`);
696
+ }
697
+ for (const [key, value] of Object.entries(data)) {
698
+ const column = this.schema.columns[key];
699
+ if (column) {
700
+ const config = column.build();
701
+ if (!config.primaryKey) {
702
+ setClauses.push(`"${config.name}" = $${paramIndex++}`);
703
+ // Apply toDriver mapper if present
704
+ const mappedValue = config.mapper
705
+ ? config.mapper.toDriver(value)
706
+ : value;
707
+ values.push(mappedValue);
708
+ }
709
+ }
710
+ }
711
+ if (setClauses.length === 0) {
712
+ return null;
713
+ }
714
+ values.push(id);
715
+ const returningColumns = Object.entries(this.schema.columns)
716
+ .map(([_, col]) => `"${col.build().name}"`)
717
+ .join(', ');
718
+ const qualifiedTableName = this.getQualifiedTableName();
719
+ const sql = `
720
+ UPDATE ${qualifiedTableName}
721
+ SET ${setClauses.join(', ')}
722
+ WHERE "${pkColumnName}" = $${paramIndex}
723
+ RETURNING ${returningColumns}
724
+ `;
725
+ const result = this.executor
726
+ ? await this.executor.query(sql, values)
727
+ : await this.client.query(sql, values);
728
+ return result.rows.length > 0 ? result.rows[0] : null;
729
+ }
730
+ /**
731
+ * Delete a row by id
732
+ */
733
+ async delete(id) {
734
+ // Find primary key
735
+ let pkColumnName;
736
+ for (const [key, col] of Object.entries(this.schema.columns)) {
737
+ const config = col.build();
738
+ if (config.primaryKey) {
739
+ pkColumnName = config.name;
740
+ break;
741
+ }
742
+ }
743
+ if (!pkColumnName) {
744
+ throw new Error(`Table ${this.schema.name} has no primary key`);
745
+ }
746
+ const qualifiedTableName = this.getQualifiedTableName();
747
+ const sql = `DELETE FROM ${qualifiedTableName} WHERE "${pkColumnName}" = $1`;
748
+ const result = this.executor
749
+ ? await this.executor.query(sql, [id])
750
+ : await this.client.query(sql, [id]);
751
+ return result.rowCount !== null && result.rowCount > 0;
752
+ }
753
+ }
754
+ exports.TableAccessor = TableAccessor;
755
+ /**
756
+ * DataContext - main entry point for database operations
757
+ */
758
+ class DataContext {
759
+ constructor(client, schema, queryOptions) {
760
+ this.schemaRegistry = new Map();
761
+ this.tableAccessors = new Map();
762
+ this.client = client;
763
+ this.queryOptions = queryOptions;
764
+ // Create executor if logging is enabled
765
+ if (queryOptions?.logQueries || queryOptions?.logExecutionTime) {
766
+ this.executor = new QueryExecutor(client, queryOptions);
767
+ }
768
+ this.initializeSchema(schema);
769
+ }
770
+ /**
771
+ * Initialize schema and create table accessors
772
+ */
773
+ initializeSchema(schema) {
774
+ for (const [key, tableBuilder] of Object.entries(schema)) {
775
+ const tableSchema = tableBuilder.build();
776
+ this.schemaRegistry.set(tableSchema.name, tableSchema);
777
+ const accessor = new TableAccessor(tableBuilder, this.client, this.schemaRegistry, this.executor, this.queryOptions?.collectionStrategy);
778
+ this.tableAccessors.set(key, accessor);
779
+ // Attach to context (skip if property already has a getter on prototype chain)
780
+ const descriptor = Object.getOwnPropertyDescriptor(this, key) ||
781
+ Object.getOwnPropertyDescriptor(Object.getPrototypeOf(this), key);
782
+ if (!descriptor || !descriptor.get) {
783
+ this[key] = accessor;
784
+ }
785
+ }
786
+ }
787
+ /**
788
+ * Get table accessor by name
789
+ */
790
+ getTable(name) {
791
+ const accessor = this.tableAccessors.get(name);
792
+ if (!accessor) {
793
+ throw new Error(`Table ${String(name)} not found in schema`);
794
+ }
795
+ return accessor;
796
+ }
797
+ /**
798
+ * Execute raw SQL
799
+ */
800
+ async query(sql, params) {
801
+ return this.client.query(sql, params);
802
+ }
803
+ /**
804
+ * Execute in transaction
805
+ */
806
+ async transaction(fn) {
807
+ const connection = await this.client.connect();
808
+ try {
809
+ await connection.query('BEGIN');
810
+ const result = await fn(this);
811
+ await connection.query('COMMIT');
812
+ return result;
813
+ }
814
+ catch (error) {
815
+ await connection.query('ROLLBACK');
816
+ throw error;
817
+ }
818
+ finally {
819
+ connection.release();
820
+ }
821
+ }
822
+ /**
823
+ * Get schema manager for create/drop operations and automatic migrations
824
+ */
825
+ getSchemaManager() {
826
+ return new db_schema_manager_1.DbSchemaManager(this.client, this.schemaRegistry, { logQueries: this.queryOptions?.logQueries });
827
+ }
828
+ /**
829
+ * Close database connection
830
+ */
831
+ async dispose() {
832
+ await this.client.end();
833
+ }
834
+ }
835
+ exports.DataContext = DataContext;
836
+ /**
837
+ * DbEntity insert builder for upsert operations with proper typing
838
+ */
839
+ class EntityInsertBuilder {
840
+ constructor(builder) {
841
+ this.builder = builder;
842
+ }
843
+ /**
844
+ * Specify conflict target (columns or constraint name)
845
+ */
846
+ onConflict(target) {
847
+ this.builder.onConflict(target);
848
+ return this;
849
+ }
850
+ /**
851
+ * Do nothing on conflict
852
+ */
853
+ doNothing() {
854
+ this.builder.doNothing();
855
+ return this;
856
+ }
857
+ /**
858
+ * Update on conflict (upsert)
859
+ */
860
+ doUpdate(options) {
861
+ this.builder.doUpdate(options);
862
+ return this;
863
+ }
864
+ /**
865
+ * Execute the insert/upsert
866
+ */
867
+ async execute() {
868
+ return this.builder.execute();
869
+ }
870
+ }
871
+ exports.EntityInsertBuilder = EntityInsertBuilder;
872
+ /**
873
+ * Table accessor with entity typing
874
+ */
875
+ class DbEntityTable {
876
+ constructor(context, tableName, tableBuilder) {
877
+ this.context = context;
878
+ this.tableName = tableName;
879
+ this.tableBuilder = tableBuilder;
880
+ }
881
+ /**
882
+ * Get the table schema for this entity
883
+ * @internal
884
+ */
885
+ _getSchema() {
886
+ const schemaRegistry = this.context.schemaRegistry;
887
+ const schema = schemaRegistry.get(this.tableName);
888
+ if (!schema) {
889
+ throw new Error(`Schema not found for table ${this.tableName}`);
890
+ }
891
+ return schema;
892
+ }
893
+ /**
894
+ * Get the database client
895
+ * @internal
896
+ */
897
+ _getClient() {
898
+ return this.context.client;
899
+ }
900
+ /**
901
+ * Get the query executor for logging
902
+ * @internal
903
+ */
904
+ _getExecutor() {
905
+ return this.context.executor;
906
+ }
907
+ /**
908
+ * Get the collection strategy
909
+ * @internal
910
+ */
911
+ _getCollectionStrategy() {
912
+ return this.context.queryOptions?.collectionStrategy;
913
+ }
914
+ /**
915
+ * Get qualified table name with schema prefix if specified
916
+ * @internal
917
+ */
918
+ _getQualifiedTableName() {
919
+ const schema = this._getSchema();
920
+ return schema.schema
921
+ ? `"${schema.schema}"."${schema.name}"`
922
+ : `"${schema.name}"`;
923
+ }
924
+ /**
925
+ * Configure query options for the current query chain
926
+ * Returns a new DbEntityTable instance with a modified context that has the specified options
927
+ *
928
+ * @example
929
+ * ```typescript
930
+ * const results = await db.users
931
+ * .withQueryOptions({ logQueries: true, collectionStrategy: 'temptable' })
932
+ * .select(u => ({ id: u.id, name: u.username }))
933
+ * .toList();
934
+ * ```
935
+ */
936
+ withQueryOptions(options) {
937
+ // Create a proxy context with modified options
938
+ const originalContext = this.context;
939
+ const originalOptions = originalContext.queryOptions || {};
940
+ const tableName = this.tableName;
941
+ // Merge options
942
+ const mergedOptions = { ...originalOptions, ...options };
943
+ // Create new executor if logging options are provided
944
+ let newExecutor = originalContext.executor;
945
+ if (mergedOptions.logQueries || mergedOptions.logExecutionTime) {
946
+ newExecutor = new QueryExecutor(originalContext.client, mergedOptions);
947
+ }
948
+ // Create a proxy context that overrides queryOptions, executor, and getTable
949
+ const proxyContext = new Proxy(originalContext, {
950
+ get(target, prop) {
951
+ if (prop === 'queryOptions') {
952
+ return mergedOptions;
953
+ }
954
+ if (prop === 'executor') {
955
+ return newExecutor;
956
+ }
957
+ if (prop === 'getTable') {
958
+ return (name) => {
959
+ // Get the original TableAccessor
960
+ const originalAccessor = target.tableAccessors.get(name);
961
+ if (!originalAccessor) {
962
+ return target.getTable(name);
963
+ }
964
+ // If requesting this table, return a TableAccessor with updated options
965
+ if (name === tableName) {
966
+ const schemaRegistry = target.schemaRegistry;
967
+ const client = target.client;
968
+ const originalTableBuilder = originalAccessor.tableBuilder;
969
+ return new TableAccessor(originalTableBuilder, client, schemaRegistry, newExecutor, mergedOptions.collectionStrategy);
970
+ }
971
+ // Otherwise return the original table
972
+ return originalAccessor;
973
+ };
974
+ }
975
+ return target[prop];
976
+ }
977
+ });
978
+ // Return new instance with proxy context
979
+ return new DbEntityTable(proxyContext, this.tableName, this.tableBuilder);
980
+ }
981
+ /**
982
+ * Select all records - returns full entities with unwrapped DbColumns
983
+ */
984
+ async toList() {
985
+ const queryBuilder = this.context.getTable(this.tableName);
986
+ const schema = this._getSchema();
987
+ // Build SELECT with all columns
988
+ const columns = Object.entries(schema.columns)
989
+ .map(([_, col]) => `"${col.build().name}"`)
990
+ .join(', ');
991
+ const qualifiedTableName = this._getQualifiedTableName();
992
+ const sql = `SELECT ${columns} FROM ${qualifiedTableName}`;
993
+ const executor = this._getExecutor();
994
+ const client = this._getClient();
995
+ const result = executor
996
+ ? await executor.query(sql, [])
997
+ : await client.query(sql, []);
998
+ return this.mapResultsToEntities(result.rows);
999
+ }
1000
+ /**
1001
+ * Count all records
1002
+ */
1003
+ async count() {
1004
+ const schema = this._getSchema();
1005
+ const qualifiedTableName = this._getQualifiedTableName();
1006
+ const sql = `SELECT COUNT(*) as count FROM ${qualifiedTableName}`;
1007
+ const executor = this._getExecutor();
1008
+ const client = this._getClient();
1009
+ const result = executor
1010
+ ? await executor.query(sql, [])
1011
+ : await client.query(sql, []);
1012
+ return parseInt(result.rows[0].count);
1013
+ }
1014
+ orderBy(selector) {
1015
+ const schema = this._getSchema();
1016
+ const allColumnsSelector = (e) => {
1017
+ const result = {};
1018
+ for (const colName of Object.keys(schema.columns)) {
1019
+ result[colName] = e[colName];
1020
+ }
1021
+ return result;
1022
+ };
1023
+ const qb = this.context.getTable(this.tableName).select(allColumnsSelector);
1024
+ return qb.orderBy(selector);
1025
+ }
1026
+ /**
1027
+ * Limit results
1028
+ */
1029
+ limit(count) {
1030
+ const schema = this._getSchema();
1031
+ const allColumnsSelector = (e) => {
1032
+ const result = {};
1033
+ for (const colName of Object.keys(schema.columns)) {
1034
+ result[colName] = e[colName];
1035
+ }
1036
+ return result;
1037
+ };
1038
+ const qb = this.context.getTable(this.tableName).select(allColumnsSelector);
1039
+ return qb.limit(count);
1040
+ }
1041
+ /**
1042
+ * Offset results
1043
+ */
1044
+ offset(count) {
1045
+ const schema = this._getSchema();
1046
+ const allColumnsSelector = (e) => {
1047
+ const result = {};
1048
+ for (const colName of Object.keys(schema.columns)) {
1049
+ result[colName] = e[colName];
1050
+ }
1051
+ return result;
1052
+ };
1053
+ const qb = this.context.getTable(this.tableName).select(allColumnsSelector);
1054
+ return qb.offset(count);
1055
+ }
1056
+ /**
1057
+ * Select query
1058
+ */
1059
+ select(selector) {
1060
+ const queryBuilder = this.context.getTable(this.tableName).select(selector);
1061
+ return queryBuilder;
1062
+ }
1063
+ /**
1064
+ * Select distinct
1065
+ */
1066
+ selectDistinct(selector) {
1067
+ const queryBuilder = this.context.getTable(this.tableName);
1068
+ // First select, then call selectDistinct to get a new builder with isDistinct=true
1069
+ const selectBuilder = queryBuilder.select(selector);
1070
+ const distinctBuilder = selectBuilder.selectDistinct((x) => x);
1071
+ return distinctBuilder;
1072
+ }
1073
+ /**
1074
+ * Where query - returns all columns by default
1075
+ */
1076
+ where(condition) {
1077
+ const schema = this._getSchema();
1078
+ // Create a selector that selects all columns with navigation property access
1079
+ const allColumnsSelector = (e) => {
1080
+ const result = {};
1081
+ // Copy all column properties
1082
+ for (const colName of Object.keys(schema.columns)) {
1083
+ result[colName] = e[colName];
1084
+ }
1085
+ // Add navigation properties
1086
+ for (const relName of Object.keys(schema.relations)) {
1087
+ const relConfig = schema.relations[relName];
1088
+ if (relConfig.type === 'many') {
1089
+ // For collections, use empty array placeholder
1090
+ // (prevents CollectionQueryBuilder evaluation)
1091
+ result[relName] = [];
1092
+ }
1093
+ else {
1094
+ // For references, copy the ReferenceQueryBuilder mock
1095
+ // This allows accessing fields like o.user!.username in chained selectors
1096
+ result[relName] = e[relName];
1097
+ }
1098
+ }
1099
+ return result;
1100
+ };
1101
+ const queryBuilder = this.context.getTable(this.tableName)
1102
+ .where(condition)
1103
+ .select(allColumnsSelector);
1104
+ return queryBuilder;
1105
+ }
1106
+ /**
1107
+ * Left join with another table or subquery and selector
1108
+ */
1109
+ leftJoin(rightTable, condition, selector, alias) {
1110
+ const queryBuilder = this.context.getTable(this.tableName).leftJoin(rightTable, condition, selector, alias);
1111
+ return queryBuilder;
1112
+ }
1113
+ /**
1114
+ * Inner join with another table or subquery and selector
1115
+ */
1116
+ innerJoin(rightTable, condition, selector, alias) {
1117
+ const queryBuilder = this.context.getTable(this.tableName).innerJoin(rightTable, condition, selector, alias);
1118
+ return queryBuilder;
1119
+ }
1120
+ /**
1121
+ * Insert - accepts only DbColumn properties (excludes navigation properties)
1122
+ */
1123
+ async insert(data) {
1124
+ const result = await this.context.getTable(this.tableName).insert(data);
1125
+ return this.mapResultToEntity(result);
1126
+ }
1127
+ /**
1128
+ * Insert multiple records
1129
+ */
1130
+ async insertMany(data) {
1131
+ return this.insertBulk(data);
1132
+ }
1133
+ /**
1134
+ * Upsert (insert or update on conflict)
1135
+ */
1136
+ async upsert(data, config) {
1137
+ return this.upsertBulk(data, config);
1138
+ }
1139
+ /**
1140
+ * Bulk insert with advanced configuration
1141
+ * Supports automatic chunking for large datasets
1142
+ */
1143
+ async insertBulk(value, insertConfig) {
1144
+ const results = await this.context.getTable(this.tableName).insertBulk(value, insertConfig);
1145
+ return this.mapResultsToEntities(results);
1146
+ }
1147
+ /**
1148
+ * Upsert with advanced configuration
1149
+ * Auto-detects primary keys and supports chunking
1150
+ */
1151
+ async upsertBulk(values, config) {
1152
+ // Convert typed config to base config
1153
+ const baseConfig = {
1154
+ chunkSize: config?.chunkSize,
1155
+ overridingSystemValue: config?.overridingSystemValue,
1156
+ targetWhere: config?.targetWhere,
1157
+ setWhere: config?.setWhere,
1158
+ referenceItem: config?.referenceItem,
1159
+ updateColumnFilter: config?.updateColumnFilter,
1160
+ };
1161
+ // Handle primaryKey (can be lambda, string, or array)
1162
+ if (config?.primaryKey) {
1163
+ if (typeof config.primaryKey === 'function') {
1164
+ // Lambda selector - extract property names
1165
+ const pkProps = this.extractPropertyNames(config.primaryKey);
1166
+ baseConfig.primaryKey = pkProps.length === 1 ? pkProps[0] : pkProps;
1167
+ }
1168
+ else {
1169
+ // Direct string or array
1170
+ baseConfig.primaryKey = config.primaryKey;
1171
+ }
1172
+ }
1173
+ // Handle updateColumns (can be lambda or array)
1174
+ if (config?.updateColumns) {
1175
+ if (typeof config.updateColumns === 'function') {
1176
+ // Lambda selector - extract property names
1177
+ baseConfig.updateColumns = this.extractPropertyNames(config.updateColumns);
1178
+ }
1179
+ else {
1180
+ // Direct array
1181
+ baseConfig.updateColumns = config.updateColumns;
1182
+ }
1183
+ }
1184
+ const results = await this.context.getTable(this.tableName).upsertBulk(values, baseConfig);
1185
+ return this.mapResultsToEntities(results);
1186
+ }
1187
+ /**
1188
+ * Map database column names back to property names
1189
+ */
1190
+ mapResultToEntity(result) {
1191
+ const schema = this._getSchema();
1192
+ const mapped = {};
1193
+ for (const [propName, colBuilder] of Object.entries(schema.columns)) {
1194
+ const config = colBuilder.build();
1195
+ const dbColumnName = config.name;
1196
+ if (dbColumnName in result) {
1197
+ mapped[propName] = result[dbColumnName];
1198
+ }
1199
+ }
1200
+ return mapped;
1201
+ }
1202
+ /**
1203
+ * Map array of database results to entities
1204
+ */
1205
+ mapResultsToEntities(results) {
1206
+ return results.map(r => this.mapResultToEntity(r));
1207
+ }
1208
+ /**
1209
+ * Extract property names from lambda selector
1210
+ */
1211
+ extractPropertyNames(selector) {
1212
+ const selectorStr = selector.toString();
1213
+ // Match patterns like: e => e.username or e => ({ username: e.username, email: e.email })
1214
+ const propertyNames = [];
1215
+ // Simple property access: e => e.username
1216
+ const simpleMatch = selectorStr.match(/=>\s*\w+\.(\w+)/);
1217
+ if (simpleMatch) {
1218
+ propertyNames.push(simpleMatch[1]);
1219
+ return propertyNames;
1220
+ }
1221
+ // Object literal: e => ({ username: e.username, email: e.email })
1222
+ const objectMatches = selectorStr.matchAll(/(\w+):\s*\w+\.\1/g);
1223
+ for (const match of objectMatches) {
1224
+ propertyNames.push(match[1]);
1225
+ }
1226
+ return propertyNames;
1227
+ }
1228
+ /**
1229
+ * Start building an upsert query with values
1230
+ */
1231
+ values(data) {
1232
+ const builder = this.context.getTable(this.tableName).values(data);
1233
+ return new EntityInsertBuilder(builder);
1234
+ }
1235
+ /**
1236
+ * Update records matching condition
1237
+ * Usage: db.users.update({ age: 30 }, u => eq(u.id, 1))
1238
+ */
1239
+ async update(data, condition) {
1240
+ const schema = this._getSchema();
1241
+ const executor = this._getExecutor();
1242
+ const client = this._getClient();
1243
+ // Build SET clause
1244
+ const setClauses = [];
1245
+ const values = [];
1246
+ let paramIndex = 1;
1247
+ for (const [key, value] of Object.entries(data)) {
1248
+ const column = schema.columns[key];
1249
+ if (column) {
1250
+ const config = column.build();
1251
+ setClauses.push(`"${config.name}" = $${paramIndex++}`);
1252
+ values.push(value);
1253
+ }
1254
+ }
1255
+ if (setClauses.length === 0) {
1256
+ throw new Error('No valid columns to update');
1257
+ }
1258
+ // Build WHERE clause
1259
+ const mockEntity = this.createMockEntity();
1260
+ const whereCondition = condition(mockEntity);
1261
+ const condBuilder = new conditions_1.ConditionBuilder();
1262
+ const { sql: whereSql, params: whereParams } = condBuilder.build(whereCondition, paramIndex);
1263
+ values.push(...whereParams);
1264
+ // Build RETURNING clause
1265
+ const returningColumns = Object.entries(schema.columns)
1266
+ .map(([_, col]) => `"${col.build().name}"`)
1267
+ .join(', ');
1268
+ const qualifiedTableName = this._getQualifiedTableName();
1269
+ const sql = `
1270
+ UPDATE ${qualifiedTableName}
1271
+ SET ${setClauses.join(', ')}
1272
+ WHERE ${whereSql}
1273
+ RETURNING ${returningColumns}
1274
+ `;
1275
+ const result = executor
1276
+ ? await executor.query(sql, values)
1277
+ : await client.query(sql, values);
1278
+ return this.mapResultsToEntities(result.rows);
1279
+ }
1280
+ /**
1281
+ * Delete records matching condition
1282
+ * Usage: db.users.delete(u => eq(u.id, 1))
1283
+ */
1284
+ async delete(condition) {
1285
+ const schema = this._getSchema();
1286
+ const executor = this._getExecutor();
1287
+ const client = this._getClient();
1288
+ // Build WHERE clause
1289
+ const mockEntity = this.createMockEntity();
1290
+ const whereCondition = condition(mockEntity);
1291
+ const condBuilder = new conditions_1.ConditionBuilder();
1292
+ const { sql: whereSql, params: whereParams } = condBuilder.build(whereCondition, 1);
1293
+ const qualifiedTableName = this._getQualifiedTableName();
1294
+ const sql = `DELETE FROM ${qualifiedTableName} WHERE ${whereSql}`;
1295
+ const result = executor
1296
+ ? await executor.query(sql, whereParams)
1297
+ : await client.query(sql, whereParams);
1298
+ }
1299
+ /**
1300
+ * Create a mock entity for type inference in lambdas
1301
+ */
1302
+ createMockEntity() {
1303
+ const schema = this._getSchema();
1304
+ const mock = {};
1305
+ // Add all columns as DbColumn-like objects
1306
+ for (const [propName, colBuilder] of Object.entries(schema.columns)) {
1307
+ const config = colBuilder.build();
1308
+ Object.defineProperty(mock, propName, {
1309
+ get: () => ({
1310
+ __fieldName: propName,
1311
+ __dbColumnName: config.name,
1312
+ __isDbColumn: true,
1313
+ }),
1314
+ enumerable: true,
1315
+ });
1316
+ }
1317
+ // Add navigation properties (both collections and references)
1318
+ for (const [relName, relConfig] of Object.entries(schema.relations)) {
1319
+ if (relConfig.type === 'many') {
1320
+ Object.defineProperty(mock, relName, {
1321
+ get: () => {
1322
+ const targetSchema = relConfig.targetTableBuilder?.build();
1323
+ return new query_builder_1.CollectionQueryBuilder(relName, relConfig.targetTable, relConfig.foreignKey || relConfig.foreignKeys?.[0] || '', schema.name, targetSchema);
1324
+ },
1325
+ enumerable: true,
1326
+ });
1327
+ }
1328
+ else {
1329
+ // Single reference navigation (many-to-one, one-to-one)
1330
+ Object.defineProperty(mock, relName, {
1331
+ get: () => {
1332
+ const targetSchema = relConfig.targetTableBuilder?.build();
1333
+ const refBuilder = new query_builder_1.ReferenceQueryBuilder(relName, relConfig.targetTable, relConfig.foreignKeys || [relConfig.foreignKey || ''], relConfig.matches || [], relConfig.isMandatory ?? false, targetSchema);
1334
+ return refBuilder.createMockTargetRow();
1335
+ },
1336
+ enumerable: true,
1337
+ });
1338
+ }
1339
+ }
1340
+ return mock;
1341
+ }
1342
+ }
1343
+ exports.DbEntityTable = DbEntityTable;
1344
+ /**
1345
+ * Base database context with entity-first approach
1346
+ */
1347
+ class DatabaseContext extends DataContext {
1348
+ constructor(client, queryOptions) {
1349
+ // Initialize model config
1350
+ const modelConfig = new model_config_1.DbModelConfig();
1351
+ // Get the actual derived class's setupModel
1352
+ const derivedPrototype = new.target.prototype;
1353
+ if (derivedPrototype.setupModel) {
1354
+ derivedPrototype.setupModel.call({ setupModel: derivedPrototype.setupModel }, modelConfig);
1355
+ }
1356
+ // Build schema from model
1357
+ const schema = {};
1358
+ const tables = modelConfig.buildTables();
1359
+ for (const [tableName, tableBuilder] of tables) {
1360
+ schema[tableName] = tableBuilder;
1361
+ }
1362
+ // Call parent with built schema
1363
+ super(client, schema, queryOptions);
1364
+ this.entityTables = new Map();
1365
+ this.sequenceRegistry = new Map();
1366
+ this.sequenceInstances = new Map();
1367
+ this.modelConfig = modelConfig;
1368
+ // Setup sequences after construction (call setupSequences if it exists)
1369
+ if (derivedPrototype.setupSequences) {
1370
+ derivedPrototype.setupSequences.call(this);
1371
+ }
1372
+ }
1373
+ /**
1374
+ * Hook called after database migrations/schema creation are complete.
1375
+ * Override this method to execute custom SQL scripts that are outside the scope of the ORM.
1376
+ *
1377
+ * @example
1378
+ * ```typescript
1379
+ * protected async onMigrationComplete(client: DatabaseClient): Promise<void> {
1380
+ * // Create custom functions, views, triggers, etc.
1381
+ * await client.query(`
1382
+ * CREATE OR REPLACE FUNCTION custom_function()
1383
+ * RETURNS void AS $$
1384
+ * BEGIN
1385
+ * -- Custom logic here
1386
+ * END;
1387
+ * $$ LANGUAGE plpgsql;
1388
+ * `);
1389
+ * }
1390
+ * ```
1391
+ *
1392
+ * @param client - Database client for executing custom SQL
1393
+ */
1394
+ async onMigrationComplete(client) {
1395
+ // Default implementation does nothing
1396
+ // Override in derived class to execute custom scripts
1397
+ }
1398
+ /**
1399
+ * Register a sequence in the schema
1400
+ * @param config - Sequence configuration
1401
+ */
1402
+ registerSequence(config) {
1403
+ const key = config.schema ? `${config.schema}.${config.name}` : config.name;
1404
+ this.sequenceRegistry.set(key, config);
1405
+ }
1406
+ /**
1407
+ * Get a sequence instance for interacting with the database
1408
+ * @param config - Sequence configuration
1409
+ * @returns DbSequence instance with nextValue() and resync() methods
1410
+ */
1411
+ sequence(config) {
1412
+ const key = config.schema ? `${config.schema}.${config.name}` : config.name;
1413
+ // Register if not already registered
1414
+ if (!this.sequenceRegistry.has(key)) {
1415
+ this.sequenceRegistry.set(key, config);
1416
+ }
1417
+ // Return cached instance or create new one
1418
+ let instance = this.sequenceInstances.get(key);
1419
+ if (!instance) {
1420
+ instance = new sequence_builder_1.DbSequence(this.client, config);
1421
+ this.sequenceInstances.set(key, instance);
1422
+ }
1423
+ return instance;
1424
+ }
1425
+ /**
1426
+ * Get all registered sequences
1427
+ * @internal
1428
+ */
1429
+ getSequenceRegistry() {
1430
+ return this.sequenceRegistry;
1431
+ }
1432
+ /**
1433
+ * Get schema manager for create/drop operations with post-migration hook support
1434
+ */
1435
+ getSchemaManager() {
1436
+ return new db_schema_manager_1.DbSchemaManager(this.client, this.schemaRegistry, {
1437
+ logQueries: this.queryOptions?.logQueries,
1438
+ postMigrationHook: async (client) => {
1439
+ await this.onMigrationComplete(client);
1440
+ },
1441
+ sequenceRegistry: this.sequenceRegistry
1442
+ });
1443
+ }
1444
+ /**
1445
+ * Get strongly-typed table accessor for an entity
1446
+ * @internal - Use property accessors on derived class instead
1447
+ */
1448
+ table(entityClass) {
1449
+ let table = this.entityTables.get(entityClass);
1450
+ if (!table) {
1451
+ const metadata = entity_base_1.EntityMetadataStore.getMetadata(entityClass);
1452
+ if (!metadata) {
1453
+ throw new Error(`No metadata found for entity ${entityClass.name}`);
1454
+ }
1455
+ // EntityTable doesn't need the tableBuilder, it just uses getTable() internally
1456
+ table = new DbEntityTable(this, metadata.tableName, null);
1457
+ this.entityTables.set(entityClass, table);
1458
+ }
1459
+ return table;
1460
+ }
1461
+ }
1462
+ exports.DatabaseContext = DatabaseContext;
1463
+ //# sourceMappingURL=db-context.js.map