refactor(server): indexer & worker & sync perf (#15504)

This commit is contained in:
DarkSky
2026-08-21 08:11:37 +08:00
committed by GitHub
parent 8c9aad9a9b
commit c57004ea2c
259 changed files with 16530 additions and 17793 deletions
@@ -3,10 +3,27 @@ import { describe, expect, test } from 'vitest';
import {
assertSupportedServerVersion,
getSyncProtocol,
isBatchSyncServerVersion,
MIN_SUPPORTED_SERVER_VERSION,
} from './server-config';
describe('server config version guard', () => {
test('selects batch sync from server version', () => {
expect(isBatchSyncServerVersion('0.27.4')).toBe(false);
expect(isBatchSyncServerVersion('0.27.5')).toBe(true);
expect(isBatchSyncServerVersion('0.27.5-beta.1')).toBe(true);
expect(isBatchSyncServerVersion('2026.8.20-canary.15')).toBe(true);
expect(isBatchSyncServerVersion('0.28.0')).toBe(true);
});
test('does not select a route before server version is verified', () => {
expect(() => getSyncProtocol()).toThrow(UserFriendlyError);
expect(() => getSyncProtocol('0.26.9')).toThrow(UserFriendlyError);
expect(getSyncProtocol('0.27.4')).toBe('legacy');
expect(getSyncProtocol('0.27.5')).toBe('batch');
});
test('accepts supported server versions', () => {
expect(() => assertSupportedServerVersion('0.27.0')).not.toThrow();
expect(() => assertSupportedServerVersion('0.27.0-beta.5')).not.toThrow();
@@ -14,6 +14,7 @@ export type ServerConfigType = ServerConfigQuery['serverConfig'] &
OauthProvidersQuery['serverConfig'];
export const MIN_SUPPORTED_SERVER_VERSION = '0.27.0';
export const BATCH_SYNC_SERVER_VERSION = '0.27.5';
const NETWORK_ERROR_PATTERNS = [
/failed to fetch/i,
@@ -63,6 +64,21 @@ export function assertSupportedServerVersion(version?: string | null) {
}
}
export function isBatchSyncServerVersion(version?: string | null) {
const normalized = version && semver.valid(version, { loose: true });
return (
!!normalized &&
semver.gte(normalized, `${BATCH_SYNC_SERVER_VERSION}-0`, {
loose: true,
})
);
}
export function getSyncProtocol(version?: string | null) {
assertSupportedServerVersion(version);
return isBatchSyncServerVersion(version) ? 'batch' : 'legacy';
}
function mapServerConfigError(error: unknown) {
const userFriendlyError = UserFriendlyError.fromAny(error);
if (
@@ -0,0 +1,77 @@
import { Framework, LiveData } from '@toeverything/infra';
import { Subject } from 'rxjs';
import { describe, expect, test, vi } from 'vitest';
import { AuthService } from '../../cloud/services/auth';
import { NbstoreService } from '../../storage/services/nbstore';
import { NotificationStore } from '../stores/notification';
import { NotificationCountService } from './count';
function createCountService() {
const events$ = new Subject<{ type: 'ready' } | { count: number }>();
const request = vi.fn().mockResolvedValue({ count: 1 });
const cache = new LiveData(0);
const setNotificationCountCache = vi.fn((count: number) =>
cache.setValue(count)
);
const store = {
watchNotificationCountCache: () => cache,
setNotificationCountCache,
} as unknown as NotificationStore;
const auth = {
session: {
status$: new LiveData<'authenticated' | 'unauthenticated'>(
'authenticated'
),
},
} as unknown as AuthService;
const nbstore = {
realtime: {
request,
subscribe: () => events$,
},
} as unknown as NbstoreService;
const framework = new Framework();
framework.service(AuthService, auth);
framework.store(NotificationStore, store);
framework.service(NbstoreService, nbstore);
framework.service(NotificationCountService, [
NotificationStore,
AuthService,
NbstoreService,
]);
return {
events$,
request,
service: framework.provider().get(NotificationCountService),
setNotificationCountCache,
};
}
describe('NotificationCountService', () => {
test('uses snapshots for reconnects and applies realtime count changes', async () => {
const { events$, request, service, setNotificationCountCache } =
createCountService();
expect(service.loggedIn$.value).toBe(true);
service.handleServerStarted();
events$.next({ type: 'ready' });
await vi.waitFor(() => expect(request).toHaveBeenCalled());
await vi.waitFor(() => expect(service.count$.value).toBe(1));
events$.next({ count: 3 });
expect(service.count$.value).toBe(3);
expect(setNotificationCountCache).toHaveBeenLastCalledWith(3);
const requestsBeforeFocus = request.mock.calls.length;
service.handleApplicationFocused();
events$.next({ type: 'ready' });
await vi.waitFor(() =>
expect(request.mock.calls.length).toBeGreaterThan(requestsBeforeFocus)
);
await vi.waitFor(() => expect(service.count$.value).toBe(1));
service.dispose();
});
});
@@ -1,8 +1,9 @@
import { LiveData, OnEvent, Service } from '@toeverything/infra';
import { AccountChanged, type AuthService } from '../../cloud';
import { AccountChanged } from '../../cloud/events/account-changed';
import { ServerStarted } from '../../cloud/events/server-started';
import { RealtimeLiveQuery } from '../../cloud/realtime/live-query';
import type { AuthService } from '../../cloud/services/auth';
import { ApplicationFocused } from '../../lifecycle';
import type { NbstoreService } from '../../storage';
import type { NotificationStore } from '../stores/notification';
@@ -19,7 +20,10 @@ export class NotificationCountService extends Service {
super();
}
loggedIn$ = this.authService.session.status$.map(v => v === 'authenticated');
loggedIn$ = LiveData.from(
this.authService.session.status$.map(v => v === 'authenticated'),
this.authService.session.status$.value === 'authenticated'
);
readonly count$ = LiveData.from(this.store.watchNotificationCountCache(), 0);
readonly isLoading$ = new LiveData(false);
@@ -8,6 +8,7 @@ import type { StoreClient } from '@affine/nbstore/worker/client';
import { Entity } from '@toeverything/infra';
import type { ServerService } from '../../cloud';
import { getSyncProtocol } from '../../cloud/stores/server-config';
import type { NbstoreService } from '../../storage';
export class UserDBEngine extends Entity<{
@@ -64,6 +65,9 @@ export class UserDBEngine extends Entity<{
opts: {
id: this.userId,
serverBaseUrl: serverService.server.baseUrl,
syncProtocol: getSyncProtocol(
serverService.server.config$.value.version
),
type: 'userspace',
isSelfHosted:
serverService.server.config$.value.type ===
@@ -66,6 +66,7 @@ import {
GraphQLService,
WorkspaceServerService,
} from '../../cloud';
import { getSyncProtocol } from '../../cloud/stores/server-config';
import { type GlobalState, NbstoreService } from '../../storage';
import type {
Workspace,
@@ -519,6 +520,7 @@ class CloudWorkspaceFlavourProvider implements WorkspaceFlavourProvider {
type: 'workspace',
id: workspaceId,
serverBaseUrl: this.server.serverMetadata.baseUrl,
syncProtocol: getSyncProtocol(this.server.config$.value.version),
isSelfHosted:
this.server.config$.value.type ===
ServerDeploymentType.Selfhosted,
@@ -537,6 +539,7 @@ class CloudWorkspaceFlavourProvider implements WorkspaceFlavourProvider {
type: 'workspace',
id: workspaceId,
serverBaseUrl: this.server.serverMetadata.baseUrl,
syncProtocol: getSyncProtocol(this.server.config$.value.version),
isSelfHosted:
this.server.config$.value.type ===
ServerDeploymentType.Selfhosted,