graphile-settings 6.24.7 → 6.25.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/esm/index.d.ts +1 -1
- package/esm/index.js +1 -1
- package/esm/presets/constructive-preset.js +2 -2
- package/esm/presigned-url-resolver.d.ts +7 -20
- package/esm/presigned-url-resolver.js +11 -55
- package/esm/upload-resolver.d.ts +4 -10
- package/esm/upload-resolver.js +7 -49
- package/index.d.ts +1 -1
- package/index.js +2 -2
- package/package.json +20 -20
- package/presets/constructive-preset.js +1 -1
- package/presigned-url-resolver.d.ts +7 -20
- package/presigned-url-resolver.js +12 -56
- package/upload-resolver.d.ts +4 -10
- package/upload-resolver.js +6 -48
package/esm/index.d.ts
CHANGED
|
@@ -35,4 +35,4 @@ export { ConstructivePreset, createConstructivePreset } from './presets/construc
|
|
|
35
35
|
export * from './plugins/index';
|
|
36
36
|
export * from './presets/index';
|
|
37
37
|
export { makePgService };
|
|
38
|
-
export {
|
|
38
|
+
export { getStorageCredentials } from './presigned-url-resolver';
|
package/esm/index.js
CHANGED
|
@@ -46,4 +46,4 @@ export * from './presets/index';
|
|
|
46
46
|
// Re-export makePgService for convenience
|
|
47
47
|
export { makePgService };
|
|
48
48
|
// Presigned URL utilities
|
|
49
|
-
export {
|
|
49
|
+
export { getStorageCredentials } from './presigned-url-resolver';
|
|
@@ -12,7 +12,7 @@ import { RealtimeSubscriptionsPreset } from 'graphile-realtime-subscriptions';
|
|
|
12
12
|
import { createMatchesOperatorFactory, createTrgmOperatorFactories, UnifiedSearchPreset } from 'graphile-search';
|
|
13
13
|
import { UploadPreset } from 'graphile-upload-plugin';
|
|
14
14
|
import { ConflictDetectorPreset, EnableAllFilterColumnsPreset, InflectorLoggerPreset, InflektPreset, ManyToManyOptInPreset, MetaSchemaPreset, MinimalPreset, NoUniqueLookupPreset, PgTypeMappingsPreset, RequiredInputPreset } from '../plugins';
|
|
15
|
-
import {
|
|
15
|
+
import { getStorageCredentials } from '../presigned-url-resolver';
|
|
16
16
|
import { constructiveUploadFieldDefinitions } from '../upload-resolver';
|
|
17
17
|
/**
|
|
18
18
|
* grafast / @dataplan/pg require Node >= 22 (they call Promise.withResolvers()
|
|
@@ -138,7 +138,7 @@ export function createConstructivePreset(options) {
|
|
|
138
138
|
}
|
|
139
139
|
if (opts.enablePresignedUploads) {
|
|
140
140
|
presets.push(PresignedUrlPreset({
|
|
141
|
-
|
|
141
|
+
credentials: getStorageCredentials,
|
|
142
142
|
}), BucketProvisionerPreset());
|
|
143
143
|
}
|
|
144
144
|
if (opts.enableAggregates) {
|
|
@@ -1,23 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Object-store credentials for the presigned URL plugin.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* (
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Follows the same lazy-init pattern as upload-resolver.ts.
|
|
9
|
-
*/
|
|
10
|
-
import type { S3Config } from 'graphile-presigned-url-plugin';
|
|
11
|
-
/**
|
|
12
|
-
* Lazily initialize and return the S3Config for the presigned URL plugin.
|
|
13
|
-
*
|
|
14
|
-
* Reads CDN config on first call via getEnvOptions() (which already merges
|
|
15
|
-
* pgpmDefaults → config file → env vars), creates an S3Client, and caches
|
|
16
|
-
* the result. Same CDN config as upload-resolver.ts.
|
|
17
|
-
*
|
|
18
|
-
* NOTE: The `bucket` field here is only the connection's default and is never
|
|
19
|
-
* uploaded to. Every managed upload names its bucket explicitly, resolved from
|
|
20
|
-
* the tenant's logical bucket row; there is no environment-global upload
|
|
21
|
-
* bucket.
|
|
4
|
+
* Credentials are the only storage input read from the environment
|
|
5
|
+
* (`STORAGE_ACCESS_KEY_ID` / `STORAGE_SECRET_ACCESS_KEY`). Endpoint, provider,
|
|
6
|
+
* region, bucket and public URL prefix are resolved per request from the
|
|
7
|
+
* tenant's `storage_module` row by the plugin itself.
|
|
22
8
|
*/
|
|
23
|
-
|
|
9
|
+
import type { StorageCredentials } from 'graphile-presigned-url-plugin';
|
|
10
|
+
export declare function getStorageCredentials(): StorageCredentials;
|
|
@@ -1,61 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Object-store credentials for the presigned URL plugin.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* (
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Follows the same lazy-init pattern as upload-resolver.ts.
|
|
4
|
+
* Credentials are the only storage input read from the environment
|
|
5
|
+
* (`STORAGE_ACCESS_KEY_ID` / `STORAGE_SECRET_ACCESS_KEY`). Endpoint, provider,
|
|
6
|
+
* region, bucket and public URL prefix are resolved per request from the
|
|
7
|
+
* tenant's `storage_module` row by the plugin itself.
|
|
9
8
|
*/
|
|
10
9
|
import { getEnvOptions } from '@constructive-io/graphql-env';
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
* Lazily initialize and return the S3Config for the presigned URL plugin.
|
|
17
|
-
*
|
|
18
|
-
* Reads CDN config on first call via getEnvOptions() (which already merges
|
|
19
|
-
* pgpmDefaults → config file → env vars), creates an S3Client, and caches
|
|
20
|
-
* the result. Same CDN config as upload-resolver.ts.
|
|
21
|
-
*
|
|
22
|
-
* NOTE: The `bucket` field here is only the connection's default and is never
|
|
23
|
-
* uploaded to. Every managed upload names its bucket explicitly, resolved from
|
|
24
|
-
* the tenant's logical bucket row; there is no environment-global upload
|
|
25
|
-
* bucket.
|
|
26
|
-
*/
|
|
27
|
-
export function getPresignedUrlS3Config() {
|
|
28
|
-
if (s3Config)
|
|
29
|
-
return s3Config;
|
|
30
|
-
const { cdn } = getEnvOptions();
|
|
31
|
-
if (!cdn) {
|
|
32
|
-
throw new Error('[presigned-url-resolver] CDN config not found. ' +
|
|
33
|
-
'Ensure CDN environment variables (AWS_ACCESS_KEY, AWS_SECRET_KEY, etc.) ' +
|
|
34
|
-
'are set or that pgpmDefaults provides CDN fields.');
|
|
35
|
-
}
|
|
36
|
-
const { bucketName, awsRegion, awsAccessKey, awsSecretKey, endpoint, publicUrlPrefix } = cdn;
|
|
37
|
-
if (!awsAccessKey || !awsSecretKey) {
|
|
38
|
-
throw new Error('[presigned-url-resolver] Missing S3 credentials. ' +
|
|
39
|
-
'Set AWS_ACCESS_KEY and AWS_SECRET_KEY environment variables.');
|
|
40
|
-
}
|
|
41
|
-
if (!bucketName) {
|
|
42
|
-
throw new Error('[presigned-url-resolver] Missing CDN bucket name. ' +
|
|
43
|
-
'Set CDN_BUCKET_NAME environment variable.');
|
|
10
|
+
export function getStorageCredentials() {
|
|
11
|
+
const { accessKeyId, secretAccessKey } = getEnvOptions().storage ?? {};
|
|
12
|
+
if (!accessKeyId || !secretAccessKey) {
|
|
13
|
+
throw new Error('STORAGE_CREDENTIALS_MISSING: object storage requires STORAGE_ACCESS_KEY_ID and ' +
|
|
14
|
+
'STORAGE_SECRET_ACCESS_KEY');
|
|
44
15
|
}
|
|
45
|
-
|
|
46
|
-
const client = createS3Client({
|
|
47
|
-
provider: (cdn.provider || 'minio'),
|
|
48
|
-
region: awsRegion,
|
|
49
|
-
accessKeyId: awsAccessKey,
|
|
50
|
-
secretAccessKey: awsSecretKey,
|
|
51
|
-
...(endpoint ? { endpoint } : {}),
|
|
52
|
-
});
|
|
53
|
-
s3Config = {
|
|
54
|
-
client,
|
|
55
|
-
bucket: bucketName,
|
|
56
|
-
region: awsRegion,
|
|
57
|
-
publicUrlPrefix,
|
|
58
|
-
...(endpoint ? { endpoint, forcePathStyle: true } : {}),
|
|
59
|
-
};
|
|
60
|
-
return s3Config;
|
|
16
|
+
return { accessKeyId, secretAccessKey };
|
|
61
17
|
}
|
package/esm/upload-resolver.d.ts
CHANGED
|
@@ -9,21 +9,15 @@
|
|
|
9
9
|
* It used to be a second storage model: stream to `BUCKET_NAME` under a random
|
|
10
10
|
* key, hand back a URL, record nothing. Objects written that way belonged to no
|
|
11
11
|
* database, could not be deduplicated, listed, or access-controlled, and storage
|
|
12
|
-
* GC could not see that a document still pointed at them.
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* GC could not see that a document still pointed at them. Nothing in this path
|
|
13
|
+
* comes from the environment but the object-store credentials: the bucket and
|
|
14
|
+
* its connection (endpoint/provider/region) are the tenant's storage module.
|
|
15
15
|
*
|
|
16
16
|
* Compatibility: `image`/`upload` columns still receive `url` alongside the new
|
|
17
17
|
* `id`/`key`/`bucket_id`/`size` fields, so existing readers of `photo.url` keep
|
|
18
18
|
* working while they migrate to `id` + the files row's late-bound `downloadUrl`.
|
|
19
19
|
*
|
|
20
|
-
* ENV VARS
|
|
21
|
-
* BUCKET_PROVIDER - 'minio' | 's3' (default: 'minio')
|
|
22
|
-
* AWS_REGION - AWS region (default: 'us-east-1')
|
|
23
|
-
* Defaults come from `pgpmDefaults.cdn` (dev-only values; set these in production).
|
|
24
|
-
* AWS_ACCESS_KEY - access key
|
|
25
|
-
* AWS_SECRET_KEY - secret key
|
|
26
|
-
* CDN_ENDPOINT - S3-compatible endpoint
|
|
20
|
+
* ENV VARS: STORAGE_ACCESS_KEY_ID, STORAGE_SECRET_ACCESS_KEY (required).
|
|
27
21
|
*/
|
|
28
22
|
import type { UploadFieldDefinition } from 'graphile-upload-plugin';
|
|
29
23
|
/**
|
package/esm/upload-resolver.js
CHANGED
|
@@ -9,70 +9,28 @@
|
|
|
9
9
|
* It used to be a second storage model: stream to `BUCKET_NAME` under a random
|
|
10
10
|
* key, hand back a URL, record nothing. Objects written that way belonged to no
|
|
11
11
|
* database, could not be deduplicated, listed, or access-controlled, and storage
|
|
12
|
-
* GC could not see that a document still pointed at them.
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* GC could not see that a document still pointed at them. Nothing in this path
|
|
13
|
+
* comes from the environment but the object-store credentials: the bucket and
|
|
14
|
+
* its connection (endpoint/provider/region) are the tenant's storage module.
|
|
15
15
|
*
|
|
16
16
|
* Compatibility: `image`/`upload` columns still receive `url` alongside the new
|
|
17
17
|
* `id`/`key`/`bucket_id`/`size` fields, so existing readers of `photo.url` keep
|
|
18
18
|
* working while they migrate to `id` + the files row's late-bound `downloadUrl`.
|
|
19
19
|
*
|
|
20
|
-
* ENV VARS
|
|
21
|
-
* BUCKET_PROVIDER - 'minio' | 's3' (default: 'minio')
|
|
22
|
-
* AWS_REGION - AWS region (default: 'us-east-1')
|
|
23
|
-
* Defaults come from `pgpmDefaults.cdn` (dev-only values; set these in production).
|
|
24
|
-
* AWS_ACCESS_KEY - access key
|
|
25
|
-
* AWS_SECRET_KEY - secret key
|
|
26
|
-
* CDN_ENDPOINT - S3-compatible endpoint
|
|
20
|
+
* ENV VARS: STORAGE_ACCESS_KEY_ID, STORAGE_SECRET_ACCESS_KEY (required).
|
|
27
21
|
*/
|
|
28
|
-
import { getEnvOptions } from '@constructive-io/graphql-env';
|
|
29
22
|
import Streamer from '@constructive-io/s3-streamer';
|
|
30
|
-
import { Logger } from '@pgpmjs/logger';
|
|
31
23
|
import { createHash, randomUUID } from 'crypto';
|
|
32
24
|
import { finalizeStagedUpload, resolveManagedUploadTarget, withRequestPgClient, } from 'graphile-presigned-url-plugin';
|
|
33
25
|
import { checkTypeAgreement } from 'mime-bytes';
|
|
34
26
|
import { Transform } from 'stream';
|
|
35
|
-
import {
|
|
36
|
-
const log = new Logger('upload-resolver');
|
|
27
|
+
import { getStorageCredentials } from './presigned-url-resolver';
|
|
37
28
|
const DEFAULT_IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/svg+xml'];
|
|
38
|
-
let streamer = null;
|
|
39
|
-
/**
|
|
40
|
-
* The S3 streamer, built from the CDN connection settings.
|
|
41
|
-
*
|
|
42
|
-
* Deliberately constructed with no `defaultBucket`: every upload names the
|
|
43
|
-
* bucket it resolved, and a default here would be an environment-owned bucket
|
|
44
|
-
* standing in for a tenant's.
|
|
45
|
-
*/
|
|
46
|
-
function getStreamer() {
|
|
47
|
-
if (streamer)
|
|
48
|
-
return streamer;
|
|
49
|
-
const { cdn } = getEnvOptions();
|
|
50
|
-
if (process.env.NODE_ENV === 'production' && (!cdn.awsAccessKey || !cdn.awsSecretKey)) {
|
|
51
|
-
log.warn('[upload-resolver] WARNING: CDN credentials not configured in production.');
|
|
52
|
-
}
|
|
53
|
-
const provider = cdn.provider;
|
|
54
|
-
log.info(`[upload-resolver] Initializing: provider=${provider}`);
|
|
55
|
-
streamer = new Streamer({
|
|
56
|
-
provider,
|
|
57
|
-
awsRegion: cdn.awsRegion,
|
|
58
|
-
awsAccessKey: cdn.awsAccessKey,
|
|
59
|
-
awsSecretKey: cdn.awsSecretKey,
|
|
60
|
-
endpoint: cdn.endpoint,
|
|
61
|
-
});
|
|
62
|
-
return streamer;
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* The upload lane's view of the presigned plugin's options: the same S3
|
|
66
|
-
* connection the presigned lane uses, so both transports resolve identical
|
|
67
|
-
* coordinates for a bucket.
|
|
68
|
-
*
|
|
69
|
-
* Built on first upload rather than at import time.
|
|
70
|
-
*/
|
|
71
29
|
let managedOptions = null;
|
|
72
30
|
function getManagedOptions() {
|
|
73
31
|
if (!managedOptions) {
|
|
74
32
|
managedOptions = {
|
|
75
|
-
|
|
33
|
+
credentials: getStorageCredentials,
|
|
76
34
|
};
|
|
77
35
|
}
|
|
78
36
|
return managedOptions;
|
|
@@ -168,7 +126,7 @@ async function uploadResolver(upload, _args, context, info) {
|
|
|
168
126
|
`bucket "${target.bucket.key}" is private, whose only URLs expire. Use an upload column, ` +
|
|
169
127
|
'which stores the file id and resolves a fresh download URL on read.');
|
|
170
128
|
}
|
|
171
|
-
const s3 =
|
|
129
|
+
const s3 = new Streamer({ client: target.s3.client, defaultBucket: target.physicalName });
|
|
172
130
|
const { filename } = upload;
|
|
173
131
|
// Validate before persisting: content type comes from the leading bytes, not
|
|
174
132
|
// from the client's claim about them.
|
package/index.d.ts
CHANGED
|
@@ -35,4 +35,4 @@ export { ConstructivePreset, createConstructivePreset } from './presets/construc
|
|
|
35
35
|
export * from './plugins/index';
|
|
36
36
|
export * from './presets/index';
|
|
37
37
|
export { makePgService };
|
|
38
|
-
export {
|
|
38
|
+
export { getStorageCredentials } from './presigned-url-resolver';
|
package/index.js
CHANGED
|
@@ -42,7 +42,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
42
42
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
43
43
|
};
|
|
44
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
-
exports.
|
|
45
|
+
exports.getStorageCredentials = exports.makePgService = exports.createConstructivePreset = exports.ConstructivePreset = exports.createGrafastCacheLimitsPreset = exports.createGrafastCacheLimitsPlugin = exports.applyGrafastCacheLimits = void 0;
|
|
46
46
|
// Import modules for type augmentation
|
|
47
47
|
// These add properties to the GraphileConfig.Preset interface:
|
|
48
48
|
// - grafserv: adds 'grafserv' property
|
|
@@ -65,4 +65,4 @@ __exportStar(require("./plugins/index"), exports);
|
|
|
65
65
|
__exportStar(require("./presets/index"), exports);
|
|
66
66
|
// Presigned URL utilities
|
|
67
67
|
var presigned_url_resolver_1 = require("./presigned-url-resolver");
|
|
68
|
-
Object.defineProperty(exports, "
|
|
68
|
+
Object.defineProperty(exports, "getStorageCredentials", { enumerable: true, get: function () { return presigned_url_resolver_1.getStorageCredentials; } });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "graphile-settings",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.25.0",
|
|
4
4
|
"author": "Constructive <developers@constructive.io>",
|
|
5
5
|
"description": "graphile settings",
|
|
6
6
|
"main": "index.js",
|
|
@@ -30,38 +30,37 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@aws-sdk/client-s3": "^3.1052.0",
|
|
33
|
-
"@constructive-io/bucket-provisioner": "^0.
|
|
34
|
-
"@constructive-io/graphql-env": "^3.
|
|
35
|
-
"@constructive-io/graphql-types": "^3.
|
|
36
|
-
"@constructive-io/s3-streamer": "^2.
|
|
37
|
-
"@constructive-io/s3-utils": "^2.34.0",
|
|
33
|
+
"@constructive-io/bucket-provisioner": "^0.30.0",
|
|
34
|
+
"@constructive-io/graphql-env": "^3.35.0",
|
|
35
|
+
"@constructive-io/graphql-types": "^3.34.0",
|
|
36
|
+
"@constructive-io/s3-streamer": "^2.45.0",
|
|
38
37
|
"@constructive-io/upload-names": "^2.31.0",
|
|
39
38
|
"@dataplan/json": "1.0.1",
|
|
40
39
|
"@dataplan/pg": "1.1.1",
|
|
41
40
|
"@graphile-contrib/pg-many-to-many": "2.0.0-rc.2",
|
|
42
41
|
"@pgpmjs/logger": "^2.26.0",
|
|
43
|
-
"@pgpmjs/types": "^2.
|
|
42
|
+
"@pgpmjs/types": "^2.57.0",
|
|
44
43
|
"@pgsql/quotes": "^18.2.4",
|
|
45
44
|
"cors": "^2.8.6",
|
|
46
45
|
"express": "^5.2.1",
|
|
47
46
|
"grafast": "1.1.2",
|
|
48
47
|
"grafserv": "1.0.1",
|
|
49
|
-
"graphile-bucket-provisioner-plugin": "1.
|
|
48
|
+
"graphile-bucket-provisioner-plugin": "1.17.0",
|
|
50
49
|
"graphile-build": "5.1.1",
|
|
51
50
|
"graphile-build-pg": "5.1.3",
|
|
52
|
-
"graphile-bulk-mutations": "^1.17.
|
|
51
|
+
"graphile-bulk-mutations": "^1.17.8",
|
|
53
52
|
"graphile-config": "1.1.0",
|
|
54
|
-
"graphile-connection-filter": "^2.17.
|
|
55
|
-
"graphile-history": "^1.9.
|
|
56
|
-
"graphile-i18n": "^2.18.
|
|
57
|
-
"graphile-llm": "^1.18.
|
|
58
|
-
"graphile-ltree": "^2.17.
|
|
53
|
+
"graphile-connection-filter": "^2.17.8",
|
|
54
|
+
"graphile-history": "^1.9.8",
|
|
55
|
+
"graphile-i18n": "^2.18.8",
|
|
56
|
+
"graphile-llm": "^1.18.8",
|
|
57
|
+
"graphile-ltree": "^2.17.8",
|
|
59
58
|
"graphile-meta": "^1.10.0",
|
|
60
|
-
"graphile-pg-aggregates": "^2.17.
|
|
61
|
-
"graphile-postgis": "^3.18.
|
|
62
|
-
"graphile-presigned-url-plugin": "^1.
|
|
59
|
+
"graphile-pg-aggregates": "^2.17.8",
|
|
60
|
+
"graphile-postgis": "^3.18.8",
|
|
61
|
+
"graphile-presigned-url-plugin": "^1.22.0",
|
|
63
62
|
"graphile-realtime-subscriptions": "^1.12.0",
|
|
64
|
-
"graphile-search": "^2.17.
|
|
63
|
+
"graphile-search": "^2.17.8",
|
|
65
64
|
"graphile-upload-plugin": "^2.27.0",
|
|
66
65
|
"graphile-utils": "5.0.3",
|
|
67
66
|
"graphql": "16.13.0",
|
|
@@ -76,11 +75,12 @@
|
|
|
76
75
|
"tamedevil": "0.1.1"
|
|
77
76
|
},
|
|
78
77
|
"devDependencies": {
|
|
78
|
+
"@constructive-io/s3-utils": "^2.34.0",
|
|
79
79
|
"@types/cors": "^2.8.17",
|
|
80
80
|
"@types/express": "^5.0.6",
|
|
81
81
|
"@types/pg": "^8.20.4",
|
|
82
82
|
"@types/request-ip": "^0.0.41",
|
|
83
|
-
"graphile-test": "^5.17.
|
|
83
|
+
"graphile-test": "^5.17.8",
|
|
84
84
|
"makage": "^0.8.0",
|
|
85
85
|
"nodemon": "^3.1.14",
|
|
86
86
|
"ts-node": "^10.9.2"
|
|
@@ -92,5 +92,5 @@
|
|
|
92
92
|
"constructive",
|
|
93
93
|
"graphql"
|
|
94
94
|
],
|
|
95
|
-
"gitHead": "
|
|
95
|
+
"gitHead": "1f3501d518e175803a1a3372cf9249922d82bfe5"
|
|
96
96
|
}
|
|
@@ -142,7 +142,7 @@ function createConstructivePreset(options) {
|
|
|
142
142
|
}
|
|
143
143
|
if (opts.enablePresignedUploads) {
|
|
144
144
|
presets.push((0, graphile_presigned_url_plugin_1.PresignedUrlPreset)({
|
|
145
|
-
|
|
145
|
+
credentials: presigned_url_resolver_1.getStorageCredentials,
|
|
146
146
|
}), (0, graphile_bucket_provisioner_plugin_1.BucketProvisionerPreset)());
|
|
147
147
|
}
|
|
148
148
|
if (opts.enableAggregates) {
|
|
@@ -1,23 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Object-store credentials for the presigned URL plugin.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* (
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Follows the same lazy-init pattern as upload-resolver.ts.
|
|
9
|
-
*/
|
|
10
|
-
import type { S3Config } from 'graphile-presigned-url-plugin';
|
|
11
|
-
/**
|
|
12
|
-
* Lazily initialize and return the S3Config for the presigned URL plugin.
|
|
13
|
-
*
|
|
14
|
-
* Reads CDN config on first call via getEnvOptions() (which already merges
|
|
15
|
-
* pgpmDefaults → config file → env vars), creates an S3Client, and caches
|
|
16
|
-
* the result. Same CDN config as upload-resolver.ts.
|
|
17
|
-
*
|
|
18
|
-
* NOTE: The `bucket` field here is only the connection's default and is never
|
|
19
|
-
* uploaded to. Every managed upload names its bucket explicitly, resolved from
|
|
20
|
-
* the tenant's logical bucket row; there is no environment-global upload
|
|
21
|
-
* bucket.
|
|
4
|
+
* Credentials are the only storage input read from the environment
|
|
5
|
+
* (`STORAGE_ACCESS_KEY_ID` / `STORAGE_SECRET_ACCESS_KEY`). Endpoint, provider,
|
|
6
|
+
* region, bucket and public URL prefix are resolved per request from the
|
|
7
|
+
* tenant's `storage_module` row by the plugin itself.
|
|
22
8
|
*/
|
|
23
|
-
|
|
9
|
+
import type { StorageCredentials } from 'graphile-presigned-url-plugin';
|
|
10
|
+
export declare function getStorageCredentials(): StorageCredentials;
|
|
@@ -1,64 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Object-store credentials for the presigned URL plugin.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* (
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* Follows the same lazy-init pattern as upload-resolver.ts.
|
|
5
|
+
* Credentials are the only storage input read from the environment
|
|
6
|
+
* (`STORAGE_ACCESS_KEY_ID` / `STORAGE_SECRET_ACCESS_KEY`). Endpoint, provider,
|
|
7
|
+
* region, bucket and public URL prefix are resolved per request from the
|
|
8
|
+
* tenant's `storage_module` row by the plugin itself.
|
|
10
9
|
*/
|
|
11
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.
|
|
11
|
+
exports.getStorageCredentials = getStorageCredentials;
|
|
13
12
|
const graphql_env_1 = require("@constructive-io/graphql-env");
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
* Lazily initialize and return the S3Config for the presigned URL plugin.
|
|
20
|
-
*
|
|
21
|
-
* Reads CDN config on first call via getEnvOptions() (which already merges
|
|
22
|
-
* pgpmDefaults → config file → env vars), creates an S3Client, and caches
|
|
23
|
-
* the result. Same CDN config as upload-resolver.ts.
|
|
24
|
-
*
|
|
25
|
-
* NOTE: The `bucket` field here is only the connection's default and is never
|
|
26
|
-
* uploaded to. Every managed upload names its bucket explicitly, resolved from
|
|
27
|
-
* the tenant's logical bucket row; there is no environment-global upload
|
|
28
|
-
* bucket.
|
|
29
|
-
*/
|
|
30
|
-
function getPresignedUrlS3Config() {
|
|
31
|
-
if (s3Config)
|
|
32
|
-
return s3Config;
|
|
33
|
-
const { cdn } = (0, graphql_env_1.getEnvOptions)();
|
|
34
|
-
if (!cdn) {
|
|
35
|
-
throw new Error('[presigned-url-resolver] CDN config not found. ' +
|
|
36
|
-
'Ensure CDN environment variables (AWS_ACCESS_KEY, AWS_SECRET_KEY, etc.) ' +
|
|
37
|
-
'are set or that pgpmDefaults provides CDN fields.');
|
|
38
|
-
}
|
|
39
|
-
const { bucketName, awsRegion, awsAccessKey, awsSecretKey, endpoint, publicUrlPrefix } = cdn;
|
|
40
|
-
if (!awsAccessKey || !awsSecretKey) {
|
|
41
|
-
throw new Error('[presigned-url-resolver] Missing S3 credentials. ' +
|
|
42
|
-
'Set AWS_ACCESS_KEY and AWS_SECRET_KEY environment variables.');
|
|
43
|
-
}
|
|
44
|
-
if (!bucketName) {
|
|
45
|
-
throw new Error('[presigned-url-resolver] Missing CDN bucket name. ' +
|
|
46
|
-
'Set CDN_BUCKET_NAME environment variable.');
|
|
13
|
+
function getStorageCredentials() {
|
|
14
|
+
const { accessKeyId, secretAccessKey } = (0, graphql_env_1.getEnvOptions)().storage ?? {};
|
|
15
|
+
if (!accessKeyId || !secretAccessKey) {
|
|
16
|
+
throw new Error('STORAGE_CREDENTIALS_MISSING: object storage requires STORAGE_ACCESS_KEY_ID and ' +
|
|
17
|
+
'STORAGE_SECRET_ACCESS_KEY');
|
|
47
18
|
}
|
|
48
|
-
|
|
49
|
-
const client = (0, s3_utils_1.createS3Client)({
|
|
50
|
-
provider: (cdn.provider || 'minio'),
|
|
51
|
-
region: awsRegion,
|
|
52
|
-
accessKeyId: awsAccessKey,
|
|
53
|
-
secretAccessKey: awsSecretKey,
|
|
54
|
-
...(endpoint ? { endpoint } : {}),
|
|
55
|
-
});
|
|
56
|
-
s3Config = {
|
|
57
|
-
client,
|
|
58
|
-
bucket: bucketName,
|
|
59
|
-
region: awsRegion,
|
|
60
|
-
publicUrlPrefix,
|
|
61
|
-
...(endpoint ? { endpoint, forcePathStyle: true } : {}),
|
|
62
|
-
};
|
|
63
|
-
return s3Config;
|
|
19
|
+
return { accessKeyId, secretAccessKey };
|
|
64
20
|
}
|
package/upload-resolver.d.ts
CHANGED
|
@@ -9,21 +9,15 @@
|
|
|
9
9
|
* It used to be a second storage model: stream to `BUCKET_NAME` under a random
|
|
10
10
|
* key, hand back a URL, record nothing. Objects written that way belonged to no
|
|
11
11
|
* database, could not be deduplicated, listed, or access-controlled, and storage
|
|
12
|
-
* GC could not see that a document still pointed at them.
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* GC could not see that a document still pointed at them. Nothing in this path
|
|
13
|
+
* comes from the environment but the object-store credentials: the bucket and
|
|
14
|
+
* its connection (endpoint/provider/region) are the tenant's storage module.
|
|
15
15
|
*
|
|
16
16
|
* Compatibility: `image`/`upload` columns still receive `url` alongside the new
|
|
17
17
|
* `id`/`key`/`bucket_id`/`size` fields, so existing readers of `photo.url` keep
|
|
18
18
|
* working while they migrate to `id` + the files row's late-bound `downloadUrl`.
|
|
19
19
|
*
|
|
20
|
-
* ENV VARS
|
|
21
|
-
* BUCKET_PROVIDER - 'minio' | 's3' (default: 'minio')
|
|
22
|
-
* AWS_REGION - AWS region (default: 'us-east-1')
|
|
23
|
-
* Defaults come from `pgpmDefaults.cdn` (dev-only values; set these in production).
|
|
24
|
-
* AWS_ACCESS_KEY - access key
|
|
25
|
-
* AWS_SECRET_KEY - secret key
|
|
26
|
-
* CDN_ENDPOINT - S3-compatible endpoint
|
|
20
|
+
* ENV VARS: STORAGE_ACCESS_KEY_ID, STORAGE_SECRET_ACCESS_KEY (required).
|
|
27
21
|
*/
|
|
28
22
|
import type { UploadFieldDefinition } from 'graphile-upload-plugin';
|
|
29
23
|
/**
|
package/upload-resolver.js
CHANGED
|
@@ -10,75 +10,33 @@
|
|
|
10
10
|
* It used to be a second storage model: stream to `BUCKET_NAME` under a random
|
|
11
11
|
* key, hand back a URL, record nothing. Objects written that way belonged to no
|
|
12
12
|
* database, could not be deduplicated, listed, or access-controlled, and storage
|
|
13
|
-
* GC could not see that a document still pointed at them.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* GC could not see that a document still pointed at them. Nothing in this path
|
|
14
|
+
* comes from the environment but the object-store credentials: the bucket and
|
|
15
|
+
* its connection (endpoint/provider/region) are the tenant's storage module.
|
|
16
16
|
*
|
|
17
17
|
* Compatibility: `image`/`upload` columns still receive `url` alongside the new
|
|
18
18
|
* `id`/`key`/`bucket_id`/`size` fields, so existing readers of `photo.url` keep
|
|
19
19
|
* working while they migrate to `id` + the files row's late-bound `downloadUrl`.
|
|
20
20
|
*
|
|
21
|
-
* ENV VARS
|
|
22
|
-
* BUCKET_PROVIDER - 'minio' | 's3' (default: 'minio')
|
|
23
|
-
* AWS_REGION - AWS region (default: 'us-east-1')
|
|
24
|
-
* Defaults come from `pgpmDefaults.cdn` (dev-only values; set these in production).
|
|
25
|
-
* AWS_ACCESS_KEY - access key
|
|
26
|
-
* AWS_SECRET_KEY - secret key
|
|
27
|
-
* CDN_ENDPOINT - S3-compatible endpoint
|
|
21
|
+
* ENV VARS: STORAGE_ACCESS_KEY_ID, STORAGE_SECRET_ACCESS_KEY (required).
|
|
28
22
|
*/
|
|
29
23
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
30
24
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
31
25
|
};
|
|
32
26
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
33
27
|
exports.constructiveUploadFieldDefinitions = void 0;
|
|
34
|
-
const graphql_env_1 = require("@constructive-io/graphql-env");
|
|
35
28
|
const s3_streamer_1 = __importDefault(require("@constructive-io/s3-streamer"));
|
|
36
|
-
const logger_1 = require("@pgpmjs/logger");
|
|
37
29
|
const crypto_1 = require("crypto");
|
|
38
30
|
const graphile_presigned_url_plugin_1 = require("graphile-presigned-url-plugin");
|
|
39
31
|
const mime_bytes_1 = require("mime-bytes");
|
|
40
32
|
const stream_1 = require("stream");
|
|
41
33
|
const presigned_url_resolver_1 = require("./presigned-url-resolver");
|
|
42
|
-
const log = new logger_1.Logger('upload-resolver');
|
|
43
34
|
const DEFAULT_IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/svg+xml'];
|
|
44
|
-
let streamer = null;
|
|
45
|
-
/**
|
|
46
|
-
* The S3 streamer, built from the CDN connection settings.
|
|
47
|
-
*
|
|
48
|
-
* Deliberately constructed with no `defaultBucket`: every upload names the
|
|
49
|
-
* bucket it resolved, and a default here would be an environment-owned bucket
|
|
50
|
-
* standing in for a tenant's.
|
|
51
|
-
*/
|
|
52
|
-
function getStreamer() {
|
|
53
|
-
if (streamer)
|
|
54
|
-
return streamer;
|
|
55
|
-
const { cdn } = (0, graphql_env_1.getEnvOptions)();
|
|
56
|
-
if (process.env.NODE_ENV === 'production' && (!cdn.awsAccessKey || !cdn.awsSecretKey)) {
|
|
57
|
-
log.warn('[upload-resolver] WARNING: CDN credentials not configured in production.');
|
|
58
|
-
}
|
|
59
|
-
const provider = cdn.provider;
|
|
60
|
-
log.info(`[upload-resolver] Initializing: provider=${provider}`);
|
|
61
|
-
streamer = new s3_streamer_1.default({
|
|
62
|
-
provider,
|
|
63
|
-
awsRegion: cdn.awsRegion,
|
|
64
|
-
awsAccessKey: cdn.awsAccessKey,
|
|
65
|
-
awsSecretKey: cdn.awsSecretKey,
|
|
66
|
-
endpoint: cdn.endpoint,
|
|
67
|
-
});
|
|
68
|
-
return streamer;
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* The upload lane's view of the presigned plugin's options: the same S3
|
|
72
|
-
* connection the presigned lane uses, so both transports resolve identical
|
|
73
|
-
* coordinates for a bucket.
|
|
74
|
-
*
|
|
75
|
-
* Built on first upload rather than at import time.
|
|
76
|
-
*/
|
|
77
35
|
let managedOptions = null;
|
|
78
36
|
function getManagedOptions() {
|
|
79
37
|
if (!managedOptions) {
|
|
80
38
|
managedOptions = {
|
|
81
|
-
|
|
39
|
+
credentials: presigned_url_resolver_1.getStorageCredentials,
|
|
82
40
|
};
|
|
83
41
|
}
|
|
84
42
|
return managedOptions;
|
|
@@ -174,7 +132,7 @@ async function uploadResolver(upload, _args, context, info) {
|
|
|
174
132
|
`bucket "${target.bucket.key}" is private, whose only URLs expire. Use an upload column, ` +
|
|
175
133
|
'which stores the file id and resolves a fresh download URL on read.');
|
|
176
134
|
}
|
|
177
|
-
const s3 =
|
|
135
|
+
const s3 = new s3_streamer_1.default({ client: target.s3.client, defaultBucket: target.physicalName });
|
|
178
136
|
const { filename } = upload;
|
|
179
137
|
// Validate before persisting: content type comes from the leading bytes, not
|
|
180
138
|
// from the client's claim about them.
|