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

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 (176) hide show
  1. package/dist/common/src/collections/CollectionRegistry.d.ts +8 -0
  2. package/dist/common/src/util/entities.d.ts +22 -0
  3. package/dist/common/src/util/relations.d.ts +14 -4
  4. package/dist/common/src/util/resolutions.d.ts +1 -1
  5. package/dist/index-DXVBFp5V.js +37 -0
  6. package/dist/index-DXVBFp5V.js.map +1 -0
  7. package/dist/index.es.js +49931 -55
  8. package/dist/index.es.js.map +1 -1
  9. package/dist/index.umd.js +28475 -34569
  10. package/dist/index.umd.js.map +1 -1
  11. package/dist/server-core/src/api/openapi-generator.d.ts +15 -1
  12. package/dist/server-core/src/api/types.d.ts +1 -1
  13. package/dist/server-core/src/auth/admin-routes.d.ts +10 -1
  14. package/dist/server-core/src/auth/apple-oauth.d.ts +30 -0
  15. package/dist/server-core/src/auth/bitbucket-oauth.d.ts +11 -0
  16. package/dist/server-core/src/auth/discord-oauth.d.ts +14 -0
  17. package/dist/server-core/src/auth/facebook-oauth.d.ts +14 -0
  18. package/dist/server-core/src/auth/github-oauth.d.ts +15 -0
  19. package/dist/server-core/src/auth/gitlab-oauth.d.ts +13 -0
  20. package/dist/server-core/src/auth/google-oauth.d.ts +5 -11
  21. package/dist/server-core/src/auth/index.d.ts +13 -2
  22. package/dist/server-core/src/auth/interfaces.d.ts +45 -6
  23. package/dist/server-core/src/auth/jwt.d.ts +1 -0
  24. package/dist/server-core/src/auth/linkedin-oauth.d.ts +18 -0
  25. package/dist/server-core/src/auth/microsoft-oauth.d.ts +16 -0
  26. package/dist/server-core/src/auth/middleware.d.ts +36 -11
  27. package/dist/server-core/src/auth/rate-limiter.d.ts +2 -2
  28. package/dist/server-core/src/auth/routes.d.ts +13 -3
  29. package/dist/server-core/src/auth/slack-oauth.d.ts +12 -0
  30. package/dist/server-core/src/auth/spotify-oauth.d.ts +12 -0
  31. package/dist/server-core/src/auth/twitter-oauth.d.ts +18 -0
  32. package/dist/server-core/src/cron/cron-loader.d.ts +17 -0
  33. package/dist/server-core/src/cron/cron-routes.d.ts +14 -0
  34. package/dist/server-core/src/cron/cron-scheduler.d.ts +61 -0
  35. package/dist/server-core/src/cron/cron-store.d.ts +32 -0
  36. package/dist/server-core/src/cron/index.d.ts +6 -0
  37. package/dist/server-core/src/email/index.d.ts +2 -2
  38. package/dist/server-core/src/email/templates.d.ts +9 -0
  39. package/dist/server-core/src/email/types.d.ts +20 -23
  40. package/dist/server-core/src/history/history-routes.d.ts +2 -2
  41. package/dist/server-core/src/index.d.ts +5 -0
  42. package/dist/server-core/src/init.d.ts +112 -2
  43. package/dist/server-core/src/singleton.d.ts +35 -0
  44. package/dist/server-core/src/storage/LocalStorageController.d.ts +9 -9
  45. package/dist/server-core/src/storage/S3StorageController.d.ts +8 -8
  46. package/dist/server-core/src/storage/index.d.ts +18 -11
  47. package/dist/server-core/src/storage/routes.d.ts +3 -3
  48. package/dist/server-core/src/storage/types.d.ts +28 -16
  49. package/dist/server-core/src/utils/dev-port.d.ts +35 -0
  50. package/dist/server-core/src/utils/logger.d.ts +31 -0
  51. package/dist/server-core/src/utils/request-logger.d.ts +19 -0
  52. package/dist/types/src/controllers/auth.d.ts +2 -0
  53. package/dist/types/src/controllers/client.d.ts +119 -7
  54. package/dist/types/src/controllers/collection_registry.d.ts +4 -3
  55. package/dist/types/src/controllers/customization_controller.d.ts +7 -1
  56. package/dist/types/src/controllers/data.d.ts +34 -7
  57. package/dist/types/src/controllers/data_driver.d.ts +20 -28
  58. package/dist/types/src/controllers/database_admin.d.ts +2 -2
  59. package/dist/types/src/controllers/email.d.ts +34 -0
  60. package/dist/types/src/controllers/index.d.ts +1 -0
  61. package/dist/types/src/controllers/local_config_persistence.d.ts +4 -4
  62. package/dist/types/src/controllers/navigation.d.ts +5 -5
  63. package/dist/types/src/controllers/registry.d.ts +6 -3
  64. package/dist/types/src/controllers/side_entity_controller.d.ts +7 -6
  65. package/dist/types/src/controllers/storage.d.ts +24 -26
  66. package/dist/types/src/rebase_context.d.ts +8 -4
  67. package/dist/types/src/types/backend.d.ts +4 -1
  68. package/dist/types/src/types/builders.d.ts +5 -4
  69. package/dist/types/src/types/chips.d.ts +1 -1
  70. package/dist/types/src/types/collections.d.ts +169 -125
  71. package/dist/types/src/types/cron.d.ts +102 -0
  72. package/dist/types/src/types/data_source.d.ts +1 -1
  73. package/dist/types/src/types/entity_actions.d.ts +8 -8
  74. package/dist/types/src/types/entity_callbacks.d.ts +15 -15
  75. package/dist/types/src/types/entity_link_builder.d.ts +1 -1
  76. package/dist/types/src/types/entity_overrides.d.ts +2 -1
  77. package/dist/types/src/types/entity_views.d.ts +8 -8
  78. package/dist/types/src/types/export_import.d.ts +3 -3
  79. package/dist/types/src/types/index.d.ts +1 -0
  80. package/dist/types/src/types/plugins.d.ts +72 -18
  81. package/dist/types/src/types/properties.d.ts +118 -33
  82. package/dist/types/src/types/relations.d.ts +1 -1
  83. package/dist/types/src/types/slots.d.ts +30 -6
  84. package/dist/types/src/types/translations.d.ts +44 -0
  85. package/dist/types/src/types/user_management_delegate.d.ts +1 -0
  86. package/package.json +84 -84
  87. package/scratch.ts +6 -5
  88. package/src/api/ast-schema-editor.ts +11 -11
  89. package/src/api/collections_for_test/callbacks_test_collection.ts +5 -2
  90. package/src/api/errors.ts +38 -14
  91. package/src/api/graphql/graphql-schema-generator.ts +5 -3
  92. package/src/api/openapi-generator.ts +613 -58
  93. package/src/api/rest/api-generator.ts +17 -11
  94. package/src/api/rest/query-parser.ts +1 -1
  95. package/src/api/schema-editor-routes.ts +2 -0
  96. package/src/api/server.ts +8 -5
  97. package/src/api/types.ts +1 -1
  98. package/src/auth/admin-routes.ts +58 -17
  99. package/src/auth/apple-oauth.ts +130 -0
  100. package/src/auth/bitbucket-oauth.ts +82 -0
  101. package/src/auth/discord-oauth.ts +83 -0
  102. package/src/auth/facebook-oauth.ts +72 -0
  103. package/src/auth/github-oauth.ts +110 -0
  104. package/src/auth/gitlab-oauth.ts +70 -0
  105. package/src/auth/google-oauth.ts +34 -46
  106. package/src/auth/index.ts +15 -2
  107. package/src/auth/interfaces.ts +54 -7
  108. package/src/auth/jwt.ts +22 -5
  109. package/src/auth/linkedin-oauth.ts +81 -0
  110. package/src/auth/microsoft-oauth.ts +88 -0
  111. package/src/auth/middleware.ts +194 -45
  112. package/src/auth/password.ts +3 -1
  113. package/src/auth/rate-limiter.ts +4 -4
  114. package/src/auth/routes.ts +197 -139
  115. package/src/auth/slack-oauth.ts +71 -0
  116. package/src/auth/spotify-oauth.ts +67 -0
  117. package/src/auth/twitter-oauth.ts +120 -0
  118. package/src/collections/loader.ts +6 -6
  119. package/src/cron/cron-loader.ts +89 -0
  120. package/src/cron/cron-routes.test.ts +265 -0
  121. package/src/cron/cron-routes.ts +85 -0
  122. package/src/cron/cron-scheduler.test.ts +421 -0
  123. package/src/cron/cron-scheduler.ts +413 -0
  124. package/src/cron/cron-store.ts +163 -0
  125. package/src/cron/index.ts +6 -0
  126. package/src/db/interfaces.ts +2 -2
  127. package/src/email/index.ts +3 -2
  128. package/src/email/smtp-email-service.ts +5 -2
  129. package/src/email/templates.ts +93 -6
  130. package/src/email/types.ts +17 -24
  131. package/src/functions/function-loader.ts +34 -6
  132. package/src/functions/function-routes.ts +2 -2
  133. package/src/history/history-routes.ts +4 -3
  134. package/src/index.ts +10 -0
  135. package/src/init.ts +475 -57
  136. package/src/services/driver-registry.ts +1 -1
  137. package/src/singleton.test.ts +28 -0
  138. package/src/singleton.ts +70 -0
  139. package/src/storage/LocalStorageController.ts +50 -53
  140. package/src/storage/S3StorageController.ts +49 -46
  141. package/src/storage/index.ts +27 -16
  142. package/src/storage/routes.ts +76 -59
  143. package/src/storage/storage-registry.ts +5 -5
  144. package/src/storage/types.ts +44 -32
  145. package/src/types/index.ts +4 -4
  146. package/src/utils/dev-port.ts +176 -0
  147. package/src/utils/logger.ts +143 -0
  148. package/src/utils/logging.ts +5 -2
  149. package/src/utils/request-logger.ts +66 -0
  150. package/test/admin-routes.test.ts +114 -65
  151. package/test/api-generator.test.ts +73 -30
  152. package/test/ast-schema-editor.test.ts +5 -3
  153. package/test/auth-middleware-hono.test.ts +268 -33
  154. package/test/auth-routes.test.ts +325 -146
  155. package/test/driver-registry.test.ts +4 -2
  156. package/test/error-propagation.test.ts +226 -0
  157. package/test/errors.test.ts +12 -7
  158. package/test/jwt-security.test.ts +18 -9
  159. package/test/jwt.test.ts +26 -13
  160. package/test/middleware.test.ts +13 -8
  161. package/test/query-parser.test.ts +13 -8
  162. package/test/rate-limiter.test.ts +7 -7
  163. package/test/safe-compare.test.ts +66 -0
  164. package/test/singleton.test.ts +59 -0
  165. package/test/storage-local.test.ts +36 -43
  166. package/test/storage-registry.test.ts +38 -36
  167. package/test/storage-routes.test.ts +9 -5
  168. package/test/storage-s3.test.ts +51 -48
  169. package/test.ts +6 -0
  170. package/tsconfig.json +1 -1
  171. package/vite.config.ts +7 -5
  172. package/dist/index-BeMqpmfQ.js +0 -239
  173. package/dist/index-BeMqpmfQ.js.map +0 -1
  174. package/dist/index-bl4J3lNb.js +0 -55823
  175. package/dist/index-bl4J3lNb.js.map +0 -1
  176. package/vite.config.ts.timestamp-1775065397568-8a853255edf6e.mjs +0 -46
