@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.
- package/LICENSE.md +26 -0
- package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs +11 -0
- package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs +10 -0
- package/dist/backend/d1-index-backend.cjs +656 -0
- package/dist/backend/d1-index-backend.d.cts +149 -0
- package/dist/backend/d1-index-backend.d.cts.map +1 -0
- package/dist/backend/d1-index-backend.d.mts +149 -0
- package/dist/backend/d1-index-backend.d.mts.map +1 -0
- package/dist/backend/d1-index-backend.mjs +657 -0
- package/dist/backend/d1-index-backend.mjs.map +1 -0
- package/dist/backend/d1-vector-backend.cjs +83 -0
- package/dist/backend/d1-vector-backend.d.cts +21 -0
- package/dist/backend/d1-vector-backend.d.cts.map +1 -0
- package/dist/backend/d1-vector-backend.d.mts +21 -0
- package/dist/backend/d1-vector-backend.d.mts.map +1 -0
- package/dist/backend/d1-vector-backend.mjs +84 -0
- package/dist/backend/d1-vector-backend.mjs.map +1 -0
- package/dist/backend/memory-backend.cjs +304 -0
- package/dist/backend/memory-backend.d.cts +66 -0
- package/dist/backend/memory-backend.d.cts.map +1 -0
- package/dist/backend/memory-backend.d.mts +66 -0
- package/dist/backend/memory-backend.d.mts.map +1 -0
- package/dist/backend/memory-backend.mjs +305 -0
- package/dist/backend/memory-backend.mjs.map +1 -0
- package/dist/backend/prefix-range.cjs +25 -0
- package/dist/backend/prefix-range.mjs +25 -0
- package/dist/backend/prefix-range.mjs.map +1 -0
- package/dist/backend/schema.cjs +187 -0
- package/dist/backend/schema.mjs +188 -0
- package/dist/backend/schema.mjs.map +1 -0
- package/dist/backend/sqlite-backend.cjs +889 -0
- package/dist/backend/sqlite-backend.d.cts +96 -0
- package/dist/backend/sqlite-backend.d.cts.map +1 -0
- package/dist/backend/sqlite-backend.d.mts +96 -0
- package/dist/backend/sqlite-backend.d.mts.map +1 -0
- package/dist/backend/sqlite-backend.mjs +890 -0
- package/dist/backend/sqlite-backend.mjs.map +1 -0
- package/dist/backend/types.d.cts +209 -0
- package/dist/backend/types.d.cts.map +1 -0
- package/dist/backend/types.d.mts +209 -0
- package/dist/backend/types.d.mts.map +1 -0
- package/dist/backend/upstash-vector-backend.cjs +98 -0
- package/dist/backend/upstash-vector-backend.d.cts +21 -0
- package/dist/backend/upstash-vector-backend.d.cts.map +1 -0
- package/dist/backend/upstash-vector-backend.d.mts +21 -0
- package/dist/backend/upstash-vector-backend.d.mts.map +1 -0
- package/dist/backend/upstash-vector-backend.mjs +98 -0
- package/dist/backend/upstash-vector-backend.mjs.map +1 -0
- package/dist/backend/vector-backend.d.cts +29 -0
- package/dist/backend/vector-backend.d.cts.map +1 -0
- package/dist/backend/vector-backend.d.mts +29 -0
- package/dist/backend/vector-backend.d.mts.map +1 -0
- package/dist/dirty-queue.d.cts +41 -0
- package/dist/dirty-queue.d.cts.map +1 -0
- package/dist/dirty-queue.d.mts +41 -0
- package/dist/dirty-queue.d.mts.map +1 -0
- package/dist/embed.cjs +45 -0
- package/dist/embed.d.cts +20 -0
- package/dist/embed.d.cts.map +1 -0
- package/dist/embed.d.mts +20 -0
- package/dist/embed.d.mts.map +1 -0
- package/dist/embed.mjs +46 -0
- package/dist/embed.mjs.map +1 -0
- package/dist/extraction/defaults.cjs +242 -0
- package/dist/extraction/defaults.d.cts +14 -0
- package/dist/extraction/defaults.d.cts.map +1 -0
- package/dist/extraction/defaults.d.mts +14 -0
- package/dist/extraction/defaults.d.mts.map +1 -0
- package/dist/extraction/defaults.mjs +242 -0
- package/dist/extraction/defaults.mjs.map +1 -0
- package/dist/extraction/field-map.cjs +162 -0
- package/dist/extraction/field-map.d.cts +70 -0
- package/dist/extraction/field-map.d.cts.map +1 -0
- package/dist/extraction/field-map.d.mts +70 -0
- package/dist/extraction/field-map.d.mts.map +1 -0
- package/dist/extraction/field-map.mjs +157 -0
- package/dist/extraction/field-map.mjs.map +1 -0
- package/dist/extraction/manager.cjs +74 -0
- package/dist/extraction/manager.d.cts +47 -0
- package/dist/extraction/manager.d.cts.map +1 -0
- package/dist/extraction/manager.d.mts +47 -0
- package/dist/extraction/manager.d.mts.map +1 -0
- package/dist/extraction/manager.mjs +74 -0
- package/dist/extraction/manager.mjs.map +1 -0
- package/dist/extraction/path-anchors.cjs +86 -0
- package/dist/extraction/path-anchors.d.cts +72 -0
- package/dist/extraction/path-anchors.d.cts.map +1 -0
- package/dist/extraction/path-anchors.d.mts +72 -0
- package/dist/extraction/path-anchors.d.mts.map +1 -0
- package/dist/extraction/path-anchors.mjs +86 -0
- package/dist/extraction/path-anchors.mjs.map +1 -0
- package/dist/follower-scheduler.cjs +92 -0
- package/dist/follower-scheduler.d.cts +58 -0
- package/dist/follower-scheduler.d.cts.map +1 -0
- package/dist/follower-scheduler.d.mts +58 -0
- package/dist/follower-scheduler.d.mts.map +1 -0
- package/dist/follower-scheduler.mjs +92 -0
- package/dist/follower-scheduler.mjs.map +1 -0
- package/dist/follower.cjs +577 -0
- package/dist/follower.d.cts +241 -0
- package/dist/follower.d.cts.map +1 -0
- package/dist/follower.d.mts +241 -0
- package/dist/follower.d.mts.map +1 -0
- package/dist/follower.mjs +571 -0
- package/dist/follower.mjs.map +1 -0
- package/dist/index-provider.cjs +1166 -0
- package/dist/index-provider.d.cts +175 -0
- package/dist/index-provider.d.cts.map +1 -0
- package/dist/index-provider.d.mts +175 -0
- package/dist/index-provider.d.mts.map +1 -0
- package/dist/index-provider.mjs +1167 -0
- package/dist/index-provider.mjs.map +1 -0
- package/dist/index.cjs +53 -0
- package/dist/index.d.cts +22 -0
- package/dist/index.d.mts +22 -0
- package/dist/index.mjs +20 -0
- package/dist/keys/exact.cjs +20 -0
- package/dist/keys/exact.mjs +20 -0
- package/dist/keys/exact.mjs.map +1 -0
- package/dist/keys/location.cjs +13 -0
- package/dist/keys/location.mjs +13 -0
- package/dist/keys/location.mjs.map +1 -0
- package/dist/keys/normalized.cjs +62 -0
- package/dist/keys/normalized.mjs +63 -0
- package/dist/keys/normalized.mjs.map +1 -0
- package/dist/keys/person.cjs +15 -0
- package/dist/keys/person.mjs +15 -0
- package/dist/keys/person.mjs.map +1 -0
- package/dist/keys/registry.cjs +45 -0
- package/dist/keys/registry.d.cts +15 -0
- package/dist/keys/registry.d.cts.map +1 -0
- package/dist/keys/registry.d.mts +15 -0
- package/dist/keys/registry.d.mts.map +1 -0
- package/dist/keys/registry.mjs +46 -0
- package/dist/keys/registry.mjs.map +1 -0
- package/dist/keys/time.cjs +128 -0
- package/dist/keys/time.mjs +128 -0
- package/dist/keys/time.mjs.map +1 -0
- package/dist/keys/topic.cjs +23 -0
- package/dist/keys/topic.mjs +23 -0
- package/dist/keys/topic.mjs.map +1 -0
- package/dist/manifest-index.cjs +29 -0
- package/dist/manifest-index.d.cts +24 -0
- package/dist/manifest-index.d.cts.map +1 -0
- package/dist/manifest-index.d.mts +24 -0
- package/dist/manifest-index.d.mts.map +1 -0
- package/dist/manifest-index.mjs +28 -0
- package/dist/manifest-index.mjs.map +1 -0
- package/dist/offline-rebuild.cjs +48 -0
- package/dist/offline-rebuild.d.cts +57 -0
- package/dist/offline-rebuild.d.cts.map +1 -0
- package/dist/offline-rebuild.d.mts +57 -0
- package/dist/offline-rebuild.d.mts.map +1 -0
- package/dist/offline-rebuild.mjs +49 -0
- package/dist/offline-rebuild.mjs.map +1 -0
- package/dist/query/engine.cjs +433 -0
- package/dist/query/engine.d.cts +34 -0
- package/dist/query/engine.d.cts.map +1 -0
- package/dist/query/engine.d.mts +34 -0
- package/dist/query/engine.d.mts.map +1 -0
- package/dist/query/engine.mjs +433 -0
- package/dist/query/engine.mjs.map +1 -0
- package/dist/query/limits.cjs +12 -0
- package/dist/query/limits.mjs +12 -0
- package/dist/query/limits.mjs.map +1 -0
- package/dist/text/cjk.cjs +30 -0
- package/dist/text/cjk.d.cts +24 -0
- package/dist/text/cjk.d.cts.map +1 -0
- package/dist/text/cjk.d.mts +24 -0
- package/dist/text/cjk.d.mts.map +1 -0
- package/dist/text/cjk.mjs +30 -0
- package/dist/text/cjk.mjs.map +1 -0
- package/dist/text/fts.cjs +244 -0
- package/dist/text/fts.mjs +245 -0
- package/dist/text/fts.mjs.map +1 -0
- package/dist/text/host.cjs +39 -0
- package/dist/text/host.mjs +38 -0
- package/dist/text/host.mjs.map +1 -0
- package/dist/types.cjs +44 -0
- package/dist/types.d.cts +393 -0
- package/dist/types.d.cts.map +1 -0
- package/dist/types.d.mts +393 -0
- package/dist/types.d.mts.map +1 -0
- package/dist/types.mjs +42 -0
- package/dist/types.mjs.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-provider.mjs","names":[],"sources":["../src/index-provider.ts"],"sourcesContent":["/**\n * AFS Index Provider — unified structured indexing.\n *\n * Combines three complementary indexing modes:\n * - Anchor indexing: structured semantic dimensions, deterministic matching\n * - Full-text search: keyword matching on summaries\n * - Embedding search: semantic fallback when anchor+FTS insufficient\n */\nimport type {\n AFSAccessMode,\n AFSEntry,\n AFSExecResult,\n AFSExplainResult,\n AFSListResult,\n AFSRoot,\n AFSSearchOptions,\n AFSSearchResult,\n AFSUnsubscribe,\n ProviderManifest,\n} from \"@aigne/afs\";\n// makeNsLog comes from @aigne/afs (ambient-based, browser/CF-safe) — NOT\n// @aigne/afs-logger (its ALS variant pulls node:async_hooks into any bundle\n// that includes this provider). Same seam the follower already logs under.\nimport { explainZodError, makeNsLog } from \"@aigne/afs\";\nimport {\n Actions,\n AFSBaseProvider,\n Explain,\n List,\n Meta,\n Read,\n type RouteContext,\n Search,\n} from \"@aigne/afs/provider\";\nimport { joinURL } from \"ufo\";\nimport { z } from \"zod/v4\";\nimport { SQLiteIndexBackend } from \"./backend/sqlite-backend.js\";\nimport { createEmbedder } from \"./embed.js\";\nimport { extractFromMeta } from \"./extraction/defaults.js\";\nimport { ExtractorManager } from \"./extraction/manager.js\";\nimport type { IndexFollower } from \"./follower.js\";\nimport { createNormalizedValueGenerator } from \"./keys/normalized.js\";\nimport { KeyGeneratorRegistry } from \"./keys/registry.js\";\nimport { generateTimeKeys } from \"./keys/time.js\";\nimport { QueryEngine } from \"./query/engine.js\";\nimport type {\n AFSIndexOptions,\n Anchor,\n AnchorTypeDefinition,\n DomainDefinition,\n ExtractorHint,\n IndexConfig,\n IndexedEntry,\n KeyGenerator,\n QueryPage,\n QueryResult,\n SearchMode,\n} from \"./types.js\";\nimport { DEFAULT_SEARCH_MODE, SEARCH_MODES } from \"./types.js\";\n\nconst log = makeNsLog(\"provider:index\");\n\n// --- Zod schemas for action args validation ---\n\nconst IndexArgsSchema = z.object({\n entryPath: z.string(),\n domain: z.string().optional(),\n anchors: z.array(z.object({ type: z.string(), confidence: z.number() }).passthrough()).optional(),\n summary: z.string().optional(),\n /** Optimized text for embedding (e.g., user messages only). Falls back to summary. */\n embeddingSummary: z.string().optional(),\n embedding: z.array(z.number()).optional(),\n sourcePath: z.string().optional(),\n sourceHash: z.string().optional(),\n extractorId: z.string().optional(),\n meta: z.record(z.string(), z.unknown()).optional(),\n});\n\nconst QueryArgsSchema = z.object({\n mode: z.enum(SEARCH_MODES).optional(),\n /** FTS token-joining semantics. \"and\" (default) = precision. \"or\" = recall. */\n ftsMode: z.enum([\"and\", \"or\"]).optional(),\n anchors: z.array(z.object({ type: z.string(), values: z.array(z.string()) })).optional(),\n text: z.string().optional(),\n /** Original natural-language text for embedding (if different from FTS-formatted `text`). */\n embeddingText: z.string().optional(),\n embedding: z.array(z.number()).optional(),\n /**\n * Optional query rewriter. When set, the provider generates additional\n * query variants via the chat provider and fuses their result lists.\n * - \"none\" (default): single-query path, no LLM call.\n * - \"multi-query\": 3 paraphrases + original, RRF-fused. Index-diverse\n * bench shows 2.6× MRR lift on paraphrase queries; abstract regresses\n * slightly. Costs 1 chat + 4 embed + 4 query calls per user query.\n * Requires `chatProvider` (or `llmProvider`) on the provider config,\n * and ignored if the caller passes `embedding` directly.\n */\n rewriter: z.enum([\"none\", \"multi-query\"]).optional(),\n opts: z\n .object({\n scope: z.string().optional(),\n /** entry_path prefix filter, pushed before LIMIT — see `QueryOpts.pathPrefix`. */\n pathPrefix: z.string().optional(),\n limit: z.number().optional(),\n cursor: z.string().optional(),\n minResults: z.number().optional(),\n minConfidence: z.number().optional(),\n metaFilter: z.record(z.string(), z.unknown()).optional(),\n /** Relevance floor for pure-embedding matches — see `QueryOpts.embeddingFloor`. */\n embeddingFloor: z.number().optional(),\n /**\n * Treat `anchors` as a HARD FILTER (constraint) rather than an RRF fusion\n * signal (Epic #932 S2/S3 facet search). See `QueryOpts.anchorFilter`.\n */\n anchorFilter: z.boolean().optional(),\n /**\n * When true: (1) the response carries an aggregate `explanation` object\n * alongside `results` so callers can answer \"why is this empty?\" without\n * manually tracing the SQL or guessing whether the embedding fallback ran\n * (see planning/memory-system-improvements/design.md §P4), AND (2) each\n * entry in `results` gets its own `explanation` string + (anchor matches)\n * `anchors` field describing \"why did THIS one match\" — which signal(s)\n * (anchor filter / FTS query / cosine similarity) contributed, their raw\n * sub-scores, and the RRF fusion math (issue #1069: \"为什么某条能被\n * 匹配/record,把原因 log 出来\"). Forwarded verbatim into `QueryEngine`'s\n * `QueryOpts.explain`. Default false: zero cost, byte-identical response.\n */\n explain: z.boolean().optional(),\n })\n .optional(),\n});\n\nconst ReindexArgsSchema = z.object({\n scope: z.string().optional(),\n extractorId: z.string().optional(),\n domain: z.string().optional(),\n});\n\nconst RegisterDomainArgsSchema = z.object({\n name: z.string(),\n types: z.array(z.object({ name: z.string() }).passthrough()).optional(),\n});\n\nconst GetArgsSchema = z.object({\n /**\n * Either a single entry path or a list. Lists are convenient for\n * RAG-style synthesis where the caller queried, took the top-K\n * results, and now wants the stored summaries for all of them in one\n * round-trip.\n */\n entryPath: z.string().optional(),\n entryPaths: z.array(z.string()).optional(),\n});\n\nconst CleanupArgsSchema = z.object({\n state: z.enum([\"stale\", \"removed\"]).optional().default(\"stale\"),\n domain: z.string().optional(),\n scope: z.string().optional(),\n});\n\n// NOT `.strict()`: the exec dispatcher injects framework keys (e.g.\n// `_scope_afs`) into every action's args, and a strict schema rejects them —\n// which made `verify` unreachable via exec (\"unknown key(s): _scope_afs\")\n// while the other (non-strict) actions passed. Default strip drops unknown\n// keys, matching get/reindex/cleanup and tolerating framework injection.\nconst VerifyArgsSchema = z.object({\n domain: z.string().optional(),\n scope: z.string().optional(),\n});\n\nconst DiscoverArgsSchema = z.object({\n providerPath: z.string(),\n domain: z.string().optional(),\n});\n\nconst afsIndexOptionsSchema = z.object({\n dbPath: z.string().optional(),\n defaultDomain: z.string().optional(),\n});\n\nexport class AFSIndex extends AFSBaseProvider {\n readonly name: string;\n readonly description?: string;\n override readonly accessMode: AFSAccessMode = \"readwrite\";\n\n private readonly options: AFSIndexOptions;\n private readonly keyRegistry = new KeyGeneratorRegistry();\n private backend!: import(\"./backend/types.js\").IndexBackend;\n private queryEngine!: QueryEngine;\n private readonly domains = new Map<string, DomainDefinition>();\n private readonly extractors = new ExtractorManager();\n private readonly unsubscribes: AFSUnsubscribe[] = [];\n private afsRoot?: AFSRoot;\n private initialized = false;\n private follower?: IndexFollower;\n private catchUpInflight?: Promise<void>;\n\n static schema() {\n return afsIndexOptionsSchema;\n }\n\n static manifest(): ProviderManifest {\n return {\n name: \"index\",\n description:\n \"Full-text search and anchor-based indexing for AFS entries.\\n- FTS5 full-text search on summaries\\n- Structured anchor indexing (time, person, topic, location)\\n- Combined query modes: anchor, fts, semantic\\n- Actions: index, query, reindex, cleanup\",\n uriTemplate: \"index://{dbPath+}\",\n category: \"composite\",\n schema: z.object({\n dbPath: z.string(),\n defaultDomain: z.string().optional(),\n }),\n tags: [\"index\", \"search\", \"fts5\", \"sqlite\", \"anchor\"],\n capabilityTags: [\"read-write\", \"search\", \"auth:none\", \"local\"],\n security: {\n riskLevel: \"local\",\n resourceAccess: [\"local-filesystem\"],\n dataSensitivity: [],\n notes: [\"Stores SQLite index database on local filesystem\"],\n },\n };\n }\n\n constructor(options: AFSIndexOptions = {}) {\n super();\n this.options = options;\n this.name = options.name ?? \"index\";\n this.description = options.description ?? \"AFS Index Provider — unified structured indexing\";\n if (options.timeKeyGranularity?.length) {\n const include = new Set(options.timeKeyGranularity);\n this.keyRegistry.register(\"time\", (anchor) => generateTimeKeys(anchor, include));\n }\n }\n\n private async ensureInitialized(): Promise<void> {\n if (this.initialized) return;\n this.backend =\n this.options.backend ??\n new SQLiteIndexBackend(this.options.dbPath ?? \":memory:\", this.keyRegistry, {\n ftsTokenizer: this.options.ftsTokenizer,\n });\n // Wire this provider's shared registry into the backend, including\n // backends injected via `options.backend` (e.g. CF's\n // `new D1IndexBackend(indexDb, scope)`) that otherwise default-construct\n // their own registry and never see discoverProvider()'s custom\n // anchor-type registrations — see `IndexBackend.setKeyRegistry`.\n this.backend.setKeyRegistry?.(this.keyRegistry);\n await this.backend.initialize();\n this.queryEngine = new QueryEngine(this.backend);\n this.initialized = true;\n }\n\n onMount(root: AFSRoot, _mountPath?: string): void {\n this.afsRoot = root;\n\n // Subscribe to write events from other providers for auto-indexing\n const unsubWrite = root.subscribe?.({ type: \"afs:write\" }, (event) => {\n this.handleWriteEvent(event.path);\n });\n if (unsubWrite) this.unsubscribes.push(unsubWrite);\n\n // Subscribe to delete events for cleanup\n const unsubDelete = root.subscribe?.({ type: \"afs:delete\" }, (event) => {\n this.handleDeleteEvent(event.path);\n });\n if (unsubDelete) this.unsubscribes.push(unsubDelete);\n\n // Subscribe to mount events for auto-discovery of .index/ conventions\n const unsubMount = root.subscribe?.({ type: \"afs:mount\" }, (event) => {\n // Skip self-discovery\n if (event.source === this.name) return;\n this.handleMountEvent(event.path);\n });\n if (unsubMount) this.unsubscribes.push(unsubMount);\n }\n\n private async handleMountEvent(providerPath: string): Promise<void> {\n try {\n if (!this.afsRoot) return;\n const domainName = providerPath.split(\"/\").filter(Boolean).pop() ?? \"unknown\";\n const result = await this.discoverProvider(providerPath, domainName);\n // Only keep the domain if discovery found something meaningful\n if (result.anchorTypes.length === 0 && !result.config && !result.extractor) {\n this.domains.delete(domainName);\n }\n } catch {\n // No .index/ convention or discovery failed — skip silently\n }\n }\n\n private async handleDeleteEvent(path: string): Promise<void> {\n try {\n await this.ensureInitialized();\n const entry = await this.backend.getEntry(path);\n if (!entry) return;\n await this.backend.removeAnchors(path);\n await this.backend.removeSummary(path);\n if (this.backend.removeEmbedding) {\n await this.backend.removeEmbedding(path);\n }\n await this.backend.removeEntry(path);\n } catch {\n // Event handler should not throw\n }\n }\n\n private async handleWriteEvent(path: string): Promise<void> {\n try {\n if (!this.afsRoot) return;\n\n await this.ensureInitialized();\n\n // 1. Try registered extractor first\n let domain: string;\n let result: import(\"./types.js\").ExtractionResult;\n\n const extracted = this.extractors.hasRegistrations\n ? await this.extractors.extract(path, this.afsRoot)\n : null;\n\n if (extracted) {\n domain = extracted.domain;\n result = extracted.result;\n } else {\n // 2. Fallback: default metadata-based extraction\n const readResult = await this.afsRoot.read?.(path);\n if (!readResult?.data?.meta) return;\n const entry = readResult.data;\n\n const defaultResult = extractFromMeta(path, entry.meta as Record<string, unknown>);\n if (!defaultResult) return;\n\n domain = this.options.defaultDomain ?? \"default\";\n result = defaultResult;\n }\n\n // Apply .index/config filters (indexable/skip patterns)\n if (!this.shouldIndex(path, domain)) return;\n\n const now = new Date().toISOString();\n\n await this.backend.writeEntry({\n entryPath: path,\n sourcePath: path,\n domain,\n state: \"active\",\n indexedAt: now,\n updatedAt: now,\n anchorCount: result.anchors.length,\n });\n\n if (result.anchors.length > 0) {\n await this.backend.writeAnchors(path, result.anchors);\n }\n if (result.summary) {\n await this.backend.writeSummary(path, result.summary);\n }\n\n // Store embedding — from extractor or auto-generated via LLM\n if (this.backend.writeEmbedding) {\n const embedding = result.embedding ?? (await this.autoEmbed(result.summary));\n if (embedding) {\n await this.backend.writeEmbedding(path, embedding);\n }\n }\n } catch {\n // Event handler should not throw\n }\n }\n\n async close(): Promise<void> {\n // Unsubscribe from all events\n for (const unsub of this.unsubscribes) {\n unsub();\n }\n this.unsubscribes.length = 0;\n this.extractors.clear();\n\n if (this.initialized) {\n await this.backend.close();\n this.initialized = false;\n }\n }\n\n // --- Read root (required for mount check) ---\n\n @Read(\"/\")\n async readRoot(ctx: RouteContext): Promise<AFSEntry | undefined> {\n return this.buildEntry(ctx.path, {\n content: this.description,\n meta: { kind: \"afs:directory\", childrenCount: 3 },\n });\n }\n\n // --- List handlers ---\n\n @List(\"/\")\n async listRoot(ctx: RouteContext): Promise<AFSListResult> {\n const entries: AFSEntry[] = [\n this.buildEntry(joinURL(ctx.path, \"domains\"), {\n meta: { kind: \"afs:directory\", childrenCount: this.domains.size },\n }),\n this.buildEntry(joinURL(ctx.path, \"stats\"), {\n meta: { kind: \"afs:directory\", childrenCount: 3 },\n }),\n this.buildEntry(joinURL(ctx.path, \"config\"), {\n meta: { kind: \"afs:directory\", childrenCount: 3 },\n }),\n ];\n return { data: entries };\n }\n\n @Actions(\"/\")\n async listActions(ctx: RouteContext): Promise<AFSListResult> {\n const actions = [\n \"index\",\n \"query\",\n \"get\",\n \"reindex\",\n \"cleanup\",\n \"verify\",\n \"register-domain\",\n \"discover\",\n ];\n // exec-auth-gate P2: query/get/discover/verify are read-only (verify only\n // aggregates listEntries stats); the rest mutate the index.\n const READ_ACTIONS = new Set([\"query\", \"get\", \"discover\", \"verify\"]);\n const entries = actions.map((name) =>\n this.buildEntry(joinURL(ctx.path, name), {\n id: name,\n meta: {\n kind: \"afs:executable\",\n effect: READ_ACTIONS.has(name) ? \"read\" : \"write\",\n },\n }),\n );\n return { data: entries };\n }\n\n @Read(\"/domains\")\n readDomains(ctx: RouteContext): AFSEntry | undefined {\n return this.buildEntry(ctx.path, {\n meta: { kind: \"afs:directory\", childrenCount: this.domains.size },\n });\n }\n\n @Read(\"/stats\")\n readStatsDir(ctx: RouteContext): AFSEntry | undefined {\n return this.buildEntry(ctx.path, {\n meta: { kind: \"afs:directory\", childrenCount: 3 },\n });\n }\n\n @Read(\"/config\")\n readConfigDir(ctx: RouteContext): AFSEntry | undefined {\n return this.buildEntry(ctx.path, {\n meta: { kind: \"afs:directory\", childrenCount: 3 },\n });\n }\n\n @List(\"/domains\")\n async listDomains(ctx: RouteContext): Promise<AFSListResult> {\n const entries = Array.from(this.domains.values()).map((d) =>\n this.buildEntry(joinURL(ctx.path, d.name), {\n id: d.name,\n // `indexable` surfaces the declared `.index/config`/manifest config so\n // callers (and tests) can read back what was registered without\n // reaching into provider internals — see issue #934 verdict P1\n // (\"registerDomains 丢弃 config\").\n content: { types: d.types.map((t) => t.name), indexable: d.config?.indexable },\n }),\n );\n return { data: entries };\n }\n\n @List(\"/stats\")\n async listStatsDir(ctx: RouteContext): Promise<AFSListResult> {\n const keys = [\"total-entries\", \"active-entries\", \"stale-entries\"];\n const entries = keys.map((key) =>\n this.buildEntry(joinURL(ctx.path, key), {\n id: key,\n meta: { kind: \"afs:value\", childrenCount: 0 },\n }),\n );\n return { data: entries };\n }\n\n @List(\"/config\")\n listConfig(ctx: RouteContext): AFSListResult {\n const keys = [\"llm-provider\", \"embedding-enabled\", \"default-domain\"];\n const entries = keys.map((key) =>\n this.buildEntry(joinURL(ctx.path, key), {\n id: key,\n meta: { kind: \"afs:value\", childrenCount: 0 },\n }),\n );\n return { data: entries };\n }\n\n @List(\"/stats/:key\")\n listStatsKey(): AFSListResult {\n return { data: [] };\n }\n\n @List(\"/config/:key\")\n listConfigKey(): AFSListResult {\n return { data: [] };\n }\n\n @List(\"/domains/:name\")\n async listDomainName(ctx: RouteContext<{ name: string }>): Promise<AFSListResult> {\n await this.ensureInitialized();\n const entries = await this.backend.listEntries({ domain: ctx.params.name });\n return {\n data: entries.map((e) =>\n this.buildEntry(joinURL(ctx.path, encodeURIComponent(e.entryPath)), {\n id: e.entryPath,\n content: {\n entryPath: e.entryPath,\n sourcePath: e.sourcePath,\n state: e.state,\n anchorCount: e.anchorCount,\n indexedAt: e.indexedAt,\n updatedAt: e.updatedAt,\n },\n meta: {\n kind: \"index:entry\",\n domain: e.domain,\n state: e.state,\n anchorCount: e.anchorCount,\n childrenCount: 0,\n ...(e.meta ?? {}),\n },\n createdAt: new Date(e.indexedAt),\n updatedAt: new Date(e.updatedAt),\n }),\n ),\n };\n }\n\n // --- Meta handlers ---\n\n @Meta(\"/\")\n readRootMeta(ctx: RouteContext): AFSEntry | undefined {\n const meta = { kind: \"afs:directory\", childrenCount: 3 };\n return this.buildEntry(ctx.path, { content: meta, meta });\n }\n\n @Meta(\"/domains\")\n readDomainsMeta(ctx: RouteContext): AFSEntry | undefined {\n const meta = { kind: \"afs:directory\", childrenCount: this.domains.size };\n return this.buildEntry(ctx.path, { content: meta, meta });\n }\n\n @Meta(\"/stats\")\n readStatsMeta(ctx: RouteContext): AFSEntry | undefined {\n const meta = { kind: \"afs:directory\", childrenCount: 3 };\n return this.buildEntry(ctx.path, { content: meta, meta });\n }\n\n @Meta(\"/config\")\n readConfigMeta(ctx: RouteContext): AFSEntry | undefined {\n const meta = { kind: \"afs:directory\", childrenCount: 3 };\n return this.buildEntry(ctx.path, { content: meta, meta });\n }\n\n @Meta(\"/stats/:key\")\n readStatsKeyMeta(ctx: RouteContext): AFSEntry | undefined {\n const meta = { kind: \"afs:value\", childrenCount: 0 };\n return this.buildEntry(ctx.path, { content: meta, meta });\n }\n\n @Meta(\"/config/:key\")\n readConfigKeyMeta(ctx: RouteContext): AFSEntry | undefined {\n const meta = { kind: \"afs:value\", childrenCount: 0 };\n return this.buildEntry(ctx.path, { content: meta, meta });\n }\n\n @Meta(\"/domains/:name\")\n async readDomainNameMeta(ctx: RouteContext<{ name: string }>): Promise<AFSEntry | undefined> {\n await this.ensureInitialized();\n const count = await this.backend.countEntries({ domain: ctx.params.name });\n const meta = { kind: \"afs:directory\", childrenCount: count };\n return this.buildEntry(ctx.path, { content: meta, meta });\n }\n\n // --- Capabilities ---\n\n @Read(\"/.meta/.capabilities\")\n readCapabilities(ctx: RouteContext): AFSEntry | undefined {\n return this.buildEntry(ctx.path, {\n content: { operations: this.getOperationsDeclaration() },\n });\n }\n\n // --- Explain ---\n\n @Explain(\"/\")\n explainRoot(_ctx: RouteContext): AFSExplainResult {\n return {\n content: `AFS Index Provider — unified structured indexing.\\n\\nCombines anchor indexing, full-text search, and optional embedding search. Domains: ${this.domains.size} registered.`,\n format: \"text\",\n };\n }\n\n @Explain(\"/:path*\")\n explainPath(ctx: RouteContext<{ path: string }>): AFSExplainResult {\n return {\n content: `Index provider path: /${ctx.params.path}`,\n format: \"text\",\n };\n }\n\n // --- Search handler ---\n\n @Search(\"/:path*\")\n async searchHandler(\n _ctx: RouteContext<{ path?: string }>,\n query: string,\n options?: AFSSearchOptions,\n ): Promise<AFSSearchResult> {\n await this.ensureInitialized();\n\n try {\n const results = await this.queryEngine.query({\n mode: \"fts\",\n text: query,\n opts: { limit: options?.limit },\n });\n\n const entries = results.map((r) =>\n this.buildEntry(r.entryPath, {\n content: { matchType: r.matchType, score: r.score },\n }),\n );\n\n return { data: entries };\n } catch {\n return { data: [] };\n }\n }\n\n // --- Read handlers ---\n\n @Read(\"/config/:key\")\n async readConfig(ctx: RouteContext<{ key: string }>): Promise<AFSEntry | undefined> {\n switch (ctx.params.key) {\n case \"llm-provider\":\n return this.buildEntry(ctx.path, {\n content: { value: this.options.llmProvider ?? null },\n });\n case \"embedding-enabled\":\n return this.buildEntry(ctx.path, {\n content: { value: this.options.embeddingEnabled ?? false },\n });\n case \"default-domain\":\n return this.buildEntry(ctx.path, {\n content: { value: this.options.defaultDomain ?? \"default\" },\n });\n default:\n return undefined;\n }\n }\n\n @Read(\"/stats/:key\")\n async readStats(ctx: RouteContext<{ key: string }>): Promise<AFSEntry | undefined> {\n await this.ensureInitialized();\n\n switch (ctx.params.key) {\n case \"total-entries\": {\n const count = await this.backend.countEntries({});\n return this.buildEntry(ctx.path, { content: { value: count } });\n }\n case \"active-entries\": {\n const count = await this.backend.countEntries({ state: \"active\" });\n return this.buildEntry(ctx.path, { content: { value: count } });\n }\n case \"stale-entries\": {\n const count = await this.backend.countEntries({ state: \"stale\" });\n return this.buildEntry(ctx.path, { content: { value: count } });\n }\n default:\n return undefined;\n }\n }\n\n // --- Action execution ---\n\n @Actions.Exec(\"/\", \"index\")\n async execIndex(_ctx: RouteContext, args: Record<string, unknown>): Promise<AFSExecResult> {\n const parsed = IndexArgsSchema.safeParse(args);\n if (!parsed.success) {\n return {\n success: false,\n error: {\n code: \"VALIDATION_ERROR\",\n message: explainZodError(parsed.error, IndexArgsSchema),\n },\n };\n }\n await this.ensureInitialized();\n\n const {\n entryPath,\n anchors: rawAnchors,\n summary,\n embeddingSummary,\n embedding: explicitEmbedding,\n sourcePath,\n sourceHash,\n extractorId,\n meta,\n } = parsed.data;\n const domain = parsed.data.domain ?? this.options.defaultDomain ?? \"default\";\n const anchors = (rawAnchors as Anchor[]) ?? [];\n\n const now = new Date().toISOString();\n const entry: IndexedEntry = {\n entryPath,\n sourcePath,\n sourceHash,\n domain,\n state: \"active\",\n extractorId,\n indexedAt: now,\n updatedAt: now,\n anchorCount: anchors.length,\n meta,\n };\n\n await this.backend.writeEntry(entry);\n\n if (anchors.length > 0) {\n await this.backend.writeAnchors(entryPath, anchors);\n }\n\n if (summary) {\n await this.backend.writeSummary(entryPath, summary);\n }\n\n // Store embedding — explicit, or auto-generated via LLM.\n // Use embeddingSummary (optimized for embedding, e.g. user messages only) if available.\n if (this.backend.writeEmbedding) {\n const embedding = explicitEmbedding ?? (await this.autoEmbed(embeddingSummary ?? summary));\n if (embedding) {\n await this.backend.writeEmbedding(entryPath, embedding);\n }\n }\n\n return { success: true, data: { entryPath, anchorCount: anchors.length } };\n }\n\n @Actions.Exec(\"/\", \"query\")\n async execQuery(_ctx: RouteContext, args: Record<string, unknown>): Promise<AFSExecResult> {\n // Detect params placed at the top level that belong inside `opts`. Zod's\n // default strip silently drops them before safeParse, so we must check\n // the raw args here (adding .strict() would also reject the _scope_afs\n // key the exec dispatcher injects into every action's args).\n const OPTS_ONLY_KEYS = [\n \"limit\",\n \"cursor\",\n \"anchorFilter\",\n \"explain\",\n \"scope\",\n \"pathPrefix\",\n \"minConfidence\",\n \"metaFilter\",\n \"embeddingFloor\",\n \"minResults\",\n ] as const;\n const misplaced = OPTS_ONLY_KEYS.filter((k) => k in args);\n if (misplaced.length > 0) {\n return {\n success: false,\n error: {\n code: \"VALIDATION_ERROR\",\n message: `Parameter(s) ${misplaced.map((k) => `\"${k}\"`).join(\", \")} must be nested inside \"opts\" (e.g. { opts: { ${misplaced[0]}: ... } })`,\n },\n };\n }\n const parsed = QueryArgsSchema.safeParse(args);\n if (!parsed.success) {\n return {\n success: false,\n error: {\n code: \"VALIDATION_ERROR\",\n message: explainZodError(parsed.error, QueryArgsSchema),\n },\n };\n }\n await this.ensureInitialized();\n\n // Catch-up-on-query: index recent writes so results stay fresh without\n // waiting for the cron. When the runtime provides a background scheduler\n // (CF `ctx.waitUntil`), run it OFF the response path so the search returns\n // immediately from the current index — the caught-up rows surface on the\n // NEXT query, and the catch-up's subrequests no longer block/gate this\n // one (issue #1168). Without a scheduler (Node/standalone/tests, not\n // subrequest-capped) stay inline so THIS query sees the fresh rows.\n if (this.follower && !parsed.data.opts?.cursor) {\n const { task, started } = this.startCatchUpOnce();\n const schedule = this.options.scheduleBackground;\n if (schedule) {\n if (started) schedule(task);\n } else await task;\n }\n\n const { anchors, text, embeddingText, opts } = parsed.data;\n // Precedence: explicit per-query `mode` > deployment default\n // (`AFS_INDEX_SEARCH_MODE` → `options.defaultSearchMode`) > built-in\n // `DEFAULT_SEARCH_MODE` (\"combined\"). The generic `@Search` route is\n // intentionally NOT affected — it stays pinned to \"fts\".\n const mode = parsed.data.mode ?? this.options.defaultSearchMode ?? DEFAULT_SEARCH_MODE;\n const ftsMode = parsed.data.ftsMode;\n const rewriter = parsed.data.rewriter ?? \"none\";\n if (rewriter !== \"none\" && opts?.cursor) {\n return {\n success: false,\n error: {\n code: \"VALIDATION_ERROR\",\n message: \"Query cursor is not supported with rewriter mode\",\n },\n };\n }\n\n // Multi-query rewriting: generate paraphrases of the user's text,\n // run a separate query per variant, RRF-fuse the result lists.\n // Skipped if the caller passed an explicit embedding (they're\n // bypassing auto-embed and presumably know what they want), if no\n // text was provided, or if the chat provider isn't configured.\n if (\n rewriter === \"multi-query\" &&\n !parsed.data.embedding &&\n (text || embeddingText) &&\n this.resolveChatProvider()\n ) {\n const original = embeddingText ?? text!;\n const fused = await this.queryWithMultiQuery({\n original,\n ftsText: text,\n mode,\n anchors,\n opts,\n });\n return { success: true, data: { results: fused } };\n }\n\n // Auto-generate query embedding if semantic mode requested but no embedding provided.\n // Use embeddingText (original natural language) if available, falling back to text (may be FTS-formatted).\n const embedding =\n parsed.data.embedding ??\n (mode === \"semantic\" ? await this.autoEmbed(embeddingText ?? text, \"query\") : null);\n\n // `boundDomains` (issue #936 P4 leak fix) — a per-caller `AFSIndex`\n // handle (e.g. Node's `/user/index`, composite domain `{name}:{scope}`)\n // is constructed with the caller's own domain names bound so a query\n // that doesn't pass an explicit `opts.scope` still can't scan another\n // caller's rows sharing the same physical backend. `undefined` here\n // (the default, every existing caller) is a strict no-op — see\n // `AFSIndexOptions.boundDomains`.\n let page: QueryPage;\n try {\n page = await this.queryEngine.queryPage({\n mode,\n ftsMode,\n anchors,\n text,\n embedding: embedding ?? undefined,\n // Inject the provider's configured default relevance floor when the caller\n // didn't pass one — the floor is model-specific, so it's wired where the\n // embed model is (Node daemon / CF session-view), not by every caller.\n opts:\n this.options.embeddingFloor != null && opts?.embeddingFloor == null\n ? { ...opts, embeddingFloor: this.options.embeddingFloor }\n : opts,\n boundDomains: this.options.boundDomains,\n });\n } catch (err) {\n if (err instanceof Error && err.message === \"Invalid or stale query cursor\") {\n return {\n success: false,\n error: { code: \"VALIDATION_ERROR\", message: err.message },\n };\n }\n throw err;\n }\n const { results } = page;\n\n if (opts?.explain) {\n const explanation = await this.buildExplanation({\n mode,\n text,\n embeddingText,\n anchors,\n opts,\n embeddingUsed: embedding !== null && embedding !== undefined,\n results,\n });\n // Issue #1069: also emit the per-result \"why did this match\" reasons to\n // the server structured log, so an operator can grep the whole causal\n // chain by requestId/userDid (docs/guides/logging.md) — the response\n // `explanation` field alone only reaches clients that choose to surface\n // it (e.g. an MCP/agent caller has nowhere to show it). `debug`\n // self-guards on the `provider:index` namespace (default off, open with\n // `ARC_LOG=provider:index`), and this whole branch is already gated on\n // the caller's `explain` opt-in — zero cost for normal queries.\n log.debug(\"query explain\", {\n ...explanation,\n results: results.map((r) => ({\n path: r.entryPath,\n matchType: r.matchType,\n score: r.score,\n explanation: r.explanation,\n })),\n });\n return {\n success: true,\n data: { results, explanation, ...(page.meta ? { meta: page.meta } : {}) },\n };\n }\n return { success: true, data: { results, ...(page.meta ? { meta: page.meta } : {}) } };\n }\n\n /**\n * Build a \"why did the query return what it returned\" diagnostic.\n *\n * Cheap probes only: scope cardinality (countEntries), result-type\n * breakdown (already in the result list), echo of the parsed inputs.\n * Does not re-run the query — purely additive per planning doc §P4.\n */\n private async buildExplanation(args: {\n mode: SearchMode;\n text?: string;\n embeddingText?: string;\n anchors?: Array<{ type: string; values: string[] }>;\n opts?: {\n scope?: string;\n limit?: number;\n metaFilter?: Record<string, unknown>;\n };\n embeddingUsed: boolean;\n results: Array<{ entryPath: string; matchType: string; score: number }>;\n }): Promise<Record<string, unknown>> {\n const { mode, text, embeddingText, anchors, opts, embeddingUsed, results } = args;\n\n // Total entries available in the scope/domain — answers \"is the\n // index empty for this scope?\" which is the most common reason recall\n // returns empty.\n const scopeFilter = opts?.scope;\n const domain = scopeFilter ? scopeFilter.replace(/^\\/+/, \"\") : undefined;\n let totalIndexedInScope: number | undefined;\n try {\n totalIndexedInScope = await this.backend.countEntries({\n ...(domain ? { domain } : {}),\n ...(scopeFilter ? { scope: scopeFilter } : {}),\n state: \"active\",\n });\n } catch {\n // countEntries is optional in some backends — keep going.\n totalIndexedInScope = undefined;\n }\n\n const matchTypeBreakdown: Record<string, number> = {};\n for (const r of results) {\n matchTypeBreakdown[r.matchType] = (matchTypeBreakdown[r.matchType] ?? 0) + 1;\n }\n\n // Plain-language root-cause hint when results are empty.\n let whyEmpty: string | undefined;\n if (results.length === 0) {\n if (totalIndexedInScope === 0) {\n whyEmpty = `No entries indexed in scope \"${scopeFilter ?? \"(no scope)\"}\" — likely a domain mismatch or missing ingestion.`;\n } else if (mode === \"fts\" && !text) {\n whyEmpty = \"FTS mode requested but `text` was empty.\";\n } else if (mode === \"anchor\" && (!anchors || anchors.length === 0)) {\n whyEmpty = \"Anchor mode requested but `anchors` was empty.\";\n } else if (mode === \"semantic\" && !embeddingUsed) {\n whyEmpty =\n \"Semantic mode requested but no embedding was generated (no `text`/`embeddingText` and no auto-embed available).\";\n } else {\n whyEmpty = `Index has ${totalIndexedInScope ?? \"?\"} entries in scope but none matched query.`;\n }\n }\n\n return {\n mode,\n ftsQuery: text,\n embeddingText: embeddingText ?? text,\n embeddingUsed,\n anchors,\n scope: scopeFilter,\n metaFilter: opts?.metaFilter,\n totalIndexedInScope,\n resultCount: results.length,\n matchTypeBreakdown,\n whyEmpty,\n };\n }\n\n @Actions.Exec(\"/\", \"get\")\n async execGet(_ctx: RouteContext, args: Record<string, unknown>): Promise<AFSExecResult> {\n const parsed = GetArgsSchema.safeParse(args);\n if (!parsed.success) {\n return {\n success: false,\n error: { code: \"VALIDATION_ERROR\", message: explainZodError(parsed.error, GetArgsSchema) },\n };\n }\n await this.ensureInitialized();\n\n const paths = parsed.data.entryPaths ?? (parsed.data.entryPath ? [parsed.data.entryPath] : []);\n if (paths.length === 0) {\n return {\n success: false,\n error: { code: \"VALIDATION_ERROR\", message: \"entryPath or entryPaths required\" },\n };\n }\n\n // Cross-blocklet boundary: the backend `scope` column already isolates\n // cross-USER on the shared file; this closes cross-BLOCKLET-same-user on\n // `get` by dropping any entry whose domain isn't one this caller declared\n // (same posture execQuery already takes via boundDomains).\n const bound = this.options.boundDomains;\n const inBounds = (entry: IndexedEntry | null): boolean =>\n !entry || !bound || bound.length === 0 || bound.includes(entry.domain);\n\n const entries: { entryPath: string; entry: IndexedEntry | null; summary: string | null }[] = [];\n for (const p of paths) {\n const entry = await this.backend.getEntry(p);\n if (!inBounds(entry)) {\n entries.push({ entryPath: p, entry: null, summary: null });\n continue;\n }\n const summary = this.backend.getSummary ? await this.backend.getSummary(p) : null;\n entries.push({ entryPath: p, entry, summary });\n }\n return { success: true, data: { entries } };\n }\n\n @Actions.Exec(\"/\", \"reindex\")\n async execReindex(_ctx: RouteContext, args: Record<string, unknown>): Promise<AFSExecResult> {\n const parsed = ReindexArgsSchema.safeParse(args);\n if (!parsed.success) {\n return {\n success: false,\n error: {\n code: \"VALIDATION_ERROR\",\n message: explainZodError(parsed.error, ReindexArgsSchema),\n },\n };\n }\n await this.ensureInitialized();\n\n const { scope, extractorId, domain } = parsed.data;\n const staleCount = await this.backend.markStale({\n scope,\n extractorId,\n domain,\n boundDomains: this.options.boundDomains,\n });\n\n return { success: true, data: { staleCount } };\n }\n\n @Actions.Exec(\"/\", \"cleanup\")\n async execCleanup(_ctx: RouteContext, args: Record<string, unknown>): Promise<AFSExecResult> {\n const parsed = CleanupArgsSchema.safeParse(args);\n if (!parsed.success) {\n return {\n success: false,\n error: {\n code: \"VALIDATION_ERROR\",\n message: explainZodError(parsed.error, CleanupArgsSchema),\n },\n };\n }\n await this.ensureInitialized();\n\n const { state, domain, scope } = parsed.data;\n const entries = await this.backend.listEntries({\n state,\n domain,\n scope,\n boundDomains: this.options.boundDomains,\n });\n const paths = entries.map((e) => e.entryPath);\n\n let purgedCount: number;\n if (this.backend.purgeEntries) {\n purgedCount = await this.backend.purgeEntries(paths);\n } else {\n purgedCount = 0;\n for (const path of paths) {\n await this.backend.removeAnchors(path);\n await this.backend.removeSummary(path);\n if (this.backend.removeEmbedding) {\n await this.backend.removeEmbedding(path);\n }\n await this.backend.removeEntry(path);\n purgedCount++;\n }\n }\n\n return { success: true, data: { purgedCount } };\n }\n\n /**\n * `verify` — index self-stats for drift detection.\n *\n * Returns the index's own view of what it holds: total entries, breakdown\n * by domain and state, and how many entries have zero anchors (a common\n * symptom of a half-finished or corrupted index). A caller compares these\n * against the source-of-truth provider (e.g. memory's `health.totalEntries`)\n * to decide whether to trigger a rebuild — without guessing from empty\n * query results.\n */\n @Actions.Exec(\"/\", \"verify\")\n async execVerify(_ctx: RouteContext, args: Record<string, unknown>): Promise<AFSExecResult> {\n const parsed = VerifyArgsSchema.safeParse(args);\n if (!parsed.success) {\n return {\n success: false,\n error: {\n code: \"VALIDATION_ERROR\",\n message: explainZodError(parsed.error, VerifyArgsSchema),\n },\n };\n }\n await this.ensureInitialized();\n\n const { domain, scope } = parsed.data;\n const entries = await this.backend.listEntries({\n ...(domain ? { domain } : {}),\n ...(scope ? { scope } : {}),\n boundDomains: this.options.boundDomains,\n });\n\n const byDomain: Record<string, number> = {};\n const byState: Record<string, number> = {};\n let anchorlessCount = 0;\n for (const e of entries) {\n byDomain[e.domain] = (byDomain[e.domain] ?? 0) + 1;\n byState[e.state] = (byState[e.state] ?? 0) + 1;\n if (!e.anchorCount) anchorlessCount++;\n }\n\n return {\n success: true,\n data: {\n totalEntries: entries.length,\n byDomain,\n byState,\n anchorlessCount,\n ...(domain ? { domain } : {}),\n ...(scope ? { scope } : {}),\n },\n };\n }\n\n /**\n * Register one or more domains from a manifest projection, wiring each\n * declared anchor type into the key registry (normalizedValue by default).\n *\n * This is the non-exec entry point for session-view wiring: called at index\n * handle creation time so domains survive Worker restarts (the in-memory\n * handles are rebuilt from the manifest on every cold start).\n */\n registerDomains(\n domains: Array<{\n name: string;\n scope?: string;\n anchorTypes: AnchorTypeDefinition[];\n config?: IndexConfig;\n }>,\n ): void {\n for (const domain of domains) {\n this.domains.set(domain.name, {\n name: domain.name,\n ...(domain.scope ? { scope: domain.scope } : {}),\n types: domain.anchorTypes,\n ...(domain.config ? { config: domain.config } : {}),\n });\n this.registerAnchorTypeKeyGenerators(domain.anchorTypes);\n }\n }\n\n /**\n * Attach a changelog follower so execQuery triggers a lightweight catch-up\n * before returning results. Called once at index handle creation time by the\n * CF session-view (cf-session-view.ts) after building the follower.\n */\n setFollower(follower: IndexFollower): void {\n this.follower = follower;\n // Give the follower the SAME embedder the query path uses, so\n // changelog-driven indexing writes embedding vectors too — semantic-recall\n // parity between the `index` action and the follower (Epic #932). autoEmbed\n // no-ops when embedding isn't configured, so this is safe for FTS-only\n // deployments.\n follower.setEmbedder((text) => this.autoEmbed(text, \"passage\"));\n }\n\n private startCatchUpOnce(): { task: Promise<void>; started: boolean } {\n if (this.catchUpInflight) return { task: this.catchUpInflight, started: false };\n const follower = this.follower;\n if (!follower) return { task: Promise.resolve(), started: false };\n let task: Promise<void>;\n task = follower\n .runCatchUp()\n .catch(() => {\n // Non-fatal — a stale index is better than a failed/blocked query.\n })\n .finally(() => {\n if (this.catchUpInflight === task) {\n this.catchUpInflight = undefined;\n }\n });\n this.catchUpInflight = task;\n return { task, started: true };\n }\n\n /**\n * Wire declared anchor types into the shared key registry. Types with an\n * explicit function keyGenerator use it directly; types without one get a\n * normalizedValue generator (lowercase+trim) instead of the exact fallback\n * (JSON.stringify, field-order/case-sensitive).\n *\n * Built-in types (time/location/person/topic — registered in\n * `KeyGeneratorRegistry`'s constructor) are never overwritten here, even if\n * a manifest or `.index/anchor-types/` declaration reuses one of those\n * names: the built-in's multi-dimensional key generator always wins (issue\n * #934 P0 — see `KeyGeneratorRegistry.isBuiltin`). Shared by\n * `registerDomains` (manifest wiring) and `discoverProvider` (`.index/`\n * convention wiring) — previously duplicated inline in both.\n */\n private registerAnchorTypeKeyGenerators(anchorTypes: AnchorTypeDefinition[]): void {\n for (const anchorType of anchorTypes) {\n if (this.keyRegistry.isBuiltin(anchorType.name)) continue;\n if (typeof anchorType.keyGenerator === \"function\") {\n this.keyRegistry.register(anchorType.name, anchorType.keyGenerator as KeyGenerator);\n } else {\n this.keyRegistry.register(anchorType.name, createNormalizedValueGenerator());\n }\n }\n }\n\n @Actions.Exec(\"/\", \"register-domain\")\n async execRegisterDomain(\n _ctx: RouteContext,\n args: Record<string, unknown>,\n ): Promise<AFSExecResult> {\n const parsed = RegisterDomainArgsSchema.safeParse(args);\n if (!parsed.success) {\n return {\n success: false,\n error: {\n code: \"VALIDATION_ERROR\",\n message: explainZodError(parsed.error, RegisterDomainArgsSchema),\n },\n };\n }\n const name = parsed.data.name;\n const types = (parsed.data.types as AnchorTypeDefinition[]) ?? [];\n\n this.domains.set(name, { name, types });\n\n return { success: true, data: { domain: name, typeCount: types.length } };\n }\n\n @Actions.Exec(\"/\", \"discover\")\n async execDiscover(_ctx: RouteContext, args: Record<string, unknown>): Promise<AFSExecResult> {\n const parsed = DiscoverArgsSchema.safeParse(args);\n if (!parsed.success) {\n return {\n success: false,\n error: {\n code: \"VALIDATION_ERROR\",\n message: explainZodError(parsed.error, DiscoverArgsSchema),\n },\n };\n }\n if (!this.afsRoot) {\n return {\n success: false,\n error: { code: \"NOT_MOUNTED\", message: \"Provider not mounted (no AFS root)\" },\n };\n }\n\n const { providerPath } = parsed.data;\n const domainName =\n parsed.data.domain ?? providerPath.split(\"/\").filter(Boolean).pop() ?? \"unknown\";\n const result = await this.discoverProvider(providerPath, domainName);\n\n return {\n success: true,\n data: {\n domain: result.domainName,\n anchorTypes: result.anchorTypes.map((t) => t.name),\n config: result.config,\n extractor: result.extractor,\n },\n };\n }\n\n private async discoverProvider(\n providerPath: string,\n domainName: string,\n ): Promise<{\n domainName: string;\n anchorTypes: AnchorTypeDefinition[];\n config?: IndexConfig;\n extractor?: ExtractorHint;\n }> {\n const root = this.afsRoot!;\n\n // Read .index/config\n let config: IndexConfig | undefined;\n try {\n const configEntry = await root.read!(joinURL(providerPath, \".index\", \"config\"));\n const content = configEntry.data?.content;\n if (content && typeof content === \"object\") {\n config = content as IndexConfig;\n }\n } catch {\n // No .index/config — that's OK\n }\n\n // Read .index/anchor-types/ to discover anchor type definitions\n const anchorTypes: AnchorTypeDefinition[] = [];\n try {\n const typesPath = joinURL(providerPath, \".index\", \"anchor-types\");\n const typesList = await root.list(typesPath);\n for (const entry of typesList.data) {\n if (entry.id) {\n try {\n const typeEntry = await root.read!(joinURL(typesPath, entry.id));\n const content = typeEntry.data?.content;\n if (content && typeof content === \"object\" && \"name\" in content) {\n anchorTypes.push(content as AnchorTypeDefinition);\n }\n } catch {\n // Skip unreadable anchor type entries\n }\n }\n }\n } catch {\n // No .index/anchor-types/ — that's OK\n }\n\n // Read .index/extractor\n let extractor: ExtractorHint | undefined;\n try {\n const extractorEntry = await root.read!(joinURL(providerPath, \".index\", \"extractor\"));\n const content = extractorEntry.data?.content;\n if (content && typeof content === \"object\" && \"path\" in content) {\n extractor = content as ExtractorHint;\n }\n } catch {\n // No .index/extractor — that's OK\n }\n\n // Register the domain with full .index/ data and provider scope\n this.domains.set(domainName, {\n name: domainName,\n scope: providerPath,\n types: anchorTypes,\n config,\n extractor,\n });\n\n // Wire declared anchor types into the shared key registry (built-ins protected).\n this.registerAnchorTypeKeyGenerators(anchorTypes);\n\n // Register extractor for auto-indexing via write events\n if (extractor) {\n this.extractors.register(domainName, {\n id: `${domainName}-extractor`,\n scope: providerPath,\n mode: extractor.mode,\n extractor: extractor.path,\n });\n }\n\n return { domainName, anchorTypes, config, extractor };\n }\n\n /**\n * Check if a path should be indexed based on .index/config rules.\n * Finds the domain whose scope covers the path and applies its config.\n * If no matching domain config exists, allows indexing by default.\n */\n private shouldIndex(path: string, _domain: string): boolean {\n // Find domain whose scope covers this path\n let config: import(\"./types.js\").IndexConfig | undefined;\n for (const domainDef of this.domains.values()) {\n if (domainDef.scope && path.startsWith(domainDef.scope) && domainDef.config) {\n config = domainDef.config;\n break;\n }\n }\n\n if (!config) return true;\n\n const { indexable, skip } = config;\n\n // Skip patterns take priority\n if (skip?.some((pattern) => matchGlob(path, pattern))) return false;\n\n // If indexable is defined, path must match at least one pattern\n if (indexable && indexable.length > 0) {\n return indexable.some((pattern) => matchGlob(path, pattern));\n }\n\n return true;\n }\n\n /**\n * Auto-generate embedding via LLM provider if configured and enabled.\n * Returns null if LLM is not available or embedding is disabled.\n *\n * @param type - \"passage\" for write-time (document), \"query\" for query-time.\n * Models like E5 require different prefixes for query vs passage.\n */\n private async autoEmbed(\n text?: string,\n type: \"query\" | \"passage\" = \"passage\",\n ): Promise<number[] | null> {\n // Prefer an explicit embedding root over the mount afsRoot: in overlay\n // runtimes (CF per-caller SessionUserAFS session view) the mount afsRoot's\n // `exec` doesn't fall through to the system `/dev/ai` hubs, so embedding must\n // target the underlying `base`. Unset → mount afsRoot (Node global mount).\n // Delegates to the shared `createEmbedder` so the query path and the two\n // index paths (index action + follower) embed identically.\n return createEmbedder({\n embeddingEnabled: this.options.embeddingEnabled,\n llmProvider: this.options.llmProvider,\n embedRoot: this.options.embeddingAfsRoot ?? this.afsRoot,\n })(text, type);\n }\n\n /**\n * Resolve the chat-action path used by query rewriters. Prefers an\n * explicit `chatProvider` config; falls back to `<llmProvider>/.actions/chat`\n * because the same AigneHub default endpoint hosts both embedding\n * and chat actions in practice.\n */\n private resolveChatProvider(): string | null {\n if (this.options.chatProvider) return this.options.chatProvider;\n if (this.options.llmProvider) {\n return joinURL(this.options.llmProvider, \".actions\", \"chat\");\n }\n return null;\n }\n\n /**\n * Generate paraphrases of `question` via the chat provider. Returns\n * the paraphrases plus the original question (the original is kept\n * as a safety net in case the rewrites drift). Returns just `[question]`\n * if the chat call fails — degrades gracefully to baseline.\n */\n private async generateParaphrases(question: string): Promise<string[]> {\n const chatPath = this.resolveChatProvider();\n // Same overlay caveat as autoEmbed: prefer the explicit embedding root so the\n // chat action resolves against `/dev/ai` even when the mount afsRoot is an\n // overlay view that doesn't fall through to it.\n const chatRoot = this.options.embeddingAfsRoot ?? this.afsRoot;\n if (!chatPath || !chatRoot) return [question];\n try {\n const result = await chatRoot.exec!(\n chatPath,\n {\n messages: [\n {\n role: \"system\",\n content:\n \"Rewrite a search query as 3 different paraphrases that mean the same thing but use different vocabulary. Each paraphrase should be a single line. Output exactly 3 lines, no numbering, no preamble. Keep them concrete and search-ready.\",\n },\n { role: \"user\", content: question },\n ],\n },\n {},\n );\n if (!result.success || !result.data) return [question];\n const data = result.data as { text?: string };\n if (!data.text) return [question];\n const lines = data.text\n .split(\"\\n\")\n .map((l) => l.replace(/^\\s*[-*0-9.)\\]]+\\s*/, \"\").trim())\n .filter((l) => l.length > 0)\n .slice(0, 3);\n return [question, ...lines];\n } catch {\n return [question];\n }\n }\n\n /**\n * Multi-query retrieval: generate N paraphrases, embed each, run a\n * separate semantic query per variant, and RRF-fuse the result lists.\n * The `index-diverse-bench` shows 2.6× MRR lift on paraphrase queries\n * with this strategy. Each variant pulls top-(2×limit) so the fusion\n * has enough candidates to differentiate; the final list is trimmed\n * to the caller's requested limit.\n */\n private async queryWithMultiQuery(input: {\n original: string;\n ftsText: string | undefined;\n mode: SearchMode;\n anchors: { type: string; values: string[] }[] | undefined;\n opts: QueryArgs[\"opts\"];\n }): Promise<QueryResult[]> {\n const { original, ftsText, mode, anchors, opts } = input;\n const variants = await this.generateParaphrases(original);\n const requestedLimit = opts?.limit ?? 10;\n const fanoutLimit = Math.max(requestedLimit * 2, 10);\n\n const lists: { entries: { entryPath: string }[] }[] = [];\n for (const variant of variants) {\n const embedding = await this.autoEmbed(variant, \"query\");\n const variantResults = await this.queryEngine.query({\n mode,\n anchors,\n // Use the variant's text on the FTS side too — that's the whole\n // point of multi-query, sample more of the keyword space. The\n // caller's pre-formatted `ftsText` only applies to the original.\n text: variant === original ? ftsText : variant,\n embedding: embedding ?? undefined,\n opts: { ...opts, limit: fanoutLimit },\n boundDomains: this.options.boundDomains,\n });\n lists.push({ entries: variantResults });\n }\n\n // RRF fusion across variants. Each list is already a ranked top-N\n // for one paraphrase; we score each entry by Σ 1/(k+rank) across\n // the lists it appears in.\n const k = 60;\n const scores = new Map<string, { entry: QueryResult; score: number }>();\n for (const list of lists) {\n for (let i = 0; i < list.entries.length; i++) {\n const entry = list.entries[i] as QueryResult;\n const contribution = 1 / (k + i + 1);\n const existing = scores.get(entry.entryPath);\n if (existing) {\n existing.score += contribution;\n } else {\n scores.set(entry.entryPath, { entry, score: contribution });\n }\n }\n }\n return [...scores.values()]\n .sort((a, b) => b.score - a.score)\n .slice(0, requestedLimit)\n .map(({ entry, score }) => ({ ...entry, score }));\n }\n}\n\ntype QueryArgs = z.infer<typeof QueryArgsSchema>;\n\n/**\n * Simple glob matching — supports `*` (single segment) and `**` (multi-segment).\n * Also matches bare extensions like `*.md` against any path ending with `.md`.\n */\nfunction matchGlob(path: string, pattern: string): boolean {\n // Convert glob pattern to regex\n const regexStr = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\") // escape special regex chars (not * and ?)\n .replace(/\\*\\*/g, \"⧫\") // temp placeholder for **\n .replace(/\\*/g, \"[^/]*\") // * matches single segment\n .replace(/⧫/g, \".*\") // ** matches any depth\n .replace(/\\?/g, \"[^/]\"); // ? matches single char\n\n return new RegExp(`(^|/)${regexStr}$`).test(path);\n}\n"],"mappings":"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"}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const require_cjk = require('./text/cjk.cjs');
|
|
2
|
+
const require_d1_vector_backend = require('./backend/d1-vector-backend.cjs');
|
|
3
|
+
const require_d1_index_backend = require('./backend/d1-index-backend.cjs');
|
|
4
|
+
const require_memory_backend = require('./backend/memory-backend.cjs');
|
|
5
|
+
const require_sqlite_backend = require('./backend/sqlite-backend.cjs');
|
|
6
|
+
const require_upstash_vector_backend = require('./backend/upstash-vector-backend.cjs');
|
|
7
|
+
const require_embed = require('./embed.cjs');
|
|
8
|
+
const require_defaults = require('./extraction/defaults.cjs');
|
|
9
|
+
const require_field_map = require('./extraction/field-map.cjs');
|
|
10
|
+
const require_manager = require('./extraction/manager.cjs');
|
|
11
|
+
const require_path_anchors = require('./extraction/path-anchors.cjs');
|
|
12
|
+
const require_follower = require('./follower.cjs');
|
|
13
|
+
const require_follower_scheduler = require('./follower-scheduler.cjs');
|
|
14
|
+
const require_engine = require('./query/engine.cjs');
|
|
15
|
+
const require_types = require('./types.cjs');
|
|
16
|
+
const require_index_provider = require('./index-provider.cjs');
|
|
17
|
+
const require_manifest_index = require('./manifest-index.cjs');
|
|
18
|
+
const require_offline_rebuild = require('./offline-rebuild.cjs');
|
|
19
|
+
|
|
20
|
+
exports.AFSIndex = require_index_provider.AFSIndex;
|
|
21
|
+
exports.D1IndexBackend = require_d1_index_backend.D1IndexBackend;
|
|
22
|
+
exports.D1VectorBackend = require_d1_vector_backend.D1VectorBackend;
|
|
23
|
+
exports.DEFAULT_MAX_DIRTY_ROWS_PER_TICK = require_follower.DEFAULT_MAX_DIRTY_ROWS_PER_TICK;
|
|
24
|
+
exports.DEFAULT_MAX_NOOP_PER_TICK = require_follower.DEFAULT_MAX_NOOP_PER_TICK;
|
|
25
|
+
exports.DEFAULT_MAX_PROCESSED_PER_TICK = require_follower.DEFAULT_MAX_PROCESSED_PER_TICK;
|
|
26
|
+
exports.DEFAULT_MAX_SCAN_PER_TICK = require_follower.DEFAULT_MAX_SCAN_PER_TICK;
|
|
27
|
+
exports.DEFAULT_SEARCH_MODE = require_types.DEFAULT_SEARCH_MODE;
|
|
28
|
+
exports.ExtractorManager = require_manager.ExtractorManager;
|
|
29
|
+
exports.FOLLOWER_CONSUMER_ID = require_follower.FOLLOWER_CONSUMER_ID;
|
|
30
|
+
exports.IndexFollower = require_follower.IndexFollower;
|
|
31
|
+
exports.MemoryIndexBackend = require_memory_backend.MemoryIndexBackend;
|
|
32
|
+
exports.SEARCH_MODES = require_types.SEARCH_MODES;
|
|
33
|
+
exports.SQLiteIndexBackend = require_sqlite_backend.SQLiteIndexBackend;
|
|
34
|
+
exports.UpstashVectorBackend = require_upstash_vector_backend.UpstashVectorBackend;
|
|
35
|
+
exports.applyEnrichContentFile = require_field_map.applyEnrichContentFile;
|
|
36
|
+
exports.applyFieldMap = require_field_map.applyFieldMap;
|
|
37
|
+
exports.coerceSearchMode = require_types.coerceSearchMode;
|
|
38
|
+
exports.createEmbedder = require_embed.createEmbedder;
|
|
39
|
+
exports.deriveFollowerConsumerId = require_follower.deriveFollowerConsumerId;
|
|
40
|
+
exports.evalFieldExpr = require_field_map.evalFieldExpr;
|
|
41
|
+
exports.evalJsonPath = require_field_map.evalJsonPath;
|
|
42
|
+
exports.extractFromMeta = require_defaults.extractFromMeta;
|
|
43
|
+
exports.extractPathTopicAnchors = require_path_anchors.extractPathTopicAnchors;
|
|
44
|
+
exports.extractSummaryText = require_field_map.extractSummaryText;
|
|
45
|
+
exports.offlineRebuildScope = require_offline_rebuild.offlineRebuildScope;
|
|
46
|
+
exports.projectDomain = require_manifest_index.projectDomain;
|
|
47
|
+
exports.projectManifestIndex = require_manifest_index.projectManifestIndex;
|
|
48
|
+
exports.reciprocalRankFusion = require_engine.reciprocalRankFusion;
|
|
49
|
+
exports.resolveEnrichPath = require_field_map.resolveEnrichPath;
|
|
50
|
+
exports.runIndexFollowerRoundRobin = require_follower_scheduler.runIndexFollowerRoundRobin;
|
|
51
|
+
exports.segmentCJK = require_cjk.segmentCJK;
|
|
52
|
+
exports.truncateToMaxChars = require_field_map.truncateToMaxChars;
|
|
53
|
+
exports.userIndexKey = require_follower.userIndexKey;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { CursorRow, CursorScopeRegistration, IndexBackend, IndexedDocumentWrite, RemoveIndexOptions } from "./backend/types.cjs";
|
|
2
|
+
import { AFSIndexOptions, Anchor, AnchorData, AnchorQuery, AnchorResult, AnchorTypeDefinition, CombinedResult, DEFAULT_SEARCH_MODE, DomainDefinition, EntryFilter, EntryState, ExtractionResult, ExtractorHint, ExtractorRegistration, FTSResult, IndexConfig, IndexedEntry, KeyGenerator, LocationAnchor, MatchType, PersonAnchor, QueryOpts, QueryPage, QueryPageMeta, QueryResult, SEARCH_MODES, SearchMode, StaleFilter, TimeAnchor, TimeKeyGranularity, TopicAnchor, VectorResult, coerceSearchMode } from "./types.cjs";
|
|
3
|
+
import { VectorBackend, VectorFilter } from "./backend/vector-backend.cjs";
|
|
4
|
+
import { D1IndexBackend } from "./backend/d1-index-backend.cjs";
|
|
5
|
+
import { D1VectorBackend } from "./backend/d1-vector-backend.cjs";
|
|
6
|
+
import { MemoryIndexBackend } from "./backend/memory-backend.cjs";
|
|
7
|
+
import { SQLiteIndexBackend } from "./backend/sqlite-backend.cjs";
|
|
8
|
+
import { UpstashVectorBackend } from "./backend/upstash-vector-backend.cjs";
|
|
9
|
+
import { DirtyPathOp, DirtyPathQueue, DirtyPathRow, ListDirtyPathOptions } from "./dirty-queue.cjs";
|
|
10
|
+
import { Embedder, EmbedderOptions, createEmbedder } from "./embed.cjs";
|
|
11
|
+
import { extractFromMeta } from "./extraction/defaults.cjs";
|
|
12
|
+
import { applyEnrichContentFile, applyFieldMap, evalFieldExpr, evalJsonPath, extractSummaryText, resolveEnrichPath, truncateToMaxChars } from "./extraction/field-map.cjs";
|
|
13
|
+
import { ExtractorManager } from "./extraction/manager.cjs";
|
|
14
|
+
import { PathAnchorOptions, extractPathTopicAnchors } from "./extraction/path-anchors.cjs";
|
|
15
|
+
import { DEFAULT_MAX_DIRTY_ROWS_PER_TICK, DEFAULT_MAX_NOOP_PER_TICK, DEFAULT_MAX_PROCESSED_PER_TICK, DEFAULT_MAX_SCAN_PER_TICK, FOLLOWER_CONSUMER_ID, IndexFollower, IndexFollowerBatchResult, IndexFollowerOptions, deriveFollowerConsumerId, userIndexKey } from "./follower.cjs";
|
|
16
|
+
import { IndexFollowerRoundRobinOptions, IndexFollowerRoundRobinResult, IndexFollowerRoundRobinRun, IndexFollowerRunLimits, runIndexFollowerRoundRobin } from "./follower-scheduler.cjs";
|
|
17
|
+
import { AFSIndex } from "./index-provider.cjs";
|
|
18
|
+
import { DomainRegistration, projectDomain, projectManifestIndex } from "./manifest-index.cjs";
|
|
19
|
+
import { OfflineRebuildOptions, OfflineRebuildResult, offlineRebuildScope } from "./offline-rebuild.cjs";
|
|
20
|
+
import { reciprocalRankFusion } from "./query/engine.cjs";
|
|
21
|
+
import { segmentCJK } from "./text/cjk.cjs";
|
|
22
|
+
export { AFSIndex, AFSIndexOptions, Anchor, AnchorData, AnchorQuery, AnchorResult, AnchorTypeDefinition, CombinedResult, type CursorRow, type CursorScopeRegistration, D1IndexBackend, D1VectorBackend, DEFAULT_MAX_DIRTY_ROWS_PER_TICK, DEFAULT_MAX_NOOP_PER_TICK, DEFAULT_MAX_PROCESSED_PER_TICK, DEFAULT_MAX_SCAN_PER_TICK, DEFAULT_SEARCH_MODE, type DirtyPathOp, type DirtyPathQueue, type DirtyPathRow, DomainDefinition, type DomainRegistration, type Embedder, type EmbedderOptions, EntryFilter, EntryState, ExtractionResult, ExtractorHint, ExtractorManager, ExtractorRegistration, FOLLOWER_CONSUMER_ID, FTSResult, type IndexBackend, IndexConfig, IndexFollower, type IndexFollowerBatchResult, type IndexFollowerOptions, type IndexFollowerRoundRobinOptions, type IndexFollowerRoundRobinResult, type IndexFollowerRoundRobinRun, type IndexFollowerRunLimits, type IndexedDocumentWrite, IndexedEntry, KeyGenerator, type ListDirtyPathOptions, LocationAnchor, MatchType, MemoryIndexBackend, type OfflineRebuildOptions, type OfflineRebuildResult, type PathAnchorOptions, PersonAnchor, QueryOpts, QueryPage, QueryPageMeta, QueryResult, type RemoveIndexOptions, SEARCH_MODES, SQLiteIndexBackend, SearchMode, StaleFilter, TimeAnchor, TimeKeyGranularity, TopicAnchor, UpstashVectorBackend, type VectorBackend, type VectorFilter, VectorResult, applyEnrichContentFile, applyFieldMap, coerceSearchMode, createEmbedder, deriveFollowerConsumerId, evalFieldExpr, evalJsonPath, extractFromMeta, extractPathTopicAnchors, extractSummaryText, offlineRebuildScope, projectDomain, projectManifestIndex, reciprocalRankFusion, resolveEnrichPath, runIndexFollowerRoundRobin, segmentCJK, truncateToMaxChars, userIndexKey };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { CursorRow, CursorScopeRegistration, IndexBackend, IndexedDocumentWrite, RemoveIndexOptions } from "./backend/types.mjs";
|
|
2
|
+
import { AFSIndexOptions, Anchor, AnchorData, AnchorQuery, AnchorResult, AnchorTypeDefinition, CombinedResult, DEFAULT_SEARCH_MODE, DomainDefinition, EntryFilter, EntryState, ExtractionResult, ExtractorHint, ExtractorRegistration, FTSResult, IndexConfig, IndexedEntry, KeyGenerator, LocationAnchor, MatchType, PersonAnchor, QueryOpts, QueryPage, QueryPageMeta, QueryResult, SEARCH_MODES, SearchMode, StaleFilter, TimeAnchor, TimeKeyGranularity, TopicAnchor, VectorResult, coerceSearchMode } from "./types.mjs";
|
|
3
|
+
import { VectorBackend, VectorFilter } from "./backend/vector-backend.mjs";
|
|
4
|
+
import { D1IndexBackend } from "./backend/d1-index-backend.mjs";
|
|
5
|
+
import { D1VectorBackend } from "./backend/d1-vector-backend.mjs";
|
|
6
|
+
import { MemoryIndexBackend } from "./backend/memory-backend.mjs";
|
|
7
|
+
import { SQLiteIndexBackend } from "./backend/sqlite-backend.mjs";
|
|
8
|
+
import { UpstashVectorBackend } from "./backend/upstash-vector-backend.mjs";
|
|
9
|
+
import { DirtyPathOp, DirtyPathQueue, DirtyPathRow, ListDirtyPathOptions } from "./dirty-queue.mjs";
|
|
10
|
+
import { Embedder, EmbedderOptions, createEmbedder } from "./embed.mjs";
|
|
11
|
+
import { extractFromMeta } from "./extraction/defaults.mjs";
|
|
12
|
+
import { applyEnrichContentFile, applyFieldMap, evalFieldExpr, evalJsonPath, extractSummaryText, resolveEnrichPath, truncateToMaxChars } from "./extraction/field-map.mjs";
|
|
13
|
+
import { ExtractorManager } from "./extraction/manager.mjs";
|
|
14
|
+
import { PathAnchorOptions, extractPathTopicAnchors } from "./extraction/path-anchors.mjs";
|
|
15
|
+
import { DEFAULT_MAX_DIRTY_ROWS_PER_TICK, DEFAULT_MAX_NOOP_PER_TICK, DEFAULT_MAX_PROCESSED_PER_TICK, DEFAULT_MAX_SCAN_PER_TICK, FOLLOWER_CONSUMER_ID, IndexFollower, IndexFollowerBatchResult, IndexFollowerOptions, deriveFollowerConsumerId, userIndexKey } from "./follower.mjs";
|
|
16
|
+
import { IndexFollowerRoundRobinOptions, IndexFollowerRoundRobinResult, IndexFollowerRoundRobinRun, IndexFollowerRunLimits, runIndexFollowerRoundRobin } from "./follower-scheduler.mjs";
|
|
17
|
+
import { AFSIndex } from "./index-provider.mjs";
|
|
18
|
+
import { DomainRegistration, projectDomain, projectManifestIndex } from "./manifest-index.mjs";
|
|
19
|
+
import { OfflineRebuildOptions, OfflineRebuildResult, offlineRebuildScope } from "./offline-rebuild.mjs";
|
|
20
|
+
import { reciprocalRankFusion } from "./query/engine.mjs";
|
|
21
|
+
import { segmentCJK } from "./text/cjk.mjs";
|
|
22
|
+
export { AFSIndex, AFSIndexOptions, Anchor, AnchorData, AnchorQuery, AnchorResult, AnchorTypeDefinition, CombinedResult, type CursorRow, type CursorScopeRegistration, D1IndexBackend, D1VectorBackend, DEFAULT_MAX_DIRTY_ROWS_PER_TICK, DEFAULT_MAX_NOOP_PER_TICK, DEFAULT_MAX_PROCESSED_PER_TICK, DEFAULT_MAX_SCAN_PER_TICK, DEFAULT_SEARCH_MODE, type DirtyPathOp, type DirtyPathQueue, type DirtyPathRow, DomainDefinition, type DomainRegistration, type Embedder, type EmbedderOptions, EntryFilter, EntryState, ExtractionResult, ExtractorHint, ExtractorManager, ExtractorRegistration, FOLLOWER_CONSUMER_ID, FTSResult, type IndexBackend, IndexConfig, IndexFollower, type IndexFollowerBatchResult, type IndexFollowerOptions, type IndexFollowerRoundRobinOptions, type IndexFollowerRoundRobinResult, type IndexFollowerRoundRobinRun, type IndexFollowerRunLimits, type IndexedDocumentWrite, IndexedEntry, KeyGenerator, type ListDirtyPathOptions, LocationAnchor, MatchType, MemoryIndexBackend, type OfflineRebuildOptions, type OfflineRebuildResult, type PathAnchorOptions, PersonAnchor, QueryOpts, QueryPage, QueryPageMeta, QueryResult, type RemoveIndexOptions, SEARCH_MODES, SQLiteIndexBackend, SearchMode, StaleFilter, TimeAnchor, TimeKeyGranularity, TopicAnchor, UpstashVectorBackend, type VectorBackend, type VectorFilter, VectorResult, applyEnrichContentFile, applyFieldMap, coerceSearchMode, createEmbedder, deriveFollowerConsumerId, evalFieldExpr, evalJsonPath, extractFromMeta, extractPathTopicAnchors, extractSummaryText, offlineRebuildScope, projectDomain, projectManifestIndex, reciprocalRankFusion, resolveEnrichPath, runIndexFollowerRoundRobin, segmentCJK, truncateToMaxChars, userIndexKey };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { segmentCJK } from "./text/cjk.mjs";
|
|
2
|
+
import { D1VectorBackend } from "./backend/d1-vector-backend.mjs";
|
|
3
|
+
import { D1IndexBackend } from "./backend/d1-index-backend.mjs";
|
|
4
|
+
import { MemoryIndexBackend } from "./backend/memory-backend.mjs";
|
|
5
|
+
import { SQLiteIndexBackend } from "./backend/sqlite-backend.mjs";
|
|
6
|
+
import { UpstashVectorBackend } from "./backend/upstash-vector-backend.mjs";
|
|
7
|
+
import { createEmbedder } from "./embed.mjs";
|
|
8
|
+
import { extractFromMeta } from "./extraction/defaults.mjs";
|
|
9
|
+
import { applyEnrichContentFile, applyFieldMap, evalFieldExpr, evalJsonPath, extractSummaryText, resolveEnrichPath, truncateToMaxChars } from "./extraction/field-map.mjs";
|
|
10
|
+
import { ExtractorManager } from "./extraction/manager.mjs";
|
|
11
|
+
import { extractPathTopicAnchors } from "./extraction/path-anchors.mjs";
|
|
12
|
+
import { DEFAULT_MAX_DIRTY_ROWS_PER_TICK, DEFAULT_MAX_NOOP_PER_TICK, DEFAULT_MAX_PROCESSED_PER_TICK, DEFAULT_MAX_SCAN_PER_TICK, FOLLOWER_CONSUMER_ID, IndexFollower, deriveFollowerConsumerId, userIndexKey } from "./follower.mjs";
|
|
13
|
+
import { runIndexFollowerRoundRobin } from "./follower-scheduler.mjs";
|
|
14
|
+
import { reciprocalRankFusion } from "./query/engine.mjs";
|
|
15
|
+
import { DEFAULT_SEARCH_MODE, SEARCH_MODES, coerceSearchMode } from "./types.mjs";
|
|
16
|
+
import { AFSIndex } from "./index-provider.mjs";
|
|
17
|
+
import { projectDomain, projectManifestIndex } from "./manifest-index.mjs";
|
|
18
|
+
import { offlineRebuildScope } from "./offline-rebuild.mjs";
|
|
19
|
+
|
|
20
|
+
export { AFSIndex, D1IndexBackend, D1VectorBackend, DEFAULT_MAX_DIRTY_ROWS_PER_TICK, DEFAULT_MAX_NOOP_PER_TICK, DEFAULT_MAX_PROCESSED_PER_TICK, DEFAULT_MAX_SCAN_PER_TICK, DEFAULT_SEARCH_MODE, ExtractorManager, FOLLOWER_CONSUMER_ID, IndexFollower, MemoryIndexBackend, SEARCH_MODES, SQLiteIndexBackend, UpstashVectorBackend, applyEnrichContentFile, applyFieldMap, coerceSearchMode, createEmbedder, deriveFollowerConsumerId, evalFieldExpr, evalJsonPath, extractFromMeta, extractPathTopicAnchors, extractSummaryText, offlineRebuildScope, projectDomain, projectManifestIndex, reciprocalRankFusion, resolveEnrichPath, runIndexFollowerRoundRobin, segmentCJK, truncateToMaxChars, userIndexKey };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/keys/exact.ts
|
|
3
|
+
function generateExactKeys(anchor) {
|
|
4
|
+
const { type, confidence: _confidence, ...fields } = anchor;
|
|
5
|
+
const keys = [];
|
|
6
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7
|
+
const push = (raw) => {
|
|
8
|
+
if (typeof raw !== "string" && typeof raw !== "number" && typeof raw !== "boolean") return;
|
|
9
|
+
const normalized = String(raw).trim().toLowerCase();
|
|
10
|
+
if (!normalized || seen.has(normalized)) return;
|
|
11
|
+
seen.add(normalized);
|
|
12
|
+
keys.push(`${type}:${normalized}`);
|
|
13
|
+
};
|
|
14
|
+
for (const [field, val] of Object.entries(fields)) if (field === "values" && Array.isArray(val)) for (const item of val) push(item);
|
|
15
|
+
else push(val);
|
|
16
|
+
return keys;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
//#endregion
|
|
20
|
+
exports.generateExactKeys = generateExactKeys;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/keys/exact.ts
|
|
2
|
+
function generateExactKeys(anchor) {
|
|
3
|
+
const { type, confidence: _confidence, ...fields } = anchor;
|
|
4
|
+
const keys = [];
|
|
5
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6
|
+
const push = (raw) => {
|
|
7
|
+
if (typeof raw !== "string" && typeof raw !== "number" && typeof raw !== "boolean") return;
|
|
8
|
+
const normalized = String(raw).trim().toLowerCase();
|
|
9
|
+
if (!normalized || seen.has(normalized)) return;
|
|
10
|
+
seen.add(normalized);
|
|
11
|
+
keys.push(`${type}:${normalized}`);
|
|
12
|
+
};
|
|
13
|
+
for (const [field, val] of Object.entries(fields)) if (field === "values" && Array.isArray(val)) for (const item of val) push(item);
|
|
14
|
+
else push(val);
|
|
15
|
+
return keys;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
//#endregion
|
|
19
|
+
export { generateExactKeys };
|
|
20
|
+
//# sourceMappingURL=exact.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exact.mjs","names":[],"sources":["../../src/keys/exact.ts"],"sourcesContent":["/**\n * Exact key generator — the fallback for anchor types WITHOUT a registered\n * built-in generator (tag / site / source / status / item-type / collection …).\n *\n * Canonical anchor-key contract (MUST match the built-in generators AND the\n * query side): an anchor key is a BARE, colon-delimited, lowercased string\n * `<type>:<value>` — NEVER a JSON blob. Built-ins already follow it\n * (`topic:cooking`, `person:name:alice`, `time:2026-06`), and the client facet\n * parser composes the same `<type>:<value>` lowercased. So a facet anchor\n * `{ type: \"tag\", value: \"94E70305-…\" }` keys as `tag:94e70305-…`.\n *\n * Every string-ish field the anchor carries (`value`, each element of a\n * `values[]` array, or any other scalar field like content-type's `mime` /\n * `category`) contributes ONE `<type>:<field-value>` key, normalized =\n * `trim().toLowerCase()`, deduped. There is deliberately NO JSON fallback:\n *\n * 1. A JSON key (`tag:{\"value\":\"94E70305-…\"}`) is structurally UNQUERYABLE —\n * a value query can never reconstruct the exact blob — so it is a dead key.\n * Emitting it is exactly what silently broke every facet filter (the write\n * key never equalled anything the query side generates).\n * 2. The read side synthesizes `{ type, values: [raw] }` and calls THIS\n * generator, so a bare-value query normalizes to the identical key — write\n * and read converge by construction, regardless of the singular `value`\n * vs plural `values` shape (the asymmetry that broke tag search).\n *\n * (`www.`-prefix normalization for host facets lives in the `site`-registered\n * normalized generator, not here — this fallback stays value-agnostic so it is\n * safe for arbitrary unregistered types.)\n */\nimport type { Anchor } from \"../types.js\";\n\nexport function generateExactKeys(anchor: Anchor): string[] {\n const { type, confidence: _confidence, ...fields } = anchor;\n\n const keys: string[] = [];\n const seen = new Set<string>();\n const push = (raw: unknown): void => {\n if (typeof raw !== \"string\" && typeof raw !== \"number\" && typeof raw !== \"boolean\") return;\n const normalized = String(raw).trim().toLowerCase();\n if (!normalized || seen.has(normalized)) return;\n seen.add(normalized);\n keys.push(`${type}:${normalized}`);\n };\n\n for (const [field, val] of Object.entries(fields)) {\n // `values: [...]` is the canonical query-side array shape (queryAnchors\n // synthesizes `{ values: [raw] }`); every other field contributes its scalar.\n if (field === \"values\" && Array.isArray(val)) {\n for (const item of val) push(item);\n } else {\n push(val);\n }\n }\n return keys;\n}\n"],"mappings":";AA+BA,SAAgB,kBAAkB,QAA0B;CAC1D,MAAM,EAAE,MAAM,YAAY,aAAa,GAAG,WAAW;CAErD,MAAM,OAAiB,EAAE;CACzB,MAAM,uBAAO,IAAI,KAAa;CAC9B,MAAM,QAAQ,QAAuB;AACnC,MAAI,OAAO,QAAQ,YAAY,OAAO,QAAQ,YAAY,OAAO,QAAQ,UAAW;EACpF,MAAM,aAAa,OAAO,IAAI,CAAC,MAAM,CAAC,aAAa;AACnD,MAAI,CAAC,cAAc,KAAK,IAAI,WAAW,CAAE;AACzC,OAAK,IAAI,WAAW;AACpB,OAAK,KAAK,GAAG,KAAK,GAAG,aAAa;;AAGpC,MAAK,MAAM,CAAC,OAAO,QAAQ,OAAO,QAAQ,OAAO,CAG/C,KAAI,UAAU,YAAY,MAAM,QAAQ,IAAI,CAC1C,MAAK,MAAM,QAAQ,IAAK,MAAK,KAAK;KAElC,MAAK,IAAI;AAGb,QAAO"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/keys/location.ts
|
|
3
|
+
function generateLocationKeys(anchor) {
|
|
4
|
+
const keys = [];
|
|
5
|
+
const geohash = anchor.geohash;
|
|
6
|
+
if (geohash && geohash.length > 0) for (let i = 1; i <= geohash.length; i++) keys.push(`location:${geohash.slice(0, i)}`);
|
|
7
|
+
const city = anchor.city;
|
|
8
|
+
if (city?.trim()) keys.push(`location:city:${city.trim().toLowerCase()}`);
|
|
9
|
+
return keys;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
//#endregion
|
|
13
|
+
exports.generateLocationKeys = generateLocationKeys;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/keys/location.ts
|
|
2
|
+
function generateLocationKeys(anchor) {
|
|
3
|
+
const keys = [];
|
|
4
|
+
const geohash = anchor.geohash;
|
|
5
|
+
if (geohash && geohash.length > 0) for (let i = 1; i <= geohash.length; i++) keys.push(`location:${geohash.slice(0, i)}`);
|
|
6
|
+
const city = anchor.city;
|
|
7
|
+
if (city?.trim()) keys.push(`location:city:${city.trim().toLowerCase()}`);
|
|
8
|
+
return keys;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
//#endregion
|
|
12
|
+
export { generateLocationKeys };
|
|
13
|
+
//# sourceMappingURL=location.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"location.mjs","names":[],"sources":["../../src/keys/location.ts"],"sourcesContent":["/**\n * Location key generator — expands a geohash into prefix keys and optional city key.\n *\n * A geohash \"xn76u\" generates 5 keys: location:x, location:xn, location:xn7,\n * location:xn76, location:xn76u. Enables hierarchical spatial matching.\n *\n * If the anchor has a `city` field, an additional key `location:city:xxx`\n * (lowercase, trimmed) is generated.\n */\nimport type { Anchor } from \"../types.js\";\n\nexport function generateLocationKeys(anchor: Anchor): string[] {\n const keys: string[] = [];\n\n const geohash = anchor.geohash as string | undefined;\n if (geohash && geohash.length > 0) {\n for (let i = 1; i <= geohash.length; i++) {\n keys.push(`location:${geohash.slice(0, i)}`);\n }\n }\n\n const city = anchor.city as string | undefined;\n if (city?.trim()) {\n keys.push(`location:city:${city.trim().toLowerCase()}`);\n }\n\n return keys;\n}\n"],"mappings":";AAWA,SAAgB,qBAAqB,QAA0B;CAC7D,MAAM,OAAiB,EAAE;CAEzB,MAAM,UAAU,OAAO;AACvB,KAAI,WAAW,QAAQ,SAAS,EAC9B,MAAK,IAAI,IAAI,GAAG,KAAK,QAAQ,QAAQ,IACnC,MAAK,KAAK,YAAY,QAAQ,MAAM,GAAG,EAAE,GAAG;CAIhD,MAAM,OAAO,OAAO;AACpB,KAAI,MAAM,MAAM,CACd,MAAK,KAAK,iBAAiB,KAAK,MAAM,CAAC,aAAa,GAAG;AAGzD,QAAO"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const require_host = require('../text/host.cjs');
|
|
2
|
+
|
|
3
|
+
//#region src/keys/normalized.ts
|
|
4
|
+
/**
|
|
5
|
+
* Normalized-value key generator factory.
|
|
6
|
+
*
|
|
7
|
+
* Produces `{type}:{normalized}` keys for string-valued anchor fields.
|
|
8
|
+
* "Normalized" means: trim whitespace + lowercase + strip a leading `www.`.
|
|
9
|
+
*
|
|
10
|
+
* Used as the default generator for custom anchor types declared in
|
|
11
|
+
* `.index/anchor-types/` that do not supply their own keyGenerator —
|
|
12
|
+
* produces field-order-insensitive, case-insensitive keys instead of the
|
|
13
|
+
* JSON.stringify-based exact fallback (which is sensitive to both).
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Create a key generator that produces `{type}:{value.trim().toLowerCase()}`
|
|
17
|
+
* for every string field in the anchor (excluding `type` and `confidence`),
|
|
18
|
+
* additionally stripping a leading `www.` from each value.
|
|
19
|
+
*
|
|
20
|
+
* The `www.` strip is unconditional, not an opt-in: it's what makes `site:`
|
|
21
|
+
* host anchors (`site: host($.url)`) match regardless of the `www.` prefix on
|
|
22
|
+
* either the write or query side (issue #1249). The write side already strips
|
|
23
|
+
* `www.` in `extractHost`, so on stored host values this is a no-op — its job
|
|
24
|
+
* is normalizing raw query values like `www.example.com` that never pass
|
|
25
|
+
* through `host()`. Non-host facet values (UUID tags, status, source) never
|
|
26
|
+
* carry a `www.` prefix, and because the strip runs identically on write and
|
|
27
|
+
* query it can only ever canonicalize, never desync the two sides.
|
|
28
|
+
*
|
|
29
|
+
* @param opts.lowercase - Apply toLowerCase (default true)
|
|
30
|
+
* @param opts.trim - Apply trim (default true)
|
|
31
|
+
* @param opts.fields - Restrict to these field names; all string fields if omitted
|
|
32
|
+
*/
|
|
33
|
+
function createNormalizedValueGenerator(opts) {
|
|
34
|
+
const lowercase = opts?.lowercase !== false;
|
|
35
|
+
const trim = opts?.trim !== false;
|
|
36
|
+
const fields = opts?.fields;
|
|
37
|
+
return (anchor) => {
|
|
38
|
+
const { type, confidence: _, ...data } = anchor;
|
|
39
|
+
const keys = [];
|
|
40
|
+
const normalize = (val) => {
|
|
41
|
+
let n = val;
|
|
42
|
+
if (trim) n = n.trim();
|
|
43
|
+
if (lowercase) n = n.toLowerCase();
|
|
44
|
+
n = require_host.stripWwwPrefix(n);
|
|
45
|
+
if (n) keys.push(`${type}:${n}`);
|
|
46
|
+
};
|
|
47
|
+
if (fields) {
|
|
48
|
+
for (const f of fields) {
|
|
49
|
+
const val = data[f];
|
|
50
|
+
if (typeof val === "string") normalize(val);
|
|
51
|
+
}
|
|
52
|
+
return keys;
|
|
53
|
+
}
|
|
54
|
+
for (const [key, val] of Object.entries(data)) if (key === "values" && Array.isArray(val)) {
|
|
55
|
+
for (const item of val) if (typeof item === "string") normalize(item);
|
|
56
|
+
} else if (typeof val === "string") normalize(val);
|
|
57
|
+
return keys;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
//#endregion
|
|
62
|
+
exports.createNormalizedValueGenerator = createNormalizedValueGenerator;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { stripWwwPrefix } from "../text/host.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/keys/normalized.ts
|
|
4
|
+
/**
|
|
5
|
+
* Normalized-value key generator factory.
|
|
6
|
+
*
|
|
7
|
+
* Produces `{type}:{normalized}` keys for string-valued anchor fields.
|
|
8
|
+
* "Normalized" means: trim whitespace + lowercase + strip a leading `www.`.
|
|
9
|
+
*
|
|
10
|
+
* Used as the default generator for custom anchor types declared in
|
|
11
|
+
* `.index/anchor-types/` that do not supply their own keyGenerator —
|
|
12
|
+
* produces field-order-insensitive, case-insensitive keys instead of the
|
|
13
|
+
* JSON.stringify-based exact fallback (which is sensitive to both).
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Create a key generator that produces `{type}:{value.trim().toLowerCase()}`
|
|
17
|
+
* for every string field in the anchor (excluding `type` and `confidence`),
|
|
18
|
+
* additionally stripping a leading `www.` from each value.
|
|
19
|
+
*
|
|
20
|
+
* The `www.` strip is unconditional, not an opt-in: it's what makes `site:`
|
|
21
|
+
* host anchors (`site: host($.url)`) match regardless of the `www.` prefix on
|
|
22
|
+
* either the write or query side (issue #1249). The write side already strips
|
|
23
|
+
* `www.` in `extractHost`, so on stored host values this is a no-op — its job
|
|
24
|
+
* is normalizing raw query values like `www.example.com` that never pass
|
|
25
|
+
* through `host()`. Non-host facet values (UUID tags, status, source) never
|
|
26
|
+
* carry a `www.` prefix, and because the strip runs identically on write and
|
|
27
|
+
* query it can only ever canonicalize, never desync the two sides.
|
|
28
|
+
*
|
|
29
|
+
* @param opts.lowercase - Apply toLowerCase (default true)
|
|
30
|
+
* @param opts.trim - Apply trim (default true)
|
|
31
|
+
* @param opts.fields - Restrict to these field names; all string fields if omitted
|
|
32
|
+
*/
|
|
33
|
+
function createNormalizedValueGenerator(opts) {
|
|
34
|
+
const lowercase = opts?.lowercase !== false;
|
|
35
|
+
const trim = opts?.trim !== false;
|
|
36
|
+
const fields = opts?.fields;
|
|
37
|
+
return (anchor) => {
|
|
38
|
+
const { type, confidence: _, ...data } = anchor;
|
|
39
|
+
const keys = [];
|
|
40
|
+
const normalize = (val) => {
|
|
41
|
+
let n = val;
|
|
42
|
+
if (trim) n = n.trim();
|
|
43
|
+
if (lowercase) n = n.toLowerCase();
|
|
44
|
+
n = stripWwwPrefix(n);
|
|
45
|
+
if (n) keys.push(`${type}:${n}`);
|
|
46
|
+
};
|
|
47
|
+
if (fields) {
|
|
48
|
+
for (const f of fields) {
|
|
49
|
+
const val = data[f];
|
|
50
|
+
if (typeof val === "string") normalize(val);
|
|
51
|
+
}
|
|
52
|
+
return keys;
|
|
53
|
+
}
|
|
54
|
+
for (const [key, val] of Object.entries(data)) if (key === "values" && Array.isArray(val)) {
|
|
55
|
+
for (const item of val) if (typeof item === "string") normalize(item);
|
|
56
|
+
} else if (typeof val === "string") normalize(val);
|
|
57
|
+
return keys;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
//#endregion
|
|
62
|
+
export { createNormalizedValueGenerator };
|
|
63
|
+
//# sourceMappingURL=normalized.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalized.mjs","names":[],"sources":["../../src/keys/normalized.ts"],"sourcesContent":["/**\n * Normalized-value key generator factory.\n *\n * Produces `{type}:{normalized}` keys for string-valued anchor fields.\n * \"Normalized\" means: trim whitespace + lowercase + strip a leading `www.`.\n *\n * Used as the default generator for custom anchor types declared in\n * `.index/anchor-types/` that do not supply their own keyGenerator —\n * produces field-order-insensitive, case-insensitive keys instead of the\n * JSON.stringify-based exact fallback (which is sensitive to both).\n */\nimport { stripWwwPrefix } from \"../text/host.js\";\nimport type { Anchor, KeyGenerator } from \"../types.js\";\n\n/**\n * Create a key generator that produces `{type}:{value.trim().toLowerCase()}`\n * for every string field in the anchor (excluding `type` and `confidence`),\n * additionally stripping a leading `www.` from each value.\n *\n * The `www.` strip is unconditional, not an opt-in: it's what makes `site:`\n * host anchors (`site: host($.url)`) match regardless of the `www.` prefix on\n * either the write or query side (issue #1249). The write side already strips\n * `www.` in `extractHost`, so on stored host values this is a no-op — its job\n * is normalizing raw query values like `www.example.com` that never pass\n * through `host()`. Non-host facet values (UUID tags, status, source) never\n * carry a `www.` prefix, and because the strip runs identically on write and\n * query it can only ever canonicalize, never desync the two sides.\n *\n * @param opts.lowercase - Apply toLowerCase (default true)\n * @param opts.trim - Apply trim (default true)\n * @param opts.fields - Restrict to these field names; all string fields if omitted\n */\nexport function createNormalizedValueGenerator(opts?: {\n lowercase?: boolean;\n trim?: boolean;\n fields?: string[];\n}): KeyGenerator {\n const lowercase = opts?.lowercase !== false;\n const trim = opts?.trim !== false;\n const fields = opts?.fields;\n\n return (anchor: Anchor): string[] => {\n const { type, confidence: _, ...data } = anchor;\n const keys: string[] = [];\n\n const normalize = (val: string): void => {\n let n = val;\n if (trim) n = n.trim();\n if (lowercase) n = n.toLowerCase();\n n = stripWwwPrefix(n);\n if (n) keys.push(`${type}:${n}`);\n };\n\n if (fields) {\n for (const f of fields) {\n const val = (data as Record<string, unknown>)[f];\n if (typeof val === \"string\") normalize(val);\n }\n return keys;\n }\n\n for (const [key, val] of Object.entries(data as Record<string, unknown>)) {\n if (key === \"values\" && Array.isArray(val)) {\n // Handle the canonical query-side array shape `values: [...]` that\n // queryAnchors synthesizes — each element processed independently.\n for (const item of val) {\n if (typeof item === \"string\") normalize(item);\n }\n } else if (typeof val === \"string\") {\n normalize(val);\n }\n }\n\n return keys;\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,SAAgB,+BAA+B,MAI9B;CACf,MAAM,YAAY,MAAM,cAAc;CACtC,MAAM,OAAO,MAAM,SAAS;CAC5B,MAAM,SAAS,MAAM;AAErB,SAAQ,WAA6B;EACnC,MAAM,EAAE,MAAM,YAAY,GAAG,GAAG,SAAS;EACzC,MAAM,OAAiB,EAAE;EAEzB,MAAM,aAAa,QAAsB;GACvC,IAAI,IAAI;AACR,OAAI,KAAM,KAAI,EAAE,MAAM;AACtB,OAAI,UAAW,KAAI,EAAE,aAAa;AAClC,OAAI,eAAe,EAAE;AACrB,OAAI,EAAG,MAAK,KAAK,GAAG,KAAK,GAAG,IAAI;;AAGlC,MAAI,QAAQ;AACV,QAAK,MAAM,KAAK,QAAQ;IACtB,MAAM,MAAO,KAAiC;AAC9C,QAAI,OAAO,QAAQ,SAAU,WAAU,IAAI;;AAE7C,UAAO;;AAGT,OAAK,MAAM,CAAC,KAAK,QAAQ,OAAO,QAAQ,KAAgC,CACtE,KAAI,QAAQ,YAAY,MAAM,QAAQ,IAAI,EAGxC;QAAK,MAAM,QAAQ,IACjB,KAAI,OAAO,SAAS,SAAU,WAAU,KAAK;aAEtC,OAAO,QAAQ,SACxB,WAAU,IAAI;AAIlB,SAAO"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/keys/person.ts
|
|
3
|
+
function generatePersonKeys(anchor) {
|
|
4
|
+
const keys = [];
|
|
5
|
+
const name = anchor.name;
|
|
6
|
+
if (name?.trim()) keys.push(`person:name:${name.trim().toLowerCase()}`);
|
|
7
|
+
const personRef = anchor.personRef;
|
|
8
|
+
if (personRef) keys.push(`person:ref:${personRef}`);
|
|
9
|
+
const role = anchor.role;
|
|
10
|
+
if (role?.trim()) keys.push(`person:role:${role.trim().toLowerCase()}`);
|
|
11
|
+
return keys;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
//#endregion
|
|
15
|
+
exports.generatePersonKeys = generatePersonKeys;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//#region src/keys/person.ts
|
|
2
|
+
function generatePersonKeys(anchor) {
|
|
3
|
+
const keys = [];
|
|
4
|
+
const name = anchor.name;
|
|
5
|
+
if (name?.trim()) keys.push(`person:name:${name.trim().toLowerCase()}`);
|
|
6
|
+
const personRef = anchor.personRef;
|
|
7
|
+
if (personRef) keys.push(`person:ref:${personRef}`);
|
|
8
|
+
const role = anchor.role;
|
|
9
|
+
if (role?.trim()) keys.push(`person:role:${role.trim().toLowerCase()}`);
|
|
10
|
+
return keys;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
//#endregion
|
|
14
|
+
export { generatePersonKeys };
|
|
15
|
+
//# sourceMappingURL=person.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"person.mjs","names":[],"sources":["../../src/keys/person.ts"],"sourcesContent":["/**\n * Person key generator — generates normalized keys for person anchors.\n *\n * Supports name (lowercased), personRef (DID), and role.\n */\nimport type { Anchor } from \"../types.js\";\n\nexport function generatePersonKeys(anchor: Anchor): string[] {\n const keys: string[] = [];\n\n const name = anchor.name as string | undefined;\n if (name?.trim()) {\n keys.push(`person:name:${name.trim().toLowerCase()}`);\n }\n\n const personRef = anchor.personRef as string | undefined;\n if (personRef) {\n keys.push(`person:ref:${personRef}`);\n }\n\n const role = anchor.role as string | undefined;\n if (role?.trim()) {\n keys.push(`person:role:${role.trim().toLowerCase()}`);\n }\n\n return keys;\n}\n"],"mappings":";AAOA,SAAgB,mBAAmB,QAA0B;CAC3D,MAAM,OAAiB,EAAE;CAEzB,MAAM,OAAO,OAAO;AACpB,KAAI,MAAM,MAAM,CACd,MAAK,KAAK,eAAe,KAAK,MAAM,CAAC,aAAa,GAAG;CAGvD,MAAM,YAAY,OAAO;AACzB,KAAI,UACF,MAAK,KAAK,cAAc,YAAY;CAGtC,MAAM,OAAO,OAAO;AACpB,KAAI,MAAM,MAAM,CACd,MAAK,KAAK,eAAe,KAAK,MAAM,CAAC,aAAa,GAAG;AAGvD,QAAO"}
|