@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
@@ -29,32 +29,39 @@ import { NodePgDatabase } from "drizzle-orm/node-postgres";
29
29
 
30
30
  // ─── Mock Tables ──────────────────────────────────────────────────────
31
31
  const mockPostsTable = {
32
- id: { name: "id", dataType: "number" },
32
+ id: { name: "id",
33
+ dataType: "number" },
33
34
  title: { name: "title" },
34
35
  _def: { tableName: "posts" }
35
36
  };
36
37
 
37
38
  const mockTagsTable = {
38
- id: { name: "id", dataType: "number" },
39
+ id: { name: "id",
40
+ dataType: "number" },
39
41
  name: { name: "name" },
40
42
  _def: { tableName: "tags" }
41
43
  };
42
44
 
43
45
  const mockPostsTagsTable = {
44
- post_id: { name: "post_id", dataType: "number" },
45
- tag_id: { name: "tag_id", dataType: "number" },
46
+ post_id: { name: "post_id",
47
+ dataType: "number" },
48
+ tag_id: { name: "tag_id",
49
+ dataType: "number" },
46
50
  _def: { tableName: "posts_tags" }
47
51
  };
48
52
 
49
53
  const mockAuthorsTable = {
50
- id: { name: "id", dataType: "number" },
54
+ id: { name: "id",
55
+ dataType: "number" },
51
56
  name: { name: "name" },
52
57
  _def: { tableName: "authors" }
53
58
  };
54
59
 
55
60
  const mockAuthorPostsTable = {
56
- author_id: { name: "author_id", dataType: "number" },
57
- post_id: { name: "post_id", dataType: "number" },
61
+ author_id: { name: "author_id",
62
+ dataType: "number" },
63
+ post_id: { name: "post_id",
64
+ dataType: "number" },
58
65
  _def: { tableName: "author_posts" }
59
66
  };
60
67
 
@@ -78,7 +85,8 @@ const postsCollection: EntityCollection = {
78
85
  properties: {
79
86
  id: { type: "number" },
80
87
  title: { type: "string" },
81
- tags: { type: "relation", relationName: "tags" }
88
+ tags: { type: "relation",
89
+ relationName: "tags" }
82
90
  },
83
91
  relations: [
84
92
  {
@@ -115,7 +123,8 @@ const tagsWithInversePosts: EntityCollection = {
115
123
  properties: {
116
124
  id: { type: "number" },
117
125
  name: { type: "string" },
118
- posts: { type: "relation", relationName: "posts" }
126
+ posts: { type: "relation",
127
+ relationName: "posts" }
119
128
  },
120
129
  relations: [
121
130
  {
@@ -141,7 +150,8 @@ const authorsWithJoinPath: EntityCollection = {
141
150
  properties: {
142
151
  id: { type: "number" },
143
152
  name: { type: "string" },
144
- posts: { type: "relation", relationName: "posts" }
153
+ posts: { type: "relation",
154
+ relationName: "posts" }
145
155
  },
146
156
  relations: [
147
157
  {
@@ -150,8 +160,12 @@ const authorsWithJoinPath: EntityCollection = {
150
160
  cardinality: "many",
151
161
  direction: "owning",
152
162
  joinPath: [
153
- { table: "author_posts", on: { from: "id", to: "author_id" } },
154
- { table: "posts", on: { from: "post_id", to: "id" } }
163
+ { table: "author_posts",
164
+ on: { from: "id",
165
+ to: "author_id" } },
166
+ { table: "posts",
167
+ on: { from: "post_id",
168
+ to: "id" } }
155
169
  ]
156
170
  }
157
171
  ],
@@ -179,7 +193,7 @@ function generateJunctionRows(
179
193
  rows.push({
180
194
  [junctionTableName]: {
181
195
  [sourceCol]: postId,
182
- [targetCol]: tagId,
196
+ [targetCol]: tagId
183
197
  },
184
198
  [targetTableName]: { ...targetRow }
185
199
  });
@@ -198,7 +212,7 @@ describe("batchFetchRelatedEntitiesMany: M2M through junction table regression",
198
212
  let queryRecorder = {
199
213
  selectCount: 0,
200
214
  innerJoinCount: 0,
201
- fromTable: undefined as string | undefined,
215
+ fromTable: undefined as string | undefined
202
216
  };
203
217
 
204
218
  function makeChainable(): Record<string, unknown> {
@@ -230,7 +244,7 @@ describe("batchFetchRelatedEntitiesMany: M2M through junction table regression",
230
244
 
231
245
  return {
232
246
  db: makeChainable() as unknown as jest.Mocked<NodePgDatabase>,
233
- recorder: queryRecorder,
247
+ recorder: queryRecorder
234
248
  };
235
249
  }
236
250
 
@@ -268,19 +282,37 @@ describe("batchFetchRelatedEntitiesMany: M2M through junction table regression",
268
282
 
269
283
  describe("Owning M2M with `through` (posts → tags)", () => {
270
284
  const mockTags = [
271
- { id: 1, name: "TypeScript" },
272
- { id: 2, name: "React" },
273
- { id: 3, name: "Node.js" },
285
+ { id: 1,
286
+ name: "TypeScript" },
287
+ { id: 2,
288
+ name: "React" },
289
+ { id: 3,
290
+ name: "Node.js" }
274
291
  ];
275
292
 
276
293
  it("should return tags for each post via junction table", async () => {
277
294
  // Post 1 → tags 1,2; Post 2 → tags 2,3; Post 3 → tag 1
278
295
  const junctionRows = [
279
- { posts_tags: { post_id: 1, tag_id: 1 }, tags: { id: 1, name: "TypeScript" } },
280
- { posts_tags: { post_id: 1, tag_id: 2 }, tags: { id: 2, name: "React" } },
281
- { posts_tags: { post_id: 2, tag_id: 2 }, tags: { id: 2, name: "React" } },
282
- { posts_tags: { post_id: 2, tag_id: 3 }, tags: { id: 3, name: "Node.js" } },
283
- { posts_tags: { post_id: 3, tag_id: 1 }, tags: { id: 1, name: "TypeScript" } },
296
+ { posts_tags: { post_id: 1,
297
+ tag_id: 1 },
298
+ tags: { id: 1,
299
+ name: "TypeScript" } },
300
+ { posts_tags: { post_id: 1,
301
+ tag_id: 2 },
302
+ tags: { id: 2,
303
+ name: "React" } },
304
+ { posts_tags: { post_id: 2,
305
+ tag_id: 2 },
306
+ tags: { id: 2,
307
+ name: "React" } },
308
+ { posts_tags: { post_id: 2,
309
+ tag_id: 3 },
310
+ tags: { id: 3,
311
+ name: "Node.js" } },
312
+ { posts_tags: { post_id: 3,
313
+ tag_id: 1 },
314
+ tags: { id: 1,
315
+ name: "TypeScript" } }
284
316
  ];
285
317
 
286
318
  const { db } = createMockDb(() => junctionRows);
@@ -310,7 +342,10 @@ describe("batchFetchRelatedEntitiesMany: M2M through junction table regression",
310
342
 
311
343
  it("should set correct entity id and path on each relation result", async () => {
312
344
  const junctionRows = [
313
- { posts_tags: { post_id: 1, tag_id: 42 }, tags: { id: 42, name: "GraphQL" } },
345
+ { posts_tags: { post_id: 1,
346
+ tag_id: 42 },
347
+ tags: { id: 42,
348
+ name: "GraphQL" } }
314
349
  ];
315
350
 
316
351
  const { db } = createMockDb(() => junctionRows);
@@ -329,7 +364,10 @@ describe("batchFetchRelatedEntitiesMany: M2M through junction table regression",
329
364
 
330
365
  it("should use exactly 1 SQL query (junction JOIN target)", async () => {
331
366
  const junctionRows = [
332
- { posts_tags: { post_id: 1, tag_id: 1 }, tags: { id: 1, name: "TypeScript" } },
367
+ { posts_tags: { post_id: 1,
368
+ tag_id: 1 },
369
+ tags: { id: 1,
370
+ name: "TypeScript" } }
333
371
  ];
334
372
 
335
373
  const { db, recorder } = createMockDb(() => junctionRows);
@@ -373,9 +411,15 @@ describe("batchFetchRelatedEntitiesMany: M2M through junction table regression",
373
411
 
374
412
  it("should handle posts where some have tags and some don't", async () => {
375
413
  const junctionRows = [
376
- { posts_tags: { post_id: 1, tag_id: 1 }, tags: { id: 1, name: "TypeScript" } },
414
+ { posts_tags: { post_id: 1,
415
+ tag_id: 1 },
416
+ tags: { id: 1,
417
+ name: "TypeScript" } },
377
418
  // Post 2 has no junction rows
378
- { posts_tags: { post_id: 3, tag_id: 2 }, tags: { id: 2, name: "React" } },
419
+ { posts_tags: { post_id: 3,
420
+ tag_id: 2 },
421
+ tags: { id: 2,
422
+ name: "React" } }
379
423
  ];
380
424
 
381
425
  const { db } = createMockDb(() => junctionRows);
@@ -400,9 +444,18 @@ describe("batchFetchRelatedEntitiesMany: M2M through junction table regression",
400
444
  // Result rows contain junction + target data.
401
445
  // The parentId (tag) is in the junction's targetColumn.
402
446
  const joinedRows = [
403
- { posts_tags: { post_id: 10, tag_id: 1 }, posts: { id: 10, title: "Post A" } },
404
- { posts_tags: { post_id: 20, tag_id: 1 }, posts: { id: 20, title: "Post B" } },
405
- { posts_tags: { post_id: 30, tag_id: 2 }, posts: { id: 30, title: "Post C" } },
447
+ { posts_tags: { post_id: 10,
448
+ tag_id: 1 },
449
+ posts: { id: 10,
450
+ title: "Post A" } },
451
+ { posts_tags: { post_id: 20,
452
+ tag_id: 1 },
453
+ posts: { id: 20,
454
+ title: "Post B" } },
455
+ { posts_tags: { post_id: 30,
456
+ tag_id: 2 },
457
+ posts: { id: 30,
458
+ title: "Post C" } }
406
459
  ];
407
460
 
408
461
  const { db } = createMockDb(() => joinedRows);
@@ -443,9 +496,18 @@ describe("batchFetchRelatedEntitiesMany: M2M through junction table regression",
443
496
  it("should return posts for each author via joinPath", async () => {
444
497
  // joinPath results are namespaced differently — parent data under parent table name
445
498
  const joinedRows = [
446
- { authors: { id: 1, name: "Alice" }, posts: { id: 10, title: "Post X" } },
447
- { authors: { id: 1, name: "Alice" }, posts: { id: 20, title: "Post Y" } },
448
- { authors: { id: 2, name: "Bob" }, posts: { id: 30, title: "Post Z" } },
499
+ { authors: { id: 1,
500
+ name: "Alice" },
501
+ posts: { id: 10,
502
+ title: "Post X" } },
503
+ { authors: { id: 1,
504
+ name: "Alice" },
505
+ posts: { id: 20,
506
+ title: "Post Y" } },
507
+ { authors: { id: 2,
508
+ name: "Bob" },
509
+ posts: { id: 30,
510
+ title: "Post Z" } }
449
511
  ];
450
512
 
451
513
  const { db } = createMockDb(() => joinedRows);
@@ -508,8 +570,14 @@ describe("batchFetchRelatedEntitiesMany: M2M through junction table regression",
508
570
  it("owning M2M: should map results when junction returns string IDs but parent IDs are numbers", async () => {
509
571
  // Junction data returns post_id as STRING, but we passed numeric parent IDs
510
572
  const junctionRows = [
511
- { posts_tags: { post_id: "1", tag_id: "5" }, tags: { id: 5, name: "Rust" } },
512
- { posts_tags: { post_id: "2", tag_id: "5" }, tags: { id: 5, name: "Rust" } },
573
+ { posts_tags: { post_id: "1",
574
+ tag_id: "5" },
575
+ tags: { id: 5,
576
+ name: "Rust" } },
577
+ { posts_tags: { post_id: "2",
578
+ tag_id: "5" },
579
+ tags: { id: 5,
580
+ name: "Rust" } }
513
581
  ];
514
582
 
515
583
  const { db } = createMockDb(() => junctionRows);
@@ -530,8 +598,14 @@ describe("batchFetchRelatedEntitiesMany: M2M through junction table regression",
530
598
  it("inverse M2M: should map results when junction returns string IDs but parent IDs are numbers", async () => {
531
599
  // Junction data returns tag_id (the parentId for inverse) as STRING
532
600
  const joinedRows = [
533
- { posts_tags: { post_id: 10, tag_id: "1" }, posts: { id: 10, title: "Post A" } },
534
- { posts_tags: { post_id: 20, tag_id: "2" }, posts: { id: 20, title: "Post B" } },
601
+ { posts_tags: { post_id: 10,
602
+ tag_id: "1" },
603
+ posts: { id: 10,
604
+ title: "Post A" } },
605
+ { posts_tags: { post_id: 20,
606
+ tag_id: "2" },
607
+ posts: { id: 20,
608
+ title: "Post B" } }
535
609
  ];
536
610
 
537
611
  const { db } = createMockDb(() => joinedRows);
@@ -553,8 +627,14 @@ describe("batchFetchRelatedEntitiesMany: M2M through junction table regression",
553
627
  it("owning M2M: should handle mixed string and number IDs in same batch", async () => {
554
628
  // Some junction rows return numbers, others return strings
555
629
  const junctionRows = [
556
- { posts_tags: { post_id: 1, tag_id: 1 }, tags: { id: 1, name: "TypeScript" } },
557
- { posts_tags: { post_id: "2", tag_id: "2" }, tags: { id: 2, name: "React" } },
630
+ { posts_tags: { post_id: 1,
631
+ tag_id: 1 },
632
+ tags: { id: 1,
633
+ name: "TypeScript" } },
634
+ { posts_tags: { post_id: "2",
635
+ tag_id: "2" },
636
+ tags: { id: 2,
637
+ name: "React" } }
558
638
  ];
559
639
 
560
640
  const { db } = createMockDb(() => junctionRows);
@@ -30,13 +30,16 @@ function makeCollection(
30
30
  // ─────────────────────────────────────────────────────────────
31
31
  describe("serializeDataToServer typed return", () => {
32
32
  const properties: Properties = {
33
- title: { type: "string", name: "Title" } as Property,
34
- count: { type: "number", name: "Count" } as Property
33
+ title: { type: "string",
34
+ name: "Title" } as Property,
35
+ count: { type: "number",
36
+ name: "Count" } as Property
35
37
  };
36
38
 
37
39
  it("returns a SerializedEntityData object with scalarData, not raw values", () => {
38
40
  const result = serializeDataToServer(
39
- { title: "Hello", count: 5 },
41
+ { title: "Hello",
42
+ count: 5 },
40
43
  properties
41
44
  );
42
45
  expect(result).toHaveProperty("scalarData");
@@ -65,7 +68,8 @@ describe("serializeDataToServer typed return", () => {
65
68
 
66
69
  it("passes scalar values through correctly", () => {
67
70
  const result = serializeDataToServer(
68
- { title: "Hello World", count: 42 },
71
+ { title: "Hello World",
72
+ count: 42 },
69
73
  properties
70
74
  );
71
75
  expect(result.scalarData.title).toBe("Hello World");
@@ -86,7 +90,8 @@ describe("serializeDataToServer typed return", () => {
86
90
  // ─────────────────────────────────────────────────────────────
87
91
  describe("parsePropertyFromServer relation factory", () => {
88
92
  const targetCollection = makeCollection("authors", {
89
- name: { type: "string", name: "Name" } as Property
93
+ name: { type: "string",
94
+ name: "Name" } as Property
90
95
  });
91
96
 
92
97
  const collection = makeCollection("posts", {
@@ -125,14 +130,18 @@ describe("parsePropertyFromServer relation factory", () => {
125
130
  // ─────────────────────────────────────────────────────────────
126
131
  describe("normalizeDbValues", () => {
127
132
  const collection = makeCollection("items", {
128
- title: { type: "string", name: "Title" } as Property,
129
- price: { type: "number", name: "Price" } as Property,
130
- created_at: { type: "date", name: "Created" } as Property
133
+ title: { type: "string",
134
+ name: "Title" } as Property,
135
+ price: { type: "number",
136
+ name: "Price" } as Property,
137
+ created_at: { type: "date",
138
+ name: "Created" } as Property
131
139
  });
132
140
 
133
141
  it("coerces string numbers to actual numbers", () => {
134
142
  const result = normalizeDbValues(
135
- { title: "Widget", price: "19.99" } as any,
143
+ { title: "Widget",
144
+ price: "19.99" } as any,
136
145
  collection
137
146
  );
138
147
  expect(result.price).toBe(19.99);
@@ -141,7 +150,8 @@ describe("normalizeDbValues", () => {
141
150
  it("converts Date objects to { __type: 'date', value: ISO } format", () => {
142
151
  const date = new Date("2024-01-15T10:30:00Z");
143
152
  const result = normalizeDbValues(
144
- { title: "Widget", created_at: date } as any,
153
+ { title: "Widget",
154
+ created_at: date } as any,
145
155
  collection
146
156
  );
147
157
  expect(result.created_at).toEqual({
@@ -152,7 +162,8 @@ describe("normalizeDbValues", () => {
152
162
 
153
163
  it("strips unknown database columns not in properties", () => {
154
164
  const result = normalizeDbValues(
155
- { title: "Widget", internal_counter: 999 } as any,
165
+ { title: "Widget",
166
+ internal_counter: 999 } as any,
156
167
  collection
157
168
  );
158
169
  expect(result).not.toHaveProperty("internal_counter");
@@ -184,11 +195,13 @@ describe("normalizeDbValues", () => {
184
195
  relationName: "customer"
185
196
  }
186
197
  } as unknown as Property,
187
- total: { type: "number", name: "Total" } as Property
198
+ total: { type: "number",
199
+ name: "Total" } as Property
188
200
  });
189
201
 
190
202
  const result = normalizeDbValues(
191
- { customer: "some-id", total: 42 } as any,
203
+ { customer: "some-id",
204
+ total: 42 } as any,
192
205
  collectionWithRelation
193
206
  );
194
207
  // Relation properties should be skipped
@@ -257,9 +270,9 @@ describe("getColumnMeta type guard", () => {
257
270
 
258
271
  it("returns undefined for wrong-typed properties instead of passing them through", () => {
259
272
  const badCol = {
260
- columnType: 42, // should be string
261
- dataType: true, // should be string
262
- primary: "yes" // should be boolean
273
+ columnType: 42, // should be string
274
+ dataType: true, // should be string
275
+ primary: "yes" // should be boolean
263
276
  };
264
277
  const meta = getColumnMeta(badCol as any);
265
278
  expect(meta.columnType).toBeUndefined();
@@ -274,7 +287,8 @@ describe("getColumnMeta type guard", () => {
274
287
  describe("normalizeDbValues FK column preservation", () => {
275
288
  it("preserves internal FK columns as primitives when not defined as properties", () => {
276
289
  const targetCollection = makeCollection("categories", {
277
- name: { type: "string", name: "Name" } as Property
290
+ name: { type: "string",
291
+ name: "Name" } as Property
278
292
  });
279
293
 
280
294
  const categoryRelation = {
@@ -286,7 +300,8 @@ describe("normalizeDbValues FK column preservation", () => {
286
300
  };
287
301
 
288
302
  const collection = makeCollection("products", {
289
- title: { type: "string", name: "Title" } as Property,
303
+ title: { type: "string",
304
+ name: "Title" } as Property,
290
305
  category: {
291
306
  type: "relation",
292
307
  name: "Category",
@@ -295,7 +310,9 @@ describe("normalizeDbValues FK column preservation", () => {
295
310
  }, [categoryRelation] as any);
296
311
 
297
312
  const result = normalizeDbValues(
298
- { title: "Widget", category_id: "cat-123", category: "ignored" } as any,
313
+ { title: "Widget",
314
+ category_id: "cat-123",
315
+ category: "ignored" } as any,
299
316
  collection
300
317
  );
301
318
 
@@ -309,7 +326,8 @@ describe("normalizeDbValues FK column preservation", () => {
309
326
 
310
327
  it("preserves numeric FK columns as numbers", () => {
311
328
  const targetCollection = makeCollection("authors", {
312
- name: { type: "string", name: "Name" } as Property
329
+ name: { type: "string",
330
+ name: "Name" } as Property
313
331
  });
314
332
 
315
333
  const authorRelation = {
@@ -321,7 +339,8 @@ describe("normalizeDbValues FK column preservation", () => {
321
339
  };
322
340
 
323
341
  const collection = makeCollection("books", {
324
- title: { type: "string", name: "Title" } as Property,
342
+ title: { type: "string",
343
+ name: "Title" } as Property,
325
344
  author: {
326
345
  type: "relation",
327
346
  name: "Author",
@@ -330,7 +349,8 @@ describe("normalizeDbValues FK column preservation", () => {
330
349
  }, [authorRelation] as any);
331
350
 
332
351
  const result = normalizeDbValues(
333
- { title: "Book", author_id: 42 } as any,
352
+ { title: "Book",
353
+ author_id: 42 } as any,
334
354
  collection
335
355
  );
336
356
  expect(result.author_id).toBe(42);
@@ -338,7 +358,8 @@ describe("normalizeDbValues FK column preservation", () => {
338
358
 
339
359
  it("converts null FK columns to null", () => {
340
360
  const targetCollection = makeCollection("authors", {
341
- name: { type: "string", name: "Name" } as Property
361
+ name: { type: "string",
362
+ name: "Name" } as Property
342
363
  });
343
364
 
344
365
  const authorRelation = {
@@ -350,7 +371,8 @@ describe("normalizeDbValues FK column preservation", () => {
350
371
  };
351
372
 
352
373
  const collection = makeCollection("books", {
353
- title: { type: "string", name: "Title" } as Property,
374
+ title: { type: "string",
375
+ name: "Title" } as Property,
354
376
  author: {
355
377
  type: "relation",
356
378
  name: "Author",
@@ -359,7 +381,8 @@ describe("normalizeDbValues FK column preservation", () => {
359
381
  }, [authorRelation] as any);
360
382
 
361
383
  const result = normalizeDbValues(
362
- { title: "Book", author_id: null } as any,
384
+ { title: "Book",
385
+ author_id: null } as any,
363
386
  collection
364
387
  );
365
388
  expect(result.author_id).toBeNull();
@@ -372,13 +395,18 @@ describe("normalizeDbValues FK column preservation", () => {
372
395
  describe("structural dunder guard", () => {
373
396
  it("scalarData never contains any __ prefixed keys", () => {
374
397
  const properties: Properties = {
375
- title: { type: "string", name: "Title" } as Property,
376
- count: { type: "number", name: "Count" } as Property,
377
- active: { type: "boolean", name: "Active" } as Property
398
+ title: { type: "string",
399
+ name: "Title" } as Property,
400
+ count: { type: "number",
401
+ name: "Count" } as Property,
402
+ active: { type: "boolean",
403
+ name: "Active" } as Property
378
404
  };
379
405
 
380
406
  const result = serializeDataToServer(
381
- { title: "Test", count: 10, active: true },
407
+ { title: "Test",
408
+ count: 10,
409
+ active: true },
382
410
  properties
383
411
  );
384
412
 
@@ -388,11 +416,13 @@ describe("structural dunder guard", () => {
388
416
 
389
417
  it("scalarData never contains any __ prefixed keys even with relation properties", () => {
390
418
  const targetCollection = makeCollection("tags", {
391
- label: { type: "string", name: "Label" } as Property
419
+ label: { type: "string",
420
+ name: "Label" } as Property
392
421
  });
393
422
 
394
423
  const properties: Properties = {
395
- title: { type: "string", name: "Title" } as Property,
424
+ title: { type: "string",
425
+ name: "Title" } as Property,
396
426
  tag: {
397
427
  type: "relation",
398
428
  name: "Tag",
@@ -407,7 +437,10 @@ describe("structural dunder guard", () => {
407
437
  };
408
438
 
409
439
  const result = serializeDataToServer(
410
- { title: "Test", tag: { id: "t1", path: "tags", __type: "relation" } },
440
+ { title: "Test",
441
+ tag: { id: "t1",
442
+ path: "tags",
443
+ __type: "relation" } },
411
444
  properties
412
445
  );
413
446
 
@@ -467,7 +500,8 @@ describe("parsePropertyFromServer binary parsing", () => {
467
500
 
468
501
  it("parses Buffer object (JSON serialization) into a base64 data URL string", () => {
469
502
  const property = targetCollection.properties.data as Property;
470
- const bufferObj = { type: "Buffer", data: Array.from(Buffer.from("hello")) };
503
+ const bufferObj = { type: "Buffer",
504
+ data: Array.from(Buffer.from("hello")) };
471
505
  const result = parsePropertyFromServer(bufferObj, property, targetCollection, "data");
472
506
  expect(result).toBe("data:application/octet-stream;base64,aGVsbG8=");
473
507
  });
@@ -175,10 +175,12 @@ path: "authors" }
175
175
 
176
176
  // ── Vector property ──
177
177
  describe("vector property", () => {
178
- const vectorProp: Property = { type: "vector", dimensions: 3 } as Property;
178
+ const vectorProp: Property = { type: "vector",
179
+ dimensions: 3 } as Property;
179
180
 
180
181
  it("should serialize a Vector instance to a flat array", () => {
181
- const vectorVal = { __type: "Vector", value: [1.5, -2.0, 3.14] };
182
+ const vectorVal = { __type: "Vector",
183
+ value: [1.5, -2.0, 3.14] };
182
184
  expect(serializePropertyToServer(vectorVal, vectorProp)).toEqual([1.5, -2.0, 3.14]);
183
185
  });
184
186
 
@@ -135,12 +135,17 @@ columnType: "time" } as DateProperty)).toBe("time without time zone");
135
135
  it("should map json types correctly", () => {
136
136
  expect(getExpectedColumnType({ type: "map" })).toBe("jsonb");
137
137
  expect(getExpectedColumnType({ type: "array" })).toBe("jsonb");
138
- expect(getExpectedColumnType({ type: "array", columnType: "json" } as ArrayProperty)).toBe("json");
139
-
138
+ expect(getExpectedColumnType({ type: "array",
139
+ columnType: "json" } as ArrayProperty)).toBe("json");
140
+
140
141
  // Native array element type mappings
141
- expect(getExpectedColumnType({ type: "array", of: { type: "string" } } as ArrayProperty)).toBe("ARRAY");
142
- expect(getExpectedColumnType({ type: "array", of: { type: "number", validation: { integer: true } } } as ArrayProperty)).toBe("ARRAY");
143
- expect(getExpectedColumnType({ type: "array", of: { type: "boolean" } } as ArrayProperty)).toBe("ARRAY");
142
+ expect(getExpectedColumnType({ type: "array",
143
+ of: { type: "string" } } as ArrayProperty)).toBe("ARRAY");
144
+ expect(getExpectedColumnType({ type: "array",
145
+ of: { type: "number",
146
+ validation: { integer: true } } } as ArrayProperty)).toBe("ARRAY");
147
+ expect(getExpectedColumnType({ type: "array",
148
+ of: { type: "boolean" } } as ArrayProperty)).toBe("ARRAY");
144
149
  });
145
150
 
146
151
  it("should map enum string to USER-DEFINED", () => {