@bobbykim/manguito-cms-cli 0.6.0 → 0.6.2

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.
@@ -49,7 +49,7 @@ var init_esm_shims = __esm({
49
49
  }
50
50
  });
51
51
 
52
- // ../api/dist/chunk-D5AO4BTL.mjs
52
+ // ../api/dist/chunk-FXPKGPYF.mjs
53
53
  init_esm_shims();
54
54
  import crypto from "crypto";
55
55
 
@@ -1212,7 +1212,7 @@ Subquery.prototype.getSQL = function() {
1212
1212
  return new SQL([this]);
1213
1213
  };
1214
1214
 
1215
- // ../api/dist/chunk-D5AO4BTL.mjs
1215
+ // ../api/dist/chunk-FXPKGPYF.mjs
1216
1216
  function isColumnBacked(field) {
1217
1217
  const col = field.db_column;
1218
1218
  if (col === null) return false;
@@ -1220,14 +1220,15 @@ function isColumnBacked(field) {
1220
1220
  if (col.junction) return false;
1221
1221
  return true;
1222
1222
  }
1223
- function buildFieldKeyMap(pairs, allLabels, droppedKeys) {
1223
+ function buildFieldKeyMap(pairs, allLabels, droppedColumns, droppedLabels) {
1224
1224
  const labelToColumn = /* @__PURE__ */ new Map();
1225
1225
  const columnToLabel = /* @__PURE__ */ new Map();
1226
1226
  for (const { label, column } of pairs) {
1227
1227
  labelToColumn.set(label, column);
1228
1228
  columnToLabel.set(column, label);
1229
1229
  }
1230
- for (const label of allLabels) {
1230
+ for (const { label, ownColumn } of allLabels) {
1231
+ if (ownColumn !== void 0) continue;
1231
1232
  const columnOwner = columnToLabel.get(label);
1232
1233
  if (columnOwner !== void 0 && columnOwner !== label) {
1233
1234
  throw new Error(
@@ -1236,23 +1237,33 @@ function buildFieldKeyMap(pairs, allLabels, droppedKeys) {
1236
1237
  }
1237
1238
  }
1238
1239
  for (const label of labelToColumn.keys()) {
1239
- if (droppedKeys.has(label)) labelToColumn.delete(label);
1240
+ if (droppedLabels.has(label)) labelToColumn.delete(label);
1240
1241
  }
1241
1242
  for (const column of columnToLabel.keys()) {
1242
- if (droppedKeys.has(column)) columnToLabel.delete(column);
1243
+ if (droppedColumns.has(column)) columnToLabel.delete(column);
1243
1244
  }
1244
1245
  const diverges = [...labelToColumn].some(([label, column]) => label !== column);
1245
- function remap(input, lookup) {
1246
+ function remap(input, lookup, dropped, droppedIfUnmapped) {
1246
1247
  const out = {};
1247
1248
  for (const key of Object.keys(input)) {
1248
- if (droppedKeys.has(key)) continue;
1249
- out[lookup.get(key) ?? key] = input[key];
1249
+ const mapped = lookup.get(key);
1250
+ if (mapped !== void 0) {
1251
+ out[mapped] = input[key];
1252
+ continue;
1253
+ }
1254
+ if (dropped.has(key) || droppedIfUnmapped?.has(key)) continue;
1255
+ out[key] = input[key];
1250
1256
  }
1251
1257
  return out;
1252
1258
  }
1253
1259
  return {
1254
- toStorage: (input) => remap(input, labelToColumn),
1255
- toLabels: (row) => remap(row, columnToLabel),
1260
+ // toStorage reads LABEL-keyed input: a live label maps and writes
1261
+ // regardless of either drop set; an unmapped key is refused if it is
1262
+ // EITHER the tombstone's own name (droppedLabels) or a raw, unexposed
1263
+ // column (droppedColumns) — see remap's comment.
1264
+ // toLabels reads COLUMN-keyed input, so it consults only droppedColumns.
1265
+ toStorage: (input) => remap(input, labelToColumn, droppedLabels, droppedColumns),
1266
+ toLabels: (row) => remap(row, columnToLabel, droppedColumns),
1256
1267
  columnFor: (label) => labelToColumn.get(label),
1257
1268
  labelFor: (column) => columnToLabel.get(column),
1258
1269
  labels: [...labelToColumn.keys()],
@@ -1260,37 +1271,52 @@ function buildFieldKeyMap(pairs, allLabels, droppedKeys) {
1260
1271
  };
1261
1272
  }
1262
1273
  function createFieldKeyMap(fields) {
1263
- const droppedKeys = /* @__PURE__ */ new Set();
1274
+ const droppedColumns = /* @__PURE__ */ new Set();
1275
+ const droppedLabels = /* @__PURE__ */ new Set();
1264
1276
  const pairs = [];
1265
1277
  for (const f of fields) {
1266
1278
  if (!isColumnBacked(f)) continue;
1267
1279
  pairs.push({ label: f.name, column: f.db_column.column_name });
1268
1280
  if (f.removed === true) {
1269
- droppedKeys.add(f.name);
1270
- droppedKeys.add(f.db_column.column_name);
1281
+ droppedLabels.add(f.name);
1282
+ droppedColumns.add(f.db_column.column_name);
1271
1283
  }
1272
1284
  }
1273
1285
  return buildFieldKeyMap(
1274
1286
  pairs,
1275
- fields.map((f) => f.name),
1276
- droppedKeys
1287
+ fields.map((f) => ({
1288
+ label: f.name,
1289
+ // Conditional spread, not a plain assignment — exactOptionalPropertyTypes
1290
+ // forbids present-and-undefined.
1291
+ ...isColumnBacked(f) && { ownColumn: f.db_column.column_name }
1292
+ })),
1293
+ droppedColumns,
1294
+ droppedLabels
1277
1295
  );
1278
1296
  }
1279
1297
  function createFieldKeyMapFromProjection(projectionType, allFields) {
1280
1298
  const pairs = projectionType.fields.map((f) => ({ label: f.exposed_as, column: f.column_name }));
1281
- const projected = new Set(pairs.map((p) => p.label));
1282
- const dropped = /* @__PURE__ */ new Set();
1299
+ const projectedColumns = new Set(pairs.map((p) => p.column));
1300
+ const projectedLabels = new Set(pairs.map((p) => p.label));
1301
+ const droppedColumns = /* @__PURE__ */ new Set();
1302
+ const droppedLabels = /* @__PURE__ */ new Set();
1283
1303
  for (const f of allFields) {
1284
1304
  if (!isColumnBacked(f)) continue;
1285
- dropped.add(f.name);
1286
- dropped.add(f.db_column.column_name);
1305
+ if (!projectedColumns.has(f.db_column.column_name)) droppedColumns.add(f.db_column.column_name);
1306
+ if (!projectedLabels.has(f.name)) droppedLabels.add(f.name);
1287
1307
  }
1288
- for (const p of pairs) dropped.delete(p.column);
1289
- for (const label of projected) dropped.delete(label);
1290
1308
  return buildFieldKeyMap(
1291
1309
  pairs,
1292
- allFields.map((f) => f.name),
1293
- dropped
1310
+ allFields.map((f) => ({
1311
+ label: f.name,
1312
+ // See buildFieldKeyMap's collision check: `ownColumn` marks every
1313
+ // column-backed field so the check skips it entirely — only a
1314
+ // paragraph/junction/programmatic label (no column of its own) can
1315
+ // still collide.
1316
+ ...isColumnBacked(f) && { ownColumn: f.db_column.column_name }
1317
+ })),
1318
+ droppedColumns,
1319
+ droppedLabels
1294
1320
  );
1295
1321
  }
1296
1322
  function quoteIdent(name) {
@@ -1462,9 +1488,8 @@ function groupBy(items, key) {
1462
1488
  }
1463
1489
  return result;
1464
1490
  }
1465
- function needsFkResolution(row, fkColumn, fieldName) {
1466
- if (fkColumn !== fieldName && !(fkColumn in row)) return false;
1467
- const current = row[fieldName];
1491
+ function needsFkResolution(row, fkColumn) {
1492
+ const current = row[fkColumn];
1468
1493
  return !(typeof current === "object" && current !== null);
1469
1494
  }
1470
1495
  async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOnly = false) {
@@ -1481,15 +1506,11 @@ async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOn
1481
1506
  row[fieldName] = byParent[row["id"]] ?? [];
1482
1507
  }
1483
1508
  } else if (rel.type === "reference") {
1484
- const dropFk = rel.fk_column !== fieldName;
1485
- const pending = batch.filter((r) => needsFkResolution(r, rel.fk_column, fieldName));
1509
+ const pending = batch.filter((r) => needsFkResolution(r, rel.fk_column));
1486
1510
  if (pending.length === 0) return;
1487
1511
  const fkValues = pending.map((r) => r[rel.fk_column]).filter(Boolean);
1488
1512
  if (fkValues.length === 0) {
1489
- for (const row of pending) {
1490
- row[fieldName] = null;
1491
- if (dropFk) delete row[rel.fk_column];
1492
- }
1513
+ for (const row of pending) row[rel.fk_column] = null;
1493
1514
  return;
1494
1515
  }
1495
1516
  const unique = [...new Set(fkValues)];
@@ -1506,8 +1527,7 @@ async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOn
1506
1527
  }
1507
1528
  for (const row of pending) {
1508
1529
  const fkVal = row[rel.fk_column];
1509
- row[fieldName] = fkVal ? cache.get(`${rel.table}:${fkVal}`) ?? null : null;
1510
- if (dropFk) delete row[rel.fk_column];
1530
+ row[rel.fk_column] = fkVal ? cache.get(`${rel.table}:${fkVal}`) ?? null : null;
1511
1531
  }
1512
1532
  } else if (rel.type === "junction") {
1513
1533
  const parentIds = batch.map((r) => r["id"]);
@@ -1535,15 +1555,11 @@ async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOn
1535
1555
  row[fieldName] = jEntries.map((jr) => cache.get(`${rel.table}:${jr[rel.right_column]}`)).filter(Boolean);
1536
1556
  }
1537
1557
  } else if (rel.type === "media") {
1538
- const dropFk = rel.fk_column !== fieldName;
1539
- const pending = batch.filter((r) => needsFkResolution(r, rel.fk_column, fieldName));
1558
+ const pending = batch.filter((r) => needsFkResolution(r, rel.fk_column));
1540
1559
  if (pending.length === 0) return;
1541
1560
  const fkValues = pending.map((r) => r[rel.fk_column]).filter(Boolean);
1542
1561
  if (fkValues.length === 0) {
1543
- for (const row of pending) {
1544
- row[fieldName] = null;
1545
- if (dropFk) delete row[rel.fk_column];
1546
- }
1562
+ for (const row of pending) row[rel.fk_column] = null;
1547
1563
  return;
1548
1564
  }
1549
1565
  const unique = [...new Set(fkValues)];
@@ -1557,8 +1573,7 @@ async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOn
1557
1573
  }
1558
1574
  for (const row of pending) {
1559
1575
  const fkVal = row[rel.fk_column];
1560
- row[fieldName] = fkVal ? cache.get(`media:${fkVal}`) ?? null : null;
1561
- if (dropFk) delete row[rel.fk_column];
1576
+ row[rel.fk_column] = fkVal ? cache.get(`media:${fkVal}`) ?? null : null;
1562
1577
  }
1563
1578
  }
1564
1579
  }
@@ -11,7 +11,7 @@ import {
11
11
  init_esm_shims,
12
12
  isColumnBacked,
13
13
  resolveRelationField
14
- } from "./chunk-STFSR5LP.js";
14
+ } from "./chunk-52I54C3X.js";
15
15
 
16
16
  // ../../node_modules/.pnpm/@whatwg-node+fetch@0.10.13/node_modules/@whatwg-node/fetch/dist/shouldSkipPonyfill.js
17
17
  var require_shouldSkipPonyfill = __commonJS({
@@ -10786,10 +10786,10 @@ var require_parser = __commonJS({
10786
10786
  }
10787
10787
  });
10788
10788
 
10789
- // ../api/dist/handler-X7JDHJAL.mjs
10789
+ // ../api/dist/handler-GED3I7Z2.mjs
10790
10790
  init_esm_shims();
10791
10791
 
10792
- // ../api/dist/chunk-ARZXLNZS.mjs
10792
+ // ../api/dist/chunk-4LLELFAZ.mjs
10793
10793
  init_esm_shims();
10794
10794
 
10795
10795
  // ../../node_modules/.pnpm/graphql-yoga@5.21.2_graphql@16.14.2/node_modules/graphql-yoga/esm/index.js
@@ -30122,7 +30122,7 @@ function createYoga(options) {
30122
30122
  });
30123
30123
  }
