chore: rename fundamentals to base (#9119)

This commit is contained in:
forehalo
2024-12-13 06:27:12 +00:00
parent 8c24f2b906
commit 4c23991047
185 changed files with 183 additions and 193 deletions
@@ -0,0 +1,39 @@
import { defineStartupConfig, ModuleConfig } from '../../base/config';
export interface ServerStartupConfigurations {
/**
* Base url of AFFiNE server, used for generating external urls.
* default to be `[AFFiNE.protocol]://[AFFiNE.host][:AFFiNE.port]/[AFFiNE.path]` if not specified
*/
externalUrl: string;
/**
* Whether the server is hosted on a ssl enabled domain
*/
https: boolean;
/**
* where the server get deployed(FQDN).
*/
host: string;
/**
* which port the server will listen on
*/
port: number;
/**
* subpath where the server get deployed if there is.
*/
path: string;
}
declare module '../../base/config' {
interface AppConfig {
server: ModuleConfig<ServerStartupConfigurations>;
}
}
defineStartupConfig('server', {
externalUrl: '',
https: false,
host: 'localhost',
port: 3010,
path: '',
});
@@ -0,0 +1,141 @@
import {
ArgumentsHost,
Catch,
Logger,
NotFoundException,
} from '@nestjs/common';
import { BaseExceptionFilter } from '@nestjs/core';
import { GqlContextType } from '@nestjs/graphql';
import { ThrottlerException } from '@nestjs/throttler';
import { BaseWsExceptionFilter } from '@nestjs/websockets';
import { Response } from 'express';
import { of } from 'rxjs';
import { Socket } from 'socket.io';
import {
InternalServerError,
NotFound,
TooManyRequest,
UserFriendlyError,
} from '../error';
import { metrics } from '../metrics';
export function mapAnyError(error: any): UserFriendlyError {
if (error instanceof UserFriendlyError) {
return error;
} else if (error instanceof ThrottlerException) {
return new TooManyRequest();
} else if (error instanceof NotFoundException) {
return new NotFound();
} else {
const e = new InternalServerError();
e.cause = error;
return e;
}
}
@Catch()
export class GlobalExceptionFilter extends BaseExceptionFilter {
logger = new Logger('GlobalExceptionFilter');
override catch(exception: Error, host: ArgumentsHost) {
const error = mapAnyError(exception);
// with useGlobalFilters, the context is always HTTP
if (host.getType<GqlContextType>() === 'graphql') {
// let Graphql LoggerPlugin handle it
// see '../graphql/logger-plugin.ts'
throw error;
} else {
error.log('HTTP');
metrics.controllers
.counter('error')
.add(1, { status: error.status, type: error.type, error: error.name });
const res = host.switchToHttp().getResponse<Response>();
const respondText = res.getHeader('content-type') === 'text/plain';
if (respondText) {
res
.setHeader('content-type', 'text/plain')
.status(error.status)
.send(error.toText());
} else {
res.status(error.status).send(error.toJSON());
}
return;
}
}
}
export class GlobalWsExceptionFilter extends BaseWsExceptionFilter {
// @ts-expect-error satisfies the override
override handleError(client: Socket, exception: any): void {
const error = mapAnyError(exception);
error.log('Websocket');
metrics.socketio
.counter('unhandled_error')
.add(1, { status: error.status });
client.emit('error', {
error: toWebsocketError(error),
});
}
}
/**
* Only exists for websocket error body backward compatibility
*
* relay on `code` field instead of `name`
*
* @TODO(@forehalo): remove
*/
function toWebsocketError(error: UserFriendlyError) {
// should be `error.toJSON()` after backward compatibility removed
return {
status: error.status,
code: error.name.toUpperCase(),
type: error.type.toUpperCase(),
name: error.name.toUpperCase(),
message: error.message,
data: error.data,
};
}
export const GatewayErrorWrapper = (event: string): MethodDecorator => {
// @ts-expect-error allow
return (
_target,
_key,
desc: TypedPropertyDescriptor<(...args: any[]) => any>
) => {
const originalMethod = desc.value;
if (!originalMethod) {
return desc;
}
desc.value = async function (...args: any[]) {
try {
return await originalMethod.apply(this, args);
} catch (error) {
const mappedError = mapAnyError(error);
mappedError.log('Websocket');
metrics.socketio
.counter('error')
.add(1, { event, status: mappedError.status });
return {
error: toWebsocketError(mappedError),
};
}
};
return desc;
};
};
export function mapSseError(originalError: any) {
const error = mapAnyError(originalError);
error.log('Sse');
metrics.sse.counter('error').add(1, { status: error.status });
return of({
type: 'error' as const,
data: error.toJSON(),
});
}
@@ -0,0 +1,3 @@
import './config';
export * from './exception';
export * from './optional-module';
@@ -0,0 +1,72 @@
import {
DynamicModule,
Module,
ModuleMetadata,
Provider,
Type,
} from '@nestjs/common';
import { omit } from 'lodash-es';
import type { AFFiNEConfig, ConfigPaths } from '../config';
export interface OptionalModuleMetadata extends ModuleMetadata {
/**
* Only install module if given config paths are defined in AFFiNE config.
*/
requires?: ConfigPaths[];
/**
* Only install module if the predication returns true.
*/
if?: (config: AFFiNEConfig) => boolean;
/**
* Defines which feature will be enabled if the module installed.
*/
contributesTo?: import('../../core/config').ServerFeature; // avoid circlar dependency
/**
* Defines which providers provided by other modules will be overridden if the module installed.
*/
overrides?: Provider[];
}
const additionalOptions = [
'contributesTo',
'requires',
'if',
'overrides',
] as const satisfies Array<keyof OptionalModuleMetadata>;
type OptionalDynamicModule = DynamicModule & OptionalModuleMetadata;
export function OptionalModule(metadata: OptionalModuleMetadata) {
return (target: Type) => {
additionalOptions.forEach(option => {
if (Object.hasOwn(metadata, option)) {
Reflect.defineMetadata(option, metadata[option], target);
}
});
if (metadata.overrides) {
metadata.providers = (metadata.providers ?? []).concat(
metadata.overrides
);
// eslint-disable-next-line @typescript-eslint/no-floating-promises
metadata.exports = (metadata.exports ?? []).concat(metadata.overrides);
}
const nestMetadata = omit(metadata, additionalOptions);
Module(nestMetadata)(target);
};
}
export function getOptionalModuleMetadata<
T extends keyof OptionalModuleMetadata,
>(target: Type | OptionalDynamicModule, key: T): OptionalModuleMetadata[T] {
if ('module' in target) {
return target[key];
} else {
return Reflect.getMetadata(key, target);
}
}