Commit aeff1b32 authored by nanahira's avatar nanahira

first

parent 98511465
Pipeline #6125 failed with stages
in 1 minute and 7 seconds
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',
},
};
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
/build
/dist
/output
/config.yaml
.idea
\ No newline at end of file
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
/install-npm.sh
.git*
/output
/dest
/config.yaml
.idea
.dockerignore
Dockerfile
/src
.eslintrc.js
.prettierrc
webpack.config.js
tsconfig.json
/test
/dist/test
\ No newline at end of file
{
"singleQuote": true,
"trailingComma": "all"
}
\ No newline at end of file
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.
# Koishi Utils Schemagen
在 Koishi.js 中,使用类装饰器定义 Schema
\ No newline at end of file
在 Koishi.js 中,使用类装饰器定义 Schema。
## 安装
```
npm install koishi-utils-schemagen
```
## 使用
### 类 Schema 定义
```ts
// 使用装饰器定义 Schema。
export class Config {
// 基本的数据类型
@DefineSchema({ type: 'number', required: true })
foo: number;
@DefineSchema({ type: 'string', default: 'shigma' })
bar: string;
@DefineSchema({ type: 'boolean', default: true, hidden: true })
baz: boolean;
// 数组
@DefineSchema({ type: 'string', array: true, default: ['foo', 'bar'] })
ant: string[];
// 也可以用定义好的 Schema
@DefineSchema({ schema: Schema.string() })
dream: string;
// 上例的另一种快捷写法
@UseSchema(Schema.string())
sapnap: string;
// 对象,这里引用另一个具有 Schema 定义类。
@DefineSchema({ type: B })
bi: B;
// 上面的另一种写法
@ObjectSchema(B)
anotherB: B;
// Dict
@DefineSchema({ type: B, dict: true })
biDict: Record<string, B>;
// Dict 和字典组合会让字典在内而数组在外。此外 ObjectSchema 后面也可以指定属性。
@ObjectSchema(B, { dict: true, array: true })
biDictArr: Record<string, B>[];
}
```
### 使用
```ts
// 获取 Schema 定义
const schema = schemaFromClass(B);
// 直接获取 Config 对象并实例化,可以代替 Schema.validate 使用。对于嵌套类会进行循环实例化。
const config = schemaTransform(B, someObject);
```
\ No newline at end of file
#!/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
This diff is collapsed.
{
"name": "koishi-utils-schemagen",
"version": "1.0.0",
"description": "在 Koishi.js 中,使用类装饰器定义 Schema",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"scripts": {
"lint": "eslint --fix .",
"build": "tsc"
},
"repository": {
"type": "git",
"url": "https://code.mycard.moe/nanahira/koishi-utils-schema.git"
},
"bugs": {
"url": "https://code.mycard.moe/nanahira/koishi-utils-schema/issues"
},
"homepage": "https://code.mycard.moe/nanahira/koishi-utils-schema",
"keywords": [
"Koishi.js",
"Nest.js",
"nest",
"koishi",
"qqbot",
"cqhttp",
"cqbot",
"koishijs",
"utils-schema"
],
"author": "Nanahira",
"license": "MIT",
"peerDependencies": {
"koishi": "^4.0.0-alpha.9",
"reflect-metadata": "^0.1.13"
},
"devDependencies": {
"@types/lodash": "^4.14.175",
"@types/node": "^16.10.3",
"@typescript-eslint/eslint-plugin": "^4.33.0",
"@typescript-eslint/parser": "^4.33.0",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.4.1",
"koishi": "^4.0.0-alpha.9",
"prettier": "^2.4.1",
"proxy-agent": "^5.0.0",
"reflect-metadata": "^0.1.13",
"typescript": "^4.4.3"
},
"dependencies": {
"class-transformer": "^0.4.0",
"lodash": "^4.17.21"
}
}
export const SchemaMetaKey = '_koishi_schema_meta';
export const SchemaKeysMetaKey = '_koishi_schemakeys_meta';
import { SchemaOptions } from './def';
import 'reflect-metadata';
import { SchemaKeysMetaKey, SchemaMetaKey } from './constants';
import { Schema } from 'koishi';
import {
ClassConstructor,
Type,
Transform,
plainToClass,
} from 'class-transformer';
import _ from 'lodash';
function transformDict<T>(cl: ClassConstructor<T>, val: any, array: boolean) {
if (array) {
return (val as Record<string, any>[]).map((v) =>
_.mapValues(v, (_v) => plainToClass(cl, _v)),
);
} else {
return _.mapValues(val, (_v) => plainToClass(cl, _v));
}
}
export function DefineSchema(options: SchemaOptions): PropertyDecorator {
return (obj, key) => {
const objClass = obj.constructor;
const keys: string[] =
Reflect.getMetadata(SchemaKeysMetaKey, objClass) || [];
keys.push(key.toString());
Reflect.defineMetadata(SchemaKeysMetaKey, keys, objClass);
Reflect.defineMetadata(SchemaMetaKey, options, objClass, key);
if (options.type && typeof options.type !== 'string') {
const cl = options.type as ClassConstructor<any>;
if (options.dict) {
const transformDecorator = Transform(({ value }) =>
transformDict(cl, value, options.array),
);
transformDecorator(obj, key);
} else {
const typeDecorator = Type(() => cl);
typeDecorator(obj, key);
}
}
};
}
export function UseSchema(schema: Schema) {
return DefineSchema({ schema });
}
export function ObjectSchema(
cl: ClassConstructor<any>,
options: SchemaOptions = {},
) {
return DefineSchema({ type: cl, ...options });
}
import { Schema } from 'koishi';
import { ClassConstructor } from 'class-transformer';
export type SchemaType =
| 'string'
| 'number'
| 'boolean'
| 'any'
| 'never'
| ClassConstructor<any>;
export interface SchemaOptions {
schema?: Schema<any>;
desc?: string;
required?: boolean;
hidden?: boolean;
comment?: string;
default?: any;
dict?: boolean;
array?: boolean;
type?: SchemaType;
}
export type SchemaOptionsDict<T> = { [P in keyof T]?: SchemaOptions };
export * from './def';
export * from './decorators';
export * from './methods';
import { Schema } from 'koishi';
import {
ClassConstructor,
plainToClass,
TransformOptions,
} from 'class-transformer';
import { SchemaOptions, SchemaOptionsDict } from './def';
import 'reflect-metadata';
import { SchemaKeysMetaKey, SchemaMetaKey } from './constants';
import _ from 'lodash';
function getBasePropertySchemaFromOptions(options: SchemaOptions) {
if (options.schema) {
return options.schema;
}
if (typeof options.type !== 'string') {
return schemaFromClass(options.type);
}
switch (options.type as string) {
case 'any':
return Schema.any();
case 'never':
return Schema.never();
case 'string':
return Schema.string();
case 'number':
return Schema.number();
case 'boolean':
return Schema.boolean();
default:
return Schema.any();
}
}
function getPropertySchemaFromOptions<PT>(options: SchemaOptions): Schema<PT> {
let schema = getBasePropertySchemaFromOptions(options);
if (options.dict) {
schema = Schema.dict(schema);
}
if (options.array) {
schema = Schema.array(schema);
}
if (options.required != undefined) {
schema._required = options.required;
}
if (options.hidden != undefined) {
schema._hidden = options.hidden;
}
if (options.default != undefined) {
schema._default = options.default;
}
if (options.comment != undefined) {
schema._comment = options.comment;
}
return schema;
}
function schemasFromDict<T>(dict: SchemaOptionsDict<T>): Schema<T> {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return Schema.object<T>(
_.mapValues(dict, (opt) => getPropertySchemaFromOptions(opt)),
);
}
function schemaOptionsFromClass<T>(
cl: ClassConstructor<T>,
): SchemaOptionsDict<T> {
const keys: (keyof T)[] = Reflect.getMetadata(SchemaKeysMetaKey, cl);
if (!keys) {
return null;
}
const result: SchemaOptionsDict<T> = {};
for (const key of keys) {
const option: SchemaOptions = Reflect.getMetadata(
SchemaMetaKey,
cl,
key as string | symbol,
);
result[key] = option;
}
return result;
}
export function schemaFromClass<T>(cl: ClassConstructor<T>): Schema<T> {
const optionsDict = schemaOptionsFromClass(cl);
if (!optionsDict) {
return Schema.any();
}
return schemasFromDict<T>(optionsDict);
}
export function schemaTransform<T>(cl: ClassConstructor<T>, data: any): T {
const validatedObj = Schema.validate(data, schemaFromClass(cl));
return plainToClass(cl, validatedObj);
}
import { DefineSchema, schemaFromClass, schemaTransform } from '../src';
import { Schema } from 'koishi';
class B {
@DefineSchema({ type: 'number', default: 2 })
aa: number;
@DefineSchema({ type: 'boolean', default: true })
bb: boolean;
}
class A {
@DefineSchema({ type: 'number', required: true })
a: number;
@DefineSchema({ type: 'string', default: 'shigma' })
b: string;
@DefineSchema({ type: 'string', array: true, default: ['foo', 'bar'] })
c: string[];
@DefineSchema({ type: B })
bi: B;
@DefineSchema({ type: B, array: true })
biArr: B[];
@DefineSchema({ type: B, dict: true, array: true })
biDict: Record<string, B>[];
}
const schema = schemaFromClass(A);
const testObject = {
a: 4,
bi: { aa: 3 },
biArr: [{ aa: 4 }, { bb: false }],
biDict: [{ cccc: { aa: 5 } }],
};
// const testValidate = Schema.validate(testObject, schema);
// console.log(JSON.stringify(testValidate, null, 2));
const testTransform = schemaTransform(A, testObject);
console.log(JSON.stringify(testTransform, null, 2));
console.log(testTransform.bi instanceof B);
console.log(testTransform.biArr[0] instanceof B);
console.log(testTransform.biDict[0].cccc instanceof B);
import 'reflect-metadata';
function TheProperty(): PropertyDecorator {
return (obj, key) => {
Reflect.defineMetadata('test', 'www', obj.constructor, key);
};
}
class A {
@TheProperty()
foo: string;
}
console.log(Reflect.getMetadata('test', A, 'foo'));
const a = new A();
console.log(Reflect.getMetadata('test', a.constructor, 'foo'));
{
"compilerOptions": {
"outDir": "dist",
"module": "commonjs",
"target": "es2021",
"esModuleInterop": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"declaration": true,
"sourceMap": true,
"skipLibCheck": true
},
"compileOnSave": true,
"allowJs": true,
"include": [
"*.ts",
"src/**/*.ts",
"test/**/*.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