SchemaTypeの型計算量を削減 (#8332)

* schema typeの型計算量を削減

* reduce some type error

* wip

* fix

* clean up

* more shrink
This commit is contained in:
tamaina 2022-02-19 23:21:28 +09:00 committed by GitHub
parent b6db709e02
commit fd8f8162e1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 57 additions and 105 deletions

View file

@ -63,14 +63,7 @@ export const refs = {
Emoji: packedEmojiSchema, Emoji: packedEmojiSchema,
}; };
// Packed = SchemaTypeDef<typeof refs[x]>; とすると展開されてマウスホバー時に型表示が使い物にならなくなる export type Packed<x extends keyof typeof refs> = SchemaType<typeof refs[x]>;
// ObjType<r['properties']>を指定するとなぜか展開されずにPacked<'Hoge'>と表示される
type PackedDef<r extends { properties?: Obj; oneOf?: ReadonlyArray<Schema>; allOf?: ReadonlyArray<Schema> }> =
r['allOf'] extends ReadonlyArray<Schema> ? UnionToIntersection<UnionSchemaType<r['allOf']>> :
r['oneOf'] extends ReadonlyArray<Schema> ? UnionSchemaType<r['oneOf']> :
r['properties'] extends Obj ? ObjType<r['properties'], any> :
never;
export type Packed<x extends keyof typeof refs> = PackedDef<typeof refs[x]>;
type TypeStringef = 'null' | 'boolean' | 'integer' | 'number' | 'string' | 'array' | 'object' | 'any'; type TypeStringef = 'null' | 'boolean' | 'integer' | 'number' | 'string' | 'array' | 'object' | 'any';
type StringDefToType<T extends TypeStringef> = type StringDefToType<T extends TypeStringef> =
@ -107,31 +100,20 @@ export interface Schema extends OfSchema {
readonly minLength?: number; readonly minLength?: number;
} }
type OptionalPropertyNames<T extends Obj> = { type RequiredPropertyNames<s extends Obj> = {
[K in keyof T]: T[K]['optional'] extends true ? K : never [K in keyof s]:
}[keyof T]; // K is not optional
s[K]['optional'] extends false ? K :
type NonOptionalPropertyNames<T extends Obj> = { // K has default value
[K in keyof T]: T[K]['optional'] extends false ? K : never s[K]['default'] extends null | string | number | boolean | Record<string, unknown> ? K : never
}[keyof T]; }[keyof s];
type DefaultPropertyNames<T extends Obj> = {
[K in keyof T]: T[K]['default'] extends null ? K :
T[K]['default'] extends string ? K :
T[K]['default'] extends number ? K :
T[K]['default'] extends boolean ? K :
T[K]['default'] extends Record<string, unknown> ? K :
never
}[keyof T];
export interface Obj { [key: string]: Schema; } export interface Obj { [key: string]: Schema; }
export type ObjType<s extends Obj, RequiredProps extends ReadonlyArray<keyof s>> = export type ObjType<s extends Obj, RequiredProps extends keyof s> =
{ -readonly [P in keyof s]?: SchemaType<s[P]> } & { -readonly [P in keyof s]?: SchemaType<s[P]> } &
{ -readonly [P in RequiredProps[number]]: SchemaType<s[P]> } & { -readonly [P in RequiredProps]: SchemaType<s[P]> } &
{ -readonly [P in OptionalPropertyNames<s>]?: SchemaType<s[P]> } & { -readonly [P in RequiredPropertyNames<s>]: SchemaType<s[P]> };
{ -readonly [P in NonOptionalPropertyNames<s>]: SchemaType<s[P]> } &
{ -readonly [P in DefaultPropertyNames<s>]: SchemaType<s[P]> };
type NullOrUndefined<p extends Schema, T> = type NullOrUndefined<p extends Schema, T> =
p['nullable'] extends true p['nullable'] extends true
@ -142,11 +124,12 @@ type NullOrUndefined<p extends Schema, T> =
? (T | undefined) ? (T | undefined)
: T; : T;
// 共用体型を交差型にする型 https://stackoverflow.com/questions/54938141/typescript-convert-union-to-intersection // https://stackoverflow.com/questions/54938141/typescript-convert-union-to-intersection
// Get intersection from union
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
// https://github.com/misskey-dev/misskey/pull/8144#discussion_r785287552 // https://github.com/misskey-dev/misskey/pull/8144#discussion_r785287552
// 単純にSchemaTypeDef<X>で判定するだけではダメ // To get union, we use `Foo extends any ? Hoge<Foo> : never`
type UnionSchemaType<a extends readonly any[], X extends Schema = a[number]> = X extends any ? SchemaType<X> : never; type UnionSchemaType<a extends readonly any[], X extends Schema = a[number]> = X extends any ? SchemaType<X> : never;
type ArrayUnion<T> = T extends any ? Array<T> : never; type ArrayUnion<T> = T extends any ? Array<T> : never;
@ -163,7 +146,7 @@ export type SchemaTypeDef<p extends Schema> =
p['type'] extends 'boolean' ? boolean : p['type'] extends 'boolean' ? boolean :
p['type'] extends 'object' ? ( p['type'] extends 'object' ? (
p['ref'] extends keyof typeof refs ? Packed<p['ref']> : p['ref'] extends keyof typeof refs ? Packed<p['ref']> :
p['properties'] extends NonNullable<Obj> ? ObjType<p['properties'], NonNullable<p['required']>> : p['properties'] extends NonNullable<Obj> ? ObjType<p['properties'], NonNullable<p['required']>[number]> :
p['anyOf'] extends ReadonlyArray<Schema> ? UnionSchemaType<p['anyOf']> & Partial<UnionToIntersection<UnionSchemaType<p['anyOf']>>> : p['anyOf'] extends ReadonlyArray<Schema> ? UnionSchemaType<p['anyOf']> & Partial<UnionToIntersection<UnionSchemaType<p['anyOf']>>> :
p['allOf'] extends ReadonlyArray<Schema> ? UnionToIntersection<UnionSchemaType<p['allOf']>> : p['allOf'] extends ReadonlyArray<Schema> ? UnionToIntersection<UnionSchemaType<p['allOf']>> :
any any

View file

@ -32,7 +32,7 @@ export class AppRepository extends Repository<App> {
...(me ? { ...(me ? {
isAuthorized: await AccessTokens.count({ isAuthorized: await AccessTokens.count({
appId: app.id, appId: app.id,
userId: me, userId: me.id,
}).then(count => count > 0), }).then(count => count > 0),
} : {}), } : {}),
}; };

View file

@ -65,5 +65,5 @@ export default define(meta, paramDef, async (ps) => {
imageUrl: ps.imageUrl, imageUrl: ps.imageUrl,
}).then(x => Announcements.findOneOrFail(x.identifiers[0])); }).then(x => Announcements.findOneOrFail(x.identifiers[0]));
return announcement; return Object.assign({}, announcement, { createdAt: announcement.createdAt.toISOString(), updatedAt: null });
}); });

