@rebasepro/server-postgresql 0.0.1-canary.4d4fb3e
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/LICENSE +6 -0
- package/README.md +106 -0
- package/build-errors.txt +37 -0
- package/dist/common/src/collections/CollectionRegistry.d.ts +48 -0
- package/dist/common/src/collections/index.d.ts +1 -0
- package/dist/common/src/data/buildRebaseData.d.ts +14 -0
- package/dist/common/src/index.d.ts +3 -0
- package/dist/common/src/util/builders.d.ts +57 -0
- package/dist/common/src/util/callbacks.d.ts +6 -0
- package/dist/common/src/util/collections.d.ts +11 -0
- package/dist/common/src/util/common.d.ts +2 -0
- package/dist/common/src/util/conditions.d.ts +26 -0
- package/dist/common/src/util/entities.d.ts +36 -0
- package/dist/common/src/util/enums.d.ts +3 -0
- package/dist/common/src/util/index.d.ts +16 -0
- package/dist/common/src/util/navigation_from_path.d.ts +34 -0
- package/dist/common/src/util/navigation_utils.d.ts +20 -0
- package/dist/common/src/util/parent_references_from_path.d.ts +6 -0
- package/dist/common/src/util/paths.d.ts +14 -0
- package/dist/common/src/util/permissions.d.ts +5 -0
- package/dist/common/src/util/references.d.ts +2 -0
- package/dist/common/src/util/relations.d.ts +12 -0
- package/dist/common/src/util/resolutions.d.ts +72 -0
- package/dist/common/src/util/storage.d.ts +24 -0
- package/dist/index.es.js +10635 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.umd.js +10643 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/server-postgresql/src/PostgresBackendDriver.d.ts +112 -0
- package/dist/server-postgresql/src/PostgresBootstrapper.d.ts +40 -0
- package/dist/server-postgresql/src/auth/ensure-tables.d.ts +6 -0
- package/dist/server-postgresql/src/auth/services.d.ts +188 -0
- package/dist/server-postgresql/src/cli.d.ts +1 -0
- package/dist/server-postgresql/src/collections/PostgresCollectionRegistry.d.ts +43 -0
- package/dist/server-postgresql/src/connection.d.ts +7 -0
- package/dist/server-postgresql/src/data-transformer.d.ts +36 -0
- package/dist/server-postgresql/src/databasePoolManager.d.ts +20 -0
- package/dist/server-postgresql/src/history/HistoryService.d.ts +71 -0
- package/dist/server-postgresql/src/history/ensure-history-table.d.ts +7 -0
- package/dist/server-postgresql/src/index.d.ts +13 -0
- package/dist/server-postgresql/src/interfaces.d.ts +18 -0
- package/dist/server-postgresql/src/schema/auth-schema.d.ts +767 -0
- package/dist/server-postgresql/src/schema/generate-drizzle-schema-logic.d.ts +2 -0
- package/dist/server-postgresql/src/schema/generate-drizzle-schema.d.ts +1 -0
- package/dist/server-postgresql/src/services/BranchService.d.ts +47 -0
- package/dist/server-postgresql/src/services/EntityFetchService.d.ts +195 -0
- package/dist/server-postgresql/src/services/EntityPersistService.d.ts +41 -0
- package/dist/server-postgresql/src/services/RelationService.d.ts +92 -0
- package/dist/server-postgresql/src/services/entity-helpers.d.ts +24 -0
- package/dist/server-postgresql/src/services/entityService.d.ts +102 -0
- package/dist/server-postgresql/src/services/index.d.ts +4 -0
- package/dist/server-postgresql/src/services/realtimeService.d.ts +186 -0
- package/dist/server-postgresql/src/utils/drizzle-conditions.d.ts +116 -0
- package/dist/server-postgresql/src/websocket.d.ts +5 -0
- package/dist/types/src/controllers/analytics_controller.d.ts +7 -0
- package/dist/types/src/controllers/auth.d.ts +117 -0
- package/dist/types/src/controllers/client.d.ts +58 -0
- package/dist/types/src/controllers/collection_registry.d.ts +44 -0
- package/dist/types/src/controllers/customization_controller.d.ts +54 -0
- package/dist/types/src/controllers/data.d.ts +141 -0
- package/dist/types/src/controllers/data_driver.d.ts +168 -0
- package/dist/types/src/controllers/database_admin.d.ts +11 -0
- package/dist/types/src/controllers/dialogs_controller.d.ts +36 -0
- package/dist/types/src/controllers/effective_role.d.ts +4 -0
- package/dist/types/src/controllers/index.d.ts +17 -0
- package/dist/types/src/controllers/local_config_persistence.d.ts +20 -0
- package/dist/types/src/controllers/navigation.d.ts +213 -0
- package/dist/types/src/controllers/registry.d.ts +51 -0
- package/dist/types/src/controllers/side_dialogs_controller.d.ts +67 -0
- package/dist/types/src/controllers/side_entity_controller.d.ts +89 -0
- package/dist/types/src/controllers/snackbar.d.ts +24 -0
- package/dist/types/src/controllers/storage.d.ts +173 -0
- package/dist/types/src/index.d.ts +4 -0
- package/dist/types/src/rebase_context.d.ts +101 -0
- package/dist/types/src/types/backend.d.ts +533 -0
- package/dist/types/src/types/builders.d.ts +14 -0
- package/dist/types/src/types/chips.d.ts +5 -0
- package/dist/types/src/types/collections.d.ts +812 -0
- package/dist/types/src/types/data_source.d.ts +64 -0
- package/dist/types/src/types/entities.d.ts +145 -0
- package/dist/types/src/types/entity_actions.d.ts +98 -0
- package/dist/types/src/types/entity_callbacks.d.ts +173 -0
- package/dist/types/src/types/entity_link_builder.d.ts +7 -0
- package/dist/types/src/types/entity_overrides.d.ts +9 -0
- package/dist/types/src/types/entity_views.d.ts +61 -0
- package/dist/types/src/types/export_import.d.ts +21 -0
- package/dist/types/src/types/index.d.ts +22 -0
- package/dist/types/src/types/locales.d.ts +4 -0
- package/dist/types/src/types/modify_collections.d.ts +5 -0
- package/dist/types/src/types/plugins.d.ts +225 -0
- package/dist/types/src/types/properties.d.ts +1091 -0
- package/dist/types/src/types/property_config.d.ts +70 -0
- package/dist/types/src/types/relations.d.ts +336 -0
- package/dist/types/src/types/slots.d.ts +228 -0
- package/dist/types/src/types/translations.d.ts +826 -0
- package/dist/types/src/types/user_management_delegate.d.ts +120 -0
- package/dist/types/src/types/websockets.d.ts +78 -0
- package/dist/types/src/users/index.d.ts +2 -0
- package/dist/types/src/users/roles.d.ts +22 -0
- package/dist/types/src/users/user.d.ts +46 -0
- package/jest-all.log +3128 -0
- package/jest.log +49 -0
- package/package.json +93 -0
- package/src/PostgresBackendDriver.ts +1024 -0
- package/src/PostgresBootstrapper.ts +232 -0
- package/src/auth/ensure-tables.ts +309 -0
- package/src/auth/services.ts +740 -0
- package/src/cli.ts +347 -0
- package/src/collections/PostgresCollectionRegistry.ts +96 -0
- package/src/connection.ts +62 -0
- package/src/data-transformer.ts +569 -0
- package/src/databasePoolManager.ts +84 -0
- package/src/history/HistoryService.ts +257 -0
- package/src/history/ensure-history-table.ts +45 -0
- package/src/index.ts +13 -0
- package/src/interfaces.ts +60 -0
- package/src/schema/auth-schema.ts +146 -0
- package/src/schema/generate-drizzle-schema-logic.ts +618 -0
- package/src/schema/generate-drizzle-schema.ts +151 -0
- package/src/services/BranchService.ts +237 -0
- package/src/services/EntityFetchService.ts +1447 -0
- package/src/services/EntityPersistService.ts +351 -0
- package/src/services/RelationService.ts +1012 -0
- package/src/services/entity-helpers.ts +121 -0
- package/src/services/entityService.ts +209 -0
- package/src/services/index.ts +13 -0
- package/src/services/realtimeService.ts +1005 -0
- package/src/utils/drizzle-conditions.ts +999 -0
- package/src/websocket.ts +487 -0
- package/test/auth-services.test.ts +569 -0
- package/test/branchService.test.ts +357 -0
- package/test/drizzle-conditions.test.ts +895 -0
- package/test/entityService.errors.test.ts +352 -0
- package/test/entityService.relations.test.ts +912 -0
- package/test/entityService.subcollection-search.test.ts +516 -0
- package/test/entityService.test.ts +977 -0
- package/test/generate-drizzle-schema.test.ts +795 -0
- package/test/historyService.test.ts +126 -0
- package/test/postgresDataDriver.test.ts +556 -0
- package/test/realtimeService.test.ts +276 -0
- package/test/relations.test.ts +662 -0
- package/test_drizzle_mock.js +3 -0
- package/test_find_changed.mjs +30 -0
- package/test_output.txt +3145 -0
- package/tsconfig.json +49 -0
- package/tsconfig.prod.json +20 -0
- package/vite.config.ts +82 -0
|
@@ -0,0 +1,795 @@
|
|
|
1
|
+
import { EntityCollection } from "@rebasepro/types";
|
|
2
|
+
import { generateSchema } from "../src/schema/generate-drizzle-schema-logic";
|
|
3
|
+
|
|
4
|
+
describe("generateDrizzleSchema", () => {
|
|
5
|
+
|
|
6
|
+
// Helper to remove comments and excessive whitespace for easier comparison
|
|
7
|
+
const cleanSchema = (schema: string) => {
|
|
8
|
+
return schema
|
|
9
|
+
.replace(/\/\/.*$/gm, "") // Remove single-line comments
|
|
10
|
+
.replace(/\/\*[\s\S]*?\*\//g, "") // Remove multi-line comments
|
|
11
|
+
.replace(/\n{2,}/g, "\n") // Collapse multiple newlines
|
|
12
|
+
.replace(/\s+/g, " ") // Collapse whitespace
|
|
13
|
+
.trim();
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
it("should generate a simple table with basic types", async () => {
|
|
17
|
+
const collections: EntityCollection[] = [
|
|
18
|
+
{
|
|
19
|
+
slug: "products",
|
|
20
|
+
table: "products",
|
|
21
|
+
name: "Products",
|
|
22
|
+
properties: {
|
|
23
|
+
name: { type: "string", validation: { required: true } },
|
|
24
|
+
price: { type: "number" },
|
|
25
|
+
available: { type: "boolean" },
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const result = await generateSchema(collections);
|
|
31
|
+
const cleanResult = cleanSchema(result);
|
|
32
|
+
|
|
33
|
+
expect(cleanResult).toContain("export const products = pgTable(\"products\", {");
|
|
34
|
+
expect(cleanResult).toContain("id: varchar(\"id\").primaryKey()");
|
|
35
|
+
expect(cleanResult).toContain("name: varchar(\"name\").notNull(),");
|
|
36
|
+
expect(cleanResult).toContain("price: numeric(\"price\"),");
|
|
37
|
+
expect(cleanResult).toContain("available: boolean(\"available\")");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should generate a one-to-many relation correctly", async () => {
|
|
41
|
+
const usersCollection: EntityCollection = {
|
|
42
|
+
slug: "users",
|
|
43
|
+
table: "users",
|
|
44
|
+
name: "Users",
|
|
45
|
+
properties: {
|
|
46
|
+
name: { type: "string" }
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const postsCollection: EntityCollection = {
|
|
50
|
+
slug: "posts",
|
|
51
|
+
table: "posts",
|
|
52
|
+
name: "Posts",
|
|
53
|
+
properties: {
|
|
54
|
+
title: { type: "string" },
|
|
55
|
+
author: { type: "relation", relationName: "author" }
|
|
56
|
+
},
|
|
57
|
+
relations: [
|
|
58
|
+
{
|
|
59
|
+
relationName: "author",
|
|
60
|
+
target: () => usersCollection,
|
|
61
|
+
cardinality: "one",
|
|
62
|
+
localKey: "author_id",
|
|
63
|
+
onDelete: "set null"
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const result = await generateSchema([usersCollection, postsCollection]);
|
|
69
|
+
const cleanResult = cleanSchema(result);
|
|
70
|
+
|
|
71
|
+
expect(cleanResult).toContain("author_id: varchar(\"author_id\").references(() => users.id, { onDelete: \"set null\" })");
|
|
72
|
+
const expectedRelation = `export const postsRelations = drizzleRelations(posts, ({ one, many }) => ({ author: one(users, { fields: [posts.author_id], references: [users.id], relationName: \"author\" }) }));`;
|
|
73
|
+
expect(cleanResult).toContain(cleanSchema(expectedRelation));
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("should generate a many-to-many relation with a junction table", async () => {
|
|
77
|
+
const postsCollection: EntityCollection = {
|
|
78
|
+
slug: "posts",
|
|
79
|
+
table: "posts",
|
|
80
|
+
name: "Posts",
|
|
81
|
+
properties: {
|
|
82
|
+
title: { type: "string" },
|
|
83
|
+
tags: { type: "relation", relationName: "tags" }
|
|
84
|
+
},
|
|
85
|
+
relations: [
|
|
86
|
+
{
|
|
87
|
+
relationName: "tags",
|
|
88
|
+
target: () => tagsCollection,
|
|
89
|
+
cardinality: "many",
|
|
90
|
+
direction: "owning",
|
|
91
|
+
through: {
|
|
92
|
+
table: "posts_to_tags",
|
|
93
|
+
sourceColumn: "post_id",
|
|
94
|
+
targetColumn: "tag_id"
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
};
|
|
99
|
+
const tagsCollection: EntityCollection = {
|
|
100
|
+
slug: "tags",
|
|
101
|
+
table: "tags",
|
|
102
|
+
name: "Tags",
|
|
103
|
+
properties: {
|
|
104
|
+
name: { type: "string" }
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const result = await generateSchema([postsCollection, tagsCollection]);
|
|
109
|
+
const cleanResult = cleanSchema(result);
|
|
110
|
+
|
|
111
|
+
expect(cleanResult).toContain("export const postsToTags = pgTable(\"posts_to_tags\",");
|
|
112
|
+
expect(cleanResult).toContain("post_id: varchar(\"post_id\").notNull().references(() => posts.id, { onDelete: \"cascade\" })");
|
|
113
|
+
expect(cleanResult).toContain("tag_id: varchar(\"tag_id\").notNull().references(() => tags.id, { onDelete: \"cascade\" })");
|
|
114
|
+
expect(cleanResult).toContain("(table) => ({ pk: primaryKey({ columns: [table.post_id, table.tag_id] }) })");
|
|
115
|
+
expect(cleanResult).toContain("export const postsRelations = drizzleRelations(posts, ({ one, many }) => ({ tags: many(postsToTags, { relationName: \"tags\" }) }));");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe("generateDrizzleSchema Column Types", () => {
|
|
119
|
+
|
|
120
|
+
describe("String Property", () => {
|
|
121
|
+
it("should default to varchar if no columnType or isId is specified", async () => {
|
|
122
|
+
const collections: EntityCollection[] = [{
|
|
123
|
+
slug: "texts", table: "texts", name: "Texts", properties: { t_default: { type: "string" } }
|
|
124
|
+
}];
|
|
125
|
+
const result = await generateSchema(collections);
|
|
126
|
+
expect(cleanSchema(result)).toContain("t_default: varchar(\"t_default\")");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("should respect explicit columnType overrides", async () => {
|
|
130
|
+
const collections: EntityCollection[] = [{
|
|
131
|
+
slug: "texts",
|
|
132
|
+
table: "texts",
|
|
133
|
+
name: "Texts",
|
|
134
|
+
properties: {
|
|
135
|
+
t_text: { type: "string", columnType: "text" },
|
|
136
|
+
t_char: { type: "string", columnType: "char" },
|
|
137
|
+
t_varchar: { type: "string", columnType: "varchar" },
|
|
138
|
+
}
|
|
139
|
+
}];
|
|
140
|
+
const result = await generateSchema(collections);
|
|
141
|
+
const cleanResult = cleanSchema(result);
|
|
142
|
+
|
|
143
|
+
expect(cleanResult).toContain("t_text: text(\"t_text\"),");
|
|
144
|
+
expect(cleanResult).toContain("t_char: char(\"t_char\"),");
|
|
145
|
+
expect(cleanResult).toContain("t_varchar: varchar(\"t_varchar\")");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("should prioritize isId='uuid' over default varchar", async () => {
|
|
149
|
+
const collections: EntityCollection[] = [{
|
|
150
|
+
slug: "texts", table: "texts", name: "Texts", properties: { t_uuid: { type: "string", isId: "uuid" } }
|
|
151
|
+
}];
|
|
152
|
+
const result = await generateSchema(collections);
|
|
153
|
+
expect(cleanSchema(result)).toContain("t_uuid: uuid(\"t_uuid\").primaryKey().defaultRandom()");
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("should combine isId=true with columnType overrides", async () => {
|
|
157
|
+
const collections: EntityCollection[] = [{
|
|
158
|
+
slug: "texts", table: "texts", name: "Texts", properties: { t_id: { type: "string", isId: true, columnType: "text" } }
|
|
159
|
+
}];
|
|
160
|
+
const result = await generateSchema(collections);
|
|
161
|
+
expect(cleanSchema(result)).toContain("t_id: text(\"t_id\").primaryKey()");
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("should respect validation.unique along with columnType", async () => {
|
|
165
|
+
const collections: EntityCollection[] = [{
|
|
166
|
+
slug: "texts", table: "texts", name: "Texts", properties: { t_unique: { type: "string", columnType: "char", validation: { unique: true } } }
|
|
167
|
+
}];
|
|
168
|
+
const result = await generateSchema(collections);
|
|
169
|
+
expect(cleanSchema(result)).toContain("t_unique: char(\"t_unique\").unique()");
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
describe("Number Property", () => {
|
|
174
|
+
it("should default to numeric for normal numbers", async () => {
|
|
175
|
+
const collections: EntityCollection[] = [{
|
|
176
|
+
slug: "nums", table: "nums", name: "Nums", properties: { n_def: { type: "number" } }
|
|
177
|
+
}];
|
|
178
|
+
const result = await generateSchema(collections);
|
|
179
|
+
expect(cleanSchema(result)).toContain("n_def: numeric(\"n_def\")");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("should default to integer if validation.integer is true", async () => {
|
|
183
|
+
const collections: EntityCollection[] = [{
|
|
184
|
+
slug: "nums", table: "nums", name: "Nums", properties: { n_int: { type: "number", validation: { integer: true } } }
|
|
185
|
+
}];
|
|
186
|
+
const result = await generateSchema(collections);
|
|
187
|
+
expect(cleanSchema(result)).toContain("n_int: integer(\"n_int\")");
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("should default to integer if isId is true", async () => {
|
|
191
|
+
const collections: EntityCollection[] = [{
|
|
192
|
+
slug: "nums", table: "nums", name: "Nums", properties: { n_id: { type: "number", isId: true } }
|
|
193
|
+
}];
|
|
194
|
+
const result = await generateSchema(collections);
|
|
195
|
+
expect(cleanSchema(result)).toContain("n_id: integer(\"n_id\").primaryKey()");
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("should respect exact columnType overrides and replace baseType", async () => {
|
|
199
|
+
const collections: EntityCollection[] = [{
|
|
200
|
+
slug: "numbers",
|
|
201
|
+
table: "numbers",
|
|
202
|
+
name: "Numbers",
|
|
203
|
+
properties: {
|
|
204
|
+
n_int: { type: "number", columnType: "integer" },
|
|
205
|
+
n_real: { type: "number", columnType: "real" },
|
|
206
|
+
n_dp: { type: "number", columnType: "double precision" },
|
|
207
|
+
n_num: { type: "number", columnType: "numeric" },
|
|
208
|
+
n_bigint: { type: "number", columnType: "bigint" },
|
|
209
|
+
n_serial: { type: "number", columnType: "serial" },
|
|
210
|
+
n_bigserial: { type: "number", columnType: "bigserial" },
|
|
211
|
+
}
|
|
212
|
+
}];
|
|
213
|
+
const result = await generateSchema(collections);
|
|
214
|
+
const cleanResult = cleanSchema(result);
|
|
215
|
+
|
|
216
|
+
expect(cleanResult).toContain("n_int: integer(\"n_int\"),");
|
|
217
|
+
expect(cleanResult).toContain("n_real: real(\"n_real\"),");
|
|
218
|
+
expect(cleanResult).toContain("n_dp: doublePrecision(\"n_dp\"),");
|
|
219
|
+
expect(cleanResult).toContain("n_num: numeric(\"n_num\"),");
|
|
220
|
+
expect(cleanResult).toContain("n_bigint: bigint(\"n_bigint\"),");
|
|
221
|
+
expect(cleanResult).toContain("n_serial: serial(\"n_serial\"),");
|
|
222
|
+
expect(cleanResult).toContain("n_bigserial: bigserial(\"n_bigserial\"),");
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("should combine isId='increment' with columnType overrides safely", async () => {
|
|
226
|
+
const collections: EntityCollection[] = [{
|
|
227
|
+
slug: "nums", table: "nums", name: "Nums", properties: { n_inc: { type: "number", isId: "increment", columnType: "bigint" } }
|
|
228
|
+
}];
|
|
229
|
+
const result = await generateSchema(collections);
|
|
230
|
+
expect(cleanSchema(result)).toContain("n_inc: bigint(\"n_inc\").generatedByDefaultAsIdentity().primaryKey()");
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("should combine validation.unique with columnType override", async () => {
|
|
234
|
+
const collections: EntityCollection[] = [{
|
|
235
|
+
slug: "nums", table: "nums", name: "Nums", properties: { n_uniq: { type: "number", columnType: "real", validation: { unique: true } } }
|
|
236
|
+
}];
|
|
237
|
+
const result = await generateSchema(collections);
|
|
238
|
+
expect(cleanSchema(result)).toContain("n_uniq: real(\"n_uniq\").unique()");
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
describe("Date Property", () => {
|
|
243
|
+
it("should default to timestamp with timezone", async () => {
|
|
244
|
+
const collections: EntityCollection[] = [{
|
|
245
|
+
slug: "dates", table: "dates", name: "Dates", properties: { d_def: { type: "date" } }
|
|
246
|
+
}];
|
|
247
|
+
const result = await generateSchema(collections);
|
|
248
|
+
expect(cleanSchema(result)).toContain("d_def: timestamp(\"d_def\", { withTimezone: true, mode: 'string' })");
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it("should respect date and time columnType overrides", async () => {
|
|
252
|
+
const collections: EntityCollection[] = [{
|
|
253
|
+
slug: "dates",
|
|
254
|
+
table: "dates",
|
|
255
|
+
name: "Dates",
|
|
256
|
+
properties: {
|
|
257
|
+
d_date: { type: "date", columnType: "date" },
|
|
258
|
+
d_time: { type: "date", columnType: "time" },
|
|
259
|
+
d_ts: { type: "date", columnType: "timestamp" },
|
|
260
|
+
}
|
|
261
|
+
}];
|
|
262
|
+
const result = await generateSchema(collections);
|
|
263
|
+
const cleanResult = cleanSchema(result);
|
|
264
|
+
|
|
265
|
+
expect(cleanResult).toContain("d_date: date(\"d_date\", { mode: 'string' }),");
|
|
266
|
+
expect(cleanResult).toContain("d_time: time(\"d_time\"),");
|
|
267
|
+
expect(cleanResult).toContain("d_ts: timestamp(\"d_ts\", { withTimezone: true, mode: 'string' }),");
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
describe("Map & Array Properties", () => {
|
|
272
|
+
it("should default to jsonb", async () => {
|
|
273
|
+
const collections: EntityCollection[] = [{
|
|
274
|
+
slug: "json_data", table: "json_data", name: "JSON Data", properties: {
|
|
275
|
+
arr_def: { type: "array" },
|
|
276
|
+
map_def: { type: "map" }
|
|
277
|
+
}
|
|
278
|
+
}];
|
|
279
|
+
const result = await generateSchema(collections);
|
|
280
|
+
expect(cleanSchema(result)).toContain("arr_def: jsonb(\"arr_def\"),");
|
|
281
|
+
expect(cleanSchema(result)).toContain("map_def: jsonb(\"map_def\")");
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it("should respect json columnType overrides", async () => {
|
|
285
|
+
const collections: EntityCollection[] = [{
|
|
286
|
+
slug: "json_data",
|
|
287
|
+
table: "json_data",
|
|
288
|
+
name: "JSON Data",
|
|
289
|
+
properties: {
|
|
290
|
+
a_json: { type: "array", columnType: "json" },
|
|
291
|
+
a_jsonb: { type: "array", columnType: "jsonb" },
|
|
292
|
+
m_json: { type: "map", columnType: "json" },
|
|
293
|
+
m_jsonb: { type: "map", columnType: "jsonb" },
|
|
294
|
+
}
|
|
295
|
+
}];
|
|
296
|
+
const result = await generateSchema(collections);
|
|
297
|
+
const cleanResult = cleanSchema(result);
|
|
298
|
+
|
|
299
|
+
expect(cleanResult).toContain("a_json: json(\"a_json\"),");
|
|
300
|
+
expect(cleanResult).toContain("a_jsonb: jsonb(\"a_jsonb\"),");
|
|
301
|
+
expect(cleanResult).toContain("m_json: json(\"m_json\"),");
|
|
302
|
+
expect(cleanResult).toContain("m_jsonb: jsonb(\"m_jsonb\")");
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// ── RLS Policy Tests ────────────────────────────────────────────────
|
|
308
|
+
|
|
309
|
+
describe("RLS policy generation", () => {
|
|
310
|
+
it("should generate ownerField policy with USING and WITH CHECK for 'all'", async () => {
|
|
311
|
+
const collections: EntityCollection[] = [{
|
|
312
|
+
slug: "notes",
|
|
313
|
+
table: "notes",
|
|
314
|
+
name: "Notes",
|
|
315
|
+
properties: {
|
|
316
|
+
title: { type: "string" },
|
|
317
|
+
user_id: { type: "string", validation: { required: true } }
|
|
318
|
+
},
|
|
319
|
+
securityRules: [
|
|
320
|
+
{ operation: "all", ownerField: "user_id" }
|
|
321
|
+
]
|
|
322
|
+
}];
|
|
323
|
+
|
|
324
|
+
const result = await generateSchema(collections);
|
|
325
|
+
expect(result).toContain("pgPolicy");
|
|
326
|
+
expect(result).toContain('as: "permissive"');
|
|
327
|
+
expect(result).toContain('for: "all"');
|
|
328
|
+
expect(result).toContain("${table.user_id} = auth.uid()");
|
|
329
|
+
// 'all' needs both using and withCheck
|
|
330
|
+
expect(result).toContain("using:");
|
|
331
|
+
expect(result).toContain("withCheck:");
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it("should generate SELECT policy with only USING clause", async () => {
|
|
335
|
+
const collections: EntityCollection[] = [{
|
|
336
|
+
slug: "notes",
|
|
337
|
+
table: "notes",
|
|
338
|
+
name: "Notes",
|
|
339
|
+
properties: {
|
|
340
|
+
title: { type: "string" },
|
|
341
|
+
user_id: { type: "string" }
|
|
342
|
+
},
|
|
343
|
+
securityRules: [
|
|
344
|
+
{ operation: "select", ownerField: "user_id" }
|
|
345
|
+
]
|
|
346
|
+
}];
|
|
347
|
+
|
|
348
|
+
const result = await generateSchema(collections);
|
|
349
|
+
expect(result).toContain('for: "select"');
|
|
350
|
+
expect(result).toContain("using:");
|
|
351
|
+
expect(result).not.toContain("withCheck:");
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it("should generate INSERT policy with only WITH CHECK clause", async () => {
|
|
355
|
+
const collections: EntityCollection[] = [{
|
|
356
|
+
slug: "notes",
|
|
357
|
+
table: "notes",
|
|
358
|
+
name: "Notes",
|
|
359
|
+
properties: {
|
|
360
|
+
title: { type: "string" },
|
|
361
|
+
user_id: { type: "string" }
|
|
362
|
+
},
|
|
363
|
+
securityRules: [
|
|
364
|
+
{ operation: "insert", ownerField: "user_id" }
|
|
365
|
+
]
|
|
366
|
+
}];
|
|
367
|
+
|
|
368
|
+
const result = await generateSchema(collections);
|
|
369
|
+
expect(result).toContain('for: "insert"');
|
|
370
|
+
expect(result).toContain("withCheck:");
|
|
371
|
+
expect(result).not.toContain("using:");
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it("should generate public access policy", async () => {
|
|
375
|
+
const collections: EntityCollection[] = [{
|
|
376
|
+
slug: "articles",
|
|
377
|
+
table: "articles",
|
|
378
|
+
name: "Articles",
|
|
379
|
+
properties: { title: { type: "string" } },
|
|
380
|
+
securityRules: [
|
|
381
|
+
{ operation: "select", access: "public" }
|
|
382
|
+
]
|
|
383
|
+
}];
|
|
384
|
+
|
|
385
|
+
const result = await generateSchema(collections);
|
|
386
|
+
expect(result).toContain("sql`true`");
|
|
387
|
+
expect(result).toContain('for: "select"');
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it("should generate roles-only policy", async () => {
|
|
391
|
+
const collections: EntityCollection[] = [{
|
|
392
|
+
slug: "admin_data",
|
|
393
|
+
table: "admin_data",
|
|
394
|
+
name: "Admin Data",
|
|
395
|
+
properties: { data: { type: "string" } },
|
|
396
|
+
securityRules: [
|
|
397
|
+
{ operation: "select", roles: ["admin"] }
|
|
398
|
+
]
|
|
399
|
+
}];
|
|
400
|
+
|
|
401
|
+
const result = await generateSchema(collections);
|
|
402
|
+
expect(result).toContain("string_to_array(auth.roles(), ',') @> ARRAY['admin']");
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it("should safely handle complex sub-string roles overlapping like 'view' and 'viewer'", async () => {
|
|
406
|
+
const collections: EntityCollection[] = [{
|
|
407
|
+
slug: "finance_data",
|
|
408
|
+
table: "finance_data",
|
|
409
|
+
name: "Finance Data",
|
|
410
|
+
properties: { amount: { type: "number" } },
|
|
411
|
+
securityRules: [
|
|
412
|
+
{ operation: "select", roles: ["view"] } // Should not match 'viewer'
|
|
413
|
+
]
|
|
414
|
+
}];
|
|
415
|
+
|
|
416
|
+
const result = await generateSchema(collections);
|
|
417
|
+
// Before array change: auth.roles() ~ 'view' would match 'viewer'
|
|
418
|
+
// Now: array intersection prevents this
|
|
419
|
+
expect(result).toContain("string_to_array(auth.roles(), ',') @> ARRAY['view']");
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
it("should securely handle multiple allowed roles", async () => {
|
|
423
|
+
const collections: EntityCollection[] = [{
|
|
424
|
+
slug: "multi_role",
|
|
425
|
+
table: "multi_role",
|
|
426
|
+
name: "Multi Role",
|
|
427
|
+
properties: { data: { type: "string" } },
|
|
428
|
+
securityRules: [
|
|
429
|
+
{ operation: "select", roles: ["admin", "editor", "super-admin"] }
|
|
430
|
+
]
|
|
431
|
+
}];
|
|
432
|
+
|
|
433
|
+
const result = await generateSchema(collections);
|
|
434
|
+
expect(result).toContain("string_to_array(auth.roles(), ',') @> ARRAY['admin','editor','super-admin']");
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
it("should combine roles with access: public", async () => {
|
|
438
|
+
const collections: EntityCollection[] = [{
|
|
439
|
+
slug: "reports",
|
|
440
|
+
table: "reports",
|
|
441
|
+
name: "Reports",
|
|
442
|
+
properties: { title: { type: "string" } },
|
|
443
|
+
securityRules: [
|
|
444
|
+
{ operation: "select", roles: ["admin", "manager"], access: "public" }
|
|
445
|
+
]
|
|
446
|
+
}];
|
|
447
|
+
|
|
448
|
+
const result = await generateSchema(collections);
|
|
449
|
+
// Should be (true) AND (role check)
|
|
450
|
+
expect(result).toContain("(true) AND (string_to_array(auth.roles(), ',') @> ARRAY['admin','manager'])");
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
it("should combine roles with ownerField", async () => {
|
|
454
|
+
const collections: EntityCollection[] = [{
|
|
455
|
+
slug: "docs",
|
|
456
|
+
table: "docs",
|
|
457
|
+
name: "Docs",
|
|
458
|
+
properties: {
|
|
459
|
+
title: { type: "string" },
|
|
460
|
+
user_id: { type: "string" }
|
|
461
|
+
},
|
|
462
|
+
securityRules: [
|
|
463
|
+
{ operation: "select", roles: ["editor"], ownerField: "user_id" }
|
|
464
|
+
]
|
|
465
|
+
}];
|
|
466
|
+
|
|
467
|
+
const result = await generateSchema(collections);
|
|
468
|
+
// Should combine owner check AND role check
|
|
469
|
+
expect(result).toContain("auth.uid()");
|
|
470
|
+
expect(result).toContain("string_to_array(auth.roles(), ',') @> ARRAY['editor']");
|
|
471
|
+
expect(result).toContain("AND");
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
it("should generate restrictive policy", async () => {
|
|
475
|
+
const collections: EntityCollection[] = [{
|
|
476
|
+
slug: "orders",
|
|
477
|
+
table: "orders",
|
|
478
|
+
name: "Orders",
|
|
479
|
+
properties: {
|
|
480
|
+
amount: { type: "number" },
|
|
481
|
+
is_locked: { type: "boolean" }
|
|
482
|
+
},
|
|
483
|
+
securityRules: [
|
|
484
|
+
{ operation: "update", mode: "restrictive", using: "{is_locked} = false" }
|
|
485
|
+
]
|
|
486
|
+
}];
|
|
487
|
+
|
|
488
|
+
const result = await generateSchema(collections);
|
|
489
|
+
expect(result).toContain('as: "restrictive"');
|
|
490
|
+
expect(result).toContain("${table.is_locked} = false");
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
it("should generate raw SQL using clause with column references", async () => {
|
|
494
|
+
const collections: EntityCollection[] = [{
|
|
495
|
+
slug: "posts",
|
|
496
|
+
table: "posts",
|
|
497
|
+
name: "Posts",
|
|
498
|
+
properties: {
|
|
499
|
+
title: { type: "string" },
|
|
500
|
+
published_at: { type: "date" }
|
|
501
|
+
},
|
|
502
|
+
securityRules: [
|
|
503
|
+
{ operation: "select", using: "{published_at} > now() - interval '30 days'" }
|
|
504
|
+
]
|
|
505
|
+
}];
|
|
506
|
+
|
|
507
|
+
const result = await generateSchema(collections);
|
|
508
|
+
expect(result).toContain("${table.published_at} > now() - interval '30 days'");
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
it("should generate raw SQL withCheck clause", async () => {
|
|
512
|
+
const collections: EntityCollection[] = [{
|
|
513
|
+
slug: "items",
|
|
514
|
+
table: "items",
|
|
515
|
+
name: "Items",
|
|
516
|
+
properties: {
|
|
517
|
+
name: { type: "string" },
|
|
518
|
+
user_id: { type: "string" },
|
|
519
|
+
status: { type: "string" }
|
|
520
|
+
},
|
|
521
|
+
securityRules: [
|
|
522
|
+
{
|
|
523
|
+
operation: "update",
|
|
524
|
+
using: "{user_id} = auth.uid()",
|
|
525
|
+
withCheck: "{status} != 'archived' OR auth.roles() ~ 'admin'"
|
|
526
|
+
}
|
|
527
|
+
]
|
|
528
|
+
}];
|
|
529
|
+
|
|
530
|
+
const result = await generateSchema(collections);
|
|
531
|
+
expect(result).toContain("using:");
|
|
532
|
+
expect(result).toContain("withCheck:");
|
|
533
|
+
expect(result).toContain("${table.user_id} = auth.uid()");
|
|
534
|
+
expect(result).toContain("${table.status} != 'archived'");
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
it("should use custom policy names when provided", async () => {
|
|
538
|
+
const collections: EntityCollection[] = [{
|
|
539
|
+
slug: "data",
|
|
540
|
+
table: "data",
|
|
541
|
+
name: "Data",
|
|
542
|
+
properties: { value: { type: "string" } },
|
|
543
|
+
securityRules: [
|
|
544
|
+
{ name: "my_custom_policy", operation: "select", access: "public" }
|
|
545
|
+
]
|
|
546
|
+
}];
|
|
547
|
+
|
|
548
|
+
const result = await generateSchema(collections);
|
|
549
|
+
expect(result).toContain('pgPolicy("my_custom_policy"');
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
it("should generate multiple policies for the same table", async () => {
|
|
553
|
+
const collections: EntityCollection[] = [{
|
|
554
|
+
slug: "notes",
|
|
555
|
+
table: "notes",
|
|
556
|
+
name: "Notes",
|
|
557
|
+
properties: {
|
|
558
|
+
title: { type: "string" },
|
|
559
|
+
user_id: { type: "string" },
|
|
560
|
+
is_locked: { type: "boolean" }
|
|
561
|
+
},
|
|
562
|
+
securityRules: [
|
|
563
|
+
{ name: "admin_read", operation: "select", roles: ["admin"], access: "public" },
|
|
564
|
+
{ name: "owner_read", operation: "select", ownerField: "user_id" },
|
|
565
|
+
{ name: "owner_write", operation: "insert", ownerField: "user_id" },
|
|
566
|
+
{ name: "no_locked_update", operation: "update", mode: "restrictive", using: "{is_locked} = false" }
|
|
567
|
+
]
|
|
568
|
+
}];
|
|
569
|
+
|
|
570
|
+
const result = await generateSchema(collections);
|
|
571
|
+
expect(result).toContain('pgPolicy("admin_read"');
|
|
572
|
+
expect(result).toContain('pgPolicy("owner_read"');
|
|
573
|
+
expect(result).toContain('pgPolicy("owner_write"');
|
|
574
|
+
expect(result).toContain('pgPolicy("no_locked_update"');
|
|
575
|
+
expect(result).toContain('as: "restrictive"');
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
});
|
|
579
|
+
});
|
|
580
|
+
// V2 improvements tests
|
|
581
|
+
describe("generateDrizzleSchema V2 improvements", () => {
|
|
582
|
+
it("should generate role-based security rule", async () => {
|
|
583
|
+
const collections: EntityCollection[] = [{
|
|
584
|
+
slug: "admin_data",
|
|
585
|
+
table: "admin_data",
|
|
586
|
+
name: "Admin Data",
|
|
587
|
+
properties: { data: { type: "string" } },
|
|
588
|
+
securityRules: [
|
|
589
|
+
{ operation: "select", roles: ["admin"] }
|
|
590
|
+
]
|
|
591
|
+
}];
|
|
592
|
+
const result = await generateSchema(collections);
|
|
593
|
+
expect(result).toContain("string_to_array(auth.roles(), ',') @> ARRAY['admin']");
|
|
594
|
+
});
|
|
595
|
+
it("should generate multiple policies from operations array", async () => {
|
|
596
|
+
const collections: EntityCollection[] = [{
|
|
597
|
+
slug: "notes",
|
|
598
|
+
table: "notes",
|
|
599
|
+
name: "Notes",
|
|
600
|
+
properties: {
|
|
601
|
+
title: { type: "string" },
|
|
602
|
+
user_id: { type: "string" }
|
|
603
|
+
},
|
|
604
|
+
securityRules: [
|
|
605
|
+
{ name: "owner_rw", operations: ["select", "update"], ownerField: "user_id" }
|
|
606
|
+
]
|
|
607
|
+
}];
|
|
608
|
+
const result = await generateSchema(collections);
|
|
609
|
+
// Should generate two separate policies
|
|
610
|
+
expect(result).toContain('pgPolicy("owner_rw_select"');
|
|
611
|
+
expect(result).toContain('pgPolicy("owner_rw_update"');
|
|
612
|
+
expect(result).toContain('for: "select"');
|
|
613
|
+
expect(result).toContain('for: "update"');
|
|
614
|
+
});
|
|
615
|
+
it("should auto-generate names with operation suffix for operations array", async () => {
|
|
616
|
+
const collections: EntityCollection[] = [{
|
|
617
|
+
slug: "items",
|
|
618
|
+
table: "items",
|
|
619
|
+
name: "Items",
|
|
620
|
+
properties: { data: { type: "string" } },
|
|
621
|
+
securityRules: [
|
|
622
|
+
{ operations: ["select", "delete"], access: "public" }
|
|
623
|
+
]
|
|
624
|
+
}];
|
|
625
|
+
const result = await generateSchema(collections);
|
|
626
|
+
expect(result).toContain('for: "select"');
|
|
627
|
+
expect(result).toContain('for: "delete"');
|
|
628
|
+
});
|
|
629
|
+
it("should not append operation suffix when operations array has single element", async () => {
|
|
630
|
+
const collections: EntityCollection[] = [{
|
|
631
|
+
slug: "items",
|
|
632
|
+
table: "items",
|
|
633
|
+
name: "Items",
|
|
634
|
+
properties: { data: { type: "string" } },
|
|
635
|
+
securityRules: [
|
|
636
|
+
{ name: "my_policy", operations: ["select"], access: "public" }
|
|
637
|
+
]
|
|
638
|
+
}];
|
|
639
|
+
const result = await generateSchema(collections);
|
|
640
|
+
expect(result).toContain('pgPolicy("my_policy"');
|
|
641
|
+
// Should NOT have _select suffix since only one operation
|
|
642
|
+
expect(result).not.toContain('pgPolicy("my_policy_select"');
|
|
643
|
+
});
|
|
644
|
+
it("should generate correct clauses per operation in operations array", async () => {
|
|
645
|
+
const collections: EntityCollection[] = [{
|
|
646
|
+
slug: "notes",
|
|
647
|
+
table: "notes",
|
|
648
|
+
name: "Notes",
|
|
649
|
+
properties: {
|
|
650
|
+
title: { type: "string" },
|
|
651
|
+
user_id: { type: "string" }
|
|
652
|
+
},
|
|
653
|
+
securityRules: [
|
|
654
|
+
{ name: "owner", operations: ["select", "insert"], ownerField: "user_id" }
|
|
655
|
+
]
|
|
656
|
+
}];
|
|
657
|
+
const result = await generateSchema(collections);
|
|
658
|
+
// SELECT should have USING only
|
|
659
|
+
const selectPolicy = result.split("\n").find(l => l.includes("owner_select"));
|
|
660
|
+
expect(selectPolicy).toContain("using:");
|
|
661
|
+
expect(selectPolicy).not.toContain("withCheck:");
|
|
662
|
+
// INSERT should have WITH CHECK only
|
|
663
|
+
const insertPolicy = result.split("\n").find(l => l.includes("owner_insert"));
|
|
664
|
+
expect(insertPolicy).toContain("withCheck:");
|
|
665
|
+
expect(insertPolicy).not.toContain("using:");
|
|
666
|
+
});
|
|
667
|
+
it("operations array takes precedence over singular operation", async () => {
|
|
668
|
+
const collections: EntityCollection[] = [{
|
|
669
|
+
slug: "items",
|
|
670
|
+
table: "items",
|
|
671
|
+
name: "Items",
|
|
672
|
+
properties: { data: { type: "string" } },
|
|
673
|
+
securityRules: [
|
|
674
|
+
{ name: "test", operation: "delete", operations: ["select", "insert"], access: "public" }
|
|
675
|
+
]
|
|
676
|
+
}];
|
|
677
|
+
const result = await generateSchema(collections);
|
|
678
|
+
// operations[] should win — no "delete" policy
|
|
679
|
+
expect(result).toContain('for: "select"');
|
|
680
|
+
expect(result).toContain('for: "insert"');
|
|
681
|
+
expect(result).not.toContain('for: "delete"');
|
|
682
|
+
});
|
|
683
|
+
it("should handle roles combined with using true for unfiltered access", async () => {
|
|
684
|
+
const collections: EntityCollection[] = [{
|
|
685
|
+
slug: "reports",
|
|
686
|
+
table: "reports",
|
|
687
|
+
name: "Reports",
|
|
688
|
+
properties: { title: { type: "string" } },
|
|
689
|
+
securityRules: [
|
|
690
|
+
{ operation: "select", roles: ["admin"], using: "true" }
|
|
691
|
+
]
|
|
692
|
+
}];
|
|
693
|
+
const result = await generateSchema(collections);
|
|
694
|
+
// Should be (true) AND (role check) — same effect as access: "public" + roles
|
|
695
|
+
expect(result).toContain("(true) AND (string_to_array(auth.roles(), ',') @> ARRAY['admin'])");
|
|
696
|
+
});
|
|
697
|
+
it("should use pgRoles instead of default 'public' when specified", async () => {
|
|
698
|
+
const collections: EntityCollection[] = [{
|
|
699
|
+
slug: "tenant_data",
|
|
700
|
+
table: "tenant_data",
|
|
701
|
+
name: "Tenant Data",
|
|
702
|
+
properties: { data: { type: "string" } },
|
|
703
|
+
securityRules: [
|
|
704
|
+
{ operation: "select", access: "public", pgRoles: ["app_role", "service_role"] }
|
|
705
|
+
]
|
|
706
|
+
}];
|
|
707
|
+
const result = await generateSchema(collections);
|
|
708
|
+
expect(result).toContain('to: ["app_role", "service_role"]');
|
|
709
|
+
expect(result).not.toContain('to: ["public"]');
|
|
710
|
+
});
|
|
711
|
+
it("should default to 'public' pgRole when pgRoles is not specified", async () => {
|
|
712
|
+
const collections: EntityCollection[] = [{
|
|
713
|
+
slug: "default_data",
|
|
714
|
+
table: "default_data",
|
|
715
|
+
name: "Default Data",
|
|
716
|
+
properties: { data: { type: "string" } },
|
|
717
|
+
securityRules: [
|
|
718
|
+
{ operation: "select", access: "public" }
|
|
719
|
+
]
|
|
720
|
+
}];
|
|
721
|
+
const result = await generateSchema(collections);
|
|
722
|
+
expect(result).toContain('to: ["public"]');
|
|
723
|
+
});
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
describe("generateDrizzleSchema ID Generation", () => {
|
|
727
|
+
const cleanSchema = (schema: string) => {
|
|
728
|
+
return schema
|
|
729
|
+
.replace(/\/\/.*$/gm, "")
|
|
730
|
+
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
731
|
+
.replace(/\n{2,}/g, "\n")
|
|
732
|
+
.replace(/\s+/g, " ")
|
|
733
|
+
.trim();
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
it("should generate a UUID primary key when isId is 'uuid'", async () => {
|
|
737
|
+
const collections: EntityCollection[] = [{
|
|
738
|
+
slug: "items",
|
|
739
|
+
table: "items",
|
|
740
|
+
name: "Items",
|
|
741
|
+
properties: {
|
|
742
|
+
custom_id: { type: "string", isId: "uuid" }
|
|
743
|
+
}
|
|
744
|
+
}];
|
|
745
|
+
const result = await generateSchema(collections);
|
|
746
|
+
const cleanResult = cleanSchema(result);
|
|
747
|
+
|
|
748
|
+
expect(cleanResult).toContain("custom_id: uuid(\"custom_id\").primaryKey().defaultRandom()");
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
it("should generate a serial primary key when isId is 'increment'", async () => {
|
|
752
|
+
const collections: EntityCollection[] = [{
|
|
753
|
+
slug: "tickets",
|
|
754
|
+
table: "tickets",
|
|
755
|
+
name: "Tickets",
|
|
756
|
+
properties: {
|
|
757
|
+
ticket_id: { type: "number", isId: "increment" }
|
|
758
|
+
}
|
|
759
|
+
}];
|
|
760
|
+
const result = await generateSchema(collections);
|
|
761
|
+
const cleanResult = cleanSchema(result);
|
|
762
|
+
|
|
763
|
+
expect(cleanResult).toContain("ticket_id: integer(\"ticket_id\").generatedByDefaultAsIdentity().primaryKey()");
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
it("should generate a custom SQL primary key when isId is a sql string", async () => {
|
|
767
|
+
const collections: EntityCollection[] = [{
|
|
768
|
+
slug: "events",
|
|
769
|
+
table: "events",
|
|
770
|
+
name: "Events",
|
|
771
|
+
properties: {
|
|
772
|
+
event_id: { type: "string", isId: "sql`gen_random_uuid()`" }
|
|
773
|
+
}
|
|
774
|
+
}];
|
|
775
|
+
const result = await generateSchema(collections);
|
|
776
|
+
const cleanResult = cleanSchema(result);
|
|
777
|
+
|
|
778
|
+
expect(cleanResult).toContain("event_id: varchar(\"event_id\").primaryKey().default(sql`gen_random_uuid()`)");
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
it("should generate a normal text primary key when isId is simply true", async () => {
|
|
782
|
+
const collections: EntityCollection[] = [{
|
|
783
|
+
slug: "users",
|
|
784
|
+
table: "users",
|
|
785
|
+
name: "Users",
|
|
786
|
+
properties: {
|
|
787
|
+
user_name: { type: "string", isId: true }
|
|
788
|
+
}
|
|
789
|
+
}];
|
|
790
|
+
const result = await generateSchema(collections);
|
|
791
|
+
const cleanResult = cleanSchema(result);
|
|
792
|
+
|
|
793
|
+
expect(cleanResult).toContain("user_name: varchar(\"user_name\").primaryKey()");
|
|
794
|
+
});
|
|
795
|
+
});
|