@bobbykim/manguito-cms-api 0.4.3 → 0.6.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-4Y4G564P.mjs → chunk-JVP33FI2.mjs} +99 -58
- package/dist/chunk-JVP33FI2.mjs.map +1 -0
- package/dist/{chunk-HEYT5CH5.mjs → chunk-XNXM4W4K.mjs} +147 -40
- package/dist/chunk-XNXM4W4K.mjs.map +1 -0
- package/dist/graphql/index.d.mts +7 -2
- package/dist/graphql/index.d.ts +7 -2
- package/dist/graphql/index.js +145 -38
- package/dist/graphql/index.js.map +1 -1
- package/dist/graphql/index.mjs +2 -2
- package/dist/handler-XZVDLKZ2.mjs +8 -0
- package/dist/index.d.mts +19 -3
- package/dist/index.d.ts +19 -3
- package/dist/index.js +557 -171
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +303 -76
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -4
- package/dist/chunk-4Y4G564P.mjs.map +0 -1
- package/dist/chunk-HEYT5CH5.mjs.map +0 -1
- package/dist/handler-45O5OX2C.mjs +0 -8
- /package/dist/{handler-45O5OX2C.mjs.map → handler-XZVDLKZ2.mjs.map} +0 -0
package/dist/index.mjs
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildRelationsMap,
|
|
3
3
|
createFieldKeyMap,
|
|
4
|
+
createFieldKeyMapFromProjection,
|
|
4
5
|
deleteParagraphField,
|
|
5
6
|
isColumnBacked,
|
|
6
7
|
persistJunctionField,
|
|
7
8
|
persistParagraphField,
|
|
8
9
|
resolveRelationBareIds,
|
|
9
10
|
resolveRelationField
|
|
10
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-JVP33FI2.mjs";
|
|
11
12
|
|
|
12
13
|
// src/paths.ts
|
|
13
14
|
function normalizePrefix(prefix) {
|
|
@@ -18,15 +19,20 @@ function normalizePrefix(prefix) {
|
|
|
18
19
|
}
|
|
19
20
|
function createPublicPaths(prefix) {
|
|
20
21
|
return {
|
|
21
|
-
collection: (basePath) => `${prefix}/${basePath}`,
|
|
22
|
-
item: (basePath) => `${prefix}/${basePath}/:slug`,
|
|
23
|
-
taxonomyCollection: (typeName) => `${prefix}/taxonomy/${typeName}`,
|
|
24
|
-
taxonomyItem: (typeName) => `${prefix}/taxonomy/${typeName}/:id`,
|
|
25
22
|
mediaCollection: () => `${prefix}/media`,
|
|
26
23
|
mediaItem: () => `${prefix}/media/:id`,
|
|
27
24
|
openapi: () => `${prefix}/openapi.json`
|
|
28
25
|
};
|
|
29
26
|
}
|
|
27
|
+
function createVersionedPaths(prefix, version) {
|
|
28
|
+
const root = version === null ? prefix : `${prefix}/${version}`;
|
|
29
|
+
return {
|
|
30
|
+
collection: (basePath) => `${root}/${basePath}`,
|
|
31
|
+
item: (basePath) => `${root}/${basePath}/:slug`,
|
|
32
|
+
taxonomyCollection: (typeName) => `${root}/taxonomy/${typeName}`,
|
|
33
|
+
taxonomyItem: (typeName) => `${root}/taxonomy/${typeName}/:id`
|
|
34
|
+
};
|
|
35
|
+
}
|
|
30
36
|
|
|
31
37
|
// src/app.ts
|
|
32
38
|
import { Hono } from "hono";
|
|
@@ -96,7 +102,7 @@ function createSecurityHeadersMiddleware(options = {}) {
|
|
|
96
102
|
c.res.headers.set("X-Content-Type-Options", "nosniff");
|
|
97
103
|
c.res.headers.set("X-Frame-Options", "DENY");
|
|
98
104
|
c.res.headers.set("Referrer-Policy", "no-referrer");
|
|
99
|
-
const isGraphiql = graphiqlPath !== void 0 && c.req.path === graphiqlPath;
|
|
105
|
+
const isGraphiql = graphiqlPath !== void 0 && (c.req.path === graphiqlPath || c.req.path.startsWith(`${graphiqlPath}/`));
|
|
100
106
|
c.res.headers.set("Content-Security-Policy", isGraphiql ? graphiqlCsp : csp);
|
|
101
107
|
};
|
|
102
108
|
}
|
|
@@ -622,7 +628,7 @@ function nestedTargets(fields) {
|
|
|
622
628
|
}
|
|
623
629
|
return out;
|
|
624
630
|
}
|
|
625
|
-
function buildProjectors(registry, fieldKeyMaps) {
|
|
631
|
+
function buildProjectors(registry, fieldKeyMaps, fallbacks) {
|
|
626
632
|
const projectors = {};
|
|
627
633
|
const sources = [
|
|
628
634
|
registry.content_types,
|
|
@@ -633,7 +639,14 @@ function buildProjectors(registry, fieldKeyMaps) {
|
|
|
633
639
|
for (const [typeName, type] of Object.entries(source)) {
|
|
634
640
|
const map = fieldKeyMaps[typeName];
|
|
635
641
|
if (!map) continue;
|
|
636
|
-
|
|
642
|
+
const typeFallbacks = fallbacks?.[typeName];
|
|
643
|
+
projectors[typeName] = {
|
|
644
|
+
map,
|
|
645
|
+
nested: nestedTargets(type.fields),
|
|
646
|
+
// Spread rather than assign: with exactOptionalPropertyTypes, an optional
|
|
647
|
+
// property may be ABSENT but not present-and-undefined.
|
|
648
|
+
...typeFallbacks !== void 0 && { fallbacks: typeFallbacks }
|
|
649
|
+
};
|
|
637
650
|
}
|
|
638
651
|
}
|
|
639
652
|
return projectors;
|
|
@@ -642,6 +655,11 @@ function projectRow(row, typeName, projectors) {
|
|
|
642
655
|
const p = projectors[typeName];
|
|
643
656
|
if (!p) return row;
|
|
644
657
|
const out = p.map.toLabels(row);
|
|
658
|
+
if (p.fallbacks !== void 0) {
|
|
659
|
+
for (const [label, value] of Object.entries(p.fallbacks)) {
|
|
660
|
+
if (out[label] === null || out[label] === void 0) out[label] = value;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
645
663
|
for (const { label, target } of p.nested) {
|
|
646
664
|
const v = out[label];
|
|
647
665
|
if (v === null || v === void 0) continue;
|
|
@@ -735,13 +753,24 @@ function registerPublicContentRoutes(app, registry, repos, projectors, paths, li
|
|
|
735
753
|
const basePath = contentType.default_base_path;
|
|
736
754
|
const repo = repos[typeName];
|
|
737
755
|
if (!repo) continue;
|
|
738
|
-
const
|
|
739
|
-
|
|
740
|
-
)
|
|
756
|
+
const fieldKeys = projectors[typeName].map;
|
|
757
|
+
const relationFieldNames = /* @__PURE__ */ new Set();
|
|
758
|
+
for (const f of contentType.fields) {
|
|
759
|
+
if (!RELATION_FIELD_TYPES.has(f.field_type)) continue;
|
|
760
|
+
if (isColumnBacked(f)) {
|
|
761
|
+
const label = fieldKeys.labelFor(f.db_column.column_name);
|
|
762
|
+
if (label !== void 0) relationFieldNames.add(label);
|
|
763
|
+
} else {
|
|
764
|
+
relationFieldNames.add(f.name);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
741
767
|
const filterableFieldNames = /* @__PURE__ */ new Set([
|
|
742
|
-
...
|
|
768
|
+
...fieldKeys.labels,
|
|
743
769
|
...contentType.system_fields.map((f) => f.name)
|
|
744
770
|
]);
|
|
771
|
+
const sortableFieldNames = new Set(
|
|
772
|
+
[...SORTABLE_FIELDS].filter((f) => filterableFieldNames.has(f))
|
|
773
|
+
);
|
|
745
774
|
if (contentType.only_one) {
|
|
746
775
|
app.get(paths.collection(basePath), async (c) => {
|
|
747
776
|
const result = await repo.findMany({ published_only: true, page: 1, per_page: 1 });
|
|
@@ -757,8 +786,6 @@ function registerPublicContentRoutes(app, registry, repos, projectors, paths, li
|
|
|
757
786
|
});
|
|
758
787
|
} else {
|
|
759
788
|
registerListRoute(paths.collection(basePath), async (c) => {
|
|
760
|
-
const projector = projectors[typeName];
|
|
761
|
-
const fieldKeys = projector.map;
|
|
762
789
|
const pagination = parsePagination(c.req.query("page"), c.req.query("per_page"));
|
|
763
790
|
if (!pagination.ok) {
|
|
764
791
|
return c.json(
|
|
@@ -773,13 +800,13 @@ function registerPublicContentRoutes(app, registry, repos, projectors, paths, li
|
|
|
773
800
|
);
|
|
774
801
|
}
|
|
775
802
|
const sortBy = c.req.query("sort_by") ?? "created_at";
|
|
776
|
-
if (!
|
|
803
|
+
if (!sortableFieldNames.has(sortBy)) {
|
|
777
804
|
return c.json(
|
|
778
805
|
{
|
|
779
806
|
ok: false,
|
|
780
807
|
error: {
|
|
781
808
|
code: "INVALID_SORT_FIELD",
|
|
782
|
-
message: `'${sortBy}' is not sortable. Allowed:
|
|
809
|
+
message: `'${sortBy}' is not sortable. Allowed: ${[...sortableFieldNames].join(", ")}`
|
|
783
810
|
}
|
|
784
811
|
},
|
|
785
812
|
400
|
|
@@ -826,7 +853,7 @@ function registerPublicContentRoutes(app, registry, repos, projectors, paths, li
|
|
|
826
853
|
);
|
|
827
854
|
}
|
|
828
855
|
}
|
|
829
|
-
const sortColumn = fieldKeys.columnFor(sortBy)
|
|
856
|
+
const sortColumn = fieldKeys.labels.includes(sortBy) ? fieldKeys.columnFor(sortBy) : sortBy;
|
|
830
857
|
const result = await repo.findMany({
|
|
831
858
|
published_only: true,
|
|
832
859
|
page: pagination.page,
|
|
@@ -2706,7 +2733,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
2706
2733
|
import { sql as sql5 } from "drizzle-orm";
|
|
2707
2734
|
var API_VERSION = (() => {
|
|
2708
2735
|
try {
|
|
2709
|
-
return "0.
|
|
2736
|
+
return "0.6.0";
|
|
2710
2737
|
} catch {
|
|
2711
2738
|
return "0.0.0";
|
|
2712
2739
|
}
|
|
@@ -3107,7 +3134,93 @@ function createMediaRepository(db) {
|
|
|
3107
3134
|
};
|
|
3108
3135
|
}
|
|
3109
3136
|
|
|
3137
|
+
// src/versions.ts
|
|
3138
|
+
var VERSION_SEGMENT = /^v\d+$/;
|
|
3139
|
+
function versionNumber(version) {
|
|
3140
|
+
return Number.parseInt(version.slice(1), 10);
|
|
3141
|
+
}
|
|
3142
|
+
function classifyVersion(segment, model) {
|
|
3143
|
+
if (!VERSION_SEGMENT.test(segment)) return "not-a-version";
|
|
3144
|
+
if (model.live.includes(segment)) return "live";
|
|
3145
|
+
return versionNumber(segment) < versionNumber(model.current) ? "retired" : "unknown";
|
|
3146
|
+
}
|
|
3147
|
+
function fallbacksFor(projection) {
|
|
3148
|
+
const out = {};
|
|
3149
|
+
for (const [typeName, type] of Object.entries(projection.types)) {
|
|
3150
|
+
const perLabel = {};
|
|
3151
|
+
for (const f of type.fields) {
|
|
3152
|
+
if (f.fallback !== void 0) perLabel[f.exposed_as] = f.fallback;
|
|
3153
|
+
}
|
|
3154
|
+
if (Object.keys(perLabel).length > 0) out[typeName] = perLabel;
|
|
3155
|
+
}
|
|
3156
|
+
return out;
|
|
3157
|
+
}
|
|
3158
|
+
function buildVersionSurface(input) {
|
|
3159
|
+
const { version, projectionVersion, prefix, registry, model, makeRepo } = input;
|
|
3160
|
+
const paths = createVersionedPaths(prefix, version);
|
|
3161
|
+
const projection = model.projections[projectionVersion];
|
|
3162
|
+
const fieldKeyMaps = {};
|
|
3163
|
+
const sources = [
|
|
3164
|
+
registry.content_types,
|
|
3165
|
+
registry.taxonomy_types,
|
|
3166
|
+
registry.paragraph_types
|
|
3167
|
+
];
|
|
3168
|
+
for (const source of sources) {
|
|
3169
|
+
for (const [typeName, type] of Object.entries(source)) {
|
|
3170
|
+
const projectionType = projection?.types[typeName];
|
|
3171
|
+
fieldKeyMaps[typeName] = projectionType ? createFieldKeyMapFromProjection(projectionType, type.fields) : createFieldKeyMap(type.fields);
|
|
3172
|
+
}
|
|
3173
|
+
}
|
|
3174
|
+
const fallbacks = projection ? fallbacksFor(projection) : void 0;
|
|
3175
|
+
const projectors = buildProjectors(registry, fieldKeyMaps, fallbacks);
|
|
3176
|
+
const sortableColumnsFor = (typeName) => {
|
|
3177
|
+
const map = fieldKeyMaps[typeName];
|
|
3178
|
+
const type = registry.content_types[typeName] ?? registry.taxonomy_types[typeName];
|
|
3179
|
+
const systemFieldNames = new Set((type?.system_fields ?? []).map((f) => f.name));
|
|
3180
|
+
const out = /* @__PURE__ */ new Set();
|
|
3181
|
+
for (const label of SORTABLE_FIELDS) {
|
|
3182
|
+
const column = map.columnFor(label);
|
|
3183
|
+
if (column !== void 0) out.add(column);
|
|
3184
|
+
else if (systemFieldNames.has(label)) out.add(label);
|
|
3185
|
+
}
|
|
3186
|
+
return out;
|
|
3187
|
+
};
|
|
3188
|
+
const repos = {};
|
|
3189
|
+
for (const [typeName, ct] of Object.entries(registry.content_types)) {
|
|
3190
|
+
repos[typeName] = makeRepo(typeName, ct.db.table_name, sortableColumnsFor(typeName));
|
|
3191
|
+
}
|
|
3192
|
+
for (const [typeName, tt] of Object.entries(registry.taxonomy_types)) {
|
|
3193
|
+
repos[typeName] = makeRepo(typeName, tt.db.table_name, sortableColumnsFor(typeName));
|
|
3194
|
+
}
|
|
3195
|
+
return { paths, fieldKeyMaps, projectors, repos };
|
|
3196
|
+
}
|
|
3197
|
+
function deprecationHeaders(input) {
|
|
3198
|
+
const { requested, model, successor } = input;
|
|
3199
|
+
const link = `<${successor}>; rel="successor-version"`;
|
|
3200
|
+
if (requested === null) {
|
|
3201
|
+
if (model.live.length <= 1) return null;
|
|
3202
|
+
return {
|
|
3203
|
+
Deprecation: "true",
|
|
3204
|
+
Link: link,
|
|
3205
|
+
Warning: `299 - "Unversioned path resolves to the latest version (${model.current}) and will change when a new version is cut. Pin a version."`
|
|
3206
|
+
};
|
|
3207
|
+
}
|
|
3208
|
+
if (requested === model.current) return null;
|
|
3209
|
+
return { Deprecation: "true", Link: link };
|
|
3210
|
+
}
|
|
3211
|
+
|
|
3110
3212
|
// src/app.ts
|
|
3213
|
+
function versionRoutePaths(registry, paths) {
|
|
3214
|
+
const out = [paths.collection("content"), paths.collection("taxonomy")];
|
|
3215
|
+
for (const ct of Object.values(registry.content_types)) {
|
|
3216
|
+
out.push(paths.collection(ct.default_base_path));
|
|
3217
|
+
if (!ct.only_one) out.push(paths.item(ct.default_base_path));
|
|
3218
|
+
}
|
|
3219
|
+
for (const tt of Object.values(registry.taxonomy_types)) {
|
|
3220
|
+
out.push(paths.taxonomyCollection(tt.name), paths.taxonomyItem(tt.name));
|
|
3221
|
+
}
|
|
3222
|
+
return out;
|
|
3223
|
+
}
|
|
3111
3224
|
var MISSING_STORAGE_ERROR = `\u2717 api.storage is required but not configured.
|
|
3112
3225
|
Add a storage adapter to your manguito.config.ts:
|
|
3113
3226
|
|
|
@@ -3123,6 +3236,7 @@ function createCmsApp(options) {
|
|
|
3123
3236
|
}
|
|
3124
3237
|
const prefix = normalizePrefix(options.prefix);
|
|
3125
3238
|
const publicPaths = createPublicPaths(prefix);
|
|
3239
|
+
const versionedPaths = createVersionedPaths(prefix, null);
|
|
3126
3240
|
const { storage, registry, db, rateLimit, media, cors } = options;
|
|
3127
3241
|
const cmsName = options.name ?? "Manguito CMS";
|
|
3128
3242
|
const maxFileSize = media?.max_file_size;
|
|
@@ -3171,9 +3285,18 @@ function createCmsApp(options) {
|
|
|
3171
3285
|
])
|
|
3172
3286
|
]);
|
|
3173
3287
|
const projectors = buildProjectors(registry, fieldKeyMaps);
|
|
3174
|
-
const sortableColumnsFor = (typeName) =>
|
|
3175
|
-
|
|
3176
|
-
|
|
3288
|
+
const sortableColumnsFor = (typeName) => {
|
|
3289
|
+
const map = fieldKeyMaps[typeName];
|
|
3290
|
+
const type = registry.content_types[typeName] ?? registry.taxonomy_types[typeName];
|
|
3291
|
+
const systemFieldNames = new Set((type?.system_fields ?? []).map((f) => f.name));
|
|
3292
|
+
const out = /* @__PURE__ */ new Set();
|
|
3293
|
+
for (const label of SORTABLE_FIELDS) {
|
|
3294
|
+
const column = map.columnFor(label);
|
|
3295
|
+
if (column !== void 0) out.add(column);
|
|
3296
|
+
else if (systemFieldNames.has(label)) out.add(label);
|
|
3297
|
+
}
|
|
3298
|
+
return out;
|
|
3299
|
+
};
|
|
3177
3300
|
const contentRepos = Object.fromEntries(
|
|
3178
3301
|
Object.entries(registry.content_types).map(([typeName, ct]) => [
|
|
3179
3302
|
typeName,
|
|
@@ -3192,41 +3315,14 @@ function createCmsApp(options) {
|
|
|
3192
3315
|
);
|
|
3193
3316
|
const repos = { ...contentRepos, ...taxonomyRepos };
|
|
3194
3317
|
const mediaRepo = createMediaRepository(db);
|
|
3195
|
-
const
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
])
|
|
3204
|
-
);
|
|
3205
|
-
const publicTaxonomyRepos = Object.fromEntries(
|
|
3206
|
-
Object.entries(registry.taxonomy_types).map(([typeName, tt]) => [
|
|
3207
|
-
typeName,
|
|
3208
|
-
createDrizzleContentRepository(db, tt.db.table_name, {
|
|
3209
|
-
relations: buildRelationsMap(tt.fields, registry),
|
|
3210
|
-
publishedRelations: true,
|
|
3211
|
-
sortableColumns: sortableColumnsFor(typeName)
|
|
3212
|
-
})
|
|
3213
|
-
])
|
|
3214
|
-
);
|
|
3215
|
-
const publicRepos = { ...publicContentRepos, ...publicTaxonomyRepos };
|
|
3216
|
-
const graphqlRepos = Object.fromEntries([
|
|
3217
|
-
...Object.entries(registry.content_types).map(([typeName, ct]) => [
|
|
3218
|
-
typeName,
|
|
3219
|
-
createDrizzleContentRepository(db, ct.db.table_name, {
|
|
3220
|
-
sortableColumns: sortableColumnsFor(typeName)
|
|
3221
|
-
})
|
|
3222
|
-
]),
|
|
3223
|
-
...Object.entries(registry.taxonomy_types).map(([typeName, tt]) => [
|
|
3224
|
-
typeName,
|
|
3225
|
-
createDrizzleContentRepository(db, tt.db.table_name, {
|
|
3226
|
-
sortableColumns: sortableColumnsFor(typeName)
|
|
3227
|
-
})
|
|
3228
|
-
])
|
|
3229
|
-
]);
|
|
3318
|
+
const makeRepo = (typeName, tableName, sortableColumns) => {
|
|
3319
|
+
const type = registry.content_types[typeName] ?? registry.taxonomy_types[typeName];
|
|
3320
|
+
return createDrizzleContentRepository(db, tableName, {
|
|
3321
|
+
relations: buildRelationsMap(type.fields, registry),
|
|
3322
|
+
publishedRelations: true,
|
|
3323
|
+
sortableColumns
|
|
3324
|
+
});
|
|
3325
|
+
};
|
|
3230
3326
|
registerAuthRoutes(app, db);
|
|
3231
3327
|
app.use("/admin/api/*", authMiddleware);
|
|
3232
3328
|
app.use("/admin/api/*", mustChangePasswordCheck);
|
|
@@ -3235,17 +3331,17 @@ function createCmsApp(options) {
|
|
|
3235
3331
|
for (const ct of Object.values(registry.content_types)) {
|
|
3236
3332
|
const base = ct.default_base_path;
|
|
3237
3333
|
if (ct.only_one) {
|
|
3238
|
-
paths[
|
|
3334
|
+
paths[versionedPaths.collection(base)] = { get: { summary: `Get ${ct.label}`, tags: [ct.label] } };
|
|
3239
3335
|
} else {
|
|
3240
|
-
paths[
|
|
3241
|
-
paths[
|
|
3336
|
+
paths[versionedPaths.collection(base)] = { get: { summary: `List published ${ct.label}`, tags: [ct.label] } };
|
|
3337
|
+
paths[versionedPaths.item(base).replace(":slug", "{slug}")] = {
|
|
3242
3338
|
get: { summary: `Get ${ct.label} by slug`, tags: [ct.label] }
|
|
3243
3339
|
};
|
|
3244
3340
|
}
|
|
3245
3341
|
}
|
|
3246
3342
|
for (const tt of Object.values(registry.taxonomy_types)) {
|
|
3247
|
-
paths[
|
|
3248
|
-
paths[
|
|
3343
|
+
paths[versionedPaths.taxonomyCollection(tt.name)] = { get: { summary: `List published ${tt.label}`, tags: [tt.label] } };
|
|
3344
|
+
paths[versionedPaths.taxonomyItem(tt.name).replace(":id", "{id}")] = {
|
|
3249
3345
|
get: { summary: `Get ${tt.label} by id`, tags: [tt.label] }
|
|
3250
3346
|
};
|
|
3251
3347
|
}
|
|
@@ -3265,35 +3361,166 @@ function createCmsApp(options) {
|
|
|
3265
3361
|
paths: {}
|
|
3266
3362
|
})
|
|
3267
3363
|
);
|
|
3268
|
-
|
|
3364
|
+
const model = options.versions ?? {
|
|
3365
|
+
current: "v1",
|
|
3366
|
+
live: ["v1"],
|
|
3367
|
+
projections: {}
|
|
3368
|
+
};
|
|
3369
|
+
const passes = [
|
|
3370
|
+
...options.versions !== void 0 ? model.live.map((v) => ({ version: v, projectionVersion: v })) : [],
|
|
3371
|
+
{ version: null, projectionVersion: model.current }
|
|
3372
|
+
];
|
|
3373
|
+
for (const pass of passes) {
|
|
3374
|
+
const surface = buildVersionSurface({ ...pass, prefix, registry, model, makeRepo });
|
|
3375
|
+
const headers = deprecationHeaders({
|
|
3376
|
+
requested: pass.version,
|
|
3377
|
+
model,
|
|
3378
|
+
// The version root, with no trailing slash — never
|
|
3379
|
+
// createVersionedPaths(prefix, model.current).collection(''), which
|
|
3380
|
+
// would yield `${prefix}/${model.current}/` in every Link header.
|
|
3381
|
+
successor: `${prefix}/${model.current}`
|
|
3382
|
+
});
|
|
3383
|
+
if (headers !== null) {
|
|
3384
|
+
const attachDeprecationHeaders = async (c, next) => {
|
|
3385
|
+
await next();
|
|
3386
|
+
for (const [key, value] of Object.entries(headers)) c.res.headers.set(key, value);
|
|
3387
|
+
};
|
|
3388
|
+
for (const path of versionRoutePaths(registry, surface.paths)) {
|
|
3389
|
+
app.use(path, attachDeprecationHeaders);
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
registerPublicContentRoutes(app, registry, surface.repos, surface.projectors, surface.paths, listRateLimit, programmaticResolver);
|
|
3393
|
+
}
|
|
3394
|
+
if (options.versions !== void 0) {
|
|
3395
|
+
app.all(`${prefix}/:version/*`, async (c, next) => {
|
|
3396
|
+
const kind = classifyVersion(c.req.param("version"), model);
|
|
3397
|
+
if (kind === "not-a-version" || kind === "live") return next();
|
|
3398
|
+
const live = model.live.join(", ");
|
|
3399
|
+
return kind === "retired" ? c.json(
|
|
3400
|
+
{
|
|
3401
|
+
ok: false,
|
|
3402
|
+
error: {
|
|
3403
|
+
code: "VERSION_RETIRED",
|
|
3404
|
+
message: `Version ${c.req.param("version")} is no longer served. Live versions: ${live}.`
|
|
3405
|
+
}
|
|
3406
|
+
},
|
|
3407
|
+
410
|
|
3408
|
+
) : c.json(
|
|
3409
|
+
{
|
|
3410
|
+
ok: false,
|
|
3411
|
+
error: {
|
|
3412
|
+
code: "VERSION_NOT_FOUND",
|
|
3413
|
+
message: `No version ${c.req.param("version")} is served. Live versions: ${live}.`
|
|
3414
|
+
}
|
|
3415
|
+
},
|
|
3416
|
+
404
|
|
3417
|
+
);
|
|
3418
|
+
});
|
|
3419
|
+
}
|
|
3269
3420
|
registerPublicMediaRoutes(app, mediaRepo, publicPaths, listRateLimit);
|
|
3270
3421
|
if (options.graphql?.enabled) {
|
|
3271
3422
|
const gqlOptions = options.graphql;
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3423
|
+
const makeGraphqlRepo = (_typeName, tableName, sortableColumns) => createDrizzleContentRepository(db, tableName, { sortableColumns });
|
|
3424
|
+
const gqlPasses = [
|
|
3425
|
+
...options.versions !== void 0 ? model.live.map((v) => ({ version: v, projectionVersion: v })) : [],
|
|
3426
|
+
{ version: null, projectionVersion: model.current }
|
|
3427
|
+
];
|
|
3428
|
+
const mounts = gqlPasses.map((pass) => ({
|
|
3429
|
+
path: pass.version === null ? "/graphql" : `/graphql/${pass.version}`,
|
|
3430
|
+
version: pass.version,
|
|
3431
|
+
handler: null,
|
|
3432
|
+
error: null
|
|
3433
|
+
}));
|
|
3434
|
+
const ready = import("./handler-XZVDLKZ2.mjs").then(({ createGraphQLHandler }) => {
|
|
3435
|
+
gqlPasses.forEach((pass, i) => {
|
|
3436
|
+
const mount = mounts[i];
|
|
3437
|
+
try {
|
|
3438
|
+
const surface = buildVersionSurface({
|
|
3439
|
+
...pass,
|
|
3440
|
+
prefix,
|
|
3441
|
+
registry,
|
|
3442
|
+
model,
|
|
3443
|
+
makeRepo: makeGraphqlRepo
|
|
3444
|
+
});
|
|
3445
|
+
const projection = model.projections[pass.projectionVersion];
|
|
3446
|
+
const currentProjection = model.projections[model.current];
|
|
3447
|
+
mount.handler = createGraphQLHandler(
|
|
3448
|
+
registry,
|
|
3449
|
+
surface.repos,
|
|
3450
|
+
surface.fieldKeyMaps,
|
|
3451
|
+
programmaticResolver,
|
|
3452
|
+
db,
|
|
3453
|
+
gqlOptions,
|
|
3454
|
+
{
|
|
3455
|
+
endpoint: mount.path,
|
|
3456
|
+
currentVersion: model.current,
|
|
3457
|
+
...projection !== void 0 && { projection },
|
|
3458
|
+
...currentProjection !== void 0 && { currentProjection }
|
|
3459
|
+
}
|
|
3460
|
+
);
|
|
3461
|
+
} catch (err) {
|
|
3462
|
+
mount.error = err;
|
|
3463
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3464
|
+
process.stderr.write(
|
|
3465
|
+
`\u2717 GraphQL schema failed to initialize for ${mount.path}; it will return 500. ${message}
|
|
3466
|
+
`
|
|
3467
|
+
);
|
|
3468
|
+
}
|
|
3469
|
+
});
|
|
3276
3470
|
}).catch((err) => {
|
|
3277
|
-
|
|
3471
|
+
for (const m of mounts) m.error = err;
|
|
3278
3472
|
const message = err instanceof Error ? err.message : String(err);
|
|
3279
|
-
|
|
3473
|
+
const paths = mounts.map((m) => m.path).join(", ");
|
|
3474
|
+
process.stderr.write(`\u2717 GraphQL module failed to load; ${paths} will return 500. ${message}
|
|
3280
3475
|
`);
|
|
3281
3476
|
});
|
|
3282
|
-
const
|
|
3283
|
-
if (!
|
|
3284
|
-
if (!
|
|
3477
|
+
const invokeFor = (mount) => async (c) => {
|
|
3478
|
+
if (!mount.handler && !mount.error) await ready;
|
|
3479
|
+
if (!mount.handler) {
|
|
3285
3480
|
return c.json(
|
|
3286
3481
|
{ ok: false, error: { code: "GRAPHQL_INIT_FAILED", message: "GraphQL schema failed to initialize" } },
|
|
3287
3482
|
500
|
|
3288
3483
|
);
|
|
3289
3484
|
}
|
|
3290
|
-
return
|
|
3485
|
+
return mount.handler(c, async () => {
|
|
3291
3486
|
});
|
|
3292
3487
|
};
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3488
|
+
for (const mount of mounts) {
|
|
3489
|
+
const headers = deprecationHeaders({
|
|
3490
|
+
requested: mount.version,
|
|
3491
|
+
model,
|
|
3492
|
+
successor: `/graphql/${model.current}`
|
|
3493
|
+
});
|
|
3494
|
+
if (headers === null) continue;
|
|
3495
|
+
const attach = async (c, next) => {
|
|
3496
|
+
await next();
|
|
3497
|
+
for (const [key, value] of Object.entries(headers)) c.res.headers.set(key, value);
|
|
3498
|
+
};
|
|
3499
|
+
app.use(mount.path, attach);
|
|
3500
|
+
}
|
|
3501
|
+
for (const mount of mounts) {
|
|
3502
|
+
if (listRateLimit) app.all(mount.path, listRateLimit, invokeFor(mount));
|
|
3503
|
+
else app.all(mount.path, invokeFor(mount));
|
|
3504
|
+
}
|
|
3505
|
+
if (options.versions !== void 0) {
|
|
3506
|
+
app.all("/graphql/:version", async (c, next) => {
|
|
3507
|
+
const segment = c.req.param("version");
|
|
3508
|
+
const kind = classifyVersion(segment, model);
|
|
3509
|
+
if (kind === "not-a-version" || kind === "live") return next();
|
|
3510
|
+
const live = model.live.join(", ");
|
|
3511
|
+
return c.json({
|
|
3512
|
+
errors: [
|
|
3513
|
+
{
|
|
3514
|
+
message: kind === "retired" ? `Version ${segment} is no longer served. Live versions: ${live}.` : `Version ${segment} does not exist. Live versions: ${live}.`,
|
|
3515
|
+
extensions: {
|
|
3516
|
+
code: kind === "retired" ? "VERSION_RETIRED" : "VERSION_UNKNOWN",
|
|
3517
|
+
current: model.current,
|
|
3518
|
+
live: model.live
|
|
3519
|
+
}
|
|
3520
|
+
}
|
|
3521
|
+
]
|
|
3522
|
+
});
|
|
3523
|
+
});
|
|
3297
3524
|
}
|
|
3298
3525
|
}
|
|
3299
3526
|
registerConfigRoute(
|