@rebasepro/server-postgresql 0.5.0 → 0.6.0

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 (165) hide show
  1. package/dist/{server-postgresql/src/PostgresAdapter.d.ts → PostgresAdapter.d.ts} +1 -1
  2. package/dist/{server-postgresql/src/PostgresBackendDriver.d.ts → PostgresBackendDriver.d.ts} +2 -2
  3. package/dist/{server-postgresql/src/PostgresBootstrapper.d.ts → PostgresBootstrapper.d.ts} +11 -1
  4. package/dist/{server-postgresql/src/collections → collections}/PostgresCollectionRegistry.d.ts +4 -0
  5. package/dist/index.es.js +10168 -11145
  6. package/dist/index.es.js.map +1 -1
  7. package/dist/index.umd.js +10735 -11429
  8. package/dist/index.umd.js.map +1 -1
  9. package/dist/{server-postgresql/src/services → services}/EntityPersistService.d.ts +0 -14
  10. package/dist/utils/pg-error-utils.d.ts +55 -0
  11. package/package.json +24 -21
  12. package/src/PostgresAdapter.ts +9 -10
  13. package/src/PostgresBackendDriver.ts +134 -121
  14. package/src/PostgresBootstrapper.ts +86 -13
  15. package/src/auth/ensure-tables.ts +28 -5
  16. package/src/auth/services.ts +28 -18
  17. package/src/cli.ts +99 -96
  18. package/src/collections/PostgresCollectionRegistry.ts +7 -0
  19. package/src/connection.ts +11 -6
  20. package/src/data-transformer.ts +16 -14
  21. package/src/databasePoolManager.ts +3 -2
  22. package/src/history/HistoryService.ts +3 -2
  23. package/src/history/ensure-history-table.ts +5 -4
  24. package/src/schema/auth-schema.ts +1 -2
  25. package/src/schema/doctor-cli.ts +2 -1
  26. package/src/schema/doctor.ts +40 -37
  27. package/src/schema/generate-drizzle-schema-logic.ts +56 -18
  28. package/src/schema/generate-drizzle-schema.ts +11 -11
  29. package/src/schema/introspect-db-inference.ts +25 -25
  30. package/src/schema/introspect-db-logic.ts +38 -38
  31. package/src/schema/introspect-db.ts +28 -27
  32. package/src/services/BranchService.ts +14 -0
  33. package/src/services/EntityFetchService.ts +28 -25
  34. package/src/services/EntityPersistService.ts +11 -141
  35. package/src/services/RelationService.ts +57 -37
  36. package/src/services/entity-helpers.ts +6 -2
  37. package/src/services/realtimeService.ts +45 -32
  38. package/src/utils/drizzle-conditions.ts +31 -15
  39. package/src/utils/pg-error-utils.ts +211 -0
  40. package/src/websocket.ts +15 -12
  41. package/test/auth-services.test.ts +36 -19
  42. package/test/batch-many-to-many-regression.test.ts +119 -39
  43. package/test/data-transformer-hardening.test.ts +67 -33
  44. package/test/data-transformer.test.ts +4 -2
  45. package/test/doctor.test.ts +10 -5
  46. package/test/drizzle-conditions.test.ts +59 -6
  47. package/test/generate-drizzle-schema.test.ts +65 -40
  48. package/test/introspect-db-generation.test.ts +179 -81
  49. package/test/introspect-db-utils.test.ts +92 -37
  50. package/test/mocks/chalk.cjs +7 -0
  51. package/test/pg-error-utils.test.ts +221 -0
  52. package/test/postgresDataDriver.test.ts +14 -5
  53. package/test/property-ordering.test.ts +126 -79
  54. package/test/realtimeService.test.ts +6 -2
  55. package/test/relation-pipeline-gaps.test.ts +84 -36
  56. package/test/relations.test.ts +247 -0
  57. package/test/unmapped-tables-safety.test.ts +14 -6
  58. package/test/websocket.test.ts +1 -1
  59. package/tsconfig.json +5 -0
  60. package/tsconfig.prod.json +3 -0
  61. package/vite.config.ts +5 -5
  62. package/dist/common/src/collections/CollectionRegistry.d.ts +0 -56
  63. package/dist/common/src/collections/default-collections.d.ts +0 -9
  64. package/dist/common/src/collections/index.d.ts +0 -2
  65. package/dist/common/src/data/buildRebaseData.d.ts +0 -14
  66. package/dist/common/src/data/query_builder.d.ts +0 -55
  67. package/dist/common/src/index.d.ts +0 -4
  68. package/dist/common/src/util/builders.d.ts +0 -57
  69. package/dist/common/src/util/callbacks.d.ts +0 -6
  70. package/dist/common/src/util/collections.d.ts +0 -11
  71. package/dist/common/src/util/common.d.ts +0 -2
  72. package/dist/common/src/util/conditions.d.ts +0 -26
  73. package/dist/common/src/util/entities.d.ts +0 -58
  74. package/dist/common/src/util/enums.d.ts +0 -3
  75. package/dist/common/src/util/index.d.ts +0 -16
  76. package/dist/common/src/util/navigation_from_path.d.ts +0 -34
  77. package/dist/common/src/util/navigation_utils.d.ts +0 -20
  78. package/dist/common/src/util/parent_references_from_path.d.ts +0 -6
  79. package/dist/common/src/util/paths.d.ts +0 -14
  80. package/dist/common/src/util/permissions.d.ts +0 -14
  81. package/dist/common/src/util/references.d.ts +0 -2
  82. package/dist/common/src/util/relations.d.ts +0 -22
  83. package/dist/common/src/util/resolutions.d.ts +0 -72
  84. package/dist/common/src/util/storage.d.ts +0 -24
  85. package/dist/types/src/controllers/analytics_controller.d.ts +0 -7
  86. package/dist/types/src/controllers/auth.d.ts +0 -104
  87. package/dist/types/src/controllers/client.d.ts +0 -168
  88. package/dist/types/src/controllers/collection_registry.d.ts +0 -46
  89. package/dist/types/src/controllers/customization_controller.d.ts +0 -60
  90. package/dist/types/src/controllers/data.d.ts +0 -207
  91. package/dist/types/src/controllers/data_driver.d.ts +0 -218
  92. package/dist/types/src/controllers/database_admin.d.ts +0 -11
  93. package/dist/types/src/controllers/dialogs_controller.d.ts +0 -36
  94. package/dist/types/src/controllers/effective_role.d.ts +0 -4
  95. package/dist/types/src/controllers/email.d.ts +0 -36
  96. package/dist/types/src/controllers/index.d.ts +0 -18
  97. package/dist/types/src/controllers/local_config_persistence.d.ts +0 -20
  98. package/dist/types/src/controllers/navigation.d.ts +0 -225
  99. package/dist/types/src/controllers/registry.d.ts +0 -63
  100. package/dist/types/src/controllers/side_dialogs_controller.d.ts +0 -67
  101. package/dist/types/src/controllers/side_entity_controller.d.ts +0 -97
  102. package/dist/types/src/controllers/snackbar.d.ts +0 -24
  103. package/dist/types/src/controllers/storage.d.ts +0 -171
  104. package/dist/types/src/index.d.ts +0 -4
  105. package/dist/types/src/rebase_context.d.ts +0 -122
  106. package/dist/types/src/types/auth_adapter.d.ts +0 -301
  107. package/dist/types/src/types/backend.d.ts +0 -571
  108. package/dist/types/src/types/backend_hooks.d.ts +0 -172
  109. package/dist/types/src/types/builders.d.ts +0 -15
  110. package/dist/types/src/types/chips.d.ts +0 -5
  111. package/dist/types/src/types/collections.d.ts +0 -961
  112. package/dist/types/src/types/component_ref.d.ts +0 -47
  113. package/dist/types/src/types/cron.d.ts +0 -102
  114. package/dist/types/src/types/data_source.d.ts +0 -64
  115. package/dist/types/src/types/database_adapter.d.ts +0 -94
  116. package/dist/types/src/types/entities.d.ts +0 -145
  117. package/dist/types/src/types/entity_actions.d.ts +0 -104
  118. package/dist/types/src/types/entity_callbacks.d.ts +0 -173
  119. package/dist/types/src/types/entity_link_builder.d.ts +0 -7
  120. package/dist/types/src/types/entity_overrides.d.ts +0 -10
  121. package/dist/types/src/types/entity_views.d.ts +0 -87
  122. package/dist/types/src/types/export_import.d.ts +0 -21
  123. package/dist/types/src/types/formex.d.ts +0 -40
  124. package/dist/types/src/types/index.d.ts +0 -28
  125. package/dist/types/src/types/locales.d.ts +0 -4
  126. package/dist/types/src/types/modify_collections.d.ts +0 -5
  127. package/dist/types/src/types/plugins.d.ts +0 -282
  128. package/dist/types/src/types/properties.d.ts +0 -1173
  129. package/dist/types/src/types/property_config.d.ts +0 -74
  130. package/dist/types/src/types/relations.d.ts +0 -336
  131. package/dist/types/src/types/slots.d.ts +0 -262
  132. package/dist/types/src/types/translations.d.ts +0 -900
  133. package/dist/types/src/types/user_management_delegate.d.ts +0 -86
  134. package/dist/types/src/types/websockets.d.ts +0 -78
  135. package/dist/types/src/users/index.d.ts +0 -1
  136. package/dist/types/src/users/user.d.ts +0 -50
  137. /package/dist/{server-postgresql/src/auth → auth}/ensure-tables.d.ts +0 -0
  138. /package/dist/{server-postgresql/src/auth → auth}/services.d.ts +0 -0
  139. /package/dist/{server-postgresql/src/cli.d.ts → cli.d.ts} +0 -0
  140. /package/dist/{server-postgresql/src/connection.d.ts → connection.d.ts} +0 -0
  141. /package/dist/{server-postgresql/src/data-transformer.d.ts → data-transformer.d.ts} +0 -0
  142. /package/dist/{server-postgresql/src/databasePoolManager.d.ts → databasePoolManager.d.ts} +0 -0
  143. /package/dist/{server-postgresql/src/history → history}/HistoryService.d.ts +0 -0
  144. /package/dist/{server-postgresql/src/history → history}/ensure-history-table.d.ts +0 -0
  145. /package/dist/{server-postgresql/src/index.d.ts → index.d.ts} +0 -0
  146. /package/dist/{server-postgresql/src/interfaces.d.ts → interfaces.d.ts} +0 -0
  147. /package/dist/{server-postgresql/src/schema → schema}/auth-schema.d.ts +0 -0
  148. /package/dist/{server-postgresql/src/schema → schema}/doctor-cli.d.ts +0 -0
  149. /package/dist/{server-postgresql/src/schema → schema}/doctor.d.ts +0 -0
  150. /package/dist/{server-postgresql/src/schema → schema}/generate-drizzle-schema-logic.d.ts +0 -0
  151. /package/dist/{server-postgresql/src/schema → schema}/generate-drizzle-schema.d.ts +0 -0
  152. /package/dist/{server-postgresql/src/schema → schema}/introspect-db-inference.d.ts +0 -0
  153. /package/dist/{server-postgresql/src/schema → schema}/introspect-db-logic.d.ts +0 -0
  154. /package/dist/{server-postgresql/src/schema → schema}/introspect-db.d.ts +0 -0
  155. /package/dist/{server-postgresql/src/schema → schema}/test-schema.d.ts +0 -0
  156. /package/dist/{server-postgresql/src/services → services}/BranchService.d.ts +0 -0
  157. /package/dist/{server-postgresql/src/services → services}/EntityFetchService.d.ts +0 -0
  158. /package/dist/{server-postgresql/src/services → services}/RelationService.d.ts +0 -0
  159. /package/dist/{server-postgresql/src/services → services}/entity-helpers.d.ts +0 -0
  160. /package/dist/{server-postgresql/src/services → services}/entityService.d.ts +0 -0
  161. /package/dist/{server-postgresql/src/services → services}/index.d.ts +0 -0
  162. /package/dist/{server-postgresql/src/services → services}/realtimeService.d.ts +0 -0
  163. /package/dist/{server-postgresql/src/types.d.ts → types.d.ts} +0 -0
  164. /package/dist/{server-postgresql/src/utils → utils}/drizzle-conditions.d.ts +0 -0
  165. /package/dist/{server-postgresql/src/websocket.d.ts → websocket.d.ts} +0 -0
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  computePropertyPriority, sortPropertiesOrder,
3
- PropertyOrderingContext, PropertyOrderEntry,
3
+ PropertyOrderingContext, PropertyOrderEntry
4
4
  } from "../src/schema/introspect-db-logic";
