@rebasepro/server-postgresql 0.5.0 → 0.6.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 (165) hide show
  1. package/dist/{server-postgresql/src/PostgresAdapter.d.ts → PostgresAdapter.d.ts} +1 -1
  2. package/dist/{server-postgresql/src/PostgresBackendDriver.d.ts → PostgresBackendDriver.d.ts} +2 -2
  3. package/dist/{server-postgresql/src/PostgresBootstrapper.d.ts → PostgresBootstrapper.d.ts} +11 -1
  4. package/dist/{server-postgresql/src/collections → collections}/PostgresCollectionRegistry.d.ts +4 -0
  5. package/dist/index.es.js +10168 -11145
  6. package/dist/index.es.js.map +1 -1
  7. package/dist/index.umd.js +10735 -11429
  8. package/dist/index.umd.js.map +1 -1
  9. package/dist/{server-postgresql/src/services → services}/EntityPersistService.d.ts +0 -14
  10. package/dist/utils/pg-error-utils.d.ts +55 -0
  11. package/package.json +24 -21
  12. package/src/PostgresAdapter.ts +9 -10
  13. package/src/PostgresBackendDriver.ts +134 -121
  14. package/src/PostgresBootstrapper.ts +86 -13
  15. package/src/auth/ensure-tables.ts +28 -5
  16. package/src/auth/services.ts +28 -18
  17. package/src/cli.ts +99 -96
  18. package/src/collections/PostgresCollectionRegistry.ts +7 -0
  19. package/src/connection.ts +11 -6
  20. package/src/data-transformer.ts +16 -14
  21. package/src/databasePoolManager.ts +3 -2
  22. package/src/history/HistoryService.ts +3 -2
  23. package/src/history/ensure-history-table.ts +5 -4
  24. package/src/schema/auth-schema.ts +1 -2
  25. package/src/schema/doctor-cli.ts +2 -1
  26. package/src/schema/doctor.ts +40 -37
  27. package/src/schema/generate-drizzle-schema-logic.ts +56 -18
  28. package/src/schema/generate-drizzle-schema.ts +11 -11
  29. package/src/schema/introspect-db-inference.ts +25 -25
  30. package/src/schema/introspect-db-logic.ts +38 -38
  31. package/src/schema/introspect-db.ts +28 -27
  32. package/src/services/BranchService.ts +14 -0
  33. package/src/services/EntityFetchService.ts +28 -25
  34. package/src/services/EntityPersistService.ts +11 -141
  35. package/src/services/RelationService.ts +57 -37
  36. package/src/services/entity-helpers.ts +6 -2
  37. package/src/services/realtimeService.ts +45 -32
  38. package/src/utils/drizzle-conditions.ts +31 -15
  39. package/src/utils/pg-error-utils.ts +211 -0
  40. package/src/websocket.ts +15 -12
  41. package/test/auth-services.test.ts +36 -19
  42. package/test/batch-many-to-many-regression.test.ts +119 -39
  43. package/test/data-transformer-hardening.test.ts +67 -33
  44. package/test/data-transformer.test.ts +4 -2
  45. package/test/doctor.test.ts +10 -5
  46. package/test/drizzle-conditions.test.ts +59 -6
  47. package/test/generate-drizzle-schema.test.ts +65 -40
  48. package/test/introspect-db-generation.test.ts +179 -81
  49. package/test/introspect-db-utils.test.ts +92 -37
  50. package/test/mocks/chalk.cjs +7 -0
  51. package/test/pg-error-utils.test.ts +221 -0
  52. package/test/postgresDataDriver.test.ts +14 -5
  53. package/test/property-ordering.test.ts +126 -79
  54. package/test/realtimeService.test.ts +6 -2
  55. package/test/relation-pipeline-gaps.test.ts +84 -36
  56. package/test/relations.test.ts +247 -0
  57. package/test/unmapped-tables-safety.test.ts +14 -6
  58. package/test/websocket.test.ts +1 -1
  59. package/tsconfig.json +5 -0
  60. package/tsconfig.prod.json +3 -0
  61. package/vite.config.ts +5 -5
  62. package/dist/common/src/collections/CollectionRegistry.d.ts +0 -56
  63. package/dist/common/src/collections/default-collections.d.ts +0 -9
  64. package/dist/common/src/collections/index.d.ts +0 -2
  65. package/dist/common/src/data/buildRebaseData.d.ts +0 -14
  66. package/dist/common/src/data/query_builder.d.ts +0 -55
  67. package/dist/common/src/index.d.ts +0 -4
  68. package/dist/common/src/util/builders.d.ts +0 -57
  69. package/dist/common/src/util/callbacks.d.ts +0 -6
  70. package/dist/common/src/util/collections.d.ts +0 -11
  71. package/dist/common/src/util/common.d.ts +0 -2
  72. package/dist/common/src/util/conditions.d.ts +0 -26
  73. package/dist/common/src/util/entities.d.ts +0 -58
  74. package/dist/common/src/util/enums.d.ts +0 -3
  75. package/dist/common/src/util/index.d.ts +0 -16
  76. package/dist/common/src/util/navigation_from_path.d.ts +0 -34
  77. package/dist/common/src/util/navigation_utils.d.ts +0 -20
  78. package/dist/common/src/util/parent_references_from_path.d.ts +0 -6
  79. package/dist/common/src/util/paths.d.ts +0 -14
  80. package/dist/common/src/util/permissions.d.ts +0 -14
  81. package/dist/common/src/util/references.d.ts +0 -2
  82. package/dist/common/src/util/relations.d.ts +0 -22
  83. package/dist/common/src/util/resolutions.d.ts +0 -72
  84. package/dist/common/src/util/storage.d.ts +0 -24
  85. package/dist/types/src/controllers/analytics_controller.d.ts +0 -7
  86. package/dist/types/src/controllers/auth.d.ts +0 -104
  87. package/dist/types/src/controllers/client.d.ts +0 -168
  88. package/dist/types/src/controllers/collection_registry.d.ts +0 -46
  89. package/dist/types/src/controllers/customization_controller.d.ts +0 -60
  90. package/dist/types/src/controllers/data.d.ts +0 -207
  91. package/dist/types/src/controllers/data_driver.d.ts +0 -218
  92. package/dist/types/src/controllers/database_admin.d.ts +0 -11
  93. package/dist/types/src/controllers/dialogs_controller.d.ts +0 -36
  94. package/dist/types/src/controllers/effective_role.d.ts +0 -4
  95. package/dist/types/src/controllers/email.d.ts +0 -36
  96. package/dist/types/src/controllers/index.d.ts +0 -18
  97. package/dist/types/src/controllers/local_config_persistence.d.ts +0 -20
  98. package/dist/types/src/controllers/navigation.d.ts +0 -225
  99. package/dist/types/src/controllers/registry.d.ts +0 -63
  100. package/dist/types/src/controllers/side_dialogs_controller.d.ts +0 -67
  101. package/dist/types/src/controllers/side_entity_controller.d.ts +0 -97
  102. package/dist/types/src/controllers/snackbar.d.ts +0 -24
  103. package/dist/types/src/controllers/storage.d.ts +0 -171
  104. package/dist/types/src/index.d.ts +0 -4
  105. package/dist/types/src/rebase_context.d.ts +0 -122
  106. package/dist/types/src/types/auth_adapter.d.ts +0 -301
  107. package/dist/types/src/types/backend.d.ts +0 -571
  108. package/dist/types/src/types/backend_hooks.d.ts +0 -172
  109. package/dist/types/src/types/builders.d.ts +0 -15
  110. package/dist/types/src/types/chips.d.ts +0 -5
  111. package/dist/types/src/types/collections.d.ts +0 -961
  112. package/dist/types/src/types/component_ref.d.ts +0 -47
  113. package/dist/types/src/types/cron.d.ts +0 -102
  114. package/dist/types/src/types/data_source.d.ts +0 -64
  115. package/dist/types/src/types/database_adapter.d.ts +0 -94
  116. package/dist/types/src/types/entities.d.ts +0 -145
  117. package/dist/types/src/types/entity_actions.d.ts +0 -104
  118. package/dist/types/src/types/entity_callbacks.d.ts +0 -173
  119. package/dist/types/src/types/entity_link_builder.d.ts +0 -7
  120. package/dist/types/src/types/entity_overrides.d.ts +0 -10
  121. package/dist/types/src/types/entity_views.d.ts +0 -87
  122. package/dist/types/src/types/export_import.d.ts +0 -21
  123. package/dist/types/src/types/formex.d.ts +0 -40
  124. package/dist/types/src/types/index.d.ts +0 -28
  125. package/dist/types/src/types/locales.d.ts +0 -4
  126. package/dist/types/src/types/modify_collections.d.ts +0 -5
  127. package/dist/types/src/types/plugins.d.ts +0 -282
  128. package/dist/types/src/types/properties.d.ts +0 -1173
  129. package/dist/types/src/types/property_config.d.ts +0 -74
  130. package/dist/types/src/types/relations.d.ts +0 -336
  131. package/dist/types/src/types/slots.d.ts +0 -262
  132. package/dist/types/src/types/translations.d.ts +0 -900
  133. package/dist/types/src/types/user_management_delegate.d.ts +0 -86
  134. package/dist/types/src/types/websockets.d.ts +0 -78
  135. package/dist/types/src/users/index.d.ts +0 -1
  136. package/dist/types/src/users/user.d.ts +0 -50
  137. /package/dist/{server-postgresql/src/auth → auth}/ensure-tables.d.ts +0 -0
  138. /package/dist/{server-postgresql/src/auth → auth}/services.d.ts +0 -0
  139. /package/dist/{server-postgresql/src/cli.d.ts → cli.d.ts} +0 -0
  140. /package/dist/{server-postgresql/src/connection.d.ts → connection.d.ts} +0 -0
  141. /package/dist/{server-postgresql/src/data-transformer.d.ts → data-transformer.d.ts} +0 -0
  142. /package/dist/{server-postgresql/src/databasePoolManager.d.ts → databasePoolManager.d.ts} +0 -0
  143. /package/dist/{server-postgresql/src/history → history}/HistoryService.d.ts +0 -0
  144. /package/dist/{server-postgresql/src/history → history}/ensure-history-table.d.ts +0 -0
  145. /package/dist/{server-postgresql/src/index.d.ts → index.d.ts} +0 -0
  146. /package/dist/{server-postgresql/src/interfaces.d.ts → interfaces.d.ts} +0 -0
  147. /package/dist/{server-postgresql/src/schema → schema}/auth-schema.d.ts +0 -0
  148. /package/dist/{server-postgresql/src/schema → schema}/doctor-cli.d.ts +0 -0
  149. /package/dist/{server-postgresql/src/schema → schema}/doctor.d.ts +0 -0
  150. /package/dist/{server-postgresql/src/schema → schema}/generate-drizzle-schema-logic.d.ts +0 -0
  151. /package/dist/{server-postgresql/src/schema → schema}/generate-drizzle-schema.d.ts +0 -0
  152. /package/dist/{server-postgresql/src/schema → schema}/introspect-db-inference.d.ts +0 -0
  153. /package/dist/{server-postgresql/src/schema → schema}/introspect-db-logic.d.ts +0 -0
  154. /package/dist/{server-postgresql/src/schema → schema}/introspect-db.d.ts +0 -0
  155. /package/dist/{server-postgresql/src/schema → schema}/test-schema.d.ts +0 -0
  156. /package/dist/{server-postgresql/src/services → services}/BranchService.d.ts +0 -0
  157. /package/dist/{server-postgresql/src/services → services}/EntityFetchService.d.ts +0 -0
  158. /package/dist/{server-postgresql/src/services → services}/RelationService.d.ts +0 -0
  159. /package/dist/{server-postgresql/src/services → services}/entity-helpers.d.ts +0 -0
  160. /package/dist/{server-postgresql/src/services → services}/entityService.d.ts +0 -0
  161. /package/dist/{server-postgresql/src/services → services}/index.d.ts +0 -0
  162. /package/dist/{server-postgresql/src/services → services}/realtimeService.d.ts +0 -0
  163. /package/dist/{server-postgresql/src/types.d.ts → types.d.ts} +0 -0
  164. /package/dist/{server-postgresql/src/utils → utils}/drizzle-conditions.d.ts +0 -0
  165. /package/dist/{server-postgresql/src/websocket.d.ts → websocket.d.ts} +0 -0
