@rebasepro/server-postgres 0.9.1-canary.16c42e9 → 0.9.1-canary.1d2d8b5

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 (41) hide show
  1. package/dist/PostgresBackendDriver.d.ts +2 -25
  2. package/dist/PostgresBootstrapper.d.ts +0 -10
  3. package/dist/auth/services.d.ts +0 -16
  4. package/dist/connection.d.ts +0 -21
  5. package/dist/data-transformer.d.ts +2 -9
  6. package/dist/index.es.js +548 -1310
  7. package/dist/index.es.js.map +1 -1
  8. package/dist/schema/auth-default-policies.d.ts +10 -0
  9. package/dist/services/FetchService.d.ts +24 -4
  10. package/dist/services/PersistService.d.ts +1 -9
  11. package/dist/services/RelationService.d.ts +1 -34
  12. package/dist/services/collection-helpers.d.ts +14 -79
  13. package/dist/services/dataService.d.ts +1 -3
  14. package/dist/services/index.d.ts +1 -1
  15. package/dist/services/realtimeService.d.ts +0 -7
  16. package/package.json +8 -10
  17. package/src/PostgresBackendDriver.ts +13 -127
  18. package/src/PostgresBootstrapper.ts +25 -62
  19. package/src/auth/ensure-tables.ts +11 -73
  20. package/src/auth/services.ts +19 -49
  21. package/src/connection.ts +1 -61
  22. package/src/data-transformer.ts +9 -11
  23. package/src/databasePoolManager.ts +0 -2
  24. package/src/schema/auth-default-policies.ts +132 -0
  25. package/src/schema/generate-drizzle-schema-logic.ts +29 -24
  26. package/src/schema/generate-postgres-ddl-logic.ts +28 -76
  27. package/src/schema/introspect-db.ts +2 -19
  28. package/src/services/BranchService.ts +10 -42
  29. package/src/services/FetchService.ts +270 -65
  30. package/src/services/PersistService.ts +9 -62
  31. package/src/services/RelationService.ts +94 -153
  32. package/src/services/collection-helpers.ts +47 -164
  33. package/src/services/dataService.ts +2 -3
  34. package/src/services/index.ts +0 -1
  35. package/src/services/realtimeService.ts +19 -40
  36. package/src/utils/drizzle-conditions.ts +0 -13
  37. package/src/websocket.ts +1 -4
  38. package/dist/collections/buildRegistry.d.ts +0 -27
  39. package/dist/services/row-pipeline.d.ts +0 -63
  40. package/src/collections/buildRegistry.ts +0 -59
  41. package/src/services/row-pipeline.ts +0 -215
@@ -1,4 +1,4 @@
1
- import { and, eq, inArray, or, sql, SQL } from "drizzle-orm";
1
+ import { and, eq, inArray, sql, SQL } from "drizzle-orm";
2
2
  import { AnyPgColumn, PgTable } from "drizzle-orm/pg-core";
3
3
  import { DrizzleClient } from "../interfaces";
4
4
  import { CollectionConfig, FilterValues, Relation } from "@rebasepro/types";
@@ -7,10 +7,9 @@ import { DrizzleConditionBuilder } from "../utils/drizzle-conditions";
7
7
  import {
8
8
  getCollectionByPath,
9
9
  getTableForCollection,
10
- requirePrimaryKeys,
10
+ getPrimaryKeys,
11
11
  parseIdValues,
12
- buildCompositeId,
13
- type PrimaryKeyInfo
12
+ buildCompositeId
14
13
  } from "./collection-helpers";
15
14
  import { parseDataFromServer } from "../data-transformer";
16
15
  import { PostgresCollectionRegistry } from "../collections/PostgresCollectionRegistry";
