@nebutra/document-pipeline 0.1.0 → 0.1.1
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/.nebutra/debug/content-store.jsonl +6 -6
- package/.nebutra/debug/document-pipeline.jsonl +3 -3
- package/.turbo/turbo-build.log +5 -5
- package/.turbo/turbo-test.log +4 -6
- package/.turbo/turbo-typecheck.log +1 -1
- package/CHANGELOG.md +13 -0
- package/dist/{chunk-HZF62UJB.js → chunk-GC4W5KJ2.js} +63 -75
- package/dist/chunk-GC4W5KJ2.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +5 -4
- package/src/index.ts +66 -89
- package/tsup.config.ts +6 -1
- package/dist/chunk-HZF62UJB.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
{"at":"2026-05-
|
|
2
|
-
{"at":"2026-05-
|
|
3
|
-
{"at":"2026-05-
|
|
4
|
-
{"at":"2026-05-
|
|
5
|
-
{"at":"2026-05-
|
|
6
|
-
{"at":"2026-05-
|
|
1
|
+
{"at":"2026-05-19T08:15:10.701Z","type":"reindex","tenantId":"tenant_a","files":0}
|
|
2
|
+
{"at":"2026-05-19T08:15:10.749Z","type":"write","tenantId":"tenant_a","path":"research/BRAND.md"}
|
|
3
|
+
{"at":"2026-05-19T08:15:10.827Z","type":"reindex","tenantId":"tenant_a","files":0}
|
|
4
|
+
{"at":"2026-05-19T08:15:10.866Z","type":"reindex","tenantId":"tenant_a","files":0}
|
|
5
|
+
{"at":"2026-05-19T08:15:10.894Z","type":"reindex","tenantId":"tenant_a","files":0}
|
|
6
|
+
{"at":"2026-05-19T08:15:10.898Z","type":"write","tenantId":"tenant_a","path":"notes/a.md"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
{"at":"2026-05-
|
|
2
|
-
{"at":"2026-05-
|
|
3
|
-
{"at":"2026-05-
|
|
1
|
+
{"at":"2026-05-19T08:15:10.708Z","type":"parse","tenantId":"tenant_a","parser":"markdown","path":"research/BRAND.md","chunks":1}
|
|
2
|
+
{"at":"2026-05-19T08:15:10.750Z","type":"ingest","tenantId":"tenant_a","path":"research/BRAND.md","parser":"markdown","chunks":1}
|
|
3
|
+
{"at":"2026-05-19T08:15:10.832Z","type":"parse","tenantId":"tenant_a","parser":"html","path":"page.html","chunks":1}
|
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @nebutra/document-pipeline@0.1.
|
|
2
|
+
> @nebutra/document-pipeline@0.1.1 build /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/document-pipeline
|
|
3
3
|
> tsup
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/cli.ts, src/index.ts
|
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
[34mESM[39m Build start
|
|
12
12
|
[32mESM[39m [1mdist/cli.js [22m[32m1.10 KB[39m
|
|
13
13
|
[32mESM[39m [1mdist/index.js [22m[32m180.00 B[39m
|
|
14
|
-
[32mESM[39m [1mdist/chunk-
|
|
14
|
+
[32mESM[39m [1mdist/chunk-GC4W5KJ2.js [22m[32m7.81 KB[39m
|
|
15
15
|
[32mESM[39m [1mdist/cli.js.map [22m[32m1.93 KB[39m
|
|
16
16
|
[32mESM[39m [1mdist/index.js.map [22m[32m71.00 B[39m
|
|
17
|
-
[32mESM[39m [1mdist/chunk-
|
|
18
|
-
[32mESM[39m ⚡️ Build success in
|
|
17
|
+
[32mESM[39m [1mdist/chunk-GC4W5KJ2.js.map [22m[32m16.66 KB[39m
|
|
18
|
+
[32mESM[39m ⚡️ Build success in 109ms
|
|
19
19
|
[34mDTS[39m Build start
|
|
20
|
-
[32mDTS[39m ⚡️ Build success in
|
|
20
|
+
[32mDTS[39m ⚡️ Build success in 10840ms
|
|
21
21
|
[32mDTS[39m [1mdist/cli.d.ts [22m[32m13.00 B[39m
|
|
22
22
|
[32mDTS[39m [1mdist/index.d.ts [22m[32m3.44 KB[39m
|
package/.turbo/turbo-test.log
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
|
|
2
|
-
> @nebutra/document-pipeline@0.1.
|
|
2
|
+
> @nebutra/document-pipeline@0.1.1 test /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/document-pipeline
|
|
3
3
|
> vitest run
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
[1m[30m[46m RUN [49m[39m[22m [36mv4.1.4 [39m[90m/home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/document-pipeline[39m
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
(Use `node --trace-warnings ...` to show where the warning was created)
|
|
10
|
-
[32m✓[39m src/index.test.ts [2m([22m[2m5 tests[22m[2m)[22m[33m 546[2mms[22m[39m
|
|
8
|
+
[32m✓[39m src/index.test.ts [2m([22m[2m5 tests[22m[2m)[22m[33m 372[2mms[22m[39m
|
|
11
9
|
|
|
12
10
|
[2m Test Files [22m [1m[32m1 passed[39m[22m[90m (1)[39m
|
|
13
11
|
[2m Tests [22m [1m[32m5 passed[39m[22m[90m (5)[39m
|
|
14
|
-
[2m Start at [22m
|
|
15
|
-
[2m Duration [22m
|
|
12
|
+
[2m Start at [22m 08:15:09
|
|
13
|
+
[2m Duration [22m 1.31s[2m (transform 563ms, setup 0ms, import 647ms, tests 372ms, environment 0ms)[22m
|
|
16
14
|
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# @nebutra/document-pipeline
|
|
2
|
+
|
|
3
|
+
## 0.1.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Publish registry package metadata under the MIT license.
|
|
8
|
+
|
|
9
|
+
- Updated dependencies []:
|
|
10
|
+
- @nebutra/capability-kit@0.2.1
|
|
11
|
+
- @nebutra/content-store@0.1.1
|
|
12
|
+
- @nebutra/errors@0.1.1
|
|
13
|
+
- @nebutra/sandbox-runtime@0.1.1
|
|
@@ -1,36 +1,17 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { readFile } from "fs/promises";
|
|
3
|
+
import { extname, join } from "path";
|
|
4
|
+
import { requireCapabilityTenant } from "@nebutra/capability-kit";
|
|
5
|
+
import { appendCapabilityDebug, readCapabilityDebug } from "@nebutra/capability-kit/debug";
|
|
6
|
+
import {
|
|
7
|
+
ContentStore,
|
|
8
|
+
parseContentFrontmatter,
|
|
9
|
+
serializeContentFrontmatter,
|
|
10
|
+
splitContentParagraphs
|
|
11
|
+
} from "@nebutra/content-store";
|
|
5
12
|
import { CapabilityError } from "@nebutra/errors";
|
|
6
|
-
function debugPath(root = process.cwd()) {
|
|
7
|
-
return join(root, ".nebutra", "debug", "document-pipeline.jsonl");
|
|
8
|
-
}
|
|
9
|
-
async function appendDebug(root, entry) {
|
|
10
|
-
const path = debugPath(root);
|
|
11
|
-
await mkdir(dirname(path), { recursive: true });
|
|
12
|
-
await writeFile(path, `${JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), ...entry })}
|
|
13
|
-
`, {
|
|
14
|
-
flag: "a"
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
13
|
async function readDocumentPipelineDebug(root = process.cwd(), limit = 20) {
|
|
18
|
-
|
|
19
|
-
const raw = await readFile(debugPath(root), "utf8");
|
|
20
|
-
return raw.trim().split("\n").filter(Boolean).slice(-limit).map((line) => JSON.parse(line));
|
|
21
|
-
} catch {
|
|
22
|
-
return [];
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
function requireTenant(explicit, fallback) {
|
|
26
|
-
const tenantId = explicit ?? fallback;
|
|
27
|
-
if (!tenantId) {
|
|
28
|
-
throw new CapabilityError("document-pipeline", "Document ingestion requires tenant context", {
|
|
29
|
-
suggestion: "Pass tenantId on the request or construct DocumentPipeline with a tenantId default.",
|
|
30
|
-
statusCode: 400
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
return tenantId;
|
|
14
|
+
return readCapabilityDebug("document-pipeline", { root, limit });
|
|
34
15
|
}
|
|
35
16
|
var DocumentPipeline = class _DocumentPipeline {
|
|
36
17
|
#tenantId;
|
|
@@ -51,7 +32,14 @@ var DocumentPipeline = class _DocumentPipeline {
|
|
|
51
32
|
return new _DocumentPipeline({ ...options, tenantId, root, contentStore });
|
|
52
33
|
}
|
|
53
34
|
async parse(request) {
|
|
54
|
-
const tenantId =
|
|
35
|
+
const tenantId = requireCapabilityTenant({
|
|
36
|
+
explicit: request.tenantId,
|
|
37
|
+
fallback: this.#tenantId,
|
|
38
|
+
onMissing: () => new CapabilityError("document-pipeline", "Document ingestion requires tenant context", {
|
|
39
|
+
suggestion: "Pass tenantId on the request or construct DocumentPipeline with a tenantId default.",
|
|
40
|
+
statusCode: 400
|
|
41
|
+
})
|
|
42
|
+
});
|
|
55
43
|
const required = { ...request, tenantId };
|
|
56
44
|
const parser = this.#chooseParser(required);
|
|
57
45
|
if (parser === "sidecar") {
|
|
@@ -63,39 +51,58 @@ var DocumentPipeline = class _DocumentPipeline {
|
|
|
63
51
|
});
|
|
64
52
|
}
|
|
65
53
|
const parsed2 = await this.#sidecarParser.parse(required);
|
|
66
|
-
await
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
54
|
+
await appendCapabilityDebug(
|
|
55
|
+
"document-pipeline",
|
|
56
|
+
{
|
|
57
|
+
type: "parse",
|
|
58
|
+
tenantId,
|
|
59
|
+
parser: "sidecar",
|
|
60
|
+
path: parsed2.path
|
|
61
|
+
},
|
|
62
|
+
{ root: this.#debugRoot }
|
|
63
|
+
);
|
|
72
64
|
return parsed2;
|
|
73
65
|
}
|
|
74
66
|
const raw = await readSource(request.source);
|
|
75
67
|
const parsed = parseNativeDocument(required, raw, parser);
|
|
76
|
-
await
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
68
|
+
await appendCapabilityDebug(
|
|
69
|
+
"document-pipeline",
|
|
70
|
+
{
|
|
71
|
+
type: "parse",
|
|
72
|
+
tenantId,
|
|
73
|
+
parser,
|
|
74
|
+
path: parsed.path,
|
|
75
|
+
chunks: parsed.chunks.length
|
|
76
|
+
},
|
|
77
|
+
{ root: this.#debugRoot }
|
|
78
|
+
);
|
|
83
79
|
return parsed;
|
|
84
80
|
}
|
|
85
81
|
async ingest(request) {
|
|
86
|
-
const tenantId =
|
|
82
|
+
const tenantId = requireCapabilityTenant({
|
|
83
|
+
explicit: request.tenantId,
|
|
84
|
+
fallback: this.#tenantId,
|
|
85
|
+
onMissing: () => new CapabilityError("document-pipeline", "Document ingestion requires tenant context", {
|
|
86
|
+
suggestion: "Pass tenantId on the request or construct DocumentPipeline with a tenantId default.",
|
|
87
|
+
statusCode: 400
|
|
88
|
+
})
|
|
89
|
+
});
|
|
87
90
|
const contentStore = await this.#resolveContentStore(tenantId);
|
|
88
91
|
const parsed = await this.parse({ ...request, tenantId });
|
|
89
92
|
const targetPath = request.targetPath ?? parsed.path;
|
|
90
93
|
const content = serializeForContentStore(parsed, request.metadata);
|
|
91
94
|
await contentStore.write(targetPath, content);
|
|
92
|
-
await
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
await appendCapabilityDebug(
|
|
96
|
+
"document-pipeline",
|
|
97
|
+
{
|
|
98
|
+
type: "ingest",
|
|
99
|
+
tenantId,
|
|
100
|
+
path: targetPath,
|
|
101
|
+
parser: parsed.parser,
|
|
102
|
+
chunks: parsed.chunks.length
|
|
103
|
+
},
|
|
104
|
+
{ root: this.#debugRoot }
|
|
105
|
+
);
|
|
99
106
|
return {
|
|
100
107
|
tenantId,
|
|
101
108
|
path: targetPath,
|
|
@@ -151,9 +158,9 @@ function mimeFromPath(path) {
|
|
|
151
158
|
}
|
|
152
159
|
function parseNativeDocument(request, raw, parser) {
|
|
153
160
|
const mimeType = request.source.mimeType ?? mimeFromPath(request.source.path);
|
|
154
|
-
const { frontmatter, body } = parser === "markdown" ?
|
|
161
|
+
const { frontmatter, body } = parser === "markdown" ? parseContentFrontmatter(raw) : { frontmatter: {}, body: raw };
|
|
155
162
|
const text = parser === "html" ? htmlToText(body) : body;
|
|
156
|
-
const chunks =
|
|
163
|
+
const chunks = splitContentParagraphs(text).map((chunk, index) => ({
|
|
157
164
|
id: `${request.tenantId}:${request.source.path}:${index}`,
|
|
158
165
|
text: chunk,
|
|
159
166
|
metadata: {
|
|
@@ -194,27 +201,8 @@ function serializeForContentStore(parsed, metadata) {
|
|
|
194
201
|
source_mime: parsed.mimeType,
|
|
195
202
|
parser: parsed.parser
|
|
196
203
|
};
|
|
197
|
-
const frontmatterLines = Object.entries(frontmatter).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${key}: ${value}`);
|
|
198
204
|
const body = parsed.chunks.map((chunk) => chunk.text).join("\n\n");
|
|
199
|
-
return
|
|
200
|
-
${frontmatterLines.join("\n")}
|
|
201
|
-
---
|
|
202
|
-
${body}` : body;
|
|
203
|
-
}
|
|
204
|
-
function parseFrontmatter(content) {
|
|
205
|
-
if (!content.startsWith("---\n")) return { frontmatter: {}, body: content };
|
|
206
|
-
const end = content.indexOf("\n---", 4);
|
|
207
|
-
if (end < 0) return { frontmatter: {}, body: content };
|
|
208
|
-
const raw = content.slice(4, end).trim();
|
|
209
|
-
const frontmatter = {};
|
|
210
|
-
for (const line of raw.split("\n")) {
|
|
211
|
-
const idx = line.indexOf(":");
|
|
212
|
-
if (idx > 0) frontmatter[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
|
|
213
|
-
}
|
|
214
|
-
return { frontmatter, body: content.slice(end + 4).trimStart() };
|
|
215
|
-
}
|
|
216
|
-
function chunkParagraphs(content) {
|
|
217
|
-
return content.split(/\n\s*\n/).map((part) => part.trim()).filter(Boolean);
|
|
205
|
+
return serializeContentFrontmatter(frontmatter, body);
|
|
218
206
|
}
|
|
219
207
|
function htmlToText(html) {
|
|
220
208
|
return html.replace(/<script[\s\S]*?<\/script>/gi, " ").replace(/<style[\s\S]*?<\/style>/gi, " ").replace(/<\/(h[1-6]|p|li|tr|div)>/gi, "\n\n").replace(/<[^>]+>/g, " ").replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/[ \t]+/g, " ").replace(/\n\s+/g, "\n").trim();
|
|
@@ -224,4 +212,4 @@ export {
|
|
|
224
212
|
readDocumentPipelineDebug,
|
|
225
213
|
DocumentPipeline
|
|
226
214
|
};
|
|
227
|
-
//# sourceMappingURL=chunk-
|
|
215
|
+
//# sourceMappingURL=chunk-GC4W5KJ2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { extname, join } from \"node:path\";\nimport { requireCapabilityTenant } from \"@nebutra/capability-kit\";\nimport { appendCapabilityDebug, readCapabilityDebug } from \"@nebutra/capability-kit/debug\";\nimport {\n ContentStore,\n parseContentFrontmatter,\n serializeContentFrontmatter,\n splitContentParagraphs,\n} from \"@nebutra/content-store\";\nimport { CapabilityError } from \"@nebutra/errors\";\n\nexport type ParserChoice = \"auto\" | \"markdown\" | \"html\" | \"text\" | \"sidecar\";\nexport type ChunkerChoice = \"paragraph\" | \"recursive\" | \"hierarchical\";\n\nexport interface InlineSource {\n readonly type: \"inline\";\n readonly path: string;\n readonly content: string;\n readonly mimeType?: string;\n}\n\nexport interface FileSource {\n readonly type: \"file\";\n readonly path: string;\n readonly mimeType?: string;\n}\n\nexport type DocumentSource = InlineSource | FileSource;\n\nexport interface IngestRequest {\n readonly tenantId?: string;\n readonly source: DocumentSource;\n readonly parser?: ParserChoice;\n readonly chunker?: ChunkerChoice;\n readonly targetPath?: string;\n readonly metadata?: Record<string, string>;\n}\n\nexport interface ParseRequest {\n readonly tenantId?: string;\n readonly source: DocumentSource;\n readonly parser?: ParserChoice;\n readonly chunker?: ChunkerChoice;\n readonly metadata?: Record<string, string>;\n}\n\nexport interface ParsedChunk {\n readonly id: string;\n readonly text: string;\n readonly metadata: {\n readonly tenantId: string;\n readonly sourcePath: string;\n readonly chunkIndex: number;\n readonly mimeType: string;\n readonly parser: ParserChoice;\n readonly heading?: string;\n } & Record<string, string | number>;\n}\n\nexport interface ParsedDocument {\n readonly tenantId: string;\n readonly path: string;\n readonly mimeType: string;\n readonly parser: ParserChoice;\n readonly chunks: readonly ParsedChunk[];\n readonly frontmatter: Record<string, string>;\n}\n\nexport interface IngestResult {\n readonly tenantId: string;\n readonly path: string;\n readonly parser: ParserChoice;\n readonly chunkCount: number;\n readonly contentIndexed: true;\n}\n\nexport interface DocumentPipelineDoctorReport {\n readonly capability: \"document-pipeline\";\n readonly nativeParsers: readonly ParserChoice[];\n readonly sidecarConfigured: boolean;\n readonly contentStore: Awaited<ReturnType<ContentStore[\"doctor\"]>>;\n readonly suggestion?: string;\n}\n\nexport interface DocumentParser {\n parse(request: RequiredTenant<ParseRequest>): Promise<ParsedDocument>;\n doctor(): Promise<{ readonly ok: boolean; readonly suggestion?: string }>;\n}\n\nexport interface DocumentPipelineOptions {\n readonly tenantId?: string;\n readonly root?: string;\n readonly debugRoot?: string;\n readonly contentStore?: ContentStore;\n readonly sidecarParser?: DocumentParser;\n}\n\ntype RequiredTenant<T extends { readonly tenantId?: string }> = Omit<T, \"tenantId\"> & {\n readonly tenantId: string;\n};\n\nexport async function readDocumentPipelineDebug(\n root = process.cwd(),\n limit = 20,\n): Promise<unknown[]> {\n return readCapabilityDebug(\"document-pipeline\", { root, limit });\n}\n\nexport class DocumentPipeline {\n readonly #tenantId: string | undefined;\n readonly #root: string;\n readonly #debugRoot: string;\n readonly #contentStore: ContentStore | undefined;\n readonly #sidecarParser: DocumentParser | undefined;\n\n constructor(options: DocumentPipelineOptions = {}) {\n this.#tenantId = options.tenantId;\n this.#root = options.root ?? join(process.cwd(), \".nebutra\", \"document-pipeline\");\n this.#debugRoot = options.debugRoot ?? process.cwd();\n this.#contentStore = options.contentStore;\n this.#sidecarParser = options.sidecarParser;\n }\n\n static async open(\n root = \".nebutra/document-pipeline\",\n options: Omit<DocumentPipelineOptions, \"root\" | \"contentStore\"> = {},\n ): Promise<DocumentPipeline> {\n const tenantId = options.tenantId ?? \"local\";\n const contentStore = await ContentStore.open(join(root, \"content\"), { tenantId });\n return new DocumentPipeline({ ...options, tenantId, root, contentStore });\n }\n\n async parse(request: ParseRequest): Promise<ParsedDocument> {\n const tenantId = requireCapabilityTenant({\n explicit: request.tenantId,\n fallback: this.#tenantId,\n onMissing: () =>\n new CapabilityError(\"document-pipeline\", \"Document ingestion requires tenant context\", {\n suggestion:\n \"Pass tenantId on the request or construct DocumentPipeline with a tenantId default.\",\n statusCode: 400,\n }),\n });\n const required: RequiredTenant<ParseRequest> = { ...request, tenantId };\n const parser = this.#chooseParser(required);\n if (parser === \"sidecar\") {\n if (!this.#sidecarParser) {\n throw new CapabilityError(\"document-pipeline\", \"Parser sidecar is not configured\", {\n suggestion:\n \"Configure the parser sidecar for PDFs, office files, OCR, and layout-aware extraction.\",\n statusCode: 503,\n metadata: { path: request.source.path },\n });\n }\n const parsed = await this.#sidecarParser.parse(required);\n await appendCapabilityDebug(\n \"document-pipeline\",\n {\n type: \"parse\",\n tenantId,\n parser: \"sidecar\",\n path: parsed.path,\n },\n { root: this.#debugRoot },\n );\n return parsed;\n }\n\n const raw = await readSource(request.source);\n const parsed = parseNativeDocument(required, raw, parser);\n await appendCapabilityDebug(\n \"document-pipeline\",\n {\n type: \"parse\",\n tenantId,\n parser,\n path: parsed.path,\n chunks: parsed.chunks.length,\n },\n { root: this.#debugRoot },\n );\n return parsed;\n }\n\n async ingest(request: IngestRequest): Promise<IngestResult> {\n const tenantId = requireCapabilityTenant({\n explicit: request.tenantId,\n fallback: this.#tenantId,\n onMissing: () =>\n new CapabilityError(\"document-pipeline\", \"Document ingestion requires tenant context\", {\n suggestion:\n \"Pass tenantId on the request or construct DocumentPipeline with a tenantId default.\",\n statusCode: 400,\n }),\n });\n const contentStore = await this.#resolveContentStore(tenantId);\n const parsed = await this.parse({ ...request, tenantId });\n const targetPath = request.targetPath ?? parsed.path;\n const content = serializeForContentStore(parsed, request.metadata);\n await contentStore.write(targetPath, content);\n await appendCapabilityDebug(\n \"document-pipeline\",\n {\n type: \"ingest\",\n tenantId,\n path: targetPath,\n parser: parsed.parser,\n chunks: parsed.chunks.length,\n },\n { root: this.#debugRoot },\n );\n return {\n tenantId,\n path: targetPath,\n parser: parsed.parser,\n chunkCount: parsed.chunks.length,\n contentIndexed: true,\n };\n }\n\n async ingestFile(\n path: string,\n options: Omit<IngestRequest, \"source\"> = {},\n ): Promise<IngestResult> {\n return this.ingest({ ...options, source: { type: \"file\", path } });\n }\n\n async doctor(): Promise<DocumentPipelineDoctorReport> {\n const contentStore = await this.#resolveContentStore(this.#tenantId ?? \"local\");\n const sidecar = this.#sidecarParser ? await this.#sidecarParser.doctor() : { ok: false };\n return {\n capability: \"document-pipeline\",\n nativeParsers: [\"markdown\", \"html\", \"text\"],\n sidecarConfigured: sidecar.ok,\n contentStore: await contentStore.doctor(),\n ...(sidecar.ok\n ? {}\n : {\n suggestion:\n \"Native parsers are active; configure the parser sidecar before ingesting PDFs, office files, OCR, or complex layout documents.\",\n }),\n };\n }\n\n #chooseParser(request: RequiredTenant<ParseRequest>): ParserChoice {\n if (request.parser && request.parser !== \"auto\") return request.parser;\n const mime = request.source.mimeType ?? mimeFromPath(request.source.path);\n if (mime === \"text/markdown\") return \"markdown\";\n if (mime === \"text/html\") return \"html\";\n if (mime.startsWith(\"text/\")) return \"text\";\n return \"sidecar\";\n }\n\n async #resolveContentStore(tenantId: string): Promise<ContentStore> {\n if (this.#contentStore) return this.#contentStore;\n return ContentStore.open(join(this.#root, \"content\"), { tenantId });\n }\n}\n\nasync function readSource(source: DocumentSource): Promise<string> {\n return source.type === \"inline\" ? source.content : readFile(source.path, \"utf8\");\n}\n\nfunction mimeFromPath(path: string): string {\n const extension = extname(path).toLowerCase();\n if (extension === \".md\" || extension === \".mdx\") return \"text/markdown\";\n if (extension === \".html\" || extension === \".htm\") return \"text/html\";\n if (extension === \".txt\" || extension === \".csv\" || extension === \".json\") return \"text/plain\";\n if (extension === \".pdf\") return \"application/pdf\";\n if (extension === \".docx\")\n return \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\";\n if (extension === \".pptx\")\n return \"application/vnd.openxmlformats-officedocument.presentationml.presentation\";\n if (extension === \".png\" || extension === \".jpg\" || extension === \".jpeg\") return \"image\";\n return \"application/octet-stream\";\n}\n\nfunction parseNativeDocument(\n request: RequiredTenant<ParseRequest>,\n raw: string,\n parser: ParserChoice,\n): ParsedDocument {\n const mimeType = request.source.mimeType ?? mimeFromPath(request.source.path);\n const { frontmatter, body } =\n parser === \"markdown\" ? parseContentFrontmatter(raw) : { frontmatter: {}, body: raw };\n const text = parser === \"html\" ? htmlToText(body) : body;\n const chunks = splitContentParagraphs(text).map((chunk, index) => ({\n id: `${request.tenantId}:${request.source.path}:${index}`,\n text: chunk,\n metadata: {\n tenantId: request.tenantId,\n sourcePath: request.source.path,\n chunkIndex: index,\n mimeType,\n parser,\n ...(request.metadata ?? {}),\n },\n }));\n return {\n tenantId: request.tenantId,\n path: request.source.path,\n mimeType,\n parser,\n chunks:\n chunks.length > 0\n ? chunks\n : [\n {\n id: `${request.tenantId}:${request.source.path}:0`,\n text,\n metadata: {\n tenantId: request.tenantId,\n sourcePath: request.source.path,\n chunkIndex: 0,\n mimeType,\n parser,\n ...(request.metadata ?? {}),\n },\n },\n ],\n frontmatter,\n };\n}\n\nfunction serializeForContentStore(\n parsed: ParsedDocument,\n metadata: Record<string, string> | undefined,\n): string {\n const frontmatter = {\n ...parsed.frontmatter,\n ...(metadata ?? {}),\n source_mime: parsed.mimeType,\n parser: parsed.parser,\n };\n const body = parsed.chunks.map((chunk) => chunk.text).join(\"\\n\\n\");\n return serializeContentFrontmatter(frontmatter, body);\n}\n\nfunction htmlToText(html: string): string {\n return html\n .replace(/<script[\\s\\S]*?<\\/script>/gi, \" \")\n .replace(/<style[\\s\\S]*?<\\/style>/gi, \" \")\n .replace(/<\\/(h[1-6]|p|li|tr|div)>/gi, \"\\n\\n\")\n .replace(/<[^>]+>/g, \" \")\n .replace(/ /g, \" \")\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/[ \\t]+/g, \" \")\n .replace(/\\n\\s+/g, \"\\n\")\n .trim();\n}\n"],"mappings":";AAAA,SAAS,gBAAgB;AACzB,SAAS,SAAS,YAAY;AAC9B,SAAS,+BAA+B;AACxC,SAAS,uBAAuB,2BAA2B;AAC3D;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,uBAAuB;AA4FhC,eAAsB,0BACpB,OAAO,QAAQ,IAAI,GACnB,QAAQ,IACY;AACpB,SAAO,oBAAoB,qBAAqB,EAAE,MAAM,MAAM,CAAC;AACjE;AAEO,IAAM,mBAAN,MAAM,kBAAiB;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAET,YAAY,UAAmC,CAAC,GAAG;AACjD,SAAK,YAAY,QAAQ;AACzB,SAAK,QAAQ,QAAQ,QAAQ,KAAK,QAAQ,IAAI,GAAG,YAAY,mBAAmB;AAChF,SAAK,aAAa,QAAQ,aAAa,QAAQ,IAAI;AACnD,SAAK,gBAAgB,QAAQ;AAC7B,SAAK,iBAAiB,QAAQ;AAAA,EAChC;AAAA,EAEA,aAAa,KACX,OAAO,8BACP,UAAkE,CAAC,GACxC;AAC3B,UAAM,WAAW,QAAQ,YAAY;AACrC,UAAM,eAAe,MAAM,aAAa,KAAK,KAAK,MAAM,SAAS,GAAG,EAAE,SAAS,CAAC;AAChF,WAAO,IAAI,kBAAiB,EAAE,GAAG,SAAS,UAAU,MAAM,aAAa,CAAC;AAAA,EAC1E;AAAA,EAEA,MAAM,MAAM,SAAgD;AAC1D,UAAM,WAAW,wBAAwB;AAAA,MACvC,UAAU,QAAQ;AAAA,MAClB,UAAU,KAAK;AAAA,MACf,WAAW,MACT,IAAI,gBAAgB,qBAAqB,8CAA8C;AAAA,QACrF,YACE;AAAA,QACF,YAAY;AAAA,MACd,CAAC;AAAA,IACL,CAAC;AACD,UAAM,WAAyC,EAAE,GAAG,SAAS,SAAS;AACtE,UAAM,SAAS,KAAK,cAAc,QAAQ;AAC1C,QAAI,WAAW,WAAW;AACxB,UAAI,CAAC,KAAK,gBAAgB;AACxB,cAAM,IAAI,gBAAgB,qBAAqB,oCAAoC;AAAA,UACjF,YACE;AAAA,UACF,YAAY;AAAA,UACZ,UAAU,EAAE,MAAM,QAAQ,OAAO,KAAK;AAAA,QACxC,CAAC;AAAA,MACH;AACA,YAAMA,UAAS,MAAM,KAAK,eAAe,MAAM,QAAQ;AACvD,YAAM;AAAA,QACJ;AAAA,QACA;AAAA,UACE,MAAM;AAAA,UACN;AAAA,UACA,QAAQ;AAAA,UACR,MAAMA,QAAO;AAAA,QACf;AAAA,QACA,EAAE,MAAM,KAAK,WAAW;AAAA,MAC1B;AACA,aAAOA;AAAA,IACT;AAEA,UAAM,MAAM,MAAM,WAAW,QAAQ,MAAM;AAC3C,UAAM,SAAS,oBAAoB,UAAU,KAAK,MAAM;AACxD,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA,MAAM,OAAO;AAAA,QACb,QAAQ,OAAO,OAAO;AAAA,MACxB;AAAA,MACA,EAAE,MAAM,KAAK,WAAW;AAAA,IAC1B;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,OAAO,SAA+C;AAC1D,UAAM,WAAW,wBAAwB;AAAA,MACvC,UAAU,QAAQ;AAAA,MAClB,UAAU,KAAK;AAAA,MACf,WAAW,MACT,IAAI,gBAAgB,qBAAqB,8CAA8C;AAAA,QACrF,YACE;AAAA,QACF,YAAY;AAAA,MACd,CAAC;AAAA,IACL,CAAC;AACD,UAAM,eAAe,MAAM,KAAK,qBAAqB,QAAQ;AAC7D,UAAM,SAAS,MAAM,KAAK,MAAM,EAAE,GAAG,SAAS,SAAS,CAAC;AACxD,UAAM,aAAa,QAAQ,cAAc,OAAO;AAChD,UAAM,UAAU,yBAAyB,QAAQ,QAAQ,QAAQ;AACjE,UAAM,aAAa,MAAM,YAAY,OAAO;AAC5C,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN;AAAA,QACA,MAAM;AAAA,QACN,QAAQ,OAAO;AAAA,QACf,QAAQ,OAAO,OAAO;AAAA,MACxB;AAAA,MACA,EAAE,MAAM,KAAK,WAAW;AAAA,IAC1B;AACA,WAAO;AAAA,MACL;AAAA,MACA,MAAM;AAAA,MACN,QAAQ,OAAO;AAAA,MACf,YAAY,OAAO,OAAO;AAAA,MAC1B,gBAAgB;AAAA,IAClB;AAAA,EACF;AAAA,EAEA,MAAM,WACJ,MACA,UAAyC,CAAC,GACnB;AACvB,WAAO,KAAK,OAAO,EAAE,GAAG,SAAS,QAAQ,EAAE,MAAM,QAAQ,KAAK,EAAE,CAAC;AAAA,EACnE;AAAA,EAEA,MAAM,SAAgD;AACpD,UAAM,eAAe,MAAM,KAAK,qBAAqB,KAAK,aAAa,OAAO;AAC9E,UAAM,UAAU,KAAK,iBAAiB,MAAM,KAAK,eAAe,OAAO,IAAI,EAAE,IAAI,MAAM;AACvF,WAAO;AAAA,MACL,YAAY;AAAA,MACZ,eAAe,CAAC,YAAY,QAAQ,MAAM;AAAA,MAC1C,mBAAmB,QAAQ;AAAA,MAC3B,cAAc,MAAM,aAAa,OAAO;AAAA,MACxC,GAAI,QAAQ,KACR,CAAC,IACD;AAAA,QACE,YACE;AAAA,MACJ;AAAA,IACN;AAAA,EACF;AAAA,EAEA,cAAc,SAAqD;AACjE,QAAI,QAAQ,UAAU,QAAQ,WAAW,OAAQ,QAAO,QAAQ;AAChE,UAAM,OAAO,QAAQ,OAAO,YAAY,aAAa,QAAQ,OAAO,IAAI;AACxE,QAAI,SAAS,gBAAiB,QAAO;AACrC,QAAI,SAAS,YAAa,QAAO;AACjC,QAAI,KAAK,WAAW,OAAO,EAAG,QAAO;AACrC,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,qBAAqB,UAAyC;AAClE,QAAI,KAAK,cAAe,QAAO,KAAK;AACpC,WAAO,aAAa,KAAK,KAAK,KAAK,OAAO,SAAS,GAAG,EAAE,SAAS,CAAC;AAAA,EACpE;AACF;AAEA,eAAe,WAAW,QAAyC;AACjE,SAAO,OAAO,SAAS,WAAW,OAAO,UAAU,SAAS,OAAO,MAAM,MAAM;AACjF;AAEA,SAAS,aAAa,MAAsB;AAC1C,QAAM,YAAY,QAAQ,IAAI,EAAE,YAAY;AAC5C,MAAI,cAAc,SAAS,cAAc,OAAQ,QAAO;AACxD,MAAI,cAAc,WAAW,cAAc,OAAQ,QAAO;AAC1D,MAAI,cAAc,UAAU,cAAc,UAAU,cAAc,QAAS,QAAO;AAClF,MAAI,cAAc,OAAQ,QAAO;AACjC,MAAI,cAAc;AAChB,WAAO;AACT,MAAI,cAAc;AAChB,WAAO;AACT,MAAI,cAAc,UAAU,cAAc,UAAU,cAAc,QAAS,QAAO;AAClF,SAAO;AACT;AAEA,SAAS,oBACP,SACA,KACA,QACgB;AAChB,QAAM,WAAW,QAAQ,OAAO,YAAY,aAAa,QAAQ,OAAO,IAAI;AAC5E,QAAM,EAAE,aAAa,KAAK,IACxB,WAAW,aAAa,wBAAwB,GAAG,IAAI,EAAE,aAAa,CAAC,GAAG,MAAM,IAAI;AACtF,QAAM,OAAO,WAAW,SAAS,WAAW,IAAI,IAAI;AACpD,QAAM,SAAS,uBAAuB,IAAI,EAAE,IAAI,CAAC,OAAO,WAAW;AAAA,IACjE,IAAI,GAAG,QAAQ,QAAQ,IAAI,QAAQ,OAAO,IAAI,IAAI,KAAK;AAAA,IACvD,MAAM;AAAA,IACN,UAAU;AAAA,MACR,UAAU,QAAQ;AAAA,MAClB,YAAY,QAAQ,OAAO;AAAA,MAC3B,YAAY;AAAA,MACZ;AAAA,MACA;AAAA,MACA,GAAI,QAAQ,YAAY,CAAC;AAAA,IAC3B;AAAA,EACF,EAAE;AACF,SAAO;AAAA,IACL,UAAU,QAAQ;AAAA,IAClB,MAAM,QAAQ,OAAO;AAAA,IACrB;AAAA,IACA;AAAA,IACA,QACE,OAAO,SAAS,IACZ,SACA;AAAA,MACE;AAAA,QACE,IAAI,GAAG,QAAQ,QAAQ,IAAI,QAAQ,OAAO,IAAI;AAAA,QAC9C;AAAA,QACA,UAAU;AAAA,UACR,UAAU,QAAQ;AAAA,UAClB,YAAY,QAAQ,OAAO;AAAA,UAC3B,YAAY;AAAA,UACZ;AAAA,UACA;AAAA,UACA,GAAI,QAAQ,YAAY,CAAC;AAAA,QAC3B;AAAA,MACF;AAAA,IACF;AAAA,IACN;AAAA,EACF;AACF;AAEA,SAAS,yBACP,QACA,UACQ;AACR,QAAM,cAAc;AAAA,IAClB,GAAG,OAAO;AAAA,IACV,GAAI,YAAY,CAAC;AAAA,IACjB,aAAa,OAAO;AAAA,IACpB,QAAQ,OAAO;AAAA,EACjB;AACA,QAAM,OAAO,OAAO,OAAO,IAAI,CAAC,UAAU,MAAM,IAAI,EAAE,KAAK,MAAM;AACjE,SAAO,4BAA4B,aAAa,IAAI;AACtD;AAEA,SAAS,WAAW,MAAsB;AACxC,SAAO,KACJ,QAAQ,+BAA+B,GAAG,EAC1C,QAAQ,6BAA6B,GAAG,EACxC,QAAQ,8BAA8B,MAAM,EAC5C,QAAQ,YAAY,GAAG,EACvB,QAAQ,WAAW,GAAG,EACtB,QAAQ,UAAU,GAAG,EACrB,QAAQ,SAAS,GAAG,EACpB,QAAQ,SAAS,GAAG,EACpB,QAAQ,WAAW,GAAG,EACtB,QAAQ,UAAU,IAAI,EACtB,KAAK;AACV;","names":["parsed"]}
|
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nebutra/document-pipeline",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Document parse, chunk, embed-port, and content-store ingestion pipeline",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,9 +28,10 @@
|
|
|
28
28
|
".": "./src/index.ts"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@nebutra/
|
|
32
|
-
"@nebutra/
|
|
33
|
-
"@nebutra/
|
|
31
|
+
"@nebutra/capability-kit": "0.2.1",
|
|
32
|
+
"@nebutra/content-store": "0.1.1",
|
|
33
|
+
"@nebutra/errors": "0.1.1",
|
|
34
|
+
"@nebutra/sandbox-runtime": "0.1.1"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
36
37
|
"@types/node": "^22.19.15",
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { extname, join } from "node:path";
|
|
3
|
+
import { requireCapabilityTenant } from "@nebutra/capability-kit";
|
|
4
|
+
import { appendCapabilityDebug, readCapabilityDebug } from "@nebutra/capability-kit/debug";
|
|
5
|
+
import {
|
|
6
|
+
ContentStore,
|
|
7
|
+
parseContentFrontmatter,
|
|
8
|
+
serializeContentFrontmatter,
|
|
9
|
+
splitContentParagraphs,
|
|
10
|
+
} from "@nebutra/content-store";
|
|
4
11
|
import { CapabilityError } from "@nebutra/errors";
|
|
5
12
|
|
|
6
13
|
export type ParserChoice = "auto" | "markdown" | "html" | "text" | "sidecar";
|
|
@@ -93,45 +100,11 @@ type RequiredTenant<T extends { readonly tenantId?: string }> = Omit<T, "tenantI
|
|
|
93
100
|
readonly tenantId: string;
|
|
94
101
|
};
|
|
95
102
|
|
|
96
|
-
function debugPath(root = process.cwd()): string {
|
|
97
|
-
return join(root, ".nebutra", "debug", "document-pipeline.jsonl");
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
async function appendDebug(root: string, entry: Record<string, unknown>): Promise<void> {
|
|
101
|
-
const path = debugPath(root);
|
|
102
|
-
await mkdir(dirname(path), { recursive: true });
|
|
103
|
-
await writeFile(path, `${JSON.stringify({ at: new Date().toISOString(), ...entry })}\n`, {
|
|
104
|
-
flag: "a",
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
|
|
108
103
|
export async function readDocumentPipelineDebug(
|
|
109
104
|
root = process.cwd(),
|
|
110
105
|
limit = 20,
|
|
111
106
|
): Promise<unknown[]> {
|
|
112
|
-
|
|
113
|
-
const raw = await readFile(debugPath(root), "utf8");
|
|
114
|
-
return raw
|
|
115
|
-
.trim()
|
|
116
|
-
.split("\n")
|
|
117
|
-
.filter(Boolean)
|
|
118
|
-
.slice(-limit)
|
|
119
|
-
.map((line) => JSON.parse(line) as unknown);
|
|
120
|
-
} catch {
|
|
121
|
-
return [];
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function requireTenant(explicit: string | undefined, fallback: string | undefined): string {
|
|
126
|
-
const tenantId = explicit ?? fallback;
|
|
127
|
-
if (!tenantId) {
|
|
128
|
-
throw new CapabilityError("document-pipeline", "Document ingestion requires tenant context", {
|
|
129
|
-
suggestion:
|
|
130
|
-
"Pass tenantId on the request or construct DocumentPipeline with a tenantId default.",
|
|
131
|
-
statusCode: 400,
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
return tenantId;
|
|
107
|
+
return readCapabilityDebug("document-pipeline", { root, limit });
|
|
135
108
|
}
|
|
136
109
|
|
|
137
110
|
export class DocumentPipeline {
|
|
@@ -159,7 +132,16 @@ export class DocumentPipeline {
|
|
|
159
132
|
}
|
|
160
133
|
|
|
161
134
|
async parse(request: ParseRequest): Promise<ParsedDocument> {
|
|
162
|
-
const tenantId =
|
|
135
|
+
const tenantId = requireCapabilityTenant({
|
|
136
|
+
explicit: request.tenantId,
|
|
137
|
+
fallback: this.#tenantId,
|
|
138
|
+
onMissing: () =>
|
|
139
|
+
new CapabilityError("document-pipeline", "Document ingestion requires tenant context", {
|
|
140
|
+
suggestion:
|
|
141
|
+
"Pass tenantId on the request or construct DocumentPipeline with a tenantId default.",
|
|
142
|
+
statusCode: 400,
|
|
143
|
+
}),
|
|
144
|
+
});
|
|
163
145
|
const required: RequiredTenant<ParseRequest> = { ...request, tenantId };
|
|
164
146
|
const parser = this.#chooseParser(required);
|
|
165
147
|
if (parser === "sidecar") {
|
|
@@ -172,41 +154,62 @@ export class DocumentPipeline {
|
|
|
172
154
|
});
|
|
173
155
|
}
|
|
174
156
|
const parsed = await this.#sidecarParser.parse(required);
|
|
175
|
-
await
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
157
|
+
await appendCapabilityDebug(
|
|
158
|
+
"document-pipeline",
|
|
159
|
+
{
|
|
160
|
+
type: "parse",
|
|
161
|
+
tenantId,
|
|
162
|
+
parser: "sidecar",
|
|
163
|
+
path: parsed.path,
|
|
164
|
+
},
|
|
165
|
+
{ root: this.#debugRoot },
|
|
166
|
+
);
|
|
181
167
|
return parsed;
|
|
182
168
|
}
|
|
183
169
|
|
|
184
170
|
const raw = await readSource(request.source);
|
|
185
171
|
const parsed = parseNativeDocument(required, raw, parser);
|
|
186
|
-
await
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
172
|
+
await appendCapabilityDebug(
|
|
173
|
+
"document-pipeline",
|
|
174
|
+
{
|
|
175
|
+
type: "parse",
|
|
176
|
+
tenantId,
|
|
177
|
+
parser,
|
|
178
|
+
path: parsed.path,
|
|
179
|
+
chunks: parsed.chunks.length,
|
|
180
|
+
},
|
|
181
|
+
{ root: this.#debugRoot },
|
|
182
|
+
);
|
|
193
183
|
return parsed;
|
|
194
184
|
}
|
|
195
185
|
|
|
196
186
|
async ingest(request: IngestRequest): Promise<IngestResult> {
|
|
197
|
-
const tenantId =
|
|
187
|
+
const tenantId = requireCapabilityTenant({
|
|
188
|
+
explicit: request.tenantId,
|
|
189
|
+
fallback: this.#tenantId,
|
|
190
|
+
onMissing: () =>
|
|
191
|
+
new CapabilityError("document-pipeline", "Document ingestion requires tenant context", {
|
|
192
|
+
suggestion:
|
|
193
|
+
"Pass tenantId on the request or construct DocumentPipeline with a tenantId default.",
|
|
194
|
+
statusCode: 400,
|
|
195
|
+
}),
|
|
196
|
+
});
|
|
198
197
|
const contentStore = await this.#resolveContentStore(tenantId);
|
|
199
198
|
const parsed = await this.parse({ ...request, tenantId });
|
|
200
199
|
const targetPath = request.targetPath ?? parsed.path;
|
|
201
200
|
const content = serializeForContentStore(parsed, request.metadata);
|
|
202
201
|
await contentStore.write(targetPath, content);
|
|
203
|
-
await
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
202
|
+
await appendCapabilityDebug(
|
|
203
|
+
"document-pipeline",
|
|
204
|
+
{
|
|
205
|
+
type: "ingest",
|
|
206
|
+
tenantId,
|
|
207
|
+
path: targetPath,
|
|
208
|
+
parser: parsed.parser,
|
|
209
|
+
chunks: parsed.chunks.length,
|
|
210
|
+
},
|
|
211
|
+
{ root: this.#debugRoot },
|
|
212
|
+
);
|
|
210
213
|
return {
|
|
211
214
|
tenantId,
|
|
212
215
|
path: targetPath,
|
|
@@ -280,9 +283,9 @@ function parseNativeDocument(
|
|
|
280
283
|
): ParsedDocument {
|
|
281
284
|
const mimeType = request.source.mimeType ?? mimeFromPath(request.source.path);
|
|
282
285
|
const { frontmatter, body } =
|
|
283
|
-
parser === "markdown" ?
|
|
286
|
+
parser === "markdown" ? parseContentFrontmatter(raw) : { frontmatter: {}, body: raw };
|
|
284
287
|
const text = parser === "html" ? htmlToText(body) : body;
|
|
285
|
-
const chunks =
|
|
288
|
+
const chunks = splitContentParagraphs(text).map((chunk, index) => ({
|
|
286
289
|
id: `${request.tenantId}:${request.source.path}:${index}`,
|
|
287
290
|
text: chunk,
|
|
288
291
|
metadata: {
|
|
@@ -330,34 +333,8 @@ function serializeForContentStore(
|
|
|
330
333
|
source_mime: parsed.mimeType,
|
|
331
334
|
parser: parsed.parser,
|
|
332
335
|
};
|
|
333
|
-
const frontmatterLines = Object.entries(frontmatter)
|
|
334
|
-
.sort(([left], [right]) => left.localeCompare(right))
|
|
335
|
-
.map(([key, value]) => `${key}: ${value}`);
|
|
336
336
|
const body = parsed.chunks.map((chunk) => chunk.text).join("\n\n");
|
|
337
|
-
return
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
function parseFrontmatter(content: string): {
|
|
341
|
-
readonly frontmatter: Record<string, string>;
|
|
342
|
-
readonly body: string;
|
|
343
|
-
} {
|
|
344
|
-
if (!content.startsWith("---\n")) return { frontmatter: {}, body: content };
|
|
345
|
-
const end = content.indexOf("\n---", 4);
|
|
346
|
-
if (end < 0) return { frontmatter: {}, body: content };
|
|
347
|
-
const raw = content.slice(4, end).trim();
|
|
348
|
-
const frontmatter: Record<string, string> = {};
|
|
349
|
-
for (const line of raw.split("\n")) {
|
|
350
|
-
const idx = line.indexOf(":");
|
|
351
|
-
if (idx > 0) frontmatter[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
|
|
352
|
-
}
|
|
353
|
-
return { frontmatter, body: content.slice(end + 4).trimStart() };
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
function chunkParagraphs(content: string): string[] {
|
|
357
|
-
return content
|
|
358
|
-
.split(/\n\s*\n/)
|
|
359
|
-
.map((part) => part.trim())
|
|
360
|
-
.filter(Boolean);
|
|
337
|
+
return serializeContentFrontmatter(frontmatter, body);
|
|
361
338
|
}
|
|
362
339
|
|
|
363
340
|
function htmlToText(html: string): string {
|
package/tsup.config.ts
CHANGED
|
@@ -7,5 +7,10 @@ export default defineConfig({
|
|
|
7
7
|
sourcemap: true,
|
|
8
8
|
clean: true,
|
|
9
9
|
target: "es2022",
|
|
10
|
-
external: [
|
|
10
|
+
external: [
|
|
11
|
+
"@nebutra/capability-kit/debug",
|
|
12
|
+
"@nebutra/content-store",
|
|
13
|
+
"@nebutra/errors",
|
|
14
|
+
"@nebutra/sandbox-runtime",
|
|
15
|
+
],
|
|
11
16
|
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, extname, join } from \"node:path\";\nimport { ContentStore } from \"@nebutra/content-store\";\nimport { CapabilityError } from \"@nebutra/errors\";\n\nexport type ParserChoice = \"auto\" | \"markdown\" | \"html\" | \"text\" | \"sidecar\";\nexport type ChunkerChoice = \"paragraph\" | \"recursive\" | \"hierarchical\";\n\nexport interface InlineSource {\n readonly type: \"inline\";\n readonly path: string;\n readonly content: string;\n readonly mimeType?: string;\n}\n\nexport interface FileSource {\n readonly type: \"file\";\n readonly path: string;\n readonly mimeType?: string;\n}\n\nexport type DocumentSource = InlineSource | FileSource;\n\nexport interface IngestRequest {\n readonly tenantId?: string;\n readonly source: DocumentSource;\n readonly parser?: ParserChoice;\n readonly chunker?: ChunkerChoice;\n readonly targetPath?: string;\n readonly metadata?: Record<string, string>;\n}\n\nexport interface ParseRequest {\n readonly tenantId?: string;\n readonly source: DocumentSource;\n readonly parser?: ParserChoice;\n readonly chunker?: ChunkerChoice;\n readonly metadata?: Record<string, string>;\n}\n\nexport interface ParsedChunk {\n readonly id: string;\n readonly text: string;\n readonly metadata: {\n readonly tenantId: string;\n readonly sourcePath: string;\n readonly chunkIndex: number;\n readonly mimeType: string;\n readonly parser: ParserChoice;\n readonly heading?: string;\n } & Record<string, string | number>;\n}\n\nexport interface ParsedDocument {\n readonly tenantId: string;\n readonly path: string;\n readonly mimeType: string;\n readonly parser: ParserChoice;\n readonly chunks: readonly ParsedChunk[];\n readonly frontmatter: Record<string, string>;\n}\n\nexport interface IngestResult {\n readonly tenantId: string;\n readonly path: string;\n readonly parser: ParserChoice;\n readonly chunkCount: number;\n readonly contentIndexed: true;\n}\n\nexport interface DocumentPipelineDoctorReport {\n readonly capability: \"document-pipeline\";\n readonly nativeParsers: readonly ParserChoice[];\n readonly sidecarConfigured: boolean;\n readonly contentStore: Awaited<ReturnType<ContentStore[\"doctor\"]>>;\n readonly suggestion?: string;\n}\n\nexport interface DocumentParser {\n parse(request: RequiredTenant<ParseRequest>): Promise<ParsedDocument>;\n doctor(): Promise<{ readonly ok: boolean; readonly suggestion?: string }>;\n}\n\nexport interface DocumentPipelineOptions {\n readonly tenantId?: string;\n readonly root?: string;\n readonly debugRoot?: string;\n readonly contentStore?: ContentStore;\n readonly sidecarParser?: DocumentParser;\n}\n\ntype RequiredTenant<T extends { readonly tenantId?: string }> = Omit<T, \"tenantId\"> & {\n readonly tenantId: string;\n};\n\nfunction debugPath(root = process.cwd()): string {\n return join(root, \".nebutra\", \"debug\", \"document-pipeline.jsonl\");\n}\n\nasync function appendDebug(root: string, entry: Record<string, unknown>): Promise<void> {\n const path = debugPath(root);\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, `${JSON.stringify({ at: new Date().toISOString(), ...entry })}\\n`, {\n flag: \"a\",\n });\n}\n\nexport async function readDocumentPipelineDebug(\n root = process.cwd(),\n limit = 20,\n): Promise<unknown[]> {\n try {\n const raw = await readFile(debugPath(root), \"utf8\");\n return raw\n .trim()\n .split(\"\\n\")\n .filter(Boolean)\n .slice(-limit)\n .map((line) => JSON.parse(line) as unknown);\n } catch {\n return [];\n }\n}\n\nfunction requireTenant(explicit: string | undefined, fallback: string | undefined): string {\n const tenantId = explicit ?? fallback;\n if (!tenantId) {\n throw new CapabilityError(\"document-pipeline\", \"Document ingestion requires tenant context\", {\n suggestion:\n \"Pass tenantId on the request or construct DocumentPipeline with a tenantId default.\",\n statusCode: 400,\n });\n }\n return tenantId;\n}\n\nexport class DocumentPipeline {\n readonly #tenantId: string | undefined;\n readonly #root: string;\n readonly #debugRoot: string;\n readonly #contentStore: ContentStore | undefined;\n readonly #sidecarParser: DocumentParser | undefined;\n\n constructor(options: DocumentPipelineOptions = {}) {\n this.#tenantId = options.tenantId;\n this.#root = options.root ?? join(process.cwd(), \".nebutra\", \"document-pipeline\");\n this.#debugRoot = options.debugRoot ?? process.cwd();\n this.#contentStore = options.contentStore;\n this.#sidecarParser = options.sidecarParser;\n }\n\n static async open(\n root = \".nebutra/document-pipeline\",\n options: Omit<DocumentPipelineOptions, \"root\" | \"contentStore\"> = {},\n ): Promise<DocumentPipeline> {\n const tenantId = options.tenantId ?? \"local\";\n const contentStore = await ContentStore.open(join(root, \"content\"), { tenantId });\n return new DocumentPipeline({ ...options, tenantId, root, contentStore });\n }\n\n async parse(request: ParseRequest): Promise<ParsedDocument> {\n const tenantId = requireTenant(request.tenantId, this.#tenantId);\n const required: RequiredTenant<ParseRequest> = { ...request, tenantId };\n const parser = this.#chooseParser(required);\n if (parser === \"sidecar\") {\n if (!this.#sidecarParser) {\n throw new CapabilityError(\"document-pipeline\", \"Parser sidecar is not configured\", {\n suggestion:\n \"Configure the parser sidecar for PDFs, office files, OCR, and layout-aware extraction.\",\n statusCode: 503,\n metadata: { path: request.source.path },\n });\n }\n const parsed = await this.#sidecarParser.parse(required);\n await appendDebug(this.#debugRoot, {\n type: \"parse\",\n tenantId,\n parser: \"sidecar\",\n path: parsed.path,\n });\n return parsed;\n }\n\n const raw = await readSource(request.source);\n const parsed = parseNativeDocument(required, raw, parser);\n await appendDebug(this.#debugRoot, {\n type: \"parse\",\n tenantId,\n parser,\n path: parsed.path,\n chunks: parsed.chunks.length,\n });\n return parsed;\n }\n\n async ingest(request: IngestRequest): Promise<IngestResult> {\n const tenantId = requireTenant(request.tenantId, this.#tenantId);\n const contentStore = await this.#resolveContentStore(tenantId);\n const parsed = await this.parse({ ...request, tenantId });\n const targetPath = request.targetPath ?? parsed.path;\n const content = serializeForContentStore(parsed, request.metadata);\n await contentStore.write(targetPath, content);\n await appendDebug(this.#debugRoot, {\n type: \"ingest\",\n tenantId,\n path: targetPath,\n parser: parsed.parser,\n chunks: parsed.chunks.length,\n });\n return {\n tenantId,\n path: targetPath,\n parser: parsed.parser,\n chunkCount: parsed.chunks.length,\n contentIndexed: true,\n };\n }\n\n async ingestFile(\n path: string,\n options: Omit<IngestRequest, \"source\"> = {},\n ): Promise<IngestResult> {\n return this.ingest({ ...options, source: { type: \"file\", path } });\n }\n\n async doctor(): Promise<DocumentPipelineDoctorReport> {\n const contentStore = await this.#resolveContentStore(this.#tenantId ?? \"local\");\n const sidecar = this.#sidecarParser ? await this.#sidecarParser.doctor() : { ok: false };\n return {\n capability: \"document-pipeline\",\n nativeParsers: [\"markdown\", \"html\", \"text\"],\n sidecarConfigured: sidecar.ok,\n contentStore: await contentStore.doctor(),\n ...(sidecar.ok\n ? {}\n : {\n suggestion:\n \"Native parsers are active; configure the parser sidecar before ingesting PDFs, office files, OCR, or complex layout documents.\",\n }),\n };\n }\n\n #chooseParser(request: RequiredTenant<ParseRequest>): ParserChoice {\n if (request.parser && request.parser !== \"auto\") return request.parser;\n const mime = request.source.mimeType ?? mimeFromPath(request.source.path);\n if (mime === \"text/markdown\") return \"markdown\";\n if (mime === \"text/html\") return \"html\";\n if (mime.startsWith(\"text/\")) return \"text\";\n return \"sidecar\";\n }\n\n async #resolveContentStore(tenantId: string): Promise<ContentStore> {\n if (this.#contentStore) return this.#contentStore;\n return ContentStore.open(join(this.#root, \"content\"), { tenantId });\n }\n}\n\nasync function readSource(source: DocumentSource): Promise<string> {\n return source.type === \"inline\" ? source.content : readFile(source.path, \"utf8\");\n}\n\nfunction mimeFromPath(path: string): string {\n const extension = extname(path).toLowerCase();\n if (extension === \".md\" || extension === \".mdx\") return \"text/markdown\";\n if (extension === \".html\" || extension === \".htm\") return \"text/html\";\n if (extension === \".txt\" || extension === \".csv\" || extension === \".json\") return \"text/plain\";\n if (extension === \".pdf\") return \"application/pdf\";\n if (extension === \".docx\")\n return \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\";\n if (extension === \".pptx\")\n return \"application/vnd.openxmlformats-officedocument.presentationml.presentation\";\n if (extension === \".png\" || extension === \".jpg\" || extension === \".jpeg\") return \"image\";\n return \"application/octet-stream\";\n}\n\nfunction parseNativeDocument(\n request: RequiredTenant<ParseRequest>,\n raw: string,\n parser: ParserChoice,\n): ParsedDocument {\n const mimeType = request.source.mimeType ?? mimeFromPath(request.source.path);\n const { frontmatter, body } =\n parser === \"markdown\" ? parseFrontmatter(raw) : { frontmatter: {}, body: raw };\n const text = parser === \"html\" ? htmlToText(body) : body;\n const chunks = chunkParagraphs(text).map((chunk, index) => ({\n id: `${request.tenantId}:${request.source.path}:${index}`,\n text: chunk,\n metadata: {\n tenantId: request.tenantId,\n sourcePath: request.source.path,\n chunkIndex: index,\n mimeType,\n parser,\n ...(request.metadata ?? {}),\n },\n }));\n return {\n tenantId: request.tenantId,\n path: request.source.path,\n mimeType,\n parser,\n chunks:\n chunks.length > 0\n ? chunks\n : [\n {\n id: `${request.tenantId}:${request.source.path}:0`,\n text,\n metadata: {\n tenantId: request.tenantId,\n sourcePath: request.source.path,\n chunkIndex: 0,\n mimeType,\n parser,\n ...(request.metadata ?? {}),\n },\n },\n ],\n frontmatter,\n };\n}\n\nfunction serializeForContentStore(\n parsed: ParsedDocument,\n metadata: Record<string, string> | undefined,\n): string {\n const frontmatter = {\n ...parsed.frontmatter,\n ...(metadata ?? {}),\n source_mime: parsed.mimeType,\n parser: parsed.parser,\n };\n const frontmatterLines = Object.entries(frontmatter)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([key, value]) => `${key}: ${value}`);\n const body = parsed.chunks.map((chunk) => chunk.text).join(\"\\n\\n\");\n return frontmatterLines.length > 0 ? `---\\n${frontmatterLines.join(\"\\n\")}\\n---\\n${body}` : body;\n}\n\nfunction parseFrontmatter(content: string): {\n readonly frontmatter: Record<string, string>;\n readonly body: string;\n} {\n if (!content.startsWith(\"---\\n\")) return { frontmatter: {}, body: content };\n const end = content.indexOf(\"\\n---\", 4);\n if (end < 0) return { frontmatter: {}, body: content };\n const raw = content.slice(4, end).trim();\n const frontmatter: Record<string, string> = {};\n for (const line of raw.split(\"\\n\")) {\n const idx = line.indexOf(\":\");\n if (idx > 0) frontmatter[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();\n }\n return { frontmatter, body: content.slice(end + 4).trimStart() };\n}\n\nfunction chunkParagraphs(content: string): string[] {\n return content\n .split(/\\n\\s*\\n/)\n .map((part) => part.trim())\n .filter(Boolean);\n}\n\nfunction htmlToText(html: string): string {\n return html\n .replace(/<script[\\s\\S]*?<\\/script>/gi, \" \")\n .replace(/<style[\\s\\S]*?<\\/style>/gi, \" \")\n .replace(/<\\/(h[1-6]|p|li|tr|div)>/gi, \"\\n\\n\")\n .replace(/<[^>]+>/g, \" \")\n .replace(/ /g, \" \")\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/[ \\t]+/g, \" \")\n .replace(/\\n\\s+/g, \"\\n\")\n .trim();\n}\n"],"mappings":"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","names":["parsed"]}
|