b9f3de80f5
The metrics INFRA is already deployed (VictoriaMetrics scraping docmost:9464,
Grafana dashboards, alerts) with a target `gitmost-app` that is red because the
app half didn't exist. This is that half. The contract (metric names, port,
table, endpoint) is FIXED by the deployed infra and matched exactly.
Server (prom-client):
- A bare node:http `/metrics` server on METRICS_PORT (default 9464), SEPARATE
from the Fastify :3000 listener so /metrics never exists publicly; the whole
subsystem is OFF when METRICS_PORT is unset.
- collectDefaultMetrics() + http_request_duration_seconds{method,route,status}
via a Fastify onResponse hook using the ROUTE TEMPLATE (req.routeOptions.url,
never the raw URL — bounded cardinality; 404 -> "unknown"), EXCLUDING SSE/
streaming responses (would record the connection lifetime and poison p95).
- db_query_duration_seconds (Kysely log callback, labelled by the leading SQL
token), bullmq_queue_depth{queue} (getJobCounts every 15s) +
bullmq_job_duration_seconds{queue} (worker completed/failed),
collab_store_duration_seconds (around onStoreDocument).
- POST /api/telemetry/vitals — PUBLIC (sendBeacon) but IP-throttled; ~16KB body
cap, <=50 events/batch, metric-name + rating whitelist, attr truncated to 120
chars, batch insert; malformed/foreign/oversized silently dropped and 200'd (no
browser retry). New migration `client_metrics` (schema byte-identical to the
contract, both indexes, conditional grafana_ro GRANT; no app-side retention —
the maintenance container prunes >90d).
Client (web-vitals):
- initVitals() decides sampling ONCE per session (25%, sessionStorage) BEFORE
subscribing; onINP/onLCP/onCLS/onTTFB (attribution) buffered + flushed via
navigator.sendBeacon on visibilitychange:hidden and a timer (not fetch-per-
metric). Custom: editor_tx_ms (dispatchTransaction sync-part timer, >8ms, with
doc_size), page_open_ms, longtask_ms. Route labels are templates only; no
titles/slugs/text.
Gate: server + client tsc 0, frozen install 0 (added prom-client + web-vitals +
regenerated the lock), server metrics/vitals tests 11, client route-template 5,
and the migration verified valid against real Postgres.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
211 lines
6.7 KiB
JSON
211 lines
6.7 KiB
JSON
{
|
|
"name": "server",
|
|
"version": "0.94.1",
|
|
"description": "",
|
|
"author": "",
|
|
"private": true,
|
|
"license": "",
|
|
"scripts": {
|
|
"build": "nest build",
|
|
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
|
"start": "cross-env NODE_ENV=development nest start",
|
|
"start:dev": "cross-env NODE_ENV=development nest start --watch",
|
|
"start:debug": "cross-env NODE_ENV=development nest start --debug --watch",
|
|
"start:prod": "cross-env NODE_ENV=production node --heapsnapshot-near-heap-limit=2 dist/main",
|
|
"collab:prod": "cross-env NODE_ENV=production node dist/collaboration/server/collab-main",
|
|
"collab:dev": "cross-env NODE_ENV=development node dist/collaboration/server/collab-main",
|
|
"email:dev": "email dev -p 5019 -d ./src/integrations/transactional/emails",
|
|
"migration:create": "tsx src/database/migrate.ts create",
|
|
"migration:up": "tsx src/database/migrate.ts up",
|
|
"migration:down": "tsx src/database/migrate.ts down",
|
|
"migration:latest": "tsx src/database/migrate.ts latest",
|
|
"migration:redo": "tsx src/database/migrate.ts redo",
|
|
"migration:reset": "tsx src/database/migrate.ts down-to NO_MIGRATIONS",
|
|
"migration:codegen": "kysely-codegen --dialect=postgres --camel-case --env-file=../../.env --out-file=./src/database/types/db.d.ts",
|
|
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
|
"pretest": "pnpm --filter @docmost/editor-ext build",
|
|
"test": "jest",
|
|
"test:int": "jest --config test/jest-integration.json",
|
|
"test:watch": "jest --watch",
|
|
"test:cov": "jest --coverage",
|
|
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
|
"test:e2e": "jest --config test/jest-e2e.json"
|
|
},
|
|
"dependencies": {
|
|
"@ai-sdk/google": "^3.0.52",
|
|
"@ai-sdk/mcp": "^1.0.51",
|
|
"@ai-sdk/openai": "^3.0.47",
|
|
"@ai-sdk/openai-compatible": "^2.0.37",
|
|
"@aws-sdk/client-s3": "3.1050.0",
|
|
"@aws-sdk/lib-storage": "3.1050.0",
|
|
"@aws-sdk/s3-request-presigner": "3.1050.0",
|
|
"@azure/storage-blob": "12.31.0",
|
|
"@clickhouse/client": "^1.18.2",
|
|
"@docmost/mcp": "workspace:*",
|
|
"@docmost/pdf-inspector": "1.9.6",
|
|
"@fastify/cookie": "^11.0.2",
|
|
"@fastify/multipart": "^10.0.0",
|
|
"@fastify/static": "^9.1.3",
|
|
"@keyv/redis": "^5.1.6",
|
|
"@langchain/core": "1.1.46",
|
|
"@langchain/textsplitters": "1.0.1",
|
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
"@nest-lab/throttler-storage-redis": "^1.2.0",
|
|
"@nestjs-labs/nestjs-ioredis": "^11.0.4",
|
|
"@nestjs/bullmq": "^11.0.4",
|
|
"@nestjs/cache-manager": "^3.1.0",
|
|
"@nestjs/common": "^11.1.19",
|
|
"@nestjs/config": "^4.0.4",
|
|
"@nestjs/core": "^11.1.19",
|
|
"@nestjs/event-emitter": "^3.0.1",
|
|
"@nestjs/jwt": "11.0.2",
|
|
"@nestjs/mapped-types": "^2.1.1",
|
|
"@nestjs/passport": "^11.0.5",
|
|
"@nestjs/platform-fastify": "^11.1.19",
|
|
"@nestjs/platform-socket.io": "^11.1.19",
|
|
"@nestjs/schedule": "^6.1.3",
|
|
"@nestjs/terminus": "^11.1.1",
|
|
"@nestjs/throttler": "^6.5.0",
|
|
"@nestjs/websockets": "^11.1.19",
|
|
"@node-saml/passport-saml": "^5.1.0",
|
|
"@socket.io/redis-adapter": "^8.3.0",
|
|
"ai": "^6.0.134",
|
|
"ai-sdk-ollama": "^3.8.1",
|
|
"bcrypt": "^6.0.0",
|
|
"bowser": "^2.14.1",
|
|
"bullmq": "^5.76.10",
|
|
"cache-manager": "^7.2.8",
|
|
"cheerio": "^1.2.0",
|
|
"class-transformer": "^0.5.1",
|
|
"class-validator": "^0.15.1",
|
|
"cookie": "^1.1.1",
|
|
"fast-bm25": "0.0.5",
|
|
"fastify-ip": "^2.0.0",
|
|
"fs-extra": "^11.3.4",
|
|
"happy-dom": "20.8.9",
|
|
"ioredis": "^5.10.1",
|
|
"ipaddr.js": "^2.2.0",
|
|
"js-tiktoken": "^1.0.21",
|
|
"jsonwebtoken": "^9.0.3",
|
|
"kysely": "^0.28.17",
|
|
"kysely-migration-cli": "^0.4.2",
|
|
"kysely-postgres-js": "^3.0.0",
|
|
"ldapts": "^8.1.7",
|
|
"lib0": "^0.2.117",
|
|
"mammoth": "^1.12.0",
|
|
"mime-types": "^3.0.2",
|
|
"msgpackr": "^1.11.9",
|
|
"nanoid": "5.1.7",
|
|
"nestjs-cls": "^6.2.0",
|
|
"nestjs-kysely": "^3.1.2",
|
|
"nestjs-pino": "^4.6.1",
|
|
"nodemailer": "^8.0.5",
|
|
"openid-client": "^6.8.2",
|
|
"otpauth": "^9.5.0",
|
|
"p-limit": "^7.3.0",
|
|
"passport-google-oauth20": "^2.0.0",
|
|
"passport-jwt": "^4.0.1",
|
|
"pg-tsquery": "^8.4.2",
|
|
"pgvector": "^0.2.1",
|
|
"pino-http": "^11.0.0",
|
|
"pino-pretty": "^13.1.3",
|
|
"postgres": "^3.4.8",
|
|
"postmark": "^4.0.7",
|
|
"prom-client": "^15.1.3",
|
|
"react": "^18.3.1",
|
|
"react-email": "6.0.8",
|
|
"reflect-metadata": "^0.2.2",
|
|
"rxjs": "^7.8.2",
|
|
"sanitize-filename": "1.6.3",
|
|
"scimmy": "1.3.5",
|
|
"socket.io": "^4.8.3",
|
|
"stripe": "^17.7.0",
|
|
"tlds": "^1.261.0",
|
|
"tmp-promise": "^3.0.3",
|
|
"tseep": "^1.3.1",
|
|
"typesense": "^3.0.5",
|
|
"undici": "7.24.0",
|
|
"ws": "^8.20.1",
|
|
"yaml": "^2.8.3",
|
|
"yauzl": "^3.2.1",
|
|
"zod": "^4.3.6"
|
|
},
|
|
"devDependencies": {
|
|
"@eslint/js": "^9.28.0",
|
|
"@nestjs/cli": "^11.0.21",
|
|
"@nestjs/schematics": "^11.0.10",
|
|
"@nestjs/testing": "^11.1.19",
|
|
"@types/bcrypt": "^6.0.0",
|
|
"@types/debounce": "^1.2.4",
|
|
"@types/fs-extra": "^11.0.4",
|
|
"@types/jest": "^30.0.0",
|
|
"@types/mime-types": "^3.0.1",
|
|
"@types/node": "^25.5.0",
|
|
"@types/nodemailer": "^7.0.11",
|
|
"@types/passport-google-oauth20": "^2.0.17",
|
|
"@types/passport-jwt": "^4.0.1",
|
|
"@types/supertest": "^6.0.3",
|
|
"@types/ws": "^8.18.1",
|
|
"@types/yauzl": "^2.10.3",
|
|
"eslint": "^9.28.0",
|
|
"eslint-config-prettier": "^10.1.8",
|
|
"globals": "^17.4.0",
|
|
"jest": "^30.3.0",
|
|
"kysely-codegen": "^0.20.0",
|
|
"prettier": "^3.8.1",
|
|
"source-map-support": "^0.5.21",
|
|
"supertest": "^7.2.2",
|
|
"ts-jest": "^29.4.6",
|
|
"ts-loader": "^9.5.7",
|
|
"ts-node": "^10.9.2",
|
|
"tsconfig-paths": "^4.2.0",
|
|
"typescript": "^5.9.3",
|
|
"typescript-eslint": "^8.57.1"
|
|
},
|
|
"jest": {
|
|
"moduleFileExtensions": [
|
|
"js",
|
|
"json",
|
|
"ts",
|
|
"tsx"
|
|
],
|
|
"rootDir": "src",
|
|
"testRegex": ".*\\.spec\\.ts$",
|
|
"testPathIgnorePatterns": [
|
|
"/node_modules/"
|
|
],
|
|
"transform": {
|
|
"happy-dom.+\\.js$": [
|
|
"babel-jest",
|
|
{
|
|
"presets": [
|
|
[
|
|
"@babel/preset-env",
|
|
{
|
|
"targets": {
|
|
"node": "current"
|
|
}
|
|
}
|
|
]
|
|
]
|
|
}
|
|
],
|
|
"^.+\\.(t|j)sx?$": "ts-jest"
|
|
},
|
|
"transformIgnorePatterns": [
|
|
"/node_modules/(?!(\\.pnpm/)?(nanoid|uuid|image-dimensions|marked|happy-dom|lib0)(@|/))"
|
|
],
|
|
"collectCoverageFrom": [
|
|
"**/*.(t|j)s"
|
|
],
|
|
"coverageDirectory": "../coverage",
|
|
"testEnvironment": "node",
|
|
"moduleNameMapper": {
|
|
"^@docmost/db/(.*)$": "<rootDir>/database/$1",
|
|
"^@docmost/transactional/(.*)$": "<rootDir>/integrations/transactional/$1",
|
|
"^@docmost/ee/(.*)$": "<rootDir>/ee/$1",
|
|
"^src/(.*)$": "<rootDir>/$1"
|
|
}
|
|
}
|
|
}
|