@dbx-tools/search 0.6.108 → 0.6.111

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/lib/src/query.js CHANGED
@@ -1,97 +1,6 @@
1
- /**
2
- * Translation between the browser-safe search contract and the Databricks
3
- * Vector Search query API, kept in one place so the client, the tools, and the
4
- * routes never hand-roll a `filters_json` string or unpack a `data_array` by
5
- * hand.
6
- *
7
- * - {@link toQueryType} maps the friendly {@link SearchMode} onto the API's
8
- * `query_type` (`HYBRID` / `ANN`). Keyword-only search rides on `ANN` with
9
- * text but no vector, which Databricks answers with its BM25 path.
10
- * - {@link compileFilter} turns the `{ column: value }` filter object (with
11
- * optional operator maps like `{ ">=": 10 }`) into the `filters_json`
12
- * string the API expects, so a caller never learns Databricks' filter
13
- * spelling.
14
- * - {@link toHits} unpacks the columnar `{ manifest, result }` response into
15
- * `{ id, score, fields }` hits, pulling the score out of the reserved
16
- * `__db_score` / `score` column and the id out of the primary-key column.
17
- *
18
- * @module
19
- */
1
+ /** Request-shape helpers for search extension write routes and tools. */
20
2
  import { ValidationError } from "@databricks/appkit";
21
3
  import { json, object } from "@dbx-tools/shared-core";