@@ -1,32 +1,43 @@
1
1
  /**
2
2
  * Storage module for Rebase backend
3
3
  *
4
- * Provides file storage functionality with support for:
5
- * - Local filesystem storage (default, zero config)
6
- * - S3-compatible storage (AWS S3, MinIO)
4
+ * Provides pluggable file storage with two built-in providers:
5
+ * - **Local filesystem** zero config, great for dev and single-server deployments.
6
+ * - **S3-compatible** works with AWS S3, Cloudflare R2, MinIO, Hetzner Object Storage,
7
+ * Backblaze B2, DigitalOcean Spaces, and GCS (via S3 interop).
8
+ *
9
+ * For other providers (native GCS SDK, Azure Blob, etc.), implement the
10
+ * `StorageController` interface and pass the instance directly to the `storage` config.
7
11
  */
8
12
 
9
- export * from './types';
10
- export { LocalStorageController } from './LocalStorageController';
11
- export { S3StorageController } from './S3StorageController';
12
- export { createStorageRoutes } from './routes';
13
- export type { StorageRoutesConfig } from './routes';
14
- export * from './storage-registry';
13
+ export * from "./types";
14
+ export { LocalStorageController } from "./LocalStorageController";
15
+ export { S3StorageController } from "./S3StorageController";
16
+ export { createStorageRoutes } from "./routes";
17
+ export type { StorageRoutesConfig } from "./routes";
18
+ export * from "./storage-registry";
15
19
 
