Commit 2783f91f authored by nanahira's avatar nanahira

first

parent a6f0cda7
# compiled output
/dist
/node_modules
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# OS
.DS_Store
# Tests
/coverage
/.nyc_output
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
/data
/output
/config.yaml
.git*
Dockerfile
.dockerignore
/tests
webpack.config.js
dist/*
build/*
*.js
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
},
};
# compiled output
/dist
/node_modules
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# OS
.DS_Store
# Tests
/coverage
/.nyc_output
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
/data
/output
/config.yaml
stages:
- build
- combine
- deploy
variables:
GIT_DEPTH: "1"
CONTAINER_TEST_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
CONTAINER_TEST_ARM_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-arm
CONTAINER_TEST_X86_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-x86
CONTAINER_RELEASE_IMAGE: $CI_REGISTRY_IMAGE:latest
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
build-x86:
stage: build
tags:
- docker
script:
- TARGET_IMAGE=$CONTAINER_TEST_X86_IMAGE
- docker build --pull -t $TARGET_IMAGE .
- docker push $TARGET_IMAGE
build-arm:
stage: build
tags:
- docker-arm
script:
- TARGET_IMAGE=$CONTAINER_TEST_ARM_IMAGE
- docker build --pull -t $TARGET_IMAGE .
- docker push $TARGET_IMAGE
combine:
stage: combine
tags:
- docker
script:
- TARGET_IMAGE=$CONTAINER_TEST_IMAGE
- SOURCE_IMAGE_2=$CONTAINER_TEST_ARM_IMAGE
- SOURCE_IMAGE_1=$CONTAINER_TEST_X86_IMAGE
- docker pull $SOURCE_IMAGE_1
- docker pull $SOURCE_IMAGE_2
- docker manifest create $TARGET_IMAGE --amend $SOURCE_IMAGE_1 --amend
$SOURCE_IMAGE_2
- docker manifest push $TARGET_IMAGE
deploy_latest:
stage: deploy
tags:
- docker
script:
- TARGET_IMAGE=$CONTAINER_RELEASE_IMAGE
- SOURCE_IMAGE=$CONTAINER_TEST_IMAGE
- docker pull $SOURCE_IMAGE
- docker tag $SOURCE_IMAGE $TARGET_IMAGE
- docker push $TARGET_IMAGE
only:
- master
deploy_tag:
stage: deploy
tags:
- docker
script:
- TARGET_IMAGE=$CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
- SOURCE_IMAGE=$CONTAINER_TEST_IMAGE
- docker pull $SOURCE_IMAGE
- docker tag $SOURCE_IMAGE $TARGET_IMAGE
- docker push $TARGET_IMAGE
only:
- tags
stages:
- install
- build
- deploy
variables:
GIT_DEPTH: "1"
npm_ci:
stage: install
tags:
- linux
script:
- npm ci
artifacts:
paths:
- node_modules
.build_base:
stage: build
tags:
- linux
dependencies:
- npm_ci
build:
extends: .build_base
script: npm run build
artifacts:
paths:
- dist/
upload_to_minio:
stage: deploy
dependencies:
- build
tags:
- linux
script:
- aws s3 --endpoint=https://minio.momobako.com:9000 sync --delete dist/ s3://nanahira/path
only:
- master
stages:
- install
- build
- deploy
variables:
GIT_DEPTH: "1"
npm_ci:
stage: install
tags:
- linux
script:
- npm ci
artifacts:
paths:
- node_modules
.build_base:
stage: build
tags:
- linux
dependencies:
- npm_ci
build:
extends:
- .build_base
script:
- npm run build
artifacts:
paths:
- dist/
unit-test:
extends:
- .build_base
script:
- npm run test
deploy_npm:
stage: deploy
dependencies:
- build
tags:
- linux
script:
- apt update;apt -y install coreutils
- echo $NPMRC | base64 --decode > ~/.npmrc
- npm publish . || true
only:
- master
/install-npm.sh
.git*
/data
/output
/config.yaml
.idea
.dockerignore
Dockerfile
/src
/coverage
/tests
/dist/tests
{
"singleQuote": true,
"trailingComma": "all"
}
\ No newline at end of file
FROM node:lts-bullseye-slim as base
LABEL Author="Nanahira <nanahira@momobako.com>"
RUN apt update && apt -y install python3 build-essential && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /var/log/*
WORKDIR /usr/src/app
COPY ./package*.json ./
FROM base as builder
RUN npm ci && npm cache clean --force
COPY . ./
RUN npm run build
FROM base
ENV NODE_ENV production
RUN npm ci && npm cache clean --force
COPY --from=builder /usr/src/app/dist ./dist
CMD [ "npm", "start" ]
The MIT License (MIT)
Copyright (c) 2021 Nanahira
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
#!/bin/bash
npm install --save-dev \
@types/node \
typescript \
'@typescript-eslint/eslint-plugin@^4.28.2' \
'@typescript-eslint/parser@^4.28.2 '\
'eslint@^7.30.0' \
'eslint-config-prettier@^8.3.0' \
'eslint-plugin-prettier@^3.4.0' \
prettier \
jest \
@types/jest \
ts-jest \
rimraf
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "cordis-decorators",
"description": "Decorator implementation of [cordis](https://github.com/shigma/cordis) .",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"lint": "eslint --fix .",
"build": "rimraf dist && tsc",
"test": "jest --passWithNoTests",
"start": "node dist/index.js"
},
"repository": {
"type": "git",
"url": "https://code.mycard.moe/3rdeye/cordis-decorators.git"
},
"author": "Nanahira <nanahira@momobako.com>",
"license": "MIT",
"keywords": [],
"bugs": {
"url": "https://code.mycard.moe/3rdeye/cordis-decorators/issues"
},
"homepage": "https://code.mycard.moe/3rdeye/cordis-decorators",
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "tests",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
},
"devDependencies": {
"@types/jest": "^28.1.4",
"@types/lodash": "^4.14.182",
"@types/mustache": "^4.1.3",
"@types/node": "^18.0.3",
"@typescript-eslint/eslint-plugin": "^4.33.0",
"@typescript-eslint/parser": "^4.33.0",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^3.4.1",
"jest": "^28.1.2",
"prettier": "^2.7.1",
"rimraf": "^3.0.2",
"ts-jest": "^28.0.5",
"typescript": "^4.7.4"
},
"peerDependencies": {
"cordis": "^2.0.6",
"schemastery": "^3.4.3"
},
"dependencies": {
"lodash": "^4.17.21",
"mustache": "^4.2.0",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.5.6",
"schemastery-gen": "^3.1.14",
"typed-reflector": "^1.0.10"
}
}
import { defaultRegistrar } from '../default-registrar';
export const { Isolate, UsingService } = defaultRegistrar.scopeDecorators();
export const { UsePlugin } = defaultRegistrar.methodDecorators();
export * from './common';
export * from './plugin';
import { defaultRegistrar } from '../default-registrar';
import { Context } from 'cordis';
import { PluginRegistrar } from '../def/plugin';
const pluginDecorators = defaultRegistrar.pluginDecorators();
export const {
If,
For,
PluginName,
PluginSchema,
Reusable,
Provide,
InjectContext,
InjectConfig,
InjectParent,
Caller,
} = pluginDecorators;
export const Fork = <Ctx extends Context>(
fork: PluginRegistrar.PluginClass<Ctx>,
) => pluginDecorators.Fork(fork);
export function Inject(name?: string, addUsing?: boolean): PropertyDecorator;
export function Inject(addUsing?: boolean): PropertyDecorator;
export function Inject(...args: [(string | boolean)?, boolean?]) {
return pluginDecorators.Inject(...args);
}
export * from './interfaces';
export type Awaitable<T> = [T] extends [Promise<unknown>] ? T : T | Promise<T>;
export type TypedMethodDecorator<F extends (...args: any[]) => any> = <
T extends F,
>(
// eslint-disable-next-line @typescript-eslint/ban-types
target: Object,
propertyKey: string | symbol,
descriptor: TypedPropertyDescriptor<T>,
) => void;
export type FunctionParam<F extends (...args: any[]) => any> = F extends (
...args: infer R
) => any
? R
: never;
export type FunctionReturn<F extends (...args: any[]) => any> = F extends (
...args: any[]
) => infer R
? R
: never;
export type PickEventFunction<M, K extends keyof M = keyof M> = M[K] extends (
...args: any[]
) => any
? (...args: FunctionParam<M[K]>) => Awaitable<FunctionReturn<M[K]>>
: M[K];
export type ParamRenderer = <T>(v: T) => T;
export interface ControlTypeMap {
if: boolean;
for: Iterable<Record<string, any>>;
}
export type Condition<R, T = any, Ext extends any[] = []> = (
o: T,
...ext: Ext
) => R;
export interface ControlType<
T extends keyof ControlTypeMap = keyof ControlTypeMap,
> {
type: T;
condition: Condition<ControlTypeMap[T], any, [Record<string, any>]>;
}
import { Context, Plugin } from 'cordis';
import Schema from 'schemastery';
import { ClassType } from 'schemastery-gen';
import { Registrar } from '../registrar';
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace PluginRegistrar {
export type PluginClass<Ctx extends Context, C = any, P = any> = new (
ctx: Ctx,
config: C,
) => P;
export type SystemInjectFun<Ctx extends Context> = <T = any>(
obj: PluginMeta<Ctx, T>,
cl: PluginClass<Ctx>,
) => any;
export interface PluginRegistrationOptions<Ctx extends Context, T = any> {
name?: string;
schema?: Schema<T> | ClassType<T>;
Config?: Schema<T> | ClassType<T>;
using?: Registrar.ServiceName<Ctx>[];
reusable?: boolean;
}
export interface PluginMeta<Ctx extends Context, T = any> {
__ctx: Ctx;
__config: T;
__pluginOptions: PluginRegistrationOptions<Ctx, T>;
__promisesToWaitFor: Promise<void>[];
__disposables: (() => void)[];
}
export type PluginOptions<T extends Plugin> = boolean | Plugin.Config<T>;
export interface PluginDefinitionExact<
Ctx extends Context,
T extends Plugin<Ctx>,
> {
plugin: T;
options?: boolean | PluginOptions<T>;
}
export interface PluginDefinitionName {
plugin: string;
options?: any;
}
export type PluginDefinition<Ctx extends Context, T extends Plugin = any> =
| PluginDefinitionExact<Ctx, T>
| PluginDefinitionName;
}
export function PluginDef<Ctx extends Context>(
name: string,
options?: any,
): PluginRegistrar.PluginDefinitionName;
export function PluginDef<Ctx extends Context, T extends Plugin>(
plugin: T,
options?: PluginRegistrar.PluginOptions<T>,
): PluginRegistrar.PluginDefinitionExact<Ctx, T>;
export function PluginDef<Ctx extends Context, T extends Plugin>(
plugin: T,
options?: PluginRegistrar.PluginOptions<T>,
): PluginRegistrar.PluginDefinition<Ctx, T> {
return { plugin, options };
}
export interface LifecycleEvents {
onApply?(): void;
onConnect?(): void | Promise<void>;
onDisconnect?(): void | Promise<void>;
onFork?(instance: any): void | Promise<void>;
onForkDisconnect?(instance: any): void | Promise<void>;
}
import { Registrar } from './registrar';
import { Context } from 'cordis';
export const defaultRegistrar = new Registrar(Context);
import { Context } from 'cordis';
import { Registrar } from './registrar';
import { generateRenderer, renderObject } from './utility/render-object';
import { extractObjectMethod } from './utility/utility';
import { ControlType } from './def';
import { from, Observable, ObservableInput, ObservedValueOf, of } from 'rxjs';
import _ from 'lodash';
type RecursiveUnwrapObservable<T> = T extends ObservableInput<any>
? RecursiveUnwrapObservable<ObservedValueOf<T>>
: T;
export class RegistrarAspect<Ctx extends Context, T = any> {
constructor(
private registrar: Registrar<Ctx>,
private obj: T,
private view: Record<any, any> = {},
) {}
getAllFieldsToRegister() {
const arr = this.registrar.reflector
.getArray('CordisRegisterKeys', this.obj)
.filter((k) => typeof k === 'string');
return arr as (keyof T & string)[];
}
getScopeContext(
ctx: Ctx,
key?: keyof T & string,
extraView: Record<any, any> = {},
autoScope = true,
) {
if (key && autoScope) {
ctx = this.getScopeContext(ctx);
}
const contextFilters = this.registrar.reflector.getArray(
'CordisContextTransformer',
this.obj,
key,
);
const r = generateRenderer({ ...this.view, ...extraView });
return this.registrar.transformContext(ctx, contextFilters, r);
}
registerMethod(
ctx: Ctx,
key: keyof T & string,
extraView: Record<any, any> = {},
): Registrar.RegisterResult<Ctx, T> {
const data = this.registrar.reflector.get('CordisRegister', this.obj, key);
if (!data) return;
const result = data.action(
this.getScopeContext(ctx, key, extraView, false),
extractObjectMethod(this.obj, key),
...renderObject(data.args, { ...this.view, ...extraView }),
);
return { ...data, key: key, result };
}
private registerWithLoopControl(
ctx: Ctx,
key: keyof T & string,
stack: ControlType[],
existing: Record<string, any> = {},
): Registrar.RegisterResult<Ctx, T>[] {
if (!stack.length) {
const result = this.registerMethod(ctx, key, existing);
return result ? [result] : [];
}
const rest = [...stack];
const control = rest.pop();
switch (control.type) {
case 'if':
if (!(control as ControlType<'if'>).condition(this, existing))
return [];
return this.registerWithLoopControl(ctx, key, rest, existing);
case 'for':
return Array.from(
(control as ControlType<'for'>).condition(this, existing),
).flatMap((item) =>
this.registerWithLoopControl(ctx, key, rest, {
...existing,
...item,
}),
);
}
}
private runLayersWith<R extends ObservableInput<any>>(
ctx: Ctx,
cb: Registrar.ContextFunction<Ctx, R>,
layers: Registrar.ContextCallbackLayer<Ctx>[],
): Observable<RecursiveUnwrapObservable<R>> {
const rest = [...layers];
const layer = rest.pop();
if (!layer) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return of(cb(ctx));
}
return new Observable((subscriber) => {
layer(ctx, async (nextCtx) => {
if (!rest.length) {
const result = cb(nextCtx);
if (result) {
from(result).subscribe({
next: (v) => subscriber.next(v),
// no error
// no complete
});
}
} else {
this.runLayersWith(nextCtx, cb, rest).subscribe(subscriber);
}
});
});
}
private runLayers<R extends ObservableInput<any>>(
ctx: Ctx,
cb: Registrar.ContextFunction<Ctx, R>,
key?: keyof T,
) {
const layers = this.registrar.reflector.getArray(
'CordisContextLayers',
this.obj,
key as string,
);
return this.runLayersWith(ctx, cb, layers);
}
registerFor(ctx: Ctx, key: keyof T & string) {
const stack = this.registrar.reflector.getArray(
'CordisControl',
this.obj,
key,
);
return this.runLayers(
ctx,
(innerCtx) => this.registerWithLoopControl(innerCtx, key, stack),
key,
);
}
register(ctx: Ctx) {
const keys = this.getAllFieldsToRegister();
return this.runLayers(ctx, (innerCtx) =>
keys.map((key) => this.registerFor(innerCtx, key)),
);
}
performTopActions(
ctx: Ctx,
autoScope = false,
extraView: Record<any, any> = {},
) {
if (autoScope) {
ctx = this.getScopeContext(ctx);
}
const actions = _.uniq(
this.registrar.reflector.getArray('CordisTopLevelAction', this.obj),
);
const renderer = generateRenderer({ ...this.view, ...extraView });
actions.forEach((action) => action(ctx, this.obj, renderer));
}
}
import 'reflect-metadata';
import { Context, Events, Fork } from 'cordis';
import { ClassType, SchemaClass } from 'schemastery-gen';
import { MetadataSetter, Reflector } from 'typed-reflector';
import {
Awaitable,
Condition,
ControlType,
FunctionParam,
FunctionReturn,
ParamRenderer,
PickEventFunction,
TypedMethodDecorator,
} from './def';
import { RegistrarAspect } from './registrar-aspect';
import Schema from 'schemastery';
import { PluginRegistrar } from './def/plugin';
import _ from 'lodash';
declare module 'cordis' {
interface Context {
__parent?: any;
}
}
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace Registrar {
export interface Methods<Ctx extends Context> {
on(event: keyof Events<Ctx>, prepend?: boolean): () => boolean;
plugin(): Awaitable<Fork<Ctx>>;
}
export interface MethodLimitations<Ctx extends Context> {
on: PickEventFunction<Events<Ctx>>;
plugin(
...args: any[]
): Awaitable<PluginRegistrar.PluginDefinition<Ctx> | undefined>;
}
export type MethodType<Ctx extends Context> = keyof Methods<Ctx>;
export type MethodBody<
C extends Context,
K extends keyof Methods<C> = keyof Methods<C>,
> = Methods<C>[K];
export type MethodParams<
C extends Context,
K extends keyof Methods<C> = keyof Methods<C>,
> = FunctionParam<MethodBody<C, K>>;
export type MethodReturn<
C extends Context,
K extends keyof Methods<C> = keyof Methods<C>,
> = FunctionReturn<MethodBody<C, K>>;
export type MethodClassMethodFunction<
C extends Context,
K extends keyof Methods<C> = keyof Methods<C>,
> = K extends keyof MethodLimitations<C>
? MethodLimitations<C>[K]
: (...args: any[]) => any;
export type MethodResolver<
C extends Context,
K extends keyof Methods<C> = keyof Methods<C>,
> = (
ctx: C,
fun: MethodClassMethodFunction<C, K>,
...args: MethodParams<C, K>
) => MethodReturn<C, K>;
export interface RegisterInfo<C extends Context> {
type: MethodType<C>;
args: MethodParams<C>;
action: MethodResolver<C>;
}
export interface RegisterResult<
C extends Context,
T,
K extends keyof Methods<C> = keyof Methods<C>,
> extends RegisterInfo<C> {
key: keyof T & string;
result: MethodReturn<C, K>;
}
export interface ProvideOptions extends Context.ServiceOptions {
immediate?: boolean;
}
export interface ProvideDefinition<C extends Context> extends ProvideOptions {
serviceName: ServiceName<C>;
}
export type ContextFunction<C extends Context, T> = (ctx: C) => T;
export type RenderedContextTransformer<C extends Context> = (
ctx: C,
r: ParamRenderer,
) => C;
export type ContextCallbackLayer<C extends Context, T = any> = (
ctx: C,
cb: ContextFunction<C, void>,
) => T;
export type ServiceName<C extends Context> = string;
export type TopLevelActionDef<C extends Context> = (
ctx: C,
obj: any,
renderer: ParamRenderer,
) => void;
export interface MetadataArrayMap<C extends Context> {
CordisRegisterKeys: string;
CordisContextTransformer: RenderedContextTransformer<C>;
CordisTopLevelAction: TopLevelActionDef<C>;
CordisContextLayers: ContextCallbackLayer<C>;
CordisControl: ControlType;
CordisPluginUsing: ServiceName<C>;
CordisPluginProvide: ProvideDefinition<C>;
CordisPluginInjectKeys: string;
CordisPluginSystemKeys: string;
}
export interface MetadataMap<C extends Context> {
CordisRegister: RegisterInfo<C>;
CordisPluginInject: ServiceName<C>;
CordisPluginSystem: PluginRegistrar.SystemInjectFun<C>;
CordisPluginPredefineSchema: Schema | ClassType<any>;
CordisPluginPredefineName: string;
CordisPluginFork: PluginRegistrar.PluginClass<C>;
CordisPluginReusable: boolean;
}
}
const ThirdEyeSym = Symbol('ThirdEyeSym');
export class Registrar<Ctx extends Context> {
metadata = new MetadataSetter<
Registrar.MetadataMap<Ctx>,
Registrar.MetadataArrayMap<Ctx>
>();
reflector = new Reflector<
Registrar.MetadataMap<Ctx>,
Registrar.MetadataArrayMap<Ctx>
>();
constructor(public contextClass: ClassType<Ctx>) {}
aspect<T>(obj: T, view: Record<any, any> = {}): RegistrarAspect<Ctx, T> {
return new RegistrarAspect(this, obj, view);
}
decorateMethod<K extends keyof Registrar.Methods<Ctx>>(
type: K,
action: Registrar.MethodResolver<Ctx, K>,
) {
return (...args: Registrar.MethodParams<Ctx, K>) =>
this.metadata.set(
'CordisRegister',
{
type,
args,
action,
},
'CordisRegisterKeys',
) as TypedMethodDecorator<Registrar.MethodClassMethodFunction<Ctx, K>>;
}
decorateTransformer(
transformer: Registrar.RenderedContextTransformer<Ctx>,
): ClassDecorator & MethodDecorator {
return this.metadata.append('CordisContextTransformer', transformer);
}
decorateTopLevelAction(action: Registrar.TopLevelActionDef<Ctx>) {
return this.metadata.append('CordisTopLevelAction', action);
}
decorateContextLayer(layer: Registrar.ContextCallbackLayer<Ctx>) {
return this.metadata.append('CordisContextLayers', layer);
}
private getFork(obj: any) {
const fork = this.reflector.get('CordisPluginFork', obj);
if (!fork) {
return;
}
return this.plugin()(fork);
}
afterPluginMethodRegistration(
result: Registrar.RegisterResult<Ctx, any, keyof Registrar.Methods<Ctx>>,
) {
// for override
}
plugin<T>(
options?: PluginRegistrar.PluginRegistrationOptions<Ctx, T>,
): <C extends PluginRegistrar.PluginClass<Ctx, T>>(
plugin: C,
) => C & PluginRegistrar.PluginRegistrationOptions<Ctx, T>;
plugin<T>(options: PluginRegistrar.PluginRegistrationOptions<Ctx, T> = {}) {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const _this = this;
const reflector = this.reflector;
return <
C extends {
new (...args: any[]): any;
},
>(
originalClass: C,
) => {
if (options.name) {
_this.pluginDecorators().PluginName(options.name)(originalClass);
}
if (options.schema) {
_this.pluginDecorators().PluginSchema(options.schema)(originalClass);
}
if (options.using) {
_this.scopeDecorators().UsingService(...options.using)(originalClass);
}
if (originalClass[ThirdEyeSym]) {
return originalClass;
}
const newClass = class
extends originalClass
implements PluginRegistrar.PluginMeta<Ctx>
{
static get Config() {
const schemaType = reflector.get(
'CordisPluginPredefineSchema',
newClass,
);
return schemaType ? SchemaClass(schemaType) : undefined;
}
static get using() {
let list = reflector.getArray('CordisPluginUsing', newClass);
const fork = _this.getFork(newClass);
if (fork) {
list = [...list, ...fork.using];
}
return _.uniq(list);
}
static get reusable() {
return reflector.get('CordisPluginReusable', newClass);
}
__ctx: Ctx;
__config: T;
__pluginOptions: PluginRegistrar.PluginRegistrationOptions<Ctx, T>;
__registrar: RegistrarAspect<Ctx>;
__promisesToWaitFor: Promise<any>[];
__disposables: (() => void)[];
_handleSystemInjections() {
const injectKeys = reflector.getArray('CordisPluginSystemKeys', this);
for (const key of injectKeys) {
const valueFunction = reflector.get(
'CordisPluginSystem',
this,
key,
);
if (!valueFunction) {
continue;
}
Object.defineProperty(this, key, {
configurable: true,
enumerable: true,
get: () => valueFunction(this, newClass),
});
}
}
_handleServiceInjections() {
const injectKeys = reflector.getArray('CordisPluginInjectKeys', this);
for (const key of injectKeys) {
const name = reflector.get('CordisPluginInject', this, key);
if (!name) {
continue;
}
Object.defineProperty(this, key, {
enumerable: true,
configurable: true,
get: () => {
return this.__ctx[name];
},
set: (val: any) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.__ctx[name] = val;
},
});
}
}
_registerDeclarations() {
this.__registrar.register(this.__ctx).subscribe({
next: (v) => {
if (!v) {
return;
}
const mayBePromise = v.result;
if (mayBePromise instanceof Promise) {
this.__promisesToWaitFor.push(mayBePromise as Promise<any>);
}
_this.afterPluginMethodRegistration(v);
},
});
}
_getProvidingServices() {
return reflector.getArray('CordisPluginProvide', this);
}
_handleServiceProvide(immediate: boolean) {
const providingServices = this._getProvidingServices().filter(
(serviceDef) => !serviceDef.immediate === !immediate,
);
for (const key of providingServices) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.__ctx[key.serviceName] = this as any;
}
}
_initializeFork() {
const fork = _this.getFork(this);
if (!fork) {
return;
}
this.__ctx.on('fork', (ctx, options) => {
ctx.__parent = this;
const instance = new fork(ctx as Ctx, options);
ctx.on('dispose', () => {
if (typeof this.onForkDisconnect === 'function') {
this.onForkDisconnect(instance);
}
delete ctx.__parent;
});
if (typeof this.onFork === 'function') {
this.onFork(instance);
}
});
}
_uninstallServiceProvide() {
const providingServices = this._getProvidingServices();
for (const key of providingServices) {
if (this.__ctx[key.serviceName] === (this as never)) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.__ctx[key.serviceName] = null;
}
}
}
_registerAfterInit() {
this.__ctx.on('ready', async () => {
if (this.__promisesToWaitFor.length) {
await Promise.all(this.__promisesToWaitFor);
this.__promisesToWaitFor = [];
}
if (typeof this.onConnect === 'function') {
await this.onConnect();
}
this._handleServiceProvide(false);
});
this.__ctx.on('dispose', async () => {
this._uninstallServiceProvide();
if (typeof this.onDisconnect === 'function') {
await this.onDisconnect();
}
this.__disposables.forEach((dispose) => dispose());
delete this.__ctx;
delete this.__config;
delete this.__pluginOptions;
delete this.__registrar;
delete this.__promisesToWaitFor;
delete this.__disposables;
});
}
_initializePluginClass() {
this._handleSystemInjections();
this._handleServiceInjections();
this.__registrar.performTopActions(this.__ctx);
this._registerDeclarations();
if (typeof this.onApply === 'function') {
this.onApply();
}
this._handleServiceProvide(true);
this._initializeFork();
this._registerAfterInit();
}
constructor(...args: any[]) {
const originalCtx: Ctx = args[0];
const config = args[1];
const ctx = _this
.aspect(newClass, config)
.getScopeContext(originalCtx);
super(ctx, config, ...args.slice(2));
this.__ctx = ctx;
this.__config = config;
this.__pluginOptions = options;
this.__registrar = _this.aspect(this, config);
this.__promisesToWaitFor = [];
this.__disposables = [];
this._initializePluginClass();
}
};
};
}
methodDecorators() {
return {
UseEvent: this.decorateMethod(
'on',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
(ctx, fun, event, prepend) => ctx.on(event, fun, prepend),
),
UsePlugin: this.decorateMethod('plugin', (ctx, fun) => {
const result = fun();
const register = (def: PluginRegistrar.PluginDefinition<Ctx, any>) => {
if (!def) {
return;
}
return ctx.plugin(def.plugin, def.options);
};
if (result instanceof Promise) {
return result.then(register);
} else {
return register(result);
}
}),
};
}
scopeDecorators() {
return {
Isolate: (...services: Registrar.ServiceName<Ctx>[]) =>
this.decorateTransformer((ctx, r) => ctx.isolate(r(services))),
UsingService: (
...services: Registrar.ServiceName<Ctx>[]
): ClassDecorator & MethodDecorator => {
return (obj, key?) => {
if (!key) {
services.forEach((service) =>
this.metadata.appendUnique('CordisPluginUsing', service)(obj),
);
} else {
const dec = this.decorateContextLayer((ctx, cb) =>
ctx.using(services, cb),
);
dec(obj, key);
}
};
},
};
}
pluginDecorators() {
const InjectSystem = (fun: PluginRegistrar.SystemInjectFun<Ctx>) =>
this.metadata.set('CordisPluginSystem', fun, 'CordisPluginSystemKeys');
return {
PluginName: (name: string) =>
this.metadata.set('CordisPluginPredefineName', name),
PluginSchema: (schema: Schema | ClassType<any>) =>
this.metadata.set('CordisPluginPredefineSchema', schema),
Reusable: (reusable = true) =>
this.metadata.set('CordisPluginReusable', reusable),
Fork: (fork: PluginRegistrar.PluginClass<Ctx>) =>
this.metadata.set('CordisPluginFork', fork),
If: <T>(
func: Condition<boolean, T, [Record<string, any>]>,
): MethodDecorator =>
this.metadata.append('CordisControl', {
type: 'if',
condition: func,
}),
For: <T>(
func: Condition<
Iterable<Record<string, any>>,
T,
[Record<string, any>]
>,
): MethodDecorator =>
this.metadata.append('CordisControl', {
type: 'for',
condition: func,
}),
Provide: (
name: Registrar.ServiceName<Ctx>,
options?: Registrar.ProvideOptions,
): ClassDecorator => {
Context.service(name, options);
return this.metadata.append('CordisPluginProvide', {
...options,
serviceName: name,
});
},
Inject: (...args: [(string | boolean)?, boolean?]): PropertyDecorator => {
let name: string;
let addUsing = false;
if (args.length === 1) {
if (typeof args[0] === 'boolean') {
addUsing = args[0];
} else {
name = args[0];
}
} else if (args.length >= 2) {
name = args[0] as string;
addUsing = args[1];
}
return (obj, key) => {
const serviceName = name || (key as string);
if (addUsing) {
this.metadata.appendUnique(
'CordisPluginUsing',
serviceName,
)(obj.constructor);
}
const dec = this.metadata.set(
'CordisPluginInject',
serviceName,
'CordisPluginInjectKeys',
);
return dec(obj, key);
};
},
InjectSystem,
InjectContext: () => InjectSystem((obj) => obj.__ctx),
InjectConfig: () => InjectSystem((obj) => obj.__config),
InjectParent: () => InjectSystem((obj) => obj.__ctx.__parent),
Caller: () =>
InjectSystem((obj) => {
const targetCtx: Context = obj[Context.current] || obj.__ctx;
return targetCtx;
}),
DefinePlugin: <T>(
options?: PluginRegistrar.PluginRegistrationOptions<Ctx, T>,
) => this.plugin(options),
};
}
transformContext(
ctx: Ctx,
filters: Registrar.RenderedContextTransformer<Ctx>[],
r: ParamRenderer = (v) => v,
) {
let targetCtx = ctx;
for (const fun of filters) {
targetCtx = fun(targetCtx, r) || targetCtx;
}
return targetCtx;
}
}
import Mustache from 'mustache';
import _ from 'lodash';
import { ParamRenderer } from '../def';
export function renderObject<T = any>(
object: T,
view: any,
visited?: Set<any>,
): T;
export function renderObject(object: any, view: any, visited: Set<any>): any {
if (!view || !object) {
return object;
}
visited ??= new Set();
if (typeof object === 'string') {
return Mustache.render(object, view, undefined, { escape: (v) => v });
}
if (visited.has(object)) {
return object;
}
if (Array.isArray(object)) {
visited.add(object);
return (object as any[]).map((item) => renderObject(item, view, visited));
}
if (typeof object === 'object') {
visited.add(object);
return _.mapValues(object, (value) => renderObject(value, view, visited));
}
return object;
}
export function generateRenderer(view: any): ParamRenderer {
return (v) => renderObject(v, view);
}
type AnyFunction = (...args: any[]) => any;
export function extractObjectMethod<T, K extends keyof T>(
o: T,
key: K,
): T[K] extends AnyFunction ? T[K] : AnyFunction {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return (...args: any[]) => o[key](...args);
}
describe('Sample test.', () => {
it('should pass', () => {
expect(true).toBe(true);
});
});
{
"compilerOptions": {
"outDir": "dist",
"module": "commonjs",
"target": "es2021",
"esModuleInterop": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"declaration": true,
"sourceMap": true
},
"compileOnSave": true,
"allowJs": true,
"include": [
"*.ts",
"src/**/*.ts",
"test/**/*.ts",
"tests/**/*.ts"
]
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment