@findlane/server 0.16.0 → 0.17.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 CHANGED
@@ -265,6 +265,19 @@ class SearchServerClient {
265
265
  const { signal, visitor } = requestOptions(requestOptionsOrSignal);
266
266
  return request("/recommendations", "POST", { objectID, ...options }, signal, visitorHeaders(visitor));
267
267
  },
268
+ lists: {
269
+ get: (id, options = {}) => request(`/lists/${encodeURIComponent(id)}${options.limit ? `?limit=${options.limit}` : ""}`, "GET", undefined, options.signal),
270
+ getMany: async (ids, options = {}) => {
271
+ const query = new URLSearchParams({ ...ids ? { ids: ids.join(",") } : {}, ...options.limit ? { limit: String(options.limit) } : {} }).toString();
272
+ return (await request(`/lists${query ? `?${query}` : ""}`, "GET", undefined, options.signal)).lists;
273
+ },
274
+ settings: (signal) => request("/lists/settings", "GET", undefined, signal),
275
+ save: (id, list, signal) => request(`/lists/${encodeURIComponent(id)}`, "PUT", list, signal),
276
+ delete: (id, signal) => request(`/lists/${encodeURIComponent(id)}`, "DELETE", undefined, signal),
277
+ order: (ids, signal) => request("/lists/order", "PUT", { lists: ids }, signal),
278
+ refresh: (signal) => request("/lists/refresh", "POST", undefined, signal),
279
+ preview: (list, signal) => request("/lists/preview", "POST", list, signal)
280
+ },
268
281
  events: {
269
282
  send: (events, options) => sendEvents(events, options === undefined ? {} : ("aborted" in options) ? { signal: options } : options),
270
283
  importOrders: (orders, signal) => sendEvents(async function* () {
package/dist/index.d.cts CHANGED
@@ -421,6 +421,9 @@ export type SearchResponse<T> = {
421
421
  totalRelation?: "eq" | "gte";
422
422
  hasMore?: boolean;
423
423
  facets: Record<string, Record<string, number>>;
424
+ /** True when the facet counts are estimates: on an index of 100,000 records or more, a search with more
425
+ * than 2,000 matches counts its facets on a sample of them, scaled to the total. */
426
+ facetsEstimated?: boolean;
424
427
  /** With `facetStats`: each field's smallest and largest value across the matches, without the field's
425
428
  * own filter. A field no match has is left out. */
426
429
  facetStats?: Record<string, {
@@ -440,6 +443,75 @@ export type SearchResponse<T> = {
440
443
  rewrittenQuery?: string;
441
444
  };
442
445
  /** How a product's recommendations are served (the recommendations add-on). */
446
+ /** One of an index's lists (docs: Lists): a row of products, such as Popular or New arrivals, from a filter and
447
+ * a sort, with products pinned first and others hidden; or a facet field's values with how many products each
448
+ * has, for a menu. `updatedAt` is when what the index's lists hold last changed. */
449
+ export type ProductList<T = JsonRecord> = {
450
+ id: string;
451
+ title: string;
452
+ kind: "products";
453
+ hits: {
454
+ id: string;
455
+ record: T;
456
+ }[];
457
+ /** The list's filters and sort, for a "See all" link to the same products as a search. */
458
+ browse: {
459
+ sort: string;
460
+ filters: Record<string, unknown>;
461
+ };
462
+ updatedAt: string;
463
+ };
464
+ export type ValueList = {
465
+ id: string;
466
+ title: string;
467
+ kind: "values";
468
+ field: string;
469
+ values: {
470
+ value: string;
471
+ count: number;
472
+ }[];
473
+ browse: {
474
+ filters: Record<string, unknown>;
475
+ };
476
+ updatedAt: string;
477
+ };
478
+ export type List<T = JsonRecord> = ProductList<T> | ValueList;
479
+ /** A list as it's set up. Products (`source` "sort", the default): `sort` is one of the index's sorts (its
480
+ * default without one), `limit` 1 to 50 (10 by default), `pinned` and `hidden` objectIDs, and
481
+ * `attributesToRetrieve` the fields each product has. Values (`source` "values"): `field` is a facet field,
482
+ * `sort` "count" (the default) or "name", `limit` 1 to 1,000, and `pinned` and `hidden` values. `filters` are
483
+ * a search's, for either. */
484
+ export type ListDefinition = {
485
+ title?: string;
486
+ source?: "sort" | "values";
487
+ sort?: string;
488
+ filters?: SearchRequest["filters"];
489
+ limit?: number;
490
+ pinned?: string[];
491
+ hidden?: string[];
492
+ attributesToRetrieve?: string[];
493
+ field?: string;
494
+ };
495
+ /** An index's lists as set up, each with how many products or values it held at its last build and why that
496
+ * failed, if it did; the last build; and how many lists the plan allows per index. */
497
+ export type ListSettings = {
498
+ lists: Array<ListDefinition & {
499
+ id: string;
500
+ title: string;
501
+ source: "sort" | "values";
502
+ updatedAt: string;
503
+ items: number | null;
504
+ error: string | null;
505
+ }>;
506
+ build: {
507
+ version: number;
508
+ builtAt: string | null;
509
+ buildMs: number | null;
510
+ updatedAt: string | null;
511
+ refreshAvailableAt: string | null;
512
+ } | null;
513
+ cap: number;
514
+ };
443
515
  export type RecommendationsRequest = {
444
516
  /** Up to this many products in each list, 1 to 20; 8 by default. */
445
517
  limit?: number;
@@ -649,13 +721,15 @@ export type SearchesReport = AnalyticsReport & {
649
721
  noResults: number;
650
722
  }[];
651
723
  };
652
- /** A top query: its clicks on results and their average place (from 1), or null when clicks aren't available. */
724
+ /** A top query: its clicks on results, or null when clicks aren't available; their average place, and the
725
+ * middle click's, which a few clicks far down don't pull up (from 1, or null without clicks that say their place). */
653
726
  export type TopQuery = {
654
727
  query: string;
655
728
  searches: number;
656
729
  noResults: number;
657
730
  clicks: number | null;
658
731
  averagePosition: number | null;
732
+ middlePosition: number | null;
659
733
  };
660
734
  export type QueryCount = {
661
735
  query: string;
@@ -803,6 +877,36 @@ export declare class SearchServerClient {
803
877
  /** A product's recommendations (the recommendations add-on): products that go with it, and similar products.
804
878
  * The API key needs the `search` scope; not counted as a search. */
805
879
  recommendations: <R = T>(objectID: string, options?: RecommendationsRequest, requestOptionsOrSignal?: AbortSignal | RequestOptions) => Promise<RecommendationsResponse<R>>;
880
+ /** The index's lists: rows of products, such as Popular, and facet fields' values for menus, built ahead
881
+ * and served from a cache, so reading them isn't counted as searches. Reading needs the `search` scope;
882
+ * setting them up needs `write`. */
883
+ lists: {
884
+ /** One list by its ID; throws a FindlaneError with status 404 when there's none. `limit` shows fewer. */
885
+ get: <R = T>(id: string, options?: {
886
+ limit?: number;
887
+ signal?: AbortSignal;
888
+ }) => Promise<List<R>>;
889
+ /** Several lists (up to 10) in the order asked, leaving out IDs without a list; without IDs, all of them. */
890
+ getMany: <R = T>(ids?: string[], options?: {
891
+ limit?: number;
892
+ signal?: AbortSignal;
893
+ }) => Promise<List<R>[]>;
894
+ /** How the index's lists are set up, and their last build. */
895
+ settings: (signal?: AbortSignal) => Promise<ListSettings>;
896
+ /** Creates or changes a list and builds it; one whose filters, sort or field don't work on the index is
897
+ * refused with why (400), and a new list past the plan's lists per index too (402). */
898
+ save: (id: string, list: ListDefinition, signal?: AbortSignal) => Promise<ListSettings>;
899
+ delete: (id: string, signal?: AbortSignal) => Promise<ListSettings>;
900
+ /** Puts the lists in this order: every list's ID once. */
901
+ order: (ids: string[], signal?: AbortSignal) => Promise<ListSettings>;
902
+ /** Builds the lists again now, rather than within the minute they take to follow the index; once a minute. */
903
+ refresh: (signal?: AbortSignal) => Promise<ListSettings>;
904
+ /** What a list would hold, built without saving it. */
905
+ preview: <R = T>(list: ListDefinition, signal?: AbortSignal) => Promise<{
906
+ list: List<R>;
907
+ tookMs: number;
908
+ }>;
909
+ };
806
910
  /** Events for the index's Events page and Bought together; the API key needs the `events` or `write`
807
911
  * scope. Orders count once each, however often they're sent. */
808
912
  events: {
package/dist/index.d.ts CHANGED
@@ -421,6 +421,9 @@ export type SearchResponse<T> = {
421
421
  totalRelation?: "eq" | "gte";
422
422
  hasMore?: boolean;
423
423
  facets: Record<string, Record<string, number>>;
424
+ /** True when the facet counts are estimates: on an index of 100,000 records or more, a search with more
425
+ * than 2,000 matches counts its facets on a sample of them, scaled to the total. */
426
+ facetsEstimated?: boolean;
424
427
  /** With `facetStats`: each field's smallest and largest value across the matches, without the field's
425
428
  * own filter. A field no match has is left out. */
426
429
  facetStats?: Record<string, {
@@ -440,6 +443,75 @@ export type SearchResponse<T> = {
440
443
  rewrittenQuery?: string;
441
444
  };
442
445
  /** How a product's recommendations are served (the recommendations add-on). */
446
+ /** One of an index's lists (docs: Lists): a row of products, such as Popular or New arrivals, from a filter and
447
+ * a sort, with products pinned first and others hidden; or a facet field's values with how many products each
448
+ * has, for a menu. `updatedAt` is when what the index's lists hold last changed. */
449
+ export type ProductList<T = JsonRecord> = {
450
+ id: string;
451
+ title: string;
452
+ kind: "products";
453
+ hits: {
454
+ id: string;
455
+ record: T;
456
+ }[];
457
+ /** The list's filters and sort, for a "See all" link to the same products as a search. */
458
+ browse: {
459
+ sort: string;
460
+ filters: Record<string, unknown>;
461
+ };
462
+ updatedAt: string;
463
+ };
464
+ export type ValueList = {
465
+ id: string;
466
+ title: string;
467
+ kind: "values";
468
+ field: string;
469
+ values: {
470
+ value: string;
471
+ count: number;
472
+ }[];
473
+ browse: {
474
+ filters: Record<string, unknown>;
475
+ };
476
+ updatedAt: string;
477
+ };
478
+ export type List<T = JsonRecord> = ProductList<T> | ValueList;
479
+ /** A list as it's set up. Products (`source` "sort", the default): `sort` is one of the index's sorts (its
480
+ * default without one), `limit` 1 to 50 (10 by default), `pinned` and `hidden` objectIDs, and
481
+ * `attributesToRetrieve` the fields each product has. Values (`source` "values"): `field` is a facet field,
482
+ * `sort` "count" (the default) or "name", `limit` 1 to 1,000, and `pinned` and `hidden` values. `filters` are
483
+ * a search's, for either. */
484
+ export type ListDefinition = {
485
+ title?: string;
486
+ source?: "sort" | "values";
487
+ sort?: string;
488
+ filters?: SearchRequest["filters"];
489
+ limit?: number;
490
+ pinned?: string[];
491
+ hidden?: string[];
492
+ attributesToRetrieve?: string[];
493
+ field?: string;
494
+ };
495
+ /** An index's lists as set up, each with how many products or values it held at its last build and why that
496
+ * failed, if it did; the last build; and how many lists the plan allows per index. */
497
+ export type ListSettings = {
498
+ lists: Array<ListDefinition & {
499
+ id: string;
500
+ title: string;
501
+ source: "sort" | "values";
502
+ updatedAt: string;
503
+ items: number | null;
504
+ error: string | null;
505
+ }>;
506
+ build: {
507
+ version: number;
508
+ builtAt: string | null;
509
+ buildMs: number | null;
510
+ updatedAt: string | null;
511
+ refreshAvailableAt: string | null;
512
+ } | null;
513
+ cap: number;
514
+ };
443
515
  export type RecommendationsRequest = {
444
516
  /** Up to this many products in each list, 1 to 20; 8 by default. */
445
517
  limit?: number;
@@ -649,13 +721,15 @@ export type SearchesReport = AnalyticsReport & {
649
721
  noResults: number;
650
722
  }[];
651
723
  };
652
- /** A top query: its clicks on results and their average place (from 1), or null when clicks aren't available. */
724
+ /** A top query: its clicks on results, or null when clicks aren't available; their average place, and the
725
+ * middle click's, which a few clicks far down don't pull up (from 1, or null without clicks that say their place). */
653
726
  export type TopQuery = {
654
727
  query: string;
655
728
  searches: number;
656
729
  noResults: number;
657
730
  clicks: number | null;
658
731
  averagePosition: number | null;
732
+ middlePosition: number | null;
659
733
  };
660
734
  export type QueryCount = {
661
735
  query: string;
@@ -803,6 +877,36 @@ export declare class SearchServerClient {
803
877
  /** A product's recommendations (the recommendations add-on): products that go with it, and similar products.
804
878
  * The API key needs the `search` scope; not counted as a search. */
805
879
  recommendations: <R = T>(objectID: string, options?: RecommendationsRequest, requestOptionsOrSignal?: AbortSignal | RequestOptions) => Promise<RecommendationsResponse<R>>;
880
+ /** The index's lists: rows of products, such as Popular, and facet fields' values for menus, built ahead
881
+ * and served from a cache, so reading them isn't counted as searches. Reading needs the `search` scope;
882
+ * setting them up needs `write`. */
883
+ lists: {
884
+ /** One list by its ID; throws a FindlaneError with status 404 when there's none. `limit` shows fewer. */
885
+ get: <R = T>(id: string, options?: {
886
+ limit?: number;
887
+ signal?: AbortSignal;
888
+ }) => Promise<List<R>>;
889
+ /** Several lists (up to 10) in the order asked, leaving out IDs without a list; without IDs, all of them. */
890
+ getMany: <R = T>(ids?: string[], options?: {
891
+ limit?: number;
892
+ signal?: AbortSignal;
893
+ }) => Promise<List<R>[]>;
894
+ /** How the index's lists are set up, and their last build. */
895
+ settings: (signal?: AbortSignal) => Promise<ListSettings>;
896
+ /** Creates or changes a list and builds it; one whose filters, sort or field don't work on the index is
897
+ * refused with why (400), and a new list past the plan's lists per index too (402). */
898
+ save: (id: string, list: ListDefinition, signal?: AbortSignal) => Promise<ListSettings>;
899
+ delete: (id: string, signal?: AbortSignal) => Promise<ListSettings>;
900
+ /** Puts the lists in this order: every list's ID once. */
901
+ order: (ids: string[], signal?: AbortSignal) => Promise<ListSettings>;
902
+ /** Builds the lists again now, rather than within the minute they take to follow the index; once a minute. */
903
+ refresh: (signal?: AbortSignal) => Promise<ListSettings>;
904
+ /** What a list would hold, built without saving it. */
905
+ preview: <R = T>(list: ListDefinition, signal?: AbortSignal) => Promise<{
906
+ list: List<R>;
907
+ tookMs: number;
908
+ }>;
909
+ };
806
910
  /** Events for the index's Events page and Bought together; the API key needs the `events` or `write`
807
911
  * scope. Orders count once each, however often they're sent. */
808
912
  events: {
package/dist/index.js CHANGED
@@ -219,6 +219,19 @@ class SearchServerClient {
219
219
  const { signal, visitor } = requestOptions(requestOptionsOrSignal);
220
220
  return request("/recommendations", "POST", { objectID, ...options }, signal, visitorHeaders(visitor));
221
221
  },
222
+ lists: {
223
+ get: (id, options = {}) => request(`/lists/${encodeURIComponent(id)}${options.limit ? `?limit=${options.limit}` : ""}`, "GET", undefined, options.signal),
224
+ getMany: async (ids, options = {}) => {
225
+ const query = new URLSearchParams({ ...ids ? { ids: ids.join(",") } : {}, ...options.limit ? { limit: String(options.limit) } : {} }).toString();
226
+ return (await request(`/lists${query ? `?${query}` : ""}`, "GET", undefined, options.signal)).lists;
227
+ },
228
+ settings: (signal) => request("/lists/settings", "GET", undefined, signal),
229
+ save: (id, list, signal) => request(`/lists/${encodeURIComponent(id)}`, "PUT", list, signal),
230
+ delete: (id, signal) => request(`/lists/${encodeURIComponent(id)}`, "DELETE", undefined, signal),
231
+ order: (ids, signal) => request("/lists/order", "PUT", { lists: ids }, signal),
232
+ refresh: (signal) => request("/lists/refresh", "POST", undefined, signal),
233
+ preview: (list, signal) => request("/lists/preview", "POST", list, signal)
234
+ },
222
235
  events: {
223
236
  send: (events, options) => sendEvents(events, options === undefined ? {} : ("aborted" in options) ? { signal: options } : options),
224
237
  importOrders: (orders, signal) => sendEvents(async function* () {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@findlane/server",
3
- "version": "0.16.0",
3
+ "version": "0.17.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",