0
0

fix(backend): use prefixItems in admin/queue/*-delayed endpoint schema (#14468)

* fix(backend): represent tuples with `prefixItems`

* refactor(frontend): fix type errors

* fix(backend): add `prefixItems` in `SchemaType`

* fix(backend): add `unevaluatedItems: false` to disallow extra items

* refactor(frontend): consolidate `'deliver' | 'queue'` type def into `queue.vue`

* fix(backend): add `unevaluatedItems` in `SchemaType`
This commit is contained in:
zyoshoka 2024-08-30 10:58:59 +09:00 committed by GitHub
parent 06855f769f
commit 7fe3035059
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 59 additions and 48 deletions

View File

@ -144,7 +144,9 @@ export interface Schema extends OfSchema {
readonly type?: TypeStringef; readonly type?: TypeStringef;
readonly nullable?: boolean; readonly nullable?: boolean;
readonly optional?: boolean; readonly optional?: boolean;
readonly prefixItems?: ReadonlyArray<Schema>;
readonly items?: Schema; readonly items?: Schema;
readonly unevaluatedItems?: Schema | boolean;
readonly properties?: Obj; readonly properties?: Obj;
readonly required?: ReadonlyArray<Extract<keyof NonNullable<this['properties']>, string>>; readonly required?: ReadonlyArray<Extract<keyof NonNullable<this['properties']>, string>>;
readonly description?: string; readonly description?: string;
@ -198,6 +200,7 @@ type UnionSchemaType<a extends readonly any[], X extends Schema = a[number]> = X
//type UnionObjectSchemaType<a extends readonly any[], X extends Schema = a[number]> = X extends any ? ObjectSchemaType<X> : never; //type UnionObjectSchemaType<a extends readonly any[], X extends Schema = a[number]> = X extends any ? ObjectSchemaType<X> : never;
type UnionObjType<s extends Obj, a extends readonly any[], X extends ReadonlyArray<keyof s> = a[number]> = X extends any ? ObjType<s, X> : never; type UnionObjType<s extends Obj, a extends readonly any[], X extends ReadonlyArray<keyof s> = a[number]> = X extends any ? ObjType<s, X> : never;
type ArrayUnion<T> = T extends any ? Array<T> : never; type ArrayUnion<T> = T extends any ? Array<T> : never;
type ArrayToTuple<X extends ReadonlyArray<Schema>> = { [K in keyof X]: SchemaType<X[K]> };
type ObjectSchemaTypeDef<p extends Schema> = type ObjectSchemaTypeDef<p extends Schema> =
p['ref'] extends keyof typeof refs ? Packed<p['ref']> : p['ref'] extends keyof typeof refs ? Packed<p['ref']> :
@ -232,6 +235,12 @@ export type SchemaTypeDef<p extends Schema> =
p['items']['allOf'] extends ReadonlyArray<Schema> ? UnionToIntersection<UnionSchemaType<NonNullable<p['items']['allOf']>>>[] : p['items']['allOf'] extends ReadonlyArray<Schema> ? UnionToIntersection<UnionSchemaType<NonNullable<p['items']['allOf']>>>[] :
never never
) : ) :
p['prefixItems'] extends ReadonlyArray<Schema> ? (
p['items'] extends NonNullable<Schema> ? [...ArrayToTuple<p['prefixItems']>, ...SchemaType<p['items']>[]] :
p['items'] extends false ? ArrayToTuple<p['prefixItems']> :
p['unevaluatedItems'] extends false ? ArrayToTuple<p['prefixItems']> :
[...ArrayToTuple<p['prefixItems']>, ...unknown[]]
) :
p['items'] extends NonNullable<Schema> ? SchemaType<p['items']>[] : p['items'] extends NonNullable<Schema> ? SchemaType<p['items']>[] :
any[] any[]
) : ) :

View File

@ -21,8 +21,7 @@ export const meta = {
items: { items: {
type: 'array', type: 'array',
optional: false, nullable: false, optional: false, nullable: false,
items: { prefixItems: [
anyOf: [
{ {
type: 'string', type: 'string',
}, },
@ -30,7 +29,7 @@ export const meta = {
type: 'number', type: 'number',
}, },
], ],
}, unevaluatedItems: false,
}, },
example: [[ example: [[
'example.com', 'example.com',

View File

@ -21,8 +21,7 @@ export const meta = {
items: { items: {
type: 'array', type: 'array',
optional: false, nullable: false, optional: false, nullable: false,
items: { prefixItems: [
anyOf: [
{ {
type: 'string', type: 'string',
}, },
@ -30,7 +29,7 @@ export const meta = {
type: 'number', type: 'number',
}, },
], ],
}, unevaluatedItems: false,
}, },
example: [[ example: [[
'example.com', 'example.com',

View File

@ -36,7 +36,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { markRaw, onMounted, onUnmounted, ref, shallowRef } from 'vue'; import { markRaw, onMounted, onUnmounted, ref, shallowRef } from 'vue';
import * as Misskey from 'misskey-js';
import XChart from './overview.queue.chart.vue'; import XChart from './overview.queue.chart.vue';
import type { ApQueueDomain } from '@/pages/admin/queue.vue';
import number from '@/filters/number.js'; import number from '@/filters/number.js';
import { useStream } from '@/stream.js'; import { useStream } from '@/stream.js';
@ -52,10 +54,10 @@ const chartDelayed = shallowRef<InstanceType<typeof XChart>>();
const chartWaiting = shallowRef<InstanceType<typeof XChart>>(); const chartWaiting = shallowRef<InstanceType<typeof XChart>>();
const props = defineProps<{ const props = defineProps<{
domain: string; domain: ApQueueDomain;
}>(); }>();
const onStats = (stats) => { function onStats(stats: Misskey.entities.QueueStats) {
activeSincePrevTick.value = stats[props.domain].activeSincePrevTick; activeSincePrevTick.value = stats[props.domain].activeSincePrevTick;
active.value = stats[props.domain].active; active.value = stats[props.domain].active;
delayed.value = stats[props.domain].delayed; delayed.value = stats[props.domain].delayed;
@ -65,13 +67,13 @@ const onStats = (stats) => {
chartActive.value.pushData(stats[props.domain].active); chartActive.value.pushData(stats[props.domain].active);
chartDelayed.value.pushData(stats[props.domain].delayed); chartDelayed.value.pushData(stats[props.domain].delayed);
chartWaiting.value.pushData(stats[props.domain].waiting); chartWaiting.value.pushData(stats[props.domain].waiting);
}; }
const onStatsLog = (statsLog) => { function onStatsLog(statsLog: Misskey.entities.QueueStatsLog) {
const dataProcess = []; const dataProcess: Misskey.entities.QueueStats[ApQueueDomain]['activeSincePrevTick'][] = [];
const dataActive = []; const dataActive: Misskey.entities.QueueStats[ApQueueDomain]['active'][] = [];
const dataDelayed = []; const dataDelayed: Misskey.entities.QueueStats[ApQueueDomain]['delayed'][] = [];
const dataWaiting = []; const dataWaiting: Misskey.entities.QueueStats[ApQueueDomain]['waiting'][] = [];
for (const stats of [...statsLog].reverse()) { for (const stats of [...statsLog].reverse()) {
dataProcess.push(stats[props.domain].activeSincePrevTick); dataProcess.push(stats[props.domain].activeSincePrevTick);
@ -84,7 +86,7 @@ const onStatsLog = (statsLog) => {
chartActive.value.setData(dataActive); chartActive.value.setData(dataActive);
chartDelayed.value.setData(dataDelayed); chartDelayed.value.setData(dataDelayed);
chartWaiting.value.setData(dataWaiting); chartWaiting.value.setData(dataWaiting);
}; }
onMounted(() => { onMounted(() => {
connection.on('stats', onStats); connection.on('stats', onStats);

View File

@ -49,7 +49,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { markRaw, onMounted, onUnmounted, ref, shallowRef } from 'vue'; import { markRaw, onMounted, onUnmounted, ref, shallowRef } from 'vue';
import * as Misskey from 'misskey-js';
import XChart from './queue.chart.chart.vue'; import XChart from './queue.chart.chart.vue';
import type { ApQueueDomain } from '@/pages/admin/queue.vue';
import number from '@/filters/number.js'; import number from '@/filters/number.js';
import { misskeyApi } from '@/scripts/misskey-api.js'; import { misskeyApi } from '@/scripts/misskey-api.js';
import { useStream } from '@/stream.js'; import { useStream } from '@/stream.js';
@ -62,17 +64,17 @@ const activeSincePrevTick = ref(0);
const active = ref(0); const active = ref(0);
const delayed = ref(0); const delayed = ref(0);
const waiting = ref(0); const waiting = ref(0);
const jobs = ref<(string | number)[][]>([]); const jobs = ref<Misskey.Endpoints[`admin/queue/${ApQueueDomain}-delayed`]['res']>([]);
const chartProcess = shallowRef<InstanceType<typeof XChart>>(); const chartProcess = shallowRef<InstanceType<typeof XChart>>();
const chartActive = shallowRef<InstanceType<typeof XChart>>(); const chartActive = shallowRef<InstanceType<typeof XChart>>();
const chartDelayed = shallowRef<InstanceType<typeof XChart>>(); const chartDelayed = shallowRef<InstanceType<typeof XChart>>();
const chartWaiting = shallowRef<InstanceType<typeof XChart>>(); const chartWaiting = shallowRef<InstanceType<typeof XChart>>();
const props = defineProps<{ const props = defineProps<{
domain: string; domain: ApQueueDomain;
}>(); }>();
const onStats = (stats) => { function onStats(stats: Misskey.entities.QueueStats) {
activeSincePrevTick.value = stats[props.domain].activeSincePrevTick; activeSincePrevTick.value = stats[props.domain].activeSincePrevTick;
active.value = stats[props.domain].active; active.value = stats[props.domain].active;
delayed.value = stats[props.domain].delayed; delayed.value = stats[props.domain].delayed;
@ -82,13 +84,13 @@ const onStats = (stats) => {
chartActive.value.pushData(stats[props.domain].active); chartActive.value.pushData(stats[props.domain].active);
chartDelayed.value.pushData(stats[props.domain].delayed); chartDelayed.value.pushData(stats[props.domain].delayed);
chartWaiting.value.pushData(stats[props.domain].waiting); chartWaiting.value.pushData(stats[props.domain].waiting);
}; }
const onStatsLog = (statsLog) => { function onStatsLog(statsLog: Misskey.entities.QueueStatsLog) {
const dataProcess = []; const dataProcess: Misskey.entities.QueueStats[ApQueueDomain]['activeSincePrevTick'][] = [];
const dataActive = []; const dataActive: Misskey.entities.QueueStats[ApQueueDomain]['active'][] = [];
const dataDelayed = []; const dataDelayed: Misskey.entities.QueueStats[ApQueueDomain]['delayed'][] = [];
const dataWaiting = []; const dataWaiting: Misskey.entities.QueueStats[ApQueueDomain]['waiting'][] = [];
for (const stats of [...statsLog].reverse()) { for (const stats of [...statsLog].reverse()) {
dataProcess.push(stats[props.domain].activeSincePrevTick); dataProcess.push(stats[props.domain].activeSincePrevTick);
@ -101,14 +103,12 @@ const onStatsLog = (statsLog) => {
chartActive.value.setData(dataActive); chartActive.value.setData(dataActive);
chartDelayed.value.setData(dataDelayed); chartDelayed.value.setData(dataDelayed);
chartWaiting.value.setData(dataWaiting); chartWaiting.value.setData(dataWaiting);
}; }
onMounted(() => { onMounted(() => {
if (props.domain === 'inbox' || props.domain === 'deliver') {
misskeyApi(`admin/queue/${props.domain}-delayed`).then(result => { misskeyApi(`admin/queue/${props.domain}-delayed`).then(result => {
jobs.value = result; jobs.value = result;
}); });
}
connection.on('stats', onStats); connection.on('stats', onStats);
connection.on('statsLog', onStatsLog); connection.on('statsLog', onStatsLog);

View File

@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, computed } from 'vue'; import { ref, computed, type Ref } from 'vue';
import XQueue from './queue.chart.vue'; import XQueue from './queue.chart.vue';
import XHeader from './_header_.vue'; import XHeader from './_header_.vue';
import * as os from '@/os.js'; import * as os from '@/os.js';
@ -25,7 +25,9 @@ import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js'; import { definePageMetadata } from '@/scripts/page-metadata.js';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
const tab = ref('deliver'); export type ApQueueDomain = 'deliver' | 'inbox';
const tab: Ref<ApQueueDomain> = ref('deliver');
function clear() { function clear() {
os.confirm({ os.confirm({

View File

@ -8218,7 +8218,7 @@ export type operations = {
/** @description OK (with results) */ /** @description OK (with results) */
200: { 200: {
content: { content: {
'application/json': ((string | number)[])[]; 'application/json': [string, number][];
}; };
}; };
/** @description Client error */ /** @description Client error */
@ -8264,7 +8264,7 @@ export type operations = {
/** @description OK (with results) */ /** @description OK (with results) */
200: { 200: {
content: { content: {
'application/json': ((string | number)[])[]; 'application/json': [string, number][];
}; };
}; };
/** @description Client error */ /** @description Client error */