refactor(server): config system (#11081)

This commit is contained in:
forehalo
2025-03-27 12:32:28 +00:00
parent 7091111f85
commit 0ea38680fa
274 changed files with 7583 additions and 5841 deletions

View File

@@ -6,8 +6,6 @@ import ava, { TestFn } from 'ava';
import { applyUpdate, Doc as YDoc } from 'yjs';
import { createTestingApp, type TestingApp } from '../../../__tests__/utils';
import { AppModule } from '../../../app.module';
import { ConfigModule } from '../../../base/config';
import { Models } from '../../../models';
import { WorkspaceBlobStorage } from '../../storage/wrappers/blob';
import { DocReader, PgWorkspaceDocStorageAdapter } from '..';
@@ -22,9 +20,7 @@ const test = ava as TestFn<{
}>;
test.before(async t => {
const app = await createTestingApp({
imports: [ConfigModule.forRoot(), AppModule],
});
const app = await createTestingApp();
t.context.models = app.get(Models);
t.context.docReader = app.get(DocReader);

View File

@@ -6,9 +6,8 @@ import ava, { TestFn } from 'ava';
import { applyUpdate, Doc as YDoc } from 'yjs';
import { createTestingApp, type TestingApp } from '../../../__tests__/utils';
import { AppModule } from '../../../app.module';
import { Config, UserFriendlyError } from '../../../base';
import { ConfigModule } from '../../../base/config';
import { UserFriendlyError } from '../../../base';
import { ConfigFactory } from '../../../base/config';
import { Models } from '../../../models';
import { DatabaseDocReader, DocReader, PgWorkspaceDocStorageAdapter } from '..';
import { RpcDocReader } from '../reader';
@@ -16,40 +15,45 @@ import { RpcDocReader } from '../reader';
const test = ava as TestFn<{
models: Models;
app: TestingApp;
docApp: TestingApp;
docReader: DocReader;
databaseDocReader: DatabaseDocReader;
adapter: PgWorkspaceDocStorageAdapter;
config: Config;
config: ConfigFactory;
}>;
test.before(async t => {
const app = await createTestingApp({
imports: [
ConfigModule.forRoot({
flavor: {
doc: false,
},
docService: {
endpoint: '',
},
}),
AppModule,
],
});
// test key
process.env.AFFINE_PRIVATE_KEY = `-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgS3IAkshQuSmFWGpe
rGTg2vwaC3LdcvBQlYHHMBYJZMyhRANCAAQXdT/TAh4neNEpd4UqpDIEqWv0XvFo
BRJxGsC5I/fetqObdx1+KEjcm8zFU2xLaUTw9IZCu8OslloOjQv4ur0a
-----END PRIVATE KEY-----`;
// @ts-expect-error testing
env.FLAVOR = 'renderer';
const notDocApp = await createTestingApp();
// @ts-expect-error testing
env.FLAVOR = 'doc';
const docApp = await createTestingApp();
t.context.models = app.get(Models);
t.context.docReader = app.get(DocReader);
t.context.databaseDocReader = app.get(DatabaseDocReader);
t.context.adapter = app.get(PgWorkspaceDocStorageAdapter);
t.context.config = app.get(Config);
t.context.app = app;
t.context.models = notDocApp.get(Models);
t.context.docReader = notDocApp.get(DocReader);
t.context.databaseDocReader = docApp.get(DatabaseDocReader);
t.context.adapter = docApp.get(PgWorkspaceDocStorageAdapter);
t.context.config = notDocApp.get(ConfigFactory);
t.context.app = notDocApp;
t.context.docApp = docApp;
});
let user: User;
let workspace: Workspace;
test.beforeEach(async t => {
t.context.config.docService.endpoint = t.context.app.url();
t.context.config.override({
docService: {
endpoint: t.context.docApp.url(),
},
});
await t.context.app.initTestingDB();
user = await t.context.models.user.create({
email: 'test@affine.pro',
@@ -63,6 +67,7 @@ test.afterEach.always(() => {
test.after.always(async t => {
await t.context.app.close();
await t.context.docApp.close();
});
test('should return null when doc not found', async t => {
@@ -113,7 +118,11 @@ test('should throw error when doc service internal error', async t => {
test('should fallback to database doc reader when endpoint network error', async t => {
const { docReader } = t.context;
t.context.config.docService.endpoint = 'http://localhost:13010';
t.context.config.override({
docService: {
endpoint: 'http://localhost:13010',
},
});
const docId = randomUUID();
const timestamp = Date.now();
await t.context.models.doc.createUpdates([
@@ -223,7 +232,11 @@ test('should return doc diff', async t => {
test('should get doc diff fallback to database doc reader when endpoint network error', async t => {
const { docReader } = t.context;
t.context.config.docService.endpoint = 'http://localhost:13010';
t.context.config.override({
docService: {
endpoint: 'http://localhost:13010',
},
});
const docId = randomUUID();
const timestamp = Date.now();
let updates: Buffer[] = [];

View File

@@ -1,44 +1,25 @@
import {
defineRuntimeConfig,
defineStartupConfig,
ModuleConfig,
} from '../../base/config';
import { defineModuleConfig } from '../../base';
interface DocStartupConfigurations {
history: {
/**
* How long the buffer time of creating a new history snapshot when doc get updated.
*
* in {ms}
*/
interval: number;
};
}
interface DocRuntimeConfigurations {
/**
* Use `y-octo` to merge updates at the same time when merging using Yjs.
*
* This is an experimental feature, and aimed to check the correctness of JwstCodec.
*/
experimentalMergeWithYOcto: boolean;
}
declare module '../../base/config' {
interface AppConfig {
doc: ModuleConfig<DocStartupConfigurations, DocRuntimeConfigurations>;
declare global {
interface AppConfigSchema {
doc: {
history: {
interval: number;
};
experimental: {
yocto: boolean;
};
};
}
}
defineStartupConfig('doc', {
history: {
interval: 1000 * 60 * 10 /* 10 mins */,
},
});
defineRuntimeConfig('doc', {
experimentalMergeWithYOcto: {
defineModuleConfig('doc', {
'experimental.yocto': {
desc: 'Use `y-octo` to merge updates at the same time when merging using Yjs.',
default: false,
},
'history.interval': {
desc: 'The minimum time interval in milliseconds of creating a new history snapshot when doc get updated.',
default: 1000 * 60 * 10 /* 10 mins */,
},
});

View File

@@ -2,13 +2,8 @@ import { Injectable, Logger } from '@nestjs/common';
import { chunk } from 'lodash-es';
import * as Y from 'yjs';
import {
CallMetric,
Config,
mergeUpdatesInApplyWay as yotcoMergeUpdates,
metrics,
Runtime,
} from '../../base';
import { CallMetric, Config, metrics } from '../../base';
import { mergeUpdatesInApplyWay as yoctoMergeUpdates } from '../../native';
import { QuotaService } from '../quota';
import { DocStorageOptions as IDocStorageOptions } from './storage';
@@ -35,7 +30,6 @@ export class DocStorageOptions implements IDocStorageOptions {
constructor(
private readonly config: Config,
private readonly runtime: Runtime,
private readonly quota: QuotaService
) {}
@@ -43,19 +37,17 @@ export class DocStorageOptions implements IDocStorageOptions {
const doc = await this.recoverDoc(updates);
const yjsResult = Buffer.from(Y.encodeStateAsUpdate(doc));
const useYocto = await this.runtime.fetch('doc/experimentalMergeWithYOcto');
if (useYocto) {
if (this.config.doc.experimental.yocto) {
metrics.jwst.counter('codec_merge_counter').add(1);
let log = false;
let yoctoResult: Buffer | null = null;
try {
yoctoResult = yotcoMergeUpdates(updates.map(Buffer.from));
yoctoResult = yoctoMergeUpdates(updates.map(Buffer.from));
if (!compare(yjsResult, yoctoResult)) {
metrics.jwst.counter('codec_not_match').add(1);
this.logger.warn(`yocto codec result doesn't match yjs codec result`);
log = true;
if (this.config.node.dev) {
if (env.dev) {
this.logger.warn(`Expected:\n ${yjsResult.toString('hex')}`);
this.logger.warn(`Result:\n ${yoctoResult.toString('hex')}`);
}
@@ -66,14 +58,14 @@ export class DocStorageOptions implements IDocStorageOptions {
log = true;
}
if (log && this.config.node.dev) {
if (log && env.dev) {
this.logger.warn(
`Updates: ${updates.map(u => Buffer.from(u).toString('hex')).join('\n')}`
);
}
if (
this.config.affine.canary &&
env.namespaces.canary &&
yoctoResult &&
yoctoResult.length > 2 /* simple test for non-empty yjs binary */
) {

View File

@@ -402,11 +402,11 @@ export class RpcDocReader extends DatabaseDocReader {
export const DocReaderProvider: FactoryProvider = {
provide: DocReader,
useFactory: (config: Config, ref: ModuleRef) => {
if (config.flavor.doc) {
useFactory: (ref: ModuleRef) => {
if (env.flavors.doc) {
return ref.create(DatabaseDocReader);
}
return ref.create(RpcDocReader);
},
inject: [Config, ModuleRef],
inject: [ModuleRef],
};