aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/docs/03-configuration.md36
-rw-r--r--packages/api/index.ts5
-rw-r--r--packages/api/middlewares/prometheusAuth.ts33
-rw-r--r--packages/api/package.json1
-rw-r--r--packages/api/routes/metrics.ts16
-rw-r--r--packages/shared/config.ts6
-rw-r--r--packages/trpc/index.ts42
-rw-r--r--packages/trpc/package.json1
-rw-r--r--packages/trpc/stats.ts142
-rw-r--r--pnpm-lock.yaml104
10 files changed, 331 insertions, 55 deletions
diff --git a/docs/docs/03-configuration.md b/docs/docs/03-configuration.md
index d8981843..c7b533ff 100644
--- a/docs/docs/03-configuration.md
+++ b/docs/docs/03-configuration.md
@@ -2,30 +2,30 @@
The app is mainly configured by environment variables. All the used environment variables are listed in [packages/shared/config.ts](https://github.com/karakeep-app/karakeep/blob/main/packages/shared/config.ts). The most important ones are:
-| Name | Required | Default | Description |
-| ------------------------- | ------------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
-| DATA_DIR | Yes | Not set | The path for the persistent data directory. This is where the db lives. Assets are stored here by default unless `ASSETS_DIR` is set. |
-| ASSETS_DIR | No | Not set | The path where crawled assets will be stored. If not set, defaults to `${DATA_DIR}/assets`. |
-| NEXTAUTH_URL | Yes | Not set | Should point to the address of your server. The app will function without it, but will redirect you to wrong addresses on signout for example. |
-| NEXTAUTH_SECRET | Yes | Not set | Random string used to sign the JWT tokens. Generate one with `openssl rand -base64 36`. |
-| MEILI_ADDR | No | Not set | The address of meilisearch. If not set, Search will be disabled. E.g. (`http://meilisearch:7700`) |
-| MEILI_MASTER_KEY | Only in Prod and if search is enabled | Not set | The master key configured for meilisearch. Not needed in development environment. Generate one with `openssl rand -base64 36 \| tr -dc 'A-Za-z0-9'` |
-| MAX_ASSET_SIZE_MB | No | 50 | Sets the maximum allowed asset size (in MB) to be uploaded |
-| DISABLE_NEW_RELEASE_CHECK | No | false | If set to true, latest release check will be disabled in the admin panel. |
+| Name | Required | Default | Description |
+| ------------------------- | ------------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| DATA_DIR | Yes | Not set | The path for the persistent data directory. This is where the db lives. Assets are stored here by default unless `ASSETS_DIR` is set. |
+| ASSETS_DIR | No | Not set | The path where crawled assets will be stored. If not set, defaults to `${DATA_DIR}/assets`. |
+| NEXTAUTH_URL | Yes | Not set | Should point to the address of your server. The app will function without it, but will redirect you to wrong addresses on signout for example. |
+| NEXTAUTH_SECRET | Yes | Not set | Random string used to sign the JWT tokens. Generate one with `openssl rand -base64 36`. |
+| MEILI_ADDR | No | Not set | The address of meilisearch. If not set, Search will be disabled. E.g. (`http://meilisearch:7700`) |
+| MEILI_MASTER_KEY | Only in Prod and if search is enabled | Not set | The master key configured for meilisearch. Not needed in development environment. Generate one with `openssl rand -base64 36 \| tr -dc 'A-Za-z0-9'` |
+| MAX_ASSET_SIZE_MB | No | 50 | Sets the maximum allowed asset size (in MB) to be uploaded |
+| DISABLE_NEW_RELEASE_CHECK | No | false | If set to true, latest release check will be disabled in the admin panel. |
+| PROMETHEUS_AUTH_TOKEN | No | Not set | If set, will enable a prometheus metrics endpoint at `/api/metrics`. This endpoint will require this token being passed in the Authorization header as a Bearer token. If not set, that endpoint will return 404. |
## Asset Storage
Karakeep supports two storage backends for assets: local filesystem (default) and S3-compatible object storage. S3 storage is automatically detected when an S3 endpoint is passed.
-| Name | Required | Default | Description |
-| -------------------------------- | ----------------- | --------- | --------------------------------------------------------------------------------------------------------- |
-| ASSET_STORE_S3_ENDPOINT | No | Not set | The S3 endpoint URL. Required for S3-compatible services like MinIO. **Setting this enables S3 storage**. |
+| Name | Required | Default | Description |
+| -------------------------------- | ----------------- | ------- | --------------------------------------------------------------------------------------------------------- |
+| ASSET_STORE_S3_ENDPOINT | No | Not set | The S3 endpoint URL. Required for S3-compatible services like MinIO. **Setting this enables S3 storage**. |
| ASSET_STORE_S3_REGION | No | Not set | The S3 region to use. |
-| ASSET_STORE_S3_BUCKET | Yes when using S3 | Not set | The S3 bucket name where assets will be stored. |
-| ASSET_STORE_S3_ACCESS_KEY_ID | Yes when using S3 | Not set | The S3 access key ID for authentication. |
-| ASSET_STORE_S3_SECRET_ACCESS_KEY | Yes when using S3 | Not set | The S3 secret access key for authentication. |
-| ASSET_STORE_S3_FORCE_PATH_STYLE | No | false | Whether to force path-style URLs for S3 requests. Set to true for MinIO and other S3-compatible services. |
-
+| ASSET_STORE_S3_BUCKET | Yes when using S3 | Not set | The S3 bucket name where assets will be stored. |
+| ASSET_STORE_S3_ACCESS_KEY_ID | Yes when using S3 | Not set | The S3 access key ID for authentication. |
+| ASSET_STORE_S3_SECRET_ACCESS_KEY | Yes when using S3 | Not set | The S3 secret access key for authentication. |
+| ASSET_STORE_S3_FORCE_PATH_STYLE | No | false | Whether to force path-style URLs for S3 requests. Set to true for MinIO and other S3-compatible services. |
:::info
When using S3 storage, make sure the bucket exists and the provided credentials have the necessary permissions to read, write, and delete objects in the bucket.
diff --git a/packages/api/index.ts b/packages/api/index.ts
index 2eb22d8f..4103e033 100644
--- a/packages/api/index.ts
+++ b/packages/api/index.ts
@@ -10,6 +10,7 @@ import bookmarks from "./routes/bookmarks";
import health from "./routes/health";
import highlights from "./routes/highlights";
import lists from "./routes/lists";
+import metrics, { registerMetrics } from "./routes/metrics";
import publicRoute from "./routes/public";
import rss from "./routes/rss";
import tags from "./routes/tags";
@@ -37,6 +38,7 @@ const app = new Hono<{
}>()
.use(logger())
.use(poweredBy())
+ .use("*", registerMetrics)
.use(async (c, next) => {
// Ensure that the ctx is set
if (!c.var.ctx) {
@@ -49,6 +51,7 @@ const app = new Hono<{
.route("/trpc", trpc)
.route("/v1", v1)
.route("/assets", assets)
- .route("/public", publicRoute);
+ .route("/public", publicRoute)
+ .route("/metrics", metrics);
export default app;
diff --git a/packages/api/middlewares/prometheusAuth.ts b/packages/api/middlewares/prometheusAuth.ts
new file mode 100644
index 00000000..bf35608f
--- /dev/null
+++ b/packages/api/middlewares/prometheusAuth.ts
@@ -0,0 +1,33 @@
+import { createMiddleware } from "hono/factory";
+import { HTTPException } from "hono/http-exception";
+
+import serverConfig from "@karakeep/shared/config";
+
+export const prometheusAuthMiddleware = createMiddleware(async (c, next) => {
+ const { metricsToken } = serverConfig.prometheus;
+
+ // If no token is configured, deny access (safe default)
+ if (!metricsToken) {
+ throw new HTTPException(404, {
+ message: "Not Found",
+ });
+ }
+
+ const auth = c.req.header("Authorization");
+
+ if (!auth || !auth.startsWith("Bearer ")) {
+ throw new HTTPException(401, {
+ message: "Unauthorized",
+ });
+ }
+
+ const token = auth.slice(7); // Remove "Bearer " prefix
+
+ if (token !== metricsToken) {
+ throw new HTTPException(401, {
+ message: "Unauthorized",
+ });
+ }
+
+ await next();
+});
diff --git a/packages/api/package.json b/packages/api/package.json
index 54656e64..18d70501 100644
--- a/packages/api/package.json
+++ b/packages/api/package.json
@@ -13,6 +13,7 @@
"test": "vitest"
},
"dependencies": {
+ "@hono/prometheus": "^1.0.2",
"@hono/trpc-server": "^0.4.0",
"@hono/zod-validator": "^0.5.0",
"@karakeep/db": "workspace:*",
diff --git a/packages/api/routes/metrics.ts b/packages/api/routes/metrics.ts
new file mode 100644
index 00000000..90eff5b9
--- /dev/null
+++ b/packages/api/routes/metrics.ts
@@ -0,0 +1,16 @@
+// Import stats to register Prometheus metrics
+import "@karakeep/trpc/stats";
+
+import { prometheus } from "@hono/prometheus";
+import { Hono } from "hono";
+import { register } from "prom-client";
+
+import { prometheusAuthMiddleware } from "../middlewares/prometheusAuth";
+
+export const { printMetrics, registerMetrics } = prometheus({
+ registry: register,
+});
+
+const app = new Hono().get("/", prometheusAuthMiddleware, printMetrics);
+
+export default app;
diff --git a/packages/shared/config.ts b/packages/shared/config.ts
index 9294e154..c435d012 100644
--- a/packages/shared/config.ts
+++ b/packages/shared/config.ts
@@ -94,6 +94,9 @@ const allEnv = z.object({
// A flag to detect if the user is running in the old separete containers setup
USING_LEGACY_SEPARATE_CONTAINERS: stringBool("false"),
+ // Prometheus metrics configuration
+ PROMETHEUS_AUTH_TOKEN: z.string().optional(),
+
// Asset storage configuration
ASSET_STORE_S3_ENDPOINT: z.string().optional(),
ASSET_STORE_S3_REGION: z.string().optional(),
@@ -222,6 +225,9 @@ const serverConfigSchema = allEnv.transform((val) => {
forcePathStyle: val.ASSET_STORE_S3_FORCE_PATH_STYLE,
},
},
+ prometheus: {
+ metricsToken: val.PROMETHEUS_AUTH_TOKEN,
+ },
};
});
diff --git a/packages/trpc/index.ts b/packages/trpc/index.ts
index e34e56eb..90f37ae4 100644
--- a/packages/trpc/index.ts
+++ b/packages/trpc/index.ts
@@ -5,6 +5,12 @@ import { ZodError } from "zod";
import type { db } from "@karakeep/db";
import serverConfig from "@karakeep/shared/config";
+import {
+ apiErrorsTotalCounter,
+ apiRequestDurationSummary,
+ apiRequestsTotalCounter,
+} from "./stats";
+
interface User {
id: string;
name?: string | null | undefined;
@@ -36,6 +42,11 @@ const t = initTRPC.context<Context>().create({
transformer: superjson,
errorFormatter(opts) {
const { shape, error } = opts;
+ apiErrorsTotalCounter.inc({
+ type: opts.type,
+ path: opts.path,
+ code: error.code,
+ });
return {
...shape,
data: {
@@ -51,15 +62,30 @@ const t = initTRPC.context<Context>().create({
export const createCallerFactory = t.createCallerFactory;
// Base router and procedure helpers
export const router = t.router;
-export const procedure = t.procedure.use(function isDemoMode(opts) {
- if (serverConfig.demoMode && opts.type == "mutation") {
- throw new TRPCError({
- message: "Mutations are not allowed in demo mode",
- code: "FORBIDDEN",
+export const procedure = t.procedure
+ .use(function isDemoMode(opts) {
+ if (serverConfig.demoMode && opts.type == "mutation") {
+ throw new TRPCError({
+ message: "Mutations are not allowed in demo mode",
+ code: "FORBIDDEN",
+ });
+ }
+ return opts.next();
+ })
+ .use(async (opts) => {
+ const end = apiRequestDurationSummary.startTimer({
+ path: opts.path,
+ type: opts.type,
});
- }
- return opts.next();
-});
+ const res = await opts.next();
+ apiRequestsTotalCounter.inc({
+ type: opts.type,
+ path: opts.path,
+ is_error: res.ok ? 0 : 1,
+ });
+ end();
+ return res;
+ });
export const publicProcedure = procedure;
export const authedProcedure = procedure.use(function isAuthed(opts) {
diff --git a/packages/trpc/package.json b/packages/trpc/package.json
index b0280d6d..f4a9d122 100644
--- a/packages/trpc/package.json
+++ b/packages/trpc/package.json
@@ -19,6 +19,7 @@
"bcryptjs": "^2.4.3",
"deep-equal": "^2.2.3",
"drizzle-orm": "^0.38.3",
+ "prom-client": "^15.1.3",
"superjson": "^2.2.1",
"tiny-invariant": "^1.3.3",
"zod": "^3.24.2"
diff --git a/packages/trpc/stats.ts b/packages/trpc/stats.ts
new file mode 100644
index 00000000..465bddcd
--- /dev/null
+++ b/packages/trpc/stats.ts
@@ -0,0 +1,142 @@
+import { count, sum } from "drizzle-orm";
+import { Counter, Gauge, register, Summary } from "prom-client";
+
+import { db } from "@karakeep/db";
+import { assets, bookmarks, users } from "@karakeep/db/schema";
+import {
+ AssetPreprocessingQueue,
+ FeedQueue,
+ LinkCrawlerQueue,
+ OpenAIQueue,
+ RuleEngineQueue,
+ SearchIndexingQueue,
+ TidyAssetsQueue,
+ VideoWorkerQueue,
+ WebhookQueue,
+} from "@karakeep/shared/queues";
+
+// Queue metrics
+const queuePendingJobsGauge = new Gauge({
+ name: "queue_jobs",
+ help: "Number of jobs in each background queue",
+ labelNames: ["queue_name", "status"],
+ async collect() {
+ const queues = [
+ { name: "link_crawler", queue: LinkCrawlerQueue },
+ { name: "openai", queue: OpenAIQueue },
+ { name: "search_indexing", queue: SearchIndexingQueue },
+ { name: "tidy_assets", queue: TidyAssetsQueue },
+ { name: "video_worker", queue: VideoWorkerQueue },
+ { name: "feed", queue: FeedQueue },
+ { name: "asset_preprocessing", queue: AssetPreprocessingQueue },
+ { name: "webhook", queue: WebhookQueue },
+ { name: "rule_engine", queue: RuleEngineQueue },
+ ];
+
+ const stats = await Promise.all(
+ queues.map(async ({ name, queue }) => {
+ try {
+ return {
+ ...(await queue.stats()),
+ name,
+ };
+ } catch (error) {
+ console.error(`Failed to get stats for queue ${name}:`, error);
+ return { name, pending: 0, pending_retry: 0, failed: 0, running: 0 };
+ }
+ }),
+ );
+
+ stats.forEach(({ name, pending, pending_retry, failed, running }) => {
+ this.set({ queue_name: name, status: "pending" }, pending);
+ this.set({ queue_name: name, status: "pending_retry" }, pending_retry);
+ this.set({ queue_name: name, status: "failed" }, failed);
+ this.set({ queue_name: name, status: "running" }, running);
+ });
+ },
+});
+
+// User metrics
+const totalUsersGauge = new Gauge({
+ name: "total_users",
+ help: "Total number of users in the system",
+ async collect() {
+ try {
+ const result = await db.select({ count: count() }).from(users);
+ this.set(result[0]?.count ?? 0);
+ } catch (error) {
+ console.error("Failed to get user count:", error);
+ this.set(0);
+ }
+ },
+});
+
+// Asset metrics
+const totalAssetSizeGauge = new Gauge({
+ name: "total_asset_size_bytes",
+ help: "Total size of all assets in bytes",
+ async collect() {
+ try {
+ const result = await db
+ .select({ totalSize: sum(assets.size) })
+ .from(assets);
+ this.set(Number(result[0]?.totalSize ?? 0));
+ } catch (error) {
+ console.error("Failed to get total asset size:", error);
+ this.set(0);
+ }
+ },
+});
+
+// Bookmark metrics
+const totalBookmarksGauge = new Gauge({
+ name: "total_bookmarks",
+ help: "Total number of bookmarks in the system",
+ async collect() {
+ try {
+ const result = await db.select({ count: count() }).from(bookmarks);
+ this.set(result[0]?.count ?? 0);
+ } catch (error) {
+ console.error("Failed to get bookmark count:", error);
+ this.set(0);
+ }
+ },
+});
+
+// Api metrics
+const apiRequestsTotalCounter = new Counter({
+ name: "trpc_requests_total",
+ help: "Total number of API requests",
+ labelNames: ["type", "path", "is_error"],
+});
+
+const apiErrorsTotalCounter = new Counter({
+ name: "trpc_errors_total",
+ help: "Total number of API requests",
+ labelNames: ["type", "path", "code"],
+});
+
+const apiRequestDurationSummary = new Summary({
+ name: "trpc_request_duration_seconds",
+ help: "Duration of tRPC requests in seconds",
+ labelNames: ["type", "path"],
+});
+
+// Register all metrics
+register.registerMetric(queuePendingJobsGauge);
+register.registerMetric(totalUsersGauge);
+register.registerMetric(totalAssetSizeGauge);
+register.registerMetric(totalBookmarksGauge);
+register.registerMetric(apiRequestsTotalCounter);
+register.registerMetric(apiErrorsTotalCounter);
+register.registerMetric(apiRequestDurationSummary);
+
+export {
+ queuePendingJobsGauge,
+ totalUsersGauge,
+ totalAssetSizeGauge,
+ totalBookmarksGauge,
+ apiRequestsTotalCounter,
+ apiErrorsTotalCounter,
+ apiRequestDurationSummary,
+};
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index cafcae03..d9fc238d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -72,7 +72,7 @@ importers:
version: 11.0.0(@trpc/server@11.0.0(typescript@5.8.2))(typescript@5.8.2)
'@trpc/next':
specifier: 11.0.0
- version: 11.0.0(@tanstack/react-query@5.69.0(react@18.3.1))(@trpc/client@11.0.0(@trpc/server@11.0.0(typescript@5.8.2))(typescript@5.8.2))(@trpc/react-query@11.0.0(@tanstack/react-query@5.69.0(react@18.3.1))(@trpc/client@11.0.0(@trpc/server@11.0.0(typescript@5.8.2))(typescript@5.8.2))(@trpc/server@11.0.0(typescript@5.8.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2))(@trpc/server@11.0.0(typescript@5.8.2))(next@14.2.25(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2)
+ version: 11.0.0(@tanstack/react-query@5.69.0(react@18.3.1))(@trpc/client@11.0.0(@trpc/server@11.0.0(typescript@5.8.2))(typescript@5.8.2))(@trpc/react-query@11.0.0(@tanstack/react-query@5.69.0(react@18.3.1))(@trpc/client@11.0.0(@trpc/server@11.0.0(typescript@5.8.2))(typescript@5.8.2))(@trpc/server@11.0.0(typescript@5.8.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2))(@trpc/server@11.0.0(typescript@5.8.2))(next@14.2.25(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2)
'@trpc/react-query':
specifier: 11.0.0
version: 11.0.0(@tanstack/react-query@5.69.0(react@18.3.1))(@trpc/client@11.0.0(@trpc/server@11.0.0(typescript@5.8.2))(typescript@5.8.2))(@trpc/server@11.0.0(typescript@5.8.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2)
@@ -604,7 +604,7 @@ importers:
version: 1.11.10
drizzle-orm:
specifier: ^0.38.3
- version: 0.38.3(@types/better-sqlite3@7.6.13)(@types/react@18.3.12)(better-sqlite3@11.3.0)(react@18.3.1)
+ version: 0.38.3(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/react@18.3.12)(better-sqlite3@11.3.0)(react@18.3.1)
fastest-levenshtein:
specifier: ^1.0.16
version: 1.0.16
@@ -622,22 +622,22 @@ importers:
version: 0.501.0(react@18.3.1)
next:
specifier: 14.2.25
- version: 14.2.25(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1)
+ version: 14.2.25(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1)
next-auth:
specifier: ^4.24.11
- version: 4.24.11(next@14.2.25(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ version: 4.24.11(next@14.2.25(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
next-i18next:
specifier: ^15.3.1
- version: 15.3.1(i18next@23.16.5)(next@14.2.25(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1))(react-i18next@15.1.1(i18next@23.16.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
+ version: 15.3.1(i18next@23.16.5)(next@14.2.25(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1))(react-i18next@15.1.1(i18next@23.16.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
next-pwa:
specifier: ^5.6.0
- version: 5.6.0(@babel/core@7.26.0)(@types/babel__core@7.20.5)(next@14.2.25(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1))(webpack@5.99.9)
+ version: 5.6.0(@babel/core@7.26.0)(@types/babel__core@7.20.5)(next@14.2.25(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1))(webpack@5.99.9)
next-themes:
specifier: ^0.3.0
version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
nuqs:
specifier: ^2.4.3
- version: 2.4.3(next@14.2.25(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1))(react-router-dom@6.22.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@6.22.1(react@18.3.1))(react@18.3.1)
+ version: 2.4.3(next@14.2.25(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1))(react-router-dom@6.22.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@6.22.1(react@18.3.1))(react@18.3.1)
prettier:
specifier: ^3.4.2
version: 3.4.2
@@ -782,7 +782,7 @@ importers:
version: 16.4.5
drizzle-orm:
specifier: ^0.38.3
- version: 0.38.3(@types/better-sqlite3@7.6.13)(@types/react@19.1.6)(better-sqlite3@11.3.0)(react@18.3.1)
+ version: 0.38.3(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/react@19.1.6)(better-sqlite3@11.3.0)(react@18.3.1)
execa:
specifier: 9.3.1
version: 9.3.1
@@ -791,7 +791,7 @@ importers:
version: 24.1.3
liteque:
specifier: ^0.3.2
- version: 0.3.2(@types/better-sqlite3@7.6.13)(@types/react@19.1.6)(better-sqlite3@11.3.0)(react@18.3.1)
+ version: 0.3.2(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/react@19.1.6)(better-sqlite3@11.3.0)(react@18.3.1)
metascraper:
specifier: ^5.46.18
version: 5.47.1
@@ -929,6 +929,9 @@ importers:
packages/api:
dependencies:
+ '@hono/prometheus':
+ specifier: ^1.0.2
+ version: 1.0.2(hono@4.7.11)(prom-client@15.1.3)
'@hono/trpc-server':
specifier: ^0.4.0
version: 0.4.0(@trpc/server@11.0.0(typescript@5.8.2))(hono@4.7.11)
@@ -995,7 +998,7 @@ importers:
version: 16.4.5
drizzle-orm:
specifier: ^0.38.3
- version: 0.38.3(@types/better-sqlite3@7.6.13)(@types/react@19.1.6)(better-sqlite3@11.3.0)(react@18.3.1)
+ version: 0.38.3(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/react@19.1.6)(better-sqlite3@11.3.0)(react@18.3.1)
tsx:
specifier: ^4.7.1
version: 4.7.1
@@ -1110,7 +1113,7 @@ importers:
version: 1.0.20
liteque:
specifier: ^0.3.2
- version: 0.3.2(@types/better-sqlite3@7.6.13)(@types/react@19.1.6)(better-sqlite3@11.3.0)(react@18.3.1)
+ version: 0.3.2(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/react@19.1.6)(better-sqlite3@11.3.0)(react@18.3.1)
meilisearch:
specifier: ^0.37.0
version: 0.37.0(encoding@0.1.13)
@@ -1193,7 +1196,10 @@ importers:
version: 2.2.3
drizzle-orm:
specifier: ^0.38.3
- version: 0.38.3(@types/better-sqlite3@7.6.13)(@types/react@19.1.6)(better-sqlite3@11.3.0)(react@18.3.1)
+ version: 0.38.3(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/react@19.1.6)(better-sqlite3@11.3.0)(react@18.3.1)
+ prom-client:
+ specifier: ^15.1.3
+ version: 15.1.3
superjson:
specifier: ^2.2.1
version: 2.2.1
@@ -3294,6 +3300,12 @@ packages:
'@hapi/topo@5.1.0':
resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==}
+ '@hono/prometheus@1.0.2':
+ resolution: {integrity: sha512-7z2nBMaiHEaAFfNWfIV2H5/HRezv9kLH0jDY6ZotQQAr3QR7cIYAd6FGiyTIng4GUAw6ZWeX3C0Y4QS36SLRjg==}
+ peerDependencies:
+ hono: '>=3.*'
+ prom-client: ^15.0.0
+
'@hono/trpc-server@0.4.0':
resolution: {integrity: sha512-LGlJfCmNIGMwcknZEIYdujVMs9OkNVazhpOhaz3kTWOXvNL660VOHpvvktosCiJrajyBY1RtIJKQ+IKaQvNuSg==}
engines: {node: '>=16.0.0'}
@@ -3809,6 +3821,10 @@ packages:
resolution: {integrity: sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==}
engines: {node: ^18.17.0 || >=20.5.0}
+ '@opentelemetry/api@1.9.0':
+ resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==}
+ engines: {node: '>=8.0.0'}
+
'@oxlint/darwin-arm64@1.2.0':
resolution: {integrity: sha512-DsdZPp59sPPmuI6pR6MP1QepWWkpibFhVmRXa7ZOUobxxubUBg12SVCchAI1Iq8jejcAg9/XHXsRFpuny2LawQ==}
cpu: [arm64]
@@ -6092,6 +6108,9 @@ packages:
bindings@1.5.0:
resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
+ bintrees@1.0.2:
+ resolution: {integrity: sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==}
+
bl@4.1.0:
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
@@ -11711,6 +11730,10 @@ packages:
resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
engines: {node: '>=0.4.0'}
+ prom-client@15.1.3:
+ resolution: {integrity: sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==}
+ engines: {node: ^16 || ^18 || >=20}
+
promise-retry@2.0.1:
resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==}
engines: {node: '>=10'}
@@ -13246,6 +13269,9 @@ packages:
resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==}
engines: {node: '>=18'}
+ tdigest@0.1.2:
+ resolution: {integrity: sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==}
+
temp-dir@2.0.0:
resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==}
engines: {node: '>=8'}
@@ -18381,6 +18407,11 @@ snapshots:
dependencies:
'@hapi/hoek': 9.3.0
+ '@hono/prometheus@1.0.2(hono@4.7.11)(prom-client@15.1.3)':
+ dependencies:
+ hono: 4.7.11
+ prom-client: 15.1.3
+
'@hono/trpc-server@0.4.0(@trpc/server@11.0.0(typescript@5.8.2))(hono@4.7.11)':
dependencies:
'@trpc/server': 11.0.0(typescript@5.8.2)
@@ -19030,6 +19061,8 @@ snapshots:
dependencies:
semver: 7.7.2
+ '@opentelemetry/api@1.9.0': {}
+
'@oxlint/darwin-arm64@1.2.0':
optional: true
@@ -20712,11 +20745,11 @@ snapshots:
'@trpc/server': 11.0.0(typescript@5.8.2)
typescript: 5.8.2
- '@trpc/next@11.0.0(@tanstack/react-query@5.69.0(react@18.3.1))(@trpc/client@11.0.0(@trpc/server@11.0.0(typescript@5.8.2))(typescript@5.8.2))(@trpc/react-query@11.0.0(@tanstack/react-query@5.69.0(react@18.3.1))(@trpc/client@11.0.0(@trpc/server@11.0.0(typescript@5.8.2))(typescript@5.8.2))(@trpc/server@11.0.0(typescript@5.8.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2))(@trpc/server@11.0.0(typescript@5.8.2))(next@14.2.25(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2)':
+ '@trpc/next@11.0.0(@tanstack/react-query@5.69.0(react@18.3.1))(@trpc/client@11.0.0(@trpc/server@11.0.0(typescript@5.8.2))(typescript@5.8.2))(@trpc/react-query@11.0.0(@tanstack/react-query@5.69.0(react@18.3.1))(@trpc/client@11.0.0(@trpc/server@11.0.0(typescript@5.8.2))(typescript@5.8.2))(@trpc/server@11.0.0(typescript@5.8.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2))(@trpc/server@11.0.0(typescript@5.8.2))(next@14.2.25(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.2)':
dependencies:
'@trpc/client': 11.0.0(@trpc/server@11.0.0(typescript@5.8.2))(typescript@5.8.2)
'@trpc/server': 11.0.0(typescript@5.8.2)
- next: 14.2.25(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1)
+ next: 14.2.25(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
typescript: 5.8.2
@@ -21790,6 +21823,8 @@ snapshots:
dependencies:
file-uri-to-path: 1.0.0
+ bintrees@1.0.2: {}
+
bl@4.1.0:
dependencies:
buffer: 5.7.1
@@ -23056,22 +23091,25 @@ snapshots:
transitivePeerDependencies:
- supports-color
- drizzle-orm@0.33.0(@types/better-sqlite3@7.6.13)(@types/react@19.1.6)(better-sqlite3@11.3.0)(react@18.3.1):
+ drizzle-orm@0.33.0(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/react@19.1.6)(better-sqlite3@11.3.0)(react@18.3.1):
optionalDependencies:
+ '@opentelemetry/api': 1.9.0
'@types/better-sqlite3': 7.6.13
'@types/react': 19.1.6
better-sqlite3: 11.3.0
react: 18.3.1
- drizzle-orm@0.38.3(@types/better-sqlite3@7.6.13)(@types/react@18.3.12)(better-sqlite3@11.3.0)(react@18.3.1):
+ drizzle-orm@0.38.3(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/react@18.3.12)(better-sqlite3@11.3.0)(react@18.3.1):
optionalDependencies:
+ '@opentelemetry/api': 1.9.0
'@types/better-sqlite3': 7.6.13
'@types/react': 18.3.12
better-sqlite3: 11.3.0
react: 18.3.1
- drizzle-orm@0.38.3(@types/better-sqlite3@7.6.13)(@types/react@19.1.6)(better-sqlite3@11.3.0)(react@18.3.1):
+ drizzle-orm@0.38.3(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/react@19.1.6)(better-sqlite3@11.3.0)(react@18.3.1):
optionalDependencies:
+ '@opentelemetry/api': 1.9.0
'@types/better-sqlite3': 7.6.13
'@types/react': 19.1.6
better-sqlite3: 11.3.0
@@ -25714,11 +25752,11 @@ snapshots:
liquid-json@0.3.1: {}
- liteque@0.3.2(@types/better-sqlite3@7.6.13)(@types/react@19.1.6)(better-sqlite3@11.3.0)(react@18.3.1):
+ liteque@0.3.2(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/react@19.1.6)(better-sqlite3@11.3.0)(react@18.3.1):
dependencies:
async-mutex: 0.4.1
better-sqlite3: 11.3.0
- drizzle-orm: 0.33.0(@types/better-sqlite3@7.6.13)(@types/react@19.1.6)(better-sqlite3@11.3.0)(react@18.3.1)
+ drizzle-orm: 0.33.0(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/react@19.1.6)(better-sqlite3@11.3.0)(react@18.3.1)
zod: 3.24.2
transitivePeerDependencies:
- '@aws-sdk/client-rds-data'
@@ -27279,13 +27317,13 @@ snapshots:
nested-error-stacks@2.0.1: {}
- next-auth@4.24.11(next@14.2.25(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+ next-auth@4.24.11(next@14.2.25(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@babel/runtime': 7.27.6
'@panva/hkdf': 1.2.1
cookie: 0.7.2
jose: 4.15.9
- next: 14.2.25(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1)
+ next: 14.2.25(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1)
oauth: 0.9.15
openid-client: 5.7.1
preact: 10.11.3
@@ -27294,7 +27332,7 @@ snapshots:
react-dom: 18.3.1(react@18.3.1)
uuid: 8.3.2
- next-i18next@15.3.1(i18next@23.16.5)(next@14.2.25(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1))(react-i18next@15.1.1(i18next@23.16.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1):
+ next-i18next@15.3.1(i18next@23.16.5)(next@14.2.25(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1))(react-i18next@15.1.1(i18next@23.16.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1):
dependencies:
'@babel/runtime': 7.27.6
'@types/hoist-non-react-statics': 3.3.6
@@ -27302,16 +27340,16 @@ snapshots:
hoist-non-react-statics: 3.3.2
i18next: 23.16.5
i18next-fs-backend: 2.6.0
- next: 14.2.25(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1)
+ next: 14.2.25(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1)
react: 18.3.1
react-i18next: 15.1.1(i18next@23.16.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- next-pwa@5.6.0(@babel/core@7.26.0)(@types/babel__core@7.20.5)(next@14.2.25(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1))(webpack@5.99.9):
+ next-pwa@5.6.0(@babel/core@7.26.0)(@types/babel__core@7.20.5)(next@14.2.25(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1))(webpack@5.99.9):
dependencies:
babel-loader: 8.4.1(@babel/core@7.26.0)(webpack@5.99.9)
clean-webpack-plugin: 4.0.0(webpack@5.99.9)
globby: 11.1.0
- next: 14.2.25(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1)
+ next: 14.2.25(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1)
terser-webpack-plugin: 5.3.14(webpack@5.99.9)
workbox-webpack-plugin: 6.6.0(@types/babel__core@7.20.5)(webpack@5.99.9)
workbox-window: 6.6.0
@@ -27331,7 +27369,7 @@ snapshots:
next-tick@1.1.0: {}
- next@14.2.25(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1):
+ next@14.2.25(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1):
dependencies:
'@next/env': 14.2.25
'@swc/helpers': 0.5.5
@@ -27352,6 +27390,7 @@ snapshots:
'@next/swc-win32-arm64-msvc': 14.2.25
'@next/swc-win32-ia32-msvc': 14.2.25
'@next/swc-win32-x64-msvc': 14.2.25
+ '@opentelemetry/api': 1.9.0
sass: 1.89.1
transitivePeerDependencies:
- '@babel/core'
@@ -27476,12 +27515,12 @@ snapshots:
nullthrows@1.1.1: {}
- nuqs@2.4.3(next@14.2.25(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1))(react-router-dom@6.22.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@6.22.1(react@18.3.1))(react@18.3.1):
+ nuqs@2.4.3(next@14.2.25(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1))(react-router-dom@6.22.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@6.22.1(react@18.3.1))(react@18.3.1):
dependencies:
mitt: 3.0.1
react: 18.3.1
optionalDependencies:
- next: 14.2.25(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1)
+ next: 14.2.25(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.89.1)
react-router: 6.22.1(react@18.3.1)
react-router-dom: 6.22.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -28582,6 +28621,11 @@ snapshots:
progress@2.0.3: {}
+ prom-client@15.1.3:
+ dependencies:
+ '@opentelemetry/api': 1.9.0
+ tdigest: 0.1.2
+
promise-retry@2.0.1:
dependencies:
err-code: 2.0.3
@@ -30623,6 +30667,10 @@ snapshots:
mkdirp: 3.0.1
yallist: 5.0.0
+ tdigest@0.1.2:
+ dependencies:
+ bintrees: 1.0.2
+
temp-dir@2.0.0: {}
temp@0.8.4: