@bobbykim/manguito-cms-api 0.1.1 → 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 +9 -2
- package/dist/index.d.ts +9 -2
- package/dist/index.js +1121 -258
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +349 -341
- 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
package/dist/index.js
CHANGED
|
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
};
|
|
8
11
|
var __export = (target, all) => {
|
|
9
12
|
for (var name in all)
|
|
10
13
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -27,12 +30,868 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
30
|
));
|
|
28
31
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
32
|
|
|
33
|
+
// src/relations.ts
|
|
34
|
+
function quoteIdent(name) {
|
|
35
|
+
if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`);
|
|
36
|
+
return `"${name}"`;
|
|
37
|
+
}
|
|
38
|
+
function paragraphMediaFieldNames(pType) {
|
|
39
|
+
return new Set(
|
|
40
|
+
pType.fields.filter((f) => f.field_type === "image" || f.field_type === "video" || f.field_type === "file").map((f) => f.name)
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
async function fetchParagraphMediaIds(db, pType, itemId, fieldName) {
|
|
44
|
+
const mediaColumns = pType.fields.filter((f) => f.field_type === "image" || f.field_type === "video" || f.field_type === "file").map((f) => f.db_column.column_name);
|
|
45
|
+
if (mediaColumns.length === 0) return [];
|
|
46
|
+
const tbl = import_drizzle_orm2.sql.raw(quoteIdent(pType.db.table_name));
|
|
47
|
+
const cols = import_drizzle_orm2.sql.join(mediaColumns.map((c) => import_drizzle_orm2.sql.raw(quoteIdent(c))), import_drizzle_orm2.sql`, `);
|
|
48
|
+
const result = await db.execute(
|
|
49
|
+
import_drizzle_orm2.sql`SELECT ${cols} FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`
|
|
50
|
+
);
|
|
51
|
+
const ids = [];
|
|
52
|
+
for (const row of result.rows) {
|
|
53
|
+
for (const col of mediaColumns) {
|
|
54
|
+
const v = row[col];
|
|
55
|
+
if (typeof v === "string" && v !== "") ids.push(v);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return ids;
|
|
59
|
+
}
|
|
60
|
+
async function persistParagraphField(db, itemId, parentType, fieldName, pType, items) {
|
|
61
|
+
const removed = await fetchParagraphMediaIds(db, pType, itemId, fieldName);
|
|
62
|
+
const tbl = import_drizzle_orm2.sql.raw(quoteIdent(pType.db.table_name));
|
|
63
|
+
await db.execute(import_drizzle_orm2.sql`DELETE FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`);
|
|
64
|
+
const mediaFieldNames = paragraphMediaFieldNames(pType);
|
|
65
|
+
const added = [];
|
|
66
|
+
for (let i = 0; i < items.length; i++) {
|
|
67
|
+
const pItem = items[i];
|
|
68
|
+
const data = {
|
|
69
|
+
id: import_node_crypto.default.randomUUID(),
|
|
70
|
+
parent_id: itemId,
|
|
71
|
+
parent_type: parentType,
|
|
72
|
+
parent_field: fieldName,
|
|
73
|
+
order: i,
|
|
74
|
+
created_at: /* @__PURE__ */ new Date(),
|
|
75
|
+
updated_at: /* @__PURE__ */ new Date()
|
|
76
|
+
};
|
|
77
|
+
for (const pf of pType.fields) {
|
|
78
|
+
if (!pf.db_column || pf.db_column.junction) continue;
|
|
79
|
+
data[pf.db_column.column_name] = pItem[pf.name] ?? null;
|
|
80
|
+
}
|
|
81
|
+
const cols = import_drizzle_orm2.sql.join(Object.keys(data).map((k) => import_drizzle_orm2.sql.raw(quoteIdent(k))), import_drizzle_orm2.sql`, `);
|
|
82
|
+
const vals = import_drizzle_orm2.sql.join(Object.values(data).map((v) => import_drizzle_orm2.sql`${v}`), import_drizzle_orm2.sql`, `);
|
|
83
|
+
await db.execute(import_drizzle_orm2.sql`INSERT INTO ${tbl} (${cols}) VALUES (${vals})`);
|
|
84
|
+
for (const name of mediaFieldNames) {
|
|
85
|
+
const val = pItem[name];
|
|
86
|
+
if (typeof val === "string" && val !== "") added.push(val);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return { added, removed };
|
|
90
|
+
}
|
|
91
|
+
async function deleteParagraphField(db, itemId, fieldName, pType) {
|
|
92
|
+
const removed = await fetchParagraphMediaIds(db, pType, itemId, fieldName);
|
|
93
|
+
const tbl = import_drizzle_orm2.sql.raw(quoteIdent(pType.db.table_name));
|
|
94
|
+
await db.execute(import_drizzle_orm2.sql`DELETE FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`);
|
|
95
|
+
return { added: [], removed };
|
|
96
|
+
}
|
|
97
|
+
async function persistJunctionField(db, itemId, junction, relatedIds) {
|
|
98
|
+
const tbl = import_drizzle_orm2.sql.raw(quoteIdent(junction.table_name));
|
|
99
|
+
const left = import_drizzle_orm2.sql.raw(quoteIdent(junction.left_column));
|
|
100
|
+
const right = import_drizzle_orm2.sql.raw(quoteIdent(junction.right_column));
|
|
101
|
+
await db.execute(import_drizzle_orm2.sql`DELETE FROM ${tbl} WHERE ${left} = ${itemId}`);
|
|
102
|
+
for (const rid of relatedIds) {
|
|
103
|
+
await db.execute(import_drizzle_orm2.sql`INSERT INTO ${tbl} (${left}, ${right}) VALUES (${itemId}, ${rid})`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function buildRelationsMap(fields, registry) {
|
|
107
|
+
const relations = {};
|
|
108
|
+
for (const field of fields) {
|
|
109
|
+
if (field.field_type === "image" || field.field_type === "video" || field.field_type === "file") {
|
|
110
|
+
if (!field.db_column) continue;
|
|
111
|
+
relations[field.name] = { type: "media", fk_column: field.db_column.column_name };
|
|
112
|
+
} else if (field.field_type === "paragraph") {
|
|
113
|
+
const ref = field.ui_component.ref;
|
|
114
|
+
const pType = ref ? registry.paragraph_types[ref] : void 0;
|
|
115
|
+
if (!pType) continue;
|
|
116
|
+
relations[field.name] = { type: "paragraph", table: pType.db.table_name };
|
|
117
|
+
} else if (field.field_type === "reference") {
|
|
118
|
+
if (!field.db_column) continue;
|
|
119
|
+
if (field.db_column.junction) {
|
|
120
|
+
const j = field.db_column.junction;
|
|
121
|
+
relations[field.name] = {
|
|
122
|
+
type: "junction",
|
|
123
|
+
table: j.right_table,
|
|
124
|
+
junction_table: j.table_name,
|
|
125
|
+
left_column: j.left_column,
|
|
126
|
+
right_column: j.right_column,
|
|
127
|
+
order_column: j.order_column
|
|
128
|
+
};
|
|
129
|
+
} else if (field.db_column.foreign_key) {
|
|
130
|
+
relations[field.name] = {
|
|
131
|
+
type: "reference",
|
|
132
|
+
table: field.db_column.foreign_key.table,
|
|
133
|
+
fk_column: field.db_column.column_name
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return relations;
|
|
139
|
+
}
|
|
140
|
+
function groupBy(items, key) {
|
|
141
|
+
const result = {};
|
|
142
|
+
for (const item of items) {
|
|
143
|
+
const k = String(item[key]);
|
|
144
|
+
if (!result[k]) result[k] = [];
|
|
145
|
+
result[k].push(item);
|
|
146
|
+
}
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOnly = false) {
|
|
150
|
+
if (rows.length === 0) return;
|
|
151
|
+
if (rel.type === "paragraph") {
|
|
152
|
+
const parentIds = rows.map((r) => r["id"]);
|
|
153
|
+
const inList = import_drizzle_orm2.sql.join(parentIds.map((id) => import_drizzle_orm2.sql`${id}`), import_drizzle_orm2.sql`, `);
|
|
154
|
+
const result = await db.execute(
|
|
155
|
+
import_drizzle_orm2.sql`SELECT * FROM ${import_drizzle_orm2.sql.raw(quoteIdent(rel.table))} WHERE parent_id IN (${inList}) ORDER BY "order" ASC`
|
|
156
|
+
);
|
|
157
|
+
const byParent = groupBy(result.rows, "parent_id");
|
|
158
|
+
for (const row of rows) {
|
|
159
|
+
row[fieldName] = byParent[row["id"]] ?? [];
|
|
160
|
+
}
|
|
161
|
+
} else if (rel.type === "reference") {
|
|
162
|
+
const fkValues = rows.map((r) => r[rel.fk_column]).filter(Boolean);
|
|
163
|
+
if (fkValues.length === 0) {
|
|
164
|
+
for (const row of rows) row[fieldName] = null;
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const unique = [...new Set(fkValues)];
|
|
168
|
+
const uncached = unique.filter((id) => !cache.has(`${rel.table}:${id}`));
|
|
169
|
+
if (uncached.length > 0) {
|
|
170
|
+
const inList = import_drizzle_orm2.sql.join(uncached.map((id) => import_drizzle_orm2.sql`${id}`), import_drizzle_orm2.sql`, `);
|
|
171
|
+
const publishedCond = publishedOnly ? import_drizzle_orm2.sql` AND published = true` : import_drizzle_orm2.sql``;
|
|
172
|
+
const result = await db.execute(
|
|
173
|
+
import_drizzle_orm2.sql`SELECT * FROM ${import_drizzle_orm2.sql.raw(quoteIdent(rel.table))} WHERE id IN (${inList})${publishedCond}`
|
|
174
|
+
);
|
|
175
|
+
for (const item of result.rows) {
|
|
176
|
+
cache.set(`${rel.table}:${item["id"]}`, item);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
for (const row of rows) {
|
|
180
|
+
const fkVal = row[rel.fk_column];
|
|
181
|
+
row[fieldName] = fkVal ? cache.get(`${rel.table}:${fkVal}`) ?? null : null;
|
|
182
|
+
}
|
|
183
|
+
} else if (rel.type === "junction") {
|
|
184
|
+
const parentIds = rows.map((r) => r["id"]);
|
|
185
|
+
const inList = import_drizzle_orm2.sql.join(parentIds.map((id) => import_drizzle_orm2.sql`${id}`), import_drizzle_orm2.sql`, `);
|
|
186
|
+
const orderBy = rel.order_column ? import_drizzle_orm2.sql` ORDER BY "order" ASC` : import_drizzle_orm2.sql``;
|
|
187
|
+
const junctionResult = await db.execute(
|
|
188
|
+
import_drizzle_orm2.sql`SELECT * FROM ${import_drizzle_orm2.sql.raw(quoteIdent(rel.junction_table))} WHERE ${import_drizzle_orm2.sql.raw(quoteIdent(rel.left_column))} IN (${inList})${orderBy}`
|
|
189
|
+
);
|
|
190
|
+
const jRows = junctionResult.rows;
|
|
191
|
+
const rightIds = [...new Set(jRows.map((r) => r[rel.right_column]))];
|
|
192
|
+
const uncached = rightIds.filter((id) => !cache.has(`${rel.table}:${id}`));
|
|
193
|
+
if (uncached.length > 0) {
|
|
194
|
+
const rightInList = import_drizzle_orm2.sql.join(uncached.map((id) => import_drizzle_orm2.sql`${id}`), import_drizzle_orm2.sql`, `);
|
|
195
|
+
const publishedCond = publishedOnly ? import_drizzle_orm2.sql` AND published = true` : import_drizzle_orm2.sql``;
|
|
196
|
+
const entitiesResult = await db.execute(
|
|
197
|
+
import_drizzle_orm2.sql`SELECT * FROM ${import_drizzle_orm2.sql.raw(quoteIdent(rel.table))} WHERE id IN (${rightInList})${publishedCond}`
|
|
198
|
+
);
|
|
199
|
+
for (const item of entitiesResult.rows) {
|
|
200
|
+
cache.set(`${rel.table}:${item["id"]}`, item);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const byLeft = groupBy(jRows, rel.left_column);
|
|
204
|
+
for (const row of rows) {
|
|
205
|
+
const jEntries = byLeft[row["id"]] ?? [];
|
|
206
|
+
row[fieldName] = jEntries.map((jr) => cache.get(`${rel.table}:${jr[rel.right_column]}`)).filter(Boolean);
|
|
207
|
+
}
|
|
208
|
+
} else if (rel.type === "media") {
|
|
209
|
+
const fkValues = rows.map((r) => r[rel.fk_column]).filter(Boolean);
|
|
210
|
+
if (fkValues.length === 0) {
|
|
211
|
+
for (const row of rows) row[fieldName] = null;
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const unique = [...new Set(fkValues)];
|
|
215
|
+
const uncached = unique.filter((id) => !cache.has(`media:${id}`));
|
|
216
|
+
if (uncached.length > 0) {
|
|
217
|
+
const inList = import_drizzle_orm2.sql.join(uncached.map((id) => import_drizzle_orm2.sql`${id}`), import_drizzle_orm2.sql`, `);
|
|
218
|
+
const result = await db.execute(import_drizzle_orm2.sql`SELECT * FROM "media" WHERE id IN (${inList})`);
|
|
219
|
+
for (const item of result.rows) {
|
|
220
|
+
cache.set(`media:${item["id"]}`, item);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
for (const row of rows) {
|
|
224
|
+
const fkVal = row[rel.fk_column];
|
|
225
|
+
row[fieldName] = fkVal ? cache.get(`media:${fkVal}`) ?? null : null;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
async function resolveRelationBareIds(db, rows, fieldName, rel) {
|
|
230
|
+
if (rows.length === 0) return;
|
|
231
|
+
if (rel.type === "paragraph") {
|
|
232
|
+
const parentIds = rows.map((r) => r["id"]);
|
|
233
|
+
const inList = import_drizzle_orm2.sql.join(parentIds.map((id) => import_drizzle_orm2.sql`${id}`), import_drizzle_orm2.sql`, `);
|
|
234
|
+
const result = await db.execute(
|
|
235
|
+
import_drizzle_orm2.sql`SELECT id, parent_id FROM ${import_drizzle_orm2.sql.raw(quoteIdent(rel.table))} WHERE parent_id IN (${inList}) ORDER BY "order" ASC`
|
|
236
|
+
);
|
|
237
|
+
const byParent = groupBy(result.rows, "parent_id");
|
|
238
|
+
for (const row of rows) {
|
|
239
|
+
row[fieldName] = (byParent[row["id"]] ?? []).map((r) => r["id"]);
|
|
240
|
+
}
|
|
241
|
+
} else if (rel.type === "junction") {
|
|
242
|
+
const parentIds = rows.map((r) => r["id"]);
|
|
243
|
+
const inList = import_drizzle_orm2.sql.join(parentIds.map((id) => import_drizzle_orm2.sql`${id}`), import_drizzle_orm2.sql`, `);
|
|
244
|
+
const result = await db.execute(
|
|
245
|
+
import_drizzle_orm2.sql`SELECT ${import_drizzle_orm2.sql.raw(quoteIdent(rel.left_column))} AS left_id, ${import_drizzle_orm2.sql.raw(quoteIdent(rel.right_column))} AS right_id FROM ${import_drizzle_orm2.sql.raw(quoteIdent(rel.junction_table))} WHERE ${import_drizzle_orm2.sql.raw(quoteIdent(rel.left_column))} IN (${inList})`
|
|
246
|
+
);
|
|
247
|
+
const byLeft = groupBy(result.rows, "left_id");
|
|
248
|
+
for (const row of rows) {
|
|
249
|
+
row[fieldName] = (byLeft[row["id"]] ?? []).map((r) => r["right_id"]);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
var import_node_crypto, import_drizzle_orm2;
|
|
254
|
+
var init_relations = __esm({
|
|
255
|
+
"src/relations.ts"() {
|
|
256
|
+
"use strict";
|
|
257
|
+
import_node_crypto = __toESM(require("crypto"));
|
|
258
|
+
import_drizzle_orm2 = require("drizzle-orm");
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// src/graphql/scalars.ts
|
|
263
|
+
var import_graphql, DateTimeScalar, JSONScalar;
|
|
264
|
+
var init_scalars = __esm({
|
|
265
|
+
"src/graphql/scalars.ts"() {
|
|
266
|
+
"use strict";
|
|
267
|
+
import_graphql = require("graphql");
|
|
268
|
+
DateTimeScalar = new import_graphql.GraphQLScalarType({
|
|
269
|
+
name: "DateTime",
|
|
270
|
+
description: "ISO-8601 date-time string",
|
|
271
|
+
serialize(value) {
|
|
272
|
+
if (value instanceof Date) return value.toISOString();
|
|
273
|
+
if (typeof value === "string") return value;
|
|
274
|
+
if (typeof value === "number") return new Date(value).toISOString();
|
|
275
|
+
throw new TypeError(`DateTime cannot serialize value: ${String(value)}`);
|
|
276
|
+
},
|
|
277
|
+
parseValue(value) {
|
|
278
|
+
if (typeof value !== "string") throw new TypeError("DateTime must be a string");
|
|
279
|
+
return value;
|
|
280
|
+
},
|
|
281
|
+
parseLiteral(ast) {
|
|
282
|
+
if (ast.kind !== import_graphql.Kind.STRING) throw new TypeError("DateTime must be a string");
|
|
283
|
+
return ast.value;
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
JSONScalar = new import_graphql.GraphQLScalarType({
|
|
287
|
+
name: "JSON",
|
|
288
|
+
description: "Arbitrary JSON value",
|
|
289
|
+
serialize: (value) => value,
|
|
290
|
+
parseValue: (value) => value,
|
|
291
|
+
parseLiteral: (ast, variables) => (0, import_graphql.valueFromASTUntyped)(ast, variables ?? void 0)
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
// src/graphql/type-mapping.ts
|
|
297
|
+
function scalarOutputType(fieldType) {
|
|
298
|
+
switch (fieldType) {
|
|
299
|
+
case "text/plain":
|
|
300
|
+
case "text/rich":
|
|
301
|
+
return import_graphql2.GraphQLString;
|
|
302
|
+
case "integer":
|
|
303
|
+
return import_graphql2.GraphQLInt;
|
|
304
|
+
case "float":
|
|
305
|
+
return import_graphql2.GraphQLFloat;
|
|
306
|
+
case "boolean":
|
|
307
|
+
return import_graphql2.GraphQLBoolean;
|
|
308
|
+
case "date":
|
|
309
|
+
return DateTimeScalar;
|
|
310
|
+
case "programmatic":
|
|
311
|
+
return JSONScalar;
|
|
312
|
+
default:
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
var import_graphql2;
|
|
317
|
+
var init_type_mapping = __esm({
|
|
318
|
+
"src/graphql/type-mapping.ts"() {
|
|
319
|
+
"use strict";
|
|
320
|
+
import_graphql2 = require("graphql");
|
|
321
|
+
init_scalars();
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// src/graphql/naming.ts
|
|
326
|
+
function schemaSegment(machineName) {
|
|
327
|
+
const idx = machineName.indexOf("--");
|
|
328
|
+
return idx === -1 ? machineName : machineName.slice(idx + 2);
|
|
329
|
+
}
|
|
330
|
+
function words(input) {
|
|
331
|
+
return input.split(/[_\s-]+/).filter(Boolean);
|
|
332
|
+
}
|
|
333
|
+
function toPascalCase(input) {
|
|
334
|
+
return words(input).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
335
|
+
}
|
|
336
|
+
function toCamelCase(input) {
|
|
337
|
+
const pascal = toPascalCase(input);
|
|
338
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
339
|
+
}
|
|
340
|
+
function pluralize(word) {
|
|
341
|
+
if (/[^aeiou]y$/.test(word)) return word.slice(0, -1) + "ies";
|
|
342
|
+
if (/(s|x|z|ch|sh)$/.test(word)) return word + "es";
|
|
343
|
+
return word + "s";
|
|
344
|
+
}
|
|
345
|
+
function isValidGraphQLName(name) {
|
|
346
|
+
return GRAPHQL_NAME.test(name);
|
|
347
|
+
}
|
|
348
|
+
function graphqlTypeName(machineName) {
|
|
349
|
+
return toPascalCase(schemaSegment(machineName));
|
|
350
|
+
}
|
|
351
|
+
function singleQueryName(machineName) {
|
|
352
|
+
return toCamelCase(schemaSegment(machineName));
|
|
353
|
+
}
|
|
354
|
+
function collectionQueryName(machineName) {
|
|
355
|
+
return pluralize(singleQueryName(machineName));
|
|
356
|
+
}
|
|
357
|
+
function buildFieldNameMap(schemaNames) {
|
|
358
|
+
const toG = /* @__PURE__ */ new Map();
|
|
359
|
+
const toS = /* @__PURE__ */ new Map();
|
|
360
|
+
for (const name of schemaNames) {
|
|
361
|
+
const g = toCamelCase(name);
|
|
362
|
+
toG.set(name, g);
|
|
363
|
+
toS.set(g, name);
|
|
364
|
+
}
|
|
365
|
+
return {
|
|
366
|
+
toGraphql: (schemaName) => toG.get(schemaName) ?? toCamelCase(schemaName),
|
|
367
|
+
toSchema: (graphqlName) => toS.get(graphqlName) ?? graphqlName
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
var GRAPHQL_NAME;
|
|
371
|
+
var init_naming = __esm({
|
|
372
|
+
"src/graphql/naming.ts"() {
|
|
373
|
+
"use strict";
|
|
374
|
+
GRAPHQL_NAME = /^[_A-Za-z][_0-9A-Za-z]*$/;
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
// src/graphql/filters.ts
|
|
379
|
+
function buildSortFieldEnum(typeName) {
|
|
380
|
+
return new import_graphql3.GraphQLEnumType({
|
|
381
|
+
name: `${typeName}SortField`,
|
|
382
|
+
values: Object.fromEntries(SORTABLE.map((s) => [s.gql, { value: s.column }]))
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
function comparableFilterInput(name, scalar) {
|
|
386
|
+
return new import_graphql3.GraphQLInputObjectType({
|
|
387
|
+
name,
|
|
388
|
+
fields: {
|
|
389
|
+
eq: { type: scalar },
|
|
390
|
+
in: { type: new import_graphql3.GraphQLList(new import_graphql3.GraphQLNonNull(scalar)) },
|
|
391
|
+
gt: { type: scalar },
|
|
392
|
+
gte: { type: scalar },
|
|
393
|
+
lt: { type: scalar },
|
|
394
|
+
lte: { type: scalar }
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
function equalityFilterInput(name, scalar) {
|
|
399
|
+
return new import_graphql3.GraphQLInputObjectType({
|
|
400
|
+
name,
|
|
401
|
+
fields: {
|
|
402
|
+
eq: { type: scalar },
|
|
403
|
+
in: { type: new import_graphql3.GraphQLList(new import_graphql3.GraphQLNonNull(scalar)) }
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
function filterInputForField(fieldType) {
|
|
408
|
+
const scalar = scalarOutputType(fieldType);
|
|
409
|
+
if (fieldType === "boolean") return BooleanFilter;
|
|
410
|
+
if (fieldType === "date") return DateTimeFilter;
|
|
411
|
+
if (fieldType === "integer") return IntFilter;
|
|
412
|
+
if (fieldType === "float") return FloatFilter;
|
|
413
|
+
if (fieldType === "reference") return IDFilter;
|
|
414
|
+
if (scalar === null) return null;
|
|
415
|
+
return StringFilter;
|
|
416
|
+
}
|
|
417
|
+
function buildFilterInputType(type) {
|
|
418
|
+
const fields = {};
|
|
419
|
+
fields["id"] = { type: IDFilter };
|
|
420
|
+
fields["published"] = { type: BooleanFilter };
|
|
421
|
+
fields["createdAt"] = { type: DateTimeFilter };
|
|
422
|
+
fields["updatedAt"] = { type: DateTimeFilter };
|
|
423
|
+
if (type.schema_type === "content-type") fields["slug"] = { type: StringFilter };
|
|
424
|
+
for (const f of type.fields) {
|
|
425
|
+
if (f.field_type === "programmatic" || f.field_type === "paragraph") continue;
|
|
426
|
+
if (f.field_type === "image" || f.field_type === "video" || f.field_type === "file") continue;
|
|
427
|
+
const input = filterInputForField(f.field_type);
|
|
428
|
+
if (input) fields[buildFieldNameMap([f.name]).toGraphql(f.name)] = { type: input };
|
|
429
|
+
}
|
|
430
|
+
if (Object.keys(fields).length === 0) return null;
|
|
431
|
+
return new import_graphql3.GraphQLInputObjectType({ name: `${graphqlTypeName(type.name)}Filter`, fields });
|
|
432
|
+
}
|
|
433
|
+
function translateFilters(input, nameMap) {
|
|
434
|
+
const out = {};
|
|
435
|
+
if (!input) return out;
|
|
436
|
+
for (const [gqlField, raw] of Object.entries(input)) {
|
|
437
|
+
if (raw === null || typeof raw !== "object") continue;
|
|
438
|
+
const column = nameMap.toSchema(gqlField);
|
|
439
|
+
const spec = raw;
|
|
440
|
+
if (spec["eq"] !== void 0) {
|
|
441
|
+
out[column] = spec["eq"];
|
|
442
|
+
} else if (Array.isArray(spec["in"])) {
|
|
443
|
+
out[column] = spec["in"];
|
|
444
|
+
} else {
|
|
445
|
+
const op = {};
|
|
446
|
+
for (const k of ["gt", "gte", "lt", "lte"]) {
|
|
447
|
+
if (spec[k] !== void 0) op[k] = spec[k];
|
|
448
|
+
}
|
|
449
|
+
if (Object.keys(op).length > 0) out[column] = op;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return out;
|
|
453
|
+
}
|
|
454
|
+
var import_graphql3, SortOrderEnum, SORTABLE, StringFilter, BooleanFilter, DateTimeFilter, IDFilter, IntFilter, FloatFilter;
|
|
455
|
+
var init_filters = __esm({
|
|
456
|
+
"src/graphql/filters.ts"() {
|
|
457
|
+
"use strict";
|
|
458
|
+
import_graphql3 = require("graphql");
|
|
459
|
+
init_naming();
|
|
460
|
+
init_type_mapping();
|
|
461
|
+
init_scalars();
|
|
462
|
+
SortOrderEnum = new import_graphql3.GraphQLEnumType({
|
|
463
|
+
name: "SortOrder",
|
|
464
|
+
values: { ASC: { value: "asc" }, DESC: { value: "desc" } }
|
|
465
|
+
});
|
|
466
|
+
SORTABLE = [
|
|
467
|
+
{ gql: "title", column: "title" },
|
|
468
|
+
{ gql: "createdAt", column: "created_at" },
|
|
469
|
+
{ gql: "updatedAt", column: "updated_at" }
|
|
470
|
+
];
|
|
471
|
+
StringFilter = equalityFilterInput("StringFilter", import_graphql3.GraphQLString);
|
|
472
|
+
BooleanFilter = new import_graphql3.GraphQLInputObjectType({
|
|
473
|
+
name: "BooleanFilter",
|
|
474
|
+
fields: { eq: { type: import_graphql3.GraphQLBoolean } }
|
|
475
|
+
});
|
|
476
|
+
DateTimeFilter = comparableFilterInput("DateTimeFilter", DateTimeScalar);
|
|
477
|
+
IDFilter = equalityFilterInput("IDFilter", import_graphql3.GraphQLString);
|
|
478
|
+
IntFilter = comparableFilterInput("IntFilter", import_graphql3.GraphQLInt);
|
|
479
|
+
FloatFilter = comparableFilterInput("FloatFilter", import_graphql3.GraphQLFloat);
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
// src/graphql/resolvers.ts
|
|
484
|
+
function codeError2(code, message) {
|
|
485
|
+
return new import_graphql4.GraphQLError(message, { extensions: { code } });
|
|
486
|
+
}
|
|
487
|
+
function isPublished2(item) {
|
|
488
|
+
return !!item && item["published"] === true;
|
|
489
|
+
}
|
|
490
|
+
function scalarFieldResolver(schemaName) {
|
|
491
|
+
return (parent) => parent[schemaName];
|
|
492
|
+
}
|
|
493
|
+
function relationFieldResolver(typeName, schemaFieldName) {
|
|
494
|
+
return (parent, _args, ctx) => ctx.loaders.load(typeName, schemaFieldName, parent);
|
|
495
|
+
}
|
|
496
|
+
function programmaticFieldResolver(typeName, schemaFieldName) {
|
|
497
|
+
return async (parent, _args, ctx) => {
|
|
498
|
+
let p = ctx.programmaticMemo.get(parent);
|
|
499
|
+
if (!p) {
|
|
500
|
+
p = ctx.resolver.resolveItem(typeName, parent);
|
|
501
|
+
ctx.programmaticMemo.set(parent, p);
|
|
502
|
+
}
|
|
503
|
+
return (await p)[schemaFieldName];
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
function collectionResolver(typeName, nameMap) {
|
|
507
|
+
return async (_root, args, ctx) => {
|
|
508
|
+
const page = args.page ?? 1;
|
|
509
|
+
const perPage = args.perPage ?? 10;
|
|
510
|
+
if (!Number.isInteger(page) || page < 1) {
|
|
511
|
+
throw codeError2("INVALID_PAGINATION", "page must be \u2265 1");
|
|
512
|
+
}
|
|
513
|
+
if (!Number.isInteger(perPage) || perPage < 1 || perPage > 100) {
|
|
514
|
+
throw codeError2("INVALID_PAGINATION", "perPage must be between 1 and 100");
|
|
515
|
+
}
|
|
516
|
+
const repo = ctx.repos[typeName];
|
|
517
|
+
const result = await repo.findMany({
|
|
518
|
+
published_only: true,
|
|
519
|
+
page,
|
|
520
|
+
per_page: perPage,
|
|
521
|
+
sort_by: args.sortBy ?? "created_at",
|
|
522
|
+
sort_order: args.sortOrder ?? "asc",
|
|
523
|
+
filters: translateFilters(args.filter, nameMap)
|
|
524
|
+
});
|
|
525
|
+
return { data: result.data, meta: result.meta };
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
function singleBySlugResolver(typeName) {
|
|
529
|
+
return async (_root, args, ctx) => {
|
|
530
|
+
const repo = ctx.repos[typeName];
|
|
531
|
+
const item = await repo.findBySlug(args.slug);
|
|
532
|
+
return isPublished2(item) ? item : null;
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
function singletonResolver(typeName) {
|
|
536
|
+
return async (_root, _args, ctx) => {
|
|
537
|
+
const repo = ctx.repos[typeName];
|
|
538
|
+
const result = await repo.findMany({ published_only: true, page: 1, per_page: 1 });
|
|
539
|
+
return result.data[0] ?? null;
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
function taxonomySingleResolver(typeName) {
|
|
543
|
+
return async (_root, args, ctx) => {
|
|
544
|
+
const repo = ctx.repos[typeName];
|
|
545
|
+
const item = await repo.findOne(args.id);
|
|
546
|
+
return isPublished2(item) ? item : null;
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
var import_graphql4;
|
|
550
|
+
var init_resolvers = __esm({
|
|
551
|
+
"src/graphql/resolvers.ts"() {
|
|
552
|
+
"use strict";
|
|
553
|
+
import_graphql4 = require("graphql");
|
|
554
|
+
init_filters();
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
// src/graphql/schema.ts
|
|
559
|
+
function buildGraphQLSchema(registry) {
|
|
560
|
+
const objectTypes = /* @__PURE__ */ new Map();
|
|
561
|
+
const enumTypes = /* @__PURE__ */ new Map();
|
|
562
|
+
for (const [name, enumType] of Object.entries(registry.enum_types)) {
|
|
563
|
+
if (enumType.values.every(isValidGraphQLName)) {
|
|
564
|
+
enumTypes.set(
|
|
565
|
+
name,
|
|
566
|
+
new import_graphql5.GraphQLEnumType({
|
|
567
|
+
name: graphqlTypeName(name),
|
|
568
|
+
values: Object.fromEntries(enumType.values.map((v) => [v, { value: v }]))
|
|
569
|
+
})
|
|
570
|
+
);
|
|
571
|
+
} else {
|
|
572
|
+
process.stderr.write(
|
|
573
|
+
`\u26A0 enum '${name}' has values that aren't valid GraphQL identifiers; exposing as String
|
|
574
|
+
`
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
function outputTypeForField(field) {
|
|
579
|
+
const scalar = scalarOutputType(field.field_type);
|
|
580
|
+
if (scalar) return field.required ? new import_graphql5.GraphQLNonNull(scalar) : scalar;
|
|
581
|
+
if (field.field_type === "enum") {
|
|
582
|
+
const ref = field.ui_component.component === "select" ? field.ui_component.enum_ref : void 0;
|
|
583
|
+
const et = ref ? enumTypes.get(ref) : void 0;
|
|
584
|
+
const t = et ?? import_graphql5.GraphQLString;
|
|
585
|
+
return field.required ? new import_graphql5.GraphQLNonNull(t) : t;
|
|
586
|
+
}
|
|
587
|
+
if (field.field_type === "image" || field.field_type === "video" || field.field_type === "file") {
|
|
588
|
+
return MEDIA;
|
|
589
|
+
}
|
|
590
|
+
if (field.field_type === "paragraph") {
|
|
591
|
+
const ref = field.ui_component.component === "paragraph-embed" ? field.ui_component.ref : void 0;
|
|
592
|
+
const target = ref ? objectTypes.get(ref) : void 0;
|
|
593
|
+
if (ref && !target) {
|
|
594
|
+
process.stderr.write(
|
|
595
|
+
`\u26A0 paragraph field '${field.name}' targets unknown paragraph type '${ref}'; exposing as Media
|
|
596
|
+
`
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
return new import_graphql5.GraphQLList(new import_graphql5.GraphQLNonNull(target ?? MEDIA));
|
|
600
|
+
}
|
|
601
|
+
if (field.field_type === "reference") {
|
|
602
|
+
const ref = field.ui_component.component === "typeahead-select" ? field.ui_component.ref : void 0;
|
|
603
|
+
const target = ref ? objectTypes.get(ref) : void 0;
|
|
604
|
+
if (ref && !target) {
|
|
605
|
+
process.stderr.write(`\u26A0 reference field '${field.name}' targets unknown type '${ref}'; exposing as Media
|
|
606
|
+
`);
|
|
607
|
+
}
|
|
608
|
+
const rel = field.ui_component.component === "typeahead-select" ? field.ui_component.rel : void 0;
|
|
609
|
+
const isMany = rel === "many-to-many" || rel === "one-to-many";
|
|
610
|
+
const t = target ?? MEDIA;
|
|
611
|
+
return isMany ? new import_graphql5.GraphQLList(new import_graphql5.GraphQLNonNull(t)) : t;
|
|
612
|
+
}
|
|
613
|
+
return import_graphql5.GraphQLString;
|
|
614
|
+
}
|
|
615
|
+
function buildObjectType(machineName, type) {
|
|
616
|
+
return new import_graphql5.GraphQLObjectType({
|
|
617
|
+
name: graphqlTypeName(machineName),
|
|
618
|
+
fields: () => {
|
|
619
|
+
const fields = {};
|
|
620
|
+
fields["id"] = { type: new import_graphql5.GraphQLNonNull(import_graphql5.GraphQLID), resolve: (p) => p["id"] };
|
|
621
|
+
if (type.schema_type !== "paragraph-type") {
|
|
622
|
+
fields["published"] = { type: new import_graphql5.GraphQLNonNull(import_graphql5.GraphQLBoolean), resolve: (p) => p["published"] };
|
|
623
|
+
}
|
|
624
|
+
if (type.schema_type === "content-type") {
|
|
625
|
+
fields["slug"] = { type: new import_graphql5.GraphQLNonNull(import_graphql5.GraphQLString), resolve: (p) => p["slug"] };
|
|
626
|
+
}
|
|
627
|
+
fields["createdAt"] = { type: new import_graphql5.GraphQLNonNull(DateTimeScalar), resolve: (p) => p["created_at"] };
|
|
628
|
+
fields["updatedAt"] = { type: new import_graphql5.GraphQLNonNull(DateTimeScalar), resolve: (p) => p["updated_at"] };
|
|
629
|
+
for (const field of type.fields) {
|
|
630
|
+
const gqlName = toCamelCase(field.name);
|
|
631
|
+
const outType = outputTypeForField(field);
|
|
632
|
+
let resolve;
|
|
633
|
+
if (field.field_type === "programmatic") {
|
|
634
|
+
resolve = programmaticFieldResolver(machineName, field.name);
|
|
635
|
+
} else if (field.field_type === "reference" || field.field_type === "paragraph" || field.field_type === "image" || field.field_type === "video" || field.field_type === "file") {
|
|
636
|
+
resolve = relationFieldResolver(machineName, field.name);
|
|
637
|
+
} else {
|
|
638
|
+
resolve = scalarFieldResolver(field.name);
|
|
639
|
+
}
|
|
640
|
+
fields[gqlName] = { type: outType, resolve };
|
|
641
|
+
}
|
|
642
|
+
return fields;
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
const typeNameOwners = /* @__PURE__ */ new Map();
|
|
647
|
+
function registerTypeName(machineName) {
|
|
648
|
+
const gqlName = graphqlTypeName(machineName);
|
|
649
|
+
const owner = typeNameOwners.get(gqlName);
|
|
650
|
+
if (owner !== void 0 && owner !== machineName) {
|
|
651
|
+
throw new Error(
|
|
652
|
+
`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.`
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
typeNameOwners.set(gqlName, machineName);
|
|
656
|
+
}
|
|
657
|
+
for (const [name, ct] of Object.entries(registry.content_types)) {
|
|
658
|
+
registerTypeName(name);
|
|
659
|
+
objectTypes.set(name, buildObjectType(name, ct));
|
|
660
|
+
}
|
|
661
|
+
for (const [name, tt] of Object.entries(registry.taxonomy_types)) {
|
|
662
|
+
registerTypeName(name);
|
|
663
|
+
objectTypes.set(name, buildObjectType(name, tt));
|
|
664
|
+
}
|
|
665
|
+
for (const [name, pt] of Object.entries(registry.paragraph_types)) {
|
|
666
|
+
registerTypeName(name);
|
|
667
|
+
objectTypes.set(name, buildObjectType(name, pt));
|
|
668
|
+
}
|
|
669
|
+
const queryFields = {};
|
|
670
|
+
for (const [name, ct] of Object.entries(registry.content_types)) {
|
|
671
|
+
const objType = objectTypes.get(name);
|
|
672
|
+
const nameMap = buildFieldNameMap(ct.fields.map((f) => f.name));
|
|
673
|
+
if (ct.only_one) {
|
|
674
|
+
queryFields[singleQueryName(name)] = { type: objType, resolve: singletonResolver(name) };
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
const listType = new import_graphql5.GraphQLObjectType({
|
|
678
|
+
name: `${graphqlTypeName(name)}List`,
|
|
679
|
+
fields: {
|
|
680
|
+
data: { type: new import_graphql5.GraphQLNonNull(new import_graphql5.GraphQLList(new import_graphql5.GraphQLNonNull(objType))), resolve: (r) => r.data },
|
|
681
|
+
meta: { type: new import_graphql5.GraphQLNonNull(PAGE_META), resolve: (r) => r.meta }
|
|
682
|
+
}
|
|
683
|
+
});
|
|
684
|
+
const filterType = buildFilterInputType(ct);
|
|
685
|
+
queryFields[collectionQueryName(name)] = {
|
|
686
|
+
type: new import_graphql5.GraphQLNonNull(listType),
|
|
687
|
+
args: {
|
|
688
|
+
page: { type: import_graphql5.GraphQLInt },
|
|
689
|
+
perPage: { type: import_graphql5.GraphQLInt },
|
|
690
|
+
sortBy: { type: buildSortFieldEnum(graphqlTypeName(name)) },
|
|
691
|
+
sortOrder: { type: SortOrderEnum },
|
|
692
|
+
...filterType ? { filter: { type: filterType } } : {}
|
|
693
|
+
},
|
|
694
|
+
resolve: collectionResolver(name, nameMap)
|
|
695
|
+
};
|
|
696
|
+
queryFields[singleQueryName(name)] = {
|
|
697
|
+
type: objType,
|
|
698
|
+
args: { slug: { type: new import_graphql5.GraphQLNonNull(import_graphql5.GraphQLString) } },
|
|
699
|
+
resolve: singleBySlugResolver(name)
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
for (const [name, tt] of Object.entries(registry.taxonomy_types)) {
|
|
703
|
+
const objType = objectTypes.get(name);
|
|
704
|
+
const listType = new import_graphql5.GraphQLObjectType({
|
|
705
|
+
name: `${graphqlTypeName(name)}List`,
|
|
706
|
+
fields: {
|
|
707
|
+
data: { type: new import_graphql5.GraphQLNonNull(new import_graphql5.GraphQLList(new import_graphql5.GraphQLNonNull(objType))), resolve: (r) => r.data },
|
|
708
|
+
meta: { type: new import_graphql5.GraphQLNonNull(PAGE_META), resolve: (r) => r.meta }
|
|
709
|
+
}
|
|
710
|
+
});
|
|
711
|
+
queryFields[collectionQueryName(name)] = {
|
|
712
|
+
type: new import_graphql5.GraphQLNonNull(listType),
|
|
713
|
+
args: { page: { type: import_graphql5.GraphQLInt }, perPage: { type: import_graphql5.GraphQLInt } },
|
|
714
|
+
resolve: collectionResolver(name, buildFieldNameMap(tt.fields.map((f) => f.name)))
|
|
715
|
+
};
|
|
716
|
+
queryFields[singleQueryName(name)] = {
|
|
717
|
+
type: objType,
|
|
718
|
+
args: { id: { type: new import_graphql5.GraphQLNonNull(import_graphql5.GraphQLID) } },
|
|
719
|
+
resolve: taxonomySingleResolver(name)
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
return new import_graphql5.GraphQLSchema({
|
|
723
|
+
query: new import_graphql5.GraphQLObjectType({ name: "Query", fields: queryFields })
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
var import_graphql5, PAGE_META, MEDIA;
|
|
727
|
+
var init_schema = __esm({
|
|
728
|
+
"src/graphql/schema.ts"() {
|
|
729
|
+
"use strict";
|
|
730
|
+
import_graphql5 = require("graphql");
|
|
731
|
+
init_scalars();
|
|
732
|
+
init_type_mapping();
|
|
733
|
+
init_naming();
|
|
734
|
+
init_filters();
|
|
735
|
+
init_resolvers();
|
|
736
|
+
PAGE_META = new import_graphql5.GraphQLObjectType({
|
|
737
|
+
name: "PageMeta",
|
|
738
|
+
fields: {
|
|
739
|
+
total: { type: new import_graphql5.GraphQLNonNull(import_graphql5.GraphQLInt), resolve: (m) => m.total },
|
|
740
|
+
page: { type: new import_graphql5.GraphQLNonNull(import_graphql5.GraphQLInt), resolve: (m) => m.page },
|
|
741
|
+
perPage: { type: new import_graphql5.GraphQLNonNull(import_graphql5.GraphQLInt), resolve: (m) => m.per_page },
|
|
742
|
+
totalPages: { type: new import_graphql5.GraphQLNonNull(import_graphql5.GraphQLInt), resolve: (m) => m.total_pages },
|
|
743
|
+
hasNext: { type: new import_graphql5.GraphQLNonNull(import_graphql5.GraphQLBoolean), resolve: (m) => m.has_next },
|
|
744
|
+
hasPrev: { type: new import_graphql5.GraphQLNonNull(import_graphql5.GraphQLBoolean), resolve: (m) => m.has_prev }
|
|
745
|
+
}
|
|
746
|
+
});
|
|
747
|
+
MEDIA = new import_graphql5.GraphQLObjectType({
|
|
748
|
+
name: "Media",
|
|
749
|
+
fields: {
|
|
750
|
+
id: { type: new import_graphql5.GraphQLNonNull(import_graphql5.GraphQLID), resolve: (m) => m.id },
|
|
751
|
+
type: { type: new import_graphql5.GraphQLNonNull(import_graphql5.GraphQLString), resolve: (m) => m.type },
|
|
752
|
+
url: { type: new import_graphql5.GraphQLNonNull(import_graphql5.GraphQLString), resolve: (m) => m.url },
|
|
753
|
+
mimeType: { type: new import_graphql5.GraphQLNonNull(import_graphql5.GraphQLString), resolve: (m) => m.mime_type },
|
|
754
|
+
alt: { type: import_graphql5.GraphQLString, resolve: (m) => m.alt },
|
|
755
|
+
fileSize: { type: import_graphql5.GraphQLInt, resolve: (m) => m.file_size },
|
|
756
|
+
width: { type: import_graphql5.GraphQLInt, resolve: (m) => m.width },
|
|
757
|
+
height: { type: import_graphql5.GraphQLInt, resolve: (m) => m.height },
|
|
758
|
+
duration: { type: import_graphql5.GraphQLInt, resolve: (m) => m.duration }
|
|
759
|
+
}
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
// src/graphql/dataloaders.ts
|
|
765
|
+
function createRelationLoaders(db, registry) {
|
|
766
|
+
const relMaps = /* @__PURE__ */ new Map();
|
|
767
|
+
const loaders = /* @__PURE__ */ new Map();
|
|
768
|
+
const cache = /* @__PURE__ */ new Map();
|
|
769
|
+
function relationsFor(typeName) {
|
|
770
|
+
let m = relMaps.get(typeName);
|
|
771
|
+
if (!m) {
|
|
772
|
+
const type = registry.content_types[typeName] ?? registry.taxonomy_types[typeName] ?? registry.paragraph_types[typeName];
|
|
773
|
+
m = type ? buildRelationsMap(type.fields, registry) : {};
|
|
774
|
+
relMaps.set(typeName, m);
|
|
775
|
+
}
|
|
776
|
+
return m;
|
|
777
|
+
}
|
|
778
|
+
function loaderFor(typeName, fieldName, rel) {
|
|
779
|
+
const key = `${typeName}:${fieldName}`;
|
|
780
|
+
let dl = loaders.get(key);
|
|
781
|
+
if (!dl) {
|
|
782
|
+
dl = new import_dataloader.default(
|
|
783
|
+
async (parents) => {
|
|
784
|
+
const rows = parents;
|
|
785
|
+
await resolveRelationField(db, rows, fieldName, rel, cache, true);
|
|
786
|
+
return rows.map((r) => r[fieldName]);
|
|
787
|
+
},
|
|
788
|
+
// Batch within a tick; do not memoize by parent identity (rows are mutated
|
|
789
|
+
// and may recur across nesting levels).
|
|
790
|
+
{ cache: false }
|
|
791
|
+
);
|
|
792
|
+
loaders.set(key, dl);
|
|
793
|
+
}
|
|
794
|
+
return dl;
|
|
795
|
+
}
|
|
796
|
+
return {
|
|
797
|
+
async load(typeName, fieldName, parent) {
|
|
798
|
+
const rel = relationsFor(typeName)[fieldName];
|
|
799
|
+
if (!rel) return null;
|
|
800
|
+
return loaderFor(typeName, fieldName, rel).load(parent);
|
|
801
|
+
}
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
var import_dataloader;
|
|
805
|
+
var init_dataloaders = __esm({
|
|
806
|
+
"src/graphql/dataloaders.ts"() {
|
|
807
|
+
"use strict";
|
|
808
|
+
import_dataloader = __toESM(require("dataloader"));
|
|
809
|
+
init_relations();
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
// src/graphql/security.ts
|
|
814
|
+
function buildArmorPlugin(options) {
|
|
815
|
+
const armor = new import_graphql_armor.EnvelopArmor({
|
|
816
|
+
maxDepth: { n: options.maxDepth },
|
|
817
|
+
costLimit: { maxCost: options.maxComplexity },
|
|
818
|
+
maxAliases: { n: 15 },
|
|
819
|
+
maxDirectives: { n: 50 },
|
|
820
|
+
blockFieldSuggestion: { enabled: true }
|
|
821
|
+
// maxTokens is intentionally left at Armor's secure default (n=1000) to bound
|
|
822
|
+
// GraphQL document token count at parse time, a layer the depth/cost/alias/directive
|
|
823
|
+
// limits do not cover.
|
|
824
|
+
});
|
|
825
|
+
return armor.protect();
|
|
826
|
+
}
|
|
827
|
+
function introspectionPlugin(enabled) {
|
|
828
|
+
return {
|
|
829
|
+
onValidate({ addValidationRule }) {
|
|
830
|
+
if (!enabled) addValidationRule(import_graphql6.NoSchemaIntrospectionCustomRule);
|
|
831
|
+
}
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
var import_graphql_armor, import_graphql6;
|
|
835
|
+
var init_security = __esm({
|
|
836
|
+
"src/graphql/security.ts"() {
|
|
837
|
+
"use strict";
|
|
838
|
+
import_graphql_armor = require("@escape.tech/graphql-armor");
|
|
839
|
+
import_graphql6 = require("graphql");
|
|
840
|
+
}
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
// src/graphql/handler.ts
|
|
844
|
+
var handler_exports = {};
|
|
845
|
+
__export(handler_exports, {
|
|
846
|
+
createGraphQLHandler: () => createGraphQLHandler
|
|
847
|
+
});
|
|
848
|
+
function createGraphQLHandler(registry, repos, resolver, db, options) {
|
|
849
|
+
const schema = buildGraphQLSchema(registry);
|
|
850
|
+
const { plugins } = buildArmorPlugin({
|
|
851
|
+
maxDepth: options.maxDepth,
|
|
852
|
+
maxComplexity: options.maxComplexity
|
|
853
|
+
});
|
|
854
|
+
const yoga = (0, import_graphql_yoga.createYoga)({
|
|
855
|
+
schema,
|
|
856
|
+
graphqlEndpoint: "/graphql",
|
|
857
|
+
graphiql: options.graphiql,
|
|
858
|
+
landingPage: false,
|
|
859
|
+
// buildArmorPlugin's return type is `unknown[]` (see security.ts) since Armor's
|
|
860
|
+
// plugin shapes aren't part of its public typings; Yoga's `plugins` option only
|
|
861
|
+
// accepts its own `Plugin` type, so the cast bridges the two.
|
|
862
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
863
|
+
plugins: [...plugins, introspectionPlugin(options.introspection)],
|
|
864
|
+
context: () => ({
|
|
865
|
+
db,
|
|
866
|
+
registry,
|
|
867
|
+
repos,
|
|
868
|
+
resolver,
|
|
869
|
+
loaders: createRelationLoaders(db, registry),
|
|
870
|
+
programmaticMemo: /* @__PURE__ */ new WeakMap()
|
|
871
|
+
})
|
|
872
|
+
});
|
|
873
|
+
return (c) => yoga.fetch(c.req.raw, {});
|
|
874
|
+
}
|
|
875
|
+
var import_graphql_yoga;
|
|
876
|
+
var init_handler = __esm({
|
|
877
|
+
"src/graphql/handler.ts"() {
|
|
878
|
+
"use strict";
|
|
879
|
+
import_graphql_yoga = require("graphql-yoga");
|
|
880
|
+
init_schema();
|
|
881
|
+
init_dataloaders();
|
|
882
|
+
init_security();
|
|
883
|
+
}
|
|
884
|
+
});
|
|
885
|
+
|
|
30
886
|
// src/index.ts
|
|
31
887
|
var index_exports = {};
|
|
32
888
|
__export(index_exports, {
|
|
33
889
|
createAPIAdapter: () => createAPIAdapter,
|
|
34
890
|
createCmsApp: () => createCmsApp,
|
|
35
|
-
|
|
891
|
+
createProgrammaticResolver: () => createProgrammaticResolver,
|
|
892
|
+
createServer: () => createServer,
|
|
893
|
+
resolverKey: () => resolverKey,
|
|
894
|
+
validateResolverBindings: () => validateResolverBindings
|
|
36
895
|
});
|
|
37
896
|
module.exports = __toCommonJS(index_exports);
|
|
38
897
|
|
|
@@ -69,26 +928,43 @@ function createCorsMiddleware(corsConfig) {
|
|
|
69
928
|
}
|
|
70
929
|
|
|
71
930
|
// src/middleware/security-headers.ts
|
|
931
|
+
var GRAPHIQL_CDN = "https://unpkg.com";
|
|
72
932
|
function createSecurityHeadersMiddleware(options = {}) {
|
|
73
933
|
const connectSrc = ["'self'", ...options.connectSrc ?? []].join(" ");
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
934
|
+
function buildCsp(parts) {
|
|
935
|
+
return [
|
|
936
|
+
"default-src 'self'",
|
|
937
|
+
"img-src 'self' data: https:",
|
|
938
|
+
"media-src 'self' https:",
|
|
939
|
+
`style-src ${parts.styleSrc}`,
|
|
940
|
+
`script-src ${parts.scriptSrc}`,
|
|
941
|
+
"font-src 'self' data:",
|
|
942
|
+
`connect-src ${parts.connectSrc}`,
|
|
943
|
+
...parts.workerSrc ? [`worker-src ${parts.workerSrc}`] : [],
|
|
944
|
+
"frame-ancestors 'none'",
|
|
945
|
+
"base-uri 'self'",
|
|
946
|
+
"form-action 'self'"
|
|
947
|
+
].join("; ");
|
|
948
|
+
}
|
|
949
|
+
const csp = buildCsp({
|
|
950
|
+
scriptSrc: "'self'",
|
|
951
|
+
styleSrc: "'self' 'unsafe-inline'",
|
|
952
|
+
connectSrc
|
|
953
|
+
});
|
|
954
|
+
const graphiqlCsp = buildCsp({
|
|
955
|
+
scriptSrc: `'self' 'unsafe-inline' ${GRAPHIQL_CDN}`,
|
|
956
|
+
styleSrc: `'self' 'unsafe-inline' ${GRAPHIQL_CDN}`,
|
|
957
|
+
connectSrc: `${connectSrc} ${GRAPHIQL_CDN}`,
|
|
958
|
+
workerSrc: "'self' blob:"
|
|
959
|
+
});
|
|
960
|
+
const graphiqlPath = options.graphiqlPath;
|
|
86
961
|
return async function securityHeaders(c, next) {
|
|
87
962
|
await next();
|
|
88
963
|
c.res.headers.set("X-Content-Type-Options", "nosniff");
|
|
89
964
|
c.res.headers.set("X-Frame-Options", "DENY");
|
|
90
965
|
c.res.headers.set("Referrer-Policy", "no-referrer");
|
|
91
|
-
c.
|
|
966
|
+
const isGraphiql = graphiqlPath !== void 0 && c.req.path === graphiqlPath;
|
|
967
|
+
c.res.headers.set("Content-Security-Policy", isGraphiql ? graphiqlCsp : csp);
|
|
92
968
|
};
|
|
93
969
|
}
|
|
94
970
|
|
|
@@ -455,12 +1331,146 @@ function validateRoles(roles) {
|
|
|
455
1331
|
`Fatal: roles registry failed to build \u2014 duplicate role name "${role.name}". Run \`manguito validate\` to check your roles schema.`
|
|
456
1332
|
);
|
|
457
1333
|
}
|
|
458
|
-
namesSeen.add(role.name);
|
|
1334
|
+
namesSeen.add(role.name);
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
function buildRolesRegistry(roles) {
|
|
1338
|
+
validateRoles(roles);
|
|
1339
|
+
return Object.fromEntries(roles.map((r) => [r.name, r]));
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
// src/programmatic/resolve.ts
|
|
1343
|
+
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
1344
|
+
var LIST_CONCURRENCY = 10;
|
|
1345
|
+
function resolverKey(schema, field) {
|
|
1346
|
+
return `${schema}::${field}`;
|
|
1347
|
+
}
|
|
1348
|
+
function validateResolverBindings(registry, resolvers) {
|
|
1349
|
+
const declared = /* @__PURE__ */ new Set();
|
|
1350
|
+
const schemas = [
|
|
1351
|
+
...Object.values(registry.content_types),
|
|
1352
|
+
...Object.values(registry.taxonomy_types)
|
|
1353
|
+
];
|
|
1354
|
+
for (const schema of schemas) {
|
|
1355
|
+
for (const field of schema.fields) {
|
|
1356
|
+
if (field.field_type === "programmatic") {
|
|
1357
|
+
declared.add(resolverKey(schema.name, field.name));
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
const missing = [];
|
|
1362
|
+
for (const key of declared) if (!resolvers.has(key)) missing.push(key);
|
|
1363
|
+
const orphans = [];
|
|
1364
|
+
for (const key of resolvers.keys()) if (!declared.has(key)) orphans.push(key);
|
|
1365
|
+
if (missing.length === 0 && orphans.length === 0) return;
|
|
1366
|
+
const lines = ["\u2717 Programmatic field resolver bindings are invalid."];
|
|
1367
|
+
if (missing.length > 0) {
|
|
1368
|
+
lines.push("", ' Declared as `type: "programmatic"` but no resolver found:');
|
|
1369
|
+
for (const k of missing) lines.push(` - ${k} (add a programmaticField in src/programmatic)`);
|
|
1370
|
+
}
|
|
1371
|
+
if (orphans.length > 0) {
|
|
1372
|
+
lines.push("", " Resolver has no matching programmatic field in the schema:");
|
|
1373
|
+
for (const k of orphans) lines.push(` - ${k}`);
|
|
1374
|
+
}
|
|
1375
|
+
lines.push("", "Exiting.");
|
|
1376
|
+
throw new Error(lines.join("\n"));
|
|
1377
|
+
}
|
|
1378
|
+
function buildContext(row) {
|
|
1379
|
+
return {
|
|
1380
|
+
get: (name) => row[name],
|
|
1381
|
+
record: row
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
async function withTimeout(p, ms) {
|
|
1385
|
+
let timer;
|
|
1386
|
+
const timeout = new Promise((_, reject) => {
|
|
1387
|
+
timer = setTimeout(() => reject(new Error("resolver timeout")), ms);
|
|
1388
|
+
});
|
|
1389
|
+
try {
|
|
1390
|
+
return await Promise.race([p, timeout]);
|
|
1391
|
+
} finally {
|
|
1392
|
+
clearTimeout(timer);
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
async function runOne(def, row, onError) {
|
|
1396
|
+
const fallback = def.fallback ?? null;
|
|
1397
|
+
try {
|
|
1398
|
+
const result = await withTimeout(
|
|
1399
|
+
Promise.resolve(def.resolve(buildContext(row))),
|
|
1400
|
+
def.timeout ?? DEFAULT_TIMEOUT_MS
|
|
1401
|
+
);
|
|
1402
|
+
return { value: result === void 0 ? null : result, failed: false };
|
|
1403
|
+
} catch (err) {
|
|
1404
|
+
onError(
|
|
1405
|
+
`\u26A0 programmatic field ${resolverKey(def.schema, def.field)} failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1406
|
+
);
|
|
1407
|
+
return { value: fallback, failed: true };
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
async function runPool(items, limit, fn) {
|
|
1411
|
+
let i = 0;
|
|
1412
|
+
async function worker() {
|
|
1413
|
+
while (i < items.length) {
|
|
1414
|
+
const idx = i++;
|
|
1415
|
+
await fn(items[idx]);
|
|
1416
|
+
}
|
|
459
1417
|
}
|
|
1418
|
+
const workers = Array.from({ length: Math.min(limit, items.length) }, worker);
|
|
1419
|
+
await Promise.all(workers);
|
|
460
1420
|
}
|
|
461
|
-
function
|
|
462
|
-
|
|
463
|
-
|
|
1421
|
+
function createProgrammaticResolver(resolvers, options) {
|
|
1422
|
+
const onError = options?.onError ?? ((message) => {
|
|
1423
|
+
process.stderr.write(message + "\n");
|
|
1424
|
+
});
|
|
1425
|
+
const bySchema = /* @__PURE__ */ new Map();
|
|
1426
|
+
for (const def of resolvers.values()) {
|
|
1427
|
+
const arr = bySchema.get(def.schema) ?? [];
|
|
1428
|
+
arr.push(def);
|
|
1429
|
+
bySchema.set(def.schema, arr);
|
|
1430
|
+
}
|
|
1431
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1432
|
+
async function resolveField(def, row) {
|
|
1433
|
+
const itemId = row["id"] !== void 0 ? String(row["id"]) : "";
|
|
1434
|
+
if (def.cache && itemId) {
|
|
1435
|
+
const key = `${resolverKey(def.schema, def.field)}::${itemId}`;
|
|
1436
|
+
const now = Date.now();
|
|
1437
|
+
const hit = cache.get(key);
|
|
1438
|
+
if (hit && hit.expires > now) return hit.value;
|
|
1439
|
+
const { value: value2, failed } = await runOne(def, row, onError);
|
|
1440
|
+
if (!failed) cache.set(key, { value: value2, expires: now + def.cache.ttl * 1e3 });
|
|
1441
|
+
return value2;
|
|
1442
|
+
}
|
|
1443
|
+
const { value } = await runOne(def, row, onError);
|
|
1444
|
+
return value;
|
|
1445
|
+
}
|
|
1446
|
+
async function resolveItem(schema, row) {
|
|
1447
|
+
const defs = bySchema.get(schema);
|
|
1448
|
+
if (!defs || defs.length === 0) return { ...row };
|
|
1449
|
+
const out = { ...row };
|
|
1450
|
+
await Promise.all(
|
|
1451
|
+
defs.map(async (def) => {
|
|
1452
|
+
out[def.field] = await resolveField(def, row);
|
|
1453
|
+
})
|
|
1454
|
+
);
|
|
1455
|
+
return out;
|
|
1456
|
+
}
|
|
1457
|
+
async function resolveList(schema, rows) {
|
|
1458
|
+
const defs = (bySchema.get(schema) ?? []).filter((d) => d.on_list === true);
|
|
1459
|
+
if (defs.length === 0) return rows.map((r) => ({ ...r }));
|
|
1460
|
+
const out = rows.map((r) => ({ ...r }));
|
|
1461
|
+
const tasks = [];
|
|
1462
|
+
for (let i = 0; i < rows.length; i++) {
|
|
1463
|
+
for (const def of defs) tasks.push({ rowIndex: i, def });
|
|
1464
|
+
}
|
|
1465
|
+
await runPool(tasks, LIST_CONCURRENCY, async ({ rowIndex, def }) => {
|
|
1466
|
+
out[rowIndex][def.field] = await resolveField(def, rows[rowIndex]);
|
|
1467
|
+
});
|
|
1468
|
+
return out;
|
|
1469
|
+
}
|
|
1470
|
+
function hasSchema(schema) {
|
|
1471
|
+
return bySchema.has(schema);
|
|
1472
|
+
}
|
|
1473
|
+
return { resolveItem, resolveList, hasSchema };
|
|
464
1474
|
}
|
|
465
1475
|
|
|
466
1476
|
// src/routes/query-params.ts
|
|
@@ -514,7 +1524,7 @@ function parseFilters(url, validFields) {
|
|
|
514
1524
|
function isPublished(item) {
|
|
515
1525
|
return item["published"] === true;
|
|
516
1526
|
}
|
|
517
|
-
function registerPublicContentRoutes(app, registry, repos, listRateLimit) {
|
|
1527
|
+
function registerPublicContentRoutes(app, registry, repos, listRateLimit, resolver) {
|
|
518
1528
|
function registerListRoute(path, handler) {
|
|
519
1529
|
if (listRateLimit) {
|
|
520
1530
|
app.get(path, listRateLimit, handler);
|
|
@@ -541,13 +1551,13 @@ function registerPublicContentRoutes(app, registry, repos, listRateLimit) {
|
|
|
541
1551
|
const basePath = contentType.default_base_path;
|
|
542
1552
|
const repo = repos[typeName];
|
|
543
1553
|
if (!repo) continue;
|
|
544
|
-
const schemaFieldNames = /* @__PURE__ */ new Set([
|
|
545
|
-
...contentType.fields.map((f) => f.name),
|
|
546
|
-
...contentType.system_fields.map((f) => f.name)
|
|
547
|
-
]);
|
|
548
1554
|
const relationFieldNames = new Set(
|
|
549
1555
|
contentType.fields.filter((f) => RELATION_FIELD_TYPES.has(f.field_type)).map((f) => f.name)
|
|
550
1556
|
);
|
|
1557
|
+
const filterableFieldNames = /* @__PURE__ */ new Set([
|
|
1558
|
+
...contentType.fields.filter((f) => f.field_type !== "programmatic").map((f) => f.name),
|
|
1559
|
+
...contentType.system_fields.map((f) => f.name)
|
|
1560
|
+
]);
|
|
551
1561
|
if (contentType.only_one) {
|
|
552
1562
|
app.get(`/api/${basePath}`, async (c) => {
|
|
553
1563
|
const result = await repo.findMany({ published_only: true, page: 1, per_page: 1 });
|
|
@@ -557,7 +1567,9 @@ function registerPublicContentRoutes(app, registry, repos, listRateLimit) {
|
|
|
557
1567
|
404
|
|
558
1568
|
);
|
|
559
1569
|
}
|
|
560
|
-
|
|
1570
|
+
let data = result.data[0];
|
|
1571
|
+
if (resolver?.hasSchema(typeName)) data = await resolver.resolveItem(typeName, data);
|
|
1572
|
+
return c.json({ ok: true, data });
|
|
561
1573
|
});
|
|
562
1574
|
} else {
|
|
563
1575
|
registerListRoute(`/api/${basePath}`, async (c) => {
|
|
@@ -600,7 +1612,7 @@ function registerPublicContentRoutes(app, registry, repos, listRateLimit) {
|
|
|
600
1612
|
400
|
|
601
1613
|
);
|
|
602
1614
|
}
|
|
603
|
-
const filtersResult = parseFilters(c.req.url,
|
|
1615
|
+
const filtersResult = parseFilters(c.req.url, filterableFieldNames);
|
|
604
1616
|
if (!filtersResult.ok) {
|
|
605
1617
|
return c.json(
|
|
606
1618
|
{
|
|
@@ -637,6 +1649,10 @@ function registerPublicContentRoutes(app, registry, repos, listRateLimit) {
|
|
|
637
1649
|
filters: filtersResult.filters,
|
|
638
1650
|
include
|
|
639
1651
|
});
|
|
1652
|
+
if (resolver?.hasSchema(typeName)) {
|
|
1653
|
+
const resolved = await resolver.resolveList(typeName, result.data);
|
|
1654
|
+
return c.json({ ...result, data: resolved });
|
|
1655
|
+
}
|
|
640
1656
|
return c.json(result);
|
|
641
1657
|
});
|
|
642
1658
|
app.get(`/api/${basePath}/:slug`, async (c) => {
|
|
@@ -666,7 +1682,9 @@ function registerPublicContentRoutes(app, registry, repos, listRateLimit) {
|
|
|
666
1682
|
404
|
|
667
1683
|
);
|
|
668
1684
|
}
|
|
669
|
-
|
|
1685
|
+
let data = item;
|
|
1686
|
+
if (resolver?.hasSchema(typeName)) data = await resolver.resolveItem(typeName, data);
|
|
1687
|
+
return c.json({ ok: true, data });
|
|
670
1688
|
});
|
|
671
1689
|
}
|
|
672
1690
|
}
|
|
@@ -692,6 +1710,10 @@ function registerPublicContentRoutes(app, registry, repos, listRateLimit) {
|
|
|
692
1710
|
page: pagination.page,
|
|
693
1711
|
per_page: pagination.per_page
|
|
694
1712
|
});
|
|
1713
|
+
if (resolver?.hasSchema(typeName)) {
|
|
1714
|
+
const resolved = await resolver.resolveList(typeName, result.data);
|
|
1715
|
+
return c.json({ ...result, data: resolved });
|
|
1716
|
+
}
|
|
695
1717
|
return c.json(result);
|
|
696
1718
|
});
|
|
697
1719
|
app.get(`/api/taxonomy/${typeName}/:id`, async (c) => {
|
|
@@ -706,7 +1728,9 @@ function registerPublicContentRoutes(app, registry, repos, listRateLimit) {
|
|
|
706
1728
|
404
|
|
707
1729
|
);
|
|
708
1730
|
}
|
|
709
|
-
|
|
1731
|
+
let data = item;
|
|
1732
|
+
if (resolver?.hasSchema(typeName)) data = await resolver.resolveItem(typeName, data);
|
|
1733
|
+
return c.json({ ok: true, data });
|
|
710
1734
|
});
|
|
711
1735
|
}
|
|
712
1736
|
}
|
|
@@ -811,228 +1835,8 @@ async function applyMediaReferenceDelta(delta, mediaRepo) {
|
|
|
811
1835
|
if (netRemove.length > 0) await mediaRepo.decrementReferenceCount(netRemove);
|
|
812
1836
|
}
|
|
813
1837
|
|
|
814
|
-
// src/relations.ts
|
|
815
|
-
var import_node_crypto = __toESM(require("crypto"));
|
|
816
|
-
var import_drizzle_orm2 = require("drizzle-orm");
|
|
817
|
-
function quoteIdent(name) {
|
|
818
|
-
if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`);
|
|
819
|
-
return `"${name}"`;
|
|
820
|
-
}
|
|
821
|
-
function paragraphMediaFieldNames(pType) {
|
|
822
|
-
return new Set(
|
|
823
|
-
pType.fields.filter((f) => f.field_type === "image" || f.field_type === "video" || f.field_type === "file").map((f) => f.name)
|
|
824
|
-
);
|
|
825
|
-
}
|
|
826
|
-
async function fetchParagraphMediaIds(db, pType, itemId, fieldName) {
|
|
827
|
-
const mediaColumns = pType.fields.filter((f) => f.field_type === "image" || f.field_type === "video" || f.field_type === "file").map((f) => f.db_column.column_name);
|
|
828
|
-
if (mediaColumns.length === 0) return [];
|
|
829
|
-
const tbl = import_drizzle_orm2.sql.raw(quoteIdent(pType.db.table_name));
|
|
830
|
-
const cols = import_drizzle_orm2.sql.join(mediaColumns.map((c) => import_drizzle_orm2.sql.raw(quoteIdent(c))), import_drizzle_orm2.sql`, `);
|
|
831
|
-
const result = await db.execute(
|
|
832
|
-
import_drizzle_orm2.sql`SELECT ${cols} FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`
|
|
833
|
-
);
|
|
834
|
-
const ids = [];
|
|
835
|
-
for (const row of result.rows) {
|
|
836
|
-
for (const col of mediaColumns) {
|
|
837
|
-
const v = row[col];
|
|
838
|
-
if (typeof v === "string" && v !== "") ids.push(v);
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
return ids;
|
|
842
|
-
}
|
|
843
|
-
async function persistParagraphField(db, itemId, parentType, fieldName, pType, items) {
|
|
844
|
-
const removed = await fetchParagraphMediaIds(db, pType, itemId, fieldName);
|
|
845
|
-
const tbl = import_drizzle_orm2.sql.raw(quoteIdent(pType.db.table_name));
|
|
846
|
-
await db.execute(import_drizzle_orm2.sql`DELETE FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`);
|
|
847
|
-
const mediaFieldNames = paragraphMediaFieldNames(pType);
|
|
848
|
-
const added = [];
|
|
849
|
-
for (let i = 0; i < items.length; i++) {
|
|
850
|
-
const pItem = items[i];
|
|
851
|
-
const data = {
|
|
852
|
-
id: import_node_crypto.default.randomUUID(),
|
|
853
|
-
parent_id: itemId,
|
|
854
|
-
parent_type: parentType,
|
|
855
|
-
parent_field: fieldName,
|
|
856
|
-
order: i,
|
|
857
|
-
created_at: /* @__PURE__ */ new Date(),
|
|
858
|
-
updated_at: /* @__PURE__ */ new Date()
|
|
859
|
-
};
|
|
860
|
-
for (const pf of pType.fields) {
|
|
861
|
-
if (!pf.db_column || pf.db_column.junction) continue;
|
|
862
|
-
data[pf.db_column.column_name] = pItem[pf.name] ?? null;
|
|
863
|
-
}
|
|
864
|
-
const cols = import_drizzle_orm2.sql.join(Object.keys(data).map((k) => import_drizzle_orm2.sql.raw(quoteIdent(k))), import_drizzle_orm2.sql`, `);
|
|
865
|
-
const vals = import_drizzle_orm2.sql.join(Object.values(data).map((v) => import_drizzle_orm2.sql`${v}`), import_drizzle_orm2.sql`, `);
|
|
866
|
-
await db.execute(import_drizzle_orm2.sql`INSERT INTO ${tbl} (${cols}) VALUES (${vals})`);
|
|
867
|
-
for (const name of mediaFieldNames) {
|
|
868
|
-
const val = pItem[name];
|
|
869
|
-
if (typeof val === "string" && val !== "") added.push(val);
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
return { added, removed };
|
|
873
|
-
}
|
|
874
|
-
async function deleteParagraphField(db, itemId, fieldName, pType) {
|
|
875
|
-
const removed = await fetchParagraphMediaIds(db, pType, itemId, fieldName);
|
|
876
|
-
const tbl = import_drizzle_orm2.sql.raw(quoteIdent(pType.db.table_name));
|
|
877
|
-
await db.execute(import_drizzle_orm2.sql`DELETE FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`);
|
|
878
|
-
return { added: [], removed };
|
|
879
|
-
}
|
|
880
|
-
async function persistJunctionField(db, itemId, junction, relatedIds) {
|
|
881
|
-
const tbl = import_drizzle_orm2.sql.raw(quoteIdent(junction.table_name));
|
|
882
|
-
const left = import_drizzle_orm2.sql.raw(quoteIdent(junction.left_column));
|
|
883
|
-
const right = import_drizzle_orm2.sql.raw(quoteIdent(junction.right_column));
|
|
884
|
-
await db.execute(import_drizzle_orm2.sql`DELETE FROM ${tbl} WHERE ${left} = ${itemId}`);
|
|
885
|
-
for (const rid of relatedIds) {
|
|
886
|
-
await db.execute(import_drizzle_orm2.sql`INSERT INTO ${tbl} (${left}, ${right}) VALUES (${itemId}, ${rid})`);
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
function buildRelationsMap(fields, registry) {
|
|
890
|
-
const relations = {};
|
|
891
|
-
for (const field of fields) {
|
|
892
|
-
if (field.field_type === "image" || field.field_type === "video" || field.field_type === "file") {
|
|
893
|
-
if (!field.db_column) continue;
|
|
894
|
-
relations[field.name] = { type: "media", fk_column: field.db_column.column_name };
|
|
895
|
-
} else if (field.field_type === "paragraph") {
|
|
896
|
-
const ref = field.ui_component.ref;
|
|
897
|
-
const pType = ref ? registry.paragraph_types[ref] : void 0;
|
|
898
|
-
if (!pType) continue;
|
|
899
|
-
relations[field.name] = { type: "paragraph", table: pType.db.table_name };
|
|
900
|
-
} else if (field.field_type === "reference") {
|
|
901
|
-
if (!field.db_column) continue;
|
|
902
|
-
if (field.db_column.junction) {
|
|
903
|
-
const j = field.db_column.junction;
|
|
904
|
-
relations[field.name] = {
|
|
905
|
-
type: "junction",
|
|
906
|
-
table: j.right_table,
|
|
907
|
-
junction_table: j.table_name,
|
|
908
|
-
left_column: j.left_column,
|
|
909
|
-
right_column: j.right_column,
|
|
910
|
-
order_column: j.order_column
|
|
911
|
-
};
|
|
912
|
-
} else if (field.db_column.foreign_key) {
|
|
913
|
-
relations[field.name] = {
|
|
914
|
-
type: "reference",
|
|
915
|
-
table: field.db_column.foreign_key.table,
|
|
916
|
-
fk_column: field.db_column.column_name
|
|
917
|
-
};
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
return relations;
|
|
922
|
-
}
|
|
923
|
-
function groupBy(items, key) {
|
|
924
|
-
const result = {};
|
|
925
|
-
for (const item of items) {
|
|
926
|
-
const k = String(item[key]);
|
|
927
|
-
if (!result[k]) result[k] = [];
|
|
928
|
-
result[k].push(item);
|
|
929
|
-
}
|
|
930
|
-
return result;
|
|
931
|
-
}
|
|
932
|
-
async function resolveRelationField(db, rows, fieldName, rel, cache) {
|
|
933
|
-
if (rows.length === 0) return;
|
|
934
|
-
if (rel.type === "paragraph") {
|
|
935
|
-
const parentIds = rows.map((r) => r["id"]);
|
|
936
|
-
const inList = import_drizzle_orm2.sql.join(parentIds.map((id) => import_drizzle_orm2.sql`${id}`), import_drizzle_orm2.sql`, `);
|
|
937
|
-
const result = await db.execute(
|
|
938
|
-
import_drizzle_orm2.sql`SELECT * FROM ${import_drizzle_orm2.sql.raw(quoteIdent(rel.table))} WHERE parent_id IN (${inList}) ORDER BY "order" ASC`
|
|
939
|
-
);
|
|
940
|
-
const byParent = groupBy(result.rows, "parent_id");
|
|
941
|
-
for (const row of rows) {
|
|
942
|
-
row[fieldName] = byParent[row["id"]] ?? [];
|
|
943
|
-
}
|
|
944
|
-
} else if (rel.type === "reference") {
|
|
945
|
-
const fkValues = rows.map((r) => r[rel.fk_column]).filter(Boolean);
|
|
946
|
-
if (fkValues.length === 0) {
|
|
947
|
-
for (const row of rows) row[fieldName] = null;
|
|
948
|
-
return;
|
|
949
|
-
}
|
|
950
|
-
const unique = [...new Set(fkValues)];
|
|
951
|
-
const uncached = unique.filter((id) => !cache.has(`${rel.table}:${id}`));
|
|
952
|
-
if (uncached.length > 0) {
|
|
953
|
-
const inList = import_drizzle_orm2.sql.join(uncached.map((id) => import_drizzle_orm2.sql`${id}`), import_drizzle_orm2.sql`, `);
|
|
954
|
-
const result = await db.execute(
|
|
955
|
-
import_drizzle_orm2.sql`SELECT * FROM ${import_drizzle_orm2.sql.raw(quoteIdent(rel.table))} WHERE id IN (${inList})`
|
|
956
|
-
);
|
|
957
|
-
for (const item of result.rows) {
|
|
958
|
-
cache.set(`${rel.table}:${item["id"]}`, item);
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
for (const row of rows) {
|
|
962
|
-
const fkVal = row[rel.fk_column];
|
|
963
|
-
row[fieldName] = fkVal ? cache.get(`${rel.table}:${fkVal}`) ?? null : null;
|
|
964
|
-
}
|
|
965
|
-
} else if (rel.type === "junction") {
|
|
966
|
-
const parentIds = rows.map((r) => r["id"]);
|
|
967
|
-
const inList = import_drizzle_orm2.sql.join(parentIds.map((id) => import_drizzle_orm2.sql`${id}`), import_drizzle_orm2.sql`, `);
|
|
968
|
-
const orderBy = rel.order_column ? import_drizzle_orm2.sql` ORDER BY "order" ASC` : import_drizzle_orm2.sql``;
|
|
969
|
-
const junctionResult = await db.execute(
|
|
970
|
-
import_drizzle_orm2.sql`SELECT * FROM ${import_drizzle_orm2.sql.raw(quoteIdent(rel.junction_table))} WHERE ${import_drizzle_orm2.sql.raw(quoteIdent(rel.left_column))} IN (${inList})${orderBy}`
|
|
971
|
-
);
|
|
972
|
-
const jRows = junctionResult.rows;
|
|
973
|
-
const rightIds = [...new Set(jRows.map((r) => r[rel.right_column]))];
|
|
974
|
-
const uncached = rightIds.filter((id) => !cache.has(`${rel.table}:${id}`));
|
|
975
|
-
if (uncached.length > 0) {
|
|
976
|
-
const rightInList = import_drizzle_orm2.sql.join(uncached.map((id) => import_drizzle_orm2.sql`${id}`), import_drizzle_orm2.sql`, `);
|
|
977
|
-
const entitiesResult = await db.execute(
|
|
978
|
-
import_drizzle_orm2.sql`SELECT * FROM ${import_drizzle_orm2.sql.raw(quoteIdent(rel.table))} WHERE id IN (${rightInList})`
|
|
979
|
-
);
|
|
980
|
-
for (const item of entitiesResult.rows) {
|
|
981
|
-
cache.set(`${rel.table}:${item["id"]}`, item);
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
const byLeft = groupBy(jRows, rel.left_column);
|
|
985
|
-
for (const row of rows) {
|
|
986
|
-
const jEntries = byLeft[row["id"]] ?? [];
|
|
987
|
-
row[fieldName] = jEntries.map((jr) => cache.get(`${rel.table}:${jr[rel.right_column]}`)).filter(Boolean);
|
|
988
|
-
}
|
|
989
|
-
} else if (rel.type === "media") {
|
|
990
|
-
const fkValues = rows.map((r) => r[rel.fk_column]).filter(Boolean);
|
|
991
|
-
if (fkValues.length === 0) {
|
|
992
|
-
for (const row of rows) row[fieldName] = null;
|
|
993
|
-
return;
|
|
994
|
-
}
|
|
995
|
-
const unique = [...new Set(fkValues)];
|
|
996
|
-
const uncached = unique.filter((id) => !cache.has(`media:${id}`));
|
|
997
|
-
if (uncached.length > 0) {
|
|
998
|
-
const inList = import_drizzle_orm2.sql.join(uncached.map((id) => import_drizzle_orm2.sql`${id}`), import_drizzle_orm2.sql`, `);
|
|
999
|
-
const result = await db.execute(import_drizzle_orm2.sql`SELECT * FROM "media" WHERE id IN (${inList})`);
|
|
1000
|
-
for (const item of result.rows) {
|
|
1001
|
-
cache.set(`media:${item["id"]}`, item);
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
for (const row of rows) {
|
|
1005
|
-
const fkVal = row[rel.fk_column];
|
|
1006
|
-
row[fieldName] = fkVal ? cache.get(`media:${fkVal}`) ?? null : null;
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
async function resolveRelationBareIds(db, rows, fieldName, rel) {
|
|
1011
|
-
if (rows.length === 0) return;
|
|
1012
|
-
if (rel.type === "paragraph") {
|
|
1013
|
-
const parentIds = rows.map((r) => r["id"]);
|
|
1014
|
-
const inList = import_drizzle_orm2.sql.join(parentIds.map((id) => import_drizzle_orm2.sql`${id}`), import_drizzle_orm2.sql`, `);
|
|
1015
|
-
const result = await db.execute(
|
|
1016
|
-
import_drizzle_orm2.sql`SELECT id, parent_id FROM ${import_drizzle_orm2.sql.raw(quoteIdent(rel.table))} WHERE parent_id IN (${inList}) ORDER BY "order" ASC`
|
|
1017
|
-
);
|
|
1018
|
-
const byParent = groupBy(result.rows, "parent_id");
|
|
1019
|
-
for (const row of rows) {
|
|
1020
|
-
row[fieldName] = (byParent[row["id"]] ?? []).map((r) => r["id"]);
|
|
1021
|
-
}
|
|
1022
|
-
} else if (rel.type === "junction") {
|
|
1023
|
-
const parentIds = rows.map((r) => r["id"]);
|
|
1024
|
-
const inList = import_drizzle_orm2.sql.join(parentIds.map((id) => import_drizzle_orm2.sql`${id}`), import_drizzle_orm2.sql`, `);
|
|
1025
|
-
const result = await db.execute(
|
|
1026
|
-
import_drizzle_orm2.sql`SELECT ${import_drizzle_orm2.sql.raw(quoteIdent(rel.left_column))} AS left_id, ${import_drizzle_orm2.sql.raw(quoteIdent(rel.right_column))} AS right_id FROM ${import_drizzle_orm2.sql.raw(quoteIdent(rel.junction_table))} WHERE ${import_drizzle_orm2.sql.raw(quoteIdent(rel.left_column))} IN (${inList})`
|
|
1027
|
-
);
|
|
1028
|
-
const byLeft = groupBy(result.rows, "left_id");
|
|
1029
|
-
for (const row of rows) {
|
|
1030
|
-
row[fieldName] = (byLeft[row["id"]] ?? []).map((r) => r["right_id"]);
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
1838
|
// src/routes/admin/content.ts
|
|
1839
|
+
init_relations();
|
|
1036
1840
|
function quoteIdent2(name) {
|
|
1037
1841
|
if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`);
|
|
1038
1842
|
return `"${name}"`;
|
|
@@ -2662,7 +3466,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
2662
3466
|
var import_drizzle_orm6 = require("drizzle-orm");
|
|
2663
3467
|
var API_VERSION = (() => {
|
|
2664
3468
|
try {
|
|
2665
|
-
return "0.
|
|
3469
|
+
return "0.3.0";
|
|
2666
3470
|
} catch {
|
|
2667
3471
|
return "0.0.0";
|
|
2668
3472
|
}
|
|
@@ -2769,6 +3573,7 @@ function registerSchemaRoute(app, registry, db) {
|
|
|
2769
3573
|
// src/repositories/content.ts
|
|
2770
3574
|
var import_node_crypto3 = __toESM(require("crypto"));
|
|
2771
3575
|
var import_drizzle_orm8 = require("drizzle-orm");
|
|
3576
|
+
init_relations();
|
|
2772
3577
|
var SORTABLE_FIELDS2 = /* @__PURE__ */ new Set(["title", "created_at", "updated_at"]);
|
|
2773
3578
|
function codeError(code, message) {
|
|
2774
3579
|
const err = new Error(message);
|
|
@@ -2824,7 +3629,7 @@ function whereFragment(conds) {
|
|
|
2824
3629
|
return conds.length > 0 ? import_drizzle_orm8.sql` WHERE ${import_drizzle_orm8.sql.join(conds, import_drizzle_orm8.sql` AND `)}` : import_drizzle_orm8.sql``;
|
|
2825
3630
|
}
|
|
2826
3631
|
function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
2827
|
-
const { relations = {} } = options;
|
|
3632
|
+
const { relations = {}, publishedRelations = false } = options;
|
|
2828
3633
|
function tableRaw() {
|
|
2829
3634
|
return import_drizzle_orm8.sql.raw(quoteIdent3(tableName));
|
|
2830
3635
|
}
|
|
@@ -2837,7 +3642,7 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
|
2837
3642
|
}
|
|
2838
3643
|
for (const [fieldName, rel] of Object.entries(relations)) {
|
|
2839
3644
|
if (rel.type === "media") {
|
|
2840
|
-
await resolveRelationField(db, rows, fieldName, rel, cache);
|
|
3645
|
+
await resolveRelationField(db, rows, fieldName, rel, cache, publishedRelations);
|
|
2841
3646
|
} else if (!include.includes(fieldName)) {
|
|
2842
3647
|
await resolveRelationBareIds(db, rows, fieldName, rel);
|
|
2843
3648
|
}
|
|
@@ -2845,7 +3650,7 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
|
2845
3650
|
for (const fieldName of include) {
|
|
2846
3651
|
const rel = relations[fieldName];
|
|
2847
3652
|
if (rel.type !== "media") {
|
|
2848
|
-
await resolveRelationField(db, rows, fieldName, rel, cache);
|
|
3653
|
+
await resolveRelationField(db, rows, fieldName, rel, cache, publishedRelations);
|
|
2849
3654
|
}
|
|
2850
3655
|
}
|
|
2851
3656
|
return rows;
|
|
@@ -2966,6 +3771,9 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
|
2966
3771
|
};
|
|
2967
3772
|
}
|
|
2968
3773
|
|
|
3774
|
+
// src/app.ts
|
|
3775
|
+
init_relations();
|
|
3776
|
+
|
|
2969
3777
|
// src/repositories/media.ts
|
|
2970
3778
|
var import_node_crypto4 = __toESM(require("crypto"));
|
|
2971
3779
|
var import_drizzle_orm9 = require("drizzle-orm");
|
|
@@ -3078,9 +3886,19 @@ function createCmsApp(options) {
|
|
|
3078
3886
|
const cmsName = options.name ?? "Manguito CMS";
|
|
3079
3887
|
const maxFileSize = media?.max_file_size;
|
|
3080
3888
|
const rolesRegistry = buildRolesRegistry(registry.roles.roles);
|
|
3889
|
+
const resolverMap = options.resolvers ?? /* @__PURE__ */ new Map();
|
|
3890
|
+
validateResolverBindings(registry, resolverMap);
|
|
3891
|
+
const programmaticResolver = createProgrammaticResolver(resolverMap);
|
|
3081
3892
|
const app = new import_hono.Hono();
|
|
3082
3893
|
const uploadOrigins = storage.getUploadOrigins?.() ?? [];
|
|
3083
|
-
|
|
3894
|
+
const graphiqlEnabled = options.graphql?.enabled === true && options.graphql.graphiql === true;
|
|
3895
|
+
app.use(
|
|
3896
|
+
"*",
|
|
3897
|
+
createSecurityHeadersMiddleware({
|
|
3898
|
+
connectSrc: uploadOrigins,
|
|
3899
|
+
...graphiqlEnabled && { graphiqlPath: "/graphql" }
|
|
3900
|
+
})
|
|
3901
|
+
);
|
|
3084
3902
|
app.use("*", createCorsMiddleware(cors ?? { origin: "*", enabled: true }));
|
|
3085
3903
|
app.onError(errorHandler);
|
|
3086
3904
|
const listRateLimit = resolveListRateLimit(rateLimit);
|
|
@@ -3115,7 +3933,8 @@ function createCmsApp(options) {
|
|
|
3115
3933
|
Object.entries(registry.content_types).map(([typeName, ct]) => [
|
|
3116
3934
|
typeName,
|
|
3117
3935
|
createDrizzleContentRepository(db, ct.db.table_name, {
|
|
3118
|
-
relations: buildRelationsMap(ct.fields, registry)
|
|
3936
|
+
relations: buildRelationsMap(ct.fields, registry),
|
|
3937
|
+
publishedRelations: true
|
|
3119
3938
|
})
|
|
3120
3939
|
])
|
|
3121
3940
|
);
|
|
@@ -3123,7 +3942,8 @@ function createCmsApp(options) {
|
|
|
3123
3942
|
Object.entries(registry.taxonomy_types).map(([typeName, tt]) => [
|
|
3124
3943
|
typeName,
|
|
3125
3944
|
createDrizzleContentRepository(db, tt.db.table_name, {
|
|
3126
|
-
relations: buildRelationsMap(tt.fields, registry)
|
|
3945
|
+
relations: buildRelationsMap(tt.fields, registry),
|
|
3946
|
+
publishedRelations: true
|
|
3127
3947
|
})
|
|
3128
3948
|
])
|
|
3129
3949
|
);
|
|
@@ -3162,8 +3982,37 @@ function createCmsApp(options) {
|
|
|
3162
3982
|
paths: {}
|
|
3163
3983
|
})
|
|
3164
3984
|
);
|
|
3165
|
-
registerPublicContentRoutes(app, registry, publicRepos, listRateLimit);
|
|
3985
|
+
registerPublicContentRoutes(app, registry, publicRepos, listRateLimit, programmaticResolver);
|
|
3166
3986
|
registerPublicMediaRoutes(app, mediaRepo, listRateLimit);
|
|
3987
|
+
if (options.graphql?.enabled) {
|
|
3988
|
+
const gqlOptions = options.graphql;
|
|
3989
|
+
let gqlHandler = null;
|
|
3990
|
+
let initError = null;
|
|
3991
|
+
const ready = Promise.resolve().then(() => (init_handler(), handler_exports)).then(({ createGraphQLHandler: createGraphQLHandler2 }) => {
|
|
3992
|
+
gqlHandler = createGraphQLHandler2(registry, publicRepos, programmaticResolver, db, gqlOptions);
|
|
3993
|
+
}).catch((err) => {
|
|
3994
|
+
initError = err;
|
|
3995
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3996
|
+
process.stderr.write(`\u2717 GraphQL schema failed to initialize; /graphql will return 500. ${message}
|
|
3997
|
+
`);
|
|
3998
|
+
});
|
|
3999
|
+
const invokeGraphQL = async (c) => {
|
|
4000
|
+
if (!gqlHandler && !initError) await ready;
|
|
4001
|
+
if (!gqlHandler) {
|
|
4002
|
+
return c.json(
|
|
4003
|
+
{ ok: false, error: { code: "GRAPHQL_INIT_FAILED", message: "GraphQL schema failed to initialize" } },
|
|
4004
|
+
500
|
|
4005
|
+
);
|
|
4006
|
+
}
|
|
4007
|
+
return gqlHandler(c, async () => {
|
|
4008
|
+
});
|
|
4009
|
+
};
|
|
4010
|
+
if (listRateLimit) {
|
|
4011
|
+
app.all("/graphql", listRateLimit, invokeGraphQL);
|
|
4012
|
+
} else {
|
|
4013
|
+
app.all("/graphql", invokeGraphQL);
|
|
4014
|
+
}
|
|
4015
|
+
}
|
|
3167
4016
|
registerConfigRoute(
|
|
3168
4017
|
app,
|
|
3169
4018
|
{
|
|
@@ -3210,19 +4059,33 @@ function createServer(options = {}) {
|
|
|
3210
4059
|
}
|
|
3211
4060
|
|
|
3212
4061
|
// src/index.ts
|
|
4062
|
+
function resolveGraphQL(cfg) {
|
|
4063
|
+
const devDefault = process.env["NODE_ENV"] !== "production";
|
|
4064
|
+
return {
|
|
4065
|
+
enabled: cfg.enabled ?? false,
|
|
4066
|
+
maxDepth: cfg.maxDepth ?? 8,
|
|
4067
|
+
maxComplexity: cfg.maxComplexity ?? 1e3,
|
|
4068
|
+
graphiql: cfg.graphiql ?? devDefault,
|
|
4069
|
+
introspection: cfg.introspection ?? cfg.graphiql ?? devDefault
|
|
4070
|
+
};
|
|
4071
|
+
}
|
|
3213
4072
|
function createAPIAdapter(options = {}) {
|
|
3214
4073
|
const prefix = options.prefix ?? "/api";
|
|
3215
4074
|
const media = options.media?.max_file_size !== void 0 ? { max_file_size: options.media.max_file_size } : void 0;
|
|
3216
4075
|
return {
|
|
3217
4076
|
prefix,
|
|
3218
4077
|
...media !== void 0 && { media },
|
|
3219
|
-
...options.rateLimit !== void 0 && { rateLimit: options.rateLimit }
|
|
4078
|
+
...options.rateLimit !== void 0 && { rateLimit: options.rateLimit },
|
|
4079
|
+
...options.graphql !== void 0 && { graphql: resolveGraphQL(options.graphql) }
|
|
3220
4080
|
};
|
|
3221
4081
|
}
|
|
3222
4082
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3223
4083
|
0 && (module.exports = {
|
|
3224
4084
|
createAPIAdapter,
|
|
3225
4085
|
createCmsApp,
|
|
3226
|
-
|
|
4086
|
+
createProgrammaticResolver,
|
|
4087
|
+
createServer,
|
|
4088
|
+
resolverKey,
|
|
4089
|
+
validateResolverBindings
|
|
3227
4090
|
});
|
|
3228
4091
|
//# sourceMappingURL=index.js.map
|