@vritti/api-sdk 0.3.8 → 0.3.10

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/storage.cjs CHANGED
@@ -22,11 +22,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
22
22
  var storage_exports = {};
23
23
  __export(storage_exports, {
24
24
  BucketUsageReader: () => BucketUsageReader,
25
- OrgStorageProvisionerFactory: () => OrgStorageProvisionerFactory,
26
25
  R2BucketProvisioner: () => R2BucketProvisioner,
27
26
  R2StorageProvider: () => R2StorageProvider,
28
27
  StorageFactory: () => StorageFactory,
29
- orgBucketNames: () => orgBucketNames,
28
+ StorageProvisionerFactory: () => StorageProvisionerFactory,
30
29
  readConfigSource: () => readConfigSource
31
30
  });
32
31
  module.exports = __toCommonJS(storage_exports);
@@ -298,17 +297,9 @@ var R2StorageProvider = class _R2StorageProvider {
298
297
  };
299
298
 
300
299
  // src/storage/provisioners/r2-bucket.provisioner.ts
301
- var import_node_crypto = require("crypto");
302
300
  var import_common22 = require("@nestjs/common");
303
301
  var CF_API = "https://api.cloudflare.com/client/v4";
304
302
  var BUCKET_ITEM_WRITE = "Workers R2 Storage Bucket Item Write";
