@findlane/server 0.19.0 → 0.20.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
@@ -318,7 +318,8 @@ class SearchServerClient {
318
318
  events: (options = {}) => report("events", options),
319
319
  products: (options = {}) => report("products", options),
320
320
  productCounts: (options = {}) => report("product-counts", options),
321
- product: (objectID, options = {}) => report(`products/${encodeURIComponent(objectID)}`, options)
321
+ product: (objectID, options = {}) => report(`products/${encodeURIComponent(objectID)}`, options),
322
+ revenue: (options = {}) => report("revenue", options)
322
323
  },
323
324
  upsert: (records, signal) => request("/records", "POST", { records }, signal),
324
325
  getRecord: (objectID, signal) => request(`/records/${encodeURIComponent(objectID)}`, "GET", undefined, signal),
package/dist/index.d.cts CHANGED
@@ -121,9 +121,11 @@ export type QuerySuggestion = {
121
121
  q: string;
122
122
  typed?: [number, number];
123
123
  };
124
- /** When a query is, starts with, or contains one of a rule's phrases, the rule's `then` applies to that
125
- * search. Phrases are compared as synonyms are: without case, accents, or the index languages' stop words,
126
- * and with their plurals and other spellings, so a rule for "balloon" also fires for "balloons". */
124
+ /** When a search meets a rule's `if`, the rule's `then` applies to that search: its words (one of the
125
+ * phrases), its filters (such as a category page's), the contexts it sends, and whether it has words; only
126
+ * between the rule's `active` times when it has them. Phrases are compared as synonyms are: without case,
127
+ * accents, or the index languages' stop words, and with their plurals and other spellings, so a rule for
128
+ * "balloon" also fires for "balloons". */
127
129
  export type QueryRule = {
128
130
  /** 1 to 64 letters, digits, `_`, `.`, `:`, or `-`, unique in the index. */
129
131
  id: string;
@@ -131,25 +133,46 @@ export type QueryRule = {
131
133
  description?: string;
132
134
  /** false keeps the rule without applying it. Reading settings always returns it; true when left out. */
133
135
  enabled?: boolean;
136
+ /** Fires only from `from` and before `until`, ISO 8601 times with their zone, such as
137
+ * "2026-11-27T00:00:00Z"; either may be left out (0.20.0). */
138
+ active?: {
139
+ from?: string;
140
+ until?: string;
141
+ };
142
+ /** Every condition given must hold; with none, the rule fires on every search. */
134
143
  if: {
135
- /** 1 to 20 phrases of one to four words. */
136
- phrases: string[];
144
+ /** 1 to 20 phrases of one to four words. Reading settings returns [] for a rule without phrases. */
145
+ phrases?: string[];
137
146
  /** The query is one of the phrases, starts with one, or contains one; "contains" when left out. */
138
147
  match?: "is" | "startsWith" | "contains";
148
+ /** The search filters each field (1 to 10 filter fields) to one of these values, alone or in an `in`
149
+ * list, as a category page filters its category (0.20.0). */
150
+ filters?: Record<string, string | number | boolean | Array<string | number | boolean>>;
151
+ /** The search sends one of these contexts (its `context`), 1 to 10 names (0.20.0). */
152
+ context?: string[];
153
+ /** For a rule without phrases: only searches without words, such as a category page ("none"), or only
154
+ * with them ("some") (0.20.0). */
155
+ words?: "none" | "some";
139
156
  };
140
157
  /** One or more of these. `filters`, `boost` and `bury` take a search's filters, on 1 to 10 fields. */
141
158
  then: {
142
159
  /** Added to the search's filters, except on fields the search (or an earlier rule) already filters.
143
160
  * Records without the field drop out, so `boost` is usually the better choice. */
144
161
  filters?: SearchRequest["filters"];
145
- /** Records matching these come before the others, after `rankFirst`'s groups, in searches with a query
146
- * sorted by relevance. Several rules' boosts add up. */
162
+ /** Records matching these come before the others, after `rankFirst`'s groups, in searches sorted by
163
+ * relevance, with words or without (0.20.0), but not named sorts or distance. Several rules' boosts add up. */
147
164
  boost?: SearchRequest["filters"];
148
165
  /** Records matching these come after the others, in the same searches as `boost`. */
149
166
  bury?: SearchRequest["filters"];
150
167
  /** Up to 100 objectIDs left out of the results. */
151
168
  hide?: string[];
152
- /** Words searched instead of the matched phrase, up to 64 characters; "" removes the phrase. */
169
+ /** 1 to 20 records at places from 1, on every sort, when they pass the search's filters (not necessarily its
170
+ * words); a later rule's pin at a taken place moves down (0.20.0). */
171
+ pin?: {
172
+ objectID: string;
173
+ position: number;
174
+ }[];
175
+ /** With phrases: words searched instead of the matched phrase, up to 64 characters; "" removes the phrase. */
153
176
  replace?: string;
154
177
  /** JSON up to 4 KB returned with the results in `appliedRules`, such as a banner or a redirect. */
155
178
  userData?: unknown;
@@ -360,6 +383,12 @@ export type SearchRequest = {
360
383
  /** With the index's `distinct` setting: false shows every record, a number 1 to 4 how many of each group's
361
384
  * records. */
362
385
  distinct?: boolean | number;
386
+ /** Contexts the page sends, which rules with a `context` condition fire on, such as "category-page": a
387
+ * name or up to 10, each 1 to 64 letters, digits, `_`, `:` or `-` (0.20.0). */
388
+ context?: string | string[];
389
+ /** Checks rules as at this time, in milliseconds since 1970, such as a campaign's start, instead of now
390
+ * (0.20.0). Browser search doesn't take it. */
391
+ rulesAt?: number;
363
392
  };
364
393
  /** A point in degrees: lat from -90 to 90, lng from -180 to 180. */
365
394
  export type GeoPoint = {
@@ -452,6 +481,8 @@ export type SearchResponse<T> = {
452
481
  variant?: {
453
482
  index: number;
454
483
  };
484
+ /** A rule pinned it at its place (0.20.0). */
485
+ pinned?: true;
455
486
  }[];
456
487
  /** The matches; with the index's `distinct` setting, the groups. */
457
488
  total: number;
@@ -884,6 +915,65 @@ export type ProductReport = AnalyticsReport & {
884
915
  together: number;
885
916
  };
886
917
  };
918
+ /** A query's search clicks, and the add-to-carts and orders of the products clicked, in the main currency.
919
+ * `searches` is its searches when it's one of the 1,000 most searched, else null. `typed`: shorter queries
920
+ * typed on the way to it, such as "perfor", whose clicks count here. */
921
+ export type RevenueQuery = {
922
+ query: string;
923
+ searches: number | null;
924
+ clicks: number;
925
+ carts: number;
926
+ orders: number;
927
+ revenue: number;
928
+ typed: string[];
929
+ };
930
+ /** A product's orders and their value, beside those found through search, and the query it was found by most. */
931
+ export type RevenueProduct = {
932
+ objectID: string;
933
+ title: string;
934
+ image: string | null;
935
+ orders: number;
936
+ revenue: number;
937
+ searchOrders: number;
938
+ searchRevenue: number;
939
+ topQuery: string | null;
940
+ };
941
+ /** Revenue from search (0.20.0): purchases and add-to-carts of products the shopper clicked in search results in
942
+ * the 24 hours before, by the same visitor ID, so it needs the index's visitor IDs on and purchases sent as
943
+ * events. Each order counts once; amounts are in the main `currency` (the most orders'), at the prices the
944
+ * events carry, and other currencies' orders are listed apart. Exact, from every event kept, like `products`. */
945
+ export type RevenueReport = AnalyticsReport & {
946
+ computedAt: string;
947
+ currency: string | null;
948
+ orders: number;
949
+ revenue: number;
950
+ searchOrders: number;
951
+ searchRevenue: number;
952
+ carts: number;
953
+ cartValue: number;
954
+ searchCarts: number;
955
+ searchCartValue: number;
956
+ searchClicks: number;
957
+ otherCurrencies: {
958
+ currency: string;
959
+ orders: number;
960
+ revenue: number;
961
+ searchRevenue: number;
962
+ }[];
963
+ /** Orders and search clicks with visitor IDs, the first linked search click in the window, whether the
964
+ * index takes visitor IDs, and purchase lines without a price. */
965
+ coverage: {
966
+ linkedOrders: number;
967
+ linkedClicks: number;
968
+ linkedSince: string | null;
969
+ visitorIds: boolean;
970
+ unpricedLines: number;
971
+ };
972
+ /** The queries, the most revenue first, up to 100. */
973
+ queries: RevenueQuery[];
974
+ /** The products bought after a search, the most revenue from search first, up to 50. */
975
+ products: RevenueProduct[];
976
+ };
887
977
  export declare class FindlaneError extends Error {
888
978
  status: number;
889
979
  constructor(status: number, message: string);
@@ -1016,6 +1106,9 @@ export declare class SearchServerClient {
1016
1106
  product: (objectID: string, options?: AnalyticsOptions & {
1017
1107
  interval?: "hour" | "day";
1018
1108
  }) => Promise<ProductReport>;
1109
+ /** Revenue from search: orders and add-to-carts of products found through search, per query and per
1110
+ * product (0.20.0). */
1111
+ revenue: (options?: Omit<AnalyticsOptions, "source">) => Promise<RevenueReport>;
1019
1112
  };
1020
1113
  upsert: (records: T[], signal?: AbortSignal) => Promise<WriteReceipt>;
1021
1114
  getRecord: (objectID: string, signal?: AbortSignal) => Promise<RecordSnapshot<T>>;
package/dist/index.d.ts CHANGED
@@ -121,9 +121,11 @@ export type QuerySuggestion = {
121
121
  q: string;
122
122
  typed?: [number, number];
123
123
  };
124
- /** When a query is, starts with, or contains one of a rule's phrases, the rule's `then` applies to that
125
- * search. Phrases are compared as synonyms are: without case, accents, or the index languages' stop words,
126
- * and with their plurals and other spellings, so a rule for "balloon" also fires for "balloons". */
124
+ /** When a search meets a rule's `if`, the rule's `then` applies to that search: its words (one of the
125
+ * phrases), its filters (such as a category page's), the contexts it sends, and whether it has words; only
126
+ * between the rule's `active` times when it has them. Phrases are compared as synonyms are: without case,
127
+ * accents, or the index languages' stop words, and with their plurals and other spellings, so a rule for
128
+ * "balloon" also fires for "balloons". */
127
129
  export type QueryRule = {
128
130
  /** 1 to 64 letters, digits, `_`, `.`, `:`, or `-`, unique in the index. */
129
131
  id: string;
@@ -131,25 +133,46 @@ export type QueryRule = {
131
133
  description?: string;
132
134
  /** false keeps the rule without applying it. Reading settings always returns it; true when left out. */
133
135
  enabled?: boolean;
136
+ /** Fires only from `from` and before `until`, ISO 8601 times with their zone, such as
137
+ * "2026-11-27T00:00:00Z"; either may be left out (0.20.0). */
138
+ active?: {
139
+ from?: string;
140
+ until?: string;
141
+ };
142
+ /** Every condition given must hold; with none, the rule fires on every search. */
134
143
  if: {
135
- /** 1 to 20 phrases of one to four words. */
136
- phrases: string[];
144
+ /** 1 to 20 phrases of one to four words. Reading settings returns [] for a rule without phrases. */
145
+ phrases?: string[];
137
146
  /** The query is one of the phrases, starts with one, or contains one; "contains" when left out. */
138
147
  match?: "is" | "startsWith" | "contains";
148
+ /** The search filters each field (1 to 10 filter fields) to one of these values, alone or in an `in`
149
+ * list, as a category page filters its category (0.20.0). */
150
+ filters?: Record<string, string | number | boolean | Array<string | number | boolean>>;
151
+ /** The search sends one of these contexts (its `context`), 1 to 10 names (0.20.0). */
152
+ context?: string[];
153
+ /** For a rule without phrases: only searches without words, such as a category page ("none"), or only
154
+ * with them ("some") (0.20.0). */
155
+ words?: "none" | "some";
139
156
  };
140
157
  /** One or more of these. `filters`, `boost` and `bury` take a search's filters, on 1 to 10 fields. */
141
158
  then: {
142
159
  /** Added to the search's filters, except on fields the search (or an earlier rule) already filters.
143
160
  * Records without the field drop out, so `boost` is usually the better choice. */
144
161
  filters?: SearchRequest["filters"];
145
- /** Records matching these come before the others, after `rankFirst`'s groups, in searches with a query
146
- * sorted by relevance. Several rules' boosts add up. */
162
+ /** Records matching these come before the others, after `rankFirst`'s groups, in searches sorted by
163
+ * relevance, with words or without (0.20.0), but not named sorts or distance. Several rules' boosts add up. */
147
164
  boost?: SearchRequest["filters"];
148
165
  /** Records matching these come after the others, in the same searches as `boost`. */
149
166
  bury?: SearchRequest["filters"];
150
167
  /** Up to 100 objectIDs left out of the results. */
151
168
  hide?: string[];
152
- /** Words searched instead of the matched phrase, up to 64 characters; "" removes the phrase. */
169
+ /** 1 to 20 records at places from 1, on every sort, when they pass the search's filters (not necessarily its
170
+ * words); a later rule's pin at a taken place moves down (0.20.0). */
171
+ pin?: {
172
+ objectID: string;
173
+ position: number;
174
+ }[];
175
+ /** With phrases: words searched instead of the matched phrase, up to 64 characters; "" removes the phrase. */
153
176
  replace?: string;
154
177
  /** JSON up to 4 KB returned with the results in `appliedRules`, such as a banner or a redirect. */
155
178
  userData?: unknown;
@@ -360,6 +383,12 @@ export type SearchRequest = {
360
383
  /** With the index's `distinct` setting: false shows every record, a number 1 to 4 how many of each group's
361
384
  * records. */
362
385
  distinct?: boolean | number;
386
+ /** Contexts the page sends, which rules with a `context` condition fire on, such as "category-page": a
387
+ * name or up to 10, each 1 to 64 letters, digits, `_`, `:` or `-` (0.20.0). */
388
+ context?: string | string[];
389
+ /** Checks rules as at this time, in milliseconds since 1970, such as a campaign's start, instead of now
390
+ * (0.20.0). Browser search doesn't take it. */
391
+ rulesAt?: number;
363
392
  };
364
393
  /** A point in degrees: lat from -90 to 90, lng from -180 to 180. */
365
394
  export type GeoPoint = {
@@ -452,6 +481,8 @@ export type SearchResponse<T> = {
452
481
  variant?: {
453
482
  index: number;
454
483
  };
484
+ /** A rule pinned it at its place (0.20.0). */
485
+ pinned?: true;
455
486
  }[];
456
487
  /** The matches; with the index's `distinct` setting, the groups. */
457
488
  total: number;
@@ -884,6 +915,65 @@ export type ProductReport = AnalyticsReport & {
884
915
  together: number;
885
916
  };
886
917
  };
918
+ /** A query's search clicks, and the add-to-carts and orders of the products clicked, in the main currency.
919
+ * `searches` is its searches when it's one of the 1,000 most searched, else null. `typed`: shorter queries
920
+ * typed on the way to it, such as "perfor", whose clicks count here. */
921
+ export type RevenueQuery = {
922
+ query: string;
923
+ searches: number | null;
924
+ clicks: number;
925
+ carts: number;
926
+ orders: number;
927
+ revenue: number;
928
+ typed: string[];
929
+ };
930
+ /** A product's orders and their value, beside those found through search, and the query it was found by most. */
931
+ export type RevenueProduct = {
932
+ objectID: string;
933
+ title: string;
934
+ image: string | null;
935
+ orders: number;
936
+ revenue: number;
937
+ searchOrders: number;
938
+ searchRevenue: number;
939
+ topQuery: string | null;
940
+ };
941
+ /** Revenue from search (0.20.0): purchases and add-to-carts of products the shopper clicked in search results in
942
+ * the 24 hours before, by the same visitor ID, so it needs the index's visitor IDs on and purchases sent as
943
+ * events. Each order counts once; amounts are in the main `currency` (the most orders'), at the prices the
944
+ * events carry, and other currencies' orders are listed apart. Exact, from every event kept, like `products`. */
945
+ export type RevenueReport = AnalyticsReport & {
946
+ computedAt: string;
947
+ currency: string | null;
948
+ orders: number;
949
+ revenue: number;
950
+ searchOrders: number;
951
+ searchRevenue: number;
952
+ carts: number;
953
+ cartValue: number;
954
+ searchCarts: number;
955
+ searchCartValue: number;
956
+ searchClicks: number;
957
+ otherCurrencies: {
958
+ currency: string;
959
+ orders: number;
960
+ revenue: number;
961
+ searchRevenue: number;
962
+ }[];
963
+ /** Orders and search clicks with visitor IDs, the first linked search click in the window, whether the
964
+ * index takes visitor IDs, and purchase lines without a price. */
965
+ coverage: {
966
+ linkedOrders: number;
967
+ linkedClicks: number;
968
+ linkedSince: string | null;
969
+ visitorIds: boolean;
970
+ unpricedLines: number;
971
+ };
972
+ /** The queries, the most revenue first, up to 100. */
973
+ queries: RevenueQuery[];
974
+ /** The products bought after a search, the most revenue from search first, up to 50. */
975
+ products: RevenueProduct[];
976
+ };
887
977
  export declare class FindlaneError extends Error {
888
978
  status: number;
889
979
  constructor(status: number, message: string);
@@ -1016,6 +1106,9 @@ export declare class SearchServerClient {
1016
1106
  product: (objectID: string, options?: AnalyticsOptions & {
1017
1107
  interval?: "hour" | "day";
1018
1108
  }) => Promise<ProductReport>;
1109
+ /** Revenue from search: orders and add-to-carts of products found through search, per query and per
1110
+ * product (0.20.0). */
1111
+ revenue: (options?: Omit<AnalyticsOptions, "source">) => Promise<RevenueReport>;
1019
1112
  };
1020
1113
  upsert: (records: T[], signal?: AbortSignal) => Promise<WriteReceipt>;
1021
1114
  getRecord: (objectID: string, signal?: AbortSignal) => Promise<RecordSnapshot<T>>;
package/dist/index.js CHANGED
@@ -272,7 +272,8 @@ class SearchServerClient {
272
272
  events: (options = {}) => report("events", options),
273
273
  products: (options = {}) => report("products", options),
274
274
  productCounts: (options = {}) => report("product-counts", options),
275
- product: (objectID, options = {}) => report(`products/${encodeURIComponent(objectID)}`, options)
275
+ product: (objectID, options = {}) => report(`products/${encodeURIComponent(objectID)}`, options),
276
+ revenue: (options = {}) => report("revenue", options)
276
277
  },
277
278
  upsert: (records, signal) => request("/records", "POST", { records }, signal),
278
279
  getRecord: (objectID, signal) => request(`/records/${encodeURIComponent(objectID)}`, "GET", undefined, signal),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@findlane/server",
3
- "version": "0.19.0",
3
+ "version": "0.20.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",