@@ -5,6 +5,7 @@ import { pathToFileURL } from "url";
5
5
  import chokidar from "chokidar";
6
6
  import { generateSchema } from "./generate-drizzle-schema-logic";
7
7
  import { EntityCollection } from "@rebasepro/types";
8
+ import { logger } from "@rebasepro/server-core";
8
9
 
9
10
 
10
11
  // --- Helper Functions ---
@@ -48,7 +49,7 @@ const formatTerminalText = (text: string, options: {
48
49
  const runGeneration = async (collectionsFilePath?: string, outputPath?: string) => {
49
50
  try {
50
51
  if (!collectionsFilePath) {
51
- console.error("Error: No collections file path provided. Skipping schema generation.");
52
+ logger.error("Error: No collections file path provided. Skipping schema generation.");
52
53
  return;
53
54
  }
54
55
 
@@ -74,7 +75,7 @@ const runGeneration = async (collectionsFilePath?: string, outputPath?: string)
74
75
  }
75
76
  } catch (err: unknown) {
76
77
  const message = err instanceof Error ? err.message : String(err);
77
- console.error(`Error loading ${file}:`, message);
78
+ logger.error(`Error loading ${file}`, { detail: message });
78
79
  }
79
80
  }
80
81
  }
@@ -91,7 +92,6 @@ const runGeneration = async (collectionsFilePath?: string, outputPath?: string)
91
92
  }