View file

@ -10,20 +10,24 @@ export const meta = {
kind: 'read:gallery-likes', kind: 'read:gallery-likes',
res: { res: {
type: 'object', type: 'array',
optional: false, nullable: false, optional: false, nullable: false,
properties: { items: {
id: { type: 'object',
type: 'string', optional: false, nullable: false,
optional: false, nullable: false, properties: {
format: 'id', id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
post: {
type: 'object',
optional: false, nullable: false,
ref: 'GalleryPost',
},
}, },
page: { }
type: 'object',
optional: false, nullable: false,
ref: 'GalleryPost',
},
},
}, },
} as const; } as const;

View file

@ -10,20 +10,23 @@ export const meta = {
kind: 'read:page-likes', kind: 'read:page-likes',
res: { res: {
type: 'object', type: 'array',
optional: false, nullable: false, optional: false, nullable: false,
properties: { items: {
id: { type: 'object',
type: 'string', properties: {
optional: false, nullable: false, id: {
format: 'id', type: 'string',
optional: false, nullable: false,
format: 'id',
},
page: {
type: 'object',
optional: false, nullable: false,
ref: 'Page',
},
}, },
page: { }
type: 'object',
optional: false, nullable: false,
ref: 'Page',
},
},
}, },
} as const; } as const;
@ -47,5 +50,5 @@ export default define(meta, paramDef, async (ps, user) => {
.take(ps.limit) .take(ps.limit)
.getMany(); .getMany();
return await PageLikes.packMany(likes, user); return PageLikes.packMany(likes, user);
}); });

