firebase-dataconnect-bootstrap 1.1.0 → 1.2.1
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 +71 -32
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +131 -2
- package/dist/index.d.ts +6 -0
- package/dist/index.js +4 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +458 -34
- package/dist/prompt.d.ts +2 -0
- package/dist/prompt.js +276 -34
- package/dist/search-client.d.ts +40 -0
- package/dist/search-client.js +56 -0
- package/dist/types.d.ts +56 -0
- package/package.json +16 -3
package/dist/prompt.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
2
3
|
import { createInterface } from "node:readline/promises";
|
|
3
4
|
import { stdin as input, stdout as output } from "node:process";
|
|
5
|
+
const DEFAULT_CONFIG_FILE = ".firebase-dataconnect-bootstrap.json";
|
|
6
|
+
const DEFAULT_RETURN_FIELDS = ["title", "summary", "createdAt"];
|
|
7
|
+
const DEFAULT_DOCUMENT_PATH = "meetingSummaries/{summaryId}";
|
|
8
|
+
const NEW_COLLECTION_DOCUMENT_PATH = "newCollection/{docId}";
|
|
4
9
|
function normalizeFunctionName(name) {
|
|
5
10
|
const cleaned = name.replace(/[^A-Za-z0-9_$]/g, "_");
|
|
6
11
|
if (!cleaned) {
|
|
@@ -11,33 +16,208 @@ function normalizeFunctionName(name) {
|
|
|
11
16
|
}
|
|
12
17
|
return `fn_${cleaned}`;
|
|
13
18
|
}
|
|
14
|
-
function
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
function toCsv(fields) {
|
|
20
|
+
return fields.join(",");
|
|
21
|
+
}
|
|
22
|
+
function parseCsvFields(value) {
|
|
23
|
+
const fields = value
|
|
24
|
+
.split(",")
|
|
25
|
+
.map((field) => field.trim())
|
|
26
|
+
.filter(Boolean);
|
|
27
|
+
return fields.length > 0 ? fields : [...DEFAULT_RETURN_FIELDS];
|
|
28
|
+
}
|
|
29
|
+
function deriveCollectionPath(documentPath) {
|
|
30
|
+
const segments = documentPath.split("/").filter(Boolean);
|
|
31
|
+
if (segments.length === 0) {
|
|
32
|
+
return "meetingSummaries";
|
|
33
|
+
}
|
|
34
|
+
while (segments.length > 0 && segments[segments.length - 1].startsWith("{")) {
|
|
35
|
+
segments.pop();
|
|
36
|
+
}
|
|
37
|
+
if (segments.length === 0) {
|
|
38
|
+
return "meetingSummaries";
|
|
39
|
+
}
|
|
40
|
+
return segments.join("/");
|
|
41
|
+
}
|
|
42
|
+
function toPascalCase(input) {
|
|
43
|
+
const tokens = input
|
|
44
|
+
.split(/[^A-Za-z0-9]+/)
|
|
45
|
+
.map((token) => token.trim())
|
|
46
|
+
.filter(Boolean);
|
|
47
|
+
if (tokens.length === 0) {
|
|
48
|
+
return "Collection";
|
|
49
|
+
}
|
|
50
|
+
return tokens.map((token) => `${token[0].toUpperCase()}${token.slice(1)}`).join("");
|
|
51
|
+
}
|
|
52
|
+
function deriveWriteFunctionName(documentPath) {
|
|
53
|
+
const collection = deriveCollectionPath(documentPath);
|
|
54
|
+
return normalizeFunctionName(`on${toPascalCase(collection)}Written`);
|
|
55
|
+
}
|
|
56
|
+
function deriveSearchFunctionName(documentPath) {
|
|
57
|
+
const collection = deriveCollectionPath(documentPath);
|
|
58
|
+
return normalizeFunctionName(`search${toPascalCase(collection)}ByVector`);
|
|
59
|
+
}
|
|
60
|
+
function normalizeVectorSearch(partialVectorSearch, documentPath) {
|
|
61
|
+
const source = partialVectorSearch ?? {};
|
|
62
|
+
const collectionPath = source.collectionPath?.trim() || deriveCollectionPath(documentPath);
|
|
63
|
+
const sourceTextField = source.sourceTextField?.trim() || "summary";
|
|
64
|
+
const vectorField = source.vectorField?.trim() || "embedding";
|
|
65
|
+
const returnFields = source.returnFields?.length ? source.returnFields : [...DEFAULT_RETURN_FIELDS];
|
|
66
|
+
const functionName = normalizeFunctionName(source.functionName?.trim() || deriveSearchFunctionName(documentPath));
|
|
67
|
+
const rawTopK = typeof source.defaultTopK === "number" && Number.isFinite(source.defaultTopK)
|
|
68
|
+
? Math.floor(source.defaultTopK)
|
|
69
|
+
: 5;
|
|
70
|
+
const defaultTopK = rawTopK > 0 ? rawTopK : 5;
|
|
71
|
+
const enabled = typeof source.enabled === "boolean" ? source.enabled : false;
|
|
72
|
+
return {
|
|
73
|
+
enabled,
|
|
74
|
+
collectionPath,
|
|
75
|
+
sourceTextField,
|
|
76
|
+
vectorField,
|
|
77
|
+
returnFields,
|
|
78
|
+
functionName,
|
|
79
|
+
defaultTopK
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function normalizeTarget(partialTarget) {
|
|
83
|
+
const documentPath = partialTarget.documentPath?.trim() || DEFAULT_DOCUMENT_PATH;
|
|
84
|
+
const functionName = normalizeFunctionName(partialTarget.functionName?.trim() || deriveWriteFunctionName(documentPath));
|
|
85
|
+
const vectorSearch = normalizeVectorSearch(partialTarget.vectorSearch, documentPath);
|
|
23
86
|
return {
|
|
24
|
-
targetDir,
|
|
25
|
-
projectId,
|
|
26
|
-
region,
|
|
27
|
-
serviceId,
|
|
28
|
-
location,
|
|
29
87
|
documentPath,
|
|
30
88
|
functionName,
|
|
31
|
-
|
|
89
|
+
vectorSearch
|
|
32
90
|
};
|
|
33
91
|
}
|
|
92
|
+
function normalizeSavedConfig(raw, configFileName) {
|
|
93
|
+
const source = typeof raw === "object" && raw !== null ? raw : {};
|
|
94
|
+
const rawTargets = Array.isArray(source.targets)
|
|
95
|
+
? source.targets
|
|
96
|
+
: source.documentPath
|
|
97
|
+
? [
|
|
98
|
+
{
|
|
99
|
+
documentPath: source.documentPath,
|
|
100
|
+
functionName: source.functionName,
|
|
101
|
+
vectorSearch: source.vectorSearch
|
|
102
|
+
}
|
|
103
|
+
]
|
|
104
|
+
: [];
|
|
105
|
+
const targets = rawTargets
|
|
106
|
+
.filter((entry) => typeof entry === "object" && entry !== null)
|
|
107
|
+
.map((entry) => normalizeTarget(entry));
|
|
108
|
+
return {
|
|
109
|
+
projectId: typeof source.projectId === "string" ? source.projectId : "",
|
|
110
|
+
region: typeof source.region === "string" ? source.region : "us-central1",
|
|
111
|
+
serviceId: typeof source.serviceId === "string" ? source.serviceId : "",
|
|
112
|
+
location: typeof source.location === "string" ? source.location : "",
|
|
113
|
+
installDependencies: typeof source.installDependencies === "boolean" ? source.installDependencies : true,
|
|
114
|
+
configFileName: typeof source.configFileName === "string" && source.configFileName
|
|
115
|
+
? source.configFileName
|
|
116
|
+
: configFileName,
|
|
117
|
+
targets,
|
|
118
|
+
lastTargetFunctionName: typeof source.lastTargetFunctionName === "string" ? source.lastTargetFunctionName : undefined
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
async function loadSavedConfig(targetDir, configFileName) {
|
|
122
|
+
try {
|
|
123
|
+
const filePath = resolve(targetDir, configFileName);
|
|
124
|
+
const rawText = await readFile(filePath, "utf8");
|
|
125
|
+
const parsed = JSON.parse(rawText);
|
|
126
|
+
return normalizeSavedConfig(parsed, configFileName);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return normalizeSavedConfig({}, configFileName);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function pickDefaultTarget(saved) {
|
|
133
|
+
if (saved.targets.length === 0) {
|
|
134
|
+
return normalizeTarget({});
|
|
135
|
+
}
|
|
136
|
+
if (saved.lastTargetFunctionName) {
|
|
137
|
+
const last = saved.targets.find((target) => target.functionName === saved.lastTargetFunctionName);
|
|
138
|
+
if (last) {
|
|
139
|
+
return last;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return saved.targets[0];
|
|
143
|
+
}
|
|
144
|
+
function hasTargetOverrides(parsed) {
|
|
145
|
+
if (parsed.documentPath || parsed.functionName) {
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
if (!parsed.vectorSearch) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
const vector = parsed.vectorSearch;
|
|
152
|
+
return Boolean(vector.enabled !== undefined ||
|
|
153
|
+
vector.collectionPath ||
|
|
154
|
+
vector.sourceTextField ||
|
|
155
|
+
vector.vectorField ||
|
|
156
|
+
vector.returnFields?.length ||
|
|
157
|
+
vector.functionName ||
|
|
158
|
+
vector.defaultTopK !== undefined);
|
|
159
|
+
}
|
|
160
|
+
function mergeTarget(base, parsed) {
|
|
161
|
+
return normalizeTarget({
|
|
162
|
+
...base,
|
|
163
|
+
documentPath: parsed.documentPath ?? base.documentPath,
|
|
164
|
+
functionName: parsed.functionName ?? base.functionName,
|
|
165
|
+
vectorSearch: {
|
|
166
|
+
...base.vectorSearch,
|
|
167
|
+
...(parsed.vectorSearch ?? {})
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
function buildAddTargetDefaults(parsed) {
|
|
172
|
+
const documentPath = parsed.documentPath ?? NEW_COLLECTION_DOCUMENT_PATH;
|
|
173
|
+
return normalizeTarget({
|
|
174
|
+
documentPath,
|
|
175
|
+
functionName: parsed.functionName ?? deriveWriteFunctionName(documentPath),
|
|
176
|
+
vectorSearch: {
|
|
177
|
+
enabled: parsed.vectorSearch?.enabled ?? false,
|
|
178
|
+
collectionPath: parsed.vectorSearch?.collectionPath ?? deriveCollectionPath(documentPath),
|
|
179
|
+
sourceTextField: parsed.vectorSearch?.sourceTextField ?? "summary",
|
|
180
|
+
vectorField: parsed.vectorSearch?.vectorField ?? "embedding",
|
|
181
|
+
returnFields: parsed.vectorSearch?.returnFields ?? [...DEFAULT_RETURN_FIELDS],
|
|
182
|
+
functionName: parsed.vectorSearch?.functionName ?? deriveSearchFunctionName(documentPath),
|
|
183
|
+
defaultTopK: parsed.vectorSearch?.defaultTopK ?? 5
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
}
|
|
34
187
|
export async function collectConfig(parsed) {
|
|
35
|
-
const
|
|
188
|
+
const targetDir = resolve(parsed.targetDir ?? process.cwd());
|
|
189
|
+
const configFileName = parsed.configFileName?.trim() || DEFAULT_CONFIG_FILE;
|
|
190
|
+
const saved = await loadSavedConfig(targetDir, configFileName);
|
|
191
|
+
const projectId = parsed.projectId?.trim() || saved.projectId || "";
|
|
192
|
+
const region = parsed.region?.trim() || saved.region || "us-central1";
|
|
193
|
+
const serviceId = parsed.serviceId?.trim() || saved.serviceId || `${projectId || "my-project"}-service`;
|
|
194
|
+
const location = parsed.location?.trim() || saved.location || region;
|
|
195
|
+
const installDependencies = typeof parsed.installDependencies === "boolean" ? parsed.installDependencies : saved.installDependencies;
|
|
196
|
+
const defaultTarget = pickDefaultTarget(saved);
|
|
197
|
+
const addCollectionDefault = parsed.addCollection ?? false;
|
|
36
198
|
if (parsed.yes) {
|
|
37
|
-
if (!
|
|
199
|
+
if (!projectId) {
|
|
38
200
|
throw new Error("`--yes` を使う場合は `--project` が必須です。");
|
|
39
201
|
}
|
|
40
|
-
|
|
202
|
+
if (parsed.addCollection && !hasTargetOverrides(parsed)) {
|
|
203
|
+
throw new Error("`--add-collection --yes` を使う場合は --document など追加ターゲットの情報を指定してください。");
|
|
204
|
+
}
|
|
205
|
+
const resolvedTarget = parsed.addCollection
|
|
206
|
+
? mergeTarget(buildAddTargetDefaults(parsed), parsed)
|
|
207
|
+
: mergeTarget(defaultTarget, parsed);
|
|
208
|
+
return {
|
|
209
|
+
targetDir,
|
|
210
|
+
projectId,
|
|
211
|
+
region,
|
|
212
|
+
serviceId,
|
|
213
|
+
location,
|
|
214
|
+
documentPath: resolvedTarget.documentPath,
|
|
215
|
+
functionName: resolvedTarget.functionName,
|
|
216
|
+
installDependencies,
|
|
217
|
+
configFileName,
|
|
218
|
+
vectorSearch: resolvedTarget.vectorSearch,
|
|
219
|
+
existingTargets: saved.targets
|
|
220
|
+
};
|
|
41
221
|
}
|
|
42
222
|
const rl = createInterface({ input, output });
|
|
43
223
|
try {
|
|
@@ -49,27 +229,89 @@ export async function collectConfig(parsed) {
|
|
|
49
229
|
}
|
|
50
230
|
return value;
|
|
51
231
|
};
|
|
232
|
+
const askBoolean = async (label, fallback) => {
|
|
233
|
+
const base = fallback ? "y" : "n";
|
|
234
|
+
const value = (await ask(label, base, true)).toLowerCase();
|
|
235
|
+
if (["y", "yes", "true", "1", "はい"].includes(value)) {
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
if (["n", "no", "false", "0", "いいえ"].includes(value)) {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
throw new Error(`${label} は y か n を入力してください。`);
|
|
242
|
+
};
|
|
52
243
|
console.log("Firebase Data Connect / Cloud Functions セットアップ");
|
|
53
244
|
console.log("");
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
245
|
+
const selectedTargetDir = resolve(await ask("対象リポジトリのパス", targetDir, true));
|
|
246
|
+
const selectedProjectId = await ask("Firebase プロジェクトID", projectId || "my-project", true);
|
|
247
|
+
const selectedRegion = await ask("Functions のリージョン", region, true);
|
|
248
|
+
const selectedServiceId = await ask("Data Connect サービスID", serviceId || `${selectedProjectId}-service`, true);
|
|
249
|
+
const selectedLocation = await ask("Data Connect ロケーション", location || selectedRegion, true);
|
|
250
|
+
const selectedInstallDependencies = await askBoolean("functions ディレクトリで npm install を実行しますか? (y/n)", installDependencies);
|
|
251
|
+
let addCollection = addCollectionDefault;
|
|
252
|
+
if (saved.targets.length > 0 && parsed.addCollection !== true) {
|
|
253
|
+
addCollection = await askBoolean("既存設定に新しいコレクション設定を追加しますか? (y/n)", true);
|
|
254
|
+
}
|
|
255
|
+
let targetDefaults = defaultTarget;
|
|
256
|
+
if (addCollection) {
|
|
257
|
+
targetDefaults = buildAddTargetDefaults(parsed);
|
|
258
|
+
}
|
|
259
|
+
else if (saved.targets.length > 1) {
|
|
260
|
+
console.log("");
|
|
261
|
+
console.log("既存ターゲット:");
|
|
262
|
+
saved.targets.forEach((target, index) => {
|
|
263
|
+
console.log(` ${index + 1}. ${target.functionName} (${target.documentPath})`);
|
|
264
|
+
});
|
|
265
|
+
const defaultIndex = Math.max(0, saved.targets.findIndex((target) => target.functionName === saved.lastTargetFunctionName));
|
|
266
|
+
const selectedIndexRaw = await ask("編集するターゲット番号", String(defaultIndex + 1), true);
|
|
267
|
+
const selectedIndex = Number.parseInt(selectedIndexRaw, 10);
|
|
268
|
+
if (!Number.isInteger(selectedIndex) || selectedIndex < 1 || selectedIndex > saved.targets.length) {
|
|
269
|
+
throw new Error("ターゲット番号が不正です。");
|
|
270
|
+
}
|
|
271
|
+
targetDefaults = saved.targets[selectedIndex - 1];
|
|
272
|
+
}
|
|
273
|
+
const baseTarget = mergeTarget(targetDefaults, parsed);
|
|
274
|
+
const documentPath = await ask("Firestore ドキュメントパス", baseTarget.documentPath, true);
|
|
275
|
+
const functionName = normalizeFunctionName(await ask("onDocumentWritten 関数のエクスポート名", baseTarget.functionName, true));
|
|
276
|
+
const vectorEnabled = await askBoolean("ベクトル検索と onRequest 検索関数を有効化しますか? (y/n)", baseTarget.vectorSearch.enabled);
|
|
277
|
+
let vectorCollectionPath = baseTarget.vectorSearch.collectionPath;
|
|
278
|
+
let sourceTextField = baseTarget.vectorSearch.sourceTextField;
|
|
279
|
+
let vectorField = baseTarget.vectorSearch.vectorField;
|
|
280
|
+
let returnFields = baseTarget.vectorSearch.returnFields;
|
|
281
|
+
let searchFunctionName = baseTarget.vectorSearch.functionName;
|
|
282
|
+
let defaultTopK = baseTarget.vectorSearch.defaultTopK;
|
|
283
|
+
if (vectorEnabled) {
|
|
284
|
+
vectorCollectionPath = await ask("ベクトル検索対象のコレクションパス", baseTarget.vectorSearch.collectionPath, true);
|
|
285
|
+
sourceTextField = await ask("埋め込み作成対象のテキストフィールド", baseTarget.vectorSearch.sourceTextField, true);
|
|
286
|
+
vectorField = await ask("ベクトル保存先フィールド名", baseTarget.vectorSearch.vectorField, true);
|
|
287
|
+
returnFields = parseCsvFields(await ask("検索結果に返すフィールド(カンマ区切り)", toCsv(baseTarget.vectorSearch.returnFields), true));
|
|
288
|
+
searchFunctionName = normalizeFunctionName(await ask("onRequest 検索関数名", baseTarget.vectorSearch.functionName, true));
|
|
289
|
+
const defaultTopKText = await ask("ベクトル検索のデフォルト topK", String(baseTarget.vectorSearch.defaultTopK), true);
|
|
290
|
+
defaultTopK = Number.parseInt(defaultTopKText, 10);
|
|
291
|
+
if (Number.isNaN(defaultTopK) || defaultTopK <= 0) {
|
|
292
|
+
throw new Error("topK は 1 以上の整数で入力してください。");
|
|
293
|
+
}
|
|
294
|
+
}
|
|
64
295
|
return {
|
|
65
|
-
targetDir,
|
|
66
|
-
projectId,
|
|
67
|
-
region,
|
|
68
|
-
serviceId,
|
|
69
|
-
location,
|
|
296
|
+
targetDir: selectedTargetDir,
|
|
297
|
+
projectId: selectedProjectId,
|
|
298
|
+
region: selectedRegion,
|
|
299
|
+
serviceId: selectedServiceId,
|
|
300
|
+
location: selectedLocation,
|
|
70
301
|
documentPath,
|
|
71
302
|
functionName,
|
|
72
|
-
installDependencies
|
|
303
|
+
installDependencies: selectedInstallDependencies,
|
|
304
|
+
configFileName,
|
|
305
|
+
vectorSearch: {
|
|
306
|
+
enabled: vectorEnabled,
|
|
307
|
+
collectionPath: vectorCollectionPath,
|
|
308
|
+
sourceTextField,
|
|
309
|
+
vectorField,
|
|
310
|
+
returnFields,
|
|
311
|
+
functionName: searchFunctionName,
|
|
312
|
+
defaultTopK
|
|
313
|
+
},
|
|
314
|
+
existingTargets: selectedTargetDir === targetDir ? saved.targets : []
|
|
73
315
|
};
|
|
74
316
|
}
|
|
75
317
|
finally {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export interface VectorSearchHit {
|
|
2
|
+
id: string;
|
|
3
|
+
score: number;
|
|
4
|
+
data: Record<string, unknown>;
|
|
5
|
+
}
|
|
6
|
+
export interface VectorSearchResponse {
|
|
7
|
+
total: number;
|
|
8
|
+
results: VectorSearchHit[];
|
|
9
|
+
}
|
|
10
|
+
export interface SearchByVectorInput {
|
|
11
|
+
endpoint: string;
|
|
12
|
+
vector?: number[];
|
|
13
|
+
query?: string;
|
|
14
|
+
topK?: number;
|
|
15
|
+
headers?: Record<string, string>;
|
|
16
|
+
signal?: AbortSignal;
|
|
17
|
+
}
|
|
18
|
+
interface MinimalResponse {
|
|
19
|
+
ok: boolean;
|
|
20
|
+
status: number;
|
|
21
|
+
statusText: string;
|
|
22
|
+
json(): Promise<unknown>;
|
|
23
|
+
text(): Promise<string>;
|
|
24
|
+
}
|
|
25
|
+
type MinimalFetch = (url: string, init: {
|
|
26
|
+
method: string;
|
|
27
|
+
headers: Record<string, string>;
|
|
28
|
+
body: string;
|
|
29
|
+
signal?: AbortSignal;
|
|
30
|
+
}) => Promise<MinimalResponse>;
|
|
31
|
+
export declare function searchByVector(input: SearchByVectorInput, fetchImpl?: MinimalFetch): Promise<VectorSearchResponse>;
|
|
32
|
+
export interface VectorSearchClientOptions {
|
|
33
|
+
endpoint: string;
|
|
34
|
+
headers?: Record<string, string>;
|
|
35
|
+
fetchImpl?: MinimalFetch;
|
|
36
|
+
}
|
|
37
|
+
export declare function createVectorSearchClient(options: VectorSearchClientOptions): {
|
|
38
|
+
search: (input: Omit<SearchByVectorInput, "endpoint">) => Promise<VectorSearchResponse>;
|
|
39
|
+
};
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const defaultFetch = (url, init) => fetch(url, init);
|
|
2
|
+
function validateVector(vector) {
|
|
3
|
+
if (!vector) {
|
|
4
|
+
return undefined;
|
|
5
|
+
}
|
|
6
|
+
if (vector.length === 0 || vector.some((value) => !Number.isFinite(value))) {
|
|
7
|
+
throw new Error("`vector` must be a non-empty number array.");
|
|
8
|
+
}
|
|
9
|
+
return vector;
|
|
10
|
+
}
|
|
11
|
+
export async function searchByVector(input, fetchImpl = defaultFetch) {
|
|
12
|
+
const endpoint = input.endpoint.trim();
|
|
13
|
+
if (!endpoint) {
|
|
14
|
+
throw new Error("`endpoint` is required.");
|
|
15
|
+
}
|
|
16
|
+
const payload = {
|
|
17
|
+
topK: input.topK
|
|
18
|
+
};
|
|
19
|
+
const vector = validateVector(input.vector);
|
|
20
|
+
if (vector) {
|
|
21
|
+
payload.vector = vector;
|
|
22
|
+
}
|
|
23
|
+
if (input.query) {
|
|
24
|
+
payload.query = input.query;
|
|
25
|
+
}
|
|
26
|
+
const response = await fetchImpl(endpoint, {
|
|
27
|
+
method: "POST",
|
|
28
|
+
headers: {
|
|
29
|
+
"content-type": "application/json",
|
|
30
|
+
...(input.headers ?? {})
|
|
31
|
+
},
|
|
32
|
+
body: JSON.stringify(payload),
|
|
33
|
+
signal: input.signal
|
|
34
|
+
});
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
const message = await response.text();
|
|
37
|
+
throw new Error(`Vector search request failed (${response.status} ${response.statusText}): ${message}`);
|
|
38
|
+
}
|
|
39
|
+
const body = (await response.json());
|
|
40
|
+
return {
|
|
41
|
+
total: typeof body.total === "number" ? body.total : 0,
|
|
42
|
+
results: Array.isArray(body.results) ? body.results : []
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export function createVectorSearchClient(options) {
|
|
46
|
+
return {
|
|
47
|
+
search: (input) => searchByVector({
|
|
48
|
+
...input,
|
|
49
|
+
endpoint: options.endpoint,
|
|
50
|
+
headers: {
|
|
51
|
+
...(options.headers ?? {}),
|
|
52
|
+
...(input.headers ?? {})
|
|
53
|
+
}
|
|
54
|
+
}, options.fetchImpl)
|
|
55
|
+
};
|
|
56
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export interface CliArgs {
|
|
2
|
+
targetDir?: string;
|
|
3
|
+
projectId?: string;
|
|
4
|
+
region?: string;
|
|
5
|
+
serviceId?: string;
|
|
6
|
+
location?: string;
|
|
7
|
+
documentPath?: string;
|
|
8
|
+
functionName?: string;
|
|
9
|
+
installDependencies?: boolean;
|
|
10
|
+
configFileName?: string;
|
|
11
|
+
vectorSearch?: Partial<VectorSearchConfig>;
|
|
12
|
+
addCollection?: boolean;
|
|
13
|
+
yes: boolean;
|
|
14
|
+
help: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface VectorSearchConfig {
|
|
17
|
+
enabled: boolean;
|
|
18
|
+
collectionPath: string;
|
|
19
|
+
sourceTextField: string;
|
|
20
|
+
vectorField: string;
|
|
21
|
+
returnFields: string[];
|
|
22
|
+
functionName: string;
|
|
23
|
+
defaultTopK: number;
|
|
24
|
+
}
|
|
25
|
+
export interface SetupConfig {
|
|
26
|
+
targetDir: string;
|
|
27
|
+
projectId: string;
|
|
28
|
+
region: string;
|
|
29
|
+
serviceId: string;
|
|
30
|
+
location: string;
|
|
31
|
+
documentPath: string;
|
|
32
|
+
functionName: string;
|
|
33
|
+
installDependencies: boolean;
|
|
34
|
+
configFileName: string;
|
|
35
|
+
vectorSearch: VectorSearchConfig;
|
|
36
|
+
existingTargets: SetupTargetConfig[];
|
|
37
|
+
}
|
|
38
|
+
export interface RunResult {
|
|
39
|
+
targetDir: string;
|
|
40
|
+
summaryLines: string[];
|
|
41
|
+
}
|
|
42
|
+
export interface SetupTargetConfig {
|
|
43
|
+
documentPath: string;
|
|
44
|
+
functionName: string;
|
|
45
|
+
vectorSearch: VectorSearchConfig;
|
|
46
|
+
}
|
|
47
|
+
export interface PersistedBootstrapConfig {
|
|
48
|
+
projectId: string;
|
|
49
|
+
region: string;
|
|
50
|
+
serviceId: string;
|
|
51
|
+
location: string;
|
|
52
|
+
installDependencies: boolean;
|
|
53
|
+
configFileName: string;
|
|
54
|
+
targets: SetupTargetConfig[];
|
|
55
|
+
lastTargetFunctionName?: string;
|
|
56
|
+
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "firebase-dataconnect-bootstrap",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Bootstrap Firebase Data Connect and Firestore onDocumentWritten function setup in any repository.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"firebase-dataconnect-bootstrap": "bin/firebase-dataconnect-bootstrap.js"
|
|
8
8
|
},
|
|
9
|
-
"main": "dist/
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"types": "dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./search-client": {
|
|
17
|
+
"types": "./dist/search-client.d.ts",
|
|
18
|
+
"import": "./dist/search-client.js"
|
|
19
|
+
},
|
|
20
|
+
"./package.json": "./package.json"
|
|
21
|
+
},
|
|
10
22
|
"files": [
|
|
11
23
|
"bin",
|
|
12
24
|
"dist",
|
|
@@ -26,7 +38,8 @@
|
|
|
26
38
|
"dataconnect",
|
|
27
39
|
"firestore",
|
|
28
40
|
"cloud-functions",
|
|
29
|
-
"cli"
|
|
41
|
+
"cli",
|
|
42
|
+
"vector-search"
|
|
30
43
|
],
|
|
31
44
|
"author": "",
|
|
32
45
|
"license": "MIT",
|