22
- /** The column name Databricks Vector Search returns the relevance score under. */
23
- const SCORE_COLUMN = "__db_score";
24
- /** Map a {@link SearchMode} onto the serving API `query_type`. */
25
- export function toQueryType(mode) {
26
- return mode === "hybrid" ? "HYBRID" : "ANN";
27
- }
28
- /**
29
- * Compile a `{ column: value }` filter object into the `filters_json` string
30
- * the query API expects. A scalar becomes an equality; an array becomes an
31
- * IN-style match; an operator map (`{ ">=": 10, "<": 20 }`) expands to the
32
- * `column operator` keys Databricks uses. Returns `undefined` for an empty
33
- * filter so the field is omitted rather than sent as `{}`.
34
- */
35
- export function compileFilter(filter) {
36
- if (!filter || Object.keys(filter).length === 0)
37
- return undefined;
38
- const compiled = {};
39
- for (const [column, value] of Object.entries(filter)) {
40
- if (object.isRecord(value)) {
41
- for (const [op, operand] of Object.entries(value)) {
42
- compiled[`${column} ${op}`] = operand;
43
- }
44
- }
45
- else {
46
- compiled[column] = value;
47
- }
48
- }
49
- return JSON.stringify(compiled);
50
- }
51
- /**
52
- * Unpack a columnar query response into {@link SearchHit}s. The manifest names
53
- * the columns in order; each row is a positional array. The score comes from
54
- * the reserved score column and the id from `primaryKey` (falling back to the
55
- * first column when the key is unknown). The score column is stripped from
56
- * `fields` so a hit's fields are just the document.
57
- */
58
- export function toHits(response, primaryKey, indexName) {
59
- const columns = (response.manifest?.columns ?? []).map((c) => c.name ?? "");
60
- const rows = response.result?.data_array ?? [];
61
- const scoreIdx = columns.indexOf(SCORE_COLUMN);
62
- const keyIdx = primaryKey ? columns.indexOf(primaryKey) : -1;
63
- return rows.map((row, rowIndex) => {
64
- const fields = {};
65
- columns.forEach((name, i) => {
66
- if (i === scoreIdx || !name)
67
- return;
68
- fields[name] = row[i];
69
- });
70
- const scoreRaw = scoreIdx >= 0 ? Number(row[scoreIdx]) : NaN;
71
- const idRaw = keyIdx >= 0 ? row[keyIdx] : primaryKey ? fields[primaryKey] : (row[0] ?? rowIndex);
72
- return {
73
- id: String(idRaw ?? rowIndex),
74
- score: Number.isFinite(scoreRaw) ? scoreRaw : 0,
75
- fields,
76
- ...(indexName ? { index: indexName } : {}),
77
- };
78
- });
79
- }
80
- /**
81
- * The columns to request from an index for a search. When neither the request
82
- * nor the index config names columns, the score column alone is requested and
83
- * the primary key is appended so a hit always has an id. Callers that want the
84
- * whole document should pass the index's own column list.
85
- */
86
- export function toRequestColumns(requested, fallback, primaryKey) {
87
- const base = requested && requested.length > 0 ? requested : (fallback ?? []);
88
- const columns = new Set(base);
89
- if (primaryKey)
90
- columns.add(primaryKey);
91
- if (columns.size === 0 && primaryKey)
92
- columns.add(primaryKey);
93
- return [...columns];
94
- }
95
4
  /**
96
5
  * Parse a JSON document payload the model / a route supplied for a write.
97
6
  * Accepts an already-parsed array/object or a JSON string, and always returns
@@ -106,4 +15,4 @@ export function toDocumentArray(input) {
106
15
  const list = Array.isArray(value) ? value : [value];
107
16
  return list.filter(object.isRecord);
108
17
  }
109
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicXVlcnkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvcXVlcnkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQWtCRztBQUVILE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUNyRCxPQUFPLEVBQUUsSUFBSSxFQUFFLE1BQU0sRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBR3RELGtGQUFrRjtBQUNsRixNQUFNLFlBQVksR0FBRyxZQUFZLENBQUM7QUFFbEMsa0VBQWtFO0FBQ2xFLE1BQU0sVUFBVSxXQUFXLENBQUMsSUFBZ0I7SUFDMUMsT0FBTyxJQUFJLEtBQUssUUFBUSxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQztBQUM5QyxDQUFDO0FBRUQ7Ozs7OztHQU1HO0FBQ0gsTUFBTSxVQUFVLGFBQWEsQ0FBQyxNQUEyQztJQUN2RSxJQUFJLENBQUMsTUFBTSxJQUFJLE1BQU0sQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUMsTUFBTSxLQUFLLENBQUM7UUFBRSxPQUFPLFNBQVMsQ0FBQztJQUNsRSxNQUFNLFFBQVEsR0FBNEIsRUFBRSxDQUFDO0lBQzdDLEtBQUssTUFBTSxDQUFDLE1BQU0sRUFBRSxLQUFLLENBQUMsSUFBSSxNQUFNLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUM7UUFDckQsSUFBSSxNQUFNLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUM7WUFDM0IsS0FBSyxNQUFNLENBQUMsRUFBRSxFQUFFLE9BQU8sQ0FBQyxJQUFJLE1BQU0sQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQztnQkFDbEQsUUFBUSxDQUFDLEdBQUcsTUFBTSxJQUFJLEVBQUUsRUFBRSxDQUFDLEdBQUcsT0FBTyxDQUFDO1lBQ3hDLENBQUM7UUFDSCxDQUFDO2FBQU0sQ0FBQztZQUNOLFFBQVEsQ0FB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QUFDLENBQUM7QUFDdEIsQ0FBQztBQUVEOzs7OztHQUtHO0FBQ0gsTUFBTSxVQUFVLGVBQWUsQ0FBQyxLQUFjO0lBQzVDLE1BQU0sS0FBSyxHQUFHLE9BQU8sS0FBSyxLQUFLLFFBQVEsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxLQUFLLEVBQUUsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQztJQUMvRSxJQUFJLEtBQUssS0FBSyxTQUFTLEVBQUUsQ0FBQztRQUN4QixNQUFNLElBQUksZUFBZSxDQUFDLG1EQUFtRCxDQUFDLENBQUM7SUFDakYsQ0FBQztJQUNELE1BQU0sSUFBSSxHQUFHLEtBQUssQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUNwRCxPQUFPLElBQUksQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxDQUFDO0FBQ3RDLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIFRyYW5zbGF0aW9uIGJldHdlZW4gdGhlIGJyb3dzZXItc2FmZSBzZWFyY2ggY29udHJhY3QgYW5kIHRoZSBEYXRhYnJpY2tzXG4gKiBWZWN0b3IgU2VhcmNoIHF1ZXJ5IEFQSSwga2VwdCBpbiBvbmUgcGxhY2Ugc28gdGhlIGNsaWVudCwgdGhlIHRvb2xzLCBhbmQgdGhlXG4gKiByb3V0ZXMgbmV2ZXIgaGFuZC1yb2xsIGEgYGZpbHRlcnNfanNvbmAgc3RyaW5nIG9yIHVucGFjayBhIGBkYXRhX2FycmF5YCBieVxuICogaGFuZC5cbiAqXG4gKiAgIC0ge0BsaW5rIHRvUXVlcnlUeXBlfSBtYXBzIHRoZSBmcmllbmRseSB7QGxpbmsgU2VhcmNoTW9kZX0gb250byB0aGUgQVBJJ3NcbiAqICAgICBgcXVlcnlfdHlwZWAgKGBIWUJSSURgIC8gYEFOTmApLiBLZXl3b3JkLW9ubHkgc2VhcmNoIHJpZGVzIG9uIGBBTk5gIHdpdGhcbiAqICAgICB0ZXh0IGJ1dCBubyB2ZWN0b3IsIHdoaWNoIERhdGFicmlja3MgYW5zd2VycyB3aXRoIGl0cyBCTTI1IHBhdGguXG4gKiAgIC0ge0BsaW5rIGNvbXBpbGVGaWx0ZXJ9IHR1cm5zIHRoZSBgeyBjb2x1bW46IHZhbHVlIH1gIGZpbHRlciBvYmplY3QgKHdpdGhcbiAqICAgICBvcHRpb25hbCBvcGVyYXRvciBtYXBzIGxpa2UgYHsgXCI+PVwiOiAxMCB9YCkgaW50byB0aGUgYGZpbHRlcnNfanNvbmBcbiAqICAgICBzdHJpbmcgdGhlIEFQSSBleHBlY3RzLCBzbyBhIGNhbGxlciBuZXZlciBsZWFybnMgRGF0YWJyaWNrcycgZmlsdGVyXG4gKiAgICAgc3BlbGxpbmcuXG4gKiAgIC0ge0BsaW5rIHRvSGl0c30gdW5wYWNrcyB0aGUgY29sdW1uYXIgYHsgbWFuaWZlc3QsIHJlc3VsdCB9YCByZXNwb25zZSBpbnRvXG4gKiAgICAgYHsgaWQsIHNjb3JlLCBmaWVsZHMgfWAgaGl0cywgcHVsbGluZyB0aGUgc2NvcmUgb3V0IG9mIHRoZSByZXNlcnZlZFxuICogICAgIGBfX2RiX3Njb3JlYCAvIGBzY29yZWAgY29sdW1uIGFuZCB0aGUgaWQgb3V0IG9mIHRoZSBwcmltYXJ5LWtleSBjb2x1bW4uXG4gKlxuICogQG1vZHVsZVxuICovXG5cbmltcG9ydCB7IFZhbGlkYXRpb25FcnJvciB9IGZyb20gXCJAZGF0YWJyaWNrcy9hcHBraXRcIjtcbmltcG9ydCB7IGpzb24sIG9iamVjdCB9IGZyb20gXCJAZGJ4LXRvb2xzL3NoYXJlZC1jb3JlXCI7XG5pbXBvcnQgdHlwZSB7IFNlYXJjaEhpdCwgU2VhcmNoTW9kZSB9IGZyb20gXCJAZGJ4LXRvb2xzL3NoYXJlZC1zZWFyY2hcIjtcblxuLyoqIFRoZSBjb2x1bW4gbmFtZSBEYXRhYnJpY2tzIFZlY3RvciBTZWFyY2ggcmV0dXJucyB0aGUgcmVsZXZhbmNlIHNjb3JlIHVuZGVyLiAqL1xuY29uc3QgU0NPUkVfQ09MVU1OID0gXCJfX2RiX3Njb3JlXCI7XG5cbi8qKiBNYXAgYSB7QGxpbmsgU2VhcmNoTW9kZX0gb250byB0aGUgc2VydmluZyBBUEkgYHF1ZXJ5X3R5cGVgLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHRvUXVlcnlUeXBlKG1vZGU6IFNlYXJjaE1vZGUpOiBzdHJpbmcge1xuICByZXR1cm4gbW9kZSA9PT0gXCJoeWJyaWRcIiA/IFwiSFlCUklEXCIgOiBcIkFOTlwiO1xufVxuXG4vKipcbiAqIENvbXBpbGUgYSBgeyBjb2x1bW46IHZhbHVlIH1gIGZpbHRlciBvYmplY3QgaW50byB0aGUgYGZpbHRlcnNfanNvbmAgc3RyaW5nXG4gKiB0aGUgcXVlcnkgQVBJIGV4cGVjdHMuIEEgc2NhbGFyIGJlY29tZXMgYW4gZXF1YWxpdHk7IGFuIGFycmF5IGJlY29tZXMgYW5cbiAqIElOLXN0eWxlIG1hdGNoOyBhbiBvcGVyYXRvciBtYXAgKGB7IFwiPj1cIjogMTAsIFwiPFwiOiAyMCB9YCkgZXhwYW5kcyB0byB0aGVcbiAqIGBjb2x1bW4gb3BlcmF0b3JgIGtleXMgRGF0YWJyaWNrcyB1c2VzLiBSZXR1cm5zIGB1bmRlZmluZWRgIGZvciBhbiBlbXB0eVxuICogZmlsdGVyIHNvIHRoZSBmaWVsZCBpcyBvbWl0dGVkIHJhdGhlciB0aGFuIHNlbnQgYXMgYHt9YC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNvbXBpbGVGaWx0ZXIoZmlsdGVyOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPiB8IHVuZGVmaW5lZCk6IHN0cmluZyB8IHVuZGVmaW5lZCB7XG4gIGlmICghZmlsdGVyIHx8IE9iamVjdC5rZXlzKGZpbHRlcikubGVuZ3RoID09PSAwKSByZXR1cm4gdW5kZWZpbmVkO1xuICBjb25zdCBjb21waWxlZDogUmVjb3JkPHN0cmluZywgdW5rbm93bj4gPSB7fTtcbiAgZm9yIChjb25zdCBbY29sdW1uLCB2YWx1ZV0gb2YgT2JqZWN0LmVudHJpZXMoZmlsdGVyKSkge1xuICAgIGlmIChvYmplY3QuaXNSZWNvcmQodmFsdWUpKSB7XG4gICAgICBmb3IgKGNvbnN0IFtvcCwgb3BlcmFuZF0gb2YgT2JqZWN0LmVudHJpZXModmFsdWUpKSB7XG4gICAgICAgIGNvbXBpbGVkW2Ake2NvbHVtbn0gJHtvcH1gXSA9IG9wZXJhbmQ7XG4gICAgICB9XG4gICAgfSBlbHNlIHtcbiAgICAgIGNvbXBpbGVkW2NvbHVtbl0gPSB2YWx1ZTtcbiAgICB9XG4gIH1cbiAgcmV0dXJuIEpTT04uc3RyaW5naWZ5KGNvbXBpbGVkKTtcbn1cblxuLyoqIEEgbWluaW1hbCBzdHJ1Y3R1cmFsIHZpZXcgb2YgdGhlIFZlY3RvciBTZWFyY2ggcXVlcnkgcmVzcG9uc2UuICovXG5leHBvcnQgaW50ZXJmYWNlIFF1ZXJ5UmVzcG9uc2VMaWtlIHtcbiAgbWFuaWZlc3Q/OiB7IGNvbHVtbnM/OiBBcnJheTx7IG5hbWU/OiBzdHJpbmcgfT4gfTtcbiAgcmVzdWx0PzogeyBkYXRhX2FycmF5PzogQXJyYXk8QXJyYXk8dW5rbm93bj4+IH07XG4gIG5leHRfcGFnZV90b2tlbj86IHN0cmluZztcbn1cblxuLyoqXG4gKiBVbnBhY2sgYSBjb2x1bW5hciBxdWVyeSByZXNwb25zZSBpbnRvIHtAbGluayBTZWFyY2hIaXR9cy4gVGhlIG1hbmlmZXN0IG5hbWVzXG4gKiB0aGUgY29sdW1ucyBpbiBvcmRlcjsgZWFjaCByb3cgaXMgYSBwb3NpdGlvbmFsIGFycmF5LiBUaGUgc2NvcmUgY29tZXMgZnJvbVxuICogdGhlIHJlc2VydmVkIHNjb3JlIGNvbHVtbiBhbmQgdGhlIGlkIGZyb20gYHByaW1hcnlLZXlgIChmYWxsaW5nIGJhY2sgdG8gdGhlXG4gKiBmaXJzdCBjb2x1bW4gd2hlbiB0aGUga2V5IGlzIHVua25vd24pLiBUaGUgc2NvcmUgY29sdW1uIGlzIHN0cmlwcGVkIGZyb21cbiAqIGBmaWVsZHNgIHNvIGEgaGl0J3MgZmllbGRzIGFyZSBqdXN0IHRoZSBkb2N1bWVudC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHRvSGl0cyhcbiAgcmVzcG9uc2U6IFF1ZXJ5UmVzcG9uc2VMaWtlLFxuICBwcmltYXJ5S2V5OiBzdHJpbmcgfCB1bmRlZmluZWQsXG4gIGluZGV4TmFtZT86IHN0cmluZyxcbik6IFNlYXJjaEhpdFtdIHtcbiAgY29uc3QgY29sdW1ucyA9IChyZXNwb25zZS5tYW5pZmVzdD8uY29sdW1ucyA/PyBbXSkubWFwKChjKSA9PiBjLm5hbWUgPz8gXCJcIik7XG4gIGNvbnN0IHJvd3MgPSByZXNwb25zZS5yZXN1bHQ/LmRhdGFfYXJyYXkgPz8gW107XG4gIGNvbnN0IHNjb3JlSWR4ID0gY29sdW1ucy5pbmRleE9mKFNDT1JFX0NPTFVNTik7XG4gIGNvbnN0IGtleUlkeCA9IHByaW1hcnlLZXkgPyBjb2x1bW5zLmluZGV4T2YocHJpbWFyeUtleSkgOiAtMTtcbiAgcmV0dXJuIHJvd3MubWFwKChyb3csIHJvd0luZGV4KSA9PiB7XG4gICAgY29uc3QgZmllbGRzOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPiA9IHt9O1xuICAgIGNvbHVtbnMuZm9yRWFjaCgobmFtZSwgaSkgPT4ge1xuICAgICAgaWYgKGkgPT09IHNjb3JlSWR4IHx8ICFuYW1lKSByZXR1cm47XG4gICAgICBmaWVsZHNbbmFtZV0gPSByb3dbaV07XG4gICAgfSk7XG4gICAgY29uc3Qgc2NvcmVSYXcgPSBzY29yZUlkeCA+PSAwID8gTnVtYmVyKHJvd1tzY29yZUlkeF0pIDogTmFOO1xuICAgIGNvbnN0IGlkUmF3ID1cbiAgICAgIGtleUlkeCA+PSAwID8gcm93W2tleUlkeF0gOiBwcmltYXJ5S2V5ID8gZmllbGRzW3ByaW1hcnlLZXldIDogKHJvd1swXSA/PyByb3dJbmRleCk7XG4gICAgcmV0dXJuIHtcbiAgICAgIGlkOiBTdHJpbmcoaWRSYXcgPz8gcm93SW5kZXgpLFxuICAgICAgc2NvcmU6IE51bWJlci5pc0Zpbml0ZShzY29yZVJhdykgPyBzY29yZVJhdyA6IDAsXG4gICAgICBmaWVsZHMsXG4gICAgICAuLi4oaW5kZXhOYW1lID8geyBpbmRleDogaW5kZXhOYW1lIH0gOiB7fSksXG4gICAgfTtcbiAgfSk7XG59XG5cbi8qKlxuICogVGhlIGNvbHVtbnMgdG8gcmVxdWVzdCBmcm9tIGFuIGluZGV4IGZvciBhIHNlYXJjaC4gV2hlbiBuZWl0aGVyIHRoZSByZXF1ZXN0XG4gKiBub3IgdGhlIGluZGV4IGNvbmZpZyBuYW1lcyBjb2x1bW5zLCB0aGUgc2NvcmUgY29sdW1uIGFsb25lIGlzIHJlcXVlc3RlZCBhbmRcbiAqIHRoZSBwcmltYXJ5IGtleSBpcyBhcHBlbmRlZCBzbyBhIGhpdCBhbHdheXMgaGFzIGFuIGlkLiBDYWxsZXJzIHRoYXQgd2FudCB0aGVcbiAqIHdob2xlIGRvY3VtZW50IHNob3VsZCBwYXNzIHRoZSBpbmRleCdzIG93biBjb2x1bW4gbGlzdC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHRvUmVxdWVzdENvbHVtbnMoXG4gIHJlcXVlc3RlZDogcmVhZG9ubHkgc3RyaW5nW10gfCB1bmRlZmluZWQsXG4gIGZhbGxiYWNrOiByZWFkb25seSBzdHJpbmdbXSB8IHVuZGVmaW5lZCxcbiAgcHJpbWFyeUtleTogc3RyaW5nIHwgdW5kZWZpbmVkLFxuKTogc3RyaW5nW10ge1xuICBjb25zdCBiYXNlID0gcmVxdWVzdGVkICYmIHJlcXVlc3RlZC5sZW5ndGggPiAwID8gcmVxdWVzdGVkIDogKGZhbGxiYWNrID8/IFtdKTtcbiAgY29uc3QgY29sdW1ucyA9IG5ldyBTZXQ8c3RyaW5nPihiYXNlKTtcbiAgaWYgKHByaW1hcnlLZXkpIGNvbHVtbnMuYWRkKHByaW1hcnlLZXkpO1xuICBpZiAoY29sdW1ucy5zaXplID09PSAwICYmIHByaW1hcnlLZXkpIGNvbHVtbnMuYWRkKHByaW1hcnlLZXkpO1xuICByZXR1cm4gWy4uLmNvbHVtbnNdO1xufVxuXG4vKipcbiAqIFBhcnNlIGEgSlNPTiBkb2N1bWVudCBwYXlsb2FkIHRoZSBtb2RlbCAvIGEgcm91dGUgc3VwcGxpZWQgZm9yIGEgd3JpdGUuXG4gKiBBY2NlcHRzIGFuIGFscmVhZHktcGFyc2VkIGFycmF5L29iamVjdCBvciBhIEpTT04gc3RyaW5nLCBhbmQgYWx3YXlzIHJldHVybnNcbiAqIGFuIGFycmF5IHNvIGEgc2luZ2xlIGRvY3VtZW50IGFuZCBhIGJhdGNoIGFyZSBoYW5kbGVkIHRoZSBzYW1lIHdheS4gVGhyb3dzIGFcbiAqIHtAbGluayBWYWxpZGF0aW9uRXJyb3J9IG9uIHVucGFyc2VhYmxlIGlucHV0IHNvIHRoZSBjYWxsZXIgY2FuIHN1cmZhY2UgaXQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiB0b0RvY3VtZW50QXJyYXkoaW5wdXQ6IHVua25vd24pOiBBcnJheTxSZWNvcmQ8c3RyaW5nLCB1bmtub3duPj4ge1xuICBjb25zdCB2YWx1ZSA9IHR5cGVvZiBpbnB1dCA9PT0gXCJzdHJpbmdcIiA/IGpzb24ucGFyc2UoaW5wdXQsIHVuZGVmaW5lZCkgOiBpbnB1dDtcbiAgaWYgKHZhbHVlID09PSB1bmRlZmluZWQpIHtcbiAgICB0aHJvdyBuZXcgVmFsaWRhdGlvbkVycm9yKFwiZG9jdW1lbnRzIG11c3QgYmUgYSBKU09OIG9iamVjdCwgYXJyYXksIG9yIHN0cmluZ1wiKTtcbiAgfVxuICBjb25zdCBsaXN0ID0gQXJyYXkuaXNBcnJheSh2YWx1ZSkgPyB2YWx1ZSA6IFt2YWx1ZV07XG4gIHJldHVybiBsaXN0LmZpbHRlcihvYmplY3QuaXNSZWNvcmQpO1xufVxuIl19
18
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicXVlcnkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvcXVlcnkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEseUVBQXlFO0FBRXpFLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUNyRCxPQUFPLEVBQUUsSUFBSSxFQUFFLE1BQU0sRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBRXREOzs7OztHQUtHO0FBQ0gsTUFBTSxVQUFVLGVBQWUsQ0FBQyxLQUFjO0lBQzVDLE1BQU0sS0FBSyxHQUFHLE9BQU8sS0FBSyxLQUFLLFFBQVEsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxLQUFLLEVBQUUsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQztJQUMvRSxJQUFJLEtBQUssS0FBSyxTQUFTLEVBQUUsQ0FBQztRQUN4QixNQUFNLElBQUksZUFBZSxDQUFDLG1EQUFtRCxDQUFDLENBQUM7SUFDakYsQ0FBQztJQUNELE1BQU0sSUFBSSxHQUFHLEtBQUssQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUNwRCxPQUFPLElBQUksQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxDQUFDO0FBQ3RDLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKiogUmVxdWVzdC1zaGFwZSBoZWxwZXJzIGZvciBzZWFyY2ggZXh0ZW5zaW9uIHdyaXRlIHJvdXRlcyBhbmQgdG9vbHMuICovXG5cbmltcG9ydCB7IFZhbGlkYXRpb25FcnJvciB9IGZyb20gXCJAZGF0YWJyaWNrcy9hcHBraXRcIjtcbmltcG9ydCB7IGpzb24sIG9iamVjdCB9IGZyb20gXCJAZGJ4LXRvb2xzL3NoYXJlZC1jb3JlXCI7XG5cbi8qKlxuICogUGFyc2UgYSBKU09OIGRvY3VtZW50IHBheWxvYWQgdGhlIG1vZGVsIC8gYSByb3V0ZSBzdXBwbGllZCBmb3IgYSB3cml0ZS5cbiAqIEFjY2VwdHMgYW4gYWxyZWFkeS1wYXJzZWQgYXJyYXkvb2JqZWN0IG9yIGEgSlNPTiBzdHJpbmcsIGFuZCBhbHdheXMgcmV0dXJuc1xuICogYW4gYXJyYXkgc28gYSBzaW5nbGUgZG9jdW1lbnQgYW5kIGEgYmF0Y2ggYXJlIGhhbmRsZWQgdGhlIHNhbWUgd2F5LiBUaHJvd3MgYVxuICoge0BsaW5rIFZhbGlkYXRpb25FcnJvcn0gb24gdW5wYXJzZWFibGUgaW5wdXQgc28gdGhlIGNhbGxlciBjYW4gc3VyZmFjZSBpdC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHRvRG9jdW1lbnRBcnJheShpbnB1dDogdW5rbm93bik6IEFycmF5PFJlY29yZDxzdHJpbmcsIHVua25vd24+PiB7XG4gIGNvbnN0IHZhbHVlID0gdHlwZW9mIGlucHV0ID09PSBcInN0cmluZ1wiID8ganNvbi5wYXJzZShpbnB1dCwgdW5kZWZpbmVkKSA6IGlucHV0O1xuICBpZiAodmFsdWUgPT09IHVuZGVmaW5lZCkge1xuICAgIHRocm93IG5ldyBWYWxpZGF0aW9uRXJyb3IoXCJkb2N1bWVudHMgbXVzdCBiZSBhIEpTT04gb2JqZWN0LCBhcnJheSwgb3Igc3RyaW5nXCIpO1xuICB9XG4gIGNvbnN0IGxpc3QgPSBBcnJheS5pc0FycmF5KHZhbHVlKSA/IHZhbHVlIDogW3ZhbHVlXTtcbiAgcmV0dXJuIGxpc3QuZmlsdGVyKG9iamVjdC5pc1JlY29yZCk7XG59XG4iXX0=
@@ -7,25 +7,18 @@
7
7
  *
8
8
  * @module
9
9
  */
