@hiai-gg/docsmint 0.8.8 → 0.9.0
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 +18 -20
- package/dist/backend/index.js +155 -120
- package/dist/client.d.ts +2 -2
- package/dist/mcp-cli.js +421 -114
- package/dist/mcp-server.d.ts +12 -4
- package/dist/mcp-server.js +453 -114
- package/dist/types.d.ts +5 -0
- package/package.json +1 -1
- package/packages/cli/src/index.ts +1 -1
- package/server.json +9 -5
package/README.md
CHANGED
|
@@ -42,13 +42,11 @@ instructions, and verify the connection. Hosted MCP follows your plan and
|
|
|
42
42
|
workspace permissions. Prefer workspace-bound or category-scoped credentials.
|
|
43
43
|
|
|
44
44
|
The Cloud endpoint is `https://docsmint.com/mcp` (Streamable HTTP). OAuth-capable
|
|
45
|
-
clients discover the hosted resource and authorization-server metadata;
|
|
46
|
-
authorization
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
45
|
+
clients discover the hosted resource and authorization-server metadata; current
|
|
46
|
+
authorization and client-connection methods are described in the
|
|
47
|
+
[DocsMint Cloud setup guide](https://docsmint.com/mcp/connect?source=github_mcp).
|
|
48
|
+
Browser consent binds the one-hour opaque access token to the selected workspace,
|
|
49
|
+
optional category, and requested scopes. Refresh tokens are not issued. API-key clients can instead send
|
|
52
50
|
`Authorization: Bearer <key>` with an MCP/API credential created in the authenticated
|
|
53
51
|
browser UI. Credentials cannot create or elevate other credentials; lifecycle
|
|
54
52
|
management belongs to the signed-in browser session.
|
|
@@ -74,19 +72,19 @@ bridge to your own API, not a server installer or hosted OAuth server. See the
|
|
|
74
72
|
- **Choose how you run it.** Use [managed DocsMint](https://docsmint.com) or
|
|
75
73
|
self-host the application, database, search, queues, and files.
|
|
76
74
|
|
|
77
|
-
## What's new in 0.
|
|
75
|
+
## What's new in 0.9.0?
|
|
78
76
|
|
|
79
|
-
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
77
|
+
- Keep MCP `list_documents` output aligned with the public SDK by returning
|
|
78
|
+
folder and category names, and invalidate stale list entries after metadata
|
|
79
|
+
changes for every member of the affected workspace.
|
|
80
|
+
- Publish all 21 MCP tools with stable schemas and clearer parameter guidance;
|
|
81
|
+
expose the canonical `capabilityCatalog` for hosted contract checks.
|
|
82
|
+
- Reject category-scoped document creation when the selected folder belongs to
|
|
83
|
+
a different category.
|
|
84
|
+
- Keep category-list response types and Cloud connection guidance aligned with
|
|
85
|
+
the public API and supported setup flow.
|
|
87
86
|
|
|
88
|
-
|
|
89
|
-
release. See the [release notes](https://github.com/HiAi-gg/docsmint/releases/tag/v0.8.8)
|
|
87
|
+
This release adds no database migration. See the [release notes](https://github.com/HiAi-gg/docsmint/releases/tag/v0.9.0)
|
|
90
88
|
and [changelog](https://github.com/HiAi-gg/docsmint/blob/main/CHANGELOG.md).
|
|
91
89
|
|
|
92
90
|
## Install with an AI agent
|
|
@@ -139,7 +137,7 @@ docker pull vgalibov/docsmint:web-latest
|
|
|
139
137
|
docker pull vgalibov/docsmint:caddy-latest
|
|
140
138
|
```
|
|
141
139
|
|
|
142
|
-
Use versioned tags `api-v0.
|
|
140
|
+
Use versioned tags `api-v0.9.0`, `web-v0.9.0`, and `caddy-v0.9.0` for
|
|
143
141
|
reproducible deploys. Caddy is the supporting reverse proxy with rate limiting;
|
|
144
142
|
it is separate from the API and web application. The quickstart still builds the Compose stack from this repository so PostgreSQL,
|
|
145
143
|
Redis, and SeaweedFS start together with the application.
|
|
@@ -224,7 +222,7 @@ command and configuration precedence.
|
|
|
224
222
|
## Connect an MCP client
|
|
225
223
|
|
|
226
224
|
Give agents a secure path to search, read, and maintain your knowledge without
|
|
227
|
-
database or filesystem access. DocsMint publishes
|
|
225
|
+
database or filesystem access. DocsMint publishes MCP tools plus ready-made
|
|
228
226
|
research prompts, scoped resources, and a document-manager skill.
|
|
229
227
|
|
|
230
228
|
### Hosted DocsMint
|
package/dist/backend/index.js
CHANGED
|
@@ -200640,6 +200640,112 @@ function buildApiAccessValues(input) {
|
|
|
200640
200640
|
};
|
|
200641
200641
|
}
|
|
200642
200642
|
|
|
200643
|
+
// ../../backend/src/lib/doc-cache.ts
|
|
200644
|
+
init_logger3();
|
|
200645
|
+
init_redis();
|
|
200646
|
+
var LIST_PREFIX = "hiai-docs:cache:docs:list:";
|
|
200647
|
+
var SINGLE_PREFIX = "hiai-docs:cache:docs:single:";
|
|
200648
|
+
function docListKey(userId, folderId, tag2, page = 1, limit = 20, workspaceId) {
|
|
200649
|
+
const parts = [`${LIST_PREFIX}${userId}`];
|
|
200650
|
+
if (workspaceId)
|
|
200651
|
+
parts.push(`w:${workspaceId}`);
|
|
200652
|
+
if (folderId)
|
|
200653
|
+
parts.push(`f:${folderId}`);
|
|
200654
|
+
if (tag2)
|
|
200655
|
+
parts.push(`t:${tag2}`);
|
|
200656
|
+
parts.push(`p:${page}`, `l:${limit}`);
|
|
200657
|
+
return parts.join(":");
|
|
200658
|
+
}
|
|
200659
|
+
function docSingleKey(docId, userId, workspaceId) {
|
|
200660
|
+
return `${SINGLE_PREFIX}${userId}${workspaceId ? `:w:${workspaceId}` : ""}:${docId}`;
|
|
200661
|
+
}
|
|
200662
|
+
async function cacheGetOrSet(key, ttl, compute, options = {}) {
|
|
200663
|
+
try {
|
|
200664
|
+
const cached2 = await redis.get(key);
|
|
200665
|
+
if (cached2)
|
|
200666
|
+
return JSON.parse(cached2);
|
|
200667
|
+
} catch (err) {
|
|
200668
|
+
logger3.warn({ err, key }, "Redis get failed, falling through to DB");
|
|
200669
|
+
}
|
|
200670
|
+
const value = await compute();
|
|
200671
|
+
if (options.shouldCache && !options.shouldCache(value))
|
|
200672
|
+
return value;
|
|
200673
|
+
try {
|
|
200674
|
+
await redis.set(key, JSON.stringify(value), "EX", ttl);
|
|
200675
|
+
} catch (err) {
|
|
200676
|
+
logger3.warn({ err, key }, "Redis set failed");
|
|
200677
|
+
}
|
|
200678
|
+
return value;
|
|
200679
|
+
}
|
|
200680
|
+
function isHttpCacheEnvelope(value) {
|
|
200681
|
+
return typeof value === "object" && value !== null && "version" in value && value.version === 1 && "kind" in value && value.kind === "http-response" && "status" in value && typeof value.status === "number" && Number.isInteger(value.status) && value.status >= 100 && value.status <= 599 && "body" in value;
|
|
200682
|
+
}
|
|
200683
|
+
async function cacheHttpResponse(key, ttl, compute, options = {}) {
|
|
200684
|
+
try {
|
|
200685
|
+
const cached2 = await redis.get(key);
|
|
200686
|
+
if (cached2) {
|
|
200687
|
+
const parsed = JSON.parse(cached2);
|
|
200688
|
+
if (isHttpCacheEnvelope(parsed)) {
|
|
200689
|
+
return {
|
|
200690
|
+
status: parsed.status,
|
|
200691
|
+
body: parsed.body,
|
|
200692
|
+
cacheHit: true
|
|
200693
|
+
};
|
|
200694
|
+
}
|
|
200695
|
+
}
|
|
200696
|
+
} catch (err) {
|
|
200697
|
+
logger3.warn({ err, key }, "Redis HTTP cache get failed, falling through to DB");
|
|
200698
|
+
}
|
|
200699
|
+
const value = await compute();
|
|
200700
|
+
if (options.shouldCache && !options.shouldCache(value)) {
|
|
200701
|
+
return { ...value, cacheHit: false };
|
|
200702
|
+
}
|
|
200703
|
+
const envelope = {
|
|
200704
|
+
version: 1,
|
|
200705
|
+
kind: "http-response",
|
|
200706
|
+
status: value.status,
|
|
200707
|
+
body: value.body
|
|
200708
|
+
};
|
|
200709
|
+
try {
|
|
200710
|
+
await redis.set(key, JSON.stringify(envelope), "EX", ttl);
|
|
200711
|
+
} catch (err) {
|
|
200712
|
+
logger3.warn({ err, key }, "Redis HTTP cache set failed");
|
|
200713
|
+
}
|
|
200714
|
+
return { ...value, cacheHit: false };
|
|
200715
|
+
}
|
|
200716
|
+
async function invalidateDocListCache(userId, workspaceId) {
|
|
200717
|
+
const patterns = new Set([`${LIST_PREFIX}${userId}:*`]);
|
|
200718
|
+
if (workspaceId)
|
|
200719
|
+
patterns.add(`${LIST_PREFIX}*:w:${workspaceId}:*`);
|
|
200720
|
+
try {
|
|
200721
|
+
for (const pattern of patterns) {
|
|
200722
|
+
let cursor = "0";
|
|
200723
|
+
do {
|
|
200724
|
+
const [newCursor, keys] = await redis.scan(cursor, "MATCH", pattern, "COUNT", 100);
|
|
200725
|
+
cursor = newCursor;
|
|
200726
|
+
if (keys.length > 0)
|
|
200727
|
+
await redis.del(...keys);
|
|
200728
|
+
} while (cursor !== "0");
|
|
200729
|
+
}
|
|
200730
|
+
} catch (err) {
|
|
200731
|
+
logger3.warn({ err, userId, workspaceId }, "Failed to invalidate doc list cache");
|
|
200732
|
+
}
|
|
200733
|
+
}
|
|
200734
|
+
async function invalidateDocCache(docId) {
|
|
200735
|
+
const pattern = `${SINGLE_PREFIX}*:${docId}:scope:*`;
|
|
200736
|
+
try {
|
|
200737
|
+
let cursor = "0";
|
|
200738
|
+
do {
|
|
200739
|
+
const [newCursor, keys] = await redis.scan(cursor, "MATCH", pattern, "COUNT", 100);
|
|
200740
|
+
cursor = newCursor;
|
|
200741
|
+
if (keys.length > 0)
|
|
200742
|
+
await redis.del(...keys);
|
|
200743
|
+
} while (cursor !== "0");
|
|
200744
|
+
} catch (err) {
|
|
200745
|
+
logger3.warn({ err, docId }, "Failed to invalidate doc cache");
|
|
200746
|
+
}
|
|
200747
|
+
}
|
|
200748
|
+
|
|
200643
200749
|
// ../../backend/src/api/routes/categories.ts
|
|
200644
200750
|
init_logger3();
|
|
200645
200751
|
var createCategorySchema = exports_external2.object({
|
|
@@ -200861,6 +200967,7 @@ var categoryRoutes = new Elysia({ prefix: "/api" }).get("/categories", async ({
|
|
|
200861
200967
|
set2.status = 404;
|
|
200862
200968
|
return { error: "Category not found" };
|
|
200863
200969
|
}
|
|
200970
|
+
await invalidateDocListCache(ctx.userId, ctx.workspaceId);
|
|
200864
200971
|
if (parsed.data.name !== undefined) {
|
|
200865
200972
|
dispatchMetadataReembedOutbox(updated.operationId, metadataReembedPageSize("category"));
|
|
200866
200973
|
}
|
|
@@ -200908,6 +201015,7 @@ var categoryRoutes = new Elysia({ prefix: "/api" }).get("/categories", async ({
|
|
|
200908
201015
|
set2.status = 404;
|
|
200909
201016
|
return { error: "Category not found" };
|
|
200910
201017
|
}
|
|
201018
|
+
await invalidateDocListCache(ctx.userId, ctx.workspaceId);
|
|
200911
201019
|
dispatchMetadataReembedOutbox(deletion.operationId, metadataReembedPageSize("category"));
|
|
200912
201020
|
return { success: true };
|
|
200913
201021
|
} catch (err) {
|
|
@@ -228024,110 +228132,6 @@ function createYjsRoomKey(input) {
|
|
|
228024
228132
|
|
|
228025
228133
|
// ../../backend/src/api/routes/collaboration.ts
|
|
228026
228134
|
init_config();
|
|
228027
|
-
|
|
228028
|
-
// ../../backend/src/lib/doc-cache.ts
|
|
228029
|
-
init_logger3();
|
|
228030
|
-
init_redis();
|
|
228031
|
-
var LIST_PREFIX = "hiai-docs:cache:docs:list:";
|
|
228032
|
-
var SINGLE_PREFIX = "hiai-docs:cache:docs:single:";
|
|
228033
|
-
function docListKey(userId, folderId, tag2, page = 1, limit = 20, workspaceId) {
|
|
228034
|
-
const parts = [`${LIST_PREFIX}${userId}`];
|
|
228035
|
-
if (workspaceId)
|
|
228036
|
-
parts.push(`w:${workspaceId}`);
|
|
228037
|
-
if (folderId)
|
|
228038
|
-
parts.push(`f:${folderId}`);
|
|
228039
|
-
if (tag2)
|
|
228040
|
-
parts.push(`t:${tag2}`);
|
|
228041
|
-
parts.push(`p:${page}`, `l:${limit}`);
|
|
228042
|
-
return parts.join(":");
|
|
228043
|
-
}
|
|
228044
|
-
function docSingleKey(docId, userId, workspaceId) {
|
|
228045
|
-
return `${SINGLE_PREFIX}${userId}${workspaceId ? `:w:${workspaceId}` : ""}:${docId}`;
|
|
228046
|
-
}
|
|
228047
|
-
async function cacheGetOrSet(key, ttl, compute, options = {}) {
|
|
228048
|
-
try {
|
|
228049
|
-
const cached2 = await redis.get(key);
|
|
228050
|
-
if (cached2)
|
|
228051
|
-
return JSON.parse(cached2);
|
|
228052
|
-
} catch (err) {
|
|
228053
|
-
logger3.warn({ err, key }, "Redis get failed, falling through to DB");
|
|
228054
|
-
}
|
|
228055
|
-
const value = await compute();
|
|
228056
|
-
if (options.shouldCache && !options.shouldCache(value))
|
|
228057
|
-
return value;
|
|
228058
|
-
try {
|
|
228059
|
-
await redis.set(key, JSON.stringify(value), "EX", ttl);
|
|
228060
|
-
} catch (err) {
|
|
228061
|
-
logger3.warn({ err, key }, "Redis set failed");
|
|
228062
|
-
}
|
|
228063
|
-
return value;
|
|
228064
|
-
}
|
|
228065
|
-
function isHttpCacheEnvelope(value) {
|
|
228066
|
-
return typeof value === "object" && value !== null && "version" in value && value.version === 1 && "kind" in value && value.kind === "http-response" && "status" in value && typeof value.status === "number" && Number.isInteger(value.status) && value.status >= 100 && value.status <= 599 && "body" in value;
|
|
228067
|
-
}
|
|
228068
|
-
async function cacheHttpResponse(key, ttl, compute, options = {}) {
|
|
228069
|
-
try {
|
|
228070
|
-
const cached2 = await redis.get(key);
|
|
228071
|
-
if (cached2) {
|
|
228072
|
-
const parsed = JSON.parse(cached2);
|
|
228073
|
-
if (isHttpCacheEnvelope(parsed)) {
|
|
228074
|
-
return {
|
|
228075
|
-
status: parsed.status,
|
|
228076
|
-
body: parsed.body,
|
|
228077
|
-
cacheHit: true
|
|
228078
|
-
};
|
|
228079
|
-
}
|
|
228080
|
-
}
|
|
228081
|
-
} catch (err) {
|
|
228082
|
-
logger3.warn({ err, key }, "Redis HTTP cache get failed, falling through to DB");
|
|
228083
|
-
}
|
|
228084
|
-
const value = await compute();
|
|
228085
|
-
if (options.shouldCache && !options.shouldCache(value)) {
|
|
228086
|
-
return { ...value, cacheHit: false };
|
|
228087
|
-
}
|
|
228088
|
-
const envelope = {
|
|
228089
|
-
version: 1,
|
|
228090
|
-
kind: "http-response",
|
|
228091
|
-
status: value.status,
|
|
228092
|
-
body: value.body
|
|
228093
|
-
};
|
|
228094
|
-
try {
|
|
228095
|
-
await redis.set(key, JSON.stringify(envelope), "EX", ttl);
|
|
228096
|
-
} catch (err) {
|
|
228097
|
-
logger3.warn({ err, key }, "Redis HTTP cache set failed");
|
|
228098
|
-
}
|
|
228099
|
-
return { ...value, cacheHit: false };
|
|
228100
|
-
}
|
|
228101
|
-
async function invalidateDocListCache(userId) {
|
|
228102
|
-
const pattern = `${LIST_PREFIX}${userId}:*`;
|
|
228103
|
-
try {
|
|
228104
|
-
let cursor = "0";
|
|
228105
|
-
do {
|
|
228106
|
-
const [newCursor, keys4] = await redis.scan(cursor, "MATCH", pattern, "COUNT", 100);
|
|
228107
|
-
cursor = newCursor;
|
|
228108
|
-
if (keys4.length > 0)
|
|
228109
|
-
await redis.del(...keys4);
|
|
228110
|
-
} while (cursor !== "0");
|
|
228111
|
-
} catch (err) {
|
|
228112
|
-
logger3.warn({ err, userId }, "Failed to invalidate doc list cache");
|
|
228113
|
-
}
|
|
228114
|
-
}
|
|
228115
|
-
async function invalidateDocCache(docId) {
|
|
228116
|
-
const pattern = `${SINGLE_PREFIX}*:${docId}:scope:*`;
|
|
228117
|
-
try {
|
|
228118
|
-
let cursor = "0";
|
|
228119
|
-
do {
|
|
228120
|
-
const [newCursor, keys4] = await redis.scan(cursor, "MATCH", pattern, "COUNT", 100);
|
|
228121
|
-
cursor = newCursor;
|
|
228122
|
-
if (keys4.length > 0)
|
|
228123
|
-
await redis.del(...keys4);
|
|
228124
|
-
} while (cursor !== "0");
|
|
228125
|
-
} catch (err) {
|
|
228126
|
-
logger3.warn({ err, docId }, "Failed to invalidate doc cache");
|
|
228127
|
-
}
|
|
228128
|
-
}
|
|
228129
|
-
|
|
228130
|
-
// ../../backend/src/api/routes/collaboration.ts
|
|
228131
228135
|
init_logger3();
|
|
228132
228136
|
|
|
228133
228137
|
// ../../node_modules/.bun/y-protocols@1.0.7+48ebd0cf55a3d4ab/node_modules/y-protocols/sync.js
|
|
@@ -228514,7 +228518,7 @@ collaborationRoutes.ws("/api/ws/collab/:documentId", {
|
|
|
228514
228518
|
});
|
|
228515
228519
|
await Promise.all([
|
|
228516
228520
|
invalidateDocCache(documentId),
|
|
228517
|
-
invalidateDocListCache(access.userId)
|
|
228521
|
+
invalidateDocListCache(access.userId, access.ctx.workspaceId)
|
|
228518
228522
|
]);
|
|
228519
228523
|
enqueueReembed([
|
|
228520
228524
|
{
|
|
@@ -228982,6 +228986,27 @@ async function withTags(ctx, rows) {
|
|
|
228982
228986
|
}
|
|
228983
228987
|
return rows.map((r4) => ({ ...r4, tags: byDoc.get(r4.id) ?? [] }));
|
|
228984
228988
|
}
|
|
228989
|
+
async function withDocumentLocationNames(ctx, rows) {
|
|
228990
|
+
if (rows.length === 0)
|
|
228991
|
+
return [];
|
|
228992
|
+
const folderIds = [
|
|
228993
|
+
...new Set(rows.flatMap((row) => row.folderId ? [row.folderId] : []))
|
|
228994
|
+
];
|
|
228995
|
+
const categoryIds = [
|
|
228996
|
+
...new Set(rows.flatMap((row) => row.categoryId ? [row.categoryId] : []))
|
|
228997
|
+
];
|
|
228998
|
+
const [folderRows, categoryRows] = await withTenant(ctx, async (tx) => Promise.all([
|
|
228999
|
+
folderIds.length > 0 ? tx.select({ id: folders.id, name: folders.name }).from(folders).where(inArray(folders.id, folderIds)) : Promise.resolve([]),
|
|
229000
|
+
categoryIds.length > 0 ? tx.select({ id: categories.id, name: categories.name }).from(categories).where(inArray(categories.id, categoryIds)) : Promise.resolve([])
|
|
229001
|
+
]));
|
|
229002
|
+
const folderNames = new Map(folderRows.map((folder) => [folder.id, folder.name]));
|
|
229003
|
+
const categoryNames = new Map(categoryRows.map((category) => [category.id, category.name]));
|
|
229004
|
+
return rows.map((row) => ({
|
|
229005
|
+
...row,
|
|
229006
|
+
folderName: row.folderId ? folderNames.get(row.folderId) ?? null : null,
|
|
229007
|
+
categoryName: row.categoryId ? categoryNames.get(row.categoryId) ?? null : null
|
|
229008
|
+
}));
|
|
229009
|
+
}
|
|
228985
229010
|
var documentRoutes = new Elysia({ prefix: "/api" }).get("/documents", async ({ query, set: set2, request }) => {
|
|
228986
229011
|
const ip = request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? request.headers.get("x-real-ip") ?? "unknown";
|
|
228987
229012
|
const rl = await documentRateLimiter(ip, request);
|
|
@@ -229009,7 +229034,7 @@ var documentRoutes = new Elysia({ prefix: "/api" }).get("/documents", async ({ q
|
|
|
229009
229034
|
}
|
|
229010
229035
|
const { folderId, tag: tag2, page, limit } = parsed.data;
|
|
229011
229036
|
const offset = (page - 1) * limit;
|
|
229012
|
-
const cacheKey = `${docListKey(userId, folderId, tag2, page, limit, ctx.workspaceId)}:scope:${access.categoryId ?? "all"}`;
|
|
229037
|
+
const cacheKey = `${docListKey(userId, folderId, tag2, page, limit, ctx.workspaceId)}:scope:${access.categoryId ?? "all"}:response:v2`;
|
|
229013
229038
|
try {
|
|
229014
229039
|
return await cacheGetOrSet(cacheKey, 30, async () => {
|
|
229015
229040
|
const conditions2 = [
|
|
@@ -229038,7 +229063,7 @@ var documentRoutes = new Elysia({ prefix: "/api" }).get("/documents", async ({ q
|
|
|
229038
229063
|
]);
|
|
229039
229064
|
});
|
|
229040
229065
|
return {
|
|
229041
|
-
items: await withTags(ctx, rows2),
|
|
229066
|
+
items: await withDocumentLocationNames(ctx, await withTags(ctx, rows2)),
|
|
229042
229067
|
total: countResult2[0]?.total ?? 0,
|
|
229043
229068
|
page,
|
|
229044
229069
|
limit
|
|
@@ -229060,7 +229085,7 @@ var documentRoutes = new Elysia({ prefix: "/api" }).get("/documents", async ({ q
|
|
|
229060
229085
|
]);
|
|
229061
229086
|
});
|
|
229062
229087
|
return {
|
|
229063
|
-
items: await withTags(ctx, rows),
|
|
229088
|
+
items: await withDocumentLocationNames(ctx, await withTags(ctx, rows)),
|
|
229064
229089
|
total: countResult[0]?.total ?? 0,
|
|
229065
229090
|
page,
|
|
229066
229091
|
limit
|
|
@@ -229231,9 +229256,9 @@ var documentRoutes = new Elysia({ prefix: "/api" }).get("/documents", async ({ q
|
|
|
229231
229256
|
await acquireTenantTopologyLock(tx, ctx);
|
|
229232
229257
|
}
|
|
229233
229258
|
let categoryId = requestedCategoryId;
|
|
229234
|
-
if (folderId
|
|
229235
|
-
const
|
|
229236
|
-
if (
|
|
229259
|
+
if (folderId) {
|
|
229260
|
+
const folderCategoryId = await resolveFolderEffectiveCategory(tx, ctx, folderId);
|
|
229261
|
+
if (folderCategoryId === undefined) {
|
|
229237
229262
|
return {
|
|
229238
229263
|
row: null,
|
|
229239
229264
|
replayed: false,
|
|
@@ -229241,7 +229266,16 @@ var documentRoutes = new Elysia({ prefix: "/api" }).get("/documents", async ({ q
|
|
|
229241
229266
|
forbidden: true
|
|
229242
229267
|
};
|
|
229243
229268
|
}
|
|
229244
|
-
|
|
229269
|
+
if (access.restricted && !isAuthorizedCategory(access, folderCategoryId)) {
|
|
229270
|
+
return {
|
|
229271
|
+
row: null,
|
|
229272
|
+
replayed: false,
|
|
229273
|
+
conflict: false,
|
|
229274
|
+
forbidden: true
|
|
229275
|
+
};
|
|
229276
|
+
}
|
|
229277
|
+
if (!categoryId)
|
|
229278
|
+
categoryId = folderCategoryId;
|
|
229245
229279
|
}
|
|
229246
229280
|
if (!isAuthorizedCategory(access, categoryId)) {
|
|
229247
229281
|
return {
|
|
@@ -229325,7 +229359,7 @@ var documentRoutes = new Elysia({ prefix: "/api" }).get("/documents", async ({ q
|
|
|
229325
229359
|
revision: contentHash(created.title, created.content ?? ""),
|
|
229326
229360
|
source: "interactive"
|
|
229327
229361
|
}).catch((err) => logger3.warn({ err, documentId: created.id }, "Pipeline enqueue failed"));
|
|
229328
|
-
invalidateDocListCache(userId);
|
|
229362
|
+
invalidateDocListCache(userId, ctx.workspaceId);
|
|
229329
229363
|
set2.status = 201;
|
|
229330
229364
|
const ipAddress = request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? request.headers.get("x-real-ip") ?? "";
|
|
229331
229365
|
const userAgent = request.headers.get("user-agent") ?? "";
|
|
@@ -230051,7 +230085,7 @@ var documentRoutes = new Elysia({ prefix: "/api" }).get("/documents", async ({ q
|
|
|
230051
230085
|
}
|
|
230052
230086
|
await Promise.all([
|
|
230053
230087
|
invalidateDocCache(params2.id),
|
|
230054
|
-
invalidateDocListCache(userId)
|
|
230088
|
+
invalidateDocListCache(userId, ctx.workspaceId)
|
|
230055
230089
|
]);
|
|
230056
230090
|
const ipAddress = request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? request.headers.get("x-real-ip") ?? "";
|
|
230057
230091
|
const userAgent = request.headers.get("user-agent") ?? "";
|
|
@@ -230316,7 +230350,7 @@ var documentRoutes = new Elysia({ prefix: "/api" }).get("/documents", async ({ q
|
|
|
230316
230350
|
revision: contentHash(copy3.title, copy3.content ?? ""),
|
|
230317
230351
|
source: "interactive"
|
|
230318
230352
|
}).catch((err) => logger3.warn({ err, documentId: copy3.id }, "Pipeline enqueue failed"));
|
|
230319
|
-
invalidateDocListCache(userId);
|
|
230353
|
+
invalidateDocListCache(userId, ctx.workspaceId);
|
|
230320
230354
|
set2.status = 201;
|
|
230321
230355
|
return copy3;
|
|
230322
230356
|
} catch (err) {
|
|
@@ -230386,7 +230420,7 @@ var documentRoutes = new Elysia({ prefix: "/api" }).get("/documents", async ({ q
|
|
|
230386
230420
|
return { error: "Document not found" };
|
|
230387
230421
|
}
|
|
230388
230422
|
invalidateDocCache(params2.id);
|
|
230389
|
-
invalidateDocListCache(ctx.userId);
|
|
230423
|
+
invalidateDocListCache(ctx.userId, ctx.workspaceId);
|
|
230390
230424
|
return { success: true };
|
|
230391
230425
|
}).delete("/trash/documents/:id", async ({ params: params2, request, set: set2 }) => {
|
|
230392
230426
|
const access = await resolveContentAccess(request);
|
|
@@ -230496,7 +230530,7 @@ var documentRoutes = new Elysia({ prefix: "/api" }).get("/documents", async ({ q
|
|
|
230496
230530
|
});
|
|
230497
230531
|
}
|
|
230498
230532
|
invalidateDocCache(params2.id);
|
|
230499
|
-
invalidateDocListCache(ctx.userId);
|
|
230533
|
+
invalidateDocListCache(ctx.userId, ctx.workspaceId);
|
|
230500
230534
|
return { success: true };
|
|
230501
230535
|
} catch (error53) {
|
|
230502
230536
|
if (error53 instanceof DocumentPurgeNotFoundError) {
|
|
@@ -230552,7 +230586,7 @@ var documentRoutes = new Elysia({ prefix: "/api" }).get("/documents", async ({ q
|
|
|
230552
230586
|
return { error: "Document not found" };
|
|
230553
230587
|
}
|
|
230554
230588
|
invalidateDocCache(params2.id);
|
|
230555
|
-
invalidateDocListCache(userId);
|
|
230589
|
+
invalidateDocListCache(userId, ctx.workspaceId);
|
|
230556
230590
|
const ipAddress = request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? request.headers.get("x-real-ip") ?? "";
|
|
230557
230591
|
const userAgent = request.headers.get("user-agent") ?? "";
|
|
230558
230592
|
recordAuditEvent({
|
|
@@ -231397,7 +231431,7 @@ var folderRoutes = new Elysia({ prefix: "/api/folders" }).get("/:id", async ({ p
|
|
|
231397
231431
|
}
|
|
231398
231432
|
if (parsed.data.name !== undefined || parsed.data.parentId !== undefined || parsed.data.categoryId !== undefined) {
|
|
231399
231433
|
dispatchMetadataReembedOutbox(result.operationId, metadataReembedPageSize("folder"));
|
|
231400
|
-
invalidateDocListCache(userId);
|
|
231434
|
+
await invalidateDocListCache(userId, ctx.workspaceId);
|
|
231401
231435
|
}
|
|
231402
231436
|
return result.updated;
|
|
231403
231437
|
} catch (err) {
|
|
@@ -231450,6 +231484,7 @@ var folderRoutes = new Elysia({ prefix: "/api/folders" }).get("/:id", async ({ p
|
|
|
231450
231484
|
set2.status = 404;
|
|
231451
231485
|
return { error: "Folder not found" };
|
|
231452
231486
|
}
|
|
231487
|
+
await invalidateDocListCache(ctx.userId, ctx.workspaceId);
|
|
231453
231488
|
dispatchMetadataReembedOutbox(deletion.operationId, metadataReembedPageSize("folder"));
|
|
231454
231489
|
return { success: true };
|
|
231455
231490
|
} catch (err) {
|
|
@@ -238425,7 +238460,7 @@ var swaggerConfig = {
|
|
|
238425
238460
|
},
|
|
238426
238461
|
info: {
|
|
238427
238462
|
title: "DocsMint API",
|
|
238428
|
-
version: "0.
|
|
238463
|
+
version: "0.9.0",
|
|
238429
238464
|
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.",
|
|
238430
238465
|
contact: { name: "HiAi-gg", url: "https://github.com/HiAi-gg/docsmint" },
|
|
238431
238466
|
license: {
|
package/dist/client.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* failures (502 / 503 / 504 / timeout / network reset) are retried
|
|
8
8
|
* with exponential backoff up to `config.retries` attempts.
|
|
9
9
|
*/
|
|
10
|
-
import type { DocsApiKeyCreated, DocsApiKeyListResponse, DocsAttachment, DocsAttachmentConfirmInput, DocsAttachmentListResponse, DocsAttachmentPresignInput, DocsAttachmentPresignResponse, DocsCategory, DocsCategoryInput, DocsCategoryUpdate, DocsDocument, DocsDocumentCreateInput, DocsDocumentCursorPage, DocsDocumentIndexRefresh, DocsDocumentIndexStatus, DocsDocumentKnowledgeSummary, DocsDocumentListResponse, DocsDocumentPipeline, DocsPipelineWarningRetry, DocsDocumentUpdateInput, DocsFolder, DocsFolderCreateInput, DocsFolderUpdateInput, DocsGraphEntitiesResponse, DocsGraphRelatedResponse, DocsGraphSearchResponse, DocsHealthResponse, DocsRequestContext, DocsSearchOptions, DocsSearchResponse, DocsSearchSuggestItem, DocsSharedContent, DocsShareLink, DocsShareListResponse, DocsShareRole, DocsTag, DocsVersion, DocsVersionDiff } from "./types.js";
|
|
10
|
+
import type { DocsApiKeyCreated, DocsApiKeyListResponse, DocsAttachment, DocsAttachmentConfirmInput, DocsAttachmentListResponse, DocsAttachmentPresignInput, DocsAttachmentPresignResponse, DocsCategory, DocsCategoryListItem, DocsCategoryInput, DocsCategoryUpdate, DocsDocument, DocsDocumentCreateInput, DocsDocumentCursorPage, DocsDocumentIndexRefresh, DocsDocumentIndexStatus, DocsDocumentKnowledgeSummary, DocsDocumentListResponse, DocsDocumentPipeline, DocsPipelineWarningRetry, DocsDocumentUpdateInput, DocsFolder, DocsFolderCreateInput, DocsFolderUpdateInput, DocsGraphEntitiesResponse, DocsGraphRelatedResponse, DocsGraphSearchResponse, DocsHealthResponse, DocsRequestContext, DocsSearchOptions, DocsSearchResponse, DocsSearchSuggestItem, DocsSharedContent, DocsShareLink, DocsShareListResponse, DocsShareRole, DocsTag, DocsVersion, DocsVersionDiff } from "./types.js";
|
|
11
11
|
export interface DocsClientConfig {
|
|
12
12
|
/** Base URL of the DocsMint API, e.g. `http://localhost:50700`. */
|
|
13
13
|
baseUrl: string;
|
|
@@ -126,7 +126,7 @@ export declare class DocsClient {
|
|
|
126
126
|
deleteTag(id: string, context?: DocsRequestContext): Promise<void>;
|
|
127
127
|
addTagToDoc(documentId: string, tagId: string, context?: DocsRequestContext): Promise<void>;
|
|
128
128
|
removeTagFromDoc(documentId: string, tagId: string, context?: DocsRequestContext): Promise<void>;
|
|
129
|
-
listCategories(context?: DocsRequestContext): Promise<
|
|
129
|
+
listCategories(context?: DocsRequestContext): Promise<DocsCategoryListItem[]>;
|
|
130
130
|
createCategory(input: DocsCategoryInput, context?: DocsRequestContext): Promise<DocsCategory>;
|
|
131
131
|
updateCategory(id: string, updates: DocsCategoryUpdate, context?: DocsRequestContext): Promise<DocsCategory>;
|
|
132
132
|
deleteCategory(id: string, context?: DocsRequestContext): Promise<void>;
|