graphile-presigned-url-plugin 0.6.1 → 0.6.3
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/download-url-field.d.ts +6 -0
- package/download-url-field.js +62 -44
- package/esm/download-url-field.d.ts +6 -0
- package/esm/download-url-field.js +62 -44
- package/esm/plugin.js +6 -6
- package/package.json +2 -2
- package/plugin.js +6 -6
package/download-url-field.d.ts
CHANGED
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
* COMMENT ON TABLE files IS E'@storageFiles\nStorage files table';
|
|
12
12
|
*
|
|
13
13
|
* This is explicit and reliable — no duck-typing on column names.
|
|
14
|
+
*
|
|
15
|
+
* IMPORTANT: Uses Grafast plan() instead of traditional resolve().
|
|
16
|
+
* In PostGraphile V5, Grafast's planning system does not invoke traditional
|
|
17
|
+
* resolve functions on PG table type fields — it plans them as column
|
|
18
|
+
* lookups. Since downloadUrl is a computed field (not a real column),
|
|
19
|
+
* the plan() function is required for Grafast to execute the S3 signing.
|
|
14
20
|
*/
|
|
15
21
|
import type { GraphileConfig } from 'graphile-config';
|
|
16
22
|
import type { PresignedUrlPluginOptions } from './types';
|
package/download-url-field.js
CHANGED
|
@@ -12,9 +12,16 @@
|
|
|
12
12
|
* COMMENT ON TABLE files IS E'@storageFiles\nStorage files table';
|
|
13
13
|
*
|
|
14
14
|
* This is explicit and reliable — no duck-typing on column names.
|
|
15
|
+
*
|
|
16
|
+
* IMPORTANT: Uses Grafast plan() instead of traditional resolve().
|
|
17
|
+
* In PostGraphile V5, Grafast's planning system does not invoke traditional
|
|
18
|
+
* resolve functions on PG table type fields — it plans them as column
|
|
19
|
+
* lookups. Since downloadUrl is a computed field (not a real column),
|
|
20
|
+
* the plan() function is required for Grafast to execute the S3 signing.
|
|
15
21
|
*/
|
|
16
22
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
23
|
exports.createDownloadUrlPlugin = createDownloadUrlPlugin;
|
|
24
|
+
const grafast_1 = require("grafast");
|
|
18
25
|
const logger_1 = require("@pgpmjs/logger");
|
|
19
26
|
const s3_signer_1 = require("./s3-signer");
|
|
20
27
|
const storage_module_cache_1 = require("./storage-module-cache");
|
|
@@ -62,7 +69,7 @@ function resolveS3ForDatabase(options, storageConfig, databaseId) {
|
|
|
62
69
|
function createDownloadUrlPlugin(options) {
|
|
63
70
|
return {
|
|
64
71
|
name: 'PresignedUrlDownloadPlugin',
|
|
65
|
-
version: '0.
|
|
72
|
+
version: '0.2.0',
|
|
66
73
|
description: 'Adds downloadUrl computed field to File types tagged with @storageFiles',
|
|
67
74
|
schema: {
|
|
68
75
|
hooks: {
|
|
@@ -84,52 +91,63 @@ function createDownloadUrlPlugin(options) {
|
|
|
84
91
|
description: 'URL to download this file. For public files, returns the public URL. ' +
|
|
85
92
|
'For private files, returns a time-limited presigned URL.',
|
|
86
93
|
type: GraphQLString,
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
94
|
+
plan($parent) {
|
|
95
|
+
// Access file attributes from the parent PgSelectSingleStep
|
|
96
|
+
const $key = $parent.get('key');
|
|
97
|
+
const $isPublic = $parent.get('is_public');
|
|
98
|
+
const $filename = $parent.get('filename');
|
|
99
|
+
const $status = $parent.get('status');
|
|
100
|
+
// Access GraphQL context for per-database config resolution
|
|
101
|
+
const $withPgClient = (0, grafast_1.context)().get('withPgClient');
|
|
102
|
+
const $pgSettings = (0, grafast_1.context)().get('pgSettings');
|
|
103
|
+
const $combined = (0, grafast_1.object)({
|
|
104
|
+
key: $key,
|
|
105
|
+
isPublic: $isPublic,
|
|
106
|
+
filename: $filename,
|
|
107
|
+
status: $status,
|
|
108
|
+
withPgClient: $withPgClient,
|
|
109
|
+
pgSettings: $pgSettings,
|
|
110
|
+
});
|
|
111
|
+
return (0, grafast_1.lambda)($combined, async ({ key, isPublic, filename, status, withPgClient, pgSettings }) => {
|
|
112
|
+
if (!key)
|
|
113
|
+
return null;
|
|
114
|
+
// Only provide download URLs for ready/processed files
|
|
115
|
+
if (status !== 'ready' && status !== 'processed') {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
// Resolve per-database config (bucket, publicUrlPrefix, expiry)
|
|
119
|
+
let s3ForDb = resolveS3(options); // fallback to global
|
|
120
|
+
let downloadUrlExpirySeconds = 3600; // fallback default
|
|
121
|
+
try {
|
|
122
|
+
if (withPgClient && pgSettings) {
|
|
123
|
+
const resolved = await withPgClient(null, async (pgClient) => {
|
|
124
|
+
const dbResult = await pgClient.query({
|
|
125
|
+
text: `SELECT jwt_private.current_database_id() AS id`,
|
|
126
|
+
});
|
|
127
|
+
const databaseId = dbResult.rows[0]?.id;
|
|
128
|
+
if (!databaseId)
|
|
129
|
+
return null;
|
|
130
|
+
const config = await (0, storage_module_cache_1.getStorageModuleConfig)(pgClient, databaseId);
|
|
131
|
+
if (!config)
|
|
132
|
+
return null;
|
|
133
|
+
return { config, databaseId };
|
|
109
134
|
});
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if (!config)
|
|
115
|
-
return null;
|
|
116
|
-
return { config, databaseId };
|
|
117
|
-
});
|
|
118
|
-
if (resolved) {
|
|
119
|
-
downloadUrlExpirySeconds = resolved.config.downloadUrlExpirySeconds;
|
|
120
|
-
s3ForDb = resolveS3ForDatabase(options, resolved.config, resolved.databaseId);
|
|
135
|
+
if (resolved) {
|
|
136
|
+
downloadUrlExpirySeconds = resolved.config.downloadUrlExpirySeconds;
|
|
137
|
+
s3ForDb = resolveS3ForDatabase(options, resolved.config, resolved.databaseId);
|
|
138
|
+
}
|
|
121
139
|
}
|
|
122
140
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
141
|
+
catch {
|
|
142
|
+
// Fall back to global config if lookup fails
|
|
143
|
+
}
|
|
144
|
+
if (isPublic && s3ForDb.publicUrlPrefix) {
|
|
145
|
+
// Public file: return direct CDN URL (per-database prefix)
|
|
146
|
+
return `${s3ForDb.publicUrlPrefix}/${key}`;
|
|
147
|
+
}
|
|
148
|
+
// Private file: generate presigned GET URL (per-database bucket)
|
|
149
|
+
return (0, s3_signer_1.generatePresignedGetUrl)(s3ForDb, key, downloadUrlExpirySeconds, filename || undefined);
|
|
150
|
+
});
|
|
133
151
|
},
|
|
134
152
|
}),
|
|
135
153
|
}, 'PresignedUrlDownloadPlugin adding downloadUrl field');
|
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
* COMMENT ON TABLE files IS E'@storageFiles\nStorage files table';
|
|
12
12
|
*
|
|
13
13
|
* This is explicit and reliable — no duck-typing on column names.
|
|
14
|
+
*
|
|
15
|
+
* IMPORTANT: Uses Grafast plan() instead of traditional resolve().
|
|
16
|
+
* In PostGraphile V5, Grafast's planning system does not invoke traditional
|
|
17
|
+
* resolve functions on PG table type fields — it plans them as column
|
|
18
|
+
* lookups. Since downloadUrl is a computed field (not a real column),
|
|
19
|
+
* the plan() function is required for Grafast to execute the S3 signing.
|
|
14
20
|
*/
|
|
15
21
|
import type { GraphileConfig } from 'graphile-config';
|
|
16
22
|
import type { PresignedUrlPluginOptions } from './types';
|
|
@@ -11,7 +11,14 @@
|
|
|
11
11
|
* COMMENT ON TABLE files IS E'@storageFiles\nStorage files table';
|
|
12
12
|
*
|
|
13
13
|
* This is explicit and reliable — no duck-typing on column names.
|
|
14
|
+
*
|
|
15
|
+
* IMPORTANT: Uses Grafast plan() instead of traditional resolve().
|
|
16
|
+
* In PostGraphile V5, Grafast's planning system does not invoke traditional
|
|
17
|
+
* resolve functions on PG table type fields — it plans them as column
|
|
18
|
+
* lookups. Since downloadUrl is a computed field (not a real column),
|
|
19
|
+
* the plan() function is required for Grafast to execute the S3 signing.
|
|
14
20
|
*/
|
|
21
|
+
import { context as grafastContext, lambda, object } from 'grafast';
|
|
15
22
|
import { Logger } from '@pgpmjs/logger';
|
|
16
23
|
import { generatePresignedGetUrl } from './s3-signer';
|
|
17
24
|
import { getStorageModuleConfig } from './storage-module-cache';
|
|
@@ -59,7 +66,7 @@ function resolveS3ForDatabase(options, storageConfig, databaseId) {
|
|
|
59
66
|
export function createDownloadUrlPlugin(options) {
|
|
60
67
|
return {
|
|
61
68
|
name: 'PresignedUrlDownloadPlugin',
|
|
62
|
-
version: '0.
|
|
69
|
+
version: '0.2.0',
|
|
63
70
|
description: 'Adds downloadUrl computed field to File types tagged with @storageFiles',
|
|
64
71
|
schema: {
|
|
65
72
|
hooks: {
|
|
@@ -81,52 +88,63 @@ export function createDownloadUrlPlugin(options) {
|
|
|
81
88
|
description: 'URL to download this file. For public files, returns the public URL. ' +
|
|
82
89
|
'For private files, returns a time-limited presigned URL.',
|
|
83
90
|
type: GraphQLString,
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
91
|
+
plan($parent) {
|
|
92
|
+
// Access file attributes from the parent PgSelectSingleStep
|
|
93
|
+
const $key = $parent.get('key');
|
|
94
|
+
const $isPublic = $parent.get('is_public');
|
|
95
|
+
const $filename = $parent.get('filename');
|
|
96
|
+
const $status = $parent.get('status');
|
|
97
|
+
// Access GraphQL context for per-database config resolution
|
|
98
|
+
const $withPgClient = grafastContext().get('withPgClient');
|
|
99
|
+
const $pgSettings = grafastContext().get('pgSettings');
|
|
100
|
+
const $combined = object({
|
|
101
|
+
key: $key,
|
|
102
|
+
isPublic: $isPublic,
|
|
103
|
+
filename: $filename,
|
|
104
|
+
status: $status,
|
|
105
|
+
withPgClient: $withPgClient,
|
|
106
|
+
pgSettings: $pgSettings,
|
|
107
|
+
});
|
|
108
|
+
return lambda($combined, async ({ key, isPublic, filename, status, withPgClient, pgSettings }) => {
|
|
109
|
+
if (!key)
|
|
110
|
+
return null;
|
|
111
|
+
// Only provide download URLs for ready/processed files
|
|
112
|
+
if (status !== 'ready' && status !== 'processed') {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
// Resolve per-database config (bucket, publicUrlPrefix, expiry)
|
|
116
|
+
let s3ForDb = resolveS3(options); // fallback to global
|
|
117
|
+
let downloadUrlExpirySeconds = 3600; // fallback default
|
|
118
|
+
try {
|
|
119
|
+
if (withPgClient && pgSettings) {
|
|
120
|
+
const resolved = await withPgClient(null, async (pgClient) => {
|
|
121
|
+
const dbResult = await pgClient.query({
|
|
122
|
+
text: `SELECT jwt_private.current_database_id() AS id`,
|
|
123
|
+
});
|
|
124
|
+
const databaseId = dbResult.rows[0]?.id;
|
|
125
|
+
if (!databaseId)
|
|
126
|
+
return null;
|
|
127
|
+
const config = await getStorageModuleConfig(pgClient, databaseId);
|
|
128
|
+
if (!config)
|
|
129
|
+
return null;
|
|
130
|
+
return { config, databaseId };
|
|
106
131
|
});
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (!config)
|
|
112
|
-
return null;
|
|
113
|
-
return { config, databaseId };
|
|
114
|
-
});
|
|
115
|
-
if (resolved) {
|
|
116
|
-
downloadUrlExpirySeconds = resolved.config.downloadUrlExpirySeconds;
|
|
117
|
-
s3ForDb = resolveS3ForDatabase(options, resolved.config, resolved.databaseId);
|
|
132
|
+
if (resolved) {
|
|
133
|
+
downloadUrlExpirySeconds = resolved.config.downloadUrlExpirySeconds;
|
|
134
|
+
s3ForDb = resolveS3ForDatabase(options, resolved.config, resolved.databaseId);
|
|
135
|
+
}
|
|
118
136
|
}
|
|
119
137
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
138
|
+
catch {
|
|
139
|
+
// Fall back to global config if lookup fails
|
|
140
|
+
}
|
|
141
|
+
if (isPublic && s3ForDb.publicUrlPrefix) {
|
|
142
|
+
// Public file: return direct CDN URL (per-database prefix)
|
|
143
|
+
return `${s3ForDb.publicUrlPrefix}/${key}`;
|
|
144
|
+
}
|
|
145
|
+
// Private file: generate presigned GET URL (per-database bucket)
|
|
146
|
+
return generatePresignedGetUrl(s3ForDb, key, downloadUrlExpirySeconds, filename || undefined);
|
|
147
|
+
});
|
|
130
148
|
},
|
|
131
149
|
}),
|
|
132
150
|
}, 'PresignedUrlDownloadPlugin adding downloadUrl field');
|
package/esm/plugin.js
CHANGED
|
@@ -275,9 +275,9 @@ export function createPresignedUrlPlugin(options) {
|
|
|
275
275
|
// Track the dedup request
|
|
276
276
|
await txClient.query({
|
|
277
277
|
text: `INSERT INTO ${storageConfig.uploadRequestsQualifiedName}
|
|
278
|
-
(file_id, bucket_id, key, content_type, content_hash,
|
|
279
|
-
VALUES ($1, $2, $3, $4, $5,
|
|
280
|
-
values: [existingFile.id, bucket.id, s3Key, contentType, contentHash
|
|
278
|
+
(file_id, bucket_id, key, content_type, content_hash, status, expires_at)
|
|
279
|
+
VALUES ($1, $2, $3, $4, $5, 'confirmed', NOW())`,
|
|
280
|
+
values: [existingFile.id, bucket.id, s3Key, contentType, contentHash],
|
|
281
281
|
});
|
|
282
282
|
return {
|
|
283
283
|
uploadUrl: null,
|
|
@@ -329,9 +329,9 @@ export function createPresignedUrlPlugin(options) {
|
|
|
329
329
|
// --- Track the upload request ---
|
|
330
330
|
await txClient.query({
|
|
331
331
|
text: `INSERT INTO ${storageConfig.uploadRequestsQualifiedName}
|
|
332
|
-
(file_id, bucket_id, key, content_type, content_hash,
|
|
333
|
-
VALUES ($1, $2, $3, $4, $5,
|
|
334
|
-
values: [fileId, bucket.id, s3Key, contentType, contentHash,
|
|
332
|
+
(file_id, bucket_id, key, content_type, content_hash, status, expires_at)
|
|
333
|
+
VALUES ($1, $2, $3, $4, $5, 'issued', $6)`,
|
|
334
|
+
values: [fileId, bucket.id, s3Key, contentType, contentHash, expiresAt],
|
|
335
335
|
});
|
|
336
336
|
return {
|
|
337
337
|
uploadUrl,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "graphile-presigned-url-plugin",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
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",
|
|
@@ -60,5 +60,5 @@
|
|
|
60
60
|
"@types/node": "^22.19.11",
|
|
61
61
|
"makage": "^0.1.10"
|
|
62
62
|
},
|
|
63
|
-
"gitHead": "
|
|
63
|
+
"gitHead": "a6424a875afd997535ca6b1636542a5158991003"
|
|
64
64
|
}
|
package/plugin.js
CHANGED
|
@@ -279,9 +279,9 @@ function createPresignedUrlPlugin(options) {
|
|
|
279
279
|
// Track the dedup request
|
|
280
280
|
await txClient.query({
|
|
281
281
|
text: `INSERT INTO ${storageConfig.uploadRequestsQualifiedName}
|
|
282
|
-
(file_id, bucket_id, key, content_type, content_hash,
|
|
283
|
-
VALUES ($1, $2, $3, $4, $5,
|
|
284
|
-
values: [existingFile.id, bucket.id, s3Key, contentType, contentHash
|
|
282
|
+
(file_id, bucket_id, key, content_type, content_hash, status, expires_at)
|
|
283
|
+
VALUES ($1, $2, $3, $4, $5, 'confirmed', NOW())`,
|
|
284
|
+
values: [existingFile.id, bucket.id, s3Key, contentType, contentHash],
|
|
285
285
|
});
|
|
286
286
|
return {
|
|
287
287
|
uploadUrl: null,
|
|
@@ -333,9 +333,9 @@ function createPresignedUrlPlugin(options) {
|
|
|
333
333
|
// --- Track the upload request ---
|
|
334
334
|
await txClient.query({
|
|
335
335
|
text: `INSERT INTO ${storageConfig.uploadRequestsQualifiedName}
|
|
336
|
-
(file_id, bucket_id, key, content_type, content_hash,
|
|
337
|
-
VALUES ($1, $2, $3, $4, $5,
|
|
338
|
-
values: [fileId, bucket.id, s3Key, contentType, contentHash,
|
|
336
|
+
(file_id, bucket_id, key, content_type, content_hash, status, expires_at)
|
|
337
|
+
VALUES ($1, $2, $3, $4, $5, 'issued', $6)`,
|
|
338
|
+
values: [fileId, bucket.id, s3Key, contentType, contentHash, expiresAt],
|
|
339
339
|
});
|
|
340
340
|
return {
|
|
341
341
|
uploadUrl,
|