@rebasepro/server-postgresql 0.6.1 → 0.7.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.
Files changed (75) hide show
  1. package/package.json +24 -18
  2. package/src/PostgresBackendDriver.ts +65 -44
  3. package/src/PostgresBootstrapper.ts +34 -2
  4. package/src/auth/ensure-tables.ts +56 -1
  5. package/src/auth/services.ts +94 -1
  6. package/src/cli-errors.ts +162 -0
  7. package/src/cli-helpers.ts +183 -0
  8. package/src/cli.ts +198 -251
  9. package/src/schema/auth-default-policies.ts +90 -0
  10. package/src/schema/auth-schema.ts +25 -2
  11. package/src/schema/doctor.ts +2 -4
  12. package/src/schema/generate-drizzle-schema-logic.ts +4 -3
  13. package/src/schema/generate-drizzle-schema.ts +3 -5
  14. package/src/schema/generate-postgres-ddl-logic.ts +524 -0
  15. package/src/schema/generate-postgres-ddl.ts +116 -0
  16. package/src/services/EntityPersistService.ts +10 -8
  17. package/src/services/entityService.ts +28 -3
  18. package/src/utils/pg-error-utils.ts +16 -0
  19. package/src/utils/table-classification.ts +16 -0
  20. package/src/websocket.ts +9 -0
  21. package/test/auth-default-policies.test.ts +89 -0
  22. package/test/cli-helpers-extended.test.ts +324 -0
  23. package/test/cli-helpers.test.ts +59 -0
  24. package/test/connection.test.ts +292 -0
  25. package/test/databasePoolManager.test.ts +289 -0
  26. package/test/doctor-extended.test.ts +443 -0
  27. package/test/e2e/db-e2e.test.ts +293 -0
  28. package/test/e2e/pg-setup.ts +79 -0
  29. package/test/entity-persist-composite-keys.test.ts +451 -0
  30. package/test/generate-postgres-ddl-edge-cases.test.ts +716 -0
  31. package/test/generate-postgres-ddl.test.ts +300 -0
  32. package/test/mfa-service.test.ts +544 -0
  33. package/test/pg-error-utils.test.ts +50 -1
  34. package/test/realtimeService-channels.test.ts +696 -0
  35. package/test/unmapped-tables-safety.test.ts +55 -342
  36. package/vitest.e2e.config.ts +10 -0
  37. package/build-errors.txt +0 -37
  38. package/dist/PostgresAdapter.d.ts +0 -6
  39. package/dist/PostgresBackendDriver.d.ts +0 -110
  40. package/dist/PostgresBootstrapper.d.ts +0 -46
  41. package/dist/auth/ensure-tables.d.ts +0 -10
  42. package/dist/auth/services.d.ts +0 -231
  43. package/dist/cli.d.ts +0 -1
  44. package/dist/collections/PostgresCollectionRegistry.d.ts +0 -47
  45. package/dist/connection.d.ts +0 -65
  46. package/dist/data-transformer.d.ts +0 -55
  47. package/dist/databasePoolManager.d.ts +0 -20
  48. package/dist/history/HistoryService.d.ts +0 -71
  49. package/dist/history/ensure-history-table.d.ts +0 -7
  50. package/dist/index.d.ts +0 -14
  51. package/dist/index.es.js +0 -10803
  52. package/dist/index.es.js.map +0 -1
  53. package/dist/interfaces.d.ts +0 -18
  54. package/dist/schema/auth-schema.d.ts +0 -2149
  55. package/dist/schema/doctor-cli.d.ts +0 -2
  56. package/dist/schema/doctor.d.ts +0 -52
  57. package/dist/schema/generate-drizzle-schema-logic.d.ts +0 -2
  58. package/dist/schema/generate-drizzle-schema.d.ts +0 -1
  59. package/dist/schema/introspect-db-inference.d.ts +0 -5
  60. package/dist/schema/introspect-db-logic.d.ts +0 -118
  61. package/dist/schema/introspect-db.d.ts +0 -1
  62. package/dist/schema/test-schema.d.ts +0 -24
  63. package/dist/services/BranchService.d.ts +0 -47
  64. package/dist/services/EntityFetchService.d.ts +0 -214
  65. package/dist/services/EntityPersistService.d.ts +0 -40
  66. package/dist/services/RelationService.d.ts +0 -98
  67. package/dist/services/entity-helpers.d.ts +0 -38
  68. package/dist/services/entityService.d.ts +0 -110
  69. package/dist/services/index.d.ts +0 -4
  70. package/dist/services/realtimeService.d.ts +0 -220
  71. package/dist/types.d.ts +0 -3
  72. package/dist/utils/drizzle-conditions.d.ts +0 -138
  73. package/dist/utils/pg-array-null-patch.d.ts +0 -16
  74. package/dist/utils/pg-error-utils.d.ts +0 -55
  75. package/dist/websocket.d.ts +0 -11
