Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .github/workflows/docker-push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ jobs:

- uses: actions/setup-node@v4
with:
node-version: 22.18.0
node-version: 22.22.3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
strategy:
fail-fast: false
matrix:
node-version: [22.18.0]
node-version: [22.22.3]
runtime:
- mode: v1
force-v2-all: ''
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/linting.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:

strategy:
matrix:
node-version: [22.18.0]
node-version: [22.22.3]

steps:
- uses: actions/checkout@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/manual-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ jobs:

- uses: actions/setup-node@v4
with:
node-version: 22.18.0
node-version: 22.22.3
- name: ⚙️ Install zx
run: npm install -g zx

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:

strategy:
matrix:
node-version: [22.18.0]
node-version: [22.22.3]

steps:
- uses: actions/checkout@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/v2-benchmark-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:

strategy:
matrix:
node-version: [22.18.0]
node-version: [22.22.3]

steps:
- uses: actions/checkout@v4
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/v2-core-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Use Node.js 22.18.0
- name: Use Node.js 22.22.3
uses: actions/setup-node@v4
with:
node-version: 22.18.0
node-version: 22.22.3

- name: 📥 Monorepo install
uses: ./.github/actions/pnpm-install
Expand All @@ -63,10 +63,10 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Use Node.js 22.18.0
- name: Use Node.js 22.22.3
uses: actions/setup-node@v4
with:
node-version: 22.18.0
node-version: 22.22.3

- name: 📥 Monorepo install
uses: ./.github/actions/pnpm-install
Expand Down Expand Up @@ -102,10 +102,10 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Use Node.js 22.18.0
- name: Use Node.js 22.22.3
uses: actions/setup-node@v4
with:
node-version: 22.18.0
node-version: 22.22.3