5
5
 
6
6
  // ── Helpers ───────────────────────────────────────────────────────────
@@ -13,12 +13,13 @@ function mkCtx(overrides: Partial<PropertyOrderingContext> = {}): PropertyOrderi
13
13
  isStorage: false,
14
14
  pgDataType: "character varying",
15
15
  originalIndex: 0,
16
- ...overrides,
16
+ ...overrides
17
17
  };
18
18
  }
19
19
 
20
20
  function mkEntry(key: string, overrides: Partial<PropertyOrderingContext> = {}): PropertyOrderEntry {
21
- return { key, ctx: mkCtx(overrides) };
21
+ return { key,
22
+ ctx: mkCtx(overrides) };
22
23
  }
23
24
 
24
25
  // ═══════════════════════════════════════════════════════════════════════
@@ -52,7 +53,7 @@ describe("computePropertyPriority", () => {
52
53
  const score = computePropertyPriority(col, mkCtx());
53
54
  expect(score).toBeGreaterThanOrEqual(10);
54
55
  expect(score).toBeLessThan(20);
55
- },
56
+ }
56
57
  );
57
58
 
58
59
  it("ranks 'product_name' as partial match in tier 1b (17-19)", () => {
@@ -75,7 +76,7 @@ describe("computePropertyPriority", () => {
75
76
  const score = computePropertyPriority(col, mkCtx());
76
77
  expect(score).toBeGreaterThanOrEqual(20);
77
78
  expect(score).toBeLessThan(30);
78
- },
79
+ }
79
80
  );
80
81
 
81
82
  it("ranks first_name before last_name", () => {
@@ -92,7 +93,7 @@ describe("computePropertyPriority", () => {
92
93
  const score = computePropertyPriority(col, mkCtx());
93
94
  expect(score).toBeGreaterThanOrEqual(30);
94
95
  expect(score).toBeLessThan(40);
95
- },
96
+ }
96
97
  );
97
98
  });