@@ -0,0 +1,443 @@
1
+ import {
2
+ StringProperty,
3
+ NumberProperty,
4
+ ArrayProperty,
5
+ MapProperty,
6
+ Property,
7
+ VectorProperty,
8
+ BinaryProperty,
9
+ } from "@rebasepro/types";
10
+ import { getExpectedColumnType } from "../src/schema/doctor";
11
+
12
+ describe("Doctor — getExpectedColumnType extended coverage", () => {
13
+
14
+ // ── String property edge cases ──────────────────────────────────────
15
+
16
+ describe("string property", () => {
17
+
18
+ it("should return 'uuid' when isId is 'uuid'", () => {
19
+ const prop: StringProperty = {
20
+ type: "string",
21
+ isId: "uuid",
22
+ };
23
+ expect(getExpectedColumnType(prop)).toBe("uuid");
24
+ });
25
+
26
+ it("should return 'character varying' for basic string (no options)", () => {
27
+ const prop: StringProperty = { type: "string" };
28
+ expect(getExpectedColumnType(prop)).toBe("character varying");
29
+ });
30
+
31
+ it("should return 'text' when columnType is 'text'", () => {
32
+ const prop: StringProperty = {
33
+ type: "string",
34
+ columnType: "text",
35
+ };
36
+ expect(getExpectedColumnType(prop)).toBe("text");
37
+ });
38
+
39
+ it("should return 'character' when columnType is 'char'", () => {
40
+ const prop: StringProperty = {
41
+ type: "string",
42
+ columnType: "char",
43
+ };
44
+ expect(getExpectedColumnType(prop)).toBe("character");
45
+ });
46
+
47
+ it("should return 'uuid' when columnType is 'uuid'", () => {
48
+ const prop: StringProperty = {
49
+ type: "string",
50
+ columnType: "uuid",
51
+ };
52
+ // Current implementation does NOT check columnType === 'uuid' (only checks isId === 'uuid')
53
+ // So this documents the current behavior:
54
+ // columnType 'uuid' falls through to the default 'character varying'
55
+ const result = getExpectedColumnType(prop);
56
+ // This is the actual behavior — columnType 'uuid' is not handled by doctor
57
+ expect(result).toBe("character varying");
58
+ });
59
+
60
+ it("should return 'USER-DEFINED' for enum string", () => {
61
+ const prop: StringProperty = {
62
+ type: "string",
63
+ enum: { active: "Active", inactive: "Inactive" },
64
+ };
65
+ expect(getExpectedColumnType(prop)).toBe("USER-DEFINED");
66
+ });
67
+
68
+ it("should prioritize enum over columnType", () => {
69
+ const prop: StringProperty = {
70
+ type: "string",
71
+ enum: ["a", "b"],
72
+ columnType: "text",
73
+ };
74
+ // enum check comes first in the switch
75
+ expect(getExpectedColumnType(prop)).toBe("USER-DEFINED");
76
+ });
77
+
78
+ it("should prioritize enum over isId", () => {
79
+ const prop: StringProperty = {
80
+ type: "string",
81
+ enum: { x: "X" },
82
+ isId: "uuid",
83
+ };
84
+ expect(getExpectedColumnType(prop)).toBe("USER-DEFINED");
85
+ });
86
+
87
+ it("should return 'character varying' for string with multiline: true", () => {
88
+ // multiline is a UI hint, doctor doesn't check it for type mapping
89
+ const prop: StringProperty = {
90
+ type: "string",
91
+ multiline: true,
92
+ };
93
+ const result = getExpectedColumnType(prop);
94
+ // Current behavior: multiline doesn't influence the column type mapping
95
+ expect(result).toBe("character varying");
96
+ });
97
+
98
+ it("should return 'character varying' for string with markdown: true", () => {
99
+ // markdown is a UI hint, doctor doesn't check it for type mapping
100
+ const prop: StringProperty = {
101
+ type: "string",
102
+ markdown: true,
103
+ };
104
+ const result = getExpectedColumnType(prop);
105
+ // Current behavior: markdown doesn't influence the column type mapping
106
+ expect(result).toBe("character varying");
107
+ });
108
+
109
+ it("should return 'character varying' for string with isId: true (boolean)", () => {
110
+ const prop: StringProperty = {
111
+ type: "string",
112
+ isId: true,
113
+ };
114
+ // isId: true (not "uuid") doesn't match isId === "uuid"
115
+ const result = getExpectedColumnType(prop);
116
+ expect(result).toBe("character varying");
117
+ });
118
+
119
+ it("should return 'uuid' for string with isId: 'uuid' even with columnType 'text'", () => {
120
+ const prop: StringProperty = {
121
+ type: "string",
122
+ isId: "uuid",
123
+ columnType: "text",
124
+ };
125
+ // isId: "uuid" check comes before columnType check
126
+ expect(getExpectedColumnType(prop)).toBe("uuid");
127
+ });
128
+
129
+ it("should return 'character varying' for string with isId: 'cuid'", () => {
130
+ const prop: StringProperty = {
131
+ type: "string",
132
+ isId: "cuid",
133
+ };
134
+ // Only "uuid" isId triggers special handling
135
+ expect(getExpectedColumnType(prop)).toBe("character varying");
136
+ });
137
+
138
+ it("should return 'character varying' for string with isId: 'manual'", () => {
139
+ const prop: StringProperty = {
140
+ type: "string",
141
+ isId: "manual",
142
+ };
143
+ expect(getExpectedColumnType(prop)).toBe("character varying");
144
+ });
145
+ });
146
+
147
+ // ── Number property edge cases ──────────────────────────────────────
148
+
149
+ describe("number property", () => {
150
+
151
+ it("should return 'integer' when isId is 'increment'", () => {
152
+ const prop: NumberProperty = {
153
+ type: "number",
154
+ isId: "increment",
155
+ };
156
+ // isId is truthy so the last condition matches: ("isId" in np && np.isId) → integer
157
+ expect(getExpectedColumnType(prop)).toBe("integer");
158
+ });
159
+
160
+ it("should return 'integer' when isId is true (boolean)", () => {
161
+ const prop: NumberProperty = {
162
+ type: "number",
163
+ isId: true,
164
+ };
165
+ expect(getExpectedColumnType(prop)).toBe("integer");
166
+ });
167
+
168
+ it("should return 'integer' when isId is 'manual'", () => {
169
+ const prop: NumberProperty = {
170
+ type: "number",
171
+ isId: "manual",
172
+ };
173
+ // "manual" is truthy, so ("isId" in np && np.isId) → integer
174
+ expect(getExpectedColumnType(prop)).toBe("integer");
175
+ });
176
+
177
+ it("should return 'numeric' for plain number without any options", () => {
178
+ const prop: NumberProperty = { type: "number" };
179
+ expect(getExpectedColumnType(prop)).toBe("numeric");
180
+ });
181
+
182
+ it("should return 'integer' when validation.integer is true", () => {
183
+ const prop: NumberProperty = {
184
+ type: "number",
185
+ validation: { integer: true },
186
+ };
187
+ expect(getExpectedColumnType(prop)).toBe("integer");
188
+ });
189
+
190
+ it("should return 'integer' for serial columnType", () => {
191
+ const prop: NumberProperty = {
192
+ type: "number",
193
+ columnType: "serial",
194
+ };
195
+ // serial maps to integer in information_schema
196
+ expect(getExpectedColumnType(prop)).toBe("integer");
197
+ });
198
+
199
+ it("should return 'bigint' for bigserial columnType", () => {
200
+ const prop: NumberProperty = {
201
+ type: "number",
202
+ columnType: "bigserial",
203
+ };
204
+ expect(getExpectedColumnType(prop)).toBe("bigint");
205
+ });
206
+
207
+ it("should return 'bigint' for bigint columnType", () => {
208
+ const prop: NumberProperty = {
209
+ type: "number",
210
+ columnType: "bigint",
211
+ };
212
+ expect(getExpectedColumnType(prop)).toBe("bigint");
213
+ });
214
+
215
+ it("should return 'numeric' for numeric columnType", () => {
216
+ const prop: NumberProperty = {
217
+ type: "number",
218
+ columnType: "numeric",
219
+ };
220
+ expect(getExpectedColumnType(prop)).toBe("numeric");
221
+ });
222
+
223
+ it("should return 'real' for real columnType", () => {
224
+ const prop: NumberProperty = {
225
+ type: "number",
226
+ columnType: "real",
227
+ };
228
+ expect(getExpectedColumnType(prop)).toBe("real");
229
+ });
230
+
231
+ it("should return 'double precision' for double precision columnType", () => {
232
+ const prop: NumberProperty = {
233
+ type: "number",
234
+ columnType: "double precision",
235
+ };
236
+ expect(getExpectedColumnType(prop)).toBe("double precision");
237
+ });
238
+
239
+ it("should prioritize columnType over validation.integer", () => {
240
+ const prop: NumberProperty = {
241
+ type: "number",
242
+ columnType: "numeric",
243
+ validation: { integer: true },
244
+ };
245
+ // columnType checks come before validation checks
246
+ expect(getExpectedColumnType(prop)).toBe("numeric");
247
+ });
248
+
249
+ it("should prioritize columnType 'real' over isId", () => {
250
+ const prop: NumberProperty = {
251
+ type: "number",
252
+ columnType: "real",
253
+ isId: true,
254
+ };
255
+ // columnType check for "real" comes before isId check
256
+ expect(getExpectedColumnType(prop)).toBe("real");
257
+ });
258
+ });
259
+
260
+ // ── Boolean property ────────────────────────────────────────────────
261
+
262
+ describe("boolean property", () => {
263
+ it("should return 'boolean'", () => {
264
+ expect(getExpectedColumnType({ type: "boolean" })).toBe("boolean");
265
+ });
266
+ });
267
+
268
+ // ── Date property edge cases ────────────────────────────────────────
269
+
270
+ describe("date property", () => {
271
+ it("should return 'timestamp with time zone' for default date", () => {
272
+ expect(getExpectedColumnType({ type: "date" })).toBe("timestamp with time zone");
273
+ });
274
+
275
+ it("should return 'date' for columnType 'date'", () => {
276
+ expect(getExpectedColumnType({ type: "date", columnType: "date" } as Property)).toBe("date");
277
+ });
278
+
279
+ it("should return 'time without time zone' for columnType 'time'", () => {
280
+ expect(getExpectedColumnType({ type: "date", columnType: "time" } as Property)).toBe("time without time zone");
281
+ });
282
+ });
283
+
284
+ // ── Reference property ──────────────────────────────────────────────
285
+
286
+ describe("reference property", () => {
287
+ it("should return 'character varying' for reference type", () => {
288
+ const prop: Property = {
289
+ type: "reference",
290
+ path: "some_collection",
291
+ } as Property;
292
+ expect(getExpectedColumnType(prop)).toBe("character varying");
293
+ });
294
+ });
295
+
296
+ // ── Vector property ─────────────────────────────────────────────────
297
+
298
+ describe("vector property", () => {
299
+ it("should return 'USER-DEFINED' for vector type", () => {
300
+ const prop: VectorProperty = {
301
+ type: "vector",
302
+ dimensions: 768,
303
+ };
304
+ expect(getExpectedColumnType(prop)).toBe("USER-DEFINED");
305
+ });
306
+
307
+ it("should return 'USER-DEFINED' for vector with any dimensions", () => {
308
+ const prop: VectorProperty = {
309
+ type: "vector",
310
+ dimensions: 1536,
311
+ };
312
+ expect(getExpectedColumnType(prop)).toBe("USER-DEFINED");
313
+ });
314
+ });
315
+
316
+ // ── Binary property ─────────────────────────────────────────────────
317
+
318
+ describe("binary property", () => {
319
+ it("should return 'bytea' for binary type", () => {
320
+ const prop: BinaryProperty = {
321
+ type: "binary",
322
+ };
323
+ expect(getExpectedColumnType(prop)).toBe("bytea");
324
+ });
325
+ });
326
+
327
+ // ── Relation property ───────────────────────────────────────────────
328
+
329
+ describe("relation property", () => {
330
+ it("should return null for relation type", () => {
331
+ expect(getExpectedColumnType({ type: "relation" } as Property)).toBeNull();
332
+ });
333
+ });
334
+
335
+ // ── Array property edge cases ───────────────────────────────────────
336
+
337
+ describe("array property", () => {
338
+ it("should return 'jsonb' for array with no columnType and no of", () => {
339
+ const prop: ArrayProperty = { type: "array" } as ArrayProperty;
340
+ expect(getExpectedColumnType(prop)).toBe("jsonb");
341
+ });
342
+
343
+ it("should return 'json' for array with columnType 'json'", () => {
344
+ const prop: ArrayProperty = {
345
+ type: "array",
346
+ columnType: "json",
347
+ } as ArrayProperty;
348
+ expect(getExpectedColumnType(prop)).toBe("json");
349
+ });
350
+
351
+ it("should return 'jsonb' for array with columnType 'jsonb'", () => {
352
+ const prop: ArrayProperty = {
353
+ type: "array",
354
+ columnType: "jsonb",
355
+ } as ArrayProperty;
356
+ expect(getExpectedColumnType(prop)).toBe("jsonb");
357
+ });
358
+
359
+ it("should return 'ARRAY' for array of strings", () => {
360
+ const prop: ArrayProperty = {
361
+ type: "array",
362
+ of: { type: "string" },
363
+ } as ArrayProperty;
364
+ expect(getExpectedColumnType(prop)).toBe("ARRAY");
365
+ });
366
+
367
+ it("should return 'ARRAY' for array of integers", () => {
368
+ const prop: ArrayProperty = {
369
+ type: "array",
370
+ of: { type: "number", validation: { integer: true } },
371
+ } as ArrayProperty;
372
+ expect(getExpectedColumnType(prop)).toBe("ARRAY");
373
+ });
374
+
375
+ it("should return 'ARRAY' for array of booleans", () => {
376
+ const prop: ArrayProperty = {
377
+ type: "array",
378
+ of: { type: "boolean" },
379
+ } as ArrayProperty;
380
+ expect(getExpectedColumnType(prop)).toBe("ARRAY");
381
+ });
382
+
383
+ it("should return 'jsonb' for array of numbers (non-integer)", () => {
384
+ const prop: ArrayProperty = {
385
+ type: "array",
386
+ of: { type: "number" },
387
+ } as ArrayProperty;
388
+ // numeric[] → "ARRAY"
389
+ expect(getExpectedColumnType(prop)).toBe("ARRAY");
390
+ });
391
+
392
+ it("should return 'jsonb' for array of maps (complex type, no native array support)", () => {
393
+ const prop: ArrayProperty = {
394
+ type: "array",
395
+ of: { type: "map" },
396
+ } as ArrayProperty;
397
+ // map type doesn't match string/number/boolean → colType remains undefined → jsonb
398
+ expect(getExpectedColumnType(prop)).toBe("jsonb");
399
+ });
400
+
401
+ it("should return 'jsonb' for array with Array.isArray(of) being true", () => {
402
+ const prop: ArrayProperty = {
403
+ type: "array",
404
+ of: [{ type: "string" }],
405
+ } as ArrayProperty;
406
+ // Array.isArray(ap.of) → true → doesn't enter the single-of branch → jsonb
407
+ expect(getExpectedColumnType(prop)).toBe("jsonb");
408
+ });
409
+ });
410
+
411
+ // ── Map property edge cases ─────────────────────────────────────────
412
+
413
+ describe("map property", () => {
414
+ it("should return 'jsonb' for default map", () => {
415
+ const prop: MapProperty = { type: "map" } as MapProperty;
416
+ expect(getExpectedColumnType(prop)).toBe("jsonb");
417
+ });
418
+
419
+ it("should return 'json' for map with columnType 'json'", () => {
420
+ const prop: MapProperty = {
421
+ type: "map",
422
+ columnType: "json",
423
+ };
424
+ expect(getExpectedColumnType(prop)).toBe("json");
425
+ });
426
+
427
+ it("should return 'jsonb' for map with columnType 'jsonb'", () => {
428
+ const prop: MapProperty = {
429
+ type: "map",
430
+ columnType: "jsonb",
431
+ };
432
+ expect(getExpectedColumnType(prop)).toBe("jsonb");
433
+ });
434
+ });
435
+
436
+ // ── Unknown / unsupported types ─────────────────────────────────────
437
+
438
+ describe("unknown types", () => {
439
+ it("should return null for geopoint type", () => {
440
+ expect(getExpectedColumnType({ type: "geopoint" } as Property)).toBeNull();
441
+ });
442
+ });
443
+ });