@bobbykim/manguito-cms-cli 0.4.3 → 0.4.4

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-TK4DNRFF.mjs
52
+ // ../api/dist/chunk-4F2CRXPG.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-TK4DNRFF.mjs
1215
+ // ../api/dist/chunk-4F2CRXPG.mjs
1216
1216
  function quoteIdent(name) {
1217
1217
  if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`);
1218
1218
  return `"${name}"`;
@@ -1382,22 +1382,34 @@ function groupBy(items, key) {
1382
1382
  }
1383
1383
  return result;
1384
1384
  }
1385
+ function needsFkResolution(row, fkColumn, fieldName) {
1386
+ if (fkColumn !== fieldName && !(fkColumn in row)) return false;
1387
+ const current = row[fieldName];
1388
+ return !(typeof current === "object" && current !== null);
1389
+ }
1385
1390
  async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOnly = false) {
1386
1391
  if (rows.length === 0) return;
1392
+ const batch = [...new Set(rows)];
1387
1393
  if (rel.type === "paragraph") {
1388
- const parentIds = rows.map((r) => r["id"]);
1394
+ const parentIds = batch.map((r) => r["id"]);
1389
1395
  const inList = sql.join(parentIds.map((id) => sql`${id}`), sql`, `);
1390
1396
  const result = await db.execute(
1391
1397
  sql`SELECT * FROM ${sql.raw(quoteIdent(rel.table))} WHERE parent_id IN (${inList}) AND parent_field = ${fieldName} ORDER BY "order" ASC`
1392
1398
  );
1393
1399
  const byParent = groupBy(result.rows, "parent_id");
1394
- for (const row of rows) {
1400
+ for (const row of batch) {
1395
1401
  row[fieldName] = byParent[row["id"]] ?? [];
1396
1402
  }
1397
1403
  } else if (rel.type === "reference") {
1398
- const fkValues = rows.map((r) => r[rel.fk_column]).filter(Boolean);
1404
+ const dropFk = rel.fk_column !== fieldName;
1405
+ const pending = batch.filter((r) => needsFkResolution(r, rel.fk_column, fieldName));
1406
+ if (pending.length === 0) return;
1407
+ const fkValues = pending.map((r) => r[rel.fk_column]).filter(Boolean);
1399
1408
  if (fkValues.length === 0) {
1400
- for (const row of rows) row[fieldName] = null;
1409
+ for (const row of pending) {
1410
+ row[fieldName] = null;
1411
+ if (dropFk) delete row[rel.fk_column];
1412
+ }
1401
1413
  return;
1402
1414
  }
1403
1415
  const unique = [...new Set(fkValues)];
@@ -1412,12 +1424,13 @@ async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOn
1412
1424
  cache.set(`${rel.table}:${item["id"]}`, item);
1413
1425
  }
1414
1426
  }
1415
- for (const row of rows) {
1427
+ for (const row of pending) {
1416
1428
  const fkVal = row[rel.fk_column];
1417
1429
  row[fieldName] = fkVal ? cache.get(`${rel.table}:${fkVal}`) ?? null : null;
1430
+ if (dropFk) delete row[rel.fk_column];
1418
1431
  }
1419
1432
  } else if (rel.type === "junction") {
1420
- const parentIds = rows.map((r) => r["id"]);
1433
+ const parentIds = batch.map((r) => r["id"]);
1421
1434
  const inList = sql.join(parentIds.map((id) => sql`${id}`), sql`, `);
1422
1435
  const orderBy = rel.order_column ? sql` ORDER BY "order" ASC` : sql``;
1423
1436
  const junctionResult = await db.execute(
@@ -1437,14 +1450,20 @@ async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOn
1437
1450
  }
1438
1451
  }
1439
1452
  const byLeft = groupBy(jRows, rel.left_column);
1440
- for (const row of rows) {
1453
+ for (const row of batch) {
1441
1454
  const jEntries = byLeft[row["id"]] ?? [];
1442
1455
  row[fieldName] = jEntries.map((jr) => cache.get(`${rel.table}:${jr[rel.right_column]}`)).filter(Boolean);
1443
1456
  }
1444
1457
  } else if (rel.type === "media") {
1445
- const fkValues = rows.map((r) => r[rel.fk_column]).filter(Boolean);
1458
+ const dropFk = rel.fk_column !== fieldName;
1459
+ const pending = batch.filter((r) => needsFkResolution(r, rel.fk_column, fieldName));
1460
+ if (pending.length === 0) return;
1461
+ const fkValues = pending.map((r) => r[rel.fk_column]).filter(Boolean);
1446
1462
  if (fkValues.length === 0) {
1447
- for (const row of rows) row[fieldName] = null;
1463
+ for (const row of pending) {
1464
+ row[fieldName] = null;
1465
+ if (dropFk) delete row[rel.fk_column];
1466
+ }
1448
1467
  return;
1449
1468
  }
1450
1469
  const unique = [...new Set(fkValues)];
@@ -1456,9 +1475,10 @@ async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOn
1456
1475
  cache.set(`media:${item["id"]}`, item);
1457
1476
  }
1458
1477
  }
1459
- for (const row of rows) {
1478
+ for (const row of pending) {
1460
1479
  const fkVal = row[rel.fk_column];
1461
1480
  row[fieldName] = fkVal ? cache.get(`media:${fkVal}`) ?? null : null;
1481
+ if (dropFk) delete row[rel.fk_column];
1462
1482
  }
1463
1483
  }
1464
1484
  }
@@ -1486,6 +1506,46 @@ async function resolveRelationBareIds(db, rows, fieldName, rel) {
1486
1506
  }
1487
1507
  }
1488
1508
  }
1509
+ function isColumnBacked(field) {
1510
+ const col = field.db_column;
1511
+ if (col === null) return false;
1512
+ if (col.column_name === "") return false;
1513
+ if (col.junction) return false;
1514
+ return true;
1515
+ }
1516
+ function createFieldKeyMap(fields) {
1517
+ const labelToColumn = /* @__PURE__ */ new Map();
1518
+ const columnToLabel = /* @__PURE__ */ new Map();
1519
+ for (const f of fields) {
1520
+ if (!isColumnBacked(f)) continue;
1521
+ labelToColumn.set(f.name, f.db_column.column_name);
1522
+ columnToLabel.set(f.db_column.column_name, f.name);
1523
+ }
1524
+ for (const f of fields) {
1525
+ const columnOwner = columnToLabel.get(f.name);
1526
+ if (columnOwner !== void 0 && columnOwner !== f.name) {
1527
+ throw new Error(
1528
+ `Fatal: field key map failed to build \u2014 field label "${f.name}" collides with the storage column of field "${columnOwner}". A field label may not reuse another field's column name. Rename the field, then run \`manguito validate\` to check your schema.`
1529
+ );
1530
+ }
1531
+ }
1532
+ const diverges = [...labelToColumn].some(([label, column]) => label !== column);
1533
+ function remap(input, lookup) {
1534
+ const out = {};
1535
+ for (const key of Object.keys(input)) {
1536
+ out[lookup.get(key) ?? key] = input[key];
1537
+ }
1538
+ return out;
1539
+ }
1540
+ return {
1541
+ toStorage: (input) => remap(input, labelToColumn),
1542
+ toLabels: (row) => remap(row, columnToLabel),
1543
+ columnFor: (label) => labelToColumn.get(label),
1544
+ labelFor: (column) => columnToLabel.get(column),
1545
+ labels: [...labelToColumn.keys()],
1546
+ diverges
1547
+ };
1548
+ }
1489
1549
 