@@ -57,94 +56,6 @@ export interface RelatedRow<M extends Record<string, unknown> = Record<string, u
57
56
  export class RelationService {
58
57
  constructor(private db: DrizzleClient, private registry: PostgresCollectionRegistry) { }
59
58
 
60
- /**
61
- * One target row, as the {@link RelatedRow} everything here returns.
62
- *
63
- * Eight sites built this by hand, which is how the address came to be the
64
- * target's first key column in all eight — one edit, eight places to miss.
65
- *
66
- * `resolveNested` is the one thing they did not agree on, and the
67
- * disagreement was invisible: the single-parent fetches pass `db` and
68
- * `registry` to `parseDataFromServer`, so the target's *own* relations get
69
- * resolved too, while the batch paths deliberately do not — a query per
70
- * target row is the N+1 the batching exists to avoid. Naming the parameter
71
- * makes that a decision rather than a difference between two call sites
72
- * nobody was comparing.
73
- */
74
- private async toRelatedRow<M extends Record<string, unknown>>(
75
- targetRow: Record<string, unknown>,
76
- targetCollection: CollectionConfig,
77
- targetPks: PrimaryKeyInfo[],
78
- options?: { resolveNested?: boolean }
79
- ): Promise<RelatedRow<M>> {
80
- const values = options?.resolveNested
81
- ? await parseDataFromServer(targetRow, targetCollection, this.db, this.registry)
82
- : await parseDataFromServer(targetRow, targetCollection);
83
-
84
- return {
85
- // The whole key: a composite target addressed by its first column
86
- // names every row that shares it.
87
- id: buildCompositeId(targetRow, targetPks),
88
- path: targetCollection.slug,
89
- values: values as M
90
- };
91
- }
92
-
93
- /**
94
- * A WHERE matching any of `parentIds`, by the whole key.
95
- *
96
- * A single key is an `IN (…)`. A composite one cannot be: matching
97
- * `tenant_id IN (1, 1)` collects every row of tenant 1, so two parents that
98
- * share their first column each receive the other's relations. It becomes
99
- * an OR of ANDs — one exact address per parent — which Postgres indexes the
100
- * same way it would a multi-column key lookup.
101
- */
102
- private parentKeyCondition(
103
- parentTable: PgTable,
104
- parentPks: PrimaryKeyInfo[],
105
- parentIds: (string | number)[]
106
- ): SQL {
107
- const columnFor = (fieldName: string) => {
108
- const col = parentTable[fieldName as keyof typeof parentTable] as AnyPgColumn;
109
- if (!col) throw new Error(`Key column '${fieldName}' not found in parent table`);
110
- return col;
111
- };
112
-
113
- if (parentPks.length === 1) {
114
- const values = parentIds.map(id => parseIdValues(id, parentPks)[parentPks[0].fieldName]);
115
- return inArray(columnFor(parentPks[0].fieldName), values);
116
- }
117
-
118
- const perParent = parentIds.map(id => {
119
- const values = parseIdValues(id, parentPks);
120
- return and(...parentPks.map(pk => eq(columnFor(pk.fieldName), values[pk.fieldName])));
121
- });
122
- return or(...perParent) as SQL;
123
- }
124
-
125
- /**
126
- * Reject a relation that cannot express a composite-keyed parent.
127
- *
128
- * `localKey` and `foreignKeyOnTarget` are single column names: one column
129
- * cannot reference a two-column key, so such a relation has no correct
130
- * reading. Left alone it would silently match on the first key column and
131
- * hand a tenant's rows to its neighbour — say so instead.
132
- */
133
- private assertSingleKeyAddressable(
134
- parentCollection: CollectionConfig,
135
- parentPks: PrimaryKeyInfo[],
136
- via: string
137
- ): void {
138
- if (parentPks.length > 1) {
139
- throw new Error(
140
- `Relation on '${parentCollection.slug}' uses '${via}', a single foreign-key column, but ` +
141
- `'${parentCollection.slug}' is keyed on ${parentPks.map(k => `'${k.fieldName}'`).join(" + ")}. ` +
142
- `One column cannot reference a composite key — express this relation with \`joinPath\`, whose ` +
143
- `\`on.from\`/\`on.to\` take every key column.`
144
- );
145
- }
146
- }
147
-
148
59
  /**
149
60
  * Fetch rows related to a parent row through a specific relation
150
61
  */
@@ -193,10 +104,10 @@ export class RelationService {
193
104
  ): Promise<RelatedRow<M>[]> {
194
105
  const targetCollection = relation.target();
195
106
  const targetTable = getTableForCollection(targetCollection, this.registry);
196
- const idInfo = requirePrimaryKeys(targetCollection, this.registry);
107
+ const idInfo = getPrimaryKeys(targetCollection, this.registry);
197
108
  const idField = targetTable[idInfo[0].fieldName as keyof typeof targetTable] as AnyPgColumn;
198
109
 
199
- const parentPks = requirePrimaryKeys(parentCollection, this.registry);
110
+ const parentPks = getPrimaryKeys(parentCollection, this.registry);
200
111
  const parentIdInfo = parentPks[0];
201
112
  const parsedParentIdObj = parseIdValues(parentId, parentPks);
202
113
  const parsedParentId = parsedParentIdObj[parentIdInfo.fieldName];
@@ -237,7 +148,7 @@ export class RelationService {
237
148
  }
238
149
 
239
150
  // Add where condition for the parent row
240
- const parentIdField = parentTable[requirePrimaryKeys(parentCollection, this.registry)[0].fieldName as keyof typeof parentTable] as AnyPgColumn;
151
+ const parentIdField = parentTable[getPrimaryKeys(parentCollection, this.registry)[0].fieldName as keyof typeof parentTable] as AnyPgColumn;
241
152
  query = query.where(eq(parentIdField, parsedParentId));
242
153
 
243
154
  if (options.limit) {
@@ -251,7 +162,14 @@ export class RelationService {
251
162
  const rows: RelatedRow<M>[] = [];
252
163
  for (const row of results as Array<Record<string, unknown>>) {
253
164
  const targetRow = (row[targetTableName] as Record<string, unknown>) || row;
254
- rows.push(await this.toRelatedRow<M>(targetRow, targetCollection, idInfo, { resolveNested: true }));
165
+ const id = targetRow[idInfo[0].fieldName as string];
166
+ const parsedValues = await parseDataFromServer(targetRow, targetCollection, this.db, this.registry);
167
+
168
+ rows.push({
169
+ id: id?.toString() || "",
170
+ path: targetCollection.slug,
171
+ values: parsedValues as M
172
+ });
255
173
  }
256
174
 
257
175
  return rows;
@@ -310,7 +228,14 @@ export class RelationService {
310
228
  const rows: RelatedRow<M>[] = [];
311
229
  for (const row of results) {
312
230
  const targetRow = row[getTableName(targetCollection)] || row;
313
- rows.push(await this.toRelatedRow<M>(targetRow as Record<string, unknown>, targetCollection, idInfo, { resolveNested: true }));
231
+ const id = targetRow[idInfo[0].fieldName];
232
+ const parsedValues = await parseDataFromServer(targetRow, targetCollection, this.db, this.registry);
233
+
234
+ rows.push({
235
+ id: id?.toString() || "",
236
+ path: targetCollection.slug,
237
+ values: parsedValues as M
238
+ });
314
239
  }
315
240
 
316
241
  return rows;
@@ -335,11 +260,11 @@ export class RelationService {
335
260
 
336
261
  const targetCollection = relation.target();
337
262
  const targetTable = getTableForCollection(targetCollection, this.registry);
338
- const targetPks = requirePrimaryKeys(targetCollection, this.registry);
263
+ const targetPks = getPrimaryKeys(targetCollection, this.registry);
339
264
  const targetIdInfo = targetPks[0];
340
265
  const targetIdField = targetTable[targetIdInfo.fieldName as keyof typeof targetTable] as AnyPgColumn;
341
266
 
342
- const parentPks = requirePrimaryKeys(parentCollection, this.registry);
267
+ const parentPks = getPrimaryKeys(parentCollection, this.registry);
343
268
  const parentIdInfo = parentPks[0];
344
269
  const parsedParentIdObj = parseIdValues(parentId, parentPks);
345
270
  const parsedParentId = parsedParentIdObj[parentIdInfo.fieldName];
@@ -384,11 +309,11 @@ export class RelationService {
384
309
  const parentCollection = getCollectionByPath(parentCollectionPath, this.registry);
385
310
  const targetCollection = relation.target();
386
311
  const targetTable = getTableForCollection(targetCollection, this.registry);
387
- const targetPks = requirePrimaryKeys(targetCollection, this.registry);
312
+ const targetPks = getPrimaryKeys(targetCollection, this.registry);
388
313
  const targetIdInfo = targetPks[0];
389
314
  const targetIdField = targetTable[targetIdInfo.fieldName as keyof typeof targetTable] as AnyPgColumn;
390
315
 
391
- const parentPks = requirePrimaryKeys(parentCollection, this.registry);
316
+ const parentPks = getPrimaryKeys(parentCollection, this.registry);
392
317
  const parentIdInfo = parentPks[0];
393
318
  const parentTable = this.registry.getTable(getTableName(parentCollection));
394
319
  if (!parentTable) throw new Error("Parent table not found");
@@ -429,23 +354,27 @@ export class RelationService {
429
354
  currentTable = joinTable;
430
355
  }
431
356
 
432
- // Match every parent at once, each by its whole key.
433
- query = query.where(this.parentKeyCondition(parentTable, parentPks, parentIds));
357
+ // Add where condition for ALL parent rows at once
358
+ const parentIdField = parentTable[getPrimaryKeys(parentCollection, this.registry)[0].fieldName as keyof typeof parentTable] as AnyPgColumn;
359
+ query = query.where(inArray(parentIdField, parsedParentIds));
434
360
 
435
361
  const results = await query;
436
362
  const targetTableName = relation.joinPath[relation.joinPath.length - 1].table;
437
363
  const resultMap = new Map<string, RelatedRow<Record<string, unknown>>>();
438
364
 
439
- // Group by the parent's address — the same token the caller looks
440
- // results up by, derived the same way on both sides.
365
+ // Group results by parent ID
441
366
  for (const row of results as Array<Record<string, unknown>>) {
442
367
  const parentRow = (row[getTableName(parentCollection)] || row) as Record<string, unknown>;
443
368
  const targetRow = (row[targetTableName] || row) as Record<string, unknown>;
369
+ const parentId = parentRow[parentIdInfo.fieldName] as string | number;
370
+
371
+ const parsedValues = await parseDataFromServer(targetRow, targetCollection);
444
372
 
445
- resultMap.set(
446
- buildCompositeId(parentRow, parentPks),
447
- await this.toRelatedRow(targetRow, targetCollection, targetPks)
448
- );
373
+ resultMap.set(String(parentId), {
374
+ id: String(targetRow[targetIdInfo.fieldName]),
375
+ path: targetCollection.slug,
376
+ values: parsedValues as Record<string, unknown>
377
+ });
449
378
  }
450
379
 
451
380
  return resultMap;
@@ -458,7 +387,6 @@ export class RelationService {
458
387
  // 2. Query the target table with unique FK values
459
388
  // 3. Map results back to parent rows via their FK values
460
389
  if (relation.direction === "owning" && relation.localKey) {
461
- this.assertSingleKeyAddressable(parentCollection, parentPks, relation.localKey);
462
390
  const localKeyCol = parentTable[relation.localKey as keyof typeof parentTable] as AnyPgColumn;
463
391
  if (!localKeyCol) {
464
392
  throw new Error(`Local key column '${relation.localKey}' not found in parent table`);
@@ -508,21 +436,19 @@ export class RelationService {
508
436
  for (const [parentIdStr, fkValue] of parentToFk) {
509
437
  const targetRow = targetById.get(String(fkValue));
510
438
  if (targetRow) {
511
- resultMap.set(parentIdStr, await this.toRelatedRow(targetRow, targetCollection, targetPks));
439
+ const parsedValues = await parseDataFromServer(targetRow, targetCollection);
440
+ resultMap.set(parentIdStr, {
441
+ id: String(targetRow[targetIdInfo.fieldName]),
442
+ path: targetCollection.slug,
443
+ values: parsedValues as Record<string, unknown>
444
+ });
512
445
  }
513
446
  }
514
447
 
515
448
  return resultMap;
516
449
  }
517
450
 
518
- // Handle inverse relation types with batching. The parent is named by a
519
- // single FK column on the target, so a composite-keyed parent has no
520
- // correct reading here either.
521
- this.assertSingleKeyAddressable(
522
- parentCollection,
523
- parentPks,
524
- relation.foreignKeyOnTarget ?? `${relation.inverseRelationName}_id`
525
- );
451
+ // Handle inverse relation types with batching
526
452
  let query = this.db.select().from(targetTable).$dynamic();
527
453
 
528
454
  // Build the relation query with ALL parent IDs
@@ -562,7 +488,12 @@ export class RelationService {
562
488
  }
563
489
 
564
490
  if (parentId !== undefined && parentIdSet.has(String(parentId))) {
565
- resultMap.set(String(parentId), await this.toRelatedRow(targetRow, targetCollection, targetPks));
491
+ const parsedValues = await parseDataFromServer(targetRow, targetCollection);
492
+ resultMap.set(String(parentId), {
493
+ id: String(targetRow[targetIdInfo.fieldName]),
494
+ path: targetCollection.slug,
495
+ values: parsedValues as Record<string, unknown>
496
+ });
566
497
  }
567
498
  }
568
499
 
@@ -585,11 +516,11 @@ export class RelationService {
585
516
  const parentCollection = getCollectionByPath(parentCollectionPath, this.registry);
586
517
  const targetCollection = relation.target();
587
518
  const targetTable = getTableForCollection(targetCollection, this.registry);
588
- const targetPks = requirePrimaryKeys(targetCollection, this.registry);
519
+ const targetPks = getPrimaryKeys(targetCollection, this.registry);
589
520
  const targetIdInfo = targetPks[0];
590
521
  const targetIdField = targetTable[targetIdInfo.fieldName as keyof typeof targetTable] as AnyPgColumn;
591
522
 
592
- const parentPks = requirePrimaryKeys(parentCollection, this.registry);
523
+ const parentPks = getPrimaryKeys(parentCollection, this.registry);
593
524
  const parentIdInfo = parentPks[0];
594
525
  const parentTable = this.registry.getTable(getTableName(parentCollection));
595
526
  if (!parentTable) throw new Error("Parent table not found");
@@ -619,7 +550,8 @@ export class RelationService {
619
550
  currentTable = joinTable;
620
551
  }
621
552
 
622
- query = query.where(this.parentKeyCondition(parentTable, parentPks, parentIds));
553
+ const parentIdField = parentTable[getPrimaryKeys(parentCollection, this.registry)[0].fieldName as keyof typeof parentTable] as AnyPgColumn;
554
+ query = query.where(inArray(parentIdField, parsedParentIds));
623
555
 
624
556
  const results = await query;
625
557
  const targetTableName = relation.joinPath[relation.joinPath.length - 1].table;
@@ -628,9 +560,15 @@ export class RelationService {
628
560
  for (const row of results as Array<Record<string, unknown>>) {
629
561
  const parentRow = (row[getTableName(parentCollection)] || row) as Record<string, unknown>;
630
562
  const targetRow = (row[targetTableName] || row) as Record<string, unknown>;
631
- const parentId = buildCompositeId(parentRow, parentPks);
563
+ const parentId = String(parentRow[parentIdInfo.fieldName]);
564
+ const parsedValues = await parseDataFromServer(targetRow, targetCollection);
565
+
632
566
  const arr = resultMap.get(parentId) || [];
633
- arr.push(await this.toRelatedRow(targetRow, targetCollection, targetPks));
567
+ arr.push({
568
+ id: String(targetRow[targetIdInfo.fieldName]),
569
+ path: targetCollection.slug,
570
+ values: parsedValues as Record<string, unknown>
571
+ });
634
572
  resultMap.set(parentId, arr);
635
573
  }
636
574
 
@@ -641,9 +579,6 @@ export class RelationService {
641
579
  // This is the standard path for posts→tags style relations where
642
580
  // sanitizeRelation populated the `through` config.
643
581
  if (relation.through && relation.cardinality === "many" && relation.direction === "owning") {
644
- // The junction names its parent with one column, so the same
645
- // single-key limit applies as for a direct foreign key.
646
- this.assertSingleKeyAddressable(parentCollection, parentPks, `${relation.through.table}.${relation.through.sourceColumn}`);
647
582
  const junctionTable = this.registry.getTable(relation.through.table);
648
583
  if (!junctionTable) {
649
584
  logger.warn(`[batchFetchRelatedEntitiesMany] Junction table '${relation.through.table}' not found`);
@@ -677,21 +612,21 @@ export class RelationService {
677
612
  const targetData = (row[targetTableName] || row) as Record<string, unknown>;
678
613
 
679
614
  const parentId = String(junctionData[relation.through.sourceColumn]);
615
+ const parsedValues = await parseDataFromServer(targetData, targetCollection);
616
+
680
617
  const arr = resultMap.get(parentId) || [];
681
- arr.push(await this.toRelatedRow(targetData, targetCollection, targetPks));
618
+ arr.push({
619
+ id: String(targetData[targetIdInfo.fieldName]),
620
+ path: targetCollection.slug,
621
+ values: parsedValues as Record<string, unknown>
622
+ });
682
623
  resultMap.set(parentId, arr);
683
624
  }
684
625
 
685
626
  return resultMap;
686
627
  }
687
628
 
688
- // Handle FK-based relations (one-to-many inverse). One column on the
689
- // target names the parent, so a composite-keyed parent cannot be named.
690
- this.assertSingleKeyAddressable(
691
- parentCollection,
692
- parentPks,
693
- relation.foreignKeyOnTarget ?? `${relation.inverseRelationName}_id`
694
- );
629
+ // Handle FK-based relations (one-to-many inverse)
695
630
  let query = this.db.select().from(targetTable).$dynamic();
696
631
 
697
632
  query = applyDynamicRelationQuery(
@@ -733,9 +668,14 @@ export class RelationService {
733
668
  }
734
669
 
735
670
  if (parentId !== undefined && parentIdSet.has(String(parentId))) {
671
+ const parsedValues = await parseDataFromServer(targetRow, targetCollection);
736
672
  const key = String(parentId);
737
673
  const arr = resultMap.get(key) || [];
738
- arr.push(await this.toRelatedRow(targetRow, targetCollection, targetPks));
674
+ arr.push({
675
+ id: String(targetRow[targetIdInfo.fieldName]),
676
+ path: targetCollection.slug,
677
+ values: parsedValues as Record<string, unknown>
678
+ });
739
679
  resultMap.set(key, arr);
740
680
  }
741
681
  }
@@ -806,7 +746,7 @@ export class RelationService {
806
746
  continue;
807
747
  }
808
748
 
809
- const parentPks = requirePrimaryKeys(collection, this.registry);
749
+ const parentPks = getPrimaryKeys(collection, this.registry);
810
750
  const parentIdInfo = parentPks[0];
811
751
  const parsedParentIdObj = parseIdValues(id, parentPks);
812
752
  const parsedParentId = parsedParentIdObj[parentIdInfo.fieldName];
@@ -815,7 +755,7 @@ export class RelationService {
815
755
  await tx.delete(junctionTable).where(eq(sourceJunctionColumn, parsedParentId));
816
756
 
817
757
  if (targetEntityIds.length > 0) {
818
- const targetPks = requirePrimaryKeys(targetCollection, this.registry);
758
+ const targetPks = getPrimaryKeys(targetCollection, this.registry);
819
759
  const targetIdInfo = targetPks[0];
820
760
  const parsedTargetIds = targetEntityIds.map(id => parseIdValues(id, targetPks)[targetIdInfo.fieldName]);
821
761
 
@@ -844,7 +784,7 @@ export class RelationService {
844
784
  continue;
845
785
  }
846
786
 
847
- const parentPks = requirePrimaryKeys(collection, this.registry);
787
+ const parentPks = getPrimaryKeys(collection, this.registry);
848
788
  const parentIdInfo = parentPks[0];
849
789
  const parsedParentIdObj = parseIdValues(id, parentPks);
850
790
  const parsedParentId = parsedParentIdObj[parentIdInfo.fieldName];
@@ -853,7 +793,7 @@ export class RelationService {
853
793
  await tx.delete(junctionTable).where(eq(sourceJunctionColumn, parsedParentId));
854
794
 
855
795
  if (targetEntityIds.length > 0) {
856
- const targetPks = requirePrimaryKeys(targetCollection, this.registry);
796
+ const targetPks = getPrimaryKeys(targetCollection, this.registry);
857
797
  const targetIdInfo = targetPks[0];
858
798
  const parsedTargetIds = targetEntityIds.map(id => parseIdValues(id, targetPks)[targetIdInfo.fieldName]);
859
799
 
@@ -873,7 +813,7 @@ export class RelationService {
873
813
  } else if (relation.cardinality === "many" && relation.direction === "inverse" && relation.foreignKeyOnTarget) {
874
814
  // Handle one-to-many (inverse) by updating target FK to point to parent
875
815
  const targetTable = getTableForCollection(targetCollection, this.registry);
876
- const targetPks = requirePrimaryKeys(targetCollection, this.registry);
816
+ const targetPks = getPrimaryKeys(targetCollection, this.registry);
877
817
  const targetIdInfo = targetPks[0];
878
818
  const targetIdCol = targetTable[targetIdInfo.fieldName as keyof typeof targetTable] as AnyPgColumn;
879
819
  const fkCol = targetTable[relation.foreignKeyOnTarget as keyof typeof targetTable] as AnyPgColumn;
@@ -883,7 +823,7 @@ export class RelationService {
883
823
  continue;
884
824
  }
885
825
 
886
- const parentPks = requirePrimaryKeys(collection, this.registry);
826
+ const parentPks = getPrimaryKeys(collection, this.registry);
887
827
  const parentIdInfo = parentPks[0];
888
828
  const parsedParentIdObj = parseIdValues(id, parentPks);
889
829
  const parsedParentId = parsedParentIdObj[parentIdInfo.fieldName];
@@ -925,6 +865,7 @@ export class RelationService {
925
865
  relationKey: string;
926
866
  relation: Relation;
927
867
  newValue: unknown;
868
+ currentId?: string | number;
928
869
  }>
929
870
  ) {
930
871
  for (const update of inverseRelationUpdates) {
@@ -933,9 +874,9 @@ export class RelationService {
933
874
  try {
934
875
  const targetCollection = relation.target();
935
876
  const targetTable = getTableForCollection(targetCollection, this.registry);
936
- const targetPks = requirePrimaryKeys(targetCollection, this.registry);
877
+ const targetPks = getPrimaryKeys(targetCollection, this.registry);
937
878
  const targetIdInfo = targetPks[0];
938
- const sourcePks = requirePrimaryKeys(sourceCollection, this.registry);
879
+ const sourcePks = getPrimaryKeys(sourceCollection, this.registry);
939
880
  const sourceIdInfo = sourcePks[0];
940
881
 
941
882
  // Handle inverse relations with joinPath
@@ -1092,7 +1033,7 @@ export class RelationService {
1092
1033
  }
1093
1034
 
1094
1035
  // Perform the junction table update
1095
- const sourcePks = requirePrimaryKeys(sourceCollection, this.registry);
1036
+ const sourcePks = getPrimaryKeys(sourceCollection, this.registry);
1096
1037
  const sourceIdInfo = sourcePks[0];
1097
1038
  const parsedSourceIdObj = parseIdValues(sourceEntityId, sourcePks);
1098
1039
  const parsedSourceId = parsedSourceIdObj[sourceIdInfo.fieldName];
@@ -1102,7 +1043,7 @@ export class RelationService {
1102
1043
 
1103
1044
  // Add new entries if newValue is provided
1104
1045
  if (newValue && Array.isArray(newValue) && newValue.length > 0) {
1105
- const targetPks = requirePrimaryKeys(targetCollection, this.registry);
1046
+ const targetPks = getPrimaryKeys(targetCollection, this.registry);
1106
1047
  const targetIdInfo = targetPks[0];
1107
1048
  const targetEntityIds = (newValue as Array<{ id: string | number } | string | number>).map((rel) => typeof rel === "object" && rel !== null ? rel.id : rel);
1108
1049
  const parsedTargetIds = targetEntityIds.map(id => parseIdValues(id, targetPks)[targetIdInfo.fieldName]);
@@ -1117,7 +1058,7 @@ export class RelationService {
1117
1058
  }
1118
1059
  } else if (newValue && !Array.isArray(newValue)) {
1119
1060
  // Single value for one-to-one
1120
- const targetPks = requirePrimaryKeys(targetCollection, this.registry);
1061
+ const targetPks = getPrimaryKeys(targetCollection, this.registry);
1121
1062
  const targetIdInfo = targetPks[0];
1122
1063
  const targetId = typeof newValue === "object" && newValue !== null ? (newValue as Record<string, unknown>).id as string | number : newValue as string | number;
1123
1064
  const parsedTargetIdObj = parseIdValues(targetId, targetPks);
@@ -1164,7 +1105,7 @@ export class RelationService {
1164
1105
  return;
1165
1106
  }
1166
1107
 
1167
- const sourcePks = requirePrimaryKeys(sourceCollection, this.registry);
1108
+ const sourcePks = getPrimaryKeys(sourceCollection, this.registry);
1168
1109
  const sourceIdInfo = sourcePks[0];
1169
1110
  const parsedSourceIdObj = parseIdValues(sourceEntityId, sourcePks);
1170
1111
  const parsedSourceId = parsedSourceIdObj[sourceIdInfo.fieldName];
@@ -1174,7 +1115,7 @@ export class RelationService {
1174
1115
 
1175
1116
  // Add new entries if newValue is provided
1176
1117
  if (newValue && Array.isArray(newValue) && newValue.length > 0) {
1177
- const targetPks = requirePrimaryKeys(targetCollection, this.registry);
1118
+ const targetPks = getPrimaryKeys(targetCollection, this.registry);
1178
1119
  const targetIdInfo = targetPks[0];
1179
1120
  const targetEntityIds = (newValue as Array<{ id: string | number }>).map((rel) => rel.id);
1180
1121
  const parsedTargetIds = targetEntityIds.map(id => parseIdValues(id, targetPks)[targetIdInfo.fieldName]);
@@ -1211,14 +1152,14 @@ export class RelationService {
1211
1152
  const { relation, newTargetId } = upd;
1212
1153
  const targetCollection = relation.target();
1213
1154
  const targetTable = getTableForCollection(targetCollection, this.registry);
1214
- const targetPks = requirePrimaryKeys(targetCollection, this.registry);
1155
+ const targetPks = getPrimaryKeys(targetCollection, this.registry);
1215
1156
  const targetIdInfo = targetPks[0];
1216
1157
  const targetIdCol = targetTable[targetIdInfo.fieldName as keyof typeof targetTable] as AnyPgColumn;
1217
1158
 
1218
1159
  // Determine mapping of columns
1219
1160
  const { targetFKColName, parentSourceColName } = this.resolveJoinPathWriteMapping(parentCollection, relation);
1220
1161
  const parentTable = getTableForCollection(parentCollection, this.registry);
1221
- const parentPks = requirePrimaryKeys(parentCollection, this.registry);
1162
+ const parentPks = getPrimaryKeys(parentCollection, this.registry);
1222
1163
  const parentIdInfo = parentPks[0];
1223
1164
  const parsedParentIdObj = parseIdValues(parentId, parentPks);
1224
1165
  const parsedParentId = parsedParentIdObj[parentIdInfo.fieldName];
@@ -1343,7 +1284,7 @@ parentSourceColName };
1343
1284
  }
1344
1285
 
1345
1286
  // Parse the new row ID to the correct type
1346
- const targetPks = requirePrimaryKeys(targetCollection, this.registry);
1287
+ const targetPks = getPrimaryKeys(targetCollection, this.registry);
1347
1288
  const targetIdInfo = targetPks[0];
1348
1289
  const parsedNewEntityIdObj = parseIdValues(newEntityId, targetPks);
1349
1290
  const parsedNewEntityId = parsedNewEntityIdObj[targetIdInfo.fieldName];