16
- import { BackendStorageConfig, StorageController } from './types';
17
- import { LocalStorageController } from './LocalStorageController';
18
- import { S3StorageController } from './S3StorageController';
20
+ import { BackendStorageConfig, StorageController } from "./types";
21
+ import { LocalStorageController } from "./LocalStorageController";
22
+ import { S3StorageController } from "./S3StorageController";
19
23
 
20
24
  /**
21
- * Factory function to create a storage controller based on configuration
25
+ * Create a storage controller from a config object.
26
+ *
27
+ * For custom providers, implement `StorageController` directly instead
28
+ * of going through this factory.
22
29
  */
23
30
  export function createStorageController(config: BackendStorageConfig): StorageController {
24
31
  switch (config.type) {
25
- case 'local':
32
+ case "local":
26
33
  return new LocalStorageController(config);
27
- case 's3':
34
+ case "s3":
28
35
  return new S3StorageController(config);
29
36
  default:
30
- throw new Error(`Unknown storage type: ${(config as Record<string, unknown>).type}`);
37
+ throw new Error(
38
+ `Unknown storage type: ${(config as Record<string, unknown>).type}. ` +
39
+ "Built-in types: local, s3. " +
40
+ "For other providers, implement the StorageController interface directly."
41
+ );
31
42
  }
32
43
  }
