graphile-presigned-url-plugin 0.2.0 → 0.3.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/README.md +12 -0
- package/download-url-field.d.ts +0 -9
- package/download-url-field.js +14 -2
- package/esm/download-url-field.d.ts +0 -9
- package/esm/download-url-field.js +14 -2
- package/esm/index.d.ts +1 -1
- package/esm/plugin.js +16 -3
- package/esm/storage-module-cache.js +6 -0
- package/esm/types.d.ts +15 -2
- package/index.d.ts +1 -1
- package/package.json +3 -2
- package/plugin.js +16 -3
- package/storage-module-cache.js +6 -0
- package/types.d.ts +15 -2
package/README.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# graphile-presigned-url-plugin
|
|
2
2
|
|
|
3
|
+
<p align="center" width="100%">
|
|
4
|
+
<img height="250" src="https://raw.githubusercontent.com/constructive-io/constructive/refs/heads/main/assets/outline-logo.svg" />
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center" width="100%">
|
|
8
|
+
<a href="https://github.com/constructive-io/constructive/actions/workflows/run-tests.yaml">
|
|
9
|
+
<img height="20" src="https://github.com/constructive-io/constructive/actions/workflows/run-tests.yaml/badge.svg" />
|
|
10
|
+
</a>
|
|
11
|
+
<a href="https://github.com/constructive-io/constructive/blob/main/LICENSE"><img height="20" src="https://img.shields.io/badge/license-MIT-blue.svg"/></a>
|
|
12
|
+
<a href="https://www.npmjs.com/package/graphile-presigned-url-plugin"><img height="20" src="https://img.shields.io/github/package-json/v/constructive-io/constructive?filename=graphile%2Fgraphile-presigned-url-plugin%2Fpackage.json"/></a>
|
|
13
|
+
</p>
|
|
14
|
+
|
|
3
15
|
Presigned URL upload plugin for PostGraphile v5.
|
|
4
16
|
|
|
5
17
|
## Features
|
package/download-url-field.d.ts
CHANGED
|
@@ -14,14 +14,5 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import type { GraphileConfig } from 'graphile-config';
|
|
16
16
|
import type { PresignedUrlPluginOptions } from './types';
|
|
17
|
-
/**
|
|
18
|
-
* Creates the downloadUrl computed field plugin.
|
|
19
|
-
*
|
|
20
|
-
* This is a separate plugin from the main presigned URL plugin because it
|
|
21
|
-
* uses the GraphQLObjectType_fields hook (low-level) rather than extendSchema.
|
|
22
|
-
* The downloadUrl field needs to be added dynamically to whatever table is
|
|
23
|
-
* the storage module's files table, which we discover at schema-build time
|
|
24
|
-
* via the `@storageFiles` smart tag.
|
|
25
|
-
*/
|
|
26
17
|
export declare function createDownloadUrlPlugin(options: PresignedUrlPluginOptions): GraphileConfig.Plugin;
|
|
27
18
|
export default createDownloadUrlPlugin;
|
package/download-url-field.js
CHANGED
|
@@ -28,8 +28,19 @@ const log = new logger_1.Logger('graphile-presigned-url:download-url');
|
|
|
28
28
|
* the storage module's files table, which we discover at schema-build time
|
|
29
29
|
* via the `@storageFiles` smart tag.
|
|
30
30
|
*/
|
|
31
|
+
/**
|
|
32
|
+
* Resolve the S3 config from the options. If the option is a lazy getter
|
|
33
|
+
* function, call it (and cache the result).
|
|
34
|
+
*/
|
|
35
|
+
function resolveS3(options) {
|
|
36
|
+
if (typeof options.s3 === 'function') {
|
|
37
|
+
const resolved = options.s3();
|
|
38
|
+
options.s3 = resolved;
|
|
39
|
+
return resolved;
|
|
40
|
+
}
|
|
41
|
+
return options.s3;
|
|
42
|
+
}
|
|
31
43
|
function createDownloadUrlPlugin(options) {
|
|
32
|
-
const { s3 } = options;
|
|
33
44
|
return {
|
|
34
45
|
name: 'PresignedUrlDownloadPlugin',
|
|
35
46
|
version: '0.1.0',
|
|
@@ -65,6 +76,7 @@ function createDownloadUrlPlugin(options) {
|
|
|
65
76
|
if (status !== 'ready' && status !== 'processed') {
|
|
66
77
|
return null;
|
|
67
78
|
}
|
|
79
|
+
const s3 = resolveS3(options);
|
|
68
80
|
if (isPublic && s3.publicUrlPrefix) {
|
|
69
81
|
// Public file: return direct URL
|
|
70
82
|
return `${s3.publicUrlPrefix}/${key}`;
|
|
@@ -92,7 +104,7 @@ function createDownloadUrlPlugin(options) {
|
|
|
92
104
|
// Fall back to default if config lookup fails
|
|
93
105
|
}
|
|
94
106
|
// Private file: generate presigned GET URL
|
|
95
|
-
return (0, s3_signer_1.generatePresignedGetUrl)(
|
|
107
|
+
return (0, s3_signer_1.generatePresignedGetUrl)(resolveS3(options), key, downloadUrlExpirySeconds, filename || undefined);
|
|
96
108
|
},
|
|
97
109
|
}),
|
|
98
110
|
}, 'PresignedUrlDownloadPlugin adding downloadUrl field');
|
|
@@ -14,14 +14,5 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import type { GraphileConfig } from 'graphile-config';
|
|
16
16
|
import type { PresignedUrlPluginOptions } from './types';
|
|
17
|
-
/**
|
|
18
|
-
* Creates the downloadUrl computed field plugin.
|
|
19
|
-
*
|
|
20
|
-
* This is a separate plugin from the main presigned URL plugin because it
|
|
21
|
-
* uses the GraphQLObjectType_fields hook (low-level) rather than extendSchema.
|
|
22
|
-
* The downloadUrl field needs to be added dynamically to whatever table is
|
|
23
|
-
* the storage module's files table, which we discover at schema-build time
|
|
24
|
-
* via the `@storageFiles` smart tag.
|
|
25
|
-
*/
|
|
26
17
|
export declare function createDownloadUrlPlugin(options: PresignedUrlPluginOptions): GraphileConfig.Plugin;
|
|
27
18
|
export default createDownloadUrlPlugin;
|
|
@@ -25,8 +25,19 @@ const log = new Logger('graphile-presigned-url:download-url');
|
|
|
25
25
|
* the storage module's files table, which we discover at schema-build time
|
|
26
26
|
* via the `@storageFiles` smart tag.
|
|
27
27
|
*/
|
|
28
|
+
/**
|
|
29
|
+
* Resolve the S3 config from the options. If the option is a lazy getter
|
|
30
|
+
* function, call it (and cache the result).
|
|
31
|
+
*/
|
|
32
|
+
function resolveS3(options) {
|
|
33
|
+
if (typeof options.s3 === 'function') {
|
|
34
|
+
const resolved = options.s3();
|
|
35
|
+
options.s3 = resolved;
|
|
36
|
+
return resolved;
|
|
37
|
+
}
|
|
38
|
+
return options.s3;
|
|
39
|
+
}
|
|
28
40
|
export function createDownloadUrlPlugin(options) {
|
|
29
|
-
const { s3 } = options;
|
|
30
41
|
return {
|
|
31
42
|
name: 'PresignedUrlDownloadPlugin',
|
|
32
43
|
version: '0.1.0',
|
|
@@ -62,6 +73,7 @@ export function createDownloadUrlPlugin(options) {
|
|
|
62
73
|
if (status !== 'ready' && status !== 'processed') {
|
|
63
74
|
return null;
|
|
64
75
|
}
|
|
76
|
+
const s3 = resolveS3(options);
|
|
65
77
|
if (isPublic && s3.publicUrlPrefix) {
|
|
66
78
|
// Public file: return direct URL
|
|
67
79
|
return `${s3.publicUrlPrefix}/${key}`;
|
|
@@ -89,7 +101,7 @@ export function createDownloadUrlPlugin(options) {
|
|
|
89
101
|
// Fall back to default if config lookup fails
|
|
90
102
|
}
|
|
91
103
|
// Private file: generate presigned GET URL
|
|
92
|
-
return generatePresignedGetUrl(
|
|
104
|
+
return generatePresignedGetUrl(resolveS3(options), key, downloadUrlExpirySeconds, filename || undefined);
|
|
93
105
|
},
|
|
94
106
|
}),
|
|
95
107
|
}, 'PresignedUrlDownloadPlugin adding downloadUrl field');
|
package/esm/index.d.ts
CHANGED
|
@@ -31,4 +31,4 @@ export { createDownloadUrlPlugin } from './download-url-field';
|
|
|
31
31
|
export { PresignedUrlPreset } from './preset';
|
|
32
32
|
export { getStorageModuleConfig, getBucketConfig, clearStorageModuleCache, clearBucketCache } from './storage-module-cache';
|
|
33
33
|
export { generatePresignedPutUrl, generatePresignedGetUrl, headObject } from './s3-signer';
|
|
34
|
-
export type { BucketConfig, StorageModuleConfig, RequestUploadUrlInput, RequestUploadUrlPayload, ConfirmUploadInput, ConfirmUploadPayload, S3Config, PresignedUrlPluginOptions, } from './types';
|
|
34
|
+
export type { BucketConfig, StorageModuleConfig, RequestUploadUrlInput, RequestUploadUrlPayload, ConfirmUploadInput, ConfirmUploadPayload, S3Config, S3ConfigOrGetter, PresignedUrlPluginOptions, } from './types';
|
package/esm/plugin.js
CHANGED
|
@@ -52,8 +52,21 @@ async function resolveDatabaseId(pgClient) {
|
|
|
52
52
|
return result.rows[0]?.id ?? null;
|
|
53
53
|
}
|
|
54
54
|
// --- Plugin factory ---
|
|
55
|
+
/**
|
|
56
|
+
* Resolve the S3 config from the options. If the option is a lazy getter
|
|
57
|
+
* function, call it (and cache the result). This avoids reading env vars
|
|
58
|
+
* or constructing an S3Client at module-import time.
|
|
59
|
+
*/
|
|
60
|
+
function resolveS3(options) {
|
|
61
|
+
if (typeof options.s3 === 'function') {
|
|
62
|
+
const resolved = options.s3();
|
|
63
|
+
// Cache so subsequent calls don't re-evaluate
|
|
64
|
+
options.s3 = resolved;
|
|
65
|
+
return resolved;
|
|
66
|
+
}
|
|
67
|
+
return options.s3;
|
|
68
|
+
}
|
|
55
69
|
export function createPresignedUrlPlugin(options) {
|
|
56
|
-
const { s3 } = options;
|
|
57
70
|
return extendSchema(() => ({
|
|
58
71
|
typeDefs: gql `
|
|
59
72
|
input RequestUploadUrlInput {
|
|
@@ -229,7 +242,7 @@ export function createPresignedUrlPlugin(options) {
|
|
|
229
242
|
]);
|
|
230
243
|
const fileId = fileResult.rows[0].id;
|
|
231
244
|
// --- Generate presigned PUT URL ---
|
|
232
|
-
const uploadUrl = await generatePresignedPutUrl(
|
|
245
|
+
const uploadUrl = await generatePresignedPutUrl(resolveS3(options), s3Key, contentType, size, storageConfig.uploadUrlExpirySeconds);
|
|
233
246
|
const expiresAt = new Date(Date.now() + storageConfig.uploadUrlExpirySeconds * 1000).toISOString();
|
|
234
247
|
// --- Track the upload request ---
|
|
235
248
|
await pgClient.query(`INSERT INTO ${storageConfig.uploadRequestsQualifiedName}
|
|
@@ -296,7 +309,7 @@ export function createPresignedUrlPlugin(options) {
|
|
|
296
309
|
};
|
|
297
310
|
}
|
|
298
311
|
// --- Verify file exists in S3 ---
|
|
299
|
-
const s3Head = await headObject(
|
|
312
|
+
const s3Head = await headObject(resolveS3(options), file.key, file.content_type);
|
|
300
313
|
if (!s3Head) {
|
|
301
314
|
throw new Error('FILE_NOT_IN_S3: the file has not been uploaded yet');
|
|
302
315
|
}
|
|
@@ -38,6 +38,9 @@ const STORAGE_MODULE_QUERY = `
|
|
|
38
38
|
ft.name AS files_table,
|
|
39
39
|
urs.schema_name AS upload_requests_schema,
|
|
40
40
|
urt.name AS upload_requests_table,
|
|
41
|
+
sm.endpoint,
|
|
42
|
+
sm.public_url_prefix,
|
|
43
|
+
sm.provider,
|
|
41
44
|
sm.upload_url_expiry_seconds,
|
|
42
45
|
sm.download_url_expiry_seconds,
|
|
43
46
|
sm.default_max_file_size,
|
|
@@ -83,6 +86,9 @@ export async function getStorageModuleConfig(pgClient, databaseId) {
|
|
|
83
86
|
bucketsTableName: row.buckets_table,
|
|
84
87
|
filesTableName: row.files_table,
|
|
85
88
|
uploadRequestsTableName: row.upload_requests_table,
|
|
89
|
+
endpoint: row.endpoint,
|
|
90
|
+
publicUrlPrefix: row.public_url_prefix,
|
|
91
|
+
provider: row.provider,
|
|
86
92
|
uploadUrlExpirySeconds: row.upload_url_expiry_seconds ?? DEFAULT_UPLOAD_URL_EXPIRY_SECONDS,
|
|
87
93
|
downloadUrlExpirySeconds: row.download_url_expiry_seconds ?? DEFAULT_DOWNLOAD_URL_EXPIRY_SECONDS,
|
|
88
94
|
defaultMaxFileSize: row.default_max_file_size ?? DEFAULT_MAX_FILE_SIZE,
|
package/esm/types.d.ts
CHANGED
|
@@ -31,6 +31,12 @@ export interface StorageModuleConfig {
|
|
|
31
31
|
filesTableName: string;
|
|
32
32
|
/** Upload requests table name */
|
|
33
33
|
uploadRequestsTableName: string;
|
|
34
|
+
/** S3-compatible API endpoint URL (per-database override) */
|
|
35
|
+
endpoint: string | null;
|
|
36
|
+
/** Public URL prefix for generating download URLs (per-database override) */
|
|
37
|
+
publicUrlPrefix: string | null;
|
|
38
|
+
/** Storage provider type: 'minio', 's3', 'gcs', etc. (per-database override) */
|
|
39
|
+
provider: string | null;
|
|
34
40
|
/** Presigned PUT URL expiry in seconds (default: 900 = 15 min) */
|
|
35
41
|
uploadUrlExpirySeconds: number;
|
|
36
42
|
/** Presigned GET URL expiry in seconds (default: 3600 = 1 hour) */
|
|
@@ -107,10 +113,17 @@ export interface S3Config {
|
|
|
107
113
|
/** Public URL prefix for generating download URLs */
|
|
108
114
|
publicUrlPrefix?: string;
|
|
109
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* S3 configuration or a lazy getter that returns it on first use.
|
|
118
|
+
* When a function is provided, it will only be called when the first
|
|
119
|
+
* mutation or resolver actually needs the S3 client — avoiding eager
|
|
120
|
+
* env-var reads and S3Client creation at module import time.
|
|
121
|
+
*/
|
|
122
|
+
export type S3ConfigOrGetter = S3Config | (() => S3Config);
|
|
110
123
|
/**
|
|
111
124
|
* Plugin options for the presigned URL plugin.
|
|
112
125
|
*/
|
|
113
126
|
export interface PresignedUrlPluginOptions {
|
|
114
|
-
/** S3 configuration */
|
|
115
|
-
s3:
|
|
127
|
+
/** S3 configuration (concrete or lazy getter) */
|
|
128
|
+
s3: S3ConfigOrGetter;
|
|
116
129
|
}
|
package/index.d.ts
CHANGED
|
@@ -31,4 +31,4 @@ export { createDownloadUrlPlugin } from './download-url-field';
|
|
|
31
31
|
export { PresignedUrlPreset } from './preset';
|
|
32
32
|
export { getStorageModuleConfig, getBucketConfig, clearStorageModuleCache, clearBucketCache } from './storage-module-cache';
|
|
33
33
|
export { generatePresignedPutUrl, generatePresignedGetUrl, headObject } from './s3-signer';
|
|
34
|
-
export type { BucketConfig, StorageModuleConfig, RequestUploadUrlInput, RequestUploadUrlPayload, ConfirmUploadInput, ConfirmUploadPayload, S3Config, PresignedUrlPluginOptions, } from './types';
|
|
34
|
+
export type { BucketConfig, StorageModuleConfig, RequestUploadUrlInput, RequestUploadUrlPayload, ConfirmUploadInput, ConfirmUploadPayload, S3Config, S3ConfigOrGetter, PresignedUrlPluginOptions, } from './types';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "graphile-presigned-url-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Presigned URL upload plugin for PostGraphile v5 — requestUploadUrl, confirmUpload mutations and downloadUrl computed field",
|
|
5
5
|
"author": "Constructive <developers@constructive.io>",
|
|
6
6
|
"homepage": "https://github.com/constructive-io/constructive",
|
|
@@ -55,8 +55,9 @@
|
|
|
55
55
|
"postgraphile": "5.0.0"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
+
"@constructive-io/s3-utils": "^2.10.2",
|
|
58
59
|
"@types/node": "^22.19.11",
|
|
59
60
|
"makage": "^0.1.10"
|
|
60
61
|
},
|
|
61
|
-
"gitHead": "
|
|
62
|
+
"gitHead": "fe60f7b81252eea53dce227bb581d5ae2ef0ec36"
|
|
62
63
|
}
|
package/plugin.js
CHANGED
|
@@ -56,8 +56,21 @@ async function resolveDatabaseId(pgClient) {
|
|
|
56
56
|
return result.rows[0]?.id ?? null;
|
|
57
57
|
}
|
|
58
58
|
// --- Plugin factory ---
|
|
59
|
+
/**
|
|
60
|
+
* Resolve the S3 config from the options. If the option is a lazy getter
|
|
61
|
+
* function, call it (and cache the result). This avoids reading env vars
|
|
62
|
+
* or constructing an S3Client at module-import time.
|
|
63
|
+
*/
|
|
64
|
+
function resolveS3(options) {
|
|
65
|
+
if (typeof options.s3 === 'function') {
|
|
66
|
+
const resolved = options.s3();
|
|
67
|
+
// Cache so subsequent calls don't re-evaluate
|
|
68
|
+
options.s3 = resolved;
|
|
69
|
+
return resolved;
|
|
70
|
+
}
|
|
71
|
+
return options.s3;
|
|
72
|
+
}
|
|
59
73
|
function createPresignedUrlPlugin(options) {
|
|
60
|
-
const { s3 } = options;
|
|
61
74
|
return (0, graphile_utils_1.extendSchema)(() => ({
|
|
62
75
|
typeDefs: (0, graphile_utils_1.gql) `
|
|
63
76
|
input RequestUploadUrlInput {
|
|
@@ -233,7 +246,7 @@ function createPresignedUrlPlugin(options) {
|
|
|
233
246
|
]);
|
|
234
247
|
const fileId = fileResult.rows[0].id;
|
|
235
248
|
// --- Generate presigned PUT URL ---
|
|
236
|
-
const uploadUrl = await (0, s3_signer_1.generatePresignedPutUrl)(
|
|
249
|
+
const uploadUrl = await (0, s3_signer_1.generatePresignedPutUrl)(resolveS3(options), s3Key, contentType, size, storageConfig.uploadUrlExpirySeconds);
|
|
237
250
|
const expiresAt = new Date(Date.now() + storageConfig.uploadUrlExpirySeconds * 1000).toISOString();
|
|
238
251
|
// --- Track the upload request ---
|
|
239
252
|
await pgClient.query(`INSERT INTO ${storageConfig.uploadRequestsQualifiedName}
|
|
@@ -300,7 +313,7 @@ function createPresignedUrlPlugin(options) {
|
|
|
300
313
|
};
|
|
301
314
|
}
|
|
302
315
|
// --- Verify file exists in S3 ---
|
|
303
|
-
const s3Head = await (0, s3_signer_1.headObject)(
|
|
316
|
+
const s3Head = await (0, s3_signer_1.headObject)(resolveS3(options), file.key, file.content_type);
|
|
304
317
|
if (!s3Head) {
|
|
305
318
|
throw new Error('FILE_NOT_IN_S3: the file has not been uploaded yet');
|
|
306
319
|
}
|
package/storage-module-cache.js
CHANGED
|
@@ -44,6 +44,9 @@ const STORAGE_MODULE_QUERY = `
|
|
|
44
44
|
ft.name AS files_table,
|
|
45
45
|
urs.schema_name AS upload_requests_schema,
|
|
46
46
|
urt.name AS upload_requests_table,
|
|
47
|
+
sm.endpoint,
|
|
48
|
+
sm.public_url_prefix,
|
|
49
|
+
sm.provider,
|
|
47
50
|
sm.upload_url_expiry_seconds,
|
|
48
51
|
sm.download_url_expiry_seconds,
|
|
49
52
|
sm.default_max_file_size,
|
|
@@ -89,6 +92,9 @@ async function getStorageModuleConfig(pgClient, databaseId) {
|
|
|
89
92
|
bucketsTableName: row.buckets_table,
|
|
90
93
|
filesTableName: row.files_table,
|
|
91
94
|
uploadRequestsTableName: row.upload_requests_table,
|
|
95
|
+
endpoint: row.endpoint,
|
|
96
|
+
publicUrlPrefix: row.public_url_prefix,
|
|
97
|
+
provider: row.provider,
|
|
92
98
|
uploadUrlExpirySeconds: row.upload_url_expiry_seconds ?? DEFAULT_UPLOAD_URL_EXPIRY_SECONDS,
|
|
93
99
|
downloadUrlExpirySeconds: row.download_url_expiry_seconds ?? DEFAULT_DOWNLOAD_URL_EXPIRY_SECONDS,
|
|
94
100
|
defaultMaxFileSize: row.default_max_file_size ?? DEFAULT_MAX_FILE_SIZE,
|
package/types.d.ts
CHANGED
|
@@ -31,6 +31,12 @@ export interface StorageModuleConfig {
|
|
|
31
31
|
filesTableName: string;
|
|
32
32
|
/** Upload requests table name */
|
|
33
33
|
uploadRequestsTableName: string;
|
|
34
|
+
/** S3-compatible API endpoint URL (per-database override) */
|
|
35
|
+
endpoint: string | null;
|
|
36
|
+
/** Public URL prefix for generating download URLs (per-database override) */
|
|
37
|
+
publicUrlPrefix: string | null;
|
|
38
|
+
/** Storage provider type: 'minio', 's3', 'gcs', etc. (per-database override) */
|
|
39
|
+
provider: string | null;
|
|
34
40
|
/** Presigned PUT URL expiry in seconds (default: 900 = 15 min) */
|
|
35
41
|
uploadUrlExpirySeconds: number;
|
|
36
42
|
/** Presigned GET URL expiry in seconds (default: 3600 = 1 hour) */
|
|
@@ -107,10 +113,17 @@ export interface S3Config {
|
|
|
107
113
|
/** Public URL prefix for generating download URLs */
|
|
108
114
|
publicUrlPrefix?: string;
|
|
109
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* S3 configuration or a lazy getter that returns it on first use.
|
|
118
|
+
* When a function is provided, it will only be called when the first
|
|
119
|
+
* mutation or resolver actually needs the S3 client — avoiding eager
|
|
120
|
+
* env-var reads and S3Client creation at module import time.
|
|
121
|
+
*/
|
|
122
|
+
export type S3ConfigOrGetter = S3Config | (() => S3Config);
|
|
110
123
|
/**
|
|
111
124
|
* Plugin options for the presigned URL plugin.
|
|
112
125
|
*/
|
|
113
126
|
export interface PresignedUrlPluginOptions {
|
|
114
|
-
/** S3 configuration */
|
|
115
|
-
s3:
|
|
127
|
+
/** S3 configuration (concrete or lazy getter) */
|
|
128
|
+
s3: S3ConfigOrGetter;
|
|
116
129
|
}
|