@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
@@ -117,7 +117,7 @@ export class DefaultDriverRegistry implements DriverRegistry {
117
117
  const delegate = this.delegates.get(DEFAULT_DRIVER_ID);
118
118
  if (!delegate) {
119
119
  throw new Error(
120
- `[DriverRegistry] No default driver registered. ` +
120
+ "[DriverRegistry] No default driver registered. " +
121
121
  `Register one with id "${DEFAULT_DRIVER_ID}" or pass a single DataDriver.`
122
122
  );
123
123
  }
@@ -0,0 +1,28 @@
1
+ import { rebase, _initRebase } from "./singleton";
2
+ import type { RebaseClient } from "@rebasepro/types";
3
+
4
+ describe("rebase singleton", () => {
5
+ beforeEach(() => {
6
+ // Reset the singleton before each test
7
+ // By calling _initRebase with null (cast to bypass type checking for the reset)
8
+ _initRebase(null as unknown as RebaseClient);
9
+ });
10
+
11
+ it("should throw an error if accessed before initialization", () => {
12
+ expect(() => rebase.data).toThrow(
13
+ "rebase.data: server not initialized yet. The singleton is available after Rebase starts — don't call it at import time."
14
+ );
15
+ });
16
+
17
+ it("should return the correctly initialized instance properties", () => {
18
+ const mockClient = {
19
+ data: { test: "mockData" },
20
+ auth: { test: "mockAuth" }
21
+ } as unknown as RebaseClient;
22
+
23
+ _initRebase(mockClient);
24
+
25
+ expect(rebase.data).toEqual({ test: "mockData" });
26
+ expect(rebase.auth).toEqual({ test: "mockAuth" });
27
+ });
28
+ });
@@ -0,0 +1,70 @@
1
+ import type { RebaseClient } from "@rebasepro/types";
2
+
3
+ let _instance: RebaseClient | null = null;
4
+
5
+ /**
6
+ * @internal Called once during server initialization to set the backing instance.
7
+ * This is invoked by `initializeRebaseBackend()` — never call it manually.
8
+ */
9
+ export function _initRebase(client: RebaseClient): void {
10
+ _instance = client;
11
+ }
12
+
13
+ /**
14
+ * @internal Allows overriding the underlying instance for unit testing.
15
+ * Throws an error if used in a non-test environment to prevent production abuse.
16
+ */
17
+ export function _setRebaseMock(mockInstance: Partial<RebaseClient>): void {
18
+ if (process.env.NODE_ENV !== "test") {
19
+ throw new Error("_setRebaseMock can only be called in a test environment (NODE_ENV=test).");
20
+ }
21
+ _instance = { ...(_instance || {} as RebaseClient),
22
+ ...mockInstance } as RebaseClient;
23
+ }
24
+
25
+ /**
26
+ * @internal Resets the singleton instance, useful for afterEach() in test suites.
27
+ */
28
+ export function _resetRebaseMock(): void {
29
+ if (process.env.NODE_ENV !== "test") {
30
+ throw new Error("_resetRebaseMock can only be called in a test environment.");
31
+ }
32
+ _instance = null;
33
+ }
34
+
35
+ /**
36
+ * The server-side Rebase singleton.
37
+ *
38
+ * Initialized automatically during server startup. Provides access to all
39
+ * app-scoped services: **data**, **auth**, **storage**, and **email**.
40
+ *
41
+ * `rebase.data` runs with **admin privileges** (no RLS). For user-scoped
42
+ * queries inside request handlers, continue using the handler's context
43
+ * or the RLS-scoped driver.
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * import { rebase } from "@rebasepro/server-core";
48
+ *
49
+ * // In a Hono handler, cron job, hook, or service file:
50
+ * await rebase.email?.send({ to: "admin@co.com", subject: "Alert", html: "<p>Hi</p>" });
51
+ * const jobs = await rebase.data.jobs.find({ limit: 10 });
52
+ * ```
53
+ */
54
+ export const rebase: RebaseClient = new Proxy({} as RebaseClient, {
55
+ get(_, prop) {
56
+ if (!_instance) {
57
+ throw new Error(
58
+ `rebase.${String(prop)}: server not initialized yet. ` +
59
+ "The singleton is available after Rebase starts — don't call it at import time."
60
+ );
61
+ }
62
+ return _instance[prop as keyof RebaseClient];
63
+ },
64
+ set(_, prop) {
65
+ throw new Error(
66
+ `Cannot set rebase.${String(prop)} directly. ` +
67
+ "The singleton is read-only. Use _initRebase() during server startup."
68
+ );
69
+ }
70
+ });
@@ -2,14 +2,14 @@
2
2
  * Local filesystem storage controller
3
3
  */
4
4
 
5
- import * as fs from 'fs';
6
- import * as path from 'path';
7
- import { promisify } from 'util';
5
+ import * as fs from "fs";
6
+ import * as path from "path";
7
+ import { promisify } from "util";
8
8
  import {
9
9
  StorageController,
10
10
  LocalStorageConfig,
11
11
  DEFAULT_MAX_FILE_SIZE
12
- } from './types';
12
+ } from "./types";
13
13
  import {
14
14
  UploadFileProps,
15
15
  UploadFileResult,
@@ -17,7 +17,7 @@ import {
17
17
  DownloadMetadata,
18
18
  StorageListResult,
19
19
  StorageReference
20
- } from '@rebasepro/types';
20
+ } from "@rebasepro/types";
21
21
 
