From 2c8c03153e0db3018acca44f5ae9f8f94841b09a Mon Sep 17 00:00:00 2001 From: undefined Date: Tue, 4 Mar 2025 03:04:01 +0800 Subject: [PATCH] framework: use @cordisjs/core instead --- framework/framework/decorators.ts | 5 +- framework/framework/interface.ts | 19 ++--- framework/framework/package.json | 2 +- framework/framework/serializer.ts | 3 +- framework/framework/server.ts | 103 +++++++++++------------- packages/hydrooj/package.json | 4 +- packages/hydrooj/src/context.ts | 105 ++----------------------- packages/hydrooj/src/model/setting.ts | 4 +- packages/hydrooj/src/service/bus.ts | 8 +- packages/hydrooj/src/service/server.ts | 12 ++- 10 files changed, 86 insertions(+), 179 deletions(-) diff --git a/framework/framework/decorators.ts b/framework/framework/decorators.ts index 16104db2f..fdd82a9e6 100644 --- a/framework/framework/decorators.ts +++ b/framework/framework/decorators.ts @@ -1,3 +1,4 @@ +import { Context } from '@cordisjs/core'; import { ValidationError } from './error'; import type { Handler } from './server'; import { Converter, Type, Validator } from './validator'; @@ -35,7 +36,7 @@ function _buildParam(name: string, source: 'get' | 'post' | 'all' | 'route', ... } function _descriptor(v: ParamOption) { - return function desc(this: Handler, target: any, funcName: string, obj: any) { + return function desc(this: Handler, target: any, funcName: string, obj: any) { target.__param ||= {}; target.__param[target.constructor.name] ||= {}; if (!target.__param[target.constructor.name][funcName]) { @@ -44,7 +45,7 @@ function _descriptor(v: ParamOption) { const firstArg = val.split(')')[0]?.split(',')[0]?.split('(')[1]?.trim() || ''; const domainIdStyle = firstArg.toLowerCase().startsWith('domainid'); target.__param[target.constructor.name][funcName] = []; - obj.value = function validate(this: Handler, rawArgs: any, ...extra: any[]) { + obj.value = function validate(this: Handler, rawArgs: any, ...extra: any[]) { if (typeof rawArgs !== 'object' || extra.length) return originalMethod.call(this, rawArgs, ...extra); const c = []; const arglist: ParamOption[] = target.__param[target.constructor.name][funcName]; diff --git a/framework/framework/interface.ts b/framework/framework/interface.ts index 3291c9d67..d054d84a9 100644 --- a/framework/framework/interface.ts +++ b/framework/framework/interface.ts @@ -1,16 +1,17 @@ import type { ConnectionHandler, Handler } from './server'; +import type { Context as CordisContext } from '@cordisjs/core'; -type HandlerEvents = - Record<`handler/${HookType}/${string}`, (thisArg: T & Record) => VoidReturn> - & Record<`handler/${HookType}`, (thisArg: T) => VoidReturn> - & Record<`handler/register/${string}`, (HandlerClass: new (...args: any[]) => T) => VoidReturn> - & Record<`connection/${'create' | 'active' | 'close'}`, (thisArg: C) => VoidReturn>; +type HandlerEvents = + Record<`handler/${HookType}/${string}`, (thisArg: Handler & Record) => VoidReturn> + & Record<`handler/${HookType}`, (thisArg: Handler) => VoidReturn> + & Record<`handler/register/${string}`, (HandlerClass: new (...args: any[]) => Handler) => VoidReturn> + & Record<`connection/${'create' | 'active' | 'close'}`, (thisArg: ConnectionHandler) => VoidReturn>; export type VoidReturn = Promise | any; export type HookType = 'before-prepare' | 'before' | 'before-operation' | 'after' | 'finish'; -export interface ServerEvents extends HandlerEvents { - 'handler/create': (thisArg: Handler, type: 'ws' | 'http') => VoidReturn - 'handler/init': (thisArg: Handler) => VoidReturn - 'handler/error': (thisArg: Handler, e: Error) => VoidReturn +export interface ServerEvents extends HandlerEvents { + 'handler/create': (thisArg: Handler, type: 'ws' | 'http') => VoidReturn + 'handler/init': (thisArg: Handler) => VoidReturn + 'handler/error': (thisArg: Handler, e: Error) => VoidReturn } diff --git a/framework/framework/package.json b/framework/framework/package.json index af4bf1d25..13353f0a6 100644 --- a/framework/framework/package.json +++ b/framework/framework/package.json @@ -4,7 +4,7 @@ "main": "index.ts", "license": "MIT", "peerDependencies": { - "cordis": "*" + "@cordisjs/core": "*" }, "dependencies": { "@hydrooj/utils": "workspace:^", diff --git a/framework/framework/serializer.ts b/framework/framework/serializer.ts index 9dd63e4a5..1510250ef 100644 --- a/framework/framework/serializer.ts +++ b/framework/framework/serializer.ts @@ -1,6 +1,7 @@ import type { HandlerCommon } from './server'; +import type { Context } from '@cordisjs/core'; -export default function serializer(ignoreSerializeFunction = false, h?: HandlerCommon) { +export default function serializer(ignoreSerializeFunction = false, h?: HandlerCommon) { return (k: string, v: any) => { if (k.startsWith('_') && k !== '_id') return undefined; if (typeof v === 'bigint') return `BigInt::${v.toString()}`; diff --git a/framework/framework/server.ts b/framework/framework/server.ts index ccacdef14..ee15b6f5f 100644 --- a/framework/framework/server.ts +++ b/framework/framework/server.ts @@ -2,7 +2,7 @@ import http from 'http'; import { tmpdir } from 'os'; import { join } from 'path'; import { PassThrough } from 'stream'; -import { Context, Service } from 'cordis'; +import { Context as CordisContext, Service } from '@cordisjs/core'; import type { Files } from 'formidable'; import fs from 'fs-extra'; import Koa from 'koa'; @@ -125,8 +125,8 @@ export interface UserModel { _id: number; } -export interface HandlerCommon { } -export class HandlerCommon { +export interface HandlerCommon { } +export class HandlerCommon { static [kHandler]: string | boolean = true; session: Record; args: Record; @@ -135,7 +135,7 @@ export class HandlerCommon { UiContext: Record; user: UserModel; - constructor(public context: KoaContext, public ctx: Context) { + constructor(public context: KoaContext, public ctx: C) { this.renderHTML = this.renderHTML.bind(this); this.url = this.url.bind(this); this.session = context.session; @@ -196,7 +196,7 @@ export class HandlerCommon { } } -export class Handler extends HandlerCommon { +export class Handler extends HandlerCommon { loginMethods: any; noCheckPermView = false; notUsage = false; @@ -242,33 +242,7 @@ export class Handler extends HandlerCommon { } } -const Checker = (permPrivChecker) => { - let perm: bigint; - let priv: number; - let checker = () => { }; - for (const item of permPrivChecker) { - if (typeof item === 'object') { - if (typeof item.call !== 'undefined') { - checker = item; - } else if (typeof item[0] === 'number') { - priv = item; - } else if (typeof item[0] === 'bigint') { - perm = item; - } - } else if (typeof item === 'number') { - priv = item; - } else if (typeof item === 'bigint') { - perm = item; - } - } - return function check(this: Handler) { - checker(); - if (perm) this.checkPerm(perm); - if (priv) this.checkPriv(priv); - }; -}; - -export class ConnectionHandler extends HandlerCommon { +export class ConnectionHandler extends HandlerCommon { conn: WebSocket; compression: Shorty; counter = 0; @@ -305,7 +279,7 @@ export class ConnectionHandler extends HandlerCommon { } } -class NotFoundHandler extends Handler { +class NotFoundHandler extends Handler { prepare() { throw new NotFoundError(this.request.path); } all() { } } @@ -344,7 +318,7 @@ export interface WebServiceConfig { xhost?: string; } -export class WebService extends Service { +export class WebService extends Service { private registry: Record = Object.create(null); private registrationCount = Counter(); private serverLayers = []; @@ -359,7 +333,7 @@ export class WebService extends Service { Handler = Handler; ConnectionHandler = ConnectionHandler; - constructor(ctx: Context, public config: WebServiceConfig) { + constructor(ctx: C, public config: WebServiceConfig) { super(ctx, 'server'); ctx.mixin('server', ['Route', 'Connection', 'withHandlerClass']); this.server.keys = this.config.keys; @@ -668,6 +642,33 @@ ${c.response.status} ${endTime - startTime}ms ${c.response.length}`); } this.registry[name] = HandlerClass; this.registrationCount[name]++; + + const Checker = (permPrivChecker) => { + let perm: bigint; + let priv: number; + let checker = () => { }; + for (const item of permPrivChecker) { + if (typeof item === 'object') { + if (typeof item.call !== 'undefined') { + checker = item; + } else if (typeof item[0] === 'number') { + priv = item; + } else if (typeof item[0] === 'bigint') { + perm = item; + } + } else if (typeof item === 'number') { + priv = item; + } else if (typeof item === 'bigint') { + perm = item; + } + } + return function check(this: Handler) { + checker(); + if (perm) this.checkPerm(perm); + if (priv) this.checkPriv(priv); + }; + }; + if (type === 'route') { router.all(routeName, path, (ctx) => this.handleHttp(ctx as any, HandlerClass, Checker(permPrivChecker))); } else { @@ -688,23 +689,20 @@ ${c.response.status} ${endTime - startTime}ms ${c.response.length}`); } public withHandlerClass( - name: T, callback: (HandlerClass: T extends `${string}ConnectionHandler` ? typeof ConnectionHandler : typeof Handler) => any, + name: T, callback: (HandlerClass: T extends `${string}ConnectionHandler` ? typeof ConnectionHandler : typeof Handler) => any, ) { if (this.registry[name]) callback(this.registry[name]); - // @ts-ignore - const dispose = this.ctx.on(`handler/register/${name}`, callback); - this[Context.current]?.on('dispose', () => { - dispose(); - }); + this.ctx.on(`handler/register/${name}`, callback); } + // FIXME: should be typeof Handler instead of any // eslint-disable-next-line @typescript-eslint/naming-convention - public Route(name: string, path: string, RouteHandler: typeof Handler, ...permPrivChecker) { + public Route(name: string, path: string, RouteHandler: any, ...permPrivChecker) { return this.register('route', name, path, RouteHandler, ...permPrivChecker); } // eslint-disable-next-line @typescript-eslint/naming-convention - public Connection(name: string, path: string, RouteHandler: typeof ConnectionHandler, ...permPrivChecker) { + public Connection(name: string, path: string, RouteHandler: any, ...permPrivChecker) { return this.register('conn', name, path, RouteHandler, ...permPrivChecker); } @@ -713,7 +711,7 @@ ${c.response.status} ${endTime - startTime}ms ${c.response.length}`); const dispose = () => { this[name] = this[name].filter((i) => i !== layer); }; - this[Context.origin]?.on('dispose', dispose); + this[CordisContext.origin]?.on('dispose', dispose); return dispose; } @@ -738,7 +736,7 @@ ${c.response.status} ${endTime - startTime}ms ${c.response.length}`); this.captureAllRoutes[prefix] = cb; } - public handlerMixin(MixinClass: Partial) { + public handlerMixin(MixinClass: Partial>) { for (const val of Object.getOwnPropertyNames(MixinClass)) { HandlerCommon.prototype[val] = MixinClass[val]; } @@ -755,16 +753,11 @@ ${c.response.status} ${endTime - startTime}ms ${c.response.length}`); } } -declare module 'cordis' { +declare module '@cordisjs/core' { interface Context { - server: WebService; - Route: WebService['Route']; - Connection: WebService['Connection']; - withHandlerClass: WebService['withHandlerClass']; + server: WebService; + Route: WebService['Route']; + Connection: WebService['Connection']; + withHandlerClass: WebService['withHandlerClass']; } } - -export async function apply(ctx: Context, config: WebServiceConfig) { - ctx.provide('server', undefined, true); - ctx.server = new WebService(ctx, config); -} diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index 13fd518e1..cc0d44242 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -16,6 +16,9 @@ "@aws-sdk/lib-storage": "3.726.1", "@aws-sdk/s3-presigned-post": "3.726.1", "@aws-sdk/s3-request-presigner": "3.726.1", + "@cordisjs/core": "4.0.0-alpha.0", + "@cordisjs/logger": "1.0.0-alpha.0", + "@cordisjs/plugin-timer": "1.0.0-alpha.0", "@graphql-tools/schema": "^10.0.18", "@hydrooj/common": "workspace:^", "@hydrooj/framework": "workspace:^", @@ -25,7 +28,6 @@ "@yarnpkg/libzip": "^3.1.0", "adm-zip": "0.5.14", "cac": "^6.7.14", - "cordis": "4.0.0-alpha.0", "csv-stringify": "^6.5.2", "emoji-regex": "^10.4.0", "emojis-list": "2.1.0", diff --git a/packages/hydrooj/src/context.ts b/packages/hydrooj/src/context.ts index 90b938438..f1f6e2610 100644 --- a/packages/hydrooj/src/context.ts +++ b/packages/hydrooj/src/context.ts @@ -1,4 +1,6 @@ -import * as cordis from 'cordis'; +import * as cordis from '@cordisjs/core'; +import { Logger } from '@cordisjs/logger'; +import { TimerService } from '@cordisjs/plugin-timer'; import Schema from 'schemastery'; import type { ServerEvents, WebService } from '@hydrooj/framework'; import type { DomainDoc, GeoIP, ModuleInterfaces } from './interface'; @@ -9,7 +11,7 @@ import type { CheckService } from './service/check'; import type { } from './service/migration'; import type { ConnectionHandler, Handler } from './service/server'; -export interface Events extends cordis.Events, EventMap, ServerEvents { } +export interface Events extends cordis.Events, EventMap { } function addScript(name: string, description: string, validate: Schema, run: (args: K, report: any) => boolean | Promise) { if (global.Hydro.script[name]) throw new Error(`duplicate script ${name} registered.`); @@ -25,11 +27,11 @@ function provideModule(type: T, id: string, mo export type EffectScope = cordis.EffectScope; -export { Disposable, Plugin, ScopeStatus } from 'cordis'; +export { Disposable, Plugin, ScopeStatus } from '@cordisjs/core'; -export interface Context extends cordis.Context, Pick { +export interface Context extends cordis.Context { // @ts-ignore - [Context.events]: Events; + [Context.events]: Events & ServerEvents; loader: Loader; check: CheckService; setImmediate: typeof setImmediate; @@ -38,12 +40,6 @@ export interface Context extends cordis.Context, Pick void, delay: number): () => void - setInterval(callback: () => void, delay: number): () => void - sleep(delay: number): Promise - throttle void>(callback: F, delay: number, noTrailing?: boolean): WithDispose - debounce void>(callback: F, delay: number): WithDispose } export abstract class Service extends cordis.Service { @@ -71,92 +67,6 @@ export class ApiMixin extends cordis.Service { } } -type WithDispose = T & { dispose: () => void }; - -class TimerService extends Service { - constructor(ctx) { - super(ctx, '$timer'); - ctx.mixin('$timer', ['setTimeout', 'setInterval', 'sleep', 'throttle', 'debounce']); - } - - setTimeout(callback: () => void, delay: number) { - const dispose = this.ctx.effect(() => { - const timer = setTimeout(() => { - dispose(); - callback(); - }, delay); - return () => clearTimeout(timer); - }); - return dispose; - } - - setInterval(callback: () => void, delay: number) { - return this.ctx.effect(() => { - const timer = setInterval(callback, delay); - return () => clearInterval(timer); - }); - } - - sleep(delay: number) { - const caller = this.ctx; - return new Promise((resolve, reject) => { - const dispose1 = this.setTimeout(() => { - dispose1(); - dispose2(); // eslint-disable-line @typescript-eslint/no-use-before-define - resolve(); - }, delay); - const dispose2 = caller.on('dispose', () => { - dispose1(); - dispose2(); - reject(new Error('Context has been disposed')); - }); - }); - } - - private createWrapper(callback: (args: any[], check: () => boolean) => any, isDisposed = false) { - this.ctx.scope.assertActive(); - - let timer: number | NodeJS.Timeout | undefined; - const dispose = () => { - isDisposed = true; - remove(); // eslint-disable-line @typescript-eslint/no-use-before-define - clearTimeout(timer); - }; - - const wrapper: any = (...args: any[]) => { - clearTimeout(timer); - timer = callback(args, () => !isDisposed && this.ctx.scope.active); - }; - wrapper.dispose = dispose; - const remove = this.ctx.scope.disposables.push(dispose); - return wrapper; - } - - throttle void>(callback: F, delay: number, noTrailing?: boolean): WithDispose { - let lastCall = -Infinity; - const execute = (...args: any[]) => { - lastCall = Date.now(); - callback(...args); - }; - return this.createWrapper((args, isActive) => { // eslint-disable-line consistent-return - const now = Date.now(); - const remaining = delay - (now - lastCall); - if (remaining <= 0) { - execute(...args); - } else if (isActive()) { - return setTimeout(execute, remaining, ...args); - } - }, noTrailing); - } - - debounce void>(callback: F, delay: number): WithDispose { - return this.createWrapper((args, isActive) => { - if (!isActive()) return; - return setTimeout(callback, delay, ...args); // eslint-disable-line consistent-return - }); - } -} - export class Context extends cordis.Context { domain?: DomainDoc; @@ -164,6 +74,7 @@ export class Context extends cordis.Context { super(); this.plugin(ApiMixin); this.plugin(TimerService); + this.plugin(Logger); } } diff --git a/packages/hydrooj/src/model/setting.ts b/packages/hydrooj/src/model/setting.ts index 6ab5eff54..a60ee380f 100644 --- a/packages/hydrooj/src/model/setting.ts +++ b/packages/hydrooj/src/model/setting.ts @@ -1,6 +1,6 @@ /* eslint-disable max-len */ /* eslint-disable no-await-in-loop */ -import * as cordis from 'cordis'; +import * as cordis from '@cordisjs/core'; import yaml from 'js-yaml'; import { Dictionary } from 'lodash'; import moment from 'moment-timezone'; @@ -295,7 +295,7 @@ SystemSetting( // eslint-disable-next-line import/no-mutable-exports export const langs: Record = {}; -declare module 'cordis' { +declare module '@cordisjs/core' { interface Context { setting: SettingService; } diff --git a/packages/hydrooj/src/service/bus.ts b/packages/hydrooj/src/service/bus.ts index 3bb3d6621..5cf0fc157 100644 --- a/packages/hydrooj/src/service/bus.ts +++ b/packages/hydrooj/src/service/bus.ts @@ -17,7 +17,7 @@ import type { DocType } from '../model/document'; export type Disposable = () => void; export type VoidReturn = Promise | any; type HookType = 'before-prepare' | 'before' | 'before-operation' | 'after' | 'finish'; -type MapHandlerEvents = Record<`handler/${HookType}/${N}`, (thisArg: H) => VoidReturn>; +type MapHandlerEvents> = Record<`handler/${HookType}/${N}`, (thisArg: H) => VoidReturn>; type KnownHandlerEvents = MapHandlerEvents<'UserRegister', UserRegisterHandler> & MapHandlerEvents<'ProblemSolution', ProblemSolutionHandler>; @@ -38,7 +38,7 @@ export interface EventMap extends KnownHandlerEvents { 'database/config': () => VoidReturn 'system/setting': (args: Record) => VoidReturn - 'bus/broadcast': (event: keyof EventMap | keyof ServerEvents, payload: any, trace?: string) => VoidReturn + 'bus/broadcast': (event: keyof EventMap | keyof ServerEvents, payload: any, trace?: string) => VoidReturn 'monitor/update': (type: 'server' | 'judge', $set: any) => VoidReturn 'monitor/collect': (info: any) => VoidReturn 'api/update': () => void; @@ -93,7 +93,7 @@ export interface EventMap extends KnownHandlerEvents { 'contest/balloon': (domainId: string, tid: ObjectId, bdoc: ContestBalloonDoc) => VoidReturn 'contest/del': (domainId: string, tid: ObjectId) => VoidReturn - 'oplog/log': (type: string, handler: Handler, args: any, data: any) => VoidReturn; + 'oplog/log': (type: string, handler: Handler, args: any, data: any) => VoidReturn; 'training/list': (query: Filter, handler: any) => VoidReturn 'training/get': (tdoc: TrainingDoc, handler: any) => VoidReturn @@ -103,7 +103,7 @@ export interface EventMap extends KnownHandlerEvents { } /* eslint-enable @typescript-eslint/naming-convention */ -declare module 'cordis' { +declare module '@cordisjs/core' { interface Events extends EventMap { } } diff --git a/packages/hydrooj/src/service/server.ts b/packages/hydrooj/src/service/server.ts index f29861714..c3b8e0466 100644 --- a/packages/hydrooj/src/service/server.ts +++ b/packages/hydrooj/src/service/server.ts @@ -28,7 +28,7 @@ const ignoredLimit = `,${argv.options.ignoredLimit},`; const logger = new Logger('server'); declare module '@hydrooj/framework' { - export interface HandlerCommon { + export interface HandlerCommon { domain: DomainDoc; paginate(cursor: FindCursor, page: number, key: string): Promise<[docs: T[], numPages: number, count: number]>; @@ -73,10 +73,9 @@ export function requireSudo(target: any, funcName: string, obj: any) { export interface Handler { domain: DomainDoc; - ctx: Context; } -export class Handler extends HandlerOriginal { - constructor(_, ctx: Context) { +export class Handler extends HandlerOriginal { + constructor(_, ctx) { super(_, ctx); this.ctx = ctx.extend({ domain: this.domain }); this.renderHTML = ((orig) => function (name: string, args: Record) { @@ -114,10 +113,9 @@ export class Handler extends HandlerOriginal { export interface ConnectionHandler { domain: DomainDoc; - ctx: Context; } -export class ConnectionHandler extends ConnectionHandlerOriginal { - constructor(_, ctx: Context) { +export class ConnectionHandler extends ConnectionHandlerOriginal { + constructor(_, ctx) { super(_, ctx); this.ctx = ctx.extend({ domain: this.domain }); }