@rebasepro/server-postgresql 0.4.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.
- package/README.md +69 -89
- package/dist/{server-postgresql/src/PostgresAdapter.d.ts → PostgresAdapter.d.ts} +1 -1
- package/dist/{server-postgresql/src/PostgresBackendDriver.d.ts → PostgresBackendDriver.d.ts} +2 -2
- package/dist/{server-postgresql/src/PostgresBootstrapper.d.ts → PostgresBootstrapper.d.ts} +11 -1
- package/dist/{server-postgresql/src/auth → auth}/services.d.ts +11 -11
- package/dist/{server-postgresql/src/collections → collections}/PostgresCollectionRegistry.d.ts +4 -0
- package/dist/{server-postgresql/src/data-transformer.d.ts → data-transformer.d.ts} +0 -3
- package/dist/{server-postgresql/src/databasePoolManager.d.ts → databasePoolManager.d.ts} +1 -1
- package/dist/index.es.js +10174 -11184
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +10735 -11462
- package/dist/index.umd.js.map +1 -1
- package/dist/{server-postgresql/src/services → services}/EntityPersistService.d.ts +0 -14
- package/dist/types.d.ts +3 -0
- package/dist/utils/pg-error-utils.d.ts +55 -0
- package/dist/{server-postgresql/src/websocket.d.ts → websocket.d.ts} +8 -3
- package/package.json +24 -21
- package/src/PostgresAdapter.ts +9 -10
- package/src/PostgresBackendDriver.ts +135 -122
- package/src/PostgresBootstrapper.ts +90 -16
- package/src/auth/ensure-tables.ts +28 -5
- package/src/auth/services.ts +56 -45
- package/src/cli.ts +140 -110
- package/src/collections/PostgresCollectionRegistry.ts +7 -0
- package/src/connection.ts +11 -6
- package/src/data-transformer.ts +73 -109
- package/src/databasePoolManager.ts +5 -3
- package/src/history/HistoryService.ts +3 -2
- package/src/history/ensure-history-table.ts +5 -4
- package/src/schema/auth-schema.ts +1 -2
- package/src/schema/doctor-cli.ts +2 -1
- package/src/schema/doctor.ts +40 -37
- package/src/schema/generate-drizzle-schema-logic.ts +56 -18
- package/src/schema/generate-drizzle-schema.ts +11 -11
- package/src/schema/introspect-db-inference.ts +25 -25
- package/src/schema/introspect-db-logic.ts +38 -38
- package/src/schema/introspect-db.ts +28 -27
- package/src/services/BranchService.ts +14 -0
- package/src/services/EntityFetchService.ts +28 -25
- package/src/services/EntityPersistService.ts +11 -124
- package/src/services/RelationService.ts +57 -37
- package/src/services/entity-helpers.ts +6 -2
- package/src/services/realtimeService.ts +45 -32
- package/src/types.ts +4 -0
- package/src/utils/drizzle-conditions.ts +31 -15
- package/src/utils/pg-error-utils.ts +211 -0
- package/src/websocket.ts +51 -33
- package/test/auth-services.test.ts +36 -19
- package/test/batch-many-to-many-regression.test.ts +119 -39
- package/test/data-transformer-hardening.test.ts +67 -33
- package/test/data-transformer.test.ts +4 -2
- package/test/doctor.test.ts +10 -5
- package/test/drizzle-conditions.test.ts +59 -6
- package/test/generate-drizzle-schema.test.ts +65 -40
- package/test/introspect-db-generation.test.ts +179 -81
- package/test/introspect-db-utils.test.ts +92 -37
- package/test/mocks/chalk.cjs +7 -0
- package/test/pg-error-utils.test.ts +221 -0
- package/test/postgresDataDriver.test.ts +14 -5
- package/test/property-ordering.test.ts +126 -79
- package/test/realtimeService.test.ts +6 -2
- package/test/relation-pipeline-gaps.test.ts +84 -36
- package/test/relations.test.ts +247 -0
- package/test/unmapped-tables-safety.test.ts +14 -6
- package/test/websocket.test.ts +1 -1
- package/tsconfig.json +5 -0
- package/tsconfig.prod.json +3 -0
- package/vite.config.ts +5 -5
- package/dist/common/src/collections/CollectionRegistry.d.ts +0 -56
- package/dist/common/src/collections/default-collections.d.ts +0 -9
- package/dist/common/src/collections/index.d.ts +0 -2
- package/dist/common/src/data/buildRebaseData.d.ts +0 -14
- package/dist/common/src/data/query_builder.d.ts +0 -55
- package/dist/common/src/index.d.ts +0 -4
- package/dist/common/src/util/builders.d.ts +0 -57
- package/dist/common/src/util/callbacks.d.ts +0 -6
- package/dist/common/src/util/collections.d.ts +0 -11
- package/dist/common/src/util/common.d.ts +0 -2
- package/dist/common/src/util/conditions.d.ts +0 -26
- package/dist/common/src/util/entities.d.ts +0 -58
- package/dist/common/src/util/enums.d.ts +0 -3
- package/dist/common/src/util/index.d.ts +0 -16
- package/dist/common/src/util/navigation_from_path.d.ts +0 -34
- package/dist/common/src/util/navigation_utils.d.ts +0 -20
- package/dist/common/src/util/parent_references_from_path.d.ts +0 -6
- package/dist/common/src/util/paths.d.ts +0 -14
- package/dist/common/src/util/permissions.d.ts +0 -6
- package/dist/common/src/util/references.d.ts +0 -2
- package/dist/common/src/util/relations.d.ts +0 -22
- package/dist/common/src/util/resolutions.d.ts +0 -72
- package/dist/common/src/util/storage.d.ts +0 -24
- package/dist/types/src/controllers/analytics_controller.d.ts +0 -7
- package/dist/types/src/controllers/auth.d.ts +0 -104
- package/dist/types/src/controllers/client.d.ts +0 -168
- package/dist/types/src/controllers/collection_registry.d.ts +0 -46
- package/dist/types/src/controllers/customization_controller.d.ts +0 -60
- package/dist/types/src/controllers/data.d.ts +0 -207
- package/dist/types/src/controllers/data_driver.d.ts +0 -218
- package/dist/types/src/controllers/database_admin.d.ts +0 -11
- package/dist/types/src/controllers/dialogs_controller.d.ts +0 -36
- package/dist/types/src/controllers/effective_role.d.ts +0 -4
- package/dist/types/src/controllers/email.d.ts +0 -36
- package/dist/types/src/controllers/index.d.ts +0 -18
- package/dist/types/src/controllers/local_config_persistence.d.ts +0 -20
- package/dist/types/src/controllers/navigation.d.ts +0 -225
- package/dist/types/src/controllers/registry.d.ts +0 -63
- package/dist/types/src/controllers/side_dialogs_controller.d.ts +0 -67
- package/dist/types/src/controllers/side_entity_controller.d.ts +0 -97
- package/dist/types/src/controllers/snackbar.d.ts +0 -24
- package/dist/types/src/controllers/storage.d.ts +0 -171
- package/dist/types/src/index.d.ts +0 -4
- package/dist/types/src/rebase_context.d.ts +0 -122
- package/dist/types/src/types/auth_adapter.d.ts +0 -301
- package/dist/types/src/types/backend.d.ts +0 -536
- package/dist/types/src/types/backend_hooks.d.ts +0 -172
- package/dist/types/src/types/builders.d.ts +0 -15
- package/dist/types/src/types/chips.d.ts +0 -5
- package/dist/types/src/types/collections.d.ts +0 -941
- package/dist/types/src/types/component_ref.d.ts +0 -47
- package/dist/types/src/types/cron.d.ts +0 -102
- package/dist/types/src/types/data_source.d.ts +0 -64
- package/dist/types/src/types/database_adapter.d.ts +0 -94
- package/dist/types/src/types/entities.d.ts +0 -145
- package/dist/types/src/types/entity_actions.d.ts +0 -104
- package/dist/types/src/types/entity_callbacks.d.ts +0 -173
- package/dist/types/src/types/entity_link_builder.d.ts +0 -7
- package/dist/types/src/types/entity_overrides.d.ts +0 -10
- package/dist/types/src/types/entity_views.d.ts +0 -87
- package/dist/types/src/types/export_import.d.ts +0 -21
- package/dist/types/src/types/formex.d.ts +0 -40
- package/dist/types/src/types/index.d.ts +0 -28
- package/dist/types/src/types/locales.d.ts +0 -4
- package/dist/types/src/types/modify_collections.d.ts +0 -5
- package/dist/types/src/types/plugins.d.ts +0 -282
- package/dist/types/src/types/properties.d.ts +0 -1181
- package/dist/types/src/types/property_config.d.ts +0 -74
- package/dist/types/src/types/relations.d.ts +0 -336
- package/dist/types/src/types/slots.d.ts +0 -262
- package/dist/types/src/types/translations.d.ts +0 -900
- package/dist/types/src/types/user_management_delegate.d.ts +0 -86
- package/dist/types/src/types/websockets.d.ts +0 -78
- package/dist/types/src/users/index.d.ts +0 -1
- package/dist/types/src/users/user.d.ts +0 -50
- package/drizzle.test.config.ts +0 -10
- /package/dist/{server-postgresql/src/auth → auth}/ensure-tables.d.ts +0 -0
- /package/dist/{server-postgresql/src/cli.d.ts → cli.d.ts} +0 -0
- /package/dist/{server-postgresql/src/connection.d.ts → connection.d.ts} +0 -0
- /package/dist/{server-postgresql/src/history → history}/HistoryService.d.ts +0 -0
- /package/dist/{server-postgresql/src/history → history}/ensure-history-table.d.ts +0 -0
- /package/dist/{server-postgresql/src/index.d.ts → index.d.ts} +0 -0
- /package/dist/{server-postgresql/src/interfaces.d.ts → interfaces.d.ts} +0 -0
- /package/dist/{server-postgresql/src/schema → schema}/auth-schema.d.ts +0 -0
- /package/dist/{server-postgresql/src/schema → schema}/doctor-cli.d.ts +0 -0
- /package/dist/{server-postgresql/src/schema → schema}/doctor.d.ts +0 -0
- /package/dist/{server-postgresql/src/schema → schema}/generate-drizzle-schema-logic.d.ts +0 -0
- /package/dist/{server-postgresql/src/schema → schema}/generate-drizzle-schema.d.ts +0 -0
- /package/dist/{server-postgresql/src/schema → schema}/introspect-db-inference.d.ts +0 -0
- /package/dist/{server-postgresql/src/schema → schema}/introspect-db-logic.d.ts +0 -0
- /package/dist/{server-postgresql/src/schema → schema}/introspect-db.d.ts +0 -0
- /package/dist/{server-postgresql/src/schema → schema}/test-schema.d.ts +0 -0
- /package/dist/{server-postgresql/src/services → services}/BranchService.d.ts +0 -0
- /package/dist/{server-postgresql/src/services → services}/EntityFetchService.d.ts +0 -0
- /package/dist/{server-postgresql/src/services → services}/RelationService.d.ts +0 -0
- /package/dist/{server-postgresql/src/services → services}/entity-helpers.d.ts +0 -0
- /package/dist/{server-postgresql/src/services → services}/entityService.d.ts +0 -0
- /package/dist/{server-postgresql/src/services → services}/index.d.ts +0 -0
- /package/dist/{server-postgresql/src/services → services}/realtimeService.d.ts +0 -0
- /package/dist/{server-postgresql/src/utils → utils}/drizzle-conditions.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",
|
|
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",
|
|
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",
|
|
45
|
-
|
|
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",
|
|
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",
|
|
57
|
-
|
|
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",
|
|
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",
|
|
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",
|
|
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",
|
|
154
|
-
|
|
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,
|
|
272
|
-
|
|
273
|
-
{ id:
|
|
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,
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
{ posts_tags: { post_id:
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
404
|
-
|
|
405
|
-
|
|
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,
|
|
447
|
-
|
|
448
|
-
|
|
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",
|
|
512
|
-
|
|
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,
|
|
534
|
-
|
|
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,
|
|
557
|
-
|
|
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",
|
|
34
|
-
|
|
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",
|
|
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",
|
|
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",
|
|
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",
|
|
129
|
-
|
|
130
|
-
|
|
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",
|
|
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",
|
|
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",
|
|
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",
|
|
198
|
+
total: { type: "number",
|
|
199
|
+
name: "Total" } as Property
|
|
188
200
|
});
|
|
189
201
|
|
|
190
202
|
const result = normalizeDbValues(
|
|
191
|
-
{ customer: "some-id",
|
|
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,
|
|
261
|
-
dataType: true,
|
|
262
|
-
primary: "yes"
|
|
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",
|
|
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",
|
|
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",
|
|
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",
|
|
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",
|
|
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",
|
|
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",
|
|
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",
|
|
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",
|
|
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",
|
|
376
|
-
|
|
377
|
-
|
|
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",
|
|
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",
|
|
419
|
+
label: { type: "string",
|
|
420
|
+
name: "Label" } as Property
|
|
392
421
|
});
|
|
393
422
|
|
|
394
423
|
const properties: Properties = {
|
|
395
|
-
title: { type: "string",
|
|
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",
|
|
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",
|
|
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",
|
|
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",
|
|
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
|
|
package/test/doctor.test.ts
CHANGED
|
@@ -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",
|
|
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",
|
|
142
|
-
|
|
143
|
-
expect(getExpectedColumnType({ type: "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", () => {
|