@things-factory/ai-assistant 10.1.103 → 10.1.104
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/dist-server/service/data-entry/entry-apply.js +2 -1
- package/dist-server/service/data-entry/entry-apply.js.map +1 -1
- package/dist-server/service/data-entry/entry-target.d.ts +10 -0
- package/dist-server/service/data-entry/entry-target.js.map +1 -1
- package/dist-server/service/data-entry/entry-update.d.ts +7 -0
- package/dist-server/service/data-entry/entry-update.js +19 -1
- package/dist-server/service/data-entry/entry-update.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/server/service/data-entry/data-entry-update.test.ts +30 -0
- package/server/service/data-entry/entry-apply.ts +3 -2
- package/server/service/data-entry/entry-target.ts +6 -0
- package/server/service/data-entry/entry-update.ts +18 -1
- package/translations/en.json +2 -1
- package/translations/ja.json +2 -1
- package/translations/ko.json +2 -1
- package/translations/ms.json +2 -1
- package/translations/zh.json +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/ai-assistant",
|
|
3
|
-
"version": "10.1.
|
|
3
|
+
"version": "10.1.104",
|
|
4
4
|
"main": "dist-server/index.js",
|
|
5
5
|
"things-factory": true,
|
|
6
6
|
"author": "heartyoh",
|
|
@@ -51,5 +51,5 @@
|
|
|
51
51
|
"copyfiles": "^2.4.1",
|
|
52
52
|
"rimraf": "^5.0.0"
|
|
53
53
|
},
|
|
54
|
-
"gitHead": "
|
|
54
|
+
"gitHead": "0d05c7573220c6da2dec2654a4b86d7bd8e3c4fb"
|
|
55
55
|
}
|
|
@@ -311,3 +311,33 @@ describe('a record with lines', () => {
|
|
|
311
311
|
})
|
|
312
312
|
})
|
|
313
313
|
|
|
314
|
+
describe('checking the changes', () => {
|
|
315
|
+
const named: EntryTarget = {
|
|
316
|
+
...expense,
|
|
317
|
+
update: {
|
|
318
|
+
...expense.update!,
|
|
319
|
+
check: rows => rows.map(row => (row.values.merchant === '스타벅스' ? [{ code: 'name-taken', blocking: true }] : []))
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
it('★ the target checks a change when the card is made - and again when it is pressed', async () => {
|
|
324
|
+
declareEntryTarget(named)
|
|
325
|
+
const proposal: any = { ...(await proposeChanges(named, { changes: [{ ref: 'l1', values: { merchant: '스타벅스' } }, { ref: 'l2', values: { amount: 9000 } }] }, { host: {} }, run)), key: 'c1' }
|
|
326
|
+
expect(proposal.rows[0].issues).toEqual([{ code: 'name-taken', blocking: true }])
|
|
327
|
+
expect(proposal.rows[1].issues).toEqual([])
|
|
328
|
+
/* A card made before the clash: the check at pressing keeps it out. */
|
|
329
|
+
const before: any = { ...proposal, rows: proposal.rows.map((row: any) => ({ ...row, issues: [] })) }
|
|
330
|
+
const outcome = await applyEntryProposal('c1', { messages: [{ id: 'm1', proposals: [before] }], answered: false, passesDoor: async () => true, execute: run, state: {} })
|
|
331
|
+
expect(outcome.rows[0]).toEqual({ index: 0, ok: false, error: 'name-taken' })
|
|
332
|
+
expect(outcome.rows[1]).toMatchObject({ ok: true })
|
|
333
|
+
})
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
describe('a check that cannot run', () => {
|
|
337
|
+
it('★ keeps every change out - nothing is written unchecked', async () => {
|
|
338
|
+
const broken: EntryTarget = { ...expense, update: { ...expense.update!, check: () => { throw new Error('db down') } } }
|
|
339
|
+
const proposal: any = await proposeChanges(broken, { changes: [{ ref: 'l1', values: { amount: 1 } }] }, { host: {} }, run)
|
|
340
|
+
expect(proposal.rows[0].issues).toEqual([{ code: 'check-failed', blocking: true }])
|
|
341
|
+
})
|
|
342
|
+
})
|
|
343
|
+
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* Dependencies come in as arguments so the rules are tested without a database.
|
|
12
12
|
*/
|
|
13
13
|
import { PROPOSAL_OUTCOME_PREFIX, PROPOSAL_VERDICTS } from '../chat-message/proposal-outcome.js'
|
|
14
|
-
import { readRecords, stillAsProposed, type ChangeRow } from './entry-update.js'
|
|
14
|
+
import { readRecords, stillAsProposed, withChangeChecks, type ChangeRow } from './entry-update.js'
|
|
15
15
|
import { changeToolName, zonedDateTimes, entryGroups, resolveReferences, entryDoorOf, entryTargetOf, entryToolName, rootFieldOf, isBlocked, recordEntryEvent, normalizeEntryRows, withCheckedIssues, type EntryRow, type EntryTarget } from './entry-target.js'
|
|
16
16
|
|
|
17
17
|
export interface StoredProposalMessage {
|
|
@@ -185,7 +185,8 @@ async function applyChanges(key: string, proposal: any, target: EntryTarget, dep
|
|
|
185
185
|
}
|
|
186
186
|
|
|
187
187
|
const results: EntryRowResult[] = []
|
|
188
|
-
|
|
188
|
+
/* The target's checks of the changes again - another record may have taken the new name since the card was made. */
|
|
189
|
+
for (const row of await withChangeChecks(target, stillAsProposed(target, stored, now), now, { state: deps.state })) {
|
|
189
190
|
if (row.issues.some(one => one.blocking)) {
|
|
190
191
|
const issue = row.issues.find(one => one.blocking)!
|
|
191
192
|
results.push({ index: row.index, ok: false, error: issue.field ? `${issue.field}: ${issue.code}` : issue.code })
|
|
@@ -179,6 +179,12 @@ export interface EntryUpdate {
|
|
|
179
179
|
labelOf?(record: EntryRecord): string
|
|
180
180
|
/** The fields that may change. Default: every declared field. */
|
|
181
181
|
fields?: string[]
|
|
182
|
+
/**
|
|
183
|
+
* Read-only checks of the changes - a new name that another record already has. Asked when the card is made and
|
|
184
|
+
* again when it is pressed, like an entry's check; one issue list per row. Without it a clash is caught only by
|
|
185
|
+
* the database's own refusal.
|
|
186
|
+
*/
|
|
187
|
+
check?(rows: { id: string; record?: EntryRecord; values: Record<string, unknown> }[], context: EntryContext): EntryIssue[][] | Promise<EntryIssue[][]>
|
|
182
188
|
}
|
|
183
189
|
|
|
184
190
|
/**
|
|
@@ -148,7 +148,8 @@ export async function proposeChanges(target: EntryTarget, args: any, context: an
|
|
|
148
148
|
} catch (error: any) {
|
|
149
149
|
return { rejected: true, error: 'read-failed', message: `could not read the records: ${error?.message ?? error}` }
|
|
150
150
|
}
|
|
151
|
-
const
|
|
151
|
+
const changed = await resolveReferences(changeableFields(target), zonedDateTimes(changeableFields(target), changeRows(target, picked, records), context?.state?.domain?.timezone), run)
|
|
152
|
+
const rows = await withChangeChecks(target, changed, records, { state: context?.state })
|
|
152
153
|
const blocked = rows.filter(blockedChange).length
|
|
153
154
|
return {
|
|
154
155
|
proposed: true,
|
|
@@ -209,3 +210,19 @@ export function stillAsProposed(target: EntryTarget, rows: ChangeRow[], now: Map
|
|
|
209
210
|
return moved ? { ...row, now: record, issues: [...row.issues, { field: moved, code: 'changed-since', blocking: true }] } : { ...row, now: record }
|
|
210
211
|
})
|
|
211
212
|
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* The target's own checks of the changes, added to each row. A check that could not run keeps every row out - a
|
|
216
|
+
* change is not written unchecked (an entry's failed check refuses its card the same way).
|
|
217
|
+
*/
|
|
218
|
+
export async function withChangeChecks<T extends ChangeRow>(target: EntryTarget, rows: T[], records: Map<string, EntryRecord>, context: { state: any }): Promise<T[]> {
|
|
219
|
+
if (!target.update?.check) return rows
|
|
220
|
+
let found: EntryIssue[][] | undefined
|
|
221
|
+
try {
|
|
222
|
+
found = await target.update.check(rows.map(row => ({ id: row.id, record: records.get(row.id), values: row.values })), context)
|
|
223
|
+
} catch {
|
|
224
|
+
return rows.map(row => ({ ...row, issues: [...row.issues, { code: 'check-failed', blocking: true }] }))
|
|
225
|
+
}
|
|
226
|
+
return rows.map((row, i) => (found?.[i]?.length ? { ...row, issues: [...row.issues, ...found[i]] } : row))
|
|
227
|
+
}
|
|
228
|
+
|
package/translations/en.json
CHANGED
|
@@ -275,5 +275,6 @@
|
|
|
275
275
|
"ai-assistant.entry.issue.reference-unreadable": "\"{text}\" - could not be looked up",
|
|
276
276
|
"ai-assistant.entry.issue.not-a-reference": "not a record it can point to",
|
|
277
277
|
"ai-assistant.entry.groups": "{count} records",
|
|
278
|
-
"ai-assistant.entry.groups-kept-out": "{count} records kept out whole"
|
|
278
|
+
"ai-assistant.entry.groups-kept-out": "{count} records kept out whole",
|
|
279
|
+
"ai-assistant.entry.issue.check-failed": "could not be checked - not changed"
|
|
279
280
|
}
|
package/translations/ja.json
CHANGED
|
@@ -275,5 +275,6 @@
|
|
|
275
275
|
"ai-assistant.entry.issue.reference-unreadable": "「{text}」— 検索できませんでした",
|
|
276
276
|
"ai-assistant.entry.issue.not-a-reference": "参照先の記録が分かりません",
|
|
277
277
|
"ai-assistant.entry.groups": "{count}件",
|
|
278
|
-
"ai-assistant.entry.groups-kept-out": "{count}件は行が止まり全体を入力しません"
|
|
278
|
+
"ai-assistant.entry.groups-kept-out": "{count}件は行が止まり全体を入力しません",
|
|
279
|
+
"ai-assistant.entry.issue.check-failed": "検査できなかったため修正しません"
|
|
279
280
|
}
|
package/translations/ko.json
CHANGED
|
@@ -275,5 +275,6 @@
|
|
|
275
275
|
"ai-assistant.entry.issue.reference-unreadable": "\"{text}\" — 찾아보지 못했습니다",
|
|
276
276
|
"ai-assistant.entry.issue.not-a-reference": "가리키는 기록을 알 수 없습니다",
|
|
277
277
|
"ai-assistant.entry.groups": "{count}건",
|
|
278
|
-
"ai-assistant.entry.groups-kept-out": "{count}건은 줄이 막혀 통째로 입력하지 않습니다"
|
|
278
|
+
"ai-assistant.entry.groups-kept-out": "{count}건은 줄이 막혀 통째로 입력하지 않습니다",
|
|
279
|
+
"ai-assistant.entry.issue.check-failed": "검사를 하지 못해 고치지 않습니다"
|
|
279
280
|
}
|
package/translations/ms.json
CHANGED
|
@@ -275,5 +275,6 @@
|
|
|
275
275
|
"ai-assistant.entry.issue.reference-unreadable": "\"{text}\" - tidak dapat dicari",
|
|
276
276
|
"ai-assistant.entry.issue.not-a-reference": "bukan rekod yang boleh dirujuk",
|
|
277
277
|
"ai-assistant.entry.groups": "{count} rekod",
|
|
278
|
-
"ai-assistant.entry.groups-kept-out": "{count} rekod ditahan sepenuhnya"
|
|
278
|
+
"ai-assistant.entry.groups-kept-out": "{count} rekod ditahan sepenuhnya",
|
|
279
|
+
"ai-assistant.entry.issue.check-failed": "tidak dapat disemak - tidak diubah"
|
|
279
280
|
}
|
package/translations/zh.json
CHANGED
|
@@ -275,5 +275,6 @@
|
|
|
275
275
|
"ai-assistant.entry.issue.reference-unreadable": "\"{text}\" — 无法查找",
|
|
276
276
|
"ai-assistant.entry.issue.not-a-reference": "无法确定所指记录",
|
|
277
277
|
"ai-assistant.entry.groups": "{count}条",
|
|
278
|
-
"ai-assistant.entry.groups-kept-out": "{count}条因有行被拦而整体不录入"
|
|
278
|
+
"ai-assistant.entry.groups-kept-out": "{count}条因有行被拦而整体不录入",
|
|
279
|
+
"ai-assistant.entry.issue.check-failed": "无法检查,未修改"
|
|
279
280
|
}
|