@h-ai/datapipe 0.1.0-alpha.67 → 0.1.0-alpha.68

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.en.md CHANGED
@@ -35,6 +35,8 @@ if (!cleaned.success)
35
35
 
36
36
  // 按 Markdown 标题分块
37
37
  const chunks = datapipe.chunk(text, { mode: 'markdown', maxSize: 2000 })
38
+ if (!chunks.success)
39
+ throw new Error(chunks.error.message)
38
40
 
39
41
  // 管线模式(链式组合多步操作)
40
42
  const result = await datapipe.pipeline()
@@ -43,6 +45,8 @@ const result = await datapipe.pipeline()
43
45
  .chunk({ mode: 'paragraph', maxSize: 1000, overlap: 100 })
44
46
  .chunkTransform(chunks => chunks.filter(c => c.content.length > 50))
45
47
  .run(rawText)
48
+ if (!result.success)
49
+ throw new Error(result.error.message)
46
50
  ```
47
51
 
48
52
  ### Chunking modes
@@ -57,6 +61,8 @@ const result = await datapipe.pipeline()
57
61
  | character | Split by character count |
58
62
  | custom | Custom separator (requires separator) |
59
63
 
64
+ Chunk `maxSize` defaults to 1500; `overlap` defaults to 0 and must satisfy `0 <= overlap < maxSize`. Both use non-whitespace words in word mode (continuous Chinese can be one long word), and Unicode code points in every other mode, rather than UTF-16 length, bytes or model tokens. The limit includes overlap and separators. Natural sentence/paragraph/page/custom boundaries only guide split positions; oversized segments use a `maxSize-overlap` sliding window. Original whitespace, punctuation, page markers and custom separators are preserved, so removing overlap reconstructs the source. Chunks are not trimmed or joined with added newlines. Markdown overlaps only within each section, uses the same hard limit and ignores headings inside fenced code. Only `markdownKeepTitle=false` explicitly removes heading lines. Empty or entirely whitespace input returns no chunks. Custom regular expressions must come from trusted configuration; invalid expressions return CHUNK_FAILED.
65
+
60
66
  ### Error handling
61
67
 
62
68
  All operations return `HaiResult<T>`; check `result.success` for success or failure. Error codes use the string format `hai:datapipe:NNN`.
@@ -84,6 +90,8 @@ if (!result.success) {
84
90
  pnpm --filter @h-ai/datapipe test
85
91
  ```
86
92
 
