@rebasepro/server-core 0.0.1-canary.4d4fb3e

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 (254) hide show
  1. package/LICENSE +6 -0
  2. package/README.md +40 -0
  3. package/build-errors.txt +52 -0
  4. package/coverage/clover.xml +3739 -0
  5. package/coverage/coverage-final.json +31 -0
  6. package/coverage/lcov-report/base.css +224 -0
  7. package/coverage/lcov-report/block-navigation.js +87 -0
  8. package/coverage/lcov-report/favicon.png +0 -0
  9. package/coverage/lcov-report/index.html +266 -0
  10. package/coverage/lcov-report/prettify.css +1 -0
  11. package/coverage/lcov-report/prettify.js +2 -0
  12. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  13. package/coverage/lcov-report/sorter.js +210 -0
  14. package/coverage/lcov-report/src/api/ast-schema-editor.ts.html +952 -0
  15. package/coverage/lcov-report/src/api/errors.ts.html +472 -0
  16. package/coverage/lcov-report/src/api/graphql/graphql-schema-generator.ts.html +1069 -0
  17. package/coverage/lcov-report/src/api/graphql/index.html +116 -0
  18. package/coverage/lcov-report/src/api/index.html +176 -0
  19. package/coverage/lcov-report/src/api/openapi-generator.ts.html +565 -0
  20. package/coverage/lcov-report/src/api/rest/api-generator.ts.html +994 -0
  21. package/coverage/lcov-report/src/api/rest/index.html +131 -0
  22. package/coverage/lcov-report/src/api/rest/query-parser.ts.html +550 -0
  23. package/coverage/lcov-report/src/api/schema-editor-routes.ts.html +202 -0
  24. package/coverage/lcov-report/src/api/server.ts.html +823 -0
  25. package/coverage/lcov-report/src/auth/admin-routes.ts.html +973 -0
  26. package/coverage/lcov-report/src/auth/index.html +176 -0
  27. package/coverage/lcov-report/src/auth/jwt.ts.html +574 -0
  28. package/coverage/lcov-report/src/auth/middleware.ts.html +745 -0
  29. package/coverage/lcov-report/src/auth/password.ts.html +310 -0
  30. package/coverage/lcov-report/src/auth/services.ts.html +2074 -0
  31. package/coverage/lcov-report/src/collections/index.html +116 -0
  32. package/coverage/lcov-report/src/collections/loader.ts.html +232 -0
  33. package/coverage/lcov-report/src/db/auth-schema.ts.html +523 -0
  34. package/coverage/lcov-report/src/db/data-transformer.ts.html +1753 -0
  35. package/coverage/lcov-report/src/db/entityService.ts.html +700 -0
  36. package/coverage/lcov-report/src/db/index.html +146 -0
  37. package/coverage/lcov-report/src/db/services/EntityFetchService.ts.html +4048 -0
  38. package/coverage/lcov-report/src/db/services/EntityPersistService.ts.html +883 -0
  39. package/coverage/lcov-report/src/db/services/RelationService.ts.html +3121 -0
  40. package/coverage/lcov-report/src/db/services/entity-helpers.ts.html +442 -0
  41. package/coverage/lcov-report/src/db/services/index.html +176 -0
  42. package/coverage/lcov-report/src/db/services/index.ts.html +124 -0
  43. package/coverage/lcov-report/src/generate-drizzle-schema-logic.ts.html +1960 -0
  44. package/coverage/lcov-report/src/index.html +116 -0
  45. package/coverage/lcov-report/src/services/driver-registry.ts.html +631 -0
  46. package/coverage/lcov-report/src/services/index.html +131 -0
  47. package/coverage/lcov-report/src/services/postgresDataDriver.ts.html +3025 -0
  48. package/coverage/lcov-report/src/storage/LocalStorageController.ts.html +1189 -0
  49. package/coverage/lcov-report/src/storage/S3StorageController.ts.html +970 -0
  50. package/coverage/lcov-report/src/storage/index.html +161 -0
  51. package/coverage/lcov-report/src/storage/storage-registry.ts.html +646 -0
  52. package/coverage/lcov-report/src/storage/types.ts.html +451 -0
  53. package/coverage/lcov-report/src/utils/drizzle-conditions.ts.html +3082 -0
  54. package/coverage/lcov-report/src/utils/index.html +116 -0
  55. package/coverage/lcov.info +7179 -0
  56. package/dist/common/src/collections/CollectionRegistry.d.ts +48 -0
  57. package/dist/common/src/collections/index.d.ts +1 -0
  58. package/dist/common/src/data/buildRebaseData.d.ts +14 -0
  59. package/dist/common/src/index.d.ts +3 -0
  60. package/dist/common/src/util/builders.d.ts +57 -0
  61. package/dist/common/src/util/callbacks.d.ts +6 -0
  62. package/dist/common/src/util/collections.d.ts +11 -0
  63. package/dist/common/src/util/common.d.ts +2 -0
  64. package/dist/common/src/util/conditions.d.ts +26 -0
  65. package/dist/common/src/util/entities.d.ts +36 -0
  66. package/dist/common/src/util/enums.d.ts +3 -0
  67. package/dist/common/src/util/index.d.ts +16 -0
  68. package/dist/common/src/util/navigation_from_path.d.ts +34 -0
  69. package/dist/common/src/util/navigation_utils.d.ts +20 -0
  70. package/dist/common/src/util/parent_references_from_path.d.ts +6 -0
  71. package/dist/common/src/util/paths.d.ts +14 -0
  72. package/dist/common/src/util/permissions.d.ts +5 -0
  73. package/dist/common/src/util/references.d.ts +2 -0
  74. package/dist/common/src/util/relations.d.ts +12 -0
  75. package/dist/common/src/util/resolutions.d.ts +72 -0
  76. package/dist/common/src/util/storage.d.ts +24 -0
  77. package/dist/index-BeMqpmfQ.js +239 -0
  78. package/dist/index-BeMqpmfQ.js.map +1 -0
  79. package/dist/index-bl4J3lNb.js +55823 -0
  80. package/dist/index-bl4J3lNb.js.map +1 -0
  81. package/dist/index.es.js +58 -0
  82. package/dist/index.es.js.map +1 -0
  83. package/dist/index.umd.js +56062 -0
  84. package/dist/index.umd.js.map +1 -0
  85. package/dist/server-core/src/api/ast-schema-editor.d.ts +21 -0
  86. package/dist/server-core/src/api/collections_for_test/callbacks_test_collection.d.ts +2 -0
  87. package/dist/server-core/src/api/errors.d.ts +35 -0
  88. package/dist/server-core/src/api/graphql/graphql-schema-generator.d.ts +35 -0
  89. package/dist/server-core/src/api/graphql/index.d.ts +1 -0
  90. package/dist/server-core/src/api/index.d.ts +9 -0
  91. package/dist/server-core/src/api/openapi-generator.d.ts +2 -0
  92. package/dist/server-core/src/api/rest/api-generator.d.ts +64 -0
  93. package/dist/server-core/src/api/rest/index.d.ts +1 -0
  94. package/dist/server-core/src/api/rest/query-parser.d.ts +9 -0
  95. package/dist/server-core/src/api/schema-editor-routes.d.ts +3 -0
  96. package/dist/server-core/src/api/server.d.ts +40 -0
  97. package/dist/server-core/src/api/types.d.ts +90 -0
  98. package/dist/server-core/src/auth/admin-routes.d.ts +7 -0
  99. package/dist/server-core/src/auth/google-oauth.d.ts +20 -0
  100. package/dist/server-core/src/auth/index.d.ts +12 -0
  101. package/dist/server-core/src/auth/interfaces.d.ts +270 -0
  102. package/dist/server-core/src/auth/jwt.d.ts +42 -0
  103. package/dist/server-core/src/auth/middleware.d.ts +56 -0
  104. package/dist/server-core/src/auth/password.d.ts +22 -0
  105. package/dist/server-core/src/auth/rate-limiter.d.ts +31 -0
  106. package/dist/server-core/src/auth/routes.d.ts +17 -0
  107. package/dist/server-core/src/bootstrappers/index.d.ts +0 -0
  108. package/dist/server-core/src/collections/BackendCollectionRegistry.d.ts +13 -0
  109. package/dist/server-core/src/collections/loader.d.ts +5 -0
  110. package/dist/server-core/src/db/interfaces.d.ts +18 -0
  111. package/dist/server-core/src/email/index.d.ts +6 -0
  112. package/dist/server-core/src/email/smtp-email-service.d.ts +25 -0
  113. package/dist/server-core/src/email/templates.d.ts +33 -0
  114. package/dist/server-core/src/email/types.d.ts +110 -0
  115. package/dist/server-core/src/functions/function-loader.d.ts +17 -0
  116. package/dist/server-core/src/functions/function-routes.d.ts +10 -0
  117. package/dist/server-core/src/functions/index.d.ts +3 -0
  118. package/dist/server-core/src/history/history-routes.d.ts +23 -0
  119. package/dist/server-core/src/history/index.d.ts +1 -0
  120. package/dist/server-core/src/index.d.ts +24 -0
  121. package/dist/server-core/src/init.d.ts +49 -0
  122. package/dist/server-core/src/serve-spa.d.ts +30 -0
  123. package/dist/server-core/src/services/driver-registry.d.ts +78 -0
  124. package/dist/server-core/src/storage/LocalStorageController.d.ts +46 -0
  125. package/dist/server-core/src/storage/S3StorageController.d.ts +36 -0
  126. package/dist/server-core/src/storage/index.d.ts +18 -0
  127. package/dist/server-core/src/storage/routes.d.ts +38 -0
  128. package/dist/server-core/src/storage/storage-registry.d.ts +78 -0
  129. package/dist/server-core/src/storage/types.d.ts +91 -0
  130. package/dist/server-core/src/types/index.d.ts +11 -0
  131. package/dist/server-core/src/utils/logging.d.ts +9 -0
  132. package/dist/server-core/src/utils/sql.d.ts +27 -0
  133. package/dist/types/src/controllers/analytics_controller.d.ts +7 -0
  134. package/dist/types/src/controllers/auth.d.ts +117 -0
  135. package/dist/types/src/controllers/client.d.ts +58 -0
  136. package/dist/types/src/controllers/collection_registry.d.ts +44 -0
  137. package/dist/types/src/controllers/customization_controller.d.ts +54 -0
  138. package/dist/types/src/controllers/data.d.ts +141 -0
  139. package/dist/types/src/controllers/data_driver.d.ts +168 -0
  140. package/dist/types/src/controllers/database_admin.d.ts +11 -0
  141. package/dist/types/src/controllers/dialogs_controller.d.ts +36 -0
  142. package/dist/types/src/controllers/effective_role.d.ts +4 -0
  143. package/dist/types/src/controllers/index.d.ts +17 -0
  144. package/dist/types/src/controllers/local_config_persistence.d.ts +20 -0
  145. package/dist/types/src/controllers/navigation.d.ts +213 -0
  146. package/dist/types/src/controllers/registry.d.ts +51 -0
  147. package/dist/types/src/controllers/side_dialogs_controller.d.ts +67 -0
  148. package/dist/types/src/controllers/side_entity_controller.d.ts +89 -0
  149. package/dist/types/src/controllers/snackbar.d.ts +24 -0
  150. package/dist/types/src/controllers/storage.d.ts +173 -0
  151. package/dist/types/src/index.d.ts +4 -0
  152. package/dist/types/src/rebase_context.d.ts +101 -0
  153. package/dist/types/src/types/backend.d.ts +533 -0
  154. package/dist/types/src/types/builders.d.ts +14 -0
  155. package/dist/types/src/types/chips.d.ts +5 -0
  156. package/dist/types/src/types/collections.d.ts +812 -0
  157. package/dist/types/src/types/data_source.d.ts +64 -0
  158. package/dist/types/src/types/entities.d.ts +145 -0
  159. package/dist/types/src/types/entity_actions.d.ts +98 -0
  160. package/dist/types/src/types/entity_callbacks.d.ts +173 -0
  161. package/dist/types/src/types/entity_link_builder.d.ts +7 -0
  162. package/dist/types/src/types/entity_overrides.d.ts +9 -0
  163. package/dist/types/src/types/entity_views.d.ts +61 -0
  164. package/dist/types/src/types/export_import.d.ts +21 -0
  165. package/dist/types/src/types/index.d.ts +22 -0
  166. package/dist/types/src/types/locales.d.ts +4 -0
  167. package/dist/types/src/types/modify_collections.d.ts +5 -0
  168. package/dist/types/src/types/plugins.d.ts +225 -0
  169. package/dist/types/src/types/properties.d.ts +1091 -0
  170. package/dist/types/src/types/property_config.d.ts +70 -0
  171. package/dist/types/src/types/relations.d.ts +336 -0
  172. package/dist/types/src/types/slots.d.ts +228 -0
  173. package/dist/types/src/types/translations.d.ts +826 -0
  174. package/dist/types/src/types/user_management_delegate.d.ts +120 -0
  175. package/dist/types/src/types/websockets.d.ts +78 -0
  176. package/dist/types/src/users/index.d.ts +2 -0
  177. package/dist/types/src/users/roles.d.ts +22 -0
  178. package/dist/types/src/users/user.d.ts +46 -0
  179. package/history_diff.log +385 -0
  180. package/jest.config.cjs +16 -0
  181. package/package.json +86 -0
  182. package/scratch.ts +8 -0
  183. package/src/api/ast-schema-editor.ts +289 -0
  184. package/src/api/collections_for_test/callbacks_test_collection.ts +57 -0
  185. package/src/api/errors.ts +155 -0
  186. package/src/api/graphql/graphql-schema-generator.ts +334 -0
  187. package/src/api/graphql/index.ts +2 -0
  188. package/src/api/index.ts +11 -0
  189. package/src/api/openapi-generator.ts +160 -0
  190. package/src/api/rest/api-generator.ts +466 -0
  191. package/src/api/rest/index.ts +2 -0
  192. package/src/api/rest/query-parser.ts +155 -0
  193. package/src/api/schema-editor-routes.ts +39 -0
  194. package/src/api/server.ts +245 -0
  195. package/src/api/types.ts +90 -0
  196. package/src/auth/admin-routes.ts +488 -0
  197. package/src/auth/google-oauth.ts +60 -0
  198. package/src/auth/index.ts +21 -0
  199. package/src/auth/interfaces.ts +316 -0
  200. package/src/auth/jwt.ts +164 -0
  201. package/src/auth/middleware.ts +235 -0
  202. package/src/auth/password.ts +75 -0
  203. package/src/auth/rate-limiter.ts +129 -0
  204. package/src/auth/routes.ts +730 -0
  205. package/src/bootstrappers/index.ts +1 -0
  206. package/src/collections/BackendCollectionRegistry.ts +20 -0
  207. package/src/collections/loader.ts +49 -0
  208. package/src/db/interfaces.ts +60 -0
  209. package/src/email/index.ts +17 -0
  210. package/src/email/smtp-email-service.ts +88 -0
  211. package/src/email/templates.ts +301 -0
  212. package/src/email/types.ts +112 -0
  213. package/src/functions/function-loader.ts +91 -0
  214. package/src/functions/function-routes.ts +31 -0
  215. package/src/functions/index.ts +3 -0
  216. package/src/history/history-routes.ts +128 -0
  217. package/src/history/index.ts +2 -0
  218. package/src/index.ts +56 -0
  219. package/src/init.ts +309 -0
  220. package/src/serve-spa.ts +81 -0
  221. package/src/services/driver-registry.ts +182 -0
  222. package/src/storage/LocalStorageController.ts +368 -0
  223. package/src/storage/S3StorageController.ts +295 -0
  224. package/src/storage/index.ts +32 -0
  225. package/src/storage/routes.ts +247 -0
  226. package/src/storage/storage-registry.ts +187 -0
  227. package/src/storage/types.ts +122 -0
  228. package/src/types/index.ts +27 -0
  229. package/src/utils/logging.ts +35 -0
  230. package/src/utils/sql.ts +38 -0
  231. package/test/admin-routes.test.ts +591 -0
  232. package/test/api-generator.test.ts +458 -0
  233. package/test/ast-schema-editor.test.ts +61 -0
  234. package/test/auth-middleware-hono.test.ts +321 -0
  235. package/test/auth-routes.test.ts +868 -0
  236. package/test/driver-registry.test.ts +280 -0
  237. package/test/errors-hono.test.ts +133 -0
  238. package/test/errors.test.ts +150 -0
  239. package/test/jwt-security.test.ts +173 -0
  240. package/test/jwt.test.ts +311 -0
  241. package/test/middleware.test.ts +295 -0
  242. package/test/password.test.ts +165 -0
  243. package/test/query-parser.test.ts +258 -0
  244. package/test/rate-limiter.test.ts +102 -0
  245. package/test/storage-local.test.ts +278 -0
  246. package/test/storage-registry.test.ts +280 -0
  247. package/test/storage-routes.test.ts +218 -0
  248. package/test/storage-s3.test.ts +301 -0
  249. package/test-ast.ts +28 -0
  250. package/test_output.txt +1133 -0
  251. package/tsconfig.json +49 -0
  252. package/tsconfig.prod.json +20 -0
  253. package/vite.config.ts +78 -0
  254. package/vite.config.ts.timestamp-1775065397568-8a853255edf6e.mjs +46 -0
