@rebasepro/server 0.24.0 → 0.24.1-canary.g56af4a4
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/dist/GCSStorageController-DivE9bAx.js +402 -0
- package/dist/GCSStorageController-DivE9bAx.js.map +1 -0
- package/dist/S3StorageController-D_vKs4kP.js +480 -0
- package/dist/S3StorageController-D_vKs4kP.js.map +1 -0
- package/dist/api/rest/api-generator.d.ts +53 -1
- package/dist/api/rest/auth-collection-writes.d.ts +28 -21
- package/dist/api/rest/field-access-query.d.ts +28 -8
- package/dist/api/rest/field-ops.d.ts +19 -7
- package/dist/api/rest/query-body.d.ts +10 -0
- package/dist/api/rest/write-validation.d.ts +7 -5
- package/dist/api/schema-editor-routes.d.ts +10 -1
- package/dist/api/sql-export-routes.d.ts +8 -0
- package/dist/api/types.d.ts +11 -0
- package/dist/{ast-schema-editor-CWqS_sLJ.js → ast-schema-editor-CSdL0iCk.js} +2 -2
- package/dist/{ast-schema-editor-CWqS_sLJ.js.map → ast-schema-editor-CSdL0iCk.js.map} +1 -1
- package/dist/auth/access.d.ts +16 -0
- package/dist/auth/admin-user-ops.d.ts +24 -2
- package/dist/auth/admin-users-route.d.ts +1 -0
- package/dist/auth/expired-token-sweep.d.ts +5 -1
- package/dist/auth/interfaces.d.ts +10 -0
- package/dist/auth/jwt-crypto.d.ts +7 -0
- package/dist/auth/jwt-keys.d.ts +4 -0
- package/dist/auth/jwt.d.ts +6 -1
- package/dist/auth/rate-limiter.d.ts +13 -1
- package/dist/auth/registration-policy.d.ts +17 -0
- package/dist/auth/user-picking.d.ts +35 -0
- package/dist/{auth-DMLngxn_.js → auth-OPq_FeMs.js} +972 -85
- package/dist/auth-OPq_FeMs.js.map +1 -0
- package/dist/auth_adapter-CIItCTc8.js +21 -0
- package/dist/auth_adapter-CIItCTc8.js.map +1 -0
- package/dist/backend-DTAOsLQc.js.map +1 -1
- package/dist/backup/backup-common.d.ts +14 -3
- package/dist/backup/backup-routes.d.ts +23 -2
- package/dist/backup/backup-storage.d.ts +3 -1
- package/dist/backup/index.d.ts +3 -1
- package/dist/{backup-CN0s50D2.js → backup-5d7XhLX3.js} +170 -33
- package/dist/backup-5d7XhLX3.js.map +1 -0
- package/dist/boot/driver.d.ts +3 -0
- package/dist/boot/env.d.ts +7 -0
- package/dist/boot/http-server.d.ts +22 -0
- package/dist/boot/options.d.ts +16 -1
- package/dist/boot/runtime-cors.d.ts +17 -0
- package/dist/boot/static-routing.d.ts +9 -0
- package/dist/{contract-routes-fz8i4pxs.js → contract-routes-02lASyJI.js} +3 -19
- package/dist/contract-routes-02lASyJI.js.map +1 -0
- package/dist/cron/cron-routes.d.ts +1 -1
- package/dist/cron/cron-scheduler.d.ts +69 -3
- package/dist/cron/cron-store.d.ts +27 -4
- package/dist/cron/define-cron.d.ts +6 -2
- package/dist/cron/index.d.ts +1 -1
- package/dist/{cron-loader-CwaANlOG.js → cron-loader-D7OmleAb.js} +8 -4
- package/dist/cron-loader-D7OmleAb.js.map +1 -0
- package/dist/{cron-routes-Bc-SB0Se.js → cron-routes-CBfiaqEL.js} +27 -4
- package/dist/cron-routes-CBfiaqEL.js.map +1 -0
- package/dist/{cron-scheduler-CYQgco86.js → cron-scheduler-C2vbETMo.js} +298 -32
- package/dist/cron-scheduler-C2vbETMo.js.map +1 -0
- package/dist/cron-store-B2Ih0gCN.js +540 -0
- package/dist/cron-store-B2Ih0gCN.js.map +1 -0
- package/dist/email/smtp-email-service.d.ts +23 -0
- package/dist/email/types.d.ts +18 -0
- package/dist/env.d.ts +0 -37
- package/dist/filter-dialect-REl-TjZr.js +1628 -0
- package/dist/filter-dialect-REl-TjZr.js.map +1 -0
- package/dist/{function-loader-D7o5Epjj.js → function-loader-BDKKiBmy.js} +4 -3
- package/dist/function-loader-BDKKiBmy.js.map +1 -0
- package/dist/{function-routes-CaNG4waN.js → function-routes-DWB7BBsi.js} +2 -2
- package/dist/function-routes-DWB7BBsi.js.map +1 -0
- package/dist/functions/context.d.ts +9 -0
- package/dist/functions/define-function.d.ts +7 -4
- package/dist/functions/index.d.ts +1 -1
- package/dist/functions/index.js +25 -2
- package/dist/functions/index.js.map +1 -1
- package/dist/functions/request-timeout.d.ts +7 -5
- package/dist/history/history-routes.d.ts +9 -0
- package/dist/history/retention-sweep.d.ts +81 -0
- package/dist/{history-recorder-Nr8zLvoU.js → history-recorder-DHenr3lm.js} +2 -2
- package/dist/{history-recorder-Nr8zLvoU.js.map → history-recorder-DHenr3lm.js.map} +1 -1
- package/dist/{history-store-rcAm_xFR.js → history-store-DPofGVDp.js} +2 -2
- package/dist/{history-store-rcAm_xFR.js.map → history-store-DPofGVDp.js.map} +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.es.js +22671 -19191
- package/dist/index.es.js.map +1 -1
- package/dist/init/shutdown.d.ts +12 -2
- package/dist/init.d.ts +16 -6
- package/dist/internal-tables-L2t39RdT.js +136 -0
- package/dist/internal-tables-L2t39RdT.js.map +1 -0
- package/dist/jobs/job-queue.d.ts +8 -0
- package/dist/jobs/job-store.d.ts +24 -0
- package/dist/jobs/types.d.ts +25 -4
- package/dist/{jobs-DqYNfquG.js → jobs-BpD4Q0qR.js} +232 -22
- package/dist/jobs-BpD4Q0qR.js.map +1 -0
- package/dist/{jwt-R6bSPMjk.js → jwt-Bqcu7cGt.js} +171 -65
- package/dist/jwt-Bqcu7cGt.js.map +1 -0
- package/dist/load-failure-UvzZgGXT.js +48 -0
- package/dist/load-failure-UvzZgGXT.js.map +1 -0
- package/dist/mcp/mcp-tools.d.ts +8 -0
- package/dist/metrics/index.d.ts +18 -0
- package/dist/metrics/pool-wait.d.ts +50 -0
- package/dist/offline-8Ks3yp9n-CadBKm2D.js +3305 -0
- package/dist/offline-8Ks3yp9n-CadBKm2D.js.map +1 -0
- package/dist/offline-store-D9ytNNaa-BCr2vsFB.js +1903 -0
- package/dist/offline-store-D9ytNNaa-BCr2vsFB.js.map +1 -0
- package/dist/{openapi-generator-DAq_XVDu.js → openapi-generator-BqoUh4Ij.js} +365 -81
- package/dist/openapi-generator-BqoUh4Ij.js.map +1 -0
- package/dist/policy-PdzX-Opd.js +121 -0
- package/dist/policy-PdzX-Opd.js.map +1 -0
- package/dist/project_manifest-R6y_KHms.js +59 -0
- package/dist/project_manifest-R6y_KHms.js.map +1 -0
- package/dist/{query-parser-BgiKJKvc.js → query-parser-DK9eMkb9.js} +510 -15
- package/dist/query-parser-DK9eMkb9.js.map +1 -0
- package/dist/{request-timeout-DgH7j8qO.js → request-timeout-Dnl2_OBc.js} +15 -8
- package/dist/request-timeout-Dnl2_OBc.js.map +1 -0
- package/dist/resumable-upload-threshold-CKv-Ry0l.js +24 -0
- package/dist/resumable-upload-threshold-CKv-Ry0l.js.map +1 -0
- package/dist/schema-edit/apply-schema-change.d.ts +10 -0
- package/dist/schema-edit/local-source.d.ts +29 -0
- package/dist/schema-edit/remote-source.d.ts +13 -3
- package/dist/schema-edit/source-editor.d.ts +28 -0
- package/dist/{schema-editor-routes-oIyuWl3L.js → schema-editor-routes-BFf3i8jm.js} +53 -9
- package/dist/schema-editor-routes-BFf3i8jm.js.map +1 -0
- package/dist/services/outbound-post.d.ts +50 -0
- package/dist/services/outbound-url-guard.d.ts +34 -5
- package/dist/services/routed-realtime-service.d.ts +5 -0
- package/dist/services/webhook-service.d.ts +66 -14
- package/dist/soft-delete-params-47yBGs3_.js +668 -0
- package/dist/soft-delete-params-47yBGs3_.js.map +1 -0
- package/dist/sql-export-routes-BNNuGGLP.js +240 -0
- package/dist/sql-export-routes-BNNuGGLP.js.map +1 -0
- package/dist/src-CatHFUym.js.map +1 -1
- package/dist/storage/GCSStorageController.d.ts +46 -2
- package/dist/storage/LocalStorageController.d.ts +67 -2
- package/dist/storage/S3StorageController.d.ts +37 -3
- package/dist/storage/image-transform.d.ts +5 -0
- package/dist/storage/maintenance-routes.d.ts +23 -0
- package/dist/storage/maintenance.d.ts +126 -0
- package/dist/storage/rendition-cache.d.ts +9 -11
- package/dist/storage/request-principals.d.ts +30 -0
- package/dist/storage/routes.d.ts +20 -0
- package/dist/storage/source-variables.d.ts +10 -0
- package/dist/storage/streams.d.ts +42 -0
- package/dist/storage/tus-handler.d.ts +46 -2
- package/dist/storage/types.d.ts +82 -7
- package/dist/storage/upload-validation.d.ts +13 -0
- package/dist/storage-resumable-VO-cMpse-C_YX71pO.js +125 -0
- package/dist/storage-resumable-VO-cMpse-C_YX71pO.js.map +1 -0
- package/dist/strings.es-BeZWAodn.js +256 -0
- package/dist/strings.es-BeZWAodn.js.map +1 -0
- package/dist/upload-validation-Dd-fnsbA.js +172 -0
- package/dist/upload-validation-Dd-fnsbA.js.map +1 -0
- package/dist/utils/byte-size.d.ts +10 -0
- package/dist/utils/compression.d.ts +6 -0
- package/dist/utils/load-failure.d.ts +14 -0
- package/package.json +5 -5
- package/dist/GCSStorageController-BSiP1c-f.js +0 -256
- package/dist/GCSStorageController-BSiP1c-f.js.map +0 -1
- package/dist/S3StorageController-CAwFRgjV.js +0 -276
- package/dist/S3StorageController-CAwFRgjV.js.map +0 -1
- package/dist/auth-DMLngxn_.js.map +0 -1
- package/dist/backup-CN0s50D2.js.map +0 -1
- package/dist/contract-routes-fz8i4pxs.js.map +0 -1
- package/dist/cron-loader-CwaANlOG.js.map +0 -1
- package/dist/cron-routes-Bc-SB0Se.js.map +0 -1
- package/dist/cron-scheduler-CYQgco86.js.map +0 -1
- package/dist/cron-store-D2Q9-Aco.js +0 -300
- package/dist/cron-store-D2Q9-Aco.js.map +0 -1
- package/dist/dynamic-import-X40pTZUQ.js +0 -23
- package/dist/dynamic-import-X40pTZUQ.js.map +0 -1
- package/dist/function-loader-D7o5Epjj.js.map +0 -1
- package/dist/function-routes-CaNG4waN.js.map +0 -1
- package/dist/jobs-DqYNfquG.js.map +0 -1
- package/dist/jwt-R6bSPMjk.js.map +0 -1
- package/dist/openapi-generator-DAq_XVDu.js.map +0 -1
- package/dist/query-parser-BgiKJKvc.js.map +0 -1
- package/dist/request-timeout-DgH7j8qO.js.map +0 -1
- package/dist/schema-editor-routes-oIyuWl3L.js.map +0 -1
- package/dist/soft-delete-params-BWPilMPF.js +0 -59
- package/dist/soft-delete-params-BWPilMPF.js.map +0 -1
- package/dist/src-I3aG1PcY.js +0 -6870
- package/dist/src-I3aG1PcY.js.map +0 -1
- package/dist/types-BfKcm9do.js +0 -39
- package/dist/types-BfKcm9do.js.map +0 -1
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
import { createRequire as __rebaseCreateRequire } from "module";
|
|
2
|
+
import __rebaseProcess from "process";
|
|
3
|
+
globalThis.process ??= __rebaseProcess;
|
|
4
|
+
__rebaseCreateRequire(import.meta.url);
|
|
5
|
+
import { n as __exportAll } from "./rolldown-runtime-dW7B1o5h.js";
|
|
6
|
+
import { t as ApiError } from "./errors-D6_y86c5.js";
|
|
7
|
+
import { c as listingPrefix, s as folderKey } from "./keys-GAVZqbqx.js";
|
|
8
|
+
import { n as bufferWebStream, o as webStreamFrom, r as bytesView, t as validateUploadedFile } from "./upload-validation-Dd-fnsbA.js";
|
|
9
|
+
//#region src/storage/GCSStorageController.ts
|
|
10
|
+
var GCSStorageController_exports = /* @__PURE__ */ __exportAll({ GCSStorageController: () => GCSStorageController });
|
|
11
|
+
var gcsModule = null;
|
|
12
|
+
async function loadGCS() {
|
|
13
|
+
if (gcsModule) return gcsModule;
|
|
14
|
+
try {
|
|
15
|
+
gcsModule = await import("@google-cloud/storage");
|
|
16
|
+
return gcsModule;
|
|
17
|
+
} catch {
|
|
18
|
+
throw new Error("Google Cloud Storage is required for the GCS storage controller. Install it with: pnpm add @google-cloud/storage");
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Google Cloud Storage implementation of `StorageController`.
|
|
23
|
+
*
|
|
24
|
+
* Works with standard GCS buckets **and** Firebase Storage buckets
|
|
25
|
+
* (e.g. `"my-project.appspot.com"`).
|
|
26
|
+
*/
|
|
27
|
+
var GCSStorageController = class {
|
|
28
|
+
config;
|
|
29
|
+
client = null;
|
|
30
|
+
constructor(config) {
|
|
31
|
+
this.config = config;
|
|
32
|
+
}
|
|
33
|
+
async getClient() {
|
|
34
|
+
if (this.client) return this.client;
|
|
35
|
+
const { Storage } = await loadGCS();
|
|
36
|
+
this.client = new Storage({
|
|
37
|
+
projectId: this.config.projectId,
|
|
38
|
+
keyFilename: this.config.keyFilename,
|
|
39
|
+
credentials: this.config.credentials
|
|
40
|
+
});
|
|
41
|
+
return this.client;
|
|
42
|
+
}
|
|
43
|
+
getType() {
|
|
44
|
+
return "gcs";
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The configured bucket, and the logical `default` that maps to it.
|
|
48
|
+
* @see S3StorageController.knownBuckets
|
|
49
|
+
*/
|
|
50
|
+
knownBuckets() {
|
|
51
|
+
return ["default", this.config.bucket];
|
|
52
|
+
}
|
|
53
|
+
async putObject({ file, key, metadata, bucket }, options) {
|
|
54
|
+
if (options?.uploadLimits !== false) this.validateFile(file);
|
|
55
|
+
const usedBucket = this.getBucket(bucket);
|
|
56
|
+
await (await this.getClient()).bucket(usedBucket).file(key).save(() => byteChunks(file), {
|
|
57
|
+
contentType: file.type,
|
|
58
|
+
metadata: metadata ? { metadata: this.flattenMetadata(metadata) } : void 0
|
|
59
|
+
}).catch((error) => {
|
|
60
|
+
throw misconfiguredOr(error, usedBucket);
|
|
61
|
+
});
|
|
62
|
+
return {
|
|
63
|
+
key,
|
|
64
|
+
bucket: usedBucket,
|
|
65
|
+
storageUrl: `gs://${usedBucket}/${key}`
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Read the object's metadata and nothing else — no signing.
|
|
70
|
+
*
|
|
71
|
+
* Signing needs a private key. With `keyFilename` or `credentials` the
|
|
72
|
+
* client has one; on Cloud Run, GKE or GCE it has only the metadata
|
|
73
|
+
* server's token, and falls back to the IAM Credentials `signBlob` API,
|
|
74
|
+
* which the runtime account may call on itself only when it holds
|
|
75
|
+
* `iam.serviceAccounts.signBlob` there. That is not a default grant.
|
|
76
|
+
*/
|
|
77
|
+
async getMetadata(key, bucket) {
|
|
78
|
+
let resolvedPath = key;
|
|
79
|
+
let resolvedBucket = this.getBucket(bucket);
|
|
80
|
+
const match = key.match(/^(gs|s3):\/\//);
|
|
81
|
+
if (match) {
|
|
82
|
+
const protocolLength = match[0].length;
|
|
83
|
+
const withoutProtocol = key.substring(protocolLength);
|
|
84
|
+
const firstSlash = withoutProtocol.indexOf("/");
|
|
85
|
+
if (firstSlash > 0) {
|
|
86
|
+
resolvedBucket = withoutProtocol.substring(0, firstSlash);
|
|
87
|
+
resolvedPath = withoutProtocol.substring(firstSlash + 1);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
const [fileMetadata] = await (await this.getClient()).bucket(resolvedBucket).file(resolvedPath).getMetadata();
|
|
92
|
+
return {
|
|
93
|
+
bucket: resolvedBucket,
|
|
94
|
+
fullPath: resolvedPath,
|
|
95
|
+
name: resolvedPath.split("/").pop() || resolvedPath,
|
|
96
|
+
size: Number(fileMetadata.size) || 0,
|
|
97
|
+
contentType: fileMetadata.contentType || "application/octet-stream",
|
|
98
|
+
customMetadata: fileMetadata.metadata || {}
|
|
99
|
+
};
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if (isNotFoundError(error)) return null;
|
|
102
|
+
throw misconfiguredOr(error, resolvedBucket);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async getSignedUrl(key, bucket) {
|
|
106
|
+
const metadata = await this.getMetadata(key, bucket);
|
|
107
|
+
if (!metadata) return {
|
|
108
|
+
url: null,
|
|
109
|
+
fileNotFound: true
|
|
110
|
+
};
|
|
111
|
+
const [url] = await (await this.getClient()).bucket(metadata.bucket).file(metadata.fullPath).getSignedUrl({
|
|
112
|
+
action: "read",
|
|
113
|
+
expires: Date.now() + 3600 * 1e3
|
|
114
|
+
});
|
|
115
|
+
return {
|
|
116
|
+
url,
|
|
117
|
+
metadata
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* The object's bytes, type and version — all three of ONE generation.
|
|
122
|
+
*
|
|
123
|
+
* Described first, then read pinned to the generation described, as
|
|
124
|
+
* {@link getObjectStream} reads. It used to download and then ask for the
|
|
125
|
+
* metadata: an overwrite between the two calls answered the old bytes
|
|
126
|
+
* under the new generation's `updated`, and that `lastModified` keys the
|
|
127
|
+
* routes' `ETag` and transform cache, so the stale bytes — or a rendition
|
|
128
|
+
* of them — were served as current. A pinned read of a generation replaced
|
|
129
|
+
* since it was described fails, and the object is described again.
|
|
130
|
+
*/
|
|
131
|
+
async getObject(key, bucket) {
|
|
132
|
+
for (let attempt = 1;; attempt++) {
|
|
133
|
+
const located = await this.locate(key, bucket);
|
|
134
|
+
if (!located) return null;
|
|
135
|
+
let contents;
|
|
136
|
+
try {
|
|
137
|
+
contents = await this.readDecompressed(located);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
if (isNotFoundError(error) && attempt < 3) continue;
|
|
140
|
+
if (isNotFoundError(error)) throw new Error(`The object "${located.key}" changed on every read; try again.`);
|
|
141
|
+
throw misconfiguredOr(error, located.bucket);
|
|
142
|
+
}
|
|
143
|
+
const contentType = located.meta.contentType || "application/octet-stream";
|
|
144
|
+
const updated = typeof located.meta.updated === "string" ? Date.parse(located.meta.updated) : NaN;
|
|
145
|
+
return new File([bytesView(contents)], located.key.split("/").pop() || located.key, {
|
|
146
|
+
type: contentType,
|
|
147
|
+
lastModified: Number.isNaN(updated) ? void 0 : updated
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* The bucket and key a call addresses: a `gs://` or `s3://` URL names its
|
|
153
|
+
* own bucket, anything else is a key in the given (or configured) bucket.
|
|
154
|
+
*/
|
|
155
|
+
resolveLocation(key, bucket) {
|
|
156
|
+
const match = key.match(/^(gs|s3):\/\//);
|
|
157
|
+
if (match) {
|
|
158
|
+
const withoutProtocol = key.substring(match[0].length);
|
|
159
|
+
const firstSlash = withoutProtocol.indexOf("/");
|
|
160
|
+
if (firstSlash > 0) return {
|
|
161
|
+
bucket: withoutProtocol.substring(0, firstSlash),
|
|
162
|
+
key: withoutProtocol.substring(firstSlash + 1)
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
bucket: this.getBucket(bucket),
|
|
167
|
+
key
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/** The object's metadata and where it lives, or `null` when there is no such object. */
|
|
171
|
+
async locate(key, bucket) {
|
|
172
|
+
const location = this.resolveLocation(key, bucket);
|
|
173
|
+
const client = await this.getClient();
|
|
174
|
+
try {
|
|
175
|
+
const [meta] = await client.bucket(location.bucket).file(location.key).getMetadata();
|
|
176
|
+
return {
|
|
177
|
+
...location,
|
|
178
|
+
meta
|
|
179
|
+
};
|
|
180
|
+
} catch (error) {
|
|
181
|
+
if (isNotFoundError(error)) return null;
|
|
182
|
+
throw misconfiguredOr(error, location.bucket);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* The generation just described, so an overwrite between describing an
|
|
187
|
+
* object and reading it fails the read instead of sending the new bytes
|
|
188
|
+
* under the old size and version.
|
|
189
|
+
*/
|
|
190
|
+
async pinnedFile(located) {
|
|
191
|
+
const client = await this.getClient();
|
|
192
|
+
const generation = located.meta.generation;
|
|
193
|
+
return client.bucket(located.bucket).file(located.key, generation !== void 0 ? { generation } : {});
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* An object stored gzip-encoded (`gsutil cp -z`) is decompressed on the way
|
|
197
|
+
* out, so its stored size is not the size served and a range of the one is
|
|
198
|
+
* not a range of the other. Such an object is read whole, as every object
|
|
199
|
+
* used to be; nothing this server writes is stored that way.
|
|
200
|
+
*/
|
|
201
|
+
async readDecompressed(located) {
|
|
202
|
+
const [contents] = await (await this.pinnedFile(located)).download();
|
|
203
|
+
return contents;
|
|
204
|
+
}
|
|
205
|
+
/** See {@link StorageController.statObject}. One metadata request; no body is read. */
|
|
206
|
+
async statObject(key, bucket) {
|
|
207
|
+
const located = await this.locate(key, bucket);
|
|
208
|
+
if (!located) return null;
|
|
209
|
+
const info = storedObjectInfo(located.meta);
|
|
210
|
+
if (located.meta.contentEncoding === "gzip") return {
|
|
211
|
+
...info,
|
|
212
|
+
size: (await this.readDecompressed(located)).byteLength
|
|
213
|
+
};
|
|
214
|
+
return info;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* See {@link StorageController.getObjectStream}. One metadata request, then
|
|
218
|
+
* a read of exactly the requested bytes, handed on as they arrive.
|
|
219
|
+
*
|
|
220
|
+
* The metadata comes first, not after the download as `getObject` has it:
|
|
221
|
+
* it is what pins the read to one generation.
|
|
222
|
+
*/
|
|
223
|
+
async getObjectStream(key, options) {
|
|
224
|
+
const located = await this.locate(key, options?.bucket);
|
|
225
|
+
if (!located) return null;
|
|
226
|
+
const info = storedObjectInfo(located.meta);
|
|
227
|
+
const range = options?.range;
|
|
228
|
+
if (located.meta.contentEncoding === "gzip") {
|
|
229
|
+
const contents = await this.readDecompressed(located);
|
|
230
|
+
const inRange = range && range.end < contents.byteLength && range.start <= range.end;
|
|
231
|
+
return {
|
|
232
|
+
...info,
|
|
233
|
+
size: contents.byteLength,
|
|
234
|
+
stream: bufferWebStream(inRange ? contents.subarray(range.start, range.end + 1) : contents),
|
|
235
|
+
...inRange ? { range } : {}
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
const file = await this.pinnedFile(located);
|
|
239
|
+
return {
|
|
240
|
+
...info,
|
|
241
|
+
stream: webStreamFrom(() => file.createReadStream(range ? {
|
|
242
|
+
start: range.start,
|
|
243
|
+
end: range.end
|
|
244
|
+
} : {})),
|
|
245
|
+
...range ? { range } : {}
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
async deleteObject(key, bucket) {
|
|
249
|
+
let resolvedPath = key;
|
|
250
|
+
let resolvedBucket = this.getBucket(bucket);
|
|
251
|
+
const match = key.match(/^(gs|s3):\/\//);
|
|
252
|
+
if (match) {
|
|
253
|
+
const protocolLength = match[0].length;
|
|
254
|
+
const withoutProtocol = key.substring(protocolLength);
|
|
255
|
+
const firstSlash = withoutProtocol.indexOf("/");
|
|
256
|
+
if (firstSlash > 0) {
|
|
257
|
+
resolvedBucket = withoutProtocol.substring(0, firstSlash);
|
|
258
|
+
resolvedPath = withoutProtocol.substring(firstSlash + 1);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
const gcsFile = (await this.getClient()).bucket(resolvedBucket).file(resolvedPath);
|
|
262
|
+
try {
|
|
263
|
+
await gcsFile.delete();
|
|
264
|
+
} catch (error) {
|
|
265
|
+
if (isNotFoundError(error)) return;
|
|
266
|
+
throw misconfiguredOr(error, resolvedBucket);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
async listObjects(prefix, options) {
|
|
270
|
+
const resolvedBucket = this.getBucket(options?.bucket);
|
|
271
|
+
const gcsBucket = (await this.getClient()).bucket(resolvedBucket);
|
|
272
|
+
const folderPrefix = listingPrefix(prefix);
|
|
273
|
+
const [listed, , filesApiResponse] = await gcsBucket.getFiles({
|
|
274
|
+
prefix: folderPrefix,
|
|
275
|
+
delimiter: "/",
|
|
276
|
+
maxResults: options?.maxResults ?? 1e3,
|
|
277
|
+
pageToken: options?.pageToken,
|
|
278
|
+
autoPaginate: false
|
|
279
|
+
}).catch((error) => {
|
|
280
|
+
throw misconfiguredOr(error, resolvedBucket);
|
|
281
|
+
});
|
|
282
|
+
return {
|
|
283
|
+
items: listed.filter((file) => file.name !== folderPrefix).map((file) => ({
|
|
284
|
+
bucket: resolvedBucket,
|
|
285
|
+
fullPath: file.name,
|
|
286
|
+
name: file.name.split("/").pop() || "",
|
|
287
|
+
...listedFacts(file.metadata),
|
|
288
|
+
parent: null,
|
|
289
|
+
root: null,
|
|
290
|
+
toString: () => `gs://${resolvedBucket}/${file.name}`
|
|
291
|
+
})),
|
|
292
|
+
prefixes: (filesApiResponse?.prefixes ?? []).map((p) => {
|
|
293
|
+
const folder = folderKey(p);
|
|
294
|
+
return {
|
|
295
|
+
bucket: resolvedBucket,
|
|
296
|
+
fullPath: folder,
|
|
297
|
+
name: folder.split("/").pop() || "",
|
|
298
|
+
parent: null,
|
|
299
|
+
root: null,
|
|
300
|
+
toString: () => `gs://${resolvedBucket}/${folder}`
|
|
301
|
+
};
|
|
302
|
+
}),
|
|
303
|
+
nextPageToken: filesApiResponse?.nextPageToken
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
/** See {@link StorageController.maxFileSize}. */
|
|
307
|
+
maxFileSize() {
|
|
308
|
+
return this.config.maxFileSize ?? 52428800;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Validate file before upload
|
|
312
|
+
*/
|
|
313
|
+
validateFile(file) {
|
|
314
|
+
validateUploadedFile(file, this.maxFileSize(), this.config.allowedMimeTypes);
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Get the bucket name — either from parameter or config.
|
|
318
|
+
*/
|
|
319
|
+
getBucket(bucket) {
|
|
320
|
+
if (!bucket || bucket === "default") return this.config.bucket;
|
|
321
|
+
return bucket;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Flatten nested metadata to string values (GCS custom metadata requirement)
|
|
325
|
+
*/
|
|
326
|
+
flattenMetadata(metadata) {
|
|
327
|
+
const flattened = {};
|
|
328
|
+
for (const [key, value] of Object.entries(metadata)) if (typeof value === "string") flattened[key] = value;
|
|
329
|
+
else if (value !== void 0 && value !== null) flattened[key] = JSON.stringify(value);
|
|
330
|
+
return flattened;
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
/** A file's bytes, a chunk at a time — what `File.save` pipes from. */
|
|
334
|
+
async function* byteChunks(file) {
|
|
335
|
+
const reader = file.stream().getReader();
|
|
336
|
+
try {
|
|
337
|
+
for (;;) {
|
|
338
|
+
const { done, value } = await reader.read();
|
|
339
|
+
if (done) return;
|
|
340
|
+
yield value;
|
|
341
|
+
}
|
|
342
|
+
} finally {
|
|
343
|
+
reader.releaseLock();
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
/** What a listing entry says about its object, from the metadata the listing carried. */
|
|
347
|
+
function listedFacts(meta) {
|
|
348
|
+
if (!meta) return {};
|
|
349
|
+
const size = meta.size === void 0 ? NaN : Number(meta.size);
|
|
350
|
+
return {
|
|
351
|
+
...Number.isFinite(size) ? { size } : {},
|
|
352
|
+
...typeof meta.updated === "string" ? { updated: meta.updated } : {},
|
|
353
|
+
...typeof meta.contentType === "string" && meta.contentType ? { contentType: meta.contentType } : {}
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
/** What `statObject` reports, from an object's metadata. */
|
|
357
|
+
function storedObjectInfo(meta) {
|
|
358
|
+
const updated = typeof meta.updated === "string" ? Date.parse(meta.updated) : NaN;
|
|
359
|
+
return {
|
|
360
|
+
size: Number(meta.size) || 0,
|
|
361
|
+
contentType: meta.contentType || "application/octet-stream",
|
|
362
|
+
lastModified: Number.isNaN(updated) ? Date.now() : updated
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Check whether an error represents a 404 / Not Found from the GCS client.
|
|
367
|
+
*/
|
|
368
|
+
function isNotFoundError(error) {
|
|
369
|
+
if (typeof error !== "object" || error === null) return false;
|
|
370
|
+
if (isMissingBucketError(error)) return false;
|
|
371
|
+
const err = error;
|
|
372
|
+
if (err["code"] === 404) return true;
|
|
373
|
+
if (typeof err["message"] === "string" && /not found/i.test(err["message"])) return true;
|
|
374
|
+
return false;
|
|
375
|
+
}
|
|
376
|
+
/** GCS's answer for a bucket that does not exist: a 404 whose message says so. */
|
|
377
|
+
function isMissingBucketError(error) {
|
|
378
|
+
return "message" in error && typeof error.message === "string" && /bucket does not exist/i.test(error.message);
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* A provider error that says this deployment's storage is misconfigured — the
|
|
382
|
+
* bucket does not exist, or the credentials may not use it — as a
|
|
383
|
+
* `502 STORAGE_MISCONFIGURED` naming the bucket; or the error as it was.
|
|
384
|
+
*
|
|
385
|
+
* These used to reach the caller as a bare 500 "Internal Server Error".
|
|
386
|
+
*/
|
|
387
|
+
function misconfiguredOr(error, bucket) {
|
|
388
|
+
if (typeof error !== "object" || error === null) return error;
|
|
389
|
+
if (isMissingBucketError(error)) return new ApiError(502, "STORAGE_MISCONFIGURED", `The storage bucket "${bucket}" does not exist. Check the bucket name this source is configured with (GCS_BUCKET, or GCS_BUCKET__<KEY> for a named source).`, {
|
|
390
|
+
bucket,
|
|
391
|
+
reason: "NoSuchBucket"
|
|
392
|
+
});
|
|
393
|
+
if ("code" in error && (error.code === 401 || error.code === 403)) return new ApiError(502, "STORAGE_MISCONFIGURED", `The storage provider refused this deployment's credentials for bucket "${bucket}". Check the service account and its permissions on the bucket.`, {
|
|
394
|
+
bucket,
|
|
395
|
+
reason: "AccessDenied"
|
|
396
|
+
});
|
|
397
|
+
return error;
|
|
398
|
+
}
|
|
399
|
+
//#endregion
|
|
400
|
+
export { GCSStorageController_exports as n, GCSStorageController as t };
|
|
401
|
+
|
|
402
|
+
//# sourceMappingURL=GCSStorageController-DivE9bAx.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GCSStorageController-DivE9bAx.js","names":[],"sources":["../src/storage/GCSStorageController.ts"],"sourcesContent":["/**\n * Google Cloud Storage controller (works with GCS and Firebase Storage)\n *\n * The `@google-cloud/storage` package is loaded lazily so it remains an\n * optional peer dependency — users who don't need GCS never pay for the\n * import cost or need it installed.\n */\n\nimport type { FileMetadata } from \"@google-cloud/storage\";\nimport {\n StorageController,\n GCSStorageConfig,\n DEFAULT_MAX_FILE_SIZE,\n type StoredObjectInfo,\n type StoredObjectRange,\n type PutObjectOptions,\n type StoredObjectStream\n} from \"./types\";\nimport { folderKey, listingPrefix } from \"./keys\";\nimport { bufferWebStream, bytesView, webStreamFrom } from \"./streams\";\nimport { ApiError } from \"../api/errors\";\nimport { validateUploadedFile } from \"./upload-validation\";\nimport {\n UploadFileProps,\n UploadFileResult,\n DownloadConfig,\n DownloadMetadata,\n StorageListResult,\n StorageReference\n} from \"@rebasepro/types\";\n\n// ---------------------------------------------------------------------------\n// Lazy import of @google-cloud/storage\n// ---------------------------------------------------------------------------\n\nlet gcsModule: typeof import(\"@google-cloud/storage\") | null = null;\n\nasync function loadGCS(): Promise<typeof import(\"@google-cloud/storage\")> {\n if (gcsModule) return gcsModule;\n try {\n gcsModule = await import(\"@google-cloud/storage\");\n return gcsModule;\n } catch {\n throw new Error(\n \"Google Cloud Storage is required for the GCS storage controller. \" +\n \"Install it with: pnpm add @google-cloud/storage\"\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// GCSStorageController\n// ---------------------------------------------------------------------------\n\n/**\n * Google Cloud Storage implementation of `StorageController`.\n *\n * Works with standard GCS buckets **and** Firebase Storage buckets\n * (e.g. `\"my-project.appspot.com\"`).\n */\nexport class GCSStorageController implements StorageController {\n private config: GCSStorageConfig;\n private client: InstanceType<typeof import(\"@google-cloud/storage\").Storage> | null = null;\n\n constructor(config: GCSStorageConfig) {\n this.config = config;\n }\n\n // ------------------------------------------------------------------\n // Lazy client initialisation\n // ------------------------------------------------------------------\n\n private async getClient(): Promise<InstanceType<typeof import(\"@google-cloud/storage\").Storage>> {\n if (this.client) return this.client;\n const { Storage } = await loadGCS();\n this.client = new Storage({\n projectId: this.config.projectId,\n keyFilename: this.config.keyFilename,\n credentials: this.config.credentials\n });\n return this.client;\n }\n\n // ------------------------------------------------------------------\n // StorageController interface\n // ------------------------------------------------------------------\n\n getType(): \"gcs\" {\n return \"gcs\";\n }\n\n /**\n * The configured bucket, and the logical `default` that maps to it.\n * @see S3StorageController.knownBuckets\n */\n knownBuckets(): string[] {\n return [\"default\", this.config.bucket];\n }\n\n async putObject({\n file,\n key,\n metadata,\n bucket\n }: UploadFileProps, options?: PutObjectOptions): Promise<UploadFileResult> {\n if (options?.uploadLimits !== false) this.validateFile(file);\n\n const usedBucket = this.getBucket(bucket);\n const client = await this.getClient();\n const gcsFile = client.bucket(usedBucket).file(key);\n\n // Streamed from the file a chunk at a time. Read into one buffer first,\n // it was held whole (twice, with the copy) for the length of the\n // upload. Handed over as a function so the client can open it again to\n // retry, which it does not do for a stream it was given once.\n await gcsFile.save(() => byteChunks(file), {\n contentType: file.type,\n metadata: metadata ? { metadata: this.flattenMetadata(metadata) } : undefined\n }).catch((error: unknown) => { throw misconfiguredOr(error, usedBucket); });\n\n return {\n key,\n bucket: usedBucket,\n storageUrl: `gs://${usedBucket}/${key}`\n };\n }\n\n /**\n * Read the object's metadata and nothing else — no signing.\n *\n * Signing needs a private key. With `keyFilename` or `credentials` the\n * client has one; on Cloud Run, GKE or GCE it has only the metadata\n * server's token, and falls back to the IAM Credentials `signBlob` API,\n * which the runtime account may call on itself only when it holds\n * `iam.serviceAccounts.signBlob` there. That is not a default grant.\n */\n async getMetadata(key: string, bucket?: string): Promise<DownloadMetadata | null> {\n // Handle gs:// and s3:// URLs\n let resolvedPath = key;\n let resolvedBucket = this.getBucket(bucket);\n\n const match = key.match(/^(gs|s3):\\/\\//);\n if (match) {\n const protocolLength = match[0].length;\n const withoutProtocol = key.substring(protocolLength);\n const firstSlash = withoutProtocol.indexOf(\"/\");\n if (firstSlash > 0) {\n resolvedBucket = withoutProtocol.substring(0, firstSlash);\n resolvedPath = withoutProtocol.substring(firstSlash + 1);\n }\n }\n\n try {\n const client = await this.getClient();\n const [fileMetadata] = await client.bucket(resolvedBucket).file(resolvedPath).getMetadata();\n\n return {\n bucket: resolvedBucket,\n fullPath: resolvedPath,\n name: resolvedPath.split(\"/\").pop() || resolvedPath,\n size: Number(fileMetadata.size) || 0,\n contentType: fileMetadata.contentType || \"application/octet-stream\",\n customMetadata: (fileMetadata.metadata as Record<string, string> | undefined) || {}\n };\n } catch (error: unknown) {\n if (isNotFoundError(error)) {\n return null;\n }\n throw misconfiguredOr(error, resolvedBucket);\n }\n }\n\n async getSignedUrl(key: string, bucket?: string): Promise<DownloadConfig> {\n // Metadata first, to check existence and populate the response.\n const metadata = await this.getMetadata(key, bucket);\n if (!metadata) {\n return {\n url: null,\n fileNotFound: true\n };\n }\n\n const client = await this.getClient();\n // Good for an hour. Server-side callers only: no route hands it out.\n const [url] = await client.bucket(metadata.bucket).file(metadata.fullPath).getSignedUrl({\n action: \"read\",\n expires: Date.now() + 3600 * 1000\n });\n\n return {\n url,\n metadata\n };\n }\n\n /**\n * The object's bytes, type and version — all three of ONE generation.\n *\n * Described first, then read pinned to the generation described, as\n * {@link getObjectStream} reads. It used to download and then ask for the\n * metadata: an overwrite between the two calls answered the old bytes\n * under the new generation's `updated`, and that `lastModified` keys the\n * routes' `ETag` and transform cache, so the stale bytes — or a rendition\n * of them — were served as current. A pinned read of a generation replaced\n * since it was described fails, and the object is described again.\n */\n async getObject(key: string, bucket?: string): Promise<File | null> {\n for (let attempt = 1; ; attempt++) {\n const located = await this.locate(key, bucket);\n if (!located) return null;\n\n let contents: Buffer;\n try {\n contents = await this.readDecompressed(located);\n } catch (error: unknown) {\n // Replaced or deleted since it was described. Three\n // overwrites inside one read is a write loop, not a race.\n if (isNotFoundError(error) && attempt < 3) continue;\n if (isNotFoundError(error)) {\n throw new Error(`The object \"${located.key}\" changed on every read; try again.`);\n }\n throw misconfiguredOr(error, located.bucket);\n }\n\n const contentType = located.meta.contentType || \"application/octet-stream\";\n // The object's own version — see `S3StorageController.getObject`.\n const updated = typeof located.meta.updated === \"string\" ? Date.parse(located.meta.updated) : NaN;\n return new File([bytesView(contents)], located.key.split(\"/\").pop() || located.key, {\n type: contentType,\n lastModified: Number.isNaN(updated) ? undefined : updated\n });\n }\n }\n\n /**\n * The bucket and key a call addresses: a `gs://` or `s3://` URL names its\n * own bucket, anything else is a key in the given (or configured) bucket.\n */\n private resolveLocation(key: string, bucket?: string): { bucket: string; key: string } {\n const match = key.match(/^(gs|s3):\\/\\//);\n if (match) {\n const withoutProtocol = key.substring(match[0].length);\n const firstSlash = withoutProtocol.indexOf(\"/\");\n if (firstSlash > 0) {\n return { bucket: withoutProtocol.substring(0, firstSlash), key: withoutProtocol.substring(firstSlash + 1) };\n }\n }\n return { bucket: this.getBucket(bucket), key };\n }\n\n /** The object's metadata and where it lives, or `null` when there is no such object. */\n private async locate(key: string, bucket?: string): Promise<{ bucket: string; key: string; meta: FileMetadata } | null> {\n const location = this.resolveLocation(key, bucket);\n const client = await this.getClient();\n try {\n const [meta] = await client.bucket(location.bucket).file(location.key).getMetadata();\n return { ...location, meta };\n } catch (error: unknown) {\n if (isNotFoundError(error)) return null;\n throw misconfiguredOr(error, location.bucket);\n }\n }\n\n /**\n * The generation just described, so an overwrite between describing an\n * object and reading it fails the read instead of sending the new bytes\n * under the old size and version.\n */\n private async pinnedFile(located: { bucket: string; key: string; meta: FileMetadata }) {\n const client = await this.getClient();\n const generation = located.meta.generation;\n return client.bucket(located.bucket).file(located.key, generation !== undefined ? { generation } : {});\n }\n\n /**\n * An object stored gzip-encoded (`gsutil cp -z`) is decompressed on the way\n * out, so its stored size is not the size served and a range of the one is\n * not a range of the other. Such an object is read whole, as every object\n * used to be; nothing this server writes is stored that way.\n */\n private async readDecompressed(located: { bucket: string; key: string; meta: FileMetadata }): Promise<Buffer> {\n const [contents] = await (await this.pinnedFile(located)).download();\n return contents;\n }\n\n /** See {@link StorageController.statObject}. One metadata request; no body is read. */\n async statObject(key: string, bucket?: string): Promise<StoredObjectInfo | null> {\n const located = await this.locate(key, bucket);\n if (!located) return null;\n const info = storedObjectInfo(located.meta);\n if (located.meta.contentEncoding === \"gzip\") {\n return { ...info, size: (await this.readDecompressed(located)).byteLength };\n }\n return info;\n }\n\n /**\n * See {@link StorageController.getObjectStream}. One metadata request, then\n * a read of exactly the requested bytes, handed on as they arrive.\n *\n * The metadata comes first, not after the download as `getObject` has it:\n * it is what pins the read to one generation.\n */\n async getObjectStream(\n key: string,\n options?: { bucket?: string; range?: StoredObjectRange }\n ): Promise<StoredObjectStream | null> {\n const located = await this.locate(key, options?.bucket);\n if (!located) return null;\n const info = storedObjectInfo(located.meta);\n const range = options?.range;\n\n if (located.meta.contentEncoding === \"gzip\") {\n const contents = await this.readDecompressed(located);\n const inRange = range && range.end < contents.byteLength && range.start <= range.end;\n return {\n ...info,\n size: contents.byteLength,\n stream: bufferWebStream(inRange ? contents.subarray(range.start, range.end + 1) : contents),\n ...(inRange ? { range } : {})\n };\n }\n\n const file = await this.pinnedFile(located);\n return {\n ...info,\n // Opened on the first read: nothing is requested from the bucket\n // for a stream the caller ends up not reading.\n stream: webStreamFrom(() => file.createReadStream(range ? { start: range.start, end: range.end } : {})),\n ...(range ? { range } : {})\n };\n }\n\n async deleteObject(key: string, bucket?: string): Promise<void> {\n // Handle gs:// and s3:// URLs\n let resolvedPath = key;\n let resolvedBucket = this.getBucket(bucket);\n\n const match = key.match(/^(gs|s3):\\/\\//);\n if (match) {\n const protocolLength = match[0].length;\n const withoutProtocol = key.substring(protocolLength);\n const firstSlash = withoutProtocol.indexOf(\"/\");\n if (firstSlash > 0) {\n resolvedBucket = withoutProtocol.substring(0, firstSlash);\n resolvedPath = withoutProtocol.substring(firstSlash + 1);\n }\n }\n\n const client = await this.getClient();\n const gcsFile = client.bucket(resolvedBucket).file(resolvedPath);\n\n // Not `ignoreNotFound`: the client ignores every 404 with it, a missing\n // bucket's included, so a typo in the bucket name deleted \"successfully\".\n try {\n await gcsFile.delete();\n } catch (error: unknown) {\n if (isNotFoundError(error)) return;\n throw misconfiguredOr(error, resolvedBucket);\n }\n }\n\n async listObjects(prefix: string, options?: {\n bucket?: string;\n maxResults?: number;\n pageToken?: string;\n }): Promise<StorageListResult> {\n const resolvedBucket = this.getBucket(options?.bucket);\n const client = await this.getClient();\n const gcsBucket = client.bucket(resolvedBucket);\n\n // Fetch files (objects) matching the prefix\n const folderPrefix = listingPrefix(prefix);\n const [listed, , filesApiResponse] = await gcsBucket.getFiles({\n prefix: folderPrefix,\n delimiter: \"/\",\n maxResults: options?.maxResults ?? 1000,\n pageToken: options?.pageToken,\n autoPaginate: false\n }).catch((error: unknown) => { throw misconfiguredOr(error, resolvedBucket); });\n\n // The folder's own marker object is not something in the folder —\n // see `S3StorageController.listObjects`.\n const files = listed.filter(file => file.name !== folderPrefix);\n const items: StorageReference[] = files.map(file => ({\n bucket: resolvedBucket,\n fullPath: file.name,\n name: file.name.split(\"/\").pop() || \"\",\n // Each listed object arrives with its full metadata, so a folder\n // can be shown without a metadata request per file.\n ...listedFacts(file.metadata),\n parent: null as never,\n root: null as never,\n toString: () => `gs://${resolvedBucket}/${file.name}`\n }));\n\n // Common prefixes (folder-like entries) come from the API response\n const apiPrefixes: string[] =\n (filesApiResponse as Record<string, unknown> | undefined)?.prefixes as string[] ?? [];\n\n const prefixes: StorageReference[] = apiPrefixes.map(p => {\n // Without the trailing slash GCS puts on a common prefix, so a\n // `fullPath` from a listing is a key any controller accepts.\n const folder = folderKey(p);\n return {\n bucket: resolvedBucket,\n fullPath: folder,\n name: folder.split(\"/\").pop() || \"\",\n parent: null as never,\n root: null as never,\n toString: () => `gs://${resolvedBucket}/${folder}`\n };\n });\n\n // The next page token is returned on the query object (second element)\n const nextPageToken =\n (filesApiResponse as Record<string, unknown> | undefined)?.nextPageToken as string | undefined;\n\n return {\n items,\n prefixes,\n nextPageToken\n };\n }\n\n // ------------------------------------------------------------------\n // Private helpers\n // ------------------------------------------------------------------\n\n /** See {@link StorageController.maxFileSize}. */\n maxFileSize(): number {\n return this.config.maxFileSize ?? DEFAULT_MAX_FILE_SIZE;\n }\n\n /**\n * Validate file before upload\n */\n private validateFile(file: File): void {\n validateUploadedFile(file, this.maxFileSize(), this.config.allowedMimeTypes);\n }\n\n /**\n * Get the bucket name — either from parameter or config.\n */\n private getBucket(bucket?: string): string {\n // \"default\" is a logical bucket name used by local storage;\n // for GCS it should resolve to the configured bucket.\n if (!bucket || bucket === \"default\") return this.config.bucket;\n return bucket;\n }\n\n /**\n * Flatten nested metadata to string values (GCS custom metadata requirement)\n */\n private flattenMetadata(metadata: Record<string, unknown>): Record<string, string> {\n const flattened: Record<string, string> = {};\n for (const [key, value] of Object.entries(metadata)) {\n if (typeof value === \"string\") {\n flattened[key] = value;\n } else if (value !== undefined && value !== null) {\n flattened[key] = JSON.stringify(value);\n }\n }\n return flattened;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Error helpers\n// ---------------------------------------------------------------------------\n\n/** A file's bytes, a chunk at a time — what `File.save` pipes from. */\nasync function* byteChunks(file: Blob): AsyncGenerator<Uint8Array> {\n const reader = file.stream().getReader();\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) return;\n yield value;\n }\n } finally {\n reader.releaseLock();\n }\n}\n\n/** What a listing entry says about its object, from the metadata the listing carried. */\nfunction listedFacts(meta: FileMetadata | undefined): Pick<StorageReference, \"size\" | \"updated\" | \"contentType\"> {\n if (!meta) return {};\n const size = meta.size === undefined ? NaN : Number(meta.size);\n return {\n ...(Number.isFinite(size) ? { size } : {}),\n ...(typeof meta.updated === \"string\" ? { updated: meta.updated } : {}),\n ...(typeof meta.contentType === \"string\" && meta.contentType ? { contentType: meta.contentType } : {})\n };\n}\n\n/** What `statObject` reports, from an object's metadata. */\nfunction storedObjectInfo(meta: FileMetadata): StoredObjectInfo {\n // The object's own version — see `S3StorageController.getObject`.\n const updated = typeof meta.updated === \"string\" ? Date.parse(meta.updated) : NaN;\n return {\n size: Number(meta.size) || 0,\n contentType: meta.contentType || \"application/octet-stream\",\n lastModified: Number.isNaN(updated) ? Date.now() : updated\n };\n}\n\n/**\n * Check whether an error represents a 404 / Not Found from the GCS client.\n */\nfunction isNotFoundError(error: unknown): boolean {\n if (typeof error !== \"object\" || error === null) return false;\n // A missing bucket is a 404 too. Read as a missing object, a typo in the\n // bucket name made every file \"not found\" and every delete a success.\n if (isMissingBucketError(error)) return false;\n const err = error as Record<string, unknown>;\n if (err[\"code\"] === 404) return true;\n if (typeof err[\"message\"] === \"string\" && /not found/i.test(err[\"message\"])) return true;\n return false;\n}\n\n/** GCS's answer for a bucket that does not exist: a 404 whose message says so. */\nfunction isMissingBucketError(error: object): boolean {\n return \"message\" in error && typeof error.message === \"string\" && /bucket does not exist/i.test(error.message);\n}\n\n/**\n * A provider error that says this deployment's storage is misconfigured — the\n * bucket does not exist, or the credentials may not use it — as a\n * `502 STORAGE_MISCONFIGURED` naming the bucket; or the error as it was.\n *\n * These used to reach the caller as a bare 500 \"Internal Server Error\".\n */\nfunction misconfiguredOr(error: unknown, bucket: string): unknown {\n if (typeof error !== \"object\" || error === null) return error;\n if (isMissingBucketError(error)) {\n return new ApiError(502, \"STORAGE_MISCONFIGURED\",\n `The storage bucket \"${bucket}\" does not exist. Check the bucket name this source is configured with (GCS_BUCKET, or GCS_BUCKET__<KEY> for a named source).`,\n { bucket, reason: \"NoSuchBucket\" });\n }\n if (\"code\" in error && (error.code === 401 || error.code === 403)) {\n return new ApiError(502, \"STORAGE_MISCONFIGURED\",\n `The storage provider refused this deployment's credentials for bucket \"${bucket}\". Check the service account and its permissions on the bucket.`,\n { bucket, reason: \"AccessDenied\" });\n }\n return error;\n}\n"],"mappings":"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"}
|