Skip to content

Commit babe0b9

Browse files
github-actions[bot]caoxing9tea-artistboris-wJocky-Teable
committed
[sync] fix: handle IM config and sandbox effort T3422 T3600
Synced from teableio/teable-ee@0c67aed Co-authored-by: Aries X <caoxing9@gmail.com> Co-authored-by: Bieber <artist@teable.io> Co-authored-by: Boris <boris2code@outlook.com> Co-authored-by: Jocky-Teable <jocky@teable.ai> Co-authored-by: Jun Lu <hammond@teable.io> Co-authored-by: Pengap <penganpingprivte@gmail.com> Co-authored-by: SkyHuang <sky.huang.fe@gmail.com> Co-authored-by: Uno <uno@teable.ai> Co-authored-by: nichenqin <nichenqin@hotmail.com>
1 parent ceba9a7 commit babe0b9

235 files changed

Lines changed: 9821 additions & 1717 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/nestjs-backend/package.json

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -123,17 +123,17 @@
123123
"webpack": "5.91.0"
124124
},
125125
"dependencies": {
126-
"@ai-sdk/amazon-bedrock": "4.0.69",
127-
"@ai-sdk/anthropic": "3.0.50",
128-
"@ai-sdk/azure": "3.0.38",
129-
"@ai-sdk/cohere": "3.0.22",
130-
"@ai-sdk/deepseek": "2.0.21",
131-
"@ai-sdk/google": "3.0.34",
132-
"@ai-sdk/mistral": "3.0.21",
133-
"@ai-sdk/openai": "3.0.37",
134-
"@ai-sdk/openai-compatible": "2.0.31",
135-
"@ai-sdk/togetherai": "2.0.35",
136-
"@ai-sdk/xai": "3.0.60",
126+
"@ai-sdk/amazon-bedrock": "4.0.96",
127+
"@ai-sdk/anthropic": "3.0.71",
128+
"@ai-sdk/azure": "3.0.54",
129+
"@ai-sdk/cohere": "3.0.30",
130+
"@ai-sdk/deepseek": "2.0.29",
131+
"@ai-sdk/google": "3.0.64",
132+
"@ai-sdk/mistral": "3.0.30",
133+
"@ai-sdk/openai": "3.0.53",
134+
"@ai-sdk/openai-compatible": "2.0.41",
135+
"@ai-sdk/togetherai": "2.0.45",
136+
"@ai-sdk/xai": "3.0.83",
137137
"@an-epiphany/websocket-json-stream": "1.2.0",
138138
"@aws-sdk/client-s3": "3.609.0",
139139
"@aws-sdk/lib-storage": "3.609.0",
@@ -154,7 +154,7 @@
154154
"@nestjs/swagger": "7.3.0",
155155
"@nestjs/terminus": "10.2.3",
156156
"@nestjs/websockets": "10.3.5",
157-
"@openrouter/ai-sdk-provider": "2.2.3",
157+
"@openrouter/ai-sdk-provider": "2.8.1",
158158
"@opentelemetry/api": "1.9.0",
159159
"@opentelemetry/context-async-hooks": "2.5.0",
160160
"@opentelemetry/exporter-logs-otlp-http": "0.201.1",
@@ -193,7 +193,7 @@
193193
"@teable/v2-di": "workspace:*",
194194
"@teable/v2-import": "workspace:*",
195195
"@valibot/to-json-schema": "1.3.0",
196-
"ai": "6.0.105",
196+
"ai": "6.0.168",
197197
"ajv": "8.12.0",
198198
"archiver": "7.0.1",
199199
"axios": "1.7.7",
@@ -241,7 +241,7 @@
241241
"oauth2orize": "1.12.0",
242242
"oauth2orize-pkce": "0.1.2",
243243
"object-sizeof": "2.6.4",
244-
"ollama-ai-provider-v2": "3.0.2",
244+
"ollama-ai-provider-v2": "3.5.0",
245245
"p-limit": "3.1.0",
246246
"papaparse": "5.4.1",
247247
"passport": "0.7.0",

apps/nestjs-backend/src/cache/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export interface ICacheStore {
3636
[key: `oauth:token-rate:${string}:${string}`]: number;
3737
[key: `automation:email:rate:${string}:${number}`]: number;
3838
[key: `automation:email-att:${string}`]: string[];
39+
[key: `automation:fail-notify-count:${string}`]: number;
3940
// Distributed lock keys
4041
[key: `lock:${string}`]: string;
4142
[key: `import:result:manifest:${string}`]: {
Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
/* eslint-disable @typescript-eslint/naming-convention */
2+
import {
3+
CellValueType,
4+
DateFieldCore,
5+
DateFormattingPreset,
6+
DriverClient,
7+
FieldType,
8+
NumberFieldCore,
9+
SingleLineTextFieldCore,
10+
TimeFormatting,
11+
} from '@teable/core';
12+
import type { FieldCore, IFilter } from '@teable/core';
13+
import knex from 'knex';
14+
import type { IRecordQueryFilterContext } from '../../../features/record/query-builder/record-query-builder.interface';
15+
import type { IDbProvider } from '../../db.provider.interface';
16+
import type { AbstractCellValueFilter } from '../cell-value-filter.abstract';
17+
import { AbstractFilterQuery } from '../filter-query.abstract';
18+
import { FilterQueryPostgres } from '../postgres/filter-query.postgres';
19+
20+
const knexBuilder = knex({ client: 'pg' });
21+
const dbProviderStub = { driver: DriverClient.Pg } as unknown as IDbProvider;
22+
const mainTableAlias = 'main_table as main';
23+
24+
function assignBaseField<T extends FieldCore>(
25+
field: T,
26+
params: {
27+
id: string;
28+
name?: string;
29+
dbFieldName: string;
30+
type: FieldType;
31+
cellValueType: CellValueType;
32+
options: T['options'];
33+
}
34+
): T {
35+
field.id = params.id;
36+
field.name = params.name ?? params.id;
37+
field.dbFieldName = params.dbFieldName;
38+
field.type = params.type;
39+
field.options = params.options;
40+
field.cellValueType = params.cellValueType;
41+
field.isMultipleCellValue = false;
42+
field.isLookup = false;
43+
field.updateDbFieldType();
44+
return field;
45+
}
46+
47+
function createNumberField(id: string, dbFieldName: string): NumberFieldCore {
48+
return assignBaseField(new NumberFieldCore(), {
49+
id,
50+
dbFieldName,
51+
type: FieldType.Number,
52+
cellValueType: CellValueType.Number,
53+
options: NumberFieldCore.defaultOptions(),
54+
});
55+
}
56+
57+
function createTextField(id: string, dbFieldName: string, name?: string): SingleLineTextFieldCore {
58+
return assignBaseField(new SingleLineTextFieldCore(), {
59+
id,
60+
name,
61+
dbFieldName,
62+
type: FieldType.SingleLineText,
63+
cellValueType: CellValueType.String,
64+
options: SingleLineTextFieldCore.defaultOptions(),
65+
});
66+
}
67+
68+
function createDateField(id: string, dbFieldName: string): DateFieldCore {
69+
const options = DateFieldCore.defaultOptions();
70+
options.formatting = {
71+
date: DateFormattingPreset.ISO,
72+
time: TimeFormatting.None,
73+
timeZone: 'UTC',
74+
};
75+
return assignBaseField(new DateFieldCore(), {
76+
id,
77+
dbFieldName,
78+
type: FieldType.Date,
79+
cellValueType: CellValueType.DateTime,
80+
options,
81+
});
82+
}
83+
84+
class ThrowingFilterQuery extends AbstractFilterQuery {
85+
private createThrowingFilter(): AbstractCellValueFilter {
86+
return {
87+
compiler: () => {
88+
throw new Error('unexpected adapter failure');
89+
},
90+
} as unknown as AbstractCellValueFilter;
91+
}
92+
93+
booleanFilter(_field: FieldCore, _context?: IRecordQueryFilterContext): AbstractCellValueFilter {
94+
return this.createThrowingFilter();
95+
}
96+
97+
numberFilter(_field: FieldCore, _context?: IRecordQueryFilterContext): AbstractCellValueFilter {
98+
return this.createThrowingFilter();
99+
}
100+
101+
dateTimeFilter(_field: FieldCore, _context?: IRecordQueryFilterContext): AbstractCellValueFilter {
102+
return this.createThrowingFilter();
103+
}
104+
105+
stringFilter(_field: FieldCore, _context?: IRecordQueryFilterContext): AbstractCellValueFilter {
106+
return this.createThrowingFilter();
107+
}
108+
109+
jsonFilter(_field: FieldCore, _context?: IRecordQueryFilterContext): AbstractCellValueFilter {
110+
return this.createThrowingFilter();
111+
}
112+
}
113+
114+
describe('filter-query invalid filter skip', () => {
115+
it('skips filter item with invalid operator instead of throwing', () => {
116+
const numberField = createNumberField('fld_num', 'num_col');
117+
const filter = {
118+
conjunction: 'and',
119+
filterSet: [
120+
{
121+
fieldId: numberField.id,
122+
operator: 'contains',
123+
value: 'whatever',
124+
},
125+
],
126+
} as unknown as IFilter;
127+
128+
const qb = knexBuilder(mainTableAlias);
129+
const filterQuery = new FilterQueryPostgres(
130+
qb,
131+
{ [numberField.id]: numberField },
132+
filter,
133+
undefined,
134+
dbProviderStub
135+
);
136+
137+
expect(() => filterQuery.appendQueryBuilder()).not.toThrow();
138+
expect(qb.toQuery()).not.toContain('num_col');
139+
});
140+
141+
it('preserves valid filter items alongside skipped invalid ones', () => {
142+
const numberField = createNumberField('fld_num', 'num_col');
143+
const textField = createTextField('fld_text', 'text_col');
144+
const filter = {
145+
conjunction: 'and',
146+
filterSet: [
147+
{
148+
fieldId: numberField.id,
149+
operator: 'contains',
150+
value: 'whatever',
151+
},
152+
{
153+
fieldId: textField.id,
154+
operator: 'contains',
155+
value: 'hello',
156+
},
157+
],
158+
} as unknown as IFilter;
159+
160+
const qb = knexBuilder(mainTableAlias);
161+
const filterQuery = new FilterQueryPostgres(
162+
qb,
163+
{ [numberField.id]: numberField, [textField.id]: textField },
164+
filter,
165+
undefined,
166+
dbProviderStub
167+
);
168+
169+
expect(() => filterQuery.appendQueryBuilder()).not.toThrow();
170+
const sql = qb.toQuery();
171+
expect(sql).toContain('text_col');
172+
expect(sql).not.toContain('num_col');
173+
});
174+
175+
it('keeps filter items keyed by field name when fields map supports name keys', () => {
176+
const textField = createTextField('fld_text_name', 'text_name_col', 'Display Name');
177+
const filter = {
178+
conjunction: 'and',
179+
filterSet: [
180+
{
181+
fieldId: textField.name,
182+
operator: 'contains',
183+
value: 'hello',
184+
},
185+
],
186+
} as unknown as IFilter;
187+
188+
const qb = knexBuilder(mainTableAlias);
189+
const filterQuery = new FilterQueryPostgres(
190+
qb,
191+
{ [textField.id]: textField, [textField.name]: textField },
192+
filter,
193+
undefined,
194+
dbProviderStub
195+
);
196+
197+
expect(() => filterQuery.appendQueryBuilder()).not.toThrow();
198+
expect(qb.toQuery()).toContain('text_name_col');
199+
});
200+
201+
it('skips filter item with invalid sub-operator mode instead of throwing', () => {
202+
const dateField = createDateField('fld_date', 'date_col');
203+
const filter = {
204+
conjunction: 'and',
205+
filterSet: [
206+
{
207+
fieldId: dateField.id,
208+
operator: 'isWithIn',
209+
value: { mode: 'invalidMode', exactDate: null, timeZone: 'UTC' },
210+
},
211+
],
212+
} as unknown as IFilter;
213+
214+
const qb = knexBuilder(mainTableAlias);
215+
const filterQuery = new FilterQueryPostgres(
216+
qb,
217+
{ [dateField.id]: dateField },
218+
filter,
219+
undefined,
220+
dbProviderStub
221+
);
222+
223+
expect(() => filterQuery.appendQueryBuilder()).not.toThrow();
224+
expect(qb.toQuery()).not.toContain('date_col');
225+
});
226+
227+
it('skips filter item whose value shape fails inside the adapter compiler', () => {
228+
const dateField = createDateField('fld_date_shape', 'date_shape_col');
229+
// value is a string, but isWithIn requires an object { mode, ... }
230+
const filter = {
231+
conjunction: 'and',
232+
filterSet: [
233+
{
234+
fieldId: dateField.id,
235+
operator: 'isWithIn',
236+
value: 'today',
237+
},
238+
],
239+
} as unknown as IFilter;
240+
241+
const qb = knexBuilder(mainTableAlias).select('id');
242+
const filterQuery = new FilterQueryPostgres(
243+
qb,
244+
{ [dateField.id]: dateField },
245+
filter,
246+
undefined,
247+
dbProviderStub
248+
);
249+
250+
expect(() => filterQuery.appendQueryBuilder()).not.toThrow();
251+
expect(() => qb.toQuery()).not.toThrow();
252+
});
253+
254+
it('rethrows non-user compiler errors instead of swallowing them', () => {
255+
const numberField = createNumberField('fld_num_system', 'num_system_col');
256+
const filter = {
257+
conjunction: 'and',
258+
filterSet: [
259+
{
260+
fieldId: numberField.id,
261+
operator: 'is',
262+
value: 1,
263+
},
264+
],
265+
} as unknown as IFilter;
266+
267+
const qb = knexBuilder(mainTableAlias).select('id');
268+
const filterQuery = new ThrowingFilterQuery(
269+
qb,
270+
{ [numberField.id]: numberField },
271+
filter,
272+
undefined,
273+
dbProviderStub
274+
);
275+
276+
expect(() => filterQuery.appendQueryBuilder()).not.toThrow();
277+
expect(() => qb.toQuery()).toThrow();
278+
});
279+
280+
it('rethrows field-reference context errors instead of skipping them', () => {
281+
const textField = createTextField('fld_text_ref_context', 'text_ref_context_col');
282+
const refField = createTextField('fld_ref_context', 'ref_context_col');
283+
const filter = {
284+
conjunction: 'and',
285+
filterSet: [
286+
{
287+
fieldId: textField.id,
288+
operator: 'is',
289+
value: { type: 'field', fieldId: refField.id },
290+
},
291+
],
292+
} as unknown as IFilter;
293+
294+
const qb = knexBuilder(mainTableAlias).select('id');
295+
const filterQuery = new FilterQueryPostgres(
296+
qb,
297+
{ [textField.id]: textField, [refField.id]: refField },
298+
filter,
299+
undefined,
300+
dbProviderStub
301+
);
302+
303+
expect(() => filterQuery.appendQueryBuilder()).not.toThrow();
304+
expect(() => qb.toQuery()).toThrow('not available for reference comparisons');
305+
});
306+
});

0 commit comments

Comments
 (0)