@rebasepro/server-core 0.0.1-canary.09e5ec5

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 (300) 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 +56 -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 +58 -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 +22 -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-DXVBFp5V.js +37 -0
  78. package/dist/index-DXVBFp5V.js.map +1 -0
  79. package/dist/index.es.js +49934 -0
  80. package/dist/index.es.js.map +1 -0
  81. package/dist/index.umd.js +49968 -0
  82. package/dist/index.umd.js.map +1 -0
  83. package/dist/server-core/src/api/ast-schema-editor.d.ts +21 -0
  84. package/dist/server-core/src/api/collections_for_test/callbacks_test_collection.d.ts +2 -0
  85. package/dist/server-core/src/api/errors.d.ts +35 -0
  86. package/dist/server-core/src/api/graphql/graphql-schema-generator.d.ts +35 -0
  87. package/dist/server-core/src/api/graphql/index.d.ts +1 -0
  88. package/dist/server-core/src/api/index.d.ts +9 -0
  89. package/dist/server-core/src/api/openapi-generator.d.ts +16 -0
  90. package/dist/server-core/src/api/rest/api-generator.d.ts +64 -0
  91. package/dist/server-core/src/api/rest/index.d.ts +1 -0
  92. package/dist/server-core/src/api/rest/query-parser.d.ts +9 -0
  93. package/dist/server-core/src/api/schema-editor-routes.d.ts +3 -0
  94. package/dist/server-core/src/api/server.d.ts +40 -0
  95. package/dist/server-core/src/api/types.d.ts +90 -0
  96. package/dist/server-core/src/auth/admin-routes.d.ts +16 -0
  97. package/dist/server-core/src/auth/apple-oauth.d.ts +30 -0
  98. package/dist/server-core/src/auth/bitbucket-oauth.d.ts +11 -0
  99. package/dist/server-core/src/auth/discord-oauth.d.ts +14 -0
  100. package/dist/server-core/src/auth/facebook-oauth.d.ts +14 -0
  101. package/dist/server-core/src/auth/github-oauth.d.ts +15 -0
  102. package/dist/server-core/src/auth/gitlab-oauth.d.ts +13 -0
  103. package/dist/server-core/src/auth/google-oauth.d.ts +14 -0
  104. package/dist/server-core/src/auth/index.d.ts +23 -0
  105. package/dist/server-core/src/auth/interfaces.d.ts +309 -0
  106. package/dist/server-core/src/auth/jwt.d.ts +43 -0
  107. package/dist/server-core/src/auth/linkedin-oauth.d.ts +18 -0
  108. package/dist/server-core/src/auth/microsoft-oauth.d.ts +16 -0
  109. package/dist/server-core/src/auth/middleware.d.ts +81 -0
  110. package/dist/server-core/src/auth/password.d.ts +22 -0
  111. package/dist/server-core/src/auth/rate-limiter.d.ts +31 -0
  112. package/dist/server-core/src/auth/routes.d.ts +27 -0
  113. package/dist/server-core/src/auth/slack-oauth.d.ts +12 -0
  114. package/dist/server-core/src/auth/spotify-oauth.d.ts +12 -0
  115. package/dist/server-core/src/auth/twitter-oauth.d.ts +18 -0
  116. package/dist/server-core/src/bootstrappers/index.d.ts +0 -0
  117. package/dist/server-core/src/collections/BackendCollectionRegistry.d.ts +13 -0
  118. package/dist/server-core/src/collections/loader.d.ts +5 -0
  119. package/dist/server-core/src/cron/cron-loader.d.ts +17 -0
  120. package/dist/server-core/src/cron/cron-routes.d.ts +14 -0
  121. package/dist/server-core/src/cron/cron-scheduler.d.ts +61 -0
  122. package/dist/server-core/src/cron/cron-store.d.ts +32 -0
  123. package/dist/server-core/src/cron/index.d.ts +6 -0
  124. package/dist/server-core/src/db/interfaces.d.ts +18 -0
  125. package/dist/server-core/src/email/index.d.ts +6 -0
  126. package/dist/server-core/src/email/smtp-email-service.d.ts +25 -0
  127. package/dist/server-core/src/email/templates.d.ts +42 -0
  128. package/dist/server-core/src/email/types.d.ts +107 -0
  129. package/dist/server-core/src/functions/function-loader.d.ts +17 -0
  130. package/dist/server-core/src/functions/function-routes.d.ts +10 -0
  131. package/dist/server-core/src/functions/index.d.ts +3 -0
  132. package/dist/server-core/src/history/history-routes.d.ts +23 -0
  133. package/dist/server-core/src/history/index.d.ts +1 -0
  134. package/dist/server-core/src/index.d.ts +29 -0
  135. package/dist/server-core/src/init.d.ts +159 -0
  136. package/dist/server-core/src/serve-spa.d.ts +30 -0
  137. package/dist/server-core/src/services/driver-registry.d.ts +78 -0
  138. package/dist/server-core/src/singleton.d.ts +35 -0
  139. package/dist/server-core/src/storage/LocalStorageController.d.ts +46 -0
  140. package/dist/server-core/src/storage/S3StorageController.d.ts +36 -0
  141. package/dist/server-core/src/storage/index.d.ts +25 -0
  142. package/dist/server-core/src/storage/routes.d.ts +38 -0
  143. package/dist/server-core/src/storage/storage-registry.d.ts +78 -0
  144. package/dist/server-core/src/storage/types.d.ts +103 -0
  145. package/dist/server-core/src/types/index.d.ts +11 -0
  146. package/dist/server-core/src/utils/dev-port.d.ts +35 -0
  147. package/dist/server-core/src/utils/logger.d.ts +31 -0
  148. package/dist/server-core/src/utils/logging.d.ts +9 -0
  149. package/dist/server-core/src/utils/request-logger.d.ts +19 -0
  150. package/dist/server-core/src/utils/sql.d.ts +27 -0
  151. package/dist/types/src/controllers/analytics_controller.d.ts +7 -0
  152. package/dist/types/src/controllers/auth.d.ts +119 -0
  153. package/dist/types/src/controllers/client.d.ts +170 -0
  154. package/dist/types/src/controllers/collection_registry.d.ts +45 -0
  155. package/dist/types/src/controllers/customization_controller.d.ts +60 -0
  156. package/dist/types/src/controllers/data.d.ts +168 -0
  157. package/dist/types/src/controllers/data_driver.d.ts +160 -0
  158. package/dist/types/src/controllers/database_admin.d.ts +11 -0
  159. package/dist/types/src/controllers/dialogs_controller.d.ts +36 -0
  160. package/dist/types/src/controllers/effective_role.d.ts +4 -0
  161. package/dist/types/src/controllers/email.d.ts +34 -0
  162. package/dist/types/src/controllers/index.d.ts +18 -0
  163. package/dist/types/src/controllers/local_config_persistence.d.ts +20 -0
  164. package/dist/types/src/controllers/navigation.d.ts +213 -0
  165. package/dist/types/src/controllers/registry.d.ts +54 -0
  166. package/dist/types/src/controllers/side_dialogs_controller.d.ts +67 -0
  167. package/dist/types/src/controllers/side_entity_controller.d.ts +90 -0
  168. package/dist/types/src/controllers/snackbar.d.ts +24 -0
  169. package/dist/types/src/controllers/storage.d.ts +171 -0
  170. package/dist/types/src/index.d.ts +4 -0
  171. package/dist/types/src/rebase_context.d.ts +105 -0
  172. package/dist/types/src/types/backend.d.ts +536 -0
  173. package/dist/types/src/types/builders.d.ts +15 -0
  174. package/dist/types/src/types/chips.d.ts +5 -0
  175. package/dist/types/src/types/collections.d.ts +856 -0
  176. package/dist/types/src/types/cron.d.ts +102 -0
  177. package/dist/types/src/types/data_source.d.ts +64 -0
  178. package/dist/types/src/types/entities.d.ts +145 -0
  179. package/dist/types/src/types/entity_actions.d.ts +98 -0
  180. package/dist/types/src/types/entity_callbacks.d.ts +173 -0
  181. package/dist/types/src/types/entity_link_builder.d.ts +7 -0
  182. package/dist/types/src/types/entity_overrides.d.ts +10 -0
  183. package/dist/types/src/types/entity_views.d.ts +61 -0
  184. package/dist/types/src/types/export_import.d.ts +21 -0
  185. package/dist/types/src/types/index.d.ts +23 -0
  186. package/dist/types/src/types/locales.d.ts +4 -0
  187. package/dist/types/src/types/modify_collections.d.ts +5 -0
  188. package/dist/types/src/types/plugins.d.ts +279 -0
  189. package/dist/types/src/types/properties.d.ts +1176 -0
  190. package/dist/types/src/types/property_config.d.ts +70 -0
  191. package/dist/types/src/types/relations.d.ts +336 -0
  192. package/dist/types/src/types/slots.d.ts +252 -0
  193. package/dist/types/src/types/translations.d.ts +870 -0
  194. package/dist/types/src/types/user_management_delegate.d.ts +121 -0
  195. package/dist/types/src/types/websockets.d.ts +78 -0
  196. package/dist/types/src/users/index.d.ts +2 -0
  197. package/dist/types/src/users/roles.d.ts +22 -0
  198. package/dist/types/src/users/user.d.ts +46 -0
  199. package/history_diff.log +385 -0
  200. package/jest.config.cjs +16 -0
  201. package/package.json +86 -0
  202. package/scratch.ts +9 -0
  203. package/src/api/ast-schema-editor.ts +289 -0
  204. package/src/api/collections_for_test/callbacks_test_collection.ts +60 -0
  205. package/src/api/errors.ts +179 -0
  206. package/src/api/graphql/graphql-schema-generator.ts +336 -0
  207. package/src/api/graphql/index.ts +2 -0
  208. package/src/api/index.ts +11 -0
  209. package/src/api/openapi-generator.ts +715 -0
  210. package/src/api/rest/api-generator.ts +472 -0
  211. package/src/api/rest/index.ts +2 -0
  212. package/src/api/rest/query-parser.ts +155 -0
  213. package/src/api/schema-editor-routes.ts +41 -0
  214. package/src/api/server.ts +248 -0
  215. package/src/api/types.ts +90 -0
  216. package/src/auth/admin-routes.ts +529 -0
  217. package/src/auth/apple-oauth.ts +130 -0
  218. package/src/auth/bitbucket-oauth.ts +82 -0
  219. package/src/auth/discord-oauth.ts +83 -0
  220. package/src/auth/facebook-oauth.ts +72 -0
  221. package/src/auth/github-oauth.ts +110 -0
  222. package/src/auth/gitlab-oauth.ts +70 -0
  223. package/src/auth/google-oauth.ts +48 -0
  224. package/src/auth/index.ts +34 -0
  225. package/src/auth/interfaces.ts +363 -0
  226. package/src/auth/jwt.ts +181 -0
  227. package/src/auth/linkedin-oauth.ts +81 -0
  228. package/src/auth/microsoft-oauth.ts +88 -0
  229. package/src/auth/middleware.ts +384 -0
  230. package/src/auth/password.ts +77 -0
  231. package/src/auth/rate-limiter.ts +129 -0
  232. package/src/auth/routes.ts +788 -0
  233. package/src/auth/slack-oauth.ts +71 -0
  234. package/src/auth/spotify-oauth.ts +67 -0
  235. package/src/auth/twitter-oauth.ts +120 -0
  236. package/src/bootstrappers/index.ts +1 -0
  237. package/src/collections/BackendCollectionRegistry.ts +20 -0
  238. package/src/collections/loader.ts +49 -0
  239. package/src/cron/cron-loader.ts +89 -0
  240. package/src/cron/cron-routes.test.ts +265 -0
  241. package/src/cron/cron-routes.ts +85 -0
  242. package/src/cron/cron-scheduler.test.ts +421 -0
  243. package/src/cron/cron-scheduler.ts +413 -0
  244. package/src/cron/cron-store.ts +163 -0
  245. package/src/cron/index.ts +6 -0
  246. package/src/db/interfaces.ts +60 -0
  247. package/src/email/index.ts +18 -0
  248. package/src/email/smtp-email-service.ts +91 -0
  249. package/src/email/templates.ts +388 -0
  250. package/src/email/types.ts +105 -0
  251. package/src/functions/function-loader.ts +119 -0
  252. package/src/functions/function-routes.ts +31 -0
  253. package/src/functions/index.ts +3 -0
  254. package/src/history/history-routes.ts +129 -0
  255. package/src/history/index.ts +2 -0
  256. package/src/index.ts +66 -0
  257. package/src/init.ts +727 -0
  258. package/src/serve-spa.ts +81 -0
  259. package/src/services/driver-registry.ts +182 -0
  260. package/src/singleton.test.ts +28 -0
  261. package/src/singleton.ts +70 -0
  262. package/src/storage/LocalStorageController.ts +365 -0
  263. package/src/storage/S3StorageController.ts +298 -0
  264. package/src/storage/index.ts +43 -0
  265. package/src/storage/routes.ts +264 -0
  266. package/src/storage/storage-registry.ts +187 -0
  267. package/src/storage/types.ts +134 -0
  268. package/src/types/index.ts +27 -0
  269. package/src/utils/dev-port.ts +176 -0
  270. package/src/utils/logger.ts +143 -0
  271. package/src/utils/logging.ts +38 -0
  272. package/src/utils/request-logger.ts +66 -0
  273. package/src/utils/sql.ts +38 -0
  274. package/test/admin-routes.test.ts +640 -0
  275. package/test/api-generator.test.ts +501 -0
  276. package/test/ast-schema-editor.test.ts +63 -0
  277. package/test/auth-middleware-hono.test.ts +556 -0
  278. package/test/auth-routes.test.ts +1047 -0
  279. package/test/driver-registry.test.ts +282 -0
  280. package/test/error-propagation.test.ts +226 -0
  281. package/test/errors-hono.test.ts +133 -0
  282. package/test/errors.test.ts +155 -0
  283. package/test/jwt-security.test.ts +182 -0
  284. package/test/jwt.test.ts +324 -0
  285. package/test/middleware.test.ts +300 -0
  286. package/test/password.test.ts +165 -0
  287. package/test/query-parser.test.ts +263 -0
  288. package/test/rate-limiter.test.ts +102 -0
  289. package/test/safe-compare.test.ts +66 -0
  290. package/test/singleton.test.ts +59 -0
  291. package/test/storage-local.test.ts +271 -0
  292. package/test/storage-registry.test.ts +282 -0
  293. package/test/storage-routes.test.ts +222 -0
  294. package/test/storage-s3.test.ts +304 -0
  295. package/test-ast.ts +28 -0
  296. package/test.ts +6 -0
  297. package/test_output.txt +1133 -0
  298. package/tsconfig.json +49 -0
  299. package/tsconfig.prod.json +20 -0
  300. package/vite.config.ts +80 -0
