@rebasepro/server-postgres 0.0.1-canary.4829d6e

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 (121) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +86 -0
  3. package/dist/PostgresAdapter.d.ts +6 -0
  4. package/dist/PostgresBackendDriver.d.ts +150 -0
  5. package/dist/PostgresBootstrapper.d.ts +51 -0
  6. package/dist/auth/ensure-tables.d.ts +10 -0
  7. package/dist/auth/services.d.ts +250 -0
  8. package/dist/backup/backup-cli.d.ts +3 -0
  9. package/dist/backup/backup-cron.d.ts +53 -0
  10. package/dist/backup/backup-service.d.ts +85 -0
  11. package/dist/backup/index.d.ts +12 -0
  12. package/dist/backup/pg-tools.d.ts +110 -0
  13. package/dist/backup/retention.d.ts +35 -0
  14. package/dist/chunk-DSJWtz9O.js +40 -0
  15. package/dist/cli-errors.d.ts +42 -0
  16. package/dist/cli-helpers.d.ts +7 -0
  17. package/dist/cli.d.ts +1 -0
  18. package/dist/collections/PostgresCollectionRegistry.d.ts +47 -0
  19. package/dist/connection.d.ts +65 -0
  20. package/dist/data-transformer.d.ts +55 -0
  21. package/dist/databasePoolManager.d.ts +20 -0
  22. package/dist/history/HistoryService.d.ts +71 -0
  23. package/dist/history/ensure-history-table.d.ts +7 -0
  24. package/dist/index.d.ts +15 -0
  25. package/dist/index.es.js +23535 -0
  26. package/dist/index.es.js.map +1 -0
  27. package/dist/interfaces.d.ts +18 -0
  28. package/dist/schema/auth-bootstrap-sql.d.ts +24 -0
  29. package/dist/schema/auth-default-policies.d.ts +10 -0
  30. package/dist/schema/auth-schema.d.ts +2376 -0
  31. package/dist/schema/doctor-cli.d.ts +2 -0
  32. package/dist/schema/doctor.d.ts +58 -0
  33. package/dist/schema/dynamic-tables.d.ts +31 -0
  34. package/dist/schema/generate-drizzle-schema-logic.d.ts +2 -0
  35. package/dist/schema/generate-drizzle-schema.d.ts +1 -0
  36. package/dist/schema/generate-postgres-ddl-logic.d.ts +5 -0
  37. package/dist/schema/generate-postgres-ddl.d.ts +1 -0
  38. package/dist/schema/introspect-db-inference.d.ts +5 -0
  39. package/dist/schema/introspect-db-logic.d.ts +118 -0
  40. package/dist/schema/introspect-db.d.ts +1 -0
  41. package/dist/schema/introspect-runtime.d.ts +57 -0
  42. package/dist/schema/test-schema.d.ts +24 -0
  43. package/dist/security/policy-drift.d.ts +57 -0
  44. package/dist/security/rls-enforcement.d.ts +122 -0
  45. package/dist/services/BranchService.d.ts +47 -0
  46. package/dist/services/FetchService.d.ts +214 -0
  47. package/dist/services/PersistService.d.ts +39 -0
  48. package/dist/services/RelationService.d.ts +109 -0
  49. package/dist/services/cdc/CdcListener.d.ts +54 -0
  50. package/dist/services/cdc/trigger-cdc.d.ts +64 -0
  51. package/dist/services/collection-helpers.d.ts +38 -0
  52. package/dist/services/dataService.d.ts +110 -0
  53. package/dist/services/index.d.ts +4 -0
  54. package/dist/services/realtimeService.d.ts +298 -0
  55. package/dist/src-Eh-CZosp.js +595 -0
  56. package/dist/src-Eh-CZosp.js.map +1 -0
  57. package/dist/types.d.ts +3 -0
  58. package/dist/utils/drizzle-conditions.d.ts +138 -0
  59. package/dist/utils/pg-array-null-patch.d.ts +16 -0
  60. package/dist/utils/pg-error-utils.d.ts +65 -0
  61. package/dist/utils/table-classification.d.ts +8 -0
  62. package/dist/websocket.d.ts +18 -0
  63. package/package.json +113 -0
  64. package/src/PostgresAdapter.ts +58 -0
  65. package/src/PostgresBackendDriver.ts +1387 -0
  66. package/src/PostgresBootstrapper.ts +581 -0
  67. package/src/auth/ensure-tables.ts +367 -0
  68. package/src/auth/services.ts +1321 -0
  69. package/src/backup/backup-cli.ts +383 -0
  70. package/src/backup/backup-cron.ts +189 -0
  71. package/src/backup/backup-service.ts +299 -0
  72. package/src/backup/index.ts +12 -0
  73. package/src/backup/pg-tools.ts +231 -0
  74. package/src/backup/retention.ts +75 -0
  75. package/src/cli-errors.ts +265 -0
  76. package/src/cli-helpers.ts +196 -0
  77. package/src/cli.ts +786 -0
  78. package/src/collections/PostgresCollectionRegistry.ts +103 -0
  79. package/src/connection.ts +166 -0
  80. package/src/data-transformer.ts +733 -0
  81. package/src/databasePoolManager.ts +87 -0
  82. package/src/history/HistoryService.ts +272 -0
  83. package/src/history/ensure-history-table.ts +46 -0
  84. package/src/index.ts +15 -0
  85. package/src/interfaces.ts +60 -0
  86. package/src/schema/auth-bootstrap-sql.ts +41 -0
  87. package/src/schema/auth-default-policies.ts +125 -0
  88. package/src/schema/auth-schema.ts +233 -0
  89. package/src/schema/doctor-cli.ts +113 -0
  90. package/src/schema/doctor.ts +733 -0
  91. package/src/schema/dynamic-tables.test.ts +302 -0
  92. package/src/schema/dynamic-tables.ts +293 -0
  93. package/src/schema/generate-drizzle-schema-logic.ts +850 -0
  94. package/src/schema/generate-drizzle-schema.ts +131 -0
  95. package/src/schema/generate-postgres-ddl-logic.ts +490 -0
  96. package/src/schema/generate-postgres-ddl.ts +92 -0
  97. package/src/schema/introspect-db-inference.ts +238 -0
  98. package/src/schema/introspect-db-logic.ts +910 -0
  99. package/src/schema/introspect-db.ts +266 -0
  100. package/src/schema/introspect-runtime.test.ts +212 -0
  101. package/src/schema/introspect-runtime.ts +293 -0
  102. package/src/schema/test-schema.ts +11 -0
  103. package/src/security/policy-drift.test.ts +122 -0
  104. package/src/security/policy-drift.ts +159 -0
  105. package/src/security/rls-enforcement.ts +295 -0
  106. package/src/services/BranchService.ts +251 -0
  107. package/src/services/FetchService.ts +1661 -0
  108. package/src/services/PersistService.ts +329 -0
  109. package/src/services/RelationService.ts +1306 -0
  110. package/src/services/cdc/CdcListener.ts +167 -0
  111. package/src/services/cdc/trigger-cdc.ts +169 -0
  112. package/src/services/collection-helpers.ts +151 -0
  113. package/src/services/dataService.ts +246 -0
  114. package/src/services/index.ts +13 -0
  115. package/src/services/realtimeService.ts +1502 -0
  116. package/src/types.ts +4 -0
  117. package/src/utils/drizzle-conditions.ts +1162 -0
  118. package/src/utils/pg-array-null-patch.ts +42 -0
  119. package/src/utils/pg-error-utils.ts +227 -0
  120. package/src/utils/table-classification.ts +16 -0
  121. package/src/websocket.ts +640 -0
