@rebasepro/server-core 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build-errors.txt +52 -0
- package/dist/api/ast-schema-editor.d.ts +21 -0
- package/dist/api/collections_for_test/callbacks_test_collection.d.ts +2 -0
- package/dist/api/errors.d.ts +53 -0
- package/dist/api/graphql/graphql-schema-generator.d.ts +44 -0
- package/dist/api/graphql/index.d.ts +1 -0
- package/dist/api/index.d.ts +9 -0
- package/dist/api/logs-routes.d.ts +37 -0
- package/dist/api/openapi-generator.d.ts +16 -0
- package/dist/api/rest/api-generator.d.ts +76 -0
- package/dist/api/rest/index.d.ts +1 -0
- package/dist/api/rest/query-parser.d.ts +8 -0
- package/dist/api/schema-editor-routes.d.ts +3 -0
- package/dist/api/server.d.ts +40 -0
- package/dist/api/types.d.ts +104 -0
- package/dist/auth/adapter-middleware.d.ts +43 -0
- package/dist/auth/admin-roles-route.d.ts +18 -0
- package/dist/auth/admin-user-ops.d.ts +79 -0
- package/dist/auth/admin-users-route.d.ts +28 -0
- package/dist/auth/api-keys/api-key-middleware.d.ts +39 -0
- package/dist/auth/api-keys/api-key-permission-guard.d.ts +32 -0
- package/dist/auth/api-keys/api-key-routes.d.ts +20 -0
- package/dist/auth/api-keys/api-key-store.d.ts +35 -0
- package/dist/auth/api-keys/api-key-types.d.ts +96 -0
- package/dist/auth/api-keys/index.d.ts +17 -0
- package/dist/auth/apple-oauth.d.ts +30 -0
- package/dist/auth/auth-hooks.d.ts +242 -0
- package/dist/auth/bitbucket-oauth.d.ts +11 -0
- package/dist/auth/builtin-auth-adapter.d.ts +51 -0
- package/dist/auth/crypto-utils.d.ts +16 -0
- package/dist/auth/custom-auth-adapter.d.ts +39 -0
- package/dist/auth/discord-oauth.d.ts +14 -0
- package/dist/auth/facebook-oauth.d.ts +14 -0
- package/dist/auth/github-oauth.d.ts +15 -0
- package/dist/auth/gitlab-oauth.d.ts +13 -0
- package/dist/auth/google-oauth.d.ts +47 -0
- package/dist/auth/index.d.ts +37 -0
- package/dist/auth/interfaces.d.ts +431 -0
- package/dist/auth/jwt.d.ts +55 -0
- package/dist/auth/linkedin-oauth.d.ts +18 -0
- package/dist/auth/magic-link-routes.d.ts +30 -0
- package/dist/auth/mfa-crypto.d.ts +23 -0
- package/dist/auth/mfa-routes.d.ts +7 -0
- package/dist/auth/mfa.d.ts +49 -0
- package/dist/auth/microsoft-oauth.d.ts +16 -0
- package/dist/auth/middleware.d.ts +113 -0
- package/dist/auth/password.d.ts +22 -0
- package/dist/auth/rate-limiter.d.ts +50 -0
- package/dist/auth/reset-password-admin.d.ts +29 -0
- package/dist/auth/rls-scope.d.ts +31 -0
- package/dist/auth/routes.d.ts +35 -0
- package/dist/auth/session-routes.d.ts +27 -0
- package/dist/auth/slack-oauth.d.ts +12 -0
- package/dist/auth/spotify-oauth.d.ts +12 -0
- package/dist/auth/twitter-oauth.d.ts +18 -0
- package/dist/backend-CIxN4FVm.js +15 -0
- package/dist/backend-CIxN4FVm.js.map +1 -0
- package/dist/base64-js-C_frYBkI.js +1687 -0
- package/dist/base64-js-C_frYBkI.js.map +1 -0
- package/dist/chunk-Dze3rakg.js +42 -0
- package/dist/collections/BackendCollectionRegistry.d.ts +13 -0
- package/dist/collections/loader.d.ts +5 -0
- package/dist/cron/cron-loader.d.ts +17 -0
- package/dist/cron/cron-routes.d.ts +14 -0
- package/dist/cron/cron-scheduler.d.ts +106 -0
- package/dist/cron/cron-store.d.ts +32 -0
- package/dist/cron/index.d.ts +6 -0
- package/dist/db/interfaces.d.ts +18 -0
- package/dist/dist-DMO-zF6D.js +832 -0
- package/dist/dist-DMO-zF6D.js.map +1 -0
- package/dist/email/index.d.ts +6 -0
- package/dist/email/smtp-email-service.d.ts +25 -0
- package/dist/email/templates.d.ts +50 -0
- package/dist/email/types.d.ts +126 -0
- package/dist/env.d.ts +102 -0
- package/dist/from-VbwD7xRf.js +3849 -0
- package/dist/from-VbwD7xRf.js.map +1 -0
- package/dist/functions/define-function.d.ts +50 -0
- package/dist/functions/function-loader.d.ts +17 -0
- package/dist/functions/function-routes.d.ts +10 -0
- package/dist/functions/index.d.ts +5 -0
- package/dist/history/history-routes.d.ts +23 -0
- package/dist/history/index.d.ts +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.es.js +61322 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.umd.js +103633 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/init/docs.d.ts +4 -0
- package/dist/init/health.d.ts +2 -0
- package/dist/init/middlewares.d.ts +10 -0
- package/dist/init/shutdown.d.ts +11 -0
- package/dist/init/storage.d.ts +5 -0
- package/dist/init.d.ts +314 -0
- package/dist/jws-BqRRaK11.js +616 -0
- package/dist/jws-BqRRaK11.js.map +1 -0
- package/dist/jwt-BETC8a1J.js +3560 -0
- package/dist/jwt-BETC8a1J.js.map +1 -0
- package/dist/logger-BYU66ENZ.js +94 -0
- package/dist/logger-BYU66ENZ.js.map +1 -0
- package/dist/ms-BeBSuOXN.js +125 -0
- package/dist/ms-BeBSuOXN.js.map +1 -0
- package/dist/multipart-parser-CedBDOeC.js +299 -0
- package/dist/multipart-parser-CedBDOeC.js.map +1 -0
- package/dist/serve-spa.d.ts +30 -0
- package/dist/services/driver-registry.d.ts +78 -0
- package/dist/services/routed-realtime-service.d.ts +43 -0
- package/dist/services/webhook-service.d.ts +29 -0
- package/dist/singleton.d.ts +35 -0
- package/dist/src-CB2PIpBe.js +1182 -0
- package/dist/src-CB2PIpBe.js.map +1 -0
- package/dist/src-DjzOT1kG.js +29746 -0
- package/dist/src-DjzOT1kG.js.map +1 -0
- package/dist/storage/GCSStorageController.d.ts +43 -0
- package/dist/storage/LocalStorageController.d.ts +46 -0
- package/dist/storage/S3StorageController.d.ts +36 -0
- package/dist/storage/image-transform.d.ts +50 -0
- package/dist/storage/index.d.ts +31 -0
- package/dist/storage/routes.d.ts +70 -0
- package/dist/storage/storage-registry.d.ts +78 -0
- package/dist/storage/tus-handler.d.ts +53 -0
- package/dist/storage/types.d.ts +128 -0
- package/dist/types/index.d.ts +11 -0
- package/dist/utils/dev-port.d.ts +37 -0
- package/dist/utils/logger.d.ts +31 -0
- package/dist/utils/logging.d.ts +9 -0
- package/dist/utils/request-id.d.ts +4 -0
- package/dist/utils/request-logger.d.ts +19 -0
- package/dist/utils/sql.d.ts +27 -0
- package/package.json +22 -14
- package/src/api/graphql/graphql-schema-generator.ts +5 -5
- package/src/api/openapi-generator.ts +2 -2
- package/src/api/rest/api-generator.ts +44 -123
- package/src/api/rest/query-parser.ts +6 -23
- package/src/api/types.ts +2 -2
- package/src/auth/auth-hooks.ts +1 -1
- package/src/auth/builtin-auth-adapter.ts +1 -2
- package/src/auth/interfaces.ts +11 -2
- package/src/auth/middleware.ts +4 -4
- package/src/auth/routes.ts +1 -2
- package/src/functions/define-function.ts +59 -0
- package/src/functions/function-loader.ts +16 -0
- package/src/functions/index.ts +2 -0
- package/src/index.ts +70 -37
- package/src/init.ts +78 -21
- package/src/storage/GCSStorageController.ts +334 -0
- package/src/storage/index.ts +9 -3
- package/src/storage/routes.ts +191 -23
- package/src/storage/tus-handler.ts +16 -4
- package/src/storage/types.ts +25 -3
- package/test/api-generator.test.ts +1 -1
- package/test/auth-config-types.test.ts +40 -0
- package/test/define-function.test.ts +45 -0
- package/test/storage-routes.test.ts +160 -0
- package/test/backend-hooks-data.test.ts +0 -477
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google Cloud Storage controller (works with GCS and Firebase Storage)
|
|
3
|
+
*
|
|
4
|
+
* The `@google-cloud/storage` package is loaded lazily so it remains an
|
|
5
|
+
* optional peer dependency — users who don't need GCS never pay for the
|
|
6
|
+
* import cost or need it installed.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
StorageController,
|
|
11
|
+
GCSStorageConfig,
|
|
12
|
+
DEFAULT_MAX_FILE_SIZE
|
|
13
|
+
} from "./types";
|
|
14
|
+
import {
|
|
15
|
+
UploadFileProps,
|
|
16
|
+
UploadFileResult,
|
|
17
|
+
DownloadConfig,
|
|
18
|
+
DownloadMetadata,
|
|
19
|
+
StorageListResult,
|
|
20
|
+
StorageReference
|
|
21
|
+
} from "@rebasepro/types";
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Lazy import of @google-cloud/storage
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
let gcsModule: typeof import("@google-cloud/storage") | null = null;
|
|
28
|
+
|
|
29
|
+
async function loadGCS(): Promise<typeof import("@google-cloud/storage")> {
|
|
30
|
+
if (gcsModule) return gcsModule;
|
|
31
|
+
try {
|
|
32
|
+
gcsModule = await import("@google-cloud/storage");
|
|
33
|
+
return gcsModule;
|
|
34
|
+
} catch {
|
|
35
|
+
throw new Error(
|
|
36
|
+
"Google Cloud Storage is required for the GCS storage controller. " +
|
|
37
|
+
"Install it with: pnpm add @google-cloud/storage"
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// GCSStorageController
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Google Cloud Storage implementation of `StorageController`.
|
|
48
|
+
*
|
|
49
|
+
* Works with standard GCS buckets **and** Firebase Storage buckets
|
|
50
|
+
* (e.g. `"my-project.appspot.com"`).
|
|
51
|
+
*/
|
|
52
|
+
export class GCSStorageController implements StorageController {
|
|
53
|
+
private config: GCSStorageConfig;
|
|
54
|
+
private client: InstanceType<typeof import("@google-cloud/storage").Storage> | null = null;
|
|
55
|
+
|
|
56
|
+
constructor(config: GCSStorageConfig) {
|
|
57
|
+
this.config = config;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ------------------------------------------------------------------
|
|
61
|
+
// Lazy client initialisation
|
|
62
|
+
// ------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
private async getClient(): Promise<InstanceType<typeof import("@google-cloud/storage").Storage>> {
|
|
65
|
+
if (this.client) return this.client;
|
|
66
|
+
const { Storage } = await loadGCS();
|
|
67
|
+
this.client = new Storage({
|
|
68
|
+
projectId: this.config.projectId,
|
|
69
|
+
keyFilename: this.config.keyFilename,
|
|
70
|
+
credentials: this.config.credentials
|
|
71
|
+
});
|
|
72
|
+
return this.client;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ------------------------------------------------------------------
|
|
76
|
+
// StorageController interface
|
|
77
|
+
// ------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
getType(): "gcs" {
|
|
80
|
+
return "gcs";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async putObject({
|
|
84
|
+
file,
|
|
85
|
+
key,
|
|
86
|
+
metadata,
|
|
87
|
+
bucket
|
|
88
|
+
}: UploadFileProps): Promise<UploadFileResult> {
|
|
89
|
+
this.validateFile(file);
|
|
90
|
+
|
|
91
|
+
const usedBucket = this.getBucket(bucket);
|
|
92
|
+
const client = await this.getClient();
|
|
93
|
+
const gcsFile = client.bucket(usedBucket).file(key);
|
|
94
|
+
|
|
95
|
+
// Convert File to Buffer
|
|
96
|
+
const arrayBuffer = await file.arrayBuffer();
|
|
97
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
98
|
+
|
|
99
|
+
await gcsFile.save(buffer, {
|
|
100
|
+
contentType: file.type,
|
|
101
|
+
metadata: metadata ? { metadata: this.flattenMetadata(metadata) } : undefined
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
key,
|
|
106
|
+
bucket: usedBucket,
|
|
107
|
+
storageUrl: `gs://${usedBucket}/${key}`
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async getSignedUrl(key: string, bucket?: string): Promise<DownloadConfig> {
|
|
112
|
+
// Handle gs:// and s3:// URLs
|
|
113
|
+
let resolvedPath = key;
|
|
114
|
+
let resolvedBucket = this.getBucket(bucket);
|
|
115
|
+
|
|
116
|
+
const match = key.match(/^(gs|s3):\/\//);
|
|
117
|
+
if (match) {
|
|
118
|
+
const protocolLength = match[0].length;
|
|
119
|
+
const withoutProtocol = key.substring(protocolLength);
|
|
120
|
+
const firstSlash = withoutProtocol.indexOf("/");
|
|
121
|
+
if (firstSlash > 0) {
|
|
122
|
+
resolvedBucket = withoutProtocol.substring(0, firstSlash);
|
|
123
|
+
resolvedPath = withoutProtocol.substring(firstSlash + 1);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
const client = await this.getClient();
|
|
129
|
+
const gcsFile = client.bucket(resolvedBucket).file(resolvedPath);
|
|
130
|
+
|
|
131
|
+
// Get metadata first to check existence and populate response
|
|
132
|
+
const [fileMetadata] = await gcsFile.getMetadata();
|
|
133
|
+
|
|
134
|
+
// Generate a signed URL
|
|
135
|
+
const expiresIn = this.config.signedUrlExpiration ?? 3600;
|
|
136
|
+
const [url] = await gcsFile.getSignedUrl({
|
|
137
|
+
action: "read",
|
|
138
|
+
expires: Date.now() + expiresIn * 1000
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const metadata: DownloadMetadata = {
|
|
142
|
+
bucket: resolvedBucket,
|
|
143
|
+
fullPath: resolvedPath,
|
|
144
|
+
name: resolvedPath.split("/").pop() || resolvedPath,
|
|
145
|
+
size: Number(fileMetadata.size) || 0,
|
|
146
|
+
contentType: fileMetadata.contentType || "application/octet-stream",
|
|
147
|
+
customMetadata: (fileMetadata.metadata as Record<string, string> | undefined) || {}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
url,
|
|
152
|
+
metadata
|
|
153
|
+
};
|
|
154
|
+
} catch (error: unknown) {
|
|
155
|
+
if (isNotFoundError(error)) {
|
|
156
|
+
return {
|
|
157
|
+
url: null,
|
|
158
|
+
fileNotFound: true
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async getObject(key: string, bucket?: string): Promise<File | null> {
|
|
166
|
+
// Handle gs:// and s3:// URLs
|
|
167
|
+
let resolvedPath = key;
|
|
168
|
+
let resolvedBucket = this.getBucket(bucket);
|
|
169
|
+
|
|
170
|
+
const match = key.match(/^(gs|s3):\/\//);
|
|
171
|
+
if (match) {
|
|
172
|
+
const protocolLength = match[0].length;
|
|
173
|
+
const withoutProtocol = key.substring(protocolLength);
|
|
174
|
+
const firstSlash = withoutProtocol.indexOf("/");
|
|
175
|
+
if (firstSlash > 0) {
|
|
176
|
+
resolvedBucket = withoutProtocol.substring(0, firstSlash);
|
|
177
|
+
resolvedPath = withoutProtocol.substring(firstSlash + 1);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
const client = await this.getClient();
|
|
183
|
+
const gcsFile = client.bucket(resolvedBucket).file(resolvedPath);
|
|
184
|
+
|
|
185
|
+
const [contents] = await gcsFile.download();
|
|
186
|
+
|
|
187
|
+
// Get content type from metadata
|
|
188
|
+
const [fileMetadata] = await gcsFile.getMetadata();
|
|
189
|
+
const contentType = fileMetadata.contentType || "application/octet-stream";
|
|
190
|
+
const fileName = resolvedPath.split("/").pop() || resolvedPath;
|
|
191
|
+
|
|
192
|
+
const blob = new Blob([new Uint8Array(contents)], { type: contentType });
|
|
193
|
+
return new File([blob], fileName, { type: contentType });
|
|
194
|
+
} catch (error: unknown) {
|
|
195
|
+
if (isNotFoundError(error)) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
throw error;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async deleteObject(key: string, bucket?: string): Promise<void> {
|
|
203
|
+
// Handle gs:// and s3:// URLs
|
|
204
|
+
let resolvedPath = key;
|
|
205
|
+
let resolvedBucket = this.getBucket(bucket);
|
|
206
|
+
|
|
207
|
+
const match = key.match(/^(gs|s3):\/\//);
|
|
208
|
+
if (match) {
|
|
209
|
+
const protocolLength = match[0].length;
|
|
210
|
+
const withoutProtocol = key.substring(protocolLength);
|
|
211
|
+
const firstSlash = withoutProtocol.indexOf("/");
|
|
212
|
+
if (firstSlash > 0) {
|
|
213
|
+
resolvedBucket = withoutProtocol.substring(0, firstSlash);
|
|
214
|
+
resolvedPath = withoutProtocol.substring(firstSlash + 1);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const client = await this.getClient();
|
|
219
|
+
const gcsFile = client.bucket(resolvedBucket).file(resolvedPath);
|
|
220
|
+
|
|
221
|
+
await gcsFile.delete({ ignoreNotFound: true });
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async listObjects(prefix: string, options?: {
|
|
225
|
+
bucket?: string;
|
|
226
|
+
maxResults?: number;
|
|
227
|
+
pageToken?: string;
|
|
228
|
+
}): Promise<StorageListResult> {
|
|
229
|
+
const resolvedBucket = this.getBucket(options?.bucket);
|
|
230
|
+
const client = await this.getClient();
|
|
231
|
+
const gcsBucket = client.bucket(resolvedBucket);
|
|
232
|
+
|
|
233
|
+
// Fetch files (objects) matching the prefix
|
|
234
|
+
const [files, , filesApiResponse] = await gcsBucket.getFiles({
|
|
235
|
+
prefix: prefix || undefined,
|
|
236
|
+
delimiter: "/",
|
|
237
|
+
maxResults: options?.maxResults ?? 1000,
|
|
238
|
+
pageToken: options?.pageToken,
|
|
239
|
+
autoPaginate: false
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const items: StorageReference[] = files.map(file => ({
|
|
243
|
+
bucket: resolvedBucket,
|
|
244
|
+
fullPath: file.name,
|
|
245
|
+
name: file.name.split("/").pop() || "",
|
|
246
|
+
parent: null as never,
|
|
247
|
+
root: null as never,
|
|
248
|
+
toString: () => `gs://${resolvedBucket}/${file.name}`
|
|
249
|
+
}));
|
|
250
|
+
|
|
251
|
+
// Common prefixes (folder-like entries) come from the API response
|
|
252
|
+
const apiPrefixes: string[] =
|
|
253
|
+
(filesApiResponse as Record<string, unknown> | undefined)?.prefixes as string[] ?? [];
|
|
254
|
+
|
|
255
|
+
const prefixes: StorageReference[] = apiPrefixes.map(p => ({
|
|
256
|
+
bucket: resolvedBucket,
|
|
257
|
+
fullPath: p,
|
|
258
|
+
name: p.replace(/\/$/, "").split("/").pop() || "",
|
|
259
|
+
parent: null as never,
|
|
260
|
+
root: null as never,
|
|
261
|
+
toString: () => `gs://${resolvedBucket}/${p}`
|
|
262
|
+
}));
|
|
263
|
+
|
|
264
|
+
// The next page token is returned on the query object (second element)
|
|
265
|
+
const nextPageToken =
|
|
266
|
+
(filesApiResponse as Record<string, unknown> | undefined)?.nextPageToken as string | undefined;
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
items,
|
|
270
|
+
prefixes,
|
|
271
|
+
nextPageToken
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ------------------------------------------------------------------
|
|
276
|
+
// Private helpers
|
|
277
|
+
// ------------------------------------------------------------------
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Validate file before upload
|
|
281
|
+
*/
|
|
282
|
+
private validateFile(file: File): void {
|
|
283
|
+
const maxSize = this.config.maxFileSize ?? DEFAULT_MAX_FILE_SIZE;
|
|
284
|
+
if (file.size > maxSize) {
|
|
285
|
+
throw new Error(`File size ${file.size} exceeds maximum allowed size ${maxSize}`);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (this.config.allowedMimeTypes && this.config.allowedMimeTypes.length > 0) {
|
|
289
|
+
if (!this.config.allowedMimeTypes.includes(file.type)) {
|
|
290
|
+
throw new Error(`File type ${file.type} is not allowed. Allowed types: ${this.config.allowedMimeTypes.join(", ")}`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Get the bucket name — either from parameter or config.
|
|
297
|
+
*/
|
|
298
|
+
private getBucket(bucket?: string): string {
|
|
299
|
+
// "default" is a logical bucket name used by local storage;
|
|
300
|
+
// for GCS it should resolve to the configured bucket.
|
|
301
|
+
if (!bucket || bucket === "default") return this.config.bucket;
|
|
302
|
+
return bucket;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Flatten nested metadata to string values (GCS custom metadata requirement)
|
|
307
|
+
*/
|
|
308
|
+
private flattenMetadata(metadata: Record<string, unknown>): Record<string, string> {
|
|
309
|
+
const flattened: Record<string, string> = {};
|
|
310
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
311
|
+
if (typeof value === "string") {
|
|
312
|
+
flattened[key] = value;
|
|
313
|
+
} else if (value !== undefined && value !== null) {
|
|
314
|
+
flattened[key] = JSON.stringify(value);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return flattened;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// ---------------------------------------------------------------------------
|
|
322
|
+
// Error helpers
|
|
323
|
+
// ---------------------------------------------------------------------------
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Check whether an error represents a 404 / Not Found from the GCS client.
|
|
327
|
+
*/
|
|
328
|
+
function isNotFoundError(error: unknown): boolean {
|
|
329
|
+
if (typeof error !== "object" || error === null) return false;
|
|
330
|
+
const err = error as Record<string, unknown>;
|
|
331
|
+
if (err["code"] === 404) return true;
|
|
332
|
+
if (typeof err["message"] === "string" && /not found/i.test(err["message"])) return true;
|
|
333
|
+
return false;
|
|
334
|
+
}
|
package/src/storage/index.ts
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Storage module for Rebase backend
|
|
3
3
|
*
|
|
4
|
-
* Provides pluggable file storage with
|
|
4
|
+
* Provides pluggable file storage with three built-in providers:
|
|
5
5
|
* - **Local filesystem** — zero config, great for dev and single-server deployments.
|
|
6
6
|
* - **S3-compatible** — works with AWS S3, Cloudflare R2, MinIO, Hetzner Object Storage,
|
|
7
7
|
* Backblaze B2, DigitalOcean Spaces, and GCS (via S3 interop).
|
|
8
|
+
* - **Google Cloud Storage / Firebase Storage** — native GCS support via `@google-cloud/storage`
|
|
9
|
+
* (optional peer dependency, lazily loaded).
|
|
8
10
|
*
|
|
9
|
-
* For other providers (
|
|
11
|
+
* For other providers (Azure Blob, etc.), implement the
|
|
10
12
|
* `StorageController` interface and pass the instance directly to the `storage` config.
|
|
11
13
|
*/
|
|
12
14
|
|
|
13
15
|
export * from "./types";
|
|
14
16
|
export { LocalStorageController } from "./LocalStorageController";
|
|
15
17
|
export { S3StorageController } from "./S3StorageController";
|
|
18
|
+
export { GCSStorageController } from "./GCSStorageController";
|
|
16
19
|
export { createStorageRoutes } from "./routes";
|
|
17
20
|
export type { StorageRoutesConfig } from "./routes";
|
|
18
21
|
export * from "./storage-registry";
|
|
@@ -23,6 +26,7 @@ export { TusHandler } from "./tus-handler";
|
|
|
23
26
|
import { BackendStorageConfig, StorageController } from "./types";
|
|
24
27
|
import { LocalStorageController } from "./LocalStorageController";
|
|
25
28
|
import { S3StorageController } from "./S3StorageController";
|
|
29
|
+
import { GCSStorageController } from "./GCSStorageController";
|
|
26
30
|
|
|
27
31
|
/**
|
|
28
32
|
* Create a storage controller from a config object.
|
|
@@ -36,10 +40,12 @@ export function createStorageController(config: BackendStorageConfig): StorageCo
|
|
|
36
40
|
return new LocalStorageController(config);
|
|
37
41
|
case "s3":
|
|
38
42
|
return new S3StorageController(config);
|
|
43
|
+
case "gcs":
|
|
44
|
+
return new GCSStorageController(config);
|
|
39
45
|
default:
|
|
40
46
|
throw new Error(
|
|
41
47
|
`Unknown storage type: ${(config as Record<string, unknown>).type}. ` +
|
|
42
|
-
"Built-in types: local, s3. " +
|
|
48
|
+
"Built-in types: local, s3, gcs. " +
|
|
43
49
|
"For other providers, implement the StorageController interface directly."
|
|
44
50
|
);
|
|
45
51
|
}
|