バックエンド・データベースの実装・レビューを行う人向け。
まずは簡単にforwardRefを試してみる
export class FooService {
constructor(
@Inject(forwardRef(() => BarService))
private barService: BarService
) {
}
}できなければOnModuleInitを使う
moduleRef.get(BarService.name) は文字列トークンで解決するため、BarService の通常の登録に加え、同じモジュールの providers に alias を登録する必要がある。Misskey では CoreModule に登録している。
providers: [
BarService,
{ provide: BarService.name, useExisting: BarService },
],import { Injectable, OnModuleInit } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import { BarService } from '@/core/BarService';
@Injectable()
export class FooService implements OnModuleInit {
private barService: BarService // constructorから移動してくる
constructor(
private moduleRef: ModuleRef,
) {
}
async onModuleInit() {
this.barService = this.moduleRef.get(BarService.name);
}
public async niceMethod() {
return await this.barService.incredibleMethod({ hoge: 'fuga' });
}
}テストでonModuleInitを呼び出す必要がある
// import ...
describe('test', () => {
let app: TestingModule;
let fooService: FooService; // for test case
let barService: BarService; // for test case
beforeEach(async () => {
app = await Test.createTestingModule({
imports: ...,
providers: [
FooService,
{ // mockする (mockは必須ではないかもしれない)
provide: BarService,
useFactory: () => ({
incredibleMethod: jest.fn(),
}),
},
{ // Provideにする
provide: BarService.name,
useExisting: BarService,
},
],
})
.useMocker(...
.compile();
fooService = app.get<FooService>(FooService);
barService = app.get<BarService>(BarService) as jest.Mocked<BarService>;
// onModuleInitを実行する
await fooService.onModuleInit();
});
test('nice', async () => {
await fooService.niceMethod();
expect(barService.incredibleMethod).toHaveBeenCalled();
expect(barService.incredibleMethod.mock.lastCall![0])
.toEqual({ hoge: 'fuga' });
});
})pnpm build-misskey-js-with-types#6441
SQLをクエリビルダで組み立てる際、使用するプレースホルダは重複してはならない 例えば
query.andWhere(new Brackets(qb => {
for (const type of ps.fileType) {
qb.orWhere(`:type = ANY(note.attachedFileTypes)`, { type: type });
}
}));と書くと、ループ中でtypeというプレースホルダが複数回使われてしまいおかしくなる
だから次のようにする必要がある
query.andWhere(new Brackets(qb => {
for (const type of ps.fileType) {
const i = ps.fileType.indexOf(type);
qb.orWhere(`:type${i} = ANY(note.attachedFileTypes)`, { [`type${i}`]: type });
}
}));const foo = await Foos.findOne({
bar: Not(null)
});のようなクエリ(barがnullではない)は期待通りに動作しない。
次のようにします:
const foo = await Foos.findOne({
bar: Not(IsNull())
});SQLを発行する際、パラメータがnullになる可能性のある場合はSQL文を出し分けなければならない
例えば
query.where('file.folderId = :folderId', { folderId: ps.folderId });という処理で、ps.folderIdがnullだと結果的にfile.folderId = nullのようなクエリが発行されてしまい、これは正しいSQLではないので期待した結果が得られない
だから次のようにする必要がある
if (ps.folderId) {
query.where('file.folderId = :folderId', { folderId: ps.folderId });
} else {
query.where('file.folderId IS NULL');
}SQLを発行する際、INのパラメータが[](空の配列)になる可能性のある場合はSQL文を出し分けなければならない
例えば
const users = await Users.find({
id: In(userIds)
});という処理で、userIdsが[]だと結果的にuser.id IN ()のようなクエリが発行されてしまい、これは正しいSQLではないので期待した結果が得られない
だから次のようにする必要がある
const users = userIds.length > 0 ? await Users.find({
id: In(userIds)
}) : [];SQLでは配列のインデックスは1始まり。
[a, b, c]の aにアクセスしたいなら[0]ではなく[1]と書く
nullが含まれる可能性のあるカラムにINするときは、そのままだとおかしくなるのでORなどでnullのハンドリングをしよう。
enumの列挙の内容の削除は、その値をもつレコードを全て削除しないといけない
削除が重たかったり不可能だったりする場合は、削除しないでおく
リポジトリルートから:
pnpm --filter backend exec typeorm migration:generate -d ormconfig.js -o --esm migration/<PascalName>- ワークスペースにインストール済みの TypeORM CLI を使う。設定ファイルと出力先は
packages/backend/基準で解決され、生成ファイルはpackages/backend/migration/に配置される - 作成されたスクリプトは不必要な変更を含むため除去してください
-o(--outputJs) で JS 形式、--esmで ESM 形式に生成する。Misskey の既存 migration はすべて ESM JS なので両方のオプションが必要