@@ -0,0 +1,92 @@
1
+ import { promises as fsPromises } from "fs";
2
+ import * as fs from "fs";
3
+ import path from "path";
4
+ import { pathToFileURL } from "url";
5
+ import chokidar from "chokidar";
6
+ import { generatePostgresDdl, generatePostgresPoliciesDdl } from "./generate-postgres-ddl-logic";
7
+ import { CollectionConfig } from "@rebasepro/types";
8
+ import { logger, loadCollectionsFromDirectory } from "@rebasepro/server";
9
+
10
+
11
+ const runGeneration = async (collectionsFilePath?: string, outputPath?: string) => {
12
+ try {
13
+ if (!collectionsFilePath) {
14
+ logger.error("Error: No collections file path provided. Skipping schema generation.");
15
+ return;
16
+ }
17
+
18
+ const resolvedPath = path.resolve(collectionsFilePath);
19
+
20
+ // Shared with the runtime and the doctor: what gets generated here must
21
+ // be exactly what the server serves, including directory-level defaults.
22
+ let collections: CollectionConfig[] = await loadCollectionsFromDirectory(resolvedPath);
23
+
24
+
25
+ if (!collections || !Array.isArray(collections)) {
26
+ collections = [];
27
+ }
28
+
29
+ // Sort collections by slug alphabetically to ensure deterministic DDL generation
30
+ collections.sort((a, b) => a.slug.localeCompare(b.slug));
31
+
32
+ const ddlContent = await generatePostgresDdl(collections, { includePolicies: false });
33
+ const policiesContent = generatePostgresPoliciesDdl(collections);
34
+
35
+ if (outputPath) {
36
+ const outputDir = path.dirname(outputPath);
37
+ await fsPromises.mkdir(outputDir, { recursive: true });
38
+ await fsPromises.writeFile(outputPath, ddlContent);
39
+ logger.info(`✅ PostgreSQL DDL generated successfully at ${outputPath}`);
40
+
41
+ const policiesPath = path.join(outputDir, "policies.sql");
42
+ await fsPromises.writeFile(policiesPath, policiesContent);
43
+ logger.info(`✅ PostgreSQL Policies DDL generated successfully at ${policiesPath}`);
44
+ } else {
45
+ logger.info("✅ PostgreSQL DDL generated successfully.");
46
+ logger.info(String(ddlContent));
47
+ logger.info("\n✅ PostgreSQL Policies DDL generated successfully.");
48
+ logger.info(String(policiesContent));
49
+ }
50
+
51
+ } catch (error) {
52
+ logger.error("Error generating DDL schema", { error: error });
53
+ }
54
+ };
55
+
56
+ const main = () => {
57
+ const collectionsFilePathArg = process.argv.find(arg => arg.startsWith("--collections="));
58
+ const collectionsFilePath = collectionsFilePathArg ? collectionsFilePathArg.split("=")[1] : process.argv[2];
59
+
60
+ const outputPathArg = process.argv.find(arg => arg.startsWith("--output="));
61
+ const outputPath = outputPathArg ? outputPathArg.split("=")[1] : undefined;
62
+
63
+ const watch = process.argv.includes("--watch");
64
+
65
+ if (!collectionsFilePath) {
66
+ logger.info("Usage: ts-node generate-postgres-ddl.ts <path-to-collections-file> [--output <path-to-output-file>] [--watch]");
67
+ return;
68
+ }
69
+
70
+ const resolvedPath = path.resolve(process.cwd(), collectionsFilePath);
71
+ const resolvedOutputPath = outputPath ? path.resolve(process.cwd(), outputPath) : undefined;
72
+
73
+ if (watch) {
74
+ logger.info(`Watching for changes in ${resolvedPath}...`);
75
+ const watcher = chokidar.watch(resolvedPath, {
76
+ persistent: true,
77
+ ignoreInitial: false
78
+ });
79
+
80
+ watcher.on("all", (event, filePath) => {
81
+ logger.info(`[${event}] ${filePath}. Regenerating DDL schema...`);
82
+ runGeneration(resolvedPath, resolvedOutputPath);
83
+ });
84
+ } else {
85
+ runGeneration(resolvedPath, resolvedOutputPath);
86
+ }
87
+ };
88
+
89
+ // This check ensures the script only runs when executed directly
90
+ if (import.meta.url.endsWith(process.argv[1])) {
91
+ main();
92
+ }
@@ -0,0 +1,238 @@
1
+ import { humanize } from "./introspect-db-logic";
2
+
3
+ export interface InferenceResult {
4
+ propType?: string; // If the inference changes the base type
5
+ extra?: string; // String to append to the property definition
6
+ }
7
+
8
+ const ISO_8601_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
9
+ const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
10
+ const CUID_REGEX = /^c[^\s-]{7,}$/i; // Basic CUID check
11
+ const COLOR_HEX_REGEX = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
12
+
13
+ export function inferPropertyFromData(
14
+ columnName: string,
15
+ pgDataType: string,
16
+ currentPropType: string,
17
+ sampleValues: unknown[],
18
+ isPk: boolean
19
+ ): InferenceResult {
20
+ const result: InferenceResult = {};
21
+ const extraLines: string[] = [];
22
+
23
+ // Filter out null/undefined for analysis
24
+ const validValues = sampleValues.filter(v => v !== null && v !== undefined && v !== "");
25
+
26
+ if (validValues.length === 0) {
27
+ return result; // Not enough data
28
+ }
29
+
30
+ const colNameLower = columnName.toLowerCase();
31
+
32
+ // ── Number Analysis ──────────────────────────────────────────────────
33
+ if (currentPropType === "number") {
34
+ // Boolean stored as int
35
+ const allZeroOrOne = validValues.every(v => v === 0 || v === 1 || v === "0" || v === "1");
36
+ if (allZeroOrOne) {
37
+ result.propType = "boolean";
38
+ return result; // Don't do min/max if boolean
39
+ }
40
+
41
+ // Min / Max constraints
42
+ const numValues = validValues.map(v => Number(v)).filter(v => !isNaN(v));
43
+ if (numValues.length === validValues.length) {
44
+ const min = Math.min(...numValues);
45
+ const max = Math.max(...numValues);
46
+ // Example heuristic: percentages
47
+ if (min >= 0 && max <= 100 && (colNameLower.includes("percent") || colNameLower.includes("rate") || colNameLower.includes("score"))) {
48
+ extraLines.push(" validation: {\n min: 0,\n max: 100\n }");
49
+ } else if (min >= 0 && (colNameLower.includes("count") || colNameLower.includes("total") || colNameLower.includes("amount"))) {
50
+ extraLines.push(" validation: {\n min: 0\n }");
51
+ }
52
+ }
53
+
54
+ // Currency
55
+ if (colNameLower.includes("price") || colNameLower.includes("cost") || colNameLower.includes("amount") || colNameLower.includes("fee") || pgDataType === "money") {
56
+ extraLines.push(" ui: {\n currency: true\n }");
57
+ }
58
+ }
59
+
60
+ // ── JSON / JSONB Analysis ────────────────────────────────────────────
61
+ if (currentPropType === "map" || pgDataType.includes("json")) {
62
+ // PostGres jsonb can be object or array
63
+ let allArrays = true;
64
+ let allObjects = true;
65
+
66
+ for (const v of validValues) {
67
+ let parsed = v;
68
+ if (typeof v === "string") {
69
+ try { parsed = JSON.parse(v); } catch (e) { allArrays = false; allObjects = false; break; }
70
+ }
71
+ if (Array.isArray(parsed)) {
72
+ allObjects = false;
73
+ } else if (typeof parsed === "object" && parsed !== null) {
74
+ allArrays = false;
75
+ } else {
76
+ allArrays = false;
77
+ allObjects = false;
78
+ }
79
+ }
80
+
81
+ if (allArrays && !allObjects) {
82
+ result.propType = "array";
83
+ // Infer inner type
84
+ let allNumbers = true;
85
+ let allStrings = true;
86
+ for (const v of validValues) {
87
+ const parsed = typeof v === "string" ? JSON.parse(v) : v;
88
+ for (const item of parsed) {
89
+ if (typeof item !== "number") allNumbers = false;
90
+ if (typeof item !== "string") allStrings = false;
91
+ }
92
+ }
93
+ const innerType = allNumbers ? "number" : allStrings ? "string" : "map";
94
+ extraLines.push(` of: { name: "${humanize(columnName)} Item", type: "${innerType}" }`);
95
+ } else {
96
+ result.propType = "map";
97
+
98
+ // Infer inner schema
99
+ if (allObjects && validValues.length > 0) {
100
+ const schema: Record<string, string> = {};
101
+ for (const v of validValues) {
102
+ const parsed = typeof v === "string" ? JSON.parse(v) : v;
103
+ for (const [k, val] of Object.entries(parsed)) {
104
+ if (val === null || val === undefined) continue;
105
+ const type = typeof val;
106
+ if (type === "string" || type === "number" || type === "boolean") {
107
+ if (!schema[k]) schema[k] = type;
108
+ else if (schema[k] !== type) schema[k] = "mixed";
109
+ } else if (Array.isArray(val)) {
110
+ if (!schema[k]) schema[k] = "array";
111
+ else if (schema[k] !== "array") schema[k] = "mixed";
112
+ } else if (type === "object") {
113
+ if (!schema[k]) schema[k] = "map";
114
+ else if (schema[k] !== "map") schema[k] = "mixed";
115
+ }
116
+ }
117
+ }
118
+
119
+ const keys = Object.keys(schema).filter(k => schema[k] !== "mixed");
120
+ if (keys.length > 0) {
121
+ const props = keys.map(k => {
122
+ return `\n ${k}: { name: "${humanize(k)}", type: "${schema[k]}" }`;
123
+ }).join(",");
124
+ extraLines.push(` properties: {${props}\n }`);
125
+ } else {
126
+ extraLines.push(" keyValue: true");
127
+ }
128
+ } else {
129
+ extraLines.push(" keyValue: true");
130
+ }
131
+ }
132
+ }
133
+
134
+ // ── String Analysis ──────────────────────────────────────────────────
135
+ if (currentPropType === "string") {
136
+ // Date/Time Strings
137
+ if (validValues.every(v => typeof v === "string" && ISO_8601_REGEX.test(v))) {
138
+ result.propType = "date";
139
+ return result;
140
+ }
141
+
142
+ // Implicit Enums
143
+ const uniqueValues = new Set(validValues);
144
+ // Determine the maximum length among unique values
145
+ let maxEnumLength = 0;
146
+ for (const v of uniqueValues) {
147
+ if (typeof v === "string" && v.length > maxEnumLength) {
148
+ maxEnumLength = v.length;
149
+ }
150
+ }
151
+
152
+ // Ensure no empty string, max length makes sense, and fewer unique values than total values (unless small total)
153
+ if (uniqueValues.size > 0 && uniqueValues.size <= 5 && maxEnumLength <= 50 && validValues.length > uniqueValues.size && !uniqueValues.has("")) {
154
+ const isLikelyId = isPk || colNameLower.endsWith("_id");
155
+ if (!isLikelyId) {
156
+ const enumEntries = Array.from(uniqueValues).map(v => `{ id: ${JSON.stringify(v)}, label: ${JSON.stringify(humanize(v as string))} }`).join(", ");
157
+ extraLines.push(` enum: [${enumEntries}]`);
158
+ result.extra = extraLines.length > 0 ? "\n" + extraLines.join(",\n") + "," : "";
159
+ return result; // Skip other string checks if it's an enum
160
+ }
161
+ }
162
+
163
+ // UUID / CUID Detection
164
+ const allUuid = validValues.every(v => typeof v === "string" && UUID_REGEX.test(v));
165
+ const allCuid = validValues.every(v => typeof v === "string" && CUID_REGEX.test(v));
166
+ if (allUuid) {
167
+ if (isPk) extraLines.push(" isId: \"uuid\"");
168
+ } else if (allCuid) {
169
+ if (isPk) extraLines.push(" isId: \"cuid\"");
170
+ }
171
+
172
+ // Color Codes
173
+ const allColors = validValues.every(v => typeof v === "string" && COLOR_HEX_REGEX.test(v));
174
+ if (allColors) {
175
+ extraLines.push(" ui: {\n color: true\n }");
176
+ }
177
+
178
+ // Text Lengths, Multiline & Markdown
179
+ let maxLength = 0;
180
+ let hasNewlines = false;
181
+ let hasMarkdown = false;
182
+ for (const v of validValues) {
183
+ if (typeof v === "string") {
184
+ if (v.length > maxLength) maxLength = v.length;
185
+ if (v.includes("\n")) hasNewlines = true;
186
+ if (/^#{1,6}\s+.+/m.test(v) || /\*\*.+\*\*/.test(v) || /\[.+\]\(.+\)/.test(v) || /^\s*[-*]\s+.+/m.test(v)) {
187
+ hasMarkdown = true;
188
+ }
189
+ }
190
+ }
191
+
192
+ if (hasMarkdown) {
193
+ extraLines.push(" multiline: true,\n markdown: true");
194
+ } else if (hasNewlines || maxLength > 100) {
195
+ extraLines.push(" multiline: true");
196
+ }
197
+
198
+ if (maxLength > 0 && maxLength < 10000) { // arbitrary cap to avoid huge limits
199
+ // Pad the max length slightly for the constraint
200
+ const paddedMax = Math.ceil((maxLength * 1.5) / 10) * 10;
201
+ // Only add length constraint if it seems like a bounded string, not a long text
202
+ if (paddedMax < 255) {
203
+ extraLines.push(` validation: {\n max: ${paddedMax}\n }`);
204
+ }
205
+ }
206
+
207
+ // Storage & Media (Extracted from old logic)
208
+ const isUrl = colNameLower.endsWith("_url") || colNameLower.endsWith("_uri") || colNameLower.endsWith("_link");
209
+ const isMedia = colNameLower.includes("image") || colNameLower.includes("avatar") || colNameLower.includes("photo") || colNameLower.includes("logo") || colNameLower.includes("cover");
210
+
211
+ const allAbsoluteUrls = validValues.every(v => typeof v === "string" && (v.startsWith("http://") || v.startsWith("https://")));
212
+ if (allAbsoluteUrls) {
213
+ const isImage = validValues.some(v => typeof v === "string" && v.match(/\.(jpeg|jpg|gif|png|webp|svg)/i));
214
+ if (isImage || isMedia) {
215
+ extraLines.push(" ui: {\n url: \"image\"\n }");
216
+ } else {
217
+ extraLines.push(" ui: {\n url: true\n }");
218
+ }
219
+ } else {
220
+ const hasFileExtension = validValues.some(v => typeof v === "string" && v.match(/\.[a-zA-Z0-9]+$/));
221
+ if (hasFileExtension) {
222
+ const firstVal = validValues[0] as string;
223
+ const lastSlash = firstVal.lastIndexOf("/");
224
+ const inferredStoragePath = lastSlash > 0 ? firstVal.substring(0, lastSlash) : "files";
225
+ extraLines.push(` storage: {\n storagePath: "${inferredStoragePath}"\n }`);
226
+ } else if (isUrl) {
227
+ if (isMedia) {
228
+ extraLines.push(" ui: {\n url: \"image\"\n }");
229
+ } else {
230
+ extraLines.push(" ui: {\n url: true\n }");
231
+ }
232
+ }
233
+ }
234
+ }
235
+
236
+ result.extra = extraLines.length > 0 ? "\n" + extraLines.join(",\n") + "," : "";
237
+ return result;
238
+ }