@bobbykim/manguito-cms-api 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-FBS2RQI3.mjs +232 -0
- package/dist/chunk-FBS2RQI3.mjs.map +1 -0
- package/dist/chunk-QXMW74MR.mjs +589 -0
- package/dist/chunk-QXMW74MR.mjs.map +1 -0
- package/dist/graphql/index.d.mts +15 -0
- package/dist/graphql/index.d.ts +15 -0
- package/dist/graphql/index.js +729 -0
- package/dist/graphql/index.js.map +1 -0
- package/dist/graphql/index.mjs +8 -0
- package/dist/graphql/index.mjs.map +1 -0
- package/dist/handler-GMCFYZY5.mjs +8 -0
- package/dist/handler-GMCFYZY5.mjs.map +1 -0
- package/dist/index.d.mts +7 -14
- package/dist/index.d.ts +7 -14
- package/dist/index.js +959 -253
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +184 -330
- package/dist/index.mjs.map +1 -1
- package/dist/resolve-Dtsk27aI.d.mts +15 -0
- package/dist/resolve-Dtsk27aI.d.ts +15 -0
- package/package.json +12 -3
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 });
|
|
@@ -15,17 +18,870 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
18
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
19
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
20
|
}
|
|
18
|
-
return to;
|
|
19
|
-
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
+
mod
|
|
30
|
+
));
|
|
31
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
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
|
+
});
|
|
29
885
|
|
|
30
886
|
// src/index.ts
|
|
31
887
|
var index_exports = {};
|
|
@@ -72,26 +928,43 @@ function createCorsMiddleware(corsConfig) {
|
|
|
72
928
|
}
|
|
73
929
|
|
|
74
930
|
// src/middleware/security-headers.ts
|
|
931
|
+
var GRAPHIQL_CDN = "https://unpkg.com";
|
|
75
932
|
function createSecurityHeadersMiddleware(options = {}) {
|
|
76
933
|
const connectSrc = ["'self'", ...options.connectSrc ?? []].join(" ");
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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;
|
|
89
961
|
return async function securityHeaders(c, next) {
|
|
90
962
|
await next();
|
|
91
963
|
c.res.headers.set("X-Content-Type-Options", "nosniff");
|
|
92
964
|
c.res.headers.set("X-Frame-Options", "DENY");
|
|
93
965
|
c.res.headers.set("Referrer-Policy", "no-referrer");
|
|
94
|
-
c.
|
|
966
|
+
const isGraphiql = graphiqlPath !== void 0 && c.req.path === graphiqlPath;
|
|
967
|
+
c.res.headers.set("Content-Security-Policy", isGraphiql ? graphiqlCsp : csp);
|
|
95
968
|
};
|
|
96
969
|
}
|
|
97
970
|
|
|
@@ -962,228 +1835,8 @@ async function applyMediaReferenceDelta(delta, mediaRepo) {
|
|
|
962
1835
|
if (netRemove.length > 0) await mediaRepo.decrementReferenceCount(netRemove);
|
|
963
1836
|
}
|
|
964
1837
|
|
|
965
|
-
// src/relations.ts
|
|
966
|
-
var import_node_crypto = __toESM(require("crypto"));
|
|
967
|
-
var import_drizzle_orm2 = require("drizzle-orm");
|
|
968
|
-
function quoteIdent(name) {
|
|
969
|
-
if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`);
|
|
970
|
-
return `"${name}"`;
|
|
971
|
-
}
|
|
972
|
-
function paragraphMediaFieldNames(pType) {
|
|
973
|
-
return new Set(
|
|
974
|
-
pType.fields.filter((f) => f.field_type === "image" || f.field_type === "video" || f.field_type === "file").map((f) => f.name)
|
|
975
|
-
);
|
|
976
|
-
}
|
|
977
|
-
async function fetchParagraphMediaIds(db, pType, itemId, fieldName) {
|
|
978
|
-
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);
|
|
979
|
-
if (mediaColumns.length === 0) return [];
|
|
980
|
-
const tbl = import_drizzle_orm2.sql.raw(quoteIdent(pType.db.table_name));
|
|
981
|
-
const cols = import_drizzle_orm2.sql.join(mediaColumns.map((c) => import_drizzle_orm2.sql.raw(quoteIdent(c))), import_drizzle_orm2.sql`, `);
|
|
982
|
-
const result = await db.execute(
|
|
983
|
-
import_drizzle_orm2.sql`SELECT ${cols} FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`
|
|
984
|
-
);
|
|
985
|
-
const ids = [];
|
|
986
|
-
for (const row of result.rows) {
|
|
987
|
-
for (const col of mediaColumns) {
|
|
988
|
-
const v = row[col];
|
|
989
|
-
if (typeof v === "string" && v !== "") ids.push(v);
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
return ids;
|
|
993
|
-
}
|
|
994
|
-
async function persistParagraphField(db, itemId, parentType, fieldName, pType, items) {
|
|
995
|
-
const removed = await fetchParagraphMediaIds(db, pType, itemId, fieldName);
|
|
996
|
-
const tbl = import_drizzle_orm2.sql.raw(quoteIdent(pType.db.table_name));
|
|
997
|
-
await db.execute(import_drizzle_orm2.sql`DELETE FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`);
|
|
998
|
-
const mediaFieldNames = paragraphMediaFieldNames(pType);
|
|
999
|
-
const added = [];
|
|
1000
|
-
for (let i = 0; i < items.length; i++) {
|
|
1001
|
-
const pItem = items[i];
|
|
1002
|
-
const data = {
|
|
1003
|
-
id: import_node_crypto.default.randomUUID(),
|
|
1004
|
-
parent_id: itemId,
|
|
1005
|
-
parent_type: parentType,
|
|
1006
|
-
parent_field: fieldName,
|
|
1007
|
-
order: i,
|
|
1008
|
-
created_at: /* @__PURE__ */ new Date(),
|
|
1009
|
-
updated_at: /* @__PURE__ */ new Date()
|
|
1010
|
-
};
|
|
1011
|
-
for (const pf of pType.fields) {
|
|
1012
|
-
if (!pf.db_column || pf.db_column.junction) continue;
|
|
1013
|
-
data[pf.db_column.column_name] = pItem[pf.name] ?? null;
|
|
1014
|
-
}
|
|
1015
|
-
const cols = import_drizzle_orm2.sql.join(Object.keys(data).map((k) => import_drizzle_orm2.sql.raw(quoteIdent(k))), import_drizzle_orm2.sql`, `);
|
|
1016
|
-
const vals = import_drizzle_orm2.sql.join(Object.values(data).map((v) => import_drizzle_orm2.sql`${v}`), import_drizzle_orm2.sql`, `);
|
|
1017
|
-
await db.execute(import_drizzle_orm2.sql`INSERT INTO ${tbl} (${cols}) VALUES (${vals})`);
|
|
1018
|
-
for (const name of mediaFieldNames) {
|
|
1019
|
-
const val = pItem[name];
|
|
1020
|
-
if (typeof val === "string" && val !== "") added.push(val);
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
return { added, removed };
|
|
1024
|
-
}
|
|
1025
|
-
async function deleteParagraphField(db, itemId, fieldName, pType) {
|
|
1026
|
-
const removed = await fetchParagraphMediaIds(db, pType, itemId, fieldName);
|
|
1027
|
-
const tbl = import_drizzle_orm2.sql.raw(quoteIdent(pType.db.table_name));
|
|
1028
|
-
await db.execute(import_drizzle_orm2.sql`DELETE FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`);
|
|
1029
|
-
return { added: [], removed };
|
|
1030
|
-
}
|
|
1031
|
-
async function persistJunctionField(db, itemId, junction, relatedIds) {
|
|
1032
|
-
const tbl = import_drizzle_orm2.sql.raw(quoteIdent(junction.table_name));
|
|
1033
|
-
const left = import_drizzle_orm2.sql.raw(quoteIdent(junction.left_column));
|
|
1034
|
-
const right = import_drizzle_orm2.sql.raw(quoteIdent(junction.right_column));
|
|
1035
|
-
await db.execute(import_drizzle_orm2.sql`DELETE FROM ${tbl} WHERE ${left} = ${itemId}`);
|
|
1036
|
-
for (const rid of relatedIds) {
|
|
1037
|
-
await db.execute(import_drizzle_orm2.sql`INSERT INTO ${tbl} (${left}, ${right}) VALUES (${itemId}, ${rid})`);
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
function buildRelationsMap(fields, registry) {
|
|
1041
|
-
const relations = {};
|
|
1042
|
-
for (const field of fields) {
|
|
1043
|
-
if (field.field_type === "image" || field.field_type === "video" || field.field_type === "file") {
|
|
1044
|
-
if (!field.db_column) continue;
|
|
1045
|
-
relations[field.name] = { type: "media", fk_column: field.db_column.column_name };
|
|
1046
|
-
} else if (field.field_type === "paragraph") {
|
|
1047
|
-
const ref = field.ui_component.ref;
|
|
1048
|
-
const pType = ref ? registry.paragraph_types[ref] : void 0;
|
|
1049
|
-
if (!pType) continue;
|
|
1050
|
-
relations[field.name] = { type: "paragraph", table: pType.db.table_name };
|
|
1051
|
-
} else if (field.field_type === "reference") {
|
|
1052
|
-
if (!field.db_column) continue;
|
|
1053
|
-
if (field.db_column.junction) {
|
|
1054
|
-
const j = field.db_column.junction;
|
|
1055
|
-
relations[field.name] = {
|
|
1056
|
-
type: "junction",
|
|
1057
|
-
table: j.right_table,
|
|
1058
|
-
junction_table: j.table_name,
|
|
1059
|
-
left_column: j.left_column,
|
|
1060
|
-
right_column: j.right_column,
|
|
1061
|
-
order_column: j.order_column
|
|
1062
|
-
};
|
|
1063
|
-
} else if (field.db_column.foreign_key) {
|
|
1064
|
-
relations[field.name] = {
|
|
1065
|
-
type: "reference",
|
|
1066
|
-
table: field.db_column.foreign_key.table,
|
|
1067
|
-
fk_column: field.db_column.column_name
|
|
1068
|
-
};
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
return relations;
|
|
1073
|
-
}
|
|
1074
|
-
function groupBy(items, key) {
|
|
1075
|
-
const result = {};
|
|
1076
|
-
for (const item of items) {
|
|
1077
|
-
const k = String(item[key]);
|
|
1078
|
-
if (!result[k]) result[k] = [];
|
|
1079
|
-
result[k].push(item);
|
|
1080
|
-
}
|
|
1081
|
-
return result;
|
|
1082
|
-
}
|
|
1083
|
-
async function resolveRelationField(db, rows, fieldName, rel, cache) {
|
|
1084
|
-
if (rows.length === 0) return;
|
|
1085
|
-
if (rel.type === "paragraph") {
|
|
1086
|
-
const parentIds = rows.map((r) => r["id"]);
|
|
1087
|
-
const inList = import_drizzle_orm2.sql.join(parentIds.map((id) => import_drizzle_orm2.sql`${id}`), import_drizzle_orm2.sql`, `);
|
|
1088
|
-
const result = await db.execute(
|
|
1089
|
-
import_drizzle_orm2.sql`SELECT * FROM ${import_drizzle_orm2.sql.raw(quoteIdent(rel.table))} WHERE parent_id IN (${inList}) ORDER BY "order" ASC`
|
|
1090
|
-
);
|
|
1091
|
-
const byParent = groupBy(result.rows, "parent_id");
|
|
1092
|
-
for (const row of rows) {
|
|
1093
|
-
row[fieldName] = byParent[row["id"]] ?? [];
|
|
1094
|
-
}
|
|
1095
|
-
} else if (rel.type === "reference") {
|
|
1096
|
-
const fkValues = rows.map((r) => r[rel.fk_column]).filter(Boolean);
|
|
1097
|
-
if (fkValues.length === 0) {
|
|
1098
|
-
for (const row of rows) row[fieldName] = null;
|
|
1099
|
-
return;
|
|
1100
|
-
}
|
|
1101
|
-
const unique = [...new Set(fkValues)];
|
|
1102
|
-
const uncached = unique.filter((id) => !cache.has(`${rel.table}:${id}`));
|
|
1103
|
-
if (uncached.length > 0) {
|
|
1104
|
-
const inList = import_drizzle_orm2.sql.join(uncached.map((id) => import_drizzle_orm2.sql`${id}`), import_drizzle_orm2.sql`, `);
|
|
1105
|
-
const result = await db.execute(
|
|
1106
|
-
import_drizzle_orm2.sql`SELECT * FROM ${import_drizzle_orm2.sql.raw(quoteIdent(rel.table))} WHERE id IN (${inList})`
|
|
1107
|
-
);
|
|
1108
|
-
for (const item of result.rows) {
|
|
1109
|
-
cache.set(`${rel.table}:${item["id"]}`, item);
|
|
1110
|
-
}
|
|
1111
|
-
}
|
|
1112
|
-
for (const row of rows) {
|
|
1113
|
-
const fkVal = row[rel.fk_column];
|
|
1114
|
-
row[fieldName] = fkVal ? cache.get(`${rel.table}:${fkVal}`) ?? null : null;
|
|
1115
|
-
}
|
|
1116
|
-
} else if (rel.type === "junction") {
|
|
1117
|
-
const parentIds = rows.map((r) => r["id"]);
|
|
1118
|
-
const inList = import_drizzle_orm2.sql.join(parentIds.map((id) => import_drizzle_orm2.sql`${id}`), import_drizzle_orm2.sql`, `);
|
|
1119
|
-
const orderBy = rel.order_column ? import_drizzle_orm2.sql` ORDER BY "order" ASC` : import_drizzle_orm2.sql``;
|
|
1120
|
-
const junctionResult = await db.execute(
|
|
1121
|
-
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}`
|
|
1122
|
-
);
|
|
1123
|
-
const jRows = junctionResult.rows;
|
|
1124
|
-
const rightIds = [...new Set(jRows.map((r) => r[rel.right_column]))];
|
|
1125
|
-
const uncached = rightIds.filter((id) => !cache.has(`${rel.table}:${id}`));
|
|
1126
|
-
if (uncached.length > 0) {
|
|
1127
|
-
const rightInList = import_drizzle_orm2.sql.join(uncached.map((id) => import_drizzle_orm2.sql`${id}`), import_drizzle_orm2.sql`, `);
|
|
1128
|
-
const entitiesResult = await db.execute(
|
|
1129
|
-
import_drizzle_orm2.sql`SELECT * FROM ${import_drizzle_orm2.sql.raw(quoteIdent(rel.table))} WHERE id IN (${rightInList})`
|
|
1130
|
-
);
|
|
1131
|
-
for (const item of entitiesResult.rows) {
|
|
1132
|
-
cache.set(`${rel.table}:${item["id"]}`, item);
|
|
1133
|
-
}
|
|
1134
|
-
}
|
|
1135
|
-
const byLeft = groupBy(jRows, rel.left_column);
|
|
1136
|
-
for (const row of rows) {
|
|
1137
|
-
const jEntries = byLeft[row["id"]] ?? [];
|
|
1138
|
-
row[fieldName] = jEntries.map((jr) => cache.get(`${rel.table}:${jr[rel.right_column]}`)).filter(Boolean);
|
|
1139
|
-
}
|
|
1140
|
-
} else if (rel.type === "media") {
|
|
1141
|
-
const fkValues = rows.map((r) => r[rel.fk_column]).filter(Boolean);
|
|
1142
|
-
if (fkValues.length === 0) {
|
|
1143
|
-
for (const row of rows) row[fieldName] = null;
|
|
1144
|
-
return;
|
|
1145
|
-
}
|
|
1146
|
-
const unique = [...new Set(fkValues)];
|
|
1147
|
-
const uncached = unique.filter((id) => !cache.has(`media:${id}`));
|
|
1148
|
-
if (uncached.length > 0) {
|
|
1149
|
-
const inList = import_drizzle_orm2.sql.join(uncached.map((id) => import_drizzle_orm2.sql`${id}`), import_drizzle_orm2.sql`, `);
|
|
1150
|
-
const result = await db.execute(import_drizzle_orm2.sql`SELECT * FROM "media" WHERE id IN (${inList})`);
|
|
1151
|
-
for (const item of result.rows) {
|
|
1152
|
-
cache.set(`media:${item["id"]}`, item);
|
|
1153
|
-
}
|
|
1154
|
-
}
|
|
1155
|
-
for (const row of rows) {
|
|
1156
|
-
const fkVal = row[rel.fk_column];
|
|
1157
|
-
row[fieldName] = fkVal ? cache.get(`media:${fkVal}`) ?? null : null;
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
async function resolveRelationBareIds(db, rows, fieldName, rel) {
|
|
1162
|
-
if (rows.length === 0) return;
|
|
1163
|
-
if (rel.type === "paragraph") {
|
|
1164
|
-
const parentIds = rows.map((r) => r["id"]);
|
|
1165
|
-
const inList = import_drizzle_orm2.sql.join(parentIds.map((id) => import_drizzle_orm2.sql`${id}`), import_drizzle_orm2.sql`, `);
|
|
1166
|
-
const result = await db.execute(
|
|
1167
|
-
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`
|
|
1168
|
-
);
|
|
1169
|
-
const byParent = groupBy(result.rows, "parent_id");
|
|
1170
|
-
for (const row of rows) {
|
|
1171
|
-
row[fieldName] = (byParent[row["id"]] ?? []).map((r) => r["id"]);
|
|
1172
|
-
}
|
|
1173
|
-
} else if (rel.type === "junction") {
|
|
1174
|
-
const parentIds = rows.map((r) => r["id"]);
|
|
1175
|
-
const inList = import_drizzle_orm2.sql.join(parentIds.map((id) => import_drizzle_orm2.sql`${id}`), import_drizzle_orm2.sql`, `);
|
|
1176
|
-
const result = await db.execute(
|
|
1177
|
-
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})`
|
|
1178
|
-
);
|
|
1179
|
-
const byLeft = groupBy(result.rows, "left_id");
|
|
1180
|
-
for (const row of rows) {
|
|
1181
|
-
row[fieldName] = (byLeft[row["id"]] ?? []).map((r) => r["right_id"]);
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
1838
|
// src/routes/admin/content.ts
|
|
1839
|
+
init_relations();
|
|
1187
1840
|
function quoteIdent2(name) {
|
|
1188
1841
|
if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`);
|
|
1189
1842
|
return `"${name}"`;
|
|
@@ -2813,7 +3466,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
2813
3466
|
var import_drizzle_orm6 = require("drizzle-orm");
|
|
2814
3467
|
var API_VERSION = (() => {
|
|
2815
3468
|
try {
|
|
2816
|
-
return "0.
|
|
3469
|
+
return "0.3.0";
|
|
2817
3470
|
} catch {
|
|
2818
3471
|
return "0.0.0";
|
|
2819
3472
|
}
|
|
@@ -2920,6 +3573,7 @@ function registerSchemaRoute(app, registry, db) {
|
|
|
2920
3573
|
// src/repositories/content.ts
|
|
2921
3574
|
var import_node_crypto3 = __toESM(require("crypto"));
|
|
2922
3575
|
var import_drizzle_orm8 = require("drizzle-orm");
|
|
3576
|
+
init_relations();
|
|
2923
3577
|
var SORTABLE_FIELDS2 = /* @__PURE__ */ new Set(["title", "created_at", "updated_at"]);
|
|
2924
3578
|
function codeError(code, message) {
|
|
2925
3579
|
const err = new Error(message);
|
|
@@ -2975,7 +3629,7 @@ function whereFragment(conds) {
|
|
|
2975
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``;
|
|
2976
3630
|
}
|
|
2977
3631
|
function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
2978
|
-
const { relations = {} } = options;
|
|
3632
|
+
const { relations = {}, publishedRelations = false } = options;
|
|
2979
3633
|
function tableRaw() {
|
|
2980
3634
|
return import_drizzle_orm8.sql.raw(quoteIdent3(tableName));
|
|
2981
3635
|
}
|
|
@@ -2988,7 +3642,7 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
|
2988
3642
|
}
|
|
2989
3643
|
for (const [fieldName, rel] of Object.entries(relations)) {
|
|
2990
3644
|
if (rel.type === "media") {
|
|
2991
|
-
await resolveRelationField(db, rows, fieldName, rel, cache);
|
|
3645
|
+
await resolveRelationField(db, rows, fieldName, rel, cache, publishedRelations);
|
|
2992
3646
|
} else if (!include.includes(fieldName)) {
|
|
2993
3647
|
await resolveRelationBareIds(db, rows, fieldName, rel);
|
|
2994
3648
|
}
|
|
@@ -2996,7 +3650,7 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
|
2996
3650
|
for (const fieldName of include) {
|
|
2997
3651
|
const rel = relations[fieldName];
|
|
2998
3652
|
if (rel.type !== "media") {
|
|
2999
|
-
await resolveRelationField(db, rows, fieldName, rel, cache);
|
|
3653
|
+
await resolveRelationField(db, rows, fieldName, rel, cache, publishedRelations);
|
|
3000
3654
|
}
|
|
3001
3655
|
}
|
|
3002
3656
|
return rows;
|
|
@@ -3117,6 +3771,9 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
|
3117
3771
|
};
|
|
3118
3772
|
}
|
|
3119
3773
|
|
|
3774
|
+
// src/app.ts
|
|
3775
|
+
init_relations();
|
|
3776
|
+
|
|
3120
3777
|
// src/repositories/media.ts
|
|
3121
3778
|
var import_node_crypto4 = __toESM(require("crypto"));
|
|
3122
3779
|
var import_drizzle_orm9 = require("drizzle-orm");
|
|
@@ -3234,7 +3891,14 @@ function createCmsApp(options) {
|
|
|
3234
3891
|
const programmaticResolver = createProgrammaticResolver(resolverMap);
|
|
3235
3892
|
const app = new import_hono.Hono();
|
|
3236
3893
|
const uploadOrigins = storage.getUploadOrigins?.() ?? [];
|
|
3237
|
-
|
|
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
|
+
);
|
|
3238
3902
|
app.use("*", createCorsMiddleware(cors ?? { origin: "*", enabled: true }));
|
|
3239
3903
|
app.onError(errorHandler);
|
|
3240
3904
|
const listRateLimit = resolveListRateLimit(rateLimit);
|
|
@@ -3269,7 +3933,8 @@ function createCmsApp(options) {
|
|
|
3269
3933
|
Object.entries(registry.content_types).map(([typeName, ct]) => [
|
|
3270
3934
|
typeName,
|
|
3271
3935
|
createDrizzleContentRepository(db, ct.db.table_name, {
|
|
3272
|
-
relations: buildRelationsMap(ct.fields, registry)
|
|
3936
|
+
relations: buildRelationsMap(ct.fields, registry),
|
|
3937
|
+
publishedRelations: true
|
|
3273
3938
|
})
|
|
3274
3939
|
])
|
|
3275
3940
|
);
|
|
@@ -3277,7 +3942,8 @@ function createCmsApp(options) {
|
|
|
3277
3942
|
Object.entries(registry.taxonomy_types).map(([typeName, tt]) => [
|
|
3278
3943
|
typeName,
|
|
3279
3944
|
createDrizzleContentRepository(db, tt.db.table_name, {
|
|
3280
|
-
relations: buildRelationsMap(tt.fields, registry)
|
|
3945
|
+
relations: buildRelationsMap(tt.fields, registry),
|
|
3946
|
+
publishedRelations: true
|
|
3281
3947
|
})
|
|
3282
3948
|
])
|
|
3283
3949
|
);
|
|
@@ -3318,6 +3984,35 @@ function createCmsApp(options) {
|
|
|
3318
3984
|
);
|
|
3319
3985
|
registerPublicContentRoutes(app, registry, publicRepos, listRateLimit, programmaticResolver);
|
|
3320
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
|
+
}
|
|
3321
4016
|
registerConfigRoute(
|
|
3322
4017
|
app,
|
|
3323
4018
|
{
|
|
@@ -3364,13 +4059,24 @@ function createServer(options = {}) {
|
|
|
3364
4059
|
}
|
|
3365
4060
|
|
|
3366
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
|
+
}
|
|
3367
4072
|
function createAPIAdapter(options = {}) {
|
|
3368
4073
|
const prefix = options.prefix ?? "/api";
|
|
3369
4074
|
const media = options.media?.max_file_size !== void 0 ? { max_file_size: options.media.max_file_size } : void 0;
|
|
3370
4075
|
return {
|
|
3371
4076
|
prefix,
|
|
3372
4077
|
...media !== void 0 && { media },
|
|
3373
|
-
...options.rateLimit !== void 0 && { rateLimit: options.rateLimit }
|
|
4078
|
+
...options.rateLimit !== void 0 && { rateLimit: options.rateLimit },
|
|
4079
|
+
...options.graphql !== void 0 && { graphql: resolveGraphQL(options.graphql) }
|
|
3374
4080
|
};
|
|
3375
4081
|
}
|
|
3376
4082
|
// Annotate the CommonJS export names for ESM import in node:
|