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