@bobbykim/manguito-cms-api 0.2.0 → 0.3.2

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.
@@ -0,0 +1,747 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/graphql/index.ts
31
+ var graphql_exports = {};
32
+ __export(graphql_exports, {
33
+ createGraphQLHandler: () => createGraphQLHandler
34
+ });
35
+ module.exports = __toCommonJS(graphql_exports);
36
+
37
+ // src/graphql/handler.ts
38
+ var import_graphql_yoga = require("graphql-yoga");
39
+
40
+ // src/graphql/schema.ts
41
+ var import_graphql5 = require("graphql");
42
+
43
+ // src/graphql/scalars.ts
44
+ var import_graphql = require("graphql");
45
+ var DateTimeScalar = new import_graphql.GraphQLScalarType({
46
+ name: "DateTime",
47
+ description: "ISO-8601 date-time string",
48
+ serialize(value) {
49
+ if (value instanceof Date) return value.toISOString();
50
+ if (typeof value === "string") return value;
51
+ if (typeof value === "number") return new Date(value).toISOString();
52
+ throw new TypeError(`DateTime cannot serialize value: ${String(value)}`);
53
+ },
54
+ parseValue(value) {
55
+ if (typeof value !== "string") throw new TypeError("DateTime must be a string");
56
+ return value;
57
+ },
58
+ parseLiteral(ast) {
59
+ if (ast.kind !== import_graphql.Kind.STRING) throw new TypeError("DateTime must be a string");
60
+ return ast.value;
61
+ }
62
+ });
63
+ var JSONScalar = new import_graphql.GraphQLScalarType({
64
+ name: "JSON",
65
+ description: "Arbitrary JSON value",
66
+ serialize: (value) => value,
67
+ parseValue: (value) => value,
68
+ parseLiteral: (ast, variables) => (0, import_graphql.valueFromASTUntyped)(ast, variables ?? void 0)
69
+ });
70
+
71
+ // src/graphql/type-mapping.ts
72
+ var import_graphql2 = require("graphql");
73
+ function scalarOutputType(fieldType) {
74
+ switch (fieldType) {
75
+ case "text/plain":
76
+ case "text/rich":
77
+ return import_graphql2.GraphQLString;
78
+ case "integer":
79
+ return import_graphql2.GraphQLInt;
80
+ case "float":
81
+ return import_graphql2.GraphQLFloat;
82
+ case "boolean":
83
+ return import_graphql2.GraphQLBoolean;
84
+ case "date":
85
+ return DateTimeScalar;
86
+ case "programmatic":
87
+ return JSONScalar;
88
+ default:
89
+ return null;
90
+ }
91
+ }
92
+
93
+ // src/graphql/naming.ts
94
+ function schemaSegment(machineName) {
95
+ const idx = machineName.indexOf("--");
96
+ return idx === -1 ? machineName : machineName.slice(idx + 2);
97
+ }
98
+ function words(input) {
99
+ return input.split(/[_\s-]+/).filter(Boolean);
100
+ }
101
+ function toPascalCase(input) {
102
+ return words(input).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
103
+ }
104
+ function toCamelCase(input) {
105
+ const pascal = toPascalCase(input);
106
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
107
+ }
108
+ function pluralize(word) {
109
+ if (/[^aeiou]y$/.test(word)) return word.slice(0, -1) + "ies";
110
+ if (/(s|x|z|ch|sh)$/.test(word)) return word + "es";
111
+ return word + "s";
112
+ }
113
+ var GRAPHQL_NAME = /^[_A-Za-z][_0-9A-Za-z]*$/;
114
+ function isValidGraphQLName(name) {
115
+ return GRAPHQL_NAME.test(name);
116
+ }
117
+ function graphqlTypeName(machineName) {
118
+ return toPascalCase(schemaSegment(machineName));
119
+ }
120
+ function singleQueryName(machineName) {
121
+ return toCamelCase(schemaSegment(machineName));
122
+ }
123
+ function collectionQueryName(machineName) {
124
+ return pluralize(singleQueryName(machineName));
125
+ }
126
+ function buildFieldNameMap(schemaNames) {
127
+ const toG = /* @__PURE__ */ new Map();
128
+ const toS = /* @__PURE__ */ new Map();
129
+ for (const name of schemaNames) {
130
+ const g = toCamelCase(name);
131
+ toG.set(name, g);
132
+ toS.set(g, name);
133
+ }
134
+ return {
135
+ toGraphql: (schemaName) => toG.get(schemaName) ?? toCamelCase(schemaName),
136
+ toSchema: (graphqlName) => toS.get(graphqlName) ?? graphqlName
137
+ };
138
+ }
139
+
140
+ // src/graphql/filters.ts
141
+ var import_graphql3 = require("graphql");
142
+ var SortOrderEnum = new import_graphql3.GraphQLEnumType({
143
+ name: "SortOrder",
144
+ values: { ASC: { value: "asc" }, DESC: { value: "desc" } }
145
+ });
146
+ var SORTABLE = [
147
+ { gql: "title", column: "title" },
148
+ { gql: "createdAt", column: "created_at" },
149
+ { gql: "updatedAt", column: "updated_at" }
150
+ ];
151
+ function buildSortFieldEnum(typeName) {
152
+ return new import_graphql3.GraphQLEnumType({
153
+ name: `${typeName}SortField`,
154
+ values: Object.fromEntries(SORTABLE.map((s) => [s.gql, { value: s.column }]))
155
+ });
156
+ }
157
+ function comparableFilterInput(name, scalar) {
158
+ return new import_graphql3.GraphQLInputObjectType({
159
+ name,
160
+ fields: {
161
+ eq: { type: scalar },
162
+ in: { type: new import_graphql3.GraphQLList(new import_graphql3.GraphQLNonNull(scalar)) },
163
+ gt: { type: scalar },
164
+ gte: { type: scalar },
165
+ lt: { type: scalar },
166
+ lte: { type: scalar }
167
+ }
168
+ });
169
+ }
170
+ function equalityFilterInput(name, scalar) {
171
+ return new import_graphql3.GraphQLInputObjectType({
172
+ name,
173
+ fields: {
174
+ eq: { type: scalar },
175
+ in: { type: new import_graphql3.GraphQLList(new import_graphql3.GraphQLNonNull(scalar)) }
176
+ }
177
+ });
178
+ }
179
+ var StringFilter = equalityFilterInput("StringFilter", import_graphql3.GraphQLString);
180
+ var BooleanFilter = new import_graphql3.GraphQLInputObjectType({
181
+ name: "BooleanFilter",
182
+ fields: { eq: { type: import_graphql3.GraphQLBoolean } }
183
+ });
184
+ var DateTimeFilter = comparableFilterInput("DateTimeFilter", DateTimeScalar);
185
+ var IDFilter = equalityFilterInput("IDFilter", import_graphql3.GraphQLString);
186
+ var IntFilter = comparableFilterInput("IntFilter", import_graphql3.GraphQLInt);
187
+ var FloatFilter = comparableFilterInput("FloatFilter", import_graphql3.GraphQLFloat);
188
+ function filterInputForField(fieldType) {
189
+ const scalar = scalarOutputType(fieldType);
190
+ if (fieldType === "boolean") return BooleanFilter;
191
+ if (fieldType === "date") return DateTimeFilter;
192
+ if (fieldType === "integer") return IntFilter;
193
+ if (fieldType === "float") return FloatFilter;
194
+ if (fieldType === "reference") return IDFilter;
195
+ if (scalar === null) return null;
196
+ return StringFilter;
197
+ }
198
+ function buildFilterInputType(type) {
199
+ const fields = {};
200
+ fields["id"] = { type: IDFilter };
201
+ fields["published"] = { type: BooleanFilter };
202
+ fields["createdAt"] = { type: DateTimeFilter };
203
+ fields["updatedAt"] = { type: DateTimeFilter };
204
+ if (type.schema_type === "content-type") fields["slug"] = { type: StringFilter };
205
+ for (const f of type.fields) {
206
+ if (f.field_type === "programmatic" || f.field_type === "paragraph") continue;
207
+ if (f.field_type === "image" || f.field_type === "video" || f.field_type === "file") continue;
208
+ const input = filterInputForField(f.field_type);
209
+ if (input) fields[buildFieldNameMap([f.name]).toGraphql(f.name)] = { type: input };
210
+ }
211
+ if (Object.keys(fields).length === 0) return null;
212
+ return new import_graphql3.GraphQLInputObjectType({ name: `${graphqlTypeName(type.name)}Filter`, fields });
213
+ }
214
+ function translateFilters(input, nameMap) {
215
+ const out = {};
216
+ if (!input) return out;
217
+ for (const [gqlField, raw] of Object.entries(input)) {
218
+ if (raw === null || typeof raw !== "object") continue;
219
+ const column = nameMap.toSchema(gqlField);
220
+ const spec = raw;
221
+ if (spec["eq"] !== void 0) {
222
+ out[column] = spec["eq"];
223
+ } else if (Array.isArray(spec["in"])) {
224
+ out[column] = spec["in"];
225
+ } else {
226
+ const op = {};
227
+ for (const k of ["gt", "gte", "lt", "lte"]) {
228
+ if (spec[k] !== void 0) op[k] = spec[k];
229
+ }
230
+ if (Object.keys(op).length > 0) out[column] = op;
231
+ }
232
+ }
233
+ return out;
234
+ }
235
+
236
+ // src/graphql/resolvers.ts
237
+ var import_graphql4 = require("graphql");
238
+ function codeError(code, message) {
239
+ return new import_graphql4.GraphQLError(message, { extensions: { code } });
240
+ }
241
+ function isPublished(item) {
242
+ return !!item && item["published"] === true;
243
+ }
244
+ function scalarFieldResolver(schemaName) {
245
+ return (parent) => parent[schemaName];
246
+ }
247
+ function relationFieldResolver(typeName, schemaFieldName) {
248
+ return (parent, _args, ctx) => ctx.loaders.load(typeName, schemaFieldName, parent);
249
+ }
250
+ function programmaticFieldResolver(typeName, schemaFieldName, mediaFieldNames = []) {
251
+ return async (parent, _args, ctx) => {
252
+ let p = ctx.programmaticMemo.get(parent);
253
+ if (!p) {
254
+ p = resolveProgrammaticRow(typeName, parent, ctx, mediaFieldNames);
255
+ ctx.programmaticMemo.set(parent, p);
256
+ }
257
+ return (await p)[schemaFieldName];
258
+ };
259
+ }
260
+ async function resolveProgrammaticRow(typeName, parent, ctx, mediaFieldNames) {
261
+ if (mediaFieldNames.length === 0) return ctx.resolver.resolveItem(typeName, parent);
262
+ const enriched = { ...parent };
263
+ await Promise.all(mediaFieldNames.map((name) => ctx.loaders.load(typeName, name, enriched)));
264
+ return ctx.resolver.resolveItem(typeName, enriched);
265
+ }
266
+ function collectionResolver(typeName, nameMap) {
267
+ return async (_root, args, ctx) => {
268
+ const page = args.page ?? 1;
269
+ const perPage = args.perPage ?? 10;
270
+ if (!Number.isInteger(page) || page < 1) {
271
+ throw codeError("INVALID_PAGINATION", "page must be \u2265 1");
272
+ }
273
+ if (!Number.isInteger(perPage) || perPage < 1 || perPage > 100) {
274
+ throw codeError("INVALID_PAGINATION", "perPage must be between 1 and 100");
275
+ }
276
+ const repo = ctx.repos[typeName];
277
+ const result = await repo.findMany({
278
+ published_only: true,
279
+ page,
280
+ per_page: perPage,
281
+ sort_by: args.sortBy ?? "created_at",
282
+ sort_order: args.sortOrder ?? "asc",
283
+ filters: translateFilters(args.filter, nameMap)
284
+ });
285
+ return { data: result.data, meta: result.meta };
286
+ };
287
+ }
288
+ function singleBySlugResolver(typeName) {
289
+ return async (_root, args, ctx) => {
290
+ const repo = ctx.repos[typeName];
291
+ const item = await repo.findBySlug(args.slug);
292
+ return isPublished(item) ? item : null;
293
+ };
294
+ }
295
+ function singletonResolver(typeName) {
296
+ return async (_root, _args, ctx) => {
297
+ const repo = ctx.repos[typeName];
298
+ const result = await repo.findMany({ published_only: true, page: 1, per_page: 1 });
299
+ return result.data[0] ?? null;
300
+ };
301
+ }
302
+ function taxonomySingleResolver(typeName) {
303
+ return async (_root, args, ctx) => {
304
+ const repo = ctx.repos[typeName];
305
+ const item = await repo.findOne(args.id);
306
+ return isPublished(item) ? item : null;
307
+ };
308
+ }
309
+
310
+ // src/graphql/schema.ts
311
+ var PAGE_META = new import_graphql5.GraphQLObjectType({
312
+ name: "PageMeta",
313
+ fields: {
314
+ total: { type: new import_graphql5.GraphQLNonNull(import_graphql5.GraphQLInt), resolve: (m) => m.total },
315
+ page: { type: new import_graphql5.GraphQLNonNull(import_graphql5.GraphQLInt), resolve: (m) => m.page },
316
+ perPage: { type: new import_graphql5.GraphQLNonNull(import_graphql5.GraphQLInt), resolve: (m) => m.per_page },
317
+ totalPages: { type: new import_graphql5.GraphQLNonNull(import_graphql5.GraphQLInt), resolve: (m) => m.total_pages },
318
+ hasNext: { type: new import_graphql5.GraphQLNonNull(import_graphql5.GraphQLBoolean), resolve: (m) => m.has_next },
319
+ hasPrev: { type: new import_graphql5.GraphQLNonNull(import_graphql5.GraphQLBoolean), resolve: (m) => m.has_prev }
320
+ }
321
+ });
322
+ var MEDIA = new import_graphql5.GraphQLObjectType({
323
+ name: "Media",
324
+ fields: {
325
+ id: { type: new import_graphql5.GraphQLNonNull(import_graphql5.GraphQLID), resolve: (m) => m.id },
326
+ type: { type: new import_graphql5.GraphQLNonNull(import_graphql5.GraphQLString), resolve: (m) => m.type },
327
+ url: { type: new import_graphql5.GraphQLNonNull(import_graphql5.GraphQLString), resolve: (m) => m.url },
328
+ mimeType: { type: new import_graphql5.GraphQLNonNull(import_graphql5.GraphQLString), resolve: (m) => m.mime_type },
329
+ alt: { type: import_graphql5.GraphQLString, resolve: (m) => m.alt },
330
+ fileSize: { type: import_graphql5.GraphQLInt, resolve: (m) => m.file_size },
331
+ width: { type: import_graphql5.GraphQLInt, resolve: (m) => m.width },
332
+ height: { type: import_graphql5.GraphQLInt, resolve: (m) => m.height },
333
+ duration: { type: import_graphql5.GraphQLInt, resolve: (m) => m.duration }
334
+ }
335
+ });
336
+ function buildGraphQLSchema(registry) {
337
+ const objectTypes = /* @__PURE__ */ new Map();
338
+ const enumTypes = /* @__PURE__ */ new Map();
339
+ for (const [name, enumType] of Object.entries(registry.enum_types)) {
340
+ if (enumType.values.every(isValidGraphQLName)) {
341
+ enumTypes.set(
342
+ name,
343
+ new import_graphql5.GraphQLEnumType({
344
+ name: graphqlTypeName(name),
345
+ values: Object.fromEntries(enumType.values.map((v) => [v, { value: v }]))
346
+ })
347
+ );
348
+ } else {
349
+ process.stderr.write(
350
+ `\u26A0 enum '${name}' has values that aren't valid GraphQL identifiers; exposing as String
351
+ `
352
+ );
353
+ }
354
+ }
355
+ function outputTypeForField(field) {
356
+ const scalar = scalarOutputType(field.field_type);
357
+ if (scalar) return field.required ? new import_graphql5.GraphQLNonNull(scalar) : scalar;
358
+ if (field.field_type === "enum") {
359
+ const ref = field.ui_component.component === "select" ? field.ui_component.enum_ref : void 0;
360
+ const et = ref ? enumTypes.get(ref) : void 0;
361
+ const t = et ?? import_graphql5.GraphQLString;
362
+ return field.required ? new import_graphql5.GraphQLNonNull(t) : t;
363
+ }
364
+ if (field.field_type === "image" || field.field_type === "video" || field.field_type === "file") {
365
+ return MEDIA;
366
+ }
367
+ if (field.field_type === "paragraph") {
368
+ const ref = field.ui_component.component === "paragraph-embed" ? field.ui_component.ref : void 0;
369
+ const target = ref ? objectTypes.get(ref) : void 0;
370
+ if (ref && !target) {
371
+ process.stderr.write(
372
+ `\u26A0 paragraph field '${field.name}' targets unknown paragraph type '${ref}'; exposing as Media
373
+ `
374
+ );
375
+ }
376
+ return new import_graphql5.GraphQLList(new import_graphql5.GraphQLNonNull(target ?? MEDIA));
377
+ }
378
+ if (field.field_type === "reference") {
379
+ const ref = field.ui_component.component === "typeahead-select" ? field.ui_component.ref : void 0;
380
+ const target = ref ? objectTypes.get(ref) : void 0;
381
+ if (ref && !target) {
382
+ process.stderr.write(`\u26A0 reference field '${field.name}' targets unknown type '${ref}'; exposing as Media
383
+ `);
384
+ }
385
+ const rel = field.ui_component.component === "typeahead-select" ? field.ui_component.rel : void 0;
386
+ const isMany = rel === "many-to-many" || rel === "one-to-many";
387
+ const t = target ?? MEDIA;
388
+ return isMany ? new import_graphql5.GraphQLList(new import_graphql5.GraphQLNonNull(t)) : t;
389
+ }
390
+ return import_graphql5.GraphQLString;
391
+ }
392
+ function buildObjectType(machineName, type) {
393
+ const mediaFieldNames = type.fields.filter((f) => f.field_type === "image" || f.field_type === "video" || f.field_type === "file").map((f) => f.name);
394
+ return new import_graphql5.GraphQLObjectType({
395
+ name: graphqlTypeName(machineName),
396
+ fields: () => {
397
+ const fields = {};
398
+ fields["id"] = { type: new import_graphql5.GraphQLNonNull(import_graphql5.GraphQLID), resolve: (p) => p["id"] };
399
+ if (type.schema_type !== "paragraph-type") {
400
+ fields["published"] = { type: new import_graphql5.GraphQLNonNull(import_graphql5.GraphQLBoolean), resolve: (p) => p["published"] };
401
+ }
402
+ if (type.schema_type === "content-type") {
403
+ fields["slug"] = { type: new import_graphql5.GraphQLNonNull(import_graphql5.GraphQLString), resolve: (p) => p["slug"] };
404
+ }
405
+ fields["createdAt"] = { type: new import_graphql5.GraphQLNonNull(DateTimeScalar), resolve: (p) => p["created_at"] };
406
+ fields["updatedAt"] = { type: new import_graphql5.GraphQLNonNull(DateTimeScalar), resolve: (p) => p["updated_at"] };
407
+ for (const field of type.fields) {
408
+ const gqlName = toCamelCase(field.name);
409
+ const outType = outputTypeForField(field);
410
+ let resolve;
411
+ if (field.field_type === "programmatic") {
412
+ resolve = programmaticFieldResolver(machineName, field.name, mediaFieldNames);
413
+ } else if (field.field_type === "reference" || field.field_type === "paragraph" || field.field_type === "image" || field.field_type === "video" || field.field_type === "file") {
414
+ resolve = relationFieldResolver(machineName, field.name);
415
+ } else {
416
+ resolve = scalarFieldResolver(field.name);
417
+ }
418
+ fields[gqlName] = { type: outType, resolve };
419
+ }
420
+ return fields;
421
+ }
422
+ });
423
+ }
424
+ const typeNameOwners = /* @__PURE__ */ new Map();
425
+ function registerTypeName(machineName) {
426
+ const gqlName = graphqlTypeName(machineName);
427
+ const owner = typeNameOwners.get(gqlName);
428
+ if (owner !== void 0 && owner !== machineName) {
429
+ throw new Error(
430
+ `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.`
431
+ );
432
+ }
433
+ typeNameOwners.set(gqlName, machineName);
434
+ }
435
+ for (const [name, ct] of Object.entries(registry.content_types)) {
436
+ registerTypeName(name);
437
+ objectTypes.set(name, buildObjectType(name, ct));
438
+ }
439
+ for (const [name, tt] of Object.entries(registry.taxonomy_types)) {
440
+ registerTypeName(name);
441
+ objectTypes.set(name, buildObjectType(name, tt));
442
+ }
443
+ for (const [name, pt] of Object.entries(registry.paragraph_types)) {
444
+ registerTypeName(name);
445
+ objectTypes.set(name, buildObjectType(name, pt));
446
+ }
447
+ const queryFields = {};
448
+ for (const [name, ct] of Object.entries(registry.content_types)) {
449
+ const objType = objectTypes.get(name);
450
+ const nameMap = buildFieldNameMap(ct.fields.map((f) => f.name));
451
+ if (ct.only_one) {
452
+ queryFields[singleQueryName(name)] = { type: objType, resolve: singletonResolver(name) };
453
+ continue;
454
+ }
455
+ const listType = new import_graphql5.GraphQLObjectType({
456
+ name: `${graphqlTypeName(name)}List`,
457
+ fields: {
458
+ data: { type: new import_graphql5.GraphQLNonNull(new import_graphql5.GraphQLList(new import_graphql5.GraphQLNonNull(objType))), resolve: (r) => r.data },
459
+ meta: { type: new import_graphql5.GraphQLNonNull(PAGE_META), resolve: (r) => r.meta }
460
+ }
461
+ });
462
+ const filterType = buildFilterInputType(ct);
463
+ queryFields[collectionQueryName(name)] = {
464
+ type: new import_graphql5.GraphQLNonNull(listType),
465
+ args: {
466
+ page: { type: import_graphql5.GraphQLInt },
467
+ perPage: { type: import_graphql5.GraphQLInt },
468
+ sortBy: { type: buildSortFieldEnum(graphqlTypeName(name)) },
469
+ sortOrder: { type: SortOrderEnum },
470
+ ...filterType ? { filter: { type: filterType } } : {}
471
+ },
472
+ resolve: collectionResolver(name, nameMap)
473
+ };
474
+ queryFields[singleQueryName(name)] = {
475
+ type: objType,
476
+ args: { slug: { type: new import_graphql5.GraphQLNonNull(import_graphql5.GraphQLString) } },
477
+ resolve: singleBySlugResolver(name)
478
+ };
479
+ }
480
+ for (const [name, tt] of Object.entries(registry.taxonomy_types)) {
481
+ const objType = objectTypes.get(name);
482
+ const listType = new import_graphql5.GraphQLObjectType({
483
+ name: `${graphqlTypeName(name)}List`,
484
+ fields: {
485
+ data: { type: new import_graphql5.GraphQLNonNull(new import_graphql5.GraphQLList(new import_graphql5.GraphQLNonNull(objType))), resolve: (r) => r.data },
486
+ meta: { type: new import_graphql5.GraphQLNonNull(PAGE_META), resolve: (r) => r.meta }
487
+ }
488
+ });
489
+ queryFields[collectionQueryName(name)] = {
490
+ type: new import_graphql5.GraphQLNonNull(listType),
491
+ args: { page: { type: import_graphql5.GraphQLInt }, perPage: { type: import_graphql5.GraphQLInt } },
492
+ resolve: collectionResolver(name, buildFieldNameMap(tt.fields.map((f) => f.name)))
493
+ };
494
+ queryFields[singleQueryName(name)] = {
495
+ type: objType,
496
+ args: { id: { type: new import_graphql5.GraphQLNonNull(import_graphql5.GraphQLID) } },
497
+ resolve: taxonomySingleResolver(name)
498
+ };
499
+ }
500
+ return new import_graphql5.GraphQLSchema({
501
+ query: new import_graphql5.GraphQLObjectType({ name: "Query", fields: queryFields })
502
+ });
503
+ }
504
+
505
+ // src/graphql/dataloaders.ts
506
+ var import_dataloader = __toESM(require("dataloader"));
507
+
508
+ // src/relations.ts
509
+ var import_node_crypto = __toESM(require("crypto"));
510
+ var import_drizzle_orm = require("drizzle-orm");
511
+ function quoteIdent(name) {
512
+ if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`);
513
+ return `"${name}"`;
514
+ }
515
+ function buildRelationsMap(fields, registry) {
516
+ const relations = {};
517
+ for (const field of fields) {
518
+ if (field.field_type === "image" || field.field_type === "video" || field.field_type === "file") {
519
+ if (!field.db_column) continue;
520
+ relations[field.name] = { type: "media", fk_column: field.db_column.column_name };
521
+ } else if (field.field_type === "paragraph") {
522
+ const ref = field.ui_component.ref;
523
+ const pType = ref ? registry.paragraph_types[ref] : void 0;
524
+ if (!pType) continue;
525
+ relations[field.name] = { type: "paragraph", table: pType.db.table_name };
526
+ } else if (field.field_type === "reference") {
527
+ if (!field.db_column) continue;
528
+ if (field.db_column.junction) {
529
+ const j = field.db_column.junction;
530
+ relations[field.name] = {
531
+ type: "junction",
532
+ table: j.right_table,
533
+ junction_table: j.table_name,
534
+ left_column: j.left_column,
535
+ right_column: j.right_column,
536
+ order_column: j.order_column
537
+ };
538
+ } else if (field.db_column.foreign_key) {
539
+ relations[field.name] = {
540
+ type: "reference",
541
+ table: field.db_column.foreign_key.table,
542
+ fk_column: field.db_column.column_name
543
+ };
544
+ }
545
+ }
546
+ }
547
+ return relations;
548
+ }
549
+ function groupBy(items, key) {
550
+ const result = {};
551
+ for (const item of items) {
552
+ const k = String(item[key]);
553
+ if (!result[k]) result[k] = [];
554
+ result[k].push(item);
555
+ }
556
+ return result;
557
+ }
558
+ async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOnly = false) {
559
+ if (rows.length === 0) return;
560
+ if (rel.type === "paragraph") {
561
+ const parentIds = rows.map((r) => r["id"]);
562
+ const inList = import_drizzle_orm.sql.join(parentIds.map((id) => import_drizzle_orm.sql`${id}`), import_drizzle_orm.sql`, `);
563
+ const result = await db.execute(
564
+ import_drizzle_orm.sql`SELECT * FROM ${import_drizzle_orm.sql.raw(quoteIdent(rel.table))} WHERE parent_id IN (${inList}) AND parent_field = ${fieldName} ORDER BY "order" ASC`
565
+ );
566
+ const byParent = groupBy(result.rows, "parent_id");
567
+ for (const row of rows) {
568
+ row[fieldName] = byParent[row["id"]] ?? [];
569
+ }
570
+ } else if (rel.type === "reference") {
571
+ const fkValues = rows.map((r) => r[rel.fk_column]).filter(Boolean);
572
+ if (fkValues.length === 0) {
573
+ for (const row of rows) row[fieldName] = null;
574
+ return;
575
+ }
576
+ const unique = [...new Set(fkValues)];
577
+ const uncached = unique.filter((id) => !cache.has(`${rel.table}:${id}`));
578
+ if (uncached.length > 0) {
579
+ const inList = import_drizzle_orm.sql.join(uncached.map((id) => import_drizzle_orm.sql`${id}`), import_drizzle_orm.sql`, `);
580
+ const publishedCond = publishedOnly ? import_drizzle_orm.sql` AND published = true` : import_drizzle_orm.sql``;
581
+ const result = await db.execute(
582
+ import_drizzle_orm.sql`SELECT * FROM ${import_drizzle_orm.sql.raw(quoteIdent(rel.table))} WHERE id IN (${inList})${publishedCond}`
583
+ );
584
+ for (const item of result.rows) {
585
+ cache.set(`${rel.table}:${item["id"]}`, item);
586
+ }
587
+ }
588
+ for (const row of rows) {
589
+ const fkVal = row[rel.fk_column];
590
+ row[fieldName] = fkVal ? cache.get(`${rel.table}:${fkVal}`) ?? null : null;
591
+ }
592
+ } else if (rel.type === "junction") {
593
+ const parentIds = rows.map((r) => r["id"]);
594
+ const inList = import_drizzle_orm.sql.join(parentIds.map((id) => import_drizzle_orm.sql`${id}`), import_drizzle_orm.sql`, `);
595
+ const orderBy = rel.order_column ? import_drizzle_orm.sql` ORDER BY "order" ASC` : import_drizzle_orm.sql``;
596
+ const junctionResult = await db.execute(
597
+ import_drizzle_orm.sql`SELECT * FROM ${import_drizzle_orm.sql.raw(quoteIdent(rel.junction_table))} WHERE ${import_drizzle_orm.sql.raw(quoteIdent(rel.left_column))} IN (${inList})${orderBy}`
598
+ );
599
+ const jRows = junctionResult.rows;
600
+ const rightIds = [...new Set(jRows.map((r) => r[rel.right_column]))];
601
+ const uncached = rightIds.filter((id) => !cache.has(`${rel.table}:${id}`));
602
+ if (uncached.length > 0) {
603
+ const rightInList = import_drizzle_orm.sql.join(uncached.map((id) => import_drizzle_orm.sql`${id}`), import_drizzle_orm.sql`, `);
604
+ const publishedCond = publishedOnly ? import_drizzle_orm.sql` AND published = true` : import_drizzle_orm.sql``;
605
+ const entitiesResult = await db.execute(
606
+ import_drizzle_orm.sql`SELECT * FROM ${import_drizzle_orm.sql.raw(quoteIdent(rel.table))} WHERE id IN (${rightInList})${publishedCond}`
607
+ );
608
+ for (const item of entitiesResult.rows) {
609
+ cache.set(`${rel.table}:${item["id"]}`, item);
610
+ }
611
+ }
612
+ const byLeft = groupBy(jRows, rel.left_column);
613
+ for (const row of rows) {
614
+ const jEntries = byLeft[row["id"]] ?? [];
615
+ row[fieldName] = jEntries.map((jr) => cache.get(`${rel.table}:${jr[rel.right_column]}`)).filter(Boolean);
616
+ }
617
+ } else if (rel.type === "media") {
618
+ const fkValues = rows.map((r) => r[rel.fk_column]).filter(Boolean);
619
+ if (fkValues.length === 0) {
620
+ for (const row of rows) row[fieldName] = null;
621
+ return;
622
+ }
623
+ const unique = [...new Set(fkValues)];
624
+ const uncached = unique.filter((id) => !cache.has(`media:${id}`));
625
+ if (uncached.length > 0) {
626
+ const inList = import_drizzle_orm.sql.join(uncached.map((id) => import_drizzle_orm.sql`${id}`), import_drizzle_orm.sql`, `);
627
+ const result = await db.execute(import_drizzle_orm.sql`SELECT * FROM "media" WHERE id IN (${inList})`);
628
+ for (const item of result.rows) {
629
+ cache.set(`media:${item["id"]}`, item);
630
+ }
631
+ }
632
+ for (const row of rows) {
633
+ const fkVal = row[rel.fk_column];
634
+ row[fieldName] = fkVal ? cache.get(`media:${fkVal}`) ?? null : null;
635
+ }
636
+ }
637
+ }
638
+
639
+ // src/graphql/dataloaders.ts
640
+ function createRelationLoaders(db, registry) {
641
+ const relMaps = /* @__PURE__ */ new Map();
642
+ const loaders = /* @__PURE__ */ new Map();
643
+ const cache = /* @__PURE__ */ new Map();
644
+ function relationsFor(typeName) {
645
+ let m = relMaps.get(typeName);
646
+ if (!m) {
647
+ const type = registry.content_types[typeName] ?? registry.taxonomy_types[typeName] ?? registry.paragraph_types[typeName];
648
+ m = type ? buildRelationsMap(type.fields, registry) : {};
649
+ relMaps.set(typeName, m);
650
+ }
651
+ return m;
652
+ }
653
+ function loaderFor(typeName, fieldName, rel) {
654
+ const key = `${typeName}:${fieldName}`;
655
+ let dl = loaders.get(key);
656
+ if (!dl) {
657
+ dl = new import_dataloader.default(
658
+ async (parents) => {
659
+ const rows = parents;
660
+ await resolveRelationField(db, rows, fieldName, rel, cache, true);
661
+ return rows.map((r) => r[fieldName]);
662
+ },
663
+ // Batch within a tick; do not memoize by parent identity (rows are mutated
664
+ // and may recur across nesting levels).
665
+ { cache: false }
666
+ );
667
+ loaders.set(key, dl);
668
+ }
669
+ return dl;
670
+ }
671
+ return {
672
+ async load(typeName, fieldName, parent) {
673
+ const rel = relationsFor(typeName)[fieldName];
674
+ if (!rel) return null;
675
+ return loaderFor(typeName, fieldName, rel).load(parent);
676
+ }
677
+ };
678
+ }
679
+
680
+ // src/graphql/security.ts
681
+ var import_graphql_armor = require("@escape.tech/graphql-armor");
682
+ var import_graphql6 = require("graphql");
683
+ function buildArmorPlugin(options) {
684
+ const armor = new import_graphql_armor.EnvelopArmor({
685
+ maxDepth: { n: options.maxDepth },
686
+ costLimit: { maxCost: options.maxComplexity },
687
+ maxAliases: { n: 15 },
688
+ maxDirectives: { n: 50 },
689
+ blockFieldSuggestion: { enabled: true }
690
+ // maxTokens is intentionally left at Armor's secure default (n=1000) to bound
691
+ // GraphQL document token count at parse time, a layer the depth/cost/alias/directive
692
+ // limits do not cover.
693
+ });
694
+ return armor.protect();
695
+ }
696
+ function introspectionPlugin(enabled) {
697
+ return {
698
+ onValidate({ addValidationRule }) {
699
+ if (!enabled) addValidationRule(import_graphql6.NoSchemaIntrospectionCustomRule);
700
+ }
701
+ };
702
+ }
703
+
704
+ // src/graphql/handler.ts
705
+ function isClientSafeGraphQLError(error) {
706
+ let current = error;
707
+ while (current != null && typeof current === "object" && current.name === "GraphQLError") {
708
+ if (current.originalError == null) return true;
709
+ current = current.originalError;
710
+ }
711
+ return false;
712
+ }
713
+ function createGraphQLHandler(registry, repos, resolver, db, options) {
714
+ const schema = buildGraphQLSchema(registry);
715
+ const { plugins } = buildArmorPlugin({
716
+ maxDepth: options.maxDepth,
717
+ maxComplexity: options.maxComplexity
718
+ });
719
+ const yoga = (0, import_graphql_yoga.createYoga)({
720
+ schema,
721
+ graphqlEndpoint: "/graphql",
722
+ graphiql: options.graphiql,
723
+ landingPage: false,
724
+ maskedErrors: {
725
+ maskError: (error, message, isDev) => isClientSafeGraphQLError(error) ? error : (0, import_graphql_yoga.maskError)(error, message, isDev)
726
+ },
727
+ // buildArmorPlugin's return type is `unknown[]` (see security.ts) since Armor's
728
+ // plugin shapes aren't part of its public typings; Yoga's `plugins` option only
729
+ // accepts its own `Plugin` type, so the cast bridges the two.
730
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
731
+ plugins: [...plugins, introspectionPlugin(options.introspection)],
732
+ context: () => ({
733
+ db,
734
+ registry,
735
+ repos,
736
+ resolver,
737
+ loaders: createRelationLoaders(db, registry),
738
+ programmaticMemo: /* @__PURE__ */ new WeakMap()
739
+ })
740
+ });
741
+ return (c) => yoga.fetch(c.req.raw, {});
742
+ }
743
+ // Annotate the CommonJS export names for ESM import in node:
744
+ 0 && (module.exports = {
745
+ createGraphQLHandler
746
+ });
747
+ //# sourceMappingURL=index.js.map