- name: 📥 Download all coverage artifacts
uses: actions/download-artifact@v4
Expand Down
2 changes: 1 addition & 1 deletion .npmrc
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ auto-install-peers=true
lockfile=true
# force use npmjs.org registry
registry=https://registry.npmjs.org/
use-node-version=22.18.0
use-node-version=22.22.3
save-prefix=''
4 changes: 3 additions & 1 deletion apps/nestjs-backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
"eslint-config-next": "15.5.9",
"get-tsconfig": "4.7.3",
"istanbul-merge": "2.0.0",
"neverthrow": "8.2.0",
"npm-run-all2": "6.1.2",
"nyc": "15.1.0",
"pg-mem": "3.0.5",
Expand Down Expand Up @@ -167,7 +168,7 @@
"@opentelemetry/instrumentation-ioredis": "0.49.0",
"@opentelemetry/instrumentation-nestjs-core": "0.49.0",
"@opentelemetry/instrumentation-pg": "0.49.0",
"@opentelemetry/instrumentation-pino": "0.49.0",
"@opentelemetry/instrumentation-pino": "0.54.0",
"@opentelemetry/instrumentation-runtime-node": "0.24.0",
"@opentelemetry/resources": "2.0.1",
"@opentelemetry/sdk-node": "0.201.1",
Expand Down Expand Up @@ -228,6 +229,7 @@
"keyv": "4.5.4",
"knex": "3.1.0",
"lodash": "4.17.21",
"markdown-it": "14.1.0",
"mime-types": "2.1.35",
"minio": "7.1.3",
"ms": "2.1.3",
Expand Down
2 changes: 1 addition & 1 deletion apps/nestjs-backend/src/cache/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export interface ICacheStore {
[key: `waitlist:invite-code:${string}`]: number;
[key: `send-mail-rate-limit:${string}`]: boolean;
[key: `oauth:token-rate:${string}:${string}`]: number;
[key: `automation:email:rate:${string}:${number}`]: number;
[key: `email:send:rate:${string}:${number}`]: number;
[key: `automation:email-att:${string}`]: string[];
[key: `automation:fail-notify-count:${string}`]: number;
// Distributed lock keys
Expand Down
3 changes: 3 additions & 0 deletions apps/nestjs-backend/src/configs/threshold.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ export const thresholdConfig = registerAs('threshold', () => ({
bigTransactionTimeout: Number(
process.env.BIG_TRANSACTION_TIMEOUT ?? 10 * 60 * 1000 /* 10 mins */
),
// DB statement_timeout (ms) for the search query, so a slow / full-scan search is canceled
// and its connection released instead of being held for minutes. Tune via SEARCH_TIMEOUT.
searchTimeout: Number(process.env.SEARCH_TIMEOUT ?? 15_000 /* 15s */),
automationGap: Number(process.env.AUTOMATION_GAP ?? 200),
maxAttachmentUploadSize: Number(process.env.MAX_ATTACHMENT_UPLOAD_SIZE ?? Infinity),
maxOpenapiAttachmentUploadSize: Number(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,17 @@ const buildDateField = (): IFieldInstance =>
},
}) as IFieldInstance;

const buildMultipleSelectField = (): IFieldInstance =>
({
id: 'fldMultiSelect0001',
dbFieldName: 'Tags',
cellValueType: CellValueType.String,
isMultipleCellValue: true,
isStructuredCellValue: false,
type: FieldType.MultipleSelect,
options: {},
}) as IFieldInstance;

describe('SearchQueryPostgres', () => {
const db = knex({ client: 'pg' });

Expand Down Expand Up @@ -52,4 +63,19 @@ describe('SearchQueryPostgres', () => {
const compiled = builder.getQuery()?.toSQL();
expect(compiled?.sql).toBe('FALSE');
});

it('matches multipleSelect as a text cast so the gin_trgm index can be used', () => {
const field = buildMultipleSelectField();
const builder = new SearchQueryPostgres(
db.queryBuilder(),
field,
['Beta', 'fldMultiSelect0001'],
[]
);

const compiled = builder.getQuery()?.toSQL();
expect(compiled?.sql).toContain('("Tags")::text ILIKE');
expect(compiled?.sql).not.toContain('jsonb_array_elements');
expect(compiled?.bindings).toEqual(['%Beta%']);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,11 @@ export class SearchQueryPostgres extends SearchQueryAbstract {
case CellValueType.String: {
if (isStructuredCellValue) {
return this.multipleJson();
} else {
return this.multipleText();
}
if (field.type === FieldType.MultipleSelect) {
return this.multipleSelectText();
}
return this.multipleText();
}
case CellValueType.DateTime: {
return this.multipleDate();
Expand Down Expand Up @@ -180,6 +182,16 @@ export class SearchQueryPostgres extends SearchQueryAbstract {
);
}

// multipleSelect stores a plain string[] of option names. Match the whole cell as text so the
// predicate is sargable against the gin_trgm index (built on the same "<col>"::text expression)
// instead of a jsonb_array_elements + regex subquery that cannot use the index. Trades negligible
// precision (JSON brackets/quotes become matchable) for index usage.
protected multipleSelectText() {
const { search, knex } = this;
const escapedSearchValue = escapeLikeWildcards(search[0]);
return knex.raw(`(${this.fieldName})::text ILIKE ? ESCAPE '\\'`, [`%${escapedSearchValue}%`]);
}

protected multipleNumber() {
const { search, knex } = this;
const searchValue = search[0];
Expand Down
15 changes: 15 additions & 0 deletions apps/nestjs-backend/src/event-emitter/events/event.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export enum Events {
SHARED_VIEW_CREATE = 'shared.view.create',
SHARED_VIEW_DELETE = 'shared.view.delete',
SHARED_VIEW_UPDATE = 'shared.view.update',
SHARED_VIEW_REFRESH = 'shared.view.refresh',

USER_SIGNIN = 'user.signin',
USER_SIGNUP = 'user.signup',
Expand All @@ -67,6 +68,20 @@ export enum Events {
COLLABORATOR_DELETE = 'collaborator.delete',
COLLABORATOR_UPDATE = 'collaborator.update',

// Base-scope collaborator audit actions (parallel to the generic COLLABORATOR_*
// business events above, which are kept for internal pub/sub). Future space-level
// audit can mirror this with SPACE_COLLABORATOR_*.
BASE_COLLABORATOR_CREATE = 'base.collaborator.create',
BASE_COLLABORATOR_DELETE = 'base.collaborator.delete',
BASE_COLLABORATOR_UPDATE = 'base.collaborator.update',

// Base/Node share lifecycle (covers both node-scoped and base-wide shares;
// payload.type distinguishes 'node' | 'base').
BASE_SHARE_CREATE = 'base.share.create',
BASE_SHARE_UPDATE = 'base.share.update',
BASE_SHARE_DELETE = 'base.share.delete',
BASE_SHARE_REFRESH = 'base.share.refresh',

BASE_FOLDER_CREATE = 'base.folder.create',
BASE_FOLDER_DELETE = 'base.folder.delete',
BASE_FOLDER_UPDATE = 'base.folder.update',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { getV2CreateTableLegacyEventsFlag } from '../../features/v2/v2-create-ta
import { ShareDbService } from '../../share-db/share-db.service';
import type { IClsStore } from '../../types/cls';
import type {
IChangeRecord,
RecordCreateEvent,
RecordDeleteEvent,
RecordUpdateEvent,
Expand All @@ -19,6 +20,17 @@ import type {
} from '../events';
import { Events } from '../events';

const collectChangedRecordFieldIds = (record: IChangeRecord | IChangeRecord[]): string[] => {
const records = Array.isArray(record) ? record : [record];
const fieldIds = new Set<string>();
for (const changeRecord of records) {
for (const fieldId of Object.keys(changeRecord?.fields ?? {})) {
fieldIds.add(fieldId);
}
}
return [...fieldIds];
};

type IViewEvent = ViewUpdateEvent;
type IRecordEvent = RecordCreateEvent | RecordDeleteEvent | RecordUpdateEvent;
type IListenerEvent =
Expand Down Expand Up @@ -141,17 +153,21 @@ export class ActionTriggerListener {
const { tableId } = event.payload;

const buffer = match(event)
.returnType<ITableActionKey[]>()
.with({ name: Events.TABLE_RECORD_CREATE }, () => ['addRecord'])
.with({ name: Events.TABLE_RECORD_UPDATE }, () => ['setRecord'])
.with({ name: Events.TABLE_RECORD_DELETE }, () => ['deleteRecord'])
.returnType<IActionTriggerData[]>()
.with({ name: Events.TABLE_RECORD_CREATE }, () => [{ actionKey: 'addRecord' as const }])
.with({ name: Events.TABLE_RECORD_UPDATE }, (updateEvent) => [
{
actionKey: 'setRecord' as const,
// changed cell field ids, letting field-aware listeners skip
// refreshes for irrelevant edits (same contract as the v2 emitter)
payload: { fieldIds: collectChangedRecordFieldIds(updateEvent.payload.record) },
},
])
.with({ name: Events.TABLE_RECORD_DELETE }, () => [{ actionKey: 'deleteRecord' as const }])
.otherwise(() => []);

if (!isEmpty(buffer)) {
this.emitActionTrigger(
tableId,
buffer.map((actionKey) => ({ actionKey }))
);
this.emitActionTrigger(tableId, buffer);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,18 @@ export class AccessTokenService {
@Audit({
action: Events.ACCESS_TOKEN_CREATE,
resourceId: (input: { userId?: string }, ctx) => input.userId ?? ctx.cls.get('user.id')!,
userId: (input: { userId?: string }, ctx) => input.userId ?? ctx.cls.get('user.id'),
// Record the token's settings so the audit row shows what access was granted. NEVER the secret:
// the token `sign` is generated server-side and is not part of the input, so this is safe.
params: (input: CreateAccessTokenRo & { clientId?: string }) => ({
name: input.name,
description: input.description,
scopes: input.scopes,
spaceIds: input.spaceIds,
baseIds: input.baseIds,
expiredTime: input.expiredTime,
hasFullAccess: input.hasFullAccess,
}),
emit: true,
})
async createAccessToken(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { IFilter, IGroup, ISortItem, StatisticsFunc } from '@teable/core';
import type {
IAggregationField,
IQueryBaseRo,
IRowCountRo,
IRawAggregationValue,
IRawAggregations,
IRawRowCountValue,
Expand Down Expand Up @@ -60,7 +60,7 @@ export interface IAggregationService {
* @param queryRo - Query parameters for filtering
* @returns Promise<IRawRowCountValue> - The row count result
*/
performRowCount(tableId: string, queryRo: IQueryBaseRo): Promise<IRawRowCountValue>;
performRowCount(tableId: string, queryRo: IRowCountRo): Promise<IRawRowCountValue>;

/**
* Get field data for a table
Expand Down
Loading
Loading