93
+ Source examples distinguish pure functions, synchronous HaiResult and asynchronous pipelines; see the [JSDoc conventions (Chinese)](../../.agents/instructions/module-conventions.md#jsdoc-与示例验证).
94
+
87
95
  ## License
88
96
 
89
97
  Apache-2.0
package/README.ja.md CHANGED
@@ -35,6 +35,8 @@ if (!cleaned.success)
35
35
 
36
36
  // 按 Markdown 标题分块
37
37
  const chunks = datapipe.chunk(text, { mode: 'markdown', maxSize: 2000 })
38
+ if (!chunks.success)
39
+ throw new Error(chunks.error.message)
38
40
 
39
41
  // 管线模式(链式组合多步操作)
40
42
  const result = await datapipe.pipeline()
@@ -43,6 +45,8 @@ const result = await datapipe.pipeline()
43
45
  .chunk({ mode: 'paragraph', maxSize: 1000, overlap: 100 })
44
46
  .chunkTransform(chunks => chunks.filter(c => c.content.length > 50))
45
47
  .run(rawText)
48
+ if (!result.success)
49
+ throw new Error(result.error.message)
46
50
  ```
47
51
 
48
52
  ### 分割モード
@@ -57,6 +61,8 @@ const result = await datapipe.pipeline()
57
61
  | character | 文字数で分割 |
58
62
  | custom | 独自の区切り文字(separator が必要) |
59
63
 
64
+ 分割の `maxSize` は既定1500、`overlap` は既定0で `0 <= overlap < maxSize` が必要です。word モードは非空白の語数(連続する中国語は1語になる場合があります)、その他は Unicode code point を単位とし、UTF-16 length・バイト・モデル token ではありません。重複と区切り文字も上限に含みます。自然な文・段落・ページ・独自境界は切点の優先度だけを決め、長い部分は `maxSize-overlap` で進みます。元の空白、句読点、改ページ、独自区切りを維持し、重複を除けば原文を復元できます。trim や改行の追加はしません。Markdown の重複は section 内だけで同じ上限を使い、コードフェンス内の見出しを本文として扱います。`markdownKeepTitle=false` の場合だけ見出し行を除きます。空または全空白の入力は空配列です。独自正規表現は信頼済み設定に限定し、不正な式は CHUNK_FAILED を返します。
65
+
60
66
  ### エラー処理
61
67
 
62
68
  すべての操作は `HaiResult<T>` を返します。`result.success` で成功・失敗を判定します。エラーコードは文字列形式 `hai:datapipe:NNN` です。
@@ -84,6 +90,8 @@ if (!result.success) {
84
90
  pnpm --filter @h-ai/datapipe test
85
91
  ```
86
92
 
93
+ ソース例は純関数・同期 HaiResult・非同期パイプラインを区別します。[JSDoc 規約(中国語)](../../.agents/instructions/module-conventions.md#jsdoc-与示例验证) を参照してください。
94
+
87
95
  ## ライセンス
88
96
 
89
97
  Apache-2.0
package/README.md CHANGED
@@ -35,6 +35,8 @@ if (!cleaned.success)
35
35
 
36
36
  // 按 Markdown 标题分块
37
37
  const chunks = datapipe.chunk(text, { mode: 'markdown', maxSize: 2000 })
38
+ if (!chunks.success)
39
+ throw new Error(chunks.error.message)
38
40
 
39
41
  // 管线模式(链式组合多步操作)
40
42
  const result = await datapipe.pipeline()
@@ -43,6 +45,8 @@ const result = await datapipe.pipeline()
43
45
  .chunk({ mode: 'paragraph', maxSize: 1000, overlap: 100 })
44
46
  .chunkTransform(chunks => chunks.filter(c => c.content.length > 50))
45
47
  .run(rawText)
48
+ if (!result.success)
49
+ throw new Error(result.error.message)
46
50
  ```
47
51
 
48
52
  ### 分块模式
@@ -53,10 +57,12 @@ const result = await datapipe.pipeline()
53
57
  | paragraph | 按段落分块 |
54
58
  | markdown | 按 Markdown 标题分块 |
55
59
  | page | 按分页符分块 |
56
- | word | 按字数分块 |
60
+ | word | 按非空白词数分块 |
57
61
  | character | 按字符数分块 |
58
62
  | custom | 自定义分隔符(需提供 separator) |
59
63
 
64
+ 分块 `maxSize` 默认 1500,`overlap` 默认 0 且必须满足 `0 <= overlap < maxSize`。word 模式两者均按非空白词计量(连续中文可视为一个长词),其余模式均按 Unicode code point 计量,不是 UTF-16 length、字节或模型 token。上限包含重叠与分隔符;自然句/段/页/自定义边界只影响优先切点,超长段统一按 `maxSize-overlap` 滑窗。保留原始空白、标点、分页符及自定义分隔符,移除重叠后可还原全文;不再 split/join 或 trim 分块。Markdown section 之间不重叠,section 内使用相同硬上限,代码围栏内的标题保留为正文;仅 `markdownKeepTitle=false` 明确移除标题行。空/全空白的整体输入返回空数组。自定义正则须由可信配置提供,非法表达式返回 CHUNK_FAILED。
65
+
60
66
  ### 错误处理
61
67
 
62
68
  所有操作返回 `HaiResult<T>`,通过 `result.success` 判断成功或失败。错误码为字符串格式 `hai:datapipe:NNN`。
@@ -84,6 +90,8 @@ if (!result.success) {
84
90
  pnpm --filter @h-ai/datapipe test
85
91
  ```
86
92
 
93
+ 源码示例区分无需生命周期的纯函数、同步 HaiResult 与异步管线;类型检查方法见[模块 JSDoc 约定](../../.agents/instructions/module-conventions.md#jsdoc-与示例验证)。
94
+
87
95
  ## 许可证
88
96
 
89
97
  Apache-2.0
package/dist/index.d.ts CHANGED
@@ -14,14 +14,10 @@ import { HaiResult } from '@h-ai/core';
14
14
  * 清洗选项 Schema
15
15
  *
16
16
  * 数据清洗规则配置,支持移除 HTML、URL、Email、标准化空白等。
17
- *
18
17
  * @example
19
18
  * ```ts
20
- * const cleanOptions = {
21
- * removeHtml: true,
22
- * removeUrls: true,
23
- * normalizeWhitespace: true,
24
- * }
19
+ * import { CleanOptionsSchema } from '@h-ai/datapipe'
20
+ * const options = CleanOptionsSchema.parse({ removeHtml: true, removeEmails: true })
25
21
  * ```
26
22
  */
27
23
  declare const CleanOptionsSchema: z.ZodObject<{
@@ -35,9 +31,23 @@ declare const CleanOptionsSchema: z.ZodObject<{
35
31
  replacement: z.ZodString;
36
32
  }, z.core.$strip>>>;
37
33
  }, z.core.$strip>;
38
- /** 清洗选项类型 */
34
+ /**
35
+ * 清洗选项类型
36
+ * @example
37
+ * ```ts
38
+ * import { CleanOptionsSchema, type CleanOptions } from '@h-ai/datapipe'
39
+ * const options: CleanOptions = CleanOptionsSchema.parse({ removeHtml: true })
40
+ * ```
41
+ */
39
42
  type CleanOptions = z.infer<typeof CleanOptionsSchema>;
40
- /** 清洗选项输入类型 */
43
+ /**
44
+ * 清洗选项输入类型
45
+ * @example
46
+ * ```ts
47
+ * import type { CleanOptionsInput } from '@h-ai/datapipe'
48
+ * const options: CleanOptionsInput = { removeHtml: true, removeUrls: true }
49
+ * ```
50
+ */
41
51
  type CleanOptionsInput = z.input<typeof CleanOptionsSchema>;
42
52
  /**
43
53
  * 分块模式枚举
@@ -46,9 +56,14 @@ type CleanOptionsInput = z.input<typeof CleanOptionsSchema>;
46
56
  * - `paragraph` — 按段落分块
47
57
  * - `markdown` — 按 Markdown 标题分块(## / ### 等)
48
58
  * - `page` — 按分页符分块
49
- * - `word` — 按字数分块
50
- * - `character` — 按字符数分块
59
+ * - `word` — 按非空白词数分块,不截断单个词
60
+ * - `character` — 按 Unicode code point 分块,不拆代理对
51
61
  * - `custom` — 自定义分隔符分块
62
+ * @example
63
+ * ```ts
64
+ * import { ChunkModeSchema } from '@h-ai/datapipe'
65
+ * const mode = ChunkModeSchema.parse('character')
66
+ * ```
52
67
  */
53
68
  declare const ChunkModeSchema: z.ZodEnum<{
54
69
  custom: "custom";
@@ -59,26 +74,23 @@ declare const ChunkModeSchema: z.ZodEnum<{
59
74
  word: "word";
60
75
  character: "character";
61
76
  }>;
62
- /** 分块模式类型 */
77
+ /**
78
+ * 分块模式类型
79
+ * @example
80
+ * ```ts
81
+ * import type { ChunkMode } from '@h-ai/datapipe'
82
+ * const options: ChunkMode = 'character'
83
+ * ```
84
+ */
63
85
  type ChunkMode = z.infer<typeof ChunkModeSchema>;
64
86
  /**
65
87
  * 分块选项 Schema
66
- *
88
+ * datapipe 的默认 overlap 为 0;消费者可显式提供自己的默认值,例如 AI Knowledge 为 200。
67
89
  * @example
68
90
  * ```ts
69
- * // 按 Markdown 标题分块
70
- * const chunkOptions = {
71
- * mode: 'markdown',
72
- * maxSize: 2000,
73
- * overlap: 200,
74
- * }
75
- *
76
- * // 按字符分块
77
- * const chunkOptions = {
78
- * mode: 'character',
79
- * maxSize: 1000,
80
- * overlap: 100,
81
- * }
91
+ * import { ChunkOptionsSchema } from '@h-ai/datapipe'
92
+ * const options = ChunkOptionsSchema.parse({ mode: 'markdown', maxSize: 500, overlap: 50 })
93
+ * // maxSize 已包含重叠,overlap 必须小于 maxSize;自定义正则仅来自可信配置。
82
94
  * ```
83
95
  */
84
96
  declare const ChunkOptionsSchema: z.ZodObject<{
@@ -97,13 +109,34 @@ declare const ChunkOptionsSchema: z.ZodObject<{
97
109
  markdownMinLevel: z.ZodDefault<z.ZodNumber>;
98
110
  markdownKeepTitle: z.ZodDefault<z.ZodBoolean>;
99
111
  }, z.core.$strip>;
100
- /** 分块选项类型 */
112
+ /**
113
+ * 分块选项类型
114
+ * @example
115
+ * ```ts
116
+ * import { ChunkOptionsSchema, type ChunkOptions } from '@h-ai/datapipe'
117
+ * const options: ChunkOptions = ChunkOptionsSchema.parse({ mode: 'character', maxSize: 500 })
118
+ * ```
119
+ */
101
120
  type ChunkOptions = z.infer<typeof ChunkOptionsSchema>;
102
- /** 分块选项输入类型 */
121
+ /**
122
+ * 分块选项输入类型
123
+ * @example
124
+ * ```ts
125
+ * import type { ChunkOptionsInput } from '@h-ai/datapipe'
126
+ * const options: ChunkOptionsInput = { mode: 'character', maxSize: 500, overlap: 50 }
127
+ * ```
128
+ */
103
129
  type ChunkOptionsInput = z.input<typeof ChunkOptionsSchema>;
104
130
 
105
131
  /**
106
132
  * Datapipe 模块标准错误定义对象。
133
+ * @example
134
+ * ```ts
135
+ * import { HaiDatapipeError } from '@h-ai/datapipe'
136
+ * import { err } from '@h-ai/core'
137
+ * const result = err(HaiDatapipeError.CONFIG_ERROR, 'Invalid chunk configuration')
138
+ * if (!result.success) result.error.httpStatus
139
+ * ```
107
140
  */
108
141
  declare const HaiDatapipeError: {
109
142
  readonly CLEAN_FAILED: _h_ai_core.HaiErrorDef;
@@ -117,20 +150,16 @@ declare const HaiDatapipeError: {
117
150
  * 数据块接口
118
151
  *
119
152
  * 表示经过分块处理后的单个文本片段。
120
- *
121
153
  * @example
122
154
  * ```ts
123
- * const chunk: DataChunk = {
124
- * index: 0,
125
- * content: '## 简介\n这是文档的第一部分。',
126
- * metadata: { title: '简介', level: 2 },
127
- * }
155
+ * import type { DataChunk } from '@h-ai/datapipe'
156
+ * const options: DataChunk = { index: 0, content: '## Intro', metadata: { title: 'Intro', level: 2 } }
128
157
  * ```
129
158
  */
130
159
  interface DataChunk {
131
160
  /** 分块索引(从 0 开始) */
132
161
  index: number;
133
- /** 分块内容 */
162
+ /** 原文切片;保留空白和分隔符。word 以词计量,其他模式以 Unicode code point 计量。 */
134
163
  content: string;
135
164
  /** 分块元数据(可选) */
136
165
  metadata?: Record<string, unknown>;
@@ -139,18 +168,33 @@ interface DataChunk {
139
168
  * 转换函数类型
140
169
  *
141
170
  * 自定义文本后处理函数,可以是同步或异步。
171
+ * @example
172
+ * ```ts
173
+ * import type { TransformFn } from '@h-ai/datapipe'
174
+ * const options: TransformFn = text => text.trim().toLowerCase()
175
+ * ```
142
176
  */
143
177
  type TransformFn = (text: string) => string | Promise<string>;
144
178
  /**
145
179
  * 分块转换函数类型
146
180
  *
147
181
  * 对分块列表进行后处理(如过滤、合并等)。
182
+ * @example
183
+ * ```ts
184
+ * import type { ChunkTransformFn } from '@h-ai/datapipe'
185
+ * const options: ChunkTransformFn = chunks => chunks.filter(chunk => chunk.content.trim().length > 0)
186
+ * ```
148
187
  */
149
188
  type ChunkTransformFn = (chunks: DataChunk[]) => DataChunk[] | Promise<DataChunk[]>;
150
189
  /**
151
190
  * 管线步骤类型
152
191
  *
153
192
  * 管线由一系列步骤组成,支持清洗、分块和自定义转换。
193
+ * @example
194
+ * ```ts
195
+ * import type { PipelineStep } from '@h-ai/datapipe'
196
+ * const options: PipelineStep = { type: 'chunk', options: { mode: 'paragraph', maxSize: 500, overlap: 50 } }
197
+ * ```
154
198
  */
155
199
  type PipelineStep = {
156
200
  type: 'clean';
@@ -167,6 +211,11 @@ type PipelineStep = {
167
211
  };
168
212
  /**
169
213
  * 管线执行结果
214
+ * @example
215
+ * ```ts
216
+ * import type { PipelineResult } from '@h-ai/datapipe'
217
+ * const options: PipelineResult = { text: 'One. Two.', chunks: [{ index: 0, content: 'One. Two.' }] }
218
+ * ```
170
219
  */
171
220
  interface PipelineResult {
172
221
  /** 处理后的文本(分块前的最终文本) */
@@ -178,6 +227,13 @@ interface PipelineResult {
178
227
  * 清洗操作接口
179
228
  *
180
229
  * 通过 `datapipe.clean()` 调用。
230
+ * @example
231
+ * ```ts
232
+ * import { datapipe, type CleanOperations } from '@h-ai/datapipe'
233
+ * const clean: CleanOperations = datapipe.clean
234
+ * const result = clean('<p>Hello</p>')
235
+ * if (result.success) result.data.trim()
236
+ * ```
181
237
  */
182
238
  interface CleanOperations {
183
239
  /**
@@ -193,6 +249,13 @@ interface CleanOperations {
193
249
  * 分块操作接口
194
250
  *
195
251
  * 通过 `datapipe.chunk()` 调用。
252
+ * @example
253
+ * ```ts
254
+ * import { datapipe, type ChunkOperations } from '@h-ai/datapipe'
255
+ * const chunk: ChunkOperations = datapipe.chunk
256
+ * const result = chunk('One. Two.', { mode: 'sentence', maxSize: 10 })
257
+ * if (result.success) result.data.map(item => item.index)
258
+ * ```
196
259
  */
197
260
  interface ChunkOperations {
198
261
  /**
@@ -208,14 +271,16 @@ interface ChunkOperations {
208
271
  * 管线构建器接口
209
272
  *
210
273
  * 通过 `datapipe.pipeline()` 创建,链式添加步骤后执行。
211
- *
212
274
  * @example
213
275
  * ```ts
276
+ * import { datapipe } from '@h-ai/datapipe'
214
277
  * const result = await datapipe.pipeline()
215
278
  * .clean({ removeHtml: true })
216
279
  * .transform(text => text.toLowerCase())
217
- * .chunk({ mode: 'markdown', maxSize: 2000 })
218
- * .run('原始文本')
280
+ * .chunk({ mode: 'character', maxSize: 500, overlap: 50 })
281
+ * .run('<p>One. Two. Three.</p>')
282
+ * if (result.success) result.data.chunks.map(chunk => chunk.content)
283
+ * else result.error.message
219
284
  * ```
220
285
  */
221
286
  interface PipelineBuilder {
@@ -232,22 +297,16 @@ interface PipelineBuilder {
232
297
  }
233
298
  /**
234
299
  * 数据处理管线服务接口
235
- *
236
300
  * @example
237
301
  * ```ts
238
302
  * import { datapipe } from '@h-ai/datapipe'
239
- *
240
- * // 直接清洗
241
- * const cleaned = datapipe.clean('<p>Hello</p>', { removeHtml: true })
242
- *
243
- * // 直接分块
244
- * const chunks = datapipe.chunk(text, { mode: 'markdown', maxSize: 2000 })
245
- *
246
- * // 管线模式
247
303
  * const result = await datapipe.pipeline()
248
- * .clean()
249
- * .chunk({ mode: 'paragraph' })
250
- * .run(text)
304
+ * .clean({ removeHtml: true })
305
+ * .transform(text => text.toLowerCase())
306
+ * .chunk({ mode: 'character', maxSize: 500, overlap: 50 })
307
+ * .run('<p>One. Two. Three.</p>')
308
+ * if (result.success) result.data.chunks.map(chunk => chunk.content)
309
+ * else result.error.message
251
310
  * ```
252
311
  */
253
312
  interface DatapipeFunctions {
@@ -271,24 +330,16 @@ interface DatapipeFunctions {
271
330
  * 数据处理管线服务对象
272
331
  *
273
332
  * 纯函数模块,无需初始化即可使用。
274
- *
275
333
  * @example
276
334
  * ```ts
277
335
  * import { datapipe } from '@h-ai/datapipe'
278
- *
279
- * // 直接清洗
280
- * const cleaned = datapipe.clean('<p>Hello</p>')
281
- *
282
- * // 按 Markdown 标题分块
283
- * const chunks = datapipe.chunk(text, { mode: 'markdown', maxSize: 2000 })
284
- *
285
- * // 管线模式
286
336
  * const result = await datapipe.pipeline()
287
- * .clean({ removeHtml: true, removeUrls: true })
337
+ * .clean({ removeHtml: true })
288
338
  * .transform(text => text.toLowerCase())
289
- * .chunk({ mode: 'paragraph', maxSize: 1000, overlap: 100 })
290
- * .chunkTransform(chunks => chunks.filter(c => c.content.length > 50))
291
- * .run(rawHtml)
339
+ * .chunk({ mode: 'character', maxSize: 500, overlap: 50 })
340
+ * .run('<p>One. Two. Three.</p>')
341
+ * if (result.success) result.data.chunks.map(chunk => chunk.content)
342
+ * else result.error.message
292
343
  * ```
293
344
  */
294
345
  declare const datapipe: DatapipeFunctions;
package/dist/index.js CHANGED
@@ -37,10 +37,10 @@ var ChunkModeSchema = z.enum([
37
37
  var ChunkOptionsSchema = z.object({
38
38
  /** 分块模式 */
39
39
  mode: ChunkModeSchema,
40
- /** 每个分块的最大大小(字符数 / 字数,取决于 mode,默认 1000) */
40
+ /** 每块上限包含 overlap/分隔符;word 按非空白词,其他按 Unicode code point,默认 1500。 */
41
41
  maxSize: z.number().int().positive().default(1500),
42
- /** 重叠大小(字符数 / 字数,用于上下文衔接,默认 200) */
43
- overlap: z.number().int().min(0).default(200),
42
+ /** 重叠使用与 maxSize 相同的单位,必须严格小于 maxSize。 */
43
+ overlap: z.number().int().min(0).default(0),
44
44
  /**
45
45
  * 自定义分隔符正则(仅 mode='custom' 时使用)
46
46
  *
@@ -51,7 +51,7 @@ var ChunkOptionsSchema = z.object({
51
51
  markdownMinLevel: z.number().int().min(1).max(6).default(2),
52
52
  /** 是否保留 Markdown 标题在分块内容中(默认 true) */
53
53
  markdownKeepTitle: z.boolean().default(true)
54
- });
54
+ }).refine((options) => options.overlap < options.maxSize, { path: ["overlap"] });
55
55
 
56
56
  // messages/en-US.json
57
57
  var en_US_default = {
@@ -64,6 +64,17 @@ var en_US_default = {
64
64
  datapipe_missingSeparator: "Custom chunk mode requires a separator parameter"
65
65
  };
66
66
 
67
+ // messages/ja-JP.json
68
+ var ja_JP_default = {
69
+ $schema: "https://inlang.com/schema/inlang-message-format",
70
+ datapipe_cleanFailed: "\u30C6\u30AD\u30B9\u30C8\u306E\u30AF\u30EA\u30FC\u30CB\u30F3\u30B0\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
71
+ datapipe_chunkFailed: "\u30C6\u30AD\u30B9\u30C8\u306E\u5206\u5272\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
72
+ datapipe_transformFailed: "\u30C6\u30AD\u30B9\u30C8\u306E\u5909\u63DB\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
73
+ datapipe_pipelineFailed: "\u30D1\u30A4\u30D7\u30E9\u30A4\u30F3\u306E\u5B9F\u884C\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
74
+ datapipe_configError: "\u30C7\u30FC\u30BF\u30D1\u30A4\u30D7\u8A2D\u5B9A\u306E\u691C\u8A3C\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
75
+ datapipe_missingSeparator: "\u30AB\u30B9\u30BF\u30E0\u5206\u5272\u30E2\u30FC\u30C9\u306B\u306Fseparator\u30D1\u30E9\u30E1\u30FC\u30BF\u30FC\u304C\u5FC5\u8981\u3067\u3059"
76
+ };
77
+
67
78
  // messages/zh-CN.json
68
79
  var zh_CN_default = {
69
80
  $schema: "https://inlang.com/schema/inlang-message-format",
@@ -78,7 +89,8 @@ var zh_CN_default = {
78
89
  // src/datapipe-i18n.ts
79
90
  var datapipeM = core.i18n.createMessageGetter({
80
91
  "zh-CN": zh_CN_default,
81
- "en-US": en_US_default
92
+ "en-US": en_US_default,
93
+ "ja-JP": ja_JP_default
82
94
  });
83
95
  var DatapipeErrorInfo = {
84
96
  CLEAN_FAILED: "001:500",
@@ -91,206 +103,135 @@ var DatapipeErrorInfo = {
91
103
  var HaiDatapipeError = core.error.buildHaiErrorsDef("datapipe", DatapipeErrorInfo);
92
104
 
93
105
  // src/datapipe-chunk.ts
94
- var SENTENCE_SPLIT_REGEX = /(?<=[。!?.!?])\s*/;
95
- var PARAGRAPH_SPLIT_REGEX = /\n\s*\n/;
96
- var PAGE_SPLIT_REGEX = /\f/;
97
- var WORD_SPLIT_REGEX = /\s+/;
98
- function splitBySentence(text) {
99
- const segments = text.split(SENTENCE_SPLIT_REGEX);
100
- return segments.filter((s) => s.trim().length > 0);
101
- }
102
- function splitByParagraph(text) {
103
- const segments = text.split(PARAGRAPH_SPLIT_REGEX);
104
- return segments.filter((s) => s.trim().length > 0);
105
- }
106
- function splitByMarkdown(text, minLevel, keepTitle) {
107
- const headerPattern = new RegExp(`^(#{1,${minLevel}})\\s+(.+)$`, "gm");
108
- const sections = [];
109
- let lastIndex = 0;
110
- let lastTitle = "";
111
- let lastLevel = 0;
112
- let match = null;
113
- match = headerPattern.exec(text);
114
- if (match && match.index > 0) {
115
- const preContent = text.slice(0, match.index).trim();
116
- if (preContent.length > 0) {
117
- sections.push({ content: preContent, title: "", level: 0 });
118
- }
119
- }
120
- headerPattern.lastIndex = 0;
121
- while ((match = headerPattern.exec(text)) !== null) {
122
- if (lastIndex > 0 || lastTitle) {
123
- const content = text.slice(lastIndex, match.index).trim();
124
- if (content.length > 0 || lastTitle) {
125
- const finalContent = keepTitle && lastTitle ? `${"#".repeat(lastLevel)} ${lastTitle}
126
- ${content}` : content;
127
- sections.push({
128
- content: finalContent.trim(),
129
- title: lastTitle,
130
- level: lastLevel
131
- });
132
- }
133
- }
134
- lastTitle = match[2].trim();
135
- lastLevel = match[1].length;
136
- lastIndex = match.index + match[0].length;
137
- }
138
- if (lastTitle || lastIndex > 0) {
139
- const content = text.slice(lastIndex).trim();
140
- const finalContent = keepTitle && lastTitle ? `${"#".repeat(lastLevel)} ${lastTitle}
141
- ${content}` : content;
142
- sections.push({
143
- content: finalContent.trim(),
144
- title: lastTitle,
145
- level: lastLevel
146
- });
147
- }
148
- if (sections.length === 0) {
149
- sections.push({ content: text.trim(), title: "", level: 0 });
106
+ function findBoundaries(text, expression) {
107
+ if (!expression)
108
+ return [];
109
+ const boundaries = [];
110
+ let previousOffset = 0;
111
+ let points = 0;
112
+ for (const match of text.matchAll(expression)) {
113
+ const offset = match.index + match[0].length;
114
+ if (offset <= previousOffset)
115
+ continue;
116
+ points += Array.from(text.slice(previousOffset, offset)).length;
117
+ boundaries.push(points);
118
+ previousOffset = offset;
150
119
  }
151
- return sections.filter((s) => s.content.length > 0);
120
+ return boundaries;
152
121
  }
153
- function splitByPage(text) {
154
- const segments = text.split(PAGE_SPLIT_REGEX);
155
- return segments.filter((s) => s.trim().length > 0);
122
+ function boundedEnd(boundaries, start, limit, overlap) {
123
+ let lower = 0;
124
+ let upper = boundaries.length;
125
+ while (lower < upper) {
126
+ const middle = Math.floor((lower + upper) / 2);
127
+ if (boundaries[middle] <= limit)
128
+ lower = middle + 1;
129
+ else
130
+ upper = middle;
131
+ }
132
+ const candidate = boundaries[lower - 1];
133
+ return candidate !== void 0 && candidate > start + overlap ? candidate : limit;
156
134
  }
157
- function splitByWord(text, maxSize) {
158
- const words = text.split(WORD_SPLIT_REGEX);
135
+ function splitCharacters(text, maxSize, overlap, expression) {
136
+ const points = Array.from(text);
137
+ const boundaries = findBoundaries(text, expression);
159
138
  const chunks = [];
160
- let current = [];
161
- let currentSize = 0;
162
- for (const word of words) {
163
- if (currentSize + 1 > maxSize && current.length > 0) {
164
- chunks.push(current.join(" "));
165
- current = [];
166
- currentSize = 0;
167
- }
168
- current.push(word);
169
- currentSize++;
170
- }
171
- if (current.length > 0) {
172
- chunks.push(current.join(" "));
139
+ for (let start = 0; start < points.length; ) {
140
+ const limit = Math.min(start + maxSize, points.length);
141
+ const end = limit === points.length ? limit : boundedEnd(boundaries, start, limit, overlap);
142
+ chunks.push(points.slice(start, end).join(""));
143
+ if (end === points.length)
144
+ break;
145
+ start = end - overlap;
173
146
  }
174
147
  return chunks;
175
148
  }
176
- function splitByCharacter(text, maxSize) {
149
+ function splitWords(text, maxSize, overlap) {
150
+ const words = [...text.matchAll(/\S+/gu)];
177
151
  const chunks = [];
178
- for (let i = 0; i < text.length; i += maxSize) {
179
- chunks.push(text.slice(i, i + maxSize));
152
+ for (let start = 0; start < words.length; ) {
153
+ const end = Math.min(start + maxSize, words.length);
154
+ const from = start === 0 ? 0 : words[start].index;
155
+ const to = end === words.length ? text.length : words[end].index;
156
+ chunks.push(text.slice(from, to));
157
+ if (end === words.length)
158
+ break;
159
+ start = end - overlap;
180
160
  }
181
161
  return chunks;
182
162
  }
183
- function splitByCustom(text, separator) {
184
- const regex = new RegExp(separator);
185
- const segments = text.split(regex);
186
- return segments.filter((s) => s.trim().length > 0);
163
+ function markdownSections(text, minLevel) {
164
+ const headings = [];
165
+ let fence;
166
+ for (const line of text.matchAll(/[^\n]*(?:\n|$)/g)) {
167
+ const content = line[0].replace(/\r?\n$/, "");
168
+ if (fence) {
169
+ if (new RegExp(`^ {0,3}${fence.character}{${fence.length},}[ \\t]*$`).test(content))
170
+ fence = void 0;
171
+ continue;
172
+ }
173
+ const opening = /^ {0,3}(`{3,}|~{3,})/.exec(content);
174
+ if (opening) {
175
+ fence = { character: opening[1][0], length: opening[1].length };
176
+ continue;
177
+ }
178
+ const heading = /^ {0,3}(#{1,6})[ \t]/.exec(content);
179
+ if (heading && heading[1].length <= minLevel) {
180
+ let title = content.slice(heading[0].length).trim();
181
+ let end = title.length;
182
+ while (title[end - 1] === "#")
183
+ end--;
184
+ if (end < title.length && (title[end - 1] === " " || title[end - 1] === " "))
185
+ title = title.slice(0, end).trimEnd();
186
+ headings.push({ start: line.index, bodyStart: line.index + line[0].length, title, level: heading[1].length });
187
+ }
188
+ }
189
+ if (headings.length === 0)
190
+ return [{ start: 0, bodyStart: 0, end: text.length, title: "", level: 0 }];
191
+ const sections = [];
192
+ if (headings[0].start > 0)
193
+ sections.push({ start: 0, bodyStart: 0, end: headings[0].start, title: "", level: 0 });
194
+ headings.forEach((heading, index) => sections.push({ ...heading, end: headings[index + 1]?.start ?? text.length }));
195
+ return sections;
187
196
  }
188
- function splitTextByMode(text, mode, maxSize, separator) {
197
+ function modeExpression(mode, separator) {
189
198
  switch (mode) {
190
199
  case "sentence":
191
- return splitBySentence(text);
200
+ return /[。!?.!?]+\s*/gu;
192
201
  case "paragraph":
193
- return splitByParagraph(text);
202
+ return /\r?\n(?:[ \t]*\r?\n)+/gu;
194
203
  case "page":
195
- return splitByPage(text);
196
- case "word":
197
- return splitByWord(text, maxSize);
198
- case "character":
199
- return splitByCharacter(text, maxSize);
204
+ return /\f/gu;
200
205
  case "custom":
201
- return splitByCustom(text, separator ?? "\n");
206
+ return new RegExp(separator, "gu");
202
207
  default:
203
- return [text];
208
+ return void 0;
204
209
  }
205
210
  }
206
- function buildChunks(segments, maxSize, overlap) {
207
- const chunks = [];
208
- let currentContent = "";
209
- let chunkIndex = 0;
210
- for (const segment of segments) {
211
- if (currentContent.length > 0 && currentContent.length + segment.length + 1 > maxSize) {
212
- chunks.push({
213
- index: chunkIndex++,
214
- content: currentContent.trim()
215
- });
216
- if (overlap > 0 && currentContent.length > overlap) {
217
- currentContent = currentContent.slice(-overlap);
218
- } else {
219
- currentContent = "";
220
- }
221
- }
222
- if (segment.length > maxSize) {
223
- if (currentContent.length > 0) {
224
- chunks.push({
225
- index: chunkIndex++,
226
- content: currentContent.trim()
227
- });
228
- currentContent = "";
229
- }
230
- for (let i = 0; i < segment.length; i += maxSize) {
231
- const part = segment.slice(i, i + maxSize);
232
- chunks.push({
233
- index: chunkIndex++,
234
- content: part.trim()
235
- });
236
- }
237
- continue;
238
- }
239
- currentContent += (currentContent.length > 0 ? "\n" : "") + segment;
240
- }
241
- if (currentContent.trim().length > 0) {
242
- chunks.push({
243
- index: chunkIndex,
244
- content: currentContent.trim()
245
- });
246
- }
247
- return chunks;
248
- }
249
- function buildMarkdownChunks(text, maxSize, overlap, markdownMinLevel, markdownKeepTitle) {
250
- const mdSegments = splitByMarkdown(text, markdownMinLevel, markdownKeepTitle);
251
- const chunks = [];
252
- for (const segment of mdSegments) {
253
- if (segment.content.length > maxSize) {
254
- const subChunks = buildChunks([segment.content], maxSize, overlap);
255
- for (const sub of subChunks) {
256
- chunks.push({
257
- index: chunks.length,
258
- content: sub.content,
259
- metadata: { title: segment.title, level: segment.level, subChunk: true }
260
- });
261
- }
262
- } else {
263
- chunks.push({
264
- index: chunks.length,
265
- content: segment.content,
266
- metadata: { title: segment.title, level: segment.level }
267
- });
268
- }
269
- }
270
- return chunks;
271
- }
272
211
  function chunkText(text, options) {
273
- const parseResult = ChunkOptionsSchema.safeParse(options);
274
- if (!parseResult.success) {
275
- return err(HaiDatapipeError.CONFIG_ERROR, datapipeM("datapipe_configError", { params: { error: parseResult.error.message } }), parseResult.error);
276
- }
277
- const opts = parseResult.data;
278
- if (opts.mode === "custom" && !opts.separator) {
212
+ const parsed = ChunkOptionsSchema.safeParse(options);
213
+ if (!parsed.success)
214
+ return err(HaiDatapipeError.CONFIG_ERROR, datapipeM("datapipe_configError", { params: { error: parsed.error.message } }), parsed.error);
215
+ const opts = parsed.data;
216
+ if (opts.mode === "custom" && !opts.separator)
279
217
  return err(HaiDatapipeError.MISSING_SEPARATOR, datapipeM("datapipe_missingSeparator"));
280
- }
281
- if (!text || text.trim().length === 0) {
218
+ if (!text || text.trim().length === 0)
282
219
  return ok([]);
283
- }
284
220
  try {
285
221
  if (opts.mode === "markdown") {
286
- const chunks2 = buildMarkdownChunks(text, opts.maxSize, opts.overlap, opts.markdownMinLevel, opts.markdownKeepTitle);
287
- return ok(chunks2);
222
+ const chunks = [];
223
+ for (const section of markdownSections(text, opts.markdownMinLevel)) {
224
+ const content = text.slice(opts.markdownKeepTitle ? section.start : section.bodyStart, section.end);
225
+ const parts2 = splitCharacters(content, opts.maxSize, opts.overlap, modeExpression("paragraph"));
226
+ for (const part of parts2)
227
+ chunks.push({ index: chunks.length, content: part, metadata: { title: section.title, level: section.level, ...parts2.length > 1 ? { subChunk: true } : {} } });
228
+ }
229
+ return ok(chunks);
288
230
  }
289
- const segments = splitTextByMode(text, opts.mode, opts.maxSize, opts.separator);
290
- const chunks = buildChunks(segments, opts.maxSize, opts.overlap);
291
- return ok(chunks);
292
- } catch (error) {
293
- return err(HaiDatapipeError.CHUNK_FAILED, datapipeM("datapipe_chunkFailed", { params: { error: String(error) } }), error);
231
+ const parts = opts.mode === "word" ? splitWords(text, opts.maxSize, opts.overlap) : splitCharacters(text, opts.maxSize, opts.overlap, modeExpression(opts.mode, opts.separator));
232
+ return ok(parts.map((content, index) => ({ index, content })));
233
+ } catch (cause) {
234
+ return err(HaiDatapipeError.CHUNK_FAILED, datapipeM("datapipe_chunkFailed", { params: { error: String(cause) } }), cause);
294
235
  }
295
236
  }
296
237
  var HTML_TAG_REGEX = /<[^>]*>/g;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/datapipe-config.ts","../messages/en-US.json","../messages/zh-CN.json","../src/datapipe-i18n.ts","../src/datapipe-types.ts","../src/datapipe-chunk.ts","../src/datapipe-clean.ts","../src/datapipe-pipeline.ts","../src/datapipe-main.ts"],"names":["core","chunks","err","ok"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @h-ai/datapipe — 数据处理管线配置 Schema\n *\n * 本文件定义数据处理模块的错误码常量、Zod Schema 和配置类型。\n * 支持清洗(clean)、分块(chunk)、转换(transform)和管线(pipeline)。\n * @module datapipe-config\n */\n\nimport { z } from 'zod'\n\n// ─── 清洗配置 ───\n\n/**\n * 清洗选项 Schema\n *\n * 数据清洗规则配置,支持移除 HTML、URL、Email、标准化空白等。\n *\n * @example\n * ```ts\n * const cleanOptions = {\n * removeHtml: true,\n * removeUrls: true,\n * normalizeWhitespace: true,\n * }\n * ```\n */\nexport const CleanOptionsSchema = z.object({\n /** 移除 HTML 标签(默认 true) */\n removeHtml: z.boolean().default(true),\n /** 移除 URL(默认 false) */\n removeUrls: z.boolean().default(false),\n /** 移除 Email 地址(默认 false) */\n removeEmails: z.boolean().default(false),\n /** 标准化空白字符(多空格→单空格、去掉多余空行,默认 true) */\n normalizeWhitespace: z.boolean().default(true),\n /** 去除首尾空白(默认 true) */\n trim: z.boolean().default(true),\n /**\n * 自定义替换规则(正则→替换字符串)\n *\n * ❗ 安全提示:pattern 会通过 `new RegExp()` 构造,如果其值来源于用户输入,请在调用方先行校验以防止 ReDoS 攻击。\n */\n customReplacements: z.array(z.object({\n /** 正则表达式字符串 */\n pattern: z.string(),\n /** 替换文本 */\n replacement: z.string(),\n })).optional(),\n})\n\n/** 清洗选项类型 */\nexport type CleanOptions = z.infer<typeof CleanOptionsSchema>\n\n/** 清洗选项输入类型 */\nexport type CleanOptionsInput = z.input<typeof CleanOptionsSchema>\n\n// ─── 分块配置 ───\n\n/**\n * 分块模式枚举\n *\n * - `sentence` — 按句子分块\n * - `paragraph` — 按段落分块\n * - `markdown` — 按 Markdown 标题分块(## / ### 等)\n * - `page` — 按分页符分块\n * - `word` — 按字数分块\n * - `character` — 按字符数分块\n * - `custom` — 自定义分隔符分块\n */\nexport const ChunkModeSchema = z.enum([\n 'sentence',\n 'paragraph',\n 'markdown',\n 'page',\n 'word',\n 'character',\n 'custom',\n])\n\n/** 分块模式类型 */\nexport type ChunkMode = z.infer<typeof ChunkModeSchema>\n\n/**\n * 分块选项 Schema\n *\n * @example\n * ```ts\n * // 按 Markdown 标题分块\n * const chunkOptions = {\n * mode: 'markdown',\n * maxSize: 2000,\n * overlap: 200,\n * }\n *\n * // 按字符分块\n * const chunkOptions = {\n * mode: 'character',\n * maxSize: 1000,\n * overlap: 100,\n * }\n * ```\n */\nexport const ChunkOptionsSchema = z.object({\n /** 分块模式 */\n mode: ChunkModeSchema,\n /** 每个分块的最大大小(字符数 / 字数,取决于 mode,默认 1000) */\n maxSize: z.number().int().positive().default(1500),\n /** 重叠大小(字符数 / 字数,用于上下文衔接,默认 200) */\n overlap: z.number().int().min(0).default(200),\n /**\n * 自定义分隔符正则(仅 mode='custom' 时使用)\n *\n * ❗ 安全提示:separator 会通过 `new RegExp()` 构造,如果其值来源于用户输入,请在调用方先行校验以防止 ReDoS 攻击。\n */\n separator: z.string().optional(),\n /** Markdown 标题分割深度(1-6,默认 2,即 # 和 ## 作为分块边界;设为 3 则 ### 也作为分块边界) */\n markdownMinLevel: z.number().int().min(1).max(6).default(2),\n /** 是否保留 Markdown 标题在分块内容中(默认 true) */\n markdownKeepTitle: z.boolean().default(true),\n})\n\n/** 分块选项类型 */\nexport type ChunkOptions = z.infer<typeof ChunkOptionsSchema>\n\n/** 分块选项输入类型 */\nexport type ChunkOptionsInput = z.input<typeof ChunkOptionsSchema>\n","{\n \"$schema\": \"https://inlang.com/schema/inlang-message-format\",\n \"datapipe_cleanFailed\": \"Failed to clean text: {error}\",\n \"datapipe_chunkFailed\": \"Failed to chunk text: {error}\",\n \"datapipe_transformFailed\": \"Failed to transform text: {error}\",\n \"datapipe_pipelineFailed\": \"Pipeline execution failed: {error}\",\n \"datapipe_configError\": \"Datapipe config validation failed: {error}\",\n \"datapipe_missingSeparator\": \"Custom chunk mode requires a separator parameter\"\n}\n","{\n \"$schema\": \"https://inlang.com/schema/inlang-message-format\",\n \"datapipe_cleanFailed\": \"清洗文本失败: {error}\",\n \"datapipe_chunkFailed\": \"分块文本失败: {error}\",\n \"datapipe_transformFailed\": \"转换文本失败: {error}\",\n \"datapipe_pipelineFailed\": \"管线执行失败: {error}\",\n \"datapipe_configError\": \"数据处理配置校验失败:{error}\",\n \"datapipe_missingSeparator\": \"自定义分块模式需要提供 separator 参数\"\n}\n","/**\n * @h-ai/datapipe — i18n\n *\n * 本文件提供数据处理模块的 i18n 文案访问入口。\n * @module datapipe-i18n\n */\n\nimport { core } from '@h-ai/core'\nimport messagesEnUS from '../messages/en-US.json'\nimport messagesZhCN from '../messages/zh-CN.json'\n\n// ─── i18n ───\n\n/**\n * 数据处理模块文案 Key 类型\n */\ntype DatapipeMessageKey = keyof typeof messagesZhCN\n\n/**\n * 获取数据处理模块文案。\n * @param key - 文案 Key\n * @param options - 插值参数与格式化选项\n * @returns 本地化后的文案字符串\n */\nexport const datapipeM = core.i18n.createMessageGetter<DatapipeMessageKey>({\n 'zh-CN': messagesZhCN,\n 'en-US': messagesEnUS,\n})\n","/**\n * @h-ai/datapipe — 类型定义\n *\n * 本文件定义数据处理模块的核心接口和类型。\n * @module datapipe-types\n */\n\nimport type { ErrorInfo, HaiResult } from '@h-ai/core'\nimport type { ChunkOptionsInput, CleanOptionsInput } from './datapipe-config.js'\nimport { core } from '@h-ai/core'\n\n// ─── 错误定义(照 @h-ai/core 范式) ───\n\n/**\n * 数据处理错误信息映射(错误码:HTTP状态码)。\n *\n * 完整错误码将自动生成为:`hai:datapipe:NNN`\n */\nconst DatapipeErrorInfo = {\n CLEAN_FAILED: '001:500',\n CHUNK_FAILED: '002:500',\n TRANSFORM_FAILED: '003:500',\n PIPELINE_FAILED: '004:500',\n CONFIG_ERROR: '005:400',\n MISSING_SEPARATOR: '006:400',\n} as const satisfies ErrorInfo\n\n/**\n * Datapipe 模块标准错误定义对象。\n */\nexport const HaiDatapipeError = core.error.buildHaiErrorsDef('datapipe', DatapipeErrorInfo)\n\n// ─── 数据块 ───\n\n/**\n * 数据块接口\n *\n * 表示经过分块处理后的单个文本片段。\n *\n * @example\n * ```ts\n * const chunk: DataChunk = {\n * index: 0,\n * content: '## 简介\\n这是文档的第一部分。',\n * metadata: { title: '简介', level: 2 },\n * }\n * ```\n */\nexport interface DataChunk {\n /** 分块索引(从 0 开始) */\n index: number\n /** 分块内容 */\n content: string\n /** 分块元数据(可选) */\n metadata?: Record<string, unknown>\n}\n\n// ─── 转换函数 ───\n\n/**\n * 转换函数类型\n *\n * 自定义文本后处理函数,可以是同步或异步。\n */\nexport type TransformFn = (text: string) => string | Promise<string>\n\n/**\n * 分块转换函数类型\n *\n * 对分块列表进行后处理(如过滤、合并等)。\n */\nexport type ChunkTransformFn = (chunks: DataChunk[]) => DataChunk[] | Promise<DataChunk[]>\n\n// ─── 管线步骤 ───\n\n/**\n * 管线步骤类型\n *\n * 管线由一系列步骤组成,支持清洗、分块和自定义转换。\n */\nexport type PipelineStep\n = | { type: 'clean', options?: CleanOptionsInput }\n | { type: 'chunk', options: ChunkOptionsInput }\n | { type: 'transform', fn: TransformFn }\n | { type: 'chunkTransform', fn: ChunkTransformFn }\n\n// ─── 管线结果 ───\n\n/**\n * 管线执行结果\n */\nexport interface PipelineResult {\n /** 处理后的文本(分块前的最终文本) */\n text: string\n /** 分块列表(如果管线包含 chunk 步骤) */\n chunks: DataChunk[]\n}\n\n// ─── 操作接口 ───\n\n/**\n * 清洗操作接口\n *\n * 通过 `datapipe.clean()` 调用。\n */\nexport interface CleanOperations {\n /**\n * 清洗文本\n *\n * @param text - 原始文本\n * @param options - 清洗选项(可选,使用默认值)\n * @returns 清洗后的文本\n */\n (text: string, options?: CleanOptionsInput): HaiResult<string>\n}\n\n/**\n * 分块操作接口\n *\n * 通过 `datapipe.chunk()` 调用。\n */\nexport interface ChunkOperations {\n /**\n * 对文本进行分块\n *\n * @param text - 输入文本\n * @param options - 分块选项\n * @returns 分块列表\n */\n (text: string, options: ChunkOptionsInput): HaiResult<DataChunk[]>\n}\n\n/**\n * 管线构建器接口\n *\n * 通过 `datapipe.pipeline()` 创建,链式添加步骤后执行。\n *\n * @example\n * ```ts\n * const result = await datapipe.pipeline()\n * .clean({ removeHtml: true })\n * .transform(text => text.toLowerCase())\n * .chunk({ mode: 'markdown', maxSize: 2000 })\n * .run('原始文本')\n * ```\n */\nexport interface PipelineBuilder {\n /** 添加清洗步骤 */\n clean: (options?: CleanOptionsInput) => PipelineBuilder\n /** 添加分块步骤 */\n chunk: (options: ChunkOptionsInput) => PipelineBuilder\n /** 添加文本转换步骤 */\n transform: (fn: TransformFn) => PipelineBuilder\n /** 添加分块后处理步骤 */\n chunkTransform: (fn: ChunkTransformFn) => PipelineBuilder\n /** 执行管线 */\n run: (text: string) => Promise<HaiResult<PipelineResult>>\n}\n\n/**\n * 数据处理管线服务接口\n *\n * @example\n * ```ts\n * import { datapipe } from '@h-ai/datapipe'\n *\n * // 直接清洗\n * const cleaned = datapipe.clean('<p>Hello</p>', { removeHtml: true })\n *\n * // 直接分块\n * const chunks = datapipe.chunk(text, { mode: 'markdown', maxSize: 2000 })\n *\n * // 管线模式\n * const result = await datapipe.pipeline()\n * .clean()\n * .chunk({ mode: 'paragraph' })\n * .run(text)\n * ```\n */\nexport interface DatapipeFunctions {\n /** 清洗文本 */\n clean: CleanOperations\n /** 分块文本 */\n chunk: ChunkOperations\n /** 创建管线构建器 */\n pipeline: () => PipelineBuilder\n}\n","/**\n * @h-ai/datapipe — 文本分块功能\n *\n * 提供多种分块模式:句子、段落、Markdown 标题、分页、字数、字符、自定义分隔符。\n * 支持重叠分块以保持上下文衔接。\n * @module datapipe-chunk\n */\n\nimport type { HaiResult } from '@h-ai/core'\nimport type { ChunkMode, ChunkOptionsInput } from './datapipe-config.js'\nimport type { DataChunk } from './datapipe-types.js'\n\nimport { err, ok } from '@h-ai/core'\n\nimport { ChunkOptionsSchema } from './datapipe-config.js'\nimport { datapipeM } from './datapipe-i18n.js'\nimport { HaiDatapipeError } from './datapipe-types.js'\n\nconst SENTENCE_SPLIT_REGEX = /(?<=[。!?.!?])\\s*/\nconst PARAGRAPH_SPLIT_REGEX = /\\n\\s*\\n/\nconst PAGE_SPLIT_REGEX = /\\f/\nconst WORD_SPLIT_REGEX = /\\s+/\n\n// ─── 分割函数 ───\n\n/**\n * 按句子分割文本\n *\n * 支持中英文句号、问号、感叹号作为分隔符。\n */\nfunction splitBySentence(text: string): string[] {\n // 匹配中文和英文的句末标点\n const segments = text.split(SENTENCE_SPLIT_REGEX)\n return segments.filter(s => s.trim().length > 0)\n}\n\n/**\n * 按段落分割文本\n *\n * 以双换行符作为段落分隔符。\n */\nfunction splitByParagraph(text: string): string[] {\n const segments = text.split(PARAGRAPH_SPLIT_REGEX)\n return segments.filter(s => s.trim().length > 0)\n}\n\n/**\n * 按 Markdown 标题分割文本\n *\n * 根据最低标题级别(默认 ##),按标题分割为独立的内容块。\n * 每个分块包含标题和其下属内容。\n *\n * @param text - Markdown 文本\n * @param minLevel - 最低标题级别(1-6)\n * @param keepTitle - 是否在分块中保留标题\n * @returns 分块列表(包含元数据:title, level)\n */\nfunction splitByMarkdown(text: string, minLevel: number, keepTitle: boolean): { content: string, title: string, level: number }[] {\n // 构造匹配 minLevel 及以上级别标题的正则\n const headerPattern = new RegExp(`^(#{1,${minLevel}})\\\\s+(.+)$`, 'gm')\n\n const sections: { content: string, title: string, level: number }[] = []\n let lastIndex = 0\n let lastTitle = ''\n let lastLevel = 0\n let match: RegExpExecArray | null = null\n\n // 处理标题前的前置内容\n match = headerPattern.exec(text)\n if (match && match.index > 0) {\n const preContent = text.slice(0, match.index).trim()\n if (preContent.length > 0) {\n sections.push({ content: preContent, title: '', level: 0 })\n }\n }\n\n // 重置正则\n headerPattern.lastIndex = 0\n\n // eslint-disable-next-line no-cond-assign\n while ((match = headerPattern.exec(text)) !== null) {\n // 保存上一个分块\n if (lastIndex > 0 || lastTitle) {\n const content = text.slice(lastIndex, match.index).trim()\n if (content.length > 0 || lastTitle) {\n const finalContent = keepTitle && lastTitle\n ? `${'#'.repeat(lastLevel)} ${lastTitle}\\n${content}`\n : content\n sections.push({\n content: finalContent.trim(),\n title: lastTitle,\n level: lastLevel,\n })\n }\n }\n\n lastTitle = match[2].trim()\n lastLevel = match[1].length\n lastIndex = match.index + match[0].length\n }\n\n // 处理最后一个分块\n if (lastTitle || lastIndex > 0) {\n const content = text.slice(lastIndex).trim()\n const finalContent = keepTitle && lastTitle\n ? `${'#'.repeat(lastLevel)} ${lastTitle}\\n${content}`\n : content\n sections.push({\n content: finalContent.trim(),\n title: lastTitle,\n level: lastLevel,\n })\n }\n\n // 如果没有匹配到任何标题,返回整个文本\n if (sections.length === 0) {\n sections.push({ content: text.trim(), title: '', level: 0 })\n }\n\n return sections.filter(s => s.content.length > 0)\n}\n\n/**\n * 按分页符分割文本\n */\nfunction splitByPage(text: string): string[] {\n const segments = text.split(PAGE_SPLIT_REGEX)\n return segments.filter(s => s.trim().length > 0)\n}\n\n/**\n * 按空格分词分割文本\n *\n * 以空白字符为分隔符进行分词,每个分块包含不超过 maxSize 个词。\n * 注意:中文等无空格语言的连续文本会被视为单个词。\n */\nfunction splitByWord(text: string, maxSize: number): string[] {\n const words = text.split(WORD_SPLIT_REGEX)\n const chunks: string[] = []\n let current: string[] = []\n let currentSize = 0\n\n for (const word of words) {\n if (currentSize + 1 > maxSize && current.length > 0) {\n chunks.push(current.join(' '))\n current = []\n currentSize = 0\n }\n current.push(word)\n currentSize++\n }\n\n if (current.length > 0) {\n chunks.push(current.join(' '))\n }\n\n return chunks\n}\n\n/**\n * 按字符数分割文本\n */\nfunction splitByCharacter(text: string, maxSize: number): string[] {\n const chunks: string[] = []\n for (let i = 0; i < text.length; i += maxSize) {\n chunks.push(text.slice(i, i + maxSize))\n }\n return chunks\n}\n\n/**\n * 按自定义分隔符分割文本\n *\n * @throws 当 separator 不是合法正则表达式时抛出异常,由外层 catch 转为 HaiResult\n */\nfunction splitByCustom(text: string, separator: string): string[] {\n const regex = new RegExp(separator)\n const segments = text.split(regex)\n return segments.filter(s => s.trim().length > 0)\n}\n\n// ─── 分割模式分发 ───\n\n/**\n * 按指定模式分割文本为字符串片段(非 Markdown 模式)\n */\nfunction splitTextByMode(\n text: string,\n mode: ChunkMode,\n maxSize: number,\n separator?: string,\n): string[] {\n switch (mode) {\n case 'sentence':\n return splitBySentence(text)\n case 'paragraph':\n return splitByParagraph(text)\n case 'page':\n return splitByPage(text)\n case 'word':\n return splitByWord(text, maxSize)\n case 'character':\n return splitByCharacter(text, maxSize)\n case 'custom':\n return splitByCustom(text, separator ?? '\\n')\n default:\n return [text]\n }\n}\n\n// ─── 合并分段至分块 ───\n\n/**\n * 将分段合并为不超过 maxSize 的分块,支持重叠\n */\nfunction buildChunks(\n segments: string[],\n maxSize: number,\n overlap: number,\n): DataChunk[] {\n const chunks: DataChunk[] = []\n let currentContent = ''\n let chunkIndex = 0\n\n for (const segment of segments) {\n // 如果当前内容加上新分段超过限制,先保存当前分块\n if (currentContent.length > 0 && currentContent.length + segment.length + 1 > maxSize) {\n chunks.push({\n index: chunkIndex++,\n content: currentContent.trim(),\n })\n\n // 处理重叠:保留尾部内容\n if (overlap > 0 && currentContent.length > overlap) {\n currentContent = currentContent.slice(-overlap)\n }\n else {\n currentContent = ''\n }\n }\n\n // 如果单个分段已超过 maxSize,按字符截断\n if (segment.length > maxSize) {\n if (currentContent.length > 0) {\n chunks.push({\n index: chunkIndex++,\n content: currentContent.trim(),\n })\n currentContent = ''\n }\n\n for (let i = 0; i < segment.length; i += maxSize) {\n const part = segment.slice(i, i + maxSize)\n chunks.push({\n index: chunkIndex++,\n content: part.trim(),\n })\n }\n continue\n }\n\n currentContent += (currentContent.length > 0 ? '\\n' : '') + segment\n }\n\n // 保存最后一个分块\n if (currentContent.trim().length > 0) {\n chunks.push({\n index: chunkIndex,\n content: currentContent.trim(),\n })\n }\n\n return chunks\n}\n\n// ─── Markdown 分块构建 ───\n\n/**\n * 将 Markdown 分割结果构建为带元数据的分块列表\n *\n * Markdown 模式下 overlap 仅用于单 section 超限时的子分块,section 间不做 overlap。\n */\nfunction buildMarkdownChunks(\n text: string,\n maxSize: number,\n overlap: number,\n markdownMinLevel: number,\n markdownKeepTitle: boolean,\n): DataChunk[] {\n const mdSegments = splitByMarkdown(text, markdownMinLevel, markdownKeepTitle)\n const chunks: DataChunk[] = []\n\n for (const segment of mdSegments) {\n if (segment.content.length > maxSize) {\n const subChunks = buildChunks([segment.content], maxSize, overlap)\n for (const sub of subChunks) {\n chunks.push({\n index: chunks.length,\n content: sub.content,\n metadata: { title: segment.title, level: segment.level, subChunk: true },\n })\n }\n }\n else {\n chunks.push({\n index: chunks.length,\n content: segment.content,\n metadata: { title: segment.title, level: segment.level },\n })\n }\n }\n\n return chunks\n}\n\n// ─── 公共 API ───\n\n/**\n * 对文本进行分块\n *\n * @param text - 输入文本\n * @param options - 分块选项\n * @returns 分块列表\n *\n * @example\n * ```ts\n * // 按 Markdown 标题分块\n * const result = chunkText(markdownText, { mode: 'markdown', maxSize: 2000 })\n *\n * // 按句子分块\n * const result = chunkText(text, { mode: 'sentence', maxSize: 500, overlap: 50 })\n * ```\n */\nexport function chunkText(text: string, options: ChunkOptionsInput): HaiResult<DataChunk[]> {\n const parseResult = ChunkOptionsSchema.safeParse(options)\n if (!parseResult.success) {\n return err(HaiDatapipeError.CONFIG_ERROR, datapipeM('datapipe_configError', { params: { error: parseResult.error.message } }), parseResult.error)\n }\n\n const opts = parseResult.data\n\n // 校验 custom 模式需要 separator\n if (opts.mode === 'custom' && !opts.separator) {\n return err(HaiDatapipeError.MISSING_SEPARATOR, datapipeM('datapipe_missingSeparator'))\n }\n\n if (!text || text.trim().length === 0) {\n return ok([])\n }\n\n try {\n // Markdown 模式:独立处理,带有 title 和 level 元数据\n if (opts.mode === 'markdown') {\n const chunks = buildMarkdownChunks(text, opts.maxSize, opts.overlap, opts.markdownMinLevel, opts.markdownKeepTitle)\n return ok(chunks)\n }\n\n // 其他模式:字符串形式的分段\n const segments = splitTextByMode(text, opts.mode, opts.maxSize, opts.separator)\n const chunks = buildChunks(segments, opts.maxSize, opts.overlap)\n\n return ok(chunks)\n }\n catch (error) {\n return err(HaiDatapipeError.CHUNK_FAILED, datapipeM('datapipe_chunkFailed', { params: { error: String(error) } }), error)\n }\n}\n","/**\n * @h-ai/datapipe — 文本清洗功能\n *\n * 提供文本清洗工具,支持移除 HTML、URL、Email、标准化空白等。\n * @module datapipe-clean\n */\n\nimport type { HaiResult } from '@h-ai/core'\nimport type { CleanOptionsInput } from './datapipe-config.js'\n\nimport { err, ok } from '@h-ai/core'\n\nimport { CleanOptionsSchema } from './datapipe-config.js'\nimport { datapipeM } from './datapipe-i18n.js'\nimport { HaiDatapipeError } from './datapipe-types.js'\n\n/**\n * HTML 标签正则\n */\nconst HTML_TAG_REGEX = /<[^>]*>/g\n\n/**\n * URL 正则(HTTP/HTTPS)\n */\nconst URL_REGEX = /https?:\\/\\/[^\\s<>\"{}|\\\\^`[\\]]+/g\n\n/**\n * Email 正则\n */\nconst EMAIL_REGEX = /[\\w.%+-]+@[a-z0-9.-]+\\.[a-z]{2,}/gi\n\n/**\n * 多空行正则(3行及以上空行 → 2行)\n */\nconst MULTI_NEWLINE_REGEX = /\\n{3,}/g\n\n/**\n * 多空格正则\n */\nconst MULTI_SPACE_REGEX = /[ \\t]{2,}/g\n\n/**\n * 清洗文本\n *\n * 根据配置选项对文本进行清洗处理。\n *\n * @param text - 原始文本\n * @param options - 清洗选项(可选)\n * @returns 清洗后的文本\n *\n * @example\n * ```ts\n * const result = cleanText('<p>Hello World</p>', { removeHtml: true })\n * // result.data === 'Hello World'\n * ```\n */\nexport function cleanText(text: string, options?: CleanOptionsInput): HaiResult<string> {\n const parseResult = CleanOptionsSchema.safeParse(options ?? {})\n if (!parseResult.success) {\n return err(HaiDatapipeError.CONFIG_ERROR, datapipeM('datapipe_configError', { params: { error: parseResult.error.message } }), parseResult.error)\n }\n\n const opts = parseResult.data\n let result = text\n\n try {\n // 移除 HTML 标签\n if (opts.removeHtml) {\n result = result.replace(HTML_TAG_REGEX, '')\n }\n\n // 移除 URL\n if (opts.removeUrls) {\n result = result.replace(URL_REGEX, '')\n }\n\n // 移除 Email\n if (opts.removeEmails) {\n result = result.replace(EMAIL_REGEX, '')\n }\n\n // 自定义替换\n if (opts.customReplacements) {\n for (const { pattern, replacement } of opts.customReplacements) {\n let regex: RegExp\n try {\n regex = new RegExp(pattern, 'g')\n }\n catch (regexError) {\n return err(HaiDatapipeError.CONFIG_ERROR, datapipeM('datapipe_configError', { params: { error: String(regexError) } }), regexError)\n }\n result = result.replace(regex, replacement)\n }\n }\n\n // 标准化空白\n if (opts.normalizeWhitespace) {\n result = result.replace(MULTI_SPACE_REGEX, ' ')\n result = result.replace(MULTI_NEWLINE_REGEX, '\\n\\n')\n }\n\n // 去除首尾空白\n if (opts.trim) {\n result = result.trim()\n }\n\n return ok(result)\n }\n catch (error) {\n return err(HaiDatapipeError.CLEAN_FAILED, datapipeM('datapipe_cleanFailed', { params: { error: String(error) } }), error)\n }\n}\n","/**\n * @h-ai/datapipe — 管线构建器\n *\n * 提供可组合的管线构建器,支持链式添加清洗、分块、转换步骤后执行。\n * @module datapipe-pipeline\n */\n\nimport type { HaiResult } from '@h-ai/core'\nimport type { ChunkOptionsInput, CleanOptionsInput } from './datapipe-config.js'\nimport type {\n ChunkTransformFn,\n DataChunk,\n PipelineBuilder,\n PipelineResult,\n PipelineStep,\n TransformFn,\n} from './datapipe-types.js'\n\nimport { err, ok } from '@h-ai/core'\n\nimport { chunkText } from './datapipe-chunk.js'\nimport { cleanText } from './datapipe-clean.js'\nimport { datapipeM } from './datapipe-i18n.js'\nimport { HaiDatapipeError } from './datapipe-types.js'\n\n/**\n * 创建管线构建器\n *\n * @returns PipelineBuilder 实例,支持链式添加步骤后执行\n *\n * @example\n * ```ts\n * const result = await datapipe.pipeline()\n * .clean({ removeHtml: true })\n * .transform(text => text.toLowerCase())\n * .chunk({ mode: 'markdown', maxSize: 2000 })\n * .run('原始文本')\n *\n * if (result.success) {\n * // result.data => { text, chunks }\n * }\n * ```\n */\nexport function createPipelineBuilder(): PipelineBuilder {\n const steps: PipelineStep[] = []\n\n const builder: PipelineBuilder = {\n clean(options?: CleanOptionsInput): PipelineBuilder {\n steps.push({ type: 'clean', options })\n return builder\n },\n\n chunk(options: ChunkOptionsInput): PipelineBuilder {\n steps.push({ type: 'chunk', options })\n return builder\n },\n\n transform(fn: TransformFn): PipelineBuilder {\n steps.push({ type: 'transform', fn })\n return builder\n },\n\n chunkTransform(fn: ChunkTransformFn): PipelineBuilder {\n steps.push({ type: 'chunkTransform', fn })\n return builder\n },\n\n run: (text: string) => runPipeline(steps, text),\n }\n\n return builder\n}\n\n// ─── 管线执行 ───\n\n/**\n * 执行管线步骤\n */\nasync function runPipeline(\n steps: PipelineStep[],\n text: string,\n): Promise<HaiResult<PipelineResult>> {\n let currentText = text\n let chunks: DataChunk[] = []\n\n for (const step of steps) {\n switch (step.type) {\n case 'clean': {\n const result = cleanText(currentText, step.options)\n if (!result.success)\n return result\n currentText = result.data\n break\n }\n\n case 'chunk': {\n const result = chunkText(currentText, step.options)\n if (!result.success)\n return result\n chunks = result.data\n break\n }\n\n case 'transform': {\n try {\n currentText = await step.fn(currentText)\n }\n catch (error) {\n return err(HaiDatapipeError.TRANSFORM_FAILED, datapipeM('datapipe_transformFailed', { params: { error: String(error) } }), error)\n }\n break\n }\n\n case 'chunkTransform': {\n if (chunks.length > 0) {\n try {\n chunks = await step.fn(chunks)\n }\n catch (error) {\n return err(HaiDatapipeError.PIPELINE_FAILED, datapipeM('datapipe_pipelineFailed', { params: { error: String(error) } }), error)\n }\n }\n break\n }\n }\n }\n\n return ok({ text: currentText, chunks })\n}\n","/**\n * @h-ai/datapipe — 数据处理管线主入口\n *\n * 提供统一的 `datapipe` 对象,聚合清洗、分块和管线功能。\n * datapipe 为纯函数模块,无需初始化即可使用。\n * @module datapipe-main\n */\n\nimport type { DatapipeFunctions } from './datapipe-types.js'\n\nimport { chunkText } from './datapipe-chunk.js'\nimport { cleanText } from './datapipe-clean.js'\nimport { createPipelineBuilder } from './datapipe-pipeline.js'\n\n// ─── 统一服务对象 ───\n\n/**\n * 数据处理管线服务对象\n *\n * 纯函数模块,无需初始化即可使用。\n *\n * @example\n * ```ts\n * import { datapipe } from '@h-ai/datapipe'\n *\n * // 直接清洗\n * const cleaned = datapipe.clean('<p>Hello</p>')\n *\n * // 按 Markdown 标题分块\n * const chunks = datapipe.chunk(text, { mode: 'markdown', maxSize: 2000 })\n *\n * // 管线模式\n * const result = await datapipe.pipeline()\n * .clean({ removeHtml: true, removeUrls: true })\n * .transform(text => text.toLowerCase())\n * .chunk({ mode: 'paragraph', maxSize: 1000, overlap: 100 })\n * .chunkTransform(chunks => chunks.filter(c => c.content.length > 50))\n * .run(rawHtml)\n * ```\n */\nexport const datapipe: DatapipeFunctions = {\n clean: cleanText,\n chunk: chunkText,\n pipeline: createPipelineBuilder,\n}\n"]}
1
+ {"version":3,"sources":["../src/datapipe-config.ts","../messages/en-US.json","../messages/ja-JP.json","../messages/zh-CN.json","../src/datapipe-i18n.ts","../src/datapipe-types.ts","../src/datapipe-chunk.ts","../src/datapipe-clean.ts","../src/datapipe-pipeline.ts","../src/datapipe-main.ts"],"names":["core","parts","err","ok"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @h-ai/datapipe — 数据处理管线配置 Schema\n *\n * 本文件定义数据处理模块的错误码常量、Zod Schema 和配置类型。\n * 支持清洗(clean)、分块(chunk)、转换(transform)和管线(pipeline)。\n * @module datapipe-config\n */\n\nimport { z } from 'zod'\n\n// ─── 清洗配置 ───\n\n/**\n * 清洗选项 Schema\n *\n * 数据清洗规则配置,支持移除 HTML、URL、Email、标准化空白等。\n * @example\n * ```ts\n * import { CleanOptionsSchema } from '@h-ai/datapipe'\n * const options = CleanOptionsSchema.parse({ removeHtml: true, removeEmails: true })\n * ```\n */\nexport const CleanOptionsSchema = z.object({\n /** 移除 HTML 标签(默认 true) */\n removeHtml: z.boolean().default(true),\n /** 移除 URL(默认 false) */\n removeUrls: z.boolean().default(false),\n /** 移除 Email 地址(默认 false) */\n removeEmails: z.boolean().default(false),\n /** 标准化空白字符(多空格→单空格、去掉多余空行,默认 true) */\n normalizeWhitespace: z.boolean().default(true),\n /** 去除首尾空白(默认 true) */\n trim: z.boolean().default(true),\n /**\n * 自定义替换规则(正则→替换字符串)\n *\n * ❗ 安全提示:pattern 会通过 `new RegExp()` 构造,如果其值来源于用户输入,请在调用方先行校验以防止 ReDoS 攻击。\n */\n customReplacements: z.array(z.object({\n /** 正则表达式字符串 */\n pattern: z.string(),\n /** 替换文本 */\n replacement: z.string(),\n })).optional(),\n})\n\n/**\n * 清洗选项类型\n * @example\n * ```ts\n * import { CleanOptionsSchema, type CleanOptions } from '@h-ai/datapipe'\n * const options: CleanOptions = CleanOptionsSchema.parse({ removeHtml: true })\n * ```\n */\nexport type CleanOptions = z.infer<typeof CleanOptionsSchema>\n\n/**\n * 清洗选项输入类型\n * @example\n * ```ts\n * import type { CleanOptionsInput } from '@h-ai/datapipe'\n * const options: CleanOptionsInput = { removeHtml: true, removeUrls: true }\n * ```\n */\nexport type CleanOptionsInput = z.input<typeof CleanOptionsSchema>\n\n// ─── 分块配置 ───\n\n/**\n * 分块模式枚举\n *\n * - `sentence` — 按句子分块\n * - `paragraph` — 按段落分块\n * - `markdown` — 按 Markdown 标题分块(## / ### 等)\n * - `page` — 按分页符分块\n * - `word` — 按非空白词数分块,不截断单个词\n * - `character` — 按 Unicode code point 分块,不拆代理对\n * - `custom` — 自定义分隔符分块\n * @example\n * ```ts\n * import { ChunkModeSchema } from '@h-ai/datapipe'\n * const mode = ChunkModeSchema.parse('character')\n * ```\n */\nexport const ChunkModeSchema = z.enum([\n 'sentence',\n 'paragraph',\n 'markdown',\n 'page',\n 'word',\n 'character',\n 'custom',\n])\n\n/**\n * 分块模式类型\n * @example\n * ```ts\n * import type { ChunkMode } from '@h-ai/datapipe'\n * const options: ChunkMode = 'character'\n * ```\n */\nexport type ChunkMode = z.infer<typeof ChunkModeSchema>\n\n/**\n * 分块选项 Schema\n * datapipe 的默认 overlap 为 0;消费者可显式提供自己的默认值,例如 AI Knowledge 为 200。\n * @example\n * ```ts\n * import { ChunkOptionsSchema } from '@h-ai/datapipe'\n * const options = ChunkOptionsSchema.parse({ mode: 'markdown', maxSize: 500, overlap: 50 })\n * // maxSize 已包含重叠,overlap 必须小于 maxSize;自定义正则仅来自可信配置。\n * ```\n */\nexport const ChunkOptionsSchema = z.object({\n /** 分块模式 */\n mode: ChunkModeSchema,\n /** 每块上限包含 overlap/分隔符;word 按非空白词,其他按 Unicode code point,默认 1500。 */\n maxSize: z.number().int().positive().default(1500),\n /** 重叠使用与 maxSize 相同的单位,必须严格小于 maxSize。 */\n overlap: z.number().int().min(0).default(0),\n /**\n * 自定义分隔符正则(仅 mode='custom' 时使用)\n *\n * ❗ 安全提示:separator 会通过 `new RegExp()` 构造,如果其值来源于用户输入,请在调用方先行校验以防止 ReDoS 攻击。\n */\n separator: z.string().optional(),\n /** Markdown 标题分割深度(1-6,默认 2,即 # 和 ## 作为分块边界;设为 3 则 ### 也作为分块边界) */\n markdownMinLevel: z.number().int().min(1).max(6).default(2),\n /** 是否保留 Markdown 标题在分块内容中(默认 true) */\n markdownKeepTitle: z.boolean().default(true),\n}).refine(options => options.overlap < options.maxSize, { path: ['overlap'] })\n\n/**\n * 分块选项类型\n * @example\n * ```ts\n * import { ChunkOptionsSchema, type ChunkOptions } from '@h-ai/datapipe'\n * const options: ChunkOptions = ChunkOptionsSchema.parse({ mode: 'character', maxSize: 500 })\n * ```\n */\nexport type ChunkOptions = z.infer<typeof ChunkOptionsSchema>\n\n/**\n * 分块选项输入类型\n * @example\n * ```ts\n * import type { ChunkOptionsInput } from '@h-ai/datapipe'\n * const options: ChunkOptionsInput = { mode: 'character', maxSize: 500, overlap: 50 }\n * ```\n */\nexport type ChunkOptionsInput = z.input<typeof ChunkOptionsSchema>\n","{\n \"$schema\": \"https://inlang.com/schema/inlang-message-format\",\n \"datapipe_cleanFailed\": \"Failed to clean text: {error}\",\n \"datapipe_chunkFailed\": \"Failed to chunk text: {error}\",\n \"datapipe_transformFailed\": \"Failed to transform text: {error}\",\n \"datapipe_pipelineFailed\": \"Pipeline execution failed: {error}\",\n \"datapipe_configError\": \"Datapipe config validation failed: {error}\",\n \"datapipe_missingSeparator\": \"Custom chunk mode requires a separator parameter\"\n}\n","{\n \"$schema\": \"https://inlang.com/schema/inlang-message-format\",\n \"datapipe_cleanFailed\": \"テキストのクリーニングに失敗しました: {error}\",\n \"datapipe_chunkFailed\": \"テキストの分割に失敗しました: {error}\",\n \"datapipe_transformFailed\": \"テキストの変換に失敗しました: {error}\",\n \"datapipe_pipelineFailed\": \"パイプラインの実行に失敗しました: {error}\",\n \"datapipe_configError\": \"データパイプ設定の検証に失敗しました: {error}\",\n \"datapipe_missingSeparator\": \"カスタム分割モードにはseparatorパラメーターが必要です\"\n}\n","{\n \"$schema\": \"https://inlang.com/schema/inlang-message-format\",\n \"datapipe_cleanFailed\": \"清洗文本失败: {error}\",\n \"datapipe_chunkFailed\": \"分块文本失败: {error}\",\n \"datapipe_transformFailed\": \"转换文本失败: {error}\",\n \"datapipe_pipelineFailed\": \"管线执行失败: {error}\",\n \"datapipe_configError\": \"数据处理配置校验失败:{error}\",\n \"datapipe_missingSeparator\": \"自定义分块模式需要提供 separator 参数\"\n}\n","/**\n * @h-ai/datapipe — i18n\n *\n * 本文件提供数据处理模块的 i18n 文案访问入口。\n * @module datapipe-i18n\n */\n\nimport { core } from '@h-ai/core'\nimport messagesEnUS from '../messages/en-US.json'\nimport messagesJaJP from '../messages/ja-JP.json'\nimport messagesZhCN from '../messages/zh-CN.json'\n\n// ─── i18n ───\n\n/**\n * 数据处理模块文案 Key 类型\n * @example 模块内部上下文\n * ```ts\n * const key: DatapipeMessageKey = 'datapipe_cleanFailed'\n * const message = datapipeM(key)\n * ```\n */\ntype DatapipeMessageKey = keyof typeof messagesZhCN\n\n/**\n * 获取数据处理模块文案。\n * @param key - 文案 Key\n * @param options - 插值参数与格式化选项\n * @returns 本地化后的文案字符串\n * @example 模块内部上下文\n * ```ts\n * const message = datapipeM('datapipe_cleanFailed', { params: { error: 'invalid input' }, locale: 'en-US' })\n * ```\n */\nexport const datapipeM = core.i18n.createMessageGetter<DatapipeMessageKey>({\n 'zh-CN': messagesZhCN,\n 'en-US': messagesEnUS,\n 'ja-JP': messagesJaJP,\n})\n","/**\n * @h-ai/datapipe — 类型定义\n *\n * 本文件定义数据处理模块的核心接口和类型。\n * @module datapipe-types\n */\n\nimport type { ErrorInfo, HaiResult } from '@h-ai/core'\nimport type { ChunkOptionsInput, CleanOptionsInput } from './datapipe-config.js'\nimport { core } from '@h-ai/core'\n\n// ─── 错误定义(照 @h-ai/core 范式) ───\n\n/**\n * 数据处理错误信息映射(错误码:HTTP状态码)。\n *\n * 完整错误码将自动生成为:`hai:datapipe:NNN`\n */\nconst DatapipeErrorInfo = {\n CLEAN_FAILED: '001:500',\n CHUNK_FAILED: '002:500',\n TRANSFORM_FAILED: '003:500',\n PIPELINE_FAILED: '004:500',\n CONFIG_ERROR: '005:400',\n MISSING_SEPARATOR: '006:400',\n} as const satisfies ErrorInfo\n\n/**\n * Datapipe 模块标准错误定义对象。\n * @example\n * ```ts\n * import { HaiDatapipeError } from '@h-ai/datapipe'\n * import { err } from '@h-ai/core'\n * const result = err(HaiDatapipeError.CONFIG_ERROR, 'Invalid chunk configuration')\n * if (!result.success) result.error.httpStatus\n * ```\n */\nexport const HaiDatapipeError = core.error.buildHaiErrorsDef('datapipe', DatapipeErrorInfo)\n\n// ─── 数据块 ───\n\n/**\n * 数据块接口\n *\n * 表示经过分块处理后的单个文本片段。\n * @example\n * ```ts\n * import type { DataChunk } from '@h-ai/datapipe'\n * const options: DataChunk = { index: 0, content: '## Intro', metadata: { title: 'Intro', level: 2 } }\n * ```\n */\nexport interface DataChunk {\n /** 分块索引(从 0 开始) */\n index: number\n /** 原文切片;保留空白和分隔符。word 以词计量,其他模式以 Unicode code point 计量。 */\n content: string\n /** 分块元数据(可选) */\n metadata?: Record<string, unknown>\n}\n\n// ─── 转换函数 ───\n\n/**\n * 转换函数类型\n *\n * 自定义文本后处理函数,可以是同步或异步。\n * @example\n * ```ts\n * import type { TransformFn } from '@h-ai/datapipe'\n * const options: TransformFn = text => text.trim().toLowerCase()\n * ```\n */\nexport type TransformFn = (text: string) => string | Promise<string>\n\n/**\n * 分块转换函数类型\n *\n * 对分块列表进行后处理(如过滤、合并等)。\n * @example\n * ```ts\n * import type { ChunkTransformFn } from '@h-ai/datapipe'\n * const options: ChunkTransformFn = chunks => chunks.filter(chunk => chunk.content.trim().length > 0)\n * ```\n */\nexport type ChunkTransformFn = (chunks: DataChunk[]) => DataChunk[] | Promise<DataChunk[]>\n\n// ─── 管线步骤 ───\n\n/**\n * 管线步骤类型\n *\n * 管线由一系列步骤组成,支持清洗、分块和自定义转换。\n * @example\n * ```ts\n * import type { PipelineStep } from '@h-ai/datapipe'\n * const options: PipelineStep = { type: 'chunk', options: { mode: 'paragraph', maxSize: 500, overlap: 50 } }\n * ```\n */\nexport type PipelineStep\n = | { type: 'clean', options?: CleanOptionsInput }\n | { type: 'chunk', options: ChunkOptionsInput }\n | { type: 'transform', fn: TransformFn }\n | { type: 'chunkTransform', fn: ChunkTransformFn }\n\n// ─── 管线结果 ───\n\n/**\n * 管线执行结果\n * @example\n * ```ts\n * import type { PipelineResult } from '@h-ai/datapipe'\n * const options: PipelineResult = { text: 'One. Two.', chunks: [{ index: 0, content: 'One. Two.' }] }\n * ```\n */\nexport interface PipelineResult {\n /** 处理后的文本(分块前的最终文本) */\n text: string\n /** 分块列表(如果管线包含 chunk 步骤) */\n chunks: DataChunk[]\n}\n\n// ─── 操作接口 ───\n\n/**\n * 清洗操作接口\n *\n * 通过 `datapipe.clean()` 调用。\n * @example\n * ```ts\n * import { datapipe, type CleanOperations } from '@h-ai/datapipe'\n * const clean: CleanOperations = datapipe.clean\n * const result = clean('<p>Hello</p>')\n * if (result.success) result.data.trim()\n * ```\n */\nexport interface CleanOperations {\n /**\n * 清洗文本\n *\n * @param text - 原始文本\n * @param options - 清洗选项(可选,使用默认值)\n * @returns 清洗后的文本\n */\n (text: string, options?: CleanOptionsInput): HaiResult<string>\n}\n\n/**\n * 分块操作接口\n *\n * 通过 `datapipe.chunk()` 调用。\n * @example\n * ```ts\n * import { datapipe, type ChunkOperations } from '@h-ai/datapipe'\n * const chunk: ChunkOperations = datapipe.chunk\n * const result = chunk('One. Two.', { mode: 'sentence', maxSize: 10 })\n * if (result.success) result.data.map(item => item.index)\n * ```\n */\nexport interface ChunkOperations {\n /**\n * 对文本进行分块\n *\n * @param text - 输入文本\n * @param options - 分块选项\n * @returns 分块列表\n */\n (text: string, options: ChunkOptionsInput): HaiResult<DataChunk[]>\n}\n\n/**\n * 管线构建器接口\n *\n * 通过 `datapipe.pipeline()` 创建,链式添加步骤后执行。\n * @example\n * ```ts\n * import { datapipe } from '@h-ai/datapipe'\n * const result = await datapipe.pipeline()\n * .clean({ removeHtml: true })\n * .transform(text => text.toLowerCase())\n * .chunk({ mode: 'character', maxSize: 500, overlap: 50 })\n * .run('<p>One. Two. Three.</p>')\n * if (result.success) result.data.chunks.map(chunk => chunk.content)\n * else result.error.message\n * ```\n */\nexport interface PipelineBuilder {\n /** 添加清洗步骤 */\n clean: (options?: CleanOptionsInput) => PipelineBuilder\n /** 添加分块步骤 */\n chunk: (options: ChunkOptionsInput) => PipelineBuilder\n /** 添加文本转换步骤 */\n transform: (fn: TransformFn) => PipelineBuilder\n /** 添加分块后处理步骤 */\n chunkTransform: (fn: ChunkTransformFn) => PipelineBuilder\n /** 执行管线 */\n run: (text: string) => Promise<HaiResult<PipelineResult>>\n}\n\n/**\n * 数据处理管线服务接口\n * @example\n * ```ts\n * import { datapipe } from '@h-ai/datapipe'\n * const result = await datapipe.pipeline()\n * .clean({ removeHtml: true })\n * .transform(text => text.toLowerCase())\n * .chunk({ mode: 'character', maxSize: 500, overlap: 50 })\n * .run('<p>One. Two. Three.</p>')\n * if (result.success) result.data.chunks.map(chunk => chunk.content)\n * else result.error.message\n * ```\n */\nexport interface DatapipeFunctions {\n /** 清洗文本 */\n clean: CleanOperations\n /** 分块文本 */\n chunk: ChunkOperations\n /** 创建管线构建器 */\n pipeline: () => PipelineBuilder\n}\n","/**\n * @h-ai/datapipe — 保留源文本的有界分块。\n * word 按非空白词计数,其他模式按 Unicode code point 计数,均先计入 overlap。\n * @module datapipe-chunk\n */\nimport type { HaiResult } from '@h-ai/core'\nimport type { ChunkMode, ChunkOptionsInput } from './datapipe-config.js'\nimport type { DataChunk } from './datapipe-types.js'\nimport { err, ok } from '@h-ai/core'\nimport { ChunkOptionsSchema } from './datapipe-config.js'\nimport { datapipeM } from './datapipe-i18n.js'\nimport { HaiDatapipeError } from './datapipe-types.js'\n\n/**\n * Markdown 源区间;标题移除仅影响显式 keepTitle=false 的内容起点。\n * @example 模块内部上下文\n * ```ts\n * const section: MarkdownSection = { start: 0, bodyStart: 8, end: 12, title: 'Title', level: 1 }\n * const bodyLength = section.end - section.bodyStart\n * ```\n */\ninterface MarkdownSection {\n start: number\n bodyStart: number\n end: number\n title: string\n level: number\n}\n\n/** 找出自然边界对应的 code point 下标;保留分隔符,不通过 split/join 改写正文。 */\nfunction findBoundaries(text: string, expression?: RegExp): number[] {\n if (!expression)\n return []\n const boundaries: number[] = []\n let previousOffset = 0\n let points = 0\n for (const match of text.matchAll(expression)) {\n const offset = match.index + match[0].length\n if (offset <= previousOffset)\n continue\n points += Array.from(text.slice(previousOffset, offset)).length\n boundaries.push(points)\n previousOffset = offset\n }\n return boundaries\n}\n\n/** 取不超过预算的最后自然边界;边界至少越过 overlap,保证下轮起点严格前进。 */\nfunction boundedEnd(boundaries: number[], start: number, limit: number, overlap: number): number {\n let lower = 0\n let upper = boundaries.length\n while (lower < upper) {\n const middle = Math.floor((lower + upper) / 2)\n if (boundaries[middle]! <= limit)\n lower = middle + 1\n else\n upper = middle\n }\n const candidate = boundaries[lower - 1]\n return candidate !== undefined && candidate > start + overlap ? candidate : limit\n}\n\n/**\n * 所有字符模式共用滑窗;原文切片已包含空白、标点和分隔符,不追加隐式换行。\n * @example splitCharacters('甲😀乙丙丁', 3, 1) // ['甲😀乙', '乙丙丁']\n */\nfunction splitCharacters(text: string, maxSize: number, overlap: number, expression?: RegExp): string[] {\n const points = Array.from(text)\n const boundaries = findBoundaries(text, expression)\n const chunks: string[] = []\n for (let start = 0; start < points.length;) {\n const limit = Math.min(start + maxSize, points.length)\n const end = limit === points.length ? limit : boundedEnd(boundaries, start, limit, overlap)\n chunks.push(points.slice(start, end).join(''))\n if (end === points.length)\n break\n start = end - overlap\n }\n return chunks\n}\n\n/** 按非空白词滑窗,切片仍使用原始字符串的 offset,保留词间全部空白。 */\nfunction splitWords(text: string, maxSize: number, overlap: number): string[] {\n const words = [...text.matchAll(/\\S+/gu)]\n const chunks: string[] = []\n for (let start = 0; start < words.length;) {\n const end = Math.min(start + maxSize, words.length)\n const from = start === 0 ? 0 : words[start]!.index\n const to = end === words.length ? text.length : words[end]!.index\n chunks.push(text.slice(from, to))\n if (end === words.length)\n break\n start = end - overlap\n }\n return chunks\n}\n\n/** 匹配 # 至配置级别的标题;fenced code 内的 # 是正文,不创建虚假 section。 */\nfunction markdownSections(text: string, minLevel: number): MarkdownSection[] {\n const headings: Array<Omit<MarkdownSection, 'end'>> = []\n let fence: { character: string, length: number } | undefined\n for (const line of text.matchAll(/[^\\n]*(?:\\n|$)/g)) {\n const content = line[0].replace(/\\r?\\n$/, '')\n if (fence) {\n if (new RegExp(`^ {0,3}${fence.character}{${fence.length},}[ \\\\t]*$`).test(content))\n fence = undefined\n continue\n }\n const opening = /^ {0,3}(`{3,}|~{3,})/.exec(content)\n if (opening) {\n fence = { character: opening[1]![0]!, length: opening[1]!.length }\n continue\n }\n const heading = /^ {0,3}(#{1,6})[ \\t]/.exec(content)\n if (heading && heading[1]!.length <= minLevel) {\n let title = content.slice(heading[0].length).trim()\n let end = title.length\n while (title[end - 1] === '#')\n end--\n // 只移除空白分隔的尾部 #,按线性扫描处理,避免可交换量词造成正则回溯。\n if (end < title.length && (title[end - 1] === ' ' || title[end - 1] === '\\t'))\n title = title.slice(0, end).trimEnd()\n headings.push({ start: line.index, bodyStart: line.index + line[0].length, title, level: heading[1]!.length })\n }\n }\n if (headings.length === 0)\n return [{ start: 0, bodyStart: 0, end: text.length, title: '', level: 0 }]\n const sections: MarkdownSection[] = []\n if (headings[0]!.start > 0)\n sections.push({ start: 0, bodyStart: 0, end: headings[0]!.start, title: '', level: 0 })\n headings.forEach((heading, index) => sections.push({ ...heading, end: headings[index + 1]?.start ?? text.length }))\n return sections\n}\n\n/** 自然边界只决定优先切点,不删除分隔符或改变预算单位。 */\nfunction modeExpression(mode: ChunkMode, separator?: string): RegExp | undefined {\n switch (mode) {\n case 'sentence': return /[。!?.!?]+\\s*/gu\n case 'paragraph': return /\\r?\\n(?:[ \\t]*\\r?\\n)+/gu\n case 'page': return /\\f/gu\n case 'custom': return new RegExp(separator!, 'gu')\n default: return undefined\n }\n}\n\n/**\n * 对文本分块;word 使用词数,其他模式使用 Unicode code point,非字节/token/UTF-16 长度。\n * maxSize 包含 overlap 和分隔符,0 <= overlap < maxSize;默认不重叠。\n * 原始空白与分隔符保留,Markdown 仅在显式 keepTitle=false 时移除标题行。\n * @param text - 源文本;空或全空白输入返回空数组。\n * @param options - 模式与预算;Markdown section 之间不重叠,section 内使用相同边界算法。\n * @returns 有界且顺序连续编号的分块;配置错误或非法正则返回错误结果。\n * @example\n * ```ts\n * import { datapipe } from '@h-ai/datapipe'\n * const result = datapipe.chunk('One. Two. Three.', { mode: 'sentence', maxSize: 12, overlap: 2 })\n * if (result.success) result.data.map(chunk => chunk.content)\n * else result.error.message\n * ```\n */\nexport function chunkText(text: string, options: ChunkOptionsInput): HaiResult<DataChunk[]> {\n const parsed = ChunkOptionsSchema.safeParse(options)\n if (!parsed.success)\n return err(HaiDatapipeError.CONFIG_ERROR, datapipeM('datapipe_configError', { params: { error: parsed.error.message } }), parsed.error)\n const opts = parsed.data\n if (opts.mode === 'custom' && !opts.separator)\n return err(HaiDatapipeError.MISSING_SEPARATOR, datapipeM('datapipe_missingSeparator'))\n if (!text || text.trim().length === 0)\n return ok([])\n try {\n if (opts.mode === 'markdown') {\n const chunks: DataChunk[] = []\n for (const section of markdownSections(text, opts.markdownMinLevel)) {\n const content = text.slice(opts.markdownKeepTitle ? section.start : section.bodyStart, section.end)\n const parts = splitCharacters(content, opts.maxSize, opts.overlap, modeExpression('paragraph'))\n for (const part of parts)\n chunks.push({ index: chunks.length, content: part, metadata: { title: section.title, level: section.level, ...(parts.length > 1 ? { subChunk: true } : {}) } })\n }\n return ok(chunks)\n }\n const parts = opts.mode === 'word'\n ? splitWords(text, opts.maxSize, opts.overlap)\n : splitCharacters(text, opts.maxSize, opts.overlap, modeExpression(opts.mode, opts.separator))\n return ok(parts.map((content, index) => ({ index, content })))\n }\n catch (cause) {\n return err(HaiDatapipeError.CHUNK_FAILED, datapipeM('datapipe_chunkFailed', { params: { error: String(cause) } }), cause)\n }\n}\n","/**\n * @h-ai/datapipe — 文本清洗功能\n *\n * 提供文本清洗工具,支持移除 HTML、URL、Email、标准化空白等。\n * @module datapipe-clean\n */\n\nimport type { HaiResult } from '@h-ai/core'\nimport type { CleanOptionsInput } from './datapipe-config.js'\n\nimport { err, ok } from '@h-ai/core'\n\nimport { CleanOptionsSchema } from './datapipe-config.js'\nimport { datapipeM } from './datapipe-i18n.js'\nimport { HaiDatapipeError } from './datapipe-types.js'\n\n/**\n * HTML 标签正则\n */\nconst HTML_TAG_REGEX = /<[^>]*>/g\n\n/**\n * URL 正则(HTTP/HTTPS)\n */\nconst URL_REGEX = /https?:\\/\\/[^\\s<>\"{}|\\\\^`[\\]]+/g\n\n/**\n * Email 正则\n */\nconst EMAIL_REGEX = /[\\w.%+-]+@[a-z0-9.-]+\\.[a-z]{2,}/gi\n\n/**\n * 多空行正则(3行及以上空行 → 2行)\n */\nconst MULTI_NEWLINE_REGEX = /\\n{3,}/g\n\n/**\n * 多空格正则\n */\nconst MULTI_SPACE_REGEX = /[ \\t]{2,}/g\n\n/**\n * 清洗文本\n *\n * 根据配置选项对文本进行清洗处理。\n *\n * @param text - 原始文本\n * @param options - 清洗选项(可选)\n * @returns 清洗后的文本\n * @example\n * ```ts\n * import { datapipe } from '@h-ai/datapipe'\n * const result = datapipe.clean('<p>Hello</p>', { removeHtml: true })\n * if (result.success) result.data.toUpperCase()\n * else result.error.message\n * ```\n */\nexport function cleanText(text: string, options?: CleanOptionsInput): HaiResult<string> {\n const parseResult = CleanOptionsSchema.safeParse(options ?? {})\n if (!parseResult.success) {\n return err(HaiDatapipeError.CONFIG_ERROR, datapipeM('datapipe_configError', { params: { error: parseResult.error.message } }), parseResult.error)\n }\n\n const opts = parseResult.data\n let result = text\n\n try {\n // 移除 HTML 标签\n if (opts.removeHtml) {\n result = result.replace(HTML_TAG_REGEX, '')\n }\n\n // 移除 URL\n if (opts.removeUrls) {\n result = result.replace(URL_REGEX, '')\n }\n\n // 移除 Email\n if (opts.removeEmails) {\n result = result.replace(EMAIL_REGEX, '')\n }\n\n // 自定义替换\n if (opts.customReplacements) {\n for (const { pattern, replacement } of opts.customReplacements) {\n let regex: RegExp\n try {\n regex = new RegExp(pattern, 'g')\n }\n catch (regexError) {\n return err(HaiDatapipeError.CONFIG_ERROR, datapipeM('datapipe_configError', { params: { error: String(regexError) } }), regexError)\n }\n result = result.replace(regex, replacement)\n }\n }\n\n // 标准化空白\n if (opts.normalizeWhitespace) {\n result = result.replace(MULTI_SPACE_REGEX, ' ')\n result = result.replace(MULTI_NEWLINE_REGEX, '\\n\\n')\n }\n\n // 去除首尾空白\n if (opts.trim) {\n result = result.trim()\n }\n\n return ok(result)\n }\n catch (error) {\n return err(HaiDatapipeError.CLEAN_FAILED, datapipeM('datapipe_cleanFailed', { params: { error: String(error) } }), error)\n }\n}\n","/**\n * @h-ai/datapipe — 管线构建器\n *\n * 提供可组合的管线构建器,支持链式添加清洗、分块、转换步骤后执行。\n * @module datapipe-pipeline\n */\n\nimport type { HaiResult } from '@h-ai/core'\nimport type { ChunkOptionsInput, CleanOptionsInput } from './datapipe-config.js'\nimport type {\n ChunkTransformFn,\n DataChunk,\n PipelineBuilder,\n PipelineResult,\n PipelineStep,\n TransformFn,\n} from './datapipe-types.js'\n\nimport { err, ok } from '@h-ai/core'\n\nimport { chunkText } from './datapipe-chunk.js'\nimport { cleanText } from './datapipe-clean.js'\nimport { datapipeM } from './datapipe-i18n.js'\nimport { HaiDatapipeError } from './datapipe-types.js'\n\n/**\n * 创建管线构建器\n *\n * @returns PipelineBuilder 实例,支持链式添加步骤后执行\n * @example\n * ```ts\n * import { datapipe } from '@h-ai/datapipe'\n * const result = await datapipe.pipeline()\n * .clean({ removeHtml: true })\n * .transform(text => text.toLowerCase())\n * .chunk({ mode: 'character', maxSize: 500, overlap: 50 })\n * .run('<p>One. Two. Three.</p>')\n * if (result.success) result.data.chunks.map(chunk => chunk.content)\n * else result.error.message\n * ```\n */\nexport function createPipelineBuilder(): PipelineBuilder {\n const steps: PipelineStep[] = []\n\n const builder: PipelineBuilder = {\n clean(options?: CleanOptionsInput): PipelineBuilder {\n steps.push({ type: 'clean', options })\n return builder\n },\n\n chunk(options: ChunkOptionsInput): PipelineBuilder {\n steps.push({ type: 'chunk', options })\n return builder\n },\n\n transform(fn: TransformFn): PipelineBuilder {\n steps.push({ type: 'transform', fn })\n return builder\n },\n\n chunkTransform(fn: ChunkTransformFn): PipelineBuilder {\n steps.push({ type: 'chunkTransform', fn })\n return builder\n },\n\n run: (text: string) => runPipeline(steps, text),\n }\n\n return builder\n}\n\n// ─── 管线执行 ───\n\n/**\n * 执行管线步骤\n */\nasync function runPipeline(\n steps: PipelineStep[],\n text: string,\n): Promise<HaiResult<PipelineResult>> {\n let currentText = text\n let chunks: DataChunk[] = []\n\n for (const step of steps) {\n switch (step.type) {\n case 'clean': {\n const result = cleanText(currentText, step.options)\n if (!result.success)\n return result\n currentText = result.data\n break\n }\n\n case 'chunk': {\n const result = chunkText(currentText, step.options)\n if (!result.success)\n return result\n chunks = result.data\n break\n }\n\n case 'transform': {\n try {\n currentText = await step.fn(currentText)\n }\n catch (error) {\n return err(HaiDatapipeError.TRANSFORM_FAILED, datapipeM('datapipe_transformFailed', { params: { error: String(error) } }), error)\n }\n break\n }\n\n case 'chunkTransform': {\n if (chunks.length > 0) {\n try {\n chunks = await step.fn(chunks)\n }\n catch (error) {\n return err(HaiDatapipeError.PIPELINE_FAILED, datapipeM('datapipe_pipelineFailed', { params: { error: String(error) } }), error)\n }\n }\n break\n }\n }\n }\n\n return ok({ text: currentText, chunks })\n}\n","/**\n * @h-ai/datapipe — 数据处理管线主入口\n *\n * 提供统一的 `datapipe` 对象,聚合清洗、分块和管线功能。\n * datapipe 为纯函数模块,无需初始化即可使用。\n * @module datapipe-main\n */\n\nimport type { DatapipeFunctions } from './datapipe-types.js'\n\nimport { chunkText } from './datapipe-chunk.js'\nimport { cleanText } from './datapipe-clean.js'\nimport { createPipelineBuilder } from './datapipe-pipeline.js'\n\n// ─── 统一服务对象 ───\n\n/**\n * 数据处理管线服务对象\n *\n * 纯函数模块,无需初始化即可使用。\n * @example\n * ```ts\n * import { datapipe } from '@h-ai/datapipe'\n * const result = await datapipe.pipeline()\n * .clean({ removeHtml: true })\n * .transform(text => text.toLowerCase())\n * .chunk({ mode: 'character', maxSize: 500, overlap: 50 })\n * .run('<p>One. Two. Three.</p>')\n * if (result.success) result.data.chunks.map(chunk => chunk.content)\n * else result.error.message\n * ```\n */\nexport const datapipe: DatapipeFunctions = {\n clean: cleanText,\n chunk: chunkText,\n pipeline: createPipelineBuilder,\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@h-ai/datapipe",
3
3
  "type": "module",
4
- "version": "0.1.0-alpha.67",
4
+ "version": "0.1.0-alpha.68",
5
5
  "description": "Data processing pipeline module for hai-framework: clean, chunk, transform",
6
6
  "exports": {
7
7
  ".": {
@@ -21,7 +21,7 @@
21
21
  "node": "^22.22.2 || ^24.15.0 || >=26.0.0"
22
22
  },
23
23
  "dependencies": {
24
- "@h-ai/core": "0.1.0-alpha.67",
24
+ "@h-ai/core": "0.1.0-alpha.68",
25
25
  "zod": "^4.6.5"
26
26
  },
27
27
  "devDependencies": {