@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/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-4Y4G564P.mjs";
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
- projectors[typeName] = { map, nested: nestedTargets(type.fields) };
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 relationFieldNames = new Set(
739
- contentType.fields.filter((f) => RELATION_FIELD_TYPES.has(f.field_type)).map((f) => f.name)
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
- ...contentType.fields.filter((f) => f.field_type !== "programmatic").map((f) => f.name),
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 (!SORTABLE_FIELDS.has(sortBy)) {
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: title, created_at, updated_at`
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) ?? 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.4.3";
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) => new Set(
3175
- [...SORTABLE_FIELDS].map((label) => fieldKeyMaps[typeName].columnFor(label) ?? label)
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 publicContentRepos = Object.fromEntries(
3196
- Object.entries(registry.content_types).map(([typeName, ct]) => [
3197
- typeName,
3198
- createDrizzleContentRepository(db, ct.db.table_name, {
3199
- relations: buildRelationsMap(ct.fields, registry),
3200
- publishedRelations: true,
3201
- sortableColumns: sortableColumnsFor(typeName)
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[publicPaths.collection(base)] = { get: { summary: `Get ${ct.label}`, tags: [ct.label] } };
3334
+ paths[versionedPaths.collection(base)] = { get: { summary: `Get ${ct.label}`, tags: [ct.label] } };
3239
3335
  } else {
3240
- paths[publicPaths.collection(base)] = { get: { summary: `List published ${ct.label}`, tags: [ct.label] } };
3241
- paths[publicPaths.item(base).replace(":slug", "{slug}")] = {
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[publicPaths.taxonomyCollection(tt.name)] = { get: { summary: `List published ${tt.label}`, tags: [tt.label] } };
3248
- paths[publicPaths.taxonomyItem(tt.name).replace(":id", "{id}")] = {
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
- registerPublicContentRoutes(app, registry, publicRepos, projectors, publicPaths, listRateLimit, programmaticResolver);
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
- let gqlHandler = null;
3273
- let initError = null;
3274
- const ready = import("./handler-45O5OX2C.mjs").then(({ createGraphQLHandler }) => {
3275
- gqlHandler = createGraphQLHandler(registry, graphqlRepos, fieldKeyMaps, programmaticResolver, db, gqlOptions);
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
- initError = err;
3471
+ for (const m of mounts) m.error = err;
3278
3472
  const message = err instanceof Error ? err.message : String(err);
3279
- process.stderr.write(`\u2717 GraphQL schema failed to initialize; /graphql will return 500. ${message}
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 invokeGraphQL = async (c) => {
3283
- if (!gqlHandler && !initError) await ready;
3284
- if (!gqlHandler) {
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 gqlHandler(c, async () => {
3485
+ return mount.handler(c, async () => {
3291
3486
  });
3292
3487
  };
3293
- if (listRateLimit) {
3294
- app.all("/graphql", listRateLimit, invokeGraphQL);
3295
- } else {
3296
- app.all("/graphql", invokeGraphQL);
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(