diff --git a/.docker/selfhost/schema.json b/.docker/selfhost/schema.json index 579dad67f0..b066dfbdec 100644 --- a/.docker/selfhost/schema.json +++ b/.docker/selfhost/schema.json @@ -507,8 +507,7 @@ "properties": { "name": { "type": "string", - "description": "A recognizable name for the server. Will be shown when connected with AFFiNE Desktop.\n@default \"AFFiNE Cloud\"", - "default": "AFFiNE Cloud" + "description": "A recognizable name for the server. Will be shown when connected with AFFiNE Desktop.\n@default undefined" }, "externalUrl": { "type": "string", @@ -532,7 +531,7 @@ }, "path": { "type": "string", - "description": "Subpath where the server get deployed if there is.\n@default \"\"\n@environment `AFFINE_SERVER_SUB_PATH`", + "description": "Subpath where the server get deployed if there is one.(e.g. /affine)\n@default \"\"\n@environment `AFFINE_SERVER_SUB_PATH`", "default": "" } } diff --git a/packages/backend/server/src/base/config/__tests__/config.spec.ts b/packages/backend/server/src/base/config/__tests__/config.spec.ts index c90dfe9cb5..cd13a1f8b7 100644 --- a/packages/backend/server/src/base/config/__tests__/config.spec.ts +++ b/packages/backend/server/src/base/config/__tests__/config.spec.ts @@ -185,3 +185,28 @@ test('should clone from original config without modifications', t => { t.not(newConfig.auth.allowSignup, config.auth.allowSignup); }); + +test('should override with undefined fields', async t => { + await using module = await createModule({ + imports: [ConfigModule], + }); + + const config = module.get(Config); + const configFactory = module.get(ConfigFactory); + + configFactory.override({ + copilot: { + providers: { + // @ts-expect-error undefined field + unknown: { + apiKey: '123', + }, + }, + }, + }); + + // @ts-expect-error undefined field + t.deepEqual(config.copilot.providers.unknown, { + apiKey: '123', + }); +}); diff --git a/packages/backend/server/src/base/config/factory.ts b/packages/backend/server/src/base/config/factory.ts index 2443f13bcb..952582241c 100644 --- a/packages/backend/server/src/base/config/factory.ts +++ b/packages/backend/server/src/base/config/factory.ts @@ -7,7 +7,7 @@ export const OVERRIDE_CONFIG_TOKEN = Symbol('OVERRIDE_CONFIG_TOKEN'); @Injectable() export class ConfigFactory { - #original: AppConfig; + readonly #original: AppConfig; readonly #config: AppConfig; get config() { return this.#config; @@ -18,8 +18,8 @@ export class ConfigFactory { @Optional() private readonly overrides: DeepPartial = {} ) { - this.#config = this.loadDefault(); - this.#original = structuredClone(this.#config); + this.#original = this.loadDefault(); + this.#config = structuredClone(this.#original); } clone() { @@ -28,8 +28,8 @@ export class ConfigFactory { } override(updates: DeepPartial) { + override(this.#original, updates); override(this.#config, updates); - this.#original = structuredClone(this.#config); } validate(updates: Array<{ module: string; key: string; value: any }>) { diff --git a/packages/backend/server/src/base/config/register.ts b/packages/backend/server/src/base/config/register.ts index 28a329804b..9dfa905394 100644 --- a/packages/backend/server/src/base/config/register.ts +++ b/packages/backend/server/src/base/config/register.ts @@ -57,6 +57,10 @@ function typeFromShape(shape: z.ZodType): ConfigType { return 'array'; case z.ZodObject: return 'object'; + case z.ZodOptional: + case z.ZodNullable: + // @ts-expect-error checked + return typeFromShape(shape.unwrap()); default: return 'any'; } @@ -251,6 +255,14 @@ export function override(config: AppConfig, update: DeepPartial) { return right; } + // EDGE CASE: + // the right value is primitive and we're still not finding the key in descriptors, + // which means the overrides has keys not defined + // that's where we should return + if (typeof right !== 'object') { + return left; + } + // go deeper return mergeWith(left, right, (left, right, key) => { return merge(left, right, path === '' ? key : `${path}.${key}`); diff --git a/packages/backend/server/src/core/config/config.ts b/packages/backend/server/src/core/config/config.ts index 2b901b68d0..b84515c3f2 100644 --- a/packages/backend/server/src/core/config/config.ts +++ b/packages/backend/server/src/core/config/config.ts @@ -23,7 +23,8 @@ declare global { defineModuleConfig('server', { name: { desc: 'A recognizable name for the server. Will be shown when connected with AFFiNE Desktop.', - default: '', + default: undefined, + shape: z.string().optional(), }, externalUrl: { desc: `Base url of AFFiNE server, used for generating external urls. diff --git a/packages/frontend/admin/src/config.json b/packages/frontend/admin/src/config.json index df6c0224ef..cae75fa051 100644 --- a/packages/frontend/admin/src/config.json +++ b/packages/frontend/admin/src/config.json @@ -174,7 +174,7 @@ }, "path": { "type": "String", - "desc": "Subpath where the server get deployed if there is.", + "desc": "Subpath where the server get deployed if there is one.(e.g. /affine)", "env": "AFFINE_SERVER_SUB_PATH" } },