graphile-settings 2.3.1 → 2.5.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.js +13 -14
- package/esm/plugins/upload-postgraphile-plugin.js +158 -0
- package/index.js +12 -13
- package/package.json +9 -8
- package/plugins/upload-postgraphile-plugin.d.ts +31 -0
- package/plugins/upload-postgraphile-plugin.js +160 -0
package/esm/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import PgPostgis from '@pyramation/postgis';
|
|
|
4
4
|
// @ts-ignore
|
|
5
5
|
import FulltextFilterPlugin from '@pyramation/postgraphile-plugin-fulltext-filter';
|
|
6
6
|
import { NodePlugin } from 'graphile-build';
|
|
7
|
-
import { additionalGraphQLContextFromRequest as langAdditional, LangPlugin
|
|
7
|
+
import { additionalGraphQLContextFromRequest as langAdditional, LangPlugin,
|
|
8
8
|
// @ts-ignore
|
|
9
9
|
} from 'graphile-i18n';
|
|
10
10
|
// @ts-ignore
|
|
@@ -13,12 +13,11 @@ import PgMetaschema from 'graphile-meta-schema';
|
|
|
13
13
|
import PgSearch from 'graphile-search-plugin';
|
|
14
14
|
// @ts-ignore
|
|
15
15
|
import PgSimpleInflector from 'graphile-simple-inflector';
|
|
16
|
-
// @ts-ignore
|
|
17
|
-
import PostGraphileUploadFieldPlugin from 'postgraphile-derived-upload-field';
|
|
18
16
|
import ConnectionFilterPlugin from 'postgraphile-plugin-connection-filter';
|
|
19
17
|
// @ts-ignore
|
|
20
18
|
import PgPostgisFilter from 'postgraphile-plugin-connection-filter-postgis';
|
|
21
19
|
import LqlTypesPlugin from './plugins/types';
|
|
20
|
+
import UploadPostGraphilePlugin from './plugins/upload-postgraphile-plugin';
|
|
22
21
|
import { Uploader } from './resolvers/upload';
|
|
23
22
|
export const getGraphileSettings = (rawOpts) => {
|
|
24
23
|
const opts = getEnvOptions(rawOpts);
|
|
@@ -29,17 +28,17 @@ export const getGraphileSettings = (rawOpts) => {
|
|
|
29
28
|
awsRegion: cdn.awsRegion,
|
|
30
29
|
awsAccessKey: cdn.awsAccessKey,
|
|
31
30
|
awsSecretKey: cdn.awsSecretKey,
|
|
32
|
-
minioEndpoint: cdn.minioEndpoint
|
|
31
|
+
minioEndpoint: cdn.minioEndpoint,
|
|
33
32
|
});
|
|
34
33
|
const resolveUpload = uploader.resolveUpload.bind(uploader);
|
|
35
34
|
const plugins = [
|
|
36
35
|
ConnectionFilterPlugin,
|
|
37
36
|
FulltextFilterPlugin,
|
|
38
37
|
LqlTypesPlugin,
|
|
39
|
-
|
|
38
|
+
UploadPostGraphilePlugin,
|
|
40
39
|
PgMetaschema,
|
|
41
40
|
PgManyToMany,
|
|
42
|
-
PgSearch
|
|
41
|
+
PgSearch,
|
|
43
42
|
];
|
|
44
43
|
if (features?.postgis) {
|
|
45
44
|
plugins.push(PgPostgis, PgPostgisFilter);
|
|
@@ -55,27 +54,27 @@ export const getGraphileSettings = (rawOpts) => {
|
|
|
55
54
|
name: 'upload',
|
|
56
55
|
namespaceName: 'public',
|
|
57
56
|
type: 'JSON',
|
|
58
|
-
resolve: resolveUpload
|
|
57
|
+
resolve: resolveUpload,
|
|
59
58
|
},
|
|
60
59
|
{
|
|
61
60
|
name: 'attachment',
|
|
62
61
|
namespaceName: 'public',
|
|
63
62
|
type: 'String',
|
|
64
|
-
resolve: resolveUpload
|
|
63
|
+
resolve: resolveUpload,
|
|
65
64
|
},
|
|
66
65
|
{
|
|
67
66
|
name: 'image',
|
|
68
67
|
namespaceName: 'public',
|
|
69
68
|
type: 'JSON',
|
|
70
|
-
resolve: resolveUpload
|
|
69
|
+
resolve: resolveUpload,
|
|
71
70
|
},
|
|
72
71
|
{
|
|
73
72
|
tag: 'upload',
|
|
74
|
-
resolve: resolveUpload
|
|
75
|
-
}
|
|
73
|
+
resolve: resolveUpload,
|
|
74
|
+
},
|
|
76
75
|
],
|
|
77
76
|
pgSimplifyOppositeBaseNames: features?.oppositeBaseNames,
|
|
78
|
-
connectionFilterComputedColumns: false
|
|
77
|
+
connectionFilterComputedColumns: false,
|
|
79
78
|
},
|
|
80
79
|
appendPlugins: plugins,
|
|
81
80
|
skipPlugins: [NodePlugin],
|
|
@@ -102,7 +101,7 @@ export const getGraphileSettings = (rawOpts) => {
|
|
|
102
101
|
additionalGraphQLContextFromRequest: (req, res) => ({
|
|
103
102
|
...langAdditional(req, res),
|
|
104
103
|
req,
|
|
105
|
-
res
|
|
106
|
-
})
|
|
104
|
+
res,
|
|
105
|
+
}),
|
|
107
106
|
};
|
|
108
107
|
};
|
|
@@ -0,0 +1,158 @@
|
|
|
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];
|
|
13
|
+
}
|
|
14
|
+
else if (types.length > 1) {
|
|
15
|
+
throw new Error('Upload field definitions are ambiguous');
|
|
16
|
+
}
|
|
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));
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
return input;
|
|
52
|
+
});
|
|
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
|
+
});
|
|
60
|
+
});
|
|
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;
|
|
66
|
+
}
|
|
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
|
+
});
|
|
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;
|
|
102
|
+
}
|
|
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
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else if (obj[key] !== null && typeof obj[key] === 'object') {
|
|
145
|
+
await resolvePromises(obj[key]);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
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;
|
package/index.js
CHANGED
|
@@ -17,12 +17,11 @@ const graphile_meta_schema_1 = __importDefault(require("graphile-meta-schema"));
|
|
|
17
17
|
const graphile_search_plugin_1 = __importDefault(require("graphile-search-plugin"));
|
|
18
18
|
// @ts-ignore
|
|
19
19
|
const graphile_simple_inflector_1 = __importDefault(require("graphile-simple-inflector"));
|
|
20
|
-
// @ts-ignore
|
|
21
|
-
const postgraphile_derived_upload_field_1 = __importDefault(require("postgraphile-derived-upload-field"));
|
|
22
20
|
const postgraphile_plugin_connection_filter_1 = __importDefault(require("postgraphile-plugin-connection-filter"));
|
|
23
21
|
// @ts-ignore
|
|
24
22
|
const postgraphile_plugin_connection_filter_postgis_1 = __importDefault(require("postgraphile-plugin-connection-filter-postgis"));
|
|
25
23
|
const types_1 = __importDefault(require("./plugins/types"));
|
|
24
|
+
const upload_postgraphile_plugin_1 = __importDefault(require("./plugins/upload-postgraphile-plugin"));
|
|
26
25
|
const upload_1 = require("./resolvers/upload");
|
|
27
26
|
const getGraphileSettings = (rawOpts) => {
|
|
28
27
|
const opts = (0, env_1.getEnvOptions)(rawOpts);
|
|
@@ -33,17 +32,17 @@ const getGraphileSettings = (rawOpts) => {
|
|
|
33
32
|
awsRegion: cdn.awsRegion,
|
|
34
33
|
awsAccessKey: cdn.awsAccessKey,
|
|
35
34
|
awsSecretKey: cdn.awsSecretKey,
|
|
36
|
-
minioEndpoint: cdn.minioEndpoint
|
|
35
|
+
minioEndpoint: cdn.minioEndpoint,
|
|
37
36
|
});
|
|
38
37
|
const resolveUpload = uploader.resolveUpload.bind(uploader);
|
|
39
38
|
const plugins = [
|
|
40
39
|
postgraphile_plugin_connection_filter_1.default,
|
|
41
40
|
postgraphile_plugin_fulltext_filter_1.default,
|
|
42
41
|
types_1.default,
|
|
43
|
-
|
|
42
|
+
upload_postgraphile_plugin_1.default,
|
|
44
43
|
graphile_meta_schema_1.default,
|
|
45
44
|
pg_many_to_many_1.default,
|
|
46
|
-
graphile_search_plugin_1.default
|
|
45
|
+
graphile_search_plugin_1.default,
|
|
47
46
|
];
|
|
48
47
|
if (features?.postgis) {
|
|
49
48
|
plugins.push(postgis_1.default, postgraphile_plugin_connection_filter_postgis_1.default);
|
|
@@ -59,27 +58,27 @@ const getGraphileSettings = (rawOpts) => {
|
|
|
59
58
|
name: 'upload',
|
|
60
59
|
namespaceName: 'public',
|
|
61
60
|
type: 'JSON',
|
|
62
|
-
resolve: resolveUpload
|
|
61
|
+
resolve: resolveUpload,
|
|
63
62
|
},
|
|
64
63
|
{
|
|
65
64
|
name: 'attachment',
|
|
66
65
|
namespaceName: 'public',
|
|
67
66
|
type: 'String',
|
|
68
|
-
resolve: resolveUpload
|
|
67
|
+
resolve: resolveUpload,
|
|
69
68
|
},
|
|
70
69
|
{
|
|
71
70
|
name: 'image',
|
|
72
71
|
namespaceName: 'public',
|
|
73
72
|
type: 'JSON',
|
|
74
|
-
resolve: resolveUpload
|
|
73
|
+
resolve: resolveUpload,
|
|
75
74
|
},
|
|
76
75
|
{
|
|
77
76
|
tag: 'upload',
|
|
78
|
-
resolve: resolveUpload
|
|
79
|
-
}
|
|
77
|
+
resolve: resolveUpload,
|
|
78
|
+
},
|
|
80
79
|
],
|
|
81
80
|
pgSimplifyOppositeBaseNames: features?.oppositeBaseNames,
|
|
82
|
-
connectionFilterComputedColumns: false
|
|
81
|
+
connectionFilterComputedColumns: false,
|
|
83
82
|
},
|
|
84
83
|
appendPlugins: plugins,
|
|
85
84
|
skipPlugins: [graphile_build_1.NodePlugin],
|
|
@@ -106,8 +105,8 @@ const getGraphileSettings = (rawOpts) => {
|
|
|
106
105
|
additionalGraphQLContextFromRequest: (req, res) => ({
|
|
107
106
|
...(0, graphile_i18n_1.additionalGraphQLContextFromRequest)(req, res),
|
|
108
107
|
req,
|
|
109
|
-
res
|
|
110
|
-
})
|
|
108
|
+
res,
|
|
109
|
+
}),
|
|
111
110
|
};
|
|
112
111
|
};
|
|
113
112
|
exports.getGraphileSettings = getGraphileSettings;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "graphile-settings",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"author": "Dan Lynch <pyramation@gmail.com>",
|
|
5
5
|
"description": "graphile settings",
|
|
6
6
|
"main": "index.js",
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@graphile-contrib/pg-many-to-many": "^1.0.2",
|
|
34
|
-
"@launchql/env": "^2.
|
|
35
|
-
"@launchql/s3-streamer": "^2.
|
|
36
|
-
"@launchql/types": "^2.
|
|
34
|
+
"@launchql/env": "^2.4.0",
|
|
35
|
+
"@launchql/s3-streamer": "^2.5.0",
|
|
36
|
+
"@launchql/types": "^2.5.0",
|
|
37
37
|
"@launchql/upload-names": "^2.2.0",
|
|
38
38
|
"@pyramation/postgis": "^0.1.1",
|
|
39
39
|
"@pyramation/postgraphile-plugin-fulltext-filter": "^2.0.0",
|
|
@@ -42,11 +42,12 @@
|
|
|
42
42
|
"graphile-build": "^4.14.1",
|
|
43
43
|
"graphile-i18n": "^0.0.3",
|
|
44
44
|
"graphile-meta-schema": "^0.2.5",
|
|
45
|
-
"graphile-query": "^2.
|
|
45
|
+
"graphile-query": "^2.3.0",
|
|
46
46
|
"graphile-search-plugin": "^0.1.2",
|
|
47
|
-
"graphile-settings": "^2.
|
|
47
|
+
"graphile-settings": "^2.5.0",
|
|
48
48
|
"graphile-simple-inflector": "^0.1.1",
|
|
49
49
|
"graphql-tag": "2.12.6",
|
|
50
|
+
"graphql-upload": "^13.0.0",
|
|
50
51
|
"lru-cache": "^11.1.0",
|
|
51
52
|
"pg": "^8.16.0",
|
|
52
53
|
"postgraphile": "^4.14.1",
|
|
@@ -65,7 +66,7 @@
|
|
|
65
66
|
"ts-node": "^10.9.2"
|
|
66
67
|
},
|
|
67
68
|
"resolutions": {
|
|
68
|
-
"graphql": "15.
|
|
69
|
+
"graphql": "15.10.1"
|
|
69
70
|
},
|
|
70
71
|
"keywords": [
|
|
71
72
|
"graphile",
|
|
@@ -74,5 +75,5 @@
|
|
|
74
75
|
"launchql",
|
|
75
76
|
"graphql"
|
|
76
77
|
],
|
|
77
|
-
"gitHead": "
|
|
78
|
+
"gitHead": "f7d2375e14a9f45ebb00d58ab87c0efd6bd50618"
|
|
78
79
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ReadStream } from 'fs';
|
|
2
|
+
import type { Plugin } from 'graphile-build';
|
|
3
|
+
import type { GraphQLResolveInfo } from 'graphql';
|
|
4
|
+
export interface FileUpload {
|
|
5
|
+
filename: string;
|
|
6
|
+
mimetype?: string;
|
|
7
|
+
encoding?: string;
|
|
8
|
+
createReadStream: () => ReadStream;
|
|
9
|
+
}
|
|
10
|
+
export interface UploadPluginInfo {
|
|
11
|
+
tags: Record<string, any>;
|
|
12
|
+
type?: string;
|
|
13
|
+
}
|
|
14
|
+
export type UploadResolver = (upload: FileUpload, args: any, context: any, info: GraphQLResolveInfo & {
|
|
15
|
+
uploadPlugin: UploadPluginInfo;
|
|
16
|
+
}) => Promise<any>;
|
|
17
|
+
export type UploadFieldDefinition = {
|
|
18
|
+
name: string;
|
|
19
|
+
namespaceName: string;
|
|
20
|
+
type: string;
|
|
21
|
+
resolve: UploadResolver;
|
|
22
|
+
tag?: never;
|
|
23
|
+
} | {
|
|
24
|
+
tag: string;
|
|
25
|
+
resolve: UploadResolver;
|
|
26
|
+
name?: never;
|
|
27
|
+
namespaceName?: never;
|
|
28
|
+
type?: string;
|
|
29
|
+
};
|
|
30
|
+
declare const UploadPostGraphilePlugin: Plugin;
|
|
31
|
+
export default UploadPostGraphilePlugin;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
// PostGraphile plugin
|
|
4
|
+
const UploadPostGraphilePlugin = (builder, opts = {}) => {
|
|
5
|
+
const { uploadFieldDefinitions = [] } = opts;
|
|
6
|
+
// Determine whether a table attribute should be treated as an Upload according to configuration
|
|
7
|
+
const relevantUploadType = (attr) => {
|
|
8
|
+
const types = uploadFieldDefinitions.filter(({ name, namespaceName, tag }) => (name &&
|
|
9
|
+
namespaceName &&
|
|
10
|
+
attr.type?.name === name &&
|
|
11
|
+
attr.type?.namespaceName === namespaceName) ||
|
|
12
|
+
(tag && attr.tags?.[tag]));
|
|
13
|
+
if (types.length === 1) {
|
|
14
|
+
return types[0];
|
|
15
|
+
}
|
|
16
|
+
else if (types.length > 1) {
|
|
17
|
+
throw new Error('Upload field definitions are ambiguous');
|
|
18
|
+
}
|
|
19
|
+
return undefined;
|
|
20
|
+
};
|
|
21
|
+
builder.hook('build', (input, build) => {
|
|
22
|
+
const { addType, graphql: { GraphQLScalarType, GraphQLError }, } = build;
|
|
23
|
+
const GraphQLUpload = new GraphQLScalarType({
|
|
24
|
+
name: 'Upload',
|
|
25
|
+
description: 'The `Upload` scalar type represents a file upload.',
|
|
26
|
+
parseValue(value) {
|
|
27
|
+
// The value should be an object with a `.promise` that resolves to the file upload
|
|
28
|
+
const maybe = value;
|
|
29
|
+
if (maybe &&
|
|
30
|
+
maybe.promise &&
|
|
31
|
+
typeof maybe.promise.then === 'function') {
|
|
32
|
+
return maybe.promise;
|
|
33
|
+
}
|
|
34
|
+
throw new GraphQLError('Upload value invalid.');
|
|
35
|
+
},
|
|
36
|
+
parseLiteral(ast) {
|
|
37
|
+
throw new GraphQLError('Upload literal unsupported.', ast);
|
|
38
|
+
},
|
|
39
|
+
serialize() {
|
|
40
|
+
throw new GraphQLError('Upload serialization unsupported.');
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
addType(GraphQLUpload);
|
|
44
|
+
// Override the internal types for configured upload-backed columns
|
|
45
|
+
uploadFieldDefinitions.forEach(({ name, namespaceName, type }) => {
|
|
46
|
+
if (!name || !type || !namespaceName)
|
|
47
|
+
return; // tag-based or incomplete definitions
|
|
48
|
+
const theType = build.pgIntrospectionResultsByKind.type.find((typ) => typ.name === name && typ.namespaceName === namespaceName);
|
|
49
|
+
if (theType) {
|
|
50
|
+
build.pgRegisterGqlTypeByTypeId(theType.id, () => build.getTypeByName(type));
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
return input;
|
|
54
|
+
});
|
|
55
|
+
builder.hook('inflection', (inflection, build) => {
|
|
56
|
+
return build.extend(inflection, {
|
|
57
|
+
// NO ARROW FUNCTIONS HERE (this)
|
|
58
|
+
uploadColumn(attr) {
|
|
59
|
+
return this.column(attr) + 'Upload';
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
// Add Upload input fields alongside matching columns
|
|
64
|
+
builder.hook('GraphQLInputObjectType:fields', (fields, build, context) => {
|
|
65
|
+
const { scope: { isPgRowType, pgIntrospection: table }, } = context;
|
|
66
|
+
if (!isPgRowType || !table || table.kind !== 'class') {
|
|
67
|
+
return fields;
|
|
68
|
+
}
|
|
69
|
+
return build.extend(fields, table.attributes.reduce((memo, attr) => {
|
|
70
|
+
if (!build.pgColumnFilter(attr, build, context))
|
|
71
|
+
return memo;
|
|
72
|
+
const action = context.scope.isPgBaseInput
|
|
73
|
+
? 'base'
|
|
74
|
+
: context.scope.isPgPatch
|
|
75
|
+
? 'update'
|
|
76
|
+
: 'create';
|
|
77
|
+
if (build.pgOmit(attr, action))
|
|
78
|
+
return memo;
|
|
79
|
+
if (attr.identity === 'a')
|
|
80
|
+
return memo;
|
|
81
|
+
if (!relevantUploadType(attr)) {
|
|
82
|
+
return memo;
|
|
83
|
+
}
|
|
84
|
+
const fieldName = build.inflection.uploadColumn(attr);
|
|
85
|
+
if (memo[fieldName]) {
|
|
86
|
+
throw new Error(`Two columns produce the same GraphQL field name '${fieldName}' on class '${table.namespaceName}.${table.name}'; one of them is '${attr.name}'`);
|
|
87
|
+
}
|
|
88
|
+
memo = build.extend(memo, {
|
|
89
|
+
[fieldName]: context.fieldWithHooks(fieldName, {
|
|
90
|
+
description: attr.description,
|
|
91
|
+
type: build.getTypeByName('Upload'),
|
|
92
|
+
}, { pgFieldIntrospection: attr, isPgUploadField: true }),
|
|
93
|
+
}, `Adding field for ${build.describePgEntity(attr)}. You can rename this field with a 'Smart Comment':\n\n ${build.sqlCommentByAddingTags(attr, {
|
|
94
|
+
name: 'newNameHere',
|
|
95
|
+
})}`);
|
|
96
|
+
return memo;
|
|
97
|
+
}, {}), `Adding columns to '${build.describePgEntity(table)}'`);
|
|
98
|
+
});
|
|
99
|
+
builder.hook('GraphQLObjectType:fields:field', (field, build, context) => {
|
|
100
|
+
const { pgIntrospectionResultsByKind: introspectionResultsByKind, inflection, } = build;
|
|
101
|
+
const { scope: { isRootMutation, fieldName, pgFieldIntrospection: table }, } = context;
|
|
102
|
+
if (!isRootMutation || !table) {
|
|
103
|
+
return field;
|
|
104
|
+
}
|
|
105
|
+
// It's possible that `resolve` isn't specified on a field, so in that case
|
|
106
|
+
// we fall back to a default resolver.
|
|
107
|
+
const defaultResolver = (obj) => obj[fieldName];
|
|
108
|
+
// Extract the old resolver from `field`
|
|
109
|
+
const { resolve: oldResolve = defaultResolver, ...rest } = field; // GraphQLFieldConfig
|
|
110
|
+
const tags = {};
|
|
111
|
+
const types = {};
|
|
112
|
+
const originals = {};
|
|
113
|
+
const uploadResolversByFieldName = introspectionResultsByKind.attribute
|
|
114
|
+
.filter((attr) => attr.classId === table.id)
|
|
115
|
+
.reduce((memo, attr) => {
|
|
116
|
+
// first, try to directly match the types here
|
|
117
|
+
const typeMatched = relevantUploadType(attr);
|
|
118
|
+
if (typeMatched) {
|
|
119
|
+
const fieldName = inflection.column(attr);
|
|
120
|
+
const uploadFieldName = inflection.uploadColumn(attr);
|
|
121
|
+
memo[uploadFieldName] = typeMatched.resolve;
|
|
122
|
+
tags[uploadFieldName] = attr.tags;
|
|
123
|
+
types[uploadFieldName] = attr.type.name;
|
|
124
|
+
originals[uploadFieldName] = fieldName;
|
|
125
|
+
}
|
|
126
|
+
return memo;
|
|
127
|
+
}, {});
|
|
128
|
+
return {
|
|
129
|
+
// Copy over everything except 'resolve'
|
|
130
|
+
...rest,
|
|
131
|
+
// Add our new resolver which wraps the old resolver
|
|
132
|
+
async resolve(source, args, context, info) {
|
|
133
|
+
// Recursively check for Upload promises to resolve
|
|
134
|
+
async function resolvePromises(obj) {
|
|
135
|
+
for (const key of Object.keys(obj)) {
|
|
136
|
+
if (obj[key] instanceof Promise) {
|
|
137
|
+
if (uploadResolversByFieldName[key]) {
|
|
138
|
+
const upload = await obj[key];
|
|
139
|
+
// eslint-disable-next-line require-atomic-updates
|
|
140
|
+
obj[originals[key]] = await uploadResolversByFieldName[key](upload, args, context, {
|
|
141
|
+
...info,
|
|
142
|
+
uploadPlugin: { tags: tags[key], type: types[key] },
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
else if (obj[key] !== null && typeof obj[key] === 'object') {
|
|
147
|
+
await resolvePromises(obj[key]);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
await resolvePromises(args);
|
|
152
|
+
// Call the old resolver
|
|
153
|
+
const oldResolveResult = await oldResolve(source, args, context, info);
|
|
154
|
+
// Finally return the result.
|
|
155
|
+
return oldResolveResult;
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
});
|
|
159
|
+
};
|
|
160
|
+
exports.default = UploadPostGraphilePlugin;
|