Openstatus www.openstatus.dev

[POC] use honojs for api server (#296)

* wip:

* feat: add bun

* fix: package and type error

* fix: type errors temp

* ๐Ÿณ

* ๐Ÿ”ฅ open api

* ๐Ÿ”ฅ open api

* ๐Ÿ”ฅ monitors

* ๐Ÿ”ฅ incidents

* ๐Ÿ”ฅ incidents

* ๐Ÿš€ launch

* feat: add unkey api settings

* ๐Ÿš€ launch

* ๐Ÿš€ launch

* feat: update docs

* fix: default enum region

* fix: package

---------

Co-authored-by: Thibault Le Ouay <thibaultleouay@gmail.Com>

authored by

Maximilian Kaske
Thibault Le Ouay
and committed by
GitHub
2419aff2 df9ebbb5

+1477 -1024
+6
.dockerignore
··· 1 + **/.dockerignore 2 + **/node_modules 3 + **/npm-debug.log 4 + **/README.md 5 + **/.next 6 + **/.git
+3
.vscode/settings.json
··· 13 13 "typescript.enablePromptUseWorkspaceTsdk": true, 14 14 "[dotenv]": { 15 15 "editor.defaultFormatter": "foxundermoon.shell-format" 16 + }, 17 + "[dockerfile]": { 18 + "editor.defaultFormatter": "ms-azuretools.vscode-docker" 16 19 } 17 20 }
+2 -2
README.md
··· 1 1 <p align="center" style="margin-top: 120px"> 2 2 3 - <h3 align="center">The Open-source Uptime Monitoring with Status Page 3 + <h3 align="center">The Open-Source Uptime Monitoring with Status Page 4 4 </h3> 5 5 6 6 <p align="center"> 7 - The BetterUptime Open Source Alternative. 7 + The Open-Source Serverless monitoring platform. 8 8 <br /> 9 9 <a href="https://www.openstatus.dev"><strong>Learn more ยป</strong></a> 10 10 <br />
-2
apps/api/.env.example
··· 1 - DATABASE_URL=http://127.0.0.1:8080 2 - DATABASE_AUTH_TOKEN=any-token
-38
apps/api/.gitignore
··· 1 - # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 - 3 - # dependencies 4 - /node_modules 5 - /.pnp 6 - .pnp.js 7 - 8 - # testing 9 - /coverage 10 - 11 - # next.js 12 - /.next/ 13 - /out/ 14 - 15 - # production 16 - /build 17 - 18 - # misc 19 - .DS_Store 20 - *.pem 21 - 22 - # debug 23 - npm-debug.log* 24 - yarn-debug.log* 25 - yarn-error.log* 26 - 27 - # local env files 28 - .env*.local 29 - 30 - # vercel 31 - .vercel 32 - 33 - # typescript 34 - *.tsbuildinfo 35 - next-env.d.ts 36 - 37 - # generated on build time 38 - /public/swagger.json
-46
apps/api/README.md
··· 1 - This is a [Next.js](https://nextjs.org/) project bootstrapped with 2 - [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 3 - 4 - ## Getting Started 5 - 6 - First, run the development server: 7 - 8 - ```bash 9 - npm run dev 10 - # or 11 - yarn dev 12 - # or 13 - pnpm dev 14 - ``` 15 - 16 - Open [http://localhost:3000](http://localhost:3000) with your browser to see the 17 - result. 18 - 19 - You can start editing the page by modifying `app/page.tsx`. The page 20 - auto-updates as you edit the file. 21 - 22 - This project uses 23 - [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to 24 - automatically optimize and load Inter, a custom Google Font. 25 - 26 - ## Learn More 27 - 28 - To learn more about Next.js, take a look at the following resources: 29 - 30 - - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js 31 - features and API. 32 - - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 33 - 34 - You can check out 35 - [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your 36 - feedback and contributions are welcome! 37 - 38 - ## Deploy on Vercel 39 - 40 - The easiest way to deploy your Next.js app is to use the 41 - [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) 42 - from the creators of Next.js. 43 - 44 - Check out our 45 - [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more 46 - details.
-19
apps/api/next-swagger-doc.json
··· 1 - { 2 - "apiFolder": "src/app", 3 - "definition": { 4 - "openapi": "3.0.0", 5 - "info": { 6 - "title": "OpenStatus API", 7 - "version": "1.0" 8 - }, 9 - "components": { 10 - "securitySchemes": { 11 - "ApiKeyAuth": { 12 - "type": "apiKey", 13 - "name": "x-openstatus-key" 14 - } 15 - } 16 - }, 17 - "security": [{ "ApiKeyAuth": [] }] 18 - } 19 - }
-4
apps/api/next.config.js
··· 1 - /** @type {import('next').NextConfig} */ 2 - const nextConfig = {} 3 - 4 - module.exports = nextConfig
-27
apps/api/package.json
··· 1 - { 2 - "name": "@openstatus/api-server", 3 - "version": "0.1.0", 4 - "private": true, 5 - "scripts": { 6 - "dev": "next dev", 7 - "build": "pnpm gen:swagger && next build", 8 - "start": "next start", 9 - "lint": "next lint", 10 - "gen:swagger": "next-swagger-doc-cli next-swagger-doc.json" 11 - }, 12 - "dependencies": { 13 - "@openstatus/db": "workspace:^", 14 - "next": "13.4.12", 15 - "next-swagger-doc": "0.4.0", 16 - "react": "18.2.0", 17 - "react-dom": "18.2.0", 18 - "zod": "3.21.4" 19 - }, 20 - "devDependencies": { 21 - "@types/node": "20.5.9", 22 - "@types/react": "18.2.12", 23 - "@types/react-dom": "18.2.5", 24 - "tsconfig": "workspace:*", 25 - "typescript": "5.1.6" 26 - } 27 - }
-6
apps/api/postcss.config.js
··· 1 - module.exports = { 2 - plugins: { 3 - tailwindcss: {}, 4 - autoprefixer: {}, 5 - }, 6 - };
apps/api/public/.gitkeep

This is a binary file and will not be displayed.

-16
apps/api/src/app/layout.tsx
··· 1 - export const metadata = { 2 - title: "Next.js", 3 - description: "Generated by Next.js", 4 - }; 5 - 6 - export default function RootLayout({ 7 - children, 8 - }: { 9 - children: React.ReactNode; 10 - }) { 11 - return ( 12 - <html lang="en"> 13 - <body>{children}</body> 14 - </html> 15 - ); 16 - }
-189
apps/api/src/app/v1/monitors/[id]/route.ts
··· 1 - import { db, eq, schema } from "@openstatus/db"; 2 - 3 - const { insertMonitorSchema, monitor } = schema; 4 - 5 - /** 6 - * @swagger 7 - * /v1/monitors/{id}: 8 - * get: 9 - * summmary: Returns the monitor 10 - * parameters: 11 - * - in: path 12 - * name: id 13 - * schema: 14 - * type: integer 15 - * required: true 16 - * description: ID of the monitors to get 17 - * tags: 18 - * - Monitors 19 - * responses: 20 - * 200: 21 - * description: The monitor 22 - * 401: 23 - * description: Unauthorized 24 - * 404: 25 - * description: No monitors were found 26 - * 500: 27 - * description: Bad Request 28 - */ 29 - export async function GET( 30 - req: Request, 31 - { params }: { params: { id: string } }, 32 - ) { 33 - try { 34 - const workspaceId = Number(req.headers.get("x-workspace-id")); 35 - const id = Number(params.id); 36 - const _monitor = await db 37 - .select() 38 - .from(monitor) 39 - .where(eq(monitor.id, id)) 40 - .get(); 41 - 42 - if (!_monitor) { 43 - return new Response("Not Found", { status: 404 }); 44 - } 45 - 46 - if (workspaceId !== _monitor.workspaceId) { 47 - return new Response("Unauthorized", { status: 401 }); 48 - } 49 - 50 - return new Response(JSON.stringify(_monitor), { status: 200 }); 51 - } catch (e) { 52 - console.error(e); 53 - 54 - return new Response("Internal Error", { status: 500 }); 55 - } 56 - } 57 - 58 - /** 59 - * @swagger 60 - * /v1/monitors/{id}: 61 - * put: 62 - * summmary: Update the monitor 63 - * parameters: 64 - * - in: path 65 - * name: id 66 - * schema: 67 - * type: integer 68 - * required: true 69 - * description: ID of the monitors to get 70 - * tags: 71 - * - Monitors 72 - * responses: 73 - * 200: 74 - * description: The monitor 75 - * 401: 76 - * description: Unauthorized 77 - * 404: 78 - * description: No monitors were found 79 - * 500: 80 - * description: Bad Request 81 - */ 82 - export async function PUT( 83 - req: Request, 84 - { params }: { params: { id: string } }, 85 - ) { 86 - try { 87 - const workspaceId = Number(req.headers.get("x-workspace-id")); 88 - const id = Number(params.id); 89 - const _monitor = await db 90 - .select() 91 - .from(monitor) 92 - .where(eq(monitor.id, id)) 93 - .get(); 94 - 95 - if (!_monitor) { 96 - return new Response("Not Found", { status: 404 }); 97 - } 98 - 99 - if (workspaceId !== _monitor.workspaceId) { 100 - return new Response("Unauthorized", { status: 401 }); 101 - } 102 - 103 - const json = await req.json(); 104 - const _valid = insertMonitorSchema 105 - .partial() 106 - .safeParse({ ...json, workspaceId }); 107 - 108 - if (!_valid.success) { 109 - return new Response(JSON.stringify(_valid.error), { status: 400 }); 110 - } 111 - 112 - const { data } = _valid; 113 - const _newMonitor = await db 114 - .update(monitor) 115 - .set({ 116 - ..._monitor, 117 - ...data, 118 - regions: (data || _monitor).regions?.join(","), // mapping array to string 119 - headers: 120 - data.headers || _monitor.headers 121 - ? JSON.parse(String((data || _monitor).headers)) 122 - : undefined, // mapping JSON to string 123 - }) 124 - .where(eq(monitor.id, id)) 125 - .returning() 126 - .get(); 127 - 128 - return new Response(JSON.stringify(_newMonitor), { status: 200 }); 129 - } catch (e) { 130 - console.error(e); 131 - 132 - return new Response("Internal Error", { status: 500 }); 133 - } 134 - } 135 - 136 - /** 137 - * @swagger 138 - * /v1/monitors/{id}: 139 - * delete: 140 - * summmary: Update the monitor 141 - * parameters: 142 - * - in: path 143 - * name: id 144 - * schema: 145 - * type: integer 146 - * required: true 147 - * description: ID of the monitors to delete 148 - * tags: 149 - * - Monitors 150 - * responses: 151 - * 200: 152 - * description: The monitor 153 - * 401: 154 - * description: Unauthorized 155 - * 404: 156 - * description: No monitors were found 157 - * 500: 158 - * description: Bad Request 159 - */ 160 - export async function DELETE( 161 - req: Request, 162 - { params }: { params: { id: string } }, 163 - ) { 164 - try { 165 - const workspaceId = Number(req.headers.get("x-workspace-id")); 166 - const id = Number(params.id); 167 - const _monitor = await db 168 - .select() 169 - .from(monitor) 170 - .where(eq(monitor.id, id)) 171 - .get(); 172 - 173 - if (!_monitor) { 174 - return new Response("Not Found", { status: 404 }); 175 - } 176 - 177 - if (workspaceId !== _monitor.workspaceId) { 178 - return new Response("Unauthorized", { status: 401 }); 179 - } 180 - 181 - await db.delete(monitor).where(eq(monitor.id, id)).run(); 182 - 183 - return new Response("Deleted", { status: 200 }); 184 - } catch (e) { 185 - console.log(e); 186 - 187 - return new Response("Internal Error", { status: 500 }); 188 - } 189 - }
-55
apps/api/src/app/v1/monitors/route.ts
··· 1 - import { db } from "@openstatus/db"; 2 - import { insertMonitorSchema, monitor } from "@openstatus/db/src/schema"; 3 - 4 - // TODO: make sure that only valid frequency values (based on plan) are allowed 5 - 6 - /** 7 - * @swagger 8 - * /v1/monitors: 9 - * post: 10 - * summmary: Returns the monitor 11 - * parameters: 12 - * - in: path 13 - * name: id 14 - * schema: 15 - * type: integer 16 - * required: true 17 - * description: ID of the monitors to get 18 - * tags: 19 - * - Monitors 20 - * responses: 21 - * 200: 22 - * description: The monitor 23 - * 404: 24 - * description: No monitors were found 25 - * 500: 26 - * description: Bad Request 27 - */ 28 - export async function POST(req: Request) { 29 - try { 30 - const workspaceId = Number(req.headers.get("x-workspace-id")); 31 - const json = await req.json(); 32 - const _valid = insertMonitorSchema.safeParse({ ...json, workspaceId }); 33 - 34 - if (!_valid.success) { 35 - return new Response(JSON.stringify(_valid.error), { status: 400 }); 36 - } 37 - 38 - const { data } = _valid; 39 - const _monitor = await db 40 - .insert(monitor) 41 - .values({ 42 - ...data, 43 - regions: data.regions?.join(","), // mapping array to string 44 - headers: data.headers ? JSON.parse(String(data.headers)) : undefined, // mapping JSON to string 45 - }) 46 - .returning() 47 - .get(); 48 - 49 - return new Response(JSON.stringify(_monitor), { status: 200 }); 50 - } catch (e) { 51 - console.error(e); 52 - 53 - return new Response("Internal Error", { status: 500 }); 54 - } 55 - }
-168
apps/api/src/app/v1/pages/[id]/route.ts
··· 1 - import { db, eq } from "@openstatus/db"; 2 - import { insertPageSchemaWithMonitors, page } from "@openstatus/db/src/schema"; 3 - 4 - /** 5 - * @swagger 6 - * /v1/pages/{id}: 7 - * get: 8 - * summmary: Returns the status page 9 - * parameters: 10 - * - in: path 11 - * name: id 12 - * schema: 13 - * type: integer 14 - * required: true 15 - * description: ID of the page to get 16 - * tags: 17 - * - Status pages 18 - * responses: 19 - * 200: 20 - * description: The page 21 - * 401: 22 - * description: Unauthorized 23 - * 404: 24 - * description: No page were found 25 - * 500: 26 - * description: Bad Request 27 - */ 28 - export async function GET( 29 - req: Request, 30 - { params }: { params: { id: string } }, 31 - ) { 32 - try { 33 - const workspaceId = Number(req.headers.get("x-workspace-id")); 34 - const id = Number(params.id); 35 - const _page = await db.select().from(page).where(eq(page.id, id)).get(); 36 - 37 - if (!_page) { 38 - return new Response("Not Found", { status: 404 }); 39 - } 40 - 41 - if (workspaceId !== _page.workspaceId) { 42 - return new Response("Unauthorized", { status: 401 }); 43 - } 44 - 45 - return new Response(JSON.stringify(_page), { status: 200 }); 46 - } catch (e) { 47 - console.error(e); 48 - 49 - return new Response("Internal Error", { status: 500 }); 50 - } 51 - } 52 - 53 - /** 54 - * @swagger 55 - * /v1/pages/{id}: 56 - * put: 57 - * summmary: Returns the status page 58 - * parameters: 59 - * - in: path 60 - * name: id 61 - * schema: 62 - * type: integer 63 - * required: true 64 - * description: ID of the page to get 65 - * tags: 66 - * - Status pages 67 - * responses: 68 - * 200: 69 - * description: The page 70 - * 401: 71 - * description: Unauthorized 72 - * 404: 73 - * description: No page were found 74 - * 500: 75 - * description: Bad Request 76 - */ 77 - export async function PUT( 78 - req: Request, 79 - { params }: { params: { id: string } }, 80 - ) { 81 - try { 82 - const workspaceId = Number(req.headers.get("x-workspace-id")); 83 - const id = Number(params.id); 84 - const _page = await db.select().from(page).where(eq(page.id, id)).get(); 85 - 86 - if (!_page) { 87 - return new Response("Not Found", { status: 404 }); 88 - } 89 - 90 - if (workspaceId !== _page.workspaceId) { 91 - return new Response("Unauthorized", { status: 401 }); 92 - } 93 - 94 - const json = await req.json(); 95 - const _valid = insertPageSchemaWithMonitors.safeParse({ 96 - ...json, 97 - workspaceId, 98 - }); 99 - 100 - if (!_valid.success) { 101 - return new Response(JSON.stringify(_valid.error), { status: 400 }); 102 - } 103 - 104 - const { data } = _valid; 105 - const _newPage = await db 106 - .update(page) 107 - .set(data) 108 - .where(eq(page.id, id)) 109 - .returning() 110 - .get(); 111 - 112 - return new Response(JSON.stringify(_newPage), { status: 200 }); 113 - } catch (e) { 114 - console.error(e); 115 - return new Response("Internal Error", { status: 500 }); 116 - } 117 - } 118 - 119 - /** 120 - * @swagger 121 - * /v1/pages/{id}: 122 - * delete: 123 - * summmary: Returns the status page 124 - * parameters: 125 - * - in: path 126 - * name: id 127 - * schema: 128 - * type: integer 129 - * required: true 130 - * description: ID of the page to get 131 - * tags: 132 - * - Status pages 133 - * responses: 134 - * 200: 135 - * description: The page 136 - * 401: 137 - * description: Unauthorized 138 - * 404: 139 - * description: No page were found 140 - * 500: 141 - * description: Bad Request 142 - */ 143 - export async function DELETE( 144 - req: Request, 145 - { params }: { params: { id: string } }, 146 - ) { 147 - try { 148 - const workspaceId = Number(req.headers.get("x-workspace-id")); 149 - const id = Number(params.id); 150 - const _page = await db.select().from(page).where(eq(page.id, id)).get(); 151 - 152 - if (!_page) { 153 - return new Response("Not Found", { status: 404 }); 154 - } 155 - 156 - if (workspaceId !== _page.workspaceId) { 157 - return new Response("Unauthorized", { status: 401 }); 158 - } 159 - 160 - await db.delete(page).where(eq(page.id, id)).run(); 161 - 162 - return new Response("Deleted", { status: 200 }); 163 - } catch (e) { 164 - console.error(e); 165 - 166 - return new Response("Internal Error", { status: 500 }); 167 - } 168 - }
-63
apps/api/src/app/v1/pages/route.ts
··· 1 - import { db } from "@openstatus/db"; 2 - import { insertPageSchemaWithMonitors, page } from "@openstatus/db/src/schema"; 3 - 4 - /** 5 - * @swagger 6 - * /v1/status-pages: 7 - * post: 8 - * summmary: Create a new status page 9 - * parameters: 10 - * - in: path 11 - * name: id 12 - * schema: 13 - * type: integer 14 - * required: true 15 - * description: ID of the monitors to get 16 - * tags: 17 - * - Status pages 18 - * security: 19 - * - ApiKeyAuth: [] 20 - * requestBody: 21 - * description: Create a new booking related to one of your event-types 22 - * required: true 23 - * content: 24 - * application/json: 25 - * schema: 26 - * type: object 27 - * required: 28 - * - name 29 - * properties: 30 - * name: 31 - * type: string 32 - * example: "My Status Page" 33 - * responses: 34 - * 200: 35 - * description: The monitor 36 - * 404: 37 - * description: No monitors were found 38 - * 500: 39 - * description: Bad Request 40 - */ 41 - export async function POST(req: Request) { 42 - try { 43 - const workspaceId = Number(req.headers.get("x-workspace-id")); 44 - const json = await req.json(); 45 - const _valid = insertPageSchemaWithMonitors.safeParse({ 46 - ...json, 47 - workspaceId, 48 - }); 49 - 50 - if (!_valid.success) { 51 - return new Response(JSON.stringify(_valid.error), { status: 400 }); 52 - } 53 - 54 - const { data } = _valid; 55 - const _page = await db.insert(page).values(data).returning().get(); 56 - 57 - return new Response(JSON.stringify(_page), { status: 200 }); 58 - } catch (e) { 59 - console.error(e); 60 - 61 - return new Response("Internal Error", { status: 500 }); 62 - } 63 - }
-7
apps/api/src/lib/schema.ts
··· 1 - import * as z from "zod"; 2 - 3 - export const keySchema = z.object({ 4 - valid: z.boolean(), 5 - ownerId: z.string(), 6 - // extend later with ratelimit,... 7 - });
-44
apps/api/src/middleware.ts
··· 1 - import { NextResponse } from "next/server"; 2 - import type { NextRequest } from "next/server"; 3 - 4 - import { keySchema } from "./lib/schema"; 5 - 6 - export async function middleware(request: NextRequest) { 7 - const auth = request.headers.get("x-openstatus-key"); 8 - 9 - if (!auth) { 10 - return new Response("Unauthorized", { status: 401 }); 11 - } 12 - 13 - const newHeaders = new Headers(request.headers); 14 - 15 - if (process.env.NODE_ENV === "production") { 16 - const res = await fetch("https://api.unkey.dev/v1/keys/verify", { 17 - method: "POST", 18 - headers: new Headers({ 19 - "Content-Type": "application/json", 20 - }), 21 - body: JSON.stringify({ key: auth }), 22 - }); 23 - 24 - const json = await res.json(); 25 - const key = keySchema.safeParse(json); 26 - 27 - if (!key.success) { 28 - return new Response("Bad Request", { status: 400 }); 29 - } 30 - 31 - if (!key.data.valid) { 32 - return new Response("Unauthorized", { status: 401 }); 33 - } 34 - 35 - newHeaders.set("x-workspace-id", key.data.ownerId); 36 - } else { 37 - // REMINDER: change the id to your workspace id 38 - newHeaders.set("x-workspace-id", "1"); 39 - } 40 - 41 - return NextResponse.next({ request: { headers: newHeaders } }); 42 - } 43 - 44 - export const config = { matcher: ["/v1/(.*)"] };
-7
apps/api/tailwind.config.ts
··· 1 - module.exports = { 2 - content: ["./src/**/*.{tsx,ts,mdx,md}"], 3 - theme: { 4 - extend: {}, 5 - }, 6 - plugins: [], 7 - };
-14
apps/api/tsconfig.json
··· 1 - { 2 - "extends": "tsconfig/nextjs.json", 3 - "compilerOptions": { 4 - "baseUrl": ".", 5 - "paths": { 6 - "@/*": ["./src/*"] 7 - }, 8 - "plugins": [{ "name": "next" }], 9 - "strictNullChecks": true, 10 - "strict": true 11 - }, 12 - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 13 - "exclude": ["node_modules"] 14 - }
+1 -1
apps/docs/package.json
··· 20 20 "swagger-ui-react": "5.6.2" 21 21 }, 22 22 "devDependencies": { 23 + "@openstatus/tsconfig": "workspace:*", 23 24 "@types/node": "18.11.10", 24 25 "@types/swagger-ui-react": "4.18.0", 25 - "tsconfig": "workspace:*", 26 26 "typescript": "5.1.6" 27 27 } 28 28 }
+2 -2
apps/docs/pages/rest-api/_meta.json
··· 1 1 { 2 - "auth": "Auth", 3 - "openapi": "OpenAPI" 2 + "auth": "Auth Token", 3 + "openapi": "OpenAPI Specs" 4 4 }
+24 -2
apps/docs/pages/rest-api/auth.mdx
··· 1 1 import { Callout } from "nextra/components"; 2 2 3 - # Authentication 3 + # Auth Token 4 + 5 + In your Settings, you can create and later revoke your API token. Once created, 6 + it will be shown only once so keep it save. 7 + 8 + On every request to `https://api.openstatus.dev/v1`, you'll have to attach the 9 + token to the header. 4 10 5 - <Callout emoji="๐Ÿšง">Work in progress</Callout> 11 + ```ts 12 + const monitors = await fetch(`https://api.openstatus.dev/v1/monitors`, { 13 + method: "GET", 14 + headers: new Headers({ 15 + "x-openstatus-key": "os_xxxxxxxxx", 16 + }), 17 + }); 18 + ``` 19 + 20 + Use the above snippet to try it out. 21 + 22 + Learn more about [all the endpoints](/rest-api/openapi) we provide. 23 + 24 + We currently do not have an SDK to make the best out of it. Any contributions 25 + are welcome. 26 + 27 + The API infrastructure is powered by [Unkey](https://unkey.dev).
+1 -1
apps/docs/pages/rest-api/openapi.mdx
··· 3 3 import { useData } from "nextra/data"; 4 4 5 5 export const getStaticProps = async ({ params }) => { 6 - const res = await fetch("https://api.openstatus.dev/swagger.json"); 6 + const res = await fetch("https://api.openstatus.dev/openapi"); 7 7 const spec = await res.json(); 8 8 return { 9 9 props: {
+1 -1
apps/docs/tsconfig.json
··· 1 1 { 2 - "extends": "tsconfig/nextjs.json", 2 + "extends": "@openstatus/tsconfig/nextjs.json", 3 3 "compilerOptions": { 4 4 "baseUrl": ".", 5 5 "plugins": [{ "name": "next" }],
+4
apps/server/.env.example
··· 1 + DATABASE_URL=http://127.0.0.1:8080 2 + DATABASE_AUTH_TOKEN= 3 + NODE_ENV=production 4 + UNKEY_TOKEN=
+1
apps/server/.gitignore
··· 1 + node_modules
+14
apps/server/Dockerfile
··· 1 + FROM oven/bun 2 + 3 + WORKDIR /app 4 + 5 + # Copy project 6 + COPY . . 7 + 8 + RUN bun install 9 + 10 + EXPOSE 3000 11 + 12 + WORKDIR /app/apps/server 13 + 14 + CMD ["bun", "start"]
+14
apps/server/README.md
··· 1 + # OpenStatus Server 2 + 3 + ## Tech 4 + 5 + - Bun 6 + - HonoJS 7 + 8 + ## Deploy 9 + 10 + From root 11 + 12 + ```bash 13 + flyctl deploy --config apps/server/fly.toml --dockerfile apps/server/Dockerfile 14 + ```
+18
apps/server/fly.toml
··· 1 + # fly.toml app configuration file generated for openstatus-api on 2023-09-13T17:29:05+02:00 2 + # 3 + # See https://fly.io/docs/reference/configuration/ for information about how to use this file. 4 + # 5 + 6 + app = "openstatus-api" 7 + primary_region = "ams" 8 + 9 + [build] 10 + dockerfile = "./Dockerfile" 11 + 12 + [http_service] 13 + internal_port = 3000 14 + force_https = true 15 + auto_stop_machines = true 16 + auto_start_machines = true 17 + min_machines_running = 0 18 + processes = ["app"]
+21
apps/server/package.json
··· 1 + { 2 + "name": "@openstatus/server", 3 + "version": "0.0.1", 4 + "description": "", 5 + "type": "module", 6 + "main": "src/index.ts", 7 + "scripts": { 8 + "dev": "bun run --hot src/index.ts", 9 + "start": "bun run src/index.ts" 10 + }, 11 + "dependencies": { 12 + "@hono/zod-openapi": "0.4.0", 13 + "@openstatus/db": "*", 14 + "@openstatus/tinybird": "*", 15 + "@unkey/api": "0.6.22", 16 + "hono": "3.6.1" 17 + }, 18 + "devDependencies": { 19 + "@openstatus/tsconfig": "workspace:*" 20 + } 21 + }
+303
apps/server/src/incident.ts
··· 1 + import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi"; 2 + 3 + import { and, db, eq } from "@openstatus/db"; 4 + import { 5 + availableStatus, 6 + incident, 7 + incidentUpdate, 8 + } from "@openstatus/db/src/schema"; 9 + 10 + import { ErrorSchema } from "./shared"; 11 + 12 + const incidentApi = new OpenAPIHono(); 13 + 14 + const ParamsSchema = z.object({ 15 + id: z 16 + .string() 17 + .min(1) 18 + .openapi({ 19 + param: { 20 + name: "id", 21 + in: "path", 22 + }, 23 + description: "The id of the incident", 24 + example: "1", 25 + }), 26 + }); 27 + 28 + const incidentUpdateSchema = z.object({ 29 + status: z.enum(availableStatus).openapi({ 30 + description: "The status of the update", 31 + }), 32 + date: z.string().openapi({ 33 + description: "The date of the update in ISO 8601 format", 34 + }), 35 + message: z.string().openapi({ 36 + description: "The message of the update", 37 + }), 38 + }); 39 + 40 + const incidentSchema = z.object({ 41 + title: z.string().openapi({ 42 + example: "Documenso", 43 + description: "The title of the incident", 44 + }), 45 + status: z.enum(availableStatus).openapi({ 46 + description: "The current status of the incident", 47 + }), 48 + }); 49 + 50 + const getAllRoute = createRoute({ 51 + method: "get", 52 + path: "/", 53 + request: {}, 54 + responses: { 55 + 200: { 56 + content: { 57 + "application/json": { 58 + schema: z.array(incidentSchema), 59 + }, 60 + }, 61 + description: "Get all incidents", 62 + }, 63 + 400: { 64 + content: { 65 + "application/json": { 66 + schema: ErrorSchema, 67 + }, 68 + }, 69 + description: "Returns an error", 70 + }, 71 + }, 72 + }); 73 + incidentApi.openapi(getAllRoute, async (c) => { 74 + const workspaceId = Number(c.req.header("x-workspace-id")); 75 + 76 + const _incidents = await db 77 + .select() 78 + .from(incident) 79 + .where(eq(incident.workspaceId, workspaceId)) 80 + .all(); 81 + 82 + if (!_incidents) return c.jsonT({ code: 404, message: "Not Found" }); 83 + 84 + const data = z.array(incidentSchema).parse(_incidents); 85 + 86 + return c.jsonT(data); 87 + }); 88 + 89 + const getRoute = createRoute({ 90 + method: "get", 91 + path: "/:id", 92 + request: { 93 + params: ParamsSchema, 94 + }, 95 + responses: { 96 + 200: { 97 + content: { 98 + "application/json": { 99 + schema: incidentSchema, 100 + }, 101 + }, 102 + description: "Get all incidents", 103 + }, 104 + 400: { 105 + content: { 106 + "application/json": { 107 + schema: ErrorSchema, 108 + }, 109 + }, 110 + description: "Returns an error", 111 + }, 112 + }, 113 + }); 114 + incidentApi.openapi(getRoute, async (c) => { 115 + const workspaceId = Number(c.req.header("x-workspace-id")); 116 + const { id } = c.req.valid("param"); 117 + 118 + const incidentId = Number(id); 119 + const _incident = await db 120 + .select() 121 + .from(incident) 122 + .where(eq(incident.id, incidentId)) 123 + .get(); 124 + 125 + if (!_incident) return c.jsonT({ code: 404, message: "Not Found" }); 126 + 127 + if (workspaceId !== _incident.workspaceId) 128 + return c.jsonT({ code: 401, message: "Unauthorized" }); 129 + 130 + const data = incidentSchema.parse(_incident); 131 + 132 + return c.jsonT(data); 133 + }); 134 + 135 + const postRoute = createRoute({ 136 + method: "post", 137 + path: "/", 138 + request: { 139 + body: { 140 + description: "The incident to create", 141 + content: { 142 + "application/json": { 143 + schema: incidentSchema, 144 + }, 145 + }, 146 + }, 147 + }, 148 + responses: { 149 + 200: { 150 + content: { 151 + "application/json": { 152 + schema: incidentSchema, 153 + }, 154 + }, 155 + description: "Create a monitor", 156 + }, 157 + 400: { 158 + content: { 159 + "application/json": { 160 + schema: ErrorSchema, 161 + }, 162 + }, 163 + description: "Returns an error", 164 + }, 165 + }, 166 + }); 167 + 168 + incidentApi.openapi(postRoute, async (c) => { 169 + const input = c.req.valid("json"); 170 + const workspaceId = Number(c.req.header("x-workspace-id")); 171 + 172 + const _newIncident = await db 173 + .insert(incident) 174 + .values({ 175 + ...input, 176 + workspaceId: workspaceId, 177 + }) 178 + .returning() 179 + .get(); 180 + 181 + const data = incidentSchema.parse(_newIncident); 182 + 183 + return c.jsonT(data); 184 + }); 185 + 186 + const deleteRoute = createRoute({ 187 + method: "delete", 188 + path: "/:id", 189 + request: { 190 + params: ParamsSchema, 191 + }, 192 + responses: { 193 + 200: { 194 + content: { 195 + "application/json": { 196 + schema: z.object({ 197 + message: z.string().openapi({ 198 + example: "Deleted", 199 + }), 200 + }), 201 + }, 202 + }, 203 + description: "Delete the incident", 204 + }, 205 + 400: { 206 + content: { 207 + "application/json": { 208 + schema: ErrorSchema, 209 + }, 210 + }, 211 + description: "Returns an error", 212 + }, 213 + }, 214 + }); 215 + 216 + incidentApi.openapi(deleteRoute, async (c) => { 217 + const workspaceId = Number(c.req.header("x-workspace-id")); 218 + const { id } = c.req.valid("param"); 219 + 220 + const incidentId = Number(id); 221 + const _incident = await db 222 + .select() 223 + .from(incident) 224 + .where(eq(incident.id, incidentId)) 225 + .get(); 226 + 227 + if (!_incident) return c.jsonT({ code: 404, message: "Not Found" }); 228 + 229 + if (workspaceId !== _incident.workspaceId) 230 + return c.jsonT({ code: 401, message: "Unauthorized" }); 231 + 232 + await db.delete(incident).where(eq(incident.id, incidentId)).run(); 233 + return c.jsonT({ message: "Deleted" }); 234 + }); 235 + 236 + const postRouteUpdate = createRoute({ 237 + method: "post", 238 + path: "/:id/update", 239 + request: { 240 + params: ParamsSchema, 241 + body: { 242 + description: "The incident to create", 243 + content: { 244 + "application/json": { 245 + schema: incidentUpdateSchema, 246 + }, 247 + }, 248 + }, 249 + }, 250 + responses: { 251 + 200: { 252 + content: { 253 + "application/json": { 254 + schema: incidentUpdateSchema, 255 + }, 256 + }, 257 + description: "Create a monitor", 258 + }, 259 + 400: { 260 + content: { 261 + "application/json": { 262 + schema: ErrorSchema, 263 + }, 264 + }, 265 + description: "Returns an error", 266 + }, 267 + }, 268 + }); 269 + 270 + incidentApi.openapi(postRouteUpdate, async (c) => { 271 + const input = c.req.valid("json"); 272 + const { id } = c.req.valid("param"); 273 + const workspaceId = Number(c.req.header("x-workspace-id")); 274 + 275 + const incidentId = Number(id); 276 + const _incident = await db 277 + .select() 278 + .from(incident) 279 + .where( 280 + and(eq(incident.id, incidentId), eq(incident.workspaceId, workspaceId)), 281 + ) 282 + .get(); 283 + 284 + if (!_incident) return c.jsonT({ code: 401, message: "Not authorized" }); 285 + 286 + const _incidentUpdate = await db 287 + .insert(incidentUpdate) 288 + .values({ 289 + ...input, 290 + date: new Date(input.date), 291 + incidentId: Number(id), 292 + }) 293 + .returning() 294 + .get(); 295 + 296 + const data = incidentUpdateSchema.parse(_incidentUpdate); 297 + 298 + return c.jsonT({ 299 + ...data, 300 + }); 301 + }); 302 + 303 + export { incidentApi };
+36
apps/server/src/index.ts
··· 1 + import { OpenAPIHono } from "@hono/zod-openapi"; 2 + 3 + import { incidentApi } from "./incident"; 4 + import { middleware } from "./middleware"; 5 + import { monitorApi } from "./monitor"; 6 + import { VercelIngest } from "./vercel"; 7 + 8 + /** 9 + * Base Path "/v1" for our api 10 + */ 11 + const app = new OpenAPIHono(); 12 + app.doc("/openapi", { 13 + openapi: "3.0.0", 14 + info: { 15 + version: "1.0.0", 16 + title: "OpenStatus API", 17 + }, 18 + }); 19 + app.get("/ping", (c) => c.text("pong")); 20 + 21 + // Where we ingest data from Vercel 22 + app.post("/integration/vercel", VercelIngest); 23 + /** 24 + * Authentification Middleware 25 + */ 26 + 27 + app.use("/v1/*", middleware); 28 + app.route("/v1/monitor", monitorApi); 29 + app.route("/v1/incident", incidentApi); 30 + 31 + if (process.env.NODE_ENV === "development") { 32 + app.showRoutes(); 33 + } 34 + console.log("Starting server on port 3000"); 35 + 36 + export default app;
+22
apps/server/src/middleware.ts
··· 1 + import { Unkey } from "@unkey/api"; 2 + import type { Context, Env, Next } from "hono"; 3 + 4 + const unkey = new Unkey({ token: "test-key" }); 5 + 6 + export async function middleware(c: Context<Env, "/v1/*", {}>, next: Next) { 7 + const auth = c.req.header("x-openstatus-key"); 8 + if (!auth) return c.text("Unauthorized", 401); 9 + 10 + if (process.env.NODE_ENV === "production") { 11 + const { error, result } = await unkey.keys.verify({ key: auth }); 12 + 13 + if (error) return c.text("Bad Request", 400); 14 + 15 + if (!result.valid) return c.text("Unauthorized", 401); 16 + 17 + c.req.raw.headers.append("x-workspace-id", `${result.ownerId}`); 18 + } else { 19 + c.req.raw.headers.append("x-workspace-id", "1"); 20 + } 21 + await next(); 22 + }
+407
apps/server/src/monitor.ts
··· 1 + import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi"; 2 + 3 + import { db, eq } from "@openstatus/db"; 4 + import { 5 + availableRegions, 6 + METHODS, 7 + monitor, 8 + periodicity, 9 + } from "@openstatus/db/src/schema/monitor"; 10 + 11 + import { ErrorSchema } from "./shared"; 12 + 13 + const ParamsSchema = z.object({ 14 + id: z 15 + .string() 16 + .min(1) 17 + .openapi({ 18 + param: { 19 + name: "id", 20 + in: "path", 21 + }, 22 + description: "The id of the monitor", 23 + example: "1", 24 + }), 25 + }); 26 + 27 + export const periodicityEnum = z.enum(periodicity); 28 + export const regionEnum = z 29 + .enum(availableRegions) 30 + .or(z.literal("")) 31 + .transform((val) => (val === "" ? "auto" : val)); 32 + 33 + const MonitorSchema = z.object({ 34 + id: z.number().openapi({ 35 + example: 123, 36 + description: "The id of the monitor", 37 + }), 38 + periodicity: periodicityEnum.openapi({ 39 + example: "1m", 40 + description: "How often the monitor should run", 41 + }), 42 + url: z.string().url().openapi({ 43 + example: "https://www.documenso.co", 44 + description: "The url to monitor", 45 + }), 46 + regions: regionEnum.openapi({ 47 + example: "arn1", 48 + description: "The regions to use", 49 + }), 50 + name: z 51 + .string() 52 + .openapi({ 53 + example: "Documenso", 54 + description: "The name of the monitor", 55 + }) 56 + .nullable(), 57 + description: z 58 + .string() 59 + .openapi({ 60 + example: "Documenso website", 61 + description: "The description of your monitor", 62 + }) 63 + .nullable(), 64 + method: z.enum(METHODS).default("GET").openapi({ example: "GET" }), 65 + body: z 66 + .preprocess((val) => { 67 + return String(val); 68 + }, z.string()) 69 + .default("") 70 + .openapi({ 71 + example: "Hello World", 72 + description: "The body", 73 + }), 74 + headers: z 75 + .preprocess( 76 + (val) => { 77 + if (String(val).length > 0) { 78 + return JSON.parse(String(val)); 79 + } else { 80 + return []; 81 + } 82 + }, 83 + z.array(z.object({ key: z.string(), value: z.string() })).default([]), 84 + ) 85 + .openapi({ 86 + description: "The headers of your request", 87 + example: [{ key: "x-apikey", value: "supersecrettoken" }], 88 + }), 89 + active: z 90 + .boolean() 91 + .default(false) 92 + .openapi({ description: "If the monitor is active" }), 93 + }); 94 + 95 + const monitorInput = z.object({ 96 + periodicity: periodicityEnum.openapi({ 97 + example: "1m", 98 + description: "How often the monitor should run", 99 + }), 100 + url: z.string().url().openapi({ 101 + example: "https://www.documenso.co", 102 + description: "The url to monitor", 103 + }), 104 + regions: regionEnum 105 + .openapi({ 106 + example: "arn1", 107 + description: "The regions to use", 108 + }) 109 + .default("auto"), 110 + name: z.string().openapi({ 111 + example: "Documenso", 112 + description: "The name of the monitor", 113 + }), 114 + description: z.string().openapi({ 115 + example: "Documenso website", 116 + description: "The description of your monitor", 117 + }), 118 + method: z.enum(METHODS).default("GET").openapi({ example: "GET" }), 119 + body: z.string().openapi({ 120 + example: "Hello World", 121 + description: "The body", 122 + }), 123 + headers: z 124 + .preprocess( 125 + (val) => { 126 + if (String(val).length > 0) { 127 + return JSON.parse(String(val)); 128 + } else { 129 + return []; 130 + } 131 + }, 132 + z.array(z.object({ key: z.string(), value: z.string() })).default([]), 133 + ) 134 + .openapi({ 135 + description: "The headers of your request", 136 + example: [{ key: "x-apikey", value: "supersecrettoken" }], 137 + }), 138 + }); 139 + 140 + z.array(z.object({ key: z.string(), value: z.string() })) 141 + .default([]) 142 + .openapi({ 143 + description: "The headers of your request", 144 + example: [{ key: "x-apikey", value: "supersecrettoken" }], 145 + }) 146 + .nullable() 147 + .openapi({ description: "the monitor input" }); 148 + 149 + const monitorApi = new OpenAPIHono(); 150 + 151 + const getAllRoute = createRoute({ 152 + method: "get", 153 + path: "/", 154 + request: {}, 155 + responses: { 156 + 200: { 157 + content: { 158 + "application/json": { 159 + schema: z.array(MonitorSchema), 160 + }, 161 + }, 162 + description: "Get the monitor", 163 + }, 164 + 400: { 165 + content: { 166 + "application/json": { 167 + schema: ErrorSchema, 168 + }, 169 + }, 170 + description: "Returns an error", 171 + }, 172 + }, 173 + }); 174 + monitorApi.openapi(getAllRoute, async (c) => { 175 + const workspaceId = Number(c.req.header("x-workspace-id")); 176 + 177 + const _monitor = await db 178 + .select() 179 + .from(monitor) 180 + .where(eq(monitor.workspaceId, workspaceId)) 181 + .all(); 182 + 183 + if (!_monitor) return c.jsonT({ code: 404, message: "Not Found" }); 184 + 185 + const data = z.array(MonitorSchema).parse(_monitor); 186 + 187 + return c.jsonT(data); 188 + }); 189 + 190 + const getRoute = createRoute({ 191 + method: "get", 192 + path: "/:id", 193 + request: { 194 + params: ParamsSchema, 195 + }, 196 + responses: { 197 + 200: { 198 + content: { 199 + "application/json": { 200 + schema: MonitorSchema, 201 + }, 202 + }, 203 + description: "Get the monitor", 204 + }, 205 + 400: { 206 + content: { 207 + "application/json": { 208 + schema: ErrorSchema, 209 + }, 210 + }, 211 + description: "Returns an error", 212 + }, 213 + }, 214 + }); 215 + 216 + monitorApi.openapi(getRoute, async (c) => { 217 + const workspaceId = Number(c.req.header("x-workspace-id")); 218 + const { id } = c.req.valid("param"); 219 + 220 + const monitorId = Number(id); 221 + console.log({ monitorId, workspaceId }); 222 + const _monitor = await db 223 + .select() 224 + .from(monitor) 225 + .where(eq(monitor.id, monitorId)) 226 + .get(); 227 + 228 + if (!_monitor) return c.jsonT({ code: 404, message: "Not Found" }); 229 + 230 + if (workspaceId !== _monitor.workspaceId) 231 + return c.jsonT({ code: 401, message: "Unauthorized" }); 232 + 233 + const data = MonitorSchema.parse(_monitor); 234 + 235 + return c.jsonT(data); 236 + }); 237 + 238 + const postRoute = createRoute({ 239 + method: "post", 240 + path: "/", 241 + request: { 242 + body: { 243 + description: "The monitor to create", 244 + content: { 245 + "application/json": { 246 + schema: monitorInput, 247 + }, 248 + }, 249 + }, 250 + }, 251 + responses: { 252 + 200: { 253 + content: { 254 + "application/json": { 255 + schema: MonitorSchema, 256 + }, 257 + }, 258 + description: "Create a monitor", 259 + }, 260 + 400: { 261 + content: { 262 + "application/json": { 263 + schema: ErrorSchema, 264 + }, 265 + }, 266 + description: "Returns an error", 267 + }, 268 + }, 269 + }); 270 + 271 + monitorApi.openapi(postRoute, async (c) => { 272 + const input = c.req.valid("json"); 273 + const workspaceId = Number(c.req.header("x-workspace-id")); 274 + 275 + const { headers, ...rest } = input; 276 + const _newMonitor = await db 277 + .insert(monitor) 278 + .values({ 279 + ...rest, 280 + workspaceId: workspaceId, 281 + headers: input.headers ? JSON.stringify(input.headers) : undefined, 282 + }) 283 + .returning() 284 + .get(); 285 + 286 + const data = MonitorSchema.parse(_newMonitor); 287 + 288 + return c.jsonT(data); 289 + }); 290 + 291 + const putRoute = createRoute({ 292 + method: "put", 293 + path: "/", 294 + request: { 295 + params: ParamsSchema, 296 + body: { 297 + description: "The monitor to update", 298 + content: { 299 + "application/json": { 300 + schema: monitorInput, 301 + }, 302 + }, 303 + }, 304 + }, 305 + responses: { 306 + 200: { 307 + content: { 308 + "application/json": { 309 + schema: MonitorSchema, 310 + }, 311 + }, 312 + description: "Update a monitor", 313 + }, 314 + 400: { 315 + content: { 316 + "application/json": { 317 + schema: ErrorSchema, 318 + }, 319 + }, 320 + description: "Returns an error", 321 + }, 322 + }, 323 + }); 324 + 325 + monitorApi.openapi(putRoute, async (c) => { 326 + const input = c.req.valid("json"); 327 + const workspaceId = Number(c.req.header("x-workspace-id")); 328 + const { id } = c.req.valid("param"); 329 + 330 + if (!id) return c.jsonT({ code: 400, message: "Bad Request" }); 331 + 332 + const _monitor = await db 333 + .select() 334 + .from(monitor) 335 + .where(eq(monitor.id, Number(id))) 336 + .get(); 337 + 338 + if (!_monitor) return c.jsonT({ code: 404, message: "Not Found" }); 339 + 340 + if (workspaceId !== _monitor.workspaceId) 341 + return c.jsonT({ code: 401, message: "Unauthorized" }); 342 + 343 + const { headers, ...rest } = input; 344 + const _newMonitor = await db 345 + .update(monitor) 346 + .set({ 347 + ...rest, 348 + headers: input.headers ? JSON.stringify(input.headers) : undefined, 349 + }) 350 + .returning() 351 + .get(); 352 + 353 + const data = MonitorSchema.parse(_newMonitor); 354 + 355 + return c.jsonT(data); 356 + }); 357 + 358 + const deleteRoute = createRoute({ 359 + method: "delete", 360 + path: "/:id", 361 + request: { 362 + params: ParamsSchema, 363 + }, 364 + responses: { 365 + 200: { 366 + content: { 367 + "application/json": { 368 + schema: z.object({ 369 + message: z.string().openapi({ 370 + example: "Deleted", 371 + }), 372 + }), 373 + }, 374 + }, 375 + description: "Delete the monitor", 376 + }, 377 + 400: { 378 + content: { 379 + "application/json": { 380 + schema: ErrorSchema, 381 + }, 382 + }, 383 + description: "Returns an error", 384 + }, 385 + }, 386 + }); 387 + monitorApi.openapi(deleteRoute, async (c) => { 388 + const workspaceId = Number(c.req.header("x-workspace-id")); 389 + const { id } = c.req.valid("param"); 390 + 391 + const monitorId = Number(id); 392 + const _monitor = await db 393 + .select() 394 + .from(monitor) 395 + .where(eq(monitor.id, monitorId)) 396 + .get(); 397 + 398 + if (!_monitor) return c.jsonT({ code: 404, message: "Not Found" }); 399 + 400 + if (workspaceId !== _monitor.workspaceId) 401 + return c.jsonT({ code: 401, message: "Unauthorized" }); 402 + 403 + await db.delete(monitor).where(eq(monitor.id, monitorId)).run(); 404 + return c.jsonT({ message: "Deleted" }); 405 + }); 406 + 407 + export { monitorApi };
+46
apps/server/src/schema/vercel.ts
··· 1 + import { z } from "zod"; 2 + 3 + // https://vercel.com/docs/observability/log-drains-overview/log-drains-reference#json-log-drains 4 + export const logDrainSchema = z.object({ 5 + id: z.string().optional(), 6 + timestamp: z.number().optional(), 7 + type: z 8 + .enum([ 9 + "middleware-invocation", 10 + "stdout", 11 + "stderr", 12 + "edge-function-invocation", 13 + "fatal", 14 + ]) 15 + .optional(), 16 + edgeType: z.enum(["edge-function", "middleware"]).optional(), 17 + requestId: z.string().optional(), 18 + statusCode: z.number().optional(), 19 + message: z.string().optional(), 20 + projectId: z.string().optional(), 21 + deploymentId: z.string().optional(), 22 + buildId: z.string().optional(), 23 + source: z.enum(["external", "lambda", "edge", "static", "build"]), 24 + host: z.string().optional(), 25 + environment: z.string().optional(), 26 + branch: z.string().optional(), 27 + destination: z.string().optional(), 28 + path: z.string().optional(), 29 + entrypoint: z.string().optional(), 30 + proxy: z 31 + .object({ 32 + timestamp: z.number().optional(), 33 + region: z.string().optional(), // TODO: use regions enum? 34 + method: z.string().optional(), // TODO: use methods enum? 35 + vercelCache: z.string().optional(), // TODO: use "HIT" / "MISS" enum? 36 + statusCode: z.number().optional(), 37 + path: z.string().optional(), 38 + host: z.string().optional(), 39 + scheme: z.string().optional(), 40 + clientIp: z.string().optional(), 41 + userAgent: z.array(z.string()).optional(), 42 + }) 43 + .optional(), 44 + }); 45 + 46 + export const logDrainSchemaArray = z.array(logDrainSchema);
+10
apps/server/src/shared.ts
··· 1 + import { z } from "@hono/zod-openapi"; 2 + 3 + export const ErrorSchema = z.object({ 4 + code: z.number().openapi({ 5 + example: 400, 6 + }), 7 + message: z.string().openapi({ 8 + example: "Bad Request", 9 + }), 10 + });
+35
apps/server/src/vercel.ts
··· 1 + import type { Context, Env } from "hono"; 2 + 3 + import { Tinybird } from "@openstatus/tinybird"; 4 + 5 + import { logDrainSchema, logDrainSchemaArray } from "./schema/vercel"; 6 + 7 + const tb = new Tinybird({ token: process.env.TINY_BIRD_API_KEY || "" }); // should we use t3-env? 8 + 9 + export function publishVercelLogDrain() { 10 + return tb.buildIngestEndpoint({ 11 + datasource: "vercel_log_drain__v0", 12 + event: logDrainSchema, 13 + }); 14 + } 15 + 16 + export const VercelIngest = async ( 17 + c: Context<Env, "/integration/vercel", {}>, 18 + ) => { 19 + const json = c.req.json(); 20 + const logDrains = logDrainSchemaArray.safeParse(json); 21 + 22 + if (logDrains.success) { 23 + // We are only pushing the logs that are not stdout or stderr 24 + const data = logDrains.data.filter( 25 + (log) => log.type !== "stdout" && log.type !== "stderr", 26 + ); 27 + 28 + for (const event of data) { 29 + // FIXME: Zod-bird is broken 30 + await publishVercelLogDrain()(event); 31 + } 32 + } 33 + 34 + c.json({ code: "ok" }); 35 + };
+4
apps/server/tsconfig.json
··· 1 + { 2 + "extends": "@openstatus/tsconfig/base.json", 3 + "include": ["src", "*.ts", "**/*.ts"] 4 + }
+4 -1
apps/web/.env.example
··· 61 61 VERCEL_CLIENT_ID= 62 62 VERCEL_CLIENT_SECRET= 63 63 VERCEL_REDIRECT_URI= 64 - AES_KEY= 64 + AES_KEY= 65 + # Unkey 66 + UNKEY_API_ID= 67 + UNKEY_TOKEN=
+2 -1
apps/web/package.json
··· 48 48 "@trpc/next": "10.37.1", 49 49 "@trpc/react-query": "10.37.1", 50 50 "@trpc/server": "10.37.1", 51 + "@unkey/api": "0.6.22", 51 52 "@upstash/qstash": "0.3.6", 52 53 "@upstash/redis": "1.21.0", 53 54 "@vercel/analytics": "1.0.1", ··· 86 87 }, 87 88 "devDependencies": { 88 89 "@openstatus/eslint-config": "workspace:*", 90 + "@openstatus/tsconfig": "workspace:*", 89 91 "@types/luxon": "^3.3.1", 90 92 "@types/node": "20.3.1", 91 93 "@types/react": "18.2.12", ··· 96 98 "rehype-slug": "5.1.0", 97 99 "remark-gfm": "3.0.1", 98 100 "tailwindcss": "3.3.2", 99 - "tsconfig": "workspace:*", 100 101 "typescript": "5.1.6", 101 102 "unified": "10.1.2" 102 103 }
+21
apps/web/src/app/app/(dashboard)/[workspaceSlug]/settings/_components/api-keys/actions.ts
··· 1 + "use server"; 2 + 3 + import { Unkey } from "@unkey/api"; 4 + 5 + import { env } from "@/env"; 6 + 7 + const unkey = new Unkey({ token: env.UNKEY_TOKEN }); 8 + 9 + export async function create(ownerId: number) { 10 + const key = await unkey.keys.create({ 11 + apiId: env.UNKEY_API_ID, 12 + ownerId: String(ownerId), 13 + prefix: "os", 14 + }); 15 + return key; 16 + } 17 + 18 + export async function revoke(keyId: string) { 19 + const res = await unkey.keys.revoke({ keyId }); 20 + return res; 21 + }
+51
apps/web/src/app/app/(dashboard)/[workspaceSlug]/settings/_components/api-keys/card.tsx
··· 1 + "use server"; 2 + 3 + import { Unkey } from "@unkey/api"; 4 + 5 + import { Container } from "@/components/dashboard/container"; 6 + import { env } from "@/env"; 7 + import { formatDate } from "@/lib/utils"; 8 + import { CreateForm } from "./create-form"; 9 + import { RevokeButton } from "./revoke-button.tsx"; 10 + 11 + const unkey = new Unkey({ token: env.UNKEY_TOKEN }); 12 + 13 + export async function ApiKeys({ ownerId }: { ownerId: number }) { 14 + const data = await unkey.apis.listKeys({ 15 + apiId: env.UNKEY_API_ID, 16 + ownerId: String(ownerId), 17 + }); 18 + 19 + if (data.error) { 20 + return <div>Something went wrong. Please contact us.</div>; 21 + } 22 + 23 + return ( 24 + <Container 25 + title="API Token" 26 + description="Use our API endpoints to create your monitors programmatically." 27 + actions={ 28 + <> 29 + {data.result.keys.length === 1 ? ( 30 + <RevokeButton keyId={data.result.keys[0].id} /> 31 + ) : ( 32 + <CreateForm ownerId={ownerId} /> 33 + )} 34 + </> 35 + } 36 + > 37 + {data.result.keys.length === 1 ? ( 38 + <dl className="[&_dt]:text-muted-foreground grid gap-2 [&>*]:text-sm [&_dt]:font-light"> 39 + <div className="flex min-w-0 items-center justify-between gap-3"> 40 + <dt>Token</dt> 41 + <dd className="font-mono">{data.result.keys[0].start}...</dd> 42 + </div> 43 + <div className="flex min-w-0 items-center justify-between gap-3"> 44 + <dt>Created At</dt> 45 + <dd>{formatDate(new Date(data.result.keys[0].createdAt))}</dd> 46 + </div> 47 + </dl> 48 + ) : null} 49 + </Container> 50 + ); 51 + }
+93
apps/web/src/app/app/(dashboard)/[workspaceSlug]/settings/_components/api-keys/create-form.tsx
··· 1 + "use client"; 2 + 3 + import * as React from "react"; 4 + import { useRouter } from "next/navigation"; 5 + 6 + import { Icons } from "@/components/icons"; 7 + import { 8 + AlertDialog, 9 + AlertDialogAction, 10 + AlertDialogContent, 11 + AlertDialogDescription, 12 + AlertDialogFooter, 13 + AlertDialogHeader, 14 + AlertDialogTitle, 15 + } from "@/components/ui/alert-dialog"; 16 + import { useToastAction } from "@/hooks/use-toast-action"; 17 + import { copyToClipboard } from "@/lib/utils"; 18 + import { create } from "./actions"; 19 + import { SubmitButton } from "./submit-button"; 20 + 21 + export function CreateForm({ ownerId }: { ownerId: number }) { 22 + const [rawKey, setRawKey] = React.useState<string | undefined>(); 23 + const router = useRouter(); 24 + const [hasCopied, setHasCopied] = React.useState(false); 25 + const { toast } = useToastAction(); 26 + 27 + React.useEffect(() => { 28 + if (hasCopied) { 29 + setTimeout(() => { 30 + setHasCopied(false); 31 + }, 2000); 32 + } 33 + }, [hasCopied]); 34 + 35 + async function onCreate() { 36 + try { 37 + const res = await create(ownerId); 38 + if (res.result) { 39 + setRawKey(res.result.key); 40 + } 41 + } catch { 42 + toast("error"); 43 + } 44 + } 45 + 46 + return ( 47 + <> 48 + <form action={onCreate}> 49 + <SubmitButton>Create</SubmitButton> 50 + </form> 51 + <AlertDialog 52 + open={Boolean(rawKey)} 53 + onOpenChange={() => setRawKey(undefined)} 54 + > 55 + <AlertDialogContent> 56 + <AlertDialogHeader> 57 + <AlertDialogTitle>Api Key</AlertDialogTitle> 58 + <AlertDialogDescription> 59 + Please make sure to store the API key safely. You will only be 60 + able to see it once. If you forgot to safe it, you will need to 61 + revoke and recreate a key. 62 + </AlertDialogDescription> 63 + </AlertDialogHeader> 64 + <div> 65 + <button 66 + className="group inline-flex items-center p-2" 67 + onClick={() => { 68 + copyToClipboard(String(rawKey)); 69 + setHasCopied(true); 70 + }} 71 + > 72 + <span className="font-mono">{rawKey}</span> 73 + {!hasCopied ? ( 74 + <Icons.copy className="ml-2 hidden h-4 w-4 group-hover:block" /> 75 + ) : ( 76 + <Icons.check className="ml-2 h-4 w-4" /> 77 + )} 78 + </button> 79 + </div> 80 + <AlertDialogFooter> 81 + <AlertDialogAction 82 + onClick={() => { 83 + router.refresh(); 84 + }} 85 + > 86 + Continue 87 + </AlertDialogAction> 88 + </AlertDialogFooter> 89 + </AlertDialogContent> 90 + </AlertDialog> 91 + </> 92 + ); 93 + }
+70
apps/web/src/app/app/(dashboard)/[workspaceSlug]/settings/_components/api-keys/revoke-button.tsx.tsx
··· 1 + "use client"; 2 + 3 + import * as React from "react"; 4 + import { useRouter } from "next/navigation"; 5 + 6 + import { LoadingAnimation } from "@/components/loading-animation"; 7 + import { 8 + AlertDialog, 9 + AlertDialogAction, 10 + AlertDialogCancel, 11 + AlertDialogContent, 12 + AlertDialogDescription, 13 + AlertDialogFooter, 14 + AlertDialogHeader, 15 + AlertDialogTitle, 16 + AlertDialogTrigger, 17 + } from "@/components/ui/alert-dialog"; 18 + import { Button } from "@/components/ui/button"; 19 + import { useToastAction } from "@/hooks/use-toast-action"; 20 + import { revoke } from "./actions"; 21 + 22 + export function RevokeButton({ keyId }: { keyId: string }) { 23 + const [open, setOpen] = React.useState(false); 24 + const [isPending, startTransition] = React.useTransition(); 25 + const router = useRouter(); 26 + const { toast } = useToastAction(); 27 + 28 + async function onRevoke() { 29 + startTransition(async () => { 30 + try { 31 + await revoke(keyId); 32 + router.refresh(); 33 + setOpen(false); 34 + toast("deleted"); 35 + } catch { 36 + toast("error"); 37 + } 38 + }); 39 + } 40 + 41 + return ( 42 + <AlertDialog open={open} onOpenChange={(value) => setOpen(value)}> 43 + <AlertDialogTrigger asChild> 44 + <Button variant="destructive">Revoke</Button> 45 + </AlertDialogTrigger> 46 + <AlertDialogContent> 47 + <AlertDialogHeader> 48 + <AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle> 49 + <AlertDialogDescription> 50 + This action cannot be undone. This will permanently revoke the API 51 + key. 52 + </AlertDialogDescription> 53 + </AlertDialogHeader> 54 + <AlertDialogFooter> 55 + <AlertDialogCancel>Cancel</AlertDialogCancel> 56 + <AlertDialogAction 57 + onClick={async (e) => { 58 + e.preventDefault(); 59 + await onRevoke(); 60 + }} 61 + className="bg-destructive text-destructive-foreground hover:bg-destructive/90" 62 + disabled={isPending} 63 + > 64 + {!isPending ? "Delete" : <LoadingAnimation />} 65 + </AlertDialogAction> 66 + </AlertDialogFooter> 67 + </AlertDialogContent> 68 + </AlertDialog> 69 + ); 70 + }
+15
apps/web/src/app/app/(dashboard)/[workspaceSlug]/settings/_components/api-keys/submit-button.tsx
··· 1 + "use client"; 2 + 3 + import { experimental_useFormStatus as useFormStatus } from "react-dom"; 4 + 5 + import { LoadingAnimation } from "@/components/loading-animation"; 6 + import { Button } from "@/components/ui/button"; 7 + 8 + export function SubmitButton({ children }: { children?: React.ReactNode }) { 9 + const { pending } = useFormStatus(); 10 + return ( 11 + <Button type="submit" disabled={pending} className="disabled:opacity-100"> 12 + {pending ? <LoadingAnimation /> : children} 13 + </Button> 14 + ); 15 + }
apps/web/src/app/app/(dashboard)/[workspaceSlug]/settings/_components/customer-portal-button.tsx apps/web/src/app/app/(dashboard)/[workspaceSlug]/settings/_components/billing/customer-portal-button.tsx
apps/web/src/app/app/(dashboard)/[workspaceSlug]/settings/_components/plan.tsx apps/web/src/app/app/(dashboard)/[workspaceSlug]/settings/_components/billing/plan.tsx
+40 -17
apps/web/src/app/app/(dashboard)/[workspaceSlug]/settings/page.tsx
··· 2 2 3 3 import { Header } from "@/components/dashboard/header"; 4 4 import { Badge } from "@/components/ui/badge"; 5 + import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; 5 6 import { api } from "@/trpc/server"; 6 - import { CustomerPortalButton } from "./_components/customer-portal-button"; 7 - import { SettingsPlan } from "./_components/plan"; 7 + import { ApiKeys } from "./_components/api-keys/card"; 8 + import { CustomerPortalButton } from "./_components/billing/customer-portal-button"; 9 + import { SettingsPlan } from "./_components/billing/plan"; 8 10 9 11 export default async function Page({ 10 12 params, ··· 24 26 title="Settings" 25 27 description="Your OpenStatus workspace settings." 26 28 /> 27 - <div className="col-span-full grid gap-4"> 28 - <h3 className="text-lg font-medium">Plans</h3> 29 - <div className="text-muted-foreground flex items-center space-x-2 text-sm"> 30 - Your current plan is{" "} 31 - <Badge className="ml-2">{data?.plan || "free"}</Badge> 32 - </div> 33 - {data?.plan === "pro" ? ( 34 - <div> 35 - <CustomerPortalButton workspaceSlug={params.workspaceSlug} /> 36 - </div> 37 - ) : null} 38 - <SettingsPlan 39 - workspaceSlug={params.workspaceSlug} 40 - workspaceData={data} 41 - /> 29 + <div className="col-span-full"> 30 + <Tabs defaultValue="api-key" className="relative mr-auto w-full"> 31 + <TabsList className="h-9 w-full justify-start rounded-none border-b bg-transparent p-0"> 32 + <TabsTrigger 33 + className="text-muted-foreground data-[state=active]:border-b-primary data-[state=active]:text-foreground relative h-9 rounded-none border-b-2 border-b-transparent bg-transparent px-4 pb-3 pt-2 font-semibold shadow-none transition-none data-[state=active]:shadow-none" 34 + value="api-key" 35 + > 36 + API Token 37 + </TabsTrigger> 38 + <TabsTrigger 39 + className="text-muted-foreground data-[state=active]:border-b-primary data-[state=active]:text-foreground relative h-9 rounded-none border-b-2 border-b-transparent bg-transparent px-4 pb-3 pt-2 font-semibold shadow-none transition-none data-[state=active]:shadow-none" 40 + value="billing" 41 + > 42 + Billing 43 + </TabsTrigger> 44 + </TabsList> 45 + <TabsContent value="api-key" className="pt-3"> 46 + <ApiKeys ownerId={data.id} /> 47 + </TabsContent> 48 + <TabsContent value="billing" className="grid gap-4 pt-3"> 49 + <h3 className="text-lg font-medium">Plans</h3> 50 + <div className="text-muted-foreground flex items-center space-x-2 text-sm"> 51 + Your current plan is{" "} 52 + <Badge className="ml-2">{data?.plan || "free"}</Badge> 53 + </div> 54 + {data?.plan === "pro" ? ( 55 + <div> 56 + <CustomerPortalButton workspaceSlug={params.workspaceSlug} /> 57 + </div> 58 + ) : null} 59 + <SettingsPlan 60 + workspaceSlug={params.workspaceSlug} 61 + workspaceData={data} 62 + /> 63 + </TabsContent> 64 + </Tabs> 42 65 </div> 43 66 </div> 44 67 );
+4
apps/web/src/components/icons.tsx
··· 1 1 import { 2 2 Activity, 3 3 Calendar, 4 + Check, 4 5 Cog, 6 + Copy, 5 7 Fingerprint, 6 8 Globe, 7 9 LayoutDashboard, ··· 44 46 twitter: TwitterIcon, 45 47 globe: Globe, 46 48 plug: Plug, 49 + copy: Copy, 50 + check: Check, 47 51 discord: ({ ...props }: LucideProps) => ( 48 52 <svg viewBox="0 0 640 512" {...props}> 49 53 <path
+4
apps/web/src/env.ts
··· 15 15 QSTASH_NEXT_SIGNING_KEY: z.string().min(1), 16 16 QSTASH_TOKEN: z.string().min(1), 17 17 STRIPE_WEBHOOK_SECRET_KEY: z.string(), 18 + UNKEY_TOKEN: z.string(), 19 + UNKEY_API_ID: z.string(), 18 20 }, 19 21 client: { 20 22 NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(1), ··· 47 49 STRIPE_WEBHOOK_SECRET_KEY: process.env.STRIPE_WEBHOOK_SECRET_KEY, 48 50 NEXT_PUBLIC_URL: process.env.NEXT_PUBLIC_URL, 49 51 NEXT_PUBLIC_SENTRY_DSN: process.env.NEXT_PUBLIC_SENTRY_DSN, 52 + UNKEY_TOKEN: process.env.UNKEY_TOKEN, 53 + UNKEY_API_ID: process.env.UNKEY_API_ID, 50 54 }, 51 55 });
+4
apps/web/src/lib/utils.ts
··· 27 27 .replace(/ /g, "-") 28 28 .replace(/[^\w-]+/g, ""); 29 29 }; 30 + 31 + export async function copyToClipboard(value: string) { 32 + navigator.clipboard.writeText(value); 33 + }
+1 -1
apps/web/tsconfig.json
··· 1 1 { 2 - "extends": "tsconfig/nextjs.json", 2 + "extends": "@openstatus/tsconfig/nextjs.json", 3 3 "compilerOptions": { 4 4 "baseUrl": ".", 5 5 "paths": {
bun.lockb

This is a binary file and will not be displayed.

+18
fly.toml
··· 1 + # fly.toml app configuration file generated for openstatus-api on 2023-09-13T17:29:05+02:00 2 + # 3 + # See https://fly.io/docs/reference/configuration/ for information about how to use this file. 4 + # 5 + 6 + app = "openstatus-api" 7 + primary_region = "ams" 8 + 9 + [build] 10 + dockerfile = "apps/server/Dockerfile" 11 + 12 + [http_service] 13 + internal_port = 3000 14 + force_https = true 15 + auto_stop_machines = true 16 + auto_start_machines = true 17 + min_machines_running = 0 18 + processes = ["app"]
+8 -2
package.json
··· 31 31 "typescript": "5.1.6" 32 32 }, 33 33 "packageManager": "pnpm@8.6.10", 34 - "name": "openstatus" 35 - } 34 + "name": "openstatus", 35 + "workspaces": [ 36 + "apps/*", 37 + "packages/*", 38 + "packages/config/*", 39 + "packages/integrations/*" 40 + ] 41 + }
+1 -1
packages/analytics/package.json
··· 11 11 "devDependencies": { 12 12 "@types/node": "20.3.1", 13 13 "ts-node": "10.9.1", 14 - "tsconfig": "workspace:^", 14 + "@openstatus/tsconfig": "workspace:^", 15 15 "typescript": "5.1.6" 16 16 }, 17 17 "scripts": {},
+1 -1
packages/analytics/tsconfig.json
··· 1 1 { 2 2 "$schema": "https://json.schemastore.org/tsconfig", 3 - "extends": "tsconfig/base.json", 3 + "extends": "@openstatus/tsconfig/base.json", 4 4 "exclude": ["dist", "node_modules"], 5 5 "compilerOptions": { 6 6 "outDir": "dist",
+1 -1
packages/api/package.json
··· 21 21 "zod": "3.21.4" 22 22 }, 23 23 "devDependencies": { 24 - "tsconfig": "workspace:^", 24 + "@openstatus/tsconfig": "workspace:^", 25 25 "typescript": "5.1.6", 26 26 "vitest": "0.34.3" 27 27 },
+1 -1
packages/api/tsconfig.json
··· 1 1 { 2 - "extends": "tsconfig/nextjs.json", 2 + "extends": "@openstatus/tsconfig/nextjs.json", 3 3 "include": ["src", "*.ts"] 4 4 }
-1
packages/config/eslint/package.json
··· 12 12 "eslint-config-turbo": "^1.10.1", 13 13 "eslint-plugin-import": "^2.27.5", 14 14 "eslint-plugin-react": "7.32.2" 15 - 16 15 }, 17 16 "devDependencies": { 18 17 "eslint": "^8.42.0",
+2 -2
packages/db/package.json
··· 11 11 "seed": "bun src/seed.mts" 12 12 }, 13 13 "dependencies": { 14 - "@libsql/client": "0.3.1", 14 + "@libsql/client": "0.3.4", 15 15 "@t3-oss/env-core": "0.6.0", 16 16 "dotenv": "16.3.1", 17 17 "drizzle-orm": "0.28.6", ··· 21 21 "devDependencies": { 22 22 "@types/node": "20.3.1", 23 23 "drizzle-kit": "0.19.12", 24 - "tsconfig": "workspace:^", 24 + "@openstatus/tsconfig": "workspace:^", 25 25 "typescript": "5.1.6", 26 26 "bufferutil": "4.0.7", 27 27 "utf-8-validate": "6.0.3",
+2 -8
packages/db/src/schema/monitor.ts
··· 34 34 "syd1", 35 35 ] as const; 36 36 37 + export const periodicity = ["1m", "5m", "10m", "30m", "1h", "other"] as const; 37 38 export const METHODS = ["GET", "POST"] as const; 38 39 39 40 export const RegionEnum = z.enum(availableRegions); ··· 107 108 }), 108 109 ); 109 110 110 - export const periodicityEnum = z.enum([ 111 - "1m", 112 - "5m", 113 - "10m", 114 - "30m", 115 - "1h", 116 - "other", 117 - ]); 111 + export const periodicityEnum = z.enum(periodicity); 118 112 // Schema for inserting a Monitor - can be used to validate API requests 119 113 export const insertMonitorSchema = createInsertSchema(monitor, { 120 114 periodicity: periodicityEnum,
+1 -1
packages/db/tsconfig.json
··· 1 1 { 2 2 "$schema": "https://json.schemastore.org/tsconfig", 3 - "extends": "tsconfig/base.json", 3 + "extends": "@openstatus/tsconfig/base.json", 4 4 "exclude": ["dist"], 5 5 "compilerOptions": { 6 6 "outDir": "dist",
+1 -1
packages/emails/package.json
··· 24 24 "@types/node": "20.3.1", 25 25 "@types/react": "18.2.12", 26 26 "react": "18.2.0", 27 - "tsconfig": "workspace:*", 27 + "@openstatus/tsconfig": "workspace:*", 28 28 "typescript": "5.1.6" 29 29 } 30 30 }
+1 -1
packages/emails/tsconfig.json
··· 1 1 { 2 - "extends": "tsconfig/react-library.json", 2 + "extends": "@openstatus/tsconfig/react-library.json", 3 3 "include": [".", "env.ts"], 4 4 "exclude": ["dist", "build", "node_modules"] 5 5 }
+1 -1
packages/integrations/vercel/package.json
··· 15 15 "@types/react": "18.2.12", 16 16 "@types/react-dom": "18.2.5", 17 17 "next": "13.4.12", 18 - "tsconfig": "workspace:*", 18 + "@openstatus/tsconfig": "workspace:*", 19 19 "typescript": "5.1.6" 20 20 } 21 21 }
+1 -1
packages/integrations/vercel/tsconfig.json
··· 1 1 { 2 - "extends": "tsconfig/nextjs.json", 2 + "extends": "@openstatus/tsconfig/nextjs.json", 3 3 "include": ["src", "*.ts"] 4 4 }
+1 -1
packages/plans/package.json
··· 11 11 "zod": "3.21.4" 12 12 }, 13 13 "devDependencies": { 14 - "tsconfig": "workspace:^", 14 + "@openstatus/tsconfig": "workspace:^", 15 15 "typescript": "5.1.6" 16 16 }, 17 17 "keywords": [],
+1 -1
packages/plans/tsconfig.json
··· 1 1 { 2 - "extends": "tsconfig/base.json", 2 + "extends": "@openstatus/tsconfig/base.json", 3 3 "include": ["src", "*.ts"] 4 4 }
+1 -1
packages/tinybird/package.json
··· 10 10 "devDependencies": { 11 11 "@types/node": "20.3.1", 12 12 "typescript": "5.1.6", 13 - "tsconfig": "workspace:*" 13 + "@openstatus/tsconfig": "workspace:*" 14 14 } 15 15 }
+2 -2
packages/tinybird/src/client.ts
··· 1 - import type { Tinybird } from "@chronark/zod-bird"; 1 + import { Tinybird } from "@chronark/zod-bird"; 2 2 3 3 import { 4 4 tbBuildMonitorList, ··· 9 9 } from "./validation"; 10 10 11 11 // REMINDER: 12 - // const tb = new Tinybird({ token: process.env.TINYBIRD_TOKEN! }); 12 + const tb = new Tinybird({ token: process.env.TINYBIRD_TOKEN! }); 13 13 14 14 export function publishPingResponse(tb: Tinybird) { 15 15 return tb.buildIngestEndpoint({
+1 -1
packages/tinybird/tsconfig.json
··· 1 1 { 2 - "extends": "tsconfig/base.json", 2 + "extends": "@openstatus/tsconfig/base.json", 3 3 "exclude": ["node_modules"], 4 4 "compilerOptions": { 5 5 "esModuleInterop": true,
+1 -1
packages/tsconfig/package.json
··· 1 1 { 2 - "name": "tsconfig", 2 + "name": "@openstatus/tsconfig", 3 3 "version": "0.0.0", 4 4 "private": true, 5 5 "license": "MIT",
+1 -1
packages/ui/package.json
··· 14 14 "eslint": "8.45.0", 15 15 "@openstatus/eslint-config": "workspace:*", 16 16 "react": "18.2.0", 17 - "tsconfig": "workspace:*", 17 + "@openstatus/tsconfig": "workspace:*", 18 18 "typescript": "5.1.6" 19 19 } 20 20 }
+1 -1
packages/ui/tsconfig.json
··· 1 1 { 2 - "extends": "tsconfig/react-library.json", 2 + "extends": "@openstatus/tsconfig/react-library.json", 3 3 "include": ["."], 4 4 "exclude": ["dist", "build", "node_modules"] 5 5 }
+1 -1
packages/upstash/package.json
··· 11 11 }, 12 12 "devDependencies": { 13 13 "@types/node": "20.3.1", 14 - "tsconfig": "workspace:*", 14 + "@openstatus/tsconfig": "workspace:*", 15 15 "tsup": "7.1.0", 16 16 "typescript": "5.1.6" 17 17 }
+1 -1
packages/upstash/tsconfig.json
··· 1 1 { 2 - "extends": "tsconfig/base.json", 2 + "extends": "@openstatus/tsconfig/base.json", 3 3 "exclude": ["node_modules"], 4 4 "compilerOptions": { 5 5 "esModuleInterop": true,
+143 -257
pnpm-lock.yaml
··· 25 25 version: link:packages/config/eslint 26 26 '@turbo/gen': 27 27 specifier: 1.10.9 28 - version: 1.10.9(@types/node@20.5.9)(typescript@5.1.6) 28 + version: 1.10.9(@types/node@20.3.1)(typescript@5.1.6) 29 29 eslint: 30 30 specifier: 8.43.0 31 31 version: 8.43.0 ··· 42 42 specifier: 5.1.6 43 43 version: 5.1.6 44 44 45 - apps/api: 46 - dependencies: 47 - '@openstatus/db': 48 - specifier: workspace:^ 49 - version: link:../../packages/db 50 - next: 51 - specifier: 13.4.12 52 - version: 13.4.12(@babel/core@7.22.9)(react-dom@18.2.0)(react@18.2.0) 53 - next-swagger-doc: 54 - specifier: 0.4.0 55 - version: 0.4.0(next@13.4.12)(openapi-types@12.1.3) 56 - react: 57 - specifier: 18.2.0 58 - version: 18.2.0 59 - react-dom: 60 - specifier: 18.2.0 61 - version: 18.2.0(react@18.2.0) 62 - zod: 63 - specifier: 3.21.4 64 - version: 3.21.4 65 - devDependencies: 66 - '@types/node': 67 - specifier: 20.5.9 68 - version: 20.5.9 69 - '@types/react': 70 - specifier: 18.2.12 71 - version: 18.2.12 72 - '@types/react-dom': 73 - specifier: 18.2.5 74 - version: 18.2.5 75 - tsconfig: 76 - specifier: workspace:* 77 - version: link:../../packages/tsconfig 78 - typescript: 79 - specifier: 5.1.6 80 - version: 5.1.6 81 - 82 45 apps/docs: 83 46 dependencies: 84 47 '@vercel/analytics': ··· 103 66 specifier: 5.6.2 104 67 version: 5.6.2(react-dom@18.2.0)(react@18.2.0) 105 68 devDependencies: 69 + '@openstatus/tsconfig': 70 + specifier: workspace:* 71 + version: link:../../packages/tsconfig 106 72 '@types/node': 107 73 specifier: 18.11.10 108 74 version: 18.11.10 109 75 '@types/swagger-ui-react': 110 76 specifier: 4.18.0 111 77 version: 4.18.0 112 - tsconfig: 113 - specifier: workspace:* 114 - version: link:../../packages/tsconfig 115 78 typescript: 116 79 specifier: 5.1.6 117 80 version: 5.1.6 118 81 82 + apps/server: 83 + dependencies: 84 + '@hono/zod-openapi': 85 + specifier: 0.4.0 86 + version: 0.4.0(hono@3.6.1)(zod@3.21.4) 87 + '@openstatus/db': 88 + specifier: '*' 89 + version: link:../../packages/db 90 + '@openstatus/tinybird': 91 + specifier: '*' 92 + version: link:../../packages/tinybird 93 + '@unkey/api': 94 + specifier: 0.6.22 95 + version: 0.6.22 96 + hono: 97 + specifier: 3.6.1 98 + version: 3.6.1 99 + devDependencies: 100 + '@openstatus/tsconfig': 101 + specifier: workspace:* 102 + version: link:../../packages/tsconfig 103 + 119 104 apps/web: 120 105 dependencies: 121 106 '@clerk/nextjs': ··· 232 217 '@trpc/server': 233 218 specifier: 10.37.1 234 219 version: 10.37.1 220 + '@unkey/api': 221 + specifier: 0.6.22 222 + version: 0.6.22 235 223 '@upstash/qstash': 236 224 specifier: 0.3.6 237 225 version: 0.3.6 ··· 341 329 '@openstatus/eslint-config': 342 330 specifier: workspace:* 343 331 version: link:../../packages/config/eslint 332 + '@openstatus/tsconfig': 333 + specifier: workspace:* 334 + version: link:../../packages/tsconfig 344 335 '@types/luxon': 345 336 specifier: ^3.3.1 346 337 version: 3.3.1 ··· 371 362 tailwindcss: 372 363 specifier: 3.3.2 373 364 version: 3.3.2 374 - tsconfig: 375 - specifier: workspace:* 376 - version: link:../../packages/tsconfig 377 365 typescript: 378 366 specifier: 5.1.6 379 367 version: 5.1.6 ··· 393 381 specifier: 3.21.4 394 382 version: 3.21.4 395 383 devDependencies: 384 + '@openstatus/tsconfig': 385 + specifier: workspace:^ 386 + version: link:../tsconfig 396 387 '@types/node': 397 388 specifier: 20.3.1 398 389 version: 20.3.1 399 390 ts-node: 400 391 specifier: 10.9.1 401 392 version: 10.9.1(@types/node@20.3.1)(typescript@5.1.6) 402 - tsconfig: 403 - specifier: workspace:^ 404 - version: link:../tsconfig 405 393 typescript: 406 394 specifier: 5.1.6 407 395 version: 5.1.6 ··· 445 433 specifier: 3.21.4 446 434 version: 3.21.4 447 435 devDependencies: 448 - tsconfig: 436 + '@openstatus/tsconfig': 449 437 specifier: workspace:^ 450 438 version: link:../tsconfig 451 439 typescript: ··· 492 480 packages/db: 493 481 dependencies: 494 482 '@libsql/client': 495 - specifier: 0.3.1 496 - version: 0.3.1(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@6.0.3) 483 + specifier: 0.3.4 484 + version: 0.3.4(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@6.0.3) 497 485 '@t3-oss/env-core': 498 486 specifier: 0.6.0 499 487 version: 0.6.0(typescript@5.1.6)(zod@3.21.4) ··· 502 490 version: 16.3.1 503 491 drizzle-orm: 504 492 specifier: 0.28.6 505 - version: 0.28.6(@libsql/client@0.3.1) 493 + version: 0.28.6(@libsql/client@0.3.4) 506 494 drizzle-zod: 507 495 specifier: 0.5.1 508 496 version: 0.5.1(drizzle-orm@0.28.6)(zod@3.21.4) ··· 510 498 specifier: 3.21.4 511 499 version: 3.21.4 512 500 devDependencies: 501 + '@openstatus/tsconfig': 502 + specifier: workspace:^ 503 + version: link:../tsconfig 513 504 '@types/node': 514 505 specifier: 20.3.1 515 506 version: 20.3.1 ··· 525 516 ts-node: 526 517 specifier: 10.9.1 527 518 version: 10.9.1(@types/node@20.3.1)(typescript@5.1.6) 528 - tsconfig: 529 - specifier: workspace:^ 530 - version: link:../tsconfig 531 519 typescript: 532 520 specifier: 5.1.6 533 521 version: 5.1.6 ··· 565 553 specifier: 3.21.4 566 554 version: 3.21.4 567 555 devDependencies: 556 + '@openstatus/tsconfig': 557 + specifier: workspace:* 558 + version: link:../tsconfig 568 559 '@types/node': 569 560 specifier: 20.3.1 570 561 version: 20.3.1 ··· 574 565 react: 575 566 specifier: 18.2.0 576 567 version: 18.2.0 577 - tsconfig: 578 - specifier: workspace:* 579 - version: link:../tsconfig 580 568 typescript: 581 569 specifier: 5.1.6 582 570 version: 5.1.6 ··· 599 587 specifier: ^3.21.4 600 588 version: 3.21.4 601 589 devDependencies: 590 + '@openstatus/tsconfig': 591 + specifier: workspace:* 592 + version: link:../../tsconfig 602 593 '@types/node': 603 594 specifier: 20.3.1 604 595 version: 20.3.1 ··· 611 602 next: 612 603 specifier: 13.4.12 613 604 version: 13.4.12(@babel/core@7.22.9)(react-dom@18.2.0)(react@18.2.0) 614 - tsconfig: 615 - specifier: workspace:* 616 - version: link:../../tsconfig 617 605 typescript: 618 606 specifier: 5.1.6 619 607 version: 5.1.6 ··· 627 615 specifier: 3.21.4 628 616 version: 3.21.4 629 617 devDependencies: 630 - tsconfig: 618 + '@openstatus/tsconfig': 631 619 specifier: workspace:^ 632 620 version: link:../tsconfig 633 621 typescript: ··· 643 631 specifier: 3.21.4 644 632 version: 3.21.4 645 633 devDependencies: 634 + '@openstatus/tsconfig': 635 + specifier: workspace:* 636 + version: link:../tsconfig 646 637 '@types/node': 647 638 specifier: 20.3.1 648 639 version: 20.3.1 649 - tsconfig: 650 - specifier: workspace:* 651 - version: link:../tsconfig 652 640 typescript: 653 641 specifier: 5.1.6 654 642 version: 5.1.6 ··· 660 648 '@openstatus/eslint-config': 661 649 specifier: workspace:* 662 650 version: link:../config/eslint 651 + '@openstatus/tsconfig': 652 + specifier: workspace:* 653 + version: link:../tsconfig 663 654 '@types/react': 664 655 specifier: 18.2.16 665 656 version: 18.2.16 ··· 672 663 react: 673 664 specifier: 18.2.0 674 665 version: 18.2.0 675 - tsconfig: 676 - specifier: workspace:* 677 - version: link:../tsconfig 678 666 typescript: 679 667 specifier: 5.1.6 680 668 version: 5.1.6 ··· 694 682 specifier: 1.22.0 695 683 version: 1.22.0 696 684 devDependencies: 685 + '@openstatus/tsconfig': 686 + specifier: workspace:* 687 + version: link:../tsconfig 697 688 '@types/node': 698 689 specifier: 20.3.1 699 690 version: 20.3.1 700 - tsconfig: 701 - specifier: workspace:* 702 - version: link:../tsconfig 703 691 tsup: 704 692 specifier: 7.1.0 705 693 version: 7.1.0(typescript@5.1.6) ··· 772 760 resolution: {integrity: sha512-TD+xbmsBLyYy/IxFimW/YL/9L2IEnM7/EoV9Aeh56U64Ify8o27HJcKjo38XY9Tcn0uOq1AX3thkKgvtWvwFQg==} 773 761 dev: false 774 762 775 - /@apidevtools/json-schema-ref-parser@9.1.2: 776 - resolution: {integrity: sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==} 777 - dependencies: 778 - '@jsdevtools/ono': 7.1.3 779 - '@types/json-schema': 7.0.12 780 - call-me-maybe: 1.0.2 781 - js-yaml: 4.1.0 782 - dev: false 783 - 784 - /@apidevtools/openapi-schemas@2.1.0: 785 - resolution: {integrity: sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==} 786 - engines: {node: '>=10'} 787 - dev: false 788 - 789 - /@apidevtools/swagger-methods@3.0.2: 790 - resolution: {integrity: sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==} 791 - dev: false 792 - 793 - /@apidevtools/swagger-parser@10.0.3(openapi-types@12.1.3): 794 - resolution: {integrity: sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==} 763 + /@asteasolutions/zod-to-openapi@5.5.0(zod@3.21.4): 764 + resolution: {integrity: sha512-d5HwrvM6dOKr3XdeF+DmashGvfEc+1oiEfbscugsiwSTrFtuMa7ETpW9sTNnVgn+hJaz+PRxPQUYD7q9/5dUig==} 795 765 peerDependencies: 796 - openapi-types: '>=7' 766 + zod: ^3.20.2 797 767 dependencies: 798 - '@apidevtools/json-schema-ref-parser': 9.1.2 799 - '@apidevtools/openapi-schemas': 2.1.0 800 - '@apidevtools/swagger-methods': 3.0.2 801 - '@jsdevtools/ono': 7.1.3 802 - call-me-maybe: 1.0.2 803 - openapi-types: 12.1.3 804 - z-schema: 5.0.5 768 + openapi3-ts: 4.1.2 769 + zod: 3.21.4 805 770 dev: false 806 771 807 772 /@babel/code-frame@7.22.5: ··· 2195 2160 react-dom: 18.2.0(react@18.2.0) 2196 2161 dev: false 2197 2162 2163 + /@hono/zod-openapi@0.4.0(hono@3.6.1)(zod@3.21.4): 2164 + resolution: {integrity: sha512-tyHXZMnfDAojLYeW+QxTaBGGkc/XaUvvfrJzuSPcCSu8LA+AjneGzzNYUKKB/rrJnrfBb+MkZYXlhb5/lmYxjQ==} 2165 + engines: {node: '>=16.0.0'} 2166 + peerDependencies: 2167 + hono: '*' 2168 + zod: 3.* 2169 + dependencies: 2170 + '@asteasolutions/zod-to-openapi': 5.5.0(zod@3.21.4) 2171 + '@hono/zod-validator': 0.1.8(hono@3.6.1)(zod@3.21.4) 2172 + hono: 3.6.1 2173 + zod: 3.21.4 2174 + dev: false 2175 + 2176 + /@hono/zod-validator@0.1.8(hono@3.6.1)(zod@3.21.4): 2177 + resolution: {integrity: sha512-/Kd/p/dUVKM7/1TY3jafTV+ngwEh3fOLmXCJQKb9esWsCom5WOJaNmZYv8jeRhyipu1xBvmN5jceRA3Ev3rmQw==} 2178 + peerDependencies: 2179 + hono: 3.* 2180 + zod: ^3.19.1 2181 + dependencies: 2182 + hono: 3.6.1 2183 + zod: 3.21.4 2184 + dev: false 2185 + 2198 2186 /@hookform/resolvers@3.1.1(react-hook-form@7.45.1): 2199 2187 resolution: {integrity: sha512-tS16bAUkqjITNSvbJuO1x7MXbn7Oe8ZziDTJdA9mMvsoYthnOOiznOTGBYwbdlYBgU+tgpI/BtTU3paRbCuSlg==} 2200 2188 peerDependencies: ··· 2336 2324 tslib: 2.6.1 2337 2325 dev: false 2338 2326 2339 - /@jsdevtools/ono@7.1.3: 2340 - resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} 2341 - dev: false 2342 - 2343 - /@libsql/client@0.3.1(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@6.0.3): 2344 - resolution: {integrity: sha512-43/zF8fJguXd6ENwYhddpbR05bDVx3BQQUZ/BsJ0b4zLJge+WFa2smC3ILVGqvVu4ZoixbC0sfLTdVPdd2NjDA==} 2327 + /@libsql/client@0.3.4(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@6.0.3): 2328 + resolution: {integrity: sha512-b1rpCzm02oQuh1zM1UKdKxhClyMMlgyFagcmsuGTve/AQxubO/FaRgoKXTcGaOqoRjymE1qf0sQM2UoyPvu3lA==} 2345 2329 dependencies: 2346 - '@libsql/hrana-client': 0.4.4(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@6.0.3) 2347 - better-sqlite3: 8.5.0 2330 + '@libsql/hrana-client': 0.5.2(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@6.0.3) 2331 + better-sqlite3: 8.6.0 2348 2332 js-base64: 3.7.5 2349 2333 transitivePeerDependencies: 2350 2334 - bufferutil ··· 2352 2336 - utf-8-validate 2353 2337 dev: false 2354 2338 2355 - /@libsql/hrana-client@0.4.4(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@6.0.3): 2356 - resolution: {integrity: sha512-BevUg0UBRLs5AEqn0fjrMcl49xCtwuFavgK4MzCb3PTtxpEbQ24oGXctspN9drBiUVmqSZr7go887aiLLzSO3A==} 2339 + /@libsql/hrana-client@0.5.2(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@6.0.3): 2340 + resolution: {integrity: sha512-0hVKUClh29Pb0bIIwqssfDH1nrsKO5SaBTfclIZ1d0W/rBBDsV6mBKx3FhQuULYu/5u0/gJcHdmdiA1SFIOXdQ==} 2357 2341 dependencies: 2358 - '@libsql/isomorphic-fetch': 0.1.6(encoding@0.1.13) 2359 - '@libsql/isomorphic-ws': 0.1.3(bufferutil@4.0.7)(utf-8-validate@6.0.3) 2342 + '@libsql/isomorphic-fetch': 0.1.7(encoding@0.1.13) 2343 + '@libsql/isomorphic-ws': 0.1.5(bufferutil@4.0.7)(utf-8-validate@6.0.3) 2360 2344 js-base64: 3.7.5 2361 2345 transitivePeerDependencies: 2362 2346 - bufferutil ··· 2364 2348 - utf-8-validate 2365 2349 dev: false 2366 2350 2367 - /@libsql/isomorphic-fetch@0.1.6(encoding@0.1.13): 2368 - resolution: {integrity: sha512-8qhxEDmVBDb54E9xdW1xqw3zLNShkMZpf5YQU3PvwjtKNLOPde59Oqez+RnZHsYkt9zQxxOF+7gSHVJeP/UWqg==} 2351 + /@libsql/isomorphic-fetch@0.1.7(encoding@0.1.13): 2352 + resolution: {integrity: sha512-/lSsPe0WTHn2SDTPbgbjUY2iF0C3Adtcwu7cajTe+3YtcxsV36u7L37/hmJIQQG8vuM/TCUu4TWb+ym/299aOg==} 2369 2353 dependencies: 2370 2354 '@types/node-fetch': 2.6.4 2371 2355 node-fetch: 2.6.12(encoding@0.1.13) ··· 2373 2357 - encoding 2374 2358 dev: false 2375 2359 2376 - /@libsql/isomorphic-ws@0.1.3(bufferutil@4.0.7)(utf-8-validate@6.0.3): 2377 - resolution: {integrity: sha512-54dZXgYwWDKsnfWv8GCVYvhn6RDlqFDGAc8EQMd941yvGMsGzo06Gn6Iyjw//nJ1iJO97FbXgoQ1apikoFD/WA==} 2360 + /@libsql/isomorphic-ws@0.1.5(bufferutil@4.0.7)(utf-8-validate@6.0.3): 2361 + resolution: {integrity: sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==} 2378 2362 dependencies: 2379 2363 '@types/ws': 8.5.5 2380 2364 ws: 8.13.0(bufferutil@4.0.7)(utf-8-validate@6.0.3) ··· 2453 2437 react: '>=16' 2454 2438 dependencies: 2455 2439 '@types/mdx': 2.0.5 2456 - '@types/react': 18.2.16 2440 + '@types/react': 18.2.21 2457 2441 react: 18.2.0 2458 2442 dev: false 2459 2443 ··· 2575 2559 2576 2560 /@next/env@13.4.12: 2577 2561 resolution: {integrity: sha512-RmHanbV21saP/6OEPBJ7yJMuys68cIf8OBBWd7+uj40LdpmswVAwe1uzeuFyUsd6SfeITWT3XnQfn6wULeKwDQ==} 2562 + dev: true 2578 2563 2579 2564 /@next/env@13.4.19: 2580 2565 resolution: {integrity: sha512-FsAT5x0jF2kkhNkKkukhsyYOrRqtSxrEhfliniIq0bwWbuXLgyt3Gv0Ml+b91XwjwArmuP7NxCiGd++GGKdNMQ==} ··· 2592 2577 cpu: [arm64] 2593 2578 os: [darwin] 2594 2579 requiresBuild: true 2580 + dev: true 2595 2581 optional: true 2596 2582 2597 2583 /@next/swc-darwin-arm64@13.4.19: ··· 2609 2595 cpu: [x64] 2610 2596 os: [darwin] 2611 2597 requiresBuild: true 2598 + dev: true 2612 2599 optional: true 2613 2600 2614 2601 /@next/swc-darwin-x64@13.4.19: ··· 2626 2613 cpu: [arm64] 2627 2614 os: [linux] 2628 2615 requiresBuild: true 2616 + dev: true 2629 2617 optional: true 2630 2618 2631 2619 /@next/swc-linux-arm64-gnu@13.4.19: ··· 2643 2631 cpu: [arm64] 2644 2632 os: [linux] 2645 2633 requiresBuild: true 2634 + dev: true 2646 2635 optional: true 2647 2636 2648 2637 /@next/swc-linux-arm64-musl@13.4.19: ··· 2660 2649 cpu: [x64] 2661 2650 os: [linux] 2662 2651 requiresBuild: true 2652 + dev: true 2663 2653 optional: true 2664 2654 2665 2655 /@next/swc-linux-x64-gnu@13.4.19: ··· 2677 2667 cpu: [x64] 2678 2668 os: [linux] 2679 2669 requiresBuild: true 2670 + dev: true 2680 2671 optional: true 2681 2672 2682 2673 /@next/swc-linux-x64-musl@13.4.19: ··· 2694 2685 cpu: [arm64] 2695 2686 os: [win32] 2696 2687 requiresBuild: true 2688 + dev: true 2697 2689 optional: true 2698 2690 2699 2691 /@next/swc-win32-arm64-msvc@13.4.19: ··· 2711 2703 cpu: [ia32] 2712 2704 os: [win32] 2713 2705 requiresBuild: true 2706 + dev: true 2714 2707 optional: true 2715 2708 2716 2709 /@next/swc-win32-ia32-msvc@13.4.19: ··· 2728 2721 cpu: [x64] 2729 2722 os: [win32] 2730 2723 requiresBuild: true 2724 + dev: true 2731 2725 optional: true 2732 2726 2733 2727 /@next/swc-win32-x64-msvc@13.4.19: ··· 5201 5195 resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} 5202 5196 dev: true 5203 5197 5204 - /@turbo/gen@1.10.9(@types/node@20.5.9)(typescript@5.1.6): 5198 + /@turbo/gen@1.10.9(@types/node@20.3.1)(typescript@5.1.6): 5205 5199 resolution: {integrity: sha512-EQdg4NqN032+o2Wgj9lg49puijGc4tbgKjGjDOi8+rN/RqE9CUKxKtrfaTGdDjfdC1Uy8FaaKk7y1zYK3zIojg==} 5206 5200 hasBin: true 5207 5201 dependencies: ··· 5212 5206 minimatch: 9.0.3 5213 5207 node-plop: 0.26.3 5214 5208 semver: 7.5.4 5215 - ts-node: 10.9.1(@types/node@20.5.9)(typescript@5.1.6) 5209 + ts-node: 10.9.1(@types/node@20.3.1)(typescript@5.1.6) 5216 5210 update-check: 1.5.4 5217 5211 validate-npm-package-name: 5.0.0 5218 5212 transitivePeerDependencies: ··· 5333 5327 /@types/hoist-non-react-statics@3.3.1: 5334 5328 resolution: {integrity: sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==} 5335 5329 dependencies: 5336 - '@types/react': 18.2.16 5330 + '@types/react': 18.2.21 5337 5331 hoist-non-react-statics: 3.3.2 5338 5332 dev: false 5339 5333 ··· 5489 5483 /@types/react-dom@18.2.5: 5490 5484 resolution: {integrity: sha512-sRQsOS/sCLnpQhR4DSKGTtWFE3FZjpQa86KPVbhUqdYMRZ9FEFcfAytKhR/vUG2rH1oFbOOej6cuD7MFSobDRQ==} 5491 5485 dependencies: 5492 - '@types/react': 18.2.16 5486 + '@types/react': 18.2.21 5493 5487 5494 5488 /@types/react-dom@18.2.7: 5495 5489 resolution: {integrity: sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==} 5496 5490 dependencies: 5497 - '@types/react': 18.2.16 5491 + '@types/react': 18.2.21 5498 5492 dev: true 5499 5493 5500 5494 /@types/react@18.2.12: ··· 5510 5504 '@types/prop-types': 15.7.5 5511 5505 '@types/scheduler': 0.16.3 5512 5506 csstype: 3.1.2 5507 + dev: true 5508 + 5509 + /@types/react@18.2.21: 5510 + resolution: {integrity: sha512-neFKG/sBAwGxHgXiIxnbm3/AAVQ/cMRS93hvBpg8xYRbeQSPVABp9U2bRnPf0iI4+Ucdv3plSxKK+3CW2ENJxA==} 5511 + dependencies: 5512 + '@types/prop-types': 15.7.5 5513 + '@types/scheduler': 0.16.3 5514 + csstype: 3.1.2 5513 5515 5514 5516 /@types/resolve@1.20.2: 5515 5517 resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} ··· 5541 5543 resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} 5542 5544 dev: false 5543 5545 5544 - /@types/swagger-jsdoc@6.0.1: 5545 - resolution: {integrity: sha512-+MUpcbyxD528dECUBCEVm6abNuORdbuGjbrUdHDeAQ+rkPuo2a+L4N02WJHF3bonSSE6SJ3dUJwF2V6+cHnf0w==} 5546 - dev: false 5547 - 5548 5546 /@types/swagger-ui-react@4.18.0: 5549 5547 resolution: {integrity: sha512-XtvFXmj46Zibe89tFQwSQknrq1NxEtOep2rZuxth7K88tyPEP00FnoA6H7ATYhocAEA4XUWaNHNFWFRl1KX8aQ==} 5550 5548 dependencies: 5551 - '@types/react': 18.2.16 5549 + '@types/react': 18.2.21 5552 5550 dev: true 5553 5551 5554 5552 /@types/through@0.0.30: ··· 5718 5716 eslint-visitor-keys: 3.4.1 5719 5717 dev: false 5720 5718 5719 + /@unkey/api@0.6.22: 5720 + resolution: {integrity: sha512-RcWr1/BuTAobx0fhbvKLuYTIDgmu7pvYqABWKhwHYpkwQg6wy+pWo5CBMsN5L30E9cd0RlM9TYiHuAdb1tk1iw==} 5721 + dev: false 5722 + 5721 5723 /@upstash/core-analytics@0.0.6: 5722 5724 resolution: {integrity: sha512-cpPSR0XJAJs4Ddz9nq3tINlPS5aLfWVCqhhtHnXt4p7qr5+/Znlt1Es736poB/9rnl1hAHrOsOvVj46NEXcVqA==} 5723 5725 engines: {node: '>=16.0.0'} ··· 6161 6163 resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==} 6162 6164 dev: false 6163 6165 6164 - /better-sqlite3@8.5.0: 6165 - resolution: {integrity: sha512-vbPcv/Hx5WYdyNg/NbcfyaBZyv9s/NVbxb7yCeC5Bq1pVocNxeL2tZmSu3Rlm4IEOTjYdGyzWQgyx0OSdORBzw==} 6166 + /better-sqlite3@8.6.0: 6167 + resolution: {integrity: sha512-jwAudeiTMTSyby+/SfbHDebShbmC2MCH8mU2+DXi0WJfv13ypEJm47cd3kljmy/H130CazEvkf2Li//ewcMJ1g==} 6166 6168 requiresBuild: true 6167 6169 dependencies: 6168 6170 bindings: 1.5.0 ··· 6306 6308 dependencies: 6307 6309 function-bind: 1.1.1 6308 6310 get-intrinsic: 1.2.1 6309 - dev: false 6310 - 6311 - /call-me-maybe@1.0.2: 6312 - resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==} 6313 6311 dev: false 6314 6312 6315 6313 /callsites@3.1.0: ··· 6521 6519 engines: {node: '>=6'} 6522 6520 dev: true 6523 6521 6524 - /cleye@1.3.2: 6525 - resolution: {integrity: sha512-MngIC2izcCz07iRKr3Pe8Z6ZBv4zbKFl/YnQEN/aMHis6PpH+MxI2e6n0bMUAmSVlMoAyQkdBCSTbfDmtcSovQ==} 6526 - dependencies: 6527 - terminal-columns: 1.4.1 6528 - type-flag: 3.0.0 6529 - dev: false 6530 - 6531 6522 /cli-color@2.0.3: 6532 6523 resolution: {integrity: sha512-OkoZnxyC4ERN3zLzZaY9Emb7f/MhBOIpePv0Ycok0fJYT+Ouo00UBEIwsVsr0yoow++n5YWlSUgST9GKhNHiRQ==} 6533 6524 engines: {node: '>=0.10'} ··· 6654 6645 engines: {node: '>= 6'} 6655 6646 dev: false 6656 6647 6657 - /commander@6.2.0: 6658 - resolution: {integrity: sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==} 6659 - engines: {node: '>= 6'} 6660 - dev: false 6661 - 6662 6648 /commander@7.2.0: 6663 6649 resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} 6664 6650 engines: {node: '>= 10'} ··· 6677 6663 /commander@9.5.0: 6678 6664 resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} 6679 6665 engines: {node: ^12.20.0 || >=14} 6666 + dev: true 6680 6667 6681 6668 /comment-json@4.2.3: 6682 6669 resolution: {integrity: sha512-SsxdiOf064DWoZLH799Ata6u7iV658A11PlWtZATDlXPpKGJnbJZ5Z24ybixAi+LUUqJ/GKowAejtC5GFUG7Tw==} ··· 7491 7478 - supports-color 7492 7479 dev: true 7493 7480 7494 - /drizzle-orm@0.28.6(@libsql/client@0.3.1): 7481 + /drizzle-orm@0.28.6(@libsql/client@0.3.4): 7495 7482 resolution: {integrity: sha512-yBe+F9htrlYER7uXgDJUQsTHFoIrI5yMm5A0bg0GiZ/kY5jNXTWoEy4KQtg35cE27sw1VbgzoMWHAgCckUUUww==} 7496 7483 peerDependencies: 7497 7484 '@aws-sdk/client-rds-data': '>=3' ··· 7553 7540 sqlite3: 7554 7541 optional: true 7555 7542 dependencies: 7556 - '@libsql/client': 0.3.1(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@6.0.3) 7543 + '@libsql/client': 0.3.4(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@6.0.3) 7557 7544 dev: false 7558 7545 7559 7546 /drizzle-zod@0.5.1(drizzle-orm@0.28.6)(zod@3.21.4): ··· 7562 7549 drizzle-orm: '>=0.23.13' 7563 7550 zod: '*' 7564 7551 dependencies: 7565 - drizzle-orm: 0.28.6(@libsql/client@0.3.1) 7552 + drizzle-orm: 0.28.6(@libsql/client@0.3.4) 7566 7553 zod: 3.21.4 7567 7554 dev: false 7568 7555 ··· 9159 9146 react-is: 16.13.1 9160 9147 dev: false 9161 9148 9149 + /hono@3.6.1: 9150 + resolution: {integrity: sha512-FaWXh0MSc2Hv2IrGI4vFvZEK69NHfggEgHUlNMXp2zrpKh23j7wS0Ku316Do9CFAl07OBNozBelcvruiBT8crQ==} 9151 + engines: {node: '>=16.0.0'} 9152 + dev: false 9153 + 9162 9154 /hosted-git-info@2.8.9: 9163 9155 resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} 9164 9156 dev: false ··· 10083 10075 10084 10076 /lodash.get@4.4.2: 10085 10077 resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} 10086 - 10087 - /lodash.isequal@4.5.0: 10088 - resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} 10089 - dev: false 10090 10078 10091 10079 /lodash.isplainobject@4.0.6: 10092 10080 resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} ··· 10095 10083 /lodash.merge@4.6.2: 10096 10084 resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} 10097 10085 10098 - /lodash.mergewith@4.6.2: 10099 - resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==} 10100 - dev: false 10101 - 10102 10086 /lodash.sortby@4.7.0: 10103 10087 resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} 10104 10088 dev: true ··· 11053 11037 react-dom: 18.2.0(react@18.2.0) 11054 11038 dev: false 11055 11039 11056 - /next-swagger-doc@0.4.0(next@13.4.12)(openapi-types@12.1.3): 11057 - resolution: {integrity: sha512-5Wt19Av4tOHVdXPJ7xTVvDJuiWuP5OYnY13BSq5SIjMtktFfhX4/9yZyaMCXImbUECu26PALn9CpRQRplIXJ3w==} 11058 - engines: {node: '>=12'} 11059 - hasBin: true 11060 - peerDependencies: 11061 - next: '>=9' 11062 - dependencies: 11063 - '@types/swagger-jsdoc': 6.0.1 11064 - cleye: 1.3.2 11065 - isarray: 2.0.5 11066 - next: 13.4.12(@babel/core@7.22.9)(react-dom@18.2.0)(react@18.2.0) 11067 - swagger-jsdoc: 6.2.8(openapi-types@12.1.3) 11068 - transitivePeerDependencies: 11069 - - openapi-types 11070 - dev: false 11071 - 11072 11040 /next-themes@0.2.1(next@13.4.19)(react-dom@18.2.0)(react@18.2.0): 11073 11041 resolution: {integrity: sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==} 11074 11042 peerDependencies: ··· 11126 11094 transitivePeerDependencies: 11127 11095 - '@babel/core' 11128 11096 - babel-plugin-macros 11097 + dev: true 11129 11098 11130 11099 /next@13.4.19(@babel/core@7.22.9)(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0): 11131 11100 resolution: {integrity: sha512-HuPSzzAbJ1T4BD8e0bs6B9C1kWQ6gv8ykZoRWs5AQoiIuqbGHHdQO7Ljuvg05Q0Z24E2ABozHe6FxDvI6HfyAw==} ··· 11470 11439 is-wsl: 2.2.0 11471 11440 dev: false 11472 11441 11473 - /openapi-types@12.1.3: 11474 - resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} 11442 + /openapi3-ts@4.1.2: 11443 + resolution: {integrity: sha512-B7gOkwsYMZO7BZXwJzXCuVagym2xhqsrilVvV0dnq2Di4+iLUXKVX9gOK23ZqaAHZOwABXN0QTdW8QnkUTX6DA==} 11444 + dependencies: 11445 + yaml: 2.3.1 11475 11446 dev: false 11476 11447 11477 11448 /optionator@0.9.3: ··· 13465 13436 - encoding 13466 13437 dev: false 13467 13438 13468 - /swagger-jsdoc@6.2.8(openapi-types@12.1.3): 13469 - resolution: {integrity: sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ==} 13470 - engines: {node: '>=12.0.0'} 13471 - hasBin: true 13472 - dependencies: 13473 - commander: 6.2.0 13474 - doctrine: 3.0.0 13475 - glob: 7.1.6 13476 - lodash.mergewith: 4.6.2 13477 - swagger-parser: 10.0.3(openapi-types@12.1.3) 13478 - yaml: 2.0.0-1 13479 - transitivePeerDependencies: 13480 - - openapi-types 13481 - dev: false 13482 - 13483 - /swagger-parser@10.0.3(openapi-types@12.1.3): 13484 - resolution: {integrity: sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==} 13485 - engines: {node: '>=10'} 13486 - dependencies: 13487 - '@apidevtools/swagger-parser': 10.0.3(openapi-types@12.1.3) 13488 - transitivePeerDependencies: 13489 - - openapi-types 13490 - dev: false 13491 - 13492 13439 /swagger-ui-react@5.6.2(react-dom@18.2.0)(react@18.2.0): 13493 13440 resolution: {integrity: sha512-UBjk84s+bu7KZ0vt4sU/R6Ej43Kf4prt9RRKFG+Bu1R4JQTNDXwlKz/+fsC2G98kxZDTkOrZKWnlJrHTSKGq7Q==} 13494 13441 peerDependencies: ··· 13667 13614 fs-constants: 1.0.0 13668 13615 inherits: 2.0.4 13669 13616 readable-stream: 3.6.2 13670 - dev: false 13671 - 13672 - /terminal-columns@1.4.1: 13673 - resolution: {integrity: sha512-IKVL/itiMy947XWVv4IHV7a0KQXvKjj4ptbi7Ew9MPMcOLzkiQeyx3Gyvh62hKrfJ0RZc4M1nbhzjNM39Kyujw==} 13674 13617 dev: false 13675 13618 13676 13619 /text-table@0.2.0: ··· 13904 13847 yn: 3.1.1 13905 13848 dev: true 13906 13849 13907 - /ts-node@10.9.1(@types/node@20.5.9)(typescript@5.1.6): 13908 - resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} 13909 - hasBin: true 13910 - peerDependencies: 13911 - '@swc/core': '>=1.2.50' 13912 - '@swc/wasm': '>=1.2.50' 13913 - '@types/node': '*' 13914 - typescript: '>=2.7' 13915 - peerDependenciesMeta: 13916 - '@swc/core': 13917 - optional: true 13918 - '@swc/wasm': 13919 - optional: true 13920 - dependencies: 13921 - '@cspotcode/source-map-support': 0.8.1 13922 - '@tsconfig/node10': 1.0.9 13923 - '@tsconfig/node12': 1.0.11 13924 - '@tsconfig/node14': 1.0.3 13925 - '@tsconfig/node16': 1.0.4 13926 - '@types/node': 20.5.9 13927 - acorn: 8.10.0 13928 - acorn-walk: 8.2.0 13929 - arg: 4.1.3 13930 - create-require: 1.1.1 13931 - diff: 4.0.2 13932 - make-error: 1.3.6 13933 - typescript: 5.1.6 13934 - v8-compile-cache-lib: 3.0.1 13935 - yn: 3.1.1 13936 - dev: true 13937 - 13938 13850 /ts-pattern@4.3.0: 13939 13851 resolution: {integrity: sha512-pefrkcd4lmIVR0LA49Imjf9DYLK8vtWhqBPA3Ya1ir8xCW0O2yjL9dsCVvI7pCodLC5q7smNpEtDR2yVulQxOg==} 13940 13852 dev: false ··· 14132 14044 /type-fest@3.13.1: 14133 14045 resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==} 14134 14046 engines: {node: '>=14.16'} 14135 - dev: false 14136 - 14137 - /type-flag@3.0.0: 14138 - resolution: {integrity: sha512-3YaYwMseXCAhBB14RXW5cRQfJQlEknS6i4C8fCfeUdS3ihG9EdccdR9kt3vP73ZdeTGmPb4bZtkDn5XMIn1DLA==} 14139 14047 dev: false 14140 14048 14141 14049 /type@1.2.0: ··· 14527 14435 builtins: 5.0.1 14528 14436 dev: true 14529 14437 14530 - /validator@13.11.0: 14531 - resolution: {integrity: sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==} 14532 - engines: {node: '>= 0.10'} 14533 - dev: false 14534 - 14535 14438 /vfile-location@4.1.0: 14536 14439 resolution: {integrity: sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==} 14537 14440 dependencies: ··· 14921 14824 engines: {node: '>= 6'} 14922 14825 dev: false 14923 14826 14924 - /yaml@2.0.0-1: 14925 - resolution: {integrity: sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==} 14926 - engines: {node: '>= 6'} 14927 - dev: false 14928 - 14929 14827 /yaml@2.3.1: 14930 14828 resolution: {integrity: sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==} 14931 14829 engines: {node: '>= 14'} ··· 14961 14859 resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} 14962 14860 engines: {node: '>=12.20'} 14963 14861 dev: true 14964 - 14965 - /z-schema@5.0.5: 14966 - resolution: {integrity: sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==} 14967 - engines: {node: '>=8.0.0'} 14968 - hasBin: true 14969 - dependencies: 14970 - lodash.get: 4.4.2 14971 - lodash.isequal: 4.5.0 14972 - validator: 13.11.0 14973 - optionalDependencies: 14974 - commander: 9.5.0 14975 - dev: false 14976 14862 14977 14863 /zenscroll@4.0.2: 14978 14864 resolution: {integrity: sha512-jEA1znR7b4C/NnaycInCU6h/d15ZzCd1jmsruqOKnZP6WXQSMH3W2GL+OXbkruslU4h+Tzuos0HdswzRUk/Vgg==}