@aigne/afs-index 1.12.0-beta.5

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.
Files changed (186) hide show
  1. package/LICENSE.md +26 -0
  2. package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs +11 -0
  3. package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs +10 -0
  4. package/dist/backend/d1-index-backend.cjs +656 -0
  5. package/dist/backend/d1-index-backend.d.cts +149 -0
  6. package/dist/backend/d1-index-backend.d.cts.map +1 -0
  7. package/dist/backend/d1-index-backend.d.mts +149 -0
  8. package/dist/backend/d1-index-backend.d.mts.map +1 -0
  9. package/dist/backend/d1-index-backend.mjs +657 -0
  10. package/dist/backend/d1-index-backend.mjs.map +1 -0
  11. package/dist/backend/d1-vector-backend.cjs +83 -0
  12. package/dist/backend/d1-vector-backend.d.cts +21 -0
  13. package/dist/backend/d1-vector-backend.d.cts.map +1 -0
  14. package/dist/backend/d1-vector-backend.d.mts +21 -0
  15. package/dist/backend/d1-vector-backend.d.mts.map +1 -0
  16. package/dist/backend/d1-vector-backend.mjs +84 -0
  17. package/dist/backend/d1-vector-backend.mjs.map +1 -0
  18. package/dist/backend/memory-backend.cjs +304 -0
  19. package/dist/backend/memory-backend.d.cts +66 -0
  20. package/dist/backend/memory-backend.d.cts.map +1 -0
  21. package/dist/backend/memory-backend.d.mts +66 -0
  22. package/dist/backend/memory-backend.d.mts.map +1 -0
  23. package/dist/backend/memory-backend.mjs +305 -0
  24. package/dist/backend/memory-backend.mjs.map +1 -0
  25. package/dist/backend/prefix-range.cjs +25 -0
  26. package/dist/backend/prefix-range.mjs +25 -0
  27. package/dist/backend/prefix-range.mjs.map +1 -0
  28. package/dist/backend/schema.cjs +187 -0
  29. package/dist/backend/schema.mjs +188 -0
  30. package/dist/backend/schema.mjs.map +1 -0
  31. package/dist/backend/sqlite-backend.cjs +889 -0
  32. package/dist/backend/sqlite-backend.d.cts +96 -0
  33. package/dist/backend/sqlite-backend.d.cts.map +1 -0
  34. package/dist/backend/sqlite-backend.d.mts +96 -0
  35. package/dist/backend/sqlite-backend.d.mts.map +1 -0
  36. package/dist/backend/sqlite-backend.mjs +890 -0
  37. package/dist/backend/sqlite-backend.mjs.map +1 -0
  38. package/dist/backend/types.d.cts +209 -0
  39. package/dist/backend/types.d.cts.map +1 -0
  40. package/dist/backend/types.d.mts +209 -0
  41. package/dist/backend/types.d.mts.map +1 -0
  42. package/dist/backend/upstash-vector-backend.cjs +98 -0
  43. package/dist/backend/upstash-vector-backend.d.cts +21 -0
  44. package/dist/backend/upstash-vector-backend.d.cts.map +1 -0
  45. package/dist/backend/upstash-vector-backend.d.mts +21 -0
  46. package/dist/backend/upstash-vector-backend.d.mts.map +1 -0
  47. package/dist/backend/upstash-vector-backend.mjs +98 -0
  48. package/dist/backend/upstash-vector-backend.mjs.map +1 -0
  49. package/dist/backend/vector-backend.d.cts +29 -0
  50. package/dist/backend/vector-backend.d.cts.map +1 -0
  51. package/dist/backend/vector-backend.d.mts +29 -0
  52. package/dist/backend/vector-backend.d.mts.map +1 -0
  53. package/dist/dirty-queue.d.cts +41 -0
  54. package/dist/dirty-queue.d.cts.map +1 -0
  55. package/dist/dirty-queue.d.mts +41 -0
  56. package/dist/dirty-queue.d.mts.map +1 -0
  57. package/dist/embed.cjs +45 -0
  58. package/dist/embed.d.cts +20 -0
  59. package/dist/embed.d.cts.map +1 -0
  60. package/dist/embed.d.mts +20 -0
  61. package/dist/embed.d.mts.map +1 -0
  62. package/dist/embed.mjs +46 -0
  63. package/dist/embed.mjs.map +1 -0
  64. package/dist/extraction/defaults.cjs +242 -0
  65. package/dist/extraction/defaults.d.cts +14 -0
  66. package/dist/extraction/defaults.d.cts.map +1 -0
  67. package/dist/extraction/defaults.d.mts +14 -0
  68. package/dist/extraction/defaults.d.mts.map +1 -0
  69. package/dist/extraction/defaults.mjs +242 -0
  70. package/dist/extraction/defaults.mjs.map +1 -0
  71. package/dist/extraction/field-map.cjs +162 -0
  72. package/dist/extraction/field-map.d.cts +70 -0
  73. package/dist/extraction/field-map.d.cts.map +1 -0
  74. package/dist/extraction/field-map.d.mts +70 -0
  75. package/dist/extraction/field-map.d.mts.map +1 -0
  76. package/dist/extraction/field-map.mjs +157 -0
  77. package/dist/extraction/field-map.mjs.map +1 -0
  78. package/dist/extraction/manager.cjs +74 -0
  79. package/dist/extraction/manager.d.cts +47 -0
  80. package/dist/extraction/manager.d.cts.map +1 -0
  81. package/dist/extraction/manager.d.mts +47 -0
  82. package/dist/extraction/manager.d.mts.map +1 -0
  83. package/dist/extraction/manager.mjs +74 -0
  84. package/dist/extraction/manager.mjs.map +1 -0
  85. package/dist/extraction/path-anchors.cjs +86 -0
  86. package/dist/extraction/path-anchors.d.cts +72 -0
  87. package/dist/extraction/path-anchors.d.cts.map +1 -0
  88. package/dist/extraction/path-anchors.d.mts +72 -0
  89. package/dist/extraction/path-anchors.d.mts.map +1 -0
  90. package/dist/extraction/path-anchors.mjs +86 -0
  91. package/dist/extraction/path-anchors.mjs.map +1 -0
  92. package/dist/follower-scheduler.cjs +92 -0
  93. package/dist/follower-scheduler.d.cts +58 -0
  94. package/dist/follower-scheduler.d.cts.map +1 -0
  95. package/dist/follower-scheduler.d.mts +58 -0
  96. package/dist/follower-scheduler.d.mts.map +1 -0
  97. package/dist/follower-scheduler.mjs +92 -0
  98. package/dist/follower-scheduler.mjs.map +1 -0
  99. package/dist/follower.cjs +577 -0
  100. package/dist/follower.d.cts +241 -0
  101. package/dist/follower.d.cts.map +1 -0
  102. package/dist/follower.d.mts +241 -0
  103. package/dist/follower.d.mts.map +1 -0
  104. package/dist/follower.mjs +571 -0
  105. package/dist/follower.mjs.map +1 -0
  106. package/dist/index-provider.cjs +1166 -0
  107. package/dist/index-provider.d.cts +175 -0
  108. package/dist/index-provider.d.cts.map +1 -0
  109. package/dist/index-provider.d.mts +175 -0
  110. package/dist/index-provider.d.mts.map +1 -0
  111. package/dist/index-provider.mjs +1167 -0
  112. package/dist/index-provider.mjs.map +1 -0
  113. package/dist/index.cjs +53 -0
  114. package/dist/index.d.cts +22 -0
  115. package/dist/index.d.mts +22 -0
  116. package/dist/index.mjs +20 -0
  117. package/dist/keys/exact.cjs +20 -0
  118. package/dist/keys/exact.mjs +20 -0
  119. package/dist/keys/exact.mjs.map +1 -0
  120. package/dist/keys/location.cjs +13 -0
  121. package/dist/keys/location.mjs +13 -0
  122. package/dist/keys/location.mjs.map +1 -0
  123. package/dist/keys/normalized.cjs +62 -0
  124. package/dist/keys/normalized.mjs +63 -0
  125. package/dist/keys/normalized.mjs.map +1 -0
  126. package/dist/keys/person.cjs +15 -0
  127. package/dist/keys/person.mjs +15 -0
  128. package/dist/keys/person.mjs.map +1 -0
  129. package/dist/keys/registry.cjs +45 -0
  130. package/dist/keys/registry.d.cts +15 -0
  131. package/dist/keys/registry.d.cts.map +1 -0
  132. package/dist/keys/registry.d.mts +15 -0
  133. package/dist/keys/registry.d.mts.map +1 -0
  134. package/dist/keys/registry.mjs +46 -0
  135. package/dist/keys/registry.mjs.map +1 -0
  136. package/dist/keys/time.cjs +128 -0
  137. package/dist/keys/time.mjs +128 -0
  138. package/dist/keys/time.mjs.map +1 -0
  139. package/dist/keys/topic.cjs +23 -0
  140. package/dist/keys/topic.mjs +23 -0
  141. package/dist/keys/topic.mjs.map +1 -0
  142. package/dist/manifest-index.cjs +29 -0
  143. package/dist/manifest-index.d.cts +24 -0
  144. package/dist/manifest-index.d.cts.map +1 -0
  145. package/dist/manifest-index.d.mts +24 -0
  146. package/dist/manifest-index.d.mts.map +1 -0
  147. package/dist/manifest-index.mjs +28 -0
  148. package/dist/manifest-index.mjs.map +1 -0
  149. package/dist/offline-rebuild.cjs +48 -0
  150. package/dist/offline-rebuild.d.cts +57 -0
  151. package/dist/offline-rebuild.d.cts.map +1 -0
  152. package/dist/offline-rebuild.d.mts +57 -0
  153. package/dist/offline-rebuild.d.mts.map +1 -0
  154. package/dist/offline-rebuild.mjs +49 -0
  155. package/dist/offline-rebuild.mjs.map +1 -0
  156. package/dist/query/engine.cjs +433 -0
  157. package/dist/query/engine.d.cts +34 -0
  158. package/dist/query/engine.d.cts.map +1 -0
  159. package/dist/query/engine.d.mts +34 -0
  160. package/dist/query/engine.d.mts.map +1 -0
  161. package/dist/query/engine.mjs +433 -0
  162. package/dist/query/engine.mjs.map +1 -0
  163. package/dist/query/limits.cjs +12 -0
  164. package/dist/query/limits.mjs +12 -0
  165. package/dist/query/limits.mjs.map +1 -0
  166. package/dist/text/cjk.cjs +30 -0
  167. package/dist/text/cjk.d.cts +24 -0
  168. package/dist/text/cjk.d.cts.map +1 -0
  169. package/dist/text/cjk.d.mts +24 -0
  170. package/dist/text/cjk.d.mts.map +1 -0
  171. package/dist/text/cjk.mjs +30 -0
  172. package/dist/text/cjk.mjs.map +1 -0
  173. package/dist/text/fts.cjs +244 -0
  174. package/dist/text/fts.mjs +245 -0
  175. package/dist/text/fts.mjs.map +1 -0
  176. package/dist/text/host.cjs +39 -0
  177. package/dist/text/host.mjs +38 -0
  178. package/dist/text/host.mjs.map +1 -0
  179. package/dist/types.cjs +44 -0
  180. package/dist/types.d.cts +393 -0
  181. package/dist/types.d.cts.map +1 -0
  182. package/dist/types.d.mts +393 -0
  183. package/dist/types.d.mts.map +1 -0
  184. package/dist/types.mjs +42 -0
  185. package/dist/types.mjs.map +1 -0
  186. package/package.json +59 -0