92
93
 
93
94
 
94
-
95
95
  // Sort collections by slug alphabetically to ensure deterministic schema generation
96
96
  collections.sort((a, b) => a.slug.localeCompare(b.slug));
97
97
 
@@ -101,20 +101,20 @@ const runGeneration = async (collectionsFilePath?: string, outputPath?: string)
101
101
  const outputDir = path.dirname(outputPath);
102
102
  await fsPromises.mkdir(outputDir, { recursive: true });
103
103
  await fsPromises.writeFile(outputPath, schemaContent);
104
- console.log("✅ Drizzle schema generated successfully at", outputPath);
104
+ logger.info("✅ Drizzle schema generated successfully at", { detail: outputPath });
105
105
  } else {
106
- console.log("✅ Drizzle schema generated successfully.");
107
- console.log(schemaContent);
106
+ logger.info("✅ Drizzle schema generated successfully.");
107
+ logger.info(String(schemaContent));
108
108
  }
109
109
 
110
- console.log(`You can now run ${formatTerminalText("rebase db generate", {
110
+ logger.info(`You can now run ${formatTerminalText("rebase db generate", {
111
111
  bold: true,
112
112
  backgroundColor: "blue",
113
113
  textColor: "black"
114
114
  })} to generate the SQL migration files.`);
115
115
 
116
116
  } catch (error) {
117
- console.error("Error generating schema:", error);
117
+ logger.error("Error generating schema", { error: error });
118
118
  }
119
119
  };
120
120
 
@@ -128,7 +128,7 @@ const main = () => {
128
128
  const watch = process.argv.includes("--watch");
129
129
 
130
130
  if (!collectionsFilePath) {
131
- console.log("Usage: ts-node generate-drizzle-schema.ts <path-to-collections-file> [--output <path-to-output-file>] [--watch]");
131
+ logger.info("Usage: ts-node generate-drizzle-schema.ts <path-to-collections-file> [--output <path-to-output-file>] [--watch]");
132
132
  return;
133
133
  }
134
134
 
@@ -136,14 +136,14 @@ const main = () => {
136
136
  const resolvedOutputPath = outputPath ? path.resolve(process.cwd(), outputPath) : undefined;
137
137
 
138
138
  if (watch) {
139
- console.log(`Watching for changes in ${resolvedPath}...`);
139
+ logger.info(`Watching for changes in ${resolvedPath}...`);
140
140
  const watcher = chokidar.watch(resolvedPath, {
141
141
  persistent: true,
142
142
  ignoreInitial: false
143
143
  });
144
144
 
145
145
  watcher.on("all", (event, filePath) => {
146
- console.log(`[${event}] ${filePath}. Regenerating schema...`);
146
+ logger.info(`[${event}] ${filePath}. Regenerating schema...`);
147
147
  runGeneration(resolvedPath, resolvedOutputPath);
148
148
  });
149
149
  } else {
@@ -45,15 +45,15 @@ export function inferPropertyFromData(
45
45
  const max = Math.max(...numValues);
46
46
  // Example heuristic: percentages
47
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 }`);
48
+ extraLines.push(" validation: {\n min: 0,\n max: 100\n }");
49
49
  } else if (min >= 0 && (colNameLower.includes("count") || colNameLower.includes("total") || colNameLower.includes("amount"))) {
50
- extraLines.push(` validation: {\n min: 0\n }`);
50
+ extraLines.push(" validation: {\n min: 0\n }");
51
51
  }
