@constructive-io/bucket-provisioner 0.25.0 → 0.25.1

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/esm/index.d.ts CHANGED
@@ -28,6 +28,7 @@
28
28
  */
29
29
  export type { BucketProvisionerOptions } from './provisioner';
30
30
  export { BucketProvisioner } from './provisioner';
31
+ export { mintPhysicalBucketName } from './naming';
31
32
  export { createS3Client } from './client';
32
33
  export type { BucketPolicyDocument, BucketPolicyStatement, PublicAccessBlockConfig, } from './policies';
33
34
  export { buildCloudFrontOacPolicy, buildPresignedUrlIamPolicy, buildPublicReadPolicy, getPublicAccessBlock, } from './policies';
package/esm/index.js CHANGED
@@ -27,6 +27,8 @@
27
27
  * ```
28
28
  */
29
29
  export { BucketProvisioner } from './provisioner';
30
+ // Physical naming policy
31
+ export { mintPhysicalBucketName } from './naming';
30
32
  // S3 client factory
31
33
  export { createS3Client } from './client';
32
34
  export { buildCloudFrontOacPolicy, buildPresignedUrlIamPolicy, buildPublicReadPolicy, getPublicAccessBlock, } from './policies';
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The single physical-bucket naming policy:
3
+ * `{prefix}-{bucketKey}-{digest}` (e.g. `myapp-public-3f9c1a2b7e04`).
4
+ *
5
+ * Both the presigned-upload (lazy) path and the bucket-provisioner (eager) path
6
+ * derive names from this one function, so a bucket's physical name is identical
7
+ * regardless of which path mints it.
8
+ *
9
+ * The name is bounded and S3-legal by construction: the prefix and key are
10
+ * sanitized to `[a-z0-9-]` and truncated to a readable budget, and the tail is
11
+ * a digest of the *untruncated* identity — so two buckets whose keys agree only
12
+ * past the truncation point, or the same key in two databases, still get distinct
13
+ * names. Names remain stable for a given (prefix, databaseId, bucketKey) because
14
+ * nothing here reads the clock or a counter; and an already-provisioned bucket
15
+ * never consults this function at all, since `platform_buckets.physical_name` is
16
+ * authoritative once recorded.
17
+ */
18
+ export declare function mintPhysicalBucketName(prefix: string, databaseId: string, bucketKey: string): string;
package/esm/naming.js ADDED
@@ -0,0 +1,54 @@
1
+ import { createHash } from 'crypto';
2
+ /** S3's hard ceiling on a bucket name. */
3
+ const MAX_BUCKET_NAME_LENGTH = 63;
4
+ /** S3's floor, which a degenerate prefix/key could otherwise fall under. */
5
+ const MIN_BUCKET_NAME_LENGTH = 3;
6
+ /** Hex characters of the identity digest kept as the uniqueness tail. */
7
+ const IDENTITY_DIGEST_LENGTH = 12;
8
+ /** Readable budget: how much of the name the prefix and key may each occupy. */
9
+ const PREFIX_BUDGET = 20;
10
+ const BUCKET_KEY_BUDGET = 63 - IDENTITY_DIGEST_LENGTH - PREFIX_BUDGET - 3;
11
+ /**
12
+ * Reduce a component to the S3 bucket-name alphabet: lowercase, `[a-z0-9-]`,
13
+ * with runs of separators collapsed and no leading or trailing hyphen.
14
+ *
15
+ * Dots are legal in a bucket name but deliberately dropped — a dotted name
16
+ * cannot be used with virtual-hosted-style HTTPS, because the wildcard
17
+ * certificate does not match a further label.
18
+ */
19
+ function sanitizeBucketNameComponent(value) {
20
+ return value
21
+ .toLowerCase()
22
+ .replace(/[^a-z0-9]+/g, '-')
23
+ .replace(/^-+|-+$/g, '');
24
+ }
25
+ /**
26
+ * The single physical-bucket naming policy:
27
+ * `{prefix}-{bucketKey}-{digest}` (e.g. `myapp-public-3f9c1a2b7e04`).
28
+ *
29
+ * Both the presigned-upload (lazy) path and the bucket-provisioner (eager) path
30
+ * derive names from this one function, so a bucket's physical name is identical
31
+ * regardless of which path mints it.
32
+ *
33
+ * The name is bounded and S3-legal by construction: the prefix and key are
34
+ * sanitized to `[a-z0-9-]` and truncated to a readable budget, and the tail is
35
+ * a digest of the *untruncated* identity — so two buckets whose keys agree only
36
+ * past the truncation point, or the same key in two databases, still get distinct
37
+ * names. Names remain stable for a given (prefix, databaseId, bucketKey) because
38
+ * nothing here reads the clock or a counter; and an already-provisioned bucket
39
+ * never consults this function at all, since `platform_buckets.physical_name` is
40
+ * authoritative once recorded.
41
+ */
42
+ export function mintPhysicalBucketName(prefix, databaseId, bucketKey) {
43
+ const identity = `${prefix}/${databaseId}/${bucketKey}`;
44
+ const digest = createHash('sha256').update(identity).digest('hex').slice(0, IDENTITY_DIGEST_LENGTH);
45
+ const safePrefix = sanitizeBucketNameComponent(prefix).slice(0, PREFIX_BUDGET).replace(/-+$/, '');
46
+ const safeKey = sanitizeBucketNameComponent(bucketKey).slice(0, BUCKET_KEY_BUDGET).replace(/-+$/, '');
47
+ const name = [safePrefix, safeKey, digest].filter((part) => part.length > 0).join('-');
48
+ // The digest alone already satisfies both bounds, so this is only reachable
49
+ // when both readable components sanitize away to nothing.
50
+ if (name.length < MIN_BUCKET_NAME_LENGTH || name.length > MAX_BUCKET_NAME_LENGTH) {
51
+ throw new Error(`[bucket-provisioner] Cannot mint a legal S3 bucket name for key "${bucketKey}": got "${name}"`);
52
+ }
53
+ return name;
54
+ }
package/index.d.ts CHANGED
@@ -28,6 +28,7 @@
28
28
  */
29
29
  export type { BucketProvisionerOptions } from './provisioner';
30
30
  export { BucketProvisioner } from './provisioner';
31
+ export { mintPhysicalBucketName } from './naming';
31
32
  export { createS3Client } from './client';
32
33
  export type { BucketPolicyDocument, BucketPolicyStatement, PublicAccessBlockConfig, } from './policies';
33
34
  export { buildCloudFrontOacPolicy, buildPresignedUrlIamPolicy, buildPublicReadPolicy, getPublicAccessBlock, } from './policies';
package/index.js CHANGED
@@ -28,9 +28,12 @@
28
28
  * ```
