graphile-presigned-url-plugin 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.
- package/LICENSE +23 -0
- package/README.md +93 -0
- package/download-url-field.d.ts +27 -0
- package/download-url-field.js +104 -0
- package/esm/download-url-field.d.ts +27 -0
- package/esm/download-url-field.js +101 -0
- package/esm/index.d.ts +34 -0
- package/esm/index.js +33 -0
- package/esm/plugin.d.ts +23 -0
- package/esm/plugin.js +339 -0
- package/esm/preset.d.ts +34 -0
- package/esm/preset.js +41 -0
- package/esm/s3-signer.d.ts +44 -0
- package/esm/s3-signer.js +87 -0
- package/esm/storage-module-cache.d.ts +40 -0
- package/esm/storage-module-cache.js +180 -0
- package/esm/types.d.ts +116 -0
- package/esm/types.js +1 -0
- package/index.d.ts +34 -0
- package/index.js +47 -0
- package/package.json +62 -0
- package/plugin.d.ts +23 -0
- package/plugin.js +343 -0
- package/preset.d.ts +34 -0
- package/preset.js +44 -0
- package/s3-signer.d.ts +44 -0
- package/s3-signer.js +92 -0
- package/storage-module-cache.d.ts +40 -0
- package/storage-module-cache.js +186 -0
- package/types.d.ts +116 -0
- package/types.js +2 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Dan Lynch <pyramation@gmail.com>
|
|
4
|
+
Copyright (c) 2025 Constructive <developers@constructive.io>
|
|
5
|
+
Copyright (c) 2020-present, Interweb, Inc.
|
|
6
|
+
|
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
in the Software without restriction, including without limitation the rights
|
|
10
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
furnished to do so, subject to the following conditions:
|
|
13
|
+
|
|
14
|
+
The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
copies or substantial portions of the Software.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# graphile-presigned-url-plugin
|
|
2
|
+
|
|
3
|
+
Presigned URL upload plugin for PostGraphile v5.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- `requestUploadUrl` mutation โ generates presigned PUT URLs for direct client-to-S3 upload
|
|
8
|
+
- `confirmUpload` mutation โ verifies upload and transitions file status to 'ready'
|
|
9
|
+
- `downloadUrl` computed field โ presigned GET URLs for private files, public URLs for public files
|
|
10
|
+
- Content-hash based S3 keys (SHA-256) with automatic deduplication
|
|
11
|
+
- Per-bucket MIME type and file size validation
|
|
12
|
+
- Upload request tracking for audit and rate limiting
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { PresignedUrlPreset } from 'graphile-presigned-url-plugin';
|
|
18
|
+
import { S3Client } from '@aws-sdk/client-s3';
|
|
19
|
+
|
|
20
|
+
const s3Client = new S3Client({ region: 'us-east-1' });
|
|
21
|
+
|
|
22
|
+
const preset = {
|
|
23
|
+
extends: [
|
|
24
|
+
PresignedUrlPreset({
|
|
25
|
+
s3: {
|
|
26
|
+
client: s3Client,
|
|
27
|
+
bucket: 'my-uploads',
|
|
28
|
+
publicUrlPrefix: 'https://cdn.example.com',
|
|
29
|
+
},
|
|
30
|
+
urlExpirySeconds: 900,
|
|
31
|
+
maxFileSize: 200 * 1024 * 1024,
|
|
32
|
+
}),
|
|
33
|
+
],
|
|
34
|
+
};
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Education and Tutorials
|
|
40
|
+
|
|
41
|
+
1. ๐ [Quickstart: Getting Up and Running](https://constructive.io/learn/quickstart)
|
|
42
|
+
Get started with modular databases in minutes. Install prerequisites and deploy your first module.
|
|
43
|
+
|
|
44
|
+
2. ๐ฆ [Modular PostgreSQL Development with Database Packages](https://constructive.io/learn/modular-postgres)
|
|
45
|
+
Learn to organize PostgreSQL projects with pgpm workspaces and reusable database modules.
|
|
46
|
+
|
|
47
|
+
3. โ๏ธ [Authoring Database Changes](https://constructive.io/learn/authoring-database-changes)
|
|
48
|
+
Master the workflow for adding, organizing, and managing database changes with pgpm.
|
|
49
|
+
|
|
50
|
+
4. ๐งช [End-to-End PostgreSQL Testing with TypeScript](https://constructive.io/learn/e2e-postgres-testing)
|
|
51
|
+
Master end-to-end PostgreSQL testing with ephemeral databases, RLS testing, and CI/CD automation.
|
|
52
|
+
|
|
53
|
+
5. โก [Supabase Testing](https://constructive.io/learn/supabase)
|
|
54
|
+
Use TypeScript-first tools to test Supabase projects with realistic RLS, policies, and auth contexts.
|
|
55
|
+
|
|
56
|
+
6. ๐ง [Drizzle ORM Testing](https://constructive.io/learn/drizzle-testing)
|
|
57
|
+
Run full-stack tests with Drizzle ORM, including database setup, teardown, and RLS enforcement.
|
|
58
|
+
|
|
59
|
+
7. ๐ง [Troubleshooting](https://constructive.io/learn/troubleshooting)
|
|
60
|
+
Common issues and solutions for pgpm, PostgreSQL, and testing.
|
|
61
|
+
|
|
62
|
+
## Related Constructive Tooling
|
|
63
|
+
|
|
64
|
+
### ๐ฆ Package Management
|
|
65
|
+
|
|
66
|
+
* [pgpm](https://github.com/constructive-io/constructive/tree/main/pgpm/pgpm): **๐ฅ๏ธ PostgreSQL Package Manager** for modular Postgres development. Works with database workspaces, scaffolding, migrations, seeding, and installing database packages.
|
|
67
|
+
|
|
68
|
+
### ๐งช Testing
|
|
69
|
+
|
|
70
|
+
* [pgsql-test](https://github.com/constructive-io/constructive/tree/main/postgres/pgsql-test): **๐ Isolated testing environments** with per-test transaction rollbacksโideal for integration tests, complex migrations, and RLS simulation.
|
|
71
|
+
* [pgsql-seed](https://github.com/constructive-io/constructive/tree/main/postgres/pgsql-seed): **๐ฑ PostgreSQL seeding utilities** for CSV, JSON, SQL data loading, and pgpm deployment.
|
|
72
|
+
* [supabase-test](https://github.com/constructive-io/constructive/tree/main/postgres/supabase-test): **๐งช Supabase-native test harness** preconfigured for the local Supabase stackโper-test rollbacks, JWT/role context helpers, and CI/GitHub Actions ready.
|
|
73
|
+
* [graphile-test](https://github.com/constructive-io/constructive/tree/main/graphile/graphile-test): **๐ Authentication mocking** for Graphile-focused test helpers and emulating row-level security contexts.
|
|
74
|
+
* [pg-query-context](https://github.com/constructive-io/constructive/tree/main/postgres/pg-query-context): **๐ Session context injection** to add session-local context (e.g., `SET LOCAL`) into queriesโideal for setting `role`, `jwt.claims`, and other session settings.
|
|
75
|
+
|
|
76
|
+
### ๐ง Parsing & AST
|
|
77
|
+
|
|
78
|
+
* [pgsql-parser](https://www.npmjs.com/package/pgsql-parser): **๐ SQL conversion engine** that interprets and converts PostgreSQL syntax.
|
|
79
|
+
* [libpg-query-node](https://www.npmjs.com/package/libpg-query): **๐ Node.js bindings** for `libpg_query`, converting SQL into parse trees.
|
|
80
|
+
* [pg-proto-parser](https://www.npmjs.com/package/pg-proto-parser): **๐ฆ Protobuf parser** for parsing PostgreSQL Protocol Buffers definitions to generate TypeScript interfaces, utility functions, and JSON mappings for enums.
|
|
81
|
+
* [@pgsql/enums](https://www.npmjs.com/package/@pgsql/enums): **๐ท๏ธ TypeScript enums** for PostgreSQL AST for safe and ergonomic parsing logic.
|
|
82
|
+
* [@pgsql/types](https://www.npmjs.com/package/@pgsql/types): **๐ Type definitions** for PostgreSQL AST nodes in TypeScript.
|
|
83
|
+
* [@pgsql/utils](https://www.npmjs.com/package/@pgsql/utils): **๐ ๏ธ AST utilities** for constructing and transforming PostgreSQL syntax trees.
|
|
84
|
+
|
|
85
|
+
## Credits
|
|
86
|
+
|
|
87
|
+
**๐ Built by the [Constructive](https://constructive.io) team โ creators of modular Postgres tooling for secure, composable backends. If you like our work, contribute on [GitHub](https://github.com/constructive-io).**
|
|
88
|
+
|
|
89
|
+
## Disclaimer
|
|
90
|
+
|
|
91
|
+
AS DESCRIBED IN THE LICENSES, THE SOFTWARE IS PROVIDED "AS IS", AT YOUR OWN RISK, AND WITHOUT WARRANTIES OF ANY KIND.
|
|
92
|
+
|
|
93
|
+
No developer or entity involved in creating this software will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the code, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or loss of profits, cryptocurrencies, tokens, or anything else of value.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* downloadUrl Computed Field Plugin
|
|
3
|
+
*
|
|
4
|
+
* Adds a `downloadUrl` computed field to File types in the GraphQL schema.
|
|
5
|
+
* For public files, returns the public URL prefix + key.
|
|
6
|
+
* For private files, generates a presigned GET URL.
|
|
7
|
+
*
|
|
8
|
+
* Detection: Uses the `@storageFiles` smart tag on the codec (table).
|
|
9
|
+
* The storage module generator in constructive-db sets this tag on the
|
|
10
|
+
* generated files table via a smart comment:
|
|
11
|
+
* COMMENT ON TABLE files IS E'@storageFiles\nStorage files table';
|
|
12
|
+
*
|
|
13
|
+
* This is explicit and reliable โ no duck-typing on column names.
|
|
14
|
+
*/
|
|
15
|
+
import type { GraphileConfig } from 'graphile-config';
|
|
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
|
+
export declare function createDownloadUrlPlugin(options: PresignedUrlPluginOptions): GraphileConfig.Plugin;
|
|
27
|
+
export default createDownloadUrlPlugin;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* downloadUrl Computed Field Plugin
|
|
4
|
+
*
|
|
5
|
+
* Adds a `downloadUrl` computed field to File types in the GraphQL schema.
|
|
6
|
+
* For public files, returns the public URL prefix + key.
|
|
7
|
+
* For private files, generates a presigned GET URL.
|
|
8
|
+
*
|
|
9
|
+
* Detection: Uses the `@storageFiles` smart tag on the codec (table).
|
|
10
|
+
* The storage module generator in constructive-db sets this tag on the
|
|
11
|
+
* generated files table via a smart comment:
|
|
12
|
+
* COMMENT ON TABLE files IS E'@storageFiles\nStorage files table';
|
|
13
|
+
*
|
|
14
|
+
* This is explicit and reliable โ no duck-typing on column names.
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.createDownloadUrlPlugin = createDownloadUrlPlugin;
|
|
18
|
+
const logger_1 = require("@pgpmjs/logger");
|
|
19
|
+
const s3_signer_1 = require("./s3-signer");
|
|
20
|
+
const storage_module_cache_1 = require("./storage-module-cache");
|
|
21
|
+
const log = new logger_1.Logger('graphile-presigned-url:download-url');
|
|
22
|
+
/**
|
|
23
|
+
* Creates the downloadUrl computed field plugin.
|
|
24
|
+
*
|
|
25
|
+
* This is a separate plugin from the main presigned URL plugin because it
|
|
26
|
+
* uses the GraphQLObjectType_fields hook (low-level) rather than extendSchema.
|
|
27
|
+
* The downloadUrl field needs to be added dynamically to whatever table is
|
|
28
|
+
* the storage module's files table, which we discover at schema-build time
|
|
29
|
+
* via the `@storageFiles` smart tag.
|
|
30
|
+
*/
|
|
31
|
+
function createDownloadUrlPlugin(options) {
|
|
32
|
+
const { s3 } = options;
|
|
33
|
+
return {
|
|
34
|
+
name: 'PresignedUrlDownloadPlugin',
|
|
35
|
+
version: '0.1.0',
|
|
36
|
+
description: 'Adds downloadUrl computed field to File types tagged with @storageFiles',
|
|
37
|
+
schema: {
|
|
38
|
+
hooks: {
|
|
39
|
+
GraphQLObjectType_fields(fields, build, context) {
|
|
40
|
+
const { scope: { pgCodec, isPgClassType }, } = context;
|
|
41
|
+
// Only process PG class types (table row types)
|
|
42
|
+
if (!isPgClassType || !pgCodec || !pgCodec.attributes) {
|
|
43
|
+
return fields;
|
|
44
|
+
}
|
|
45
|
+
// Check for @storageFiles smart tag โ set by the storage module generator
|
|
46
|
+
const tags = pgCodec.extensions?.tags;
|
|
47
|
+
if (!tags?.storageFiles) {
|
|
48
|
+
return fields;
|
|
49
|
+
}
|
|
50
|
+
log.debug(`Adding downloadUrl field to type: ${pgCodec.name} (has @storageFiles tag)`);
|
|
51
|
+
const { graphql: { GraphQLString }, } = build;
|
|
52
|
+
return build.extend(fields, {
|
|
53
|
+
downloadUrl: context.fieldWithHooks({ fieldName: 'downloadUrl' }, {
|
|
54
|
+
description: 'URL to download this file. For public files, returns the public URL. ' +
|
|
55
|
+
'For private files, returns a time-limited presigned URL.',
|
|
56
|
+
type: GraphQLString,
|
|
57
|
+
async resolve(parent, _args, context) {
|
|
58
|
+
const key = parent.key || parent.get?.('key');
|
|
59
|
+
const isPublic = parent.is_public ?? parent.get?.('is_public');
|
|
60
|
+
const filename = parent.filename || parent.get?.('filename');
|
|
61
|
+
const status = parent.status || parent.get?.('status');
|
|
62
|
+
if (!key)
|
|
63
|
+
return null;
|
|
64
|
+
// Only provide download URLs for ready/processed files
|
|
65
|
+
if (status !== 'ready' && status !== 'processed') {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
if (isPublic && s3.publicUrlPrefix) {
|
|
69
|
+
// Public file: return direct URL
|
|
70
|
+
return `${s3.publicUrlPrefix}/${key}`;
|
|
71
|
+
}
|
|
72
|
+
// Resolve download URL expiry from storage module config (per-database)
|
|
73
|
+
let downloadUrlExpirySeconds = 3600; // fallback default
|
|
74
|
+
try {
|
|
75
|
+
const withPgClient = context.pgSettings
|
|
76
|
+
? context.withPgClient
|
|
77
|
+
: null;
|
|
78
|
+
if (withPgClient) {
|
|
79
|
+
const config = await withPgClient(null, async (pgClient) => {
|
|
80
|
+
const dbResult = await pgClient.query(`SELECT jwt_private.current_database_id() AS id`);
|
|
81
|
+
const databaseId = dbResult.rows[0]?.id;
|
|
82
|
+
if (!databaseId)
|
|
83
|
+
return null;
|
|
84
|
+
return (0, storage_module_cache_1.getStorageModuleConfig)(pgClient, databaseId);
|
|
85
|
+
});
|
|
86
|
+
if (config) {
|
|
87
|
+
downloadUrlExpirySeconds = config.downloadUrlExpirySeconds;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// Fall back to default if config lookup fails
|
|
93
|
+
}
|
|
94
|
+
// Private file: generate presigned GET URL
|
|
95
|
+
return (0, s3_signer_1.generatePresignedGetUrl)(s3, key, downloadUrlExpirySeconds, filename || undefined);
|
|
96
|
+
},
|
|
97
|
+
}),
|
|
98
|
+
}, 'PresignedUrlDownloadPlugin adding downloadUrl field');
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
exports.default = createDownloadUrlPlugin;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* downloadUrl Computed Field Plugin
|
|
3
|
+
*
|
|
4
|
+
* Adds a `downloadUrl` computed field to File types in the GraphQL schema.
|
|
5
|
+
* For public files, returns the public URL prefix + key.
|
|
6
|
+
* For private files, generates a presigned GET URL.
|
|
7
|
+
*
|
|
8
|
+
* Detection: Uses the `@storageFiles` smart tag on the codec (table).
|
|
9
|
+
* The storage module generator in constructive-db sets this tag on the
|
|
10
|
+
* generated files table via a smart comment:
|
|
11
|
+
* COMMENT ON TABLE files IS E'@storageFiles\nStorage files table';
|
|
12
|
+
*
|
|
13
|
+
* This is explicit and reliable โ no duck-typing on column names.
|
|
14
|
+
*/
|
|
15
|
+
import type { GraphileConfig } from 'graphile-config';
|
|
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
|
+
export declare function createDownloadUrlPlugin(options: PresignedUrlPluginOptions): GraphileConfig.Plugin;
|
|
27
|
+
export default createDownloadUrlPlugin;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* downloadUrl Computed Field Plugin
|
|
3
|
+
*
|
|
4
|
+
* Adds a `downloadUrl` computed field to File types in the GraphQL schema.
|
|
5
|
+
* For public files, returns the public URL prefix + key.
|
|
6
|
+
* For private files, generates a presigned GET URL.
|
|
7
|
+
*
|
|
8
|
+
* Detection: Uses the `@storageFiles` smart tag on the codec (table).
|
|
9
|
+
* The storage module generator in constructive-db sets this tag on the
|
|
10
|
+
* generated files table via a smart comment:
|
|
11
|
+
* COMMENT ON TABLE files IS E'@storageFiles\nStorage files table';
|
|
12
|
+
*
|
|
13
|
+
* This is explicit and reliable โ no duck-typing on column names.
|
|
14
|
+
*/
|
|
15
|
+
import { Logger } from '@pgpmjs/logger';
|
|
16
|
+
import { generatePresignedGetUrl } from './s3-signer';
|
|
17
|
+
import { getStorageModuleConfig } from './storage-module-cache';
|
|
18
|
+
const log = new Logger('graphile-presigned-url:download-url');
|
|
19
|
+
/**
|
|
20
|
+
* Creates the downloadUrl computed field plugin.
|
|
21
|
+
*
|
|
22
|
+
* This is a separate plugin from the main presigned URL plugin because it
|
|
23
|
+
* uses the GraphQLObjectType_fields hook (low-level) rather than extendSchema.
|
|
24
|
+
* The downloadUrl field needs to be added dynamically to whatever table is
|
|
25
|
+
* the storage module's files table, which we discover at schema-build time
|
|
26
|
+
* via the `@storageFiles` smart tag.
|
|
27
|
+
*/
|
|
28
|
+
export function createDownloadUrlPlugin(options) {
|
|
29
|
+
const { s3 } = options;
|
|
30
|
+
return {
|
|
31
|
+
name: 'PresignedUrlDownloadPlugin',
|
|
32
|
+
version: '0.1.0',
|
|
33
|
+
description: 'Adds downloadUrl computed field to File types tagged with @storageFiles',
|
|
34
|
+
schema: {
|
|
35
|
+
hooks: {
|
|
36
|
+
GraphQLObjectType_fields(fields, build, context) {
|
|
37
|
+
const { scope: { pgCodec, isPgClassType }, } = context;
|
|
38
|
+
// Only process PG class types (table row types)
|
|
39
|
+
if (!isPgClassType || !pgCodec || !pgCodec.attributes) {
|
|
40
|
+
return fields;
|
|
41
|
+
}
|
|
42
|
+
// Check for @storageFiles smart tag โ set by the storage module generator
|
|
43
|
+
const tags = pgCodec.extensions?.tags;
|
|
44
|
+
if (!tags?.storageFiles) {
|
|
45
|
+
return fields;
|
|
46
|
+
}
|
|
47
|
+
log.debug(`Adding downloadUrl field to type: ${pgCodec.name} (has @storageFiles tag)`);
|
|
48
|
+
const { graphql: { GraphQLString }, } = build;
|
|
49
|
+
return build.extend(fields, {
|
|
50
|
+
downloadUrl: context.fieldWithHooks({ fieldName: 'downloadUrl' }, {
|
|
51
|
+
description: 'URL to download this file. For public files, returns the public URL. ' +
|
|
52
|
+
'For private files, returns a time-limited presigned URL.',
|
|
53
|
+
type: GraphQLString,
|
|
54
|
+
async resolve(parent, _args, context) {
|
|
55
|
+
const key = parent.key || parent.get?.('key');
|
|
56
|
+
const isPublic = parent.is_public ?? parent.get?.('is_public');
|
|
57
|
+
const filename = parent.filename || parent.get?.('filename');
|
|
58
|
+
const status = parent.status || parent.get?.('status');
|
|
59
|
+
if (!key)
|
|
60
|
+
return null;
|
|
61
|
+
// Only provide download URLs for ready/processed files
|
|
62
|
+
if (status !== 'ready' && status !== 'processed') {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
if (isPublic && s3.publicUrlPrefix) {
|
|
66
|
+
// Public file: return direct URL
|
|
67
|
+
return `${s3.publicUrlPrefix}/${key}`;
|
|
68
|
+
}
|
|
69
|
+
// Resolve download URL expiry from storage module config (per-database)
|
|
70
|
+
let downloadUrlExpirySeconds = 3600; // fallback default
|
|
71
|
+
try {
|
|
72
|
+
const withPgClient = context.pgSettings
|
|
73
|
+
? context.withPgClient
|
|
74
|
+
: null;
|
|
75
|
+
if (withPgClient) {
|
|
76
|
+
const config = await withPgClient(null, async (pgClient) => {
|
|
77
|
+
const dbResult = await pgClient.query(`SELECT jwt_private.current_database_id() AS id`);
|
|
78
|
+
const databaseId = dbResult.rows[0]?.id;
|
|
79
|
+
if (!databaseId)
|
|
80
|
+
return null;
|
|
81
|
+
return getStorageModuleConfig(pgClient, databaseId);
|
|
82
|
+
});
|
|
83
|
+
if (config) {
|
|
84
|
+
downloadUrlExpirySeconds = config.downloadUrlExpirySeconds;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Fall back to default if config lookup fails
|
|
90
|
+
}
|
|
91
|
+
// Private file: generate presigned GET URL
|
|
92
|
+
return generatePresignedGetUrl(s3, key, downloadUrlExpirySeconds, filename || undefined);
|
|
93
|
+
},
|
|
94
|
+
}),
|
|
95
|
+
}, 'PresignedUrlDownloadPlugin adding downloadUrl field');
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
export default createDownloadUrlPlugin;
|
package/esm/index.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Presigned URL Plugin for PostGraphile v5
|
|
3
|
+
*
|
|
4
|
+
* Provides presigned URL upload capabilities for PostGraphile v5:
|
|
5
|
+
* - requestUploadUrl mutation (presigned PUT URL generation)
|
|
6
|
+
* - confirmUpload mutation (upload verification + status transition)
|
|
7
|
+
* - downloadUrl computed field (presigned GET URL / public URL)
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { PresignedUrlPreset } from 'graphile-presigned-url-plugin';
|
|
12
|
+
* import { S3Client } from '@aws-sdk/client-s3';
|
|
13
|
+
*
|
|
14
|
+
* const s3Client = new S3Client({ region: 'us-east-1' });
|
|
15
|
+
*
|
|
16
|
+
* const preset = {
|
|
17
|
+
* extends: [
|
|
18
|
+
* PresignedUrlPreset({
|
|
19
|
+
* s3: {
|
|
20
|
+
* client: s3Client,
|
|
21
|
+
* bucket: 'my-uploads',
|
|
22
|
+
* publicUrlPrefix: 'https://cdn.example.com',
|
|
23
|
+
* },
|
|
24
|
+
* }),
|
|
25
|
+
* ],
|
|
26
|
+
* };
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export { PresignedUrlPlugin, createPresignedUrlPlugin } from './plugin';
|
|
30
|
+
export { createDownloadUrlPlugin } from './download-url-field';
|
|
31
|
+
export { PresignedUrlPreset } from './preset';
|
|
32
|
+
export { getStorageModuleConfig, getBucketConfig, clearStorageModuleCache, clearBucketCache } from './storage-module-cache';
|
|
33
|
+
export { generatePresignedPutUrl, generatePresignedGetUrl, headObject } from './s3-signer';
|
|
34
|
+
export type { BucketConfig, StorageModuleConfig, RequestUploadUrlInput, RequestUploadUrlPayload, ConfirmUploadInput, ConfirmUploadPayload, S3Config, PresignedUrlPluginOptions, } from './types';
|
package/esm/index.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Presigned URL Plugin for PostGraphile v5
|
|
3
|
+
*
|
|
4
|
+
* Provides presigned URL upload capabilities for PostGraphile v5:
|
|
5
|
+
* - requestUploadUrl mutation (presigned PUT URL generation)
|
|
6
|
+
* - confirmUpload mutation (upload verification + status transition)
|
|
7
|
+
* - downloadUrl computed field (presigned GET URL / public URL)
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { PresignedUrlPreset } from 'graphile-presigned-url-plugin';
|
|
12
|
+
* import { S3Client } from '@aws-sdk/client-s3';
|
|
13
|
+
*
|
|
14
|
+
* const s3Client = new S3Client({ region: 'us-east-1' });
|
|
15
|
+
*
|
|
16
|
+
* const preset = {
|
|
17
|
+
* extends: [
|
|
18
|
+
* PresignedUrlPreset({
|
|
19
|
+
* s3: {
|
|
20
|
+
* client: s3Client,
|
|
21
|
+
* bucket: 'my-uploads',
|
|
22
|
+
* publicUrlPrefix: 'https://cdn.example.com',
|
|
23
|
+
* },
|
|
24
|
+
* }),
|
|
25
|
+
* ],
|
|
26
|
+
* };
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export { PresignedUrlPlugin, createPresignedUrlPlugin } from './plugin';
|
|
30
|
+
export { createDownloadUrlPlugin } from './download-url-field';
|
|
31
|
+
export { PresignedUrlPreset } from './preset';
|
|
32
|
+
export { getStorageModuleConfig, getBucketConfig, clearStorageModuleCache, clearBucketCache } from './storage-module-cache';
|
|
33
|
+
export { generatePresignedPutUrl, generatePresignedGetUrl, headObject } from './s3-signer';
|
package/esm/plugin.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Presigned URL Plugin for PostGraphile v5
|
|
3
|
+
*
|
|
4
|
+
* Adds presigned URL upload support to PostGraphile v5:
|
|
5
|
+
*
|
|
6
|
+
* 1. `requestUploadUrl` mutation โ generates a presigned PUT URL for direct
|
|
7
|
+
* client-to-S3 upload. Checks bucket access via RLS, deduplicates by
|
|
8
|
+
* content hash, tracks the request in upload_requests.
|
|
9
|
+
*
|
|
10
|
+
* 2. `confirmUpload` mutation โ confirms a file was uploaded to S3, verifies
|
|
11
|
+
* the object exists with correct content-type, transitions file status
|
|
12
|
+
* from 'pending' to 'ready'.
|
|
13
|
+
*
|
|
14
|
+
* 3. `downloadUrl` computed field on File types โ generates presigned GET URLs
|
|
15
|
+
* for private files, returns public URL prefix + key for public files.
|
|
16
|
+
*
|
|
17
|
+
* Uses the extendSchema + grafast plan pattern (same as PublicKeySignature).
|
|
18
|
+
*/
|
|
19
|
+
import type { GraphileConfig } from 'graphile-config';
|
|
20
|
+
import type { PresignedUrlPluginOptions } from './types';
|
|
21
|
+
export declare function createPresignedUrlPlugin(options: PresignedUrlPluginOptions): GraphileConfig.Plugin;
|
|
22
|
+
export declare const PresignedUrlPlugin: typeof createPresignedUrlPlugin;
|
|
23
|
+
export default PresignedUrlPlugin;
|