@rebasepro/server-postgresql 0.0.1-canary.09e5ec5
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 +56 -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 +58 -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 +22 -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 +11298 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.umd.js +11306 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/server-postgresql/src/PostgresBackendDriver.d.ts +100 -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 +192 -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 +40 -0
- package/dist/server-postgresql/src/data-transformer.d.ts +58 -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 +868 -0
- package/dist/server-postgresql/src/schema/doctor-cli.d.ts +2 -0
- package/dist/server-postgresql/src/schema/doctor.d.ts +43 -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/schema/introspect-db-logic.d.ts +82 -0
- package/dist/server-postgresql/src/schema/introspect-db.d.ts +1 -0
- package/dist/server-postgresql/src/schema/test-schema.d.ts +24 -0
- package/dist/server-postgresql/src/services/BranchService.d.ts +47 -0
- package/dist/server-postgresql/src/services/EntityFetchService.d.ts +209 -0
- package/dist/server-postgresql/src/services/EntityPersistService.d.ts +41 -0
- package/dist/server-postgresql/src/services/RelationService.d.ts +98 -0
- package/dist/server-postgresql/src/services/entity-helpers.d.ts +38 -0
- package/dist/server-postgresql/src/services/entityService.d.ts +104 -0
- package/dist/server-postgresql/src/services/index.d.ts +4 -0
- package/dist/server-postgresql/src/services/realtimeService.d.ts +188 -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 +119 -0
- package/dist/types/src/controllers/client.d.ts +170 -0
- package/dist/types/src/controllers/collection_registry.d.ts +45 -0
- package/dist/types/src/controllers/customization_controller.d.ts +60 -0
- package/dist/types/src/controllers/data.d.ts +168 -0
- package/dist/types/src/controllers/data_driver.d.ts +160 -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/email.d.ts +34 -0
- package/dist/types/src/controllers/index.d.ts +18 -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 +54 -0
- package/dist/types/src/controllers/side_dialogs_controller.d.ts +67 -0
- package/dist/types/src/controllers/side_entity_controller.d.ts +90 -0
- package/dist/types/src/controllers/snackbar.d.ts +24 -0
- package/dist/types/src/controllers/storage.d.ts +171 -0
- package/dist/types/src/index.d.ts +4 -0
- package/dist/types/src/rebase_context.d.ts +105 -0
- package/dist/types/src/types/backend.d.ts +536 -0
- package/dist/types/src/types/builders.d.ts +15 -0
- package/dist/types/src/types/chips.d.ts +5 -0
- package/dist/types/src/types/collections.d.ts +856 -0
- package/dist/types/src/types/cron.d.ts +102 -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 +10 -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 +23 -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 +279 -0
- package/dist/types/src/types/properties.d.ts +1176 -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 +252 -0
- package/dist/types/src/types/translations.d.ts +870 -0
- package/dist/types/src/types/user_management_delegate.d.ts +121 -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/drizzle-test/0000_woozy_junta.sql +6 -0
- package/drizzle-test/0001_youthful_arachne.sql +1 -0
- package/drizzle-test/0002_lively_dragon_lord.sql +2 -0
- package/drizzle-test/0003_mean_king_cobra.sql +2 -0
- package/drizzle-test/meta/0000_snapshot.json +47 -0
- package/drizzle-test/meta/0001_snapshot.json +48 -0
- package/drizzle-test/meta/0002_snapshot.json +38 -0
- package/drizzle-test/meta/0003_snapshot.json +48 -0
- package/drizzle-test/meta/_journal.json +34 -0
- package/drizzle-test-out/0000_tan_trauma.sql +6 -0
- package/drizzle-test-out/0001_rapid_drax.sql +1 -0
- package/drizzle-test-out/meta/0000_snapshot.json +44 -0
- package/drizzle-test-out/meta/0001_snapshot.json +54 -0
- package/drizzle-test-out/meta/_journal.json +20 -0
- package/drizzle.test.config.ts +10 -0
- package/jest-all.log +3128 -0
- package/jest.log +49 -0
- package/package.json +92 -0
- package/scratch.ts +41 -0
- package/src/PostgresBackendDriver.ts +1008 -0
- package/src/PostgresBootstrapper.ts +231 -0
- package/src/auth/ensure-tables.ts +381 -0
- package/src/auth/services.ts +799 -0
- package/src/cli.ts +648 -0
- package/src/collections/PostgresCollectionRegistry.ts +96 -0
- package/src/connection.ts +84 -0
- package/src/data-transformer.ts +608 -0
- package/src/databasePoolManager.ts +85 -0
- package/src/history/HistoryService.ts +248 -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 +169 -0
- package/src/schema/doctor-cli.ts +47 -0
- package/src/schema/doctor.ts +595 -0
- package/src/schema/generate-drizzle-schema-logic.ts +765 -0
- package/src/schema/generate-drizzle-schema.ts +151 -0
- package/src/schema/introspect-db-logic.ts +542 -0
- package/src/schema/introspect-db.ts +211 -0
- package/src/schema/test-schema.ts +11 -0
- package/src/services/BranchService.ts +237 -0
- package/src/services/EntityFetchService.ts +1576 -0
- package/src/services/EntityPersistService.ts +349 -0
- package/src/services/RelationService.ts +1274 -0
- package/src/services/entity-helpers.ts +147 -0
- package/src/services/entityService.ts +211 -0
- package/src/services/index.ts +13 -0
- package/src/services/realtimeService.ts +1034 -0
- package/src/utils/drizzle-conditions.ts +1000 -0
- package/src/websocket.ts +518 -0
- package/test/auth-services.test.ts +661 -0
- package/test/batch-many-to-many-regression.test.ts +573 -0
- package/test/branchService.test.ts +367 -0
- package/test/data-transformer-hardening.test.ts +417 -0
- package/test/data-transformer.test.ts +175 -0
- package/test/doctor.test.ts +182 -0
- package/test/drizzle-conditions.test.ts +895 -0
- package/test/entityService.errors.test.ts +367 -0
- package/test/entityService.relations.test.ts +1008 -0
- package/test/entityService.subcollection-search.test.ts +566 -0
- package/test/entityService.test.ts +1035 -0
- package/test/generate-drizzle-schema.test.ts +988 -0
- package/test/historyService.test.ts +141 -0
- package/test/introspect-db-generation.test.ts +436 -0
- package/test/introspect-db-utils.test.ts +389 -0
- package/test/n-plus-one-regression.test.ts +314 -0
- package/test/postgresDataDriver.test.ts +648 -0
- package/test/realtimeService.test.ts +307 -0
- package/test/relation-pipeline-gaps.test.ts +637 -0
- package/test/relations.test.ts +1115 -0
- package/test/unmapped-tables-safety.test.ts +345 -0
- package/test-drizzle-bug.ts +18 -0
- package/test-drizzle-out/0000_cultured_freak.sql +7 -0
- package/test-drizzle-out/0001_tiresome_professor_monster.sql +1 -0
- package/test-drizzle-out/meta/0000_snapshot.json +55 -0
- package/test-drizzle-out/meta/0001_snapshot.json +63 -0
- package/test-drizzle-out/meta/_journal.json +20 -0
- package/test-drizzle-prompt.sh +2 -0
- package/test-policy-prompt.sh +3 -0
- package/test-programmatic.ts +30 -0
- package/test-programmatic2.ts +59 -0
- package/test-schema-no-policies.ts +12 -0
- package/test_drizzle_mock.js +3 -0
- package/test_find_changed.mjs +32 -0
- package/test_hash.js +14 -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,141 @@
|
|
|
1
|
+
import { HistoryService, findChangedFields } from "../src/history/HistoryService";
|
|
2
|
+
import { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
3
|
+
import { DrizzleClient } from "../src/interfaces";
|
|
4
|
+
import { PostgresCollectionRegistry } from "../src/collections/PostgresCollectionRegistry";
|
|
5
|
+
|
|
6
|
+
describe("HistoryService - changedFields and history insertion logic", () => {
|
|
7
|
+
describe("findChangedFields", () => {
|
|
8
|
+
it("should return null when identical flat objects are compared", () => {
|
|
9
|
+
const oldValues = { title: "Hello",
|
|
10
|
+
description: "World" };
|
|
11
|
+
const newValues = { title: "Hello",
|
|
12
|
+
description: "World" };
|
|
13
|
+
const result = findChangedFields(oldValues, newValues);
|
|
14
|
+
expect(result).toBeNull();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("should detect changes on simple properties", () => {
|
|
18
|
+
const oldValues = { title: "Hello" };
|
|
19
|
+
const newValues = { title: "Hello World" };
|
|
20
|
+
const result = findChangedFields(oldValues, newValues);
|
|
21
|
+
expect(result).toEqual(["title"]);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("should skip properties starting with double underscore", () => {
|
|
25
|
+
const oldValues = { title: "Hello",
|
|
26
|
+
__internal: 123 };
|
|
27
|
+
const newValues = { title: "Hello",
|
|
28
|
+
__internal: 456 };
|
|
29
|
+
const result = findChangedFields(oldValues, newValues);
|
|
30
|
+
expect(result).toBeNull();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should return null for deeply identical relations", () => {
|
|
34
|
+
const oldValues = {
|
|
35
|
+
author: { id: "1",
|
|
36
|
+
path: "authors",
|
|
37
|
+
__type: "relation" },
|
|
38
|
+
tags: [{ id: "1" }, { id: "2" }]
|
|
39
|
+
};
|
|
40
|
+
const newValues = {
|
|
41
|
+
author: { id: "1",
|
|
42
|
+
path: "authors",
|
|
43
|
+
__type: "relation" },
|
|
44
|
+
tags: [{ id: "1" }, { id: "2" }]
|
|
45
|
+
};
|
|
46
|
+
const result = findChangedFields(oldValues as Record<string, unknown>, newValues as Record<string, unknown>);
|
|
47
|
+
expect(result).toBeNull();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("should detect changes in relation properties when IDs differ", () => {
|
|
51
|
+
const oldValues = {
|
|
52
|
+
author: { id: "1",
|
|
53
|
+
path: "authors",
|
|
54
|
+
__type: "relation" }
|
|
55
|
+
};
|
|
56
|
+
const newValues = {
|
|
57
|
+
author: { id: "2",
|
|
58
|
+
path: "authors",
|
|
59
|
+
__type: "relation" }
|
|
60
|
+
};
|
|
61
|
+
const result = findChangedFields(oldValues as Record<string, unknown>, newValues as Record<string, unknown>);
|
|
62
|
+
expect(result).toEqual(["author"]);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("should detect differences in relation arrays", () => {
|
|
66
|
+
const oldValues = {
|
|
67
|
+
tags: [{ id: "1" }]
|
|
68
|
+
};
|
|
69
|
+
const newValues = {
|
|
70
|
+
tags: [{ id: "1" }, { id: "2" }]
|
|
71
|
+
};
|
|
72
|
+
const result = findChangedFields(oldValues as unknown as Record<string, unknown>, newValues as unknown as Record<string, unknown>);
|
|
73
|
+
expect(result).toEqual(["tags"]);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe("recordHistory execution mapping", () => {
|
|
78
|
+
let db: jest.Mocked<NodePgDatabase>;
|
|
79
|
+
let historyService: HistoryService;
|
|
80
|
+
|
|
81
|
+
beforeEach(() => {
|
|
82
|
+
db = {
|
|
83
|
+
execute: jest.fn().mockResolvedValue({})
|
|
84
|
+
} as unknown as jest.Mocked<NodePgDatabase>;
|
|
85
|
+
historyService = new HistoryService(db as unknown as DrizzleClient, {} as unknown as PostgresCollectionRegistry);
|
|
86
|
+
jest.spyOn(console, "error").mockImplementation(() => {});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
afterEach(() => {
|
|
90
|
+
jest.restoreAllMocks();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("should skip execution when changed fields evaluate to null on update", async () => {
|
|
94
|
+
await historyService.recordHistory({
|
|
95
|
+
tableName: "posts",
|
|
96
|
+
entityId: "1",
|
|
97
|
+
action: "update",
|
|
98
|
+
previousValues: { title: "same" },
|
|
99
|
+
values: { title: "same" }
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// db.execute should not be called since there is no data to log
|
|
103
|
+
expect(db.execute).not.toHaveBeenCalled();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("should properly structure database query on actual array changes", async () => {
|
|
107
|
+
await historyService.recordHistory({
|
|
108
|
+
tableName: "posts",
|
|
109
|
+
entityId: "1",
|
|
110
|
+
action: "update",
|
|
111
|
+
previousValues: { title: "old",
|
|
112
|
+
tags: [{ id: 1 }] },
|
|
113
|
+
values: { title: "new",
|
|
114
|
+
tags: [{ id: 2 }] }
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Since it's a difference, db.execute should be called. (plus 2 prune calls)
|
|
118
|
+
expect(db.execute.mock.calls.length).toBeGreaterThanOrEqual(1);
|
|
119
|
+
|
|
120
|
+
const executedSql = db.execute.mock.calls[0][0] as unknown as { query: string; sql?: string; strings?: string[]; values?: unknown[] };
|
|
121
|
+
|
|
122
|
+
// Drizzle wraps SQL in its own SQL type which contains sql strings and params.
|
|
123
|
+
const serializedSql = JSON.stringify(executedSql);
|
|
124
|
+
// The syntax we added is ARRAY[?]::text[] or similar
|
|
125
|
+
expect(serializedSql).toContain("::text[]");
|
|
126
|
+
expect(serializedSql).toContain("ARRAY[");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("should properly perform query during entity creation (insert)", async () => {
|
|
130
|
+
await historyService.recordHistory({
|
|
131
|
+
tableName: "posts",
|
|
132
|
+
entityId: "1",
|
|
133
|
+
action: "create",
|
|
134
|
+
previousValues: undefined,
|
|
135
|
+
values: { title: "new" }
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
expect(db.execute.mock.calls.length).toBeGreaterThanOrEqual(1);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
});
|
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generateCollectionFile, buildTablesMap, buildEnumMap,
|
|
3
|
+
identifyJoinTables, TableRow, TableColumn, PrimaryKeyRow,
|
|
4
|
+
ForeignKeyRow, EnumValue, TableMeta,
|
|
5
|
+
} from "../src/schema/introspect-db-logic";
|
|
6
|
+
|
|
7
|
+
// ── Helpers ───────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
const mkCol = (table: string, col: string, opts: Partial<TableColumn> = {}): TableColumn => ({
|
|
10
|
+
table_name: table, column_name: col, data_type: "character varying",
|
|
11
|
+
udt_name: "varchar", is_nullable: "YES", column_default: null, ...opts,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const mkFk = (table: string, col: string, fTable: string, fCol = "id"): ForeignKeyRow => ({
|
|
15
|
+
table_name: table, column_name: col, foreign_table_name: fTable, foreign_column_name: fCol,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
function makeSimpleTable(name: string, columns: TableColumn[], pks: string[] = ["id"], fks: ForeignKeyRow[] = []): TableMeta {
|
|
19
|
+
return { name, columns, pks, fks };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
23
|
+
// generateCollectionFile() — property generation
|
|
24
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
25
|
+
describe("generateCollectionFile", () => {
|
|
26
|
+
describe("basic property generation", () => {
|
|
27
|
+
it("generates a simple collection with slug, name, singularName and table", () => {
|
|
28
|
+
const meta = makeSimpleTable("products", [
|
|
29
|
+
mkCol("products", "id", { data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
|
|
30
|
+
mkCol("products", "name", { is_nullable: "NO" }),
|
|
31
|
+
]);
|
|
32
|
+
const result = generateCollectionFile("products", meta, [], new Set(), new Map([["products", meta]]), new Map());
|
|
33
|
+
expect(result).toContain('slug: "products"');
|
|
34
|
+
expect(result).toContain('name: "Products"');
|
|
35
|
+
expect(result).toContain('singularName: "Product"');
|
|
36
|
+
expect(result).toContain('table: "products"');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("generates correct property types from columns", () => {
|
|
40
|
+
const meta = makeSimpleTable("items", [
|
|
41
|
+
mkCol("items", "id", { data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
|
|
42
|
+
mkCol("items", "count", { data_type: "integer", udt_name: "int4" }),
|
|
43
|
+
mkCol("items", "active", { data_type: "boolean", udt_name: "bool" }),
|
|
44
|
+
mkCol("items", "created_at", { data_type: "timestamp", udt_name: "timestamp" }),
|
|
45
|
+
mkCol("items", "metadata", { data_type: "jsonb", udt_name: "jsonb" }),
|
|
46
|
+
]);
|
|
47
|
+
const result = generateCollectionFile("items", meta, [], new Set(), new Map([["items", meta]]), new Map());
|
|
48
|
+
expect(result).toContain('type: "string"'); // id
|
|
49
|
+
expect(result).toContain('type: "number"');
|
|
50
|
+
expect(result).toContain('type: "boolean"');
|
|
51
|
+
expect(result).toContain('type: "date"');
|
|
52
|
+
expect(result).toContain('type: "json"');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("skips FK columns from properties (they become relations)", () => {
|
|
56
|
+
const fks = [mkFk("posts", "author_id", "users")];
|
|
57
|
+
const meta = makeSimpleTable("posts", [
|
|
58
|
+
mkCol("posts", "id", { data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
|
|
59
|
+
mkCol("posts", "title", { is_nullable: "NO" }),
|
|
60
|
+
mkCol("posts", "author_id", { is_nullable: "NO" }),
|
|
61
|
+
], ["id"], fks);
|
|
62
|
+
const result = generateCollectionFile("posts", meta, fks, new Set(), new Map([["posts", meta]]), new Map());
|
|
63
|
+
// author_id should NOT appear as a regular property
|
|
64
|
+
expect(result).not.toMatch(/author_id:\s*\{[^}]*type:\s*"string"/);
|
|
65
|
+
// but author should appear as a relation
|
|
66
|
+
expect(result).toContain('type: "relation"');
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe("propertiesOrder correctness", () => {
|
|
71
|
+
it("includes relation property key, not FK column name", () => {
|
|
72
|
+
const fks = [mkFk("posts", "author_id", "users")];
|
|
73
|
+
const meta = makeSimpleTable("posts", [
|
|
74
|
+
mkCol("posts", "id", { data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
|
|
75
|
+
mkCol("posts", "title"),
|
|
76
|
+
mkCol("posts", "author_id"),
|
|
77
|
+
], ["id"], fks);
|
|
78
|
+
const result = generateCollectionFile("posts", meta, fks, new Set(), new Map([["posts", meta]]), new Map());
|
|
79
|
+
// propertiesOrder should contain "author" (the relation key), not "author_id" (the FK column)
|
|
80
|
+
const orderMatch = result.match(/propertiesOrder:\s*(\[[\s\S]*?\])/);
|
|
81
|
+
expect(orderMatch).toBeTruthy();
|
|
82
|
+
const orderBlock = orderMatch![1];
|
|
83
|
+
expect(orderBlock).toContain('"author"');
|
|
84
|
+
// author_id should NOT appear in propertiesOrder (it may appear in localKey elsewhere)
|
|
85
|
+
expect(orderBlock).not.toContain('"author_id"');
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe("ID detection", () => {
|
|
90
|
+
it("marks uuid PK as isId uuid", () => {
|
|
91
|
+
const meta = makeSimpleTable("users", [
|
|
92
|
+
mkCol("users", "id", { data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
|
|
93
|
+
]);
|
|
94
|
+
const result = generateCollectionFile("users", meta, [], new Set(), new Map([["users", meta]]), new Map());
|
|
95
|
+
expect(result).toContain('isId: "uuid"');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("marks integer PK as isId increment", () => {
|
|
99
|
+
const meta = makeSimpleTable("counters", [
|
|
100
|
+
mkCol("counters", "id", { data_type: "integer", udt_name: "int4", is_nullable: "NO", column_default: "nextval" }),
|
|
101
|
+
]);
|
|
102
|
+
const result = generateCollectionFile("counters", meta, [], new Set(), new Map([["counters", meta]]), new Map());
|
|
103
|
+
expect(result).toContain('isId: "increment"');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("flags composite primary keys with a comment", () => {
|
|
107
|
+
const meta = makeSimpleTable("scores", [
|
|
108
|
+
mkCol("scores", "user_id", { data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
|
|
109
|
+
mkCol("scores", "game_id", { data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
|
|
110
|
+
mkCol("scores", "score", { data_type: "integer", udt_name: "int4" }),
|
|
111
|
+
], ["user_id", "game_id"]);
|
|
112
|
+
const result = generateCollectionFile("scores", meta, [], new Set(), new Map([["scores", meta]]), new Map());
|
|
113
|
+
expect(result).toContain("composite primary key");
|
|
114
|
+
expect(result).toContain("user_id, game_id");
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe("validation.required", () => {
|
|
119
|
+
it("adds required when is_nullable is NO, not a PK, and no default", () => {
|
|
120
|
+
const meta = makeSimpleTable("items", [
|
|
121
|
+
mkCol("items", "id", { data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
|
|
122
|
+
mkCol("items", "name", { is_nullable: "NO" }),
|
|
123
|
+
]);
|
|
124
|
+
const result = generateCollectionFile("items", meta, [], new Set(), new Map([["items", meta]]), new Map());
|
|
125
|
+
// name should have required
|
|
126
|
+
expect(result).toContain("required: true");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("does NOT add required for nullable columns", () => {
|
|
130
|
+
const meta = makeSimpleTable("items", [
|
|
131
|
+
mkCol("items", "id", { data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
|
|
132
|
+
mkCol("items", "bio", { is_nullable: "YES" }),
|
|
133
|
+
]);
|
|
134
|
+
const result = generateCollectionFile("items", meta, [], new Set(), new Map([["items", meta]]), new Map());
|
|
135
|
+
const bioSection = result.split("bio:")[1].split("},")[0];
|
|
136
|
+
expect(bioSection).not.toContain("required");
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("does NOT add required for columns with defaults", () => {
|
|
140
|
+
const meta = makeSimpleTable("items", [
|
|
141
|
+
mkCol("items", "id", { data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
|
|
142
|
+
mkCol("items", "role", { is_nullable: "NO", column_default: "'user'" }),
|
|
143
|
+
]);
|
|
144
|
+
const result = generateCollectionFile("items", meta, [], new Set(), new Map([["items", meta]]), new Map());
|
|
145
|
+
const roleSection = result.split("role:")[1].split("},")[0];
|
|
146
|
+
expect(roleSection).not.toContain("required");
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe("enum support", () => {
|
|
151
|
+
it("generates enumValues for USER-DEFINED columns with matching enum", () => {
|
|
152
|
+
const enumMap = new Map([["order_status", ["pending", "shipped", "delivered"]]]);
|
|
153
|
+
const meta = makeSimpleTable("orders", [
|
|
154
|
+
mkCol("orders", "id", { data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
|
|
155
|
+
mkCol("orders", "status", { data_type: "USER-DEFINED", udt_name: "order_status" }),
|
|
156
|
+
]);
|
|
157
|
+
const result = generateCollectionFile("orders", meta, [], new Set(), new Map([["orders", meta]]), enumMap);
|
|
158
|
+
expect(result).toContain('enumValues:');
|
|
159
|
+
expect(result).toContain('{ id: "pending", label: "Pending" }');
|
|
160
|
+
expect(result).toContain('{ id: "shipped", label: "Shipped" }');
|
|
161
|
+
expect(result).toContain('{ id: "delivered", label: "Delivered" }');
|
|
162
|
+
expect(result).toContain('type: "string"');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("does NOT add enumValues for USER-DEFINED without matching enum", () => {
|
|
166
|
+
const meta = makeSimpleTable("things", [
|
|
167
|
+
mkCol("things", "id", { data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
|
|
168
|
+
mkCol("things", "geom", { data_type: "USER-DEFINED", udt_name: "geometry" }),
|
|
169
|
+
]);
|
|
170
|
+
const result = generateCollectionFile("things", meta, [], new Set(), new Map([["things", meta]]), new Map());
|
|
171
|
+
expect(result).not.toContain("enumValues");
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("humanizes enum value labels with underscores", () => {
|
|
175
|
+
const enumMap = new Map([["my_enum", ["in_progress", "on_hold"]]]);
|
|
176
|
+
const meta = makeSimpleTable("tasks", [
|
|
177
|
+
mkCol("tasks", "id", { data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
|
|
178
|
+
mkCol("tasks", "state", { data_type: "USER-DEFINED", udt_name: "my_enum" }),
|
|
179
|
+
]);
|
|
180
|
+
const result = generateCollectionFile("tasks", meta, [], new Set(), new Map([["tasks", meta]]), enumMap);
|
|
181
|
+
expect(result).toContain('label: "In Progress"');
|
|
182
|
+
expect(result).toContain('label: "On Hold"');
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe("date auto-value heuristics", () => {
|
|
187
|
+
it("sets autoValue on_create for created_at", () => {
|
|
188
|
+
const meta = makeSimpleTable("items", [
|
|
189
|
+
mkCol("items", "id", { data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
|
|
190
|
+
mkCol("items", "created_at", { data_type: "timestamp", udt_name: "timestamp" }),
|
|
191
|
+
]);
|
|
192
|
+
const result = generateCollectionFile("items", meta, [], new Set(), new Map([["items", meta]]), new Map());
|
|
193
|
+
expect(result).toContain('autoValue: "on_create"');
|
|
194
|
+
expect(result).toContain("hideFromCollection: true");
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("sets autoValue on_update for updated_at", () => {
|
|
198
|
+
const meta = makeSimpleTable("items", [
|
|
199
|
+
mkCol("items", "id", { data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
|
|
200
|
+
mkCol("items", "updated_at", { data_type: "timestamp", udt_name: "timestamp" }),
|
|
201
|
+
]);
|
|
202
|
+
const result = generateCollectionFile("items", meta, [], new Set(), new Map([["items", meta]]), new Map());
|
|
203
|
+
expect(result).toContain('autoValue: "on_update"');
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("sets autoValue on_create for columns with now() default", () => {
|
|
207
|
+
const meta = makeSimpleTable("items", [
|
|
208
|
+
mkCol("items", "id", { data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
|
|
209
|
+
mkCol("items", "published_at", { data_type: "timestamp", udt_name: "timestamp", column_default: "now()" }),
|
|
210
|
+
]);
|
|
211
|
+
const result = generateCollectionFile("items", meta, [], new Set(), new Map([["items", meta]]), new Map());
|
|
212
|
+
expect(result).toContain('autoValue: "on_create"');
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
describe("string sub-type heuristics", () => {
|
|
217
|
+
it("adds storage config for image-like column names", () => {
|
|
218
|
+
for (const name of ["profile_image", "avatar", "photo_url", "logo", "cover_image"]) {
|
|
219
|
+
const meta = makeSimpleTable("t", [
|
|
220
|
+
mkCol("t", "id", { data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
|
|
221
|
+
mkCol("t", name),
|
|
222
|
+
]);
|
|
223
|
+
const result = generateCollectionFile("t", meta, [], new Set(), new Map([["t", meta]]), new Map());
|
|
224
|
+
expect(result).toContain("storagePath:");
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("adds multiline for description/summary/excerpt", () => {
|
|
229
|
+
for (const name of ["description", "summary", "excerpt"]) {
|
|
230
|
+
const meta = makeSimpleTable("t", [
|
|
231
|
+
mkCol("t", "id", { data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
|
|
232
|
+
mkCol("t", name),
|
|
233
|
+
]);
|
|
234
|
+
const result = generateCollectionFile("t", meta, [], new Set(), new Map([["t", meta]]), new Map());
|
|
235
|
+
expect(result).toContain("multiline: true");
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it("adds markdown for content/body", () => {
|
|
240
|
+
for (const name of ["content", "body"]) {
|
|
241
|
+
const meta = makeSimpleTable("t", [
|
|
242
|
+
mkCol("t", "id", { data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
|
|
243
|
+
mkCol("t", name),
|
|
244
|
+
]);
|
|
245
|
+
const result = generateCollectionFile("t", meta, [], new Set(), new Map([["t", meta]]), new Map());
|
|
246
|
+
expect(result).toContain("markdown: true");
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("adds multiline for text data_type columns", () => {
|
|
251
|
+
const meta = makeSimpleTable("t", [
|
|
252
|
+
mkCol("t", "id", { data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
|
|
253
|
+
mkCol("t", "notes", { data_type: "text", udt_name: "text" }),
|
|
254
|
+
]);
|
|
255
|
+
const result = generateCollectionFile("t", meta, [], new Set(), new Map([["t", meta]]), new Map());
|
|
256
|
+
expect(result).toContain("multiline: true");
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it("does NOT apply string heuristics to enum columns", () => {
|
|
260
|
+
const enumMap = new Map([["img_type", ["png", "jpg"]]]);
|
|
261
|
+
const meta = makeSimpleTable("t", [
|
|
262
|
+
mkCol("t", "id", { data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
|
|
263
|
+
mkCol("t", "image_type", { data_type: "USER-DEFINED", udt_name: "img_type" }),
|
|
264
|
+
]);
|
|
265
|
+
const result = generateCollectionFile("t", meta, [], new Set(), new Map([["t", meta]]), enumMap);
|
|
266
|
+
expect(result).not.toContain("storagePath");
|
|
267
|
+
expect(result).toContain("enumValues");
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
describe("owning relation (FK -> other table)", () => {
|
|
272
|
+
it("generates a one-to-one owning relation with correct import", () => {
|
|
273
|
+
const fks = [mkFk("posts", "author_id", "users")];
|
|
274
|
+
const meta = makeSimpleTable("posts", [
|
|
275
|
+
mkCol("posts", "id", { data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
|
|
276
|
+
mkCol("posts", "author_id"),
|
|
277
|
+
], ["id"], fks);
|
|
278
|
+
const result = generateCollectionFile("posts", meta, fks, new Set(), new Map([["posts", meta]]), new Map());
|
|
279
|
+
expect(result).toContain('import usersCollection from "./users"');
|
|
280
|
+
expect(result).toContain('author: {');
|
|
281
|
+
expect(result).toContain('cardinality: "one"');
|
|
282
|
+
expect(result).toContain('direction: "owning"');
|
|
283
|
+
expect(result).toContain('localKey: "author_id"');
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
describe("inverse relation (other table -> this table)", () => {
|
|
288
|
+
it("generates a one-to-many inverse relation", () => {
|
|
289
|
+
const allFks: ForeignKeyRow[] = [mkFk("comments", "post_id", "posts")];
|
|
290
|
+
const meta = makeSimpleTable("posts", [
|
|
291
|
+
mkCol("posts", "id", { data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
|
|
292
|
+
]);
|
|
293
|
+
const result = generateCollectionFile("posts", meta, allFks, new Set(), new Map([["posts", meta]]), new Map());
|
|
294
|
+
expect(result).toContain('import commentsCollection from "./comments"');
|
|
295
|
+
expect(result).toContain('cardinality: "many"');
|
|
296
|
+
expect(result).toContain('direction: "inverse"');
|
|
297
|
+
expect(result).toContain('inverseRelationName: "post"');
|
|
298
|
+
expect(result).toContain('foreignKeyOnTarget: "post_id"');
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
describe("many-to-many relations", () => {
|
|
303
|
+
it("generates owning M2M with through config for alphabetically-first table", () => {
|
|
304
|
+
const jtFks: ForeignKeyRow[] = [
|
|
305
|
+
mkFk("articles_tags", "article_id", "articles"),
|
|
306
|
+
mkFk("articles_tags", "tag_id", "tags"),
|
|
307
|
+
];
|
|
308
|
+
const jtMeta: TableMeta = {
|
|
309
|
+
name: "articles_tags", pks: [],
|
|
310
|
+
columns: [mkCol("articles_tags", "article_id"), mkCol("articles_tags", "tag_id")],
|
|
311
|
+
fks: jtFks,
|
|
312
|
+
};
|
|
313
|
+
const articlesMeta = makeSimpleTable("articles", [
|
|
314
|
+
mkCol("articles", "id", { data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
|
|
315
|
+
]);
|
|
316
|
+
const tablesMap = new Map([["articles", articlesMeta], ["articles_tags", jtMeta]]);
|
|
317
|
+
const joinTables = new Set(["articles_tags"]);
|
|
318
|
+
|
|
319
|
+
const result = generateCollectionFile("articles", articlesMeta, [], joinTables, tablesMap, new Map());
|
|
320
|
+
expect(result).toContain('direction: "owning"');
|
|
321
|
+
expect(result).toContain('table: "articles_tags"');
|
|
322
|
+
expect(result).toContain('sourceColumn: "article_id"');
|
|
323
|
+
expect(result).toContain('targetColumn: "tag_id"');
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it("generates inverse M2M for alphabetically-second table", () => {
|
|
327
|
+
const jtFks: ForeignKeyRow[] = [
|
|
328
|
+
mkFk("articles_tags", "article_id", "articles"),
|
|
329
|
+
mkFk("articles_tags", "tag_id", "tags"),
|
|
330
|
+
];
|
|
331
|
+
const jtMeta: TableMeta = {
|
|
332
|
+
name: "articles_tags", pks: [],
|
|
333
|
+
columns: [mkCol("articles_tags", "article_id"), mkCol("articles_tags", "tag_id")],
|
|
334
|
+
fks: jtFks,
|
|
335
|
+
};
|
|
336
|
+
const tagsMeta = makeSimpleTable("tags", [
|
|
337
|
+
mkCol("tags", "id", { data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
|
|
338
|
+
]);
|
|
339
|
+
const tablesMap = new Map([["tags", tagsMeta], ["articles_tags", jtMeta]]);
|
|
340
|
+
const joinTables = new Set(["articles_tags"]);
|
|
341
|
+
|
|
342
|
+
const result = generateCollectionFile("tags", tagsMeta, [], joinTables, tablesMap, new Map());
|
|
343
|
+
expect(result).toContain('direction: "inverse"');
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
describe("self-referencing M2M", () => {
|
|
348
|
+
it("generates self-ref M2M with _via_ property name", () => {
|
|
349
|
+
const jtFks: ForeignKeyRow[] = [
|
|
350
|
+
mkFk("user_friends", "user_id", "users"),
|
|
351
|
+
mkFk("user_friends", "friend_id", "users"),
|
|
352
|
+
];
|
|
353
|
+
const jtMeta: TableMeta = {
|
|
354
|
+
name: "user_friends", pks: [],
|
|
355
|
+
columns: [mkCol("user_friends", "user_id"), mkCol("user_friends", "friend_id")],
|
|
356
|
+
fks: jtFks,
|
|
357
|
+
};
|
|
358
|
+
const usersMeta = makeSimpleTable("users", [
|
|
359
|
+
mkCol("users", "id", { data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
|
|
360
|
+
]);
|
|
361
|
+
const tablesMap = new Map([["users", usersMeta], ["user_friends", jtMeta]]);
|
|
362
|
+
const joinTables = new Set(["user_friends"]);
|
|
363
|
+
|
|
364
|
+
const result = generateCollectionFile("users", usersMeta, [], joinTables, tablesMap, new Map());
|
|
365
|
+
expect(result).toContain("users_via_friend");
|
|
366
|
+
expect(result).toContain('table: "user_friends"');
|
|
367
|
+
expect(result).toContain('sourceColumn: "user_id"');
|
|
368
|
+
expect(result).toContain('targetColumn: "friend_id"');
|
|
369
|
+
// Self-ref should reference its own collection variable
|
|
370
|
+
expect(result).toContain("usersCollection");
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
describe("icon mapping", () => {
|
|
375
|
+
it("assigns correct icons based on table name", () => {
|
|
376
|
+
const make = (name: string) => {
|
|
377
|
+
const meta = makeSimpleTable(name, [mkCol(name, "id", { data_type: "uuid", udt_name: "uuid", is_nullable: "NO" })]);
|
|
378
|
+
return generateCollectionFile(name, meta, [], new Set(), new Map([[name, meta]]), new Map());
|
|
379
|
+
};
|
|
380
|
+
expect(make("users")).toContain('icon: "Users"');
|
|
381
|
+
expect(make("blog_posts")).toContain('icon: "FileText"');
|
|
382
|
+
expect(make("products")).toContain('icon: "Package"');
|
|
383
|
+
expect(make("orders")).toContain('icon: "ShoppingCart"');
|
|
384
|
+
expect(make("app_settings")).toContain('icon: "Settings"');
|
|
385
|
+
expect(make("random_xyz")).toContain('icon: "Database"');
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
describe("humanized names", () => {
|
|
390
|
+
it("uses Title Case for collection name from snake_case table", () => {
|
|
391
|
+
const meta = makeSimpleTable("user_profiles", [
|
|
392
|
+
mkCol("user_profiles", "id", { data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
|
|
393
|
+
]);
|
|
394
|
+
const result = generateCollectionFile("user_profiles", meta, [], new Set(), new Map([["user_profiles", meta]]), new Map());
|
|
395
|
+
expect(result).toContain('name: "User Profiles"');
|
|
396
|
+
expect(result).toContain('singularName: "User Profile"');
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it("humanizes property names", () => {
|
|
400
|
+
const meta = makeSimpleTable("t", [
|
|
401
|
+
mkCol("t", "id", { data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
|
|
402
|
+
mkCol("t", "first_name"),
|
|
403
|
+
]);
|
|
404
|
+
const result = generateCollectionFile("t", meta, [], new Set(), new Map([["t", meta]]), new Map());
|
|
405
|
+
expect(result).toContain('name: "First Name"');
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
describe("import generation", () => {
|
|
410
|
+
it("always imports PostgresCollection", () => {
|
|
411
|
+
const meta = makeSimpleTable("t", [mkCol("t", "id", { data_type: "uuid", udt_name: "uuid", is_nullable: "NO" })]);
|
|
412
|
+
const result = generateCollectionFile("t", meta, [], new Set(), new Map([["t", meta]]), new Map());
|
|
413
|
+
expect(result).toContain('import { PostgresCollection } from "@rebasepro/types"');
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
it("does not duplicate imports for multiple relations to same table", () => {
|
|
417
|
+
const fks = [mkFk("posts", "author_id", "users"), mkFk("posts", "reviewer_id", "users")];
|
|
418
|
+
const meta = makeSimpleTable("posts", [
|
|
419
|
+
mkCol("posts", "id", { data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
|
|
420
|
+
mkCol("posts", "author_id"),
|
|
421
|
+
mkCol("posts", "reviewer_id"),
|
|
422
|
+
], ["id"], fks);
|
|
423
|
+
const result = generateCollectionFile("posts", meta, fks, new Set(), new Map([["posts", meta]]), new Map());
|
|
424
|
+
const importMatches = result.match(/import usersCollection/g);
|
|
425
|
+
expect(importMatches).toHaveLength(1);
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
describe("export default", () => {
|
|
430
|
+
it("exports the collection variable as default", () => {
|
|
431
|
+
const meta = makeSimpleTable("orders", [mkCol("orders", "id", { data_type: "uuid", udt_name: "uuid", is_nullable: "NO" })]);
|
|
432
|
+
const result = generateCollectionFile("orders", meta, [], new Set(), new Map([["orders", meta]]), new Map());
|
|
433
|
+
expect(result).toContain("export default ordersCollection;");
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
});
|