10
- import { SearchClient } from "./client.ts";
10
+ import { SearchClient, type SearchReadBackend } from "./client.ts";
11
11
  import { type SearchPluginConfig, type ResolvedSearchConfig } from "./config.ts";
12
- import { LakebaseSearchBackend } from "./lakebase.ts";
13
- /**
14
- * How the runtime is built. `lakebase` (when present) is the Postgres full-text
15
- * FALLBACK backend the plugin wires up when no Vector Search endpoint is
16
- * configured but the AppKit `lakebase` plugin is registered. Every read/write
17
- * returns the same shape either way.
18
- */
12
+ /** Configuration and provider used to build the shared extension runtime. */
19
13
  export interface SearchRuntimeOptions {
20
14
  config?: SearchPluginConfig;
21
- lakebase?: LakebaseSearchBackend;
15
+ readBackend?: SearchReadBackend;
22
16
  }
23
17
  /** The shared resolved config plus the client reads run through. */
24
18
  export interface SearchRuntime {
25
19
  config: ResolvedSearchConfig;
26
20
  client: SearchClient;
27
- /** The Lakebase fallback backend, when the runtime is backed by Postgres. */
28
- lakebase?: LakebaseSearchBackend;
21
+ readBackend?: SearchReadBackend;
29
22
  }
