@bobbykim/manguito-cms-api 0.3.3 → 0.4.1

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.
@@ -144,14 +144,14 @@ var SortOrderEnum = new import_graphql3.GraphQLEnumType({
144
144
  values: { ASC: { value: "asc" }, DESC: { value: "desc" } }
145
145
  });
146
146
  var SORTABLE = [
147
- { gql: "title", column: "title" },
148
- { gql: "createdAt", column: "created_at" },
149
- { gql: "updatedAt", column: "updated_at" }
147
+ { gql: "title", value: "title" },
148
+ { gql: "createdAt", value: "created_at" },
149
+ { gql: "updatedAt", value: "updated_at" }
150
150
  ];
151
151
  function buildSortFieldEnum(typeName) {
152
152
  return new import_graphql3.GraphQLEnumType({
153
153
  name: `${typeName}SortField`,
154
- values: Object.fromEntries(SORTABLE.map((s) => [s.gql, { value: s.column }]))
154
+ values: Object.fromEntries(SORTABLE.map((s) => [s.gql, { value: s.value }]))
155
155
  });
156
156
  }
157
157
  function comparableFilterInput(name, scalar) {
@@ -211,12 +211,13 @@ function buildFilterInputType(type) {
211
211
  if (Object.keys(fields).length === 0) return null;
212
212
  return new import_graphql3.GraphQLInputObjectType({ name: `${graphqlTypeName(type.name)}Filter`, fields });
213
213
  }
214
- function translateFilters(input, nameMap) {
214
+ function translateFilters(input, nameMap, columnFor) {
215
215
  const out = {};
216
216
  if (!input) return out;
217
217
  for (const [gqlField, raw] of Object.entries(input)) {
218
218
  if (raw === null || typeof raw !== "object") continue;
219
- const column = nameMap.toSchema(gqlField);
219
+ const label = nameMap.toSchema(gqlField);
220
+ const column = columnFor?.(label) ?? label;
220
221
  const spec = raw;
221
222
  if (spec["eq"] !== void 0) {
222
223
  out[column] = spec["eq"];
@@ -235,35 +236,51 @@ function translateFilters(input, nameMap) {
235
236
 
236
237
  // src/graphql/resolvers.ts
237
238
  var import_graphql4 = require("graphql");
239
+
240
+ // src/field-keys.ts
241
+ function isColumnBacked(field) {
242
+ const col = field.db_column;
243
+ if (col === null) return false;
244
+ if (col.column_name === "") return false;
245
+ if (col.junction) return false;
246
+ return true;
247
+ }
248
+
249
+ // src/graphql/resolvers.ts
238
250
  function codeError(code, message) {
239
251
  return new import_graphql4.GraphQLError(message, { extensions: { code } });
240
252
  }
241
253
  function isPublished(item) {
242
254
  return !!item && item["published"] === true;
243
255
  }
244
- function scalarFieldResolver(schemaName) {
245
- return (parent) => parent[schemaName];
256
+ function resolveFieldValue(field, row) {
257
+ const key = isColumnBacked(field) ? field.db_column.column_name : field.name;
258
+ return row[key] ?? null;
259
+ }
260
+ function scalarFieldResolver(field) {
261
+ return (parent) => resolveFieldValue(field, parent);
246
262
  }
247
263
  function relationFieldResolver(typeName, schemaFieldName) {
248
264
  return (parent, _args, ctx) => ctx.loaders.load(typeName, schemaFieldName, parent);
249
265
  }
250
- function programmaticFieldResolver(typeName, schemaFieldName, mediaFieldNames = []) {
266
+ function programmaticFieldResolver(typeName, schemaFieldName, mediaFieldNames = [], fieldKeys) {
251
267
  return async (parent, _args, ctx) => {
252
268
  let p = ctx.programmaticMemo.get(parent);
253
269
  if (!p) {
254
- p = resolveProgrammaticRow(typeName, parent, ctx, mediaFieldNames);
270
+ p = resolveProgrammaticRow(typeName, parent, ctx, mediaFieldNames, fieldKeys);
255
271
  ctx.programmaticMemo.set(parent, p);
256
272
  }
257
273
  return (await p)[schemaFieldName];
258
274
  };
259
275
  }
260
- async function resolveProgrammaticRow(typeName, parent, ctx, mediaFieldNames) {
261
- if (mediaFieldNames.length === 0) return ctx.resolver.resolveItem(typeName, parent);
276
+ async function resolveProgrammaticRow(typeName, parent, ctx, mediaFieldNames, fieldKeys) {
277
+ const toLabels = (row) => fieldKeys ? fieldKeys.toLabels(row) : row;
278
+ if (mediaFieldNames.length === 0) return ctx.resolver.resolveItem(typeName, toLabels(parent));
262
279
  const enriched = { ...parent };
263
280
  await Promise.all(mediaFieldNames.map((name) => ctx.loaders.load(typeName, name, enriched)));
264
- return ctx.resolver.resolveItem(typeName, enriched);
281
+ return ctx.resolver.resolveItem(typeName, toLabels(enriched));
265
282
  }
266
- function collectionResolver(typeName, nameMap) {
283
+ function collectionResolver(typeName, nameMap, fieldKeys) {
267
284
  return async (_root, args, ctx) => {
268
285
  const page = args.page ?? 1;
269
286
  const perPage = args.perPage ?? 10;
@@ -274,13 +291,15 @@ function collectionResolver(typeName, nameMap) {
274
291
  throw codeError("INVALID_PAGINATION", "perPage must be between 1 and 100");
275
292
  }
276
293
  const repo = ctx.repos[typeName];
294
+ const sortBy = args.sortBy ?? "created_at";
295
+ const sortColumn = fieldKeys?.columnFor(sortBy) ?? sortBy;
277
296
  const result = await repo.findMany({
278
297
  published_only: true,
279
298
  page,
280
299
  per_page: perPage,
281
- sort_by: args.sortBy ?? "created_at",
300
+ sort_by: sortColumn,
282
301
  sort_order: args.sortOrder ?? "asc",
283
- filters: translateFilters(args.filter, nameMap)
302
+ filters: translateFilters(args.filter, nameMap, fieldKeys?.columnFor)
284
303
  });
285
304
  return { data: result.data, meta: result.meta };
286
305
  };
@@ -333,7 +352,7 @@ var MEDIA = new import_graphql5.GraphQLObjectType({
333
352
  duration: { type: import_graphql5.GraphQLInt, resolve: (m) => m.duration }
334
353
  }
335
354
  });
336
- function buildGraphQLSchema(registry) {
355
+ function buildGraphQLSchema(registry, fieldKeyMaps = {}) {
337
356
  const objectTypes = /* @__PURE__ */ new Map();
338
357
  const enumTypes = /* @__PURE__ */ new Map();
339
358
  for (const [name, enumType] of Object.entries(registry.enum_types)) {
@@ -409,11 +428,16 @@ function buildGraphQLSchema(registry) {
409
428
  const outType = outputTypeForField(field);
410
429
  let resolve;
411
430
  if (field.field_type === "programmatic") {
412
- resolve = programmaticFieldResolver(machineName, field.name, mediaFieldNames);
431
+ resolve = programmaticFieldResolver(
432
+ machineName,
433
+ field.name,
434
+ mediaFieldNames,
435
+ fieldKeyMaps[machineName]
436
+ );
413
437
  } else if (field.field_type === "reference" || field.field_type === "paragraph" || field.field_type === "image" || field.field_type === "video" || field.field_type === "file") {
414
438
  resolve = relationFieldResolver(machineName, field.name);
415
439
  } else {
416
- resolve = scalarFieldResolver(field.name);
440
+ resolve = scalarFieldResolver(field);
417
441
  }
418
442
  fields[gqlName] = { type: outType, resolve };
419
443
  }
@@ -469,7 +493,7 @@ function buildGraphQLSchema(registry) {
469
493
  sortOrder: { type: SortOrderEnum },
470
494
  ...filterType ? { filter: { type: filterType } } : {}
471
495
  },
472
- resolve: collectionResolver(name, nameMap)
496
+ resolve: collectionResolver(name, nameMap, fieldKeyMaps[name])
473
497
  };
474
498
  queryFields[singleQueryName(name)] = {
475
499
  type: objType,
@@ -489,7 +513,7 @@ function buildGraphQLSchema(registry) {
489
513
  queryFields[collectionQueryName(name)] = {
490
514
  type: new import_graphql5.GraphQLNonNull(listType),
491
515
  args: { page: { type: import_graphql5.GraphQLInt }, perPage: { type: import_graphql5.GraphQLInt } },
492
- resolve: collectionResolver(name, buildFieldNameMap(tt.fields.map((f) => f.name)))
516
+ resolve: collectionResolver(name, buildFieldNameMap(tt.fields.map((f) => f.name)), fieldKeyMaps[name])
493
517
  };
494
518
  queryFields[singleQueryName(name)] = {
495
519
  type: objType,
@@ -555,22 +579,34 @@ function groupBy(items, key) {
555
579
  }
556
580
  return result;
557
581
  }
582
+ function needsFkResolution(row, fkColumn, fieldName) {
583
+ if (fkColumn !== fieldName && !(fkColumn in row)) return false;
584
+ const current = row[fieldName];
585
+ return !(typeof current === "object" && current !== null);
586
+ }
558
587
  async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOnly = false) {
559
588
  if (rows.length === 0) return;
589
+ const batch = [...new Set(rows)];
560
590
  if (rel.type === "paragraph") {
561
- const parentIds = rows.map((r) => r["id"]);
591
+ const parentIds = batch.map((r) => r["id"]);
562
592
  const inList = import_drizzle_orm.sql.join(parentIds.map((id) => import_drizzle_orm.sql`${id}`), import_drizzle_orm.sql`, `);
563
593
  const result = await db.execute(
564
594
  import_drizzle_orm.sql`SELECT * FROM ${import_drizzle_orm.sql.raw(quoteIdent(rel.table))} WHERE parent_id IN (${inList}) AND parent_field = ${fieldName} ORDER BY "order" ASC`
565
595
  );
566
596
  const byParent = groupBy(result.rows, "parent_id");
567
- for (const row of rows) {
597
+ for (const row of batch) {
568
598
  row[fieldName] = byParent[row["id"]] ?? [];
569
599
  }
570
600
  } else if (rel.type === "reference") {
571
- const fkValues = rows.map((r) => r[rel.fk_column]).filter(Boolean);
601
+ const dropFk = rel.fk_column !== fieldName;
602
+ const pending = batch.filter((r) => needsFkResolution(r, rel.fk_column, fieldName));
603
+ if (pending.length === 0) return;
604
+ const fkValues = pending.map((r) => r[rel.fk_column]).filter(Boolean);
572
605
  if (fkValues.length === 0) {
573
- for (const row of rows) row[fieldName] = null;
606
+ for (const row of pending) {
607
+ row[fieldName] = null;
608
+ if (dropFk) delete row[rel.fk_column];
609
+ }
574
610
  return;
575
611
  }
576
612
  const unique = [...new Set(fkValues)];
@@ -585,12 +621,13 @@ async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOn
585
621
  cache.set(`${rel.table}:${item["id"]}`, item);
586
622
  }
587
623
  }
588
- for (const row of rows) {
624
+ for (const row of pending) {
589
625
  const fkVal = row[rel.fk_column];
590
626
  row[fieldName] = fkVal ? cache.get(`${rel.table}:${fkVal}`) ?? null : null;
627
+ if (dropFk) delete row[rel.fk_column];
591
628
  }
592
629
  } else if (rel.type === "junction") {
593
- const parentIds = rows.map((r) => r["id"]);
630
+ const parentIds = batch.map((r) => r["id"]);
594
631
  const inList = import_drizzle_orm.sql.join(parentIds.map((id) => import_drizzle_orm.sql`${id}`), import_drizzle_orm.sql`, `);
595
632
  const orderBy = rel.order_column ? import_drizzle_orm.sql` ORDER BY "order" ASC` : import_drizzle_orm.sql``;
596
633
  const junctionResult = await db.execute(
@@ -610,14 +647,20 @@ async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOn
610
647
  }
611
648
  }
612
649
  const byLeft = groupBy(jRows, rel.left_column);
613
- for (const row of rows) {
650
+ for (const row of batch) {
614
651
  const jEntries = byLeft[row["id"]] ?? [];
615
652
  row[fieldName] = jEntries.map((jr) => cache.get(`${rel.table}:${jr[rel.right_column]}`)).filter(Boolean);
616
653
  }
617
654
  } else if (rel.type === "media") {
618
- const fkValues = rows.map((r) => r[rel.fk_column]).filter(Boolean);
655
+ const dropFk = rel.fk_column !== fieldName;
656
+ const pending = batch.filter((r) => needsFkResolution(r, rel.fk_column, fieldName));
657
+ if (pending.length === 0) return;
658
+ const fkValues = pending.map((r) => r[rel.fk_column]).filter(Boolean);
619
659
  if (fkValues.length === 0) {
620
- for (const row of rows) row[fieldName] = null;
660
+ for (const row of pending) {
661
+ row[fieldName] = null;
662
+ if (dropFk) delete row[rel.fk_column];
663
+ }
621
664
  return;
622
665
  }
623
666
  const unique = [...new Set(fkValues)];
@@ -629,9 +672,10 @@ async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOn
629
672
  cache.set(`media:${item["id"]}`, item);
630
673
  }
631
674
  }
632
- for (const row of rows) {
675
+ for (const row of pending) {
633
676
  const fkVal = row[rel.fk_column];
634
677
  row[fieldName] = fkVal ? cache.get(`media:${fkVal}`) ?? null : null;
678
+ if (dropFk) delete row[rel.fk_column];
635
679
  }
636
680
  }
637
681
  }
@@ -710,8 +754,8 @@ function isClientSafeGraphQLError(error) {
710
754
  }
711
755
  return false;
712
756
  }
713
- function createGraphQLHandler(registry, repos, resolver, db, options) {
714
- const schema = buildGraphQLSchema(registry);
757
+ function createGraphQLHandler(registry, repos, fieldKeyMaps, resolver, db, options) {
758
+ const schema = buildGraphQLSchema(registry, fieldKeyMaps);
715
759
  const { plugins } = buildArmorPlugin({
716
760
  maxDepth: options.maxDepth,
717
761
  maxComplexity: options.maxComplexity