@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.
- package/README.md +297 -0
- package/index.ts +28 -0
- package/lib/index.d.ts +24 -0
- package/lib/index.js +22 -0
- package/lib/src/client.d.ts +304 -0
- package/lib/src/client.js +598 -0
- package/lib/src/config.d.ts +154 -0
- package/lib/src/config.js +225 -0
- package/lib/src/index-tools.d.ts +19 -0
- package/lib/src/index-tools.js +34 -0
- package/lib/src/lakebase.d.ts +86 -0
- package/lib/src/lakebase.js +218 -0
- package/lib/src/plugin.d.ts +216 -0
- package/lib/src/plugin.js +506 -0
- package/lib/src/query.d.ts +64 -0
- package/lib/src/query.js +109 -0
- package/lib/src/runtime.d.ts +38 -0
- package/lib/src/runtime.js +38 -0
- package/lib/src/schema.d.ts +83 -0
- package/lib/src/schema.js +64 -0
- package/lib/src/tool.d.ts +116 -0
- package/lib/src/tool.js +145 -0
- package/lib/tsconfig.tsbuildinfo +1 -0
- package/package.json +65 -0
- package/src/client.ts +848 -0
- package/src/config.ts +356 -0
- package/src/index-tools.ts +40 -0
- package/src/lakebase.ts +299 -0
- package/src/plugin.ts +582 -0
- package/src/query.ts +127 -0
- package/src/runtime.ts +64 -0
- package/src/schema.ts +75 -0
- package/src/tool.ts +169 -0
|
@@ -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">;
|