@bobbykim/manguito-cms-api 0.5.0 → 0.7.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-ARZXLNZS.mjs → chunk-4LLELFAZ.mjs} +139 -35
- package/dist/chunk-4LLELFAZ.mjs.map +1 -0
- package/dist/{chunk-D5AO4BTL.mjs → chunk-FXPKGPYF.mjs} +57 -42
- package/dist/chunk-FXPKGPYF.mjs.map +1 -0
- package/dist/graphql/index.d.mts +8 -2
- package/dist/graphql/index.d.ts +8 -2
- package/dist/graphql/index.js +145 -52
- package/dist/graphql/index.js.map +1 -1
- package/dist/graphql/index.mjs +2 -2
- package/dist/handler-GED3I7Z2.mjs +8 -0
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +372 -154
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +173 -82
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/dist/chunk-ARZXLNZS.mjs.map +0 -1
- package/dist/chunk-D5AO4BTL.mjs.map +0 -1
- package/dist/handler-X7JDHJAL.mjs +0 -8
- /package/dist/{handler-X7JDHJAL.mjs.map → handler-GED3I7Z2.mjs.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -38,14 +38,15 @@ function isColumnBacked(field) {
|
|
|
38
38
|
if (col.junction) return false;
|
|
39
39
|
return true;
|
|
40
40
|
}
|
|
41
|
-
function buildFieldKeyMap(pairs, allLabels,
|
|
41
|
+
function buildFieldKeyMap(pairs, allLabels, droppedColumns, droppedLabels) {
|
|
42
42
|
const labelToColumn = /* @__PURE__ */ new Map();
|
|
43
43
|
const columnToLabel = /* @__PURE__ */ new Map();
|
|
44
44
|
for (const { label, column } of pairs) {
|
|
45
45
|
labelToColumn.set(label, column);
|
|
46
46
|
columnToLabel.set(column, label);
|
|
47
47
|
}
|
|
48
|
-
for (const label of allLabels) {
|
|
48
|
+
for (const { label, ownColumn } of allLabels) {
|
|
49
|
+
if (ownColumn !== void 0) continue;
|
|
49
50
|
const columnOwner = columnToLabel.get(label);
|
|
50
51
|
if (columnOwner !== void 0 && columnOwner !== label) {
|
|
51
52
|
throw new Error(
|
|
@@ -54,23 +55,33 @@ function buildFieldKeyMap(pairs, allLabels, droppedKeys) {
|
|
|
54
55
|
}
|
|
55
56
|
}
|
|
56
57
|
for (const label of labelToColumn.keys()) {
|
|
57
|
-
if (
|
|
58
|
+
if (droppedLabels.has(label)) labelToColumn.delete(label);
|
|
58
59
|
}
|
|
59
60
|
for (const column of columnToLabel.keys()) {
|
|
60
|
-
if (
|
|
61
|
+
if (droppedColumns.has(column)) columnToLabel.delete(column);
|
|
61
62
|
}
|
|
62
63
|
const diverges = [...labelToColumn].some(([label, column]) => label !== column);
|
|
63
|
-
function remap(input, lookup) {
|
|
64
|
+
function remap(input, lookup, dropped, droppedIfUnmapped) {
|
|
64
65
|
const out = {};
|
|
65
66
|
for (const key of Object.keys(input)) {
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
const mapped = lookup.get(key);
|
|
68
|
+
if (mapped !== void 0) {
|
|
69
|
+
out[mapped] = input[key];
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (dropped.has(key) || droppedIfUnmapped?.has(key)) continue;
|
|
73
|
+
out[key] = input[key];
|
|
68
74
|
}
|
|
69
75
|
return out;
|
|
70
76
|
}
|
|
71
77
|
return {
|
|
72
|
-
toStorage
|
|
73
|
-
|
|
78
|
+
// toStorage reads LABEL-keyed input: a live label maps and writes
|
|
79
|
+
// regardless of either drop set; an unmapped key is refused if it is
|
|
80
|
+
// EITHER the tombstone's own name (droppedLabels) or a raw, unexposed
|
|
81
|
+
// column (droppedColumns) — see remap's comment.
|
|
82
|
+
// toLabels reads COLUMN-keyed input, so it consults only droppedColumns.
|
|
83
|
+
toStorage: (input) => remap(input, labelToColumn, droppedLabels, droppedColumns),
|
|
84
|
+
toLabels: (row) => remap(row, columnToLabel, droppedColumns),
|
|
74
85
|
columnFor: (label) => labelToColumn.get(label),
|
|
75
86
|
labelFor: (column) => columnToLabel.get(column),
|
|
76
87
|
labels: [...labelToColumn.keys()],
|
|
@@ -78,37 +89,52 @@ function buildFieldKeyMap(pairs, allLabels, droppedKeys) {
|
|
|
78
89
|
};
|
|
79
90
|
}
|
|
80
91
|
function createFieldKeyMap(fields) {
|
|
81
|
-
const
|
|
92
|
+
const droppedColumns = /* @__PURE__ */ new Set();
|
|
93
|
+
const droppedLabels = /* @__PURE__ */ new Set();
|
|
82
94
|
const pairs = [];
|
|
83
95
|
for (const f of fields) {
|
|
84
96
|
if (!isColumnBacked(f)) continue;
|
|
85
97
|
pairs.push({ label: f.name, column: f.db_column.column_name });
|
|
86
98
|
if (f.removed === true) {
|
|
87
|
-
|
|
88
|
-
|
|
99
|
+
droppedLabels.add(f.name);
|
|
100
|
+
droppedColumns.add(f.db_column.column_name);
|
|
89
101
|
}
|
|
90
102
|
}
|
|
91
103
|
return buildFieldKeyMap(
|
|
92
104
|
pairs,
|
|
93
|
-
fields.map((f) =>
|
|
94
|
-
|
|
105
|
+
fields.map((f) => ({
|
|
106
|
+
label: f.name,
|
|
107
|
+
// Conditional spread, not a plain assignment — exactOptionalPropertyTypes
|
|
108
|
+
// forbids present-and-undefined.
|
|
109
|
+
...isColumnBacked(f) && { ownColumn: f.db_column.column_name }
|
|
110
|
+
})),
|
|
111
|
+
droppedColumns,
|
|
112
|
+
droppedLabels
|
|
95
113
|
);
|
|
96
114
|
}
|
|
97
115
|
function createFieldKeyMapFromProjection(projectionType, allFields) {
|
|
98
116
|
const pairs = projectionType.fields.map((f) => ({ label: f.exposed_as, column: f.column_name }));
|
|
99
|
-
const
|
|
100
|
-
const
|
|
117
|
+
const projectedColumns = new Set(pairs.map((p) => p.column));
|
|
118
|
+
const projectedLabels = new Set(pairs.map((p) => p.label));
|
|
119
|
+
const droppedColumns = /* @__PURE__ */ new Set();
|
|
120
|
+
const droppedLabels = /* @__PURE__ */ new Set();
|
|
101
121
|
for (const f of allFields) {
|
|
102
122
|
if (!isColumnBacked(f)) continue;
|
|
103
|
-
|
|
104
|
-
|
|
123
|
+
if (!projectedColumns.has(f.db_column.column_name)) droppedColumns.add(f.db_column.column_name);
|
|
124
|
+
if (!projectedLabels.has(f.name)) droppedLabels.add(f.name);
|
|
105
125
|
}
|
|
106
|
-
for (const p of pairs) dropped.delete(p.column);
|
|
107
|
-
for (const label of projected) dropped.delete(label);
|
|
108
126
|
return buildFieldKeyMap(
|
|
109
127
|
pairs,
|
|
110
|
-
allFields.map((f) =>
|
|
111
|
-
|
|
128
|
+
allFields.map((f) => ({
|
|
129
|
+
label: f.name,
|
|
130
|
+
// See buildFieldKeyMap's collision check: `ownColumn` marks every
|
|
131
|
+
// column-backed field so the check skips it entirely — only a
|
|
132
|
+
// paragraph/junction/programmatic label (no column of its own) can
|
|
133
|
+
// still collide.
|
|
134
|
+
...isColumnBacked(f) && { ownColumn: f.db_column.column_name }
|
|
135
|
+
})),
|
|
136
|
+
droppedColumns,
|
|
137
|
+
droppedLabels
|
|
112
138
|
);
|
|
113
139
|
}
|
|
114
140
|
var init_field_keys = __esm({
|
|
@@ -287,9 +313,8 @@ function groupBy(items, key) {
|
|
|
287
313
|
}
|
|
288
314
|
return result;
|
|
289
315
|
}
|
|
290
|
-
function needsFkResolution(row, fkColumn
|
|
291
|
-
|
|
292
|
-
const current = row[fieldName];
|
|
316
|
+
function needsFkResolution(row, fkColumn) {
|
|
317
|
+
const current = row[fkColumn];
|
|
293
318
|
return !(typeof current === "object" && current !== null);
|
|
294
319
|
}
|
|
295
320
|
async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOnly = false) {
|
|
@@ -306,15 +331,11 @@ async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOn
|
|
|
306
331
|
row[fieldName] = byParent[row["id"]] ?? [];
|
|
307
332
|
}
|
|
308
333
|
} else if (rel.type === "reference") {
|
|
309
|
-
const
|
|
310
|
-
const pending = batch.filter((r) => needsFkResolution(r, rel.fk_column, fieldName));
|
|
334
|
+
const pending = batch.filter((r) => needsFkResolution(r, rel.fk_column));
|
|
311
335
|
if (pending.length === 0) return;
|
|
312
336
|
const fkValues = pending.map((r) => r[rel.fk_column]).filter(Boolean);
|
|
313
337
|
if (fkValues.length === 0) {
|
|
314
|
-
for (const row of pending)
|
|
315
|
-
row[fieldName] = null;
|
|
316
|
-
if (dropFk) delete row[rel.fk_column];
|
|
317
|
-
}
|
|
338
|
+
for (const row of pending) row[rel.fk_column] = null;
|
|
318
339
|
return;
|
|
319
340
|
}
|
|
320
341
|
const unique = [...new Set(fkValues)];
|
|
@@ -331,8 +352,7 @@ async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOn
|
|
|
331
352
|
}
|
|
332
353
|
for (const row of pending) {
|
|
333
354
|
const fkVal = row[rel.fk_column];
|
|
334
|
-
row[
|
|
335
|
-
if (dropFk) delete row[rel.fk_column];
|
|
355
|
+
row[rel.fk_column] = fkVal ? cache.get(`${rel.table}:${fkVal}`) ?? null : null;
|
|
336
356
|
}
|
|
337
357
|
} else if (rel.type === "junction") {
|
|
338
358
|
const parentIds = batch.map((r) => r["id"]);
|
|
@@ -360,15 +380,11 @@ async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOn
|
|
|
360
380
|
row[fieldName] = jEntries.map((jr) => cache.get(`${rel.table}:${jr[rel.right_column]}`)).filter(Boolean);
|
|
361
381
|
}
|
|
362
382
|
} else if (rel.type === "media") {
|
|
363
|
-
const
|
|
364
|
-
const pending = batch.filter((r) => needsFkResolution(r, rel.fk_column, fieldName));
|
|
383
|
+
const pending = batch.filter((r) => needsFkResolution(r, rel.fk_column));
|
|
365
384
|
if (pending.length === 0) return;
|
|
366
385
|
const fkValues = pending.map((r) => r[rel.fk_column]).filter(Boolean);
|
|
367
386
|
if (fkValues.length === 0) {
|
|
368
|
-
for (const row of pending)
|
|
369
|
-
row[fieldName] = null;
|
|
370
|
-
if (dropFk) delete row[rel.fk_column];
|
|
371
|
-
}
|
|
387
|
+
for (const row of pending) row[rel.fk_column] = null;
|
|
372
388
|
return;
|
|
373
389
|
}
|
|
374
390
|
const unique = [...new Set(fkValues)];
|
|
@@ -382,8 +398,7 @@ async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOn
|
|
|
382
398
|
}
|
|
383
399
|
for (const row of pending) {
|
|
384
400
|
const fkVal = row[rel.fk_column];
|
|
385
|
-
row[
|
|
386
|
-
if (dropFk) delete row[rel.fk_column];
|
|
401
|
+
row[rel.fk_column] = fkVal ? cache.get(`media:${fkVal}`) ?? null : null;
|
|
387
402
|
}
|
|
388
403
|
}
|
|
389
404
|
}
|
|
@@ -537,10 +552,13 @@ var init_naming = __esm({
|
|
|
537
552
|
});
|
|
538
553
|
|
|
539
554
|
// src/graphql/filters.ts
|
|
540
|
-
function buildSortFieldEnum(typeName) {
|
|
555
|
+
function buildSortFieldEnum(typeName, fieldKeys) {
|
|
556
|
+
const resolvable = SORTABLE.filter(
|
|
557
|
+
(s) => s.gql === "createdAt" || s.gql === "updatedAt" || fieldKeys === void 0 || fieldKeys.columnFor(s.value) !== void 0
|
|
558
|
+
);
|
|
541
559
|
return new import_graphql3.GraphQLEnumType({
|
|
542
560
|
name: `${typeName}SortField`,
|
|
543
|
-
values: Object.fromEntries(
|
|
561
|
+
values: Object.fromEntries(resolvable.map((s) => [s.gql, { value: s.value }]))
|
|
544
562
|
});
|
|
545
563
|
}
|
|
546
564
|
function comparableFilterInput(name, scalar) {
|
|
@@ -575,18 +593,19 @@ function filterInputForField(fieldType) {
|
|
|
575
593
|
if (scalar === null) return null;
|
|
576
594
|
return StringFilter;
|
|
577
595
|
}
|
|
578
|
-
function buildFilterInputType(type) {
|
|
596
|
+
function buildFilterInputType(type, viewFields) {
|
|
579
597
|
const fields = {};
|
|
580
598
|
fields["id"] = { type: IDFilter };
|
|
581
599
|
fields["published"] = { type: BooleanFilter };
|
|
582
600
|
fields["createdAt"] = { type: DateTimeFilter };
|
|
583
601
|
fields["updatedAt"] = { type: DateTimeFilter };
|
|
584
602
|
if (type.schema_type === "content-type") fields["slug"] = { type: StringFilter };
|
|
585
|
-
|
|
603
|
+
const entries = viewFields !== void 0 ? viewFields.map((v) => ({ field: v.field, exposedAs: v.exposedAs })) : type.fields.filter((f) => f.removed !== true).map((f) => ({ field: f, exposedAs: f.name }));
|
|
604
|
+
for (const { field: f, exposedAs } of entries) {
|
|
586
605
|
if (f.field_type === "programmatic" || f.field_type === "paragraph") continue;
|
|
587
606
|
if (f.field_type === "image" || f.field_type === "video" || f.field_type === "file") continue;
|
|
588
607
|
const input = filterInputForField(f.field_type);
|
|
589
|
-
if (input) fields[buildFieldNameMap([
|
|
608
|
+
if (input) fields[buildFieldNameMap([exposedAs]).toGraphql(exposedAs)] = { type: input };
|
|
590
609
|
}
|
|
591
610
|
if (Object.keys(fields).length === 0) return null;
|
|
592
611
|
return new import_graphql3.GraphQLInputObjectType({ name: `${graphqlTypeName(type.name)}Filter`, fields });
|
|
@@ -649,12 +668,14 @@ function codeError2(code, message) {
|
|
|
649
668
|
function isPublished2(item) {
|
|
650
669
|
return !!item && item["published"] === true;
|
|
651
670
|
}
|
|
652
|
-
function resolveFieldValue(field, row) {
|
|
671
|
+
function resolveFieldValue(field, row, fallback) {
|
|
653
672
|
const key = isColumnBacked(field) ? field.db_column.column_name : field.name;
|
|
654
|
-
|
|
673
|
+
const value = row[key];
|
|
674
|
+
if ((value === null || value === void 0) && fallback !== void 0) return fallback;
|
|
675
|
+
return value ?? null;
|
|
655
676
|
}
|
|
656
|
-
function scalarFieldResolver(field) {
|
|
657
|
-
return (parent) => resolveFieldValue(field, parent);
|
|
677
|
+
function scalarFieldResolver(field, fallback) {
|
|
678
|
+
return (parent) => resolveFieldValue(field, parent, fallback);
|
|
658
679
|
}
|
|
659
680
|
function relationFieldResolver(typeName, schemaFieldName) {
|
|
660
681
|
return (parent, _args, ctx) => ctx.loaders.load(typeName, schemaFieldName, parent);
|
|
@@ -732,7 +753,7 @@ var init_resolvers = __esm({
|
|
|
732
753
|
});
|
|
733
754
|
|
|
734
755
|
// src/graphql/schema.ts
|
|
735
|
-
function buildGraphQLSchema(registry, fieldKeyMaps = {}) {
|
|
756
|
+
function buildGraphQLSchema(registry, fieldKeyMaps = {}, view, programmaticKeyMaps = fieldKeyMaps) {
|
|
736
757
|
const objectTypes = /* @__PURE__ */ new Map();
|
|
737
758
|
const enumTypes = /* @__PURE__ */ new Map();
|
|
738
759
|
for (const [name, enumType] of Object.entries(registry.enum_types)) {
|
|
@@ -751,14 +772,14 @@ function buildGraphQLSchema(registry, fieldKeyMaps = {}) {
|
|
|
751
772
|
);
|
|
752
773
|
}
|
|
753
774
|
}
|
|
754
|
-
function outputTypeForField(field) {
|
|
775
|
+
function outputTypeForField(field, required) {
|
|
755
776
|
const scalar = scalarOutputType(field.field_type);
|
|
756
|
-
if (scalar) return
|
|
777
|
+
if (scalar) return required ? new import_graphql5.GraphQLNonNull(scalar) : scalar;
|
|
757
778
|
if (field.field_type === "enum") {
|
|
758
779
|
const ref = field.ui_component.component === "select" ? field.ui_component.enum_ref : void 0;
|
|
759
780
|
const et = ref ? enumTypes.get(ref) : void 0;
|
|
760
781
|
const t = et ?? import_graphql5.GraphQLString;
|
|
761
|
-
return
|
|
782
|
+
return required ? new import_graphql5.GraphQLNonNull(t) : t;
|
|
762
783
|
}
|
|
763
784
|
if (field.field_type === "image" || field.field_type === "video" || field.field_type === "file") {
|
|
764
785
|
return MEDIA;
|
|
@@ -788,8 +809,17 @@ function buildGraphQLSchema(registry, fieldKeyMaps = {}) {
|
|
|
788
809
|
}
|
|
789
810
|
return import_graphql5.GraphQLString;
|
|
790
811
|
}
|
|
812
|
+
function exposedFields(type) {
|
|
813
|
+
return type.fields.filter((f) => f.removed !== true);
|
|
814
|
+
}
|
|
815
|
+
function viewFieldsFor(machineName, type) {
|
|
816
|
+
return view?.[machineName]?.fields ?? exposedFields(type).map((f) => ({ field: f, exposedAs: f.name, required: f.required }));
|
|
817
|
+
}
|
|
791
818
|
function buildObjectType(machineName, type) {
|
|
792
|
-
const
|
|
819
|
+
const visible = viewFieldsFor(machineName, type);
|
|
820
|
+
const mediaFieldNames = type.fields.filter(
|
|
821
|
+
(f) => f.removed !== true && (f.field_type === "image" || f.field_type === "video" || f.field_type === "file")
|
|
822
|
+
).map((f) => f.name);
|
|
793
823
|
return new import_graphql5.GraphQLObjectType({
|
|
794
824
|
name: graphqlTypeName(machineName),
|
|
795
825
|
fields: () => {
|
|
@@ -803,23 +833,25 @@ function buildGraphQLSchema(registry, fieldKeyMaps = {}) {
|
|
|
803
833
|
}
|
|
804
834
|
fields["createdAt"] = { type: new import_graphql5.GraphQLNonNull(DateTimeScalar), resolve: (p) => p["created_at"] };
|
|
805
835
|
fields["updatedAt"] = { type: new import_graphql5.GraphQLNonNull(DateTimeScalar), resolve: (p) => p["updated_at"] };
|
|
806
|
-
for (const
|
|
807
|
-
const
|
|
808
|
-
const
|
|
836
|
+
for (const vf of visible) {
|
|
837
|
+
const field = vf.field;
|
|
838
|
+
const gqlName = toCamelCase(vf.exposedAs);
|
|
839
|
+
const outType = outputTypeForField(field, vf.required);
|
|
809
840
|
let resolve;
|
|
810
841
|
if (field.field_type === "programmatic") {
|
|
811
|
-
resolve = programmaticFieldResolver(
|
|
812
|
-
machineName,
|
|
813
|
-
field.name,
|
|
814
|
-
mediaFieldNames,
|
|
815
|
-
fieldKeyMaps[machineName]
|
|
816
|
-
);
|
|
842
|
+
resolve = programmaticFieldResolver(machineName, field.name, mediaFieldNames, programmaticKeyMaps[machineName]);
|
|
817
843
|
} else if (field.field_type === "reference" || field.field_type === "paragraph" || field.field_type === "image" || field.field_type === "video" || field.field_type === "file") {
|
|
818
844
|
resolve = relationFieldResolver(machineName, field.name);
|
|
819
845
|
} else {
|
|
820
|
-
resolve = scalarFieldResolver(field);
|
|
846
|
+
resolve = scalarFieldResolver(field, vf.fallback);
|
|
821
847
|
}
|
|
822
|
-
fields[gqlName] = {
|
|
848
|
+
fields[gqlName] = {
|
|
849
|
+
type: outType,
|
|
850
|
+
resolve,
|
|
851
|
+
// Conditional spread, not `deprecationReason: vf.deprecationReason`
|
|
852
|
+
// — exactOptionalPropertyTypes forbids present-and-undefined.
|
|
853
|
+
...vf.deprecationReason !== void 0 && { deprecationReason: vf.deprecationReason }
|
|
854
|
+
};
|
|
823
855
|
}
|
|
824
856
|
return fields;
|
|
825
857
|
}
|
|
@@ -851,7 +883,11 @@ function buildGraphQLSchema(registry, fieldKeyMaps = {}) {
|
|
|
851
883
|
const queryFields = {};
|
|
852
884
|
for (const [name, ct] of Object.entries(registry.content_types)) {
|
|
853
885
|
const objType = objectTypes.get(name);
|
|
854
|
-
const
|
|
886
|
+
const visible = viewFieldsFor(name, ct);
|
|
887
|
+
const nameMap = buildFieldNameMap([
|
|
888
|
+
...visible.map((v) => v.exposedAs),
|
|
889
|
+
...ct.system_fields.map((s) => s.name)
|
|
890
|
+
]);
|
|
855
891
|
if (ct.only_one) {
|
|
856
892
|
queryFields[singleQueryName(name)] = { type: objType, resolve: singletonResolver(name) };
|
|
857
893
|
continue;
|
|
@@ -863,13 +899,13 @@ function buildGraphQLSchema(registry, fieldKeyMaps = {}) {
|
|
|
863
899
|
meta: { type: new import_graphql5.GraphQLNonNull(PAGE_META), resolve: (r) => r.meta }
|
|
864
900
|
}
|
|
865
901
|
});
|
|
866
|
-
const filterType = buildFilterInputType(ct);
|
|
902
|
+
const filterType = buildFilterInputType(ct, visible);
|
|
867
903
|
queryFields[collectionQueryName(name)] = {
|
|
868
904
|
type: new import_graphql5.GraphQLNonNull(listType),
|
|
869
905
|
args: {
|
|
870
906
|
page: { type: import_graphql5.GraphQLInt },
|
|
871
907
|
perPage: { type: import_graphql5.GraphQLInt },
|
|
872
|
-
sortBy: { type: buildSortFieldEnum(graphqlTypeName(name)) },
|
|
908
|
+
sortBy: { type: buildSortFieldEnum(graphqlTypeName(name), fieldKeyMaps[name]) },
|
|
873
909
|
sortOrder: { type: SortOrderEnum },
|
|
874
910
|
...filterType ? { filter: { type: filterType } } : {}
|
|
875
911
|
},
|
|
@@ -893,7 +929,11 @@ function buildGraphQLSchema(registry, fieldKeyMaps = {}) {
|
|
|
893
929
|
queryFields[collectionQueryName(name)] = {
|
|
894
930
|
type: new import_graphql5.GraphQLNonNull(listType),
|
|
895
931
|
args: { page: { type: import_graphql5.GraphQLInt }, perPage: { type: import_graphql5.GraphQLInt } },
|
|
896
|
-
resolve: collectionResolver(
|
|
932
|
+
resolve: collectionResolver(
|
|
933
|
+
name,
|
|
934
|
+
buildFieldNameMap(viewFieldsFor(name, tt).map((v) => v.exposedAs)),
|
|
935
|
+
fieldKeyMaps[name]
|
|
936
|
+
)
|
|
897
937
|
};
|
|
898
938
|
queryFields[singleQueryName(name)] = {
|
|
899
939
|
type: objType,
|
|
@@ -943,6 +983,76 @@ var init_schema = __esm({
|
|
|
943
983
|
}
|
|
944
984
|
});
|
|
945
985
|
|
|
986
|
+
// src/graphql/version-view.ts
|
|
987
|
+
function byColumn(fields) {
|
|
988
|
+
const out = /* @__PURE__ */ new Map();
|
|
989
|
+
for (const f of fields) {
|
|
990
|
+
if (!isColumnBacked(f)) continue;
|
|
991
|
+
out.set(f.db_column.column_name, f);
|
|
992
|
+
}
|
|
993
|
+
return out;
|
|
994
|
+
}
|
|
995
|
+
function deprecationFor(field, currentLabels, currentVersion) {
|
|
996
|
+
const currentLabel = currentLabels.get(field.column_name);
|
|
997
|
+
if (currentLabel === void 0) {
|
|
998
|
+
return `Removed in ${currentVersion}; column retained while this version is live.`;
|
|
999
|
+
}
|
|
1000
|
+
if (currentLabel !== field.exposed_as) {
|
|
1001
|
+
return `Renamed to '${currentLabel}' in ${currentVersion}.`;
|
|
1002
|
+
}
|
|
1003
|
+
return void 0;
|
|
1004
|
+
}
|
|
1005
|
+
function buildVersionView(input) {
|
|
1006
|
+
const { registry, projection, currentProjection, currentVersion } = input;
|
|
1007
|
+
const out = {};
|
|
1008
|
+
const sources = [
|
|
1009
|
+
registry.content_types,
|
|
1010
|
+
registry.taxonomy_types,
|
|
1011
|
+
registry.paragraph_types
|
|
1012
|
+
];
|
|
1013
|
+
for (const source of sources) {
|
|
1014
|
+
for (const [typeName, type] of Object.entries(source)) {
|
|
1015
|
+
const projectionType = projection?.types[typeName];
|
|
1016
|
+
if (projectionType === void 0) {
|
|
1017
|
+
out[typeName] = {
|
|
1018
|
+
fields: type.fields.filter((f) => f.removed !== true).map((f) => ({ field: f, exposedAs: f.name, required: f.required }))
|
|
1019
|
+
};
|
|
1020
|
+
continue;
|
|
1021
|
+
}
|
|
1022
|
+
const columns = byColumn(type.fields);
|
|
1023
|
+
const currentLabels = /* @__PURE__ */ new Map();
|
|
1024
|
+
for (const f of currentProjection?.types[typeName]?.fields ?? []) {
|
|
1025
|
+
currentLabels.set(f.column_name, f.exposed_as);
|
|
1026
|
+
}
|
|
1027
|
+
const fields = [];
|
|
1028
|
+
for (const pf of projectionType.fields) {
|
|
1029
|
+
const field = columns.get(pf.column_name);
|
|
1030
|
+
if (field === void 0) continue;
|
|
1031
|
+
const reason = deprecationFor(pf, currentLabels, currentVersion);
|
|
1032
|
+
fields.push({
|
|
1033
|
+
field,
|
|
1034
|
+
exposedAs: pf.exposed_as,
|
|
1035
|
+
required: pf.required,
|
|
1036
|
+
...pf.fallback !== void 0 && { fallback: pf.fallback },
|
|
1037
|
+
...reason !== void 0 && { deprecationReason: reason }
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
for (const f of type.fields) {
|
|
1041
|
+
if (isColumnBacked(f) || f.removed === true) continue;
|
|
1042
|
+
fields.push({ field: f, exposedAs: f.name, required: f.required });
|
|
1043
|
+
}
|
|
1044
|
+
out[typeName] = { fields };
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
return out;
|
|
1048
|
+
}
|
|
1049
|
+
var init_version_view = __esm({
|
|
1050
|
+
"src/graphql/version-view.ts"() {
|
|
1051
|
+
"use strict";
|
|
1052
|
+
init_field_keys();
|
|
1053
|
+
}
|
|
1054
|
+
});
|
|
1055
|
+
|
|
946
1056
|
// src/graphql/dataloaders.ts
|
|
947
1057
|
function createRelationLoaders(db, registry) {
|
|
948
1058
|
const relMaps = /* @__PURE__ */ new Map();
|
|
@@ -965,7 +1075,8 @@ function createRelationLoaders(db, registry) {
|
|
|
965
1075
|
async (parents) => {
|
|
966
1076
|
const rows = parents;
|
|
967
1077
|
await resolveRelationField(db, rows, fieldName, rel, cache, true);
|
|
968
|
-
|
|
1078
|
+
const resultKey = rel.type === "reference" || rel.type === "media" ? rel.fk_column : fieldName;
|
|
1079
|
+
return rows.map((r) => r[resultKey]);
|
|
969
1080
|
},
|
|
970
1081
|
// Batch within a tick; do not memoize by parent identity (rows are mutated
|
|
971
1082
|
// and may recur across nesting levels).
|
|
@@ -1035,15 +1146,29 @@ function isClientSafeGraphQLError(error) {
|
|
|
1035
1146
|
}
|
|
1036
1147
|
return false;
|
|
1037
1148
|
}
|
|
1038
|
-
function createGraphQLHandler(registry, repos, fieldKeyMaps, resolver, db, options) {
|
|
1039
|
-
|
|
1149
|
+
function createGraphQLHandler(registry, repos, fieldKeyMaps, resolver, db, options, versioning = {}) {
|
|
1150
|
+
let view;
|
|
1151
|
+
if (versioning.projection !== void 0) {
|
|
1152
|
+
if (versioning.currentVersion === void 0) {
|
|
1153
|
+
throw new Error(
|
|
1154
|
+
'createGraphQLHandler: currentVersion is required when a projection is supplied \u2014 deprecation reasons name it, and an empty one ships "Removed in ." into published SDL.'
|
|
1155
|
+
);
|
|
1156
|
+
}
|
|
1157
|
+
view = buildVersionView({
|
|
1158
|
+
registry,
|
|
1159
|
+
projection: versioning.projection,
|
|
1160
|
+
currentProjection: versioning.currentProjection,
|
|
1161
|
+
currentVersion: versioning.currentVersion
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
const schema = buildGraphQLSchema(registry, fieldKeyMaps, view, versioning.currentFieldKeyMaps ?? fieldKeyMaps);
|
|
1040
1165
|
const { plugins } = buildArmorPlugin({
|
|
1041
1166
|
maxDepth: options.maxDepth,
|
|
1042
1167
|
maxComplexity: options.maxComplexity
|
|
1043
1168
|
});
|
|
1044
1169
|
const yoga = (0, import_graphql_yoga.createYoga)({
|
|
1045
1170
|
schema,
|
|
1046
|
-
graphqlEndpoint: "/graphql",
|
|
1171
|
+
graphqlEndpoint: versioning.endpoint ?? "/graphql",
|
|
1047
1172
|
graphiql: options.graphiql,
|
|
1048
1173
|
landingPage: false,
|
|
1049
1174
|
maskedErrors: {
|
|
@@ -1071,6 +1196,7 @@ var init_handler = __esm({
|
|
|
1071
1196
|
"use strict";
|
|
1072
1197
|
import_graphql_yoga = require("graphql-yoga");
|
|
1073
1198
|
init_schema();
|
|
1199
|
+
init_version_view();
|
|
1074
1200
|
init_dataloaders();
|
|
1075
1201
|
init_security();
|
|
1076
1202
|
}
|
|
@@ -1180,7 +1306,7 @@ function createSecurityHeadersMiddleware(options = {}) {
|
|
|
1180
1306
|
c.res.headers.set("X-Content-Type-Options", "nosniff");
|
|
1181
1307
|
c.res.headers.set("X-Frame-Options", "DENY");
|
|
1182
1308
|
c.res.headers.set("Referrer-Policy", "no-referrer");
|
|
1183
|
-
const isGraphiql = graphiqlPath !== void 0 && c.req.path === graphiqlPath;
|
|
1309
|
+
const isGraphiql = graphiqlPath !== void 0 && (c.req.path === graphiqlPath || c.req.path.startsWith(`${graphiqlPath}/`));
|
|
1184
1310
|
c.res.headers.set("Content-Security-Policy", isGraphiql ? graphiqlCsp : csp);
|
|
1185
1311
|
};
|
|
1186
1312
|
}
|
|
@@ -1691,18 +1817,21 @@ function createProgrammaticResolver(resolvers, options) {
|
|
|
1691
1817
|
}
|
|
1692
1818
|
|
|
1693
1819
|
// src/projector.ts
|
|
1820
|
+
init_field_keys();
|
|
1694
1821
|
function isPlainRow(v) {
|
|
1695
1822
|
return typeof v === "object" && v !== null && !Array.isArray(v) && !(v instanceof Date);
|
|
1696
1823
|
}
|
|
1697
1824
|
var MEDIA_FIELD_TYPES = /* @__PURE__ */ new Set(["image", "video", "file"]);
|
|
1698
|
-
function nestedTargets(fields) {
|
|
1825
|
+
function nestedTargets(fields, map) {
|
|
1699
1826
|
const out = [];
|
|
1700
1827
|
for (const f of fields) {
|
|
1701
1828
|
if (MEDIA_FIELD_TYPES.has(f.field_type)) continue;
|
|
1702
1829
|
if (f.field_type !== "paragraph" && f.field_type !== "reference") continue;
|
|
1703
1830
|
const ref = f.ui_component.ref;
|
|
1704
1831
|
if (!ref) continue;
|
|
1705
|
-
|
|
1832
|
+
const label = isColumnBacked(f) ? map.labelFor(f.db_column.column_name) : f.name;
|
|
1833
|
+
if (label === void 0) continue;
|
|
1834
|
+
out.push({ label, target: ref });
|
|
1706
1835
|
}
|
|
1707
1836
|
return out;
|
|
1708
1837
|
}
|
|
@@ -1720,7 +1849,7 @@ function buildProjectors(registry, fieldKeyMaps, fallbacks) {
|
|
|
1720
1849
|
const typeFallbacks = fallbacks?.[typeName];
|
|
1721
1850
|
projectors[typeName] = {
|
|
1722
1851
|
map,
|
|
1723
|
-
nested: nestedTargets(type.fields),
|
|
1852
|
+
nested: nestedTargets(type.fields, map),
|
|
1724
1853
|
// Spread rather than assign: with exactOptionalPropertyTypes, an optional
|
|
1725
1854
|
// property may be ABSENT but not present-and-undefined.
|
|
1726
1855
|
...typeFallbacks !== void 0 && { fallbacks: typeFallbacks }
|
|
@@ -1807,7 +1936,28 @@ function parseFilters(url, validFields, columnFor) {
|
|
|
1807
1936
|
function isPublished(item) {
|
|
1808
1937
|
return item["published"] === true;
|
|
1809
1938
|
}
|
|
1810
|
-
function registerPublicContentRoutes(app, registry, repos, projectors, paths, listRateLimit, resolver) {
|
|
1939
|
+
function registerPublicContentRoutes(app, registry, repos, projectors, paths, listRateLimit, resolver, resolverProjectors) {
|
|
1940
|
+
const forResolver = resolverProjectors ?? projectors;
|
|
1941
|
+
function pickProgrammatic(resolved, names) {
|
|
1942
|
+
const out = {};
|
|
1943
|
+
for (const name of names) if (name in resolved) out[name] = resolved[name];
|
|
1944
|
+
return out;
|
|
1945
|
+
}
|
|
1946
|
+
async function respondItem(row, typeName, programmatic) {
|
|
1947
|
+
const data = projectRow(row, typeName, projectors);
|
|
1948
|
+
if (!resolver?.hasSchema(typeName)) return data;
|
|
1949
|
+
const resolved = await resolver.resolveItem(typeName, projectRow(row, typeName, forResolver));
|
|
1950
|
+
return { ...data, ...pickProgrammatic(resolved, programmatic) };
|
|
1951
|
+
}
|
|
1952
|
+
async function respondList(rows, typeName, programmatic) {
|
|
1953
|
+
const data = rows.map((row) => projectRow(row, typeName, projectors));
|
|
1954
|
+
if (!resolver?.hasSchema(typeName)) return data;
|
|
1955
|
+
const resolved = await resolver.resolveList(
|
|
1956
|
+
typeName,
|
|
1957
|
+
rows.map((row) => projectRow(row, typeName, forResolver))
|
|
1958
|
+
);
|
|
1959
|
+
return data.map((d, i) => ({ ...d, ...pickProgrammatic(resolved[i], programmatic) }));
|
|
1960
|
+
}
|
|
1811
1961
|
function registerListRoute(path, handler) {
|
|
1812
1962
|
if (listRateLimit) {
|
|
1813
1963
|
app.get(path, listRateLimit, handler);
|
|
@@ -1831,18 +1981,29 @@ function registerPublicContentRoutes(app, registry, repos, projectors, paths, li
|
|
|
1831
1981
|
return c.json({ ok: true, data });
|
|
1832
1982
|
});
|
|
1833
1983
|
for (const [typeName, contentType] of Object.entries(registry.content_types)) {
|
|
1984
|
+
let translateInclude2 = function(requested) {
|
|
1985
|
+
const include = [];
|
|
1986
|
+
for (const field of requested) {
|
|
1987
|
+
const registryName = includeNames.get(field);
|
|
1988
|
+
if (registryName === void 0) return { ok: false, field };
|
|
1989
|
+
include.push(registryName);
|
|
1990
|
+
}
|
|
1991
|
+
return { ok: true, include };
|
|
1992
|
+
};
|
|
1993
|
+
var translateInclude = translateInclude2;
|
|
1834
1994
|
const basePath = contentType.default_base_path;
|
|
1835
1995
|
const repo = repos[typeName];
|
|
1836
1996
|
if (!repo) continue;
|
|
1837
1997
|
const fieldKeys = projectors[typeName].map;
|
|
1838
|
-
const
|
|
1998
|
+
const programmatic = contentType.fields.filter((f) => f.field_type === "programmatic").map((f) => f.name);
|
|
1999
|
+
const includeNames = /* @__PURE__ */ new Map();
|
|
1839
2000
|
for (const f of contentType.fields) {
|
|
1840
2001
|
if (!RELATION_FIELD_TYPES.has(f.field_type)) continue;
|
|
1841
2002
|
if (isColumnBacked(f)) {
|
|
1842
2003
|
const label = fieldKeys.labelFor(f.db_column.column_name);
|
|
1843
|
-
if (label !== void 0)
|
|
2004
|
+
if (label !== void 0) includeNames.set(label, f.name);
|
|
1844
2005
|
} else {
|
|
1845
|
-
|
|
2006
|
+
includeNames.set(f.name, f.name);
|
|
1846
2007
|
}
|
|
1847
2008
|
}
|
|
1848
2009
|
const filterableFieldNames = /* @__PURE__ */ new Set([
|
|
@@ -1861,8 +2022,11 @@ function registerPublicContentRoutes(app, registry, repos, projectors, paths, li
|
|
|
1861
2022
|
404
|
|
1862
2023
|
);
|
|
1863
2024
|
}
|
|
1864
|
-
|
|
1865
|
-
|
|
2025
|
+
const data = await respondItem(
|
|
2026
|
+
result.data[0],
|
|
2027
|
+
typeName,
|
|
2028
|
+
programmatic
|
|
2029
|
+
);
|
|
1866
2030
|
return c.json({ ok: true, data });
|
|
1867
2031
|
});
|
|
1868
2032
|
} else {
|
|
@@ -1919,21 +2083,20 @@ function registerPublicContentRoutes(app, registry, repos, projectors, paths, li
|
|
|
1919
2083
|
400
|
|
1920
2084
|
);
|
|
1921
2085
|
}
|
|
1922
|
-
const
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
);
|
|
1935
|
-
}
|
|
2086
|
+
const translated = translateInclude2(parseInclude(c.req.query("include")));
|
|
2087
|
+
if (!translated.ok) {
|
|
2088
|
+
return c.json(
|
|
2089
|
+
{
|
|
2090
|
+
ok: false,
|
|
2091
|
+
error: {
|
|
2092
|
+
code: "INVALID_INCLUDE_FIELD",
|
|
2093
|
+
message: `'${translated.field}' is not a valid relation field`
|
|
2094
|
+
}
|
|
2095
|
+
},
|
|
2096
|
+
400
|
|
2097
|
+
);
|
|
1936
2098
|
}
|
|
2099
|
+
const include = translated.include;
|
|
1937
2100
|
const sortColumn = fieldKeys.labels.includes(sortBy) ? fieldKeys.columnFor(sortBy) : sortBy;
|
|
1938
2101
|
const result = await repo.findMany({
|
|
1939
2102
|
published_only: true,
|
|
@@ -1944,29 +2107,25 @@ function registerPublicContentRoutes(app, registry, repos, projectors, paths, li
|
|
|
1944
2107
|
filters: filtersResult.filters,
|
|
1945
2108
|
include
|
|
1946
2109
|
});
|
|
1947
|
-
const
|
|
1948
|
-
(row) => projectRow(row, typeName, projectors)
|
|
1949
|
-
);
|
|
1950
|
-
const data = resolver?.hasSchema(typeName) ? await resolver.resolveList(typeName, labeled) : labeled;
|
|
2110
|
+
const data = await respondList(result.data, typeName, programmatic);
|
|
1951
2111
|
return c.json({ ...result, data });
|
|
1952
2112
|
});
|
|
1953
2113
|
app.get(paths.item(basePath), async (c) => {
|
|
1954
2114
|
const slug = c.req.param("slug");
|
|
1955
|
-
const
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
);
|
|
1968
|
-
}
|
|
2115
|
+
const translated = translateInclude2(parseInclude(c.req.query("include")));
|
|
2116
|
+
if (!translated.ok) {
|
|
2117
|
+
return c.json(
|
|
2118
|
+
{
|
|
2119
|
+
ok: false,
|
|
2120
|
+
error: {
|
|
2121
|
+
code: "INVALID_INCLUDE_FIELD",
|
|
2122
|
+
message: `'${translated.field}' is not a valid relation field`
|
|
2123
|
+
}
|
|
2124
|
+
},
|
|
2125
|
+
400
|
|
2126
|
+
);
|
|
1969
2127
|
}
|
|
2128
|
+
const include = translated.include;
|
|
1970
2129
|
const item = await repo.findBySlug(slug, include);
|
|
1971
2130
|
if (!item || !isPublished(item)) {
|
|
1972
2131
|
return c.json(
|
|
@@ -1977,15 +2136,15 @@ function registerPublicContentRoutes(app, registry, repos, projectors, paths, li
|
|
|
1977
2136
|
404
|
|
1978
2137
|
);
|
|
1979
2138
|
}
|
|
1980
|
-
|
|
1981
|
-
if (resolver?.hasSchema(typeName)) data = await resolver.resolveItem(typeName, data);
|
|
2139
|
+
const data = await respondItem(item, typeName, programmatic);
|
|
1982
2140
|
return c.json({ ok: true, data });
|
|
1983
2141
|
});
|
|
1984
2142
|
}
|
|
1985
2143
|
}
|
|
1986
|
-
for (const [typeName] of Object.entries(registry.taxonomy_types)) {
|
|
2144
|
+
for (const [typeName, taxonomyType] of Object.entries(registry.taxonomy_types)) {
|
|
1987
2145
|
const repo = repos[typeName];
|
|
1988
2146
|
if (!repo) continue;
|
|
2147
|
+
const programmatic = taxonomyType.fields.filter((f) => f.field_type === "programmatic").map((f) => f.name);
|
|
1989
2148
|
registerListRoute(paths.taxonomyCollection(typeName), async (c) => {
|
|
1990
2149
|
const pagination = parsePagination(c.req.query("page"), c.req.query("per_page"));
|
|
1991
2150
|
if (!pagination.ok) {
|
|
@@ -2005,10 +2164,7 @@ function registerPublicContentRoutes(app, registry, repos, projectors, paths, li
|
|
|
2005
2164
|
page: pagination.page,
|
|
2006
2165
|
per_page: pagination.per_page
|
|
2007
2166
|
});
|
|
2008
|
-
const
|
|
2009
|
-
(row) => projectRow(row, typeName, projectors)
|
|
2010
|
-
);
|
|
2011
|
-
const data = resolver?.hasSchema(typeName) ? await resolver.resolveList(typeName, labeled) : labeled;
|
|
2167
|
+
const data = await respondList(result.data, typeName, programmatic);
|
|
2012
2168
|
return c.json({ ...result, data });
|
|
2013
2169
|
});
|
|
2014
2170
|
app.get(paths.taxonomyItem(typeName), async (c) => {
|
|
@@ -2023,8 +2179,7 @@ function registerPublicContentRoutes(app, registry, repos, projectors, paths, li
|
|
|
2023
2179
|
404
|
|
2024
2180
|
);
|
|
2025
2181
|
}
|
|
2026
|
-
|
|
2027
|
-
if (resolver?.hasSchema(typeName)) data = await resolver.resolveItem(typeName, data);
|
|
2182
|
+
const data = await respondItem(item, typeName, programmatic);
|
|
2028
2183
|
return c.json({ ok: true, data });
|
|
2029
2184
|
});
|
|
2030
2185
|
}
|
|
@@ -3816,7 +3971,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3816
3971
|
var import_drizzle_orm6 = require("drizzle-orm");
|
|
3817
3972
|
var API_VERSION = (() => {
|
|
3818
3973
|
try {
|
|
3819
|
-
return "0.
|
|
3974
|
+
return "0.7.0";
|
|
3820
3975
|
} catch {
|
|
3821
3976
|
return "0.0.0";
|
|
3822
3977
|
}
|
|
@@ -4414,20 +4569,6 @@ function createCmsApp(options) {
|
|
|
4414
4569
|
sortableColumns
|
|
4415
4570
|
});
|
|
4416
4571
|
};
|
|
4417
|
-
const graphqlRepos = Object.fromEntries([
|
|
4418
|
-
...Object.entries(registry.content_types).map(([typeName, ct]) => [
|
|
4419
|
-
typeName,
|
|
4420
|
-
createDrizzleContentRepository(db, ct.db.table_name, {
|
|
4421
|
-
sortableColumns: sortableColumnsFor(typeName)
|
|
4422
|
-
})
|
|
4423
|
-
]),
|
|
4424
|
-
...Object.entries(registry.taxonomy_types).map(([typeName, tt]) => [
|
|
4425
|
-
typeName,
|
|
4426
|
-
createDrizzleContentRepository(db, tt.db.table_name, {
|
|
4427
|
-
sortableColumns: sortableColumnsFor(typeName)
|
|
4428
|
-
})
|
|
4429
|
-
])
|
|
4430
|
-
]);
|
|
4431
4572
|
registerAuthRoutes(app, db);
|
|
4432
4573
|
app.use("/admin/api/*", authMiddleware);
|
|
4433
4574
|
app.use("/admin/api/*", mustChangePasswordCheck);
|
|
@@ -4494,7 +4635,7 @@ function createCmsApp(options) {
|
|
|
4494
4635
|
app.use(path, attachDeprecationHeaders);
|
|
4495
4636
|
}
|
|
4496
4637
|
}
|
|
4497
|
-
registerPublicContentRoutes(app, registry, surface.repos, surface.projectors, surface.paths, listRateLimit, programmaticResolver);
|
|
4638
|
+
registerPublicContentRoutes(app, registry, surface.repos, surface.projectors, surface.paths, listRateLimit, programmaticResolver, projectors);
|
|
4498
4639
|
}
|
|
4499
4640
|
if (options.versions !== void 0) {
|
|
4500
4641
|
app.all(`${prefix}/:version/*`, async (c, next) => {
|
|
@@ -4525,31 +4666,108 @@ function createCmsApp(options) {
|
|
|
4525
4666
|
registerPublicMediaRoutes(app, mediaRepo, publicPaths, listRateLimit);
|
|
4526
4667
|
if (options.graphql?.enabled) {
|
|
4527
4668
|
const gqlOptions = options.graphql;
|
|
4528
|
-
|
|
4529
|
-
|
|
4669
|
+
const makeGraphqlRepo = (_typeName, tableName, sortableColumns) => createDrizzleContentRepository(db, tableName, { sortableColumns });
|
|
4670
|
+
const gqlPasses = [
|
|
4671
|
+
...options.versions !== void 0 ? model.live.map((v) => ({ version: v, projectionVersion: v })) : [],
|
|
4672
|
+
{ version: null, projectionVersion: model.current }
|
|
4673
|
+
];
|
|
4674
|
+
const mounts = gqlPasses.map((pass) => ({
|
|
4675
|
+
path: pass.version === null ? "/graphql" : `/graphql/${pass.version}`,
|
|
4676
|
+
version: pass.version,
|
|
4677
|
+
handler: null,
|
|
4678
|
+
error: null
|
|
4679
|
+
}));
|
|
4530
4680
|
const ready = Promise.resolve().then(() => (init_handler(), handler_exports)).then(({ createGraphQLHandler: createGraphQLHandler2 }) => {
|
|
4531
|
-
|
|
4681
|
+
gqlPasses.forEach((pass, i) => {
|
|
4682
|
+
const mount = mounts[i];
|
|
4683
|
+
try {
|
|
4684
|
+
const surface = buildVersionSurface({
|
|
4685
|
+
...pass,
|
|
4686
|
+
prefix,
|
|
4687
|
+
registry,
|
|
4688
|
+
model,
|
|
4689
|
+
makeRepo: makeGraphqlRepo
|
|
4690
|
+
});
|
|
4691
|
+
const projection = model.projections[pass.projectionVersion];
|
|
4692
|
+
const currentProjection = model.projections[model.current];
|
|
4693
|
+
mount.handler = createGraphQLHandler2(
|
|
4694
|
+
registry,
|
|
4695
|
+
surface.repos,
|
|
4696
|
+
surface.fieldKeyMaps,
|
|
4697
|
+
programmaticResolver,
|
|
4698
|
+
db,
|
|
4699
|
+
gqlOptions,
|
|
4700
|
+
{
|
|
4701
|
+
endpoint: mount.path,
|
|
4702
|
+
currentVersion: model.current,
|
|
4703
|
+
...projection !== void 0 && { projection },
|
|
4704
|
+
...currentProjection !== void 0 && { currentProjection },
|
|
4705
|
+
currentFieldKeyMaps: fieldKeyMaps
|
|
4706
|
+
}
|
|
4707
|
+
);
|
|
4708
|
+
} catch (err) {
|
|
4709
|
+
mount.error = err;
|
|
4710
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4711
|
+
process.stderr.write(
|
|
4712
|
+
`\u2717 GraphQL schema failed to initialize for ${mount.path}; it will return 500. ${message}
|
|
4713
|
+
`
|
|
4714
|
+
);
|
|
4715
|
+
}
|
|
4716
|
+
});
|
|
4532
4717
|
}).catch((err) => {
|
|
4533
|
-
|
|
4718
|
+
for (const m of mounts) m.error = err;
|
|
4534
4719
|
const message = err instanceof Error ? err.message : String(err);
|
|
4535
|
-
|
|
4720
|
+
const paths = mounts.map((m) => m.path).join(", ");
|
|
4721
|
+
process.stderr.write(`\u2717 GraphQL module failed to load; ${paths} will return 500. ${message}
|
|
4536
4722
|
`);
|
|
4537
4723
|
});
|
|
4538
|
-
const
|
|
4539
|
-
if (!
|
|
4540
|
-
if (!
|
|
4724
|
+
const invokeFor = (mount) => async (c) => {
|
|
4725
|
+
if (!mount.handler && !mount.error) await ready;
|
|
4726
|
+
if (!mount.handler) {
|
|
4541
4727
|
return c.json(
|
|
4542
4728
|
{ ok: false, error: { code: "GRAPHQL_INIT_FAILED", message: "GraphQL schema failed to initialize" } },
|
|
4543
4729
|
500
|
|
4544
4730
|
);
|
|
4545
4731
|
}
|
|
4546
|
-
return
|
|
4732
|
+
return mount.handler(c, async () => {
|
|
4547
4733
|
});
|
|
4548
4734
|
};
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4735
|
+
for (const mount of mounts) {
|
|
4736
|
+
const headers = deprecationHeaders({
|
|
4737
|
+
requested: mount.version,
|
|
4738
|
+
model,
|
|
4739
|
+
successor: `/graphql/${model.current}`
|
|
4740
|
+
});
|
|
4741
|
+
if (headers === null) continue;
|
|
4742
|
+
const attach = async (c, next) => {
|
|
4743
|
+
await next();
|
|
4744
|
+
for (const [key, value] of Object.entries(headers)) c.res.headers.set(key, value);
|
|
4745
|
+
};
|
|
4746
|
+
app.use(mount.path, attach);
|
|
4747
|
+
}
|
|
4748
|
+
for (const mount of mounts) {
|
|
4749
|
+
if (listRateLimit) app.all(mount.path, listRateLimit, invokeFor(mount));
|
|
4750
|
+
else app.all(mount.path, invokeFor(mount));
|
|
4751
|
+
}
|
|
4752
|
+
if (options.versions !== void 0) {
|
|
4753
|
+
app.all("/graphql/:version", async (c, next) => {
|
|
4754
|
+
const segment = c.req.param("version");
|
|
4755
|
+
const kind = classifyVersion(segment, model);
|
|
4756
|
+
if (kind === "not-a-version" || kind === "live") return next();
|
|
4757
|
+
const live = model.live.join(", ");
|
|
4758
|
+
return c.json({
|
|
4759
|
+
errors: [
|
|
4760
|
+
{
|
|
4761
|
+
message: kind === "retired" ? `Version ${segment} is no longer served. Live versions: ${live}.` : `Version ${segment} does not exist. Live versions: ${live}.`,
|
|
4762
|
+
extensions: {
|
|
4763
|
+
code: kind === "retired" ? "VERSION_RETIRED" : "VERSION_UNKNOWN",
|
|
4764
|
+
current: model.current,
|
|
4765
|
+
live: model.live
|
|
4766
|
+
}
|
|
4767
|
+
}
|
|
4768
|
+
]
|
|
4769
|
+
});
|
|
4770
|
+
});
|
|
4553
4771
|
}
|
|
4554
4772
|
}
|
|
4555
4773
|
registerConfigRoute(
|