@holo-js/db 0.2.5 → 0.2.6

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 (3) hide show
  1. package/dist/index.d.ts +279 -6
  2. package/dist/index.mjs +2639 -204
  3. package/package.json +7 -7
package/dist/index.mjs CHANGED
@@ -122,6 +122,11 @@ function redactSql(sql, policy) {
122
122
  // src/cache.ts
123
123
  import { createHash } from "crypto";
124
124
  import { AsyncLocalStorage } from "async_hooks";
125
+ var DATABASE_DEPENDENCY_PREFIX = "db:";
126
+ var DATABASE_DEPENDENCY_METADATA_KEY = "__holoDatabaseDependencyMetadata__";
127
+ var MUTATION_DEPENDENCY_SUFFIX = "mutation";
128
+ var WHERE_DEPENDENCY_PREFIX = "where:";
129
+ var WHERE_EXACT_DEPENDENCY_PREFIX = "where-exact:";
125
130
  var databaseDependencyCollector = new AsyncLocalStorage();
126
131
  function getQueryCacheBridgeState() {
127
132
  const runtime = globalThis;
@@ -140,20 +145,32 @@ function resetDatabaseQueryCacheBridge() {
140
145
  function onDatabaseDependencyInvalidated(listener) {
141
146
  const state = getQueryCacheBridgeState();
142
147
  state.dependencyInvalidationListeners ??= /* @__PURE__ */ new Set();
143
- state.dependencyInvalidationListeners.add(listener);
148
+ const internalListener = listener;
149
+ state.dependencyInvalidationListeners.add(internalListener);
144
150
  return () => {
145
- state.dependencyInvalidationListeners?.delete(listener);
151
+ state.dependencyInvalidationListeners?.delete(internalListener);
146
152
  };
147
153
  }
148
154
  function resetDatabaseDependencyInvalidationListeners() {
149
155
  getQueryCacheBridgeState().dependencyInvalidationListeners = void 0;
150
156
  }
151
157
  async function collectDatabaseQueryDependencies(callback) {
152
- const dependencies = /* @__PURE__ */ new Set();
153
- const value = await databaseDependencyCollector.run(dependencies, callback);
158
+ const result = await collectDatabaseQueryDependenciesInternal(callback);
159
+ return Object.freeze({
160
+ value: result.value,
161
+ dependencies: result.dependencies
162
+ });
163
+ }
164
+ async function collectDatabaseQueryDependenciesInternal(callback) {
165
+ const state = {
166
+ dependencies: /* @__PURE__ */ new Set(),
167
+ queries: []
168
+ };
169
+ const value = await databaseDependencyCollector.run(state, callback);
154
170
  return Object.freeze({
155
171
  value,
156
- dependencies: Object.freeze([...dependencies])
172
+ dependencies: Object.freeze([...state.dependencies]),
173
+ queries: Object.freeze([...state.queries])
157
174
  });
158
175
  }
159
176
  function hasActiveDatabaseDependencyCollector() {
@@ -163,12 +180,227 @@ function recordDatabaseQueryDependencies(dependencies) {
163
180
  if (!dependencies || dependencies.length === 0) {
164
181
  return;
165
182
  }
166
- const active = databaseDependencyCollector.getStore();
167
- if (!active) {
183
+ const state = databaseDependencyCollector.getStore();
184
+ if (!state) {
168
185
  return;
169
186
  }
170
187
  for (const dependency of dependencies) {
171
- active.add(dependency);
188
+ state.dependencies.add(dependency);
189
+ }
190
+ }
191
+ function recordDatabaseQueryObservation(observation) {
192
+ if (!observation) {
193
+ return;
194
+ }
195
+ const state = databaseDependencyCollector.getStore();
196
+ if (!state) {
197
+ return;
198
+ }
199
+ state.queries.push(observation);
200
+ }
201
+ function readDatabaseQueryObservationCount() {
202
+ return databaseDependencyCollector.getStore()?.queries.length ?? 0;
203
+ }
204
+ function truncateDatabaseQueryObservations(count) {
205
+ const state = databaseDependencyCollector.getStore();
206
+ if (!state || count >= state.queries.length) {
207
+ return;
208
+ }
209
+ state.queries.length = Math.max(0, count);
210
+ }
211
+ function rebindDatabaseQueryObservationResult(source, result) {
212
+ rebindDatabaseQueryObservation(source, result);
213
+ }
214
+ function rebindDatabaseQueryObservationHydratedResult(source, result, belongsToHydrations, relatedHydrations) {
215
+ rebindDatabaseQueryObservation(source, result, void 0, void 0, void 0, void 0, belongsToHydrations, relatedHydrations);
216
+ }
217
+ function rebindDatabaseQueryObservationPagination(source, data, meta, pagination, offset, belongsToHydrations, relatedHydrations) {
218
+ const state = databaseDependencyCollector.getStore();
219
+ if (!state) {
220
+ return;
221
+ }
222
+ for (let index = state.queries.length - 1; index >= 0; index -= 1) {
223
+ const query = state.queries[index];
224
+ if (!query || query.result !== source) {
225
+ continue;
226
+ }
227
+ state.queries[index] = Object.freeze({
228
+ aggregate: void 0,
229
+ belongsToHydrations: belongsToHydrations ?? query.belongsToHydrations,
230
+ connectionName: query.connectionName,
231
+ tableName: query.tableName,
232
+ dependencies: query.dependencies,
233
+ limit: pagination.perPage,
234
+ offset,
235
+ orderBy: query.orderBy,
236
+ patchable: query.patchable,
237
+ pagination: void 0,
238
+ predicates: query.predicates,
239
+ relation: query.relation,
240
+ result: data,
241
+ resultPath: query.resultPath,
242
+ relatedHydrations: relatedHydrations ?? query.relatedHydrations,
243
+ scalarColumn: void 0,
244
+ scalarListColumn: void 0,
245
+ scalarListRows: void 0,
246
+ selections: query.selections
247
+ });
248
+ state.queries.push(Object.freeze({
249
+ aggregate: void 0,
250
+ belongsToHydrations: void 0,
251
+ connectionName: query.connectionName,
252
+ tableName: query.tableName,
253
+ dependencies: query.dependencies,
254
+ limit: void 0,
255
+ offset: void 0,
256
+ orderBy: query.orderBy,
257
+ patchable: query.patchable,
258
+ pagination,
259
+ predicates: query.predicates,
260
+ relation: query.relation,
261
+ result: meta,
262
+ resultPath: void 0,
263
+ relatedHydrations: void 0,
264
+ scalarColumn: void 0,
265
+ scalarListColumn: void 0,
266
+ scalarListRows: void 0,
267
+ selections: query.selections
268
+ }));
269
+ return;
270
+ }
271
+ }
272
+ function rebindDatabaseQueryObservationCursorPagination(source, data, meta, pagination, belongsToHydrations, relatedHydrations) {
273
+ const state = databaseDependencyCollector.getStore();
274
+ if (!state) {
275
+ return;
276
+ }
277
+ for (let index = state.queries.length - 1; index >= 0; index -= 1) {
278
+ const query = state.queries[index];
279
+ if (!query || query.result !== source) {
280
+ continue;
281
+ }
282
+ state.queries[index] = Object.freeze({
283
+ aggregate: void 0,
284
+ belongsToHydrations: belongsToHydrations ?? query.belongsToHydrations,
285
+ connectionName: query.connectionName,
286
+ tableName: query.tableName,
287
+ cursorRowCount: pagination.rowCount,
288
+ cursorRows: pagination.rows,
289
+ dependencies: query.dependencies,
290
+ limit: pagination.perPage,
291
+ offset: void 0,
292
+ orderBy: query.orderBy,
293
+ patchable: query.patchable,
294
+ pagination: void 0,
295
+ predicates: query.predicates,
296
+ relation: query.relation,
297
+ result: data,
298
+ resultPath: query.resultPath,
299
+ relatedHydrations: relatedHydrations ?? query.relatedHydrations,
300
+ scalarColumn: void 0,
301
+ scalarListColumn: void 0,
302
+ scalarListRows: void 0,
303
+ selections: query.selections
304
+ });
305
+ state.queries.push(Object.freeze({
306
+ aggregate: void 0,
307
+ belongsToHydrations: void 0,
308
+ connectionName: query.connectionName,
309
+ tableName: query.tableName,
310
+ dependencies: query.dependencies,
311
+ limit: void 0,
312
+ offset: void 0,
313
+ orderBy: query.orderBy,
314
+ patchable: query.patchable,
315
+ pagination,
316
+ predicates: query.predicates,
317
+ relation: query.relation,
318
+ result: meta.nextCursor,
319
+ resultPath: Object.freeze(["nextCursor"]),
320
+ relatedHydrations: void 0,
321
+ scalarColumn: void 0,
322
+ scalarListColumn: void 0,
323
+ scalarListRows: void 0,
324
+ selections: query.selections
325
+ }));
326
+ return;
327
+ }
328
+ }
329
+ function disableDatabaseQueryObservationPatching(source) {
330
+ const state = databaseDependencyCollector.getStore();
331
+ if (!state) {
332
+ return;
333
+ }
334
+ for (let index = state.queries.length - 1; index >= 0; index -= 1) {
335
+ const query = state.queries[index];
336
+ if (!query || query.result !== source) {
337
+ continue;
338
+ }
339
+ state.queries[index] = Object.freeze({
340
+ aggregate: query.aggregate,
341
+ belongsToHydrations: query.belongsToHydrations,
342
+ connectionName: query.connectionName,
343
+ tableName: query.tableName,
344
+ dependencies: query.dependencies,
345
+ limit: query.limit,
346
+ offset: query.offset,
347
+ orderBy: query.orderBy,
348
+ patchable: false,
349
+ pagination: query.pagination,
350
+ predicates: query.predicates,
351
+ relation: query.relation,
352
+ result: query.result,
353
+ resultPath: query.resultPath,
354
+ relatedHydrations: query.relatedHydrations,
355
+ scalarColumn: query.scalarColumn,
356
+ scalarListColumn: query.scalarListColumn,
357
+ scalarListRows: query.scalarListRows,
358
+ selections: query.selections
359
+ });
360
+ return;
361
+ }
362
+ }
363
+ function rebindDatabaseQueryObservationAggregate(source, result, aggregate) {
364
+ rebindDatabaseQueryObservation(source, result, aggregate);
365
+ }
366
+ function rebindDatabaseQueryObservationScalar(source, result, column2) {
367
+ rebindDatabaseQueryObservation(source, result, void 0, column2);
368
+ }
369
+ function rebindDatabaseQueryObservationScalarList(rows, result, column2) {
370
+ rebindDatabaseQueryObservation(rows, result, void 0, void 0, column2, rows);
371
+ }
372
+ function rebindDatabaseQueryObservation(source, result, aggregate, scalarColumn, scalarListColumn, scalarListRows, belongsToHydrations, relatedHydrations) {
373
+ const state = databaseDependencyCollector.getStore();
374
+ if (!state) {
375
+ return;
376
+ }
377
+ for (let index = state.queries.length - 1; index >= 0; index -= 1) {
378
+ const query = state.queries[index];
379
+ if (!query || query.result !== source) {
380
+ continue;
381
+ }
382
+ state.queries[index] = Object.freeze({
383
+ aggregate,
384
+ belongsToHydrations,
385
+ connectionName: query.connectionName,
386
+ tableName: query.tableName,
387
+ dependencies: query.dependencies,
388
+ limit: query.limit,
389
+ offset: query.offset,
390
+ orderBy: query.orderBy,
391
+ patchable: query.patchable,
392
+ pagination: query.pagination,
393
+ predicates: query.predicates,
394
+ relation: query.relation,
395
+ result,
396
+ resultPath: query.resultPath,
397
+ relatedHydrations,
398
+ scalarColumn,
399
+ scalarListColumn,
400
+ scalarListRows,
401
+ selections: query.selections
402
+ });
403
+ return;
172
404
  }
173
405
  }
174
406
  function hasDatabaseDependencyInvalidationListeners() {
@@ -239,15 +471,173 @@ function resolveQueryCacheKey(statement, connectionName, config) {
239
471
  function createTableCacheDependency(connectionName, tableName) {
240
472
  return `db:${connectionName}:${tableName}`;
241
473
  }
474
+ function encodeDependencyValue(value) {
475
+ return encodeURIComponent(JSON.stringify(value));
476
+ }
477
+ function createPredicateMetadata(connectionName, tableName, columnName, value) {
478
+ if (typeof value === "undefined") {
479
+ return void 0;
480
+ }
481
+ return Object.freeze({
482
+ tableKey: createTableCacheDependency(connectionName, tableName),
483
+ columnName: getColumnName(columnName),
484
+ encodedValue: encodeDependencyValue(value)
485
+ });
486
+ }
487
+ function createPredicateDependencyFromMetadata(metadata, exact) {
488
+ return `${metadata.tableKey}:${exact ? WHERE_EXACT_DEPENDENCY_PREFIX : WHERE_DEPENDENCY_PREFIX}${metadata.columnName}:${metadata.encodedValue}`;
489
+ }
490
+ function createPredicateDependencyEntry(connectionName, tableName, columnName, value, exact = false) {
491
+ const metadata = createPredicateMetadata(connectionName, tableName, columnName, value);
492
+ if (!metadata) {
493
+ return void 0;
494
+ }
495
+ return {
496
+ dependency: createPredicateDependencyFromMetadata(metadata, exact),
497
+ metadata
498
+ };
499
+ }
500
+ function readExactPredicateValues(predicate) {
501
+ if (predicate.kind !== "comparison") {
502
+ return Object.freeze([]);
503
+ }
504
+ if (predicate.operator === "=") {
505
+ return Object.freeze([predicate.value]);
506
+ }
507
+ if (predicate.operator === "in" && Array.isArray(predicate.value)) {
508
+ return Object.freeze([...predicate.value]);
509
+ }
510
+ return Object.freeze([]);
511
+ }
242
512
  function createTableRowCacheDependency(connectionName, tableName, columnName, value) {
243
513
  if (typeof value === "undefined") {
244
514
  return void 0;
245
515
  }
246
- return `db:${connectionName}:${tableName}:row:${columnName}:${encodeURIComponent(JSON.stringify(value))}`;
516
+ return `db:${connectionName}:${tableName}:row:${columnName}:${encodeDependencyValue(value)}`;
247
517
  }
248
518
  function createTableRowWildcardCacheDependency(connectionName, tableName) {
249
519
  return `db:${connectionName}:${tableName}:row:*`;
250
520
  }
521
+ function createTablePredicateCacheDependency(connectionName, tableName, columnName, value) {
522
+ return createPredicateDependencyEntry(connectionName, tableName, columnName, value)?.dependency;
523
+ }
524
+ function createTableMutationCacheDependency(connectionName, tableName) {
525
+ return `db:${connectionName}:${tableName}:mutation`;
526
+ }
527
+ function createTableExactPredicateCacheDependency(connectionName, tableName, columnName, value) {
528
+ return createPredicateDependencyEntry(connectionName, tableName, columnName, value, true)?.dependency;
529
+ }
530
+ function parseDatabaseDependency(dependency) {
531
+ if (!dependency.startsWith(DATABASE_DEPENDENCY_PREFIX)) {
532
+ return void 0;
533
+ }
534
+ const connectionEnd = dependency.indexOf(":", DATABASE_DEPENDENCY_PREFIX.length);
535
+ if (connectionEnd <= DATABASE_DEPENDENCY_PREFIX.length) {
536
+ return void 0;
537
+ }
538
+ const tableStart = connectionEnd + 1;
539
+ const tableEnd = dependency.indexOf(":", tableStart);
540
+ if (tableEnd < 0) {
541
+ return tableStart === dependency.length ? void 0 : { tableKey: dependency };
542
+ }
543
+ if (tableEnd === tableStart) {
544
+ return void 0;
545
+ }
546
+ const suffixStart = tableEnd + 1;
547
+ return {
548
+ suffix: suffixStart === dependency.length ? "" : dependency.slice(suffixStart),
549
+ tableKey: dependency.slice(0, tableEnd)
550
+ };
551
+ }
552
+ function parsePredicateDependencySuffix(tableKey, suffix, prefix) {
553
+ if (!suffix.startsWith(prefix)) {
554
+ return void 0;
555
+ }
556
+ const columnStart = prefix.length;
557
+ const valueSeparator = suffix.indexOf(":", columnStart);
558
+ if (valueSeparator <= columnStart || valueSeparator === suffix.length - 1) {
559
+ return void 0;
560
+ }
561
+ return Object.freeze({
562
+ tableKey,
563
+ columnName: suffix.slice(columnStart, valueSeparator),
564
+ encodedValue: suffix.slice(valueSeparator + 1)
565
+ });
566
+ }
567
+ function createDatabaseDependencyInvalidationMetadata(dependencies, mutations) {
568
+ const directDependencies = [];
569
+ const exactPredicates = [];
570
+ const predicates = [];
571
+ const tableDependencyCandidates = [];
572
+ let hasMutationDependency = mutations.some((mutation) => mutation.kind === "upsert");
573
+ for (const dependency of dependencies) {
574
+ const parsed = parseDatabaseDependency(dependency);
575
+ if (!parsed) {
576
+ directDependencies.push(dependency);
577
+ continue;
578
+ }
579
+ if (parsed.suffix === void 0) {
580
+ tableDependencyCandidates.push(parsed.tableKey);
581
+ continue;
582
+ }
583
+ if (parsed.suffix === MUTATION_DEPENDENCY_SUFFIX) {
584
+ hasMutationDependency = true;
585
+ directDependencies.push(dependency);
586
+ continue;
587
+ }
588
+ const exactPredicate = parsePredicateDependencySuffix(
589
+ parsed.tableKey,
590
+ parsed.suffix,
591
+ WHERE_EXACT_DEPENDENCY_PREFIX
592
+ );
593
+ if (exactPredicate) {
594
+ exactPredicates.push(exactPredicate);
595
+ directDependencies.push(dependency);
596
+ continue;
597
+ }
598
+ const predicate = parsePredicateDependencySuffix(
599
+ parsed.tableKey,
600
+ parsed.suffix,
601
+ WHERE_DEPENDENCY_PREFIX
602
+ );
603
+ if (predicate) {
604
+ predicates.push(predicate);
605
+ directDependencies.push(dependency);
606
+ continue;
607
+ }
608
+ directDependencies.push(dependency);
609
+ }
610
+ const exactPredicateTables = new Set(exactPredicates.map((predicate) => predicate.tableKey));
611
+ const tableDependencies = [];
612
+ for (const tableKey of tableDependencyCandidates) {
613
+ if (exactPredicateTables.has(tableKey)) {
614
+ tableDependencies.push(tableKey);
615
+ continue;
616
+ }
617
+ directDependencies.push(tableKey);
618
+ }
619
+ return Object.freeze({
620
+ directDependencies: Object.freeze(directDependencies),
621
+ exactPredicates: Object.freeze(exactPredicates),
622
+ hasMutationDependency,
623
+ predicates: Object.freeze(predicates),
624
+ tableDependencies: Object.freeze(tableDependencies)
625
+ });
626
+ }
627
+ function createDatabaseDependencyInvalidationEvent(connectionName, dependencies, mutations, metadata) {
628
+ const event = {
629
+ connectionName,
630
+ dependencies,
631
+ mutations
632
+ };
633
+ Object.defineProperty(event, DATABASE_DEPENDENCY_METADATA_KEY, {
634
+ configurable: false,
635
+ enumerable: false,
636
+ value: metadata ?? createDatabaseDependencyInvalidationMetadata(dependencies, mutations),
637
+ writable: false
638
+ });
639
+ return Object.freeze(event);
640
+ }
251
641
  function normalizeQueryCacheDependencies(connectionName, dependencies) {
252
642
  return Object.freeze(dependencies.map((dependency) => {
253
643
  return dependency.startsWith("db:") ? dependency : createTableCacheDependency(connectionName, dependency);
@@ -309,8 +699,367 @@ function findExactPrimaryKeyValue(predicates, primaryKeyColumn) {
309
699
  }
310
700
  return void 0;
311
701
  }
702
+ function collectExactPredicateDependencies(predicates, connectionName, tableName, exact = false) {
703
+ const dependencies = /* @__PURE__ */ new Set();
704
+ for (const predicate of predicates) {
705
+ const values = readExactPredicateValues(predicate);
706
+ if (values.length > 0 && predicate.kind === "comparison") {
707
+ for (const value of values) {
708
+ const dependency = exact ? createTableExactPredicateCacheDependency(
709
+ connectionName,
710
+ tableName,
711
+ getColumnName(predicate.column),
712
+ value
713
+ ) : createTablePredicateCacheDependency(
714
+ connectionName,
715
+ tableName,
716
+ getColumnName(predicate.column),
717
+ value
718
+ );
719
+ if (dependency) {
720
+ dependencies.add(dependency);
721
+ }
722
+ }
723
+ continue;
724
+ }
725
+ if (predicate.kind === "group" && !predicate.negated && predicate.boolean !== "or") {
726
+ for (const dependency of collectExactPredicateDependencies(predicate.predicates, connectionName, tableName, exact)) {
727
+ dependencies.add(dependency);
728
+ }
729
+ }
730
+ }
731
+ return Object.freeze([...dependencies]);
732
+ }
733
+ function appendExactPredicateDependencyEntries(target, predicates, connectionName, tableName, exact = false) {
734
+ for (const predicate of predicates) {
735
+ const values = readExactPredicateValues(predicate);
736
+ if (values.length > 0 && predicate.kind === "comparison") {
737
+ for (const value of values) {
738
+ const entry = createPredicateDependencyEntry(
739
+ connectionName,
740
+ tableName,
741
+ getColumnName(predicate.column),
742
+ value,
743
+ exact
744
+ );
745
+ if (entry) {
746
+ target.set(entry.dependency, entry);
747
+ }
748
+ }
749
+ continue;
750
+ }
751
+ if (predicate.kind === "group" && !predicate.negated && predicate.boolean !== "or") {
752
+ appendExactPredicateDependencyEntries(target, predicate.predicates, connectionName, tableName, exact);
753
+ }
754
+ }
755
+ }
756
+ function collectRecordPredicateDependencies(connectionName, tableName, record, exact = false) {
757
+ const dependencies = /* @__PURE__ */ new Set();
758
+ for (const [columnName, value] of Object.entries(record)) {
759
+ const dependency = exact ? createTableExactPredicateCacheDependency(connectionName, tableName, getColumnName(columnName), value) : createTablePredicateCacheDependency(connectionName, tableName, getColumnName(columnName), value);
760
+ if (dependency) {
761
+ dependencies.add(dependency);
762
+ }
763
+ }
764
+ return Object.freeze([...dependencies]);
765
+ }
766
+ function appendRecordPredicateDependencyEntries(target, connectionName, tableName, record, exact = false) {
767
+ for (const [columnName, value] of Object.entries(record)) {
768
+ const entry = createPredicateDependencyEntry(connectionName, tableName, getColumnName(columnName), value, exact);
769
+ if (entry) {
770
+ target.set(entry.dependency, entry);
771
+ }
772
+ }
773
+ }
774
+ function createInvalidationMetadataFromParts(directDependencies, exactPredicates, predicates, tableDependencyCandidates, hasMutationDependency) {
775
+ const exactPredicateTables = new Set(exactPredicates.map((predicate) => predicate.tableKey));
776
+ const nextDirectDependencies = [...directDependencies];
777
+ const tableDependencies = [];
778
+ for (const tableKey of tableDependencyCandidates) {
779
+ if (exactPredicateTables.has(tableKey)) {
780
+ tableDependencies.push(tableKey);
781
+ continue;
782
+ }
783
+ nextDirectDependencies.push(tableKey);
784
+ }
785
+ return Object.freeze({
786
+ directDependencies: Object.freeze(nextDirectDependencies),
787
+ exactPredicates: Object.freeze([...exactPredicates]),
788
+ hasMutationDependency,
789
+ predicates: Object.freeze([...predicates]),
790
+ tableDependencies: Object.freeze(tableDependencies)
791
+ });
792
+ }
793
+ function collectQueryPredicateObservations(predicates) {
794
+ const observations = [];
795
+ for (const predicate of predicates) {
796
+ if (predicate.kind === "comparison") {
797
+ observations.push(Object.freeze({
798
+ column: getColumnName(predicate.column),
799
+ operator: predicate.operator,
800
+ value: predicate.value
801
+ }));
802
+ continue;
803
+ }
804
+ if (predicate.kind === "group" && !predicate.negated && predicate.boolean !== "or") {
805
+ observations.push(...collectQueryPredicateObservations(predicate.predicates));
806
+ }
807
+ }
808
+ return Object.freeze(observations);
809
+ }
810
+ function collectQueryOrderObservations(plan) {
811
+ return Object.freeze(plan.orderBy.flatMap((order) => {
812
+ if (order.kind !== "column") {
813
+ return [];
814
+ }
815
+ return [Object.freeze({
816
+ column: getColumnName(order.column),
817
+ direction: order.direction
818
+ })];
819
+ }));
820
+ }
821
+ function collectQuerySelectionObservations(plan) {
822
+ return Object.freeze(plan.selections.flatMap((selection) => {
823
+ if (selection.kind !== "column") {
824
+ return [];
825
+ }
826
+ const column2 = getColumnName(selection.column);
827
+ return [Object.freeze({
828
+ column: column2,
829
+ resultKey: selection.alias ?? column2
830
+ })];
831
+ }));
832
+ }
833
+ function isPatchablePredicateObservation(predicate) {
834
+ if (predicate.kind === "comparison" && predicate.boolean !== "or") {
835
+ return true;
836
+ }
837
+ if (predicate.kind !== "group" || predicate.negated || predicate.boolean === "or") {
838
+ return false;
839
+ }
840
+ return predicate.predicates.every((child) => isPatchablePredicateObservation(child));
841
+ }
842
+ function isColumnSelection(selection) {
843
+ return selection.kind === "column";
844
+ }
845
+ function hasRequiredProjectionSelection(selections, column2) {
846
+ return selections.some((selection) => selection.column === column2 && selection.resultKey === column2);
847
+ }
848
+ function isExactPrimaryKeySingleResultProjection(plan) {
849
+ return plan.limit === 1 && typeof findExactPrimaryKeyValue(plan.predicates, getPrimaryKeyColumn(plan)) !== "undefined";
850
+ }
851
+ function isPatchableProjectionObservation(plan, selections, predicates, orderBy) {
852
+ if (plan.selections.length === 0) {
853
+ return true;
854
+ }
855
+ if (plan.selections.every((selection) => selection.kind === "aggregate")) {
856
+ return true;
857
+ }
858
+ if (!plan.selections.every((selection) => isColumnSelection(selection))) {
859
+ return false;
860
+ }
861
+ const needsProjectedPrimaryKey = !isExactPrimaryKeySingleResultProjection(plan);
862
+ const requiredColumns = /* @__PURE__ */ new Set([
863
+ ...orderBy.map((order) => order.column)
864
+ ]);
865
+ if (needsProjectedPrimaryKey) {
866
+ requiredColumns.add(getPrimaryKeyColumn(plan));
867
+ }
868
+ for (const column2 of requiredColumns) {
869
+ if (!hasRequiredProjectionSelection(selections, column2)) {
870
+ return false;
871
+ }
872
+ }
873
+ return true;
874
+ }
875
+ function isPatchableOffsetObservation(plan, orderBy) {
876
+ return typeof plan.offset === "undefined" || typeof plan.limit === "number" && plan.offset > 0 && orderBy.length > 0;
877
+ }
878
+ function findGroupedAggregateObservation(plan, averageStates, aggregateStates) {
879
+ if (plan.groupBy.length !== 1 || plan.selections.length !== 2) {
880
+ return void 0;
881
+ }
882
+ const groupColumn = getColumnName(plan.groupBy[0]);
883
+ const groupSelection = plan.selections.find((selection) => {
884
+ return selection.kind === "column" && getColumnName(selection.column) === groupColumn;
885
+ });
886
+ const aggregateSelection = plan.selections.find((selection) => {
887
+ return selection.kind === "aggregate" && (selection.aggregate === "count" && selection.column === "*" || selection.aggregate === "avg" && selection.column !== "*" || selection.aggregate === "sum" && selection.column !== "*" || selection.aggregate === "min" && selection.column !== "*" || selection.aggregate === "max" && selection.column !== "*");
888
+ });
889
+ if (!groupSelection || !aggregateSelection) {
890
+ return void 0;
891
+ }
892
+ const orderBy = collectQueryOrderObservations(plan);
893
+ if (orderBy.some((order) => order.column !== groupColumn)) {
894
+ return void 0;
895
+ }
896
+ const aggregateKind = aggregateSelection.aggregate === "count" ? "count" : aggregateSelection.aggregate;
897
+ const having = readGroupedCountHavingObservation(plan);
898
+ if (plan.having.length > 0 && !isSupportedGroupedCountHaving(plan)) {
899
+ return void 0;
900
+ }
901
+ const groupedAggregate = {
902
+ aggregateColumn: aggregateKind === "count" ? void 0 : getColumnName(aggregateSelection.column),
903
+ aggregateResultKey: aggregateSelection.alias,
904
+ ...(aggregateKind === "count" || aggregateKind === "sum" || aggregateKind === "min" || aggregateKind === "max") && aggregateStates ? { aggregateStates: Object.freeze([...aggregateStates]) } : {},
905
+ ...aggregateKind === "avg" && averageStates ? { averageStates: Object.freeze([...averageStates]) } : {},
906
+ groupColumn,
907
+ groupResultKey: groupSelection.alias ?? groupColumn,
908
+ having,
909
+ kind: aggregateKind
910
+ };
911
+ return Object.freeze(groupedAggregate);
912
+ }
913
+ function isSupportedGroupedCountHaving(plan) {
914
+ if (plan.having.length === 0) {
915
+ return true;
916
+ }
917
+ if (plan.groupBy.length === 0 || plan.having.length !== 1) {
918
+ return false;
919
+ }
920
+ const clause = plan.having[0];
921
+ return typeof clause !== "undefined" && clause.expression.replace(/\s+/g, "").toLowerCase() === "count(*)" && typeof clause.value === "number" && Number.isFinite(clause.value) && isGroupedCountHavingOperator(clause.operator);
922
+ }
923
+ function readGroupedCountHavingObservation(plan) {
924
+ if (!isSupportedGroupedCountHaving(plan) || hasOnlyRedundantGroupedCountHaving(plan)) {
925
+ return void 0;
926
+ }
927
+ const clause = plan.having[0];
928
+ if (!clause || !isGroupedCountHavingOperator(clause.operator) || typeof clause.value !== "number" || !Number.isFinite(clause.value)) {
929
+ return void 0;
930
+ }
931
+ return Object.freeze({
932
+ operator: clause.operator,
933
+ value: clause.value
934
+ });
935
+ }
936
+ function isGroupedCountHavingOperator(operator) {
937
+ return operator === "<" || operator === "<=" || operator === "=" || operator === ">" || operator === ">=";
938
+ }
939
+ function hasOnlyRedundantGroupedCountHaving(plan) {
940
+ if (plan.having.length === 0) {
941
+ return true;
942
+ }
943
+ if (plan.groupBy.length === 0 || plan.having.length !== 1) {
944
+ return false;
945
+ }
946
+ const clause = plan.having[0];
947
+ return typeof clause !== "undefined" && clause.expression.replace(/\s+/g, "").toLowerCase() === "count(*)" && clause.operator === ">=" && clause.value === 1;
948
+ }
949
+ function isPatchableGroupedAggregateObservation(plan) {
950
+ return supportsAutomaticQueryCacheInvalidation(plan) && Boolean(findGroupedAggregateObservation(plan)) && !plan.distinct && !plan.lockMode && !plan.source.alias && plan.joins.length === 0 && typeof plan.limit === "undefined" && typeof plan.offset === "undefined" && plan.unions.length === 0 && plan.orderBy.every((order) => order.kind === "column") && plan.predicates.every((predicate) => isPatchablePredicateObservation(predicate));
951
+ }
952
+ function isPatchableQueryObservation(plan) {
953
+ const predicates = collectQueryPredicateObservations(plan.predicates);
954
+ const orderBy = collectQueryOrderObservations(plan);
955
+ const selections = collectQuerySelectionObservations(plan);
956
+ return isPatchableGroupedAggregateObservation(plan) || supportsAutomaticQueryCacheInvalidation(plan) && !plan.distinct && !plan.lockMode && !plan.source.alias && plan.groupBy.length === 0 && plan.having.length === 0 && plan.joins.length === 0 && isPatchableOffsetObservation(plan, orderBy) && isPatchableProjectionObservation(plan, selections, predicates, orderBy) && plan.unions.length === 0 && plan.orderBy.every((order) => order.kind === "column") && plan.predicates.every((predicate) => isPatchablePredicateObservation(predicate));
957
+ }
958
+ function appendObservableQueryDependencies(plan, connectionName, dependencies, seen) {
959
+ if (seen.has(plan)) {
960
+ return;
961
+ }
962
+ seen.add(plan);
963
+ dependencies.add(createTableCacheDependency(connectionName, plan.source.tableName));
964
+ for (const join of plan.joins) {
965
+ if (join.table) {
966
+ dependencies.add(createTableCacheDependency(connectionName, join.table));
967
+ }
968
+ if (join.subquery) {
969
+ appendObservableQueryDependencies(join.subquery, connectionName, dependencies, seen);
970
+ }
971
+ }
972
+ for (const union of plan.unions) {
973
+ appendObservableQueryDependencies(union.query, connectionName, dependencies, seen);
974
+ }
975
+ for (const selection of plan.selections) {
976
+ if (selection.kind === "subquery") {
977
+ appendObservableQueryDependencies(selection.query, connectionName, dependencies, seen);
978
+ }
979
+ }
980
+ for (const predicate of plan.predicates) {
981
+ appendObservablePredicateDependencies(predicate, connectionName, dependencies, seen);
982
+ }
983
+ }
984
+ function appendObservablePredicateDependencies(predicate, connectionName, dependencies, seen) {
985
+ if (predicate.kind === "group") {
986
+ for (const child of predicate.predicates) {
987
+ appendObservablePredicateDependencies(child, connectionName, dependencies, seen);
988
+ }
989
+ return;
990
+ }
991
+ if (predicate.kind === "exists" || predicate.kind === "subquery") {
992
+ appendObservableQueryDependencies(predicate.subquery, connectionName, dependencies, seen);
993
+ }
994
+ }
995
+ function hasObservationDependencyFallbackPredicate(predicate) {
996
+ if (predicate.kind === "group") {
997
+ return predicate.predicates.some((child) => hasObservationDependencyFallbackPredicate(child));
998
+ }
999
+ return predicate.kind === "exists" || predicate.kind === "subquery" || predicate.kind === "raw" || predicate.kind === "vector" || predicate.kind === "fulltext";
1000
+ }
1001
+ function hasObservationDependencyFallbackShape(plan) {
1002
+ return plan.joins.length > 0 || plan.unions.length > 0 || !isSupportedGroupedCountHaving(plan) || plan.selections.some((selection) => selection.kind === "raw" || selection.kind === "subquery") || plan.orderBy.some((order) => order.kind === "raw") || plan.predicates.some((predicate) => hasObservationDependencyFallbackPredicate(predicate));
1003
+ }
1004
+ function inferDatabaseQueryObservationDependencies(plan, connectionName) {
1005
+ if (!hasObservationDependencyFallbackShape(plan)) {
1006
+ return void 0;
1007
+ }
1008
+ const dependencies = /* @__PURE__ */ new Set();
1009
+ appendObservableQueryDependencies(plan, connectionName, dependencies, /* @__PURE__ */ new Set());
1010
+ return Object.freeze([...dependencies]);
1011
+ }
1012
+ function createDatabaseQueryObservationFromPlan(plan, connectionName, dependencies, result, patchable = isPatchableQueryObservation(plan), groupedAverageStates, groupedAggregateStates) {
1013
+ return Object.freeze({
1014
+ connectionName,
1015
+ belongsToHydrations: void 0,
1016
+ relatedHydrations: void 0,
1017
+ tableName: plan.source.tableName,
1018
+ dependencies: Object.freeze([...dependencies]),
1019
+ groupedAggregate: findGroupedAggregateObservation(plan, groupedAverageStates, groupedAggregateStates),
1020
+ limit: plan.limit,
1021
+ offset: plan.offset,
1022
+ orderBy: collectQueryOrderObservations(plan),
1023
+ patchable,
1024
+ pagination: void 0,
1025
+ predicates: collectQueryPredicateObservations(plan.predicates),
1026
+ result,
1027
+ selections: collectQuerySelectionObservations(plan)
1028
+ });
1029
+ }
1030
+ function createDatabaseQueryObservation(plan, connectionName, dependencies, result, groupedAverageStates, groupedAggregateStates) {
1031
+ if (!supportsAutomaticQueryCacheInvalidation(plan)) {
1032
+ return void 0;
1033
+ }
1034
+ return createDatabaseQueryObservationFromPlan(
1035
+ plan,
1036
+ connectionName,
1037
+ dependencies,
1038
+ result,
1039
+ void 0,
1040
+ groupedAverageStates,
1041
+ groupedAggregateStates
1042
+ );
1043
+ }
1044
+ function createDatabaseQueryFallbackObservation(plan, connectionName, dependencies, result) {
1045
+ if (dependencies.length === 0) {
1046
+ return void 0;
1047
+ }
1048
+ return createDatabaseQueryObservationFromPlan(plan, connectionName, dependencies, result, false);
1049
+ }
1050
+ function createDatabaseMutationEvent(kind, connectionName, tableName, predicates = [], values, rows, previousRows) {
1051
+ return Object.freeze({
1052
+ connectionName,
1053
+ tableName,
1054
+ kind,
1055
+ predicates: collectQueryPredicateObservations(predicates),
1056
+ previousRows: previousRows ? Object.freeze([...previousRows]) : void 0,
1057
+ rows: rows ? Object.freeze([...rows]) : void 0,
1058
+ values
1059
+ });
1060
+ }
312
1061
  function supportsAutomaticQueryCacheInvalidation(plan) {
313
- if (plan.joins.length > 0 || plan.unions.length > 0 || plan.having.length > 0) {
1062
+ if (plan.joins.length > 0 || plan.unions.length > 0 || !isSupportedGroupedCountHaving(plan)) {
314
1063
  return false;
315
1064
  }
316
1065
  if (plan.selections.some((selection) => selection.kind === "raw" || selection.kind === "subquery")) {
@@ -341,18 +1090,48 @@ function inferAutomaticQueryCacheDependencies(plan, connectionName) {
341
1090
  ]);
342
1091
  }
343
1092
  }
1093
+ if (!hasDisjunctivePredicate(plan.predicates)) {
1094
+ const predicateDependencies = collectExactPredicateDependencies(
1095
+ plan.predicates,
1096
+ connectionName,
1097
+ plan.source.tableName
1098
+ );
1099
+ if (predicateDependencies.length > 0) {
1100
+ return Object.freeze([
1101
+ createTableCacheDependency(connectionName, plan.source.tableName),
1102
+ ...predicateDependencies
1103
+ ]);
1104
+ }
1105
+ }
344
1106
  return Object.freeze([
345
1107
  createTableCacheDependency(connectionName, plan.source.tableName)
346
1108
  ]);
347
1109
  }
348
- function inferAutomaticQueryCacheInvalidationDependencies(plan, connectionName) {
349
- const dependencies = [
350
- createTableCacheDependency(connectionName, plan.source.tableName)
351
- ];
1110
+ function inferAutomaticQueryCacheInvalidationDependencies(plan, connectionName, values = {}) {
1111
+ return inferAutomaticQueryCacheInvalidationPlan(plan, connectionName, values).dependencies;
1112
+ }
1113
+ function inferAutomaticQueryCacheInvalidationPlan(plan, connectionName, values = {}, includeMetadata = false) {
1114
+ const tableKey = createTableCacheDependency(connectionName, plan.source.tableName);
1115
+ const dependencies = [tableKey];
352
1116
  if (hasDisjunctivePredicate(plan.predicates)) {
353
1117
  dependencies.push(createTableRowWildcardCacheDependency(connectionName, plan.source.tableName));
354
- return Object.freeze(dependencies);
1118
+ return Object.freeze({
1119
+ dependencies: Object.freeze(dependencies),
1120
+ metadata: includeMetadata ? createInvalidationMetadataFromParts(
1121
+ dependencies,
1122
+ Object.freeze([]),
1123
+ Object.freeze([]),
1124
+ Object.freeze([]),
1125
+ false
1126
+ ) : void 0
1127
+ });
355
1128
  }
1129
+ const directDependencies = [];
1130
+ const exactPredicates = [];
1131
+ const predicates = [];
1132
+ const mutationDependency = createTableMutationCacheDependency(connectionName, plan.source.tableName);
1133
+ dependencies.push(mutationDependency);
1134
+ directDependencies.push(mutationDependency);
356
1135
  const primaryKeyColumn = getPrimaryKeyColumn(plan);
357
1136
  const primaryKeyValue = findExactPrimaryKeyValue(plan.predicates, primaryKeyColumn);
358
1137
  const primaryKeyDependency = createTableRowCacheDependency(
@@ -361,27 +1140,118 @@ function inferAutomaticQueryCacheInvalidationDependencies(plan, connectionName)
361
1140
  primaryKeyColumn,
362
1141
  primaryKeyValue
363
1142
  );
364
- dependencies.push(primaryKeyDependency ?? createTableRowWildcardCacheDependency(connectionName, plan.source.tableName));
365
- return Object.freeze(dependencies);
1143
+ const rowDependency = primaryKeyDependency ?? createTableRowWildcardCacheDependency(connectionName, plan.source.tableName);
1144
+ dependencies.push(rowDependency);
1145
+ directDependencies.push(rowDependency);
1146
+ if (includeMetadata) {
1147
+ const predicateEntries = /* @__PURE__ */ new Map();
1148
+ appendExactPredicateDependencyEntries(predicateEntries, plan.predicates, connectionName, plan.source.tableName);
1149
+ for (const entry of predicateEntries.values()) {
1150
+ dependencies.push(entry.dependency);
1151
+ directDependencies.push(entry.dependency);
1152
+ predicates.push(entry.metadata);
1153
+ }
1154
+ const exactPredicateEntries = /* @__PURE__ */ new Map();
1155
+ appendExactPredicateDependencyEntries(exactPredicateEntries, plan.predicates, connectionName, plan.source.tableName, true);
1156
+ for (const entry of exactPredicateEntries.values()) {
1157
+ dependencies.push(entry.dependency);
1158
+ directDependencies.push(entry.dependency);
1159
+ exactPredicates.push(entry.metadata);
1160
+ }
1161
+ const recordPredicateEntries = /* @__PURE__ */ new Map();
1162
+ appendRecordPredicateDependencyEntries(recordPredicateEntries, connectionName, plan.source.tableName, values);
1163
+ for (const entry of recordPredicateEntries.values()) {
1164
+ dependencies.push(entry.dependency);
1165
+ directDependencies.push(entry.dependency);
1166
+ predicates.push(entry.metadata);
1167
+ }
1168
+ const exactRecordPredicateEntries = /* @__PURE__ */ new Map();
1169
+ appendRecordPredicateDependencyEntries(exactRecordPredicateEntries, connectionName, plan.source.tableName, values, true);
1170
+ for (const entry of exactRecordPredicateEntries.values()) {
1171
+ dependencies.push(entry.dependency);
1172
+ directDependencies.push(entry.dependency);
1173
+ exactPredicates.push(entry.metadata);
1174
+ }
1175
+ return Object.freeze({
1176
+ dependencies: Object.freeze(dependencies),
1177
+ metadata: createInvalidationMetadataFromParts(
1178
+ directDependencies,
1179
+ exactPredicates,
1180
+ predicates,
1181
+ Object.freeze([tableKey]),
1182
+ true
1183
+ )
1184
+ });
1185
+ }
1186
+ dependencies.push(...collectExactPredicateDependencies(plan.predicates, connectionName, plan.source.tableName));
1187
+ dependencies.push(...collectExactPredicateDependencies(plan.predicates, connectionName, plan.source.tableName, true));
1188
+ dependencies.push(...collectRecordPredicateDependencies(connectionName, plan.source.tableName, values));
1189
+ dependencies.push(...collectRecordPredicateDependencies(connectionName, plan.source.tableName, values, true));
1190
+ return Object.freeze({
1191
+ dependencies: Object.freeze(dependencies)
1192
+ });
366
1193
  }
367
1194
  function inferAutomaticInsertCacheInvalidationDependencies(connectionName, tableName, rows, lastInsertId) {
1195
+ return inferAutomaticInsertCacheInvalidationPlan(connectionName, tableName, rows, lastInsertId).dependencies;
1196
+ }
1197
+ function inferAutomaticInsertCacheInvalidationPlan(connectionName, tableName, rows, lastInsertId, includeMetadata = false) {
1198
+ const tableKey = createTableCacheDependency(connectionName, tableName);
368
1199
  const dependencies = /* @__PURE__ */ new Set([
369
- createTableCacheDependency(connectionName, tableName)
1200
+ tableKey
370
1201
  ]);
1202
+ const directDependencies = /* @__PURE__ */ new Set();
1203
+ const exactPredicates = /* @__PURE__ */ new Map();
1204
+ const predicates = /* @__PURE__ */ new Map();
371
1205
  for (const row of rows) {
372
1206
  const dependency = createTableRowCacheDependency(connectionName, tableName, "id", row.id);
373
1207
  if (dependency) {
374
1208
  dependencies.add(dependency);
1209
+ directDependencies.add(dependency);
1210
+ }
1211
+ if (includeMetadata) {
1212
+ const rowPredicateEntries = /* @__PURE__ */ new Map();
1213
+ appendRecordPredicateDependencyEntries(rowPredicateEntries, connectionName, tableName, row);
1214
+ for (const entry of rowPredicateEntries.values()) {
1215
+ dependencies.add(entry.dependency);
1216
+ directDependencies.add(entry.dependency);
1217
+ predicates.set(entry.dependency, entry);
1218
+ }
1219
+ const rowExactPredicateEntries = /* @__PURE__ */ new Map();
1220
+ appendRecordPredicateDependencyEntries(rowExactPredicateEntries, connectionName, tableName, row, true);
1221
+ for (const entry of rowExactPredicateEntries.values()) {
1222
+ dependencies.add(entry.dependency);
1223
+ directDependencies.add(entry.dependency);
1224
+ exactPredicates.set(entry.dependency, entry);
1225
+ }
1226
+ continue;
1227
+ }
1228
+ for (const dependency2 of collectRecordPredicateDependencies(connectionName, tableName, row)) {
1229
+ dependencies.add(dependency2);
1230
+ }
1231
+ for (const dependency2 of collectRecordPredicateDependencies(connectionName, tableName, row, true)) {
1232
+ dependencies.add(dependency2);
375
1233
  }
376
1234
  }
377
1235
  const lastInsertDependency = createTableRowCacheDependency(connectionName, tableName, "id", lastInsertId);
378
1236
  if (lastInsertDependency) {
379
1237
  dependencies.add(lastInsertDependency);
1238
+ directDependencies.add(lastInsertDependency);
380
1239
  }
381
1240
  if (dependencies.size === 1) {
382
- dependencies.add(createTableRowWildcardCacheDependency(connectionName, tableName));
1241
+ const rowWildcardDependency = createTableRowWildcardCacheDependency(connectionName, tableName);
1242
+ dependencies.add(rowWildcardDependency);
1243
+ directDependencies.add(rowWildcardDependency);
383
1244
  }
384
- return Object.freeze([...dependencies]);
1245
+ return Object.freeze({
1246
+ dependencies: Object.freeze([...dependencies]),
1247
+ metadata: includeMetadata ? createInvalidationMetadataFromParts(
1248
+ [...directDependencies],
1249
+ [...exactPredicates.values()].map((entry) => entry.metadata),
1250
+ [...predicates.values()].map((entry) => entry.metadata),
1251
+ Object.freeze([tableKey]),
1252
+ false
1253
+ ) : void 0
1254
+ });
385
1255
  }
386
1256
  function resolveQueryCacheDependencies(plan, connectionName, explicit) {
387
1257
  if (explicit && explicit.length > 0) {
@@ -389,17 +1259,18 @@ function resolveQueryCacheDependencies(plan, connectionName, explicit) {
389
1259
  }
390
1260
  return inferAutomaticQueryCacheDependencies(plan, connectionName);
391
1261
  }
392
- async function invalidateQueryCacheDependencies(connection, dependencies) {
1262
+ async function invalidateQueryCacheDependencies(connection, dependencies, mutations = [], plan) {
393
1263
  if (dependencies.length === 0) {
394
1264
  return;
395
1265
  }
396
1266
  const invalidate = async () => {
397
1267
  const bridge = getDatabaseQueryCacheBridge();
398
1268
  await bridge?.invalidateDependencies(dependencies);
399
- await notifyDatabaseDependencyInvalidationListeners({
400
- connectionName: connection.getConnectionName(),
401
- dependencies
402
- });
1269
+ if (hasDatabaseDependencyInvalidationListeners()) {
1270
+ await notifyDatabaseDependencyInvalidationListeners(
1271
+ createDatabaseDependencyInvalidationEvent(connection.getConnectionName(), dependencies, mutations, plan?.metadata)
1272
+ );
1273
+ }
403
1274
  };
404
1275
  if (connection.getScope().kind === "root") {
405
1276
  await invalidate();
@@ -408,9 +1279,12 @@ async function invalidateQueryCacheDependencies(connection, dependencies) {
408
1279
  connection.afterCommit(invalidate);
409
1280
  }
410
1281
  var queryCacheInternals = {
411
- collectDatabaseQueryDependencies,
1282
+ collectDatabaseQueryDependencies: collectDatabaseQueryDependenciesInternal,
412
1283
  configureDatabaseQueryCacheBridge,
1284
+ createDatabaseMutationEvent,
1285
+ createDatabaseQueryObservation,
413
1286
  createDeterministicQueryCacheKey,
1287
+ createTablePredicateCacheDependency,
414
1288
  createTableCacheDependency,
415
1289
  createTableRowCacheDependency,
416
1290
  createTableRowWildcardCacheDependency,
@@ -418,13 +1292,20 @@ var queryCacheInternals = {
418
1292
  hasActiveDatabaseDependencyCollector,
419
1293
  hasDatabaseDependencyInvalidationListeners,
420
1294
  inferAutomaticInsertCacheInvalidationDependencies,
1295
+ inferAutomaticInsertCacheInvalidationPlan,
421
1296
  inferAutomaticQueryCacheDependencies,
422
1297
  inferAutomaticQueryCacheInvalidationDependencies,
1298
+ inferAutomaticQueryCacheInvalidationPlan,
423
1299
  normalizeQueryCacheConfig,
424
1300
  normalizeQueryCacheDependencies,
425
1301
  notifyDatabaseDependencyInvalidationListeners,
1302
+ disableDatabaseQueryObservationPatching,
1303
+ rebindDatabaseQueryObservationResult,
1304
+ rebindDatabaseQueryObservationScalar,
1305
+ rebindDatabaseQueryObservationScalarList,
426
1306
  onDatabaseDependencyInvalidated,
427
1307
  recordDatabaseQueryDependencies,
1308
+ recordDatabaseQueryObservation,
428
1309
  resolveQueryCacheDependencies,
429
1310
  resolveQueryCacheKey,
430
1311
  resetDatabaseDependencyInvalidationListeners,
@@ -800,34 +1681,47 @@ function createInsertQueryPlan(source, values, options = {}) {
800
1681
  kind: "insert",
801
1682
  source,
802
1683
  ignoreConflicts: options.ignoreConflicts ?? false,
1684
+ returning: options.returning === true,
803
1685
  values: Object.freeze(values.map((value) => Object.freeze({ ...value })))
804
1686
  });
805
1687
  }
806
- function createUpsertQueryPlan(source, values, uniqueBy, updateColumns) {
1688
+ function createUpsertQueryPlan(source, values, uniqueBy, updateColumns, options = {}) {
807
1689
  return Object.freeze({
808
1690
  kind: "upsert",
809
1691
  source,
1692
+ returning: options.returning === true,
810
1693
  values: Object.freeze(values.map((value) => Object.freeze({ ...value }))),
811
1694
  uniqueBy: Object.freeze([...uniqueBy]),
812
1695
  updateColumns: Object.freeze([...updateColumns])
813
1696
  });
814
1697
  }
815
- function createUpdateQueryPlan(source, predicates, values) {
1698
+ function createUpdateQueryPlan(source, predicates, values, options = {}) {
816
1699
  return Object.freeze({
817
1700
  kind: "update",
818
1701
  source,
819
1702
  predicates: Object.freeze([...predicates]),
1703
+ returning: options.returning === true,
820
1704
  values: Object.freeze({ ...values })
821
1705
  });
822
1706
  }
823
- function createDeleteQueryPlan(source, predicates) {
1707
+ function createDeleteQueryPlan(source, predicates, options = {}) {
824
1708
  return Object.freeze({
825
1709
  kind: "delete",
826
1710
  source,
827
- predicates: Object.freeze([...predicates])
1711
+ predicates: Object.freeze([...predicates]),
1712
+ returning: options.returning === true
828
1713
  });
829
1714
  }
830
1715
 
1716
+ // src/query/aggregateValueCounts.ts
1717
+ function createAggregateValueCounts(values) {
1718
+ const counts = /* @__PURE__ */ new Map();
1719
+ for (const value of values) {
1720
+ counts.set(value, (counts.get(value) ?? 0) + 1);
1721
+ }
1722
+ return Object.freeze([...counts.entries()].sort(([left], [right]) => left - right).map(([value, count]) => Object.freeze({ count, value })));
1723
+ }
1724
+
831
1725
  // src/query/validator.ts
832
1726
  var IDENTIFIER_PATTERN = /^[A-Z_]\w*$/i;
833
1727
  var OPERATORS = /* @__PURE__ */ new Set([
@@ -1464,8 +2358,9 @@ var SQLQueryCompiler = class {
1464
2358
  return `${this.quoteIdentifier(column2)} = ${this.compileUpdateValue(column2, plan.values[column2], bindings)}`;
1465
2359
  }).join(", ");
1466
2360
  const whereClause = this.compilePredicates(plan.predicates, bindings);
2361
+ const returningClause = this.compileReturningClause(plan.returning === true);
1467
2362
  return this.withMetadata({
1468
- sql: `UPDATE ${this.quoteIdentifier(plan.source.tableName)} SET ${assignments}${whereClause}`,
2363
+ sql: `UPDATE ${this.quoteIdentifier(plan.source.tableName)} SET ${assignments}${whereClause}${returningClause}`,
1469
2364
  bindings,
1470
2365
  source: `query:update:${plan.source.tableName}`
1471
2366
  }, this.createWriteMetadata("update", plan.source.tableName, this.calculatePlanComplexity(plan)));
@@ -1473,8 +2368,9 @@ var SQLQueryCompiler = class {
1473
2368
  compileDelete(plan) {
1474
2369
  const bindings = [];
1475
2370
  const whereClause = this.compilePredicates(plan.predicates, bindings);
2371
+ const returningClause = this.compileReturningClause(plan.returning === true);
1476
2372
  return this.withMetadata({
1477
- sql: `DELETE FROM ${this.quoteIdentifier(plan.source.tableName)}${whereClause}`,
2373
+ sql: `DELETE FROM ${this.quoteIdentifier(plan.source.tableName)}${whereClause}${returningClause}`,
1478
2374
  bindings,
1479
2375
  source: `query:delete:${plan.source.tableName}`
1480
2376
  }, this.createWriteMetadata("delete", plan.source.tableName, this.calculatePlanComplexity(plan)));
@@ -1751,15 +2647,19 @@ var SQLQueryCompiler = class {
1751
2647
  return "INSERT INTO";
1752
2648
  }
1753
2649
  compileInsertSuffix(_plan) {
1754
- return "";
2650
+ return _plan.returning === true ? " RETURNING *" : "";
1755
2651
  }
1756
2652
  compileUpsertSuffix(plan, _insertColumns) {
1757
2653
  const conflictColumns = plan.uniqueBy.map((column2) => this.quoteIdentifier(column2)).join(", ");
2654
+ const returning = plan.returning === true ? " RETURNING *" : "";
1758
2655
  if (plan.updateColumns.length === 0) {
1759
- return ` ON CONFLICT (${conflictColumns}) DO NOTHING`;
2656
+ return ` ON CONFLICT (${conflictColumns}) DO NOTHING${returning}`;
1760
2657
  }
1761
2658
  const updates = plan.updateColumns.map((column2) => `${this.quoteIdentifier(column2)} = EXCLUDED.${this.quoteIdentifier(column2)}`).join(", ");
1762
- return ` ON CONFLICT (${conflictColumns}) DO UPDATE SET ${updates}`;
2659
+ return ` ON CONFLICT (${conflictColumns}) DO UPDATE SET ${updates}${returning}`;
2660
+ }
2661
+ compileReturningClause(returning) {
2662
+ return returning ? " RETURNING *" : "";
1763
2663
  }
1764
2664
  compileHavingExpression(expression) {
1765
2665
  const aggregateMatch = expression.match(/^(count|sum|avg|min|max)\((\*|[A-Z_]\w*)\)$/i);
@@ -1941,12 +2841,18 @@ var PostgresQueryCompiler = class extends SQLQueryCompiler {
1941
2841
  return lockMode === "update" ? "FOR UPDATE" : "FOR SHARE";
1942
2842
  }
1943
2843
  compileInsertSuffix(plan) {
2844
+ if (plan.returning === true) {
2845
+ return `${plan.ignoreConflicts ? " ON CONFLICT DO NOTHING" : ""} RETURNING *`;
2846
+ }
1944
2847
  const primaryKey = Object.values(plan.source.table?.columns ?? {}).find((column2) => column2.primaryKey)?.name;
1945
2848
  const returning = primaryKey ? ` RETURNING ${this.quoteIdentifier(primaryKey)}` : "";
1946
2849
  return `${plan.ignoreConflicts ? " ON CONFLICT DO NOTHING" : ""}${returning}`;
1947
2850
  }
1948
2851
  compileUpsertSuffix(plan, insertColumns) {
1949
2852
  const suffix = super.compileUpsertSuffix(plan, insertColumns);
2853
+ if (plan.returning === true) {
2854
+ return suffix;
2855
+ }
1950
2856
  const primaryKey = Object.values(plan.source.table?.columns ?? {}).find((column2) => column2.primaryKey)?.name;
1951
2857
  return primaryKey ? `${suffix} RETURNING ${this.quoteIdentifier(primaryKey)}` : suffix;
1952
2858
  }
@@ -2224,6 +3130,15 @@ function normalizeDialectWriteValue(dialect, column2, value) {
2224
3130
  }
2225
3131
 
2226
3132
  // src/query/TableQueryBuilder.ts
3133
+ var GROUPED_AVERAGE_COUNT_KEY = "__holo_grouped_average_count";
3134
+ var GROUPED_AVERAGE_ROW_COUNT_KEY = "__holo_grouped_average_row_count";
3135
+ var GROUPED_AVERAGE_SUM_KEY = "__holo_grouped_average_sum";
3136
+ var GROUPED_AVERAGE_GROUP_KEY = "__holo_grouped_average_group";
3137
+ var GROUPED_AGGREGATE_VALUE_KEY = "__holo_grouped_aggregate_state_value";
3138
+ var GROUPED_AGGREGATE_ROW_COUNT_KEY = "__holo_grouped_aggregate_state_row_count";
3139
+ var GROUPED_AGGREGATE_GROUP_KEY = "__holo_grouped_aggregate_state_group";
3140
+ var GROUPED_AGGREGATE_VALUE_COUNT_VALUE_KEY = "__holo_grouped_aggregate_state_count_value";
3141
+ var GROUPED_AGGREGATE_VALUE_COUNT_KEY = "__holo_grouped_aggregate_state_value_count";
2227
3142
  function normalizeAtomicQueryCacheTtl(ttl) {
2228
3143
  if (ttl instanceof Date) {
2229
3144
  const expiresAt = ttl.getTime();
@@ -2921,14 +3836,23 @@ var TableQueryBuilder = class _TableQueryBuilder {
2921
3836
  async get() {
2922
3837
  const statement = this.toSQL();
2923
3838
  const cacheConfig = this.queryCacheConfig;
2924
- const dependencies = this.plan.lockMode || !cacheConfig && !hasActiveDatabaseDependencyCollector() ? void 0 : resolveQueryCacheDependencies(
3839
+ const activeDependencyCollector = hasActiveDatabaseDependencyCollector();
3840
+ const dependencies = this.plan.lockMode || !cacheConfig && !activeDependencyCollector ? void 0 : resolveQueryCacheDependencies(
2925
3841
  this.plan,
2926
3842
  this.connection.getConnectionName(),
2927
3843
  cacheConfig?.invalidate
2928
3844
  );
2929
- recordDatabaseQueryDependencies(dependencies);
3845
+ const observationDependencies = activeDependencyCollector && !this.plan.lockMode ? dependencies ?? inferDatabaseQueryObservationDependencies(this.plan, this.connection.getConnectionName()) : dependencies;
3846
+ const createObservation = dependencies ? createDatabaseQueryObservation : createDatabaseQueryFallbackObservation;
3847
+ recordDatabaseQueryDependencies(observationDependencies);
2930
3848
  if (!cacheConfig || this.plan.lockMode) {
2931
3849
  const result = await this.connection.queryCompiled(statement);
3850
+ await this.recordCollectedQueryObservation(
3851
+ activeDependencyCollector,
3852
+ observationDependencies,
3853
+ createObservation,
3854
+ result.rows
3855
+ );
2932
3856
  return result.rows;
2933
3857
  }
2934
3858
  const bridge = getDatabaseQueryCacheBridge();
@@ -2937,7 +3861,7 @@ var TableQueryBuilder = class _TableQueryBuilder {
2937
3861
  }
2938
3862
  const cacheKey = resolveQueryCacheKey(statement, this.connection.getConnectionName(), cacheConfig);
2939
3863
  if (cacheConfig.flexible) {
2940
- return bridge.flexible(
3864
+ const rows2 = await bridge.flexible(
2941
3865
  cacheKey,
2942
3866
  cacheConfig.flexible,
2943
3867
  async () => {
@@ -2949,11 +3873,18 @@ var TableQueryBuilder = class _TableQueryBuilder {
2949
3873
  dependencies
2950
3874
  }
2951
3875
  );
3876
+ await this.recordCollectedQueryObservation(
3877
+ activeDependencyCollector,
3878
+ observationDependencies,
3879
+ createObservation,
3880
+ rows2
3881
+ );
3882
+ return rows2;
2952
3883
  }
2953
3884
  if (typeof cacheConfig.ttl === "undefined") {
2954
3885
  throw new ConfigurationError('[@holo-js/db] Query cache config requires "ttl" or "flexible".');
2955
3886
  }
2956
- return bridge.flexible(
3887
+ const rows = await bridge.flexible(
2957
3888
  cacheKey,
2958
3889
  normalizeAtomicQueryCacheTtl(cacheConfig.ttl),
2959
3890
  async () => {
@@ -2965,17 +3896,227 @@ var TableQueryBuilder = class _TableQueryBuilder {
2965
3896
  dependencies
2966
3897
  }
2967
3898
  );
3899
+ await this.recordCollectedQueryObservation(
3900
+ activeDependencyCollector,
3901
+ observationDependencies,
3902
+ createObservation,
3903
+ rows
3904
+ );
3905
+ return rows;
3906
+ }
3907
+ async recordCollectedQueryObservation(activeDependencyCollector, observationDependencies, createObservation, rows) {
3908
+ if (!activeDependencyCollector || !observationDependencies) {
3909
+ return;
3910
+ }
3911
+ const observation = createObservation(
3912
+ this.plan,
3913
+ this.connection.getConnectionName(),
3914
+ observationDependencies,
3915
+ rows
3916
+ );
3917
+ const groupedAggregate = observation?.groupedAggregate;
3918
+ if (!observation?.patchable || !groupedAggregate) {
3919
+ recordDatabaseQueryObservation(observation);
3920
+ return;
3921
+ }
3922
+ const averageStates = groupedAggregate.kind === "avg" && groupedAggregate.aggregateColumn ? await this.readGroupedAverageStates(groupedAggregate) : void 0;
3923
+ const aggregateStates = (groupedAggregate.kind === "count" || groupedAggregate.kind === "sum" || groupedAggregate.kind === "min" || groupedAggregate.kind === "max") && groupedAggregate.having ? await this.readGroupedAggregateStates(groupedAggregate) : void 0;
3924
+ recordDatabaseQueryObservation(averageStates || aggregateStates ? createObservation(
3925
+ this.plan,
3926
+ this.connection.getConnectionName(),
3927
+ observationDependencies,
3928
+ rows,
3929
+ averageStates,
3930
+ aggregateStates
3931
+ ) : observation);
3932
+ }
3933
+ async readGroupedAverageStates(groupedAggregate) {
3934
+ const aggregateColumn = groupedAggregate.aggregateColumn;
3935
+ if (!aggregateColumn) {
3936
+ return void 0;
3937
+ }
3938
+ const selections = [
3939
+ Object.freeze({
3940
+ alias: GROUPED_AVERAGE_GROUP_KEY,
3941
+ column: groupedAggregate.groupColumn,
3942
+ kind: "column"
3943
+ }),
3944
+ this.createAggregateSelection("count", GROUPED_AVERAGE_COUNT_KEY, aggregateColumn),
3945
+ this.createAggregateSelection("count", GROUPED_AVERAGE_ROW_COUNT_KEY, "*"),
3946
+ this.createAggregateSelection("sum", GROUPED_AVERAGE_SUM_KEY, aggregateColumn)
3947
+ ];
3948
+ const metadataPlan = Object.freeze({
3949
+ ...this.plan,
3950
+ having: Object.freeze([]),
3951
+ selections: Object.freeze(selections)
3952
+ });
3953
+ const result = await this.connection.queryCompiled(this.getCompiler().compile(metadataPlan));
3954
+ const states = [];
3955
+ for (const row of result.rows) {
3956
+ const count = this.normalizeAggregateMetadataNumber(row[GROUPED_AVERAGE_COUNT_KEY]);
3957
+ const rowCount = this.normalizeAggregateMetadataNumber(row[GROUPED_AVERAGE_ROW_COUNT_KEY]);
3958
+ const sum = this.normalizeAggregateMetadataNumber(row[GROUPED_AVERAGE_SUM_KEY] ?? 0);
3959
+ if (typeof count === "undefined" || typeof rowCount === "undefined" || typeof sum === "undefined" || count < 0 || rowCount < 0) {
3960
+ return void 0;
3961
+ }
3962
+ states.push(Object.freeze({
3963
+ count,
3964
+ groupValue: row[GROUPED_AVERAGE_GROUP_KEY],
3965
+ rowCount,
3966
+ sum
3967
+ }));
3968
+ }
3969
+ return Object.freeze(states);
3970
+ }
3971
+ async readGroupedAggregateStates(groupedAggregate) {
3972
+ const aggregateSelection = this.createGroupedAggregateStateValueSelection(groupedAggregate);
3973
+ if (!aggregateSelection) {
3974
+ return void 0;
3975
+ }
3976
+ const valueCounts = await this.readGroupedAggregateValueCounts(groupedAggregate);
3977
+ const selections = [
3978
+ Object.freeze({
3979
+ alias: GROUPED_AGGREGATE_GROUP_KEY,
3980
+ column: groupedAggregate.groupColumn,
3981
+ kind: "column"
3982
+ }),
3983
+ aggregateSelection,
3984
+ this.createAggregateSelection("count", GROUPED_AGGREGATE_ROW_COUNT_KEY, "*")
3985
+ ];
3986
+ const metadataPlan = Object.freeze({
3987
+ ...this.plan,
3988
+ having: Object.freeze([]),
3989
+ selections: Object.freeze(selections)
3990
+ });
3991
+ const result = await this.connection.queryCompiled(this.getCompiler().compile(metadataPlan));
3992
+ const states = [];
3993
+ for (const row of result.rows) {
3994
+ const aggregateValue = this.normalizeGroupedAggregateStateValue(
3995
+ groupedAggregate,
3996
+ row[GROUPED_AGGREGATE_VALUE_KEY]
3997
+ );
3998
+ const rowCount = this.normalizeAggregateMetadataNumber(row[GROUPED_AGGREGATE_ROW_COUNT_KEY]);
3999
+ if (typeof aggregateValue === "undefined" || typeof rowCount === "undefined" || rowCount < 0) {
4000
+ return void 0;
4001
+ }
4002
+ states.push(Object.freeze({
4003
+ aggregateValue,
4004
+ groupValue: row[GROUPED_AGGREGATE_GROUP_KEY],
4005
+ rowCount,
4006
+ ...valueCounts ? { valueCounts: this.readGroupedAggregateValueCountsForGroup(valueCounts, row[GROUPED_AGGREGATE_GROUP_KEY]) } : {}
4007
+ }));
4008
+ }
4009
+ return Object.freeze(states);
4010
+ }
4011
+ async readGroupedAggregateValueCounts(groupedAggregate) {
4012
+ if (groupedAggregate.kind !== "min" && groupedAggregate.kind !== "max") {
4013
+ return void 0;
4014
+ }
4015
+ const aggregateColumn = groupedAggregate.aggregateColumn;
4016
+ if (!aggregateColumn) {
4017
+ return void 0;
4018
+ }
4019
+ const metadataPlan = Object.freeze({
4020
+ ...this.plan,
4021
+ groupBy: Object.freeze([groupedAggregate.groupColumn, aggregateColumn]),
4022
+ having: Object.freeze([]),
4023
+ selections: Object.freeze([
4024
+ Object.freeze({
4025
+ alias: GROUPED_AGGREGATE_GROUP_KEY,
4026
+ column: groupedAggregate.groupColumn,
4027
+ kind: "column"
4028
+ }),
4029
+ Object.freeze({
4030
+ alias: GROUPED_AGGREGATE_VALUE_COUNT_VALUE_KEY,
4031
+ column: aggregateColumn,
4032
+ kind: "column"
4033
+ }),
4034
+ this.createAggregateSelection("count", GROUPED_AGGREGATE_VALUE_COUNT_KEY, "*")
4035
+ ])
4036
+ });
4037
+ const result = await this.connection.queryCompiled(this.getCompiler().compile(metadataPlan));
4038
+ const groups = [];
4039
+ for (const row of result.rows) {
4040
+ const groupValue = row[GROUPED_AGGREGATE_GROUP_KEY];
4041
+ const value = this.normalizeGroupedAggregateStateValue(
4042
+ groupedAggregate,
4043
+ row[GROUPED_AGGREGATE_VALUE_COUNT_VALUE_KEY]
4044
+ );
4045
+ const count = this.normalizeAggregateMetadataNumber(row[GROUPED_AGGREGATE_VALUE_COUNT_KEY]);
4046
+ if (typeof value === "undefined" || typeof count === "undefined" || count <= 0) {
4047
+ return void 0;
4048
+ }
4049
+ this.pushGroupedAggregateValueCount(groups, groupValue, Object.freeze({ count, value }));
4050
+ }
4051
+ return Object.freeze(groups.map((group) => Object.freeze({
4052
+ groupValue: group.groupValue,
4053
+ valueCounts: [...group.valueCounts].sort((left, right) => left.value - right.value)
4054
+ })));
4055
+ }
4056
+ pushGroupedAggregateValueCount(groups, groupValue, valueCount) {
4057
+ const group = groups.find((candidate) => Object.is(candidate.groupValue, groupValue));
4058
+ if (group) {
4059
+ group.valueCounts.push(valueCount);
4060
+ return;
4061
+ }
4062
+ groups.push({
4063
+ groupValue,
4064
+ valueCounts: [valueCount]
4065
+ });
4066
+ }
4067
+ readGroupedAggregateValueCountsForGroup(groups, groupValue) {
4068
+ return groups.find((group) => Object.is(group.groupValue, groupValue))?.valueCounts ?? Object.freeze([]);
4069
+ }
4070
+ createGroupedAggregateStateValueSelection(groupedAggregate) {
4071
+ if (groupedAggregate.kind === "count") {
4072
+ return this.createAggregateSelection("count", GROUPED_AGGREGATE_VALUE_KEY, "*");
4073
+ }
4074
+ if ((groupedAggregate.kind === "sum" || groupedAggregate.kind === "min" || groupedAggregate.kind === "max") && groupedAggregate.aggregateColumn) {
4075
+ return this.createAggregateSelection(
4076
+ groupedAggregate.kind,
4077
+ GROUPED_AGGREGATE_VALUE_KEY,
4078
+ groupedAggregate.aggregateColumn
4079
+ );
4080
+ }
4081
+ return void 0;
4082
+ }
4083
+ normalizeGroupedAggregateStateValue(groupedAggregate, value) {
4084
+ if ((groupedAggregate.kind === "min" || groupedAggregate.kind === "max") && value === null) {
4085
+ return void 0;
4086
+ }
4087
+ return this.normalizeAggregateMetadataNumber(value);
4088
+ }
4089
+ normalizeAggregateMetadataNumber(value) {
4090
+ if (value === null) {
4091
+ return 0;
4092
+ }
4093
+ if (typeof value === "number") {
4094
+ return Number.isFinite(value) ? value : void 0;
4095
+ }
4096
+ if (typeof value === "bigint") {
4097
+ const numericValue = Number(value);
4098
+ return Number.isSafeInteger(numericValue) ? numericValue : void 0;
4099
+ }
4100
+ if (typeof value === "string" && value.trim()) {
4101
+ const numericValue = Number(value);
4102
+ return Number.isFinite(numericValue) ? numericValue : void 0;
4103
+ }
4104
+ return void 0;
2968
4105
  }
2969
4106
  async first() {
2970
4107
  const rows = await this.limit(1).get();
2971
- return rows[0];
4108
+ const row = rows[0];
4109
+ rebindDatabaseQueryObservationResult(rows, row);
4110
+ return row;
2972
4111
  }
2973
4112
  async sole() {
2974
4113
  const rows = await this.limit(2).get();
2975
4114
  if (rows.length !== 1) {
2976
4115
  throw new CompilerError(`Query expected exactly one row but found ${rows.length}.`);
2977
4116
  }
2978
- return rows[0];
4117
+ const row = rows[0];
4118
+ rebindDatabaseQueryObservationResult(rows, row);
4119
+ return row;
2979
4120
  }
2980
4121
  async paginate(perPage = 15, page = 1, options = {}) {
2981
4122
  assertPositiveInteger(perPage, "Per-page value", (message) => new SecurityError(message));
@@ -2987,7 +4128,7 @@ var TableQueryBuilder = class _TableQueryBuilder {
2987
4128
  const data = rows.slice(offset, offset + perPage);
2988
4129
  const from = data.length === 0 ? null : offset + 1;
2989
4130
  const to = data.length === 0 ? null : offset + data.length;
2990
- return createPaginator(data, {
4131
+ const result = createPaginator(data, {
2991
4132
  total,
2992
4133
  perPage,
2993
4134
  pageName,
@@ -2997,6 +4138,14 @@ var TableQueryBuilder = class _TableQueryBuilder {
2997
4138
  to,
2998
4139
  hasMorePages: offset + data.length < total
2999
4140
  });
4141
+ rebindDatabaseQueryObservationPagination(rows, result.data, result.meta, Object.freeze({
4142
+ currentPage: page,
4143
+ kind: "standard",
4144
+ pageName,
4145
+ perPage,
4146
+ total
4147
+ }), offset);
4148
+ return result;
3000
4149
  }
3001
4150
  async simplePaginate(perPage = 15, page = 1, options = {}) {
3002
4151
  assertPositiveInteger(perPage, "Per-page value", (message) => new SecurityError(message));
@@ -3009,7 +4158,7 @@ var TableQueryBuilder = class _TableQueryBuilder {
3009
4158
  const data = hasMorePages ? pageRows.slice(0, perPage) : pageRows;
3010
4159
  const from = data.length === 0 ? null : offset + 1;
3011
4160
  const to = data.length === 0 ? null : offset + data.length;
3012
- return createSimplePaginator(data, {
4161
+ const result = createSimplePaginator(data, {
3013
4162
  perPage,
3014
4163
  pageName,
3015
4164
  currentPage: page,
@@ -3017,6 +4166,15 @@ var TableQueryBuilder = class _TableQueryBuilder {
3017
4166
  to,
3018
4167
  hasMorePages
3019
4168
  });
4169
+ rebindDatabaseQueryObservationPagination(rows, result.data, result.meta, Object.freeze({
4170
+ currentPage: page,
4171
+ hasMorePages,
4172
+ kind: "simple",
4173
+ pageName,
4174
+ perPage,
4175
+ rowCount: rows.length
4176
+ }), offset);
4177
+ return result;
3020
4178
  }
3021
4179
  async cursorPaginate(perPage = 15, cursor = null, options = {}) {
3022
4180
  assertPositiveInteger(perPage, "Per-page value", (message) => new SecurityError(message));
@@ -3034,12 +4192,32 @@ var TableQueryBuilder = class _TableQueryBuilder {
3034
4192
  const hasMorePages = pageRows.length > perPage;
3035
4193
  const data = hasMorePages ? pageRows.slice(0, perPage) : pageRows;
3036
4194
  const lastRow = data.at(-1);
3037
- return createCursorPaginator(data, {
4195
+ const result = createCursorPaginator(data, {
3038
4196
  perPage,
3039
4197
  cursorName,
3040
4198
  nextCursor: hasMorePages && lastRow ? encodeValueCursor(cursorOrders.map((order) => orderedQuery.readCursorColumnValue(lastRow, order.column))) : null,
3041
4199
  prevCursor: cursor
3042
4200
  });
4201
+ if (cursor === null) {
4202
+ rebindDatabaseQueryObservationCursorPagination(rows, result.data, {
4203
+ cursorName: result.cursorName,
4204
+ nextCursor: result.nextCursor,
4205
+ perPage: result.perPage,
4206
+ prevCursor: result.prevCursor
4207
+ }, Object.freeze({
4208
+ cursorName: result.cursorName,
4209
+ hasMorePages,
4210
+ kind: "cursor",
4211
+ nextCursor: result.nextCursor,
4212
+ perPage: result.perPage,
4213
+ prevCursor: result.prevCursor,
4214
+ rows: pageRows,
4215
+ rowCount: rows.length
4216
+ }));
4217
+ } else {
4218
+ rebindDatabaseQueryObservationResult(rows, data);
4219
+ }
4220
+ return result;
3043
4221
  }
3044
4222
  async chunk(size, callback) {
3045
4223
  assertPositiveInteger(size, "Chunk size", (message) => new SecurityError(message));
@@ -3103,35 +4281,66 @@ var TableQueryBuilder = class _TableQueryBuilder {
3103
4281
  }
3104
4282
  }
3105
4283
  async count() {
3106
- return (await this.get()).length;
4284
+ const rows = await this.get();
4285
+ const result = rows.length;
4286
+ if (hasActiveDatabaseDependencyCollector()) {
4287
+ rebindDatabaseQueryObservationAggregate(rows, result, Object.freeze({ kind: "count" }));
4288
+ }
4289
+ return result;
3107
4290
  }
3108
4291
  async exists() {
3109
- return await this.count() > 0;
4292
+ const count = await this.count();
4293
+ const result = count > 0;
4294
+ if (hasActiveDatabaseDependencyCollector()) {
4295
+ rebindDatabaseQueryObservationAggregate(count, result, Object.freeze({
4296
+ count,
4297
+ kind: "count",
4298
+ output: "boolean"
4299
+ }));
4300
+ }
4301
+ return result;
3110
4302
  }
3111
4303
  async doesntExist() {
3112
- return !await this.exists();
4304
+ const count = await this.count();
4305
+ const result = count === 0;
4306
+ if (hasActiveDatabaseDependencyCollector()) {
4307
+ rebindDatabaseQueryObservationAggregate(count, result, Object.freeze({
4308
+ count,
4309
+ kind: "count",
4310
+ output: "inverseBoolean"
4311
+ }));
4312
+ }
4313
+ return result;
3113
4314
  }
3114
4315
  async pluck(column2) {
3115
4316
  const rows = await this.get();
3116
- return rows.map((row) => row[column2]);
4317
+ const result = rows.map((row) => row[column2]);
4318
+ rebindDatabaseQueryObservationScalarList(rows, result, column2);
4319
+ return result;
3117
4320
  }
3118
4321
  async value(column2) {
3119
4322
  const row = await this.first();
3120
- return row?.[column2];
4323
+ const result = row?.[column2];
4324
+ rebindDatabaseQueryObservationScalar(row, result, column2);
4325
+ return result;
3121
4326
  }
3122
4327
  async valueOrFail(column2) {
3123
4328
  const row = await this.first();
3124
4329
  if (!row || typeof row[column2] === "undefined") {
3125
4330
  throw new CompilerError(`Query returned no value for column "${column2}".`);
3126
4331
  }
3127
- return row[column2];
4332
+ const result = row[column2];
4333
+ rebindDatabaseQueryObservationScalar(row, result, column2);
4334
+ return result;
3128
4335
  }
3129
4336
  async soleValue(column2) {
3130
4337
  const row = await this.sole();
3131
4338
  if (typeof row[column2] === "undefined") {
3132
4339
  throw new CompilerError(`Query returned no value for column "${column2}".`);
3133
4340
  }
3134
- return row[column2];
4341
+ const result = row[column2];
4342
+ rebindDatabaseQueryObservationScalar(row, result, column2);
4343
+ return result;
3135
4344
  }
3136
4345
  async sum(column2) {
3137
4346
  return this.aggregateNumeric(column2, "sum");
@@ -3139,45 +4348,95 @@ var TableQueryBuilder = class _TableQueryBuilder {
3139
4348
  async avg(column2) {
3140
4349
  const rows = await this.get();
3141
4350
  if (rows.length === 0) {
4351
+ if (hasActiveDatabaseDependencyCollector()) {
4352
+ rebindDatabaseQueryObservationAggregate(rows, null, Object.freeze({ column: column2, count: 0, kind: "avg", sum: 0 }));
4353
+ }
3142
4354
  return null;
3143
4355
  }
3144
4356
  const values = this.extractNumericValues(rows, column2, "avg");
3145
- return values.reduce((sum, value) => sum + value, 0) / values.length;
4357
+ const sum = values.reduce((total, value) => total + value, 0);
4358
+ const result = sum / values.length;
4359
+ if (hasActiveDatabaseDependencyCollector()) {
4360
+ rebindDatabaseQueryObservationAggregate(rows, result, Object.freeze({ column: column2, count: values.length, kind: "avg", sum }));
4361
+ }
4362
+ return result;
3146
4363
  }
3147
4364
  async min(column2) {
3148
4365
  const rows = await this.get();
3149
4366
  if (rows.length === 0) {
4367
+ if (hasActiveDatabaseDependencyCollector()) {
4368
+ rebindDatabaseQueryObservationAggregate(rows, null, Object.freeze({ column: column2, kind: "min" }));
4369
+ }
3150
4370
  return null;
3151
4371
  }
3152
4372
  const values = this.extractNumericValues(rows, column2, "min");
3153
- return Math.min(...values);
4373
+ const result = Math.min(...values);
4374
+ if (hasActiveDatabaseDependencyCollector()) {
4375
+ rebindDatabaseQueryObservationAggregate(rows, result, Object.freeze({
4376
+ column: column2,
4377
+ currentValueCount: values.filter((value) => value === result).length,
4378
+ kind: "min",
4379
+ valueCounts: createAggregateValueCounts(values)
4380
+ }));
4381
+ }
4382
+ return result;
3154
4383
  }
3155
4384
  async max(column2) {
3156
4385
  const rows = await this.get();
3157
4386
  if (rows.length === 0) {
4387
+ if (hasActiveDatabaseDependencyCollector()) {
4388
+ rebindDatabaseQueryObservationAggregate(rows, null, Object.freeze({ column: column2, kind: "max" }));
4389
+ }
3158
4390
  return null;
3159
4391
  }
3160
4392
  const values = this.extractNumericValues(rows, column2, "max");
3161
- return Math.max(...values);
4393
+ const result = Math.max(...values);
4394
+ if (hasActiveDatabaseDependencyCollector()) {
4395
+ rebindDatabaseQueryObservationAggregate(rows, result, Object.freeze({
4396
+ column: column2,
4397
+ currentValueCount: values.filter((value) => value === result).length,
4398
+ kind: "max",
4399
+ valueCounts: createAggregateValueCounts(values)
4400
+ }));
4401
+ }
4402
+ return result;
3162
4403
  }
3163
4404
  async find(value, column2 = "id") {
3164
4405
  return this.where(column2, value).limit(1).first();
3165
4406
  }
3166
4407
  async insert(values) {
3167
4408
  const rows = Array.isArray(values) ? values.map((value) => this.normalizeWriteRecord(value)) : [this.normalizeWriteRecord(values)];
3168
- const result = await this.connection.executeCompiled(this.getCompiler().compile(
4409
+ const hasInvalidationListeners = hasDatabaseDependencyInvalidationListeners();
4410
+ const useReturningRows = this.shouldUseReturningMutationRows(hasInvalidationListeners);
4411
+ const result = useReturningRows ? await this.queryReturningMutationRows(
4412
+ createInsertQueryPlan(this.source, rows, { returning: true }),
4413
+ true
4414
+ ) : await this.connection.executeCompiled(this.getCompiler().compile(
3169
4415
  createInsertQueryPlan(this.source, rows)
3170
4416
  ));
3171
- await this.invalidateInsertQueries(rows, result.lastInsertId);
3172
- return result;
4417
+ await this.invalidateInsertQueries("insert", result.rows?.rows ?? rows, result.lastInsertId);
4418
+ return {
4419
+ affectedRows: result.affectedRows,
4420
+ lastInsertId: result.lastInsertId
4421
+ };
3173
4422
  }
3174
4423
  async insertOrIgnore(values) {
3175
4424
  const rows = Array.isArray(values) ? values.map((value) => this.normalizeWriteRecord(value)) : [this.normalizeWriteRecord(values)];
3176
- const result = await this.connection.executeCompiled(this.getCompiler().compile(
4425
+ const hasInvalidationListeners = hasDatabaseDependencyInvalidationListeners();
4426
+ const useReturningRows = this.shouldUseReturningMutationRows(hasInvalidationListeners);
4427
+ const result = useReturningRows ? await this.queryReturningMutationRows(
4428
+ createInsertQueryPlan(this.source, rows, { ignoreConflicts: true, returning: true }),
4429
+ true
4430
+ ) : await this.connection.executeCompiled(this.getCompiler().compile(
3177
4431
  createInsertQueryPlan(this.source, rows, { ignoreConflicts: true })
3178
4432
  ));
3179
- await this.invalidateInsertQueries(rows, result.lastInsertId);
3180
- return result;
4433
+ if (!useReturningRows || result.affectedRows !== 0) {
4434
+ await this.invalidateInsertQueries("insert", result.rows?.rows ?? rows, result.lastInsertId);
4435
+ }
4436
+ return {
4437
+ affectedRows: result.affectedRows,
4438
+ lastInsertId: result.lastInsertId
4439
+ };
3181
4440
  }
3182
4441
  async insertGetId(values) {
3183
4442
  const result = await this.insert(values);
@@ -3185,78 +4444,272 @@ var TableQueryBuilder = class _TableQueryBuilder {
3185
4444
  }
3186
4445
  async upsert(values, uniqueBy, updateColumns = []) {
3187
4446
  const rows = Array.isArray(values) ? values.map((value) => this.normalizeWriteRecord(value)) : [this.normalizeWriteRecord(values)];
3188
- const result = await this.connection.executeCompiled(this.getCompiler().compile(
4447
+ const hasInvalidationListeners = hasDatabaseDependencyInvalidationListeners();
4448
+ const previousRows = hasInvalidationListeners ? await this.captureUpsertPreviousRows(rows, uniqueBy) : void 0;
4449
+ const useReturningRows = this.shouldUseReturningMutationRows(hasInvalidationListeners);
4450
+ const result = useReturningRows ? await this.queryReturningMutationRows(
4451
+ createUpsertQueryPlan(this.source, rows, uniqueBy, updateColumns, { returning: true }),
4452
+ true
4453
+ ) : await this.connection.executeCompiled(this.getCompiler().compile(
3189
4454
  createUpsertQueryPlan(this.source, rows, uniqueBy, updateColumns)
3190
4455
  ));
3191
- await this.invalidateInsertQueries(rows, result.lastInsertId);
3192
- return result;
4456
+ if (!useReturningRows || result.affectedRows !== 0) {
4457
+ await this.invalidateInsertQueries("upsert", result.rows?.rows ?? rows, result.lastInsertId, previousRows);
4458
+ }
4459
+ return {
4460
+ affectedRows: result.affectedRows,
4461
+ lastInsertId: result.lastInsertId
4462
+ };
3193
4463
  }
3194
4464
  async increment(column2, amount = 1, extraValues = {}) {
3195
4465
  return this.adjustNumericColumn(column2, amount, extraValues);
3196
4466
  }
3197
- async decrement(column2, amount = 1, extraValues = {}) {
3198
- return this.adjustNumericColumn(column2, -amount, extraValues);
4467
+ async decrement(column2, amount = 1, extraValues = {}) {
4468
+ return this.adjustNumericColumn(column2, -amount, extraValues);
4469
+ }
4470
+ async update(values) {
4471
+ const normalizedValues = this.normalizeUpdateValues(values);
4472
+ const hasInvalidationListeners = hasDatabaseDependencyInvalidationListeners();
4473
+ const useReturningRows = this.shouldUseReturningMutationRows(hasInvalidationListeners);
4474
+ const mutationRows = hasInvalidationListeners && !useReturningRows ? await this.captureUpdatedMutationRows(normalizedValues) : void 0;
4475
+ const result = useReturningRows ? await this.queryReturningMutationRows(
4476
+ createUpdateQueryPlan(this.source, this.plan.predicates, normalizedValues, { returning: true })
4477
+ ) : await this.connection.executeCompiled(this.getCompiler().compile(
4478
+ createUpdateQueryPlan(this.source, this.plan.predicates, normalizedValues)
4479
+ ));
4480
+ const capturedRows = result.rows ?? mutationRows;
4481
+ if (result.affectedRows !== 0) {
4482
+ await this.invalidateMutationQueries(normalizedValues, capturedRows);
4483
+ }
4484
+ return {
4485
+ affectedRows: result.affectedRows,
4486
+ lastInsertId: result.lastInsertId
4487
+ };
4488
+ }
4489
+ async updateJson(columnPath, value) {
4490
+ return this.update({ [columnPath]: value });
4491
+ }
4492
+ async delete() {
4493
+ const hasInvalidationListeners = hasDatabaseDependencyInvalidationListeners();
4494
+ const useReturningRows = this.shouldUseReturningMutationRows(hasInvalidationListeners);
4495
+ const mutationRows = hasInvalidationListeners && !useReturningRows ? await this.captureDeletedMutationRows() : void 0;
4496
+ const result = useReturningRows ? await this.queryReturningMutationRows(
4497
+ createDeleteQueryPlan(this.source, this.plan.predicates, { returning: true })
4498
+ ) : await this.connection.executeCompiled(this.getCompiler().compile(
4499
+ createDeleteQueryPlan(this.source, this.plan.predicates)
4500
+ ));
4501
+ const capturedRows = result.rows ?? mutationRows;
4502
+ if (result.affectedRows !== 0) {
4503
+ await this.invalidateMutationQueries({}, capturedRows);
4504
+ }
4505
+ return {
4506
+ affectedRows: result.affectedRows,
4507
+ lastInsertId: result.lastInsertId
4508
+ };
4509
+ }
4510
+ async unsafeQuery(statement) {
4511
+ return this.connection.unsafeQuery({
4512
+ ...statement,
4513
+ unsafe: true,
4514
+ source: `table:${this.source.tableName}`
4515
+ });
4516
+ }
4517
+ async unsafeExecute(statement) {
4518
+ return this.connection.unsafeExecute({
4519
+ ...statement,
4520
+ unsafe: true,
4521
+ source: `table:${this.source.tableName}`
4522
+ });
4523
+ }
4524
+ clone(plan) {
4525
+ const table = this.source.table ?? this.source.tableName;
4526
+ return new _TableQueryBuilder(table, this.connection, plan, this.queryCacheConfig);
4527
+ }
4528
+ async captureUpdatedMutationRows(values) {
4529
+ const plan = Object.freeze({
4530
+ ...createSelectQueryPlan(this.source),
4531
+ predicates: this.plan.predicates
4532
+ });
4533
+ const result = await this.connection.queryCompiled(this.getCompiler().compile(plan));
4534
+ if (result.rows.length === 0) {
4535
+ return void 0;
4536
+ }
4537
+ const previousRows = Object.freeze(result.rows.map((row) => Object.freeze({ ...row })));
4538
+ const rows = Object.freeze(previousRows.map((row) => Object.freeze(
4539
+ this.applyCapturedUpdateValues(row, values)
4540
+ )));
4541
+ return Object.freeze({
4542
+ previousRows,
4543
+ rows
4544
+ });
4545
+ }
4546
+ async captureDeletedMutationRows() {
4547
+ const plan = Object.freeze({
4548
+ ...createSelectQueryPlan(this.source),
4549
+ predicates: this.plan.predicates
4550
+ });
4551
+ const result = await this.connection.queryCompiled(this.getCompiler().compile(plan));
4552
+ if (result.rows.length === 0) {
4553
+ return void 0;
4554
+ }
4555
+ return Object.freeze({
4556
+ rows: Object.freeze(result.rows.map((row) => Object.freeze({ ...row })))
4557
+ });
4558
+ }
4559
+ applyCapturedUpdateValues(row, values) {
4560
+ const nextRow = { ...row };
4561
+ for (const [column2, value] of Object.entries(values)) {
4562
+ nextRow[column2] = this.applyCapturedUpdateValue(row[column2], value);
4563
+ }
4564
+ return nextRow;
4565
+ }
4566
+ applyCapturedUpdateValue(currentValue, value) {
4567
+ if (!Array.isArray(value) || !value.every((item) => this.isCapturedJsonUpdateOperation(item))) {
4568
+ return value;
4569
+ }
4570
+ let nextValue = currentValue;
4571
+ for (const operation of value) {
4572
+ nextValue = this.applyCapturedJsonUpdateOperation(nextValue, operation);
4573
+ }
4574
+ return nextValue;
4575
+ }
4576
+ applyCapturedJsonUpdateOperation(currentValue, operation) {
4577
+ return this.applyCapturedJsonPathValue(currentValue, operation.path, operation.value);
4578
+ }
4579
+ isCapturedJsonUpdateOperation(value) {
4580
+ return typeof value === "object" && value !== null && !Array.isArray(value) && value.kind === "json-set" && Array.isArray(value.path);
3199
4581
  }
3200
- async update(values) {
3201
- const result = await this.connection.executeCompiled(this.getCompiler().compile(
3202
- createUpdateQueryPlan(this.source, this.plan.predicates, this.normalizeUpdateValues(values))
3203
- ));
3204
- if (result.affectedRows !== 0) {
3205
- await this.invalidateMutationQueries();
4582
+ applyCapturedJsonPathValue(currentValue, path, value) {
4583
+ const segment = path[0];
4584
+ if (typeof segment === "undefined") {
4585
+ return value;
3206
4586
  }
3207
- return result;
4587
+ const currentRecord = this.isJsonRecord(currentValue) ? currentValue : {};
4588
+ const childValue = this.applyCapturedJsonPathValue(currentRecord[segment], path.slice(1), value);
4589
+ return Object.freeze({
4590
+ ...currentRecord,
4591
+ [segment]: childValue
4592
+ });
3208
4593
  }
3209
- async updateJson(columnPath, value) {
3210
- return this.update({ [columnPath]: value });
4594
+ isJsonRecord(value) {
4595
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3211
4596
  }
3212
- async delete() {
3213
- const result = await this.connection.executeCompiled(this.getCompiler().compile(
3214
- createDeleteQueryPlan(this.source, this.plan.predicates)
3215
- ));
3216
- if (result.affectedRows !== 0) {
3217
- await this.invalidateMutationQueries();
4597
+ async captureUpsertPreviousRows(rows, uniqueBy) {
4598
+ const firstUniqueColumn = uniqueBy[0];
4599
+ if (!firstUniqueColumn || rows.length === 0 || !rows.every((row) => this.hasUniqueByValues(row, uniqueBy))) {
4600
+ return void 0;
3218
4601
  }
3219
- return result;
4602
+ const firstColumnValues = Object.freeze([...new Set(rows.map((row) => row[firstUniqueColumn]))]);
4603
+ const query = new _TableQueryBuilder(this.source.tableName, this.connection).whereIn(firstUniqueColumn, firstColumnValues);
4604
+ const result = await this.connection.queryCompiled(this.getCompiler().compile(query.getPlan()));
4605
+ const previousRows = [];
4606
+ const usedResultIndexes = /* @__PURE__ */ new Set();
4607
+ for (const row of rows) {
4608
+ const resultIndex = result.rows.findIndex((candidate, index) => {
4609
+ return !usedResultIndexes.has(index) && this.rowsMatchUniqueBy(candidate, row, uniqueBy);
4610
+ });
4611
+ if (resultIndex < 0) {
4612
+ continue;
4613
+ }
4614
+ const previousRow = result.rows[resultIndex];
4615
+ if (!previousRow) {
4616
+ continue;
4617
+ }
4618
+ usedResultIndexes.add(resultIndex);
4619
+ previousRows.push(Object.freeze({ ...previousRow }));
4620
+ }
4621
+ return Object.freeze(previousRows);
3220
4622
  }
3221
- async unsafeQuery(statement) {
3222
- return this.connection.unsafeQuery({
3223
- ...statement,
3224
- unsafe: true,
3225
- source: `table:${this.source.tableName}`
3226
- });
4623
+ hasUniqueByValues(row, uniqueBy) {
4624
+ return uniqueBy.every((column2) => Object.prototype.hasOwnProperty.call(row, column2));
3227
4625
  }
3228
- async unsafeExecute(statement) {
3229
- return this.connection.unsafeExecute({
3230
- ...statement,
3231
- unsafe: true,
3232
- source: `table:${this.source.tableName}`
3233
- });
4626
+ rowsMatchUniqueBy(left, right, uniqueBy) {
4627
+ return uniqueBy.every((column2) => left[column2] === right[column2]);
3234
4628
  }
3235
- clone(plan) {
3236
- const table = this.source.table ?? this.source.tableName;
3237
- return new _TableQueryBuilder(table, this.connection, plan, this.queryCacheConfig);
4629
+ shouldUseReturningMutationRows(hasInvalidationListeners) {
4630
+ return hasInvalidationListeners && this.connection.getCapabilities().returning;
4631
+ }
4632
+ async queryReturningMutationRows(plan, includeLastInsertId = false) {
4633
+ const result = await this.connection.queryCompiled(this.getCompiler().compile(plan));
4634
+ const rows = this.freezeMutationRows(result.rows);
4635
+ return {
4636
+ affectedRows: result.rowCount,
4637
+ lastInsertId: includeLastInsertId ? this.readReturnedLastInsertId(rows) : void 0,
4638
+ rows: rows.length === 0 ? void 0 : Object.freeze({
4639
+ rows
4640
+ })
4641
+ };
4642
+ }
4643
+ freezeMutationRows(rows) {
4644
+ return Object.freeze(rows.map((row) => Object.freeze({ ...row })));
4645
+ }
4646
+ readReturnedLastInsertId(rows) {
4647
+ if (rows.length !== 1) {
4648
+ return void 0;
4649
+ }
4650
+ const value = rows[0]?.[this.resolvePrimaryKeyColumn()];
4651
+ return typeof value === "number" || typeof value === "string" ? value : void 0;
3238
4652
  }
3239
- async invalidateInsertQueries(rows, lastInsertId) {
3240
- if (!getDatabaseQueryCacheBridge() && !hasDatabaseDependencyInvalidationListeners()) {
4653
+ async invalidateInsertQueries(kind, rows, lastInsertId, previousRows) {
4654
+ const hasInvalidationListeners = hasDatabaseDependencyInvalidationListeners();
4655
+ if (!getDatabaseQueryCacheBridge() && !hasInvalidationListeners) {
3241
4656
  return;
3242
4657
  }
3243
- await invalidateQueryCacheDependencies(
3244
- this.connection,
3245
- inferAutomaticInsertCacheInvalidationDependencies(
4658
+ const mutations = hasInvalidationListeners ? [
4659
+ createDatabaseMutationEvent(
4660
+ kind,
3246
4661
  this.connection.getConnectionName(),
3247
4662
  this.source.tableName,
3248
- rows,
3249
- lastInsertId
4663
+ [],
4664
+ void 0,
4665
+ rows.length === 1 && typeof lastInsertId !== "undefined" && !Object.prototype.hasOwnProperty.call(rows[0], "id") ? Object.freeze([Object.freeze({
4666
+ ...rows[0],
4667
+ id: lastInsertId
4668
+ })]) : rows,
4669
+ previousRows
3250
4670
  )
4671
+ ] : [];
4672
+ const invalidation = inferAutomaticInsertCacheInvalidationPlan(
4673
+ this.connection.getConnectionName(),
4674
+ this.source.tableName,
4675
+ rows,
4676
+ lastInsertId,
4677
+ hasInvalidationListeners
4678
+ );
4679
+ await invalidateQueryCacheDependencies(
4680
+ this.connection,
4681
+ invalidation.dependencies,
4682
+ mutations,
4683
+ invalidation
3251
4684
  );
3252
4685
  }
3253
- async invalidateMutationQueries() {
3254
- if (!getDatabaseQueryCacheBridge() && !hasDatabaseDependencyInvalidationListeners()) {
4686
+ async invalidateMutationQueries(values = {}, capturedRows) {
4687
+ const hasInvalidationListeners = hasDatabaseDependencyInvalidationListeners();
4688
+ if (!getDatabaseQueryCacheBridge() && !hasInvalidationListeners) {
3255
4689
  return;
3256
4690
  }
4691
+ const hasValues = Object.keys(values).length > 0;
4692
+ const invalidation = inferAutomaticQueryCacheInvalidationPlan(
4693
+ this.plan,
4694
+ this.connection.getConnectionName(),
4695
+ values,
4696
+ hasInvalidationListeners
4697
+ );
3257
4698
  await invalidateQueryCacheDependencies(
3258
4699
  this.connection,
3259
- inferAutomaticQueryCacheInvalidationDependencies(this.plan, this.connection.getConnectionName())
4700
+ invalidation.dependencies,
4701
+ hasInvalidationListeners ? [
4702
+ createDatabaseMutationEvent(
4703
+ hasValues ? "update" : "delete",
4704
+ this.connection.getConnectionName(),
4705
+ this.source.tableName,
4706
+ this.plan.predicates,
4707
+ hasValues ? values : void 0,
4708
+ capturedRows?.rows,
4709
+ capturedRows?.previousRows
4710
+ )
4711
+ ] : [],
4712
+ invalidation
3260
4713
  );
3261
4714
  }
3262
4715
  getCompiler() {
@@ -3400,10 +4853,11 @@ var TableQueryBuilder = class _TableQueryBuilder {
3400
4853
  }
3401
4854
  async aggregateNumeric(column2, kind) {
3402
4855
  const rows = await this.get();
3403
- if (rows.length === 0) {
3404
- return 0;
4856
+ const result = rows.length === 0 ? 0 : this.extractNumericValues(rows, column2, kind).reduce((sum, value) => sum + value, 0);
4857
+ if (hasActiveDatabaseDependencyCollector()) {
4858
+ rebindDatabaseQueryObservationAggregate(rows, result, Object.freeze({ column: column2, kind }));
3405
4859
  }
3406
- return this.extractNumericValues(rows, column2, kind).reduce((sum, value) => sum + value, 0);
4860
+ return result;
3407
4861
  }
3408
4862
  extractNumericValues(rows, column2, kind) {
3409
4863
  return rows.map((row) => {
@@ -8219,6 +9673,13 @@ var ModelQueryBuilder = class _ModelQueryBuilder {
8219
9673
  }
8220
9674
  async get() {
8221
9675
  const rows = await this.tableQuery.get();
9676
+ const collection = await this.hydrateRows(rows);
9677
+ this.recordCollectionRelationObservations(collection);
9678
+ this.recordCollectionRelationAggregateObservations(collection);
9679
+ this.rebindRowsToSerializedResult(rows, collection);
9680
+ return collection;
9681
+ }
9682
+ async hydrateRows(rows) {
8222
9683
  const hasQueryCasts = Object.keys(this.queryCasts).length > 0;
8223
9684
  let entities = await Promise.all(
8224
9685
  rows.map((row) => hasQueryCasts ? this.repository.retrieveWithCasts(row, this.queryCasts) : this.repository.retrieve(row))
@@ -8230,39 +9691,76 @@ var ModelQueryBuilder = class _ModelQueryBuilder {
8230
9691
  return this.repository.createCollection(entities);
8231
9692
  }
8232
9693
  async getJson() {
8233
- return (await this.get()).toJSON();
9694
+ const rows = await this.tableQuery.get();
9695
+ const collection = await this.hydrateRows(rows);
9696
+ this.recordCollectionRelationObservations(collection);
9697
+ this.recordCollectionRelationAggregateObservations(collection);
9698
+ const data = collection.toJSON();
9699
+ this.rebindRowsToSerializedResult(rows, data);
9700
+ return data;
8234
9701
  }
8235
9702
  async first() {
8236
- const [entity] = await this.limit(1).get();
9703
+ const query = this.limit(1);
9704
+ const { collection, rows } = await query.getRowsAndEntities();
9705
+ const entity = collection[0];
9706
+ query.recordSingleRelationObservations(entity);
9707
+ query.recordSingleRelationAggregateObservations(entity);
9708
+ query.rebindRowsToSerializedResult(rows, entity);
8237
9709
  return entity;
8238
9710
  }
8239
9711
  async firstJson() {
8240
- return (await this.first())?.toJSON();
9712
+ const query = this.limit(1);
9713
+ const { collection, rows } = await query.getRowsAndEntities();
9714
+ const data = collection[0]?.toJSON();
9715
+ query.recordSingleRelationObservations(collection[0]);
9716
+ query.recordSingleRelationAggregateObservations(collection[0]);
9717
+ query.rebindRowsToSerializedResult(rows, data);
9718
+ return data;
8241
9719
  }
8242
9720
  async sole() {
8243
- const entities = await this.limit(2).get();
8244
- if (entities.length === 0) {
9721
+ const query = this.limit(2);
9722
+ const { collection, rows } = await query.getRowsAndEntities();
9723
+ if (collection.length === 0) {
8245
9724
  throw new ModelNotFoundException(this.repository.definition.name, `${this.repository.definition.name} query expected exactly one result but found 0.`);
8246
9725
  }
8247
- if (entities.length !== 1) {
8248
- throw new HydrationError(`${this.repository.definition.name} query expected exactly one result but found ${entities.length}.`);
9726
+ if (collection.length !== 1) {
9727
+ throw new HydrationError(`${this.repository.definition.name} query expected exactly one result but found ${collection.length}.`);
8249
9728
  }
8250
- return entities[0];
9729
+ const entity = collection[0];
9730
+ query.recordSingleRelationObservations(entity);
9731
+ query.recordSingleRelationAggregateObservations(entity);
9732
+ query.rebindRowsToSerializedResult(rows, entity);
9733
+ return entity;
8251
9734
  }
8252
9735
  async soleJson() {
8253
- return (await this.sole()).toJSON();
9736
+ const query = this.limit(2);
9737
+ const { collection, rows } = await query.getRowsAndEntities();
9738
+ if (collection.length === 0) {
9739
+ throw new ModelNotFoundException(this.repository.definition.name, `${this.repository.definition.name} query expected exactly one result but found 0.`);
9740
+ }
9741
+ if (collection.length !== 1) {
9742
+ throw new HydrationError(`${this.repository.definition.name} query expected exactly one result but found ${collection.length}.`);
9743
+ }
9744
+ const data = collection[0].toJSON();
9745
+ query.recordSingleRelationObservations(collection[0]);
9746
+ query.recordSingleRelationAggregateObservations(collection[0]);
9747
+ query.rebindRowsToSerializedResult(rows, data);
9748
+ return data;
8254
9749
  }
8255
9750
  async paginate(perPage = 15, page = 1, options = {}) {
8256
9751
  assertPositiveInteger(perPage, "Per-page value", (message) => new HydrationError(message));
8257
9752
  assertPositiveInteger(page, "Page", (message) => new HydrationError(message));
8258
9753
  const pageName = normalizePaginationParameterName(options.pageName, "page", (message) => new HydrationError(message));
8259
- const entities = await this.getUnpaginatedEntities();
9754
+ const { collection: entities, rows } = await this.getUnpaginatedRowsAndEntities();
8260
9755
  const total = entities.length;
8261
9756
  const offset = (page - 1) * perPage;
8262
9757
  const data = entities.slice(offset, offset + perPage);
8263
9758
  const from = data.length === 0 ? null : offset + 1;
8264
9759
  const to = data.length === 0 ? null : offset + data.length;
8265
- return createPaginator(this.repository.createCollection(data), {
9760
+ const collection = this.repository.createCollection(data);
9761
+ this.recordPaginatedRelationObservations(collection);
9762
+ this.recordPaginatedRelationAggregateObservations(collection);
9763
+ const result = createPaginator(collection, {
8266
9764
  total,
8267
9765
  perPage,
8268
9766
  pageName,
@@ -8272,22 +9770,50 @@ var ModelQueryBuilder = class _ModelQueryBuilder {
8272
9770
  to,
8273
9771
  hasMorePages: offset + data.length < total
8274
9772
  });
9773
+ this.rebindRowsToPaginatedResult(rows, result.data, result.meta, page, pageName, perPage, total, offset);
9774
+ return result;
8275
9775
  }
8276
9776
  async paginateJson(perPage = 15, page = 1, options = {}) {
8277
- return (await this.paginate(perPage, page, options)).toJSON();
9777
+ assertPositiveInteger(perPage, "Per-page value", (message) => new HydrationError(message));
9778
+ assertPositiveInteger(page, "Page", (message) => new HydrationError(message));
9779
+ const pageName = normalizePaginationParameterName(options.pageName, "page", (message) => new HydrationError(message));
9780
+ const { collection, rows } = await this.getUnpaginatedRowsAndEntities();
9781
+ const total = collection.length;
9782
+ const offset = (page - 1) * perPage;
9783
+ const pageCollection = this.repository.createCollection(collection.slice(offset, offset + perPage));
9784
+ const data = pageCollection.toJSON();
9785
+ this.recordPaginatedRelationObservations(pageCollection);
9786
+ this.recordPaginatedRelationAggregateObservations(pageCollection);
9787
+ const from = data.length === 0 ? null : offset + 1;
9788
+ const to = data.length === 0 ? null : offset + data.length;
9789
+ const result = createPaginator(data, {
9790
+ total,
9791
+ perPage,
9792
+ pageName,
9793
+ currentPage: page,
9794
+ lastPage: Math.max(1, Math.ceil(total / perPage)),
9795
+ from,
9796
+ to,
9797
+ hasMorePages: offset + data.length < total
9798
+ }).toJSON();
9799
+ this.rebindRowsToPaginatedResult(rows, result.data, result.meta, page, pageName, perPage, total, offset);
9800
+ return result;
8278
9801
  }
8279
9802
  async simplePaginate(perPage = 15, page = 1, options = {}) {
8280
9803
  assertPositiveInteger(perPage, "Per-page value", (message) => new HydrationError(message));
8281
9804
  assertPositiveInteger(page, "Page", (message) => new HydrationError(message));
8282
9805
  const pageName = normalizePaginationParameterName(options.pageName, "page", (message) => new HydrationError(message));
8283
- const entities = await this.getUnpaginatedEntities();
9806
+ const { collection: entities, rows } = await this.getUnpaginatedRowsAndEntities();
8284
9807
  const offset = (page - 1) * perPage;
8285
9808
  const pageEntities = entities.slice(offset, offset + perPage + 1);
8286
9809
  const hasMorePages = pageEntities.length > perPage;
8287
9810
  const data = hasMorePages ? pageEntities.slice(0, perPage) : pageEntities;
8288
9811
  const from = data.length === 0 ? null : offset + 1;
8289
9812
  const to = data.length === 0 ? null : offset + data.length;
8290
- return createSimplePaginator(this.repository.createCollection(data), {
9813
+ const collection = this.repository.createCollection(data);
9814
+ this.recordPaginatedRelationObservations(collection);
9815
+ this.recordPaginatedRelationAggregateObservations(collection);
9816
+ const result = createSimplePaginator(collection, {
8291
9817
  perPage,
8292
9818
  pageName,
8293
9819
  currentPage: page,
@@ -8295,9 +9821,33 @@ var ModelQueryBuilder = class _ModelQueryBuilder {
8295
9821
  to,
8296
9822
  hasMorePages
8297
9823
  });
9824
+ this.rebindRowsToSimplePaginatedResult(rows, result.data, result.meta, page, pageName, perPage, entities.length, hasMorePages, offset);
9825
+ return result;
8298
9826
  }
8299
9827
  async simplePaginateJson(perPage = 15, page = 1, options = {}) {
8300
- return (await this.simplePaginate(perPage, page, options)).toJSON();
9828
+ assertPositiveInteger(perPage, "Per-page value", (message) => new HydrationError(message));
9829
+ assertPositiveInteger(page, "Page", (message) => new HydrationError(message));
9830
+ const pageName = normalizePaginationParameterName(options.pageName, "page", (message) => new HydrationError(message));
9831
+ const { collection, rows } = await this.getUnpaginatedRowsAndEntities();
9832
+ const offset = (page - 1) * perPage;
9833
+ const pageEntities = collection.slice(offset, offset + perPage + 1);
9834
+ const hasMorePages = pageEntities.length > perPage;
9835
+ const pageCollection = this.repository.createCollection(hasMorePages ? pageEntities.slice(0, perPage) : pageEntities);
9836
+ const data = pageCollection.toJSON();
9837
+ this.recordPaginatedRelationObservations(pageCollection);
9838
+ this.recordPaginatedRelationAggregateObservations(pageCollection);
9839
+ const from = data.length === 0 ? null : offset + 1;
9840
+ const to = data.length === 0 ? null : offset + data.length;
9841
+ const result = createSimplePaginator(data, {
9842
+ perPage,
9843
+ pageName,
9844
+ currentPage: page,
9845
+ from,
9846
+ to,
9847
+ hasMorePages
9848
+ }).toJSON();
9849
+ this.rebindRowsToSimplePaginatedResult(rows, result.data, result.meta, page, pageName, perPage, collection.length, hasMorePages, offset);
9850
+ return result;
8301
9851
  }
8302
9852
  async cursorPaginate(perPage = 15, cursor = null, options = {}) {
8303
9853
  assertPositiveInteger(perPage, "Per-page value", (message) => new HydrationError(message));
@@ -8305,7 +9855,7 @@ var ModelQueryBuilder = class _ModelQueryBuilder {
8305
9855
  const decodedCursor = decodeValueCursor(cursor, (message) => new HydrationError(message));
8306
9856
  const orderedQuery = this.prepareCursorPaginationQuery();
8307
9857
  const cursorOrders = orderedQuery.resolveCursorOrders();
8308
- const entities = await orderedQuery.getUnpaginatedEntities();
9858
+ const { collection: entities, rows } = await orderedQuery.getUnpaginatedRowsAndEntities();
8309
9859
  const filteredEntities = decodedCursor ? entities.filter((entity) => isRowAfterCursor(
8310
9860
  cursorOrders.map((order) => orderedQuery.readCursorColumnValue(entity, order.column)),
8311
9861
  decodedCursor.values,
@@ -8315,15 +9865,65 @@ var ModelQueryBuilder = class _ModelQueryBuilder {
8315
9865
  const hasMorePages = pageEntities.length > perPage;
8316
9866
  const data = hasMorePages ? pageEntities.slice(0, perPage) : pageEntities;
8317
9867
  const lastEntity = data.at(-1);
8318
- return createCursorPaginator(this.repository.createCollection(data), {
9868
+ const collection = orderedQuery.repository.createCollection(data);
9869
+ const observedPageRows = orderedQuery.repository.createCollection(pageEntities).toJSON();
9870
+ orderedQuery.recordPaginatedRelationObservations(collection);
9871
+ orderedQuery.recordPaginatedRelationAggregateObservations(collection);
9872
+ const result = createCursorPaginator(collection, {
8319
9873
  perPage,
8320
9874
  cursorName,
8321
9875
  nextCursor: hasMorePages && lastEntity ? encodeValueCursor(cursorOrders.map((order) => orderedQuery.readCursorColumnValue(lastEntity, order.column))) : null,
8322
9876
  prevCursor: cursor
8323
9877
  });
9878
+ if (cursor === null) {
9879
+ orderedQuery.rebindRowsToCursorPaginatedResult(rows, result.data, {
9880
+ cursorName: result.cursorName,
9881
+ nextCursor: result.nextCursor,
9882
+ perPage: result.perPage,
9883
+ prevCursor: result.prevCursor
9884
+ }, observedPageRows, entities.length, hasMorePages);
9885
+ } else {
9886
+ orderedQuery.rebindRowsToSerializedResult(rows, collection);
9887
+ }
9888
+ return result;
8324
9889
  }
8325
9890
  async cursorPaginateJson(perPage = 15, cursor = null, options = {}) {
8326
- return (await this.cursorPaginate(perPage, cursor, options)).toJSON();
9891
+ assertPositiveInteger(perPage, "Per-page value", (message) => new HydrationError(message));
9892
+ const cursorName = normalizePaginationParameterName(options.cursorName, "cursor", (message) => new HydrationError(message));
9893
+ const decodedCursor = decodeValueCursor(cursor, (message) => new HydrationError(message));
9894
+ const orderedQuery = this.prepareCursorPaginationQuery();
9895
+ const cursorOrders = orderedQuery.resolveCursorOrders();
9896
+ const { collection, rows } = await orderedQuery.getUnpaginatedRowsAndEntities();
9897
+ const filteredEntities = decodedCursor ? collection.filter((entity) => isRowAfterCursor(
9898
+ cursorOrders.map((order) => orderedQuery.readCursorColumnValue(entity, order.column)),
9899
+ decodedCursor.values,
9900
+ cursorOrders
9901
+ )) : collection;
9902
+ const pageEntities = filteredEntities.slice(0, perPage + 1);
9903
+ const hasMorePages = pageEntities.length > perPage;
9904
+ const pageCollection = orderedQuery.repository.createCollection(hasMorePages ? pageEntities.slice(0, perPage) : pageEntities);
9905
+ const data = pageCollection.toJSON();
9906
+ const observedPageRows = orderedQuery.repository.createCollection(pageEntities).toJSON();
9907
+ const lastEntity = pageCollection.at(-1);
9908
+ orderedQuery.recordPaginatedRelationObservations(pageCollection);
9909
+ orderedQuery.recordPaginatedRelationAggregateObservations(pageCollection);
9910
+ const result = createCursorPaginator(data, {
9911
+ perPage,
9912
+ cursorName,
9913
+ nextCursor: hasMorePages && lastEntity ? encodeValueCursor(cursorOrders.map((order) => orderedQuery.readCursorColumnValue(lastEntity, order.column))) : null,
9914
+ prevCursor: cursor
9915
+ }).toJSON();
9916
+ if (cursor === null) {
9917
+ orderedQuery.rebindRowsToCursorPaginatedResult(rows, result.data, {
9918
+ cursorName: result.cursorName,
9919
+ nextCursor: result.nextCursor,
9920
+ perPage: result.perPage,
9921
+ prevCursor: result.prevCursor
9922
+ }, observedPageRows, collection.length, hasMorePages);
9923
+ } else {
9924
+ orderedQuery.rebindRowsToSerializedResult(rows, result.data);
9925
+ }
9926
+ return result;
8327
9927
  }
8328
9928
  async chunk(size, callback) {
8329
9929
  assertPositiveInteger(size, "Chunk size", (message) => new HydrationError(message));
@@ -8387,21 +9987,42 @@ var ModelQueryBuilder = class _ModelQueryBuilder {
8387
9987
  }
8388
9988
  }
8389
9989
  async count() {
8390
- return (await this.get()).length;
9990
+ const collection = await this.get();
9991
+ const result = collection.length;
9992
+ rebindDatabaseQueryObservationAggregate(collection, result, Object.freeze({ kind: "count" }));
9993
+ return result;
8391
9994
  }
8392
9995
  async exists() {
8393
- return await this.count() > 0;
9996
+ const count = await this.count();
9997
+ const result = count > 0;
9998
+ rebindDatabaseQueryObservationAggregate(count, result, Object.freeze({
9999
+ count,
10000
+ kind: "count",
10001
+ output: "boolean"
10002
+ }));
10003
+ return result;
8394
10004
  }
8395
10005
  async doesntExist() {
8396
- return !await this.exists();
10006
+ const count = await this.count();
10007
+ const result = count === 0;
10008
+ rebindDatabaseQueryObservationAggregate(count, result, Object.freeze({
10009
+ count,
10010
+ kind: "count",
10011
+ output: "inverseBoolean"
10012
+ }));
10013
+ return result;
8397
10014
  }
8398
10015
  async pluck(column2) {
8399
- const entities = await this.get();
8400
- return entities.map((entity) => entity.get(column2));
10016
+ const { collection, rows } = await this.getRowsAndEntities();
10017
+ const result = collection.map((entity) => entity.get(column2));
10018
+ rebindDatabaseQueryObservationScalarList(rows, result, column2);
10019
+ return result;
8401
10020
  }
8402
10021
  async value(column2) {
8403
10022
  const entity = await this.first();
8404
- return entity?.get(column2);
10023
+ const value = entity?.get(column2);
10024
+ rebindDatabaseQueryObservationScalar(entity, value, column2);
10025
+ return value;
8405
10026
  }
8406
10027
  async valueOrFail(column2) {
8407
10028
  const entity = await this.first();
@@ -8412,6 +10033,7 @@ var ModelQueryBuilder = class _ModelQueryBuilder {
8412
10033
  if (typeof value === "undefined") {
8413
10034
  throw new HydrationError(`${this.repository.definition.name} query returned no value for column "${column2}".`);
8414
10035
  }
10036
+ rebindDatabaseQueryObservationScalar(entity, value, column2);
8415
10037
  return value;
8416
10038
  }
8417
10039
  async soleValue(column2) {
@@ -8420,36 +10042,78 @@ var ModelQueryBuilder = class _ModelQueryBuilder {
8420
10042
  if (typeof value === "undefined") {
8421
10043
  throw new HydrationError(`${this.repository.definition.name} query returned no value for column "${column2}".`);
8422
10044
  }
10045
+ rebindDatabaseQueryObservationScalar(entity, value, column2);
8423
10046
  return value;
8424
10047
  }
8425
10048
  async sum(column2) {
8426
10049
  const entities = await this.get();
8427
10050
  if (entities.length === 0) {
10051
+ if (hasActiveDatabaseDependencyCollector()) {
10052
+ rebindDatabaseQueryObservationAggregate(entities, 0, Object.freeze({ column: column2, kind: "sum" }));
10053
+ }
8428
10054
  return 0;
8429
10055
  }
8430
- return this.extractNumericValues(entities, column2, "sum").reduce((sum, value) => sum + value, 0);
10056
+ const result = this.extractNumericValues(entities, column2, "sum").reduce((sum, value) => sum + value, 0);
10057
+ if (hasActiveDatabaseDependencyCollector()) {
10058
+ rebindDatabaseQueryObservationAggregate(entities, result, Object.freeze({ column: column2, kind: "sum" }));
10059
+ }
10060
+ return result;
8431
10061
  }
8432
10062
  async avg(column2) {
8433
10063
  const entities = await this.get();
8434
10064
  if (entities.length === 0) {
10065
+ if (hasActiveDatabaseDependencyCollector()) {
10066
+ rebindDatabaseQueryObservationAggregate(entities, null, Object.freeze({ column: column2, count: 0, kind: "avg", sum: 0 }));
10067
+ }
8435
10068
  return null;
8436
10069
  }
8437
10070
  const values = this.extractNumericValues(entities, column2, "avg");
8438
- return values.reduce((sum, value) => sum + value, 0) / values.length;
10071
+ const sum = values.reduce((total, value) => total + value, 0);
10072
+ const result = sum / values.length;
10073
+ if (hasActiveDatabaseDependencyCollector()) {
10074
+ rebindDatabaseQueryObservationAggregate(entities, result, Object.freeze({ column: column2, count: values.length, kind: "avg", sum }));
10075
+ }
10076
+ return result;
8439
10077
  }
8440
10078
  async min(column2) {
8441
10079
  const entities = await this.get();
8442
10080
  if (entities.length === 0) {
10081
+ if (hasActiveDatabaseDependencyCollector()) {
10082
+ rebindDatabaseQueryObservationAggregate(entities, null, Object.freeze({ column: column2, kind: "min" }));
10083
+ }
8443
10084
  return null;
8444
10085
  }
8445
- return Math.min(...this.extractNumericValues(entities, column2, "min"));
10086
+ const values = this.extractNumericValues(entities, column2, "min");
10087
+ const result = Math.min(...values);
10088
+ if (hasActiveDatabaseDependencyCollector()) {
10089
+ rebindDatabaseQueryObservationAggregate(entities, result, Object.freeze({
10090
+ column: column2,
10091
+ currentValueCount: values.filter((value) => value === result).length,
10092
+ kind: "min",
10093
+ valueCounts: createAggregateValueCounts(values)
10094
+ }));
10095
+ }
10096
+ return result;
8446
10097
  }
8447
10098
  async max(column2) {
8448
10099
  const entities = await this.get();
8449
10100
  if (entities.length === 0) {
10101
+ if (hasActiveDatabaseDependencyCollector()) {
10102
+ rebindDatabaseQueryObservationAggregate(entities, null, Object.freeze({ column: column2, kind: "max" }));
10103
+ }
8450
10104
  return null;
8451
10105
  }
8452
- return Math.max(...this.extractNumericValues(entities, column2, "max"));
10106
+ const values = this.extractNumericValues(entities, column2, "max");
10107
+ const result = Math.max(...values);
10108
+ if (hasActiveDatabaseDependencyCollector()) {
10109
+ rebindDatabaseQueryObservationAggregate(entities, result, Object.freeze({
10110
+ column: column2,
10111
+ currentValueCount: values.filter((value) => value === result).length,
10112
+ kind: "max",
10113
+ valueCounts: createAggregateValueCounts(values)
10114
+ }));
10115
+ }
10116
+ return result;
8453
10117
  }
8454
10118
  async firstOrFail() {
8455
10119
  const entity = await this.first();
@@ -8722,68 +10386,296 @@ var ModelQueryBuilder = class _ModelQueryBuilder {
8722
10386
  column: column2
8723
10387
  }));
8724
10388
  }
8725
- parseAggregateRelation(spec) {
8726
- const [relationPart, aliasPart] = spec.split(/\s+as\s+/i);
8727
- const relation = relationPart?.trim();
8728
- const alias = aliasPart?.trim();
8729
- if (!relation) {
8730
- throw new HydrationError("Aggregate relation names cannot be empty.");
10389
+ parseAggregateRelation(spec) {
10390
+ const [relationPart, aliasPart] = spec.split(/\s+as\s+/i);
10391
+ const relation = relationPart?.trim();
10392
+ const alias = aliasPart?.trim();
10393
+ if (!relation) {
10394
+ throw new HydrationError("Aggregate relation names cannot be empty.");
10395
+ }
10396
+ if (typeof aliasPart !== "undefined" && !alias) {
10397
+ throw new HydrationError("Aggregate relation aliases cannot be empty.");
10398
+ }
10399
+ return alias ? { relation, alias } : { relation };
10400
+ }
10401
+ resolveBelongsToRelation(relatedEntity, relationName) {
10402
+ const resolvedName = this.resolveBelongsToRelationName(relatedEntity, relationName);
10403
+ const relation = this.repository.getRelationDefinition(resolvedName);
10404
+ if (relation.kind !== "belongsTo") {
10405
+ throw new RelationError(`Relation "${resolvedName}" on model "${this.repository.definition.name}" is not a belongs-to relation.`);
10406
+ }
10407
+ return relation;
10408
+ }
10409
+ applyBelongsToFilter(relatedEntity, relationName, boolean) {
10410
+ const resolvedRelationName = this.resolveBelongsToRelationName(relatedEntity, relationName);
10411
+ const relation = this.resolveBelongsToRelation(relatedEntity, resolvedRelationName);
10412
+ const ownerValue = relatedEntity.get(relation.ownerKey);
10413
+ if (ownerValue === null || typeof ownerValue === "undefined") {
10414
+ return boolean === "and" ? this.whereDoesntHave(resolvedRelationName) : this.orWhereDoesntHave(resolvedRelationName);
10415
+ }
10416
+ return boolean === "and" ? this.whereHas(resolvedRelationName, (query) => query.where(relation.ownerKey, ownerValue)) : this.orWhereHas(resolvedRelationName, (query) => query.where(relation.ownerKey, ownerValue));
10417
+ }
10418
+ resolveBelongsToRelationName(relatedEntity, relationName) {
10419
+ if (relationName) {
10420
+ return relationName;
10421
+ }
10422
+ const relatedDefinition = relatedEntity.getRepository().definition;
10423
+ const candidates = Object.entries(this.repository.definition.relations).filter(([, relation]) => relation.kind === "belongsTo").map(([name, relation]) => [name, relation]).filter(([, relation]) => {
10424
+ const ref = relation.related();
10425
+ const definition = "definition" in ref ? ref.definition : ref;
10426
+ return definition.table.tableName === relatedDefinition.table.tableName;
10427
+ }).map(([name]) => name);
10428
+ if (candidates.length === 1) {
10429
+ return candidates[0];
10430
+ }
10431
+ if (candidates.length === 0) {
10432
+ throw new RelationError(
10433
+ `No belongs-to relation on model "${this.repository.definition.name}" matches related model "${relatedDefinition.name}".`
10434
+ );
10435
+ }
10436
+ throw new RelationError(
10437
+ `Multiple belongs-to relations on model "${this.repository.definition.name}" match related model "${relatedDefinition.name}". Specify the relation name explicitly.`
10438
+ );
10439
+ }
10440
+ extractNumericValues(entities, column2, kind) {
10441
+ return entities.map((entity) => {
10442
+ const value = entity.get(column2);
10443
+ if (typeof value !== "number" || Number.isNaN(value)) {
10444
+ throw new HydrationError(`Model aggregate "${kind}" requires numeric values for column "${column2}".`);
10445
+ }
10446
+ return value;
10447
+ });
10448
+ }
10449
+ async getUnpaginatedEntities() {
10450
+ return this.clone(this.tableQuery.limit(void 0).offset(void 0)).get();
10451
+ }
10452
+ async getUnpaginatedRowsAndEntities() {
10453
+ const query = this.clone(this.tableQuery.limit(void 0).offset(void 0));
10454
+ return await query.getRowsAndEntities();
10455
+ }
10456
+ async getRowsAndEntities() {
10457
+ const rows = await this.tableQuery.get();
10458
+ return {
10459
+ collection: await this.hydrateRows(rows),
10460
+ rows
10461
+ };
10462
+ }
10463
+ recordCollectionRelationAggregateObservations(collection) {
10464
+ this.repository.recordRelationAggregateObservations(
10465
+ collection,
10466
+ this.aggregateLoads,
10467
+ (index, key) => Object.freeze([index, key])
10468
+ );
10469
+ }
10470
+ recordCollectionRelationObservations(collection) {
10471
+ this.repository.recordRelationObservations(
10472
+ collection,
10473
+ this.eagerLoads,
10474
+ (index, relationName) => Object.freeze([index, relationName])
10475
+ );
10476
+ }
10477
+ recordSingleRelationObservations(entity) {
10478
+ if (!entity) {
10479
+ return;
10480
+ }
10481
+ this.repository.recordRelationObservations(
10482
+ [entity],
10483
+ this.eagerLoads,
10484
+ (_index, relationName) => Object.freeze([relationName])
10485
+ );
10486
+ }
10487
+ recordPaginatedRelationObservations(collection) {
10488
+ this.repository.recordRelationObservations(
10489
+ collection,
10490
+ this.eagerLoads,
10491
+ (index, relationName) => Object.freeze(["data", index, relationName])
10492
+ );
10493
+ }
10494
+ recordSingleRelationAggregateObservations(entity) {
10495
+ if (!entity) {
10496
+ return;
10497
+ }
10498
+ this.repository.recordRelationAggregateObservations(
10499
+ [entity],
10500
+ this.aggregateLoads,
10501
+ (_index, key) => Object.freeze([key])
10502
+ );
10503
+ }
10504
+ recordPaginatedRelationAggregateObservations(collection) {
10505
+ this.repository.recordRelationAggregateObservations(
10506
+ collection,
10507
+ this.aggregateLoads,
10508
+ (index, key) => Object.freeze(["data", index, key])
10509
+ );
10510
+ }
10511
+ canBindRowsToSerializedResultBase() {
10512
+ return Object.keys(this.repository.definition.accessors).length === 0 && Object.keys(this.repository.definition.casts).length === 0 && Object.keys(this.queryCasts).length === 0 && this.repository.definition.appended.length === 0 && this.repository.definition.hidden.length === 0 && this.repository.definition.visible.length === 0 && this.relationFilters.length === 0 && this.aggregateLoads.length === 0;
10513
+ }
10514
+ canBindRowsToSerializedResult() {
10515
+ return this.canBindRowsToSerializedResultBase() && this.eagerLoads.length === 0;
10516
+ }
10517
+ createPatchableEagerRelationHydrations() {
10518
+ if (!this.canBindRowsToSerializedResultBase() || this.eagerLoads.length === 0) {
10519
+ return void 0;
10520
+ }
10521
+ const belongsToHydrations = [];
10522
+ const relatedHydrations = [];
10523
+ for (const load of this.eagerLoads) {
10524
+ if (load.relation.includes(".")) {
10525
+ return void 0;
10526
+ }
10527
+ const relation = this.repository.getRelationDefinition(load.relation);
10528
+ if (relation.kind === "belongsTo") {
10529
+ if (relation.constraint || load.constraint) {
10530
+ return void 0;
10531
+ }
10532
+ const related = relation.related();
10533
+ const relatedDefinition = "definition" in related ? related.definition : related;
10534
+ belongsToHydrations.push(Object.freeze({
10535
+ foreignKey: relation.foreignKey,
10536
+ ownerKey: relation.ownerKey,
10537
+ relationKey: load.relation,
10538
+ relatedConnectionName: relatedDefinition.connectionName ?? this.getConnectionName(),
10539
+ relatedTableName: relatedDefinition.table.tableName
10540
+ }));
10541
+ continue;
10542
+ }
10543
+ if (relation.kind === "hasMany" || relation.kind === "hasOne") {
10544
+ const hydrationQuery = this.createPatchableRelatedHydrationQuery(relation, load.constraint);
10545
+ if (!hydrationQuery) {
10546
+ return void 0;
10547
+ }
10548
+ const related = relation.related();
10549
+ const relatedDefinition = "definition" in related ? related.definition : related;
10550
+ relatedHydrations.push(Object.freeze({
10551
+ foreignKey: relation.foreignKey,
10552
+ kind: relation.kind,
10553
+ localKey: relation.localKey,
10554
+ orderBy: hydrationQuery.orderBy,
10555
+ predicates: hydrationQuery.predicates,
10556
+ relationKey: load.relation,
10557
+ relatedConnectionName: relatedDefinition.connectionName ?? this.getConnectionName(),
10558
+ relatedTableName: relatedDefinition.table.tableName
10559
+ }));
10560
+ continue;
10561
+ }
10562
+ return void 0;
10563
+ }
10564
+ return Object.freeze({
10565
+ belongsToHydrations: belongsToHydrations.length > 0 ? Object.freeze(belongsToHydrations) : void 0,
10566
+ relatedHydrations: relatedHydrations.length > 0 ? Object.freeze(relatedHydrations) : void 0
10567
+ });
10568
+ }
10569
+ createPatchableRelatedHydrationQuery(relation, constraint) {
10570
+ if (!relation.constraint && !constraint) {
10571
+ return Object.freeze({
10572
+ orderBy: Object.freeze([]),
10573
+ predicates: Object.freeze([])
10574
+ });
8731
10575
  }
8732
- if (typeof aliasPart !== "undefined" && !alias) {
8733
- throw new HydrationError("Aggregate relation aliases cannot be empty.");
10576
+ const related = relation.related();
10577
+ if (!this.isQueryableModelReference(related)) {
10578
+ return void 0;
8734
10579
  }
8735
- return alias ? { relation, alias } : { relation };
8736
- }
8737
- resolveBelongsToRelation(relatedEntity, relationName) {
8738
- const resolvedName = this.resolveBelongsToRelationName(relatedEntity, relationName);
8739
- const relation = this.repository.getRelationDefinition(resolvedName);
8740
- if (relation.kind !== "belongsTo") {
8741
- throw new RelationError(`Relation "${resolvedName}" on model "${this.repository.definition.name}" is not a belongs-to relation.`);
10580
+ let query = related.query();
10581
+ const relationResult = relation.constraint?.(query);
10582
+ query = relationResult instanceof _ModelQueryBuilder ? relationResult : query;
10583
+ if (constraint) {
10584
+ const result = constraint(query);
10585
+ query = result instanceof _ModelQueryBuilder ? result : query;
10586
+ }
10587
+ const plan = query.getTableQueryBuilder().getPlan();
10588
+ const relatedDefinition = "definition" in related ? related.definition : related;
10589
+ const observation = createDatabaseQueryObservation(
10590
+ plan,
10591
+ relatedDefinition.connectionName ?? this.getConnectionName(),
10592
+ Object.freeze([])
10593
+ );
10594
+ if (!observation?.patchable || typeof observation.limit !== "undefined" || typeof observation.offset !== "undefined") {
10595
+ return void 0;
8742
10596
  }
8743
- return relation;
10597
+ return Object.freeze({
10598
+ orderBy: observation.orderBy,
10599
+ predicates: observation.predicates
10600
+ });
8744
10601
  }
8745
- applyBelongsToFilter(relatedEntity, relationName, boolean) {
8746
- const resolvedRelationName = this.resolveBelongsToRelationName(relatedEntity, relationName);
8747
- const relation = this.resolveBelongsToRelation(relatedEntity, resolvedRelationName);
8748
- const ownerValue = relatedEntity.get(relation.ownerKey);
8749
- if (ownerValue === null || typeof ownerValue === "undefined") {
8750
- return boolean === "and" ? this.whereDoesntHave(resolvedRelationName) : this.orWhereDoesntHave(resolvedRelationName);
8751
- }
8752
- return boolean === "and" ? this.whereHas(resolvedRelationName, (query) => query.where(relation.ownerKey, ownerValue)) : this.orWhereHas(resolvedRelationName, (query) => query.where(relation.ownerKey, ownerValue));
10602
+ isQueryableModelReference(value) {
10603
+ return "query" in value && typeof value.query === "function";
8753
10604
  }
8754
- resolveBelongsToRelationName(relatedEntity, relationName) {
8755
- if (relationName) {
8756
- return relationName;
10605
+ rebindRowsToSerializedResult(rows, result) {
10606
+ if (!hasActiveDatabaseDependencyCollector()) {
10607
+ return;
8757
10608
  }
8758
- const relatedDefinition = relatedEntity.getRepository().definition;
8759
- const candidates = Object.entries(this.repository.definition.relations).filter(([, relation]) => relation.kind === "belongsTo").map(([name, relation]) => [name, relation]).filter(([, relation]) => {
8760
- const ref = relation.related();
8761
- const definition = "definition" in ref ? ref.definition : ref;
8762
- return definition.table.tableName === relatedDefinition.table.tableName;
8763
- }).map(([name]) => name);
8764
- if (candidates.length === 1) {
8765
- return candidates[0];
10609
+ if (this.canBindRowsToSerializedResult()) {
10610
+ rebindDatabaseQueryObservationResult(rows, result);
10611
+ return;
8766
10612
  }
8767
- if (candidates.length === 0) {
8768
- throw new RelationError(
8769
- `No belongs-to relation on model "${this.repository.definition.name}" matches related model "${relatedDefinition.name}".`
10613
+ const hydrations = this.createPatchableEagerRelationHydrations();
10614
+ if (hydrations) {
10615
+ rebindDatabaseQueryObservationHydratedResult(
10616
+ rows,
10617
+ result,
10618
+ hydrations.belongsToHydrations ?? Object.freeze([]),
10619
+ hydrations.relatedHydrations
8770
10620
  );
10621
+ return;
8771
10622
  }
8772
- throw new RelationError(
8773
- `Multiple belongs-to relations on model "${this.repository.definition.name}" match related model "${relatedDefinition.name}". Specify the relation name explicitly.`
8774
- );
10623
+ disableDatabaseQueryObservationPatching(rows);
8775
10624
  }
8776
- extractNumericValues(entities, column2, kind) {
8777
- return entities.map((entity) => {
8778
- const value = entity.get(column2);
8779
- if (typeof value !== "number" || Number.isNaN(value)) {
8780
- throw new HydrationError(`Model aggregate "${kind}" requires numeric values for column "${column2}".`);
8781
- }
8782
- return value;
8783
- });
10625
+ rebindRowsToPaginatedResult(rows, data, meta, currentPage, pageName, perPage, total, offset) {
10626
+ if (!hasActiveDatabaseDependencyCollector()) {
10627
+ return;
10628
+ }
10629
+ const hydrations = this.createPatchableEagerRelationHydrations();
10630
+ if (!this.canBindRowsToSerializedResult() && !hydrations) {
10631
+ disableDatabaseQueryObservationPatching(rows);
10632
+ return;
10633
+ }
10634
+ rebindDatabaseQueryObservationPagination(rows, data, meta, Object.freeze({
10635
+ currentPage,
10636
+ kind: "standard",
10637
+ pageName,
10638
+ perPage,
10639
+ total
10640
+ }), offset, hydrations?.belongsToHydrations, hydrations?.relatedHydrations);
8784
10641
  }
8785
- async getUnpaginatedEntities() {
8786
- return this.clone(this.tableQuery.limit(void 0).offset(void 0)).get();
10642
+ rebindRowsToSimplePaginatedResult(rows, data, meta, currentPage, pageName, perPage, rowCount, hasMorePages, offset) {
10643
+ if (!hasActiveDatabaseDependencyCollector()) {
10644
+ return;
10645
+ }
10646
+ const hydrations = this.createPatchableEagerRelationHydrations();
10647
+ if (!this.canBindRowsToSerializedResult() && !hydrations) {
10648
+ disableDatabaseQueryObservationPatching(rows);
10649
+ return;
10650
+ }
10651
+ rebindDatabaseQueryObservationPagination(rows, data, meta, Object.freeze({
10652
+ currentPage,
10653
+ hasMorePages,
10654
+ kind: "simple",
10655
+ pageName,
10656
+ perPage,
10657
+ rowCount
10658
+ }), offset, hydrations?.belongsToHydrations, hydrations?.relatedHydrations);
10659
+ }
10660
+ rebindRowsToCursorPaginatedResult(rows, data, meta, pageRows, rowCount, hasMorePages) {
10661
+ if (!hasActiveDatabaseDependencyCollector()) {
10662
+ return;
10663
+ }
10664
+ const hydrations = this.createPatchableEagerRelationHydrations();
10665
+ if (!this.canBindRowsToSerializedResult() && !hydrations) {
10666
+ disableDatabaseQueryObservationPatching(rows);
10667
+ return;
10668
+ }
10669
+ rebindDatabaseQueryObservationCursorPagination(rows, data, meta, Object.freeze({
10670
+ cursorName: meta.cursorName,
10671
+ hasMorePages,
10672
+ kind: "cursor",
10673
+ nextCursor: meta.nextCursor,
10674
+ perPage: meta.perPage,
10675
+ prevCursor: meta.prevCursor,
10676
+ rows: pageRows,
10677
+ rowCount
10678
+ }), hydrations?.belongsToHydrations, hydrations?.relatedHydrations);
8787
10679
  }
8788
10680
  prepareCursorPaginationQuery() {
8789
10681
  const plan = this.tableQuery.getPlan();
@@ -8846,6 +10738,8 @@ function setAutomaticEagerLoading(definition, value) {
8846
10738
  }
8847
10739
 
8848
10740
  // src/model/ModelRepository.ts
10741
+ var relationAggregateObservationMetadata = /* @__PURE__ */ new WeakMap();
10742
+ var relationQueryObservationMetadata = /* @__PURE__ */ new WeakMap();
8849
10743
  function hasDefinition(reference) {
8850
10744
  return "definition" in reference;
8851
10745
  }
@@ -9190,6 +11084,7 @@ var ModelRepository = class _ModelRepository {
9190
11084
  if (entities.length === 0 || aggregates.length === 0) {
9191
11085
  return;
9192
11086
  }
11087
+ const observationCount = hasActiveDatabaseDependencyCollector() ? readDatabaseQueryObservationCount() : void 0;
9193
11088
  for (const aggregate of aggregates) {
9194
11089
  if (aggregate.relation.includes(".")) {
9195
11090
  throw new SecurityError("Nested relation aggregates are not supported yet.");
@@ -9202,6 +11097,12 @@ var ModelRepository = class _ModelRepository {
9202
11097
  const parentKey = this.getRelationParentValue(entity, relation);
9203
11098
  const count = counts.get(parentKey) ?? 0;
9204
11099
  entity.setComputed(key, aggregate.kind === "count" ? count : count > 0);
11100
+ if (aggregate.kind === "exists" && typeof observationCount === "number") {
11101
+ this.setRelationAggregateObservationMetadata(entity, key, Object.freeze({
11102
+ count,
11103
+ kind: "count"
11104
+ }));
11105
+ }
9205
11106
  }
9206
11107
  continue;
9207
11108
  }
@@ -9211,8 +11112,489 @@ var ModelRepository = class _ModelRepository {
9211
11112
  const values = await this.getRelationAggregateValues(entities, relation, aggregate);
9212
11113
  for (const entity of entities) {
9213
11114
  const parentKey = this.getRelationParentValue(entity, relation);
9214
- entity.setComputed(key, values.get(parentKey) ?? null);
11115
+ const computation = values.get(parentKey);
11116
+ entity.setComputed(key, computation?.value ?? null);
11117
+ if (computation?.metadata && typeof observationCount === "number") {
11118
+ this.setRelationAggregateObservationMetadata(entity, key, computation.metadata);
11119
+ }
11120
+ }
11121
+ }
11122
+ if (typeof observationCount === "number") {
11123
+ truncateDatabaseQueryObservations(observationCount);
11124
+ }
11125
+ }
11126
+ recordRelationAggregateObservations(entities, aggregates, createPath) {
11127
+ if (!hasActiveDatabaseDependencyCollector() || entities.length === 0 || aggregates.length === 0) {
11128
+ return;
11129
+ }
11130
+ for (const aggregate of aggregates) {
11131
+ if (aggregate.relation.includes(".")) {
11132
+ continue;
11133
+ }
11134
+ const relation = this.getRelationDefinition(aggregate.relation);
11135
+ const key = this.getAggregateAttributeKey(aggregate);
11136
+ for (let index = 0; index < entities.length; index += 1) {
11137
+ const entity = entities[index];
11138
+ if (!entity) {
11139
+ continue;
11140
+ }
11141
+ const target = this.createRelationAggregateObservationTarget(entity, relation);
11142
+ if (!target) {
11143
+ continue;
11144
+ }
11145
+ const aggregateObservation = this.createRelationAggregateObservation(aggregate, entity, key);
11146
+ if (!aggregateObservation) {
11147
+ continue;
11148
+ }
11149
+ const predicateDependency = createTablePredicateCacheDependency(
11150
+ target.connectionName,
11151
+ target.tableName,
11152
+ target.column,
11153
+ target.value
11154
+ );
11155
+ const dependencies = predicateDependency ? Object.freeze([
11156
+ createTableCacheDependency(target.connectionName, target.tableName),
11157
+ predicateDependency
11158
+ ]) : Object.freeze([createTableCacheDependency(target.connectionName, target.tableName)]);
11159
+ const resultPath = createPath(index, key);
11160
+ const data = entity.toJSON();
11161
+ recordDatabaseQueryObservation(Object.freeze({
11162
+ aggregate: aggregate.kind === "exists" ? Object.freeze({
11163
+ ...aggregateObservation,
11164
+ output: "boolean"
11165
+ }) : aggregateObservation,
11166
+ connectionName: target.connectionName,
11167
+ dependencies,
11168
+ orderBy: [],
11169
+ patchable: true,
11170
+ predicates: Object.freeze([Object.freeze({
11171
+ column: target.column,
11172
+ operator: "=",
11173
+ value: target.value
11174
+ })]),
11175
+ result: data[key],
11176
+ resultPath,
11177
+ selections: [],
11178
+ tableName: target.tableName
11179
+ }));
11180
+ }
11181
+ }
11182
+ }
11183
+ recordRelationObservations(entities, relations, createPath) {
11184
+ if (!hasActiveDatabaseDependencyCollector() || entities.length === 0 || relations.length === 0) {
11185
+ return;
11186
+ }
11187
+ for (const entry of this.normalizeEagerLoads(relations)) {
11188
+ if (entry.relation.includes(".")) {
11189
+ continue;
11190
+ }
11191
+ const relationName = entry.relation;
11192
+ const relation = this.getRelationDefinition(relationName);
11193
+ if (relation.kind === "hasMany") {
11194
+ this.recordHasManyRelationObservations(entities, relationName, relation, createPath);
11195
+ continue;
11196
+ }
11197
+ if (relation.kind === "hasOne") {
11198
+ this.recordHasOneRelationObservations(entities, relationName, relation, createPath);
11199
+ continue;
11200
+ }
11201
+ if (relation.kind === "belongsTo") {
11202
+ this.recordBelongsToRelationObservations(entities, relationName, relation, createPath);
11203
+ continue;
11204
+ }
11205
+ if (relation.constraint) {
11206
+ continue;
11207
+ }
11208
+ if (relation.kind === "belongsToMany") {
11209
+ this.recordBelongsToManyRelationObservations(entities, relationName, relation, createPath);
11210
+ }
11211
+ }
11212
+ }
11213
+ recordHasManyRelationObservations(entities, relationName, relation, createPath) {
11214
+ const related = this.resolveRelatedRepository(relation.related);
11215
+ for (let index = 0; index < entities.length; index += 1) {
11216
+ const entity = entities[index];
11217
+ if (!entity || !entity.hasRelation(relationName)) {
11218
+ continue;
11219
+ }
11220
+ const observed = this.getRelationQueryObservation(entity, relationName);
11221
+ if (observed) {
11222
+ recordDatabaseQueryObservation(Object.freeze({
11223
+ ...observed,
11224
+ resultPath: createPath(index, relationName)
11225
+ }));
11226
+ continue;
11227
+ }
11228
+ if (relation.constraint) {
11229
+ continue;
9215
11230
  }
11231
+ const parentKey = this.getRelationParentValue(entity, relation);
11232
+ const predicateDependency = createTablePredicateCacheDependency(
11233
+ related.getConnectionName(),
11234
+ related.definition.table.tableName,
11235
+ relation.foreignKey,
11236
+ parentKey
11237
+ );
11238
+ const dependencies = predicateDependency ? Object.freeze([
11239
+ createTableCacheDependency(related.getConnectionName(), related.definition.table.tableName),
11240
+ predicateDependency
11241
+ ]) : Object.freeze([createTableCacheDependency(related.getConnectionName(), related.definition.table.tableName)]);
11242
+ recordDatabaseQueryObservation(Object.freeze({
11243
+ connectionName: related.getConnectionName(),
11244
+ dependencies,
11245
+ orderBy: [],
11246
+ patchable: true,
11247
+ predicates: Object.freeze([Object.freeze({
11248
+ column: relation.foreignKey,
11249
+ operator: "=",
11250
+ value: parentKey
11251
+ })]),
11252
+ resultPath: createPath(index, relationName),
11253
+ selections: [],
11254
+ tableName: related.definition.table.tableName
11255
+ }));
11256
+ }
11257
+ }
11258
+ recordHasOneRelationObservations(entities, relationName, relation, createPath) {
11259
+ const related = this.resolveRelatedRepository(relation.related);
11260
+ for (let index = 0; index < entities.length; index += 1) {
11261
+ const entity = entities[index];
11262
+ if (!entity || !entity.hasRelation(relationName)) {
11263
+ continue;
11264
+ }
11265
+ const resultPath = createPath(index, relationName);
11266
+ const observed = this.getRelationQueryObservation(entity, relationName);
11267
+ if (observed) {
11268
+ recordDatabaseQueryObservation(this.createNullableSingleRecordRelationObservation(observed, resultPath));
11269
+ continue;
11270
+ }
11271
+ if (relation.constraint) {
11272
+ continue;
11273
+ }
11274
+ const parentKey = this.getRelationParentValue(entity, relation);
11275
+ const predicateDependency = createTablePredicateCacheDependency(
11276
+ related.getConnectionName(),
11277
+ related.definition.table.tableName,
11278
+ relation.foreignKey,
11279
+ parentKey
11280
+ );
11281
+ const dependencies = predicateDependency ? Object.freeze([
11282
+ createTableCacheDependency(related.getConnectionName(), related.definition.table.tableName),
11283
+ predicateDependency
11284
+ ]) : Object.freeze([createTableCacheDependency(related.getConnectionName(), related.definition.table.tableName)]);
11285
+ recordDatabaseQueryObservation(this.createNullableSingleRecordRelationObservation(Object.freeze({
11286
+ connectionName: related.getConnectionName(),
11287
+ dependencies,
11288
+ orderBy: [],
11289
+ patchable: true,
11290
+ predicates: Object.freeze([Object.freeze({
11291
+ column: relation.foreignKey,
11292
+ operator: "=",
11293
+ value: parentKey
11294
+ })]),
11295
+ selections: [],
11296
+ tableName: related.definition.table.tableName
11297
+ }), resultPath));
11298
+ }
11299
+ }
11300
+ recordBelongsToRelationObservations(entities, relationName, relation, createPath) {
11301
+ const related = this.resolveRelatedRepository(relation.related);
11302
+ for (let index = 0; index < entities.length; index += 1) {
11303
+ const entity = entities[index];
11304
+ if (!entity || !entity.hasRelation(relationName)) {
11305
+ continue;
11306
+ }
11307
+ const resultPath = createPath(index, relationName);
11308
+ const observed = this.getRelationQueryObservation(entity, relationName);
11309
+ recordDatabaseQueryObservation(this.createBelongsToParentKeyRelationObservation(
11310
+ entity,
11311
+ relationName,
11312
+ relation,
11313
+ resultPath.slice(0, -1),
11314
+ !relation.constraint
11315
+ ));
11316
+ if (observed) {
11317
+ recordDatabaseQueryObservation(this.createNullableSingleRecordRelationObservation(observed, resultPath));
11318
+ continue;
11319
+ }
11320
+ if (relation.constraint) {
11321
+ continue;
11322
+ }
11323
+ const ownerKey = this.getRelationParentValue(entity, relation);
11324
+ if (ownerKey === null || typeof ownerKey === "undefined") {
11325
+ continue;
11326
+ }
11327
+ const predicateDependency = createTablePredicateCacheDependency(
11328
+ related.getConnectionName(),
11329
+ related.definition.table.tableName,
11330
+ relation.ownerKey,
11331
+ ownerKey
11332
+ );
11333
+ const dependencies = predicateDependency ? Object.freeze([
11334
+ createTableCacheDependency(related.getConnectionName(), related.definition.table.tableName),
11335
+ predicateDependency
11336
+ ]) : Object.freeze([createTableCacheDependency(related.getConnectionName(), related.definition.table.tableName)]);
11337
+ recordDatabaseQueryObservation(this.createNullableSingleRecordRelationObservation(Object.freeze({
11338
+ connectionName: related.getConnectionName(),
11339
+ dependencies,
11340
+ orderBy: [],
11341
+ patchable: true,
11342
+ predicates: Object.freeze([Object.freeze({
11343
+ column: relation.ownerKey,
11344
+ operator: "=",
11345
+ value: ownerKey
11346
+ })]),
11347
+ selections: [],
11348
+ tableName: related.definition.table.tableName
11349
+ }), resultPath));
11350
+ }
11351
+ }
11352
+ createBelongsToParentKeyRelationObservation(entity, relationName, relation, resultPath, patchable) {
11353
+ const parentKey = entity.toAttributes()[this.definition.primaryKey];
11354
+ if (parentKey === null || typeof parentKey === "undefined") {
11355
+ return void 0;
11356
+ }
11357
+ const predicateDependency = createTablePredicateCacheDependency(
11358
+ this.getConnectionName(),
11359
+ this.definition.table.tableName,
11360
+ this.definition.primaryKey,
11361
+ parentKey
11362
+ );
11363
+ const dependencies = predicateDependency ? Object.freeze([
11364
+ createTableCacheDependency(this.getConnectionName(), this.definition.table.tableName),
11365
+ predicateDependency
11366
+ ]) : Object.freeze([createTableCacheDependency(this.getConnectionName(), this.definition.table.tableName)]);
11367
+ const related = this.resolveRelatedRepository(relation.related);
11368
+ return Object.freeze({
11369
+ connectionName: this.getConnectionName(),
11370
+ dependencies,
11371
+ orderBy: [],
11372
+ patchable,
11373
+ predicates: Object.freeze([Object.freeze({
11374
+ column: this.definition.primaryKey,
11375
+ operator: "=",
11376
+ value: parentKey
11377
+ })]),
11378
+ relation: Object.freeze({
11379
+ foreignKey: relation.foreignKey,
11380
+ kind: "belongsToParentKey",
11381
+ ownerKey: relation.ownerKey,
11382
+ relationKey: relationName,
11383
+ relatedConnectionName: related.getConnectionName(),
11384
+ relatedTableName: related.definition.table.tableName
11385
+ }),
11386
+ resultPath,
11387
+ selections: Object.freeze([Object.freeze({
11388
+ column: relation.foreignKey,
11389
+ resultKey: relation.foreignKey
11390
+ })]),
11391
+ tableName: this.definition.table.tableName
11392
+ });
11393
+ }
11394
+ createNullableSingleRecordRelationObservation(observation, resultPath) {
11395
+ return Object.freeze({
11396
+ ...observation,
11397
+ emptyRecordValue: null,
11398
+ limit: 1,
11399
+ resultPath,
11400
+ rowWindowMode: "single"
11401
+ });
11402
+ }
11403
+ rememberHasManyRelationObservation(entity, relationName, relation, related, constrainedPlan, parentKey) {
11404
+ const observation = this.createHasManyRelationObservation(relation, related, constrainedPlan, parentKey);
11405
+ if (!observation) {
11406
+ return;
11407
+ }
11408
+ const observations = relationQueryObservationMetadata.get(entity) ?? /* @__PURE__ */ new Map();
11409
+ observations.set(relationName, observation);
11410
+ relationQueryObservationMetadata.set(entity, observations);
11411
+ }
11412
+ rememberBelongsToRelationObservation(entity, relationName, relation, related, constrainedPlan, ownerKey) {
11413
+ const observation = this.createBelongsToRelationObservation(relation, related, constrainedPlan, ownerKey);
11414
+ if (!observation) {
11415
+ return;
11416
+ }
11417
+ const observations = relationQueryObservationMetadata.get(entity) ?? /* @__PURE__ */ new Map();
11418
+ observations.set(relationName, observation);
11419
+ relationQueryObservationMetadata.set(entity, observations);
11420
+ }
11421
+ getRelationQueryObservation(entity, relationName) {
11422
+ return relationQueryObservationMetadata.get(entity)?.get(relationName);
11423
+ }
11424
+ createHasManyRelationObservation(relation, related, constrainedPlan, parentKey) {
11425
+ if (parentKey === null || typeof parentKey === "undefined") {
11426
+ return void 0;
11427
+ }
11428
+ const parentPlan = withPredicate(constrainedPlan, {
11429
+ kind: "comparison",
11430
+ column: relation.foreignKey,
11431
+ operator: "=",
11432
+ value: parentKey
11433
+ });
11434
+ const dependencies = resolveQueryCacheDependencies(parentPlan, related.getConnectionName());
11435
+ return dependencies ? createDatabaseQueryObservation(parentPlan, related.getConnectionName(), dependencies) : void 0;
11436
+ }
11437
+ createBelongsToRelationObservation(relation, related, constrainedPlan, ownerKey) {
11438
+ if (ownerKey === null || typeof ownerKey === "undefined") {
11439
+ return void 0;
11440
+ }
11441
+ const ownerPlan = withPredicate(constrainedPlan, {
11442
+ kind: "comparison",
11443
+ column: relation.ownerKey,
11444
+ operator: "=",
11445
+ value: ownerKey
11446
+ });
11447
+ const dependencies = resolveQueryCacheDependencies(ownerPlan, related.getConnectionName());
11448
+ return dependencies ? createDatabaseQueryObservation(ownerPlan, related.getConnectionName(), dependencies) : void 0;
11449
+ }
11450
+ recordBelongsToManyRelationObservations(entities, relationName, relation, createPath) {
11451
+ const related = this.resolveRelatedRepository(relation.related);
11452
+ const pivotTableName = this.resolvePivotTableName(relation.pivotTable);
11453
+ for (let index = 0; index < entities.length; index += 1) {
11454
+ const entity = entities[index];
11455
+ if (!entity || !entity.hasRelation(relationName)) {
11456
+ continue;
11457
+ }
11458
+ const parentKey = this.getRelationParentValue(entity, relation);
11459
+ const predicateDependency = createTablePredicateCacheDependency(
11460
+ this.getConnectionName(),
11461
+ pivotTableName,
11462
+ relation.foreignPivotKey,
11463
+ parentKey
11464
+ );
11465
+ const relatedMetadata = this.createBelongsToManyRelatedObservationMetadata(entity, relationName, relation, related);
11466
+ const dependencies = predicateDependency ? Object.freeze([
11467
+ createTableCacheDependency(this.getConnectionName(), pivotTableName),
11468
+ predicateDependency,
11469
+ ...relatedMetadata.dependencies
11470
+ ]) : Object.freeze([
11471
+ createTableCacheDependency(this.getConnectionName(), pivotTableName),
11472
+ ...relatedMetadata.dependencies
11473
+ ]);
11474
+ const relationObservation = Object.freeze({
11475
+ foreignPivotKey: relation.foreignPivotKey,
11476
+ kind: "belongsToMany",
11477
+ pivotAccessor: relation.pivotAccessor,
11478
+ pivotColumns: relation.pivotColumns,
11479
+ pivotOrderBy: relation.pivotOrderBy,
11480
+ relatedConnectionName: related.getConnectionName(),
11481
+ relatedKey: relation.relatedKey,
11482
+ relatedPivotKey: relation.relatedPivotKey,
11483
+ relatedTableName: related.definition.table.tableName
11484
+ });
11485
+ recordDatabaseQueryObservation(Object.freeze({
11486
+ connectionName: this.getConnectionName(),
11487
+ dependencies,
11488
+ orderBy: relation.pivotOrderBy,
11489
+ patchable: relation.pivotWheres.length === 0,
11490
+ predicates: Object.freeze([Object.freeze({
11491
+ column: relation.foreignPivotKey,
11492
+ operator: "=",
11493
+ value: parentKey
11494
+ })]),
11495
+ relation: relationObservation,
11496
+ resultPath: createPath(index, relationName),
11497
+ selections: [],
11498
+ tableName: pivotTableName
11499
+ }));
11500
+ if (relatedMetadata.values.length === 0) {
11501
+ continue;
11502
+ }
11503
+ recordDatabaseQueryObservation(Object.freeze({
11504
+ connectionName: related.getConnectionName(),
11505
+ dependencies: Object.freeze([
11506
+ createTableCacheDependency(related.getConnectionName(), related.definition.table.tableName),
11507
+ ...relatedMetadata.dependencies
11508
+ ]),
11509
+ orderBy: [],
11510
+ patchable: true,
11511
+ predicates: Object.freeze([Object.freeze({
11512
+ column: relation.relatedKey,
11513
+ operator: "in",
11514
+ value: relatedMetadata.values
11515
+ })]),
11516
+ relation: relationObservation,
11517
+ resultPath: createPath(index, relationName),
11518
+ selections: [],
11519
+ tableName: related.definition.table.tableName
11520
+ }));
11521
+ }
11522
+ }
11523
+ createBelongsToManyRelatedObservationMetadata(entity, relationName, relation, related) {
11524
+ const loaded = entity.getRelation(relationName);
11525
+ const dependencies = [];
11526
+ const values = [];
11527
+ for (const relatedEntity of loaded) {
11528
+ if (!isEntity2(relatedEntity)) {
11529
+ continue;
11530
+ }
11531
+ const relatedValue = relatedEntity.get(relation.relatedKey);
11532
+ values.push(relatedValue);
11533
+ const dependency = createTablePredicateCacheDependency(
11534
+ related.getConnectionName(),
11535
+ related.definition.table.tableName,
11536
+ relation.relatedKey,
11537
+ relatedValue
11538
+ );
11539
+ if (dependency) {
11540
+ dependencies.push(dependency);
11541
+ }
11542
+ }
11543
+ return Object.freeze({
11544
+ dependencies: Object.freeze(dependencies),
11545
+ values: Object.freeze(values)
11546
+ });
11547
+ }
11548
+ createRelationAggregateObservation(aggregate, entity, key) {
11549
+ const metadata = this.getRelationAggregateObservationMetadata(entity, key);
11550
+ if (metadata) {
11551
+ return metadata;
11552
+ }
11553
+ if (aggregate.kind === "count" || aggregate.kind === "exists") {
11554
+ return Object.freeze({
11555
+ kind: "count"
11556
+ });
11557
+ }
11558
+ if (!aggregate.column) {
11559
+ return void 0;
11560
+ }
11561
+ return Object.freeze({
11562
+ column: aggregate.column,
11563
+ kind: aggregate.kind
11564
+ });
11565
+ }
11566
+ setRelationAggregateObservationMetadata(entity, key, metadata) {
11567
+ const byKey = relationAggregateObservationMetadata.get(entity) ?? /* @__PURE__ */ new Map();
11568
+ byKey.set(key, metadata);
11569
+ relationAggregateObservationMetadata.set(entity, byKey);
11570
+ }
11571
+ getRelationAggregateObservationMetadata(entity, key) {
11572
+ return relationAggregateObservationMetadata.get(entity)?.get(key);
11573
+ }
11574
+ createRelationAggregateObservationTarget(entity, relation) {
11575
+ switch (relation.kind) {
11576
+ case "belongsTo": {
11577
+ const related = this.resolveRelatedRepository(relation.related);
11578
+ return {
11579
+ column: relation.ownerKey,
11580
+ connectionName: related.getConnectionName(),
11581
+ tableName: related.definition.table.tableName,
11582
+ value: this.getRelationParentValue(entity, relation)
11583
+ };
11584
+ }
11585
+ case "hasMany":
11586
+ case "hasOne":
11587
+ case "hasOneOfMany": {
11588
+ const related = this.resolveRelatedRepository(relation.related);
11589
+ return {
11590
+ column: relation.foreignKey,
11591
+ connectionName: related.getConnectionName(),
11592
+ tableName: related.definition.table.tableName,
11593
+ value: this.getRelationParentValue(entity, relation)
11594
+ };
11595
+ }
11596
+ default:
11597
+ return void 0;
9216
11598
  }
9217
11599
  }
9218
11600
  async create(values) {
@@ -9901,13 +12283,22 @@ var ModelRepository = class _ModelRepository {
9901
12283
  return;
9902
12284
  }
9903
12285
  const related = this.resolveRelatedRepository(relation.related);
9904
- const relatedEntities = await this.applyRelationConstraint(relation, related, constraint).where(relation.ownerKey, "in", foreignKeys).get();
12286
+ const observationCount = hasActiveDatabaseDependencyCollector() ? readDatabaseQueryObservationCount() : void 0;
12287
+ const constrainedQuery = this.applyRelationConstraint(relation, related, constraint);
12288
+ const constrainedPlan = typeof observationCount === "number" ? constrainedQuery.getTableQueryBuilder().getPlan() : void 0;
12289
+ const relatedEntities = await constrainedQuery.where(relation.ownerKey, "in", foreignKeys).get();
12290
+ if (typeof observationCount === "number") {
12291
+ truncateDatabaseQueryObservations(observationCount);
12292
+ }
9905
12293
  const relatedMap = new Map(
9906
12294
  relatedEntities.map((entity) => [entity.get(relation.ownerKey), entity])
9907
12295
  );
9908
12296
  for (const entity of entities) {
9909
12297
  const foreignKey = entity.toAttributes()[relation.foreignKey];
9910
12298
  entity.setRelation(relationName, relatedMap.get(foreignKey) ?? null);
12299
+ if (constrainedPlan) {
12300
+ this.rememberBelongsToRelationObservation(entity, relationName, relation, related, constrainedPlan, foreignKey);
12301
+ }
9911
12302
  }
9912
12303
  }
9913
12304
  async loadHasManyRelation(entities, relationName, relation, constraint) {
@@ -9921,7 +12312,13 @@ var ModelRepository = class _ModelRepository {
9921
12312
  return;
9922
12313
  }
9923
12314
  const related = this.resolveRelatedRepository(relation.related);
9924
- const relatedEntities = await this.applyRelationConstraint(relation, related, constraint).where(relation.foreignKey, "in", localKeys).get();
12315
+ const observationCount = hasActiveDatabaseDependencyCollector() ? readDatabaseQueryObservationCount() : void 0;
12316
+ const constrainedQuery = this.applyRelationConstraint(relation, related, constraint);
12317
+ const constrainedPlan = typeof observationCount === "number" ? constrainedQuery.getTableQueryBuilder().getPlan() : void 0;
12318
+ const relatedEntities = await constrainedQuery.where(relation.foreignKey, "in", localKeys).get();
12319
+ if (typeof observationCount === "number") {
12320
+ truncateDatabaseQueryObservations(observationCount);
12321
+ }
9925
12322
  const grouped = /* @__PURE__ */ new Map();
9926
12323
  for (const relatedEntity of relatedEntities) {
9927
12324
  const foreignKey = relatedEntity.get(relation.foreignKey);
@@ -9932,6 +12329,9 @@ var ModelRepository = class _ModelRepository {
9932
12329
  for (const entity of entities) {
9933
12330
  const localKey = entity.toAttributes()[relation.localKey];
9934
12331
  entity.setRelation(relationName, grouped.get(localKey) ?? []);
12332
+ if (constrainedPlan) {
12333
+ this.rememberHasManyRelationObservation(entity, relationName, relation, related, constrainedPlan, localKey);
12334
+ }
9935
12335
  }
9936
12336
  }
9937
12337
  async loadHasOneRelation(entities, relationName, relation, constraint) {
@@ -10003,8 +12403,12 @@ var ModelRepository = class _ModelRepository {
10003
12403
  }
10004
12404
  return;
10005
12405
  }
12406
+ const observationCount = hasActiveDatabaseDependencyCollector() ? readDatabaseQueryObservationCount() : void 0;
10006
12407
  const pivotRows = await this.createBelongsToManyPivotQuery(relation, this.getConnection()).where(relation.foreignPivotKey, "in", parentKeys).get();
10007
12408
  if (pivotRows.length === 0) {
12409
+ if (typeof observationCount === "number") {
12410
+ truncateDatabaseQueryObservations(observationCount);
12411
+ }
10008
12412
  for (const entity of entities) {
10009
12413
  entity.setRelation(relationName, []);
10010
12414
  }
@@ -10014,6 +12418,9 @@ var ModelRepository = class _ModelRepository {
10014
12418
  pivotRows.map((row) => row[relation.relatedPivotKey]).filter((value) => value !== null && typeof value !== "undefined")
10015
12419
  )];
10016
12420
  if (relatedIds.length === 0) {
12421
+ if (typeof observationCount === "number") {
12422
+ truncateDatabaseQueryObservations(observationCount);
12423
+ }
10017
12424
  for (const entity of entities) {
10018
12425
  entity.setRelation(relationName, []);
10019
12426
  }
@@ -10021,6 +12428,9 @@ var ModelRepository = class _ModelRepository {
10021
12428
  }
10022
12429
  const related = this.resolveRelatedRepository(relation.related);
10023
12430
  const relatedEntities = await this.applyRelationConstraint(relation, related, constraint).where(relation.relatedKey, "in", relatedIds).get();
12431
+ if (typeof observationCount === "number") {
12432
+ truncateDatabaseQueryObservations(observationCount);
12433
+ }
10024
12434
  const relatedMap = new Map(
10025
12435
  relatedEntities.map((entity) => [entity.get(relation.relatedKey), entity])
10026
12436
  );
@@ -10333,31 +12743,53 @@ var ModelRepository = class _ModelRepository {
10333
12743
  switch (kind) {
10334
12744
  case "sum": {
10335
12745
  const numbers = values.map((value) => this.assertNumericAggregateValue(value, kind, column2));
10336
- return numbers.reduce((sum, value) => sum + value, 0);
12746
+ return Object.freeze({
12747
+ value: numbers.reduce((sum, value) => sum + value, 0)
12748
+ });
10337
12749
  }
10338
12750
  case "avg": {
10339
12751
  const numbers = values.map((value) => this.assertNumericAggregateValue(value, kind, column2));
12752
+ const sum = numbers.reduce((total, value) => total + value, 0);
12753
+ const metadata = Object.freeze({
12754
+ column: column2,
12755
+ count: numbers.length,
12756
+ kind,
12757
+ sum
12758
+ });
10340
12759
  if (numbers.length === 0) {
10341
- return null;
12760
+ return Object.freeze({
12761
+ metadata,
12762
+ value: null
12763
+ });
10342
12764
  }
10343
- return numbers.reduce((sum, value) => sum + value, 0) / numbers.length;
12765
+ return Object.freeze({
12766
+ metadata,
12767
+ value: sum / numbers.length
12768
+ });
10344
12769
  }
10345
12770
  case "min": {
10346
12771
  const numbers = values.map((value) => this.assertNumericAggregateValue(value, kind, column2));
10347
- if (numbers.length === 0) {
10348
- return null;
10349
- }
10350
- return Math.min(...numbers);
12772
+ return this.computeExtremeAggregateValue(kind, column2, numbers);
10351
12773
  }
10352
12774
  case "max": {
10353
12775
  const numbers = values.map((value) => this.assertNumericAggregateValue(value, kind, column2));
10354
- if (numbers.length === 0) {
10355
- return null;
10356
- }
10357
- return Math.max(...numbers);
12776
+ return this.computeExtremeAggregateValue(kind, column2, numbers);
10358
12777
  }
10359
12778
  }
10360
12779
  }
12780
+ computeExtremeAggregateValue(kind, column2, numbers) {
12781
+ const value = numbers.length === 0 ? null : kind === "min" ? Math.min(...numbers) : Math.max(...numbers);
12782
+ const metadata = Object.freeze({
12783
+ column: column2,
12784
+ currentValueCount: typeof value === "number" ? numbers.filter((number) => number === value).length : 0,
12785
+ kind,
12786
+ valueCounts: createAggregateValueCounts(numbers)
12787
+ });
12788
+ return Object.freeze({
12789
+ metadata,
12790
+ value
12791
+ });
12792
+ }
10361
12793
  assertNumericAggregateValue(value, kind, column2) {
10362
12794
  if (typeof value !== "number" || Number.isNaN(value)) {
10363
12795
  throw new DatabaseError(`Relation aggregate "${kind}" requires numeric values for column "${column2}".`);
@@ -10925,6 +13357,9 @@ var ModelRepository = class _ModelRepository {
10925
13357
  createBelongsToManyPivotQuery(relation, connection) {
10926
13358
  return this.applyPivotQueryConfig(new TableQueryBuilder(relation.pivotTable, connection), relation);
10927
13359
  }
13360
+ resolvePivotTableName(pivotTable) {
13361
+ return typeof pivotTable === "string" ? pivotTable : pivotTable.tableName;
13362
+ }
10928
13363
  createMorphToManyPivotQuery(relation, connection) {
10929
13364
  return this.applyPivotQueryConfig(new TableQueryBuilder(relation.pivotTable, connection), relation);
10930
13365
  }
@@ -14428,14 +16863,14 @@ function getHoloModelReferenceRegistry() {
14428
16863
 
14429
16864
  // src/model/serialize.ts
14430
16865
  function isSerializableModel(value) {
14431
- return Boolean(value && typeof value === "object" && typeof value.toJSON === "function");
16866
+ return typeof value.toJSON === "function";
14432
16867
  }
14433
16868
  function serializeModels(value) {
14434
16869
  if (value instanceof Date || value === null || typeof value !== "object") {
14435
16870
  return value;
14436
16871
  }
14437
16872
  if (isSerializableModel(value)) {
14438
- return value.toJSON();
16873
+ return serializeModels(value.toJSON());
14439
16874
  }
14440
16875
  if (Array.isArray(value)) {
14441
16876
  return value.map((item) => serializeModels(item));