@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.
@@ -0,0 +1,397 @@
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 { CreateBucketCommand, PutPublicAccessBlockCommand, PutBucketPolicyCommand, DeleteBucketPolicyCommand, PutBucketCorsCommand, PutBucketVersioningCommand, PutBucketLifecycleConfigurationCommand, HeadBucketCommand, GetBucketPolicyCommand, GetBucketCorsCommand, GetBucketVersioningCommand, GetBucketLifecycleConfigurationCommand, GetPublicAccessBlockCommand, } from '@aws-sdk/client-s3';
13
+ import { ProvisionerError } from './types';
14
+ import { createS3Client } from './client';
15
+ import { getPublicAccessBlock, buildPublicReadPolicy } from './policies';
16
+ import { buildUploadCorsRules, buildPrivateCorsRules } from './cors';
17
+ import { buildTempCleanupRule } from './lifecycle';
18
+ /**
19
+ * The BucketProvisioner handles creating and configuring S3-compatible
20
+ * buckets with the correct privacy settings, CORS rules, and policies
21
+ * for the Constructive storage module.
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * const provisioner = new BucketProvisioner({
26
+ * connection: {
27
+ * provider: 'minio',
28
+ * region: 'us-east-1',
29
+ * endpoint: 'http://minio:9000',
30
+ * accessKeyId: 'minioadmin',
31
+ * secretAccessKey: 'minioadmin',
32
+ * },
33
+ * allowedOrigins: ['https://app.example.com'],
34
+ * });
35
+ *
36
+ * // Provision a private bucket
37
+ * const result = await provisioner.provision({
38
+ * bucketName: 'my-app-storage',
39
+ * accessType: 'private',
40
+ * });
41
+ * ```
42
+ */
43
+ export class BucketProvisioner {
44
+ client;
45
+ config;
46
+ allowedOrigins;
47
+ constructor(options) {
48
+ if (!options.allowedOrigins || options.allowedOrigins.length === 0) {
49
+ throw new ProvisionerError('INVALID_CONFIG', 'allowedOrigins must contain at least one origin for CORS configuration');
50
+ }
51
+ this.config = options.connection;
52
+ this.allowedOrigins = options.allowedOrigins;
53
+ this.client = createS3Client(options.connection);
54
+ }
55
+ /**
56
+ * Get the underlying S3Client instance.
57
+ * Useful for advanced operations not covered by the provisioner.
58
+ */
59
+ getClient() {
60
+ return this.client;
61
+ }
62
+ /**
63
+ * Provision a fully configured S3 bucket.
64
+ *
65
+ * This is the main entry point. It:
66
+ * 1. Creates the bucket (or verifies it exists)
67
+ * 2. Configures Block Public Access based on access type
68
+ * 3. Applies the appropriate bucket policy (public-read or none)
69
+ * 4. Sets CORS rules for presigned URL uploads
70
+ * 5. Optionally enables versioning
71
+ * 6. Optionally adds lifecycle rules (auto-enabled for temp buckets)
72
+ *
73
+ * @param options - Bucket creation options
74
+ * @returns ProvisionResult with all configuration details
75
+ */
76
+ async provision(options) {
77
+ const { bucketName, accessType, versioning = false } = options;
78
+ const region = options.region ?? this.config.region;
79
+ // 1. Create the bucket
80
+ await this.createBucket(bucketName, region);
81
+ // 2. Configure Block Public Access
82
+ const publicAccessBlock = getPublicAccessBlock(accessType);
83
+ await this.setPublicAccessBlock(bucketName, publicAccessBlock);
84
+ // 3. Apply bucket policy
85
+ if (accessType === 'public') {
86
+ const policy = buildPublicReadPolicy(bucketName);
87
+ await this.setBucketPolicy(bucketName, policy);
88
+ }
89
+ else {
90
+ // Ensure no leftover public policy on private/temp buckets
91
+ await this.deleteBucketPolicy(bucketName);
92
+ }
93
+ // 4. Set CORS rules (per-bucket override takes precedence over default)
94
+ const effectiveOrigins = options.allowedOrigins ?? this.allowedOrigins;
95
+ const corsRules = accessType === 'private'
96
+ ? buildPrivateCorsRules(effectiveOrigins)
97
+ : buildUploadCorsRules(effectiveOrigins);
98
+ await this.setCors(bucketName, corsRules);
99
+ // 5. Versioning
100
+ if (versioning) {
101
+ await this.enableVersioning(bucketName);
102
+ }
103
+ // 6. Lifecycle rules for temp buckets
104
+ const lifecycleRules = [];
105
+ if (accessType === 'temp') {
106
+ const tempRule = buildTempCleanupRule(1);
107
+ lifecycleRules.push(tempRule);
108
+ await this.setLifecycleRules(bucketName, lifecycleRules);
109
+ }
110
+ // Build result
111
+ const publicUrlPrefix = accessType === 'public'
112
+ ? (options.publicUrlPrefix ?? null)
113
+ : null;
114
+ return {
115
+ bucketName,
116
+ accessType,
117
+ endpoint: this.config.endpoint ?? null,
118
+ provider: this.config.provider,
119
+ region,
120
+ publicUrlPrefix,
121
+ blockPublicAccess: accessType !== 'public',
122
+ versioning,
123
+ corsRules,
124
+ lifecycleRules,
125
+ };
126
+ }
127
+ /**
128
+ * Create an S3 bucket. Handles the "bucket already exists" case gracefully.
129
+ */
130
+ async createBucket(bucketName, region) {
131
+ try {
132
+ const command = new CreateBucketCommand({
133
+ Bucket: bucketName,
134
+ ...(region && region !== 'us-east-1'
135
+ ? { CreateBucketConfiguration: { LocationConstraint: region } }
136
+ : {}),
137
+ });
138
+ await this.client.send(command);
139
+ }
140
+ catch (err) {
141
+ // Bucket already exists and we own it — that's fine
142
+ if (err.name === 'BucketAlreadyOwnedByYou' ||
143
+ err.name === 'BucketAlreadyExists' ||
144
+ err.Code === 'BucketAlreadyOwnedByYou' ||
145
+ err.Code === 'BucketAlreadyExists') {
146
+ return;
147
+ }
148
+ throw new ProvisionerError('PROVIDER_ERROR', `Failed to create bucket '${bucketName}': ${err.message}`, err);
149
+ }
150
+ }
151
+ /**
152
+ * Check if a bucket exists and is accessible.
153
+ */
154
+ async bucketExists(bucketName) {
155
+ try {
156
+ await this.client.send(new HeadBucketCommand({ Bucket: bucketName }));
157
+ return true;
158
+ }
159
+ catch (err) {
160
+ if (err.name === 'NotFound' || err.$metadata?.httpStatusCode === 404) {
161
+ return false;
162
+ }
163
+ if (err.$metadata?.httpStatusCode === 403) {
164
+ throw new ProvisionerError('ACCESS_DENIED', `Access denied to bucket '${bucketName}'`, err);
165
+ }
166
+ throw new ProvisionerError('PROVIDER_ERROR', `Failed to check bucket '${bucketName}': ${err.message}`, err);
167
+ }
168
+ }
169
+ /**
170
+ * Configure S3 Block Public Access settings.
171
+ */
172
+ async setPublicAccessBlock(bucketName, config) {
173
+ try {
174
+ await this.client.send(new PutPublicAccessBlockCommand({
175
+ Bucket: bucketName,
176
+ PublicAccessBlockConfiguration: config,
177
+ }));
178
+ }
179
+ catch (err) {
180
+ throw new ProvisionerError('POLICY_FAILED', `Failed to set public access block on '${bucketName}': ${err.message}`, err);
181
+ }
182
+ }
183
+ /**
184
+ * Apply an S3 bucket policy.
185
+ */
186
+ async setBucketPolicy(bucketName, policy) {
187
+ try {
188
+ await this.client.send(new PutBucketPolicyCommand({
189
+ Bucket: bucketName,
190
+ Policy: JSON.stringify(policy),
191
+ }));
192
+ }
193
+ catch (err) {
194
+ throw new ProvisionerError('POLICY_FAILED', `Failed to set bucket policy on '${bucketName}': ${err.message}`, err);
195
+ }
196
+ }
197
+ /**
198
+ * Delete an S3 bucket policy (used to clear leftover public policies).
199
+ */
200
+ async deleteBucketPolicy(bucketName) {
201
+ try {
202
+ await this.client.send(new DeleteBucketPolicyCommand({ Bucket: bucketName }));
203
+ }
204
+ catch (err) {
205
+ // No policy to delete — that's fine
206
+ if (err.name === 'NoSuchBucketPolicy' || err.$metadata?.httpStatusCode === 404) {
207
+ return;
208
+ }
209
+ throw new ProvisionerError('POLICY_FAILED', `Failed to delete bucket policy on '${bucketName}': ${err.message}`, err);
210
+ }
211
+ }
212
+ /**
213
+ * Set CORS configuration on an S3 bucket.
214
+ */
215
+ async setCors(bucketName, rules) {
216
+ try {
217
+ await this.client.send(new PutBucketCorsCommand({
218
+ Bucket: bucketName,
219
+ CORSConfiguration: {
220
+ CORSRules: rules.map((rule) => ({
221
+ AllowedOrigins: rule.allowedOrigins,
222
+ AllowedMethods: rule.allowedMethods,
223
+ AllowedHeaders: rule.allowedHeaders,
224
+ ExposeHeaders: rule.exposedHeaders,
225
+ MaxAgeSeconds: rule.maxAgeSeconds,
226
+ })),
227
+ },
228
+ }));
229
+ }
230
+ catch (err) {
231
+ throw new ProvisionerError('CORS_FAILED', `Failed to set CORS on '${bucketName}': ${err.message}`, err);
232
+ }
233
+ }
234
+ /**
235
+ * Enable versioning on an S3 bucket.
236
+ */
237
+ async enableVersioning(bucketName) {
238
+ try {
239
+ await this.client.send(new PutBucketVersioningCommand({
240
+ Bucket: bucketName,
241
+ VersioningConfiguration: { Status: 'Enabled' },
242
+ }));
243
+ }
244
+ catch (err) {
245
+ throw new ProvisionerError('VERSIONING_FAILED', `Failed to enable versioning on '${bucketName}': ${err.message}`, err);
246
+ }
247
+ }
248
+ /**
249
+ * Set lifecycle rules on an S3 bucket.
250
+ */
251
+ async setLifecycleRules(bucketName, rules) {
252
+ try {
253
+ await this.client.send(new PutBucketLifecycleConfigurationCommand({
254
+ Bucket: bucketName,
255
+ LifecycleConfiguration: {
256
+ Rules: rules.map((rule) => ({
257
+ ID: rule.id,
258
+ Filter: { Prefix: rule.prefix },
259
+ Status: rule.enabled ? 'Enabled' : 'Disabled',
260
+ Expiration: { Days: rule.expirationDays },
261
+ })),
262
+ },
263
+ }));
264
+ }
265
+ catch (err) {
266
+ throw new ProvisionerError('LIFECYCLE_FAILED', `Failed to set lifecycle rules on '${bucketName}': ${err.message}`, err);
267
+ }
268
+ }
269
+ /**
270
+ * Update CORS configuration on an existing S3 bucket.
271
+ *
272
+ * Call this when the `allowed_origins` column changes on a bucket row.
273
+ * Builds the appropriate CORS rule set for the bucket's access type
274
+ * and applies it to the S3 bucket.
275
+ *
276
+ * @param options - Bucket name, access type, and new allowed origins
277
+ * @returns The CORS rules that were applied
278
+ */
279
+ async updateCors(options) {
280
+ const { bucketName, accessType, allowedOrigins } = options;
281
+ if (!allowedOrigins || allowedOrigins.length === 0) {
282
+ throw new ProvisionerError('INVALID_CONFIG', 'allowedOrigins must contain at least one origin for CORS configuration');
283
+ }
284
+ const corsRules = accessType === 'private'
285
+ ? buildPrivateCorsRules(allowedOrigins)
286
+ : buildUploadCorsRules(allowedOrigins);
287
+ await this.setCors(bucketName, corsRules);
288
+ return corsRules;
289
+ }
290
+ /**
291
+ * Inspect the current configuration of an existing bucket.
292
+ *
293
+ * Reads the bucket's policy, CORS, versioning, lifecycle, and public access
294
+ * settings and returns them in a structured format. Useful for auditing
295
+ * or verifying that a bucket is correctly configured.
296
+ *
297
+ * @param bucketName - S3 bucket name
298
+ * @param accessType - Expected access type (used in the result)
299
+ */
300
+ async inspect(bucketName, accessType) {
301
+ const exists = await this.bucketExists(bucketName);
302
+ if (!exists) {
303
+ throw new ProvisionerError('BUCKET_NOT_FOUND', `Bucket '${bucketName}' does not exist`);
304
+ }
305
+ // Read all configurations in parallel
306
+ const [publicAccessBlock, policy, cors, versioning, lifecycle] = await Promise.all([
307
+ this.getPublicAccessBlock(bucketName),
308
+ this.getBucketPolicy(bucketName),
309
+ this.getBucketCors(bucketName),
310
+ this.getBucketVersioning(bucketName),
311
+ this.getBucketLifecycle(bucketName),
312
+ ]);
313
+ const isFullyBlocked = publicAccessBlock
314
+ ? publicAccessBlock.BlockPublicAcls === true &&
315
+ publicAccessBlock.IgnorePublicAcls === true &&
316
+ publicAccessBlock.BlockPublicPolicy === true &&
317
+ publicAccessBlock.RestrictPublicBuckets === true
318
+ : false;
319
+ return {
320
+ bucketName,
321
+ accessType,
322
+ endpoint: this.config.endpoint ?? null,
323
+ provider: this.config.provider,
324
+ region: this.config.region,
325
+ publicUrlPrefix: null,
326
+ blockPublicAccess: isFullyBlocked,
327
+ versioning: versioning === 'Enabled',
328
+ corsRules: cors,
329
+ lifecycleRules: lifecycle,
330
+ };
331
+ }
332
+ // --- Private read methods for inspect ---
333
+ async getPublicAccessBlock(bucketName) {
334
+ try {
335
+ const result = await this.client.send(new GetPublicAccessBlockCommand({ Bucket: bucketName }));
336
+ const config = result.PublicAccessBlockConfiguration;
337
+ if (!config)
338
+ return null;
339
+ return {
340
+ BlockPublicAcls: config.BlockPublicAcls ?? false,
341
+ IgnorePublicAcls: config.IgnorePublicAcls ?? false,
342
+ BlockPublicPolicy: config.BlockPublicPolicy ?? false,
343
+ RestrictPublicBuckets: config.RestrictPublicBuckets ?? false,
344
+ };
345
+ }
346
+ catch {
347
+ return null;
348
+ }
349
+ }
350
+ async getBucketPolicy(bucketName) {
351
+ try {
352
+ const result = await this.client.send(new GetBucketPolicyCommand({ Bucket: bucketName }));
353
+ return result.Policy ? JSON.parse(result.Policy) : null;
354
+ }
355
+ catch {
356
+ return null;
357
+ }
358
+ }
359
+ async getBucketCors(bucketName) {
360
+ try {
361
+ const result = await this.client.send(new GetBucketCorsCommand({ Bucket: bucketName }));
362
+ return (result.CORSRules ?? []).map((rule) => ({
363
+ allowedOrigins: rule.AllowedOrigins ?? [],
364
+ allowedMethods: (rule.AllowedMethods ?? []),
365
+ allowedHeaders: rule.AllowedHeaders ?? [],
366
+ exposedHeaders: rule.ExposeHeaders ?? [],
367
+ maxAgeSeconds: rule.MaxAgeSeconds ?? 0,
368
+ }));
369
+ }
370
+ catch {
371
+ return [];
372
+ }
373
+ }
374
+ async getBucketVersioning(bucketName) {
375
+ try {
376
+ const result = await this.client.send(new GetBucketVersioningCommand({ Bucket: bucketName }));
377
+ return result.Status ?? 'Disabled';
378
+ }
379
+ catch {
380
+ return 'Disabled';
381
+ }
382
+ }
383
+ async getBucketLifecycle(bucketName) {
384
+ try {
385
+ const result = await this.client.send(new GetBucketLifecycleConfigurationCommand({ Bucket: bucketName }));
386
+ return (result.Rules ?? []).map((rule) => ({
387
+ id: rule.ID ?? '',
388
+ prefix: rule.Filter?.Prefix ?? '',
389
+ expirationDays: rule.Expiration?.Days ?? 0,
390
+ enabled: rule.Status === 'Enabled',
391
+ }));
392
+ }
393
+ catch {
394
+ return [];
395
+ }
396
+ }
397
+ }
package/esm/types.d.ts ADDED
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Types for the bucket provisioner library.
3
+ *
4
+ * Defines the configuration interfaces for S3-compatible storage providers,
5
+ * bucket creation options, privacy policies, and CORS rules.
6
+ */
7
+ /**
8
+ * Supported storage provider identifiers.
9
+ *
10
+ * Used to select provider-specific behavior (e.g., path-style URLs for MinIO,
11
+ * jurisdiction headers for R2).
12
+ */
13
+ export type StorageProvider = 's3' | 'minio' | 'r2' | 'gcs' | 'spaces';
14
+ /**
15
+ * Connection configuration for an S3-compatible storage backend.
16
+ *
17
+ * This is the input you provide to connect to your storage provider.
18
+ * For AWS S3, only `region` and credentials are needed.
19
+ * For MinIO/R2/etc., also provide `endpoint`.
20
+ */
21
+ export interface StorageConnectionConfig {
22
+ /** Storage provider type */
23
+ provider: StorageProvider;
24
+ /** S3 region (e.g., "us-east-1"). Required for AWS S3. */
25
+ region: string;
26
+ /** S3-compatible endpoint URL (e.g., "http://minio:9000"). Required for non-AWS providers. */
27
+ endpoint?: string;
28
+ /** AWS access key ID */
29
+ accessKeyId: string;
30
+ /** AWS secret access key */
31
+ secretAccessKey: string;
32
+ /** Use path-style URLs (required for MinIO, optional for others) */
33
+ forcePathStyle?: boolean;
34
+ }
35
+ /**
36
+ * Bucket access type, matching the database bucket `type` column.
37
+ *
38
+ * - `public`: Files served via CDN/public URL. Bucket policy allows public reads.
39
+ * - `private`: Files served via presigned GET URLs only. No public access.
40
+ * - `temp`: Staging area for uploads. Treated as private. Lifecycle rules may apply.
41
+ */
42
+ export type BucketAccessType = 'public' | 'private' | 'temp';
43
+ /**
44
+ * Options for creating or configuring an S3 bucket.
45
+ */
46
+ export interface CreateBucketOptions {
47
+ /** The S3 bucket name (globally unique for AWS, locally unique for MinIO) */
48
+ bucketName: string;
49
+ /** Bucket access type — determines which policies are applied */
50
+ accessType: BucketAccessType;
51
+ /** S3 region for bucket creation (defaults to connection config region) */
52
+ region?: string;
53
+ /** Whether to enable versioning (recommended for durability) */
54
+ versioning?: boolean;
55
+ /**
56
+ * Public URL prefix for public buckets.
57
+ * This is the CDN or public endpoint URL that serves files from the bucket.
58
+ * Only meaningful for `accessType: 'public'`.
59
+ * Example: "https://cdn.example.com/public"
60
+ */
61
+ publicUrlPrefix?: string;
62
+ /**
63
+ * Per-bucket CORS allowed origins override.
64
+ * When provided, these origins are used instead of the provisioner's default allowedOrigins.
65
+ * Use ['*'] for open/CDN mode (wildcard CORS, any origin can fetch).
66
+ * NULL/undefined = use the provisioner's default allowedOrigins.
67
+ */
68
+ allowedOrigins?: string[];
69
+ }
70
+ /**
71
+ * Options for updating CORS on an existing S3 bucket.
72
+ */
73
+ export interface UpdateCorsOptions {
74
+ /** The S3 bucket name */
75
+ bucketName: string;
76
+ /** Bucket access type — determines which CORS rule set to apply */
77
+ accessType: BucketAccessType;
78
+ /** The allowed origins to set. Use ['*'] for open/CDN mode. */
79
+ allowedOrigins: string[];
80
+ }
81
+ /**
82
+ * CORS rule for S3 bucket configuration.
83
+ *
84
+ * Required for browser-based presigned URL uploads.
85
+ * The presigned PUT request is a cross-origin request from the client
86
+ * to the S3 endpoint, so CORS must be configured on the bucket.
87
+ */
88
+ export interface CorsRule {
89
+ /** Allowed origin domains (e.g., ["https://app.example.com"]) */
90
+ allowedOrigins: string[];
91
+ /** Allowed HTTP methods */
92
+ allowedMethods: ('GET' | 'PUT' | 'HEAD' | 'POST' | 'DELETE')[];
93
+ /** Allowed request headers */
94
+ allowedHeaders: string[];
95
+ /** Headers exposed to the browser */
96
+ exposedHeaders: string[];
97
+ /** Preflight cache duration in seconds */
98
+ maxAgeSeconds: number;
99
+ }
100
+ /**
101
+ * Lifecycle rule for automatic object expiration.
102
+ *
103
+ * Useful for temp buckets where uploads expire after a set period.
104
+ */
105
+ export interface LifecycleRule {
106
+ /** Rule ID (descriptive name) */
107
+ id: string;
108
+ /** S3 key prefix to apply the rule to (empty string = entire bucket) */
109
+ prefix: string;
110
+ /** Number of days after which objects expire */
111
+ expirationDays: number;
112
+ /** Whether the rule is enabled */
113
+ enabled: boolean;
114
+ }
115
+ /**
116
+ * Result of a bucket provisioning operation.
117
+ *
118
+ * Contains all the information needed to configure the `storage_module`
119
+ * table and the presigned URL plugin.
120
+ */
121
+ export interface ProvisionResult {
122
+ /** The S3 bucket name */
123
+ bucketName: string;
124
+ /** Bucket access type */
125
+ accessType: BucketAccessType;
126
+ /** S3 endpoint URL (null for AWS S3 default) */
127
+ endpoint: string | null;
128
+ /** Storage provider type */
129
+ provider: StorageProvider;
130
+ /** S3 region */
131
+ region: string;
132
+ /**
133
+ * Public URL prefix for download URLs.
134
+ * For public buckets: the CDN/public endpoint.
135
+ * For private buckets: null (presigned URLs only).
136
+ */
137
+ publicUrlPrefix: string | null;
138
+ /** Whether Block Public Access is enabled */
139
+ blockPublicAccess: boolean;
140
+ /** Whether versioning is enabled */
141
+ versioning: boolean;
142
+ /** CORS rules applied */
143
+ corsRules: CorsRule[];
144
+ /** Lifecycle rules applied */
145
+ lifecycleRules: LifecycleRule[];
146
+ }
147
+ export type ProvisionerErrorCode = 'CONNECTION_FAILED' | 'BUCKET_ALREADY_EXISTS' | 'BUCKET_NOT_FOUND' | 'INVALID_CONFIG' | 'POLICY_FAILED' | 'CORS_FAILED' | 'LIFECYCLE_FAILED' | 'VERSIONING_FAILED' | 'ACCESS_DENIED' | 'PROVIDER_ERROR';
148
+ /**
149
+ * Structured error thrown by the bucket provisioner.
150
+ */
151
+ export declare class ProvisionerError extends Error {
152
+ readonly code: ProvisionerErrorCode;
153
+ readonly cause?: unknown;
154
+ constructor(code: ProvisionerErrorCode, message: string, cause?: unknown);
155
+ }
package/esm/types.js ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Types for the bucket provisioner library.
3
+ *
4
+ * Defines the configuration interfaces for S3-compatible storage providers,
5
+ * bucket creation options, privacy policies, and CORS rules.
6
+ */
7
+ /**
8
+ * Structured error thrown by the bucket provisioner.
9
+ */
10
+ export class ProvisionerError extends Error {
11
+ code;
12
+ cause;
13
+ constructor(code, message, cause) {
14
+ super(message);
15
+ this.name = 'ProvisionerError';
16
+ this.code = code;
17
+ this.cause = cause;
18
+ }
19
+ }
package/index.d.ts ADDED
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @constructive-io/bucket-provisioner
3
+ *
4
+ * S3-compatible bucket provisioning library for the Constructive storage module.
5
+ * Creates and configures buckets with the correct privacy policies, CORS rules,
6
+ * versioning, and lifecycle settings for private and public file storage.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { BucketProvisioner } from '@constructive-io/bucket-provisioner';
11
+ *
12
+ * const provisioner = new BucketProvisioner({
13
+ * connection: {
14
+ * provider: 'minio',
15
+ * region: 'us-east-1',
16
+ * endpoint: 'http://minio:9000',
17
+ * accessKeyId: 'minioadmin',
18
+ * secretAccessKey: 'minioadmin',
19
+ * },
20
+ * allowedOrigins: ['https://app.example.com'],
21
+ * });
22
+ *
23
+ * const result = await provisioner.provision({
24
+ * bucketName: 'my-app-storage',
25
+ * accessType: 'private',
26
+ * });
27
+ * ```
28
+ */
29
+ export { BucketProvisioner } from './provisioner';
30
+ export type { BucketProvisionerOptions } from './provisioner';
31
+ export { createS3Client } from './client';
32
+ export { getPublicAccessBlock, buildPublicReadPolicy, buildCloudFrontOacPolicy, buildPresignedUrlIamPolicy, } from './policies';
33
+ export type { PublicAccessBlockConfig, BucketPolicyDocument, BucketPolicyStatement, } from './policies';
34
+ export { buildUploadCorsRules, buildPrivateCorsRules } from './cors';
35
+ export { buildTempCleanupRule, buildAbortIncompleteMultipartRule } from './lifecycle';
36
+ export type { StorageProvider, StorageConnectionConfig, BucketAccessType, CreateBucketOptions, UpdateCorsOptions, CorsRule, LifecycleRule, ProvisionResult, ProvisionerErrorCode, } from './types';
37
+ export { ProvisionerError } from './types';
package/index.js ADDED
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ /**
3
+ * @constructive-io/bucket-provisioner
4
+ *
5
+ * S3-compatible bucket provisioning library for the Constructive storage module.
6
+ * Creates and configures buckets with the correct privacy policies, CORS rules,
7
+ * versioning, and lifecycle settings for private and public file storage.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import { BucketProvisioner } from '@constructive-io/bucket-provisioner';
12
+ *
13
+ * const provisioner = new BucketProvisioner({
14
+ * connection: {
15
+ * provider: 'minio',
16
+ * region: 'us-east-1',
17
+ * endpoint: 'http://minio:9000',
18
+ * accessKeyId: 'minioadmin',
19
+ * secretAccessKey: 'minioadmin',
20
+ * },
21
+ * allowedOrigins: ['https://app.example.com'],
22
+ * });
23
+ *
24
+ * const result = await provisioner.provision({
25
+ * bucketName: 'my-app-storage',
26
+ * accessType: 'private',
27
+ * });
28
+ * ```
29
+ */
30
+ Object.defineProperty(exports, "__esModule", { value: true });
31
+ exports.ProvisionerError = exports.buildAbortIncompleteMultipartRule = exports.buildTempCleanupRule = exports.buildPrivateCorsRules = exports.buildUploadCorsRules = exports.buildPresignedUrlIamPolicy = exports.buildCloudFrontOacPolicy = exports.buildPublicReadPolicy = exports.getPublicAccessBlock = exports.createS3Client = exports.BucketProvisioner = void 0;
32
+ // Core provisioner
33
+ var provisioner_1 = require("./provisioner");
34
+ Object.defineProperty(exports, "BucketProvisioner", { enumerable: true, get: function () { return provisioner_1.BucketProvisioner; } });
35
+ // S3 client factory
36
+ var client_1 = require("./client");
37
+ Object.defineProperty(exports, "createS3Client", { enumerable: true, get: function () { return client_1.createS3Client; } });
38
+ // Policy builders
39
+ var policies_1 = require("./policies");
40
+ Object.defineProperty(exports, "getPublicAccessBlock", { enumerable: true, get: function () { return policies_1.getPublicAccessBlock; } });
41
+ Object.defineProperty(exports, "buildPublicReadPolicy", { enumerable: true, get: function () { return policies_1.buildPublicReadPolicy; } });
42
+ Object.defineProperty(exports, "buildCloudFrontOacPolicy", { enumerable: true, get: function () { return policies_1.buildCloudFrontOacPolicy; } });
43
+ Object.defineProperty(exports, "buildPresignedUrlIamPolicy", { enumerable: true, get: function () { return policies_1.buildPresignedUrlIamPolicy; } });
44
+ // CORS builders
45
+ var cors_1 = require("./cors");
46
+ Object.defineProperty(exports, "buildUploadCorsRules", { enumerable: true, get: function () { return cors_1.buildUploadCorsRules; } });
47
+ Object.defineProperty(exports, "buildPrivateCorsRules", { enumerable: true, get: function () { return cors_1.buildPrivateCorsRules; } });
48
+ // Lifecycle builders
49
+ var lifecycle_1 = require("./lifecycle");
50
+ Object.defineProperty(exports, "buildTempCleanupRule", { enumerable: true, get: function () { return lifecycle_1.buildTempCleanupRule; } });
51
+ Object.defineProperty(exports, "buildAbortIncompleteMultipartRule", { enumerable: true, get: function () { return lifecycle_1.buildAbortIncompleteMultipartRule; } });
52
+ var types_1 = require("./types");
53
+ Object.defineProperty(exports, "ProvisionerError", { enumerable: true, get: function () { return types_1.ProvisionerError; } });