30124
30124
 
30125
- // ../api/dist/chunk-ARZXLNZS.mjs
30125
+ // ../api/dist/chunk-4LLELFAZ.mjs
30126
30126
  var import_dataloader = __toESM(require_dataloader(), 1);
30127
30127
 
30128
30128
  // ../../node_modules/.pnpm/@escape.tech+graphql-armor@3.2.0_@envelop+core@5.5.1_@escape.tech+graphql-armor-types@0.7.0/node_modules/@escape.tech/graphql-armor/dist/graphql-armor.esm.js
@@ -30880,7 +30880,7 @@ var EnvelopArmor = class {
30880
30880
  }
30881
30881
  };
30882
30882
 
30883
- // ../api/dist/chunk-ARZXLNZS.mjs
30883
+ // ../api/dist/chunk-4LLELFAZ.mjs
30884
30884
  var DateTimeScalar = new GraphQLScalarType({
30885
30885
  name: "DateTime",
30886
30886
  description: "ISO-8601 date-time string",
@@ -30979,10 +30979,13 @@ var SORTABLE = [
30979
30979
  { gql: "createdAt", value: "created_at" },
30980
30980
  { gql: "updatedAt", value: "updated_at" }
30981
30981
  ];
30982
- function buildSortFieldEnum(typeName) {
30982
+ function buildSortFieldEnum(typeName, fieldKeys) {
30983
+ const resolvable = SORTABLE.filter(
30984
+ (s) => s.gql === "createdAt" || s.gql === "updatedAt" || fieldKeys === void 0 || fieldKeys.columnFor(s.value) !== void 0
30985
+ );
30983
30986
  return new GraphQLEnumType({
30984
30987
  name: `${typeName}SortField`,
30985
- values: Object.fromEntries(SORTABLE.map((s) => [s.gql, { value: s.value }]))
30988
+ values: Object.fromEntries(resolvable.map((s) => [s.gql, { value: s.value }]))
30986
30989
  });
30987
30990
  }
30988
30991
  function comparableFilterInput(name, scalar) {
@@ -31026,18 +31029,19 @@ function filterInputForField(fieldType) {
31026
31029
  if (scalar === null) return null;
31027
31030
  return StringFilter;
31028
31031
  }
31029
- function buildFilterInputType(type) {
31032
+ function buildFilterInputType(type, viewFields) {
31030
31033
  const fields = {};
31031
31034
  fields["id"] = { type: IDFilter };
31032
31035
  fields["published"] = { type: BooleanFilter };
31033
31036
  fields["createdAt"] = { type: DateTimeFilter };
31034
31037
  fields["updatedAt"] = { type: DateTimeFilter };
31035
31038
  if (type.schema_type === "content-type") fields["slug"] = { type: StringFilter };
31036
- for (const f of type.fields) {
31039
+ const entries = viewFields !== void 0 ? viewFields.map((v) => ({ field: v.field, exposedAs: v.exposedAs })) : type.fields.filter((f) => f.removed !== true).map((f) => ({ field: f, exposedAs: f.name }));
31040
+ for (const { field: f, exposedAs } of entries) {
31037
31041
  if (f.field_type === "programmatic" || f.field_type === "paragraph") continue;
31038
31042
  if (f.field_type === "image" || f.field_type === "video" || f.field_type === "file") continue;
31039
31043
  const input = filterInputForField(f.field_type);
31040
- if (input) fields[buildFieldNameMap([f.name]).toGraphql(f.name)] = { type: input };
31044
+ if (input) fields[buildFieldNameMap([exposedAs]).toGraphql(exposedAs)] = { type: input };
31041
31045
  }
31042
31046
  if (Object.keys(fields).length === 0) return null;
31043
31047
  return new GraphQLInputObjectType({ name: `${graphqlTypeName(type.name)}Filter`, fields });
@@ -31070,12 +31074,14 @@ function codeError(code, message) {
31070
31074
  function isPublished(item) {
31071
31075
  return !!item && item["published"] === true;
31072
31076
  }
31073
- function resolveFieldValue(field, row) {
31077
+ function resolveFieldValue(field, row, fallback) {
31074
31078
  const key = isColumnBacked(field) ? field.db_column.column_name : field.name;
31075
- return row[key] ?? null;
31079
+ const value = row[key];
31080
+ if ((value === null || value === void 0) && fallback !== void 0) return fallback;
31081
+ return value ?? null;
31076
31082
  }
31077
- function scalarFieldResolver(field) {
31078
- return (parent) => resolveFieldValue(field, parent);
31083
+ function scalarFieldResolver(field, fallback) {
31084
+ return (parent) => resolveFieldValue(field, parent, fallback);
31079
31085
  }
31080
31086
  function relationFieldResolver(typeName, schemaFieldName) {
31081
31087
  return (parent, _args, ctx) => ctx.loaders.load(typeName, schemaFieldName, parent);
@@ -31167,7 +31173,7 @@ var MEDIA = new GraphQLObjectType({
31167
31173
  duration: { type: GraphQLInt, resolve: (m) => m.duration }
31168
31174
  }
31169
31175
  });
31170
- function buildGraphQLSchema(registry, fieldKeyMaps = {}) {
31176
+ function buildGraphQLSchema(registry, fieldKeyMaps = {}, view, programmaticKeyMaps = fieldKeyMaps) {
31171
31177
  const objectTypes = /* @__PURE__ */ new Map();
31172
31178
  const enumTypes = /* @__PURE__ */ new Map();
31173
31179
  for (const [name, enumType] of Object.entries(registry.enum_types)) {
@@ -31186,14 +31192,14 @@ function buildGraphQLSchema(registry, fieldKeyMaps = {}) {
31186
31192
  );
31187
31193
  }
31188
31194
  }
31189
- function outputTypeForField(field) {
31195
+ function outputTypeForField(field, required) {
31190
31196
  const scalar = scalarOutputType(field.field_type);
31191
- if (scalar) return field.required ? new GraphQLNonNull(scalar) : scalar;
31197
+ if (scalar) return required ? new GraphQLNonNull(scalar) : scalar;
31192
31198
  if (field.field_type === "enum") {
31193
31199
  const ref = field.ui_component.component === "select" ? field.ui_component.enum_ref : void 0;
31194
31200
  const et = ref ? enumTypes.get(ref) : void 0;
31195
31201
  const t = et ?? GraphQLString;
31196
- return field.required ? new GraphQLNonNull(t) : t;
31202
+ return required ? new GraphQLNonNull(t) : t;
31197
31203
  }
31198
31204
  if (field.field_type === "image" || field.field_type === "video" || field.field_type === "file") {
31199
31205
  return MEDIA;
@@ -31223,8 +31229,17 @@ function buildGraphQLSchema(registry, fieldKeyMaps = {}) {
31223
31229
  }
31224
31230
  return GraphQLString;
31225
31231
  }
31232
+ function exposedFields(type) {
31233
+ return type.fields.filter((f) => f.removed !== true);
31234
+ }
31235
+ function viewFieldsFor(machineName, type) {
31236
+ return view?.[machineName]?.fields ?? exposedFields(type).map((f) => ({ field: f, exposedAs: f.name, required: f.required }));
31237
+ }
31226
31238
  function buildObjectType(machineName, type) {
31227
- const mediaFieldNames = type.fields.filter((f) => f.field_type === "image" || f.field_type === "video" || f.field_type === "file").map((f) => f.name);
31239
+ const visible = viewFieldsFor(machineName, type);
31240
+ const mediaFieldNames = type.fields.filter(
31241
+ (f) => f.removed !== true && (f.field_type === "image" || f.field_type === "video" || f.field_type === "file")
31242
+ ).map((f) => f.name);
31228
31243
  return new GraphQLObjectType({
31229
31244
  name: graphqlTypeName(machineName),
31230
31245
  fields: () => {
@@ -31238,23 +31253,25 @@ function buildGraphQLSchema(registry, fieldKeyMaps = {}) {
31238
31253
  }
31239
31254
  fields["createdAt"] = { type: new GraphQLNonNull(DateTimeScalar), resolve: (p) => p["created_at"] };
31240
31255
  fields["updatedAt"] = { type: new GraphQLNonNull(DateTimeScalar), resolve: (p) => p["updated_at"] };
31241
- for (const field of type.fields) {
31242
- const gqlName = toCamelCase(field.name);
31243
- const outType = outputTypeForField(field);
31256
+ for (const vf of visible) {
31257
+ const field = vf.field;
31258
+ const gqlName = toCamelCase(vf.exposedAs);
31259
+ const outType = outputTypeForField(field, vf.required);
31244
31260
  let resolve;
31245
31261
  if (field.field_type === "programmatic") {
31246
- resolve = programmaticFieldResolver(
31247
- machineName,
31248
- field.name,
31249
- mediaFieldNames,
31250
- fieldKeyMaps[machineName]
31251
- );
31262
+ resolve = programmaticFieldResolver(machineName, field.name, mediaFieldNames, programmaticKeyMaps[machineName]);
31252
31263
  } else if (field.field_type === "reference" || field.field_type === "paragraph" || field.field_type === "image" || field.field_type === "video" || field.field_type === "file") {
31253
31264
  resolve = relationFieldResolver(machineName, field.name);
31254
31265
  } else {
31255
- resolve = scalarFieldResolver(field);
31266
+ resolve = scalarFieldResolver(field, vf.fallback);
31256
31267
  }
31257
- fields[gqlName] = { type: outType, resolve };
31268
+ fields[gqlName] = {
31269
+ type: outType,
31270
+ resolve,
31271
+ // Conditional spread, not `deprecationReason: vf.deprecationReason`
31272
+ // — exactOptionalPropertyTypes forbids present-and-undefined.
31273
+ ...vf.deprecationReason !== void 0 && { deprecationReason: vf.deprecationReason }
31274
+ };
31258
31275
  }
31259
31276
  return fields;
31260
31277
  }
@@ -31286,7 +31303,11 @@ function buildGraphQLSchema(registry, fieldKeyMaps = {}) {
31286
31303
  const queryFields = {};
31287
31304
  for (const [name, ct] of Object.entries(registry.content_types)) {
31288
31305
  const objType = objectTypes.get(name);
31289
- const nameMap = buildFieldNameMap(ct.fields.map((f) => f.name));
31306
+ const visible = viewFieldsFor(name, ct);
31307
+ const nameMap = buildFieldNameMap([
31308
+ ...visible.map((v) => v.exposedAs),
31309
+ ...ct.system_fields.map((s) => s.name)
31310
+ ]);
31290
31311
  if (ct.only_one) {
31291
31312
  queryFields[singleQueryName(name)] = { type: objType, resolve: singletonResolver(name) };
31292
31313
  continue;
@@ -31298,13 +31319,13 @@ function buildGraphQLSchema(registry, fieldKeyMaps = {}) {
31298
31319
  meta: { type: new GraphQLNonNull(PAGE_META), resolve: (r) => r.meta }
31299
31320
  }
31300
31321
  });
31301
- const filterType = buildFilterInputType(ct);
31322
+ const filterType = buildFilterInputType(ct, visible);
31302
31323
  queryFields[collectionQueryName(name)] = {
31303
31324
  type: new GraphQLNonNull(listType),
31304
31325
  args: {
31305
31326
  page: { type: GraphQLInt },
31306
31327
  perPage: { type: GraphQLInt },
31307
- sortBy: { type: buildSortFieldEnum(graphqlTypeName(name)) },
31328
+ sortBy: { type: buildSortFieldEnum(graphqlTypeName(name), fieldKeyMaps[name]) },
31308
31329
  sortOrder: { type: SortOrderEnum },
31309
31330
  ...filterType ? { filter: { type: filterType } } : {}
31310
31331
  },
@@ -31328,7 +31349,11 @@ function buildGraphQLSchema(registry, fieldKeyMaps = {}) {
31328
31349
  queryFields[collectionQueryName(name)] = {
31329
31350
  type: new GraphQLNonNull(listType),
31330
31351
  args: { page: { type: GraphQLInt }, perPage: { type: GraphQLInt } },
31331
- resolve: collectionResolver(name, buildFieldNameMap(tt.fields.map((f) => f.name)), fieldKeyMaps[name])
31352
+ resolve: collectionResolver(
31353
+ name,
31354
+ buildFieldNameMap(viewFieldsFor(name, tt).map((v) => v.exposedAs)),
31355
+ fieldKeyMaps[name]
31356
+ )
31332
31357
  };
31333
31358
  queryFields[singleQueryName(name)] = {
31334
31359
  type: objType,
@@ -31340,6 +31365,68 @@ function buildGraphQLSchema(registry, fieldKeyMaps = {}) {
31340
31365
  query: new GraphQLObjectType({ name: "Query", fields: queryFields })
31341
31366
  });
31342
31367
  }
31368
+ function byColumn(fields) {
31369
+ const out = /* @__PURE__ */ new Map();
31370
+ for (const f of fields) {
31371
+ if (!isColumnBacked(f)) continue;
31372
+ out.set(f.db_column.column_name, f);
31373
+ }
31374
+ return out;
31375
+ }
31376
+ function deprecationFor(field, currentLabels, currentVersion) {
31377
+ const currentLabel = currentLabels.get(field.column_name);
31378
+ if (currentLabel === void 0) {
31379
+ return `Removed in ${currentVersion}; column retained while this version is live.`;
31380
+ }
31381
+ if (currentLabel !== field.exposed_as) {
31382
+ return `Renamed to '${currentLabel}' in ${currentVersion}.`;
31383
+ }
31384
+ return void 0;
31385
+ }
31386
+ function buildVersionView(input) {
31387
+ const { registry, projection, currentProjection, currentVersion } = input;
31388
+ const out = {};
31389
+ const sources = [
31390
+ registry.content_types,
31391
+ registry.taxonomy_types,
31392
+ registry.paragraph_types
31393
+ ];
31394
+ for (const source of sources) {
31395
+ for (const [typeName, type] of Object.entries(source)) {
31396
+ const projectionType = projection?.types[typeName];
31397
+ if (projectionType === void 0) {
31398
+ out[typeName] = {
31399
+ fields: type.fields.filter((f) => f.removed !== true).map((f) => ({ field: f, exposedAs: f.name, required: f.required }))
31400
+ };
31401
+ continue;
31402
+ }
31403
+ const columns = byColumn(type.fields);
31404
+ const currentLabels = /* @__PURE__ */ new Map();
31405
+ for (const f of currentProjection?.types[typeName]?.fields ?? []) {
31406
+ currentLabels.set(f.column_name, f.exposed_as);
31407
+ }
31408
+ const fields = [];
31409
+ for (const pf of projectionType.fields) {
31410
+ const field = columns.get(pf.column_name);
31411
+ if (field === void 0) continue;
31412
+ const reason = deprecationFor(pf, currentLabels, currentVersion);
31413
+ fields.push({
31414
+ field,
31415
+ exposedAs: pf.exposed_as,
31416
+ required: pf.required,
31417
+ ...pf.fallback !== void 0 && { fallback: pf.fallback },
31418
+ ...reason !== void 0 && { deprecationReason: reason }
31419
+ });
31420
+ }
31421
+ for (const f of type.fields) {
31422
+ if (isColumnBacked(f) || f.removed === true) continue;
31423
+ fields.push({ field: f, exposedAs: f.name, required: f.required });
31424
+ }
31425
+ out[typeName] = { fields };
31426
+ }
31427
+ }
31428
+ return out;
31429
+ }
31343
31430
  function createRelationLoaders(db, registry) {
31344
31431
  const relMaps = /* @__PURE__ */ new Map();
31345
31432
  const loaders = /* @__PURE__ */ new Map();
@@ -31361,7 +31448,8 @@ function createRelationLoaders(db, registry) {
31361
31448
  async (parents) => {
31362
31449
  const rows = parents;
31363
31450
  await resolveRelationField(db, rows, fieldName, rel, cache, true);
31364
- return rows.map((r) => r[fieldName]);
31451
+ const resultKey = rel.type === "reference" || rel.type === "media" ? rel.fk_column : fieldName;
31452
+ return rows.map((r) => r[resultKey]);
31365
31453
  },
31366
31454
  // Batch within a tick; do not memoize by parent identity (rows are mutated
31367
31455
  // and may recur across nesting levels).
@@ -31407,15 +31495,29 @@ function isClientSafeGraphQLError(error) {
31407
31495
  }
31408
31496
  return false;
31409
31497
  }
31410
- function createGraphQLHandler(registry, repos, fieldKeyMaps, resolver, db, options) {
31411
- const schema = buildGraphQLSchema(registry, fieldKeyMaps);
31498
+ function createGraphQLHandler(registry, repos, fieldKeyMaps, resolver, db, options, versioning = {}) {
31499
+ let view;
31500
+ if (versioning.projection !== void 0) {
31501
+ if (versioning.currentVersion === void 0) {
31502
+ throw new Error(
31503
+ 'createGraphQLHandler: currentVersion is required when a projection is supplied \u2014 deprecation reasons name it, and an empty one ships "Removed in ." into published SDL.'
31504
+ );
31505
+ }
31506
+ view = buildVersionView({
31507
+ registry,
31508
+ projection: versioning.projection,
31509
+ currentProjection: versioning.currentProjection,
31510
+ currentVersion: versioning.currentVersion
31511
+ });
31512
+ }
31513
+ const schema = buildGraphQLSchema(registry, fieldKeyMaps, view, versioning.currentFieldKeyMaps ?? fieldKeyMaps);
31412
31514
  const { plugins } = buildArmorPlugin({
31413
31515
  maxDepth: options.maxDepth,
31414
31516
  maxComplexity: options.maxComplexity
31415
31517
  });
31416
31518
  const yoga = createYoga({
31417
31519
  schema,
31418
- graphqlEndpoint: "/graphql",
31520
+ graphqlEndpoint: versioning.endpoint ?? "/graphql",
31419
31521
  graphiql: options.graphiql,
31420
31522
  landingPage: false,
31421
31523
  maskedErrors: {
package/dist/index.js CHANGED
@@ -39,7 +39,7 @@ import {
39
39
  resolveRelationField,
40
40
  sql,
41
41
  tracer
42
- } from "./chunk-STFSR5LP.js";
42
+ } from "./chunk-52I54C3X.js";
43
43
 
44
44
  // ../../node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/dist/nodes/identity.js
45
45
  var require_identity = __commonJS({
@@ -30444,7 +30444,7 @@ function buildProjections(input2) {
30444
30444
  const fields = type.fields.filter((f) => isColumnBacked2(f) && f.removed !== true).map((f) => {
30445
30445
  const column_name = f.db_column.column_name;
30446
30446
  const fallback = fallbacks.get(`${typeName}.${column_name}`);
30447
- return fallback === void 0 ? { column_name, exposed_as: f.name } : { column_name, exposed_as: f.name, fallback };
30447
+ return fallback === void 0 ? { column_name, exposed_as: f.name, required: f.required } : { column_name, exposed_as: f.name, required: f.required, fallback };
30448
30448
  });
30449
30449
  types3[typeName] = { fields };
30450
30450
  }
@@ -47129,7 +47129,7 @@ function createSecurityHeadersMiddleware(options = {}) {
47129
47129
  c.res.headers.set("X-Content-Type-Options", "nosniff");
47130
47130
  c.res.headers.set("X-Frame-Options", "DENY");
47131
47131
  c.res.headers.set("Referrer-Policy", "no-referrer");
47132
- const isGraphiql = graphiqlPath !== void 0 && c.req.path === graphiqlPath;
47132
+ const isGraphiql = graphiqlPath !== void 0 && (c.req.path === graphiqlPath || c.req.path.startsWith(`${graphiqlPath}/`));
47133
47133
  c.res.headers.set("Content-Security-Policy", isGraphiql ? graphiqlCsp : csp);
47134
47134
  };
47135
47135
  }
@@ -47618,14 +47618,16 @@ function isPlainRow(v2) {
47618
47618
  return typeof v2 === "object" && v2 !== null && !Array.isArray(v2) && !(v2 instanceof Date);
47619
47619
  }
47620
47620
  var MEDIA_FIELD_TYPES = /* @__PURE__ */ new Set(["image", "video", "file"]);
47621
- function nestedTargets(fields) {
47621
+ function nestedTargets(fields, map2) {
47622
47622
  const out = [];
47623
47623
  for (const f of fields) {
47624
47624
  if (MEDIA_FIELD_TYPES.has(f.field_type)) continue;
47625
47625
  if (f.field_type !== "paragraph" && f.field_type !== "reference") continue;
47626
47626
  const ref = f.ui_component.ref;
47627
47627
  if (!ref) continue;
47628
- out.push({ label: f.name, target: ref });
47628
+ const label = isColumnBacked(f) ? map2.labelFor(f.db_column.column_name) : f.name;
47629
+ if (label === void 0) continue;
47630
+ out.push({ label, target: ref });
47629
47631
  }
47630
47632
  return out;
47631
47633
  }
@@ -47643,7 +47645,7 @@ function buildProjectors(registry2, fieldKeyMaps, fallbacks) {
47643
47645
  const typeFallbacks = fallbacks?.[typeName];
47644
47646
  projectors[typeName] = {
47645
47647
  map: map2,
47646
- nested: nestedTargets(type.fields),
47648
+ nested: nestedTargets(type.fields, map2),
47647
47649
  // Spread rather than assign: with exactOptionalPropertyTypes, an optional
47648
47650
  // property may be ABSENT but not present-and-undefined.
47649
47651
  ...typeFallbacks !== void 0 && { fallbacks: typeFallbacks }
@@ -47723,7 +47725,28 @@ function parseFilters(url2, validFields, columnFor) {
47723
47725
  function isPublished(item) {
47724
47726
  return item["published"] === true;
47725
47727
  }
47726
- function registerPublicContentRoutes(app, registry2, repos, projectors, paths, listRateLimit, resolver) {
47728
+ function registerPublicContentRoutes(app, registry2, repos, projectors, paths, listRateLimit, resolver, resolverProjectors) {
47729
+ const forResolver = resolverProjectors ?? projectors;
47730
+ function pickProgrammatic(resolved, names) {
47731
+ const out = {};
47732
+ for (const name of names) if (name in resolved) out[name] = resolved[name];
47733
+ return out;
47734
+ }
47735
+ async function respondItem(row, typeName, programmatic) {
47736
+ const data = projectRow(row, typeName, projectors);
47737
+ if (!resolver?.hasSchema(typeName)) return data;
47738
+ const resolved = await resolver.resolveItem(typeName, projectRow(row, typeName, forResolver));
47739
+ return { ...data, ...pickProgrammatic(resolved, programmatic) };
47740
+ }
47741
+ async function respondList(rows, typeName, programmatic) {
47742
+ const data = rows.map((row) => projectRow(row, typeName, projectors));
47743
+ if (!resolver?.hasSchema(typeName)) return data;
47744
+ const resolved = await resolver.resolveList(
47745
+ typeName,
47746
+ rows.map((row) => projectRow(row, typeName, forResolver))
47747
+ );
47748
+ return data.map((d2, i) => ({ ...d2, ...pickProgrammatic(resolved[i], programmatic) }));
47749
+ }
47727
47750
  function registerListRoute(path7, handler) {
47728
47751
  if (listRateLimit) {
47729
47752
  app.get(path7, listRateLimit, handler);
@@ -47747,18 +47770,29 @@ function registerPublicContentRoutes(app, registry2, repos, projectors, paths, l
47747
47770
  return c.json({ ok: true, data });
47748
47771
  });
47749
47772
  for (const [typeName, contentType] of Object.entries(registry2.content_types)) {
47773
+ let translateInclude2 = function(requested) {
47774
+ const include = [];
47775
+ for (const field of requested) {
47776
+ const registryName = includeNames.get(field);
47777
+ if (registryName === void 0) return { ok: false, field };
47778
+ include.push(registryName);
47779
+ }
47780
+ return { ok: true, include };
47781
+ };
47782
+ var translateInclude = translateInclude2;
47750
47783
  const basePath = contentType.default_base_path;
47751
47784
  const repo = repos[typeName];
47752
47785
  if (!repo) continue;
47753
47786
  const fieldKeys = projectors[typeName].map;
47754
- const relationFieldNames = /* @__PURE__ */ new Set();
47787
+ const programmatic = contentType.fields.filter((f) => f.field_type === "programmatic").map((f) => f.name);
47788
+ const includeNames = /* @__PURE__ */ new Map();
47755
47789
  for (const f of contentType.fields) {
47756
47790
  if (!RELATION_FIELD_TYPES.has(f.field_type)) continue;
47757
47791
  if (isColumnBacked(f)) {
47758
47792
  const label = fieldKeys.labelFor(f.db_column.column_name);
47759
- if (label !== void 0) relationFieldNames.add(label);
47793
+ if (label !== void 0) includeNames.set(label, f.name);
47760
47794
  } else {
47761
- relationFieldNames.add(f.name);
47795
+ includeNames.set(f.name, f.name);
47762
47796
  }
47763
47797
  }
47764
47798
  const filterableFieldNames = /* @__PURE__ */ new Set([
@@ -47777,8 +47811,11 @@ function registerPublicContentRoutes(app, registry2, repos, projectors, paths, l
47777
47811
  404
47778
47812
  );
47779
47813
  }
47780
- let data = projectRow(result.data[0], typeName, projectors);
47781
- if (resolver?.hasSchema(typeName)) data = await resolver.resolveItem(typeName, data);
47814
+ const data = await respondItem(
47815
+ result.data[0],
47816
+ typeName,
47817
+ programmatic
47818
+ );
47782
47819
  return c.json({ ok: true, data });
47783
47820
  });
47784
47821
  } else {
@@ -47835,21 +47872,20 @@ function registerPublicContentRoutes(app, registry2, repos, projectors, paths, l
47835
47872
  400
47836
47873
  );
47837
47874
  }
47838
- const include = parseInclude(c.req.query("include"));
47839
- for (const field of include) {
47840
- if (!relationFieldNames.has(field)) {
47841
- return c.json(
47842
- {
47843
- ok: false,
47844
- error: {
47845
- code: "INVALID_INCLUDE_FIELD",
47846
- message: `'${field}' is not a valid relation field`
47847
- }
47848
- },
47849
- 400
47850
- );
47851
- }
47875
+ const translated = translateInclude2(parseInclude(c.req.query("include")));
47876
+ if (!translated.ok) {
47877
+ return c.json(
47878
+ {
47879
+ ok: false,
47880
+ error: {
47881
+ code: "INVALID_INCLUDE_FIELD",
47882
+ message: `'${translated.field}' is not a valid relation field`
47883
+ }
47884
+ },
47885
+ 400
47886
+ );
47852
47887
  }
47888
+ const include = translated.include;
47853
47889
  const sortColumn = fieldKeys.labels.includes(sortBy) ? fieldKeys.columnFor(sortBy) : sortBy;
47854
47890
  const result = await repo.findMany({
47855
47891
  published_only: true,
@@ -47860,29 +47896,25 @@ function registerPublicContentRoutes(app, registry2, repos, projectors, paths, l
47860
47896
  filters: filtersResult.filters,
47861
47897
  include
47862
47898
  });
47863
- const labeled = result.data.map(
47864
- (row) => projectRow(row, typeName, projectors)
47865
- );
47866
- const data = resolver?.hasSchema(typeName) ? await resolver.resolveList(typeName, labeled) : labeled;
47899
+ const data = await respondList(result.data, typeName, programmatic);
47867
47900
  return c.json({ ...result, data });
47868
47901
  });
47869
47902
  app.get(paths.item(basePath), async (c) => {
47870
47903
  const slug = c.req.param("slug");
47871
- const include = parseInclude(c.req.query("include"));
47872
- for (const field of include) {
47873
- if (!relationFieldNames.has(field)) {
47874
- return c.json(
47875
- {
47876
- ok: false,
47877
- error: {
47878
- code: "INVALID_INCLUDE_FIELD",
47879
- message: `'${field}' is not a valid relation field`
47880
- }
47881
- },
47882
- 400
47883
- );
47884
- }
47904
+ const translated = translateInclude2(parseInclude(c.req.query("include")));
47905
+ if (!translated.ok) {
47906
+ return c.json(
47907
+ {
47908
+ ok: false,
47909
+ error: {
47910
+ code: "INVALID_INCLUDE_FIELD",
47911
+ message: `'${translated.field}' is not a valid relation field`
47912
+ }
47913
+ },
47914
+ 400
47915
+ );
47885
47916
  }
47917
+ const include = translated.include;
47886
47918
  const item = await repo.findBySlug(slug, include);
47887
47919
  if (!item || !isPublished(item)) {
47888
47920
  return c.json(
@@ -47893,15 +47925,15 @@ function registerPublicContentRoutes(app, registry2, repos, projectors, paths, l
47893
47925
  404
47894
47926
  );
47895
47927
  }
47896
- let data = projectRow(item, typeName, projectors);
47897
- if (resolver?.hasSchema(typeName)) data = await resolver.resolveItem(typeName, data);
47928
+ const data = await respondItem(item, typeName, programmatic);
47898
47929
  return c.json({ ok: true, data });
47899
47930
  });
47900
47931
  }
47901
47932
  }
47902
- for (const [typeName] of Object.entries(registry2.taxonomy_types)) {
47933
+ for (const [typeName, taxonomyType] of Object.entries(registry2.taxonomy_types)) {
47903
47934
  const repo = repos[typeName];
47904
47935
  if (!repo) continue;
47936
+ const programmatic = taxonomyType.fields.filter((f) => f.field_type === "programmatic").map((f) => f.name);
47905
47937
  registerListRoute(paths.taxonomyCollection(typeName), async (c) => {
47906
47938
  const pagination = parsePagination(c.req.query("page"), c.req.query("per_page"));
47907
47939
  if (!pagination.ok) {
@@ -47921,10 +47953,7 @@ function registerPublicContentRoutes(app, registry2, repos, projectors, paths, l
47921
47953
  page: pagination.page,
47922
47954
  per_page: pagination.per_page
47923
47955
  });
47924
- const labeled = result.data.map(
47925
- (row) => projectRow(row, typeName, projectors)
47926
- );
47927
- const data = resolver?.hasSchema(typeName) ? await resolver.resolveList(typeName, labeled) : labeled;
47956
+ const data = await respondList(result.data, typeName, programmatic);
47928
47957
  return c.json({ ...result, data });
47929
47958
  });
47930
47959
  app.get(paths.taxonomyItem(typeName), async (c) => {
@@ -47939,8 +47968,7 @@ function registerPublicContentRoutes(app, registry2, repos, projectors, paths, l
47939
47968
  404
47940
47969
  );
47941
47970
  }
47942
- let data = projectRow(item, typeName, projectors);
47943
- if (resolver?.hasSchema(typeName)) data = await resolver.resolveItem(typeName, data);
47971
+ const data = await respondItem(item, typeName, programmatic);
47944
47972
  return c.json({ ok: true, data });
47945
47973
  });
47946
47974
  }
@@ -49702,7 +49730,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
49702
49730
  }
49703
49731
  var API_VERSION = (() => {
49704
49732
  try {
49705
- return "0.5.0";
49733
+ return "0.7.0";
49706
49734
  } catch {
49707
49735
  return "0.0.0";
49708
49736
  }
@@ -50277,20 +50305,6 @@ function createCmsApp(options) {
50277
50305
  sortableColumns
50278
50306
  });
50279
50307
  };
50280
- const graphqlRepos = Object.fromEntries([
50281
- ...Object.entries(registry2.content_types).map(([typeName, ct2]) => [
50282
- typeName,
50283
- createDrizzleContentRepository(db, ct2.db.table_name, {
50284
- sortableColumns: sortableColumnsFor(typeName)
50285
- })
50286
- ]),
50287
- ...Object.entries(registry2.taxonomy_types).map(([typeName, tt2]) => [
50288
- typeName,
50289
- createDrizzleContentRepository(db, tt2.db.table_name, {
50290
- sortableColumns: sortableColumnsFor(typeName)
50291
- })
50292
- ])
50293
- ]);
50294
50308
  registerAuthRoutes(app, db);
50295
50309
  app.use("/admin/api/*", authMiddleware);
50296
50310
  app.use("/admin/api/*", mustChangePasswordCheck);
@@ -50357,7 +50371,7 @@ function createCmsApp(options) {
50357
50371
  app.use(path7, attachDeprecationHeaders);
50358
50372
  }
50359
50373
  }
50360
- registerPublicContentRoutes(app, registry2, surface.repos, surface.projectors, surface.paths, listRateLimit, programmaticResolver);
50374
+ registerPublicContentRoutes(app, registry2, surface.repos, surface.projectors, surface.paths, listRateLimit, programmaticResolver, projectors);
50361
50375
  }
50362
50376
  if (options.versions !== void 0) {
50363
50377
  app.all(`${prefix}/:version/*`, async (c, next) => {
@@ -50388,31 +50402,108 @@ function createCmsApp(options) {
50388
50402
  registerPublicMediaRoutes(app, mediaRepo, publicPaths, listRateLimit);
50389
50403
  if (options.graphql?.enabled) {
50390
50404
  const gqlOptions = options.graphql;
50391
- let gqlHandler = null;
50392
- let initError = null;
50393
- const ready = import("./handler-X7JDHJAL-JEAY3P6W.js").then(({ createGraphQLHandler }) => {
50394
- gqlHandler = createGraphQLHandler(registry2, graphqlRepos, fieldKeyMaps, programmaticResolver, db, gqlOptions);
50405
+ const makeGraphqlRepo = (_typeName, tableName, sortableColumns) => createDrizzleContentRepository(db, tableName, { sortableColumns });
50406
+ const gqlPasses = [
50407
+ ...options.versions !== void 0 ? model.live.map((v2) => ({ version: v2, projectionVersion: v2 })) : [],
50408
+ { version: null, projectionVersion: model.current }
50409
+ ];
50410
+ const mounts = gqlPasses.map((pass) => ({
50411
+ path: pass.version === null ? "/graphql" : `/graphql/${pass.version}`,
50412
+ version: pass.version,
50413
+ handler: null,
50414
+ error: null
50415
+ }));
50416
+ const ready = import("./handler-GED3I7Z2-HZAD77OI.js").then(({ createGraphQLHandler }) => {
50417
+ gqlPasses.forEach((pass, i) => {
50418
+ const mount = mounts[i];
50419
+ try {
50420
+ const surface = buildVersionSurface({
50421
+ ...pass,
50422
+ prefix,
50423
+ registry: registry2,
50424
+ model,
50425
+ makeRepo: makeGraphqlRepo
50426
+ });
50427
+ const projection = model.projections[pass.projectionVersion];
50428
+ const currentProjection = model.projections[model.current];
50429
+ mount.handler = createGraphQLHandler(
50430
+ registry2,
50431
+ surface.repos,
50432
+ surface.fieldKeyMaps,
50433
+ programmaticResolver,
50434
+ db,
50435
+ gqlOptions,
50436
+ {
50437
+ endpoint: mount.path,
50438
+ currentVersion: model.current,
50439
+ ...projection !== void 0 && { projection },
50440
+ ...currentProjection !== void 0 && { currentProjection },
50441
+ currentFieldKeyMaps: fieldKeyMaps
50442
+ }
50443
+ );
50444
+ } catch (err) {
50445
+ mount.error = err;
50446
+ const message = err instanceof Error ? err.message : String(err);
50447
+ process.stderr.write(
50448
+ `\u2717 GraphQL schema failed to initialize for ${mount.path}; it will return 500. ${message}
50449
+ `
50450
+ );
50451
+ }
50452
+ });
50395
50453
  }).catch((err) => {
50396
- initError = err;
50454
+ for (const m2 of mounts) m2.error = err;
50397
50455
  const message = err instanceof Error ? err.message : String(err);
50398
- process.stderr.write(`\u2717 GraphQL schema failed to initialize; /graphql will return 500. ${message}
50456
+ const paths = mounts.map((m2) => m2.path).join(", ");
50457
+ process.stderr.write(`\u2717 GraphQL module failed to load; ${paths} will return 500. ${message}
50399
50458
  `);
50400
50459
  });
50401
- const invokeGraphQL = async (c) => {
50402
- if (!gqlHandler && !initError) await ready;
50403
- if (!gqlHandler) {
50460
+ const invokeFor = (mount) => async (c) => {
50461
+ if (!mount.handler && !mount.error) await ready;
50462
+ if (!mount.handler) {
50404
50463
  return c.json(
50405
50464
  { ok: false, error: { code: "GRAPHQL_INIT_FAILED", message: "GraphQL schema failed to initialize" } },
50406
50465
  500
50407
50466
  );
50408
50467
  }
50409
- return gqlHandler(c, async () => {
50468
+ return mount.handler(c, async () => {
50410
50469
  });
50411
50470
  };
50412
- if (listRateLimit) {
50413
- app.all("/graphql", listRateLimit, invokeGraphQL);
50414
- } else {
50415
- app.all("/graphql", invokeGraphQL);
50471
+ for (const mount of mounts) {
50472
+ const headers = deprecationHeaders({
50473
+ requested: mount.version,
50474
+ model,
50475
+ successor: `/graphql/${model.current}`
50476
+ });
50477
+ if (headers === null) continue;
50478
+ const attach = async (c, next) => {
50479
+ await next();
50480
+ for (const [key, value] of Object.entries(headers)) c.res.headers.set(key, value);
50481
+ };
50482
+ app.use(mount.path, attach);
50483
+ }
50484
+ for (const mount of mounts) {
50485
+ if (listRateLimit) app.all(mount.path, listRateLimit, invokeFor(mount));
50486
+ else app.all(mount.path, invokeFor(mount));
50487
+ }
50488
+ if (options.versions !== void 0) {
50489
+ app.all("/graphql/:version", async (c, next) => {
50490
+ const segment = c.req.param("version");
50491
+ const kind = classifyVersion(segment, model);
50492
+ if (kind === "not-a-version" || kind === "live") return next();
50493
+ const live = model.live.join(", ");
50494
+ return c.json({
50495
+ errors: [
50496
+ {
50497
+ message: kind === "retired" ? `Version ${segment} is no longer served. Live versions: ${live}.` : `Version ${segment} does not exist. Live versions: ${live}.`,
50498
+ extensions: {
50499
+ code: kind === "retired" ? "VERSION_RETIRED" : "VERSION_UNKNOWN",
50500
+ current: model.current,
50501
+ live: model.live
50502
+ }
50503
+ }
50504
+ ]
50505
+ });
50506
+ });
50416
50507
  }
50417
50508
  }
50418
50509
  registerConfigRoute(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobbykim/manguito-cms-cli",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "license": "MIT",
5
5
  "author": "Bobby Kim <bobby.sihun.kim@gmail.com>",
6
6
  "homepage": "https://github.com/bobbykim89/manguito-cms",
@@ -29,10 +29,10 @@
29
29
  "tsup": "^8.5.1",
30
30
  "typescript": "^6.0.3",
31
31
  "vite": "^8.1.3",
32
- "@bobbykim/manguito-cms-admin": "0.4.2",
33
- "@bobbykim/manguito-cms-core": "0.5.0",
34
- "@bobbykim/manguito-cms-db": "0.1.5",
35
- "@bobbykim/manguito-cms-api": "0.5.0"
32
+ "@bobbykim/manguito-cms-admin": "0.4.3",
33
+ "@bobbykim/manguito-cms-core": "0.6.0",
34
+ "@bobbykim/manguito-cms-api": "0.7.0",
35
+ "@bobbykim/manguito-cms-db": "0.1.6"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/node": "^25.9.5",
@@ -42,7 +42,7 @@
42
42
  "scripts": {
43
43
  "build": "tsup",
44
44
  "dev": "tsup --watch",
45
- "test": "dotenv -e .env.test -- vitest run",
45
+ "test": "dotenv -e ../../.env.test -- vitest run",
46
46
  "lint": "eslint src",
47
47
  "typecheck": "tsc --noEmit"
48
48
  }