98
99
 
@@ -110,7 +111,8 @@ describe("computePropertyPriority", () => {
110
111
  });
111
112
 
112
113
  it("ranks short string (varchar) in tier 4", () => {
113
- const score = computePropertyPriority("color", mkCtx({ propType: "string", pgDataType: "character varying" }));
114
+ const score = computePropertyPriority("color", mkCtx({ propType: "string",
115
+ pgDataType: "character varying" }));
114
116
  expect(score).toBeGreaterThanOrEqual(40);
115
117
  expect(score).toBeLessThan(50);
116
118
  });
@@ -145,7 +147,7 @@ describe("computePropertyPriority", () => {
145
147
  const score = computePropertyPriority(col, mkCtx());
146
148
  expect(score).toBeGreaterThanOrEqual(70);
147
149
  expect(score).toBeLessThan(80);
148
- },
150
+ }
149
151
  );
150
152
  });
151
153
 
@@ -156,7 +158,7 @@ describe("computePropertyPriority", () => {
156
158
  const score = computePropertyPriority(col, mkCtx());
157
159
  expect(score).toBeGreaterThanOrEqual(80);
158
160
  expect(score).toBeLessThan(90);
159
- },
161
+ }
160
162
  );
161
163
  });
162
164
 
@@ -173,7 +175,7 @@ describe("computePropertyPriority", () => {
173
175
  const score = computePropertyPriority(col, mkCtx());
174
176
  expect(score).toBeGreaterThanOrEqual(90);
175
177
  expect(score).toBeLessThan(100);
176
- },
178
+ }
177
179
  );
178
180
 
