linkgress-orm 0.4.48 → 0.4.49

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.
@@ -1379,7 +1379,12 @@ class SelectQueryBuilder {
1379
1379
  }
1380
1380
  return this.transformResults(processedRows, selectionResult);
1381
1381
  };
1382
- return new future_query_1.FutureQuery(sql, params, transformFn, this.client, this.executor);
1382
+ const future = new future_query_1.FutureQuery(sql, params, transformFn, this.client, this.executor);
1383
+ future._batchMeta = {
1384
+ hasNestedPaths: nestedPaths.size > 0,
1385
+ reviveJsonRow: this.buildJsonRowReviver(selectionResult),
1386
+ };
1387
+ return future;
1383
1388
  }
1384
1389
  /**
1385
1390
  * Create a future query that returns a single result or null.
@@ -1425,7 +1430,12 @@ class SelectQueryBuilder {
1425
1430
  }
1426
1431
  return this.transformResults(processedRows, selectionResult);
1427
1432
  };
1428
- return new future_query_1.FutureSingleQuery(sql, params, transformFn, this.client, this.executor);
1433
+ const future = new future_query_1.FutureSingleQuery(sql, params, transformFn, this.client, this.executor);
1434
+ future._batchMeta = {
1435
+ hasNestedPaths: nestedPaths.size > 0,
1436
+ reviveJsonRow: this.buildJsonRowReviver(selectionResult),
1437
+ };
1438
+ return future;
1429
1439
  }
1430
1440
  /**
1431
1441
  * Create a future query that returns a count.
@@ -1452,7 +1462,89 @@ class SelectQueryBuilder {
1452
1462
  executor: this.executor,
1453
1463
  };
1454
1464
  const { sql, params } = this.buildAggregateQuery(context, 'count');
1455
- return new future_query_1.FutureCountQuery(sql, params, this.client, this.executor);
1465
+ const future = new future_query_1.FutureCountQuery(sql, params, this.client, this.executor);
1466
+ future._batchMeta = { hasNestedPaths: false };
1467
+ return future;
1468
+ }
1469
+ /**
1470
+ * Build a value reviver for rows delivered as JSON (QueryBatch json_agg
1471
+ * envelope). JSON serialization bypasses the driver's type parsers:
1472
+ * timestamps and dates arrive as ISO strings, numerics as JSON numbers.
1473
+ * The reviver restores driver-equivalent values BEFORE the normal transform
1474
+ * pipeline runs, driven by the DECLARED column types of the selected fields —
1475
+ * never by value shape, so string columns are never misinterpreted.
1476
+ * Returns undefined when no selected column needs revival.
1477
+ * @internal
1478
+ */
1479
+ buildJsonRowReviver(selection) {
1480
+ const revivals = [];
1481
+ for (const key in selection) {
1482
+ const value = selection[key];
1483
+ if (!value || typeof value !== 'object' || !('__fieldName' in value)) {
1484
+ continue;
1485
+ }
1486
+ const config = this.resolveFieldColumnConfig(value);
1487
+ if (!config) {
1488
+ continue;
1489
+ }
1490
+ if (config.type === 'timestamp' || config.type === 'timestamptz') {
1491
+ revivals.push({ key, revive: (v) => (typeof v === 'string' ? new Date(v) : v) });
1492
+ }
1493
+ else if (config.type === 'date') {
1494
+ revivals.push({
1495
+ key,
1496
+ revive: (v) => {
1497
+ if (typeof v !== 'string') {
1498
+ return v;
1499
+ }
1500
+ // Mirror the drivers' date parsing: local midnight, not UTC
1501
+ const [year, month, day] = v.split('-').map(Number);
1502
+ return new Date(year, month - 1, day);
1503
+ },
1504
+ });
1505
+ }
1506
+ else if (config.type === 'decimal' || config.type === 'numeric') {
1507
+ const scale = config.scale;
1508
+ revivals.push({
1509
+ key,
1510
+ revive: (v) => (typeof v === 'number' ? (scale != null ? v.toFixed(scale) : String(v)) : v),
1511
+ });
1512
+ }
1513
+ else if (config.type === 'bigint') {
1514
+ revivals.push({ key, revive: (v) => (typeof v === 'number' ? String(v) : v) });
1515
+ }
1516
+ }
1517
+ if (revivals.length === 0) {
1518
+ return undefined;
1519
+ }
1520
+ return (row) => {
1521
+ for (const { key, revive } of revivals) {
1522
+ const value = row[key];
1523
+ if (value !== null && value !== undefined) {
1524
+ row[key] = revive(value);
1525
+ }
1526
+ }
1527
+ return row;
1528
+ };
1529
+ }
1530
+ /**
1531
+ * Resolve the declared column config for a selected FieldRef: base table
1532
+ * fast path, then schema registry lookup for navigation-sourced fields.
1533
+ * Mirrors the mapper resolution order used by transformResults().
1534
+ * @internal
1535
+ */
1536
+ resolveFieldColumnConfig(fieldRef) {
1537
+ const sourceTable = fieldRef.__sourceTable;
1538
+ const schema = !sourceTable || sourceTable === this.schema.name ? this.schema : this.schemaRegistry?.get(sourceTable);
1539
+ if (!schema) {
1540
+ return undefined;
1541
+ }
1542
+ const cached = schema.columnMetadataCache?.get(fieldRef.__fieldName);
1543
+ if (cached?.config) {
1544
+ return cached.config;
1545
+ }
1546
+ const column = schema.columns?.[fieldRef.__fieldName];
1547
+ return column && typeof column.build === 'function' ? column.build() : undefined;
1456
1548
  }
1457
1549
  /**
1458
1550
  * Execute query and return results as array