@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
|
@@ -1,22 +1,33 @@
|
|
|
1
1
|
import {
|
|
2
2
|
generateCollectionFile, buildTablesMap, buildEnumMap,
|
|
3
3
|
identifyJoinTables, TableRow, TableColumn, PrimaryKeyRow,
|
|
4
|
-
ForeignKeyRow, EnumValue, TableMeta
|
|
4
|
+
ForeignKeyRow, EnumValue, TableMeta
|
|
5
5
|
} from "../src/schema/introspect-db-logic";
|
|
6
6
|
|
|
7
7
|
// ── Helpers ───────────────────────────────────────────────────────────
|
|
8
8
|
|
|
9
9
|
const mkCol = (table: string, col: string, opts: Partial<TableColumn> = {}): TableColumn => ({
|
|
10
|
-
table_name: table,
|
|
11
|
-
|
|
10
|
+
table_name: table,
|
|
11
|
+
column_name: col,
|
|
12
|
+
data_type: "character varying",
|
|
13
|
+
udt_name: "varchar",
|
|
14
|
+
is_nullable: "YES",
|
|
15
|
+
column_default: null,
|
|
16
|
+
...opts
|
|
12
17
|
});
|
|
13
18
|
|
|
14
19
|
const mkFk = (table: string, col: string, fTable: string, fCol = "id"): ForeignKeyRow => ({
|
|
15
|
-
table_name: table,
|
|
20
|
+
table_name: table,
|
|
21
|
+
column_name: col,
|
|
22
|
+
foreign_table_name: fTable,
|
|
23
|
+
foreign_column_name: fCol
|
|
16
24
|
});
|
|
17
25
|
|
|
18
26
|
function makeSimpleTable(name: string, columns: TableColumn[], pks: string[] = ["id"], fks: ForeignKeyRow[] = []): TableMeta {
|
|
19
|
-
return { name,
|
|
27
|
+
return { name,
|
|
28
|
+
columns,
|
|
29
|
+
pks,
|
|
30
|
+
fks };
|
|
20
31
|
}
|
|
21
32
|
|
|
22
33
|
// ═══════════════════════════════════════════════════════════════════════
|
|
@@ -26,8 +37,10 @@ describe("generateCollectionFile", () => {
|
|
|
26
37
|
describe("basic property generation", () => {
|
|
27
38
|
it("generates a simple collection with slug, name, singularName and table", () => {
|
|
28
39
|
const meta = makeSimpleTable("products", [
|
|
29
|
-
mkCol("products", "id", { data_type: "uuid",
|
|
30
|
-
|
|
40
|
+
mkCol("products", "id", { data_type: "uuid",
|
|
41
|
+
udt_name: "uuid",
|
|
42
|
+
is_nullable: "NO" }),
|
|
43
|
+
mkCol("products", "name", { is_nullable: "NO" })
|
|
31
44
|
]);
|
|
32
45
|
const result = generateCollectionFile("products", meta, [], new Set(), new Map([["products", meta]]), new Map());
|
|
33
46
|
expect(result).toContain('slug: "products"');
|
|
@@ -38,14 +51,20 @@ describe("generateCollectionFile", () => {
|
|
|
38
51
|
|
|
39
52
|
it("generates correct property types from columns", () => {
|
|
40
53
|
const meta = makeSimpleTable("items", [
|
|
41
|
-
mkCol("items", "id", { data_type: "uuid",
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
mkCol("items", "
|
|
45
|
-
|
|
54
|
+
mkCol("items", "id", { data_type: "uuid",
|
|
55
|
+
udt_name: "uuid",
|
|
56
|
+
is_nullable: "NO" }),
|
|
57
|
+
mkCol("items", "count", { data_type: "integer",
|
|
58
|
+
udt_name: "int4" }),
|
|
59
|
+
mkCol("items", "active", { data_type: "boolean",
|
|
60
|
+
udt_name: "bool" }),
|
|
61
|
+
mkCol("items", "created_at", { data_type: "timestamp",
|
|
62
|
+
udt_name: "timestamp" }),
|
|
63
|
+
mkCol("items", "metadata", { data_type: "jsonb",
|
|
64
|
+
udt_name: "jsonb" })
|
|
46
65
|
]);
|
|
47
66
|
const result = generateCollectionFile("items", meta, [], new Set(), new Map([["items", meta]]), new Map());
|
|
48
|
-
expect(result).toContain('type: "string"');
|
|
67
|
+
expect(result).toContain('type: "string"'); // id
|
|
49
68
|
expect(result).toContain('type: "number"');
|
|
50
69
|
expect(result).toContain('type: "boolean"');
|
|
51
70
|
expect(result).toContain('type: "date"');
|
|
@@ -55,9 +74,11 @@ describe("generateCollectionFile", () => {
|
|
|
55
74
|
it("skips FK columns from properties (they become relations)", () => {
|
|
56
75
|
const fks = [mkFk("posts", "author_id", "users")];
|
|
57
76
|
const meta = makeSimpleTable("posts", [
|
|
58
|
-
mkCol("posts", "id", { data_type: "uuid",
|
|
77
|
+
mkCol("posts", "id", { data_type: "uuid",
|
|
78
|
+
udt_name: "uuid",
|
|
79
|
+
is_nullable: "NO" }),
|
|
59
80
|
mkCol("posts", "title", { is_nullable: "NO" }),
|
|
60
|
-
mkCol("posts", "author_id", { is_nullable: "NO" })
|
|
81
|
+
mkCol("posts", "author_id", { is_nullable: "NO" })
|
|
61
82
|
], ["id"], fks);
|
|
62
83
|
const result = generateCollectionFile("posts", meta, fks, new Set(), new Map([["posts", meta]]), new Map());
|
|
63
84
|
// author_id should NOT appear as a regular property
|
|
@@ -71,9 +92,11 @@ describe("generateCollectionFile", () => {
|
|
|
71
92
|
it("includes relation property key, not FK column name", () => {
|
|
72
93
|
const fks = [mkFk("posts", "author_id", "users")];
|
|
73
94
|
const meta = makeSimpleTable("posts", [
|
|
74
|
-
mkCol("posts", "id", { data_type: "uuid",
|
|
95
|
+
mkCol("posts", "id", { data_type: "uuid",
|
|
96
|
+
udt_name: "uuid",
|
|
97
|
+
is_nullable: "NO" }),
|
|
75
98
|
mkCol("posts", "title"),
|
|
76
|
-
mkCol("posts", "author_id")
|
|
99
|
+
mkCol("posts", "author_id")
|
|
77
100
|
], ["id"], fks);
|
|
78
101
|
const result = generateCollectionFile("posts", meta, fks, new Set(), new Map([["posts", meta]]), new Map());
|
|
79
102
|
// propertiesOrder should contain "author" (the relation key), not "author_id" (the FK column)
|
|
@@ -89,7 +112,9 @@ describe("generateCollectionFile", () => {
|
|
|
89
112
|
describe("ID detection", () => {
|
|
90
113
|
it("marks uuid PK as isId uuid", () => {
|
|
91
114
|
const meta = makeSimpleTable("users", [
|
|
92
|
-
mkCol("users", "id", { data_type: "uuid",
|
|
115
|
+
mkCol("users", "id", { data_type: "uuid",
|
|
116
|
+
udt_name: "uuid",
|
|
117
|
+
is_nullable: "NO" })
|
|
93
118
|
]);
|
|
94
119
|
const result = generateCollectionFile("users", meta, [], new Set(), new Map([["users", meta]]), new Map());
|
|
95
120
|
expect(result).toContain('isId: "uuid"');
|
|
@@ -97,7 +122,10 @@ describe("generateCollectionFile", () => {
|
|
|
97
122
|
|
|
98
123
|
it("marks integer PK as isId increment", () => {
|
|
99
124
|
const meta = makeSimpleTable("counters", [
|
|
100
|
-
mkCol("counters", "id", { data_type: "integer",
|
|
125
|
+
mkCol("counters", "id", { data_type: "integer",
|
|
126
|
+
udt_name: "int4",
|
|
127
|
+
is_nullable: "NO",
|
|
128
|
+
column_default: "nextval" })
|
|
101
129
|
]);
|
|
102
130
|
const result = generateCollectionFile("counters", meta, [], new Set(), new Map([["counters", meta]]), new Map());
|
|
103
131
|
expect(result).toContain('isId: "increment"');
|
|
@@ -105,9 +133,14 @@ describe("generateCollectionFile", () => {
|
|
|
105
133
|
|
|
106
134
|
it("flags composite primary keys with a comment", () => {
|
|
107
135
|
const meta = makeSimpleTable("scores", [
|
|
108
|
-
mkCol("scores", "user_id", { data_type: "uuid",
|
|
109
|
-
|
|
110
|
-
|
|
136
|
+
mkCol("scores", "user_id", { data_type: "uuid",
|
|
137
|
+
udt_name: "uuid",
|
|
138
|
+
is_nullable: "NO" }),
|
|
139
|
+
mkCol("scores", "game_id", { data_type: "uuid",
|
|
140
|
+
udt_name: "uuid",
|
|
141
|
+
is_nullable: "NO" }),
|
|
142
|
+
mkCol("scores", "score", { data_type: "integer",
|
|
143
|
+
udt_name: "int4" })
|
|
111
144
|
], ["user_id", "game_id"]);
|
|
112
145
|
const result = generateCollectionFile("scores", meta, [], new Set(), new Map([["scores", meta]]), new Map());
|
|
113
146
|
expect(result).toContain("composite primary key");
|
|
@@ -118,8 +151,10 @@ describe("generateCollectionFile", () => {
|
|
|
118
151
|
describe("validation.required", () => {
|
|
119
152
|
it("adds required when is_nullable is NO, not a PK, and no default", () => {
|
|
120
153
|
const meta = makeSimpleTable("items", [
|
|
121
|
-
mkCol("items", "id", { data_type: "uuid",
|
|
122
|
-
|
|
154
|
+
mkCol("items", "id", { data_type: "uuid",
|
|
155
|
+
udt_name: "uuid",
|
|
156
|
+
is_nullable: "NO" }),
|
|
157
|
+
mkCol("items", "name", { is_nullable: "NO" })
|
|
123
158
|
]);
|
|
124
159
|
const result = generateCollectionFile("items", meta, [], new Set(), new Map([["items", meta]]), new Map());
|
|
125
160
|
// name should have required
|
|
@@ -128,8 +163,10 @@ describe("generateCollectionFile", () => {
|
|
|
128
163
|
|
|
129
164
|
it("does NOT add required for nullable columns", () => {
|
|
130
165
|
const meta = makeSimpleTable("items", [
|
|
131
|
-
mkCol("items", "id", { data_type: "uuid",
|
|
132
|
-
|
|
166
|
+
mkCol("items", "id", { data_type: "uuid",
|
|
167
|
+
udt_name: "uuid",
|
|
168
|
+
is_nullable: "NO" }),
|
|
169
|
+
mkCol("items", "bio", { is_nullable: "YES" })
|
|
133
170
|
]);
|
|
134
171
|
const result = generateCollectionFile("items", meta, [], new Set(), new Map([["items", meta]]), new Map());
|
|
135
172
|
const bioSection = result.split("bio:")[1].split("},")[0];
|
|
@@ -138,8 +175,11 @@ describe("generateCollectionFile", () => {
|
|
|
138
175
|
|
|
139
176
|
it("does NOT add required for columns with defaults", () => {
|
|
140
177
|
const meta = makeSimpleTable("items", [
|
|
141
|
-
mkCol("items", "id", { data_type: "uuid",
|
|
142
|
-
|
|
178
|
+
mkCol("items", "id", { data_type: "uuid",
|
|
179
|
+
udt_name: "uuid",
|
|
180
|
+
is_nullable: "NO" }),
|
|
181
|
+
mkCol("items", "role", { is_nullable: "NO",
|
|
182
|
+
column_default: "'user'" })
|
|
143
183
|
]);
|
|
144
184
|
const result = generateCollectionFile("items", meta, [], new Set(), new Map([["items", meta]]), new Map());
|
|
145
185
|
const roleSection = result.split("role:")[1].split("},")[0];
|
|
@@ -151,11 +191,14 @@ describe("generateCollectionFile", () => {
|
|
|
151
191
|
it("generates enum for USER-DEFINED columns with matching enum", () => {
|
|
152
192
|
const enumMap = new Map([["order_status", ["pending", "shipped", "delivered"]]]);
|
|
153
193
|
const meta = makeSimpleTable("orders", [
|
|
154
|
-
mkCol("orders", "id", { data_type: "uuid",
|
|
155
|
-
|
|
194
|
+
mkCol("orders", "id", { data_type: "uuid",
|
|
195
|
+
udt_name: "uuid",
|
|
196
|
+
is_nullable: "NO" }),
|
|
197
|
+
mkCol("orders", "status", { data_type: "USER-DEFINED",
|
|
198
|
+
udt_name: "order_status" })
|
|
156
199
|
]);
|
|
157
200
|
const result = generateCollectionFile("orders", meta, [], new Set(), new Map([["orders", meta]]), enumMap);
|
|
158
|
-
expect(result).toContain(
|
|
201
|
+
expect(result).toContain("enum:");
|
|
159
202
|
expect(result).toContain('{ id: "pending", label: "Pending" }');
|
|
160
203
|
expect(result).toContain('{ id: "shipped", label: "Shipped" }');
|
|
161
204
|
expect(result).toContain('{ id: "delivered", label: "Delivered" }');
|
|
@@ -164,8 +207,11 @@ describe("generateCollectionFile", () => {
|
|
|
164
207
|
|
|
165
208
|
it("does NOT add enum for USER-DEFINED without matching enum", () => {
|
|
166
209
|
const meta = makeSimpleTable("things", [
|
|
167
|
-
mkCol("things", "id", { data_type: "uuid",
|
|
168
|
-
|
|
210
|
+
mkCol("things", "id", { data_type: "uuid",
|
|
211
|
+
udt_name: "uuid",
|
|
212
|
+
is_nullable: "NO" }),
|
|
213
|
+
mkCol("things", "geom", { data_type: "USER-DEFINED",
|
|
214
|
+
udt_name: "geometry" })
|
|
169
215
|
]);
|
|
170
216
|
const result = generateCollectionFile("things", meta, [], new Set(), new Map([["things", meta]]), new Map());
|
|
171
217
|
expect(result).not.toContain("enum");
|
|
@@ -174,8 +220,11 @@ describe("generateCollectionFile", () => {
|
|
|
174
220
|
it("humanizes enum value labels with underscores", () => {
|
|
175
221
|
const enumMap = new Map([["my_enum", ["in_progress", "on_hold"]]]);
|
|
176
222
|
const meta = makeSimpleTable("tasks", [
|
|
177
|
-
mkCol("tasks", "id", { data_type: "uuid",
|
|
178
|
-
|
|
223
|
+
mkCol("tasks", "id", { data_type: "uuid",
|
|
224
|
+
udt_name: "uuid",
|
|
225
|
+
is_nullable: "NO" }),
|
|
226
|
+
mkCol("tasks", "state", { data_type: "USER-DEFINED",
|
|
227
|
+
udt_name: "my_enum" })
|
|
179
228
|
]);
|
|
180
229
|
const result = generateCollectionFile("tasks", meta, [], new Set(), new Map([["tasks", meta]]), enumMap);
|
|
181
230
|
expect(result).toContain('label: "In Progress"');
|
|
@@ -186,8 +235,11 @@ describe("generateCollectionFile", () => {
|
|
|
186
235
|
describe("date auto-value heuristics", () => {
|
|
187
236
|
it("sets autoValue on_create for created_at", () => {
|
|
188
237
|
const meta = makeSimpleTable("items", [
|
|
189
|
-
mkCol("items", "id", { data_type: "uuid",
|
|
190
|
-
|
|
238
|
+
mkCol("items", "id", { data_type: "uuid",
|
|
239
|
+
udt_name: "uuid",
|
|
240
|
+
is_nullable: "NO" }),
|
|
241
|
+
mkCol("items", "created_at", { data_type: "timestamp",
|
|
242
|
+
udt_name: "timestamp" })
|
|
191
243
|
]);
|
|
192
244
|
const result = generateCollectionFile("items", meta, [], new Set(), new Map([["items", meta]]), new Map());
|
|
193
245
|
expect(result).toContain('autoValue: "on_create"');
|
|
@@ -196,8 +248,11 @@ describe("generateCollectionFile", () => {
|
|
|
196
248
|
|
|
197
249
|
it("sets autoValue on_update for updated_at", () => {
|
|
198
250
|
const meta = makeSimpleTable("items", [
|
|
199
|
-
mkCol("items", "id", { data_type: "uuid",
|
|
200
|
-
|
|
251
|
+
mkCol("items", "id", { data_type: "uuid",
|
|
252
|
+
udt_name: "uuid",
|
|
253
|
+
is_nullable: "NO" }),
|
|
254
|
+
mkCol("items", "updated_at", { data_type: "timestamp",
|
|
255
|
+
udt_name: "timestamp" })
|
|
201
256
|
]);
|
|
202
257
|
const result = generateCollectionFile("items", meta, [], new Set(), new Map([["items", meta]]), new Map());
|
|
203
258
|
expect(result).toContain('autoValue: "on_update"');
|
|
@@ -205,8 +260,12 @@ describe("generateCollectionFile", () => {
|
|
|
205
260
|
|
|
206
261
|
it("sets autoValue on_create for columns with now() default", () => {
|
|
207
262
|
const meta = makeSimpleTable("items", [
|
|
208
|
-
mkCol("items", "id", { data_type: "uuid",
|
|
209
|
-
|
|
263
|
+
mkCol("items", "id", { data_type: "uuid",
|
|
264
|
+
udt_name: "uuid",
|
|
265
|
+
is_nullable: "NO" }),
|
|
266
|
+
mkCol("items", "published_at", { data_type: "timestamp",
|
|
267
|
+
udt_name: "timestamp",
|
|
268
|
+
column_default: "now()" })
|
|
210
269
|
]);
|
|
211
270
|
const result = generateCollectionFile("items", meta, [], new Set(), new Map([["items", meta]]), new Map());
|
|
212
271
|
expect(result).toContain('autoValue: "on_create"');
|
|
@@ -217,8 +276,10 @@ describe("generateCollectionFile", () => {
|
|
|
217
276
|
it("adds storage config for image-like column names", () => {
|
|
218
277
|
for (const name of ["profile_image", "avatar", "photo_url", "logo", "cover_image"]) {
|
|
219
278
|
const meta = makeSimpleTable("t", [
|
|
220
|
-
mkCol("t", "id", { data_type: "uuid",
|
|
221
|
-
|
|
279
|
+
mkCol("t", "id", { data_type: "uuid",
|
|
280
|
+
udt_name: "uuid",
|
|
281
|
+
is_nullable: "NO" }),
|
|
282
|
+
mkCol("t", name)
|
|
222
283
|
]);
|
|
223
284
|
const result = generateCollectionFile("t", meta, [], new Set(), new Map([["t", meta]]), new Map());
|
|
224
285
|
expect(result).toContain("storagePath:");
|
|
@@ -228,8 +289,10 @@ describe("generateCollectionFile", () => {
|
|
|
228
289
|
it("adds multiline for description/summary/excerpt", () => {
|
|
229
290
|
for (const name of ["description", "summary", "excerpt"]) {
|
|
230
291
|
const meta = makeSimpleTable("t", [
|
|
231
|
-
mkCol("t", "id", { data_type: "uuid",
|
|
232
|
-
|
|
292
|
+
mkCol("t", "id", { data_type: "uuid",
|
|
293
|
+
udt_name: "uuid",
|
|
294
|
+
is_nullable: "NO" }),
|
|
295
|
+
mkCol("t", name)
|
|
233
296
|
]);
|
|
234
297
|
const result = generateCollectionFile("t", meta, [], new Set(), new Map([["t", meta]]), new Map());
|
|
235
298
|
expect(result).toContain("multiline: true");
|
|
@@ -239,8 +302,10 @@ describe("generateCollectionFile", () => {
|
|
|
239
302
|
it("adds markdown for content/body", () => {
|
|
240
303
|
for (const name of ["content", "body"]) {
|
|
241
304
|
const meta = makeSimpleTable("t", [
|
|
242
|
-
mkCol("t", "id", { data_type: "uuid",
|
|
243
|
-
|
|
305
|
+
mkCol("t", "id", { data_type: "uuid",
|
|
306
|
+
udt_name: "uuid",
|
|
307
|
+
is_nullable: "NO" }),
|
|
308
|
+
mkCol("t", name)
|
|
244
309
|
]);
|
|
245
310
|
const result = generateCollectionFile("t", meta, [], new Set(), new Map([["t", meta]]), new Map());
|
|
246
311
|
expect(result).toContain("markdown: true");
|
|
@@ -249,8 +314,11 @@ describe("generateCollectionFile", () => {
|
|
|
249
314
|
|
|
250
315
|
it("adds multiline for text data_type columns", () => {
|
|
251
316
|
const meta = makeSimpleTable("t", [
|
|
252
|
-
mkCol("t", "id", { data_type: "uuid",
|
|
253
|
-
|
|
317
|
+
mkCol("t", "id", { data_type: "uuid",
|
|
318
|
+
udt_name: "uuid",
|
|
319
|
+
is_nullable: "NO" }),
|
|
320
|
+
mkCol("t", "notes", { data_type: "text",
|
|
321
|
+
udt_name: "text" })
|
|
254
322
|
]);
|
|
255
323
|
const result = generateCollectionFile("t", meta, [], new Set(), new Map([["t", meta]]), new Map());
|
|
256
324
|
expect(result).toContain("multiline: true");
|
|
@@ -259,8 +327,11 @@ describe("generateCollectionFile", () => {
|
|
|
259
327
|
it("does NOT apply string heuristics to enum columns", () => {
|
|
260
328
|
const enumMap = new Map([["img_type", ["png", "jpg"]]]);
|
|
261
329
|
const meta = makeSimpleTable("t", [
|
|
262
|
-
mkCol("t", "id", { data_type: "uuid",
|
|
263
|
-
|
|
330
|
+
mkCol("t", "id", { data_type: "uuid",
|
|
331
|
+
udt_name: "uuid",
|
|
332
|
+
is_nullable: "NO" }),
|
|
333
|
+
mkCol("t", "image_type", { data_type: "USER-DEFINED",
|
|
334
|
+
udt_name: "img_type" })
|
|
264
335
|
]);
|
|
265
336
|
const result = generateCollectionFile("t", meta, [], new Set(), new Map([["t", meta]]), enumMap);
|
|
266
337
|
expect(result).not.toContain("storagePath");
|
|
@@ -272,12 +343,14 @@ describe("generateCollectionFile", () => {
|
|
|
272
343
|
it("generates a one-to-one owning relation with correct import", () => {
|
|
273
344
|
const fks = [mkFk("posts", "author_id", "users")];
|
|
274
345
|
const meta = makeSimpleTable("posts", [
|
|
275
|
-
mkCol("posts", "id", { data_type: "uuid",
|
|
276
|
-
|
|
346
|
+
mkCol("posts", "id", { data_type: "uuid",
|
|
347
|
+
udt_name: "uuid",
|
|
348
|
+
is_nullable: "NO" }),
|
|
349
|
+
mkCol("posts", "author_id")
|
|
277
350
|
], ["id"], fks);
|
|
278
351
|
const result = generateCollectionFile("posts", meta, fks, new Set(), new Map([["posts", meta]]), new Map());
|
|
279
352
|
expect(result).toContain('import usersCollection from "./users"');
|
|
280
|
-
expect(result).toContain(
|
|
353
|
+
expect(result).toContain("author: {");
|
|
281
354
|
expect(result).toContain('cardinality: "one"');
|
|
282
355
|
expect(result).toContain('direction: "owning"');
|
|
283
356
|
expect(result).toContain('localKey: "author_id"');
|
|
@@ -288,26 +361,30 @@ describe("generateCollectionFile", () => {
|
|
|
288
361
|
it("generates a one-to-many inverse relation in the relations array", () => {
|
|
289
362
|
const allFks: ForeignKeyRow[] = [mkFk("comments", "post_id", "posts")];
|
|
290
363
|
const meta = makeSimpleTable("posts", [
|
|
291
|
-
mkCol("posts", "id", { data_type: "uuid",
|
|
364
|
+
mkCol("posts", "id", { data_type: "uuid",
|
|
365
|
+
udt_name: "uuid",
|
|
366
|
+
is_nullable: "NO" })
|
|
292
367
|
]);
|
|
293
368
|
const result = generateCollectionFile("posts", meta, allFks, new Set(), new Map([["posts", meta]]), new Map());
|
|
294
369
|
expect(result).toContain('import commentsCollection from "./comments"');
|
|
295
370
|
// Should be in the relations array, not as an inline property
|
|
296
|
-
expect(result).toContain(
|
|
371
|
+
expect(result).toContain("relations: [");
|
|
297
372
|
expect(result).toContain('relationName: "comments"');
|
|
298
373
|
expect(result).toContain('cardinality: "many"');
|
|
299
374
|
expect(result).toContain('direction: "inverse"');
|
|
300
375
|
expect(result).toContain('inverseRelationName: "post"');
|
|
301
376
|
expect(result).toContain('foreignKeyOnTarget: "post_id"');
|
|
302
377
|
// Should NOT appear as an inline property with type: "relation"
|
|
303
|
-
const propsSection = result.split(
|
|
378
|
+
const propsSection = result.split("properties:")[1].split("relations:")[0];
|
|
304
379
|
expect(propsSection).not.toContain('"relation"');
|
|
305
380
|
});
|
|
306
381
|
|
|
307
382
|
it("does NOT include inverse relations in propertiesOrder", () => {
|
|
308
383
|
const allFks: ForeignKeyRow[] = [mkFk("comments", "post_id", "posts")];
|
|
309
384
|
const meta = makeSimpleTable("posts", [
|
|
310
|
-
mkCol("posts", "id", { data_type: "uuid",
|
|
385
|
+
mkCol("posts", "id", { data_type: "uuid",
|
|
386
|
+
udt_name: "uuid",
|
|
387
|
+
is_nullable: "NO" })
|
|
311
388
|
]);
|
|
312
389
|
const result = generateCollectionFile("posts", meta, allFks, new Set(), new Map([["posts", meta]]), new Map());
|
|
313
390
|
const orderMatch = result.match(/propertiesOrder:\s*(\[[\s\S]*?\])/);
|
|
@@ -321,21 +398,24 @@ describe("generateCollectionFile", () => {
|
|
|
321
398
|
it("generates owning M2M with through config in relations array", () => {
|
|
322
399
|
const jtFks: ForeignKeyRow[] = [
|
|
323
400
|
mkFk("articles_tags", "article_id", "articles"),
|
|
324
|
-
mkFk("articles_tags", "tag_id", "tags")
|
|
401
|
+
mkFk("articles_tags", "tag_id", "tags")
|
|
325
402
|
];
|
|
326
403
|
const jtMeta: TableMeta = {
|
|
327
|
-
name: "articles_tags",
|
|
404
|
+
name: "articles_tags",
|
|
405
|
+
pks: [],
|
|
328
406
|
columns: [mkCol("articles_tags", "article_id"), mkCol("articles_tags", "tag_id")],
|
|
329
|
-
fks: jtFks
|
|
407
|
+
fks: jtFks
|
|
330
408
|
};
|
|
331
409
|
const articlesMeta = makeSimpleTable("articles", [
|
|
332
|
-
mkCol("articles", "id", { data_type: "uuid",
|
|
410
|
+
mkCol("articles", "id", { data_type: "uuid",
|
|
411
|
+
udt_name: "uuid",
|
|
412
|
+
is_nullable: "NO" })
|
|
333
413
|
]);
|
|
334
414
|
const tablesMap = new Map([["articles", articlesMeta], ["articles_tags", jtMeta]]);
|
|
335
415
|
const joinTables = new Set(["articles_tags"]);
|
|
336
416
|
|
|
337
417
|
const result = generateCollectionFile("articles", articlesMeta, [], joinTables, tablesMap, new Map());
|
|
338
|
-
expect(result).toContain(
|
|
418
|
+
expect(result).toContain("relations: [");
|
|
339
419
|
expect(result).toContain('relationName: "tags"');
|
|
340
420
|
expect(result).toContain('direction: "owning"');
|
|
341
421
|
expect(result).toContain('table: "articles_tags"');
|
|
@@ -346,21 +426,24 @@ describe("generateCollectionFile", () => {
|
|
|
346
426
|
it("generates inverse M2M in relations array", () => {
|
|
347
427
|
const jtFks: ForeignKeyRow[] = [
|
|
348
428
|
mkFk("articles_tags", "article_id", "articles"),
|
|
349
|
-
mkFk("articles_tags", "tag_id", "tags")
|
|
429
|
+
mkFk("articles_tags", "tag_id", "tags")
|
|
350
430
|
];
|
|
351
431
|
const jtMeta: TableMeta = {
|
|
352
|
-
name: "articles_tags",
|
|
432
|
+
name: "articles_tags",
|
|
433
|
+
pks: [],
|
|
353
434
|
columns: [mkCol("articles_tags", "article_id"), mkCol("articles_tags", "tag_id")],
|
|
354
|
-
fks: jtFks
|
|
435
|
+
fks: jtFks
|
|
355
436
|
};
|
|
356
437
|
const tagsMeta = makeSimpleTable("tags", [
|
|
357
|
-
mkCol("tags", "id", { data_type: "uuid",
|
|
438
|
+
mkCol("tags", "id", { data_type: "uuid",
|
|
439
|
+
udt_name: "uuid",
|
|
440
|
+
is_nullable: "NO" })
|
|
358
441
|
]);
|
|
359
442
|
const tablesMap = new Map([["tags", tagsMeta], ["articles_tags", jtMeta]]);
|
|
360
443
|
const joinTables = new Set(["articles_tags"]);
|
|
361
444
|
|
|
362
445
|
const result = generateCollectionFile("tags", tagsMeta, [], joinTables, tablesMap, new Map());
|
|
363
|
-
expect(result).toContain(
|
|
446
|
+
expect(result).toContain("relations: [");
|
|
364
447
|
expect(result).toContain('direction: "inverse"');
|
|
365
448
|
});
|
|
366
449
|
});
|
|
@@ -369,21 +452,24 @@ describe("generateCollectionFile", () => {
|
|
|
369
452
|
it("generates self-ref M2M with _via_ relation name in relations array", () => {
|
|
370
453
|
const jtFks: ForeignKeyRow[] = [
|
|
371
454
|
mkFk("user_friends", "user_id", "users"),
|
|
372
|
-
mkFk("user_friends", "friend_id", "users")
|
|
455
|
+
mkFk("user_friends", "friend_id", "users")
|
|
373
456
|
];
|
|
374
457
|
const jtMeta: TableMeta = {
|
|
375
|
-
name: "user_friends",
|
|
458
|
+
name: "user_friends",
|
|
459
|
+
pks: [],
|
|
376
460
|
columns: [mkCol("user_friends", "user_id"), mkCol("user_friends", "friend_id")],
|
|
377
|
-
fks: jtFks
|
|
461
|
+
fks: jtFks
|
|
378
462
|
};
|
|
379
463
|
const usersMeta = makeSimpleTable("users", [
|
|
380
|
-
mkCol("users", "id", { data_type: "uuid",
|
|
464
|
+
mkCol("users", "id", { data_type: "uuid",
|
|
465
|
+
udt_name: "uuid",
|
|
466
|
+
is_nullable: "NO" })
|
|
381
467
|
]);
|
|
382
468
|
const tablesMap = new Map([["users", usersMeta], ["user_friends", jtMeta]]);
|
|
383
469
|
const joinTables = new Set(["user_friends"]);
|
|
384
470
|
|
|
385
471
|
const result = generateCollectionFile("users", usersMeta, [], joinTables, tablesMap, new Map());
|
|
386
|
-
expect(result).toContain(
|
|
472
|
+
expect(result).toContain("relations: [");
|
|
387
473
|
expect(result).toContain('relationName: "users_via_friend"');
|
|
388
474
|
expect(result).toContain('table: "user_friends"');
|
|
389
475
|
expect(result).toContain('sourceColumn: "user_id"');
|
|
@@ -396,7 +482,9 @@ describe("generateCollectionFile", () => {
|
|
|
396
482
|
describe("icon mapping", () => {
|
|
397
483
|
it("assigns correct icons based on table name", () => {
|
|
398
484
|
const make = (name: string) => {
|
|
399
|
-
const meta = makeSimpleTable(name, [mkCol(name, "id", { data_type: "uuid",
|
|
485
|
+
const meta = makeSimpleTable(name, [mkCol(name, "id", { data_type: "uuid",
|
|
486
|
+
udt_name: "uuid",
|
|
487
|
+
is_nullable: "NO" })]);
|
|
400
488
|
return generateCollectionFile(name, meta, [], new Set(), new Map([[name, meta]]), new Map());
|
|
401
489
|
};
|
|
402
490
|
expect(make("users")).toContain('icon: "Users"');
|
|
@@ -411,7 +499,9 @@ describe("generateCollectionFile", () => {
|
|
|
411
499
|
describe("humanized names", () => {
|
|
412
500
|
it("uses Title Case for collection name from snake_case table", () => {
|
|
413
501
|
const meta = makeSimpleTable("user_profiles", [
|
|
414
|
-
mkCol("user_profiles", "id", { data_type: "uuid",
|
|
502
|
+
mkCol("user_profiles", "id", { data_type: "uuid",
|
|
503
|
+
udt_name: "uuid",
|
|
504
|
+
is_nullable: "NO" })
|
|
415
505
|
]);
|
|
416
506
|
const result = generateCollectionFile("user_profiles", meta, [], new Set(), new Map([["user_profiles", meta]]), new Map());
|
|
417
507
|
expect(result).toContain('name: "User Profiles"');
|
|
@@ -420,8 +510,10 @@ describe("generateCollectionFile", () => {
|
|
|
420
510
|
|
|
421
511
|
it("humanizes property names", () => {
|
|
422
512
|
const meta = makeSimpleTable("t", [
|
|
423
|
-
mkCol("t", "id", { data_type: "uuid",
|
|
424
|
-
|
|
513
|
+
mkCol("t", "id", { data_type: "uuid",
|
|
514
|
+
udt_name: "uuid",
|
|
515
|
+
is_nullable: "NO" }),
|
|
516
|
+
mkCol("t", "first_name")
|
|
425
517
|
]);
|
|
426
518
|
const result = generateCollectionFile("t", meta, [], new Set(), new Map([["t", meta]]), new Map());
|
|
427
519
|
expect(result).toContain('name: "First Name"');
|
|
@@ -430,7 +522,9 @@ describe("generateCollectionFile", () => {
|
|
|
430
522
|
|
|
431
523
|
describe("import generation", () => {
|
|
432
524
|
it("always imports PostgresCollection", () => {
|
|
433
|
-
const meta = makeSimpleTable("t", [mkCol("t", "id", { data_type: "uuid",
|
|
525
|
+
const meta = makeSimpleTable("t", [mkCol("t", "id", { data_type: "uuid",
|
|
526
|
+
udt_name: "uuid",
|
|
527
|
+
is_nullable: "NO" })]);
|
|
434
528
|
const result = generateCollectionFile("t", meta, [], new Set(), new Map([["t", meta]]), new Map());
|
|
435
529
|
expect(result).toContain('import { PostgresCollection } from "@rebasepro/types"');
|
|
436
530
|
});
|
|
@@ -438,9 +532,11 @@ describe("generateCollectionFile", () => {
|
|
|
438
532
|
it("does not duplicate imports for multiple relations to same table", () => {
|
|
439
533
|
const fks = [mkFk("posts", "author_id", "users"), mkFk("posts", "reviewer_id", "users")];
|
|
440
534
|
const meta = makeSimpleTable("posts", [
|
|
441
|
-
mkCol("posts", "id", { data_type: "uuid",
|
|
535
|
+
mkCol("posts", "id", { data_type: "uuid",
|
|
536
|
+
udt_name: "uuid",
|
|
537
|
+
is_nullable: "NO" }),
|
|
442
538
|
mkCol("posts", "author_id"),
|
|
443
|
-
mkCol("posts", "reviewer_id")
|
|
539
|
+
mkCol("posts", "reviewer_id")
|
|
444
540
|
], ["id"], fks);
|
|
445
541
|
const result = generateCollectionFile("posts", meta, fks, new Set(), new Map([["posts", meta]]), new Map());
|
|
446
542
|
const importMatches = result.match(/import usersCollection/g);
|
|
@@ -450,7 +546,9 @@ describe("generateCollectionFile", () => {
|
|
|
450
546
|
|
|
451
547
|
describe("export default", () => {
|
|
452
548
|
it("exports the collection variable as default", () => {
|
|
453
|
-
const meta = makeSimpleTable("orders", [mkCol("orders", "id", { data_type: "uuid",
|
|
549
|
+
const meta = makeSimpleTable("orders", [mkCol("orders", "id", { data_type: "uuid",
|
|
550
|
+
udt_name: "uuid",
|
|
551
|
+
is_nullable: "NO" })]);
|
|
454
552
|
const result = generateCollectionFile("orders", meta, [], new Set(), new Map([["orders", meta]]), new Map());
|
|
455
553
|
expect(result).toContain("export default ordersCollection;");
|
|
456
554
|
});
|