@bobbykim/manguito-cms-api 0.2.0 → 0.3.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/dist/chunk-FBS2RQI3.mjs +232 -0
- package/dist/chunk-FBS2RQI3.mjs.map +1 -0
- package/dist/chunk-QXMW74MR.mjs +589 -0
- package/dist/chunk-QXMW74MR.mjs.map +1 -0
- package/dist/graphql/index.d.mts +15 -0
- package/dist/graphql/index.d.ts +15 -0
- package/dist/graphql/index.js +729 -0
- package/dist/graphql/index.js.map +1 -0
- package/dist/graphql/index.mjs +8 -0
- package/dist/graphql/index.mjs.map +1 -0
- package/dist/handler-GMCFYZY5.mjs +8 -0
- package/dist/handler-GMCFYZY5.mjs.map +1 -0
- package/dist/index.d.mts +7 -14
- package/dist/index.d.ts +7 -14
- package/dist/index.js +959 -253
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +184 -330
- package/dist/index.mjs.map +1 -1
- package/dist/resolve-Dtsk27aI.d.mts +15 -0
- package/dist/resolve-Dtsk27aI.d.ts +15 -0
- package/package.json +12 -3
|
@@ -0,0 +1,589 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildRelationsMap,
|
|
3
|
+
resolveRelationField
|
|
4
|
+
} from "./chunk-FBS2RQI3.mjs";
|
|
5
|
+
|
|
6
|
+
// src/graphql/handler.ts
|
|
7
|
+
import { createYoga } from "graphql-yoga";
|
|
8
|
+
|
|
9
|
+
// src/graphql/schema.ts
|
|
10
|
+
import {
|
|
11
|
+
GraphQLSchema,
|
|
12
|
+
GraphQLObjectType,
|
|
13
|
+
GraphQLEnumType as GraphQLEnumType2,
|
|
14
|
+
GraphQLNonNull as GraphQLNonNull2,
|
|
15
|
+
GraphQLList as GraphQLList2,
|
|
16
|
+
GraphQLID,
|
|
17
|
+
GraphQLString as GraphQLString3,
|
|
18
|
+
GraphQLInt as GraphQLInt3,
|
|
19
|
+
GraphQLBoolean as GraphQLBoolean3
|
|
20
|
+
} from "graphql";
|
|
21
|
+
|
|
22
|
+
// src/graphql/scalars.ts
|
|
23
|
+
import { GraphQLScalarType, Kind, valueFromASTUntyped } from "graphql";
|
|
24
|
+
var DateTimeScalar = new GraphQLScalarType({
|
|
25
|
+
name: "DateTime",
|
|
26
|
+
description: "ISO-8601 date-time string",
|
|
27
|
+
serialize(value) {
|
|
28
|
+
if (value instanceof Date) return value.toISOString();
|
|
29
|
+
if (typeof value === "string") return value;
|
|
30
|
+
if (typeof value === "number") return new Date(value).toISOString();
|
|
31
|
+
throw new TypeError(`DateTime cannot serialize value: ${String(value)}`);
|
|
32
|
+
},
|
|
33
|
+
parseValue(value) {
|
|
34
|
+
if (typeof value !== "string") throw new TypeError("DateTime must be a string");
|
|
35
|
+
return value;
|
|
36
|
+
},
|
|
37
|
+
parseLiteral(ast) {
|
|
38
|
+
if (ast.kind !== Kind.STRING) throw new TypeError("DateTime must be a string");
|
|
39
|
+
return ast.value;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
var JSONScalar = new GraphQLScalarType({
|
|
43
|
+
name: "JSON",
|
|
44
|
+
description: "Arbitrary JSON value",
|
|
45
|
+
serialize: (value) => value,
|
|
46
|
+
parseValue: (value) => value,
|
|
47
|
+
parseLiteral: (ast, variables) => valueFromASTUntyped(ast, variables ?? void 0)
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// src/graphql/type-mapping.ts
|
|
51
|
+
import {
|
|
52
|
+
GraphQLString,
|
|
53
|
+
GraphQLInt,
|
|
54
|
+
GraphQLFloat,
|
|
55
|
+
GraphQLBoolean
|
|
56
|
+
} from "graphql";
|
|
57
|
+
function scalarOutputType(fieldType) {
|
|
58
|
+
switch (fieldType) {
|
|
59
|
+
case "text/plain":
|
|
60
|
+
case "text/rich":
|
|
61
|
+
return GraphQLString;
|
|
62
|
+
case "integer":
|
|
63
|
+
return GraphQLInt;
|
|
64
|
+
case "float":
|
|
65
|
+
return GraphQLFloat;
|
|
66
|
+
case "boolean":
|
|
67
|
+
return GraphQLBoolean;
|
|
68
|
+
case "date":
|
|
69
|
+
return DateTimeScalar;
|
|
70
|
+
case "programmatic":
|
|
71
|
+
return JSONScalar;
|
|
72
|
+
default:
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/graphql/naming.ts
|
|
78
|
+
function schemaSegment(machineName) {
|
|
79
|
+
const idx = machineName.indexOf("--");
|
|
80
|
+
return idx === -1 ? machineName : machineName.slice(idx + 2);
|
|
81
|
+
}
|
|
82
|
+
function words(input) {
|
|
83
|
+
return input.split(/[_\s-]+/).filter(Boolean);
|
|
84
|
+
}
|
|
85
|
+
function toPascalCase(input) {
|
|
86
|
+
return words(input).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
87
|
+
}
|
|
88
|
+
function toCamelCase(input) {
|
|
89
|
+
const pascal = toPascalCase(input);
|
|
90
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
91
|
+
}
|
|
92
|
+
function pluralize(word) {
|
|
93
|
+
if (/[^aeiou]y$/.test(word)) return word.slice(0, -1) + "ies";
|
|
94
|
+
if (/(s|x|z|ch|sh)$/.test(word)) return word + "es";
|
|
95
|
+
return word + "s";
|
|
96
|
+
}
|
|
97
|
+
var GRAPHQL_NAME = /^[_A-Za-z][_0-9A-Za-z]*$/;
|
|
98
|
+
function isValidGraphQLName(name) {
|
|
99
|
+
return GRAPHQL_NAME.test(name);
|
|
100
|
+
}
|
|
101
|
+
function graphqlTypeName(machineName) {
|
|
102
|
+
return toPascalCase(schemaSegment(machineName));
|
|
103
|
+
}
|
|
104
|
+
function singleQueryName(machineName) {
|
|
105
|
+
return toCamelCase(schemaSegment(machineName));
|
|
106
|
+
}
|
|
107
|
+
function collectionQueryName(machineName) {
|
|
108
|
+
return pluralize(singleQueryName(machineName));
|
|
109
|
+
}
|
|
110
|
+
function buildFieldNameMap(schemaNames) {
|
|
111
|
+
const toG = /* @__PURE__ */ new Map();
|
|
112
|
+
const toS = /* @__PURE__ */ new Map();
|
|
113
|
+
for (const name of schemaNames) {
|
|
114
|
+
const g = toCamelCase(name);
|
|
115
|
+
toG.set(name, g);
|
|
116
|
+
toS.set(g, name);
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
toGraphql: (schemaName) => toG.get(schemaName) ?? toCamelCase(schemaName),
|
|
120
|
+
toSchema: (graphqlName) => toS.get(graphqlName) ?? graphqlName
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/graphql/filters.ts
|
|
125
|
+
import {
|
|
126
|
+
GraphQLEnumType,
|
|
127
|
+
GraphQLInputObjectType,
|
|
128
|
+
GraphQLList,
|
|
129
|
+
GraphQLNonNull,
|
|
130
|
+
GraphQLString as GraphQLString2,
|
|
131
|
+
GraphQLBoolean as GraphQLBoolean2,
|
|
132
|
+
GraphQLInt as GraphQLInt2,
|
|
133
|
+
GraphQLFloat as GraphQLFloat2
|
|
134
|
+
} from "graphql";
|
|
135
|
+
var SortOrderEnum = new GraphQLEnumType({
|
|
136
|
+
name: "SortOrder",
|
|
137
|
+
values: { ASC: { value: "asc" }, DESC: { value: "desc" } }
|
|
138
|
+
});
|
|
139
|
+
var SORTABLE = [
|
|
140
|
+
{ gql: "title", column: "title" },
|
|
141
|
+
{ gql: "createdAt", column: "created_at" },
|
|
142
|
+
{ gql: "updatedAt", column: "updated_at" }
|
|
143
|
+
];
|
|
144
|
+
function buildSortFieldEnum(typeName) {
|
|
145
|
+
return new GraphQLEnumType({
|
|
146
|
+
name: `${typeName}SortField`,
|
|
147
|
+
values: Object.fromEntries(SORTABLE.map((s) => [s.gql, { value: s.column }]))
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
function comparableFilterInput(name, scalar) {
|
|
151
|
+
return new GraphQLInputObjectType({
|
|
152
|
+
name,
|
|
153
|
+
fields: {
|
|
154
|
+
eq: { type: scalar },
|
|
155
|
+
in: { type: new GraphQLList(new GraphQLNonNull(scalar)) },
|
|
156
|
+
gt: { type: scalar },
|
|
157
|
+
gte: { type: scalar },
|
|
158
|
+
lt: { type: scalar },
|
|
159
|
+
lte: { type: scalar }
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
function equalityFilterInput(name, scalar) {
|
|
164
|
+
return new GraphQLInputObjectType({
|
|
165
|
+
name,
|
|
166
|
+
fields: {
|
|
167
|
+
eq: { type: scalar },
|
|
168
|
+
in: { type: new GraphQLList(new GraphQLNonNull(scalar)) }
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
var StringFilter = equalityFilterInput("StringFilter", GraphQLString2);
|
|
173
|
+
var BooleanFilter = new GraphQLInputObjectType({
|
|
174
|
+
name: "BooleanFilter",
|
|
175
|
+
fields: { eq: { type: GraphQLBoolean2 } }
|
|
176
|
+
});
|
|
177
|
+
var DateTimeFilter = comparableFilterInput("DateTimeFilter", DateTimeScalar);
|
|
178
|
+
var IDFilter = equalityFilterInput("IDFilter", GraphQLString2);
|
|
179
|
+
var IntFilter = comparableFilterInput("IntFilter", GraphQLInt2);
|
|
180
|
+
var FloatFilter = comparableFilterInput("FloatFilter", GraphQLFloat2);
|
|
181
|
+
function filterInputForField(fieldType) {
|
|
182
|
+
const scalar = scalarOutputType(fieldType);
|
|
183
|
+
if (fieldType === "boolean") return BooleanFilter;
|
|
184
|
+
if (fieldType === "date") return DateTimeFilter;
|
|
185
|
+
if (fieldType === "integer") return IntFilter;
|
|
186
|
+
if (fieldType === "float") return FloatFilter;
|
|
187
|
+
if (fieldType === "reference") return IDFilter;
|
|
188
|
+
if (scalar === null) return null;
|
|
189
|
+
return StringFilter;
|
|
190
|
+
}
|
|
191
|
+
function buildFilterInputType(type) {
|
|
192
|
+
const fields = {};
|
|
193
|
+
fields["id"] = { type: IDFilter };
|
|
194
|
+
fields["published"] = { type: BooleanFilter };
|
|
195
|
+
fields["createdAt"] = { type: DateTimeFilter };
|
|
196
|
+
fields["updatedAt"] = { type: DateTimeFilter };
|
|
197
|
+
if (type.schema_type === "content-type") fields["slug"] = { type: StringFilter };
|
|
198
|
+
for (const f of type.fields) {
|
|
199
|
+
if (f.field_type === "programmatic" || f.field_type === "paragraph") continue;
|
|
200
|
+
if (f.field_type === "image" || f.field_type === "video" || f.field_type === "file") continue;
|
|
201
|
+
const input = filterInputForField(f.field_type);
|
|
202
|
+
if (input) fields[buildFieldNameMap([f.name]).toGraphql(f.name)] = { type: input };
|
|
203
|
+
}
|
|
204
|
+
if (Object.keys(fields).length === 0) return null;
|
|
205
|
+
return new GraphQLInputObjectType({ name: `${graphqlTypeName(type.name)}Filter`, fields });
|
|
206
|
+
}
|
|
207
|
+
function translateFilters(input, nameMap) {
|
|
208
|
+
const out = {};
|
|
209
|
+
if (!input) return out;
|
|
210
|
+
for (const [gqlField, raw] of Object.entries(input)) {
|
|
211
|
+
if (raw === null || typeof raw !== "object") continue;
|
|
212
|
+
const column = nameMap.toSchema(gqlField);
|
|
213
|
+
const spec = raw;
|
|
214
|
+
if (spec["eq"] !== void 0) {
|
|
215
|
+
out[column] = spec["eq"];
|
|
216
|
+
} else if (Array.isArray(spec["in"])) {
|
|
217
|
+
out[column] = spec["in"];
|
|
218
|
+
} else {
|
|
219
|
+
const op = {};
|
|
220
|
+
for (const k of ["gt", "gte", "lt", "lte"]) {
|
|
221
|
+
if (spec[k] !== void 0) op[k] = spec[k];
|
|
222
|
+
}
|
|
223
|
+
if (Object.keys(op).length > 0) out[column] = op;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return out;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// src/graphql/resolvers.ts
|
|
230
|
+
import { GraphQLError } from "graphql";
|
|
231
|
+
function codeError(code, message) {
|
|
232
|
+
return new GraphQLError(message, { extensions: { code } });
|
|
233
|
+
}
|
|
234
|
+
function isPublished(item) {
|
|
235
|
+
return !!item && item["published"] === true;
|
|
236
|
+
}
|
|
237
|
+
function scalarFieldResolver(schemaName) {
|
|
238
|
+
return (parent) => parent[schemaName];
|
|
239
|
+
}
|
|
240
|
+
function relationFieldResolver(typeName, schemaFieldName) {
|
|
241
|
+
return (parent, _args, ctx) => ctx.loaders.load(typeName, schemaFieldName, parent);
|
|
242
|
+
}
|
|
243
|
+
function programmaticFieldResolver(typeName, schemaFieldName) {
|
|
244
|
+
return async (parent, _args, ctx) => {
|
|
245
|
+
let p = ctx.programmaticMemo.get(parent);
|
|
246
|
+
if (!p) {
|
|
247
|
+
p = ctx.resolver.resolveItem(typeName, parent);
|
|
248
|
+
ctx.programmaticMemo.set(parent, p);
|
|
249
|
+
}
|
|
250
|
+
return (await p)[schemaFieldName];
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function collectionResolver(typeName, nameMap) {
|
|
254
|
+
return async (_root, args, ctx) => {
|
|
255
|
+
const page = args.page ?? 1;
|
|
256
|
+
const perPage = args.perPage ?? 10;
|
|
257
|
+
if (!Number.isInteger(page) || page < 1) {
|
|
258
|
+
throw codeError("INVALID_PAGINATION", "page must be \u2265 1");
|
|
259
|
+
}
|
|
260
|
+
if (!Number.isInteger(perPage) || perPage < 1 || perPage > 100) {
|
|
261
|
+
throw codeError("INVALID_PAGINATION", "perPage must be between 1 and 100");
|
|
262
|
+
}
|
|
263
|
+
const repo = ctx.repos[typeName];
|
|
264
|
+
const result = await repo.findMany({
|
|
265
|
+
published_only: true,
|
|
266
|
+
page,
|
|
267
|
+
per_page: perPage,
|
|
268
|
+
sort_by: args.sortBy ?? "created_at",
|
|
269
|
+
sort_order: args.sortOrder ?? "asc",
|
|
270
|
+
filters: translateFilters(args.filter, nameMap)
|
|
271
|
+
});
|
|
272
|
+
return { data: result.data, meta: result.meta };
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
function singleBySlugResolver(typeName) {
|
|
276
|
+
return async (_root, args, ctx) => {
|
|
277
|
+
const repo = ctx.repos[typeName];
|
|
278
|
+
const item = await repo.findBySlug(args.slug);
|
|
279
|
+
return isPublished(item) ? item : null;
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
function singletonResolver(typeName) {
|
|
283
|
+
return async (_root, _args, ctx) => {
|
|
284
|
+
const repo = ctx.repos[typeName];
|
|
285
|
+
const result = await repo.findMany({ published_only: true, page: 1, per_page: 1 });
|
|
286
|
+
return result.data[0] ?? null;
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
function taxonomySingleResolver(typeName) {
|
|
290
|
+
return async (_root, args, ctx) => {
|
|
291
|
+
const repo = ctx.repos[typeName];
|
|
292
|
+
const item = await repo.findOne(args.id);
|
|
293
|
+
return isPublished(item) ? item : null;
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// src/graphql/schema.ts
|
|
298
|
+
var PAGE_META = new GraphQLObjectType({
|
|
299
|
+
name: "PageMeta",
|
|
300
|
+
fields: {
|
|
301
|
+
total: { type: new GraphQLNonNull2(GraphQLInt3), resolve: (m) => m.total },
|
|
302
|
+
page: { type: new GraphQLNonNull2(GraphQLInt3), resolve: (m) => m.page },
|
|
303
|
+
perPage: { type: new GraphQLNonNull2(GraphQLInt3), resolve: (m) => m.per_page },
|
|
304
|
+
totalPages: { type: new GraphQLNonNull2(GraphQLInt3), resolve: (m) => m.total_pages },
|
|
305
|
+
hasNext: { type: new GraphQLNonNull2(GraphQLBoolean3), resolve: (m) => m.has_next },
|
|
306
|
+
hasPrev: { type: new GraphQLNonNull2(GraphQLBoolean3), resolve: (m) => m.has_prev }
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
var MEDIA = new GraphQLObjectType({
|
|
310
|
+
name: "Media",
|
|
311
|
+
fields: {
|
|
312
|
+
id: { type: new GraphQLNonNull2(GraphQLID), resolve: (m) => m.id },
|
|
313
|
+
type: { type: new GraphQLNonNull2(GraphQLString3), resolve: (m) => m.type },
|
|
314
|
+
url: { type: new GraphQLNonNull2(GraphQLString3), resolve: (m) => m.url },
|
|
315
|
+
mimeType: { type: new GraphQLNonNull2(GraphQLString3), resolve: (m) => m.mime_type },
|
|
316
|
+
alt: { type: GraphQLString3, resolve: (m) => m.alt },
|
|
317
|
+
fileSize: { type: GraphQLInt3, resolve: (m) => m.file_size },
|
|
318
|
+
width: { type: GraphQLInt3, resolve: (m) => m.width },
|
|
319
|
+
height: { type: GraphQLInt3, resolve: (m) => m.height },
|
|
320
|
+
duration: { type: GraphQLInt3, resolve: (m) => m.duration }
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
function buildGraphQLSchema(registry) {
|
|
324
|
+
const objectTypes = /* @__PURE__ */ new Map();
|
|
325
|
+
const enumTypes = /* @__PURE__ */ new Map();
|
|
326
|
+
for (const [name, enumType] of Object.entries(registry.enum_types)) {
|
|
327
|
+
if (enumType.values.every(isValidGraphQLName)) {
|
|
328
|
+
enumTypes.set(
|
|
329
|
+
name,
|
|
330
|
+
new GraphQLEnumType2({
|
|
331
|
+
name: graphqlTypeName(name),
|
|
332
|
+
values: Object.fromEntries(enumType.values.map((v) => [v, { value: v }]))
|
|
333
|
+
})
|
|
334
|
+
);
|
|
335
|
+
} else {
|
|
336
|
+
process.stderr.write(
|
|
337
|
+
`\u26A0 enum '${name}' has values that aren't valid GraphQL identifiers; exposing as String
|
|
338
|
+
`
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
function outputTypeForField(field) {
|
|
343
|
+
const scalar = scalarOutputType(field.field_type);
|
|
344
|
+
if (scalar) return field.required ? new GraphQLNonNull2(scalar) : scalar;
|
|
345
|
+
if (field.field_type === "enum") {
|
|
346
|
+
const ref = field.ui_component.component === "select" ? field.ui_component.enum_ref : void 0;
|
|
347
|
+
const et = ref ? enumTypes.get(ref) : void 0;
|
|
348
|
+
const t = et ?? GraphQLString3;
|
|
349
|
+
return field.required ? new GraphQLNonNull2(t) : t;
|
|
350
|
+
}
|
|
351
|
+
if (field.field_type === "image" || field.field_type === "video" || field.field_type === "file") {
|
|
352
|
+
return MEDIA;
|
|
353
|
+
}
|
|
354
|
+
if (field.field_type === "paragraph") {
|
|
355
|
+
const ref = field.ui_component.component === "paragraph-embed" ? field.ui_component.ref : void 0;
|
|
356
|
+
const target = ref ? objectTypes.get(ref) : void 0;
|
|
357
|
+
if (ref && !target) {
|
|
358
|
+
process.stderr.write(
|
|
359
|
+
`\u26A0 paragraph field '${field.name}' targets unknown paragraph type '${ref}'; exposing as Media
|
|
360
|
+
`
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
return new GraphQLList2(new GraphQLNonNull2(target ?? MEDIA));
|
|
364
|
+
}
|
|
365
|
+
if (field.field_type === "reference") {
|
|
366
|
+
const ref = field.ui_component.component === "typeahead-select" ? field.ui_component.ref : void 0;
|
|
367
|
+
const target = ref ? objectTypes.get(ref) : void 0;
|
|
368
|
+
if (ref && !target) {
|
|
369
|
+
process.stderr.write(`\u26A0 reference field '${field.name}' targets unknown type '${ref}'; exposing as Media
|
|
370
|
+
`);
|
|
371
|
+
}
|
|
372
|
+
const rel = field.ui_component.component === "typeahead-select" ? field.ui_component.rel : void 0;
|
|
373
|
+
const isMany = rel === "many-to-many" || rel === "one-to-many";
|
|
374
|
+
const t = target ?? MEDIA;
|
|
375
|
+
return isMany ? new GraphQLList2(new GraphQLNonNull2(t)) : t;
|
|
376
|
+
}
|
|
377
|
+
return GraphQLString3;
|
|
378
|
+
}
|
|
379
|
+
function buildObjectType(machineName, type) {
|
|
380
|
+
return new GraphQLObjectType({
|
|
381
|
+
name: graphqlTypeName(machineName),
|
|
382
|
+
fields: () => {
|
|
383
|
+
const fields = {};
|
|
384
|
+
fields["id"] = { type: new GraphQLNonNull2(GraphQLID), resolve: (p) => p["id"] };
|
|
385
|
+
if (type.schema_type !== "paragraph-type") {
|
|
386
|
+
fields["published"] = { type: new GraphQLNonNull2(GraphQLBoolean3), resolve: (p) => p["published"] };
|
|
387
|
+
}
|
|
388
|
+
if (type.schema_type === "content-type") {
|
|
389
|
+
fields["slug"] = { type: new GraphQLNonNull2(GraphQLString3), resolve: (p) => p["slug"] };
|
|
390
|
+
}
|
|
391
|
+
fields["createdAt"] = { type: new GraphQLNonNull2(DateTimeScalar), resolve: (p) => p["created_at"] };
|
|
392
|
+
fields["updatedAt"] = { type: new GraphQLNonNull2(DateTimeScalar), resolve: (p) => p["updated_at"] };
|
|
393
|
+
for (const field of type.fields) {
|
|
394
|
+
const gqlName = toCamelCase(field.name);
|
|
395
|
+
const outType = outputTypeForField(field);
|
|
396
|
+
let resolve;
|
|
397
|
+
if (field.field_type === "programmatic") {
|
|
398
|
+
resolve = programmaticFieldResolver(machineName, field.name);
|
|
399
|
+
} else if (field.field_type === "reference" || field.field_type === "paragraph" || field.field_type === "image" || field.field_type === "video" || field.field_type === "file") {
|
|
400
|
+
resolve = relationFieldResolver(machineName, field.name);
|
|
401
|
+
} else {
|
|
402
|
+
resolve = scalarFieldResolver(field.name);
|
|
403
|
+
}
|
|
404
|
+
fields[gqlName] = { type: outType, resolve };
|
|
405
|
+
}
|
|
406
|
+
return fields;
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
const typeNameOwners = /* @__PURE__ */ new Map();
|
|
411
|
+
function registerTypeName(machineName) {
|
|
412
|
+
const gqlName = graphqlTypeName(machineName);
|
|
413
|
+
const owner = typeNameOwners.get(gqlName);
|
|
414
|
+
if (owner !== void 0 && owner !== machineName) {
|
|
415
|
+
throw new Error(
|
|
416
|
+
`GraphQL type name collision: '${gqlName}' is produced by both '${owner}' and '${machineName}'. Rename one schema's machine name segment so their GraphQL type names differ.`
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
typeNameOwners.set(gqlName, machineName);
|
|
420
|
+
}
|
|
421
|
+
for (const [name, ct] of Object.entries(registry.content_types)) {
|
|
422
|
+
registerTypeName(name);
|
|
423
|
+
objectTypes.set(name, buildObjectType(name, ct));
|
|
424
|
+
}
|
|
425
|
+
for (const [name, tt] of Object.entries(registry.taxonomy_types)) {
|
|
426
|
+
registerTypeName(name);
|
|
427
|
+
objectTypes.set(name, buildObjectType(name, tt));
|
|
428
|
+
}
|
|
429
|
+
for (const [name, pt] of Object.entries(registry.paragraph_types)) {
|
|
430
|
+
registerTypeName(name);
|
|
431
|
+
objectTypes.set(name, buildObjectType(name, pt));
|
|
432
|
+
}
|
|
433
|
+
const queryFields = {};
|
|
434
|
+
for (const [name, ct] of Object.entries(registry.content_types)) {
|
|
435
|
+
const objType = objectTypes.get(name);
|
|
436
|
+
const nameMap = buildFieldNameMap(ct.fields.map((f) => f.name));
|
|
437
|
+
if (ct.only_one) {
|
|
438
|
+
queryFields[singleQueryName(name)] = { type: objType, resolve: singletonResolver(name) };
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
const listType = new GraphQLObjectType({
|
|
442
|
+
name: `${graphqlTypeName(name)}List`,
|
|
443
|
+
fields: {
|
|
444
|
+
data: { type: new GraphQLNonNull2(new GraphQLList2(new GraphQLNonNull2(objType))), resolve: (r) => r.data },
|
|
445
|
+
meta: { type: new GraphQLNonNull2(PAGE_META), resolve: (r) => r.meta }
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
const filterType = buildFilterInputType(ct);
|
|
449
|
+
queryFields[collectionQueryName(name)] = {
|
|
450
|
+
type: new GraphQLNonNull2(listType),
|
|
451
|
+
args: {
|
|
452
|
+
page: { type: GraphQLInt3 },
|
|
453
|
+
perPage: { type: GraphQLInt3 },
|
|
454
|
+
sortBy: { type: buildSortFieldEnum(graphqlTypeName(name)) },
|
|
455
|
+
sortOrder: { type: SortOrderEnum },
|
|
456
|
+
...filterType ? { filter: { type: filterType } } : {}
|
|
457
|
+
},
|
|
458
|
+
resolve: collectionResolver(name, nameMap)
|
|
459
|
+
};
|
|
460
|
+
queryFields[singleQueryName(name)] = {
|
|
461
|
+
type: objType,
|
|
462
|
+
args: { slug: { type: new GraphQLNonNull2(GraphQLString3) } },
|
|
463
|
+
resolve: singleBySlugResolver(name)
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
for (const [name, tt] of Object.entries(registry.taxonomy_types)) {
|
|
467
|
+
const objType = objectTypes.get(name);
|
|
468
|
+
const listType = new GraphQLObjectType({
|
|
469
|
+
name: `${graphqlTypeName(name)}List`,
|
|
470
|
+
fields: {
|
|
471
|
+
data: { type: new GraphQLNonNull2(new GraphQLList2(new GraphQLNonNull2(objType))), resolve: (r) => r.data },
|
|
472
|
+
meta: { type: new GraphQLNonNull2(PAGE_META), resolve: (r) => r.meta }
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
queryFields[collectionQueryName(name)] = {
|
|
476
|
+
type: new GraphQLNonNull2(listType),
|
|
477
|
+
args: { page: { type: GraphQLInt3 }, perPage: { type: GraphQLInt3 } },
|
|
478
|
+
resolve: collectionResolver(name, buildFieldNameMap(tt.fields.map((f) => f.name)))
|
|
479
|
+
};
|
|
480
|
+
queryFields[singleQueryName(name)] = {
|
|
481
|
+
type: objType,
|
|
482
|
+
args: { id: { type: new GraphQLNonNull2(GraphQLID) } },
|
|
483
|
+
resolve: taxonomySingleResolver(name)
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
return new GraphQLSchema({
|
|
487
|
+
query: new GraphQLObjectType({ name: "Query", fields: queryFields })
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// src/graphql/dataloaders.ts
|
|
492
|
+
import DataLoader from "dataloader";
|
|
493
|
+
function createRelationLoaders(db, registry) {
|
|
494
|
+
const relMaps = /* @__PURE__ */ new Map();
|
|
495
|
+
const loaders = /* @__PURE__ */ new Map();
|
|
496
|
+
const cache = /* @__PURE__ */ new Map();
|
|
497
|
+
function relationsFor(typeName) {
|
|
498
|
+
let m = relMaps.get(typeName);
|
|
499
|
+
if (!m) {
|
|
500
|
+
const type = registry.content_types[typeName] ?? registry.taxonomy_types[typeName] ?? registry.paragraph_types[typeName];
|
|
501
|
+
m = type ? buildRelationsMap(type.fields, registry) : {};
|
|
502
|
+
relMaps.set(typeName, m);
|
|
503
|
+
}
|
|
504
|
+
return m;
|
|
505
|
+
}
|
|
506
|
+
function loaderFor(typeName, fieldName, rel) {
|
|
507
|
+
const key = `${typeName}:${fieldName}`;
|
|
508
|
+
let dl = loaders.get(key);
|
|
509
|
+
if (!dl) {
|
|
510
|
+
dl = new DataLoader(
|
|
511
|
+
async (parents) => {
|
|
512
|
+
const rows = parents;
|
|
513
|
+
await resolveRelationField(db, rows, fieldName, rel, cache, true);
|
|
514
|
+
return rows.map((r) => r[fieldName]);
|
|
515
|
+
},
|
|
516
|
+
// Batch within a tick; do not memoize by parent identity (rows are mutated
|
|
517
|
+
// and may recur across nesting levels).
|
|
518
|
+
{ cache: false }
|
|
519
|
+
);
|
|
520
|
+
loaders.set(key, dl);
|
|
521
|
+
}
|
|
522
|
+
return dl;
|
|
523
|
+
}
|
|
524
|
+
return {
|
|
525
|
+
async load(typeName, fieldName, parent) {
|
|
526
|
+
const rel = relationsFor(typeName)[fieldName];
|
|
527
|
+
if (!rel) return null;
|
|
528
|
+
return loaderFor(typeName, fieldName, rel).load(parent);
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// src/graphql/security.ts
|
|
534
|
+
import { EnvelopArmor } from "@escape.tech/graphql-armor";
|
|
535
|
+
import { NoSchemaIntrospectionCustomRule } from "graphql";
|
|
536
|
+
function buildArmorPlugin(options) {
|
|
537
|
+
const armor = new EnvelopArmor({
|
|
538
|
+
maxDepth: { n: options.maxDepth },
|
|
539
|
+
costLimit: { maxCost: options.maxComplexity },
|
|
540
|
+
maxAliases: { n: 15 },
|
|
541
|
+
maxDirectives: { n: 50 },
|
|
542
|
+
blockFieldSuggestion: { enabled: true }
|
|
543
|
+
// maxTokens is intentionally left at Armor's secure default (n=1000) to bound
|
|
544
|
+
// GraphQL document token count at parse time, a layer the depth/cost/alias/directive
|
|
545
|
+
// limits do not cover.
|
|
546
|
+
});
|
|
547
|
+
return armor.protect();
|
|
548
|
+
}
|
|
549
|
+
function introspectionPlugin(enabled) {
|
|
550
|
+
return {
|
|
551
|
+
onValidate({ addValidationRule }) {
|
|
552
|
+
if (!enabled) addValidationRule(NoSchemaIntrospectionCustomRule);
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// src/graphql/handler.ts
|
|
558
|
+
function createGraphQLHandler(registry, repos, resolver, db, options) {
|
|
559
|
+
const schema = buildGraphQLSchema(registry);
|
|
560
|
+
const { plugins } = buildArmorPlugin({
|
|
561
|
+
maxDepth: options.maxDepth,
|
|
562
|
+
maxComplexity: options.maxComplexity
|
|
563
|
+
});
|
|
564
|
+
const yoga = createYoga({
|
|
565
|
+
schema,
|
|
566
|
+
graphqlEndpoint: "/graphql",
|
|
567
|
+
graphiql: options.graphiql,
|
|
568
|
+
landingPage: false,
|
|
569
|
+
// buildArmorPlugin's return type is `unknown[]` (see security.ts) since Armor's
|
|
570
|
+
// plugin shapes aren't part of its public typings; Yoga's `plugins` option only
|
|
571
|
+
// accepts its own `Plugin` type, so the cast bridges the two.
|
|
572
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
573
|
+
plugins: [...plugins, introspectionPlugin(options.introspection)],
|
|
574
|
+
context: () => ({
|
|
575
|
+
db,
|
|
576
|
+
registry,
|
|
577
|
+
repos,
|
|
578
|
+
resolver,
|
|
579
|
+
loaders: createRelationLoaders(db, registry),
|
|
580
|
+
programmaticMemo: /* @__PURE__ */ new WeakMap()
|
|
581
|
+
})
|
|
582
|
+
});
|
|
583
|
+
return (c) => yoga.fetch(c.req.raw, {});
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
export {
|
|
587
|
+
createGraphQLHandler
|
|
588
|
+
};
|
|
589
|
+
//# sourceMappingURL=chunk-QXMW74MR.mjs.map
|