@open-mercato/core 0.6.1-develop.3246.1.dbef9d7392 → 0.6.1-develop.3260.1.dc719cac84
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/helpers/integration/authFixtures.js.map +1 -1
- package/dist/helpers/integration/authUi.js +10 -2
- package/dist/helpers/integration/authUi.js.map +2 -2
- package/dist/helpers/integration/salesUi.js +3 -1
- package/dist/helpers/integration/salesUi.js.map +2 -2
- package/dist/modules/api_docs/frontend/docs/api/Explorer.js +10 -1
- package/dist/modules/api_docs/frontend/docs/api/Explorer.js.map +2 -2
- package/dist/modules/audit_logs/lib/display-helpers.js +28 -27
- package/dist/modules/audit_logs/lib/display-helpers.js.map +2 -2
- package/dist/modules/auth/api/users/route.js +21 -3
- package/dist/modules/auth/api/users/route.js.map +2 -2
- package/dist/modules/auth/backend/users/[id]/edit/page.js +6 -1
- package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
- package/dist/modules/auth/backend/users/create/page.js +4 -1
- package/dist/modules/auth/backend/users/create/page.js.map +2 -2
- package/dist/modules/auth/backend/users/page.js +56 -2
- package/dist/modules/auth/backend/users/page.js.map +3 -3
- package/dist/modules/auth/commands/users.js +14 -0
- package/dist/modules/auth/commands/users.js.map +2 -2
- package/dist/modules/auth/data/validators.js +8 -0
- package/dist/modules/auth/data/validators.js.map +2 -2
- package/dist/modules/catalog/ai-agents.js +4 -0
- package/dist/modules/catalog/ai-agents.js.map +2 -2
- package/dist/modules/customers/ai-agents.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +1 -1
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/page.js +15 -9
- package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js +26 -3
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +1 -1
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/components/PipelineSettings.js +108 -99
- package/dist/modules/customers/components/PipelineSettings.js.map +2 -2
- package/dist/modules/customers/components/detail/AssignRoleDialog.js +13 -5
- package/dist/modules/customers/components/detail/AssignRoleDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/EntityTagsDialog.js +10 -2
- package/dist/modules/customers/components/detail/EntityTagsDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/PersonCompaniesSection.js +15 -8
- package/dist/modules/customers/components/detail/PersonCompaniesSection.js.map +2 -2
- package/dist/modules/customers/components/detail/RolesSection.js +35 -22
- package/dist/modules/customers/components/detail/RolesSection.js.map +2 -2
- package/dist/modules/customers/components/detail/TasksSection.js +2 -2
- package/dist/modules/customers/components/detail/TasksSection.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/DateTimeFields.js +73 -64
- package/dist/modules/customers/components/detail/schedule/DateTimeFields.js.map +2 -2
- package/dist/modules/customers/components/linking/LinkEntityDialog.js +11 -2
- package/dist/modules/customers/components/linking/LinkEntityDialog.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/page.js +2 -9
- package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js +14 -29
- package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js.map +2 -2
- package/dist/modules/dictionaries/components/DictionariesManager.js +10 -2
- package/dist/modules/dictionaries/components/DictionariesManager.js.map +2 -2
- package/dist/modules/entities/components/EncryptionManager.js +40 -17
- package/dist/modules/entities/components/EncryptionManager.js.map +2 -2
- package/dist/modules/inbox_ops/lib/llmProvider.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/[id]/page.js +79 -95
- package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
- package/dist/modules/payment_gateways/backend/payment-gateways/page.js +49 -70
- package/dist/modules/payment_gateways/backend/payment-gateways/page.js.map +2 -2
- package/dist/modules/planner/components/AvailabilityRuleSetForm.js +1 -1
- package/dist/modules/planner/components/AvailabilityRuleSetForm.js.map +1 -1
- package/dist/modules/planner/components/AvailabilityRulesEditor.js +19 -19
- package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
- package/dist/modules/resources/components/ResourceCrudForm.js +1 -1
- package/dist/modules/resources/components/ResourceCrudForm.js.map +1 -1
- package/dist/modules/resources/components/ResourceTypeCrudForm.js +1 -1
- package/dist/modules/resources/components/ResourceTypeCrudForm.js.map +2 -2
- package/dist/modules/sales/components/documents/ReturnDialog.js +11 -8
- package/dist/modules/sales/components/documents/ReturnDialog.js.map +2 -2
- package/dist/modules/staff/backend/staff/profile/create/page.js +1 -1
- package/dist/modules/staff/backend/staff/profile/create/page.js.map +2 -2
- package/dist/modules/staff/components/TeamForm.js +1 -1
- package/dist/modules/staff/components/TeamForm.js.map +2 -2
- package/dist/modules/staff/components/TeamMemberForm.js +1 -1
- package/dist/modules/staff/components/TeamMemberForm.js.map +1 -1
- package/dist/modules/staff/components/TeamRoleForm.js +1 -1
- package/dist/modules/staff/components/TeamRoleForm.js.map +2 -2
- package/package.json +5 -5
- package/src/helpers/integration/authFixtures.ts +1 -1
- package/src/helpers/integration/authUi.ts +11 -2
- package/src/helpers/integration/salesUi.ts +10 -1
- package/src/modules/api_docs/frontend/docs/api/Explorer.tsx +7 -3
- package/src/modules/audit_logs/lib/display-helpers.tsx +22 -14
- package/src/modules/auth/api/users/route.ts +22 -3
- package/src/modules/auth/backend/users/[id]/edit/page.tsx +9 -1
- package/src/modules/auth/backend/users/create/page.tsx +6 -2
- package/src/modules/auth/backend/users/page.tsx +60 -3
- package/src/modules/auth/commands/users.ts +15 -0
- package/src/modules/auth/data/validators.ts +8 -0
- package/src/modules/auth/i18n/de.json +4 -0
- package/src/modules/auth/i18n/en.json +4 -0
- package/src/modules/auth/i18n/es.json +4 -0
- package/src/modules/auth/i18n/pl.json +4 -0
- package/src/modules/catalog/ai-agents.ts +8 -49
- package/src/modules/customers/ai-agents.ts +4 -51
- package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +1 -1
- package/src/modules/customers/backend/customers/deals/[id]/page.tsx +12 -13
- package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +17 -13
- package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +1 -1
- package/src/modules/customers/components/PipelineSettings.tsx +95 -95
- package/src/modules/customers/components/detail/AssignRoleDialog.tsx +7 -4
- package/src/modules/customers/components/detail/EntityTagsDialog.tsx +13 -8
- package/src/modules/customers/components/detail/PersonCompaniesSection.tsx +16 -12
- package/src/modules/customers/components/detail/RolesSection.tsx +34 -31
- package/src/modules/customers/components/detail/TasksSection.tsx +16 -8
- package/src/modules/customers/components/detail/schedule/DateTimeFields.tsx +77 -51
- package/src/modules/customers/components/linking/LinkEntityDialog.tsx +7 -3
- package/src/modules/data_sync/backend/data-sync/page.tsx +4 -7
- package/src/modules/data_sync/backend/data-sync/runs/[id]/page.tsx +19 -54
- package/src/modules/dictionaries/components/DictionariesManager.tsx +7 -4
- package/src/modules/entities/components/EncryptionManager.tsx +50 -21
- package/src/modules/inbox_ops/lib/llmProvider.ts +10 -0
- package/src/modules/integrations/backend/integrations/[id]/page.tsx +101 -146
- package/src/modules/payment_gateways/backend/payment-gateways/page.tsx +60 -103
- package/src/modules/planner/components/AvailabilityRuleSetForm.tsx +1 -1
- package/src/modules/planner/components/AvailabilityRulesEditor.tsx +17 -17
- package/src/modules/resources/components/ResourceCrudForm.tsx +1 -1
- package/src/modules/resources/components/ResourceTypeCrudForm.tsx +1 -1
- package/src/modules/sales/components/documents/ReturnDialog.tsx +10 -7
- package/src/modules/sales/i18n/de.json +2 -0
- package/src/modules/sales/i18n/en.json +2 -0
- package/src/modules/sales/i18n/es.json +2 -0
- package/src/modules/sales/i18n/pl.json +2 -0
- package/src/modules/staff/backend/staff/profile/create/page.tsx +1 -1
- package/src/modules/staff/components/TeamForm.tsx +1 -1
- package/src/modules/staff/components/TeamMemberForm.tsx +1 -1
- package/src/modules/staff/components/TeamRoleForm.tsx +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/dictionaries/components/DictionariesManager.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useSearchParams } from 'next/navigation'\nimport { Plus, Pencil, Trash2 } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@open-mercato/ui/primitives/dialog'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { DictionaryEntriesEditor } from './DictionaryEntriesEditor'\n\nexport type DictionarySummary = {\n id: string\n key: string\n name: string\n description?: string | null\n isSystem?: boolean\n isActive?: boolean\n organizationId: string\n isInherited: boolean\n managerVisibility: 'default' | 'hidden'\n}\n\ntype DialogState = {\n mode: 'create' | 'edit'\n dictionary?: DictionarySummary\n}\n\nexport function DictionariesManager() {\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const searchParams = useSearchParams()\n const [items, setItems] = React.useState<DictionarySummary[]>([])\n const [selectedId, setSelectedId] = React.useState<string | null>(null)\n const [loading, setLoading] = React.useState(true)\n const [dialog, setDialog] = React.useState<DialogState | null>(null)\n const [form, setForm] = React.useState({ key: '', name: '', description: '' })\n const [errors, setErrors] = React.useState<{ key?: string; name?: string }>({})\n const [submitting, setSubmitting] = React.useState(false)\n const [deleting, setDeleting] = React.useState<string | null>(null)\n const selectionFromQueryApplied = React.useRef(false)\n const inheritedManageMessage = t('dictionaries.config.error.inheritedManage', 'Inherited dictionaries must be managed at the parent organization.')\n const requestedDictionaryId = searchParams?.get('dictionaryId') ?? null\n const requestedDictionaryKey = searchParams?.get('key')?.trim().toLowerCase() ?? null\n\n const loadDictionaries = React.useCallback(async () => {\n setLoading(true)\n try {\n const call = await apiCall<{ items?: unknown[]; error?: string }>('/api/dictionaries')\n if (!call.ok) {\n throw new Error(typeof call.result?.error === 'string' ? call.result.error : 'Failed to load dictionaries')\n }\n const resultItems = Array.isArray(call.result?.items) ? call.result!.items : []\n const list: DictionarySummary[] = Array.isArray(resultItems)\n ? resultItems.map((item: any): DictionarySummary => ({\n id: String(item.id),\n key: String(item.key),\n name: String(item.name ?? item.key),\n description: typeof item.description === 'string' ? item.description : null,\n isSystem: Boolean(item.isSystem),\n isActive: item.isActive !== false,\n organizationId: typeof item.organizationId === 'string' ? item.organizationId : '',\n isInherited: item.isInherited === true,\n managerVisibility:\n item.managerVisibility === 'hidden' ? 'hidden' : 'default',\n }))\n : []\n const filtered = list.filter((dictionary: DictionarySummary) => dictionary.managerVisibility !== 'hidden')\n setItems(filtered)\n if (!filtered.find((dict: DictionarySummary) => dict.id === selectedId)) {\n setSelectedId(filtered.length ? filtered[0].id : null)\n }\n } catch (err) {\n console.error('Failed to load dictionaries', err)\n flash(t('dictionaries.config.error.load', 'Failed to load dictionaries.'), 'error')\n } finally {\n setLoading(false)\n }\n }, [selectedId, t])\n\n React.useEffect(() => {\n loadDictionaries().catch(() => {})\n }, [loadDictionaries])\n\n React.useEffect(() => {\n if (selectionFromQueryApplied.current) return\n if (!items.length) return\n if (!requestedDictionaryId && !requestedDictionaryKey) return\n if (requestedDictionaryId) {\n const match = items.find((dictionary) => dictionary.id === requestedDictionaryId)\n if (match && selectedId !== match.id) {\n setSelectedId(match.id)\n }\n selectionFromQueryApplied.current = true\n return\n }\n if (requestedDictionaryKey) {\n const match = items.find((dictionary) => dictionary.key.toLowerCase() === requestedDictionaryKey)\n if (match && selectedId !== match.id) {\n setSelectedId(match.id)\n }\n selectionFromQueryApplied.current = true\n }\n }, [items, requestedDictionaryId, requestedDictionaryKey, selectedId])\n\n const openCreateDialog = React.useCallback(() => {\n setForm({ key: '', name: '', description: '' })\n setDialog({ mode: 'create' })\n setErrors({})\n }, [])\n\n const openEditDialog = React.useCallback((dictionary: DictionarySummary) => {\n if (dictionary.isInherited) {\n flash(inheritedManageMessage, 'info')\n return\n }\n setForm({ key: dictionary.key, name: dictionary.name, description: dictionary.description ?? '' })\n setDialog({ mode: 'edit', dictionary })\n setErrors({})\n }, [inheritedManageMessage])\n\n const closeDialog = React.useCallback(() => {\n setDialog(null)\n setForm({ key: '', name: '', description: '' })\n setErrors({})\n }, [])\n\n const handleSubmit = React.useCallback(async () => {\n if (!dialog) return\n if (dialog.mode === 'edit' && dialog.dictionary?.isInherited) {\n flash(inheritedManageMessage, 'info')\n return\n }\n const trimmedKey = form.key.trim()\n const trimmedName = form.name.trim()\n const nextErrors: { key?: string; name?: string } = {}\n if (!trimmedKey) {\n nextErrors.key = t('dictionaries.config.dialog.keyErrorRequired', 'Key is required.')\n } else if (trimmedKey.length > 100) {\n nextErrors.key = t('dictionaries.config.dialog.keyErrorLength', 'Key must be at most 100 characters long.')\n } else if (!/^[a-z0-9][a-z0-9_-]*$/.test(trimmedKey)) {\n nextErrors.key = t('dictionaries.config.dialog.keyErrorPattern', 'Use lowercase letters, numbers, hyphen, or underscore.')\n }\n if (!trimmedName) {\n nextErrors.name = t('dictionaries.config.dialog.nameErrorRequired', 'Name is required.')\n }\n if (nextErrors.key || nextErrors.name) {\n setErrors(nextErrors)\n return\n }\n setSubmitting(true)\n try {\n const payload = {\n key: trimmedKey,\n name: trimmedName,\n description: form.description.trim() || undefined,\n }\n if (dialog.mode === 'create') {\n const call = await apiCall<Record<string, unknown>>('/api/dictionaries', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n })\n if (!call.ok) {\n throw new Error(typeof call.result?.error === 'string' ? call.result.error : 'Failed to create dictionary')\n }\n flash(t('dictionaries.config.success.create', 'Dictionary created.'), 'success')\n } else if (dialog.dictionary) {\n const call = await apiCall<Record<string, unknown>>(`/api/dictionaries/${dialog.dictionary.id}`, {\n method: 'PATCH',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n })\n if (!call.ok) {\n throw new Error(typeof call.result?.error === 'string' ? call.result.error : 'Failed to update dictionary')\n }\n flash(t('dictionaries.config.success.update', 'Dictionary updated.'), 'success')\n }\n closeDialog()\n await loadDictionaries()\n setErrors({})\n } catch (err) {\n console.error('Failed to save dictionary', err)\n flash(t('dictionaries.config.error.save', 'Failed to save dictionary.'), 'error')\n } finally {\n setSubmitting(false)\n }\n }, [closeDialog, dialog, form.description, form.key, form.name, inheritedManageMessage, loadDictionaries, t])\n\n const handleDelete = React.useCallback(\n async (dictionary: DictionarySummary) => {\n if (dictionary.isInherited) {\n flash(inheritedManageMessage, 'info')\n return\n }\n if (dictionary.isSystem) {\n flash(t('dictionaries.config.error.system', 'System dictionaries cannot be deleted.'), 'error')\n return\n }\n const rawConfirm = t('dictionaries.config.delete.confirm', { name: dictionary.name })\n const confirmMessage = rawConfirm && rawConfirm !== 'dictionaries.config.delete.confirm'\n ? rawConfirm\n : `Delete dictionary \"${dictionary.name}\"?`\n const confirmed = await confirm({\n title: confirmMessage,\n variant: 'destructive',\n })\n if (!confirmed) return\n setDeleting(dictionary.id)\n try {\n const call = await apiCall<Record<string, unknown>>(`/api/dictionaries/${dictionary.id}`, { method: 'DELETE' })\n if (!call.ok) {\n throw new Error(typeof call.result?.error === 'string' ? call.result.error : 'Failed to delete dictionary')\n }\n flash(t('dictionaries.config.success.delete', 'Dictionary deleted.'), 'success')\n await loadDictionaries()\n } catch (err) {\n console.error('Failed to delete dictionary', err)\n flash(t('dictionaries.config.error.delete', 'Failed to delete dictionary.'), 'error')\n } finally {\n setDeleting(null)\n }\n },\n [confirm, inheritedManageMessage, loadDictionaries, t],\n )\n\n const selectedDictionary = items.find((item) => item.id === selectedId) ?? null\n\n return (\n <div className=\"grid gap-6 lg:grid-cols-[320px_1fr]\">\n <div className=\"rounded-lg border bg-card p-4 shadow-sm\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"text-base font-semibold\">\n {t('dictionaries.config.list.title', 'Dictionaries')}\n </h2>\n <Button type=\"button\" size=\"sm\" onClick={openCreateDialog}>\n <Plus className=\"mr-2 h-4 w-4\" />\n {t('dictionaries.config.list.add', 'New dictionary')}\n </Button>\n </div>\n <div className=\"mt-4 space-y-2\">\n {loading ? (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <Spinner className=\"h-4 w-4\" />\n {t('dictionaries.config.list.loading', 'Loading dictionaries\u2026')}\n </div>\n ) : items.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('dictionaries.config.list.empty', 'No dictionaries yet. Create one to get started.')}\n </p>\n ) : (\n <ul className=\"space-y-1\">\n {items.map((dictionary) => (\n <li key={dictionary.id}>\n <div\n role=\"button\"\n tabIndex={0}\n aria-pressed={dictionary.id === selectedId}\n className={`flex w-full cursor-pointer select-none items-center justify-between rounded border px-3 py-2 text-left text-sm transition ${\n dictionary.id === selectedId ? 'border-primary bg-primary/5 text-primary' : 'border-border hover:bg-muted'\n }`}\n onClick={() => setSelectedId(dictionary.id)}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n setSelectedId(dictionary.id)\n }\n }}\n >\n <div>\n <div className=\"flex items-center gap-2 font-medium\">\n <span>{dictionary.name}</span>\n {dictionary.isInherited ? (\n <span className=\"rounded-full border border-border px-2 py-0.5 text-overline font-normal uppercase tracking-wide text-muted-foreground\">\n {t('dictionaries.config.list.inherited', 'Inherited')}\n </span>\n ) : null}\n </div>\n <div className=\"text-xs text-muted-foreground\">{dictionary.key}</div>\n </div>\n <div className=\"flex items-center gap-2\">\n <Button\n type=\"button\"\n size=\"icon\"\n variant=\"ghost\"\n disabled={dictionary.isInherited}\n title={dictionary.isInherited ? inheritedManageMessage : undefined}\n onClick={(event) => {\n event.stopPropagation()\n openEditDialog(dictionary)\n }}\n >\n <Pencil className=\"h-4 w-4\" />\n </Button>\n <Button\n type=\"button\"\n size=\"icon\"\n variant=\"ghost\"\n disabled={dictionary.isInherited || deleting === dictionary.id}\n title={dictionary.isInherited ? inheritedManageMessage : undefined}\n onClick={(event) => {\n event.stopPropagation()\n handleDelete(dictionary)\n }}\n >\n <Trash2 className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n </li>\n ))}\n </ul>\n )}\n </div>\n </div>\n <div>\n {selectedDictionary ? (\n <DictionaryEntriesEditor\n dictionaryId={selectedDictionary.id}\n dictionaryName={selectedDictionary.name}\n readOnly={selectedDictionary.isInherited}\n />\n ) : (\n <div className=\"flex h-full flex-col items-center justify-center rounded border border-dashed p-10 text-center text-sm text-muted-foreground\">\n {t('dictionaries.config.entries.placeholder', 'Select a dictionary to manage its entries.')}\n </div>\n )}\n </div>\n\n <Dialog open={dialog != null} onOpenChange={(open) => (open ? undefined : closeDialog())}>\n <DialogContent>\n <DialogHeader>\n <DialogTitle>\n {dialog?.mode === 'create'\n ? t('dictionaries.config.dialog.createTitle', 'Create dictionary')\n : t('dictionaries.config.dialog.editTitle', 'Edit dictionary')}\n </DialogTitle>\n </DialogHeader>\n <div className=\"space-y-4\">\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">{t('dictionaries.config.dialog.keyLabel', 'Key')}</label>\n <input\n value={form.key}\n onChange={(event) => {\n const next = event.target.value\n setForm((prev) => ({ ...prev, key: next }))\n if (errors.key) setErrors((prev) => ({ ...prev, key: undefined }))\n }}\n placeholder={t('dictionaries.config.dialog.keyPlaceholder', 'slug_name')}\n disabled={dialog?.mode === 'edit'}\n className={`w-full rounded border px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:bg-muted ${errors.key ? 'border-destructive focus-visible:ring-destructive' : ''}`}\n aria-invalid={errors.key ? 'true' : 'false'}\n aria-describedby=\"dictionary-key-hint\"\n />\n <p\n id=\"dictionary-key-hint\"\n className={`text-xs ${errors.key ? 'text-destructive' : 'text-muted-foreground'}`}\n >\n {errors.key ?? t('dictionaries.config.dialog.keyHint', 'Use lowercase letters, numbers, hyphen, or underscore.')}\n </p>\n </div>\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">{t('dictionaries.config.dialog.nameLabel', 'Name')}</label>\n <input\n value={form.name}\n onChange={(event) => {\n const next = event.target.value\n setForm((prev) => ({ ...prev, name: next }))\n if (errors.name) setErrors((prev) => ({ ...prev, name: undefined }))\n }}\n placeholder={t('dictionaries.config.dialog.namePlaceholder', 'Display name')}\n className={`w-full rounded border px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring ${errors.name ? 'border-destructive focus-visible:ring-destructive' : ''}`}\n aria-invalid={errors.name ? 'true' : 'false'}\n />\n {errors.name ? (\n <p className=\"text-xs text-destructive\">{errors.name}</p>\n ) : null}\n </div>\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">{t('dictionaries.config.dialog.descriptionLabel', 'Description')}</label>\n <textarea\n value={form.description}\n onChange={(event) => setForm((prev) => ({ ...prev, description: event.target.value }))}\n className=\"min-h-[120px] w-full rounded border px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n placeholder={t('dictionaries.config.dialog.descriptionPlaceholder', 'Explain how this dictionary is used (optional).')}\n />\n </div>\n </div>\n <DialogFooter>\n <Button type=\"button\" variant=\"ghost\" onClick={closeDialog} disabled={submitting}>\n {t('dictionaries.config.dialog.cancel', 'Cancel')}\n </Button>\n <Button type=\"button\" onClick={handleSubmit} disabled={submitting}>\n {submitting ? <Spinner className=\"mr-2 h-4 w-4\" /> : null}\n {t('dictionaries.config.dialog.save', 'Save')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n {ConfirmDialogElement}\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useSearchParams } from 'next/navigation'\nimport { Book, Plus, Pencil, Trash2 } from 'lucide-react'\nimport { EmptyState } from '@open-mercato/ui/primitives/empty-state'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@open-mercato/ui/primitives/dialog'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { DictionaryEntriesEditor } from './DictionaryEntriesEditor'\n\nexport type DictionarySummary = {\n id: string\n key: string\n name: string\n description?: string | null\n isSystem?: boolean\n isActive?: boolean\n organizationId: string\n isInherited: boolean\n managerVisibility: 'default' | 'hidden'\n}\n\ntype DialogState = {\n mode: 'create' | 'edit'\n dictionary?: DictionarySummary\n}\n\nexport function DictionariesManager() {\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const searchParams = useSearchParams()\n const [items, setItems] = React.useState<DictionarySummary[]>([])\n const [selectedId, setSelectedId] = React.useState<string | null>(null)\n const [loading, setLoading] = React.useState(true)\n const [dialog, setDialog] = React.useState<DialogState | null>(null)\n const [form, setForm] = React.useState({ key: '', name: '', description: '' })\n const [errors, setErrors] = React.useState<{ key?: string; name?: string }>({})\n const [submitting, setSubmitting] = React.useState(false)\n const [deleting, setDeleting] = React.useState<string | null>(null)\n const selectionFromQueryApplied = React.useRef(false)\n const inheritedManageMessage = t('dictionaries.config.error.inheritedManage', 'Inherited dictionaries must be managed at the parent organization.')\n const requestedDictionaryId = searchParams?.get('dictionaryId') ?? null\n const requestedDictionaryKey = searchParams?.get('key')?.trim().toLowerCase() ?? null\n\n const loadDictionaries = React.useCallback(async () => {\n setLoading(true)\n try {\n const call = await apiCall<{ items?: unknown[]; error?: string }>('/api/dictionaries')\n if (!call.ok) {\n throw new Error(typeof call.result?.error === 'string' ? call.result.error : 'Failed to load dictionaries')\n }\n const resultItems = Array.isArray(call.result?.items) ? call.result!.items : []\n const list: DictionarySummary[] = Array.isArray(resultItems)\n ? resultItems.map((item: any): DictionarySummary => ({\n id: String(item.id),\n key: String(item.key),\n name: String(item.name ?? item.key),\n description: typeof item.description === 'string' ? item.description : null,\n isSystem: Boolean(item.isSystem),\n isActive: item.isActive !== false,\n organizationId: typeof item.organizationId === 'string' ? item.organizationId : '',\n isInherited: item.isInherited === true,\n managerVisibility:\n item.managerVisibility === 'hidden' ? 'hidden' : 'default',\n }))\n : []\n const filtered = list.filter((dictionary: DictionarySummary) => dictionary.managerVisibility !== 'hidden')\n setItems(filtered)\n if (!filtered.find((dict: DictionarySummary) => dict.id === selectedId)) {\n setSelectedId(filtered.length ? filtered[0].id : null)\n }\n } catch (err) {\n console.error('Failed to load dictionaries', err)\n flash(t('dictionaries.config.error.load', 'Failed to load dictionaries.'), 'error')\n } finally {\n setLoading(false)\n }\n }, [selectedId, t])\n\n React.useEffect(() => {\n loadDictionaries().catch(() => {})\n }, [loadDictionaries])\n\n React.useEffect(() => {\n if (selectionFromQueryApplied.current) return\n if (!items.length) return\n if (!requestedDictionaryId && !requestedDictionaryKey) return\n if (requestedDictionaryId) {\n const match = items.find((dictionary) => dictionary.id === requestedDictionaryId)\n if (match && selectedId !== match.id) {\n setSelectedId(match.id)\n }\n selectionFromQueryApplied.current = true\n return\n }\n if (requestedDictionaryKey) {\n const match = items.find((dictionary) => dictionary.key.toLowerCase() === requestedDictionaryKey)\n if (match && selectedId !== match.id) {\n setSelectedId(match.id)\n }\n selectionFromQueryApplied.current = true\n }\n }, [items, requestedDictionaryId, requestedDictionaryKey, selectedId])\n\n const openCreateDialog = React.useCallback(() => {\n setForm({ key: '', name: '', description: '' })\n setDialog({ mode: 'create' })\n setErrors({})\n }, [])\n\n const openEditDialog = React.useCallback((dictionary: DictionarySummary) => {\n if (dictionary.isInherited) {\n flash(inheritedManageMessage, 'info')\n return\n }\n setForm({ key: dictionary.key, name: dictionary.name, description: dictionary.description ?? '' })\n setDialog({ mode: 'edit', dictionary })\n setErrors({})\n }, [inheritedManageMessage])\n\n const closeDialog = React.useCallback(() => {\n setDialog(null)\n setForm({ key: '', name: '', description: '' })\n setErrors({})\n }, [])\n\n const handleSubmit = React.useCallback(async () => {\n if (!dialog) return\n if (dialog.mode === 'edit' && dialog.dictionary?.isInherited) {\n flash(inheritedManageMessage, 'info')\n return\n }\n const trimmedKey = form.key.trim()\n const trimmedName = form.name.trim()\n const nextErrors: { key?: string; name?: string } = {}\n if (!trimmedKey) {\n nextErrors.key = t('dictionaries.config.dialog.keyErrorRequired', 'Key is required.')\n } else if (trimmedKey.length > 100) {\n nextErrors.key = t('dictionaries.config.dialog.keyErrorLength', 'Key must be at most 100 characters long.')\n } else if (!/^[a-z0-9][a-z0-9_-]*$/.test(trimmedKey)) {\n nextErrors.key = t('dictionaries.config.dialog.keyErrorPattern', 'Use lowercase letters, numbers, hyphen, or underscore.')\n }\n if (!trimmedName) {\n nextErrors.name = t('dictionaries.config.dialog.nameErrorRequired', 'Name is required.')\n }\n if (nextErrors.key || nextErrors.name) {\n setErrors(nextErrors)\n return\n }\n setSubmitting(true)\n try {\n const payload = {\n key: trimmedKey,\n name: trimmedName,\n description: form.description.trim() || undefined,\n }\n if (dialog.mode === 'create') {\n const call = await apiCall<Record<string, unknown>>('/api/dictionaries', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n })\n if (!call.ok) {\n throw new Error(typeof call.result?.error === 'string' ? call.result.error : 'Failed to create dictionary')\n }\n flash(t('dictionaries.config.success.create', 'Dictionary created.'), 'success')\n } else if (dialog.dictionary) {\n const call = await apiCall<Record<string, unknown>>(`/api/dictionaries/${dialog.dictionary.id}`, {\n method: 'PATCH',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n })\n if (!call.ok) {\n throw new Error(typeof call.result?.error === 'string' ? call.result.error : 'Failed to update dictionary')\n }\n flash(t('dictionaries.config.success.update', 'Dictionary updated.'), 'success')\n }\n closeDialog()\n await loadDictionaries()\n setErrors({})\n } catch (err) {\n console.error('Failed to save dictionary', err)\n flash(t('dictionaries.config.error.save', 'Failed to save dictionary.'), 'error')\n } finally {\n setSubmitting(false)\n }\n }, [closeDialog, dialog, form.description, form.key, form.name, inheritedManageMessage, loadDictionaries, t])\n\n const handleDelete = React.useCallback(\n async (dictionary: DictionarySummary) => {\n if (dictionary.isInherited) {\n flash(inheritedManageMessage, 'info')\n return\n }\n if (dictionary.isSystem) {\n flash(t('dictionaries.config.error.system', 'System dictionaries cannot be deleted.'), 'error')\n return\n }\n const rawConfirm = t('dictionaries.config.delete.confirm', { name: dictionary.name })\n const confirmMessage = rawConfirm && rawConfirm !== 'dictionaries.config.delete.confirm'\n ? rawConfirm\n : `Delete dictionary \"${dictionary.name}\"?`\n const confirmed = await confirm({\n title: confirmMessage,\n variant: 'destructive',\n })\n if (!confirmed) return\n setDeleting(dictionary.id)\n try {\n const call = await apiCall<Record<string, unknown>>(`/api/dictionaries/${dictionary.id}`, { method: 'DELETE' })\n if (!call.ok) {\n throw new Error(typeof call.result?.error === 'string' ? call.result.error : 'Failed to delete dictionary')\n }\n flash(t('dictionaries.config.success.delete', 'Dictionary deleted.'), 'success')\n await loadDictionaries()\n } catch (err) {\n console.error('Failed to delete dictionary', err)\n flash(t('dictionaries.config.error.delete', 'Failed to delete dictionary.'), 'error')\n } finally {\n setDeleting(null)\n }\n },\n [confirm, inheritedManageMessage, loadDictionaries, t],\n )\n\n const selectedDictionary = items.find((item) => item.id === selectedId) ?? null\n\n return (\n <div className=\"grid gap-6 lg:grid-cols-[320px_1fr]\">\n <div className=\"rounded-lg border bg-card p-4 shadow-sm\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"text-base font-semibold\">\n {t('dictionaries.config.list.title', 'Dictionaries')}\n </h2>\n <Button type=\"button\" size=\"sm\" onClick={openCreateDialog}>\n <Plus className=\"mr-2 h-4 w-4\" />\n {t('dictionaries.config.list.add', 'New dictionary')}\n </Button>\n </div>\n <div className=\"mt-4 space-y-2\">\n {loading ? (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <Spinner className=\"h-4 w-4\" />\n {t('dictionaries.config.list.loading', 'Loading dictionaries\u2026')}\n </div>\n ) : items.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('dictionaries.config.list.empty', 'No dictionaries yet. Create one to get started.')}\n </p>\n ) : (\n <ul className=\"space-y-1\">\n {items.map((dictionary) => (\n <li key={dictionary.id}>\n <div\n role=\"button\"\n tabIndex={0}\n aria-pressed={dictionary.id === selectedId}\n className={`flex w-full cursor-pointer select-none items-center justify-between rounded border px-3 py-2 text-left text-sm transition ${\n dictionary.id === selectedId ? 'border-primary bg-primary/5 text-primary' : 'border-border hover:bg-muted'\n }`}\n onClick={() => setSelectedId(dictionary.id)}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n setSelectedId(dictionary.id)\n }\n }}\n >\n <div>\n <div className=\"flex items-center gap-2 font-medium\">\n <span>{dictionary.name}</span>\n {dictionary.isInherited ? (\n <span className=\"rounded-full border border-border px-2 py-0.5 text-overline font-normal uppercase tracking-wide text-muted-foreground\">\n {t('dictionaries.config.list.inherited', 'Inherited')}\n </span>\n ) : null}\n </div>\n <div className=\"text-xs text-muted-foreground\">{dictionary.key}</div>\n </div>\n <div className=\"flex items-center gap-2\">\n <Button\n type=\"button\"\n size=\"icon\"\n variant=\"ghost\"\n disabled={dictionary.isInherited}\n title={dictionary.isInherited ? inheritedManageMessage : undefined}\n onClick={(event) => {\n event.stopPropagation()\n openEditDialog(dictionary)\n }}\n >\n <Pencil className=\"h-4 w-4\" />\n </Button>\n <Button\n type=\"button\"\n size=\"icon\"\n variant=\"ghost\"\n disabled={dictionary.isInherited || deleting === dictionary.id}\n title={dictionary.isInherited ? inheritedManageMessage : undefined}\n onClick={(event) => {\n event.stopPropagation()\n handleDelete(dictionary)\n }}\n >\n <Trash2 className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n </li>\n ))}\n </ul>\n )}\n </div>\n </div>\n <div>\n {selectedDictionary ? (\n <DictionaryEntriesEditor\n dictionaryId={selectedDictionary.id}\n dictionaryName={selectedDictionary.name}\n readOnly={selectedDictionary.isInherited}\n />\n ) : (\n <EmptyState\n icon={<Book className=\"h-8 w-8\" aria-hidden=\"true\" />}\n title={t('dictionaries.config.entries.placeholder', 'Select a dictionary to manage its entries.')}\n className=\"h-full\"\n />\n )}\n </div>\n\n <Dialog open={dialog != null} onOpenChange={(open) => (open ? undefined : closeDialog())}>\n <DialogContent>\n <DialogHeader>\n <DialogTitle>\n {dialog?.mode === 'create'\n ? t('dictionaries.config.dialog.createTitle', 'Create dictionary')\n : t('dictionaries.config.dialog.editTitle', 'Edit dictionary')}\n </DialogTitle>\n </DialogHeader>\n <div className=\"space-y-4\">\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">{t('dictionaries.config.dialog.keyLabel', 'Key')}</label>\n <input\n value={form.key}\n onChange={(event) => {\n const next = event.target.value\n setForm((prev) => ({ ...prev, key: next }))\n if (errors.key) setErrors((prev) => ({ ...prev, key: undefined }))\n }}\n placeholder={t('dictionaries.config.dialog.keyPlaceholder', 'slug_name')}\n disabled={dialog?.mode === 'edit'}\n className={`w-full rounded border px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:bg-muted ${errors.key ? 'border-destructive focus-visible:ring-destructive' : ''}`}\n aria-invalid={errors.key ? 'true' : 'false'}\n aria-describedby=\"dictionary-key-hint\"\n />\n <p\n id=\"dictionary-key-hint\"\n className={`text-xs ${errors.key ? 'text-destructive' : 'text-muted-foreground'}`}\n >\n {errors.key ?? t('dictionaries.config.dialog.keyHint', 'Use lowercase letters, numbers, hyphen, or underscore.')}\n </p>\n </div>\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">{t('dictionaries.config.dialog.nameLabel', 'Name')}</label>\n <input\n value={form.name}\n onChange={(event) => {\n const next = event.target.value\n setForm((prev) => ({ ...prev, name: next }))\n if (errors.name) setErrors((prev) => ({ ...prev, name: undefined }))\n }}\n placeholder={t('dictionaries.config.dialog.namePlaceholder', 'Display name')}\n className={`w-full rounded border px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring ${errors.name ? 'border-destructive focus-visible:ring-destructive' : ''}`}\n aria-invalid={errors.name ? 'true' : 'false'}\n />\n {errors.name ? (\n <p className=\"text-xs text-destructive\">{errors.name}</p>\n ) : null}\n </div>\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">{t('dictionaries.config.dialog.descriptionLabel', 'Description')}</label>\n <textarea\n value={form.description}\n onChange={(event) => setForm((prev) => ({ ...prev, description: event.target.value }))}\n className=\"min-h-[120px] w-full rounded border px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n placeholder={t('dictionaries.config.dialog.descriptionPlaceholder', 'Explain how this dictionary is used (optional).')}\n />\n </div>\n </div>\n <DialogFooter>\n <Button type=\"button\" variant=\"ghost\" onClick={closeDialog} disabled={submitting}>\n {t('dictionaries.config.dialog.cancel', 'Cancel')}\n </Button>\n <Button type=\"button\" onClick={handleSubmit} disabled={submitting}>\n {submitting ? <Spinner className=\"mr-2 h-4 w-4\" /> : null}\n {t('dictionaries.config.dialog.save', 'Save')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n {ConfirmDialogElement}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -3,6 +3,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { useQuery, useMutation } from "@tanstack/react-query";
|
|
5
5
|
import { Button } from "@open-mercato/ui/primitives/button";
|
|
6
|
+
import { Checkbox } from "@open-mercato/ui/primitives/checkbox";
|
|
6
7
|
import {
|
|
7
8
|
Select,
|
|
8
9
|
SelectContent,
|
|
@@ -19,6 +20,7 @@ import { Plus, Save, Trash2 } from "lucide-react";
|
|
|
19
20
|
import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
|
|
20
21
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
21
22
|
import { getEntityFields } from "../../../generated-shims/entity-fields-registry.js";
|
|
23
|
+
const EMPTY_FIELD_DEFS = [];
|
|
22
24
|
function normalizeToken(value) {
|
|
23
25
|
return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-z0-9]/gi, "").toLowerCase();
|
|
24
26
|
}
|
|
@@ -41,6 +43,22 @@ function normalizeFieldRows(raw, options) {
|
|
|
41
43
|
hashField: entry.hashField ? canonicalizeFieldName(entry.hashField, options) : null
|
|
42
44
|
}));
|
|
43
45
|
}
|
|
46
|
+
function areFieldRowsEqual(a, b) {
|
|
47
|
+
if (a === b) return true;
|
|
48
|
+
if (a.length !== b.length) return false;
|
|
49
|
+
return a.every((row, idx) => {
|
|
50
|
+
const other = b[idx];
|
|
51
|
+
return !!other && row.id === other.id && row.field === other.field && (row.hashField ?? null) === (other.hashField ?? null);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
function areFieldOptionsEqual(a, b) {
|
|
55
|
+
if (a === b) return true;
|
|
56
|
+
if (a.length !== b.length) return false;
|
|
57
|
+
return a.every((option, idx) => {
|
|
58
|
+
const other = b[idx];
|
|
59
|
+
return !!other && option.value === other.value && option.label === other.label;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
44
62
|
function buildRowId() {
|
|
45
63
|
if (typeof crypto !== "undefined" && crypto.randomUUID) return crypto.randomUUID();
|
|
46
64
|
return `row-${Math.random().toString(36).slice(2)}`;
|
|
@@ -53,9 +71,10 @@ function EncryptionManager() {
|
|
|
53
71
|
const [isActive, setIsActive] = React.useState(true);
|
|
54
72
|
const [baseFieldOptions, setBaseFieldOptions] = React.useState([]);
|
|
55
73
|
const [hasUserEdited, setHasUserEdited] = React.useState(false);
|
|
56
|
-
const { data:
|
|
74
|
+
const { data: rawFieldDefs, isLoading: loadingFieldDefs } = useCustomFieldDefs(selectedEntityId ? [selectedEntityId] : [], {
|
|
57
75
|
enabled: !!selectedEntityId
|
|
58
76
|
});
|
|
77
|
+
const fieldDefs = rawFieldDefs ?? EMPTY_FIELD_DEFS;
|
|
59
78
|
const canonicalOptions = React.useMemo(() => {
|
|
60
79
|
const options = [];
|
|
61
80
|
for (const option of baseFieldOptions) {
|
|
@@ -105,19 +124,19 @@ function EncryptionManager() {
|
|
|
105
124
|
}, [entities, selectedEntityId]);
|
|
106
125
|
React.useEffect(() => {
|
|
107
126
|
if (!selectedEntityId) {
|
|
108
|
-
setBaseFieldOptions([]);
|
|
127
|
+
setBaseFieldOptions((prev) => prev.length ? [] : prev);
|
|
109
128
|
return;
|
|
110
129
|
}
|
|
111
130
|
const parts = selectedEntityId.split(":");
|
|
112
131
|
const entitySlug = parts[1];
|
|
113
132
|
if (!entitySlug) {
|
|
114
|
-
setBaseFieldOptions([]);
|
|
133
|
+
setBaseFieldOptions((prev) => prev.length ? [] : prev);
|
|
115
134
|
return;
|
|
116
135
|
}
|
|
117
136
|
const mod = getEntityFields(entitySlug);
|
|
118
137
|
if (!mod) {
|
|
119
138
|
console.warn("[encryption] No fields found for entity", entitySlug);
|
|
120
|
-
setBaseFieldOptions([]);
|
|
139
|
+
setBaseFieldOptions((prev) => prev.length ? [] : prev);
|
|
121
140
|
return;
|
|
122
141
|
}
|
|
123
142
|
const options = [];
|
|
@@ -128,7 +147,7 @@ function EncryptionManager() {
|
|
|
128
147
|
const label = value.split("_").map((segment) => segment ? `${segment[0].toUpperCase()}${segment.slice(1)}` : "").join(" ").trim() || value;
|
|
129
148
|
options.push({ value, label });
|
|
130
149
|
}
|
|
131
|
-
setBaseFieldOptions(options);
|
|
150
|
+
setBaseFieldOptions((prev) => areFieldOptionsEqual(prev, options) ? prev : options);
|
|
132
151
|
}, [selectedEntityId]);
|
|
133
152
|
const {
|
|
134
153
|
data: map,
|
|
@@ -149,14 +168,16 @@ function EncryptionManager() {
|
|
|
149
168
|
const isSameMap = signature === lastMapSignatureRef.current;
|
|
150
169
|
if (isSameMap && hasUserEdited) return;
|
|
151
170
|
if (!map) {
|
|
152
|
-
setFields([]);
|
|
153
|
-
setIsActive(true);
|
|
171
|
+
setFields((prev) => prev.length ? [] : prev);
|
|
172
|
+
setIsActive((prev) => prev === true ? prev : true);
|
|
154
173
|
setHasUserEdited(false);
|
|
155
174
|
lastMapSignatureRef.current = signature;
|
|
156
175
|
return;
|
|
157
176
|
}
|
|
158
|
-
|
|
159
|
-
|
|
177
|
+
const nextFields = normalizeFieldRows(map, canonicalOptions);
|
|
178
|
+
setFields((prev) => areFieldRowsEqual(prev, nextFields) ? prev : nextFields);
|
|
179
|
+
const nextIsActive = map?.isActive !== false;
|
|
180
|
+
setIsActive((prev) => prev === nextIsActive ? prev : nextIsActive);
|
|
160
181
|
setHasUserEdited(false);
|
|
161
182
|
lastMapSignatureRef.current = signature;
|
|
162
183
|
}, [mapSignature, map, canonicalOptions, hasUserEdited]);
|
|
@@ -221,7 +242,7 @@ function EncryptionManager() {
|
|
|
221
242
|
ErrorMessage,
|
|
222
243
|
{
|
|
223
244
|
label: t("entities.encryption.errors.loadMap", "Failed to load encryption map"),
|
|
224
|
-
action: /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: () => void refetchMap(), children: t("entities.encryption.actions.retry", "Retry") })
|
|
245
|
+
action: /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => void refetchMap(), children: t("entities.encryption.actions.retry", "Retry") })
|
|
225
246
|
}
|
|
226
247
|
);
|
|
227
248
|
}
|
|
@@ -272,6 +293,7 @@ function EncryptionManager() {
|
|
|
272
293
|
/* @__PURE__ */ jsx("td", { className: "px-3 py-2 align-top text-right", children: /* @__PURE__ */ jsx(
|
|
273
294
|
Button,
|
|
274
295
|
{
|
|
296
|
+
type: "button",
|
|
275
297
|
variant: "ghost",
|
|
276
298
|
size: "sm",
|
|
277
299
|
className: "h-9 w-9 px-0",
|
|
@@ -308,18 +330,18 @@ function EncryptionManager() {
|
|
|
308
330
|
]
|
|
309
331
|
}
|
|
310
332
|
),
|
|
311
|
-
entitiesError ? /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-
|
|
333
|
+
entitiesError ? /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-status-error-text", children: t("entities.encryption.errors.loadEntities", "Failed to load entities") }) : /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: t("entities.encryption.entityHint", "Maps apply per tenant/organization. Use field names from your entities.") })
|
|
312
334
|
] }),
|
|
313
|
-
/* @__PURE__ */ jsxs("
|
|
335
|
+
/* @__PURE__ */ jsxs("div", { className: "inline-flex items-center gap-2 text-sm", children: [
|
|
314
336
|
/* @__PURE__ */ jsx(
|
|
315
|
-
|
|
337
|
+
Checkbox,
|
|
316
338
|
{
|
|
317
|
-
|
|
339
|
+
id: "entities-encryption-active",
|
|
318
340
|
checked: isActive,
|
|
319
|
-
|
|
341
|
+
onCheckedChange: (checked) => setIsActive(checked === true)
|
|
320
342
|
}
|
|
321
343
|
),
|
|
322
|
-
t("entities.encryption.active", "Encryption enabled for this entity")
|
|
344
|
+
/* @__PURE__ */ jsx("label", { htmlFor: "entities-encryption-active", children: t("entities.encryption.active", "Encryption enabled for this entity") })
|
|
323
345
|
] })
|
|
324
346
|
] }) }),
|
|
325
347
|
/* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-background/80 p-4", children: [
|
|
@@ -328,7 +350,7 @@ function EncryptionManager() {
|
|
|
328
350
|
/* @__PURE__ */ jsx("h3", { className: "text-sm font-medium", children: t("entities.encryption.fields.title", "Encrypted fields") }),
|
|
329
351
|
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("entities.encryption.fields.subtitle", "List the attributes that should be encrypted with the tenant key.") })
|
|
330
352
|
] }),
|
|
331
|
-
/* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: addField, children: [
|
|
353
|
+
/* @__PURE__ */ jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: addField, children: [
|
|
332
354
|
/* @__PURE__ */ jsx(Plus, { className: "mr-2 h-4 w-4" }),
|
|
333
355
|
t("entities.encryption.actions.add", "Add field")
|
|
334
356
|
] })
|
|
@@ -338,6 +360,7 @@ function EncryptionManager() {
|
|
|
338
360
|
/* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs(
|
|
339
361
|
Button,
|
|
340
362
|
{
|
|
363
|
+
type: "button",
|
|
341
364
|
onClick: () => mutation.mutate(),
|
|
342
365
|
disabled: mutation.isPending || loadingEntities || !!entitiesError || !selectedEntityId,
|
|
343
366
|
children: [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/entities/components/EncryptionManager.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useQuery, useMutation } from '@tanstack/react-query'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { apiCall, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { raiseCrudError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { useCustomFieldDefs } from '@open-mercato/ui/backend/utils/customFieldDefs'\nimport { Plus, Save, Trash2 } from 'lucide-react'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { getEntityFields } from '#generated/entity-fields-registry'\n\ntype EntityOption = { entityId: string; label?: string; source?: string }\n\ntype EncryptionFieldRow = {\n id: string\n field: string\n hashField?: string | null\n}\n\ntype EncryptionMapResponse = {\n entityId: string\n fields?: Array<{ field: string; hashField?: string | null }>\n isActive?: boolean\n}\n\ntype CanonicalOption = { value: string; label?: string }\n\nfunction normalizeToken(value: string): string {\n return value\n .replace(/([a-z0-9])([A-Z])/g, '$1_$2')\n .replace(/[^a-z0-9]/gi, '')\n .toLowerCase()\n}\n\nfunction canonicalizeFieldName(raw: string, options: CanonicalOption[]): string {\n const trimmed = typeof raw === 'string' ? raw.trim() : ''\n if (!trimmed) return ''\n const normalized = normalizeToken(trimmed)\n for (const option of options) {\n const optionValue = typeof option.value === 'string' ? option.value.trim() : ''\n if (optionValue && normalizeToken(optionValue) === normalized) return optionValue\n if (option.label && normalizeToken(option.label) === normalized) return optionValue\n }\n return trimmed\n}\n\nfunction normalizeFieldRows(raw: EncryptionMapResponse | undefined, options: CanonicalOption[]): EncryptionFieldRow[] {\n const rows = Array.isArray(raw?.fields) ? raw!.fields : []\n return rows.map((entry, idx) => ({\n id: `${entry.field}-${idx}`,\n field: canonicalizeFieldName(entry.field, options),\n hashField: entry.hashField ? canonicalizeFieldName(entry.hashField, options) : null,\n }))\n}\n\nfunction buildRowId(): string {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) return crypto.randomUUID()\n return `row-${Math.random().toString(36).slice(2)}`\n}\n\nexport function EncryptionManager() {\n const t = useT()\n const scopeVersion = useOrganizationScopeVersion()\n const [selectedEntityId, setSelectedEntityId] = React.useState('')\n const [fields, setFields] = React.useState<EncryptionFieldRow[]>([])\n const [isActive, setIsActive] = React.useState(true)\n const [baseFieldOptions, setBaseFieldOptions] = React.useState<Array<{ value: string; label: string }>>([])\n const [hasUserEdited, setHasUserEdited] = React.useState(false)\n const { data: fieldDefs = [], isLoading: loadingFieldDefs } = useCustomFieldDefs(selectedEntityId ? [selectedEntityId] : [], {\n enabled: !!selectedEntityId,\n })\n const canonicalOptions = React.useMemo<CanonicalOption[]>(() => {\n const options: CanonicalOption[] = []\n for (const option of baseFieldOptions) {\n if (!option.value) continue\n if (options.some((opt) => opt.value === option.value)) continue\n options.push(option)\n }\n for (const def of fieldDefs) {\n const key = typeof def.key === 'string' ? def.key.trim() : ''\n if (!key || options.some((opt) => opt.value === key)) continue\n const label = typeof def.label === 'string' ? def.label : undefined\n options.push({ value: key, label })\n }\n return options\n }, [baseFieldOptions, fieldDefs])\n const fieldOptions = React.useMemo(() => {\n const entries = new Map<string, string>()\n for (const option of baseFieldOptions) {\n if (!option.value || entries.has(option.value)) continue\n entries.set(option.value, option.label || option.value)\n }\n for (const def of fieldDefs) {\n const key = typeof def.key === 'string' ? def.key.trim() : ''\n if (!key || entries.has(key)) continue\n const label = typeof def.label === 'string' && def.label.trim().length ? def.label : key\n entries.set(key, label)\n }\n for (const row of fields) {\n const main = row.field?.trim()\n if (main && !entries.has(main)) entries.set(main, main)\n const hash = row.hashField?.trim()\n if (hash && !entries.has(hash)) entries.set(hash, hash)\n }\n return Array.from(entries.entries()).map(([value, label]) => ({ value, label }))\n }, [baseFieldOptions, fieldDefs, fields])\n\n const { data: entities, isLoading: loadingEntities, error: entitiesError } = useQuery<{ items: EntityOption[] }>({\n queryKey: ['entities-list', scopeVersion],\n queryFn: async () =>\n readApiResultOrThrow('/api/entities/entities', undefined, {\n errorMessage: t('entities.encryption.errors.loadEntities', 'Failed to load entities'),\n }),\n })\n\n React.useEffect(() => {\n if (!selectedEntityId && entities?.items?.length) {\n const first = entities.items[0]\n setSelectedEntityId(first.entityId)\n }\n }, [entities, selectedEntityId])\n\n React.useEffect(() => {\n if (!selectedEntityId) {\n setBaseFieldOptions([])\n return\n }\n const parts = selectedEntityId.split(':')\n const entitySlug = parts[1]\n if (!entitySlug) {\n setBaseFieldOptions([])\n return\n }\n\n // Use static registry instead of dynamic import for Turbopack compatibility\n const mod = getEntityFields(entitySlug)\n if (!mod) {\n console.warn('[encryption] No fields found for entity', entitySlug)\n setBaseFieldOptions([])\n return\n }\n\n const options: Array<{ value: string; label: string }> = []\n for (const raw of Object.values(mod)) {\n if (typeof raw !== 'string' || !raw.trim()) continue\n const value = raw.trim()\n if (options.some((opt) => opt.value === value)) continue\n const label = value\n .split('_')\n .map((segment) => (segment ? `${segment[0].toUpperCase()}${segment.slice(1)}` : ''))\n .join(' ')\n .trim() || value\n options.push({ value, label })\n }\n setBaseFieldOptions(options)\n }, [selectedEntityId])\n\n const {\n data: map,\n isLoading: loadingMap,\n isError: mapError,\n refetch: refetchMap,\n } = useQuery<EncryptionMapResponse>({\n queryKey: ['encryption-map', selectedEntityId, scopeVersion],\n enabled: !!selectedEntityId,\n queryFn: async () =>\n readApiResultOrThrow(`/api/entities/encryption?entityId=${encodeURIComponent(selectedEntityId)}`, undefined, {\n errorMessage: t('entities.encryption.errors.loadMap', 'Failed to load encryption map'),\n }),\n })\n\n const mapSignature = React.useMemo(() => JSON.stringify(map ?? null), [map])\n const lastMapSignatureRef = React.useRef<string | null>(null)\n\n React.useEffect(() => {\n const signature = mapSignature\n const isSameMap = signature === lastMapSignatureRef.current\n if (isSameMap && hasUserEdited) return\n if (!map) {\n setFields([])\n setIsActive(true)\n setHasUserEdited(false)\n lastMapSignatureRef.current = signature\n return\n }\n setFields(normalizeFieldRows(map, canonicalOptions))\n setIsActive(map?.isActive !== false)\n setHasUserEdited(false)\n lastMapSignatureRef.current = signature\n }, [mapSignature, map, canonicalOptions, hasUserEdited])\n\n const mutation = useMutation({\n mutationFn: async () => {\n const trimmed = fields\n .map((row) => ({\n field: canonicalizeFieldName(row.field, canonicalOptions).trim(),\n hashField: row.hashField ? canonicalizeFieldName(row.hashField, canonicalOptions).trim() : '',\n }))\n .filter((row) => row.field.length > 0)\n .map((row) => ({\n field: row.field,\n hashField: row.hashField ? row.hashField : null,\n }))\n if (!selectedEntityId) {\n throw new Error(t('entities.encryption.errors.selectEntity', 'Select an entity before saving'))\n }\n if (!trimmed.length) {\n throw new Error(t('entities.encryption.errors.noFields', 'Add at least one field to encrypt'))\n }\n const res = await apiCall('/api/entities/encryption', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ entityId: selectedEntityId, fields: trimmed, isActive }),\n })\n if (!res.ok) {\n await raiseCrudError(res.response, t('entities.encryption.errors.save', 'Failed to save encryption map'))\n }\n return true\n },\n onSuccess: () => {\n flash(t('entities.encryption.flash.saved', 'Encryption map saved'), 'success')\n void refetchMap()\n },\n onError: (err: any) => {\n const message = err?.message || t('entities.encryption.errors.save', 'Failed to save encryption map')\n flash(message, 'error')\n },\n })\n\n const addField = () => {\n setHasUserEdited(true)\n setFields((prev) => [...prev, { id: buildRowId(), field: '', hashField: null }])\n }\n\n const updateField = (id: string, patch: Partial<EncryptionFieldRow>) => {\n setHasUserEdited(true)\n setFields((prev) => prev.map((row) => (row.id === id ? { ...row, ...patch } : row)))\n }\n\n const removeField = (id: string) => {\n setHasUserEdited(true)\n setFields((prev) => prev.filter((row) => row.id !== id))\n }\n\n const renderFields = () => {\n if (loadingMap || loadingFieldDefs) {\n return (\n <LoadingMessage\n label={t('entities.encryption.loading', 'Loading encryption map\u2026')}\n className=\"border-0 bg-transparent p-4\"\n />\n )\n }\n if (mapError) {\n return (\n <ErrorMessage\n label={t('entities.encryption.errors.loadMap', 'Failed to load encryption map')}\n action={(\n <Button variant=\"outline\" size=\"sm\" onClick={() => void refetchMap()}>\n {t('entities.encryption.actions.retry', 'Retry')}\n </Button>\n )}\n />\n )\n }\n if (!fields.length) {\n return (\n <div className=\"rounded border bg-background/80 p-4 text-sm text-muted-foreground\">\n {t('entities.encryption.empty', 'No fields are encrypted yet. Add the first one below.')}\n </div>\n )\n }\n const withFallbackOption = (value?: string | null) => {\n if (value && !fieldOptions.some((opt) => opt.value === value)) {\n return [...fieldOptions, { value, label: value }]\n }\n return fieldOptions\n }\n return (\n <div className=\"overflow-x-auto\">\n <table className=\"w-full min-w-[720px] text-sm\">\n <thead>\n <tr className=\"text-xs uppercase tracking-wide text-muted-foreground\">\n <th className=\"px-3 py-2 text-left\">\n {t('entities.encryption.fields.field', 'Field name')}\n </th>\n <th className=\"px-3 py-2 text-left\">\n {t('entities.encryption.fields.hash', 'Hash field (optional)')}\n </th>\n <th className=\"px-3 py-2 text-right\">\n {t('entities.encryption.fields.actions', 'Actions')}\n </th>\n </tr>\n </thead>\n <tbody>\n {fields.map((row) => {\n const fieldOpts = withFallbackOption(row.field)\n const hashOpts = withFallbackOption(row.hashField)\n return (\n <tr key={row.id} className=\"border-t\">\n <td className=\"px-3 py-2 align-top\">\n <Select\n value={row.field || undefined}\n onValueChange={(value) => updateField(row.id, { field: value ?? '' })}\n >\n <SelectTrigger>\n <SelectValue placeholder={t('entities.encryption.fields.selectField', 'Select field')} />\n </SelectTrigger>\n <SelectContent>\n {fieldOpts.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </td>\n <td className=\"px-3 py-2 align-top\">\n <Select\n value={row.hashField || undefined}\n onValueChange={(value) => updateField(row.id, { hashField: value || null })}\n >\n <SelectTrigger>\n <SelectValue placeholder={t('entities.encryption.fields.selectHash', 'Select hash field (optional)')} />\n </SelectTrigger>\n <SelectContent>\n {hashOpts.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <p className=\"mt-1 text-overline text-muted-foreground\">\n {t('entities.encryption.fields.hashHint', 'Use when lookups must stay deterministic (e.g., login by email).')}\n </p>\n </td>\n <td className=\"px-3 py-2 align-top text-right\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-9 w-9 px-0\"\n aria-label={t('entities.encryption.actions.remove', 'Remove')}\n onClick={() => removeField(row.id)}\n >\n <Trash2 className=\"h-4 w-4\" />\n </Button>\n </td>\n </tr>\n )\n })}\n </tbody>\n </table>\n </div>\n )\n }\n\n return (\n <div className=\"space-y-6\">\n <div className=\"flex flex-col gap-3 rounded-lg border bg-card p-4 shadow-sm\">\n <div className=\"space-y-2\">\n <h2 className=\"text-xl font-semibold\">{t('entities.encryption.title', 'Encryption')}</h2>\n <p className=\"text-sm text-muted-foreground\">\n {t('entities.encryption.description', 'Manage which entity fields are encrypted with tenant keys and optional hash columns.')}\n </p>\n </div>\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-end\">\n <div className=\"flex-1 space-y-3\">\n <div>\n <label className=\"text-xs text-muted-foreground\">\n {t('entities.encryption.selectEntity', 'Choose entity')}\n </label>\n <Select\n value={selectedEntityId || undefined}\n onValueChange={(value) => setSelectedEntityId(value ?? '')}\n disabled={loadingEntities || !!entitiesError}\n >\n <SelectTrigger className=\"mt-1\">\n <SelectValue placeholder={t('entities.encryption.placeholder', 'Select an entity')} />\n </SelectTrigger>\n <SelectContent>\n {(entities?.items || []).map((item) => (\n <SelectItem key={item.entityId} value={item.entityId}>\n {item.label || item.entityId} {item.source === 'custom' ? `(${t('entities.encryption.source.custom', 'custom')})` : ''}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {entitiesError ? (\n <p className=\"mt-1 text-xs text-red-600\">\n {t('entities.encryption.errors.loadEntities', 'Failed to load entities')}\n </p>\n ) : (\n <p className=\"mt-1 text-xs text-muted-foreground\">\n {t('entities.encryption.entityHint', 'Maps apply per tenant/organization. Use field names from your entities.')}\n </p>\n )}\n </div>\n <label className=\"inline-flex items-center gap-2 text-sm\">\n <input\n type=\"checkbox\"\n checked={isActive}\n onChange={(event) => setIsActive(event.target.checked)}\n />\n {t('entities.encryption.active', 'Encryption enabled for this entity')}\n </label>\n </div>\n </div>\n <div className=\"rounded-lg border bg-background/80 p-4\">\n <div className=\"mb-3 flex items-center justify-between\">\n <div>\n <h3 className=\"text-sm font-medium\">{t('entities.encryption.fields.title', 'Encrypted fields')}</h3>\n <p className=\"text-xs text-muted-foreground\">\n {t('entities.encryption.fields.subtitle', 'List the attributes that should be encrypted with the tenant key.')}\n </p>\n </div>\n <Button variant=\"outline\" size=\"sm\" onClick={addField}>\n <Plus className=\"mr-2 h-4 w-4\" />\n {t('entities.encryption.actions.add', 'Add field')}\n </Button>\n </div>\n {renderFields()}\n </div>\n <div className=\"flex justify-end\">\n <Button\n onClick={() => mutation.mutate()}\n disabled={mutation.isPending || loadingEntities || !!entitiesError || !selectedEntityId}\n >\n <Save className=\"mr-2 h-4 w-4\" />\n {mutation.isPending\n ? t('entities.encryption.actions.saving', 'Saving\u2026')\n : t('entities.encryption.actions.save', 'Save encryption map')}\n </Button>\n </div>\n </div>\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useQuery, useMutation } from '@tanstack/react-query'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Checkbox } from '@open-mercato/ui/primitives/checkbox'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { apiCall, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { raiseCrudError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { useCustomFieldDefs, type CustomFieldDefDto } from '@open-mercato/ui/backend/utils/customFieldDefs'\nimport { Plus, Save, Trash2 } from 'lucide-react'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { getEntityFields } from '#generated/entity-fields-registry'\n\ntype EntityOption = { entityId: string; label?: string; source?: string }\ntype FieldOption = { value: string; label: string }\n\ntype EncryptionFieldRow = {\n id: string\n field: string\n hashField?: string | null\n}\n\ntype EncryptionMapResponse = {\n entityId: string\n fields?: Array<{ field: string; hashField?: string | null }>\n isActive?: boolean\n}\n\ntype CanonicalOption = { value: string; label?: string }\n\nconst EMPTY_FIELD_DEFS: CustomFieldDefDto[] = []\n\nfunction normalizeToken(value: string): string {\n return value\n .replace(/([a-z0-9])([A-Z])/g, '$1_$2')\n .replace(/[^a-z0-9]/gi, '')\n .toLowerCase()\n}\n\nfunction canonicalizeFieldName(raw: string, options: CanonicalOption[]): string {\n const trimmed = typeof raw === 'string' ? raw.trim() : ''\n if (!trimmed) return ''\n const normalized = normalizeToken(trimmed)\n for (const option of options) {\n const optionValue = typeof option.value === 'string' ? option.value.trim() : ''\n if (optionValue && normalizeToken(optionValue) === normalized) return optionValue\n if (option.label && normalizeToken(option.label) === normalized) return optionValue\n }\n return trimmed\n}\n\nfunction normalizeFieldRows(raw: EncryptionMapResponse | undefined, options: CanonicalOption[]): EncryptionFieldRow[] {\n const rows = Array.isArray(raw?.fields) ? raw!.fields : []\n return rows.map((entry, idx) => ({\n id: `${entry.field}-${idx}`,\n field: canonicalizeFieldName(entry.field, options),\n hashField: entry.hashField ? canonicalizeFieldName(entry.hashField, options) : null,\n }))\n}\n\nfunction areFieldRowsEqual(a: EncryptionFieldRow[], b: EncryptionFieldRow[]): boolean {\n if (a === b) return true\n if (a.length !== b.length) return false\n return a.every((row, idx) => {\n const other = b[idx]\n return !!other && row.id === other.id && row.field === other.field && (row.hashField ?? null) === (other.hashField ?? null)\n })\n}\n\nfunction areFieldOptionsEqual(a: FieldOption[], b: FieldOption[]): boolean {\n if (a === b) return true\n if (a.length !== b.length) return false\n return a.every((option, idx) => {\n const other = b[idx]\n return !!other && option.value === other.value && option.label === other.label\n })\n}\n\nfunction buildRowId(): string {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) return crypto.randomUUID()\n return `row-${Math.random().toString(36).slice(2)}`\n}\n\nexport function EncryptionManager() {\n const t = useT()\n const scopeVersion = useOrganizationScopeVersion()\n const [selectedEntityId, setSelectedEntityId] = React.useState('')\n const [fields, setFields] = React.useState<EncryptionFieldRow[]>([])\n const [isActive, setIsActive] = React.useState(true)\n const [baseFieldOptions, setBaseFieldOptions] = React.useState<FieldOption[]>([])\n const [hasUserEdited, setHasUserEdited] = React.useState(false)\n const { data: rawFieldDefs, isLoading: loadingFieldDefs } = useCustomFieldDefs(selectedEntityId ? [selectedEntityId] : [], {\n enabled: !!selectedEntityId,\n })\n const fieldDefs = rawFieldDefs ?? EMPTY_FIELD_DEFS\n const canonicalOptions = React.useMemo<CanonicalOption[]>(() => {\n const options: CanonicalOption[] = []\n for (const option of baseFieldOptions) {\n if (!option.value) continue\n if (options.some((opt) => opt.value === option.value)) continue\n options.push(option)\n }\n for (const def of fieldDefs) {\n const key = typeof def.key === 'string' ? def.key.trim() : ''\n if (!key || options.some((opt) => opt.value === key)) continue\n const label = typeof def.label === 'string' ? def.label : undefined\n options.push({ value: key, label })\n }\n return options\n }, [baseFieldOptions, fieldDefs])\n const fieldOptions = React.useMemo(() => {\n const entries = new Map<string, string>()\n for (const option of baseFieldOptions) {\n if (!option.value || entries.has(option.value)) continue\n entries.set(option.value, option.label || option.value)\n }\n for (const def of fieldDefs) {\n const key = typeof def.key === 'string' ? def.key.trim() : ''\n if (!key || entries.has(key)) continue\n const label = typeof def.label === 'string' && def.label.trim().length ? def.label : key\n entries.set(key, label)\n }\n for (const row of fields) {\n const main = row.field?.trim()\n if (main && !entries.has(main)) entries.set(main, main)\n const hash = row.hashField?.trim()\n if (hash && !entries.has(hash)) entries.set(hash, hash)\n }\n return Array.from(entries.entries()).map(([value, label]) => ({ value, label }))\n }, [baseFieldOptions, fieldDefs, fields])\n\n const { data: entities, isLoading: loadingEntities, error: entitiesError } = useQuery<{ items: EntityOption[] }>({\n queryKey: ['entities-list', scopeVersion],\n queryFn: async () =>\n readApiResultOrThrow('/api/entities/entities', undefined, {\n errorMessage: t('entities.encryption.errors.loadEntities', 'Failed to load entities'),\n }),\n })\n\n React.useEffect(() => {\n if (!selectedEntityId && entities?.items?.length) {\n const first = entities.items[0]\n setSelectedEntityId(first.entityId)\n }\n }, [entities, selectedEntityId])\n\n React.useEffect(() => {\n if (!selectedEntityId) {\n setBaseFieldOptions((prev) => (prev.length ? [] : prev))\n return\n }\n const parts = selectedEntityId.split(':')\n const entitySlug = parts[1]\n if (!entitySlug) {\n setBaseFieldOptions((prev) => (prev.length ? [] : prev))\n return\n }\n\n // Use static registry instead of dynamic import for Turbopack compatibility\n const mod = getEntityFields(entitySlug)\n if (!mod) {\n console.warn('[encryption] No fields found for entity', entitySlug)\n setBaseFieldOptions((prev) => (prev.length ? [] : prev))\n return\n }\n\n const options: FieldOption[] = []\n for (const raw of Object.values(mod)) {\n if (typeof raw !== 'string' || !raw.trim()) continue\n const value = raw.trim()\n if (options.some((opt) => opt.value === value)) continue\n const label = value\n .split('_')\n .map((segment) => (segment ? `${segment[0].toUpperCase()}${segment.slice(1)}` : ''))\n .join(' ')\n .trim() || value\n options.push({ value, label })\n }\n setBaseFieldOptions((prev) => (areFieldOptionsEqual(prev, options) ? prev : options))\n }, [selectedEntityId])\n\n const {\n data: map,\n isLoading: loadingMap,\n isError: mapError,\n refetch: refetchMap,\n } = useQuery<EncryptionMapResponse>({\n queryKey: ['encryption-map', selectedEntityId, scopeVersion],\n enabled: !!selectedEntityId,\n queryFn: async () =>\n readApiResultOrThrow(`/api/entities/encryption?entityId=${encodeURIComponent(selectedEntityId)}`, undefined, {\n errorMessage: t('entities.encryption.errors.loadMap', 'Failed to load encryption map'),\n }),\n })\n\n const mapSignature = React.useMemo(() => JSON.stringify(map ?? null), [map])\n const lastMapSignatureRef = React.useRef<string | null>(null)\n\n React.useEffect(() => {\n const signature = mapSignature\n const isSameMap = signature === lastMapSignatureRef.current\n if (isSameMap && hasUserEdited) return\n if (!map) {\n setFields((prev) => (prev.length ? [] : prev))\n setIsActive((prev) => (prev === true ? prev : true))\n setHasUserEdited(false)\n lastMapSignatureRef.current = signature\n return\n }\n const nextFields = normalizeFieldRows(map, canonicalOptions)\n setFields((prev) => (areFieldRowsEqual(prev, nextFields) ? prev : nextFields))\n const nextIsActive = map?.isActive !== false\n setIsActive((prev) => (prev === nextIsActive ? prev : nextIsActive))\n setHasUserEdited(false)\n lastMapSignatureRef.current = signature\n }, [mapSignature, map, canonicalOptions, hasUserEdited])\n\n const mutation = useMutation({\n mutationFn: async () => {\n const trimmed = fields\n .map((row) => ({\n field: canonicalizeFieldName(row.field, canonicalOptions).trim(),\n hashField: row.hashField ? canonicalizeFieldName(row.hashField, canonicalOptions).trim() : '',\n }))\n .filter((row) => row.field.length > 0)\n .map((row) => ({\n field: row.field,\n hashField: row.hashField ? row.hashField : null,\n }))\n if (!selectedEntityId) {\n throw new Error(t('entities.encryption.errors.selectEntity', 'Select an entity before saving'))\n }\n if (!trimmed.length) {\n throw new Error(t('entities.encryption.errors.noFields', 'Add at least one field to encrypt'))\n }\n const res = await apiCall('/api/entities/encryption', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ entityId: selectedEntityId, fields: trimmed, isActive }),\n })\n if (!res.ok) {\n await raiseCrudError(res.response, t('entities.encryption.errors.save', 'Failed to save encryption map'))\n }\n return true\n },\n onSuccess: () => {\n flash(t('entities.encryption.flash.saved', 'Encryption map saved'), 'success')\n void refetchMap()\n },\n onError: (err: any) => {\n const message = err?.message || t('entities.encryption.errors.save', 'Failed to save encryption map')\n flash(message, 'error')\n },\n })\n\n const addField = () => {\n setHasUserEdited(true)\n setFields((prev) => [...prev, { id: buildRowId(), field: '', hashField: null }])\n }\n\n const updateField = (id: string, patch: Partial<EncryptionFieldRow>) => {\n setHasUserEdited(true)\n setFields((prev) => prev.map((row) => (row.id === id ? { ...row, ...patch } : row)))\n }\n\n const removeField = (id: string) => {\n setHasUserEdited(true)\n setFields((prev) => prev.filter((row) => row.id !== id))\n }\n\n const renderFields = () => {\n if (loadingMap || loadingFieldDefs) {\n return (\n <LoadingMessage\n label={t('entities.encryption.loading', 'Loading encryption map\u2026')}\n className=\"border-0 bg-transparent p-4\"\n />\n )\n }\n if (mapError) {\n return (\n <ErrorMessage\n label={t('entities.encryption.errors.loadMap', 'Failed to load encryption map')}\n action={(\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => void refetchMap()}>\n {t('entities.encryption.actions.retry', 'Retry')}\n </Button>\n )}\n />\n )\n }\n if (!fields.length) {\n return (\n <div className=\"rounded border bg-background/80 p-4 text-sm text-muted-foreground\">\n {t('entities.encryption.empty', 'No fields are encrypted yet. Add the first one below.')}\n </div>\n )\n }\n const withFallbackOption = (value?: string | null) => {\n if (value && !fieldOptions.some((opt) => opt.value === value)) {\n return [...fieldOptions, { value, label: value }]\n }\n return fieldOptions\n }\n return (\n <div className=\"overflow-x-auto\">\n <table className=\"w-full min-w-[720px] text-sm\">\n <thead>\n <tr className=\"text-xs uppercase tracking-wide text-muted-foreground\">\n <th className=\"px-3 py-2 text-left\">\n {t('entities.encryption.fields.field', 'Field name')}\n </th>\n <th className=\"px-3 py-2 text-left\">\n {t('entities.encryption.fields.hash', 'Hash field (optional)')}\n </th>\n <th className=\"px-3 py-2 text-right\">\n {t('entities.encryption.fields.actions', 'Actions')}\n </th>\n </tr>\n </thead>\n <tbody>\n {fields.map((row) => {\n const fieldOpts = withFallbackOption(row.field)\n const hashOpts = withFallbackOption(row.hashField)\n return (\n <tr key={row.id} className=\"border-t\">\n <td className=\"px-3 py-2 align-top\">\n <Select\n value={row.field || undefined}\n onValueChange={(value) => updateField(row.id, { field: value ?? '' })}\n >\n <SelectTrigger>\n <SelectValue placeholder={t('entities.encryption.fields.selectField', 'Select field')} />\n </SelectTrigger>\n <SelectContent>\n {fieldOpts.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </td>\n <td className=\"px-3 py-2 align-top\">\n <Select\n value={row.hashField || undefined}\n onValueChange={(value) => updateField(row.id, { hashField: value || null })}\n >\n <SelectTrigger>\n <SelectValue placeholder={t('entities.encryption.fields.selectHash', 'Select hash field (optional)')} />\n </SelectTrigger>\n <SelectContent>\n {hashOpts.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <p className=\"mt-1 text-overline text-muted-foreground\">\n {t('entities.encryption.fields.hashHint', 'Use when lookups must stay deterministic (e.g., login by email).')}\n </p>\n </td>\n <td className=\"px-3 py-2 align-top text-right\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-9 w-9 px-0\"\n aria-label={t('entities.encryption.actions.remove', 'Remove')}\n onClick={() => removeField(row.id)}\n >\n <Trash2 className=\"h-4 w-4\" />\n </Button>\n </td>\n </tr>\n )\n })}\n </tbody>\n </table>\n </div>\n )\n }\n\n return (\n <div className=\"space-y-6\">\n <div className=\"flex flex-col gap-3 rounded-lg border bg-card p-4 shadow-sm\">\n <div className=\"space-y-2\">\n <h2 className=\"text-xl font-semibold\">{t('entities.encryption.title', 'Encryption')}</h2>\n <p className=\"text-sm text-muted-foreground\">\n {t('entities.encryption.description', 'Manage which entity fields are encrypted with tenant keys and optional hash columns.')}\n </p>\n </div>\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-end\">\n <div className=\"flex-1 space-y-3\">\n <div>\n <label className=\"text-xs text-muted-foreground\">\n {t('entities.encryption.selectEntity', 'Choose entity')}\n </label>\n <Select\n value={selectedEntityId || undefined}\n onValueChange={(value) => setSelectedEntityId(value ?? '')}\n disabled={loadingEntities || !!entitiesError}\n >\n <SelectTrigger className=\"mt-1\">\n <SelectValue placeholder={t('entities.encryption.placeholder', 'Select an entity')} />\n </SelectTrigger>\n <SelectContent>\n {(entities?.items || []).map((item) => (\n <SelectItem key={item.entityId} value={item.entityId}>\n {item.label || item.entityId} {item.source === 'custom' ? `(${t('entities.encryption.source.custom', 'custom')})` : ''}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {entitiesError ? (\n <p className=\"mt-1 text-xs text-status-error-text\">\n {t('entities.encryption.errors.loadEntities', 'Failed to load entities')}\n </p>\n ) : (\n <p className=\"mt-1 text-xs text-muted-foreground\">\n {t('entities.encryption.entityHint', 'Maps apply per tenant/organization. Use field names from your entities.')}\n </p>\n )}\n </div>\n <div className=\"inline-flex items-center gap-2 text-sm\">\n <Checkbox\n id=\"entities-encryption-active\"\n checked={isActive}\n onCheckedChange={(checked) => setIsActive(checked === true)}\n />\n <label htmlFor=\"entities-encryption-active\">\n {t('entities.encryption.active', 'Encryption enabled for this entity')}\n </label>\n </div>\n </div>\n </div>\n <div className=\"rounded-lg border bg-background/80 p-4\">\n <div className=\"mb-3 flex items-center justify-between\">\n <div>\n <h3 className=\"text-sm font-medium\">{t('entities.encryption.fields.title', 'Encrypted fields')}</h3>\n <p className=\"text-xs text-muted-foreground\">\n {t('entities.encryption.fields.subtitle', 'List the attributes that should be encrypted with the tenant key.')}\n </p>\n </div>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={addField}>\n <Plus className=\"mr-2 h-4 w-4\" />\n {t('entities.encryption.actions.add', 'Add field')}\n </Button>\n </div>\n {renderFields()}\n </div>\n <div className=\"flex justify-end\">\n <Button\n type=\"button\"\n onClick={() => mutation.mutate()}\n disabled={mutation.isPending || loadingEntities || !!entitiesError || !selectedEntityId}\n >\n <Save className=\"mr-2 h-4 w-4\" />\n {mutation.isPending\n ? t('entities.encryption.actions.saving', 'Saving\u2026')\n : t('entities.encryption.actions.save', 'Save encryption map')}\n </Button>\n </div>\n </div>\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AA2RQ,cAmCI,YAnCJ;AAzRR,YAAY,WAAW;AACvB,SAAS,UAAU,mBAAmB;AACtC,SAAS,cAAc;AACvB,SAAS,gBAAgB;AACzB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,gBAAgB,oBAAoB;AAC7C,SAAS,aAAa;AACtB,SAAS,SAAS,4BAA4B;AAC9C,SAAS,sBAAsB;AAC/B,SAAS,0BAAkD;AAC3D,SAAS,MAAM,MAAM,cAAc;AACnC,SAAS,mCAAmC;AAC5C,SAAS,YAAY;AACrB,SAAS,uBAAuB;AAmBhC,MAAM,mBAAwC,CAAC;AAE/C,SAAS,eAAe,OAAuB;AAC7C,SAAO,MACJ,QAAQ,sBAAsB,OAAO,EACrC,QAAQ,eAAe,EAAE,EACzB,YAAY;AACjB;AAEA,SAAS,sBAAsB,KAAa,SAAoC;AAC9E,QAAM,UAAU,OAAO,QAAQ,WAAW,IAAI,KAAK,IAAI;AACvD,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,aAAa,eAAe,OAAO;AACzC,aAAW,UAAU,SAAS;AAC5B,UAAM,cAAc,OAAO,OAAO,UAAU,WAAW,OAAO,MAAM,KAAK,IAAI;AAC7E,QAAI,eAAe,eAAe,WAAW,MAAM,WAAY,QAAO;AACtE,QAAI,OAAO,SAAS,eAAe,OAAO,KAAK,MAAM,WAAY,QAAO;AAAA,EAC1E;AACA,SAAO;AACT;AAEA,SAAS,mBAAmB,KAAwC,SAAkD;AACpH,QAAM,OAAO,MAAM,QAAQ,KAAK,MAAM,IAAI,IAAK,SAAS,CAAC;AACzD,SAAO,KAAK,IAAI,CAAC,OAAO,SAAS;AAAA,IAC/B,IAAI,GAAG,MAAM,KAAK,IAAI,GAAG;AAAA,IACzB,OAAO,sBAAsB,MAAM,OAAO,OAAO;AAAA,IACjD,WAAW,MAAM,YAAY,sBAAsB,MAAM,WAAW,OAAO,IAAI;AAAA,EACjF,EAAE;AACJ;AAEA,SAAS,kBAAkB,GAAyB,GAAkC;AACpF,MAAI,MAAM,EAAG,QAAO;AACpB,MAAI,EAAE,WAAW,EAAE,OAAQ,QAAO;AAClC,SAAO,EAAE,MAAM,CAAC,KAAK,QAAQ;AAC3B,UAAM,QAAQ,EAAE,GAAG;AACnB,WAAO,CAAC,CAAC,SAAS,IAAI,OAAO,MAAM,MAAM,IAAI,UAAU,MAAM,UAAU,IAAI,aAAa,WAAW,MAAM,aAAa;AAAA,EACxH,CAAC;AACH;AAEA,SAAS,qBAAqB,GAAkB,GAA2B;AACzE,MAAI,MAAM,EAAG,QAAO;AACpB,MAAI,EAAE,WAAW,EAAE,OAAQ,QAAO;AAClC,SAAO,EAAE,MAAM,CAAC,QAAQ,QAAQ;AAC9B,UAAM,QAAQ,EAAE,GAAG;AACnB,WAAO,CAAC,CAAC,SAAS,OAAO,UAAU,MAAM,SAAS,OAAO,UAAU,MAAM;AAAA,EAC3E,CAAC;AACH;AAEA,SAAS,aAAqB;AAC5B,MAAI,OAAO,WAAW,eAAe,OAAO,WAAY,QAAO,OAAO,WAAW;AACjF,SAAO,OAAO,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,CAAC;AACnD;AAEO,SAAS,oBAAoB;AAClC,QAAM,IAAI,KAAK;AACf,QAAM,eAAe,4BAA4B;AACjD,QAAM,CAAC,kBAAkB,mBAAmB,IAAI,MAAM,SAAS,EAAE;AACjE,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAA+B,CAAC,CAAC;AACnE,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAS,IAAI;AACnD,QAAM,CAAC,kBAAkB,mBAAmB,IAAI,MAAM,SAAwB,CAAC,CAAC;AAChF,QAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAAS,KAAK;AAC9D,QAAM,EAAE,MAAM,cAAc,WAAW,iBAAiB,IAAI,mBAAmB,mBAAmB,CAAC,gBAAgB,IAAI,CAAC,GAAG;AAAA,IACzH,SAAS,CAAC,CAAC;AAAA,EACb,CAAC;AACD,QAAM,YAAY,gBAAgB;AAClC,QAAM,mBAAmB,MAAM,QAA2B,MAAM;AAC9D,UAAM,UAA6B,CAAC;AACpC,eAAW,UAAU,kBAAkB;AACrC,UAAI,CAAC,OAAO,MAAO;AACnB,UAAI,QAAQ,KAAK,CAAC,QAAQ,IAAI,UAAU,OAAO,KAAK,EAAG;AACvD,cAAQ,KAAK,MAAM;AAAA,IACrB;AACA,eAAW,OAAO,WAAW;AAC3B,YAAM,MAAM,OAAO,IAAI,QAAQ,WAAW,IAAI,IAAI,KAAK,IAAI;AAC3D,UAAI,CAAC,OAAO,QAAQ,KAAK,CAAC,QAAQ,IAAI,UAAU,GAAG,EAAG;AACtD,YAAM,QAAQ,OAAO,IAAI,UAAU,WAAW,IAAI,QAAQ;AAC1D,cAAQ,KAAK,EAAE,OAAO,KAAK,MAAM,CAAC;AAAA,IACpC;AACA,WAAO;AAAA,EACT,GAAG,CAAC,kBAAkB,SAAS,CAAC;AAChC,QAAM,eAAe,MAAM,QAAQ,MAAM;AACvC,UAAM,UAAU,oBAAI,IAAoB;AACxC,eAAW,UAAU,kBAAkB;AACrC,UAAI,CAAC,OAAO,SAAS,QAAQ,IAAI,OAAO,KAAK,EAAG;AAChD,cAAQ,IAAI,OAAO,OAAO,OAAO,SAAS,OAAO,KAAK;AAAA,IACxD;AACA,eAAW,OAAO,WAAW;AAC3B,YAAM,MAAM,OAAO,IAAI,QAAQ,WAAW,IAAI,IAAI,KAAK,IAAI;AAC3D,UAAI,CAAC,OAAO,QAAQ,IAAI,GAAG,EAAG;AAC9B,YAAM,QAAQ,OAAO,IAAI,UAAU,YAAY,IAAI,MAAM,KAAK,EAAE,SAAS,IAAI,QAAQ;AACrF,cAAQ,IAAI,KAAK,KAAK;AAAA,IACxB;AACA,eAAW,OAAO,QAAQ;AACxB,YAAM,OAAO,IAAI,OAAO,KAAK;AAC7B,UAAI,QAAQ,CAAC,QAAQ,IAAI,IAAI,EAAG,SAAQ,IAAI,MAAM,IAAI;AACtD,YAAM,OAAO,IAAI,WAAW,KAAK;AACjC,UAAI,QAAQ,CAAC,QAAQ,IAAI,IAAI,EAAG,SAAQ,IAAI,MAAM,IAAI;AAAA,IACxD;AACA,WAAO,MAAM,KAAK,QAAQ,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,OAAO,KAAK,OAAO,EAAE,OAAO,MAAM,EAAE;AAAA,EACjF,GAAG,CAAC,kBAAkB,WAAW,MAAM,CAAC;AAExC,QAAM,EAAE,MAAM,UAAU,WAAW,iBAAiB,OAAO,cAAc,IAAI,SAAoC;AAAA,IAC/G,UAAU,CAAC,iBAAiB,YAAY;AAAA,IACxC,SAAS,YACP,qBAAqB,0BAA0B,QAAW;AAAA,MACxD,cAAc,EAAE,2CAA2C,yBAAyB;AAAA,IACtF,CAAC;AAAA,EACL,CAAC;AAED,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,oBAAoB,UAAU,OAAO,QAAQ;AAChD,YAAM,QAAQ,SAAS,MAAM,CAAC;AAC9B,0BAAoB,MAAM,QAAQ;AAAA,IACpC;AAAA,EACF,GAAG,CAAC,UAAU,gBAAgB,CAAC;AAE/B,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,kBAAkB;AACrB,0BAAoB,CAAC,SAAU,KAAK,SAAS,CAAC,IAAI,IAAK;AACvD;AAAA,IACF;AACA,UAAM,QAAQ,iBAAiB,MAAM,GAAG;AACxC,UAAM,aAAa,MAAM,CAAC;AAC1B,QAAI,CAAC,YAAY;AACf,0BAAoB,CAAC,SAAU,KAAK,SAAS,CAAC,IAAI,IAAK;AACvD;AAAA,IACF;AAGA,UAAM,MAAM,gBAAgB,UAAU;AACtC,QAAI,CAAC,KAAK;AACR,cAAQ,KAAK,2CAA2C,UAAU;AAClE,0BAAoB,CAAC,SAAU,KAAK,SAAS,CAAC,IAAI,IAAK;AACvD;AAAA,IACF;AAEA,UAAM,UAAyB,CAAC;AAChC,eAAW,OAAO,OAAO,OAAO,GAAG,GAAG;AACpC,UAAI,OAAO,QAAQ,YAAY,CAAC,IAAI,KAAK,EAAG;AAC5C,YAAM,QAAQ,IAAI,KAAK;AACvB,UAAI,QAAQ,KAAK,CAAC,QAAQ,IAAI,UAAU,KAAK,EAAG;AAChD,YAAM,QAAQ,MACX,MAAM,GAAG,EACT,IAAI,CAAC,YAAa,UAAU,GAAG,QAAQ,CAAC,EAAE,YAAY,CAAC,GAAG,QAAQ,MAAM,CAAC,CAAC,KAAK,EAAG,EAClF,KAAK,GAAG,EACR,KAAK,KAAK;AACb,cAAQ,KAAK,EAAE,OAAO,MAAM,CAAC;AAAA,IAC/B;AACA,wBAAoB,CAAC,SAAU,qBAAqB,MAAM,OAAO,IAAI,OAAO,OAAQ;AAAA,EACtF,GAAG,CAAC,gBAAgB,CAAC;AAErB,QAAM;AAAA,IACJ,MAAM;AAAA,IACN,WAAW;AAAA,IACX,SAAS;AAAA,IACT,SAAS;AAAA,EACX,IAAI,SAAgC;AAAA,IAClC,UAAU,CAAC,kBAAkB,kBAAkB,YAAY;AAAA,IAC3D,SAAS,CAAC,CAAC;AAAA,IACX,SAAS,YACP,qBAAqB,qCAAqC,mBAAmB,gBAAgB,CAAC,IAAI,QAAW;AAAA,MAC3G,cAAc,EAAE,sCAAsC,+BAA+B;AAAA,IACvF,CAAC;AAAA,EACL,CAAC;AAED,QAAM,eAAe,MAAM,QAAQ,MAAM,KAAK,UAAU,OAAO,IAAI,GAAG,CAAC,GAAG,CAAC;AAC3E,QAAM,sBAAsB,MAAM,OAAsB,IAAI;AAE5D,QAAM,UAAU,MAAM;AACpB,UAAM,YAAY;AAClB,UAAM,YAAY,cAAc,oBAAoB;AACpD,QAAI,aAAa,cAAe;AAChC,QAAI,CAAC,KAAK;AACR,gBAAU,CAAC,SAAU,KAAK,SAAS,CAAC,IAAI,IAAK;AAC7C,kBAAY,CAAC,SAAU,SAAS,OAAO,OAAO,IAAK;AACnD,uBAAiB,KAAK;AACtB,0BAAoB,UAAU;AAC9B;AAAA,IACF;AACA,UAAM,aAAa,mBAAmB,KAAK,gBAAgB;AAC3D,cAAU,CAAC,SAAU,kBAAkB,MAAM,UAAU,IAAI,OAAO,UAAW;AAC7E,UAAM,eAAe,KAAK,aAAa;AACvC,gBAAY,CAAC,SAAU,SAAS,eAAe,OAAO,YAAa;AACnE,qBAAiB,KAAK;AACtB,wBAAoB,UAAU;AAAA,EAChC,GAAG,CAAC,cAAc,KAAK,kBAAkB,aAAa,CAAC;AAEvD,QAAM,WAAW,YAAY;AAAA,IAC3B,YAAY,YAAY;AACtB,YAAM,UAAU,OACb,IAAI,CAAC,SAAS;AAAA,QACb,OAAO,sBAAsB,IAAI,OAAO,gBAAgB,EAAE,KAAK;AAAA,QAC/D,WAAW,IAAI,YAAY,sBAAsB,IAAI,WAAW,gBAAgB,EAAE,KAAK,IAAI;AAAA,MAC7F,EAAE,EACD,OAAO,CAAC,QAAQ,IAAI,MAAM,SAAS,CAAC,EACpC,IAAI,CAAC,SAAS;AAAA,QACb,OAAO,IAAI;AAAA,QACX,WAAW,IAAI,YAAY,IAAI,YAAY;AAAA,MAC7C,EAAE;AACJ,UAAI,CAAC,kBAAkB;AACrB,cAAM,IAAI,MAAM,EAAE,2CAA2C,gCAAgC,CAAC;AAAA,MAChG;AACA,UAAI,CAAC,QAAQ,QAAQ;AACnB,cAAM,IAAI,MAAM,EAAE,uCAAuC,mCAAmC,CAAC;AAAA,MAC/F;AACA,YAAM,MAAM,MAAM,QAAQ,4BAA4B;AAAA,QACpD,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU,EAAE,UAAU,kBAAkB,QAAQ,SAAS,SAAS,CAAC;AAAA,MAChF,CAAC;AACD,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,eAAe,IAAI,UAAU,EAAE,mCAAmC,+BAA+B,CAAC;AAAA,MAC1G;AACA,aAAO;AAAA,IACT;AAAA,IACA,WAAW,MAAM;AACf,YAAM,EAAE,mCAAmC,sBAAsB,GAAG,SAAS;AAC7E,WAAK,WAAW;AAAA,IAClB;AAAA,IACA,SAAS,CAAC,QAAa;AACrB,YAAM,UAAU,KAAK,WAAW,EAAE,mCAAmC,+BAA+B;AACpG,YAAM,SAAS,OAAO;AAAA,IACxB;AAAA,EACF,CAAC;AAED,QAAM,WAAW,MAAM;AACrB,qBAAiB,IAAI;AACrB,cAAU,CAAC,SAAS,CAAC,GAAG,MAAM,EAAE,IAAI,WAAW,GAAG,OAAO,IAAI,WAAW,KAAK,CAAC,CAAC;AAAA,EACjF;AAEA,QAAM,cAAc,CAAC,IAAY,UAAuC;AACtE,qBAAiB,IAAI;AACrB,cAAU,CAAC,SAAS,KAAK,IAAI,CAAC,QAAS,IAAI,OAAO,KAAK,EAAE,GAAG,KAAK,GAAG,MAAM,IAAI,GAAI,CAAC;AAAA,EACrF;AAEA,QAAM,cAAc,CAAC,OAAe;AAClC,qBAAiB,IAAI;AACrB,cAAU,CAAC,SAAS,KAAK,OAAO,CAAC,QAAQ,IAAI,OAAO,EAAE,CAAC;AAAA,EACzD;AAEA,QAAM,eAAe,MAAM;AACzB,QAAI,cAAc,kBAAkB;AAClC,aACE;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,EAAE,+BAA+B,8BAAyB;AAAA,UACjE,WAAU;AAAA;AAAA,MACZ;AAAA,IAEJ;AACA,QAAI,UAAU;AACZ,aACE;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,EAAE,sCAAsC,+BAA+B;AAAA,UAC9E,QACE,oBAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,MAAK,MAAK,SAAS,MAAM,KAAK,WAAW,GAC9E,YAAE,qCAAqC,OAAO,GACjD;AAAA;AAAA,MAEJ;AAAA,IAEJ;AACA,QAAI,CAAC,OAAO,QAAQ;AAClB,aACE,oBAAC,SAAI,WAAU,qEACZ,YAAE,6BAA6B,uDAAuD,GACzF;AAAA,IAEJ;AACA,UAAM,qBAAqB,CAAC,UAA0B;AACpD,UAAI,SAAS,CAAC,aAAa,KAAK,CAAC,QAAQ,IAAI,UAAU,KAAK,GAAG;AAC7D,eAAO,CAAC,GAAG,cAAc,EAAE,OAAO,OAAO,MAAM,CAAC;AAAA,MAClD;AACA,aAAO;AAAA,IACT;AACA,WACE,oBAAC,SAAI,WAAU,mBACb,+BAAC,WAAM,WAAU,gCACf;AAAA,0BAAC,WACC,+BAAC,QAAG,WAAU,yDACZ;AAAA,4BAAC,QAAG,WAAU,uBACX,YAAE,oCAAoC,YAAY,GACrD;AAAA,QACA,oBAAC,QAAG,WAAU,uBACX,YAAE,mCAAmC,uBAAuB,GAC/D;AAAA,QACA,oBAAC,QAAG,WAAU,wBACX,YAAE,sCAAsC,SAAS,GACpD;AAAA,SACF,GACF;AAAA,MACA,oBAAC,WACE,iBAAO,IAAI,CAAC,QAAQ;AACnB,cAAM,YAAY,mBAAmB,IAAI,KAAK;AAC9C,cAAM,WAAW,mBAAmB,IAAI,SAAS;AACjD,eACE,qBAAC,QAAgB,WAAU,YACzB;AAAA,8BAAC,QAAG,WAAU,uBACZ;AAAA,YAAC;AAAA;AAAA,cACC,OAAO,IAAI,SAAS;AAAA,cACpB,eAAe,CAAC,UAAU,YAAY,IAAI,IAAI,EAAE,OAAO,SAAS,GAAG,CAAC;AAAA,cAEpE;AAAA,oCAAC,iBACC,8BAAC,eAAY,aAAa,EAAE,0CAA0C,cAAc,GAAG,GACzF;AAAA,gBACA,oBAAC,iBACE,oBAAU,IAAI,CAAC,WACd,oBAAC,cAA8B,OAAO,OAAO,OAC1C,iBAAO,SADO,OAAO,KAExB,CACD,GACH;AAAA;AAAA;AAAA,UACF,GACF;AAAA,UACA,qBAAC,QAAG,WAAU,uBACZ;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,OAAO,IAAI,aAAa;AAAA,gBACxB,eAAe,CAAC,UAAU,YAAY,IAAI,IAAI,EAAE,WAAW,SAAS,KAAK,CAAC;AAAA,gBAE1E;AAAA,sCAAC,iBACC,8BAAC,eAAY,aAAa,EAAE,yCAAyC,8BAA8B,GAAG,GACxG;AAAA,kBACA,oBAAC,iBACE,mBAAS,IAAI,CAAC,WACb,oBAAC,cAA8B,OAAO,OAAO,OAC1C,iBAAO,SADO,OAAO,KAExB,CACD,GACH;AAAA;AAAA;AAAA,YACF;AAAA,YACA,oBAAC,OAAE,WAAU,4CACV,YAAE,uCAAuC,kEAAkE,GAC9G;AAAA,aACF;AAAA,UACA,oBAAC,QAAG,WAAU,kCACZ;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,WAAU;AAAA,cACV,cAAY,EAAE,sCAAsC,QAAQ;AAAA,cAC5D,SAAS,MAAM,YAAY,IAAI,EAAE;AAAA,cAEjC,8BAAC,UAAO,WAAU,WAAU;AAAA;AAAA,UAC9B,GACF;AAAA,aAjDO,IAAI,EAkDb;AAAA,MAEJ,CAAC,GACH;AAAA,OACF,GACF;AAAA,EAEJ;AAEA,SACE,oBAAC,SAAI,WAAU,aACb,+BAAC,SAAI,WAAU,+DACb;AAAA,yBAAC,SAAI,WAAU,aACb;AAAA,0BAAC,QAAG,WAAU,yBAAyB,YAAE,6BAA6B,YAAY,GAAE;AAAA,MACpF,oBAAC,OAAE,WAAU,iCACV,YAAE,mCAAmC,sFAAsF,GAC9H;AAAA,OACF;AAAA,IACA,oBAAC,SAAI,WAAU,gDACb,+BAAC,SAAI,WAAU,oBACb;AAAA,2BAAC,SACC;AAAA,4BAAC,WAAM,WAAU,iCACd,YAAE,oCAAoC,eAAe,GACxD;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO,oBAAoB;AAAA,YAC3B,eAAe,CAAC,UAAU,oBAAoB,SAAS,EAAE;AAAA,YACzD,UAAU,mBAAmB,CAAC,CAAC;AAAA,YAE/B;AAAA,kCAAC,iBAAc,WAAU,QACvB,8BAAC,eAAY,aAAa,EAAE,mCAAmC,kBAAkB,GAAG,GACtF;AAAA,cACA,oBAAC,iBACG,qBAAU,SAAS,CAAC,GAAG,IAAI,CAAC,SAC5B,qBAAC,cAA+B,OAAO,KAAK,UACzC;AAAA,qBAAK,SAAS,KAAK;AAAA,gBAAS;AAAA,gBAAE,KAAK,WAAW,WAAW,IAAI,EAAE,qCAAqC,QAAQ,CAAC,MAAM;AAAA,mBADrG,KAAK,QAEtB,CACD,GACH;AAAA;AAAA;AAAA,QACF;AAAA,QACC,gBACC,oBAAC,OAAE,WAAU,uCACV,YAAE,2CAA2C,yBAAyB,GACzE,IAEA,oBAAC,OAAE,WAAU,sCACV,YAAE,kCAAkC,yEAAyE,GAChH;AAAA,SAEJ;AAAA,MACA,qBAAC,SAAI,WAAU,0CACb;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,IAAG;AAAA,YACH,SAAS;AAAA,YACT,iBAAiB,CAAC,YAAY,YAAY,YAAY,IAAI;AAAA;AAAA,QAC5D;AAAA,QACA,oBAAC,WAAM,SAAQ,8BACZ,YAAE,8BAA8B,oCAAoC,GACvE;AAAA,SACF;AAAA,OACF,GACF;AAAA,IACA,qBAAC,SAAI,WAAU,0CACb;AAAA,2BAAC,SAAI,WAAU,0CACb;AAAA,6BAAC,SACC;AAAA,8BAAC,QAAG,WAAU,uBAAuB,YAAE,oCAAoC,kBAAkB,GAAE;AAAA,UAC/F,oBAAC,OAAE,WAAU,iCACV,YAAE,uCAAuC,mEAAmE,GAC/G;AAAA,WACF;AAAA,QACA,qBAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,MAAK,MAAK,SAAS,UACzD;AAAA,8BAAC,QAAK,WAAU,gBAAe;AAAA,UAC9B,EAAE,mCAAmC,WAAW;AAAA,WACnD;AAAA,SACF;AAAA,MACC,aAAa;AAAA,OAChB;AAAA,IACA,oBAAC,SAAI,WAAU,oBACb;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAS,MAAM,SAAS,OAAO;AAAA,QAC/B,UAAU,SAAS,aAAa,mBAAmB,CAAC,CAAC,iBAAiB,CAAC;AAAA,QAEvE;AAAA,8BAAC,QAAK,WAAU,gBAAe;AAAA,UAC9B,SAAS,YACN,EAAE,sCAAsC,cAAS,IACjD,EAAE,oCAAoC,qBAAqB;AAAA;AAAA;AAAA,IACjE,GACF;AAAA,KACF,GACF;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/inbox_ops/lib/llmProvider.ts"],
|
|
4
|
-
"sourcesContent": ["import { generateObject } from 'ai'\nimport type { AwilixContainer } from 'awilix'\nimport { createContainer } from 'awilix'\nimport {\n resolveAiProviderIdFromEnv,\n resolveFirstConfiguredOpenCodeProvider,\n resolveOpenCodeModel,\n requireOpenCodeProviderApiKey,\n resolveOpenCodeProviderId,\n type OpenCodeProviderId,\n} from '@open-mercato/shared/lib/ai/opencode-provider'\nimport {\n AiModelFactoryError,\n createModelFactory,\n type AiModelFactory,\n} from '@open-mercato/ai-assistant/modules/ai_assistant/lib/model-factory'\nimport { extractionOutputSchema } from '../data/validators'\n\n// Vercel AI SDK provider factories return LanguageModelV1 but generateObject()\n// expects a narrower LanguageModel union. The types are structurally compatible\n// at runtime; the cast is required until the AI SDK unifies its model types.\ntype AiModel = Parameters<typeof generateObject>[0]['model']\nfunction asAiModel(model: unknown): AiModel {\n return model as AiModel\n}\n\n/**\n * Step 5.1 \u2014 thin backward-compatibility shim. The public surface of this\n * module (`resolveExtractionProviderId`, `createStructuredModel`,\n * `withTimeout`, `runExtractionWithConfiguredProvider`) is unchanged so\n * `ai-tools.ts`, `translationProvider.ts`, and `extractionWorker.ts` continue\n * to compile and pass their existing tests.\n *\n * The model-instantiation path inside {@link runExtractionWithConfiguredProvider}\n * now delegates to the shared {@link createModelFactory} so every\n * AI-runtime caller shares one resolution order. The legacy\n * `OPENCODE_MODEL` / `OPENCODE_PROVIDER` envs remain honored via\n * {@link resolveExtractionProviderId} and {@link resolveOpenCodeModel} so\n * inbox_ops deployments do not see a behavior change \u2014 the factory is\n * consulted first (honoring `OM_AI_INBOX_OPS_MODEL` \u2014 legacy `INBOX_OPS_AI_MODEL` \u2014 + `input.modelOverride`),\n * with the legacy path as the fallback when no registry provider is\n * configured (preserving the historical error messages).\n */\n\nexport function resolveExtractionProviderId(): OpenCodeProviderId {\n // Honors OM_AI_PROVIDER first, then the legacy OPENCODE_PROVIDER, then the\n // first configured provider from `OPEN_CODE_PROVIDER_IDS`, then the unified\n // default (currently `openai`). Mirrors the precedence applied by the\n // shared model factory so the BC fallback path stays consistent.\n const explicit = (process.env.OM_AI_PROVIDER ?? process.env.OPENCODE_PROVIDER ?? '').trim()\n if (explicit.length > 0) {\n return resolveAiProviderIdFromEnv(process.env)\n }\n\n const firstConfiguredProvider = resolveFirstConfiguredOpenCodeProvider()\n if (firstConfiguredProvider) {\n return firstConfiguredProvider\n }\n\n return resolveOpenCodeProviderId(undefined)\n}\n\nexport async function createStructuredModel(\n providerId: OpenCodeProviderId,\n apiKey: string,\n modelId: string,\n): Promise<AiModel> {\n switch (providerId) {\n case 'anthropic': {\n const { createAnthropic } = await import('@ai-sdk/anthropic')\n return asAiModel(createAnthropic({ apiKey })(modelId))\n }\n case 'openai': {\n const { createOpenAI } = await import('@ai-sdk/openai')\n return asAiModel(createOpenAI({ apiKey })(modelId))\n }\n case 'google': {\n const { createGoogleGenerativeAI } = await import('@ai-sdk/google')\n return asAiModel(createGoogleGenerativeAI({ apiKey })(modelId))\n }\n default:\n throw new Error(`Unsupported provider: ${providerId}`)\n }\n}\n\nexport async function withTimeout<T>(\n operation: Promise<T>,\n timeoutMs: number,\n timeoutMessage: string,\n): Promise<T> {\n let timeoutHandle: ReturnType<typeof setTimeout> | undefined\n\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutHandle = setTimeout(() => reject(new Error(timeoutMessage)), timeoutMs)\n })\n\n try {\n return await Promise.race([operation, timeoutPromise])\n } finally {\n if (timeoutHandle) {\n clearTimeout(timeoutHandle)\n }\n }\n}\n\n/**\n * Test-only seam for the factory-delegation regression suite. Production\n * callers MUST use {@link runExtractionWithConfiguredProvider} directly; the\n * suite overrides this binding via `jest.spyOn` to assert the shim actually\n * reaches `createModelFactory` without stubbing `@open-mercato/ai-assistant`.\n */\nexport const __inboxOpsLlmProviderInternal = {\n createModelFactory,\n createContainer,\n}\n\nfunction tryFactoryResolution(input: {\n modelOverride?: string | null\n}): { modelId: string; providerId: OpenCodeProviderId; model: AiModel } | null {\n let factory: AiModelFactory\n try {\n const container = __inboxOpsLlmProviderInternal.createContainer()\n factory = __inboxOpsLlmProviderInternal.createModelFactory(container as AwilixContainer)\n } catch {\n return null\n }\n try {\n const resolution = factory.resolveModel({\n moduleId: 'inbox_ops',\n callerOverride: input.modelOverride ?? undefined,\n })\n const providerId = resolveOpenCodeProviderId(resolution.providerId)\n return {\n modelId: resolution.modelId,\n providerId,\n model: asAiModel(resolution.model),\n }\n } catch (err) {\n if (err instanceof AiModelFactoryError) {\n // Fall back to the legacy path so the shim keeps throwing the original\n // OPENCODE_*-era error messages existing tests/consumers rely on.\n return null\n }\n throw err\n }\n}\n\nexport async function runExtractionWithConfiguredProvider(input: {\n systemPrompt: string\n userPrompt: string\n modelOverride?: string | null\n timeoutMs: number\n}): Promise<{\n object: ReturnType<typeof extractionOutputSchema.parse>\n totalTokens: number\n modelWithProvider: string\n}> {\n const factoryResolution = tryFactoryResolution({ modelOverride: input.modelOverride })\n\n let model: AiModel\n let modelWithProvider: string\n if (factoryResolution) {\n model = factoryResolution.model\n modelWithProvider = `${factoryResolution.providerId}/${factoryResolution.modelId}`\n } else {\n const providerId = resolveExtractionProviderId()\n const apiKey = requireOpenCodeProviderApiKey(providerId)\n const modelConfig = resolveOpenCodeModel(providerId, {\n overrideModel: input.modelOverride,\n })\n model = await createStructuredModel(providerId, apiKey, modelConfig.modelId)\n modelWithProvider = modelConfig.modelWithProvider\n }\n\n const result = await withTimeout(\n generateObject({\n model,\n schema: extractionOutputSchema,\n system: input.systemPrompt,\n prompt: input.userPrompt,\n temperature: 0,\n }),\n input.timeoutMs,\n `LLM extraction timed out after ${input.timeoutMs}ms`,\n )\n\n return {\n object: result.object,\n totalTokens: Number(result.usage?.totalTokens ?? 0) || 0,\n modelWithProvider,\n }\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,sBAAsB;AAE/B,SAAS,uBAAuB;AAChC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AACP,SAAS,8BAA8B;AAMvC,SAAS,UAAU,OAAyB;AAC1C,SAAO;AACT;AAoBO,SAAS,8BAAkD;AAKhE,QAAM,YAAY,QAAQ,IAAI,kBAAkB,QAAQ,IAAI,qBAAqB,IAAI,KAAK;AAC1F,MAAI,SAAS,SAAS,GAAG;AACvB,WAAO,2BAA2B,QAAQ,GAAG;AAAA,EAC/C;AAEA,QAAM,0BAA0B,uCAAuC;AACvE,MAAI,yBAAyB;AAC3B,WAAO;AAAA,EACT;AAEA,SAAO,0BAA0B,MAAS;AAC5C;AAEA,eAAsB,sBACpB,YACA,QACA,SACkB;AAClB,UAAQ,YAAY;AAAA,IAClB,KAAK,aAAa;AAChB,YAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,mBAAmB;AAC5D,aAAO,UAAU,gBAAgB,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC;AAAA,IACvD;AAAA,IACA,KAAK,UAAU;AACb,YAAM,EAAE,aAAa,IAAI,MAAM,OAAO,gBAAgB;AACtD,aAAO,UAAU,aAAa,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC;AAAA,IACpD;AAAA,IACA,KAAK,UAAU;AACb,YAAM,EAAE,yBAAyB,IAAI,MAAM,OAAO,gBAAgB;AAClE,aAAO,UAAU,yBAAyB,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC;AAAA,IAChE;AAAA,IACA;AACE,YAAM,IAAI,MAAM,yBAAyB,UAAU,EAAE;AAAA,EACzD;AACF;AAEA,eAAsB,YACpB,WACA,WACA,gBACY;AACZ,MAAI;AAEJ,QAAM,iBAAiB,IAAI,QAAe,CAAC,GAAG,WAAW;AACvD,oBAAgB,WAAW,MAAM,OAAO,IAAI,MAAM,cAAc,CAAC,GAAG,SAAS;AAAA,EAC/E,CAAC;AAED,MAAI;AACF,WAAO,MAAM,QAAQ,KAAK,CAAC,WAAW,cAAc,CAAC;AAAA,EACvD,UAAE;AACA,QAAI,eAAe;AACjB,mBAAa,aAAa;AAAA,IAC5B;AAAA,EACF;AACF;AAQO,MAAM,gCAAgC;AAAA,EAC3C;AAAA,EACA;AACF;AAEA,SAAS,qBAAqB,OAEiD;AAC7E,MAAI;AACJ,MAAI;AACF,UAAM,YAAY,8BAA8B,gBAAgB;AAChE,cAAU,8BAA8B,mBAAmB,SAA4B;AAAA,EACzF,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI;AACF,UAAM,aAAa,QAAQ,aAAa;AAAA,MACtC,UAAU;AAAA,MACV,gBAAgB,MAAM,iBAAiB;AAAA,IACzC,CAAC;AACD,UAAM,aAAa,0BAA0B,WAAW,UAAU;AAClE,WAAO;AAAA,MACL,SAAS,WAAW;AAAA,MACpB;AAAA,MACA,OAAO,UAAU,WAAW,KAAK;AAAA,IACnC;AAAA,EACF,SAAS,KAAK;AACZ,QAAI,eAAe,qBAAqB;AAGtC,aAAO;AAAA,IACT;AACA,UAAM;AAAA,EACR;AACF;AAEA,eAAsB,oCAAoC,OASvD;AACD,QAAM,oBAAoB,qBAAqB,EAAE,eAAe,MAAM,cAAc,CAAC;AAErF,MAAI;AACJ,MAAI;AACJ,MAAI,mBAAmB;AACrB,YAAQ,kBAAkB;AAC1B,wBAAoB,GAAG,kBAAkB,UAAU,IAAI,kBAAkB,OAAO;AAAA,EAClF,OAAO;
|
|
4
|
+
"sourcesContent": ["import { generateObject } from 'ai'\nimport type { AwilixContainer } from 'awilix'\nimport { createContainer } from 'awilix'\nimport {\n resolveAiProviderIdFromEnv,\n resolveFirstConfiguredOpenCodeProvider,\n resolveOpenCodeModel,\n requireOpenCodeProviderApiKey,\n resolveOpenCodeProviderId,\n type OpenCodeProviderId,\n} from '@open-mercato/shared/lib/ai/opencode-provider'\nimport {\n AiModelFactoryError,\n createModelFactory,\n type AiModelFactory,\n} from '@open-mercato/ai-assistant/modules/ai_assistant/lib/model-factory'\nimport { extractionOutputSchema } from '../data/validators'\n\n// Vercel AI SDK provider factories return LanguageModelV1 but generateObject()\n// expects a narrower LanguageModel union. The types are structurally compatible\n// at runtime; the cast is required until the AI SDK unifies its model types.\ntype AiModel = Parameters<typeof generateObject>[0]['model']\nfunction asAiModel(model: unknown): AiModel {\n return model as AiModel\n}\n\n/**\n * Step 5.1 \u2014 thin backward-compatibility shim. The public surface of this\n * module (`resolveExtractionProviderId`, `createStructuredModel`,\n * `withTimeout`, `runExtractionWithConfiguredProvider`) is unchanged so\n * `ai-tools.ts`, `translationProvider.ts`, and `extractionWorker.ts` continue\n * to compile and pass their existing tests.\n *\n * The model-instantiation path inside {@link runExtractionWithConfiguredProvider}\n * now delegates to the shared {@link createModelFactory} so every\n * AI-runtime caller shares one resolution order. The legacy\n * `OPENCODE_MODEL` / `OPENCODE_PROVIDER` envs remain honored via\n * {@link resolveExtractionProviderId} and {@link resolveOpenCodeModel} so\n * inbox_ops deployments do not see a behavior change \u2014 the factory is\n * consulted first (honoring `OM_AI_INBOX_OPS_MODEL` \u2014 legacy `INBOX_OPS_AI_MODEL` \u2014 + `input.modelOverride`),\n * with the legacy path as the fallback when no registry provider is\n * configured (preserving the historical error messages).\n */\n\nexport function resolveExtractionProviderId(): OpenCodeProviderId {\n // Honors OM_AI_PROVIDER first, then the legacy OPENCODE_PROVIDER, then the\n // first configured provider from `OPEN_CODE_PROVIDER_IDS`, then the unified\n // default (currently `openai`). Mirrors the precedence applied by the\n // shared model factory so the BC fallback path stays consistent.\n const explicit = (process.env.OM_AI_PROVIDER ?? process.env.OPENCODE_PROVIDER ?? '').trim()\n if (explicit.length > 0) {\n return resolveAiProviderIdFromEnv(process.env)\n }\n\n const firstConfiguredProvider = resolveFirstConfiguredOpenCodeProvider()\n if (firstConfiguredProvider) {\n return firstConfiguredProvider\n }\n\n return resolveOpenCodeProviderId(undefined)\n}\n\nexport async function createStructuredModel(\n providerId: OpenCodeProviderId,\n apiKey: string,\n modelId: string,\n): Promise<AiModel> {\n switch (providerId) {\n case 'anthropic': {\n const { createAnthropic } = await import('@ai-sdk/anthropic')\n return asAiModel(createAnthropic({ apiKey })(modelId))\n }\n case 'openai': {\n const { createOpenAI } = await import('@ai-sdk/openai')\n return asAiModel(createOpenAI({ apiKey })(modelId))\n }\n case 'google': {\n const { createGoogleGenerativeAI } = await import('@ai-sdk/google')\n return asAiModel(createGoogleGenerativeAI({ apiKey })(modelId))\n }\n default:\n throw new Error(`Unsupported provider: ${providerId}`)\n }\n}\n\nexport async function withTimeout<T>(\n operation: Promise<T>,\n timeoutMs: number,\n timeoutMessage: string,\n): Promise<T> {\n let timeoutHandle: ReturnType<typeof setTimeout> | undefined\n\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutHandle = setTimeout(() => reject(new Error(timeoutMessage)), timeoutMs)\n })\n\n try {\n return await Promise.race([operation, timeoutPromise])\n } finally {\n if (timeoutHandle) {\n clearTimeout(timeoutHandle)\n }\n }\n}\n\n/**\n * Test-only seam for the factory-delegation regression suite. Production\n * callers MUST use {@link runExtractionWithConfiguredProvider} directly; the\n * suite overrides this binding via `jest.spyOn` to assert the shim actually\n * reaches `createModelFactory` without stubbing `@open-mercato/ai-assistant`.\n */\nexport const __inboxOpsLlmProviderInternal = {\n createModelFactory,\n createContainer,\n}\n\nfunction tryFactoryResolution(input: {\n modelOverride?: string | null\n}): { modelId: string; providerId: OpenCodeProviderId; model: AiModel } | null {\n let factory: AiModelFactory\n try {\n const container = __inboxOpsLlmProviderInternal.createContainer()\n factory = __inboxOpsLlmProviderInternal.createModelFactory(container as AwilixContainer)\n } catch {\n return null\n }\n try {\n const resolution = factory.resolveModel({\n moduleId: 'inbox_ops',\n callerOverride: input.modelOverride ?? undefined,\n })\n const providerId = resolveOpenCodeProviderId(resolution.providerId)\n return {\n modelId: resolution.modelId,\n providerId,\n model: asAiModel(resolution.model),\n }\n } catch (err) {\n if (err instanceof AiModelFactoryError) {\n // Fall back to the legacy path so the shim keeps throwing the original\n // OPENCODE_*-era error messages existing tests/consumers rely on.\n return null\n }\n throw err\n }\n}\n\nexport async function runExtractionWithConfiguredProvider(input: {\n systemPrompt: string\n userPrompt: string\n modelOverride?: string | null\n timeoutMs: number\n}): Promise<{\n object: ReturnType<typeof extractionOutputSchema.parse>\n totalTokens: number\n modelWithProvider: string\n}> {\n const factoryResolution = tryFactoryResolution({ modelOverride: input.modelOverride })\n\n let model: AiModel\n let modelWithProvider: string\n if (factoryResolution) {\n model = factoryResolution.model\n modelWithProvider = `${factoryResolution.providerId}/${factoryResolution.modelId}`\n } else {\n // BC: Legacy OPENCODE_PROVIDER / OPENCODE_MODEL path. This branch only\n // runs when createModelFactory throws AiModelFactoryError('no_provider_configured'),\n // meaning none of the new-style provider env vars (ANTHROPIC_API_KEY,\n // OPENAI_API_KEY, GOOGLE_GENERATIVE_AI_API_KEY, OM_AI_PROVIDER, ...) is\n // set. The OPENCODE_* envs are deliberately kept as BC fallbacks instead\n // of being treated as canonical OM_AI_* values because they are scoped to\n // the OpenCode Code Mode stack (spec\n // 2026-04-27-ai-agents-provider-model-baseurl-overrides, R1 mitigation).\n // Keeping this fallback alive preserves backward compatibility for existing\n // inbox_ops deployments that have not yet migrated to the new env vars.\n const providerId = resolveExtractionProviderId()\n const apiKey = requireOpenCodeProviderApiKey(providerId)\n const modelConfig = resolveOpenCodeModel(providerId, {\n overrideModel: input.modelOverride,\n })\n model = await createStructuredModel(providerId, apiKey, modelConfig.modelId)\n modelWithProvider = modelConfig.modelWithProvider\n }\n\n const result = await withTimeout(\n generateObject({\n model,\n schema: extractionOutputSchema,\n system: input.systemPrompt,\n prompt: input.userPrompt,\n temperature: 0,\n }),\n input.timeoutMs,\n `LLM extraction timed out after ${input.timeoutMs}ms`,\n )\n\n return {\n object: result.object,\n totalTokens: Number(result.usage?.totalTokens ?? 0) || 0,\n modelWithProvider,\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|