52
52
  }
53
53
 
54
54
  // Currency
55
55
  if (colNameLower.includes("price") || colNameLower.includes("cost") || colNameLower.includes("amount") || colNameLower.includes("fee") || pgDataType === "money") {
56
- extraLines.push(` ui: {\n currency: true\n }`);
56
+ extraLines.push(" ui: {\n currency: true\n }");
57
57
  }
58
58
  }
59
59
 
@@ -94,7 +94,7 @@ export function inferPropertyFromData(
94
94
  extraLines.push(` of: { name: "${humanize(columnName)} Item", type: "${innerType}" }`);
95
95
  } else {
96
96
  result.propType = "map";
97
-
97
+
98
98
  // Infer inner schema
99
99
  if (allObjects && validValues.length > 0) {
100
100
  const schema: Record<string, string> = {};
@@ -115,7 +115,7 @@ export function inferPropertyFromData(
115
115
  }
116
116
  }
117
117
  }
118
-
118
+
119
119
  const keys = Object.keys(schema).filter(k => schema[k] !== "mixed");
120
120
  if (keys.length > 0) {
121
121
  const props = keys.map(k => {
@@ -123,10 +123,10 @@ export function inferPropertyFromData(
123
123
  }).join(",");
124
124
  extraLines.push(` properties: {${props}\n }`);
125
125
  } else {
126
- extraLines.push(` keyValue: true`);
126
+ extraLines.push(" keyValue: true");
127
127
  }
128
128
  } else {
129
- extraLines.push(` keyValue: true`);
129
+ extraLines.push(" keyValue: true");
130
130
  }
131
131
  }
132
132
  }
