graphile-upload-plugin 1.1.1 โ†’ 2.2.1

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 CHANGED
@@ -1,85 +1,6 @@
1
1
  # graphile-upload-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">
12
- <img height="20" src="https://img.shields.io/badge/license-MIT-blue.svg"/>
13
- </a>
14
- <a href="https://www.npmjs.com/package/graphile-upload-plugin">
15
- <img height="20" src="https://img.shields.io/github/package-json/v/constructive-io/constructive?filename=graphile%2Fgraphile-upload-plugin%2Fpackage.json"/>
16
- </a>
17
- </p>
18
-
19
- **`graphile-upload-plugin`** adds an `Upload` scalar and upload field resolvers for PostGraphile, letting you store uploaded metadata in PostgreSQL columns.
20
-
21
- ## ๐Ÿš€ Installation
22
-
23
- ```bash
24
- pnpm add graphile-upload-plugin
25
- ```
26
-
27
- ## โœจ Features
28
-
29
- - Adds the `Upload` scalar to PostGraphile
30
- - Supports upload resolvers by type or smart comment tag
31
- - Flexible resolver hook to store files anywhere (S3, local, etc.)
32
-
33
- ## ๐Ÿ“ฆ Usage
34
-
35
- ```ts
36
- import express from 'express';
37
- import { postgraphile } from 'postgraphile';
38
- import UploadPostGraphilePlugin from 'graphile-upload-plugin';
39
-
40
- const app = express();
41
- app.use(
42
- postgraphile(process.env.DATABASE_URL, ['app_public'], {
43
- appendPlugins: [UploadPostGraphilePlugin],
44
- graphileBuildOptions: {
45
- uploadFieldDefinitions: [
46
- {
47
- name: 'upload',
48
- namespaceName: 'public',
49
- type: 'JSON',
50
- resolve: async (upload, args, context, info) => {
51
- // Handle upload
52
- return { url: '...', size: upload.size };
53
- },
54
- },
55
- {
56
- tag: 'upload',
57
- resolve: async (upload, args, context, info) => {
58
- // Handle upload by tag
59
- return { url: '...' };
60
- },
61
- },
62
- ],
63
- },
64
- })
65
- );
66
- ```
67
-
68
- ## ๐Ÿ”ง Configuration
69
-
70
- The plugin accepts `uploadFieldDefinitions` in `graphileBuildOptions`:
71
-
72
- - **By type**: Match PostgreSQL types by `name` and `namespaceName`
73
- - **By tag**: Match columns via smart comments (e.g., `@upload`)
74
-
75
- Each definition requires a `resolve` function that processes the upload and returns the value to store in the database.
76
-
77
- ## ๐Ÿงช Testing
78
-
79
- ```sh
80
- # requires a local Postgres available (defaults to postgres/password@localhost:5432)
81
- pnpm --filter graphile-upload-plugin test
82
- ```
3
+ File upload support for PostGraphile v5.
83
4
 
84
5
  ---
85
6
 
