@open-mercato/core 0.6.7-develop.6834.1.e76f4b8cbc → 0.6.7-develop.6851.1.c1680b00d8

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.
Files changed (88) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/customers/components/detail/ActivitiesDayStrip.js +2 -3
  3. package/dist/modules/customers/components/detail/ActivitiesDayStrip.js.map +2 -2
  4. package/dist/modules/dashboards/lib/aggregations.js +20 -6
  5. package/dist/modules/dashboards/lib/aggregations.js.map +2 -2
  6. package/dist/modules/entities/api/definitions.js +2 -0
  7. package/dist/modules/entities/api/definitions.js.map +2 -2
  8. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +18 -3
  9. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
  10. package/dist/modules/entities/components/RecordListRelationCell.js +63 -0
  11. package/dist/modules/entities/components/RecordListRelationCell.js.map +7 -0
  12. package/dist/modules/entities/components/recordListRelationDisplays.js +80 -0
  13. package/dist/modules/entities/components/recordListRelationDisplays.js.map +7 -0
  14. package/dist/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.js +14 -1
  15. package/dist/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.js.map +2 -2
  16. package/dist/modules/inbox_ops/components/proposals/EditActionDialog.js +22 -17
  17. package/dist/modules/inbox_ops/components/proposals/EditActionDialog.js.map +2 -2
  18. package/dist/modules/query_index/api/reindex.js +10 -0
  19. package/dist/modules/query_index/api/reindex.js.map +2 -2
  20. package/dist/modules/query_index/cli.js +125 -1
  21. package/dist/modules/query_index/cli.js.map +2 -2
  22. package/dist/modules/query_index/lib/coverage.js +19 -7
  23. package/dist/modules/query_index/lib/coverage.js.map +2 -2
  24. package/dist/modules/query_index/lib/reindexer.js +43 -37
  25. package/dist/modules/query_index/lib/reindexer.js.map +2 -2
  26. package/dist/modules/sales/commands/documents.js +27 -0
  27. package/dist/modules/sales/commands/documents.js.map +2 -2
  28. package/dist/modules/sales/data/validators.js +2 -0
  29. package/dist/modules/sales/data/validators.js.map +2 -2
  30. package/package.json +8 -8
  31. package/src/modules/api_keys/i18n/ko.json +42 -0
  32. package/src/modules/attachments/i18n/ko.json +172 -0
  33. package/src/modules/audit_logs/i18n/ko.json +66 -0
  34. package/src/modules/auth/i18n/ko.json +255 -0
  35. package/src/modules/business_rules/i18n/ko.json +396 -0
  36. package/src/modules/catalog/i18n/ko.json +723 -0
  37. package/src/modules/communication_channels/i18n/ko.json +138 -0
  38. package/src/modules/configs/i18n/ko.json +197 -0
  39. package/src/modules/currencies/i18n/de.json +1 -0
  40. package/src/modules/currencies/i18n/en.json +1 -0
  41. package/src/modules/currencies/i18n/es.json +1 -0
  42. package/src/modules/currencies/i18n/ko.json +160 -0
  43. package/src/modules/currencies/i18n/pl.json +1 -0
  44. package/src/modules/customer_accounts/i18n/ko.json +354 -0
  45. package/src/modules/customers/components/detail/ActivitiesDayStrip.tsx +2 -3
  46. package/src/modules/customers/i18n/de.json +10 -0
  47. package/src/modules/customers/i18n/en.json +10 -0
  48. package/src/modules/customers/i18n/es.json +10 -0
  49. package/src/modules/customers/i18n/ko.json +2855 -0
  50. package/src/modules/customers/i18n/pl.json +10 -0
  51. package/src/modules/dashboards/i18n/ko.json +118 -0
  52. package/src/modules/dashboards/lib/aggregations.ts +42 -6
  53. package/src/modules/data_sync/i18n/ko.json +125 -0
  54. package/src/modules/design_system/i18n/ko.json +58 -0
  55. package/src/modules/dictionaries/i18n/ko.json +130 -0
  56. package/src/modules/directory/i18n/ko.json +105 -0
  57. package/src/modules/entities/api/definitions.ts +4 -0
  58. package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +94 -77
  59. package/src/modules/entities/components/RecordListRelationCell.tsx +90 -0
  60. package/src/modules/entities/components/recordListRelationDisplays.ts +109 -0
  61. package/src/modules/entities/i18n/ko.json +260 -0
  62. package/src/modules/feature_toggles/i18n/ko.json +96 -0
  63. package/src/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.ts +17 -1
  64. package/src/modules/inbox_ops/components/proposals/EditActionDialog.tsx +21 -16
  65. package/src/modules/inbox_ops/components/proposals/types.ts +1 -0
  66. package/src/modules/inbox_ops/i18n/ko.json +196 -0
  67. package/src/modules/integrations/i18n/ko.json +129 -0
  68. package/src/modules/messages/i18n/ko.json +263 -0
  69. package/src/modules/notifications/i18n/ko.json +55 -0
  70. package/src/modules/payment_gateways/i18n/ko.json +87 -0
  71. package/src/modules/planner/i18n/ko.json +565 -0
  72. package/src/modules/portal/i18n/ko.json +143 -0
  73. package/src/modules/progress/i18n/ko.json +20 -0
  74. package/src/modules/query_index/api/reindex.ts +15 -0
  75. package/src/modules/query_index/cli.ts +180 -1
  76. package/src/modules/query_index/i18n/ko.json +69 -0
  77. package/src/modules/query_index/lib/coverage.ts +38 -8
  78. package/src/modules/query_index/lib/reindexer.ts +62 -40
  79. package/src/modules/resources/i18n/ko.json +770 -0
  80. package/src/modules/sales/commands/documents.ts +49 -0
  81. package/src/modules/sales/data/validators.ts +2 -0
  82. package/src/modules/sales/i18n/ko.json +1442 -0
  83. package/src/modules/shipping_carriers/i18n/ko.json +85 -0
  84. package/src/modules/staff/i18n/ko.json +1162 -0
  85. package/src/modules/sync_excel/i18n/ko.json +107 -0
  86. package/src/modules/translations/i18n/ko.json +45 -0
  87. package/src/modules/wms/i18n/ko.json +1053 -0
  88. package/src/modules/workflows/i18n/ko.json +1203 -0
@@ -20,6 +20,11 @@ import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuarde
20
20
  import { useT } from "@open-mercato/shared/lib/i18n/context";
21
21
  import { ErrorMessage, LoadingMessage } from "@open-mercato/ui/backend/detail";
22
22
  import { useRecordsEntityGuard } from "@open-mercato/core/modules/entities/components/useRecordsEntityGuard";
23
+ import {
24
+ RelationCell,
25
+ RelationDisplaysProvider,
26
+ useRecordListRelationDisplays
27
+ } from "@open-mercato/core/modules/entities/components/RecordListRelationCell";
23
28
  function toCsvUrl(base, params) {
24
29
  const p = new URLSearchParams(params);
25
30
  p.set("format", "csv");
@@ -67,6 +72,13 @@ function RecordsPageInner({ params }) {
67
72
  enabled: Boolean(entityId),
68
73
  keyExtras: [scopeVersion]
69
74
  });
75
+ const relationDefinitions = React.useMemo(
76
+ () => filterCustomFieldDefs(cfDefs, "list").filter(
77
+ (definition) => definition.kind === "relation"
78
+ ),
79
+ [cfDefs]
80
+ );
81
+ const relationDisplaysByField = useRecordListRelationDisplays(relationDefinitions, rawData);
70
82
  const searchableFields = React.useMemo(() => {
71
83
  const fields = (columns || []).map((col) => col.accessorKey).filter((key) => typeof key === "string" && key.length > 0);
72
84
  return Array.from(/* @__PURE__ */ new Set(["id", ...fields]));
@@ -142,7 +154,10 @@ function RecordsPageInner({ params }) {
142
154
  meta: { priority: idx < 4 ? 1 : idx < 6 ? 2 : idx < 8 ? 3 : idx < maxVisible ? 4 : 5 },
143
155
  cell: ({ getValue }) => {
144
156
  const v = getValue();
145
- return /* @__PURE__ */ jsx("span", { className: "truncate max-w-[24ch] inline-block align-top", title: normalizeCell(v), children: normalizeCell(v) });
157
+ if (d.kind === "relation") {
158
+ return /* @__PURE__ */ jsx(RelationCell, { fieldKey: d.key, value: v, fallbackText: normalizeCell(v) });
159
+ }
160
+ return /* @__PURE__ */ jsx("span", { className: "inline-block max-w-60 truncate align-top", title: normalizeCell(v), children: normalizeCell(v) });
146
161
  }
147
162
  }));
148
163
  const hasIdCol = cols.some((c) => c.accessorKey === "id" || c.id === "id");
@@ -297,7 +312,7 @@ export RECORD_ID="<record uuid>"` }) }),
297
312
  ] })
298
313
  ] })
299
314
  ] }),
300
- /* @__PURE__ */ jsx(
315
+ /* @__PURE__ */ jsx(RelationDisplaysProvider, { displaysByField: relationDisplaysByField, children: /* @__PURE__ */ jsx(
301
316
  DataTable,
302
317
  {
303
318
  stickyActionsColumn: true,
@@ -384,7 +399,7 @@ export RECORD_ID="<record uuid>"` }) }),
384
399
  pagination: { page, pageSize, total, totalPages, onPageChange: setPage },
385
400
  isLoading: loading
386
401
  }
387
- )
402
+ ) })
388
403
  ] }),
389
404
  ConfirmDialogElement