@@ -134,7 +134,7 @@ export function inferPropertyFromData(
134
134
  // ── String Analysis ──────────────────────────────────────────────────
135
135
  if (currentPropType === "string") {
136
136
  // Date/Time Strings
137
- if (validValues.every(v => typeof v === 'string' && ISO_8601_REGEX.test(v))) {
137
+ if (validValues.every(v => typeof v === "string" && ISO_8601_REGEX.test(v))) {
138
138
  result.propType = "date";
139
139
  return result;
140
140
  }
@@ -148,7 +148,7 @@ export function inferPropertyFromData(
148
148
  maxEnumLength = v.length;
149
149
  }
150
150
  }
151
-
151
+
152
152
  // Ensure no empty string, max length makes sense, and fewer unique values than total values (unless small total)
153
153
  if (uniqueValues.size > 0 && uniqueValues.size <= 5 && maxEnumLength <= 50 && validValues.length > uniqueValues.size && !uniqueValues.has("")) {
154
154
  const isLikelyId = isPk || colNameLower.endsWith("_id");
@@ -161,18 +161,18 @@ export function inferPropertyFromData(
161
161
  }
162
162
 
163
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));
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
166
  if (allUuid) {
167
- if (isPk) extraLines.push(` isId: "uuid"`);
167
+ if (isPk) extraLines.push(" isId: \"uuid\"");
168
168
  } else if (allCuid) {
169
- if (isPk) extraLines.push(` isId: "cuid"`);
169
+ if (isPk) extraLines.push(" isId: \"cuid\"");
170
170
  }
171
171
 
172
172
  // Color Codes
173
- const allColors = validValues.every(v => typeof v === 'string' && COLOR_HEX_REGEX.test(v));
173
+ const allColors = validValues.every(v => typeof v === "string" && COLOR_HEX_REGEX.test(v));
174
174
  if (allColors) {
175
- extraLines.push(` ui: {\n color: true\n }`);
175
+ extraLines.push(" ui: {\n color: true\n }");
176
176
  }
177
177
 
178
178
  // Text Lengths, Multiline & Markdown
@@ -190,9 +190,9 @@ export function inferPropertyFromData(
190
190
  }
191
191
 
192
192
  if (hasMarkdown) {
193
- extraLines.push(` multiline: true,\n markdown: true`);
193
+ extraLines.push(" multiline: true,\n markdown: true");
194
194
  } else if (hasNewlines || maxLength > 100) {
195
- extraLines.push(` multiline: true`);
195
+ extraLines.push(" multiline: true");
196
196
  }
197
197
 
198
198
  if (maxLength > 0 && maxLength < 10000) { // arbitrary cap to avoid huge limits
@@ -208,26 +208,26 @@ export function inferPropertyFromData(
208
208
  const isUrl = colNameLower.endsWith("_url") || colNameLower.endsWith("_uri") || colNameLower.endsWith("_link");
209
209
  const isMedia = colNameLower.includes("image") || colNameLower.includes("avatar") || colNameLower.includes("photo") || colNameLower.includes("logo") || colNameLower.includes("cover");
210
210
 
211
- const allAbsoluteUrls = validValues.every(v => typeof v === 'string' && (v.startsWith("http://") || v.startsWith("https://")));
211
+ const allAbsoluteUrls = validValues.every(v => typeof v === "string" && (v.startsWith("http://") || v.startsWith("https://")));
212
212
  if (allAbsoluteUrls) {
213
- const isImage = validValues.some(v => typeof v === 'string' && v.match(/\.(jpeg|jpg|gif|png|webp|svg)/i));
213
+ const isImage = validValues.some(v => typeof v === "string" && v.match(/\.(jpeg|jpg|gif|png|webp|svg)/i));
214
214
  if (isImage || isMedia) {
215
- extraLines.push(` ui: {\n url: "image"\n }`);
215
+ extraLines.push(" ui: {\n url: \"image\"\n }");
216
216
  } else {
217
- extraLines.push(` ui: {\n url: true\n }`);
217
+ extraLines.push(" ui: {\n url: true\n }");
218
218
  }
219
219
  } else {
220
- const hasFileExtension = validValues.some(v => typeof v === 'string' && v.match(/\.[a-zA-Z0-9]+$/));
220
+ const hasFileExtension = validValues.some(v => typeof v === "string" && v.match(/\.[a-zA-Z0-9]+$/));
221
221
  if (hasFileExtension) {
222
222
  const firstVal = validValues[0] as string;
223
- const lastSlash = firstVal.lastIndexOf('/');
223
+ const lastSlash = firstVal.lastIndexOf("/");
224
224
  const inferredStoragePath = lastSlash > 0 ? firstVal.substring(0, lastSlash) : "files";
225
225
  extraLines.push(` storage: {\n storagePath: "${inferredStoragePath}"\n }`);
226
226
  } else if (isUrl) {
227
227
  if (isMedia) {
228
- extraLines.push(` ui: {\n url: "image"\n }`);
228
+ extraLines.push(" ui: {\n url: \"image\"\n }");
229
229
  } else {
230
- extraLines.push(` ui: {\n url: true\n }`);
230
+ extraLines.push(" ui: {\n url: true\n }");
231
231
  }
232
232
  }
233
233
  }
@@ -63,7 +63,7 @@ const IRREGULAR_SINGULARS: Record<string, string> = {
63
63
  data: "datum",
64
64
  media: "medium",
65
65
  criteria: "criterion",
66
- phenomena: "phenomenon",
66
+ phenomena: "phenomenon"
67
67
  };
68
68
 
69
69
  /** Words ending in 's' that are already singular. */
@@ -73,7 +73,7 @@ const UNCOUNTABLE = new Set([
73
73
  "synopsis", "parenthesis", "hypothesis", "emphasis",
74
74
  "news", "series", "species", "means", "athletics",
75
75
  "economics", "electronics", "mathematics", "physics",
76
- "politics", "statistics",
76
+ "politics", "statistics"
77
77
  ]);
78
78
 
79
79
  export function singularize(word: string): string {
@@ -165,10 +165,10 @@ export function mapPgType(dataType: string): string {
165
165
 
166
166
  // Numeric types
167
167
  if (
168
- dt.includes("int") || // integer, smallint, bigint
168
+ dt.includes("int") || // integer, smallint, bigint
169
169
  dt.includes("numeric") ||
170
170
  dt.includes("decimal") ||
171
- dt.includes("serial") || // serial, bigserial
171
+ dt.includes("serial") || // serial, bigserial
172
172
  dt === "real" ||
173
173
  dt === "float4" ||
174
174
  dt === "float8" ||
@@ -281,7 +281,7 @@ export interface PropertyOrderingContext {
281
281
  const IDENTITY_EXACT: Record<string, number> = {
282
282
  id: 0,
283
283
  uuid: 1,
284
- _id: 2,
284
+ _id: 2
285
285
  };
286
286
 
287
287
  // — Tier 1: Title / Name — the "display column" (10–19) ———————————————
@@ -293,7 +293,7 @@ const TITLE_EXACT: Record<string, number> = {
293
293
  displayname: 13,
294
294
  headline: 14,
295
295
  subject: 15,
296
- heading: 16,
296
+ heading: 16
297
297
  };
298
298
 
299
299
  // — Tier 2: Human identity fields (20–29) —————————————————————————————
@@ -313,7 +313,7 @@ const HUMAN_IDENTITY_EXACT: Record<string, number> = {
313
313
  email_address: 26,
314
314
  phone: 27,
315
315
  phone_number: 27,
316
- mobile: 27,
316
+ mobile: 27
317
317
  };
318
318
 
319
319
  // — Tier 3: Core descriptors (30–39) ——————————————————————————————————
@@ -333,7 +333,7 @@ const DESCRIPTOR_EXACT: Record<string, number> = {
333
333
  priority: 39,
334
334
  order: 39,
335
335
  sort_order: 39,
336
- position: 39,
336
+ position: 39
337
337
  };
338
338
 
339
339
  // — Tier 12: System timestamps (120–129) ——————————————————————————————
@@ -348,7 +348,7 @@ const SYSTEM_TIMESTAMP_EXACT: Record<string, number> = {
348
348
  last_modified: 122,
349
349
  deleted_at: 123,
350
350
  deletedat: 123,
351
- archived_at: 124,
351
+ archived_at: 124
352
352
  };
353
353
 
354
354
  // — Pattern-based rules for partial matches ———————————————————————————
@@ -370,7 +370,7 @@ const JSON_MAP_NAMES = new Set(["metadata", "meta", "config", "configuration", "
370
370
  */
371
371
  export function computePropertyPriority(
372
372
  columnName: string,
373
- ctx: PropertyOrderingContext,
373
+ ctx: PropertyOrderingContext
374
374
  ): number {
375
375
  // Normalize camelCase/PascalCase to snake_case, then lowercase
376
376
  const col = columnName.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
@@ -521,7 +521,7 @@ export function generateCollectionFile(
521
521
  joinTables: Set<string>,
522
522
  tablesMap: Map<string, TableMeta>,
523
523
  enumMap: Map<string, string[]>,
524
- sampleData?: Record<string, unknown>[],
524
+ sampleData?: Record<string, unknown>[]
525
525
  ): string {
526
526
  const collectionName = humanize(tableName);
527
527
  const singular = singularize(collectionName);
@@ -529,8 +529,8 @@ export function generateCollectionFile(
529
529
 
530
530
  const imports = new Set<string>(['import { PostgresCollection } from "@rebasepro/types";']);
531
531
 
532
- let propsOutput = ``;
533
- let relationsOutput = ``;
532
+ let propsOutput = "";
533
+ let relationsOutput = "";
534
534
  const orderEntries: PropertyOrderEntry[] = [];
535
535
  const propertyBlocks = new Map<string, string>();
536
536
  let columnIndex = 0;
@@ -559,7 +559,7 @@ export function generateCollectionFile(
559
559
  // ── Data Inference Engine ────────────────────────────────────────────
560
560
  let finalPropType = propType;
561
561
  let inferenceExtra = "";
562
-
562
+
563
563
  if (!isEnumColumn && sampleData && sampleData.length > 0) {
564
564
  const values = sampleData.map(r => r[col.column_name]);
565
565
  const inferred = inferPropertyFromData(col.column_name, col.data_type, propType, values, meta.pks.includes(col.column_name));
@@ -578,11 +578,11 @@ export function generateCollectionFile(
578
578
  // Date auto-value heuristics
579
579
  if (finalPropType === "date") {
580
580
  if (colNameLower === "created_at" || colNameLower === "createdat") {
581
- extra += `\n autoValue: "on_create",\n ui: {\n readOnly: true,\n hideFromCollection: true\n },`;
581
+ extra += "\n autoValue: \"on_create\",\n ui: {\n readOnly: true,\n hideFromCollection: true\n },";
582
582
  } else if (colNameLower === "updated_at" || colNameLower === "updatedat") {
583
- extra += `\n autoValue: "on_update",\n ui: {\n readOnly: true,\n hideFromCollection: true\n },`;
583
+ extra += "\n autoValue: \"on_update\",\n ui: {\n readOnly: true,\n hideFromCollection: true\n },";
584
584
  } else if (col.column_default && (col.column_default.includes("now()") || col.column_default.includes("CURRENT_TIMESTAMP"))) {
585
- extra += `\n autoValue: "on_create",\n ui: {\n readOnly: true\n },`;
585
+ extra += "\n autoValue: \"on_create\",\n ui: {\n readOnly: true\n },";
586
586
  }
587
587
  }
588
588
 
@@ -602,7 +602,7 @@ export function generateCollectionFile(
602
602
  }
603
603
  extra += `\n of: { name: "${humanize(col.column_name)} Item", type: "${innerType}" },`;
604
604
  } else if (finalPropType === "map" && !inferenceExtra.includes("keyValue: true") && !inferenceExtra.includes("properties: {")) {
605
- extra += `\n keyValue: true,`;
605
+ extra += "\n keyValue: true,";
606
606
  }
607
607
 
608
608
  // String sub-type heuristics (Fallback if not handled by inference or enum)
@@ -613,16 +613,16 @@ export function generateCollectionFile(
613
613
  if (isMedia) {
614
614
  extra += `\n storage: {\n storagePath: "${tableName}/${col.column_name}"\n },`;
615
615
  } else if (isUrl) {
616
- extra += `\n ui: {\n url: true\n },`;
616
+ extra += "\n ui: {\n url: true\n },";
617
617
  } else if (colNameLower === "description" || colNameLower === "summary" || colNameLower === "excerpt") {
618
- extra += `\n multiline: true,`;
618
+ extra += "\n multiline: true,";
619
619
  } else if (colNameLower === "content" || colNameLower === "body") {
620
- extra += `\n multiline: true,\n markdown: true,`;
620
+ extra += "\n multiline: true,\n markdown: true,";
621
621
  } else if (col.data_type === "text") {
622
- extra += `\n multiline: true,`;
622
+ extra += "\n multiline: true,";
623
623
  }
624
624
  }
625
-
625
+
626
626
  // Append inference results
627
627
  if (inferenceExtra) {
628
628
  extra += inferenceExtra;
@@ -634,11 +634,11 @@ export function generateCollectionFile(
634
634
  if (isCompositePk) {
635
635
  extra += `\n // Part of composite primary key (${meta.pks.join(", ")})`;
636
636
  } else if (finalPropType === "number" && !inferenceExtra.includes("isId:")) {
637
- extra += `\n isId: "increment",`;
637
+ extra += "\n isId: \"increment\",";
638
638
  } else if (col.data_type.toLowerCase() === "uuid" && !inferenceExtra.includes("isId:")) {
639
- extra += `\n isId: "uuid",`;
639
+ extra += "\n isId: \"uuid\",";
640
640
  } else if (!inferenceExtra.includes("isId:")) {
641
- extra += `\n isId: "uuid", // Verify if this is a UUID or CUID`;
641
+ extra += "\n isId: \"uuid\", // Verify if this is a UUID or CUID";
642
642
  }
643
643
  }
644
644
 
@@ -651,7 +651,7 @@ export function generateCollectionFile(
651
651
  if (extra.includes("validation: {")) {
652
652
  extra = extra.replace("validation: {", "validation: {\n required: true,");
653
653
  } else {
654
- extra += `\n validation: {\n required: true\n },`;
654
+ extra += "\n validation: {\n required: true\n },";
655
655
  }
656
656
  }
657
657
 
@@ -665,8 +665,8 @@ export function generateCollectionFile(
665
665
  isEnum: isEnumColumn,
666
666
  isStorage: extra.includes("storage: {") || inferenceExtra.includes("storage: {"),
667
667
  pgDataType: col.data_type,
668
- originalIndex: currentIndex,
669
- },
668
+ originalIndex: currentIndex
669
+ }
670
670
  });
671
671
 
672
672
  propertyBlocks.set(col.column_name, `
@@ -696,8 +696,8 @@ export function generateCollectionFile(
696
696
  isEnum: false,
697
697
  isStorage: false,
698
698
  pgDataType: "",
699
- originalIndex: columnIndex++,
700
- },
699
+ originalIndex: columnIndex++
700
+ }
701
701
  });
702
702
 
703
703
  const targetCollectionCamel = toCollectionVarName(targetTableName);
@@ -794,7 +794,7 @@ export function generateCollectionFile(
794
794
  if (direction === "owning" && thisFk) {
795
795
  throughCode = `\n through: {\n table: "${jt}",\n sourceColumn: "${thisFk.column_name}",\n targetColumn: "${otherFk.column_name}"\n },`;
796
796
  } else if (direction === "inverse") {
797
- throughCode = `\n // Make sure the target collection configures the 'through' property.`;
797
+ throughCode = "\n // Make sure the target collection configures the 'through' property.";
798
798
  }
799
799
 
800
800
  relationsOutput += `
@@ -860,10 +860,10 @@ export function mergeIndexContent(existingContent: string, newFileNames: string[
860
860
  [...existingContent.matchAll(/import\s+([a-zA-Z0-9_]+)\s+from\s+"\.\/([^"]+)"/g)].map((m) => m[2])
861
861
  );
862
862
  const sorted = [...newFileNames].sort();
863
-
863
+
864
864
  let newImports = "";
865
865
  let newElements = "";
866
-
866
+
867
867
  for (const f of sorted) {
868
868
  if (!existingImports.has(f)) {
869
869
  const varName = toCollectionVarName(f);
@@ -871,9 +871,9 @@ export function mergeIndexContent(existingContent: string, newFileNames: string[
871
871
  newElements += ` ${varName},\n`;
872
872
  }
873
873
  }
874
-
874
+
875
875
  if (!newImports) return existingContent;
876
-
876
+
877
877
  // Simple injection logic:
878
878
  // Add new imports below the last import or at the top
879
879
  const importRegex = /import\s+.*?;/g;
@@ -882,7 +882,7 @@ export function mergeIndexContent(existingContent: string, newFileNames: string[
882
882
  while ((match = importRegex.exec(existingContent)) !== null) {
883
883
  lastImportMatch = match;
884
884
  }
885
-
885
+
886
886
  let contentWithImports = existingContent;
887
887
  if (lastImportMatch) {
888
888
  const pos = lastImportMatch.index + lastImportMatch[0].length;
@@ -890,7 +890,7 @@ export function mergeIndexContent(existingContent: string, newFileNames: string[
890
890
  } else {
891
891
  contentWithImports = newImports + "\n" + existingContent;
892
892
  }
893
-
893
+
894
894
  // Inject into the `collections = [...]` array
895
895
  const arrayRegex = /export\s+const\s+collections\s*=\s*\[([\s\S]*?)\];/;
896
896
  return contentWithImports.replace(arrayRegex, (fullMatch, arrayContent) => {
@@ -18,8 +18,9 @@ import {
18
18
  generateCollectionFile,
19
19
  generateIndexContent,
20
20
  mergeIndexContent,
21
- safeHostFromUrl,
21
+ safeHostFromUrl
22
22
  } from "./introspect-db-logic";
23
+ import { logger } from "@rebasepro/server-core";
23
24
 
24
25
  async function main() {
25
26
  const args = arg(
@@ -31,15 +32,15 @@ async function main() {
31
32
  "--data-inference": Boolean,
32
33
  "-o": "--output",
33
34
  "-c": "--collections",
34
- "-f": "--force",
35
+ "-f": "--force"
35
36
  },
36
37
  { permissive: true }
37
38
  );
38
39
 
39
40
  const cwd = process.cwd();
40
41
  const isBackendDir = path.basename(cwd) === "backend";
41
- const defaultOutDir = isBackendDir
42
- ? path.resolve(cwd, "..", "config", "collections")
42
+ const defaultOutDir = isBackendDir
43
+ ? path.resolve(cwd, "..", "config", "collections")
43
44
  : path.resolve(cwd, "config", "collections");
44
45
 
45
46
  const outDir = args["--output"] || args["--collections"] || defaultOutDir;
@@ -67,7 +68,7 @@ async function main() {
67
68
 
68
69
  const databaseUrl = process.env.DATABASE_URL || process.env.ADMIN_CONNECTION_STRING;
69
70
  if (!databaseUrl) {
70
- console.error(chalk.red("✗ DATABASE_URL is not set. Make sure your .env file is configured."));
71
+ logger.error(chalk.red("✗ DATABASE_URL is not set. Make sure your .env file is configured."));
71
72
  process.exit(1);
72
73
  }
73
74
 
@@ -76,15 +77,15 @@ async function main() {
76
77
  try {
77
78
  await client.connect();
78
79
  } catch (err) {
79
- console.error(chalk.red(`✗ Failed to connect to database: ${err instanceof Error ? err.message : String(err)}`));
80
- console.error(chalk.gray(" Check your DATABASE_URL and ensure the database is reachable."));
80
+ logger.error(chalk.red(`✗ Failed to connect to database: ${err instanceof Error ? err.message : String(err)}`));
81
+ logger.error(chalk.gray(" Check your DATABASE_URL and ensure the database is reachable."));
81
82
  process.exit(1);
82
83
  }
83
84
 
84
85
  // Log the host portion safely — handle URLs without "@"
85
86
  const hostPart = safeHostFromUrl(databaseUrl);
86
- console.log(chalk.gray(`Connected to database: ${hostPart}`));
87
- console.log(chalk.gray(`Introspecting schema '${pgSchema}'...`));
87
+ logger.info(chalk.gray(`Connected to database: ${hostPart}`));
88
+ logger.info(chalk.gray(`Introspecting schema '${pgSchema}'...`));
88
89
 
89
90
  try {
90
91
  // 1. Get Tables
@@ -162,7 +163,7 @@ async function main() {
162
163
  const tablesMap = buildTablesMap(tables, columns, pks, fks);
163
164
  const joinTables = identifyJoinTables(tablesMap);
164
165
 
165
- console.log(chalk.blue(`Found ${tablesMap.size} tables (including ${joinTables.size} detected join tables).`));
166
+ logger.info(chalk.blue(`Found ${tablesMap.size} tables (including ${joinTables.size} detected join tables).`));
166
167
 
167
168
  let runDataInference = false;
168
169
  if (args["--data-inference"] !== undefined) {
@@ -173,12 +174,12 @@ async function main() {
173
174
  output: process.stdout
174
175
  });
175
176
  const answer = await new Promise<string>((resolve) => rl.question(chalk.yellow("? Do you want to run comprehensive data inference on sampled rows to auto-detect types, formats, constraints, and UI configurations? (y/N) "), resolve));
176
- runDataInference = answer.trim().toLowerCase() === 'y';
177
+ runDataInference = answer.trim().toLowerCase() === "y";
177
178
  rl.close();
178
179
  }
179
180
 
180
181
  if (runDataInference) {
181
- console.log(chalk.gray(`Sampling database rows for data inference...`));
182
+ logger.info(chalk.gray("Sampling database rows for data inference..."));
182
183
  }
183
184
 
184
185
  // Generate Collections
@@ -186,11 +187,11 @@ async function main() {
186
187
  const skippedFiles: string[] = [];
187
188
 
188
189
  const tablesToProcess = Array.from(tablesMap.entries()).filter(([tableName]) => !joinTables.has(tableName));
189
-
190
+
190
191
  const BATCH_SIZE = 10;
191
192
  for (let i = 0; i < tablesToProcess.length; i += BATCH_SIZE) {
192
193
  const batch = tablesToProcess.slice(i, i + BATCH_SIZE);
193
-
194
+
194
195
  await Promise.all(batch.map(async ([tableName, meta]) => {
195
196
  // ── File overwrite protection ──────────────────────────────
196
197
  const filePath = path.join(outDir, `${tableName}.ts`);
@@ -205,7 +206,7 @@ async function main() {
205
206
  const { rows } = await client.query(`SELECT * FROM "${pgSchema}"."${tableName}" LIMIT 100`);
206
207
  sampleData = rows;
207
208
  } catch (err) {
208
- console.error(chalk.yellow(`⚠ Failed to sample data for table ${tableName}: ${err instanceof Error ? err.message : String(err)}`));
209
+ logger.error(chalk.yellow(`⚠ Failed to sample data for table ${tableName}: ${err instanceof Error ? err.message : String(err)}`));
209
210
  }
210
211
  }
211
212
 
@@ -216,12 +217,12 @@ async function main() {
216
217
  joinTables,
217
218
  tablesMap,
218
219
  enumMap,
219
- sampleData,
220
+ sampleData
220
221
  );
221
222
 
222
223
  fs.writeFileSync(filePath, fileContent, "utf-8");
223
224
  generatedFiles.push(tableName);
224
- console.log(chalk.green(` ✓ ${filePath}`));
225
+ logger.info(chalk.green(` ✓ ${filePath}`));
225
226
  }));
226
227
  }
227
228
 
@@ -238,21 +239,21 @@ async function main() {
238
239
  const indexContent = generateIndexContent(generatedFiles);
239
240
  fs.writeFileSync(indexPath, indexContent, "utf-8");
240
241
  }
241
- console.log(chalk.green(` ✓ ${indexPath}`));
242
+ logger.info(chalk.green(` ✓ ${indexPath}`));
242
243
  }
243
244
 
244
- console.log("");
245
+ logger.info("");
245
246
  if (skippedFiles.length > 0) {
246
- console.log(chalk.yellow(`⚠ Skipped ${skippedFiles.length} existing file(s): ${skippedFiles.join(", ")}`));
247
- console.log(chalk.gray(` Use --force to overwrite existing files.`));
248
- console.log("");
247
+ logger.info(chalk.yellow(`⚠ Skipped ${skippedFiles.length} existing file(s): ${skippedFiles.join(", ")}`));
248
+ logger.info(chalk.gray(" Use --force to overwrite existing files."));
249
+ logger.info("");
249
250
  }
250
- console.log(chalk.bold.green(`✓ Introspected ${tablesMap.size} tables — generated ${generatedFiles.length} collection(s).`));
251
- console.log(chalk.gray(` Review the generated files in ${outDir} and customize properties as needed.`));
252
- console.log("");
251
+ logger.info(chalk.bold.green(`✓ Introspected ${tablesMap.size} tables — generated ${generatedFiles.length} collection(s).`));
252
+ logger.info(chalk.gray(` Review the generated files in ${outDir} and customize properties as needed.`));
253
+ logger.info("");
253
254
 
254
255
  } catch (e) {
255
- console.error(chalk.red(`✗ Error introspecting database: ${e instanceof Error ? e.message : String(e)}`));
256
+ logger.error(chalk.red(`✗ Error introspecting database: ${e instanceof Error ? e.message : String(e)}`));
256
257
  process.exit(1);
257
258
  } finally {
258
259
  await client.end();
@@ -260,6 +261,6 @@ async function main() {
260
261
  }
261
262
 
262
263
  main().catch((err) => {
263
- console.error(err);
264
+ logger.error(String(err));
264
265
  process.exit(1);
265
266
  });