feat: bump eslint & oxlint (#14452)

#### PR Dependency Tree


* **PR #14452** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Improved null-safety, dependency tracking, upload validation, and
error logging for more reliable uploads, clipboard, calendar linking,
telemetry, PDF/theme printing, and preview/zoom behavior.
* Tightened handling of all-day calendar events (missing date now
reported).

* **Deprecations**
  * Removed deprecated RadioButton and RadioButtonGroup; use RadioGroup.

* **Chores**
* Unified and upgraded linting/config, reorganized imports, and
standardized binary handling for more consistent builds and tooling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2026-02-16 13:52:08 +08:00
committed by GitHub
parent 792164edd1
commit 728e02cab7
156 changed files with 1230 additions and 1066 deletions
+77
View File
@@ -5,6 +5,10 @@
"correctness": "error", "correctness": "error",
"perf": "error" "perf": "error"
}, },
"env": {
"builtin": true,
"es2026": true
},
"ignorePatterns": [ "ignorePatterns": [
"**/node_modules", "**/node_modules",
".yarn", ".yarn",
@@ -44,6 +48,34 @@
"**/test-blocks.json" "**/test-blocks.json"
], ],
"rules": { "rules": {
"no-empty-static-block": "error",
"no-misleading-character-class": "error",
"no-new-native-nonconstructor": "error",
"no-unused-private-class-members": "error",
"no-useless-backreference": "error",
"react/display-name": "error",
"react/rules-of-hooks": "error",
"react/exhaustive-deps": "warn",
"@typescript-eslint/prefer-for-of": "error",
"@typescript-eslint/no-unsafe-function-type": "error",
"@typescript-eslint/no-wrapper-object-types": "error",
"no-restricted-imports": [
"error",
{
"patterns": [
{
"group": ["**/dist"],
"message": "Don't import from dist",
"allowTypeImports": false
},
{
"group": ["**/src"],
"message": "Don't import from src",
"allowTypeImports": false
}
]
}
],
"no-await-in-loop": "allow", "no-await-in-loop": "allow",
"no-redeclare": "allow", "no-redeclare": "allow",
"promise/no-callback-in-promise": "allow", "promise/no-callback-in-promise": "allow",
@@ -70,6 +102,14 @@
"no-func-assign": "error", "no-func-assign": "error",
"no-global-assign": "error", "no-global-assign": "error",
"no-unused-vars": "error", "no-unused-vars": "error",
"no-unused-expressions": [
"error",
{
"allowShortCircuit": true,
"allowTernary": true,
"allowTaggedTemplates": true
}
],
"no-ex-assign": "error", "no-ex-assign": "error",
"no-loss-of-precision": "error", "no-loss-of-precision": "error",
"no-fallthrough": "error", "no-fallthrough": "error",
@@ -126,6 +166,7 @@
"react/no-render-return-value": "error", "react/no-render-return-value": "error",
"react/jsx-no-target-blank": "error", "react/jsx-no-target-blank": "error",
"react/jsx-no-comment-textnodes": "error", "react/jsx-no-comment-textnodes": "error",
"react/no-array-index-key": "off",
"typescript/consistent-type-imports": "error", "typescript/consistent-type-imports": "error",
"typescript/no-non-null-assertion": "error", "typescript/no-non-null-assertion": "error",
"typescript/triple-slash-reference": "error", "typescript/triple-slash-reference": "error",
@@ -241,6 +282,42 @@
"typescript/consistent-type-imports": "off", "typescript/consistent-type-imports": "off",
"import/no-cycle": "off" "import/no-cycle": "off"
} }
},
{
"files": [
"packages/**/*.{ts,tsx}",
"tools/**/*.{ts,tsx}",
"blocksuite/**/*.{ts,tsx}"
],
"rules": {
"react/exhaustive-deps": [
"warn",
{
"additionalHooks": "(useAsyncCallback|useCatchEventCallback|useDraggable|useDropTarget|useRefEffect)"
}
]
}
},
{
"files": [
"**/__tests__/**/*",
"**/*.stories.tsx",
"**/*.spec.ts",
"**/tests/**/*",
"scripts/**/*",
"**/benchmark/**/*",
"**/__debug__/**/*",
"**/e2e/**/*"
],
"rules": {
"no-restricted-imports": "off"
}
},
{
"files": ["**/*.{ts,js,mjs}"],
"rules": {
"react/rules-of-hooks": "off"
}
} }
] ]
} }
+1 -1
View File
@@ -17,7 +17,7 @@
"explorer.fileNesting.enabled": true, "explorer.fileNesting.enabled": true,
"explorer.fileNesting.patterns": { "explorer.fileNesting.patterns": {
"*.js": "${capture}.js.map, ${capture}.min.js, ${capture}.d.ts, ${capture}.d.ts.map", "*.js": "${capture}.js.map, ${capture}.min.js, ${capture}.d.ts, ${capture}.d.ts.map",
"package.json": ".browserslist*, .circleci*, .codecov, .commitlint*, .cz-config.js, .czrc, .dlint.json, .dprint.json, .editorconfig, .eslint*, eslint.*, .firebase*, .flowconfig, .github*, .gitlab*, .gitpod*, .huskyrc*, .jslint*, .lighthouserc.*, .lintstagedrc*, .markdownlint*, .mocha*, .node-version, .nodemon*, .npm*, .nvmrc, .pm2*, .pnp.*, .pnpm*, .prettier*, .releaserc*, .sentry*, .stackblitz*, .styleci*, .stylelint*, .tazerc*, .textlint*, .tool-versions, .travis*, .versionrc*, .vscode*, .watchman*, .xo-config*, .yamllint*, .yarnrc*, Procfile, api-extractor.json, apollo.config.*, appveyor*, ava.config.*, azure-pipelines*, bower.json, build.config.*, commitlint*, dangerfile*, dlint.json, dprint.json, firebase.json, grunt*, gulp*, histoire.config.*, jasmine.*, jenkins*, jest.config.*, jsconfig.*, karma*, lerna*, lighthouserc.*, lint-staged*, nest-cli.*, netlify*, nodemon*, nx.*, package-lock.json, package.nls*.json, phpcs.xml, playwright.config.*, pm2.*, pnpm*, prettier*, pullapprove*, puppeteer.config.*, pyrightconfig.json, release-tasks.sh, renovate*, rollup.config.*, stylelint*, tsconfig.*, tsdoc.*, tslint*, tsup.config.*, turbo*, typedoc*, unlighthouse*, vercel*, vetur.config.*, vitest.*, webpack*, workspace.json, xo.config.*, yarn*, babel.*, .babelrc, project.json, oxlint.json, nyc.config.*", "package.json": ".browserslist*, .circleci*, .codecov, .commitlint*, .cz-config.js, .czrc, .dlint.json, .dprint.json, .editorconfig, .eslint*, eslint.*, .firebase*, .flowconfig, .github*, .gitlab*, .gitpod*, .huskyrc*, .jslint*, .lighthouserc.*, .lintstagedrc*, .markdownlint*, .mocha*, .node-version, .nodemon*, .npm*, .nvmrc, .pm2*, .pnp.*, .pnpm*, .prettier*, .releaserc*, .sentry*, .stackblitz*, .styleci*, .stylelint*, .tazerc*, .textlint*, .tool-versions, .travis*, .versionrc*, .vscode*, .watchman*, .xo-config*, .yamllint*, .yarnrc*, Procfile, api-extractor.json, apollo.config.*, appveyor*, ava.config.*, azure-pipelines*, bower.json, build.config.*, commitlint*, dangerfile*, dlint.json, dprint.json, firebase.json, grunt*, gulp*, histoire.config.*, jasmine.*, jenkins*, jest.config.*, jsconfig.*, karma*, lerna*, lighthouserc.*, lint-staged*, nest-cli.*, netlify*, nodemon*, nx.*, package-lock.json, package.nls*.json, phpcs.xml, playwright.config.*, pm2.*, pnpm*, prettier*, pullapprove*, puppeteer.config.*, pyrightconfig.json, release-tasks.sh, renovate*, rollup.config.*, stylelint*, tsconfig.*, tsdoc.*, tslint*, tsup.config.*, turbo*, typedoc*, unlighthouse*, vercel*, vetur.config.*, vitest.*, webpack*, workspace.json, xo.config.*, yarn*, babel.*, .babelrc, project.json, .oxlintrc.json, oxlint.json, nyc.config.*",
"Cargo.toml": "Cargo.lock, rust-toolchain*, rustfmt.toml, .taplo.toml", "Cargo.toml": "Cargo.lock, rust-toolchain*, rustfmt.toml, .taplo.toml",
"README.md": "LICENSE*, CHANGELOG.md, CODE_OF_CONDUCT.md, CONTRIBUTING.md, SECURITY.md, README.*", "README.md": "LICENSE*, CHANGELOG.md, CODE_OF_CONDUCT.md, CONTRIBUTING.md, SECURITY.md, README.*",
".gitignore": ".gitattributes, .dockerignore, .eslintignore, .prettierignore, .stylelintignore, .tslintignore, .yarnignore" ".gitignore": ".gitattributes, .dockerignore, .eslintignore, .prettierignore, .stylelintignore, .tslintignore, .yarnignore"
@@ -67,7 +67,7 @@ export const autoScrollOnBoundary = (
}; };
const cancelBoxListen = effect(() => { const cancelBoxListen = effect(() => {
box.value; void box.value;
startUpdate(); startUpdate();
}); });
@@ -24,12 +24,12 @@ import {
DataViewUIBase, DataViewUIBase,
DataViewUILogicBase, DataViewUILogicBase,
} from '../../../core/view/data-view-base.js'; } from '../../../core/view/data-view-base.js';
import { LEFT_TOOL_BAR_WIDTH } from '../consts.js';
import { import {
type TableSingleView,
TableViewRowSelection, TableViewRowSelection,
type TableViewSelectionWithType, type TableViewSelectionWithType,
} from '../../index.js'; } from '../selection.js';
import { LEFT_TOOL_BAR_WIDTH } from '../consts.js'; import type { TableSingleView } from '../table-view-manager.js';
import { TableClipboardController } from './controller/clipboard.js'; import { TableClipboardController } from './controller/clipboard.js';
import { TableDragController } from './controller/drag.js'; import { TableDragController } from './controller/drag.js';
import { TableHotkeysController } from './controller/hotkeys.js'; import { TableHotkeysController } from './controller/hotkeys.js';
@@ -60,10 +60,9 @@ export class BaseExtensionProvider<
* @param context - The context object containing scope and registration function * @param context - The context object containing scope and registration function
* @param option - Optional configuration options for the provider * @param option - Optional configuration options for the provider
*/ */
setup(context: Context<Scope>, option?: Options) { setup(_context: Context<Scope>, option?: Options) {
if (option) { if (option) {
this.schema.parse(option); this.schema.parse(option);
} }
context;
} }
} }
@@ -884,7 +884,7 @@ export class ConnectionOverlay extends Overlay {
private _setupThemeListener(): void { private _setupThemeListener(): void {
const themeService = this.gfx.std.get(ThemeProvider); const themeService = this.gfx.std.get(ThemeProvider);
this._themeDisposer = effect(() => { this._themeDisposer = effect(() => {
themeService.theme$; void themeService.theme$.value;
this._emphasisColor = this._getEmphasisColor(); this._emphasisColor = this._getEmphasisColor();
}); });
} }
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/await-thenable */
import type { import type {
Template, Template,
TemplateCategory, TemplateCategory,
@@ -9,7 +9,7 @@ import rehypeParse from 'rehype-parse';
import { unified } from 'unified'; import { unified } from 'unified';
import type { AffineTextAttributes } from '../../types/index.js'; import type { AffineTextAttributes } from '../../types/index.js';
import { HtmlDeltaConverter } from '../html/delta-converter.js'; import type { HtmlDeltaConverter } from '../html/delta-converter.js';
import { import {
rehypeInlineToBlock, rehypeInlineToBlock,
rehypeWrapInlineElements, rehypeWrapInlineElements,
@@ -873,7 +873,7 @@ export class PdfAdapter extends BaseAdapter<PdfAdapterFile> {
return { return {
table: { table: {
headerRows: 0, headerRows: 0,
widths: Array(sortedColumns.length).fill('*'), widths: Array.from({ length: sortedColumns.length }, () => '*'),
body: tableBody, body: tableBody,
}, },
margin: [0, 5, 0, 5], margin: [0, 5, 0, 5],
@@ -115,12 +115,9 @@ export async function printToPdf(
) as HTMLDivElement; ) as HTMLDivElement;
// force light theme in print iframe // force light theme in print iframe
iframe.contentWindow.document.documentElement.setAttribute( iframe.contentWindow.document.documentElement.dataset.theme = 'light';
'data-theme', iframe.contentWindow.document.body.dataset.theme = 'light';
'light' importedRoot.dataset.theme = 'light';
);
iframe.contentWindow.document.body.setAttribute('data-theme', 'light');
importedRoot.setAttribute('data-theme', 'light');
// draw saved canvas image to canvas // draw saved canvas image to canvas
const allImportedCanvas = importedRoot.getElementsByTagName('canvas'); const allImportedCanvas = importedRoot.getElementsByTagName('canvas');
@@ -126,7 +126,7 @@ export class EdgelessZoomToolbar extends WithDisposable(LitElement) {
this.disposables.add( this.disposables.add(
effect(() => { effect(() => {
this.gfx.tool.currentToolName$.value; void this.gfx.tool.currentToolName$.value;
this.requestUpdate(); this.requestUpdate();
}) })
); );
@@ -289,7 +289,7 @@ export class AffineKeyboardToolbar extends SignalWatcher(
this.disposables.add( this.disposables.add(
effect(() => { effect(() => {
const std = this.rootComponent.std; const std = this.rootComponent.std;
std.selection.value; void std.selection.value;
// wait cursor updated // wait cursor updated
requestAnimationFrame(() => { requestAnimationFrame(() => {
this._scrollCurrentBlockIntoView(); this._scrollCurrentBlockIntoView();
@@ -1,5 +1,5 @@
import type { ExtensionType, Schema, Workspace } from '@blocksuite/store'; import type { ExtensionType, Schema, Workspace } from '@blocksuite/store';
// @ts-ignore // @ts-expect-error -- mammoth.browser has no compatible type declaration for this subpath.
import { convertToHtml } from 'mammoth/mammoth.browser'; import { convertToHtml } from 'mammoth/mammoth.browser';
import { HtmlTransformer } from './html'; import { HtmlTransformer } from './html';
@@ -10,12 +10,12 @@ import { Container } from '@blocksuite/global/di';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions'; import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { sha } from '@blocksuite/global/utils'; import { sha } from '@blocksuite/global/utils';
import type { import type {
DocMeta,
ExtensionType, ExtensionType,
Schema, Schema,
Store, Store,
Workspace, Workspace,
} from '@blocksuite/store'; } from '@blocksuite/store';
import type { DocMeta } from '@blocksuite/store';
import { extMimeMap, Transformer } from '@blocksuite/store'; import { extMimeMap, Transformer } from '@blocksuite/store';
import type { AssetMap, ImportedFileEntry, PathBlobIdMap } from './type.js'; import type { AssetMap, ImportedFileEntry, PathBlobIdMap } from './type.js';
@@ -171,9 +171,11 @@ export class Unzip {
const fileExt = const fileExt =
fileName.lastIndexOf('.') === -1 ? '' : fileName.split('.').at(-1); fileName.lastIndexOf('.') === -1 ? '' : fileName.split('.').at(-1);
const mime = extMimeMap.get(fileExt ?? ''); const mime = extMimeMap.get(fileExt ?? '');
const content = new File([this.unzipped![path]], fileName, { const content = new File(
type: mime ?? '', [new Uint8Array(this.unzipped![path]).buffer],
}) as Blob; fileName,
mime ? { type: mime } : undefined
) as Blob;
const fixedPath = this.fixFileNameEncoding(path); const fixedPath = this.fixFileNameEncoding(path);
@@ -27,10 +27,10 @@ async function exportDocs(
titleMiddleware(collection.meta.docMetas), titleMiddleware(collection.meta.docMetas),
], ],
}); });
const snapshots = await Promise.all(docs.map(job.docToSnapshot));
await Promise.all( await Promise.all(
snapshots docs
.map(job.docToSnapshot)
.filter((snapshot): snapshot is DocSnapshot => !!snapshot) .filter((snapshot): snapshot is DocSnapshot => !!snapshot)
.map(async snapshot => { .map(async snapshot => {
// Use the title and id as the snapshot file name // Use the title and id as the snapshot file name
@@ -190,7 +190,7 @@ export class Clipboard extends LifeCycleWatcher {
); );
} }
return slice; return slice;
} catch (error) { } catch {
const getDataByType = this._getDataByType(data); const getDataByType = this._getDataByType(data);
const slice = await this._getSnapshotByPriority( const slice = await this._getSnapshotByPriority(
type => getDataByType(type), type => getDataByType(type),
@@ -1,5 +1,5 @@
import { LifeCycleWatcher } from '../extension/index.js';
import { BlockServiceIdentifier } from '../identifier.js'; import { BlockServiceIdentifier } from '../identifier.js';
import { LifeCycleWatcher } from './lifecycle-watcher.js';
export class ServiceManager extends LifeCycleWatcher { export class ServiceManager extends LifeCycleWatcher {
static override readonly key = 'serviceManager'; static override readonly key = 'serviceManager';
+4 -1
View File
@@ -87,6 +87,7 @@ export function batchRemoveChildren(
} }
uniqueElements.forEach(element => { uniqueElements.forEach(element => {
// oxlint-disable-next-line unicorn/prefer-dom-node-remove
container.removeChild(element); container.removeChild(element);
}); });
} }
@@ -114,7 +115,9 @@ function traverse(
}); });
} }
postCallBack && postCallBack(element); if (postCallBack) {
postCallBack(element);
}
}; };
innerTraverse(element); innerTraverse(element);
@@ -170,10 +170,10 @@ export class EditorHost extends SignalWatcher(
...Object.values(widgetTags), ...Object.values(widgetTags),
]; ];
await Promise.all( await Promise.all(
elementsTags.map(tag => { elementsTags.map(async tag => {
const element = this.renderRoot.querySelector(tag._$litStatic$); const element = this.renderRoot.querySelector(tag._$litStatic$);
if (element instanceof LitElement) { if (element instanceof LitElement) {
return element.updateComplete; return await element.updateComplete;
} }
return null; return null;
}) })
@@ -382,6 +382,7 @@ describe('addBlock', () => {
const doc0 = collection.createDoc('doc:home'); const doc0 = collection.createDoc('doc:home');
const doc1 = collection.createDoc('space:doc1'); const doc1 = collection.createDoc('space:doc1');
// eslint-disable-next-line @typescript-eslint/await-thenable
await Promise.all([doc0.load(), doc1.load()]); await Promise.all([doc0.load(), doc1.load()]);
assert.equal(collection.docs.size, 2); assert.equal(collection.docs.size, 2);
const store0 = doc0.getStore({ const store0 = doc0.getStore({
@@ -1,7 +1,7 @@
import { minimatch } from 'minimatch'; import { minimatch } from 'minimatch';
import { SCHEMA_NOT_FOUND_MESSAGE } from '../consts.js'; import { SCHEMA_NOT_FOUND_MESSAGE } from '../consts.js';
import { BlockSchema, type BlockSchemaType } from '../model/index.js'; import { BlockSchema, type BlockSchemaType } from '../model/block/zod.js';
import { SchemaValidateError } from './error.js'; import { SchemaValidateError } from './error.js';
/** /**
@@ -1,9 +1,6 @@
import { import { BlockModel } from '../model/block/block-model.js';
BlockModel, import { type DraftModel, toDraftModel } from '../model/block/draft.js';
type DraftModel, import type { Store } from '../model/store/store.js';
type Store,
toDraftModel,
} from '../model/index';
type SliceData = { type SliceData = {
content: DraftModel[]; content: DraftModel[];
@@ -3,14 +3,11 @@ import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { nextTick } from '@blocksuite/global/utils'; import { nextTick } from '@blocksuite/global/utils';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { import { BlockModel } from '../model/block/block-model.js';
BlockModel, import { type DraftModel, toDraftModel } from '../model/block/draft.js';
type BlockSchemaType, import type { BlockSchemaType } from '../model/block/zod.js';
type DraftModel, import type { Store } from '../model/store/store.js';
type Store, import type { Schema } from '../schema/schema.js';
toDraftModel,
} from '../model/index.js';
import type { Schema } from '../schema/index.js';
import { AssetsManager } from './assets.js'; import { AssetsManager } from './assets.js';
import { BaseBlockTransformer } from './base.js'; import { BaseBlockTransformer } from './base.js';
import type { import type {
+13 -89
View File
@@ -5,6 +5,7 @@ import eslint from '@eslint/js';
import tsParser from '@typescript-eslint/parser'; import tsParser from '@typescript-eslint/parser';
import eslintConfigPrettier from 'eslint-config-prettier'; import eslintConfigPrettier from 'eslint-config-prettier';
import importX from 'eslint-plugin-import-x'; import importX from 'eslint-plugin-import-x';
import oxlint from 'eslint-plugin-oxlint';
import react from 'eslint-plugin-react'; import react from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks'; import reactHooks from 'eslint-plugin-react-hooks';
import simpleImportSort from 'eslint-plugin-simple-import-sort'; import simpleImportSort from 'eslint-plugin-simple-import-sort';
@@ -16,7 +17,10 @@ const __require = createRequire(import.meta.url);
const rxjs = __require('@smarttools/eslint-plugin-rxjs'); const rxjs = __require('@smarttools/eslint-plugin-rxjs');
const ignoreList = readFileSync('.prettierignore', 'utf-8') const ignoreList = readFileSync(
new URL('.prettierignore', import.meta.url),
'utf-8'
)
.split('\n') .split('\n')
.filter(line => line.trim() && !line.startsWith('#')); .filter(line => line.trim() && !line.startsWith('#'));
@@ -60,105 +64,51 @@ export default tseslint.config(
'simple-import-sort': simpleImportSort, 'simple-import-sort': simpleImportSort,
rxjs, rxjs,
unicorn, unicorn,
oxlint,
}, },
rules: { rules: {
...eslint.configs.recommended.rules, ...eslint.configs.recommended.rules,
...react.configs.recommended.rules, ...react.configs.recommended.rules,
...react.configs['jsx-runtime'].rules, ...react.configs['jsx-runtime'].rules,
...reactHooks.configs.recommended.rules, ...reactHooks.configs.recommended.rules,
...oxlint.configs.recommended.rules,
// covered by TypeScript // covered by TypeScript
'no-dupe-args': 'off', 'no-dupe-args': 'off',
// the following rules are disabled because they are covered by oxlint // the following rules are disabled because they are covered by oxlint
'array-callback-return': 'off', 'array-callback-return': 'off',
'constructor-super': 'off',
eqeqeq: 'off', eqeqeq: 'off',
'getter-return': 'off', 'getter-return': 'off',
'for-direction': 'off',
'require-yield': 'off',
'use-isnan': 'off',
'valid-typeof': 'off',
'no-self-compare': 'off', 'no-self-compare': 'off',
'no-empty': 'off', 'no-empty': 'off',
'no-constant-binary-expression': 'off',
'no-constructor-return': 'off', 'no-constructor-return': 'off',
'no-func-assign': 'off',
'no-global-assign': 'off',
'no-ex-assign': 'off',
'no-fallthrough': 'off', 'no-fallthrough': 'off',
'no-irregular-whitespace': 'off',
'no-control-regex': 'off',
'no-with': 'off',
'no-debugger': 'off',
'no-const-assign': 'off',
'no-import-assign': 'off',
'no-setter-return': 'off',
'no-obj-calls': 'off',
'no-unsafe-negation': 'off',
'no-dupe-class-members': 'off',
'no-dupe-keys': 'off',
'no-this-before-super': 'off',
'no-empty-character-class': 'off',
'no-useless-catch': 'off',
'no-async-promise-executor': 'off',
'no-unreachable': 'off', 'no-unreachable': 'off',
'no-duplicate-case': 'off',
'no-empty-pattern': 'off',
'no-unused-labels': 'off',
'no-sparse-arrays': 'off',
'no-delete-var': 'off',
'no-compare-neg-zero': 'off',
'no-redeclare': 'off', 'no-redeclare': 'off',
'no-case-declarations': 'off', 'no-case-declarations': 'off',
'no-class-assign': 'off',
'no-var': 'off', 'no-var': 'off',
'no-self-assign': 'off',
'no-inner-declarations': 'off', 'no-inner-declarations': 'off',
'no-dupe-else-if': 'off',
'no-invalid-regexp': 'off',
'no-unsafe-finally': 'off',
'no-prototype-builtins': 'off', 'no-prototype-builtins': 'off',
'no-shadow-restricted-names': 'off',
'no-nonoctal-decimal-escape': 'off',
'no-constant-condition': 'off',
'no-useless-escape': 'off',
'no-unsafe-optional-chaining': 'off',
'no-extra-boolean-cast': 'off',
'no-regex-spaces': 'off', 'no-regex-spaces': 'off',
'no-unused-vars': 'off', 'no-unused-vars': 'off',
'no-undef': 'off', 'no-undef': 'off',
'no-cond-assign': 'off',
'react/jsx-no-useless-fragment': 'off', 'react/jsx-no-useless-fragment': 'off',
'react/no-unknown-property': 'off', 'react/no-unknown-property': 'off',
'react/no-string-refs': 'off',
'react/no-direct-mutation-state': 'off',
'react/require-render-return': 'off', 'react/require-render-return': 'off',
'react/jsx-no-undef': 'off',
'react/jsx-no-duplicate-props': 'off',
'react/jsx-key': 'off',
'react/no-danger-with-children': 'off',
'react/no-unescaped-entities': 'off', 'react/no-unescaped-entities': 'off',
'react/no-is-mounted': 'off',
'react/no-find-dom-node': 'off',
'react/no-children-prop': 'off',
'react/no-render-return-value': 'off',
'react/jsx-no-target-blank': 'off', 'react/jsx-no-target-blank': 'off',
'react/jsx-no-comment-textnodes': 'off', 'react/jsx-no-comment-textnodes': 'off',
'react/prop-types': 'off', 'react/prop-types': 'off',
'react-hooks/immutability': 'off',
'react-hooks/refs': 'off',
'react-hooks/set-state-in-effect': 'off',
'react-hooks/static-components': 'off',
'react-hooks/use-memo': 'off',
'sonarjs/no-useless-catch': 'off', 'sonarjs/no-useless-catch': 'off',
'@typescript-eslint/consistent-type-imports': 'off', '@typescript-eslint/consistent-type-imports': 'off',
'@typescript-eslint/no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-loss-of-precision': 'off',
'@typescript-eslint/ban-ts-comment': 'off', '@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/triple-slash-reference': 'off',
'@typescript-eslint/no-non-null-asserted-optional-chain': 'off',
'@typescript-eslint/no-duplicate-enum-values': 'off',
'@typescript-eslint/no-extra-non-null-assertion': 'off',
'@typescript-eslint/no-misused-new': 'off',
'@typescript-eslint/prefer-for-of': 'error', '@typescript-eslint/prefer-for-of': 'error',
'@typescript-eslint/no-unsafe-declaration-merging': 'off',
'@typescript-eslint/no-this-alias': 'off',
'@typescript-eslint/prefer-as-const': 'off',
'@typescript-eslint/no-var-requires': 'off', '@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/no-namespace': 'off', '@typescript-eslint/no-namespace': 'off',
'@typescript-eslint/no-unnecessary-type-constraint': 'off', '@typescript-eslint/no-unnecessary-type-constraint': 'off',
@@ -167,30 +117,13 @@ export default tseslint.config(
'@typescript-eslint/no-empty-function': 'off', '@typescript-eslint/no-empty-function': 'off',
// rules that are not supported by oxlint // rules that are not supported by oxlint
'no-unreachable-loop': 'error',
'@typescript-eslint/no-unsafe-function-type': 'error', '@typescript-eslint/no-unsafe-function-type': 'error',
'@typescript-eslint/no-wrapper-object-types': 'error',
'@typescript-eslint/unified-signatures': 'error', '@typescript-eslint/unified-signatures': 'error',
'@typescript-eslint/return-await': [ '@typescript-eslint/return-await': [
'error', 'error',
'error-handling-correctness-only', 'error-handling-correctness-only',
], ],
'@typescript-eslint/no-restricted-imports': [
'error',
{
patterns: [
{
group: ['**/dist'],
message: "Don't import from dist",
allowTypeImports: false,
},
{
group: ['**/src'],
message: "Don't import from src",
allowTypeImports: false,
},
],
},
],
'sonarjs/no-all-duplicated-branches': 'error', 'sonarjs/no-all-duplicated-branches': 'error',
'sonarjs/no-element-overwrite': 'error', 'sonarjs/no-element-overwrite': 'error',
'sonarjs/no-empty-collection': 'error', 'sonarjs/no-empty-collection': 'error',
@@ -198,7 +131,6 @@ export default tseslint.config(
'sonarjs/no-identical-conditions': 'error', 'sonarjs/no-identical-conditions': 'error',
'sonarjs/no-identical-expressions': 'error', 'sonarjs/no-identical-expressions': 'error',
'sonarjs/no-ignored-return': 'error', 'sonarjs/no-ignored-return': 'error',
'sonarjs/no-one-iteration-loop': 'error',
'sonarjs/no-use-of-empty-return-value': 'error', 'sonarjs/no-use-of-empty-return-value': 'error',
'sonarjs/non-existent-operator': 'error', 'sonarjs/non-existent-operator': 'error',
'sonarjs/no-collapsible-if': 'error', 'sonarjs/no-collapsible-if': 'error',
@@ -234,13 +166,6 @@ export default tseslint.config(
'error', 'error',
{ includeInternal: true }, { includeInternal: true },
], ],
'react-hooks/exhaustive-deps': [
'warn',
{
additionalHooks:
'(useAsyncCallback|useCatchEventCallback|useDraggable|useDropTarget|useRefEffect)',
},
],
'rxjs/finnish': [ 'rxjs/finnish': [
'error', 'error',
{ {
@@ -304,7 +229,6 @@ export default tseslint.config(
{ ignoreVoid: true }, { ignoreVoid: true },
], ],
'@typescript-eslint/no-misused-promises': 0, '@typescript-eslint/no-misused-promises': 0,
'@typescript-eslint/no-restricted-imports': 0,
}, },
}, },
{ {
+17 -15
View File
@@ -26,9 +26,10 @@
"lint:eslint:fix": "yarn lint:eslint --fix --fix-type problem,suggestion,layout", "lint:eslint:fix": "yarn lint:eslint --fix --fix-type problem,suggestion,layout",
"lint:prettier": "prettier --ignore-unknown --cache --check .", "lint:prettier": "prettier --ignore-unknown --cache --check .",
"lint:prettier:fix": "prettier --ignore-unknown --cache --write .", "lint:prettier:fix": "prettier --ignore-unknown --cache --write .",
"lint:ox": "oxlint -c oxlint.json --deny-warnings", "lint:ox": "oxlint --deny-warnings",
"lint": "yarn lint:eslint && yarn lint:prettier", "lint:ox:fix": "yarn lint:ox --fix",
"lint:fix": "yarn lint:eslint:fix && yarn lint:prettier:fix", "lint": "yarn lint:ox && yarn lint:eslint && yarn lint:prettier",
"lint:fix": "yarn lint:ox:fix && yarn lint:eslint:fix && yarn lint:prettier:fix",
"test": "vitest --run", "test": "vitest --run",
"test:ui": "vitest --ui", "test:ui": "vitest --ui",
"test:coverage": "vitest run --coverage", "test:coverage": "vitest run --coverage",
@@ -51,7 +52,7 @@
"devDependencies": { "devDependencies": {
"@affine-tools/cli": "workspace:*", "@affine-tools/cli": "workspace:*",
"@capacitor/cli": "^7.0.0", "@capacitor/cli": "^7.0.0",
"@eslint/js": "^9.16.0", "@eslint/js": "^9.39.2",
"@faker-js/faker": "^10.1.0", "@faker-js/faker": "^10.1.0",
"@istanbuljs/schema": "^0.1.3", "@istanbuljs/schema": "^0.1.3",
"@magic-works/i18n-codegen": "^0.6.1", "@magic-works/i18n-codegen": "^0.6.1",
@@ -61,32 +62,33 @@
"@toeverything/infra": "workspace:*", "@toeverything/infra": "workspace:*",
"@types/eslint": "^9.6.1", "@types/eslint": "^9.6.1",
"@types/node": "^22.0.0", "@types/node": "^22.0.0",
"@typescript-eslint/parser": "^8.18.0", "@typescript-eslint/parser": "^8.55.0",
"@vanilla-extract/vite-plugin": "^5.0.0", "@vanilla-extract/vite-plugin": "^5.0.0",
"@vitest/browser": "^3.2.4", "@vitest/browser": "^3.2.4",
"@vitest/coverage-istanbul": "^3.2.4", "@vitest/coverage-istanbul": "^3.2.4",
"@vitest/ui": "^3.2.4", "@vitest/ui": "^3.2.4",
"cross-env": "^10.1.0", "cross-env": "^10.1.0",
"electron": "^39.0.0", "electron": "^39.0.0",
"eslint": "^9.16.0", "eslint": "^9.39.2",
"eslint-config-prettier": "^10.0.0", "eslint-config-prettier": "^10.1.8",
"eslint-import-resolver-typescript": "^4.0.0", "eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-import-x": "^4.5.0", "eslint-plugin-import-x": "^4.16.1",
"eslint-plugin-react": "^7.37.2", "eslint-plugin-oxlint": "^1.46.0",
"eslint-plugin-react-hooks": "^5.1.0", "eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-simple-import-sort": "^12.1.1", "eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-sonarjs": "^3.0.1", "eslint-plugin-sonarjs": "^3.0.7",
"eslint-plugin-unicorn": "^59.0.0", "eslint-plugin-unicorn": "^63.0.0",
"happy-dom": "^20.0.0", "happy-dom": "^20.0.0",
"husky": "^9.1.7", "husky": "^9.1.7",
"lint-staged": "^16.0.0", "lint-staged": "^16.0.0",
"msw": "^2.12.4", "msw": "^2.12.4",
"oxlint": "~1.18.0", "oxlint": "^1.47.0",
"prettier": "^3.7.4", "prettier": "^3.7.4",
"semver": "^7.7.3", "semver": "^7.7.3",
"serve": "^14.2.4", "serve": "^14.2.4",
"typescript": "^5.7.2", "typescript": "^5.7.2",
"typescript-eslint": "^8.18.0", "typescript-eslint": "^8.55.0",
"unplugin-swc": "^1.5.9", "unplugin-swc": "^1.5.9",
"vite": "^7.2.7", "vite": "^7.2.7",
"vitest": "^3.2.4" "vitest": "^3.2.4"
+1 -1
View File
@@ -31,7 +31,7 @@ assert.strictEqual(
bench bench
.add('tiktoken', () => { .add('tiktoken', () => {
const encoder = encoding_for_model('gpt-4o'); const encoder = encoding_for_model('gpt-4o');
encoder.encode_ordinary(FIXTURE).length; void encoder.encode_ordinary(FIXTURE).length;
}) })
.add('native', () => { .add('native', () => {
fromModelName('gpt-4o').count(FIXTURE); fromModelName('gpt-4o').count(FIXTURE);
@@ -43,7 +43,6 @@ class MockR2Provider extends R2StorageProvider {
destroy() {} destroy() {}
// @ts-ignore expect override
override async proxyPutObject( override async proxyPutObject(
key: string, key: string,
body: any, body: any,
@@ -1,6 +1,7 @@
import { LookupAddress } from 'node:dns';
import type { ExecutionContext, TestFn } from 'ava'; import type { ExecutionContext, TestFn } from 'ava';
import ava from 'ava'; import ava from 'ava';
import { LookupAddress } from 'dns';
import Sinon from 'sinon'; import Sinon from 'sinon';
import type { Response } from 'supertest'; import type { Response } from 'supertest';
@@ -14,7 +15,6 @@ import { createTestingApp, TestingApp } from './utils';
type TestContext = { type TestContext = {
app: TestingApp; app: TestingApp;
}; };
const test = ava as TestFn<TestContext>; const test = ava as TestFn<TestContext>;
const LookupAddressStub = (async (_hostname, options) => { const LookupAddressStub = (async (_hostname, options) => {
@@ -51,10 +51,10 @@ function parseKey(privateKey: string) {
let priv: KeyObject; let priv: KeyObject;
try { try {
priv = createPrivateKey({ key: keyBuf, format: 'pem', type: 'pkcs8' }); priv = createPrivateKey({ key: keyBuf, format: 'pem', type: 'pkcs8' });
} catch (e1) { } catch {
try { try {
priv = createPrivateKey({ key: keyBuf, format: 'pem', type: 'sec1' }); priv = createPrivateKey({ key: keyBuf, format: 'pem', type: 'sec1' });
} catch (e2) { } catch {
// As a last resort rely on auto-detection // As a last resort rely on auto-detection
priv = createPrivateKey(keyBuf); priv = createPrivateKey(keyBuf);
} }
@@ -22,12 +22,14 @@ function firstNonEmpty(...values: Array<string | undefined>) {
} }
export function getRequestClientIp(req: Request) { export function getRequestClientIp(req: Request) {
return firstNonEmpty( return (
req.get('CF-Connecting-IP'), firstNonEmpty(
firstForwardedForIp(req.get('X-Forwarded-For')), req.get('CF-Connecting-IP'),
req.get('X-Real-IP'), firstForwardedForIp(req.get('X-Forwarded-For')),
req.ip req.get('X-Real-IP'),
)!; req.ip
) ?? ''
);
} }
export function getRequestTrackerId(req: Request) { export function getRequestTrackerId(req: Request) {
@@ -39,6 +41,7 @@ export function getRequestTrackerId(req: Request) {
req.get('X-Real-IP'), req.get('X-Real-IP'),
req.get('CF-Ray'), req.get('CF-Ray'),
req.ip req.ip
)! ) ??
''
); );
} }
@@ -180,7 +180,7 @@ export async function assertSsrFSafeUrl(
let addresses: string[]; let addresses: string[];
try { try {
addresses = await resolveHostAddresses(hostname); addresses = await resolveHostAddresses(hostname);
} catch (error) { } catch {
throw createSsrfBlockedError('unresolvable_hostname', { throw createSsrfBlockedError('unresolvable_hostname', {
url: url.toString(), url: url.toString(),
hostname, hostname,
@@ -44,11 +44,11 @@ const staticPaths = new Set([
'trash', 'trash',
]); ]);
const markdownType = [ const markdownType = new Set([
'text/markdown', 'text/markdown',
'application/markdown', 'application/markdown',
'text/x-markdown', 'text/x-markdown',
]; ]);
@Controller('/workspace') @Controller('/workspace')
export class DocRendererController { export class DocRendererController {
@@ -109,7 +109,7 @@ export class DocRendererController {
if ( if (
isDocPath && isDocPath &&
req.accepts().some(t => markdownType.includes(t.toLowerCase())) req.accepts().some(t => markdownType.has(t.toLowerCase()))
) { ) {
try { try {
const allowPreview = await this.allowDocPreview(workspaceId, sub); const allowPreview = await this.allowDocPreview(workspaceId, sub);
@@ -56,7 +56,7 @@ defineModuleConfig('mailer', {
env: 'MAILER_PASSWORD', env: 'MAILER_PASSWORD',
}, },
'SMTP.sender': { 'SMTP.sender': {
desc: 'Sender of all the emails (e.g. "AFFiNE Self Hosted \<noreply@example.com\>")', desc: 'Sender of all the emails (e.g. "AFFiNE Self Hosted <noreply@example.com>")',
default: 'AFFiNE Self Hosted <noreply@example.com>', default: 'AFFiNE Self Hosted <noreply@example.com>',
env: 'MAILER_SENDER', env: 'MAILER_SENDER',
}, },
@@ -92,7 +92,7 @@ defineModuleConfig('mailer', {
default: '', default: '',
}, },
'fallbackSMTP.sender': { 'fallbackSMTP.sender': {
desc: 'Sender of all the emails (e.g. "AFFiNE Self Hosted \<noreply@example.com\>")', desc: 'Sender of all the emails (e.g. "AFFiNE Self Hosted <noreply@example.com>")',
default: '', default: '',
}, },
'fallbackSMTP.ignoreTLS': { 'fallbackSMTP.ignoreTLS': {
@@ -2,9 +2,11 @@ import { Body, Controller, Options, Post, Req, Res } from '@nestjs/common';
import type { Request, Response } from 'express'; import type { Request, Response } from 'express';
import { BadRequest, Throttle, UseNamedGuard } from '../../base'; import { BadRequest, Throttle, UseNamedGuard } from '../../base';
import type { CurrentUser as CurrentUserType } from '../auth'; import {
import { Public } from '../auth'; CurrentUser,
import { CurrentUser } from '../auth'; type CurrentUser as CurrentUserType,
Public,
} from '../auth';
import { TelemetryService } from './service'; import { TelemetryService } from './service';
import { TelemetryAck, type TelemetryBatch } from './types'; import { TelemetryAck, type TelemetryBatch } from './types';
@@ -110,10 +110,10 @@ export class CalendarAccountModel extends BaseModel {
refreshIntervalMinutes: data.refreshIntervalMinutes, refreshIntervalMinutes: data.refreshIntervalMinutes,
}; };
if (!!accessToken) { if (accessToken) {
updateData.accessToken = accessToken; updateData.accessToken = accessToken;
} }
if (!!refreshToken) { if (refreshToken) {
updateData.refreshToken = refreshToken; updateData.refreshToken = refreshToken;
} }
@@ -117,7 +117,7 @@ export class CopilotSessionModel extends BaseModel {
if (typeof value !== 'string') { if (typeof value !== 'string') {
return value; return value;
} }
return value.replace(/\u0000/g, '') as T; return value.replaceAll('\0', '') as T;
} }
private sanitizeJsonValue<T>(value: T): T { private sanitizeJsonValue<T>(value: T): T {
@@ -22,8 +22,8 @@ import {
CalendarProviderListCalendarsParams, CalendarProviderListCalendarsParams,
CalendarProviderListEventsParams, CalendarProviderListEventsParams,
CalendarProviderListEventsResult, CalendarProviderListEventsResult,
CalendarProviderName,
} from './def'; } from './def';
import { CalendarProviderName } from './factory';
import { CalendarSyncTokenInvalid } from './google'; import { CalendarSyncTokenInvalid } from './google';
const XML_PARSER = new XMLParser({ const XML_PARSER = new XMLParser({
@@ -113,7 +113,7 @@ const isRedirectStatus = (status: number) =>
const splitHeaderTokens = (value: string) => const splitHeaderTokens = (value: string) =>
value value
.split(/,(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)/) .split(/,(?=(?:[^"]*"[^"]*")*[^"]*$)/)
.map(token => token.trim()) .map(token => token.trim())
.filter(Boolean); .filter(Boolean);
@@ -2,12 +2,7 @@ import { Inject, Injectable, Logger } from '@nestjs/common';
import type { CalendarAccount } from '@prisma/client'; import type { CalendarAccount } from '@prisma/client';
import { CalendarProviderRequestError, Config, OnEvent } from '../../../base'; import { CalendarProviderRequestError, Config, OnEvent } from '../../../base';
import { CalendarProviderFactory } from './factory'; import { CalendarProviderFactory, CalendarProviderName } from './factory';
export enum CalendarProviderName {
Google = 'google',
CalDAV = 'caldav',
}
export interface CalendarProviderTokens { export interface CalendarProviderTokens {
accessToken: string; accessToken: string;
@@ -1,12 +1,20 @@
import { Injectable, Logger } from '@nestjs/common'; import { Injectable, Logger } from '@nestjs/common';
import type { CalendarProvider } from './def'; export enum CalendarProviderName {
import { CalendarProviderName } from './def'; Google = 'google',
CalDAV = 'caldav',
}
export interface CalendarProviderRef {
provider: CalendarProviderName;
}
@Injectable() @Injectable()
export class CalendarProviderFactory { export class CalendarProviderFactory<
TProvider extends CalendarProviderRef = CalendarProviderRef,
> {
private readonly logger = new Logger(CalendarProviderFactory.name); private readonly logger = new Logger(CalendarProviderFactory.name);
readonly #providers = new Map<CalendarProviderName, CalendarProvider>(); readonly #providers = new Map<CalendarProviderName, TProvider>();
get providers() { get providers() {
return Array.from(this.#providers.keys()); return Array.from(this.#providers.keys());
@@ -16,12 +24,12 @@ export class CalendarProviderFactory {
return this.#providers.get(name); return this.#providers.get(name);
} }
register(provider: CalendarProvider) { register(provider: TProvider) {
this.#providers.set(provider.provider, provider); this.#providers.set(provider.provider, provider);
this.logger.log(`Calendar provider [${provider.provider}] registered.`); this.logger.log(`Calendar provider [${provider.provider}] registered.`);
} }
unregister(provider: CalendarProvider) { unregister(provider: TProvider) {
this.#providers.delete(provider.provider); this.#providers.delete(provider.provider);
this.logger.log(`Calendar provider [${provider.provider}] unregistered.`); this.logger.log(`Calendar provider [${provider.provider}] unregistered.`);
} }
@@ -1,17 +1,17 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { CalendarProviderRequestError } from '../../../base'; import { CalendarProviderRequestError } from '../../../base';
import { CalendarProvider } from './def';
import { import {
CalendarProvider,
CalendarProviderEvent, CalendarProviderEvent,
CalendarProviderListCalendarsParams, CalendarProviderListCalendarsParams,
CalendarProviderListEventsParams, CalendarProviderListEventsParams,
CalendarProviderListEventsResult, CalendarProviderListEventsResult,
CalendarProviderName,
CalendarProviderTokens, CalendarProviderTokens,
CalendarProviderWatchParams, CalendarProviderWatchParams,
CalendarProviderWatchResult, CalendarProviderWatchResult,
} from './def'; } from './def';
import { CalendarProviderName } from './factory';
export class CalendarSyncTokenInvalid extends Error { export class CalendarSyncTokenInvalid extends Error {
readonly code = 'calendar_sync_token_invalid'; readonly code = 'calendar_sync_token_invalid';
@@ -14,9 +14,8 @@ export type {
CalendarProviderWatchParams, CalendarProviderWatchParams,
CalendarProviderWatchResult, CalendarProviderWatchResult,
} from './def'; } from './def';
export { CalendarProviderName } from './def';
export { CalendarProvider } from './def'; export { CalendarProvider } from './def';
export { CalendarProviderFactory } from './factory'; export { CalendarProviderFactory, CalendarProviderName } from './factory';
export { CalendarSyncTokenInvalid, GoogleCalendarProvider } from './google'; export { CalendarSyncTokenInvalid, GoogleCalendarProvider } from './google';
export const CalendarProviders = [GoogleCalendarProvider, CalDAVProvider]; export const CalendarProviders = [GoogleCalendarProvider, CalDAVProvider];
@@ -18,10 +18,10 @@ import {
CalendarProvider, CalendarProvider,
CalendarProviderEvent, CalendarProviderEvent,
CalendarProviderEventTime, CalendarProviderEventTime,
CalendarProviderFactory,
CalendarProviderName, CalendarProviderName,
CalendarSyncTokenInvalid, CalendarSyncTokenInvalid,
} from './providers'; } from './providers';
import { CalendarProviderFactory } from './providers';
import type { LinkCalDAVAccountInput } from './types'; import type { LinkCalDAVAccountInput } from './types';
const TOKEN_REFRESH_SKEW_MS = 60 * 1000; const TOKEN_REFRESH_SKEW_MS = 60 * 1000;
@@ -35,7 +35,7 @@ export class CalendarService {
constructor( constructor(
private readonly models: Models, private readonly models: Models,
private readonly providerFactory: CalendarProviderFactory, private readonly providerFactory: CalendarProviderFactory<CalendarProvider>,
private readonly mutex: Mutex, private readonly mutex: Mutex,
private readonly config: Config, private readonly config: Config,
private readonly url: URLHelper private readonly url: URLHelper
@@ -105,11 +105,11 @@ export class CalendarService {
const accessToken = accountTokens.accessToken; const accessToken = accountTokens.accessToken;
if (accessToken) { if (accessToken) {
await Promise.allSettled( await Promise.allSettled(
needToStopChannel.map(s => { needToStopChannel.map(async s => {
if (!s.customChannelId || !s.customResourceId) { if (!s.customChannelId || !s.customResourceId) {
return Promise.resolve(); return;
} }
return provider.stopChannel?.({ return await provider.stopChannel?.({
accessToken, accessToken,
channelId: s.customChannelId, channelId: s.customChannelId,
resourceId: s.customResourceId, resourceId: s.customResourceId,
@@ -654,8 +654,11 @@ export class CalendarService {
} }
const zone = time.timeZone ?? fallbackTimezone ?? 'UTC'; const zone = time.timeZone ?? fallbackTimezone ?? 'UTC';
if (!time.date) {
throw new Error('Calendar provider returned all-day event without date');
}
return { return {
date: this.convertDateToUtc(time.date!, zone), date: this.convertDateToUtc(time.date, zone),
allDay: true, allDay: true,
}; };
} }
@@ -49,7 +49,7 @@ import {
FileChunkSimilarity, FileChunkSimilarity,
Models, Models,
} from '../../../models'; } from '../../../models';
import { CopilotEmbeddingJob } from '../embedding'; import { CopilotEmbeddingJob } from '../embedding/job';
import { COPILOT_LOCKER, CopilotType } from '../resolver'; import { COPILOT_LOCKER, CopilotType } from '../resolver';
import { ChatSessionService } from '../session'; import { ChatSessionService } from '../session';
import { CopilotStorage } from '../storage'; import { CopilotStorage } from '../storage';
@@ -15,7 +15,8 @@ import {
ContextFile, ContextFile,
Models, Models,
} from '../../../models'; } from '../../../models';
import { type EmbeddingClient, getEmbeddingClient } from '../embedding'; import { getEmbeddingClient } from '../embedding/client';
import type { EmbeddingClient } from '../embedding/types';
import { ContextSession } from './session'; import { ContextSession } from './session';
const CONTEXT_SESSION_KEY = 'context-session'; const CONTEXT_SESSION_KEY = 'context-session';
@@ -11,7 +11,7 @@ import {
FileChunkSimilarity, FileChunkSimilarity,
Models, Models,
} from '../../../models'; } from '../../../models';
import { EmbeddingClient } from '../embedding'; import { EmbeddingClient } from '../embedding/types';
export class ContextSession implements AsyncDisposable { export class ContextSession implements AsyncDisposable {
constructor( constructor(
@@ -47,14 +47,14 @@ import {
} from '../../base'; } from '../../base';
import { ServerFeature, ServerService } from '../../core'; import { ServerFeature, ServerService } from '../../core';
import { CurrentUser, Public } from '../../core/auth'; import { CurrentUser, Public } from '../../core/auth';
import { CopilotContextService } from './context'; import { CopilotContextService } from './context/service';
import { CopilotProviderFactory } from './providers/factory';
import type { CopilotProvider } from './providers/provider';
import { import {
CopilotProvider,
CopilotProviderFactory,
ModelInputType, ModelInputType,
ModelOutputType, ModelOutputType,
StreamObject, type StreamObject,
} from './providers'; } from './providers/types';
import { StreamObjectParser } from './providers/utils'; import { StreamObjectParser } from './providers/utils';
import { ChatSession, ChatSessionService } from './session'; import { ChatSession, ChatSessionService } from './session';
import { CopilotStorage } from './storage'; import { CopilotStorage } from './storage';
@@ -12,14 +12,14 @@ import {
Embedding, Embedding,
EMBEDDING_DIMENSIONS, EMBEDDING_DIMENSIONS,
} from '../../../models'; } from '../../../models';
import { PromptService } from '../prompt'; import { PromptService } from '../prompt/service';
import { CopilotProviderFactory } from '../providers/factory';
import type { CopilotProvider } from '../providers/provider';
import { import {
type CopilotProvider,
CopilotProviderFactory,
type ModelFullConditions, type ModelFullConditions,
ModelInputType, ModelInputType,
ModelOutputType, ModelOutputType,
} from '../providers'; } from '../providers/types';
import { EmbeddingClient, type ReRankResult } from './types'; import { EmbeddingClient, type ReRankResult } from './types';
const EMBEDDING_MODEL = 'gemini-embedding-001'; const EMBEDDING_MODEL = 'gemini-embedding-001';
@@ -8,7 +8,7 @@ import { DocReader, DocWriter } from '../../../core/doc';
import { AccessController } from '../../../core/permission'; import { AccessController } from '../../../core/permission';
import { clearEmbeddingChunk } from '../../../models'; import { clearEmbeddingChunk } from '../../../models';
import { IndexerService } from '../../indexer'; import { IndexerService } from '../../indexer';
import { CopilotContextService } from '../context'; import { CopilotContextService } from '../context/service';
@Injectable() @Injectable()
export class WorkspaceMcpProvider { export class WorkspaceMcpProvider {
@@ -4,7 +4,11 @@ import { AiPrompt } from '@prisma/client';
import Mustache from 'mustache'; import Mustache from 'mustache';
import { getTokenEncoder } from '../../../native'; import { getTokenEncoder } from '../../../native';
import { PromptConfig, PromptMessage, PromptParams } from '../providers'; import type {
PromptConfig,
PromptMessage,
PromptParams,
} from '../providers/types';
// disable escaping // disable escaping
Mustache.escape = (text: string) => text; Mustache.escape = (text: string) => text;
@@ -1,7 +1,7 @@
import { Logger } from '@nestjs/common'; import { Logger } from '@nestjs/common';
import { AiPrompt, PrismaClient } from '@prisma/client'; import { AiPrompt, PrismaClient } from '@prisma/client';
import { PromptConfig, PromptMessage } from '../providers'; import type { PromptConfig, PromptMessage } from '../providers/types';
type Prompt = Omit< type Prompt = Omit<
AiPrompt, AiPrompt,
@@ -8,7 +8,7 @@ import {
PromptConfigSchema, PromptConfigSchema,
PromptMessage, PromptMessage,
PromptMessageSchema, PromptMessageSchema,
} from '../providers'; } from '../providers/types';
import { ChatPrompt } from './chat-prompt'; import { ChatPrompt } from './chat-prompt';
import { import {
CopilotPromptScenario, CopilotPromptScenario,
@@ -13,8 +13,8 @@ import { DocReader, DocWriter } from '../../../core/doc';
import { AccessController } from '../../../core/permission'; import { AccessController } from '../../../core/permission';
import { Models } from '../../../models'; import { Models } from '../../../models';
import { IndexerService } from '../../indexer'; import { IndexerService } from '../../indexer';
import { CopilotContextService } from '../context'; import { CopilotContextService } from '../context/service';
import { PromptService } from '../prompt'; import { PromptService } from '../prompt/service';
import { import {
buildBlobContentGetter, buildBlobContentGetter,
buildContentGetter, buildContentGetter,
@@ -42,9 +42,9 @@ import { AccessController, DocAction } from '../../core/permission';
import { UserType } from '../../core/user'; import { UserType } from '../../core/user';
import type { ListSessionOptions, UpdateChatSession } from '../../models'; import type { ListSessionOptions, UpdateChatSession } from '../../models';
import { CopilotCronJobs } from './cron'; import { CopilotCronJobs } from './cron';
import { PromptService } from './prompt'; import { PromptService } from './prompt/service';
import { PromptMessage, StreamObject } from './providers';
import { CopilotProviderFactory } from './providers/factory'; import { CopilotProviderFactory } from './providers/factory';
import type { PromptMessage, StreamObject } from './providers/types';
import { ChatSessionService } from './session'; import { ChatSessionService } from './session';
import { CopilotStorage } from './storage'; import { CopilotStorage } from './storage';
import { type ChatHistory, type ChatMessage, SubmittedMessage } from './types'; import { type ChatHistory, type ChatMessage, SubmittedMessage } from './types';
@@ -28,13 +28,14 @@ import {
import { SubscriptionService } from '../payment/service'; import { SubscriptionService } from '../payment/service';
import { SubscriptionPlan, SubscriptionStatus } from '../payment/types'; import { SubscriptionPlan, SubscriptionStatus } from '../payment/types';
import { ChatMessageCache } from './message'; import { ChatMessageCache } from './message';
import { ChatPrompt, PromptService } from './prompt'; import { ChatPrompt } from './prompt/chat-prompt';
import { PromptService } from './prompt/service';
import { CopilotProviderFactory } from './providers/factory';
import { import {
CopilotProviderFactory,
ModelOutputType, ModelOutputType,
PromptMessage, type PromptMessage,
PromptParams, type PromptParams,
} from './providers'; } from './providers/types';
import { import {
type ChatHistory, type ChatHistory,
type ChatMessage, type ChatMessage,
@@ -322,7 +323,7 @@ export class ChatSessionService {
private stripNullBytes(value?: string | null): string { private stripNullBytes(value?: string | null): string {
if (!value) return ''; if (!value) return '';
return value.replace(/\u0000/g, ''); return value.replaceAll('\0', '');
} }
private isNullByteError(error: unknown): boolean { private isNullByteError(error: unknown): boolean {
@@ -3,9 +3,8 @@ import { tool } from 'ai';
import { z } from 'zod'; import { z } from 'zod';
import { AccessController } from '../../../core/permission'; import { AccessController } from '../../../core/permission';
import type { ContextSession } from '../context/session';
import type { CopilotChatOptions } from '../providers';
import { toolError } from './error'; import { toolError } from './error';
import type { ContextSession, CopilotChatOptions } from './types';
const logger = new Logger('ContextBlobReadTool'); const logger = new Logger('ContextBlobReadTool');
@@ -2,9 +2,9 @@ import { Logger } from '@nestjs/common';
import { tool } from 'ai'; import { tool } from 'ai';
import { z } from 'zod'; import { z } from 'zod';
import type { PromptService } from '../prompt';
import type { CopilotProviderFactory } from '../providers';
import { toolError } from './error'; import { toolError } from './error';
import type { CopilotProviderFactory, PromptService } from './types';
const logger = new Logger('CodeArtifactTool'); const logger = new Logger('CodeArtifactTool');
/** /**
* A copilot tool that produces a completely self-contained HTML artifact. * A copilot tool that produces a completely self-contained HTML artifact.
@@ -2,9 +2,8 @@ import { Logger } from '@nestjs/common';
import { tool } from 'ai'; import { tool } from 'ai';
import { z } from 'zod'; import { z } from 'zod';
import type { PromptService } from '../prompt';
import type { CopilotProviderFactory } from '../providers';
import { toolError } from './error'; import { toolError } from './error';
import type { CopilotProviderFactory, PromptService } from './types';
const logger = new Logger('ConversationSummaryTool'); const logger = new Logger('ConversationSummaryTool');
@@ -2,9 +2,8 @@ import { Logger } from '@nestjs/common';
import { tool } from 'ai'; import { tool } from 'ai';
import { z } from 'zod'; import { z } from 'zod';
import type { PromptService } from '../prompt';
import type { CopilotProviderFactory } from '../providers';
import { toolError } from './error'; import { toolError } from './error';
import type { CopilotProviderFactory, PromptService } from './types';
const logger = new Logger('DocComposeTool'); const logger = new Logger('DocComposeTool');
@@ -3,8 +3,11 @@ import { z } from 'zod';
import { DocReader } from '../../../core/doc'; import { DocReader } from '../../../core/doc';
import { AccessController } from '../../../core/permission'; import { AccessController } from '../../../core/permission';
import { type PromptService } from '../prompt'; import type {
import type { CopilotChatOptions, CopilotProviderFactory } from '../providers'; CopilotChatOptions,
CopilotProviderFactory,
PromptService,
} from './types';
const CodeEditSchema = z const CodeEditSchema = z
.array( .array(
@@ -3,8 +3,8 @@ import { z } from 'zod';
import type { AccessController } from '../../../core/permission'; import type { AccessController } from '../../../core/permission';
import type { IndexerService, SearchDoc } from '../../indexer'; import type { IndexerService, SearchDoc } from '../../indexer';
import type { CopilotChatOptions } from '../providers';
import { toolError } from './error'; import { toolError } from './error';
import type { CopilotChatOptions } from './types';
export const buildDocKeywordSearchGetter = ( export const buildDocKeywordSearchGetter = (
ac: AccessController, ac: AccessController,
@@ -5,8 +5,8 @@ import { z } from 'zod';
import { DocReader } from '../../../core/doc'; import { DocReader } from '../../../core/doc';
import { AccessController } from '../../../core/permission'; import { AccessController } from '../../../core/permission';
import { Models, publicUserSelect } from '../../../models'; import { Models, publicUserSelect } from '../../../models';
import type { CopilotChatOptions } from '../providers';
import { toolError } from './error'; import { toolError } from './error';
import type { CopilotChatOptions } from './types';
const logger = new Logger('DocReadTool'); const logger = new Logger('DocReadTool');
@@ -8,10 +8,12 @@ import {
clearEmbeddingChunk, clearEmbeddingChunk,
type Models, type Models,
} from '../../../models'; } from '../../../models';
import type { CopilotContextService } from '../context';
import type { ContextSession } from '../context/session';
import type { CopilotChatOptions } from '../providers';
import { toolError } from './error'; import { toolError } from './error';
import type {
ContextSession,
CopilotChatOptions,
CopilotContextService,
} from './types';
export const buildDocSearchGetter = ( export const buildDocSearchGetter = (
ac: AccessController, ac: AccessController,
@@ -4,8 +4,8 @@ import { z } from 'zod';
import { DocWriter } from '../../../core/doc'; import { DocWriter } from '../../../core/doc';
import { AccessController } from '../../../core/permission'; import { AccessController } from '../../../core/permission';
import type { CopilotChatOptions } from '../providers';
import { toolError } from './error'; import { toolError } from './error';
import type { CopilotChatOptions } from './types';
const logger = new Logger('DocWriteTool'); const logger = new Logger('DocWriteTool');
@@ -2,9 +2,8 @@ import { Logger } from '@nestjs/common';
import { tool } from 'ai'; import { tool } from 'ai';
import { z } from 'zod'; import { z } from 'zod';
import type { PromptService } from '../prompt';
import type { CopilotProviderFactory } from '../providers';
import { toolError } from './error'; import { toolError } from './error';
import type { CopilotProviderFactory, PromptService } from './types';
const logger = new Logger('SectionEditTool'); const logger = new Logger('SectionEditTool');
@@ -0,0 +1,5 @@
export type { CopilotContextService } from '../context/service';
export type { ContextSession } from '../context/session';
export type { PromptService } from '../prompt/service';
export type { CopilotProviderFactory } from '../providers/factory';
export type { CopilotChatOptions } from '../providers/types';
@@ -125,6 +125,7 @@ export class CopilotTranscriptionResolver {
user.id, user.id,
workspaceId, workspaceId,
blobId, blobId,
// eslint-disable-next-line @typescript-eslint/await-thenable
await Promise.all(allBlobs) await Promise.all(allBlobs)
); );
@@ -15,14 +15,10 @@ import {
sniffMime, sniffMime,
} from '../../../base'; } from '../../../base';
import { Models } from '../../../models'; import { Models } from '../../../models';
import { PromptService } from '../prompt'; import { PromptService } from '../prompt/service';
import { import type { CopilotProvider, PromptMessage } from '../providers';
CopilotProvider, import { CopilotProviderFactory } from '../providers/factory';
CopilotProviderFactory, import { CopilotProviderType, ModelOutputType } from '../providers/types';
CopilotProviderType,
ModelOutputType,
PromptMessage,
} from '../providers';
import { CopilotStorage } from '../storage'; import { CopilotStorage } from '../storage';
import { import {
AudioBlobInfos, AudioBlobInfos,
@@ -171,7 +167,7 @@ export class CopilotTranscriptionService {
if (payload.success) { if (payload.success) {
let { url, mimeType, infos } = payload.data; let { url, mimeType, infos } = payload.data;
infos = infos || []; infos = infos || [];
if (url && mimeType && !infos.find(i => i.url === url)) { if (url && mimeType && !infos.some(i => i.url === url)) {
infos.push({ url, mimeType }); infos.push({ url, mimeType });
} }
@@ -1,7 +1,7 @@
import { z } from 'zod'; import { z } from 'zod';
import type { ChatPrompt } from './prompt'; import type { ChatPrompt } from './prompt/chat-prompt';
import { PromptMessageSchema, PureMessageSchema } from './providers'; import { PromptMessageSchema, PureMessageSchema } from './providers/types';
const takeFirst = (v: unknown) => (Array.isArray(v) ? v[0] : v); const takeFirst = (v: unknown) => (Array.isArray(v) ? v[0] : v);
@@ -3,7 +3,7 @@ import { Readable } from 'node:stream';
import type { Request } from 'express'; import type { Request } from 'express';
import { OneMB, readBufferWithLimit } from '../../base'; import { OneMB, readBufferWithLimit } from '../../base';
import type { PromptTools } from './providers'; import type { PromptTools } from './providers/types';
import type { ToolsConfig } from './types'; import type { ToolsConfig } from './types';
export const MAX_EMBEDDABLE_SIZE = 50 * OneMB; export const MAX_EMBEDDABLE_SIZE = 50 * OneMB;
@@ -4,7 +4,7 @@ import { fileURLToPath } from 'node:url';
import { Logger } from '@nestjs/common'; import { Logger } from '@nestjs/common';
import Piscina from 'piscina'; import Piscina from 'piscina';
import { CopilotChatOptions } from '../providers'; import type { CopilotChatOptions } from '../providers/types';
import type { NodeExecuteResult, NodeExecutor } from './executor'; import type { NodeExecuteResult, NodeExecutor } from './executor';
import { getWorkflowExecutor, NodeExecuteState } from './executor'; import { getWorkflowExecutor, NodeExecuteState } from './executor';
import type { import type {
@@ -1,6 +1,6 @@
import { Injectable, Logger } from '@nestjs/common'; import { Injectable, Logger } from '@nestjs/common';
import { CopilotChatOptions } from '../providers'; import type { CopilotChatOptions } from '../providers/types';
import { WorkflowGraphList } from './graph'; import { WorkflowGraphList } from './graph';
import { WorkflowNode } from './node'; import { WorkflowNode } from './node';
import type { WorkflowGraph, WorkflowGraphInstances } from './types'; import type { WorkflowGraph, WorkflowGraphInstances } from './types';
@@ -1,6 +1,6 @@
import { Logger } from '@nestjs/common'; import { Logger } from '@nestjs/common';
import { CopilotChatOptions } from '../providers'; import type { CopilotChatOptions } from '../providers/types';
import { NodeExecuteState } from './executor'; import { NodeExecuteState } from './executor';
import { WorkflowNode } from './node'; import { WorkflowNode } from './node';
import type { WorkflowGraphInstances, WorkflowNodeState } from './types'; import type { WorkflowGraphInstances, WorkflowNodeState } from './types';
@@ -132,10 +132,11 @@ export class AppleOAuthProvider extends OAuthProvider {
{ method: 'GET' }, { method: 'GET' },
{ treatServerErrorAsInvalid: true } { treatServerErrorAsInvalid: true }
); );
const idToken = tokens.idToken;
const payload = await new Promise<JwtPayload>((resolve, reject) => { const payload = await new Promise<JwtPayload>((resolve, reject) => {
jwt.verify( jwt.verify(
tokens.idToken!, idToken,
(header, callback) => { (header, callback) => {
const key = keys.find(key => key.kid === header.kid); const key = keys.find(key => key.kid === header.kid);
if (!key) { if (!key) {
@@ -29,6 +29,36 @@ const SHOULD_MANUAL_REDIRECT =
BUILD_CONFIG.isAndroid || BUILD_CONFIG.isIOS || BUILD_CONFIG.isElectron; BUILD_CONFIG.isAndroid || BUILD_CONFIG.isIOS || BUILD_CONFIG.isElectron;
const UPLOAD_REQUEST_TIMEOUT = 0; const UPLOAD_REQUEST_TIMEOUT = 0;
function toStrictArrayBuffer(
data: ArrayBuffer | ArrayBufferLike | ArrayBufferView
): ArrayBuffer {
if (data instanceof ArrayBuffer) {
return data;
}
if (ArrayBuffer.isView(data)) {
if (data.buffer instanceof ArrayBuffer) {
if (data.byteOffset === 0 && data.byteLength === data.buffer.byteLength) {
return data.buffer;
}
return data.buffer.slice(
data.byteOffset,
data.byteOffset + data.byteLength
);
}
const bytes = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
const copy = new Uint8Array(bytes.byteLength);
copy.set(bytes);
return copy.buffer;
}
const bytes = new Uint8Array(data);
const copy = new Uint8Array(bytes.byteLength);
copy.set(bytes);
return copy.buffer;
}
export class CloudBlobStorage extends BlobStorageBase { export class CloudBlobStorage extends BlobStorageBase {
static readonly identifier = 'CloudBlobStorage'; static readonly identifier = 'CloudBlobStorage';
override readonly isReadonly = false; override readonly isReadonly = false;
@@ -127,8 +157,11 @@ export class CloudBlobStorage extends BlobStorageBase {
if (upload.method === BlobUploadMethod.PRESIGNED) { if (upload.method === BlobUploadMethod.PRESIGNED) {
try { try {
if (!upload.uploadUrl) {
throw new Error('Missing upload URL for presigned upload.');
}
await this.uploadViaPresigned( await this.uploadViaPresigned(
upload.uploadUrl!, upload.uploadUrl,
upload.headers, upload.headers,
blob.data, blob.data,
signal signal
@@ -143,15 +176,20 @@ export class CloudBlobStorage extends BlobStorageBase {
if (upload.method === BlobUploadMethod.MULTIPART) { if (upload.method === BlobUploadMethod.MULTIPART) {
try { try {
if (!upload.uploadId || !upload.partSize) {
throw new Error(
'Missing upload ID or part size for multipart upload.'
);
}
const parts = await this.uploadViaMultipart( const parts = await this.uploadViaMultipart(
blob.key, blob.key,
upload.uploadId!, upload.uploadId,
upload.partSize!, upload.partSize,
blob.data, blob.data,
upload.uploadedParts, upload.uploadedParts,
signal signal
); );
await this.completeUpload(blob.key, upload.uploadId!, parts, signal); await this.completeUpload(blob.key, upload.uploadId, parts, signal);
return; return;
} catch { } catch {
if (upload.uploadId) { if (upload.uploadId) {
@@ -216,7 +254,9 @@ export class CloudBlobStorage extends BlobStorageBase {
query: setBlobMutation, query: setBlobMutation,
variables: { variables: {
workspaceId: this.options.id, workspaceId: this.options.id,
blob: new File([blob.data], blob.key, { type: blob.mime }), blob: new File([toStrictArrayBuffer(blob.data)], blob.key, {
type: blob.mime,
}),
}, },
context: { signal }, context: { signal },
timeout: UPLOAD_REQUEST_TIMEOUT, timeout: UPLOAD_REQUEST_TIMEOUT,
@@ -232,7 +272,7 @@ export class CloudBlobStorage extends BlobStorageBase {
const res = await this.fetchWithTimeout(uploadUrl, { const res = await this.fetchWithTimeout(uploadUrl, {
method: 'PUT', method: 'PUT',
headers: headers ?? undefined, headers: headers ?? undefined,
body: data, body: toStrictArrayBuffer(data),
signal, signal,
timeout: UPLOAD_REQUEST_TIMEOUT, timeout: UPLOAD_REQUEST_TIMEOUT,
}); });
@@ -275,7 +315,7 @@ export class CloudBlobStorage extends BlobStorageBase {
{ {
method: 'PUT', method: 'PUT',
headers: part.workspace.blobUploadPartUrl.headers ?? undefined, headers: part.workspace.blobUploadPartUrl.headers ?? undefined,
body: chunk, body: toStrictArrayBuffer(chunk),
signal, signal,
timeout: UPLOAD_REQUEST_TIMEOUT, timeout: UPLOAD_REQUEST_TIMEOUT,
} }
@@ -141,10 +141,10 @@ export class CloudIndexerStorage extends IndexerStorageBase {
} }
override async refreshIfNeed(): Promise<void> { override async refreshIfNeed(): Promise<void> {
return Promise.resolve(); return;
} }
override async indexVersion(): Promise<number> { override async indexVersion(): Promise<number> {
return Promise.resolve(1); return 1;
} }
} }
@@ -222,6 +222,6 @@ export class IndexedDBIndexerStorage extends IndexerStorageBase {
// Get the current indexer version // Get the current indexer version
// increase this number to re-index all docs // increase this number to re-index all docs
async indexVersion(): Promise<number> { async indexVersion(): Promise<number> {
return Promise.resolve(1); return 1;
} }
} }
@@ -1,4 +1,5 @@
import { merge, Observable, of, Subject } from 'rxjs'; import type { Observable } from 'rxjs';
import { merge, of, Subject } from 'rxjs';
import { filter, throttleTime } from 'rxjs/operators'; import { filter, throttleTime } from 'rxjs/operators';
import { share } from '../../../connection'; import { share } from '../../../connection';
@@ -194,9 +195,9 @@ export class SqliteIndexerStorage extends IndexerStorageBase {
const schema = IndexerSchema[table]; const schema = IndexerSchema[table];
for (const [field, values] of document.fields) { for (const [field, values] of document.fields) {
const fieldSchema = schema[field]; const fieldSchema = schema[field];
// @ts-expect-error // @ts-expect-error -- IndexerSchema uses runtime-keyed fields from each table schema.
const shouldIndex = fieldSchema.index !== false; const shouldIndex = fieldSchema.index !== false;
// @ts-expect-error // @ts-expect-error -- IndexerSchema uses runtime-keyed fields from each table schema.
const shouldStore = fieldSchema.store !== false; const shouldStore = fieldSchema.store !== false;
if (!shouldStore && !shouldIndex) continue; if (!shouldStore && !shouldIndex) continue;
@@ -86,9 +86,9 @@ export class DummyIndexerStorage extends IndexerStorageBase {
return Promise.resolve(); return Promise.resolve();
} }
override async refreshIfNeed(): Promise<void> { override async refreshIfNeed(): Promise<void> {
return Promise.resolve(); return;
} }
override async indexVersion(): Promise<number> { override async indexVersion(): Promise<number> {
return Promise.resolve(0); return 0;
} }
} }
@@ -190,6 +190,7 @@ export class BlobSyncImpl implements BlobSync {
): Promise<void> { ): Promise<void> {
return Promise.race([ return Promise.race([
Promise.all( Promise.all(
// eslint-disable-next-line @typescript-eslint/await-thenable
peerId peerId
? [this.fullDownloadPeer(peerId)] ? [this.fullDownloadPeer(peerId)]
: this.peers.map(p => this.fullDownloadPeer(p.peerId)) : this.peers.map(p => this.fullDownloadPeer(p.peerId))
@@ -125,8 +125,8 @@ export class TelemetryManager {
private mergeContext(event: TelemetryEvent): TelemetryEvent { private mergeContext(event: TelemetryEvent): TelemetryEvent {
const mergedUserProps = { const mergedUserProps = {
...(this.context.userProperties ?? {}), ...this.context.userProperties,
...(event.userProperties ?? {}), ...event.userProperties,
}; };
const mergedContext = { const mergedContext = {
+7 -7
View File
@@ -1,6 +1,6 @@
import { Buffer } from 'node:buffer'; import type { Buffer } from 'node:buffer';
import { stringify as stringifyQuery } from 'node:querystring'; import { stringify as stringifyQuery } from 'node:querystring';
import { Readable } from 'node:stream'; import type { Readable } from 'node:stream';
import aws4 from 'aws4'; import aws4 from 'aws4';
import { XMLParser } from 'fast-xml-parser'; import { XMLParser } from 'fast-xml-parser';
@@ -180,16 +180,16 @@ export function parseListPartsXml(xml: string): ParsedListParts {
function buildEndpoint(config: S3CompatConfig) { function buildEndpoint(config: S3CompatConfig) {
const url = new URL(config.endpoint); const url = new URL(config.endpoint);
if (config.forcePathStyle) { if (config.forcePathStyle) {
const segments = url.pathname.split('/').filter(Boolean); const firstSegment = url.pathname.split('/').find(Boolean);
if (segments[0] !== config.bucket) { if (firstSegment !== config.bucket) {
url.pathname = joinPath(url.pathname, config.bucket); url.pathname = joinPath(url.pathname, config.bucket);
} }
return url; return url;
} }
const pathSegments = url.pathname.split('/').filter(Boolean); const firstSegment = url.pathname.split('/').find(Boolean);
const hostHasBucket = url.hostname.startsWith(`${config.bucket}.`); const hostHasBucket = url.hostname.startsWith(`${config.bucket}.`);
const pathHasBucket = pathSegments[0] === config.bucket; const pathHasBucket = firstSegment === config.bucket;
if (!hostHasBucket && !pathHasBucket) { if (!hostHasBucket && !pathHasBucket) {
url.hostname = `${config.bucket}.${url.hostname}`; url.hostname = `${config.bucket}.${url.hostname}`;
} }
@@ -297,7 +297,7 @@ export class S3Compat implements S3CompatClient {
const expiresInSeconds = this.presignConfig.expiresInSeconds; const expiresInSeconds = this.presignConfig.expiresInSeconds;
const path = this.buildObjectPath(key); const path = this.buildObjectPath(key);
const queryString = buildQuery({ const queryString = buildQuery({
...(query ?? {}), ...query,
'X-Amz-Expires': expiresInSeconds, 'X-Amz-Expires': expiresInSeconds,
}); });
const requestPath = queryString ? `${path}?${queryString}` : path; const requestPath = queryString ? `${path}?${queryString}` : path;
@@ -66,6 +66,7 @@ export function SharedDataTable<TData extends { id: string }, TValue>({
setColumnFilters([]); setColumnFilters([]);
}, [resetFiltersDeps]); }, [resetFiltersDeps]);
// eslint-disable-next-line react-hooks/incompatible-library
const table = useReactTable({ const table = useReactTable({
data, data,
columns, columns,
@@ -1,4 +1,4 @@
import { FeatureType } from '@affine/graphql'; import type { FeatureType } from '@affine/graphql';
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { Header } from '../header'; import { Header } from '../header';
@@ -1,5 +1,6 @@
import { useQuery } from '@affine/admin/use-query'; import { useQuery } from '@affine/admin/use-query';
import { FeatureType, listUsersQuery } from '@affine/graphql'; import type { FeatureType } from '@affine/graphql';
import { listUsersQuery } from '@affine/graphql';
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
export const useUserList = (filter?: { export const useUserList = (filter?: {
@@ -1,6 +1,7 @@
import { Button } from '@affine/admin/components/ui/button'; import { Button } from '@affine/admin/components/ui/button';
import { Input } from '@affine/admin/components/ui/input'; import { Input } from '@affine/admin/components/ui/input';
import { AdminWorkspaceSort, FeatureType } from '@affine/graphql'; import type { FeatureType } from '@affine/graphql';
import { AdminWorkspaceSort } from '@affine/graphql';
import type { Table } from '@tanstack/react-table'; import type { Table } from '@tanstack/react-table';
import { import {
type ChangeEvent, type ChangeEvent,
@@ -7,11 +7,11 @@ import { Input } from '@affine/admin/components/ui/input';
import { Label } from '@affine/admin/components/ui/label'; import { Label } from '@affine/admin/components/ui/label';
import { Separator } from '@affine/admin/components/ui/separator'; import { Separator } from '@affine/admin/components/ui/separator';
import { Switch } from '@affine/admin/components/ui/switch'; import { Switch } from '@affine/admin/components/ui/switch';
import type { FeatureType } from '@affine/graphql';
import { import {
adminUpdateWorkspaceMutation, adminUpdateWorkspaceMutation,
adminWorkspaceQuery, adminWorkspaceQuery,
adminWorkspacesQuery, adminWorkspacesQuery,
FeatureType,
} from '@affine/graphql'; } from '@affine/graphql';
import { AccountIcon } from '@blocksuite/icons/rc'; import { AccountIcon } from '@blocksuite/icons/rc';
import { cssVarV2 } from '@toeverything/theme/v2'; import { cssVarV2 } from '@toeverything/theme/v2';
@@ -1,4 +1,5 @@
import { AdminWorkspaceSort, FeatureType } from '@affine/graphql'; import type { FeatureType } from '@affine/graphql';
import { AdminWorkspaceSort } from '@affine/graphql';
import { useState } from 'react'; import { useState } from 'react';
import { Header } from '../header'; import { Header } from '../header';
@@ -1,9 +1,8 @@
import { useQuery } from '@affine/admin/use-query'; import { useQuery } from '@affine/admin/use-query';
import type { AdminWorkspaceSort, FeatureType } from '@affine/graphql';
import { import {
adminWorkspacesCountQuery, adminWorkspacesCountQuery,
AdminWorkspaceSort,
adminWorkspacesQuery, adminWorkspacesQuery,
FeatureType,
} from '@affine/graphql'; } from '@affine/graphql';
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
@@ -27,7 +26,7 @@ export const useWorkspaceList = (filter?: {
.join(',')}-${filter?.orderBy ?? ''}-${JSON.stringify( .join(',')}-${filter?.orderBy ?? ''}-${JSON.stringify(
filter?.flags ?? {} filter?.flags ?? {}
)}`, )}`,
[filter?.features, filter?.flags, filter?.keyword, filter?.orderBy] [filter]
); );
useEffect(() => { useEffect(() => {
@@ -52,18 +51,7 @@ export const useWorkspaceList = (filter?: {
enableDocEmbedding: filter?.flags?.enableDocEmbedding, enableDocEmbedding: filter?.flags?.enableDocEmbedding,
}, },
}), }),
[ [filter, pagination.pageIndex, pagination.pageSize]
filter?.features,
filter?.flags?.enableAi,
filter?.flags?.enableDocEmbedding,
filter?.flags?.enableSharing,
filter?.flags?.enableUrlPreview,
filter?.flags?.public,
filter?.keyword,
filter?.orderBy,
pagination.pageIndex,
pagination.pageSize,
]
); );
const { data: listData, isValidating: isListValidating } = useQuery( const { data: listData, isValidating: isListValidating } = useQuery(
@@ -24,7 +24,7 @@ export function useDisposable<T extends Disposable | AsyncDisposable>(
error: null, error: null,
}); });
// oxlint-disable-next-line react-hooks/exhaustive-deps // oxlint-disable-next-line react/exhaustive-deps
useEffect(() => { useEffect(() => {
const abortController = new AbortController(); const abortController = new AbortController();
let _data: T | null = null; let _data: T | null = null;
@@ -54,7 +54,7 @@ export function useDisposable<T extends Disposable | AsyncDisposable>(
} }
} }
}; };
// eslint-disable-next-line react-hooks/exhaustive-deps // oxlint-disable-next-line react/exhaustive-deps
}, deps || []); }, deps || []);
return state; return state;
@@ -3,9 +3,6 @@
*/ */
import { useDebugValue, useEffect, useState } from 'react'; import { useDebugValue, useEffect, useState } from 'react';
// internalRef is used as a reference and therefore save to be used inside an effect
/* eslint-disable react-hooks/exhaustive-deps */
// the `process.env.NODE_ENV !== 'production'` condition is resolved by the build tool // the `process.env.NODE_ENV !== 'production'` condition is resolved by the build tool
const noop: (...args: any[]) => any = () => {}; const noop: (...args: any[]) => any = () => {};
@@ -84,6 +81,7 @@ export const useRefEffect = <T>(
} }
}; };
}, // Keep a ref to the latest dependencies }, // Keep a ref to the latest dependencies
// oxlint-disable-next-line react/exhaustive-deps
(internalRef.dependencies_ = dependencies) (internalRef.dependencies_ = dependencies)
); );
@@ -1,4 +1,3 @@
export * from './button'; export * from './button';
export * from './dropdown-button'; export * from './dropdown-button';
export * from './icon-button'; export * from './icon-button';
export * from './radio';
@@ -1,60 +0,0 @@
import type {
RadioGroupItemProps,
RadioGroupProps,
} from '@radix-ui/react-radio-group';
import * as RadixRadioGroup from '@radix-ui/react-radio-group';
import clsx from 'clsx';
import type { CSSProperties } from 'react';
import { forwardRef } from 'react';
import { RadioGroup } from '../radio';
import * as styles from './styles.css';
// for reference
RadioGroup;
/**
* @deprecated
* use {@link RadioGroup } instead
*/
export const RadioButton = forwardRef<
HTMLButtonElement,
RadioGroupItemProps & { spanStyle?: string }
>(({ children, className, spanStyle, ...props }, ref) => {
return (
<RadixRadioGroup.Item
ref={ref}
{...props}
className={clsx(styles.radioButton, className)}
>
<span className={clsx(styles.radioUncheckedButton, spanStyle)}>
{children}
</span>
<RadixRadioGroup.Indicator
className={clsx(styles.radioButtonContent, spanStyle)}
>
{children}
</RadixRadioGroup.Indicator>
</RadixRadioGroup.Item>
);
});
RadioButton.displayName = 'RadioButton';
/**
* @deprecated
* use {@link RadioGroup} instead
*/
export const RadioButtonGroup = forwardRef<
HTMLDivElement,
RadioGroupProps & { width?: CSSProperties['width'] }
>(({ className, style, width, ...props }, ref) => {
return (
<RadixRadioGroup.Root
ref={ref}
className={clsx(styles.radioButtonGroup, className)}
style={{ width, ...style }}
{...props}
></RadixRadioGroup.Root>
);
});
RadioButtonGroup.displayName = 'RadioButtonGroup';
@@ -1,5 +1,6 @@
import { cssVar } from '@toeverything/theme'; import { cssVar } from '@toeverything/theme';
import { style } from '@vanilla-extract/css'; import { style } from '@vanilla-extract/css';
export const dropdownBtn = style({ export const dropdownBtn = style({
display: 'inline-flex', display: 'inline-flex',
alignItems: 'center', alignItems: 'center',
@@ -30,6 +31,7 @@ export const dropdownBtn = style({
}, },
}, },
}); });
export const divider = style({ export const divider = style({
width: '0.5px', width: '0.5px',
height: '16px', height: '16px',
@@ -38,6 +40,7 @@ export const divider = style({
margin: '0 4px', margin: '0 4px',
marginRight: 0, marginRight: 0,
}); });
export const dropdownWrapper = style({ export const dropdownWrapper = style({
width: '100%', width: '100%',
height: '100%', height: '100%',
@@ -47,6 +50,7 @@ export const dropdownWrapper = style({
paddingLeft: '4px', paddingLeft: '4px',
paddingRight: '10px', paddingRight: '10px',
}); });
export const dropdownIcon = style({ export const dropdownIcon = style({
borderRadius: '4px', borderRadius: '4px',
selectors: { selectors: {
@@ -55,55 +59,3 @@ export const dropdownIcon = style({
}, },
}, },
}); });
export const radioButton = style({
flexGrow: 1,
flex: 1,
selectors: {
'&:not(:last-of-type)': {
marginRight: '4px',
},
},
});
export const radioButtonContent = style({
fontSize: cssVar('fontXs'),
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '28px',
padding: '4px 8px',
borderRadius: '8px',
filter: 'drop-shadow(0px 0px 4px rgba(0, 0, 0, 0.1))',
whiteSpace: 'nowrap',
userSelect: 'none',
fontWeight: 600,
selectors: {
'&:hover': {
background: cssVar('hoverColor'),
},
'&[data-state="checked"]': {
background: cssVar('white'),
},
},
});
export const radioUncheckedButton = style([
radioButtonContent,
{
color: cssVar('textSecondaryColor'),
filter: 'none',
selectors: {
'[data-state="checked"] > &': {
display: 'none',
},
},
},
]);
export const radioButtonGroup = style({
display: 'inline-flex',
justifyContent: 'space-between',
alignItems: 'center',
background: cssVar('hoverColorFilled'),
borderRadius: '10px',
padding: '2px',
// @ts-expect-error - fix electron drag
WebkitAppRegion: 'no-drag',
});
@@ -46,7 +46,7 @@ export const DatePicker = (props: DatePickerProps) => {
setCursor(dayjs(v)); setCursor(dayjs(v));
onChange?.(v); onChange?.(v);
}, },
[onChange] [setMode, onChange]
); );
const onCursorChange = useCallback( const onCursorChange = useCallback(
@@ -83,7 +83,7 @@ export const useDraggable = <D extends DNDData = DNDData>(
} }
: undefined, : undefined,
}; };
// eslint-disable-next-line react-hooks/exhaustive-deps // oxlint-disable-next-line react/exhaustive-deps
}, [...deps, getOptions, context.toExternalData]); }, [...deps, getOptions, context.toExternalData]);
useEffect(() => { useEffect(() => {
@@ -206,7 +206,7 @@ export const useDropTarget = <D extends DNDData = DNDData>(
(dropTargetContext.fromExternalData as fromExternalData<D>)) (dropTargetContext.fromExternalData as fromExternalData<D>))
: undefined, : undefined,
}; };
// eslint-disable-next-line react-hooks/exhaustive-deps // oxlint-disable-next-line react/exhaustive-deps
}, [...deps, getOptions, dropTargetContext.fromExternalData]); }, [...deps, getOptions, dropTargetContext.fromExternalData]);
const getDropTargetOptions = useCallback(() => { const getDropTargetOptions = useCallback(() => {
@@ -94,7 +94,7 @@ export const useDndMonitor = <D extends DNDData = DNDData>(
(dropTargetContext.fromExternalData as fromExternalData<D>)) (dropTargetContext.fromExternalData as fromExternalData<D>))
: undefined, : undefined,
}; };
// eslint-disable-next-line react-hooks/exhaustive-deps // oxlint-disable-next-line react/exhaustive-deps
}, [...deps, getOptions, dropTargetContext.fromExternalData]); }, [...deps, getOptions, dropTargetContext.fromExternalData]);
const monitorOptions = useMemo(() => { const monitorOptions = useMemo(() => {
@@ -93,15 +93,15 @@ export const InlineEdit = ({
const [editingValue, setEditingValue] = useState(value); const [editingValue, setEditingValue] = useState(value);
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
useImperativeHandle<InlineEditHandle, InlineEditHandle>(handleRef, () => ({
triggerEdit,
}));
const triggerEdit = useCallback(() => { const triggerEdit = useCallback(() => {
if (!editable) return; if (!editable) return;
setEditing(true); setEditing(true);
}, [editable]); }, [editable]);
useImperativeHandle<InlineEditHandle, InlineEditHandle>(handleRef, () => ({
triggerEdit,
}));
const onDoubleClick = useCallback(() => { const onDoubleClick = useCallback(() => {
if (trigger !== 'doubleClick') return; if (trigger !== 'doubleClick') return;
triggerEdit(); triggerEdit();
@@ -69,7 +69,7 @@ export const RowInput = forwardRef<HTMLInputElement, RowInputProps>(
if (!onBlur) return; if (!onBlur) return;
selectRef.current?.addEventListener('blur', onBlur as any); selectRef.current?.addEventListener('blur', onBlur as any);
return () => { return () => {
// oxlint-disable-next-line react-hooks/exhaustive-deps // oxlint-disable-next-line react/exhaustive-deps
selectRef.current?.removeEventListener('blur', onBlur as any); selectRef.current?.removeEventListener('blur', onBlur as any);
}; };
}, [onBlur, selectRef]); }, [onBlur, selectRef]);

Some files were not shown because too many files have changed in this diff Show More