@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 +39 -1
- package/dist/index.d.cts +200 -0
- package/dist/index.d.ts +200 -0
- package/dist/index.js +39 -1
- package/package.json +1 -1
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.
|
|
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",
|