@@ -0,0 +1,162 @@
1
+ const require_host = require('../text/host.cjs');
2
+ let ufo = require("ufo");
3
+
4
+ //#region src/extraction/field-map.ts
5
+ function tokenizePath(path) {
6
+ const segments = [];
7
+ let rest = path;
8
+ while (rest.length > 0) if (rest.startsWith("[*]")) {
9
+ segments.push({ type: "wildcard" });
10
+ rest = rest.slice(3);
11
+ } else if (rest.startsWith(".")) {
12
+ rest = rest.slice(1);
13
+ const m = rest.match(/^([^.[*\]]+)/);
14
+ if (!m) break;
15
+ segments.push({
16
+ type: "field",
17
+ name: m[1]
18
+ });
19
+ rest = rest.slice(m[1].length);
20
+ } else break;
21
+ return segments;
22
+ }
23
+ function evalSegments(current, segments) {
24
+ if (segments.length === 0) return current !== void 0 && current !== null ? [current] : [];
25
+ const [seg, ...rest] = segments;
26
+ if (!seg) return [];
27
+ if (seg.type === "field") {
28
+ if (typeof current !== "object" || current === null || Array.isArray(current)) return [];
29
+ const next = current[seg.name];
30
+ if (next === void 0 || next === null) return [];
31
+ return evalSegments(next, rest);
32
+ }
33
+ if (seg.type === "wildcard") {
34
+ if (!Array.isArray(current)) return [];
35
+ return current.flatMap((item) => evalSegments(item, rest));
36
+ }
37
+ return [];
38
+ }
39
+ /**
40
+ * Evaluate a JSONPath expression against a JSON value.
41
+ * Returns an array of all matched values (may be empty).
42
+ * Expressions must start with `$`; otherwise returns [].
43
+ */
44
+ function evalJsonPath(obj, expr) {
45
+ if (!expr.startsWith("$")) return [];
46
+ return evalSegments(obj, tokenizePath(expr.slice(1)));
47
+ }
48
+ const HOST_EXPR_RE = /^host\((.+)\)$/;
49
+ function extractHost(url) {
50
+ try {
51
+ const u = new URL(url);
52
+ return u.hostname ? require_host.normalizeHost(u.hostname) : null;
53
+ } catch {
54
+ return null;
55
+ }
56
+ }
57
+ /**
58
+ * Evaluate a single field expression (JSONPath or `host()`) against a JSON value.
59
+ * Returns an array of non-empty string values.
60
+ */
61
+ function evalFieldExpr(obj, expr) {
62
+ const hostMatch = expr.match(HOST_EXPR_RE);
63
+ if (hostMatch) return evalJsonPath(obj, hostMatch[1].trim()).flatMap((url) => {
64
+ if (typeof url !== "string") return [];
65
+ const h = extractHost(url);
66
+ return h ? [h] : [];
67
+ });
68
+ return evalJsonPath(obj, expr).filter((v) => typeof v === "string").filter((s) => s.length > 0);
69
+ }
70
+ /**
71
+ * Apply a field map against a JSON document.
72
+ * Returns a record of field name → deduplicated string values.
73
+ * Fields with no matching values are omitted.
74
+ */
75
+ function applyFieldMap(obj, fields) {
76
+ const result = {};
77
+ for (const [fieldName, exprs] of Object.entries(fields)) {
78
+ const values = (Array.isArray(exprs) ? exprs : [exprs]).flatMap((expr) => evalFieldExpr(obj, expr));
79
+ const unique = [...new Set(values)];
80
+ if (unique.length > 0) result[fieldName] = unique;
81
+ }
82
+ return result;
83
+ }
84
+ /**
85
+ * Extract FTS summary text by evaluating one or more JSONPath expressions.
86
+ * Values are joined with newline; empty/missing values are skipped.
87
+ */
88
+ function extractSummaryText(obj, exprs) {
89
+ return (Array.isArray(exprs) ? exprs : [exprs]).flatMap((expr) => evalFieldExpr(obj, expr)).filter(Boolean).join("\n").trim();
90
+ }
91
+ /**
92
+ * Resolve an enrich contentFile path template against the item JSON.
93
+ *
94
+ * Template syntax: `assets/{$.slug}/content.md` — `{$.expr}` is replaced
95
+ * with the first string value from the expression.
96
+ *
97
+ * Returns null if:
98
+ * - No `{$.field}` placeholders resolve to a value
99
+ * - Resolved path would traverse `..` (injection guard)
100
+ */
101
+ function resolveEnrichPath(template, itemJson) {
102
+ const resolved = template.replace(/\{(\$[^}]+)\}/g, (_, expr) => {
103
+ return evalFieldExpr(itemJson, expr.trim())[0] ?? "";
104
+ });
105
+ if (!resolved) return null;
106
+ if (resolved.split("/").some((s) => s === "..")) return null;
107
+ return resolved;
108
+ }
109
+ /**
110
+ * Perform the `enrich.contentFile` second hop: resolve the sidecar path
111
+ * template against the item JSON (via {@link resolveEnrichPath}), read it
112
+ * through the AFS handle, truncate to `maxChars`, and append the result to
113
+ * `baseSummary`. This is the real two-hop read the field-map engine promises
114
+ * (read item.json → derive a path → read the sidecar) — kept AFS-Only: the
115
+ * only I/O here is `afs.read()`.
116
+ *
117
+ * Fails soft in every case — returns `baseSummary` unchanged when:
118
+ * - `enrich.contentFile` is not configured
119
+ * - the path template can't be resolved (missing placeholder value, or `..`
120
+ * traversal after resolution — {@link resolveEnrichPath} returns null)
121
+ * - the sidecar doesn't exist or isn't readable (`afs.read` throws or
122
+ * returns no string content)
123
+ *
124
+ * Path assembly uses the real two-argument `joinURL(base, relative)` form
125
+ * (not the single-arg passthrough) so the scope and the resolved relative
126
+ * path are joined with correct slash normalization.
127
+ */
128
+ async function applyEnrichContentFile(params) {
129
+ const { afs, itemJson, baseSummary, enrich, scope } = params;
130
+ const contentFile = enrich?.contentFile;
131
+ if (!contentFile) return baseSummary;
132
+ const relative = resolveEnrichPath(contentFile.path, itemJson);
133
+ if (!relative) return baseSummary;
134
+ const sidecarPath = (0, ufo.joinURL)(scope ?? "/", relative);
135
+ try {
136
+ const content = (await afs.read?.(sidecarPath))?.data?.content;
137
+ if (typeof content !== "string" || content.length === 0) return baseSummary;
138
+ const enriched = contentFile.maxChars != null ? truncateToMaxChars(content, contentFile.maxChars) : content;
139
+ return baseSummary ? `${baseSummary}\n${enriched}` : enriched;
140
+ } catch {
141
+ return baseSummary;
142
+ }
143
+ }
144
+ /**
145
+ * Truncate a string to at most `maxChars` characters.
146
+ * Truncation happens at a word boundary if possible, otherwise at the hard limit.
147
+ */
148
+ function truncateToMaxChars(text, maxChars) {
149
+ if (text.length <= maxChars) return text;
150
+ const cut = text.slice(0, maxChars);
151
+ const lastSpace = cut.lastIndexOf(" ");
152
+ return lastSpace > maxChars * .8 ? cut.slice(0, lastSpace) : cut;
153
+ }
154
+
155
+ //#endregion
156
+ exports.applyEnrichContentFile = applyEnrichContentFile;
157
+ exports.applyFieldMap = applyFieldMap;
158
+ exports.evalFieldExpr = evalFieldExpr;
159
+ exports.evalJsonPath = evalJsonPath;
160
+ exports.extractSummaryText = extractSummaryText;
161
+ exports.resolveEnrichPath = resolveEnrichPath;
162
+ exports.truncateToMaxChars = truncateToMaxChars;
@@ -0,0 +1,70 @@
1
+ import { AFSRoot, IndexEnrich } from "@aigne/afs";
2
+
3
+ //#region src/extraction/field-map.d.ts
4
+ /**
5
+ * Evaluate a JSONPath expression against a JSON value.
6
+ * Returns an array of all matched values (may be empty).
7
+ * Expressions must start with `$`; otherwise returns [].
8
+ */
9
+ declare function evalJsonPath(obj: unknown, expr: string): unknown[];
10
+ /**
11
+ * Evaluate a single field expression (JSONPath or `host()`) against a JSON value.
12
+ * Returns an array of non-empty string values.
13
+ */
14
+ declare function evalFieldExpr(obj: unknown, expr: string): string[];
15
+ /**
16
+ * Apply a field map against a JSON document.
17
+ * Returns a record of field name → deduplicated string values.
18
+ * Fields with no matching values are omitted.
19
+ */
20
+ declare function applyFieldMap(obj: unknown, fields: Record<string, string | string[]>): Record<string, string[]>;
21
+ /**
22
+ * Extract FTS summary text by evaluating one or more JSONPath expressions.
23
+ * Values are joined with newline; empty/missing values are skipped.
24
+ */
25
+ declare function extractSummaryText(obj: unknown, exprs: string | string[]): string;
26
+ /**
27
+ * Resolve an enrich contentFile path template against the item JSON.
28
+ *
29
+ * Template syntax: `assets/{$.slug}/content.md` — `{$.expr}` is replaced
30
+ * with the first string value from the expression.
31
+ *
32
+ * Returns null if:
33
+ * - No `{$.field}` placeholders resolve to a value
34
+ * - Resolved path would traverse `..` (injection guard)
35
+ */
36
+ declare function resolveEnrichPath(template: string, itemJson: Record<string, unknown>): string | null;
37
+ /**
38
+ * Perform the `enrich.contentFile` second hop: resolve the sidecar path
39
+ * template against the item JSON (via {@link resolveEnrichPath}), read it
40
+ * through the AFS handle, truncate to `maxChars`, and append the result to
41
+ * `baseSummary`. This is the real two-hop read the field-map engine promises
42
+ * (read item.json → derive a path → read the sidecar) — kept AFS-Only: the
43
+ * only I/O here is `afs.read()`.
44
+ *
45
+ * Fails soft in every case — returns `baseSummary` unchanged when:
46
+ * - `enrich.contentFile` is not configured
47
+ * - the path template can't be resolved (missing placeholder value, or `..`
48
+ * traversal after resolution — {@link resolveEnrichPath} returns null)
49
+ * - the sidecar doesn't exist or isn't readable (`afs.read` throws or
50
+ * returns no string content)
51
+ *
52
+ * Path assembly uses the real two-argument `joinURL(base, relative)` form
53
+ * (not the single-arg passthrough) so the scope and the resolved relative
54
+ * path are joined with correct slash normalization.
55
+ */
56
+ declare function applyEnrichContentFile(params: {
57
+ /** AFS handle used for the second-hop read. Only `read()` is used (AFS-Only). */afs: Pick<AFSRoot, "read">; /** The item JSON the field map was extracted from (and enrich path is templated against). */
58
+ itemJson: Record<string, unknown>; /** Summary text already extracted via `extractSummaryText`, to append to. */
59
+ baseSummary: string; /** The domain's `enrich` config (from `BlockletIndexDomain.enrich`). */
60
+ enrich: IndexEnrich | undefined; /** Base path the resolved relative sidecar path is joined against (e.g. the domain's `scope`, "/user"). */
61
+ scope?: string;
62
+ }): Promise<string>;
63
+ /**
64
+ * Truncate a string to at most `maxChars` characters.
65
+ * Truncation happens at a word boundary if possible, otherwise at the hard limit.
66
+ */
67
+ declare function truncateToMaxChars(text: string, maxChars: number): string;
68
+ //#endregion
69
+ export { applyEnrichContentFile, applyFieldMap, evalFieldExpr, evalJsonPath, extractSummaryText, resolveEnrichPath, truncateToMaxChars };
70
+ //# sourceMappingURL=field-map.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"field-map.d.cts","names":[],"sources":["../../src/extraction/field-map.ts"],"mappings":";;;;;;;;iBAuEgB,YAAA,CAAa,GAAA,WAAc,IAAA;;;;;iBA6B3B,aAAA,CAAc,GAAA,WAAc,IAAA;;;;;;iBAwB5B,aAAA,CACd,GAAA,WACA,MAAA,EAAQ,MAAA,8BACP,MAAA;;;AA0EH;;iBAzDgB,kBAAA,CAAmB,GAAA,WAAc,KAAA;;;;;;;;;;;iBAgBjC,iBAAA,CACd,QAAA,UACA,QAAA,EAAU,MAAA;;;;;;;;;;AA8EZ;;;;;;;;;;iBAvCsB,sBAAA,CAAuB,MAAA;mFAE3C,GAAA,EAAK,IAAA,CAAK,OAAA;EAEV,QAAA,EAAU,MAAA;EAEV,WAAA;EAEA,MAAA,EAAQ,WAAA;EAER,KAAA;AAAA,IACE,OAAA;;;;;iBA4BY,kBAAA,CAAmB,IAAA,UAAc,QAAA"}
@@ -0,0 +1,70 @@
1
+ import { AFSRoot, IndexEnrich } from "@aigne/afs";
2
+
3
+ //#region src/extraction/field-map.d.ts
4
+ /**
5
+ * Evaluate a JSONPath expression against a JSON value.
6
+ * Returns an array of all matched values (may be empty).
7
+ * Expressions must start with `$`; otherwise returns [].
8
+ */
9
+ declare function evalJsonPath(obj: unknown, expr: string): unknown[];
10
+ /**
11
+ * Evaluate a single field expression (JSONPath or `host()`) against a JSON value.
12
+ * Returns an array of non-empty string values.
13
+ */
14
+ declare function evalFieldExpr(obj: unknown, expr: string): string[];
15
+ /**
16
+ * Apply a field map against a JSON document.
17
+ * Returns a record of field name → deduplicated string values.
18
+ * Fields with no matching values are omitted.
19
+ */
20
+ declare function applyFieldMap(obj: unknown, fields: Record<string, string | string[]>): Record<string, string[]>;
21
+ /**
22
+ * Extract FTS summary text by evaluating one or more JSONPath expressions.
23
+ * Values are joined with newline; empty/missing values are skipped.
24
+ */
25
+ declare function extractSummaryText(obj: unknown, exprs: string | string[]): string;
26
+ /**
27
+ * Resolve an enrich contentFile path template against the item JSON.
28
+ *
29
+ * Template syntax: `assets/{$.slug}/content.md` — `{$.expr}` is replaced
30
+ * with the first string value from the expression.
31
+ *
32
+ * Returns null if:
33
+ * - No `{$.field}` placeholders resolve to a value
34
+ * - Resolved path would traverse `..` (injection guard)
35
+ */
36
+ declare function resolveEnrichPath(template: string, itemJson: Record<string, unknown>): string | null;
37
+ /**
38
+ * Perform the `enrich.contentFile` second hop: resolve the sidecar path
39
+ * template against the item JSON (via {@link resolveEnrichPath}), read it
40
+ * through the AFS handle, truncate to `maxChars`, and append the result to
41
+ * `baseSummary`. This is the real two-hop read the field-map engine promises
42
+ * (read item.json → derive a path → read the sidecar) — kept AFS-Only: the
43
+ * only I/O here is `afs.read()`.
44
+ *
45
+ * Fails soft in every case — returns `baseSummary` unchanged when:
46
+ * - `enrich.contentFile` is not configured
47
+ * - the path template can't be resolved (missing placeholder value, or `..`
48
+ * traversal after resolution — {@link resolveEnrichPath} returns null)
49
+ * - the sidecar doesn't exist or isn't readable (`afs.read` throws or
50
+ * returns no string content)
51
+ *
52
+ * Path assembly uses the real two-argument `joinURL(base, relative)` form
53
+ * (not the single-arg passthrough) so the scope and the resolved relative
54
+ * path are joined with correct slash normalization.
55
+ */
56
+ declare function applyEnrichContentFile(params: {
57
+ /** AFS handle used for the second-hop read. Only `read()` is used (AFS-Only). */afs: Pick<AFSRoot, "read">; /** The item JSON the field map was extracted from (and enrich path is templated against). */
58
+ itemJson: Record<string, unknown>; /** Summary text already extracted via `extractSummaryText`, to append to. */
59
+ baseSummary: string; /** The domain's `enrich` config (from `BlockletIndexDomain.enrich`). */
60
+ enrich: IndexEnrich | undefined; /** Base path the resolved relative sidecar path is joined against (e.g. the domain's `scope`, "/user"). */
61
+ scope?: string;
62
+ }): Promise<string>;
63
+ /**
64
+ * Truncate a string to at most `maxChars` characters.
65
+ * Truncation happens at a word boundary if possible, otherwise at the hard limit.
66
+ */
67
+ declare function truncateToMaxChars(text: string, maxChars: number): string;
68
+ //#endregion
69
+ export { applyEnrichContentFile, applyFieldMap, evalFieldExpr, evalJsonPath, extractSummaryText, resolveEnrichPath, truncateToMaxChars };
70
+ //# sourceMappingURL=field-map.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"field-map.d.mts","names":[],"sources":["../../src/extraction/field-map.ts"],"mappings":";;;;;;;;iBAuEgB,YAAA,CAAa,GAAA,WAAc,IAAA;;;;;iBA6B3B,aAAA,CAAc,GAAA,WAAc,IAAA;;;;;;iBAwB5B,aAAA,CACd,GAAA,WACA,MAAA,EAAQ,MAAA,8BACP,MAAA;;;AA0EH;;iBAzDgB,kBAAA,CAAmB,GAAA,WAAc,KAAA;;;;;;;;;;;iBAgBjC,iBAAA,CACd,QAAA,UACA,QAAA,EAAU,MAAA;;;;;;;;;;AA8EZ;;;;;;;;;;iBAvCsB,sBAAA,CAAuB,MAAA;mFAE3C,GAAA,EAAK,IAAA,CAAK,OAAA;EAEV,QAAA,EAAU,MAAA;EAEV,WAAA;EAEA,MAAA,EAAQ,WAAA;EAER,KAAA;AAAA,IACE,OAAA;;;;;iBA4BY,kBAAA,CAAmB,IAAA,UAAc,QAAA"}
@@ -0,0 +1,157 @@
1
+ import { normalizeHost } from "../text/host.mjs";
2
+ import { joinURL } from "ufo";
3
+
4
+ //#region src/extraction/field-map.ts
5
+ function tokenizePath(path) {
6
+ const segments = [];
7
+ let rest = path;
8
+ while (rest.length > 0) if (rest.startsWith("[*]")) {
9
+ segments.push({ type: "wildcard" });
10
+ rest = rest.slice(3);
11
+ } else if (rest.startsWith(".")) {
12
+ rest = rest.slice(1);
13
+ const m = rest.match(/^([^.[*\]]+)/);
14
+ if (!m) break;
15
+ segments.push({
16
+ type: "field",
17
+ name: m[1]
18
+ });
19
+ rest = rest.slice(m[1].length);
20
+ } else break;
21
+ return segments;
22
+ }
23
+ function evalSegments(current, segments) {
24
+ if (segments.length === 0) return current !== void 0 && current !== null ? [current] : [];
25
+ const [seg, ...rest] = segments;
26
+ if (!seg) return [];
27
+ if (seg.type === "field") {
28
+ if (typeof current !== "object" || current === null || Array.isArray(current)) return [];
29
+ const next = current[seg.name];
30
+ if (next === void 0 || next === null) return [];
31
+ return evalSegments(next, rest);
32
+ }
33
+ if (seg.type === "wildcard") {
34
+ if (!Array.isArray(current)) return [];
35
+ return current.flatMap((item) => evalSegments(item, rest));
36
+ }
37
+ return [];
38
+ }
39
+ /**
40
+ * Evaluate a JSONPath expression against a JSON value.
41
+ * Returns an array of all matched values (may be empty).
42
+ * Expressions must start with `$`; otherwise returns [].
43
+ */
44
+ function evalJsonPath(obj, expr) {
45
+ if (!expr.startsWith("$")) return [];
46
+ return evalSegments(obj, tokenizePath(expr.slice(1)));
47
+ }
48
+ const HOST_EXPR_RE = /^host\((.+)\)$/;
49
+ function extractHost(url) {
50
+ try {
51
+ const u = new URL(url);
52
+ return u.hostname ? normalizeHost(u.hostname) : null;
53
+ } catch {
54
+ return null;
55
+ }
56
+ }
57
+ /**
58
+ * Evaluate a single field expression (JSONPath or `host()`) against a JSON value.
59
+ * Returns an array of non-empty string values.
60
+ */
61
+ function evalFieldExpr(obj, expr) {
62
+ const hostMatch = expr.match(HOST_EXPR_RE);
63
+ if (hostMatch) return evalJsonPath(obj, hostMatch[1].trim()).flatMap((url) => {
64
+ if (typeof url !== "string") return [];
65
+ const h = extractHost(url);
66
+ return h ? [h] : [];
67
+ });
68
+ return evalJsonPath(obj, expr).filter((v) => typeof v === "string").filter((s) => s.length > 0);
69
+ }
70
+ /**
71
+ * Apply a field map against a JSON document.
72
+ * Returns a record of field name → deduplicated string values.
73
+ * Fields with no matching values are omitted.
74
+ */
75
+ function applyFieldMap(obj, fields) {
76
+ const result = {};
77
+ for (const [fieldName, exprs] of Object.entries(fields)) {
78
+ const values = (Array.isArray(exprs) ? exprs : [exprs]).flatMap((expr) => evalFieldExpr(obj, expr));
79
+ const unique = [...new Set(values)];
80
+ if (unique.length > 0) result[fieldName] = unique;
81
+ }
82
+ return result;
83
+ }
84
+ /**
85
+ * Extract FTS summary text by evaluating one or more JSONPath expressions.
86
+ * Values are joined with newline; empty/missing values are skipped.
87
+ */
88
+ function extractSummaryText(obj, exprs) {
89
+ return (Array.isArray(exprs) ? exprs : [exprs]).flatMap((expr) => evalFieldExpr(obj, expr)).filter(Boolean).join("\n").trim();
90
+ }
91
+ /**
92
+ * Resolve an enrich contentFile path template against the item JSON.
93
+ *
94
+ * Template syntax: `assets/{$.slug}/content.md` — `{$.expr}` is replaced
95
+ * with the first string value from the expression.
96
+ *
97
+ * Returns null if:
98
+ * - No `{$.field}` placeholders resolve to a value
99
+ * - Resolved path would traverse `..` (injection guard)
100
+ */
101
+ function resolveEnrichPath(template, itemJson) {
102
+ const resolved = template.replace(/\{(\$[^}]+)\}/g, (_, expr) => {
103
+ return evalFieldExpr(itemJson, expr.trim())[0] ?? "";
104
+ });
105
+ if (!resolved) return null;
106
+ if (resolved.split("/").some((s) => s === "..")) return null;
107
+ return resolved;
108
+ }
109
+ /**
110
+ * Perform the `enrich.contentFile` second hop: resolve the sidecar path
111
+ * template against the item JSON (via {@link resolveEnrichPath}), read it
112
+ * through the AFS handle, truncate to `maxChars`, and append the result to
113
+ * `baseSummary`. This is the real two-hop read the field-map engine promises
114
+ * (read item.json → derive a path → read the sidecar) — kept AFS-Only: the
115
+ * only I/O here is `afs.read()`.
116
+ *
117
+ * Fails soft in every case — returns `baseSummary` unchanged when:
118
+ * - `enrich.contentFile` is not configured
119
+ * - the path template can't be resolved (missing placeholder value, or `..`
120
+ * traversal after resolution — {@link resolveEnrichPath} returns null)
121
+ * - the sidecar doesn't exist or isn't readable (`afs.read` throws or
122
+ * returns no string content)
123
+ *
124
+ * Path assembly uses the real two-argument `joinURL(base, relative)` form
125
+ * (not the single-arg passthrough) so the scope and the resolved relative
126
+ * path are joined with correct slash normalization.
127
+ */
128
+ async function applyEnrichContentFile(params) {
129
+ const { afs, itemJson, baseSummary, enrich, scope } = params;
130
+ const contentFile = enrich?.contentFile;
131
+ if (!contentFile) return baseSummary;
132
+ const relative = resolveEnrichPath(contentFile.path, itemJson);
133
+ if (!relative) return baseSummary;
134
+ const sidecarPath = joinURL(scope ?? "/", relative);
135
+ try {
136
+ const content = (await afs.read?.(sidecarPath))?.data?.content;
137
+ if (typeof content !== "string" || content.length === 0) return baseSummary;
138
+ const enriched = contentFile.maxChars != null ? truncateToMaxChars(content, contentFile.maxChars) : content;
139
+ return baseSummary ? `${baseSummary}\n${enriched}` : enriched;
140
+ } catch {
141
+ return baseSummary;
142
+ }
143
+ }
144
+ /**
145
+ * Truncate a string to at most `maxChars` characters.
146
+ * Truncation happens at a word boundary if possible, otherwise at the hard limit.
147
+ */
148
+ function truncateToMaxChars(text, maxChars) {
149
+ if (text.length <= maxChars) return text;
150
+ const cut = text.slice(0, maxChars);
151
+ const lastSpace = cut.lastIndexOf(" ");
152
+ return lastSpace > maxChars * .8 ? cut.slice(0, lastSpace) : cut;
153
+ }
154
+
155
+ //#endregion
156
+ export { applyEnrichContentFile, applyFieldMap, evalFieldExpr, evalJsonPath, extractSummaryText, resolveEnrichPath, truncateToMaxChars };
157
+ //# sourceMappingURL=field-map.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"field-map.mjs","names":[],"sources":["../../src/extraction/field-map.ts"],"sourcesContent":["/**\n * Field-map extraction engine — evaluates JSONPath expressions and the\n * `host()` built-in against a JSON document to produce anchor values and\n * FTS summary text.\n *\n * JSONPath subset supported:\n * $.field — top-level field\n * $.a.b — nested field\n * $.array[*] — all elements of an array field\n * $.a.b[*] — all elements of a nested array\n * $.a[*].b — field access on each array element\n *\n * Built-in:\n * host($.url) — extracts the hostname from a URL field value\n *\n * All other expressions return no values (no throw).\n */\nimport type { AFSRoot, IndexEnrich } from \"@aigne/afs\";\nimport { joinURL } from \"ufo\";\nimport { normalizeHost } from \"../text/host.js\";\n\n// ── JSONPath evaluator ────────────────────────────────────────────────────────\n\ntype Segment = { type: \"field\"; name: string } | { type: \"wildcard\" };\n\nfunction tokenizePath(path: string): Segment[] {\n const segments: Segment[] = [];\n let rest = path;\n while (rest.length > 0) {\n if (rest.startsWith(\"[*]\")) {\n segments.push({ type: \"wildcard\" });\n rest = rest.slice(3);\n } else if (rest.startsWith(\".\")) {\n rest = rest.slice(1);\n const m = rest.match(/^([^.[*\\]]+)/);\n if (!m) break;\n segments.push({ type: \"field\", name: m[1]! });\n rest = rest.slice(m[1]!.length);\n } else {\n break;\n }\n }\n return segments;\n}\n\nfunction evalSegments(current: unknown, segments: Segment[]): unknown[] {\n if (segments.length === 0) return current !== undefined && current !== null ? [current] : [];\n\n const [seg, ...rest] = segments;\n if (!seg) return [];\n\n if (seg.type === \"field\") {\n if (typeof current !== \"object\" || current === null || Array.isArray(current)) return [];\n const next = (current as Record<string, unknown>)[seg.name];\n if (next === undefined || next === null) return [];\n return evalSegments(next, rest);\n }\n\n if (seg.type === \"wildcard\") {\n if (!Array.isArray(current)) return [];\n return current.flatMap((item) => evalSegments(item, rest));\n }\n\n return [];\n}\n\n/**\n * Evaluate a JSONPath expression against a JSON value.\n * Returns an array of all matched values (may be empty).\n * Expressions must start with `$`; otherwise returns [].\n */\nexport function evalJsonPath(obj: unknown, expr: string): unknown[] {\n if (!expr.startsWith(\"$\")) return [];\n const path = expr.slice(1);\n const segments = tokenizePath(path);\n return evalSegments(obj, segments);\n}\n\n// ── host() built-in ───────────────────────────────────────────────────────────\n\nconst HOST_EXPR_RE = /^host\\((.+)\\)$/;\n\nfunction extractHost(url: string): string | null {\n try {\n const u = new URL(url);\n // Canonicalize at the source so the `site` anchor value, its generated\n // anchor key, and the FTS summary text all agree on one host form —\n // `www.example.com` and `example.com` collapse to `example.com` (#1249).\n return u.hostname ? normalizeHost(u.hostname) : null;\n } catch {\n return null;\n }\n}\n\n// ── Field expression evaluator ────────────────────────────────────────────────\n\n/**\n * Evaluate a single field expression (JSONPath or `host()`) against a JSON value.\n * Returns an array of non-empty string values.\n */\nexport function evalFieldExpr(obj: unknown, expr: string): string[] {\n const hostMatch = expr.match(HOST_EXPR_RE);\n if (hostMatch) {\n const innerExpr = hostMatch[1]!.trim();\n const urls = evalJsonPath(obj, innerExpr);\n return urls.flatMap((url) => {\n if (typeof url !== \"string\") return [];\n const h = extractHost(url);\n return h ? [h] : [];\n });\n }\n\n return evalJsonPath(obj, expr)\n .filter((v): v is string => typeof v === \"string\")\n .filter((s) => s.length > 0);\n}\n\n// ── Public API ────────────────────────────────────────────────────────────────\n\n/**\n * Apply a field map against a JSON document.\n * Returns a record of field name → deduplicated string values.\n * Fields with no matching values are omitted.\n */\nexport function applyFieldMap(\n obj: unknown,\n fields: Record<string, string | string[]>,\n): Record<string, string[]> {\n const result: Record<string, string[]> = {};\n for (const [fieldName, exprs] of Object.entries(fields)) {\n const exprList = Array.isArray(exprs) ? exprs : [exprs];\n const values = exprList.flatMap((expr) => evalFieldExpr(obj, expr));\n const unique = [...new Set(values)];\n if (unique.length > 0) {\n result[fieldName] = unique;\n }\n }\n return result;\n}\n\n/**\n * Extract FTS summary text by evaluating one or more JSONPath expressions.\n * Values are joined with newline; empty/missing values are skipped.\n */\nexport function extractSummaryText(obj: unknown, exprs: string | string[]): string {\n const exprList = Array.isArray(exprs) ? exprs : [exprs];\n const texts = exprList.flatMap((expr) => evalFieldExpr(obj, expr));\n return texts.filter(Boolean).join(\"\\n\").trim();\n}\n\n/**\n * Resolve an enrich contentFile path template against the item JSON.\n *\n * Template syntax: `assets/{$.slug}/content.md` — `{$.expr}` is replaced\n * with the first string value from the expression.\n *\n * Returns null if:\n * - No `{$.field}` placeholders resolve to a value\n * - Resolved path would traverse `..` (injection guard)\n */\nexport function resolveEnrichPath(\n template: string,\n itemJson: Record<string, unknown>,\n): string | null {\n const resolved = template.replace(/\\{(\\$[^}]+)\\}/g, (_, expr: string) => {\n const values = evalFieldExpr(itemJson, expr.trim());\n return values[0] ?? \"\";\n });\n\n if (!resolved) return null;\n\n const segments = resolved.split(\"/\");\n if (segments.some((s) => s === \"..\")) return null;\n\n // `resolved` is already a bare relative path (no base to join against here\n // — the base/scope is only known at the call site, see\n // `applyEnrichContentFile`). A single-arg `joinURL(resolved)` is a no-op\n // passthrough, so return the string directly instead of the misleading\n // decorative call.\n return resolved;\n}\n\n/**\n * Perform the `enrich.contentFile` second hop: resolve the sidecar path\n * template against the item JSON (via {@link resolveEnrichPath}), read it\n * through the AFS handle, truncate to `maxChars`, and append the result to\n * `baseSummary`. This is the real two-hop read the field-map engine promises\n * (read item.json → derive a path → read the sidecar) — kept AFS-Only: the\n * only I/O here is `afs.read()`.\n *\n * Fails soft in every case — returns `baseSummary` unchanged when:\n * - `enrich.contentFile` is not configured\n * - the path template can't be resolved (missing placeholder value, or `..`\n * traversal after resolution — {@link resolveEnrichPath} returns null)\n * - the sidecar doesn't exist or isn't readable (`afs.read` throws or\n * returns no string content)\n *\n * Path assembly uses the real two-argument `joinURL(base, relative)` form\n * (not the single-arg passthrough) so the scope and the resolved relative\n * path are joined with correct slash normalization.\n */\nexport async function applyEnrichContentFile(params: {\n /** AFS handle used for the second-hop read. Only `read()` is used (AFS-Only). */\n afs: Pick<AFSRoot, \"read\">;\n /** The item JSON the field map was extracted from (and enrich path is templated against). */\n itemJson: Record<string, unknown>;\n /** Summary text already extracted via `extractSummaryText`, to append to. */\n baseSummary: string;\n /** The domain's `enrich` config (from `BlockletIndexDomain.enrich`). */\n enrich: IndexEnrich | undefined;\n /** Base path the resolved relative sidecar path is joined against (e.g. the domain's `scope`, \"/user\"). */\n scope?: string;\n}): Promise<string> {\n const { afs, itemJson, baseSummary, enrich, scope } = params;\n const contentFile = enrich?.contentFile;\n if (!contentFile) return baseSummary;\n\n const relative = resolveEnrichPath(contentFile.path, itemJson);\n if (!relative) return baseSummary;\n\n const sidecarPath = joinURL(scope ?? \"/\", relative);\n\n try {\n const result = await afs.read?.(sidecarPath);\n const content = result?.data?.content;\n if (typeof content !== \"string\" || content.length === 0) return baseSummary;\n\n const enriched =\n contentFile.maxChars != null ? truncateToMaxChars(content, contentFile.maxChars) : content;\n\n return baseSummary ? `${baseSummary}\\n${enriched}` : enriched;\n } catch {\n return baseSummary;\n }\n}\n\n/**\n * Truncate a string to at most `maxChars` characters.\n * Truncation happens at a word boundary if possible, otherwise at the hard limit.\n */\nexport function truncateToMaxChars(text: string, maxChars: number): string {\n if (text.length <= maxChars) return text;\n const cut = text.slice(0, maxChars);\n const lastSpace = cut.lastIndexOf(\" \");\n return lastSpace > maxChars * 0.8 ? cut.slice(0, lastSpace) : cut;\n}\n"],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+
2
+ //#region src/extraction/manager.ts
3
+ var ExtractorManager = class {
4
+ registrations = /* @__PURE__ */ new Map();
5
+ /**
6
+ * Register an extractor for a domain.
7
+ * @param domain - Domain name this extractor serves
8
+ * @param registration - Extractor configuration
9
+ */
10
+ register(domain, registration) {
11
+ this.registrations.set(domain, registration);
12
+ }
13
+ /**
14
+ * Unregister an extractor for a domain.
15
+ */
16
+ unregister(domain) {
17
+ this.registrations.delete(domain);
18
+ }
19
+ /**
20
+ * Find the first extractor whose scope matches the given path.
21
+ * @returns [domainName, registration] or undefined if no match
22
+ */
23
+ findForPath(path) {
24
+ for (const [domain, reg] of this.registrations) if (path.startsWith(reg.scope)) return [domain, reg];
25
+ }
26
+ /**
27
+ * Dispatch extraction for a path using the matching extractor.
28
+ * Calls the extractor's AFS exec endpoint and returns the result.
29
+ *
30
+ * @returns ExtractionResult or null if no extractor matched or extraction failed
31
+ */
32
+ async extract(path, root) {
33
+ const match = this.findForPath(path);
34
+ if (!match) return null;
35
+ const [domain, reg] = match;
36
+ if (!reg.extractor || !root.exec) return null;
37
+ try {
38
+ const execResult = await root.exec(reg.extractor, { path }, {});
39
+ if (!execResult.success || !execResult.data) return null;
40
+ const data = execResult.data;
41
+ return {
42
+ domain,
43
+ result: {
44
+ anchors: data.anchors ?? [],
45
+ summary: data.summary,
46
+ embedding: data.embedding
47
+ }
48
+ };
49
+ } catch {
50
+ return null;
51
+ }
52
+ }
53
+ /**
54
+ * List all registered extractors.
55
+ */
56
+ list() {
57
+ return this.registrations;
58
+ }
59
+ /**
60
+ * Check if any extractors are registered.
61
+ */
62
+ get hasRegistrations() {
63
+ return this.registrations.size > 0;
64
+ }
65
+ /**
66
+ * Clear all registrations.
67
+ */
68
+ clear() {
69
+ this.registrations.clear();
70
+ }
71
+ };
72
+
73
+ //#endregion
74
+ exports.ExtractorManager = ExtractorManager;
@@ -0,0 +1,47 @@
1
+ import { ExtractionResult, ExtractorRegistration } from "../types.cjs";
2
+ import { AFSRoot } from "@aigne/afs";
3
+
4
+ //#region src/extraction/manager.d.ts
5
+ declare class ExtractorManager {
6
+ private readonly registrations;
7
+ /**
8
+ * Register an extractor for a domain.
9
+ * @param domain - Domain name this extractor serves
10
+ * @param registration - Extractor configuration
11
+ */
12
+ register(domain: string, registration: ExtractorRegistration): void;
13
+ /**
14
+ * Unregister an extractor for a domain.
15
+ */
16
+ unregister(domain: string): void;
17
+ /**
18
+ * Find the first extractor whose scope matches the given path.
19
+ * @returns [domainName, registration] or undefined if no match
20
+ */
21
+ findForPath(path: string): [string, ExtractorRegistration] | undefined;
22
+ /**
23
+ * Dispatch extraction for a path using the matching extractor.
24
+ * Calls the extractor's AFS exec endpoint and returns the result.
25
+ *
26
+ * @returns ExtractionResult or null if no extractor matched or extraction failed
27
+ */
28
+ extract(path: string, root: AFSRoot): Promise<{
29
+ domain: string;
30
+ result: ExtractionResult;
31
+ } | null>;
32
+ /**
33
+ * List all registered extractors.
34
+ */
35
+ list(): ReadonlyMap<string, ExtractorRegistration>;
36
+ /**
37
+ * Check if any extractors are registered.
38
+ */
39
+ get hasRegistrations(): boolean;
40
+ /**
41
+ * Clear all registrations.
42
+ */
43
+ clear(): void;
44
+ }
45
+ //#endregion
46
+ export { ExtractorManager };
47
+ //# sourceMappingURL=manager.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manager.d.cts","names":[],"sources":["../../src/extraction/manager.ts"],"mappings":";;;;cAUa,gBAAA;EAAA,iBACM,aAAA;EAwCd;;;;;EAjCH,QAAA,CAAS,MAAA,UAAgB,YAAA,EAAc,qBAAA;EAAvC;;;EAOA,UAAA,CAAW,MAAA;EAAX;;;;EAQA,WAAA,CAAY,IAAA,oBAAwB,qBAAA;EAe9B;;;;;;EAAA,OAAA,CACJ,IAAA,UACA,IAAA,EAAM,OAAA,GACL,OAAA;IAAU,MAAA;IAAgB,MAAA,EAAQ,gBAAA;EAAA;EAgCT;;;EAA5B,IAAA,CAAA,GAAQ,WAAA,SAAoB,qBAAA;EAcvB;;;EAAA,IAPD,gBAAA,CAAA;;;;EAOJ,KAAA,CAAA;AAAA"}
@@ -0,0 +1,47 @@
1
+ import { ExtractionResult, ExtractorRegistration } from "../types.mjs";
2
+ import { AFSRoot } from "@aigne/afs";
3
+
4
+ //#region src/extraction/manager.d.ts
5
+ declare class ExtractorManager {
6
+ private readonly registrations;
7
+ /**
8
+ * Register an extractor for a domain.
9
+ * @param domain - Domain name this extractor serves
10
+ * @param registration - Extractor configuration
11
+ */
12
+ register(domain: string, registration: ExtractorRegistration): void;
13
+ /**
14
+ * Unregister an extractor for a domain.
15
+ */
16
+ unregister(domain: string): void;
17
+ /**
18
+ * Find the first extractor whose scope matches the given path.
19
+ * @returns [domainName, registration] or undefined if no match
20
+ */
21
+ findForPath(path: string): [string, ExtractorRegistration] | undefined;
22
+ /**
23
+ * Dispatch extraction for a path using the matching extractor.
24
+ * Calls the extractor's AFS exec endpoint and returns the result.
25
+ *
26
+ * @returns ExtractionResult or null if no extractor matched or extraction failed
27
+ */
28
+ extract(path: string, root: AFSRoot): Promise<{
29
+ domain: string;
30
+ result: ExtractionResult;
31
+ } | null>;
32
+ /**
33
+ * List all registered extractors.
34
+ */
35
+ list(): ReadonlyMap<string, ExtractorRegistration>;
36
+ /**
37
+ * Check if any extractors are registered.
38
+ */
39
+ get hasRegistrations(): boolean;
40
+ /**
41
+ * Clear all registrations.
42
+ */
43
+ clear(): void;
44
+ }
45
+ //#endregion
46
+ export { ExtractorManager };
47
+ //# sourceMappingURL=manager.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manager.d.mts","names":[],"sources":["../../src/extraction/manager.ts"],"mappings":";;;;cAUa,gBAAA;EAAA,iBACM,aAAA;EAwCd;;;;;EAjCH,QAAA,CAAS,MAAA,UAAgB,YAAA,EAAc,qBAAA;EAAvC;;;EAOA,UAAA,CAAW,MAAA;EAAX;;;;EAQA,WAAA,CAAY,IAAA,oBAAwB,qBAAA;EAe9B;;;;;;EAAA,OAAA,CACJ,IAAA,UACA,IAAA,EAAM,OAAA,GACL,OAAA;IAAU,MAAA;IAAgB,MAAA,EAAQ,gBAAA;EAAA;EAgCT;;;EAA5B,IAAA,CAAA,GAAQ,WAAA,SAAoB,qBAAA;EAcvB;;;EAAA,IAPD,gBAAA,CAAA;;;;EAOJ,KAAA,CAAA;AAAA"}