Commit 17fbe4af authored by nanahira's avatar nanahira

first

parent 478feae5
Pipeline #9336 passed with stages
in 1 minute and 19 seconds
# 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
webpack.config.js
dist/*
build/*
\ No newline at end of file
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
\ No newline at end of file
stages:
- build
- deploy
variables:
GIT_DEPTH: "1"
build:
stage: build
tags:
- linux
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
deploy_npm:
stage: deploy
dependencies:
- build
tags:
- linux
script:
- apt update;apt -y install coreutils
- echo $NPMRC | base64 --decode > ~/.npmrc
- npm publish . --access public
only:
- master
/install-npm.sh
.git*
/data
/output
/config.yaml
.idea
.dockerignore
Dockerfile
/src
/dist/full
{
"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-plugin-typeorm
TypeORM integration for Koishi
\ No newline at end of file
如果不喜欢 Koishi 的数据库设计的话,来用这个也是一个选择。
本插件提供了 TypeORM 的服务,可以用 `ctx.typeorm` 进行访问。
本插件**不是** Koishi 数据库支持。
## 安装
```bash
npm install koishi-plugin-typeorm
```
## 使用
### 配置
详见 [TypeORM 连接参数](https://github.com/typeorm/typeorm/blob/master/docs/connection-options.md)
只需要配置 `type` 以及 `host` `port` `usertoken` `password` 等连接参数即可。 `entities` 不在此处进行配置。
### 使用
每一个 Koishi 插件可以使用本插件提供的服务,使用 `create` 方法创建数据库连接,并在插件中使用。
每个连接的实体是独立的,不可以跨数据库连接建立关系。
#### 示例
```ts
export const using = ['typeorm']
export function apply(ctx: Context, config: any) {
ctx.on('ready', async () => {
await ctx.typeorm.create('myplugin', [MyEntity]);
});
ctx.command('foo')
.action(async(argv, id) => {
const repo = ctx.typeorm.getRepository(MyEntity);
const data = await repo.fineOne(id);
});
}
```
### API
- `create(token: string, entities: TypeORMEntity[], extraOptions?: ConnectionOptions): Promise<Connection>` 注册一个数据库连接。
- `token` 数据库连接名称。
- `entities` 数据库实体。
- `extraOptions` 额外的连接参数。
该方法同时会在插件关闭时自动关闭数据库连接。
- `getConnection(token: string): Connection` 获取一个数据库连接。
- `getEntityManager(token: string): EntityManager` 获取一个实体管理器。
- `getRepository<T>(entity: TypeORMEntity<T>): Repository<T>` 获取一个数据库实体的仓库。
- `close(token: string): Promise<void>` 关闭一个数据库连接。
#!/bin/bash
npm install --save \
source-map-support \
koishi-thirdeye
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 \
raw-loader \
ts-loader \
webpack \
webpack-cli \
koishi@latest \
ws
This diff is collapsed.
{
"name": "koishi-plugin-typeorm",
"description": "如果不喜欢 Koishi 的数据库设计的话,来用这个也是一个选择。",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"lint": "eslint --fix .",
"build": "webpack",
"test": "jest --passWithNoTests"
},
"repository": {
"type": "git",
"url": "https://code.mycard.moe/3rdeye/koishi-plugin-typeorm.git"
},
"author": "Nanahira <nanahira@momobako.com>",
"license": "MIT",
"keywords": [
"Koishi.js",
"qqbot",
"cqhttp",
"onebot"
],
"bugs": {
"url": "https://code.mycard.moe/3rdeye/koishi-plugin-typeorm/issues"
},
"homepage": "https://code.mycard.moe/3rdeye/koishi-plugin-typeorm",
"peerDependencies": {
"koishi": "^4.1.2"
},
"dependencies": {
"koishi-thirdeye": "^8.1.5",
"mysql": "^2.18.1",
"pg": "^8.7.3",
"source-map-support": "^0.5.21",
"typeorm": "^0.2.42-dev.1446e02"
},
"devDependencies": {
"@types/jest": "^27.4.0",
"@types/node": "^17.0.16",
"@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",
"jest": "^27.5.1",
"koishi": "^4.1.2",
"prettier": "^2.5.1",
"raw-loader": "^4.0.2",
"ts-jest": "^27.1.3",
"ts-loader": "^9.2.6",
"typescript": "^4.5.5",
"webpack": "^5.68.0",
"webpack-cli": "^4.9.2",
"ws": "^8.5.0"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "tests",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}
import 'source-map-support/register';
import { DefineSchema, RegisterSchema } from 'koishi-thirdeye';
import { ConnectionOptions, EntitySchema } from 'typeorm';
import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions';
export type TypeORMPluginConfigLike = ConnectionOptions;
@RegisterSchema()
export class TypeORMPluginConfig implements PostgresConnectionOptions {
constructor(config: TypeORMPluginConfigLike) {}
@DefineSchema({
description: '数据库类型',
default: 'postgres',
type: String,
})
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
type:
| 'postgres'
| 'mysql'
| 'mariadb'
| 'cockroachdb'
| 'sqlite'
| 'mssql'
| 'sap'
| 'oracle'
| 'cordova'
| 'nativescript'
| 'react-native'
| 'sqljs'
| 'mongodb'
| 'aurora-data-api'
| 'aurora-data-api-pg'
| 'expo'
| 'better-sqlite3'
| 'capacitor';
@DefineSchema({ description: '数据库地址。' })
host: string;
@DefineSchema({ description: '数据库端口。' })
port: number;
@DefineSchema({ description: '数据库名称。' })
database: string;
@DefineSchema({ description: '数据库用户名。' })
username: string;
@DefineSchema({ description: '数据库密码。' })
password: string;
@DefineSchema({ description: '是否自动建表。', default: true })
synchronize: boolean;
}
// eslint-disable-next-line @typescript-eslint/ban-types
export type TypeORMEntity<T = any> =
| { new (...args: any[]): T }
| EntitySchema<T>;
import 'source-map-support/register';
import {
TypeORMEntity,
TypeORMPluginConfig,
TypeORMPluginConfigLike,
} from './config';
import {
DefinePlugin,
BasePlugin,
Caller,
Provide,
Inject,
} from 'koishi-thirdeye';
import { Connection, ConnectionOptions, createConnection } from 'typeorm';
import { Context, Database } from 'koishi';
export * from './config';
interface ConnectionEntry {
connection: Connection;
entities: TypeORMEntity[];
}
declare module 'koishi' {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Context {
interface Services {
typeorm: TypeORMPlugin;
}
}
}
@Provide('typeorm', { immediate: true })
@DefinePlugin({ name: 'typeorm', schema: TypeORMPluginConfig })
export default class TypeORMPlugin extends BasePlugin<TypeORMPluginConfigLike> {
private registryMap = new Map<string, ConnectionEntry>();
private entityMap = new Map<TypeORMEntity, string>();
@Caller()
private caller: Context;
@Inject()
private database: Database;
async close(token: string) {
const entry = this.registryMap.get(token)!;
if (!entry) return;
const { connection, entities } = entry;
await connection.close();
this.registryMap.delete(token);
for (const entity of entities) {
this.entityMap.delete(entity);
}
}
getConnection(token: string) {
return this.registryMap.get(token)?.connection;
}
getEntityManager(token: string) {
return this.getConnection(token)?.manager;
}
getRepository<T>(entity: TypeORMEntity<T>) {
const token = this.entityMap.get(entity);
if (!token) return;
const connection = this.getConnection(token);
if (!connection) return;
return connection.getRepository(entity);
}
async create(
token: string,
entities: TypeORMEntity[],
extraOptions: Partial<ConnectionOptions> = {},
) {
const ctx = this.caller;
ctx.on('dispose', () => this.close(token));
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const connectionOptions: ConnectionOptions = {
...this.config,
entities,
entityPrefix: `koishi_${token}_`,
metadataTableName: `koishi_typeorm_metadata_${token}`,
...extraOptions,
};
const connection = await createConnection(connectionOptions);
this.registryMap.set(token, { connection, entities });
for (const entity of entities) {
this.entityMap.set(entity, token);
}
return connection;
}
}
import { App } from 'koishi';
import TypeORMPlugin from '../src';
import {
Column,
Connection,
Entity,
EntityManager,
ManyToOne,
OneToMany,
PrimaryGeneratedColumn,
Repository,
} from 'typeorm';
@Entity()
class User {
@PrimaryGeneratedColumn()
id: number;
@Column('varchar', { length: 32 })
name: string;
@OneToMany((type) => Book, (book) => book.author)
books: Book[];
}
@Entity()
class Book {
@PrimaryGeneratedColumn()
id: number;
@Column('varchar', { length: 32 })
name: string;
@ManyToOne((type) => User, (user) => user.books)
author: User;
}
describe('Koishi typeorm', () => {
let app: App;
beforeEach(async () => {
app = new App();
await app.start();
app.plugin(TypeORMPlugin, {
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'koishi',
password: 'koishi@114514',
database: 'koishi',
dropSchema: true,
});
});
it('should create connection', async () => {
expect(app.typeorm).toBeTruthy();
await app.typeorm.create('foo', [User, Book]);
const connection = app.typeorm.getConnection('foo');
expect(connection).toBeInstanceOf(Connection);
const manager = app.typeorm.getEntityManager('foo');
expect(manager).toBeInstanceOf(EntityManager);
const userRepo = app.typeorm.getRepository(User);
expect(userRepo).toBeInstanceOf(Repository);
let user = new User();
user.name = 'Shigma';
user = await userRepo.save(user);
expect(typeof user.id).toBe('number');
const gotUser = await userRepo.findOne({ where: { name: 'Shigma' } });
expect(gotUser.name).toBe('Shigma');
});
it('should connect with custom options', async () => {
await app.typeorm.create('foo', [User, Book], {
entityPrefix: 'fooooo_',
metadataTableName: 'fooooo_metadata',
});
const connection = app.typeorm.getConnection('foo');
expect(connection.metadataTableName).toBe('fooooo_metadata');
});
it('should auto dispose when plugin gone', async () => {
await new Promise<void>((resolve) => {
app.plugin(async (ctx) => {
await ctx.typeorm.create('bar', [User, Book]);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
ctx.on('fooo', () => ctx.dispose());
resolve();
});
});
expect(app.typeorm.getConnection('bar')).toBeInstanceOf(Connection);
expect(app.typeorm.getRepository(User)).toBeInstanceOf(Repository);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
app.emit('fooo');
await new Promise<void>((resolve) => {
setTimeout(resolve, 1);
});
expect(app.typeorm.getConnection('bar')).toBeUndefined();
expect(app.typeorm.getRepository(User)).toBeUndefined();
});
});
{
"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",
"tests/**/*.ts"
]
}
const path = require('path');
const packgeInfo = require('./package.json');
function externalsFromDep() {
return Object.fromEntries(
[
...Object.keys(packgeInfo.dependencies || {}),
...Object.keys(packgeInfo.peerDependencies || {}),
]
.filter((dep) => dep !== 'source-map-support')
.map((dep) => [dep, dep]),
);
}
const packAll = !!process.env.PACK_ALL;
module.exports = {
entry: './src/index.ts',
mode: 'production',
target: 'node',
devtool: 'source-map',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
{ test: /\.mustache$/, use: 'raw-loader' },
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
output: {
filename: 'index.js',
library: {
type: 'commonjs',
},
path: path.resolve(__dirname, packAll ? 'dist/full' : 'dist'),
},
externals: {
koishi: 'koishi',
...(packAll ? {} : externalsFromDep()),
},
};
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