179
181
  it("ranks URL-suffix fields in tier 9", () => {
@@ -212,7 +214,7 @@ describe("computePropertyPriority", () => {
212
214
  const score = computePropertyPriority(col, mkCtx({ propType: "date" }));
213
215
  expect(score).toBeGreaterThanOrEqual(120);
214
216
  expect(score).toBeLessThan(130);
215
- },
217
+ }
216
218
  );
217
219
 
218
220
  it("ranks created_at before updated_at", () => {
@@ -249,10 +251,12 @@ describe("computePropertyPriority", () => {
249
251
  describe("sortPropertiesOrder", () => {
250
252
  it("places 'id' before 'name' before generic fields", () => {
251
253
  const entries: PropertyOrderEntry[] = [
252
- mkEntry("created_at", { propType: "date", originalIndex: 3 }),
254
+ mkEntry("created_at", { propType: "date",
255
+ originalIndex: 3 }),
253
256
  mkEntry("name", { originalIndex: 1 }),
254
- mkEntry("id", { isPk: true, originalIndex: 0 }),
255
- mkEntry("status", { originalIndex: 2 }),
257
+ mkEntry("id", { isPk: true,
258
+ originalIndex: 0 }),
259
+ mkEntry("status", { originalIndex: 2 })
256
260
  ];
257
261
  const result = sortPropertiesOrder(entries);
258
262
  expect(result).toEqual(["id", "name", "status", "created_at"]);
@@ -260,95 +264,132 @@ describe("sortPropertiesOrder", () => {
260
264
 
261
265
  it("sorts a realistic user table correctly", () => {
262
266
  const entries: PropertyOrderEntry[] = [
263
- mkEntry("id", { isPk: true, propType: "string", pgDataType: "uuid", originalIndex: 0 }),
264
- mkEntry("created_at", { propType: "date", originalIndex: 1 }),
265
- mkEntry("updated_at", { propType: "date", originalIndex: 2 }),
267
+ mkEntry("id", { isPk: true,
268
+ propType: "string",
269
+ pgDataType: "uuid",
270
+ originalIndex: 0 }),
271
+ mkEntry("created_at", { propType: "date",
272
+ originalIndex: 1 }),
273
+ mkEntry("updated_at", { propType: "date",
274
+ originalIndex: 2 }),
266
275
  mkEntry("email", { originalIndex: 3 }),
267
276
  mkEntry("first_name", { originalIndex: 4 }),
268
277
  mkEntry("last_name", { originalIndex: 5 }),
269
- mkEntry("avatar", { isStorage: true, originalIndex: 6 }),
270
- mkEntry("role", { isEnum: true, originalIndex: 7 }),
271
- mkEntry("active", { propType: "boolean", originalIndex: 8 }),
272
- mkEntry("bio", { pgDataType: "text", originalIndex: 9 }),
273
- mkEntry("metadata", { propType: "map", pgDataType: "jsonb", originalIndex: 10 }),
278
+ mkEntry("avatar", { isStorage: true,
279
+ originalIndex: 6 }),
280
+ mkEntry("role", { isEnum: true,
281
+ originalIndex: 7 }),
282
+ mkEntry("active", { propType: "boolean",
283
+ originalIndex: 8 }),
284
+ mkEntry("bio", { pgDataType: "text",
285
+ originalIndex: 9 }),
286
+ mkEntry("metadata", { propType: "map",
287
+ pgDataType: "jsonb",
288
+ originalIndex: 10 })
274
289
  ];
275
290
  const result = sortPropertiesOrder(entries);
276
291
  expect(result).toEqual([
277
- "id", // tier 0: PK
278
- "first_name", // tier 2: human identity
279
- "last_name", // tier 2: human identity
280
- "email", // tier 2: human identity
281
- "role", // tier 4: enum
282
- "active", // tier 4: boolean
283
- "bio", // tier 7: long text
284
- "avatar", // tier 9: storage
285
- "metadata", // tier 10: map
286
- "created_at", // tier 12: system timestamp
287
- "updated_at", // tier 12: system timestamp
292
+ "id", // tier 0: PK
293
+ "first_name", // tier 2: human identity
294
+ "last_name", // tier 2: human identity
295
+ "email", // tier 2: human identity
296
+ "role", // tier 4: enum
297
+ "active", // tier 4: boolean
298
+ "bio", // tier 7: long text
299
+ "avatar", // tier 9: storage
300
+ "metadata", // tier 10: map
301
+ "created_at", // tier 12: system timestamp
302
+ "updated_at" // tier 12: system timestamp
288
303
  ]);
289
304
  });
290
305
 
291
306
  it("sorts a realistic product table correctly", () => {
292
307
  const entries: PropertyOrderEntry[] = [
293
- mkEntry("id", { isPk: true, propType: "number", pgDataType: "integer", originalIndex: 0 }),
294
- mkEntry("created_at", { propType: "date", originalIndex: 1 }),
308
+ mkEntry("id", { isPk: true,
309
+ propType: "number",
310
+ pgDataType: "integer",
311
+ originalIndex: 0 }),
312
+ mkEntry("created_at", { propType: "date",
313
+ originalIndex: 1 }),
295
314
  mkEntry("sku", { originalIndex: 2 }),
296
315
  mkEntry("name", { originalIndex: 3 }),
297
- mkEntry("description", { pgDataType: "text", originalIndex: 4 }),
298
- mkEntry("price", { propType: "number", originalIndex: 5 }),
299
- mkEntry("category", { propType: "relation", originalIndex: 6 }),
300
- mkEntry("cover_image", { isStorage: true, originalIndex: 7 }),
301
- mkEntry("active", { propType: "boolean", originalIndex: 8 }),
316
+ mkEntry("description", { pgDataType: "text",
317
+ originalIndex: 4 }),
318
+ mkEntry("price", { propType: "number",
319
+ originalIndex: 5 }),
320
+ mkEntry("category", { propType: "relation",
321
+ originalIndex: 6 }),
322
+ mkEntry("cover_image", { isStorage: true,
323
+ originalIndex: 7 }),
324
+ mkEntry("active", { propType: "boolean",
325
+ originalIndex: 8 })
302
326
  ];
303
327
  const result = sortPropertiesOrder(entries);
304
328
  expect(result).toEqual([
305
- "id", // tier 0: PK
306
- "name", // tier 1: title/name
307
- "sku", // tier 3: descriptor
308
- "category", // tier 3: descriptor (name match overrides relation tier)
309
- "active", // tier 4: boolean
310
- "price", // tier 5: number
311
- "description", // tier 7: long text
312
- "cover_image", // tier 9: storage
313
- "created_at", // tier 12: system timestamp
329
+ "id", // tier 0: PK
330
+ "name", // tier 1: title/name
331
+ "sku", // tier 3: descriptor
332
+ "category", // tier 3: descriptor (name match overrides relation tier)
333
+ "active", // tier 4: boolean
334
+ "price", // tier 5: number
335
+ "description", // tier 7: long text
336
+ "cover_image", // tier 9: storage
337
+ "created_at" // tier 12: system timestamp
314
338
  ]);
315
339
  });
316
340
 
317
341
  it("sorts a blog post table correctly", () => {
318
342
  const entries: PropertyOrderEntry[] = [
319
- mkEntry("id", { isPk: true, propType: "string", pgDataType: "uuid", originalIndex: 0 }),
320
- mkEntry("updated_at", { propType: "date", originalIndex: 1 }),
321
- mkEntry("created_at", { propType: "date", originalIndex: 2 }),
322
- mkEntry("content", { pgDataType: "text", originalIndex: 3 }),
343
+ mkEntry("id", { isPk: true,
344
+ propType: "string",
345
+ pgDataType: "uuid",
346
+ originalIndex: 0 }),
347
+ mkEntry("updated_at", { propType: "date",
348
+ originalIndex: 1 }),
349
+ mkEntry("created_at", { propType: "date",
350
+ originalIndex: 2 }),
351
+ mkEntry("content", { pgDataType: "text",
352
+ originalIndex: 3 }),
323
353
  mkEntry("title", { originalIndex: 4 }),
324
354
  mkEntry("slug", { originalIndex: 5 }),
325
- mkEntry("status", { isEnum: true, originalIndex: 6 }),
326
- mkEntry("author", { propType: "relation", originalIndex: 7 }),
327
- mkEntry("published_at", { propType: "date", originalIndex: 8 }),
328
- mkEntry("cover_image", { isStorage: true, originalIndex: 9 }),
329
- mkEntry("excerpt", { pgDataType: "text", originalIndex: 10 }),
355
+ mkEntry("status", { isEnum: true,
356
+ originalIndex: 6 }),
357
+ mkEntry("author", { propType: "relation",
358
+ originalIndex: 7 }),
359
+ mkEntry("published_at", { propType: "date",
360
+ originalIndex: 8 }),
361
+ mkEntry("cover_image", { isStorage: true,
362
+ originalIndex: 9 }),
363
+ mkEntry("excerpt", { pgDataType: "text",
364
+ originalIndex: 10 })
330
365
  ];
331
366
  const result = sortPropertiesOrder(entries);
332
367
  expect(result).toEqual([
333
- "id", // tier 0: PK
334
- "title", // tier 1: title
335
- "slug", // tier 3: descriptor
336
- "status", // tier 4: enum
337
- "published_at", // tier 5: user-facing date
338
- "author", // tier 6: relation
339
- "excerpt", // tier 7: long text
340
- "content", // tier 8: rich content
341
- "cover_image", // tier 9: storage
342
- "created_at", // tier 12: system timestamp
343
- "updated_at", // tier 12: system timestamp
368
+ "id", // tier 0: PK
369
+ "title", // tier 1: title
370
+ "slug", // tier 3: descriptor
371
+ "status", // tier 4: enum
372
+ "published_at", // tier 5: user-facing date
373
+ "author", // tier 6: relation
374
+ "excerpt", // tier 7: long text
375
+ "content", // tier 8: rich content
376
+ "cover_image", // tier 9: storage
377
+ "created_at", // tier 12: system timestamp
378
+ "updated_at" // tier 12: system timestamp
344
379
  ]);
345
380
  });
346
381
 
347
382
  it("preserves original order for properties in the same tier", () => {
348
383
  const entries: PropertyOrderEntry[] = [
349
- mkEntry("color", { propType: "string", pgDataType: "character varying", originalIndex: 0 }),
350
- mkEntry("size", { propType: "string", pgDataType: "character varying", originalIndex: 1 }),
351
- mkEntry("weight", { propType: "string", pgDataType: "character varying", originalIndex: 2 }),
384
+ mkEntry("color", { propType: "string",
385
+ pgDataType: "character varying",
386
+ originalIndex: 0 }),
387
+ mkEntry("size", { propType: "string",
388
+ pgDataType: "character varying",
389
+ originalIndex: 1 }),
390
+ mkEntry("weight", { propType: "string",
391
+ pgDataType: "character varying",
392
+ originalIndex: 2 })
352
393
  ];
353
394
  const result = sortPropertiesOrder(entries);
354
395
  // All three are tier 4 (short strings), should preserve original order
@@ -357,9 +398,12 @@ describe("sortPropertiesOrder", () => {
357
398
 
358
399
  it("handles tables with only system columns", () => {
359
400
  const entries: PropertyOrderEntry[] = [
360
- mkEntry("id", { isPk: true, originalIndex: 0 }),
361
- mkEntry("created_at", { propType: "date", originalIndex: 1 }),
362
- mkEntry("updated_at", { propType: "date", originalIndex: 2 }),
401
+ mkEntry("id", { isPk: true,
402
+ originalIndex: 0 }),
403
+ mkEntry("created_at", { propType: "date",
404
+ originalIndex: 1 }),
405
+ mkEntry("updated_at", { propType: "date",
406
+ originalIndex: 2 })
363
407
  ];
364
408
  const result = sortPropertiesOrder(entries);
365
409
  expect(result).toEqual(["id", "created_at", "updated_at"]);
@@ -367,10 +411,12 @@ describe("sortPropertiesOrder", () => {
367
411
 
368
412
  it("handles partial name matches (product_name, page_title)", () => {
369
413
  const entries: PropertyOrderEntry[] = [
370
- mkEntry("id", { isPk: true, originalIndex: 0 }),
414
+ mkEntry("id", { isPk: true,
415
+ originalIndex: 0 }),
371
416
  mkEntry("product_name", { originalIndex: 1 }),
372
417
  mkEntry("category", { originalIndex: 2 }),
373
- mkEntry("price", { propType: "number", originalIndex: 3 }),
418
+ mkEntry("price", { propType: "number",
419
+ originalIndex: 3 })
374
420
  ];
375
421
  const result = sortPropertiesOrder(entries);
376
422
  // product_name should come after id but before category and price
@@ -382,8 +428,9 @@ describe("sortPropertiesOrder", () => {
382
428
  const entries: PropertyOrderEntry[] = [
383
429
  mkEntry("name", { originalIndex: 0 }),
384
430
  mkEntry("website_url", { originalIndex: 1 }),
385
- mkEntry("avatar", { isStorage: true, originalIndex: 2 }),
386
- mkEntry("thumbnail", { originalIndex: 3 }),
431
+ mkEntry("avatar", { isStorage: true,
432
+ originalIndex: 2 }),
433
+ mkEntry("thumbnail", { originalIndex: 3 })
387
434
  ];
388
435
  const result = sortPropertiesOrder(entries);
389
436
  expect(result[0]).toBe("name");
@@ -3,8 +3,12 @@ import { NodePgDatabase } from "drizzle-orm/node-postgres";
3
3
  import { PostgresCollectionRegistry } from "../src/collections/PostgresCollectionRegistry";
4
4
  import { EntityCollection } from "@rebasepro/types";
5
5
 
6
- const mockFetchCollection = jest.fn().mockResolvedValue([{ id: 1, path: "posts", values: { title: "Refetched Title" } }]);
7
- const mockFetchEntity = jest.fn().mockResolvedValue({ id: 1, path: "posts", values: { title: "Refetched Entity Title" } });
6
+ const mockFetchCollection = jest.fn().mockResolvedValue([{ id: 1,
7
+ path: "posts",
8
+ values: { title: "Refetched Title" } }]);
9
+ const mockFetchEntity = jest.fn().mockResolvedValue({ id: 1,
10
+ path: "posts",
11
+ values: { title: "Refetched Entity Title" } });
8
12
 
9
13
  jest.mock("../src/services/entityService", () => ({
10
14
  EntityService: jest.fn().mockImplementation(() => ({
@@ -25,26 +25,32 @@ import { NodePgDatabase } from "drizzle-orm/node-postgres";
25
25
 
26
26
  // ─── Mock Tables ──────────────────────────────────────────────────────
27
27
  const mockPostsTable = {
28
- id: { name: "id", dataType: "number" },
28
+ id: { name: "id",
29
+ dataType: "number" },
29
30
  title: { name: "title" },
30
- author_id: { name: "author_id", dataType: "number" },
31
+ author_id: { name: "author_id",
32
+ dataType: "number" },
31
33
  _def: { tableName: "posts" }
32
34
  };
33
35
 
34
36
  const mockTagsTable = {
35
- id: { name: "id", dataType: "number" },
37
+ id: { name: "id",
38
+ dataType: "number" },
36
39
  name: { name: "name" },
37
40
  _def: { tableName: "tags" }
38
41
  };
39
42
 
40
43
  const mockPostsTagsTable = {
41
- post_id: { name: "post_id", dataType: "number" },
42
- tag_id: { name: "tag_id", dataType: "number" },
44
+ post_id: { name: "post_id",
45
+ dataType: "number" },
46
+ tag_id: { name: "tag_id",
47
+ dataType: "number" },
43
48
  _def: { tableName: "posts_tags" }
44
49
  };
45
50
 
46
51
  const mockAuthorsTable = {
47
- id: { name: "id", dataType: "number" },
52
+ id: { name: "id",
53
+ dataType: "number" },
48
54
  name: { name: "name" },
49
55
  _def: { tableName: "authors" }
50
56
  };
@@ -69,7 +75,8 @@ const postsCollection: EntityCollection = {
69
75
  properties: {
70
76
  id: { type: "number" },
71
77
  title: { type: "string" },
72
- tags: { type: "relation", relationName: "tags" }
78
+ tags: { type: "relation",
79
+ relationName: "tags" }
73
80
  },
74
81
  relations: [
75
82
  {
@@ -94,7 +101,8 @@ const authorsCollection: EntityCollection = {
94
101
  properties: {
95
102
  id: { type: "number" },
96
103
  name: { type: "string" },
97
- posts: { type: "relation", relationName: "posts" }
104
+ posts: { type: "relation",
105
+ relationName: "posts" }
98
106
  },
99
107
  relations: [
100
108
  {
@@ -116,7 +124,8 @@ const tagsWithInversePosts: EntityCollection = {
116
124
  properties: {
117
125
  id: { type: "number" },
118
126
  name: { type: "string" },
119
- posts: { type: "relation", relationName: "posts" }
127
+ posts: { type: "relation",
128
+ relationName: "posts" }
120
129
  },
121
130
  relations: [
122
131
  {
@@ -142,7 +151,7 @@ function createMockDb(resolveResults: () => unknown[]) {
142
151
  innerJoinCount: 0,
143
152
  fromTable: undefined as string | undefined,
144
153
  deleteCalls: [] as unknown[],
145
- insertCalls: [] as unknown[],
154
+ insertCalls: [] as unknown[]
146
155
  };
147
156
 
148
157
  function makeChainable(): Record<string, unknown> {
@@ -184,7 +193,7 @@ function createMockDb(resolveResults: () => unknown[]) {
184
193
 
185
194
  return {
186
195
  db: makeChainable() as unknown as jest.Mocked<NodePgDatabase>,
187
- recorder,
196
+ recorder
188
197
  };
189
198
  }
190
199
 
@@ -226,8 +235,12 @@ describe("batchFetchRelatedEntities: ID type coercion (single cardinality)", ()
226
235
  it("should match results when Drizzle returns string IDs but parsedParentIds are numbers (FK inverse)", async () => {
227
236
  // Simulate Drizzle returning author_id as a string even though it's a numeric column
228
237
  const resultRows = [
229
- { id: 10, title: "Post A", author_id: "1" },
230
- { id: 20, title: "Post B", author_id: "2" },
238
+ { id: 10,
239
+ title: "Post A",
240
+ author_id: "1" },
241
+ { id: 20,
242
+ title: "Post B",
243
+ author_id: "2" }
231
244
  ];
232
245
 
233
246
  const { db } = createMockDb(() => resultRows);
@@ -250,7 +263,9 @@ describe("batchFetchRelatedEntities: ID type coercion (single cardinality)", ()
250
263
  it("should match results when Drizzle returns number IDs but parsedParentIds contain strings", async () => {
251
264
  // Simulate the reverse: Drizzle returns numbers, but parsed IDs might be strings
252
265
  const resultRows = [
253
- { id: 10, title: "Post A", author_id: 1 },
266
+ { id: 10,
267
+ title: "Post A",
268
+ author_id: 1 }
254
269
  ];
255
270
 
256
271
  const { db } = createMockDb(() => resultRows);
@@ -267,8 +282,12 @@ describe("batchFetchRelatedEntities: ID type coercion (single cardinality)", ()
267
282
 
268
283
  it("should handle mixed string and number IDs across result rows", async () => {
269
284
  const resultRows = [
270
- { id: 10, title: "Post A", author_id: 1 }, // number
271
- { id: 20, title: "Post B", author_id: "2" }, // string
285
+ { id: 10,
286
+ title: "Post A",
287
+ author_id: 1 }, // number
288
+ { id: 20,
289
+ title: "Post B",
290
+ author_id: "2" } // string
272
291
  ];
273
292
 
274
293
  const { db } = createMockDb(() => resultRows);
@@ -285,7 +304,9 @@ describe("batchFetchRelatedEntities: ID type coercion (single cardinality)", ()
285
304
 
286
305
  it("should not match results for IDs not in the parent set", async () => {
287
306
  const resultRows = [
288
- { id: 10, title: "Post A", author_id: "999" },
307
+ { id: 10,
308
+ title: "Post A",
309
+ author_id: "999" }
289
310
  ];
290
311
 
291
312
  const { db } = createMockDb(() => resultRows);
@@ -340,7 +361,9 @@ describe("batchFetchRelatedEntities: ID type coercion (single cardinality)", ()
340
361
 
341
362
  // Drizzle returns author_id as string
342
363
  const resultRows = [
343
- { id: 10, title: "Post A", author_id: "1" },
364
+ { id: 10,
365
+ title: "Post A",
366
+ author_id: "1" }
344
367
  ];
345
368
 
346
369
  const { db } = createMockDb(() => resultRows);
@@ -624,8 +647,12 @@ describe("sanitizeRelation: auto-inferred junction table naming", () => {
624
647
  cardinality: "many",
625
648
  direction: "owning",
626
649
  joinPath: [
627
- { table: "user_roles", on: { from: "id", to: "user_id" } },
628
- { table: "permissions", on: { from: "permission_id", to: "id" } }
650
+ { table: "user_roles",
651
+ on: { from: "id",
652
+ to: "user_id" } },
653
+ { table: "permissions",
654
+ on: { from: "permission_id",
655
+ to: "id" } }
629
656
  ]
630
657
  };
631
658
 
@@ -645,15 +672,18 @@ describe("batchFetchRelatedEntities: owning direction (FK-based)", () => {
645
672
 
646
673
  // Mock collections simulating tasks → clients owning relation
647
674
  const mockClientsTable = {
648
- id: { name: "id", dataType: "string" },
675
+ id: { name: "id",
676
+ dataType: "string" },
649
677
  name: { name: "name" },
650
678
  email: { name: "email" },
651
679
  _def: { tableName: "clients" }
652
680
  };
653
681
 
654
682
  const mockTasksTable = {
655
- id: { name: "id", dataType: "string" },
656
- clientId: { name: "client_id", dataType: "string" },
683
+ id: { name: "id",
684
+ dataType: "string" },
685
+ clientId: { name: "client_id",
686
+ dataType: "string" },
657
687
  title: { name: "title" },
658
688
  _def: { tableName: "tasks" }
659
689
  };
@@ -663,7 +693,8 @@ describe("batchFetchRelatedEntities: owning direction (FK-based)", () => {
663
693
  name: "Clients",
664
694
  table: "clients",
665
695
  properties: {
666
- id: { type: "string", isId: "uuid" },
696
+ id: { type: "string",
697
+ isId: "uuid" },
667
698
  name: { type: "string" },
668
699
  email: { type: "string" }
669
700
  }
@@ -674,8 +705,10 @@ describe("batchFetchRelatedEntities: owning direction (FK-based)", () => {
674
705
  name: "Tasks",
675
706
  table: "tasks",
676
707
  properties: {
677
- id: { type: "string", isId: "uuid" },
678
- clientId: { type: "string", columnName: "client_id" },
708
+ id: { type: "string",
709
+ isId: "uuid" },
710
+ clientId: { type: "string",
711
+ columnName: "client_id" },
679
712
  title: { type: "string" },
680
713
  client: {
681
714
  type: "relation",
@@ -748,9 +781,12 @@ describe("batchFetchRelatedEntities: owning direction (FK-based)", () => {
748
781
 
749
782
  const db = createSequencedMockDb([
750
783
  // Query 1: FK lookup from tasks table
751
- () => [{ parentId: taskUuid, fkValue: clientUuid }],
784
+ () => [{ parentId: taskUuid,
785
+ fkValue: clientUuid }],
752
786
  // Query 2: Target entity from clients table
753
- () => [{ id: clientUuid, name: "Francesco", email: "f@test.com" }]
787
+ () => [{ id: clientUuid,
788
+ name: "Francesco",
789
+ email: "f@test.com" }]
754
790
  ]);
755
791
 
756
792
  const service = new RelationService(db, registry);
@@ -779,11 +815,15 @@ describe("batchFetchRelatedEntities: owning direction (FK-based)", () => {
779
815
  const db = createSequencedMockDb([
780
816
  // Both tasks have the same clientId
781
817
  () => [
782
- { parentId: task1, fkValue: clientUuid },
783
- { parentId: task2, fkValue: clientUuid }
818
+ { parentId: task1,
819
+ fkValue: clientUuid },
820
+ { parentId: task2,
821
+ fkValue: clientUuid }
784
822
  ],
785
823
  // Only one client row
786
- () => [{ id: clientUuid, name: "Francesco", email: "f@test.com" }]
824
+ () => [{ id: clientUuid,
825
+ name: "Francesco",
826
+ email: "f@test.com" }]
787
827
  ]);
788
828
 
789
829
  const service = new RelationService(db, registry);
@@ -803,7 +843,8 @@ describe("batchFetchRelatedEntities: owning direction (FK-based)", () => {
803
843
 
804
844
  const db = createSequencedMockDb([
805
845
  // FK is null
806
- () => [{ parentId: task1, fkValue: null }],
846
+ () => [{ parentId: task1,
847
+ fkValue: null }]
807
848
  ]);
808
849
 
809
850
  const service = new RelationService(db, registry);
@@ -897,7 +938,9 @@ describe("Relation data JSON round-trip", () => {
897
938
  });
898
939
 
899
940
  it("should handle entity with no relation data (stub)", () => {
900
- const stubRef = { id: "client-uuid", path: "clients", __type: "relation" as const };
941
+ const stubRef = { id: "client-uuid",
942
+ path: "clients",
943
+ __type: "relation" as const };
901
944
 
902
945
  const json = JSON.stringify({ values: { client: stubRef } });
903
946
  const parsed = JSON.parse(json, rebaseReviver);
@@ -914,7 +957,8 @@ describe("Relation data JSON round-trip", () => {
914
957
  const clientEntity = {
915
958
  id: "client-uuid",
916
959
  path: "clients",
917
- values: { name: "Acme Corp", email: "acme@corp.com" }
960
+ values: { name: "Acme Corp",
961
+ email: "acme@corp.com" }
918
962
  };
919
963
 
920
964
  const ref = createRelationRefWithData(clientEntity.id, clientEntity.path, clientEntity as any);
@@ -927,12 +971,16 @@ describe("Relation data JSON round-trip", () => {
927
971
  {
928
972
  id: "task-1",
929
973
  path: "tasks",
930
- values: { title: "Task A", client: ref, status: "pending" }
974
+ values: { title: "Task A",
975
+ client: ref,
976
+ status: "pending" }
931
977
  },
932
978
  {
933
979
  id: "task-2",
934
980
  path: "tasks",
935
- values: { title: "Task B", client: ref, status: "completed" }
981
+ values: { title: "Task B",
982
+ client: ref,
983
+ status: "completed" }
936
984
  }
937
985
  ]
938
986
  };