1490
1550
  export {
1491
1551
  __require,
@@ -1523,5 +1583,7 @@ export {
1523
1583
  persistJunctionField,
1524
1584
  buildRelationsMap,
1525
1585
  resolveRelationField,
1526
- resolveRelationBareIds
1586
+ resolveRelationBareIds,
1587
+ isColumnBacked,
1588
+ createFieldKeyMap
1527
1589
  };
@@ -9,8 +9,9 @@ import {
9
9
  __toESM,
10
10
  buildRelationsMap,
11
11
  init_esm_shims,
12
+ isColumnBacked,
12
13
  resolveRelationField
13
- } from "./chunk-OAUBUE7N.js";
14
+ } from "./chunk-UNBMPHIT.js";
14
15
 
15
16
  // ../../node_modules/.pnpm/@whatwg-node+fetch@0.10.13/node_modules/@whatwg-node/fetch/dist/shouldSkipPonyfill.js
16
17
  var require_shouldSkipPonyfill = __commonJS({
@@ -10785,10 +10786,10 @@ var require_parser = __commonJS({
10785
10786
  }
10786
10787
  });
10787
10788
 
10788
- // ../api/dist/handler-CGHZG3J5.mjs
10789
+ // ../api/dist/handler-TPEULQ5W.mjs
10789
10790
  init_esm_shims();
10790
10791
 
10791
- // ../api/dist/chunk-GM55HGWA.mjs
10792
+ // ../api/dist/chunk-B5LWIR4U.mjs
10792
10793
  init_esm_shims();
10793
10794
 
10794
10795
  // ../../node_modules/.pnpm/graphql-yoga@5.21.2_graphql@16.14.2/node_modules/graphql-yoga/esm/index.js
@@ -30121,7 +30122,7 @@ function createYoga(options) {
30121
30122
  });
30122
30123
  }
30123
30124
 
30124
- // ../api/dist/chunk-GM55HGWA.mjs
30125
+ // ../api/dist/chunk-B5LWIR4U.mjs
30125
30126
  var import_dataloader = __toESM(require_dataloader(), 1);
30126
30127
 
30127
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
@@ -30879,7 +30880,7 @@ var EnvelopArmor = class {
30879
30880
  }
30880
30881
  };
30881
30882
 