@@ -0,0 +1,264 @@
1
+ /**
2
+ * Storage REST API routes using Hono
3
+ */
4
+
5
+ import { Hono } from "hono";
6
+ import * as fs from "fs";
7
+ import { StorageController } from "./types";
8
+ import { LocalStorageController } from "./LocalStorageController";
9
+ import { requireAuth as jwtRequireAuth, optionalAuth } from "../auth/middleware";
10
+ import { ApiError, errorHandler } from "../api/errors";
11
+ import { HonoEnv } from "../api/types";
12
+
13
+ export interface StorageRoutesConfig {
14
+ controller: StorageController;
15
+ /** Base path for storage routes (default: '/api/storage') */
16
+ basePath?: string;
17
+ /** Require authentication for write operations (default: true) */
18
+ requireAuth?: boolean;
19
+ /** Allow unauthenticated read access to stored files (default: false).
20
+ * When false and requireAuth is true, reads also require authentication. */
21
+ publicRead?: boolean;
22
+ }
23
+
24
+ /**
25
+ * Extract the wildcard portion of a route path from the full request path.
26
+ *
27
+ * Hono's `c.req.param('*')` does not work reliably in sub-routers mounted
28
+ * via `app.route(prefix, subRouter)`. Instead we derive the wildcard value
29
+ * from the fully-resolved `c.req.path` and `c.req.routePath`.
30
+ *
31
+ * For a route `/metadata/*` mounted at `/api/storage`, a request to
32
+ * `/api/storage/metadata/default/file.jpg` yields routePath
33
+ * `/api/storage/metadata/*`. We strip the prefix (everything before `/*`)
34
+ * plus one character for the trailing `/` to obtain `default/file.jpg`.
35
+ */
36
+ export function extractWildcardPath(c: { req: { path: string; routePath: string } }): string {
37
+ const routePath = c.req.routePath; // e.g. "/api/storage/metadata/*"
38
+ const prefix = routePath.replace("/*", ""); // e.g. "/api/storage/metadata"
39
+ const fullPath = c.req.path; // e.g. "/api/storage/metadata/default/file.jpg"
40
+ const idx = fullPath.indexOf(prefix);
41
+ if (idx < 0) return "";
42
+ // +1 to skip the '/' after the prefix
43
+ return fullPath.substring(idx + prefix.length + 1);
44
+ }
45
+
46
+ /**
47
+ * Create storage REST API routes
48
+ */
49
+ export function createStorageRoutes(config: StorageRoutesConfig): Hono<HonoEnv> {
50
+ const router = new Hono<HonoEnv>();
51
+ router.onError(errorHandler);
52
+ const { controller, requireAuth = true, publicRead = false } = config;
53
+
54
+ // Use actual JWT auth middleware from auth module
55
+ const writeAuthMiddleware = requireAuth ? jwtRequireAuth : optionalAuth;
56
+
57
+ // For read operations: respect publicRead config.
58
+ const readAuthMiddleware = (publicRead || !requireAuth) ? optionalAuth : jwtRequireAuth;
59
+
60
+ /**
61
+ * Parse bucket and path from a combined file path.
62
+ */
63
+ const parseBucketAndPath = (filePath: string): { bucket: string; resolvedPath: string } => {
64
+ const parts = filePath.split("/");
65
+
66
+ // Only recognize 'default' as an explicit bucket prefix
67
+ if (parts.length > 1 && parts[0].toLowerCase() === "default") {
68
+ return {
69
+ bucket: "default",
70
+ resolvedPath: parts.slice(1).join("/")
71
+ };
72
+ }
73
+
74
+ // All other paths use 'default' bucket with the full path
75
+ return {
76
+ bucket: "default",
77
+ resolvedPath: filePath
78
+ };
79
+ };
80
+
81
+ /**
82
+ * POST /upload - Upload a file
83
+ * Body: multipart/form-data with 'file' field
84
+ * Request body can also contain metadata keys 'metadata_*'
85
+ */
86
+ router.post("/upload", writeAuthMiddleware, async (c) => {
87
+ const body = await c.req.parseBody();
88
+ const uploadedFile = body["file"];
89
+
90
+ if (!uploadedFile || typeof uploadedFile === "string") {
91
+ throw ApiError.badRequest("No file provided");
92
+ }
93
+
94
+ const key = typeof body["key"] === "string" ? body["key"] : "";
95
+ const bucket = typeof body["bucket"] === "string" ? body["bucket"] : undefined;
96
+
97
+ // Backward compatibility support for older clients sending path and fileName
98
+ const legacyPath = typeof body["path"] === "string" ? body["path"] : "";
99
+ const legacyFileName = typeof body["fileName"] === "string" ? body["fileName"] : undefined;
100
+
101
+ let finalKey = key;
102
+ if (!finalKey) {
103
+ if (legacyPath || legacyFileName) {
104
+ const parts = [];
105
+ if (legacyPath) parts.push(legacyPath);
106
+ if (legacyFileName) {
107
+ parts.push(legacyFileName);
108
+ } else {
109
+ parts.push(uploadedFile.name || "unnamed");
110
+ }
111
+ finalKey = parts.join("/");
112
+ } else {
113
+ finalKey = uploadedFile.name || "unnamed";
114
+ }
115
+ }
116
+
117
+ // Extract custom metadata from request body
118
+ const metadata: Record<string, unknown> = {};
119
+ for (const [k, value] of Object.entries(body)) {
120
+ if (k.startsWith("metadata_")) {
121
+ metadata[k.replace("metadata_", "")] = value;
122
+ }
123
+ }
124
+
125
+ const result = await controller.putObject({
126
+ file: uploadedFile,
127
+ key: finalKey,
128
+ metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
129
+ bucket
130
+ });
131
+
132
+ return c.json({
133
+ success: true,
134
+ data: result
135
+ }, 201);
136
+ });
137
+
138
+ /**
139
+ * GET /file/* - Download/serve a file
140
+ * Path: /file/{bucket}/{path} or /file/{path}
141
+ */
142
+ router.get("/file/*", readAuthMiddleware, async (c) => {
143
+ const rawPath = extractWildcardPath(c);
144
+ if (!rawPath) {
145
+ throw ApiError.notFound("File not found");
146
+ }
147
+
148
+ const filePath = decodeURIComponent(rawPath);
149
+
150
+ // For local storage, serve the file directly from disk
151
+ if (controller.getType() === "local") {
152
+ const localController = controller as LocalStorageController;
153
+ const { bucket, resolvedPath } = parseBucketAndPath(filePath);
154
+
155
+ const absolutePath = localController.getAbsolutePath(resolvedPath, bucket);
156
+
157
+ // Check if file exists
158
+ if (!fs.existsSync(absolutePath)) {
159
+ throw ApiError.notFound("File not found");
160
+ }
161
+
162
+ // Get content type from metadata or infer from extension
163
+ let contentType = "application/octet-stream";
164
+ const metadataPath = `${absolutePath}.metadata.json`;
165
+ if (fs.existsSync(metadataPath)) {
166
+ try {
167
+ const metadata = JSON.parse(fs.readFileSync(metadataPath, "utf-8"));
168
+ contentType = metadata.contentType || contentType;
169
+ } catch {
170
+ // Ignore metadata errors
171
+ }
172
+ }
173
+
174
+ c.header("Content-Type", contentType);
175
+ // In a better scenario, we should pipe the stream instead of reading whole file
176
+ const fileContent = fs.readFileSync(absolutePath);
177
+ return c.body(new Uint8Array(fileContent));
178
+ }
179
+
180
+ // For remote storage (S3, GCS, etc.), redirect to a signed URL
181
+ const downloadConfig = await controller.getSignedUrl(filePath);
182
+ if (downloadConfig.fileNotFound || !downloadConfig.url) {
183
+ throw ApiError.notFound("File not found");
184
+ }
185
+
186
+ return c.redirect(downloadConfig.url);
187
+ });
188
+
189
+ /**
190
+ * GET /metadata/* - Get file metadata
191
+ */
192
+ router.get("/metadata/*", readAuthMiddleware, async (c) => {
193
+ const rawPath = extractWildcardPath(c);
194
+ if (!rawPath) {
195
+ return c.json({
196
+ success: true,
197
+ data: null,
198
+ fileNotFound: true
199
+ }, 404);
200
+ }
201
+
202
+ const filePath = decodeURIComponent(rawPath);
203
+ const { bucket, resolvedPath } = parseBucketAndPath(filePath);
204
+
205
+ const downloadConfig = await controller.getSignedUrl(resolvedPath, bucket);
206
+
207
+ if (downloadConfig.fileNotFound) {
208
+ throw ApiError.notFound("File not found");
209
+ }
210
+
211
+ return c.json({
212
+ success: true,
213
+ data: downloadConfig.metadata
214
+ });
215
+ });
216
+
217
+ /**
218
+ * DELETE /file/* - Delete a file
219
+ */
220
+ router.delete("/file/*", writeAuthMiddleware, async (c) => {
221
+ const rawPath = extractWildcardPath(c);
222
+ if (!rawPath) {
223
+ return c.json({ success: true,
224
+ message: "No file to delete" });
225
+ }
226
+
227
+ const filePath = decodeURIComponent(rawPath);
228
+ const { bucket, resolvedPath } = parseBucketAndPath(filePath);
229
+
230
+ await controller.deleteObject(resolvedPath, bucket);
231
+
232
+ return c.json({
233
+ success: true,
234
+ message: "File deleted"
235
+ });
236
+ });
237
+
238
+ /**
239
+ * GET /list - List files in a path
240
+ */
241
+ router.get("/list", writeAuthMiddleware, async (c) => {
242
+ // Fallback to path for backward compatibility
243
+ const storagePrefix = c.req.query("prefix") || c.req.query("path") || "";
244
+ const bucket = c.req.query("bucket");
245
+ const maxResults = c.req.query("maxResults");
246
+ const pageToken = c.req.query("pageToken");
247
+
248
+ const result = await controller.listObjects(
249
+ storagePrefix,
250
+ {
251
+ bucket,
252
+ maxResults: maxResults ? parseInt(maxResults, 10) : undefined,
253
+ pageToken
254
+ }
255
+ );
256
+
257
+ return c.json({
258
+ success: true,
259
+ data: result
260
+ });
261
+ });
262
+
263
+ return router;
264
+ }
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Storage Registry
3
+ *
4
+ * Manages multiple storage controllers for Rebase backend.
5
+ * Allows different storage backends for different use cases.
6
+ *
7
+ * Usage:
8
+ * - Single storage: Pass a single StorageController → maps to "(default)"
9
+ * - Multiple storages: Pass a map of { storageId: StorageController }
10
+ * - String properties use `storageId` in their config to specify which storage to use
11
+ * - Properties without `storageId` fallback to "(default)"
12
+ */
13
+
14
+ import { StorageController } from "./types";
15
+
16
+ /**
17
+ * The default storage identifier used when:
18
+ * - A single storage controller is provided (not a map)
19
+ * - A property doesn't specify a storageId
20
+ */
21
+ export const DEFAULT_STORAGE_ID = "(default)";
22
+
23
+ /**
24
+ * Registry for managing multiple storage controllers
25
+ */
26
+ export interface StorageRegistry {
27
+ /**
28
+ * Register a storage controller with an ID
29
+ * @param id - Unique identifier for this storage (e.g., "media", "backups")
30
+ * @param controller - The StorageController instance
31
+ */
32
+ register(id: string, controller: StorageController): void;
33
+
34
+ /**
35
+ * Get the default storage controller (id = "(default)")
36
+ * @throws Error if no default storage is registered
37
+ */
38
+ getDefault(): StorageController;
39
+
40
+ /**
41
+ * Get a storage controller by ID
42
+ * @param id - Storage identifier, or undefined/null for default
43
+ * @returns The StorageController, or undefined if not found
44
+ */
45
+ get(id: string | undefined | null): StorageController | undefined;
46
+
47
+ /**
48
+ * Get a storage controller by ID, with fallback to default
49
+ * @param id - Storage identifier, or undefined/null for default
50
+ * @returns The StorageController (falls back to default if id not found)
51
+ * @throws Error if neither the specified nor default storage exists
52
+ */
53
+ getOrDefault(id: string | undefined | null): StorageController;
54
+
55
+ /**
56
+ * Check if a storage with the given ID exists
57
+ */
58
+ has(id: string): boolean;
59
+
60
+ /**
61
+ * List all registered storage IDs
62
+ */
63
+ list(): string[];
64
+
65
+ /**
66
+ * Get the number of registered storage controllers
67
+ */
68
+ size(): number;
69
+ }
70
+
71
+ /**
72
+ * Default implementation of StorageRegistry
73
+ */
74
+ export class DefaultStorageRegistry implements StorageRegistry {
75
+ private controllers = new Map<string, StorageController>();
76
+
77
+ /**
78
+ * Create a StorageRegistry from either a single controller or a map
79
+ * @param input - Single StorageController (maps to "(default)") or Record<string, StorageController>
80
+ */
81
+ static create(
82
+ input: StorageController | Record<string, StorageController>
83
+ ): DefaultStorageRegistry {
84
+ const registry = new DefaultStorageRegistry();
85
+
86
+ if (isStorageController(input)) {
87
+ // Single controller → register as "(default)"
88
+ registry.register(DEFAULT_STORAGE_ID, input);
89
+ } else {
90
+ // Map of controllers → register each
91
+ for (const [id, controller] of Object.entries(input)) {
92
+ if (isStorageController(controller)) {
93
+ registry.register(id, controller);
94
+ }
95
+ }
96
+ // Ensure there's a default if not explicitly provided
97
+ if (!registry.has(DEFAULT_STORAGE_ID) && registry.size() > 0) {
98
+ // If no explicit "(default)", use the first one as default
99
+ const firstId = Object.keys(input).find(k => isStorageController(input[k]));
100
+ if (firstId) {
101
+ console.warn(
102
+ `[StorageRegistry] No "${DEFAULT_STORAGE_ID}" storage provided. ` +
103
+ `Using "${firstId}" as the default.`
104
+ );
105
+ registry.register(DEFAULT_STORAGE_ID, input[firstId]);
106
+ }
107
+ }
108
+ }
109
+
110
+ return registry;
111
+ }
112
+
113
+ register(id: string, controller: StorageController): void {
114
+ if (this.controllers.has(id)) {
115
+ console.warn(`[StorageRegistry] Overwriting storage with id "${id}"`);
116
+ }
117
+ this.controllers.set(id, controller);
118
+ }
119
+
120
+ getDefault(): StorageController {
121
+ const controller = this.controllers.get(DEFAULT_STORAGE_ID);
122
+ if (!controller) {
123
+ throw new Error(
124
+ "[StorageRegistry] No default storage registered. " +
125
+ `Register one with id "${DEFAULT_STORAGE_ID}" or pass a single StorageController.`
126
+ );
127
+ }
128
+ return controller;
129
+ }
130
+
131
+ get(id: string | undefined | null): StorageController | undefined {
132
+ if (id === undefined || id === null) {
133
+ return this.controllers.get(DEFAULT_STORAGE_ID);
134
+ }
135
+ return this.controllers.get(id);
136
+ }
137
+
138
+ getOrDefault(id: string | undefined | null): StorageController {
139
+ // If no ID specified, return default
140
+ if (id === undefined || id === null) {
141
+ return this.getDefault();
142
+ }
143
+
144
+ // Try to get by ID
145
+ const controller = this.controllers.get(id);
146
+ if (controller) {
147
+ return controller;
148
+ }
149
+
150
+ // Fallback to default with warning
151
+ console.warn(
152
+ `[StorageRegistry] Storage "${id}" not found, falling back to "${DEFAULT_STORAGE_ID}"`
153
+ );
154
+ return this.getDefault();
155
+ }
156
+
157
+ has(id: string): boolean {
158
+ return this.controllers.has(id);
159
+ }
160
+
161
+ list(): string[] {
162
+ return Array.from(this.controllers.keys());
163
+ }
164
+
165
+ size(): number {
166
+ return this.controllers.size;
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Type guard to check if an object is a StorageController
172
+ * vs a Record<string, StorageController> (multiple storages)
173
+ */
174
+ function isStorageController(obj: unknown): obj is StorageController {
175
+ if (typeof obj !== "object" || obj === null) {
176
+ return false;
177
+ }
178
+ const controller = obj as StorageController;
179
+ // Check for required StorageController properties
180
+ return (
181
+ typeof controller.putObject === "function" &&
182
+ typeof controller.getSignedUrl === "function" &&
183
+ typeof controller.deleteObject === "function" &&
184
+ typeof controller.listObjects === "function" &&
185
+ typeof controller.getType === "function"
186
+ );
187
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Storage configuration and types for Rebase backend
3
+ */
4
+
5
+ import { StorageSource, UploadFileProps, UploadFileResult, DownloadConfig, StorageListResult, StorageReference } from "@rebasepro/types";
6
+
7
+ /**
8
+ * Local filesystem storage configuration
9
+ */
10
+ export interface LocalStorageConfig {
11
+ type: "local";
12
+ /** Base directory for file storage (e.g., './uploads') */
13
+ basePath: string;
14
+ /** Maximum file size in bytes (default: 50MB) */
15
+ maxFileSize?: number;
16
+ /** Allowed MIME types (if not set, all types allowed) */
17
+ allowedMimeTypes?: string[];
18
+ /** Base URL for generating download URLs (default: auto-detected from request) */
19
+ baseUrl?: string;
20
+ }
21
+
22
+ /**
23
+ * S3-compatible storage configuration (works with AWS S3 and MinIO)
24
+ */
25
+ export interface S3StorageConfig {
26
+ type: "s3";
27
+ /** S3 bucket name */
28
+ bucket: string;
29
+ /** AWS region (e.g., 'us-east-1') */
30
+ region?: string;
31
+ /** Custom endpoint URL (required for MinIO, Cloudflare R2, Hetzner Object Storage) */
32
+ endpoint?: string;
33
+ /** AWS access key ID */
34
+ accessKeyId: string;
35
+ /** AWS secret access key */
36
+ secretAccessKey: string;
37
+ /** Use path-style URLs (required for MinIO) */
38
+ forcePathStyle?: boolean;
39
+ /** Maximum file size in bytes (default: 50MB) */
40
+ maxFileSize?: number;
41
+ /** Allowed MIME types (if not set, all types allowed) */
42
+ allowedMimeTypes?: string[];
43
+ /** URL expiration time in seconds for signed URLs (default: 3600) */
44
+ signedUrlExpiration?: number;
45
+ }
46
+
47
+ /**
48
+ * Storage configuration — local filesystem or S3-compatible.
49
+ *
50
+ * **Built-in providers:**
51
+ * - `local` — Zero-config filesystem storage. Great for dev and single-server deployments (Hetzner, bare metal).
52
+ * - `s3` — Any S3-compatible provider. AWS S3, Cloudflare R2, MinIO, Hetzner Object Storage,
53
+ * Backblaze B2, DigitalOcean Spaces, and even GCS (via its S3-compatible interoperability API).
54
+ *
55
+ * **Custom providers:**
56
+ * For cloud-native storage (GCS, Azure Blob, etc.), implement the `StorageController`
57
+ * interface and pass the instance directly to the `storage` config.
58
+ */
59
+ export type BackendStorageConfig = LocalStorageConfig | S3StorageConfig;
60
+
61
+ /**
62
+ * Storage controller interface for backend implementations
63
+ */
64
+ export interface StorageController {
65
+ /**
66
+ * Upload an object
67
+ */
68
+ putObject(props: UploadFileProps): Promise<UploadFileResult>;
69
+
70
+ /**
71
+ * Get a download URL (signed URL equivalent) for an object
72
+ */
73
+ getSignedUrl(key: string, bucket?: string): Promise<DownloadConfig>;
74
+
75
+ /**
76
+ * Get object as a File
77
+ */
78
+ getObject(key: string, bucket?: string): Promise<File | null>;
79
+
80
+ /**
81
+ * Delete an object
82
+ */
83
+ deleteObject(key: string, bucket?: string): Promise<void>;
84
+
85
+ /**
86
+ * List objects in a prefix
87
+ */
88
+ listObjects(prefix: string, options?: {
89
+ bucket?: string;
90
+ maxResults?: number;
91
+ pageToken?: string;
92
+ }): Promise<StorageListResult>;
93
+
94
+ /**
95
+ * Get the storage provider identifier.
96
+ *
97
+ * Built-in values are `'local'` and `'s3'`. Custom implementations
98
+ * should return their own identifier (e.g. `'gcs'`, `'azure'`).
99
+ */
100
+ getType(): string;
101
+ }
102
+
103
+ /**
104
+ * Default maximum file size (50MB)
105
+ */
106
+ export const DEFAULT_MAX_FILE_SIZE = 50 * 1024 * 1024;
107
+
108
+ /**
109
+ * Common image MIME types
110
+ */
111
+ export const IMAGE_MIME_TYPES = [
112
+ "image/jpeg",
113
+ "image/png",
114
+ "image/gif",
115
+ "image/webp",
116
+ "image/svg+xml",
117
+ "image/bmp",
118
+ "image/tiff"
119
+ ];
120
+
121
+ /**
122
+ * Common document MIME types
123
+ */
124
+ export const DOCUMENT_MIME_TYPES = [
125
+ "application/pdf",
126
+ "application/msword",
127
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
128
+ "application/vnd.ms-excel",
129
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
130
+ "application/vnd.ms-powerpoint",
131
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation",
132
+ "text/plain",
133
+ "text/csv"
134
+ ];
@@ -0,0 +1,27 @@
1
+ import {
2
+ Entity,
3
+ EntityCollection,
4
+ EntityStatus,
5
+ FilterValues,
6
+ FetchCollectionProps,
7
+ FetchEntityProps,
8
+ SaveEntityProps,
9
+ DeleteEntityProps,
10
+ WebSocketMessage,
11
+ CollectionUpdateMessage,
12
+ EntityUpdateMessage
13
+ } from "@rebasepro/types";
14
+
15
+ // Subscription types
16
+ export interface ListenCollectionRequest<M extends Record<string, unknown> = Record<string, unknown>> extends FetchCollectionProps<M> {
17
+ subscriptionId: string;
18
+ onUpdate: (entities: Entity<M>[]) => void;
19
+ onError?: (error: Error) => void;
20
+ }
21
+
22
+ export interface ListenEntityRequest<M extends Record<string, unknown> = Record<string, unknown>> extends FetchEntityProps<M> {
23
+ subscriptionId: string;
24
+ onUpdate: (entity: Entity<M> | null) => void;
25
+ onError?: (error: Error) => void;
26
+ }
27
+