From 9b654412b0ddcc70b44202146ecfcf87687562a6 Mon Sep 17 00:00:00 2001 From: Eddy Chen <89349085+ecxyzzy@users.noreply.github.com> Date: Fri, 8 Sep 2023 14:42:21 -0700 Subject: [PATCH] =?UTF-8?q?feat:=20=E2=9C=A8=20migrate=20graphql=20endpoin?= =?UTF-8?q?t=20(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Aponia --- apps/api/bronya.config.ts | 42 +- apps/api/package.json | 9 +- apps/api/src/routes/v1/graphql/+endpoint.ts | 85 ++++ apps/api/src/routes/v1/graphql/lib.ts | 70 +++ apps/api/src/routes/v1/graphql/resolvers.ts | 23 + .../src/routes/v1/graphql/schema/base.graphql | 8 + .../routes/v1/graphql/schema/calendar.graphql | 16 + .../routes/v1/graphql/schema/courses.graphql | 87 ++++ .../src/routes/v1/graphql/schema/enum.graphql | 70 +++ .../routes/v1/graphql/schema/grades.graphql | 129 +++++ .../v1/graphql/schema/instructors.graphql | 49 ++ .../src/routes/v1/graphql/schema/larc.graphql | 32 ++ .../routes/v1/graphql/schema/websoc.graphql | 184 +++++++ .../src/routes/v1/graphql/schema/week.graphql | 20 + pnpm-lock.yaml | 448 +++++++++++++++++- 15 files changed, 1240 insertions(+), 32 deletions(-) create mode 100644 apps/api/src/routes/v1/graphql/+endpoint.ts create mode 100644 apps/api/src/routes/v1/graphql/lib.ts create mode 100644 apps/api/src/routes/v1/graphql/resolvers.ts create mode 100644 apps/api/src/routes/v1/graphql/schema/base.graphql create mode 100644 apps/api/src/routes/v1/graphql/schema/calendar.graphql create mode 100644 apps/api/src/routes/v1/graphql/schema/courses.graphql create mode 100644 apps/api/src/routes/v1/graphql/schema/enum.graphql create mode 100644 apps/api/src/routes/v1/graphql/schema/grades.graphql create mode 100644 apps/api/src/routes/v1/graphql/schema/instructors.graphql create mode 100644 apps/api/src/routes/v1/graphql/schema/larc.graphql create mode 100644 apps/api/src/routes/v1/graphql/schema/websoc.graphql create mode 100644 apps/api/src/routes/v1/graphql/schema/week.graphql diff --git a/apps/api/bronya.config.ts b/apps/api/bronya.config.ts index a2ebb58b..76c3ac41 100644 --- a/apps/api/bronya.config.ts +++ b/apps/api/bronya.config.ts @@ -9,15 +9,17 @@ import { createApiCliPlugins } from "@bronya.js/api-construct/plugins/cli"; import { logger } from "@libs/lambda"; /** - * @see https://github.com/evanw/esbuild/issues/1921#issuecomment-1491470829 + * @see https://github.com/evanw/esbuild/issues/1921#issuecomment-1623640043 */ -const js = `\ -import * as path from 'path'; -import { fileURLToPath } from 'url'; -import { createRequire as topLevelCreateRequire } from 'module'; -const require = topLevelCreateRequire(import.meta.url); -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); +// language=JavaScript +const js = ` + import topLevelModule from "node:module"; + import topLevelUrl from "node:url"; + import topLevelPath from "node:path"; + + const require = topLevelModule.createRequire(import.meta.url); + const __filename = topLevelUrl.fileURLToPath(import.meta.url); + const __dirname = topLevelPath.dirname(__filename); `; const projectRoot = process.cwd(); @@ -80,19 +82,33 @@ class MyStack extends Stack { }); }, }, - environment: { - DATABASE_URL: process.env["DATABASE_URL"] ?? "", - SHADOW_DATABASE_URL: process.env["SHADOW_DATABASE_URL"] ?? "", - }, + environment: { DATABASE_URL: process.env["DATABASE_URL"] ?? "" }, esbuild: { format: "esm", platform: "node", bundle: true, + minify: true, banner: { js }, outExtension: { ".js": ".mjs" }, plugins: [ { - name: "copy", + name: "copy-graphql-schema", + setup(build) { + build.onStart(async () => { + if (!build.initialOptions.outdir?.endsWith("graphql")) return; + + fs.mkdirSync(build.initialOptions.outdir, { recursive: true }); + + fs.cpSync( + path.resolve(projectRoot, "src/routes/v1/graphql/schema"), + path.join(build.initialOptions.outdir, "schema"), + { recursive: true }, + ); + }); + }, + }, + { + name: "copy-prisma", setup(build) { build.onStart(async () => { const outDirectory = build.initialOptions.outdir ?? projectRoot; diff --git a/apps/api/package.json b/apps/api/package.json index d319392b..b0c2c598 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -21,7 +21,11 @@ "dev": "bunny dev-api" }, "dependencies": { + "@apollo/server": "4.9.2", "@aws-sdk/client-lambda": "3.398.0", + "@graphql-tools/load-files": "7.0.0", + "@graphql-tools/merge": "9.0.0", + "@graphql-tools/utils": "10.0.5", "@libs/db": "workspace:^", "@libs/lambda": "workspace:^", "@libs/uc-irvine-api": "workspace:^", @@ -31,11 +35,12 @@ "aws-cdk-lib": "2.93.0", "cheerio": "1.0.0-rc.12", "cross-fetch": "4.0.0", + "graphql": "16.8.0", "zod": "3.22.2" }, "devDependencies": { - "@bronya.js/api-construct": "0.10.8", - "@bronya.js/core": "0.10.8", + "@bronya.js/api-construct": "0.10.9", + "@bronya.js/core": "0.10.9", "@types/aws-lambda": "8.10.119", "aws-cdk": "2.93.0", "dotenv": "16.3.1", diff --git a/apps/api/src/routes/v1/graphql/+endpoint.ts b/apps/api/src/routes/v1/graphql/+endpoint.ts new file mode 100644 index 00000000..61f98399 --- /dev/null +++ b/apps/api/src/routes/v1/graphql/+endpoint.ts @@ -0,0 +1,85 @@ +import { join } from "node:path"; +import { parse } from "node:url"; + +import { ApolloServer, HeaderMap, HTTPGraphQLRequest } from "@apollo/server"; +import { + ApolloServerPluginLandingPageLocalDefault, + ApolloServerPluginLandingPageProductionDefault, +} from "@apollo/server/plugin/landingPage/default"; +import { loadFilesSync } from "@graphql-tools/load-files"; +import { mergeTypeDefs } from "@graphql-tools/merge"; +import { compress } from "@libs/lambda"; +import type { APIGatewayProxyHandler, APIGatewayProxyResult } from "aws-lambda"; + +import { transformBody } from "./lib"; +import { resolvers } from "./resolvers"; + +const responseHeaders: APIGatewayProxyResult["headers"] = { + "Access-Control-Allow-Headers": "Apollo-Require-Preflight, Content-Type", + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, POST, OPTIONS", +}; + +const graphqlServer = new ApolloServer({ + introspection: true, + plugins: [ + process.env.NODE_ENV === "development" + ? ApolloServerPluginLandingPageLocalDefault() + : ApolloServerPluginLandingPageProductionDefault({ footer: false }), + ], + resolvers, + typeDefs: mergeTypeDefs(loadFilesSync(join(__dirname, "schema/*.graphql"))), +}); + +export const ANY: APIGatewayProxyHandler = async (event) => { + const { body, headers: eventHeaders, httpMethod: method } = event; + + try { + graphqlServer.assertStarted(""); + } catch { + await graphqlServer.start(); + } + + let headers: HeaderMap; + const req: HTTPGraphQLRequest = { + body, + get headers() { + headers ??= Object.entries(eventHeaders).reduce( + (m, [k, v]) => m.set(k, Array.isArray(v) ? v.join(", ") : v ?? ""), + new HeaderMap(), + ); + return headers; + }, + method, + search: parse(event.path ?? "").search ?? "", + }; + const res = await graphqlServer.executeHTTPGraphQLRequest({ + httpGraphQLRequest: req, + context: async () => ({}), + }); + + const statusCode = res.status ?? 200; + res.headers.forEach((v, k) => (responseHeaders[k] = v)); + + try { + const { body, method } = compress( + await transformBody(res.body), + req.headers.get("accept-encoding"), + ); + if (method) { + responseHeaders["content-encoding"] = method; + } + return { + body, + headers: responseHeaders, + isBase64Encoded: !!method, + statusCode, + }; + } catch { + return { + body: "", + headers: responseHeaders, + statusCode: 500, + }; + } +}; diff --git a/apps/api/src/routes/v1/graphql/lib.ts b/apps/api/src/routes/v1/graphql/lib.ts new file mode 100644 index 00000000..a643400a --- /dev/null +++ b/apps/api/src/routes/v1/graphql/lib.ts @@ -0,0 +1,70 @@ +import type { BaseContext, HTTPGraphQLResponse } from "@apollo/server"; +import type { IFieldResolver } from "@graphql-tools/utils"; +import { isErrorResponse } from "@peterportal-api/types"; +import type { RawResponse } from "@peterportal-api/types"; +import { GraphQLError } from "graphql/error"; + +function getBaseUrl() { + switch (process.env.NODE_ENV) { + case "production": + return `https://api-next.peterportal.org`; + case "staging": + return `https://${process.env.STAGE}.api-next.peterportal.org`; + default: + return `http://localhost:${process.env.API_PORT ?? 8080}`; + } +} + +export async function transformBody(body: HTTPGraphQLResponse["body"]): Promise { + if (body.kind === "complete") { + return body.string; + } + let transformedBody = ""; + for await (const chunk of body.asyncIterator) { + transformedBody += chunk; + } + return transformedBody; +} + +export function geTransform(args: Record) { + if (args.ge) return { ...args, ge: args.ge.replace("_", "-") }; + delete args.ge; + return args; +} + +export const proxyRestApi = + ( + route: string, + proxyArgs?: { + argsTransform?: (args: Record) => Record; + pathArg?: string; + }, + ): IFieldResolver => + async (_source, args, _context, _info) => { + const { argsTransform = (args: Record) => args, pathArg } = proxyArgs ?? {}; + const urlSearchParams = new URLSearchParams(argsTransform(args)); + const query = urlSearchParams.toString(); + + const data: RawResponse = await fetch( + pathArg + ? `${getBaseUrl()}${route}/${args[pathArg]}` + : `${getBaseUrl()}${route}${query ? "?" + query : ""}`, + ) + .then((res) => res.json()) + .catch((err) => { + return { + error: `INTERNAL_SERVER_ERROR: ${err.message}`, + message: err.message, + }; + }); + + if (isErrorResponse(data)) { + throw new GraphQLError(data.message, { + extensions: { + code: data.error.toUpperCase().replace(" ", "_"), + }, + }); + } + + return data.payload; + }; diff --git a/apps/api/src/routes/v1/graphql/resolvers.ts b/apps/api/src/routes/v1/graphql/resolvers.ts new file mode 100644 index 00000000..bb0caec7 --- /dev/null +++ b/apps/api/src/routes/v1/graphql/resolvers.ts @@ -0,0 +1,23 @@ +import type { ApolloServerOptions, BaseContext } from "@apollo/server"; + +import { geTransform, proxyRestApi } from "./lib"; + +export const resolvers: ApolloServerOptions["resolvers"] = { + Query: { + calendar: proxyRestApi("/v1/rest/calendar"), + course: proxyRestApi("/v1/rest/courses", { pathArg: "courseId" }), + courses: proxyRestApi("/v1/rest/courses", { argsTransform: geTransform }), + allCourses: proxyRestApi("/v1/rest/courses/all"), + rawGrades: proxyRestApi("/v1/rest/grades/raw"), + aggregateGrades: proxyRestApi("/v1/rest/grades/aggregate"), + gradesOptions: proxyRestApi("/v1/rest/grades/options"), + instructor: proxyRestApi("/v1/rest/instructors", { pathArg: "courseId" }), + instructors: proxyRestApi("/v1/rest/instructors"), + allInstructors: proxyRestApi("/v1/rest/instructors/all"), + larc: proxyRestApi("/v1/rest/larc"), + websoc: proxyRestApi("/v1/rest/websoc", { argsTransform: geTransform }), + depts: proxyRestApi("/v1/rest/websoc/depts"), + terms: proxyRestApi("/v1/rest/websoc/terms"), + week: proxyRestApi("/v1/rest/week"), + }, +}; diff --git a/apps/api/src/routes/v1/graphql/schema/base.graphql b/apps/api/src/routes/v1/graphql/schema/base.graphql new file mode 100644 index 00000000..812e0035 --- /dev/null +++ b/apps/api/src/routes/v1/graphql/schema/base.graphql @@ -0,0 +1,8 @@ +"A JavaScript Date object returned as a string." +scalar Date +"A JavaScript object." +scalar JSON + +type Query { + _empty: String +} diff --git a/apps/api/src/routes/v1/graphql/schema/calendar.graphql b/apps/api/src/routes/v1/graphql/schema/calendar.graphql new file mode 100644 index 00000000..0cd39563 --- /dev/null +++ b/apps/api/src/routes/v1/graphql/schema/calendar.graphql @@ -0,0 +1,16 @@ +"An object that includes important dates for a specified quarter." +type QuarterDates { + "When instruction begins for the given quarter." + instructionStart: Date! + "When instruction ends for the given quarter." + instructionEnd: Date! + "When finals begin for the given quarter." + finalsStart: Date! + "When finals end for the given quarter." + finalsEnd: Date! +} + +extend type Query { + "Get important dates for a quarter." + calendar(year: String!, quarter: Quarter!): QuarterDates! +} diff --git a/apps/api/src/routes/v1/graphql/schema/courses.graphql b/apps/api/src/routes/v1/graphql/schema/courses.graphql new file mode 100644 index 00000000..f7584eec --- /dev/null +++ b/apps/api/src/routes/v1/graphql/schema/courses.graphql @@ -0,0 +1,87 @@ +"An object that represents a course." +type Course { + "The course ID." + id: String! + "The department code that the course belongs to." + department: String! + "The course number of the course." + courseNumber: String! + "The numeric part of the course number." + courseNumeric: Int! + "The school that the course belongs to." + school: String! + "The title of the course." + title: String! + "The level of the course." + courseLevel: String! + "The minimum number of units that can be earned by taking the course." + minUnits: Float! + "The maximum number of units that can be earned by taking the course." + maxUnits: Float! + "The course description." + description: String! + "The name of the department that the course belongs to." + departmentName: String! + "The UCINetIDs of all instructors who have taught this course in the past." + instructorHistory: [String!]! + "The prerequisite tree object for the course." + prerequisiteTree: JSON! + "The list of prerequisites for the course." + prerequisiteList: [String!]! + "The catalogue's prerequisite text for the course." + prerequisiteText: String! + "The courses for which this course is a prerequisite." + prerequisiteFor: [String!]! + "The repeat policy for this course." + repeatability: String! + "The grading option(s) available for this course." + gradingOption: String! + "The course(s) with which this course is concurrent." + concurrent: String! + "The course(s) that are the same as this course." + sameAs: String! + "The enrollment restriction(s) placed on this course." + restriction: String! + "The course(s) with which this course overlaps." + overlap: String! + "The corequisites for this course." + corequisites: String! + "The list of GE categories that this course fulfills." + geList: [String!]! + "The catalogue's GE text for this course." + geText: String! + "The list of terms in which this course was offered." + terms: [String!]! +} + +extend type Query { + "Get the course with the given ID." + course(courseId: String!): Course! + "Get courses that match the given constraints." + courses( + "The department the courses are in." + department: String + "The course number of the courses." + courseNumber: String + "The numeric part of the course number." + courseNumeric: Int + "A substring of the courses' titles." + titleContains: String + "The level of the courses." + courseLevel: Division + "The minimum units of the courses." + minUnits: Float + "The maximum units of the courses." + maxUnits: Float + "A substring of the courses' descriptions." + descriptionContains: String + "A comma-separated list of all instructors that may have taught these courses." + taughtByInstructors: String + "The GE category that the courses fulfill." + geCategory: GE + "A comma-separated list of all terms that the courses may have been taught in." + taughtInTerms: String + ): [Course!]! + "Get all courses." + allCourses: [Course!]! +} diff --git a/apps/api/src/routes/v1/graphql/schema/enum.graphql b/apps/api/src/routes/v1/graphql/schema/enum.graphql new file mode 100644 index 00000000..2bbeecfa --- /dev/null +++ b/apps/api/src/routes/v1/graphql/schema/enum.graphql @@ -0,0 +1,70 @@ +"The set of valid quarters." +enum Quarter { + Fall + Winter + Spring + Summer1 + Summer10wk + Summer2 +} +"The set of valid GE category codes." +enum GE { + GE_1A + GE_1B + GE_2 + GE_3 + GE_4 + GE_5A + GE_5B + GE_6 + GE_7 +} +"The set of valid course levels." +enum Division { + ANY + LowerDiv + UpperDiv + Graduate +} +"The set of valid section types." +enum SectionType { + ANY + Act + Col + Dis + Fld + Lab + Lec + Qiz + Res + Sem + Stu + Tap + Tut +} +"The set of valid options for filtering full courses." +enum FullCourses { + ANY + SkipFullWaitlist + FullOnly + OverEnrolled +} +"The set of valid options for filtering cancelled courses." +enum CancelledCourses { + Exclude + Include + Only +} +"The set of valid enrollment statuses." +enum EnrollmentStatus { + OPEN + Waitl + FULL + NewOnly +} +"The set of valid final exam statuses for a section." +enum WebsocSectionFinalExamStatus { + NO_FINAL + TBA_FINAL + SCHEDULED_FINAL +} diff --git a/apps/api/src/routes/v1/graphql/schema/grades.graphql b/apps/api/src/routes/v1/graphql/schema/grades.graphql new file mode 100644 index 00000000..4f6089a6 --- /dev/null +++ b/apps/api/src/routes/v1/graphql/schema/grades.graphql @@ -0,0 +1,129 @@ +"A section which has grades data associated with it." +type GradesSection { + "The year the section was offered." + year: String! + "The quarter the section was offered." + quarter: Quarter! + "The section code of the section." + sectionCode: String! + "The department code." + department: String! + "The course number the section belongs to." + courseNumber: String! + "The numeric part of the course number." + courseNumeric: Int! + "The shortened name(s) of the instructor(s) who taught the section." + instructors: [String!]! +} +"The distribution of grades within a section or among all queried sections." +type GradeDistribution { + "How many students attained an A+/A/A-." + gradeACount: Int! + "How many students attained a B+/B/B-." + gradeBCount: Int! + "How many students attained a C+/C/C-." + gradeCCount: Int! + "How many students attained a D+/D/D-." + gradeDCount: Int! + "How many students attained an F." + gradeFCount: Int! + "How many students attained a P." + gradePCount: Int! + "How many students attained an NP." + gradeNPCount: Int! + "How many students attained a W." + gradeWCount: Int! + "The average GPA of all assigned grades in the object." + averageGPA: Float! +} +"An object that represents raw grades statistics for a section." +type RawGrade { + "The year the section was offered." + year: String! + "The quarter the section was offered." + quarter: Quarter! + "The section code of the section." + sectionCode: String! + "The department code." + department: String! + "The course number the section belongs to." + courseNumber: String! + "The numeric part of the course number." + courseNumeric: Int! + "How many students attained an A+/A/A-." + gradeACount: Int! + "How many students attained a B+/B/B-." + gradeBCount: Int! + "How many students attained a C+/C/C-." + gradeCCount: Int! + "How many students attained a D+/D/D-." + gradeDCount: Int! + "How many students attained an F." + gradeFCount: Int! + "How many students attained a P." + gradePCount: Int! + "How many students attained an NP." + gradeNPCount: Int! + "How many students attained a W." + gradeWCount: Int! + "The average GPA of all assigned grades in the section." + averageGPA: Float! + "The shortened name(s) of the instructor(s) who taught the section." + instructors: [String!]! +} +"An object that represents aggregate grades statistics for a given query." +type AggregateGrades { + "The list of sections in the query." + sectionList: [GradesSection!]! + "The combined grades distribution of all sections in the query." + gradeDistribution: GradeDistribution +} +"The lists of options that matched the given filters." +type GradesOptions { + "The list of years that matched the given filters." + years: [String!]! + "The list of departments that matched the given filters." + departments: [String!]! + "The list of course numbers that matched the given filters." + courseNumbers: [String!]! + "The list of section codes that matched the given filters." + sectionCodes: [String!]! + "The list of instructors that matched the given filters." + instructors: [String!]! +} + +extend type Query { + "Get the raw grade info for the given parameters." + rawGrades( + year: String + quarter: Quarter + instructor: String + department: String + courseNumber: String + sectionCode: String + division: Division + excludePNP: Boolean + ): [RawGrade!]! + "Get the aggregate grade info for the given parameters." + aggregateGrades( + year: String + quarter: Quarter + instructor: String + department: String + courseNumber: String + sectionCode: String + division: Division + excludePNP: Boolean + ): AggregateGrades! + "Get the available options for the given constraints." + gradesOptions( + year: String + quarter: Quarter + instructor: String + department: String + courseNumber: String + sectionCode: String + division: Division + excludePNP: Boolean + ): GradesOptions! +} diff --git a/apps/api/src/routes/v1/graphql/schema/instructors.graphql b/apps/api/src/routes/v1/graphql/schema/instructors.graphql new file mode 100644 index 00000000..0bb3a637 --- /dev/null +++ b/apps/api/src/routes/v1/graphql/schema/instructors.graphql @@ -0,0 +1,49 @@ +"An object representing an instructor." +type Instructor { + "The instructor's UCINetID." + ucinetid: String! + "The full name of the instructor." + name: String! + "The shortened name (or WebSoc name; e.g. ``SHINDLER, M.``) of the instructor." + shortenedName: String! + "The instructor's title." + title: String! + "The instructor's email address." + email: String! + "The department to which the instructor belongs." + department: String! + "The school(s) associated with the instructor." + schools: [String!]! + "The department(s) related to the instructor." + relatedDepartments: [String!]! + """ + Course(s) this instructor has taught in the past. + Keys are properly spaced course numbers; values are the term(s) in which + the instructor taught the corresponding course. + """ + courseHistory: JSON! +} + +extend type Query { + "Get the instructor with the corresponding UCInetID." + instructor(ucinetid: String!): Instructor! + "Get instructors that match the given constraints." + instructors( + "A substring of the instructors' full names." + nameContains: String + "The shortened/WebSoc name of the instructor (e.g., `SHINDLER, M.`)." + shortenedName: String + "A substring of the instructors' title." + titleContains: String + "A substring of the instructors' department." + departmentContains: String + "A comma-separated list of all schools that the instructors may be affiliated with." + schoolsContains: String + "A comma-separated list of all departments that the instructors may be affiliated with." + relatedDepartmentsContains: String + "A comma-separated list of all terms in which the instructors may have taught." + taughtInTerms: String + ): [Instructor!]! + "Get all instructors." + allInstructors: [Instructor!]! +} diff --git a/apps/api/src/routes/v1/graphql/schema/larc.graphql b/apps/api/src/routes/v1/graphql/schema/larc.graphql new file mode 100644 index 00000000..fd29e429 --- /dev/null +++ b/apps/api/src/routes/v1/graphql/schema/larc.graphql @@ -0,0 +1,32 @@ +"The course info for the set of LARC sections." +type LarcCourseInfo { + "The course number." + courseNumber: String! + "The course number that the number above is the same as, if any." + sameAs: String + "The name of the course." + courseName: String! +} +"One of the LARC sections for a course." +type LarcSection { + "The days the section will meet." + days: String! + "The time at which the section will meet." + time: String! + "The instructor of the section." + instructor: String! + "The classroom where the section will meet." + bldg: String! +} +"A set of LARC sections for a single course." +type LarcCourse { + "The course info for the set of LARC sections." + courseInfo: LarcCourseInfo! + "The sections associated with this set." + sections: [LarcSection!]! +} + +extend type Query { + "Get the available LARC sections for the given terms." + larc(year: String!, quarter: Quarter!): [LarcCourse!]! +} diff --git a/apps/api/src/routes/v1/graphql/schema/websoc.graphql b/apps/api/src/routes/v1/graphql/schema/websoc.graphql new file mode 100644 index 00000000..bc211dad --- /dev/null +++ b/apps/api/src/routes/v1/graphql/schema/websoc.graphql @@ -0,0 +1,184 @@ +"A type that represents the hour and minute parts of a time." +type HourMinute { + "The hour (0-23)." + hour: Int! + "The minute (0-59)." + minute: Int! +} +"The meeting information for a section." +type WebsocSectionMeeting { + """ + Whether the meeting time is TBA. + + If this field is `false`, then `days`, `startTime`, and `endTime` + are **guaranteed** to be non-null; otherwise, they are **guaranteed** to be null. + """ + timeIsTBA: Boolean! + "The classroom(s) the section meets in." + bldg: [String!]! + "What day(s) the section meets on (e.g. ``MWF``)." + days: String + "The time at which the section begins." + startTime: HourMinute + "The time at which the section concludes." + endTime: HourMinute +} +"The enrollment statistics for a section." +type WebsocSectionEnrollment { + "The total number of students enrolled in this section." + totalEnrolled: String! + """ + The number of students enrolled in the section referred to by this section + code, if the section is cross-listed. If the section is not cross-listed, + this field is the empty string. + """ + sectionEnrolled: String! +} +"The final exam data for a section." +type WebsocSectionFinalExam { + """ + The status of the exam. + + If this field is `SCHEDULED_FINAL`, then all other fields are + **guaranteed** to be non-null; otherwise, they are **guaranteed** to be null. + """ + examStatus: WebsocSectionFinalExamStatus! + "The month in which the final exam takes place." + month: Int + "The day of the month in which the final exam takes place." + day: Int + "When the final exam starts." + startTime: HourMinute + "When the final exam ends." + endTime: HourMinute + "Where the final exam takes place." + bldg: String +} +"A WebSoc section object." +type WebsocSection { + "The section code." + sectionCode: String! + "The section type (e.g. ``Lec``, ``Dis``, ``Lab``, etc.)" + sectionType: String! + "The section number (e.g. ``A1``)." + sectionNum: String! + "The number of units afforded by taking this section." + units: String! + "The name(s) of the instructor(s) teaching this section." + instructors: [String!]! + "The meeting time(s) of this section." + meetings: [WebsocSectionMeeting!]! + "The date and time of the final exam for this section." + finalExam: WebsocSectionFinalExam! + "The maximum capacity of this section." + maxCapacity: String! + """ + The number of students currently enrolled (cross-listed or otherwise) in + this section. + """ + numCurrentlyEnrolled: WebsocSectionEnrollment! + "The number of students currently on the waitlist for this section." + numOnWaitlist: String! + "The maximum number of students that can be on the waitlist for this section." + numWaitlistCap: String! + "The number of students who have requested to be enrolled in this section." + numRequested: String! + "The number of seats in this section reserved for new students." + numNewOnlyReserved: String! + "The restriction code(s) for this section." + restrictions: String! + "The enrollment status." + status: EnrollmentStatus! + "Any comments for the section." + sectionComment: String! +} +"A WebSoc course object." +type WebsocCourse { + "The code of the department the course belongs to." + deptCode: String! + "The course number." + courseNumber: String! + "The title of the course." + courseTitle: String! + "Any comments for the course." + courseComment: String! + "The link to the WebReg Course Prerequisites page for this course." + prerequisiteLink: String! + "All sections of the course." + sections: [WebsocSection!]! +} +"A WebSoc department object." +type WebsocDepartment { + "The name of the department." + deptName: String! + "The department code." + deptCode: String! + "Any comments from the department." + deptComment: String! + "All courses of the department." + courses: [WebsocCourse!]! + "Any comments for section code(s) under the department." + sectionCodeRangeComments: [String!]! + "Any comments for course number(s) under the department." + courseNumberRangeComments: [String!]! +} +"A WebSoc school object." +type WebsocSchool { + "The name of the school." + schoolName: String! + "Any comments from the school." + schoolComment: String! + "All departments of the school." + departments: [WebsocDepartment!]! +} +type WebsocAPIResponse { + schools: [WebsocSchool!]! +} +type Department { + "A string containing the department code and name (e.g. `COMPSCI: Computer Science`)." + deptLabel: String! + "The department code." + deptValue: String! +} +"An object that contains information on a term." +type TermData { + """ + The short name of the term. (e.g. `2023 Summer1`) + """ + shortName: String! + """ + The full name of the term. (`2023 Summer Session 1`) + """ + longName: String! +} +extend type Query { + "Perform a WebSoc query." + websoc( + year: String! + quarter: Quarter! + cache: Boolean + cacheOnly: Boolean + includeCoCourses: Boolean + ge: GE + department: String + sectionCodes: String + instructorName: String + building: String + room: String + division: Division + courseNumber: String + courseTitle: String + sectionType: SectionType + units: String + days: String + startTime: String + endTime: String + maxCapacity: String + fullCourses: FullCourses + cancelledCourses: CancelledCourses + ): WebsocAPIResponse! + "Get data on all available departments." + depts: [Department!]! + "Get data on all available terms." + terms: [TermData!]! +} diff --git a/apps/api/src/routes/v1/graphql/schema/week.graphql b/apps/api/src/routes/v1/graphql/schema/week.graphql new file mode 100644 index 00000000..b609a1b6 --- /dev/null +++ b/apps/api/src/routes/v1/graphql/schema/week.graphql @@ -0,0 +1,20 @@ +type WeekData { + """ + The week number(s) of the term(s) in session. + If a term is in finals, then that term's week number will be -1. + If there are no terms in session, then this will be equal to `[-1]`. + """ + weeks: [Int!]! + """ + The name of the term(s) the week is in. + If there are no terms in session, then this will be equal to `["N/A"]`. + """ + quarters: [String!]! + "The display string for the given week." + display: String! +} + +extend type Query { + "Fetch the `WeekData` for the given date. If all fields are empty, returns data for today." + week(year: Int, month: Int, day: Int): WeekData! +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4af494d6..616bb077 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -83,9 +83,21 @@ importers: apps/api: dependencies: + '@apollo/server': + specifier: 4.9.2 + version: 4.9.2(graphql@16.8.0) '@aws-sdk/client-lambda': specifier: 3.398.0 version: 3.398.0 + '@graphql-tools/load-files': + specifier: 7.0.0 + version: 7.0.0(graphql@16.8.0) + '@graphql-tools/merge': + specifier: 9.0.0 + version: 9.0.0(graphql@16.8.0) + '@graphql-tools/utils': + specifier: 10.0.5 + version: 10.0.5(graphql@16.8.0) '@libs/db': specifier: workspace:^ version: link:../../libs/db @@ -113,16 +125,19 @@ importers: cross-fetch: specifier: 4.0.0 version: 4.0.0 + graphql: + specifier: 16.8.0 + version: 16.8.0 zod: specifier: 3.22.2 version: 3.22.2 devDependencies: '@bronya.js/api-construct': - specifier: 0.10.8 - version: 0.10.8 + specifier: 0.10.9 + version: 0.10.9 '@bronya.js/core': - specifier: 0.10.8 - version: 0.10.8 + specifier: 0.10.9 + version: 0.10.9 '@types/aws-lambda': specifier: 8.10.119 version: 8.10.119 @@ -542,6 +557,33 @@ packages: resolution: {integrity: sha512-w7mMvo4meCeZ9uPo33Q4BmwOveZ9AcLlnjxmO/5pMzQ9foClkF8YXMv44taSI9jaEZ5b5JmD1dgutr3HhBao/w==} dev: false + /@apollo/cache-control-types@1.0.3(graphql@16.8.0): + resolution: {integrity: sha512-F17/vCp7QVwom9eG7ToauIKdAxpSoadsJnqIfyryLFSkLSOEqu+eC5Z3N8OXcUVStuOMcNHlyraRsA6rRICu4g==} + peerDependencies: + graphql: 14.x || 15.x || 16.x + dependencies: + graphql: 16.8.0 + dev: false + + /@apollo/protobufjs@1.2.7: + resolution: {integrity: sha512-Lahx5zntHPZia35myYDBRuF58tlwPskwHc5CWBZC/4bMKB6siTBWwtMrkqXcsNwQiFSzSx5hKdRPUmemrEp3Gg==} + hasBin: true + requiresBuild: true + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/long': 4.0.2 + long: 4.0.0 + dev: false + /@apollo/sandbox@2.5.0(graphql@16.8.0)(react-dom@17.0.2)(react@17.0.2): resolution: {integrity: sha512-AmAzOj3em/UhFxdjJuF3ohJ7OOw9/rO/V3hrmETxVetc51fvQD2nyr/egXvMgZlrLpzpp7ft1RcyHjR20DLbNA==} peerDependencies: @@ -567,6 +609,159 @@ packages: - utf-8-validate dev: false + /@apollo/server-gateway-interface@1.1.1(graphql@16.8.0): + resolution: {integrity: sha512-pGwCl/po6+rxRmDMFgozKQo2pbsSwE91TpsDBAOgf74CRDPXHHtM88wbwjab0wMMZh95QfR45GGyDIdhY24bkQ==} + peerDependencies: + graphql: 14.x || 15.x || 16.x + dependencies: + '@apollo/usage-reporting-protobuf': 4.1.1 + '@apollo/utils.fetcher': 2.0.1 + '@apollo/utils.keyvaluecache': 2.1.1 + '@apollo/utils.logger': 2.0.1 + graphql: 16.8.0 + dev: false + + /@apollo/server@4.9.2(graphql@16.8.0): + resolution: {integrity: sha512-DXARzsL7gvBfhUL2gTCpGduaH5wQFZi72/6ZOalpzT9InepIz0wL9TffSNVuaYla5u6JB9kX8WtnVUKf7IuHTA==} + engines: {node: '>=14.16.0'} + peerDependencies: + graphql: ^16.6.0 + dependencies: + '@apollo/cache-control-types': 1.0.3(graphql@16.8.0) + '@apollo/server-gateway-interface': 1.1.1(graphql@16.8.0) + '@apollo/usage-reporting-protobuf': 4.1.1 + '@apollo/utils.createhash': 2.0.1 + '@apollo/utils.fetcher': 2.0.1 + '@apollo/utils.isnodelike': 2.0.1 + '@apollo/utils.keyvaluecache': 2.1.1 + '@apollo/utils.logger': 2.0.1 + '@apollo/utils.usagereporting': 2.1.0(graphql@16.8.0) + '@apollo/utils.withrequired': 2.0.1 + '@graphql-tools/schema': 9.0.19(graphql@16.8.0) + '@josephg/resolvable': 1.0.1 + '@types/express': 4.17.17 + '@types/express-serve-static-core': 4.17.35 + '@types/node-fetch': 2.6.4 + async-retry: 1.3.3 + body-parser: 1.20.2 + cors: 2.8.5 + express: 4.18.2 + graphql: 16.8.0 + loglevel: 1.8.1 + lru-cache: 7.18.3 + negotiator: 0.6.3 + node-abort-controller: 3.1.1 + node-fetch: 2.6.12 + uuid: 9.0.0 + whatwg-mimetype: 3.0.0 + transitivePeerDependencies: + - encoding + - supports-color + dev: false + + /@apollo/usage-reporting-protobuf@4.1.1: + resolution: {integrity: sha512-u40dIUePHaSKVshcedO7Wp+mPiZsaU6xjv9J+VyxpoU/zL6Jle+9zWeG98tr/+SZ0nZ4OXhrbb8SNr0rAPpIDA==} + dependencies: + '@apollo/protobufjs': 1.2.7 + dev: false + + /@apollo/utils.createhash@2.0.1: + resolution: {integrity: sha512-fQO4/ZOP8LcXWvMNhKiee+2KuKyqIcfHrICA+M4lj/h/Lh1H10ICcUtk6N/chnEo5HXu0yejg64wshdaiFitJg==} + engines: {node: '>=14'} + dependencies: + '@apollo/utils.isnodelike': 2.0.1 + sha.js: 2.4.11 + dev: false + + /@apollo/utils.dropunuseddefinitions@2.0.1(graphql@16.8.0): + resolution: {integrity: sha512-EsPIBqsSt2BwDsv8Wu76LK5R1KtsVkNoO4b0M5aK0hx+dGg9xJXuqlr7Fo34Dl+y83jmzn+UvEW+t1/GP2melA==} + engines: {node: '>=14'} + peerDependencies: + graphql: 14.x || 15.x || 16.x + dependencies: + graphql: 16.8.0 + dev: false + + /@apollo/utils.fetcher@2.0.1: + resolution: {integrity: sha512-jvvon885hEyWXd4H6zpWeN3tl88QcWnHp5gWF5OPF34uhvoR+DFqcNxs9vrRaBBSY3qda3Qe0bdud7tz2zGx1A==} + engines: {node: '>=14'} + dev: false + + /@apollo/utils.isnodelike@2.0.1: + resolution: {integrity: sha512-w41XyepR+jBEuVpoRM715N2ZD0xMD413UiJx8w5xnAZD2ZkSJnMJBoIzauK83kJpSgNuR6ywbV29jG9NmxjK0Q==} + engines: {node: '>=14'} + dev: false + + /@apollo/utils.keyvaluecache@2.1.1: + resolution: {integrity: sha512-qVo5PvUUMD8oB9oYvq4ViCjYAMWnZ5zZwEjNF37L2m1u528x5mueMlU+Cr1UinupCgdB78g+egA1G98rbJ03Vw==} + engines: {node: '>=14'} + dependencies: + '@apollo/utils.logger': 2.0.1 + lru-cache: 7.18.3 + dev: false + + /@apollo/utils.logger@2.0.1: + resolution: {integrity: sha512-YuplwLHaHf1oviidB7MxnCXAdHp3IqYV8n0momZ3JfLniae92eYqMIx+j5qJFX6WKJPs6q7bczmV4lXIsTu5Pg==} + engines: {node: '>=14'} + dev: false + + /@apollo/utils.printwithreducedwhitespace@2.0.1(graphql@16.8.0): + resolution: {integrity: sha512-9M4LUXV/fQBh8vZWlLvb/HyyhjJ77/I5ZKu+NBWV/BmYGyRmoEP9EVAy7LCVoY3t8BDcyCAGfxJaLFCSuQkPUg==} + engines: {node: '>=14'} + peerDependencies: + graphql: 14.x || 15.x || 16.x + dependencies: + graphql: 16.8.0 + dev: false + + /@apollo/utils.removealiases@2.0.1(graphql@16.8.0): + resolution: {integrity: sha512-0joRc2HBO4u594Op1nev+mUF6yRnxoUH64xw8x3bX7n8QBDYdeYgY4tF0vJReTy+zdn2xv6fMsquATSgC722FA==} + engines: {node: '>=14'} + peerDependencies: + graphql: 14.x || 15.x || 16.x + dependencies: + graphql: 16.8.0 + dev: false + + /@apollo/utils.sortast@2.0.1(graphql@16.8.0): + resolution: {integrity: sha512-eciIavsWpJ09za1pn37wpsCGrQNXUhM0TktnZmHwO+Zy9O4fu/WdB4+5BvVhFiZYOXvfjzJUcc+hsIV8RUOtMw==} + engines: {node: '>=14'} + peerDependencies: + graphql: 14.x || 15.x || 16.x + dependencies: + graphql: 16.8.0 + lodash.sortby: 4.7.0 + dev: false + + /@apollo/utils.stripsensitiveliterals@2.0.1(graphql@16.8.0): + resolution: {integrity: sha512-QJs7HtzXS/JIPMKWimFnUMK7VjkGQTzqD9bKD1h3iuPAqLsxd0mUNVbkYOPTsDhUKgcvUOfOqOJWYohAKMvcSA==} + engines: {node: '>=14'} + peerDependencies: + graphql: 14.x || 15.x || 16.x + dependencies: + graphql: 16.8.0 + dev: false + + /@apollo/utils.usagereporting@2.1.0(graphql@16.8.0): + resolution: {integrity: sha512-LPSlBrn+S17oBy5eWkrRSGb98sWmnEzo3DPTZgp8IQc8sJe0prDgDuppGq4NeQlpoqEHz0hQeYHAOA0Z3aQsxQ==} + engines: {node: '>=14'} + peerDependencies: + graphql: 14.x || 15.x || 16.x + dependencies: + '@apollo/usage-reporting-protobuf': 4.1.1 + '@apollo/utils.dropunuseddefinitions': 2.0.1(graphql@16.8.0) + '@apollo/utils.printwithreducedwhitespace': 2.0.1(graphql@16.8.0) + '@apollo/utils.removealiases': 2.0.1(graphql@16.8.0) + '@apollo/utils.sortast': 2.0.1(graphql@16.8.0) + '@apollo/utils.stripsensitiveliterals': 2.0.1(graphql@16.8.0) + graphql: 16.8.0 + dev: false + + /@apollo/utils.withrequired@2.0.1: + resolution: {integrity: sha512-YBDiuAX9i1lLc6GeTy1m7DGLFn/gMnvXqlalOIMjM7DeOgIacEjjfwPqb0M1CQ2v11HhR15d1NmxJoRCfrNqcA==} + engines: {node: '>=14'} + dev: false + /@aws-cdk/asset-awscli-v1@2.2.200: resolution: {integrity: sha512-Kf5J8DfJK4wZFWT2Myca0lhwke7LwHcHBo+4TvWOGJrFVVKVuuiLCkzPPRBQQVDj0Vtn2NBokZAz8pfMpAqAKg==} @@ -2788,12 +2983,12 @@ packages: /@balena/dockerignore@1.0.2: resolution: {integrity: sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==} - /@bronya.js/api-construct@0.10.8: - resolution: {integrity: sha512-a+3oQx3JbywcaK9TGW0j1lHcAvPvlk7yvyKxL4T8zUoQVILDSBGAifHp4+7WkEj1TFYEZL3bkNRGDiriGT7+5Q==} + /@bronya.js/api-construct@0.10.9: + resolution: {integrity: sha512-OfkmE01TMCwX35uOba4v74U1sa2kZASDUGVUZXjR8hJriU55V0QA6QXyE/aovc51SyvqUnOwhhyZAxWhIIl/5A==} engines: {node: '>=18', pnpm: ^8.0.0} dependencies: - '@bronya.js/cli': 0.10.8 - '@bronya.js/core': 0.10.8 + '@bronya.js/cli': 0.10.9 + '@bronya.js/core': 0.10.9 acorn: 8.10.0 acorn-typescript: 1.4.5(acorn@8.10.0) aws-cdk-lib: 2.93.0(constructs@10.2.69) @@ -2809,17 +3004,17 @@ packages: - supports-color dev: true - /@bronya.js/cli@0.10.8: - resolution: {integrity: sha512-tlyftBwwOPoAycyvCBSEYfpozu7DTCzBaeSk1y0ZXAw6Z4CnSIUslkuQBhJbMia0jO/JfjPtB4+Er0wMKWpJhQ==} + /@bronya.js/cli@0.10.9: + resolution: {integrity: sha512-se2qMA4ObQnpEStGt4XFYjUolQVrpfdmL/upd7SGX55yr8TBcnKn7bQG14LrGIRymyG0S+wwkC5p0I+rn8s64Q==} engines: {node: '>=18', pnpm: ^8.0.0} dev: true - /@bronya.js/core@0.10.8: - resolution: {integrity: sha512-DQqsAbAVypGAjquegTUS4xpA6O2QxykhMr0fWd2FNqqyFBte3rOUmOzxZhsSpkA3Qo31TYdEOeaL1aeZ0XxWmg==} + /@bronya.js/core@0.10.9: + resolution: {integrity: sha512-RHQ9Qau8eWe40TBTU8kZa/9PdQw9ROrJ5L7BaGK2Cl0gRmRfJauiXXJ6FoKpmX+hFYsd/bxBbzI0rg8zGJ1hFw==} engines: {node: '>=18', pnpm: ^8.0.0} hasBin: true dependencies: - '@bronya.js/cli': 0.10.8 + '@bronya.js/cli': 0.10.9 acorn: 8.10.0 acorn-typescript: 1.4.5(acorn@8.10.0) aws-cdk-lib: 2.93.0(constructs@10.2.69) @@ -4468,6 +4663,81 @@ packages: resolution: {integrity: sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@graphql-tools/load-files@7.0.0(graphql@16.8.0): + resolution: {integrity: sha512-P98amERIwI7FD8Bsq6xUbz9Mj63W8qucfrE/WQjad5jFMZYdFFt46a99FFdfx8S/ZYgpAlj/AZbaTtWLitMgNQ==} + engines: {node: '>=16.0.0'} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + dependencies: + globby: 11.1.0 + graphql: 16.8.0 + tslib: 2.5.2 + unixify: 1.0.0 + dev: false + + /@graphql-tools/merge@8.4.2(graphql@16.8.0): + resolution: {integrity: sha512-XbrHAaj8yDuINph+sAfuq3QCZ/tKblrTLOpirK0+CAgNlZUCHs0Fa+xtMUURgwCVThLle1AF7svJCxFizygLsw==} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + dependencies: + '@graphql-tools/utils': 9.2.1(graphql@16.8.0) + graphql: 16.8.0 + tslib: 2.5.2 + dev: false + + /@graphql-tools/merge@9.0.0(graphql@16.8.0): + resolution: {integrity: sha512-J7/xqjkGTTwOJmaJQJ2C+VDBDOWJL3lKrHJN4yMaRLAJH3PosB7GiPRaSDZdErs0+F77sH2MKs2haMMkywzx7Q==} + engines: {node: '>=16.0.0'} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + dependencies: + '@graphql-tools/utils': 10.0.5(graphql@16.8.0) + graphql: 16.8.0 + tslib: 2.5.2 + dev: false + + /@graphql-tools/schema@9.0.19(graphql@16.8.0): + resolution: {integrity: sha512-oBRPoNBtCkk0zbUsyP4GaIzCt8C0aCI4ycIRUL67KK5pOHljKLBBtGT+Jr6hkzA74C8Gco8bpZPe7aWFjiaK2w==} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + dependencies: + '@graphql-tools/merge': 8.4.2(graphql@16.8.0) + '@graphql-tools/utils': 9.2.1(graphql@16.8.0) + graphql: 16.8.0 + tslib: 2.5.2 + value-or-promise: 1.0.12 + dev: false + + /@graphql-tools/utils@10.0.5(graphql@16.8.0): + resolution: {integrity: sha512-ZTioQqg9z9eCG3j+KDy54k1gp6wRIsLqkx5yi163KVvXVkfjsrdErCyZjrEug21QnKE9piP4tyxMpMMOT1RuRw==} + engines: {node: '>=16.0.0'} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + dependencies: + '@graphql-typed-document-node/core': 3.2.0(graphql@16.8.0) + dset: 3.1.2 + graphql: 16.8.0 + tslib: 2.5.2 + dev: false + + /@graphql-tools/utils@9.2.1(graphql@16.8.0): + resolution: {integrity: sha512-WUw506Ql6xzmOORlriNrD6Ugx+HjVgYxt9KCXD9mHAak+eaXSwuGGPyE60hy9xaDEoXKBsG7SkG69ybitaVl6A==} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + dependencies: + '@graphql-typed-document-node/core': 3.2.0(graphql@16.8.0) + graphql: 16.8.0 + tslib: 2.5.2 + dev: false + + /@graphql-typed-document-node/core@3.2.0(graphql@16.8.0): + resolution: {integrity: sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==} + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + dependencies: + graphql: 16.8.0 + dev: false + /@hapi/hoek@9.3.0: resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} @@ -4512,6 +4782,10 @@ packages: chalk: 4.1.2 dev: false + /@josephg/resolvable@1.0.1: + resolution: {integrity: sha512-CtzORUwWTTOTqfVtHaKRJ0I1kNQd1bpn3sUh8I3nJDVY+5/M/Oe1DnEWzPQvqq/xPIIkzzzIP7mfCoAjFRvDhg==} + dev: false + /@jridgewell/gen-mapping@0.3.3: resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} engines: {node: '>=6.0.0'} @@ -4662,6 +4936,49 @@ packages: resolution: {integrity: sha512-JFdsnSgBPN8reDTLOI9Vh/6ccCb2aD1LbY/LWQnkcIgNo6IdpzvuM+qRVbBuA6IZP2SdqQI8Lu6RL2P8EFBQUA==} dev: true + /@protobufjs/aspromise@1.1.2: + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + dev: false + + /@protobufjs/base64@1.1.2: + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + dev: false + + /@protobufjs/codegen@2.0.4: + resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + dev: false + + /@protobufjs/eventemitter@1.1.0: + resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + dev: false + + /@protobufjs/fetch@1.1.0: + resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/inquire': 1.1.0 + dev: false + + /@protobufjs/float@1.0.2: + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + dev: false + + /@protobufjs/inquire@1.1.0: + resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} + dev: false + + /@protobufjs/path@1.1.2: + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + dev: false + + /@protobufjs/pool@1.1.0: + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + dev: false + + /@protobufjs/utf8@1.1.0: + resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + dev: false + /@sideway/address@4.1.4: resolution: {integrity: sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==} dependencies: @@ -5402,6 +5719,10 @@ packages: resolution: {integrity: sha512-rYHDtOV4BHb1hRH5dA7P+jarU/YFmQ9CGgwYPZNVRoxhrsrm2exEETeIEB+EmBk2AXIKAaK9JDzjpNCkceDGFQ==} dev: true + /@types/long@4.0.2: + resolution: {integrity: sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==} + dev: false + /@types/mdast@3.0.11: resolution: {integrity: sha512-Y/uImid8aAwrEA24/1tcRZwpxX3pIFTSilcNDKSPn+Y2iDywSEachzRuvgAYYLR3wpGXAsMbv5lvKLDZLeYPAw==} dependencies: @@ -5420,6 +5741,13 @@ packages: resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} dev: true + /@types/node-fetch@2.6.4: + resolution: {integrity: sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==} + dependencies: + '@types/node': 18.17.12 + form-data: 3.0.1 + dev: false + /@types/node@17.0.45: resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==} dev: false @@ -6086,10 +6414,20 @@ packages: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} engines: {node: '>=8'} + /async-retry@1.3.3: + resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==} + dependencies: + retry: 0.13.1 + dev: false + /async@3.2.4: resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==} dev: false + /asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: false + /at-least-node@1.0.0: resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} engines: {node: '>= 4.0.0'} @@ -6322,7 +6660,6 @@ packages: unpipe: 1.0.0 transitivePeerDependencies: - supports-color - dev: true /bonjour-service@1.1.1: resolution: {integrity: sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg==} @@ -6755,6 +7092,13 @@ packages: engines: {node: '>=10'} dev: false + /combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + dev: false + /comma-separated-tokens@1.0.8: resolution: {integrity: sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==} dev: false @@ -6976,7 +7320,6 @@ packages: dependencies: object-assign: 4.1.1 vary: 1.1.2 - dev: true /cosmiconfig-typescript-loader@4.3.0(@types/node@20.4.7)(cosmiconfig@8.1.3)(ts-node@10.9.1)(typescript@5.2.2): resolution: {integrity: sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==} @@ -7411,6 +7754,11 @@ packages: slash: 3.0.0 dev: false + /delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dev: false + /depd@1.1.2: resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} engines: {node: '>= 0.6'} @@ -7643,6 +7991,11 @@ packages: engines: {node: '>=10'} dev: false + /dset@3.1.2: + resolution: {integrity: sha512-g/M9sqy3oHe477Ar4voQxWtaPIFw1jTdKZuomOjhCcBx9nHUNn0pu6NopuFFrTh/TRZIKEj+76vLWFu9BNKk+Q==} + engines: {node: '>=4'} + dev: false + /duplexer3@0.1.5: resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==} dev: false @@ -8475,6 +8828,15 @@ packages: webpack: 5.84.1 dev: false + /form-data@3.0.1: + resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: false + /forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -9884,7 +10246,6 @@ packages: /lodash.sortby@4.7.0: resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} - dev: true /lodash.startcase@4.4.0: resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} @@ -9933,6 +10294,15 @@ packages: triple-beam: 1.3.0 dev: false + /loglevel@1.8.1: + resolution: {integrity: sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg==} + engines: {node: '>= 0.6.0'} + dev: false + + /long@4.0.0: + resolution: {integrity: sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==} + dev: false + /longest@2.0.1: resolution: {integrity: sha512-Ajzxb8CM6WAnFjgiloPsI3bF+WCxcvhdIG3KNA2KN962+tdBsHcuQ4k4qX/EcS/2CRkcc0iAkR956Nib6aXU/Q==} engines: {node: '>=0.10.0'} @@ -9972,6 +10342,11 @@ packages: dependencies: yallist: 4.0.0 + /lru-cache@7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + dev: false + /make-dir@3.1.0: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} engines: {node: '>=8'} @@ -10235,6 +10610,10 @@ packages: tslib: 2.5.2 dev: false + /node-abort-controller@3.1.1: + resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} + dev: false + /node-emoji@1.11.0: resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} dependencies: @@ -10279,6 +10658,13 @@ packages: validate-npm-package-license: 3.0.4 dev: true + /normalize-path@2.1.1: + resolution: {integrity: sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==} + engines: {node: '>=0.10.0'} + dependencies: + remove-trailing-separator: 1.1.0 + dev: false + /normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -11320,7 +11706,6 @@ packages: http-errors: 2.0.0 iconv-lite: 0.4.24 unpipe: 1.0.0 - dev: true /rc@1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} @@ -11706,6 +12091,10 @@ packages: mdast-squeeze-paragraphs: 4.0.0 dev: false + /remove-trailing-separator@1.1.0: + resolution: {integrity: sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==} + dev: false + /renderkid@3.0.0: resolution: {integrity: sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==} dependencies: @@ -12045,6 +12434,14 @@ packages: /setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + /sha.js@2.4.11: + resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==} + hasBin: true + dependencies: + inherits: 2.0.4 + safe-buffer: 5.2.1 + dev: false + /shallow-clone@3.0.1: resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==} engines: {node: '>=8'} @@ -13096,6 +13493,13 @@ packages: resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} engines: {node: '>= 10.0.0'} + /unixify@1.0.0: + resolution: {integrity: sha512-6bc58dPYhCMHHuwxldQxO3RRNZ4eCogZ/st++0+fcC1nr0jiGUtAdBJ2qzmLQWSxbtz42pWt4QQMiZ9HvZf5cg==} + engines: {node: '>=0.10.0'} + dependencies: + normalize-path: 2.1.1 + dev: false + /unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} @@ -13225,6 +13629,11 @@ packages: hasBin: true dev: false + /uuid@9.0.0: + resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==} + hasBin: true + dev: false + /v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} requiresBuild: true @@ -13241,6 +13650,11 @@ packages: resolution: {integrity: sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==} dev: false + /value-or-promise@1.0.12: + resolution: {integrity: sha512-Z6Uz+TYwEqE7ZN50gwn+1LCVo9ZVrpxRPOhOLnncYkY1ZzOYtrX8Fwf/rFktZ8R5mJms6EZf5TqNOMeZmnPq9Q==} + engines: {node: '>=12'} + dev: false + /vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'}