@rebasepro/server-postgresql 0.5.0 → 0.6.1

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 (169) 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 +10207 -11145
  6. package/dist/index.es.js.map +1 -1
  7. package/dist/{server-postgresql/src/services → services}/EntityPersistService.d.ts +0 -14
  8. package/dist/utils/pg-array-null-patch.d.ts +16 -0
  9. package/dist/utils/pg-error-utils.d.ts +55 -0
  10. package/package.json +24 -21
  11. package/src/PostgresAdapter.ts +9 -10
  12. package/src/PostgresBackendDriver.ts +134 -121
  13. package/src/PostgresBootstrapper.ts +100 -30
  14. package/src/auth/ensure-tables.ts +28 -5
  15. package/src/auth/services.ts +28 -18
  16. package/src/cli.ts +99 -96
  17. package/src/collections/PostgresCollectionRegistry.ts +7 -0
  18. package/src/connection.ts +11 -6
  19. package/src/data-transformer.ts +25 -15
  20. package/src/databasePoolManager.ts +3 -2
  21. package/src/history/HistoryService.ts +3 -2
  22. package/src/history/ensure-history-table.ts +5 -4
  23. package/src/schema/auth-schema.ts +1 -2
  24. package/src/schema/doctor-cli.ts +2 -1
  25. package/src/schema/doctor.ts +40 -37
  26. package/src/schema/generate-drizzle-schema-logic.ts +56 -18
  27. package/src/schema/generate-drizzle-schema.ts +11 -11
  28. package/src/schema/introspect-db-inference.ts +25 -25
  29. package/src/schema/introspect-db-logic.ts +38 -38
  30. package/src/schema/introspect-db.ts +28 -27
  31. package/src/services/BranchService.ts +14 -0
  32. package/src/services/EntityFetchService.ts +28 -25
  33. package/src/services/EntityPersistService.ts +19 -149
  34. package/src/services/RelationService.ts +57 -37
  35. package/src/services/entity-helpers.ts +6 -2
  36. package/src/services/realtimeService.ts +45 -32
  37. package/src/utils/drizzle-conditions.ts +31 -15
  38. package/src/utils/pg-array-null-patch.ts +42 -0
  39. package/src/utils/pg-error-utils.ts +211 -0
  40. package/src/websocket.ts +15 -12
  41. package/test/array-null-safety.test.ts +335 -0
  42. package/test/auth-services.test.ts +36 -19
  43. package/test/batch-many-to-many-regression.test.ts +119 -39
  44. package/test/data-transformer-hardening.test.ts +67 -33
  45. package/test/data-transformer.test.ts +57 -4
  46. package/test/doctor.test.ts +10 -5
  47. package/test/drizzle-conditions.test.ts +59 -6
  48. package/test/generate-drizzle-schema.test.ts +65 -40
  49. package/test/introspect-db-generation.test.ts +179 -81
  50. package/test/introspect-db-utils.test.ts +92 -37
  51. package/test/mocks/chalk.cjs +7 -0
  52. package/test/pg-array-null-patch.test.ts +65 -0
  53. package/test/pg-error-utils.test.ts +221 -0
  54. package/test/postgresDataDriver.test.ts +14 -5
  55. package/test/property-ordering.test.ts +126 -79
  56. package/test/realtimeService.test.ts +6 -2
  57. package/test/relation-pipeline-gaps.test.ts +84 -36
  58. package/test/relations.test.ts +247 -0
  59. package/test/unmapped-tables-safety.test.ts +14 -6
  60. package/test/websocket.test.ts +1 -1
  61. package/tsconfig.json +5 -0
  62. package/tsconfig.prod.json +3 -0
  63. package/vite.config.ts +12 -10
  64. package/dist/common/src/collections/CollectionRegistry.d.ts +0 -56
  65. package/dist/common/src/collections/default-collections.d.ts +0 -9
  66. package/dist/common/src/collections/index.d.ts +0 -2
  67. package/dist/common/src/data/buildRebaseData.d.ts +0 -14
  68. package/dist/common/src/data/query_builder.d.ts +0 -55
  69. package/dist/common/src/index.d.ts +0 -4
  70. package/dist/common/src/util/builders.d.ts +0 -57
  71. package/dist/common/src/util/callbacks.d.ts +0 -6
  72. package/dist/common/src/util/collections.d.ts +0 -11
  73. package/dist/common/src/util/common.d.ts +0 -2
  74. package/dist/common/src/util/conditions.d.ts +0 -26
  75. package/dist/common/src/util/entities.d.ts +0 -58
  76. package/dist/common/src/util/enums.d.ts +0 -3
  77. package/dist/common/src/util/index.d.ts +0 -16
  78. package/dist/common/src/util/navigation_from_path.d.ts +0 -34
  79. package/dist/common/src/util/navigation_utils.d.ts +0 -20
  80. package/dist/common/src/util/parent_references_from_path.d.ts +0 -6
  81. package/dist/common/src/util/paths.d.ts +0 -14
  82. package/dist/common/src/util/permissions.d.ts +0 -14
  83. package/dist/common/src/util/references.d.ts +0 -2
  84. package/dist/common/src/util/relations.d.ts +0 -22
  85. package/dist/common/src/util/resolutions.d.ts +0 -72
  86. package/dist/common/src/util/storage.d.ts +0 -24
  87. package/dist/index.umd.js +0 -11749
  88. package/dist/index.umd.js.map +0 -1
  89. package/dist/types/src/controllers/analytics_controller.d.ts +0 -7
  90. package/dist/types/src/controllers/auth.d.ts +0 -104
  91. package/dist/types/src/controllers/client.d.ts +0 -168
  92. package/dist/types/src/controllers/collection_registry.d.ts +0 -46
  93. package/dist/types/src/controllers/customization_controller.d.ts +0 -60
  94. package/dist/types/src/controllers/data.d.ts +0 -207
  95. package/dist/types/src/controllers/data_driver.d.ts +0 -218
  96. package/dist/types/src/controllers/database_admin.d.ts +0 -11
  97. package/dist/types/src/controllers/dialogs_controller.d.ts +0 -36
  98. package/dist/types/src/controllers/effective_role.d.ts +0 -4
  99. package/dist/types/src/controllers/email.d.ts +0 -36
  100. package/dist/types/src/controllers/index.d.ts +0 -18
  101. package/dist/types/src/controllers/local_config_persistence.d.ts +0 -20
  102. package/dist/types/src/controllers/navigation.d.ts +0 -225
  103. package/dist/types/src/controllers/registry.d.ts +0 -63
  104. package/dist/types/src/controllers/side_dialogs_controller.d.ts +0 -67
  105. package/dist/types/src/controllers/side_entity_controller.d.ts +0 -97
  106. package/dist/types/src/controllers/snackbar.d.ts +0 -24
  107. package/dist/types/src/controllers/storage.d.ts +0 -171
  108. package/dist/types/src/index.d.ts +0 -4
  109. package/dist/types/src/rebase_context.d.ts +0 -122
  110. package/dist/types/src/types/auth_adapter.d.ts +0 -301
  111. package/dist/types/src/types/backend.d.ts +0 -571
  112. package/dist/types/src/types/backend_hooks.d.ts +0 -172
  113. package/dist/types/src/types/builders.d.ts +0 -15
  114. package/dist/types/src/types/chips.d.ts +0 -5
  115. package/dist/types/src/types/collections.d.ts +0 -961
  116. package/dist/types/src/types/component_ref.d.ts +0 -47
  117. package/dist/types/src/types/cron.d.ts +0 -102
  118. package/dist/types/src/types/data_source.d.ts +0 -64
  119. package/dist/types/src/types/database_adapter.d.ts +0 -94
  120. package/dist/types/src/types/entities.d.ts +0 -145
  121. package/dist/types/src/types/entity_actions.d.ts +0 -104
  122. package/dist/types/src/types/entity_callbacks.d.ts +0 -173
  123. package/dist/types/src/types/entity_link_builder.d.ts +0 -7
  124. package/dist/types/src/types/entity_overrides.d.ts +0 -10
  125. package/dist/types/src/types/entity_views.d.ts +0 -87
  126. package/dist/types/src/types/export_import.d.ts +0 -21
  127. package/dist/types/src/types/formex.d.ts +0 -40
  128. package/dist/types/src/types/index.d.ts +0 -28
  129. package/dist/types/src/types/locales.d.ts +0 -4
  130. package/dist/types/src/types/modify_collections.d.ts +0 -5
  131. package/dist/types/src/types/plugins.d.ts +0 -282
  132. package/dist/types/src/types/properties.d.ts +0 -1173
  133. package/dist/types/src/types/property_config.d.ts +0 -74
  134. package/dist/types/src/types/relations.d.ts +0 -336
  135. package/dist/types/src/types/slots.d.ts +0 -262
  136. package/dist/types/src/types/translations.d.ts +0 -900
  137. package/dist/types/src/types/user_management_delegate.d.ts +0 -86
  138. package/dist/types/src/types/websockets.d.ts +0 -78
  139. package/dist/types/src/users/index.d.ts +0 -1
  140. package/dist/types/src/users/user.d.ts +0 -50
  141. /package/dist/{server-postgresql/src/auth → auth}/ensure-tables.d.ts +0 -0
  142. /package/dist/{server-postgresql/src/auth → auth}/services.d.ts +0 -0
  143. /package/dist/{server-postgresql/src/cli.d.ts → cli.d.ts} +0 -0
  144. /package/dist/{server-postgresql/src/connection.d.ts → connection.d.ts} +0 -0
  145. /package/dist/{server-postgresql/src/data-transformer.d.ts → data-transformer.d.ts} +0 -0
  146. /package/dist/{server-postgresql/src/databasePoolManager.d.ts → databasePoolManager.d.ts} +0 -0
  147. /package/dist/{server-postgresql/src/history → history}/HistoryService.d.ts +0 -0
  148. /package/dist/{server-postgresql/src/history → history}/ensure-history-table.d.ts +0 -0
  149. /package/dist/{server-postgresql/src/index.d.ts → index.d.ts} +0 -0
  150. /package/dist/{server-postgresql/src/interfaces.d.ts → interfaces.d.ts} +0 -0
  151. /package/dist/{server-postgresql/src/schema → schema}/auth-schema.d.ts +0 -0
  152. /package/dist/{server-postgresql/src/schema → schema}/doctor-cli.d.ts +0 -0
  153. /package/dist/{server-postgresql/src/schema → schema}/doctor.d.ts +0 -0
  154. /package/dist/{server-postgresql/src/schema → schema}/generate-drizzle-schema-logic.d.ts +0 -0
  155. /package/dist/{server-postgresql/src/schema → schema}/generate-drizzle-schema.d.ts +0 -0
  156. /package/dist/{server-postgresql/src/schema → schema}/introspect-db-inference.d.ts +0 -0
  157. /package/dist/{server-postgresql/src/schema → schema}/introspect-db-logic.d.ts +0 -0
  158. /package/dist/{server-postgresql/src/schema → schema}/introspect-db.d.ts +0 -0
  159. /package/dist/{server-postgresql/src/schema → schema}/test-schema.d.ts +0 -0
  160. /package/dist/{server-postgresql/src/services → services}/BranchService.d.ts +0 -0
  161. /package/dist/{server-postgresql/src/services → services}/EntityFetchService.d.ts +0 -0
  162. /package/dist/{server-postgresql/src/services → services}/RelationService.d.ts +0 -0
  163. /package/dist/{server-postgresql/src/services → services}/entity-helpers.d.ts +0 -0
  164. /package/dist/{server-postgresql/src/services → services}/entityService.d.ts +0 -0
  165. /package/dist/{server-postgresql/src/services → services}/index.d.ts +0 -0
  166. /package/dist/{server-postgresql/src/services → services}/realtimeService.d.ts +0 -0
  167. /package/dist/{server-postgresql/src/types.d.ts → types.d.ts} +0 -0
  168. /package/dist/{server-postgresql/src/utils → utils}/drizzle-conditions.d.ts +0 -0
  169. /package/dist/{server-postgresql/src/websocket.d.ts → websocket.d.ts} +0 -0
