@raybase/core 0.0.2 → 0.3.4
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 +21 -0
- package/dist/.admin-build/AdminApp.css +2 -0
- package/dist/.admin-build/AdminApp.js +77 -0
- package/dist/.admin-docs-build/AdminDocsApp.css +2 -0
- package/dist/.admin-docs-build/AdminDocsApp.js +45 -0
- package/dist/actions.d.ts +347 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +661 -0
- package/dist/actions.js.map +1 -0
- package/dist/admin-assets/admin.7738f50720a8ed7b.css +2 -0
- package/dist/admin-assets/admin.8678cb61db34dc81.js +77 -0
- package/dist/admin-boot.d.ts +94 -0
- package/dist/admin-boot.d.ts.map +1 -0
- package/dist/admin-boot.js +139 -0
- package/dist/admin-boot.js.map +1 -0
- package/dist/admin-docs-content.d.ts +95 -0
- package/dist/admin-docs-content.d.ts.map +1 -0
- package/dist/admin-docs-content.js +463 -0
- package/dist/admin-docs-content.js.map +1 -0
- package/dist/admin-docs-ui.d.ts +11 -0
- package/dist/admin-docs-ui.d.ts.map +1 -0
- package/dist/admin-docs-ui.js +23 -0
- package/dist/admin-docs-ui.js.map +1 -0
- package/dist/admin-kit.d.ts +29 -0
- package/dist/admin-kit.js +1557 -0
- package/dist/admin-role.d.ts +8 -0
- package/dist/admin-role.d.ts.map +1 -0
- package/dist/admin-role.js +22 -0
- package/dist/admin-role.js.map +1 -0
- package/dist/admin-ui.d.ts +3 -0
- package/dist/admin-ui.d.ts.map +1 -0
- package/dist/admin-ui.js +16 -0
- package/dist/admin-ui.js.map +1 -0
- package/dist/agent-ai-chat.d.ts +127 -0
- package/dist/agent-ai-chat.d.ts.map +1 -0
- package/dist/agent-ai-chat.js +5975 -0
- package/dist/agent-ai-chat.js.map +1 -0
- package/dist/agent-chapter-archive.d.ts +214 -0
- package/dist/agent-chapter-archive.d.ts.map +1 -0
- package/dist/agent-chapter-archive.js +696 -0
- package/dist/agent-chapter-archive.js.map +1 -0
- package/dist/agent-chapter-search.d.ts +116 -0
- package/dist/agent-chapter-search.d.ts.map +1 -0
- package/dist/agent-chapter-search.js +605 -0
- package/dist/agent-chapter-search.js.map +1 -0
- package/dist/agent-chapter-tools.d.ts +45 -0
- package/dist/agent-chapter-tools.d.ts.map +1 -0
- package/dist/agent-chapter-tools.js +344 -0
- package/dist/agent-chapter-tools.js.map +1 -0
- package/dist/agent-chapters.d.ts +228 -0
- package/dist/agent-chapters.d.ts.map +1 -0
- package/dist/agent-chapters.js +737 -0
- package/dist/agent-chapters.js.map +1 -0
- package/dist/agent-data-parts.d.ts +167 -0
- package/dist/agent-data-parts.d.ts.map +1 -0
- package/dist/agent-data-parts.js +68 -0
- package/dist/agent-data-parts.js.map +1 -0
- package/dist/agent-errors.d.ts +41 -0
- package/dist/agent-errors.d.ts.map +1 -0
- package/dist/agent-errors.js +191 -0
- package/dist/agent-errors.js.map +1 -0
- package/dist/agent-follow-up-store.d.ts +273 -0
- package/dist/agent-follow-up-store.d.ts.map +1 -0
- package/dist/agent-follow-up-store.js +703 -0
- package/dist/agent-follow-up-store.js.map +1 -0
- package/dist/agent-follow-ups.d.ts +314 -0
- package/dist/agent-follow-ups.d.ts.map +1 -0
- package/dist/agent-follow-ups.js +840 -0
- package/dist/agent-follow-ups.js.map +1 -0
- package/dist/agent-memory.d.ts +323 -0
- package/dist/agent-memory.d.ts.map +1 -0
- package/dist/agent-memory.js +1270 -0
- package/dist/agent-memory.js.map +1 -0
- package/dist/agent-message-parts.d.ts +58 -0
- package/dist/agent-message-parts.d.ts.map +1 -0
- package/dist/agent-message-parts.js +163 -0
- package/dist/agent-message-parts.js.map +1 -0
- package/dist/agent-remote-mcp-credentials.d.ts +154 -0
- package/dist/agent-remote-mcp-credentials.d.ts.map +1 -0
- package/dist/agent-remote-mcp-credentials.js +287 -0
- package/dist/agent-remote-mcp-credentials.js.map +1 -0
- package/dist/agent-remote-mcp-crypto.d.ts +32 -0
- package/dist/agent-remote-mcp-crypto.d.ts.map +1 -0
- package/dist/agent-remote-mcp-crypto.js +101 -0
- package/dist/agent-remote-mcp-crypto.js.map +1 -0
- package/dist/agent-remote-mcp-install.d.ts +299 -0
- package/dist/agent-remote-mcp-install.d.ts.map +1 -0
- package/dist/agent-remote-mcp-install.js +785 -0
- package/dist/agent-remote-mcp-install.js.map +1 -0
- package/dist/agent-remote-mcp-oauth.d.ts +73 -0
- package/dist/agent-remote-mcp-oauth.d.ts.map +1 -0
- package/dist/agent-remote-mcp-oauth.js +344 -0
- package/dist/agent-remote-mcp-oauth.js.map +1 -0
- package/dist/agent-remote-mcp-transport.d.ts +46 -0
- package/dist/agent-remote-mcp-transport.d.ts.map +1 -0
- package/dist/agent-remote-mcp-transport.js +225 -0
- package/dist/agent-remote-mcp-transport.js.map +1 -0
- package/dist/agent-remote-mcp.d.ts +147 -0
- package/dist/agent-remote-mcp.d.ts.map +1 -0
- package/dist/agent-remote-mcp.js +681 -0
- package/dist/agent-remote-mcp.js.map +1 -0
- package/dist/agent-room-redaction.d.ts +66 -0
- package/dist/agent-room-redaction.d.ts.map +1 -0
- package/dist/agent-room-redaction.js +264 -0
- package/dist/agent-room-redaction.js.map +1 -0
- package/dist/agent-rooms.d.ts +209 -0
- package/dist/agent-rooms.d.ts.map +1 -0
- package/dist/agent-rooms.js +244 -0
- package/dist/agent-rooms.js.map +1 -0
- package/dist/agent-runtime.d.ts +541 -0
- package/dist/agent-runtime.d.ts.map +1 -0
- package/dist/agent-runtime.js +4260 -0
- package/dist/agent-runtime.js.map +1 -0
- package/dist/agent-skill-archive.d.ts +20 -0
- package/dist/agent-skill-archive.d.ts.map +1 -0
- package/dist/agent-skill-archive.js +316 -0
- package/dist/agent-skill-archive.js.map +1 -0
- package/dist/agent-skill-discovery.d.ts +96 -0
- package/dist/agent-skill-discovery.d.ts.map +1 -0
- package/dist/agent-skill-discovery.js +464 -0
- package/dist/agent-skill-discovery.js.map +1 -0
- package/dist/agent-skill-format.d.ts +57 -0
- package/dist/agent-skill-format.d.ts.map +1 -0
- package/dist/agent-skill-format.js +238 -0
- package/dist/agent-skill-format.js.map +1 -0
- package/dist/agent-skill-import.d.ts +64 -0
- package/dist/agent-skill-import.d.ts.map +1 -0
- package/dist/agent-skill-import.js +295 -0
- package/dist/agent-skill-import.js.map +1 -0
- package/dist/agent-skill-publish.d.ts +51 -0
- package/dist/agent-skill-publish.d.ts.map +1 -0
- package/dist/agent-skill-publish.js +136 -0
- package/dist/agent-skill-publish.js.map +1 -0
- package/dist/agent-skill-store.d.ts +252 -0
- package/dist/agent-skill-store.d.ts.map +1 -0
- package/dist/agent-skill-store.js +514 -0
- package/dist/agent-skill-store.js.map +1 -0
- package/dist/agent-skills.d.ts +85 -0
- package/dist/agent-skills.d.ts.map +1 -0
- package/dist/agent-skills.js +291 -0
- package/dist/agent-skills.js.map +1 -0
- package/dist/agent-transcript-pager.d.ts +77 -0
- package/dist/agent-transcript-pager.d.ts.map +1 -0
- package/dist/agent-transcript-pager.js +210 -0
- package/dist/agent-transcript-pager.js.map +1 -0
- package/dist/agent-user-data-parts.d.ts +20 -0
- package/dist/agent-user-data-parts.d.ts.map +1 -0
- package/dist/agent-user-data-parts.js +161 -0
- package/dist/agent-user-data-parts.js.map +1 -0
- package/dist/agent-user-transcript.d.ts +46 -0
- package/dist/agent-user-transcript.d.ts.map +1 -0
- package/dist/agent-user-transcript.js +106 -0
- package/dist/agent-user-transcript.js.map +1 -0
- package/dist/agent-voice-notes.d.ts +15 -0
- package/dist/agent-voice-notes.d.ts.map +1 -0
- package/dist/agent-voice-notes.js +17 -0
- package/dist/agent-voice-notes.js.map +1 -0
- package/dist/agent-wakes.d.ts +213 -0
- package/dist/agent-wakes.d.ts.map +1 -0
- package/dist/agent-wakes.js +534 -0
- package/dist/agent-wakes.js.map +1 -0
- package/dist/agent.d.ts +1780 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +3123 -0
- package/dist/agent.js.map +1 -0
- package/dist/ai.d.ts +583 -0
- package/dist/ai.d.ts.map +1 -0
- package/dist/ai.js +1858 -0
- package/dist/ai.js.map +1 -0
- package/dist/app-manifest.d.ts +255 -0
- package/dist/app-manifest.d.ts.map +1 -0
- package/dist/app-manifest.js +372 -0
- package/dist/app-manifest.js.map +1 -0
- package/dist/app-users.d.ts +30 -0
- package/dist/app-users.d.ts.map +1 -0
- package/dist/app-users.js +91 -0
- package/dist/app-users.js.map +1 -0
- package/dist/audit-archive.d.ts +85 -0
- package/dist/audit-archive.d.ts.map +1 -0
- package/dist/audit-archive.js +178 -0
- package/dist/audit-archive.js.map +1 -0
- package/dist/audit.d.ts +54 -0
- package/dist/audit.d.ts.map +1 -0
- package/dist/audit.js +95 -0
- package/dist/audit.js.map +1 -0
- package/dist/auth-logger.d.ts +28 -0
- package/dist/auth-logger.d.ts.map +1 -0
- package/dist/auth-logger.js +53 -0
- package/dist/auth-logger.js.map +1 -0
- package/dist/auth-password-secret.d.ts +59 -0
- package/dist/auth-password-secret.d.ts.map +1 -0
- package/dist/auth-password-secret.js +409 -0
- package/dist/auth-password-secret.js.map +1 -0
- package/dist/auth-schema.d.ts +120 -0
- package/dist/auth-schema.d.ts.map +1 -0
- package/dist/auth-schema.js +285 -0
- package/dist/auth-schema.js.map +1 -0
- package/dist/auth.d.ts +321 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +1562 -0
- package/dist/auth.js.map +1 -0
- package/dist/backup-archive.d.ts +257 -0
- package/dist/backup-archive.d.ts.map +1 -0
- package/dist/backup-archive.js +2068 -0
- package/dist/backup-archive.js.map +1 -0
- package/dist/backup-crypto.d.ts +313 -0
- package/dist/backup-crypto.d.ts.map +1 -0
- package/dist/backup-crypto.js +693 -0
- package/dist/backup-crypto.js.map +1 -0
- package/dist/backup-format.d.ts +200 -0
- package/dist/backup-format.d.ts.map +1 -0
- package/dist/backup-format.js +1247 -0
- package/dist/backup-format.js.map +1 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +15 -0
- package/dist/cli.js.map +1 -0
- package/dist/clock.d.ts +35 -0
- package/dist/clock.d.ts.map +1 -0
- package/dist/clock.js +83 -0
- package/dist/clock.js.map +1 -0
- package/dist/data-client.d.ts +187 -0
- package/dist/data-client.d.ts.map +1 -0
- package/dist/data-client.js +590 -0
- package/dist/data-client.js.map +1 -0
- package/dist/data-secrets.d.ts +164 -0
- package/dist/data-secrets.d.ts.map +1 -0
- package/dist/data-secrets.js +383 -0
- package/dist/data-secrets.js.map +1 -0
- package/dist/db.d.ts +453 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +324 -0
- package/dist/db.js.map +1 -0
- package/dist/dev-activity.d.ts +48 -0
- package/dist/dev-activity.d.ts.map +1 -0
- package/dist/dev-activity.js +50 -0
- package/dist/dev-activity.js.map +1 -0
- package/dist/do.d.ts +429 -0
- package/dist/do.d.ts.map +1 -0
- package/dist/do.js +4526 -0
- package/dist/do.js.map +1 -0
- package/dist/email.d.ts +137 -0
- package/dist/email.d.ts.map +1 -0
- package/dist/email.js +527 -0
- package/dist/email.js.map +1 -0
- package/dist/generated/admin-assets.d.ts +16 -0
- package/dist/generated/admin-assets.d.ts.map +1 -0
- package/dist/generated/admin-assets.js +16 -0
- package/dist/generated/admin-assets.js.map +1 -0
- package/dist/generated/admin-docs-assets.d.ts +3 -0
- package/dist/generated/admin-docs-assets.d.ts.map +1 -0
- package/dist/generated/admin-docs-assets.js +3 -0
- package/dist/generated/admin-docs-assets.js.map +1 -0
- package/dist/handoff-consumer.d.ts +79 -0
- package/dist/handoff-consumer.d.ts.map +1 -0
- package/dist/handoff-consumer.js +300 -0
- package/dist/handoff-consumer.js.map +1 -0
- package/dist/handoff.d.ts +70 -0
- package/dist/handoff.d.ts.map +1 -0
- package/dist/handoff.js +228 -0
- package/dist/handoff.js.map +1 -0
- package/dist/host-auth.d.ts +47 -0
- package/dist/host-auth.d.ts.map +1 -0
- package/dist/host-auth.js +59 -0
- package/dist/host-auth.js.map +1 -0
- package/dist/host-user.d.ts +15 -0
- package/dist/host-user.d.ts.map +1 -0
- package/dist/host-user.js +23 -0
- package/dist/host-user.js.map +1 -0
- package/dist/html.d.ts +35 -0
- package/dist/html.d.ts.map +1 -0
- package/dist/html.js +634 -0
- package/dist/html.js.map +1 -0
- package/dist/http-error.d.ts +13 -0
- package/dist/http-error.d.ts.map +1 -0
- package/dist/http-error.js +15 -0
- package/dist/http-error.js.map +1 -0
- package/dist/imports.d.ts +219 -0
- package/dist/imports.d.ts.map +1 -0
- package/dist/imports.js +571 -0
- package/dist/imports.js.map +1 -0
- package/dist/index.d.ts +78 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +76 -0
- package/dist/index.js.map +1 -0
- package/dist/inference-tape.d.ts +64 -0
- package/dist/inference-tape.d.ts.map +1 -0
- package/dist/inference-tape.js +278 -0
- package/dist/inference-tape.js.map +1 -0
- package/dist/internal.d.ts +15 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +48 -0
- package/dist/internal.js.map +1 -0
- package/dist/jobs.d.ts +796 -0
- package/dist/jobs.d.ts.map +1 -0
- package/dist/jobs.js +538 -0
- package/dist/jobs.js.map +1 -0
- package/dist/json-schema.d.ts +91 -0
- package/dist/json-schema.d.ts.map +1 -0
- package/dist/json-schema.js +884 -0
- package/dist/json-schema.js.map +1 -0
- package/dist/knowledge.d.ts +33 -0
- package/dist/knowledge.d.ts.map +1 -0
- package/dist/knowledge.js +348 -0
- package/dist/knowledge.js.map +1 -0
- package/dist/local-dev-chrome.d.ts +29 -0
- package/dist/local-dev-chrome.d.ts.map +1 -0
- package/dist/local-dev-chrome.js +514 -0
- package/dist/local-dev-chrome.js.map +1 -0
- package/dist/local-dev-personas.d.ts +15 -0
- package/dist/local-dev-personas.d.ts.map +1 -0
- package/dist/local-dev-personas.js +46 -0
- package/dist/local-dev-personas.js.map +1 -0
- package/dist/mcp.d.ts +567 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +2480 -0
- package/dist/mcp.js.map +1 -0
- package/dist/modules.d.ts +145 -0
- package/dist/modules.d.ts.map +1 -0
- package/dist/modules.js +164 -0
- package/dist/modules.js.map +1 -0
- package/dist/notification-client.d.ts +94 -0
- package/dist/notification-client.d.ts.map +1 -0
- package/dist/notification-client.js +127 -0
- package/dist/notification-client.js.map +1 -0
- package/dist/notifications.d.ts +447 -0
- package/dist/notifications.d.ts.map +1 -0
- package/dist/notifications.js +1625 -0
- package/dist/notifications.js.map +1 -0
- package/dist/operator-data-query.d.ts +117 -0
- package/dist/operator-data-query.d.ts.map +1 -0
- package/dist/operator-data-query.js +431 -0
- package/dist/operator-data-query.js.map +1 -0
- package/dist/operator-mcp.d.ts +63 -0
- package/dist/operator-mcp.d.ts.map +1 -0
- package/dist/operator-mcp.js +2828 -0
- package/dist/operator-mcp.js.map +1 -0
- package/dist/outbound-https.d.ts +24 -0
- package/dist/outbound-https.d.ts.map +1 -0
- package/dist/outbound-https.js +172 -0
- package/dist/outbound-https.js.map +1 -0
- package/dist/pitr.d.ts +60 -0
- package/dist/pitr.d.ts.map +1 -0
- package/dist/pitr.js +94 -0
- package/dist/pitr.js.map +1 -0
- package/dist/platform.d.ts +199 -0
- package/dist/platform.d.ts.map +1 -0
- package/dist/platform.js +196 -0
- package/dist/platform.js.map +1 -0
- package/dist/policies.d.ts +115 -0
- package/dist/policies.d.ts.map +1 -0
- package/dist/policies.js +158 -0
- package/dist/policies.js.map +1 -0
- package/dist/principal.d.ts +143 -0
- package/dist/principal.d.ts.map +1 -0
- package/dist/principal.js +174 -0
- package/dist/principal.js.map +1 -0
- package/dist/realtime.d.ts +280 -0
- package/dist/realtime.d.ts.map +1 -0
- package/dist/realtime.js +578 -0
- package/dist/realtime.js.map +1 -0
- package/dist/retrieval.d.ts +263 -0
- package/dist/retrieval.d.ts.map +1 -0
- package/dist/retrieval.js +602 -0
- package/dist/retrieval.js.map +1 -0
- package/dist/runtime-error-client.d.ts +10 -0
- package/dist/runtime-error-client.d.ts.map +1 -0
- package/dist/runtime-error-client.js +70 -0
- package/dist/runtime-error-client.js.map +1 -0
- package/dist/runtime-errors.d.ts +180 -0
- package/dist/runtime-errors.d.ts.map +1 -0
- package/dist/runtime-errors.js +572 -0
- package/dist/runtime-errors.js.map +1 -0
- package/dist/schema.d.ts +434 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +232 -0
- package/dist/schema.js.map +1 -0
- package/dist/search.d.ts +216 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +785 -0
- package/dist/search.js.map +1 -0
- package/dist/settings.d.ts +191 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +478 -0
- package/dist/settings.js.map +1 -0
- package/dist/sql.d.ts +7 -0
- package/dist/sql.d.ts.map +1 -0
- package/dist/sql.js +30 -0
- package/dist/sql.js.map +1 -0
- package/dist/storage-client.d.ts +69 -0
- package/dist/storage-client.d.ts.map +1 -0
- package/dist/storage-client.js +469 -0
- package/dist/storage-client.js.map +1 -0
- package/dist/storage.d.ts +21 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +82 -0
- package/dist/storage.js.map +1 -0
- package/dist/subject-domains.d.ts +15 -0
- package/dist/subject-domains.d.ts.map +1 -0
- package/dist/subject-domains.js +38 -0
- package/dist/subject-domains.js.map +1 -0
- package/dist/support.d.ts +132 -0
- package/dist/support.d.ts.map +1 -0
- package/dist/support.js +483 -0
- package/dist/support.js.map +1 -0
- package/dist/system-tables.d.ts +12 -0
- package/dist/system-tables.d.ts.map +1 -0
- package/dist/system-tables.js +225 -0
- package/dist/system-tables.js.map +1 -0
- package/dist/test.d.ts +57 -0
- package/dist/test.d.ts.map +1 -0
- package/dist/test.js +454 -0
- package/dist/test.js.map +1 -0
- package/dist/vite.d.ts +82 -0
- package/dist/vite.d.ts.map +1 -0
- package/dist/vite.js +82 -0
- package/dist/vite.js.map +1 -0
- package/dist/webpush-crypto.d.ts +39 -0
- package/dist/webpush-crypto.d.ts.map +1 -0
- package/dist/webpush-crypto.js +213 -0
- package/dist/webpush-crypto.js.map +1 -0
- package/dist/webpush-private.d.ts +11 -0
- package/dist/webpush-private.d.ts.map +1 -0
- package/dist/webpush-private.js +11 -0
- package/dist/webpush-private.js.map +1 -0
- package/dist/webpush.d.ts +86 -0
- package/dist/webpush.d.ts.map +1 -0
- package/dist/webpush.js +375 -0
- package/dist/webpush.js.map +1 -0
- package/dist/worker.d.ts +185 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +13021 -0
- package/dist/worker.js.map +1 -0
- package/package.json +161 -8
- package/README.md +0 -3
package/dist/search.js
ADDED
|
@@ -0,0 +1,785 @@
|
|
|
1
|
+
import { normalizeNamedDefinitions, stableJson } from "./internal.js";
|
|
2
|
+
import { nowIso, randomId } from "./sql.js";
|
|
3
|
+
import { chunkRetrievalText } from "./retrieval.js";
|
|
4
|
+
export const raybaseSearchDocumentsTableName = "_search_documents";
|
|
5
|
+
export const raybaseSearchReindexRunsTableName = "_search_reindex_runs";
|
|
6
|
+
export const raybaseSearchFtsTableName = "_search_fts";
|
|
7
|
+
export const raybaseSearchRebuildStateKey = "search.fts.rebuildRequired";
|
|
8
|
+
export const raybaseSearchSchemaSql = `
|
|
9
|
+
CREATE TABLE IF NOT EXISTS "_search_documents" (
|
|
10
|
+
"rowid" INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
11
|
+
"id" TEXT NOT NULL UNIQUE,
|
|
12
|
+
"indexName" TEXT NOT NULL,
|
|
13
|
+
"sourceType" TEXT NOT NULL,
|
|
14
|
+
"sourceTable" TEXT,
|
|
15
|
+
"sourceId" TEXT NOT NULL,
|
|
16
|
+
"sourceVersion" TEXT,
|
|
17
|
+
"chunkKey" TEXT NOT NULL,
|
|
18
|
+
"chunkOrdinal" INTEGER NOT NULL,
|
|
19
|
+
"title" TEXT NOT NULL,
|
|
20
|
+
"body" TEXT NOT NULL,
|
|
21
|
+
"url" TEXT,
|
|
22
|
+
"textHash" TEXT NOT NULL,
|
|
23
|
+
"visibility" TEXT NOT NULL,
|
|
24
|
+
"metadata" TEXT,
|
|
25
|
+
"status" TEXT NOT NULL,
|
|
26
|
+
"lastError" TEXT,
|
|
27
|
+
"indexedAt" TEXT,
|
|
28
|
+
"updatedAt" TEXT NOT NULL
|
|
29
|
+
);
|
|
30
|
+
CREATE INDEX IF NOT EXISTS "_search_documents_source_idx"
|
|
31
|
+
ON "_search_documents" ("sourceType", "sourceTable", "sourceId");
|
|
32
|
+
CREATE INDEX IF NOT EXISTS "_search_documents_index_idx"
|
|
33
|
+
ON "_search_documents" ("indexName", "status", "updatedAt");
|
|
34
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS "_search_fts" USING fts5(
|
|
35
|
+
"title",
|
|
36
|
+
"body",
|
|
37
|
+
content='_search_documents',
|
|
38
|
+
content_rowid='rowid',
|
|
39
|
+
tokenize='unicode61'
|
|
40
|
+
);
|
|
41
|
+
CREATE TABLE IF NOT EXISTS "_search_reindex_runs" (
|
|
42
|
+
"id" TEXT PRIMARY KEY NOT NULL,
|
|
43
|
+
"indexName" TEXT NOT NULL,
|
|
44
|
+
"status" TEXT NOT NULL,
|
|
45
|
+
"actorId" TEXT,
|
|
46
|
+
"actorKind" TEXT,
|
|
47
|
+
"totalSources" INTEGER NOT NULL DEFAULT 0,
|
|
48
|
+
"totalChunks" INTEGER NOT NULL DEFAULT 0,
|
|
49
|
+
"upsertedChunks" INTEGER NOT NULL DEFAULT 0,
|
|
50
|
+
"deletedChunks" INTEGER NOT NULL DEFAULT 0,
|
|
51
|
+
"skippedChunks" INTEGER NOT NULL DEFAULT 0,
|
|
52
|
+
"errorCount" INTEGER NOT NULL DEFAULT 0,
|
|
53
|
+
"startedAt" TEXT NOT NULL,
|
|
54
|
+
"completedAt" TEXT,
|
|
55
|
+
"summaryJson" TEXT
|
|
56
|
+
);
|
|
57
|
+
CREATE INDEX IF NOT EXISTS "_search_reindex_runs_index_started_idx"
|
|
58
|
+
ON "_search_reindex_runs" ("indexName", "startedAt");
|
|
59
|
+
`;
|
|
60
|
+
const searchNamePattern = /^[A-Za-z][A-Za-z0-9_.:-]{0,127}$/;
|
|
61
|
+
const defaultSearchChunkMaxChars = 2000;
|
|
62
|
+
const defaultSearchChunkOverlapChars = 0;
|
|
63
|
+
const defaultSearchSnippetChars = 240;
|
|
64
|
+
const ftsShadowTables = new Set([
|
|
65
|
+
"_search_fts_data",
|
|
66
|
+
"_search_fts_idx",
|
|
67
|
+
"_search_fts_content",
|
|
68
|
+
"_search_fts_docsize",
|
|
69
|
+
"_search_fts_config"
|
|
70
|
+
]);
|
|
71
|
+
export function defineSearchIndex(definition) {
|
|
72
|
+
validateSearchName(definition.name);
|
|
73
|
+
validateSearchIndex(definition);
|
|
74
|
+
return { ...definition };
|
|
75
|
+
}
|
|
76
|
+
export function defineSearch(definition) {
|
|
77
|
+
const indexes = normalizeNamedDefinitions("search index", definition.indexes, validateSearchName);
|
|
78
|
+
for (const index of Object.values(indexes))
|
|
79
|
+
validateSearchIndex(index);
|
|
80
|
+
return { ...definition, indexes };
|
|
81
|
+
}
|
|
82
|
+
function validateSearchIndex(index) {
|
|
83
|
+
if (!index.sources.length)
|
|
84
|
+
throw new Error(`Search index ${index.name} must declare at least one source`);
|
|
85
|
+
if (index.tokenizer && index.tokenizer !== "unicode61")
|
|
86
|
+
throw new Error(`Search index ${index.name} only supports the unicode61 tokenizer in v1`);
|
|
87
|
+
if (index.ranking && index.ranking !== "bm25")
|
|
88
|
+
throw new Error(`Search index ${index.name} only supports bm25 ranking in v1`);
|
|
89
|
+
}
|
|
90
|
+
export function tableSearchSource(table, options) {
|
|
91
|
+
return { kind: "table", table, ...options };
|
|
92
|
+
}
|
|
93
|
+
export function corpusSearchSource(name, options) {
|
|
94
|
+
validateSearchName(name);
|
|
95
|
+
return { kind: "corpus", name, ...options };
|
|
96
|
+
}
|
|
97
|
+
export function validateSearch(search, schema) {
|
|
98
|
+
if (!search)
|
|
99
|
+
return;
|
|
100
|
+
for (const index of Object.values(search.indexes)) {
|
|
101
|
+
validateSearchName(index.name);
|
|
102
|
+
if (!index.sources.length)
|
|
103
|
+
throw new Error(`Search index ${index.name} must declare at least one source`);
|
|
104
|
+
for (const source of index.sources) {
|
|
105
|
+
if (source.kind === "table") {
|
|
106
|
+
tableKeyFor(schema, source.table);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
validateSearchName(source.name);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
export function validateSearchName(name) {
|
|
115
|
+
if (!searchNamePattern.test(name)) {
|
|
116
|
+
throw new Error(`Invalid Raybase search index name "${name}". Use letters, numbers, "_", "-", ".", or ":".`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
export function isRaybaseSearchInternalTable(name) {
|
|
120
|
+
return name === raybaseSearchFtsTableName
|
|
121
|
+
|| ftsShadowTables.has(name)
|
|
122
|
+
|| name === "_agent_chapter_fts"
|
|
123
|
+
|| name.startsWith("_agent_chapter_fts_");
|
|
124
|
+
}
|
|
125
|
+
export function isRaybaseSearchTable(name) {
|
|
126
|
+
return name === raybaseSearchDocumentsTableName || name === raybaseSearchReindexRunsTableName || isRaybaseSearchInternalTable(name);
|
|
127
|
+
}
|
|
128
|
+
export function parseSearchQuery(query) {
|
|
129
|
+
const raw = query.trim();
|
|
130
|
+
if (!raw)
|
|
131
|
+
return null;
|
|
132
|
+
const clauses = [];
|
|
133
|
+
const operators = [];
|
|
134
|
+
let pendingOperator = null;
|
|
135
|
+
for (const token of searchQueryTokens(raw)) {
|
|
136
|
+
if (token.kind === "operator") {
|
|
137
|
+
if (!clauses.length)
|
|
138
|
+
continue;
|
|
139
|
+
if (token.value === "NOT") {
|
|
140
|
+
pendingOperator = null;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
pendingOperator = token.value;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (clauses.length >= 12)
|
|
147
|
+
break;
|
|
148
|
+
if (clauses.length)
|
|
149
|
+
operators.push(pendingOperator ?? "AND");
|
|
150
|
+
clauses.push(token);
|
|
151
|
+
pendingOperator = null;
|
|
152
|
+
}
|
|
153
|
+
if (!clauses.length)
|
|
154
|
+
return null;
|
|
155
|
+
const finalClause = clauses.at(-1);
|
|
156
|
+
if (finalClause?.prefixMatch)
|
|
157
|
+
finalClause.match = finalClause.prefixMatch;
|
|
158
|
+
const terms = clauses.flatMap((clause) => clause.terms).slice(0, 12);
|
|
159
|
+
let match = clauses[0]?.match ?? "";
|
|
160
|
+
for (let index = 1; index < clauses.length; index += 1) {
|
|
161
|
+
match += ` ${operators[index - 1] ?? "AND"} ${clauses[index]?.match ?? ""}`;
|
|
162
|
+
}
|
|
163
|
+
return { raw, terms, match };
|
|
164
|
+
}
|
|
165
|
+
function searchQueryTokens(raw) {
|
|
166
|
+
const tokens = [];
|
|
167
|
+
const pattern = /"([^"]*)"|([\p{L}\p{N}_]+)(\*)?/gu;
|
|
168
|
+
for (const found of raw.matchAll(pattern)) {
|
|
169
|
+
const phrase = found[1];
|
|
170
|
+
if (phrase !== undefined) {
|
|
171
|
+
const words = searchWords(phrase, { minLength: 1 });
|
|
172
|
+
const terms = words.filter((term) => term.length > 1);
|
|
173
|
+
if (!terms.length)
|
|
174
|
+
continue;
|
|
175
|
+
tokens.push({ kind: "phrase", match: quoteFtsPhrase(words), terms });
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
const word = found[2]?.toLowerCase();
|
|
179
|
+
if (!word || word.length <= 1)
|
|
180
|
+
continue;
|
|
181
|
+
if (word === "and" || word === "or" || word === "not") {
|
|
182
|
+
tokens.push({ kind: "operator", value: word.toUpperCase() });
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
const prefixMatch = `${word}*`;
|
|
186
|
+
tokens.push({ kind: "term", match: found[3] === "*" ? prefixMatch : quoteFtsPhrase([word]), prefixMatch, terms: [word] });
|
|
187
|
+
}
|
|
188
|
+
return tokens;
|
|
189
|
+
}
|
|
190
|
+
function searchWords(value, options = {}) {
|
|
191
|
+
const minLength = options.minLength ?? 2;
|
|
192
|
+
return Array.from(value.matchAll(/[\p{L}\p{N}_]+/gu))
|
|
193
|
+
.map((match) => match[0].toLowerCase())
|
|
194
|
+
.filter((term) => term.length >= minLength);
|
|
195
|
+
}
|
|
196
|
+
function quoteFtsPhrase(terms) {
|
|
197
|
+
return `"${terms.join(" ").replace(/"/g, '""')}"`;
|
|
198
|
+
}
|
|
199
|
+
export async function searchDocumentIdForChunk(input) {
|
|
200
|
+
const digest = await sha256Base64Url([input.indexName, input.sourceType, input.sourceTable ?? "", input.sourceId, input.chunkKey].join("|"));
|
|
201
|
+
return `search_${digest.slice(0, 40)}`;
|
|
202
|
+
}
|
|
203
|
+
export async function chunkSearchText(text, options = {}) {
|
|
204
|
+
return chunkRetrievalText(text, {
|
|
205
|
+
maxChars: options.maxChars ?? defaultSearchChunkMaxChars,
|
|
206
|
+
overlapChars: options.overlapChars ?? defaultSearchChunkOverlapChars
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
export function simpleSearchSnippet(text, terms, maxChars = defaultSearchSnippetChars) {
|
|
210
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
211
|
+
if (!normalized)
|
|
212
|
+
return "";
|
|
213
|
+
const lower = normalized.toLowerCase();
|
|
214
|
+
const first = terms
|
|
215
|
+
.map((term) => lower.indexOf(term.toLowerCase()))
|
|
216
|
+
.filter((position) => position >= 0)
|
|
217
|
+
.sort((left, right) => left - right)[0] ?? 0;
|
|
218
|
+
const half = Math.floor(maxChars / 2);
|
|
219
|
+
const start = Math.max(0, first - half);
|
|
220
|
+
const end = Math.min(normalized.length, start + maxChars);
|
|
221
|
+
const prefix = start > 0 ? "..." : "";
|
|
222
|
+
const suffix = end < normalized.length ? "..." : "";
|
|
223
|
+
return `${prefix}${normalized.slice(start, end).trim()}${suffix}`;
|
|
224
|
+
}
|
|
225
|
+
export function createRaybaseSearchClient(options = {}) {
|
|
226
|
+
return {
|
|
227
|
+
async search(input) {
|
|
228
|
+
const request = typeof input === "string" ? { query: input } : input;
|
|
229
|
+
const fetcher = options.fetch ?? fetch;
|
|
230
|
+
const baseUrl = options.baseUrl?.replace(/\/$/, "") ?? "";
|
|
231
|
+
const url = new URL(`${baseUrl}/api/search`, "http://raybase.local");
|
|
232
|
+
url.searchParams.set("q", request.query);
|
|
233
|
+
const index = request.index ?? options.defaultIndex;
|
|
234
|
+
if (index)
|
|
235
|
+
url.searchParams.set("index", index);
|
|
236
|
+
if (request.limit !== undefined)
|
|
237
|
+
url.searchParams.set("limit", String(request.limit));
|
|
238
|
+
if (request.sourceTable)
|
|
239
|
+
url.searchParams.append("sourceTable", request.sourceTable);
|
|
240
|
+
for (const sourceTable of request.sourceTables ?? [])
|
|
241
|
+
url.searchParams.append("sourceTable", sourceTable);
|
|
242
|
+
const href = options.baseUrl && /^https?:\/\//.test(options.baseUrl) ? url.toString() : `${url.pathname}${url.search}`;
|
|
243
|
+
const response = await fetcher(href, { credentials: "same-origin" });
|
|
244
|
+
const body = await response.json().catch(() => ({}));
|
|
245
|
+
if (!response.ok) {
|
|
246
|
+
const message = body && typeof body === "object" && typeof body.error === "string"
|
|
247
|
+
? body.error
|
|
248
|
+
: `Search request failed with ${response.status}`;
|
|
249
|
+
throw new Error(message);
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
results: Array.isArray(body.results) ? body.results : [],
|
|
253
|
+
query: typeof body.query === "string" ? body.query : request.query
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
export async function ensureSearchSchema(control) {
|
|
259
|
+
for (const statement of splitSqlStatements(raybaseSearchSchemaSql))
|
|
260
|
+
await control.run(statement);
|
|
261
|
+
}
|
|
262
|
+
export async function reindexSearchIndex(input) {
|
|
263
|
+
await ensureSearchSchema(input.control);
|
|
264
|
+
const runId = randomId("search_reindex");
|
|
265
|
+
const startedAt = nowIso();
|
|
266
|
+
const counts = { totalSources: 0, totalChunks: 0, upsertedChunks: 0, deletedChunks: 0, skippedChunks: 0, errorCount: 0 };
|
|
267
|
+
await input.control.run(`INSERT INTO "_search_reindex_runs"
|
|
268
|
+
("id", "indexName", "status", "actorId", "actorKind", "startedAt", "summaryJson")
|
|
269
|
+
VALUES (?, ?, 'running', ?, ?, ?, ?)`, [runId, input.index.name, input.actorId ?? null, input.actorKind ?? "operator", startedAt, JSON.stringify({ sources: input.index.sources.length })]);
|
|
270
|
+
try {
|
|
271
|
+
for (const source of input.index.sources) {
|
|
272
|
+
if (source.kind === "table") {
|
|
273
|
+
const activeDocumentIds = new Set();
|
|
274
|
+
const tableKey = tableKeyFor(input.schema, source.table);
|
|
275
|
+
let offset = 0;
|
|
276
|
+
let remaining = source.limit ?? Number.POSITIVE_INFINITY;
|
|
277
|
+
while (remaining > 0) {
|
|
278
|
+
const limit = Math.min(500, remaining);
|
|
279
|
+
const rows = await input.db.list(tableKey, { limit, offset });
|
|
280
|
+
if (!rows.rows.length)
|
|
281
|
+
break;
|
|
282
|
+
counts.totalSources += rows.rows.length;
|
|
283
|
+
for (const row of rows.rows) {
|
|
284
|
+
const result = await indexTableSearchRow({
|
|
285
|
+
control: input.control,
|
|
286
|
+
index: input.index,
|
|
287
|
+
source,
|
|
288
|
+
row
|
|
289
|
+
});
|
|
290
|
+
result.documentIds.forEach((id) => activeDocumentIds.add(id));
|
|
291
|
+
counts.totalChunks += result.indexed;
|
|
292
|
+
counts.upsertedChunks += result.upserted;
|
|
293
|
+
counts.skippedChunks += result.skipped;
|
|
294
|
+
counts.errorCount += result.errors;
|
|
295
|
+
}
|
|
296
|
+
offset += rows.rows.length;
|
|
297
|
+
remaining -= rows.rows.length;
|
|
298
|
+
if (rows.rows.length < limit)
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
const existing = await input.control.all(`SELECT "id" FROM "_search_documents"
|
|
302
|
+
WHERE "indexName" = ? AND "sourceType" = 'table' AND "sourceTable" = ? AND "status" != 'deleted'`, [input.index.name, source.table.name]);
|
|
303
|
+
const stale = existing.filter((row) => !activeDocumentIds.has(row.id));
|
|
304
|
+
for (const row of stale) {
|
|
305
|
+
await deleteSearchDocumentById(input.control, row.id);
|
|
306
|
+
counts.deletedChunks += 1;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
const result = await indexCorpusSearchSource({
|
|
311
|
+
control: input.control,
|
|
312
|
+
index: input.index,
|
|
313
|
+
source
|
|
314
|
+
});
|
|
315
|
+
counts.totalSources += result.sources;
|
|
316
|
+
counts.totalChunks += result.indexed;
|
|
317
|
+
counts.upsertedChunks += result.upserted;
|
|
318
|
+
counts.deletedChunks += result.deleted;
|
|
319
|
+
counts.skippedChunks += result.skipped;
|
|
320
|
+
counts.errorCount += result.errors;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
await completeSearchReindexRun(input.control, runId, "succeeded", counts, {});
|
|
324
|
+
return {
|
|
325
|
+
ok: true,
|
|
326
|
+
runId,
|
|
327
|
+
indexed: counts.totalChunks,
|
|
328
|
+
upserted: counts.upsertedChunks,
|
|
329
|
+
deleted: counts.deletedChunks,
|
|
330
|
+
skipped: counts.skippedChunks,
|
|
331
|
+
errors: counts.errorCount
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
catch (error) {
|
|
335
|
+
counts.errorCount += 1;
|
|
336
|
+
await completeSearchReindexRun(input.control, runId, "failed", counts, { error: error instanceof Error ? error.message : String(error) });
|
|
337
|
+
throw error;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
export async function updateSearchForDataChange(input) {
|
|
341
|
+
if (!input.search)
|
|
342
|
+
return;
|
|
343
|
+
await ensureSearchSchema(input.control);
|
|
344
|
+
for (const index of Object.values(input.search.indexes)) {
|
|
345
|
+
for (const source of index.sources) {
|
|
346
|
+
if (source.kind !== "table" || source.table.name !== input.change.tableName)
|
|
347
|
+
continue;
|
|
348
|
+
if (input.change.op === "delete") {
|
|
349
|
+
await deleteSearchDocumentsForSource(input.control, {
|
|
350
|
+
indexName: index.name,
|
|
351
|
+
sourceType: "table",
|
|
352
|
+
sourceTable: source.table.name,
|
|
353
|
+
sourceId: input.change.rowId
|
|
354
|
+
});
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
const row = input.change.newRow;
|
|
358
|
+
if (!row)
|
|
359
|
+
continue;
|
|
360
|
+
await indexTableSearchRow({ control: input.control, index, source, row });
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
export async function searchIndex(input) {
|
|
365
|
+
if (!input.search)
|
|
366
|
+
return { results: [], query: parseSearchQuery(input.query) };
|
|
367
|
+
const indexName = input.indexName || "app";
|
|
368
|
+
const index = input.search.indexes[indexName];
|
|
369
|
+
if (!index)
|
|
370
|
+
throw new Error(`Unknown search index: ${indexName}`);
|
|
371
|
+
const requestedSourceTables = input.sourceTables?.filter(Boolean) ?? [];
|
|
372
|
+
const selectedTableSources = index.sources.filter((source) => source.kind === "table" && (!requestedSourceTables.length || requestedSourceTables.includes(source.table.name)));
|
|
373
|
+
for (const source of selectedTableSources) {
|
|
374
|
+
await input.db.assertReadable(tableKeyFor(input.schema, source.table));
|
|
375
|
+
}
|
|
376
|
+
await ensureSearchSchema(input.control);
|
|
377
|
+
const query = parseSearchQuery(input.query);
|
|
378
|
+
if (!query)
|
|
379
|
+
return { results: [], query };
|
|
380
|
+
const limit = Math.min(Math.max(input.limit ?? 20, 1), 50);
|
|
381
|
+
const params = [query.match, index.name, "indexed"];
|
|
382
|
+
const where = [`_search_fts MATCH ?`, `d."indexName" = ?`, `d."status" = ?`];
|
|
383
|
+
if (requestedSourceTables.length) {
|
|
384
|
+
where.push(`d."sourceTable" IN (${requestedSourceTables.map(() => "?").join(", ")})`);
|
|
385
|
+
params.push(...requestedSourceTables);
|
|
386
|
+
}
|
|
387
|
+
if (input.publicOnly) {
|
|
388
|
+
where.push(`d."visibility" = 'public'`);
|
|
389
|
+
}
|
|
390
|
+
const candidates = await input.control.all(`SELECT d."id", d."indexName", d."sourceType", d."sourceTable", d."sourceId", d."sourceVersion", d."chunkKey",
|
|
391
|
+
d."chunkOrdinal", d."title", d."body", d."url", d."textHash", d."visibility", d."metadata",
|
|
392
|
+
bm25(_search_fts) AS "rank"
|
|
393
|
+
FROM "_search_fts"
|
|
394
|
+
JOIN "_search_documents" d ON d."rowid" = _search_fts.rowid
|
|
395
|
+
WHERE ${where.join(" AND ")}
|
|
396
|
+
ORDER BY "rank" ASC
|
|
397
|
+
LIMIT ?`, [...params, Math.min(limit * 4, 200)]).catch(() => []);
|
|
398
|
+
const results = [];
|
|
399
|
+
const seenRows = new Set();
|
|
400
|
+
for (const candidate of candidates) {
|
|
401
|
+
if (results.length >= limit)
|
|
402
|
+
break;
|
|
403
|
+
const sourceType = candidate.sourceType === "corpus" ? "corpus" : "table";
|
|
404
|
+
const sourceTable = asNullableString(candidate.sourceTable);
|
|
405
|
+
const sourceId = String(candidate.sourceId ?? "");
|
|
406
|
+
if (sourceType === "table") {
|
|
407
|
+
if (!sourceTable)
|
|
408
|
+
continue;
|
|
409
|
+
const source = findTableSearchSource(index, sourceTable);
|
|
410
|
+
if (!source)
|
|
411
|
+
continue;
|
|
412
|
+
const rowKey = `${sourceTable}:${sourceId}`;
|
|
413
|
+
if (seenRows.has(rowKey))
|
|
414
|
+
continue;
|
|
415
|
+
const tableKey = tableKeyFor(input.schema, source.table);
|
|
416
|
+
const row = await input.db.get(tableKey, sourceId);
|
|
417
|
+
if (!row)
|
|
418
|
+
continue;
|
|
419
|
+
const indexed = await sourceDocumentsForTableRow(index, source, row);
|
|
420
|
+
const current = indexed.find((document) => document.id === candidate.id || document.chunkKey === candidate.chunkKey);
|
|
421
|
+
if (!current || current.textHash !== candidate.textHash) {
|
|
422
|
+
await input.control.run(`UPDATE "_search_documents" SET "status" = 'pending', "lastError" = ?, "updatedAt" = ? WHERE "id" = ?`, ["Source row changed after indexing", nowIso(), String(candidate.id)]);
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
seenRows.add(rowKey);
|
|
426
|
+
results.push({
|
|
427
|
+
id: String(candidate.id),
|
|
428
|
+
indexName: index.name,
|
|
429
|
+
sourceType,
|
|
430
|
+
sourceTable,
|
|
431
|
+
sourceId,
|
|
432
|
+
title: current.title,
|
|
433
|
+
url: current.url ?? null,
|
|
434
|
+
snippet: simpleSearchSnippet(current.body, query.terms, index.maxSnippetChars ?? defaultSearchSnippetChars),
|
|
435
|
+
metadata: current.metadata,
|
|
436
|
+
visibility: current.visibility
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
results.push({
|
|
441
|
+
id: String(candidate.id),
|
|
442
|
+
indexName: index.name,
|
|
443
|
+
sourceType,
|
|
444
|
+
sourceTable: null,
|
|
445
|
+
sourceId,
|
|
446
|
+
title: String(candidate.title ?? ""),
|
|
447
|
+
url: asNullableString(candidate.url),
|
|
448
|
+
snippet: simpleSearchSnippet(String(candidate.body ?? ""), query.terms, index.maxSnippetChars ?? defaultSearchSnippetChars),
|
|
449
|
+
metadata: parseJson(candidate.metadata),
|
|
450
|
+
visibility: candidate.visibility === "public" ? "public" : "private"
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return { results, query };
|
|
455
|
+
}
|
|
456
|
+
export async function rebuildSearchFts(input) {
|
|
457
|
+
await ensureSearchSchema(input.control);
|
|
458
|
+
if (!input.indexName) {
|
|
459
|
+
await input.control.run(`INSERT INTO "_search_fts"("_search_fts") VALUES('rebuild')`);
|
|
460
|
+
return { ok: true, rebuilt: -1 };
|
|
461
|
+
}
|
|
462
|
+
const docs = await input.control.all(`SELECT "rowid", "title", "body" FROM "_search_documents" WHERE "indexName" = ? AND "status" = 'indexed'`, [input.indexName]);
|
|
463
|
+
for (const doc of docs) {
|
|
464
|
+
await input.control.run(`INSERT OR REPLACE INTO "_search_fts" ("rowid", "title", "body") VALUES (?, ?, ?)`, [
|
|
465
|
+
Number(doc.rowid),
|
|
466
|
+
String(doc.title ?? ""),
|
|
467
|
+
String(doc.body ?? "")
|
|
468
|
+
]);
|
|
469
|
+
}
|
|
470
|
+
return { ok: true, rebuilt: docs.length };
|
|
471
|
+
}
|
|
472
|
+
export async function clearSearchIndex(input) {
|
|
473
|
+
await ensureSearchSchema(input.control);
|
|
474
|
+
const docs = await input.control.all(`SELECT "id" FROM "_search_documents" WHERE "indexName" = ?`, [input.indexName]);
|
|
475
|
+
for (const doc of docs)
|
|
476
|
+
await deleteSearchDocumentById(input.control, doc.id);
|
|
477
|
+
return { ok: true, deleted: docs.length };
|
|
478
|
+
}
|
|
479
|
+
export async function searchIndexesSummary(input) {
|
|
480
|
+
await ensureSearchSchema(input.control).catch(() => undefined);
|
|
481
|
+
const rebuildRequired = Boolean(await readSearchRebuildRequired(input.control));
|
|
482
|
+
const indexes = [];
|
|
483
|
+
for (const index of Object.values(input.search?.indexes ?? {})) {
|
|
484
|
+
const countRows = await input.control.all(`SELECT "status", COUNT(*) as "count" FROM "_search_documents" WHERE "indexName" = ? GROUP BY "status"`, [index.name]).catch(() => []);
|
|
485
|
+
const lastRun = await input.control.first(`SELECT * FROM "_search_reindex_runs" WHERE "indexName" = ? ORDER BY "startedAt" DESC LIMIT 1`, [index.name]).catch(() => null);
|
|
486
|
+
indexes.push({
|
|
487
|
+
name: index.name,
|
|
488
|
+
title: index.title,
|
|
489
|
+
description: index.description,
|
|
490
|
+
sources: index.sources.map((source) => source.kind === "table"
|
|
491
|
+
? { kind: "table", table: source.table.name, label: source.table.label ?? null }
|
|
492
|
+
: { kind: "corpus", name: source.name, title: source.title ?? null }),
|
|
493
|
+
counts: Object.fromEntries(countRows.map((row) => [String(row.status), Number(row.count)])),
|
|
494
|
+
lastRun,
|
|
495
|
+
rebuildRequired
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
return { indexes, rebuildRequired };
|
|
499
|
+
}
|
|
500
|
+
export async function searchIndexDetail(input) {
|
|
501
|
+
const summary = await searchIndexesSummary(input);
|
|
502
|
+
const index = summary.indexes.find((item) => item.name === input.indexName);
|
|
503
|
+
if (!index)
|
|
504
|
+
throw new Error(`Unknown search index: ${input.indexName}`);
|
|
505
|
+
const recentErrors = await input.control.all(`SELECT "id", "sourceType", "sourceTable", "sourceId", "chunkKey", "status", "lastError", "updatedAt"
|
|
506
|
+
FROM "_search_documents"
|
|
507
|
+
WHERE "indexName" = ? AND ("status" = 'error' OR "lastError" IS NOT NULL)
|
|
508
|
+
ORDER BY "updatedAt" DESC LIMIT 25`, [index.name]).catch(() => []);
|
|
509
|
+
const recentRuns = await searchReindexRunsQuery({ control: input.control, indexName: index.name, limit: 25 });
|
|
510
|
+
return { ...index, recentErrors, recentRuns: recentRuns.runs };
|
|
511
|
+
}
|
|
512
|
+
export async function searchReindexRunsQuery(input) {
|
|
513
|
+
await ensureSearchSchema(input.control).catch(() => undefined);
|
|
514
|
+
const where = [];
|
|
515
|
+
const params = [];
|
|
516
|
+
if (input.indexName) {
|
|
517
|
+
where.push(`"indexName" = ?`);
|
|
518
|
+
params.push(input.indexName);
|
|
519
|
+
}
|
|
520
|
+
if (input.status) {
|
|
521
|
+
where.push(`"status" = ?`);
|
|
522
|
+
params.push(input.status);
|
|
523
|
+
}
|
|
524
|
+
const whereSql = where.length ? `WHERE ${where.join(" AND ")}` : "";
|
|
525
|
+
const runs = await input.control.all(`SELECT * FROM "_search_reindex_runs" ${whereSql} ORDER BY "startedAt" DESC LIMIT ? OFFSET ?`, [...params, Math.min(Math.max(input.limit ?? 50, 1), 500), Math.max(input.offset ?? 0, 0)]).catch(() => []);
|
|
526
|
+
return { runs };
|
|
527
|
+
}
|
|
528
|
+
export async function markSearchRebuildRequired(control, reason) {
|
|
529
|
+
await control.run(`INSERT OR REPLACE INTO "_state" ("key", "value", "updatedAt") VALUES (?, ?, ?)`, [raybaseSearchRebuildStateKey, JSON.stringify({ reason, markedAt: nowIso() }), nowIso()]);
|
|
530
|
+
}
|
|
531
|
+
async function indexTableSearchRow(input) {
|
|
532
|
+
try {
|
|
533
|
+
const documents = await sourceDocumentsForTableRow(input.index, input.source, input.row);
|
|
534
|
+
if (!documents.length) {
|
|
535
|
+
const sourceId = String(input.row.id ?? "");
|
|
536
|
+
if (sourceId) {
|
|
537
|
+
await deleteSearchDocumentsForSource(input.control, {
|
|
538
|
+
indexName: input.index.name,
|
|
539
|
+
sourceType: "table",
|
|
540
|
+
sourceTable: input.source.table.name,
|
|
541
|
+
sourceId
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
return { documentIds: [], indexed: 0, upserted: 0, skipped: 1, errors: 0 };
|
|
545
|
+
}
|
|
546
|
+
const documentIds = new Set();
|
|
547
|
+
for (const document of documents) {
|
|
548
|
+
documentIds.add(document.id);
|
|
549
|
+
await upsertSearchDocument(input.control, document);
|
|
550
|
+
}
|
|
551
|
+
const sourceId = documents[0].sourceId;
|
|
552
|
+
const existing = await input.control.all(`SELECT "id" FROM "_search_documents"
|
|
553
|
+
WHERE "indexName" = ? AND "sourceType" = 'table' AND "sourceTable" = ? AND "sourceId" = ? AND "status" != 'deleted'`, [input.index.name, input.source.table.name, sourceId]);
|
|
554
|
+
for (const existingDocument of existing) {
|
|
555
|
+
if (!documentIds.has(existingDocument.id))
|
|
556
|
+
await deleteSearchDocumentById(input.control, existingDocument.id);
|
|
557
|
+
}
|
|
558
|
+
return { documentIds: [...documentIds], indexed: documents.length, upserted: documents.length, skipped: 0, errors: 0 };
|
|
559
|
+
}
|
|
560
|
+
catch (error) {
|
|
561
|
+
const sourceId = String(input.row.id ?? "");
|
|
562
|
+
if (sourceId) {
|
|
563
|
+
await input.control.run(`UPDATE "_search_documents"
|
|
564
|
+
SET "status" = 'error', "lastError" = ?, "updatedAt" = ?
|
|
565
|
+
WHERE "indexName" = ? AND "sourceType" = 'table' AND "sourceTable" = ? AND "sourceId" = ?`, [error instanceof Error ? error.message : String(error), nowIso(), input.index.name, input.source.table.name, sourceId]).catch(() => undefined);
|
|
566
|
+
}
|
|
567
|
+
return { documentIds: [], indexed: 0, upserted: 0, skipped: 0, errors: 1 };
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
async function sourceDocumentsForTableRow(index, source, row) {
|
|
571
|
+
const sourceId = String(row.id ?? "");
|
|
572
|
+
if (!sourceId)
|
|
573
|
+
return [];
|
|
574
|
+
const body = source.text(row).trim();
|
|
575
|
+
if (!body)
|
|
576
|
+
return [];
|
|
577
|
+
const title = source.title(row).trim() || sourceId;
|
|
578
|
+
const version = source.version?.(row) ?? String(row.updatedAt ?? row.createdAt ?? "");
|
|
579
|
+
const visibility = typeof source.public === "function" ? source.public(row) : source.public === true;
|
|
580
|
+
const chunks = await chunkSearchText(body, index.chunking);
|
|
581
|
+
const documents = [];
|
|
582
|
+
for (const chunk of chunks) {
|
|
583
|
+
documents.push({
|
|
584
|
+
id: await searchDocumentIdForChunk({
|
|
585
|
+
indexName: index.name,
|
|
586
|
+
sourceType: "table",
|
|
587
|
+
sourceTable: source.table.name,
|
|
588
|
+
sourceId,
|
|
589
|
+
chunkKey: chunk.key
|
|
590
|
+
}),
|
|
591
|
+
indexName: index.name,
|
|
592
|
+
sourceType: "table",
|
|
593
|
+
sourceTable: source.table.name,
|
|
594
|
+
sourceId,
|
|
595
|
+
sourceVersion: version,
|
|
596
|
+
chunkKey: chunk.key,
|
|
597
|
+
chunkOrdinal: chunk.ordinal,
|
|
598
|
+
title,
|
|
599
|
+
body: chunk.text,
|
|
600
|
+
url: source.url?.(row) ?? null,
|
|
601
|
+
textHash: chunk.hash,
|
|
602
|
+
visibility: visibility ? "public" : "private",
|
|
603
|
+
metadata: source.metadata ? source.metadata(row) : undefined
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
return documents;
|
|
607
|
+
}
|
|
608
|
+
async function indexCorpusSearchSource(input) {
|
|
609
|
+
const docs = typeof input.source.documents === "function" ? await input.source.documents() : input.source.documents;
|
|
610
|
+
const activeDocumentIds = new Set();
|
|
611
|
+
const counts = { sources: 0, indexed: 0, upserted: 0, deleted: 0, skipped: 0, errors: 0 };
|
|
612
|
+
for (const doc of docs.slice(0, Math.min(input.source.limit ?? docs.length, 5000))) {
|
|
613
|
+
counts.sources += 1;
|
|
614
|
+
if (!doc.id || !doc.body.trim()) {
|
|
615
|
+
counts.skipped += 1;
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
618
|
+
const chunks = await chunkSearchText(doc.body, input.index.chunking);
|
|
619
|
+
if (!chunks.length) {
|
|
620
|
+
counts.skipped += 1;
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
for (const chunk of chunks) {
|
|
624
|
+
const document = {
|
|
625
|
+
id: await searchDocumentIdForChunk({
|
|
626
|
+
indexName: input.index.name,
|
|
627
|
+
sourceType: "corpus",
|
|
628
|
+
sourceTable: input.source.name,
|
|
629
|
+
sourceId: doc.id,
|
|
630
|
+
chunkKey: chunk.key
|
|
631
|
+
}),
|
|
632
|
+
indexName: input.index.name,
|
|
633
|
+
sourceType: "corpus",
|
|
634
|
+
sourceTable: input.source.name,
|
|
635
|
+
sourceId: doc.id,
|
|
636
|
+
sourceVersion: doc.version ?? "",
|
|
637
|
+
chunkKey: chunk.key,
|
|
638
|
+
chunkOrdinal: chunk.ordinal,
|
|
639
|
+
title: doc.title,
|
|
640
|
+
body: chunk.text,
|
|
641
|
+
url: doc.url ?? null,
|
|
642
|
+
textHash: chunk.hash,
|
|
643
|
+
visibility: doc.public === true ? "public" : "private",
|
|
644
|
+
metadata: doc.metadata
|
|
645
|
+
};
|
|
646
|
+
activeDocumentIds.add(document.id);
|
|
647
|
+
await upsertSearchDocument(input.control, document);
|
|
648
|
+
counts.indexed += 1;
|
|
649
|
+
counts.upserted += 1;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
const existing = await input.control.all(`SELECT "id" FROM "_search_documents"
|
|
653
|
+
WHERE "indexName" = ? AND "sourceType" = 'corpus' AND "sourceTable" = ? AND "status" != 'deleted'`, [input.index.name, input.source.name]);
|
|
654
|
+
for (const row of existing) {
|
|
655
|
+
if (!activeDocumentIds.has(row.id)) {
|
|
656
|
+
await deleteSearchDocumentById(input.control, row.id);
|
|
657
|
+
counts.deleted += 1;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
return counts;
|
|
661
|
+
}
|
|
662
|
+
async function upsertSearchDocument(control, document) {
|
|
663
|
+
const now = nowIso();
|
|
664
|
+
const metadata = document.metadata === undefined ? null : stableJson(document.metadata);
|
|
665
|
+
const existing = await control.first(`SELECT "id", "rowid" FROM "_search_documents" WHERE "id" = ? LIMIT 1`, [document.id]);
|
|
666
|
+
const values = [
|
|
667
|
+
document.indexName,
|
|
668
|
+
document.sourceType,
|
|
669
|
+
document.sourceTable,
|
|
670
|
+
document.sourceId,
|
|
671
|
+
document.sourceVersion ?? null,
|
|
672
|
+
document.chunkKey,
|
|
673
|
+
document.chunkOrdinal,
|
|
674
|
+
document.title,
|
|
675
|
+
document.body,
|
|
676
|
+
document.url ?? null,
|
|
677
|
+
document.textHash,
|
|
678
|
+
document.visibility,
|
|
679
|
+
metadata,
|
|
680
|
+
"indexed",
|
|
681
|
+
null,
|
|
682
|
+
now,
|
|
683
|
+
now
|
|
684
|
+
];
|
|
685
|
+
if (existing) {
|
|
686
|
+
await deleteFtsRow(control, Number(existing.rowid)).catch(() => undefined);
|
|
687
|
+
await control.run(`UPDATE "_search_documents"
|
|
688
|
+
SET "indexName" = ?, "sourceType" = ?, "sourceTable" = ?, "sourceId" = ?, "sourceVersion" = ?,
|
|
689
|
+
"chunkKey" = ?, "chunkOrdinal" = ?, "title" = ?, "body" = ?, "url" = ?, "textHash" = ?,
|
|
690
|
+
"visibility" = ?, "metadata" = ?, "status" = ?, "lastError" = ?, "indexedAt" = ?, "updatedAt" = ?
|
|
691
|
+
WHERE "id" = ?`, [...values, document.id]);
|
|
692
|
+
await control.run(`INSERT INTO "_search_fts" ("rowid", "title", "body") VALUES (?, ?, ?)`, [
|
|
693
|
+
Number(existing.rowid),
|
|
694
|
+
document.title,
|
|
695
|
+
document.body
|
|
696
|
+
]);
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
await control.run(`INSERT INTO "_search_documents"
|
|
700
|
+
("id", "indexName", "sourceType", "sourceTable", "sourceId", "sourceVersion", "chunkKey", "chunkOrdinal",
|
|
701
|
+
"title", "body", "url", "textHash", "visibility", "metadata", "status", "lastError", "indexedAt", "updatedAt")
|
|
702
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [document.id, ...values]);
|
|
703
|
+
const inserted = await control.first(`SELECT "rowid" FROM "_search_documents" WHERE "id" = ?`, [document.id]);
|
|
704
|
+
if (inserted) {
|
|
705
|
+
await control.run(`INSERT INTO "_search_fts" ("rowid", "title", "body") VALUES (?, ?, ?)`, [
|
|
706
|
+
Number(inserted.rowid),
|
|
707
|
+
document.title,
|
|
708
|
+
document.body
|
|
709
|
+
]);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
async function deleteSearchDocumentsForSource(control, input) {
|
|
713
|
+
const docs = await control.all(`SELECT "id" FROM "_search_documents"
|
|
714
|
+
WHERE "indexName" = ? AND "sourceType" = ? AND "sourceTable" IS ? AND "sourceId" = ?`, [input.indexName, input.sourceType, input.sourceTable ?? null, input.sourceId]);
|
|
715
|
+
for (const doc of docs)
|
|
716
|
+
await deleteSearchDocumentById(control, doc.id);
|
|
717
|
+
}
|
|
718
|
+
async function deleteSearchDocumentById(control, id) {
|
|
719
|
+
const existing = await control.first(`SELECT "rowid", "title", "body" FROM "_search_documents" WHERE "id" = ? LIMIT 1`, [id]);
|
|
720
|
+
if (!existing)
|
|
721
|
+
return;
|
|
722
|
+
await deleteFtsRow(control, Number(existing.rowid)).catch(() => undefined);
|
|
723
|
+
await control.run(`UPDATE "_search_documents" SET "status" = 'deleted', "updatedAt" = ? WHERE "id" = ?`, [nowIso(), id]);
|
|
724
|
+
}
|
|
725
|
+
async function deleteFtsRow(control, rowid) {
|
|
726
|
+
await control.run(`DELETE FROM "_search_fts" WHERE "rowid" = ?`, [rowid]);
|
|
727
|
+
}
|
|
728
|
+
async function completeSearchReindexRun(control, runId, status, counts, summary) {
|
|
729
|
+
await control.run(`UPDATE "_search_reindex_runs"
|
|
730
|
+
SET "status" = ?, "totalSources" = ?, "totalChunks" = ?, "upsertedChunks" = ?, "deletedChunks" = ?,
|
|
731
|
+
"skippedChunks" = ?, "errorCount" = ?, "completedAt" = ?, "summaryJson" = ?
|
|
732
|
+
WHERE "id" = ?`, [
|
|
733
|
+
status,
|
|
734
|
+
counts.totalSources,
|
|
735
|
+
counts.totalChunks,
|
|
736
|
+
counts.upsertedChunks,
|
|
737
|
+
counts.deletedChunks,
|
|
738
|
+
counts.skippedChunks,
|
|
739
|
+
counts.errorCount,
|
|
740
|
+
nowIso(),
|
|
741
|
+
stableJson(summary),
|
|
742
|
+
runId
|
|
743
|
+
]);
|
|
744
|
+
}
|
|
745
|
+
function findTableSearchSource(index, tableName) {
|
|
746
|
+
return index.sources.find((source) => source.kind === "table" && source.table.name === tableName) ?? null;
|
|
747
|
+
}
|
|
748
|
+
function tableKeyFor(schema, table) {
|
|
749
|
+
const entry = Object.entries(schema.tables).find(([, candidate]) => candidate.name === table.name);
|
|
750
|
+
if (!entry)
|
|
751
|
+
throw new Error(`Search source table ${table.name} is not in the app schema`);
|
|
752
|
+
return entry[0];
|
|
753
|
+
}
|
|
754
|
+
function splitSqlStatements(sql) {
|
|
755
|
+
return sql
|
|
756
|
+
.split(";")
|
|
757
|
+
.map((statement) => statement.trim())
|
|
758
|
+
.filter(Boolean);
|
|
759
|
+
}
|
|
760
|
+
function parseJson(value) {
|
|
761
|
+
if (typeof value !== "string" || !value)
|
|
762
|
+
return value;
|
|
763
|
+
try {
|
|
764
|
+
return JSON.parse(value);
|
|
765
|
+
}
|
|
766
|
+
catch {
|
|
767
|
+
return value;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
function asNullableString(value) {
|
|
771
|
+
return typeof value === "string" && value ? value : null;
|
|
772
|
+
}
|
|
773
|
+
async function readSearchRebuildRequired(control) {
|
|
774
|
+
const row = await control.first(`SELECT "value" FROM "_state" WHERE "key" = ?`, [raybaseSearchRebuildStateKey]).catch(() => null);
|
|
775
|
+
return row?.value ? parseJson(row.value) : null;
|
|
776
|
+
}
|
|
777
|
+
async function sha256Base64Url(value) {
|
|
778
|
+
const bytes = new TextEncoder().encode(value);
|
|
779
|
+
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
780
|
+
let binary = "";
|
|
781
|
+
for (const byte of new Uint8Array(digest))
|
|
782
|
+
binary += String.fromCharCode(byte);
|
|
783
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
784
|
+
}
|
|
785
|
+
//# sourceMappingURL=search.js.map
|