30
23
  /**
31
24
  * Return the shared runtime, building it on first use from the supplied config
@@ -19,20 +19,16 @@ let runtime;
19
19
  export function getSearchRuntime(options) {
20
20
  if (!runtime) {
21
21
  const config = resolveSearchConfig(options?.config);
22
- const lakebase = options?.lakebase;
23
22
  runtime = {
24
23
  config,
25
- client: createSearchClient(config, undefined, lakebase),
26
- ...(lakebase ? { lakebase } : {}),
24
+ client: createSearchClient(config, undefined, options?.readBackend),
25
+ ...(options?.readBackend ? { readBackend: options.readBackend } : {}),
27
26
  };
28
27
  }
29
28
  return runtime;
30
29
  }
31
30
  /** Drop the memoized runtime so the next {@link getSearchRuntime} rebuilds it. */
32
31
  export function resetSearchRuntime() {
33
- const backend = runtime?.lakebase;
34
32
  runtime = undefined;
35
- if (backend)
36
- void backend.close();
37
33
  }
38
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicnVudGltZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9ydW50aW1lLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7OztHQVFHO0FBRUgsT0FBTyxFQUFFLGtCQUFrQixFQUFnQixNQUFNLGFBQWEsQ0FBQztBQUMvRCxPQUFPLEVBQ0wsbUJBQW1CLEdBR3BCLE1BQU0sYUFBYSxDQUFDO0FBc0JyQixJQUFJLE9BQWtDLENBQUM7QUFFdkM7Ozs7O0dBS0c7QUFDSCxNQUFNLFVBQVUsZ0JBQWdCLENBQUMsT0FBOEI7SUFDN0QsSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFDO1FBQ2IsTUFBTSxNQUFNLEdBQUcsbUJBQW1CLENBQUMsT0FBTyxFQUFFLE1BQU0sQ0FBQyxDQUFDO1FBQ3BELE1BQU0sUUFBUSxHQUFHLE9BQU8sRUFBRSxRQUFRLENBQUM7UUFDbkMsT0FBTyxHQUFHO1lBQ1IsTUFBTTtZQUNOLE1BQU0sRUFBRSxrQkFBa0IsQ0FBQyxNQUFNLEVBQUUsU0FBUyxFQUFFLFFBQVEsQ0FBQztZQUN2RCxHQUFHLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxFQUFFLFFBQVEsRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7U0FDbEMsQ0FBQztJQUNKLENBQUM7SUFDRCxPQUFPLE9BQU8sQ0FBQztBQUNqQixDQUFDO0FBRUQsa0ZBQWtGO0FBQ2xGLE1BQU0sVUFBVSxrQkFBa0I7SUFDaEMsTUFBTSxPQUFPLEdBQUcsT0FBTyxFQUFFLFFBQVEsQ0FBQztJQUNsQyxPQUFPLEdBQUcsU0FBUyxDQUFDO0lBQ3BCLElBQUksT0FBTztRQUFFLEtBQUssT0FBTyxDQUFDLEtBQUssRUFBRSxDQUFDO0FBQ3BDLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIFRoZSBzaGFyZWQgQUkgU2VhcmNoIHJ1bnRpbWU6IHRoZSByZXNvbHZlZCBjb25maWcgcGx1cyBhIHNpbmdsZVxuICoge0BsaW5rIFNlYXJjaENsaWVudH0gaW5zdGFuY2UgdGhlIHBsdWdpbiwgdGhlIE1hc3RyYSB0b29scywgYW5kIHRoZSByb3V0ZXNcbiAqIGFsbCByZWFkLiBNaXJyb3JzIHRoZSB3ZWItc2VhcmNoIC8gZW1haWwgcnVudGltZSBwYXR0ZXJuIC0gdGhlIHBsdWdpbiBwcmltZXNcbiAqIGl0IGZyb20gaXRzIGNvbmZpZyBhdCBzZXR1cCwgYW5kIGV2ZXJ5dGhpbmcgZWxzZSByZWFkcyB0aGUgc2FtZSBpbnN0YW5jZSBzb1xuICogYSB0b29sIGludm9rZWQgb3V0c2lkZSB0aGUgYWdlbnQgc3RpbGwgc2VlcyB0aGUgZGVwbG95bWVudCdzIGNvbmZpZy5cbiAqXG4gKiBAbW9kdWxlXG4gKi9cblxuaW1wb3J0IHsgY3JlYXRlU2VhcmNoQ2xpZW50LCBTZWFyY2hDbGllbnQgfSBmcm9tIFwiLi9jbGllbnQudHNcIjtcbmltcG9ydCB7XG4gIHJlc29sdmVTZWFyY2hDb25maWcsXG4gIHR5cGUgU2VhcmNoUGx1Z2luQ29uZmlnLFxuICB0eXBlIFJlc29sdmVkU2VhcmNoQ29uZmlnLFxufSBmcm9tIFwiLi9jb25maWcudHNcIjtcbmltcG9ydCB7IExha2ViYXNlU2VhcmNoQmFja2VuZCB9IGZyb20gXCIuL2xha2ViYXNlLnRzXCI7XG5cbi8qKlxuICogSG93IHRoZSBydW50aW1lIGlzIGJ1aWx0LiBgbGFrZWJhc2VgICh3aGVuIHByZXNlbnQpIGlzIHRoZSBQb3N0Z3JlcyBmdWxsLXRleHRcbiAqIEZBTExCQUNLIGJhY2tlbmQgdGhlIHBsdWdpbiB3aXJlcyB1cCB3aGVuIG5vIFZlY3RvciBTZWFyY2ggZW5kcG9pbnQgaXNcbiAqIGNvbmZpZ3VyZWQgYnV0IHRoZSBBcHBLaXQgYGxha2ViYXNlYCBwbHVnaW4gaXMgcmVnaXN0ZXJlZC4gRXZlcnkgcmVhZC93cml0ZVxuICogcmV0dXJucyB0aGUgc2FtZSBzaGFwZSBlaXRoZXIgd2F5LlxuICovXG5leHBvcnQgaW50ZXJmYWNlIFNlYXJjaFJ1bnRpbWVPcHRpb25zIHtcbiAgY29uZmlnPzogU2VhcmNoUGx1Z2luQ29uZmlnO1xuICBsYWtlYmFzZT86IExha2ViYXNlU2VhcmNoQmFja2VuZDtcbn1cblxuLyoqIFRoZSBzaGFyZWQgcmVzb2x2ZWQgY29uZmlnIHBsdXMgdGhlIGNsaWVudCByZWFkcyBydW4gdGhyb3VnaC4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgU2VhcmNoUnVudGltZSB7XG4gIGNvbmZpZzogUmVzb2x2ZWRTZWFyY2hDb25maWc7XG4gIGNsaWVudDogU2VhcmNoQ2xpZW50O1xuICAvKiogVGhlIExha2ViYXNlIGZhbGxiYWNrIGJhY2tlbmQsIHdoZW4gdGhlIHJ1bnRpbWUgaXMgYmFja2VkIGJ5IFBvc3RncmVzLiAqL1xuICBsYWtlYmFzZT86IExha2ViYXNlU2VhcmNoQmFja2VuZDtcbn1cblxubGV0IHJ1bnRpbWU6IFNlYXJjaFJ1bnRpbWUgfCB1bmRlZmluZWQ7XG5cbi8qKlxuICogUmV0dXJuIHRoZSBzaGFyZWQgcnVudGltZSwgYnVpbGRpbmcgaXQgb24gZmlyc3QgdXNlIGZyb20gdGhlIHN1cHBsaWVkIGNvbmZpZ1xuICogbGF5ZXJlZCBvdmVyIGVudmlyb25tZW50IGRlZmF1bHRzLiBPdmVycmlkZXMgYXJlIG9ubHkgcmVhZCB3aGVuIHRoZSBydW50aW1lXG4gKiBpcyBmaXJzdCBjcmVhdGVkLCBzbyBwcmltZSBpdCBmcm9tIHRoZSBwbHVnaW4ncyBjb25maWcgYXQgc2V0dXA7IHN1YnNlcXVlbnRcbiAqIGNhbGxzIHBhc3Mgbm90aGluZyBhbmQgZ2V0IHRoZSBzYW1lIGluc3RhbmNlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gZ2V0U2VhcmNoUnVudGltZShvcHRpb25zPzogU2VhcmNoUnVudGltZU9wdGlvbnMpOiBTZWFyY2hSdW50aW1lIHtcbiAgaWYgKCFydW50aW1lKSB7XG4gICAgY29uc3QgY29uZmlnID0gcmVzb2x2ZVNlYXJjaENvbmZpZyhvcHRpb25zPy5jb25maWcpO1xuICAgIGNvbnN0IGxha2ViYXNlID0gb3B0aW9ucz8ubGFrZWJhc2U7XG4gICAgcnVudGltZSA9IHtcbiAgICAgIGNvbmZpZyxcbiAgICAgIGNsaWVudDogY3JlYXRlU2VhcmNoQ2xpZW50KGNvbmZpZywgdW5kZWZpbmVkLCBsYWtlYmFzZSksXG4gICAgICAuLi4obGFrZWJhc2UgPyB7IGxha2ViYXNlIH0gOiB7fSksXG4gICAgfTtcbiAgfVxuICByZXR1cm4gcnVudGltZTtcbn1cblxuLyoqIERyb3AgdGhlIG1lbW9pemVkIHJ1bnRpbWUgc28gdGhlIG5leHQge0BsaW5rIGdldFNlYXJjaFJ1bnRpbWV9IHJlYnVpbGRzIGl0LiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJlc2V0U2VhcmNoUnVudGltZSgpOiB2b2lkIHtcbiAgY29uc3QgYmFja2VuZCA9IHJ1bnRpbWU/Lmxha2ViYXNlO1xuICBydW50aW1lID0gdW5kZWZpbmVkO1xuICBpZiAoYmFja2VuZCkgdm9pZCBiYWNrZW5kLmNsb3NlKCk7XG59XG4iXX0=
34
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicnVudGltZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9ydW50aW1lLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7OztHQVFHO0FBRUgsT0FBTyxFQUFFLGtCQUFrQixFQUF3QyxNQUFNLGFBQWEsQ0FBQztBQUN2RixPQUFPLEVBQ0wsbUJBQW1CLEdBR3BCLE1BQU0sYUFBYSxDQUFDO0FBZXJCLElBQUksT0FBa0MsQ0FBQztBQUV2Qzs7Ozs7R0FLRztBQUNILE1BQU0sVUFBVSxnQkFBZ0IsQ0FBQyxPQUE4QjtJQUM3RCxJQUFJLENBQUMsT0FBTyxFQUFFLENBQUM7UUFDYixNQUFNLE1BQU0sR0FBRyxtQkFBbUIsQ0FBQyxPQUFPLEVBQUUsTUFBTSxDQUFDLENBQUM7UUFDcEQsT0FBTyxHQUFHO1lBQ1IsTUFBTTtZQUNOLE1BQU0sRUFBRSxrQkFBa0IsQ0FBQyxNQUFNLEVBQUUsU0FBUyxFQUFFLE9BQU8sRUFBRSxXQUFXLENBQUM7WUFDbkUsR0FBRyxDQUFDLE9BQU8sRUFBRSxXQUFXLENBQUMsQ0FBQyxDQUFDLEVBQUUsV0FBVyxFQUFFLE9BQU8sQ0FBQyxXQUFXLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO1NBQ3RFLENBQUM7SUFDSixDQUFDO0lBQ0QsT0FBTyxPQUFPLENBQUM7QUFDakIsQ0FBQztBQUVELGtGQUFrRjtBQUNsRixNQUFNLFVBQVUsa0JBQWtCO0lBQ2hDLE9BQU8sR0FBRyxTQUFTLENBQUM7QUFDdEIsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogVGhlIHNoYXJlZCBBSSBTZWFyY2ggcnVudGltZTogdGhlIHJlc29sdmVkIGNvbmZpZyBwbHVzIGEgc2luZ2xlXG4gKiB7QGxpbmsgU2VhcmNoQ2xpZW50fSBpbnN0YW5jZSB0aGUgcGx1Z2luLCB0aGUgTWFzdHJhIHRvb2xzLCBhbmQgdGhlIHJvdXRlc1xuICogYWxsIHJlYWQuIE1pcnJvcnMgdGhlIHdlYi1zZWFyY2ggLyBlbWFpbCBydW50aW1lIHBhdHRlcm4gLSB0aGUgcGx1Z2luIHByaW1lc1xuICogaXQgZnJvbSBpdHMgY29uZmlnIGF0IHNldHVwLCBhbmQgZXZlcnl0aGluZyBlbHNlIHJlYWRzIHRoZSBzYW1lIGluc3RhbmNlIHNvXG4gKiBhIHRvb2wgaW52b2tlZCBvdXRzaWRlIHRoZSBhZ2VudCBzdGlsbCBzZWVzIHRoZSBkZXBsb3ltZW50J3MgY29uZmlnLlxuICpcbiAqIEBtb2R1bGVcbiAqL1xuXG5pbXBvcnQgeyBjcmVhdGVTZWFyY2hDbGllbnQsIFNlYXJjaENsaWVudCwgdHlwZSBTZWFyY2hSZWFkQmFja2VuZCB9IGZyb20gXCIuL2NsaWVudC50c1wiO1xuaW1wb3J0IHtcbiAgcmVzb2x2ZVNlYXJjaENvbmZpZyxcbiAgdHlwZSBTZWFyY2hQbHVnaW5Db25maWcsXG4gIHR5cGUgUmVzb2x2ZWRTZWFyY2hDb25maWcsXG59IGZyb20gXCIuL2NvbmZpZy50c1wiO1xuXG4vKiogQ29uZmlndXJhdGlvbiBhbmQgcHJvdmlkZXIgdXNlZCB0byBidWlsZCB0aGUgc2hhcmVkIGV4dGVuc2lvbiBydW50aW1lLiAqL1xuZXhwb3J0IGludGVyZmFjZSBTZWFyY2hSdW50aW1lT3B0aW9ucyB7XG4gIGNvbmZpZz86IFNlYXJjaFBsdWdpbkNvbmZpZztcbiAgcmVhZEJhY2tlbmQ/OiBTZWFyY2hSZWFkQmFja2VuZDtcbn1cblxuLyoqIFRoZSBzaGFyZWQgcmVzb2x2ZWQgY29uZmlnIHBsdXMgdGhlIGNsaWVudCByZWFkcyBydW4gdGhyb3VnaC4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgU2VhcmNoUnVudGltZSB7XG4gIGNvbmZpZzogUmVzb2x2ZWRTZWFyY2hDb25maWc7XG4gIGNsaWVudDogU2VhcmNoQ2xpZW50O1xuICByZWFkQmFja2VuZD86IFNlYXJjaFJlYWRCYWNrZW5kO1xufVxuXG5sZXQgcnVudGltZTogU2VhcmNoUnVudGltZSB8IHVuZGVmaW5lZDtcblxuLyoqXG4gKiBSZXR1cm4gdGhlIHNoYXJlZCBydW50aW1lLCBidWlsZGluZyBpdCBvbiBmaXJzdCB1c2UgZnJvbSB0aGUgc3VwcGxpZWQgY29uZmlnXG4gKiBsYXllcmVkIG92ZXIgZW52aXJvbm1lbnQgZGVmYXVsdHMuIE92ZXJyaWRlcyBhcmUgb25seSByZWFkIHdoZW4gdGhlIHJ1bnRpbWVcbiAqIGlzIGZpcnN0IGNyZWF0ZWQsIHNvIHByaW1lIGl0IGZyb20gdGhlIHBsdWdpbidzIGNvbmZpZyBhdCBzZXR1cDsgc3Vic2VxdWVudFxuICogY2FsbHMgcGFzcyBub3RoaW5nIGFuZCBnZXQgdGhlIHNhbWUgaW5zdGFuY2UuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBnZXRTZWFyY2hSdW50aW1lKG9wdGlvbnM/OiBTZWFyY2hSdW50aW1lT3B0aW9ucyk6IFNlYXJjaFJ1bnRpbWUge1xuICBpZiAoIXJ1bnRpbWUpIHtcbiAgICBjb25zdCBjb25maWcgPSByZXNvbHZlU2VhcmNoQ29uZmlnKG9wdGlvbnM/LmNvbmZpZyk7XG4gICAgcnVudGltZSA9IHtcbiAgICAgIGNvbmZpZyxcbiAgICAgIGNsaWVudDogY3JlYXRlU2VhcmNoQ2xpZW50KGNvbmZpZywgdW5kZWZpbmVkLCBvcHRpb25zPy5yZWFkQmFja2VuZCksXG4gICAgICAuLi4ob3B0aW9ucz8ucmVhZEJhY2tlbmQgPyB7IHJlYWRCYWNrZW5kOiBvcHRpb25zLnJlYWRCYWNrZW5kIH0gOiB7fSksXG4gICAgfTtcbiAgfVxuICByZXR1cm4gcnVudGltZTtcbn1cblxuLyoqIERyb3AgdGhlIG1lbW9pemVkIHJ1bnRpbWUgc28gdGhlIG5leHQge0BsaW5rIGdldFNlYXJjaFJ1bnRpbWV9IHJlYnVpbGRzIGl0LiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJlc2V0U2VhcmNoUnVudGltZSgpOiB2b2lkIHtcbiAgcnVudGltZSA9IHVuZGVmaW5lZDtcbn1cbiJdfQ==
@@ -27,7 +27,7 @@ export declare const searchToolSchema: import("zod").ZodObject<{
27
27
  keyword: "keyword";
28
28
  }>>;