29
29
  */
30
30
  Object.defineProperty(exports, "__esModule", { value: true });
31
- exports.ProvisionerError = exports.buildTempCleanupRule = exports.buildAbortIncompleteMultipartRule = exports.buildUploadCorsRules = exports.buildPrivateCorsRules = exports.getPublicAccessBlock = exports.buildPublicReadPolicy = exports.buildPresignedUrlIamPolicy = exports.buildCloudFrontOacPolicy = exports.createS3Client = exports.BucketProvisioner = void 0;
31
+ exports.ProvisionerError = exports.buildTempCleanupRule = exports.buildAbortIncompleteMultipartRule = exports.buildUploadCorsRules = exports.buildPrivateCorsRules = exports.getPublicAccessBlock = exports.buildPublicReadPolicy = exports.buildPresignedUrlIamPolicy = exports.buildCloudFrontOacPolicy = exports.createS3Client = exports.mintPhysicalBucketName = exports.BucketProvisioner = void 0;
32
32
  var provisioner_1 = require("./provisioner");
33
33
  Object.defineProperty(exports, "BucketProvisioner", { enumerable: true, get: function () { return provisioner_1.BucketProvisioner; } });
34
+ // Physical naming policy
35
+ var naming_1 = require("./naming");
36
+ Object.defineProperty(exports, "mintPhysicalBucketName", { enumerable: true, get: function () { return naming_1.mintPhysicalBucketName; } });
34
37
  // S3 client factory
35
38
  var client_1 = require("./client");
36
39
  Object.defineProperty(exports, "createS3Client", { enumerable: true, get: function () { return client_1.createS3Client; } });
