@dbx-tools/search 0.6.9

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.
@@ -0,0 +1,218 @@
1
+ /**
2
+ * A Lakebase (Postgres) full-text search backend - the FALLBACK used when no
3
+ * Databricks Vector Search endpoint/index is configured but a Lakebase pool is
4
+ * available. It provisions a single table per index, indexes a generated
5
+ * `tsvector`, and answers queries with `websearch_to_tsquery` + `ts_rank`.
6
+ *
7
+ * The whole point is parity: this backend returns the EXACT same
8
+ * `@dbx-tools/shared-search` shapes (`SearchResult` / `SearchHit` /
9
+ * `UpsertResult`) as the Vector Search backend, so the client, the Mastra
10
+ * tools, the routes, and the React search box cannot tell which one answered.
11
+ * A hit's `id` is the primary key, its `score` is the text-rank, and `fields`
12
+ * is the stored document minus the internal columns.
13
+ *
14
+ * The Postgres pool is built the same way `@dbx-tools/appkit-mastra` builds its
15
+ * memory pool: the AppKit `lakebase` plugin resolves a service-principal
16
+ * `PoolConfig` (connection target + OAuth token-refresh `password` callback),
17
+ * and this backend constructs a `pg.Pool` from it. It never re-implements auth.
18
+ *
19
+ * @module
20
+ */
21
+ import { ExecutionError } from "@databricks/appkit";
22
+ import { log, object, string } from "@dbx-tools/shared-core";
23
+ import { Pool } from "pg";
24
+ const logger = log.logger("search/lakebase");
25
+ /** The internal columns every search table carries, excluded from a hit's `fields`. */
26
+ const RESERVED_COLUMNS = new Set(["id", "search_text", "document", "search_vector"]);
27
+ /**
28
+ * A Postgres full-text backend. One instance is shared across indexes; each
29
+ * index maps to a table whose name is derived from the index reference.
30
+ */
31
+ export class LakebaseSearchBackend {
32
+ pgConfigFactory;
33
+ schema;
34
+ poolFactory;
35
+ pool;
36
+ poolPromise;
37
+ provisioned = new Set();
38
+ constructor(pgConfigFactory, schema = "public",
39
+ /** How a pool is built from the resolved config. Overridable for tests. */
40
+ poolFactory = (config) => new Pool(config)) {
41
+ this.pgConfigFactory = pgConfigFactory;
42
+ this.schema = schema;
43
+ this.poolFactory = poolFactory;
44
+ }
45
+ /** Lazily build (and cache) the pg pool from the resolved Lakebase config. */
46
+ async getPool() {
47
+ if (this.pool)
48
+ return this.pool;
49
+ this.poolPromise ??= (async () => {
50
+ const config = await this.pgConfigFactory();
51
+ const pool = this.poolFactory(config);
52
+ this.pool = pool;
53
+ return pool;
54
+ })();
55
+ return this.poolPromise;
56
+ }
57
+ /** Close the pool so a restarted app rebuilds it. */
58
+ async close() {
59
+ const pool = this.pool;
60
+ this.pool = undefined;
61
+ this.poolPromise = undefined;
62
+ this.provisioned.clear();
63
+ if (pool)
64
+ await pool.end();
65
+ }
66
+ /**
67
+ * Search a Lakebase-backed index. Returns hits sorted most-relevant-first,
68
+ * shaped identically to the Vector Search backend.
69
+ */
70
+ async search(index, query, options = {}) {
71
+ const text = string.trimToEmpty(query);
72
+ const table = this.tableFor(index);
73
+ const limit = options.limit ?? 10;
74
+ const pool = await this.getPool();
75
+ // `websearch_to_tsquery` accepts a bare search box string (quoted phrases,
76
+ // `or`, `-term`); an empty query returns the most recent rows so an empty
77
+ // search box still shows content, matching a keyword index's behavior.
78
+ const sql = text
79
+ ? `SELECT id, document, ts_rank(search_vector, websearch_to_tsquery('english', $1)) AS score
80
+ FROM ${table}
81
+ WHERE search_vector @@ websearch_to_tsquery('english', $1)
82
+ ORDER BY score DESC
83
+ LIMIT $2`
84
+ : `SELECT id, document, 0::float4 AS score FROM ${table} ORDER BY id LIMIT $2`;
85
+ const params = text ? [text, limit] : [limit];
86
+ const { rows } = await this.query(pool, sql, params, options.signal);
87
+ const hits = rows
88
+ .map((row) => ({
89
+ id: String(row.id),
90
+ score: Number(row.score) || 0,
91
+ fields: this.toFields(row.document),
92
+ }))
93
+ .filter((hit) => options.scoreThreshold === undefined || hit.score >= options.scoreThreshold);
94
+ return { query: text, index, hits, count: hits.length };
95
+ }
96
+ /**
97
+ * Ensure the table + full-text index exist and seed documents when empty.
98
+ * Idempotent, so it is safe to call on every boot.
99
+ */
100
+ async provision(index, options = {}) {
101
+ const table = this.tableFor(index);
102
+ const pool = await this.getPool();
103
+ await this.ensureTable(pool, table, options.signal);
104
+ const { rows } = await this.query(pool, `SELECT count(*)::text AS count FROM ${table}`, [], options.signal);
105
+ const existing = Number(rows[0]?.count ?? "0");
106
+ const seed = options.seed ?? [];
107
+ if (existing === 0 && seed.length > 0) {
108
+ await this.upsert(pool, table, seed, options.textColumn ?? "text", options.signal);
109
+ logger.info("index-seeded", { index, table, count: seed.length });
110
+ return seed.length;
111
+ }
112
+ return existing;
113
+ }
114
+ /** Add or update documents by primary key. */
115
+ async addDocuments(index, documents, textColumn = "text", signal) {
116
+ const table = this.tableFor(index);
117
+ const pool = await this.getPool();
118
+ await this.ensureTable(pool, table, signal);
119
+ await this.upsert(pool, table, documents, textColumn, signal);
120
+ return { index, count: documents.length };
121
+ }
122
+ /** Delete documents by primary key. */
123
+ async deleteDocuments(index, ids, signal) {
124
+ const table = this.tableFor(index);
125
+ const pool = await this.getPool();
126
+ await this.query(pool, `DELETE FROM ${table} WHERE id = ANY($1)`, [ids.map(String)], signal);
127
+ return { index, count: ids.length };
128
+ }
129
+ /** Create the table + GIN index once per table (memoized across calls). */
130
+ async ensureTable(pool, table, signal) {
131
+ if (this.provisioned.has(table))
132
+ return;
133
+ // `document` holds the whole row; `search_text` is the indexed text; the
134
+ // generated `search_vector` keeps the tsvector in lockstep with it so a
135
+ // write never has to compute the vector by hand.
136
+ await this.query(pool, `CREATE SCHEMA IF NOT EXISTS ${this.ident(this.schema)}`, [], signal);
137
+ await this.query(pool, `CREATE TABLE IF NOT EXISTS ${table} (
138
+ id text PRIMARY KEY,
139
+ search_text text NOT NULL DEFAULT '',
140
+ document jsonb NOT NULL DEFAULT '{}'::jsonb,
141
+ search_vector tsvector GENERATED ALWAYS AS (to_tsvector('english', search_text)) STORED
142
+ )`, [], signal);
143
+ await this.query(pool, `CREATE INDEX IF NOT EXISTS ${this.ident(`${this.bareName(table)}_fts`)}
144
+ ON ${table} USING gin (search_vector)`, [], signal);
145
+ this.provisioned.add(table);
146
+ logger.info("index-created", { table });
147
+ }
148
+ /** Upsert rows: the whole document as jsonb + a flattened text blob to index. */
149
+ async upsert(pool, table, documents, textColumn, signal) {
150
+ for (const doc of documents) {
151
+ const id = string.trimToNull(String(doc.id ?? doc.ID ?? "")) ?? undefined;
152
+ if (id === undefined) {
153
+ throw new ExecutionError("search (lakebase): a document is missing an `id`", {
154
+ context: { operation: "addDocuments" },
155
+ });
156
+ }
157
+ const searchText = this.searchText(doc, textColumn);
158
+ await this.query(pool, `INSERT INTO ${table} (id, search_text, document)
159
+ VALUES ($1, $2, $3::jsonb)
160
+ ON CONFLICT (id) DO UPDATE
161
+ SET search_text = EXCLUDED.search_text, document = EXCLUDED.document`, [id, searchText, JSON.stringify(doc)], signal);
162
+ }
163
+ }
164
+ /** The text a row is indexed by: the text column first, then any other string field. */
165
+ searchText(doc, textColumn) {
166
+ const primary = string.trimToEmpty(String(doc[textColumn] ?? ""));
167
+ const rest = Object.entries(doc)
168
+ .filter(([key, value]) => key !== textColumn && key !== "id" && typeof value === "string")
169
+ .map(([, value]) => value);
170
+ return [primary, ...rest].filter(Boolean).join("\n");
171
+ }
172
+ /** A hit's `fields`: the stored document minus the reserved/internal keys. */
173
+ toFields(document) {
174
+ if (!object.isRecord(document))
175
+ return {};
176
+ const fields = {};
177
+ for (const [key, value] of Object.entries(document)) {
178
+ if (RESERVED_COLUMNS.has(key))
179
+ continue;
180
+ fields[key] = value;
181
+ }
182
+ return fields;
183
+ }
184
+ /** The fully-qualified table name for an index reference. */
185
+ tableFor(index) {
186
+ return `${this.ident(this.schema)}.${this.ident(this.bareName(index))}`;
187
+ }
188
+ /** A safe bare table name derived from an index reference. */
189
+ bareName(reference) {
190
+ const last = reference.split(".").filter(Boolean).pop() ?? reference;
191
+ const slug = last
192
+ .toLowerCase()
193
+ .replace(/[^a-z0-9_]+/g, "_")
194
+ .replace(/^_+|_+$/g, "");
195
+ return slug.length > 0 ? slug : "documents";
196
+ }
197
+ /** Quote a Postgres identifier. */
198
+ ident(name) {
199
+ return `"${name.replace(/"/g, '""')}"`;
200
+ }
201
+ /** Run one query under external cancellation. */
202
+ async query(pool, sql, params, signal) {
203
+ if (signal?.aborted)
204
+ throw ExecutionError.canceled();
205
+ const client = await pool.connect();
206
+ const onAbort = () => void client.release(true);
207
+ signal?.addEventListener("abort", onAbort, { once: true });
208
+ try {
209
+ const result = await client.query(sql, params);
210
+ return { rows: result.rows };
211
+ }
212
+ finally {
213
+ signal?.removeEventListener("abort", onAbort);
214
+ client.release();
215
+ }
216
+ }
217
+ }
218
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibGFrZWJhc2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvbGFrZWJhc2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FtQkc7QUFFSCxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDcEQsT0FBTyxFQUFFLEdBQUcsRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFDN0QsT0FBTyxFQUFFLElBQUksRUFBbUIsTUFBTSxJQUFJLENBQUM7QUFRM0MsTUFBTSxNQUFNLEdBQUcsR0FBRyxDQUFDLE1BQU0sQ0FBQyxpQkFBaUIsQ0FBQyxDQUFDO0FBRTdDLHVGQUF1RjtBQUN2RixNQUFNLGdCQUFnQixHQUFHLElBQUksR0FBRyxDQUFDLENBQUMsSUFBSSxFQUFFLGFBQWEsRUFBRSxVQUFVLEVBQUUsZUFBZSxDQUFDLENBQUMsQ0FBQztBQWtCckY7OztHQUdHO0FBQ0gsTUFBTSxPQUFPLHFCQUFxQjtJQU1iO0lBQ0E7SUFFQTtJQVJYLElBQUksQ0FBbUI7SUFDdkIsV0FBVyxDQUE0QjtJQUM5QixXQUFXLEdBQUcsSUFBSSxHQUFHLEVBQVUsQ0FBQztJQUVqRCxZQUNtQixlQUF1RCxFQUN2RCxTQUFTLFFBQVE7SUFDbEMsMkVBQTJFO0lBQzFELGNBQTRDLENBQUMsTUFBTSxFQUFFLEVBQUUsQ0FBQyxJQUFJLElBQUksQ0FBQyxNQUFNLENBQUM7UUFIeEUsb0JBQWUsR0FBZixlQUFlLENBQXdDO1FBQ3ZELFdBQU0sR0FBTixNQUFNLENBQVc7UUFFakIsZ0JBQVcsR0FBWCxXQUFXLENBQTZEO0lBQ3hGLENBQUM7SUFFSiw4RUFBOEU7SUFDdEUsS0FBSyxDQUFDLE9BQU87UUFDbkIsSUFBSSxJQUFJLENBQUMsSUFBSTtZQUFFLE9BQU8sSUFBSSxDQUFDLElBQUksQ0FBQztRQUNoQyxJQUFJLENBQUMsV0FBVyxLQUFLLENBQUMsS0FBSyxJQUFJ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QUUsRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztRQUMzRCxJQUFJLENBQUM7WUFDSCxNQUFNLE1BQU0sR0FBRyxNQUFNLE1BQU0sQ0FBQyxLQUFLLENBQUMsR0FBRyxFQUFFLE1BQU0sQ0FBQyxDQUFDO1lBQy9DLE9BQU8sRUFBRSxJQUFJLEVBQUUsTUFBTSxDQUFDLElBQVcsRUFBRSxDQUFDO1FBQ3RDLENBQUM7Z0JBQVMsQ0FBQztZQUNULE1BQU0sRUFBRSxtQkFBbUIsQ0FBQyxPQUFPLEVBQUUsT0FBTyxDQUFDLENBQUM7WUFDOUMsTUFBTSxDQUFDLE9BQU8sRUFBRSxDQUFDO1FBQ25CLENBQUM7SUFDSCxDQUFDO0NBQ0YiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIEEgTGFrZWJhc2UgKFBvc3RncmVzKSBmdWxsLXRleHQgc2VhcmNoIGJhY2tlbmQgLSB0aGUgRkFMTEJBQ0sgdXNlZCB3aGVuIG5vXG4gKiBEYXRhYnJpY2tzIFZlY3RvciBTZWFyY2ggZW5kcG9pbnQvaW5kZXggaXMgY29uZmlndXJlZCBidXQgYSBMYWtlYmFzZSBwb29sIGlzXG4gKiBhdmFpbGFibGUuIEl0IHByb3Zpc2lvbnMgYSBzaW5nbGUgdGFibGUgcGVyIGluZGV4LCBpbmRleGVzIGEgZ2VuZXJhdGVkXG4gKiBgdHN2ZWN0b3JgLCBhbmQgYW5zd2VycyBxdWVyaWVzIHdpdGggYHdlYnNlYXJjaF90b190c3F1ZXJ5YCArIGB0c19yYW5rYC5cbiAqXG4gKiBUaGUgd2hvbGUgcG9pbnQgaXMgcGFyaXR5OiB0aGlzIGJhY2tlbmQgcmV0dXJucyB0aGUgRVhBQ1Qgc2FtZVxuICogYEBkYngtdG9vbHMvc2hhcmVkLXNlYXJjaGAgc2hhcGVzIChgU2VhcmNoUmVzdWx0YCAvIGBTZWFyY2hIaXRgIC9cbiAqIGBVcHNlcnRSZXN1bHRgKSBhcyB0aGUgVmVjdG9yIFNlYXJjaCBiYWNrZW5kLCBzbyB0aGUgY2xpZW50LCB0aGUgTWFzdHJhXG4gKiB0b29scywgdGhlIHJvdXRlcywgYW5kIHRoZSBSZWFjdCBzZWFyY2ggYm94IGNhbm5vdCB0ZWxsIHdoaWNoIG9uZSBhbnN3ZXJlZC5cbiAqIEEgaGl0J3MgYGlkYCBpcyB0aGUgcHJpbWFyeSBrZXksIGl0cyBgc2NvcmVgIGlzIHRoZSB0ZXh0LXJhbmssIGFuZCBgZmllbGRzYFxuICogaXMgdGhlIHN0b3JlZCBkb2N1bWVudCBtaW51cyB0aGUgaW50ZXJuYWwgY29sdW1ucy5cbiAqXG4gKiBUaGUgUG9zdGdyZXMgcG9vbCBpcyBidWlsdCB0aGUgc2FtZSB3YXkgYEBkYngtdG9vbHMvYXBwa2l0LW1hc3RyYWAgYnVpbGRzIGl0c1xuICogbWVtb3J5IHBvb2w6IHRoZSBBcHBLaXQgYGxha2ViYXNlYCBwbHVnaW4gcmVzb2x2ZXMgYSBzZXJ2aWNlLXByaW5jaXBhbFxuICogYFBvb2xDb25maWdgIChjb25uZWN0aW9uIHRhcmdldCArIE9BdXRoIHRva2VuLXJlZnJlc2ggYHBhc3N3b3JkYCBjYWxsYmFjayksXG4gKiBhbmQgdGhpcyBiYWNrZW5kIGNvbnN0cnVjdHMgYSBgcGcuUG9vbGAgZnJvbSBpdC4gSXQgbmV2ZXIgcmUtaW1wbGVtZW50cyBhdXRoLlxuICpcbiAqIEBtb2R1bGVcbiAqL1xuXG5pbXBvcnQgeyBFeGVjdXRpb25FcnJvciB9IGZyb20gXCJAZGF0YWJyaWNrcy9hcHBraXRcIjtcbmltcG9ydCB7IGxvZywgb2JqZWN0LCBzdHJpbmcgfSBmcm9tIFwiQGRieC10b29scy9zaGFyZWQtY29yZVwiO1xuaW1wb3J0IHsgUG9vbCwgdHlwZSBQb29sQ29uZmlnIH0gZnJvbSBcInBnXCI7XG5pbXBvcnQgdHlwZSB7XG4gIFNlYXJjaERvY3VtZW50LFxuICBTZWFyY2hIaXQsXG4gIFNlYXJjaFJlc3VsdCxcbiAgVXBzZXJ0UmVzdWx0LFxufSBmcm9tIFwiQGRieC10b29scy9zaGFyZWQtc2VhcmNoXCI7XG5cbmNvbnN0IGxvZ2dlciA9IGxvZy5sb2dnZXIoXCJzZWFyY2gvbGFrZWJhc2VcIik7XG5cbi8qKiBUaGUgaW50ZXJuYWwgY29sdW1ucyBldmVyeSBzZWFyY2ggdGFibGUgY2FycmllcywgZXhjbHVkZWQgZnJvbSBhIGhpdCdzIGBmaWVsZHNgLiAqL1xuY29uc3QgUkVTRVJWRURfQ09MVU1OUyA9IG5ldyBTZXQoW1wiaWRcIiwgXCJzZWFyY2hfdGV4dFwiLCBcImRvY3VtZW50XCIsIFwic2VhcmNoX3ZlY3RvclwiXSk7XG5cbi8qKiBPcHRpb25zIGZvciBhIHNpbmdsZS1pbmRleCBMYWtlYmFzZSBzZWFyY2ggKG1pcnJvcnMgdGhlIGNsaWVudCdzIGBTZWFyY2hPcHRpb25zYCkuICovXG5leHBvcnQgaW50ZXJmYWNlIExha2ViYXNlU2VhcmNoT3B0aW9ucyB7XG4gIGxpbWl0PzogbnVtYmVyO1xuICBzY29yZVRocmVzaG9sZD86IG51bWJlcjtcbiAgc2lnbmFsPzogQWJvcnRTaWduYWw7XG59XG5cbi8qKiBPcHRpb25zIGZvciBwcm92aXNpb25pbmcgYSBMYWtlYmFzZS1iYWNrZWQgaW5kZXguICovXG5leHBvcnQgaW50ZXJmYWNlIExha2ViYXNlUHJvdmlzaW9uT3B0aW9ucyB7XG4gIC8qKiBUZXh0IGNvbHVtbiBlbWJlZGRlZCBpbnRvIHRoZSBzZWFyY2ggdmVjdG9yLiBEZWZhdWx0cyB0byBgdGV4dGAuICovXG4gIHRleHRDb2x1bW4/OiBzdHJpbmc7XG4gIC8qKiBEb2N1bWVudHMgdG8gc2VlZCB3aGVuIHRoZSB0YWJsZSBpcyBlbXB0eS4gKi9cbiAgc2VlZD86IFNlYXJjaERvY3VtZW50W107XG4gIHNpZ25hbD86IEFib3J0U2lnbmFsO1xufVxuXG4vKipcbiAqIEEgUG9zdGdyZXMgZnVsbC10ZXh0IGJhY2tlbmQuIE9uZSBpbnN0YW5jZSBpcyBzaGFyZWQgYWNyb3NzIGluZGV4ZXM7IGVhY2hcbiAqIGluZGV4IG1hcHMgdG8gYSB0YWJsZSB3aG9zZSBuYW1lIGlzIGRlcml2ZWQgZnJvbSB0aGUgaW5kZXggcmVmZXJlbmNlLlxuICovXG5leHBvcnQgY2xhc3MgTGFrZWJhc2VTZWFyY2hCYWNrZW5kIHtcbiAgcHJpdmF0ZSBwb29sOiBQb29sIHwgdW5kZWZpbmVkO1xuICBwcml2YXRlIHBvb2xQcm9taXNlOiBQcm9taXNlPFBvb2w+IHwgdW5kZWZpbmVkO1xuICBwcml2YXRlIHJlYWRvbmx5IHByb3Zpc2lvbmVkID0gbmV3IFNldDxzdHJpbmc+KCk7XG5cbiAgY29uc3RydWN0b3IoXG4gICAgcHJpdmF0ZSByZWFkb25seSBwZ0NvbmZpZ0ZhY3Rvcnk6ICgpID0+IFByb21pc2U8UG9vbENvbmZpZz4gfCBQb29sQ29uZmlnLFxuICAgIHByaXZhdGUgcmVhZG9ubHkgc2NoZW1hID0gXCJwdWJsaWNcIixcbiAgICAvKiogSG93IGEgcG9vbCBpcyBidWlsdCBmcm9tIHRoZSByZXNvbHZlZCBjb25maWcuIE92ZXJyaWRhYmxlIGZvciB0ZXN0cy4gKi9cbiAgICBwcml2YXRlIHJlYWRvbmx5IHBvb2xGYWN0b3J5OiAoY29uZmlnOiBQb29sQ29uZmlnKSA9PiBQb29sID0gKGNvbmZpZykgPT4gbmV3IFBvb2woY29uZmlnKSxcbiAgKSB7fVxuXG4gIC8qKiBMYXppbHkgYnVpbGQgKGFuZCBjYWNoZSkgdGhlIHBnIHBvb2wgZnJvbSB0aGUgcmVzb2x2ZWQgTGFrZWJhc2UgY29uZmlnLiAqL1xuICBwcml2YXRlIGFzeW5jIGdldFBvb2woKTogUHJvbWlzZTxQb29sPiB7XG4gICAgaWYgKHRoaXMucG9vbCkgcmV0dXJuIHRoaXMucG9vbDtcbiAgICB0aGlzLnBvb2xQcm9taXNlID8/PSAoYXN5bmMgKCkgPT4ge1xuICAgICAgY29uc3QgY29uZmlnID0gYXdhaXQgdGhpcy5wZ0NvbmZpZ0ZhY3RvcnkoKTtcbiAgICAgIGNvbnN0IHBvb2wgPSB0aGlzLnBvb2xGYWN0b3J5KGNvbmZpZyk7XG4gICAgICB0aGlzLnBvb2wgPSBwb29sO1xuICAgICAgcmV0dXJuIHBvb2w7XG4gICAgfSkoKTtcbiAgICByZXR1cm4gdGhpcy5wb29sUHJvbWlzZTtcbiAgfVxuXG4gIC8qKiBDbG9zZSB0aGUgcG9vbCBzbyBhIHJlc3RhcnRlZCBhcHAgcmVidWlsZHMgaXQuICovXG4gIGFzeW5jIGNsb3NlKCk6IFByb21pc2U8dm9pZD4ge1xuICAgIGNvbnN0IHBvb2wgPSB0aGlzLnBvb2w7XG4gICAgdGhpcy5wb29sID0gdW5kZWZpbmVkO1xuICAgIHRoaXMucG9vbFByb21pc2UgPSB1bmRlZmluZWQ7XG4gICAgdGhpcy5wcm92aXNpb25lZC5jbGVhcigpO1xuICAgIGlmIChwb29sKSBhd2FpdCBwb29sLmVuZCgpO1xuICB9XG5cbiAgLyoqXG4gICAqIFNlYXJjaCBhIExha2ViYXNlLWJhY2tlZCBpbmRleC4gUmV0dXJucyBoaXRzIHNvcnRlZCBtb3N0LXJlbGV2YW50LWZpcnN0LFxuICAgKiBzaGFwZWQgaWRlbnRpY2FsbHkgdG8gdGhlIFZlY3RvciBTZWFyY2ggYmFja2VuZC5cbiAgICovXG4gIGFzeW5jIHNlYXJjaChcbiAgICBpbmRleDogc3RyaW5nLFxuICAgIHF1ZXJ5OiBzdHJpbmcsXG4gICAgb3B0aW9uczogTGFrZWJhc2VTZWFyY2hPcHRpb25zID0ge30sXG4gICk6IFByb21pc2U8U2VhcmNoUmVzdWx0PiB7XG4gICAgY29uc3QgdGV4dCA9IHN0cmluZy50cmltVG9FbXB0eShxdWVyeSk7XG4gICAgY29uc3QgdGFibGUgPSB0aGlzLnRhYmxlRm9yKGluZGV4KTtcbiAgICBjb25zdCBsaW1pdCA9IG9wdGlvbnMubGltaXQgPz8gMTA7XG4gICAgY29uc3QgcG9vbCA9IGF3YWl0IHRoaXMuZ2V0UG9vbCgpO1xuXG4gICAgLy8gYHdlYnNlYXJjaF90b190c3F1ZXJ5YCBhY2NlcHRzIGEgYmFyZSBzZWFyY2ggYm94IHN0cmluZyAocXVvdGVkIHBocmFzZXMsXG4gICAgLy8gYG9yYCwgYC10ZXJtYCk7IGFuIGVtcHR5IHF1ZXJ5IHJldHVybnMgdGhlIG1vc3QgcmVjZW50IHJvd3Mgc28gYW4gZW1wdHlcbiAgICAvLyBzZWFyY2ggYm94IHN0aWxsIHNob3dzIGNvbnRlbnQsIG1hdGNoaW5nIGEga2V5d29yZCBpbmRleCdzIGJlaGF2aW9yLlxuICAgIGNvbnN0IHNxbCA9IHRleHRcbiAgICAgID8gYFNFTEVDVCBpZCwgZG9jdW1lbnQsIHRzX3Jhbmsoc2VhcmNoX3ZlY3Rvciwgd2Vic2VhcmNoX3RvX3RzcXVlcnkoJ2VuZ2xpc2gnLCAkMSkpIEFTIHNjb3JlXG4gICAgICAgICAgIEZST00gJHt0YWJsZX1cbiAgICAgICAgICBXSEVSRSBzZWFyY2hfdmVjdG9yIEBAIHdlYnNlYXJjaF90b190c3F1ZXJ5KCdlbmdsaXNoJywgJDEpXG4gICAgICAgICAgT1JERVIgQlkgc2NvcmUgREVTQ1xuICAgICAgICAgIExJTUlUICQyYFxuICAgICAgOiBgU0VMRUNUIGlkLCBkb2N1bWVudCwgMDo6ZmxvYXQ0IEFTIHNjb3JlIEZST00gJHt0YWJsZX0gT1JERVIgQlkgaWQgTElNSVQgJDJgO1xuICAgIGNvbnN0IHBhcmFtcyA9IHRleHQgPyBbdGV4dCwgbGltaXRdIDogW2xpbWl0XTtcblxuICAgIGNvbnN0IHsgcm93cyB9ID0gYXdhaXQgdGhpcy5xdWVyeTx7IGlkOiBzdHJpbmc7IGRvY3VtZW50OiB1bmtub3duOyBzY29yZTogbnVtYmVyIH0+KFxuICAgICAgcG9vbCxcbiAgICAgIHNxbCxcbiAgICAgIHBhcmFtcyxcbiAgICAgIG9wdGlvbnMuc2lnbmFsLFxuICAgICk7XG4gICAgY29uc3QgaGl0czogU2VhcmNoSGl0W10gPSByb3dzXG4gICAgICAubWFwKChyb3cpID0+ICh7XG4gICAgICAgIGlkOiBTdHJpbmcocm93LmlkKSxcbiAgICAgICAgc2NvcmU6IE51bWJlcihyb3cuc2NvcmUpIHx8IDAsXG4gICAgICAgIGZpZWxkczogdGhpcy50b0ZpZWxkcyhyb3cuZG9jdW1lbnQpLFxuICAgICAgfSkpXG4gICAgICAuZmlsdGVyKChoaXQpID0+IG9wdGlvbnMuc2NvcmVUaHJlc2hvbGQgPT09IHVuZGVmaW5lZCB8fCBoaXQuc2NvcmUgPj0gb3B0aW9ucy5zY29yZVRocmVzaG9sZCk7XG4gICAgcmV0dXJuIHsgcXVlcnk6IHRleHQsIGluZGV4LCBoaXRzLCBjb3VudDogaGl0cy5sZW5ndGggfTtcbiAgfVxuXG4gIC8qKlxuICAgKiBFbnN1cmUgdGhlIHRhYmxlICsgZnVsbC10ZXh0IGluZGV4IGV4aXN0IGFuZCBzZWVkIGRvY3VtZW50cyB3aGVuIGVtcHR5LlxuICAgKiBJZGVtcG90ZW50LCBzbyBpdCBpcyBzYWZlIHRvIGNhbGwgb24gZXZlcnkgYm9vdC5cbiAgICovXG4gIGFzeW5jIHByb3Zpc2lvbihpbmRleDogc3RyaW5nLCBvcHRpb25zOiBMYWtlYmFzZVByb3Zpc2lvbk9wdGlvbnMgPSB7fSk6IFByb21pc2U8bnVtYmVyPiB7XG4gICAgY29uc3QgdGFibGUgPSB0aGlzLnRhYmxlRm9yKGluZGV4KTtcbiAgICBjb25zdCBwb29sID0gYXdhaXQgdGhpcy5nZXRQb29sKCk7XG4gICAgYXdhaXQgdGhpcy5lbnN1cmVUYWJsZShwb29sLCB0YWJsZSwgb3B0aW9ucy5zaWduYWwpO1xuXG4gICAgY29uc3QgeyByb3dzIH0gPSBhd2FpdCB0aGlzLnF1ZXJ5PHsgY291bnQ6IHN0cmluZyB9PihcbiAgICAgIHBvb2wsXG4gICAgICBgU0VMRUNUIGNvdW50KCopOjp0ZXh0IEFTIGNvdW50IEZST00gJHt0YWJsZX1gLFxuICAgICAgW10sXG4gICAgICBvcHRpb25zLnNpZ25hbCxcbiAgICApO1xuICAgIGNvbnN0IGV4aXN0aW5nID0gTnVtYmVyKHJvd3NbMF0/LmNvdW50ID8/IFwiMFwiKTtcbiAgICBjb25zdCBzZWVkID0gb3B0aW9ucy5zZWVkID8/IFtdO1xuICAgIGlmIChleGlzdGluZyA9PT0gMCAmJiBzZWVkLmxlbmd0aCA+IDApIHtcbiAgICAgIGF3YWl0IHRoaXMudXBzZXJ0KHBvb2wsIHRhYmxlLCBzZWVkLCBvcHRpb25zLnRleHRDb2x1bW4gPz8gXCJ0ZXh0XCIsIG9wdGlvbnMuc2lnbmFsKTtcbiAgICAgIGxvZ2dlci5pbmZvKFwiaW5kZXgtc2VlZGVkXCIsIHsgaW5kZXgsIHRhYmxlLCBjb3VudDogc2VlZC5sZW5ndGggfSk7XG4gICAgICByZXR1cm4gc2VlZC5sZW5ndGg7XG4gICAgfVxuICAgIHJldHVybiBleGlzdGluZztcbiAgfVxuXG4gIC8qKiBBZGQgb3IgdXBkYXRlIGRvY3VtZW50cyBieSBwcmltYXJ5IGtleS4gKi9cbiAgYXN5bmMgYWRkRG9jdW1lbnRzKFxuICAgIGluZGV4OiBzdHJpbmcsXG4gICAgZG9jdW1lbnRzOiBTZWFyY2hEb2N1bWVudFtdLFxuICAgIHRleHRDb2x1bW4gPSBcInRleHRcIixcbiAgICBzaWduYWw/OiBBYm9ydFNpZ25hbCxcbiAgKTogUHJvbWlzZTxVcHNlcnRSZXN1bHQ+IHtcbiAgICBjb25zdCB0YWJsZSA9IHRoaXMudGFibGVGb3IoaW5kZXgpO1xuICAgIGNvbnN0IHBvb2wgPSBhd2FpdCB0aGlzLmdldFBvb2woKTtcbiAgICBhd2FpdCB0aGlzLmVuc3VyZVRhYmxlKHBvb2wsIHRhYmxlLCBzaWduYWwpO1xuICAgIGF3YWl0IHRoaXMudXBzZXJ0KHBvb2wsIHRhYmxlLCBkb2N1bWVudHMsIHRleHRDb2x1bW4sIHNpZ25hbCk7XG4gICAgcmV0dXJuIHsgaW5kZXgsIGNvdW50OiBkb2N1bWVudHMubGVuZ3RoIH07XG4gIH1cblxuICAvKiogRGVsZXRlIGRvY3VtZW50cyBieSBwcmltYXJ5IGtleS4gKi9cbiAgYXN5bmMgZGVsZXRlRG9jdW1lbnRzKFxuICAgIGluZGV4OiBzdHJpbmcsXG4gICAgaWRzOiBBcnJheTxzdHJpbmcgfCBudW1iZXI+LFxuICAgIHNpZ25hbD86IEFib3J0U2lnbmFsLFxuICApOiBQcm9taXNlPFVwc2VydFJlc3VsdD4ge1xuICAgIGNvbnN0IHRhYmxlID0gdGhpcy50YWJsZUZvcihpbmRleCk7XG4gICAgY29uc3QgcG9vbCA9IGF3YWl0IHRoaXMuZ2V0UG9vbCgpO1xuICAgIGF3YWl0IHRoaXMucXVlcnkocG9vbCwgYERFTEVURSBGUk9NICR7dGFibGV9IFdIRVJFIGlkID0gQU5ZKCQxKWAsIFtpZHMubWFwKFN0cmluZyldLCBzaWduYWwpO1xuICAgIHJldHVybiB7IGluZGV4LCBjb3VudDogaWRzLmxlbmd0aCB9O1xuICB9XG5cbiAgLyoqIENyZWF0ZSB0aGUgdGFibGUgKyBHSU4gaW5kZXggb25jZSBwZXIgdGFibGUgKG1lbW9pemVkIGFjcm9zcyBjYWxscykuICovXG4gIHByaXZhdGUgYXN5bmMgZW5zdXJlVGFibGUocG9vbDogUG9vbCwgdGFibGU6IHN0cmluZywgc2lnbmFsPzogQWJvcnRTaWduYWwpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBpZiAodGhpcy5wcm92aXNpb25lZC5oYXModGFibGUpKSByZXR1cm47XG4gICAgLy8gYGRvY3VtZW50YCBob2xkcyB0aGUgd2hvbGUgcm93OyBgc2VhcmNoX3RleHRgIGlzIHRoZSBpbmRleGVkIHRleHQ7IHRoZVxuICAgIC8vIGdlbmVyYXRlZCBgc2VhcmNoX3ZlY3RvcmAga2VlcHMgdGhlIHRzdmVjdG9yIGluIGxvY2tzdGVwIHdpdGggaXQgc28gYVxuICAgIC8vIHdyaXRlIG5ldmVyIGhhcyB0byBjb21wdXRlIHRoZSB2ZWN0b3IgYnkgaGFuZC5cbiAgICBhd2FpdCB0aGlzLnF1ZXJ5KHBvb2wsIGBDUkVBVEUgU0NIRU1BIElGIE5PVCBFWElTVFMgJHt0aGlzLmlkZW50KHRoaXMuc2NoZW1hKX1gLCBbXSwgc2lnbmFsKTtcbiAgICBhd2FpdCB0aGlzLnF1ZXJ5KFxuICAgICAgcG9vbCxcbiAgICAgIGBDUkVBVEUgVEFCTEUgSUYgTk9UIEVYSVNUUyAke3RhYmxlfSAoXG4gICAgICAgICBpZCB0ZXh0IFBSSU1BUlkgS0VZLFxuICAgICAgICAgc2VhcmNoX3RleHQgdGV4dCBOT1QgTlVMTCBERUZBVUxUICcnLFxuICAgICAgICAgZG9jdW1lbnQganNvbmIgTk9UIE5VTEwgREVGQVVMVCAne30nOjpqc29uYixcbiAgICAgICAgIHNlYXJjaF92ZWN0b3IgdHN2ZWN0b3IgR0VORVJBVEVEIEFMV0FZUyBBUyAodG9fdHN2ZWN0b3IoJ2VuZ2xpc2gnLCBzZWFyY2hfdGV4dCkpIFNUT1JFRFxuICAgICAgIClgLFxuICAgICAgW10sXG4gICAgICBzaWduYWwsXG4gICAgKTtcbiAgICBhd2FpdCB0aGlzLnF1ZXJ5KFxuICAgICAgcG9vbCxcbiAgICAgIGBDUkVBVEUgSU5ERVggSUYgTk9UIEVYSVNUUyAke3RoaXMuaWRlbnQoYCR7dGhpcy5iYXJlTmFtZSh0YWJsZSl9X2Z0c2ApfVxuICAgICAgICAgT04gJHt0YWJsZX0gVVNJTkcgZ2luIChzZWFyY2hfdmVjdG9yKWAsXG4gICAgICBbXSxcbiAgICAgIHNpZ25hbCxcbiAgICApO1xuICAgIHRoaXMucHJvdmlzaW9uZWQuYWRkKHRhYmxlKTtcbiAgICBsb2dnZXIuaW5mbyhcImluZGV4LWNyZWF0ZWRcIiwgeyB0YWJsZSB9KTtcbiAgfVxuXG4gIC8qKiBVcHNlcnQgcm93czogdGhlIHdob2xlIGRvY3VtZW50IGFzIGpzb25iICsgYSBmbGF0dGVuZWQgdGV4dCBibG9iIHRvIGluZGV4LiAqL1xuICBwcml2YXRlIGFzeW5jIHVwc2VydChcbiAgICBwb29sOiBQb29sLFxuICAgIHRhYmxlOiBzdHJpbmcsXG4gICAgZG9jdW1lbnRzOiBTZWFyY2hEb2N1bWVudFtdLFxuICAgIHRleHRDb2x1bW46IHN0cmluZyxcbiAgICBzaWduYWw/OiBBYm9ydFNpZ25hbCxcbiAgKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgZm9yIChjb25zdCBkb2Mgb2YgZG9jdW1lbnRzKSB7XG4gICAgICBjb25zdCBpZCA9IHN0cmluZy50cmltVG9OdWxsKFN0cmluZyhkb2MuaWQgPz8gZG9jLklEID8/IFwiXCIpKSA/PyB1bmRlZmluZWQ7XG4gICAgICBpZiAoaWQgPT09IHVuZGVmaW5lZCkge1xuICAgICAgICB0aHJvdyBuZXcgRXhlY3V0aW9uRXJyb3IoXCJzZWFyY2ggKGxha2ViYXNlKTogYSBkb2N1bWVudCBpcyBtaXNzaW5nIGFuIGBpZGBcIiwge1xuICAgICAgICAgIGNvbnRleHQ6IHsgb3BlcmF0aW9uOiBcImFkZERvY3VtZW50c1wiIH0sXG4gICAgICAgIH0pO1xuICAgICAgfVxuICAgICAgY29uc3Qgc2VhcmNoVGV4dCA9IHRoaXMuc2VhcmNoVGV4dChkb2MsIHRleHRDb2x1bW4pO1xuICAgICAgYXdhaXQgdGhpcy5xdWVyeShcbiAgICAgICAgcG9vbCxcbiAgICAgICAgYElOU0VSVCBJTlRPICR7dGFibGV9IChpZCwgc2VhcmNoX3RleHQsIGRvY3VtZW50KVxuICAgICAgICAgICBWQUxVRVMgKCQxLCAkMiwgJDM6Ompzb25iKVxuICAgICAgICAgT04gQ09ORkxJQ1QgKGlkKSBETyBVUERBVEVcbiAgICAgICAgICAgU0VUIHNlYXJjaF90ZXh0ID0gRVhDTFVERUQuc2VhcmNoX3RleHQsIGRvY3VtZW50ID0gRVhDTFVERUQuZG9jdW1lbnRgLFxuICAgICAgICBbaWQsIHNlYXJjaFRleHQsIEpTT04uc3RyaW5naWZ5KGRvYyldLFxuICAgICAgICBzaWduYWwsXG4gICAgICApO1xuICAgIH1cbiAgfVxuXG4gIC8qKiBUaGUgdGV4dCBhIHJvdyBpcyBpbmRleGVkIGJ5OiB0aGUgdGV4dCBjb2x1bW4gZmlyc3QsIHRoZW4gYW55IG90aGVyIHN0cmluZyBmaWVsZC4gKi9cbiAgcHJpdmF0ZSBzZWFyY2hUZXh0KGRvYzogU2VhcmNoRG9jdW1lbnQsIHRleHRDb2x1bW46IHN0cmluZyk6IHN0cmluZyB7XG4gICAgY29uc3QgcHJpbWFyeSA9IHN0cmluZy50cmltVG9FbXB0eShTdHJpbmcoZG9jW3RleHRDb2x1bW5dID8/IFwiXCIpKTtcbiAgICBjb25zdCByZXN0ID0gT2JqZWN0LmVudHJpZXMoZG9jKVxuICAgICAgLmZpbHRlcigoW2tleSwgdmFsdWVdKSA9PiBrZXkgIT09IHRleHRDb2x1bW4gJiYga2V5ICE9PSBcImlkXCIgJiYgdHlwZW9mIHZhbHVlID09PSBcInN0cmluZ1wiKVxuICAgICAgLm1hcCgoWywgdmFsdWVdKSA9PiB2YWx1ZSBhcyBzdHJpbmcpO1xuICAgIHJldHVybiBbcHJpbWFyeSwgLi4ucmVzdF0uZmlsdGVyKEJvb2xlYW4pLmpvaW4oXCJcXG5cIik7XG4gIH1cblxuICAvKiogQSBoaXQncyBgZmllbGRzYDogdGhlIHN0b3JlZCBkb2N1bWVudCBtaW51cyB0aGUgcmVzZXJ2ZWQvaW50ZXJuYWwga2V5cy4gKi9cbiAgcHJpdmF0ZSB0b0ZpZWxkcyhkb2N1bWVudDogdW5rbm93bik6IFJlY29yZDxzdHJpbmcsIHVua25vd24+IHtcbiAgICBpZiAoIW9iamVjdC5pc1JlY29yZChkb2N1bWVudCkpIHJldHVybiB7fTtcbiAgICBjb25zdCBmaWVsZHM6IFJlY29yZDxzdHJpbmcsIHVua25vd24+ID0ge307XG4gICAgZm9yIChjb25zdCBba2V5LCB2YWx1ZV0gb2YgT2JqZWN0LmVudHJpZXMoZG9jdW1lbnQpKSB7XG4gICAgICBpZiAoUkVTRVJWRURfQ09MVU1OUy5oYXMoa2V5KSkgY29udGludWU7XG4gICAgICBmaWVsZHNba2V5XSA9IHZhbHVlO1xuICAgIH1cbiAgICByZXR1cm4gZmllbGRzO1xuICB9XG5cbiAgLyoqIFRoZSBmdWxseS1xdWFsaWZpZWQgdGFibGUgbmFtZSBmb3IgYW4gaW5kZXggcmVmZXJlbmNlLiAqL1xuICBwcml2YXRlIHRhYmxlRm9yKGluZGV4OiBzdHJpbmcpOiBzdHJpbmcge1xuICAgIHJldHVybiBgJHt0aGlzLmlkZW50KHRoaXMuc2NoZW1hKX0uJHt0aGlzLmlkZW50KHRoaXMuYmFyZU5hbWUoaW5kZXgpKX1gO1xuICB9XG5cbiAgLyoqIEEgc2FmZSBiYXJlIHRhYmxlIG5hbWUgZGVyaXZlZCBmcm9tIGFuIGluZGV4IHJlZmVyZW5jZS4gKi9cbiAgcHJpdmF0ZSBiYXJlTmFtZShyZWZlcmVuY2U6IHN0cmluZyk6IHN0cmluZyB7XG4gICAgY29uc3QgbGFzdCA9IHJlZmVyZW5jZS5zcGxpdChcIi5cIikuZmlsdGVyKEJvb2xlYW4pLnBvcCgpID8/IHJlZmVyZW5jZTtcbiAgICBjb25zdCBzbHVnID0gbGFzdFxuICAgICAgLnRvTG93ZXJDYXNlKClcbiAgICAgIC5yZXBsYWNlKC9bXmEtejAtOV9dKy9nLCBcIl9cIilcbiAgICAgIC5yZXBsYWNlKC9eXyt8XyskL2csIFwiXCIpO1xuICAgIHJldHVybiBzbHVnLmxlbmd0aCA+IDAgPyBzbHVnIDogXCJkb2N1bWVudHNcIjtcbiAgfVxuXG4gIC8qKiBRdW90ZSBhIFBvc3RncmVzIGlkZW50aWZpZXIuICovXG4gIHByaXZhdGUgaWRlbnQobmFtZTogc3RyaW5nKTogc3RyaW5nIHtcbiAgICByZXR1cm4gYFwiJHtuYW1lLnJlcGxhY2UoL1wiL2csICdcIlwiJyl9XCJgO1xuICB9XG5cbiAgLyoqIFJ1biBvbmUgcXVlcnkgdW5kZXIgZXh0ZXJuYWwgY2FuY2VsbGF0aW9uLiAqL1xuICBwcml2YXRlIGFzeW5jIHF1ZXJ5PFQ+KFxuICAgIHBvb2w6IFBvb2wsXG4gICAgc3FsOiBzdHJpbmcsXG4gICAgcGFyYW1zOiB1bmtub3duW10sXG4gICAgc2lnbmFsPzogQWJvcnRTaWduYWwsXG4gICk6IFByb21pc2U8eyByb3dzOiBUW10gfT4ge1xuICAgIGlmIChzaWduYWw/LmFib3J0ZWQpIHRocm93IEV4ZWN1dGlvbkVycm9yLmNhbmNlbGVkKCk7XG4gICAgY29uc3QgY2xpZW50ID0gYXdhaXQgcG9vbC5jb25uZWN0KCk7XG4gICAgY29uc3Qgb25BYm9ydCA9ICgpID0+IHZvaWQgY2xpZW50LnJlbGVhc2UodHJ1ZSk7XG4gICAgc2lnbmFsPy5hZGRFdmVudExpc3RlbmVyKFwiYWJvcnRcIiwgb25BYm9ydCwgeyBvbmNlOiB0cnVlIH0pO1xuICAgIHRyeSB7XG4gICAgICBjb25zdCByZXN1bHQgPSBhd2FpdCBjbGllbnQucXVlcnkoc3FsLCBwYXJhbXMpO1xuICAgICAgcmV0dXJuIHsgcm93czogcmVzdWx0LnJvd3MgYXMgVFtdIH07XG4gICAgfSBmaW5hbGx5IHtcbiAgICAgIHNpZ25hbD8ucmVtb3ZlRXZlbnRMaXN0ZW5lcihcImFib3J0XCIsIG9uQWJvcnQpO1xuICAgICAgY2xpZW50LnJlbGVhc2UoKTtcbiAgICB9XG4gIH1cbn1cbiJdfQ==
@@ -0,0 +1,216 @@
1
+ /**
2
+ * AppKit plugin (registered name: `search`) that turns a Databricks AI
3
+ * Search (Vector Search) index into a batteries-included search surface. It is
4
+ * the "shortcut" half of this package: register it with nothing but an index
5
+ * name and you get, all at once -
6
+ *
7
+ * - a `search` / `universal_search` / (opt-in) `add_documents` tool set for
8
+ * both Mastra and AppKit agents (autocomplete is a small-`limit` search);
9
+ * - HTTP routes under `/api/search` a browser search box calls directly
10
+ * (`POST /` search, `POST /universal` federated, `GET /indexes` catalogue,
11
+ * and `POST /documents` when writes are enabled);
12
+ * - a `clientConfig()` payload so a UI knows the indexes, default, and page
13
+ * size at boot with no round-trip;
14
+ * - `exports()` so app code can `appkit.search.search(...)` directly.
15
+ *
16
+ * Everything runs under the caller's OBO identity (routes wrap in `asUser`),
17
+ * so Unity Catalog ACLs on the index apply. Registering the plugin resolves
18
+ * and logs the effective config (default index, known indexes, page size,
19
+ * mode) so a misconfiguration shows up in the boot log rather than on the
20
+ * first search.
21
+ *
22
+ * @module
23
+ */
24
+ import { Plugin, ResourceType, type IAppRouter, type ResourceRequirement } from "@databricks/appkit";
25
+ import { type AgentToolDefinition, type ToolProvider } from "@databricks/appkit/beta";
26
+ import { search as searchContract } from "@dbx-tools/shared-search";
27
+ import { type SearchPluginConfig } from "./config.ts";
28
+ /**
29
+ * AppKit plugin exposing AI Search as a search box, an agent tool set, and a
30
+ * direct API.
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * import { createApp, server } from "@databricks/appkit";
35
+ * import { plugin as searchPlugin } from "@dbx-tools/search";
36
+ *
37
+ * await createApp({
38
+ * plugins: [
39
+ * server(),
40
+ * // zero-config: reads DATABRICKS_VECTOR_SEARCH_INDEX
41
+ * searchPlugin.search(),
42
+ * // or go deeper:
43
+ * // searchPlugin.search({
44
+ * // index: "main.support.docs",
45
+ * // indexes: ["main.support.docs", "main.catalog.products"],
46
+ * // columns: ["title", "url", "body"],
47
+ * // mode: "hybrid",
48
+ * // }),
49
+ * ],
50
+ * });
51
+ * ```
52
+ */
53
+ export declare class SearchPlugin extends Plugin<SearchPluginConfig> implements ToolProvider {
54
+ static manifest: {
55
+ name: "search";
56
+ displayName: string;
57
+ description: string;
58
+ stability: "beta";
59
+ resources: {
60
+ required: never[];
61
+ optional: {
62
+ type: ResourceType.VECTOR_SEARCH_INDEX;
63
+ alias: string;
64
+ resourceKey: string;
65
+ description: string;
66
+ permission: "SELECT";
67
+ fields: {
68
+ name: {
69
+ env: string;
70
+ description: string;
71
+ discovery: {
72
+ type: "cli";
73
+ cliCommand: string;
74
+ selectField: string;
75
+ };
76
+ };
77
+ };
78
+ }[];
79
+ };
80
+ config: {
81
+ schema: import("json-schema").JSONSchema7;
82
+ };
83
+ };
84
+ /** The base path AppKit mounts this plugin's routes under. */
85
+ private get basePath();
86
+ /**
87
+ * Promote the index to a required resource once a deployment pins a default,
88
+ * through plugin config or either environment name.
89
+ */
90
+ static getResourceRequirements(config: SearchPluginConfig): ResourceRequirement[];
91
+ /**
92
+ * The tools this plugin offers to an AppKit agent. `search` /
93
+ * `universal_search` are reads; `add_documents` / `create_index` /
94
+ * `sync_index` are only offered when the write surface is enabled. None is
95
+ * autoInheritable: every tool runs under the caller's identity, so it must be
96
+ * granted explicitly.
97
+ */
98
+ private get tools();
99
+ /** Prime the shared runtime from config and log the effective policy at boot. */
100
+ setup(): Promise<void>;
101
+ /**
102
+ * Build the Lakebase FALLBACK backend, or return `undefined` when Vector
103
+ * Search should be used. Falls back only when there is NO Vector Search
104
+ * endpoint configured (plugin config or `SEARCH_ENDPOINT` /
105
+ * `DATABRICKS_VECTOR_SEARCH_ENDPOINT`) AND the sibling AppKit `lakebase`
106
+ * plugin is registered. The pg pool is built from that plugin's
107
+ * service-principal config exactly like `@dbx-tools/appkit-mastra`'s memory
108
+ * pool - no auth is re-implemented here.
109
+ */
110
+ private resolveLakebaseBackend;
111
+ /**
112
+ * Ensure the endpoint + index exist and seed them, honoring `ensureOnSetup`.
113
+ * Uses boot-time SDK auth (env / config profile) via the client's
114
+ * out-of-request fallback. Failures are logged, never thrown - a search app
115
+ * should still start even if provisioning is slow or a permission is missing.
116
+ */
117
+ private runEnsureOnSetup;
118
+ /** Build a Vector Search `schema_json` map from a seed document's value types. */
119
+ private inferSchema;
120
+ /** Drop the shared runtime so a restarted app re-resolves config. */
121
+ shutdown(): Promise<void>;
122
+ abortActiveOperations(): void;
123
+ /**
124
+ * Mount the search routes under `/api/search`. Each is wrapped in
125
+ * `asUser(req)` so the query runs as the requesting user and the index's
126
+ * Unity Catalog ACLs apply. `GET /indexes` needs no user scope - it just
127
+ * echoes the configured catalogue.
128
+ */
129
+ injectRoutes(router: IAppRouter): void;
130
+ /**
131
+ * Run a route body, turning a failure into a JSON error response.
132
+ *
133
+ * AppKit's `route()` registers the handler as-is, so a rejection escapes as
134
+ * an unhandled promise rejection and takes the whole process down rather than
135
+ * failing the one request. Every route here goes through this, so a bad index
136
+ * name or a Vector Search API error returns 500 with a message instead of
137
+ * killing the server.
138
+ */
139
+ private respond;
140
+ /** Surface the index catalogue + defaults so a search box needs no round-trip. */
141
+ clientConfig(): Record<string, unknown>;
142
+ /** AppKit `ToolProvider`: the tool definitions offered to an agent. */
143
+ getAgentTools(): AgentToolDefinition[];
144
+ /** AppKit `ToolProvider`: run one tool call, validating input against its schema. */
145
+ executeAgentTool(name: string, args: unknown, signal?: AbortSignal): Promise<unknown>;
146
+ exports(): {
147
+ /** Search one index (default when omitted). Runs as the current context user. */
148
+ search: (request: searchContract.SearchRequest, signal?: AbortSignal) => Promise<{
149
+ query: string;
150
+ hits: {
151
+ id: string;
152
+ score: number;
153
+ fields: Record<string, unknown>;
154
+ index?: string | undefined;
155
+ }[];
156
+ count: number;
157
+ index?: string | undefined;
158
+ }>;
159
+ /** Search across every configured index and merge the hits. */
160
+ universalSearch: (request: searchContract.UniversalSearchRequest, signal?: AbortSignal) => Promise<{
161
+ query: string;
162
+ hits: {
163
+ id: string;
164
+ score: number;
165
+ fields: Record<string, unknown>;
166
+ index?: string | undefined;
167
+ }[];
168
+ count: number;
169
+ index?: string | undefined;
170
+ }>;
171
+ /** Add or update documents in a direct-access index (throws when writes are disabled). */
172
+ addDocuments: (request: {
173
+ index?: string;
174
+ documents: unknown;
175
+ }, signal?: AbortSignal) => Promise<{
176
+ index: string;
177
+ count: number;
178
+ }>;
179
+ /** Create a Vector Search index (throws when writes are disabled). */
180
+ createIndex: (request: searchContract.CreateIndexRequest, signal?: AbortSignal) => Promise<import("./client.ts").IndexInfo>;
181
+ /** Sync a Delta Sync index from its source table (throws when writes are disabled). */
182
+ syncIndex: (request: searchContract.SyncIndexRequest, signal?: AbortSignal) => Promise<{
183
+ index: string;
184
+ synced: boolean;
185
+ }>;
186
+ };
187
+ private runSearch;
188
+ private runUniversalSearch;
189
+ private runAddDocuments;
190
+ private runCreateIndex;
191
+ private runSyncIndex;
192
+ }
193
+ /**
194
+ * Register the AI Search plugin with AppKit.
195
+ *
196
+ * @example
197
+ * ```ts
198
+ * import { createApp, server } from "@databricks/appkit";
199
+ * import { plugin as searchPlugin, tool as searchToolModule } from "@dbx-tools/search";
200
+ * import { agents, plugin as mastraPlugin } from "@dbx-tools/appkit-mastra";
201
+ *
202
+ * const support = agents.createAgent({
203
+ * instructions: "Answer from the docs; use `search` to find them.",
204
+ * tools: () => ({ search: searchToolModule.searchTool() }),
205
+ * });
206
+ *
207
+ * await createApp({
208
+ * plugins: [
209
+ * server(),
210
+ * searchPlugin.search({ index: "main.support.docs" }),
211
+ * mastraPlugin.mastra({ agents: support }),
212
+ * ],
213
+ * });
214
+ * ```
215
+ */
216
+ export declare const search: import("@databricks/appkit").ToPlugin<typeof SearchPlugin, SearchPluginConfig, "search">;