29
29
  columns: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
30
- filter: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodUnknown>>;
30
+ filter: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodUnion<readonly [import("zod").ZodString, import("zod").ZodNumber, import("zod").ZodBoolean, import("zod").ZodArray<import("zod").ZodUnion<readonly [import("zod").ZodString, import("zod").ZodNumber]>>]>>>;
31
31
  scoreThreshold: import("zod").ZodOptional<import("zod").ZodNumber>;
32
32
  }, import("zod/v4/core").$strip>;
33
33
  /** Schema for the `universal_search` tool input. */
package/lib/src/tool.d.ts CHANGED
@@ -39,7 +39,7 @@ export declare function searchTool(options?: SearchToolOptions): import("@mastra
39
39
  limit?: number | undefined;
40
40
  mode?: "hybrid" | "vector" | "keyword" | undefined;
41
41
  columns?: string[] | undefined;
42
- filter?: Record<string, unknown> | undefined;
42
+ filter?: Record<string, string | number | boolean | (string | number)[]> | undefined;
43
43
  scoreThreshold?: number | undefined;
44
44
  }, {
45
45
  query: string;
@@ -1 +1 @@
1
- {"root":["../src/client.ts","../src/config.ts","../src/index-tools.ts","../src/lakebase.ts","../src/plugin.ts","../src/query.ts","../src/runtime.ts","../src/schema.ts","../src/tool.ts","../index.ts"],"version":"5.9.3"}
1
+ {"root":["../src/client.ts","../src/config.ts","../src/index-tools.ts","../src/lakebase-plugin.ts","../src/lakebase.ts","../src/native.ts","../src/plugin.ts","../src/query.ts","../src/runtime.ts","../src/schema.ts","../src/tool.ts","../index.ts"],"version":"5.9.3"}
package/package.json CHANGED
@@ -26,15 +26,15 @@
26
26
  "typescript": "^5.9.3"
27
27
  },
28
28
  "dependencies": {
29
- "@databricks/appkit": "^0.43.0",
29
+ "@databricks/appkit": "^0.60.0",
30
30
  "@databricks/sdk-experimental": "^0.17.0",
31
- "@dbx-tools/appkit": "0.6.108",
32
- "@dbx-tools/core": "0.6.108",
33
- "@dbx-tools/model": "0.6.108",
34
- "@dbx-tools/shared-core": "0.6.108",
35
- "@dbx-tools/shared-model": "0.6.108",
36
- "@dbx-tools/shared-search": "0.6.108",
37
- "@mastra/core": "^1.47.0",
31
+ "@dbx-tools/appkit": "0.6.111",
32
+ "@dbx-tools/core": "0.6.111",
33
+ "@dbx-tools/model": "0.6.111",
34
+ "@dbx-tools/shared-core": "0.6.111",
35
+ "@dbx-tools/shared-model": "0.6.111",
36
+ "@dbx-tools/shared-search": "0.6.111",
37
+ "@mastra/core": "1.47.0",
38
38
  "pg": "^8.22.0",
39
39
  "zod": "4.3.6"
40
40
  },
@@ -52,7 +52,7 @@
52
52
  "./package.json": "./package.json"
53
53
  }
54
54
  },
55
- "version": "0.6.108",
55
+ "version": "0.6.111",
56
56
  "types": "./lib/index.d.ts",
57
57
  "type": "module",