package/naming.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The single physical-bucket naming policy:
3
+ * `{prefix}-{bucketKey}-{digest}` (e.g. `myapp-public-3f9c1a2b7e04`).
4
+ *
5
+ * Both the presigned-upload (lazy) path and the bucket-provisioner (eager) path
6
+ * derive names from this one function, so a bucket's physical name is identical
7
+ * regardless of which path mints it.
8
+ *
9
+ * The name is bounded and S3-legal by construction: the prefix and key are
10
+ * sanitized to `[a-z0-9-]` and truncated to a readable budget, and the tail is
11
+ * a digest of the *untruncated* identity — so two buckets whose keys agree only
12
+ * past the truncation point, or the same key in two databases, still get distinct
13
+ * names. Names remain stable for a given (prefix, databaseId, bucketKey) because
14
+ * nothing here reads the clock or a counter; and an already-provisioned bucket
15
+ * never consults this function at all, since `platform_buckets.physical_name` is
16
+ * authoritative once recorded.
17
+ */
18
+ export declare function mintPhysicalBucketName(prefix: string, databaseId: string, bucketKey: string): string;
package/naming.js ADDED
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mintPhysicalBucketName = mintPhysicalBucketName;
4
+ const crypto_1 = require("crypto");
5
+ /** S3's hard ceiling on a bucket name. */
6
+ const MAX_BUCKET_NAME_LENGTH = 63;
7
+ /** S3's floor, which a degenerate prefix/key could otherwise fall under. */
8
+ const MIN_BUCKET_NAME_LENGTH = 3;
9
+ /** Hex characters of the identity digest kept as the uniqueness tail. */
10
+ const IDENTITY_DIGEST_LENGTH = 12;
11
+ /** Readable budget: how much of the name the prefix and key may each occupy. */
12
+ const PREFIX_BUDGET = 20;
13
+ const BUCKET_KEY_BUDGET = 63 - IDENTITY_DIGEST_LENGTH - PREFIX_BUDGET - 3;
14
+ /**
15
+ * Reduce a component to the S3 bucket-name alphabet: lowercase, `[a-z0-9-]`,
16
+ * with runs of separators collapsed and no leading or trailing hyphen.
17
+ *
18
+ * Dots are legal in a bucket name but deliberately dropped — a dotted name
19
+ * cannot be used with virtual-hosted-style HTTPS, because the wildcard
20
+ * certificate does not match a further label.
21
+ */
22
+ function sanitizeBucketNameComponent(value) {
23
+ return value
24
+ .toLowerCase()
25
+ .replace(/[^a-z0-9]+/g, '-')
26
+ .replace(/^-+|-+$/g, '');
27
+ }
28
+ /**
29
+ * The single physical-bucket naming policy:
30
+ * `{prefix}-{bucketKey}-{digest}` (e.g. `myapp-public-3f9c1a2b7e04`).
31
+ *
32
+ * Both the presigned-upload (lazy) path and the bucket-provisioner (eager) path
33
+ * derive names from this one function, so a bucket's physical name is identical
34
+ * regardless of which path mints it.
35
+ *
36
+ * The name is bounded and S3-legal by construction: the prefix and key are
37
+ * sanitized to `[a-z0-9-]` and truncated to a readable budget, and the tail is
38
+ * a digest of the *untruncated* identity — so two buckets whose keys agree only
39
+ * past the truncation point, or the same key in two databases, still get distinct
40
+ * names. Names remain stable for a given (prefix, databaseId, bucketKey) because
41
+ * nothing here reads the clock or a counter; and an already-provisioned bucket
42
+ * never consults this function at all, since `platform_buckets.physical_name` is
43
+ * authoritative once recorded.
44
+ */
45
+ function mintPhysicalBucketName(prefix, databaseId, bucketKey) {
46
+ const identity = `${prefix}/${databaseId}/${bucketKey}`;
47
+ const digest = (0, crypto_1.createHash)('sha256').update(identity).digest('hex').slice(0, IDENTITY_DIGEST_LENGTH);
48
+ const safePrefix = sanitizeBucketNameComponent(prefix).slice(0, PREFIX_BUDGET).replace(/-+$/, '');
49
+ const safeKey = sanitizeBucketNameComponent(bucketKey).slice(0, BUCKET_KEY_BUDGET).replace(/-+$/, '');
50
+ const name = [safePrefix, safeKey, digest].filter((part) => part.length > 0).join('-');
51
+ // The digest alone already satisfies both bounds, so this is only reachable
52
+ // when both readable components sanitize away to nothing.
53
+ if (name.length < MIN_BUCKET_NAME_LENGTH || name.length > MAX_BUCKET_NAME_LENGTH) {
54
+ throw new Error(`[bucket-provisioner] Cannot mint a legal S3 bucket name for key "${bucketKey}": got "${name}"`);
55
+ }
56
+ return name;
57
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constructive-io/bucket-provisioner",
3
- "version": "0.25.0",
3
+ "version": "0.25.1",
4
4
  "author": "Constructive <developers@constructive.io>",
5
5
  "description": "S3-compatible bucket provisioning library — create buckets, configure privacy policies, CORS, and access controls",
6
6
  "main": "index.js",
@@ -44,5 +44,5 @@
44
44
  "storage",
45
45
  "constructive"
46
46
  ],
47
- "gitHead": "b5b6b9f05276b7e9cbd3d1f4acd31dd315c353ea"
47
+ "gitHead": "7e6cc68f5659d12ec852835a7d8fcca6b05a8f50"
48
48
  }