diff --git a/packages/api/package.json b/packages/api/package.json index f98e2816..69290373 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -12,22 +12,21 @@ }, "devDependencies": { "@types/bun": "latest", + "@types/pg": "^8.11.10", "config": "workspace:*", "eslint": "^9.13.0", "typescript": "^5.6.3" }, "dependencies": { "@aws-sdk/client-s3": "^3.623.0", - "@elysiajs/bearer": "^1.1.2", "@elysiajs/cors": "^1.1.1", "@elysiajs/eden": "^1.1.3", - "@elysiajs/jwt": "^1.1.1", "@matvp91/elysia-swagger": "^2.0.0", "@superstreamer/artisan": "workspace:*", "bullmq": "^5.12.0", "elysia": "^1.1.20", "kysely": "^0.27.4", - "kysely-bun-sqlite": "^0.3.2", + "pg": "^8.13.1", "shared": "workspace:*" } } diff --git a/packages/api/src/db/index.ts b/packages/api/src/db/index.ts index 7a39e649..cc9b38db 100644 --- a/packages/api/src/db/index.ts +++ b/packages/api/src/db/index.ts @@ -1,17 +1,19 @@ -import { BunSqliteDialect } from "kysely-bun-sqlite"; -import { Kysely, Migrator, FileMigrationProvider } from "kysely"; -import { Database } from "bun:sqlite"; +import { + Kysely, + Migrator, + FileMigrationProvider, + PostgresDialect, +} from "kysely"; import * as path from "node:path"; import * as fs from "node:fs/promises"; +import { Pool } from "pg"; import { env } from "../env"; import type { KyselyDatabase } from "./types.ts"; -const dialect = new BunSqliteDialect({ - database: new Database(env.DATABASE), -}); - export const db = new Kysely({ - dialect, + dialect: new PostgresDialect({ + pool: new Pool({ connectionString: env.DATABASE }), + }), }); async function migrateToLatest() { diff --git a/packages/api/src/db/migrations/2024_10_26_init.ts b/packages/api/src/db/migrations/2024_10_26_init.ts index bf089b6f..09b21d52 100644 --- a/packages/api/src/db/migrations/2024_10_26_init.ts +++ b/packages/api/src/db/migrations/2024_10_26_init.ts @@ -5,9 +5,10 @@ import { Kysely } from "kysely"; export async function up(db: Kysely) { await db.schema .createTable("user") - .addColumn("id", "integer", (col) => col.primaryKey()) + .addColumn("id", "serial", (col) => col.primaryKey()) .addColumn("username", "text", (col) => col.notNull()) .addColumn("password", "text", (col) => col.notNull()) + .addColumn("settingAutoRefetch", "boolean", (col) => col.defaultTo(true)) .execute(); await db diff --git a/packages/api/src/db/repo-user.ts b/packages/api/src/db/repo-user.ts index d5597774..d2565435 100644 --- a/packages/api/src/db/repo-user.ts +++ b/packages/api/src/db/repo-user.ts @@ -1,4 +1,5 @@ import { db } from "."; +import type { UserUpdate } from "./types"; export async function getUserIdByCredentials(name: string, password: string) { const user = await db @@ -22,7 +23,18 @@ export async function getUserIdByCredentials(name: string, password: string) { export async function getUser(id: number) { return await db .selectFrom("user") - .select(["id", "username"]) + .select(["id", "username", "settingAutoRefetch"]) + .where("id", "=", id) + .executeTakeFirstOrThrow(); +} + +export async function updateUser( + id: number, + fields: Omit, +) { + return await db + .updateTable("user") + .set(fields) .where("id", "=", id) .executeTakeFirstOrThrow(); } diff --git a/packages/api/src/db/types.ts b/packages/api/src/db/types.ts index c3031848..91ba7dcb 100644 --- a/packages/api/src/db/types.ts +++ b/packages/api/src/db/types.ts @@ -1,4 +1,4 @@ -import type { Generated, Selectable, Updateable } from "kysely"; +import type { Generated, Updateable } from "kysely"; export interface KyselyDatabase { user: UserTable; @@ -8,7 +8,7 @@ export interface UserTable { id: Generated; username: string; password: string; + settingAutoRefetch: boolean; } -export type User = Selectable; export type UserUpdate = Updateable; diff --git a/packages/api/src/routes/auth.ts b/packages/api/src/routes/auth.ts index b5a06642..aa2776d5 100644 --- a/packages/api/src/routes/auth.ts +++ b/packages/api/src/routes/auth.ts @@ -1,57 +1,20 @@ import { Elysia, t } from "elysia"; -import { jwt } from "@elysiajs/jwt"; import { env } from "../env"; import { getUserIdByCredentials } from "../db/repo-user"; -import bearer from "@elysiajs/bearer"; +import { bearerAuth } from "shared/auth"; -export const authJwt = new Elysia().use( - jwt({ - name: "authJwt", - schema: t.Union([ - // User tokens describe a user interacting with the API. - t.Object({ - type: t.Literal("user"), - id: t.Number(), - }), - // Service tokens, such as Stitcher. - t.Object({ type: t.Literal("service") }), - ]), - secret: env.JWT_SECRET, - }), -); +const { user, jwtUser } = bearerAuth(env.JWT_SECRET); -export const authUser = new Elysia() - .use(bearer()) - .use(authJwt) - .derive({ as: "scoped" }, async ({ bearer, authJwt, set }) => { - const token = await authJwt.verify(bearer); - if (!token) { - set.status = 401; - throw new Error("Unauthorized"); - } - if (token.type === "user") { - return { - user: { type: "user", id: token.id }, - }; - } - if (token.type === "service") { - return { - user: { type: "service" }, - }; - } - throw new Error("Invalid token type"); - }); - -export const auth = new Elysia().use(authJwt).post( +export const auth = new Elysia().use(jwtUser).post( "/login", - async ({ authJwt, body, set }) => { + async ({ jwtUser, body, set }) => { const id = await getUserIdByCredentials(body.username, body.password); if (id === null) { set.status = 400; return "Unauthorized"; } return { - token: await authJwt.sign({ + token: await jwtUser.sign({ type: "user", id, }), @@ -73,3 +36,6 @@ export const auth = new Elysia().use(authJwt).post( }, }, ); + +// Re-export these so we can consume them in other routes. +export { user, jwtUser }; diff --git a/packages/api/src/routes/jobs.ts b/packages/api/src/routes/jobs.ts index cf837cb4..e6430610 100644 --- a/packages/api/src/routes/jobs.ts +++ b/packages/api/src/routes/jobs.ts @@ -10,10 +10,10 @@ import { } from "shared/typebox"; import { getJob, getJobs, getJobLogs } from "../jobs"; import { JobSchema } from "../types"; -import { authUser } from "./auth"; +import { user } from "./auth"; export const jobs = new Elysia() - .use(authUser) + .use(user) .post( "/transcode", async ({ body }) => { diff --git a/packages/api/src/routes/profile.ts b/packages/api/src/routes/profile.ts index db3db1e6..a3920a58 100644 --- a/packages/api/src/routes/profile.ts +++ b/packages/api/src/routes/profile.ts @@ -1,22 +1,38 @@ import { Elysia, t } from "elysia"; -import { authUser } from "./auth"; -import { getUser } from "../db/repo-user"; +import { user } from "./auth"; +import { getUser, updateUser } from "../db/repo-user"; import { UserSchema } from "../types"; -export const profile = new Elysia().use(authUser).get( - "/profile", - async ({ user }) => { - if (user.type !== "user") { - throw new Error(`Not a user token , received "${user.type}"`); - } - return await getUser(user.id); - }, - { - detail: { - summary: "Get your profile", +export const profile = new Elysia() + .use(user) + .get( + "/profile", + async ({ user }) => { + if (user.type !== "user") { + throw new Error(`Not a user token , received "${user.type}"`); + } + return await getUser(user.id); }, - response: { - 200: t.Ref(UserSchema), + { + detail: { + summary: "Get your profile", + }, + response: { + 200: t.Ref(UserSchema), + }, }, - }, -); + ) + .put( + "/profile", + async ({ user, body }) => { + if (user.type !== "user") { + throw new Error(`Not a user token , received "${user.type}"`); + } + await updateUser(user.id, body); + }, + { + body: t.Object({ + settingAutoRefetch: t.Optional(t.Boolean()), + }), + }, + ); diff --git a/packages/api/src/routes/storage.ts b/packages/api/src/routes/storage.ts index d6a300c3..e76f8229 100644 --- a/packages/api/src/routes/storage.ts +++ b/packages/api/src/routes/storage.ts @@ -1,10 +1,10 @@ import { Elysia, t } from "elysia"; -import { authUser } from "./auth"; +import { user } from "./auth"; import { getStorageFolder, getStorageFile } from "../s3"; import { StorageFolderSchema, StorageFileSchema } from "../types"; export const storage = new Elysia() - .use(authUser) + .use(user) .get( "/storage/folder", async ({ query }) => { diff --git a/packages/api/src/types.ts b/packages/api/src/types.ts index e769b8ad..fccb08f3 100644 --- a/packages/api/src/types.ts +++ b/packages/api/src/types.ts @@ -71,6 +71,7 @@ export const UserSchema = t.Object( { id: t.Number(), username: t.String(), + settingAutoRefetch: t.Boolean(), }, { $id: "#/components/schemas/User" }, ); diff --git a/packages/app/src/AuthContext.tsx b/packages/app/src/AuthContext.tsx index 4eb9e932..7c65fd72 100644 --- a/packages/app/src/AuthContext.tsx +++ b/packages/app/src/AuthContext.tsx @@ -6,6 +6,7 @@ import type { ReactNode } from "react"; import type { User } from "@superstreamer/api/client"; type AuthContextValue = { + token: string | null; setToken(value: string | null): void; user: User | null; api: ReturnType; @@ -62,11 +63,12 @@ export function AuthProvider({ children }: AuthProviderProps) { const value = useMemo(() => { return { + token, setToken, user, api, }; - }, [setToken, user, api]); + }, [token, setToken, user, api]); return {children}; } diff --git a/packages/app/src/components/auto-refresh/AutoRefreshContext.tsx b/packages/app/src/components/auto-refresh/AutoRefreshContext.tsx index 1595ecde..d684cf9f 100644 --- a/packages/app/src/components/auto-refresh/AutoRefreshContext.tsx +++ b/packages/app/src/components/auto-refresh/AutoRefreshContext.tsx @@ -1,3 +1,4 @@ +import { useUser } from "@/AuthContext"; import { createContext, useState, @@ -28,7 +29,8 @@ type AutoRefreshProviderProps = { const COUNTDOWN_INTERVAL = 5; export function AutoRefreshProvider({ children }: AutoRefreshProviderProps) { - const [active, setActive] = useState(false); + const user = useUser(); + const [active, setActive] = useState(user.settingAutoRefetch); const [countdown, setCountdown] = useState(COUNTDOWN_INTERVAL); const [listeners] = useState(() => new Set()); diff --git a/packages/app/src/pages/PlayerPage.tsx b/packages/app/src/pages/PlayerPage.tsx index 30b7d27a..a34eb0de 100644 --- a/packages/app/src/pages/PlayerPage.tsx +++ b/packages/app/src/pages/PlayerPage.tsx @@ -8,11 +8,13 @@ import { ResizablePanel, ResizablePanelGroup, } from "@/components/ui/resizable"; +import { useAuth } from "@/AuthContext"; export function PlayerPage() { const [schema, setSchema] = useState(); const [masterUrl, setMasterUrl] = useState(); const [error, setError] = useState(); + const { token } = useAuth(); useEffect(() => { fetch(`${window.__ENV__.PUBLIC_STITCHER_ENDPOINT}/swagger/json`) @@ -34,6 +36,7 @@ export function PlayerPage() { method: "post", headers: { "Content-Type": "application/json", + Authorization: `Bearer ${token}`, }, body, }, diff --git a/packages/shared/package.json b/packages/shared/package.json index a2fd5bd3..45e28ed6 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -6,16 +6,18 @@ "exports": { "./env": "./src/env.ts", "./typebox": "./src/typebox.ts", - "./lang": "./src/lang.ts" + "./lang": "./src/lang.ts", + "./auth": "./src/auth.ts" }, "scripts": { "lint": "eslint" }, "dependencies": { - "@elysiajs/swagger": "^1.1.5", "@sinclair/typebox": "^0.33.16", "dotenv": "^16.4.5", "elysia": "^1.1.20", + "@elysiajs/bearer": "^1.1.2", + "@elysiajs/jwt": "^1.1.1", "find-config": "^1.0.0", "iso-language-codes": "^2.0.0" }, diff --git a/packages/shared/src/auth.ts b/packages/shared/src/auth.ts new file mode 100644 index 00000000..f47d0cbd --- /dev/null +++ b/packages/shared/src/auth.ts @@ -0,0 +1,51 @@ +import { Elysia, t } from "elysia"; +import { jwt } from "@elysiajs/jwt"; +import bearer from "@elysiajs/bearer"; +import type { Static } from "elysia"; + +const userSchema = t.Union([ + // User tokens describe a user interacting with the API. + t.Object({ + type: t.Literal("user"), + id: t.Number(), + }), + // Service tokens, such as Stitcher. + t.Object({ type: t.Literal("service") }), +]); + +export type User = Static; + +export function bearerAuth(secret: string) { + const jwtUser = jwt({ + name: "jwtUser", + schema: userSchema, + secret, + }); + + const user = new Elysia() + .use(bearer()) + .use(jwtUser) + .derive({ as: "scoped" }, async ({ bearer, jwtUser, set }) => { + const token = await jwtUser.verify(bearer); + if (!token) { + set.status = 401; + throw new Error("Unauthorized"); + } + if (token.type === "user") { + return { + user: { type: "user", id: token.id }, + }; + } + if (token.type === "service") { + return { + user: { type: "service" }, + }; + } + throw new Error("Invalid token type"); + }); + + return { + jwtUser, + user, + }; +} diff --git a/packages/stitcher/src/app.ts b/packages/stitcher/src/app.ts new file mode 100644 index 00000000..6e564190 --- /dev/null +++ b/packages/stitcher/src/app.ts @@ -0,0 +1,26 @@ +import { Elysia } from "elysia"; +import { cors } from "@elysiajs/cors"; +import { swagger } from "@matvp91/elysia-swagger"; +import { env } from "./env"; +import { session } from "./routes/session"; + +export const app = new Elysia({ + // Serverless env does not support ahead of time compilation, + // let's turn it off. + aot: env.SERVERLESS ? false : true, +}) + .use(cors()) + .use( + swagger({ + scalarVersion: "1.25.50", + documentation: { + info: { + title: "Superstreamer Stitcher API", + version: "1.0.0", + description: + "Realtime playlist manipulator. Can be used for ad, bumper or other HLS interstitials insertion on-the-fly. Can apply filters to playlists.", + }, + }, + }), + ) + .use(session); diff --git a/packages/stitcher/src/index.ts b/packages/stitcher/src/index.ts index ca95daa4..7adb40c4 100644 --- a/packages/stitcher/src/index.ts +++ b/packages/stitcher/src/index.ts @@ -1,204 +1,21 @@ -import { Elysia, t } from "elysia"; -import { cors } from "@elysiajs/cors"; -import { swagger } from "@matvp91/elysia-swagger"; +import { app } from "./app"; import { env } from "./env"; -import { - createStarter, - getSession, - getStarter, - swapStarterForSession, -} from "./session"; -import { validateFilter } from "./filters"; -import { getMasterUrl } from "./url"; -import { - formatMasterPlaylist, - formatMediaPlaylist, - formatAssetList, -} from "./playlist"; -export const app = new Elysia({ - // Serverless env does not support ahead of time compilation, - // let's turn it off. - aot: env.SERVERLESS ? false : true, -}) - .use(cors()) - .use( - swagger({ - scalarVersion: "1.25.50", - documentation: { - info: { - title: "Superstreamer Stitcher API", - version: "1.0.0", - description: - "Realtime playlist manipulator. Can be used for ad, bumper or other HLS interstitials insertion on-the-fly. Can apply filters to playlists.", - }, - }, - }), - ) - .post( - "/session", - async ({ body }) => { - // This'll fail when uri is invalid. - getMasterUrl(body.uri); - - if (body.filter) { - // When we have a filter, validate it here first. There is no need to wait until we approach - // the master playlist. We can bail out early. - validateFilter(body.filter); - } - - const id = await createStarter(body); - return { - url: `${env.PUBLIC_STITCHER_ENDPOINT}/session/${id}/master.m3u8`, - }; - }, - { - detail: { - summary: "Create a session", - }, - body: t.Object({ - uri: t.String({ - description: - 'Reference to a master playlist, you can point to an asset with "asset://{uuid}" or as http(s).', - }), - interstitials: t.Optional( - t.Array( - t.Object({ - timeOffset: t.Number(), - uri: t.String(), - type: t.Optional(t.Union([t.Literal("ad"), t.Literal("bumper")])), - }), - { - description: "Manual HLS interstitial insertion.", - }, - ), - ), - filter: t.Optional( - t.Object( - { - resolution: t.Optional( - t.String({ - description: 'Filter on resolution, like "<= 720".', - }), - ), - }, - { - description: "Filter applies to master and media playlist.", - }, - ), - ), - vmap: t.Optional( - t.Object( - { - url: t.String(), - }, - { - description: - "Describes a VMAP, will transcode ads and insert interstitials on the fly.", - }, - ), - ), - expiry: t.Optional( - t.Number({ - description: - "In seconds, the session will no longer be available after this time.", - default: 3600, - minimum: 60, - }), - ), - }), - }, - ) - .get( - "/session/:sessionId/master.m3u8", - async ({ set, params }) => { - let session = await getSession(params.sessionId); - - if (!session) { - const starter = await getStarter(params.sessionId); - session = await swapStarterForSession(params.sessionId, starter); - } - - const playlist = await formatMasterPlaylist(session); - - set.headers["content-type"] = "application/x-mpegURL"; - - return playlist; - }, - { - detail: { - hide: true, - }, - params: t.Object({ - sessionId: t.String(), - }), - }, - ) - .get( - "/session/:sessionId/*", - async ({ set, params }) => { - const session = await getSession(params.sessionId); - if (!session) { - throw new Error(`Invalid session for "${params.sessionId}"`); - } - - const playlist = await formatMediaPlaylist(session, params["*"]); - set.headers["content-type"] = "application/x-mpegURL"; - return playlist; - }, - { - detail: { - hide: true, - }, - params: t.Object({ - sessionId: t.String(), - "*": t.String(), - }), - }, - ) - .get( - "/session/:sessionId/asset-list.json", - async ({ params, query }) => { - const session = await getSession(params.sessionId); - if (!session) { - throw new Error(`Invalid session for "${params.sessionId}"`); - } - - return await formatAssetList(session, query.startDate); - }, - { - detail: { - hide: true, - }, - params: t.Object({ - sessionId: t.String(), - }), - query: t.Object({ - startDate: t.String(), - _HLS_primary_id: t.Optional(t.String()), - }), - }, - ); - -// When we don't run on a serverless env, -// we'll start the server locally. -if (!env.SERVERLESS) { - app.on("stop", () => { - process.exit(0); - }); - - process - .on("beforeExit", app.stop) - .on("SIGINT", app.stop) - .on("SIGTERM", app.stop); - - app.listen( - { - port: env.PORT, - hostname: env.HOST, - }, - () => { - console.log(`Started stitcher on port ${env.PORT}`); - }, - ); -} +app.on("stop", () => { + process.exit(0); +}); + +process + .on("beforeExit", app.stop) + .on("SIGINT", app.stop) + .on("SIGTERM", app.stop); + +app.listen( + { + port: env.PORT, + hostname: env.HOST, + }, + () => { + console.log(`Started stitcher on port ${env.PORT}`); + }, +); diff --git a/packages/stitcher/src/routes/session.ts b/packages/stitcher/src/routes/session.ts new file mode 100644 index 00000000..b02275ee --- /dev/null +++ b/packages/stitcher/src/routes/session.ts @@ -0,0 +1,168 @@ +import { Elysia, t } from "elysia"; +import { bearerAuth } from "shared/auth"; +import { env } from "../env"; +import { + createStarter, + getSession, + getStarter, + swapStarterForSession, +} from "../session"; +import { validateFilter } from "../filters"; +import { getMasterUrl } from "../url"; +import { + formatMasterPlaylist, + formatMediaPlaylist, + formatAssetList, +} from "../playlist"; + +const { user } = bearerAuth(env.JWT_SECRET); + +export const session = new Elysia() + .group("", (app) => + app.use(user).post( + "/session", + async ({ body }) => { + // This'll fail when uri is invalid. + getMasterUrl(body.uri); + + if (body.filter) { + // When we have a filter, validate it here first. There is no need to wait until we approach + // the master playlist. We can bail out early. + validateFilter(body.filter); + } + + const id = await createStarter(body); + return { + url: `${env.PUBLIC_STITCHER_ENDPOINT}/session/${id}/master.m3u8`, + }; + }, + { + detail: { + summary: "Create a session", + }, + body: t.Object({ + uri: t.String({ + description: + 'Reference to a master playlist, you can point to an asset with "asset://{uuid}" or as http(s).', + }), + interstitials: t.Optional( + t.Array( + t.Object({ + timeOffset: t.Number(), + uri: t.String(), + type: t.Optional( + t.Union([t.Literal("ad"), t.Literal("bumper")]), + ), + }), + { + description: "Manual HLS interstitial insertion.", + }, + ), + ), + filter: t.Optional( + t.Object( + { + resolution: t.Optional( + t.String({ + description: 'Filter on resolution, like "<= 720".', + }), + ), + }, + { + description: "Filter applies to master and media playlist.", + }, + ), + ), + vmap: t.Optional( + t.Object( + { + url: t.String(), + }, + { + description: + "Describes a VMAP, will transcode ads and insert interstitials on the fly.", + }, + ), + ), + expiry: t.Optional( + t.Number({ + description: + "In seconds, the session will no longer be available after this time.", + default: 3600, + minimum: 60, + }), + ), + }), + }, + ), + ) + .get( + "/session/:sessionId/master.m3u8", + async ({ set, params }) => { + let session = await getSession(params.sessionId); + + if (!session) { + const starter = await getStarter(params.sessionId); + session = await swapStarterForSession(params.sessionId, starter); + } + + const playlist = await formatMasterPlaylist(session); + + set.headers["content-type"] = "application/x-mpegURL"; + + return playlist; + }, + { + detail: { + hide: true, + }, + params: t.Object({ + sessionId: t.String(), + }), + }, + ) + .get( + "/session/:sessionId/*", + async ({ set, params }) => { + const session = await getSession(params.sessionId); + if (!session) { + throw new Error(`Invalid session for "${params.sessionId}"`); + } + + const playlist = await formatMediaPlaylist(session, params["*"]); + set.headers["content-type"] = "application/x-mpegURL"; + return playlist; + }, + { + detail: { + hide: true, + }, + params: t.Object({ + sessionId: t.String(), + "*": t.String(), + }), + }, + ) + .get( + "/session/:sessionId/asset-list.json", + async ({ params, query }) => { + const session = await getSession(params.sessionId); + if (!session) { + throw new Error(`Invalid session for "${params.sessionId}"`); + } + + return await formatAssetList(session, query.startDate); + }, + { + detail: { + hide: true, + }, + params: t.Object({ + sessionId: t.String(), + }), + query: t.Object({ + startDate: t.String(), + _HLS_primary_id: t.Optional(t.String()), + }), + }, + ); diff --git a/packages/stitcher/src/serverless/cloudflare.ts b/packages/stitcher/src/serverless/cloudflare.ts index 8c8f73dd..4bae6732 100644 --- a/packages/stitcher/src/serverless/cloudflare.ts +++ b/packages/stitcher/src/serverless/cloudflare.ts @@ -3,7 +3,7 @@ export default { // Manually set the env before we load the app in memory. process.env = env; - const { app } = await import("../index"); + const { app } = await import("../app"); return await app.fetch(request); }, }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 376101dd..9d76588f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -59,9 +59,9 @@ importers: kysely: specifier: ^0.27.4 version: 0.27.4 - kysely-bun-sqlite: - specifier: ^0.3.2 - version: 0.3.2(kysely@0.27.4) + pg: + specifier: ^8.13.1 + version: 8.13.1 shared: specifier: workspace:* version: link:../shared @@ -69,6 +69,9 @@ importers: '@types/bun': specifier: latest version: 1.1.12 + '@types/pg': + specifier: ^8.11.10 + version: 8.11.10 config: specifier: workspace:* version: link:../config @@ -408,9 +411,12 @@ importers: packages/shared: dependencies: - '@elysiajs/swagger': - specifier: ^1.1.5 - version: 1.1.5(elysia@1.1.20(@sinclair/typebox@0.33.16)(openapi-types@12.1.3)(typescript@5.6.3)) + '@elysiajs/bearer': + specifier: ^1.1.2 + version: 1.1.2(elysia@1.1.20(@sinclair/typebox@0.33.16)(openapi-types@12.1.3)(typescript@5.6.3)) + '@elysiajs/jwt': + specifier: ^1.1.1 + version: 1.1.1(elysia@1.1.20(@sinclair/typebox@0.33.16)(openapi-types@12.1.3)(typescript@5.6.3)) '@sinclair/typebox': specifier: ^0.33.16 version: 0.33.16 @@ -1162,11 +1168,6 @@ packages: peerDependencies: elysia: '>= 1.1.0' - '@elysiajs/swagger@1.1.5': - resolution: {integrity: sha512-B+lzODhQ6lAMlBHMzf5QcniMCdw5b6dE8D5j4eTrzcgg4cm/jtOh6bYOUL1QHZJt4Zf/v5GvzzF/tJLHAFBq1Q==} - peerDependencies: - elysia: '>= 1.1.0' - '@esbuild-plugins/node-globals-polyfill@0.2.3': resolution: {integrity: sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==} peerDependencies: @@ -3080,6 +3081,9 @@ packages: '@types/parse-filepath@1.0.2': resolution: {integrity: sha512-CcyaQuvlpejsJR57RWxUR++E7zTKvbDDotZyiKaJsZdlbV8JfHgRYj4aZszEGKVLhiX0pbD8QeAuzEFUol4mRA==} + '@types/pg@8.11.10': + resolution: {integrity: sha512-LczQUW4dbOQzsH2RQ5qoeJ6qJPdrcM/DcMLoqWQkMLMsq83J5lAX3LXjdkWdpscFy67JSOWDnh7Ny/sPFykmkg==} + '@types/prop-types@15.7.12': resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} @@ -4205,12 +4209,6 @@ packages: kolorist@1.8.0: resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} - kysely-bun-sqlite@0.3.2: - resolution: {integrity: sha512-YucMcOGGxNCmlAnkvNfTKZX6Bk1sYsuVVXlQN/5ZUgerlq/J9/EuR3aMOOZ25ASLM7oMglSxfeQxkiw0+hrEOQ==} - engines: {bun: '>=1'} - peerDependencies: - kysely: ^0.27.2 - kysely@0.27.4: resolution: {integrity: sha512-dyNKv2KRvYOQPLCAOCjjQuCk4YFd33BvGdf/o5bC7FiW+BB6snA81Zt+2wT9QDFzKqxKa5rrOmvlK/anehCcgA==} engines: {node: '>=14.0.0'} @@ -4461,6 +4459,9 @@ packages: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} + obuf@1.1.2: + resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} + ohash@1.1.4: resolution: {integrity: sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g==} @@ -4581,6 +4582,48 @@ packages: perfect-debounce@1.0.0: resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + pg-cloudflare@1.1.1: + resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==} + + pg-connection-string@2.7.0: + resolution: {integrity: sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==} + + pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + pg-numeric@1.0.2: + resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==} + engines: {node: '>=4'} + + pg-pool@3.7.0: + resolution: {integrity: sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==} + peerDependencies: + pg: '>=8.0' + + pg-protocol@1.7.0: + resolution: {integrity: sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==} + + pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + + pg-types@4.0.2: + resolution: {integrity: sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==} + engines: {node: '>=10'} + + pg@8.13.1: + resolution: {integrity: sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ==} + engines: {node: '>= 8.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true + + pgpass@1.0.5: + resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + picocolors@1.0.1: resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} @@ -4651,6 +4694,41 @@ packages: resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} engines: {node: ^10 || ^12 || >=14} + postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + + postgres-array@3.0.2: + resolution: {integrity: sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==} + engines: {node: '>=12'} + + postgres-bytea@1.0.0: + resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} + engines: {node: '>=0.10.0'} + + postgres-bytea@3.0.0: + resolution: {integrity: sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==} + engines: {node: '>= 6'} + + postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + + postgres-date@2.1.0: + resolution: {integrity: sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==} + engines: {node: '>=12'} + + postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + + postgres-interval@3.0.0: + resolution: {integrity: sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==} + engines: {node: '>=12'} + + postgres-range@1.1.4: + resolution: {integrity: sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==} + preact@10.24.3: resolution: {integrity: sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==} @@ -5000,6 +5078,10 @@ packages: resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} engines: {node: '>=0.10.0'} + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} @@ -7035,13 +7117,6 @@ snapshots: elysia: 1.1.20(@sinclair/typebox@0.33.16)(openapi-types@12.1.3)(typescript@5.6.3) jose: 4.15.9 - '@elysiajs/swagger@1.1.5(elysia@1.1.20(@sinclair/typebox@0.33.16)(openapi-types@12.1.3)(typescript@5.6.3))': - dependencies: - '@scalar/types': 0.0.12 - elysia: 1.1.20(@sinclair/typebox@0.33.16)(openapi-types@12.1.3)(typescript@5.6.3) - openapi-types: 12.1.3 - pathe: 1.1.2 - '@esbuild-plugins/node-globals-polyfill@0.2.3(esbuild@0.17.19)': dependencies: esbuild: 0.17.19 @@ -9057,6 +9132,12 @@ snapshots: '@types/parse-filepath@1.0.2': {} + '@types/pg@8.11.10': + dependencies: + '@types/node': 22.1.0 + pg-protocol: 1.7.0 + pg-types: 4.0.2 + '@types/prop-types@15.7.12': {} '@types/react-dom@18.3.0': @@ -10276,11 +10357,6 @@ snapshots: kolorist@1.8.0: {} - kysely-bun-sqlite@0.3.2(kysely@0.27.4): - dependencies: - bun-types: 1.1.32 - kysely: 0.27.4 - kysely@0.27.4: {} levn@0.4.1: @@ -10537,6 +10613,8 @@ snapshots: object-hash@3.0.0: {} + obuf@1.1.2: {} + ohash@1.1.4: {} oniguruma-to-js@0.4.3: @@ -10645,6 +10723,53 @@ snapshots: perfect-debounce@1.0.0: {} + pg-cloudflare@1.1.1: + optional: true + + pg-connection-string@2.7.0: {} + + pg-int8@1.0.1: {} + + pg-numeric@1.0.2: {} + + pg-pool@3.7.0(pg@8.13.1): + dependencies: + pg: 8.13.1 + + pg-protocol@1.7.0: {} + + pg-types@2.2.0: + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.0 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + + pg-types@4.0.2: + dependencies: + pg-int8: 1.0.1 + pg-numeric: 1.0.2 + postgres-array: 3.0.2 + postgres-bytea: 3.0.0 + postgres-date: 2.1.0 + postgres-interval: 3.0.0 + postgres-range: 1.1.4 + + pg@8.13.1: + dependencies: + pg-connection-string: 2.7.0 + pg-pool: 3.7.0(pg@8.13.1) + pg-protocol: 1.7.0 + pg-types: 2.2.0 + pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.1.1 + + pgpass@1.0.5: + dependencies: + split2: 4.2.0 + picocolors@1.0.1: {} picocolors@1.1.1: {} @@ -10706,6 +10831,28 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + postgres-array@2.0.0: {} + + postgres-array@3.0.2: {} + + postgres-bytea@1.0.0: {} + + postgres-bytea@3.0.0: + dependencies: + obuf: 1.1.2 + + postgres-date@1.0.7: {} + + postgres-date@2.1.0: {} + + postgres-interval@1.2.0: + dependencies: + xtend: 4.0.2 + + postgres-interval@3.0.0: {} + + postgres-range@1.1.4: {} + preact@10.24.3: {} prelude-ls@1.2.1: {} @@ -11028,6 +11175,8 @@ snapshots: speakingurl@14.0.1: {} + split2@4.2.0: {} + sprintf-js@1.0.3: {} stacktracey@2.1.8: