@findlane/server 0.17.0 → 0.19.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/dist/index.cjs +6 -6
- package/dist/index.d.cts +52 -1
- package/dist/index.d.ts +52 -1
- package/dist/index.js +6 -6
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -232,7 +232,7 @@ class SearchServerClient {
|
|
|
232
232
|
await flush();
|
|
233
233
|
return result;
|
|
234
234
|
};
|
|
235
|
-
const report = (
|
|
235
|
+
const report = (name, options) => retry(() => request(`/analytics/${name}${analyticsQuery(options)}`, "GET", undefined, options.signal), options.signal);
|
|
236
236
|
const stagePath = (stageId) => `/staged-replacements/${encodeURIComponent(stageId)}`;
|
|
237
237
|
const beginReplacement = (stageId, signal) => retry(() => request(stagePath(stageId), "POST", undefined, signal), signal);
|
|
238
238
|
const uploadReplacementBatch = (stageId, batchId, records, signal) => retry(() => request(`${stagePath(stageId)}/batches/${encodeURIComponent(batchId)}`, "POST", { records }, signal), signal);
|
|
@@ -295,16 +295,16 @@ class SearchServerClient {
|
|
|
295
295
|
forgotten += (await postEvents({ forget: ids.slice(start, start + 1000) }, signal)).forgotten ?? 0;
|
|
296
296
|
return { forgotten };
|
|
297
297
|
},
|
|
298
|
-
proxy: async (
|
|
299
|
-
if (
|
|
298
|
+
proxy: async (request, visitor = {}) => {
|
|
299
|
+
if (request.method !== "POST")
|
|
300
300
|
return jsonResponse(JSON.stringify({ error: "Only POST is allowed" }), 405, { allow: "POST" });
|
|
301
301
|
const tooLarge = () => jsonResponse(JSON.stringify({ error: "The request is too large" }), 413);
|
|
302
|
-
if (Number(
|
|
302
|
+
if (Number(request.headers.get("content-length") ?? 0) > relayedBytes)
|
|
303
303
|
return tooLarge();
|
|
304
|
-
const body = await
|
|
304
|
+
const body = await request.text();
|
|
305
305
|
if (new TextEncoder().encode(body).length > relayedBytes)
|
|
306
306
|
return tooLarge();
|
|
307
|
-
const found = { ...requestVisitor(
|
|
307
|
+
const found = { ...requestVisitor(request), ...visitor };
|
|
308
308
|
if (!found.ip)
|
|
309
309
|
throw new Error("No visitor IP address in the request's headers; pass it: events.proxy(request, { ip })");
|
|
310
310
|
return relayEvents(body, { ...found, ip: found.ip });
|
package/dist/index.d.cts
CHANGED
|
@@ -66,9 +66,29 @@ export type IndexSettings = {
|
|
|
66
66
|
/** Query suggestions: whole searches offered as shoppers type, and popular searches for an empty box,
|
|
67
67
|
* from what shoppers search and the catalog. On by default. Changes apply within a few minutes. */
|
|
68
68
|
querySuggestions?: QuerySuggestionSettings;
|
|
69
|
+
/** Related results (0.18.0): when a search's words find nothing, the products closest to it in meaning, in
|
|
70
|
+
* the response's `related`. Paid plans, indexes of up to 200,000 records. Turned on, every record is
|
|
71
|
+
* embedded within minutes, and `minScore`, the floor, is calibrated from the index's own searches that
|
|
72
|
+
* found nothing; none show until it is. Off by default. */
|
|
73
|
+
relatedResults?: {
|
|
74
|
+
enabled?: boolean;
|
|
75
|
+
minScore?: number | null;
|
|
76
|
+
};
|
|
69
77
|
normalizationProfile?: "default-v2" | "default-v1" | "icelandic-ascii-v1";
|
|
70
78
|
/** An object field of lat and lng numbers, such as `_geoloc`, that `near` and `within` search; null for none. */
|
|
71
79
|
locationField?: string | null;
|
|
80
|
+
/** Shows each value of a field once, such as a product whose colours or sizes are records sharing its ID:
|
|
81
|
+
* the record (or up to `limit`, 1 to 4) that best matches the search. A text or number field with one value;
|
|
82
|
+
* `total` then counts groups. Null for none. */
|
|
83
|
+
distinct?: {
|
|
84
|
+
field: string;
|
|
85
|
+
limit?: number;
|
|
86
|
+
} | null;
|
|
87
|
+
/** Variants listed inside each record, such as a product's flavours: a list of objects at `field`. Each hit
|
|
88
|
+
* then says which one the search matched (`variant`). Null for none. */
|
|
89
|
+
variants?: {
|
|
90
|
+
field: string;
|
|
91
|
+
} | null;
|
|
72
92
|
/** When saving: types for fields no record has yet, so settings can come before the records. Records'
|
|
73
93
|
* types win once they have the field. Not returned when reading settings. */
|
|
74
94
|
fieldTypes?: Record<string, "text" | "number" | "boolean" | "text[]" | "number[]" | "boolean[]" | "location">;
|
|
@@ -335,6 +355,11 @@ export type SearchRequest = {
|
|
|
335
355
|
/** Only records inside a box, in degrees, such as a map's visible area. West greater than east crosses the
|
|
336
356
|
* antimeridian. */
|
|
337
357
|
within?: GeoBox;
|
|
358
|
+
/** With the index's `relatedResults` on: false asks for no related results (0.18.0). */
|
|
359
|
+
related?: boolean;
|
|
360
|
+
/** With the index's `distinct` setting: false shows every record, a number 1 to 4 how many of each group's
|
|
361
|
+
* records. */
|
|
362
|
+
distinct?: boolean | number;
|
|
338
363
|
};
|
|
339
364
|
/** A point in degrees: lat from -90 to 90, lng from -180 to 180. */
|
|
340
365
|
export type GeoPoint = {
|
|
@@ -415,8 +440,23 @@ export type SearchResponse<T> = {
|
|
|
415
440
|
highlights?: Record<string, string | string[]>;
|
|
416
441
|
/** With `explain`: why the hit matched and what ordered it. */
|
|
417
442
|
explain?: HitExplanation;
|
|
443
|
+
/** With the index's `distinct` setting: the hit's group (its value of the group field, null for a record
|
|
444
|
+
* without one) and how many of the group's records matched. */
|
|
445
|
+
group?: {
|
|
446
|
+
value: string | number | null;
|
|
447
|
+
matched: number;
|
|
448
|
+
};
|
|
449
|
+
/** With the index's `variants` setting: which item of the record's variants list the search matched, by
|
|
450
|
+
* its words or by filters on the variants' fields, such as the flavour a shopper typed. Absent when none
|
|
451
|
+
* stands out. */
|
|
452
|
+
variant?: {
|
|
453
|
+
index: number;
|
|
454
|
+
};
|
|
418
455
|
}[];
|
|
456
|
+
/** The matches; with the index's `distinct` setting, the groups. */
|
|
419
457
|
total: number;
|
|
458
|
+
/** With `distinct`: the matching records, which `total` counts as groups. */
|
|
459
|
+
totalRecords?: number;
|
|
420
460
|
/** "gte" when `total` is a lower bound (page count mode). */
|
|
421
461
|
totalRelation?: "eq" | "gte";
|
|
422
462
|
hasMore?: boolean;
|
|
@@ -441,6 +481,17 @@ export type SearchResponse<T> = {
|
|
|
441
481
|
appliedRules?: AppliedRule[];
|
|
442
482
|
/** When a rule replaced words: the query that was searched. */
|
|
443
483
|
rewrittenQuery?: string;
|
|
484
|
+
/** With the index's `relatedResults` on (0.18.0): when the query's words found nothing (`hits` empty,
|
|
485
|
+
* `total` 0), the products closest to it in meaning, best first, with their similarity as `score`. Show
|
|
486
|
+
* them under a heading of their own, such as "No exact matches. Related products". */
|
|
487
|
+
related?: {
|
|
488
|
+
hits: Array<{
|
|
489
|
+
id: string;
|
|
490
|
+
score: number | null;
|
|
491
|
+
record: T;
|
|
492
|
+
}>;
|
|
493
|
+
total: number;
|
|
494
|
+
};
|
|
444
495
|
};
|
|
445
496
|
/** How a product's recommendations are served (the recommendations add-on). */
|
|
446
497
|
/** One of an index's lists (docs: Lists): a row of products, such as Popular or New arrivals, from a filter and
|
|
@@ -626,7 +677,7 @@ export type ServerEvent = {
|
|
|
626
677
|
objectID: string;
|
|
627
678
|
position?: number;
|
|
628
679
|
query?: string;
|
|
629
|
-
list?: "goesWith" | "similar" | "together";
|
|
680
|
+
list?: "goesWith" | "similar" | "together" | "related";
|
|
630
681
|
from?: string;
|
|
631
682
|
timestamp?: number;
|
|
632
683
|
} | {
|
package/dist/index.d.ts
CHANGED
|
@@ -66,9 +66,29 @@ export type IndexSettings = {
|
|
|
66
66
|
/** Query suggestions: whole searches offered as shoppers type, and popular searches for an empty box,
|
|
67
67
|
* from what shoppers search and the catalog. On by default. Changes apply within a few minutes. */
|
|
68
68
|
querySuggestions?: QuerySuggestionSettings;
|
|
69
|
+
/** Related results (0.18.0): when a search's words find nothing, the products closest to it in meaning, in
|
|
70
|
+
* the response's `related`. Paid plans, indexes of up to 200,000 records. Turned on, every record is
|
|
71
|
+
* embedded within minutes, and `minScore`, the floor, is calibrated from the index's own searches that
|
|
72
|
+
* found nothing; none show until it is. Off by default. */
|
|
73
|
+
relatedResults?: {
|
|
74
|
+
enabled?: boolean;
|
|
75
|
+
minScore?: number | null;
|
|
76
|
+
};
|
|
69
77
|
normalizationProfile?: "default-v2" | "default-v1" | "icelandic-ascii-v1";
|
|
70
78
|
/** An object field of lat and lng numbers, such as `_geoloc`, that `near` and `within` search; null for none. */
|
|
71
79
|
locationField?: string | null;
|
|
80
|
+
/** Shows each value of a field once, such as a product whose colours or sizes are records sharing its ID:
|
|
81
|
+
* the record (or up to `limit`, 1 to 4) that best matches the search. A text or number field with one value;
|
|
82
|
+
* `total` then counts groups. Null for none. */
|
|
83
|
+
distinct?: {
|
|
84
|
+
field: string;
|
|
85
|
+
limit?: number;
|
|
86
|
+
} | null;
|
|
87
|
+
/** Variants listed inside each record, such as a product's flavours: a list of objects at `field`. Each hit
|
|
88
|
+
* then says which one the search matched (`variant`). Null for none. */
|
|
89
|
+
variants?: {
|
|
90
|
+
field: string;
|
|
91
|
+
} | null;
|
|
72
92
|
/** When saving: types for fields no record has yet, so settings can come before the records. Records'
|
|
73
93
|
* types win once they have the field. Not returned when reading settings. */
|
|
74
94
|
fieldTypes?: Record<string, "text" | "number" | "boolean" | "text[]" | "number[]" | "boolean[]" | "location">;
|
|
@@ -335,6 +355,11 @@ export type SearchRequest = {
|
|
|
335
355
|
/** Only records inside a box, in degrees, such as a map's visible area. West greater than east crosses the
|
|
336
356
|
* antimeridian. */
|
|
337
357
|
within?: GeoBox;
|
|
358
|
+
/** With the index's `relatedResults` on: false asks for no related results (0.18.0). */
|
|
359
|
+
related?: boolean;
|
|
360
|
+
/** With the index's `distinct` setting: false shows every record, a number 1 to 4 how many of each group's
|
|
361
|
+
* records. */
|
|
362
|
+
distinct?: boolean | number;
|
|
338
363
|
};
|
|
339
364
|
/** A point in degrees: lat from -90 to 90, lng from -180 to 180. */
|
|
340
365
|
export type GeoPoint = {
|
|
@@ -415,8 +440,23 @@ export type SearchResponse<T> = {
|
|
|
415
440
|
highlights?: Record<string, string | string[]>;
|
|
416
441
|
/** With `explain`: why the hit matched and what ordered it. */
|
|
417
442
|
explain?: HitExplanation;
|
|
443
|
+
/** With the index's `distinct` setting: the hit's group (its value of the group field, null for a record
|
|
444
|
+
* without one) and how many of the group's records matched. */
|
|
445
|
+
group?: {
|
|
446
|
+
value: string | number | null;
|
|
447
|
+
matched: number;
|
|
448
|
+
};
|
|
449
|
+
/** With the index's `variants` setting: which item of the record's variants list the search matched, by
|
|
450
|
+
* its words or by filters on the variants' fields, such as the flavour a shopper typed. Absent when none
|
|
451
|
+
* stands out. */
|
|
452
|
+
variant?: {
|
|
453
|
+
index: number;
|
|
454
|
+
};
|
|
418
455
|
}[];
|
|
456
|
+
/** The matches; with the index's `distinct` setting, the groups. */
|
|
419
457
|
total: number;
|
|
458
|
+
/** With `distinct`: the matching records, which `total` counts as groups. */
|
|
459
|
+
totalRecords?: number;
|
|
420
460
|
/** "gte" when `total` is a lower bound (page count mode). */
|
|
421
461
|
totalRelation?: "eq" | "gte";
|
|
422
462
|
hasMore?: boolean;
|
|
@@ -441,6 +481,17 @@ export type SearchResponse<T> = {
|
|
|
441
481
|
appliedRules?: AppliedRule[];
|
|
442
482
|
/** When a rule replaced words: the query that was searched. */
|
|
443
483
|
rewrittenQuery?: string;
|
|
484
|
+
/** With the index's `relatedResults` on (0.18.0): when the query's words found nothing (`hits` empty,
|
|
485
|
+
* `total` 0), the products closest to it in meaning, best first, with their similarity as `score`. Show
|
|
486
|
+
* them under a heading of their own, such as "No exact matches. Related products". */
|
|
487
|
+
related?: {
|
|
488
|
+
hits: Array<{
|
|
489
|
+
id: string;
|
|
490
|
+
score: number | null;
|
|
491
|
+
record: T;
|
|
492
|
+
}>;
|
|
493
|
+
total: number;
|
|
494
|
+
};
|
|
444
495
|
};
|
|
445
496
|
/** How a product's recommendations are served (the recommendations add-on). */
|
|
446
497
|
/** One of an index's lists (docs: Lists): a row of products, such as Popular or New arrivals, from a filter and
|
|
@@ -626,7 +677,7 @@ export type ServerEvent = {
|
|
|
626
677
|
objectID: string;
|
|
627
678
|
position?: number;
|
|
628
679
|
query?: string;
|
|
629
|
-
list?: "goesWith" | "similar" | "together";
|
|
680
|
+
list?: "goesWith" | "similar" | "together" | "related";
|
|
630
681
|
from?: string;
|
|
631
682
|
timestamp?: number;
|
|
632
683
|
} | {
|
package/dist/index.js
CHANGED
|
@@ -186,7 +186,7 @@ class SearchServerClient {
|
|
|
186
186
|
await flush();
|
|
187
187
|
return result;
|
|
188
188
|
};
|
|
189
|
-
const report = (
|
|
189
|
+
const report = (name, options) => retry(() => request(`/analytics/${name}${analyticsQuery(options)}`, "GET", undefined, options.signal), options.signal);
|
|
190
190
|
const stagePath = (stageId) => `/staged-replacements/${encodeURIComponent(stageId)}`;
|
|
191
191
|
const beginReplacement = (stageId, signal) => retry(() => request(stagePath(stageId), "POST", undefined, signal), signal);
|
|
192
192
|
const uploadReplacementBatch = (stageId, batchId, records, signal) => retry(() => request(`${stagePath(stageId)}/batches/${encodeURIComponent(batchId)}`, "POST", { records }, signal), signal);
|
|
@@ -249,16 +249,16 @@ class SearchServerClient {
|
|
|
249
249
|
forgotten += (await postEvents({ forget: ids.slice(start, start + 1000) }, signal)).forgotten ?? 0;
|
|
250
250
|
return { forgotten };
|
|
251
251
|
},
|
|
252
|
-
proxy: async (
|
|
253
|
-
if (
|
|
252
|
+
proxy: async (request, visitor = {}) => {
|
|
253
|
+
if (request.method !== "POST")
|
|
254
254
|
return jsonResponse(JSON.stringify({ error: "Only POST is allowed" }), 405, { allow: "POST" });
|
|
255
255
|
const tooLarge = () => jsonResponse(JSON.stringify({ error: "The request is too large" }), 413);
|
|
256
|
-
if (Number(
|
|
256
|
+
if (Number(request.headers.get("content-length") ?? 0) > relayedBytes)
|
|
257
257
|
return tooLarge();
|
|
258
|
-
const body = await
|
|
258
|
+
const body = await request.text();
|
|
259
259
|
if (new TextEncoder().encode(body).length > relayedBytes)
|
|
260
260
|
return tooLarge();
|
|
261
|
-
const found = { ...requestVisitor(
|
|
261
|
+
const found = { ...requestVisitor(request), ...visitor };
|
|
262
262
|
if (!found.ip)
|
|
263
263
|
throw new Error("No visitor IP address in the request's headers; pass it: events.proxy(request, { ip })");
|
|
264
264
|
return relayEvents(body, { ...found, ip: found.ip });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@findlane/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.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",
|