@rebasepro/server-postgres 0.19.2-canary.g201749f → 0.19.2-canary.g2cd43e9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.es.js CHANGED
@@ -2537,12 +2537,49 @@ function coerceDeclaredNumber(value, property) {
2537
2537
  const parsed = parseFloat(value);
2538
2538
  return isNaN(parsed) ? null : parsed;
2539
2539
  }
2540
- /** Apply {@link coerceDeclaredNumber} across a row, leaving every other column alone. */
2541
- function coerceDeclaredNumbers(row, collection) {
2540
+ /**
2541
+ * Serve a `date` column as the timestamp this API says it serves.
2542
+ *
2543
+ * The OpenAPI document this server publishes types a `date` property as
2544
+ * `string, format: date-time` — RFC 3339 — and node-postgres hands over the
2545
+ * Postgres literal: `"2026-08-24 01:37:57.647+02"`, with a space where the `T`
2546
+ * belongs and a two-digit offset. V8 parses that by accident; Safari's
2547
+ * `new Date()` does not have to, and the spec never promised it. A column whose
2548
+ * documented type only parses in some browsers is not a contract.
2549
+ *
2550
+ * A date-only column (`mode: "date"`, `format: "date"` in the spec) is already
2551
+ * RFC 3339 as `YYYY-MM-DD` and is left alone — widening it to a timestamp would
2552
+ * invent a time of day and a timezone the column does not have.
2553
+ */
2554
+ function toRestDate(value) {
2555
+ if (value instanceof Date) return isNaN(value.getTime()) ? null : value.toISOString();
2556
+ if (value && typeof value === "object" && value.__type === "date") return value.value ?? null;
2557
+ if (typeof value !== "string" && typeof value !== "number") return value;
2558
+ if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}$/.test(value)) return value;
2559
+ const date = new Date(value);
2560
+ return isNaN(date.getTime()) ? value : date.toISOString();
2561
+ }
2562
+ /**
2563
+ * One scalar, as REST serves it: declared numbers as numbers, declared dates as
2564
+ * RFC 3339, everything else exactly as the database returned it.
2565
+ */
2566
+ function toRestScalar(value, property) {
2567
+ if (property?.type === "date") return toRestDate(value);
2568
+ return coerceDeclaredNumber(value, property);
2569
+ }
2570
+ /**
2571
+ * Apply {@link toRestScalar} across a row, leaving undeclared columns alone.
2572
+ *
2573
+ * Exported because the include loader attaches related rows itself, and a
2574
+ * target rendered differently from its parent is the shape bug this whole file
2575
+ * exists to prevent — a date was a string at the top level and a
2576
+ * `{ __type: "date" }` envelope one level down, in the same response.
2577
+ */
2578
+ function toRestValues(row, collection) {
2542
2579
  const properties = collection.properties;
2543
2580
  if (!properties) return row;
2544
2581
  const out = {};
2545
- for (const [key, value] of Object.entries(row)) out[key] = coerceDeclaredNumber(value, properties[key]);
2582
+ for (const [key, value] of Object.entries(row)) out[key] = toRestScalar(value, properties[key]);
2546
2583
  return out;
2547
2584
  }
2548
2585
  /** Render one target row in the requested style. */
@@ -2588,7 +2625,7 @@ function stripUnreadable(row, collection) {
2588
2625
  return row;
2589
2626
  }
2590
2627
  function renderTarget(targetRow, targetCollection, style, registry) {
2591
- if (style === "inline") return stripUnreadable(coerceDeclaredNumbers({ ...targetRow }, targetCollection), targetCollection);
2628
+ if (style === "inline") return stripUnreadable(toRestValues({ ...targetRow }, targetCollection), targetCollection);
2592
2629
  const address = relationTargetAddress(targetRow, targetCollection, registry);
2593
2630
  const path = targetCollection.slug;
2594
2631
  return createRelationRefWithData(address, path, {
@@ -2638,9 +2675,9 @@ function toFlatRow(row, collection, registry) {
2638
2675
  *
2639
2676
  * Values are the ones the database returned, except where that contradicts the
2640
2677
  * declared type: a `number` property is served as a number (see
2641
- * {@link coerceDeclaredNumber}). Dates stay as the database returned them
2642
- * JSON has its own opinions about dates that the admin's view-model does not
2643
- * share.
2678
+ * {@link coerceDeclaredNumber}) and a `date` as RFC 3339, which is what this
2679
+ * server's own OpenAPI document says a date column is (see
2680
+ * {@link toRestDate}).
2644
2681
  *
2645
2682
  * Keyed by the row rather than by the relation list — a REST fetch only loads
2646
2683
  * the relations `include` asked for, so the row is the authority on which are
@@ -2653,7 +2690,7 @@ function toRestRow(row, collection, registry) {
2653
2690
  const relation = findRelation(resolvedRelations, key);
2654
2691
  if (relation && Array.isArray(value)) flat[key] = value.map((item) => renderTarget(isJunctionRelation(relation) ? unwrapJunctionRow(item) : item, relation.target(), "inline", registry));
2655
2692
  else if (relation && typeof value === "object" && value !== null) flat[key] = renderTarget(value, relation.target(), "inline", registry);
2656
- else flat[key] = coerceDeclaredNumber(value, collection.properties?.[key]);
2693
+ else flat[key] = toRestScalar(value, collection.properties?.[key]);
2657
2694
  }
2658
2695
  return stripUnreadable(flat, collection);
2659
2696
  }
@@ -3909,7 +3946,7 @@ var FetchService = class FetchService {
3909
3946
  row[key] = null;
3910
3947
  continue;
3911
3948
  }
3912
- const [shaped] = this.shapeRelatedRows([{ ...related.values }], node, targetCollection);
3949
+ const [shaped] = this.shapeRelatedRows([toRestValues({ ...related.values }, targetCollection)], node, targetCollection);
3913
3950
  row[key] = shaped;
3914
3951
  loaded.push(shaped);
3915
3952
  }
@@ -3917,7 +3954,7 @@ var FetchService = class FetchService {
3917
3954
  const results = await this.relationService.batchFetchRelatedEntitiesMany(collectionPath, rowIds, key, relation, narrow);
3918
3955
  for (const row of addressable) {
3919
3956
  const related = results.get(String(addressOf(row))) ?? [];
3920
- const shaped = this.shapeRelatedRows(related.map((e) => ({ ...e.values })), node, targetCollection);
3957
+ const shaped = this.shapeRelatedRows(related.map((e) => toRestValues({ ...e.values }, targetCollection)), node, targetCollection);
3921
3958
  row[key] = shaped;
3922
3959
  loaded.push(...shaped);
3923
3960
  }