diff --git a/.prettierignore b/.prettierignore index bc7f45b8..1f2d0eb5 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1 @@ -src/api/ \ No newline at end of file +src/infrastructure/api/gen \ No newline at end of file diff --git a/package.json b/package.json index 47f47ccb..93003b1a 100644 --- a/package.json +++ b/package.json @@ -17,10 +17,14 @@ "apigen": "rm -rf src/infrastructure/api/aspida && npx openapi2aspida", "prepare": "husky install && rimraf ./node_modules/@types/react", "storybook": "start-storybook -p 6006", - "build-storybook": "yarn typecheck && build-storybook" + "build-storybook": "yarn typecheck && build-storybook", + "buf-gen": "rm -rf src/infrastructure/api/gen && buf generate --template ./twinte-proto/buf.front.gen.yaml ./twinte-proto" }, "dependencies": { "@aspida/axios": "^1.12.0", + "@bufbuild/protobuf": "^1.8.0", + "@connectrpc/connect": "^1.4.0", + "@connectrpc/connect-web": "^1.4.0", "@gtm-support/vue-gtm": "^2.0.0", "@sentry/rollup-plugin": "^0.4.0", "@sentry/tracing": "^7.16.0", @@ -31,6 +35,7 @@ "@vueuse/core": "^9.12.0", "@vueuse/head": "^1.0.24", "aspida": "^1.6.3", + "async-mutex": "^0.5.0", "axios": "^1.4.0", "core-js": "^3.33.0", "dayjs": "^1.10.4", @@ -44,6 +49,9 @@ "vuex": "4.1.0" }, "devDependencies": { + "@bufbuild/buf": "^1.30.0", + "@bufbuild/protoc-gen-es": "^1.8.0", + "@connectrpc/protoc-gen-connect-es": "^1.4.0", "@rushstack/eslint-patch": "^1.1.1", "@storybook/addon-actions": "^6.5.12", "@storybook/addon-essentials": "^6.4.19", diff --git a/src/domain/timetable.ts b/src/domain/timetable.ts index 154861a3..61c27dc2 100644 --- a/src/domain/timetable.ts +++ b/src/domain/timetable.ts @@ -1,5 +1,5 @@ import { initializeObject } from "~/utils"; -import { NormalDay, normalDays, SpecialDay, specialDays } from "./day"; +import { days, NormalDay, normalDays, SpecialDay, specialDays } from "./day"; import { Module, modules } from "./module"; import { Period, periods } from "./period"; import { @@ -97,3 +97,29 @@ export const schedulesToTimetable = ( return timetable; }; + +export const timetableToSchedules = ( + timetable: Timetable +): Schedule[] => { + const schedules: Schedule[] = []; + + modules.forEach((module) => { + normalDays.forEach((day) => { + periods.forEach((period) => { + if (timetable.normal[module][day][period]) { + schedules.push({ module, day, period }); + } + }); + }); + }); + + modules.forEach((module) => { + specialDays.forEach((day) => { + if (timetable.special[module][day]) { + schedules.push({ module, day }); + } + }); + }); + + return schedules; +}; diff --git a/src/usecases/timetable.ts b/src/usecases/timetable.ts index 98d4401b..2687d23c 100644 --- a/src/usecases/timetable.ts +++ b/src/usecases/timetable.ts @@ -5,7 +5,9 @@ import { PromiseClient, Transport, } from "@connectrpc/connect"; +import { Mutex } from "async-mutex"; import { RegisteredCourse, Course } from "~/domain/course"; +import { normalDays } from "~/domain/day"; import { InternalServerError, NetworkError, @@ -14,8 +16,16 @@ import { ValueError, isResultError, } from "~/domain/error"; -import { Schedule } from "~/domain/schedule"; +import { Module, modules } from "~/domain/module"; +import { periods } from "~/domain/period"; +import { NormalSchedule, Schedule, isNormalSchedule } from "~/domain/schedule"; import { Tag } from "~/domain/tag"; +import { + NormalTimetable, + initializeTimetable, + normalSchedulesToNormalTimetable, + timetableToSchedules, +} from "~/domain/timetable"; import { toPBAcademicYear, toPBUUID, @@ -40,6 +50,7 @@ import { deleteElementInArray, updateElementInArray, } from "~/utils"; +import { IAuthUseCase } from "./auth"; export interface ITimetableUseCase { getCoursesByCodes(inputData: { @@ -53,17 +64,29 @@ export interface ITimetableUseCase { | InternalServerError >; - searchCourses( - year: number, - keywords: string[], - codePrefixes: { included: string[]; excluded: string[] }, + searchCourses(conds: { + year: number; + keywords: string[]; + codePrefixes: { included: string[]; excluded: string[] }; schedules: { fullyIncluded: Schedule[]; partiallyOverlapped: Schedule[]; - }, - offset: number, - limit: number - ): Promise; + }; + offset: number; + limit: number; + }): Promise< + Course[] | UnauthorizedError | NetworkError | InternalServerError + >; + + searchCoursesOnBlank(conds: { + year: number; + keywords: string[]; + codePrefixes: { included: string[]; excluded: string[] }; + offset: number; + limit: number; + }): Promise< + Course[] | UnauthorizedError | NetworkError | InternalServerError + >; addCoursesByCodes(inputData: { year: number; @@ -128,6 +151,14 @@ export interface ITimetableUseCase { | InternalServerError >; + /** + * Return true if the schedules do not overlap comparing to the schedules of registered courses. Return false otherwise. + */ + checkScheduleDuplicate( + year: number, + schedules: Schedule[] + ): Promise; + createTag( name: string ): Promise; @@ -167,57 +198,70 @@ export interface ITimetableUseCase { export class TimetableUseCase implements ITimetableUseCase { #client: PromiseClient; + #mutex: { + registeredCourses: Mutex; + tags: Mutex; + }; + #registeredCourses?: RegisteredCourse[]; #tags?: Tag[]; constructor(transport: Transport) { this.#client = createPromiseClient(TimetableService, transport); + this.#mutex = { + registeredCourses: new Mutex(), + tags: new Mutex(), + }; } #getRegisteredCourses(): Promise< RegisteredCourse[] | UnauthorizedError | NetworkError > { - if (this.#registeredCourses) { - return Promise.resolve(this.#registeredCourses); - } - - return this.#client - .getRegisteredCourses({}) - .then((res) => res.registeredCourses.map(fromPBRegisteredCourse)) - .then((registeredCourses) => { - return (this.#registeredCourses = registeredCourses); - }) - .catch((error) => { - return handleError(error, (connectError: ConnectError) => { - if (connectError.code === Code.Unauthenticated) { - return new UnauthorizedError(); - } - - throw error; + return this.#mutex.registeredCourses.runExclusive(() => { + if (this.#registeredCourses) { + return this.#registeredCourses; + } + + return this.#client + .getRegisteredCourses({}) + .then((res) => res.registeredCourses.map(fromPBRegisteredCourse)) + .then((registeredCourses) => { + return (this.#registeredCourses = registeredCourses); + }) + .catch((error) => { + return handleError(error, (connectError: ConnectError) => { + if (connectError.code === Code.Unauthenticated) { + return new UnauthorizedError(); + } + + throw error; + }); }); - }); + }); } #getTags(): Promise { - if (this.#tags) { - return Promise.resolve(this.#tags); - } - - return this.#client - .getTags({}) - .then((res) => res.tags.map(fromPBTag)) - .then((tags) => { - return (this.#tags = tags); - }) - .catch((error) => { - return handleError(error, (connectError: ConnectError) => { - if (connectError.code === Code.Unauthenticated) { - return new UnauthorizedError(); - } - - throw error; + return this.#mutex.tags.runExclusive(() => { + if (this.#tags) { + return this.#tags; + } + + return this.#client + .getTags({}) + .then((res) => res.tags.map(fromPBTag)) + .then((tags) => { + return (this.#tags = tags); + }) + .catch((error) => { + return handleError(error, (connectError: ConnectError) => { + if (connectError.code === Code.Unauthenticated) { + return new UnauthorizedError(); + } + + throw error; + }); }); - }); + }); } getCoursesByCodes(inputData: { @@ -239,27 +283,77 @@ export class TimetableUseCase implements ITimetableUseCase { } // TODO - searchCourses( - year: number, - keywords: string[], - codePrefixes: { included: string[]; excluded: string[] }, + searchCourses(conds: { + year: number; + keywords: string[]; + codePrefixes: { included: string[]; excluded: string[] }; schedules: { fullyIncluded: Schedule[]; partiallyOverlapped: Schedule[]; - }, - offset: number, - limit: number - ): Promise< + }; + offset: number; + limit: number; + }): Promise< Course[] | UnauthorizedError | NetworkError | InternalServerError > { return this.#client .searchCourses({ - year: toPBAcademicYear(year), - keywords: keywords, - codePrefixesExcluded: codePrefixes.included, - codePrefixesIncluded: codePrefixes.excluded, - offset, - limit, + year: toPBAcademicYear(conds.year), + keywords: conds.keywords, + codePrefixesIncluded: conds.codePrefixes.included, + codePrefixesExcluded: conds.codePrefixes.excluded, + schedulesFullyIncluded: toPBSchedules( + conds.schedules.fullyIncluded, + [] + ), + schedulesPartiallyOverlapped: toPBSchedules( + conds.schedules.partiallyOverlapped, + [] + ), + offset: conds.offset, + limit: conds.limit, + }) + .then((res) => res.courses.map(fromPBCourse)) + .catch((error) => handleError(error)); + } + + // TODO + async searchCoursesOnBlank(conds: { + year: number; + keywords: string[]; + codePrefixes: { included: string[]; excluded: string[] }; + offset: number; + limit: number; + }): Promise< + Course[] | UnauthorizedError | NetworkError | InternalServerError + > { + const result = await this.getRegisteredCourses(conds.year); + if (isResultError(result)) { + return result; + } + + const timetable = initializeTimetable(modules, true); + + result + .map(({ schedules }) => schedules) + .flat() + .filter(isNormalSchedule) + .forEach(({ module, day, period }) => { + timetable.normal[module][day][period] = false; + }); + + const schedules = timetableToSchedules(timetable); + + return this.#client + .searchCourses({ + year: toPBAcademicYear(conds.year), + keywords: conds.keywords, + codePrefixesIncluded: conds.codePrefixes.included, + codePrefixesExcluded: conds.codePrefixes.excluded, + schedulesFullyIncluded: toPBSchedules(schedules, []), + schedulesPartiallyOverlapped: [], + offset: conds.offset, + limit: conds.limit, }) .then((res) => res.courses.map(fromPBCourse)) .catch((error) => handleError(error)); @@ -283,13 +377,15 @@ export class TimetableUseCase implements ITimetableUseCase { }) .then((res) => res.registeredCourses.map(fromPBRegisteredCourse)) .then((registeredCourses) => { - if (this.#registeredCourses) { - addElementsInArray( - this.#registeredCourses, - ...deepCopy(registeredCourses) - ); - } - return registeredCourses; + return this.#mutex.registeredCourses.runExclusive(() => { + if (this.#registeredCourses) { + addElementsInArray( + this.#registeredCourses, + ...deepCopy(registeredCourses) + ); + } + return registeredCourses; + }); }) .catch((error) => handleError(error, (connectError: ConnectError) => { @@ -333,13 +429,15 @@ export class TimetableUseCase implements ITimetableUseCase { fromPBRegisteredCourse(assurePresence(res.registeredCourse)) ) .then((registeredCourse) => { - if (this.#registeredCourses) { - addElementsInArray( - this.#registeredCourses, - deepCopy(registeredCourse) - ); - } - return registeredCourse; + return this.#mutex.registeredCourses.runExclusive(() => { + if (this.#registeredCourses) { + addElementsInArray( + this.#registeredCourses, + deepCopy(registeredCourse) + ); + } + return registeredCourse; + }); }) .catch((error) => handleError(error, (connectError: ConnectError) => { @@ -360,7 +458,7 @@ export class TimetableUseCase implements ITimetableUseCase { const result = await this.#getRegisteredCourses(); if (isResultError(result)) { - return Promise.resolve(result); + return result; } let registeredCourses = result; @@ -371,7 +469,7 @@ export class TimetableUseCase implements ITimetableUseCase { ); } - return Promise.resolve(deepCopy(registeredCourses)); + return deepCopy(registeredCourses); } async getRegisteredCourseById( @@ -386,14 +484,14 @@ export class TimetableUseCase implements ITimetableUseCase { const result = await this.#getRegisteredCourses(); if (isResultError(result)) { - return Promise.resolve(result); + return result; } const registeredCourse = result.find( (registeredCourse) => registeredCourse.id === id ); - return Promise.resolve(registeredCourse ?? new NotFoundError()); + return registeredCourse ?? new NotFoundError(); } // TODO @@ -438,13 +536,15 @@ export class TimetableUseCase implements ITimetableUseCase { fromPBRegisteredCourse(assurePresence(res.registeredCourse)) ) .then((registeredCourse) => { - if (this.#registeredCourses) { - updateElementInArray( - this.#registeredCourses, - deepCopy(registeredCourse) - ); - } - return registeredCourse; + return this.#mutex.registeredCourses.runExclusive(() => { + if (this.#registeredCourses) { + updateElementInArray( + this.#registeredCourses, + deepCopy(registeredCourse) + ); + } + return registeredCourse; + }); }) .catch((error) => { return handleError(error, (connectError: ConnectError) => { @@ -473,10 +573,12 @@ export class TimetableUseCase implements ITimetableUseCase { return this.#client .deleteRegisteredCourse({ id: toPBUUID(id) }) .then(() => { - if (this.#registeredCourses) { - deleteElementInArray(this.#registeredCourses, id); - } - return null; + return this.#mutex.registeredCourses.runExclusive(() => { + if (this.#registeredCourses) { + deleteElementInArray(this.#registeredCourses, id); + } + return null; + }); }) .catch((error) => { return handleError(error, (connectError: ConnectError) => { @@ -493,6 +595,41 @@ export class TimetableUseCase implements ITimetableUseCase { }); } + async checkScheduleDuplicate( + year: number, + schedules: Schedule[] + ): Promise { + const result = await this.getRegisteredCourses(year); + if (isResultError(result)) return result; + + const normalSchedules: NormalSchedule[] = schedules.filter( + isNormalSchedule + ); + const registeredNormalSchedules: NormalSchedule[] = result + .map(({ schedules }) => schedules) + .flat() + .filter(isNormalSchedule); + + const timetable: NormalTimetable< + Module, + boolean + > = normalSchedulesToNormalTimetable(normalSchedules); + const registeredTimetable: NormalTimetable< + Module, + boolean + > = normalSchedulesToNormalTimetable(registeredNormalSchedules); + + return !modules.some((module) => + normalDays.some((day) => + periods.some( + (period) => + timetable[module][day][period] && + registeredTimetable[module][day][period] + ) + ) + ); + } + createTag( name: string ): Promise { @@ -500,10 +637,12 @@ export class TimetableUseCase implements ITimetableUseCase { .createTag({ name }) .then((res) => fromPBTag(assurePresence(res.tag))) .then((tag) => { - if (this.#tags) { - addElementsInArray(this.#tags, deepCopy(tag)); - } - return tag; + return this.#mutex.tags.runExclusive(() => { + if (this.#tags) { + addElementsInArray(this.#tags, deepCopy(tag)); + } + return tag; + }); }) .catch((error) => { return handleError(error, (connectError: ConnectError) => { @@ -522,10 +661,10 @@ export class TimetableUseCase implements ITimetableUseCase { const result = await this.#getTags(); if (isResultError(result)) { - return Promise.resolve(result); + return result; } - return Promise.resolve(deepCopy(result)); + return deepCopy(result); } updateTagName( @@ -538,10 +677,12 @@ export class TimetableUseCase implements ITimetableUseCase { .updateTag({ id: toPBUUID(id), name }) .then((res) => fromPBTag(assurePresence(res.tag))) .then((tag) => { - if (this.#tags) { - updateElementInArray(this.#tags, deepCopy(tag)); - } - return tag; + return this.#mutex.tags.runExclusive(() => { + if (this.#tags) { + updateElementInArray(this.#tags, deepCopy(tag)); + } + return tag; + }); }) .catch((error) => { return handleError(error, (connectError: ConnectError) => { @@ -571,10 +712,12 @@ export class TimetableUseCase implements ITimetableUseCase { .rearrangeTags({ ids: ids.map(toPBUUID) }) .then((res) => res.tags.map((tag) => fromPBTag(assurePresence(tag)))) .then((tags) => { - if (this.#tags) { - this.#tags = deepCopy(tags); - } - return tags; + return this.#mutex.tags.runExclusive(() => { + if (this.#tags) { + this.#tags = deepCopy(tags); + } + return tags; + }); }) .catch((error) => { return handleError(error, (connectError: ConnectError) => { @@ -603,10 +746,12 @@ export class TimetableUseCase implements ITimetableUseCase { return this.#client .deleteTag({ id: toPBUUID(id) }) .then(() => { - if (this.#tags) { - deleteElementInArray(this.#tags, id); - } - return null; + return this.#mutex.tags.runExclusive(() => { + if (this.#tags) { + deleteElementInArray(this.#tags, id); + } + return null; + }); }) .catch((error) => { return handleError(error, (connectError: ConnectError) => { diff --git a/yarn.lock b/yarn.lock index 0b097d60..40b61fc5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1151,6 +1151,70 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@bufbuild/buf-darwin-arm64@1.30.0": + version "1.30.0" + resolved "https://registry.yarnpkg.com/@bufbuild/buf-darwin-arm64/-/buf-darwin-arm64-1.30.0.tgz#5fa3aed8af0dadefa9b6e9d14569300d4ca3af73" + integrity sha512-EUhh/hiazZPyf6I0HcQHf/ODZAq2js+PdIbemEWm8WQV29rqhY/stRxKDF755gG5xNr4xwhraQXYTizG3MZV5A== + +"@bufbuild/buf-darwin-x64@1.30.0": + version "1.30.0" + resolved "https://registry.yarnpkg.com/@bufbuild/buf-darwin-x64/-/buf-darwin-x64-1.30.0.tgz#c129ce4b77cfb80a799d8d046c2dd1b51ad00ef7" + integrity sha512-ID9G93hlpvy3KwYqu1UFZCFHaQ7KDESH3OmL33ASpdCojtQbD7EtKCQe8vwIW2raim7lSocSHLyZuLCUAvWoUw== + +"@bufbuild/buf-linux-aarch64@1.30.0": + version "1.30.0" + resolved "https://registry.yarnpkg.com/@bufbuild/buf-linux-aarch64/-/buf-linux-aarch64-1.30.0.tgz#b39ce6b962cea6b6cc16cfd256fa56c47f7bc126" + integrity sha512-VTp5YnfsHtCmRNqGAgzSm18fCTVw8ErK0Lk506CWtqqGhPJ+lWuVkxgAAzG+qoK4LZXIpX9dXxkoqdb+L1NTkg== + +"@bufbuild/buf-linux-x64@1.30.0": + version "1.30.0" + resolved "https://registry.yarnpkg.com/@bufbuild/buf-linux-x64/-/buf-linux-x64-1.30.0.tgz#99a5c7a8cf6a2ca04d91b5fbe7a09f4245221e01" + integrity sha512-XBasj1Baf/lJIiJbX40RDjM8OLS3eVIiilnDqQ7ZL3WOCn6gROYr9lLjlUg9GYox9On5LCFp3536zfr3hNqQwg== + +"@bufbuild/buf-win32-arm64@1.30.0": + version "1.30.0" + resolved "https://registry.yarnpkg.com/@bufbuild/buf-win32-arm64/-/buf-win32-arm64-1.30.0.tgz#d33a6565e351a449401816f5cadd48c88eeafa1b" + integrity sha512-1C18Abq2GpBeGP54VOr9ABzSobynH/q8pUkbY6ftp7JIYdrCvKfJmianEpvTHJWl5kwe3UP9xYYgr5Yt+Y/8Yw== + +"@bufbuild/buf-win32-x64@1.30.0": + version "1.30.0" + resolved "https://registry.yarnpkg.com/@bufbuild/buf-win32-x64/-/buf-win32-x64-1.30.0.tgz#6006e4f0cbe3c36036830fa3b4e3aede0138b8fb" + integrity sha512-uRc05jpbvdUxKSHpUj6RfNR3yJTIdSYccgtupgc6qPoDXE+KveyqjbA0CqIqXhgJmgyggBcYLEbX+tdtz5LRhA== + +"@bufbuild/buf@^1.30.0": + version "1.30.0" + resolved "https://registry.yarnpkg.com/@bufbuild/buf/-/buf-1.30.0.tgz#765a5926bf0d87c2ebfd7fba6552fdfda3096a4b" + integrity sha512-SImlJJA+9uDccWzt6SFXJwRQE/6fGv2cIKyXqDJCWthYMMYw9/QOdM/pXg5JZx1XJrHiEvzZ0y+ump7Q08/FPw== + optionalDependencies: + "@bufbuild/buf-darwin-arm64" "1.30.0" + "@bufbuild/buf-darwin-x64" "1.30.0" + "@bufbuild/buf-linux-aarch64" "1.30.0" + "@bufbuild/buf-linux-x64" "1.30.0" + "@bufbuild/buf-win32-arm64" "1.30.0" + "@bufbuild/buf-win32-x64" "1.30.0" + +"@bufbuild/protobuf@1.8.0", "@bufbuild/protobuf@^1.7.2", "@bufbuild/protobuf@^1.8.0": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@bufbuild/protobuf/-/protobuf-1.8.0.tgz#1c8651ea34adb8019b483e09de02aeeb1cd57d79" + integrity sha512-qR9FwI8QKIveDnUYutvfzbC21UZJJryYrLuZGjeZ/VGz+vXelUkK+xgkOHsvPEdYEdxtgUUq4313N8QtOehJ1Q== + +"@bufbuild/protoc-gen-es@^1.8.0": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@bufbuild/protoc-gen-es/-/protoc-gen-es-1.8.0.tgz#b16cca884ccba23f1401104ca002028635a2b169" + integrity sha512-jnvBKwHq3o/iOgfKxaxn5Za7ay4oAs8KWgoHiDc9Fsb0g+/d1z+mHlHvmevOiCPcVZsnH6V3LImOJvGStPONpA== + dependencies: + "@bufbuild/protobuf" "^1.8.0" + "@bufbuild/protoplugin" "1.8.0" + +"@bufbuild/protoplugin@1.8.0", "@bufbuild/protoplugin@^1.7.2": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@bufbuild/protoplugin/-/protoplugin-1.8.0.tgz#0e60a79d195ad79d014c263ae9c57f64063d23a5" + integrity sha512-Pb89cTshW+I577qh27VvxGYvZEvQ3zJ8La1OfzPCKugP9d4A4P65WStkAY+aSCiDHk68m1/+mtBb6elfiLPuFg== + dependencies: + "@bufbuild/protobuf" "1.8.0" + "@typescript/vfs" "^1.4.0" + typescript "4.5.2" + "@cnakazawa/watch@^1.0.3": version "1.0.4" resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" @@ -1164,6 +1228,24 @@ resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== +"@connectrpc/connect-web@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@connectrpc/connect-web/-/connect-web-1.4.0.tgz#4f46a6728251244e9fc837bcdbf0911b0b0def15" + integrity sha512-13aO4psFbbm7rdOFGV0De2Za64DY/acMspgloDlcOKzLPPs0yZkhp1OOzAQeiAIr7BM/VOHIA3p8mF0inxCYTA== + +"@connectrpc/connect@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@connectrpc/connect/-/connect-1.4.0.tgz#4a987d6c9fc78ea61bce7d19e27b2de4d14c658f" + integrity sha512-vZeOkKaAjyV4+RH3+rJZIfDFJAfr+7fyYr6sLDKbYX3uuTVszhFe9/YKf5DNqrDb5cKdKVlYkGn6DTDqMitAnA== + +"@connectrpc/protoc-gen-connect-es@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@connectrpc/protoc-gen-connect-es/-/protoc-gen-connect-es-1.4.0.tgz#428d971bcf2c6b4241058cdf8f98a68723db616f" + integrity sha512-/7vQ8Q7mEBhV8qEVh/eifRQlQnf8EJ6weMwCD2DljVAQRlZYcW9SLxjYZhV1uM1ZZqQC7Cw2vvgXRg2XQswHBg== + dependencies: + "@bufbuild/protobuf" "^1.7.2" + "@bufbuild/protoplugin" "^1.7.2" + "@cspotcode/source-map-support@^0.8.0": version "0.8.1" resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" @@ -3495,6 +3577,13 @@ "@typescript-eslint/types" "5.52.0" eslint-visitor-keys "^3.3.0" +"@typescript/vfs@^1.4.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@typescript/vfs/-/vfs-1.5.0.tgz#ed942922724f9ace8c07c80b006c47e5e3833218" + integrity sha512-AJS307bPgbsZZ9ggCT3wwpg3VbTKMFNHfaY/uF0ahSkYYrPF2dSSKDNIDIQAHm9qJqbLvCsSJH7yN4Vs/CsMMg== + dependencies: + debug "^4.1.1" + "@unhead/dom@^1.0.20": version "1.0.20" resolved "https://registry.yarnpkg.com/@unhead/dom/-/dom-1.0.20.tgz#bb15e893ed2242d79e7f8e85ca55fc4556c00d29" @@ -4482,6 +4571,13 @@ async-each@^1.0.1: resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.5.tgz#6eea184b2df0ec09f3deebe165c97c85c911d7b8" integrity sha512-5QzqtU3BlagehwmdoqwaS2FBQF2P5eL6vFqXwNsb5jwoEsmtfAXg1ocFvW7I6/gGLFhBMKwcMwZuy7uv/Bo9jA== +async-mutex@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.5.0.tgz#353c69a0b9e75250971a64ac203b0ebfddd75482" + integrity sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA== + dependencies: + tslib "^2.4.0" + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -12438,6 +12534,11 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== +typescript@4.5.2: + version "4.5.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.2.tgz#8ac1fba9f52256fdb06fb89e4122fa6a346c2998" + integrity sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw== + typescript@^4.6.2: version "4.9.5" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a"