@bobbykim/manguito-cms-cli 0.11.3 → 0.11.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.
@@ -9270,7 +9270,7 @@ var require_bcryptjs = __commonJS({
9270
9270
  }
9271
9271
  });
9272
9272
 
9273
- // ../api/dist/chunk-GUZZQZSN.mjs
9273
+ // ../api/dist/chunk-IFE2L4VM.mjs
9274
9274
  init_esm_shims();
9275
9275
  import crypto from "crypto";
9276
9276
 
@@ -26681,7 +26681,7 @@ function describeSchemaChange(input) {
26681
26681
  return { from: from?.version ?? null, to: to.version, types, identical };
26682
26682
  }
26683
26683
 
26684
- // ../api/dist/chunk-GUZZQZSN.mjs
26684
+ // ../api/dist/chunk-IFE2L4VM.mjs
26685
26685
  function isColumnBacked2(field) {
26686
26686
  const col = field.db_column;
26687
26687
  if (col === null) return false;
@@ -26788,10 +26788,131 @@ function createFieldKeyMapFromProjection(projectionType, allFields) {
26788
26788
  droppedLabels
26789
26789
  );
26790
26790
  }
26791
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
26792
+ function isUuid(v) {
26793
+ return typeof v === "string" && UUID_RE.test(v);
26794
+ }
26795
+ function isPlainObject2(v) {
26796
+ return typeof v === "object" && v !== null && !Array.isArray(v);
26797
+ }
26798
+ function checkRelationInput(fields, body, registry2, path4 = "") {
26799
+ const errors = [];
26800
+ for (const field of fields) {
26801
+ if (!Object.hasOwn(body, field.name)) continue;
26802
+ const cardinality = relationCardinality(field);
26803
+ if (cardinality === null) continue;
26804
+ const value = body[field.name];
26805
+ const name = `${path4}${field.name}`;
26806
+ if (field.field_type === "reference") {
26807
+ const ok = cardinality === "one" ? value === null || isUuid(value) : Array.isArray(value) && value.every(isUuid);
26808
+ if (!ok) {
26809
+ errors.push({
26810
+ field: name,
26811
+ message: cardinality === "one" ? `${name} holds one item: send a UUID string or null.` : `${name} holds a list: send an array of UUID strings.`
26812
+ });
26813
+ }
26814
+ continue;
26815
+ }
26816
+ const items = cardinality === "one" ? value === null ? [] : isPlainObject2(value) ? [value] : void 0 : Array.isArray(value) && value.every(isPlainObject2) ? value : void 0;
26817
+ if (items === void 0) {
26818
+ errors.push({
26819
+ field: name,
26820
+ message: cardinality === "one" ? `${name} holds one item: send an object or null.` : `${name} holds a list: send an array of objects.`
26821
+ });
26822
+ continue;
26823
+ }
26824
+ const pType = paragraphTypeOf(field, registry2);
26825
+ if (!pType) continue;
26826
+ items.forEach((item, i) => {
26827
+ const prefix = cardinality === "one" ? `${name}.` : `${name}[${i}].`;
26828
+ errors.push(...checkRelationInput(pType.fields, item, registry2, prefix));
26829
+ });
26830
+ }
26831
+ return errors;
26832
+ }
26833
+ function paragraphTypeOf(field, registry2) {
26834
+ const comp = field.ui_component;
26835
+ return comp.component === "paragraph-embed" ? registry2.paragraph_types[comp.ref] : void 0;
26836
+ }
26837
+ function paragraphItemsIn(fields, body, registry2, path4) {
26838
+ const out = [];
26839
+ for (const field of fields) {
26840
+ if (field.field_type !== "paragraph" || !Object.hasOwn(body, field.name)) continue;
26841
+ const pType = paragraphTypeOf(field, registry2);
26842
+ if (!pType) continue;
26843
+ const value = body[field.name];
26844
+ const name = `${path4}${field.name}`;
26845
+ if (relationCardinality(field) === "one") {
26846
+ if (isPlainObject2(value)) out.push({ item: value, fields: pType.fields, prefix: `${name}.` });
26847
+ } else if (Array.isArray(value)) {
26848
+ value.forEach((item, i) => {
26849
+ if (isPlainObject2(item)) out.push({ item, fields: pType.fields, prefix: `${name}[${i}].` });
26850
+ });
26851
+ }
26852
+ }
26853
+ return out;
26854
+ }
26855
+ function isMissing(value) {
26856
+ if (value === null || value === void 0) return true;
26857
+ if (typeof value === "string" && value.trim() === "") return true;
26858
+ if (Array.isArray(value) && value.length === 0) return true;
26859
+ return false;
26860
+ }
26861
+ function checkRequiredInParagraphItems(fields, body, registry2, path4 = "") {
26862
+ const errors = [];
26863
+ for (const { item, fields: itemFields, prefix } of paragraphItemsIn(fields, body, registry2, path4)) {
26864
+ for (const f of itemFields) {
26865
+ if (!f.required || f.removed === true || !f.db_column || f.db_column.junction) continue;
26866
+ if (isMissing(item[f.name])) {
26867
+ errors.push({ field: `${prefix}${f.name}`, message: `${prefix}${f.name} is required.` });
26868
+ }
26869
+ }
26870
+ errors.push(...checkRequiredInParagraphItems(itemFields, item, registry2, prefix));
26871
+ }
26872
+ return errors;
26873
+ }
26791
26874
  function quoteIdent(name) {
26792
26875
  if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`);
26793
26876
  return `"${name}"`;
26794
26877
  }
26878
+ function collectReferences(fields, body, registry2, path4, out) {
26879
+ for (const f of fields) {
26880
+ if (!Object.hasOwn(body, f.name) || !f.db_column) continue;
26881
+ const table = f.db_column.junction ? f.db_column.junction.right_table : f.db_column.foreign_key?.table;
26882
+ if (!table) continue;
26883
+ const value = body[f.name];
26884
+ const name = `${path4}${f.name}`;
26885
+ if (typeof value === "string") {
26886
+ out.push({ table, id: value, field: name });
26887
+ } else if (Array.isArray(value)) {
26888
+ value.forEach((v, i) => {
26889
+ if (typeof v === "string") out.push({ table, id: v, field: `${name}[${i}]` });
26890
+ });
26891
+ }
26892
+ }
26893
+ for (const { item, fields: itemFields, prefix } of paragraphItemsIn(fields, body, registry2, path4)) {
26894
+ collectReferences(itemFields, item, registry2, prefix, out);
26895
+ }
26896
+ }
26897
+ async function findMissingReferences(db, fields, body, registry2) {
26898
+ const uses = [];
26899
+ collectReferences(fields, body, registry2, "", uses);
26900
+ if (uses.length === 0) return [];
26901
+ const lookups = [...new Set(uses.map((u) => u.table))].map(async (table) => {
26902
+ const ids = [...new Set(uses.filter((u) => u.table === table && isUuid(u.id)).map((u) => u.id.toLowerCase()))];
26903
+ if (ids.length === 0) return [table, /* @__PURE__ */ new Set()];
26904
+ const result = await db.execute(
26905
+ sql`SELECT id FROM ${sql.raw(quoteIdent(table))} WHERE id IN (${sql.join(ids.map((id) => sql`${id}`), sql`, `)})`
26906
+ );
26907
+ return [table, new Set(result.rows.map((r) => String(r.id).toLowerCase()))];
26908
+ });
26909
+ const found = new Map(await Promise.all(lookups));
26910
+ return uses.filter((u) => !found.get(u.table).has(u.id.toLowerCase())).map((u) => ({ field: u.field, message: `${u.field} refers to an item that does not exist.` }));
26911
+ }
26912
+ function quoteIdent2(name) {
26913
+ if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`);
26914
+ return `"${name}"`;
26915
+ }
26795
26916
  function paragraphMediaFieldNames(pType) {
26796
26917
  return new Set(
26797
26918
  pType.fields.filter((f) => f.field_type === "image" || f.field_type === "video" || f.field_type === "file").map((f) => f.name)
@@ -26800,8 +26921,8 @@ function paragraphMediaFieldNames(pType) {
26800
26921
  async function fetchParagraphMediaIds(db, pType, itemId, fieldName) {
26801
26922
  const mediaColumns = pType.fields.filter((f) => f.field_type === "image" || f.field_type === "video" || f.field_type === "file").map((f) => f.db_column.column_name);
26802
26923
  if (mediaColumns.length === 0) return [];
26803
- const tbl = sql.raw(quoteIdent(pType.db.table_name));
26804
- const cols = sql.join(mediaColumns.map((c) => sql.raw(quoteIdent(c))), sql`, `);
26924
+ const tbl = sql.raw(quoteIdent2(pType.db.table_name));
26925
+ const cols = sql.join(mediaColumns.map((c) => sql.raw(quoteIdent2(c))), sql`, `);
26805
26926
  const result = await db.execute(
26806
26927
  sql`SELECT ${cols} FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`
26807
26928
  );
@@ -26819,20 +26940,37 @@ function nestedParagraphFields(pType, registry2) {
26819
26940
  const out = [];
26820
26941
  for (const f of pType.fields) {
26821
26942
  if (f.db_column !== null) continue;
26822
- const comp = f.ui_component;
26823
- if (comp.component !== "paragraph-embed" || !comp.ref) continue;
26824
- const nType = registry2.paragraph_types[comp.ref];
26943
+ const nType = paragraphTypeOf(f, registry2);
26825
26944
  if (nType) out.push({ fieldName: f.name, nType, field: f });
26826
26945
  }
26827
26946
  return out;
26828
26947
  }
26829
26948
  async function fetchParagraphRowIds(db, pType, itemId, fieldName) {
26830
- const tbl = sql.raw(quoteIdent(pType.db.table_name));
26949
+ const tbl = sql.raw(quoteIdent2(pType.db.table_name));
26831
26950
  const result = await db.execute(
26832
26951
  sql`SELECT id FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`
26833
26952
  );
26834
26953
  return result.rows.map((r) => r["id"]).filter((id) => typeof id === "string");
26835
26954
  }
26955
+ async function ownParagraphRowIds(db, registry2, fields, itemId) {
26956
+ const ids = [];
26957
+ for (const f of fields) {
26958
+ const pType = paragraphTypeOf(f, registry2);
26959
+ if (!pType) continue;
26960
+ const rowIds = await fetchParagraphRowIds(db, pType, itemId, f.name);
26961
+ ids.push(...rowIds);
26962
+ if (rowIds.length === 0) continue;
26963
+ for (const n of nestedParagraphFields(pType, registry2)) {
26964
+ const result = await db.execute(
26965
+ sql`SELECT id FROM ${sql.raw(quoteIdent2(n.nType.db.table_name))} WHERE parent_id = ANY(${sql.param(rowIds)}::uuid[]) AND parent_field = ${n.fieldName}`
26966
+ );
26967
+ for (const r of result.rows) {
26968
+ if (typeof r["id"] === "string") ids.push(r["id"]);
26969
+ }
26970
+ }
26971
+ }
26972
+ return ids;
26973
+ }
26836
26974
  async function persistParagraphField(db, itemId, parentType, fieldName, pType, items, registry2) {
26837
26975
  const removed = await fetchParagraphMediaIds(db, pType, itemId, fieldName);
26838
26976
  const nested = nestedParagraphFields(pType, registry2);
@@ -26845,7 +26983,7 @@ async function persistParagraphField(db, itemId, parentType, fieldName, pType, i
26845
26983
  }
26846
26984
  }
26847
26985
  }
26848
- const tbl = sql.raw(quoteIdent(pType.db.table_name));
26986
+ const tbl = sql.raw(quoteIdent2(pType.db.table_name));
26849
26987
  await db.execute(sql`DELETE FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`);
26850
26988
  const mediaFieldNames = paragraphMediaFieldNames(pType);
26851
26989
  const added = [];
@@ -26865,7 +27003,7 @@ async function persistParagraphField(db, itemId, parentType, fieldName, pType, i
26865
27003
  if (!pf.db_column || pf.db_column.junction) continue;
26866
27004
  data[pf.db_column.column_name] = pItem[pf.name] ?? null;
26867
27005
  }
26868
- const cols = sql.join(Object.keys(data).map((k) => sql.raw(quoteIdent(k))), sql`, `);
27006
+ const cols = sql.join(Object.keys(data).map((k) => sql.raw(quoteIdent2(k))), sql`, `);
26869
27007
  const vals = sql.join(Object.values(data).map((v) => sql`${v}`), sql`, `);
26870
27008
  await db.execute(sql`INSERT INTO ${tbl} (${cols}) VALUES (${vals})`);
26871
27009
  for (const name of mediaFieldNames) {
@@ -26901,14 +27039,14 @@ async function deleteParagraphField(db, itemId, fieldName, pType, registry2) {
26901
27039
  }
26902
27040
  }
26903
27041
  }
26904
- const tbl = sql.raw(quoteIdent(pType.db.table_name));
27042
+ const tbl = sql.raw(quoteIdent2(pType.db.table_name));
26905
27043
  await db.execute(sql`DELETE FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`);
26906
27044
  return { added: [], removed };
26907
27045
  }
26908
27046
  async function persistJunctionField(db, itemId, junction, relatedIds) {
26909
- const tbl = sql.raw(quoteIdent(junction.table_name));
26910
- const left = sql.raw(quoteIdent(junction.left_column));
26911
- const right = sql.raw(quoteIdent(junction.right_column));
27047
+ const tbl = sql.raw(quoteIdent2(junction.table_name));
27048
+ const left = sql.raw(quoteIdent2(junction.left_column));
27049
+ const right = sql.raw(quoteIdent2(junction.right_column));
26912
27050
  const seen = /* @__PURE__ */ new Set();
26913
27051
  const ids = relatedIds.filter((id) => {
26914
27052
  const key = id.toLowerCase();
@@ -26929,9 +27067,18 @@ async function persistJunctionField(db, itemId, junction, relatedIds) {
26929
27067
  );
26930
27068
  }
26931
27069
  function junctionOrderBy(junction) {
26932
- const right = sql.raw(quoteIdent(junction.right_column));
27070
+ const right = sql.raw(quoteIdent2(junction.right_column));
26933
27071
  return junction.order_column ? sql` ORDER BY "order" ASC, ${right} ASC` : sql` ORDER BY ${right} ASC`;
26934
27072
  }
27073
+ function firstOfEach(items, key) {
27074
+ const seen = /* @__PURE__ */ new Set();
27075
+ return items.filter((item) => {
27076
+ const k = key(item);
27077
+ if (seen.has(k)) return false;
27078
+ seen.add(k);
27079
+ return true;
27080
+ });
27081
+ }
26935
27082
  function oneOrMany(cardinality, list) {
26936
27083
  return cardinality === "one" ? list[0] ?? null : list;
26937
27084
  }
@@ -26999,7 +27146,7 @@ async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOn
26999
27146
  const parentIds = batch.map((r) => r["id"]);
27000
27147
  const inList = sql.join(parentIds.map((id) => sql`${id}`), sql`, `);
27001
27148
  const result = await db.execute(
27002
- sql`SELECT * FROM ${sql.raw(quoteIdent(rel.table))} WHERE parent_id IN (${inList}) AND parent_field = ${fieldName} ORDER BY "order" ASC`
27149
+ sql`SELECT * FROM ${sql.raw(quoteIdent2(rel.table))} WHERE parent_id IN (${inList}) AND parent_field = ${fieldName} ORDER BY "order" ASC, id ASC`
27003
27150
  );
27004
27151
  const byParent = groupBy(result.rows, "parent_id");
27005
27152
  for (const row of batch) {
@@ -27019,7 +27166,7 @@ async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOn
27019
27166
  const inList = sql.join(uncached.map((id) => sql`${id}`), sql`, `);
27020
27167
  const publishedCond = publishedOnly ? sql` AND published = true` : sql``;
27021
27168
  const result = await db.execute(
27022
- sql`SELECT * FROM ${sql.raw(quoteIdent(rel.table))} WHERE id IN (${inList})${publishedCond}`
27169
+ sql`SELECT * FROM ${sql.raw(quoteIdent2(rel.table))} WHERE id IN (${inList})${publishedCond}`
27023
27170
  );
27024
27171
  for (const item of result.rows) {
27025
27172
  cache.set(`${rel.table}:${item["id"]}`, item);
@@ -27033,7 +27180,7 @@ async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOn
27033
27180
  const parentIds = batch.map((r) => r["id"]);
27034
27181
  const inList = sql.join(parentIds.map((id) => sql`${id}`), sql`, `);
27035
27182
  const junctionResult = await db.execute(
27036
- sql`SELECT * FROM ${sql.raw(quoteIdent(rel.junction_table))} WHERE ${sql.raw(quoteIdent(rel.left_column))} IN (${inList})${junctionOrderBy(rel)}`
27183
+ sql`SELECT * FROM ${sql.raw(quoteIdent2(rel.junction_table))} WHERE ${sql.raw(quoteIdent2(rel.left_column))} IN (${inList})${junctionOrderBy(rel)}`
27037
27184
  );
27038
27185
  const jRows = junctionResult.rows;
27039
27186
  const rightIds = [...new Set(jRows.map((r) => r[rel.right_column]))];
@@ -27042,7 +27189,7 @@ async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOn
27042
27189
  const rightInList = sql.join(uncached.map((id) => sql`${id}`), sql`, `);
27043
27190
  const publishedCond = publishedOnly ? sql` AND published = true` : sql``;
27044
27191
  const entitiesResult = await db.execute(
27045
- sql`SELECT * FROM ${sql.raw(quoteIdent(rel.table))} WHERE id IN (${rightInList})${publishedCond}`
27192
+ sql`SELECT * FROM ${sql.raw(quoteIdent2(rel.table))} WHERE id IN (${rightInList})${publishedCond}`
27046
27193
  );
27047
27194
  for (const item of entitiesResult.rows) {
27048
27195
  cache.set(`${rel.table}:${item["id"]}`, item);
@@ -27050,7 +27197,7 @@ async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOn
27050
27197
  }
27051
27198
  const byLeft = groupBy(jRows, rel.left_column);
27052
27199
  for (const row of batch) {
27053
- const jEntries = byLeft[row["id"]] ?? [];
27200
+ const jEntries = firstOfEach(byLeft[row["id"]] ?? [], (jr) => jr[rel.right_column]);
27054
27201
  row[fieldName] = jEntries.map((jr) => cache.get(`${rel.table}:${jr[rel.right_column]}`)).filter(Boolean);
27055
27202
  }
27056
27203
  } else if (rel.type === "media") {
@@ -27082,7 +27229,7 @@ async function resolveRelationBareIds(db, rows, fieldName, rel) {
27082
27229
  const parentIds = rows.map((r) => r["id"]);
27083
27230
  const inList = sql.join(parentIds.map((id) => sql`${id}`), sql`, `);
27084
27231
  const result = await db.execute(
27085
- sql`SELECT id, parent_id FROM ${sql.raw(quoteIdent(rel.table))} WHERE parent_id IN (${inList}) AND parent_field = ${fieldName} ORDER BY "order" ASC`
27232
+ sql`SELECT id, parent_id FROM ${sql.raw(quoteIdent2(rel.table))} WHERE parent_id IN (${inList}) AND parent_field = ${fieldName} ORDER BY "order" ASC, id ASC`
27086
27233
  );
27087
27234
  const byParent = groupBy(result.rows, "parent_id");
27088
27235
  for (const row of rows) {
@@ -27092,11 +27239,11 @@ async function resolveRelationBareIds(db, rows, fieldName, rel) {
27092
27239
  const parentIds = rows.map((r) => r["id"]);
27093
27240
  const inList = sql.join(parentIds.map((id) => sql`${id}`), sql`, `);
27094
27241
  const result = await db.execute(
27095
- sql`SELECT ${sql.raw(quoteIdent(rel.left_column))} AS left_id, ${sql.raw(quoteIdent(rel.right_column))} AS right_id FROM ${sql.raw(quoteIdent(rel.junction_table))} WHERE ${sql.raw(quoteIdent(rel.left_column))} IN (${inList})${junctionOrderBy(rel)}`
27242
+ sql`SELECT ${sql.raw(quoteIdent2(rel.left_column))} AS left_id, ${sql.raw(quoteIdent2(rel.right_column))} AS right_id FROM ${sql.raw(quoteIdent2(rel.junction_table))} WHERE ${sql.raw(quoteIdent2(rel.left_column))} IN (${inList})${junctionOrderBy(rel)}`
27096
27243
  );
27097
27244
  const byLeft = groupBy(result.rows, "left_id");
27098
27245
  for (const row of rows) {
27099
- row[fieldName] = (byLeft[row["id"]] ?? []).map((r) => r["right_id"]);
27246
+ row[fieldName] = firstOfEach(byLeft[row["id"]] ?? [], (r) => r["right_id"]).map((r) => r["right_id"]);
27100
27247
  }
27101
27248
  }
27102
27249
  }
@@ -27149,6 +27296,14 @@ export {
27149
27296
  isColumnBacked2 as isColumnBacked,
27150
27297
  createFieldKeyMap,
27151
27298
  createFieldKeyMapFromProjection,
27299
+ isPlainObject2 as isPlainObject,
27300
+ checkRelationInput,
27301
+ paragraphTypeOf,
27302
+ paragraphItemsIn,
27303
+ isMissing,
27304
+ checkRequiredInParagraphItems,
27305
+ findMissingReferences,
27306
+ ownParagraphRowIds,
27152
27307
  persistParagraphField,
27153
27308
  deleteParagraphField,
27154
27309
  persistJunctionField,
@@ -12,7 +12,7 @@ import {
12
12
  isColumnBacked,
13
13
  relationCardinality,
14
14
  resolveRelationField
15
- } from "./chunk-N75KN2YC.js";
15
+ } from "./chunk-44VXH5BN.js";
16
16
 
17
17
  // ../../node_modules/.pnpm/@whatwg-node+fetch@0.10.13/node_modules/@whatwg-node/fetch/dist/shouldSkipPonyfill.js
18
18
  var require_shouldSkipPonyfill = __commonJS({
@@ -10787,10 +10787,10 @@ var require_parser = __commonJS({
10787
10787
  }
10788
10788
  });
10789
10789
 
10790
- // ../api/dist/handler-MFF32JB4.mjs
10790
+ // ../api/dist/handler-N5IMQJBP.mjs
10791
10791
  init_esm_shims();
10792
10792
 
10793
- // ../api/dist/chunk-ITOSMGDT.mjs
10793
+ // ../api/dist/chunk-IMDC3NGM.mjs
10794
10794
  init_esm_shims();
10795
10795
 
10796
10796
  // ../../node_modules/.pnpm/graphql-yoga@5.21.2_graphql@16.14.2/node_modules/graphql-yoga/esm/index.js
@@ -30123,7 +30123,7 @@ function createYoga(options) {
30123
30123
  });
30124
30124
  }
30125
30125
 
30126
- // ../api/dist/chunk-ITOSMGDT.mjs
30126
+ // ../api/dist/chunk-IMDC3NGM.mjs
30127
30127
  var import_dataloader = __toESM(require_dataloader(), 1);
30128
30128
 
30129
30129
  // ../../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
@@ -30881,7 +30881,7 @@ var EnvelopArmor = class {
30881
30881
  }
30882
30882
  };
30883
30883
 
30884
- // ../api/dist/chunk-ITOSMGDT.mjs
30884
+ // ../api/dist/chunk-IMDC3NGM.mjs
30885
30885
  var DateTimeScalar = new GraphQLScalarType({
30886
30886
  name: "DateTime",
30887
30887
  description: "ISO-8601 date-time string",
package/dist/index.js CHANGED
@@ -21,6 +21,8 @@ import {
21
21
  __toESM,
22
22
  buildRelationsMap,
23
23
  buildSchemaRegistry,
24
+ checkRelationInput,
25
+ checkRequiredInParagraphItems,
24
26
  computeVersionModel,
25
27
  createFieldKeyMap,
26
28
  createFieldKeyMapFromProjection,
@@ -28,6 +30,7 @@ import {
28
30
  describeSchemaChange,
29
31
  entityKind,
30
32
  fillPlaceholders,
33
+ findMissingReferences,
31
34
  findSchemaDeprecations,
32
35
  getTableName,
33
36
  getTableUniqueName,
@@ -36,12 +39,17 @@ import {
36
39
  is,
37
40
  isColumnBacked,
38
41
  isDriverValueEncoder,
42
+ isMissing,
43
+ isPlainObject,
39
44
  isSQLWrapper,
40
45
  junctionOrderBy,
41
46
  loadSchemaFile,
42
47
  loadVersionSnapshots,
43
48
  oneOrMany,
49
+ ownParagraphRowIds,
44
50
  paragraphItems,
51
+ paragraphItemsIn,
52
+ paragraphTypeOf,
45
53
  parseRoles,
46
54
  parseRoutes,
47
55
  parseSchema,
@@ -55,7 +63,7 @@ import {
55
63
  validateCrossReferences,
56
64
  verifyPassword,
57
65
  walkSchemaDirectory
58
- } from "./chunk-N75KN2YC.js";
66
+ } from "./chunk-44VXH5BN.js";
59
67
 
60
68
  // ../../node_modules/.pnpm/postgres-array@2.0.0/node_modules/postgres-array/index.js
61
69
  var require_postgres_array = __commonJS({
@@ -21901,7 +21909,7 @@ var ERROR_STATUS_MAP = {
21901
21909
  var errorHandler2 = (err, c) => {
21902
21910
  console.error(err.stack ?? err.message);
21903
21911
  const raw2 = err.code;
21904
- const deliberate = raw2 !== void 0 && raw2 !== "INTERNAL_ERROR" && raw2 in ERROR_STATUS_MAP;
21912
+ const deliberate = raw2 !== void 0 && raw2 !== "INTERNAL_ERROR" && Object.hasOwn(ERROR_STATUS_MAP, raw2);
21905
21913
  if (!deliberate) {
21906
21914
  return c.json({ ok: false, error: { code: "INTERNAL_ERROR", message: "Internal server error" } }, 500);
21907
21915
  }
@@ -22464,16 +22472,16 @@ function parseFilters(url, validFields, columnFor) {
22464
22472
  }
22465
22473
  return { ok: true, filters };
22466
22474
  }
22475
+ function pickProgrammatic(resolved, names) {
22476
+ const out = {};
22477
+ for (const name of names) if (Object.hasOwn(resolved, name)) out[name] = resolved[name];
22478
+ return out;
22479
+ }
22467
22480
  function isPublished(item) {
22468
22481
  return item["published"] === true;
22469
22482
  }
22470
22483
  function registerPublicContentRoutes(app, registry, repos, projectors, paths, listRateLimit, resolver, resolverProjectors) {
22471
22484
  const forResolver = resolverProjectors ?? projectors;
22472
- function pickProgrammatic(resolved, names) {
22473
- const out = {};
22474
- for (const name of names) if (name in resolved) out[name] = resolved[name];
22475
- return out;
22476
- }
22477
22485
  async function respondItem(row, typeName, programmatic) {
22478
22486
  const data = projectRow(row, typeName, projectors);
22479
22487
  if (!resolver?.hasSchema(typeName)) return data;
@@ -22785,25 +22793,42 @@ function quoteIdent(name) {
22785
22793
  function isRequiredSingleReferenceTo(f, targetType) {
22786
22794
  return f.field_type === "reference" && f.required && relationCardinality(f) === "one" && f.db_column?.foreign_key !== void 0 && f.ui_component.component === "typeahead-select" && f.ui_component.ref === targetType;
22787
22795
  }
22796
+ function requiredReferencesTo(owners, targetType) {
22797
+ return owners.flatMap(
22798
+ (owner) => owner.fields.filter((f) => isRequiredSingleReferenceTo(f, targetType)).map((f) => ({ owner, f }))
22799
+ );
22800
+ }
22788
22801
  async function findRequiredReferrers(db, registry, targetType, id) {
22789
- const owners = [
22790
- ...Object.values(registry.content_types),
22791
- ...Object.values(registry.taxonomy_types),
22792
- ...Object.values(registry.paragraph_types)
22793
- ];
22794
- const out = [];
22795
- for (const owner of owners) {
22796
- for (const f of owner.fields) {
22797
- if (!isRequiredSingleReferenceTo(f, targetType)) continue;
22798
- const notSelf = owner.name === targetType ? sql` AND id <> ${id}` : sql``;
22799
- const r = await db.execute(
22800
- sql`SELECT count(*)::int AS n FROM ${sql.raw(quoteIdent(owner.db.table_name))} WHERE ${sql.raw(quoteIdent(f.db_column.column_name))} = ${id}${notSelf}`
22801
- );
22802
- const n = r.rows[0].n;
22803
- if (n > 0) out.push({ type_label: owner.label, field_label: f.label, count: n });
22804
- }
22802
+ const targetFields = registry.content_types[targetType]?.fields ?? registry.taxonomy_types[targetType]?.fields ?? [];
22803
+ const itemOwners = [...Object.values(registry.content_types), ...Object.values(registry.taxonomy_types)];
22804
+ const itemFields = requiredReferencesTo(itemOwners, targetType);
22805
+ const paragraphFields = requiredReferencesTo(Object.values(registry.paragraph_types), targetType);
22806
+ const ownIds = paragraphFields.length > 0 ? await ownParagraphRowIds(db, registry, targetFields, id) : [];
22807
+ const [itemCounts, paragraphCounts] = await Promise.all([
22808
+ Promise.all(
22809
+ itemFields.map(async ({ owner, f }) => {
22810
+ const notSelf = owner.name === targetType ? sql` AND id <> ${id}` : sql``;
22811
+ const r = await db.execute(
22812
+ sql`SELECT count(*)::int AS n FROM ${sql.raw(quoteIdent(owner.db.table_name))} WHERE ${sql.raw(quoteIdent(f.db_column.column_name))} = ${id}${notSelf}`
22813
+ );
22814
+ return { owner, f, n: r.rows[0].n, own: 0 };
22815
+ })
22816
+ ),
22817
+ Promise.all(
22818
+ paragraphFields.map(async ({ owner, f }) => {
22819
+ const r = await db.execute(
22820
+ sql`SELECT count(*)::int AS n, count(*) FILTER (WHERE id = ANY(${sql.param(ownIds)}::uuid[]))::int AS own FROM ${sql.raw(quoteIdent(owner.db.table_name))} WHERE ${sql.raw(quoteIdent(f.db_column.column_name))} = ${id}`
22821
+ );
22822
+ const { n, own } = r.rows[0];
22823
+ return { owner, f, n, own };
22824
+ })
22825
+ )
22826
+ ]);
22827
+ const others = [];
22828
+ for (const { owner, f, n, own } of [...itemCounts, ...paragraphCounts]) {
22829
+ if (n - own > 0) others.push({ type_label: owner.label, field_label: f.label, count: n - own });
22805
22830
  }
22806
- return out;
22831
+ return { others, fromOwnParagraphs: paragraphCounts.some((c) => c.own > 0) };
22807
22832
  }
22808
22833
  function inUseMessage(referrers) {
22809
22834
  const total = referrers.reduce((sum, r) => sum + r.count, 0);
@@ -22828,7 +22853,7 @@ function topLevelMediaDelta(mediaFields, before, after) {
22828
22853
  for (const f of mediaFields) {
22829
22854
  const key = f.db_column?.column_name;
22830
22855
  if (key === void 0 || key === "") continue;
22831
- if (after !== null && !(key in after)) continue;
22856
+ if (after !== null && !Object.hasOwn(after, key)) continue;
22832
22857
  const oldId = before ? extractMediaId(before[key]) : null;
22833
22858
  const newId = after ? extractMediaId(after[key]) : null;
22834
22859
  if (newId === oldId) continue;
@@ -22851,127 +22876,6 @@ async function applyMediaReferenceDelta(delta, mediaRepo) {
22851
22876
  if (netAdd.length > 0) await mediaRepo.incrementReferenceCount(netAdd);
22852
22877
  if (netRemove.length > 0) await mediaRepo.decrementReferenceCount(netRemove);
22853
22878
  }
22854
- var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
22855
- function isUuid(v2) {
22856
- return typeof v2 === "string" && UUID_RE.test(v2);
22857
- }
22858
- function isPlainObject(v2) {
22859
- return typeof v2 === "object" && v2 !== null && !Array.isArray(v2);
22860
- }
22861
- function checkRelationInput(fields, body, registry, path5 = "") {
22862
- const errors = [];
22863
- for (const field of fields) {
22864
- if (!(field.name in body)) continue;
22865
- const cardinality = relationCardinality(field);
22866
- if (cardinality === null) continue;
22867
- const value = body[field.name];
22868
- const name = `${path5}${field.name}`;
22869
- if (field.field_type === "reference") {
22870
- const ok = cardinality === "one" ? value === null || isUuid(value) : Array.isArray(value) && value.every(isUuid);
22871
- if (!ok) {
22872
- errors.push({
22873
- field: name,
22874
- message: cardinality === "one" ? `${name} holds one item: send a UUID string or null.` : `${name} holds a list: send an array of UUID strings.`
22875
- });
22876
- }
22877
- continue;
22878
- }
22879
- const items = cardinality === "one" ? value === null ? [] : isPlainObject(value) ? [value] : void 0 : Array.isArray(value) && value.every(isPlainObject) ? value : void 0;
22880
- if (items === void 0) {
22881
- errors.push({
22882
- field: name,
22883
- message: cardinality === "one" ? `${name} holds one item: send an object or null.` : `${name} holds a list: send an array of objects.`
22884
- });
22885
- continue;
22886
- }
22887
- const pType = paragraphTypeOf(field, registry);
22888
- if (!pType) continue;
22889
- items.forEach((item, i) => {
22890
- const prefix = cardinality === "one" ? `${name}.` : `${name}[${i}].`;
22891
- errors.push(...checkRelationInput(pType.fields, item, registry, prefix));
22892
- });
22893
- }
22894
- return errors;
22895
- }
22896
- function paragraphTypeOf(field, registry) {
22897
- const comp = field.ui_component;
22898
- return comp.component === "paragraph-embed" ? registry.paragraph_types[comp.ref] : void 0;
22899
- }
22900
- function paragraphItemsIn(fields, body, registry, path5) {
22901
- const out = [];
22902
- for (const field of fields) {
22903
- if (field.field_type !== "paragraph" || !(field.name in body)) continue;
22904
- const pType = paragraphTypeOf(field, registry);
22905
- if (!pType) continue;
22906
- const value = body[field.name];
22907
- const name = `${path5}${field.name}`;
22908
- if (relationCardinality(field) === "one") {
22909
- if (isPlainObject(value)) out.push({ item: value, fields: pType.fields, prefix: `${name}.` });
22910
- } else if (Array.isArray(value)) {
22911
- value.forEach((item, i) => {
22912
- if (isPlainObject(item)) out.push({ item, fields: pType.fields, prefix: `${name}[${i}].` });
22913
- });
22914
- }
22915
- }
22916
- return out;
22917
- }
22918
- function isMissing(value) {
22919
- if (value === null || value === void 0) return true;
22920
- if (typeof value === "string" && value.trim() === "") return true;
22921
- if (Array.isArray(value) && value.length === 0) return true;
22922
- return false;
22923
- }
22924
- function checkRequiredInParagraphItems(fields, body, registry, path5 = "") {
22925
- const errors = [];
22926
- for (const { item, fields: itemFields, prefix } of paragraphItemsIn(fields, body, registry, path5)) {
22927
- for (const f of itemFields) {
22928
- if (!f.required || f.removed === true || !f.db_column || f.db_column.junction) continue;
22929
- if (isMissing(item[f.name])) {
22930
- errors.push({ field: `${prefix}${f.name}`, message: `${prefix}${f.name} is required.` });
22931
- }
22932
- }
22933
- errors.push(...checkRequiredInParagraphItems(itemFields, item, registry, prefix));
22934
- }
22935
- return errors;
22936
- }
22937
- function quoteIdent2(name) {
22938
- if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`);
22939
- return `"${name}"`;
22940
- }
22941
- function collectReferences(fields, body, registry, path5, out) {
22942
- for (const f of fields) {
22943
- if (!(f.name in body) || !f.db_column) continue;
22944
- const table = f.db_column.junction ? f.db_column.junction.right_table : f.db_column.foreign_key?.table;
22945
- if (!table) continue;
22946
- const value = body[f.name];
22947
- const name = `${path5}${f.name}`;
22948
- if (typeof value === "string") {
22949
- out.push({ table, id: value, field: name });
22950
- } else if (Array.isArray(value)) {
22951
- value.forEach((v2, i) => {
22952
- if (typeof v2 === "string") out.push({ table, id: v2, field: `${name}[${i}]` });
22953
- });
22954
- }
22955
- }
22956
- for (const { item, fields: itemFields, prefix } of paragraphItemsIn(fields, body, registry, path5)) {
22957
- collectReferences(itemFields, item, registry, prefix, out);
22958
- }
22959
- }
22960
- async function findMissingReferences(db, fields, body, registry) {
22961
- const uses = [];
22962
- collectReferences(fields, body, registry, "", uses);
22963
- if (uses.length === 0) return [];
22964
- const lookups = [...new Set(uses.map((u) => u.table))].map(async (table) => {
22965
- const ids = [...new Set(uses.filter((u) => u.table === table && isUuid(u.id)).map((u) => u.id.toLowerCase()))];
22966
- if (ids.length === 0) return [table, /* @__PURE__ */ new Set()];
22967
- const result = await db.execute(
22968
- sql`SELECT id FROM ${sql.raw(quoteIdent2(table))} WHERE id IN (${sql.join(ids.map((id) => sql`${id}`), sql`, `)})`
22969
- );
22970
- return [table, new Set(result.rows.map((r) => String(r.id).toLowerCase()))];
22971
- });
22972
- const found = new Map(await Promise.all(lookups));
22973
- return uses.filter((u) => !found.get(u.table).has(u.id.toLowerCase())).map((u) => ({ field: u.field, message: `${u.field} refers to an item that does not exist.` }));
22974
- }
22975
22879
  var VARCHAR_DEFAULT_LIMIT = 255;
22976
22880
  var INTEGER_MIN = -2147483648;
22977
22881
  var INTEGER_MAX = 2147483647;
@@ -22982,7 +22886,7 @@ var EMPTY_MEANS_NULL = /* @__PURE__ */ new Set(["date", "integer", "float", "enu
22982
22886
  function emptyValuesToNull(fields, body, registry) {
22983
22887
  const out = { ...body };
22984
22888
  for (const f of fields) {
22985
- if (!(f.name in out)) continue;
22889
+ if (!Object.hasOwn(out, f.name)) continue;
22986
22890
  const value = out[f.name];
22987
22891
  if (value === "" && EMPTY_MEANS_NULL.has(f.field_type)) {
22988
22892
  out[f.name] = null;
@@ -23087,7 +22991,7 @@ function rangeError(name, value, min, max) {
23087
22991
  function checkFieldValues(fields, body, registry, path5 = "") {
23088
22992
  const errors = [];
23089
22993
  for (const f of fields) {
23090
- if (!(f.name in body) || f.removed === true) continue;
22994
+ if (!Object.hasOwn(body, f.name) || f.removed === true) continue;
23091
22995
  const name = `${path5}${f.name}`;
23092
22996
  const message = valueError(f, body[f.name], name);
23093
22997
  if (message) errors.push({ field: name, message });
@@ -23097,20 +23001,18 @@ function checkFieldValues(fields, body, registry, path5 = "") {
23097
23001
  }
23098
23002
  return errors;
23099
23003
  }
23100
- function quoteIdent3(name) {
23004
+ function quoteIdent2(name) {
23101
23005
  if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`);
23102
23006
  return `"${name}"`;
23103
23007
  }
23104
23008
  async function loadParagraphRows(db, registry, pType, parentId, fieldName) {
23105
23009
  const result = await db.execute(
23106
- sql`SELECT * FROM ${sql.raw(quoteIdent3(pType.db.table_name))} WHERE parent_id = ${parentId} AND parent_field = ${fieldName} ORDER BY "order" ASC`
23010
+ sql`SELECT * FROM ${sql.raw(quoteIdent2(pType.db.table_name))} WHERE parent_id = ${parentId} AND parent_field = ${fieldName} ORDER BY "order" ASC, id ASC`
23107
23011
  );
23108
23012
  const rows = result.rows;
23109
23013
  for (const nf of pType.fields) {
23110
23014
  if (nf.db_column !== null) continue;
23111
- const comp = nf.ui_component;
23112
- if (comp.component !== "paragraph-embed" || !comp.ref) continue;
23113
- const nType = registry.paragraph_types[comp.ref];
23015
+ const nType = paragraphTypeOf(nf, registry);
23114
23016
  if (!nType) continue;
23115
23017
  for (const row of rows) {
23116
23018
  row[nf.name] = oneOrMany(
@@ -23122,35 +23024,36 @@ async function loadParagraphRows(db, registry, pType, parentId, fieldName) {
23122
23024
  return rows;
23123
23025
  }
23124
23026
  async function loadRelationsForEdit(db, registry, fields, id, row) {
23125
- for (const f of fields) {
23126
- if (f.db_column === null) {
23127
- const comp = f.ui_component;
23128
- if (comp.component !== "paragraph-embed" || !comp.ref) continue;
23129
- const pType = registry.paragraph_types[comp.ref];
23130
- if (!pType) continue;
23131
- row[f.name] = oneOrMany(relationCardinality(f) ?? "many", await loadParagraphRows(db, registry, pType, id, f.name));
23132
- } else if (f.db_column.junction) {
23133
- const j = f.db_column.junction;
23134
- const r = await db.execute(
23135
- sql`SELECT ${sql.raw(quoteIdent3(j.right_column))} FROM ${sql.raw(quoteIdent3(j.table_name))} WHERE ${sql.raw(quoteIdent3(j.left_column))} = ${id}${junctionOrderBy(j)}`
23136
- );
23137
- row[f.name] = r.rows.map((linked) => linked[j.right_column]);
23138
- }
23139
- }
23027
+ const loaded = await Promise.all(
23028
+ fields.map(async (f) => {
23029
+ if (f.db_column === null) {
23030
+ const pType = paragraphTypeOf(f, registry);
23031
+ if (!pType) return null;
23032
+ return [f.name, oneOrMany(relationCardinality(f) ?? "many", await loadParagraphRows(db, registry, pType, id, f.name))];
23033
+ }
23034
+ if (f.db_column.junction) {
23035
+ const j = f.db_column.junction;
23036
+ const r = await db.execute(
23037
+ sql`SELECT ${sql.raw(quoteIdent2(j.right_column))} FROM ${sql.raw(quoteIdent2(j.table_name))} WHERE ${sql.raw(quoteIdent2(j.left_column))} = ${id}${junctionOrderBy(j)}`
23038
+ );
23039
+ return [f.name, [...new Set(r.rows.map((linked) => linked[j.right_column]))]];
23040
+ }
23041
+ return null;
23042
+ })
23043
+ );
23044
+ for (const entry of loaded) if (entry) row[entry[0]] = entry[1];
23140
23045
  }
23141
23046
  async function valuesForPublishCheck(db, registry, requiredFields, id, storedLabels, body) {
23142
23047
  const stored = { ...storedLabels };
23143
- if (db) await loadRelationsForEdit(db, registry, requiredFields.filter((f) => !(f.name in body)), id, stored);
23048
+ if (db) await loadRelationsForEdit(db, registry, requiredFields.filter((f) => !Object.hasOwn(body, f.name)), id, stored);
23144
23049
  return { ...stored, ...body };
23145
23050
  }
23146
23051
  async function persistRelationFields(db, registry, fields, body, itemId, ownerTable) {
23147
23052
  const deltas = [];
23148
23053
  for (const f of fields) {
23149
- if (!(f.name in body)) continue;
23054
+ if (!Object.hasOwn(body, f.name)) continue;
23150
23055
  if (f.db_column === null) {
23151
- const comp = f.ui_component;
23152
- if (comp.component !== "paragraph-embed" || !comp.ref) continue;
23153
- const pType = registry.paragraph_types[comp.ref];
23056
+ const pType = paragraphTypeOf(f, registry);
23154
23057
  if (!pType) continue;
23155
23058
  deltas.push(await persistParagraphField(db, itemId, ownerTable, f.name, pType, paragraphItems(f, body[f.name]), registry));
23156
23059
  } else if (f.db_column.junction) {
@@ -23161,6 +23064,46 @@ async function persistRelationFields(db, registry, fields, body, itemId, ownerTa
23161
23064
  }
23162
23065
  return deltas;
23163
23066
  }
23067
+ async function deleteOwnParagraphs(db, registry, fields, itemId) {
23068
+ const deltas = [];
23069
+ for (const f of fields) {
23070
+ const pType = paragraphTypeOf(f, registry);
23071
+ if (!pType) continue;
23072
+ deltas.push(await deleteParagraphField(db, itemId, f.name, pType, registry));
23073
+ }
23074
+ return deltas;
23075
+ }
23076
+ function releasedFromParagraphsOnly(paragraphDeltas, topLevel) {
23077
+ const stillHeld = new Set(topLevel.removed);
23078
+ return { added: [], removed: mergeMediaDeltas(...paragraphDeltas).removed.filter((mediaId) => !stillHeld.has(mediaId)) };
23079
+ }
23080
+ async function deleteItemCascade(db, registry, mediaRepo, target) {
23081
+ const { typeName, fields, mediaFields, repo, id, row } = target;
23082
+ const referrers = db ? await findRequiredReferrers(db, registry, typeName, id) : { others: [], fromOwnParagraphs: false };
23083
+ if (referrers.others.length > 0) return { ok: false, message: inUseMessage(referrers.others) };
23084
+ const topLevel = topLevelMediaDelta(mediaFields, row, null);
23085
+ if (!db || !referrers.fromOwnParagraphs) {
23086
+ try {
23087
+ await repo.delete(id);
23088
+ } catch (err) {
23089
+ if (isForeignKeyViolation(err)) return { ok: false, message: IN_USE_RACE_MESSAGE };
23090
+ throw err;
23091
+ }
23092
+ const paragraphDeltas2 = db ? await deleteOwnParagraphs(db, registry, fields, id) : [];
23093
+ await applyMediaReferenceDelta(mergeMediaDeltas(topLevel, ...paragraphDeltas2), mediaRepo);
23094
+ return { ok: true };
23095
+ }
23096
+ const paragraphDeltas = await deleteOwnParagraphs(db, registry, fields, id);
23097
+ try {
23098
+ await repo.delete(id);
23099
+ } catch (err) {
23100
+ await applyMediaReferenceDelta(releasedFromParagraphsOnly(paragraphDeltas, topLevel), mediaRepo);
23101
+ if (isForeignKeyViolation(err)) return { ok: false, message: IN_USE_RACE_MESSAGE };
23102
+ throw err;
23103
+ }
23104
+ await applyMediaReferenceDelta(mergeMediaDeltas(topLevel, ...paragraphDeltas), mediaRepo);
23105
+ return { ok: true };
23106
+ }
23164
23107
  async function lookupBasePathId(db, pathOrName) {
23165
23108
  const r = await db.execute(
23166
23109
  sql`SELECT id FROM base_paths WHERE path = ${pathOrName} OR name = ${pathOrName} LIMIT 1`
@@ -23270,7 +23213,6 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
23270
23213
  const mediaFields = contentType.fields.filter(
23271
23214
  (f) => f.field_type === "image" || f.field_type === "video" || f.field_type === "file"
23272
23215
  );
23273
- const paragraphFieldDefs = contentType.fields.filter((f) => f.db_column === null);
23274
23216
  const searchableColumns = [
23275
23217
  ...contentType.fields.filter((f) => f.field_type === "text/plain" && f.db_column !== null).map((f) => f.db_column.column_name),
23276
23218
  ...contentType.system_fields.some((f) => f.name === "slug") ? ["slug"] : []
@@ -23420,7 +23362,7 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
23420
23362
  }
23421
23363
  for (const f of columnBackedFields) {
23422
23364
  const key = f.db_column.column_name;
23423
- if (!(key in storageBody)) continue;
23365
+ if (!Object.hasOwn(storageBody, key)) continue;
23424
23366
  columnData[key] = storageBody[key];
23425
23367
  }
23426
23368
  if (db) {
@@ -23462,7 +23404,7 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
23462
23404
  404
23463
23405
  );
23464
23406
  }
23465
- if (!contentType.only_one && "slug" in body) {
23407
+ if (!contentType.only_one && Object.hasOwn(body, "slug")) {
23466
23408
  const slug = body["slug"];
23467
23409
  if (typeof slug !== "string" || slug.trim() === "") {
23468
23410
  return c.json(
@@ -23532,11 +23474,11 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
23532
23474
  }
23533
23475
  const patchColumnBackedFields = contentType.fields.filter(isColumnBacked);
23534
23476
  const patchData = {};
23535
- if (!contentType.only_one && "slug" in body) patchData["slug"] = body["slug"];
23536
- if ("published" in body) patchData["published"] = body["published"];
23477
+ if (!contentType.only_one && Object.hasOwn(body, "slug")) patchData["slug"] = body["slug"];
23478
+ if (Object.hasOwn(body, "published")) patchData["published"] = body["published"];
23537
23479
  for (const f of patchColumnBackedFields) {
23538
23480
  const key = f.db_column.column_name;
23539
- if (!(key in storageBody)) continue;
23481
+ if (!Object.hasOwn(storageBody, key)) continue;
23540
23482
  patchData[key] = storageBody[key];
23541
23483
  }
23542
23484
  const updated = await repo.update(id, patchData);
@@ -23586,31 +23528,17 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
23586
23528
  404
23587
23529
  );
23588
23530
  }
23589
- const referrers = db ? await findRequiredReferrers(db, registry, typeName, id) : [];
23590
- if (referrers.length > 0) {
23591
- return c.json({ ok: false, error: { code: "ITEM_IN_USE", message: inUseMessage(referrers) } }, 409);
23592
- }
23593
- try {
23594
- await repo.delete(id);
23595
- } catch (err) {
23596
- if (isForeignKeyViolation(err)) {
23597
- return c.json({ ok: false, error: { code: "ITEM_IN_USE", message: IN_USE_RACE_MESSAGE } }, 409);
23598
- }
23599
- throw err;
23600
- }
23601
- const mediaDeltas = [
23602
- topLevelMediaDelta(mediaFields, item, null)
23603
- ];
23604
- if (db) {
23605
- for (const f of paragraphFieldDefs) {
23606
- const comp = f.ui_component;
23607
- if (comp.component !== "paragraph-embed" || !comp.ref) continue;
23608
- const pType = registry.paragraph_types[comp.ref];
23609
- if (!pType) continue;
23610
- mediaDeltas.push(await deleteParagraphField(db, id, f.name, pType, registry));
23611
- }
23531
+ const outcome = await deleteItemCascade(db, registry, mediaRepo, {
23532
+ typeName,
23533
+ fields: contentType.fields,
23534
+ mediaFields,
23535
+ repo,
23536
+ id,
23537
+ row: item
23538
+ });
23539
+ if (!outcome.ok) {
23540
+ return c.json({ ok: false, error: { code: "ITEM_IN_USE", message: outcome.message } }, 409);
23612
23541
  }
23613
- await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
23614
23542
  return c.json({ ok: true });
23615
23543
  }
23616
23544
  );
@@ -23631,7 +23559,6 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
23631
23559
  const mediaFields = taxonomyType.fields.filter(
23632
23560
  (f) => f.field_type === "image" || f.field_type === "video" || f.field_type === "file"
23633
23561
  );
23634
- const paragraphFieldDefs = taxonomyType.fields.filter((f) => f.db_column === null);
23635
23562
  const searchableColumns = [
23636
23563
  ...taxonomyType.fields.filter((f) => f.field_type === "text/plain" && f.db_column !== null).map((f) => f.db_column.column_name),
23637
23564
  ...taxonomyType.system_fields.some((f) => f.name === "slug") ? ["slug"] : []
@@ -23717,7 +23644,7 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
23717
23644
  };
23718
23645
  for (const f of taxColumnBackedFields) {
23719
23646
  const key = f.db_column.column_name;
23720
- if (!(key in storageBody)) continue;
23647
+ if (!Object.hasOwn(storageBody, key)) continue;
23721
23648
  taxColumnData[key] = storageBody[key];
23722
23649
  }
23723
23650
  const item = await repo.create(taxColumnData);
@@ -23769,10 +23696,10 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
23769
23696
  }
23770
23697
  const taxPatchColumnBackedFields = taxonomyType.fields.filter(isColumnBacked);
23771
23698
  const taxPatchData = {};
23772
- if ("published" in body) taxPatchData["published"] = body["published"];
23699
+ if (Object.hasOwn(body, "published")) taxPatchData["published"] = body["published"];
23773
23700
  for (const f of taxPatchColumnBackedFields) {
23774
23701
  const key = f.db_column.column_name;
23775
- if (!(key in storageBody)) continue;
23702
+ if (!Object.hasOwn(storageBody, key)) continue;
23776
23703
  taxPatchData[key] = storageBody[key];
23777
23704
  }
23778
23705
  const updated = await repo.update(id, taxPatchData);
@@ -23805,31 +23732,17 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
23805
23732
  404
23806
23733
  );
23807
23734
  }
23808
- const referrers = db ? await findRequiredReferrers(db, registry, typeName, id) : [];
23809
- if (referrers.length > 0) {
23810
- return c.json({ ok: false, error: { code: "ITEM_IN_USE", message: inUseMessage(referrers) } }, 409);
23811
- }
23812
- try {
23813
- await repo.delete(id);
23814
- } catch (err) {
23815
- if (isForeignKeyViolation(err)) {
23816
- return c.json({ ok: false, error: { code: "ITEM_IN_USE", message: IN_USE_RACE_MESSAGE } }, 409);
23817
- }
23818
- throw err;
23819
- }
23820
- const mediaDeltas = [
23821
- topLevelMediaDelta(mediaFields, item, null)
23822
- ];
23823
- if (db) {
23824
- for (const f of paragraphFieldDefs) {
23825
- const comp = f.ui_component;
23826
- if (comp.component !== "paragraph-embed" || !comp.ref) continue;
23827
- const pType = registry.paragraph_types[comp.ref];
23828
- if (!pType) continue;
23829
- mediaDeltas.push(await deleteParagraphField(db, id, f.name, pType, registry));
23830
- }
23735
+ const outcome = await deleteItemCascade(db, registry, mediaRepo, {
23736
+ typeName,
23737
+ fields: taxonomyType.fields,
23738
+ mediaFields,
23739
+ repo,
23740
+ id,
23741
+ row: item
23742
+ });
23743
+ if (!outcome.ok) {
23744
+ return c.json({ ok: false, error: { code: "ITEM_IN_USE", message: outcome.message } }, 409);
23831
23745
  }
23832
- await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
23833
23746
  return c.json({ ok: true });
23834
23747
  }
23835
23748
  );
@@ -24774,7 +24687,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
24774
24687
  }
24775
24688
  var API_VERSION = (() => {
24776
24689
  try {
24777
- return "0.9.2";
24690
+ return "0.9.3";
24778
24691
  } catch {
24779
24692
  return "0.0.0";
24780
24693
  }
@@ -24883,7 +24796,7 @@ function codeError(code, message) {
24883
24796
  err.code = code;
24884
24797
  return err;
24885
24798
  }
24886
- function quoteIdent4(name) {
24799
+ function quoteIdent3(name) {
24887
24800
  if (!/^[a-z][a-z0-9_-]*$/.test(name)) {
24888
24801
  throw new Error(`Unsafe SQL identifier: ${name}`);
24889
24802
  }
@@ -24934,7 +24847,7 @@ function whereFragment(conds) {
24934
24847
  function createDrizzleContentRepository(db, tableName, options = {}) {
24935
24848
  const { relations = {}, publishedRelations = false, sortableColumns } = options;
24936
24849
  function tableRaw() {
24937
- return sql.raw(quoteIdent4(tableName));
24850
+ return sql.raw(quoteIdent3(tableName));
24938
24851
  }
24939
24852
  async function resolveRows(rows, include, cache) {
24940
24853
  if (rows.length === 0) return rows;
@@ -24977,7 +24890,7 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
24977
24890
  const offset = (page - 1) * per_page;
24978
24891
  const conds = buildConditions(published_only, filters, search);
24979
24892
  const where = whereFragment(conds);
24980
- const sortCol = sql.raw(quoteIdent4(sort_by));
24893
+ const sortCol = sql.raw(quoteIdent3(sort_by));
24981
24894
  const sortDir = sql.raw(sort_order === "desc" ? "DESC" : "ASC");
24982
24895
  const countResult = await db.execute(
24983
24896
  sql`SELECT COUNT(*) AS total FROM ${tableRaw()}${where}`
@@ -25031,7 +24944,7 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
25031
24944
  updated_at: /* @__PURE__ */ new Date()
25032
24945
  };
25033
24946
  const entries = Object.entries(record).filter(([, v2]) => v2 !== void 0);
25034
- const colsSql = sql.join(entries.map(([k]) => sql.raw(quoteIdent4(k))), sql`, `);
24947
+ const colsSql = sql.join(entries.map(([k]) => sql.raw(quoteIdent3(k))), sql`, `);
25035
24948
  const valsSql = sql.join(entries.map(([, v2]) => sql`${v2}`), sql`, `);
25036
24949
  const result = await db.execute(
25037
24950
  sql`INSERT INTO ${tableRaw()} (${colsSql}) VALUES (${valsSql}) RETURNING *`
@@ -25048,7 +24961,7 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
25048
24961
  return this.findOne(id);
25049
24962
  }
25050
24963
  const setClauses = sql.join(
25051
- entries.map(([k, v2]) => sql`${sql.raw(quoteIdent4(k))} = ${v2}`),
24964
+ entries.map(([k, v2]) => sql`${sql.raw(quoteIdent3(k))} = ${v2}`),
25052
24965
  sql`, `
25053
24966
  );
25054
24967
  const result = await db.execute(
@@ -25457,7 +25370,7 @@ function createCmsApp(options) {
25457
25370
  handler: null,
25458
25371
  error: null
25459
25372
  }));
25460
- const ready = import("./handler-MFF32JB4-NUVEJZXC.js").then(({ createGraphQLHandler }) => {
25373
+ const ready = import("./handler-N5IMQJBP-USVWSLZP.js").then(({ createGraphQLHandler }) => {
25461
25374
  gqlPasses.forEach((pass, i) => {
25462
25375
  const mount = mounts[i];
25463
25376
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobbykim/manguito-cms-cli",
3
- "version": "0.11.3",
3
+ "version": "0.11.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-core": "0.11.0",
32
+ "@bobbykim/manguito-cms-admin": "0.6.2",
33
+ "@bobbykim/manguito-cms-api": "0.9.3",
33
34
  "@bobbykim/manguito-cms-db": "0.2.1",
34
- "@bobbykim/manguito-cms-api": "0.9.2",
35
- "@bobbykim/manguito-cms-admin": "0.6.1"
35
+ "@bobbykim/manguito-cms-core": "0.11.0"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/node": "^25.9.5",