@bobbykim/manguito-cms-api 0.4.0 → 0.4.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.
package/dist/index.js CHANGED
@@ -344,10 +344,16 @@ function isColumnBacked(field) {
344
344
  function createFieldKeyMap(fields) {
345
345
  const labelToColumn = /* @__PURE__ */ new Map();
346
346
  const columnToLabel = /* @__PURE__ */ new Map();
347
+ const droppedKeys = /* @__PURE__ */ new Set();
347
348
  for (const f of fields) {
348
349
  if (!isColumnBacked(f)) continue;
349
- labelToColumn.set(f.name, f.db_column.column_name);
350
350
  columnToLabel.set(f.db_column.column_name, f.name);
351
+ if (f.removed === true) {
352
+ droppedKeys.add(f.name);
353
+ droppedKeys.add(f.db_column.column_name);
354
+ continue;
355
+ }
356
+ labelToColumn.set(f.name, f.db_column.column_name);
351
357
  }
352
358
  for (const f of fields) {
353
359
  const columnOwner = columnToLabel.get(f.name);
@@ -357,10 +363,16 @@ function createFieldKeyMap(fields) {
357
363
  );
358
364
  }
359
365
  }
366
+ for (const f of fields) {
367
+ if (isColumnBacked(f) && f.removed === true) {
368
+ columnToLabel.delete(f.db_column.column_name);
369
+ }
370
+ }
360
371
  const diverges = [...labelToColumn].some(([label, column]) => label !== column);
361
372
  function remap(input, lookup) {
362
373
  const out = {};
363
374
  for (const key of Object.keys(input)) {
375
+ if (droppedKeys.has(key)) continue;
364
376
  out[lookup.get(key) ?? key] = input[key];
365
377
  }
366
378
  return out;
@@ -500,7 +512,7 @@ var init_naming = __esm({
500
512
  function buildSortFieldEnum(typeName) {
501
513
  return new import_graphql3.GraphQLEnumType({
502
514
  name: `${typeName}SortField`,
503
- values: Object.fromEntries(SORTABLE.map((s) => [s.gql, { value: s.column }]))
515
+ values: Object.fromEntries(SORTABLE.map((s) => [s.gql, { value: s.value }]))
504
516
  });
505
517
  }
506
518
  function comparableFilterInput(name, scalar) {
@@ -586,9 +598,9 @@ var init_filters = __esm({
586
598
  values: { ASC: { value: "asc" }, DESC: { value: "desc" } }
587
599
  });
588
600
  SORTABLE = [
589
- { gql: "title", column: "title" },
590
- { gql: "createdAt", column: "created_at" },
591
- { gql: "updatedAt", column: "updated_at" }
601
+ { gql: "title", value: "title" },
602
+ { gql: "createdAt", value: "created_at" },
603
+ { gql: "updatedAt", value: "updated_at" }
592
604
  ];
593
605
  StringFilter = equalityFilterInput("StringFilter", import_graphql3.GraphQLString);
594
606
  BooleanFilter = new import_graphql3.GraphQLInputObjectType({
@@ -647,11 +659,13 @@ function collectionResolver(typeName, nameMap, fieldKeys) {
647
659
  throw codeError2("INVALID_PAGINATION", "perPage must be between 1 and 100");
648
660
  }
649
661
  const repo = ctx.repos[typeName];
662
+ const sortBy = args.sortBy ?? "created_at";
663
+ const sortColumn = fieldKeys?.columnFor(sortBy) ?? sortBy;
650
664
  const result = await repo.findMany({
651
665
  published_only: true,
652
666
  page,
653
667
  per_page: perPage,
654
- sort_by: args.sortBy ?? "created_at",
668
+ sort_by: sortColumn,
655
669
  sort_order: args.sortOrder ?? "asc",
656
670
  filters: translateFilters(args.filter, nameMap, fieldKeys?.columnFor)
657
671
  });
@@ -1643,6 +1657,54 @@ function createProgrammaticResolver(resolvers, options) {
1643
1657
  return { resolveItem, resolveList, hasSchema };
1644
1658
  }
1645
1659
 
1660
+ // src/projector.ts
1661
+ function isPlainRow(v) {
1662
+ return typeof v === "object" && v !== null && !Array.isArray(v) && !(v instanceof Date);
1663
+ }
1664
+ var MEDIA_FIELD_TYPES = /* @__PURE__ */ new Set(["image", "video", "file"]);
1665
+ function nestedTargets(fields) {
1666
+ const out = [];
1667
+ for (const f of fields) {
1668
+ if (MEDIA_FIELD_TYPES.has(f.field_type)) continue;
1669
+ if (f.field_type !== "paragraph" && f.field_type !== "reference") continue;
1670
+ const ref = f.ui_component.ref;
1671
+ if (!ref) continue;
1672
+ out.push({ label: f.name, target: ref });
1673
+ }
1674
+ return out;
1675
+ }
1676
+ function buildProjectors(registry, fieldKeyMaps) {
1677
+ const projectors = {};
1678
+ const sources = [
1679
+ registry.content_types,
1680
+ registry.taxonomy_types,
1681
+ registry.paragraph_types
1682
+ ];
1683
+ for (const source of sources) {
1684
+ for (const [typeName, type] of Object.entries(source)) {
1685
+ const map = fieldKeyMaps[typeName];
1686
+ if (!map) continue;
1687
+ projectors[typeName] = { map, nested: nestedTargets(type.fields) };
1688
+ }
1689
+ }
1690
+ return projectors;
1691
+ }
1692
+ function projectRow(row, typeName, projectors) {
1693
+ const p = projectors[typeName];
1694
+ if (!p) return row;
1695
+ const out = p.map.toLabels(row);
1696
+ for (const { label, target } of p.nested) {
1697
+ const v = out[label];
1698
+ if (v === null || v === void 0) continue;
1699
+ if (Array.isArray(v)) {
1700
+ out[label] = v.map((item) => isPlainRow(item) ? projectRow(item, target, projectors) : item);
1701
+ } else if (isPlainRow(v)) {
1702
+ out[label] = projectRow(v, target, projectors);
1703
+ }
1704
+ }
1705
+ return out;
1706
+ }
1707
+
1646
1708
  // src/routes/query-params.ts
1647
1709
  var SORTABLE_FIELDS = /* @__PURE__ */ new Set(["title", "created_at", "updated_at"]);
1648
1710
  var RELATION_FIELD_TYPES = /* @__PURE__ */ new Set([
@@ -1697,7 +1759,7 @@ function parseFilters(url, validFields, columnFor) {
1697
1759
  function isPublished(item) {
1698
1760
  return item["published"] === true;
1699
1761
  }
1700
- function registerPublicContentRoutes(app, registry, repos, fieldKeyMaps, paths, listRateLimit, resolver) {
1762
+ function registerPublicContentRoutes(app, registry, repos, projectors, paths, listRateLimit, resolver) {
1701
1763
  function registerListRoute(path, handler) {
1702
1764
  if (listRateLimit) {
1703
1765
  app.get(path, listRateLimit, handler);
@@ -1740,13 +1802,14 @@ function registerPublicContentRoutes(app, registry, repos, fieldKeyMaps, paths,
1740
1802
  404
1741
1803
  );
1742
1804
  }
1743
- let data = fieldKeyMaps[typeName].toLabels(result.data[0]);
1805
+ let data = projectRow(result.data[0], typeName, projectors);
1744
1806
  if (resolver?.hasSchema(typeName)) data = await resolver.resolveItem(typeName, data);
1745
1807
  return c.json({ ok: true, data });
1746
1808
  });
1747
1809
  } else {
1748
1810
  registerListRoute(paths.collection(basePath), async (c) => {
1749
- const fieldKeys = fieldKeyMaps[typeName];
1811
+ const projector = projectors[typeName];
1812
+ const fieldKeys = projector.map;
1750
1813
  const pagination = parsePagination(c.req.query("page"), c.req.query("per_page"));
1751
1814
  if (!pagination.ok) {
1752
1815
  return c.json(
@@ -1814,17 +1877,18 @@ function registerPublicContentRoutes(app, registry, repos, fieldKeyMaps, paths,
1814
1877
  );
1815
1878
  }
1816
1879
  }
1880
+ const sortColumn = fieldKeys.columnFor(sortBy) ?? sortBy;
1817
1881
  const result = await repo.findMany({
1818
1882
  published_only: true,
1819
1883
  page: pagination.page,
1820
1884
  per_page: pagination.per_page,
1821
- sort_by: sortBy,
1885
+ sort_by: sortColumn,
1822
1886
  sort_order: sortOrder,
1823
1887
  filters: filtersResult.filters,
1824
1888
  include
1825
1889
  });
1826
1890
  const labeled = result.data.map(
1827
- (row) => fieldKeys.toLabels(row)
1891
+ (row) => projectRow(row, typeName, projectors)
1828
1892
  );
1829
1893
  const data = resolver?.hasSchema(typeName) ? await resolver.resolveList(typeName, labeled) : labeled;
1830
1894
  return c.json({ ...result, data });
@@ -1856,7 +1920,7 @@ function registerPublicContentRoutes(app, registry, repos, fieldKeyMaps, paths,
1856
1920
  404
1857
1921
  );
1858
1922
  }
1859
- let data = fieldKeyMaps[typeName].toLabels(item);
1923
+ let data = projectRow(item, typeName, projectors);
1860
1924
  if (resolver?.hasSchema(typeName)) data = await resolver.resolveItem(typeName, data);
1861
1925
  return c.json({ ok: true, data });
1862
1926
  });
@@ -1884,9 +1948,8 @@ function registerPublicContentRoutes(app, registry, repos, fieldKeyMaps, paths,
1884
1948
  page: pagination.page,
1885
1949
  per_page: pagination.per_page
1886
1950
  });
1887
- const fieldKeys = fieldKeyMaps[typeName];
1888
1951
  const labeled = result.data.map(
1889
- (row) => fieldKeys.toLabels(row)
1952
+ (row) => projectRow(row, typeName, projectors)
1890
1953
  );
1891
1954
  const data = resolver?.hasSchema(typeName) ? await resolver.resolveList(typeName, labeled) : labeled;
1892
1955
  return c.json({ ...result, data });
@@ -1903,7 +1966,7 @@ function registerPublicContentRoutes(app, registry, repos, fieldKeyMaps, paths,
1903
1966
  404
1904
1967
  );
1905
1968
  }
1906
- let data = fieldKeyMaps[typeName].toLabels(item);
1969
+ let data = projectRow(item, typeName, projectors);
1907
1970
  if (resolver?.hasSchema(typeName)) data = await resolver.resolveItem(typeName, data);
1908
1971
  return c.json({ ok: true, data });
1909
1972
  });
@@ -2119,7 +2182,7 @@ function parseListQuery(url, schemaFieldNames, relationFieldNames, columnFor) {
2119
2182
  search
2120
2183
  };
2121
2184
  }
2122
- function registerAdminContentRoutes(app, registry, repos, fieldKeyMaps, mediaRepo, requirePermission, db) {
2185
+ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo, requirePermission, db) {
2123
2186
  for (const [typeName, contentType] of Object.entries(registry.content_types)) {
2124
2187
  const basePath = `content/${typeName}`;
2125
2188
  const repo = repos[typeName];
@@ -2131,7 +2194,8 @@ function registerAdminContentRoutes(app, registry, repos, fieldKeyMaps, mediaRep
2131
2194
  const relationFieldNames = new Set(
2132
2195
  contentType.fields.filter((f) => RELATION_FIELD_TYPES.has(f.field_type)).map((f) => f.name)
2133
2196
  );
2134
- const fieldKeys = fieldKeyMaps[typeName];
2197
+ const projector = projectors[typeName];
2198
+ const fieldKeys = projector.map;
2135
2199
  const requiredFields = contentType.fields.filter((f) => f.required);
2136
2200
  const mediaFields = contentType.fields.filter(
2137
2201
  (f) => f.field_type === "image" || f.field_type === "video" || f.field_type === "file"
@@ -2152,10 +2216,11 @@ function registerAdminContentRoutes(app, registry, repos, fieldKeyMaps, mediaRep
2152
2216
  const publishedParam = c.req.query("published");
2153
2217
  const extraFilters = {};
2154
2218
  if (publishedParam === "false") extraFilters["published"] = false;
2219
+ const sortColumn = fieldKeys.columnFor(parsed.sortBy) ?? parsed.sortBy;
2155
2220
  const findOpts = {
2156
2221
  page: parsed.pagination.page,
2157
2222
  per_page: parsed.pagination.per_page,
2158
- sort_by: parsed.sortBy,
2223
+ sort_by: sortColumn,
2159
2224
  sort_order: parsed.sortOrder,
2160
2225
  filters: { ...parsed.filters, ...extraFilters },
2161
2226
  include: parsed.include
@@ -2165,7 +2230,9 @@ function registerAdminContentRoutes(app, registry, repos, fieldKeyMaps, mediaRep
2165
2230
  findOpts.search = { term: parsed.search, columns: searchableColumns };
2166
2231
  }
2167
2232
  const result = await repo.findMany(findOpts);
2168
- const data = result.data.map((row) => fieldKeys.toLabels(row));
2233
+ const data = result.data.map(
2234
+ (row) => projectRow(row, typeName, projectors)
2235
+ );
2169
2236
  return c.json({ ...result, data });
2170
2237
  }
2171
2238
  );
@@ -2201,7 +2268,7 @@ function registerAdminContentRoutes(app, registry, repos, fieldKeyMaps, mediaRep
2201
2268
  }
2202
2269
  }
2203
2270
  }
2204
- const data = fieldKeys.toLabels(item);
2271
+ const data = projectRow(item, typeName, projectors);
2205
2272
  return c.json({ ok: true, data });
2206
2273
  }
2207
2274
  );
@@ -2342,7 +2409,7 @@ function registerAdminContentRoutes(app, registry, repos, fieldKeyMaps, mediaRep
2342
2409
  }
2343
2410
  }
2344
2411
  await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
2345
- const data = fieldKeys.toLabels(item);
2412
+ const data = projectRow(item, typeName, projectors);
2346
2413
  return c.json({ ok: true, data }, 201);
2347
2414
  };
2348
2415
  app.patch(
@@ -2463,7 +2530,7 @@ function registerAdminContentRoutes(app, registry, repos, fieldKeyMaps, mediaRep
2463
2530
  }
2464
2531
  }
2465
2532
  await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
2466
- const data = fieldKeys.toLabels(updated);
2533
+ const data = projectRow(updated, typeName, projectors);
2467
2534
  return c.json({ ok: true, data });
2468
2535
  };
2469
2536
  if (contentType.only_one) {
@@ -2524,7 +2591,8 @@ function registerAdminContentRoutes(app, registry, repos, fieldKeyMaps, mediaRep
2524
2591
  const relationFieldNames = new Set(
2525
2592
  taxonomyType.fields.filter((f) => RELATION_FIELD_TYPES.has(f.field_type)).map((f) => f.name)
2526
2593
  );
2527
- const fieldKeys = fieldKeyMaps[typeName];
2594
+ const projector = projectors[typeName];
2595
+ const fieldKeys = projector.map;
2528
2596
  const requiredFields = taxonomyType.fields.filter((f) => f.required);
2529
2597
  const mediaFields = taxonomyType.fields.filter(
2530
2598
  (f) => f.field_type === "image" || f.field_type === "video" || f.field_type === "file"
@@ -2545,10 +2613,11 @@ function registerAdminContentRoutes(app, registry, repos, fieldKeyMaps, mediaRep
2545
2613
  const publishedParam = c.req.query("published");
2546
2614
  const extraFilters = {};
2547
2615
  if (publishedParam === "false") extraFilters["published"] = false;
2616
+ const sortColumn = fieldKeys.columnFor(parsed.sortBy) ?? parsed.sortBy;
2548
2617
  const findOpts = {
2549
2618
  page: parsed.pagination.page,
2550
2619
  per_page: parsed.pagination.per_page,
2551
- sort_by: parsed.sortBy,
2620
+ sort_by: sortColumn,
2552
2621
  sort_order: parsed.sortOrder,
2553
2622
  filters: { ...parsed.filters, ...extraFilters },
2554
2623
  include: parsed.include
@@ -2558,7 +2627,9 @@ function registerAdminContentRoutes(app, registry, repos, fieldKeyMaps, mediaRep
2558
2627
  findOpts.search = { term: parsed.search, columns: searchableColumns };
2559
2628
  }
2560
2629
  const result = await repo.findMany(findOpts);
2561
- const data = result.data.map((row) => fieldKeys.toLabels(row));
2630
+ const data = result.data.map(
2631
+ (row) => projectRow(row, typeName, projectors)
2632
+ );
2562
2633
  return c.json({ ...result, data });
2563
2634
  }
2564
2635
  );
@@ -2574,7 +2645,7 @@ function registerAdminContentRoutes(app, registry, repos, fieldKeyMaps, mediaRep
2574
2645
  404
2575
2646
  );
2576
2647
  }
2577
- const data = fieldKeys.toLabels(item);
2648
+ const data = projectRow(item, typeName, projectors);
2578
2649
  return c.json({ ok: true, data });
2579
2650
  }
2580
2651
  );
@@ -2627,7 +2698,7 @@ function registerAdminContentRoutes(app, registry, repos, fieldKeyMaps, mediaRep
2627
2698
  }
2628
2699
  }
2629
2700
  await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
2630
- const data = fieldKeys.toLabels(item);
2701
+ const data = projectRow(item, typeName, projectors);
2631
2702
  return c.json({ ok: true, data }, 201);
2632
2703
  }