View file

@ -12,46 +12,7 @@ export const meta = {
items: { items: {
type: 'object', type: 'object',
optional: false, nullable: false, optional: false, nullable: false,
properties: { ref: 'App',
id: {
type: 'string',
optional: false, nullable: false,
},
name: {
type: 'string',
optional: false, nullable: false,
},
callbackUrl: {
type: 'string',
optional: false, nullable: false,
},
permission: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
},
},
secret: {
type: 'string',
optional: true, nullable: false,
},
isAuthorized: {
type: 'object',
optional: true, nullable: false,
properties: {
appId: {
type: 'string',
optional: false, nullable: false,
},
userId: {
type: 'string',
optional: false, nullable: false,
},
},
},
},
}, },
}, },
} as const; } as const;

View file

@ -14,12 +14,12 @@ export const meta = {
properties: { properties: {
state: { state: {
type: 'string', type: 'string',
optional: false, nullable: false, optional: true, nullable: false,
enum: ['already-subscribed', 'subscribed'], enum: ['already-subscribed', 'subscribed'],
}, },
key: { key: {
type: 'string', type: 'string',
optional: false, nullable: false, optional: false, nullable: true,
}, },
}, },
}, },
@ -49,7 +49,7 @@ export default define(meta, paramDef, async (ps, user) => {
if (exist != null) { if (exist != null) {
return { return {
state: 'already-subscribed', state: 'already-subscribed' as const,
key: instance.swPublicKey, key: instance.swPublicKey,
}; };
} }
@ -64,7 +64,7 @@ export default define(meta, paramDef, async (ps, user) => {
}); });
return { return {
state: 'subscribed', state: 'subscribed' as const,
key: instance.swPublicKey, key: instance.swPublicKey,
}; };
}); });

View file

@ -84,6 +84,7 @@ export interface MainStreamTypes {
}; };
driveFileCreated: Packed<'DriveFile'>; driveFileCreated: Packed<'DriveFile'>;
readAntenna: Antenna; readAntenna: Antenna;
receiveFollowRequest: Packed<'User'>;
} }
export interface DriveStreamTypes { export interface DriveStreamTypes {

View file

@ -4,7 +4,7 @@ import generateNativeUserToken from '../server/api/common/generate-native-user-t
import { genRsaKeyPair } from '@/misc/gen-key-pair'; import { genRsaKeyPair } from '@/misc/gen-key-pair';
import { User } from '@/models/entities/user'; import { User } from '@/models/entities/user';
import { UserProfile } from '@/models/entities/user-profile'; import { UserProfile } from '@/models/entities/user-profile';
import { getConnection } from 'typeorm'; import { getConnection, ObjectLiteral } from 'typeorm';
import { genId } from '@/misc/gen-id'; import { genId } from '@/misc/gen-id';
import { UserKeypair } from '@/models/entities/user-keypair'; import { UserKeypair } from '@/models/entities/user-keypair';
import { UsedUsername } from '@/models/entities/used-username'; import { UsedUsername } from '@/models/entities/used-username';
@ -21,7 +21,7 @@ export async function createSystemUser(username: string) {
const keyPair = await genRsaKeyPair(4096); const keyPair = await genRsaKeyPair(4096);
let account!: User; let account!: User | ObjectLiteral;
// Start transaction // Start transaction
await getConnection().transaction(async transactionalEntityManager => { await getConnection().transaction(async transactionalEntityManager => {