@constructive-io/bucket-provisioner 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lifecycle.d.ts ADDED
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Lifecycle rule builders.
3
+ *
4
+ * Generates S3 lifecycle configurations for automatic object expiration.
5
+ * Primarily used for temp buckets where uploads should be cleaned up
6
+ * after a configurable period.
7
+ */
8
+ import type { LifecycleRule } from './types';
9
+ /**
10
+ * Build a lifecycle rule for temp bucket cleanup.
11
+ *
12
+ * Temp buckets hold staging uploads (files with status='pending' that
13
+ * were never confirmed). This rule automatically deletes objects after
14
+ * a set number of days, preventing storage cost accumulation from
15
+ * abandoned uploads.
16
+ *
17
+ * @param expirationDays - Days after which objects expire (default: 1)
18
+ * @param prefix - Key prefix to target (default: "" = entire bucket)
19
+ */
20
+ export declare function buildTempCleanupRule(expirationDays?: number, prefix?: string): LifecycleRule;
21
+ /**
22
+ * Build a lifecycle rule for incomplete multipart upload cleanup.
23
+ *
24
+ * Incomplete multipart uploads consume storage but serve no purpose.
25
+ * This rule aborts them after a set number of days.
26
+ *
27
+ * @param expirationDays - Days after which incomplete uploads are aborted (default: 1)
28
+ */
29
+ export declare function buildAbortIncompleteMultipartRule(expirationDays?: number): LifecycleRule;
package/lifecycle.js ADDED
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ /**
3
+ * Lifecycle rule builders.
4
+ *
5
+ * Generates S3 lifecycle configurations for automatic object expiration.
6
+ * Primarily used for temp buckets where uploads should be cleaned up
7
+ * after a configurable period.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.buildTempCleanupRule = buildTempCleanupRule;
11
+ exports.buildAbortIncompleteMultipartRule = buildAbortIncompleteMultipartRule;
12
+ /**
13
+ * Build a lifecycle rule for temp bucket cleanup.
14
+ *
15
+ * Temp buckets hold staging uploads (files with status='pending' that
16
+ * were never confirmed). This rule automatically deletes objects after
17
+ * a set number of days, preventing storage cost accumulation from
18
+ * abandoned uploads.
19
+ *
20
+ * @param expirationDays - Days after which objects expire (default: 1)
21
+ * @param prefix - Key prefix to target (default: "" = entire bucket)
22
+ */
23
+ function buildTempCleanupRule(expirationDays = 1, prefix = '') {
24
+ return {
25
+ id: 'temp-cleanup',
26
+ prefix,
27
+ expirationDays,
28
+ enabled: true,
29
+ };
30
+ }
31
+ /**
32
+ * Build a lifecycle rule for incomplete multipart upload cleanup.
33
+ *
34
+ * Incomplete multipart uploads consume storage but serve no purpose.
35
+ * This rule aborts them after a set number of days.
36
+ *
37
+ * @param expirationDays - Days after which incomplete uploads are aborted (default: 1)
38
+ */
39
+ function buildAbortIncompleteMultipartRule(expirationDays = 1) {
40
+ return {
41
+ id: 'abort-incomplete-multipart',
42
+ prefix: '',
43
+ expirationDays,
44
+ enabled: true,
45
+ };
46
+ }
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@constructive-io/bucket-provisioner",
3
+ "version": "0.2.0",
4
+ "author": "Constructive <developers@constructive.io>",
5
+ "description": "S3-compatible bucket provisioning library — create buckets, configure privacy policies, CORS, and access controls",
6
+ "main": "index.js",
7
+ "module": "esm/index.js",
8
+ "types": "index.d.ts",
9
+ "homepage": "https://github.com/constructive-io/constructive",
10
+ "license": "MIT",
11
+ "publishConfig": {
12
+ "access": "public",
13
+ "directory": "dist"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/constructive-io/constructive"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/constructive-io/constructive/issues"
21
+ },
22
+ "scripts": {
23
+ "clean": "makage clean",
24
+ "prepack": "npm run build",
25
+ "build": "makage build",
26
+ "build:dev": "makage build --dev",
27
+ "lint": "eslint . --fix",
28
+ "test": "jest",
29
+ "test:watch": "jest --watch"
30
+ },
31
+ "dependencies": {
32
+ "@aws-sdk/client-s3": "^3.1009.0"
33
+ },
34
+ "devDependencies": {
35
+ "makage": "^0.3.0"
36
+ },
37
+ "keywords": [
38
+ "s3",
39
+ "bucket",
40
+ "provisioner",
41
+ "minio",
42
+ "cloudflare-r2",
43
+ "storage",
44
+ "constructive"
45
+ ],
46
+ "gitHead": "fe60f7b81252eea53dce227bb581d5ae2ef0ec36"
47
+ }
package/policies.d.ts ADDED
@@ -0,0 +1,88 @@
1
+ /**
2
+ * S3 bucket policy builders.
3
+ *
4
+ * Generates the JSON policy documents for private and public bucket
5
+ * configurations. These are the actual S3 bucket policies that control
6
+ * who can access objects in the bucket.
7
+ *
8
+ * Privacy model:
9
+ * - Private buckets: Block All Public Access enabled, no bucket policy needed.
10
+ * All access goes through presigned URLs generated server-side.
11
+ * - Public buckets: Block Public Access disabled for GetObject only.
12
+ * A bucket policy grants public read access (optionally restricted to a key prefix).
13
+ */
14
+ import type { BucketAccessType } from './types';
15
+ /**
16
+ * S3 Block Public Access configuration.
17
+ *
18
+ * For private/temp buckets: all four flags are true (maximum lockdown).
19
+ * For public buckets: BlockPublicPolicy and RestrictPublicBuckets are false
20
+ * so that a public-read bucket policy can be applied.
21
+ */
22
+ export interface PublicAccessBlockConfig {
23
+ BlockPublicAcls: boolean;
24
+ IgnorePublicAcls: boolean;
25
+ BlockPublicPolicy: boolean;
26
+ RestrictPublicBuckets: boolean;
27
+ }
28
+ /**
29
+ * Get the Block Public Access configuration for a bucket access type.
30
+ *
31
+ * - private/temp: all blocks enabled (maximum security)
32
+ * - public: ACL blocks enabled (ACLs are deprecated), but policy blocks
33
+ * disabled so a public-read bucket policy can be attached
34
+ */
35
+ export declare function getPublicAccessBlock(accessType: BucketAccessType): PublicAccessBlockConfig;
36
+ /**
37
+ * AWS IAM-style policy document for S3 bucket policies.
38
+ */
39
+ export interface BucketPolicyDocument {
40
+ Version: string;
41
+ Statement: BucketPolicyStatement[];
42
+ }
43
+ export interface BucketPolicyStatement {
44
+ Sid: string;
45
+ Effect: 'Allow' | 'Deny';
46
+ Principal: string | {
47
+ AWS: string;
48
+ } | {
49
+ Service: string;
50
+ };
51
+ Action: string | string[];
52
+ Resource: string | string[];
53
+ Condition?: Record<string, Record<string, string>>;
54
+ }
55
+ /**
56
+ * Build a public-read bucket policy.
57
+ *
58
+ * Grants anonymous GetObject access to the entire bucket or a specific prefix.
59
+ * This is the standard way to serve public files via direct URL or CDN.
60
+ *
61
+ * @param bucketName - S3 bucket name
62
+ * @param keyPrefix - Optional key prefix to restrict public reads (e.g., "public/").
63
+ * If provided, only objects under this prefix are publicly readable.
64
+ * If omitted, the entire bucket is publicly readable.
65
+ */
66
+ export declare function buildPublicReadPolicy(bucketName: string, keyPrefix?: string): BucketPolicyDocument;
67
+ /**
68
+ * Build a CloudFront Origin Access Control (OAC) bucket policy.
69
+ *
70
+ * This is the recommended way to serve public files through CloudFront
71
+ * without making the S3 bucket itself public. CloudFront authenticates
72
+ * to S3 using the OAC, and the bucket policy only allows CloudFront.
73
+ *
74
+ * @param bucketName - S3 bucket name
75
+ * @param cloudFrontDistributionArn - The CloudFront distribution ARN
76
+ * @param keyPrefix - Optional key prefix to restrict access
77
+ */
78
+ export declare function buildCloudFrontOacPolicy(bucketName: string, cloudFrontDistributionArn: string, keyPrefix?: string): BucketPolicyDocument;
79
+ /**
80
+ * Build the IAM policy for the presigned URL plugin's S3 credentials.
81
+ *
82
+ * This is the minimum-permission policy that the GraphQL server's
83
+ * S3 access key should have. It only allows PutObject, GetObject,
84
+ * and HeadObject — no delete, no list, no bucket management.
85
+ *
86
+ * @param bucketName - S3 bucket name
87
+ */
88
+ export declare function buildPresignedUrlIamPolicy(bucketName: string): BucketPolicyDocument;
package/policies.js ADDED
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ /**
3
+ * S3 bucket policy builders.
4
+ *
5
+ * Generates the JSON policy documents for private and public bucket
6
+ * configurations. These are the actual S3 bucket policies that control
7
+ * who can access objects in the bucket.
8
+ *
9
+ * Privacy model:
10
+ * - Private buckets: Block All Public Access enabled, no bucket policy needed.
11
+ * All access goes through presigned URLs generated server-side.
12
+ * - Public buckets: Block Public Access disabled for GetObject only.
13
+ * A bucket policy grants public read access (optionally restricted to a key prefix).
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.getPublicAccessBlock = getPublicAccessBlock;
17
+ exports.buildPublicReadPolicy = buildPublicReadPolicy;
18
+ exports.buildCloudFrontOacPolicy = buildCloudFrontOacPolicy;
19
+ exports.buildPresignedUrlIamPolicy = buildPresignedUrlIamPolicy;
20
+ /**
21
+ * Get the Block Public Access configuration for a bucket access type.
22
+ *
23
+ * - private/temp: all blocks enabled (maximum security)
24
+ * - public: ACL blocks enabled (ACLs are deprecated), but policy blocks
25
+ * disabled so a public-read bucket policy can be attached
26
+ */
27
+ function getPublicAccessBlock(accessType) {
28
+ if (accessType === 'public') {
29
+ return {
30
+ BlockPublicAcls: true,
31
+ IgnorePublicAcls: true,
32
+ BlockPublicPolicy: false,
33
+ RestrictPublicBuckets: false,
34
+ };
35
+ }
36
+ // private and temp: full lockdown
37
+ return {
38
+ BlockPublicAcls: true,
39
+ IgnorePublicAcls: true,
40
+ BlockPublicPolicy: true,
41
+ RestrictPublicBuckets: true,
42
+ };
43
+ }
44
+ /**
45
+ * Build a public-read bucket policy.
46
+ *
47
+ * Grants anonymous GetObject access to the entire bucket or a specific prefix.
48
+ * This is the standard way to serve public files via direct URL or CDN.
49
+ *
50
+ * @param bucketName - S3 bucket name
51
+ * @param keyPrefix - Optional key prefix to restrict public reads (e.g., "public/").
52
+ * If provided, only objects under this prefix are publicly readable.
53
+ * If omitted, the entire bucket is publicly readable.
54
+ */
55
+ function buildPublicReadPolicy(bucketName, keyPrefix) {
56
+ const resource = keyPrefix
57
+ ? `arn:aws:s3:::${bucketName}/${keyPrefix}*`
58
+ : `arn:aws:s3:::${bucketName}/*`;
59
+ return {
60
+ Version: '2012-10-17',
61
+ Statement: [
62
+ {
63
+ Sid: 'PublicReadAccess',
64
+ Effect: 'Allow',
65
+ Principal: '*',
66
+ Action: 's3:GetObject',
67
+ Resource: resource,
68
+ },
69
+ ],
70
+ };
71
+ }
72
+ /**
73
+ * Build a CloudFront Origin Access Control (OAC) bucket policy.
74
+ *
75
+ * This is the recommended way to serve public files through CloudFront
76
+ * without making the S3 bucket itself public. CloudFront authenticates
77
+ * to S3 using the OAC, and the bucket policy only allows CloudFront.
78
+ *
79
+ * @param bucketName - S3 bucket name
80
+ * @param cloudFrontDistributionArn - The CloudFront distribution ARN
81
+ * @param keyPrefix - Optional key prefix to restrict access
82
+ */
83
+ function buildCloudFrontOacPolicy(bucketName, cloudFrontDistributionArn, keyPrefix) {
84
+ const resource = keyPrefix
85
+ ? `arn:aws:s3:::${bucketName}/${keyPrefix}*`
86
+ : `arn:aws:s3:::${bucketName}/*`;
87
+ return {
88
+ Version: '2012-10-17',
89
+ Statement: [
90
+ {
91
+ Sid: 'AllowCloudFrontOACRead',
92
+ Effect: 'Allow',
93
+ Principal: { Service: 'cloudfront.amazonaws.com' },
94
+ Action: 's3:GetObject',
95
+ Resource: resource,
96
+ Condition: {
97
+ StringEquals: {
98
+ 'AWS:SourceArn': cloudFrontDistributionArn,
99
+ },
100
+ },
101
+ },
102
+ ],
103
+ };
104
+ }
105
+ /**
106
+ * Build the IAM policy for the presigned URL plugin's S3 credentials.
107
+ *
108
+ * This is the minimum-permission policy that the GraphQL server's
109
+ * S3 access key should have. It only allows PutObject, GetObject,
110
+ * and HeadObject — no delete, no list, no bucket management.
111
+ *
112
+ * @param bucketName - S3 bucket name
113
+ */
114
+ function buildPresignedUrlIamPolicy(bucketName) {
115
+ return {
116
+ Version: '2012-10-17',
117
+ Statement: [
118
+ {
119
+ Sid: 'PresignedUrlPluginAccess',
120
+ Effect: 'Allow',
121
+ Principal: '*',
122
+ Action: ['s3:PutObject', 's3:GetObject', 's3:HeadObject'],
123
+ Resource: `arn:aws:s3:::${bucketName}/*`,
124
+ },
125
+ ],
126
+ };
127
+ }
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Bucket Provisioner — core provisioning logic.
3
+ *
4
+ * Orchestrates S3 bucket creation, privacy configuration, CORS setup,
5
+ * versioning, and lifecycle rules. Uses the AWS SDK S3 client for all
6
+ * operations, which works with any S3-compatible backend (MinIO, R2, etc.).
7
+ *
8
+ * Privacy model:
9
+ * - Private/temp buckets: Block All Public Access, no bucket policy, presigned URLs only
10
+ * - Public buckets: Block Public Access partially relaxed, public-read bucket policy applied
11
+ */
12
+ import type { S3Client } from '@aws-sdk/client-s3';
13
+ import type { StorageConnectionConfig, CreateBucketOptions, UpdateCorsOptions, CorsRule, LifecycleRule, ProvisionResult, BucketAccessType } from './types';
14
+ import type { BucketPolicyDocument, PublicAccessBlockConfig } from './policies';
15
+ /**
16
+ * Options for the BucketProvisioner constructor.
17
+ */
18
+ export interface BucketProvisionerOptions {
19
+ /** Storage connection config — credentials, endpoint, provider */
20
+ connection: StorageConnectionConfig;
21
+ /**
22
+ * Default allowed origins for CORS rules.
23
+ * These are the domains where your app runs (e.g., ["https://app.example.com"]).
24
+ * Required for browser-based presigned URL uploads.
25
+ */
26
+ allowedOrigins: string[];
27
+ }
28
+ /**
29
+ * The BucketProvisioner handles creating and configuring S3-compatible
30
+ * buckets with the correct privacy settings, CORS rules, and policies
31
+ * for the Constructive storage module.
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * const provisioner = new BucketProvisioner({
36
+ * connection: {
37
+ * provider: 'minio',
38
+ * region: 'us-east-1',
39
+ * endpoint: 'http://minio:9000',
40
+ * accessKeyId: 'minioadmin',
41
+ * secretAccessKey: 'minioadmin',
42
+ * },
43
+ * allowedOrigins: ['https://app.example.com'],
44
+ * });
45
+ *
46
+ * // Provision a private bucket
47
+ * const result = await provisioner.provision({
48
+ * bucketName: 'my-app-storage',
49
+ * accessType: 'private',
50
+ * });
51
+ * ```
52
+ */
53
+ export declare class BucketProvisioner {
54
+ private readonly client;
55
+ private readonly config;
56
+ private readonly allowedOrigins;
57
+ constructor(options: BucketProvisionerOptions);
58
+ /**
59
+ * Get the underlying S3Client instance.
60
+ * Useful for advanced operations not covered by the provisioner.
61
+ */
62
+ getClient(): S3Client;
63
+ /**
64
+ * Provision a fully configured S3 bucket.
65
+ *
66
+ * This is the main entry point. It:
67
+ * 1. Creates the bucket (or verifies it exists)
68
+ * 2. Configures Block Public Access based on access type
69
+ * 3. Applies the appropriate bucket policy (public-read or none)
70
+ * 4. Sets CORS rules for presigned URL uploads
71
+ * 5. Optionally enables versioning
72
+ * 6. Optionally adds lifecycle rules (auto-enabled for temp buckets)
73
+ *
74
+ * @param options - Bucket creation options
75
+ * @returns ProvisionResult with all configuration details
76
+ */
77
+ provision(options: CreateBucketOptions): Promise<ProvisionResult>;
78
+ /**
79
+ * Create an S3 bucket. Handles the "bucket already exists" case gracefully.
80
+ */
81
+ createBucket(bucketName: string, region?: string): Promise<void>;
82
+ /**
83
+ * Check if a bucket exists and is accessible.
84
+ */
85
+ bucketExists(bucketName: string): Promise<boolean>;
86
+ /**
87
+ * Configure S3 Block Public Access settings.
88
+ */
89
+ setPublicAccessBlock(bucketName: string, config: PublicAccessBlockConfig): Promise<void>;
90
+ /**
91
+ * Apply an S3 bucket policy.
92
+ */
93
+ setBucketPolicy(bucketName: string, policy: BucketPolicyDocument): Promise<void>;
94
+ /**
95
+ * Delete an S3 bucket policy (used to clear leftover public policies).
96
+ */
97
+ deleteBucketPolicy(bucketName: string): Promise<void>;
98
+ /**
99
+ * Set CORS configuration on an S3 bucket.
100
+ */
101
+ setCors(bucketName: string, rules: CorsRule[]): Promise<void>;
102
+ /**
103
+ * Enable versioning on an S3 bucket.
104
+ */
105
+ enableVersioning(bucketName: string): Promise<void>;
106
+ /**
107
+ * Set lifecycle rules on an S3 bucket.
108
+ */
109
+ setLifecycleRules(bucketName: string, rules: LifecycleRule[]): Promise<void>;
110
+ /**
111
+ * Update CORS configuration on an existing S3 bucket.
112
+ *
113
+ * Call this when the `allowed_origins` column changes on a bucket row.
114
+ * Builds the appropriate CORS rule set for the bucket's access type
115
+ * and applies it to the S3 bucket.
116
+ *
117
+ * @param options - Bucket name, access type, and new allowed origins
118
+ * @returns The CORS rules that were applied
119
+ */
120
+ updateCors(options: UpdateCorsOptions): Promise<CorsRule[]>;
121
+ /**
122
+ * Inspect the current configuration of an existing bucket.
123
+ *
124
+ * Reads the bucket's policy, CORS, versioning, lifecycle, and public access
125
+ * settings and returns them in a structured format. Useful for auditing
126
+ * or verifying that a bucket is correctly configured.
127
+ *
128
+ * @param bucketName - S3 bucket name
129
+ * @param accessType - Expected access type (used in the result)
130
+ */
131
+ inspect(bucketName: string, accessType: BucketAccessType): Promise<ProvisionResult>;
132
+ private getPublicAccessBlock;
133
+ private getBucketPolicy;
134
+ private getBucketCors;
135
+ private getBucketVersioning;
136
+ private getBucketLifecycle;
137
+ }