22
22
  const mkdir = promisify(fs.mkdir);
23
23
  const writeFile = promisify(fs.writeFile);
@@ -33,10 +33,10 @@ const access = promisify(fs.access);
33
33
  */
34
34
  function normalizeStoragePath(s: string): string {
35
35
  let result = s;
36
- while (result.startsWith('/')) {
36
+ while (result.startsWith("/")) {
37
37
  result = result.slice(1);
38
38
  }
39
- while (result.endsWith('/')) {
39
+ while (result.endsWith("/")) {
40
40
  result = result.slice(0, -1);
41
41
  }
42
42
  return result;
@@ -55,8 +55,8 @@ export class LocalStorageController implements StorageController {
55
55
  this.basePath = path.resolve(config.basePath);
56
56
  }
57
57
 
58
- getType(): 'local' {
59
- return 'local';
58
+ getType(): "local" {
59
+ return "local";
60
60
  }
61
61
 
62
62
  /**
@@ -66,7 +66,7 @@ export class LocalStorageController implements StorageController {
66
66
  try {
67
67
  await mkdir(dirPath, { recursive: true });
68
68
  } catch (error: unknown) {
69
- if (error instanceof Error && (error as NodeJS.ErrnoException).code !== 'EEXIST') {
69
+ if (error instanceof Error && (error as NodeJS.ErrnoException).code !== "EEXIST") {
70
70
  throw error;
71
71
  }
72
72
  }
@@ -96,26 +96,22 @@ export class LocalStorageController implements StorageController {
96
96
 
97
97
  if (this.config.allowedMimeTypes && this.config.allowedMimeTypes.length > 0) {
98
98
  if (!this.config.allowedMimeTypes.includes(file.type)) {
99
- throw new Error(`File type ${file.type} is not allowed. Allowed types: ${this.config.allowedMimeTypes.join(', ')}`);
99
+ throw new Error(`File type ${file.type} is not allowed. Allowed types: ${this.config.allowedMimeTypes.join(", ")}`);
100
100
  }
101
101
  }
102
102
  }
103
103
 
104
- async uploadFile({
104
+ async putObject({
105
105
  file,
106
- fileName,
107
- path: storagePath,
106
+ key,
108
107
  metadata,
109
108
  bucket
110
109
  }: UploadFileProps): Promise<UploadFileResult> {
111
110
  this.validateFile(file);
112
111
 
113
112
  // Always use a bucket (default to 'default')
114
- const usedBucket = bucket ?? 'default';
115
- const usedFileName = fileName ?? file.name;
116
- // Normalize storage path to remove leading/trailing slashes
117
- const normalizedPath = storagePath ? normalizeStoragePath(storagePath) : '';
118
- const fullStoragePath = normalizedPath ? `${normalizedPath}/${usedFileName}` : usedFileName;
113
+ const usedBucket = bucket ?? "default";
114
+ const fullStoragePath = key;
119
115
  const fullPath = this.getFullPath(fullStoragePath, usedBucket);
120
116
 
121
117
  // Ensure parent directory exists
@@ -136,20 +132,20 @@ export class LocalStorageController implements StorageController {
136
132
  }, null, 2));
137
133
 
138
134
  return {
139
- path: fullStoragePath,
135
+ key: fullStoragePath,
140
136
  bucket: usedBucket,
141
137
  storageUrl: `local://${usedBucket}/${fullStoragePath}`
142
138
  };
143
139
  }
144
140
 
145
- async getDownloadURL(storagePath: string, bucket?: string): Promise<DownloadConfig> {
141
+ async getSignedUrl(key: string, bucket?: string): Promise<DownloadConfig> {
146
142
  // Handle local:// URLs
147
- let resolvedPath = storagePath;
143
+ let resolvedPath = key;
148
144
  let resolvedBucket = bucket;
149
145
 
150
- if (storagePath.startsWith('local://')) {
151
- const withoutProtocol = storagePath.substring('local://'.length);
152
- const firstSlash = withoutProtocol.indexOf('/');
146
+ if (key.startsWith("local://")) {
147
+ const withoutProtocol = key.substring("local://".length);
148
+ const firstSlash = withoutProtocol.indexOf("/");
153
149
  if (firstSlash > 0) {
154
150
  resolvedBucket = withoutProtocol.substring(0, firstSlash);
155
151
  resolvedPath = withoutProtocol.substring(firstSlash + 1);
@@ -173,16 +169,16 @@ export class LocalStorageController implements StorageController {
173
169
  let metadata: DownloadMetadata | undefined;
174
170
  const metadataPath = `${fullPath}.metadata.json`;
175
171
  try {
176
- const metadataContent = await readFile(metadataPath, 'utf-8');
172
+ const metadataContent = await readFile(metadataPath, "utf-8");
177
173
  const savedMetadata = JSON.parse(metadataContent);
178
174
  const fileStat = await stat(fullPath);
179
175
 
180
176
  metadata = {
181
- bucket: resolvedBucket ?? 'default',
177
+ bucket: resolvedBucket ?? "default",
182
178
  fullPath: resolvedPath,
183
179
  name: path.basename(resolvedPath),
184
180
  size: fileStat.size,
185
- contentType: savedMetadata.contentType || 'application/octet-stream',
181
+ contentType: savedMetadata.contentType || "application/octet-stream",
186
182
  customMetadata: savedMetadata
187
183
  };
188
184
  } catch {
@@ -190,11 +186,11 @@ export class LocalStorageController implements StorageController {
190
186
  try {
191
187
  const fileStat = await stat(fullPath);
192
188
  metadata = {
193
- bucket: resolvedBucket ?? 'default',
189
+ bucket: resolvedBucket ?? "default",
194
190
  fullPath: resolvedPath,
195
191
  name: path.basename(resolvedPath),
196
192
  size: fileStat.size,
197
- contentType: 'application/octet-stream',
193
+ contentType: "application/octet-stream",
198
194
  customMetadata: {}
199
195
  };
200
196
  } catch {
@@ -203,7 +199,7 @@ export class LocalStorageController implements StorageController {
203
199
  }
204
200
 
205
201
  // Return a relative URL that will be served by the storage routes
206
- const bucketPath = resolvedBucket ? `${resolvedBucket}/` : '';
202
+ const bucketPath = resolvedBucket ? `${resolvedBucket}/` : "";
207
203
  const url = `/api/storage/file/${bucketPath}${resolvedPath}`;
208
204
 
209
205
  return {
@@ -212,14 +208,14 @@ export class LocalStorageController implements StorageController {
212
208
  };
213
209
  }
214
210
 
215
- async getFile(storagePath: string, bucket?: string): Promise<File | null> {
211
+ async getObject(key: string, bucket?: string): Promise<File | null> {
216
212
  // Handle local:// URLs
217
- let resolvedPath = storagePath;
213
+ let resolvedPath = key;
218
214
  let resolvedBucket = bucket;
219
215
 
220
- if (storagePath.startsWith('local://')) {
221
- const withoutProtocol = storagePath.substring('local://'.length);
222
- const firstSlash = withoutProtocol.indexOf('/');
216
+ if (key.startsWith("local://")) {
217
+ const withoutProtocol = key.substring("local://".length);
218
+ const firstSlash = withoutProtocol.indexOf("/");
223
219
  if (firstSlash > 0) {
224
220
  resolvedBucket = withoutProtocol.substring(0, firstSlash);
225
221
  resolvedPath = withoutProtocol.substring(firstSlash + 1);
@@ -235,10 +231,10 @@ export class LocalStorageController implements StorageController {
235
231
  const buffer = await readFile(fullPath);
236
232
 
237
233
  // Try to get content type from metadata
238
- let contentType = 'application/octet-stream';
234
+ let contentType = "application/octet-stream";
239
235
  try {
240
236
  const metadataPath = `${fullPath}.metadata.json`;
241
- const metadataContent = await readFile(metadataPath, 'utf-8');
237
+ const metadataContent = await readFile(metadataPath, "utf-8");
242
238
  const metadata = JSON.parse(metadataContent);
243
239
  contentType = metadata.contentType || contentType;
244
240
  } catch {
@@ -252,14 +248,14 @@ export class LocalStorageController implements StorageController {
252
248
  }
253
249
  }
254
250
 
255
- async deleteFile(storagePath: string, bucket?: string): Promise<void> {
251
+ async deleteObject(key: string, bucket?: string): Promise<void> {
256
252
  // Handle local:// URLs
257
- let resolvedPath = storagePath;
253
+ let resolvedPath = key;
258
254
  let resolvedBucket = bucket;
259
255
 
260
- if (storagePath.startsWith('local://')) {
261
- const withoutProtocol = storagePath.substring('local://'.length);
262
- const firstSlash = withoutProtocol.indexOf('/');
256
+ if (key.startsWith("local://")) {
257
+ const withoutProtocol = key.substring("local://".length);
258
+ const firstSlash = withoutProtocol.indexOf("/");
263
259
  if (firstSlash > 0) {
264
260
  resolvedBucket = withoutProtocol.substring(0, firstSlash);
265
261
  resolvedPath = withoutProtocol.substring(firstSlash + 1);
@@ -279,20 +275,20 @@ export class LocalStorageController implements StorageController {
279
275
  // Metadata file might not exist
280
276
  }
281
277
  } catch (error: unknown) {
282
- if (error instanceof Error && (error as NodeJS.ErrnoException).code !== 'ENOENT') {
278
+ if (error instanceof Error && (error as NodeJS.ErrnoException).code !== "ENOENT") {
283
279
  throw error;
284
280
  }
285
281
  // File doesn't exist, nothing to delete
286
282
  }
287
283
  }
288
284
 
289
- async list(storagePath: string, options?: {
285
+ async listObjects(prefix: string, options?: {
290
286
  bucket?: string;
291
287
  maxResults?: number;
292
288
  pageToken?: string;
293
289
  }): Promise<StorageListResult> {
294
290
  // Normalize path to handle leading/trailing slashes
295
- const normalizedPath = normalizeStoragePath(storagePath);
291
+ const normalizedPath = normalizeStoragePath(prefix);
296
292
  const fullPath = this.getFullPath(normalizedPath, options?.bucket);
297
293
  const items: StorageReference[] = [];
298
294
  const prefixes: StorageReference[] = [];
@@ -309,12 +305,12 @@ export class LocalStorageController implements StorageController {
309
305
  const entry = entries[i];
310
306
 
311
307
  // Skip metadata files
312
- if (entry.name.endsWith('.metadata.json')) {
308
+ if (entry.name.endsWith(".metadata.json")) {
313
309
  continue;
314
310
  }
315
311
 
316
- const entryPath = storagePath ? `${storagePath}/${entry.name}` : entry.name;
317
- const bucket = options?.bucket ?? 'default';
312
+ const entryPath = prefix ? `${prefix}/${entry.name}` : entry.name;
313
+ const bucket = options?.bucket ?? "default";
318
314
 
319
315
  const ref: StorageReference = {
320
316
  bucket,
@@ -344,8 +340,9 @@ export class LocalStorageController implements StorageController {
344
340
  };
345
341
  } catch (error: unknown) {
346
342
  const code = (error as NodeJS.ErrnoException)?.code;
347
- if (code === 'ENOENT' || code === 'ENOTDIR') {
348
- return { items: [], prefixes: [] };
343
+ if (code === "ENOENT" || code === "ENOTDIR") {
344
+ return { items: [],
345
+ prefixes: [] };
349
346
  }
350
347
  throw error;
351
348
  }
@@ -355,8 +352,8 @@ export class LocalStorageController implements StorageController {
355
352
  * Get the absolute filesystem path for serving files
356
353
  * Used by the storage routes to serve files directly
357
354
  */
358
- getAbsolutePath(storagePath: string, bucket?: string): string {
359
- return this.getFullPath(storagePath, bucket);
355
+ getAbsolutePath(key: string, bucket?: string): string {
356
+ return this.getFullPath(key, bucket);
360
357
  }
361
358
 
362
359
  /**
@@ -11,13 +11,13 @@ import {
11
11
  HeadObjectCommand,
12
12
  _Object,
13
13
  CommonPrefix
14
- } from '@aws-sdk/client-s3';
15
- import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
14
+ } from "@aws-sdk/client-s3";
15
+ import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
16
16
  import {
17
17
  StorageController,
18
18
  S3StorageConfig,
19
19
  DEFAULT_MAX_FILE_SIZE
20
- } from './types';
20
+ } from "./types";
21
21
  import {
22
22
  UploadFileProps,
23
23
  UploadFileResult,
@@ -25,7 +25,7 @@ import {
25
25
  DownloadMetadata,
26
26
  StorageListResult,
27
27
  StorageReference
28
- } from '@rebasepro/types';
28
+ } from "@rebasepro/types";
29
29
 
30
30
  /**
31
31
  * S3-compatible storage implementation
@@ -38,7 +38,7 @@ export class S3StorageController implements StorageController {
38
38
  constructor(config: S3StorageConfig) {
39
39
  this.config = config;
40
40
  this.client = new S3Client({
41
- region: config.region || 'us-east-1',
41
+ region: config.region || "us-east-1",
42
42
  endpoint: config.endpoint,
43
43
  forcePathStyle: config.forcePathStyle ?? !!config.endpoint, // Auto-enable for custom endpoints (MinIO)
44
44
  credentials: {
@@ -48,8 +48,8 @@ export class S3StorageController implements StorageController {
48
48
  });
49
49
  }
50
50
 
51
- getType(): 's3' {
52
- return 's3';
51
+ getType(): "s3" {
52
+ return "s3";
53
53
  }
54
54
 
55
55
  /**
@@ -63,7 +63,7 @@ export class S3StorageController implements StorageController {
63
63
 
64
64
  if (this.config.allowedMimeTypes && this.config.allowedMimeTypes.length > 0) {
65
65
  if (!this.config.allowedMimeTypes.includes(file.type)) {
66
- throw new Error(`File type ${file.type} is not allowed. Allowed types: ${this.config.allowedMimeTypes.join(', ')}`);
66
+ throw new Error(`File type ${file.type} is not allowed. Allowed types: ${this.config.allowedMimeTypes.join(", ")}`);
67
67
  }
68
68
  }
69
69
  }
@@ -75,17 +75,14 @@ export class S3StorageController implements StorageController {
75
75
  return bucket ?? this.config.bucket;
76
76
  }
77
77
 
78
- async uploadFile({
78
+ async putObject({
79
79
  file,
80
- fileName,
81
- path: storagePath,
80
+ key,
82
81
  metadata,
83
82
  bucket
84
83
  }: UploadFileProps): Promise<UploadFileResult> {
85
84
  this.validateFile(file);
86
85
 
87
- const usedFileName = fileName ?? file.name;
88
- const key = storagePath ? `${storagePath}/${usedFileName}` : usedFileName;
89
86
  const usedBucket = this.getBucket(bucket);
90
87
 
91
88
  // Convert File to Buffer
@@ -103,7 +100,7 @@ export class S3StorageController implements StorageController {
103
100
  await this.client.send(command);
104
101
 
105
102
  return {
106
- path: key,
103
+ key,
107
104
  bucket: usedBucket,
108
105
  storageUrl: `s3://${usedBucket}/${key}`
109
106
  };
@@ -115,7 +112,7 @@ export class S3StorageController implements StorageController {
115
112
  private flattenMetadata(metadata: Record<string, unknown>): Record<string, string> {
116
113
  const flattened: Record<string, string> = {};
117
114
  for (const [key, value] of Object.entries(metadata)) {
118
- if (typeof value === 'string') {
115
+ if (typeof value === "string") {
119
116
  flattened[key] = value;
120
117
  } else if (value !== undefined && value !== null) {
121
118
  flattened[key] = JSON.stringify(value);
@@ -124,14 +121,16 @@ export class S3StorageController implements StorageController {
124
121
  return flattened;
125
122
  }
126
123
 
127
- async getDownloadURL(storagePath: string, bucket?: string): Promise<DownloadConfig> {
128
- // Handle s3:// URLs
129
- let resolvedPath = storagePath;
124
+ async getSignedUrl(key: string, bucket?: string): Promise<DownloadConfig> {
125
+ // Handle s3:// and gs:// URLs for backward compatibility
126
+ let resolvedPath = key;
130
127
  let resolvedBucket = this.getBucket(bucket);
131
128
 
132
- if (storagePath.startsWith('s3://')) {
133
- const withoutProtocol = storagePath.substring('s3://'.length);
134
- const firstSlash = withoutProtocol.indexOf('/');
129
+ const match = key.match(/^(s3|gs):\/\//);
130
+ if (match) {
131
+ const protocolLength = match[0].length;
132
+ const withoutProtocol = key.substring(protocolLength);
133
+ const firstSlash = withoutProtocol.indexOf("/");
135
134
  if (firstSlash > 0) {
136
135
  resolvedBucket = withoutProtocol.substring(0, firstSlash);
137
136
  resolvedPath = withoutProtocol.substring(firstSlash + 1);
@@ -159,9 +158,9 @@ export class S3StorageController implements StorageController {
159
158
  const metadata: DownloadMetadata = {
160
159
  bucket: resolvedBucket,
161
160
  fullPath: resolvedPath,
162
- name: resolvedPath.split('/').pop() || resolvedPath,
161
+ name: resolvedPath.split("/").pop() || resolvedPath,
163
162
  size: headResult.ContentLength || 0,
164
- contentType: headResult.ContentType || 'application/octet-stream',
163
+ contentType: headResult.ContentType || "application/octet-stream",
165
164
  customMetadata: headResult.Metadata || {}
166
165
  };
167
166
 
@@ -171,7 +170,7 @@ export class S3StorageController implements StorageController {
171
170
  };
172
171
  } catch (error: unknown) {
173
172
  const s3Error = error as { name?: string; $metadata?: { httpStatusCode?: number } };
174
- if (s3Error.name === 'NotFound' || s3Error.$metadata?.httpStatusCode === 404) {
173
+ if (s3Error.name === "NotFound" || s3Error.$metadata?.httpStatusCode === 404) {
175
174
  return {
176
175
  url: null,
177
176
  fileNotFound: true
@@ -181,14 +180,16 @@ export class S3StorageController implements StorageController {
181
180
  }
182
181
  }
183
182
 
184
- async getFile(storagePath: string, bucket?: string): Promise<File | null> {
185
- // Handle s3:// URLs
186
- let resolvedPath = storagePath;
183
+ async getObject(key: string, bucket?: string): Promise<File | null> {
184
+ // Handle s3:// and gs:// URLs
185
+ let resolvedPath = key;
187
186
  let resolvedBucket = this.getBucket(bucket);
188
187
 
189
- if (storagePath.startsWith('s3://')) {
190
- const withoutProtocol = storagePath.substring('s3://'.length);
191
- const firstSlash = withoutProtocol.indexOf('/');
188
+ const match = key.match(/^(s3|gs):\/\//);
189
+ if (match) {
190
+ const protocolLength = match[0].length;
191
+ const withoutProtocol = key.substring(protocolLength);
192
+ const firstSlash = withoutProtocol.indexOf("/");
192
193
  if (firstSlash > 0) {
193
194
  resolvedBucket = withoutProtocol.substring(0, firstSlash);
194
195
  resolvedPath = withoutProtocol.substring(firstSlash + 1);
@@ -215,28 +216,30 @@ export class S3StorageController implements StorageController {
215
216
  }
216
217
  const buffer = Buffer.concat(chunks);
217
218
 
218
- const contentType = response.ContentType || 'application/octet-stream';
219
- const fileName = resolvedPath.split('/').pop() || resolvedPath;
219
+ const contentType = response.ContentType || "application/octet-stream";
220
+ const fileName = resolvedPath.split("/").pop() || resolvedPath;
220
221
 
221
222
  const blob = new Blob([buffer], { type: contentType });
222
223
  return new File([blob], fileName, { type: contentType });
223
224
  } catch (error: unknown) {
224
225
  const s3Error = error as { name?: string; $metadata?: { httpStatusCode?: number } };
225
- if (s3Error.name === 'NoSuchKey' || s3Error.$metadata?.httpStatusCode === 404) {
226
+ if (s3Error.name === "NoSuchKey" || s3Error.$metadata?.httpStatusCode === 404) {
226
227
  return null;
227
228
  }
228
229
  throw error;
229
230
  }
230
231
  }
231
232
 
232
- async deleteFile(storagePath: string, bucket?: string): Promise<void> {
233
- // Handle s3:// URLs
234
- let resolvedPath = storagePath;
233
+ async deleteObject(key: string, bucket?: string): Promise<void> {
234
+ // Handle s3:// and gs:// URLs
235
+ let resolvedPath = key;
235
236
  let resolvedBucket = this.getBucket(bucket);
236
237
 
237
- if (storagePath.startsWith('s3://')) {
238
- const withoutProtocol = storagePath.substring('s3://'.length);
239
- const firstSlash = withoutProtocol.indexOf('/');
238
+ const match = key.match(/^(s3|gs):\/\//);
239
+ if (match) {
240
+ const protocolLength = match[0].length;
241
+ const withoutProtocol = key.substring(protocolLength);
242
+ const firstSlash = withoutProtocol.indexOf("/");
240
243
  if (firstSlash > 0) {
241
244
  resolvedBucket = withoutProtocol.substring(0, firstSlash);
242
245
  resolvedPath = withoutProtocol.substring(firstSlash + 1);
@@ -251,7 +254,7 @@ export class S3StorageController implements StorageController {
251
254
  await this.client.send(command);
252
255
  }
253
256
 
254
- async list(storagePath: string, options?: {
257
+ async listObjects(prefix: string, options?: {
255
258
  bucket?: string;
256
259
  maxResults?: number;
257
260
  pageToken?: string;
@@ -260,18 +263,18 @@ export class S3StorageController implements StorageController {
260
263
 
261
264
  const command = new ListObjectsV2Command({
262
265
  Bucket: resolvedBucket,
263
- Prefix: storagePath || undefined,
266
+ Prefix: prefix || undefined,
264
267
  MaxKeys: options?.maxResults ?? 1000,
265
268
  ContinuationToken: options?.pageToken,
266
- Delimiter: '/' // This gives us folder-like behavior
269
+ Delimiter: "/" // This gives us folder-like behavior
267
270
  });
268
271
 
269
272
  const response = await this.client.send(command);
270
273
 
271
274
  const items: StorageReference[] = (response.Contents || []).map(obj => ({
272
275
  bucket: resolvedBucket,
273
- fullPath: obj.Key || '',
274
- name: (obj.Key || '').split('/').pop() || '',
276
+ fullPath: obj.Key || "",
277
+ name: (obj.Key || "").split("/").pop() || "",
275
278
  parent: null as never,
276
279
  root: null as never,
277
280
  toString: () => `s3://${resolvedBucket}/${obj.Key}`
@@ -279,8 +282,8 @@ export class S3StorageController implements StorageController {
279
282
 
280
283
  const prefixes: StorageReference[] = (response.CommonPrefixes || []).map(prefix => ({
281
284
  bucket: resolvedBucket,
282
- fullPath: prefix.Prefix || '',
283
- name: (prefix.Prefix || '').replace(/\/$/, '').split('/').pop() || '',
285
+ fullPath: prefix.Prefix || "",
286
+ name: (prefix.Prefix || "").replace(/\/$/, "").split("/").pop() || "",
284
287
  parent: null as never,
285
288
  root: null as never,
286
289
  toString: () => `s3://${resolvedBucket}/${prefix.Prefix}`