graphile-presigned-url-plugin 0.6.2 → 0.6.4
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/package.json +3 -3
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "graphile-presigned-url-plugin",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.4",
|
|
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",
|
|
@@ -56,9 +56,9 @@
|
|
|
56
56
|
"postgraphile": "5.0.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"@constructive-io/s3-utils": "^2.
|
|
59
|
+
"@constructive-io/s3-utils": "^2.12.0",
|
|
60
60
|
"@types/node": "^22.19.11",
|
|
61
61
|
"makage": "^0.1.10"
|
|
62
62
|
},
|
|
63
|
-
"gitHead": "
|
|
63
|
+
"gitHead": "434a578648cb04d3ed25e79a75c654f71573292f"
|
|
64
64
|
}
|