@@ -0,0 +1,91 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { pathToFileURL } from "url";
4
+ import { Hono } from "hono";
5
+
6
+ export interface LoadedFunction {
7
+ /** Endpoint name derived from filename (e.g., "send-invoice") */
8
+ name: string;
9
+ /** The Hono sub-app to mount */
10
+ app: Hono<import("hono").Env>;
11
+ }
12
+
13
+ /**
14
+ * Auto-discover Hono route files from a directory.
15
+ *
16
+ * Each file should default-export a Hono app (or router).
17
+ * The filename (without extension) becomes the mount path:
18
+ * `functions/send-invoice.ts` → mounted at `/send-invoice`
19
+ *
20
+ * This mirrors how `loadCollectionsFromDirectory` works for collections.
21
+ */
22
+ export async function loadFunctionsFromDirectory(
23
+ directory: string
24
+ ): Promise<LoadedFunction[]> {
25
+ const functions: LoadedFunction[] = [];
26
+
27
+ if (!fs.existsSync(directory)) {
28
+ return functions;
29
+ }
30
+
31
+ const files = fs.readdirSync(directory);
32
+ for (const file of files) {
33
+ if (
34
+ (file.endsWith(".ts") || file.endsWith(".js")) &&
35
+ !file.includes(".test.") &&
36
+ !file.endsWith(".d.ts") &&
37
+ file !== "index.ts" &&
38
+ file !== "index.js"
39
+ ) {
40
+ const filePath = path.join(directory, file);
41
+ try {
42
+ const fileUrl = pathToFileURL(filePath).href;
43
+
44
+ // Use new Function to compile dynamic import natively and bypass
45
+ // tsc converting import() to require() — same pattern as collection loader
46
+ const dynamicImport = new Function("url", "return import(url)");
47
+ const mod = await dynamicImport(fileUrl);
48
+
49
+ const exported = mod.default;
50
+
51
+ if (!exported) {
52
+ console.warn(
53
+ `[functions] ${file}: no default export. Skipping.`
54
+ );
55
+ continue;
56
+ }
57
+
58
+ // Accept a Hono instance directly
59
+ if (exported instanceof Hono) {
60
+ const name = path.basename(file, path.extname(file));
61
+ functions.push({ name, app: exported });
62
+ console.log(`⚡ Loaded function route: ${name}`);
63
+ continue;
64
+ }
65
+
66
+ // Also accept a factory function that returns a Hono instance
67
+ if (typeof exported === "function") {
68
+ const result = exported();
69
+ if (result instanceof Hono) {
70
+ const name = path.basename(file, path.extname(file));
71
+ functions.push({ name, app: result });
72
+ console.log(`⚡ Loaded function route: ${name}`);
73
+ continue;
74
+ }
75
+ }
76
+
77
+ console.warn(
78
+ `[functions] ${file}: default export is not a Hono app or factory. Skipping.`
79
+ );
80
+ } catch (err: unknown) {
81
+ const message =
82
+ err instanceof Error ? err.message : String(err);
83
+ console.error(
84
+ `[functions] Failed to load ${file}: ${message}`
85
+ );
86
+ }
87
+ }
88
+ }
89
+
90
+ return functions;
91
+ }
@@ -0,0 +1,31 @@
1
+ import { Hono } from "hono";
2
+ import { HonoEnv } from "../api/types";
3
+ import { LoadedFunction } from "./function-loader";
4
+
5
+ /**
6
+ * Mount all loaded function routes under a single Hono router.
7
+ *
8
+ * Each function is mounted at `/<function-name>`, preserving
9
+ * whatever HTTP methods and middleware the Hono sub-app defines.
10
+ */
11
+ export function createFunctionRoutes(
12
+ functions: LoadedFunction[]
13
+ ): Hono<HonoEnv> {
14
+ const router = new Hono<HonoEnv>();
15
+
16
+ // Listing endpoint: GET / → list available functions
17
+ router.get("/", (c) => {
18
+ return c.json({
19
+ functions: functions.map((fn) => ({
20
+ name: fn.name,
21
+ endpoint: `/functions/${fn.name}`,
22
+ })),
23
+ });
24
+ });
25
+
26
+ for (const fn of functions) {
27
+ router.route(`/${fn.name}`, fn.app);
28
+ }
29
+
30
+ return router;
31
+ }
@@ -0,0 +1,3 @@
1
+ export { loadFunctionsFromDirectory } from "./function-loader";
2
+ export type { LoadedFunction } from "./function-loader";
3
+ export { createFunctionRoutes } from "./function-routes";
@@ -0,0 +1,128 @@
1
+ import { Hono } from "hono";
2
+ import { HonoEnv } from "../api/types";
3
+ import { BackendCollectionRegistry } from "../collections/BackendCollectionRegistry";
4
+ import { ApiError } from "../api/errors";
5
+ import { DataDriver } from "@rebasepro/types";
6
+ /**
7
+ * Create Hono routes for entity history.
8
+ * Mounted at `{basePath}/data/:slug/:entityId/history`.
9
+ */
10
+ export interface HistoryService {
11
+ fetchHistory(tableName: string, entityId: string, options: { limit: number, offset: number }): Promise<{ data: any[], total: number }>;
12
+ fetchHistoryEntry(historyId: string): Promise<any>;
13
+ }
14
+
15
+ export function createHistoryRoutes(params: {
16
+ historyService: HistoryService;
17
+ registry: BackendCollectionRegistry;
18
+ driver: DataDriver;
19
+ }): Hono<HonoEnv> {
20
+ const { historyService, registry, driver } = params;
21
+ const router = new Hono<HonoEnv>();
22
+
23
+ /**
24
+ * GET /:slug/:entityId/history - List history entries for an entity
25
+ *
26
+ * Query params:
27
+ * limit (default 20)
28
+ * offset (default 0)
29
+ */
30
+ router.get("/:slug/:entityId/history", async (c) => {
31
+ const slug = c.req.param("slug");
32
+ const entityId = c.req.param("entityId");
33
+ const parsedLimit = parseInt(c.req.query("limit") ?? "20", 10);
34
+ const parsedOffset = parseInt(c.req.query("offset") ?? "0", 10);
35
+ const limit = Number.isNaN(parsedLimit) ? 20 : parsedLimit;
36
+ const offset = Number.isNaN(parsedOffset) ? 0 : parsedOffset;
37
+
38
+ // Resolve the collection to get the actual table name
39
+ const collection = registry.getCollections().find(
40
+ col => col.slug === slug || false
41
+ );
42
+
43
+ if (!collection) {
44
+ throw ApiError.notFound(`Collection '${slug}' not found`);
45
+ }
46
+
47
+ if (!collection.history) {
48
+ throw ApiError.badRequest(`History is not enabled for collection '${slug}'`);
49
+ }
50
+
51
+ const tableName = collection.slug;
52
+
53
+ const result = await historyService.fetchHistory(tableName, entityId, {
54
+ limit: Math.min(limit, 100),
55
+ offset: Math.max(offset, 0)
56
+ });
57
+
58
+ return c.json({
59
+ data: result.data,
60
+ meta: {
61
+ total: result.total,
62
+ limit,
63
+ offset,
64
+ hasMore: offset + result.data.length < result.total
65
+ }
66
+ });
67
+ });
68
+
69
+ /**
70
+ * POST /:slug/:entityId/history/:historyId/revert - Revert entity to a historical version
71
+ *
72
+ * This goes through the normal save path, so it creates its own history entry.
73
+ */
74
+ router.post("/:slug/:entityId/history/:historyId/revert", async (c) => {
75
+ const slug = c.req.param("slug");
76
+ const entityId = c.req.param("entityId");
77
+ const historyId = c.req.param("historyId");
78
+
79
+ const collection = registry.getCollections().find(
80
+ col => col.slug === slug || false
81
+ );
82
+
83
+ if (!collection) {
84
+ throw ApiError.notFound(`Collection '${slug}' not found`);
85
+ }
86
+
87
+ if (!collection.history) {
88
+ throw ApiError.badRequest(`History is not enabled for collection '${slug}'`);
89
+ }
90
+
91
+ // Fetch the history entry
92
+ const historyEntry = await historyService.fetchHistoryEntry(historyId);
93
+
94
+ if (!historyEntry) {
95
+ throw ApiError.notFound(`History entry '${historyId}' not found`);
96
+ }
97
+
98
+ // Verify the history entry belongs to this entity (prevent cross-entity revert)
99
+ const tableName = collection.slug;
100
+ if (historyEntry.entity_id !== String(entityId) || historyEntry.table_name !== tableName) {
101
+ throw ApiError.badRequest("History entry does not belong to this entity");
102
+ }
103
+
104
+ if (!historyEntry.values) {
105
+ throw ApiError.badRequest("Cannot revert: history entry has no stored values");
106
+ }
107
+
108
+ // Revert by saving through the normal driver path — this will
109
+ // itself create another history entry, giving a full audit trail.
110
+ const authDriver = c.get("driver") || driver;
111
+ const path = collection.slug;
112
+
113
+ const savedEntity = await authDriver.saveEntity({
114
+ path,
115
+ entityId: String(entityId),
116
+ values: historyEntry.values,
117
+ collection,
118
+ status: "existing"
119
+ });
120
+
121
+ return c.json({
122
+ data: savedEntity,
123
+ meta: { reverted_from: historyId }
124
+ });
125
+ });
126
+
127
+ return router;
128
+ }
@@ -0,0 +1,2 @@
1
+
2
+ export { createHistoryRoutes } from "./history-routes";
package/src/index.ts ADDED
@@ -0,0 +1,56 @@
1
+ /**
2
+ * @rebasepro/server-core
3
+ *
4
+ * Database-Agnostic Backend Core for Rebase.
5
+ * This package provides the core backend services, generic driver routing,
6
+ * and API layers. Database implementations (e.g., PostgreSQL) are provided
7
+ * by specialized driver packages like `@rebasepro/server-postgresql`.
8
+ */
9
+
10
+ // =============================================================================
11
+ // Abstract Interfaces (for database abstraction)
12
+ // =============================================================================
13
+ export * from "./db/interfaces";
14
+ export * from "./auth/interfaces";
15
+
16
+ // Core functionality
17
+ export * from "./init";
18
+
19
+ // Services
20
+ export * from "./services/driver-registry";
21
+
22
+ // API types (HonoEnv, ApiConfig, etc.)
23
+ export * from "./api/types";
24
+
25
+ // API Generation
26
+ export * from "./api";
27
+
28
+ // Types
29
+ export * from "./types";
30
+ export * from "./types/index";
31
+
32
+ // Auth module
33
+ export * from "./auth";
34
+
35
+ // Email module
36
+ export * from "./email";
37
+
38
+ // Storage module
39
+ export * from "./storage";
40
+
41
+ export * from "./utils/logging";
42
+ export * from "./utils/sql";
43
+
44
+ // Entity history
45
+ export * from "./history";
46
+
47
+ // Custom Functions (auto-discovered Hono routes)
48
+ export * from "./functions";
49
+
50
+
51
+
52
+ // SPA serving helper
53
+ export * from "./serve-spa";
54
+
55
+ // Backend bootstrappers (pluggable driver initialization)
56
+
package/src/init.ts ADDED
@@ -0,0 +1,309 @@
1
+ import { DataDriver, EntityCollection, BackendBootstrapper, BootstrappedAuth, RealtimeProvider } from "@rebasepro/types";
2
+ import { BackendCollectionRegistry } from "./collections/BackendCollectionRegistry";
3
+ import { loadCollectionsFromDirectory } from "./collections/loader";
4
+ import { DriverRegistry, DEFAULT_DRIVER_ID, DefaultDriverRegistry } from "./services/driver-registry";
5
+ import { Server } from "http";
6
+
7
+ import { RestApiGenerator } from "./api/rest/api-generator";
8
+ import { createAuthMiddleware } from "./auth/middleware";
9
+ import { errorHandler } from "./api/errors";
10
+ import { Hono } from "hono";
11
+ import { HonoEnv } from "./api/types";
12
+ import { configureLogLevel } from "./utils/logging";
13
+ import { createAdminRoutes, createAuthRoutes, requireAuth, requireAdmin, configureJwt } from "./auth";
14
+ import { createStorageController, createStorageRoutes, DEFAULT_STORAGE_ID, DefaultStorageRegistry, BackendStorageConfig, StorageController, StorageRegistry } from "./storage";
15
+ import { createHistoryRoutes } from "./history";
16
+ import { EmailConfig } from "./email";
17
+
18
+ export interface RebaseAuthConfig {
19
+ jwtSecret?: string;
20
+ accessExpiresIn?: string;
21
+ refreshExpiresIn?: string;
22
+ requireAuth?: boolean;
23
+ allowRegistration?: boolean;
24
+ email?: EmailConfig;
25
+ google?: { clientId: string };
26
+ defaultRole?: string;
27
+ [key: string]: unknown;
28
+ }
29
+
30
+ export interface RebaseBackendConfig {
31
+ collections?: EntityCollection[];
32
+ collectionsDir?: string;
33
+ server: Server;
34
+ app: Hono<HonoEnv>;
35
+ basePath?: string;
36
+ bootstrappers: BackendBootstrapper[];
37
+ logging?: {
38
+ level?: "error" | "warn" | "info" | "debug";
39
+ };
40
+ auth?: RebaseAuthConfig;
41
+ storage?: BackendStorageConfig | Record<string, BackendStorageConfig>;
42
+ history?: unknown;
43
+ enableSwagger?: boolean;
44
+ functionsDir?: string;
45
+ }
46
+
47
+ export interface RebaseBackendInstance {
48
+ driverRegistry: DriverRegistry;
49
+ driver: DataDriver;
50
+ realtimeServices: Record<string, RealtimeProvider>;
51
+ realtimeService: RealtimeProvider;
52
+ auth?: BootstrappedAuth;
53
+ history?: unknown;
54
+ storageRegistry?: StorageRegistry;
55
+ storageController?: StorageController;
56
+ collectionRegistry: BackendCollectionRegistry;
57
+ }
58
+
59
+ export async function initializeRebaseBackend(config: RebaseBackendConfig): Promise<RebaseBackendInstance> {
60
+ try {
61
+ return await _initializeRebaseBackend(config);
62
+ } catch (error: unknown) {
63
+ console.error("❌ Critical error during Rebase Backend initialization:", error);
64
+
65
+ const basePath = config.basePath || "/api";
66
+ config.app.use(`${basePath}/*`, async (c) => {
67
+ return c.json({
68
+ error: {
69
+ message: "Backend initialization failed. Please check the backend server logs.",
70
+ code: "backend-init-failed"
71
+ }
72
+ }, 503);
73
+ });
74
+
75
+ return {
76
+ __failed: true,
77
+ driverRegistry: DefaultDriverRegistry.create({}),
78
+ driver: {} as unknown as DataDriver,
79
+ realtimeServices: {},
80
+ realtimeService: {} as unknown as RealtimeProvider,
81
+ } as unknown as RebaseBackendInstance;
82
+ }
83
+ }
84
+
85
+ async function _initializeRebaseBackend(config: RebaseBackendConfig): Promise<RebaseBackendInstance> {
86
+ if (config.logging?.level) {
87
+ configureLogLevel(config.logging.level);
88
+ } else {
89
+ configureLogLevel();
90
+ }
91
+
92
+ console.log("🔥 Initializing Rebase Backend (Bootstrapper Protocol V2)");
93
+
94
+ const collectionRegistry = new BackendCollectionRegistry();
95
+ let activeCollections = config.collections || [];
96
+ if (config.collectionsDir && activeCollections.length === 0) {
97
+ activeCollections = await loadCollectionsFromDirectory(config.collectionsDir);
98
+ console.log(`📁 Auto-discovered ${activeCollections.length} collections from ${config.collectionsDir}`);
99
+ }
100
+
101
+ const realtimeServices: Record<string, RealtimeProvider> = {};
102
+ const delegates: Record<string, DataDriver> = {};
103
+ const bootstrappers = config.bootstrappers || [];
104
+
105
+ if (bootstrappers.length === 0) {
106
+ throw new Error("No bootstrappers provided. Cannot initialize database drivers.");
107
+ }
108
+
109
+ let defaultDriverId = DEFAULT_DRIVER_ID;
110
+
111
+ let defaultDriverResult: import("@rebasepro/types").InitializedDriver | undefined = undefined;
112
+
113
+ // 1. Initialize all drivers
114
+ for (const bootstrapper of bootstrappers) {
115
+ const b = bootstrapper as BackendBootstrapper & { id?: string; isDefault?: boolean };
116
+ console.log(`📦 Running bootstrapper for driver: "${b.id || bootstrapper.type}"`);
117
+ if (b.isDefault) {
118
+ defaultDriverId = b.id || bootstrapper.type;
119
+ }
120
+
121
+ const driverResult = await bootstrapper.initializeDriver({ collections: activeCollections, collectionRegistry });
122
+ delegates[b.id || bootstrapper.type] = driverResult.driver;
123
+
124
+ if ((b.id || bootstrapper.type) === defaultDriverId || !defaultDriverResult) {
125
+ defaultDriverResult = driverResult;
126
+ }
127
+
128
+ if (bootstrapper.initializeRealtime) {
129
+ const realtime = await bootstrapper.initializeRealtime({}, driverResult);
130
+ realtimeServices[b.id || bootstrapper.type] = realtime as RealtimeProvider;
131
+ }
132
+ }
133
+
134
+ const driverRegistry = DefaultDriverRegistry.create(delegates);
135
+ activeCollections.forEach(collection => collectionRegistry.register(collection));
136
+
137
+ const defaultDriver = driverRegistry.getOrDefault(defaultDriverId);
138
+ if (!defaultDriver || !defaultDriverResult) {
139
+ throw new Error("Default driver not initialized by bootstrappers");
140
+ }
141
+ const defaultBootstrapper = bootstrappers.find(b => (b as BackendBootstrapper & { id?: string }).id === defaultDriverId || b.type === defaultDriverId) || bootstrappers[0];
142
+ const defaultRealtimeService = defaultDriverResult.realtimeProvider;
143
+
144
+ // 2. Initialize Auth & History via the default driver's bootstrapper
145
+ let authConfigResult: BootstrappedAuth | undefined = undefined;
146
+ if (config.auth) {
147
+ // Secure JWT setup proactively within core package memory to eliminate dual-package hazards
148
+ const safeAuthConfig = config.auth;
149
+ if (safeAuthConfig.jwtSecret) {
150
+ configureJwt({
151
+ secret: safeAuthConfig.jwtSecret,
152
+ accessExpiresIn: safeAuthConfig.accessExpiresIn || "1h",
153
+ refreshExpiresIn: safeAuthConfig.refreshExpiresIn || "30d"
154
+ });
155
+ }
156
+
157
+ if (defaultBootstrapper.initializeAuth) {
158
+ console.log("🔐 Bootstrapping authentication via driver protocol...");
159
+ authConfigResult = await defaultBootstrapper.initializeAuth(config.auth, defaultDriverResult);
160
+ console.log("✅ Authentication initialized");
161
+ } else {
162
+ console.warn("⚠️ Auth requested but default bootstrapper does not support initializeAuth");
163
+ }
164
+ }
165
+
166
+ let historyConfigResult: Record<string, unknown> | undefined = undefined;
167
+ if (config.history) {
168
+ if (defaultBootstrapper.initializeHistory) {
169
+ console.log("📜 Bootstrapping entity history via driver protocol...");
170
+ historyConfigResult = await defaultBootstrapper.initializeHistory(config.history, defaultDriverResult);
171
+ console.log("✅ Entity history initialized");
172
+ } else {
173
+ console.warn("⚠️ History requested but default bootstrapper does not support initializeHistory");
174
+ }
175
+ }
176
+
177
+ // 3. Initialize Storage
178
+ let storageRegistry: StorageRegistry | undefined;
179
+ let storageController: StorageController | undefined;
180
+
181
+ if (config.storage) {
182
+ console.log("📁 Configuring storage...");
183
+ const controllers: Record<string, StorageController> = {};
184
+
185
+ if (typeof config.storage === "object" && "type" in config.storage) {
186
+ const controller = createStorageController(config.storage as BackendStorageConfig);
187
+ controllers[DEFAULT_STORAGE_ID] = controller;
188
+ } else {
189
+ for (const [storageId, storageConfig] of Object.entries(config.storage as Record<string, BackendStorageConfig>)) {
190
+ controllers[storageId] = createStorageController(storageConfig);
191
+ }
192
+ }
193
+
194
+ if (Object.keys(controllers).length > 0) {
195
+ storageRegistry = DefaultStorageRegistry.create(controllers);
196
+ storageController = storageRegistry.getDefault();
197
+ console.log(`✅ Initialized ${Object.keys(controllers).length} storage backend(s)`);
198
+ }
199
+ }
200
+
201
+ const basePath = config.basePath || "/api";
202
+
203
+ // 4. Mount API Routes
204
+ if (config.auth && authConfigResult) {
205
+ const authRoutes = createAuthRoutes({
206
+ authRepo: authConfigResult.authRepository as import("./auth/interfaces").AuthRepository ?? authConfigResult.userService as import("./auth/interfaces").AuthRepository,
207
+ emailService: authConfigResult.emailService as import("./email").EmailService,
208
+ emailConfig: config.auth.email,
209
+ allowRegistration: config.auth.allowRegistration ?? false,
210
+ defaultRole: config.auth.defaultRole
211
+ });
212
+ config.app.route(`${basePath}/auth`, authRoutes);
213
+
214
+ const adminRoutes = createAdminRoutes({
215
+ authRepo: authConfigResult.authRepository as import("./auth/interfaces").AuthRepository ?? authConfigResult.userService as import("./auth/interfaces").AuthRepository,
216
+ emailService: authConfigResult.emailService as import("./email").EmailService,
217
+ emailConfig: config.auth.email,
218
+ });
219
+ config.app.route(`${basePath}/admin`, adminRoutes);
220
+ }
221
+
222
+ if (config.collectionsDir) {
223
+ if (process.env.NODE_ENV !== "production") {
224
+ const { createSchemaEditorRoutes } = await import("./api/schema-editor-routes");
225
+ const schemaEditorRoutes = createSchemaEditorRoutes(config.collectionsDir);
226
+
227
+ if (config.auth?.requireAuth !== false && !!config.auth?.jwtSecret) {
228
+ schemaEditorRoutes.use("/*", requireAuth, requireAdmin);
229
+ }
230
+
231
+ config.app.route(`${basePath}/schema-editor`, schemaEditorRoutes);
232
+ console.log(`✅ Schema Editor mounted at ${basePath}/schema-editor`);
233
+ }
234
+ }
235
+
236
+ if (storageController) {
237
+ const storageRoutes = createStorageRoutes({
238
+ controller: storageController,
239
+ requireAuth: config.auth?.requireAuth ?? true
240
+ });
241
+ config.app.route(`${basePath}/storage`, storageRoutes);
242
+ }
243
+
244
+ if (activeCollections.length > 0) {
245
+ const dataRouter = new Hono<HonoEnv>();
246
+
247
+ dataRouter.use("/*", createAuthMiddleware({
248
+ driver: defaultDriver,
249
+ requireAuth: false // true BaaS — delegate completely to Postgres RLS
250
+ }));
251
+
252
+ // Mount history routes BEFORE the REST API subcollection catch-all so
253
+ // that /:slug/:entityId/history is matched by the dedicated handler first.
254
+ if (historyConfigResult && historyConfigResult.historyService) {
255
+ const historyRoutes = createHistoryRoutes({
256
+ historyService: historyConfigResult.historyService as import("./history/history-routes").HistoryService,
257
+ registry: collectionRegistry,
258
+ driver: defaultDriver
259
+ });
260
+ dataRouter.route("/", historyRoutes);
261
+ }
262
+
263
+ const restGenerator = new RestApiGenerator(activeCollections, defaultDriver);
264
+ dataRouter.route("/", restGenerator.generateRoutes());
265
+
266
+ config.app.route(`${basePath}/data`, dataRouter);
267
+ }
268
+
269
+ // 5. Mount Custom Functions
270
+ if (config.functionsDir) {
271
+ const { loadFunctionsFromDirectory } = await import("./functions/function-loader");
272
+ const { createFunctionRoutes } = await import("./functions/function-routes");
273
+
274
+ const loadedFunctions = await loadFunctionsFromDirectory(config.functionsDir);
275
+
276
+ if (loadedFunctions.length > 0) {
277
+ const functionsRouter = new Hono<HonoEnv>();
278
+
279
+ // Apply auth middleware — delegates to RLS, per-route auth is up to the function
280
+ functionsRouter.use("/*", createAuthMiddleware({
281
+ driver: defaultDriver,
282
+ requireAuth: false
283
+ }));
284
+
285
+ const fnRoutes = createFunctionRoutes(loadedFunctions);
286
+ functionsRouter.route("/", fnRoutes);
287
+ config.app.route(`${basePath}/functions`, functionsRouter);
288
+ console.log(`⚡ Mounted ${loadedFunctions.length} custom function(s) at ${basePath}/functions`);
289
+ }
290
+ }
291
+
292
+ if ((defaultBootstrapper as BackendBootstrapper & { initializeWebsockets?: (...args: unknown[]) => unknown }).initializeWebsockets) {
293
+ await (defaultBootstrapper as BackendBootstrapper & { initializeWebsockets: (...args: unknown[]) => unknown }).initializeWebsockets(config.server, defaultRealtimeService, defaultDriver, config.auth);
294
+ }
295
+
296
+ console.log("✅ Rebase Backend Initialized");
297
+
298
+ return {
299
+ driverRegistry,
300
+ driver: defaultDriver,
301
+ realtimeServices,
302
+ realtimeService: defaultRealtimeService as RealtimeProvider,
303
+ auth: authConfigResult,
304
+ history: historyConfigResult,
305
+ storageRegistry,
306
+ storageController,
307
+ collectionRegistry
308
+ };
309
+ }