@things-factory/ai-assistant 10.1.104 → 10.1.106
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/client/components/assistant-chat.ts +2 -2
- package/client/utils/entry-proposal.ts +11 -0
- package/dist-client/components/assistant-chat.js +2 -2
- package/dist-client/components/assistant-chat.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/utils/entry-proposal.d.ts +5 -0
- package/dist-client/utils/entry-proposal.js +10 -0
- package/dist-client/utils/entry-proposal.js.map +1 -1
- package/dist-server/service/data-entry/entry-update.js +9 -2
- package/dist-server/service/data-entry/entry-update.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
- package/server/service/data-entry/data-entry-update.test.ts +7 -0
- package/server/service/data-entry/entry-update.ts +6 -2
- package/test/entry-proposal.test.ts +19 -0
- package/translations/en.json +2 -0
- package/translations/ja.json +2 -0
- package/translations/ko.json +2 -0
- package/translations/ms.json +2 -0
- package/translations/zh.json +2 -0
|
@@ -60,4 +60,9 @@ export declare function entryProposalHandler(apply: (sessionId: string, proposal
|
|
|
60
60
|
export declare function latestEntryKeys(proposalsInOrder: any[][]): Map<string, string>;
|
|
61
61
|
/** A 입력안 and a 수정안 of one record are counted apart - a newer one of the same kind replaces the older. */
|
|
62
62
|
export declare function entryGroupOf(proposal: any): string;
|
|
63
|
+
/**
|
|
64
|
+
* The card's title - the record's name from the target (`부서`, `거래처`) and the kind of card from here, so a
|
|
65
|
+
* 수정안 is never titled as a 입력안.
|
|
66
|
+
*/
|
|
67
|
+
export declare function entryTitle(proposal: any, t: (key: string, options?: any) => string): string;
|
|
63
68
|
export declare function isChangeProposal(proposal: any): boolean;
|
|
@@ -67,6 +67,16 @@ export function latestEntryKeys(proposalsInOrder) {
|
|
|
67
67
|
export function entryGroupOf(proposal) {
|
|
68
68
|
return `${proposal?.target}:${proposal?.op ?? 'create'}`;
|
|
69
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* The card's title - the record's name from the target (`부서`, `거래처`) and the kind of card from here, so a
|
|
72
|
+
* 수정안 is never titled as a 입력안.
|
|
73
|
+
*/
|
|
74
|
+
export function entryTitle(proposal, t) {
|
|
75
|
+
const name = proposal?.labelKey ? t(proposal.labelKey) : proposal?.target ?? '';
|
|
76
|
+
return proposal?.op === 'update'
|
|
77
|
+
? t('ai-assistant.entry.title-update', { defaultValue: '{name} to change', name })
|
|
78
|
+
: t('ai-assistant.entry.title-create', { defaultValue: '{name} to enter', name });
|
|
79
|
+
}
|
|
70
80
|
export function isChangeProposal(proposal) {
|
|
71
81
|
return isEntryProposal(proposal) && proposal.op === 'update';
|
|
72
82
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entry-proposal.js","sourceRoot":"","sources":["../../client/utils/entry-proposal.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The data entry card's rules and its handler (operato-application/design/ai/data-entry.md §5).\n *\n * The card is a review, not an editor: a correction is said in the conversation and the model proposes\n * again - the newer card replaces the older one. Pressing asks the server to enter the stored rows; the\n * screen sends no rows of its own.\n */\nimport type { ProposalContext, ProposalOutcome } from './assistant-proposals.js'\n\nexport interface EntryIssueView {\n field?: string\n code: string\n messageKey?: string\n params?: Record<string, unknown>\n paramKeys?: Record<string, string>\n blocking: boolean\n}\n\nexport interface EntryRowView {\n index: number\n values: Record<string, unknown>\n sources: { attachmentId?: string; where?: string; person?: boolean }[]\n issues: EntryIssueView[]\n /** The server found this row's numbers and dates together on this line of the cited file. */\n evidence?: { attachmentId: string; line: number }\n}\n\nexport interface EntryTotalView {\n field: string\n all: number\n ready: number\n declared?: number\n declaredFound?: boolean\n difference?: number\n declaredBy?: 'model' | 'file'\n}\n\nexport interface EntryRowResult {\n index: number\n ok: boolean\n id?: string\n label?: string\n error?: string\n}\n\nexport interface EntryApplyAnswer extends ProposalOutcome {\n rows?: EntryRowResult[]\n /** Set by the handler from the card - which words the conversation line uses. */\n op?: string\n}\n\nexport function isEntryProposal(proposal: any): boolean {\n return proposal?.kind === 'entry' && Array.isArray(proposal?.rows)\n}\n\nexport function entryReadyCount(proposal: any): number {\n const rows: EntryRowView[] = proposal?.rows ?? []\n const blocked = (row: EntryRowView) => row.issues?.some(issue => issue.blocking)\n if (!proposal?.groupBy) return rows.filter(row => !blocked(row)).length\n /* Lines of one record go in together - a record with a line kept out is kept out whole, and its lines do not count. */\n const keyOf = (row: EntryRowView) => {\n const value: any = row.values?.[proposal.groupBy]\n return value === undefined || value === null || value === '' ? `row-${row.index}` : String(typeof value === 'object' ? value.id ?? value.text : value)\n }\n const out = new Set(rows.filter(blocked).map(keyOf))\n return rows.filter(row => !out.has(keyOf(row))).length\n}\n\n/** The i18n key of an issue - the target's own, or the framework's by code. */\nexport function entryIssueKey(issue: EntryIssueView): string {\n return issue.messageKey || `ai-assistant.entry.issue.${issue.code}`\n}\n\n/** The issues of one cell, and of the row as a whole (no field). */\nexport function entryCellIssues(row: EntryRowView, field?: string): EntryIssueView[] {\n return (row.issues ?? []).filter(issue => (field ? issue.field === field : !issue.field))\n}\n\n/** What stays in the conversation - the model reads it on its next turn, and the person sees what went in. */\nexport function entryOutcomeNote(answer: EntryApplyAnswer, t: (key: string, options?: any) => string): string {\n const refused = (answer.rows ?? []).filter(row => !row.ok)\n const head = answer.op === 'update'\n ? answer.applied\n ? t('ai-assistant.entry.changed', { defaultValue: 'changed {count} records.', count: answer.applied })\n : t('ai-assistant.entry.changed-none', { defaultValue: 'no record was changed.' })\n : answer.applied\n ? t('ai-assistant.entry.entered', { defaultValue: 'entered {count} rows.', count: answer.applied })\n : t('ai-assistant.entry.entered-none', { defaultValue: 'no row was entered.' })\n const lines = refused.slice(0, 20).map(row => t('ai-assistant.entry.row-refused', { defaultValue: 'row {n}: {reason}', n: row.index + 1, reason: row.error || '' }))\n const more = refused.length > 20 ? [t('ai-assistant.entry.more-refused', { defaultValue: '{count} more rows were not entered.', count: refused.length - 20 })] : []\n return [head, ...lines, ...more].join('\\n')\n}\n\n/** The handler every entry card runs - built with the mutation so the rules are tested without a server. */\nexport function entryProposalHandler(\n apply: (sessionId: string, proposalKey: string) => Promise<EntryApplyAnswer>,\n t: (key: string, options?: any) => string\n) {\n return async (proposal: any, context: ProposalContext): Promise<ProposalOutcome> => {\n if (!context.sessionId || !proposal?.key) return { verdict: 'unavailable', errorCode: 'no-conversation' }\n const answer = { ...(await apply(context.sessionId, proposal.key)), op: proposal.op }\n return {\n verdict: answer.verdict,\n ...(answer.errorCode ? { errorCode: answer.errorCode } : {}),\n applied: answer.applied ?? 0,\n missed: answer.missed ?? 0,\n note: entryOutcomeNote(answer, t)\n }\n }\n}\n\n/**\n * The newest card per record, over the conversation's proposals in order - an older one is replaced and\n * cannot be entered (the server refuses it as `superseded`; the card says so before anyone presses).\n */\nexport function latestEntryKeys(proposalsInOrder: any[][]): Map<string, string> {\n const latest = new Map<string, string>()\n for (const list of proposalsInOrder) for (const one of list ?? []) if (isEntryProposal(one) && one.key) latest.set(entryGroupOf(one), one.key)\n return latest\n}\n\n/** A 입력안 and a 수정안 of one record are counted apart - a newer one of the same kind replaces the older. */\nexport function entryGroupOf(proposal: any): string {\n return `${proposal?.target}:${proposal?.op ?? 'create'}`\n}\n\nexport function isChangeProposal(proposal: any): boolean {\n return isEntryProposal(proposal) && proposal.op === 'update'\n}\n"]}
|
|
1
|
+
{"version":3,"file":"entry-proposal.js","sourceRoot":"","sources":["../../client/utils/entry-proposal.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The data entry card's rules and its handler (operato-application/design/ai/data-entry.md §5).\n *\n * The card is a review, not an editor: a correction is said in the conversation and the model proposes\n * again - the newer card replaces the older one. Pressing asks the server to enter the stored rows; the\n * screen sends no rows of its own.\n */\nimport type { ProposalContext, ProposalOutcome } from './assistant-proposals.js'\n\nexport interface EntryIssueView {\n field?: string\n code: string\n messageKey?: string\n params?: Record<string, unknown>\n paramKeys?: Record<string, string>\n blocking: boolean\n}\n\nexport interface EntryRowView {\n index: number\n values: Record<string, unknown>\n sources: { attachmentId?: string; where?: string; person?: boolean }[]\n issues: EntryIssueView[]\n /** The server found this row's numbers and dates together on this line of the cited file. */\n evidence?: { attachmentId: string; line: number }\n}\n\nexport interface EntryTotalView {\n field: string\n all: number\n ready: number\n declared?: number\n declaredFound?: boolean\n difference?: number\n declaredBy?: 'model' | 'file'\n}\n\nexport interface EntryRowResult {\n index: number\n ok: boolean\n id?: string\n label?: string\n error?: string\n}\n\nexport interface EntryApplyAnswer extends ProposalOutcome {\n rows?: EntryRowResult[]\n /** Set by the handler from the card - which words the conversation line uses. */\n op?: string\n}\n\nexport function isEntryProposal(proposal: any): boolean {\n return proposal?.kind === 'entry' && Array.isArray(proposal?.rows)\n}\n\nexport function entryReadyCount(proposal: any): number {\n const rows: EntryRowView[] = proposal?.rows ?? []\n const blocked = (row: EntryRowView) => row.issues?.some(issue => issue.blocking)\n if (!proposal?.groupBy) return rows.filter(row => !blocked(row)).length\n /* Lines of one record go in together - a record with a line kept out is kept out whole, and its lines do not count. */\n const keyOf = (row: EntryRowView) => {\n const value: any = row.values?.[proposal.groupBy]\n return value === undefined || value === null || value === '' ? `row-${row.index}` : String(typeof value === 'object' ? value.id ?? value.text : value)\n }\n const out = new Set(rows.filter(blocked).map(keyOf))\n return rows.filter(row => !out.has(keyOf(row))).length\n}\n\n/** The i18n key of an issue - the target's own, or the framework's by code. */\nexport function entryIssueKey(issue: EntryIssueView): string {\n return issue.messageKey || `ai-assistant.entry.issue.${issue.code}`\n}\n\n/** The issues of one cell, and of the row as a whole (no field). */\nexport function entryCellIssues(row: EntryRowView, field?: string): EntryIssueView[] {\n return (row.issues ?? []).filter(issue => (field ? issue.field === field : !issue.field))\n}\n\n/** What stays in the conversation - the model reads it on its next turn, and the person sees what went in. */\nexport function entryOutcomeNote(answer: EntryApplyAnswer, t: (key: string, options?: any) => string): string {\n const refused = (answer.rows ?? []).filter(row => !row.ok)\n const head = answer.op === 'update'\n ? answer.applied\n ? t('ai-assistant.entry.changed', { defaultValue: 'changed {count} records.', count: answer.applied })\n : t('ai-assistant.entry.changed-none', { defaultValue: 'no record was changed.' })\n : answer.applied\n ? t('ai-assistant.entry.entered', { defaultValue: 'entered {count} rows.', count: answer.applied })\n : t('ai-assistant.entry.entered-none', { defaultValue: 'no row was entered.' })\n const lines = refused.slice(0, 20).map(row => t('ai-assistant.entry.row-refused', { defaultValue: 'row {n}: {reason}', n: row.index + 1, reason: row.error || '' }))\n const more = refused.length > 20 ? [t('ai-assistant.entry.more-refused', { defaultValue: '{count} more rows were not entered.', count: refused.length - 20 })] : []\n return [head, ...lines, ...more].join('\\n')\n}\n\n/** The handler every entry card runs - built with the mutation so the rules are tested without a server. */\nexport function entryProposalHandler(\n apply: (sessionId: string, proposalKey: string) => Promise<EntryApplyAnswer>,\n t: (key: string, options?: any) => string\n) {\n return async (proposal: any, context: ProposalContext): Promise<ProposalOutcome> => {\n if (!context.sessionId || !proposal?.key) return { verdict: 'unavailable', errorCode: 'no-conversation' }\n const answer = { ...(await apply(context.sessionId, proposal.key)), op: proposal.op }\n return {\n verdict: answer.verdict,\n ...(answer.errorCode ? { errorCode: answer.errorCode } : {}),\n applied: answer.applied ?? 0,\n missed: answer.missed ?? 0,\n note: entryOutcomeNote(answer, t)\n }\n }\n}\n\n/**\n * The newest card per record, over the conversation's proposals in order - an older one is replaced and\n * cannot be entered (the server refuses it as `superseded`; the card says so before anyone presses).\n */\nexport function latestEntryKeys(proposalsInOrder: any[][]): Map<string, string> {\n const latest = new Map<string, string>()\n for (const list of proposalsInOrder) for (const one of list ?? []) if (isEntryProposal(one) && one.key) latest.set(entryGroupOf(one), one.key)\n return latest\n}\n\n/** A 입력안 and a 수정안 of one record are counted apart - a newer one of the same kind replaces the older. */\nexport function entryGroupOf(proposal: any): string {\n return `${proposal?.target}:${proposal?.op ?? 'create'}`\n}\n\n/**\n * The card's title - the record's name from the target (`부서`, `거래처`) and the kind of card from here, so a\n * 수정안 is never titled as a 입력안.\n */\nexport function entryTitle(proposal: any, t: (key: string, options?: any) => string): string {\n const name = proposal?.labelKey ? t(proposal.labelKey) : proposal?.target ?? ''\n return proposal?.op === 'update'\n ? t('ai-assistant.entry.title-update', { defaultValue: '{name} to change', name })\n : t('ai-assistant.entry.title-create', { defaultValue: '{name} to enter', name })\n}\n\nexport function isChangeProposal(proposal: any): boolean {\n return isEntryProposal(proposal) && proposal.op === 'update'\n}\n"]}
|
|
@@ -101,8 +101,15 @@ function changeRows(target, picked, records) {
|
|
|
101
101
|
}
|
|
102
102
|
if (!record)
|
|
103
103
|
issues.push({ code: 'record-not-found', blocking: true });
|
|
104
|
-
else
|
|
105
|
-
|
|
104
|
+
else {
|
|
105
|
+
/* A field the model restated as it already is does not change - the card shows it as no change, and it is not written. */
|
|
106
|
+
const same = Object.keys(values).filter(name => fields.has(name) && !issues.some(issue => issue.field === name) && (0, entry_target_js_1.sameFieldValue)(fields.get(name), before[name], values[name]));
|
|
107
|
+
if (same.length && same.length === Object.keys(values).length)
|
|
108
|
+
issues.push({ code: 'no-change', blocking: true });
|
|
109
|
+
else
|
|
110
|
+
for (const name of same)
|
|
111
|
+
delete values[name], delete before[name];
|
|
112
|
+
}
|
|
106
113
|
if (!Object.keys(values).length && !issues.some(issue => issue.blocking))
|
|
107
114
|
issues.push({ code: 'nothing-to-change', blocking: true });
|
|
108
115
|
return { index, id: one.id, via: one.via, ...(record && target.update?.labelOf ? { label: target.update.labelOf(record) } : {}), before, values, issues };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entry-update.js","sourceRoot":"","sources":["../../../server/service/data-entry/entry-update.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Changing records that exist - the 수정안 (operato-application/design/ai/data-entry.md §5-1).\n *\n * The most expensive mistake is changing the wrong record, so which record is meant is settled here, by the\n * server, never by the model's memory. A record is picked by the screen (the one being looked at, the rows\n * selected) or by an id the find tool handed back; whichever way, it is read again through the application's\n * read query as the person, and a record they cannot see is not found. The card shows what is there now and\n * what it becomes; pressing reads it once more, and a field that moved in between keeps its row out.\n *\n * Pure but for the GraphQL runner, which comes in as an argument - the rules are tested without a server.\n */\nimport {\n changeToolName,\n findToolName,\n readFieldValue,\n resolveReferences,\n zonedDateTimes,\n sameFieldValue,\n type EntryField,\n type EntryIssue,\n type EntryRecord,\n type EntryTarget\n} from './entry-target.js'\n\n/** Runs one of the application's queries or mutations as the person. */\nexport type GraphqlRunner = (document: string, variables: Record<string, unknown>) => Promise<{ data?: any; errors?: readonly { message: string }[] }>\n\n/** How the record of a change was picked - said on the card, so the person sees the ground of it. */\nexport type ChangeVia = 'current' | 'selected' | 'id'\n\nexport interface ChangeRow {\n index: number\n id: string\n via: ChangeVia\n label?: string\n /** What the record holds now, for the fields that change - read by the server. */\n before: Record<string, unknown>\n values: Record<string, unknown>\n issues: EntryIssue[]\n}\n\nexport const MAX_CHANGES = 100\n\nconst blockedChange = (row: { issues: EntryIssue[] }) => row.issues.some(issue => issue.blocking)\n\n/** The fields a target lets change. */\nexport function changeableFields(target: EntryTarget): EntryField[] {\n const names = target.update?.fields\n return names ? target.fields.filter(field => names.includes(field.name)) : target.fields\n}\n\n/**\n * The records the changes name. `current` is the record on screen, `selected` every row selected there -\n * both from the surface's host context, which is **not trusted**: an id from it is only a candidate, read\n * through the application before anything is shown. Any other ref is an id, from the find tool.\n */\nexport function resolveChangeRefs(\n targetKey: string,\n changes: unknown,\n host: Record<string, any> | undefined\n): { picked: { id: string; via: ChangeVia; values: Record<string, unknown> }[]; unresolved: string[] } {\n const picked: { id: string; via: ChangeVia; values: Record<string, unknown> }[] = []\n const unresolved: string[] = []\n const mine = (one: any) => !one?.target || one.target === targetKey\n for (const change of Array.isArray(changes) ? changes : []) {\n const values = change?.values && typeof change.values === 'object' ? change.values : {}\n const ref = typeof change?.ref === 'string' ? change.ref.trim() : ''\n if (ref === 'current') {\n const id = mine(host?.record) ? host?.record?.id : undefined\n if (typeof id === 'string' && id) picked.push({ id, via: 'current', values })\n else unresolved.push('current')\n } else if (ref === 'selected') {\n const ids = mine(host?.selection) && Array.isArray(host?.selection?.ids) ? host!.selection.ids.filter((id: unknown) => typeof id === 'string' && id) : []\n if (ids.length) for (const id of ids) picked.push({ id, via: 'selected', values })\n else unresolved.push('selected')\n } else if (ref) {\n picked.push({ id: ref, via: 'id', values })\n } else {\n unresolved.push('no-ref')\n }\n }\n return { picked: picked.slice(0, MAX_CHANGES), unresolved }\n}\n\n/** Reads the records by id through the application's read query, as the person. */\nexport async function readRecords(target: EntryTarget, ids: string[], run: GraphqlRunner): Promise<Map<string, EntryRecord>> {\n const update = target.update!\n const answer = await run(update.read.query, update.read.variables([...new Set(ids)]))\n if (answer.errors?.length) throw new Error(answer.errors.map(one => one.message).join('; '))\n return new Map((update.read.recordsOf(answer.data, [...new Set(ids)]) ?? []).filter(record => record && typeof record.id === 'string').map(record => [record.id, record]))\n}\n\n/** The change rows against what the records hold now. */\nexport function changeRows(target: EntryTarget, picked: { id: string; via: ChangeVia; values: Record<string, unknown> }[], records: Map<string, EntryRecord>): ChangeRow[] {\n const fields = new Map(changeableFields(target).map(field => [field.name, field]))\n return picked.map((one, index): ChangeRow => {\n const record = records.get(one.id)\n const issues: EntryIssue[] = []\n const values: Record<string, unknown> = {}\n const before: Record<string, unknown> = {}\n for (const [name, value] of Object.entries(one.values)) {\n const field = fields.get(name)\n if (!field) {\n issues.push({ field: name, code: 'not-changeable', blocking: false })\n continue\n }\n const { read, code } = value === null || value === '' ? { read: null, code: undefined } : readFieldValue(field, value)\n if (read === undefined) {\n values[name] = value\n issues.push({ field: name, code: code!, params: code === 'not-in-list' ? { values: field.values } : undefined, blocking: true })\n } else {\n if (read === null && field.required) issues.push({ field: name, code: 'required', blocking: true })\n values[name] = read\n }\n if (record) before[name] = record[name] ?? null\n }\n if (!record) issues.push({ code: 'record-not-found', blocking: true })\n else if (Object.keys(values).length && Object.keys(values).every(name => sameFieldValue(fields.get(name)!, before[name], values[name])))\n issues.push({ code: 'no-change', blocking: true })\n if (!Object.keys(values).length && !issues.some(issue => issue.blocking)) issues.push({ code: 'nothing-to-change', blocking: true })\n return { index, id: one.id, via: one.via, ...(record && target.update?.labelOf ? { label: target.update.labelOf(record) } : {}), before, values, issues }\n })\n}\n\n/** What the card needs besides the rows - the fields that change, in the target's order. */\nfunction changedColumns(target: EntryTarget, rows: ChangeRow[]) {\n const used = new Set(rows.flatMap(row => Object.keys(row.values)))\n return changeableFields(target)\n .filter(field => used.has(field.name))\n .map(field => ({ name: field.name, type: field.type, ...(field.labelKey ? { labelKey: field.labelKey } : {}), ...(field.valueKeyPrefix ? { valueKeyPrefix: field.valueKeyPrefix } : {}) }))\n}\n\nexport async function proposeChanges(target: EntryTarget, args: any, context: any, run: GraphqlRunner) {\n const { picked, unresolved } = resolveChangeRefs(target.key, args?.changes, context?.host)\n if (!picked.length) {\n return {\n rejected: true,\n error: unresolved.length ? `unresolved-${unresolved[0]}` : 'no-changes',\n message:\n unresolved[0] === 'current' || unresolved[0] === 'selected'\n ? `the screen names no ${unresolved[0] === 'current' ? 'record' : 'selected rows'} of this kind. Ask the person which record, or call ${findToolName(target.key)}.`\n : `name each record: ref \"current\", \"selected\", or an id from ${findToolName(target.key)}. Never guess an id.`\n }\n }\n let records: Map<string, EntryRecord>\n try {\n records = await readRecords(target, picked.map(one => one.id), run)\n } catch (error: any) {\n return { rejected: true, error: 'read-failed', message: `could not read the records: ${error?.message ?? error}` }\n }\n const changed = await resolveReferences(changeableFields(target), zonedDateTimes(changeableFields(target), changeRows(target, picked, records), context?.state?.domain?.timezone), run)\n const rows = await withChangeChecks(target, changed, records, { state: context?.state })\n const blocked = rows.filter(blockedChange).length\n return {\n proposed: true,\n kind: 'entry',\n op: 'update',\n target: target.key,\n labelKey: target.labelKey,\n label: `${rows.length} ${target.key} changes`,\n icon: 'edit_note',\n columns: changedColumns(target, rows),\n rows,\n counts: { rows: rows.length, ready: rows.length - blocked, blocked, cut: 0 },\n ...(typeof args?.note === 'string' && args.note.trim() ? { reason: args.note.trim().slice(0, 300) } : {}),\n message:\n `proposed ${rows.length} changes (${rows.length - blocked} ready, ${blocked} blocked). Nothing changes until the person presses the card. ` +\n 'Tell them which records are not found or would not change.'\n }\n}\n\nexport const MAX_CANDIDATES = 20\n\n/** Candidates for what the person described - the model lists them and asks when there is more than one. */\nexport async function findRecords(target: EntryTarget, args: any, run: GraphqlRunner) {\n const find = target.update?.find\n if (!find) return { rejected: true, error: 'no-find', message: 'this record cannot be searched.' }\n const criteria: Record<string, unknown> = {}\n for (const field of find.criteria) {\n const value = args?.[field.name]\n if (value === undefined || value === null || value === '') continue\n const { read } = readFieldValue(field, value)\n if (read !== undefined) criteria[field.name] = read\n }\n const answer = await run(find.query, find.variables(criteria))\n if (answer.errors?.length) return { rejected: true, error: 'find-failed', message: answer.errors.map(one => one.message).join('; ') }\n const found = (find.recordsOf(answer.data, criteria) ?? []).filter(record => record && typeof record.id === 'string')\n return {\n total: found.length,\n candidates: found.slice(0, MAX_CANDIDATES).map(record => ({ id: record.id, ...(target.update?.labelOf ? { label: target.update.labelOf(record) } : {}), record })),\n message:\n found.length === 1\n ? `one record matches; pass its id to ${changeToolName(target.key)}.`\n : found.length\n ? `${found.length} records match${found.length > MAX_CANDIDATES ? ` (first ${MAX_CANDIDATES} shown)` : ''}. List them to the person and ask which - never pick one yourself.`\n : 'no record matches. Tell the person and ask for more to go on.'\n }\n}\n\n/**\n * The rows of a pressed 수정안, against what the records hold now. A row whose field moved since the card was\n * made is kept out as `changed-since` - it is not overwritten unseen.\n */\nexport function stillAsProposed(target: EntryTarget, rows: ChangeRow[], now: Map<string, EntryRecord>): (ChangeRow & { now?: EntryRecord })[] {\n const fields = new Map(target.fields.map(field => [field.name, field]))\n return rows.map(row => {\n const record = now.get(row.id)\n if (!record) return { ...row, issues: [...row.issues, { code: 'record-not-found', blocking: true }] }\n const moved = Object.keys(row.values).find(name => fields.get(name) && !sameFieldValue(fields.get(name)!, record[name] ?? null, row.before[name]))\n return moved ? { ...row, now: record, issues: [...row.issues, { field: moved, code: 'changed-since', blocking: true }] } : { ...row, now: record }\n })\n}\n\n/**\n * The target's own checks of the changes, added to each row. A check that could not run keeps every row out - a\n * change is not written unchecked (an entry's failed check refuses its card the same way).\n */\nexport async function withChangeChecks<T extends ChangeRow>(target: EntryTarget, rows: T[], records: Map<string, EntryRecord>, context: { state: any }): Promise<T[]> {\n if (!target.update?.check) return rows\n let found: EntryIssue[][] | undefined\n try {\n found = await target.update.check(rows.map(row => ({ id: row.id, record: records.get(row.id), values: row.values })), context)\n } catch {\n return rows.map(row => ({ ...row, issues: [...row.issues, { code: 'check-failed', blocking: true }] }))\n }\n return rows.map((row, i) => (found?.[i]?.length ? { ...row, issues: [...row.issues, ...found[i]] } : row))\n}\n\n"]}
|
|
1
|
+
{"version":3,"file":"entry-update.js","sourceRoot":"","sources":["../../../server/service/data-entry/entry-update.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Changing records that exist - the 수정안 (operato-application/design/ai/data-entry.md §5-1).\n *\n * The most expensive mistake is changing the wrong record, so which record is meant is settled here, by the\n * server, never by the model's memory. A record is picked by the screen (the one being looked at, the rows\n * selected) or by an id the find tool handed back; whichever way, it is read again through the application's\n * read query as the person, and a record they cannot see is not found. The card shows what is there now and\n * what it becomes; pressing reads it once more, and a field that moved in between keeps its row out.\n *\n * Pure but for the GraphQL runner, which comes in as an argument - the rules are tested without a server.\n */\nimport {\n changeToolName,\n findToolName,\n readFieldValue,\n resolveReferences,\n zonedDateTimes,\n sameFieldValue,\n type EntryField,\n type EntryIssue,\n type EntryRecord,\n type EntryTarget\n} from './entry-target.js'\n\n/** Runs one of the application's queries or mutations as the person. */\nexport type GraphqlRunner = (document: string, variables: Record<string, unknown>) => Promise<{ data?: any; errors?: readonly { message: string }[] }>\n\n/** How the record of a change was picked - said on the card, so the person sees the ground of it. */\nexport type ChangeVia = 'current' | 'selected' | 'id'\n\nexport interface ChangeRow {\n index: number\n id: string\n via: ChangeVia\n label?: string\n /** What the record holds now, for the fields that change - read by the server. */\n before: Record<string, unknown>\n values: Record<string, unknown>\n issues: EntryIssue[]\n}\n\nexport const MAX_CHANGES = 100\n\nconst blockedChange = (row: { issues: EntryIssue[] }) => row.issues.some(issue => issue.blocking)\n\n/** The fields a target lets change. */\nexport function changeableFields(target: EntryTarget): EntryField[] {\n const names = target.update?.fields\n return names ? target.fields.filter(field => names.includes(field.name)) : target.fields\n}\n\n/**\n * The records the changes name. `current` is the record on screen, `selected` every row selected there -\n * both from the surface's host context, which is **not trusted**: an id from it is only a candidate, read\n * through the application before anything is shown. Any other ref is an id, from the find tool.\n */\nexport function resolveChangeRefs(\n targetKey: string,\n changes: unknown,\n host: Record<string, any> | undefined\n): { picked: { id: string; via: ChangeVia; values: Record<string, unknown> }[]; unresolved: string[] } {\n const picked: { id: string; via: ChangeVia; values: Record<string, unknown> }[] = []\n const unresolved: string[] = []\n const mine = (one: any) => !one?.target || one.target === targetKey\n for (const change of Array.isArray(changes) ? changes : []) {\n const values = change?.values && typeof change.values === 'object' ? change.values : {}\n const ref = typeof change?.ref === 'string' ? change.ref.trim() : ''\n if (ref === 'current') {\n const id = mine(host?.record) ? host?.record?.id : undefined\n if (typeof id === 'string' && id) picked.push({ id, via: 'current', values })\n else unresolved.push('current')\n } else if (ref === 'selected') {\n const ids = mine(host?.selection) && Array.isArray(host?.selection?.ids) ? host!.selection.ids.filter((id: unknown) => typeof id === 'string' && id) : []\n if (ids.length) for (const id of ids) picked.push({ id, via: 'selected', values })\n else unresolved.push('selected')\n } else if (ref) {\n picked.push({ id: ref, via: 'id', values })\n } else {\n unresolved.push('no-ref')\n }\n }\n return { picked: picked.slice(0, MAX_CHANGES), unresolved }\n}\n\n/** Reads the records by id through the application's read query, as the person. */\nexport async function readRecords(target: EntryTarget, ids: string[], run: GraphqlRunner): Promise<Map<string, EntryRecord>> {\n const update = target.update!\n const answer = await run(update.read.query, update.read.variables([...new Set(ids)]))\n if (answer.errors?.length) throw new Error(answer.errors.map(one => one.message).join('; '))\n return new Map((update.read.recordsOf(answer.data, [...new Set(ids)]) ?? []).filter(record => record && typeof record.id === 'string').map(record => [record.id, record]))\n}\n\n/** The change rows against what the records hold now. */\nexport function changeRows(target: EntryTarget, picked: { id: string; via: ChangeVia; values: Record<string, unknown> }[], records: Map<string, EntryRecord>): ChangeRow[] {\n const fields = new Map(changeableFields(target).map(field => [field.name, field]))\n return picked.map((one, index): ChangeRow => {\n const record = records.get(one.id)\n const issues: EntryIssue[] = []\n const values: Record<string, unknown> = {}\n const before: Record<string, unknown> = {}\n for (const [name, value] of Object.entries(one.values)) {\n const field = fields.get(name)\n if (!field) {\n issues.push({ field: name, code: 'not-changeable', blocking: false })\n continue\n }\n const { read, code } = value === null || value === '' ? { read: null, code: undefined } : readFieldValue(field, value)\n if (read === undefined) {\n values[name] = value\n issues.push({ field: name, code: code!, params: code === 'not-in-list' ? { values: field.values } : undefined, blocking: true })\n } else {\n if (read === null && field.required) issues.push({ field: name, code: 'required', blocking: true })\n values[name] = read\n }\n if (record) before[name] = record[name] ?? null\n }\n if (!record) issues.push({ code: 'record-not-found', blocking: true })\n else {\n /* A field the model restated as it already is does not change - the card shows it as no change, and it is not written. */\n const same = Object.keys(values).filter(name => fields.has(name) && !issues.some(issue => issue.field === name) && sameFieldValue(fields.get(name)!, before[name], values[name]))\n if (same.length && same.length === Object.keys(values).length) issues.push({ code: 'no-change', blocking: true })\n else for (const name of same) delete values[name], delete before[name]\n }\n if (!Object.keys(values).length && !issues.some(issue => issue.blocking)) issues.push({ code: 'nothing-to-change', blocking: true })\n return { index, id: one.id, via: one.via, ...(record && target.update?.labelOf ? { label: target.update.labelOf(record) } : {}), before, values, issues }\n })\n}\n\n/** What the card needs besides the rows - the fields that change, in the target's order. */\nfunction changedColumns(target: EntryTarget, rows: ChangeRow[]) {\n const used = new Set(rows.flatMap(row => Object.keys(row.values)))\n return changeableFields(target)\n .filter(field => used.has(field.name))\n .map(field => ({ name: field.name, type: field.type, ...(field.labelKey ? { labelKey: field.labelKey } : {}), ...(field.valueKeyPrefix ? { valueKeyPrefix: field.valueKeyPrefix } : {}) }))\n}\n\nexport async function proposeChanges(target: EntryTarget, args: any, context: any, run: GraphqlRunner) {\n const { picked, unresolved } = resolveChangeRefs(target.key, args?.changes, context?.host)\n if (!picked.length) {\n return {\n rejected: true,\n error: unresolved.length ? `unresolved-${unresolved[0]}` : 'no-changes',\n message:\n unresolved[0] === 'current' || unresolved[0] === 'selected'\n ? `the screen names no ${unresolved[0] === 'current' ? 'record' : 'selected rows'} of this kind. Ask the person which record, or call ${findToolName(target.key)}.`\n : `name each record: ref \"current\", \"selected\", or an id from ${findToolName(target.key)}. Never guess an id.`\n }\n }\n let records: Map<string, EntryRecord>\n try {\n records = await readRecords(target, picked.map(one => one.id), run)\n } catch (error: any) {\n return { rejected: true, error: 'read-failed', message: `could not read the records: ${error?.message ?? error}` }\n }\n const changed = await resolveReferences(changeableFields(target), zonedDateTimes(changeableFields(target), changeRows(target, picked, records), context?.state?.domain?.timezone), run)\n const rows = await withChangeChecks(target, changed, records, { state: context?.state })\n const blocked = rows.filter(blockedChange).length\n return {\n proposed: true,\n kind: 'entry',\n op: 'update',\n target: target.key,\n labelKey: target.labelKey,\n label: `${rows.length} ${target.key} changes`,\n icon: 'edit_note',\n columns: changedColumns(target, rows),\n rows,\n counts: { rows: rows.length, ready: rows.length - blocked, blocked, cut: 0 },\n ...(typeof args?.note === 'string' && args.note.trim() ? { reason: args.note.trim().slice(0, 300) } : {}),\n message:\n `proposed ${rows.length} changes (${rows.length - blocked} ready, ${blocked} blocked). Nothing changes until the person presses the card. ` +\n 'Tell them which records are not found or would not change.'\n }\n}\n\nexport const MAX_CANDIDATES = 20\n\n/** Candidates for what the person described - the model lists them and asks when there is more than one. */\nexport async function findRecords(target: EntryTarget, args: any, run: GraphqlRunner) {\n const find = target.update?.find\n if (!find) return { rejected: true, error: 'no-find', message: 'this record cannot be searched.' }\n const criteria: Record<string, unknown> = {}\n for (const field of find.criteria) {\n const value = args?.[field.name]\n if (value === undefined || value === null || value === '') continue\n const { read } = readFieldValue(field, value)\n if (read !== undefined) criteria[field.name] = read\n }\n const answer = await run(find.query, find.variables(criteria))\n if (answer.errors?.length) return { rejected: true, error: 'find-failed', message: answer.errors.map(one => one.message).join('; ') }\n const found = (find.recordsOf(answer.data, criteria) ?? []).filter(record => record && typeof record.id === 'string')\n return {\n total: found.length,\n candidates: found.slice(0, MAX_CANDIDATES).map(record => ({ id: record.id, ...(target.update?.labelOf ? { label: target.update.labelOf(record) } : {}), record })),\n message:\n found.length === 1\n ? `one record matches; pass its id to ${changeToolName(target.key)}.`\n : found.length\n ? `${found.length} records match${found.length > MAX_CANDIDATES ? ` (first ${MAX_CANDIDATES} shown)` : ''}. List them to the person and ask which - never pick one yourself.`\n : 'no record matches. Tell the person and ask for more to go on.'\n }\n}\n\n/**\n * The rows of a pressed 수정안, against what the records hold now. A row whose field moved since the card was\n * made is kept out as `changed-since` - it is not overwritten unseen.\n */\nexport function stillAsProposed(target: EntryTarget, rows: ChangeRow[], now: Map<string, EntryRecord>): (ChangeRow & { now?: EntryRecord })[] {\n const fields = new Map(target.fields.map(field => [field.name, field]))\n return rows.map(row => {\n const record = now.get(row.id)\n if (!record) return { ...row, issues: [...row.issues, { code: 'record-not-found', blocking: true }] }\n const moved = Object.keys(row.values).find(name => fields.get(name) && !sameFieldValue(fields.get(name)!, record[name] ?? null, row.before[name]))\n return moved ? { ...row, now: record, issues: [...row.issues, { field: moved, code: 'changed-since', blocking: true }] } : { ...row, now: record }\n })\n}\n\n/**\n * The target's own checks of the changes, added to each row. A check that could not run keeps every row out - a\n * change is not written unchecked (an entry's failed check refuses its card the same way).\n */\nexport async function withChangeChecks<T extends ChangeRow>(target: EntryTarget, rows: T[], records: Map<string, EntryRecord>, context: { state: any }): Promise<T[]> {\n if (!target.update?.check) return rows\n let found: EntryIssue[][] | undefined\n try {\n found = await target.update.check(rows.map(row => ({ id: row.id, record: records.get(row.id), values: row.values })), context)\n } catch {\n return rows.map(row => ({ ...row, issues: [...row.issues, { code: 'check-failed', blocking: true }] }))\n }\n return rows.map((row, i) => (found?.[i]?.length ? { ...row, issues: [...row.issues, ...found[i]] } : row))\n}\n\n"]}
|