@rebasepro/server-postgres 0.9.1-canary.7dddf96 → 0.9.1-canary.a57c262

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
@@ -4549,8 +4549,26 @@ function getTableForCollection(collection, registry) {
4549
4549
  if (!table) throw new Error(`Table not found for collection '${collection.slug}' (table: ${tableName})`);
4550
4550
  return table;
4551
4551
  }
4552
+ /**
4553
+ * The key columns a collection's rows are addressed by.
4554
+ *
4555
+ * Three tiers, in order: properties marked `isId`, the primary keys of the
4556
+ * drizzle schema, and finally a column literally named `id`. Only the first is
4557
+ * visible to the browser, which is why a key known only to drizzle is reported
4558
+ * at boot — see {@link warnOnKeysTheAdminCannotResolve}.
4559
+ *
4560
+ * Returns `[]` when nothing resolves, rather than throwing. It used to open by
4561
+ * resolving the table, which throws when there is none — so the `isId` tier,
4562
+ * which needs no table at all, was unreachable for exactly the collections
4563
+ * most likely to have no table registered. Every caller that wanted "no keys"
4564
+ * to mean "no keys" had to spell that out in a try/catch.
4565
+ *
4566
+ * Callers that cannot proceed without a key must say so themselves, naming the
4567
+ * collection: an empty array here means "this collection has no address", which
4568
+ * is a different answer in a notification (broadcast a wildcard) than in a save
4569
+ * (fail).
4570
+ */
4552
4571
  function getPrimaryKeys(collection, registry) {
4553
- const table = getTableForCollection(collection, registry);
4554
4572
  if (collection.properties) {
4555
4573
  const idProps = Object.entries(collection.properties).filter(([_, prop]) => "isId" in prop && Boolean(prop.isId)).map(([key, prop]) => ({
4556
4574
  fieldName: key,
@@ -4559,6 +4577,8 @@ function getPrimaryKeys(collection, registry) {
4559
4577
  }));
4560
4578
  if (idProps.length > 0) return idProps;
4561
4579
  }
4580
+ const table = registry.getTable(getTableName$1(collection));
4581
+ if (!table) return [];
4562
4582
  const keys = [];
4563
4583
  for (const [key, colRaw] of Object.entries(table)) {
4564
4584
  const col = colRaw;
@@ -4587,6 +4607,21 @@ function getPrimaryKeys(collection, registry) {
4587
4607
  return keys;
4588
4608
  }
4589
4609
  /**
4610
+ * The key columns, for callers that cannot do their job without one.
4611
+ *
4612
+ * {@link getPrimaryKeys} answers "what keys, if any" and returns `[]` for a
4613
+ * collection with no address. Most of this driver, though, is building a WHERE
4614
+ * clause and has no meaning without a key — for those, an empty array is not an
4615
+ * answer, and indexing `[0]` into it produces `Cannot read properties of
4616
+ * undefined` three frames from where the real problem is. This says what is
4617
+ * wrong and which collection it is wrong about.
4618
+ */
4619
+ function requirePrimaryKeys(collection, registry) {
4620
+ const keys = getPrimaryKeys(collection, registry);
4621
+ if (keys.length === 0) throw new Error(`Collection '${collection.slug}' has no primary key, so its rows cannot be addressed. Mark the key property with \`isId\` in its config, or register a table whose schema declares one.`);
4622
+ return keys;
4623
+ }
4624
+ /**
4590
4625
  * Collections whose key the *browser* cannot resolve, and what it will do
4591
4626
  * instead.
4592
4627
  *
@@ -4613,12 +4648,7 @@ function findUnresolvableKeyCollections(collections, registry) {
4613
4648
  const findings = [];
4614
4649
  for (const collection of collections) {
4615
4650
  if (getDeclaredPrimaryKeys(collection).length > 0) continue;
4616
- let keys;
4617
- try {
4618
- keys = getPrimaryKeys(collection, registry);
4619
- } catch {
4620
- continue;
4621
- }
4651
+ const keys = getPrimaryKeys(collection, registry);
4622
4652
  if (keys.length === 0) continue;
4623
4653
  if (keys.length === 1 && keys[0].fieldName === "id") continue;
4624
4654
  findings.push({
@@ -4649,19 +4679,14 @@ function warnOnKeysTheAdminCannotResolve(collections, registry) {
4649
4679
  * The address of a row: derived from the collection's primary keys, because a
4650
4680
  * row does not carry one — it is exactly its columns.
4651
4681
  *
4652
- * Falls back to a literal `id` column, which covers the two cases where the
4653
- * keys cannot be resolved: a row that reached us from somewhere other than the
4654
- * postgres driver, and a collection whose table the registry cannot look up
4655
- * (`getPrimaryKeys` throws for an unregistered table rather than returning
4656
- * nothing). Returns `""` when there is no key and no `id` — callers decide what
4657
- * that means, since "unaddressable" is a different answer in a notification
4658
- * (broadcast a wildcard) than in a save (fail).
4682
+ * Falls back to a literal `id` column, for a row that reached us from somewhere
4683
+ * other than this driver. Returns `""` when there is no key and no `id` —
4684
+ * callers decide what that means, since "unaddressable" is a different answer
4685
+ * in a notification (broadcast a wildcard) than in a save (fail).
4659
4686
  */
4660
4687
  function deriveRowAddress(row, collection, registry) {
4661
- try {
4662
- const composite = buildCompositeId(row, getPrimaryKeys(collection, registry));
4663
- if (composite && composite.split(":::").some((part) => part !== "")) return composite;
4664
- } catch {}
4688
+ const composite = buildCompositeId(row, getPrimaryKeys(collection, registry));
4689
+ if (composite && composite.split(":::").some((part) => part !== "")) return composite;
4665
4690
  if (row.id !== void 0 && row.id !== null) return String(row.id);
4666
4691
  return "";
4667
4692
  }
@@ -5703,6 +5728,63 @@ var RelationService = class {
5703
5728
  this.registry = registry;
5704
5729
  }
5705
5730
  /**
5731
+ * One target row, as the {@link RelatedRow} everything here returns.
5732
+ *
5733
+ * Eight sites built this by hand, which is how the address came to be the
5734
+ * target's first key column in all eight — one edit, eight places to miss.
5735
+ *
5736
+ * `resolveNested` is the one thing they did not agree on, and the
5737
+ * disagreement was invisible: the single-parent fetches pass `db` and
5738
+ * `registry` to `parseDataFromServer`, so the target's *own* relations get
5739
+ * resolved too, while the batch paths deliberately do not — a query per
5740
+ * target row is the N+1 the batching exists to avoid. Naming the parameter
5741
+ * makes that a decision rather than a difference between two call sites
5742
+ * nobody was comparing.
5743
+ */
5744
+ async toRelatedRow(targetRow, targetCollection, targetPks, options) {
5745
+ const values = options?.resolveNested ? await parseDataFromServer(targetRow, targetCollection, this.db, this.registry) : await parseDataFromServer(targetRow, targetCollection);
5746
+ return {
5747
+ id: buildCompositeId(targetRow, targetPks),
5748
+ path: targetCollection.slug,
5749
+ values
5750
+ };
5751
+ }
5752
+ /**
5753
+ * A WHERE matching any of `parentIds`, by the whole key.
5754
+ *
5755
+ * A single key is an `IN (…)`. A composite one cannot be: matching
5756
+ * `tenant_id IN (1, 1)` collects every row of tenant 1, so two parents that
5757
+ * share their first column each receive the other's relations. It becomes
5758
+ * an OR of ANDs — one exact address per parent — which Postgres indexes the
5759
+ * same way it would a multi-column key lookup.
5760
+ */
5761
+ parentKeyCondition(parentTable, parentPks, parentIds) {
5762
+ const columnFor = (fieldName) => {
5763
+ const col = parentTable[fieldName];
5764
+ if (!col) throw new Error(`Key column '${fieldName}' not found in parent table`);
5765
+ return col;
5766
+ };
5767
+ if (parentPks.length === 1) {
5768
+ const values = parentIds.map((id) => parseIdValues(id, parentPks)[parentPks[0].fieldName]);
5769
+ return inArray(columnFor(parentPks[0].fieldName), values);
5770
+ }
5771
+ return or(...parentIds.map((id) => {
5772
+ const values = parseIdValues(id, parentPks);
5773
+ return and(...parentPks.map((pk) => eq(columnFor(pk.fieldName), values[pk.fieldName])));
5774
+ }));
5775
+ }
5776
+ /**
5777
+ * Reject a relation that cannot express a composite-keyed parent.
5778
+ *
5779
+ * `localKey` and `foreignKeyOnTarget` are single column names: one column
5780
+ * cannot reference a two-column key, so such a relation has no correct
5781
+ * reading. Left alone it would silently match on the first key column and
5782
+ * hand a tenant's rows to its neighbour — say so instead.
5783
+ */
5784
+ assertSingleKeyAddressable(parentCollection, parentPks, via) {
5785
+ if (parentPks.length > 1) throw new Error(`Relation on '${parentCollection.slug}' uses '${via}', a single foreign-key column, but '${parentCollection.slug}' is keyed on ${parentPks.map((k) => `'${k.fieldName}'`).join(" + ")}. One column cannot reference a composite key — express this relation with \`joinPath\`, whose \`on.from\`/\`on.to\` take every key column.`);
5786
+ }
5787
+ /**
5706
5788
  * Fetch rows related to a parent row through a specific relation
5707
5789
  */
5708
5790
  async fetchRelatedEntities(parentCollectionPath, parentId, relationKey, options = {}) {
@@ -5721,9 +5803,9 @@ var RelationService = class {
5721
5803
  async fetchEntitiesUsingJoins(parentCollection, parentId, relation, options = {}) {
5722
5804
  const targetCollection = relation.target();
5723
5805
  const targetTable = getTableForCollection(targetCollection, this.registry);
5724
- const idInfo = getPrimaryKeys(targetCollection, this.registry);
5806
+ const idInfo = requirePrimaryKeys(targetCollection, this.registry);
5725
5807
  const idField = targetTable[idInfo[0].fieldName];
5726
- const parentPks = getPrimaryKeys(parentCollection, this.registry);
5808
+ const parentPks = requirePrimaryKeys(parentCollection, this.registry);
5727
5809
  const parentIdInfo = parentPks[0];
5728
5810
  const parsedParentId = parseIdValues(parentId, parentPks)[parentIdInfo.fieldName];
5729
5811
  const parentTable = this.registry.getTable(getTableName$1(parentCollection));
@@ -5747,7 +5829,7 @@ var RelationService = class {
5747
5829
  query = applyDynamicJoin(query, joinTable, eq(fromCol, toCol));
5748
5830
  currentTable = joinTable;
5749
5831
  }
5750
- const parentIdField = parentTable[getPrimaryKeys(parentCollection, this.registry)[0].fieldName];
5832
+ const parentIdField = parentTable[requirePrimaryKeys(parentCollection, this.registry)[0].fieldName];
5751
5833
  query = query.where(eq(parentIdField, parsedParentId));
5752
5834
  if (options.limit) query = query.limit(options.limit);
5753
5835
  const results = await query;
@@ -5755,12 +5837,7 @@ var RelationService = class {
5755
5837
  const rows = [];
5756
5838
  for (const row of results) {
5757
5839
  const targetRow = row[targetTableName] || row;
5758
- const parsedValues = await parseDataFromServer(targetRow, targetCollection, this.db, this.registry);
5759
- rows.push({
5760
- id: buildCompositeId(targetRow, idInfo),
5761
- path: targetCollection.slug,
5762
- values: parsedValues
5763
- });
5840
+ rows.push(await this.toRelatedRow(targetRow, targetCollection, idInfo, { resolveNested: true }));
5764
5841
  }
5765
5842
  return rows;
5766
5843
  }
@@ -5779,12 +5856,7 @@ var RelationService = class {
5779
5856
  const rows = [];
5780
5857
  for (const row of results) {
5781
5858
  const targetRow = row[getTableName$1(targetCollection)] || row;
5782
- const parsedValues = await parseDataFromServer(targetRow, targetCollection, this.db, this.registry);
5783
- rows.push({
5784
- id: buildCompositeId(targetRow, idInfo),
5785
- path: targetCollection.slug,
5786
- values: parsedValues
5787
- });
5859
+ rows.push(await this.toRelatedRow(targetRow, targetCollection, idInfo, { resolveNested: true }));
5788
5860
  }
5789
5861
  return rows;
5790
5862
  }
@@ -5801,8 +5873,8 @@ var RelationService = class {
5801
5873
  }
5802
5874
  const targetCollection = relation.target();
5803
5875
  const targetTable = getTableForCollection(targetCollection, this.registry);
5804
- const targetIdField = targetTable[getPrimaryKeys(targetCollection, this.registry)[0].fieldName];
5805
- const parentPks = getPrimaryKeys(parentCollection, this.registry);
5876
+ const targetIdField = targetTable[requirePrimaryKeys(targetCollection, this.registry)[0].fieldName];
5877
+ const parentPks = requirePrimaryKeys(parentCollection, this.registry);
5806
5878
  const parentIdInfo = parentPks[0];
5807
5879
  const parsedParentId = parseIdValues(parentId, parentPks)[parentIdInfo.fieldName];
5808
5880
  const parentTable = this.registry.getTable(getTableName$1(parentCollection));
@@ -5821,10 +5893,10 @@ var RelationService = class {
5821
5893
  const parentCollection = getCollectionByPath(parentCollectionPath, this.registry);
5822
5894
  const targetCollection = relation.target();
5823
5895
  const targetTable = getTableForCollection(targetCollection, this.registry);
5824
- const targetPks = getPrimaryKeys(targetCollection, this.registry);
5896
+ const targetPks = requirePrimaryKeys(targetCollection, this.registry);
5825
5897
  const targetIdInfo = targetPks[0];
5826
5898
  const targetIdField = targetTable[targetIdInfo.fieldName];
5827
- const parentPks = getPrimaryKeys(parentCollection, this.registry);
5899
+ const parentPks = requirePrimaryKeys(parentCollection, this.registry);
5828
5900
  const parentIdInfo = parentPks[0];
5829
5901
  const parentTable = this.registry.getTable(getTableName$1(parentCollection));
5830
5902
  if (!parentTable) throw new Error("Parent table not found");
@@ -5848,25 +5920,19 @@ var RelationService = class {
5848
5920
  query = applyDynamicJoin(query, joinTable, eq(fromCol, toCol));
5849
5921
  currentTable = joinTable;
5850
5922
  }
5851
- const parentIdField = parentTable[getPrimaryKeys(parentCollection, this.registry)[0].fieldName];
5852
- query = query.where(inArray(parentIdField, parsedParentIds));
5923
+ query = query.where(this.parentKeyCondition(parentTable, parentPks, parentIds));
5853
5924
  const results = await query;
5854
5925
  const targetTableName = relation.joinPath[relation.joinPath.length - 1].table;
5855
5926
  const resultMap = /* @__PURE__ */ new Map();
5856
5927
  for (const row of results) {
5857
5928
  const parentRow = row[getTableName$1(parentCollection)] || row;
5858
5929
  const targetRow = row[targetTableName] || row;
5859
- const parentId = parentRow[parentIdInfo.fieldName];
5860
- const parsedValues = await parseDataFromServer(targetRow, targetCollection);
5861
- resultMap.set(String(parentId), {
5862
- id: buildCompositeId(targetRow, targetPks),
5863
- path: targetCollection.slug,
5864
- values: parsedValues
5865
- });
5930
+ resultMap.set(buildCompositeId(parentRow, parentPks), await this.toRelatedRow(targetRow, targetCollection, targetPks));
5866
5931
  }
5867
5932
  return resultMap;
5868
5933
  }
5869
5934
  if (relation.direction === "owning" && relation.localKey) {
5935
+ this.assertSingleKeyAddressable(parentCollection, parentPks, relation.localKey);
5870
5936
  const localKeyCol = parentTable[relation.localKey];
5871
5937
  if (!localKeyCol) throw new Error(`Local key column '${relation.localKey}' not found in parent table`);
5872
5938
  const fkRows = await this.db.select({
@@ -5895,17 +5961,11 @@ var RelationService = class {
5895
5961
  const resultMap = /* @__PURE__ */ new Map();
5896
5962
  for (const [parentIdStr, fkValue] of parentToFk) {
5897
5963
  const targetRow = targetById.get(String(fkValue));
5898
- if (targetRow) {
5899
- const parsedValues = await parseDataFromServer(targetRow, targetCollection);
5900
- resultMap.set(parentIdStr, {
5901
- id: buildCompositeId(targetRow, targetPks),
5902
- path: targetCollection.slug,
5903
- values: parsedValues
5904
- });
5905
- }
5964
+ if (targetRow) resultMap.set(parentIdStr, await this.toRelatedRow(targetRow, targetCollection, targetPks));
5906
5965
  }
5907
5966
  return resultMap;
5908
5967
  }
5968
+ this.assertSingleKeyAddressable(parentCollection, parentPks, relation.foreignKeyOnTarget ?? `${relation.inverseRelationName}_id`);
5909
5969
  let query = this.db.select().from(targetTable).$dynamic();
5910
5970
  query = applyDynamicRelationQuery(query, query, relation, parsedParentIds, targetTable, parentTable, parentIdCol, targetIdField, this.registry, []);
5911
5971
  const results = await query;
@@ -5916,14 +5976,7 @@ var RelationService = class {
5916
5976
  let parentId;
5917
5977
  if (relation.direction === "inverse" && relation.foreignKeyOnTarget) parentId = targetRow[relation.foreignKeyOnTarget];
5918
5978
  else if (relation.direction === "inverse" && relation.cardinality === "one" && relation.inverseRelationName) parentId = targetRow[`${relation.inverseRelationName}_id`];
5919
- if (parentId !== void 0 && parentIdSet.has(String(parentId))) {
5920
- const parsedValues = await parseDataFromServer(targetRow, targetCollection);
5921
- resultMap.set(String(parentId), {
5922
- id: buildCompositeId(targetRow, targetPks),
5923
- path: targetCollection.slug,
5924
- values: parsedValues
5925
- });
5926
- }
5979
+ if (parentId !== void 0 && parentIdSet.has(String(parentId))) resultMap.set(String(parentId), await this.toRelatedRow(targetRow, targetCollection, targetPks));
5927
5980
  }
5928
5981
  return resultMap;
5929
5982
  }
@@ -5937,9 +5990,9 @@ var RelationService = class {
5937
5990
  const parentCollection = getCollectionByPath(parentCollectionPath, this.registry);
5938
5991
  const targetCollection = relation.target();
5939
5992
  const targetTable = getTableForCollection(targetCollection, this.registry);
5940
- const targetPks = getPrimaryKeys(targetCollection, this.registry);
5993
+ const targetPks = requirePrimaryKeys(targetCollection, this.registry);
5941
5994
  const targetIdField = targetTable[targetPks[0].fieldName];
5942
- const parentPks = getPrimaryKeys(parentCollection, this.registry);
5995
+ const parentPks = requirePrimaryKeys(parentCollection, this.registry);
5943
5996
  const parentIdInfo = parentPks[0];
5944
5997
  const parentTable = this.registry.getTable(getTableName$1(parentCollection));
5945
5998
  if (!parentTable) throw new Error("Parent table not found");
@@ -5961,27 +6014,22 @@ var RelationService = class {
5961
6014
  query = applyDynamicJoin(query, joinTable, eq(fromCol, toCol));
5962
6015
  currentTable = joinTable;
5963
6016
  }
5964
- const parentIdField = parentTable[getPrimaryKeys(parentCollection, this.registry)[0].fieldName];
5965
- query = query.where(inArray(parentIdField, parsedParentIds));
6017
+ query = query.where(this.parentKeyCondition(parentTable, parentPks, parentIds));
5966
6018
  const results = await query;
5967
6019
  const targetTableName = relation.joinPath[relation.joinPath.length - 1].table;
5968
6020
  const resultMap = /* @__PURE__ */ new Map();
5969
6021
  for (const row of results) {
5970
6022
  const parentRow = row[getTableName$1(parentCollection)] || row;
5971
6023
  const targetRow = row[targetTableName] || row;
5972
- const parentId = String(parentRow[parentIdInfo.fieldName]);
5973
- const parsedValues = await parseDataFromServer(targetRow, targetCollection);
6024
+ const parentId = buildCompositeId(parentRow, parentPks);
5974
6025
  const arr = resultMap.get(parentId) || [];
5975
- arr.push({
5976
- id: buildCompositeId(targetRow, targetPks),
5977
- path: targetCollection.slug,
5978
- values: parsedValues
5979
- });
6026
+ arr.push(await this.toRelatedRow(targetRow, targetCollection, targetPks));
5980
6027
  resultMap.set(parentId, arr);
5981
6028
  }
5982
6029
  return resultMap;
5983
6030
  }
5984
6031
  if (relation.through && relation.cardinality === "many" && relation.direction === "owning") {
6032
+ this.assertSingleKeyAddressable(parentCollection, parentPks, `${relation.through.table}.${relation.through.sourceColumn}`);
5985
6033
  const junctionTable = this.registry.getTable(relation.through.table);
5986
6034
  if (!junctionTable) {
5987
6035
  logger.warn(`[batchFetchRelatedEntitiesMany] Junction table '${relation.through.table}' not found`);
@@ -6000,17 +6048,13 @@ var RelationService = class {
6000
6048
  const junctionData = row[relation.through.table] || row;
6001
6049
  const targetData = row[targetTableName] || row;
6002
6050
  const parentId = String(junctionData[relation.through.sourceColumn]);
6003
- const parsedValues = await parseDataFromServer(targetData, targetCollection);
6004
6051
  const arr = resultMap.get(parentId) || [];
6005
- arr.push({
6006
- id: buildCompositeId(targetData, targetPks),
6007
- path: targetCollection.slug,
6008
- values: parsedValues
6009
- });
6052
+ arr.push(await this.toRelatedRow(targetData, targetCollection, targetPks));
6010
6053
  resultMap.set(parentId, arr);
6011
6054
  }
6012
6055
  return resultMap;
6013
6056
  }
6057
+ this.assertSingleKeyAddressable(parentCollection, parentPks, relation.foreignKeyOnTarget ?? `${relation.inverseRelationName}_id`);
6014
6058
  let query = this.db.select().from(targetTable).$dynamic();
6015
6059
  query = applyDynamicRelationQuery(query, query, relation, parsedParentIds, targetTable, parentTable, parentIdCol, targetIdField, this.registry, []);
6016
6060
  const results = await query;
@@ -6023,14 +6067,9 @@ var RelationService = class {
6023
6067
  else if (relation.direction === "inverse" && relation.foreignKeyOnTarget) parentId = targetRow[relation.foreignKeyOnTarget];
6024
6068
  else if (relation.direction === "inverse" && relation.inverseRelationName) parentId = targetRow[`${relation.inverseRelationName}_id`];
6025
6069
  if (parentId !== void 0 && parentIdSet.has(String(parentId))) {
6026
- const parsedValues = await parseDataFromServer(targetRow, targetCollection);
6027
6070
  const key = String(parentId);
6028
6071
  const arr = resultMap.get(key) || [];
6029
- arr.push({
6030
- id: buildCompositeId(targetRow, targetPks),
6031
- path: targetCollection.slug,
6032
- values: parsedValues
6033
- });
6072
+ arr.push(await this.toRelatedRow(targetRow, targetCollection, targetPks));
6034
6073
  resultMap.set(key, arr);
6035
6074
  }
6036
6075
  }
@@ -6078,12 +6117,12 @@ var RelationService = class {
6078
6117
  logger.warn(`Could not determine junction table for relation '${key}' in collection '${collection.slug}'`);
6079
6118
  continue;
6080
6119
  }
6081
- const parentPks = getPrimaryKeys(collection, this.registry);
6120
+ const parentPks = requirePrimaryKeys(collection, this.registry);
6082
6121
  const parentIdInfo = parentPks[0];
6083
6122
  const parsedParentId = parseIdValues(id, parentPks)[parentIdInfo.fieldName];
6084
6123
  await tx.delete(junctionTable).where(eq(sourceJunctionColumn, parsedParentId));
6085
6124
  if (targetEntityIds.length > 0) {
6086
- const targetPks = getPrimaryKeys(targetCollection, this.registry);
6125
+ const targetPks = requirePrimaryKeys(targetCollection, this.registry);
6087
6126
  const targetIdInfo = targetPks[0];
6088
6127
  const newLinks = targetEntityIds.map((id) => parseIdValues(id, targetPks)[targetIdInfo.fieldName]).map((targetId) => ({
6089
6128
  [sourceJunctionColumn.name]: parsedParentId,
@@ -6103,12 +6142,12 @@ var RelationService = class {
6103
6142
  logger.warn(`Junction columns not found for relation '${key}'`);
6104
6143
  continue;
6105
6144
  }
6106
- const parentPks = getPrimaryKeys(collection, this.registry);
6145
+ const parentPks = requirePrimaryKeys(collection, this.registry);
6107
6146
  const parentIdInfo = parentPks[0];
6108
6147
  const parsedParentId = parseIdValues(id, parentPks)[parentIdInfo.fieldName];
6109
6148
  await tx.delete(junctionTable).where(eq(sourceJunctionColumn, parsedParentId));
6110
6149
  if (targetEntityIds.length > 0) {
6111
- const targetPks = getPrimaryKeys(targetCollection, this.registry);
6150
+ const targetPks = requirePrimaryKeys(targetCollection, this.registry);
6112
6151
  const targetIdInfo = targetPks[0];
6113
6152
  const newLinks = targetEntityIds.map((id) => parseIdValues(id, targetPks)[targetIdInfo.fieldName]).map((targetId) => ({
6114
6153
  [sourceJunctionColumn.name]: parsedParentId,
@@ -6119,7 +6158,7 @@ var RelationService = class {
6119
6158
  } else if (relation.through && relation.cardinality === "many" && relation.direction === "inverse") logger.warn(`[updateRelationsUsingJoins] Inverse M2M relation '${key}' in collection '${collection.slug}' should be saved from the owning side. Skipping.`);
6120
6159
  else if (relation.cardinality === "many" && relation.direction === "inverse" && relation.foreignKeyOnTarget) {
6121
6160
  const targetTable = getTableForCollection(targetCollection, this.registry);
6122
- const targetPks = getPrimaryKeys(targetCollection, this.registry);
6161
+ const targetPks = requirePrimaryKeys(targetCollection, this.registry);
6123
6162
  const targetIdInfo = targetPks[0];
6124
6163
  const targetIdCol = targetTable[targetIdInfo.fieldName];
6125
6164
  const fkCol = targetTable[relation.foreignKeyOnTarget];
@@ -6127,7 +6166,7 @@ var RelationService = class {
6127
6166
  logger.warn(`Invalid inverse-many config for relation '${key}' in collection '${collection.slug}'`);
6128
6167
  continue;
6129
6168
  }
6130
- const parentPks = getPrimaryKeys(collection, this.registry);
6169
+ const parentPks = requirePrimaryKeys(collection, this.registry);
6131
6170
  const parentIdInfo = parentPks[0];
6132
6171
  const parsedParentId = parseIdValues(id, parentPks)[parentIdInfo.fieldName];
6133
6172
  if (targetEntityIds.length > 0) {
@@ -6147,9 +6186,9 @@ var RelationService = class {
6147
6186
  try {
6148
6187
  const targetCollection = relation.target();
6149
6188
  const targetTable = getTableForCollection(targetCollection, this.registry);
6150
- const targetPks = getPrimaryKeys(targetCollection, this.registry);
6189
+ const targetPks = requirePrimaryKeys(targetCollection, this.registry);
6151
6190
  const targetIdInfo = targetPks[0];
6152
- const sourcePks = getPrimaryKeys(sourceCollection, this.registry);
6191
+ const sourcePks = requirePrimaryKeys(sourceCollection, this.registry);
6153
6192
  const sourceIdInfo = sourcePks[0];
6154
6193
  if (relation.direction === "inverse" && relation.joinPath && relation.joinPath.length > 0) {
6155
6194
  await this.updateInverseJoinPathRelation(tx, sourceCollection, sourceEntityId, targetCollection, relation, newValue);
@@ -6230,12 +6269,12 @@ var RelationService = class {
6230
6269
  logger.warn(`Could not determine junction columns for inverse joinPath relation '${relation.relationName}'`);
6231
6270
  return;
6232
6271
  }
6233
- const sourcePks = getPrimaryKeys(sourceCollection, this.registry);
6272
+ const sourcePks = requirePrimaryKeys(sourceCollection, this.registry);
6234
6273
  const sourceIdInfo = sourcePks[0];
6235
6274
  const parsedSourceId = parseIdValues(sourceEntityId, sourcePks)[sourceIdInfo.fieldName];
6236
6275
  await tx.delete(junctionTable).where(eq(sourceJunctionColumn, parsedSourceId));
6237
6276
  if (newValue && Array.isArray(newValue) && newValue.length > 0) {
6238
- const targetPks = getPrimaryKeys(targetCollection, this.registry);
6277
+ const targetPks = requirePrimaryKeys(targetCollection, this.registry);
6239
6278
  const targetIdInfo = targetPks[0];
6240
6279
  const newLinks = newValue.map((rel) => typeof rel === "object" && rel !== null ? rel.id : rel).map((id) => parseIdValues(id, targetPks)[targetIdInfo.fieldName]).map((targetId) => ({
6241
6280
  [sourceJunctionColumn.name]: parsedSourceId,
@@ -6243,7 +6282,7 @@ var RelationService = class {
6243
6282
  }));
6244
6283
  if (newLinks.length > 0) await tx.insert(junctionTable).values(newLinks);
6245
6284
  } else if (newValue && !Array.isArray(newValue)) {
6246
- const targetPks = getPrimaryKeys(targetCollection, this.registry);
6285
+ const targetPks = requirePrimaryKeys(targetCollection, this.registry);
6247
6286
  const targetIdInfo = targetPks[0];
6248
6287
  const parsedTargetId = parseIdValues(typeof newValue === "object" && newValue !== null ? newValue.id : newValue, targetPks)[targetIdInfo.fieldName];
6249
6288
  const newLink = {
@@ -6274,12 +6313,12 @@ var RelationService = class {
6274
6313
  logger.warn(`Junction columns not found for relation '${relation.relationName}'`);
6275
6314
  return;
6276
6315
  }
6277
- const sourcePks = getPrimaryKeys(sourceCollection, this.registry);
6316
+ const sourcePks = requirePrimaryKeys(sourceCollection, this.registry);
6278
6317
  const sourceIdInfo = sourcePks[0];
6279
6318
  const parsedSourceId = parseIdValues(sourceEntityId, sourcePks)[sourceIdInfo.fieldName];
6280
6319
  await tx.delete(junctionTable).where(eq(sourceJunctionColumn, parsedSourceId));
6281
6320
  if (newValue && Array.isArray(newValue) && newValue.length > 0) {
6282
- const targetPks = getPrimaryKeys(targetCollection, this.registry);
6321
+ const targetPks = requirePrimaryKeys(targetCollection, this.registry);
6283
6322
  const targetIdInfo = targetPks[0];
6284
6323
  const newLinks = newValue.map((rel) => rel.id).map((id) => parseIdValues(id, targetPks)[targetIdInfo.fieldName]).map((targetId) => ({
6285
6324
  [sourceJunctionColumn.name]: parsedSourceId,
@@ -6300,12 +6339,12 @@ var RelationService = class {
6300
6339
  const { relation, newTargetId } = upd;
6301
6340
  const targetCollection = relation.target();
6302
6341
  const targetTable = getTableForCollection(targetCollection, this.registry);
6303
- const targetPks = getPrimaryKeys(targetCollection, this.registry);
6342
+ const targetPks = requirePrimaryKeys(targetCollection, this.registry);
6304
6343
  const targetIdInfo = targetPks[0];
6305
6344
  const targetIdCol = targetTable[targetIdInfo.fieldName];
6306
6345
  const { targetFKColName, parentSourceColName } = this.resolveJoinPathWriteMapping(parentCollection, relation);
6307
6346
  const parentTable = getTableForCollection(parentCollection, this.registry);
6308
- const parentPks = getPrimaryKeys(parentCollection, this.registry);
6347
+ const parentPks = requirePrimaryKeys(parentCollection, this.registry);
6309
6348
  const parentIdInfo = parentPks[0];
6310
6349
  const parsedParentId = parseIdValues(parentId, parentPks)[parentIdInfo.fieldName];
6311
6350
  const parentIdCol = parentTable[parentIdInfo.fieldName];
@@ -6376,7 +6415,7 @@ var RelationService = class {
6376
6415
  logger.warn(`Junction columns not found for relation '${relationKey}'`);
6377
6416
  return;
6378
6417
  }
6379
- const targetPks = getPrimaryKeys(targetCollection, this.registry);
6418
+ const targetPks = requirePrimaryKeys(targetCollection, this.registry);
6380
6419
  const targetIdInfo = targetPks[0];
6381
6420
  const parsedNewEntityId = parseIdValues(newEntityId, targetPks)[targetIdInfo.fieldName];
6382
6421
  const junctionData = {
@@ -6392,6 +6431,100 @@ var RelationService = class {
6392
6431
  }
6393
6432
  };
6394
6433
  //#endregion
6434
+ //#region src/services/row-pipeline.ts
6435
+ /**
6436
+ * Whether a many-relation reaches its target through a junction table.
6437
+ *
6438
+ * Also used to build the drizzle `with` config, which is why it is exported:
6439
+ * the query has to nest one level deeper for a junction, and the row walk has
6440
+ * to unwrap that same level back out.
6441
+ */
6442
+ function isJunctionRelation(relation) {
6443
+ if (relation.through) return true;
6444
+ if (relation.joinPath && relation.joinPath.length > 1) return true;
6445
+ return false;
6446
+ }
6447
+ /**
6448
+ * Reach the target row inside a junction row.
6449
+ *
6450
+ * A junction row looks like `{ post_id: 1, tag_id: { id: 5, name: "ts" } }` —
6451
+ * the foreign keys, and the target nested under one of them. The target is the
6452
+ * only object among them, so that is how it is found. A junction row that has
6453
+ * not been nested (no `with` on the join) has no object and is returned as-is.
6454
+ */
6455
+ function unwrapJunctionRow(item) {
6456
+ const nestedKey = Object.keys(item).find((key) => typeof item[key] === "object" && item[key] !== null && !Array.isArray(item[key]));
6457
+ return nestedKey ? item[nestedKey] : item;
6458
+ }
6459
+ /** Render one target row in the requested style. */
6460
+ function renderTarget(targetRow, targetCollection, style, registry) {
6461
+ if (style === "inline") return { ...targetRow };
6462
+ const address = relationTargetAddress(targetRow, targetCollection, registry);
6463
+ const path = targetCollection.slug;
6464
+ return createRelationRefWithData(address, path, {
6465
+ id: address,
6466
+ path,
6467
+ values: normalizeDbValues(targetRow, targetCollection)
6468
+ });
6469
+ }
6470
+ /**
6471
+ * The address a relation ref points at.
6472
+ *
6473
+ * The whole key, not its first column: a composite-keyed target addressed by
6474
+ * `tenant_id` alone points at every row that shares it. A target whose key
6475
+ * cannot be resolved at all used to throw here — reading `[0]` of an empty
6476
+ * array — taking down the parent's fetch over a relation it may not even have
6477
+ * asked for. The first column is a guess, but a ref that resolves to nothing
6478
+ * beats no rows at all.
6479
+ */
6480
+ function relationTargetAddress(targetRow, targetCollection, registry) {
6481
+ const address = deriveRowAddress(targetRow, targetCollection, registry);
6482
+ if (address) return address;
6483
+ return String(targetRow[Object.keys(targetRow)[0]] ?? "");
6484
+ }
6485
+ /**
6486
+ * The row the admin renders: every column, with relations as references.
6487
+ *
6488
+ * Values are normalized (dates, numbers, NaN) because the admin's view-model
6489
+ * expects real types. The row's own address is *not* among the columns — it is
6490
+ * derived by the consumer from the collection's primary keys.
6491
+ */
6492
+ function toCmsRow(row, collection, registry) {
6493
+ const resolvedRelations = resolveCollectionRelations(collection);
6494
+ const normalized = normalizeDbValues(row, collection);
6495
+ for (const [key, relation] of Object.entries(resolvedRelations)) {
6496
+ const relData = row[relation.relationName || key];
6497
+ if (relData === void 0 || relData === null) continue;
6498
+ if (relation.cardinality === "many" && Array.isArray(relData)) {
6499
+ const targetCollection = relation.target();
6500
+ normalized[key] = relData.map((item) => renderTarget(isJunctionRelation(relation) ? unwrapJunctionRow(item) : item, targetCollection, "ref", registry));
6501
+ } else if (relation.cardinality === "one" && typeof relData === "object" && !Array.isArray(relData)) normalized[key] = renderTarget(relData, relation.target(), "ref", registry);
6502
+ }
6503
+ return normalized;
6504
+ }
6505
+ /**
6506
+ * The row REST serves: every column under its own name, with the value Postgres
6507
+ * returned, and relations inlined as the target's columns.
6508
+ *
6509
+ * Deliberately not normalized: REST serves what the database holds, and JSON
6510
+ * has its own opinions about dates that the admin's view-model does not share.
6511
+ *
6512
+ * Keyed by the row rather than by the relation list — a REST fetch only loads
6513
+ * the relations `include` asked for, so the row is the authority on which are
6514
+ * actually there.
6515
+ */
6516
+ function toRestRow(row, collection, registry) {
6517
+ const resolvedRelations = resolveCollectionRelations(collection);
6518
+ const flat = {};
6519
+ for (const [key, value] of Object.entries(row)) {
6520
+ const relation = findRelation(resolvedRelations, key);
6521
+ if (relation && Array.isArray(value)) flat[key] = value.map((item) => renderTarget(isJunctionRelation(relation) ? unwrapJunctionRow(item) : item, relation.target(), "inline", registry));
6522
+ else if (relation && typeof value === "object" && value !== null) flat[key] = renderTarget(value, relation.target(), "inline", registry);
6523
+ else flat[key] = value;
6524
+ }
6525
+ return flat;
6526
+ }
6527
+ //#endregion
6395
6528
  //#region src/services/FetchService.ts
6396
6529
  /**
6397
6530
  * Service for handling all row read operations.
@@ -6450,7 +6583,7 @@ var FetchService = class {
6450
6583
  if (!shouldInclude(key)) continue;
6451
6584
  const drizzleRelName = relation.relationName || key;
6452
6585
  if (relation.joinPath && relation.joinPath.length > 0) continue;
6453
- if (relation.cardinality === "many" && this.isJunctionRelation(relation, collection)) {
6586
+ if (relation.cardinality === "many" && isJunctionRelation(relation)) {
6454
6587
  const targetFkName = this.getJunctionTargetRelationName(relation, collection);
6455
6588
  if (targetFkName) withConfig[drizzleRelName] = { with: { [targetFkName]: true } };
6456
6589
  else withConfig[drizzleRelName] = true;
@@ -6459,14 +6592,6 @@ var FetchService = class {
6459
6592
  return withConfig;
6460
6593
  }
6461
6594
  /**
6462
- * Detect if a many-to-many relation uses a junction table in the Drizzle schema.
6463
- */
6464
- isJunctionRelation(relation, _collection) {
6465
- if (relation.through) return true;
6466
- if (relation.joinPath && relation.joinPath.length > 1) return true;
6467
- return false;
6468
- }
6469
- /**
6470
6595
  * Get the Drizzle relation name on the junction table that points to the actual target row.
6471
6596
  * For example, for posts_tags junction, this returns "tag_id" (the relation pointing to tags).
6472
6597
  */
@@ -6475,68 +6600,6 @@ var FetchService = class {
6475
6600
  return null;
6476
6601
  }
6477
6602
  /**
6478
- * The address a relation ref points at.
6479
- *
6480
- * The whole key, not its first column: a composite-keyed target addressed
6481
- * by `tenant_id` alone points at every row that shares it. And a target
6482
- * whose key cannot be resolved at all used to throw here — reading
6483
- * `targetPks[0]` of an empty array — taking down the parent's fetch over a
6484
- * relation it may not even have asked for; the first column is a guess, but
6485
- * a ref that resolves to nothing beats no rows at all.
6486
- */
6487
- relationTargetAddress(targetRow, targetCollection) {
6488
- const address = deriveRowAddress(targetRow, targetCollection, this.registry);
6489
- if (address) return address;
6490
- const firstColumn = targetRow[Object.keys(targetRow)[0]];
6491
- return String(firstColumn ?? "");
6492
- }
6493
- /**
6494
- * Convert a db.query result row (with nested relation objects) to a flat row.
6495
- * Handles:
6496
- * - Type normalization (dates, numbers, NaN) via normalizeDbValues
6497
- * - Converting nested relation objects to { id, path, __type: "relation" } for CMS
6498
- * - Flattening junction-table many-to-many results
6499
- *
6500
- * The row's own address is not among them: it is derived by the consumer
6501
- * from the collection's primary keys.
6502
- */
6503
- drizzleResultToRow(row, collection) {
6504
- const resolvedRelations = resolveCollectionRelations(collection);
6505
- const normalizedValues = normalizeDbValues(row, collection);
6506
- for (const [key, relation] of Object.entries(resolvedRelations)) {
6507
- const relData = row[relation.relationName || key];
6508
- if (relData === void 0 || relData === null) continue;
6509
- if (relation.cardinality === "many" && Array.isArray(relData)) {
6510
- const targetCollection = relation.target();
6511
- const targetPath = targetCollection.slug;
6512
- normalizedValues[key] = relData.map((item) => {
6513
- let targetRow = item;
6514
- if (this.isJunctionRelation(relation, collection)) {
6515
- const nestedKey = Object.keys(item).find((nk) => typeof item[nk] === "object" && item[nk] !== null && !Array.isArray(item[nk]));
6516
- if (nestedKey) targetRow = item[nestedKey];
6517
- }
6518
- const relId = this.relationTargetAddress(targetRow, targetCollection);
6519
- return createRelationRefWithData(relId, targetPath, {
6520
- id: relId,
6521
- path: targetPath,
6522
- values: normalizeDbValues(targetRow, targetCollection)
6523
- });
6524
- });
6525
- } else if (relation.cardinality === "one" && typeof relData === "object" && !Array.isArray(relData)) {
6526
- const targetCollection = relation.target();
6527
- const targetPath = targetCollection.slug;
6528
- const relObj = relData;
6529
- const relId = this.relationTargetAddress(relObj, targetCollection);
6530
- normalizedValues[key] = createRelationRefWithData(relId, targetPath, {
6531
- id: relId,
6532
- path: targetPath,
6533
- values: normalizeDbValues(relObj, targetCollection)
6534
- });
6535
- }
6536
- }
6537
- return normalizedValues;
6538
- }
6539
- /**
6540
6603
  * Post-fetch joinPath relations for a single flat row.
6541
6604
  * joinPath relations cannot be expressed via Drizzle's `with` config,
6542
6605
  * so they must be loaded separately after the primary query.
@@ -6567,8 +6630,10 @@ var FetchService = class {
6567
6630
  const shouldInclude = (key) => !include || include.length === 0 || include[0] === "*" || include.includes(key);
6568
6631
  const joinPathRelations = Object.entries(resolvedRelations).filter(([key, relation]) => relation.joinPath && relation.joinPath.length > 0 && propertyKeys.has(key) && shouldInclude(key));
6569
6632
  if (joinPathRelations.length === 0) return;
6570
- const idInfo = idInfoArray[0];
6571
- const parentIdOf = (row) => row[idInfo.fieldName];
6633
+ const parentIdOf = (row) => {
6634
+ const address = buildCompositeId(row, idInfoArray);
6635
+ return address && address.split(":::").some((part) => part !== "") ? address : void 0;
6636
+ };
6572
6637
  for (const [key, relation] of joinPathRelations) try {
6573
6638
  const addressable = rows.filter((r) => parentIdOf(r) !== void 0 && parentIdOf(r) !== null);
6574
6639
  if (addressable.length === 0) continue;
@@ -6588,32 +6653,6 @@ var FetchService = class {
6588
6653
  }
6589
6654
  }
6590
6655
  /**
6591
- * Convert a db.query result row to a flat REST-style row with populated relations.
6592
- *
6593
- * Every column is copied through under its own name, with the value Postgres
6594
- * returned. This used to open with a synthesized `id` and then skip the key
6595
- * column, which renamed it (a `sku` primary key was served as `id`, and `sku`
6596
- * did not appear at all) and restringified it (`42` → `"42"`). Consumers that
6597
- * need an address derive it from the collection's primary keys.
6598
- */
6599
- drizzleResultToRestRow(row, collection) {
6600
- const flat = {};
6601
- const resolvedRelations = resolveCollectionRelations(collection);
6602
- for (const [k, v] of Object.entries(row)) {
6603
- const relation = findRelation(resolvedRelations, k);
6604
- if (Array.isArray(v) && relation) flat[k] = v.map((item) => {
6605
- if (this.isJunctionRelation(relation, collection)) {
6606
- const nestedKey = Object.keys(item).find((nk) => typeof item[nk] === "object" && item[nk] !== null && !Array.isArray(item[nk]));
6607
- if (nestedKey) return { ...item[nestedKey] };
6608
- }
6609
- return { ...item };
6610
- });
6611
- else if (typeof v === "object" && v !== null && !Array.isArray(v) && relation) flat[k] = { ...v };
6612
- else flat[k] = v;
6613
- }
6614
- return flat;
6615
- }
6616
- /**
6617
6656
  * Build db.query-compatible options from standard fetch options.
6618
6657
  * Handles filter, search, orderBy, limit, and cursor-based pagination.
6619
6658
  */
@@ -6683,7 +6722,7 @@ var FetchService = class {
6683
6722
  async fetchOne(collectionPath, id, databaseId) {
6684
6723
  const collection = getCollectionByPath(collectionPath, this.registry);
6685
6724
  const table = getTableForCollection(collection, this.registry);
6686
- const idInfoArray = getPrimaryKeys(collection, this.registry);
6725
+ const idInfoArray = requirePrimaryKeys(collection, this.registry);
6687
6726
  const idInfo = idInfoArray[0];
6688
6727
  const idField = table[idInfo.fieldName];
6689
6728
  if (!idField) throw new Error(`ID field '${idInfo.fieldName}' not found in table for collection '${collectionPath}'`);
@@ -6697,7 +6736,7 @@ var FetchService = class {
6697
6736
  with: withConfig
6698
6737
  });
6699
6738
  if (!row) return void 0;
6700
- const flatRow = this.drizzleResultToRow(row, collection);
6739
+ const flatRow = toCmsRow(row, collection, this.registry);
6701
6740
  await this.resolveJoinPathRelations(flatRow, collection, collectionPath, parsedId, databaseId);
6702
6741
  return flatRow;
6703
6742
  } catch (e) {
@@ -6739,7 +6778,7 @@ var FetchService = class {
6739
6778
  async fetchRowsWithConditions(collectionPath, options = {}) {
6740
6779
  const collection = getCollectionByPath(collectionPath, this.registry);
6741
6780
  const table = getTableForCollection(collection, this.registry);
6742
- const idInfoArray = getPrimaryKeys(collection, this.registry);
6781
+ const idInfoArray = requirePrimaryKeys(collection, this.registry);
6743
6782
  const idInfo = idInfoArray[0];
6744
6783
  const idField = table[idInfo.fieldName];
6745
6784
  if (!idField) throw new Error(`ID field '${idInfo.fieldName}' not found in table for collection '${collectionPath}'`);
@@ -6749,7 +6788,7 @@ var FetchService = class {
6749
6788
  const hasRelations = withConfig && Object.keys(withConfig).length > 0;
6750
6789
  if (qb && !options.searchString && !hasRelations && !options.vectorSearch) try {
6751
6790
  const queryOpts = this.buildDrizzleQueryOptions(table, idField, idInfo, options, collectionPath, void 0);
6752
- return (await qb.findMany(queryOpts)).map((row) => this.drizzleResultToRow(row, collection));
6791
+ return (await qb.findMany(queryOpts)).map((row) => toCmsRow(row, collection, this.registry));
6753
6792
  } catch (e) {
6754
6793
  if (e instanceof Error && e.message.includes("not enough information to infer relation")) {
6755
6794
  logger.error(`[FetchService] Relation inference error for collection '${collectionPath}': ${e.message}`);
@@ -6810,8 +6849,10 @@ var FetchService = class {
6810
6849
  }
6811
6850
  /**
6812
6851
  * Fallback path used when db.query is unavailable.
6813
- * The primary path uses drizzleResultToRow which handles relation
6814
- * mapping without N+1 queries.
6852
+ *
6853
+ * The primary path runs the results through `toCmsRow`, which maps
6854
+ * relations from what drizzle already nested — no query per row. This one
6855
+ * has no nesting to read, so it resolves relations itself, in batches.
6815
6856
  *
6816
6857
  * Process raw database results into flat rows with relations.
6817
6858
  */
@@ -6953,7 +6994,7 @@ var FetchService = class {
6953
6994
  if (value === void 0 || value === null) return true;
6954
6995
  const collection = getCollectionByPath(collectionPath, this.registry);
6955
6996
  const table = getTableForCollection(collection, this.registry);
6956
- const idInfoArray = getPrimaryKeys(collection, this.registry);
6997
+ const idInfoArray = requirePrimaryKeys(collection, this.registry);
6957
6998
  const idInfo = idInfoArray[0];
6958
6999
  const idField = table[idInfo.fieldName];
6959
7000
  const field = table[fieldName];
@@ -6980,7 +7021,7 @@ var FetchService = class {
6980
7021
  async fetchCollectionForRest(collectionPath, options = {}, include) {
6981
7022
  const collection = getCollectionByPath(collectionPath, this.registry);
6982
7023
  const table = getTableForCollection(collection, this.registry);
6983
- const idInfoArray = getPrimaryKeys(collection, this.registry);
7024
+ const idInfoArray = requirePrimaryKeys(collection, this.registry);
6984
7025
  const idInfo = idInfoArray[0];
6985
7026
  const idField = table[idInfo.fieldName];
6986
7027
  const tableName = getTableName(table);
@@ -6988,7 +7029,7 @@ var FetchService = class {
6988
7029
  if (qb && !options.searchString && !options.vectorSearch) try {
6989
7030
  const withConfig = include && include.length > 0 ? this.buildWithConfig(collection, include) : void 0;
6990
7031
  const queryOpts = this.buildDrizzleQueryOptions(table, idField, idInfo, options, collectionPath, withConfig);
6991
- const restRows = (await qb.findMany(queryOpts)).map((row) => this.drizzleResultToRestRow(row, collection));
7032
+ const restRows = (await qb.findMany(queryOpts)).map((row) => toRestRow(row, collection, this.registry));
6992
7033
  await this.resolveJoinPathRelationsBatchRest(restRows, collection, collectionPath, idInfoArray, include);
6993
7034
  return restRows;
6994
7035
  } catch (e) {
@@ -7037,7 +7078,7 @@ var FetchService = class {
7037
7078
  async fetchOneForRest(collectionPath, id, include, databaseId) {
7038
7079
  const collection = getCollectionByPath(collectionPath, this.registry);
7039
7080
  const table = getTableForCollection(collection, this.registry);
7040
- const idInfoArray = getPrimaryKeys(collection, this.registry);
7081
+ const idInfoArray = requirePrimaryKeys(collection, this.registry);
7041
7082
  const idInfo = idInfoArray[0];
7042
7083
  const idField = table[idInfo.fieldName];
7043
7084
  const parsedId = parseIdValues(id, idInfoArray)[idInfo.fieldName];
@@ -7050,7 +7091,7 @@ var FetchService = class {
7050
7091
  ...withConfig ? { with: withConfig } : {}
7051
7092
  });
7052
7093
  if (!row) return null;
7053
- const restRow = this.drizzleResultToRestRow(row, collection);
7094
+ const restRow = toRestRow(row, collection, this.registry);
7054
7095
  await this.resolveJoinPathRelationsBatchRest([restRow], collection, collectionPath, idInfoArray, include);
7055
7096
  return restRow;
7056
7097
  } catch (e) {
@@ -7095,7 +7136,7 @@ var FetchService = class {
7095
7136
  async fetchRowsWithConditionsRaw(collectionPath, options = {}) {
7096
7137
  const collection = getCollectionByPath(collectionPath, this.registry);
7097
7138
  const table = getTableForCollection(collection, this.registry);
7098
- const idField = table[getPrimaryKeys(collection, this.registry)[0].fieldName];
7139
+ const idField = table[requirePrimaryKeys(collection, this.registry)[0].fieldName];
7099
7140
  let vectorMeta;
7100
7141
  if (options.vectorSearch) vectorMeta = DrizzleConditionBuilder.buildVectorSearchConditions(table, options.vectorSearch);
7101
7142
  let query = vectorMeta ? this.db.select({