@@ -2,13 +2,13 @@
2
2
  * Storage REST API routes using Hono
3
3
  */
4
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 } from '../api/errors';
11
- import { HonoEnv } from '../api/types';
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
12
 
13
13
  export interface StorageRoutesConfig {
14
14
  controller: StorageController;
@@ -34,11 +34,11 @@ export interface StorageRoutesConfig {
34
34
  * plus one character for the trailing `/` to obtain `default/file.jpg`.
35
35
  */
36
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"
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
40
  const idx = fullPath.indexOf(prefix);
41
- if (idx < 0) return '';
41
+ if (idx < 0) return "";
42
42
  // +1 to skip the '/' after the prefix
43
43
  return fullPath.substring(idx + prefix.length + 1);
44
44
  }
@@ -48,6 +48,7 @@ export function extractWildcardPath(c: { req: { path: string; routePath: string
48
48
  */
49
49
  export function createStorageRoutes(config: StorageRoutesConfig): Hono<HonoEnv> {
50
50
  const router = new Hono<HonoEnv>();
51
+ router.onError(errorHandler);
51
52
  const { controller, requireAuth = true, publicRead = false } = config;
52
53
 
53
54
  // Use actual JWT auth middleware from auth module
@@ -60,19 +61,19 @@ export function createStorageRoutes(config: StorageRoutesConfig): Hono<HonoEnv>
60
61
  * Parse bucket and path from a combined file path.
61
62
  */
62
63
  const parseBucketAndPath = (filePath: string): { bucket: string; resolvedPath: string } => {
63
- const parts = filePath.split('/');
64
+ const parts = filePath.split("/");
64
65
 
65
66
  // Only recognize 'default' as an explicit bucket prefix
66
- if (parts.length > 1 && parts[0].toLowerCase() === 'default') {
67
+ if (parts.length > 1 && parts[0].toLowerCase() === "default") {
67
68
  return {
68
- bucket: 'default',
69
- resolvedPath: parts.slice(1).join('/')
69
+ bucket: "default",
70
+ resolvedPath: parts.slice(1).join("/")
70
71
  };
71
72
  }
72
73
 
73
74
  // All other paths use 'default' bucket with the full path
74
75
  return {
75
- bucket: 'default',
76
+ bucket: "default",
76
77
  resolvedPath: filePath
77
78
  };
78
79
  };
@@ -82,34 +83,48 @@ export function createStorageRoutes(config: StorageRoutesConfig): Hono<HonoEnv>
82
83
  * Body: multipart/form-data with 'file' field
83
84
  * Request body can also contain metadata keys 'metadata_*'
84
85
  */
85
- router.post('/upload', writeAuthMiddleware, async (c) => {
86
+ router.post("/upload", writeAuthMiddleware, async (c) => {
86
87
  const body = await c.req.parseBody();
87
- const uploadedFile = body['file'];
88
+ const uploadedFile = body["file"];
88
89
 
89
- if (!uploadedFile || typeof uploadedFile === 'string') {
90
- throw ApiError.badRequest('No file provided');
90
+ if (!uploadedFile || typeof uploadedFile === "string") {
91
+ throw ApiError.badRequest("No file provided");
91
92
  }
92
93
 
93
- const storagePath = typeof body['path'] === 'string' ? body['path'] : '';
94
- const bucket = typeof body['bucket'] === 'string' ? body['bucket'] : undefined;
95
- let fileName = typeof body['fileName'] === 'string' ? body['fileName'] : undefined;
96
-
97
- if (!fileName) {
98
- fileName = uploadedFile.name;
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
+ }
99
115
  }
100
116
 
101
117
  // Extract custom metadata from request body
102
118
  const metadata: Record<string, unknown> = {};
103
- for (const [key, value] of Object.entries(body)) {
104
- if (key.startsWith('metadata_')) {
105
- metadata[key.replace('metadata_', '')] = value;
119
+ for (const [k, value] of Object.entries(body)) {
120
+ if (k.startsWith("metadata_")) {
121
+ metadata[k.replace("metadata_", "")] = value;
106
122
  }
107
123
  }
108
124
 
109
- const result = await controller.uploadFile({
125
+ const result = await controller.putObject({
110
126
  file: uploadedFile,
111
- fileName: fileName || 'unnamed',
112
- path: storagePath,
127
+ key: finalKey,
113
128
  metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
114
129
  bucket
115
130
  });
@@ -124,16 +139,16 @@ export function createStorageRoutes(config: StorageRoutesConfig): Hono<HonoEnv>
124
139
  * GET /file/* - Download/serve a file
125
140
  * Path: /file/{bucket}/{path} or /file/{path}
126
141
  */
127
- router.get('/file/*', readAuthMiddleware, async (c) => {
142
+ router.get("/file/*", readAuthMiddleware, async (c) => {
128
143
  const rawPath = extractWildcardPath(c);
129
144
  if (!rawPath) {
130
- throw ApiError.notFound('File not found');
145
+ throw ApiError.notFound("File not found");
131
146
  }
132
147
 
133
148
  const filePath = decodeURIComponent(rawPath);
134
149
 
135
- // For local storage, serve the file directly
136
- if (controller.getType() === 'local') {
150
+ // For local storage, serve the file directly from disk
151
+ if (controller.getType() === "local") {
137
152
  const localController = controller as LocalStorageController;
138
153
  const { bucket, resolvedPath } = parseBucketAndPath(filePath);
139
154
 
@@ -141,31 +156,31 @@ export function createStorageRoutes(config: StorageRoutesConfig): Hono<HonoEnv>
141
156
 
142
157
  // Check if file exists
143
158
  if (!fs.existsSync(absolutePath)) {
144
- throw ApiError.notFound('File not found');
159
+ throw ApiError.notFound("File not found");
145
160
  }
146
161
 
147
162
  // Get content type from metadata or infer from extension
148
- let contentType = 'application/octet-stream';
163
+ let contentType = "application/octet-stream";
149
164
  const metadataPath = `${absolutePath}.metadata.json`;
150
165
  if (fs.existsSync(metadataPath)) {
151
166
  try {
152
- const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf-8'));
167
+ const metadata = JSON.parse(fs.readFileSync(metadataPath, "utf-8"));
153
168
  contentType = metadata.contentType || contentType;
154
169
  } catch {
155
170
  // Ignore metadata errors
156
171
  }
157
172
  }
158
173
 
159
- c.header('Content-Type', contentType);
174
+ c.header("Content-Type", contentType);
160
175
  // In a better scenario, we should pipe the stream instead of reading whole file
161
176
  const fileContent = fs.readFileSync(absolutePath);
162
- return c.body(new Uint8Array(fileContent));
177
+ return c.body(new Uint8Array(fileContent));
163
178
  }
164
179
 
165
- // For S3 storage, redirect to signed URL
166
- const downloadConfig = await controller.getDownloadURL(filePath);
180
+ // For remote storage (S3, GCS, etc.), redirect to a signed URL
181
+ const downloadConfig = await controller.getSignedUrl(filePath);
167
182
  if (downloadConfig.fileNotFound || !downloadConfig.url) {
168
- throw ApiError.notFound('File not found');
183
+ throw ApiError.notFound("File not found");
169
184
  }
170
185
 
171
186
  return c.redirect(downloadConfig.url);
@@ -174,7 +189,7 @@ export function createStorageRoutes(config: StorageRoutesConfig): Hono<HonoEnv>
174
189
  /**
175
190
  * GET /metadata/* - Get file metadata
176
191
  */
177
- router.get('/metadata/*', readAuthMiddleware, async (c) => {
192
+ router.get("/metadata/*", readAuthMiddleware, async (c) => {
178
193
  const rawPath = extractWildcardPath(c);
179
194
  if (!rawPath) {
180
195
  return c.json({
@@ -187,10 +202,10 @@ export function createStorageRoutes(config: StorageRoutesConfig): Hono<HonoEnv>
187
202
  const filePath = decodeURIComponent(rawPath);
188
203
  const { bucket, resolvedPath } = parseBucketAndPath(filePath);
189
204
 
190
- const downloadConfig = await controller.getDownloadURL(resolvedPath, bucket);
205
+ const downloadConfig = await controller.getSignedUrl(resolvedPath, bucket);
191
206
 
192
207
  if (downloadConfig.fileNotFound) {
193
- throw ApiError.notFound('File not found');
208
+ throw ApiError.notFound("File not found");
194
209
  }
195
210
 
196
211
  return c.json({
@@ -202,34 +217,36 @@ export function createStorageRoutes(config: StorageRoutesConfig): Hono<HonoEnv>
202
217
  /**
203
218
  * DELETE /file/* - Delete a file
204
219
  */
205
- router.delete('/file/*', writeAuthMiddleware, async (c) => {
220
+ router.delete("/file/*", writeAuthMiddleware, async (c) => {
206
221
  const rawPath = extractWildcardPath(c);
207
222
  if (!rawPath) {
208
- return c.json({ success: true, message: 'No file to delete' });
223
+ return c.json({ success: true,
224
+ message: "No file to delete" });
209
225
  }
210
226
 
211
227
  const filePath = decodeURIComponent(rawPath);
212
228
  const { bucket, resolvedPath } = parseBucketAndPath(filePath);
213
229
 
214
- await controller.deleteFile(resolvedPath, bucket);
230
+ await controller.deleteObject(resolvedPath, bucket);
215
231
 
216
232
  return c.json({
217
233
  success: true,
218
- message: 'File deleted'
234
+ message: "File deleted"
219
235
  });
220
236
  });
221
237
 
222
238
  /**
223
239
  * GET /list - List files in a path
224
240
  */
225
- router.get('/list', writeAuthMiddleware, async (c) => {
226
- const storagePath = c.req.query('path') || '';
227
- const bucket = c.req.query('bucket');
228
- const maxResults = c.req.query('maxResults');
229
- const pageToken = c.req.query('pageToken');
230
-
231
- const result = await controller.list(
232
- storagePath,
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,
233
250
  {
234
251
  bucket,
235
252
  maxResults: maxResults ? parseInt(maxResults, 10) : undefined,
@@ -121,7 +121,7 @@ export class DefaultStorageRegistry implements StorageRegistry {
121
121
  const controller = this.controllers.get(DEFAULT_STORAGE_ID);
122
122
  if (!controller) {
123
123
  throw new Error(
124
- `[StorageRegistry] No default storage registered. ` +
124
+ "[StorageRegistry] No default storage registered. " +
125
125
  `Register one with id "${DEFAULT_STORAGE_ID}" or pass a single StorageController.`
126
126
  );
127
127
  }
@@ -178,10 +178,10 @@ function isStorageController(obj: unknown): obj is StorageController {
178
178
  const controller = obj as StorageController;
179
179
  // Check for required StorageController properties
180
180
  return (
181
- typeof controller.uploadFile === "function" &&
182
- typeof controller.getDownloadURL === "function" &&
183
- typeof controller.deleteFile === "function" &&
184
- typeof controller.list === "function" &&
181
+ typeof controller.putObject === "function" &&
182
+ typeof controller.getSignedUrl === "function" &&
183
+ typeof controller.deleteObject === "function" &&
184
+ typeof controller.listObjects === "function" &&
185
185
  typeof controller.getType === "function"
186
186
  );
187
187
  }
@@ -8,7 +8,7 @@ import { StorageSource, UploadFileProps, UploadFileResult, DownloadConfig, Stora
8
8
  * Local filesystem storage configuration
9
9
  */
10
10
  export interface LocalStorageConfig {
11
- type: 'local';
11
+ type: "local";
12
12
  /** Base directory for file storage (e.g., './uploads') */
13
13
  basePath: string;
14
14
  /** Maximum file size in bytes (default: 50MB) */
@@ -23,12 +23,12 @@ export interface LocalStorageConfig {
23
23
  * S3-compatible storage configuration (works with AWS S3 and MinIO)
24
24
  */
25
25
  export interface S3StorageConfig {
26
- type: 's3';
26
+ type: "s3";
27
27
  /** S3 bucket name */
28
28
  bucket: string;
29
29
  /** AWS region (e.g., 'us-east-1') */
30
30
  region?: string;
31
- /** Custom endpoint URL (required for MinIO) */
31
+ /** Custom endpoint URL (required for MinIO, Cloudflare R2, Hetzner Object Storage) */
32
32
  endpoint?: string;
33
33
  /** AWS access key ID */
34
34
  accessKeyId: string;
@@ -45,7 +45,16 @@ export interface S3StorageConfig {
45
45
  }
46
46
 
47
47
  /**
48
- * Storage configuration - either local filesystem or S3-compatible
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.
49
58
  */
50
59
  export type BackendStorageConfig = LocalStorageConfig | S3StorageConfig;
51
60
 
@@ -54,38 +63,41 @@ export type BackendStorageConfig = LocalStorageConfig | S3StorageConfig;
54
63
  */
55
64
  export interface StorageController {
56
65
  /**
57
- * Upload a file
66
+ * Upload an object
58
67
  */
59
- uploadFile(props: UploadFileProps): Promise<UploadFileResult>;
68
+ putObject(props: UploadFileProps): Promise<UploadFileResult>;
60
69
 
61
70
  /**
62
- * Get a download URL for a file
71
+ * Get a download URL (signed URL equivalent) for an object
63
72
  */
64
- getDownloadURL(path: string, bucket?: string): Promise<DownloadConfig>;
73
+ getSignedUrl(key: string, bucket?: string): Promise<DownloadConfig>;
65
74
 
66
75
  /**
67
- * Get file as a File object
76
+ * Get object as a File
68
77
  */
69
- getFile(path: string, bucket?: string): Promise<File | null>;
78
+ getObject(key: string, bucket?: string): Promise<File | null>;
70
79
 
71
80
  /**
72
- * Delete a file
81
+ * Delete an object
73
82
  */
74
- deleteFile(path: string, bucket?: string): Promise<void>;
83
+ deleteObject(key: string, bucket?: string): Promise<void>;
75
84
 
76
85
  /**
77
- * List files in a path
86
+ * List objects in a prefix
78
87
  */
79
- list(path: string, options?: {
88
+ listObjects(prefix: string, options?: {
80
89
  bucket?: string;
81
90
  maxResults?: number;
82
91
  pageToken?: string;
83
92
  }): Promise<StorageListResult>;
84
93
 
85
94
  /**
86
- * Get the storage configuration type
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'`).
87
99
  */
88
- getType(): 'local' | 's3';
100
+ getType(): string;
89
101
  }
90
102
 
91
103
  /**
@@ -97,26 +109,26 @@ export const DEFAULT_MAX_FILE_SIZE = 50 * 1024 * 1024;
97
109
  * Common image MIME types
98
110
  */
99
111
  export const IMAGE_MIME_TYPES = [
100
- 'image/jpeg',
101
- 'image/png',
102
- 'image/gif',
103
- 'image/webp',
104
- 'image/svg+xml',
105
- 'image/bmp',
106
- 'image/tiff'
112
+ "image/jpeg",
113
+ "image/png",
114
+ "image/gif",
115
+ "image/webp",
116
+ "image/svg+xml",
117
+ "image/bmp",
118
+ "image/tiff"
107
119
  ];
108
120
 
109
121
  /**
110
122
  * Common document MIME types
111
123
  */
112
124
  export const DOCUMENT_MIME_TYPES = [
113
- 'application/pdf',
114
- 'application/msword',
115
- 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
116
- 'application/vnd.ms-excel',
117
- 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
118
- 'application/vnd.ms-powerpoint',
119
- 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
120
- 'text/plain',
121
- 'text/csv'
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"
122
134
  ];
@@ -1,7 +1,7 @@
1
- import {
2
- Entity,
3
- EntityCollection,
4
- EntityStatus,
1
+ import {
2
+ Entity,
3
+ EntityCollection,
4
+ EntityStatus,
5
5
  FilterValues,
6
6
  FetchCollectionProps,
7
7
  FetchEntityProps,