58
58
  "exports": {
package/src/client.ts CHANGED
@@ -1,25 +1,16 @@
1
1
  /**
2
- * A small, Meilisearch-shaped client over Databricks AI Search (Vector
3
- * Search). The whole point of this module is ergonomics: the Databricks SDK's
4
- * `vectorSearchIndexes.queryIndex({ index_name, columns, query_text,
5
- * query_type, num_results, filters_json })` is powerful but verbose, and the
6
- * response is columnar. This client hides all of that behind two objects:
7
- *
8
- * ```ts
9
- * const client = createSearchClient();
10
- * const index = client.index("main.support.docs");
11
- * const { hits } = await index.search("reset my password", { limit: 5 });
12
- * await index.addDocuments([{ id: "42", title: "Reset", body: "..." }]);
13
- * ```
2
+ * Search extension client. Reads delegate to the registered AppKit-compatible
3
+ * `aiSearch` provider; this module owns federated fan-out and the Vector Search
4
+ * lifecycle APIs AppKit does not expose.
14
5
  *
15
6
  * `client.search(query, opts)` searches the default index; `client.index(name)`
16
7
  * returns a handle bound to one index; `client.universalSearch(query)` fans a
17
8
  * query across several indexes and merges the results (Meilisearch's federated
18
9
  * / multi-search, the "universal search" the caller asked for). Everything is
19
- * async and cancellable, resolves the OBO workspace client from the active
20
- * AppKit execution context (falling back to a service-principal client outside
21
- * a request), and returns the browser-safe shapes from
22
- * `@dbx-tools/shared-search`.
10
+ * async and cancellable and returns the browser-safe shapes from
11
+ * `@dbx-tools/shared-search`. Lifecycle methods cross AppKit's explicit legacy
12
+ * client handoff because the public facade does not yet expose index creation,
13
+ * sync, or deletion.
23
14
  *
24
15
  * Autocomplete is just a search with a small `limit` and the raw query text -
25
16
  * hybrid mode already prefix-matches, so no separate endpoint is needed; the
@@ -38,6 +29,7 @@ import type {
38
29
  SearchDocument,
39
30
  SearchHit,
40
31
  SearchMode,
32
+ SearchRequest,
41
33
  SearchResult,
42
34
  UpsertResult,
43
35
  } from "@dbx-tools/shared-search";
@@ -45,22 +37,25 @@ import {
45
37
  DEFAULT_MODE,
46
38
  DEFAULT_PAGE_SIZE,
47
39
  DEFAULT_TIMEOUT_MS,
48
- indexConfigFor,
49
40
  resolveIndexName,
50
41
  type ResolvedSearchConfig,
51
42
  } from "./config.ts";
52
- import type { LakebaseSearchBackend } from "./lakebase.ts";
53
- import {
54
- compileFilter,
55
- toHits,
56
- toQueryType,
57
- toRequestColumns,
58
- type QueryResponseLike,
59
- } from "./query.ts";
60
-
61
- type WorkspaceClientLike = appkit.WorkspaceClientLike;
43
+
44
+ type WorkspaceClientLike = ReturnType<appkit.WorkspaceClientLike["toLegacyWorkspaceClient"]>;
62
45
  const logger = log.logger("search/client");
63
46
 
47
+ /** Query backend used when AppKit owns the Vector Search execution path. */
48
+ export interface SearchReadBackend {
49
+ /** Whether Vector Search lifecycle methods are valid for this provider. */
50
+ supportsLifecycle: boolean;
51
+ search(index: string, query: string, options?: SearchOptions): Promise<SearchResult>;
52
+ addDocuments?(
53
+ index: string,
54
+ documents: SearchDocument[],
55
+ signal?: AbortSignal,
56
+ ): Promise<UpsertResult>;
57
+ }
58
+
64
59
  /** Options accepted by a single-index search. */
65
60
  export interface SearchOptions {
66
61
  /** Maximum hits to return. Defaults to the configured page size. */
@@ -69,8 +64,8 @@ export interface SearchOptions {
69
64
  mode?: SearchMode;
70
65
  /** Columns to return per hit. Defaults to the index's configured columns. */
71
66
  columns?: readonly string[];
72
- /** Attribute filters as `{ column: value }` or `{ column: { ">=": n } }`. */
73
- filter?: Record<string, unknown>;
67
+ /** AppKit AI Search scalar/array filters. */
68
+ filter?: SearchRequest["filter"];
74
69
  /** Drop hits below this score. */
75
70
  scoreThreshold?: number;
76
71
  /** External cancellation. */
@@ -247,20 +242,9 @@ export class SearchClient {
247
242
  allowWrite: false,
248
243
  },
249
244
  private readonly workspaceClientFactory: () => WorkspaceClientLike = defaultWorkspaceClient,
250
- /**
251
- * Optional Lakebase full-text FALLBACK backend. Present only when no Vector
252
- * Search endpoint is configured but a Lakebase pool is available; when set,
253
- * search / provision / write operations delegate to it and return the exact
254
- * same shapes, so nothing downstream can tell which backend answered.
255
- */
256
- private readonly lakebase?: LakebaseSearchBackend,
245
+ private readonly readBackend?: SearchReadBackend,
257
246
  ) {}
258
247
 
259
- /** True when this client is answering out of the Lakebase fallback backend. */
260
- get usesLakebase(): boolean {
261
- return this.lakebase !== undefined;
262
- }
263
-
264
248
  /** A handle bound to one index (by full UC name or configured alias). */
265
249
  index(reference: string): SearchIndex {
266
250
  const name = resolveIndexName(this.config, reference) ?? reference;
@@ -282,68 +266,16 @@ export class SearchClient {
282
266
  context: { operation: "search" },
283
267
  });
284
268
  }
285
- if (this.lakebase) {
286
- return this.lakebase.search(name, text, {
287
- limit: options.limit ?? this.config.pageSize,
288
- ...(options.scoreThreshold !== undefined ? { scoreThreshold: options.scoreThreshold } : {}),
289
- ...(options.signal ? { signal: options.signal } : {}),
290
- });
291
- }
292
- const known = indexConfigFor(this.config, name);
293
- const primaryKey = known?.primaryKey;
294
- const columns = toRequestColumns(
295
- options.columns,
296
- known?.columns ?? this.config.columns,
297
- primaryKey,
298
- );
299
- const mode = options.mode ?? this.config.mode;
300
- const limit = options.limit ?? this.config.pageSize;
301
-
302
- // Databricks only manages embeddings for Delta Sync indexes, so a query
303
- // against a DIRECT_ACCESS index must carry a query VECTOR, not text. Embed
304
- // the query client-side in that case (the index type is cached after the
305
- // first lookup so this costs one extra call per index, not per search).
306
- const directAccess = await this.isDirectAccess(name, options.signal);
307
- const queryVector = directAccess
308
- ? (await this.embed([text], this.config.embeddingModel, options.signal))[0]
309
- : undefined;
310
-
311
- const response = await this.withClient("search", options.signal, async (client, context) => {
312
- return client.vectorSearchIndexes.queryIndex(
313
- {
314
- index_name: name,
315
- columns,
316
- ...(queryVector ? { query_vector: queryVector } : { query_text: text }),
317
- query_type: toQueryType(mode),
318
- num_results: limit,
319
- ...(compileFilter(options.filter) ? { filters_json: compileFilter(options.filter) } : {}),
320
- ...(options.scoreThreshold !== undefined
321
- ? { score_threshold: options.scoreThreshold }
322
- : {}),
323
- },
324
- context,
269
+ if (!this.readBackend) {
270
+ throw new ExecutionError(
271
+ "search: no AI Search provider registered; add aiSearch() or lakebaseAiSearch()",
272
+ { context: { operation: "search" } },
325
273
  );
326
- });
327
-
328
- const hits = toHits(response as QueryResponseLike, primaryKey);
329
- return { query: text, index: name, hits, count: hits.length };
330
- }
331
-
332
- /** Cache of index-name -> is-DIRECT_ACCESS, so a search embeds its query only when needed. */
333
- private readonly directAccessCache = new Map<string, boolean>();
334
-
335
- /** Whether an index is DIRECT_ACCESS (memoized); a lookup failure assumes Delta Sync. */
336
- private async isDirectAccess(name: string, signal?: AbortSignal): Promise<boolean> {
337
- const cached = this.directAccessCache.get(name);
338
- if (cached !== undefined) return cached;
339
- try {
340
- const info = await this.getIndex(name, signal);
341
- const value = info.directAccess ?? false;
342
- this.directAccessCache.set(name, value);
343
- return value;
344
- } catch {
345
- return false;
346
274
  }
275
+ return this.readBackend.search(name, text, {
276
+ ...options,
277
+ limit: options.limit ?? this.config.pageSize,
278
+ });
347
279
  }
348
280
 
349
281
  /**
@@ -405,8 +337,18 @@ export class SearchClient {
405
337
  return name;
406
338
  }
407
339
 
340
+ /** Reject Vector Search lifecycle calls against a non-Vector provider. */
341
+ private requireLifecycle(operation: string): void {
342
+ if (this.readBackend && !this.readBackend.supportsLifecycle) {
343
+ throw new ExecutionError(`search: the registered provider does not support ${operation}`, {
344
+ context: { operation },
345
+ });
346
+ }
347
+ }
348
+
408
349
  /** Fetch an index's live definition. */
409
350
  async getIndex(reference: string, signal?: AbortSignal): Promise<IndexInfo> {
351
+ this.requireLifecycle("getIndex");
410
352
  const name = this.requireIndexName(reference, "getIndex");
411
353
  const index = await this.withClient("getIndex", signal, (client, context) =>
412
354
  client.vectorSearchIndexes.getIndex({ index_name: name }, context),
@@ -446,14 +388,10 @@ export class SearchClient {
446
388
  signal?: AbortSignal,
447
389
  ): Promise<UpsertResult> {
448
390
  const name = this.requireIndexName(reference, "addDocuments");
449
- if (this.lakebase) {
450
- return this.lakebase.addDocuments(
451
- name,
452
- documents,
453
- this.config.ensureOnSetup?.textColumn ?? "text",
454
- signal,
455
- );
391
+ if (this.readBackend?.addDocuments) {
392
+ return this.readBackend.addDocuments(name, documents, signal);
456
393
  }
394
+ this.requireLifecycle("addDocuments");
457
395
  await this.withClient("addDocuments", signal, (client, context) =>
458
396
  client.vectorSearchIndexes.upsertDataVectorIndex(
459
397
  { index_name: name, inputs_json: JSON.stringify(documents) },
@@ -469,10 +407,8 @@ export class SearchClient {
469
407
  ids: Array<string | number>,
470
408
  signal?: AbortSignal,
471
409
  ): Promise<UpsertResult> {
410
+ this.requireLifecycle("deleteDocuments");
472
411
  const name = this.requireIndexName(reference, "deleteDocuments");
473
- if (this.lakebase) {
474
- return this.lakebase.deleteDocuments(name, ids, signal);
475
- }
476
412
  await this.withClient("deleteDocuments", signal, (client, context) =>
477
413
  client.vectorSearchIndexes.deleteDataVectorIndex(
478
414
  { index_name: name, primary_keys: ids.map(String) },
@@ -493,14 +429,15 @@ export class SearchClient {
493
429
  // is used verbatim - no need to fetch and fuzzy-match the live catalogue.
494
430
  // A genuinely loose name (e.g. "gte large") still resolves against it.
495
431
  if (explicit && !/\s/.test(explicit)) return explicit;
496
- return this.withClient("resolveEmbeddingModel", signal, async (client) => {
497
- const host = (await client.config.getHost()).toString();
498
- const endpoints = await serving.listServingEndpoints(client, host);
499
- const { modelId } = modelResolve.resolveModel(endpoints, {
500
- ...(explicit ? { explicit } : { modelClass: ModelClass.Embedding }),
501
- });
502
- return string.trimToNull(modelId);
432
+ signal?.throwIfAborted();
433
+ const client = defaultAppKitWorkspaceClient();
434
+ const host = (await client.config.getHost()).toString();
435
+ const endpoints = await serving.listServingEndpoints(client, host);
436
+ signal?.throwIfAborted();
437
+ const { modelId } = modelResolve.resolveModel(endpoints, {
438
+ ...(explicit ? { explicit } : { modelClass: ModelClass.Embedding }),
503
439
  });
440
+ return string.trimToNull(modelId);
504
441
  }
505
442
 
506
443
  /**
@@ -571,6 +508,7 @@ export class SearchClient {
571
508
  * column (`embedding`). Returns the created index's {@link IndexInfo}.
572
509
  */
573
510
  async createIndex(name: string, options: CreateIndexOptions = {}): Promise<IndexInfo> {
511
+ this.requireLifecycle("createIndex");
574
512
  const endpoint = options.endpoint ?? this.config.endpoint;
575
513
  if (!endpoint) {
576
514
  throw new ExecutionError(
@@ -652,6 +590,7 @@ export class SearchClient {
652
590
  * index is present.
653
591
  */
654
592
  async ensureIndex(name: string, options: CreateIndexOptions = {}): Promise<IndexInfo> {
593
+ this.requireLifecycle("ensureIndex");
655
594
  try {
656
595
  return await this.getIndex(name, options.signal);
657
596
  } catch {
@@ -671,23 +610,7 @@ export class SearchClient {
671
610
  * rows and search-by-text works immediately.
672
611
  */
673
612
  async provision(name: string, options: ProvisionOptions = {}): Promise<IndexInfo> {
674
- if (this.lakebase) {
675
- // Lakebase fallback: no endpoint, no embeddings, no vectors - just a
676
- // Postgres full-text table. Returns an IndexInfo-shaped result so the
677
- // caller's logging + readiness handling is identical to Vector Search.
678
- const rowCount = await this.lakebase.provision(name, {
679
- textColumn: options.embeddingSourceColumn ?? "text",
680
- ...(options.seed ? { seed: options.seed } : {}),
681
- ...(options.signal ? { signal: options.signal } : {}),
682
- });
683
- return {
684
- name,
685
- primaryKey: options.primaryKey ?? "id",
686
- columns: [],
687
- ready: true,
688
- rowCount,
689
- };
690
- }
613
+ this.requireLifecycle("provision");
691
614
  const wait = options.wait ?? true;
692
615
  const timeoutMs = options.timeoutMs ?? 20 * 60 * 1000;
693
616
  const endpoint = options.endpoint ?? this.config.endpoint;
@@ -742,6 +665,7 @@ export class SearchClient {
742
665
 
743
666
  /** Trigger a sync of a Delta Sync index from its source table. */
744
667
  async syncIndex(reference: string, signal?: AbortSignal): Promise<void> {
668
+ this.requireLifecycle("syncIndex");
745
669
  const name = this.requireIndexName(reference, "syncIndex");
746
670
  await this.withClient("syncIndex", signal, (client, context) =>
747
671
  client.vectorSearchIndexes.syncIndex({ index_name: name }, context),
@@ -751,6 +675,7 @@ export class SearchClient {
751
675
 
752
676
  /** Delete an index. */
753
677
  async deleteIndex(reference: string, signal?: AbortSignal): Promise<void> {
678
+ this.requireLifecycle("deleteIndex");
754
679
  const name = this.requireIndexName(reference, "deleteIndex");
755
680
  await this.withClient("deleteIndex", signal, (client, context) =>
756
681
  client.vectorSearchIndexes.deleteIndex({ index_name: name }, context),
@@ -760,6 +685,7 @@ export class SearchClient {
760
685
 
761
686
  /** List the indexes hosted on a Vector Search endpoint (name + type only). */
762
687
  async listIndexes(endpoint?: string, signal?: AbortSignal): Promise<string[]> {
688
+ this.requireLifecycle("listIndexes");
763
689
  const endpointName = endpoint ?? this.config.endpoint;
764
690
  if (!endpointName) {
765
691
  throw new ExecutionError("search: no endpoint configured to list indexes", {
@@ -783,6 +709,7 @@ export class SearchClient {
783
709
  * does not. Optionally wait for it to come online. Idempotent.
784
710
  */
785
711
  async ensureEndpoint(name?: string, options: EnsureEndpointOptions = {}): Promise<void> {
712
+ this.requireLifecycle("ensureEndpoint");
786
713
  const endpoint = name ?? this.config.endpoint;
787
714
  if (!endpoint) {
788
715
  throw new ExecutionError("search: no endpoint name to ensure", {
@@ -832,6 +759,11 @@ export class SearchClient {
832
759
 
833
760
  /** The OBO workspace client from the active context, or a service-principal client. */
834
761
  function defaultWorkspaceClient(): WorkspaceClientLike {
762
+ return defaultAppKitWorkspaceClient().toLegacyWorkspaceClient();
763
+ }
764
+
765
+ /** AppKit workspace-client facade from the active request or service context. */
766
+ function defaultAppKitWorkspaceClient(): appkit.WorkspaceClientLike {
835
767
  const ctx = appkit.tryGetExecutionContext();
836
768
  if (ctx?.client) return ctx.client;
837
769
  // Outside a request scope (a script, a test): a fresh env-auth client.
@@ -842,7 +774,7 @@ function defaultWorkspaceClient(): WorkspaceClientLike {
842
774
  export function createSearchClient(
843
775
  config?: ResolvedSearchConfig,
844
776
  workspaceClientFactory?: () => WorkspaceClientLike,
845
- lakebase?: LakebaseSearchBackend,
777
+ readBackend?: SearchReadBackend,
846
778
  ): SearchClient {
847
- return new SearchClient(config, workspaceClientFactory, lakebase);
779
+ return new SearchClient(config, workspaceClientFactory, readBackend);
848
780
  }