@misiki/gocommerce-connector 0.12.0 → 0.14.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/README.md +7 -4
- package/dist/{index-Bu4bVBBm.d.cts → index-C0CR05Q4.d.cts} +166 -70
- package/dist/{index-Bu4bVBBm.d.ts → index-C0CR05Q4.d.ts} +166 -70
- package/dist/index.cjs +109 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +109 -44
- package/dist/index.js.map +1 -1
- package/dist/services/index.cjs +108 -43
- package/dist/services/index.cjs.map +1 -1
- package/dist/services/index.d.cts +1 -1
- package/dist/services/index.d.ts +1 -1
- package/dist/services/index.js +108 -43
- package/dist/services/index.js.map +1 -1
- package/package.json +1 -1
package/dist/services/index.cjs
CHANGED
|
@@ -466,9 +466,27 @@ function productImages(p) {
|
|
|
466
466
|
if (url && !out.includes(url)) out.push(url);
|
|
467
467
|
};
|
|
468
468
|
push(mediaUrl(p.image_url));
|
|
469
|
+
for (const m of arr(p.media)) if (m.kind === "image") push(mediaUrl(m.url));
|
|
469
470
|
for (const v of arr(p.variants)) for (const url of variantImages(v)) push(url);
|
|
470
471
|
return out;
|
|
471
472
|
}
|
|
473
|
+
function productVideos(p) {
|
|
474
|
+
const out = [];
|
|
475
|
+
for (const m of arr(p.media)) {
|
|
476
|
+
const url = m.kind === "video" ? mediaUrl(m.url) : null;
|
|
477
|
+
if (url && !out.includes(url)) out.push(url);
|
|
478
|
+
}
|
|
479
|
+
return out;
|
|
480
|
+
}
|
|
481
|
+
function highlightsOf(p) {
|
|
482
|
+
const raw = p.metadata?.highlights;
|
|
483
|
+
if (!Array.isArray(raw)) return [];
|
|
484
|
+
return raw.filter((s) => typeof s === "string").map((s) => s.trim()).filter(Boolean);
|
|
485
|
+
}
|
|
486
|
+
function freeGiftOf(p) {
|
|
487
|
+
const raw = p.metadata?.free_gift;
|
|
488
|
+
return typeof raw === "string" && raw.trim() ? raw.trim() : null;
|
|
489
|
+
}
|
|
472
490
|
function toVariant(v, p, optionless) {
|
|
473
491
|
const images = variantImages(v);
|
|
474
492
|
const options = arr(p.options);
|
|
@@ -516,6 +534,7 @@ function toProduct(p, extra = {}) {
|
|
|
516
534
|
const lead = live[0] ?? arr(p.variants)[0];
|
|
517
535
|
const optionless = arr(p.options).length === 0;
|
|
518
536
|
const images = productImages(p);
|
|
537
|
+
const videos = productVideos(p);
|
|
519
538
|
const prices = live.map((v) => toMajor(v.price));
|
|
520
539
|
const tracked = live.filter((v) => v.track_inventory);
|
|
521
540
|
const category = p.category;
|
|
@@ -542,8 +561,14 @@ function toProduct(p, extra = {}) {
|
|
|
542
561
|
originCountry: lead?.origin_country ?? null,
|
|
543
562
|
thumbnail: images[0] ?? null,
|
|
544
563
|
image_url: images[0] ?? null,
|
|
545
|
-
images: images.join(","),
|
|
564
|
+
images: [...images, ...videos].join(","),
|
|
546
565
|
imageList: images,
|
|
566
|
+
videos,
|
|
567
|
+
// What the buy box says beside the price (D134): plain text, drawn as text.
|
|
568
|
+
highlights: highlightsOf(p),
|
|
569
|
+
freeGift: freeGiftOf(p),
|
|
570
|
+
// Counted by the engine, rounded down, and absent rather than zero.
|
|
571
|
+
soldAtLeast: typeof p.sold_at_least === "number" && p.sold_at_least > 0 ? p.sold_at_least : null,
|
|
547
572
|
// Product-level stock is what a card shows before a variant is picked:
|
|
548
573
|
// buyable when any live variant is, counted only where counting means
|
|
549
574
|
// something.
|
|
@@ -716,7 +741,9 @@ function toIdentityAddress(a) {
|
|
|
716
741
|
state: e.state ?? "",
|
|
717
742
|
postal_code: e.postal_code,
|
|
718
743
|
country: e.country,
|
|
719
|
-
|
|
744
|
+
// Sent whenever the form has it, empty included: the engine keeps a label it is not sent, so
|
|
745
|
+
// leaving out an emptied one would keep the old Home or Work (D134).
|
|
746
|
+
...typeof a.label === "string" ? { label: a.label } : {},
|
|
720
747
|
...a.isPrimary ? { is_default: true } : {}
|
|
721
748
|
};
|
|
722
749
|
}
|
|
@@ -1170,14 +1197,17 @@ var CatalogService = class extends BaseService {
|
|
|
1170
1197
|
const attr = Object.entries(q.attributes ?? {}).flatMap(
|
|
1171
1198
|
([handle, values]) => values.filter(Boolean).map((v) => `${handle}:${v}`)
|
|
1172
1199
|
);
|
|
1173
|
-
|
|
1200
|
+
const refined = attr.length > 0 || !!(q.tags?.length || q.vendors?.length || q.productTypes?.length);
|
|
1201
|
+
const triedIndex = !!search && !refined;
|
|
1202
|
+
const counted = q.facets && !legacyListing.has(this.engineBase());
|
|
1203
|
+
const facetListing = async (page2, limit2) => {
|
|
1174
1204
|
const { currency } = await this.facts.ready();
|
|
1175
1205
|
const minor = (n) => Number.isFinite(n) ? Math.max(0, toMinor(n, currency)) : void 0;
|
|
1176
|
-
|
|
1206
|
+
return this.engine("/api/products", {
|
|
1177
1207
|
query: {
|
|
1178
1208
|
q: search,
|
|
1179
|
-
page,
|
|
1180
|
-
limit,
|
|
1209
|
+
page: page2,
|
|
1210
|
+
limit: limit2,
|
|
1181
1211
|
channel: this.creds.channel,
|
|
1182
1212
|
category: q.category,
|
|
1183
1213
|
tag: q.tags,
|
|
@@ -1190,6 +1220,16 @@ var CatalogService = class extends BaseService {
|
|
|
1190
1220
|
facets: 1
|
|
1191
1221
|
}
|
|
1192
1222
|
});
|
|
1223
|
+
};
|
|
1224
|
+
if (triedIndex && await this.facts.pluginOn("meilisearch")) {
|
|
1225
|
+
const [found, rail] = await Promise.all([
|
|
1226
|
+
this.fromIndex(q, { page, limit, search, sortKey, wantsRange, hierarchy }),
|
|
1227
|
+
counted ? facetListing(1, 1).catch(() => null) : null
|
|
1228
|
+
]);
|
|
1229
|
+
if (found) return rail?.meta?.facets ? { ...found, facets: rail.meta.facets } : found;
|
|
1230
|
+
}
|
|
1231
|
+
if (counted) {
|
|
1232
|
+
const env2 = await facetListing(page, limit);
|
|
1193
1233
|
if (env2.meta?.facets) {
|
|
1194
1234
|
return {
|
|
1195
1235
|
...toPage(env2, (p) => toProduct(p)),
|
|
@@ -1200,39 +1240,9 @@ var CatalogService = class extends BaseService {
|
|
|
1200
1240
|
}
|
|
1201
1241
|
legacyListing.add(this.engineBase());
|
|
1202
1242
|
}
|
|
1203
|
-
if ((sortKey || wantsRange)
|
|
1204
|
-
const
|
|
1205
|
-
if (
|
|
1206
|
-
const names = (await this.facts.subtree(q.category).catch(() => [])).map(
|
|
1207
|
-
(c) => quote(c.full_name)
|
|
1208
|
-
);
|
|
1209
|
-
if (!names.length) return { ...emptyCatalog(limit), categoryHierarchy: hierarchy, sorted: true };
|
|
1210
|
-
filters.push(`category IN [${names.join(", ")}]`);
|
|
1211
|
-
}
|
|
1212
|
-
if (wantsRange) {
|
|
1213
|
-
const { currency } = await this.facts.ready();
|
|
1214
|
-
if (Number.isFinite(q.priceFrom)) filters.push(`price_minor >= ${toMinor(q.priceFrom, currency)}`);
|
|
1215
|
-
if (Number.isFinite(q.priceTo)) filters.push(`price_minor <= ${toMinor(q.priceTo, currency)}`);
|
|
1216
|
-
}
|
|
1217
|
-
const res = await this.data("/x/meilisearch/search", {
|
|
1218
|
-
query: {
|
|
1219
|
-
q: search ?? "",
|
|
1220
|
-
limit,
|
|
1221
|
-
offset: (page - 1) * limit,
|
|
1222
|
-
filter: filters.join(" AND ") || void 0,
|
|
1223
|
-
sort: sortKey
|
|
1224
|
-
}
|
|
1225
|
-
});
|
|
1226
|
-
const total = res.total ?? 0;
|
|
1227
|
-
return {
|
|
1228
|
-
data: (res.hits ?? []).map(fromSearchDoc),
|
|
1229
|
-
count: total,
|
|
1230
|
-
pageSize: limit,
|
|
1231
|
-
noOfPage: Math.max(1, Math.ceil(total / limit)),
|
|
1232
|
-
page,
|
|
1233
|
-
categoryHierarchy: hierarchy,
|
|
1234
|
-
sorted: true
|
|
1235
|
-
};
|
|
1243
|
+
if (!triedIndex && (sortKey || wantsRange)) {
|
|
1244
|
+
const found = await this.fromIndex(q, { page, limit, search, sortKey, wantsRange, hierarchy });
|
|
1245
|
+
if (found) return found;
|
|
1236
1246
|
}
|
|
1237
1247
|
const sorted = !sortKey && !wantsRange;
|
|
1238
1248
|
if (q.category && !search) {
|
|
@@ -1252,6 +1262,45 @@ var CatalogService = class extends BaseService {
|
|
|
1252
1262
|
});
|
|
1253
1263
|
return { ...toPage(env, (p) => toProduct(p)), categoryHierarchy: hierarchy, sorted };
|
|
1254
1264
|
}
|
|
1265
|
+
/**
|
|
1266
|
+
* The shelf from the Meilisearch module, or null when the store has it off,
|
|
1267
|
+
* sells through a channel, finds nothing for the words, or it cannot answer (Meilisearch down: the engine says `unavailable`), so
|
|
1268
|
+
* the caller asks the database instead of showing the shopper an error.
|
|
1269
|
+
*/
|
|
1270
|
+
async fromIndex(q, o) {
|
|
1271
|
+
if (this.creds.channel || !await this.facts.pluginOn("meilisearch")) return null;
|
|
1272
|
+
const filters = [];
|
|
1273
|
+
if (q.category) {
|
|
1274
|
+
const names = (await this.facts.subtree(q.category).catch(() => [])).map((c) => quote(c.full_name));
|
|
1275
|
+
if (!names.length) return { ...emptyCatalog(o.limit), categoryHierarchy: o.hierarchy, sorted: true };
|
|
1276
|
+
filters.push(`category IN [${names.join(", ")}]`);
|
|
1277
|
+
}
|
|
1278
|
+
if (o.wantsRange) {
|
|
1279
|
+
const { currency } = await this.facts.ready();
|
|
1280
|
+
if (Number.isFinite(q.priceFrom)) filters.push(`price_minor >= ${toMinor(q.priceFrom, currency)}`);
|
|
1281
|
+
if (Number.isFinite(q.priceTo)) filters.push(`price_minor <= ${toMinor(q.priceTo, currency)}`);
|
|
1282
|
+
}
|
|
1283
|
+
const res = await this.data("/x/meilisearch/search", {
|
|
1284
|
+
query: {
|
|
1285
|
+
q: o.search ?? "",
|
|
1286
|
+
limit: o.limit,
|
|
1287
|
+
offset: (o.page - 1) * o.limit,
|
|
1288
|
+
filter: filters.join(" AND ") || void 0,
|
|
1289
|
+
sort: o.sortKey
|
|
1290
|
+
}
|
|
1291
|
+
}).catch(() => null);
|
|
1292
|
+
if (!res || res.unavailable || o.search && !res.hits?.length) return null;
|
|
1293
|
+
const total = res.total ?? 0;
|
|
1294
|
+
return {
|
|
1295
|
+
data: (res.hits ?? []).map(fromSearchDoc),
|
|
1296
|
+
count: total,
|
|
1297
|
+
pageSize: o.limit,
|
|
1298
|
+
noOfPage: Math.max(1, Math.ceil(total / o.limit)),
|
|
1299
|
+
page: o.page,
|
|
1300
|
+
categoryHierarchy: o.hierarchy,
|
|
1301
|
+
sorted: true
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1255
1304
|
};
|
|
1256
1305
|
function emptyCatalog(limit = PAGE_SIZE) {
|
|
1257
1306
|
return { data: [], count: 0, pageSize: limit, noOfPage: 0, page: 1 };
|
|
@@ -1549,10 +1598,8 @@ var MeilisearchService = class extends CatalogService {
|
|
|
1549
1598
|
async searchAutoComplete({ query, search, limit = 8 } = {}) {
|
|
1550
1599
|
const q = (query ?? search ?? "").trim();
|
|
1551
1600
|
if (!q) return { data: [] };
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
return { data: (res.hits ?? []).map(fromSearchDoc) };
|
|
1555
|
-
}
|
|
1601
|
+
const found = await this.fromIndex({}, { page: 1, limit, search: q, wantsRange: false, hierarchy: [] });
|
|
1602
|
+
if (found) return { data: found.data };
|
|
1556
1603
|
const rows = await this.data("/api/products", { query: { q, limit, channel: this.creds.channel } }) ?? [];
|
|
1557
1604
|
return { data: rows.map((p) => toProduct(p)) };
|
|
1558
1605
|
}
|
|
@@ -2669,6 +2716,24 @@ var AddressService = class extends BaseService {
|
|
|
2669
2716
|
});
|
|
2670
2717
|
return fromIdentityAddress(a, currentMe()?.email);
|
|
2671
2718
|
}
|
|
2719
|
+
/**
|
|
2720
|
+
* The city and state for a postcode (D134), for the address form to fill
|
|
2721
|
+
* in. India's six-digit PIN codes only, from the directory the engine was
|
|
2722
|
+
* given; null for anything it cannot answer, so the form simply leaves the
|
|
2723
|
+
* fields to the shopper.
|
|
2724
|
+
*/
|
|
2725
|
+
async lookupPostcode({ country = "IN", postcode }) {
|
|
2726
|
+
const code = String(postcode ?? "").trim();
|
|
2727
|
+
const cc = String(country ?? "").trim().toUpperCase();
|
|
2728
|
+
if (cc !== "IN" || !/^[1-9][0-9]{5}$/.test(code)) return null;
|
|
2729
|
+
try {
|
|
2730
|
+
const p = await this.data(`/api/postcodes/${cc}/${code}`);
|
|
2731
|
+
return { country: p.country, postcode: p.postcode, city: p.city, state: p.state, stateCode: p.state_code };
|
|
2732
|
+
} catch (err) {
|
|
2733
|
+
if (err instanceof GoCommerceError && (err.status === 404 || err.status === 400)) return null;
|
|
2734
|
+
throw err;
|
|
2735
|
+
}
|
|
2736
|
+
}
|
|
2672
2737
|
async deleteAddress(id2) {
|
|
2673
2738
|
await this.engine(`/x/identity/me/addresses/${enc(id2)}`, { method: "DELETE", auth: true });
|
|
2674
2739
|
return { id: String(id2), deleted: true };
|