@rebasepro/server-postgresql 0.0.1-canary.eae7889 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.es.js +458 -201
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +458 -201
- package/dist/index.umd.js.map +1 -1
- package/dist/server-postgresql/src/PostgresBackendDriver.d.ts +8 -1
- package/dist/server-postgresql/src/schema/introspect-db-inference.d.ts +5 -0
- package/dist/server-postgresql/src/schema/introspect-db-logic.d.ts +117 -0
- package/dist/server-postgresql/src/schema/introspect-db.d.ts +1 -0
- package/dist/server-postgresql/src/services/EntityPersistService.d.ts +9 -0
- package/dist/types/src/controllers/auth.d.ts +8 -2
- package/dist/types/src/controllers/client.d.ts +13 -0
- package/dist/types/src/controllers/collection_registry.d.ts +2 -1
- package/dist/types/src/controllers/data_driver.d.ts +36 -1
- package/dist/types/src/controllers/navigation.d.ts +18 -6
- package/dist/types/src/controllers/registry.d.ts +9 -1
- package/dist/types/src/controllers/side_entity_controller.d.ts +7 -0
- package/dist/types/src/rebase_context.d.ts +17 -0
- package/dist/types/src/types/backend_hooks.d.ts +187 -0
- package/dist/types/src/types/collections.d.ts +31 -11
- package/dist/types/src/types/component_ref.d.ts +47 -0
- package/dist/types/src/types/cron.d.ts +1 -1
- package/dist/types/src/types/entity_views.d.ts +6 -7
- package/dist/types/src/types/formex.d.ts +40 -0
- package/dist/types/src/types/index.d.ts +3 -0
- package/dist/types/src/types/plugins.d.ts +6 -3
- package/dist/types/src/types/properties.d.ts +72 -88
- package/dist/types/src/types/slots.d.ts +20 -10
- package/dist/types/src/types/translations.d.ts +6 -0
- package/examples/sdk-demo/node_modules/esbuild/LICENSE.md +21 -0
- package/examples/sdk-demo/node_modules/esbuild/README.md +3 -0
- package/examples/sdk-demo/node_modules/esbuild/bin/esbuild +223 -0
- package/examples/sdk-demo/node_modules/esbuild/install.js +289 -0
- package/examples/sdk-demo/node_modules/esbuild/lib/main.d.ts +716 -0
- package/examples/sdk-demo/node_modules/esbuild/lib/main.js +2242 -0
- package/examples/sdk-demo/node_modules/esbuild/package.json +49 -0
- package/package.json +6 -5
- package/src/PostgresBackendDriver.ts +32 -6
- package/src/cli.ts +68 -2
- package/src/data-transformer.ts +84 -1
- package/src/schema/doctor.ts +14 -2
- package/src/schema/generate-drizzle-schema-logic.ts +59 -30
- package/src/schema/introspect-db-inference.ts +238 -0
- package/src/schema/introspect-db-logic.ts +896 -0
- package/src/schema/introspect-db.ts +254 -0
- package/src/services/EntityFetchService.ts +16 -0
- package/src/services/EntityPersistService.ts +95 -13
- package/test/generate-drizzle-schema.test.ts +342 -0
- package/test/introspect-db-generation.test.ts +458 -0
- package/test/introspect-db-utils.test.ts +392 -0
- package/test/property-ordering.test.ts +395 -0
- package/test/relations.test.ts +4 -4
- package/test/unmapped-tables-safety.test.ts +345 -0
- package/jest-all.log +0 -3128
- package/jest.log +0 -49
- package/scratch.ts +0 -41
- package/test-drizzle-bug.ts +0 -18
- package/test-drizzle-out/0000_cultured_freak.sql +0 -7
- package/test-drizzle-out/0001_tiresome_professor_monster.sql +0 -1
- package/test-drizzle-out/meta/0000_snapshot.json +0 -55
- package/test-drizzle-out/meta/0001_snapshot.json +0 -63
- package/test-drizzle-out/meta/_journal.json +0 -20
- package/test-drizzle-prompt.sh +0 -2
- package/test-policy-prompt.sh +0 -3
- package/test-programmatic.ts +0 -30
- package/test-programmatic2.ts +0 -59
- package/test-schema-no-policies.ts +0 -12
- package/test_drizzle_mock.js +0 -3
- package/test_find_changed.mjs +0 -32
- package/test_hash.js +0 -14
- package/test_output.txt +0 -3145
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import {
|
|
2
|
+
computePropertyPriority, sortPropertiesOrder,
|
|
3
|
+
PropertyOrderingContext, PropertyOrderEntry,
|
|
4
|
+
} from "../src/schema/introspect-db-logic";
|
|
5
|
+
|
|
6
|
+
// ── Helpers ───────────────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
function mkCtx(overrides: Partial<PropertyOrderingContext> = {}): PropertyOrderingContext {
|
|
9
|
+
return {
|
|
10
|
+
propType: "string",
|
|
11
|
+
isPk: false,
|
|
12
|
+
isEnum: false,
|
|
13
|
+
isStorage: false,
|
|
14
|
+
pgDataType: "character varying",
|
|
15
|
+
originalIndex: 0,
|
|
16
|
+
...overrides,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function mkEntry(key: string, overrides: Partial<PropertyOrderingContext> = {}): PropertyOrderEntry {
|
|
21
|
+
return { key, ctx: mkCtx(overrides) };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
25
|
+
// computePropertyPriority() — tier assignment
|
|
26
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
27
|
+
describe("computePropertyPriority", () => {
|
|
28
|
+
describe("Tier 0: Identity / Primary keys", () => {
|
|
29
|
+
it("ranks 'id' PK as tier 0 (score 0)", () => {
|
|
30
|
+
const score = computePropertyPriority("id", mkCtx({ isPk: true }));
|
|
31
|
+
expect(score).toBeGreaterThanOrEqual(0);
|
|
32
|
+
expect(score).toBeLessThan(10);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("ranks any PK column in tier 0", () => {
|
|
36
|
+
const score = computePropertyPriority("uuid", mkCtx({ isPk: true }));
|
|
37
|
+
expect(score).toBeGreaterThanOrEqual(0);
|
|
38
|
+
expect(score).toBeLessThan(10);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("ranks unknown PK name with fallback score 5", () => {
|
|
42
|
+
const score = computePropertyPriority("pk_col", mkCtx({ isPk: true }));
|
|
43
|
+
expect(score).toBeGreaterThanOrEqual(5);
|
|
44
|
+
expect(score).toBeLessThan(10);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("Tier 1: Title / Name fields", () => {
|
|
49
|
+
it.each(["name", "title", "label", "display_name", "headline", "subject", "heading"])(
|
|
50
|
+
"ranks '%s' in tier 1 (10-19)",
|
|
51
|
+
(col) => {
|
|
52
|
+
const score = computePropertyPriority(col, mkCtx());
|
|
53
|
+
expect(score).toBeGreaterThanOrEqual(10);
|
|
54
|
+
expect(score).toBeLessThan(20);
|
|
55
|
+
},
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
it("ranks 'product_name' as partial match in tier 1b (17-19)", () => {
|
|
59
|
+
const score = computePropertyPriority("product_name", mkCtx());
|
|
60
|
+
expect(score).toBeGreaterThanOrEqual(17);
|
|
61
|
+
expect(score).toBeLessThan(20);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("ranks 'page_title' as partial match in tier 1b", () => {
|
|
65
|
+
const score = computePropertyPriority("page_title", mkCtx());
|
|
66
|
+
expect(score).toBeGreaterThanOrEqual(17);
|
|
67
|
+
expect(score).toBeLessThan(20);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe("Tier 2: Human identity fields", () => {
|
|
72
|
+
it.each(["first_name", "last_name", "full_name", "username", "email", "phone", "phone_number"])(
|
|
73
|
+
"ranks '%s' in tier 2 (20-29)",
|
|
74
|
+
(col) => {
|
|
75
|
+
const score = computePropertyPriority(col, mkCtx());
|
|
76
|
+
expect(score).toBeGreaterThanOrEqual(20);
|
|
77
|
+
expect(score).toBeLessThan(30);
|
|
78
|
+
},
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
it("ranks first_name before last_name", () => {
|
|
82
|
+
const first = computePropertyPriority("first_name", mkCtx());
|
|
83
|
+
const last = computePropertyPriority("last_name", mkCtx());
|
|
84
|
+
expect(first).toBeLessThan(last);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe("Tier 3: Core descriptors", () => {
|
|
89
|
+
it.each(["slug", "code", "sku", "type", "status", "role", "category"])(
|
|
90
|
+
"ranks '%s' in tier 3 (30-39)",
|
|
91
|
+
(col) => {
|
|
92
|
+
const score = computePropertyPriority(col, mkCtx());
|
|
93
|
+
expect(score).toBeGreaterThanOrEqual(30);
|
|
94
|
+
expect(score).toBeLessThan(40);
|
|
95
|
+
},
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe("Tier 4: Short text, enums, booleans", () => {
|
|
100
|
+
it("ranks enum fields in tier 4", () => {
|
|
101
|
+
const score = computePropertyPriority("payment_method", mkCtx({ isEnum: true }));
|
|
102
|
+
expect(score).toBeGreaterThanOrEqual(40);
|
|
103
|
+
expect(score).toBeLessThan(50);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("ranks boolean fields in tier 4", () => {
|
|
107
|
+
const score = computePropertyPriority("active", mkCtx({ propType: "boolean" }));
|
|
108
|
+
expect(score).toBeGreaterThanOrEqual(40);
|
|
109
|
+
expect(score).toBeLessThan(50);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("ranks short string (varchar) in tier 4", () => {
|
|
113
|
+
const score = computePropertyPriority("color", mkCtx({ propType: "string", pgDataType: "character varying" }));
|
|
114
|
+
expect(score).toBeGreaterThanOrEqual(40);
|
|
115
|
+
expect(score).toBeLessThan(50);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe("Tier 5: Numbers & user-facing dates", () => {
|
|
120
|
+
it("ranks number fields in tier 5", () => {
|
|
121
|
+
const score = computePropertyPriority("price", mkCtx({ propType: "number" }));
|
|
122
|
+
expect(score).toBeGreaterThanOrEqual(50);
|
|
123
|
+
expect(score).toBeLessThan(60);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("ranks non-system date fields in tier 5", () => {
|
|
127
|
+
const score = computePropertyPriority("published_at", mkCtx({ propType: "date" }));
|
|
128
|
+
expect(score).toBeGreaterThanOrEqual(50);
|
|
129
|
+
expect(score).toBeLessThan(60);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe("Tier 6: Relations", () => {
|
|
134
|
+
it("ranks relation fields in tier 6", () => {
|
|
135
|
+
const score = computePropertyPriority("author", mkCtx({ propType: "relation" }));
|
|
136
|
+
expect(score).toBeGreaterThanOrEqual(60);
|
|
137
|
+
expect(score).toBeLessThan(70);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe("Tier 7: Long text fields", () => {
|
|
142
|
+
it.each(["description", "summary", "excerpt", "bio", "about"])(
|
|
143
|
+
"ranks '%s' in tier 7 (70-79)",
|
|
144
|
+
(col) => {
|
|
145
|
+
const score = computePropertyPriority(col, mkCtx());
|
|
146
|
+
expect(score).toBeGreaterThanOrEqual(70);
|
|
147
|
+
expect(score).toBeLessThan(80);
|
|
148
|
+
},
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe("Tier 8: Rich content fields", () => {
|
|
153
|
+
it.each(["content", "body", "html"])(
|
|
154
|
+
"ranks '%s' in tier 8 (80-89)",
|
|
155
|
+
(col) => {
|
|
156
|
+
const score = computePropertyPriority(col, mkCtx());
|
|
157
|
+
expect(score).toBeGreaterThanOrEqual(80);
|
|
158
|
+
expect(score).toBeLessThan(90);
|
|
159
|
+
},
|
|
160
|
+
);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe("Tier 9: Media / storage fields", () => {
|
|
164
|
+
it("ranks storage fields in tier 9", () => {
|
|
165
|
+
const score = computePropertyPriority("profile_image", mkCtx({ isStorage: true }));
|
|
166
|
+
expect(score).toBeGreaterThanOrEqual(90);
|
|
167
|
+
expect(score).toBeLessThan(100);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it.each(["avatar", "photo_url", "logo", "cover_image", "thumbnail"])(
|
|
171
|
+
"ranks '%s' (media pattern) in tier 9",
|
|
172
|
+
(col) => {
|
|
173
|
+
const score = computePropertyPriority(col, mkCtx());
|
|
174
|
+
expect(score).toBeGreaterThanOrEqual(90);
|
|
175
|
+
expect(score).toBeLessThan(100);
|
|
176
|
+
},
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
it("ranks URL-suffix fields in tier 9", () => {
|
|
180
|
+
const score = computePropertyPriority("website_url", mkCtx());
|
|
181
|
+
expect(score).toBeGreaterThanOrEqual(90);
|
|
182
|
+
expect(score).toBeLessThan(100);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe("Tier 10: JSON / Map types", () => {
|
|
187
|
+
it("ranks map types in tier 10", () => {
|
|
188
|
+
const score = computePropertyPriority("metadata", mkCtx({ propType: "map" }));
|
|
189
|
+
expect(score).toBeGreaterThanOrEqual(100);
|
|
190
|
+
expect(score).toBeLessThan(110);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("ranks unknown map field names slightly higher", () => {
|
|
194
|
+
const known = computePropertyPriority("metadata", mkCtx({ propType: "map" }));
|
|
195
|
+
const unknown = computePropertyPriority("weird_json", mkCtx({ propType: "map" }));
|
|
196
|
+
expect(unknown).toBeGreaterThan(known);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
describe("Tier 11: Array types", () => {
|
|
201
|
+
it("ranks array types in tier 11", () => {
|
|
202
|
+
const score = computePropertyPriority("tags", mkCtx({ propType: "array" }));
|
|
203
|
+
expect(score).toBeGreaterThanOrEqual(110);
|
|
204
|
+
expect(score).toBeLessThan(120);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe("Tier 12: System timestamps", () => {
|
|
209
|
+
it.each(["created_at", "updated_at", "deleted_at", "archived_at"])(
|
|
210
|
+
"ranks '%s' in tier 12 (120-129)",
|
|
211
|
+
(col) => {
|
|
212
|
+
const score = computePropertyPriority(col, mkCtx({ propType: "date" }));
|
|
213
|
+
expect(score).toBeGreaterThanOrEqual(120);
|
|
214
|
+
expect(score).toBeLessThan(130);
|
|
215
|
+
},
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
it("ranks created_at before updated_at", () => {
|
|
219
|
+
const created = computePropertyPriority("created_at", mkCtx({ propType: "date" }));
|
|
220
|
+
const updated = computePropertyPriority("updated_at", mkCtx({ propType: "date" }));
|
|
221
|
+
expect(created).toBeLessThan(updated);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("ranks updated_at before deleted_at", () => {
|
|
225
|
+
const updated = computePropertyPriority("updated_at", mkCtx({ propType: "date" }));
|
|
226
|
+
const deleted = computePropertyPriority("deleted_at", mkCtx({ propType: "date" }));
|
|
227
|
+
expect(updated).toBeLessThan(deleted);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe("tiebreaking with originalIndex", () => {
|
|
232
|
+
it("preserves original column order within the same tier", () => {
|
|
233
|
+
const a = computePropertyPriority("color", mkCtx({ originalIndex: 0 }));
|
|
234
|
+
const b = computePropertyPriority("size", mkCtx({ originalIndex: 1 }));
|
|
235
|
+
expect(a).toBeLessThan(b);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it("tiebreaker is fractional (doesn't cross tier boundaries)", () => {
|
|
239
|
+
const lastInTier = computePropertyPriority("color", mkCtx({ originalIndex: 9999 }));
|
|
240
|
+
// Should still be in tier 4 (42.x) not tier 5 (50+)
|
|
241
|
+
expect(lastInTier).toBeLessThan(50);
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
247
|
+
// sortPropertiesOrder() — full sorting integration
|
|
248
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
249
|
+
describe("sortPropertiesOrder", () => {
|
|
250
|
+
it("places 'id' before 'name' before generic fields", () => {
|
|
251
|
+
const entries: PropertyOrderEntry[] = [
|
|
252
|
+
mkEntry("created_at", { propType: "date", originalIndex: 3 }),
|
|
253
|
+
mkEntry("name", { originalIndex: 1 }),
|
|
254
|
+
mkEntry("id", { isPk: true, originalIndex: 0 }),
|
|
255
|
+
mkEntry("status", { originalIndex: 2 }),
|
|
256
|
+
];
|
|
257
|
+
const result = sortPropertiesOrder(entries);
|
|
258
|
+
expect(result).toEqual(["id", "name", "status", "created_at"]);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it("sorts a realistic user table correctly", () => {
|
|
262
|
+
const entries: PropertyOrderEntry[] = [
|
|
263
|
+
mkEntry("id", { isPk: true, propType: "string", pgDataType: "uuid", originalIndex: 0 }),
|
|
264
|
+
mkEntry("created_at", { propType: "date", originalIndex: 1 }),
|
|
265
|
+
mkEntry("updated_at", { propType: "date", originalIndex: 2 }),
|
|
266
|
+
mkEntry("email", { originalIndex: 3 }),
|
|
267
|
+
mkEntry("first_name", { originalIndex: 4 }),
|
|
268
|
+
mkEntry("last_name", { originalIndex: 5 }),
|
|
269
|
+
mkEntry("avatar", { isStorage: true, originalIndex: 6 }),
|
|
270
|
+
mkEntry("role", { isEnum: true, originalIndex: 7 }),
|
|
271
|
+
mkEntry("active", { propType: "boolean", originalIndex: 8 }),
|
|
272
|
+
mkEntry("bio", { pgDataType: "text", originalIndex: 9 }),
|
|
273
|
+
mkEntry("metadata", { propType: "map", pgDataType: "jsonb", originalIndex: 10 }),
|
|
274
|
+
];
|
|
275
|
+
const result = sortPropertiesOrder(entries);
|
|
276
|
+
expect(result).toEqual([
|
|
277
|
+
"id", // tier 0: PK
|
|
278
|
+
"first_name", // tier 2: human identity
|
|
279
|
+
"last_name", // tier 2: human identity
|
|
280
|
+
"email", // tier 2: human identity
|
|
281
|
+
"role", // tier 4: enum
|
|
282
|
+
"active", // tier 4: boolean
|
|
283
|
+
"bio", // tier 7: long text
|
|
284
|
+
"avatar", // tier 9: storage
|
|
285
|
+
"metadata", // tier 10: map
|
|
286
|
+
"created_at", // tier 12: system timestamp
|
|
287
|
+
"updated_at", // tier 12: system timestamp
|
|
288
|
+
]);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it("sorts a realistic product table correctly", () => {
|
|
292
|
+
const entries: PropertyOrderEntry[] = [
|
|
293
|
+
mkEntry("id", { isPk: true, propType: "number", pgDataType: "integer", originalIndex: 0 }),
|
|
294
|
+
mkEntry("created_at", { propType: "date", originalIndex: 1 }),
|
|
295
|
+
mkEntry("sku", { originalIndex: 2 }),
|
|
296
|
+
mkEntry("name", { originalIndex: 3 }),
|
|
297
|
+
mkEntry("description", { pgDataType: "text", originalIndex: 4 }),
|
|
298
|
+
mkEntry("price", { propType: "number", originalIndex: 5 }),
|
|
299
|
+
mkEntry("category", { propType: "relation", originalIndex: 6 }),
|
|
300
|
+
mkEntry("cover_image", { isStorage: true, originalIndex: 7 }),
|
|
301
|
+
mkEntry("active", { propType: "boolean", originalIndex: 8 }),
|
|
302
|
+
];
|
|
303
|
+
const result = sortPropertiesOrder(entries);
|
|
304
|
+
expect(result).toEqual([
|
|
305
|
+
"id", // tier 0: PK
|
|
306
|
+
"name", // tier 1: title/name
|
|
307
|
+
"sku", // tier 3: descriptor
|
|
308
|
+
"category", // tier 3: descriptor (name match overrides relation tier)
|
|
309
|
+
"active", // tier 4: boolean
|
|
310
|
+
"price", // tier 5: number
|
|
311
|
+
"description", // tier 7: long text
|
|
312
|
+
"cover_image", // tier 9: storage
|
|
313
|
+
"created_at", // tier 12: system timestamp
|
|
314
|
+
]);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it("sorts a blog post table correctly", () => {
|
|
318
|
+
const entries: PropertyOrderEntry[] = [
|
|
319
|
+
mkEntry("id", { isPk: true, propType: "string", pgDataType: "uuid", originalIndex: 0 }),
|
|
320
|
+
mkEntry("updated_at", { propType: "date", originalIndex: 1 }),
|
|
321
|
+
mkEntry("created_at", { propType: "date", originalIndex: 2 }),
|
|
322
|
+
mkEntry("content", { pgDataType: "text", originalIndex: 3 }),
|
|
323
|
+
mkEntry("title", { originalIndex: 4 }),
|
|
324
|
+
mkEntry("slug", { originalIndex: 5 }),
|
|
325
|
+
mkEntry("status", { isEnum: true, originalIndex: 6 }),
|
|
326
|
+
mkEntry("author", { propType: "relation", originalIndex: 7 }),
|
|
327
|
+
mkEntry("published_at", { propType: "date", originalIndex: 8 }),
|
|
328
|
+
mkEntry("cover_image", { isStorage: true, originalIndex: 9 }),
|
|
329
|
+
mkEntry("excerpt", { pgDataType: "text", originalIndex: 10 }),
|
|
330
|
+
];
|
|
331
|
+
const result = sortPropertiesOrder(entries);
|
|
332
|
+
expect(result).toEqual([
|
|
333
|
+
"id", // tier 0: PK
|
|
334
|
+
"title", // tier 1: title
|
|
335
|
+
"slug", // tier 3: descriptor
|
|
336
|
+
"status", // tier 4: enum
|
|
337
|
+
"published_at", // tier 5: user-facing date
|
|
338
|
+
"author", // tier 6: relation
|
|
339
|
+
"excerpt", // tier 7: long text
|
|
340
|
+
"content", // tier 8: rich content
|
|
341
|
+
"cover_image", // tier 9: storage
|
|
342
|
+
"created_at", // tier 12: system timestamp
|
|
343
|
+
"updated_at", // tier 12: system timestamp
|
|
344
|
+
]);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it("preserves original order for properties in the same tier", () => {
|
|
348
|
+
const entries: PropertyOrderEntry[] = [
|
|
349
|
+
mkEntry("color", { propType: "string", pgDataType: "character varying", originalIndex: 0 }),
|
|
350
|
+
mkEntry("size", { propType: "string", pgDataType: "character varying", originalIndex: 1 }),
|
|
351
|
+
mkEntry("weight", { propType: "string", pgDataType: "character varying", originalIndex: 2 }),
|
|
352
|
+
];
|
|
353
|
+
const result = sortPropertiesOrder(entries);
|
|
354
|
+
// All three are tier 4 (short strings), should preserve original order
|
|
355
|
+
expect(result).toEqual(["color", "size", "weight"]);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it("handles tables with only system columns", () => {
|
|
359
|
+
const entries: PropertyOrderEntry[] = [
|
|
360
|
+
mkEntry("id", { isPk: true, originalIndex: 0 }),
|
|
361
|
+
mkEntry("created_at", { propType: "date", originalIndex: 1 }),
|
|
362
|
+
mkEntry("updated_at", { propType: "date", originalIndex: 2 }),
|
|
363
|
+
];
|
|
364
|
+
const result = sortPropertiesOrder(entries);
|
|
365
|
+
expect(result).toEqual(["id", "created_at", "updated_at"]);
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it("handles partial name matches (product_name, page_title)", () => {
|
|
369
|
+
const entries: PropertyOrderEntry[] = [
|
|
370
|
+
mkEntry("id", { isPk: true, originalIndex: 0 }),
|
|
371
|
+
mkEntry("product_name", { originalIndex: 1 }),
|
|
372
|
+
mkEntry("category", { originalIndex: 2 }),
|
|
373
|
+
mkEntry("price", { propType: "number", originalIndex: 3 }),
|
|
374
|
+
];
|
|
375
|
+
const result = sortPropertiesOrder(entries);
|
|
376
|
+
// product_name should come after id but before category and price
|
|
377
|
+
expect(result[0]).toBe("id");
|
|
378
|
+
expect(result[1]).toBe("product_name");
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it("handles mixed URL and media fields", () => {
|
|
382
|
+
const entries: PropertyOrderEntry[] = [
|
|
383
|
+
mkEntry("name", { originalIndex: 0 }),
|
|
384
|
+
mkEntry("website_url", { originalIndex: 1 }),
|
|
385
|
+
mkEntry("avatar", { isStorage: true, originalIndex: 2 }),
|
|
386
|
+
mkEntry("thumbnail", { originalIndex: 3 }),
|
|
387
|
+
];
|
|
388
|
+
const result = sortPropertiesOrder(entries);
|
|
389
|
+
expect(result[0]).toBe("name");
|
|
390
|
+
// All media/url fields should cluster together after name
|
|
391
|
+
expect(result.indexOf("website_url")).toBeGreaterThan(result.indexOf("name"));
|
|
392
|
+
expect(result.indexOf("avatar")).toBeGreaterThan(result.indexOf("name"));
|
|
393
|
+
expect(result.indexOf("thumbnail")).toBeGreaterThan(result.indexOf("name"));
|
|
394
|
+
});
|
|
395
|
+
});
|
package/test/relations.test.ts
CHANGED
|
@@ -382,8 +382,8 @@ relationName: "author" }
|
|
|
382
382
|
// Should create owning relation on profiles
|
|
383
383
|
expect(cleanResult).toContain("export const profilesRelations = drizzleRelations(profiles, ({ one, many }) => ({ \"author\": one(authors, { fields: [profiles.author_id], references: [authors.id], relationName: \"profiles_author_id\" }) }));");
|
|
384
384
|
|
|
385
|
-
// Should create inverse relation on authors
|
|
386
|
-
expect(cleanResult).toContain("export const authorsRelations = drizzleRelations(authors, ({ one, many }) => ({ \"profile\": one(profiles, {
|
|
385
|
+
// Should create inverse relation on authors — inverse side has NO fields/references
|
|
386
|
+
expect(cleanResult).toContain("export const authorsRelations = drizzleRelations(authors, ({ one, many }) => ({ \"profile\": one(profiles, { relationName: \"profiles_author_id\" }) }));");
|
|
387
387
|
});
|
|
388
388
|
|
|
389
389
|
it("should generate owning one-to-many relations", async () => {
|
|
@@ -818,9 +818,9 @@ relationName: "user" }
|
|
|
818
818
|
`"user": one(users, { fields: [profiles.user_id], references: [users.id], relationName: \"${expectedSharedName}\" })`
|
|
819
819
|
);
|
|
820
820
|
|
|
821
|
-
// Inverse side (users → profiles)
|
|
821
|
+
// Inverse side (users → profiles) — no fields/references, paired by relationName only
|
|
822
822
|
expect(cleanResult).toContain(
|
|
823
|
-
`"profile": one(profiles, {
|
|
823
|
+
`"profile": one(profiles, { relationName: \"${expectedSharedName}\" })`
|
|
824
824
|
);
|
|
825
825
|
|
|
826
826
|
// Both must match
|