@@ -0,0 +1,335 @@
1
+ import { describe, it, expect } from "@jest/globals";
2
+ import {
3
+ serializePropertyToServer,
4
+ parsePropertyFromServer,
5
+ serializeDataToServer
6
+ } from "../src/data-transformer";
7
+ import type { Property, Properties, EntityCollection } from "@rebasepro/types";
8
+
9
+ // ─────────────────────────────────────────────────────────────
10
+ // Fixture helpers
11
+ // ─────────────────────────────────────────────────────────────
12
+ function makeCollection(slug: string, properties: Properties): EntityCollection {
13
+ return {
14
+ name: slug,
15
+ slug,
16
+ path: slug,
17
+ collectionType: "postgres",
18
+ tableName: slug,
19
+ properties
20
+ } as unknown as EntityCollection;
21
+ }
22
+
23
+ // ─────────────────────────────────────────────────────────────
24
+ // Property fixtures
25
+ // ─────────────────────────────────────────────────────────────
26
+ const arrayOfStringsProp: Property = {
27
+ type: "array",
28
+ name: "Tags",
29
+ of: { type: "string", name: "Tag" }
30
+ } as Property;
31
+
32
+ const arrayOfNumbersProp: Property = {
33
+ type: "array",
34
+ name: "Scores",
35
+ of: { type: "number", name: "Score" }
36
+ } as Property;
37
+
38
+ const arrayOfMapsProp: Property = {
39
+ type: "array",
40
+ name: "Addresses",
41
+ of: {
42
+ type: "map",
43
+ name: "Address",
44
+ properties: {
45
+ street: { type: "string", name: "Street" },
46
+ city: { type: "string", name: "City" }
47
+ }
48
+ }
49
+ } as Property;
50
+
51
+ const arrayOneOfProp: Property = {
52
+ type: "array",
53
+ name: "Blocks",
54
+ oneOf: {
55
+ typeField: "type",
56
+ valueField: "value",
57
+ properties: {
58
+ text: { type: "string", name: "Text" } as Property,
59
+ number: { type: "number", name: "Number" } as Property
60
+ }
61
+ }
62
+ } as Property;
63
+
64
+ const arrayOfRelationsProp: Property = {
65
+ type: "array",
66
+ name: "Authors",
67
+ of: { type: "relation", name: "Author" }
68
+ } as Property;
69
+
70
+ // A bare array property without `of` or `oneOf`
71
+ const bareArrayProp: Property = {
72
+ type: "array",
73
+ name: "RawArray"
74
+ } as Property;
75
+
76
+ // ─────────────────────────────────────────────────────────────
77
+ // 1. serializePropertyToServer — array null safety
78
+ // ─────────────────────────────────────────────────────────────
79
+ describe("serializePropertyToServer — array null safety", () => {
80
+
81
+ // ── Null / undefined early return ──
82
+ it("returns null when value is null with array-of-strings property", () => {
83
+ expect(serializePropertyToServer(null, arrayOfStringsProp)).toBeNull();
84
+ });
85
+
86
+ it("returns undefined when value is undefined with array-of-strings property", () => {
87
+ expect(serializePropertyToServer(undefined, arrayOfStringsProp)).toBeUndefined();
88
+ });
89
+
90
+ // ── Empty array ──
91
+ it("returns [] when value is an empty array", () => {
92
+ expect(serializePropertyToServer([], arrayOfStringsProp)).toEqual([]);
93
+ });
94
+
95
+ // ── Null with different sub-types ──
96
+ it("returns null when value is null with array-of-numbers property", () => {
97
+ expect(serializePropertyToServer(null, arrayOfNumbersProp)).toBeNull();
98
+ });
99
+
100
+ it("returns null when value is null with array-of-maps property", () => {
101
+ expect(serializePropertyToServer(null, arrayOfMapsProp)).toBeNull();
102
+ });
103
+
104
+ it("returns null when value is null with array oneOf property", () => {
105
+ expect(serializePropertyToServer(null, arrayOneOfProp)).toBeNull();
106
+ });
107
+
108
+ // ── Non-array values (coerced to empty array) ──
109
+ it("coerces a non-array string to empty array for array property", () => {
110
+ expect(serializePropertyToServer("not-an-array", arrayOfStringsProp)).toEqual([]);
111
+ });
112
+
113
+ it("coerces a non-array number to empty array for array property", () => {
114
+ expect(serializePropertyToServer(42, arrayOfStringsProp)).toEqual([]);
115
+ });
116
+
117
+ it("coerces a non-array object to empty array for bare array property", () => {
118
+ const obj = { a: 1 };
119
+ expect(serializePropertyToServer(obj, bareArrayProp)).toEqual([]);
120
+ });
121
+
122
+ it("coerces a boolean to empty array for array property", () => {
123
+ expect(serializePropertyToServer(true, arrayOfStringsProp)).toEqual([]);
124
+ expect(serializePropertyToServer(false, arrayOfStringsProp)).toEqual([]);
125
+ });
126
+
127
+ // ── Arrays with null / undefined elements ──
128
+ it("handles array with null elements by passing each through sub-serialization", () => {
129
+ const result = serializePropertyToServer([null, "a", null], arrayOfStringsProp);
130
+ expect(result).toEqual([null, "a", null]);
131
+ });
132
+
133
+ it("handles array with undefined elements by passing each through sub-serialization", () => {
134
+ const result = serializePropertyToServer([undefined, "a"], arrayOfStringsProp);
135
+ expect(result).toEqual([undefined, "a"]);
136
+ });
137
+
138
+ // ── Array of relation objects with null entries ──
139
+ it("serializes array of relation objects with null entries correctly", () => {
140
+ const value = [{ id: "1" }, null, { id: "2" }];
141
+ const result = serializePropertyToServer(value, arrayOfRelationsProp);
142
+ // null entries pass through serializePropertyToServer for the sub-property
143
+ // which returns null for null values
144
+ expect(result).toEqual(["1", null, "2"]);
145
+ });
146
+
147
+ // ── Array of maps where some entries are null ──
148
+ it("handles array of maps where some entries are null gracefully", () => {
149
+ const value = [
150
+ { street: "123 Main", city: "NYC" },
151
+ null,
152
+ { street: "456 Oak", city: "LA" }
153
+ ];
154
+ const result = serializePropertyToServer(value, arrayOfMapsProp);
155
+ expect(result).toEqual([
156
+ { street: "123 Main", city: "NYC" },
157
+ null,
158
+ { street: "456 Oak", city: "LA" }
159
+ ]);
160
+ });
161
+
162
+ // ── Empty array with oneOf property ──
163
+ it("returns [] when value is an empty array with oneOf property", () => {
164
+ expect(serializePropertyToServer([], arrayOneOfProp)).toEqual([]);
165
+ });
166
+
167
+ // ── Nested array of arrays (unusual) ──
168
+ it("returns nested array of arrays as-is for bare array property", () => {
169
+ const nested = [["a", "b"], ["c"]];
170
+ // Without `of` or `oneOf`, the array case falls through to `return value`
171
+ expect(serializePropertyToServer(nested, bareArrayProp)).toEqual([["a", "b"], ["c"]]);
172
+ });
173
+ });
174
+
175
+ // ─────────────────────────────────────────────────────────────
176
+ // 2. parsePropertyFromServer — array null safety
177
+ // ─────────────────────────────────────────────────────────────
178
+ describe("parsePropertyFromServer — array null safety", () => {
179
+ const dummyCollection = makeCollection("items", {
180
+ tags: arrayOfStringsProp
181
+ });
182
+
183
+ // ── Null / undefined early return ──
184
+ it("returns null when value is null with array property", () => {
185
+ expect(parsePropertyFromServer(null, arrayOfStringsProp, dummyCollection)).toBeNull();
186
+ });
187
+
188
+ it("returns undefined when value is undefined with array property", () => {
189
+ expect(parsePropertyFromServer(undefined, arrayOfStringsProp, dummyCollection)).toBeUndefined();
190
+ });
191
+
192
+ // ── Empty array ──
193
+ it("returns [] when value is an empty array with array-of-strings", () => {
194
+ expect(parsePropertyFromServer([], arrayOfStringsProp, dummyCollection)).toEqual([]);
195
+ });
196
+
197
+ // ── Non-array values (coerced to array) ──
198
+ it("coerces a non-array string to single-element array for array property", () => {
199
+ expect(parsePropertyFromServer("not-an-array", arrayOfStringsProp, dummyCollection)).toEqual(["not-an-array"]);
200
+ });
201
+
202
+ it("coerces a non-array number to empty array for array property", () => {
203
+ expect(parsePropertyFromServer(99, arrayOfStringsProp, dummyCollection)).toEqual([]);
204
+ });
205
+
206
+ // ── Null with oneOf ──
207
+ it("returns null when value is null with array oneOf property", () => {
208
+ expect(parsePropertyFromServer(null, arrayOneOfProp, dummyCollection)).toBeNull();
209
+ });
210
+
211
+ // ── Array with null elements ──
212
+ it("handles array with null elements for parsing gracefully", () => {
213
+ const result = parsePropertyFromServer([null, "a", null], arrayOfStringsProp, dummyCollection);
214
+ expect(result).toEqual([null, "a", null]);
215
+ });
216
+
217
+ // ── Falsy non-null values (coerced to array) ──
218
+ it("coerces falsy number 0 to empty array for array property", () => {
219
+ expect(parsePropertyFromServer(0, arrayOfStringsProp, dummyCollection)).toEqual([]);
220
+ });
221
+
222
+ it("coerces false to empty array for array property", () => {
223
+ expect(parsePropertyFromServer(false, arrayOfStringsProp, dummyCollection)).toEqual([]);
224
+ });
225
+
226
+ it("coerces empty string to single-element array for array property", () => {
227
+ expect(parsePropertyFromServer("", arrayOfStringsProp, dummyCollection)).toEqual([""]);
228
+ });
229
+ });
230
+
231
+ // ─────────────────────────────────────────────────────────────
232
+ // 3. serializeDataToServer — entity with null array fields
233
+ // ─────────────────────────────────────────────────────────────
234
+ describe("serializeDataToServer — entity with null array fields", () => {
235
+ const tagsProperty: Property = {
236
+ type: "array",
237
+ name: "Tags",
238
+ of: { type: "string", name: "Tag" }
239
+ } as Property;
240
+
241
+ const categoriesProperty: Property = {
242
+ type: "array",
243
+ name: "Categories",
244
+ of: { type: "string", name: "Category" }
245
+ } as Property;
246
+
247
+ const metadataProperty: Property = {
248
+ type: "map",
249
+ name: "Metadata",
250
+ properties: {
251
+ labels: {
252
+ type: "array",
253
+ name: "Labels",
254
+ of: { type: "string", name: "Label" }
255
+ } as Property
256
+ }
257
+ } as Property;
258
+
259
+ const properties: Properties = {
260
+ tags: tagsProperty
261
+ };
262
+
263
+ // ── Null array field ──
264
+ it("serializes entity with tags: null — scalarData.tags should be null", () => {
265
+ const result = serializeDataToServer(
266
+ { tags: null },
267
+ properties
268
+ );
269
+ expect(result.scalarData.tags).toBeNull();
270
+ });
271
+
272
+ // ── Undefined array field ──
273
+ it("serializes entity with tags: undefined — tags value should be undefined in scalarData", () => {
274
+ const result = serializeDataToServer(
275
+ { tags: undefined },
276
+ properties
277
+ );
278
+ // Object.entries iterates keys even when value is undefined,
279
+ // and serializePropertyToServer returns undefined for undefined input
280
+ expect(result.scalarData.tags).toBeUndefined();
281
+ });
282
+
283
+ // ── Empty array field ──
284
+ it("serializes entity with tags: [] — scalarData.tags should be []", () => {
285
+ const result = serializeDataToServer(
286
+ { tags: [] },
287
+ properties
288
+ );
289
+ expect(result.scalarData.tags).toEqual([]);
290
+ });
291
+
292
+ // ── Multiple array fields, mixed null / populated ──
293
+ it("handles entity with multiple array fields, some null, some populated", () => {
294
+ const multiProperties: Properties = {
295
+ tags: tagsProperty,
296
+ categories: categoriesProperty
297
+ };
298
+
299
+ const result = serializeDataToServer(
300
+ { tags: null, categories: ["cat-a", "cat-b"] },
301
+ multiProperties
302
+ );
303
+ expect(result.scalarData.tags).toBeNull();
304
+ expect(result.scalarData.categories).toEqual(["cat-a", "cat-b"]);
305
+ });
306
+
307
+ // ── Nested map containing null array ──
308
+ it("handles entity with nested map containing array field that is null", () => {
309
+ const nestedProperties: Properties = {
310
+ metadata: metadataProperty
311
+ };
312
+
313
+ const result = serializeDataToServer(
314
+ { metadata: { labels: null } },
315
+ nestedProperties
316
+ );
317
+ // The map serializer recurses into sub-properties; the inner array gets null-checked
318
+ expect(result.scalarData.metadata).toEqual({ labels: null });
319
+ });
320
+
321
+ // ── All array fields null ──
322
+ it("handles entity with only array fields, all null — scalarData should have all null values", () => {
323
+ const allArrayProperties: Properties = {
324
+ tags: tagsProperty,
325
+ categories: categoriesProperty
326
+ };
327
+
328
+ const result = serializeDataToServer(
329
+ { tags: null, categories: null },
330
+ allArrayProperties
331
+ );
332
+ expect(result.scalarData.tags).toBeNull();
333
+ expect(result.scalarData.categories).toBeNull();
334
+ });
335
+ });
@@ -8,7 +8,9 @@ jest.mock("drizzle-orm", () => {
8
8
  const actual = jest.requireActual("drizzle-orm");
9
9
  return {
10
10
  ...actual,
11
- eq: jest.fn((field, value) => ({ field, value, type: "eq" })),
11
+ eq: jest.fn((field, value) => ({ field,
12
+ value,
13
+ type: "eq" })),
12
14
  sql: Object.assign(
13
15
  jest.fn((strings: TemplateStringsArray, ...values: unknown[]) => ({
14
16
  strings,
@@ -16,8 +18,11 @@ jest.mock("drizzle-orm", () => {
16
18
  type: "sql"
17
19
  })),
18
20
  {
19
- raw: jest.fn((val: string) => ({ val, type: "sql-raw" })),
20
- join: jest.fn((parts: unknown[], separator: unknown) => ({ parts, separator, type: "sql-join" }))
21
+ raw: jest.fn((val: string) => ({ val,
22
+ type: "sql-raw" })),
23
+ join: jest.fn((parts: unknown[], separator: unknown) => ({ parts,
24
+ separator,
25
+ type: "sql-join" }))
21
26
  }
22
27
  ),
23
28
  relations: jest.fn(() => ({}))
@@ -133,11 +138,11 @@ describe("Auth Services", () => {
133
138
  email: "test@example.com",
134
139
  displayName: "Test User"
135
140
  };
136
- const dbReturnedUser = {
141
+ const dbReturnedUser = {
137
142
  id: "user-123",
138
143
  ...newUser,
139
144
  createdAt: new Date(),
140
- updatedAt: new Date()
145
+ updatedAt: new Date()
141
146
  };
142
147
  mockInsertReturning.mockResolvedValueOnce([dbReturnedUser]);
143
148
 
@@ -154,7 +159,8 @@ describe("Auth Services", () => {
154
159
 
155
160
  describe("getUserById", () => {
156
161
  it("should return user when found", async () => {
157
- const mockUser = { id: "user-123", email: "test@example.com" };
162
+ const mockUser = { id: "user-123",
163
+ email: "test@example.com" };
158
164
  mockSelectWhere.mockResolvedValueOnce([mockUser]);
159
165
 
160
166
  const result = await userService.getUserById("user-123");
@@ -174,7 +180,8 @@ describe("Auth Services", () => {
174
180
 
175
181
  describe("getUserByEmail", () => {
176
182
  it("should return user when found by email", async () => {
177
- const mockUser = { id: "user-123", email: "test@example.com" };
183
+ const mockUser = { id: "user-123",
184
+ email: "test@example.com" };
178
185
  mockSelectWhere.mockResolvedValueOnce([mockUser]);
179
186
 
180
187
  const result = await userService.getUserByEmail("test@example.com");
@@ -194,13 +201,15 @@ describe("Auth Services", () => {
194
201
 
195
202
  describe("getUserByIdentity", () => {
196
203
  it("should fetch user by identity", async () => {
197
- const mockUser = { id: "user-123", email: "test@example.com" };
204
+ const mockUser = { id: "user-123",
205
+ email: "test@example.com" };
198
206
  mockSelectWhere.mockResolvedValueOnce([{ user: mockUser }]);
199
207
 
200
208
  const result = await userService.getUserByIdentity("google", "google-abc");
201
209
 
202
210
  expect(db.select).toHaveBeenCalled();
203
- expect(result).toEqual(expect.objectContaining({ id: "user-123", email: "test@example.com" }));
211
+ expect(result).toEqual(expect.objectContaining({ id: "user-123",
212
+ email: "test@example.com" }));
204
213
  });
205
214
  });
206
215
 
@@ -223,10 +232,10 @@ describe("Auth Services", () => {
223
232
 
224
233
  describe("updateUser", () => {
225
234
  it("should update user and return updated record", async () => {
226
- const updatedUser = {
235
+ const updatedUser = {
227
236
  id: "user-123",
228
237
  email: "test@example.com",
229
- displayName: "Updated Name"
238
+ displayName: "Updated Name"
230
239
  };
231
240
  mockUpdateReturning.mockResolvedValueOnce([updatedUser]);
232
241
 
@@ -261,8 +270,10 @@ describe("Auth Services", () => {
261
270
  describe("listUsers", () => {
262
271
  it("should return all users", async () => {
263
272
  const mockUsers = [
264
- { id: "user-1", email: "user1@example.com" },
265
- { id: "user-2", email: "user2@example.com" }
273
+ { id: "user-1",
274
+ email: "user1@example.com" },
275
+ { id: "user-2",
276
+ email: "user2@example.com" }
266
277
  ];
267
278
  mockSelectFrom.mockReturnValueOnce(Promise.resolve(mockUsers));
268
279
 
@@ -270,8 +281,10 @@ describe("Auth Services", () => {
270
281
 
271
282
  expect(db.select).toHaveBeenCalled();
272
283
  expect(result).toEqual([
273
- mockUserData({ id: "user-1", email: "user1@example.com" }),
274
- mockUserData({ id: "user-2", email: "user2@example.com" })
284
+ mockUserData({ id: "user-1",
285
+ email: "user1@example.com" }),
286
+ mockUserData({ id: "user-2",
287
+ email: "user2@example.com" })
275
288
  ]);
276
289
  });
277
290
  });
@@ -332,7 +345,8 @@ describe("Auth Services", () => {
332
345
 
333
346
  describe("getUserByVerificationToken", () => {
334
347
  it("should find user by verification token", async () => {
335
- const mockUser = { id: "user-123", email: "test@example.com" };
348
+ const mockUser = { id: "user-123",
349
+ email: "test@example.com" };
336
350
  mockSelectWhere.mockResolvedValueOnce([mockUser]);
337
351
 
338
352
  const result = await userService.getUserByVerificationToken("token-abc");
@@ -396,7 +410,8 @@ describe("Auth Services", () => {
396
410
 
397
411
  describe("getUserWithRoles", () => {
398
412
  it("should return user with roles", async () => {
399
- const mockUser = { id: "user-123", email: "test@example.com" };
413
+ const mockUser = { id: "user-123",
414
+ email: "test@example.com" };
400
415
  mockSelectWhere.mockResolvedValueOnce([mockUser]);
401
416
  mockExecute.mockResolvedValueOnce({
402
417
  rows: [{ roles: ["admin"] }]
@@ -427,7 +442,8 @@ describe("Auth Services", () => {
427
442
  it("should return paginated and filtered users list", async () => {
428
443
  mockExecute
429
444
  .mockResolvedValueOnce({ rows: [{ total: 1 }] })
430
- .mockResolvedValueOnce({ rows: [{ id: "user-123", email: "test@example.com" }] });
445
+ .mockResolvedValueOnce({ rows: [{ id: "user-123",
446
+ email: "test@example.com" }] });
431
447
 
432
448
  const result = await userService.listUsersPaginated({
433
449
  limit: 10,
@@ -439,7 +455,8 @@ describe("Auth Services", () => {
439
455
 
440
456
  expect(mockExecute).toHaveBeenCalledTimes(2);
441
457
  expect(result).toEqual({
442
- users: [mockUserData({ id: "user-123", email: "test@example.com" })],
458
+ users: [mockUserData({ id: "user-123",
459
+ email: "test@example.com" })],
443
460
  total: 1,
444
461
  limit: 10,
445
462
  offset: 0