diff --git a/.dockerignore b/.dockerignore index f22ac65..14af5f5 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,4 @@ +.env build db.sqlite3 dist diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..15b95b5 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "typescript.tsdk": "node_modules/typescript/lib", +} diff --git a/package.json b/package.json index 9e2a076..b0f06b5 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "frontend:dev": "pnpm --filter frontend exec vite --open" }, "devDependencies": { + "@effect/language-service": "catalog:", "@eslint/js": "catalog:", "@stylistic/eslint-plugin": "catalog:", "eslint-plugin-react-hooks": "catalog:", diff --git a/packages/backend/package.json b/packages/backend/package.json index 05351dd..4ad84e0 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -8,12 +8,9 @@ "typescript": "catalog:" }, "dependencies": { - "@elysiajs/cors": "catalog:", - "@elysiajs/static": "catalog:", - "@elysiajs/swagger": "catalog:", + "cbor2": "catalog:", "common": "workspace:^", "effect": "catalog:", - "elysia": "catalog:", "kysely": "catalog:", "kysely-bun-sqlite": "catalog:" } diff --git a/packages/backend/src/api.ts b/packages/backend/src/api.ts new file mode 100644 index 0000000..29a6219 --- /dev/null +++ b/packages/backend/src/api.ts @@ -0,0 +1,106 @@ +import * as Api from "common/Api"; +import * as Cbor from "common/Cbor"; +import * as Client from "common/Client"; +import { Cause, Effect, Either, HashMap, Inspectable, Option, pipe } from "effect"; + +/* NOTE We shouldn't need to extract this to a separate type, but if we don't do + * it the TypeScript parser in VS Code sort of blows up and the syntax colors + * turn wrong. + */ +type Return> = Effect.Effect>>; + +const catchToResponse = Effect.catchAll((error: unknown) => pipe( + error, + Inspectable.toJSON, + Cbor.encode, + Effect.map(Client.cborBody), + Effect.matchEffect({ + onSuccess: ({ body, headers }) => Effect.fail(new Response(body, { status: 500, headers })), + onFailure: () => Effect.fail(new Response(null, { status: 500 })), + }), +)); + +export const implement = < + const Record extends { readonly [_: string]: Api.ApiAny }, + const Impl extends Api.ApiBundleImpl, +>( + bundle: Api.ApiBundle, + impl: Impl, +): (key: string, request: Request) => Return => { + return (key, requestObject) => { + /* Force both return types to be `Response`. We can use the error route + * for it's short-circuit capabilities. + */ + const effect: Effect.Effect = Effect.gen(function* () { + const maybeApi = HashMap.get(bundle.map, key); + if (Option.isNone(maybeApi)) { + return RESPONSE_API_NOT_FOUND; + } + const { value: api } = maybeApi; + + const fn = impl[key]; + if (fn === undefined) { + return RESPONSE_API_NOT_IMPLEMENTED; + } + + const request = yield* pipe( + requestObject, + Client.decodeBody(api.request), + catchToResponse, + ); + + const result = yield* pipe( + request, + fn, + Effect.sandbox, + Effect.either, + ); + + const { status, response } = pipe( + result, + Either.match({ + onLeft: (cause) => pipe( + cause, + Cause.failureOrCause, + Either.match({ + onLeft: (error) => ({ + status: 400, + response: pipe( + error, + Client.encodeBody(api.error), + catchToResponse, + ), + }), + onRight: (die) => ({ + status: 500, + response: pipe( + die, + Inspectable.toJSON, + Cbor.encode, + Effect.map(Client.cborBody), + catchToResponse, + ), + }), + }), + ), + onRight: (response) => ({ + status: 200, + response: pipe( + response, + Client.encodeBody(api.response), + catchToResponse, + ), + }), + }), + ); + + const { body, headers } = yield* response; + return new Response(body, { status, headers }); + }); + + return Effect.catchAll(effect, Effect.succeed); + }; +}; + +const RESPONSE_API_NOT_FOUND = new Response(null, { status: 404 }); +const RESPONSE_API_NOT_IMPLEMENTED = new Response(null, { status: 501 }); diff --git a/packages/backend/src/app.ts b/packages/backend/src/app.ts index 74cfab4..38fe96a 100644 --- a/packages/backend/src/app.ts +++ b/packages/backend/src/app.ts @@ -1,864 +1,160 @@ -import cors from "@elysiajs/cors"; -import { staticPlugin } from "@elysiajs/static"; -import { swagger } from "@elysiajs/swagger"; -import { AttachmentId, PieceId, RepertoireId, RequestId, SessionId, Sha256_Bin, Sha256_Hex } from "common"; -import { Effect, Option, pipe, Redacted } from "effect"; -import { Elysia, error, t } from "elysia"; -import { sql } from "kysely"; -import { EXTERNAL_OAUTH_CONFIGURATION, getUser, INTERNAL_OAUTH_CONFIGURATION, makeAuthorizationUrl, REDIRECT_URI, revalidateTokens } from "./auth"; +import * as Body from "common/Body"; +import { fetch } from "common/Fetch"; +import { Cause, Effect, Layer, Option, pipe, Record, Redacted, Stream } from "effect"; +import * as path from "node:path"; import { config } from "./config"; -import * as Db from "./database"; -import * as Model from "./model"; -import { DbFromInstance } from "./services/db"; -import { SessionFromValue } from "./services/session"; +import * as Authentication from "./services/Authentication"; +import * as Database from "./services/Database"; +import { handle } from "./the_api"; -const app = new Elysia() +const FRONTEND_ROOT = "packages/frontend/build"; +const FRONTEND_ASSETS_ROOT = path.join(FRONTEND_ROOT, "assets"); - .use(swagger({ - scalarConfig: { - authentication: { - securitySchemes: { - cookieAuth: { - type: "apiKey", - in: "cookie", - name: "sessionId", - }, - }, +const assetRoutes = await pipe( + Stream.fromAsyncIterable( + new Bun.Glob("**/*").scan(FRONTEND_ASSETS_ROOT), + (error) => new Cause.UnknownException(error), + ), + Stream.map((filepath): [string, Response] => [ + `/assets/${filepath}`, + new Response(Bun.file(path.join(FRONTEND_ASSETS_ROOT, filepath))), + ]), + Stream.runCollect, + Effect.map(Record.fromEntries), + Effect.runPromise, +); + +const CORS_HEADERS: [string, string][] = [ + ["Access-Control-Allow-Origin", "http://localhost:5173"], + ["Access-Control-Allow-Methods", "POST, OPTIONS"], + ["Access-Control-Allow-Credentials", "true"], + ["Access-Control-Allow-Headers", "Content-Type"], +]; + +const homepage = new Response(Bun.file(path.join(FRONTEND_ROOT, "index.html"))); + +const databaseLayer = Database.FromPath(config.DB_PATH); + +Bun.serve({ + routes: { + ...assetRoutes, + "/login": { + GET: async (req) => { + const res = await pipe( + Authentication.Authentication, + Effect.flatMap(({ sessionId }) => Authentication.makeAuthorizationUrl({ + external: new URL(req.url).searchParams.has("external"), + sessionId, + })), + Effect.map((url) => Response.redirect(url)), + Effect.provide(Layer.provideMerge(Authentication.Live(req), databaseLayer)), + Effect.runPromise, + ); + + return res; }, - }, - swaggerOptions: { - withCredentials: true, - }, - })) + POST: (req) => Effect.gen(function* () { + const { sessionId } = yield* Authentication.Authentication; + const db = yield* Database.Database; - .use(cors({ origin: config.NODE_ENV === "production" ? false : "localhost:5173" })) + const data = yield* Body.formData(req); - .decorate("db", await Db.initDatabase(config.DB_PATH)) + const code = data.get("code") as string | null; + const state = data.get("state") as string | null; - .resolve(async ({ db, cookie }) => { - await db - .deleteFrom("Session") - .where(sql`datetime()`, ">=", "expiresAt") - .execute(); + const session = yield* db + .selectFrom("Session") + .select(["external", "codeVerifier"]) + .where("sessionId", "=", sessionId) + .$call(Database.executeTakeFirst); - const sessionId = (cookie.sessionId.value as SessionId | undefined) ?? Db.generateSessionId(); - - const expiresAt = new Date().getTime() + 604800000; - cookie.sessionId.set({ - value: sessionId, - expires: new Date(expiresAt), - httpOnly: true, - sameSite: "none", - secure: true, - }); - - const returning = [ - "sessionId", - "accessToken", - "codeVerifier", - "external", - "idToken", - "refreshToken", - "state", - ] as const; - - let session = await db - .updateTable("Session") - .set({ expiresAt: sql`datetime('now', '+7 days') ` }) - .where("sessionId", "=", sessionId) - .returning(returning) - .executeTakeFirst(); - - if (session === undefined) { - session = await db - .insertInto("Session") - .values({ sessionId, expiresAt: sql`datetime('now', '+7 days')` }) - .returning(returning) - .executeTakeFirstOrThrow(); - } - - const { accessToken, idToken, refreshToken, roles, userId } = await pipe( - { - accessToken: Option.fromNullable(session.accessToken), - idToken: Option.fromNullable(session.idToken), - refreshToken: Option.fromNullable(session.refreshToken), - external: Boolean(session.external), - }, - revalidateTokens, - Effect.runPromise, - ); - - await db - .updateTable("Session") - .set({ - accessToken: pipe( - accessToken, - Option.map((at) => at.token), - Option.getOrNull, - ), - idToken: pipe( - idToken, - Option.map((it) => it.token), - Option.getOrNull, - ), - refreshToken: Option.getOrNull(refreshToken), - }) - .execute(); - - return { - session: { - sessionId: session.sessionId, - accessToken, - idToken, - refreshToken, - roles, - userId, - codeVerifier: Option.fromNullable(session.codeVerifier), - external: pipe( + const external = pipe( session.external, Option.fromNullable, - Option.map((e) => e !== 0), - ), - state: Option.fromNullable(session.state), - }, - }; - }) + Option.map((external) => external !== 0), + ); - .onTransform(async ({ db, request, server }) => { + const codeVerifier = Option.fromNullable(session.codeVerifier); - const requestId = RequestId(Bun.randomUUIDv7("hex")); - const timestamp = new Date().toISOString(); - const { method } = request; - const url = new URL(request.url); - const { pathname } = url; - const query = JSON.stringify(Object.fromEntries(url.searchParams.entries())); - const ip = server?.requestIP(request)?.address ?? null; + if (code !== null && state !== null && Option.isSome(external) && Option.isSome(codeVerifier)) { + const { tokenEndpoint } = external.value + ? Authentication.EXTERNAL_OAUTH_CONFIGURATION + : Authentication.INTERNAL_OAUTH_CONFIGURATION; - await db - .insertInto("AccessLog") - .values({ requestId, timestamp, method, pathname, query, ip }) - .execute(); + const res = yield* fetch(tokenEndpoint, { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: new URLSearchParams({ + "client_id": config.CLIENT_ID, + "code": code, + "redirect_uri": Authentication.REDIRECT_URI, + "grant_type": "authorization_code", + "code_verifier": codeVerifier.value, + "client_secret": Redacted.value(config.CLIENT_SECRET), + }).toString(), + }); - console.log(`${timestamp} ${method} ${request.url} ${ip}`); - }) + const { + access_token: accessToken, + refresh_token: refreshToken, + id_token: idToken, + } = (yield* Body.json(res)) as { + access_token: string, + refresh_token: string, + id_token: string, + }; - .use(staticPlugin({ - assets: "packages/frontend/build/assets", - prefix: "/assets", - alwaysStatic: true, - indexHTML: false, - })) + yield* db + .updateTable("Session") + .set({ + accessToken, + refreshToken, + idToken, + codeVerifier: null, + state: null, + }) + .where("sessionId", "=", sessionId) + .$call(Database.execute); + } - .group("/api/v1", (app) => app - - // --- MARK: AUTHENTICATION -------------------------------------------- - - .get("/me", ({ session: { idToken, roles } }) => { - return Option.match(idToken, { - onNone: () => error("Unauthorized", "Session invalid or expired"), - onSome: ({ payload: { oid, name } }) => ({ - userId: oid, - username: name, - roles: roles as string[], - }), - }); - }, { - response: { - 200: Model.Me, - 401: t.Literal("Session invalid or expired"), - }, - }) - - .get("/login", async ({ db, query, redirect, session: { sessionId } }) => { - - const url = await pipe( - makeAuthorizationUrl({ external: "external" in query }), - Effect.provide([ - DbFromInstance(db), - SessionFromValue(sessionId), - ]), + return Response.redirect(config.NODE_ENV === "production" ? `https://${config.HOSTNAME}/` : "http://localhost:5173/", 303); + }).pipe( + Effect.provide(Layer.provideMerge(Authentication.Live(req), databaseLayer)), Effect.runPromise, - ); + ), + }, + "/api/:key": async (req, server) => { - return redirect(url, 302) as unknown as void; - }, { - response: { - 302: t.Void(), - }, - }) + const timestamp = new Date().toISOString(); + console.log(`${timestamp} ${req.method} ${req.url} ${server.requestIP(req)?.address}`); - .post("/login", async ({ db, request, redirect, session: { sessionId, external, codeVerifier } }) => { - const data = await request.formData(); - - const code = data.get("code") as string | null; - const state = data.get("state") as string | null; - - if (code !== null && state !== null && Option.isSome(external) && Option.isSome(codeVerifier)) { - const { tokenEndpoint } = external.value ? EXTERNAL_OAUTH_CONFIGURATION : INTERNAL_OAUTH_CONFIGURATION; - - const res = await fetch(tokenEndpoint, { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - body: new URLSearchParams({ - "client_id": config.CLIENT_ID, - "code": code, - "redirect_uri": REDIRECT_URI, - "grant_type": "authorization_code", - "code_verifier": codeVerifier.value, - "client_secret": Redacted.value(config.CLIENT_SECRET), - }).toString(), + if (req.method === "OPTIONS") { + return new Response(null, { + status: 204, + headers: CORS_HEADERS, }); - - const { - access_token: accessToken, - refresh_token: refreshToken, - id_token: idToken, - } = await res.json() as { access_token: string, refresh_token: string, id_token: string }; - - await db - .updateTable("Session") - .set({ - accessToken, - refreshToken, - idToken, - codeVerifier: null, - state: null, - }) - .where("sessionId", "=", sessionId) - .execute(); } - return redirect(config.NODE_ENV === "production" ? `https://${config.HOSTNAME}/` : "http://localhost:5173/", 303) as unknown as void; - }, { - response: { - 303: t.Void(), - }, - }) + const authenticationLayer = Authentication.Live(req); + const layers = Layer.provideMerge(authenticationLayer, databaseLayer); - .post("/logout", async ({ db, cookie, set }) => { - - set.status = "No Content"; - - const sessionCookie = cookie.sessionId; - sessionCookie.remove(); - - const sessionId = sessionCookie.value; - if (sessionId === undefined) { - return; - } - - await db - .deleteFrom("Session") - .where("sessionId", "=", SessionId(sessionId)) - .execute(); - }, { - response: { - 204: t.Void(), - }, - }) - - // --- MARK: USER MANAGEMENT ------------------------------------------- - - .get("/user/:userId", async ({ params: { userId }, session: { accessToken } }) => { - - if (Option.isNone(accessToken)) { - return error("Unauthorized", "Session invalid or expired"); - } - - const res = await pipe( - { accessToken: accessToken.value.token, userId }, - getUser, + const response = await pipe( + handle(req.params.key, req), + Effect.provide(layers), Effect.runPromise, ); - return Option.match(res, { - onNone: () => error("Not Found", new Response() as unknown as void), - onSome: ({ displayName }) => ({ userId, displayName }), - }); - }, { - params: t.Object({ - userId: Model.UserId, - }), - response: { - 200: Model.User, - 401: t.Literal("Session invalid or expired"), - 404: t.Void(), - }, - }) - - // --- MARK: PIECE CRUD ------------------------------------------------ - - .post("/piece", async ({ db, body: { name, composer, lyricist, arranger }, session: { idToken, roles } }) => { - - if (Option.isNone(idToken)) { - return error("Unauthorized", "Session invalid or expired"); + for (const [name, value] of CORS_HEADERS) { + response.headers.set(name, value); } - if (!roles.includes("Editor")) { - return error("Forbidden", "Must be an Editor"); - } - - const pieceId = PieceId(Bun.randomUUIDv7()); - - const res = await db - .insertInto("Piece") - .values({ pieceId, name, composer, lyricist, arranger, createdBy: idToken.value.payload.oid, createdAt: sql`datetime()` }) - .returningAll() - .executeTakeFirstOrThrow(); - - return { - ...res, - attachments: [], - }; - }, { - body: Model.Piece_Post, - response: { - 200: Model.Piece, - 401: t.Literal("Session invalid or expired"), - 403: t.Literal("Must be an Editor"), - }, - }) - - .get("/piece", async ({ db, query, session: { idToken } }) => { - - if (Option.isNone(idToken)) { - return error("Unauthorized", "Session invalid or expired"); - } - - let q = db - .selectFrom("Piece") - .select("pieceId") - .orderBy(["name", "composer", "arranger"]) - .offset(query.offset ?? 0) - .limit(query.limit ?? 100); - - if (query.name !== undefined) { - q = q.where("name", "like", "%" + query.name + "%"); - } - - if (query.author !== undefined) { - q = q.where((eb) => eb.or([ - eb("composer", "like", "%" + query.author + "%"), - eb("arranger", "like", "%" + query.author + "%"), - eb("lyricist", "like", "%" + query.author + "%"), - ])); - } - - const res = await q.execute(); - return res.map(({ pieceId }) => pieceId); - }, { - query: Model.Piece_Query, - response: { - 200: t.Array(Model.PieceId), - 401: t.Literal("Session invalid or expired"), - }, - }) - - .get("/piece/:pieceId", async ({ db, params: { pieceId }, session: { idToken } }) => { - - if (Option.isNone(idToken)) { - return error("Unauthorized", "Session invalid or expired"); - } - - const piece = await db - .selectFrom("Piece") - .selectAll() - .where("pieceId", "=", pieceId) - .executeTakeFirst(); - - if (piece === undefined) { - return error("Not Found", new Response() as unknown as void); - } - - const attachments = await db - .selectFrom("Attachment") - .selectAll() - .where("pieceId", "=", pieceId) - .execute(); - - return { - ...piece, - attachments: attachments.map(({ sha256, ...rest }) => ({ - sha256: Sha256_Hex(Buffer.from(sha256).toString("hex")), - ...rest, - })), - }; - }, { - params: t.Object({ - pieceId: Model.PieceId, - }), - response: { - 200: Model.Piece, - 401: t.Literal("Session invalid or expired"), - 404: t.Void(), - }, - }) - - .put("/piece/:pieceId", async ({ db, body: { name, composer, lyricist, arranger }, params: { pieceId }, session: { idToken, roles } }) => { - - if (Option.isNone(idToken)) { - return error("Unauthorized", "Session invalid or expired"); - } - - if (!roles.includes("Editor")) { - return error("Forbidden", "Must be an Editor"); - } - - const res = await db - .updateTable("Piece") - .set({ name, composer, lyricist, arranger, modifiedBy: idToken.value.payload.oid, modifiedAt: sql`datetime()` }) - .where("pieceId", "=", pieceId) - .returningAll() - .execute(); - - if (res.length === 0) { - return error("Not Found", new Response() as unknown as void); - } - - const attachments = await db - .selectFrom("Attachment") - .selectAll() - .where("pieceId", "=", pieceId) - .execute(); - - return { - ...res[0], - attachments: attachments.map(({ sha256, ...rest }) => ({ - sha256: Sha256_Hex(Buffer.from(sha256).toString("hex")), - ...rest, - })), - }; - }, { - body: t.Object({ - name: t.String({ minLength: 1 }), - composer: t.Nullable(t.String({ minLength: 1 })), - lyricist: t.Nullable(t.String({ minLength: 1 })), - arranger: t.Nullable(t.String({ minLength: 1 })), - }), - params: t.Object({ - pieceId: Model.PieceId, - }), - response: { - 200: Model.Piece, - 401: t.Literal("Session invalid or expired"), - 403: t.Literal("Must be an Editor"), - 404: t.Void(), - }, - }) - - .delete("/piece/:pieceId", async ({ db, params: { pieceId }, set, session: { idToken, roles } }) => { - - if (Option.isNone(idToken)) { - return error("Unauthorized", "Session invalid or expired"); - } - - if (!roles.includes("Editor")) { - return error("Forbidden", "Must be an Editor"); - } - - const res = await db - .deleteFrom("Piece") - .where("pieceId", "=", pieceId) - .returningAll() - .execute(); - - if (res.length === 0) { - return error("Not Found", new Response() as unknown as void); - } - - set.status = "No Content"; - }, { - params: t.Object({ - pieceId: Model.PieceId, - }), - response: { - 204: t.Void(), - 401: t.Literal("Session invalid or expired"), - 404: t.Void(), - }, - }) - - // --- MARK: ATTACHMENT CRUD ------------------------------------------- - - .post("/piece/:pieceId/attachment", async ({ db, body: { filename, mediaType, data }, params: { pieceId }, session: { idToken, roles } }) => { - - if (Option.isNone(idToken)) { - return error("Unauthorized", "Session invalid or expired"); - } - - if (!roles.includes("Editor")) { - return error("Forbidden", "Must be an Editor"); - } - - const attachmentId = AttachmentId(Bun.randomUUIDv7()); - const dataArray = new Uint8Array(await data.arrayBuffer()); - - const sha256 = Sha256_Bin(new Uint8Array(Bun.SHA256.byteLength)); - Bun.SHA256.hash(dataArray, sha256); - - await db - .insertInto("File") - .values({ sha256, data: dataArray }) - .onConflict((cb) => cb.column("sha256").doNothing()) - .execute(); - - const res = await db - .insertInto("Attachment") - .values({ attachmentId, pieceId, sha256, filename, mediaType, createdBy: idToken.value.payload.oid, createdAt: sql`datetime()` }) - .returningAll() - .executeTakeFirstOrThrow(); - - return { - ...res, - sha256: Sha256_Hex(Buffer.from(res.sha256).toString("hex")), - }; - }, { - body: t.Object({ - filename: t.String({ minLength: 1 }), - mediaType: t.String({ minLength: 1 }), - data: t.File(), - }), - params: t.Object({ - pieceId: Model.PieceId, - }), - response: { - 200: Model.Attachment, - 401: t.Literal("Session invalid or expired"), - 403: t.Literal("Must be an Editor"), - }, - }) - - /* NOTE The piece ID is reduntant, because attachment IDs are unique for - * the entire DB, not just per piece. However, we consider a piece to be - * the sole owner of an attachment, i.e. attachments are not shared - * (attachments are deduplicated on file storage level by their SHA-256 - * hash). Thus, we reflect the ownership in the URLs. - */ - - .get("/piece/:pieceId/attachment/:attachmentId", async ({ db, params: { pieceId, attachmentId }, session: { idToken }, set }) => { - - if (Option.isNone(idToken)) { - return error("Unauthorized", "Session invalid or expired"); - } - - const res = await db - .selectFrom("File") - .innerJoin("Attachment", "File.sha256", "Attachment.sha256") - .select(["Attachment.filename", "Attachment.mediaType", "File.data"]) - .where((eb) => eb.and([ - eb("Attachment.pieceId", "=", pieceId), - eb("Attachment.attachmentId", "=", attachmentId), - ])) - .executeTakeFirst(); - - if (res === undefined) { - return error("Not Found", new Response() as unknown as void); - } - - set.headers["content-disposition"] = `attachment; filename*=UTF-8''${encodeURIComponent(res.filename)}`; - set.headers["content-type"] = res.mediaType; - return new File([res.data], res.filename, { type: res.mediaType }); - }, { - params: t.Object({ - pieceId: Model.PieceId, - attachmentId: Model.AttachmentId, - }), - response: { - 200: t.File(), - 401: t.Literal("Session invalid or expired"), - 404: t.Void(), - }, - }) - - .put("/piece/:pieceId/attachment/:attachmentId", async ({ db, body: { filename }, params: { pieceId, attachmentId }, session: { idToken, roles } }) => { - - if (Option.isNone(idToken)) { - return error("Unauthorized", "Session invalid or expired"); - } - - if (!roles.includes("Editor")) { - return error("Forbidden", "Must be an Editor"); - } - - const res = await db - .updateTable("Attachment") - .set({ filename, modifiedBy: idToken.value.payload.oid, modifiedAt: sql`datetime()` }) - .where((eb) => eb.and([ - eb("pieceId", "=", pieceId), - eb("attachmentId", "=", attachmentId), - ])) - .returningAll() - .execute(); - - if (res.length === 0) { - return error("Not Found", new Response() as unknown as void); - } - - return { - ...res[0], - sha256: Sha256_Hex(Buffer.from(res[0].sha256).toString("hex")), - }; - }, { - body: t.Object({ - filename: t.String({ minLength: 1 }), - }), - params: t.Object({ - pieceId: Model.PieceId, - attachmentId: Model.AttachmentId, - }), - response: { - 200: Model.Attachment, - 401: t.Literal("Session invalid or expired"), - 403: t.Literal("Must be an Editor"), - 404: t.Void(), - }, - }) - - .delete("/piece/:pieceId/attachment/:attachmentId", async ({ db, params: { pieceId, attachmentId }, set, session: { idToken, roles } }) => { - - if (Option.isNone(idToken)) { - return error("Unauthorized", "Session invalid or expired"); - } - - if (!roles.includes("Editor")) { - return error("Forbidden", "Must be an Editor"); - } - - const res = await db - .deleteFrom("Attachment") - .where((eb) => eb.and([ - eb("pieceId", "=", pieceId), - eb("attachmentId", "=", attachmentId), - ])) - .returningAll() - .execute(); - - if (res.length === 0) { - return error("Not Found", new Response() as unknown as void); - } - - set.status = "No Content"; - }, { - params: t.Object({ - pieceId: Model.PieceId, - attachmentId: Model.AttachmentId, - }), - response: { - 204: t.Void(), - 401: t.Literal("Session invalid or expired"), - 403: t.Literal("Must be an Editor"), - 404: t.Void(), - }, - }) - - // --- MARK: REPERTOIRE CRUD ------------------------------------------- - - .post("/repertoire", async ({ db, body: { name, entries }, session: { idToken, roles } }) => { - - if (Option.isNone(idToken)) { - return error("Unauthorized", "Session invalid or expired"); - } - - if (!roles.includes("Editor")) { - return error("Forbidden", "Must be an Editor"); - } - - const repertoireId = RepertoireId(Bun.randomUUIDv7()); - - const repertoire = await db - .insertInto("Repertoire") - .values({ repertoireId, name, createdBy: idToken.value.payload.oid, createdAt: sql`datetime()` }) - .returningAll() - .executeTakeFirstOrThrow(); - - const dbEntries: Db.RepertoireEntry[] = []; - for (let i = 0; i < entries.length; ++i) { - const entry = entries[i]; - const dbEntry = await db - .insertInto("RepertoireEntry") - .values({ ...entry, repertoireId, order: i }) - .returningAll() - .executeTakeFirstOrThrow(); - dbEntries.push(dbEntry); - } - - return { - ...repertoire, - entries: dbEntries, - }; - }, { - body: t.Object({ - name: t.String({ minLength: 1 }), - entries: t.Array(t.Object({ - pieceId: Model.PieceId, - })), - }), - response: { - 401: t.Literal("Session invalid or expired"), - 403: t.Literal("Must be an Editor"), - }, - }) - - .get("/repertoire", async ({ db, query, session: { idToken } }) => { - - if (Option.isNone(idToken)) { - return error("Unauthorized", "Session invalid or expired"); - } - - let q = db - .selectFrom("Repertoire") - .select("repertoireId") - .orderBy(["name"]) - .offset(query.offset ?? 0) - .limit(query.limit ?? 100); - - if (query.name !== undefined) { - q = q.where("name", "like", "%" + query.name + "%"); - } - - const res = await q.execute(); - return res.map(({ repertoireId }) => repertoireId); - }, { - query: Model.Repertoire_Query, - response: { - 200: t.Array(Model.RepertoireId), - 401: t.Literal("Session invalid or expired"), - }, - }) - - .get("/repertoire/:repertoireId", async ({ db, params: { repertoireId }, session: { idToken } }) => { - - if (Option.isNone(idToken)) { - return error("Unauthorized", "Session invalid or expired"); - } - - const repertoire = await db - .selectFrom("Repertoire") - .selectAll() - .where("repertoireId", "=", repertoireId) - .executeTakeFirst(); - - if (repertoire === undefined) { - return error("Not Found", new Response() as unknown as void); - } - - const entries = await db - .selectFrom("RepertoireEntry") - .select(["pieceId"]) - .where("repertoireId", "=", repertoireId) - .orderBy("order") - .execute(); - - return { - ...repertoire, - entries: entries.map(({ pieceId }) => pieceId), - }; - }, { - params: t.Object({ - repertoireId: Model.RepertoireId, - }), - response: { - 200: Model.Repertoire, - 401: t.Literal("Session invalid or expired"), - 404: t.Void(), - }, - }) - - .put("/repertoire/:repertoireId", async ({ db, body: { name, entries }, params: { repertoireId }, session: { idToken, roles } }) => { - - if (Option.isNone(idToken)) { - return error("Unauthorized", "Session invalid or expired"); - } - - if (!roles.includes("Editor")) { - return error("Forbidden", "Must be an Editor"); - } - - const res = await db - .updateTable("Repertoire") - .set({ name, modifiedBy: idToken.value.payload.oid, modifiedAt: sql`datetime()` }) - .where("repertoireId", "=", repertoireId) - .returningAll() - .execute(); - - if (res.length === 0) { - return error("Not Found", new Response() as unknown as void); - } - - await db - .deleteFrom("RepertoireEntry") - .where("repertoireId", "=", repertoireId) - .execute(); - - for (let i = 0; i < entries.length; ++i) { - const entry = entries[i]; - await db - .insertInto("RepertoireEntry") - .values({ pieceId: entry, repertoireId, order: i }) - .returning(["pieceId"]) - .executeTakeFirstOrThrow(); - } - - return { - ...res[0], - entries, - }; - }, { - body: t.Object({ - name: t.String({ minLength: 1 }), - entries: t.Array(Model.PieceId), - }), - params: t.Object({ - repertoireId: Model.RepertoireId, - }), - response: { - 200: Model.Repertoire, - 401: t.Literal("Session invalid or expired"), - 403: t.Literal("Must be an Editor"), - 404: t.Void(), - }, - }) - - .delete("/repertoire/:repertoireId", async ({ db, params: { repertoireId }, set, session: { idToken, roles } }) => { - - if (Option.isNone(idToken)) { - return error("Unauthorized", "Session invalid or expired"); - } - - if (!roles.includes("Editor")) { - return error("Forbidden", "Must be an Editor"); - } - - const res = await db - .deleteFrom("Repertoire") - .where("repertoireId", "=", repertoireId) - .returningAll() - .execute(); - - if (res.length === 0) { - return error("Not Found"); - } - - set.status = "No Content"; - }, { - params: t.Object({ - repertoireId: Model.RepertoireId, - }), - response: { - 204: t.Void(), - 401: t.Literal("Session invalid or expired"), - 403: t.Literal("Must be an Editor"), - 404: t.Void(), - }, - // eslint-disable-next-line @stylistic/comma-dangle -- a comma would confuse the TS compiler here - }) - ) - - .get("*", () => Bun.file("packages/frontend/build/index.html")); - -// ------------------------------------------------------------------------- - -app.listen(config.PORT); -export type App = typeof app; + return response; + }, + "/*": homepage, + }, + websocket: { + message: () => { }, + }, +}); diff --git a/packages/backend/src/auth.ts b/packages/backend/src/auth.ts deleted file mode 100644 index 277b894..0000000 --- a/packages/backend/src/auth.ts +++ /dev/null @@ -1,230 +0,0 @@ -import { UserId } from "common"; -import { DateTime, Duration, Effect, Option, pipe, Redacted } from "effect"; -import { constant } from "effect/Function"; -import { config } from "./config"; -import * as Model from "./model"; -import { Db } from "./services/db"; -import { Session } from "./services/session"; - -export const OAUTH_SCOPE = "email offline_access openid profile https://graph.microsoft.com/User.Read.All"; -export const REDIRECT_URI = config.NODE_ENV === "production" ? `https://${config.HOSTNAME}/api/v1/login` : "http://localhost:3000/api/v1/login"; - -export const EXPIRATION_BUFFER = Duration.seconds(10); - -export interface OAuthConfiguration { - readonly authorizationEndpoint: string; - readonly tokenEndpoint: string; -} - -export const INTERNAL_OAUTH_CONFIGURATION: OAuthConfiguration = Object.freeze({ - authorizationEndpoint: `https://login.microsoftonline.com/${config.TENANT_ID}/oauth2/v2.0/authorize`, - tokenEndpoint: `https://login.microsoftonline.com/${config.TENANT_ID}/oauth2/v2.0/token`, -}); - -export const EXTERNAL_OAUTH_CONFIGURATION: OAuthConfiguration = Object.freeze({ - authorizationEndpoint: `https://${config.TENANT_SUBDOMAIN}.ciamlogin.com/${config.TENANT_ID}/oauth2/v2.0/authorize`, - tokenEndpoint: `https://${config.TENANT_SUBDOMAIN}.ciamlogin.com/${config.TENANT_ID}/oauth2/v2.0/token`, -}); - -export namespace makeAuthorizationUrl { - export interface Args { - readonly external: boolean; - } -} - -export const makeAuthorizationUrl = Effect.fn("makeAuthorizationUrl")( - function* ({ external }: makeAuthorizationUrl.Args) { - const { db, execute } = yield* Db; - const sessionId = yield* Session; - - const { codeVerifier, codeChallenge } = generateCodeVerifier(); - const state = generateRandomState(); - - yield* db - .updateTable("Session") - .set({ - codeVerifier, - state, - external: external ? 1 : 0, - accessToken: null, - idToken: null, - refreshToken: null, - }) - .where("sessionId", "=", sessionId) - .$call(execute); - - const { authorizationEndpoint } = external ? EXTERNAL_OAUTH_CONFIGURATION : INTERNAL_OAUTH_CONFIGURATION; - - const url = new URL(authorizationEndpoint); - url.searchParams.set("client_id", config.CLIENT_ID); - url.searchParams.set("response_type", "code"); - url.searchParams.set("redirect_uri", REDIRECT_URI); - url.searchParams.set("scope", OAUTH_SCOPE); - url.searchParams.set("response_mode", "form_post"); - url.searchParams.set("state", state); - url.searchParams.set("prompt", "select_account"); - url.searchParams.set("code_challenge", codeChallenge); - url.searchParams.set("code_challenge_method", "S256"); - - return url.toString(); - }, -); - -export namespace revaildateTokens { - export interface Args { - readonly accessToken: Option.Option; - readonly idToken: Option.Option; - readonly refreshToken: Option.Option; - readonly external: boolean; - } - - export interface Result { - readonly accessToken: Option.Option<{ - readonly token: string, - readonly payload: Model.AccessTokenPayload, - }>; - readonly idToken: Option.Option<{ - readonly token: string, - readonly payload: Model.IdTokenPayload, - }>; - readonly refreshToken: Option.Option; - readonly userId: Option.Option; - readonly roles: readonly string[]; - } -} - -export const revalidateTokens = Effect.fn("revaildateTokens")( - function* ({ accessToken, idToken, refreshToken, external }: revaildateTokens.Args) { - - const accessTokenPayload = Option.map(accessToken, getJwtTokenPayload); - const idTokenPayload = Option.map(accessToken, getJwtTokenPayload); - - const expirationThreshold = yield* pipe( - DateTime.now, - Effect.map(DateTime.addDuration(EXPIRATION_BUFFER)), - ); - - // Token expired or missing - if (Option.match(accessTokenPayload, { - onNone: constant(false), - onSome: (atp) => DateTime.greaterThan(expirationThreshold, DateTime.unsafeMake(1000 * atp.exp)), - }) || Option.match(idTokenPayload, { - onNone: constant(false), - onSome: (itp) => DateTime.greaterThan(expirationThreshold, DateTime.unsafeMake(1000 * itp.exp)), - })) { - - accessToken = Option.none(); - idToken = Option.none(); - - // try refreshing - if (Option.isSome(refreshToken)) { - const refreshTokenValue = refreshToken.value; - const { tokenEndpoint } = external ? EXTERNAL_OAUTH_CONFIGURATION : INTERNAL_OAUTH_CONFIGURATION; - - const res = yield* Effect.promise((signal) => fetch(tokenEndpoint, { - method: "POST", - signal, - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - body: new URLSearchParams({ - "client_id": config.CLIENT_ID, - "grant_type": "refresh_token", - "refresh_token": refreshTokenValue, - "client_secret": Redacted.value(config.CLIENT_SECRET), - }).toString(), - })); - - const json = (yield* Effect.promise(() => res.json())) as { access_token: string, refresh_token: string, id_token: string }; - - accessToken = Option.some(json.access_token); - idToken = Option.some(json.id_token); - refreshToken = Option.some(json.refresh_token); - } - } - - const it = Option.map(idToken, (it) => Object.freeze({ - token: it, - payload: getJwtTokenPayload(it), - })); - - const res: revaildateTokens.Result = Object.freeze({ - accessToken: Option.map(accessToken, (at) => Object.freeze({ - token: at, - payload: getJwtTokenPayload(at), - })), - idToken: it, - refreshToken, - userId: Option.map(it, ({ payload: { oid } }) => oid), - roles: Option.match(it, { - onNone: constant(Object.freeze([])), - onSome: ({ payload: { roles } }) => roles ?? Object.freeze([]), - }), - }); - - return res; - }, -); - -function getJwtTokenPayload(token: string): O { - return JSON.parse(Buffer.from(token.split(".")[1], "base64url").toString("utf-8")) as O; -} - -export namespace generateCodeVerifier { - export interface Result { - codeVerifier: string; - codeChallenge: string; - } -} - -export function generateCodeVerifier(byteLength: number = 32): generateCodeVerifier.Result { - const codeVerifierBytes = new Uint8Array(byteLength); - crypto.getRandomValues(codeVerifierBytes); - const codeVerifier = Buffer.from(codeVerifierBytes).toString("base64url"); - - const codeVerifierAsciiBuffer = Buffer.from(codeVerifier, "ascii"); - const codeVerifierAsciiArray = new Uint8Array( - codeVerifierAsciiBuffer.buffer, - codeVerifierAsciiBuffer.byteOffset, - codeVerifierAsciiBuffer.length, - ); - const codeChallenge = Bun.SHA256.hash(codeVerifierAsciiArray, "base64url"); - - return { codeVerifier, codeChallenge }; -} - -export function generateRandomState(byteLength: number = 32): string { - const array = new Uint8Array(byteLength); - crypto.getRandomValues(array); - const state = Buffer.from(array).toString("base64url"); - return state; -} - -export namespace getUser { - export interface Args { - readonly accessToken: string; - readonly userId: UserId; - } - - export interface Result { - readonly displayName: string; - } -} - -export const getUser = Effect.fn("getUser")( - function* ({ accessToken, userId }: getUser.Args) { - const res = yield* Effect.promise((signal) => fetch(`https://graph.microsoft.com/v1.0/users/${userId}?$select=displayName`, { - signal, - headers: { - "Authorization": `Bearer ${accessToken}`, - }, - })); - - if (res.status === 404) { - return Option.none(); - } - - const json = (yield* Effect.promise(() => res.json())) as getUser.Result; - return Option.some(json); - }, -); diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 10d4504..c9b327c 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -11,7 +11,7 @@ export const Config = Schema.Struct({ ), DB_PATH: pipe( Schema.String, - Schema.optional, + Schema.optionalWith({ default: constant("db.sqlite3") }), ), HOSTNAME: Schema.String, NODE_ENV: pipe( @@ -22,8 +22,10 @@ export const Config = Schema.Struct({ Schema.NumberFromString, Schema.optionalWith({ default: constant(3000) }), ), - TENANT_ID: Schema.UUID, - TENANT_SUBDOMAIN: Schema.String, + OAUTH_AUTHORIZATION_ENDPOINT: Schema.String, + OAUTH_TOKEN_ENDPOINT: Schema.String, + POCKET_ID_API_ORIGIN: Schema.String, + POCKET_ID_API_KEY: Schema.String, }); export type Config = typeof Config.Type; diff --git a/packages/backend/src/model.ts b/packages/backend/src/model.ts deleted file mode 100644 index 9ac5bb0..0000000 --- a/packages/backend/src/model.ts +++ /dev/null @@ -1,122 +0,0 @@ -import * as Common from "common"; -import { unsafeCoerce } from "effect"; -import { t } from "elysia"; - -export interface AccessTokenPayload { - readonly aud: string; - readonly iss: string; - readonly iat: number; - readonly nbf: number; - readonly exp: number; - readonly name: string; - readonly oid: Common.UserId; -} - -export interface IdTokenPayload { - readonly aud: string; - readonly iss: string; - readonly iat: number; - readonly nbf: number; - readonly exp: number; - readonly name: string; - readonly oid: Common.UserId; - readonly roles?: readonly string[]; -} - -const brandedString = () => t.Transform(t.String()) - .Decode(unsafeCoerce) - .Encode(unsafeCoerce); - -export const Sha256_Hex = brandedString(); -export const AttachmentId = brandedString(); -export const PieceId = brandedString(); -export const RepertoireId = brandedString(); -export const RequestId = brandedString(); -export const SessionId = brandedString(); -export const UserId = brandedString(); - -const SystemInformation = Object.freeze({ - createdBy: t.Nullable(UserId), - createdAt: t.String(), - modifiedBy: t.Nullable(UserId), - modifiedAt: t.Nullable(t.String()), -}); - -const Pagination = Object.freeze({ - offset: t.Optional(t.Numeric({ minimum: 0 })), - limit: t.Optional(t.Numeric({ minimum: 1, maximum: 100 })), -}); - -export const AccessLog = t.Object({ - requestId: RequestId, - timestamp: t.String(), - method: t.String(), - pathname: t.String(), - query: t.String(), - ip: t.Nullable(t.String()), -}); - -export const Attachment = t.Object({ - attachmentId: AttachmentId, - pieceId: PieceId, - filename: t.String(), - sha256: Sha256_Hex, - mediaType: t.String(), - ...SystemInformation, -}); - -export const Me = t.Object({ - userId: UserId, - username: t.String(), - roles: t.Array(t.String()), -}); - -export const Piece = t.Object({ - pieceId: PieceId, - name: t.String({ minLength: 1 }), - composer: t.Nullable(t.String({ minLength: 1 })), - lyricist: t.Nullable(t.String({ minLength: 1 })), - arranger: t.Nullable(t.String({ minLength: 1 })), - attachments: t.Array(Attachment), - ...SystemInformation, -}); - -export const Piece_Post = t.Object({ - name: t.String({ minLength: 1 }), - composer: t.Nullable(t.String({ minLength: 1 })), - lyricist: t.Nullable(t.String({ minLength: 1 })), - arranger: t.Nullable(t.String({ minLength: 1 })), -}); - -export const Piece_Query = t.Object({ - name: t.Optional(t.String()), - author: t.Optional(t.String()), - ...Pagination, -}); - -export const Repertoire = t.Object({ - repertoireId: RepertoireId, - name: t.String(), - entries: t.Array(PieceId), - ...SystemInformation, -}); - -export const Repertoire_Query = t.Object({ - name: t.Optional(t.String()), - ...Pagination, -}); - -export const User = t.Object({ - userId: UserId, - displayName: t.String(), -}); - -export type AccessLog = typeof AccessLog.static; -export type Attachment = typeof Attachment.static; -export type Me = typeof Me.static; -export type Piece = typeof Piece.static; -export type Piece_Post = typeof Piece_Post.static; -export type Piece_Query = typeof Piece_Query.static; -export type Repertoire = typeof Repertoire.static; -export type Repertoire_Query = typeof Repertoire_Query.static; -export type User = typeof User.static; diff --git a/packages/backend/src/services/Authentication.ts b/packages/backend/src/services/Authentication.ts new file mode 100644 index 0000000..e906593 --- /dev/null +++ b/packages/backend/src/services/Authentication.ts @@ -0,0 +1,397 @@ +import { config } from "backend/config"; +import { BunRequest } from "bun"; +import { SessionId, UserId } from "common"; +import { fetch, FetchError } from "common/Fetch"; +import { Me, NotFound, Other, Role, Unauthenticated } from "common/the_api"; +import { Context, DateTime, Duration, Effect, HashMap, HashSet, Layer, Option, pipe, Redacted, Schema } from "effect"; +import { constant } from "effect/Function"; +import { sql } from "kysely"; +import * as Database from "./Database"; + +export interface AuthenticationInterface { + readonly me: Effect.Effect; + readonly logout: Effect.Effect; + readonly sessionId: SessionId; + readonly getUser: (userId: UserId) => Effect.Effect; +} + +export class Authentication extends Context.Tag("Authentication")() { } + +export const OAUTH_SCOPE = "email openid profile offline_access"; +export const REDIRECT_URI = config.NODE_ENV === "production" ? `https://${config.HOSTNAME}/login` : "http://localhost:3000/login"; + +export const EXPIRATION_BUFFER = Duration.seconds(10); + +export const SESSION_COOKIE_NAME = "sessionId"; + +const ALL_ROLES = HashSet.fromIterable(Object.values(Role)); + +export const Live = (request: BunRequest) => Layer.effect(Authentication, Effect.gen(function* () { + const database = yield* Database.Database; + + yield* database + .deleteFrom("Session") + .where(sql`datetime()`, ">=", "expiresAt") + .$call(Database.execute); + + const sessionId = pipe( + request.cookies.get(SESSION_COOKIE_NAME), + Option.fromNullable, + Option.map(SessionId.make), + Option.getOrElse(generateSessionId), + ); + + request.cookies.set(SESSION_COOKIE_NAME, sessionId, { + expires: yield* pipe( + DateTime.now, + Effect.map(DateTime.addDuration("7 days")), + Effect.map(DateTime.toDateUtc), + ), + httpOnly: true, + sameSite: "none", + secure: true, + }); + + const returning = [ + "sessionId", + "accessToken", + "codeVerifier", + "idToken", + "refreshToken", + "state", + ] as const; + + const session = yield* database + .updateTable("Session") + .set({ expiresAt: sql`datetime('now', '+7 days') ` }) + .where("sessionId", "=", sessionId) + .returning(returning) + .$call(Database.executeTakeFirst) + .pipe(Effect.catchTag("NoSuchElementException", () => database + .insertInto("Session") + .values({ sessionId, expiresAt: sql`datetime('now', '+7 days')` }) + .returning(returning) + .$call(Database.executeTakeFirstOrDie) + )); + + const { accessToken, idToken, refreshToken } = yield* revalidateTokens({ + accessToken: Option.fromNullable(session.accessToken), + idToken: Option.fromNullable(session.idToken), + refreshToken: Option.fromNullable(session.refreshToken), + }); + + yield* database + .updateTable("Session") + .set({ + accessToken: pipe( + accessToken, + Option.map((at) => at.token), + Option.getOrNull, + ), + idToken: pipe( + idToken, + Option.map((it) => it.token), + Option.getOrNull, + ), + refreshToken: Option.getOrNull(refreshToken), + }) + .$call(Database.execute); + + const state = Object.freeze({ + sessionId: session.sessionId, + accessToken, + idToken, + refreshToken, + codeVerifier: Option.fromNullable(session.codeVerifier), + state: Option.fromNullable(session.state), + }); + + return Object.freeze({ + me: pipe( + state.idToken, + Option.map(({ payload }) => { + const userData: Me = Object.freeze({ + userId: payload.sub, + displayName: payload.display_name, + roles: ALL_ROLES, + }); + return userData; + }), + Option.match({ + onNone: () => Effect.fail(Unauthenticated.make()), + onSome: Effect.succeed, + }), + ), + logout: database + .updateTable("Session") + .set({ + accessToken: null, + codeVerifier: null, + expiresAt: sql`datetime('now', '+7 days')`, + idToken: null, + refreshToken: null, + state: null, + }) + .where("sessionId", "=", state.sessionId) + .$call(Database.execute), + sessionId: state.sessionId, + getUser: (userId) => pipe( + getDisplayName(userId), + Effect.map((displayName) => Object.freeze({ userId, displayName })), + ), + }); +})); + +export const Test = (me: Option.Option, other: HashMap.HashMap) => Layer.sync(Authentication, constant(Object.freeze({ + me: Option.match(me, { + onNone: () => Effect.fail(Unauthenticated.make()), + onSome: (me) => Effect.succeed(me), + }), + logout: Effect.void, + sessionId: generateSessionId(), + getUser: Option.match(me, { + onNone: () => constant(Effect.fail(Unauthenticated.make())), + onSome: () => (userId) => pipe( + other, + HashMap.get(userId), + Option.match({ + onNone: () => Effect.fail(NotFound.make()), + onSome: Effect.succeed, + }), + ), + }), +}))); + +export const AccessTokenPayload = Schema.Struct({ + aud: pipe( + Schema.String, + Schema.HashSet, + ), + exp: Schema.DateTimeUtc, + iat: Schema.DateTimeUtc, + iss: Schema.String, + sub: UserId, +}); + +export const IdTokenPayload = Schema.Struct({ + aud: pipe( + Schema.String, + Schema.HashSet, + ), + exp: Schema.DateTimeUtc, + iat: Schema.DateTimeUtc, + iss: Schema.String, + sub: UserId, + + name: Schema.String, + given_name: Schema.String, + family_name: Schema.String, + display_name: Schema.String, + preferred_username: Schema.String, + + email: Schema.String, + email_verified: Schema.Boolean, + + picture: Schema.String, +}); + +export type AccessTokenPayload = typeof AccessTokenPayload.Type; +export type IdTokenPayload = typeof IdTokenPayload.Type; + +function generateCodeVerifier(byteLength: number = 32) { + const codeVerifierBytes = new Uint8Array(byteLength); + crypto.getRandomValues(codeVerifierBytes); + const codeVerifier = Buffer.from(codeVerifierBytes).toString("base64url"); + + const codeVerifierAsciiBuffer = Buffer.from(codeVerifier, "ascii"); + const codeVerifierAsciiArray = new Uint8Array( + codeVerifierAsciiBuffer.buffer, + codeVerifierAsciiBuffer.byteOffset, + codeVerifierAsciiBuffer.length, + ); + const codeChallenge = Bun.SHA256.hash(codeVerifierAsciiArray, "base64url"); + + return { codeVerifier, codeChallenge }; +} + +function generateSessionId(byteLength: number = 32): SessionId { + const array = new Uint8Array(byteLength); + crypto.getRandomValues(array); + const string = Buffer.from(array).toString("base64url"); + return SessionId.make(string); +}; + +function generateRandomState(byteLength: number = 32): string { + const array = new Uint8Array(byteLength); + crypto.getRandomValues(array); + const state = Buffer.from(array).toString("base64url"); + return state; +} + +const getJwtTokenPayload = (schema: Schema.Schema) => { + const decoder = Schema.decodeUnknown(schema); + return (token: string) => { + const json = JSON.parse(Buffer.from(token.split(".")[1], "base64url").toString("utf-8")); + return pipe( + decoder(json), + Effect.orDie, + ); + }; +}; + +const getDisplayName = (userId: UserId) => Effect.gen(function* () { + const url = new URL(`/api/users/${userId}`, config.POCKET_ID_API_ORIGIN); + const res = yield* fetch(url, { + headers: { + "X-API-KEY": config.POCKET_ID_API_KEY, + }, + }); + + if (!res.ok) { + return yield* Effect.fail(NotFound.make()); + } + + const json = yield* Effect.promise(() => res.json()); + const displayName: string = json.displayName; + return displayName; +}); + +export const makeAuthorizationUrl = (sessionId: SessionId) => Effect.gen(function* () { + const database = yield* Database.Database; + + const { codeVerifier, codeChallenge } = generateCodeVerifier(); + const state = generateRandomState(); + + yield* database + .updateTable("Session") + .set({ + codeVerifier, + state, + accessToken: null, + idToken: null, + refreshToken: null, + }) + .where("sessionId", "=", sessionId) + .$call(Database.execute); + + const url = new URL(config.OAUTH_AUTHORIZATION_ENDPOINT); + url.searchParams.set("client_id", config.CLIENT_ID); + url.searchParams.set("response_type", "code"); + url.searchParams.set("redirect_uri", REDIRECT_URI); + url.searchParams.set("scope", OAUTH_SCOPE); + url.searchParams.set("response_mode", "form_post"); + url.searchParams.set("state", state); + url.searchParams.set("code_challenge", codeChallenge); + url.searchParams.set("code_challenge_method", "S256"); + + return url.toString(); +}); + +namespace revaildateTokens { + export interface Args { + readonly accessToken: Option.Option; + readonly idToken: Option.Option; + readonly refreshToken: Option.Option; + } + + export interface Result { + readonly accessToken: Option.Option<{ + readonly token: string, + readonly payload: AccessTokenPayload, + }>; + readonly idToken: Option.Option<{ + readonly token: string, + readonly payload: IdTokenPayload, + }>; + readonly refreshToken: Option.Option; + } +} + +const revalidateTokens = ({ accessToken, idToken, refreshToken }: revaildateTokens.Args) => Effect.gen(function* () { + + const accessTokenPayload = yield* pipe( + accessToken, + Effect.transposeMapOption(getJwtTokenPayload(AccessTokenPayload)), + ); + + const idTokenPayload = yield* pipe( + idToken, + Effect.transposeMapOption(getJwtTokenPayload(IdTokenPayload)), + ); + + const expirationThreshold = DateTime.addDuration( + DateTime.unsafeNow(), + EXPIRATION_BUFFER, + ); + + // Token expired or missing + if (Option.match(accessTokenPayload, { + onNone: constant(false), + onSome: (atp) => DateTime.greaterThan(expirationThreshold, atp.exp), + }) || Option.match(idTokenPayload, { + onNone: constant(false), + onSome: (itp) => DateTime.greaterThan(expirationThreshold, itp.exp), + })) { + + accessToken = Option.none(); + idToken = Option.none(); + + // try refreshing + if (Option.isSome(refreshToken)) { + const refreshTokenValue = refreshToken.value; + + const res = yield* fetch(config.OAUTH_TOKEN_ENDPOINT, { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: new URLSearchParams({ + "client_id": config.CLIENT_ID, + "grant_type": "refresh_token", + "refresh_token": refreshTokenValue, + "client_secret": Redacted.value(config.CLIENT_SECRET), + }).toString(), + }); + + const json: { + access_token: string, + refresh_token: string, + id_token: string, + } = yield* Effect.promise(() => res.json()); + + accessToken = Option.some(json.access_token); + idToken = Option.some(json.id_token); + refreshToken = Option.some(json.refresh_token); + } + } + + const it = yield* pipe( + idToken, + Option.map((it) => pipe( + Effect.Do, + Effect.let("token", () => it), + Effect.bind("payload", ({ token }) => getJwtTokenPayload(IdTokenPayload)(token)), + )), + Effect.transposeOption, + Effect.map(Option.map((it) => Object.freeze(it))), + ); + + const at = yield* pipe( + accessToken, + Option.map((at) => pipe( + Effect.Do, + Effect.let("token", () => at), + Effect.bind("payload", ({ token }) => getJwtTokenPayload(AccessTokenPayload)(token)), + )), + Effect.transposeOption, + Effect.map(Option.map((at) => Object.freeze(at))), + ); + + const res: revaildateTokens.Result = Object.freeze({ + accessToken: at, + idToken: it, + refreshToken, + }); + + return res; +}); diff --git a/packages/backend/src/database.ts b/packages/backend/src/services/Database.ts similarity index 62% rename from packages/backend/src/database.ts rename to packages/backend/src/services/Database.ts index 4fe1cfa..b12dfe9 100644 --- a/packages/backend/src/database.ts +++ b/packages/backend/src/services/Database.ts @@ -1,18 +1,12 @@ import { Database as BunSqliteDatabase } from "bun:sqlite"; -import { AttachmentId, PieceId, RepertoireId, RequestId, SessionId, Sha256_Bin, UserId } from "common"; -import { HashSet } from "effect"; -import { ColumnType, CompiledQuery, CreateTableBuilder, Kysely, Selectable } from "kysely"; +import { AttachmentId, PieceId, RepertoireId, RequestId, SessionId, Sha256, UserId } from "common"; +import { Cause, Context, Effect, Either, HashSet, Layer, pipe, Runtime } from "effect"; +import { ColumnType, CompiledQuery, CreateTableBuilder, Insertable, Kysely, Selectable, Transaction } from "kysely"; import { BunSqliteDialect } from "kysely-bun-sqlite"; -export function generateSessionId(byteLength: number = 32): SessionId { - const array = new Uint8Array(byteLength); - crypto.getRandomValues(array); - const string = Buffer.from(array).toString("base64url"); - return SessionId(string); -}; +// --- MARK: KYSELY SCHEMA ----------------------------------------------------- -export interface Database { - AccessLog: AccessLogTable; +export interface DatabaseSchema { Attachment: AttachmentTable; File: FileTable; Piece: PieceTable; @@ -30,18 +24,9 @@ export interface SystemInformation { modifiedAt: ColumnType; } -export interface AccessLogTable { - requestId: ColumnType; - timestamp: ColumnType; - method: ColumnType; - pathname: ColumnType; - query: ColumnType; - ip: ColumnType; -} - export interface AttachmentData { pieceId: ColumnType; - sha256: Sha256_Bin; + sha256: Sha256; filename: string; mediaType: string; } @@ -51,7 +36,7 @@ export interface AttachmentTable extends AttachmentData, SystemInformation { } export interface FileTable { - sha256: ColumnType; + sha256: ColumnType; data: ColumnType; } @@ -91,7 +76,6 @@ export interface SessionData { accessToken: string | null; idToken: string | null; refreshToken: string | null; - external: number | null; } export interface SessionTable extends SessionData { @@ -107,7 +91,6 @@ export interface SqliteSchemaTable { sql: ColumnType; } -export type AccessLog = Selectable; export type Attachment = Selectable; export type File = Selectable; export type Option = Selectable; @@ -117,65 +100,112 @@ export type RepertoireEntry = Selectable; export type Session = Selectable; export type SqliteSchema = Selectable; -function systemInformation(schema: CreateTableBuilder) { - return schema +// --- MARK: EFFECT LAYERS ----------------------------------------------------- + +export type ArrayOrSingle = T | readonly T[]; + +export type MockData = { + readonly [K in keyof DatabaseSchema]?: ArrayOrSingle>; +}; + +export interface Executable { + execute(): Promise; +} + +export interface ExecutableTakeFirst { + executeTakeFirst(): Promise; +} + +export class Database extends Context.Tag("Db")>() { } + +export const FromPath = (path: string) => Layer.effect(Database, initDatabase(path)); + +export const Mocked = (data: MockData) => Layer.effect(Database, Effect.gen(function* () { + const database = yield* initDatabase(":memory:"); + + yield* transaction(database, (trx) => Effect.gen(function* () { + for (const [table, values] of Object.entries(data)) { + yield* trx + .insertInto(table as keyof typeof data) + .values(values) + .$call(execute); + } + })); + + return database; +})); + +export const execute = (executable: Executable) => Effect.promise(() => Object.freeze(executable.execute())); + +export const executeTakeFirst = (executable: ExecutableTakeFirst) => pipe( + Effect.promise(() => executable.executeTakeFirst()), + Effect.flatMap(Effect.fromNullable), +); + +export const executeTakeFirstOrDie = (executable: ExecutableTakeFirst) => pipe( + Effect.promise(() => executable.executeTakeFirst()), + Effect.flatMap(Effect.fromNullable), + Effect.orDie, +); + +export const transaction = (database: Kysely, callback: (trx: Transaction) => Effect.Effect): Effect.Effect => Effect.gen(function* () { + const runtime = yield* Effect.runtime(); + const promise = database.transaction().execute((trx) => Runtime.runPromise(runtime, callback(trx))); + return yield* pipe( + Effect.tryPromise(() => promise), + Effect.catchAll((error) => { + if (!Runtime.isFiberFailure(error.error)) { + return Effect.die(error); + } + + const cause = error.error[Runtime.FiberFailureCauseId] as Cause.Cause; + return Either.match(Cause.failureOrCause(cause), { + onLeft: Effect.fail, + onRight: Effect.die, + }); + }) + ); +}); + +const initDatabase = (filename: string) => Effect.gen(function* () { + + const systemInformation = (schema: CreateTableBuilder) => schema .addColumn("createdBy", "text") .addColumn("createdAt", "text", (c) => c.notNull()) .addColumn("modifiedBy", "text") .addColumn("modifiedAt", "text"); -} - -export async function initDatabase(filename: string = "db.sqlite3"): Promise> { const database = new BunSqliteDatabase(filename, { create: true, readwrite: true }); const dialect = new BunSqliteDialect({ database }); - const db = new Kysely({ dialect }); + const db = new Kysely({ dialect }); - await db.executeQuery(CompiledQuery.raw("PRAGMA foreign_keys = ON")); + yield* Effect.promise(() => db.executeQuery(CompiledQuery.raw("PRAGMA foreign_keys = ON"))); - const tables = await db + const tables = yield* db .selectFrom("sqlite_schema") .select("name") .where("type", "=", "table") - .execute() - .then((tables) => HashSet.make(...tables.map(({ name }) => name))); + .$call(execute) + .pipe(Effect.map((tables) => HashSet.make(...tables.map(({ name }) => name)))); if (!HashSet.has(tables, "Option")) { - await db.schema - .createTable("AccessLog") - .ifNotExists() - .addColumn("requestId", "text", (c) => c.notNull().primaryKey()) - .addColumn("timestamp", "text", (c) => c.notNull()) - .addColumn("method", "text", (c) => c.notNull()) - .addColumn("pathname", "text", (c) => c.notNull()) - .addColumn("query", "text", (c) => c.notNull()) - .addColumn("ip", "text") - .execute(); - - await db.schema - .createIndex("AccessLog_timestamp") - .ifNotExists() - .on("AccessLog") - .column("timestamp") - .execute(); - - await db.schema + yield* db.schema .createTable("File") .ifNotExists() .addColumn("sha256", "blob", (c) => c.notNull().primaryKey()) .addColumn("data", "blob", (c) => c.notNull()) - .execute(); + .$call(execute); - await db.schema + yield* db.schema .createTable("User") .ifNotExists() .addColumn("userId", "text", (c) => c.notNull().primaryKey()) .addColumn("username", "text", (c) => c.notNull().unique()) .addColumn("password", "text", (c) => c.notNull()) .addColumn("admin", "boolean", (c) => c.notNull()) - .execute(); + .$call(execute); - await db.schema + yield* db.schema .createTable("Piece") .ifNotExists() .addColumn("pieceId", "text", (c) => c.notNull().primaryKey()) @@ -184,40 +214,40 @@ export async function initDatabase(filename: string = "db.sqlite3"): Promise c.notNull().primaryKey()) .addColumn("name", "text", (c) => c.notNull()) .$call(systemInformation) - .execute(); + .$call(execute); - await db.schema + yield* db.schema .createIndex("Repertoire_name") .ifNotExists() .on("Repertoire") .column("name") - .execute(); + .$call(execute); - await db.schema + yield* db.schema .createTable("RepertoireEntry") .ifNotExists() .addColumn("repertoireId", "text", (c) => c.notNull().references("Repertoire.repertoireId").onDelete("cascade").onUpdate("cascade")) .addColumn("order", "integer", (c) => c.notNull()) .addColumn("pieceId", "text", (c) => c.notNull().references("Piece.pieceId").onDelete("restrict").onUpdate("restrict")) .addPrimaryKeyConstraint("pk_RepertoireEntry", ["repertoireId", "order"]) - .execute(); + .$call(execute); - await db.schema + yield* db.schema .createTable("Session") .ifNotExists() .addColumn("sessionId", "text", (c) => c.notNull().primaryKey()) @@ -226,11 +256,10 @@ export async function initDatabase(filename: string = "db.sqlite3"): Promise c.notNull()) - .execute(); + .$call(execute); - await db.schema + yield* db.schema .createTable("Attachment") .ifNotExists() .addColumn("attachmentId", "text", (c) => c.notNull().primaryKey()) @@ -239,27 +268,27 @@ export async function initDatabase(filename: string = "db.sqlite3"): Promise c.notNull()) .addColumn("mediaType", "text", (c) => c.notNull()) .$call(systemInformation) - .execute(); + .$call(execute); - await db.schema + yield* db.schema .createIndex("Attachment_pieceId_filename") .ifNotExists() .on("Attachment") .columns(["pieceId", "filename"]) - .execute(); + .$call(execute); - await db.schema + yield* db.schema .createTable("Option") .ifNotExists() .addColumn("key", "text", (c) => c.notNull().primaryKey()) .addColumn("value", "text", (c) => c.notNull()) - .execute(); + .$call(execute); - await db + yield* db .insertInto("Option") .values({ key: "database_version", value: "0" }) - .execute(); + .$call(execute); } return db; -} +}); diff --git a/packages/backend/src/services/db.ts b/packages/backend/src/services/db.ts deleted file mode 100644 index daa2aaa..0000000 --- a/packages/backend/src/services/db.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Cause, Context, Effect, Layer, pipe } from "effect"; -import { Kysely } from "kysely"; -import { Database } from "../database"; - -export interface Executable { - execute(): Promise; - executeTakeFirst(): Promise; -} - -export interface DbInterface { - readonly db: Kysely; - readonly execute: (executable: Executable) => Effect.Effect; - readonly executeTakeFirst: (executable: Executable) => Effect.Effect; - readonly executeTakeFirstOrDefect: (executable: Executable) => Effect.Effect; -} - -export class Db extends Context.Tag("Db")() { } - -export const DbFromInstance = (db: Kysely) => Layer.succeed(Db, Object.freeze({ - db, - execute: (executable) => Effect.promise(() => Object.freeze(executable.execute())), - executeTakeFirst: (executable) => pipe( - Effect.promise(() => executable.executeTakeFirst()), - Effect.flatMap(Effect.fromNullable), - ), - executeTakeFirstOrDefect: (executable) => pipe( - Effect.promise(() => executable.executeTakeFirst()), - Effect.flatMap(Effect.fromNullable), - Effect.orDie, - ), -})); diff --git a/packages/backend/src/services/session.ts b/packages/backend/src/services/session.ts deleted file mode 100644 index 8491e19..0000000 --- a/packages/backend/src/services/session.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { SessionId } from "common"; -import { Context, Layer } from "effect"; - -export class Session extends Context.Tag("Session")() { } - -export const SessionFromValue = Layer.succeed(Session); diff --git a/packages/backend/src/the_api.ts b/packages/backend/src/the_api.ts new file mode 100644 index 0000000..c3b24d0 --- /dev/null +++ b/packages/backend/src/the_api.ts @@ -0,0 +1,486 @@ +import { AttachmentId, PieceId, RepertoireId, Sha256 } from "common"; +import api, { NotFound, Role, Unauthorized } from "common/the_api"; +import { DateTime, Effect, HashSet, Option, pipe } from "effect"; +import { sql } from "kysely"; +import { implement } from "./api"; +import * as Authentication from "./services/Authentication"; +import * as Database from "./services/Database"; + +const READ_ACCESS = HashSet.make(Role.Editor, Role.Viewer); +const WRITE_ACCESS = HashSet.make(Role.Editor); + +const requireReadAccess = pipe( + Authentication.Authentication, + Effect.flatMap(({ me }) => me), + Effect.flatMap((user) => HashSet.isSubset(user.roles, READ_ACCESS) + ? Effect.succeed(user) + : Effect.fail(Unauthorized.make()) + ), +); + +const requireWriteAccess = pipe( + Authentication.Authentication, + Effect.flatMap(({ me }) => me), + Effect.flatMap((user) => HashSet.isSubset(user.roles, WRITE_ACCESS) + ? Effect.succeed(user) + : Effect.fail(Unauthorized.make()) + ), +); + +export const handle = implement(api, { + + // --- Authentication --- + + me: () => pipe( + Authentication.Authentication, + Effect.flatMap(({ me }) => me), + ), + logout: () => pipe( + Authentication.Authentication, + Effect.flatMap(({ logout }) => logout), + ), + getUser: (userId) => pipe( + Authentication.Authentication, + Effect.flatMap(({ getUser }) => getUser(userId)), + ), + + // --- Piece CRUD --- + + createPiece: (piece) => Effect.gen(function* () { + const { userId } = yield* requireWriteAccess; + const db = yield* Database.Database; + + const res = yield* db + .insertInto("Piece") + .values({ + pieceId: PieceId.make(Bun.randomUUIDv7()), + name: piece.name, + composer: Option.getOrNull(piece.composer), + lyricist: Option.getOrNull(piece.lyricist), + arranger: Option.getOrNull(piece.arranger), + createdBy: userId, + createdAt: sql`datetime()`, + }) + .returningAll() + .$call(Database.executeTakeFirstOrDie); + + return { + pieceId: res.pieceId, + name: res.name, + composer: Option.fromNullable(res.composer), + lyricist: Option.fromNullable(res.lyricist), + arranger: Option.fromNullable(res.arranger), + attachments: [], + createdBy: Option.fromNullable(res.createdBy), + createdAt: DateTime.unsafeMake(res.createdAt), + modifiedBy: Option.fromNullable(res.modifiedBy), + modifiedAt: pipe( + res.modifiedAt, + Option.fromNullable, + Option.map(DateTime.unsafeMake), + ), + }; + }), + getPiece: (pieceId) => Effect.gen(function* () { + yield* requireReadAccess; + const db = yield* Database.Database; + + const piece = yield* db + .selectFrom("Piece") + .selectAll() + .where("pieceId", "=", pieceId) + .$call(Database.executeTakeFirst) + .pipe(Effect.mapError(() => NotFound.make())); + + const attachments = yield* db + .selectFrom("Attachment") + .selectAll() + .where("pieceId", "=", pieceId) + .$call(Database.execute); + + return { + pieceId: piece.pieceId, + name: piece.name, + composer: Option.fromNullable(piece.composer), + lyricist: Option.fromNullable(piece.lyricist), + arranger: Option.fromNullable(piece.arranger), + attachments: attachments.map(({ + createdBy, + createdAt, + modifiedBy, + modifiedAt, + ...rest + }) => ({ + createdBy: Option.fromNullable(createdBy), + createdAt: DateTime.unsafeMake(createdAt), + modifiedBy: Option.fromNullable(modifiedBy), + modifiedAt: pipe( + modifiedAt, + Option.fromNullable, + Option.map(DateTime.unsafeMake), + ), + ...rest, + })), + createdBy: Option.fromNullable(piece.createdBy), + createdAt: DateTime.unsafeMake(piece.createdAt), + modifiedBy: Option.fromNullable(piece.modifiedBy), + modifiedAt: pipe( + piece.modifiedAt, + Option.fromNullable, + Option.map(DateTime.unsafeMake), + ), + }; + }), + queryPieces: ({ name, author, offset, limit }) => Effect.gen(function* () { + yield* requireReadAccess; + const db = yield* Database.Database; + + let query = db + .selectFrom("Piece") + .select("pieceId") + .orderBy(["name", "composer", "arranger"]) + .offset(offset) + .limit(limit); + + query = Option.match(name, { + onNone: () => query, + onSome: (name) => query.where("name", "like", "%" + name + "%"), + }); + + query = Option.match(author, { + onNone: () => query, + onSome: (author) => query.where((eb) => eb.or([ + eb("composer", "like", "%" + author + "%"), + eb("arranger", "like", "%" + author + "%"), + eb("lyricist", "like", "%" + author + "%"), + ])) + }); + + const res = yield* query.$call(Database.execute); + return res.map(({ pieceId }) => pieceId); + }), + updatePiece: ({ pieceId, ...piece }) => Effect.gen(function* () { + const { userId } = yield* requireWriteAccess; + const db = yield* Database.Database; + + const res = yield* db + .updateTable("Piece") + .set({ + name: piece.name, + composer: Option.getOrUndefined(piece.composer), + lyricist: Option.getOrUndefined(piece.lyricist), + arranger: Option.getOrUndefined(piece.arranger), + modifiedBy: userId, + modifiedAt: sql`datetime()`, + }) + .where("pieceId", "=", pieceId) + .returningAll() + .$call(Database.executeTakeFirst) + .pipe(Effect.mapError(() => NotFound.make())); + + const attachments = yield* db + .selectFrom("Attachment") + .selectAll() + .where("pieceId", "=", pieceId) + .$call(Database.execute); + + return { + pieceId: res.pieceId, + name: res.name, + composer: Option.fromNullable(res.composer), + lyricist: Option.fromNullable(res.lyricist), + arranger: Option.fromNullable(res.arranger), + attachments: attachments.map(({ + createdBy, + createdAt, + modifiedBy, + modifiedAt, + ...rest + }) => ({ + createdBy: Option.fromNullable(createdBy), + createdAt: DateTime.unsafeMake(createdAt), + modifiedBy: Option.fromNullable(modifiedBy), + modifiedAt: pipe( + modifiedAt, + Option.fromNullable, + Option.map(DateTime.unsafeMake), + ), + ...rest, + })), + createdBy: Option.fromNullable(res.createdBy), + createdAt: DateTime.unsafeMake(res.createdAt), + modifiedBy: Option.fromNullable(res.modifiedBy), + modifiedAt: pipe( + res.modifiedAt, + Option.fromNullable, + Option.map(DateTime.unsafeMake), + ), + }; + }), + deletePiece: (pieceId) => Effect.gen(function* () { + yield* requireWriteAccess; + const db = yield* Database.Database; + + yield* db + .deleteFrom("Piece") + .where("pieceId", "=", pieceId) + .returning("pieceId") + .$call(Database.executeTakeFirst) + .pipe(Effect.mapError(() => NotFound.make())); + }), + + // --- Attachment CRUD --- + + createAttachment: (attachment) => Effect.gen(function* () { + const { userId } = yield* requireWriteAccess; + const db = yield* Database.Database; + + const sha256 = Sha256.make(new Uint8Array(Bun.SHA256.byteLength)); + Bun.SHA256.hash(attachment.data, sha256); + + yield* db + .insertInto("File") + .values({ sha256, data: attachment.data }) + .onConflict((cb) => cb.column("sha256").doNothing()) + .$call(Database.execute); + + const res = yield* db + .insertInto("Attachment") + .values({ + attachmentId: AttachmentId.make(Bun.randomUUIDv7()), + pieceId: attachment.pieceId, + filename: attachment.filename, + mediaType: attachment.mediaType, + sha256, + createdBy: userId, + createdAt: sql`datetime()`, + }) + .returningAll() + .$call(Database.executeTakeFirstOrDie); + + return { + attachmentId: res.attachmentId, + pieceId: res.pieceId, + filename: res.filename, + sha256: res.sha256, + mediaType: res.mediaType, + createdBy: Option.fromNullable(res.createdBy), + createdAt: DateTime.unsafeMake(res.createdAt), + modifiedBy: Option.fromNullable(res.modifiedBy), + modifiedAt: pipe( + res.modifiedAt, + Option.fromNullable, + Option.map(DateTime.unsafeMake), + ), + }; + }), + getAttachment: (attachmentId) => Effect.gen(function* () { + yield* requireReadAccess; + const db = yield* Database.Database; + + const res = yield* db + .selectFrom("File") + .innerJoin("Attachment", "File.sha256", "Attachment.sha256") + .select(["Attachment.filename", "Attachment.mediaType", "File.data"]) + .where("Attachment.attachmentId", "=", attachmentId) + .$call(Database.executeTakeFirst) + .pipe(Effect.mapError(() => NotFound.make())); + + return res; + }), + updateAttachment: ({ attachmentId, ...attachment }) => Effect.gen(function* () { + const { userId } = yield* requireWriteAccess; + const db = yield* Database.Database; + + const res = yield* db + .updateTable("Attachment") + .set({ + ...attachment, + modifiedBy: userId, + modifiedAt: sql`datetime()`, + }) + .returningAll() + .$call(Database.executeTakeFirst) + .pipe(Effect.mapError(() => NotFound.make())); + + return { + attachmentId: res.attachmentId, + pieceId: res.pieceId, + filename: res.filename, + sha256: res.sha256, + mediaType: res.mediaType, + createdBy: Option.fromNullable(res.createdBy), + createdAt: DateTime.unsafeMake(res.createdAt), + modifiedBy: Option.fromNullable(res.modifiedBy), + modifiedAt: pipe( + res.modifiedAt, + Option.fromNullable, + Option.map(DateTime.unsafeMake), + ), + }; + }), + deleteAttachment: (attachmentId) => Effect.gen(function* () { + yield* requireWriteAccess; + const db = yield* Database.Database; + + yield* db + .deleteFrom("Attachment") + .where("attachmentId", "=", attachmentId) + .returning("attachmentId") + .$call(Database.executeTakeFirst) + .pipe(Effect.mapError(() => NotFound.make())); + }), + + // --- Repertoire CRUD --- + + createRepertoire: (repertoire) => Effect.gen(function* () { + const { userId } = yield* requireWriteAccess; + const db = yield* Database.Database; + + const repertoireId = RepertoireId.make(Bun.randomUUIDv7()); + + const res = yield* db + .insertInto("Repertoire") + .values({ + repertoireId, + name: repertoire.name, + createdBy: userId, + createdAt: sql`datetime()`, + }) + .returningAll() + .$call(Database.executeTakeFirstOrDie); + + const entries = yield* pipe( + repertoire.entries, + Effect.forEach((pieceId, i) => db + .insertInto("RepertoireEntry") + .values({ pieceId, repertoireId, order: i }) + .returningAll() + .$call(Database.executeTakeFirstOrDie) + .pipe(Effect.map(({ pieceId }) => pieceId)), + ), + ); + + return { + repertoireId: res.repertoireId, + name: res.name, + entries, + createdBy: Option.fromNullable(res.createdBy), + createdAt: DateTime.unsafeMake(res.createdAt), + modifiedBy: Option.fromNullable(res.modifiedBy), + modifiedAt: pipe( + res.modifiedAt, + Option.fromNullable, + Option.map(DateTime.unsafeMake), + ), + }; + }), + getRepertoire: (repertoireId) => Effect.gen(function* () { + yield* requireReadAccess; + const db = yield* Database.Database; + + const repertoire = yield* db + .selectFrom("Repertoire") + .selectAll() + .where("repertoireId", "=", repertoireId) + .$call(Database.executeTakeFirst) + .pipe(Effect.mapError(() => NotFound.make())); + + const entries = yield* db + .selectFrom("RepertoireEntry") + .select("pieceId") + .where("repertoireId", "=", repertoireId) + .orderBy("order") + .$call(Database.execute); + + return { + repertoireId: repertoire.repertoireId, + name: repertoire.name, + entries: entries.map(({ pieceId }) => pieceId), + createdBy: Option.fromNullable(repertoire.createdBy), + createdAt: DateTime.unsafeMake(repertoire.createdAt), + modifiedBy: Option.fromNullable(repertoire.modifiedBy), + modifiedAt: pipe( + repertoire.modifiedAt, + Option.fromNullable, + Option.map(DateTime.unsafeMake), + ), + }; + }), + queryRepertoire: ({ name, offset, limit }) => Effect.gen(function* () { + yield* requireReadAccess; + const db = yield* Database.Database; + + let query = db + .selectFrom("Repertoire") + .select("repertoireId") + .orderBy("name") + .offset(offset) + .limit(limit); + + query = Option.match(name, { + onNone: () => query, + onSome: (name) => query.where("name", "like", "%" + name + "%"), + }); + + const res = yield* query.$call(Database.execute); + return res.map(({ repertoireId }) => repertoireId); + }), + updateRepertoire: ({ repertoireId, ...repertoire }) => Effect.gen(function* () { + const { userId } = yield* requireWriteAccess; + const db = yield* Database.Database; + + const res = yield* db + .updateTable("Repertoire") + .set({ + name: repertoire.name, + modifiedBy: userId, + modifiedAt: sql`datetime()`, + }) + .where("repertoireId", "=", repertoireId) + .returningAll() + .$call(Database.executeTakeFirst) + .pipe(Effect.mapError(() => NotFound.make())); + + yield* db + .deleteFrom("RepertoireEntry") + .where("repertoireId", "=", repertoireId) + .$call(Database.execute); + + const entries = yield* pipe( + repertoire.entries, + Effect.forEach((pieceId, i) => db + .insertInto("RepertoireEntry") + .values({ pieceId, repertoireId, order: i }) + .returningAll() + .$call(Database.executeTakeFirstOrDie) + .pipe(Effect.map(({ pieceId }) => pieceId)), + ), + ); + + return { + repertoireId: res.repertoireId, + name: res.name, + entries, + createdBy: Option.fromNullable(res.createdBy), + createdAt: DateTime.unsafeMake(res.createdAt), + modifiedBy: Option.fromNullable(res.modifiedBy), + modifiedAt: pipe( + res.modifiedAt, + Option.fromNullable, + Option.map(DateTime.unsafeMake), + ), + }; + }), + deleteRepertoire: (repertoireId) => Effect.gen(function* () { + yield* requireWriteAccess; + const db = yield* Database.Database; + + yield* db + .deleteFrom("Repertoire") + .where("repertoireId", "=", repertoireId) + .returning("repertoireId") + .$call(Database.executeTakeFirst) + .pipe(Effect.mapError(() => NotFound.make())); + }), +}); diff --git a/packages/common/package.json b/packages/common/package.json index 0bd1736..892ec6a 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -7,10 +7,12 @@ ".": { "import": "./src/index.ts" }, "./*": { "import": "./src/*.ts" } }, - "dependencies": { - "effect": "catalog:" - }, "devDependencies": { + "@types/bun": "catalog:", "typescript": "catalog:" + }, + "dependencies": { + "cbor2": "catalog:", + "effect": "catalog:" } } diff --git a/packages/common/src/Api.test.ts b/packages/common/src/Api.test.ts new file mode 100644 index 0000000..521d20e --- /dev/null +++ b/packages/common/src/Api.test.ts @@ -0,0 +1,67 @@ +/// + +import { describe, expect, test } from "bun:test"; +import { HashMap, Schema } from "effect"; +import * as Api from "./Api"; + +describe("bundle", () => { + test("constructs a HashMap", () => { + const foo = Api.make(Schema.Void, Schema.String, Schema.String); + const bar = Api.make(Schema.Void, Schema.Number, Schema.String); + const bundle = Api.bundle({ foo, bar }); + + expect(HashMap.unsafeGet(bundle.map, "foo")).toBe(foo); + expect(HashMap.unsafeGet(bundle.map, "bar")).toBe(bar); + }); + + test("constructs a record", () => { + const foo = Api.make(Schema.Void, Schema.String, Schema.String); + const bar = Api.make(Schema.Void, Schema.Number, Schema.String); + const bundle = Api.bundle({ foo, bar }); + + expect(bundle.record.foo).toBe(foo); + expect(bundle.record.bar).toBe(bar); + }); + + test("freezes the object", () => { + const bundle = Api.bundle({ + foo: Api.make(Schema.Void, Schema.String, Schema.String), + bar: Api.make(Schema.Void, Schema.Number, Schema.String), + }); + + expect(Object.isFrozen(bundle)).toBeTrue(); + }); + + test("freezes the record", () => { + const bundle = Api.bundle({ + foo: Api.make(Schema.Void, Schema.String, Schema.String), + bar: Api.make(Schema.Void, Schema.Number, Schema.String), + }); + + expect(Object.isFrozen(bundle.record)).toBeTrue(); + }); +}); + +describe("make", () => { + test("constructs an object", () => { + const request = Schema.String.annotations({ title: "request" }); + const response = Schema.String.annotations({ title: "response" }); + const error = Schema.String.annotations({ title: "error" }); + + const api = Api.make(request, response, error); + + expect(api.request).toBe(request); + expect(api.response).toBe(response); + expect(api.error).toBe(error); + }); + + test("freezes the object", () => { + const request = Schema.String.annotations({ title: "request" }); + const response = Schema.String.annotations({ title: "response" }); + const error = Schema.String.annotations({ title: "error" }); + + const api = Api.make(request, response, error); + + expect(Object.isFrozen(api)).toBeTrue(); + }) +}); diff --git a/packages/common/src/Api.ts b/packages/common/src/Api.ts new file mode 100644 index 0000000..339a8ce --- /dev/null +++ b/packages/common/src/Api.ts @@ -0,0 +1,59 @@ +import { Data, Effect, HashMap, ParseResult, Schema } from "effect"; + +export class InternalServerError extends Data.Error<{ data: unknown }> { } +export class RequestParseError extends Data.Error<{ cause: ParseResult.ParseError }> { } +export class ResponseParseError extends Data.Error<{ cause: ParseResult.ParseError }> { } +export class UnexpectedResponseContentType extends Data.Error<{ contentType: string }> { } +export class UnexpectedResponseStatus extends Data.Error<{ status: number, statusText: string }> { } + +export interface Api< + RequestSchema extends Schema.Schema.AnyNoContext, + ResponseSchema extends Schema.Schema.AnyNoContext, + ErrorSchema extends Schema.Schema.AnyNoContext, +> { + readonly request: RequestSchema; + readonly response: ResponseSchema; + readonly error: ErrorSchema; +} + +export type ApiAny = Api< + Schema.Schema.AnyNoContext, + Schema.Schema.AnyNoContext, + Schema.Schema.AnyNoContext +>; + +export type ApiBundle< + Record extends { readonly [_: string]: ApiAny }, +> = { + readonly map: HashMap.HashMap; + readonly record: Record; +} + +export type ApiBundleAny = ApiBundle<{ readonly [_: string]: ApiAny }>; + +export type ApiBundleImpl< + Record extends { readonly [_: string]: ApiAny }, +> = { + readonly [K in keyof Record]: (request: Record[K]["request"]["Type"]) => Effect.Effect; +} + +export function bundle< + const Record extends { readonly [_: string]: ApiAny; } +>(record: Record): ApiBundle { + return Object.freeze({ + map: HashMap.fromIterable(Object.entries(record)), + record: Object.freeze(Object.assign({}, record)), + }); +} + +export function make< + RequestSchema extends Schema.Schema.AnyNoContext = typeof Schema.Void, + ResponseSchema extends Schema.Schema.AnyNoContext = typeof Schema.Void, + ErrorSchema extends Schema.Schema.AnyNoContext = typeof Schema.Void, +>( + request: RequestSchema = Schema.Void as unknown as RequestSchema, + response: ResponseSchema = Schema.Void as unknown as ResponseSchema, + error: ErrorSchema = Schema.Void as unknown as ErrorSchema, +): Api { + return Object.freeze({ request, response, error }); +}; diff --git a/packages/common/src/Body.test.ts b/packages/common/src/Body.test.ts new file mode 100644 index 0000000..14e4bec --- /dev/null +++ b/packages/common/src/Body.test.ts @@ -0,0 +1,92 @@ +/// + +import { describe, test } from "bun:test"; +import { Cause, Effect } from "effect"; +import * as Body from "./Body"; +import * as Test from "./Test"; + +describe("arrayBuffer", () => { + test("succeeds", async () => { + const buffer = new ArrayBuffer(4); + const body = { arrayBuffer: () => Promise.resolve(buffer) }; + + const effect = Body.arrayBuffer(body); + const exit = await Effect.runPromiseExit(effect); + + Test.expectSuccess(exit, buffer); + }); + + test("fails", async () => { + const error = new RangeError(); + const body = { arrayBuffer: () => Promise.reject(error) }; + + const effect = Body.arrayBuffer(body); + const exit = await Effect.runPromiseExit(effect); + + Test.expectFailure(exit, Cause.fail(new Body.BodyError({ cause: error }))); + }); + + test("dies", async () => { + const error = new Error(); + const body = { arrayBuffer: () => Promise.reject(error) }; + + const effect = Body.arrayBuffer(body); + const exit = await Effect.runPromiseExit(effect); + + Test.expectFailure(exit, Cause.die(error)); + }); +}); + +describe("blob", () => { + test("succeeds", async () => { + const blob = new Blob(["foo"]); + const body = { blob: () => Promise.resolve(blob) }; + + const effect = Body.blob(body); + const exit = await Effect.runPromiseExit(effect); + + Test.expectSuccess(exit, blob); + }); + + test("dies", async () => { + const error = new Error(); + const body = { blob: () => Promise.reject(error) }; + + const effect = Body.blob(body); + const exit = await Effect.runPromiseExit(effect); + + Test.expectFailure(exit, Cause.die(error)); + }) +}); + +describe("body", () => { + test.todo("succeeds with none"); + test.todo("succeeds with some"); +}); + +describe("bytes", () => { + test.todo("succeeds"); + test.todo("fails"); + test.todo("dies"); +}); + +describe("formData", () => { + test.todo("succeeds"); + test.todo("fails"); + test.todo("dies"); +}); + +describe("json", () => { + test.todo("succeeds"); + test.todo("fails"); + test.todo("dies"); +}); + +describe("stream", () => { + test.todo("succeeds"); +}); + +describe("json", () => { + test.todo("succeeds"); + test.todo("dies"); +}); diff --git a/packages/common/src/Body.ts b/packages/common/src/Body.ts new file mode 100644 index 0000000..dbb5b67 --- /dev/null +++ b/packages/common/src/Body.ts @@ -0,0 +1,70 @@ +import { Data, Effect, Option, pipe, Stream } from "effect"; + +export class BodyError extends Data.TaggedClass("BodyError")<{ cause: RangeError | TypeError | SyntaxError }> { } +export class StreamError extends Data.TaggedClass("StreamError")<{ cause: unknown }> { } + +export function arrayBuffer(body: { arrayBuffer(): Promise }): Effect.Effect { + return Effect.tryPromise({ + try: () => body.arrayBuffer(), + catch: (cause) => { + if (cause instanceof RangeError) return new BodyError({ cause }); + else throw cause; + }, + }); +} + +export function blob(body: { blob(): Promise }): Effect.Effect { + return Effect.promise(() => body.blob()); +} + +export function body(body: { readonly body: ReadableStream | null; }): Option.Option> { + return pipe( + body.body, + Option.fromNullable, + Option.map((stream) => Stream.fromReadableStream({ + evaluate: () => stream, + onError: (cause) => new StreamError({ cause }), + })) + ); +} + +export function bytes(body: { bytes(): Promise }): Effect.Effect { + return Effect.tryPromise({ + try: () => body.bytes(), + catch: (cause) => { + if (cause instanceof RangeError) return new BodyError({ cause }); + else throw cause; + }, + }); +} + +export function formData(body: { formData(): Promise }): Effect.Effect { + return Effect.tryPromise({ + try: () => body.formData(), + catch: (cause) => { + if (cause instanceof TypeError) return new BodyError({ cause }); + else throw cause; + }, + }); +} + +export function json(body: { json(): Promise }): Effect.Effect { + return Effect.tryPromise({ + try: () => body.json(), + catch: (cause) => { + if (cause instanceof SyntaxError) return new BodyError({ cause }); + else throw cause; + } + }); +} + +export function stream(body: { stream(): ReadableStream }): Stream.Stream { + return Stream.fromReadableStream({ + evaluate: () => body.stream(), + onError: (cause) => new StreamError({ cause }), + }); +} + +export function text(body: { text(): Promise }): Effect.Effect { + return Effect.promise(() => body.text()); +} diff --git a/packages/common/src/Cbor.test.ts b/packages/common/src/Cbor.test.ts new file mode 100644 index 0000000..8aa2ebe --- /dev/null +++ b/packages/common/src/Cbor.test.ts @@ -0,0 +1,117 @@ +/// + +import { describe, test } from "bun:test"; +import * as cbor from "cbor2"; +import { Effect, Schema } from "effect"; +import * as Cbor from "./Cbor"; +import * as Test from "./Test"; + +describe("encode", () => { + test("succeeds", async () => { + const object = { + x: "foo", + y: new Date(2025, 0, 1), + z: new Uint8Array([0, 1, 2, 3]), + }; + + const effect = Cbor.encode(object); + const exit = await Effect.runPromiseExit(effect); + + Test.expectSuccess(exit, cbor.encode(object)); + }); + + test("fails", async () => { + const object = { + x: Symbol(), + }; + + const effect = Cbor.encode(object); + const exit = await Effect.runPromiseExit(effect); + + Test.expectFailureTag(exit, "CborEncodeError"); + }); +}); + +describe("decode", () => { + test("succeeds", async () => { + const object = { + x: "foo", + y: new Date(2025, 0, 1), + z: new Uint8Array([0, 1, 2, 3]), + }; + + const effect = Cbor.decode(cbor.encode(object)); + const exit = await Effect.runPromiseExit(effect); + + Test.expectSuccess(exit, object); + }); + + test("fails", async () => { + const array = new Uint8Array([0xFE]); + + const effect = Cbor.decode(array); + const exit = await Effect.runPromiseExit(effect); + + Test.expectFailureTag(exit, "CborDecodeError"); + }); +}); + +describe("encodeSchema", () => { + test("succeeds", async () => { + const schema = Schema.Struct({ + x: Schema.String, + y: Schema.DateFromSelf, + z: Schema.Uint8ArrayFromSelf, + }); + + const object = schema.make({ + x: "foo", + y: new Date(2025, 0, 1), + z: new Uint8Array([0, 1, 2, 3]), + }); + + const effect = Cbor.encodeSchema(schema)(object); + const exit = await Effect.runPromiseExit(effect); + + Test.expectSuccess(exit, cbor.encode(object)); + }); + + test.todo("fails"); +}); + +describe("decodeSchema", () => { + test("succeeds", async () => { + const schema = Schema.Struct({ + x: Schema.String, + y: Schema.DateFromSelf, + z: Schema.Uint8ArrayFromSelf, + }); + + const object = schema.make({ + x: "foo", + y: new Date(2025, 0, 1), + z: new Uint8Array([0, 1, 2, 3]), + }); + + const effect = Cbor.decodeSchema(schema)(cbor.encode(object)); + const exit = await Effect.runPromiseExit(effect); + + Test.expectSuccess(exit, object); + }); + + test("fails with CborDecodeError", async () => { + const array = new Uint8Array([0xFE]); + + const effect = Cbor.decodeSchema(Schema.Any)(array); + const exit = await Effect.runPromiseExit(effect); + + Test.expectFailureTag(exit, "CborDecodeError"); + }); + + test("fails with ParseError", async () => { + const effect = Cbor.decodeSchema(Schema.Number)(cbor.encode("foo")); + const exit = await Effect.runPromiseExit(effect); + + Test.expectFailureTag(exit, "ParseError"); + }); +}); diff --git a/packages/common/src/Cbor.ts b/packages/common/src/Cbor.ts new file mode 100644 index 0000000..abe8700 --- /dev/null +++ b/packages/common/src/Cbor.ts @@ -0,0 +1,35 @@ +import * as cbor from "cbor2"; +import { Data, Effect, pipe, Schema } from "effect"; + +export class CborDecodeError extends Data.TaggedError("CborDecodeError")<{ cause: unknown }> { } +export class CborEncodeError extends Data.TaggedError("CborEncodeError")<{ cause: unknown }> { } + +export function encode(u: unknown): Effect.Effect { + return Effect.try({ + try: () => cbor.encode(u), + catch: (cause) => new CborEncodeError({ cause }), + }); +} + +export function decode(u: Uint8Array): Effect.Effect { + return Effect.try({ + try: () => cbor.decode(u), + catch: (cause) => new CborDecodeError({ cause }), + }); +} + +export const encodeSchema = (schema: Schema.Schema) => { + const schemaEncoder = Schema.encode(schema); + return (a: A) => pipe( + schemaEncoder(a), + Effect.flatMap((i) => encode(i)), + ); +}; + +export const decodeSchema = (schema: Schema.Schema) => { + const schemaDecoder = Schema.decodeUnknown(schema); + return (u: Uint8Array) => pipe( + decode(u), + Effect.flatMap((u) => schemaDecoder(u)), + ); +}; diff --git a/packages/common/src/Client.ts b/packages/common/src/Client.ts new file mode 100644 index 0000000..9c9fa61 --- /dev/null +++ b/packages/common/src/Client.ts @@ -0,0 +1,109 @@ +import { Data, Effect, Option, pipe, Record, Schema } from "effect"; +import { constant } from "effect/Function"; +import * as Api from "./Api"; +import * as Body from "./Body"; +import * as Cbor from "./Cbor"; +import { fetch, FetchError } from "./Fetch"; + +export class ServerDie extends Data.TaggedError("ServerDie")<{ cause: unknown }> { } +export class UnexpectedStatus extends Data.TaggedError("UnexpectedStatusCode")<{ status: number }> { } + +export interface ClientOptions { + readonly baseUrl?: string | URL | undefined; +} + +export const client = ( + bundle: Api.ApiBundle, + { baseUrl }: ClientOptions = {}, +) => { + return Object.freeze(Record.map(bundle.record, (api, key) => { + const url = new URL(`/api/${key}`, baseUrl); + + const requestEncoder = encodeBody(api.request); + const responseDecoder = decodeBody(api.response); + const errorDecoder = decodeBody(api.error); + + return Effect.fn(`call.${key}`)(function* (request: any) { + + const body = yield* pipe( + request, + requestEncoder, + Effect.flatMap(({ body, headers }) => fetch(url, { + method: "POST", + credentials: "include", + body, + headers, + })), + Effect.catchTags({ + CborEncodeError: Effect.die, + ParseError: Effect.die, + }), + ); + + switch (body.status) { + case 200: { + const result = yield* responseDecoder(body).pipe(Effect.orDie); + return result; + } + case 400: { + const result = yield* errorDecoder(body).pipe(Effect.orDie); + return yield* Effect.fail(result); + } + case 500: { + const result = yield* readBody(body).pipe(Effect.orDie); + return yield* Effect.die(new ServerDie({ cause: result })); + } + default: { + return yield* Effect.die(new UnexpectedStatus({ status: body.status })); + } + } + }); + })) as { readonly [K in keyof Record]: (request: Record[K]["request"]["Type"]) => Effect.Effect }; +}; + +export const readBody = (body: Body) => pipe( + body, + Body.bytes, + Effect.map((bytes) => bytes.byteLength > 0 ? Option.some(bytes) : Option.none()), + Effect.flatMap((maybeBytes) => pipe( + maybeBytes, + Effect.transposeMapOption(Cbor.decode), + )), + Effect.map(Option.getOrUndefined), +); + +export const decodeBody = (schema: Schema.Schema) => { + const decoder = Schema.decodeUnknown(schema); + return (body: Body) => Effect.flatMap(readBody(body), decoder); +}; + +export interface BodyData { + readonly body: null | Uint8Array; + readonly headers: { readonly [_: string]: string }; +} + +export const encodeBody = (schema: Schema.Schema) => { + const encoder = Cbor.encodeSchema(schema); + return (a: A) => pipe( + a, + Option.fromNullable, + Effect.transposeMapOption(encoder), + Effect.map(Option.match({ + onNone: emptyBody, + onSome: cborBody, + })) + ); +}; + +export const emptyBody: () => BodyData = constant(Object.freeze({ + body: null, + headers: Object.freeze({}), +})); + +export const cborBody: (bytes: Uint8Array) => BodyData = (bytes) => Object.freeze({ + body: bytes, + headers: Object.freeze({ + "Content-Type": "application/cbor", + "Content-Length": String(bytes.byteLength), + }), +}); diff --git a/packages/common/src/Fetch.ts b/packages/common/src/Fetch.ts new file mode 100644 index 0000000..9ddb9f3 --- /dev/null +++ b/packages/common/src/Fetch.ts @@ -0,0 +1,15 @@ +import { Data, Effect } from "effect"; + +export class FetchError extends Data.TaggedError("FetchError")<{ cause: TypeError }> { } + +const _fetch = (input: string | URL, init?: RequestInit) => Effect.tryPromise({ + try: (signal) => fetch(input, { ...init, signal }), + catch: (cause) => { + if (cause instanceof TypeError) return new FetchError({ cause }); + else throw cause; + }, +}); + +export { + _fetch as fetch, +}; diff --git a/packages/common/src/Test.ts b/packages/common/src/Test.ts new file mode 100644 index 0000000..94d084b --- /dev/null +++ b/packages/common/src/Test.ts @@ -0,0 +1,40 @@ +/// + +import { expect } from "bun:test"; +import { Cause, Exit, Predicate } from "effect"; + +export function expectSuccess( + actual: Exit.Exit, + expected: A, +): asserts actual is Exit.Success { + expect>(Exit.succeed(expected)).toStrictEqual(actual); +} + +export function expectFailure( + actual: Exit.Exit, + expected: Cause.Cause, +): asserts actual is Exit.Failure { + expect>(Exit.failCause(expected)).toStrictEqual(actual); +} + +export function expectFailureTag( + actual: Exit.Exit, + tag: Tag, +): asserts actual is Exit.Failure { + if (!Exit.isFailure(actual)) { + expect(actual).fail("Expected Exit to be a Failure"); + throw new Error("Unreachable"); + } + + const { cause } = actual; + if (!Cause.isFailType(cause)) { + expect(cause).fail("Expected Cause to be a Fail"); + throw new Error("Unreachable"); + } + + const { error } = cause; + if (!Predicate.isTagged(tag)) { + expect(error).fail(`Expected error to be tagged with ${JSON.stringify(tag)}`); + throw new Error("Unreachable"); + } +} diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 5af7734..3f14490 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -1,28 +1,26 @@ -import { Brand } from "effect"; +import { pipe, Schema } from "effect"; -export type UUID = Brand.Branded; -export const UUID = Brand.nominal(); +export const Sha256 = pipe( + Schema.Uint8ArrayFromSelf, + Schema.filter((arr) => arr.byteLength === 32), + Schema.brand("Sha256"), +); +export type Sha256 = typeof Sha256.Type; -export type Sha256_Bin = Brand.Branded; -export const Sha256_Bin = Brand.nominal(); +export const SessionId = pipe(Schema.String, Schema.brand("SessionId")); +export type SessionId = typeof SessionId.Type; -export type Sha256_Hex = Brand.Branded; -export const Sha256_Hex = Brand.nominal(); +export const AttachmentId = pipe(Schema.UUID, Schema.brand("AttachmentId")); +export type AttachmentId = typeof AttachmentId.Type; -export type AttachmentId = Brand.Branded; -export const AttachmentId = Brand.nominal(); +export const PieceId = pipe(Schema.UUID, Schema.brand("PieceId")); +export type PieceId = typeof PieceId.Type; -export type PieceId = Brand.Branded; -export const PieceId = Brand.nominal(); +export const RepertoireId = pipe(Schema.UUID, Schema.brand("RepertoireId")); +export type RepertoireId = typeof RepertoireId.Type; -export type RepertoireId = Brand.Branded; -export const RepertoireId = Brand.nominal(); +export const RequestId = pipe(Schema.UUID, Schema.brand("RequestId")); +export type RequestId = typeof RequestId.Type; -export type RequestId = Brand.Branded; -export const RequestId = Brand.nominal(); - -export type SessionId = Brand.Branded; -export const SessionId = Brand.nominal(); - -export type UserId = Brand.Branded; -export const UserId = Brand.nominal(); +export const UserId = pipe(Schema.UUID, Schema.brand("UserId")); +export type UserId = typeof UserId.Type; diff --git a/packages/common/src/the_api.ts b/packages/common/src/the_api.ts new file mode 100644 index 0000000..fa65f0f --- /dev/null +++ b/packages/common/src/the_api.ts @@ -0,0 +1,206 @@ +import { pipe, Schema } from "effect"; +import * as Api from "./Api"; +import { AttachmentId, PieceId, RepertoireId, Sha256, UserId } from "common"; +import { constant } from "effect/Function"; + +// --- MARK: COMMON TYPES ------------------------------------------------------ + +export enum Role { + Viewer = "Viewer", + Editor = "Editor", +} + +export const SystemInformation = Schema.Struct({ + createdBy: pipe(UserId, Schema.optionalWith({ as: "Option", exact: true })), + createdAt: Schema.DateTimeUtc, + modifiedBy: pipe(UserId, Schema.optionalWith({ as: "Option", exact: true })), + modifiedAt: pipe(Schema.DateTimeUtc, Schema.optionalWith({ as: "Option", exact: true })), +}); + +export const Pagination = Schema.Struct({ + offset: pipe(Schema.Int, Schema.greaterThanOrEqualTo(0), Schema.optionalWith({ default: constant(0), exact: true })), + limit: pipe(Schema.Int, Schema.between(1, 100), Schema.optionalWith({ default: constant(100), exact: true })), +}); + +export type SystemInformation = typeof SystemInformation.Type; +export type Pagination = typeof Pagination.Type; + +// --- MARK: RESPONSE TYPES ---------------------------------------------------- + +export const Me = Schema.Struct({ + userId: UserId, + displayName: Schema.NonEmptyString, + roles: Schema.HashSet(Schema.Enums(Role)), +}); + +export const Other = Schema.Struct({ + userId: UserId, + displayName: Schema.NonEmptyString, +}); + +export const Attachment = Schema.Struct({ + attachmentId: AttachmentId, + pieceId: PieceId, + filename: Schema.NonEmptyString, + sha256: Sha256, + mediaType: Schema.NonEmptyString, +}).pipe(Schema.extend(SystemInformation)); + +export const Piece = Schema.Struct({ + pieceId: PieceId, + name: Schema.NonEmptyString, + composer: pipe(Schema.NonEmptyString, Schema.optionalWith({ as: "Option", exact: true })), + lyricist: pipe(Schema.NonEmptyString, Schema.optionalWith({ as: "Option", exact: true })), + arranger: pipe(Schema.NonEmptyString, Schema.optionalWith({ as: "Option", exact: true })), + attachments: pipe(Attachment, Schema.Array), +}).pipe(Schema.extend(SystemInformation)); + +export const Piece_Create = Schema.Struct({ + name: Schema.NonEmptyString, + composer: pipe(Schema.NonEmptyString, Schema.optionalWith({ as: "Option", exact: true })), + lyricist: pipe(Schema.NonEmptyString, Schema.optionalWith({ as: "Option", exact: true })), + arranger: pipe(Schema.NonEmptyString, Schema.optionalWith({ as: "Option", exact: true })), +}); + +export const Piece_Query = Schema.Struct({ + name: pipe(Schema.NonEmptyString, Schema.optionalWith({ as: "Option", exact: true })), + author: pipe(Schema.NonEmptyString, Schema.optionalWith({ as: "Option", exact: true })), +}).pipe(Schema.extend(Pagination)); + +export const Repertoire = Schema.Struct({ + repertoireId: RepertoireId, + name: Schema.NonEmptyString, + entries: pipe(PieceId, Schema.Array), +}).pipe(Schema.extend(SystemInformation)); + +export const Repertoire_Create = Schema.Struct({ + name: Schema.NonEmptyString, + entries: pipe(PieceId, Schema.Array), +}); + +export const Repertoire_Query = Schema.Struct({ + name: pipe(Schema.NonEmptyString, Schema.optionalWith({ as: "Option", exact: true })), +}).pipe(Schema.extend(Pagination)); + +export type Me = typeof Me.Type; +export type Other = typeof Other.Type; +export type Attachment = typeof Attachment.Type; +export type Piece = typeof Piece.Type; +export type Piece_Create = typeof Piece_Create.Type; +export type Piece_Query = typeof Piece_Query.Type; +export type Repertoire = typeof Repertoire.Type; +export type Repertoire_Query = typeof Repertoire_Query.Type; + +// --- MARK: ERROR TYPES ------------------------------------------------------- + +export const Unauthenticated = Schema.TaggedStruct("Unauthenticated", {}); +export const Unauthorized = Schema.TaggedStruct("Unauthorized", {}); +export const NotFound = Schema.TaggedStruct("NotFound", {}); + +export type Unauthenticated = typeof Unauthenticated.Type; +export type Unauthorized = typeof Unauthorized.Type; +export type NotFound = typeof NotFound.Type; + +export default Api.bundle({ + + // --- Authentication --- + + me: Api.make(Schema.Void, Me, Unauthenticated), + logout: Api.make(Schema.Void, Schema.Void), + getUser: Api.make( + UserId, + Other, + Schema.Union(Unauthenticated, NotFound), + ), + + // --- Piece CRUD --- + + createPiece: Api.make( + Piece_Create, + Piece, + Schema.Union(Unauthenticated, Unauthorized), + ), + getPiece: Api.make( + PieceId, + Piece, + Schema.Union(Unauthenticated, Unauthorized, NotFound), + ), + queryPieces: Api.make( + Piece_Query, + pipe(PieceId, Schema.Array), + Schema.Union(Unauthenticated, Unauthorized), + ), + updatePiece: Api.make( + Piece_Create.pipe(Schema.extend(Schema.Struct({ pieceId: PieceId }))), + Piece, + Schema.Union(Unauthenticated, Unauthorized, NotFound), + ), + deletePiece: Api.make( + PieceId, + Schema.Void, + Schema.Union(Unauthenticated, Unauthorized, NotFound), + ), + + // --- Attachment CRUD --- + + createAttachment: Api.make( + Schema.Struct({ + pieceId: PieceId, + filename: Schema.NonEmptyString, + mediaType: Schema.NonEmptyString, + data: Schema.Uint8ArrayFromSelf, + }), + Attachment, + Schema.Union(Unauthenticated, Unauthorized, NotFound), + ), + getAttachment: Api.make( + AttachmentId, + Schema.Struct({ + filename: Schema.NonEmptyString, + mediaType: Schema.NonEmptyString, + data: Schema.Uint8ArrayFromSelf, + }), + Schema.Union(Unauthenticated, Unauthorized, NotFound), + ), + updateAttachment: Api.make( + Schema.Struct({ + attachmentId: AttachmentId, + filename: Schema.NonEmptyString, + }), + Attachment, + Schema.Union(Unauthenticated, Unauthorized, NotFound), + ), + deleteAttachment: Api.make( + AttachmentId, + Schema.Void, + Schema.Union(Unauthenticated, Unauthorized, NotFound), + ), + + // --- Repertoire CRUD --- + + createRepertoire: Api.make( + Repertoire_Create, + Repertoire, + Schema.Union(Unauthenticated, Unauthorized), + ), + getRepertoire: Api.make( + RepertoireId, + Repertoire, + Schema.Union(Unauthenticated, Unauthorized, NotFound), + ), + queryRepertoire: Api.make( + Repertoire_Query, + pipe(RepertoireId, Schema.Array), + Schema.Union(Unauthenticated, Unauthorized), + ), + updateRepertoire: Api.make( + Repertoire_Create.pipe(Schema.extend(Schema.Struct({ repertoireId: RepertoireId }))), + Repertoire, + Schema.Union(Unauthenticated, Unauthorized, NotFound), + ), + deleteRepertoire: Api.make( + RepertoireId, + Schema.Void, + Schema.Union(Unauthenticated, Unauthorized, NotFound), + ), +}); diff --git a/packages/frontend/components.json b/packages/frontend/components.json index 39eb7a4..7c2c969 100644 --- a/packages/frontend/components.json +++ b/packages/frontend/components.json @@ -1,13 +1,13 @@ { "$schema": "https://ui.shadcn.com/schema.json", - "style": "default", + "style": "new-york", "rsc": false, "tsx": true, "tailwind": { "config": "tailwind.config.js", "css": "src/style.css", "baseColor": "stone", - "cssVariables": false, + "cssVariables": true, "prefix": "" }, "aliases": { diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 5a30a55..ee44abe 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -4,20 +4,19 @@ "type": "module", "license": "UNLICENSED", "devDependencies": { + "@tailwindcss/vite": "catalog:", "@types/react": "catalog:", "@types/react-dom": "catalog:", "@vitejs/plugin-react": "catalog:", - "autoprefixer": "catalog:", + "babel-plugin-react-compiler": "catalog:", "backend": "workspace:^", "class-variance-authority": "catalog:", - "elysia": "catalog:", - "postcss": "catalog:", "tailwindcss": "catalog:", + "tw-animate-css": "catalog:", "typescript": "catalog:", "vite": "catalog:" }, "dependencies": { - "@elysiajs/eden": "catalog:", "@radix-ui/react-dialog": "catalog:", "@radix-ui/react-dropdown-menu": "catalog:", "@radix-ui/react-label": "catalog:", @@ -31,7 +30,6 @@ "react": "catalog:", "react-dom": "catalog:", "react-router-dom": "catalog:", - "tailwind-merge": "catalog:", - "tailwindcss-animate": "catalog:" + "tailwind-merge": "catalog:" } } diff --git a/packages/frontend/postcss.config.js b/packages/frontend/postcss.config.js deleted file mode 100644 index 7b75c83..0000000 --- a/packages/frontend/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -export default { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; diff --git a/packages/frontend/src/app.tsx b/packages/frontend/src/app.tsx index 3374a23..551477a 100644 --- a/packages/frontend/src/app.tsx +++ b/packages/frontend/src/app.tsx @@ -1,4 +1,5 @@ import { Home } from "@/routes/Home"; +import { Login } from "@/routes/Login"; import { Piece } from "@/routes/Piece"; import { Pieces } from "@/routes/Pieces"; import { Repertoire } from "@/routes/Repertoire"; @@ -62,21 +63,17 @@ const router = createBrowserRouter([ }, ], }, -], { - future: { - v7_fetcherPersist: true, - v7_normalizeFormMethod: true, - v7_partialHydration: true, - v7_relativeSplatPath: true, - v7_skipActionErrorRevalidation: true, + { + path: "/login", + Component: Login, }, -}); +]); const rootElement = document.getElementById("root") as HTMLDivElement; const root = createRoot(rootElement); root.render( - + , ); diff --git a/packages/frontend/src/cache.ts b/packages/frontend/src/cache.ts index 204009d..f602e39 100644 --- a/packages/frontend/src/cache.ts +++ b/packages/frontend/src/cache.ts @@ -1,66 +1,21 @@ -import type * as Db from "backend/database"; -import { AttachmentId, PieceId, RepertoireId, UserId } from "common"; -import { Cache, Duration, Effect, Option, pipe } from "effect"; -import { client, mapResponse } from "./client"; +import { PieceId, RepertoireId, UserId } from "common"; +import the_api, { SystemInformation } from "common/the_api"; +import { Array, Cache, Duration, Effect, pipe } from "effect"; +import { client } from "./client"; -export interface User { - readonly userId: UserId; - readonly displayName: string; -} - -export interface SystemInformation { - readonly createdBy: Option.Option; - readonly createdAt: string; - readonly modifiedBy: Option.Option; - readonly modifiedAt: Option.Option; -} - -export interface Attachment extends SystemInformation { - readonly attachmentId: AttachmentId; - readonly pieceId: PieceId; - readonly sha256: string; - readonly filename: string; - readonly mediaType: string; -} - -export interface Piece extends SystemInformation { - readonly pieceId: PieceId; - readonly name: string; - readonly composer: Option.Option; - readonly lyricist: Option.Option; - readonly arranger: Option.Option; - readonly attachments: readonly Attachment[]; -} - -export interface Repertoire extends SystemInformation { - readonly repertoireId: RepertoireId; - readonly name: string; - readonly entries: readonly Piece[]; -} - -interface DbSystemInformation { - readonly createdBy: UserId | null; - readonly createdAt: string; - readonly modifiedBy: UserId | null; - readonly modifiedAt: string | null; -} - -export const denormalizeSystemInformation = ({ +export const denormalizeSystemInformation = ({ createdBy, modifiedBy, - modifiedAt, ...rest }: T) => pipe( Effect.all({ createdBy: pipe( createdBy, - Effect.fromNullable, Effect.flatMap((userId) => Effect.uninterruptible(userCache.get(userId))), Effect.optionFromOptional, ), modifiedBy: pipe( modifiedBy, - Effect.fromNullable, Effect.flatMap((userId) => Effect.uninterruptible(userCache.get(userId))), Effect.optionFromOptional, ), @@ -68,26 +23,19 @@ export const denormalizeSystemInformation = ({ Effect.map((si) => Object.freeze({ ...rest, ...si, - modifiedAt: Option.fromNullable(modifiedAt), })), ); export const denormalizePiece = ({ - composer, - lyricist, - arranger, attachments, ...rest -}: Db.Piece & { attachments: (Omit & { sha256: string })[] }) => pipe( +}: typeof the_api.record.getPiece.response.Type) => pipe( Effect.all({ attachments: Effect.all(attachments.map(denormalizeSystemInformation), { concurrency: "unbounded" }), }, { concurrency: "unbounded" }), Effect.map((piece) => Object.freeze({ ...rest, ...piece, - composer: Option.fromNullable(composer), - lyricist: Option.fromNullable(lyricist), - arranger: Option.fromNullable(arranger), })), Effect.flatMap(denormalizeSystemInformation), ); @@ -95,7 +43,7 @@ export const denormalizePiece = ({ export const denormalizeRepertoire = ({ entries, ...rest -}: Db.Repertoire & { entries: PieceId[] }) => pipe( +}: typeof the_api.record.getRepertoire.response.Type) => pipe( Effect.all({ entries: Effect.all(entries.map((entry) => Effect.uninterruptible(pieceCache.get(entry))), { concurrency: "unbounded" }), }, { concurrency: "unbounded" }), @@ -110,44 +58,38 @@ const UserSemaphore = Effect.unsafeMakeSemaphore(1); const PieceSemaphore = Effect.unsafeMakeSemaphore(4); const RepertoireSemaphore = Effect.unsafeMakeSemaphore(1); -export const userLookup = (userId: UserId) => pipe( - Effect.promise((signal) => client.user({ userId }).get({ fetch: { signal } })), - Effect.flatMap(mapResponse), - Effect.catchAll((error) => error.status === 404 ? Effect.succeed(null) : Effect.fail(error)), - Effect.map((x): User | null => x), // safely coerce to interface - UserSemaphore.withPermits(1), -); - -export const pieceLookup = (pieceId: PieceId) => pipe( - Effect.promise((signal) => client.piece({ pieceId }).get({ fetch: { signal } })), - Effect.flatMap(mapResponse), - Effect.flatMap(denormalizePiece), - Effect.map((x): Piece => x), // safely coerce to interface - PieceSemaphore.withPermits(1), -); - -export const repertoireLookup = (repertoireId: RepertoireId) => pipe( - Effect.promise((signal) => client.repertoire({ repertoireId }).get({ fetch: { signal } })), - Effect.flatMap(mapResponse), - Effect.flatMap(denormalizeRepertoire), - Effect.map((x): Repertoire => x), // safely coerce to interface - RepertoireSemaphore.withPermits(1), -); - export const userCache = Effect.runSync(Cache.make({ capacity: Infinity, timeToLive: Duration.days(1), - lookup: userLookup, + lookup: (userId: UserId) => pipe( + client.getUser(userId), + UserSemaphore.withPermits(1), + ), })); export const pieceCache = Effect.runSync(Cache.make({ capacity: Infinity, timeToLive: Duration.days(1), - lookup: pieceLookup, + lookup: (pieceId: PieceId) => pipe( + client.getPiece(pieceId), + Effect.flatMap(denormalizePiece), + PieceSemaphore.withPermits(1), + ), })); export const repertoireCache = Effect.runSync(Cache.make({ capacity: Infinity, timeToLive: Duration.days(1), - lookup: repertoireLookup, + lookup: (repertoireId: RepertoireId) => pipe( + client.getRepertoire(repertoireId), + Effect.flatMap(denormalizeRepertoire), + RepertoireSemaphore.withPermits(1), + ), })); + +export type User = Effect.Effect.Success>; +export type Piece = Effect.Effect.Success>; +export type Repertoire = Effect.Effect.Success>; + +export type Attachment = Array.ReadonlyArray.Infer; +export type DenormalizedSystemInformation = Effect.Effect.Success>>; diff --git a/packages/frontend/src/client.ts b/packages/frontend/src/client.ts index 4af6d97..cabd317 100644 --- a/packages/frontend/src/client.ts +++ b/packages/frontend/src/client.ts @@ -1,30 +1,6 @@ -import { Treaty, treaty } from "@elysiajs/eden"; -import type { App } from "backend/app"; -import { ACCEPTED_MEDIA_TYPES } from "common/MediaType"; -import { Effect } from "effect"; +import * as Client from "common/Client"; +import the_api from "common/the_api"; -export type ResponseEffect> = Effect.Effect extends never ? never : { [Status in keyof R]: { status: Status, value: R[Status] } }[Exclude]>; +export const API_URL_PREFIX = process.env.NODE_ENV === "production" ? undefined : "http://localhost:3000"; -export const API_URL_PREFIX = process.env.NODE_ENV === "production" ? "" : "http://localhost:3000"; - -export const client = treaty(API_URL_PREFIX, { - fetch: { - credentials: "include", - }, - keepDomain: true, - onResponse: async (res) => { - const contentType = res.headers.get('Content-Type')?.split(';')[0]; - if (contentType !== undefined && ACCEPTED_MEDIA_TYPES.includes(contentType)) { - const blob = await res.blob(); - // TODO Decode filename from Content-Disposition header - const file = new File([blob], "", { type: contentType }); - return file; - } - }, -}).api.v1; - -export const mapResponse = >({ error, data }: Treaty.TreatyResponse): ResponseEffect => { - return error !== null - ? Effect.fail(error as Exclude extends never ? never : { [Status in keyof R]: { status: Status, value: R[Status] } }[Exclude]) - : Effect.succeed(data); -}; +export const client = Client.client(the_api, { baseUrl: API_URL_PREFIX }); diff --git a/packages/frontend/src/hooks/useLoading.ts b/packages/frontend/src/hooks/useLoading.ts index cd06eba..1520aa4 100644 --- a/packages/frontend/src/hooks/useLoading.ts +++ b/packages/frontend/src/hooks/useLoading.ts @@ -1,9 +1,6 @@ -import { API_URL_PREFIX } from "@/client"; import { mapProp, Update, Updater } from "@/hooks/useStore"; -import { Treaty } from "@elysiajs/eden"; -import { Effect, Fiber, pipe } from "effect"; +import { Console, Effect, Fiber, pipe } from "effect"; import React, { useCallback, useEffect, useState } from "react"; -import { useNavigate } from "react-router-dom"; export namespace Loading { export interface Pending { @@ -34,57 +31,6 @@ export type Loading = | Loading.Error ; -export type ErrorResponses> = - Exclude extends never - ? { status: unknown, value: unknown } - : { [Status in keyof R]: { status: Status, value: R[Status] } }[Exclude]; - -export type LoadingResult> = Loading>; - -export function useLoading>(fn: () => Promise>, deps: React.DependencyList) { - - const navigate = useNavigate(); - - const [result, setResult] = useState>(IS_LOADING); - - useEffect(() => { - setResult(IS_LOADING); - let cancelled = false; - - fn().then(({ error, data }) => { - if (cancelled) return; - - if (error !== null) { - if (error.status === 401) { - window.location.href = `${API_URL_PREFIX}/api/v1/login`; - return; - } - - setResult(Object.freeze>>({ - isLoading: false, - data: null, - error: error as ErrorResponses, - setData: null, - })); - } else { - setResult({ - isLoading: false, - error, - data, - setData: (action) => setResult(mapProp("data", action) as Update>), - } as LoadingResult); - } - }, (error) => { - console.error(error); - }); - - return () => { cancelled = true; }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [navigate, ...deps]); - - return result; -} - const IS_LOADING = Object.freeze({ isLoading: true, data: null, @@ -92,10 +38,9 @@ const IS_LOADING = Object.freeze({ setData: null, }); -export function useLoadingEffect(effect: Effect.Effect, deps: React.DependencyList) { +export function useLoading(effect: Effect.Effect, deps: React.DependencyList) { const [result, setResult] = useState>(IS_LOADING); - const setResultEffect = useCallback((action: Loading) => Effect.sync(() => setResult(action)), []); useEffect(() => { @@ -117,6 +62,7 @@ export function useLoadingEffect(effect: Effect.Effect, deps: React. setData: null, })), }), + Effect.catchAllDefect(Console.error), Effect.runFork, ); const interruptEffect = Fiber.interrupt(fiber); @@ -125,7 +71,7 @@ export function useLoadingEffect(effect: Effect.Effect, deps: React. Effect.runFork(interruptEffect); }; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [setResultEffect, ...deps]); + }, deps); return result; } diff --git a/packages/frontend/src/hooks/useStore.ts b/packages/frontend/src/hooks/useStore.ts index 7bcb1d3..9764bf2 100644 --- a/packages/frontend/src/hooks/useStore.ts +++ b/packages/frontend/src/hooks/useStore.ts @@ -1,4 +1,4 @@ -import { UserId } from "common"; +import { type Me } from "common/the_api"; import { identity } from "effect"; import { useLayoutEffect, useState } from "react"; @@ -9,23 +9,15 @@ export const mapProp = (prop: K, action: Update) = return Object.freeze({ ...object, [prop]: typeof action === "function" ? (action as (prev: T) => T)(object[prop]) : action }); }; -export namespace Store { - export interface User { - readonly userId: UserId; - readonly username: string; - readonly roles: readonly string[]; - } -} - export interface Store { - readonly user: Store.User | null; + readonly user: Me | null; } let store: Store = Object.freeze({ user: null, }); -export function setUser(action: Update) { +export function setUser(action: Update) { set(mapProp("user", action)); } diff --git a/packages/frontend/src/icons/microsoft-entra-id.svg b/packages/frontend/src/icons/microsoft-entra-id.svg new file mode 100644 index 0000000..198adab --- /dev/null +++ b/packages/frontend/src/icons/microsoft-entra-id.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/frontend/src/routes/Attachment.tsx b/packages/frontend/src/routes/Attachment.tsx index e65fbc1..d010512 100644 --- a/packages/frontend/src/routes/Attachment.tsx +++ b/packages/frontend/src/routes/Attachment.tsx @@ -1,6 +1,6 @@ import { client } from "@/client"; import { useLoading } from "@/hooks/useLoading.ts"; -import { AttachmentId, PieceId } from "common"; +import { AttachmentId } from "common"; import { Match } from "effect"; import JSZip from "jszip"; import { OpenSheetMusicDisplay } from "opensheetmusicdisplay"; @@ -10,10 +10,9 @@ import { useParams } from "react-router-dom"; export default function Attachment() { const params = useParams(); - const pieceId = PieceId(params.pieceId!); - const attachmentId = AttachmentId(params.attachmentId!); + const attachmentId = AttachmentId.make(params.attachmentId!); - const { isLoading, error, data } = useLoading(() => client.piece({ pieceId }).attachment({ attachmentId }).get(), [pieceId, attachmentId]); + const { isLoading, error, data } = useLoading(client.getAttachment(attachmentId), [attachmentId]); const containerRef = useRef(null); const renderFn = useRef void)>(null); @@ -22,14 +21,14 @@ export default function Attachment() { if (isLoading || error !== null) return; - let musixXmlBlob: Blob = data; + let musixXmlData: Uint8Array = data.data; /* If the file is the compressed .mxl file, we do the uncompression * ourselves, because apparently OpenSheetMusicDisplay is incapable. */ - if (data.type === "application/vnd.recordare.musicxml") { + if (data.mediaType === "application/vnd.recordare.musicxml") { const zip = new JSZip(); - await zip.loadAsync(data); + await zip.loadAsync(musixXmlData); const containerFile = zip.file("META-INF/container.xml"); if (containerFile === null) { @@ -58,10 +57,10 @@ export default function Attachment() { return; } - musixXmlBlob = await musicXmlFile.async("blob"); + musixXmlData = await musicXmlFile.async("uint8array"); } - const musicXml = await musixXmlBlob.text(); + const musicXml = new TextDecoder().decode(musixXmlData); const osmd = new OpenSheetMusicDisplay(containerRef.current!, { autoResize: false, @@ -105,8 +104,10 @@ export default function Attachment() {
Wystąpił błąd: {Match.value(error).pipe( - Match.when({ status: 422 }, ({ value }) => value.message), - Match.when({ status: 404 }, () => "Załącznik nie istnieje"), + Match.tag("FetchError", () => "Nie można połączyć się z serwerem"), + Match.tag("NotFound", () => "Załącznik nie istnieje"), + Match.tag("Unauthenticated", () => "Zaloguj się, aby kontynuować"), + Match.tag("Unauthorized", () => "Nie posiadasz uprawnień"), Match.exhaustive, )}
diff --git a/packages/frontend/src/routes/Login.tsx b/packages/frontend/src/routes/Login.tsx new file mode 100644 index 0000000..b337b23 --- /dev/null +++ b/packages/frontend/src/routes/Login.tsx @@ -0,0 +1,40 @@ +import { API_URL_PREFIX } from "@/client"; +import { buttonVariants } from "@/components/ui/button"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import microsoftEntraId from "@/icons/microsoft-entra-id.svg"; + +export function Login() { + + const internalUrl = `${API_URL_PREFIX}/login`; + const externalUrl = `${API_URL_PREFIX}/login?external`; + + return ( +
+ ); +} diff --git a/packages/frontend/src/routes/Piece.tsx b/packages/frontend/src/routes/Piece.tsx index ba60039..0874d6b 100644 --- a/packages/frontend/src/routes/Piece.tsx +++ b/packages/frontend/src/routes/Piece.tsx @@ -1,16 +1,17 @@ -import { Attachment, denormalizeSystemInformation, type Piece, pieceCache } from "@/cache"; +import { Attachment, denormalizeSystemInformation, pieceCache, type Piece } from "@/cache"; import { API_URL_PREFIX, client } from "@/client"; import { Button, buttonVariants } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; -import { useLoadingEffect } from "@/hooks/useLoading"; +import { useLoading } from "@/hooks/useLoading"; import { mapProp, Update, Updater } from "@/hooks/useStore"; -import { created, modified, saveDelay } from "@/snippets"; +import { created, modified, SAVE_DELAY, saveDelay } from "@/snippets"; import { Label } from "@radix-ui/react-label"; import clsx from "clsx"; import { PieceId } from "common"; +import * as Body from "common/Body"; import { getMediaTypeForFilename } from "common/MediaType"; -import { Cause, Effect, Option } from "effect"; +import { Cause, Clock, DateTime, Effect, Exit, Fiber, Option, pipe, Scope } from "effect"; import { constant } from "effect/Function"; import { Download, Loader2, Trash, UploadCloud } from "lucide-react"; import { DragEventHandler, FormEventHandler, MouseEvent, useCallback, useId, useState } from "react"; @@ -18,9 +19,9 @@ import { Link, useNavigate, useParams } from "react-router-dom"; export function Piece() { - const id = PieceId(useParams().pieceId!); + const id = PieceId.make(useParams().pieceId!); - const { isLoading, error, data, setData } = useLoadingEffect(Effect.uninterruptible(pieceCache.get(id)), [id]); + const { isLoading, error, data, setData } = useLoading(Effect.uninterruptible(pieceCache.get(id)), [id]); const setAttachments = useCallback((action: Update) => { setData!(mapProp("attachments", action)); @@ -38,7 +39,7 @@ export function Piece() { return (
{error !== null ? ( - Cause.isUnknownException(error) ? "Wystąpił nieznany błąd" : `Wystąpił błąd: ${JSON.stringify(error.value)}` + Cause.isUnknownException(error) ? "Wystąpił nieznany błąd" : `Wystąpił błąd: ${JSON.stringify(error)}` ) : (<>

Utwór

@@ -82,17 +83,15 @@ function PieceForm(props: PieceForm.Props) { try { setIsSaving(true); - const { error } = await client.piece({ pieceId: props.piece.pieceId }).put({ + await client.updatePiece({ + pieceId: props.piece.pieceId, name, - composer: composer.length > 0 ? composer : null, - lyricist: lyricist.length > 0 ? lyricist : null, - arranger: arranger.length > 0 ? arranger : null, - }); - - if (error !== null) { - console.error(error.value); - return; - } + composer: composer.length > 0 ? Option.some(composer) : Option.none(), + lyricist: lyricist.length > 0 ? Option.some(lyricist) : Option.none(), + arranger: arranger.length > 0 ? Option.some(arranger) : Option.none(), + }).pipe(Effect.runPromise); + } catch (error) { + console.error(error); } finally { await delay; setIsSaving(false); @@ -103,17 +102,14 @@ function PieceForm(props: PieceForm.Props) { try { setIsDeleting(true); - const { error } = await client - .piece({ pieceId: props.piece.pieceId }) - .delete(); + await Effect.runPromise(pipe( + client.deletePiece(props.piece.pieceId), + Effect.andThen(pieceCache.invalidate(props.piece.pieceId)), + )); - if (error !== null) { - console.error(error.value); - return; - } - - Effect.runFork(pieceCache.invalidate(props.piece.pieceId)); navigate(".."); + } catch (error) { + console.error(error); } finally { setIsDeleting(false); } @@ -214,45 +210,47 @@ namespace AttachmentRow { function AttachmentRow(props: AttachmentRow.Props) { - const url = `${API_URL_PREFIX}/api/v1/piece/${props.attachment.pieceId}/attachment/${props.attachment.attachmentId}`; + const url = `${API_URL_PREFIX}/api/piece/${props.attachment.pieceId}/attachment/${props.attachment.attachmentId}`; - const open = useCallback(async (event: MouseEvent) => { + const download = () => Effect.gen(function* () { + const { data, mediaType, filename } = yield* client.getAttachment(props.attachment.attachmentId); + + const file = new File([data], filename, { + type: mediaType, + lastModified: pipe( + props.attachment.modifiedAt, + Option.getOrElse(() => props.attachment.createdAt), + DateTime.toEpochMillis, + ), + }); + + const url = URL.createObjectURL(file); + const a = document.createElement("a"); + a.href = url; + a.download = filename; + a.click(); + URL.revokeObjectURL(url); + }).pipe(Effect.runPromise); + + const open = (event: MouseEvent) => Effect.gen(function* () { if (props.attachment.mediaType !== "application/pdf") { return; } event.preventDefault(); - const { error, data } = await client - .piece({ pieceId: props.attachment.pieceId }) - .attachment({ attachmentId: props.attachment.attachmentId }) - .get(); + const { data, mediaType } = yield* client.getAttachment(props.attachment.attachmentId); + const blob = new Blob([data], { type: mediaType }); - if (error !== null) { - console.error(error.value); - return; - } - - const url = URL.createObjectURL(data); + const url = URL.createObjectURL(blob); window.open(url, "_target"); URL.revokeObjectURL(url); - }, [props.attachment.attachmentId, props.attachment.mediaType, props.attachment.pieceId]); - - const doDelete = useCallback(async () => { - - const { error } = await client - .piece({ pieceId: props.attachment.pieceId }) - .attachment({ attachmentId: props.attachment.attachmentId }) - .delete(); - - if (error !== null) { - console.error(error.value); - return; - } + }).pipe(Effect.runPromise); + const doDelete = () => Effect.gen(function* () { + yield* client.deleteAttachment(props.attachment.attachmentId); props.setAttachments((prev) => prev.filter((a) => a.attachmentId !== props.attachment.attachmentId)); - - }, [props]); + }).pipe(Effect.runPromise); return ( @@ -277,9 +275,9 @@ function AttachmentRow(props: AttachmentRow.Props) { {modified(props.attachment)} - + @@ -304,15 +302,22 @@ function AttachmentForm(props: AttachmentForm.Props) { e.dataTransfer.dropEffect = "copy"; }; - const onDrop: DragEventHandler = async (e) => { + const onDrop: DragEventHandler = (e) => Effect.gen(function* () { e.preventDefault(); if (isLoading) { return; } - const delay = saveDelay(); - try { + const delay = yield* Effect.fork(SAVE_DELAY); + + yield* Effect.scopedWith((scope) => Effect.gen(function* () { + + yield* Scope.addFinalizer(scope, Effect.gen(function* () { + yield* Fiber.join(delay); + setIsLoading(false); + })); + setIsLoading(true); for (const file of e.dataTransfer.files) { @@ -321,18 +326,21 @@ function AttachmentForm(props: AttachmentForm.Props) { continue; } - const { data, error } = await client.piece({ pieceId: props.pieceId }).attachment.post({ + const data = yield* Body.bytes(file); + + const exit = yield* Effect.exit(client.createAttachment({ + pieceId: props.pieceId, + data, filename: file.name, mediaType, - data: file, - }); + })); - if (error !== null) { - console.error(error.value); + if (Exit.isFailure(exit)) { + console.error(exit.cause); continue; } - const attachment = await Effect.runPromise(denormalizeSystemInformation(data)); + const attachment = yield* denormalizeSystemInformation(exit.exitValue); props.setAttachments((prev) => { const next = [...prev, attachment]; @@ -340,11 +348,8 @@ function AttachmentForm(props: AttachmentForm.Props) { return next; }); } - } finally { - await delay; - setIsLoading(false); - } - }; + })); + }).pipe(Effect.runPromise); return (
client.piece.get({ - query: { - ...(name !== "" ? { name } : undefined), - ...(author !== "" ? { author } : undefined), - }, - fetch: { signal }, - })); - - if (error !== null) { - return yield* Effect.fail(error); - } else { - return data; - } + const data = yield* client.queryPieces({ + name: name !== "" ? Option.some(name) : Option.none(), + author: author !== "" ? Option.some(author) : Option.none(), + offset: 0, + limit: 100, + }); + return data; }), [name, author]); return ( @@ -91,7 +85,7 @@ export function Pieces() { ) : error !== null ? ( - {Cause.isUnknownException(error) ? "Wystąpił nieznany błąd" : `Wystąpił błąd: ${JSON.stringify(error.value)}`} + {Cause.isUnknownException(error) ? "Wystąpił nieznany błąd" : `Wystąpił błąd: ${JSON.stringify(error)}`} ) : ( @@ -111,7 +105,7 @@ namespace PieceRow { function PieceRow(props: PieceRow.Props) { - const { isLoading, error, data: piece } = useLoadingEffect(Effect.uninterruptible(pieceCache.get(props.pieceId)), [props.pieceId]); + const { isLoading, error, data: piece } = useLoading(Effect.uninterruptible(pieceCache.get(props.pieceId)), [props.pieceId]); if (isLoading) { return ( @@ -126,9 +120,10 @@ function PieceRow(props: PieceRow.Props) { Wystąpił błąd: {Match.value(error).pipe( - Match.when({ status: 401 }, () => "Zaloguj się ponownie"), - Match.when({ status: 422 }, ({ value }) => value.message), - Match.when({ status: 404 }, () => "Utwór nie istnieje"), + Match.tag("FetchError", () => "Nie można połączyć się z serwerem"), + Match.tag("NotFound", () => "Utwór nie istnieje"), + Match.tag("Unauthenticated", () => "Zaloguj się, aby kontynuować"), + Match.tag("Unauthorized", () => "Nie posiadasz uprawnień"), Match.exhaustive, )} @@ -170,29 +165,25 @@ function AddPieceDialogContent() { const [isLoading, setIsLoading] = useState(false); - const onSubmit: FormEventHandler = async (e) => { + const onSubmit: FormEventHandler = (e) => Effect.gen(function* () { e.preventDefault(); - try { + yield* Effect.scopedWith((scope) => Effect.gen(function* () { + + yield* Scope.addFinalizer(scope, Effect.sync(() => setIsLoading(false))); + setIsLoading(true); - const { data, error } = await client.piece.post({ + const { pieceId } = yield* client.createPiece({ name, - composer: composer.length > 0 ? composer : null, - lyricist: lyricist.length > 0 ? lyricist : null, - arranger: arranger.length > 0 ? arranger : null, + composer: composer.length > 0 ? Option.some(composer) : Option.none(), + lyricist: lyricist.length > 0 ? Option.some(lyricist) : Option.none(), + arranger: arranger.length > 0 ? Option.some(arranger) : Option.none(), }); - if (error !== null) { - console.error(error.value); - return; - } - - navigate(data.pieceId); - } finally { - setIsLoading(false); - } - }; + navigate(pieceId); + })); + }).pipe(Effect.runPromise); return ( diff --git a/packages/frontend/src/routes/Repertoire.tsx b/packages/frontend/src/routes/Repertoire.tsx index 159b4ee..c110968 100644 --- a/packages/frontend/src/routes/Repertoire.tsx +++ b/packages/frontend/src/routes/Repertoire.tsx @@ -5,20 +5,20 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; -import { useLoadingEffect } from "@/hooks/useLoading"; +import { useLoading } from "@/hooks/useLoading"; import { mapProp, Update, Updater } from "@/hooks/useStore"; -import { authors, DEBOUNCE, saveDelay } from "@/snippets"; +import { authors, DEBOUNCE, SAVE_DELAY, saveDelay } from "@/snippets"; import { PieceId, RepertoireId } from "common"; -import { Array, Cause, Effect, Match, Option, pipe } from "effect"; +import { Array, Cause, Effect, Fiber, Match, Option, pipe, Scope } from "effect"; import { ChevronDown, ChevronUp, CircleMinus, Loader2, Plus } from "lucide-react"; -import { FormEventHandler, useCallback, useId, useMemo, useRef, useState } from "react"; +import { FormEventHandler, useCallback, useId, useRef, useState } from "react"; import { Link, useNavigate, useParams } from "react-router-dom"; export function Repertoire() { - const id = RepertoireId(useParams().repertoireId!); + const id = RepertoireId.make(useParams().repertoireId!); - const { isLoading, error, data, setData } = useLoadingEffect(Effect.uninterruptible(repertoireCache.get(id)), [id]); + const { isLoading, error, data, setData } = useLoading(Effect.uninterruptible(repertoireCache.get(id)), [id]); const setEntries = useCallback((action: Update) => { setData!(mapProp("entries", action)); @@ -36,7 +36,7 @@ export function Repertoire() { return (
{error !== null ? ( - Cause.isUnknownException(error) ? "Wystąpił nieznany błąd" : `Wystąpił błąd: ${JSON.stringify(error.value)}` + Cause.isUnknownException(error) ? "Wystąpił nieznany błąd" : `Wystąpił błąd: ${JSON.stringify(error)}` ) : (<>

Repertuar

@@ -65,47 +65,39 @@ function RepertoireForm(props: RepertoireForm.Props) { const [isSaving, setIsSaving] = useState(false); const [isDeleting, setIsDeleting] = useState(false); - const onSubmit: FormEventHandler = async (e) => { + const onSubmit: FormEventHandler = async (e) => Effect.gen(function* () { e.preventDefault(); - const delay = saveDelay(); - try { + const delay = yield* Effect.fork(SAVE_DELAY); + + yield* Effect.scopedWith((scope) => Effect.gen(function* () { + + yield* Scope.addFinalizer(scope, Effect.gen(function* () { + yield* Fiber.join(delay); + setIsSaving(false); + })); + setIsSaving(true); - const { error } = await client.repertoire({ repertoireId: props.repertoire.repertoireId }).put({ + yield* client.updateRepertoire({ + repertoireId: props.repertoire.repertoireId, name, entries: props.repertoire.entries.map(({ pieceId }) => pieceId), }); + })); + }).pipe(Effect.runPromise); - if (error !== null) { - console.error(error.value); - return; - } - } finally { - await delay; - setIsSaving(false); - } - }; + const doDelete = () => Effect.scopedWith((scope) => Effect.gen(function* () { - const doDelete = useCallback(async () => { - try { - setIsDeleting(true); + yield* Scope.addFinalizer(scope, Effect.sync(() => setIsSaving(false))); - const { error } = await client - .repertoire({ repertoireId: props.repertoire.repertoireId }) - .delete(); + setIsDeleting(true); - if (error !== null) { - console.error(error.value); - return; - } + yield* client.deleteRepertoire(props.repertoire.repertoireId); - Effect.runFork(repertoireCache.invalidate(props.repertoire.repertoireId)); - navigate(".."); - } finally { - setIsDeleting(false); - } - }, [props.repertoire.repertoireId, navigate]); + yield* repertoireCache.invalidate(props.repertoire.repertoireId); + navigate(".."); + })).pipe(Effect.runPromise); return (
@@ -196,47 +188,41 @@ function EntryRow({ setEntries, }: EntryRow.Props) { - const moveUpAction = useCallback((entries: readonly Piece[]) => pipe( + const moveUpAction = (entries: readonly Piece[]) => pipe( entries, Array.remove(no - 1), Array.insertAt(no - 2, piece), Option.getOrThrow, - ), [no, piece]); + ); - const moveDownAction = useCallback((entries: readonly Piece[]) => pipe( + const moveDownAction = (entries: readonly Piece[]) => pipe( entries, Array.remove(no - 1), Array.insertAt(no, piece), Option.getOrThrow, - ), [no, piece]); + ); - const removeAction = useCallback((entries: readonly Piece[]) => pipe( + const removeAction = (entries: readonly Piece[]) => pipe( entries, Array.filter((p) => p.pieceId !== piece.pieceId), - ), [piece.pieceId]); + ); - const update = useCallback(async (action: (prev: readonly Piece[]) => readonly Piece[]) => { + const update = (action: (prev: readonly Piece[]) => readonly Piece[]) => Effect.gen(function* () { const mapToId = Array.map(({ pieceId }) => pieceId); - const { error } = await client - .repertoire({ repertoireId: repertoire.repertoireId }) - .put({ - name: repertoire.name, - entries: pipe(repertoire.entries, action, mapToId), - }); - - if (error !== null) { - console.error(error.value); - return; - } + yield* client.updateRepertoire({ + repertoireId: repertoire.repertoireId, + name: repertoire.name, + entries: pipe(repertoire.entries, action, mapToId), + }); setEntries(action); - }, [repertoire.entries, repertoire.name, repertoire.repertoireId, setEntries]); + }); - const moveUp = useMemo(() => update.bind(undefined, moveUpAction), [moveUpAction, update]); - const moveDown = useMemo(() => update.bind(undefined, moveDownAction), [moveDownAction, update]); - const remove = useMemo(() => update.bind(undefined, removeAction), [removeAction, update]); + const moveUp = () => Effect.runPromise(update(moveUpAction)); + const moveDown = () => Effect.runPromise(update(moveDownAction)); + const remove = () => Effect.runPromise(update(removeAction)); return ( @@ -283,22 +269,16 @@ function AddEntryDialogContent(props: AddEntryDialogContent.Props) { const debounce = useRef(Effect.void); - const { isLoading, error, data: pieceIds } = useLoadingEffect(Effect.gen(function* () { + const { isLoading, error, data: pieceIds } = useLoading(Effect.gen(function* () { yield* debounce.current; - const { error, data } = yield* Effect.promise((signal) => client.piece.get({ - query: { - ...(name !== "" ? { name } : undefined), - ...(author !== "" ? { author } : undefined), - limit: ADD_ENTRY_DIALOG_LIMIT, - }, - fetch: { signal }, - })); + const data = yield* client.queryPieces({ + name: name !== "" ? Option.some(name) : Option.none(), + author: author !== "" ? Option.some(author) : Option.none(), + offset: 0, + limit: ADD_ENTRY_DIALOG_LIMIT, + }); - if (error !== null) { - return yield* Effect.fail(error); - } else { - return data; - } + return data; }), [name, author]); return ( @@ -343,7 +323,7 @@ function AddEntryDialogContent(props: AddEntryDialogContent.Props) { ) : error !== null ? ( - {Cause.isUnknownException(error) ? "Wystąpił nieznany błąd" : `Wystąpił błąd: ${JSON.stringify(error.value)}`} + {Cause.isUnknownException(error) ? "Wystąpił nieznany błąd" : `Wystąpił błąd: ${JSON.stringify(error)}`} ) : ( @@ -375,29 +355,22 @@ namespace EntryDialogPieceRow { function EntryDialogPieceRow(props: EntryDialogPieceRow.Props) { - const { isLoading, error, data: piece } = useLoadingEffect(Effect.uninterruptible(pieceCache.get(props.pieceId)), [props.pieceId]); + const { isLoading, error, data: piece } = useLoading(Effect.uninterruptible(pieceCache.get(props.pieceId)), [props.pieceId]); - const onClick = useCallback(async () => { + const onClick = () => Effect.gen(function* () { const action = Array.append(piece!); const mapToId = Array.map(({ pieceId }) => pieceId); - const { error } = await client - .repertoire({ repertoireId: props.repertoire.repertoireId }) - .put({ - name: props.repertoire.name, - entries: pipe(props.repertoire.entries, action, mapToId), - }); - - if (error !== null) { - console.error(error.value); - return; - } + yield* client.updateRepertoire({ + repertoireId: props.repertoire.repertoireId, + name: props.repertoire.name, + entries: pipe(props.repertoire.entries, action, mapToId), + }); props.setEntries(action); props.setDialogOpen(false); - - }, [piece, props]); + }).pipe(Effect.runPromise); if (isLoading) { return ( @@ -412,9 +385,10 @@ function EntryDialogPieceRow(props: EntryDialogPieceRow.Props) { Wystąpił błąd: {Match.value(error).pipe( - Match.when({ status: 401 }, () => "Zaloguj się ponownie"), - Match.when({ status: 422 }, ({ value }) => value.message), - Match.when({ status: 404 }, () => "Utwór nie istnieje"), + Match.tag("FetchError", () => "Nie można połączyć się z serwerem"), + Match.tag("NotFound", () => "Repertuar nie istnieje"), + Match.tag("Unauthenticated", () => "Zaloguj się, aby kontynuować"), + Match.tag("Unauthorized", () => "Nie posiadasz uprawnień"), Match.exhaustive, )} diff --git a/packages/frontend/src/routes/Repertoires.tsx b/packages/frontend/src/routes/Repertoires.tsx index 0f3bad5..1640d89 100644 --- a/packages/frontend/src/routes/Repertoires.tsx +++ b/packages/frontend/src/routes/Repertoires.tsx @@ -5,10 +5,10 @@ import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogT import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; -import { useLoadingEffect } from "@/hooks/useLoading"; +import { useLoading } from "@/hooks/useLoading"; import { created, DEBOUNCE, modified } from "@/snippets"; import { RepertoireId } from "common"; -import { Cause, Effect, Match } from "effect"; +import { Cause, Effect, Match, Option, Scope } from "effect"; import { Loader2, Plus } from "lucide-react"; import { FormEventHandler, ReactNode, useId, useRef, useState } from "react"; import { Link, useNavigate } from "react-router-dom"; @@ -19,20 +19,14 @@ export function Repertoires() { const debounce = useRef(Effect.void); - const { isLoading, error, data: repertoireIds } = useLoadingEffect(Effect.gen(function* () { + const { isLoading, error, data: repertoireIds } = useLoading(Effect.gen(function* () { yield* debounce.current; - const { error, data } = yield* Effect.promise((signal) => client.repertoire.get({ - query: { - ...(name !== "" ? { name } : undefined), - }, - fetch: { signal }, - })); - - if (error !== null) { - return yield* Effect.fail(error); - } else { - return data; - } + const data = yield* client.queryRepertoire({ + name: name !== "" ? Option.some(name) : Option.none(), + offset: 0, + limit: 100, + }); + return data; }), [name]); return ( @@ -79,7 +73,7 @@ export function Repertoires() { ) : error !== null ? ( - {Cause.isUnknownException(error) ? "Wystąpił nieznany błąd" : `Wystąpił błąd: ${JSON.stringify(error.value)}`} + {Cause.isUnknownException(error) ? "Wystąpił nieznany błąd" : `Wystąpił błąd: ${JSON.stringify(error)}`} ) : ( @@ -99,7 +93,7 @@ namespace RepertoireRow { function RepertoireRow(props: RepertoireRow.Props) { - const { isLoading, error, data: repertoire } = useLoadingEffect(Effect.uninterruptible(repertoireCache.get(props.repertoireId)), [props.repertoireId]); + const { isLoading, error, data: repertoire } = useLoading(Effect.uninterruptible(repertoireCache.get(props.repertoireId)), [props.repertoireId]); if (isLoading) { return ( @@ -114,9 +108,10 @@ function RepertoireRow(props: RepertoireRow.Props) { Wystąpił błąd: {Match.value(error).pipe( - Match.when({ status: 401 }, () => "Zaloguj się ponownie"), - Match.when({ status: 422 }, ({ value }) => value.message), - Match.when({ status: 404 }, () => "Repertuar nie istnieje"), + Match.tag("FetchError", () => "Nie można połączyć się z serwerem"), + Match.tag("NotFound", () => "Repertuar nie istnieje"), + Match.tag("Unauthenticated", () => "Zaloguj się, aby kontynuować"), + Match.tag("Unauthorized", () => "Nie posiadasz uprawnień"), Match.exhaustive, )} @@ -166,27 +161,23 @@ function AddRepertoireDialogContent() { const [isLoading, setIsLoading] = useState(false); - const onSubmit: FormEventHandler = async (e) => { + const onSubmit: FormEventHandler = (e) => Effect.gen(function* () { e.preventDefault(); - try { + yield* Effect.scopedWith((scope) => Effect.gen(function* () { + + yield* Scope.addFinalizer(scope, Effect.sync(() => setIsLoading(false))); + setIsLoading(true); - const { data, error } = await client.repertoire.post({ + const { repertoireId } = yield* client.createRepertoire({ name, entries: [], }); - if (error !== null) { - console.error(error.value); - return; - } - - navigate(data.repertoireId); - } finally { - setIsLoading(false); - } - }; + navigate(repertoireId); + })); + }).pipe(Effect.runPromise); return ( diff --git a/packages/frontend/src/routes/Root.tsx b/packages/frontend/src/routes/Root.tsx index c91f73f..1953e65 100644 --- a/packages/frontend/src/routes/Root.tsx +++ b/packages/frontend/src/routes/Root.tsx @@ -1,30 +1,40 @@ -import { API_URL_PREFIX, client } from "@/client"; +import { client } from "@/client"; import { Button, buttonVariants } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; import { setUser, useStore } from "@/hooks/useStore"; -import { Settings, User } from "lucide-react"; +import { Effect, pipe } from "effect"; +import { LogOut, Settings, User } from "lucide-react"; import { useEffect } from "react"; -import { Link, Outlet } from "react-router-dom"; +import { Link, Outlet, useNavigate } from "react-router-dom"; export function Root() { + const navigate = useNavigate(); + const user = useStore(state => state.user); - const init = async () => { + const init = Effect.gen(function* () { if (user !== null) return; - const { data, error } = await client.me.get(); - - if (error !== null) { - window.location.href = `${API_URL_PREFIX}/api/v1/login`; - return; - } + const data = yield* pipe( + client.me(), + Effect.tapErrorTag("Unauthenticated", () => Effect.sync(() => { + navigate("/login"); + })), + ); setUser(data); - }; + }); + + const onLogoutClick = () => Effect.gen(function* () { + yield* client.logout(); + + setUser(null); + navigate("/login"); + }).pipe(Effect.runPromise); // eslint-disable-next-line react-hooks/exhaustive-deps - useEffect(() => void init(), []); + useEffect(() => void Effect.runFork(init), []); if (user === null) { return ( @@ -43,7 +53,7 @@ export function Root() { @@ -52,6 +62,9 @@ export function Root() { Ustawienia + + Wyloguj się +
diff --git a/packages/frontend/src/routes/Settings.tsx b/packages/frontend/src/routes/Settings.tsx index b157724..1ddf855 100644 --- a/packages/frontend/src/routes/Settings.tsx +++ b/packages/frontend/src/routes/Settings.tsx @@ -1,6 +1,7 @@ export function Settings() { return (
+ Jakby były ustawienia, to by tu były.
); } diff --git a/packages/frontend/src/snippets.tsx b/packages/frontend/src/snippets.tsx index a631767..0c2f792 100644 --- a/packages/frontend/src/snippets.tsx +++ b/packages/frontend/src/snippets.tsx @@ -1,9 +1,10 @@ -import { Piece, SystemInformation } from "@/cache"; +import { Piece, DenormalizedSystemInformation } from "@/cache"; import { timeout } from "@/lib/utils"; -import { Clock, Duration, Option } from "effect"; +import { Clock, DateTime, Duration, Option } from "effect"; import { ReactNode } from "react"; export const DEBOUNCE = Clock.sleep(Duration.millis(250)); +export const SAVE_DELAY = Clock.sleep(Duration.millis(250)); export const saveDelay = () => timeout(250); @@ -30,9 +31,9 @@ export function authors(piece: Piece): ReactNode { return nodes.flatMap((x, i, a) => i < a.length - 1 ? [x,
] : [x]); } -export function created({ createdAt, createdBy }: SystemInformation): ReactNode { +export function created({ createdAt, createdBy }: DenormalizedSystemInformation): ReactNode { - const nodes: ReactNode[] = [createdAt]; + const nodes: ReactNode[] = [DateTime.formatLocal(createdAt)]; if (Option.isSome(createdBy)) { nodes.push(
); @@ -46,7 +47,7 @@ export function created({ createdAt, createdBy }: SystemInformation): ReactNode return nodes; } -export function modified({ modifiedAt, modifiedBy }: SystemInformation): ReactNode { +export function modified({ modifiedAt, modifiedBy }: DenormalizedSystemInformation): ReactNode { if (Option.isNone(modifiedAt)) { if (Option.isNone(modifiedBy)) { @@ -60,7 +61,7 @@ export function modified({ modifiedAt, modifiedBy }: SystemInformation): ReactNo } } - const nodes: ReactNode[] = [modifiedAt.value]; + const nodes: ReactNode[] = [DateTime.formatLocal(modifiedAt.value)]; if (Option.isSome(modifiedBy)) { nodes.push(
); diff --git a/packages/frontend/src/style.css b/packages/frontend/src/style.css index f0c1044..9e78131 100644 --- a/packages/frontend/src/style.css +++ b/packages/frontend/src/style.css @@ -1,9 +1,123 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; +@import "tailwindcss"; +@import "tw-animate-css"; + +@custom-variant dark (&:is(.dark *)); + +:root { + --font-sans: Lato, sans-serif; + --font-mono: 'JetBrains Mono', monospace; + + --accent-foreground: oklch(0.216 0.006 56.043); + --accent: oklch(0.97 0.001 106.424); + --background: oklch(1 0 0); + --border: oklch(0.923 0.003 48.717); + --card-foreground: oklch(0.147 0.004 49.25); + --card: oklch(1 0 0); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --destructive: oklch(0.577 0.245 27.325); + --foreground: oklch(0.147 0.004 49.25); + --input: oklch(0.923 0.003 48.717); + --muted-foreground: oklch(0.553 0.013 58.071); + --muted: oklch(0.97 0.001 106.424); + --popover-foreground: oklch(0.147 0.004 49.25); + --popover: oklch(1 0 0); + --primary-foreground: oklch(0.985 0.001 106.423); + --primary: oklch(0.216 0.006 56.043); + --radius: 0.625rem; + --ring: oklch(0.709 0.01 56.259); + --secondary-foreground: oklch(0.216 0.006 56.043); + --secondary: oklch(0.97 0.001 106.424); + --sidebar-accent-foreground: oklch(0.216 0.006 56.043); + --sidebar-accent: oklch(0.97 0.001 106.424); + --sidebar-border: oklch(0.923 0.003 48.717); + --sidebar-foreground: oklch(0.147 0.004 49.25); + --sidebar-primary-foreground: oklch(0.985 0.001 106.423); + --sidebar-primary: oklch(0.216 0.006 56.043); + --sidebar-ring: oklch(0.709 0.01 56.259); + --sidebar: oklch(0.985 0.001 106.423); +} + +.dark { + --accent-foreground: oklch(0.985 0.001 106.423); + --accent: oklch(0.268 0.007 34.298); + --background: oklch(0.147 0.004 49.25); + --border: oklch(1 0 0 / 10%); + --card-foreground: oklch(0.985 0.001 106.423); + --card: oklch(0.216 0.006 56.043); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --destructive: oklch(0.704 0.191 22.216); + --foreground: oklch(0.985 0.001 106.423); + --input: oklch(1 0 0 / 15%); + --muted-foreground: oklch(0.709 0.01 56.259); + --muted: oklch(0.268 0.007 34.298); + --popover-foreground: oklch(0.985 0.001 106.423); + --popover: oklch(0.216 0.006 56.043); + --primary-foreground: oklch(0.216 0.006 56.043); + --primary: oklch(0.923 0.003 48.717); + --ring: oklch(0.553 0.013 58.071); + --secondary-foreground: oklch(0.985 0.001 106.423); + --secondary: oklch(0.268 0.007 34.298); + --sidebar-accent-foreground: oklch(0.985 0.001 106.423); + --sidebar-accent: oklch(0.268 0.007 34.298); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-foreground: oklch(0.985 0.001 106.423); + --sidebar-primary-foreground: oklch(0.985 0.001 106.423); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-ring: oklch(0.553 0.013 58.071); + --sidebar: oklch(0.216 0.006 56.043); +} + +@theme inline { + --color-accent-foreground: var(--accent-foreground); + --color-accent: var(--accent); + --color-background: var(--background); + --color-border: var(--border); + --color-card-foreground: var(--card-foreground); + --color-card: var(--card); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --color-destructive: var(--destructive); + --color-foreground: var(--foreground); + --color-input: var(--input); + --color-muted-foreground: var(--muted-foreground); + --color-muted: var(--muted); + --color-popover-foreground: var(--popover-foreground); + --color-popover: var(--popover); + --color-primary-foreground: var(--primary-foreground); + --color-primary: var(--primary); + --color-ring: var(--ring); + --color-secondary-foreground: var(--secondary-foreground); + --color-secondary: var(--secondary); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-ring: var(--sidebar-ring); + --color-sidebar: var(--sidebar); + --radius-lg: var(--radius); + --radius-md: calc(var(--radius) - 2px); + --radius-sm: calc(var(--radius) - 4px); + --radius-xl: calc(var(--radius) + 4px); +} @layer base { - :root { - --radius: 0.5rem; + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; } } diff --git a/packages/frontend/tailwind.config.js b/packages/frontend/tailwind.config.js deleted file mode 100644 index 2b45fec..0000000 --- a/packages/frontend/tailwind.config.js +++ /dev/null @@ -1,25 +0,0 @@ -import tailwindcssAnimate from "tailwindcss-animate"; - -/** @type {import("tailwindcss").Config} */ -export default { - darkMode: ["class"], - content: [ - "./index.html", - "./src/**/*.{js,ts,jsx,tsx}", - ], - theme: { - fontFamily: { - sans: ["Lato", "sans-serif"], - mono: ["JetBrains Mono", "monospace"], - }, - extend: { - borderRadius: { - lg: "var(--radius)", - md: "calc(var(--radius) - 2px)", - sm: "calc(var(--radius) - 4px)", - }, - colors: {}, - }, - }, - plugins: [tailwindcssAnimate], -}; diff --git a/packages/frontend/vite.config.ts b/packages/frontend/vite.config.ts index 70fdcf2..00c27ce 100644 --- a/packages/frontend/vite.config.ts +++ b/packages/frontend/vite.config.ts @@ -1,9 +1,23 @@ +import tailwindcss from "@tailwindcss/vite"; import react from "@vitejs/plugin-react"; import path from "node:path"; import { defineConfig } from "vite"; +const ReactCompilerConfig = { + target: "19", +}; + export default defineConfig({ - plugins: [react()], + plugins: [ + tailwindcss(), + react({ + babel: { + plugins: [ + ["babel-plugin-react-compiler", ReactCompilerConfig], + ], + }, + }), + ], resolve: { alias: { "@": path.resolve(__dirname, "./src"), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1c45295..69638cc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,51 +6,48 @@ settings: catalogs: default: - '@elysiajs/cors': - specifier: ^1.2.0 - version: 1.2.0 - '@elysiajs/eden': - specifier: ^1.2.0 - version: 1.2.0 - '@elysiajs/static': - specifier: ^1.2.0 - version: 1.2.0 - '@elysiajs/swagger': - specifier: ^1.2.2 - version: 1.2.2 + '@effect/language-service': + specifier: ^0.21.4 + version: 0.21.4 '@eslint/js': - specifier: ^9.23.0 - version: 9.23.0 + specifier: ^9.29.0 + version: 9.29.0 '@radix-ui/react-dialog': - specifier: ^1.1.6 - version: 1.1.6 + specifier: ^1.1.14 + version: 1.1.14 '@radix-ui/react-dropdown-menu': - specifier: ^2.1.6 - version: 2.1.6 + specifier: ^2.1.15 + version: 2.1.15 '@radix-ui/react-label': - specifier: ^2.1.2 - version: 2.1.2 + specifier: ^2.1.7 + version: 2.1.7 '@radix-ui/react-slot': - specifier: ^1.1.2 - version: 1.1.2 + specifier: ^1.2.3 + version: 1.2.3 '@stylistic/eslint-plugin': - specifier: ^4.2.0 - version: 4.2.0 + specifier: ^4.4.1 + version: 4.4.1 + '@tailwindcss/vite': + specifier: ^4.1.10 + version: 4.1.10 '@types/bun': - specifier: ^1.2.8 - version: 1.2.8 + specifier: ^1.2.16 + version: 1.2.16 '@types/react': - specifier: ^18.3.12 - version: 18.3.20 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': - specifier: ^18.3.1 - version: 18.3.5 + specifier: ^19.1.6 + version: 19.1.6 '@vitejs/plugin-react': - specifier: ^4.3.4 - version: 4.3.4 - autoprefixer: - specifier: ^10.4.21 - version: 10.4.21 + specifier: ^4.5.2 + version: 4.5.2 + babel-plugin-react-compiler: + specifier: ^19.1.0-rc.2 + version: 19.1.0-rc.2 + cbor2: + specifier: ^2.0.1 + version: 2.0.1 class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -58,141 +55,132 @@ catalogs: specifier: ^2.1.1 version: 2.1.1 effect: - specifier: ^3.14.2 - version: 3.14.2 - elysia: - specifier: ^1.2.2 - version: 1.2.25 + specifier: ^3.16.8 + version: 3.16.8 eslint-plugin-react-hooks: - specifier: ^5.2.0 - version: 5.2.0 + specifier: 6.0.0-rc1 + version: 6.0.0-rc1 jszip: specifier: ^3.10.1 version: 3.10.1 kysely: - specifier: ^0.27.6 - version: 0.27.6 + specifier: ^0.28.2 + version: 0.28.2 kysely-bun-sqlite: - specifier: ^0.3.2 - version: 0.3.2 + specifier: ^0.4.0 + version: 0.4.0 lucide-react: - specifier: ^0.485.0 - version: 0.485.0 + specifier: ^0.518.0 + version: 0.518.0 opensheetmusicdisplay: specifier: ^1.9.0 version: 1.9.0 - postcss: - specifier: ^8.5.3 - version: 8.5.3 react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.1.0 + version: 19.1.0 react-dom: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.1.0 + version: 19.1.0 react-router-dom: - specifier: ^6.30.0 - version: 6.30.0 + specifier: ^7.6.2 + version: 7.6.2 tailwind-merge: - specifier: ^2.6.0 - version: 2.6.0 + specifier: ^3.3.1 + version: 3.3.1 tailwindcss: - specifier: ^3.4.17 - version: 3.4.17 - tailwindcss-animate: - specifier: ^1.0.7 - version: 1.0.7 + specifier: ^4.1.10 + version: 4.1.10 + tw-animate-css: + specifier: ^1.3.4 + version: 1.3.4 typescript: - specifier: ^5.8.2 - version: 5.8.2 + specifier: ^5.8.3 + version: 5.8.3 typescript-eslint: - specifier: ^8.28.0 - version: 8.28.0 + specifier: ^8.34.1 + version: 8.34.1 vite: - specifier: ^6.2.3 - version: 6.2.3 + specifier: ^6.3.5 + version: 6.3.5 importers: .: devDependencies: + '@effect/language-service': + specifier: 'catalog:' + version: 0.21.4 '@eslint/js': specifier: 'catalog:' - version: 9.23.0 + version: 9.29.0 '@stylistic/eslint-plugin': specifier: 'catalog:' - version: 4.2.0(eslint@9.23.0(jiti@1.21.7))(typescript@5.8.2) + version: 4.4.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) eslint-plugin-react-hooks: specifier: 'catalog:' - version: 5.2.0(eslint@9.23.0(jiti@1.21.7)) + version: 6.0.0-rc1(eslint@9.29.0(jiti@2.4.2)) typescript: specifier: 'catalog:' - version: 5.8.2 + version: 5.8.3 typescript-eslint: specifier: 'catalog:' - version: 8.28.0(eslint@9.23.0(jiti@1.21.7))(typescript@5.8.2) + version: 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) packages/backend: dependencies: - '@elysiajs/cors': + cbor2: specifier: 'catalog:' - version: 1.2.0(elysia@1.2.25(@sinclair/typebox@0.34.31)(openapi-types@12.1.3)(typescript@5.8.2)) - '@elysiajs/static': - specifier: 'catalog:' - version: 1.2.0(elysia@1.2.25(@sinclair/typebox@0.34.31)(openapi-types@12.1.3)(typescript@5.8.2)) - '@elysiajs/swagger': - specifier: 'catalog:' - version: 1.2.2(elysia@1.2.25(@sinclair/typebox@0.34.31)(openapi-types@12.1.3)(typescript@5.8.2)) + version: 2.0.1 common: specifier: workspace:^ version: link:../common effect: specifier: 'catalog:' - version: 3.14.2 - elysia: - specifier: 'catalog:' - version: 1.2.25(@sinclair/typebox@0.34.31)(openapi-types@12.1.3)(typescript@5.8.2) + version: 3.16.8 kysely: specifier: 'catalog:' - version: 0.27.6 + version: 0.28.2 kysely-bun-sqlite: specifier: 'catalog:' - version: 0.3.2(kysely@0.27.6) + version: 0.4.0(kysely@0.28.2) devDependencies: '@types/bun': specifier: 'catalog:' - version: 1.2.8 + version: 1.2.16 typescript: specifier: 'catalog:' - version: 5.8.2 + version: 5.8.3 packages/common: dependencies: + cbor2: + specifier: 'catalog:' + version: 2.0.1 effect: specifier: 'catalog:' - version: 3.14.2 + version: 3.16.8 devDependencies: + '@types/bun': + specifier: 'catalog:' + version: 1.2.16 typescript: specifier: 'catalog:' - version: 5.8.2 + version: 5.8.3 packages/frontend: dependencies: - '@elysiajs/eden': - specifier: 'catalog:' - version: 1.2.0(elysia@1.2.25(@sinclair/typebox@0.34.31)(openapi-types@12.1.3)(typescript@5.8.2)) '@radix-ui/react-dialog': specifier: 'catalog:' - version: 1.1.6(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@radix-ui/react-dropdown-menu': specifier: 'catalog:' - version: 2.1.6(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 2.1.15(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@radix-ui/react-label': specifier: 'catalog:' - version: 2.1.2(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 2.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@radix-ui/react-slot': specifier: 'catalog:' - version: 1.1.2(@types/react@18.3.20)(react@18.3.1) + version: 1.2.3(@types/react@19.1.8)(react@19.1.0) clsx: specifier: 'catalog:' version: 2.1.1 @@ -201,327 +189,341 @@ importers: version: link:../common effect: specifier: 'catalog:' - version: 3.14.2 + version: 3.16.8 jszip: specifier: 'catalog:' version: 3.10.1 lucide-react: specifier: 'catalog:' - version: 0.485.0(react@18.3.1) + version: 0.518.0(react@19.1.0) opensheetmusicdisplay: specifier: 'catalog:' version: 1.9.0 react: specifier: 'catalog:' - version: 18.3.1 + version: 19.1.0 react-dom: specifier: 'catalog:' - version: 18.3.1(react@18.3.1) + version: 19.1.0(react@19.1.0) react-router-dom: specifier: 'catalog:' - version: 6.30.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 7.6.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) tailwind-merge: specifier: 'catalog:' - version: 2.6.0 - tailwindcss-animate: - specifier: 'catalog:' - version: 1.0.7(tailwindcss@3.4.17) + version: 3.3.1 devDependencies: + '@tailwindcss/vite': + specifier: 'catalog:' + version: 4.1.10(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(lightningcss@1.30.1)) '@types/react': specifier: 'catalog:' - version: 18.3.20 + version: 19.1.8 '@types/react-dom': specifier: 'catalog:' - version: 18.3.5(@types/react@18.3.20) + version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: 'catalog:' - version: 4.3.4(vite@6.2.3(@types/node@22.13.14)(jiti@1.21.7)(yaml@2.7.0)) - autoprefixer: + version: 4.5.2(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(lightningcss@1.30.1)) + babel-plugin-react-compiler: specifier: 'catalog:' - version: 10.4.21(postcss@8.5.3) + version: 19.1.0-rc.2 backend: specifier: workspace:^ version: link:../backend class-variance-authority: specifier: 'catalog:' version: 0.7.1 - elysia: - specifier: 'catalog:' - version: 1.2.25(@sinclair/typebox@0.34.31)(openapi-types@12.1.3)(typescript@5.8.2) - postcss: - specifier: 'catalog:' - version: 8.5.3 tailwindcss: specifier: 'catalog:' - version: 3.4.17 + version: 4.1.10 + tw-animate-css: + specifier: 'catalog:' + version: 1.3.4 typescript: specifier: 'catalog:' - version: 5.8.2 + version: 5.8.3 vite: specifier: 'catalog:' - version: 6.2.3(@types/node@22.13.14)(jiti@1.21.7)(yaml@2.7.0) + version: 6.3.5(@types/node@24.0.3)(jiti@2.4.2)(lightningcss@1.30.1) packages: - '@alloc/quick-lru@5.2.0': - resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} - engines: {node: '>=10'} - '@ampproject/remapping@2.3.0': resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} - '@babel/code-frame@7.26.2': - resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.26.8': - resolution: {integrity: sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==} + '@babel/compat-data@7.27.5': + resolution: {integrity: sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==} engines: {node: '>=6.9.0'} - '@babel/core@7.26.10': - resolution: {integrity: sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==} + '@babel/core@7.27.4': + resolution: {integrity: sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==} engines: {node: '>=6.9.0'} - '@babel/generator@7.27.0': - resolution: {integrity: sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==} + '@babel/generator@7.27.5': + resolution: {integrity: sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.27.0': - resolution: {integrity: sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==} + '@babel/helper-annotate-as-pure@7.27.3': + resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.25.9': - resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} + '@babel/helper-compilation-targets@7.27.2': + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.26.0': - resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} + '@babel/helper-create-class-features-plugin@7.27.1': + resolution: {integrity: sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-plugin-utils@7.26.5': - resolution: {integrity: sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==} + '@babel/helper-member-expression-to-functions@7.27.1': + resolution: {integrity: sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==} engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.25.9': - resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + '@babel/helper-module-imports@7.27.1': + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.25.9': - resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + '@babel/helper-module-transforms@7.27.3': + resolution: {integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.27.1': + resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-option@7.25.9': - resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} + '@babel/helper-plugin-utils@7.27.1': + resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.27.0': - resolution: {integrity: sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==} + '@babel/helper-replace-supers@7.27.1': + resolution: {integrity: sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} engines: {node: '>=6.9.0'} - '@babel/parser@7.27.0': - resolution: {integrity: sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==} + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.27.6': + resolution: {integrity: sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.27.5': + resolution: {integrity: sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/plugin-transform-react-jsx-self@7.25.9': - resolution: {integrity: sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==} + '@babel/plugin-transform-private-methods@7.27.1': + resolution: {integrity: sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx-source@7.25.9': - resolution: {integrity: sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==} + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/template@7.27.0': - resolution: {integrity: sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==} + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.27.2': + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.27.0': - resolution: {integrity: sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==} + '@babel/traverse@7.27.4': + resolution: {integrity: sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==} engines: {node: '>=6.9.0'} - '@babel/types@7.27.0': - resolution: {integrity: sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==} + '@babel/types@7.27.6': + resolution: {integrity: sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==} engines: {node: '>=6.9.0'} - '@elysiajs/cors@1.2.0': - resolution: {integrity: sha512-qsJwDAg6WfdQRMfj6uSMcDPSpXvm/zQFeAX1uuJXhIgazH8itSfcDxcH9pMuXVRX1yQNi2pPwNQLJmAcw5mzvw==} - peerDependencies: - elysia: '>= 1.2.0' + '@cto.af/wtf8@0.0.2': + resolution: {integrity: sha512-ATm4UQiKrdm5GnU6BvIwUDN+LDEtt23zuzKFpnfDT59ULAd0aMYm/nSFzbSO02garLcXumRC13PzNfa7BsfvSg==} + engines: {node: '>=20'} - '@elysiajs/eden@1.2.0': - resolution: {integrity: sha512-MpV45ahuF+iFZUg4tyJbLr9qxzY99m8clJVgQrDrz7Qh6eOKQ8MY6vjYMj3Wh21pTIRHPHzOLhVorRGby1/Owg==} - peerDependencies: - elysia: '>= 1.2.0' + '@effect/language-service@0.21.4': + resolution: {integrity: sha512-o45QICueVVPsF7YEF/KJzZxtFG+CE9kKy2/ZTG6ANwrii+8d950me0LMkhwbx5Wypt98vQbXivWsH86D0akXSw==} - '@elysiajs/static@1.2.0': - resolution: {integrity: sha512-oLpAi8c+maPpA0XhhK3BELaIjIG+nXg/K9p8cFfW4q5ayRD59a3MOMOOGgpiXZkHJzLPWcouhhyyLAYtaANW4g==} - peerDependencies: - elysia: '>= 1.2.0' - - '@elysiajs/swagger@1.2.2': - resolution: {integrity: sha512-DG0PbX/wzQNQ6kIpFFPCvmkkWTIbNWDS7lVLv3Puy6ONklF14B4NnbDfpYjX1hdSYKeCqKBBOuenh6jKm8tbYA==} - peerDependencies: - elysia: '>= 1.2.0' - - '@esbuild/aix-ppc64@0.25.1': - resolution: {integrity: sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==} + '@esbuild/aix-ppc64@0.25.5': + resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.25.1': - resolution: {integrity: sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==} + '@esbuild/android-arm64@0.25.5': + resolution: {integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.25.1': - resolution: {integrity: sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==} + '@esbuild/android-arm@0.25.5': + resolution: {integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.25.1': - resolution: {integrity: sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==} + '@esbuild/android-x64@0.25.5': + resolution: {integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.25.1': - resolution: {integrity: sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==} + '@esbuild/darwin-arm64@0.25.5': + resolution: {integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.25.1': - resolution: {integrity: sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==} + '@esbuild/darwin-x64@0.25.5': + resolution: {integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.25.1': - resolution: {integrity: sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==} + '@esbuild/freebsd-arm64@0.25.5': + resolution: {integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.1': - resolution: {integrity: sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==} + '@esbuild/freebsd-x64@0.25.5': + resolution: {integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.25.1': - resolution: {integrity: sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==} + '@esbuild/linux-arm64@0.25.5': + resolution: {integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.25.1': - resolution: {integrity: sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==} + '@esbuild/linux-arm@0.25.5': + resolution: {integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.25.1': - resolution: {integrity: sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==} + '@esbuild/linux-ia32@0.25.5': + resolution: {integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.25.1': - resolution: {integrity: sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==} + '@esbuild/linux-loong64@0.25.5': + resolution: {integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.25.1': - resolution: {integrity: sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==} + '@esbuild/linux-mips64el@0.25.5': + resolution: {integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.25.1': - resolution: {integrity: sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==} + '@esbuild/linux-ppc64@0.25.5': + resolution: {integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.25.1': - resolution: {integrity: sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==} + '@esbuild/linux-riscv64@0.25.5': + resolution: {integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.25.1': - resolution: {integrity: sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==} + '@esbuild/linux-s390x@0.25.5': + resolution: {integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.25.1': - resolution: {integrity: sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==} + '@esbuild/linux-x64@0.25.5': + resolution: {integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.25.1': - resolution: {integrity: sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==} + '@esbuild/netbsd-arm64@0.25.5': + resolution: {integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.1': - resolution: {integrity: sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==} + '@esbuild/netbsd-x64@0.25.5': + resolution: {integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.25.1': - resolution: {integrity: sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==} + '@esbuild/openbsd-arm64@0.25.5': + resolution: {integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.1': - resolution: {integrity: sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==} + '@esbuild/openbsd-x64@0.25.5': + resolution: {integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/sunos-x64@0.25.1': - resolution: {integrity: sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==} + '@esbuild/sunos-x64@0.25.5': + resolution: {integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.25.1': - resolution: {integrity: sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==} + '@esbuild/win32-arm64@0.25.5': + resolution: {integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.25.1': - resolution: {integrity: sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==} + '@esbuild/win32-ia32@0.25.5': + resolution: {integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.25.1': - resolution: {integrity: sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==} + '@esbuild/win32-x64@0.25.5': + resolution: {integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==} engines: {node: '>=18'} cpu: [x64] os: [win32] - '@eslint-community/eslint-utils@4.5.1': - resolution: {integrity: sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==} + '@eslint-community/eslint-utils@4.7.0': + resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 @@ -530,42 +532,46 @@ packages: resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/config-array@0.19.2': - resolution: {integrity: sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==} + '@eslint/config-array@0.20.1': + resolution: {integrity: sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/config-helpers@0.2.0': - resolution: {integrity: sha512-yJLLmLexii32mGrhW29qvU3QBVTu0GUmEf/J4XsBtVhp4JkIUFN/BjWqTF63yRvGApIDpZm5fa97LtYtINmfeQ==} + '@eslint/config-helpers@0.2.3': + resolution: {integrity: sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/core@0.12.0': - resolution: {integrity: sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==} + '@eslint/core@0.14.0': + resolution: {integrity: sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.15.0': + resolution: {integrity: sha512-b7ePw78tEWWkpgZCDYkbqDOP8dmM6qe+AOC6iuJqlq1R/0ahMAeH3qynpnqKFGkMltrp44ohV4ubGyvLX28tzw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/eslintrc@3.3.1': resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.23.0': - resolution: {integrity: sha512-35MJ8vCPU0ZMxo7zfev2pypqTwWTofFZO6m4KAtdoFhRpLJUpHTZZ+KB3C7Hb1d7bULYwO4lJXGCi5Se+8OMbw==} + '@eslint/js@9.29.0': + resolution: {integrity: sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/object-schema@2.1.6': resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.2.7': - resolution: {integrity: sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==} + '@eslint/plugin-kit@0.3.2': + resolution: {integrity: sha512-4SaFZCNfJqvk/kenHpI8xvN42DMaoycy4PzKc5otHxRswww1kAt82OlBuwRVLofCACCTZEcla2Ydxv8scMXaTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@floating-ui/core@1.6.9': - resolution: {integrity: sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==} + '@floating-ui/core@1.7.1': + resolution: {integrity: sha512-azI0DrjMMfIug/ExbBaeDVJXcY0a7EPvPjb2xAJPa4HeimBX+Z18HK8QQR3jb6356SnDDdxx+hinMLcJEDdOjw==} - '@floating-ui/dom@1.6.13': - resolution: {integrity: sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==} + '@floating-ui/dom@1.7.1': + resolution: {integrity: sha512-cwsmW/zyw5ltYTUeeYJ60CnQuPqmGwuGVhG9w0PRaRKkAyi38BT5CKrpIbb+jtahSwUl04cWzSx9ZOIxeS6RsQ==} - '@floating-ui/react-dom@2.1.2': - resolution: {integrity: sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==} + '@floating-ui/react-dom@2.1.3': + resolution: {integrity: sha512-huMBfiU9UnQ2oBwIhgzyIiSpVgvlDstU8CX0AF+wS+KzmYMs0J2a3GwuFHV1Lz+jlrQGeC1fF+Nv0QoumyV0bA==} peerDependencies: react: '>=16.8.0' react-dom: '>=16.8.0' @@ -592,13 +598,13 @@ packages: resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} engines: {node: '>=18.18'} - '@humanwhocodes/retry@0.4.2': - resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==} + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} '@jridgewell/gen-mapping@0.3.8': resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} @@ -639,15 +645,11 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} deprecated: This functionality has been moved to @npmcli/fs - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} + '@radix-ui/primitive@1.1.2': + resolution: {integrity: sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==} - '@radix-ui/primitive@1.1.1': - resolution: {integrity: sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==} - - '@radix-ui/react-arrow@1.1.2': - resolution: {integrity: sha512-G+KcpzXHq24iH0uGG/pF8LyzpFJYGD4RfLjCIBfGdSLXvjLHST31RUiRVrupIBMvIppMgSzQ6l66iAxl03tdlg==} + '@radix-ui/react-arrow@1.1.7': + resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -659,8 +661,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-collection@1.1.2': - resolution: {integrity: sha512-9z54IEKRxIa9VityapoEYMuByaG42iSy1ZXlY2KcuLSEtq8x4987/N6m15ppoMffgZX72gER2uHe1D9Y6Unlcw==} + '@radix-ui/react-collection@1.1.7': + resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -672,8 +674,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-compose-refs@1.1.1': - resolution: {integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==} + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -681,8 +683,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-context@1.1.1': - resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==} + '@radix-ui/react-context@1.1.2': + resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -690,8 +692,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-dialog@1.1.6': - resolution: {integrity: sha512-/IVhJV5AceX620DUJ4uYVMymzsipdKBzo3edo+omeskCKGm9FRHM0ebIdbPnlQVJqyuHbuBltQUOG2mOTq2IYw==} + '@radix-ui/react-dialog@1.1.14': + resolution: {integrity: sha512-+CpweKjqpzTmwRwcYECQcNYbI8V9VSQt0SNFKeEBLgfucbsLssU6Ppq7wUdNXEGb573bMjFhVjKVll8rmV6zMw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -703,8 +705,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-direction@1.1.0': - resolution: {integrity: sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==} + '@radix-ui/react-direction@1.1.1': + resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -712,8 +714,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-dismissable-layer@1.1.5': - resolution: {integrity: sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg==} + '@radix-ui/react-dismissable-layer@1.1.10': + resolution: {integrity: sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -725,8 +727,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-dropdown-menu@2.1.6': - resolution: {integrity: sha512-no3X7V5fD487wab/ZYSHXq3H37u4NVeLDKI/Ks724X/eEFSSEFYZxWgsIlr1UBeEyDaM29HM5x9p1Nv8DuTYPA==} + '@radix-ui/react-dropdown-menu@2.1.15': + resolution: {integrity: sha512-mIBnOjgwo9AH3FyKaSWoSu/dYj6VdhJ7frEPiGTeXCdUFHjl9h3mFh2wwhEtINOmYXWhdpf1rY2minFsmaNgVQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -738,8 +740,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-focus-guards@1.1.1': - resolution: {integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==} + '@radix-ui/react-focus-guards@1.1.2': + resolution: {integrity: sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -747,8 +749,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-focus-scope@1.1.2': - resolution: {integrity: sha512-zxwE80FCU7lcXUGWkdt6XpTTCKPitG1XKOwViTxHVKIJhZl9MvIl2dVHeZENCWD9+EdWv05wlaEkRXUykU27RA==} + '@radix-ui/react-focus-scope@1.1.7': + resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -760,8 +762,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-id@1.1.0': - resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==} + '@radix-ui/react-id@1.1.1': + resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -769,8 +771,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-label@2.1.2': - resolution: {integrity: sha512-zo1uGMTaNlHehDyFQcDZXRJhUPDuukcnHz0/jnrup0JA6qL+AFpAnty+7VKa9esuU5xTblAZzTGYJKSKaBxBhw==} + '@radix-ui/react-label@2.1.7': + resolution: {integrity: sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -782,8 +784,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-menu@2.1.6': - resolution: {integrity: sha512-tBBb5CXDJW3t2mo9WlO7r6GTmWV0F0uzHZVFmlRmYpiSK1CDU5IKojP1pm7oknpBOrFZx/YgBRW9oorPO2S/Lg==} + '@radix-ui/react-menu@2.1.15': + resolution: {integrity: sha512-tVlmA3Vb9n8SZSd+YSbuFR66l87Wiy4du+YE+0hzKQEANA+7cWKH1WgqcEX4pXqxUFQKrWQGHdvEfw00TjFiew==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -795,8 +797,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-popper@1.2.2': - resolution: {integrity: sha512-Rvqc3nOpwseCyj/rgjlJDYAgyfw7OC1tTkKn2ivhaMGcYt8FSBlahHOZak2i3QwkRXUXgGgzeEe2RuqeEHuHgA==} + '@radix-ui/react-popper@1.2.7': + resolution: {integrity: sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -808,8 +810,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-portal@1.1.4': - resolution: {integrity: sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA==} + '@radix-ui/react-portal@1.1.9': + resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -821,8 +823,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-presence@1.1.2': - resolution: {integrity: sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==} + '@radix-ui/react-presence@1.1.4': + resolution: {integrity: sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -834,8 +836,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-primitive@2.0.2': - resolution: {integrity: sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==} + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -847,8 +849,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-roving-focus@1.1.2': - resolution: {integrity: sha512-zgMQWkNO169GtGqRvYrzb0Zf8NhMHS2DuEB/TiEmVnpr5OqPU3i8lfbxaAmC2J/KYuIQxyoQQ6DxepyXp61/xw==} + '@radix-ui/react-roving-focus@1.1.10': + resolution: {integrity: sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -860,8 +862,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-slot@1.1.2': - resolution: {integrity: sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==} + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -869,8 +871,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-callback-ref@1.1.0': - resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==} + '@radix-ui/react-use-callback-ref@1.1.1': + resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -878,8 +880,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-controllable-state@1.1.0': - resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==} + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -887,8 +889,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-escape-keydown@1.1.0': - resolution: {integrity: sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==} + '@radix-ui/react-use-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -896,8 +898,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-layout-effect@1.1.0': - resolution: {integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==} + '@radix-ui/react-use-escape-keydown@1.1.1': + resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -905,8 +907,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-rect@1.1.0': - resolution: {integrity: sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==} + '@radix-ui/react-use-layout-effect@1.1.1': + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -914,8 +916,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-size@1.1.0': - resolution: {integrity: sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==} + '@radix-ui/react-use-rect@1.1.1': + resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -923,145 +925,220 @@ packages: '@types/react': optional: true - '@radix-ui/rect@1.1.0': - resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==} + '@radix-ui/react-use-size@1.1.1': + resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - '@remix-run/router@1.23.0': - resolution: {integrity: sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==} - engines: {node: '>=14.0.0'} + '@radix-ui/rect@1.1.1': + resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} - '@rollup/rollup-android-arm-eabi@4.37.0': - resolution: {integrity: sha512-l7StVw6WAa8l3vA1ov80jyetOAEo1FtHvZDbzXDO/02Sq/QVvqlHkYoFwDJPIMj0GKiistsBudfx5tGFnwYWDQ==} + '@rolldown/pluginutils@1.0.0-beta.11': + resolution: {integrity: sha512-L/gAA/hyCSuzTF1ftlzUSI/IKr2POHsv1Dd78GfqkR83KMNuswWD61JxGV2L7nRwBBBSDr6R1gCkdTmoN7W4ag==} + + '@rollup/rollup-android-arm-eabi@4.44.0': + resolution: {integrity: sha512-xEiEE5oDW6tK4jXCAyliuntGR+amEMO7HLtdSshVuhFnKTYoeYMyXQK7pLouAJJj5KHdwdn87bfHAR2nSdNAUA==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.37.0': - resolution: {integrity: sha512-6U3SlVyMxezt8Y+/iEBcbp945uZjJwjZimu76xoG7tO1av9VO691z8PkhzQ85ith2I8R2RddEPeSfcbyPfD4hA==} + '@rollup/rollup-android-arm64@4.44.0': + resolution: {integrity: sha512-uNSk/TgvMbskcHxXYHzqwiyBlJ/lGcv8DaUfcnNwict8ba9GTTNxfn3/FAoFZYgkaXXAdrAA+SLyKplyi349Jw==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.37.0': - resolution: {integrity: sha512-+iTQ5YHuGmPt10NTzEyMPbayiNTcOZDWsbxZYR1ZnmLnZxG17ivrPSWFO9j6GalY0+gV3Jtwrrs12DBscxnlYA==} + '@rollup/rollup-darwin-arm64@4.44.0': + resolution: {integrity: sha512-VGF3wy0Eq1gcEIkSCr8Ke03CWT+Pm2yveKLaDvq51pPpZza3JX/ClxXOCmTYYq3us5MvEuNRTaeyFThCKRQhOA==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.37.0': - resolution: {integrity: sha512-m8W2UbxLDcmRKVjgl5J/k4B8d7qX2EcJve3Sut7YGrQoPtCIQGPH5AMzuFvYRWZi0FVS0zEY4c8uttPfX6bwYQ==} + '@rollup/rollup-darwin-x64@4.44.0': + resolution: {integrity: sha512-fBkyrDhwquRvrTxSGH/qqt3/T0w5Rg0L7ZIDypvBPc1/gzjJle6acCpZ36blwuwcKD/u6oCE/sRWlUAcxLWQbQ==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.37.0': - resolution: {integrity: sha512-FOMXGmH15OmtQWEt174v9P1JqqhlgYge/bUjIbiVD1nI1NeJ30HYT9SJlZMqdo1uQFyt9cz748F1BHghWaDnVA==} + '@rollup/rollup-freebsd-arm64@4.44.0': + resolution: {integrity: sha512-u5AZzdQJYJXByB8giQ+r4VyfZP+walV+xHWdaFx/1VxsOn6eWJhK2Vl2eElvDJFKQBo/hcYIBg/jaKS8ZmKeNQ==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.37.0': - resolution: {integrity: sha512-SZMxNttjPKvV14Hjck5t70xS3l63sbVwl98g3FlVVx2YIDmfUIy29jQrsw06ewEYQ8lQSuY9mpAPlmgRD2iSsA==} + '@rollup/rollup-freebsd-x64@4.44.0': + resolution: {integrity: sha512-qC0kS48c/s3EtdArkimctY7h3nHicQeEUdjJzYVJYR3ct3kWSafmn6jkNCA8InbUdge6PVx6keqjk5lVGJf99g==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.37.0': - resolution: {integrity: sha512-hhAALKJPidCwZcj+g+iN+38SIOkhK2a9bqtJR+EtyxrKKSt1ynCBeqrQy31z0oWU6thRZzdx53hVgEbRkuI19w==} + '@rollup/rollup-linux-arm-gnueabihf@4.44.0': + resolution: {integrity: sha512-x+e/Z9H0RAWckn4V2OZZl6EmV0L2diuX3QB0uM1r6BvhUIv6xBPL5mrAX2E3e8N8rEHVPwFfz/ETUbV4oW9+lQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.37.0': - resolution: {integrity: sha512-jUb/kmn/Gd8epbHKEqkRAxq5c2EwRt0DqhSGWjPFxLeFvldFdHQs/n8lQ9x85oAeVb6bHcS8irhTJX2FCOd8Ag==} + '@rollup/rollup-linux-arm-musleabihf@4.44.0': + resolution: {integrity: sha512-1exwiBFf4PU/8HvI8s80icyCcnAIB86MCBdst51fwFmH5dyeoWVPVgmQPcKrMtBQ0W5pAs7jBCWuRXgEpRzSCg==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.37.0': - resolution: {integrity: sha512-oNrJxcQT9IcbcmKlkF+Yz2tmOxZgG9D9GRq+1OE6XCQwCVwxixYAa38Z8qqPzQvzt1FCfmrHX03E0pWoXm1DqA==} + '@rollup/rollup-linux-arm64-gnu@4.44.0': + resolution: {integrity: sha512-ZTR2mxBHb4tK4wGf9b8SYg0Y6KQPjGpR4UWwTFdnmjB4qRtoATZ5dWn3KsDwGa5Z2ZBOE7K52L36J9LueKBdOQ==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.37.0': - resolution: {integrity: sha512-pfxLBMls+28Ey2enpX3JvjEjaJMBX5XlPCZNGxj4kdJyHduPBXtxYeb8alo0a7bqOoWZW2uKynhHxF/MWoHaGQ==} + '@rollup/rollup-linux-arm64-musl@4.44.0': + resolution: {integrity: sha512-GFWfAhVhWGd4r6UxmnKRTBwP1qmModHtd5gkraeW2G490BpFOZkFtem8yuX2NyafIP/mGpRJgTJ2PwohQkUY/Q==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.37.0': - resolution: {integrity: sha512-yCE0NnutTC/7IGUq/PUHmoeZbIwq3KRh02e9SfFh7Vmc1Z7atuJRYWhRME5fKgT8aS20mwi1RyChA23qSyRGpA==} + '@rollup/rollup-linux-loongarch64-gnu@4.44.0': + resolution: {integrity: sha512-xw+FTGcov/ejdusVOqKgMGW3c4+AgqrfvzWEVXcNP6zq2ue+lsYUgJ+5Rtn/OTJf7e2CbgTFvzLW2j0YAtj0Gg==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.37.0': - resolution: {integrity: sha512-NxcICptHk06E2Lh3a4Pu+2PEdZ6ahNHuK7o6Np9zcWkrBMuv21j10SQDJW3C9Yf/A/P7cutWoC/DptNLVsZ0VQ==} + '@rollup/rollup-linux-powerpc64le-gnu@4.44.0': + resolution: {integrity: sha512-bKGibTr9IdF0zr21kMvkZT4K6NV+jjRnBoVMt2uNMG0BYWm3qOVmYnXKzx7UhwrviKnmK46IKMByMgvpdQlyJQ==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.37.0': - resolution: {integrity: sha512-PpWwHMPCVpFZLTfLq7EWJWvrmEuLdGn1GMYcm5MV7PaRgwCEYJAwiN94uBuZev0/J/hFIIJCsYw4nLmXA9J7Pw==} + '@rollup/rollup-linux-riscv64-gnu@4.44.0': + resolution: {integrity: sha512-vV3cL48U5kDaKZtXrti12YRa7TyxgKAIDoYdqSIOMOFBXqFj2XbChHAtXquEn2+n78ciFgr4KIqEbydEGPxXgA==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.37.0': - resolution: {integrity: sha512-DTNwl6a3CfhGTAOYZ4KtYbdS8b+275LSLqJVJIrPa5/JuIufWWZ/QFvkxp52gpmguN95eujrM68ZG+zVxa8zHA==} + '@rollup/rollup-linux-riscv64-musl@4.44.0': + resolution: {integrity: sha512-TDKO8KlHJuvTEdfw5YYFBjhFts2TR0VpZsnLLSYmB7AaohJhM8ctDSdDnUGq77hUh4m/djRafw+9zQpkOanE2Q==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.37.0': - resolution: {integrity: sha512-hZDDU5fgWvDdHFuExN1gBOhCuzo/8TMpidfOR+1cPZJflcEzXdCy1LjnklQdW8/Et9sryOPJAKAQRw8Jq7Tg+A==} + '@rollup/rollup-linux-s390x-gnu@4.44.0': + resolution: {integrity: sha512-8541GEyktXaw4lvnGp9m84KENcxInhAt6vPWJ9RodsB/iGjHoMB2Pp5MVBCiKIRxrxzJhGCxmNzdu+oDQ7kwRA==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.37.0': - resolution: {integrity: sha512-pKivGpgJM5g8dwj0ywBwe/HeVAUSuVVJhUTa/URXjxvoyTT/AxsLTAbkHkDHG7qQxLoW2s3apEIl26uUe08LVQ==} + '@rollup/rollup-linux-x64-gnu@4.44.0': + resolution: {integrity: sha512-iUVJc3c0o8l9Sa/qlDL2Z9UP92UZZW1+EmQ4xfjTc1akr0iUFZNfxrXJ/R1T90h/ILm9iXEY6+iPrmYB3pXKjw==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.37.0': - resolution: {integrity: sha512-E2lPrLKE8sQbY/2bEkVTGDEk4/49UYRVWgj90MY8yPjpnGBQ+Xi1Qnr7b7UIWw1NOggdFQFOLZ8+5CzCiz143w==} + '@rollup/rollup-linux-x64-musl@4.44.0': + resolution: {integrity: sha512-PQUobbhLTQT5yz/SPg116VJBgz+XOtXt8D1ck+sfJJhuEsMj2jSej5yTdp8CvWBSceu+WW+ibVL6dm0ptG5fcA==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.37.0': - resolution: {integrity: sha512-Jm7biMazjNzTU4PrQtr7VS8ibeys9Pn29/1bm4ph7CP2kf21950LgN+BaE2mJ1QujnvOc6p54eWWiVvn05SOBg==} + '@rollup/rollup-win32-arm64-msvc@4.44.0': + resolution: {integrity: sha512-M0CpcHf8TWn+4oTxJfh7LQuTuaYeXGbk0eageVjQCKzYLsajWS/lFC94qlRqOlyC2KvRT90ZrfXULYmukeIy7w==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.37.0': - resolution: {integrity: sha512-e3/1SFm1OjefWICB2Ucstg2dxYDkDTZGDYgwufcbsxTHyqQps1UQf33dFEChBNmeSsTOyrjw2JJq0zbG5GF6RA==} + '@rollup/rollup-win32-ia32-msvc@4.44.0': + resolution: {integrity: sha512-3XJ0NQtMAXTWFW8FqZKcw3gOQwBtVWP/u8TpHP3CRPXD7Pd6s8lLdH3sHWh8vqKCyyiI8xW5ltJScQmBU9j7WA==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.37.0': - resolution: {integrity: sha512-LWbXUBwn/bcLx2sSsqy7pK5o+Nr+VCoRoAohfJ5C/aBio9nfJmGQqHAhU6pwxV/RmyTk5AqdySma7uwWGlmeuA==} + '@rollup/rollup-win32-x64-msvc@4.44.0': + resolution: {integrity: sha512-Q2Mgwt+D8hd5FIPUuPDsvPR7Bguza6yTkJxspDGkZj7tBRn2y4KSWYuIXpftFSjBra76TbKerCV7rgFPQrn+wQ==} cpu: [x64] os: [win32] - '@scalar/openapi-types@0.1.1': - resolution: {integrity: sha512-NMy3QNk6ytcCoPUGJH0t4NNr36OWXgZhA3ormr3TvhX1NDgoF95wFyodGVH8xiHeUyn2/FxtETm8UBLbB5xEmg==} - engines: {node: '>=18'} - - '@scalar/openapi-types@0.1.9': - resolution: {integrity: sha512-HQQudOSQBU7ewzfnBW9LhDmBE2XOJgSfwrh5PlUB7zJup/kaRkBGNgV2wMjNz9Af/uztiU/xNrO179FysmUT+g==} - engines: {node: '>=18'} - - '@scalar/themes@0.9.82': - resolution: {integrity: sha512-VbzDVyF6yQMeqsr9nj+twyMXmO5k0AEmL6Zsjf3alzOAMuiN4ee5Hcssd7XE44cnUrHiwm1i578bU6949N+7lQ==} - engines: {node: '>=18'} - - '@scalar/types@0.0.12': - resolution: {integrity: sha512-XYZ36lSEx87i4gDqopQlGCOkdIITHHEvgkuJFrXFATQs9zHARop0PN0g4RZYWj+ZpCUclOcaOjbCt8JGe22mnQ==} - engines: {node: '>=18'} - - '@scalar/types@0.1.4': - resolution: {integrity: sha512-IAxpfrfdYfliLJR6WbuC8hxUwBUOeVsGuZxQE+zP8JDtdoHmgT6aNxCqMnGZ1ft6dvJ4jvzVR6qCWrq6Kg25oA==} - engines: {node: '>=18'} - - '@sinclair/typebox@0.34.31': - resolution: {integrity: sha512-qQ71T9DsITbX3dVCrcBERbs11YuSMg3wZPnT472JhqhWGPdiLgyvihJXU8m+ADJtJvRdjATIiACJD22dEknBrQ==} - '@standard-schema/spec@1.0.0': resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} - '@stylistic/eslint-plugin@4.2.0': - resolution: {integrity: sha512-8hXezgz7jexGHdo5WN6JBEIPHCSFyyU4vgbxevu4YLVS5vl+sxqAAGyXSzfNDyR6xMNSH5H1x67nsXcYMOHtZA==} + '@stylistic/eslint-plugin@4.4.1': + resolution: {integrity: sha512-CEigAk7eOLyHvdgmpZsKFwtiqS2wFwI1fn4j09IU9GmD4euFM4jEBAViWeCqaNLlbX2k2+A/Fq9cje4HQBXuJQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: '>=9.0.0' + '@tailwindcss/node@4.1.10': + resolution: {integrity: sha512-2ACf1znY5fpRBwRhMgj9ZXvb2XZW8qs+oTfotJ2C5xR0/WNL7UHZ7zXl6s+rUqedL1mNi+0O+WQr5awGowS3PQ==} + + '@tailwindcss/oxide-android-arm64@4.1.10': + resolution: {integrity: sha512-VGLazCoRQ7rtsCzThaI1UyDu/XRYVyH4/EWiaSX6tFglE+xZB5cvtC5Omt0OQ+FfiIVP98su16jDVHDEIuH4iQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.1.10': + resolution: {integrity: sha512-ZIFqvR1irX2yNjWJzKCqTCcHZbgkSkSkZKbRM3BPzhDL/18idA8uWCoopYA2CSDdSGFlDAxYdU2yBHwAwx8euQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.1.10': + resolution: {integrity: sha512-eCA4zbIhWUFDXoamNztmS0MjXHSEJYlvATzWnRiTqJkcUteSjO94PoRHJy1Xbwp9bptjeIxxBHh+zBWFhttbrQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.1.10': + resolution: {integrity: sha512-8/392Xu12R0cc93DpiJvNpJ4wYVSiciUlkiOHOSOQNH3adq9Gi/dtySK7dVQjXIOzlpSHjeCL89RUUI8/GTI6g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.10': + resolution: {integrity: sha512-t9rhmLT6EqeuPT+MXhWhlRYIMSfh5LZ6kBrC4FS6/+M1yXwfCtp24UumgCWOAJVyjQwG+lYva6wWZxrfvB+NhQ==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.10': + resolution: {integrity: sha512-3oWrlNlxLRxXejQ8zImzrVLuZ/9Z2SeKoLhtCu0hpo38hTO2iL86eFOu4sVR8cZc6n3z7eRXXqtHJECa6mFOvA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.1.10': + resolution: {integrity: sha512-saScU0cmWvg/Ez4gUmQWr9pvY9Kssxt+Xenfx1LG7LmqjcrvBnw4r9VjkFcqmbBb7GCBwYNcZi9X3/oMda9sqQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.1.10': + resolution: {integrity: sha512-/G3ao/ybV9YEEgAXeEg28dyH6gs1QG8tvdN9c2MNZdUXYBaIY/Gx0N6RlJzfLy/7Nkdok4kaxKPHKJUlAaoTdA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.1.10': + resolution: {integrity: sha512-LNr7X8fTiKGRtQGOerSayc2pWJp/9ptRYAa4G+U+cjw9kJZvkopav1AQc5HHD+U364f71tZv6XamaHKgrIoVzA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-wasm32-wasi@4.1.10': + resolution: {integrity: sha512-d6ekQpopFQJAcIK2i7ZzWOYGZ+A6NzzvQ3ozBvWFdeyqfOZdYHU66g5yr+/HC4ipP1ZgWsqa80+ISNILk+ae/Q==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.10': + resolution: {integrity: sha512-i1Iwg9gRbwNVOCYmnigWCCgow8nDWSFmeTUU5nbNx3rqbe4p0kRbEqLwLJbYZKmSSp23g4N6rCDmm7OuPBXhDA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.1.10': + resolution: {integrity: sha512-sGiJTjcBSfGq2DVRtaSljq5ZgZS2SDHSIfhOylkBvHVjwOsodBhnb3HdmiKkVuUGKD0I7G63abMOVaskj1KpOA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.1.10': + resolution: {integrity: sha512-v0C43s7Pjw+B9w21htrQwuFObSkio2aV/qPx/mhrRldbqxbWJK6KizM+q7BF1/1CmuLqZqX3CeYF7s7P9fbA8Q==} + engines: {node: '>= 10'} + + '@tailwindcss/vite@4.1.10': + resolution: {integrity: sha512-QWnD5HDY2IADv+vYR82lOhqOlS1jSCUUAmfem52cXAhRTKxpDh3ARX8TTXJTCCO7Rv7cD2Nlekabv02bwP3a2A==} + peerDependencies: + vite: ^5.2.0 || ^6 + '@tootallnate/once@2.0.0': resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} engines: {node: '>= 10'} @@ -1069,8 +1146,8 @@ packages: '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} - '@types/babel__generator@7.6.8': - resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} '@types/babel__template@7.4.4': resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} @@ -1078,93 +1155,93 @@ packages: '@types/babel__traverse@7.20.7': resolution: {integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==} - '@types/bun@1.2.8': - resolution: {integrity: sha512-t8L1RvJVUghW5V+M/fL3Thbxcs0HwNsXsnTEBEfEVqGteiJToOlZ/fyOEaR1kZsNqnu+3XA4RI/qmnX4w6+S+w==} + '@types/bun@1.2.16': + resolution: {integrity: sha512-1aCZJ/6nSiViw339RsaNhkNoEloLaPzZhxMOYEa7OzRzO41IGg5n/7I43/ZIAW/c+Q6cT12Vf7fOZOoVIzb5BQ==} - '@types/estree@1.0.6': - resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} - - '@types/estree@1.0.7': - resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/node@22.13.14': - resolution: {integrity: sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w==} + '@types/node@24.0.3': + resolution: {integrity: sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg==} - '@types/prop-types@15.7.14': - resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==} - - '@types/react-dom@18.3.5': - resolution: {integrity: sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==} + '@types/react-dom@19.1.6': + resolution: {integrity: sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==} peerDependencies: - '@types/react': ^18.0.0 + '@types/react': ^19.0.0 - '@types/react@18.3.20': - resolution: {integrity: sha512-IPaCZN7PShZK/3t6Q87pfTkRm6oLTd4vztyoj+cbHUF1g3FfVb2tFIL79uCRKEfv16AhqDMBywP2VW3KIZUvcg==} + '@types/react@19.1.8': + resolution: {integrity: sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==} '@types/vexflow@1.2.42': resolution: {integrity: sha512-bj3If1sm1FYJN0tJMV2BJNrwoeQ6xfrTt+rbmFvUytyxUhklLlW79MR4uwQ+upzrNJLVy012TnC7oZqSpEglSw==} - '@types/ws@8.18.0': - resolution: {integrity: sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw==} - - '@typescript-eslint/eslint-plugin@8.28.0': - resolution: {integrity: sha512-lvFK3TCGAHsItNdWZ/1FkvpzCxTHUVuFrdnOGLMa0GGCFIbCgQWVk3CzCGdA7kM3qGVc+dfW9tr0Z/sHnGDFyg==} + '@typescript-eslint/eslint-plugin@8.34.1': + resolution: {integrity: sha512-STXcN6ebF6li4PxwNeFnqF8/2BNDvBupf2OPx2yWNzr6mKNGF7q49VM00Pz5FaomJyqvbXpY6PhO+T9w139YEQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + '@typescript-eslint/parser': ^8.34.1 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/parser@8.28.0': - resolution: {integrity: sha512-LPcw1yHD3ToaDEoljFEfQ9j2xShY367h7FZ1sq5NJT9I3yj4LHer1Xd1yRSOdYy9BpsrxU7R+eoDokChYM53lQ==} + '@typescript-eslint/parser@8.34.1': + resolution: {integrity: sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/scope-manager@8.28.0': - resolution: {integrity: sha512-u2oITX3BJwzWCapoZ/pXw6BCOl8rJP4Ij/3wPoGvY8XwvXflOzd1kLrDUUUAIEdJSFh+ASwdTHqtan9xSg8buw==} + '@typescript-eslint/project-service@8.34.1': + resolution: {integrity: sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/scope-manager@8.34.1': + resolution: {integrity: sha512-beu6o6QY4hJAgL1E8RaXNC071G4Kso2MGmJskCFQhRhg8VOH/FDbC8soP8NHN7e/Hdphwp8G8cE6OBzC8o41ZA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/type-utils@8.28.0': - resolution: {integrity: sha512-oRoXu2v0Rsy/VoOGhtWrOKDiIehvI+YNrDk5Oqj40Mwm0Yt01FC/Q7nFqg088d3yAsR1ZcZFVfPCTTFCe/KPwg==} + '@typescript-eslint/tsconfig-utils@8.34.1': + resolution: {integrity: sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/type-utils@8.34.1': + resolution: {integrity: sha512-Tv7tCCr6e5m8hP4+xFugcrwTOucB8lshffJ6zf1mF1TbU67R+ntCc6DzLNKM+s/uzDyv8gLq7tufaAhIBYeV8g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/types@8.28.0': - resolution: {integrity: sha512-bn4WS1bkKEjx7HqiwG2JNB3YJdC1q6Ue7GyGlwPHyt0TnVq6TtD/hiOdTZt71sq0s7UzqBFXD8t8o2e63tXgwA==} + '@typescript-eslint/types@8.34.1': + resolution: {integrity: sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.28.0': - resolution: {integrity: sha512-H74nHEeBGeklctAVUvmDkxB1mk+PAZ9FiOMPFncdqeRBXxk1lWSYraHw8V12b7aa6Sg9HOBNbGdSHobBPuQSuA==} + '@typescript-eslint/typescript-estree@8.34.1': + resolution: {integrity: sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/utils@8.28.0': - resolution: {integrity: sha512-OELa9hbTYciYITqgurT1u/SzpQVtDLmQMFzy/N8pQE+tefOyCWT79jHsav294aTqV1q1u+VzqDGbuujvRYaeSQ==} + '@typescript-eslint/utils@8.34.1': + resolution: {integrity: sha512-mqOwUdZ3KjtGk7xJJnLbHxTuWVn3GO2WZZuM+Slhkun4+qthLdXx32C8xIXbO1kfCECb3jIs3eoxK3eryk7aoQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/visitor-keys@8.28.0': - resolution: {integrity: sha512-hbn8SZ8w4u2pRwgQ1GlUrPKE+t2XvcCW5tTRF7j6SMYIuYG37XuzIW44JCZPa36evi0Oy2SnM664BlIaAuQcvg==} + '@typescript-eslint/visitor-keys@8.34.1': + resolution: {integrity: sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@unhead/schema@1.11.20': - resolution: {integrity: sha512-0zWykKAaJdm+/Y7yi/Yds20PrUK7XabLe9c3IRcjnwYmSWY6z0Cr19VIs3ozCj8P+GhR+/TI2mwtGlueCEYouA==} - - '@vitejs/plugin-react@4.3.4': - resolution: {integrity: sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==} + '@vitejs/plugin-react@4.5.2': + resolution: {integrity: sha512-QNVT3/Lxx99nMQWJWF7K4N6apUEuT0KlZA3mx/mVaoGj3smm/8rc8ezz15J1pcbcjDK0V15rpHetVfya08r76Q==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: - vite: ^4.2.0 || ^5.0.0 || ^6.0.0 + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0 abbrev@1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} @@ -1174,8 +1251,8 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn@8.14.1: - resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} engines: {node: '>=0.4.0'} hasBin: true @@ -1198,25 +1275,10 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - ansi-regex@6.1.0: - resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} - engines: {node: '>=12'} - ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} - ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} - engines: {node: '>=12'} - - any-promise@1.3.0: - resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - aproba@2.0.0: resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} @@ -1225,22 +1287,15 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} deprecated: This package is no longer supported. - arg@5.0.2: - resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} - argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - aria-hidden@1.2.4: - resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==} + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} engines: {node: '>=10'} - autoprefixer@10.4.21: - resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} - engines: {node: ^10 || ^12 || >=14} - hasBin: true - peerDependencies: - postcss: ^8.1.0 + babel-plugin-react-compiler@19.1.0-rc.2: + resolution: {integrity: sha512-kSNA//p5fMO6ypG8EkEVPIqAjwIXm5tMjfD1XRPL/sRjYSbJ6UsvORfaeolNWnZ9n310aM0xJP7peW26BuCVzA==} balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -1248,10 +1303,6 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} - bindings@1.5.0: resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} @@ -1261,26 +1312,26 @@ packages: bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} - brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.24.4: - resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} + browserslist@4.25.0: + resolution: {integrity: sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - bun-types@1.2.7: - resolution: {integrity: sha512-P4hHhk7kjF99acXqKvltyuMQ2kf/rzIw3ylEDpCxDS9Xa0X0Yp/gJu/vDCucmWpiur5qJ0lwB2bWzOXa2GlHqA==} + bun-types@1.2.16: + resolution: {integrity: sha512-ciXLrHV4PXax9vHvUrkvun9VPVGOVwbbbBF/Ev1cXz12lyEZMoJpIJABOfPcN9gDJRaiKF9MVbSygLg4NXu3/A==} cacache@16.1.3: resolution: {integrity: sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==} @@ -1290,21 +1341,17 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - camelcase-css@2.0.1: - resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} - engines: {node: '>= 6'} + caniuse-lite@1.0.30001723: + resolution: {integrity: sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==} - caniuse-lite@1.0.30001707: - resolution: {integrity: sha512-3qtRjw/HQSMlDWf+X79N206fepf4SOOU6SQLMaq/0KkZLmSjPxAkBOQQ+FxbHKfHmYLZFfdWsO3KA90ceHPSnw==} + cbor2@2.0.1: + resolution: {integrity: sha512-9bE8+tueGxONyxpttNKkAKKcGVtAPeoSJ64AjVTTjEuBOuRaeeP76EN9BbmQqkz1ZeTP0QPvksNBKwvEutIUzQ==} + engines: {node: '>=20'} chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} - chownr@1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} @@ -1312,6 +1359,10 @@ packages: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} @@ -1319,10 +1370,6 @@ packages: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} - clone@2.1.2: - resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} - engines: {node: '>=0.8'} - clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -1338,10 +1385,6 @@ packages: resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} hasBin: true - commander@4.1.1: - resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} - engines: {node: '>= 6'} - concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -1362,16 +1405,11 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - cssesc@3.0.0: - resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} - engines: {node: '>=4'} - hasBin: true - csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - debug@4.4.0: - resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} engines: {node: '>=6.0'} peerDependencies: supports-color: '*' @@ -1393,51 +1431,31 @@ packages: delegates@1.0.0: resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} - detect-libc@2.0.3: - resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} + detect-libc@2.0.4: + resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} engines: {node: '>=8'} detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} - didyoumean@1.2.2: - resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + effect@3.16.8: + resolution: {integrity: sha512-E4U0MZFBun99myxOogy9ZZ1c3IYR47L/A5GqCP9Lp+6ORag0YLmGHOrYxQ3agN1FOMTrElgtJmciicwnHdE+Ug==} - dlv@1.1.3: - resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} - - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - - effect@3.14.2: - resolution: {integrity: sha512-AqLlvhkcWqSgfPnfGO/JdwvEqhtzFLb4qwe43YLwrvnN5ev2dqB4ve2Bv6oq64iplRMYO9lmYRibN0Ig6fK/nQ==} - - electron-to-chromium@1.5.128: - resolution: {integrity: sha512-bo1A4HH/NS522Ws0QNFIzyPcyUUNV/yyy70Ho1xqfGYzPUme2F/xr4tlEOuM6/A538U1vDA7a4XfCd1CKRegKQ==} - - elysia@1.2.25: - resolution: {integrity: sha512-WsdQpORJvb4uszzeqYT0lg97knw1iBW1NTzJ1Jm57tiHg+DfAotlWXYbjmvQ039ssV0fYELDHinLLoUazZkEHg==} - peerDependencies: - '@sinclair/typebox': '>= 0.34.0' - openapi-types: '>= 12.0.0' - typescript: '>= 5.0.0' - peerDependenciesMeta: - openapi-types: - optional: true - typescript: - optional: true + electron-to-chromium@1.5.170: + resolution: {integrity: sha512-GP+M7aeluQo9uAyiTCxgIj/j+PrWhMlY7LFVj8prlsPljd0Fdg9AprlfUi+OCSFWy9Y5/2D/Jrj9HS8Z4rpKWA==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - encoding@0.1.13: resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} - end-of-stream@1.4.4: - resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + enhanced-resolve@5.18.1: + resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==} + engines: {node: '>=10.13.0'} env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} @@ -1446,8 +1464,8 @@ packages: err-code@2.0.3: resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} - esbuild@0.25.1: - resolution: {integrity: sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==} + esbuild@0.25.5: + resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==} engines: {node: '>=18'} hasBin: true @@ -1459,26 +1477,26 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - eslint-plugin-react-hooks@5.2.0: - resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} - engines: {node: '>=10'} + eslint-plugin-react-hooks@6.0.0-rc1: + resolution: {integrity: sha512-I4ntWyjqgGemGtOU85FUdVo00h0i0Y5xvQ7a8EVxyzjOZsxXaxvkKBcYoXbP97QDvDjMzY/nGIvfdB/WRLTGxQ==} + engines: {node: '>=18'} peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 - eslint-scope@8.3.0: - resolution: {integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==} + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint-visitor-keys@4.2.0: - resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.23.0: - resolution: {integrity: sha512-jV7AbNoFPAY1EkFYpLq5bslU9NLNO8xnEeQXwErNibVryjk67wHVmddTBilc5srIttJDBrB0eMHKZBFbSIABCw==} + eslint@9.29.0: + resolution: {integrity: sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -1487,8 +1505,8 @@ packages: jiti: optional: true - espree@10.3.0: - resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} esquery@1.6.0: @@ -1534,6 +1552,14 @@ packages: fastq@1.19.1: resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + fdir@6.4.6: + resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -1556,13 +1582,6 @@ packages: flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} - foreground-child@3.3.1: - resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} - engines: {node: '>=14'} - - fraction.js@4.3.7: - resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} - fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} @@ -1578,9 +1597,6 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - gauge@4.0.4: resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -1609,10 +1625,6 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - glob@10.4.5: - resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} - hasBin: true - glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported @@ -1646,15 +1658,14 @@ packages: has-unicode@2.0.1: resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} - hookable@5.5.3: - resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} - http-cache-semantics@4.1.1: - resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} + http-cache-semantics@4.2.0: + resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} http-proxy-agent@5.0.0: resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} @@ -1678,6 +1689,10 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + immediate@3.0.6: resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} @@ -1710,14 +1725,6 @@ packages: resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} engines: {node: '>= 12'} - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - - is-core-module@2.16.1: - resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} - engines: {node: '>= 0.4'} - is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -1746,11 +1753,8 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - - jiti@1.21.7: - resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} + jiti@2.4.2: + resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} hasBin: true js-tokens@4.0.0: @@ -1788,15 +1792,15 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - kysely-bun-sqlite@0.3.2: - resolution: {integrity: sha512-YucMcOGGxNCmlAnkvNfTKZX6Bk1sYsuVVXlQN/5ZUgerlq/J9/EuR3aMOOZ25ASLM7oMglSxfeQxkiw0+hrEOQ==} - engines: {bun: '>=1'} + kysely-bun-sqlite@0.4.0: + resolution: {integrity: sha512-2EkQE5sT4ewiw7IWfJsAkpxJ/QPVKXKO5sRYI/xjjJIJlECuOdtG+ssYM0twZJySrdrmuildNPFYVreyu1EdZg==} + engines: {bun: '>=1.1.31'} peerDependencies: - kysely: ^0.27.2 + kysely: ^0.28.2 - kysely@0.27.6: - resolution: {integrity: sha512-FIyV/64EkKhJmjgC0g2hygpBv5RNWVPyNCqSAD7eTCv6eFWNIi4PN1UvdSJGicN/o35bnevgis4Y0UDC0qi8jQ==} - engines: {node: '>=14.0.0'} + kysely@0.28.2: + resolution: {integrity: sha512-4YAVLoF0Sf0UTqlhgQMFU9iQECdah7n+13ANkiuVfRvlK+uI0Etbgd7bVP36dKlG+NXWbhGua8vnGt+sdhvT7A==} + engines: {node: '>=18.0.0'} levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} @@ -1805,12 +1809,69 @@ packages: lie@3.3.0: resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} - lilconfig@3.1.3: - resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} - engines: {node: '>=14'} + lightningcss-darwin-arm64@1.30.1: + resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] - lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + lightningcss-darwin-x64@1.30.1: + resolution: {integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.30.1: + resolution: {integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.30.1: + resolution: {integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.30.1: + resolution: {integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.30.1: + resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.30.1: + resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.30.1: + resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.30.1: + resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.30.1: + resolution: {integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.30.1: + resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==} + engines: {node: '>= 12.0.0'} locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} @@ -1823,13 +1884,6 @@ packages: resolution: {integrity: sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==} engines: {node: '>= 0.6.0'} - loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} - hasBin: true - - lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -1837,18 +1891,18 @@ packages: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} engines: {node: '>=12'} - lucide-react@0.485.0: - resolution: {integrity: sha512-NvyQJ0LKyyCxL23nPKESlr/jmz8r7fJO1bkuptSNYSy0s8VVj4ojhX0YAgmE1e0ewfxUZjIlZpvH+otfTnla8Q==} + lucide-react@0.518.0: + resolution: {integrity: sha512-kFg34uQqnVl/7HwAiigxPSpj//43VIVHQbMygQPtS1yT4btMXHCWUipHcgcXHD2pm1Z2nUBA/M+Vnh/YmWXQUw==} peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + make-fetch-happen@10.2.1: resolution: {integrity: sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - memoirist@0.3.0: - resolution: {integrity: sha512-wR+4chMgVPq+T6OOsk40u9Wlpw1Pjx66NMNiYxCQQ4EUJ7jDs3D9kTCeKdBOkvAiqXlHLVJlvYL01PvIJ1MPNg==} - merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -1911,6 +1965,10 @@ packages: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} engines: {node: '>= 8'} + minizlib@3.0.2: + resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==} + engines: {node: '>= 18'} + mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} @@ -1919,12 +1977,14 @@ packages: engines: {node: '>=10'} hasBin: true + mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} + hasBin: true + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - mz@2.7.0: - resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - nan@2.22.2: resolution: {integrity: sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==} @@ -1933,11 +1993,6 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - nanoid@5.1.5: - resolution: {integrity: sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==} - engines: {node: ^18 || >=20} - hasBin: true - napi-build-utils@2.0.0: resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} @@ -1948,14 +2003,10 @@ packages: resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} engines: {node: '>= 0.6'} - node-abi@3.74.0: - resolution: {integrity: sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==} + node-abi@3.75.0: + resolution: {integrity: sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==} engines: {node: '>=10'} - node-cache@5.1.2: - resolution: {integrity: sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==} - engines: {node: '>= 8.0.0'} - node-gyp@9.4.1: resolution: {integrity: sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==} engines: {node: ^12.13 || ^14.13 || >=16} @@ -1969,33 +2020,14 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} hasBin: true - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - - normalize-range@0.1.2: - resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} - engines: {node: '>=0.10.0'} - npmlog@6.0.2: resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} deprecated: This package is no longer supported. - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - - object-hash@3.0.0: - resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} - engines: {node: '>= 6'} - once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - openapi-types@12.1.3: - resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} - opensheetmusicdisplay@1.9.0: resolution: {integrity: sha512-gZqPSMP13fGMw8n/z83K4HjM5eaZzvGvctNl9tVtf7j7toGZKpyDEyhJ+sSHdO+QNg8DvrnYG7ZIKZRzdcWipQ==} @@ -2015,9 +2047,6 @@ packages: resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} engines: {node: '>=10'} - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - pako@1.0.11: resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} @@ -2037,16 +2066,6 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} - path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} - - pathe@1.1.2: - resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} - picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -2058,53 +2077,8 @@ packages: resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} engines: {node: '>=12'} - pify@2.3.0: - resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} - engines: {node: '>=0.10.0'} - - pirates@4.0.7: - resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} - engines: {node: '>= 6'} - - postcss-import@15.1.0: - resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} - engines: {node: '>=14.0.0'} - peerDependencies: - postcss: ^8.0.0 - - postcss-js@4.0.1: - resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} - engines: {node: ^12 || ^14 || >= 16} - peerDependencies: - postcss: ^8.4.21 - - postcss-load-config@4.0.2: - resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} - engines: {node: '>= 14'} - peerDependencies: - postcss: '>=8.0.9' - ts-node: '>=9.0.0' - peerDependenciesMeta: - postcss: - optional: true - ts-node: - optional: true - - postcss-nested@6.2.0: - resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} - engines: {node: '>=12.0'} - peerDependencies: - postcss: ^8.2.14 - - postcss-selector-parser@6.1.2: - resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} - engines: {node: '>=4'} - - postcss-value-parser@4.2.0: - resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - - postcss@8.5.3: - resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} prebuild-install@7.1.3: @@ -2131,8 +2105,8 @@ packages: resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} engines: {node: '>=10'} - pump@3.0.2: - resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} @@ -2148,13 +2122,13 @@ packages: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true - react-dom@18.3.1: - resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + react-dom@19.1.0: + resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} peerDependencies: - react: ^18.3.1 + react: ^19.1.0 - react-refresh@0.14.2: - resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} react-remove-scroll-bar@2.3.8: @@ -2167,8 +2141,8 @@ packages: '@types/react': optional: true - react-remove-scroll@2.6.3: - resolution: {integrity: sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ==} + react-remove-scroll@2.7.1: + resolution: {integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==} engines: {node: '>=10'} peerDependencies: '@types/react': '*' @@ -2177,18 +2151,22 @@ packages: '@types/react': optional: true - react-router-dom@6.30.0: - resolution: {integrity: sha512-x30B78HV5tFk8ex0ITwzC9TTZMua4jGyA9IUlH1JLQYQTFyxr/ZxwOJq7evg1JX1qGVUcvhsmQSKdPncQrjTgA==} - engines: {node: '>=14.0.0'} + react-router-dom@7.6.2: + resolution: {integrity: sha512-Q8zb6VlTbdYKK5JJBLQEN06oTUa/RAbG/oQS1auK1I0TbJOXktqm+QENEVJU6QvWynlXPRBXI3fiOQcSEA78rA==} + engines: {node: '>=20.0.0'} peerDependencies: - react: '>=16.8' - react-dom: '>=16.8' + react: '>=18' + react-dom: '>=18' - react-router@6.30.0: - resolution: {integrity: sha512-D3X8FyH9nBcTSHGdEKurK7r8OYE1kKFn3d/CF+CoxbSHkxU7o37+Uh7eAHRXr6k2tSExXYO++07PeXJtA/dEhQ==} - engines: {node: '>=14.0.0'} + react-router@7.6.2: + resolution: {integrity: sha512-U7Nv3y+bMimgWjhlT5CRdzHPu2/KVmqPwKUCChW8en5P3znxUqwlYFlbmyj8Rgp1SF6zs5X4+77kBVknkg6a0w==} + engines: {node: '>=20.0.0'} peerDependencies: - react: '>=16.8' + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true react-style-singleton@2.2.3: resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} @@ -2200,13 +2178,10 @@ packages: '@types/react': optional: true - react@18.3.1: - resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + react@19.1.0: + resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} engines: {node: '>=0.10.0'} - read-cache@1.0.0: - resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} - readable-stream@1.0.34: resolution: {integrity: sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==} @@ -2217,19 +2192,10 @@ packages: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} - resolve@1.22.10: - resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} - engines: {node: '>= 0.4'} - hasBin: true - retry@0.12.0: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} @@ -2243,8 +2209,8 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true - rollup@4.37.0: - resolution: {integrity: sha512-iAtQy/L4QFU+rTJ1YUjXqJOJzuwEghqWzCEYD2FEghT7Gsy1VdABntrO4CLopA5IkflTyqNiLNwPcOJ3S7UKLg==} + rollup@4.44.0: + resolution: {integrity: sha512-qHcdEzLCiktQIfwBq420pn2dP+30uzqYxv9ETm91wdt2R9AFcWfjNAmje4NWlnCIQ5RMTzVf0ZyisOKqHR6RwA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -2260,21 +2226,24 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - scheduler@0.23.2: - resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + scheduler@0.26.0: + resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.7.1: - resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} engines: {node: '>=10'} hasBin: true set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + set-cookie-parser@2.7.1: + resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} + setimmediate@1.0.5: resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} @@ -2289,10 +2258,6 @@ packages: signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - simple-concat@1.0.1: resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} @@ -2307,8 +2272,8 @@ packages: resolution: {integrity: sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==} engines: {node: '>= 10'} - socks@2.8.4: - resolution: {integrity: sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==} + socks@2.8.5: + resolution: {integrity: sha512-iF+tNDQla22geJdTyJB1wM/qrX9DMRwWrciEPwWLPRWAUEM8sQiyxgckLxWT1f7+9VabJS0jTGGr4QgBuvi6Ww==} engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} source-map-js@1.2.1: @@ -2326,10 +2291,6 @@ packages: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - string_decoder@0.10.31: resolution: {integrity: sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==} @@ -2343,10 +2304,6 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} - engines: {node: '>=12'} - strip-json-comments@2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} @@ -2355,34 +2312,22 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - sucrase@3.35.0: - resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} - engines: {node: '>=16 || 14 >=14.17'} - hasBin: true - supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} - supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} + tailwind-merge@3.3.1: + resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==} - tailwind-merge@2.6.0: - resolution: {integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==} + tailwindcss@4.1.10: + resolution: {integrity: sha512-P3nr6WkvKV/ONsTzj6Gb57sWPMX29EPNPopo7+FcpkQaNsrNpZ1pv8QmrYI2RqEKD7mlGqLnGovlcYnBK0IqUA==} - tailwindcss-animate@1.0.7: - resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} - peerDependencies: - tailwindcss: '>=3.0.0 || insiders' + tapable@2.2.2: + resolution: {integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==} + engines: {node: '>=6'} - tailwindcss@3.4.17: - resolution: {integrity: sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==} - engines: {node: '>=14.0.0'} - hasBin: true - - tar-fs@2.1.2: - resolution: {integrity: sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==} + tar-fs@2.1.3: + resolution: {integrity: sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==} tar-stream@2.2.0: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} @@ -2392,16 +2337,17 @@ packages: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} - thenify-all@1.6.0: - resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} - engines: {node: '>=0.8'} - - thenify@3.3.1: - resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + tar@7.4.3: + resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} + engines: {node: '>=18'} through2@0.6.5: resolution: {integrity: sha512-RkK/CCESdTKQZHdmKICijdKKsCRVHs5KsLZ6pACAmF/1GPUQhonHSXWNERctxEp7RmvjdNbZTL5z9V7nSCXKcg==} + tinyglobby@0.2.14: + resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} + engines: {node: '>=12.0.0'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -2412,40 +2358,36 @@ packages: peerDependencies: typescript: '>=4.8.4' - ts-interface-checker@0.1.13: - resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + tw-animate-css@1.3.4: + resolution: {integrity: sha512-dd1Ht6/YQHcNbq0znIT6dG8uhO7Ce+VIIhZUhjsryXsMPJQz3bZg7Q2eNzLwipb25bRZslGb2myio5mScd1TFg==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - type-fest@4.38.0: - resolution: {integrity: sha512-2dBz5D5ycHIoliLYLi0Q2V7KRaDlH0uWIvmk7TYlAg5slqwiPv1ezJdZm1QEM0xgk29oYWMCbIG7E6gHpvChlg==} - engines: {node: '>=16'} - typescript-collections@1.3.3: resolution: {integrity: sha512-7sI4e/bZijOzyURng88oOFZCISQPTHozfE2sUu5AviFYk5QV7fYGb6YiDl+vKjF/pICA354JImBImL9XJWUvdQ==} - typescript-eslint@8.28.0: - resolution: {integrity: sha512-jfZtxJoHm59bvoCMYCe2BM0/baMswRhMmYhy+w6VfcyHrjxZ0OJe0tGasydCpIpA+A/WIJhTyZfb3EtwNC/kHQ==} + typescript-eslint@8.34.1: + resolution: {integrity: sha512-XjS+b6Vg9oT1BaIUfkW3M3LvqZE++rbzAMEHuccCfO/YkP43ha6w3jTEMilQxMF92nVOYCcdjv1ZUhAa1D/0ow==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' - typescript@5.8.2: - resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} engines: {node: '>=14.17'} hasBin: true - undici-types@6.20.0: - resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + undici-types@7.8.0: + resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==} unique-filename@2.0.1: resolution: {integrity: sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==} @@ -2490,8 +2432,8 @@ packages: vexflow@1.2.93: resolution: {integrity: sha512-LwHQDCc257Lwju35BhyZuPYcVWu0hIUqEdM7j9+B+bq91bSelssnAG5JR8odTUtgGuwwvGwLhXw37wtmHNCS6Q==} - vite@6.2.3: - resolution: {integrity: sha512-IzwM54g4y9JA/xAeBPNaDXiBF8Jsgl3VBQ2YQ/wOY6fyW3xMdSoltIV3Bo59DErdqdE6RxUfv8W69DvUorE4Eg==} + vite@6.3.5: + resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: @@ -2542,14 +2484,6 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -2563,262 +2497,301 @@ packages: yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - yaml@2.7.0: - resolution: {integrity: sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==} - engines: {node: '>= 14'} - hasBin: true + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - zhead@2.2.4: - resolution: {integrity: sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag==} + zod-validation-error@3.5.2: + resolution: {integrity: sha512-mdi7YOLtram5dzJ5aDtm1AG9+mxRma1iaMrZdYIpFO7epdKBUwLHIxTF8CPDeCQ828zAXYtizrKlEJAtzgfgrw==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.25.0 - zod@3.24.2: - resolution: {integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==} + zod@3.25.67: + resolution: {integrity: sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==} snapshots: - '@alloc/quick-lru@5.2.0': {} - '@ampproject/remapping@2.3.0': dependencies: '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 - '@babel/code-frame@7.26.2': + '@babel/code-frame@7.27.1': dependencies: - '@babel/helper-validator-identifier': 7.25.9 + '@babel/helper-validator-identifier': 7.27.1 js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.26.8': {} + '@babel/compat-data@7.27.5': {} - '@babel/core@7.26.10': + '@babel/core@7.27.4': dependencies: '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.26.2 - '@babel/generator': 7.27.0 - '@babel/helper-compilation-targets': 7.27.0 - '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) - '@babel/helpers': 7.27.0 - '@babel/parser': 7.27.0 - '@babel/template': 7.27.0 - '@babel/traverse': 7.27.0 - '@babel/types': 7.27.0 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.27.5 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.27.3(@babel/core@7.27.4) + '@babel/helpers': 7.27.6 + '@babel/parser': 7.27.5 + '@babel/template': 7.27.2 + '@babel/traverse': 7.27.4 + '@babel/types': 7.27.6 convert-source-map: 2.0.0 - debug: 4.4.0 + debug: 4.4.1 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/generator@7.27.0': + '@babel/generator@7.27.5': dependencies: - '@babel/parser': 7.27.0 - '@babel/types': 7.27.0 + '@babel/parser': 7.27.5 + '@babel/types': 7.27.6 '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 jsesc: 3.1.0 - '@babel/helper-compilation-targets@7.27.0': + '@babel/helper-annotate-as-pure@7.27.3': dependencies: - '@babel/compat-data': 7.26.8 - '@babel/helper-validator-option': 7.25.9 - browserslist: 4.24.4 + '@babel/types': 7.27.6 + + '@babel/helper-compilation-targets@7.27.2': + dependencies: + '@babel/compat-data': 7.27.5 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.25.0 lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-module-imports@7.25.9': + '@babel/helper-create-class-features-plugin@7.27.1(@babel/core@7.27.4)': dependencies: - '@babel/traverse': 7.27.0 - '@babel/types': 7.27.0 + '@babel/core': 7.27.4 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.27.1 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.27.1(@babel/core@7.27.4) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.27.4 + semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.10)': + '@babel/helper-member-expression-to-functions@7.27.1': dependencies: - '@babel/core': 7.26.10 - '@babel/helper-module-imports': 7.25.9 - '@babel/helper-validator-identifier': 7.25.9 - '@babel/traverse': 7.27.0 + '@babel/traverse': 7.27.4 + '@babel/types': 7.27.6 transitivePeerDependencies: - supports-color - '@babel/helper-plugin-utils@7.26.5': {} - - '@babel/helper-string-parser@7.25.9': {} - - '@babel/helper-validator-identifier@7.25.9': {} - - '@babel/helper-validator-option@7.25.9': {} - - '@babel/helpers@7.27.0': + '@babel/helper-module-imports@7.27.1': dependencies: - '@babel/template': 7.27.0 - '@babel/types': 7.27.0 + '@babel/traverse': 7.27.4 + '@babel/types': 7.27.6 + transitivePeerDependencies: + - supports-color - '@babel/parser@7.27.0': + '@babel/helper-module-transforms@7.27.3(@babel/core@7.27.4)': dependencies: - '@babel/types': 7.27.0 + '@babel/core': 7.27.4 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/traverse': 7.27.4 + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.10)': + '@babel/helper-optimise-call-expression@7.27.1': dependencies: - '@babel/core': 7.26.10 - '@babel/helper-plugin-utils': 7.26.5 + '@babel/types': 7.27.6 - '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.26.10)': - dependencies: - '@babel/core': 7.26.10 - '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-plugin-utils@7.27.1': {} - '@babel/template@7.27.0': + '@babel/helper-replace-supers@7.27.1(@babel/core@7.27.4)': dependencies: - '@babel/code-frame': 7.26.2 - '@babel/parser': 7.27.0 - '@babel/types': 7.27.0 + '@babel/core': 7.27.4 + '@babel/helper-member-expression-to-functions': 7.27.1 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/traverse': 7.27.4 + transitivePeerDependencies: + - supports-color - '@babel/traverse@7.27.0': + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': dependencies: - '@babel/code-frame': 7.26.2 - '@babel/generator': 7.27.0 - '@babel/parser': 7.27.0 - '@babel/template': 7.27.0 - '@babel/types': 7.27.0 - debug: 4.4.0 + '@babel/traverse': 7.27.4 + '@babel/types': 7.27.6 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.27.1': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.27.6': + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.27.6 + + '@babel/parser@7.27.5': + dependencies: + '@babel/types': 7.27.6 + + '@babel/plugin-transform-private-methods@7.27.1(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.27.4) + '@babel/helper-plugin-utils': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/template@7.27.2': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.27.5 + '@babel/types': 7.27.6 + + '@babel/traverse@7.27.4': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.27.5 + '@babel/parser': 7.27.5 + '@babel/template': 7.27.2 + '@babel/types': 7.27.6 + debug: 4.4.1 globals: 11.12.0 transitivePeerDependencies: - supports-color - '@babel/types@7.27.0': + '@babel/types@7.27.6': dependencies: - '@babel/helper-string-parser': 7.25.9 - '@babel/helper-validator-identifier': 7.25.9 + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 - '@elysiajs/cors@1.2.0(elysia@1.2.25(@sinclair/typebox@0.34.31)(openapi-types@12.1.3)(typescript@5.8.2))': + '@cto.af/wtf8@0.0.2': {} + + '@effect/language-service@0.21.4': {} + + '@esbuild/aix-ppc64@0.25.5': + optional: true + + '@esbuild/android-arm64@0.25.5': + optional: true + + '@esbuild/android-arm@0.25.5': + optional: true + + '@esbuild/android-x64@0.25.5': + optional: true + + '@esbuild/darwin-arm64@0.25.5': + optional: true + + '@esbuild/darwin-x64@0.25.5': + optional: true + + '@esbuild/freebsd-arm64@0.25.5': + optional: true + + '@esbuild/freebsd-x64@0.25.5': + optional: true + + '@esbuild/linux-arm64@0.25.5': + optional: true + + '@esbuild/linux-arm@0.25.5': + optional: true + + '@esbuild/linux-ia32@0.25.5': + optional: true + + '@esbuild/linux-loong64@0.25.5': + optional: true + + '@esbuild/linux-mips64el@0.25.5': + optional: true + + '@esbuild/linux-ppc64@0.25.5': + optional: true + + '@esbuild/linux-riscv64@0.25.5': + optional: true + + '@esbuild/linux-s390x@0.25.5': + optional: true + + '@esbuild/linux-x64@0.25.5': + optional: true + + '@esbuild/netbsd-arm64@0.25.5': + optional: true + + '@esbuild/netbsd-x64@0.25.5': + optional: true + + '@esbuild/openbsd-arm64@0.25.5': + optional: true + + '@esbuild/openbsd-x64@0.25.5': + optional: true + + '@esbuild/sunos-x64@0.25.5': + optional: true + + '@esbuild/win32-arm64@0.25.5': + optional: true + + '@esbuild/win32-ia32@0.25.5': + optional: true + + '@esbuild/win32-x64@0.25.5': + optional: true + + '@eslint-community/eslint-utils@4.7.0(eslint@9.29.0(jiti@2.4.2))': dependencies: - elysia: 1.2.25(@sinclair/typebox@0.34.31)(openapi-types@12.1.3)(typescript@5.8.2) - - '@elysiajs/eden@1.2.0(elysia@1.2.25(@sinclair/typebox@0.34.31)(openapi-types@12.1.3)(typescript@5.8.2))': - dependencies: - elysia: 1.2.25(@sinclair/typebox@0.34.31)(openapi-types@12.1.3)(typescript@5.8.2) - - '@elysiajs/static@1.2.0(elysia@1.2.25(@sinclair/typebox@0.34.31)(openapi-types@12.1.3)(typescript@5.8.2))': - dependencies: - elysia: 1.2.25(@sinclair/typebox@0.34.31)(openapi-types@12.1.3)(typescript@5.8.2) - node-cache: 5.1.2 - - '@elysiajs/swagger@1.2.2(elysia@1.2.25(@sinclair/typebox@0.34.31)(openapi-types@12.1.3)(typescript@5.8.2))': - dependencies: - '@scalar/themes': 0.9.82 - '@scalar/types': 0.0.12 - elysia: 1.2.25(@sinclair/typebox@0.34.31)(openapi-types@12.1.3)(typescript@5.8.2) - openapi-types: 12.1.3 - pathe: 1.1.2 - - '@esbuild/aix-ppc64@0.25.1': - optional: true - - '@esbuild/android-arm64@0.25.1': - optional: true - - '@esbuild/android-arm@0.25.1': - optional: true - - '@esbuild/android-x64@0.25.1': - optional: true - - '@esbuild/darwin-arm64@0.25.1': - optional: true - - '@esbuild/darwin-x64@0.25.1': - optional: true - - '@esbuild/freebsd-arm64@0.25.1': - optional: true - - '@esbuild/freebsd-x64@0.25.1': - optional: true - - '@esbuild/linux-arm64@0.25.1': - optional: true - - '@esbuild/linux-arm@0.25.1': - optional: true - - '@esbuild/linux-ia32@0.25.1': - optional: true - - '@esbuild/linux-loong64@0.25.1': - optional: true - - '@esbuild/linux-mips64el@0.25.1': - optional: true - - '@esbuild/linux-ppc64@0.25.1': - optional: true - - '@esbuild/linux-riscv64@0.25.1': - optional: true - - '@esbuild/linux-s390x@0.25.1': - optional: true - - '@esbuild/linux-x64@0.25.1': - optional: true - - '@esbuild/netbsd-arm64@0.25.1': - optional: true - - '@esbuild/netbsd-x64@0.25.1': - optional: true - - '@esbuild/openbsd-arm64@0.25.1': - optional: true - - '@esbuild/openbsd-x64@0.25.1': - optional: true - - '@esbuild/sunos-x64@0.25.1': - optional: true - - '@esbuild/win32-arm64@0.25.1': - optional: true - - '@esbuild/win32-ia32@0.25.1': - optional: true - - '@esbuild/win32-x64@0.25.1': - optional: true - - '@eslint-community/eslint-utils@4.5.1(eslint@9.23.0(jiti@1.21.7))': - dependencies: - eslint: 9.23.0(jiti@1.21.7) + eslint: 9.29.0(jiti@2.4.2) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} - '@eslint/config-array@0.19.2': + '@eslint/config-array@0.20.1': dependencies: '@eslint/object-schema': 2.1.6 - debug: 4.4.0 + debug: 4.4.1 minimatch: 3.1.2 transitivePeerDependencies: - supports-color - '@eslint/config-helpers@0.2.0': {} + '@eslint/config-helpers@0.2.3': {} - '@eslint/core@0.12.0': + '@eslint/core@0.14.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/core@0.15.0': dependencies: '@types/json-schema': 7.0.15 '@eslint/eslintrc@3.3.1': dependencies: ajv: 6.12.6 - debug: 4.4.0 - espree: 10.3.0 + debug: 4.4.1 + espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.1 @@ -2828,29 +2801,29 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@9.23.0': {} + '@eslint/js@9.29.0': {} '@eslint/object-schema@2.1.6': {} - '@eslint/plugin-kit@0.2.7': + '@eslint/plugin-kit@0.3.2': dependencies: - '@eslint/core': 0.12.0 + '@eslint/core': 0.15.0 levn: 0.4.1 - '@floating-ui/core@1.6.9': + '@floating-ui/core@1.7.1': dependencies: '@floating-ui/utils': 0.2.9 - '@floating-ui/dom@1.6.13': + '@floating-ui/dom@1.7.1': dependencies: - '@floating-ui/core': 1.6.9 + '@floating-ui/core': 1.7.1 '@floating-ui/utils': 0.2.9 - '@floating-ui/react-dom@2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@floating-ui/react-dom@2.1.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - '@floating-ui/dom': 1.6.13 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@floating-ui/dom': 1.7.1 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) '@floating-ui/utils@0.2.9': {} @@ -2868,16 +2841,11 @@ snapshots: '@humanwhocodes/retry@0.3.1': {} - '@humanwhocodes/retry@0.4.2': {} + '@humanwhocodes/retry@0.4.3': {} - '@isaacs/cliui@8.0.2': + '@isaacs/fs-minipass@4.0.1': dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.0 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 + minipass: 7.1.2 '@jridgewell/gen-mapping@0.3.8': dependencies: @@ -2911,7 +2879,7 @@ snapshots: '@npmcli/fs@2.1.2': dependencies: '@gar/promisify': 1.1.3 - semver: 7.7.1 + semver: 7.7.2 optional: true '@npmcli/move-file@2.0.1': @@ -2920,531 +2888,586 @@ snapshots: rimraf: 3.0.2 optional: true - '@pkgjs/parseargs@0.11.0': + '@radix-ui/primitive@1.1.2': {} + + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.8)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-context@1.1.2(@types/react@19.1.8)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-dialog@1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0) + aria-hidden: 1.2.6 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-remove-scroll: 2.7.1(@types/react@19.1.8)(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-direction@1.1.1(@types/react@19.1.8)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-dismissable-layer@1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-dropdown-menu@2.1.15(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-menu': 2.1.15(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-focus-guards@1.1.2(@types/react@19.1.8)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-id@1.1.1(@types/react@19.1.8)(react@19.1.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-label@2.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-menu@2.1.15(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-roving-focus': 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + aria-hidden: 1.2.6 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-remove-scroll: 2.7.1(@types/react@19.1.8)(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-popper@1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@floating-ui/react-dom': 2.1.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/rect': 1.1.1 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-presence@1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-roving-focus@1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-slot@1.2.3(@types/react@19.1.8)(react@19.1.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.8)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.1.8)(react@19.1.0)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.8)(react@19.1.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.1.8)(react@19.1.0)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.8)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-use-rect@1.1.1(@types/react@19.1.8)(react@19.1.0)': + dependencies: + '@radix-ui/rect': 1.1.1 + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-use-size@1.1.1(@types/react@19.1.8)(react@19.1.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/rect@1.1.1': {} + + '@rolldown/pluginutils@1.0.0-beta.11': {} + + '@rollup/rollup-android-arm-eabi@4.44.0': optional: true - '@radix-ui/primitive@1.1.1': {} - - '@radix-ui/react-arrow@1.1.2(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.20 - '@types/react-dom': 18.3.5(@types/react@18.3.20) - - '@radix-ui/react-collection@1.1.2(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.20)(react@18.3.1) - '@radix-ui/react-context': 1.1.1(@types/react@18.3.20)(react@18.3.1) - '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.1.2(@types/react@18.3.20)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.20 - '@types/react-dom': 18.3.5(@types/react@18.3.20) - - '@radix-ui/react-compose-refs@1.1.1(@types/react@18.3.20)(react@18.3.1)': - dependencies: - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.20 - - '@radix-ui/react-context@1.1.1(@types/react@18.3.20)(react@18.3.1)': - dependencies: - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.20 - - '@radix-ui/react-dialog@1.1.6(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.20)(react@18.3.1) - '@radix-ui/react-context': 1.1.1(@types/react@18.3.20)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.1.5(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.20)(react@18.3.1) - '@radix-ui/react-focus-scope': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-id': 1.1.0(@types/react@18.3.20)(react@18.3.1) - '@radix-ui/react-portal': 1.1.4(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.1.2(@types/react@18.3.20)(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.20)(react@18.3.1) - aria-hidden: 1.2.4 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.6.3(@types/react@18.3.20)(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.20 - '@types/react-dom': 18.3.5(@types/react@18.3.20) - - '@radix-ui/react-direction@1.1.0(@types/react@18.3.20)(react@18.3.1)': - dependencies: - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.20 - - '@radix-ui/react-dismissable-layer@1.1.5(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.20)(react@18.3.1) - '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.20)(react@18.3.1) - '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@18.3.20)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.20 - '@types/react-dom': 18.3.5(@types/react@18.3.20) - - '@radix-ui/react-dropdown-menu@2.1.6(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.20)(react@18.3.1) - '@radix-ui/react-context': 1.1.1(@types/react@18.3.20)(react@18.3.1) - '@radix-ui/react-id': 1.1.0(@types/react@18.3.20)(react@18.3.1) - '@radix-ui/react-menu': 2.1.6(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.20)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.20 - '@types/react-dom': 18.3.5(@types/react@18.3.20) - - '@radix-ui/react-focus-guards@1.1.1(@types/react@18.3.20)(react@18.3.1)': - dependencies: - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.20 - - '@radix-ui/react-focus-scope@1.1.2(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.20)(react@18.3.1) - '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.20)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.20 - '@types/react-dom': 18.3.5(@types/react@18.3.20) - - '@radix-ui/react-id@1.1.0(@types/react@18.3.20)(react@18.3.1)': - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.20)(react@18.3.1) - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.20 - - '@radix-ui/react-label@2.1.2(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.20 - '@types/react-dom': 18.3.5(@types/react@18.3.20) - - '@radix-ui/react-menu@2.1.6(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-collection': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.20)(react@18.3.1) - '@radix-ui/react-context': 1.1.1(@types/react@18.3.20)(react@18.3.1) - '@radix-ui/react-direction': 1.1.0(@types/react@18.3.20)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.1.5(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.20)(react@18.3.1) - '@radix-ui/react-focus-scope': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-id': 1.1.0(@types/react@18.3.20)(react@18.3.1) - '@radix-ui/react-popper': 1.2.2(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-portal': 1.1.4(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-roving-focus': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.1.2(@types/react@18.3.20)(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.20)(react@18.3.1) - aria-hidden: 1.2.4 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.6.3(@types/react@18.3.20)(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.20 - '@types/react-dom': 18.3.5(@types/react@18.3.20) - - '@radix-ui/react-popper@1.2.2(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@floating-ui/react-dom': 2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-arrow': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.20)(react@18.3.1) - '@radix-ui/react-context': 1.1.1(@types/react@18.3.20)(react@18.3.1) - '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.20)(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.20)(react@18.3.1) - '@radix-ui/react-use-rect': 1.1.0(@types/react@18.3.20)(react@18.3.1) - '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.20)(react@18.3.1) - '@radix-ui/rect': 1.1.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.20 - '@types/react-dom': 18.3.5(@types/react@18.3.20) - - '@radix-ui/react-portal@1.1.4(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.20)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.20 - '@types/react-dom': 18.3.5(@types/react@18.3.20) - - '@radix-ui/react-presence@1.1.2(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.20)(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.20)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.20 - '@types/react-dom': 18.3.5(@types/react@18.3.20) - - '@radix-ui/react-primitive@2.0.2(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-slot': 1.1.2(@types/react@18.3.20)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.20 - '@types/react-dom': 18.3.5(@types/react@18.3.20) - - '@radix-ui/react-roving-focus@1.1.2(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-collection': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.20)(react@18.3.1) - '@radix-ui/react-context': 1.1.1(@types/react@18.3.20)(react@18.3.1) - '@radix-ui/react-direction': 1.1.0(@types/react@18.3.20)(react@18.3.1) - '@radix-ui/react-id': 1.1.0(@types/react@18.3.20)(react@18.3.1) - '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.20)(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.20)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.20 - '@types/react-dom': 18.3.5(@types/react@18.3.20) - - '@radix-ui/react-slot@1.1.2(@types/react@18.3.20)(react@18.3.1)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.20)(react@18.3.1) - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.20 - - '@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.20)(react@18.3.1)': - dependencies: - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.20 - - '@radix-ui/react-use-controllable-state@1.1.0(@types/react@18.3.20)(react@18.3.1)': - dependencies: - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.20)(react@18.3.1) - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.20 - - '@radix-ui/react-use-escape-keydown@1.1.0(@types/react@18.3.20)(react@18.3.1)': - dependencies: - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.20)(react@18.3.1) - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.20 - - '@radix-ui/react-use-layout-effect@1.1.0(@types/react@18.3.20)(react@18.3.1)': - dependencies: - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.20 - - '@radix-ui/react-use-rect@1.1.0(@types/react@18.3.20)(react@18.3.1)': - dependencies: - '@radix-ui/rect': 1.1.0 - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.20 - - '@radix-ui/react-use-size@1.1.0(@types/react@18.3.20)(react@18.3.1)': - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.20)(react@18.3.1) - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.20 - - '@radix-ui/rect@1.1.0': {} - - '@remix-run/router@1.23.0': {} - - '@rollup/rollup-android-arm-eabi@4.37.0': + '@rollup/rollup-android-arm64@4.44.0': optional: true - '@rollup/rollup-android-arm64@4.37.0': + '@rollup/rollup-darwin-arm64@4.44.0': optional: true - '@rollup/rollup-darwin-arm64@4.37.0': + '@rollup/rollup-darwin-x64@4.44.0': optional: true - '@rollup/rollup-darwin-x64@4.37.0': + '@rollup/rollup-freebsd-arm64@4.44.0': optional: true - '@rollup/rollup-freebsd-arm64@4.37.0': + '@rollup/rollup-freebsd-x64@4.44.0': optional: true - '@rollup/rollup-freebsd-x64@4.37.0': + '@rollup/rollup-linux-arm-gnueabihf@4.44.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.37.0': + '@rollup/rollup-linux-arm-musleabihf@4.44.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.37.0': + '@rollup/rollup-linux-arm64-gnu@4.44.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.37.0': + '@rollup/rollup-linux-arm64-musl@4.44.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.37.0': + '@rollup/rollup-linux-loongarch64-gnu@4.44.0': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.37.0': + '@rollup/rollup-linux-powerpc64le-gnu@4.44.0': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.37.0': + '@rollup/rollup-linux-riscv64-gnu@4.44.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.37.0': + '@rollup/rollup-linux-riscv64-musl@4.44.0': optional: true - '@rollup/rollup-linux-riscv64-musl@4.37.0': + '@rollup/rollup-linux-s390x-gnu@4.44.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.37.0': + '@rollup/rollup-linux-x64-gnu@4.44.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.37.0': + '@rollup/rollup-linux-x64-musl@4.44.0': optional: true - '@rollup/rollup-linux-x64-musl@4.37.0': + '@rollup/rollup-win32-arm64-msvc@4.44.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.37.0': + '@rollup/rollup-win32-ia32-msvc@4.44.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.37.0': + '@rollup/rollup-win32-x64-msvc@4.44.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.37.0': - optional: true - - '@scalar/openapi-types@0.1.1': {} - - '@scalar/openapi-types@0.1.9': {} - - '@scalar/themes@0.9.82': - dependencies: - '@scalar/types': 0.1.4 - - '@scalar/types@0.0.12': - dependencies: - '@scalar/openapi-types': 0.1.1 - '@unhead/schema': 1.11.20 - - '@scalar/types@0.1.4': - dependencies: - '@scalar/openapi-types': 0.1.9 - '@unhead/schema': 1.11.20 - nanoid: 5.1.5 - type-fest: 4.38.0 - zod: 3.24.2 - - '@sinclair/typebox@0.34.31': {} - '@standard-schema/spec@1.0.0': {} - '@stylistic/eslint-plugin@4.2.0(eslint@9.23.0(jiti@1.21.7))(typescript@5.8.2)': + '@stylistic/eslint-plugin@4.4.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: - '@typescript-eslint/utils': 8.28.0(eslint@9.23.0(jiti@1.21.7))(typescript@5.8.2) - eslint: 9.23.0(jiti@1.21.7) - eslint-visitor-keys: 4.2.0 - espree: 10.3.0 + '@typescript-eslint/utils': 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + eslint: 9.29.0(jiti@2.4.2) + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 estraverse: 5.3.0 picomatch: 4.0.2 transitivePeerDependencies: - supports-color - typescript + '@tailwindcss/node@4.1.10': + dependencies: + '@ampproject/remapping': 2.3.0 + enhanced-resolve: 5.18.1 + jiti: 2.4.2 + lightningcss: 1.30.1 + magic-string: 0.30.17 + source-map-js: 1.2.1 + tailwindcss: 4.1.10 + + '@tailwindcss/oxide-android-arm64@4.1.10': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.1.10': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.1.10': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.1.10': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.10': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.10': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.1.10': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.1.10': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.1.10': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.1.10': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.10': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.1.10': + optional: true + + '@tailwindcss/oxide@4.1.10': + dependencies: + detect-libc: 2.0.4 + tar: 7.4.3 + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.10 + '@tailwindcss/oxide-darwin-arm64': 4.1.10 + '@tailwindcss/oxide-darwin-x64': 4.1.10 + '@tailwindcss/oxide-freebsd-x64': 4.1.10 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.10 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.10 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.10 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.10 + '@tailwindcss/oxide-linux-x64-musl': 4.1.10 + '@tailwindcss/oxide-wasm32-wasi': 4.1.10 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.10 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.10 + + '@tailwindcss/vite@4.1.10(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(lightningcss@1.30.1))': + dependencies: + '@tailwindcss/node': 4.1.10 + '@tailwindcss/oxide': 4.1.10 + tailwindcss: 4.1.10 + vite: 6.3.5(@types/node@24.0.3)(jiti@2.4.2)(lightningcss@1.30.1) + '@tootallnate/once@2.0.0': optional: true '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.27.0 - '@babel/types': 7.27.0 - '@types/babel__generator': 7.6.8 + '@babel/parser': 7.27.5 + '@babel/types': 7.27.6 + '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.20.7 - '@types/babel__generator@7.6.8': + '@types/babel__generator@7.27.0': dependencies: - '@babel/types': 7.27.0 + '@babel/types': 7.27.6 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.27.0 - '@babel/types': 7.27.0 + '@babel/parser': 7.27.5 + '@babel/types': 7.27.6 '@types/babel__traverse@7.20.7': dependencies: - '@babel/types': 7.27.0 + '@babel/types': 7.27.6 - '@types/bun@1.2.8': + '@types/bun@1.2.16': dependencies: - bun-types: 1.2.7 + bun-types: 1.2.16 - '@types/estree@1.0.6': {} - - '@types/estree@1.0.7': {} + '@types/estree@1.0.8': {} '@types/json-schema@7.0.15': {} - '@types/node@22.13.14': + '@types/node@24.0.3': dependencies: - undici-types: 6.20.0 + undici-types: 7.8.0 - '@types/prop-types@15.7.14': {} - - '@types/react-dom@18.3.5(@types/react@18.3.20)': + '@types/react-dom@19.1.6(@types/react@19.1.8)': dependencies: - '@types/react': 18.3.20 + '@types/react': 19.1.8 - '@types/react@18.3.20': + '@types/react@19.1.8': dependencies: - '@types/prop-types': 15.7.14 csstype: 3.1.3 '@types/vexflow@1.2.42': {} - '@types/ws@8.18.0': - dependencies: - '@types/node': 22.13.14 - - '@typescript-eslint/eslint-plugin@8.28.0(@typescript-eslint/parser@8.28.0(eslint@9.23.0(jiti@1.21.7))(typescript@5.8.2))(eslint@9.23.0(jiti@1.21.7))(typescript@5.8.2)': + '@typescript-eslint/eslint-plugin@8.34.1(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.28.0(eslint@9.23.0(jiti@1.21.7))(typescript@5.8.2) - '@typescript-eslint/scope-manager': 8.28.0 - '@typescript-eslint/type-utils': 8.28.0(eslint@9.23.0(jiti@1.21.7))(typescript@5.8.2) - '@typescript-eslint/utils': 8.28.0(eslint@9.23.0(jiti@1.21.7))(typescript@5.8.2) - '@typescript-eslint/visitor-keys': 8.28.0 - eslint: 9.23.0(jiti@1.21.7) + '@typescript-eslint/parser': 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.34.1 + '@typescript-eslint/type-utils': 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/utils': 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.34.1 + eslint: 9.29.0(jiti@2.4.2) graphemer: 1.4.0 - ignore: 5.3.2 + ignore: 7.0.5 natural-compare: 1.4.0 - ts-api-utils: 2.1.0(typescript@5.8.2) - typescript: 5.8.2 + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.28.0(eslint@9.23.0(jiti@1.21.7))(typescript@5.8.2)': + '@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: - '@typescript-eslint/scope-manager': 8.28.0 - '@typescript-eslint/types': 8.28.0 - '@typescript-eslint/typescript-estree': 8.28.0(typescript@5.8.2) - '@typescript-eslint/visitor-keys': 8.28.0 - debug: 4.4.0 - eslint: 9.23.0(jiti@1.21.7) - typescript: 5.8.2 + '@typescript-eslint/scope-manager': 8.34.1 + '@typescript-eslint/types': 8.34.1 + '@typescript-eslint/typescript-estree': 8.34.1(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.34.1 + debug: 4.4.1 + eslint: 9.29.0(jiti@2.4.2) + typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.28.0': + '@typescript-eslint/project-service@8.34.1(typescript@5.8.3)': dependencies: - '@typescript-eslint/types': 8.28.0 - '@typescript-eslint/visitor-keys': 8.28.0 - - '@typescript-eslint/type-utils@8.28.0(eslint@9.23.0(jiti@1.21.7))(typescript@5.8.2)': - dependencies: - '@typescript-eslint/typescript-estree': 8.28.0(typescript@5.8.2) - '@typescript-eslint/utils': 8.28.0(eslint@9.23.0(jiti@1.21.7))(typescript@5.8.2) - debug: 4.4.0 - eslint: 9.23.0(jiti@1.21.7) - ts-api-utils: 2.1.0(typescript@5.8.2) - typescript: 5.8.2 + '@typescript-eslint/tsconfig-utils': 8.34.1(typescript@5.8.3) + '@typescript-eslint/types': 8.34.1 + debug: 4.4.1 + typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.28.0': {} - - '@typescript-eslint/typescript-estree@8.28.0(typescript@5.8.2)': + '@typescript-eslint/scope-manager@8.34.1': dependencies: - '@typescript-eslint/types': 8.28.0 - '@typescript-eslint/visitor-keys': 8.28.0 - debug: 4.4.0 + '@typescript-eslint/types': 8.34.1 + '@typescript-eslint/visitor-keys': 8.34.1 + + '@typescript-eslint/tsconfig-utils@8.34.1(typescript@5.8.3)': + dependencies: + typescript: 5.8.3 + + '@typescript-eslint/type-utils@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)': + dependencies: + '@typescript-eslint/typescript-estree': 8.34.1(typescript@5.8.3) + '@typescript-eslint/utils': 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + debug: 4.4.1 + eslint: 9.29.0(jiti@2.4.2) + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.34.1': {} + + '@typescript-eslint/typescript-estree@8.34.1(typescript@5.8.3)': + dependencies: + '@typescript-eslint/project-service': 8.34.1(typescript@5.8.3) + '@typescript-eslint/tsconfig-utils': 8.34.1(typescript@5.8.3) + '@typescript-eslint/types': 8.34.1 + '@typescript-eslint/visitor-keys': 8.34.1 + debug: 4.4.1 fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 - semver: 7.7.1 - ts-api-utils: 2.1.0(typescript@5.8.2) - typescript: 5.8.2 + semver: 7.7.2 + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.28.0(eslint@9.23.0(jiti@1.21.7))(typescript@5.8.2)': + '@typescript-eslint/utils@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: - '@eslint-community/eslint-utils': 4.5.1(eslint@9.23.0(jiti@1.21.7)) - '@typescript-eslint/scope-manager': 8.28.0 - '@typescript-eslint/types': 8.28.0 - '@typescript-eslint/typescript-estree': 8.28.0(typescript@5.8.2) - eslint: 9.23.0(jiti@1.21.7) - typescript: 5.8.2 + '@eslint-community/eslint-utils': 4.7.0(eslint@9.29.0(jiti@2.4.2)) + '@typescript-eslint/scope-manager': 8.34.1 + '@typescript-eslint/types': 8.34.1 + '@typescript-eslint/typescript-estree': 8.34.1(typescript@5.8.3) + eslint: 9.29.0(jiti@2.4.2) + typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.28.0': + '@typescript-eslint/visitor-keys@8.34.1': dependencies: - '@typescript-eslint/types': 8.28.0 - eslint-visitor-keys: 4.2.0 + '@typescript-eslint/types': 8.34.1 + eslint-visitor-keys: 4.2.1 - '@unhead/schema@1.11.20': + '@vitejs/plugin-react@4.5.2(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(lightningcss@1.30.1))': dependencies: - hookable: 5.5.3 - zhead: 2.2.4 - - '@vitejs/plugin-react@4.3.4(vite@6.2.3(@types/node@22.13.14)(jiti@1.21.7)(yaml@2.7.0))': - dependencies: - '@babel/core': 7.26.10 - '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.10) - '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.10) + '@babel/core': 7.27.4 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.27.4) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.27.4) + '@rolldown/pluginutils': 1.0.0-beta.11 '@types/babel__core': 7.20.5 - react-refresh: 0.14.2 - vite: 6.2.3(@types/node@22.13.14)(jiti@1.21.7)(yaml@2.7.0) + react-refresh: 0.17.0 + vite: 6.3.5(@types/node@24.0.3)(jiti@2.4.2)(lightningcss@1.30.1) transitivePeerDependencies: - supports-color abbrev@1.1.1: optional: true - acorn-jsx@5.3.2(acorn@8.14.1): + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: - acorn: 8.14.1 + acorn: 8.15.0 - acorn@8.14.1: {} + acorn@8.15.0: {} agent-base@6.0.2: dependencies: - debug: 4.4.0 + debug: 4.4.1 transitivePeerDependencies: - supports-color optional: true @@ -3467,23 +3490,13 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ansi-regex@5.0.1: {} - - ansi-regex@6.1.0: {} + ansi-regex@5.0.1: + optional: true ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 - ansi-styles@6.2.1: {} - - any-promise@1.3.0: {} - - anymatch@3.1.3: - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - aproba@2.0.0: optional: true @@ -3493,31 +3506,21 @@ snapshots: readable-stream: 3.6.2 optional: true - arg@5.0.2: {} - argparse@2.0.1: {} - aria-hidden@1.2.4: + aria-hidden@1.2.6: dependencies: tslib: 2.8.1 - autoprefixer@10.4.21(postcss@8.5.3): + babel-plugin-react-compiler@19.1.0-rc.2: dependencies: - browserslist: 4.24.4 - caniuse-lite: 1.0.30001707 - fraction.js: 4.3.7 - normalize-range: 0.1.2 - picocolors: 1.1.1 - postcss: 8.5.3 - postcss-value-parser: 4.2.0 + '@babel/types': 7.27.6 balanced-match@1.0.2: {} base64-js@1.5.1: optional: true - binary-extensions@2.3.0: {} - bindings@1.5.0: dependencies: file-uri-to-path: 1.0.0 @@ -3533,12 +3536,12 @@ snapshots: readable-stream: 3.6.2 optional: true - brace-expansion@1.1.11: + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@2.0.1: + brace-expansion@2.0.2: dependencies: balanced-match: 1.0.2 @@ -3546,12 +3549,12 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.24.4: + browserslist@4.25.0: dependencies: - caniuse-lite: 1.0.30001707 - electron-to-chromium: 1.5.128 + caniuse-lite: 1.0.30001723 + electron-to-chromium: 1.5.170 node-releases: 2.0.19 - update-browserslist-db: 1.1.3(browserslist@4.24.4) + update-browserslist-db: 1.1.3(browserslist@4.25.0) buffer@5.7.1: dependencies: @@ -3559,10 +3562,9 @@ snapshots: ieee754: 1.2.1 optional: true - bun-types@1.2.7: + bun-types@1.2.16: dependencies: - '@types/node': 22.13.14 - '@types/ws': 8.18.0 + '@types/node': 24.0.3 cacache@16.1.3: dependencies: @@ -3590,33 +3592,25 @@ snapshots: callsites@3.1.0: {} - camelcase-css@2.0.1: {} + caniuse-lite@1.0.30001723: {} - caniuse-lite@1.0.30001707: {} + cbor2@2.0.1: + dependencies: + '@cto.af/wtf8': 0.0.2 chalk@4.1.2: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 - chokidar@3.6.0: - dependencies: - anymatch: 3.1.3 - braces: 3.0.3 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 - chownr@1.1.4: optional: true chownr@2.0.0: optional: true + chownr@3.0.0: {} + class-variance-authority@0.7.1: dependencies: clsx: 2.1.1 @@ -3624,8 +3618,6 @@ snapshots: clean-stack@2.2.0: optional: true - clone@2.1.2: {} - clsx@2.1.1: {} color-convert@2.0.1: @@ -3637,8 +3629,6 @@ snapshots: color-support@1.1.3: optional: true - commander@4.1.1: {} - concat-map@0.0.1: {} console-control-strings@1.1.0: @@ -3656,11 +3646,9 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - cssesc@3.0.0: {} - csstype@3.1.3: {} - debug@4.4.0: + debug@4.4.1: dependencies: ms: 2.1.3 @@ -3677,121 +3665,117 @@ snapshots: delegates@1.0.0: optional: true - detect-libc@2.0.3: - optional: true + detect-libc@2.0.4: {} detect-node-es@1.1.0: {} - didyoumean@1.2.2: {} - - dlv@1.1.3: {} - - eastasianwidth@0.2.0: {} - - effect@3.14.2: + effect@3.16.8: dependencies: '@standard-schema/spec': 1.0.0 fast-check: 3.23.2 - electron-to-chromium@1.5.128: {} + electron-to-chromium@1.5.170: {} - elysia@1.2.25(@sinclair/typebox@0.34.31)(openapi-types@12.1.3)(typescript@5.8.2): - dependencies: - '@sinclair/typebox': 0.34.31 - cookie: 1.0.2 - memoirist: 0.3.0 - optionalDependencies: - openapi-types: 12.1.3 - typescript: 5.8.2 - - emoji-regex@8.0.0: {} - - emoji-regex@9.2.2: {} + emoji-regex@8.0.0: + optional: true encoding@0.1.13: dependencies: iconv-lite: 0.6.3 optional: true - end-of-stream@1.4.4: + end-of-stream@1.4.5: dependencies: once: 1.4.0 optional: true + enhanced-resolve@5.18.1: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.2 + env-paths@2.2.1: optional: true err-code@2.0.3: optional: true - esbuild@0.25.1: + esbuild@0.25.5: optionalDependencies: - '@esbuild/aix-ppc64': 0.25.1 - '@esbuild/android-arm': 0.25.1 - '@esbuild/android-arm64': 0.25.1 - '@esbuild/android-x64': 0.25.1 - '@esbuild/darwin-arm64': 0.25.1 - '@esbuild/darwin-x64': 0.25.1 - '@esbuild/freebsd-arm64': 0.25.1 - '@esbuild/freebsd-x64': 0.25.1 - '@esbuild/linux-arm': 0.25.1 - '@esbuild/linux-arm64': 0.25.1 - '@esbuild/linux-ia32': 0.25.1 - '@esbuild/linux-loong64': 0.25.1 - '@esbuild/linux-mips64el': 0.25.1 - '@esbuild/linux-ppc64': 0.25.1 - '@esbuild/linux-riscv64': 0.25.1 - '@esbuild/linux-s390x': 0.25.1 - '@esbuild/linux-x64': 0.25.1 - '@esbuild/netbsd-arm64': 0.25.1 - '@esbuild/netbsd-x64': 0.25.1 - '@esbuild/openbsd-arm64': 0.25.1 - '@esbuild/openbsd-x64': 0.25.1 - '@esbuild/sunos-x64': 0.25.1 - '@esbuild/win32-arm64': 0.25.1 - '@esbuild/win32-ia32': 0.25.1 - '@esbuild/win32-x64': 0.25.1 + '@esbuild/aix-ppc64': 0.25.5 + '@esbuild/android-arm': 0.25.5 + '@esbuild/android-arm64': 0.25.5 + '@esbuild/android-x64': 0.25.5 + '@esbuild/darwin-arm64': 0.25.5 + '@esbuild/darwin-x64': 0.25.5 + '@esbuild/freebsd-arm64': 0.25.5 + '@esbuild/freebsd-x64': 0.25.5 + '@esbuild/linux-arm': 0.25.5 + '@esbuild/linux-arm64': 0.25.5 + '@esbuild/linux-ia32': 0.25.5 + '@esbuild/linux-loong64': 0.25.5 + '@esbuild/linux-mips64el': 0.25.5 + '@esbuild/linux-ppc64': 0.25.5 + '@esbuild/linux-riscv64': 0.25.5 + '@esbuild/linux-s390x': 0.25.5 + '@esbuild/linux-x64': 0.25.5 + '@esbuild/netbsd-arm64': 0.25.5 + '@esbuild/netbsd-x64': 0.25.5 + '@esbuild/openbsd-arm64': 0.25.5 + '@esbuild/openbsd-x64': 0.25.5 + '@esbuild/sunos-x64': 0.25.5 + '@esbuild/win32-arm64': 0.25.5 + '@esbuild/win32-ia32': 0.25.5 + '@esbuild/win32-x64': 0.25.5 escalade@3.2.0: {} escape-string-regexp@4.0.0: {} - eslint-plugin-react-hooks@5.2.0(eslint@9.23.0(jiti@1.21.7)): + eslint-plugin-react-hooks@6.0.0-rc1(eslint@9.29.0(jiti@2.4.2)): dependencies: - eslint: 9.23.0(jiti@1.21.7) + '@babel/core': 7.27.4 + '@babel/parser': 7.27.5 + '@babel/plugin-transform-private-methods': 7.27.1(@babel/core@7.27.4) + eslint: 9.29.0(jiti@2.4.2) + hermes-parser: 0.25.1 + zod: 3.25.67 + zod-validation-error: 3.5.2(zod@3.25.67) + transitivePeerDependencies: + - supports-color - eslint-scope@8.3.0: + eslint-scope@8.4.0: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 eslint-visitor-keys@3.4.3: {} - eslint-visitor-keys@4.2.0: {} + eslint-visitor-keys@4.2.1: {} - eslint@9.23.0(jiti@1.21.7): + eslint@9.29.0(jiti@2.4.2): dependencies: - '@eslint-community/eslint-utils': 4.5.1(eslint@9.23.0(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.29.0(jiti@2.4.2)) '@eslint-community/regexpp': 4.12.1 - '@eslint/config-array': 0.19.2 - '@eslint/config-helpers': 0.2.0 - '@eslint/core': 0.12.0 + '@eslint/config-array': 0.20.1 + '@eslint/config-helpers': 0.2.3 + '@eslint/core': 0.14.0 '@eslint/eslintrc': 3.3.1 - '@eslint/js': 9.23.0 - '@eslint/plugin-kit': 0.2.7 + '@eslint/js': 9.29.0 + '@eslint/plugin-kit': 0.3.2 '@humanfs/node': 0.16.6 '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.4.2 - '@types/estree': 1.0.7 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 '@types/json-schema': 7.0.15 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.0 + debug: 4.4.1 escape-string-regexp: 4.0.0 - eslint-scope: 8.3.0 - eslint-visitor-keys: 4.2.0 - espree: 10.3.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 esquery: 1.6.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 @@ -3807,15 +3791,15 @@ snapshots: natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: - jiti: 1.21.7 + jiti: 2.4.2 transitivePeerDependencies: - supports-color - espree@10.3.0: + espree@10.4.0: dependencies: - acorn: 8.14.1 - acorn-jsx: 5.3.2(acorn@8.14.1) - eslint-visitor-keys: 4.2.0 + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 esquery@1.6.0: dependencies: @@ -3857,6 +3841,10 @@ snapshots: dependencies: reusify: 1.1.0 + fdir@6.4.6(picomatch@4.0.2): + optionalDependencies: + picomatch: 4.0.2 + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -3880,13 +3868,6 @@ snapshots: flatted@3.3.3: {} - foreground-child@3.3.1: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - - fraction.js@4.3.7: {} - fs-constants@1.0.0: optional: true @@ -3901,8 +3882,6 @@ snapshots: fsevents@2.3.3: optional: true - function-bind@1.1.2: {} - gauge@4.0.4: dependencies: aproba: 2.0.0 @@ -3928,7 +3907,7 @@ snapshots: bit-twiddle: 1.0.2 glsl-tokenizer: 2.1.5 nan: 2.22.2 - node-abi: 3.74.0 + node-abi: 3.75.0 node-gyp: 9.4.1 prebuild-install: 7.1.3 transitivePeerDependencies: @@ -3944,15 +3923,6 @@ snapshots: dependencies: is-glob: 4.0.3 - glob@10.4.5: - dependencies: - foreground-child: 3.3.1 - jackspeak: 3.4.3 - minimatch: 9.0.5 - minipass: 7.1.2 - package-json-from-dist: 1.0.1 - path-scurry: 1.11.1 - glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -3981,8 +3951,7 @@ snapshots: through2: 0.6.5 optional: true - graceful-fs@4.2.11: - optional: true + graceful-fs@4.2.11: {} graphemer@1.4.0: {} @@ -3991,20 +3960,20 @@ snapshots: has-unicode@2.0.1: optional: true - hasown@2.0.2: + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: dependencies: - function-bind: 1.1.2 + hermes-estree: 0.25.1 - hookable@5.5.3: {} - - http-cache-semantics@4.1.1: + http-cache-semantics@4.2.0: optional: true http-proxy-agent@5.0.0: dependencies: '@tootallnate/once': 2.0.0 agent-base: 6.0.2 - debug: 4.4.0 + debug: 4.4.1 transitivePeerDependencies: - supports-color optional: true @@ -4012,7 +3981,7 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.4.0 + debug: 4.4.1 transitivePeerDependencies: - supports-color optional: true @@ -4032,6 +4001,8 @@ snapshots: ignore@5.3.2: {} + ignore@7.0.5: {} + immediate@3.0.6: {} import-fresh@3.3.1: @@ -4064,17 +4035,10 @@ snapshots: sprintf-js: 1.1.3 optional: true - is-binary-path@2.1.0: - dependencies: - binary-extensions: 2.3.0 - - is-core-module@2.16.1: - dependencies: - hasown: 2.0.2 - is-extglob@2.1.1: {} - is-fullwidth-code-point@3.0.0: {} + is-fullwidth-code-point@3.0.0: + optional: true is-glob@4.0.3: dependencies: @@ -4092,13 +4056,7 @@ snapshots: isexe@2.0.0: {} - jackspeak@3.4.3: - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - - jiti@1.21.7: {} + jiti@2.4.2: {} js-tokens@4.0.0: {} @@ -4130,12 +4088,12 @@ snapshots: dependencies: json-buffer: 3.0.1 - kysely-bun-sqlite@0.3.2(kysely@0.27.6): + kysely-bun-sqlite@0.4.0(kysely@0.28.2): dependencies: - bun-types: 1.2.7 - kysely: 0.27.6 + bun-types: 1.2.16 + kysely: 0.28.2 - kysely@0.27.6: {} + kysely@0.28.2: {} levn@0.4.1: dependencies: @@ -4146,9 +4104,50 @@ snapshots: dependencies: immediate: 3.0.6 - lilconfig@3.1.3: {} + lightningcss-darwin-arm64@1.30.1: + optional: true - lines-and-columns@1.2.4: {} + lightningcss-darwin-x64@1.30.1: + optional: true + + lightningcss-freebsd-x64@1.30.1: + optional: true + + lightningcss-linux-arm-gnueabihf@1.30.1: + optional: true + + lightningcss-linux-arm64-gnu@1.30.1: + optional: true + + lightningcss-linux-arm64-musl@1.30.1: + optional: true + + lightningcss-linux-x64-gnu@1.30.1: + optional: true + + lightningcss-linux-x64-musl@1.30.1: + optional: true + + lightningcss-win32-arm64-msvc@1.30.1: + optional: true + + lightningcss-win32-x64-msvc@1.30.1: + optional: true + + lightningcss@1.30.1: + dependencies: + detect-libc: 2.0.4 + optionalDependencies: + lightningcss-darwin-arm64: 1.30.1 + lightningcss-darwin-x64: 1.30.1 + lightningcss-freebsd-x64: 1.30.1 + lightningcss-linux-arm-gnueabihf: 1.30.1 + lightningcss-linux-arm64-gnu: 1.30.1 + lightningcss-linux-arm64-musl: 1.30.1 + lightningcss-linux-x64-gnu: 1.30.1 + lightningcss-linux-x64-musl: 1.30.1 + lightningcss-win32-arm64-msvc: 1.30.1 + lightningcss-win32-x64-msvc: 1.30.1 locate-path@6.0.0: dependencies: @@ -4158,12 +4157,6 @@ snapshots: loglevel@1.9.2: {} - loose-envify@1.4.0: - dependencies: - js-tokens: 4.0.0 - - lru-cache@10.4.3: {} - lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -4171,15 +4164,19 @@ snapshots: lru-cache@7.18.3: optional: true - lucide-react@0.485.0(react@18.3.1): + lucide-react@0.518.0(react@19.1.0): dependencies: - react: 18.3.1 + react: 19.1.0 + + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 make-fetch-happen@10.2.1: dependencies: agentkeepalive: 4.6.0 cacache: 16.1.3 - http-cache-semantics: 4.1.1 + http-cache-semantics: 4.2.0 http-proxy-agent: 5.0.0 https-proxy-agent: 5.0.1 is-lambda: 1.0.1 @@ -4198,8 +4195,6 @@ snapshots: - supports-color optional: true - memoirist@0.3.0: {} - merge2@1.4.1: {} micromatch@4.0.8: @@ -4212,16 +4207,16 @@ snapshots: minimatch@3.1.2: dependencies: - brace-expansion: 1.1.11 + brace-expansion: 1.1.12 minimatch@5.1.6: dependencies: - brace-expansion: 2.0.1 + brace-expansion: 2.0.2 optional: true minimatch@9.0.5: dependencies: - brace-expansion: 2.0.1 + brace-expansion: 2.0.2 minimist@1.2.8: optional: true @@ -4271,27 +4266,25 @@ snapshots: yallist: 4.0.0 optional: true + minizlib@3.0.2: + dependencies: + minipass: 7.1.2 + mkdirp-classic@0.5.3: optional: true mkdirp@1.0.4: optional: true - ms@2.1.3: {} + mkdirp@3.0.1: {} - mz@2.7.0: - dependencies: - any-promise: 1.3.0 - object-assign: 4.1.1 - thenify-all: 1.6.0 + ms@2.1.3: {} nan@2.22.2: optional: true nanoid@3.3.11: {} - nanoid@5.1.5: {} - napi-build-utils@2.0.0: optional: true @@ -4300,15 +4293,11 @@ snapshots: negotiator@0.6.4: optional: true - node-abi@3.74.0: + node-abi@3.75.0: dependencies: - semver: 7.7.1 + semver: 7.7.2 optional: true - node-cache@5.1.2: - dependencies: - clone: 2.1.2 - node-gyp@9.4.1: dependencies: env-paths: 2.2.1 @@ -4319,7 +4308,7 @@ snapshots: nopt: 6.0.0 npmlog: 6.0.2 rimraf: 3.0.2 - semver: 7.7.1 + semver: 7.7.2 tar: 6.2.1 which: 2.0.2 transitivePeerDependencies: @@ -4334,10 +4323,6 @@ snapshots: abbrev: 1.1.1 optional: true - normalize-path@3.0.0: {} - - normalize-range@0.1.2: {} - npmlog@6.0.2: dependencies: are-we-there-yet: 3.0.1 @@ -4346,17 +4331,11 @@ snapshots: set-blocking: 2.0.0 optional: true - object-assign@4.1.1: {} - - object-hash@3.0.0: {} - once@1.4.0: dependencies: wrappy: 1.0.2 optional: true - openapi-types@12.1.3: {} - opensheetmusicdisplay@1.9.0: dependencies: '@types/vexflow': 1.2.42 @@ -4392,8 +4371,6 @@ snapshots: aggregate-error: 3.1.0 optional: true - package-json-from-dist@1.0.1: {} - pako@1.0.11: {} parent-module@1.0.1: @@ -4407,57 +4384,13 @@ snapshots: path-key@3.1.1: {} - path-parse@1.0.7: {} - - path-scurry@1.11.1: - dependencies: - lru-cache: 10.4.3 - minipass: 7.1.2 - - pathe@1.1.2: {} - picocolors@1.1.1: {} picomatch@2.3.1: {} picomatch@4.0.2: {} - pify@2.3.0: {} - - pirates@4.0.7: {} - - postcss-import@15.1.0(postcss@8.5.3): - dependencies: - postcss: 8.5.3 - postcss-value-parser: 4.2.0 - read-cache: 1.0.0 - resolve: 1.22.10 - - postcss-js@4.0.1(postcss@8.5.3): - dependencies: - camelcase-css: 2.0.1 - postcss: 8.5.3 - - postcss-load-config@4.0.2(postcss@8.5.3): - dependencies: - lilconfig: 3.1.3 - yaml: 2.7.0 - optionalDependencies: - postcss: 8.5.3 - - postcss-nested@6.2.0(postcss@8.5.3): - dependencies: - postcss: 8.5.3 - postcss-selector-parser: 6.1.2 - - postcss-selector-parser@6.1.2: - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - - postcss-value-parser@4.2.0: {} - - postcss@8.5.3: + postcss@8.5.6: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 @@ -4465,17 +4398,17 @@ snapshots: prebuild-install@7.1.3: dependencies: - detect-libc: 2.0.3 + detect-libc: 2.0.4 expand-template: 2.0.3 github-from-package: 0.0.0 minimist: 1.2.8 mkdirp-classic: 0.5.3 napi-build-utils: 2.0.0 - node-abi: 3.74.0 - pump: 3.0.2 + node-abi: 3.75.0 + pump: 3.0.3 rc: 1.2.8 simple-get: 4.0.1 - tar-fs: 2.1.2 + tar-fs: 2.1.3 tunnel-agent: 0.6.0 optional: true @@ -4492,9 +4425,9 @@ snapshots: retry: 0.12.0 optional: true - pump@3.0.2: + pump@3.0.3: dependencies: - end-of-stream: 1.4.4 + end-of-stream: 1.4.5 once: 1.4.0 optional: true @@ -4512,60 +4445,55 @@ snapshots: strip-json-comments: 2.0.1 optional: true - react-dom@18.3.1(react@18.3.1): + react-dom@19.1.0(react@19.1.0): dependencies: - loose-envify: 1.4.0 - react: 18.3.1 - scheduler: 0.23.2 + react: 19.1.0 + scheduler: 0.26.0 - react-refresh@0.14.2: {} + react-refresh@0.17.0: {} - react-remove-scroll-bar@2.3.8(@types/react@18.3.20)(react@18.3.1): + react-remove-scroll-bar@2.3.8(@types/react@19.1.8)(react@19.1.0): dependencies: - react: 18.3.1 - react-style-singleton: 2.2.3(@types/react@18.3.20)(react@18.3.1) + react: 19.1.0 + react-style-singleton: 2.2.3(@types/react@19.1.8)(react@19.1.0) tslib: 2.8.1 optionalDependencies: - '@types/react': 18.3.20 + '@types/react': 19.1.8 - react-remove-scroll@2.6.3(@types/react@18.3.20)(react@18.3.1): + react-remove-scroll@2.7.1(@types/react@19.1.8)(react@19.1.0): dependencies: - react: 18.3.1 - react-remove-scroll-bar: 2.3.8(@types/react@18.3.20)(react@18.3.1) - react-style-singleton: 2.2.3(@types/react@18.3.20)(react@18.3.1) + react: 19.1.0 + react-remove-scroll-bar: 2.3.8(@types/react@19.1.8)(react@19.1.0) + react-style-singleton: 2.2.3(@types/react@19.1.8)(react@19.1.0) tslib: 2.8.1 - use-callback-ref: 1.3.3(@types/react@18.3.20)(react@18.3.1) - use-sidecar: 1.1.3(@types/react@18.3.20)(react@18.3.1) + use-callback-ref: 1.3.3(@types/react@19.1.8)(react@19.1.0) + use-sidecar: 1.1.3(@types/react@19.1.8)(react@19.1.0) optionalDependencies: - '@types/react': 18.3.20 + '@types/react': 19.1.8 - react-router-dom@6.30.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + react-router-dom@7.6.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: - '@remix-run/router': 1.23.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-router: 6.30.0(react@18.3.1) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-router: 7.6.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - react-router@6.30.0(react@18.3.1): + react-router@7.6.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: - '@remix-run/router': 1.23.0 - react: 18.3.1 + cookie: 1.0.2 + react: 19.1.0 + set-cookie-parser: 2.7.1 + optionalDependencies: + react-dom: 19.1.0(react@19.1.0) - react-style-singleton@2.2.3(@types/react@18.3.20)(react@18.3.1): + react-style-singleton@2.2.3(@types/react@19.1.8)(react@19.1.0): dependencies: get-nonce: 1.0.1 - react: 18.3.1 + react: 19.1.0 tslib: 2.8.1 optionalDependencies: - '@types/react': 18.3.20 + '@types/react': 19.1.8 - react@18.3.1: - dependencies: - loose-envify: 1.4.0 - - read-cache@1.0.0: - dependencies: - pify: 2.3.0 + react@19.1.0: {} readable-stream@1.0.34: dependencies: @@ -4592,18 +4520,8 @@ snapshots: util-deprecate: 1.0.2 optional: true - readdirp@3.6.0: - dependencies: - picomatch: 2.3.1 - resolve-from@4.0.0: {} - resolve@1.22.10: - dependencies: - is-core-module: 2.16.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - retry@0.12.0: optional: true @@ -4614,30 +4532,30 @@ snapshots: glob: 7.2.3 optional: true - rollup@4.37.0: + rollup@4.44.0: dependencies: - '@types/estree': 1.0.6 + '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.37.0 - '@rollup/rollup-android-arm64': 4.37.0 - '@rollup/rollup-darwin-arm64': 4.37.0 - '@rollup/rollup-darwin-x64': 4.37.0 - '@rollup/rollup-freebsd-arm64': 4.37.0 - '@rollup/rollup-freebsd-x64': 4.37.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.37.0 - '@rollup/rollup-linux-arm-musleabihf': 4.37.0 - '@rollup/rollup-linux-arm64-gnu': 4.37.0 - '@rollup/rollup-linux-arm64-musl': 4.37.0 - '@rollup/rollup-linux-loongarch64-gnu': 4.37.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.37.0 - '@rollup/rollup-linux-riscv64-gnu': 4.37.0 - '@rollup/rollup-linux-riscv64-musl': 4.37.0 - '@rollup/rollup-linux-s390x-gnu': 4.37.0 - '@rollup/rollup-linux-x64-gnu': 4.37.0 - '@rollup/rollup-linux-x64-musl': 4.37.0 - '@rollup/rollup-win32-arm64-msvc': 4.37.0 - '@rollup/rollup-win32-ia32-msvc': 4.37.0 - '@rollup/rollup-win32-x64-msvc': 4.37.0 + '@rollup/rollup-android-arm-eabi': 4.44.0 + '@rollup/rollup-android-arm64': 4.44.0 + '@rollup/rollup-darwin-arm64': 4.44.0 + '@rollup/rollup-darwin-x64': 4.44.0 + '@rollup/rollup-freebsd-arm64': 4.44.0 + '@rollup/rollup-freebsd-x64': 4.44.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.44.0 + '@rollup/rollup-linux-arm-musleabihf': 4.44.0 + '@rollup/rollup-linux-arm64-gnu': 4.44.0 + '@rollup/rollup-linux-arm64-musl': 4.44.0 + '@rollup/rollup-linux-loongarch64-gnu': 4.44.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.44.0 + '@rollup/rollup-linux-riscv64-gnu': 4.44.0 + '@rollup/rollup-linux-riscv64-musl': 4.44.0 + '@rollup/rollup-linux-s390x-gnu': 4.44.0 + '@rollup/rollup-linux-x64-gnu': 4.44.0 + '@rollup/rollup-linux-x64-musl': 4.44.0 + '@rollup/rollup-win32-arm64-msvc': 4.44.0 + '@rollup/rollup-win32-ia32-msvc': 4.44.0 + '@rollup/rollup-win32-x64-msvc': 4.44.0 fsevents: 2.3.3 run-parallel@1.2.0: @@ -4652,17 +4570,17 @@ snapshots: safer-buffer@2.1.2: optional: true - scheduler@0.23.2: - dependencies: - loose-envify: 1.4.0 + scheduler@0.26.0: {} semver@6.3.1: {} - semver@7.7.1: {} + semver@7.7.2: {} set-blocking@2.0.0: optional: true + set-cookie-parser@2.7.1: {} + setimmediate@1.0.5: {} shebang-command@2.0.0: @@ -4674,8 +4592,6 @@ snapshots: signal-exit@3.0.7: optional: true - signal-exit@4.1.0: {} - simple-concat@1.0.1: optional: true @@ -4692,13 +4608,13 @@ snapshots: socks-proxy-agent@7.0.0: dependencies: agent-base: 6.0.2 - debug: 4.4.0 - socks: 2.8.4 + debug: 4.4.1 + socks: 2.8.5 transitivePeerDependencies: - supports-color optional: true - socks@2.8.4: + socks@2.8.5: dependencies: ip-address: 9.0.5 smart-buffer: 4.2.0 @@ -4719,12 +4635,7 @@ snapshots: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - - string-width@5.1.2: - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.1.0 + optional: true string_decoder@0.10.31: optional: true @@ -4741,77 +4652,35 @@ snapshots: strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 - - strip-ansi@7.1.0: - dependencies: - ansi-regex: 6.1.0 + optional: true strip-json-comments@2.0.1: optional: true strip-json-comments@3.1.1: {} - sucrase@3.35.0: - dependencies: - '@jridgewell/gen-mapping': 0.3.8 - commander: 4.1.1 - glob: 10.4.5 - lines-and-columns: 1.2.4 - mz: 2.7.0 - pirates: 4.0.7 - ts-interface-checker: 0.1.13 - supports-color@7.2.0: dependencies: has-flag: 4.0.0 - supports-preserve-symlinks-flag@1.0.0: {} + tailwind-merge@3.3.1: {} - tailwind-merge@2.6.0: {} + tailwindcss@4.1.10: {} - tailwindcss-animate@1.0.7(tailwindcss@3.4.17): - dependencies: - tailwindcss: 3.4.17 + tapable@2.2.2: {} - tailwindcss@3.4.17: - dependencies: - '@alloc/quick-lru': 5.2.0 - arg: 5.0.2 - chokidar: 3.6.0 - didyoumean: 1.2.2 - dlv: 1.1.3 - fast-glob: 3.3.3 - glob-parent: 6.0.2 - is-glob: 4.0.3 - jiti: 1.21.7 - lilconfig: 3.1.3 - micromatch: 4.0.8 - normalize-path: 3.0.0 - object-hash: 3.0.0 - picocolors: 1.1.1 - postcss: 8.5.3 - postcss-import: 15.1.0(postcss@8.5.3) - postcss-js: 4.0.1(postcss@8.5.3) - postcss-load-config: 4.0.2(postcss@8.5.3) - postcss-nested: 6.2.0(postcss@8.5.3) - postcss-selector-parser: 6.1.2 - resolve: 1.22.10 - sucrase: 3.35.0 - transitivePeerDependencies: - - ts-node - - tar-fs@2.1.2: + tar-fs@2.1.3: dependencies: chownr: 1.1.4 mkdirp-classic: 0.5.3 - pump: 3.0.2 + pump: 3.0.3 tar-stream: 2.2.0 optional: true tar-stream@2.2.0: dependencies: bl: 4.1.0 - end-of-stream: 1.4.4 + end-of-stream: 1.4.5 fs-constants: 1.0.0 inherits: 2.0.4 readable-stream: 3.6.2 @@ -4827,13 +4696,14 @@ snapshots: yallist: 4.0.0 optional: true - thenify-all@1.6.0: + tar@7.4.3: dependencies: - thenify: 3.3.1 - - thenify@3.3.1: - dependencies: - any-promise: 1.3.0 + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.0.2 + mkdirp: 3.0.1 + yallist: 5.0.0 through2@0.6.5: dependencies: @@ -4841,15 +4711,18 @@ snapshots: xtend: 4.0.2 optional: true + tinyglobby@0.2.14: + dependencies: + fdir: 6.4.6(picomatch@4.0.2) + picomatch: 4.0.2 + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 - ts-api-utils@2.1.0(typescript@5.8.2): + ts-api-utils@2.1.0(typescript@5.8.3): dependencies: - typescript: 5.8.2 - - ts-interface-checker@0.1.13: {} + typescript: 5.8.3 tslib@2.8.1: {} @@ -4858,27 +4731,27 @@ snapshots: safe-buffer: 5.2.1 optional: true + tw-animate-css@1.3.4: {} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 - type-fest@4.38.0: {} - typescript-collections@1.3.3: {} - typescript-eslint@8.28.0(eslint@9.23.0(jiti@1.21.7))(typescript@5.8.2): + typescript-eslint@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.28.0(@typescript-eslint/parser@8.28.0(eslint@9.23.0(jiti@1.21.7))(typescript@5.8.2))(eslint@9.23.0(jiti@1.21.7))(typescript@5.8.2) - '@typescript-eslint/parser': 8.28.0(eslint@9.23.0(jiti@1.21.7))(typescript@5.8.2) - '@typescript-eslint/utils': 8.28.0(eslint@9.23.0(jiti@1.21.7))(typescript@5.8.2) - eslint: 9.23.0(jiti@1.21.7) - typescript: 5.8.2 + '@typescript-eslint/eslint-plugin': 8.34.1(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/parser': 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/utils': 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + eslint: 9.29.0(jiti@2.4.2) + typescript: 5.8.3 transitivePeerDependencies: - supports-color - typescript@5.8.2: {} + typescript@5.8.3: {} - undici-types@6.20.0: {} + undici-types@7.8.0: {} unique-filename@2.0.1: dependencies: @@ -4890,9 +4763,9 @@ snapshots: imurmurhash: 0.1.4 optional: true - update-browserslist-db@1.1.3(browserslist@4.24.4): + update-browserslist-db@1.1.3(browserslist@4.25.0): dependencies: - browserslist: 4.24.4 + browserslist: 4.25.0 escalade: 3.2.0 picocolors: 1.1.1 @@ -4900,35 +4773,38 @@ snapshots: dependencies: punycode: 2.3.1 - use-callback-ref@1.3.3(@types/react@18.3.20)(react@18.3.1): + use-callback-ref@1.3.3(@types/react@19.1.8)(react@19.1.0): dependencies: - react: 18.3.1 + react: 19.1.0 tslib: 2.8.1 optionalDependencies: - '@types/react': 18.3.20 + '@types/react': 19.1.8 - use-sidecar@1.1.3(@types/react@18.3.20)(react@18.3.1): + use-sidecar@1.1.3(@types/react@19.1.8)(react@19.1.0): dependencies: detect-node-es: 1.1.0 - react: 18.3.1 + react: 19.1.0 tslib: 2.8.1 optionalDependencies: - '@types/react': 18.3.20 + '@types/react': 19.1.8 util-deprecate@1.0.2: {} vexflow@1.2.93: {} - vite@6.2.3(@types/node@22.13.14)(jiti@1.21.7)(yaml@2.7.0): + vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(lightningcss@1.30.1): dependencies: - esbuild: 0.25.1 - postcss: 8.5.3 - rollup: 4.37.0 + esbuild: 0.25.5 + fdir: 6.4.6(picomatch@4.0.2) + picomatch: 4.0.2 + postcss: 8.5.6 + rollup: 4.44.0 + tinyglobby: 0.2.14 optionalDependencies: - '@types/node': 22.13.14 + '@types/node': 24.0.3 fsevents: 2.3.3 - jiti: 1.21.7 - yaml: 2.7.0 + jiti: 2.4.2 + lightningcss: 1.30.1 which@2.0.2: dependencies: @@ -4941,18 +4817,6 @@ snapshots: word-wrap@1.2.5: {} - wrap-ansi@7.0.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - wrap-ansi@8.1.0: - dependencies: - ansi-styles: 6.2.1 - string-width: 5.1.2 - strip-ansi: 7.1.0 - wrappy@1.0.2: optional: true @@ -4964,10 +4828,12 @@ snapshots: yallist@4.0.0: optional: true - yaml@2.7.0: {} + yallist@5.0.0: {} yocto-queue@0.1.0: {} - zhead@2.2.4: {} + zod-validation-error@3.5.2(zod@3.25.67): + dependencies: + zod: 3.25.67 - zod@3.24.2: {} + zod@3.25.67: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 7846a6e..8a97c76 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,42 +1,44 @@ packages: + - '.' - 'packages/*' +ignoredBuiltDependencies: + - 'gl' + onlyBuiltDependencies: + - '@tailwindcss/oxide' - 'esbuild' catalog: - '@elysiajs/cors': '^1.2.0' - '@elysiajs/eden': '^1.2.0' - '@elysiajs/static': '^1.2.0' - '@elysiajs/swagger': '^1.2.2' - '@eslint/js': '^9.23.0' - '@radix-ui/react-dialog': '^1.1.6' - '@radix-ui/react-dropdown-menu': '^2.1.6' - '@radix-ui/react-label': '^2.1.2' - '@radix-ui/react-slot': '^1.1.2' - '@stylistic/eslint-plugin': '^4.2.0' - '@types/bun': '^1.2.8' - '@types/react': '^18.3.12' - '@types/react-dom': '^18.3.1' - '@vitejs/plugin-react': '^4.3.4' - autoprefixer: '^10.4.21' + '@effect/language-service': '^0.21.4' + '@eslint/js': '^9.29.0' + '@radix-ui/react-dialog': '^1.1.14' + '@radix-ui/react-dropdown-menu': '^2.1.15' + '@radix-ui/react-label': '^2.1.7' + '@radix-ui/react-slot': '^1.2.3' + '@stylistic/eslint-plugin': '^4.4.1' + '@tailwindcss/vite': '^4.1.10' + '@types/bun': '^1.2.16' + '@types/react': '^19.1.8' + '@types/react-dom': '^19.1.6' + '@vitejs/plugin-react': '^4.5.2' + babel-plugin-react-compiler: '^19.1.0-rc.2' + cbor2: '^2.0.1' class-variance-authority: '^0.7.1' clsx: '^2.1.1' - effect: '^3.14.2' - elysia: '^1.2.2' - eslint-plugin-react-hooks: '^5.2.0' + effect: '^3.16.8' + eslint-plugin-react-hooks: '6.0.0-rc1' jszip: '^3.10.1' - kysely: '^0.27.6' - kysely-bun-sqlite: '^0.3.2' - lucide-react: '^0.485.0' + kysely: '^0.28.2' + kysely-bun-sqlite: '^0.4.0' + lucide-react: '^0.518.0' opensheetmusicdisplay: '^1.9.0' - postcss: '^8.5.3' - react: '^18.3.1' - react-dom: '^18.3.1' - react-router-dom: '^6.30.0' - tailwind-merge: '^2.6.0' - tailwindcss: '^3.4.17' - tailwindcss-animate: '^1.0.7' - typescript: '^5.8.2' - typescript-eslint: '^8.28.0' - vite: '^6.2.3' + react: '^19.1.0' + react-dom: '^19.1.0' + react-router-dom: '^7.6.2' + tailwind-merge: '^3.3.1' + tailwindcss: '^4.1.10' + tw-animate-css: '^1.3.4' + typescript: '^5.8.3' + typescript-eslint: '^8.34.1' + vite: '^6.3.5' diff --git a/tsconfig.base.json b/tsconfig.base.json index af9a578..5f7e5f9 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -31,6 +31,10 @@ "backend": ["./packages/backend/src/index.ts"], "backend/*": ["./packages/backend/src/*.ts"], }, + + "plugins": [ + { "name": "@effect/language-service" }, + ], }, "include": ["${configDir}/src"], }