@things-factory/ai-assistant 10.1.141 → 10.1.142

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/ai-assistant",
3
- "version": "10.1.141",
3
+ "version": "10.1.142",
4
4
  "main": "dist-server/index.js",
5
5
  "things-factory": true,
6
6
  "author": "heartyoh",
@@ -34,11 +34,11 @@
34
34
  "@operato/shell": "^10.0.0",
35
35
  "@operato/styles": "^10.0.0",
36
36
  "@things-factory/ai-client-base": "^10.1.129",
37
- "@things-factory/apptool-base": "^10.1.141",
38
- "@things-factory/attachment-base": "^10.1.141",
39
- "@things-factory/auth-base": "^10.1.141",
37
+ "@things-factory/apptool-base": "^10.1.142",
38
+ "@things-factory/attachment-base": "^10.1.142",
39
+ "@things-factory/auth-base": "^10.1.142",
40
40
  "@things-factory/env": "^10.1.20",
41
- "@things-factory/shell": "^10.1.141",
41
+ "@things-factory/shell": "^10.1.142",
42
42
  "dompurify": "^3.0.0",
43
43
  "exceljs": "4.2.0",
44
44
  "graphql-scalars": "^1.22.0",
@@ -52,5 +52,5 @@
52
52
  "copyfiles": "^2.4.1",
53
53
  "rimraf": "^5.0.0"
54
54
  },
55
- "gitHead": "931866a13b9149be42a294f207e245aba11e57f8"
55
+ "gitHead": "73522201151b0156168b0eeea15f2962cda0bd8f"
56
56
  }
@@ -221,6 +221,18 @@ describe('a list declared in one line', () => {
221
221
  expect(plain.screen!.params({ year: 2026 })).toEqual({})
222
222
  })
223
223
 
224
+ it('★ a fixed filter is on every read, out of the model\'s reach - a condition narrows the list, never widens it', async () => {
225
+ const made: ReadTarget = listReadTarget({ ...line, key: 'office.spending', listQuery: 'cashTransactions', fixedFilters: [{ name: 'type', operator: 'eq', value: 'EXPENSE' }] })
226
+ declareReadTarget(made)
227
+ expect(made.criteria.map(one => one.name)).not.toContain('type')
228
+ const all: any = await queryRecords(made, {}, {}, run)
229
+ expect(all.total).toBe(4)
230
+ expect(calls[0].filters).toEqual([{ name: 'type', operator: 'eq', value: 'EXPENSE' }])
231
+ expect(((await queryRecords(made, { flow: 'INCOME' }, {}, run)) as any).total).toBe(0)
232
+ expect(((await queryRecords(made, { id: 't3' }, {}, run)) as any).total).toBe(0)
233
+ expect(listReadTarget(line).fixedFilters).toBeUndefined()
234
+ })
235
+
224
236
  it('a minimum or maximum amount means the amount column, else the first summed one', () => {
225
237
  const made: ReadTarget = listReadTarget({ ...line, key: 'office.claims', columns: [{ name: 'totalAmount', type: 'number', total: true }] })
226
238
  expect(made.criteria.find(one => one.name === 'minAmount')!.filter(5)).toEqual({ name: 'totalAmount', operator: 'gte', value: 5 })
@@ -3,7 +3,7 @@
3
3
  * a ListParam query, the fields it reads, a date to range over, a status, an amount, search words, and a screen.
4
4
  * Each application writes its lists as these lines; this turns one into a read target with the conditions it takes.
5
5
  */
6
- import type { ReadColumn, ReadCriterion, ReadTarget } from './read-target.js'
6
+ import type { ReadColumn, ReadCriterion, ReadFilter, ReadTarget } from './read-target.js'
7
7
 
8
8
  export interface ListRead {
9
9
  /** `<app>.<record>`. */
@@ -12,8 +12,10 @@ export interface ListRead {
12
12
  title: string
13
13
  /** The screen's name as the person knows it - the model picks the list by it. */
14
14
  name: string
15
- /** The ListParam query (`cashTransactions`). */
15
+ /** The ListParam query (`transactions`). */
16
16
  listQuery: string
17
+ /** Filters every read applies, out of the model's reach - the records the list is of (`category` CONFIRMED). */
18
+ fixedFilters?: ReadFilter[]
17
19
  /** The fields of each item it reads - the screen's own selection where there is one. */
18
20
  selection: string
19
21
  columns: ReadColumn[]
@@ -75,6 +77,7 @@ export function listReadTarget(list: ListRead): ReadTarget {
75
77
  `query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]${search ? ', $search: String' : ''}) ` +
76
78
  `{ ${list.listQuery}(filters: $filters, pagination: $pagination, sortings: $sortings${search ? ', search: $search' : ''}) { items { ${list.selection} } total } }`,
77
79
  criteria,
80
+ ...(list.fixedFilters?.length ? { fixedFilters: list.fixedFilters } : {}),
78
81
  columns: list.columns,
79
82
  rowsOf: data => ({ rows: data?.[list.listQuery]?.items ?? [], total: data?.[list.listQuery]?.total ?? 0 }),
80
83
  ...(search ? { search: { description: `the ${list.search}` } } : {}),
@@ -62,6 +62,11 @@ export interface ReadTarget {
62
62
  */
63
63
  search?: { description: string }
64
64
  criteria: ReadCriterion[]
65
+ /**
66
+ * Filters every read of this list applies - the records it is of (a book's confirmed lines, not its forecasts). Not
67
+ * a condition: the model neither sees nor sets them, so a read can narrow the list but never widen it.
68
+ */
69
+ fixedFilters?: ReadFilter[]
65
70
  columns: ReadColumn[]
66
71
  rowsOf: (data: any) => { rows: any[]; total: number }
67
72
  /** Orders the rows; the query's own order when left out. */
@@ -167,7 +172,7 @@ export async function readRows(target: ReadTarget, filters: ReadFilter[], run: G
167
172
  let total = 0
168
173
  for (let page = 1; rows.length < MAX_READ_ROWS; page++) {
169
174
  const answer = await run(target.query, {
170
- filters,
175
+ filters: [...filters, ...(target.fixedFilters ?? [])],
171
176
  ...(search ? { search } : {}),
172
177
  pagination: { page, limit: READ_PAGE },
173
178
  ...(target.sortings ? { sortings: target.sortings } : {})