305
- function orgBucketNames(subdomain) {
306
- return {
307
- storageBucket: `org-${subdomain}`,
308
- storagePublicBucket: `org-${subdomain}-public`
309
- };
310
- }
311
- __name(orgBucketNames, "orgBucketNames");
312
303
  var R2BucketProvisioner = class _R2BucketProvisioner {
313
304
  static {
314
305
  __name(this, "R2BucketProvisioner");
@@ -321,84 +312,6 @@ var R2BucketProvisioner = class _R2BucketProvisioner {
321
312
  this.config = config;
322
313
  this.jurisdiction = config.jurisdiction ?? "default";
323
314
  }
324
- // Creates the org's two buckets and one credential scoped to just those buckets
325
- async provisionOrg(subdomain) {
326
- const names = orgBucketNames(subdomain);
327
- await this.createBucket(names.storageBucket);
328
- await this.createBucket(names.storagePublicBucket);
329
- let publicUrl = null;
330
- try {
331
- publicUrl = await this.enablePublicAccess(names.storagePublicBucket);
332
- } catch (error) {
333
- this.logger.warn(`Public access not enabled for ${names.storagePublicBucket}: ${error}`);
334
- }
335
- const token = await this.createScopedToken(subdomain, [
336
- names.storageBucket,
337
- names.storagePublicBucket
338
- ]);
339
- return {
340
- provider: "r2",
341
- accountId: this.config.accountId,
342
- bucket: names.storageBucket,
343
- publicBucket: names.storagePublicBucket,
344
- publicUrl,
345
- accessKeyId: token.id,
346
- // R2 derives the S3 pair from the token: key id = token id, secret = SHA-256 of the token value. The value is
347
- // returned exactly once, so it is hashed here rather than handed back to a caller that might drop it.
348
- secretAccessKey: (0, import_node_crypto.createHash)("sha256").update(token.value).digest("hex"),
349
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
350
- };
351
- }
352
- // Mints a replacement credential scoped to the same buckets. Returns only the credential pair, not a whole
353
- // descriptor: the control plane does not hold the org's publicUrl, so it is in no position to rebuild one.
354
- //
355
- // The old token is NOT revoked here. Revoking before the caller has persisted the new credential would leave the org
356
- // with no working key in the gap; deleteCredential is a separate call, made once the new one is stored.
357
- async rotateCredential(subdomain, buckets) {
358
- const token = await this.createScopedToken(subdomain, [
359
- buckets.storageBucket,
360
- buckets.storagePublicBucket
361
- ]);
362
- return {
363
- accessKeyId: token.id,
364
- secretAccessKey: (0, import_node_crypto.createHash)("sha256").update(token.value).digest("hex")
365
- };
366
- }
367
- // Removes an org's buckets. R2 refuses to delete a bucket that still holds objects, so the uploading server must
368
- // have emptied them first — a 'not empty' failure here means that step did not finish.
369
- async deleteOrgBuckets(buckets) {
370
- for (const bucket of [
371
- buckets.storageBucket,
372
- buckets.storagePublicBucket
373
- ]) {
374
- const response = await fetch(`${CF_API}/accounts/${this.config.accountId}/r2/buckets/${bucket}`, {
375
- method: "DELETE",
376
- headers: {
377
- Authorization: `Bearer ${this.config.adminToken}`
378
- }
379
- });
380
- const body = await response.json();
381
- if (body.success || body.errors?.some((e) => e.code === 10006)) {
382
- this.logger.log(`Deleted bucket ${bucket}`);
383
- continue;
384
- }
385
- throw new Error(`Cloudflare bucket delete failed for ${bucket}: ${this.describe(body)}`);
386
- }
387
- }
388
- // Revokes a credential by its access key id, which on R2 is the Cloudflare token id
389
- async deleteCredential(accessKeyId) {
390
- const response = await fetch(`${CF_API}/accounts/${this.config.accountId}/tokens/${accessKeyId}`, {
391
- method: "DELETE",
392
- headers: {
393
- Authorization: `Bearer ${this.config.tokensToken}`
394
- }
395
- });
396
- const body = await response.json();
397
- if (!body.success) {
398
- throw new Error(`Cloudflare token delete failed for ${accessKeyId}: ${this.describe(body)}`);
399
- }
400
- this.logger.log(`Revoked storage credential ${accessKeyId}`);
401
- }
402
315
  // Creates one bucket, treating an existing bucket as success so provisioning can be re-run to reconcile
403
316
  async createBucket(name) {
404
317
  const response = await fetch(`${CF_API}/accounts/${this.config.accountId}/r2/buckets`, {
@@ -423,9 +336,25 @@ var R2BucketProvisioner = class _R2BucketProvisioner {
423
336
  if (body.errors?.some((e) => e.code === 10004)) return;
424
337
  throw new Error(`Cloudflare bucket create failed for ${name}: ${this.describe(body)}`);
425
338
  }
426
- // Turns on the bucket's Cloudflare-managed domain and returns it. NOTE: r2.dev is rate limited and documented as
427
- // non-productionsustained traffic gets 429s. A custom domain per bucket is the production answer, and drops into
428
- // this same field.
339
+ // Deletes one bucket. R2 refuses to delete a bucket that still holds objects, so the caller must have emptied it
340
+ // firsta 'not empty' failure here means that step did not finish. A missing bucket counts as success.
341
+ async deleteBucket(name) {
342
+ const response = await fetch(`${CF_API}/accounts/${this.config.accountId}/r2/buckets/${name}`, {
343
+ method: "DELETE",
344
+ headers: {
345
+ Authorization: `Bearer ${this.config.adminToken}`
346
+ }
347
+ });
348
+ const body = await response.json();
349
+ if (body.success || body.errors?.some((e) => e.code === 10006)) {
350
+ this.logger.log(`Deleted bucket ${name}`);
351
+ return;
352
+ }
353
+ throw new Error(`Cloudflare bucket delete failed for ${name}: ${this.describe(body)}`);
354
+ }
355
+ // Turns on the bucket's Cloudflare-managed domain and returns its https URL. NOTE: r2.dev is rate limited and
356
+ // documented as non-production — sustained traffic gets 429s. A custom domain per bucket is the production answer,
357
+ // and drops into this same field. Throws on failure; the caller decides whether that is fatal.
429
358
  async enablePublicAccess(bucket) {
430
359
  const response = await fetch(`${CF_API}/accounts/${this.config.accountId}/r2/buckets/${bucket}/domains/managed`, {
431
360
  method: "PUT",
@@ -444,10 +373,11 @@ var R2BucketProvisioner = class _R2BucketProvisioner {
444
373
  this.logger.log(`Enabled public access for ${bucket} \u2192 ${body.result.domain}`);
445
374
  return `https://${body.result.domain}`;
446
375
  }
447
- // Mints an ACCOUNT-owned token that can only touch this org's buckets. Account-owned rather than user-owned so a
376
+ // Mints an ACCOUNT-owned token that can only touch the named buckets. Account-owned rather than user-owned so a
448
377
  // tenant's credential does not die with whichever Cloudflare user happened to create the parent token. Requires the
449
378
  // parent to hold `Account API Tokens Write`; a user-scoped `API Tokens: Edit` token is rejected here with 9109.
450
- async createScopedToken(subdomain, buckets) {
379
+ // `name` is the token's display label; the value is returned by R2 exactly once.
380
+ async createScopedToken(name, buckets) {
451
381
  const permissionGroupId = await this.resolveBucketItemWriteGroupId();
452
382
  const resources = Object.fromEntries(buckets.map((bucket) => [
453
383
  `com.cloudflare.edge.r2.bucket.${this.config.accountId}_${this.jurisdiction}_${bucket}`,
@@ -460,7 +390,7 @@ var R2BucketProvisioner = class _R2BucketProvisioner {
460
390
  "Content-Type": "application/json"
461
391
  },
462
392
  body: JSON.stringify({
463
- name: `org-${subdomain}`,
393
+ name,
464
394
  policies: [
465
395
  {
466
396
  effect: "allow",
@@ -476,11 +406,25 @@ var R2BucketProvisioner = class _R2BucketProvisioner {
476
406
  });
477
407
  const body = await response.json();
478
408
  if (!body.success || !body.result?.value) {
479
- throw new Error(`Cloudflare token create failed for org-${subdomain}: ${this.describe(body)}`);
409
+ throw new Error(`Cloudflare token create failed for ${name}: ${this.describe(body)}`);
480
410
  }
481
- this.logger.log(`Minted storage credential for org-${subdomain} scoped to ${buckets.length} bucket(s)`);
411
+ this.logger.log(`Minted storage credential ${name} scoped to ${buckets.length} bucket(s)`);
482
412
  return body.result;
483
413
  }
414
+ // Revokes a credential by its access key id, which on R2 is the Cloudflare token id
415
+ async deleteCredential(accessKeyId) {
416
+ const response = await fetch(`${CF_API}/accounts/${this.config.accountId}/tokens/${accessKeyId}`, {
417
+ method: "DELETE",
418
+ headers: {
419
+ Authorization: `Bearer ${this.config.tokensToken}`
420
+ }
421
+ });
422
+ const body = await response.json();
423
+ if (!body.success) {
424
+ throw new Error(`Cloudflare token delete failed for ${accessKeyId}: ${this.describe(body)}`);
425
+ }
426
+ this.logger.log(`Revoked storage credential ${accessKeyId}`);
427
+ }
484
428
  // The create-token API takes permission group UUIDs, not names. Looked up once rather than hardcoded: a UUID copied
485
429
  // from docs fails much later with an error that says nothing useful. Same account-scoped endpoint as creation.
486
430
  async resolveBucketItemWriteGroupId() {
@@ -503,17 +447,17 @@ var R2BucketProvisioner = class _R2BucketProvisioner {
503
447
  }
504
448
  };
505
449
 
506
- // src/storage/provisioner.factory.ts
507
- var OrgStorageProvisionerFactory = class {
450
+ // src/storage/storage.factory.ts
451
+ var StorageFactory = class {
508
452
  static {
509
- __name(this, "OrgStorageProvisionerFactory");
453
+ __name(this, "StorageFactory");
510
454
  }
511
455
  options;
512
456
  cache = /* @__PURE__ */ new Map();
513
457
  constructor(options) {
514
458
  this.options = options;
515
459
  }
516
- // Resolves a provisioner by provider name, constructing it once on first use
460
+ // Resolves a provider by name, constructing it once on first use
517
461
  resolve(provider) {
518
462
  const cached = this.cache.get(provider);
519
463
  if (cached) return cached;
@@ -524,24 +468,24 @@ var OrgStorageProvisionerFactory = class {
524
468
  create(provider) {
525
469
  switch (provider) {
526
470
  case "r2":
527
- return new R2BucketProvisioner(readConfigSource(this.options.r2, "r2"));
471
+ return new R2StorageProvider(readConfigSource(this.options.r2, "r2"));
528
472
  default:
529
473
  throw new Error(`Unsupported storage provider: ${provider}`);
530
474
  }
531
475
  }
532
476
  };
533
477
 
534
- // src/storage/storage.factory.ts
535
- var StorageFactory = class {
478
+ // src/storage/storage-provisioner.factory.ts
479
+ var StorageProvisionerFactory = class {
536
480
  static {
537
- __name(this, "StorageFactory");
481
+ __name(this, "StorageProvisionerFactory");
538
482
  }
539
483
  options;
540
484
  cache = /* @__PURE__ */ new Map();
541
485
  constructor(options) {
542
486
  this.options = options;
543
487
  }
544
- // Resolves a provider by name, constructing it once on first use
488
+ // Resolves a provisioner by provider name, constructing it once on first use
545
489
  resolve(provider) {
546
490
  const cached = this.cache.get(provider);
547
491
  if (cached) return cached;
@@ -552,20 +496,19 @@ var StorageFactory = class {
552
496
  create(provider) {
553
497
  switch (provider) {
554
498
  case "r2":
555
- return new R2StorageProvider(readConfigSource(this.options.r2, "r2"));
499
+ return new R2BucketProvisioner(readConfigSource(this.options.r2, "r2"));
556
500
  default:
557
- throw new Error(`Unsupported storage provider: ${provider}`);
501
+ throw new Error(`Unsupported storage provisioner: ${provider}`);
558
502
  }
559
503
  }
560
504
  };
561
505
  // Annotate the CommonJS export names for ESM import in node:
562
506
  0 && (module.exports = {
563
507
  BucketUsageReader,
564
- OrgStorageProvisionerFactory,
565
508
  R2BucketProvisioner,
566
509
  R2StorageProvider,
567
510
  StorageFactory,
568
- orgBucketNames,
511
+ StorageProvisionerFactory,
569
512
  readConfigSource
570
513
  });
571
514
  //# sourceMappingURL=storage.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/storage/index.ts","../src/storage/bucket-usage.reader.ts","../src/storage/config-source.ts","../src/storage/providers/r2-storage.provider.ts","../src/exceptions/bad-gateway.exception.ts","../src/exceptions/base-field.exception.ts","../src/exceptions/bad-request.exception.ts","../src/exceptions/conflict.exception.ts","../src/exceptions/forbidden.exception.ts","../src/exceptions/gone.exception.ts","../src/exceptions/internal-server-error.exception.ts","../src/exceptions/method-not-allowed.exception.ts","../src/exceptions/not-acceptable.exception.ts","../src/exceptions/not-found.exception.ts","../src/exceptions/not-implemented.exception.ts","../src/exceptions/payload-too-large.exception.ts","../src/exceptions/request-timeout.exception.ts","../src/exceptions/service-unavailable.exception.ts","../src/exceptions/too-many-requests.exception.ts","../src/exceptions/unauthorized.exception.ts","../src/exceptions/unprocessable-entity.exception.ts","../src/exceptions/unsupported-media-type.exception.ts","../src/exceptions/validation.exception.ts","../src/storage/provisioners/r2-bucket.provisioner.ts","../src/storage/provisioner.factory.ts","../src/storage/storage.factory.ts"],"sourcesContent":["export { type BucketUsage, BucketUsageReader, type BucketUsageReaderConfig } from './bucket-usage.reader';\nexport { readConfigSource, type StorageConfigSource } from './config-source';\nexport { R2StorageProvider } from './providers/r2-storage.provider';\nexport { OrgStorageProvisionerFactory, type OrgStorageProvisionerFactoryOptions } from './provisioner.factory';\nexport { orgBucketNames, R2BucketProvisioner } from './provisioners/r2-bucket.provisioner';\nexport { StorageFactory, type StorageFactoryOptions } from './storage.factory';\nexport type {\n ListObjectsPage,\n OrgBuckets,\n OrgCredential,\n OrgStorage,\n OrgStorageProvisioner,\n OrgStorageTracking,\n R2ProvisionerConfig,\n R2StorageConfig,\n StorageProvider,\n StoredObject,\n UploadParams,\n} from './types';\n","import { Logger } from '@nestjs/common';\n\nconst CF_GRAPHQL = 'https://api.cloudflare.com/client/v4/graphql';\n\nexport interface BucketUsage {\n bytes: number;\n objectCount: number;\n}\n\nexport interface BucketUsageReaderConfig {\n accountId: string;\n // Account-level token with Analytics Read. NOT the org's scoped S3 credential and NOT the R2 admin token: the\n // analytics dataset is filtered by accountTag and is not reachable with object-level credentials.\n analyticsToken: string;\n}\n\n// r2StorageAdaptiveGroups is a time series, so the newest bucketed sample is the closest thing to \"current\". It lags\n// real writes by minutes — fine for a periodic quota check, useless for gating an individual upload.\n//\n// `dimensions { datetime }` is required, not decorative: ordering by a field that is neither aggregated nor selected\n// as a dimension is rejected. Keep the query free of `#` comments too — Cloudflare's parser rejects them.\nconst QUERY = `query BucketUsage($accountTag: string!, $bucketName: string, $start: Time, $end: Time) {\n viewer {\n accounts(filter: { accountTag: $accountTag }) {\n r2StorageAdaptiveGroups(\n limit: 1\n filter: { datetime_geq: $start, datetime_leq: $end, bucketName: $bucketName }\n orderBy: [datetime_DESC]\n ) {\n max { objectCount payloadSize metadataSize }\n dimensions { datetime }\n }\n }\n }\n}`;\n\ninterface UsageResponse {\n errors?: { message: string }[];\n data?: {\n viewer?: {\n accounts?: {\n r2StorageAdaptiveGroups?: {\n max?: { objectCount?: number; payloadSize?: number; metadataSize?: number };\n }[];\n }[];\n };\n };\n}\n\n// Reads how much an org's bucket actually holds, straight from the provider — the authoritative figure a locally\n// maintained counter would only ever approximate.\nexport class BucketUsageReader {\n private readonly logger = new Logger(BucketUsageReader.name);\n\n constructor(private readonly config: BucketUsageReaderConfig) {}\n\n // Returns the newest reported sample, or zeroes for a bucket the dataset has not reported on yet (a new or\n // empty bucket produces no rows at all rather than a row of zeroes)\n async getBucketUsage(bucketName: string, windowHours = 24): Promise<BucketUsage> {\n const end = new Date();\n const start = new Date(end.getTime() - windowHours * 60 * 60 * 1000);\n\n const response = await fetch(CF_GRAPHQL, {\n method: 'POST',\n headers: { Authorization: `Bearer ${this.config.analyticsToken}`, 'Content-Type': 'application/json' },\n body: JSON.stringify({\n query: QUERY,\n variables: {\n accountTag: this.config.accountId,\n bucketName,\n start: start.toISOString(),\n end: end.toISOString(),\n },\n }),\n });\n\n const body = (await response.json()) as UsageResponse;\n if (body.errors?.length) {\n throw new Error(\n `Cloudflare usage query failed for ${bucketName}: ${body.errors.map((e) => e.message).join('; ')}`,\n );\n }\n\n const sample = body.data?.viewer?.accounts?.[0]?.r2StorageAdaptiveGroups?.[0]?.max;\n if (!sample) {\n this.logger.debug(`No usage samples yet for bucket ${bucketName}`);\n return { bytes: 0, objectCount: 0 };\n }\n\n // Billed storage is payload plus per-object metadata, so both count against a quota\n return {\n bytes: (sample.payloadSize ?? 0) + (sample.metadataSize ?? 0),\n objectCount: sample.objectCount ?? 0,\n };\n }\n}\n","// Config may be a value or a thunk. Servers pass a thunk when the credentials come from required-only-if-selected\n// env keys, so reading them is deferred to the first resolve() instead of running at module construction.\nexport type StorageConfigSource<T> = T | (() => T);\n\n// A backend configured nowhere is a deployment asking for something it was never given credentials for\nexport function readConfigSource<T>(source: StorageConfigSource<T> | undefined, provider: string): T {\n if (source === undefined) {\n throw new Error(`Storage provider '${provider}' is not configured.`);\n }\n return typeof source === 'function' ? (source as () => T)() : source;\n}\n","import type { Readable } from 'node:stream';\nimport {\n DeleteObjectCommand,\n GetObjectCommand,\n ListObjectsV2Command,\n PutObjectCommand,\n S3Client,\n} from '@aws-sdk/client-s3';\nimport { getSignedUrl } from '@aws-sdk/s3-request-presigner';\nimport { Logger } from '@nestjs/common';\nimport { NotFoundException } from '../../exceptions';\nimport type { ListObjectsPage, R2StorageConfig, StorageProvider, UploadParams } from '../types';\n\nexport class R2StorageProvider implements StorageProvider {\n private readonly logger = new Logger(R2StorageProvider.name);\n private readonly client: S3Client;\n\n constructor(private readonly config: R2StorageConfig) {\n this.client = new S3Client({\n region: 'auto',\n endpoint: `https://${config.accountId}.r2.cloudflarestorage.com`,\n credentials: { accessKeyId: config.accessKeyId, secretAccessKey: config.secretAccessKey },\n });\n }\n\n // Uploads a file buffer or stream\n async upload(params: UploadParams): Promise<string> {\n await this.client.send(\n new PutObjectCommand({\n Bucket: this.requireBucket(params.bucket, this.config.defaultBucket),\n Key: params.key,\n Body: params.body,\n ContentType: params.contentType,\n }),\n );\n\n this.logger.log(`Uploaded file to R2: ${params.key}`);\n return params.key;\n }\n\n // Uploads a file to a public bucket and returns its permanent URL\n async uploadPublic(key: string, body: Buffer, contentType: string, bucket?: string): Promise<string> {\n await this.client.send(\n new PutObjectCommand({\n Bucket: this.requireBucket(bucket, this.config.publicBucket),\n Key: key,\n Body: body,\n ContentType: contentType,\n }),\n );\n\n const url = this.getPublicUrl(key);\n this.logger.log(`Uploaded public file to R2: ${key} → ${url}`);\n return url;\n }\n\n // Public URLs come from the configured custom domain, which maps to one bucket — R2 has no per-bucket URL to derive,\n // so a multi-tenant caller with a bucket per org cannot build one at all\n getPublicUrl(key: string): string {\n if (!this.config.publicUrl) {\n throw new Error('R2 storage has no publicUrl configured; public URLs cannot be built.');\n }\n return `${this.config.publicUrl}/${key}`;\n }\n\n // Deletes a file\n async delete(key: string, bucket?: string): Promise<void> {\n await this.client.send(new DeleteObjectCommand({ Bucket: bucket ?? this.config.defaultBucket, Key: key }));\n\n this.logger.log(`Deleted file from R2: ${key}`);\n }\n\n // Generates a presigned download URL (default 1 hour)\n async getSignedUrl(key: string, expiresInSeconds = 3600, bucket?: string): Promise<string> {\n const command = new GetObjectCommand({ Bucket: this.requireBucket(bucket, this.config.defaultBucket), Key: key });\n return getSignedUrl(this.client, command, { expiresIn: expiresInSeconds });\n }\n\n // Returns a readable stream\n async getStream(key: string, bucket?: string): Promise<Readable> {\n const response = await this.client.send(\n new GetObjectCommand({ Bucket: this.requireBucket(bucket, this.config.defaultBucket), Key: key }),\n );\n\n if (!response.Body) {\n throw new NotFoundException('File not found in storage.');\n }\n\n return response.Body as Readable;\n }\n\n // One page of a bucket's contents. S3 has no \"list everything\" call and no bucket-size call — a full inventory is\n // this looped until nextToken is absent, and each page is a Class A request.\n async listObjects(bucket: string, continuationToken?: string): Promise<ListObjectsPage> {\n const response = await this.client.send(\n new ListObjectsV2Command({ Bucket: bucket, ContinuationToken: continuationToken }),\n );\n\n return {\n objects: (response.Contents ?? []).map((o) => ({\n key: o.Key ?? '',\n size: o.Size ?? 0,\n lastModified: o.LastModified ?? new Date(0),\n })),\n nextToken: response.NextContinuationToken,\n };\n }\n\n // Failing loudly here beats letting a missing org bucket fall through to some other tenant's default\n private requireBucket(bucket: string | undefined, fallback: string | undefined): string {\n const resolved = bucket ?? fallback;\n if (!resolved) {\n throw new Error('No bucket supplied and R2 storage has no default bucket configured.');\n }\n return resolved;\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class BadGatewayException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Bad Gateway', HttpStatus.BAD_GATEWAY);\n }\n}\n","import { HttpException, HttpStatus } from '@nestjs/common';\nimport type { FieldError } from '../types/error-response.types';\n\n// Re-export FieldError for backwards compatibility\nexport type { FieldError } from '../types/error-response.types';\n\nexport interface ProblemOptions {\n type?: string;\n label?: string;\n detail?: string;\n errors?: FieldError[];\n}\n\nexport abstract class HttpProblemException extends HttpException {\n constructor(detailOrOptions: string | ProblemOptions, httpStatus: HttpStatus) {\n const options = typeof detailOrOptions === 'string' ? { detail: detailOrOptions } : detailOrOptions;\n\n super(\n {\n type: options.type ?? 'about:blank',\n label: options.label,\n detail: options.detail,\n errors: options.errors ?? [],\n },\n httpStatus,\n );\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class BadRequestException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Bad Request', HttpStatus.BAD_REQUEST);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class ConflictException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Conflict', HttpStatus.CONFLICT);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class ForbiddenException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Forbidden', HttpStatus.FORBIDDEN);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class GoneException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Gone', HttpStatus.GONE);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class InternalServerErrorException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Internal Server Error', HttpStatus.INTERNAL_SERVER_ERROR);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class MethodNotAllowedException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Method Not Allowed', HttpStatus.METHOD_NOT_ALLOWED);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class NotAcceptableException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Not Acceptable', HttpStatus.NOT_ACCEPTABLE);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class NotFoundException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Not Found', HttpStatus.NOT_FOUND);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class NotImplementedException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Not Implemented', HttpStatus.NOT_IMPLEMENTED);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class PayloadTooLargeException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Payload Too Large', HttpStatus.PAYLOAD_TOO_LARGE);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class RequestTimeoutException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Request Timeout', HttpStatus.REQUEST_TIMEOUT);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class ServiceUnavailableException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Service Unavailable', HttpStatus.SERVICE_UNAVAILABLE);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class TooManyRequestsException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Too Many Requests', HttpStatus.TOO_MANY_REQUESTS);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class UnauthorizedException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Unauthorized', HttpStatus.UNAUTHORIZED);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class UnprocessableEntityException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Unprocessable Entity', HttpStatus.UNPROCESSABLE_ENTITY);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class UnsupportedMediaTypeException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Unsupported Media Type', HttpStatus.UNSUPPORTED_MEDIA_TYPE);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class ValidationException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Validation Failed', HttpStatus.UNPROCESSABLE_ENTITY);\n }\n}\n","import { createHash } from 'node:crypto';\nimport { Logger } from '@nestjs/common';\nimport type { OrgBuckets, OrgCredential, OrgStorage, OrgStorageProvisioner, R2ProvisionerConfig } from '../types';\n\nconst CF_API = 'https://api.cloudflare.com/client/v4';\n\n// Bucket-level group: read/write/list objects in named buckets. Deliberately NOT 'Workers R2 Storage Write', which is\n// account-level and can create and delete buckets — exactly what a tenant credential must never do.\nconst BUCKET_ITEM_WRITE = 'Workers R2 Storage Bucket Item Write';\n\n// Bucket names are prefixed because the account also holds Vritti's own buckets, and a subdomain like \"media\" collides\nexport function orgBucketNames(subdomain: string): OrgBuckets {\n return { storageBucket: `org-${subdomain}`, storagePublicBucket: `org-${subdomain}-public` };\n}\n\ninterface CloudflareEnvelope<T> {\n success: boolean;\n errors: { code: number; message: string }[];\n result: T;\n}\n\nexport class R2BucketProvisioner implements OrgStorageProvisioner {\n private readonly logger = new Logger(R2BucketProvisioner.name);\n private readonly jurisdiction: string;\n private bucketItemWriteGroupId: string | null = null;\n\n constructor(private readonly config: R2ProvisionerConfig) {\n this.jurisdiction = config.jurisdiction ?? 'default';\n }\n\n // Creates the org's two buckets and one credential scoped to just those buckets\n async provisionOrg(subdomain: string): Promise<OrgStorage> {\n const names = orgBucketNames(subdomain);\n await this.createBucket(names.storageBucket);\n await this.createBucket(names.storagePublicBucket);\n\n // Not fatal: an org whose public bucket is private still works for every presigned read, and the URL can be\n // filled in later. Failing the whole signup over a CDN convenience would be the wrong trade.\n let publicUrl: string | null = null;\n try {\n publicUrl = await this.enablePublicAccess(names.storagePublicBucket);\n } catch (error: unknown) {\n this.logger.warn(`Public access not enabled for ${names.storagePublicBucket}: ${error}`);\n }\n\n const token = await this.createScopedToken(subdomain, [names.storageBucket, names.storagePublicBucket]);\n\n return {\n provider: 'r2',\n accountId: this.config.accountId,\n bucket: names.storageBucket,\n publicBucket: names.storagePublicBucket,\n publicUrl,\n accessKeyId: token.id,\n // R2 derives the S3 pair from the token: key id = token id, secret = SHA-256 of the token value. The value is\n // returned exactly once, so it is hashed here rather than handed back to a caller that might drop it.\n secretAccessKey: createHash('sha256').update(token.value).digest('hex'),\n createdAt: new Date().toISOString(),\n };\n }\n\n // Mints a replacement credential scoped to the same buckets. Returns only the credential pair, not a whole\n // descriptor: the control plane does not hold the org's publicUrl, so it is in no position to rebuild one.\n //\n // The old token is NOT revoked here. Revoking before the caller has persisted the new credential would leave the org\n // with no working key in the gap; deleteCredential is a separate call, made once the new one is stored.\n async rotateCredential(subdomain: string, buckets: OrgBuckets): Promise<OrgCredential> {\n const token = await this.createScopedToken(subdomain, [buckets.storageBucket, buckets.storagePublicBucket]);\n\n return {\n accessKeyId: token.id,\n secretAccessKey: createHash('sha256').update(token.value).digest('hex'),\n };\n }\n\n // Removes an org's buckets. R2 refuses to delete a bucket that still holds objects, so the uploading server must\n // have emptied them first — a 'not empty' failure here means that step did not finish.\n async deleteOrgBuckets(buckets: OrgBuckets): Promise<void> {\n for (const bucket of [buckets.storageBucket, buckets.storagePublicBucket]) {\n const response = await fetch(`${CF_API}/accounts/${this.config.accountId}/r2/buckets/${bucket}`, {\n method: 'DELETE',\n headers: { Authorization: `Bearer ${this.config.adminToken}` },\n });\n\n const body = (await response.json()) as CloudflareEnvelope<unknown>;\n // 10006 is \"bucket not found\" — already gone, which is the state we wanted\n if (body.success || body.errors?.some((e) => e.code === 10006)) {\n this.logger.log(`Deleted bucket ${bucket}`);\n continue;\n }\n throw new Error(`Cloudflare bucket delete failed for ${bucket}: ${this.describe(body)}`);\n }\n }\n\n // Revokes a credential by its access key id, which on R2 is the Cloudflare token id\n async deleteCredential(accessKeyId: string): Promise<void> {\n const response = await fetch(`${CF_API}/accounts/${this.config.accountId}/tokens/${accessKeyId}`, {\n method: 'DELETE',\n headers: { Authorization: `Bearer ${this.config.tokensToken}` },\n });\n\n const body = (await response.json()) as CloudflareEnvelope<unknown>;\n if (!body.success) {\n throw new Error(`Cloudflare token delete failed for ${accessKeyId}: ${this.describe(body)}`);\n }\n\n this.logger.log(`Revoked storage credential ${accessKeyId}`);\n }\n\n // Creates one bucket, treating an existing bucket as success so provisioning can be re-run to reconcile\n private async createBucket(name: string): Promise<void> {\n const response = await fetch(`${CF_API}/accounts/${this.config.accountId}/r2/buckets`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.config.adminToken}`,\n 'Content-Type': 'application/json',\n 'cf-r2-jurisdiction': this.jurisdiction,\n },\n body: JSON.stringify({\n name,\n ...(this.config.locationHint && { locationHint: this.config.locationHint }),\n }),\n });\n\n const body = (await response.json()) as CloudflareEnvelope<unknown>;\n if (body.success) {\n this.logger.log(`Created bucket ${name}`);\n return;\n }\n\n // 10004 is \"bucket already exists\" — two provisioning attempts racing, or a reconcile pass\n if (body.errors?.some((e) => e.code === 10004)) return;\n throw new Error(`Cloudflare bucket create failed for ${name}: ${this.describe(body)}`);\n }\n\n // Turns on the bucket's Cloudflare-managed domain and returns it. NOTE: r2.dev is rate limited and documented as\n // non-production — sustained traffic gets 429s. A custom domain per bucket is the production answer, and drops into\n // this same field.\n private async enablePublicAccess(bucket: string): Promise<string | null> {\n const response = await fetch(`${CF_API}/accounts/${this.config.accountId}/r2/buckets/${bucket}/domains/managed`, {\n method: 'PUT',\n headers: { Authorization: `Bearer ${this.config.adminToken}`, 'Content-Type': 'application/json' },\n body: JSON.stringify({ enabled: true }),\n });\n\n const body = (await response.json()) as CloudflareEnvelope<{ domain?: string; enabled?: boolean }>;\n if (!body.success || !body.result?.domain) {\n throw new Error(`Cloudflare enable public access failed for ${bucket}: ${this.describe(body)}`);\n }\n\n this.logger.log(`Enabled public access for ${bucket} → ${body.result.domain}`);\n return `https://${body.result.domain}`;\n }\n\n // Mints an ACCOUNT-owned token that can only touch this org's buckets. Account-owned rather than user-owned so a\n // tenant's credential does not die with whichever Cloudflare user happened to create the parent token. Requires the\n // parent to hold `Account API Tokens Write`; a user-scoped `API Tokens: Edit` token is rejected here with 9109.\n private async createScopedToken(subdomain: string, buckets: string[]): Promise<{ id: string; value: string }> {\n const permissionGroupId = await this.resolveBucketItemWriteGroupId();\n\n // The jurisdiction is embedded in the resource key and must match the bucket's, or the token authenticates fine\n // and then 403s on every object because it is scoped to a bucket that does not exist\n const resources = Object.fromEntries(\n buckets.map((bucket) => [\n `com.cloudflare.edge.r2.bucket.${this.config.accountId}_${this.jurisdiction}_${bucket}`,\n '*',\n ]),\n );\n\n const response = await fetch(`${CF_API}/accounts/${this.config.accountId}/tokens`, {\n method: 'POST',\n headers: { Authorization: `Bearer ${this.config.tokensToken}`, 'Content-Type': 'application/json' },\n body: JSON.stringify({\n name: `org-${subdomain}`,\n policies: [{ effect: 'allow', permission_groups: [{ id: permissionGroupId }], resources }],\n }),\n });\n\n const body = (await response.json()) as CloudflareEnvelope<{ id: string; value: string }>;\n if (!body.success || !body.result?.value) {\n throw new Error(`Cloudflare token create failed for org-${subdomain}: ${this.describe(body)}`);\n }\n\n this.logger.log(`Minted storage credential for org-${subdomain} scoped to ${buckets.length} bucket(s)`);\n return body.result;\n }\n\n // The create-token API takes permission group UUIDs, not names. Looked up once rather than hardcoded: a UUID copied\n // from docs fails much later with an error that says nothing useful. Same account-scoped endpoint as creation.\n private async resolveBucketItemWriteGroupId(): Promise<string> {\n if (this.bucketItemWriteGroupId) return this.bucketItemWriteGroupId;\n\n const response = await fetch(`${CF_API}/accounts/${this.config.accountId}/tokens/permission_groups`, {\n headers: { Authorization: `Bearer ${this.config.tokensToken}` },\n });\n\n const body = (await response.json()) as CloudflareEnvelope<{ id: string; name: string }[]>;\n const group = body.result?.find((g) => g.name === BUCKET_ITEM_WRITE);\n if (!group) {\n throw new Error(`Cloudflare permission group '${BUCKET_ITEM_WRITE}' not found: ${this.describe(body)}`);\n }\n\n this.bucketItemWriteGroupId = group.id;\n return group.id;\n }\n\n private describe(body: CloudflareEnvelope<unknown>): string {\n return body.errors?.map((e) => `${e.code} ${e.message}`).join('; ') || 'unknown error';\n }\n}\n","import { readConfigSource, type StorageConfigSource } from './config-source';\nimport { R2BucketProvisioner } from './provisioners/r2-bucket.provisioner';\nimport type { OrgStorageProvisioner, R2ProvisionerConfig } from './types';\n\nexport interface OrgStorageProvisionerFactoryOptions {\n r2?: StorageConfigSource<R2ProvisionerConfig>;\n}\n\n// Mirrors StorageFactory for the control-plane side: same provider names, same lazy config, different job\nexport class OrgStorageProvisionerFactory {\n private readonly cache = new Map<string, OrgStorageProvisioner>();\n\n constructor(private readonly options: OrgStorageProvisionerFactoryOptions) {}\n\n // Resolves a provisioner by provider name, constructing it once on first use\n resolve(provider: string): OrgStorageProvisioner {\n const cached = this.cache.get(provider);\n if (cached) return cached;\n\n const created = this.create(provider);\n this.cache.set(provider, created);\n return created;\n }\n\n private create(provider: string): OrgStorageProvisioner {\n switch (provider) {\n case 'r2':\n return new R2BucketProvisioner(readConfigSource(this.options.r2, 'r2'));\n default:\n throw new Error(`Unsupported storage provider: ${provider}`);\n }\n }\n}\n","import { readConfigSource, type StorageConfigSource } from './config-source';\nimport { R2StorageProvider } from './providers/r2-storage.provider';\nimport type { R2StorageConfig, StorageProvider } from './types';\n\nexport interface StorageFactoryOptions {\n r2?: StorageConfigSource<R2StorageConfig>;\n}\n\nexport class StorageFactory {\n private readonly cache = new Map<string, StorageProvider>();\n\n constructor(private readonly options: StorageFactoryOptions) {}\n\n // Resolves a provider by name, constructing it once on first use\n resolve(provider: string): StorageProvider {\n const cached = this.cache.get(provider);\n if (cached) return cached;\n\n const created = this.create(provider);\n this.cache.set(provider, created);\n return created;\n }\n\n private create(provider: string): StorageProvider {\n switch (provider) {\n case 'r2':\n return new R2StorageProvider(readConfigSource(this.options.r2, 'r2'));\n default:\n throw new Error(`Unsupported storage provider: ${provider}`);\n }\n }\n}\n"],"mappings":"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","names":["CF_GRAPHQL","QUERY","BucketUsageReader","logger","Logger","name","config","getBucketUsage","bucketName","windowHours","end","Date","start","getTime","response","fetch","method","headers","Authorization","analyticsToken","body","JSON","stringify","query","variables","accountTag","accountId","toISOString","json","errors","length","Error","map","e","message","join","sample","data","viewer","accounts","r2StorageAdaptiveGroups","max","debug","bytes","objectCount","payloadSize","metadataSize","readConfigSource","source","provider","undefined","Error","import_common","import_common","import_common","HttpProblemException","HttpException","detailOrOptions","httpStatus","options","detail","type","label","errors","import_common","import_common","import_common","import_common","import_common","import_common","import_common","import_common","NotFoundException","HttpProblemException","detailOrOptions","HttpStatus","NOT_FOUND","import_common","import_common","import_common","import_common","import_common","import_common","import_common","import_common","import_common","R2StorageProvider","logger","Logger","name","client","config","S3Client","region","endpoint","accountId","credentials","accessKeyId","secretAccessKey","upload","params","send","PutObjectCommand","Bucket","requireBucket","bucket","defaultBucket","Key","key","Body","body","ContentType","contentType","log","uploadPublic","publicBucket","url","getPublicUrl","publicUrl","Error","delete","DeleteObjectCommand","getSignedUrl","expiresInSeconds","command","GetObjectCommand","expiresIn","getStream","response","NotFoundException","listObjects","continuationToken","ListObjectsV2Command","ContinuationToken","objects","Contents","map","o","size","Size","lastModified","LastModified","Date","nextToken","NextContinuationToken","fallback","resolved","import_common","CF_API","BUCKET_ITEM_WRITE","orgBucketNames","subdomain","storageBucket","storagePublicBucket","R2BucketProvisioner","logger","Logger","name","jurisdiction","bucketItemWriteGroupId","config","provisionOrg","names","createBucket","publicUrl","enablePublicAccess","error","warn","token","createScopedToken","provider","accountId","bucket","publicBucket","accessKeyId","id","secretAccessKey","createHash","update","value","digest","createdAt","Date","toISOString","rotateCredential","buckets","deleteOrgBuckets","response","fetch","method","headers","Authorization","adminToken","body","json","success","errors","some","e","code","log","Error","describe","deleteCredential","tokensToken","JSON","stringify","locationHint","enabled","result","domain","permissionGroupId","resolveBucketItemWriteGroupId","resources","Object","fromEntries","map","policies","effect","permission_groups","length","group","find","g","message","join","OrgStorageProvisionerFactory","cache","Map","options","resolve","provider","cached","get","created","create","set","R2BucketProvisioner","readConfigSource","r2","Error","StorageFactory","cache","Map","options","resolve","provider","cached","get","created","create","set","R2StorageProvider","readConfigSource","r2","Error"]}
1
+ {"version":3,"sources":["../src/storage/index.ts","../src/storage/bucket-usage.reader.ts","../src/storage/config-source.ts","../src/storage/providers/r2-storage.provider.ts","../src/exceptions/bad-gateway.exception.ts","../src/exceptions/base-field.exception.ts","../src/exceptions/bad-request.exception.ts","../src/exceptions/conflict.exception.ts","../src/exceptions/forbidden.exception.ts","../src/exceptions/gone.exception.ts","../src/exceptions/internal-server-error.exception.ts","../src/exceptions/method-not-allowed.exception.ts","../src/exceptions/not-acceptable.exception.ts","../src/exceptions/not-found.exception.ts","../src/exceptions/not-implemented.exception.ts","../src/exceptions/payload-too-large.exception.ts","../src/exceptions/request-timeout.exception.ts","../src/exceptions/service-unavailable.exception.ts","../src/exceptions/too-many-requests.exception.ts","../src/exceptions/unauthorized.exception.ts","../src/exceptions/unprocessable-entity.exception.ts","../src/exceptions/unsupported-media-type.exception.ts","../src/exceptions/validation.exception.ts","../src/storage/provisioners/r2-bucket.provisioner.ts","../src/storage/storage.factory.ts","../src/storage/storage-provisioner.factory.ts"],"sourcesContent":["export { type BucketUsage, BucketUsageReader, type BucketUsageReaderConfig } from './bucket-usage.reader';\nexport { readConfigSource, type StorageConfigSource } from './config-source';\nexport { R2StorageProvider } from './providers/r2-storage.provider';\nexport { R2BucketProvisioner } from './provisioners/r2-bucket.provisioner';\nexport { StorageFactory, type StorageFactoryOptions } from './storage.factory';\nexport { StorageProvisionerFactory, type StorageProvisionerFactoryOptions } from './storage-provisioner.factory';\nexport type {\n ListObjectsPage,\n R2ProvisionerConfig,\n R2StorageConfig,\n ScopedTokenResult,\n StorageProvider,\n StorageProvisioner,\n StoredObject,\n UploadParams,\n} from './types';\n","import { Logger } from '@nestjs/common';\n\nconst CF_GRAPHQL = 'https://api.cloudflare.com/client/v4/graphql';\n\nexport interface BucketUsage {\n bytes: number;\n objectCount: number;\n}\n\nexport interface BucketUsageReaderConfig {\n accountId: string;\n // Account-level token with Analytics Read. NOT the org's scoped S3 credential and NOT the R2 admin token: the\n // analytics dataset is filtered by accountTag and is not reachable with object-level credentials.\n analyticsToken: string;\n}\n\n// r2StorageAdaptiveGroups is a time series, so the newest bucketed sample is the closest thing to \"current\". It lags\n// real writes by minutes — fine for a periodic quota check, useless for gating an individual upload.\n//\n// `dimensions { datetime }` is required, not decorative: ordering by a field that is neither aggregated nor selected\n// as a dimension is rejected. Keep the query free of `#` comments too — Cloudflare's parser rejects them.\nconst QUERY = `query BucketUsage($accountTag: string!, $bucketName: string, $start: Time, $end: Time) {\n viewer {\n accounts(filter: { accountTag: $accountTag }) {\n r2StorageAdaptiveGroups(\n limit: 1\n filter: { datetime_geq: $start, datetime_leq: $end, bucketName: $bucketName }\n orderBy: [datetime_DESC]\n ) {\n max { objectCount payloadSize metadataSize }\n dimensions { datetime }\n }\n }\n }\n}`;\n\ninterface UsageResponse {\n errors?: { message: string }[];\n data?: {\n viewer?: {\n accounts?: {\n r2StorageAdaptiveGroups?: {\n max?: { objectCount?: number; payloadSize?: number; metadataSize?: number };\n }[];\n }[];\n };\n };\n}\n\n// Reads how much an org's bucket actually holds, straight from the provider — the authoritative figure a locally\n// maintained counter would only ever approximate.\nexport class BucketUsageReader {\n private readonly logger = new Logger(BucketUsageReader.name);\n\n constructor(private readonly config: BucketUsageReaderConfig) {}\n\n // Returns the newest reported sample, or zeroes for a bucket the dataset has not reported on yet (a new or\n // empty bucket produces no rows at all rather than a row of zeroes)\n async getBucketUsage(bucketName: string, windowHours = 24): Promise<BucketUsage> {\n const end = new Date();\n const start = new Date(end.getTime() - windowHours * 60 * 60 * 1000);\n\n const response = await fetch(CF_GRAPHQL, {\n method: 'POST',\n headers: { Authorization: `Bearer ${this.config.analyticsToken}`, 'Content-Type': 'application/json' },\n body: JSON.stringify({\n query: QUERY,\n variables: {\n accountTag: this.config.accountId,\n bucketName,\n start: start.toISOString(),\n end: end.toISOString(),\n },\n }),\n });\n\n const body = (await response.json()) as UsageResponse;\n if (body.errors?.length) {\n throw new Error(\n `Cloudflare usage query failed for ${bucketName}: ${body.errors.map((e) => e.message).join('; ')}`,\n );\n }\n\n const sample = body.data?.viewer?.accounts?.[0]?.r2StorageAdaptiveGroups?.[0]?.max;\n if (!sample) {\n this.logger.debug(`No usage samples yet for bucket ${bucketName}`);\n return { bytes: 0, objectCount: 0 };\n }\n\n // Billed storage is payload plus per-object metadata, so both count against a quota\n return {\n bytes: (sample.payloadSize ?? 0) + (sample.metadataSize ?? 0),\n objectCount: sample.objectCount ?? 0,\n };\n }\n}\n","// Config may be a value or a thunk. Servers pass a thunk when the credentials come from required-only-if-selected\n// env keys, so reading them is deferred to the first resolve() instead of running at module construction.\nexport type StorageConfigSource<T> = T | (() => T);\n\n// A backend configured nowhere is a deployment asking for something it was never given credentials for\nexport function readConfigSource<T>(source: StorageConfigSource<T> | undefined, provider: string): T {\n if (source === undefined) {\n throw new Error(`Storage provider '${provider}' is not configured.`);\n }\n return typeof source === 'function' ? (source as () => T)() : source;\n}\n","import type { Readable } from 'node:stream';\nimport {\n DeleteObjectCommand,\n GetObjectCommand,\n ListObjectsV2Command,\n PutObjectCommand,\n S3Client,\n} from '@aws-sdk/client-s3';\nimport { getSignedUrl } from '@aws-sdk/s3-request-presigner';\nimport { Logger } from '@nestjs/common';\nimport { NotFoundException } from '../../exceptions';\nimport type { ListObjectsPage, R2StorageConfig, StorageProvider, UploadParams } from '../types';\n\nexport class R2StorageProvider implements StorageProvider {\n private readonly logger = new Logger(R2StorageProvider.name);\n private readonly client: S3Client;\n\n constructor(private readonly config: R2StorageConfig) {\n this.client = new S3Client({\n region: 'auto',\n endpoint: `https://${config.accountId}.r2.cloudflarestorage.com`,\n credentials: { accessKeyId: config.accessKeyId, secretAccessKey: config.secretAccessKey },\n });\n }\n\n // Uploads a file buffer or stream\n async upload(params: UploadParams): Promise<string> {\n await this.client.send(\n new PutObjectCommand({\n Bucket: this.requireBucket(params.bucket, this.config.defaultBucket),\n Key: params.key,\n Body: params.body,\n ContentType: params.contentType,\n }),\n );\n\n this.logger.log(`Uploaded file to R2: ${params.key}`);\n return params.key;\n }\n\n // Uploads a file to a public bucket and returns its permanent URL\n async uploadPublic(key: string, body: Buffer, contentType: string, bucket?: string): Promise<string> {\n await this.client.send(\n new PutObjectCommand({\n Bucket: this.requireBucket(bucket, this.config.publicBucket),\n Key: key,\n Body: body,\n ContentType: contentType,\n }),\n );\n\n const url = this.getPublicUrl(key);\n this.logger.log(`Uploaded public file to R2: ${key} → ${url}`);\n return url;\n }\n\n // Public URLs come from the configured custom domain, which maps to one bucket — R2 has no per-bucket URL to derive,\n // so a multi-tenant caller with a bucket per org cannot build one at all\n getPublicUrl(key: string): string {\n if (!this.config.publicUrl) {\n throw new Error('R2 storage has no publicUrl configured; public URLs cannot be built.');\n }\n return `${this.config.publicUrl}/${key}`;\n }\n\n // Deletes a file\n async delete(key: string, bucket?: string): Promise<void> {\n await this.client.send(new DeleteObjectCommand({ Bucket: bucket ?? this.config.defaultBucket, Key: key }));\n\n this.logger.log(`Deleted file from R2: ${key}`);\n }\n\n // Generates a presigned download URL (default 1 hour)\n async getSignedUrl(key: string, expiresInSeconds = 3600, bucket?: string): Promise<string> {\n const command = new GetObjectCommand({ Bucket: this.requireBucket(bucket, this.config.defaultBucket), Key: key });\n return getSignedUrl(this.client, command, { expiresIn: expiresInSeconds });\n }\n\n // Returns a readable stream\n async getStream(key: string, bucket?: string): Promise<Readable> {\n const response = await this.client.send(\n new GetObjectCommand({ Bucket: this.requireBucket(bucket, this.config.defaultBucket), Key: key }),\n );\n\n if (!response.Body) {\n throw new NotFoundException('File not found in storage.');\n }\n\n return response.Body as Readable;\n }\n\n // One page of a bucket's contents. S3 has no \"list everything\" call and no bucket-size call — a full inventory is\n // this looped until nextToken is absent, and each page is a Class A request.\n async listObjects(bucket: string, continuationToken?: string): Promise<ListObjectsPage> {\n const response = await this.client.send(\n new ListObjectsV2Command({ Bucket: bucket, ContinuationToken: continuationToken }),\n );\n\n return {\n objects: (response.Contents ?? []).map((o) => ({\n key: o.Key ?? '',\n size: o.Size ?? 0,\n lastModified: o.LastModified ?? new Date(0),\n })),\n nextToken: response.NextContinuationToken,\n };\n }\n\n // Failing loudly here beats letting a missing org bucket fall through to some other tenant's default\n private requireBucket(bucket: string | undefined, fallback: string | undefined): string {\n const resolved = bucket ?? fallback;\n if (!resolved) {\n throw new Error('No bucket supplied and R2 storage has no default bucket configured.');\n }\n return resolved;\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class BadGatewayException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Bad Gateway', HttpStatus.BAD_GATEWAY);\n }\n}\n","import { HttpException, HttpStatus } from '@nestjs/common';\nimport type { FieldError } from '../types/error-response.types';\n\n// Re-export FieldError for backwards compatibility\nexport type { FieldError } from '../types/error-response.types';\n\nexport interface ProblemOptions {\n type?: string;\n label?: string;\n detail?: string;\n errors?: FieldError[];\n}\n\nexport abstract class HttpProblemException extends HttpException {\n constructor(detailOrOptions: string | ProblemOptions, httpStatus: HttpStatus) {\n const options = typeof detailOrOptions === 'string' ? { detail: detailOrOptions } : detailOrOptions;\n\n super(\n {\n type: options.type ?? 'about:blank',\n label: options.label,\n detail: options.detail,\n errors: options.errors ?? [],\n },\n httpStatus,\n );\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class BadRequestException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Bad Request', HttpStatus.BAD_REQUEST);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class ConflictException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Conflict', HttpStatus.CONFLICT);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class ForbiddenException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Forbidden', HttpStatus.FORBIDDEN);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class GoneException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Gone', HttpStatus.GONE);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class InternalServerErrorException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Internal Server Error', HttpStatus.INTERNAL_SERVER_ERROR);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class MethodNotAllowedException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Method Not Allowed', HttpStatus.METHOD_NOT_ALLOWED);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class NotAcceptableException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Not Acceptable', HttpStatus.NOT_ACCEPTABLE);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class NotFoundException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Not Found', HttpStatus.NOT_FOUND);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class NotImplementedException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Not Implemented', HttpStatus.NOT_IMPLEMENTED);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class PayloadTooLargeException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Payload Too Large', HttpStatus.PAYLOAD_TOO_LARGE);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class RequestTimeoutException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Request Timeout', HttpStatus.REQUEST_TIMEOUT);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class ServiceUnavailableException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Service Unavailable', HttpStatus.SERVICE_UNAVAILABLE);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class TooManyRequestsException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Too Many Requests', HttpStatus.TOO_MANY_REQUESTS);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class UnauthorizedException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Unauthorized', HttpStatus.UNAUTHORIZED);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class UnprocessableEntityException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Unprocessable Entity', HttpStatus.UNPROCESSABLE_ENTITY);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class UnsupportedMediaTypeException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Unsupported Media Type', HttpStatus.UNSUPPORTED_MEDIA_TYPE);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class ValidationException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Validation Failed', HttpStatus.UNPROCESSABLE_ENTITY);\n }\n}\n","import { Logger } from '@nestjs/common';\nimport type { R2ProvisionerConfig, ScopedTokenResult, StorageProvisioner } from '../types';\n\nconst CF_API = 'https://api.cloudflare.com/client/v4';\n\n// Bucket-level group: read/write/list objects in named buckets. Deliberately NOT 'Workers R2 Storage Write', which is\n// account-level and can create and delete buckets — exactly what a tenant credential must never do.\nconst BUCKET_ITEM_WRITE = 'Workers R2 Storage Bucket Item Write';\n\ninterface CloudflareEnvelope<T> {\n success: boolean;\n errors: { code: number; message: string }[];\n result: T;\n}\n\n// Generic R2 admin client: create/delete buckets, toggle a bucket's Cloudflare-managed public domain, and mint/revoke\n// bucket-scoped credentials. It has NO notion of organizations — bucket naming, which buckets a tenant gets, and how\n// the stored descriptor is assembled all live in the caller. Every operation is a Cloudflare REST call.\nexport class R2BucketProvisioner implements StorageProvisioner {\n private readonly logger = new Logger(R2BucketProvisioner.name);\n private readonly jurisdiction: string;\n private bucketItemWriteGroupId: string | null = null;\n\n constructor(private readonly config: R2ProvisionerConfig) {\n this.jurisdiction = config.jurisdiction ?? 'default';\n }\n\n // Creates one bucket, treating an existing bucket as success so provisioning can be re-run to reconcile\n async createBucket(name: string): Promise<void> {\n const response = await fetch(`${CF_API}/accounts/${this.config.accountId}/r2/buckets`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.config.adminToken}`,\n 'Content-Type': 'application/json',\n 'cf-r2-jurisdiction': this.jurisdiction,\n },\n body: JSON.stringify({\n name,\n ...(this.config.locationHint && { locationHint: this.config.locationHint }),\n }),\n });\n\n const body = (await response.json()) as CloudflareEnvelope<unknown>;\n if (body.success) {\n this.logger.log(`Created bucket ${name}`);\n return;\n }\n\n // 10004 is \"bucket already exists\" — two provisioning attempts racing, or a reconcile pass\n if (body.errors?.some((e) => e.code === 10004)) return;\n throw new Error(`Cloudflare bucket create failed for ${name}: ${this.describe(body)}`);\n }\n\n // Deletes one bucket. R2 refuses to delete a bucket that still holds objects, so the caller must have emptied it\n // first — a 'not empty' failure here means that step did not finish. A missing bucket counts as success.\n async deleteBucket(name: string): Promise<void> {\n const response = await fetch(`${CF_API}/accounts/${this.config.accountId}/r2/buckets/${name}`, {\n method: 'DELETE',\n headers: { Authorization: `Bearer ${this.config.adminToken}` },\n });\n\n const body = (await response.json()) as CloudflareEnvelope<unknown>;\n // 10006 is \"bucket not found\" — already gone, which is the state we wanted\n if (body.success || body.errors?.some((e) => e.code === 10006)) {\n this.logger.log(`Deleted bucket ${name}`);\n return;\n }\n throw new Error(`Cloudflare bucket delete failed for ${name}: ${this.describe(body)}`);\n }\n\n // Turns on the bucket's Cloudflare-managed domain and returns its https URL. NOTE: r2.dev is rate limited and\n // documented as non-production — sustained traffic gets 429s. A custom domain per bucket is the production answer,\n // and drops into this same field. Throws on failure; the caller decides whether that is fatal.\n async enablePublicAccess(bucket: string): Promise<string> {\n const response = await fetch(`${CF_API}/accounts/${this.config.accountId}/r2/buckets/${bucket}/domains/managed`, {\n method: 'PUT',\n headers: { Authorization: `Bearer ${this.config.adminToken}`, 'Content-Type': 'application/json' },\n body: JSON.stringify({ enabled: true }),\n });\n\n const body = (await response.json()) as CloudflareEnvelope<{ domain?: string; enabled?: boolean }>;\n if (!body.success || !body.result?.domain) {\n throw new Error(`Cloudflare enable public access failed for ${bucket}: ${this.describe(body)}`);\n }\n\n this.logger.log(`Enabled public access for ${bucket} → ${body.result.domain}`);\n return `https://${body.result.domain}`;\n }\n\n // Mints an ACCOUNT-owned token that can only touch the named buckets. Account-owned rather than user-owned so a\n // tenant's credential does not die with whichever Cloudflare user happened to create the parent token. Requires the\n // parent to hold `Account API Tokens Write`; a user-scoped `API Tokens: Edit` token is rejected here with 9109.\n // `name` is the token's display label; the value is returned by R2 exactly once.\n async createScopedToken(name: string, buckets: string[]): Promise<ScopedTokenResult> {\n const permissionGroupId = await this.resolveBucketItemWriteGroupId();\n\n // The jurisdiction is embedded in the resource key and must match the bucket's, or the token authenticates fine\n // and then 403s on every object because it is scoped to a bucket that does not exist\n const resources = Object.fromEntries(\n buckets.map((bucket) => [\n `com.cloudflare.edge.r2.bucket.${this.config.accountId}_${this.jurisdiction}_${bucket}`,\n '*',\n ]),\n );\n\n const response = await fetch(`${CF_API}/accounts/${this.config.accountId}/tokens`, {\n method: 'POST',\n headers: { Authorization: `Bearer ${this.config.tokensToken}`, 'Content-Type': 'application/json' },\n body: JSON.stringify({\n name,\n policies: [{ effect: 'allow', permission_groups: [{ id: permissionGroupId }], resources }],\n }),\n });\n\n const body = (await response.json()) as CloudflareEnvelope<{ id: string; value: string }>;\n if (!body.success || !body.result?.value) {\n throw new Error(`Cloudflare token create failed for ${name}: ${this.describe(body)}`);\n }\n\n this.logger.log(`Minted storage credential ${name} scoped to ${buckets.length} bucket(s)`);\n return body.result;\n }\n\n // Revokes a credential by its access key id, which on R2 is the Cloudflare token id\n async deleteCredential(accessKeyId: string): Promise<void> {\n const response = await fetch(`${CF_API}/accounts/${this.config.accountId}/tokens/${accessKeyId}`, {\n method: 'DELETE',\n headers: { Authorization: `Bearer ${this.config.tokensToken}` },\n });\n\n const body = (await response.json()) as CloudflareEnvelope<unknown>;\n if (!body.success) {\n throw new Error(`Cloudflare token delete failed for ${accessKeyId}: ${this.describe(body)}`);\n }\n\n this.logger.log(`Revoked storage credential ${accessKeyId}`);\n }\n\n // The create-token API takes permission group UUIDs, not names. Looked up once rather than hardcoded: a UUID copied\n // from docs fails much later with an error that says nothing useful. Same account-scoped endpoint as creation.\n private async resolveBucketItemWriteGroupId(): Promise<string> {\n if (this.bucketItemWriteGroupId) return this.bucketItemWriteGroupId;\n\n const response = await fetch(`${CF_API}/accounts/${this.config.accountId}/tokens/permission_groups`, {\n headers: { Authorization: `Bearer ${this.config.tokensToken}` },\n });\n\n const body = (await response.json()) as CloudflareEnvelope<{ id: string; name: string }[]>;\n const group = body.result?.find((g) => g.name === BUCKET_ITEM_WRITE);\n if (!group) {\n throw new Error(`Cloudflare permission group '${BUCKET_ITEM_WRITE}' not found: ${this.describe(body)}`);\n }\n\n this.bucketItemWriteGroupId = group.id;\n return group.id;\n }\n\n private describe(body: CloudflareEnvelope<unknown>): string {\n return body.errors?.map((e) => `${e.code} ${e.message}`).join('; ') || 'unknown error';\n }\n}\n","import { readConfigSource, type StorageConfigSource } from './config-source';\nimport { R2StorageProvider } from './providers/r2-storage.provider';\nimport type { R2StorageConfig, StorageProvider } from './types';\n\nexport interface StorageFactoryOptions {\n r2?: StorageConfigSource<R2StorageConfig>;\n}\n\nexport class StorageFactory {\n private readonly cache = new Map<string, StorageProvider>();\n\n constructor(private readonly options: StorageFactoryOptions) {}\n\n // Resolves a provider by name, constructing it once on first use\n resolve(provider: string): StorageProvider {\n const cached = this.cache.get(provider);\n if (cached) return cached;\n\n const created = this.create(provider);\n this.cache.set(provider, created);\n return created;\n }\n\n private create(provider: string): StorageProvider {\n switch (provider) {\n case 'r2':\n return new R2StorageProvider(readConfigSource(this.options.r2, 'r2'));\n default:\n throw new Error(`Unsupported storage provider: ${provider}`);\n }\n }\n}\n","import { readConfigSource, type StorageConfigSource } from './config-source';\nimport { R2BucketProvisioner } from './provisioners/r2-bucket.provisioner';\nimport type { R2ProvisionerConfig, StorageProvisioner } from './types';\n\nexport interface StorageProvisionerFactoryOptions {\n r2?: StorageConfigSource<R2ProvisionerConfig>;\n}\n\n// Mirrors StorageFactory for the admin side: same provider names, same lazy config, different job. Resolves a generic\n// StorageProvisioner (bucket + credential admin) by provider name so the caller is not pinned to a single backend.\nexport class StorageProvisionerFactory {\n private readonly cache = new Map<string, StorageProvisioner>();\n\n constructor(private readonly options: StorageProvisionerFactoryOptions) {}\n\n // Resolves a provisioner by provider name, constructing it once on first use\n resolve(provider: string): StorageProvisioner {\n const cached = this.cache.get(provider);\n if (cached) return cached;\n\n const created = this.create(provider);\n this.cache.set(provider, created);\n return created;\n }\n\n private create(provider: string): StorageProvisioner {\n switch (provider) {\n case 'r2':\n return new R2BucketProvisioner(readConfigSource(this.options.r2, 'r2'));\n default:\n throw new Error(`Unsupported storage provisioner: ${provider}`);\n }\n }\n}\n"],"mappings":"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","names":["CF_GRAPHQL","QUERY","BucketUsageReader","logger","Logger","name","config","getBucketUsage","bucketName","windowHours","end","Date","start","getTime","response","fetch","method","headers","Authorization","analyticsToken","body","JSON","stringify","query","variables","accountTag","accountId","toISOString","json","errors","length","Error","map","e","message","join","sample","data","viewer","accounts","r2StorageAdaptiveGroups","max","debug","bytes","objectCount","payloadSize","metadataSize","readConfigSource","source","provider","undefined","Error","import_common","import_common","import_common","HttpProblemException","HttpException","detailOrOptions","httpStatus","options","detail","type","label","errors","import_common","import_common","import_common","import_common","import_common","import_common","import_common","import_common","NotFoundException","HttpProblemException","detailOrOptions","HttpStatus","NOT_FOUND","import_common","import_common","import_common","import_common","import_common","import_common","import_common","import_common","import_common","R2StorageProvider","logger","Logger","name","client","config","S3Client","region","endpoint","accountId","credentials","accessKeyId","secretAccessKey","upload","params","send","PutObjectCommand","Bucket","requireBucket","bucket","defaultBucket","Key","key","Body","body","ContentType","contentType","log","uploadPublic","publicBucket","url","getPublicUrl","publicUrl","Error","delete","DeleteObjectCommand","getSignedUrl","expiresInSeconds","command","GetObjectCommand","expiresIn","getStream","response","NotFoundException","listObjects","continuationToken","ListObjectsV2Command","ContinuationToken","objects","Contents","map","o","size","Size","lastModified","LastModified","Date","nextToken","NextContinuationToken","fallback","resolved","import_common","CF_API","BUCKET_ITEM_WRITE","R2BucketProvisioner","logger","Logger","name","jurisdiction","bucketItemWriteGroupId","config","createBucket","response","fetch","accountId","method","headers","Authorization","adminToken","body","JSON","stringify","locationHint","json","success","log","errors","some","e","code","Error","describe","deleteBucket","enablePublicAccess","bucket","enabled","result","domain","createScopedToken","buckets","permissionGroupId","resolveBucketItemWriteGroupId","resources","Object","fromEntries","map","tokensToken","policies","effect","permission_groups","id","value","length","deleteCredential","accessKeyId","group","find","g","message","join","StorageFactory","cache","Map","options","resolve","provider","cached","get","created","create","set","R2StorageProvider","readConfigSource","r2","Error","StorageProvisionerFactory","cache","Map","options","resolve","provider","cached","get","created","create","set","R2BucketProvisioner","readConfigSource","r2","Error"]}
@@ -42,25 +42,17 @@ interface StorageProvider {
42
42
  getStream(key: string, bucket?: string): Promise<Readable>;
43
43
  listObjects(bucket: string, continuationToken?: string): Promise<ListObjectsPage>;
44
44
  }
45
- interface OrgBuckets {
46
- storageBucket: string;
47
- storagePublicBucket: string;
48
- }
49
- interface OrgCredential {
50
- accessKeyId: string;
51
- secretAccessKey: string;
52
- }
53
- interface OrgStorageProvisioner {
54
- provisionOrg(subdomain: string): Promise<OrgStorage>;
55
- rotateCredential(subdomain: string, buckets: OrgBuckets): Promise<OrgCredential>;
56
- deleteOrgBuckets(buckets: OrgBuckets): Promise<void>;
45
+ interface ScopedTokenResult {
46
+ id: string;
47
+ value: string;
48
+ }
49
+ interface StorageProvisioner {
50
+ createBucket(name: string): Promise<void>;
51
+ deleteBucket(name: string): Promise<void>;
52
+ enablePublicAccess(bucket: string): Promise<string>;
53
+ createScopedToken(name: string, buckets: string[]): Promise<ScopedTokenResult>;
57
54
  deleteCredential(accessKeyId: string): Promise<void>;
58
55
  }
59
- interface OrgStorageTracking {
60
- bucket: string;
61
- publicBucket: string;
62
- accessKeyId: string | null;
63
- }
64
56
  interface R2ProvisionerConfig {
65
57
  accountId: string;
66
58
  adminToken: string;
@@ -68,16 +60,6 @@ interface R2ProvisionerConfig {
68
60
  locationHint?: string;
69
61
  jurisdiction?: string;
70
62
  }
71
- interface OrgStorage {
72
- provider: 'r2';
73
- accountId: string;
74
- bucket: string;
75
- publicBucket: string;
76
- publicUrl: string | null;
77
- accessKeyId: string;
78
- secretAccessKey: string;
79
- createdAt: string;
80
- }
81
63
  interface R2StorageConfig {
82
64
  accountId: string;
83
65
  accessKeyId: string;
@@ -102,31 +84,17 @@ declare class R2StorageProvider implements StorageProvider {
102
84
  private requireBucket;
103
85
  }
104
86
 
105
- interface OrgStorageProvisionerFactoryOptions {
106
- r2?: StorageConfigSource<R2ProvisionerConfig>;
107
- }
108
- declare class OrgStorageProvisionerFactory {
109
- private readonly options;
110
- private readonly cache;
111
- constructor(options: OrgStorageProvisionerFactoryOptions);
112
- resolve(provider: string): OrgStorageProvisioner;
113
- private create;
114
- }
115
-
116
- declare function orgBucketNames(subdomain: string): OrgBuckets;
117
- declare class R2BucketProvisioner implements OrgStorageProvisioner {
87
+ declare class R2BucketProvisioner implements StorageProvisioner {
118
88
  private readonly config;
119
89
  private readonly logger;
120
90
  private readonly jurisdiction;
121
91
  private bucketItemWriteGroupId;
122
92
  constructor(config: R2ProvisionerConfig);
123
- provisionOrg(subdomain: string): Promise<OrgStorage>;
124
- rotateCredential(subdomain: string, buckets: OrgBuckets): Promise<OrgCredential>;
125
- deleteOrgBuckets(buckets: OrgBuckets): Promise<void>;
93
+ createBucket(name: string): Promise<void>;
94
+ deleteBucket(name: string): Promise<void>;
95
+ enablePublicAccess(bucket: string): Promise<string>;
96
+ createScopedToken(name: string, buckets: string[]): Promise<ScopedTokenResult>;
126
97
  deleteCredential(accessKeyId: string): Promise<void>;
127
- private createBucket;
128
- private enablePublicAccess;
129
- private createScopedToken;
130
98
  private resolveBucketItemWriteGroupId;
131
99
  private describe;
132
100
  }
@@ -142,4 +110,15 @@ declare class StorageFactory {
142
110
  private create;
143
111
  }
144
112
 
145
- export { type BucketUsage, BucketUsageReader, type BucketUsageReaderConfig, type ListObjectsPage, type OrgBuckets, type OrgCredential, type OrgStorage, type OrgStorageProvisioner, OrgStorageProvisionerFactory, type OrgStorageProvisionerFactoryOptions, type OrgStorageTracking, R2BucketProvisioner, type R2ProvisionerConfig, type R2StorageConfig, R2StorageProvider, type StorageConfigSource, StorageFactory, type StorageFactoryOptions, type StorageProvider, type StoredObject, type UploadParams, orgBucketNames, readConfigSource };
113
+ interface StorageProvisionerFactoryOptions {
114
+ r2?: StorageConfigSource<R2ProvisionerConfig>;
115
+ }
116
+ declare class StorageProvisionerFactory {
117
+ private readonly options;
118
+ private readonly cache;
119
+ constructor(options: StorageProvisionerFactoryOptions);
120
+ resolve(provider: string): StorageProvisioner;
121
+ private create;
122
+ }
123
+
124
+ export { type BucketUsage, BucketUsageReader, type BucketUsageReaderConfig, type ListObjectsPage, R2BucketProvisioner, type R2ProvisionerConfig, type R2StorageConfig, R2StorageProvider, type ScopedTokenResult, type StorageConfigSource, StorageFactory, type StorageFactoryOptions, type StorageProvider, type StorageProvisioner, StorageProvisionerFactory, type StorageProvisionerFactoryOptions, type StoredObject, type UploadParams, readConfigSource };
package/dist/storage.d.ts CHANGED
@@ -42,25 +42,17 @@ interface StorageProvider {
42
42
  getStream(key: string, bucket?: string): Promise<Readable>;
43
43
  listObjects(bucket: string, continuationToken?: string): Promise<ListObjectsPage>;
44
44
  }
45
- interface OrgBuckets {
46
- storageBucket: string;
47
- storagePublicBucket: string;
48
- }
49
- interface OrgCredential {
50
- accessKeyId: string;
51
- secretAccessKey: string;
52
- }
53
- interface OrgStorageProvisioner {
54
- provisionOrg(subdomain: string): Promise<OrgStorage>;
55
- rotateCredential(subdomain: string, buckets: OrgBuckets): Promise<OrgCredential>;
56
- deleteOrgBuckets(buckets: OrgBuckets): Promise<void>;
45
+ interface ScopedTokenResult {
46
+ id: string;
47
+ value: string;
48
+ }
49
+ interface StorageProvisioner {
50
+ createBucket(name: string): Promise<void>;
51
+ deleteBucket(name: string): Promise<void>;
52
+ enablePublicAccess(bucket: string): Promise<string>;
53
+ createScopedToken(name: string, buckets: string[]): Promise<ScopedTokenResult>;
57
54
  deleteCredential(accessKeyId: string): Promise<void>;
58
55
  }
59
- interface OrgStorageTracking {
60
- bucket: string;
61
- publicBucket: string;
62
- accessKeyId: string | null;
63
- }
64
56
  interface R2ProvisionerConfig {
65
57
  accountId: string;
66
58
  adminToken: string;
@@ -68,16 +60,6 @@ interface R2ProvisionerConfig {
68
60
  locationHint?: string;
69
61
  jurisdiction?: string;
70
62
  }
71
- interface OrgStorage {
72
- provider: 'r2';
73
- accountId: string;
74
- bucket: string;
75
- publicBucket: string;
76
- publicUrl: string | null;
77
- accessKeyId: string;
78
- secretAccessKey: string;
79
- createdAt: string;
80
- }
81
63
  interface R2StorageConfig {
82
64
  accountId: string;
83
65
  accessKeyId: string;
@@ -102,31 +84,17 @@ declare class R2StorageProvider implements StorageProvider {
102
84
  private requireBucket;
103
85
  }
104
86
 
105
- interface OrgStorageProvisionerFactoryOptions {
106
- r2?: StorageConfigSource<R2ProvisionerConfig>;
107
- }
108
- declare class OrgStorageProvisionerFactory {
109
- private readonly options;
110
- private readonly cache;
111
- constructor(options: OrgStorageProvisionerFactoryOptions);
112
- resolve(provider: string): OrgStorageProvisioner;
113
- private create;
114
- }
115
-
116
- declare function orgBucketNames(subdomain: string): OrgBuckets;
117
- declare class R2BucketProvisioner implements OrgStorageProvisioner {
87
+ declare class R2BucketProvisioner implements StorageProvisioner {
118
88
  private readonly config;
119
89
  private readonly logger;
120
90
  private readonly jurisdiction;
121
91
  private bucketItemWriteGroupId;
122
92
  constructor(config: R2ProvisionerConfig);
123
- provisionOrg(subdomain: string): Promise<OrgStorage>;
124
- rotateCredential(subdomain: string, buckets: OrgBuckets): Promise<OrgCredential>;
125
- deleteOrgBuckets(buckets: OrgBuckets): Promise<void>;
93
+ createBucket(name: string): Promise<void>;
94
+ deleteBucket(name: string): Promise<void>;
95
+ enablePublicAccess(bucket: string): Promise<string>;
96
+ createScopedToken(name: string, buckets: string[]): Promise<ScopedTokenResult>;
126
97
  deleteCredential(accessKeyId: string): Promise<void>;
127
- private createBucket;
128
- private enablePublicAccess;
129
- private createScopedToken;
130
98
  private resolveBucketItemWriteGroupId;
131
99
  private describe;
132
100
  }
@@ -142,4 +110,15 @@ declare class StorageFactory {
142
110
  private create;
143
111
  }
144
112
 
145
- export { type BucketUsage, BucketUsageReader, type BucketUsageReaderConfig, type ListObjectsPage, type OrgBuckets, type OrgCredential, type OrgStorage, type OrgStorageProvisioner, OrgStorageProvisionerFactory, type OrgStorageProvisionerFactoryOptions, type OrgStorageTracking, R2BucketProvisioner, type R2ProvisionerConfig, type R2StorageConfig, R2StorageProvider, type StorageConfigSource, StorageFactory, type StorageFactoryOptions, type StorageProvider, type StoredObject, type UploadParams, orgBucketNames, readConfigSource };
113
+ interface StorageProvisionerFactoryOptions {
114
+ r2?: StorageConfigSource<R2ProvisionerConfig>;
115
+ }
116
+ declare class StorageProvisionerFactory {
117
+ private readonly options;
118
+ private readonly cache;
119
+ constructor(options: StorageProvisionerFactoryOptions);
120
+ resolve(provider: string): StorageProvisioner;
121
+ private create;
122
+ }
123
+
124
+ export { type BucketUsage, BucketUsageReader, type BucketUsageReaderConfig, type ListObjectsPage, R2BucketProvisioner, type R2ProvisionerConfig, type R2StorageConfig, R2StorageProvider, type ScopedTokenResult, type StorageConfigSource, StorageFactory, type StorageFactoryOptions, type StorageProvider, type StorageProvisioner, StorageProvisionerFactory, type StorageProvisionerFactoryOptions, type StoredObject, type UploadParams, readConfigSource };
package/dist/storage.js CHANGED
@@ -268,17 +268,9 @@ var R2StorageProvider = class _R2StorageProvider {
268
268
  };
269
269
 
270
270
  // src/storage/provisioners/r2-bucket.provisioner.ts
271
- import { createHash } from "crypto";
272
271
  import { Logger as Logger3 } from "@nestjs/common";
273
272
  var CF_API = "https://api.cloudflare.com/client/v4";
274
273
  var BUCKET_ITEM_WRITE = "Workers R2 Storage Bucket Item Write";
275
- function orgBucketNames(subdomain) {
276
- return {
277
- storageBucket: `org-${subdomain}`,
278
- storagePublicBucket: `org-${subdomain}-public`
279
- };
280
- }
281
- __name(orgBucketNames, "orgBucketNames");
282
274
  var R2BucketProvisioner = class _R2BucketProvisioner {
283
275
  static {
284
276
  __name(this, "R2BucketProvisioner");
@@ -291,84 +283,6 @@ var R2BucketProvisioner = class _R2BucketProvisioner {
291
283
  this.config = config;
292
284
  this.jurisdiction = config.jurisdiction ?? "default";
293
285
  }
294
- // Creates the org's two buckets and one credential scoped to just those buckets
295
- async provisionOrg(subdomain) {
296
- const names = orgBucketNames(subdomain);
297
- await this.createBucket(names.storageBucket);
298
- await this.createBucket(names.storagePublicBucket);
299
- let publicUrl = null;
300
- try {
301
- publicUrl = await this.enablePublicAccess(names.storagePublicBucket);
302
- } catch (error) {
303
- this.logger.warn(`Public access not enabled for ${names.storagePublicBucket}: ${error}`);
304
- }
305
- const token = await this.createScopedToken(subdomain, [
306
- names.storageBucket,
307
- names.storagePublicBucket
308
- ]);
309
- return {
310
- provider: "r2",
311
- accountId: this.config.accountId,
312
- bucket: names.storageBucket,
313
- publicBucket: names.storagePublicBucket,
314
- publicUrl,
315
- accessKeyId: token.id,
316
- // R2 derives the S3 pair from the token: key id = token id, secret = SHA-256 of the token value. The value is
317
- // returned exactly once, so it is hashed here rather than handed back to a caller that might drop it.
318
- secretAccessKey: createHash("sha256").update(token.value).digest("hex"),
319
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
320
- };
321
- }
322
- // Mints a replacement credential scoped to the same buckets. Returns only the credential pair, not a whole
323
- // descriptor: the control plane does not hold the org's publicUrl, so it is in no position to rebuild one.
324
- //
325
- // The old token is NOT revoked here. Revoking before the caller has persisted the new credential would leave the org
326
- // with no working key in the gap; deleteCredential is a separate call, made once the new one is stored.
327
- async rotateCredential(subdomain, buckets) {
328
- const token = await this.createScopedToken(subdomain, [
329
- buckets.storageBucket,
330
- buckets.storagePublicBucket
331
- ]);
332
- return {
333
- accessKeyId: token.id,
334
- secretAccessKey: createHash("sha256").update(token.value).digest("hex")
335
- };
336
- }
337
- // Removes an org's buckets. R2 refuses to delete a bucket that still holds objects, so the uploading server must
338
- // have emptied them first — a 'not empty' failure here means that step did not finish.
339
- async deleteOrgBuckets(buckets) {
340
- for (const bucket of [
341
- buckets.storageBucket,
342
- buckets.storagePublicBucket
343
- ]) {
344
- const response = await fetch(`${CF_API}/accounts/${this.config.accountId}/r2/buckets/${bucket}`, {
345
- method: "DELETE",
346
- headers: {
347
- Authorization: `Bearer ${this.config.adminToken}`
348
- }
349
- });
350
- const body = await response.json();
351
- if (body.success || body.errors?.some((e) => e.code === 10006)) {
352
- this.logger.log(`Deleted bucket ${bucket}`);
353
- continue;
354
- }
355
- throw new Error(`Cloudflare bucket delete failed for ${bucket}: ${this.describe(body)}`);
356
- }
357
- }
358
- // Revokes a credential by its access key id, which on R2 is the Cloudflare token id
359
- async deleteCredential(accessKeyId) {
360
- const response = await fetch(`${CF_API}/accounts/${this.config.accountId}/tokens/${accessKeyId}`, {
361
- method: "DELETE",
362
- headers: {
363
- Authorization: `Bearer ${this.config.tokensToken}`
364
- }
365
- });
366
- const body = await response.json();
367
- if (!body.success) {
368
- throw new Error(`Cloudflare token delete failed for ${accessKeyId}: ${this.describe(body)}`);
369
- }
370
- this.logger.log(`Revoked storage credential ${accessKeyId}`);
371
- }
372
286
  // Creates one bucket, treating an existing bucket as success so provisioning can be re-run to reconcile
373
287
  async createBucket(name) {
374
288
  const response = await fetch(`${CF_API}/accounts/${this.config.accountId}/r2/buckets`, {
@@ -393,9 +307,25 @@ var R2BucketProvisioner = class _R2BucketProvisioner {
393
307
  if (body.errors?.some((e) => e.code === 10004)) return;
394
308
  throw new Error(`Cloudflare bucket create failed for ${name}: ${this.describe(body)}`);
395
309
  }
396
- // Turns on the bucket's Cloudflare-managed domain and returns it. NOTE: r2.dev is rate limited and documented as
397
- // non-productionsustained traffic gets 429s. A custom domain per bucket is the production answer, and drops into
398
- // this same field.
310
+ // Deletes one bucket. R2 refuses to delete a bucket that still holds objects, so the caller must have emptied it
311
+ // firsta 'not empty' failure here means that step did not finish. A missing bucket counts as success.
312
+ async deleteBucket(name) {
313
+ const response = await fetch(`${CF_API}/accounts/${this.config.accountId}/r2/buckets/${name}`, {
314
+ method: "DELETE",
315
+ headers: {
316
+ Authorization: `Bearer ${this.config.adminToken}`
317
+ }
318
+ });
319
+ const body = await response.json();
320
+ if (body.success || body.errors?.some((e) => e.code === 10006)) {
321
+ this.logger.log(`Deleted bucket ${name}`);
322
+ return;
323
+ }
324
+ throw new Error(`Cloudflare bucket delete failed for ${name}: ${this.describe(body)}`);
325
+ }
326
+ // Turns on the bucket's Cloudflare-managed domain and returns its https URL. NOTE: r2.dev is rate limited and
327
+ // documented as non-production — sustained traffic gets 429s. A custom domain per bucket is the production answer,
328
+ // and drops into this same field. Throws on failure; the caller decides whether that is fatal.
399
329
  async enablePublicAccess(bucket) {
400
330
  const response = await fetch(`${CF_API}/accounts/${this.config.accountId}/r2/buckets/${bucket}/domains/managed`, {
401
331
  method: "PUT",
@@ -414,10 +344,11 @@ var R2BucketProvisioner = class _R2BucketProvisioner {
414
344
  this.logger.log(`Enabled public access for ${bucket} \u2192 ${body.result.domain}`);
415
345
  return `https://${body.result.domain}`;
416
346
  }
417
- // Mints an ACCOUNT-owned token that can only touch this org's buckets. Account-owned rather than user-owned so a
347
+ // Mints an ACCOUNT-owned token that can only touch the named buckets. Account-owned rather than user-owned so a
418
348
  // tenant's credential does not die with whichever Cloudflare user happened to create the parent token. Requires the
419
349
  // parent to hold `Account API Tokens Write`; a user-scoped `API Tokens: Edit` token is rejected here with 9109.
420
- async createScopedToken(subdomain, buckets) {
350
+ // `name` is the token's display label; the value is returned by R2 exactly once.
351
+ async createScopedToken(name, buckets) {
421
352
  const permissionGroupId = await this.resolveBucketItemWriteGroupId();
422
353
  const resources = Object.fromEntries(buckets.map((bucket) => [
423
354
  `com.cloudflare.edge.r2.bucket.${this.config.accountId}_${this.jurisdiction}_${bucket}`,
@@ -430,7 +361,7 @@ var R2BucketProvisioner = class _R2BucketProvisioner {
430
361
  "Content-Type": "application/json"
431
362
  },
432
363
  body: JSON.stringify({
433
- name: `org-${subdomain}`,
364
+ name,
434
365
  policies: [
435
366
  {
436
367
  effect: "allow",
@@ -446,11 +377,25 @@ var R2BucketProvisioner = class _R2BucketProvisioner {
446
377
  });
447
378
  const body = await response.json();
448
379
  if (!body.success || !body.result?.value) {
449
- throw new Error(`Cloudflare token create failed for org-${subdomain}: ${this.describe(body)}`);
380
+ throw new Error(`Cloudflare token create failed for ${name}: ${this.describe(body)}`);
450
381
  }
451
- this.logger.log(`Minted storage credential for org-${subdomain} scoped to ${buckets.length} bucket(s)`);
382
+ this.logger.log(`Minted storage credential ${name} scoped to ${buckets.length} bucket(s)`);
452
383
  return body.result;
453
384
  }
385
+ // Revokes a credential by its access key id, which on R2 is the Cloudflare token id
386
+ async deleteCredential(accessKeyId) {
387
+ const response = await fetch(`${CF_API}/accounts/${this.config.accountId}/tokens/${accessKeyId}`, {
388
+ method: "DELETE",
389
+ headers: {
390
+ Authorization: `Bearer ${this.config.tokensToken}`
391
+ }
392
+ });
393
+ const body = await response.json();
394
+ if (!body.success) {
395
+ throw new Error(`Cloudflare token delete failed for ${accessKeyId}: ${this.describe(body)}`);
396
+ }
397
+ this.logger.log(`Revoked storage credential ${accessKeyId}`);
398
+ }
454
399
  // The create-token API takes permission group UUIDs, not names. Looked up once rather than hardcoded: a UUID copied
455
400
  // from docs fails much later with an error that says nothing useful. Same account-scoped endpoint as creation.
456
401
  async resolveBucketItemWriteGroupId() {
@@ -473,17 +418,17 @@ var R2BucketProvisioner = class _R2BucketProvisioner {
473
418
  }
474
419
  };
475
420
 
476
- // src/storage/provisioner.factory.ts
477
- var OrgStorageProvisionerFactory = class {
421
+ // src/storage/storage.factory.ts
422
+ var StorageFactory = class {
478
423
  static {
479
- __name(this, "OrgStorageProvisionerFactory");
424
+ __name(this, "StorageFactory");
480
425
  }
481
426
  options;
482
427
  cache = /* @__PURE__ */ new Map();
483
428
  constructor(options) {
484
429
  this.options = options;
485
430
  }
486
- // Resolves a provisioner by provider name, constructing it once on first use
431
+ // Resolves a provider by name, constructing it once on first use
487
432
  resolve(provider) {
488
433
  const cached = this.cache.get(provider);
489
434
  if (cached) return cached;
@@ -494,24 +439,24 @@ var OrgStorageProvisionerFactory = class {
494
439
  create(provider) {
495
440
  switch (provider) {
496
441
  case "r2":
497
- return new R2BucketProvisioner(readConfigSource(this.options.r2, "r2"));
442
+ return new R2StorageProvider(readConfigSource(this.options.r2, "r2"));
498
443
  default:
499
444
  throw new Error(`Unsupported storage provider: ${provider}`);
500
445
  }
501
446
  }
502
447
  };
503
448
 
504
- // src/storage/storage.factory.ts
505
- var StorageFactory = class {
449
+ // src/storage/storage-provisioner.factory.ts
450
+ var StorageProvisionerFactory = class {
506
451
  static {
507
- __name(this, "StorageFactory");
452
+ __name(this, "StorageProvisionerFactory");
508
453
  }
509
454
  options;
510
455
  cache = /* @__PURE__ */ new Map();
511
456
  constructor(options) {
512
457
  this.options = options;
513
458
  }
514
- // Resolves a provider by name, constructing it once on first use
459
+ // Resolves a provisioner by provider name, constructing it once on first use
515
460
  resolve(provider) {
516
461
  const cached = this.cache.get(provider);
517
462
  if (cached) return cached;
@@ -522,19 +467,18 @@ var StorageFactory = class {
522
467
  create(provider) {
523
468
  switch (provider) {
524
469
  case "r2":
525
- return new R2StorageProvider(readConfigSource(this.options.r2, "r2"));
470
+ return new R2BucketProvisioner(readConfigSource(this.options.r2, "r2"));
526
471
  default:
527
- throw new Error(`Unsupported storage provider: ${provider}`);
472
+ throw new Error(`Unsupported storage provisioner: ${provider}`);
528
473
  }
529
474
  }
530
475
  };
531
476
  export {
532
477
  BucketUsageReader,
533
- OrgStorageProvisionerFactory,
534
478
  R2BucketProvisioner,
535
479
  R2StorageProvider,
536
480
  StorageFactory,
537
- orgBucketNames,
481
+ StorageProvisionerFactory,
538
482
  readConfigSource
539
483
  };
540
484
  //# sourceMappingURL=storage.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/storage/bucket-usage.reader.ts","../src/storage/config-source.ts","../src/storage/providers/r2-storage.provider.ts","../src/exceptions/bad-gateway.exception.ts","../src/exceptions/base-field.exception.ts","../src/exceptions/bad-request.exception.ts","../src/exceptions/conflict.exception.ts","../src/exceptions/forbidden.exception.ts","../src/exceptions/gone.exception.ts","../src/exceptions/internal-server-error.exception.ts","../src/exceptions/method-not-allowed.exception.ts","../src/exceptions/not-acceptable.exception.ts","../src/exceptions/not-found.exception.ts","../src/exceptions/not-implemented.exception.ts","../src/exceptions/payload-too-large.exception.ts","../src/exceptions/request-timeout.exception.ts","../src/exceptions/service-unavailable.exception.ts","../src/exceptions/too-many-requests.exception.ts","../src/exceptions/unauthorized.exception.ts","../src/exceptions/unprocessable-entity.exception.ts","../src/exceptions/unsupported-media-type.exception.ts","../src/exceptions/validation.exception.ts","../src/storage/provisioners/r2-bucket.provisioner.ts","../src/storage/provisioner.factory.ts","../src/storage/storage.factory.ts"],"sourcesContent":["import { Logger } from '@nestjs/common';\n\nconst CF_GRAPHQL = 'https://api.cloudflare.com/client/v4/graphql';\n\nexport interface BucketUsage {\n bytes: number;\n objectCount: number;\n}\n\nexport interface BucketUsageReaderConfig {\n accountId: string;\n // Account-level token with Analytics Read. NOT the org's scoped S3 credential and NOT the R2 admin token: the\n // analytics dataset is filtered by accountTag and is not reachable with object-level credentials.\n analyticsToken: string;\n}\n\n// r2StorageAdaptiveGroups is a time series, so the newest bucketed sample is the closest thing to \"current\". It lags\n// real writes by minutes — fine for a periodic quota check, useless for gating an individual upload.\n//\n// `dimensions { datetime }` is required, not decorative: ordering by a field that is neither aggregated nor selected\n// as a dimension is rejected. Keep the query free of `#` comments too — Cloudflare's parser rejects them.\nconst QUERY = `query BucketUsage($accountTag: string!, $bucketName: string, $start: Time, $end: Time) {\n viewer {\n accounts(filter: { accountTag: $accountTag }) {\n r2StorageAdaptiveGroups(\n limit: 1\n filter: { datetime_geq: $start, datetime_leq: $end, bucketName: $bucketName }\n orderBy: [datetime_DESC]\n ) {\n max { objectCount payloadSize metadataSize }\n dimensions { datetime }\n }\n }\n }\n}`;\n\ninterface UsageResponse {\n errors?: { message: string }[];\n data?: {\n viewer?: {\n accounts?: {\n r2StorageAdaptiveGroups?: {\n max?: { objectCount?: number; payloadSize?: number; metadataSize?: number };\n }[];\n }[];\n };\n };\n}\n\n// Reads how much an org's bucket actually holds, straight from the provider — the authoritative figure a locally\n// maintained counter would only ever approximate.\nexport class BucketUsageReader {\n private readonly logger = new Logger(BucketUsageReader.name);\n\n constructor(private readonly config: BucketUsageReaderConfig) {}\n\n // Returns the newest reported sample, or zeroes for a bucket the dataset has not reported on yet (a new or\n // empty bucket produces no rows at all rather than a row of zeroes)\n async getBucketUsage(bucketName: string, windowHours = 24): Promise<BucketUsage> {\n const end = new Date();\n const start = new Date(end.getTime() - windowHours * 60 * 60 * 1000);\n\n const response = await fetch(CF_GRAPHQL, {\n method: 'POST',\n headers: { Authorization: `Bearer ${this.config.analyticsToken}`, 'Content-Type': 'application/json' },\n body: JSON.stringify({\n query: QUERY,\n variables: {\n accountTag: this.config.accountId,\n bucketName,\n start: start.toISOString(),\n end: end.toISOString(),\n },\n }),\n });\n\n const body = (await response.json()) as UsageResponse;\n if (body.errors?.length) {\n throw new Error(\n `Cloudflare usage query failed for ${bucketName}: ${body.errors.map((e) => e.message).join('; ')}`,\n );\n }\n\n const sample = body.data?.viewer?.accounts?.[0]?.r2StorageAdaptiveGroups?.[0]?.max;\n if (!sample) {\n this.logger.debug(`No usage samples yet for bucket ${bucketName}`);\n return { bytes: 0, objectCount: 0 };\n }\n\n // Billed storage is payload plus per-object metadata, so both count against a quota\n return {\n bytes: (sample.payloadSize ?? 0) + (sample.metadataSize ?? 0),\n objectCount: sample.objectCount ?? 0,\n };\n }\n}\n","// Config may be a value or a thunk. Servers pass a thunk when the credentials come from required-only-if-selected\n// env keys, so reading them is deferred to the first resolve() instead of running at module construction.\nexport type StorageConfigSource<T> = T | (() => T);\n\n// A backend configured nowhere is a deployment asking for something it was never given credentials for\nexport function readConfigSource<T>(source: StorageConfigSource<T> | undefined, provider: string): T {\n if (source === undefined) {\n throw new Error(`Storage provider '${provider}' is not configured.`);\n }\n return typeof source === 'function' ? (source as () => T)() : source;\n}\n","import type { Readable } from 'node:stream';\nimport {\n DeleteObjectCommand,\n GetObjectCommand,\n ListObjectsV2Command,\n PutObjectCommand,\n S3Client,\n} from '@aws-sdk/client-s3';\nimport { getSignedUrl } from '@aws-sdk/s3-request-presigner';\nimport { Logger } from '@nestjs/common';\nimport { NotFoundException } from '../../exceptions';\nimport type { ListObjectsPage, R2StorageConfig, StorageProvider, UploadParams } from '../types';\n\nexport class R2StorageProvider implements StorageProvider {\n private readonly logger = new Logger(R2StorageProvider.name);\n private readonly client: S3Client;\n\n constructor(private readonly config: R2StorageConfig) {\n this.client = new S3Client({\n region: 'auto',\n endpoint: `https://${config.accountId}.r2.cloudflarestorage.com`,\n credentials: { accessKeyId: config.accessKeyId, secretAccessKey: config.secretAccessKey },\n });\n }\n\n // Uploads a file buffer or stream\n async upload(params: UploadParams): Promise<string> {\n await this.client.send(\n new PutObjectCommand({\n Bucket: this.requireBucket(params.bucket, this.config.defaultBucket),\n Key: params.key,\n Body: params.body,\n ContentType: params.contentType,\n }),\n );\n\n this.logger.log(`Uploaded file to R2: ${params.key}`);\n return params.key;\n }\n\n // Uploads a file to a public bucket and returns its permanent URL\n async uploadPublic(key: string, body: Buffer, contentType: string, bucket?: string): Promise<string> {\n await this.client.send(\n new PutObjectCommand({\n Bucket: this.requireBucket(bucket, this.config.publicBucket),\n Key: key,\n Body: body,\n ContentType: contentType,\n }),\n );\n\n const url = this.getPublicUrl(key);\n this.logger.log(`Uploaded public file to R2: ${key} → ${url}`);\n return url;\n }\n\n // Public URLs come from the configured custom domain, which maps to one bucket — R2 has no per-bucket URL to derive,\n // so a multi-tenant caller with a bucket per org cannot build one at all\n getPublicUrl(key: string): string {\n if (!this.config.publicUrl) {\n throw new Error('R2 storage has no publicUrl configured; public URLs cannot be built.');\n }\n return `${this.config.publicUrl}/${key}`;\n }\n\n // Deletes a file\n async delete(key: string, bucket?: string): Promise<void> {\n await this.client.send(new DeleteObjectCommand({ Bucket: bucket ?? this.config.defaultBucket, Key: key }));\n\n this.logger.log(`Deleted file from R2: ${key}`);\n }\n\n // Generates a presigned download URL (default 1 hour)\n async getSignedUrl(key: string, expiresInSeconds = 3600, bucket?: string): Promise<string> {\n const command = new GetObjectCommand({ Bucket: this.requireBucket(bucket, this.config.defaultBucket), Key: key });\n return getSignedUrl(this.client, command, { expiresIn: expiresInSeconds });\n }\n\n // Returns a readable stream\n async getStream(key: string, bucket?: string): Promise<Readable> {\n const response = await this.client.send(\n new GetObjectCommand({ Bucket: this.requireBucket(bucket, this.config.defaultBucket), Key: key }),\n );\n\n if (!response.Body) {\n throw new NotFoundException('File not found in storage.');\n }\n\n return response.Body as Readable;\n }\n\n // One page of a bucket's contents. S3 has no \"list everything\" call and no bucket-size call — a full inventory is\n // this looped until nextToken is absent, and each page is a Class A request.\n async listObjects(bucket: string, continuationToken?: string): Promise<ListObjectsPage> {\n const response = await this.client.send(\n new ListObjectsV2Command({ Bucket: bucket, ContinuationToken: continuationToken }),\n );\n\n return {\n objects: (response.Contents ?? []).map((o) => ({\n key: o.Key ?? '',\n size: o.Size ?? 0,\n lastModified: o.LastModified ?? new Date(0),\n })),\n nextToken: response.NextContinuationToken,\n };\n }\n\n // Failing loudly here beats letting a missing org bucket fall through to some other tenant's default\n private requireBucket(bucket: string | undefined, fallback: string | undefined): string {\n const resolved = bucket ?? fallback;\n if (!resolved) {\n throw new Error('No bucket supplied and R2 storage has no default bucket configured.');\n }\n return resolved;\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class BadGatewayException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Bad Gateway', HttpStatus.BAD_GATEWAY);\n }\n}\n","import { HttpException, HttpStatus } from '@nestjs/common';\nimport type { FieldError } from '../types/error-response.types';\n\n// Re-export FieldError for backwards compatibility\nexport type { FieldError } from '../types/error-response.types';\n\nexport interface ProblemOptions {\n type?: string;\n label?: string;\n detail?: string;\n errors?: FieldError[];\n}\n\nexport abstract class HttpProblemException extends HttpException {\n constructor(detailOrOptions: string | ProblemOptions, httpStatus: HttpStatus) {\n const options = typeof detailOrOptions === 'string' ? { detail: detailOrOptions } : detailOrOptions;\n\n super(\n {\n type: options.type ?? 'about:blank',\n label: options.label,\n detail: options.detail,\n errors: options.errors ?? [],\n },\n httpStatus,\n );\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class BadRequestException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Bad Request', HttpStatus.BAD_REQUEST);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class ConflictException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Conflict', HttpStatus.CONFLICT);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class ForbiddenException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Forbidden', HttpStatus.FORBIDDEN);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class GoneException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Gone', HttpStatus.GONE);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class InternalServerErrorException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Internal Server Error', HttpStatus.INTERNAL_SERVER_ERROR);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class MethodNotAllowedException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Method Not Allowed', HttpStatus.METHOD_NOT_ALLOWED);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class NotAcceptableException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Not Acceptable', HttpStatus.NOT_ACCEPTABLE);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class NotFoundException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Not Found', HttpStatus.NOT_FOUND);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class NotImplementedException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Not Implemented', HttpStatus.NOT_IMPLEMENTED);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class PayloadTooLargeException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Payload Too Large', HttpStatus.PAYLOAD_TOO_LARGE);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class RequestTimeoutException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Request Timeout', HttpStatus.REQUEST_TIMEOUT);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class ServiceUnavailableException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Service Unavailable', HttpStatus.SERVICE_UNAVAILABLE);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class TooManyRequestsException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Too Many Requests', HttpStatus.TOO_MANY_REQUESTS);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class UnauthorizedException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Unauthorized', HttpStatus.UNAUTHORIZED);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class UnprocessableEntityException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Unprocessable Entity', HttpStatus.UNPROCESSABLE_ENTITY);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class UnsupportedMediaTypeException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Unsupported Media Type', HttpStatus.UNSUPPORTED_MEDIA_TYPE);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class ValidationException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Validation Failed', HttpStatus.UNPROCESSABLE_ENTITY);\n }\n}\n","import { createHash } from 'node:crypto';\nimport { Logger } from '@nestjs/common';\nimport type { OrgBuckets, OrgCredential, OrgStorage, OrgStorageProvisioner, R2ProvisionerConfig } from '../types';\n\nconst CF_API = 'https://api.cloudflare.com/client/v4';\n\n// Bucket-level group: read/write/list objects in named buckets. Deliberately NOT 'Workers R2 Storage Write', which is\n// account-level and can create and delete buckets — exactly what a tenant credential must never do.\nconst BUCKET_ITEM_WRITE = 'Workers R2 Storage Bucket Item Write';\n\n// Bucket names are prefixed because the account also holds Vritti's own buckets, and a subdomain like \"media\" collides\nexport function orgBucketNames(subdomain: string): OrgBuckets {\n return { storageBucket: `org-${subdomain}`, storagePublicBucket: `org-${subdomain}-public` };\n}\n\ninterface CloudflareEnvelope<T> {\n success: boolean;\n errors: { code: number; message: string }[];\n result: T;\n}\n\nexport class R2BucketProvisioner implements OrgStorageProvisioner {\n private readonly logger = new Logger(R2BucketProvisioner.name);\n private readonly jurisdiction: string;\n private bucketItemWriteGroupId: string | null = null;\n\n constructor(private readonly config: R2ProvisionerConfig) {\n this.jurisdiction = config.jurisdiction ?? 'default';\n }\n\n // Creates the org's two buckets and one credential scoped to just those buckets\n async provisionOrg(subdomain: string): Promise<OrgStorage> {\n const names = orgBucketNames(subdomain);\n await this.createBucket(names.storageBucket);\n await this.createBucket(names.storagePublicBucket);\n\n // Not fatal: an org whose public bucket is private still works for every presigned read, and the URL can be\n // filled in later. Failing the whole signup over a CDN convenience would be the wrong trade.\n let publicUrl: string | null = null;\n try {\n publicUrl = await this.enablePublicAccess(names.storagePublicBucket);\n } catch (error: unknown) {\n this.logger.warn(`Public access not enabled for ${names.storagePublicBucket}: ${error}`);\n }\n\n const token = await this.createScopedToken(subdomain, [names.storageBucket, names.storagePublicBucket]);\n\n return {\n provider: 'r2',\n accountId: this.config.accountId,\n bucket: names.storageBucket,\n publicBucket: names.storagePublicBucket,\n publicUrl,\n accessKeyId: token.id,\n // R2 derives the S3 pair from the token: key id = token id, secret = SHA-256 of the token value. The value is\n // returned exactly once, so it is hashed here rather than handed back to a caller that might drop it.\n secretAccessKey: createHash('sha256').update(token.value).digest('hex'),\n createdAt: new Date().toISOString(),\n };\n }\n\n // Mints a replacement credential scoped to the same buckets. Returns only the credential pair, not a whole\n // descriptor: the control plane does not hold the org's publicUrl, so it is in no position to rebuild one.\n //\n // The old token is NOT revoked here. Revoking before the caller has persisted the new credential would leave the org\n // with no working key in the gap; deleteCredential is a separate call, made once the new one is stored.\n async rotateCredential(subdomain: string, buckets: OrgBuckets): Promise<OrgCredential> {\n const token = await this.createScopedToken(subdomain, [buckets.storageBucket, buckets.storagePublicBucket]);\n\n return {\n accessKeyId: token.id,\n secretAccessKey: createHash('sha256').update(token.value).digest('hex'),\n };\n }\n\n // Removes an org's buckets. R2 refuses to delete a bucket that still holds objects, so the uploading server must\n // have emptied them first — a 'not empty' failure here means that step did not finish.\n async deleteOrgBuckets(buckets: OrgBuckets): Promise<void> {\n for (const bucket of [buckets.storageBucket, buckets.storagePublicBucket]) {\n const response = await fetch(`${CF_API}/accounts/${this.config.accountId}/r2/buckets/${bucket}`, {\n method: 'DELETE',\n headers: { Authorization: `Bearer ${this.config.adminToken}` },\n });\n\n const body = (await response.json()) as CloudflareEnvelope<unknown>;\n // 10006 is \"bucket not found\" — already gone, which is the state we wanted\n if (body.success || body.errors?.some((e) => e.code === 10006)) {\n this.logger.log(`Deleted bucket ${bucket}`);\n continue;\n }\n throw new Error(`Cloudflare bucket delete failed for ${bucket}: ${this.describe(body)}`);\n }\n }\n\n // Revokes a credential by its access key id, which on R2 is the Cloudflare token id\n async deleteCredential(accessKeyId: string): Promise<void> {\n const response = await fetch(`${CF_API}/accounts/${this.config.accountId}/tokens/${accessKeyId}`, {\n method: 'DELETE',\n headers: { Authorization: `Bearer ${this.config.tokensToken}` },\n });\n\n const body = (await response.json()) as CloudflareEnvelope<unknown>;\n if (!body.success) {\n throw new Error(`Cloudflare token delete failed for ${accessKeyId}: ${this.describe(body)}`);\n }\n\n this.logger.log(`Revoked storage credential ${accessKeyId}`);\n }\n\n // Creates one bucket, treating an existing bucket as success so provisioning can be re-run to reconcile\n private async createBucket(name: string): Promise<void> {\n const response = await fetch(`${CF_API}/accounts/${this.config.accountId}/r2/buckets`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.config.adminToken}`,\n 'Content-Type': 'application/json',\n 'cf-r2-jurisdiction': this.jurisdiction,\n },\n body: JSON.stringify({\n name,\n ...(this.config.locationHint && { locationHint: this.config.locationHint }),\n }),\n });\n\n const body = (await response.json()) as CloudflareEnvelope<unknown>;\n if (body.success) {\n this.logger.log(`Created bucket ${name}`);\n return;\n }\n\n // 10004 is \"bucket already exists\" — two provisioning attempts racing, or a reconcile pass\n if (body.errors?.some((e) => e.code === 10004)) return;\n throw new Error(`Cloudflare bucket create failed for ${name}: ${this.describe(body)}`);\n }\n\n // Turns on the bucket's Cloudflare-managed domain and returns it. NOTE: r2.dev is rate limited and documented as\n // non-production — sustained traffic gets 429s. A custom domain per bucket is the production answer, and drops into\n // this same field.\n private async enablePublicAccess(bucket: string): Promise<string | null> {\n const response = await fetch(`${CF_API}/accounts/${this.config.accountId}/r2/buckets/${bucket}/domains/managed`, {\n method: 'PUT',\n headers: { Authorization: `Bearer ${this.config.adminToken}`, 'Content-Type': 'application/json' },\n body: JSON.stringify({ enabled: true }),\n });\n\n const body = (await response.json()) as CloudflareEnvelope<{ domain?: string; enabled?: boolean }>;\n if (!body.success || !body.result?.domain) {\n throw new Error(`Cloudflare enable public access failed for ${bucket}: ${this.describe(body)}`);\n }\n\n this.logger.log(`Enabled public access for ${bucket} → ${body.result.domain}`);\n return `https://${body.result.domain}`;\n }\n\n // Mints an ACCOUNT-owned token that can only touch this org's buckets. Account-owned rather than user-owned so a\n // tenant's credential does not die with whichever Cloudflare user happened to create the parent token. Requires the\n // parent to hold `Account API Tokens Write`; a user-scoped `API Tokens: Edit` token is rejected here with 9109.\n private async createScopedToken(subdomain: string, buckets: string[]): Promise<{ id: string; value: string }> {\n const permissionGroupId = await this.resolveBucketItemWriteGroupId();\n\n // The jurisdiction is embedded in the resource key and must match the bucket's, or the token authenticates fine\n // and then 403s on every object because it is scoped to a bucket that does not exist\n const resources = Object.fromEntries(\n buckets.map((bucket) => [\n `com.cloudflare.edge.r2.bucket.${this.config.accountId}_${this.jurisdiction}_${bucket}`,\n '*',\n ]),\n );\n\n const response = await fetch(`${CF_API}/accounts/${this.config.accountId}/tokens`, {\n method: 'POST',\n headers: { Authorization: `Bearer ${this.config.tokensToken}`, 'Content-Type': 'application/json' },\n body: JSON.stringify({\n name: `org-${subdomain}`,\n policies: [{ effect: 'allow', permission_groups: [{ id: permissionGroupId }], resources }],\n }),\n });\n\n const body = (await response.json()) as CloudflareEnvelope<{ id: string; value: string }>;\n if (!body.success || !body.result?.value) {\n throw new Error(`Cloudflare token create failed for org-${subdomain}: ${this.describe(body)}`);\n }\n\n this.logger.log(`Minted storage credential for org-${subdomain} scoped to ${buckets.length} bucket(s)`);\n return body.result;\n }\n\n // The create-token API takes permission group UUIDs, not names. Looked up once rather than hardcoded: a UUID copied\n // from docs fails much later with an error that says nothing useful. Same account-scoped endpoint as creation.\n private async resolveBucketItemWriteGroupId(): Promise<string> {\n if (this.bucketItemWriteGroupId) return this.bucketItemWriteGroupId;\n\n const response = await fetch(`${CF_API}/accounts/${this.config.accountId}/tokens/permission_groups`, {\n headers: { Authorization: `Bearer ${this.config.tokensToken}` },\n });\n\n const body = (await response.json()) as CloudflareEnvelope<{ id: string; name: string }[]>;\n const group = body.result?.find((g) => g.name === BUCKET_ITEM_WRITE);\n if (!group) {\n throw new Error(`Cloudflare permission group '${BUCKET_ITEM_WRITE}' not found: ${this.describe(body)}`);\n }\n\n this.bucketItemWriteGroupId = group.id;\n return group.id;\n }\n\n private describe(body: CloudflareEnvelope<unknown>): string {\n return body.errors?.map((e) => `${e.code} ${e.message}`).join('; ') || 'unknown error';\n }\n}\n","import { readConfigSource, type StorageConfigSource } from './config-source';\nimport { R2BucketProvisioner } from './provisioners/r2-bucket.provisioner';\nimport type { OrgStorageProvisioner, R2ProvisionerConfig } from './types';\n\nexport interface OrgStorageProvisionerFactoryOptions {\n r2?: StorageConfigSource<R2ProvisionerConfig>;\n}\n\n// Mirrors StorageFactory for the control-plane side: same provider names, same lazy config, different job\nexport class OrgStorageProvisionerFactory {\n private readonly cache = new Map<string, OrgStorageProvisioner>();\n\n constructor(private readonly options: OrgStorageProvisionerFactoryOptions) {}\n\n // Resolves a provisioner by provider name, constructing it once on first use\n resolve(provider: string): OrgStorageProvisioner {\n const cached = this.cache.get(provider);\n if (cached) return cached;\n\n const created = this.create(provider);\n this.cache.set(provider, created);\n return created;\n }\n\n private create(provider: string): OrgStorageProvisioner {\n switch (provider) {\n case 'r2':\n return new R2BucketProvisioner(readConfigSource(this.options.r2, 'r2'));\n default:\n throw new Error(`Unsupported storage provider: ${provider}`);\n }\n }\n}\n","import { readConfigSource, type StorageConfigSource } from './config-source';\nimport { R2StorageProvider } from './providers/r2-storage.provider';\nimport type { R2StorageConfig, StorageProvider } from './types';\n\nexport interface StorageFactoryOptions {\n r2?: StorageConfigSource<R2StorageConfig>;\n}\n\nexport class StorageFactory {\n private readonly cache = new Map<string, StorageProvider>();\n\n constructor(private readonly options: StorageFactoryOptions) {}\n\n // Resolves a provider by name, constructing it once on first use\n resolve(provider: string): StorageProvider {\n const cached = this.cache.get(provider);\n if (cached) return cached;\n\n const created = this.create(provider);\n this.cache.set(provider, created);\n return created;\n }\n\n private create(provider: string): StorageProvider {\n switch (provider) {\n case 'r2':\n return new R2StorageProvider(readConfigSource(this.options.r2, 'r2'));\n default:\n throw new Error(`Unsupported storage provider: ${provider}`);\n }\n }\n}\n"],"mappings":"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","names":["Logger","CF_GRAPHQL","QUERY","BucketUsageReader","logger","Logger","name","config","getBucketUsage","bucketName","windowHours","end","Date","start","getTime","response","fetch","method","headers","Authorization","analyticsToken","body","JSON","stringify","query","variables","accountTag","accountId","toISOString","json","errors","length","Error","map","e","message","join","sample","data","viewer","accounts","r2StorageAdaptiveGroups","max","debug","bytes","objectCount","payloadSize","metadataSize","readConfigSource","source","provider","undefined","Error","DeleteObjectCommand","GetObjectCommand","ListObjectsV2Command","PutObjectCommand","S3Client","getSignedUrl","Logger","HttpStatus","HttpException","HttpProblemException","HttpException","detailOrOptions","httpStatus","options","detail","type","label","errors","HttpStatus","HttpStatus","HttpStatus","HttpStatus","HttpStatus","HttpStatus","HttpStatus","HttpStatus","NotFoundException","HttpProblemException","detailOrOptions","HttpStatus","NOT_FOUND","HttpStatus","HttpStatus","HttpStatus","HttpStatus","HttpStatus","HttpStatus","HttpStatus","HttpStatus","HttpStatus","R2StorageProvider","logger","Logger","name","client","config","S3Client","region","endpoint","accountId","credentials","accessKeyId","secretAccessKey","upload","params","send","PutObjectCommand","Bucket","requireBucket","bucket","defaultBucket","Key","key","Body","body","ContentType","contentType","log","uploadPublic","publicBucket","url","getPublicUrl","publicUrl","Error","delete","DeleteObjectCommand","getSignedUrl","expiresInSeconds","command","GetObjectCommand","expiresIn","getStream","response","NotFoundException","listObjects","continuationToken","ListObjectsV2Command","ContinuationToken","objects","Contents","map","o","size","Size","lastModified","LastModified","Date","nextToken","NextContinuationToken","fallback","resolved","createHash","Logger","CF_API","BUCKET_ITEM_WRITE","orgBucketNames","subdomain","storageBucket","storagePublicBucket","R2BucketProvisioner","logger","Logger","name","jurisdiction","bucketItemWriteGroupId","config","provisionOrg","names","createBucket","publicUrl","enablePublicAccess","error","warn","token","createScopedToken","provider","accountId","bucket","publicBucket","accessKeyId","id","secretAccessKey","createHash","update","value","digest","createdAt","Date","toISOString","rotateCredential","buckets","deleteOrgBuckets","response","fetch","method","headers","Authorization","adminToken","body","json","success","errors","some","e","code","log","Error","describe","deleteCredential","tokensToken","JSON","stringify","locationHint","enabled","result","domain","permissionGroupId","resolveBucketItemWriteGroupId","resources","Object","fromEntries","map","policies","effect","permission_groups","length","group","find","g","message","join","OrgStorageProvisionerFactory","cache","Map","options","resolve","provider","cached","get","created","create","set","R2BucketProvisioner","readConfigSource","r2","Error","StorageFactory","cache","Map","options","resolve","provider","cached","get","created","create","set","R2StorageProvider","readConfigSource","r2","Error"]}
1
+ {"version":3,"sources":["../src/storage/bucket-usage.reader.ts","../src/storage/config-source.ts","../src/storage/providers/r2-storage.provider.ts","../src/exceptions/bad-gateway.exception.ts","../src/exceptions/base-field.exception.ts","../src/exceptions/bad-request.exception.ts","../src/exceptions/conflict.exception.ts","../src/exceptions/forbidden.exception.ts","../src/exceptions/gone.exception.ts","../src/exceptions/internal-server-error.exception.ts","../src/exceptions/method-not-allowed.exception.ts","../src/exceptions/not-acceptable.exception.ts","../src/exceptions/not-found.exception.ts","../src/exceptions/not-implemented.exception.ts","../src/exceptions/payload-too-large.exception.ts","../src/exceptions/request-timeout.exception.ts","../src/exceptions/service-unavailable.exception.ts","../src/exceptions/too-many-requests.exception.ts","../src/exceptions/unauthorized.exception.ts","../src/exceptions/unprocessable-entity.exception.ts","../src/exceptions/unsupported-media-type.exception.ts","../src/exceptions/validation.exception.ts","../src/storage/provisioners/r2-bucket.provisioner.ts","../src/storage/storage.factory.ts","../src/storage/storage-provisioner.factory.ts"],"sourcesContent":["import { Logger } from '@nestjs/common';\n\nconst CF_GRAPHQL = 'https://api.cloudflare.com/client/v4/graphql';\n\nexport interface BucketUsage {\n bytes: number;\n objectCount: number;\n}\n\nexport interface BucketUsageReaderConfig {\n accountId: string;\n // Account-level token with Analytics Read. NOT the org's scoped S3 credential and NOT the R2 admin token: the\n // analytics dataset is filtered by accountTag and is not reachable with object-level credentials.\n analyticsToken: string;\n}\n\n// r2StorageAdaptiveGroups is a time series, so the newest bucketed sample is the closest thing to \"current\". It lags\n// real writes by minutes — fine for a periodic quota check, useless for gating an individual upload.\n//\n// `dimensions { datetime }` is required, not decorative: ordering by a field that is neither aggregated nor selected\n// as a dimension is rejected. Keep the query free of `#` comments too — Cloudflare's parser rejects them.\nconst QUERY = `query BucketUsage($accountTag: string!, $bucketName: string, $start: Time, $end: Time) {\n viewer {\n accounts(filter: { accountTag: $accountTag }) {\n r2StorageAdaptiveGroups(\n limit: 1\n filter: { datetime_geq: $start, datetime_leq: $end, bucketName: $bucketName }\n orderBy: [datetime_DESC]\n ) {\n max { objectCount payloadSize metadataSize }\n dimensions { datetime }\n }\n }\n }\n}`;\n\ninterface UsageResponse {\n errors?: { message: string }[];\n data?: {\n viewer?: {\n accounts?: {\n r2StorageAdaptiveGroups?: {\n max?: { objectCount?: number; payloadSize?: number; metadataSize?: number };\n }[];\n }[];\n };\n };\n}\n\n// Reads how much an org's bucket actually holds, straight from the provider — the authoritative figure a locally\n// maintained counter would only ever approximate.\nexport class BucketUsageReader {\n private readonly logger = new Logger(BucketUsageReader.name);\n\n constructor(private readonly config: BucketUsageReaderConfig) {}\n\n // Returns the newest reported sample, or zeroes for a bucket the dataset has not reported on yet (a new or\n // empty bucket produces no rows at all rather than a row of zeroes)\n async getBucketUsage(bucketName: string, windowHours = 24): Promise<BucketUsage> {\n const end = new Date();\n const start = new Date(end.getTime() - windowHours * 60 * 60 * 1000);\n\n const response = await fetch(CF_GRAPHQL, {\n method: 'POST',\n headers: { Authorization: `Bearer ${this.config.analyticsToken}`, 'Content-Type': 'application/json' },\n body: JSON.stringify({\n query: QUERY,\n variables: {\n accountTag: this.config.accountId,\n bucketName,\n start: start.toISOString(),\n end: end.toISOString(),\n },\n }),\n });\n\n const body = (await response.json()) as UsageResponse;\n if (body.errors?.length) {\n throw new Error(\n `Cloudflare usage query failed for ${bucketName}: ${body.errors.map((e) => e.message).join('; ')}`,\n );\n }\n\n const sample = body.data?.viewer?.accounts?.[0]?.r2StorageAdaptiveGroups?.[0]?.max;\n if (!sample) {\n this.logger.debug(`No usage samples yet for bucket ${bucketName}`);\n return { bytes: 0, objectCount: 0 };\n }\n\n // Billed storage is payload plus per-object metadata, so both count against a quota\n return {\n bytes: (sample.payloadSize ?? 0) + (sample.metadataSize ?? 0),\n objectCount: sample.objectCount ?? 0,\n };\n }\n}\n","// Config may be a value or a thunk. Servers pass a thunk when the credentials come from required-only-if-selected\n// env keys, so reading them is deferred to the first resolve() instead of running at module construction.\nexport type StorageConfigSource<T> = T | (() => T);\n\n// A backend configured nowhere is a deployment asking for something it was never given credentials for\nexport function readConfigSource<T>(source: StorageConfigSource<T> | undefined, provider: string): T {\n if (source === undefined) {\n throw new Error(`Storage provider '${provider}' is not configured.`);\n }\n return typeof source === 'function' ? (source as () => T)() : source;\n}\n","import type { Readable } from 'node:stream';\nimport {\n DeleteObjectCommand,\n GetObjectCommand,\n ListObjectsV2Command,\n PutObjectCommand,\n S3Client,\n} from '@aws-sdk/client-s3';\nimport { getSignedUrl } from '@aws-sdk/s3-request-presigner';\nimport { Logger } from '@nestjs/common';\nimport { NotFoundException } from '../../exceptions';\nimport type { ListObjectsPage, R2StorageConfig, StorageProvider, UploadParams } from '../types';\n\nexport class R2StorageProvider implements StorageProvider {\n private readonly logger = new Logger(R2StorageProvider.name);\n private readonly client: S3Client;\n\n constructor(private readonly config: R2StorageConfig) {\n this.client = new S3Client({\n region: 'auto',\n endpoint: `https://${config.accountId}.r2.cloudflarestorage.com`,\n credentials: { accessKeyId: config.accessKeyId, secretAccessKey: config.secretAccessKey },\n });\n }\n\n // Uploads a file buffer or stream\n async upload(params: UploadParams): Promise<string> {\n await this.client.send(\n new PutObjectCommand({\n Bucket: this.requireBucket(params.bucket, this.config.defaultBucket),\n Key: params.key,\n Body: params.body,\n ContentType: params.contentType,\n }),\n );\n\n this.logger.log(`Uploaded file to R2: ${params.key}`);\n return params.key;\n }\n\n // Uploads a file to a public bucket and returns its permanent URL\n async uploadPublic(key: string, body: Buffer, contentType: string, bucket?: string): Promise<string> {\n await this.client.send(\n new PutObjectCommand({\n Bucket: this.requireBucket(bucket, this.config.publicBucket),\n Key: key,\n Body: body,\n ContentType: contentType,\n }),\n );\n\n const url = this.getPublicUrl(key);\n this.logger.log(`Uploaded public file to R2: ${key} → ${url}`);\n return url;\n }\n\n // Public URLs come from the configured custom domain, which maps to one bucket — R2 has no per-bucket URL to derive,\n // so a multi-tenant caller with a bucket per org cannot build one at all\n getPublicUrl(key: string): string {\n if (!this.config.publicUrl) {\n throw new Error('R2 storage has no publicUrl configured; public URLs cannot be built.');\n }\n return `${this.config.publicUrl}/${key}`;\n }\n\n // Deletes a file\n async delete(key: string, bucket?: string): Promise<void> {\n await this.client.send(new DeleteObjectCommand({ Bucket: bucket ?? this.config.defaultBucket, Key: key }));\n\n this.logger.log(`Deleted file from R2: ${key}`);\n }\n\n // Generates a presigned download URL (default 1 hour)\n async getSignedUrl(key: string, expiresInSeconds = 3600, bucket?: string): Promise<string> {\n const command = new GetObjectCommand({ Bucket: this.requireBucket(bucket, this.config.defaultBucket), Key: key });\n return getSignedUrl(this.client, command, { expiresIn: expiresInSeconds });\n }\n\n // Returns a readable stream\n async getStream(key: string, bucket?: string): Promise<Readable> {\n const response = await this.client.send(\n new GetObjectCommand({ Bucket: this.requireBucket(bucket, this.config.defaultBucket), Key: key }),\n );\n\n if (!response.Body) {\n throw new NotFoundException('File not found in storage.');\n }\n\n return response.Body as Readable;\n }\n\n // One page of a bucket's contents. S3 has no \"list everything\" call and no bucket-size call — a full inventory is\n // this looped until nextToken is absent, and each page is a Class A request.\n async listObjects(bucket: string, continuationToken?: string): Promise<ListObjectsPage> {\n const response = await this.client.send(\n new ListObjectsV2Command({ Bucket: bucket, ContinuationToken: continuationToken }),\n );\n\n return {\n objects: (response.Contents ?? []).map((o) => ({\n key: o.Key ?? '',\n size: o.Size ?? 0,\n lastModified: o.LastModified ?? new Date(0),\n })),\n nextToken: response.NextContinuationToken,\n };\n }\n\n // Failing loudly here beats letting a missing org bucket fall through to some other tenant's default\n private requireBucket(bucket: string | undefined, fallback: string | undefined): string {\n const resolved = bucket ?? fallback;\n if (!resolved) {\n throw new Error('No bucket supplied and R2 storage has no default bucket configured.');\n }\n return resolved;\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class BadGatewayException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Bad Gateway', HttpStatus.BAD_GATEWAY);\n }\n}\n","import { HttpException, HttpStatus } from '@nestjs/common';\nimport type { FieldError } from '../types/error-response.types';\n\n// Re-export FieldError for backwards compatibility\nexport type { FieldError } from '../types/error-response.types';\n\nexport interface ProblemOptions {\n type?: string;\n label?: string;\n detail?: string;\n errors?: FieldError[];\n}\n\nexport abstract class HttpProblemException extends HttpException {\n constructor(detailOrOptions: string | ProblemOptions, httpStatus: HttpStatus) {\n const options = typeof detailOrOptions === 'string' ? { detail: detailOrOptions } : detailOrOptions;\n\n super(\n {\n type: options.type ?? 'about:blank',\n label: options.label,\n detail: options.detail,\n errors: options.errors ?? [],\n },\n httpStatus,\n );\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class BadRequestException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Bad Request', HttpStatus.BAD_REQUEST);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class ConflictException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Conflict', HttpStatus.CONFLICT);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class ForbiddenException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Forbidden', HttpStatus.FORBIDDEN);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class GoneException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Gone', HttpStatus.GONE);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class InternalServerErrorException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Internal Server Error', HttpStatus.INTERNAL_SERVER_ERROR);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class MethodNotAllowedException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Method Not Allowed', HttpStatus.METHOD_NOT_ALLOWED);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class NotAcceptableException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Not Acceptable', HttpStatus.NOT_ACCEPTABLE);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class NotFoundException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Not Found', HttpStatus.NOT_FOUND);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class NotImplementedException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Not Implemented', HttpStatus.NOT_IMPLEMENTED);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class PayloadTooLargeException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Payload Too Large', HttpStatus.PAYLOAD_TOO_LARGE);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class RequestTimeoutException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Request Timeout', HttpStatus.REQUEST_TIMEOUT);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class ServiceUnavailableException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Service Unavailable', HttpStatus.SERVICE_UNAVAILABLE);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class TooManyRequestsException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Too Many Requests', HttpStatus.TOO_MANY_REQUESTS);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class UnauthorizedException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Unauthorized', HttpStatus.UNAUTHORIZED);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class UnprocessableEntityException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Unprocessable Entity', HttpStatus.UNPROCESSABLE_ENTITY);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class UnsupportedMediaTypeException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Unsupported Media Type', HttpStatus.UNSUPPORTED_MEDIA_TYPE);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class ValidationException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Validation Failed', HttpStatus.UNPROCESSABLE_ENTITY);\n }\n}\n","import { Logger } from '@nestjs/common';\nimport type { R2ProvisionerConfig, ScopedTokenResult, StorageProvisioner } from '../types';\n\nconst CF_API = 'https://api.cloudflare.com/client/v4';\n\n// Bucket-level group: read/write/list objects in named buckets. Deliberately NOT 'Workers R2 Storage Write', which is\n// account-level and can create and delete buckets — exactly what a tenant credential must never do.\nconst BUCKET_ITEM_WRITE = 'Workers R2 Storage Bucket Item Write';\n\ninterface CloudflareEnvelope<T> {\n success: boolean;\n errors: { code: number; message: string }[];\n result: T;\n}\n\n// Generic R2 admin client: create/delete buckets, toggle a bucket's Cloudflare-managed public domain, and mint/revoke\n// bucket-scoped credentials. It has NO notion of organizations — bucket naming, which buckets a tenant gets, and how\n// the stored descriptor is assembled all live in the caller. Every operation is a Cloudflare REST call.\nexport class R2BucketProvisioner implements StorageProvisioner {\n private readonly logger = new Logger(R2BucketProvisioner.name);\n private readonly jurisdiction: string;\n private bucketItemWriteGroupId: string | null = null;\n\n constructor(private readonly config: R2ProvisionerConfig) {\n this.jurisdiction = config.jurisdiction ?? 'default';\n }\n\n // Creates one bucket, treating an existing bucket as success so provisioning can be re-run to reconcile\n async createBucket(name: string): Promise<void> {\n const response = await fetch(`${CF_API}/accounts/${this.config.accountId}/r2/buckets`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.config.adminToken}`,\n 'Content-Type': 'application/json',\n 'cf-r2-jurisdiction': this.jurisdiction,\n },\n body: JSON.stringify({\n name,\n ...(this.config.locationHint && { locationHint: this.config.locationHint }),\n }),\n });\n\n const body = (await response.json()) as CloudflareEnvelope<unknown>;\n if (body.success) {\n this.logger.log(`Created bucket ${name}`);\n return;\n }\n\n // 10004 is \"bucket already exists\" — two provisioning attempts racing, or a reconcile pass\n if (body.errors?.some((e) => e.code === 10004)) return;\n throw new Error(`Cloudflare bucket create failed for ${name}: ${this.describe(body)}`);\n }\n\n // Deletes one bucket. R2 refuses to delete a bucket that still holds objects, so the caller must have emptied it\n // first — a 'not empty' failure here means that step did not finish. A missing bucket counts as success.\n async deleteBucket(name: string): Promise<void> {\n const response = await fetch(`${CF_API}/accounts/${this.config.accountId}/r2/buckets/${name}`, {\n method: 'DELETE',\n headers: { Authorization: `Bearer ${this.config.adminToken}` },\n });\n\n const body = (await response.json()) as CloudflareEnvelope<unknown>;\n // 10006 is \"bucket not found\" — already gone, which is the state we wanted\n if (body.success || body.errors?.some((e) => e.code === 10006)) {\n this.logger.log(`Deleted bucket ${name}`);\n return;\n }\n throw new Error(`Cloudflare bucket delete failed for ${name}: ${this.describe(body)}`);\n }\n\n // Turns on the bucket's Cloudflare-managed domain and returns its https URL. NOTE: r2.dev is rate limited and\n // documented as non-production — sustained traffic gets 429s. A custom domain per bucket is the production answer,\n // and drops into this same field. Throws on failure; the caller decides whether that is fatal.\n async enablePublicAccess(bucket: string): Promise<string> {\n const response = await fetch(`${CF_API}/accounts/${this.config.accountId}/r2/buckets/${bucket}/domains/managed`, {\n method: 'PUT',\n headers: { Authorization: `Bearer ${this.config.adminToken}`, 'Content-Type': 'application/json' },\n body: JSON.stringify({ enabled: true }),\n });\n\n const body = (await response.json()) as CloudflareEnvelope<{ domain?: string; enabled?: boolean }>;\n if (!body.success || !body.result?.domain) {\n throw new Error(`Cloudflare enable public access failed for ${bucket}: ${this.describe(body)}`);\n }\n\n this.logger.log(`Enabled public access for ${bucket} → ${body.result.domain}`);\n return `https://${body.result.domain}`;\n }\n\n // Mints an ACCOUNT-owned token that can only touch the named buckets. Account-owned rather than user-owned so a\n // tenant's credential does not die with whichever Cloudflare user happened to create the parent token. Requires the\n // parent to hold `Account API Tokens Write`; a user-scoped `API Tokens: Edit` token is rejected here with 9109.\n // `name` is the token's display label; the value is returned by R2 exactly once.\n async createScopedToken(name: string, buckets: string[]): Promise<ScopedTokenResult> {\n const permissionGroupId = await this.resolveBucketItemWriteGroupId();\n\n // The jurisdiction is embedded in the resource key and must match the bucket's, or the token authenticates fine\n // and then 403s on every object because it is scoped to a bucket that does not exist\n const resources = Object.fromEntries(\n buckets.map((bucket) => [\n `com.cloudflare.edge.r2.bucket.${this.config.accountId}_${this.jurisdiction}_${bucket}`,\n '*',\n ]),\n );\n\n const response = await fetch(`${CF_API}/accounts/${this.config.accountId}/tokens`, {\n method: 'POST',\n headers: { Authorization: `Bearer ${this.config.tokensToken}`, 'Content-Type': 'application/json' },\n body: JSON.stringify({\n name,\n policies: [{ effect: 'allow', permission_groups: [{ id: permissionGroupId }], resources }],\n }),\n });\n\n const body = (await response.json()) as CloudflareEnvelope<{ id: string; value: string }>;\n if (!body.success || !body.result?.value) {\n throw new Error(`Cloudflare token create failed for ${name}: ${this.describe(body)}`);\n }\n\n this.logger.log(`Minted storage credential ${name} scoped to ${buckets.length} bucket(s)`);\n return body.result;\n }\n\n // Revokes a credential by its access key id, which on R2 is the Cloudflare token id\n async deleteCredential(accessKeyId: string): Promise<void> {\n const response = await fetch(`${CF_API}/accounts/${this.config.accountId}/tokens/${accessKeyId}`, {\n method: 'DELETE',\n headers: { Authorization: `Bearer ${this.config.tokensToken}` },\n });\n\n const body = (await response.json()) as CloudflareEnvelope<unknown>;\n if (!body.success) {\n throw new Error(`Cloudflare token delete failed for ${accessKeyId}: ${this.describe(body)}`);\n }\n\n this.logger.log(`Revoked storage credential ${accessKeyId}`);\n }\n\n // The create-token API takes permission group UUIDs, not names. Looked up once rather than hardcoded: a UUID copied\n // from docs fails much later with an error that says nothing useful. Same account-scoped endpoint as creation.\n private async resolveBucketItemWriteGroupId(): Promise<string> {\n if (this.bucketItemWriteGroupId) return this.bucketItemWriteGroupId;\n\n const response = await fetch(`${CF_API}/accounts/${this.config.accountId}/tokens/permission_groups`, {\n headers: { Authorization: `Bearer ${this.config.tokensToken}` },\n });\n\n const body = (await response.json()) as CloudflareEnvelope<{ id: string; name: string }[]>;\n const group = body.result?.find((g) => g.name === BUCKET_ITEM_WRITE);\n if (!group) {\n throw new Error(`Cloudflare permission group '${BUCKET_ITEM_WRITE}' not found: ${this.describe(body)}`);\n }\n\n this.bucketItemWriteGroupId = group.id;\n return group.id;\n }\n\n private describe(body: CloudflareEnvelope<unknown>): string {\n return body.errors?.map((e) => `${e.code} ${e.message}`).join('; ') || 'unknown error';\n }\n}\n","import { readConfigSource, type StorageConfigSource } from './config-source';\nimport { R2StorageProvider } from './providers/r2-storage.provider';\nimport type { R2StorageConfig, StorageProvider } from './types';\n\nexport interface StorageFactoryOptions {\n r2?: StorageConfigSource<R2StorageConfig>;\n}\n\nexport class StorageFactory {\n private readonly cache = new Map<string, StorageProvider>();\n\n constructor(private readonly options: StorageFactoryOptions) {}\n\n // Resolves a provider by name, constructing it once on first use\n resolve(provider: string): StorageProvider {\n const cached = this.cache.get(provider);\n if (cached) return cached;\n\n const created = this.create(provider);\n this.cache.set(provider, created);\n return created;\n }\n\n private create(provider: string): StorageProvider {\n switch (provider) {\n case 'r2':\n return new R2StorageProvider(readConfigSource(this.options.r2, 'r2'));\n default:\n throw new Error(`Unsupported storage provider: ${provider}`);\n }\n }\n}\n","import { readConfigSource, type StorageConfigSource } from './config-source';\nimport { R2BucketProvisioner } from './provisioners/r2-bucket.provisioner';\nimport type { R2ProvisionerConfig, StorageProvisioner } from './types';\n\nexport interface StorageProvisionerFactoryOptions {\n r2?: StorageConfigSource<R2ProvisionerConfig>;\n}\n\n// Mirrors StorageFactory for the admin side: same provider names, same lazy config, different job. Resolves a generic\n// StorageProvisioner (bucket + credential admin) by provider name so the caller is not pinned to a single backend.\nexport class StorageProvisionerFactory {\n private readonly cache = new Map<string, StorageProvisioner>();\n\n constructor(private readonly options: StorageProvisionerFactoryOptions) {}\n\n // Resolves a provisioner by provider name, constructing it once on first use\n resolve(provider: string): StorageProvisioner {\n const cached = this.cache.get(provider);\n if (cached) return cached;\n\n const created = this.create(provider);\n this.cache.set(provider, created);\n return created;\n }\n\n private create(provider: string): StorageProvisioner {\n switch (provider) {\n case 'r2':\n return new R2BucketProvisioner(readConfigSource(this.options.r2, 'r2'));\n default:\n throw new Error(`Unsupported storage provisioner: ${provider}`);\n }\n }\n}\n"],"mappings":"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","names":["Logger","CF_GRAPHQL","QUERY","BucketUsageReader","logger","Logger","name","config","getBucketUsage","bucketName","windowHours","end","Date","start","getTime","response","fetch","method","headers","Authorization","analyticsToken","body","JSON","stringify","query","variables","accountTag","accountId","toISOString","json","errors","length","Error","map","e","message","join","sample","data","viewer","accounts","r2StorageAdaptiveGroups","max","debug","bytes","objectCount","payloadSize","metadataSize","readConfigSource","source","provider","undefined","Error","DeleteObjectCommand","GetObjectCommand","ListObjectsV2Command","PutObjectCommand","S3Client","getSignedUrl","Logger","HttpStatus","HttpException","HttpProblemException","HttpException","detailOrOptions","httpStatus","options","detail","type","label","errors","HttpStatus","HttpStatus","HttpStatus","HttpStatus","HttpStatus","HttpStatus","HttpStatus","HttpStatus","NotFoundException","HttpProblemException","detailOrOptions","HttpStatus","NOT_FOUND","HttpStatus","HttpStatus","HttpStatus","HttpStatus","HttpStatus","HttpStatus","HttpStatus","HttpStatus","HttpStatus","R2StorageProvider","logger","Logger","name","client","config","S3Client","region","endpoint","accountId","credentials","accessKeyId","secretAccessKey","upload","params","send","PutObjectCommand","Bucket","requireBucket","bucket","defaultBucket","Key","key","Body","body","ContentType","contentType","log","uploadPublic","publicBucket","url","getPublicUrl","publicUrl","Error","delete","DeleteObjectCommand","getSignedUrl","expiresInSeconds","command","GetObjectCommand","expiresIn","getStream","response","NotFoundException","listObjects","continuationToken","ListObjectsV2Command","ContinuationToken","objects","Contents","map","o","size","Size","lastModified","LastModified","Date","nextToken","NextContinuationToken","fallback","resolved","Logger","CF_API","BUCKET_ITEM_WRITE","R2BucketProvisioner","logger","Logger","name","jurisdiction","bucketItemWriteGroupId","config","createBucket","response","fetch","accountId","method","headers","Authorization","adminToken","body","JSON","stringify","locationHint","json","success","log","errors","some","e","code","Error","describe","deleteBucket","enablePublicAccess","bucket","enabled","result","domain","createScopedToken","buckets","permissionGroupId","resolveBucketItemWriteGroupId","resources","Object","fromEntries","map","tokensToken","policies","effect","permission_groups","id","value","length","deleteCredential","accessKeyId","group","find","g","message","join","StorageFactory","cache","Map","options","resolve","provider","cached","get","created","create","set","R2StorageProvider","readConfigSource","r2","Error","StorageProvisionerFactory","cache","Map","options","resolve","provider","cached","get","created","create","set","R2BucketProvisioner","readConfigSource","r2","Error"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vritti/api-sdk",
3
3
  "type": "module",
4
- "version": "0.3.8",
4
+ "version": "0.3.10",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",