390
405
  ] });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../../src/modules/entities/backend/entities/user/%5BentityId%5D/records/page.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { resolveExtensionPointPattern } from '@open-mercato/shared/modules/widgets/extension-points'\nimport { extensionPoints } from '@open-mercato/core/modules/entities/extension-points'\nimport { useSearchParams } from 'next/navigation'\nimport type { ColumnDef, SortingState } from '@tanstack/react-table'\nimport { filterCustomFieldDefs, useCustomFieldDefs } from '@open-mercato/ui/backend/utils/customFieldDefs'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable, type DataTableExportFormat } from '@open-mercato/ui/backend/DataTable'\nimport type { PreparedExport } from '@open-mercato/shared/lib/crud/exporters'\nimport type { FilterDef } from '@open-mercato/ui/backend/FilterBar'\nimport { ContextHelp } from '@open-mercato/ui/backend/ContextHelp'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport Link from 'next/link'\nimport { apiCall, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { raiseCrudError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { ErrorMessage, LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { useRecordsEntityGuard } from '@open-mercato/core/modules/entities/components/useRecordsEntityGuard'\n\ntype RecordsResponse = {\n items: any[]\n total: number\n page: number\n pageSize: number\n totalPages: number\n}\n\nfunction toCsvUrl(base: string, params: URLSearchParams) {\n // Build a relative URL to avoid SSR/CSR origin mismatch hydration issues\n const p = new URLSearchParams(params)\n p.set('format', 'csv')\n const qs = p.toString()\n return qs ? `${base}?${qs}` : base\n}\n\nfunction normalizeCell(v: any): string {\n if (Array.isArray(v)) return v.filter((x) => x != null && x !== '').join(', ')\n if (v === true) return 'Yes'\n if (v === false) return 'No'\n if (v == null) return ''\n if (v instanceof Date) return v.toISOString()\n return String(v)\n}\n\nexport default function RecordsPage({ params }: { params: { entityId?: string } }) {\n const t = useT()\n const entityId = decodeURIComponent(params?.entityId || '')\n const guard = useRecordsEntityGuard(entityId)\n if (guard !== 'allowed') {\n return (\n <Page>\n <PageBody>\n {guard === 'blocked' ? (\n <ErrorMessage label={t('entities.userEntities.records.errors.systemEntity', 'This entity is system-managed. Records are available for custom entities only.')} />\n ) : (\n <LoadingMessage label={t('entities.userEntities.records.loading', 'Loading records...')} />\n )}\n </PageBody>\n </Page>\n )\n }\n return <RecordsPageInner params={params} />\n}\n\nfunction RecordsPageInner({ params }: { params: { entityId?: string } }) {\n const t = useT()\n const entityId = decodeURIComponent(params?.entityId || '')\n const [sorting, setSorting] = React.useState<SortingState>([{ id: 'id', desc: false }])\n const [page, setPage] = React.useState(1)\n const [pageSize, setPageSize] = React.useState(50)\n const [search, setSearch] = React.useState('')\n const [filterValues, setFilterValues] = React.useState<Record<string, any>>({})\n const [columns, setColumns] = React.useState<ColumnDef<any>[]>([])\n const [rawData, setRawData] = React.useState<any[]>([])\n const [total, setTotal] = React.useState(0)\n const [totalPages, setTotalPages] = React.useState(1)\n const [loading, setLoading] = React.useState(false)\n const scopeVersion = useOrganizationScopeVersion()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const deleteMutationContextId = `entities.user.records.${entityId}:single-delete`\n const { runMutation: runDeleteMutation, retryLastMutation: retryDeleteMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n resourceId: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: deleteMutationContextId,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n const { data: cfDefs = [] } = useCustomFieldDefs(entityId, {\n enabled: Boolean(entityId),\n keyExtras: [scopeVersion],\n })\n\n // Fields searched server-side: every visible column plus the base `id`.\n const searchableFields = React.useMemo(() => {\n const fields = (columns || [])\n .map((col) => (col as any).accessorKey)\n .filter((key): key is string => typeof key === 'string' && key.length > 0)\n return Array.from(new Set(['id', ...fields]))\n }, [columns])\n\n // Fetch records whenever paging/sorting/filters/search change. Search is applied\n // server-side (before pagination) so totals and exports stay consistent (#3229).\n React.useEffect(() => {\n let cancelled = false\n const run = async () => {\n setLoading(true)\n try {\n const params = new URLSearchParams()\n params.set('entityId', entityId)\n params.set('page', String(page))\n params.set('pageSize', String(pageSize))\n const trimmedSearch = search.trim()\n if (trimmedSearch) {\n params.set('search', trimmedSearch)\n if (searchableFields.length) params.set('searchFields', searchableFields.join(','))\n }\n const s = sorting?.[0]\n if (s?.id) {\n params.set('sortField', String(s.id))\n params.set('sortDir', s.desc ? 'desc' : 'asc')\n }\n // Flatten filter values into query params\n for (const [k, v] of Object.entries(filterValues)) {\n if (v == null) continue\n if (Array.isArray(v)) {\n if (v.length) params.set(k, v.join(','))\n } else if (typeof v === 'object') {\n // dateRange-like shapes are not supported generically here; skip\n } else {\n params.set(k, String(v))\n }\n }\n const j = await readApiResultOrThrow<RecordsResponse>(\n `/api/entities/records?${params.toString()}`,\n undefined,\n {\n errorMessage: 'Failed to load records',\n fallback: {\n items: [],\n total: 0,\n page,\n pageSize,\n totalPages: 1,\n },\n },\n )\n if (!cancelled) {\n setRawData(j.items || [])\n setTotal(j.total)\n setTotalPages(j.totalPages)\n }\n } catch (e) {\n if (!cancelled) {\n setRawData([])\n setTotal(0)\n setTotalPages(1)\n }\n } finally {\n if (!cancelled) setLoading(false)\n }\n }\n if (entityId) run()\n return () => { cancelled = true }\n }, [entityId, page, pageSize, sorting, filterValues, scopeVersion, search, searchableFields])\n\n // Build columns from custom field definitions only (no data round-trip)\n React.useEffect(() => {\n const visibleDefs = filterCustomFieldDefs(cfDefs, 'list') as any\n const maxVisible = 10\n const cols: ColumnDef<any>[] = visibleDefs.map((d: any, idx: number) => ({\n accessorKey: d.key,\n header: d.label || d.key,\n meta: { priority: idx < 4 ? 1 : idx < 6 ? 2 : idx < 8 ? 3 : idx < maxVisible ? 4 : 5 },\n cell: ({ getValue }: { getValue: () => unknown }) => {\n const v = getValue() as any\n return <span className=\"truncate max-w-[24ch] inline-block align-top\" title={normalizeCell(v)}>{normalizeCell(v)}</span>\n },\n }))\n // Ensure hidden 'id' column exists for sorting/state\n const hasIdCol = cols.some((c) => (c as any).accessorKey === 'id' || (c as any).id === 'id')\n if (!hasIdCol) cols.unshift({ accessorKey: 'id', header: 'ID', meta: { hidden: true, priority: 6 } } as any)\n setColumns(cols)\n }, [cfDefs])\n\n // Search is server-side (see fetch effect); render the fetched page as-is so\n // pagination totals and exports stay consistent with the active search (#3229).\n const data = rawData\n\n const viewExportColumns = React.useMemo(() => {\n return (columns || [])\n .map((col) => {\n const accessorKey = (col as any).accessorKey\n if (!accessorKey || typeof accessorKey !== 'string') return null\n if ((col as any).meta?.hidden) return null\n const header = typeof col.header === 'string'\n ? col.header\n : accessorKey.startsWith('cf_')\n ? accessorKey.slice(3)\n : accessorKey\n return { field: accessorKey, header }\n })\n .filter((col): col is { field: string; header: string } => !!col)\n }, [columns])\n\n const buildFullExportUrl = React.useCallback((format: DataTableExportFormat) => {\n const qp = new URLSearchParams({\n entityId,\n format,\n exportScope: 'full',\n all: 'true',\n })\n const sort = sorting?.[0]\n if (sort?.id) {\n qp.set('sortField', String(sort.id))\n qp.set('sortDir', sort.desc ? 'desc' : 'asc')\n }\n const trimmedSearch = search.trim()\n if (trimmedSearch) {\n qp.set('search', trimmedSearch)\n if (searchableFields.length) qp.set('searchFields', searchableFields.join(','))\n }\n return `/api/entities/records?${qp.toString()}`\n }, [entityId, sorting, search, searchableFields])\n\n const exportConfig = React.useMemo(() => {\n const safeEntityId = entityId.replace(/[^a-z0-9_-]/gi, '_') || 'records'\n return {\n view: {\n description: 'Exports the current list respecting filters and column visibility.',\n prepare: async (): Promise<{ prepared: PreparedExport; filename: string }> => {\n const rowsForExport = data.map((row) => {\n const out: Record<string, unknown> = {}\n for (const col of viewExportColumns) {\n out[col.field] = (row as Record<string, unknown>)[col.field]\n }\n return out\n })\n const prepared: PreparedExport = {\n columns: viewExportColumns.map((col) => ({ field: col.field, header: col.header })),\n rows: rowsForExport,\n }\n return { prepared, filename: `${safeEntityId}_view` }\n },\n },\n full: {\n description: 'Exports raw records with every field and custom field included.',\n getUrl: (format: DataTableExportFormat) => buildFullExportUrl(format),\n filename: () => `${safeEntityId}_full`,\n },\n }\n }, [buildFullExportUrl, data, entityId, viewExportColumns])\n\n const hasAnyFormFields = React.useMemo(() => filterCustomFieldDefs(cfDefs, 'form').length > 0, [cfDefs])\n const actions = (\n <>\n <Button asChild variant=\"outline\" size=\"sm\">\n <Link href={`/backend/entities/user/${encodeURIComponent(entityId)}`}>\n Edit Entity Definition\n </Link>\n </Button>\n {hasAnyFormFields && (\n <Button asChild>\n <Link href={`/backend/entities/user/${encodeURIComponent(entityId)}/records/create`}>\n Create\n </Link>\n </Button>\n )}\n </>\n )\n\n // Ensure filters are visible even if no custom fields are marked filterable\n const baseFilters: FilterDef[] = React.useMemo(() => ([\n { id: 'id', label: 'ID', type: 'text' },\n ]), [])\n\n return (\n <Page>\n <PageBody>\n <ContextHelp bulb title=\"API: Manage Records via cURL\" className=\"mb-4\">\n <p className=\"mb-2\">\n Interact with this custom entity via the backend API using cURL. Use API keys for machine-to-machine access\u2014mint one from the{' '}\n <a className=\"underline\" target=\"_blank\" rel=\"noreferrer\" href=\"https://docs.openmercato.com/user-guide/api-keys\">\n Managing API keys guide\n </a>{' '}\n or the{' '}\n <a className=\"underline\" target=\"_blank\" rel=\"noreferrer\" href=\"https://docs.openmercato.com/cli/api-keys\">\n API keys CLI documentation\n </a>{' '}\n before running these calls.\n </p>\n <div className=\"space-y-2\">\n <div>\n <div className=\"font-medium mb-1\">1) Configure environment variables</div>\n <pre className=\"bg-muted p-3 rounded text-xs overflow-auto\"><code>{`export BASE_URL=\"http://localhost:3000/api\"\nexport API_KEY=\"<paste API key secret here>\" # scoped with entities.features\nexport ENTITY_ID=\"${entityId}\"\nexport RECORD_ID=\"<record uuid>\"`}</code></pre>\n <p className=\"text-muted-foreground mt-1\">\n Need a new key? Follow the{' '}\n <a className=\"underline\" target=\"_blank\" rel=\"noreferrer\" href=\"https://docs.openmercato.com/user-guide/api-keys\">\n Managing API keys\n </a>{' '}\n walkthrough or mint one via{' '}\n <a className=\"underline\" target=\"_blank\" rel=\"noreferrer\" href=\"https://docs.openmercato.com/cli/api-keys\">\n mercato api_keys add\n </a>\n .\n </p>\n </div>\n\n <div>\n <div className=\"font-medium mb-1\">2) List records</div>\n <pre className=\"bg-muted p-3 rounded text-xs overflow-auto\"><code>{`curl -s -H \"X-Api-Key: $API_KEY\" \\\n \"$BASE_URL/entities/records?entityId=$ENTITY_ID\" | jq`}</code></pre>\n </div>\n\n <div>\n <div className=\"font-medium mb-1\">3) Read a single record (by id)</div>\n <pre className=\"bg-muted p-3 rounded text-xs overflow-auto\"><code>{`curl -s -H \"X-Api-Key: $API_KEY\" \\\n \"$BASE_URL/entities/records?entityId=$ENTITY_ID&id=$RECORD_ID\" | jq`}</code></pre>\n <p className=\"text-muted-foreground mt-1\">Note: Response is a list; filter by <code>id</code> to get a single item.</p>\n </div>\n\n <div>\n <div className=\"font-medium mb-1\">4) Create a record</div>\n <pre className=\"bg-muted p-3 rounded text-xs overflow-auto\"><code>{`curl -s -X POST \\\n -H \"X-Api-Key: $API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d \"{\n \\\\\"entityId\\\\\": \\\\\"$ENTITY_ID\\\\\",\n \\\\\"values\\\\\": {\n \\\\\"field_one\\\\\": \\\\\"Example\\\\\",\n \\\\\"field_two\\\\\": 123\n }\n }\" \\\n \"$BASE_URL/entities/records\" | jq`}</code></pre>\n <p className=\"text-muted-foreground mt-1\">For custom entities, send field keys without the <code>cf_</code> prefix. The API normalizes this server-side.</p>\n </div>\n\n <div>\n <div className=\"font-medium mb-1\">5) Update a record</div>\n <pre className=\"bg-muted p-3 rounded text-xs overflow-auto\"><code>{`curl -s -X PUT \\\n -H \"X-Api-Key: $API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d \"{\n \\\\\"entityId\\\\\": \\\\\"$ENTITY_ID\\\\\",\n \\\\\"recordId\\\\\": \\\\\"$RECORD_ID\\\\\",\n \\\\\"values\\\\\": {\n \\\\\"field_one\\\\\": \\\\\"Updated\\\\\"\n }\n }\" \\\n \"$BASE_URL/entities/records\" | jq`}</code></pre>\n </div>\n\n <div>\n <div className=\"font-medium mb-1\">6) Delete a record</div>\n <pre className=\"bg-muted p-3 rounded text-xs overflow-auto\"><code>{`curl -s -X DELETE \\\n -H \"X-Api-Key: $API_KEY\" \\\n \"$BASE_URL/entities/records?entityId=$ENTITY_ID&recordId=$RECORD_ID\" | jq`}</code></pre>\n </div>\n\n <div className=\"text-muted-foreground\">\n Security notes:\n <ul className=\"list-disc pl-5 mt-1 space-y-1\">\n <li>All endpoints require a valid API key. Keys inherit tenant, organization, and feature scope.</li>\n <li>Rotate keys regularly and delete unused ones in the admin UI.</li>\n <li>Store the secret in a secure vault; anyone with the header can act within the key&apos;s permissions.</li>\n </ul>\n </div>\n </div>\n </ContextHelp>\n <DataTable\n stickyActionsColumn\n title={`Records: ${entityId}`}\n entityId={entityId}\n actions={actions}\n columns={columns}\n data={data}\n perspective={{\n tableId: resolveExtensionPointPattern(extensionPoints.hosts.userRecordsTable.pattern, { entityId }),\n }}\n exporter={exportConfig}\n filters={baseFilters}\n filterValues={filterValues}\n rowActions={(row) => (\n <RowActions\n items={[\n { id: 'edit', label: 'Edit', href: `/backend/entities/user/${encodeURIComponent(entityId)}/records/${encodeURIComponent(String((row as any).id))}` },\n { id: 'delete', label: 'Delete', destructive: true, onSelect: async () => {\n const recordId = String((row as any).id)\n try {\n const confirmed = await confirm({\n title: 'Delete this record?',\n variant: 'destructive',\n })\n if (!confirmed) return\n await runDeleteMutation({\n operation: async () => {\n const deleteCall = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader((row as any).updatedAt),\n () => apiCall(\n `/api/entities/records?entityId=${encodeURIComponent(entityId)}&recordId=${encodeURIComponent(recordId)}`,\n { method: 'DELETE' },\n ),\n )\n if (!deleteCall.ok) {\n await raiseCrudError(deleteCall.response, 'Failed to delete record')\n }\n },\n context: {\n formId: deleteMutationContextId,\n resourceKind: 'entities.record',\n resourceId: recordId,\n retryLastMutation: retryDeleteMutation,\n },\n })\n const j = await readApiResultOrThrow<RecordsResponse>(\n `/api/entities/records?entityId=${encodeURIComponent(entityId)}&page=${page}&pageSize=${pageSize}`,\n undefined,\n {\n errorMessage: 'Failed to reload records',\n fallback: { items: [], total: 0, page, pageSize, totalPages: 1 },\n },\n )\n setRawData(j.items || [])\n setTotal(j.total || 0)\n setTotalPages(j.totalPages || 1)\n flash('Record has been removed', 'success')\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Failed to delete record'\n flash(message, 'error')\n }\n } },\n ]}\n />\n )}\n sortable\n sorting={sorting}\n onSortingChange={setSorting}\n searchValue={search}\n onSearchChange={(v) => { setSearch(v); setPage(1) }}\n onFiltersApply={(vals) => { setFilterValues(vals); setPage(1) }}\n onFiltersClear={() => { setFilterValues({}); setPage(1) }}\n pagination={{ page, pageSize, total, totalPages, onPageChange: setPage }}\n isLoading={loading}\n />\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n}\n"],
5
- "mappings": ";AA4DY,SA2MR,UA3MQ,KA2MR,YA3MQ;AA3DZ,YAAY,WAAW;AACvB,SAAS,oCAAoC;AAC7C,SAAS,uBAAuB;AAGhC,SAAS,uBAAuB,0BAA0B;AAC1D,SAAS,MAAM,gBAAgB;AAC/B,SAAS,iBAA6C;AAGtD,SAAS,mBAAmB;AAC5B,SAAS,cAAc;AACvB,SAAS,kBAAkB;AAC3B,OAAO,UAAU;AACjB,SAAS,SAAS,sBAAsB,mCAAmC;AAC3E,SAAS,iCAAiC;AAC1C,SAAS,aAAa;AACtB,SAAS,sBAAsB;AAC/B,SAAS,mCAAmC;AAC5C,SAAS,wBAAwB;AACjC,SAAS,0BAA0B;AACnC,SAAS,YAAY;AACrB,SAAS,cAAc,sBAAsB;AAC7C,SAAS,6BAA6B;AAUtC,SAAS,SAAS,MAAc,QAAyB;AAEvD,QAAM,IAAI,IAAI,gBAAgB,MAAM;AACpC,IAAE,IAAI,UAAU,KAAK;AACrB,QAAM,KAAK,EAAE,SAAS;AACtB,SAAO,KAAK,GAAG,IAAI,IAAI,EAAE,KAAK;AAChC;AAEA,SAAS,cAAc,GAAgB;AACrC,MAAI,MAAM,QAAQ,CAAC,EAAG,QAAO,EAAE,OAAO,CAAC,MAAM,KAAK,QAAQ,MAAM,EAAE,EAAE,KAAK,IAAI;AAC7E,MAAI,MAAM,KAAM,QAAO;AACvB,MAAI,MAAM,MAAO,QAAO;AACxB,MAAI,KAAK,KAAM,QAAO;AACtB,MAAI,aAAa,KAAM,QAAO,EAAE,YAAY;AAC5C,SAAO,OAAO,CAAC;AACjB;AAEe,SAAR,YAA6B,EAAE,OAAO,GAAsC;AACjF,QAAM,IAAI,KAAK;AACf,QAAM,WAAW,mBAAmB,QAAQ,YAAY,EAAE;AAC1D,QAAM,QAAQ,sBAAsB,QAAQ;AAC5C,MAAI,UAAU,WAAW;AACvB,WACE,oBAAC,QACC,8BAAC,YACE,oBAAU,YACT,oBAAC,gBAAa,OAAO,EAAE,qDAAqD,gFAAgF,GAAG,IAE/J,oBAAC,kBAAe,OAAO,EAAE,yCAAyC,oBAAoB,GAAG,GAE7F,GACF;AAAA,EAEJ;AACA,SAAO,oBAAC,oBAAiB,QAAgB;AAC3C;AAEA,SAAS,iBAAiB,EAAE,OAAO,GAAsC;AACvE,QAAM,IAAI,KAAK;AACf,QAAM,WAAW,mBAAmB,QAAQ,YAAY,EAAE;AAC1D,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAuB,CAAC,EAAE,IAAI,MAAM,MAAM,MAAM,CAAC,CAAC;AACtF,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAS,CAAC;AACxC,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAS,EAAE;AACjD,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAS,EAAE;AAC7C,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAA8B,CAAC,CAAC;AAC9E,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAA2B,CAAC,CAAC;AACjE,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAgB,CAAC,CAAC;AACtD,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAS,CAAC;AAC1C,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,CAAC;AACpD,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAS,KAAK;AAClD,QAAM,eAAe,4BAA4B;AACjD,QAAM,EAAE,SAAS,qBAAqB,IAAI,iBAAiB;AAC3D,QAAM,0BAA0B,yBAAyB,QAAQ;AACjE,QAAM,EAAE,aAAa,mBAAmB,mBAAmB,oBAAoB,IAAI,mBAKhF;AAAA,IACD,WAAW;AAAA,IACX,gBAAgB,EAAE,8BAA8B,4BAA4B;AAAA,EAC9E,CAAC;AACD,QAAM,EAAE,MAAM,SAAS,CAAC,EAAE,IAAI,mBAAmB,UAAU;AAAA,IACzD,SAAS,QAAQ,QAAQ;AAAA,IACzB,WAAW,CAAC,YAAY;AAAA,EAC1B,CAAC;AAGD,QAAM,mBAAmB,MAAM,QAAQ,MAAM;AAC3C,UAAM,UAAU,WAAW,CAAC,GACzB,IAAI,CAAC,QAAS,IAAY,WAAW,EACrC,OAAO,CAAC,QAAuB,OAAO,QAAQ,YAAY,IAAI,SAAS,CAAC;AAC3E,WAAO,MAAM,KAAK,oBAAI,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;AAAA,EAC9C,GAAG,CAAC,OAAO,CAAC;AAIZ,QAAM,UAAU,MAAM;AACpB,QAAI,YAAY;AAChB,UAAM,MAAM,YAAY;AACtB,iBAAW,IAAI;AACf,UAAI;AACF,cAAMA,UAAS,IAAI,gBAAgB;AACnC,QAAAA,QAAO,IAAI,YAAY,QAAQ;AAC/B,QAAAA,QAAO,IAAI,QAAQ,OAAO,IAAI,CAAC;AAC/B,QAAAA,QAAO,IAAI,YAAY,OAAO,QAAQ,CAAC;AACvC,cAAM,gBAAgB,OAAO,KAAK;AAClC,YAAI,eAAe;AACjB,UAAAA,QAAO,IAAI,UAAU,aAAa;AAClC,cAAI,iBAAiB,OAAQ,CAAAA,QAAO,IAAI,gBAAgB,iBAAiB,KAAK,GAAG,CAAC;AAAA,QACpF;AACA,cAAM,IAAI,UAAU,CAAC;AACrB,YAAI,GAAG,IAAI;AACT,UAAAA,QAAO,IAAI,aAAa,OAAO,EAAE,EAAE,CAAC;AACpC,UAAAA,QAAO,IAAI,WAAW,EAAE,OAAO,SAAS,KAAK;AAAA,QAC/C;AAEA,mBAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,YAAY,GAAG;AACjD,cAAI,KAAK,KAAM;AACf,cAAI,MAAM,QAAQ,CAAC,GAAG;AACpB,gBAAI,EAAE,OAAQ,CAAAA,QAAO,IAAI,GAAG,EAAE,KAAK,GAAG,CAAC;AAAA,UACzC,WAAW,OAAO,MAAM,UAAU;AAAA,UAElC,OAAO;AACL,YAAAA,QAAO,IAAI,GAAG,OAAO,CAAC,CAAC;AAAA,UACzB;AAAA,QACF;AACA,cAAM,IAAI,MAAM;AAAA,UACd,yBAAyBA,QAAO,SAAS,CAAC;AAAA,UAC1C;AAAA,UACA;AAAA,YACE,cAAc;AAAA,YACd,UAAU;AAAA,cACR,OAAO,CAAC;AAAA,cACR,OAAO;AAAA,cACP;AAAA,cACA;AAAA,cACA,YAAY;AAAA,YACd;AAAA,UACF;AAAA,QACF;AACA,YAAI,CAAC,WAAW;AACd,qBAAW,EAAE,SAAS,CAAC,CAAC;AACxB,mBAAS,EAAE,KAAK;AAChB,wBAAc,EAAE,UAAU;AAAA,QAC5B;AAAA,MACF,SAAS,GAAG;AACV,YAAI,CAAC,WAAW;AACd,qBAAW,CAAC,CAAC;AACb,mBAAS,CAAC;AACV,wBAAc,CAAC;AAAA,QACjB;AAAA,MACF,UAAE;AACA,YAAI,CAAC,UAAW,YAAW,KAAK;AAAA,MAClC;AAAA,IACF;AACA,QAAI,SAAU,KAAI;AAClB,WAAO,MAAM;AAAE,kBAAY;AAAA,IAAK;AAAA,EAClC,GAAG,CAAC,UAAU,MAAM,UAAU,SAAS,cAAc,cAAc,QAAQ,gBAAgB,CAAC;AAG5F,QAAM,UAAU,MAAM;AACpB,UAAM,cAAc,sBAAsB,QAAQ,MAAM;AACxD,UAAM,aAAa;AACnB,UAAM,OAAyB,YAAY,IAAI,CAAC,GAAQ,SAAiB;AAAA,MACvE,aAAa,EAAE;AAAA,MACf,QAAQ,EAAE,SAAS,EAAE;AAAA,MACrB,MAAM,EAAE,UAAU,MAAM,IAAI,IAAI,MAAM,IAAI,IAAI,MAAM,IAAI,IAAI,MAAM,aAAa,IAAI,EAAE;AAAA,MACrF,MAAM,CAAC,EAAE,SAAS,MAAmC;AACnD,cAAM,IAAI,SAAS;AACnB,eAAO,oBAAC,UAAK,WAAU,gDAA+C,OAAO,cAAc,CAAC,GAAI,wBAAc,CAAC,GAAE;AAAA,MACnH;AAAA,IACF,EAAE;AAEF,UAAM,WAAW,KAAK,KAAK,CAAC,MAAO,EAAU,gBAAgB,QAAS,EAAU,OAAO,IAAI;AAC3F,QAAI,CAAC,SAAU,MAAK,QAAQ,EAAE,aAAa,MAAM,QAAQ,MAAM,MAAM,EAAE,QAAQ,MAAM,UAAU,EAAE,EAAE,CAAQ;AAC3G,eAAW,IAAI;AAAA,EACjB,GAAG,CAAC,MAAM,CAAC;AAIX,QAAM,OAAO;AAEb,QAAM,oBAAoB,MAAM,QAAQ,MAAM;AAC5C,YAAQ,WAAW,CAAC,GACjB,IAAI,CAAC,QAAQ;AACZ,YAAM,cAAe,IAAY;AACjC,UAAI,CAAC,eAAe,OAAO,gBAAgB,SAAU,QAAO;AAC5D,UAAK,IAAY,MAAM,OAAQ,QAAO;AACtC,YAAM,SAAS,OAAO,IAAI,WAAW,WACjC,IAAI,SACJ,YAAY,WAAW,KAAK,IAC1B,YAAY,MAAM,CAAC,IACnB;AACN,aAAO,EAAE,OAAO,aAAa,OAAO;AAAA,IACtC,CAAC,EACA,OAAO,CAAC,QAAkD,CAAC,CAAC,GAAG;AAAA,EACpE,GAAG,CAAC,OAAO,CAAC;AAEZ,QAAM,qBAAqB,MAAM,YAAY,CAAC,WAAkC;AAC9E,UAAM,KAAK,IAAI,gBAAgB;AAAA,MAC7B;AAAA,MACA;AAAA,MACA,aAAa;AAAA,MACb,KAAK;AAAA,IACP,CAAC;AACD,UAAM,OAAO,UAAU,CAAC;AACxB,QAAI,MAAM,IAAI;AACZ,SAAG,IAAI,aAAa,OAAO,KAAK,EAAE,CAAC;AACnC,SAAG,IAAI,WAAW,KAAK,OAAO,SAAS,KAAK;AAAA,IAC9C;AACA,UAAM,gBAAgB,OAAO,KAAK;AAClC,QAAI,eAAe;AACjB,SAAG,IAAI,UAAU,aAAa;AAC9B,UAAI,iBAAiB,OAAQ,IAAG,IAAI,gBAAgB,iBAAiB,KAAK,GAAG,CAAC;AAAA,IAChF;AACA,WAAO,yBAAyB,GAAG,SAAS,CAAC;AAAA,EAC/C,GAAG,CAAC,UAAU,SAAS,QAAQ,gBAAgB,CAAC;AAEhD,QAAM,eAAe,MAAM,QAAQ,MAAM;AACvC,UAAM,eAAe,SAAS,QAAQ,iBAAiB,GAAG,KAAK;AAC/D,WAAO;AAAA,MACL,MAAM;AAAA,QACJ,aAAa;AAAA,QACb,SAAS,YAAqE;AAC5E,gBAAM,gBAAgB,KAAK,IAAI,CAAC,QAAQ;AACtC,kBAAM,MAA+B,CAAC;AACtC,uBAAW,OAAO,mBAAmB;AACnC,kBAAI,IAAI,KAAK,IAAK,IAAgC,IAAI,KAAK;AAAA,YAC7D;AACA,mBAAO;AAAA,UACT,CAAC;AACD,gBAAM,WAA2B;AAAA,YAC/B,SAAS,kBAAkB,IAAI,CAAC,SAAS,EAAE,OAAO,IAAI,OAAO,QAAQ,IAAI,OAAO,EAAE;AAAA,YAClF,MAAM;AAAA,UACR;AACA,iBAAO,EAAE,UAAU,UAAU,GAAG,YAAY,QAAQ;AAAA,QACtD;AAAA,MACF;AAAA,MACA,MAAM;AAAA,QACJ,aAAa;AAAA,QACb,QAAQ,CAAC,WAAkC,mBAAmB,MAAM;AAAA,QACpE,UAAU,MAAM,GAAG,YAAY;AAAA,MACjC;AAAA,IACF;AAAA,EACF,GAAG,CAAC,oBAAoB,MAAM,UAAU,iBAAiB,CAAC;AAE1D,QAAM,mBAAmB,MAAM,QAAQ,MAAM,sBAAsB,QAAQ,MAAM,EAAE,SAAS,GAAG,CAAC,MAAM,CAAC;AACvG,QAAM,UACJ,iCACE;AAAA,wBAAC,UAAO,SAAO,MAAC,SAAQ,WAAU,MAAK,MACrC,8BAAC,QAAK,MAAM,0BAA0B,mBAAmB,QAAQ,CAAC,IAAI,oCAEtE,GACF;AAAA,IACC,oBACC,oBAAC,UAAO,SAAO,MACb,8BAAC,QAAK,MAAM,0BAA0B,mBAAmB,QAAQ,CAAC,mBAAmB,oBAErF,GACF;AAAA,KAEJ;AAIF,QAAM,cAA2B,MAAM,QAAQ,MAAO;AAAA,IACpD,EAAE,IAAI,MAAM,OAAO,MAAM,MAAM,OAAO;AAAA,EACxC,GAAI,CAAC,CAAC;AAEN,SACE,qBAAC,QACC;AAAA,yBAAC,YACC;AAAA,2BAAC,eAAY,MAAI,MAAC,OAAM,gCAA+B,WAAU,QAC/D;AAAA,6BAAC,OAAE,WAAU,QAAO;AAAA;AAAA,UAC4G;AAAA,UAC9H,oBAAC,OAAE,WAAU,aAAY,QAAO,UAAS,KAAI,cAAa,MAAK,oDAAmD,qCAElH;AAAA,UAAK;AAAA,UAAI;AAAA,UACF;AAAA,UACP,oBAAC,OAAE,WAAU,aAAY,QAAO,UAAS,KAAI,cAAa,MAAK,6CAA4C,wCAE3G;AAAA,UAAK;AAAA,UAAI;AAAA,WAEX;AAAA,QACA,qBAAC,SAAI,WAAU,aACb;AAAA,+BAAC,SACC;AAAA,gCAAC,SAAI,WAAU,oBAAmB,gDAAkC;AAAA,YACpE,oBAAC,SAAI,WAAU,8CAA6C,8BAAC,UAAM;AAAA;AAAA,oBAE7D,QAAQ;AAAA,mCACM,GAAO;AAAA,YAC3B,qBAAC,OAAE,WAAU,8BAA6B;AAAA;AAAA,cACb;AAAA,cAC3B,oBAAC,OAAE,WAAU,aAAY,QAAO,UAAS,KAAI,cAAa,MAAK,oDAAmD,+BAElH;AAAA,cAAK;AAAA,cAAI;AAAA,cACmB;AAAA,cAC5B,oBAAC,OAAE,WAAU,aAAY,QAAO,UAAS,KAAI,cAAa,MAAK,6CAA4C,kCAE3G;AAAA,cAAI;AAAA,eAEN;AAAA,aACF;AAAA,UAEA,qBAAC,SACC;AAAA,gCAAC,SAAI,WAAU,oBAAmB,6BAAe;AAAA,YACjD,oBAAC,SAAI,WAAU,8CAA6C,8BAAC,UAAM,sGACxB,GAAO;AAAA,aACpD;AAAA,UAEA,qBAAC,SACC;AAAA,gCAAC,SAAI,WAAU,oBAAmB,6CAA+B;AAAA,YACjE,oBAAC,SAAI,WAAU,8CAA6C,8BAAC,UAAM,oHACV,GAAO;AAAA,YAChE,qBAAC,OAAE,WAAU,8BAA6B;AAAA;AAAA,cAAoC,oBAAC,UAAK,gBAAE;AAAA,cAAO;AAAA,eAAsB;AAAA,aACrH;AAAA,UAEA,qBAAC,SACC;AAAA,gCAAC,SAAI,WAAU,oBAAmB,gCAAkB;AAAA,YACpD,oBAAC,SAAI,WAAU,8CAA6C,8BAAC,UAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,2CAU5C,GAAO;AAAA,YAC9B,qBAAC,OAAE,WAAU,8BAA6B;AAAA;AAAA,cAAiD,oBAAC,UAAK,iBAAG;AAAA,cAAO;AAAA,eAA6C;AAAA,aAC1J;AAAA,UAEA,qBAAC,SACC;AAAA,gCAAC,SAAI,WAAU,oBAAmB,gCAAkB;AAAA,YACpD,oBAAC,SAAI,WAAU,8CAA6C,8BAAC,UAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,2CAU5C,GAAO;AAAA,aAChC;AAAA,UAEA,qBAAC,SACC;AAAA,gCAAC,SAAI,WAAU,oBAAmB,gCAAkB;AAAA,YACpD,oBAAC,SAAI,WAAU,8CAA6C,8BAAC,UAAM,sIAEJ,GAAO;AAAA,aACxE;AAAA,UAEA,qBAAC,SAAI,WAAU,yBAAwB;AAAA;AAAA,YAErC,qBAAC,QAAG,WAAU,iCACZ;AAAA,kCAAC,QAAG,0GAA4F;AAAA,cAChG,oBAAC,QAAG,2EAA6D;AAAA,cACjE,oBAAC,QAAG,8GAAqG;AAAA,eAC3G;AAAA,aACF;AAAA,WACF;AAAA,SACF;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,qBAAmB;AAAA,UACnB,OAAO,YAAY,QAAQ;AAAA,UAC3B;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,aAAa;AAAA,YACX,SAAS,6BAA6B,gBAAgB,MAAM,iBAAiB,SAAS,EAAE,SAAS,CAAC;AAAA,UACpG;AAAA,UACA,UAAU;AAAA,UACV,SAAS;AAAA,UACT;AAAA,UACA,YAAY,CAAC,QACX;AAAA,YAAC;AAAA;AAAA,cACC,OAAO;AAAA,gBACL,EAAE,IAAI,QAAQ,OAAO,QAAQ,MAAM,0BAA0B,mBAAmB,QAAQ,CAAC,YAAY,mBAAmB,OAAQ,IAAY,EAAE,CAAC,CAAC,GAAG;AAAA,gBACnJ,EAAE,IAAI,UAAU,OAAO,UAAU,aAAa,MAAM,UAAU,YAAY;AACxE,wBAAM,WAAW,OAAQ,IAAY,EAAE;AACvC,sBAAI;AACF,0BAAM,YAAY,MAAM,QAAQ;AAAA,sBAC9B,OAAO;AAAA,sBACP,SAAS;AAAA,oBACX,CAAC;AACD,wBAAI,CAAC,UAAW;AAChB,0BAAM,kBAAkB;AAAA,sBACtB,WAAW,YAAY;AACrB,8BAAM,aAAa,MAAM;AAAA,0BACvB,0BAA2B,IAAY,SAAS;AAAA,0BAChD,MAAM;AAAA,4BACJ,kCAAkC,mBAAmB,QAAQ,CAAC,aAAa,mBAAmB,QAAQ,CAAC;AAAA,4BACvG,EAAE,QAAQ,SAAS;AAAA,0BACrB;AAAA,wBACF;AACA,4BAAI,CAAC,WAAW,IAAI;AAClB,gCAAM,eAAe,WAAW,UAAU,yBAAyB;AAAA,wBACrE;AAAA,sBACF;AAAA,sBACA,SAAS;AAAA,wBACP,QAAQ;AAAA,wBACR,cAAc;AAAA,wBACd,YAAY;AAAA,wBACZ,mBAAmB;AAAA,sBACrB;AAAA,oBACF,CAAC;AACD,0BAAM,IAAI,MAAM;AAAA,sBACd,kCAAkC,mBAAmB,QAAQ,CAAC,SAAS,IAAI,aAAa,QAAQ;AAAA,sBAChG;AAAA,sBACA;AAAA,wBACE,cAAc;AAAA,wBACd,UAAU,EAAE,OAAO,CAAC,GAAG,OAAO,GAAG,MAAM,UAAU,YAAY,EAAE;AAAA,sBACjE;AAAA,oBACF;AACA,+BAAW,EAAE,SAAS,CAAC,CAAC;AACxB,6BAAS,EAAE,SAAS,CAAC;AACrB,kCAAc,EAAE,cAAc,CAAC;AAC/B,0BAAM,2BAA2B,SAAS;AAAA,kBAC5C,SAAS,OAAO;AACd,0BAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AACzD,0BAAM,SAAS,OAAO;AAAA,kBACxB;AAAA,gBACF,EAAE;AAAA,cACJ;AAAA;AAAA,UACF;AAAA,UAEF,UAAQ;AAAA,UACR;AAAA,UACA,iBAAiB;AAAA,UACjB,aAAa;AAAA,UACb,gBAAgB,CAAC,MAAM;AAAE,sBAAU,CAAC;AAAG,oBAAQ,CAAC;AAAA,UAAE;AAAA,UAClD,gBAAgB,CAAC,SAAS;AAAE,4BAAgB,IAAI;AAAG,oBAAQ,CAAC;AAAA,UAAE;AAAA,UAC9D,gBAAgB,MAAM;AAAE,4BAAgB,CAAC,CAAC;AAAG,oBAAQ,CAAC;AAAA,UAAE;AAAA,UACxD,YAAY,EAAE,MAAM,UAAU,OAAO,YAAY,cAAc,QAAQ;AAAA,UACvE,WAAW;AAAA;AAAA,MACb;AAAA,OACF;AAAA,IACC;AAAA,KACH;AAEJ;",
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { resolveExtensionPointPattern } from '@open-mercato/shared/modules/widgets/extension-points'\nimport { extensionPoints } from '@open-mercato/core/modules/entities/extension-points'\nimport { useSearchParams } from 'next/navigation'\nimport type { ColumnDef, SortingState } from '@tanstack/react-table'\nimport { filterCustomFieldDefs, useCustomFieldDefs, type CustomFieldDefDto } from '@open-mercato/ui/backend/utils/customFieldDefs'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable, type DataTableExportFormat } from '@open-mercato/ui/backend/DataTable'\nimport type { PreparedExport } from '@open-mercato/shared/lib/crud/exporters'\nimport type { FilterDef } from '@open-mercato/ui/backend/FilterBar'\nimport { ContextHelp } from '@open-mercato/ui/backend/ContextHelp'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport Link from 'next/link'\nimport { apiCall, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { raiseCrudError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { ErrorMessage, LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { useRecordsEntityGuard } from '@open-mercato/core/modules/entities/components/useRecordsEntityGuard'\nimport {\n RelationCell,\n RelationDisplaysProvider,\n useRecordListRelationDisplays,\n} from '@open-mercato/core/modules/entities/components/RecordListRelationCell'\n\ntype RecordsResponse = {\n items: any[]\n total: number\n page: number\n pageSize: number\n totalPages: number\n}\n\nfunction toCsvUrl(base: string, params: URLSearchParams) {\n // Build a relative URL to avoid SSR/CSR origin mismatch hydration issues\n const p = new URLSearchParams(params)\n p.set('format', 'csv')\n const qs = p.toString()\n return qs ? `${base}?${qs}` : base\n}\n\nfunction normalizeCell(v: any): string {\n if (Array.isArray(v)) return v.filter((x) => x != null && x !== '').join(', ')\n if (v === true) return 'Yes'\n if (v === false) return 'No'\n if (v == null) return ''\n if (v instanceof Date) return v.toISOString()\n return String(v)\n}\n\nexport default function RecordsPage({ params }: { params: { entityId?: string } }) {\n const t = useT()\n const entityId = decodeURIComponent(params?.entityId || '')\n const guard = useRecordsEntityGuard(entityId)\n if (guard !== 'allowed') {\n return (\n <Page>\n <PageBody>\n {guard === 'blocked' ? (\n <ErrorMessage label={t('entities.userEntities.records.errors.systemEntity', 'This entity is system-managed. Records are available for custom entities only.')} />\n ) : (\n <LoadingMessage label={t('entities.userEntities.records.loading', 'Loading records...')} />\n )}\n </PageBody>\n </Page>\n )\n }\n return <RecordsPageInner params={params} />\n}\n\nfunction RecordsPageInner({ params }: { params: { entityId?: string } }) {\n const t = useT()\n const entityId = decodeURIComponent(params?.entityId || '')\n const [sorting, setSorting] = React.useState<SortingState>([{ id: 'id', desc: false }])\n const [page, setPage] = React.useState(1)\n const [pageSize, setPageSize] = React.useState(50)\n const [search, setSearch] = React.useState('')\n const [filterValues, setFilterValues] = React.useState<Record<string, any>>({})\n const [columns, setColumns] = React.useState<ColumnDef<any>[]>([])\n const [rawData, setRawData] = React.useState<any[]>([])\n const [total, setTotal] = React.useState(0)\n const [totalPages, setTotalPages] = React.useState(1)\n const [loading, setLoading] = React.useState(false)\n const scopeVersion = useOrganizationScopeVersion()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const deleteMutationContextId = `entities.user.records.${entityId}:single-delete`\n const { runMutation: runDeleteMutation, retryLastMutation: retryDeleteMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n resourceId: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: deleteMutationContextId,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n const { data: cfDefs = [] } = useCustomFieldDefs(entityId, {\n enabled: Boolean(entityId),\n keyExtras: [scopeVersion],\n })\n const relationDefinitions = React.useMemo(\n () => filterCustomFieldDefs(cfDefs, 'list').filter(\n (definition): definition is CustomFieldDefDto => definition.kind === 'relation',\n ),\n [cfDefs],\n )\n const relationDisplaysByField = useRecordListRelationDisplays(relationDefinitions, rawData)\n\n // Fields searched server-side: every visible column plus the base `id`.\n const searchableFields = React.useMemo(() => {\n const fields = (columns || [])\n .map((col) => (col as any).accessorKey)\n .filter((key): key is string => typeof key === 'string' && key.length > 0)\n return Array.from(new Set(['id', ...fields]))\n }, [columns])\n\n // Fetch records whenever paging/sorting/filters/search change. Search is applied\n // server-side (before pagination) so totals and exports stay consistent (#3229).\n React.useEffect(() => {\n let cancelled = false\n const run = async () => {\n setLoading(true)\n try {\n const params = new URLSearchParams()\n params.set('entityId', entityId)\n params.set('page', String(page))\n params.set('pageSize', String(pageSize))\n const trimmedSearch = search.trim()\n if (trimmedSearch) {\n params.set('search', trimmedSearch)\n if (searchableFields.length) params.set('searchFields', searchableFields.join(','))\n }\n const s = sorting?.[0]\n if (s?.id) {\n params.set('sortField', String(s.id))\n params.set('sortDir', s.desc ? 'desc' : 'asc')\n }\n // Flatten filter values into query params\n for (const [k, v] of Object.entries(filterValues)) {\n if (v == null) continue\n if (Array.isArray(v)) {\n if (v.length) params.set(k, v.join(','))\n } else if (typeof v === 'object') {\n // dateRange-like shapes are not supported generically here; skip\n } else {\n params.set(k, String(v))\n }\n }\n const j = await readApiResultOrThrow<RecordsResponse>(\n `/api/entities/records?${params.toString()}`,\n undefined,\n {\n errorMessage: 'Failed to load records',\n fallback: {\n items: [],\n total: 0,\n page,\n pageSize,\n totalPages: 1,\n },\n },\n )\n if (!cancelled) {\n setRawData(j.items || [])\n setTotal(j.total)\n setTotalPages(j.totalPages)\n }\n } catch (e) {\n if (!cancelled) {\n setRawData([])\n setTotal(0)\n setTotalPages(1)\n }\n } finally {\n if (!cancelled) setLoading(false)\n }\n }\n if (entityId) run()\n return () => { cancelled = true }\n }, [entityId, page, pageSize, sorting, filterValues, scopeVersion, search, searchableFields])\n\n // Build columns from custom field definitions only (no data round-trip)\n React.useEffect(() => {\n const visibleDefs = filterCustomFieldDefs(cfDefs, 'list') as any\n const maxVisible = 10\n const cols: ColumnDef<any>[] = visibleDefs.map((d: any, idx: number) => ({\n accessorKey: d.key,\n header: d.label || d.key,\n meta: { priority: idx < 4 ? 1 : idx < 6 ? 2 : idx < 8 ? 3 : idx < maxVisible ? 4 : 5 },\n cell: ({ getValue }: { getValue: () => unknown }) => {\n const v = getValue() as any\n if (d.kind === 'relation') {\n return <RelationCell fieldKey={d.key} value={v} fallbackText={normalizeCell(v)} />\n }\n return <span className=\"inline-block max-w-60 truncate align-top\" title={normalizeCell(v)}>{normalizeCell(v)}</span>\n },\n }))\n // Ensure hidden 'id' column exists for sorting/state\n const hasIdCol = cols.some((c) => (c as any).accessorKey === 'id' || (c as any).id === 'id')\n if (!hasIdCol) cols.unshift({ accessorKey: 'id', header: 'ID', meta: { hidden: true, priority: 6 } } as any)\n setColumns(cols)\n }, [cfDefs])\n\n // Search is server-side (see fetch effect); render the fetched page as-is so\n // pagination totals and exports stay consistent with the active search (#3229).\n const data = rawData\n\n const viewExportColumns = React.useMemo(() => {\n return (columns || [])\n .map((col) => {\n const accessorKey = (col as any).accessorKey\n if (!accessorKey || typeof accessorKey !== 'string') return null\n if ((col as any).meta?.hidden) return null\n const header = typeof col.header === 'string'\n ? col.header\n : accessorKey.startsWith('cf_')\n ? accessorKey.slice(3)\n : accessorKey\n return { field: accessorKey, header }\n })\n .filter((col): col is { field: string; header: string } => !!col)\n }, [columns])\n\n const buildFullExportUrl = React.useCallback((format: DataTableExportFormat) => {\n const qp = new URLSearchParams({\n entityId,\n format,\n exportScope: 'full',\n all: 'true',\n })\n const sort = sorting?.[0]\n if (sort?.id) {\n qp.set('sortField', String(sort.id))\n qp.set('sortDir', sort.desc ? 'desc' : 'asc')\n }\n const trimmedSearch = search.trim()\n if (trimmedSearch) {\n qp.set('search', trimmedSearch)\n if (searchableFields.length) qp.set('searchFields', searchableFields.join(','))\n }\n return `/api/entities/records?${qp.toString()}`\n }, [entityId, sorting, search, searchableFields])\n\n const exportConfig = React.useMemo(() => {\n const safeEntityId = entityId.replace(/[^a-z0-9_-]/gi, '_') || 'records'\n return {\n view: {\n description: 'Exports the current list respecting filters and column visibility.',\n prepare: async (): Promise<{ prepared: PreparedExport; filename: string }> => {\n const rowsForExport = data.map((row) => {\n const out: Record<string, unknown> = {}\n for (const col of viewExportColumns) {\n out[col.field] = (row as Record<string, unknown>)[col.field]\n }\n return out\n })\n const prepared: PreparedExport = {\n columns: viewExportColumns.map((col) => ({ field: col.field, header: col.header })),\n rows: rowsForExport,\n }\n return { prepared, filename: `${safeEntityId}_view` }\n },\n },\n full: {\n description: 'Exports raw records with every field and custom field included.',\n getUrl: (format: DataTableExportFormat) => buildFullExportUrl(format),\n filename: () => `${safeEntityId}_full`,\n },\n }\n }, [buildFullExportUrl, data, entityId, viewExportColumns])\n\n const hasAnyFormFields = React.useMemo(() => filterCustomFieldDefs(cfDefs, 'form').length > 0, [cfDefs])\n const actions = (\n <>\n <Button asChild variant=\"outline\" size=\"sm\">\n <Link href={`/backend/entities/user/${encodeURIComponent(entityId)}`}>\n Edit Entity Definition\n </Link>\n </Button>\n {hasAnyFormFields && (\n <Button asChild>\n <Link href={`/backend/entities/user/${encodeURIComponent(entityId)}/records/create`}>\n Create\n </Link>\n </Button>\n )}\n </>\n )\n\n // Ensure filters are visible even if no custom fields are marked filterable\n const baseFilters: FilterDef[] = React.useMemo(() => ([\n { id: 'id', label: 'ID', type: 'text' },\n ]), [])\n\n return (\n <Page>\n <PageBody>\n <ContextHelp bulb title=\"API: Manage Records via cURL\" className=\"mb-4\">\n <p className=\"mb-2\">\n Interact with this custom entity via the backend API using cURL. Use API keys for machine-to-machine access\u2014mint one from the{' '}\n <a className=\"underline\" target=\"_blank\" rel=\"noreferrer\" href=\"https://docs.openmercato.com/user-guide/api-keys\">\n Managing API keys guide\n </a>{' '}\n or the{' '}\n <a className=\"underline\" target=\"_blank\" rel=\"noreferrer\" href=\"https://docs.openmercato.com/cli/api-keys\">\n API keys CLI documentation\n </a>{' '}\n before running these calls.\n </p>\n <div className=\"space-y-2\">\n <div>\n <div className=\"font-medium mb-1\">1) Configure environment variables</div>\n <pre className=\"bg-muted p-3 rounded text-xs overflow-auto\"><code>{`export BASE_URL=\"http://localhost:3000/api\"\nexport API_KEY=\"<paste API key secret here>\" # scoped with entities.features\nexport ENTITY_ID=\"${entityId}\"\nexport RECORD_ID=\"<record uuid>\"`}</code></pre>\n <p className=\"text-muted-foreground mt-1\">\n Need a new key? Follow the{' '}\n <a className=\"underline\" target=\"_blank\" rel=\"noreferrer\" href=\"https://docs.openmercato.com/user-guide/api-keys\">\n Managing API keys\n </a>{' '}\n walkthrough or mint one via{' '}\n <a className=\"underline\" target=\"_blank\" rel=\"noreferrer\" href=\"https://docs.openmercato.com/cli/api-keys\">\n mercato api_keys add\n </a>\n .\n </p>\n </div>\n\n <div>\n <div className=\"font-medium mb-1\">2) List records</div>\n <pre className=\"bg-muted p-3 rounded text-xs overflow-auto\"><code>{`curl -s -H \"X-Api-Key: $API_KEY\" \\\n \"$BASE_URL/entities/records?entityId=$ENTITY_ID\" | jq`}</code></pre>\n </div>\n\n <div>\n <div className=\"font-medium mb-1\">3) Read a single record (by id)</div>\n <pre className=\"bg-muted p-3 rounded text-xs overflow-auto\"><code>{`curl -s -H \"X-Api-Key: $API_KEY\" \\\n \"$BASE_URL/entities/records?entityId=$ENTITY_ID&id=$RECORD_ID\" | jq`}</code></pre>\n <p className=\"text-muted-foreground mt-1\">Note: Response is a list; filter by <code>id</code> to get a single item.</p>\n </div>\n\n <div>\n <div className=\"font-medium mb-1\">4) Create a record</div>\n <pre className=\"bg-muted p-3 rounded text-xs overflow-auto\"><code>{`curl -s -X POST \\\n -H \"X-Api-Key: $API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d \"{\n \\\\\"entityId\\\\\": \\\\\"$ENTITY_ID\\\\\",\n \\\\\"values\\\\\": {\n \\\\\"field_one\\\\\": \\\\\"Example\\\\\",\n \\\\\"field_two\\\\\": 123\n }\n }\" \\\n \"$BASE_URL/entities/records\" | jq`}</code></pre>\n <p className=\"text-muted-foreground mt-1\">For custom entities, send field keys without the <code>cf_</code> prefix. The API normalizes this server-side.</p>\n </div>\n\n <div>\n <div className=\"font-medium mb-1\">5) Update a record</div>\n <pre className=\"bg-muted p-3 rounded text-xs overflow-auto\"><code>{`curl -s -X PUT \\\n -H \"X-Api-Key: $API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d \"{\n \\\\\"entityId\\\\\": \\\\\"$ENTITY_ID\\\\\",\n \\\\\"recordId\\\\\": \\\\\"$RECORD_ID\\\\\",\n \\\\\"values\\\\\": {\n \\\\\"field_one\\\\\": \\\\\"Updated\\\\\"\n }\n }\" \\\n \"$BASE_URL/entities/records\" | jq`}</code></pre>\n </div>\n\n <div>\n <div className=\"font-medium mb-1\">6) Delete a record</div>\n <pre className=\"bg-muted p-3 rounded text-xs overflow-auto\"><code>{`curl -s -X DELETE \\\n -H \"X-Api-Key: $API_KEY\" \\\n \"$BASE_URL/entities/records?entityId=$ENTITY_ID&recordId=$RECORD_ID\" | jq`}</code></pre>\n </div>\n\n <div className=\"text-muted-foreground\">\n Security notes:\n <ul className=\"list-disc pl-5 mt-1 space-y-1\">\n <li>All endpoints require a valid API key. Keys inherit tenant, organization, and feature scope.</li>\n <li>Rotate keys regularly and delete unused ones in the admin UI.</li>\n <li>Store the secret in a secure vault; anyone with the header can act within the key&apos;s permissions.</li>\n </ul>\n </div>\n </div>\n </ContextHelp>\n <RelationDisplaysProvider displaysByField={relationDisplaysByField}>\n <DataTable\n stickyActionsColumn\n title={`Records: ${entityId}`}\n entityId={entityId}\n actions={actions}\n columns={columns}\n data={data}\n perspective={{\n tableId: resolveExtensionPointPattern(extensionPoints.hosts.userRecordsTable.pattern, { entityId }),\n }}\n exporter={exportConfig}\n filters={baseFilters}\n filterValues={filterValues}\n rowActions={(row) => (\n <RowActions\n items={[\n { id: 'edit', label: 'Edit', href: `/backend/entities/user/${encodeURIComponent(entityId)}/records/${encodeURIComponent(String((row as any).id))}` },\n { id: 'delete', label: 'Delete', destructive: true, onSelect: async () => {\n const recordId = String((row as any).id)\n try {\n const confirmed = await confirm({\n title: 'Delete this record?',\n variant: 'destructive',\n })\n if (!confirmed) return\n await runDeleteMutation({\n operation: async () => {\n const deleteCall = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader((row as any).updatedAt),\n () => apiCall(\n `/api/entities/records?entityId=${encodeURIComponent(entityId)}&recordId=${encodeURIComponent(recordId)}`,\n { method: 'DELETE' },\n ),\n )\n if (!deleteCall.ok) {\n await raiseCrudError(deleteCall.response, 'Failed to delete record')\n }\n },\n context: {\n formId: deleteMutationContextId,\n resourceKind: 'entities.record',\n resourceId: recordId,\n retryLastMutation: retryDeleteMutation,\n },\n })\n const j = await readApiResultOrThrow<RecordsResponse>(\n `/api/entities/records?entityId=${encodeURIComponent(entityId)}&page=${page}&pageSize=${pageSize}`,\n undefined,\n {\n errorMessage: 'Failed to reload records',\n fallback: { items: [], total: 0, page, pageSize, totalPages: 1 },\n },\n )\n setRawData(j.items || [])\n setTotal(j.total || 0)\n setTotalPages(j.totalPages || 1)\n flash('Record has been removed', 'success')\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Failed to delete record'\n flash(message, 'error')\n }\n } },\n ]}\n />\n )}\n sortable\n sorting={sorting}\n onSortingChange={setSorting}\n searchValue={search}\n onSearchChange={(v) => { setSearch(v); setPage(1) }}\n onFiltersApply={(vals) => { setFilterValues(vals); setPage(1) }}\n onFiltersClear={() => { setFilterValues({}); setPage(1) }}\n pagination={{ page, pageSize, total, totalPages, onPageChange: setPage }}\n isLoading={loading}\n />\n </RelationDisplaysProvider>\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["params"]
7
7
  }
@@ -0,0 +1,63 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import Link from "next/link";
5
+ import {
6
+ getRecordListRelationDisplays,
7
+ resolveRecordListRelationDisplays
8
+ } from "./recordListRelationDisplays.js";
9
+ const RelationDisplaysContext = React.createContext({});
10
+ function RelationDisplaysProvider({
11
+ displaysByField,
12
+ children
13
+ }) {
14
+ return /* @__PURE__ */ jsx(RelationDisplaysContext.Provider, { value: displaysByField, children });
15
+ }
16
+ function useRecordListRelationDisplays(definitions, records) {
17
+ const [displaysByField, setDisplaysByField] = React.useState({});
18
+ React.useEffect(() => {
19
+ const abortController = new AbortController();
20
+ setDisplaysByField((current) => Object.keys(current).length ? {} : current);
21
+ if (!definitions.length || !records.length) {
22
+ return () => abortController.abort();
23
+ }
24
+ void resolveRecordListRelationDisplays(definitions, records, abortController.signal).then((resolvedDisplays) => {
25
+ if (!abortController.signal.aborted) setDisplaysByField(resolvedDisplays);
26
+ }).catch(() => {
27
+ if (!abortController.signal.aborted) setDisplaysByField({});
28
+ });
29
+ return () => abortController.abort();
30
+ }, [definitions, records]);
31
+ return displaysByField;
32
+ }
33
+ function RelationCell({
34
+ fieldKey,
35
+ value,
36
+ fallbackText
37
+ }) {
38
+ const displaysByField = React.useContext(RelationDisplaysContext);
39
+ const resolvedDisplays = getRecordListRelationDisplays(value, displaysByField[fieldKey]);
40
+ const rawValue = resolvedDisplays.map((display) => display.id).join(", ") || fallbackText;
41
+ if (!resolvedDisplays.length) {
42
+ return /* @__PURE__ */ jsx("span", { className: "inline-block max-w-60 truncate align-top", title: rawValue, children: rawValue });
43
+ }
44
+ return /* @__PURE__ */ jsx("span", { className: "inline-block max-w-60 truncate align-top", title: rawValue, children: resolvedDisplays.map((display, index) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
45
+ index > 0 ? ", " : null,
46
+ display.href ? /* @__PURE__ */ jsx(
47
+ Link,
48
+ {
49
+ href: display.href,
50
+ className: "font-medium text-primary underline-offset-2 hover:underline focus-visible:underline",
51
+ onClick: (event) => event.stopPropagation(),
52
+ onKeyDown: (event) => event.stopPropagation(),
53
+ children: display.label
54
+ }
55
+ ) : display.label
56
+ ] }, `${display.id}-${index}`)) });
57
+ }
58
+ export {
59
+ RelationCell,
60
+ RelationDisplaysProvider,
61
+ useRecordListRelationDisplays
62
+ };
63
+ //# sourceMappingURL=RecordListRelationCell.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/entities/components/RecordListRelationCell.tsx"],
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport Link from 'next/link'\nimport type { CustomFieldDefDto } from '@open-mercato/ui/backend/utils/customFieldDefs'\nimport {\n getRecordListRelationDisplays,\n resolveRecordListRelationDisplays,\n type RelationDisplaysByField,\n} from './recordListRelationDisplays'\n\nconst RelationDisplaysContext = React.createContext<RelationDisplaysByField>({})\n\nexport function RelationDisplaysProvider({\n displaysByField,\n children,\n}: {\n displaysByField: RelationDisplaysByField\n children: React.ReactNode\n}) {\n return (\n <RelationDisplaysContext.Provider value={displaysByField}>\n {children}\n </RelationDisplaysContext.Provider>\n )\n}\n\nexport function useRecordListRelationDisplays(\n definitions: CustomFieldDefDto[],\n records: Array<Record<string, unknown>>,\n): RelationDisplaysByField {\n const [displaysByField, setDisplaysByField] = React.useState<RelationDisplaysByField>({})\n\n React.useEffect(() => {\n const abortController = new AbortController()\n setDisplaysByField((current) => (Object.keys(current).length ? {} : current))\n if (!definitions.length || !records.length) {\n return () => abortController.abort()\n }\n\n void resolveRecordListRelationDisplays(definitions, records, abortController.signal)\n .then((resolvedDisplays) => {\n if (!abortController.signal.aborted) setDisplaysByField(resolvedDisplays)\n })\n .catch(() => {\n if (!abortController.signal.aborted) setDisplaysByField({})\n })\n\n return () => abortController.abort()\n }, [definitions, records])\n\n return displaysByField\n}\n\nexport function RelationCell({\n fieldKey,\n value,\n fallbackText,\n}: {\n fieldKey: string\n value: unknown\n fallbackText: string\n}) {\n const displaysByField = React.useContext(RelationDisplaysContext)\n const resolvedDisplays = getRecordListRelationDisplays(value, displaysByField[fieldKey])\n const rawValue = resolvedDisplays.map((display) => display.id).join(', ') || fallbackText\n\n if (!resolvedDisplays.length) {\n return <span className=\"inline-block max-w-60 truncate align-top\" title={rawValue}>{rawValue}</span>\n }\n\n return (\n <span className=\"inline-block max-w-60 truncate align-top\" title={rawValue}>\n {resolvedDisplays.map((display, index) => (\n <React.Fragment key={`${display.id}-${index}`}>\n {index > 0 ? ', ' : null}\n {display.href ? (\n <Link\n href={display.href}\n className=\"font-medium text-primary underline-offset-2 hover:underline focus-visible:underline\"\n onClick={(event) => event.stopPropagation()}\n onKeyDown={(event) => event.stopPropagation()}\n >\n {display.label}\n </Link>\n ) : display.label}\n </React.Fragment>\n ))}\n </span>\n )\n}\n"],
5
+ "mappings": ";AAoBI,cAqDI,YArDJ;AAnBJ,YAAY,WAAW;AACvB,OAAO,UAAU;AAEjB;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AAEP,MAAM,0BAA0B,MAAM,cAAuC,CAAC,CAAC;AAExE,SAAS,yBAAyB;AAAA,EACvC;AAAA,EACA;AACF,GAGG;AACD,SACE,oBAAC,wBAAwB,UAAxB,EAAiC,OAAO,iBACtC,UACH;AAEJ;AAEO,SAAS,8BACd,aACA,SACyB;AACzB,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,MAAM,SAAkC,CAAC,CAAC;AAExF,QAAM,UAAU,MAAM;AACpB,UAAM,kBAAkB,IAAI,gBAAgB;AAC5C,uBAAmB,CAAC,YAAa,OAAO,KAAK,OAAO,EAAE,SAAS,CAAC,IAAI,OAAQ;AAC5E,QAAI,CAAC,YAAY,UAAU,CAAC,QAAQ,QAAQ;AAC1C,aAAO,MAAM,gBAAgB,MAAM;AAAA,IACrC;AAEA,SAAK,kCAAkC,aAAa,SAAS,gBAAgB,MAAM,EAChF,KAAK,CAAC,qBAAqB;AAC1B,UAAI,CAAC,gBAAgB,OAAO,QAAS,oBAAmB,gBAAgB;AAAA,IAC1E,CAAC,EACA,MAAM,MAAM;AACX,UAAI,CAAC,gBAAgB,OAAO,QAAS,oBAAmB,CAAC,CAAC;AAAA,IAC5D,CAAC;AAEH,WAAO,MAAM,gBAAgB,MAAM;AAAA,EACrC,GAAG,CAAC,aAAa,OAAO,CAAC;AAEzB,SAAO;AACT;AAEO,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,QAAM,kBAAkB,MAAM,WAAW,uBAAuB;AAChE,QAAM,mBAAmB,8BAA8B,OAAO,gBAAgB,QAAQ,CAAC;AACvF,QAAM,WAAW,iBAAiB,IAAI,CAAC,YAAY,QAAQ,EAAE,EAAE,KAAK,IAAI,KAAK;AAE7E,MAAI,CAAC,iBAAiB,QAAQ;AAC5B,WAAO,oBAAC,UAAK,WAAU,4CAA2C,OAAO,UAAW,oBAAS;AAAA,EAC/F;AAEA,SACE,oBAAC,UAAK,WAAU,4CAA2C,OAAO,UAC/D,2BAAiB,IAAI,CAAC,SAAS,UAC9B,qBAAC,MAAM,UAAN,EACE;AAAA,YAAQ,IAAI,OAAO;AAAA,IACnB,QAAQ,OACP;AAAA,MAAC;AAAA;AAAA,QACC,MAAM,QAAQ;AAAA,QACd,WAAU;AAAA,QACV,SAAS,CAAC,UAAU,MAAM,gBAAgB;AAAA,QAC1C,WAAW,CAAC,UAAU,MAAM,gBAAgB;AAAA,QAE3C,kBAAQ;AAAA;AAAA,IACX,IACE,QAAQ;AAAA,OAXO,GAAG,QAAQ,EAAE,IAAI,KAAK,EAY3C,CACD,GACH;AAEJ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,80 @@
1
+ import {
2
+ buildRelationHref,
3
+ collectRelationValueIds,
4
+ fetchRelationRecordDisplays,
5
+ parseRelationOptionsMetadata
6
+ } from "@open-mercato/ui/backend/utils/customFieldRelationDisplay";
7
+ function resolveRelationOptionsUrl(definition) {
8
+ if (definition.kind !== "relation") return null;
9
+ const configuredUrl = definition.optionsUrl?.trim();
10
+ if (configuredUrl && parseRelationOptionsMetadata(configuredUrl)) return configuredUrl;
11
+ const relatedEntityId = definition.relatedEntityId?.trim();
12
+ if (!relatedEntityId) return null;
13
+ return `/api/entities/relations/options?entityId=${encodeURIComponent(relatedEntityId)}`;
14
+ }
15
+ function collectRelationLookups(definitions, records) {
16
+ const lookups = /* @__PURE__ */ new Map();
17
+ definitions.forEach((definition) => {
18
+ const optionsUrl = resolveRelationOptionsUrl(definition);
19
+ if (!optionsUrl) return;
20
+ const relation = parseRelationOptionsMetadata(optionsUrl);
21
+ if (!relation) return;
22
+ const recordIds = new Set(
23
+ records.flatMap((record) => collectRelationValueIds(record[definition.key]))
24
+ );
25
+ if (!recordIds.size) return;
26
+ const lookupKey = JSON.stringify([relation.entityId, optionsUrl]);
27
+ const lookup = lookups.get(lookupKey) ?? {
28
+ optionsUrl,
29
+ relation,
30
+ recordIds: /* @__PURE__ */ new Set(),
31
+ recordIdsByField: /* @__PURE__ */ new Map()
32
+ };
33
+ recordIds.forEach((recordId) => lookup.recordIds.add(recordId));
34
+ lookup.recordIdsByField.set(definition.key, recordIds);
35
+ lookups.set(lookupKey, lookup);
36
+ });
37
+ return Array.from(lookups.values());
38
+ }
39
+ async function resolveRecordListRelationDisplays(definitions, records, signal) {
40
+ const displaysByField = {};
41
+ const lookups = collectRelationLookups(definitions, records);
42
+ await Promise.all(lookups.map(async (lookup) => {
43
+ let fetchedDisplays = {};
44
+ try {
45
+ fetchedDisplays = await fetchRelationRecordDisplays(
46
+ lookup.optionsUrl,
47
+ lookup.relation,
48
+ Array.from(lookup.recordIds),
49
+ signal
50
+ );
51
+ } catch {
52
+ fetchedDisplays = {};
53
+ }
54
+ if (signal?.aborted) return;
55
+ lookup.recordIdsByField.forEach((recordIds, fieldKey) => {
56
+ const fieldDisplays = {};
57
+ recordIds.forEach((recordId) => {
58
+ fieldDisplays[recordId] = fetchedDisplays[recordId] ?? {
59
+ label: recordId,
60
+ href: buildRelationHref(lookup.relation.entityId, recordId)
61
+ };
62
+ });
63
+ displaysByField[fieldKey] = fieldDisplays;
64
+ });
65
+ }));
66
+ return displaysByField;
67
+ }
68
+ function getRecordListRelationDisplays(value, displays) {
69
+ return collectRelationValueIds(value).map((recordId) => ({
70
+ id: recordId,
71
+ label: displays?.[recordId]?.label ?? recordId,
72
+ href: displays?.[recordId]?.href
73
+ }));
74
+ }
75
+ export {
76
+ getRecordListRelationDisplays,
77
+ resolveRecordListRelationDisplays,
78
+ resolveRelationOptionsUrl
79
+ };
80
+ //# sourceMappingURL=recordListRelationDisplays.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/entities/components/recordListRelationDisplays.ts"],
4
+ "sourcesContent": ["import type { CustomFieldDefDto } from '@open-mercato/ui/backend/utils/customFieldDefs'\nimport {\n buildRelationHref,\n collectRelationValueIds,\n fetchRelationRecordDisplays,\n parseRelationOptionsMetadata,\n type RelationOptionsMetadata,\n type ResolvedValueDisplay,\n} from '@open-mercato/ui/backend/utils/customFieldRelationDisplay'\n\nexport type RelationDisplaysByField = Record<string, Record<string, ResolvedValueDisplay>>\n\ntype RelationLookup = {\n optionsUrl: string\n relation: RelationOptionsMetadata\n recordIds: Set<string>\n recordIdsByField: Map<string, Set<string>>\n}\n\nexport function resolveRelationOptionsUrl(definition: CustomFieldDefDto): string | null {\n if (definition.kind !== 'relation') return null\n\n const configuredUrl = definition.optionsUrl?.trim()\n if (configuredUrl && parseRelationOptionsMetadata(configuredUrl)) return configuredUrl\n\n const relatedEntityId = definition.relatedEntityId?.trim()\n if (!relatedEntityId) return null\n return `/api/entities/relations/options?entityId=${encodeURIComponent(relatedEntityId)}`\n}\n\nfunction collectRelationLookups(\n definitions: CustomFieldDefDto[],\n records: Array<Record<string, unknown>>,\n): RelationLookup[] {\n const lookups = new Map<string, RelationLookup>()\n\n definitions.forEach((definition) => {\n const optionsUrl = resolveRelationOptionsUrl(definition)\n if (!optionsUrl) return\n const relation = parseRelationOptionsMetadata(optionsUrl)\n if (!relation) return\n\n const recordIds = new Set(\n records.flatMap((record) => collectRelationValueIds(record[definition.key])),\n )\n if (!recordIds.size) return\n\n const lookupKey = JSON.stringify([relation.entityId, optionsUrl])\n const lookup = lookups.get(lookupKey) ?? {\n optionsUrl,\n relation,\n recordIds: new Set<string>(),\n recordIdsByField: new Map<string, Set<string>>(),\n }\n recordIds.forEach((recordId) => lookup.recordIds.add(recordId))\n lookup.recordIdsByField.set(definition.key, recordIds)\n lookups.set(lookupKey, lookup)\n })\n\n return Array.from(lookups.values())\n}\n\nexport async function resolveRecordListRelationDisplays(\n definitions: CustomFieldDefDto[],\n records: Array<Record<string, unknown>>,\n signal?: AbortSignal,\n): Promise<RelationDisplaysByField> {\n const displaysByField: RelationDisplaysByField = {}\n const lookups = collectRelationLookups(definitions, records)\n\n await Promise.all(lookups.map(async (lookup) => {\n let fetchedDisplays: Record<string, ResolvedValueDisplay> = {}\n try {\n fetchedDisplays = await fetchRelationRecordDisplays(\n lookup.optionsUrl,\n lookup.relation,\n Array.from(lookup.recordIds),\n signal,\n )\n } catch {\n fetchedDisplays = {}\n }\n\n if (signal?.aborted) return\n lookup.recordIdsByField.forEach((recordIds, fieldKey) => {\n const fieldDisplays: Record<string, ResolvedValueDisplay> = {}\n recordIds.forEach((recordId) => {\n fieldDisplays[recordId] = fetchedDisplays[recordId] ?? {\n label: recordId,\n href: buildRelationHref(lookup.relation.entityId, recordId),\n }\n })\n displaysByField[fieldKey] = fieldDisplays\n })\n }))\n\n return displaysByField\n}\n\nexport function getRecordListRelationDisplays(\n value: unknown,\n displays: Record<string, ResolvedValueDisplay> | undefined,\n): Array<{ id: string; label: string; href?: string }> {\n return collectRelationValueIds(value).map((recordId) => ({\n id: recordId,\n label: displays?.[recordId]?.label ?? recordId,\n href: displays?.[recordId]?.href,\n }))\n}\n"],
5
+ "mappings": "AACA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAWA,SAAS,0BAA0B,YAA8C;AACtF,MAAI,WAAW,SAAS,WAAY,QAAO;AAE3C,QAAM,gBAAgB,WAAW,YAAY,KAAK;AAClD,MAAI,iBAAiB,6BAA6B,aAAa,EAAG,QAAO;AAEzE,QAAM,kBAAkB,WAAW,iBAAiB,KAAK;AACzD,MAAI,CAAC,gBAAiB,QAAO;AAC7B,SAAO,4CAA4C,mBAAmB,eAAe,CAAC;AACxF;AAEA,SAAS,uBACP,aACA,SACkB;AAClB,QAAM,UAAU,oBAAI,IAA4B;AAEhD,cAAY,QAAQ,CAAC,eAAe;AAClC,UAAM,aAAa,0BAA0B,UAAU;AACvD,QAAI,CAAC,WAAY;AACjB,UAAM,WAAW,6BAA6B,UAAU;AACxD,QAAI,CAAC,SAAU;AAEf,UAAM,YAAY,IAAI;AAAA,MACpB,QAAQ,QAAQ,CAAC,WAAW,wBAAwB,OAAO,WAAW,GAAG,CAAC,CAAC;AAAA,IAC7E;AACA,QAAI,CAAC,UAAU,KAAM;AAErB,UAAM,YAAY,KAAK,UAAU,CAAC,SAAS,UAAU,UAAU,CAAC;AAChE,UAAM,SAAS,QAAQ,IAAI,SAAS,KAAK;AAAA,MACvC;AAAA,MACA;AAAA,MACA,WAAW,oBAAI,IAAY;AAAA,MAC3B,kBAAkB,oBAAI,IAAyB;AAAA,IACjD;AACA,cAAU,QAAQ,CAAC,aAAa,OAAO,UAAU,IAAI,QAAQ,CAAC;AAC9D,WAAO,iBAAiB,IAAI,WAAW,KAAK,SAAS;AACrD,YAAQ,IAAI,WAAW,MAAM;AAAA,EAC/B,CAAC;AAED,SAAO,MAAM,KAAK,QAAQ,OAAO,CAAC;AACpC;AAEA,eAAsB,kCACpB,aACA,SACA,QACkC;AAClC,QAAM,kBAA2C,CAAC;AAClD,QAAM,UAAU,uBAAuB,aAAa,OAAO;AAE3D,QAAM,QAAQ,IAAI,QAAQ,IAAI,OAAO,WAAW;AAC9C,QAAI,kBAAwD,CAAC;AAC7D,QAAI;AACF,wBAAkB,MAAM;AAAA,QACtB,OAAO;AAAA,QACP,OAAO;AAAA,QACP,MAAM,KAAK,OAAO,SAAS;AAAA,QAC3B;AAAA,MACF;AAAA,IACF,QAAQ;AACN,wBAAkB,CAAC;AAAA,IACrB;AAEA,QAAI,QAAQ,QAAS;AACrB,WAAO,iBAAiB,QAAQ,CAAC,WAAW,aAAa;AACvD,YAAM,gBAAsD,CAAC;AAC7D,gBAAU,QAAQ,CAAC,aAAa;AAC9B,sBAAc,QAAQ,IAAI,gBAAgB,QAAQ,KAAK;AAAA,UACrD,OAAO;AAAA,UACP,MAAM,kBAAkB,OAAO,SAAS,UAAU,QAAQ;AAAA,QAC5D;AAAA,MACF,CAAC;AACD,sBAAgB,QAAQ,IAAI;AAAA,IAC9B,CAAC;AAAA,EACH,CAAC,CAAC;AAEF,SAAO;AACT;AAEO,SAAS,8BACd,OACA,UACqD;AACrD,SAAO,wBAAwB,KAAK,EAAE,IAAI,CAAC,cAAc;AAAA,IACvD,IAAI;AAAA,IACJ,OAAO,WAAW,QAAQ,GAAG,SAAS;AAAA,IACtC,MAAM,WAAW,QAAQ,GAAG;AAAA,EAC9B,EAAE;AACJ;",
6
+ "names": []
7
+ }
@@ -1,5 +1,7 @@
1
1
  import { NextResponse } from "next/server";
2
2
  import { runWithCacheTenant } from "@open-mercato/cache";
3
+ import { enforceCommandOptimisticLockWithGuards } from "@open-mercato/shared/lib/crud/optimistic-lock-command";
4
+ import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
3
5
  import {
4
6
  runCrudMutationGuardAfterSuccess,
5
7
  validateCrudMutationGuard
@@ -32,6 +34,17 @@ async function PATCH(req) {
32
34
  if (action.status !== "pending" && action.status !== "failed") {
33
35
  return NextResponse.json({ error: "Action already processed" }, { status: 409 });
34
36
  }
37
+ try {
38
+ await enforceCommandOptimisticLockWithGuards(ctx.container, {
39
+ resourceKind: "inbox_ops.action",
40
+ resourceId: action.id,
41
+ current: action.updatedAt ?? null,
42
+ request: req
43
+ });
44
+ } catch (err) {
45
+ if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status });
46
+ throw err;
47
+ }
35
48
  const mergedPayload = { ...action.payload, ...parsed.data.payload };
36
49
  const payloadValidation = validateActionPayloadForType(action.actionType, mergedPayload);
37
50
  if (!payloadValidation.success) {
@@ -93,7 +106,7 @@ const openApi = {
93
106
  responses: [
94
107
  { status: 200, description: "Action updated" },
95
108
  { status: 404, description: "Action not found" },
96
- { status: 409, description: "Action already processed" }
109
+ { status: 409, description: "Action already processed or stale (optimistic-lock conflict)" }
97
110
  ]
98
111
  }
99
112
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../../src/modules/inbox_ops/api/proposals/%5Bid%5D/actions/%5BactionId%5D/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { runWithCacheTenant } from '@open-mercato/cache'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { actionEditSchema, validateActionPayloadForType } from '../../../../../data/validators'\nimport { emitInboxOpsEvent } from '../../../../../events'\nimport { resolveCache, invalidateCountsCache } from '../../../../../lib/cache'\nimport {\n resolveRequestContext,\n resolveActionAndProposal,\n handleRouteError,\n isErrorResponse,\n} from '../../../../routeHelpers'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('inbox_ops').child({ component: 'action-edit' })\n\nexport const metadata = {\n PATCH: { requireAuth: true, requireFeatures: ['inbox_ops.proposals.manage'] },\n}\n\nexport async function PATCH(req: Request) {\n try {\n const body = await req.json()\n const parsed = actionEditSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.issues }, { status: 400 })\n }\n\n const ctx = await resolveRequestContext(req)\n const resolved = await resolveActionAndProposal(new URL(req.url), ctx)\n if (isErrorResponse(resolved)) return resolved\n\n const { action } = resolved\n\n if (action.status !== 'pending' && action.status !== 'failed') {\n return NextResponse.json({ error: 'Action already processed' }, { status: 409 })\n }\n\n const mergedPayload = { ...action.payload as Record<string, unknown>, ...parsed.data.payload }\n const payloadValidation = validateActionPayloadForType(action.actionType, mergedPayload)\n if (!payloadValidation.success) {\n return NextResponse.json({ error: payloadValidation.error }, { status: 400 })\n }\n\n const guardResult = await validateCrudMutationGuard(ctx.container, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n userId: ctx.userId,\n resourceKind: 'inbox_ops:inbox_proposal_action',\n resourceId: action.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: parsed.data,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n action.payload = mergedPayload\n await ctx.em.flush()\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(ctx.container, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n userId: ctx.userId,\n resourceKind: 'inbox_ops:inbox_proposal_action',\n resourceId: action.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n const cache = resolveCache(ctx.container)\n await runWithCacheTenant(ctx.tenantId, () => invalidateCountsCache(cache, ctx.tenantId))\n\n try {\n await emitInboxOpsEvent('inbox_ops.action.edited', {\n actionId: action.id,\n proposalId: action.proposalId,\n actionType: action.actionType,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n })\n } catch (eventError) {\n logger.error('Failed to emit event', { err: eventError })\n }\n\n return NextResponse.json({ ok: true, action })\n } catch (err) {\n return handleRouteError(err, 'edit action')\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'InboxOps',\n summary: 'Edit action',\n methods: {\n PATCH: {\n summary: 'Edit action payload before accepting',\n responses: [\n { status: 200, description: 'Action updated' },\n { status: 404, description: 'Action not found' },\n { status: 409, description: 'Action already processed' },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { runWithCacheTenant } from '@open-mercato/cache'\nimport { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { actionEditSchema, validateActionPayloadForType } from '../../../../../data/validators'\nimport { emitInboxOpsEvent } from '../../../../../events'\nimport { resolveCache, invalidateCountsCache } from '../../../../../lib/cache'\nimport {\n resolveRequestContext,\n resolveActionAndProposal,\n handleRouteError,\n isErrorResponse,\n} from '../../../../routeHelpers'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('inbox_ops').child({ component: 'action-edit' })\n\nexport const metadata = {\n PATCH: { requireAuth: true, requireFeatures: ['inbox_ops.proposals.manage'] },\n}\n\nexport async function PATCH(req: Request) {\n try {\n const body = await req.json()\n const parsed = actionEditSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.issues }, { status: 400 })\n }\n\n const ctx = await resolveRequestContext(req)\n const resolved = await resolveActionAndProposal(new URL(req.url), ctx)\n if (isErrorResponse(resolved)) return resolved\n\n const { action } = resolved\n\n if (action.status !== 'pending' && action.status !== 'failed') {\n return NextResponse.json({ error: 'Action already processed' }, { status: 409 })\n }\n\n // Optimistic lock: refuse a stale overwrite when two tabs edit the same action\n // payload. Strictly additive \u2014 a no-op without the expected-version header.\n try {\n await enforceCommandOptimisticLockWithGuards(ctx.container, {\n resourceKind: 'inbox_ops.action',\n resourceId: action.id,\n current: action.updatedAt ?? null,\n request: req,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n throw err\n }\n\n const mergedPayload = { ...action.payload as Record<string, unknown>, ...parsed.data.payload }\n const payloadValidation = validateActionPayloadForType(action.actionType, mergedPayload)\n if (!payloadValidation.success) {\n return NextResponse.json({ error: payloadValidation.error }, { status: 400 })\n }\n\n const guardResult = await validateCrudMutationGuard(ctx.container, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n userId: ctx.userId,\n resourceKind: 'inbox_ops:inbox_proposal_action',\n resourceId: action.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: parsed.data,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n action.payload = mergedPayload\n await ctx.em.flush()\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(ctx.container, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n userId: ctx.userId,\n resourceKind: 'inbox_ops:inbox_proposal_action',\n resourceId: action.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n const cache = resolveCache(ctx.container)\n await runWithCacheTenant(ctx.tenantId, () => invalidateCountsCache(cache, ctx.tenantId))\n\n try {\n await emitInboxOpsEvent('inbox_ops.action.edited', {\n actionId: action.id,\n proposalId: action.proposalId,\n actionType: action.actionType,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n })\n } catch (eventError) {\n logger.error('Failed to emit event', { err: eventError })\n }\n\n return NextResponse.json({ ok: true, action })\n } catch (err) {\n return handleRouteError(err, 'edit action')\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'InboxOps',\n summary: 'Edit action',\n methods: {\n PATCH: {\n summary: 'Edit action payload before accepting',\n responses: [\n { status: 200, description: 'Action updated' },\n { status: 404, description: 'Action not found' },\n { status: 409, description: 'Action already processed or stale (optimistic-lock conflict)' },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -9,8 +9,8 @@ import {
9
9
  SelectTrigger,
10
10
  SelectValue
11
11
  } from "@open-mercato/ui/primitives/select";
12
- import { apiCall, withScopedApiRequestHeaders } from "@open-mercato/ui/backend/utils/apiCall";
13
- import { buildOptimisticLockHeader } from "@open-mercato/ui/backend/utils/optimisticLock";
12
+ import { apiCallOrThrow, withScopedApiRequestHeaders } from "@open-mercato/ui/backend/utils/apiCall";
13
+ import { buildOptimisticLockHeader, extractOptimisticLockConflict } from "@open-mercato/ui/backend/utils/optimisticLock";
14
14
  import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuardedMutation";
15
15
  import { flash } from "@open-mercato/ui/backend/FlashMessages";
16
16
  import { useT } from "@open-mercato/shared/lib/i18n/context";
@@ -426,25 +426,30 @@ function EditActionDialog({
426
426
  }
427
427
  }
428
428
  setIsSaving(true);
429
- const result = await runMutation({
430
- // TODO(#2373-D): thread updatedAt — ActionDetail type lives outside this file's edit scope
431
- operation: () => withScopedApiRequestHeaders(
432
- buildOptimisticLockHeader(void 0),
433
- () => apiCall(
434
- `/api/inbox_ops/proposals/${action.proposalId}/actions/${action.id}`,
435
- { method: "PATCH", body: JSON.stringify({ payload: finalPayload }) }
436
- )
437
- ),
438
- context: {}
439
- });
440
- if (result?.ok && result.result?.ok) {
429
+ try {
430
+ await runMutation({
431
+ operation: () => withScopedApiRequestHeaders(
432
+ buildOptimisticLockHeader(action.updatedAt),
433
+ () => apiCallOrThrow(
434
+ `/api/inbox_ops/proposals/${action.proposalId}/actions/${action.id}`,
435
+ { method: "PATCH", body: JSON.stringify({ payload: finalPayload }) }
436
+ )
437
+ ),
438
+ context: {}
439
+ });
441
440
  flash(t("inbox_ops.edit_dialog.saved", "Action updated successfully"), "success");
442
441
  onSaved();
443
442
  onClose();
444
- } else {
445
- flash(result?.result?.error || t("inbox_ops.flash.save_failed", "Failed to save"), "error");
443
+ } catch (error) {
444
+ if (extractOptimisticLockConflict(error)) {
445
+ onClose();
446
+ return;
447
+ }
448
+ const message = error instanceof Error ? error.message.trim() : "";
449
+ flash(message || t("inbox_ops.flash.save_failed", "Failed to save"), "error");
450
+ } finally {
451
+ setIsSaving(false);
446
452
  }
447
- setIsSaving(false);
448
453
  }, [action, payload, jsonMode, jsonText, t, onSaved, onClose, runMutation]);
449
454
  React.useEffect(() => {
450
455
  function handleKeyDown(event) {