@findlane/server 0.14.0 → 0.15.0
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 +11 -0
- package/dist/index.cjs +30 -0
- package/dist/index.d.cts +298 -0
- package/dist/index.d.ts +298 -0
- package/dist/index.js +30 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -55,11 +55,14 @@ const results = await products.search({ q }, { visitor: { ip: req.ip, userAgent:
|
|
|
55
55
|
| `getRecord(objectID)` | A record and its version. |
|
|
56
56
|
| `upsertOne(record, { expectedVersion })`, `deleteOne(objectID, { expectedVersion })` | Write only if the record is still at `expectedVersion` (0 for one that never existed); retried with one idempotency key. |
|
|
57
57
|
| `getSettings()`, `replaceSettings(settings)`, `waitForSettings()` | Read and change searchable fields, facets, filters, sorts, custom ranking, synonyms, languages, typo tolerance, and text normalization. |
|
|
58
|
+
| `compareSettings(settings, { queries, searched }?)` | Compare settings with the index's own without saving them: its search tests and most searched queries, each searched with both. Says which tests would fail and whose results change. |
|
|
59
|
+
| `getSearchTests()`, `setSearchTests(tests)` | Searches that must keep working, saved apart from the settings, such as a record in the top 3 or at least 100 results. `compareSettings` checks them. |
|
|
58
60
|
| `getIndexingStatus()`, `waitUntilIndexed(revision)` | Writes return an accepted revision and are searchable when accepted, so `waitUntilIndexed` returns at once. |
|
|
59
61
|
| `getPublicSearchAccess()`, `setPublicSearchPolicy(policy)`, `usePublicIndexDefaults()` | Restrict what browser search may read and query. |
|
|
60
62
|
| `events.send(events, { visitorId, visitId }?)`, `events.importOrders(orders)`, `events.cancelOrder(orderID)` | Orders and other events for the index's Events page and Bought together; the key needs the `events` or `write` scope. Your own anonymous visitor IDs link a shopper's events, when the index takes them. |
|
|
61
63
|
| `events.forget(ids)` | Deletes the events sent with these visitor or visit IDs, within a week. |
|
|
62
64
|
| `events.proxy(request)`, `events.relay(body, visitor)` | A route on your own site for the browser SDK's events (its `eventsUrl`), which ad blockers let through. See [Events](https://findlane.dev/docs/events#through-your-own-domain). |
|
|
65
|
+
| `analytics.searches()`, `.queries({ list, limit })`, `.filters()`, `.events()`, `.products()`, `.productCounts()`, `.product(objectID)` | The index's analytics, for your own admin: searches over time, top queries with their clicks, queries without results or clicks, the filters used, events, and each product's views, add-to-carts and orders. The key needs the `analytics` scope, and works only from a server. See [Analytics](https://findlane.dev/docs/api#analytics). |
|
|
63
66
|
|
|
64
67
|
### Settings
|
|
65
68
|
|
|
@@ -72,6 +75,14 @@ const results = await products.search({ q }, { visitor: { ip: req.ip, userAgent:
|
|
|
72
75
|
|
|
73
76
|
A large `searchable` or `rebuild` change continues in the background, with its progress in `rebuild` (also returned by `getSettings`). Searches use the previous settings until it finishes, and `waitForSettings()` waits for it.
|
|
74
77
|
|
|
78
|
+
Before saving a change to rules, synonyms, typo tolerance, or ranking, `compareSettings` shows what it does, and `{}` runs the search tests with the current settings:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
const comparison = await products.compareSettings({ rules: nextRules })
|
|
82
|
+
const failing = comparison.tests.filter((test) => test.before && !test.after)
|
|
83
|
+
if (!failing.length) await products.replaceSettings({ ...(await products.getSettings()), rules: nextRules })
|
|
84
|
+
```
|
|
85
|
+
|
|
75
86
|
### Browser search
|
|
76
87
|
|
|
77
88
|
Only upload fields you intend to make public if browser search is enabled. `getPublicSearchAccess()` shows the current mode. `setPublicSearchPolicy({ returnFields, searchFields, filterFields, facetFields, sortIds, defaultSort })` restricts the index at the API, and `usePublicIndexDefaults()` removes the restrictions. Only a workspace owner can change allowed browser origins, in the dashboard.
|
package/dist/index.cjs
CHANGED
|
@@ -67,6 +67,16 @@ class StagedReplacementError extends Error {
|
|
|
67
67
|
this.name = "StagedReplacementError";
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
|
+
function analyticsQuery(options) {
|
|
71
|
+
const params = new URLSearchParams;
|
|
72
|
+
for (const name of ["range", "from", "to", "interval", "source", "list", "limit"]) {
|
|
73
|
+
const value = options[name];
|
|
74
|
+
if (value !== undefined)
|
|
75
|
+
params.set(name, value instanceof Date ? value.toISOString() : String(value));
|
|
76
|
+
}
|
|
77
|
+
const query = params.toString();
|
|
78
|
+
return query ? `?${query}` : "";
|
|
79
|
+
}
|
|
70
80
|
var eventsPerRequest = 200;
|
|
71
81
|
var requestOptions = (value) => value === undefined ? {} : ("aborted" in value) ? { signal: value } : value;
|
|
72
82
|
var relayedBytes = 256 * 1024;
|
|
@@ -222,6 +232,7 @@ class SearchServerClient {
|
|
|
222
232
|
await flush();
|
|
223
233
|
return result;
|
|
224
234
|
};
|
|
235
|
+
const report = (name2, options) => retry(() => request(`/analytics/${name2}${analyticsQuery(options)}`, "GET", undefined, options.signal), options.signal);
|
|
225
236
|
const stagePath = (stageId) => `/staged-replacements/${encodeURIComponent(stageId)}`;
|
|
226
237
|
const beginReplacement = (stageId, signal) => retry(() => request(stagePath(stageId), "POST", undefined, signal), signal);
|
|
227
238
|
const uploadReplacementBatch = (stageId, batchId, records, signal) => retry(() => request(`${stagePath(stageId)}/batches/${encodeURIComponent(batchId)}`, "POST", { records }, signal), signal);
|
|
@@ -279,6 +290,15 @@ class SearchServerClient {
|
|
|
279
290
|
},
|
|
280
291
|
relay: (body, visitor, signal) => relayEvents(body, visitor, signal)
|
|
281
292
|
},
|
|
293
|
+
analytics: {
|
|
294
|
+
searches: (options = {}) => report("searches", options),
|
|
295
|
+
queries: (options = {}) => report("queries", options),
|
|
296
|
+
filters: (options = {}) => report("filters", options),
|
|
297
|
+
events: (options = {}) => report("events", options),
|
|
298
|
+
products: (options = {}) => report("products", options),
|
|
299
|
+
productCounts: (options = {}) => report("product-counts", options),
|
|
300
|
+
product: (objectID, options = {}) => report(`products/${encodeURIComponent(objectID)}`, options)
|
|
301
|
+
},
|
|
282
302
|
upsert: (records, signal) => request("/records", "POST", { records }, signal),
|
|
283
303
|
getRecord: (objectID, signal) => request(`/records/${encodeURIComponent(objectID)}`, "GET", undefined, signal),
|
|
284
304
|
upsertOne: (record, options) => writeOne("upsert", record.objectID, record, options),
|
|
@@ -337,6 +357,16 @@ class SearchServerClient {
|
|
|
337
357
|
const { rebuild: _rebuild, rebuilt: _rebuilt, applied: _applied, ...body } = settings;
|
|
338
358
|
return request("/config", "PUT", body, signal);
|
|
339
359
|
},
|
|
360
|
+
compareSettings: (settings, options = {}) => {
|
|
361
|
+
const { rebuild: _rebuild, rebuilt: _rebuilt, applied: _applied, ...body } = settings;
|
|
362
|
+
return request("/compare", "POST", {
|
|
363
|
+
settings: body,
|
|
364
|
+
...options.queries ? { queries: options.queries } : {},
|
|
365
|
+
...options.searched === false ? { searched: false } : {}
|
|
366
|
+
}, options.signal);
|
|
367
|
+
},
|
|
368
|
+
getSearchTests: async (signal) => (await request("/tests", "GET", undefined, signal)).tests,
|
|
369
|
+
setSearchTests: async (tests, signal) => (await request("/tests", "PUT", { tests }, signal)).tests,
|
|
340
370
|
waitForSettings: async (options = {}) => {
|
|
341
371
|
const deadline = Date.now() + (options.timeoutMs ?? 30 * 60000);
|
|
342
372
|
while (true) {
|
package/dist/index.d.cts
CHANGED
|
@@ -136,6 +136,101 @@ export type SavedSettings = IndexSettings & {
|
|
|
136
136
|
rebuilt: boolean;
|
|
137
137
|
rebuild: SettingsRebuild | null;
|
|
138
138
|
};
|
|
139
|
+
/** What a search test expects of its query's results: a record among the first `inTop` results (1 to
|
|
140
|
+
* 20), a record not among the first `notInTop`, or at least `minResults` results (1 to 10,000). */
|
|
141
|
+
export type TestExpectation = {
|
|
142
|
+
objectID: string;
|
|
143
|
+
inTop: number;
|
|
144
|
+
} | {
|
|
145
|
+
objectID: string;
|
|
146
|
+
notInTop: number;
|
|
147
|
+
} | {
|
|
148
|
+
minResults: number;
|
|
149
|
+
};
|
|
150
|
+
/** A search that must keep working, saved apart from the settings, which `compareSettings` checks: a query
|
|
151
|
+
* (empty browses), its filters on facet, filter or sort fields, and what its results should hold. IDs are
|
|
152
|
+
* 1 to 64 letters, digits, `_`, `.`, `:`, or `-`, unique in the index; notes up to 200 characters. */
|
|
153
|
+
export type SearchTest = {
|
|
154
|
+
id: string;
|
|
155
|
+
q: string;
|
|
156
|
+
filters?: SearchRequest["filters"];
|
|
157
|
+
expect: TestExpectation;
|
|
158
|
+
note?: string;
|
|
159
|
+
};
|
|
160
|
+
/** How many compared queries changed in a way, and their searches in the last 30 days. */
|
|
161
|
+
export type CompareCount = {
|
|
162
|
+
queries: number;
|
|
163
|
+
searches: number;
|
|
164
|
+
};
|
|
165
|
+
export type ComparedHit = {
|
|
166
|
+
objectID: string;
|
|
167
|
+
title: string;
|
|
168
|
+
};
|
|
169
|
+
/** Proposed settings compared with the index's own, without saving them (`compareSettings`). */
|
|
170
|
+
export type SettingsComparison = {
|
|
171
|
+
/** The queries compared, and their searches in the last 30 days. */
|
|
172
|
+
compared: number;
|
|
173
|
+
searches: number;
|
|
174
|
+
/** Time (20 seconds) ran out before every query was compared. */
|
|
175
|
+
truncated: boolean;
|
|
176
|
+
summary: {
|
|
177
|
+
topChanged: CompareCount;
|
|
178
|
+
moreResults: CompareCount;
|
|
179
|
+
fewerResults: CompareCount;
|
|
180
|
+
nowNothing: CompareCount;
|
|
181
|
+
nowSomething: CompareCount;
|
|
182
|
+
};
|
|
183
|
+
/** Every search test: whether it passes with the current settings (`before`) and the proposed ones
|
|
184
|
+
* (`after`), or `skipped` when time ran out first, or the search's `error`. */
|
|
185
|
+
tests: (Omit<SearchTest, "note"> & {
|
|
186
|
+
before?: boolean;
|
|
187
|
+
after?: boolean;
|
|
188
|
+
skipped?: true;
|
|
189
|
+
error?: string;
|
|
190
|
+
})[];
|
|
191
|
+
/** Up to 50 queries whose top 10 or total changed, the tests' queries first, then the most searched: the
|
|
192
|
+
* totals and first 5 results with each, and the results that left, joined, and moved within the top 10.
|
|
193
|
+
* Places are in the first 20 results; null is outside them. */
|
|
194
|
+
changes: {
|
|
195
|
+
q: string;
|
|
196
|
+
filters?: SearchRequest["filters"];
|
|
197
|
+
searches: number;
|
|
198
|
+
before: {
|
|
199
|
+
total: number;
|
|
200
|
+
top: ComparedHit[];
|
|
201
|
+
};
|
|
202
|
+
after: {
|
|
203
|
+
total: number;
|
|
204
|
+
top: ComparedHit[];
|
|
205
|
+
};
|
|
206
|
+
gone: (ComparedHit & {
|
|
207
|
+
from: number;
|
|
208
|
+
to: number | null;
|
|
209
|
+
})[];
|
|
210
|
+
added: (ComparedHit & {
|
|
211
|
+
from: number | null;
|
|
212
|
+
to: number;
|
|
213
|
+
})[];
|
|
214
|
+
moved: (ComparedHit & {
|
|
215
|
+
from: number;
|
|
216
|
+
to: number;
|
|
217
|
+
})[];
|
|
218
|
+
}[];
|
|
219
|
+
errors: {
|
|
220
|
+
q: string;
|
|
221
|
+
error: string;
|
|
222
|
+
}[];
|
|
223
|
+
};
|
|
224
|
+
export type CompareOptions = {
|
|
225
|
+
/** More queries to compare, such as those a change is for: text, or `{ q, filters }`. Up to 200. */
|
|
226
|
+
queries?: (string | {
|
|
227
|
+
q: string;
|
|
228
|
+
filters?: SearchRequest["filters"];
|
|
229
|
+
})[];
|
|
230
|
+
/** false leaves out the index's 100 most searched queries of the last 30 days. */
|
|
231
|
+
searched?: boolean;
|
|
232
|
+
signal?: AbortSignal;
|
|
233
|
+
};
|
|
139
234
|
export type PublicSearchPolicy = {
|
|
140
235
|
returnFields: string[];
|
|
141
236
|
searchFields: string[];
|
|
@@ -474,6 +569,166 @@ export type EventOptions = {
|
|
|
474
569
|
visitorId?: string;
|
|
475
570
|
visitId?: string;
|
|
476
571
|
};
|
|
572
|
+
/** An analytics window, in UTC: `range` (7d by default), or `from` and `to`. A date as `from` is the start of
|
|
573
|
+
* that day, and as `to` includes the whole day; `to` is now by default. Analytics go back 90 days: a window
|
|
574
|
+
* starting earlier starts then, and the report's `from` says so. */
|
|
575
|
+
export type AnalyticsOptions = {
|
|
576
|
+
range?: "24h" | "7d" | "30d" | "90d";
|
|
577
|
+
from?: string | Date;
|
|
578
|
+
to?: string | Date;
|
|
579
|
+
/** Searches from browsers (browser search), from servers (API keys), or both, by default. */
|
|
580
|
+
source?: "all" | "browser" | "server";
|
|
581
|
+
signal?: AbortSignal;
|
|
582
|
+
};
|
|
583
|
+
/** What every analytics report starts with. `sampled`: the counts are estimates, because Analytics Engine,
|
|
584
|
+
* which keeps them, sampled some of them; totals and lists stay close, small counts less so. */
|
|
585
|
+
export type AnalyticsReport = {
|
|
586
|
+
index: string;
|
|
587
|
+
from: string;
|
|
588
|
+
to: string;
|
|
589
|
+
source: "all" | "browser" | "server";
|
|
590
|
+
sampled: boolean;
|
|
591
|
+
};
|
|
592
|
+
export type SearchTotals = {
|
|
593
|
+
searches: number;
|
|
594
|
+
/** Searches whose query and result count were recorded: the base for `noResultRate`. */
|
|
595
|
+
tracked: number;
|
|
596
|
+
noResults: number;
|
|
597
|
+
/** `noResults` of `tracked`, 0 to 1; null without tracked searches. */
|
|
598
|
+
noResultRate: number | null;
|
|
599
|
+
browser: number;
|
|
600
|
+
server: number;
|
|
601
|
+
uniqueQueries: number;
|
|
602
|
+
/** Response times at the API, in ms. */
|
|
603
|
+
medianMs: number;
|
|
604
|
+
p95Ms: number;
|
|
605
|
+
};
|
|
606
|
+
export type SearchesReport = AnalyticsReport & {
|
|
607
|
+
interval: "hour" | "day";
|
|
608
|
+
totals: SearchTotals;
|
|
609
|
+
/** The same span just before `from`. */
|
|
610
|
+
previous: SearchTotals & {
|
|
611
|
+
from: string;
|
|
612
|
+
to: string;
|
|
613
|
+
};
|
|
614
|
+
/** Every hour or day of the window, the first one starting at or before `from`. */
|
|
615
|
+
series: {
|
|
616
|
+
t: string;
|
|
617
|
+
searches: number;
|
|
618
|
+
tracked: number;
|
|
619
|
+
noResults: number;
|
|
620
|
+
}[];
|
|
621
|
+
};
|
|
622
|
+
/** A top query: its clicks on results and their average place (from 1), or null when clicks aren't available. */
|
|
623
|
+
export type TopQuery = {
|
|
624
|
+
query: string;
|
|
625
|
+
searches: number;
|
|
626
|
+
noResults: number;
|
|
627
|
+
clicks: number | null;
|
|
628
|
+
averagePosition: number | null;
|
|
629
|
+
};
|
|
630
|
+
export type QueryCount = {
|
|
631
|
+
query: string;
|
|
632
|
+
searches: number;
|
|
633
|
+
};
|
|
634
|
+
export type QueryLists = {
|
|
635
|
+
top: TopQuery;
|
|
636
|
+
noResults: QueryCount;
|
|
637
|
+
noClicks: QueryCount;
|
|
638
|
+
};
|
|
639
|
+
export type QueriesReport<L extends keyof QueryLists = "top"> = AnalyticsReport & {
|
|
640
|
+
list: L;
|
|
641
|
+
queries: QueryLists[L][];
|
|
642
|
+
};
|
|
643
|
+
export type FiltersReport = AnalyticsReport & {
|
|
644
|
+
/** Searches whose filters and sort were recorded. */
|
|
645
|
+
searches: number;
|
|
646
|
+
/** Each filter field with the searches that filtered on it, up to 100. */
|
|
647
|
+
filters: {
|
|
648
|
+
field: string;
|
|
649
|
+
searches: number;
|
|
650
|
+
}[];
|
|
651
|
+
/** Each sort with its searches; `sort` is null for searches that named none, which use the index's default. */
|
|
652
|
+
sorts: {
|
|
653
|
+
sort: string | null;
|
|
654
|
+
searches: number;
|
|
655
|
+
}[];
|
|
656
|
+
};
|
|
657
|
+
export type EventType = "view" | "click" | "addToCart" | "purchase" | "cancel";
|
|
658
|
+
export type EventsReport = AnalyticsReport & {
|
|
659
|
+
interval: "hour" | "day";
|
|
660
|
+
/** Accepted events of each type, by source. */
|
|
661
|
+
types: {
|
|
662
|
+
type: EventType;
|
|
663
|
+
browser: number;
|
|
664
|
+
server: number;
|
|
665
|
+
}[];
|
|
666
|
+
/** Orders accepted, and of them those stored, which Bought together and the order counts use. */
|
|
667
|
+
orders: {
|
|
668
|
+
accepted: number;
|
|
669
|
+
stored: number;
|
|
670
|
+
};
|
|
671
|
+
/** Events from crawlers and scripts, which are dropped. */
|
|
672
|
+
bots: number;
|
|
673
|
+
/** Refused events, by why. */
|
|
674
|
+
refused: {
|
|
675
|
+
reason: string;
|
|
676
|
+
events: number;
|
|
677
|
+
}[];
|
|
678
|
+
series: ({
|
|
679
|
+
t: string;
|
|
680
|
+
} & Record<EventType, number>)[];
|
|
681
|
+
};
|
|
682
|
+
/** A product's counts. `viewers` is how many visitor IDs viewed it, or null when the index's views carry
|
|
683
|
+
* none. `searchClicks` is clicks on it outside recommendations: in search results and product lists. */
|
|
684
|
+
export type ProductCount = {
|
|
685
|
+
objectID: string;
|
|
686
|
+
views: number;
|
|
687
|
+
viewers: number | null;
|
|
688
|
+
searchClicks: number;
|
|
689
|
+
carts: number;
|
|
690
|
+
orders: number;
|
|
691
|
+
};
|
|
692
|
+
export type ListedProduct = ProductCount & {
|
|
693
|
+
title: string;
|
|
694
|
+
image: string | null;
|
|
695
|
+
};
|
|
696
|
+
/** Product reports are exact and about 2 minutes behind; `computedAt` is when they were counted, up to 5
|
|
697
|
+
* minutes ago. `orders` is the orders stored in the window. */
|
|
698
|
+
export type ProductListsReport = AnalyticsReport & {
|
|
699
|
+
computedAt: string;
|
|
700
|
+
orders: number;
|
|
701
|
+
/** The most viewed, most added to carts, and most ordered products, by views. */
|
|
702
|
+
products: ListedProduct[];
|
|
703
|
+
/** Products with 50 or more views, the fewest orders per view first; null without orders. */
|
|
704
|
+
viewedNotBought: ListedProduct[] | null;
|
|
705
|
+
};
|
|
706
|
+
export type ProductCountsReport = AnalyticsReport & {
|
|
707
|
+
computedAt: string;
|
|
708
|
+
orders: number;
|
|
709
|
+
/** false when more than 50,000 products had events, and only the most viewed are listed. */
|
|
710
|
+
complete: boolean;
|
|
711
|
+
products: ProductCount[];
|
|
712
|
+
};
|
|
713
|
+
export type ProductReport = AnalyticsReport & {
|
|
714
|
+
objectID: string;
|
|
715
|
+
interval: "hour" | "day";
|
|
716
|
+
totals: Omit<ProductCount, "objectID">;
|
|
717
|
+
series: ({
|
|
718
|
+
t: string;
|
|
719
|
+
} & Omit<ProductCount, "objectID">)[];
|
|
720
|
+
/** The queries whose results it was clicked in, the most clicks first, up to 20. */
|
|
721
|
+
searchedFor: {
|
|
722
|
+
query: string;
|
|
723
|
+
clicks: number;
|
|
724
|
+
}[];
|
|
725
|
+
/** Clicks on it in each recommendation list. */
|
|
726
|
+
recommendationClicks: {
|
|
727
|
+
goesWith: number;
|
|
728
|
+
similar: number;
|
|
729
|
+
together: number;
|
|
730
|
+
};
|
|
731
|
+
};
|
|
477
732
|
export declare class FindlaneError extends Error {
|
|
478
733
|
status: number;
|
|
479
734
|
constructor(status: number, message: string);
|
|
@@ -532,6 +787,39 @@ export declare class SearchServerClient {
|
|
|
532
787
|
* and its visitor. Answers with the API's response. */
|
|
533
788
|
relay: (body: string, visitor: RelayedVisitor, signal?: AbortSignal) => Promise<Response>;
|
|
534
789
|
};
|
|
790
|
+
/** The index's search analytics. The API key needs the `analytics` scope, and works only from a server:
|
|
791
|
+
* requests from web pages are refused. */
|
|
792
|
+
analytics: {
|
|
793
|
+
/** Searches, searches without results and response times, beside the same span before, and a series per
|
|
794
|
+
* hour (up to 7 days, by default) or day. */
|
|
795
|
+
searches: (options?: AnalyticsOptions & {
|
|
796
|
+
interval?: "hour" | "day";
|
|
797
|
+
}) => Promise<SearchesReport>;
|
|
798
|
+
/** Queries, the most searched first: `top` (by default) with their clicks, `noResults`, or `noClicks`, which
|
|
799
|
+
* found something nobody clicked. `limit` is 1 to 1,000, 100 by default. */
|
|
800
|
+
queries: <L extends keyof QueryLists = "top">(options?: AnalyticsOptions & {
|
|
801
|
+
list?: L;
|
|
802
|
+
limit?: number;
|
|
803
|
+
}) => Promise<QueriesReport<L>>;
|
|
804
|
+
/** The filter fields and sorts searches used. */
|
|
805
|
+
filters: (options?: AnalyticsOptions) => Promise<FiltersReport>;
|
|
806
|
+
/** Events of each type over time and by source, orders stored, and refused and bot events. */
|
|
807
|
+
events: (options?: AnalyticsOptions & {
|
|
808
|
+
interval?: "hour" | "day";
|
|
809
|
+
}) => Promise<EventsReport>;
|
|
810
|
+
/** The most viewed, most added to carts and most ordered products, with titles and images, and those
|
|
811
|
+
* viewed but rarely bought. Exact, from every event kept (13 months, from 9 October 2026). */
|
|
812
|
+
products: (options?: AnalyticsOptions) => Promise<ProductListsReport>;
|
|
813
|
+
/** Every product with events or orders in the window, the most viewed first, up to 50,000: views,
|
|
814
|
+
* viewers, clicks, add-to-carts and orders. Exact, like `products`. */
|
|
815
|
+
productCounts: (options?: AnalyticsOptions) => Promise<ProductCountsReport>;
|
|
816
|
+
/** One product's views, viewers, clicks, add-to-carts and orders per hour or day, the queries it was
|
|
817
|
+
* clicked from, and its clicks in recommendations. Fast, for a product page in an admin: views and
|
|
818
|
+
* clicks are estimates when `sampled`, and an add-to-cart of several products counts toward the first. */
|
|
819
|
+
product: (objectID: string, options?: AnalyticsOptions & {
|
|
820
|
+
interval?: "hour" | "day";
|
|
821
|
+
}) => Promise<ProductReport>;
|
|
822
|
+
};
|
|
535
823
|
upsert: (records: T[], signal?: AbortSignal) => Promise<WriteReceipt>;
|
|
536
824
|
getRecord: (objectID: string, signal?: AbortSignal) => Promise<RecordSnapshot<T>>;
|
|
537
825
|
upsertOne: (record: T, options: ConditionalWriteOptions) => Promise<WriteReceipt>;
|
|
@@ -545,6 +833,16 @@ export declare class SearchServerClient {
|
|
|
545
833
|
delete: (objectID: string, signal?: AbortSignal) => Promise<WriteReceipt>;
|
|
546
834
|
getSettings: (signal?: AbortSignal) => Promise<CurrentSettings>;
|
|
547
835
|
replaceSettings: (settings: IndexSettings, signal?: AbortSignal) => Promise<SavedSettings>;
|
|
836
|
+
/** Compares settings with the index's own without saving them: its search tests, `queries`, and its 100
|
|
837
|
+
* most searched queries of the last 30 days, each searched with both, for up to 20 seconds. Takes the
|
|
838
|
+
* settings `replaceSettings` does, or only those that change; `{}` runs the tests with the current
|
|
839
|
+
* settings. A change to what the index stores (new searchable, facet, filter, sort or location fields,
|
|
840
|
+
* or normalization) throws a 409: save it first. Not counted as searches. */
|
|
841
|
+
compareSettings: (settings: Partial<IndexSettings>, options?: CompareOptions) => Promise<SettingsComparison>;
|
|
842
|
+
/** The index's search tests. */
|
|
843
|
+
getSearchTests: (signal?: AbortSignal) => Promise<SearchTest[]>;
|
|
844
|
+
/** Replaces the index's search tests, up to 200; `[]` deletes them. */
|
|
845
|
+
setSearchTests: (tests: SearchTest[], signal?: AbortSignal) => Promise<SearchTest[]>;
|
|
548
846
|
/** Waits until no settings rebuild is running; throws if it failed. */
|
|
549
847
|
waitForSettings: (options?: {
|
|
550
848
|
timeoutMs?: number;
|
package/dist/index.d.ts
CHANGED
|
@@ -136,6 +136,101 @@ export type SavedSettings = IndexSettings & {
|
|
|
136
136
|
rebuilt: boolean;
|
|
137
137
|
rebuild: SettingsRebuild | null;
|
|
138
138
|
};
|
|
139
|
+
/** What a search test expects of its query's results: a record among the first `inTop` results (1 to
|
|
140
|
+
* 20), a record not among the first `notInTop`, or at least `minResults` results (1 to 10,000). */
|
|
141
|
+
export type TestExpectation = {
|
|
142
|
+
objectID: string;
|
|
143
|
+
inTop: number;
|
|
144
|
+
} | {
|
|
145
|
+
objectID: string;
|
|
146
|
+
notInTop: number;
|
|
147
|
+
} | {
|
|
148
|
+
minResults: number;
|
|
149
|
+
};
|
|
150
|
+
/** A search that must keep working, saved apart from the settings, which `compareSettings` checks: a query
|
|
151
|
+
* (empty browses), its filters on facet, filter or sort fields, and what its results should hold. IDs are
|
|
152
|
+
* 1 to 64 letters, digits, `_`, `.`, `:`, or `-`, unique in the index; notes up to 200 characters. */
|
|
153
|
+
export type SearchTest = {
|
|
154
|
+
id: string;
|
|
155
|
+
q: string;
|
|
156
|
+
filters?: SearchRequest["filters"];
|
|
157
|
+
expect: TestExpectation;
|
|
158
|
+
note?: string;
|
|
159
|
+
};
|
|
160
|
+
/** How many compared queries changed in a way, and their searches in the last 30 days. */
|
|
161
|
+
export type CompareCount = {
|
|
162
|
+
queries: number;
|
|
163
|
+
searches: number;
|
|
164
|
+
};
|
|
165
|
+
export type ComparedHit = {
|
|
166
|
+
objectID: string;
|
|
167
|
+
title: string;
|
|
168
|
+
};
|
|
169
|
+
/** Proposed settings compared with the index's own, without saving them (`compareSettings`). */
|
|
170
|
+
export type SettingsComparison = {
|
|
171
|
+
/** The queries compared, and their searches in the last 30 days. */
|
|
172
|
+
compared: number;
|
|
173
|
+
searches: number;
|
|
174
|
+
/** Time (20 seconds) ran out before every query was compared. */
|
|
175
|
+
truncated: boolean;
|
|
176
|
+
summary: {
|
|
177
|
+
topChanged: CompareCount;
|
|
178
|
+
moreResults: CompareCount;
|
|
179
|
+
fewerResults: CompareCount;
|
|
180
|
+
nowNothing: CompareCount;
|
|
181
|
+
nowSomething: CompareCount;
|
|
182
|
+
};
|
|
183
|
+
/** Every search test: whether it passes with the current settings (`before`) and the proposed ones
|
|
184
|
+
* (`after`), or `skipped` when time ran out first, or the search's `error`. */
|
|
185
|
+
tests: (Omit<SearchTest, "note"> & {
|
|
186
|
+
before?: boolean;
|
|
187
|
+
after?: boolean;
|
|
188
|
+
skipped?: true;
|
|
189
|
+
error?: string;
|
|
190
|
+
})[];
|
|
191
|
+
/** Up to 50 queries whose top 10 or total changed, the tests' queries first, then the most searched: the
|
|
192
|
+
* totals and first 5 results with each, and the results that left, joined, and moved within the top 10.
|
|
193
|
+
* Places are in the first 20 results; null is outside them. */
|
|
194
|
+
changes: {
|
|
195
|
+
q: string;
|
|
196
|
+
filters?: SearchRequest["filters"];
|
|
197
|
+
searches: number;
|
|
198
|
+
before: {
|
|
199
|
+
total: number;
|
|
200
|
+
top: ComparedHit[];
|
|
201
|
+
};
|
|
202
|
+
after: {
|
|
203
|
+
total: number;
|
|
204
|
+
top: ComparedHit[];
|
|
205
|
+
};
|
|
206
|
+
gone: (ComparedHit & {
|
|
207
|
+
from: number;
|
|
208
|
+
to: number | null;
|
|
209
|
+
})[];
|
|
210
|
+
added: (ComparedHit & {
|
|
211
|
+
from: number | null;
|
|
212
|
+
to: number;
|
|
213
|
+
})[];
|
|
214
|
+
moved: (ComparedHit & {
|
|
215
|
+
from: number;
|
|
216
|
+
to: number;
|
|
217
|
+
})[];
|
|
218
|
+
}[];
|
|
219
|
+
errors: {
|
|
220
|
+
q: string;
|
|
221
|
+
error: string;
|
|
222
|
+
}[];
|
|
223
|
+
};
|
|
224
|
+
export type CompareOptions = {
|
|
225
|
+
/** More queries to compare, such as those a change is for: text, or `{ q, filters }`. Up to 200. */
|
|
226
|
+
queries?: (string | {
|
|
227
|
+
q: string;
|
|
228
|
+
filters?: SearchRequest["filters"];
|
|
229
|
+
})[];
|
|
230
|
+
/** false leaves out the index's 100 most searched queries of the last 30 days. */
|
|
231
|
+
searched?: boolean;
|
|
232
|
+
signal?: AbortSignal;
|
|
233
|
+
};
|
|
139
234
|
export type PublicSearchPolicy = {
|
|
140
235
|
returnFields: string[];
|
|
141
236
|
searchFields: string[];
|
|
@@ -474,6 +569,166 @@ export type EventOptions = {
|
|
|
474
569
|
visitorId?: string;
|
|
475
570
|
visitId?: string;
|
|
476
571
|
};
|
|
572
|
+
/** An analytics window, in UTC: `range` (7d by default), or `from` and `to`. A date as `from` is the start of
|
|
573
|
+
* that day, and as `to` includes the whole day; `to` is now by default. Analytics go back 90 days: a window
|
|
574
|
+
* starting earlier starts then, and the report's `from` says so. */
|
|
575
|
+
export type AnalyticsOptions = {
|
|
576
|
+
range?: "24h" | "7d" | "30d" | "90d";
|
|
577
|
+
from?: string | Date;
|
|
578
|
+
to?: string | Date;
|
|
579
|
+
/** Searches from browsers (browser search), from servers (API keys), or both, by default. */
|
|
580
|
+
source?: "all" | "browser" | "server";
|
|
581
|
+
signal?: AbortSignal;
|
|
582
|
+
};
|
|
583
|
+
/** What every analytics report starts with. `sampled`: the counts are estimates, because Analytics Engine,
|
|
584
|
+
* which keeps them, sampled some of them; totals and lists stay close, small counts less so. */
|
|
585
|
+
export type AnalyticsReport = {
|
|
586
|
+
index: string;
|
|
587
|
+
from: string;
|
|
588
|
+
to: string;
|
|
589
|
+
source: "all" | "browser" | "server";
|
|
590
|
+
sampled: boolean;
|
|
591
|
+
};
|
|
592
|
+
export type SearchTotals = {
|
|
593
|
+
searches: number;
|
|
594
|
+
/** Searches whose query and result count were recorded: the base for `noResultRate`. */
|
|
595
|
+
tracked: number;
|
|
596
|
+
noResults: number;
|
|
597
|
+
/** `noResults` of `tracked`, 0 to 1; null without tracked searches. */
|
|
598
|
+
noResultRate: number | null;
|
|
599
|
+
browser: number;
|
|
600
|
+
server: number;
|
|
601
|
+
uniqueQueries: number;
|
|
602
|
+
/** Response times at the API, in ms. */
|
|
603
|
+
medianMs: number;
|
|
604
|
+
p95Ms: number;
|
|
605
|
+
};
|
|
606
|
+
export type SearchesReport = AnalyticsReport & {
|
|
607
|
+
interval: "hour" | "day";
|
|
608
|
+
totals: SearchTotals;
|
|
609
|
+
/** The same span just before `from`. */
|
|
610
|
+
previous: SearchTotals & {
|
|
611
|
+
from: string;
|
|
612
|
+
to: string;
|
|
613
|
+
};
|
|
614
|
+
/** Every hour or day of the window, the first one starting at or before `from`. */
|
|
615
|
+
series: {
|
|
616
|
+
t: string;
|
|
617
|
+
searches: number;
|
|
618
|
+
tracked: number;
|
|
619
|
+
noResults: number;
|
|
620
|
+
}[];
|
|
621
|
+
};
|
|
622
|
+
/** A top query: its clicks on results and their average place (from 1), or null when clicks aren't available. */
|
|
623
|
+
export type TopQuery = {
|
|
624
|
+
query: string;
|
|
625
|
+
searches: number;
|
|
626
|
+
noResults: number;
|
|
627
|
+
clicks: number | null;
|
|
628
|
+
averagePosition: number | null;
|
|
629
|
+
};
|
|
630
|
+
export type QueryCount = {
|
|
631
|
+
query: string;
|
|
632
|
+
searches: number;
|
|
633
|
+
};
|
|
634
|
+
export type QueryLists = {
|
|
635
|
+
top: TopQuery;
|
|
636
|
+
noResults: QueryCount;
|
|
637
|
+
noClicks: QueryCount;
|
|
638
|
+
};
|
|
639
|
+
export type QueriesReport<L extends keyof QueryLists = "top"> = AnalyticsReport & {
|
|
640
|
+
list: L;
|
|
641
|
+
queries: QueryLists[L][];
|
|
642
|
+
};
|
|
643
|
+
export type FiltersReport = AnalyticsReport & {
|
|
644
|
+
/** Searches whose filters and sort were recorded. */
|
|
645
|
+
searches: number;
|
|
646
|
+
/** Each filter field with the searches that filtered on it, up to 100. */
|
|
647
|
+
filters: {
|
|
648
|
+
field: string;
|
|
649
|
+
searches: number;
|
|
650
|
+
}[];
|
|
651
|
+
/** Each sort with its searches; `sort` is null for searches that named none, which use the index's default. */
|
|
652
|
+
sorts: {
|
|
653
|
+
sort: string | null;
|
|
654
|
+
searches: number;
|
|
655
|
+
}[];
|
|
656
|
+
};
|
|
657
|
+
export type EventType = "view" | "click" | "addToCart" | "purchase" | "cancel";
|
|
658
|
+
export type EventsReport = AnalyticsReport & {
|
|
659
|
+
interval: "hour" | "day";
|
|
660
|
+
/** Accepted events of each type, by source. */
|
|
661
|
+
types: {
|
|
662
|
+
type: EventType;
|
|
663
|
+
browser: number;
|
|
664
|
+
server: number;
|
|
665
|
+
}[];
|
|
666
|
+
/** Orders accepted, and of them those stored, which Bought together and the order counts use. */
|
|
667
|
+
orders: {
|
|
668
|
+
accepted: number;
|
|
669
|
+
stored: number;
|
|
670
|
+
};
|
|
671
|
+
/** Events from crawlers and scripts, which are dropped. */
|
|
672
|
+
bots: number;
|
|
673
|
+
/** Refused events, by why. */
|
|
674
|
+
refused: {
|
|
675
|
+
reason: string;
|
|
676
|
+
events: number;
|
|
677
|
+
}[];
|
|
678
|
+
series: ({
|
|
679
|
+
t: string;
|
|
680
|
+
} & Record<EventType, number>)[];
|
|
681
|
+
};
|
|
682
|
+
/** A product's counts. `viewers` is how many visitor IDs viewed it, or null when the index's views carry
|
|
683
|
+
* none. `searchClicks` is clicks on it outside recommendations: in search results and product lists. */
|
|
684
|
+
export type ProductCount = {
|
|
685
|
+
objectID: string;
|
|
686
|
+
views: number;
|
|
687
|
+
viewers: number | null;
|
|
688
|
+
searchClicks: number;
|
|
689
|
+
carts: number;
|
|
690
|
+
orders: number;
|
|
691
|
+
};
|
|
692
|
+
export type ListedProduct = ProductCount & {
|
|
693
|
+
title: string;
|
|
694
|
+
image: string | null;
|
|
695
|
+
};
|
|
696
|
+
/** Product reports are exact and about 2 minutes behind; `computedAt` is when they were counted, up to 5
|
|
697
|
+
* minutes ago. `orders` is the orders stored in the window. */
|
|
698
|
+
export type ProductListsReport = AnalyticsReport & {
|
|
699
|
+
computedAt: string;
|
|
700
|
+
orders: number;
|
|
701
|
+
/** The most viewed, most added to carts, and most ordered products, by views. */
|
|
702
|
+
products: ListedProduct[];
|
|
703
|
+
/** Products with 50 or more views, the fewest orders per view first; null without orders. */
|
|
704
|
+
viewedNotBought: ListedProduct[] | null;
|
|
705
|
+
};
|
|
706
|
+
export type ProductCountsReport = AnalyticsReport & {
|
|
707
|
+
computedAt: string;
|
|
708
|
+
orders: number;
|
|
709
|
+
/** false when more than 50,000 products had events, and only the most viewed are listed. */
|
|
710
|
+
complete: boolean;
|
|
711
|
+
products: ProductCount[];
|
|
712
|
+
};
|
|
713
|
+
export type ProductReport = AnalyticsReport & {
|
|
714
|
+
objectID: string;
|
|
715
|
+
interval: "hour" | "day";
|
|
716
|
+
totals: Omit<ProductCount, "objectID">;
|
|
717
|
+
series: ({
|
|
718
|
+
t: string;
|
|
719
|
+
} & Omit<ProductCount, "objectID">)[];
|
|
720
|
+
/** The queries whose results it was clicked in, the most clicks first, up to 20. */
|
|
721
|
+
searchedFor: {
|
|
722
|
+
query: string;
|
|
723
|
+
clicks: number;
|
|
724
|
+
}[];
|
|
725
|
+
/** Clicks on it in each recommendation list. */
|
|
726
|
+
recommendationClicks: {
|
|
727
|
+
goesWith: number;
|
|
728
|
+
similar: number;
|
|
729
|
+
together: number;
|
|
730
|
+
};
|
|
731
|
+
};
|
|
477
732
|
export declare class FindlaneError extends Error {
|
|
478
733
|
status: number;
|
|
479
734
|
constructor(status: number, message: string);
|
|
@@ -532,6 +787,39 @@ export declare class SearchServerClient {
|
|
|
532
787
|
* and its visitor. Answers with the API's response. */
|
|
533
788
|
relay: (body: string, visitor: RelayedVisitor, signal?: AbortSignal) => Promise<Response>;
|
|
534
789
|
};
|
|
790
|
+
/** The index's search analytics. The API key needs the `analytics` scope, and works only from a server:
|
|
791
|
+
* requests from web pages are refused. */
|
|
792
|
+
analytics: {
|
|
793
|
+
/** Searches, searches without results and response times, beside the same span before, and a series per
|
|
794
|
+
* hour (up to 7 days, by default) or day. */
|
|
795
|
+
searches: (options?: AnalyticsOptions & {
|
|
796
|
+
interval?: "hour" | "day";
|
|
797
|
+
}) => Promise<SearchesReport>;
|
|
798
|
+
/** Queries, the most searched first: `top` (by default) with their clicks, `noResults`, or `noClicks`, which
|
|
799
|
+
* found something nobody clicked. `limit` is 1 to 1,000, 100 by default. */
|
|
800
|
+
queries: <L extends keyof QueryLists = "top">(options?: AnalyticsOptions & {
|
|
801
|
+
list?: L;
|
|
802
|
+
limit?: number;
|
|
803
|
+
}) => Promise<QueriesReport<L>>;
|
|
804
|
+
/** The filter fields and sorts searches used. */
|
|
805
|
+
filters: (options?: AnalyticsOptions) => Promise<FiltersReport>;
|
|
806
|
+
/** Events of each type over time and by source, orders stored, and refused and bot events. */
|
|
807
|
+
events: (options?: AnalyticsOptions & {
|
|
808
|
+
interval?: "hour" | "day";
|
|
809
|
+
}) => Promise<EventsReport>;
|
|
810
|
+
/** The most viewed, most added to carts and most ordered products, with titles and images, and those
|
|
811
|
+
* viewed but rarely bought. Exact, from every event kept (13 months, from 9 October 2026). */
|
|
812
|
+
products: (options?: AnalyticsOptions) => Promise<ProductListsReport>;
|
|
813
|
+
/** Every product with events or orders in the window, the most viewed first, up to 50,000: views,
|
|
814
|
+
* viewers, clicks, add-to-carts and orders. Exact, like `products`. */
|
|
815
|
+
productCounts: (options?: AnalyticsOptions) => Promise<ProductCountsReport>;
|
|
816
|
+
/** One product's views, viewers, clicks, add-to-carts and orders per hour or day, the queries it was
|
|
817
|
+
* clicked from, and its clicks in recommendations. Fast, for a product page in an admin: views and
|
|
818
|
+
* clicks are estimates when `sampled`, and an add-to-cart of several products counts toward the first. */
|
|
819
|
+
product: (objectID: string, options?: AnalyticsOptions & {
|
|
820
|
+
interval?: "hour" | "day";
|
|
821
|
+
}) => Promise<ProductReport>;
|
|
822
|
+
};
|
|
535
823
|
upsert: (records: T[], signal?: AbortSignal) => Promise<WriteReceipt>;
|
|
536
824
|
getRecord: (objectID: string, signal?: AbortSignal) => Promise<RecordSnapshot<T>>;
|
|
537
825
|
upsertOne: (record: T, options: ConditionalWriteOptions) => Promise<WriteReceipt>;
|
|
@@ -545,6 +833,16 @@ export declare class SearchServerClient {
|
|
|
545
833
|
delete: (objectID: string, signal?: AbortSignal) => Promise<WriteReceipt>;
|
|
546
834
|
getSettings: (signal?: AbortSignal) => Promise<CurrentSettings>;
|
|
547
835
|
replaceSettings: (settings: IndexSettings, signal?: AbortSignal) => Promise<SavedSettings>;
|
|
836
|
+
/** Compares settings with the index's own without saving them: its search tests, `queries`, and its 100
|
|
837
|
+
* most searched queries of the last 30 days, each searched with both, for up to 20 seconds. Takes the
|
|
838
|
+
* settings `replaceSettings` does, or only those that change; `{}` runs the tests with the current
|
|
839
|
+
* settings. A change to what the index stores (new searchable, facet, filter, sort or location fields,
|
|
840
|
+
* or normalization) throws a 409: save it first. Not counted as searches. */
|
|
841
|
+
compareSettings: (settings: Partial<IndexSettings>, options?: CompareOptions) => Promise<SettingsComparison>;
|
|
842
|
+
/** The index's search tests. */
|
|
843
|
+
getSearchTests: (signal?: AbortSignal) => Promise<SearchTest[]>;
|
|
844
|
+
/** Replaces the index's search tests, up to 200; `[]` deletes them. */
|
|
845
|
+
setSearchTests: (tests: SearchTest[], signal?: AbortSignal) => Promise<SearchTest[]>;
|
|
548
846
|
/** Waits until no settings rebuild is running; throws if it failed. */
|
|
549
847
|
waitForSettings: (options?: {
|
|
550
848
|
timeoutMs?: number;
|
package/dist/index.js
CHANGED
|
@@ -21,6 +21,16 @@ class StagedReplacementError extends Error {
|
|
|
21
21
|
this.name = "StagedReplacementError";
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
+
function analyticsQuery(options) {
|
|
25
|
+
const params = new URLSearchParams;
|
|
26
|
+
for (const name of ["range", "from", "to", "interval", "source", "list", "limit"]) {
|
|
27
|
+
const value = options[name];
|
|
28
|
+
if (value !== undefined)
|
|
29
|
+
params.set(name, value instanceof Date ? value.toISOString() : String(value));
|
|
30
|
+
}
|
|
31
|
+
const query = params.toString();
|
|
32
|
+
return query ? `?${query}` : "";
|
|
33
|
+
}
|
|
24
34
|
var eventsPerRequest = 200;
|
|
25
35
|
var requestOptions = (value) => value === undefined ? {} : ("aborted" in value) ? { signal: value } : value;
|
|
26
36
|
var relayedBytes = 256 * 1024;
|
|
@@ -176,6 +186,7 @@ class SearchServerClient {
|
|
|
176
186
|
await flush();
|
|
177
187
|
return result;
|
|
178
188
|
};
|
|
189
|
+
const report = (name2, options) => retry(() => request(`/analytics/${name2}${analyticsQuery(options)}`, "GET", undefined, options.signal), options.signal);
|
|
179
190
|
const stagePath = (stageId) => `/staged-replacements/${encodeURIComponent(stageId)}`;
|
|
180
191
|
const beginReplacement = (stageId, signal) => retry(() => request(stagePath(stageId), "POST", undefined, signal), signal);
|
|
181
192
|
const uploadReplacementBatch = (stageId, batchId, records, signal) => retry(() => request(`${stagePath(stageId)}/batches/${encodeURIComponent(batchId)}`, "POST", { records }, signal), signal);
|
|
@@ -233,6 +244,15 @@ class SearchServerClient {
|
|
|
233
244
|
},
|
|
234
245
|
relay: (body, visitor, signal) => relayEvents(body, visitor, signal)
|
|
235
246
|
},
|
|
247
|
+
analytics: {
|
|
248
|
+
searches: (options = {}) => report("searches", options),
|
|
249
|
+
queries: (options = {}) => report("queries", options),
|
|
250
|
+
filters: (options = {}) => report("filters", options),
|
|
251
|
+
events: (options = {}) => report("events", options),
|
|
252
|
+
products: (options = {}) => report("products", options),
|
|
253
|
+
productCounts: (options = {}) => report("product-counts", options),
|
|
254
|
+
product: (objectID, options = {}) => report(`products/${encodeURIComponent(objectID)}`, options)
|
|
255
|
+
},
|
|
236
256
|
upsert: (records, signal) => request("/records", "POST", { records }, signal),
|
|
237
257
|
getRecord: (objectID, signal) => request(`/records/${encodeURIComponent(objectID)}`, "GET", undefined, signal),
|
|
238
258
|
upsertOne: (record, options) => writeOne("upsert", record.objectID, record, options),
|
|
@@ -291,6 +311,16 @@ class SearchServerClient {
|
|
|
291
311
|
const { rebuild: _rebuild, rebuilt: _rebuilt, applied: _applied, ...body } = settings;
|
|
292
312
|
return request("/config", "PUT", body, signal);
|
|
293
313
|
},
|
|
314
|
+
compareSettings: (settings, options = {}) => {
|
|
315
|
+
const { rebuild: _rebuild, rebuilt: _rebuilt, applied: _applied, ...body } = settings;
|
|
316
|
+
return request("/compare", "POST", {
|
|
317
|
+
settings: body,
|
|
318
|
+
...options.queries ? { queries: options.queries } : {},
|
|
319
|
+
...options.searched === false ? { searched: false } : {}
|
|
320
|
+
}, options.signal);
|
|
321
|
+
},
|
|
322
|
+
getSearchTests: async (signal) => (await request("/tests", "GET", undefined, signal)).tests,
|
|
323
|
+
setSearchTests: async (tests, signal) => (await request("/tests", "PUT", { tests }, signal)).tests,
|
|
294
324
|
waitForSettings: async (options = {}) => {
|
|
295
325
|
const deadline = Date.now() + (options.timeoutMs ?? 30 * 60000);
|
|
296
326
|
while (true) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@findlane/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Findlane server SDK: upload catalogs, manage index settings, and search with a secret workspace API key.",
|
|
5
5
|
"keywords": ["findlane", "search", "full-text search", "typo tolerance", "ecommerce search", "search api", "sdk"],
|
|
6
6
|
"license": "MIT",
|