package/esm/index.d.ts ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * PostGraphile v5 Upload Plugin
3
+ *
4
+ * Provides file upload capabilities for PostGraphile v5 mutations.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { UploadPlugin, UploadPreset } from 'graphile-upload-plugin';
9
+ *
10
+ * // Option 1: Use the preset (recommended)
11
+ * const preset = {
12
+ * extends: [
13
+ * UploadPreset({
14
+ * uploadFieldDefinitions: [
15
+ * { tag: 'upload', resolve: myUploadResolver },
16
+ * ],
17
+ * }),
18
+ * ],
19
+ * };
20
+ *
21
+ * // Option 2: Use the plugin directly
22
+ * const plugin = UploadPlugin({
23
+ * uploadFieldDefinitions: [
24
+ * { tag: 'upload', resolve: myUploadResolver },
25
+ * ],
26
+ * });
27
+ * ```
28
+ */
29
+ export { UploadPlugin, createUploadPlugin } from './plugin';
30
+ export { UploadPreset } from './preset';
31
+ export type { FileUpload, UploadFieldDefinition, UploadPluginInfo, UploadPluginOptions, UploadResolver } from './types';
package/esm/index.js CHANGED
@@ -1,4 +1,30 @@
1
- import UploadPostGraphilePlugin from './plugin';
2
- export { UploadPostGraphilePlugin, };
3
- export { Uploader } from './resolvers/upload';
4
- export default UploadPostGraphilePlugin;
1
+ /**
2
+ * PostGraphile v5 Upload Plugin
3
+ *
4
+ * Provides file upload capabilities for PostGraphile v5 mutations.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { UploadPlugin, UploadPreset } from 'graphile-upload-plugin';
9
+ *
10
+ * // Option 1: Use the preset (recommended)
11
+ * const preset = {
12
+ * extends: [
13
+ * UploadPreset({
14
+ * uploadFieldDefinitions: [
15
+ * { tag: 'upload', resolve: myUploadResolver },
16
+ * ],
17
+ * }),
18
+ * ],
19
+ * };
20
+ *
21
+ * // Option 2: Use the plugin directly
22
+ * const plugin = UploadPlugin({
23
+ * uploadFieldDefinitions: [
24
+ * { tag: 'upload', resolve: myUploadResolver },
25
+ * ],
26
+ * });
27
+ * ```
28
+ */
29
+ export { UploadPlugin, createUploadPlugin } from './plugin';
30
+ export { UploadPreset } from './preset';
@@ -0,0 +1,32 @@
1
+ /**
2
+ * PostGraphile v5 Upload Plugin
3
+ *
4
+ * Adds file upload support to PostGraphile v5 mutations. For columns that match
5
+ * the configured upload field definitions (by PG type name/namespace or by smart
6
+ * tag), this plugin:
7
+ *
8
+ * 1. Registers a GraphQL `Upload` scalar type
9
+ * 2. Adds `*Upload` input fields on mutation input types
10
+ * 3. Wraps mutation field resolvers to process file uploads before the mutation
11
+ * executes, calling the user-supplied resolver for each upload
12
+ *
13
+ * In v5, the `GraphQLObjectType_fields_field` hook wraps the `resolve` function
14
+ * (which still exists on mutation fields alongside `plan`) to intercept uploads
15
+ * at the HTTP layer before the plan executes.
16
+ *
17
+ * COMPATIBILITY NOTE:
18
+ * This plugin uses v4-style resolver wrapping via GraphQLObjectType_fields_field hook.
19
+ * grafserv v5 supports this through its backwards-compatibility layer.
20
+ * This plugin requires grafserv's resolver support to be enabled (default in v5 RC).
21
+ * It will NOT work in a pure grafast plan-only execution context.
22
+ */
23
+ import 'graphile-build';
24
+ import 'graphile-build-pg';
25
+ import type { GraphileConfig } from 'graphile-config';
26
+ import type { UploadPluginOptions } from './types';
27
+ /**
28
+ * Creates the Upload plugin with the given options.
29
+ */
30
+ export declare function createUploadPlugin(options?: UploadPluginOptions): GraphileConfig.Plugin;
31
+ export declare const UploadPlugin: typeof createUploadPlugin;
32
+ export default UploadPlugin;
package/esm/plugin.js CHANGED
@@ -1,158 +1,260 @@
1
- // PostGraphile plugin
2
- const UploadPostGraphilePlugin = (builder, opts = {}) => {
3
- const { uploadFieldDefinitions = [] } = opts;
4
- // Determine whether a table attribute should be treated as an Upload according to configuration
5
- const relevantUploadType = (attr) => {
6
- const types = uploadFieldDefinitions.filter(({ name, namespaceName, tag }) => (name &&
7
- namespaceName &&
8
- attr.type?.name === name &&
9
- attr.type?.namespaceName === namespaceName) ||
10
- (tag && attr.tags?.[tag]));
11
- if (types.length === 1) {
12
- return types[0];
1
+ /**
2
+ * PostGraphile v5 Upload Plugin
3
+ *
4
+ * Adds file upload support to PostGraphile v5 mutations. For columns that match
5
+ * the configured upload field definitions (by PG type name/namespace or by smart
6
+ * tag), this plugin:
7
+ *
8
+ * 1. Registers a GraphQL `Upload` scalar type
9
+ * 2. Adds `*Upload` input fields on mutation input types
10
+ * 3. Wraps mutation field resolvers to process file uploads before the mutation
11
+ * executes, calling the user-supplied resolver for each upload
12
+ *
13
+ * In v5, the `GraphQLObjectType_fields_field` hook wraps the `resolve` function
14
+ * (which still exists on mutation fields alongside `plan`) to intercept uploads
15
+ * at the HTTP layer before the plan executes.
16
+ *
17
+ * COMPATIBILITY NOTE:
18
+ * This plugin uses v4-style resolver wrapping via GraphQLObjectType_fields_field hook.
19
+ * grafserv v5 supports this through its backwards-compatibility layer.
20
+ * This plugin requires grafserv's resolver support to be enabled (default in v5 RC).
21
+ * It will NOT work in a pure grafast plan-only execution context.
22
+ */
23
+ import 'graphile-build';
24
+ import 'graphile-build-pg';
25
+ import { Transform } from 'stream';
26
+ /**
27
+ * Determines whether a codec attribute matches an upload field definition.
28
+ * Returns the matching definition or undefined.
29
+ */
30
+ function relevantUploadType(attribute, uploadFieldDefinitions) {
31
+ const types = uploadFieldDefinitions.filter(({ name, namespaceName, tag }) => {
32
+ if (name && namespaceName) {
33
+ // Type-name based matching: check the attribute's codec PG extension metadata
34
+ const pgExt = attribute.codec?.extensions?.pg;
35
+ if (pgExt && pgExt.name === name && pgExt.schemaName === namespaceName) {
36
+ return true;
37
+ }
38
+ // Fallback: check codec name directly
39
+ if (attribute.codec?.name === name) {
40
+ return true;
41
+ }
13
42
  }
14
- else if (types.length > 1) {
15
- throw new Error('Upload field definitions are ambiguous');
43
+ if (tag) {
44
+ // Smart-tag based matching: check if the attribute has the specified tag
45
+ const tags = attribute.extensions?.tags;
46
+ if (tags && tags[tag]) {
47
+ return true;
48
+ }
16
49
  }
17
- return undefined;
18
- };
19
- builder.hook('build', (input, build) => {
20
- const { addType, graphql: { GraphQLScalarType, GraphQLError }, } = build;
21
- const GraphQLUpload = new GraphQLScalarType({
22
- name: 'Upload',
23
- description: 'The `Upload` scalar type represents a file upload.',
24
- parseValue(value) {
25
- // The value should be an object with a `.promise` that resolves to the file upload
26
- const maybe = value;
27
- if (maybe &&
28
- maybe.promise &&
29
- typeof maybe.promise.then === 'function') {
30
- return maybe.promise;
31
- }
32
- throw new GraphQLError('Upload value invalid.');
33
- },
34
- parseLiteral(ast) {
35
- throw new GraphQLError('Upload literal unsupported.', ast);
36
- },
37
- serialize() {
38
- throw new GraphQLError('Upload serialization unsupported.');
39
- },
40
- });
41
- addType(GraphQLUpload);
42
- // Override the internal types for configured upload-backed columns
43
- uploadFieldDefinitions.forEach(({ name, namespaceName, type }) => {
44
- if (!name || !type || !namespaceName)
45
- return; // tag-based or incomplete definitions
46
- const theType = build.pgIntrospectionResultsByKind.type.find((typ) => typ.name === name && typ.namespaceName === namespaceName);
47
- if (theType) {
48
- build.pgRegisterGqlTypeByTypeId(theType.id, () => build.getTypeByName(type));
50
+ return false;
51
+ });
52
+ if (types.length === 1) {
53
+ return types[0];
54
+ }
55
+ else if (types.length > 1) {
56
+ throw new Error('Upload field definitions are ambiguous');
57
+ }
58
+ return undefined;
59
+ }
60
+ function createSizeLimitStream(source, maxFileSize) {
61
+ let bytesRead = 0;
62
+ const limiter = new Transform({
63
+ transform(chunk, _encoding, callback) {
64
+ const chunkSize = Buffer.isBuffer(chunk)
65
+ ? chunk.length
66
+ : Buffer.byteLength(String(chunk));
67
+ bytesRead += chunkSize;
68
+ if (bytesRead > maxFileSize) {
69
+ callback(new Error(`File exceeds maximum size of ${maxFileSize} bytes`));
70
+ return;
49
71
  }
50
- });
51
- return input;
72
+ callback(null, chunk);
73
+ }
52
74
  });
53
- builder.hook('inflection', (inflection, build) => {
54
- return build.extend(inflection, {
55
- // NO ARROW FUNCTIONS HERE (this)
56
- uploadColumn(attr) {
57
- return this.column(attr) + 'Upload';
58
- },
59
- });
75
+ // Ensure source stream errors are always forwarded to the consumer stream.
76
+ source.on('error', (error) => {
77
+ limiter.destroy(error);
60
78
  });
61
- // Add Upload input fields alongside matching columns
62
- builder.hook('GraphQLInputObjectType:fields', (fields, build, context) => {
63
- const { scope: { isPgRowType, pgIntrospection: table }, } = context;
64
- if (!isPgRowType || !table || table.kind !== 'class') {
65
- return fields;
79
+ limiter.on('error', (error) => {
80
+ if (!source.destroyed) {
81
+ source.destroy(error);
66
82
  }
67
- return build.extend(fields, table.attributes.reduce((memo, attr) => {
68
- if (!build.pgColumnFilter(attr, build, context))
69
- return memo;
70
- const action = context.scope.isPgBaseInput
71
- ? 'base'
72
- : context.scope.isPgPatch
73
- ? 'update'
74
- : 'create';
75
- if (build.pgOmit(attr, action))
76
- return memo;
77
- if (attr.identity === 'a')
78
- return memo;
79
- if (!relevantUploadType(attr)) {
80
- return memo;
81
- }
82
- const fieldName = build.inflection.uploadColumn(attr);
83
- if (memo[fieldName]) {
84
- throw new Error(`Two columns produce the same GraphQL field name '${fieldName}' on class '${table.namespaceName}.${table.name}'; one of them is '${attr.name}'`);
85
- }
86
- memo = build.extend(memo, {
87
- [fieldName]: context.fieldWithHooks(fieldName, {
88
- description: attr.description,
89
- type: build.getTypeByName('Upload'),
90
- }, { pgFieldIntrospection: attr, isPgUploadField: true }),
91
- }, `Adding field for ${build.describePgEntity(attr)}. You can rename this field with a 'Smart Comment':\n\n ${build.sqlCommentByAddingTags(attr, {
92
- name: 'newNameHere',
93
- })}`);
94
- return memo;
95
- }, {}), `Adding columns to '${build.describePgEntity(table)}'`);
96
83
  });
97
- builder.hook('GraphQLObjectType:fields:field', (field, build, context) => {
98
- const { pgIntrospectionResultsByKind: introspectionResultsByKind, inflection, } = build;
99
- const { scope: { isRootMutation, fieldName, pgFieldIntrospection: table }, } = context;
100
- if (!isRootMutation || !table) {
101
- return field;
84
+ source.pipe(limiter);
85
+ return limiter;
86
+ }
87
+ function isThenable(value) {
88
+ return (value !== null &&
89
+ (typeof value === 'object' || typeof value === 'function') &&
90
+ typeof value.then === 'function');
91
+ }
92
+ function wrapUploadForMaxFileSize(upload, maxFileSize) {
93
+ if (!maxFileSize) {
94
+ return upload;
95
+ }
96
+ return {
97
+ ...upload,
98
+ createReadStream() {
99
+ const source = upload.createReadStream();
100
+ return createSizeLimitStream(source, maxFileSize);
102
101
  }
103
- // It's possible that `resolve` isn't specified on a field, so in that case
104
- // we fall back to a default resolver.
105
- const defaultResolver = (obj) => obj[fieldName];
106
- // Extract the old resolver from `field`
107
- const { resolve: oldResolve = defaultResolver, ...rest } = field; // GraphQLFieldConfig
108
- const tags = {};
109
- const types = {};
110
- const originals = {};
111
- const uploadResolversByFieldName = introspectionResultsByKind.attribute
112
- .filter((attr) => attr.classId === table.id)
113
- .reduce((memo, attr) => {
114
- // first, try to directly match the types here
115
- const typeMatched = relevantUploadType(attr);
116
- if (typeMatched) {
117
- const fieldName = inflection.column(attr);
118
- const uploadFieldName = inflection.uploadColumn(attr);
119
- memo[uploadFieldName] = typeMatched.resolve;
120
- tags[uploadFieldName] = attr.tags;
121
- types[uploadFieldName] = attr.type.name;
122
- originals[uploadFieldName] = fieldName;
123
- }
124
- return memo;
125
- }, {});
126
- return {
127
- // Copy over everything except 'resolve'
128
- ...rest,
129
- // Add our new resolver which wraps the old resolver
130
- async resolve(source, args, context, info) {
131
- // Recursively check for Upload promises to resolve
132
- async function resolvePromises(obj) {
133
- for (const key of Object.keys(obj)) {
134
- if (obj[key] instanceof Promise) {
135
- if (uploadResolversByFieldName[key]) {
136
- const upload = await obj[key];
137
- // eslint-disable-next-line require-atomic-updates
138
- obj[originals[key]] = await uploadResolversByFieldName[key](upload, args, context, {
139
- ...info,
140
- uploadPlugin: { tags: tags[key], type: types[key] },
141
- });
102
+ };
103
+ }
104
+ /**
105
+ * Creates the Upload plugin with the given options.
106
+ */
107
+ export function createUploadPlugin(options = {}) {
108
+ const { uploadFieldDefinitions = [], maxFileSize } = options;
109
+ return {
110
+ name: 'UploadPlugin',
111
+ version: '2.0.0',
112
+ description: 'File upload support for PostGraphile v5',
113
+ after: ['PgAttributesPlugin', 'PgMutationCreatePlugin', 'PgMutationUpdateDeletePlugin'],
114
+ schema: {
115
+ hooks: {
116
+ // Register the Upload scalar type
117
+ init(_, build) {
118
+ const { graphql: { GraphQLScalarType, GraphQLError } } = build;
119
+ const GraphQLUpload = new GraphQLScalarType({
120
+ name: 'Upload',
121
+ description: 'The `Upload` scalar type represents a file upload.',
122
+ parseValue(value) {
123
+ const maybe = value;
124
+ if (maybe &&
125
+ maybe.promise &&
126
+ typeof maybe.promise.then === 'function') {
127
+ return maybe.promise;
142
128
  }
129
+ throw new GraphQLError('Upload value invalid.');
130
+ },
131
+ parseLiteral(_ast) {
132
+ throw new GraphQLError('Upload literal unsupported.');
133
+ },
134
+ serialize() {
135
+ throw new GraphQLError('Upload serialization unsupported.');
143
136
  }
144
- else if (obj[key] !== null && typeof obj[key] === 'object') {
145
- await resolvePromises(obj[key]);
137
+ });
138
+ build.registerScalarType(GraphQLUpload.name, {}, () => GraphQLUpload, 'UploadPlugin registering Upload scalar');
139
+ return _;
140
+ },
141
+ // Add *Upload input fields alongside matching columns on mutation input types
142
+ GraphQLInputObjectType_fields(fields, build, context) {
143
+ const { scope: { pgCodec, isPgPatch, isPgBaseInput, isMutationInput } } = context;
144
+ // Only process row-based input types (create inputs, patch inputs, base inputs)
145
+ if (!pgCodec || !pgCodec.attributes) {
146
+ return fields;
147
+ }
148
+ // Must be a mutation-related input type
149
+ if (!isPgPatch && !isPgBaseInput && !isMutationInput) {
150
+ return fields;
151
+ }
152
+ const UploadType = build.getTypeByName('Upload');
153
+ if (!UploadType) {
154
+ return fields;
155
+ }
156
+ let newFields = fields;
157
+ for (const [attributeName, attribute] of Object.entries(pgCodec.attributes)) {
158
+ const matchedDef = relevantUploadType(attribute, uploadFieldDefinitions);
159
+ if (!matchedDef)
160
+ continue;
161
+ // Generate the upload field name: columnName + 'Upload'
162
+ const baseFieldName = build.inflection.attribute({
163
+ codec: pgCodec,
164
+ attributeName
165
+ });
166
+ const uploadFieldName = baseFieldName + 'Upload';
167
+ if (newFields[uploadFieldName]) {
168
+ throw new Error(`Two columns produce the same upload field name '${uploadFieldName}' ` +
169
+ `on codec '${pgCodec.name}'; one of them is '${attributeName}'`);
146
170
  }
171
+ newFields = build.extend(newFields, {
172
+ [uploadFieldName]: context.fieldWithHooks({ fieldName: uploadFieldName, isPgUploadField: true }, {
173
+ description: attribute.description
174
+ ? `Upload for ${attribute.description}`
175
+ : `File upload for the \`${baseFieldName}\` field.`,
176
+ type: UploadType
177
+ })
178
+ }, `UploadPlugin adding upload field '${uploadFieldName}' for ` +
179
+ `attribute '${attributeName}' on '${pgCodec.name}'`);
180
+ }
181
+ return newFields;
182
+ },
183
+ // Wrap mutation field resolvers to process file uploads
184
+ GraphQLObjectType_fields_field(field, build, context) {
185
+ const { scope: { isRootMutation, fieldName, pgCodec } } = context;
186
+ if (!isRootMutation || !pgCodec || !pgCodec.attributes) {
187
+ return field;
147
188
  }
189
+ // Build the mapping of upload field names to their resolvers
190
+ const uploadResolversByFieldName = {};
191
+ const tags = {};
192
+ const types = {};
193
+ const originals = {};
194
+ for (const [attributeName, attribute] of Object.entries(pgCodec.attributes)) {
195
+ const matchedDef = relevantUploadType(attribute, uploadFieldDefinitions);
196
+ if (!matchedDef)
197
+ continue;
198
+ const baseFieldName = build.inflection.attribute({
199
+ codec: pgCodec,
200
+ attributeName
201
+ });
202
+ const uploadFieldName = baseFieldName + 'Upload';
203
+ uploadResolversByFieldName[uploadFieldName] = matchedDef.resolve;
204
+ tags[uploadFieldName] = attribute.extensions?.tags || {};
205
+ types[uploadFieldName] = matchedDef.type || '';
206
+ originals[uploadFieldName] = baseFieldName;
207
+ }
208
+ // If no upload fields match this mutation's codec, skip wrapping
209
+ if (Object.keys(uploadResolversByFieldName).length === 0) {
210
+ return field;
211
+ }
212
+ const defaultResolver = (obj) => obj[fieldName];
213
+ const { resolve: oldResolve = defaultResolver, ...rest } = field;
214
+ return {
215
+ ...rest,
216
+ async resolve(source, args, context, info) {
217
+ // Recursively walk args to find and resolve upload thenables.
218
+ async function resolvePromises(obj) {
219
+ const pending = [];
220
+ for (const key of Object.keys(obj)) {
221
+ const value = obj[key];
222
+ if (isThenable(value)) {
223
+ const resolver = uploadResolversByFieldName[key];
224
+ if (!resolver)
225
+ continue;
226
+ pending.push((async () => {
227
+ const upload = await value;
228
+ const uploadForResolver = wrapUploadForMaxFileSize(upload, maxFileSize);
229
+ obj[originals[key]] = await resolver(uploadForResolver, args, context, {
230
+ ...info,
231
+ uploadPlugin: {
232
+ tags: tags[key],
233
+ type: types[key]
234
+ }
235
+ });
236
+ // Remove consumed `*Upload` key so downstream resolvers only
237
+ // see the materialized column value.
238
+ delete obj[key];
239
+ })());
240
+ continue;
241
+ }
242
+ if (value !== null && typeof value === 'object') {
243
+ pending.push(resolvePromises(value));
244
+ }
245
+ }
246
+ if (pending.length > 0) {
247
+ await Promise.all(pending);
248
+ }
249
+ }
250
+ await resolvePromises(args);
251
+ return oldResolve(source, args, context, info);
252
+ }
253
+ };
148
254
  }
149
- await resolvePromises(args);
150
- // Call the old resolver
151
- const oldResolveResult = await oldResolve(source, args, context, info);
152
- // Finally return the result.
153
- return oldResolveResult;
154
- },
155
- };
156
- });
157
- };
158
- export default UploadPostGraphilePlugin;
255
+ }
256
+ }
257
+ };
258
+ }
259
+ export const UploadPlugin = createUploadPlugin;
260
+ export default UploadPlugin;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * PostGraphile v5 Upload Preset
3
+ *
4
+ * Provides a convenient preset for including upload support in PostGraphile.
5
+ */
6
+ import type { GraphileConfig } from 'graphile-config';
7
+ import type { UploadPluginOptions } from './types';
8
+ /**
9
+ * Creates a preset that includes the upload plugin with the given options.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { UploadPreset } from 'graphile-upload-plugin';
14
+ *
15
+ * const preset = {
16
+ * extends: [
17
+ * UploadPreset({
18
+ * uploadFieldDefinitions: [
19
+ * {
20
+ * tag: 'upload',
21
+ * resolve: async (upload, args, context, info) => {
22
+ * // Process the upload and return the value to store in the column
23
+ * const stream = upload.createReadStream();
24
+ * const url = await uploadToStorage(stream, upload.filename);
25
+ * return url;
26
+ * },
27
+ * },
28
+ * ],
29
+ * }),
30
+ * ],
31
+ * };
32
+ * ```
33
+ */
34
+ export declare function UploadPreset(options?: UploadPluginOptions): GraphileConfig.Preset;
35
+ export default UploadPreset;