@findlane/server 0.22.0 → 0.24.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,43 @@ class SearchServerClient {
265
265
  const { signal, visitor } = requestOptions(requestOptionsOrSignal);
266
266
  return request("/recommendations", "POST", { objectID, ...options }, signal, visitorHeaders(visitor));
267
267
  },
268
+ merchandising: {
269
+ list: async (signal) => (await request("/merchandising", "GET", undefined, signal)).arrangements,
270
+ get: (target, signal) => request(`/merchandising/arrangement?${new URLSearchParams(target)}`, "GET", undefined, signal),
271
+ set: (target, change, signal) => request("/merchandising/arrangement", "PUT", { ...target, ...change }, signal),
272
+ remove: (target, signal) => request(`/merchandising/arrangement?${new URLSearchParams(target)}`, "DELETE", undefined, signal)
273
+ },
274
+ images: {
275
+ list: (signal) => request("/images", "GET", undefined, signal),
276
+ upload: async (image, options = {}) => {
277
+ const response = await this.fetcher(`${this.baseUrl}${path}/images`, {
278
+ method: "POST",
279
+ signal: options.signal,
280
+ headers: {
281
+ authorization: `Bearer ${this.key}`,
282
+ "content-type": options.contentType ?? (image instanceof Blob && image.type ? image.type : "application/octet-stream")
283
+ },
284
+ body: image
285
+ });
286
+ const data = await response.json();
287
+ if (!response.ok)
288
+ throw new FindlaneError(response.status, data.error ?? `HTTP ${response.status}`);
289
+ return data;
290
+ },
291
+ uploadFromUrl: (url, signal) => request("/images", "POST", { url }, signal),
292
+ delete: (key, signal) => request(`/images/${key}`, "DELETE", undefined, signal)
293
+ },
294
+ feed: {
295
+ get: async (signal) => (await request("/feed", "GET", undefined, signal)).feed,
296
+ set: async (setup, signal) => (await request("/feed", "PUT", setup, signal)).feed,
297
+ remove: async (signal) => {
298
+ await request("/feed", "DELETE", undefined, signal);
299
+ },
300
+ check: (setup = {}, signal) => request("/feed/check", "POST", setup, signal),
301
+ run: async (signal) => (await request("/feed/runs", "POST", undefined, signal)).run,
302
+ runs: async (signal) => (await request("/feed/runs", "GET", undefined, signal)).runs,
303
+ importHeld: async (runId, signal) => (await request(`/feed/runs/${encodeURIComponent(runId)}/import`, "POST", undefined, signal)).run
304
+ },
268
305
  lists: {
269
306
  get: (id, options = {}) => request(`/lists/${encodeURIComponent(id)}${options.limit ? `?limit=${options.limit}` : ""}`, "GET", undefined, options.signal),
270
307
  getMany: async (ids, options = {}) => {
@@ -319,7 +356,8 @@ class SearchServerClient {
319
356
  products: (options = {}) => report("products", options),
320
357
  productCounts: (options = {}) => report("product-counts", options),
321
358
  product: (objectID, options = {}) => report(`products/${encodeURIComponent(objectID)}`, options),
322
- revenue: (options = {}) => report("revenue", options)
359
+ revenue: (options = {}) => report("revenue", options),
360
+ banners: (options = {}) => request(`/banners?range=${options.range ?? "30d"}`, "GET", undefined, options.signal)
323
361
  },
324
362
  upsert: (records, signal) => request("/records", "POST", { records }, signal),
325
363
  getRecord: (objectID, signal) => request(`/records/${encodeURIComponent(objectID)}`, "GET", undefined, signal),
package/dist/index.d.cts CHANGED
@@ -755,6 +755,148 @@ export type ServerEvent = {
755
755
  position?: number;
756
756
  timestamp?: number;
757
757
  };
758
+ /** A category page (a facet field's value, without search words), or a search's exact words. */
759
+ export type MerchandisingTarget = {
760
+ field: string;
761
+ value: string;
762
+ } | {
763
+ q: string;
764
+ };
765
+ export type MerchandisingBanner = {
766
+ image: string;
767
+ link?: string;
768
+ title?: string;
769
+ position?: number;
770
+ };
771
+ /** How a category page or search is arranged, as the dashboard's Merchandising page arranges it. */
772
+ export type Arrangement = {
773
+ target: MerchandisingTarget;
774
+ /** The rule that holds it; null when nothing is arranged. */
775
+ rule: string | null;
776
+ /** The products placed first, in order; null when the rule's pins have gaps, listed in `pins`. */
777
+ order: string[] | null;
778
+ pins: {
779
+ objectID: string;
780
+ position: number;
781
+ }[];
782
+ hidden: string[];
783
+ banner: MerchandisingBanner | null;
784
+ active: {
785
+ from?: string;
786
+ until?: string;
787
+ } | null;
788
+ enabled: boolean;
789
+ status: "live" | "scheduled" | "ended" | "off" | "none";
790
+ };
791
+ /** What to change: each field given replaces the current one; null clears the banner or the times. */
792
+ export type ArrangementChange = {
793
+ order?: string[];
794
+ hidden?: string[];
795
+ banner?: MerchandisingBanner | null;
796
+ active?: {
797
+ from?: string;
798
+ until?: string;
799
+ } | null;
800
+ enabled?: boolean;
801
+ };
802
+ /** An image uploaded for the index's banners, hosted by Findlane, and the rules showing it. */
803
+ export type UploadedImage = {
804
+ key: string;
805
+ url: string;
806
+ bytes: number;
807
+ contentType: string;
808
+ width: number | null;
809
+ height: number | null;
810
+ createdAt: string;
811
+ usedBy: string[];
812
+ };
813
+ export type ImageLibrary = {
814
+ images: UploadedImage[];
815
+ used: {
816
+ files: number;
817
+ bytes: number;
818
+ };
819
+ limits: {
820
+ fileBytes: number;
821
+ files: number;
822
+ bytes: number;
823
+ };
824
+ };
825
+ /** Each banner's shown and clicks by its rule; `analytics` says whether they could be read. */
826
+ export type BannerStatsReport = {
827
+ days: number;
828
+ banners: {
829
+ rule: string;
830
+ shown: number;
831
+ clicks: number;
832
+ }[];
833
+ analytics: "ok" | "off" | "unavailable";
834
+ };
835
+ export type FeedFormat = "xml" | "tsv" | "csv" | "json" | "jsonl";
836
+ /** One run of a product feed: unchanged when the file hadn't changed; held when it would have emptied the index,
837
+ * removed more than half its products, or refused a fifth of its items (`message` says which). */
838
+ export type FeedRun = {
839
+ id: string;
840
+ trigger: "schedule" | "manual" | "connect" | "held";
841
+ status: "running" | "unchanged" | "imported" | "held" | "failed";
842
+ startedAt: string;
843
+ finishedAt: string | null;
844
+ format: FeedFormat | null;
845
+ bytes: number | null;
846
+ items: number | null;
847
+ records: number | null;
848
+ added: number | null;
849
+ changed: number | null;
850
+ removed: number | null;
851
+ refused: number | null;
852
+ reasons: Record<string, number>;
853
+ refusals: {
854
+ id?: string;
855
+ reason: string;
856
+ }[];
857
+ message: string | null;
858
+ };
859
+ /** An index's product feed: where it's read from, when, and how its last run went. `password` says whether it has one. */
860
+ export type ProductFeed = {
861
+ url: string;
862
+ username: string | null;
863
+ password: boolean;
864
+ schedule: "hourly" | "6h" | "daily";
865
+ hour: number | null;
866
+ variants: "group" | "separate";
867
+ paused: boolean;
868
+ pausedReason: "owner" | "failing" | null;
869
+ failures: number;
870
+ running: boolean;
871
+ nextRunAt: string | null;
872
+ lastRun: FeedRun | null;
873
+ held: FeedRun | null;
874
+ };
875
+ /** A feed's setup: each field given replaces the current one; null clears the username or password. */
876
+ export type FeedSetup = {
877
+ url?: string;
878
+ username?: string | null;
879
+ password?: string | null;
880
+ schedule?: "hourly" | "6h" | "daily";
881
+ hour?: number;
882
+ variants?: "group" | "separate";
883
+ paused?: boolean;
884
+ };
885
+ /** What a feed's first 2,000 items hold: the records they make (the first five in `sample`), and refused items. */
886
+ export type FeedCheck = {
887
+ format: FeedFormat | null;
888
+ complete: boolean;
889
+ items: number;
890
+ records: number;
891
+ refused: number;
892
+ sample: Record<string, unknown>[];
893
+ fields: string[];
894
+ reasons: Record<string, number>;
895
+ refusals: {
896
+ id?: string;
897
+ reason: string;
898
+ }[];
899
+ };
758
900
  /** A past order for importOrders; `timestamp` is when it was placed (ms, a Date, or an ISO string). */
759
901
  export type Order = {
760
902
  orderID: string;
@@ -1048,6 +1190,58 @@ export declare class SearchServerClient {
1048
1190
  /** The index's lists: rows of products, such as Popular, and facet fields' values for menus, built ahead
1049
1191
  * and served from a cache, so reading them isn't counted as searches. Reading needs the `search` scope;
1050
1192
  * setting them up needs `write`. */
1193
+ /** Merchandising (0.23.0): category pages and searches arranged as the dashboard's Merchandising page does:
1194
+ * the products placed first, hidden products, a banner, and when. Saved as the page's rule within seconds,
1195
+ * keeping anything else the rule does. Reading needs the `search` or `write` scope; changing, `write`. */
1196
+ merchandising: {
1197
+ /** Every arranged category page and search. */
1198
+ list: (signal?: AbortSignal) => Promise<Arrangement[]>;
1199
+ /** One page's or search's arrangement; status "none" when nothing is arranged. */
1200
+ get: (target: MerchandisingTarget, signal?: AbortSignal) => Promise<Arrangement>;
1201
+ /** Changes one: `order` up to 20 objectIDs placed first, `hidden` up to 100, `banner` ({ image, link?, title?,
1202
+ * position? } or null), `active` ({ from?, until? } or null), `enabled`. Fields left out stay as they are. */
1203
+ set: (target: MerchandisingTarget, change: ArrangementChange, signal?: AbortSignal) => Promise<Arrangement>;
1204
+ /** Stops arranging one: its order, hidden products and banner go; the rule's other actions stay. */
1205
+ remove: (target: MerchandisingTarget, signal?: AbortSignal) => Promise<Arrangement>;
1206
+ };
1207
+ /** Images for the index's banners, hosted by Findlane at https://images.findlane.dev (0.23.0): JPEG, PNG,
1208
+ * WebP or AVIF, up to 2 MB each, 50 images and 100 MB an index. An image no rule shows is deleted a week
1209
+ * later. Uploading and deleting need the `write` scope. */
1210
+ images: {
1211
+ list: (signal?: AbortSignal) => Promise<ImageLibrary>;
1212
+ /** Uploads an image's bytes; the same image again gives the same URL. */
1213
+ upload: (image: Blob | ArrayBuffer | Uint8Array, options?: {
1214
+ contentType?: string;
1215
+ signal?: AbortSignal;
1216
+ }) => Promise<UploadedImage & {
1217
+ error?: string;
1218
+ }>;
1219
+ /** Uploads the image at an https address, which Findlane fetches. */
1220
+ uploadFromUrl: (url: string, signal?: AbortSignal) => Promise<UploadedImage>;
1221
+ /** Deletes an image by its key; refused (409) while a rule's banner shows it. */
1222
+ delete: (key: string, signal?: AbortSignal) => Promise<{
1223
+ deleted: true;
1224
+ }>;
1225
+ };
1226
+ /** The index's product feed (0.24.0): a store's Google or Meta product feed, or a JSON file of records, read on
1227
+ * a schedule (hourly, every 6 hours, or daily), replacing the index's records with only what changed. Reading
1228
+ * needs the `search` or `write` scope; the rest, `write`. */
1229
+ feed: {
1230
+ /** The feed and its last run, or null. */
1231
+ get: (signal?: AbortSignal) => Promise<ProductFeed | null>;
1232
+ /** Connects a feed or changes its setup; a new address, sign-in or variants setting reads it now. */
1233
+ set: (setup: FeedSetup, signal?: AbortSignal) => Promise<ProductFeed>;
1234
+ /** Disconnects it; the index keeps its records. */
1235
+ remove: (signal?: AbortSignal) => Promise<void>;
1236
+ /** Reads a feed's first 2,000 items without saving anything; without a url, the connected feed's. */
1237
+ check: (setup?: Pick<FeedSetup, "url" | "username" | "password" | "variants">, signal?: AbortSignal) => Promise<FeedCheck>;
1238
+ /** Reads it now, in full, in the background; `runs` shows how it went. */
1239
+ run: (signal?: AbortSignal) => Promise<FeedRun>;
1240
+ /** The last 20 runs, newest first. */
1241
+ runs: (signal?: AbortSignal) => Promise<FeedRun[]>;
1242
+ /** Imports a held run's copy of the feed as it is, without the checks that held it. */
1243
+ importHeld: (runId: string, signal?: AbortSignal) => Promise<FeedRun>;
1244
+ };
1051
1245
  lists: {
1052
1246
  /** One list by its ID; throws a FindlaneError with status 404 when there's none. `limit` shows fewer. */
1053
1247
  get: <R = T>(id: string, options?: {
@@ -1136,6 +1330,12 @@ export declare class SearchServerClient {
1136
1330
  /** Revenue from search: orders and add-to-carts of products found through search, per query and per
1137
1331
  * product (0.20.0). */
1138
1332
  revenue: (options?: Omit<AnalyticsOptions, "source">) => Promise<RevenueReport>;
1333
+ /** Each banner's shown (shoppers' searches that showed it, cache answers included) and clicks, by its rule,
1334
+ * over the last 7, 30 (by default) or 90 days (0.23.0). */
1335
+ banners: (options?: {
1336
+ range?: "7d" | "30d" | "90d";
1337
+ signal?: AbortSignal;
1338
+ }) => Promise<BannerStatsReport>;
1139
1339
  };
1140
1340
  upsert: (records: T[], signal?: AbortSignal) => Promise<WriteReceipt>;
1141
1341
  getRecord: (objectID: string, signal?: AbortSignal) => Promise<RecordSnapshot<T>>;
package/dist/index.d.ts CHANGED
@@ -755,6 +755,148 @@ export type ServerEvent = {
755
755
  position?: number;
756
756
  timestamp?: number;
757
757
  };
758
+ /** A category page (a facet field's value, without search words), or a search's exact words. */
759
+ export type MerchandisingTarget = {
760
+ field: string;
761
+ value: string;
762
+ } | {
763
+ q: string;
764
+ };
765
+ export type MerchandisingBanner = {
766
+ image: string;
767
+ link?: string;
768
+ title?: string;
769
+ position?: number;
770
+ };
771
+ /** How a category page or search is arranged, as the dashboard's Merchandising page arranges it. */
772
+ export type Arrangement = {
773
+ target: MerchandisingTarget;
774
+ /** The rule that holds it; null when nothing is arranged. */
775
+ rule: string | null;
776
+ /** The products placed first, in order; null when the rule's pins have gaps, listed in `pins`. */
777
+ order: string[] | null;
778
+ pins: {
779
+ objectID: string;
780
+ position: number;
781
+ }[];
782
+ hidden: string[];
783
+ banner: MerchandisingBanner | null;
784
+ active: {
785
+ from?: string;
786
+ until?: string;
787
+ } | null;
788
+ enabled: boolean;
789
+ status: "live" | "scheduled" | "ended" | "off" | "none";
790
+ };
791
+ /** What to change: each field given replaces the current one; null clears the banner or the times. */
792
+ export type ArrangementChange = {
793
+ order?: string[];
794
+ hidden?: string[];
795
+ banner?: MerchandisingBanner | null;
796
+ active?: {
797
+ from?: string;
798
+ until?: string;
799
+ } | null;
800
+ enabled?: boolean;
801
+ };
802
+ /** An image uploaded for the index's banners, hosted by Findlane, and the rules showing it. */
803
+ export type UploadedImage = {
804
+ key: string;
805
+ url: string;
806
+ bytes: number;
807
+ contentType: string;
808
+ width: number | null;
809
+ height: number | null;
810
+ createdAt: string;
811
+ usedBy: string[];
812
+ };
813
+ export type ImageLibrary = {
814
+ images: UploadedImage[];
815
+ used: {
816
+ files: number;
817
+ bytes: number;
818
+ };
819
+ limits: {
820
+ fileBytes: number;
821
+ files: number;
822
+ bytes: number;
823
+ };
824
+ };
825
+ /** Each banner's shown and clicks by its rule; `analytics` says whether they could be read. */
826
+ export type BannerStatsReport = {
827
+ days: number;
828
+ banners: {
829
+ rule: string;
830
+ shown: number;
831
+ clicks: number;
832
+ }[];
833
+ analytics: "ok" | "off" | "unavailable";
834
+ };
835
+ export type FeedFormat = "xml" | "tsv" | "csv" | "json" | "jsonl";
836
+ /** One run of a product feed: unchanged when the file hadn't changed; held when it would have emptied the index,
837
+ * removed more than half its products, or refused a fifth of its items (`message` says which). */
838
+ export type FeedRun = {
839
+ id: string;
840
+ trigger: "schedule" | "manual" | "connect" | "held";
841
+ status: "running" | "unchanged" | "imported" | "held" | "failed";
842
+ startedAt: string;
843
+ finishedAt: string | null;
844
+ format: FeedFormat | null;
845
+ bytes: number | null;
846
+ items: number | null;
847
+ records: number | null;
848
+ added: number | null;
849
+ changed: number | null;
850
+ removed: number | null;
851
+ refused: number | null;
852
+ reasons: Record<string, number>;
853
+ refusals: {
854
+ id?: string;
855
+ reason: string;
856
+ }[];
857
+ message: string | null;
858
+ };
859
+ /** An index's product feed: where it's read from, when, and how its last run went. `password` says whether it has one. */
860
+ export type ProductFeed = {
861
+ url: string;
862
+ username: string | null;
863
+ password: boolean;
864
+ schedule: "hourly" | "6h" | "daily";
865
+ hour: number | null;
866
+ variants: "group" | "separate";
867
+ paused: boolean;
868
+ pausedReason: "owner" | "failing" | null;
869
+ failures: number;
870
+ running: boolean;
871
+ nextRunAt: string | null;
872
+ lastRun: FeedRun | null;
873
+ held: FeedRun | null;
874
+ };
875
+ /** A feed's setup: each field given replaces the current one; null clears the username or password. */
876
+ export type FeedSetup = {
877
+ url?: string;
878
+ username?: string | null;
879
+ password?: string | null;
880
+ schedule?: "hourly" | "6h" | "daily";
881
+ hour?: number;
882
+ variants?: "group" | "separate";
883
+ paused?: boolean;
884
+ };
885
+ /** What a feed's first 2,000 items hold: the records they make (the first five in `sample`), and refused items. */
886
+ export type FeedCheck = {
887
+ format: FeedFormat | null;
888
+ complete: boolean;
889
+ items: number;
890
+ records: number;
891
+ refused: number;
892
+ sample: Record<string, unknown>[];
893
+ fields: string[];
894
+ reasons: Record<string, number>;
895
+ refusals: {
896
+ id?: string;
897
+ reason: string;
898
+ }[];
899
+ };
758
900
  /** A past order for importOrders; `timestamp` is when it was placed (ms, a Date, or an ISO string). */
759
901
  export type Order = {
760
902
  orderID: string;
@@ -1048,6 +1190,58 @@ export declare class SearchServerClient {
1048
1190
  /** The index's lists: rows of products, such as Popular, and facet fields' values for menus, built ahead
1049
1191
  * and served from a cache, so reading them isn't counted as searches. Reading needs the `search` scope;
1050
1192
  * setting them up needs `write`. */
1193
+ /** Merchandising (0.23.0): category pages and searches arranged as the dashboard's Merchandising page does:
1194
+ * the products placed first, hidden products, a banner, and when. Saved as the page's rule within seconds,
1195
+ * keeping anything else the rule does. Reading needs the `search` or `write` scope; changing, `write`. */
1196
+ merchandising: {
1197
+ /** Every arranged category page and search. */
1198
+ list: (signal?: AbortSignal) => Promise<Arrangement[]>;
1199
+ /** One page's or search's arrangement; status "none" when nothing is arranged. */
1200
+ get: (target: MerchandisingTarget, signal?: AbortSignal) => Promise<Arrangement>;
1201
+ /** Changes one: `order` up to 20 objectIDs placed first, `hidden` up to 100, `banner` ({ image, link?, title?,
1202
+ * position? } or null), `active` ({ from?, until? } or null), `enabled`. Fields left out stay as they are. */
1203
+ set: (target: MerchandisingTarget, change: ArrangementChange, signal?: AbortSignal) => Promise<Arrangement>;
1204
+ /** Stops arranging one: its order, hidden products and banner go; the rule's other actions stay. */
1205
+ remove: (target: MerchandisingTarget, signal?: AbortSignal) => Promise<Arrangement>;
1206
+ };
1207
+ /** Images for the index's banners, hosted by Findlane at https://images.findlane.dev (0.23.0): JPEG, PNG,
1208
+ * WebP or AVIF, up to 2 MB each, 50 images and 100 MB an index. An image no rule shows is deleted a week
1209
+ * later. Uploading and deleting need the `write` scope. */
1210
+ images: {
1211
+ list: (signal?: AbortSignal) => Promise<ImageLibrary>;
1212
+ /** Uploads an image's bytes; the same image again gives the same URL. */
1213
+ upload: (image: Blob | ArrayBuffer | Uint8Array, options?: {
1214
+ contentType?: string;
1215
+ signal?: AbortSignal;
1216
+ }) => Promise<UploadedImage & {
1217
+ error?: string;
1218
+ }>;
1219
+ /** Uploads the image at an https address, which Findlane fetches. */
1220
+ uploadFromUrl: (url: string, signal?: AbortSignal) => Promise<UploadedImage>;
1221
+ /** Deletes an image by its key; refused (409) while a rule's banner shows it. */
1222
+ delete: (key: string, signal?: AbortSignal) => Promise<{
1223
+ deleted: true;
1224
+ }>;
1225
+ };
1226
+ /** The index's product feed (0.24.0): a store's Google or Meta product feed, or a JSON file of records, read on
1227
+ * a schedule (hourly, every 6 hours, or daily), replacing the index's records with only what changed. Reading
1228
+ * needs the `search` or `write` scope; the rest, `write`. */
1229
+ feed: {
1230
+ /** The feed and its last run, or null. */
1231
+ get: (signal?: AbortSignal) => Promise<ProductFeed | null>;
1232
+ /** Connects a feed or changes its setup; a new address, sign-in or variants setting reads it now. */
1233
+ set: (setup: FeedSetup, signal?: AbortSignal) => Promise<ProductFeed>;
1234
+ /** Disconnects it; the index keeps its records. */
1235
+ remove: (signal?: AbortSignal) => Promise<void>;
1236
+ /** Reads a feed's first 2,000 items without saving anything; without a url, the connected feed's. */
1237
+ check: (setup?: Pick<FeedSetup, "url" | "username" | "password" | "variants">, signal?: AbortSignal) => Promise<FeedCheck>;
1238
+ /** Reads it now, in full, in the background; `runs` shows how it went. */
1239
+ run: (signal?: AbortSignal) => Promise<FeedRun>;
1240
+ /** The last 20 runs, newest first. */
1241
+ runs: (signal?: AbortSignal) => Promise<FeedRun[]>;
1242
+ /** Imports a held run's copy of the feed as it is, without the checks that held it. */
1243
+ importHeld: (runId: string, signal?: AbortSignal) => Promise<FeedRun>;
1244
+ };
1051
1245
  lists: {
1052
1246
  /** One list by its ID; throws a FindlaneError with status 404 when there's none. `limit` shows fewer. */
1053
1247
  get: <R = T>(id: string, options?: {
@@ -1136,6 +1330,12 @@ export declare class SearchServerClient {
1136
1330
  /** Revenue from search: orders and add-to-carts of products found through search, per query and per
1137
1331
  * product (0.20.0). */
1138
1332
  revenue: (options?: Omit<AnalyticsOptions, "source">) => Promise<RevenueReport>;
1333
+ /** Each banner's shown (shoppers' searches that showed it, cache answers included) and clicks, by its rule,
1334
+ * over the last 7, 30 (by default) or 90 days (0.23.0). */
1335
+ banners: (options?: {
1336
+ range?: "7d" | "30d" | "90d";
1337
+ signal?: AbortSignal;
1338
+ }) => Promise<BannerStatsReport>;
1139
1339
  };
1140
1340
  upsert: (records: T[], signal?: AbortSignal) => Promise<WriteReceipt>;
1141
1341
  getRecord: (objectID: string, signal?: AbortSignal) => Promise<RecordSnapshot<T>>;
package/dist/index.js CHANGED
@@ -219,6 +219,43 @@ class SearchServerClient {
219
219
  const { signal, visitor } = requestOptions(requestOptionsOrSignal);
220
220
  return request("/recommendations", "POST", { objectID, ...options }, signal, visitorHeaders(visitor));
221
221
  },
222
+ merchandising: {
223
+ list: async (signal) => (await request("/merchandising", "GET", undefined, signal)).arrangements,
224
+ get: (target, signal) => request(`/merchandising/arrangement?${new URLSearchParams(target)}`, "GET", undefined, signal),
225
+ set: (target, change, signal) => request("/merchandising/arrangement", "PUT", { ...target, ...change }, signal),
226
+ remove: (target, signal) => request(`/merchandising/arrangement?${new URLSearchParams(target)}`, "DELETE", undefined, signal)
227
+ },
228
+ images: {
229
+ list: (signal) => request("/images", "GET", undefined, signal),
230
+ upload: async (image, options = {}) => {
231
+ const response = await this.fetcher(`${this.baseUrl}${path}/images`, {
232
+ method: "POST",
233
+ signal: options.signal,
234
+ headers: {
235
+ authorization: `Bearer ${this.key}`,
236
+ "content-type": options.contentType ?? (image instanceof Blob && image.type ? image.type : "application/octet-stream")
237
+ },
238
+ body: image
239
+ });
240
+ const data = await response.json();
241
+ if (!response.ok)
242
+ throw new FindlaneError(response.status, data.error ?? `HTTP ${response.status}`);
243
+ return data;
244
+ },
245
+ uploadFromUrl: (url, signal) => request("/images", "POST", { url }, signal),
246
+ delete: (key, signal) => request(`/images/${key}`, "DELETE", undefined, signal)
247
+ },
248
+ feed: {
249
+ get: async (signal) => (await request("/feed", "GET", undefined, signal)).feed,
250
+ set: async (setup, signal) => (await request("/feed", "PUT", setup, signal)).feed,
251
+ remove: async (signal) => {
252
+ await request("/feed", "DELETE", undefined, signal);
253
+ },
254
+ check: (setup = {}, signal) => request("/feed/check", "POST", setup, signal),
255
+ run: async (signal) => (await request("/feed/runs", "POST", undefined, signal)).run,
256
+ runs: async (signal) => (await request("/feed/runs", "GET", undefined, signal)).runs,
257
+ importHeld: async (runId, signal) => (await request(`/feed/runs/${encodeURIComponent(runId)}/import`, "POST", undefined, signal)).run
258
+ },
222
259
  lists: {
223
260
  get: (id, options = {}) => request(`/lists/${encodeURIComponent(id)}${options.limit ? `?limit=${options.limit}` : ""}`, "GET", undefined, options.signal),
224
261
  getMany: async (ids, options = {}) => {
@@ -273,7 +310,8 @@ class SearchServerClient {
273
310
  products: (options = {}) => report("products", options),
274
311
  productCounts: (options = {}) => report("product-counts", options),
275
312
  product: (objectID, options = {}) => report(`products/${encodeURIComponent(objectID)}`, options),
276
- revenue: (options = {}) => report("revenue", options)
313
+ revenue: (options = {}) => report("revenue", options),
314
+ banners: (options = {}) => request(`/banners?range=${options.range ?? "30d"}`, "GET", undefined, options.signal)
277
315
  },
278
316
  upsert: (records, signal) => request("/records", "POST", { records }, signal),
279
317
  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.22.0",
3
+ "version": "0.24.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",