2633
2704
  );
@@ -2695,7 +2766,7 @@ function registerAdminContentRoutes(app, registry, repos, fieldKeyMaps, mediaRep
2695
2766
  }
2696
2767
  }
2697
2768
  await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
2698
- const data = fieldKeys.toLabels(updated);
2769
+ const data = projectRow(updated, typeName, projectors);
2699
2770
  return c.json({ ok: true, data });
2700
2771
  }
2701
2772
  );
@@ -3688,7 +3759,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
3688
3759
  var import_drizzle_orm6 = require("drizzle-orm");
3689
3760
  var API_VERSION = (() => {
3690
3761
  try {
3691
- return "0.4.0";
3762
+ return "0.4.2";
3692
3763
  } catch {
3693
3764
  return "0.0.0";
3694
3765
  }
@@ -3734,6 +3805,9 @@ function registerConfigRoute(app, config, registry, db) {
3734
3805
 
3735
3806
  // src/routes/admin/schema.ts
3736
3807
  var import_drizzle_orm7 = require("drizzle-orm");
3808
+ function liveFields(fields) {
3809
+ return fields.filter((f) => f.removed !== true);
3810
+ }
3737
3811
  function registerSchemaRoute(app, registry, db) {
3738
3812
  app.get("/admin/api/schema", (c) => {
3739
3813
  const content_types = Object.values(registry.content_types).map((ct) => ({
@@ -3742,19 +3816,19 @@ function registerSchemaRoute(app, registry, db) {
3742
3816
  only_one: ct.only_one,
3743
3817
  ui: ct.ui,
3744
3818
  system_fields: ct.system_fields,
3745
- fields: ct.fields
3819
+ fields: liveFields(ct.fields)
3746
3820
  }));
3747
3821
  const taxonomy_types = Object.values(registry.taxonomy_types).map((tt) => ({
3748
3822
  name: tt.name,
3749
3823
  label: tt.label,
3750
3824
  system_fields: tt.system_fields,
3751
- fields: tt.fields
3825
+ fields: liveFields(tt.fields)
3752
3826
  }));
3753
3827
  const paragraph_types = Object.values(registry.paragraph_types).map((pt) => ({
3754
3828
  name: pt.name,
3755
3829
  label: pt.label,
3756
3830
  system_fields: pt.system_fields,
3757
- fields: pt.fields
3831
+ fields: liveFields(pt.fields)
3758
3832
  }));
3759
3833
  const enum_types = Object.values(registry.enum_types).map((et) => ({
3760
3834
  name: et.name,
@@ -3851,7 +3925,7 @@ function whereFragment(conds) {
3851
3925
  return conds.length > 0 ? import_drizzle_orm8.sql` WHERE ${import_drizzle_orm8.sql.join(conds, import_drizzle_orm8.sql` AND `)}` : import_drizzle_orm8.sql``;
3852
3926
  }
3853
3927
  function createDrizzleContentRepository(db, tableName, options = {}) {
3854
- const { relations = {}, publishedRelations = false } = options;
3928
+ const { relations = {}, publishedRelations = false, sortableColumns } = options;
3855
3929
  function tableRaw() {
3856
3930
  return import_drizzle_orm8.sql.raw(quoteIdent3(tableName));
3857
3931
  }
@@ -3889,8 +3963,9 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
3889
3963
  sort_order = "asc",
3890
3964
  search
3891
3965
  } = opts;
3892
- if (!SORTABLE_FIELDS2.has(sort_by)) {
3893
- throw codeError("INVALID_SORT_FIELD", `'${sort_by}' is not sortable. Allowed: title, created_at, updated_at`);
3966
+ const allowed = sortableColumns ?? SORTABLE_FIELDS2;
3967
+ if (!allowed.has(sort_by)) {
3968
+ throw codeError("INVALID_SORT_FIELD", `'${sort_by}' is not sortable.`);
3894
3969
  }
3895
3970
  const offset = (page - 1) * per_page;
3896
3971
  const conds = buildConditions(published_only, filters, search);
@@ -4139,16 +4214,38 @@ function createCmsApp(options) {
4139
4214
  return result.rows[0]?.role ?? null;
4140
4215
  };
4141
4216
  const requireHierarchy = createHierarchyMiddleware(rolesRegistry, getUserRole);
4217
+ const fieldKeyMaps = Object.fromEntries([
4218
+ ...Object.entries(registry.content_types).map(([typeName, ct]) => [
4219
+ typeName,
4220
+ createFieldKeyMap(ct.fields)
4221
+ ]),
4222
+ ...Object.entries(registry.taxonomy_types).map(([typeName, tt]) => [
4223
+ typeName,
4224
+ createFieldKeyMap(tt.fields)
4225
+ ]),
4226
+ ...Object.entries(registry.paragraph_types).map(([typeName, pt]) => [
4227
+ typeName,
4228
+ createFieldKeyMap(pt.fields)
4229
+ ])
4230
+ ]);
4231
+ const projectors = buildProjectors(registry, fieldKeyMaps);
4232
+ const sortableColumnsFor = (typeName) => new Set(
4233
+ [...SORTABLE_FIELDS].map((label) => fieldKeyMaps[typeName].columnFor(label) ?? label)
4234
+ );
4142
4235
  const contentRepos = Object.fromEntries(
4143
4236
  Object.entries(registry.content_types).map(([typeName, ct]) => [
4144
4237
  typeName,
4145
- createDrizzleContentRepository(db, ct.db.table_name)
4238
+ createDrizzleContentRepository(db, ct.db.table_name, {
4239
+ sortableColumns: sortableColumnsFor(typeName)
4240
+ })
4146
4241
  ])
4147
4242
  );
4148
4243
  const taxonomyRepos = Object.fromEntries(
4149
4244
  Object.entries(registry.taxonomy_types).map(([typeName, tt]) => [
4150
4245
  typeName,
4151
- createDrizzleContentRepository(db, tt.db.table_name)
4246
+ createDrizzleContentRepository(db, tt.db.table_name, {
4247
+ sortableColumns: sortableColumnsFor(typeName)
4248
+ })
4152
4249
  ])
4153
4250
  );
4154
4251
  const repos = { ...contentRepos, ...taxonomyRepos };
@@ -4158,7 +4255,8 @@ function createCmsApp(options) {
4158
4255
  typeName,
4159
4256
  createDrizzleContentRepository(db, ct.db.table_name, {
4160
4257
  relations: buildRelationsMap(ct.fields, registry),
4161
- publishedRelations: true
4258
+ publishedRelations: true,
4259
+ sortableColumns: sortableColumnsFor(typeName)
4162
4260
  })
4163
4261
  ])
4164
4262
  );
@@ -4167,7 +4265,8 @@ function createCmsApp(options) {
4167
4265
  typeName,
4168
4266
  createDrizzleContentRepository(db, tt.db.table_name, {
4169
4267
  relations: buildRelationsMap(tt.fields, registry),
4170
- publishedRelations: true
4268
+ publishedRelations: true,
4269
+ sortableColumns: sortableColumnsFor(typeName)
4171
4270
  })
4172
4271
  ])
4173
4272
  );
@@ -4175,11 +4274,15 @@ function createCmsApp(options) {
4175
4274
  const graphqlRepos = Object.fromEntries([
4176
4275
  ...Object.entries(registry.content_types).map(([typeName, ct]) => [
4177
4276
  typeName,
4178
- createDrizzleContentRepository(db, ct.db.table_name)
4277
+ createDrizzleContentRepository(db, ct.db.table_name, {
4278
+ sortableColumns: sortableColumnsFor(typeName)
4279
+ })
4179
4280
  ]),
4180
4281
  ...Object.entries(registry.taxonomy_types).map(([typeName, tt]) => [
4181
4282
  typeName,
4182
- createDrizzleContentRepository(db, tt.db.table_name)
4283
+ createDrizzleContentRepository(db, tt.db.table_name, {
4284
+ sortableColumns: sortableColumnsFor(typeName)
4285
+ })
4183
4286
  ])
4184
4287
  ]);
4185
4288
  registerAuthRoutes(app, db);
@@ -4220,17 +4323,7 @@ function createCmsApp(options) {
4220
4323
  paths: {}
4221
4324
  })
4222
4325
  );
4223
- const fieldKeyMaps = Object.fromEntries([
4224
- ...Object.entries(registry.content_types).map(([typeName, ct]) => [
4225
- typeName,
4226
- createFieldKeyMap(ct.fields)
4227
- ]),
4228
- ...Object.entries(registry.taxonomy_types).map(([typeName, tt]) => [
4229
- typeName,
4230
- createFieldKeyMap(tt.fields)
4231
- ])
4232
- ]);
4233
- registerPublicContentRoutes(app, registry, publicRepos, fieldKeyMaps, publicPaths, listRateLimit, programmaticResolver);
4326
+ registerPublicContentRoutes(app, registry, publicRepos, projectors, publicPaths, listRateLimit, programmaticResolver);
4234
4327
  registerPublicMediaRoutes(app, mediaRepo, publicPaths, listRateLimit);
4235
4328
  if (options.graphql?.enabled) {
4236
4329
  const gqlOptions = options.graphql;
@@ -4277,7 +4370,7 @@ function createCmsApp(options) {
4277
4370
  );
4278
4371
  registerSchemaRoute(app, registry, db);
4279
4372
  registerUserRoutes(app, db, requirePermission, requireHierarchy);
4280
- registerAdminContentRoutes(app, registry, repos, fieldKeyMaps, mediaRepo, requirePermission, db);
4373
+ registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo, requirePermission, db);
4281
4374
  registerAdminMediaRoutes(app, mediaRepo, storage, requirePermission, maxFileSize);
4282
4375
  return { prefix, app };
4283
4376
  }