@hiai-gg/docsmint 0.8.6 → 0.8.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,15 +10,22 @@ agents access to the same documents through MCP, REST, the SDK, or CLI.
10
10
  operating the stack, or **[self-host with Docker](#quickstart)** to run the
11
11
  Apache-2.0 application on your own infrastructure.
12
12
 
13
- [![Apache-2.0 License](https://img.shields.io/badge/License-Apache--2.0-green.svg)](https://github.com/HiAi-gg/docsmint/blob/main/LICENSE)
13
+ [![Apache-2.0 License](https://img.shields.io/badge/License-Apache--2.0-green.svg)](LICENSE)
14
14
  [![Release](https://img.shields.io/github/v/release/hiai-gg/docsmint?sort=semver)](https://github.com/hiai-gg/docsmint/releases)
15
15
  [![npm](https://img.shields.io/npm/v/@hiai-gg/docsmint?logo=npm)](https://www.npmjs.com/package/@hiai-gg/docsmint)
16
+ [![Docker Pulls](https://img.shields.io/docker/pulls/vgalibov/docsmint?logo=docker)](https://hub.docker.com/r/vgalibov/docsmint)
17
+ [![Stars](https://img.shields.io/github/stars/hiai-gg/docsmint)](https://github.com/hiai-gg/docsmint/stargazers)
16
18
  [![CI](https://github.com/hiai-gg/docsmint/actions/workflows/ci.yml/badge.svg)](https://github.com/hiai-gg/docsmint/actions/workflows/ci.yml)
17
-
18
- [GitHub](https://github.com/HiAi-gg/docsmint) ·
19
- [Docker Hub](https://hub.docker.com/r/vgalibov/docsmint) ·
20
- [npm](https://www.npmjs.com/package/@hiai-gg/docsmint) ·
21
- [LobeHub MCP](https://lobehub.com/mcp/hiai-gg-docsmint)
19
+ [![Bun](https://img.shields.io/badge/Runtime-Bun_1.4-black?logo=bun&logoColor=white)](https://bun.sh)
20
+ [![TypeScript](https://img.shields.io/badge/TypeScript-6.x-3178C6?logo=typescript&logoColor=white)](https://www.typescriptlang.org)
21
+ [![Svelte](https://img.shields.io/badge/Svelte-5.x-FF3E00?logo=svelte&logoColor=white)](https://svelte.dev)
22
+ [![Elysia](https://img.shields.io/badge/Elysia-1.4-lightgrey?logo=elysia&logoColor=white)](https://elysiajs.com)
23
+ [![Tailwind_CSS](https://img.shields.io/badge/Tailwind_CSS-v4-06B6D4?logo=tailwindcss&logoColor=white)](https://tailwindcss.com)
24
+ [![Drizzle_ORM](https://img.shields.io/badge/Drizzle_ORM-0.45-C5F74F?logo=drizzle&logoColor=black)](https://orm.drizzle.team)
25
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md)
26
+ [![MCP Badge](https://lobehub.com/badge/mcp/hiai-gg-docsmint)](https://lobehub.com/mcp/hiai-gg-docsmint)
27
+ [![DocsMint Cloud](https://img.shields.io/badge/DocsMint_Cloud-Connect-16a34a)](https://docsmint.com/mcp/connect?source=github_mcp)
28
+ [![MCP Registry](https://img.shields.io/badge/MCP_Registry-Listed-0891b2)](https://registry.modelcontextprotocol.io/v0.1/servers/io.github.HiAi-gg%2Fdocsmint/versions/latest)
22
29
 
23
30
  <img width="1920" height="974" alt="DocsMint installable document workspace" src="https://github.com/user-attachments/assets/94701d01-a361-4ca1-b16d-de2a0c64d684" />
24
31
 
@@ -35,17 +42,21 @@ instructions, and verify the connection. Hosted MCP follows your plan and
35
42
  workspace permissions. Prefer workspace-bound or category-scoped credentials.
36
43
 
37
44
  The Cloud endpoint is `https://docsmint.com/mcp` (Streamable HTTP). OAuth-capable
38
- clients discover authorization and open DocsMint login and consent in the browser.
39
- Choose your workspace and explicit scopes; the authorization code uses PKCE S256.
40
- Access tokens expire after one hour; reconnect through browser authorization when
41
- they expire (no refresh tokens). API-key clients
42
- send `Authorization: Bearer <key>`. Credentials cannot create or elevate other
43
- credentials; lifecycle management belongs to the signed-in browser session.
45
+ clients discover the hosted resource and authorization-server metadata; the current
46
+ authorization metadata advertises client registration at
47
+ `https://docsmint.com/oauth/register` (DCR). The hosted flow uses authorization-code
48
+ PKCE S256; metadata advertises only the `authorization_code` grant and no token
49
+ endpoint client authentication. CIMD is not part of the current contract. Browser consent binds the
50
+ one-hour opaque access token to the selected workspace, optional category, and
51
+ requested scopes. Refresh tokens are not issued. API-key clients can instead send
52
+ `Authorization: Bearer <key>` with an MCP/API credential created in the authenticated
53
+ browser UI. Credentials cannot create or elevate other credentials; lifecycle
54
+ management belongs to the signed-in browser session.
44
55
 
45
56
  **Self-hosted alternative:** run [DocsMint with Docker](#quickstart), create an API
46
57
  key in its browser UI, and configure `HIAI_DOCS_URL` and `HIAI_DOCS_API_KEY` for
47
58
  `npx --yes --package @hiai-gg/docsmint docsmint-mcp`. The npm package is a stdio
48
- bridge to your running deployment, not a server installer. See the
59
+ bridge to your own API, not a server installer or hosted OAuth server. See the
49
60
  [MCP guide](https://github.com/HiAi-gg/docsmint/blob/main/packages/mcp-server/README.md) for client configuration.
50
61
 
51
62
  ## Why DocsMint?
@@ -63,19 +74,20 @@ bridge to your running deployment, not a server installer. See the
63
74
  - **Choose how you run it.** Use [managed DocsMint](https://docsmint.com) or
64
75
  self-host the application, database, search, queues, and files.
65
76
 
66
- ## What's new in 0.8.6?
67
-
68
- This patch completes the publication improvements from 0.8.5:
77
+ ## What's new in 0.8.8?
69
78
 
70
- - Corrected Docker Hub description updates and direct Docker links in LobeHub.
71
- - Clearer guides for choosing DocsMint Cloud or a self-hosted workspace.
72
- - One public package, `@hiai-gg/docsmint`, for the SDK, CLI, and MCP bridge.
73
- - A Docker image guide that distinguishes the API, web application, and Caddy proxy.
74
- - Consistent release notes and package documentation, with links that also work on npm.
79
+ - Clarify that hosted MCP authorization belongs to DocsMint Cloud and that the
80
+ stdio bridge uses an API key to connect to your own DocsMint API.
81
+ - Synchronize MCP Registry and LobeHub descriptions with the current Cloud
82
+ authorization contract and the self-hosted boundary.
83
+ - Keep the published catalog aligned with the implementation: 21 tools,
84
+ 2 prompts, and 3 resources, with regression checks for route and metadata drift.
85
+ - Update vulnerability reporting details and clarify that OAuth protocol flaws
86
+ remain in scope for the product where they occur.
75
87
 
76
- No database migration or public API change is required. Read the
77
- [release notes](https://github.com/HiAi-gg/docsmint/releases/tag/v0.8.6),
78
- [changelog](https://github.com/HiAi-gg/docsmint/blob/main/CHANGELOG.md), or [roadmap](https://github.com/HiAi-gg/docsmint/blob/main/docs/ROADMAP.md).
88
+ No MCP capabilities, existing integrations, or database schema change in this
89
+ release. See the [release notes](https://github.com/HiAi-gg/docsmint/releases/tag/v0.8.8)
90
+ and [changelog](https://github.com/HiAi-gg/docsmint/blob/main/CHANGELOG.md).
79
91
 
80
92
  ## Install with an AI agent
81
93
 
@@ -127,7 +139,7 @@ docker pull vgalibov/docsmint:web-latest
127
139
  docker pull vgalibov/docsmint:caddy-latest
128
140
  ```
129
141
 
130
- Use versioned tags `api-v0.8.6`, `web-v0.8.6`, and `caddy-v0.8.6` for
142
+ Use versioned tags `api-v0.8.8`, `web-v0.8.8`, and `caddy-v0.8.8` for
131
143
  reproducible deploys. Caddy is the supporting reverse proxy with rate limiting;
132
144
  it is separate from the API and web application. The quickstart still builds the Compose stack from this repository so PostgreSQL,
133
145
  Redis, and SeaweedFS start together with the application.
@@ -212,7 +224,7 @@ command and configuration precedence.
212
224
  ## Connect an MCP client
213
225
 
214
226
  Give agents a secure path to search, read, and maintain your knowledge without
215
- database or filesystem access. DocsMint publishes 17 tools plus ready-made
227
+ database or filesystem access. DocsMint publishes 21 tools plus ready-made
216
228
  research prompts, scoped resources, and a document-manager skill.
217
229
 
218
230
  ### Hosted DocsMint
@@ -231613,6 +231613,106 @@ function stripQuotes(value) {
231613
231613
  return trimmed;
231614
231614
  }
231615
231615
 
231616
+ // ../../backend/src/lib/graph/search-query.ts
231617
+ init_drizzle_orm();
231618
+
231619
+ // ../../backend/src/search/query-analyzer.ts
231620
+ var CYRILLIC_LETTER = /\p{Script=Cyrillic}/u;
231621
+ var LATIN_LETTER = /\p{Script=Latin}/u;
231622
+ var LETTER2 = /\p{L}/u;
231623
+ function normalizeQuery(query) {
231624
+ const normalized = query.normalize("NFC");
231625
+ let result = "";
231626
+ let inQuotedPhrase = false;
231627
+ let escaped = false;
231628
+ let pendingWhitespace = false;
231629
+ for (const character of normalized) {
231630
+ if (inQuotedPhrase) {
231631
+ result += character;
231632
+ if (escaped) {
231633
+ escaped = false;
231634
+ } else if (character === "\\") {
231635
+ escaped = true;
231636
+ } else if (character === '"') {
231637
+ inQuotedPhrase = false;
231638
+ }
231639
+ continue;
231640
+ }
231641
+ if (character === '"') {
231642
+ if (pendingWhitespace && result.length > 0)
231643
+ result += " ";
231644
+ pendingWhitespace = false;
231645
+ result += character;
231646
+ inQuotedPhrase = true;
231647
+ continue;
231648
+ }
231649
+ if (/\s/u.test(character)) {
231650
+ pendingWhitespace = true;
231651
+ continue;
231652
+ }
231653
+ if (pendingWhitespace && result.length > 0)
231654
+ result += " ";
231655
+ pendingWhitespace = false;
231656
+ result += character;
231657
+ }
231658
+ return result;
231659
+ }
231660
+ function detectLanguage(query) {
231661
+ const normalized = normalizeQuery(query);
231662
+ let cyrillic = 0;
231663
+ let latin = 0;
231664
+ for (const token of normalized.split(" ")) {
231665
+ if (token.includes("/"))
231666
+ continue;
231667
+ for (const character of token) {
231668
+ if (!LETTER2.test(character))
231669
+ continue;
231670
+ if (CYRILLIC_LETTER.test(character))
231671
+ cyrillic += 1;
231672
+ else if (LATIN_LETTER.test(character))
231673
+ latin += 1;
231674
+ }
231675
+ }
231676
+ if (cyrillic > 0 && latin > 0)
231677
+ return "mixed";
231678
+ if (cyrillic > 0)
231679
+ return "ru";
231680
+ if (latin > 0)
231681
+ return "en";
231682
+ return "und";
231683
+ }
231684
+ function analyzeQuery(query) {
231685
+ return {
231686
+ original: query,
231687
+ normalized: normalizeQuery(query),
231688
+ detectedLanguage: detectLanguage(query),
231689
+ translations: [],
231690
+ synonyms: [],
231691
+ concepts: [],
231692
+ namedEntities: []
231693
+ };
231694
+ }
231695
+
231696
+ // ../../backend/src/lib/graph/search-query.ts
231697
+ function graphSearchDocumentQuery(ctx, documentIds, query) {
231698
+ const normalized = normalizeQuery(query ?? "");
231699
+ const english = sql`websearch_to_tsquery('english', ${normalized})`;
231700
+ const simple = sql`websearch_to_tsquery('simple', ${normalized})`;
231701
+ const exact = sql`lower(trim(d.title)) = lower(${normalized})`;
231702
+ const match2 = normalized ? sql`AND (${exact} OR d.search_vector @@ ${english} OR d.search_vector_simple @@ ${simple})` : sql``;
231703
+ const order = normalized ? sql`${exact} DESC, GREATEST(ts_rank(d.search_vector, ${english}), ts_rank(d.search_vector_simple, ${simple})) DESC,` : sql``;
231704
+ const allowed2 = documentIds.length ? sql`d.id IN (${sql.join(documentIds.map((id3) => sql`${id3}`), sql`, `)})` : sql`false`;
231705
+ return sql`
231706
+ SELECT d.id, d.title, d.content
231707
+ FROM documents d
231708
+ WHERE ${tenantOwnerSql("d", ctx)}
231709
+ AND d.deleted_at IS NULL
231710
+ AND ${allowed2}
231711
+ ${match2}
231712
+ ORDER BY ${order} d.id ASC
231713
+ `;
231714
+ }
231715
+
231616
231716
  // ../../backend/src/api/routes/graph.ts
231617
231717
  init_logger3();
231618
231718
  var entitiesQuerySchema = exports_external2.object({
@@ -231621,8 +231721,11 @@ var entitiesQuerySchema = exports_external2.object({
231621
231721
  var relatedParamsSchema = exports_external2.object({
231622
231722
  docId: exports_external2.string().min(1)
231623
231723
  });
231724
+ var relatedQuerySchema = exports_external2.object({
231725
+ limit: exports_external2.coerce.number().int().min(1).max(100).default(20)
231726
+ });
231624
231727
  var graphSearchBodySchema = exports_external2.object({
231625
- query: exports_external2.string().optional(),
231728
+ query: exports_external2.string().max(2000).optional(),
231626
231729
  docIds: exports_external2.array(exports_external2.string().min(1)).min(1).max(50),
231627
231730
  maxResults: exports_external2.number().int().min(1).max(100).optional()
231628
231731
  });
@@ -231668,7 +231771,7 @@ var graphRoutes = new Elysia({ prefix: "/api/graph" }).get("/entities", async ({
231668
231771
  tags: ["Graph"],
231669
231772
  summary: "List entities linked to a document"
231670
231773
  }
231671
- }).get("/related/:docId", async ({ params: params2, set: set2, request }) => {
231774
+ }).get("/related/:docId", async ({ params: params2, query, set: set2, request }) => {
231672
231775
  const rl = await applyRateLimit(request, set2);
231673
231776
  if (!rl.ok)
231674
231777
  return rl.response;
@@ -231687,6 +231790,11 @@ var graphRoutes = new Elysia({ prefix: "/api/graph" }).get("/entities", async ({
231687
231790
  set2.status = 400;
231688
231791
  return { error: "Invalid params", details: parsed.error.flatten() };
231689
231792
  }
231793
+ const parsedQuery = relatedQuerySchema.safeParse(query);
231794
+ if (!parsedQuery.success) {
231795
+ set2.status = 400;
231796
+ return { error: "Invalid query", details: parsedQuery.error.flatten() };
231797
+ }
231690
231798
  try {
231691
231799
  const seedIds = await allowedGraphDocumentIds(access, [
231692
231800
  parsed.data.docId
@@ -231698,7 +231806,7 @@ var graphRoutes = new Elysia({ prefix: "/api/graph" }).get("/entities", async ({
231698
231806
  if (!config3.GRAPH_SEARCH_ENABLED)
231699
231807
  return { related: [] };
231700
231808
  const related = await fetchRelatedDocuments(ctx, parsed.data.docId, access);
231701
- return { related };
231809
+ return { related: related.slice(0, parsedQuery.data.limit) };
231702
231810
  } catch (err) {
231703
231811
  logger3.warn({ err, docId: parsed.data.docId }, "Graph related lookup failed \u2014 returning empty");
231704
231812
  return { related: [] };
@@ -231737,7 +231845,7 @@ var graphRoutes = new Elysia({ prefix: "/api/graph" }).get("/entities", async ({
231737
231845
  if (!config3.GRAPH_SEARCH_ENABLED) {
231738
231846
  return { query, entities: [], relatedDocs: [] };
231739
231847
  }
231740
- const result = await graphRagLookup(ctx, docIds, maxResults, access);
231848
+ const result = await graphRagLookup(ctx, docIds, maxResults, access, query);
231741
231849
  return { query, ...result };
231742
231850
  } catch (err) {
231743
231851
  logger3.warn({ err, docIds: docIds.length }, "Graph RAG search failed \u2014 returning empty");
@@ -231805,7 +231913,7 @@ async function fetchRelatedDocuments(ctx, docId, access) {
231805
231913
  const allowedIds = access ? await allowedGraphDocumentIds(access, neighborIds) : await filterToOwnedDocuments(ctx, neighborIds);
231806
231914
  return current.filter((neighbor) => allowedIds.has(neighbor.docId));
231807
231915
  }
231808
- async function graphRagLookup(ctx, docIds, maxResults, access) {
231916
+ async function graphRagLookup(ctx, docIds, maxResults, access, query) {
231809
231917
  const sql6 = await getGraphDb();
231810
231918
  if (!sql6)
231811
231919
  return { entities: [], relatedDocs: [] };
@@ -231838,12 +231946,13 @@ async function graphRagLookup(ctx, docIds, maxResults, access) {
231838
231946
  if (ownedNeighborIds.length === 0) {
231839
231947
  return { entities: Array.from(entityMap.values()), relatedDocs: [] };
231840
231948
  }
231841
- const rows = await loadDocumentSummaries(ctx, ownedNeighborIds);
231949
+ const rows = await loadDocumentSummaries(ctx, ownedNeighborIds, query);
231842
231950
  const byId = new Map;
231843
231951
  for (const r4 of rows)
231844
231952
  byId.set(r4.id, r4);
231845
231953
  const relatedDocs = [];
231846
- for (const id3 of ownedNeighborIds) {
231954
+ const rankedIds = query?.trim() ? rows.map((row) => row.id) : ownedNeighborIds;
231955
+ for (const id3 of rankedIds) {
231847
231956
  const meta3 = neighborMap.get(id3);
231848
231957
  const row = byId.get(id3);
231849
231958
  if (!meta3 || !row)
@@ -231919,19 +232028,11 @@ function currentGraphNeighbors(expansion, activeGenerations) {
231919
232028
  }
231920
232029
  return [...current.values()].sort((left, right) => left.hopDistance - right.hopDistance || left.docId.localeCompare(right.docId));
231921
232030
  }
231922
- async function loadDocumentSummaries(ctx, docIds) {
232031
+ async function loadDocumentSummaries(ctx, docIds, query) {
231923
232032
  if (docIds.length === 0)
231924
232033
  return [];
231925
232034
  return withTenant(ctx, async (tx) => {
231926
- return tx.select({
231927
- id: documents.id,
231928
- title: documents.title,
231929
- content: documents.content,
231930
- folderId: documents.folderId,
231931
- folderName: folders.name,
231932
- createdAt: documents.createdAt,
231933
- updatedAt: documents.updatedAt
231934
- }).from(documents).leftJoin(folders, eq(folders.id, documents.folderId)).where(and(tenantOwnerCondition(documents.ownerId, documents.workspaceId, ctx), inArray(documents.id, docIds), isNull(documents.deletedAt)));
232035
+ return await tx.execute(graphSearchDocumentQuery(ctx, docIds, query));
231935
232036
  });
231936
232037
  }
231937
232038
  function cypherDocReplace(cypher, docId) {
@@ -231958,50 +232059,6 @@ function stripQuotes2(value) {
231958
232059
  return trimmed;
231959
232060
  }
231960
232061
 
231961
- // ../../backend/src/api/routes/health.ts
231962
- function createHealthRoutes(dependencies) {
231963
- return new Elysia().get("/api/health", async ({ request, set: set2 }) => {
231964
- const ip = request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? request.headers.get("x-real-ip") ?? "unknown";
231965
- const rateLimit = await dependencies.rateLimiter(ip, request);
231966
- set2.headers = {
231967
- "X-RateLimit-Remaining": String(rateLimit.remaining),
231968
- ...rateLimit.retryAfter ? { "Retry-After": String(rateLimit.retryAfter) } : {}
231969
- };
231970
- if (!rateLimit.allowed) {
231971
- set2.status = 429;
231972
- return { error: "Too many requests" };
231973
- }
231974
- const [databaseAvailable, redisAvailable, storageAvailable] = await Promise.all([
231975
- dependencies.databaseAvailable(),
231976
- dependencies.redisAvailable(),
231977
- dependencies.storageAvailable()
231978
- ]);
231979
- const queueAvailable = dependencies.queueAvailable();
231980
- const readiness = evaluatePipelineHealth({
231981
- databaseAvailable,
231982
- redisAvailable,
231983
- storageAvailable,
231984
- queueAvailable,
231985
- recoveryAvailable: true,
231986
- oldestInteractiveWaitMs: 0,
231987
- interactiveSloMs: Number.POSITIVE_INFINITY,
231988
- graphAvailable: true
231989
- });
231990
- if (readiness.status === "unhealthy")
231991
- set2.status = 503;
231992
- return {
231993
- status: readiness.status === "unhealthy" ? "unhealthy" : "ok",
231994
- service: "docsmint",
231995
- timestamp: new Date().toISOString(),
231996
- database: databaseAvailable ? "ok" : "error",
231997
- redis: redisAvailable ? "ok" : "error",
231998
- storage: storageAvailable ? "ok" : "error",
231999
- queue: queueAvailable ? "ok" : "error",
232000
- ...readiness.reasons.length ? { reasons: readiness.reasons } : {}
232001
- };
232002
- });
232003
- }
232004
-
232005
232062
  // ../../backend/src/api/routes/keys.ts
232006
232063
  init_schema2();
232007
232064
  init_drizzle_orm();
@@ -232493,83 +232550,6 @@ function evaluateConfidence(results, _plan, thresholds = { vectorMinSimilarity:
232493
232550
  return { confident: reasons.length === 0, reasons };
232494
232551
  }
232495
232552
 
232496
- // ../../backend/src/search/query-analyzer.ts
232497
- var CYRILLIC_LETTER = /\p{Script=Cyrillic}/u;
232498
- var LATIN_LETTER = /\p{Script=Latin}/u;
232499
- var LETTER2 = /\p{L}/u;
232500
- function normalizeQuery(query) {
232501
- const normalized = query.normalize("NFC");
232502
- let result = "";
232503
- let inQuotedPhrase = false;
232504
- let escaped = false;
232505
- let pendingWhitespace = false;
232506
- for (const character of normalized) {
232507
- if (inQuotedPhrase) {
232508
- result += character;
232509
- if (escaped) {
232510
- escaped = false;
232511
- } else if (character === "\\") {
232512
- escaped = true;
232513
- } else if (character === '"') {
232514
- inQuotedPhrase = false;
232515
- }
232516
- continue;
232517
- }
232518
- if (character === '"') {
232519
- if (pendingWhitespace && result.length > 0)
232520
- result += " ";
232521
- pendingWhitespace = false;
232522
- result += character;
232523
- inQuotedPhrase = true;
232524
- continue;
232525
- }
232526
- if (/\s/u.test(character)) {
232527
- pendingWhitespace = true;
232528
- continue;
232529
- }
232530
- if (pendingWhitespace && result.length > 0)
232531
- result += " ";
232532
- pendingWhitespace = false;
232533
- result += character;
232534
- }
232535
- return result;
232536
- }
232537
- function detectLanguage(query) {
232538
- const normalized = normalizeQuery(query);
232539
- let cyrillic = 0;
232540
- let latin = 0;
232541
- for (const token of normalized.split(" ")) {
232542
- if (token.includes("/"))
232543
- continue;
232544
- for (const character of token) {
232545
- if (!LETTER2.test(character))
232546
- continue;
232547
- if (CYRILLIC_LETTER.test(character))
232548
- cyrillic += 1;
232549
- else if (LATIN_LETTER.test(character))
232550
- latin += 1;
232551
- }
232552
- }
232553
- if (cyrillic > 0 && latin > 0)
232554
- return "mixed";
232555
- if (cyrillic > 0)
232556
- return "ru";
232557
- if (latin > 0)
232558
- return "en";
232559
- return "und";
232560
- }
232561
- function analyzeQuery(query) {
232562
- return {
232563
- original: query,
232564
- normalized: normalizeQuery(query),
232565
- detectedLanguage: detectLanguage(query),
232566
- translations: [],
232567
- synonyms: [],
232568
- concepts: [],
232569
- namedEntities: []
232570
- };
232571
- }
232572
-
232573
232553
  // ../../backend/src/search/query-expander.ts
232574
232554
  init_zod();
232575
232555
  init_config();
@@ -235935,6 +235915,53 @@ var webhookRoutes = new Elysia({ prefix: "/api/webhooks" }).post("/storage", asy
235935
235915
  return { received: true };
235936
235916
  });
235937
235917
 
235918
+ // ../../backend/src/api/register-routes.ts
235919
+ var selfHostedApiRoutes = new Elysia().use(authRoutes).use(tagRoutes).use(categoryRoutes).use(attachmentRoutes).use(shareRoutes).use(searchRoutes).use(documentRoutes).use(folderRoutes).use(versionRoutes).use(webhookRoutes).use(collaborationRoutes).use(graphRoutes).use(keysRoutes).use(pluginsRoutes).use(visibilityRoutes).use(adminRoutes).use(metricsRoutes);
235920
+
235921
+ // ../../backend/src/api/routes/health.ts
235922
+ function createHealthRoutes(dependencies) {
235923
+ return new Elysia().get("/api/health", async ({ request, set: set2 }) => {
235924
+ const ip = request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? request.headers.get("x-real-ip") ?? "unknown";
235925
+ const rateLimit = await dependencies.rateLimiter(ip, request);
235926
+ set2.headers = {
235927
+ "X-RateLimit-Remaining": String(rateLimit.remaining),
235928
+ ...rateLimit.retryAfter ? { "Retry-After": String(rateLimit.retryAfter) } : {}
235929
+ };
235930
+ if (!rateLimit.allowed) {
235931
+ set2.status = 429;
235932
+ return { error: "Too many requests" };
235933
+ }
235934
+ const [databaseAvailable, redisAvailable, storageAvailable] = await Promise.all([
235935
+ dependencies.databaseAvailable(),
235936
+ dependencies.redisAvailable(),
235937
+ dependencies.storageAvailable()
235938
+ ]);
235939
+ const queueAvailable = dependencies.queueAvailable();
235940
+ const readiness = evaluatePipelineHealth({
235941
+ databaseAvailable,
235942
+ redisAvailable,
235943
+ storageAvailable,
235944
+ queueAvailable,
235945
+ recoveryAvailable: true,
235946
+ oldestInteractiveWaitMs: 0,
235947
+ interactiveSloMs: Number.POSITIVE_INFINITY,
235948
+ graphAvailable: true
235949
+ });
235950
+ if (readiness.status === "unhealthy")
235951
+ set2.status = 503;
235952
+ return {
235953
+ status: readiness.status === "unhealthy" ? "unhealthy" : "ok",
235954
+ service: "docsmint",
235955
+ timestamp: new Date().toISOString(),
235956
+ database: databaseAvailable ? "ok" : "error",
235957
+ redis: redisAvailable ? "ok" : "error",
235958
+ storage: storageAvailable ? "ok" : "error",
235959
+ queue: queueAvailable ? "ok" : "error",
235960
+ ...readiness.reasons.length ? { reasons: readiness.reasons } : {}
235961
+ };
235962
+ });
235963
+ }
235964
+
235938
235965
  // ../../backend/src/lib/api-key-owner.ts
235939
235966
  init_schema2();
235940
235967
  init_config();
@@ -238398,7 +238425,7 @@ var swaggerConfig = {
238398
238425
  },
238399
238426
  info: {
238400
238427
  title: "DocsMint API",
238401
- version: "0.8.6",
238428
+ version: "0.8.8",
238402
238429
  description: "Self-hosted AI-native knowledge workspace and installable PWA with hybrid search, GraphRAG, REST, SDK, CLI, and MCP access for people and AI agents.",
238403
238430
  contact: { name: "HiAi-gg", url: "https://github.com/HiAi-gg/docsmint" },
238404
238431
  license: {
@@ -238452,7 +238479,7 @@ var app = new Elysia().use(bodySizeLimit).onError(({ error: error53, set: set2 }
238452
238479
  redisAvailable: () => redis.ping().then(() => true, () => false),
238453
238480
  storageAvailable: () => storage.send(new ListBucketsCommand({})).then(() => true, () => false),
238454
238481
  queueAvailable: () => pipelineRuntime.workers.size === PIPELINE_STAGES.length && [...pipelineRuntime.workers.values()].every((worker2) => worker2.isRunning?.() === true)
238455
- })).use(csrfMiddleware).use(authMiddleware).use(authRoutes).use(tagRoutes).use(categoryRoutes).use(attachmentRoutes).use(shareRoutes).use(searchRoutes).use(documentRoutes).use(folderRoutes).use(versionRoutes).use(webhookRoutes).use(collaborationRoutes).use(graphRoutes).use(keysRoutes).use(pluginsRoutes).use(visibilityRoutes).use(adminRoutes).use(metricsRoutes);
238482
+ })).use(csrfMiddleware).use(authMiddleware).use(selfHostedApiRoutes);
238456
238483
  app.listen({
238457
238484
  port: config3.API_PORT,
238458
238485
  development: config3.NODE_ENV !== "production",