30882
- // ../api/dist/chunk-GM55HGWA.mjs
30883
+ // ../api/dist/chunk-B5LWIR4U.mjs
30883
30884
  var DateTimeScalar = new GraphQLScalarType({
30884
30885
  name: "DateTime",
30885
30886
  description: "ISO-8601 date-time string",
@@ -31041,12 +31042,13 @@ function buildFilterInputType(type) {
31041
31042
  if (Object.keys(fields).length === 0) return null;
31042
31043
  return new GraphQLInputObjectType({ name: `${graphqlTypeName(type.name)}Filter`, fields });
31043
31044
  }
31044
- function translateFilters(input, nameMap) {
31045
+ function translateFilters(input, nameMap, columnFor) {
31045
31046
  const out = {};
31046
31047
  if (!input) return out;
31047
31048
  for (const [gqlField, raw] of Object.entries(input)) {
31048
31049
  if (raw === null || typeof raw !== "object") continue;
31049
- const column = nameMap.toSchema(gqlField);
31050
+ const label = nameMap.toSchema(gqlField);
31051
+ const column = columnFor?.(label) ?? label;
31050
31052
  const spec = raw;
31051
31053
  if (spec["eq"] !== void 0) {
31052
31054
  out[column] = spec["eq"];
@@ -31068,29 +31070,34 @@ function codeError(code, message) {
31068
31070
  function isPublished(item) {
31069
31071
  return !!item && item["published"] === true;
31070
31072
  }
31071
- function scalarFieldResolver(schemaName) {
31072
- return (parent) => parent[schemaName];
31073
+ function resolveFieldValue(field, row) {
31074
+ const key = isColumnBacked(field) ? field.db_column.column_name : field.name;
31075
+ return row[key] ?? null;
31076
+ }
31077
+ function scalarFieldResolver(field) {
31078
+ return (parent) => resolveFieldValue(field, parent);
31073
31079
  }
31074
31080
  function relationFieldResolver(typeName, schemaFieldName) {
31075
31081
  return (parent, _args, ctx) => ctx.loaders.load(typeName, schemaFieldName, parent);
31076
31082
  }
31077
- function programmaticFieldResolver(typeName, schemaFieldName, mediaFieldNames = []) {
31083
+ function programmaticFieldResolver(typeName, schemaFieldName, mediaFieldNames = [], fieldKeys) {
31078
31084
  return async (parent, _args, ctx) => {
31079
31085
  let p = ctx.programmaticMemo.get(parent);
31080
31086
  if (!p) {
31081
- p = resolveProgrammaticRow(typeName, parent, ctx, mediaFieldNames);
31087
+ p = resolveProgrammaticRow(typeName, parent, ctx, mediaFieldNames, fieldKeys);
31082
31088
  ctx.programmaticMemo.set(parent, p);
31083
31089
  }
31084
31090
  return (await p)[schemaFieldName];
31085
31091
  };
31086
31092
  }
31087
- async function resolveProgrammaticRow(typeName, parent, ctx, mediaFieldNames) {
31088
- if (mediaFieldNames.length === 0) return ctx.resolver.resolveItem(typeName, parent);
31093
+ async function resolveProgrammaticRow(typeName, parent, ctx, mediaFieldNames, fieldKeys) {
31094
+ const toLabels = (row) => fieldKeys ? fieldKeys.toLabels(row) : row;
31095
+ if (mediaFieldNames.length === 0) return ctx.resolver.resolveItem(typeName, toLabels(parent));
31089
31096
  const enriched = { ...parent };
31090
31097
  await Promise.all(mediaFieldNames.map((name) => ctx.loaders.load(typeName, name, enriched)));
31091
- return ctx.resolver.resolveItem(typeName, enriched);
31098
+ return ctx.resolver.resolveItem(typeName, toLabels(enriched));
31092
31099
  }
31093
- function collectionResolver(typeName, nameMap) {
31100
+ function collectionResolver(typeName, nameMap, fieldKeys) {
31094
31101
  return async (_root, args, ctx) => {
31095
31102
  const page = args.page ?? 1;
31096
31103
  const perPage = args.perPage ?? 10;
@@ -31107,7 +31114,7 @@ function collectionResolver(typeName, nameMap) {
31107
31114
  per_page: perPage,
31108
31115
  sort_by: args.sortBy ?? "created_at",
31109
31116
  sort_order: args.sortOrder ?? "asc",
31110
- filters: translateFilters(args.filter, nameMap)
31117
+ filters: translateFilters(args.filter, nameMap, fieldKeys?.columnFor)
31111
31118
  });
31112
31119
  return { data: result.data, meta: result.meta };
31113
31120
  };
@@ -31158,7 +31165,7 @@ var MEDIA = new GraphQLObjectType({
31158
31165
  duration: { type: GraphQLInt, resolve: (m) => m.duration }
31159
31166
  }
31160
31167
  });
31161
- function buildGraphQLSchema(registry) {
31168
+ function buildGraphQLSchema(registry, fieldKeyMaps = {}) {
31162
31169
  const objectTypes = /* @__PURE__ */ new Map();
31163
31170
  const enumTypes = /* @__PURE__ */ new Map();
31164
31171
  for (const [name, enumType] of Object.entries(registry.enum_types)) {
@@ -31234,11 +31241,16 @@ function buildGraphQLSchema(registry) {
31234
31241
  const outType = outputTypeForField(field);
31235
31242
  let resolve;
31236
31243
  if (field.field_type === "programmatic") {
31237
- resolve = programmaticFieldResolver(machineName, field.name, mediaFieldNames);
31244
+ resolve = programmaticFieldResolver(
31245
+ machineName,
31246
+ field.name,
31247
+ mediaFieldNames,
31248
+ fieldKeyMaps[machineName]
31249
+ );
31238
31250
  } else if (field.field_type === "reference" || field.field_type === "paragraph" || field.field_type === "image" || field.field_type === "video" || field.field_type === "file") {
31239
31251
  resolve = relationFieldResolver(machineName, field.name);
31240
31252
  } else {
31241
- resolve = scalarFieldResolver(field.name);
31253
+ resolve = scalarFieldResolver(field);
31242
31254
  }
31243
31255
  fields[gqlName] = { type: outType, resolve };
31244
31256
  }
@@ -31294,7 +31306,7 @@ function buildGraphQLSchema(registry) {
31294
31306
  sortOrder: { type: SortOrderEnum },
31295
31307
  ...filterType ? { filter: { type: filterType } } : {}
31296
31308
  },
31297
- resolve: collectionResolver(name, nameMap)
31309
+ resolve: collectionResolver(name, nameMap, fieldKeyMaps[name])
31298
31310
  };
31299
31311
  queryFields[singleQueryName(name)] = {
31300
31312
  type: objType,
@@ -31314,7 +31326,7 @@ function buildGraphQLSchema(registry) {
31314
31326
  queryFields[collectionQueryName(name)] = {
31315
31327
  type: new GraphQLNonNull(listType),
31316
31328
  args: { page: { type: GraphQLInt }, perPage: { type: GraphQLInt } },
31317
- resolve: collectionResolver(name, buildFieldNameMap(tt.fields.map((f) => f.name)))
31329
+ resolve: collectionResolver(name, buildFieldNameMap(tt.fields.map((f) => f.name)), fieldKeyMaps[name])
31318
31330
  };
31319
31331
  queryFields[singleQueryName(name)] = {
31320
31332
  type: objType,
@@ -31393,8 +31405,8 @@ function isClientSafeGraphQLError(error) {
31393
31405
  }
31394
31406
  return false;
31395
31407
  }
31396
- function createGraphQLHandler(registry, repos, resolver, db, options) {
31397
- const schema = buildGraphQLSchema(registry);
31408
+ function createGraphQLHandler(registry, repos, fieldKeyMaps, resolver, db, options) {
31409
+ const schema = buildGraphQLSchema(registry, fieldKeyMaps);
31398
31410
  const { plugins } = buildArmorPlugin({
31399
31411
  maxDepth: options.maxDepth,
31400
31412
  maxComplexity: options.maxComplexity
package/dist/index.js CHANGED
@@ -21,6 +21,7 @@ import {
21
21
  __require,
22
22
  __toESM,
23
23
  buildRelationsMap,
24
+ createFieldKeyMap,
24
25
  deleteParagraphField,
25
26
  entityKind,
26
27
  fillPlaceholders,
@@ -28,6 +29,7 @@ import {
28
29
  getTableUniqueName,
29
30
  init_esm_shims,
30
31
  is,
32
+ isColumnBacked,
31
33
  isDriverValueEncoder,
32
34
  isSQLWrapper,
33
35
  persistJunctionField,
@@ -36,7 +38,7 @@ import {
36
38
  resolveRelationField,
37
39
  sql,
38
40
  tracer
39
- } from "./chunk-OAUBUE7N.js";
41
+ } from "./chunk-UNBMPHIT.js";
40
42
 
41
43
  // ../../node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/dist/nodes/identity.js
42
44
  var require_identity = __commonJS({
@@ -46557,6 +46559,23 @@ var sign2 = Jwt.sign;
46557
46559
  import { randomBytes } from "crypto";
46558
46560
  import crypto3 from "crypto";
46559
46561
  import crypto22 from "crypto";
46562
+ function normalizePrefix(prefix) {
46563
+ if (prefix === void 0) return "/api";
46564
+ const withSlash = prefix.startsWith("/") ? prefix : `/${prefix}`;
46565
+ const trimmed = withSlash.replace(/\/+$/, "");
46566
+ return trimmed === "" ? "/api" : trimmed;
46567
+ }
46568
+ function createPublicPaths(prefix) {
46569
+ return {
46570
+ collection: (basePath) => `${prefix}/${basePath}`,
46571
+ item: (basePath) => `${prefix}/${basePath}/:slug`,
46572
+ taxonomyCollection: (typeName) => `${prefix}/taxonomy/${typeName}`,
46573
+ taxonomyItem: (typeName) => `${prefix}/taxonomy/${typeName}/:id`,
46574
+ mediaCollection: () => `${prefix}/media`,
46575
+ mediaItem: () => `${prefix}/media/:id`,
46576
+ openapi: () => `${prefix}/openapi.json`
46577
+ };
46578
+ }
46560
46579
  function createCorsMiddleware(corsConfig) {
46561
46580
  const allowList = Array.isArray(corsConfig.origin) ? corsConfig.origin : [corsConfig.origin];
46562
46581
  const wildcard = allowList.includes("*");
@@ -47122,28 +47141,31 @@ function parseInclude(includeParam) {
47122
47141
  if (!includeParam) return [];
47123
47142
  return includeParam.split(",").map((s) => s.trim()).filter(Boolean);
47124
47143
  }
47125
- function parseFilters(url2, validFields) {
47144
+ function parseFilters(url2, validFields, columnFor) {
47126
47145
  const { searchParams } = new URL(url2);
47127
47146
  const filters = {};
47147
+ const toKey = (label) => columnFor?.(label) ?? label;
47128
47148
  for (const [key, value] of searchParams.entries()) {
47129
47149
  const simpleMatch = /^filter\[([^\]]+)\]$/.exec(key);
47130
47150
  const opMatch = /^filter\[([^\]]+)\]\[([^\]]+)\]$/.exec(key);
47131
47151
  if (simpleMatch) {
47132
47152
  const field = simpleMatch[1];
47133
47153
  if (!validFields.has(field)) return { ok: false, invalidField: field };
47134
- const existing = filters[field];
47154
+ const storageKey = toKey(field);
47155
+ const existing = filters[storageKey];
47135
47156
  if (existing !== void 0) {
47136
- filters[field] = Array.isArray(existing) ? [...existing, value] : [existing, value];
47157
+ filters[storageKey] = Array.isArray(existing) ? [...existing, value] : [existing, value];
47137
47158
  } else {
47138
- filters[field] = value;
47159
+ filters[storageKey] = value;
47139
47160
  }
47140
47161
  } else if (opMatch) {
47141
47162
  const field = opMatch[1];
47142
47163
  const operator = opMatch[2];
47143
47164
  if (!validFields.has(field)) return { ok: false, invalidField: field };
47144
47165
  if (!["gt", "gte", "lt", "lte"].includes(operator)) continue;
47145
- const existing = filters[field] ?? {};
47146
- filters[field] = { ...existing, [operator]: value };
47166
+ const storageKey = toKey(field);
47167
+ const existing = filters[storageKey] ?? {};
47168
+ filters[storageKey] = { ...existing, [operator]: value };
47147
47169
  }
47148
47170
  }
47149
47171
  return { ok: true, filters };
@@ -47151,7 +47173,7 @@ function parseFilters(url2, validFields) {
47151
47173
  function isPublished(item) {
47152
47174
  return item["published"] === true;
47153
47175
  }
47154
- function registerPublicContentRoutes(app, registry2, repos, listRateLimit, resolver) {
47176
+ function registerPublicContentRoutes(app, registry2, repos, fieldKeyMaps, paths, listRateLimit, resolver) {
47155
47177
  function registerListRoute(path3, handler) {
47156
47178
  if (listRateLimit) {
47157
47179
  app.get(path3, listRateLimit, handler);
@@ -47159,7 +47181,7 @@ function registerPublicContentRoutes(app, registry2, repos, listRateLimit, resol
47159
47181
  app.get(path3, handler);
47160
47182
  }
47161
47183
  }
47162
- registerListRoute("/api/content", (c) => {
47184
+ registerListRoute(paths.collection("content"), (c) => {
47163
47185
  const data = Object.values(registry2.content_types).map((ct2) => ({
47164
47186
  name: ct2.name,
47165
47187
  label: ct2.label,
@@ -47167,7 +47189,7 @@ function registerPublicContentRoutes(app, registry2, repos, listRateLimit, resol
47167
47189
  }));
47168
47190
  return c.json({ ok: true, data });
47169
47191
  });
47170
- registerListRoute("/api/taxonomy", (c) => {
47192
+ registerListRoute(paths.collection("taxonomy"), (c) => {
47171
47193
  const data = Object.values(registry2.taxonomy_types).map((tt2) => ({
47172
47194
  name: tt2.name,
47173
47195
  label: tt2.label
@@ -47186,7 +47208,7 @@ function registerPublicContentRoutes(app, registry2, repos, listRateLimit, resol
47186
47208
  ...contentType.system_fields.map((f) => f.name)
47187
47209
  ]);
47188
47210
  if (contentType.only_one) {
47189
- app.get(`/api/${basePath}`, async (c) => {
47211
+ app.get(paths.collection(basePath), async (c) => {
47190
47212
  const result = await repo.findMany({ published_only: true, page: 1, per_page: 1 });
47191
47213
  if (result.data.length === 0) {
47192
47214
  return c.json(
@@ -47194,12 +47216,13 @@ function registerPublicContentRoutes(app, registry2, repos, listRateLimit, resol
47194
47216
  404
47195
47217
  );
47196
47218
  }
47197
- let data = result.data[0];
47219
+ let data = fieldKeyMaps[typeName].toLabels(result.data[0]);
47198
47220
  if (resolver?.hasSchema(typeName)) data = await resolver.resolveItem(typeName, data);
47199
47221
  return c.json({ ok: true, data });
47200
47222
  });
47201
47223
  } else {
47202
- registerListRoute(`/api/${basePath}`, async (c) => {
47224
+ registerListRoute(paths.collection(basePath), async (c) => {
47225
+ const fieldKeys = fieldKeyMaps[typeName];
47203
47226
  const pagination = parsePagination(c.req.query("page"), c.req.query("per_page"));
47204
47227
  if (!pagination.ok) {
47205
47228
  return c.json(
@@ -47239,7 +47262,7 @@ function registerPublicContentRoutes(app, registry2, repos, listRateLimit, resol
47239
47262
  400
47240
47263
  );
47241
47264
  }
47242
- const filtersResult = parseFilters(c.req.url, filterableFieldNames);
47265
+ const filtersResult = parseFilters(c.req.url, filterableFieldNames, fieldKeys.columnFor);
47243
47266
  if (!filtersResult.ok) {
47244
47267
  return c.json(
47245
47268
  {
@@ -47276,13 +47299,13 @@ function registerPublicContentRoutes(app, registry2, repos, listRateLimit, resol
47276
47299
  filters: filtersResult.filters,
47277
47300
  include
47278
47301
  });
47279
- if (resolver?.hasSchema(typeName)) {
47280
- const resolved = await resolver.resolveList(typeName, result.data);
47281
- return c.json({ ...result, data: resolved });
47282
- }
47283
- return c.json(result);
47302
+ const labeled = result.data.map(
47303
+ (row) => fieldKeys.toLabels(row)
47304
+ );
47305
+ const data = resolver?.hasSchema(typeName) ? await resolver.resolveList(typeName, labeled) : labeled;
47306
+ return c.json({ ...result, data });
47284
47307
  });
47285
- app.get(`/api/${basePath}/:slug`, async (c) => {
47308
+ app.get(paths.item(basePath), async (c) => {
47286
47309
  const slug = c.req.param("slug");
47287
47310
  const include = parseInclude(c.req.query("include"));
47288
47311
  for (const field of include) {
@@ -47309,7 +47332,7 @@ function registerPublicContentRoutes(app, registry2, repos, listRateLimit, resol
47309
47332
  404
47310
47333
  );
47311
47334
  }
47312
- let data = item;
47335
+ let data = fieldKeyMaps[typeName].toLabels(item);
47313
47336
  if (resolver?.hasSchema(typeName)) data = await resolver.resolveItem(typeName, data);
47314
47337
  return c.json({ ok: true, data });
47315
47338
  });
@@ -47318,7 +47341,7 @@ function registerPublicContentRoutes(app, registry2, repos, listRateLimit, resol
47318
47341
  for (const [typeName] of Object.entries(registry2.taxonomy_types)) {
47319
47342
  const repo = repos[typeName];
47320
47343
  if (!repo) continue;
47321
- registerListRoute(`/api/taxonomy/${typeName}`, async (c) => {
47344
+ registerListRoute(paths.taxonomyCollection(typeName), async (c) => {
47322
47345
  const pagination = parsePagination(c.req.query("page"), c.req.query("per_page"));
47323
47346
  if (!pagination.ok) {
47324
47347
  return c.json(
@@ -47337,13 +47360,14 @@ function registerPublicContentRoutes(app, registry2, repos, listRateLimit, resol
47337
47360
  page: pagination.page,
47338
47361
  per_page: pagination.per_page
47339
47362
  });
47340
- if (resolver?.hasSchema(typeName)) {
47341
- const resolved = await resolver.resolveList(typeName, result.data);
47342
- return c.json({ ...result, data: resolved });
47343
- }
47344
- return c.json(result);
47363
+ const fieldKeys = fieldKeyMaps[typeName];
47364
+ const labeled = result.data.map(
47365
+ (row) => fieldKeys.toLabels(row)
47366
+ );
47367
+ const data = resolver?.hasSchema(typeName) ? await resolver.resolveList(typeName, labeled) : labeled;
47368
+ return c.json({ ...result, data });
47345
47369
  });
47346
- app.get(`/api/taxonomy/${typeName}/:id`, async (c) => {
47370
+ app.get(paths.taxonomyItem(typeName), async (c) => {
47347
47371
  const id = c.req.param("id");
47348
47372
  const item = await repo.findOne(id);
47349
47373
  if (!item || !isPublished(item)) {
@@ -47355,7 +47379,7 @@ function registerPublicContentRoutes(app, registry2, repos, listRateLimit, resol
47355
47379
  404
47356
47380
  );
47357
47381
  }
47358
- let data = item;
47382
+ let data = fieldKeyMaps[typeName].toLabels(item);
47359
47383
  if (resolver?.hasSchema(typeName)) data = await resolver.resolveItem(typeName, data);
47360
47384
  return c.json({ ok: true, data });
47361
47385
  });
@@ -47369,7 +47393,7 @@ function parsePagination2(pageStr, perPageStr) {
47369
47393
  if (!Number.isInteger(per_page) || per_page < 1 || per_page > 100) return { ok: false };
47370
47394
  return { ok: true, page, per_page };
47371
47395
  }
47372
- function registerPublicMediaRoutes(app, mediaRepo, listRateLimit) {
47396
+ function registerPublicMediaRoutes(app, mediaRepo, paths, listRateLimit) {
47373
47397
  const mediaListHandler = async (c) => {
47374
47398
  const pagination = parsePagination2(c.req.query("page"), c.req.query("per_page"));
47375
47399
  if (!pagination.ok) {
@@ -47408,11 +47432,11 @@ function registerPublicMediaRoutes(app, mediaRepo, listRateLimit) {
47408
47432
  return c.json(result);
47409
47433
  };
47410
47434
  if (listRateLimit) {
47411
- app.get("/api/media", listRateLimit, mediaListHandler);
47435
+ app.get(paths.mediaCollection(), listRateLimit, mediaListHandler);
47412
47436
  } else {
47413
- app.get("/api/media", mediaListHandler);
47437
+ app.get(paths.mediaCollection(), mediaListHandler);
47414
47438
  }
47415
- app.get("/api/media/:id", async (c) => {
47439
+ app.get(paths.mediaItem(), async (c) => {
47416
47440
  const id = c.req.param("id");
47417
47441
  const item = await mediaRepo.findOne(id);
47418
47442
  if (!item) {
@@ -47431,9 +47455,11 @@ function topLevelMediaDelta(mediaFields, before, after) {
47431
47455
  const added = [];
47432
47456
  const removed = [];
47433
47457
  for (const f of mediaFields) {
47434
- if (after !== null && !(f.name in after)) continue;
47435
- const oldId = before ? extractMediaId(before[f.name]) : null;
47436
- const newId = after ? extractMediaId(after[f.name]) : null;
47458
+ const key = f.db_column?.column_name;
47459
+ if (key === void 0 || key === "") continue;
47460
+ if (after !== null && !(key in after)) continue;
47461
+ const oldId = before ? extractMediaId(before[key]) : null;
47462
+ const newId = after ? extractMediaId(after[key]) : null;
47437
47463
  if (newId === oldId) continue;
47438
47464
  if (oldId) removed.push(oldId);
47439
47465
  if (newId) added.push(newId);
@@ -47491,7 +47517,7 @@ function isEmpty(value) {
47491
47517
  function checkRequiredFields(fields, data) {
47492
47518
  return fields.filter((f) => f.required && isEmpty(data[f.name])).map((f) => ({ field: f.name, message: `${f.label} is required` }));
47493
47519
  }
47494
- function parseListQuery(url2, schemaFieldNames, relationFieldNames) {
47520
+ function parseListQuery(url2, schemaFieldNames, relationFieldNames, columnFor) {
47495
47521
  const searchParams = new URL(url2).searchParams;
47496
47522
  const pagination = parsePagination(
47497
47523
  searchParams.get("page") ?? void 0,
@@ -47526,7 +47552,7 @@ function parseListQuery(url2, schemaFieldNames, relationFieldNames) {
47526
47552
  status: 400
47527
47553
  };
47528
47554
  }
47529
- const filtersResult = parseFilters(url2, schemaFieldNames);
47555
+ const filtersResult = parseFilters(url2, schemaFieldNames, columnFor);
47530
47556
  if (!filtersResult.ok) {
47531
47557
  return {
47532
47558
  ok: false,
@@ -47558,7 +47584,7 @@ function parseListQuery(url2, schemaFieldNames, relationFieldNames) {
47558
47584
  search
47559
47585
  };
47560
47586
  }
47561
- function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePermission, db) {
47587
+ function registerAdminContentRoutes(app, registry2, repos, fieldKeyMaps, mediaRepo, requirePermission, db) {
47562
47588
  for (const [typeName, contentType] of Object.entries(registry2.content_types)) {
47563
47589
  const basePath = `content/${typeName}`;
47564
47590
  const repo = repos[typeName];
@@ -47570,6 +47596,7 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
47570
47596
  const relationFieldNames = new Set(
47571
47597
  contentType.fields.filter((f) => RELATION_FIELD_TYPES.has(f.field_type)).map((f) => f.name)
47572
47598
  );
47599
+ const fieldKeys = fieldKeyMaps[typeName];
47573
47600
  const requiredFields = contentType.fields.filter((f) => f.required);
47574
47601
  const mediaFields = contentType.fields.filter(
47575
47602
  (f) => f.field_type === "image" || f.field_type === "video" || f.field_type === "file"
@@ -47583,7 +47610,7 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
47583
47610
  `/admin/api/${basePath}`,
47584
47611
  requirePermission("content:read"),
47585
47612
  async (c) => {
47586
- const parsed = parseListQuery(c.req.url, schemaFieldNames, relationFieldNames);
47613
+ const parsed = parseListQuery(c.req.url, schemaFieldNames, relationFieldNames, fieldKeys.columnFor);
47587
47614
  if (!parsed.ok) {
47588
47615
  return c.json({ ok: false, error: parsed.response }, parsed.status);
47589
47616
  }
@@ -47603,7 +47630,8 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
47603
47630
  findOpts.search = { term: parsed.search, columns: searchableColumns };
47604
47631
  }
47605
47632
  const result = await repo.findMany(findOpts);
47606
- return c.json(result);
47633
+ const data = result.data.map((row) => fieldKeys.toLabels(row));
47634
+ return c.json({ ...result, data });
47607
47635
  }
47608
47636
  );
47609
47637
  app.get(
@@ -47638,7 +47666,8 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
47638
47666
  }
47639
47667
  }
47640
47668
  }
47641
- return c.json({ ok: true, data: item });
47669
+ const data = fieldKeys.toLabels(item);
47670
+ return c.json({ ok: true, data });
47642
47671
  }
47643
47672
  );
47644
47673
  app.post(
@@ -47705,6 +47734,7 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
47705
47734
  }
47706
47735
  );
47707
47736
  const writeNewItem = async (c, body) => {
47737
+ const storageBody = fieldKeys.toStorage(body);
47708
47738
  const fieldErrors = checkRequiredFields(requiredFields, body);
47709
47739
  if (fieldErrors.length > 0) {
47710
47740
  return c.json(
@@ -47728,9 +47758,7 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
47728
47758
  const junctionFields = contentType.fields.filter(
47729
47759
  (f) => f.db_column !== null && f.db_column.junction !== void 0
47730
47760
  );
47731
- const columnFields = contentType.fields.filter(
47732
- (f) => f.db_column !== null && f.db_column.junction === void 0
47733
- );
47761
+ const columnBackedFields = contentType.fields.filter(isColumnBacked);
47734
47762
  const columnData = {
47735
47763
  published: body["published"] ?? false
47736
47764
  };
@@ -47739,9 +47767,10 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
47739
47767
  } else {
47740
47768
  columnData["slug"] = body["slug"];
47741
47769
  }
47742
- for (const f of columnFields) {
47743
- const val = body[f.name];
47744
- columnData[f.db_column.column_name] = val !== void 0 ? val : null;
47770
+ for (const f of columnBackedFields) {
47771
+ const key = f.db_column.column_name;
47772
+ if (!(key in storageBody)) continue;
47773
+ columnData[key] = storageBody[key];
47745
47774
  }
47746
47775
  if (db) {
47747
47776
  const basePathId = await lookupBasePathId(db, contentType.default_base_path);
@@ -47761,7 +47790,7 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
47761
47790
  }
47762
47791
  const item = await repo.create(columnData);
47763
47792
  const itemId = item["id"];
47764
- const mediaDeltas = [topLevelMediaDelta(mediaFields, null, body)];
47793
+ const mediaDeltas = [topLevelMediaDelta(mediaFields, null, storageBody)];
47765
47794
  if (db) {
47766
47795
  for (const f of paragraphFields) {
47767
47796
  const comp = f.ui_component;
@@ -47778,7 +47807,8 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
47778
47807
  }
47779
47808
  }
47780
47809
  await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
47781
- return c.json({ ok: true, data: item }, 201);
47810
+ const data = fieldKeys.toLabels(item);
47811
+ return c.json({ ok: true, data }, 201);
47782
47812
  };
47783
47813
  app.patch(
47784
47814
  `/admin/api/${basePath}/:id`,
@@ -47838,11 +47868,12 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
47838
47868
  }
47839
47869
  );
47840
47870
  const writeExistingItem = async (c, id, existing, body) => {
47871
+ const storageBody = fieldKeys.toStorage(body);
47841
47872
  if (body["published"] === true) {
47842
47873
  const publishDeny = await requirePermission("content:edit")(c, async () => {
47843
47874
  });
47844
47875
  if (publishDeny) return publishDeny;
47845
- const merged = { ...existing, ...body };
47876
+ const merged = { ...fieldKeys.toLabels(existing), ...body };
47846
47877
  const fieldErrors = checkRequiredFields(requiredFields, merged);
47847
47878
  if (fieldErrors.length > 0) {
47848
47879
  return c.json(
@@ -47862,17 +47893,14 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
47862
47893
  const patchJunctionFields = contentType.fields.filter(
47863
47894
  (f) => f.db_column !== null && f.db_column.junction !== void 0
47864
47895
  );
47865
- const patchColumnFields = contentType.fields.filter(
47866
- (f) => f.db_column !== null && f.db_column.junction === void 0
47867
- );
47896
+ const patchColumnBackedFields = contentType.fields.filter(isColumnBacked);
47868
47897
  const patchData = {};
47869
47898
  if (!contentType.only_one && "slug" in body) patchData["slug"] = body["slug"];
47870
47899
  if ("published" in body) patchData["published"] = body["published"];
47871
- for (const f of patchColumnFields) {
47872
- if (f.name in body) {
47873
- const val = body[f.name];
47874
- patchData[f.db_column.column_name] = val !== void 0 ? val : null;
47875
- }
47900
+ for (const f of patchColumnBackedFields) {
47901
+ const key = f.db_column.column_name;
47902
+ if (!(key in storageBody)) continue;
47903
+ patchData[key] = storageBody[key];
47876
47904
  }
47877
47905
  const updated = await repo.update(id, patchData);
47878
47906
  if (!updated) {
@@ -47882,7 +47910,7 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
47882
47910
  );
47883
47911
  }
47884
47912
  const mediaDeltas = [
47885
- topLevelMediaDelta(mediaFields, existing, body)
47913
+ topLevelMediaDelta(mediaFields, existing, storageBody)
47886
47914
  ];
47887
47915
  if (db) {
47888
47916
  for (const f of patchParagraphFields) {
@@ -47900,7 +47928,8 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
47900
47928
  }
47901
47929
  }
47902
47930
  await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
47903
- return c.json({ ok: true, data: updated });
47931
+ const data = fieldKeys.toLabels(updated);
47932
+ return c.json({ ok: true, data });
47904
47933
  };
47905
47934
  if (contentType.only_one) {
47906
47935
  app.put(
@@ -47960,6 +47989,7 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
47960
47989
  const relationFieldNames = new Set(
47961
47990
  taxonomyType.fields.filter((f) => RELATION_FIELD_TYPES.has(f.field_type)).map((f) => f.name)
47962
47991
  );
47992
+ const fieldKeys = fieldKeyMaps[typeName];
47963
47993
  const requiredFields = taxonomyType.fields.filter((f) => f.required);
47964
47994
  const mediaFields = taxonomyType.fields.filter(
47965
47995
  (f) => f.field_type === "image" || f.field_type === "video" || f.field_type === "file"
@@ -47973,7 +48003,7 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
47973
48003
  `/admin/api/taxonomy/${typeName}`,
47974
48004
  requirePermission("content:read"),
47975
48005
  async (c) => {
47976
- const parsed = parseListQuery(c.req.url, schemaFieldNames, relationFieldNames);
48006
+ const parsed = parseListQuery(c.req.url, schemaFieldNames, relationFieldNames, fieldKeys.columnFor);
47977
48007
  if (!parsed.ok) {
47978
48008
  return c.json({ ok: false, error: parsed.response }, parsed.status);
47979
48009
  }
@@ -47993,7 +48023,8 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
47993
48023
  findOpts.search = { term: parsed.search, columns: searchableColumns };
47994
48024
  }
47995
48025
  const result = await repo.findMany(findOpts);
47996
- return c.json(result);
48026
+ const data = result.data.map((row) => fieldKeys.toLabels(row));
48027
+ return c.json({ ...result, data });
47997
48028
  }
47998
48029
  );
47999
48030
  app.get(
@@ -48008,7 +48039,8 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
48008
48039
  404
48009
48040
  );
48010
48041
  }
48011
- return c.json({ ok: true, data: item });
48042
+ const data = fieldKeys.toLabels(item);
48043
+ return c.json({ ok: true, data });
48012
48044
  }
48013
48045
  );
48014
48046
  app.post(
@@ -48016,6 +48048,7 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
48016
48048
  requirePermission("content:create"),
48017
48049
  async (c) => {
48018
48050
  const body = await c.req.json();
48051
+ const storageBody = fieldKeys.toStorage(body);
48019
48052
  const fieldErrors = checkRequiredFields(requiredFields, body);
48020
48053
  if (fieldErrors.length > 0) {
48021
48054
  return c.json(
@@ -48035,20 +48068,19 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
48035
48068
  });
48036
48069
  if (publishDeny) return publishDeny;
48037
48070
  }
48038
- const taxColumnFields = taxonomyType.fields.filter(
48039
- (f) => f.db_column !== null && f.db_column.junction === void 0
48040
- );
48041
48071
  const taxParagraphFields = taxonomyType.fields.filter((f) => f.db_column === null);
48072
+ const taxColumnBackedFields = taxonomyType.fields.filter(isColumnBacked);
48042
48073
  const taxColumnData = {
48043
48074
  published: body["published"] ?? false
48044
48075
  };
48045
- for (const f of taxColumnFields) {
48046
- const val = body[f.name];
48047
- taxColumnData[f.db_column.column_name] = val !== void 0 ? val : null;
48076
+ for (const f of taxColumnBackedFields) {
48077
+ const key = f.db_column.column_name;
48078
+ if (!(key in storageBody)) continue;
48079
+ taxColumnData[key] = storageBody[key];
48048
48080
  }
48049
48081
  const item = await repo.create(taxColumnData);
48050
48082
  const taxItemId = item["id"];
48051
- const mediaDeltas = [topLevelMediaDelta(mediaFields, null, body)];
48083
+ const mediaDeltas = [topLevelMediaDelta(mediaFields, null, storageBody)];
48052
48084
  if (db) {
48053
48085
  for (const f of taxParagraphFields) {
48054
48086
  const comp = f.ui_component;
@@ -48060,7 +48092,8 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
48060
48092
  }
48061
48093
  }
48062
48094
  await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
48063
- return c.json({ ok: true, data: item }, 201);
48095
+ const data = fieldKeys.toLabels(item);
48096
+ return c.json({ ok: true, data }, 201);
48064
48097
  }
48065
48098
  );
48066
48099
  app.patch(
@@ -48069,6 +48102,7 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
48069
48102
  async (c) => {
48070
48103
  const id = c.req.param("id");
48071
48104
  const body = await c.req.json();
48105
+ const storageBody = fieldKeys.toStorage(body);
48072
48106
  const existing = await repo.findOne(id);
48073
48107
  if (!existing) {
48074
48108
  return c.json(
@@ -48080,7 +48114,7 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
48080
48114
  const publishDeny = await requirePermission("content:edit")(c, async () => {
48081
48115
  });
48082
48116
  if (publishDeny) return publishDeny;
48083
- const merged = { ...existing, ...body };
48117
+ const merged = { ...fieldKeys.toLabels(existing), ...body };
48084
48118
  const fieldErrors = checkRequiredFields(requiredFields, merged);
48085
48119
  if (fieldErrors.length > 0) {
48086
48120
  return c.json(
@@ -48096,17 +48130,14 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
48096
48130
  );
48097
48131
  }
48098
48132
  }
48099
- const taxPatchColumnFields = taxonomyType.fields.filter(
48100
- (f) => f.db_column !== null && f.db_column.junction === void 0
48101
- );
48102
48133
  const taxPatchParagraphFields = taxonomyType.fields.filter((f) => f.db_column === null);
48134
+ const taxPatchColumnBackedFields = taxonomyType.fields.filter(isColumnBacked);
48103
48135
  const taxPatchData = {};
48104
48136
  if ("published" in body) taxPatchData["published"] = body["published"];
48105
- for (const f of taxPatchColumnFields) {
48106
- if (f.name in body) {
48107
- const val = body[f.name];
48108
- taxPatchData[f.db_column.column_name] = val !== void 0 ? val : null;
48109
- }
48137
+ for (const f of taxPatchColumnBackedFields) {
48138
+ const key = f.db_column.column_name;
48139
+ if (!(key in storageBody)) continue;
48140
+ taxPatchData[key] = storageBody[key];
48110
48141
  }
48111
48142
  const updated = await repo.update(id, taxPatchData);
48112
48143
  if (!updated) {
@@ -48116,7 +48147,7 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
48116
48147
  );
48117
48148
  }
48118
48149
  const mediaDeltas = [
48119
- topLevelMediaDelta(mediaFields, existing, body)
48150
+ topLevelMediaDelta(mediaFields, existing, storageBody)
48120
48151
  ];
48121
48152
  if (db) {
48122
48153
  for (const f of taxPatchParagraphFields) {
@@ -48129,7 +48160,8 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
48129
48160
  }
48130
48161
  }
48131
48162
  await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
48132
- return c.json({ ok: true, data: updated });
48163
+ const data = fieldKeys.toLabels(updated);
48164
+ return c.json({ ok: true, data });
48133
48165
  }
48134
48166
  );
48135
48167
  app.delete(
@@ -49102,7 +49134,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
49102
49134
  }
49103
49135
  var API_VERSION = (() => {
49104
49136
  try {
49105
- return "0.3.3";
49137
+ return "0.4.0";
49106
49138
  } catch {
49107
49139
  return "0.0.0";
49108
49140
  }
@@ -49500,7 +49532,8 @@ function createCmsApp(options) {
49500
49532
  if (!options.storage) {
49501
49533
  throw new Error(MISSING_STORAGE_ERROR);
49502
49534
  }
49503
- const prefix = options.prefix ?? "/api";
49535
+ const prefix = normalizePrefix(options.prefix);
49536
+ const publicPaths = createPublicPaths(prefix);
49504
49537
  const { storage, registry: registry2, db, rateLimit, media, cors } = options;
49505
49538
  const cmsName = options.name ?? "Manguito CMS";
49506
49539
  const maxFileSize = media?.max_file_size;
@@ -49580,23 +49613,27 @@ function createCmsApp(options) {
49580
49613
  registerAuthRoutes(app, db);
49581
49614
  app.use("/admin/api/*", authMiddleware);
49582
49615
  app.use("/admin/api/*", mustChangePasswordCheck);
49583
- app.get("/api/openapi.json", (c) => {
49616
+ app.get(publicPaths.openapi(), (c) => {
49584
49617
  const paths = {};
49585
49618
  for (const ct2 of Object.values(registry2.content_types)) {
49586
49619
  const base = ct2.default_base_path;
49587
49620
  if (ct2.only_one) {
49588
- paths[`/api/${base}`] = { get: { summary: `Get ${ct2.label}`, tags: [ct2.label] } };
49621
+ paths[publicPaths.collection(base)] = { get: { summary: `Get ${ct2.label}`, tags: [ct2.label] } };
49589
49622
  } else {
49590
- paths[`/api/${base}`] = { get: { summary: `List published ${ct2.label}`, tags: [ct2.label] } };
49591
- paths[`/api/${base}/{slug}`] = { get: { summary: `Get ${ct2.label} by slug`, tags: [ct2.label] } };
49623
+ paths[publicPaths.collection(base)] = { get: { summary: `List published ${ct2.label}`, tags: [ct2.label] } };
49624
+ paths[publicPaths.item(base).replace(":slug", "{slug}")] = {
49625
+ get: { summary: `Get ${ct2.label} by slug`, tags: [ct2.label] }
49626
+ };
49592
49627
  }
49593
49628
  }
49594
49629
  for (const tt2 of Object.values(registry2.taxonomy_types)) {
49595
- paths[`/api/taxonomy/${tt2.name}`] = { get: { summary: `List published ${tt2.label}`, tags: [tt2.label] } };
49596
- paths[`/api/taxonomy/${tt2.name}/{id}`] = { get: { summary: `Get ${tt2.label} by id`, tags: [tt2.label] } };
49630
+ paths[publicPaths.taxonomyCollection(tt2.name)] = { get: { summary: `List published ${tt2.label}`, tags: [tt2.label] } };
49631
+ paths[publicPaths.taxonomyItem(tt2.name).replace(":id", "{id}")] = {
49632
+ get: { summary: `Get ${tt2.label} by id`, tags: [tt2.label] }
49633
+ };
49597
49634
  }
49598
- paths["/api/media"] = { get: { summary: "List media items", tags: ["Media"] } };
49599
- paths["/api/media/{id}"] = { get: { summary: "Get media item by id", tags: ["Media"] } };
49635
+ paths[publicPaths.mediaCollection()] = { get: { summary: "List media items", tags: ["Media"] } };
49636
+ paths[publicPaths.mediaItem().replace(":id", "{id}")] = { get: { summary: "Get media item by id", tags: ["Media"] } };
49600
49637
  return c.json({
49601
49638
  openapi: "3.0.3",
49602
49639
  info: { title: "Manguito CMS Public API", version: "1.0.0" },
@@ -49611,14 +49648,24 @@ function createCmsApp(options) {
49611
49648
  paths: {}
49612
49649
  })
49613
49650
  );
49614
- registerPublicContentRoutes(app, registry2, publicRepos, listRateLimit, programmaticResolver);
49615
- registerPublicMediaRoutes(app, mediaRepo, listRateLimit);
49651
+ const fieldKeyMaps = Object.fromEntries([
49652
+ ...Object.entries(registry2.content_types).map(([typeName, ct2]) => [
49653
+ typeName,
49654
+ createFieldKeyMap(ct2.fields)
49655
+ ]),
49656
+ ...Object.entries(registry2.taxonomy_types).map(([typeName, tt2]) => [
49657
+ typeName,
49658
+ createFieldKeyMap(tt2.fields)
49659
+ ])
49660
+ ]);
49661
+ registerPublicContentRoutes(app, registry2, publicRepos, fieldKeyMaps, publicPaths, listRateLimit, programmaticResolver);
49662
+ registerPublicMediaRoutes(app, mediaRepo, publicPaths, listRateLimit);
49616
49663
  if (options.graphql?.enabled) {
49617
49664
  const gqlOptions = options.graphql;
49618
49665
  let gqlHandler = null;
49619
49666
  let initError = null;
49620
- const ready = import("./handler-CGHZG3J5-TCG6K3FE.js").then(({ createGraphQLHandler }) => {
49621
- gqlHandler = createGraphQLHandler(registry2, graphqlRepos, programmaticResolver, db, gqlOptions);
49667
+ const ready = import("./handler-TPEULQ5W-5VGZ4FJU.js").then(({ createGraphQLHandler }) => {
49668
+ gqlHandler = createGraphQLHandler(registry2, graphqlRepos, fieldKeyMaps, programmaticResolver, db, gqlOptions);
49622
49669
  }).catch((err) => {
49623
49670
  initError = err;
49624
49671
  const message = err instanceof Error ? err.message : String(err);
@@ -49658,7 +49705,7 @@ function createCmsApp(options) {
49658
49705
  );
49659
49706
  registerSchemaRoute(app, registry2, db);
49660
49707
  registerUserRoutes(app, db, requirePermission, requireHierarchy);
49661
- registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePermission, db);
49708
+ registerAdminContentRoutes(app, registry2, repos, fieldKeyMaps, mediaRepo, requirePermission, db);
49662
49709
  registerAdminMediaRoutes(app, mediaRepo, storage, requirePermission, maxFileSize);
49663
49710
  return { prefix, app };
49664
49711
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobbykim/manguito-cms-cli",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
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.0",
32
+ "@bobbykim/manguito-cms-api": "0.4.0",
33
33
  "@bobbykim/manguito-cms-core": "0.3.0",
34
- "@bobbykim/manguito-cms-db": "0.1.3",
35
- "@bobbykim/manguito-cms-api": "0.3.3"
34
+ "@bobbykim/manguito-cms-admin": "0.4.0",
35
+ "@bobbykim/manguito-cms-db": "0.1.3"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/node": "^25.9.5",