@occluze/tether 0.6.8 → 0.7.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 +11 -11
- package/dist/esm/astro.js +2 -2
- package/dist/esm/chunks/account-UIPWJQ3Y.js +1 -0
- package/dist/esm/chunks/account-pages-6ROA6Y4V.js +1 -0
- package/dist/esm/chunks/analytics-2V632IRV.js +1 -0
- package/dist/esm/chunks/chunk-4GCXM764.js +1 -0
- package/dist/esm/chunks/chunk-56FCLJJD.js +1 -0
- package/dist/esm/chunks/chunk-5Z5O3767.js +1 -0
- package/dist/esm/chunks/chunk-6OZJCAGZ.js +1 -0
- package/dist/esm/chunks/chunk-A5SYCVQM.js +1 -0
- package/dist/esm/chunks/chunk-BA4LQJJQ.js +1 -0
- package/dist/esm/chunks/chunk-BFTZR4OB.js +4 -0
- package/dist/esm/chunks/chunk-C5JYQCWH.js +1 -0
- package/dist/esm/chunks/chunk-E5YO6274.js +1 -0
- package/dist/esm/chunks/chunk-MPDWM2NN.js +1 -0
- package/dist/esm/chunks/chunk-T44MUL4Y.js +1 -0
- package/dist/esm/chunks/chunk-YLUULATY.js +1 -0
- package/dist/esm/chunks/diagnose-3FEKRIEK.js +14 -0
- package/dist/esm/chunks/extras-6POFBNOP.js +1 -0
- package/dist/esm/chunks/markets-ui-FD6NI2OG.js +1 -0
- package/dist/esm/chunks/search-UJ73YBQ5.js +1 -0
- package/dist/esm/index.js +1 -1
- package/dist/esm/server.js +2 -2
- package/dist/presets/barba.esm.js +19 -23
- package/dist/tether.account-pages.js +320 -365
- package/dist/tether.account-pages.min.js +1 -1
- package/dist/tether.account.js +242 -491
- package/dist/tether.account.min.js +1 -1
- package/dist/tether.analytics.js +66 -88
- package/dist/tether.analytics.min.js +1 -1
- package/dist/tether.diagnose.js +36 -33
- package/dist/tether.diagnose.min.js +2 -2
- package/dist/tether.extras.js +28 -36
- package/dist/tether.extras.min.js +1 -1
- package/dist/tether.js +1270 -894
- package/dist/tether.markets.js +73 -90
- package/dist/tether.markets.min.js +1 -1
- package/dist/tether.min.js +0 -0
- package/dist/tether.min.js.map +4 -4
- package/dist/tether.search.js +44 -68
- package/dist/tether.search.min.js +1 -1
- package/dist/types/server/src/shared/account-orders.d.ts +44 -0
- package/dist/types/server/src/shared/account-routes.d.ts +153 -0
- package/dist/types/server/src/shared/api-versions.d.ts +13 -0
- package/dist/types/server/src/shared/error-message.d.ts +2 -0
- package/dist/types/server/src/shared/json.d.ts +25 -0
- package/dist/types/server/src/shared/market-codes.d.ts +16 -0
- package/dist/types/src/account/addresses-view.d.ts +5 -0
- package/dist/types/src/account/answers.d.ts +40 -0
- package/dist/types/src/account/markup.d.ts +19 -0
- package/dist/types/src/account/operations.d.ts +41 -0
- package/dist/types/src/account/order-lines.d.ts +13 -0
- package/dist/types/src/account/order-list.d.ts +12 -0
- package/dist/types/src/account/order-page.d.ts +6 -0
- package/dist/types/src/account/page-answers.d.ts +182 -0
- package/dist/types/src/account/page-events.d.ts +2 -0
- package/dist/types/src/account/server-client.d.ts +60 -0
- package/dist/types/src/account/subscriptions-view.d.ts +2 -0
- package/dist/types/src/account-api.d.ts +18 -0
- package/dist/types/src/account-marks.d.ts +15 -0
- package/dist/types/src/account-pages.d.ts +8 -0
- package/dist/types/src/account.d.ts +64 -0
- package/dist/types/src/amounts.d.ts +12 -0
- package/dist/types/src/analytics-shopify.d.ts +41 -0
- package/dist/types/src/analytics.d.ts +2 -0
- package/dist/types/src/api-version.d.ts +28 -0
- package/dist/types/src/astro.d.ts +42 -0
- package/dist/types/src/cart/cart-shape.d.ts +43 -0
- package/dist/types/src/cart/checkout.d.ts +16 -0
- package/dist/types/src/cart/drawer.d.ts +18 -0
- package/dist/types/src/cart/mutations.d.ts +79 -0
- package/dist/types/src/cart/render.d.ts +17 -0
- package/dist/types/src/cart/store.d.ts +182 -0
- package/dist/types/src/cart.d.ts +106 -0
- package/dist/types/src/client.d.ts +100 -0
- package/dist/types/src/collection.d.ts +25 -0
- package/dist/types/src/config-sections.d.ts +40 -0
- package/dist/types/src/config.d.ts +185 -0
- package/dist/types/src/controls/cart-controls.d.ts +2 -0
- package/dist/types/src/controls/discount.d.ts +2 -0
- package/dist/types/src/controls/extras.d.ts +5 -0
- package/dist/types/src/controls/navigation.d.ts +12 -0
- package/dist/types/src/diagnose-markup.d.ts +19 -0
- package/dist/types/src/diagnose.d.ts +60 -0
- package/dist/types/src/dom.d.ts +75 -0
- package/dist/types/src/extras.d.ts +21 -0
- package/dist/types/src/fragments.d.ts +158 -0
- package/dist/types/src/globals.d.ts +15 -0
- package/dist/types/src/images.d.ts +20 -0
- package/dist/types/src/index.d.ts +167 -0
- package/dist/types/src/initial.d.ts +56 -0
- package/dist/types/src/lazy.d.ts +28 -0
- package/dist/types/src/loader.d.ts +84 -0
- package/dist/types/src/market-codes.d.ts +6 -0
- package/dist/types/src/markets-ui.d.ts +32 -0
- package/dist/types/src/markets.d.ts +72 -0
- package/dist/types/src/markup.d.ts +131 -0
- package/dist/types/src/mounting.d.ts +46 -0
- package/dist/types/src/page-market.d.ts +27 -0
- package/dist/types/src/presets/barba.d.ts +56 -0
- package/dist/types/src/product-events.d.ts +2 -0
- package/dist/types/src/product-render.d.ts +17 -0
- package/dist/types/src/product-shape.d.ts +88 -0
- package/dist/types/src/product-state.d.ts +110 -0
- package/dist/types/src/product.d.ts +66 -0
- package/dist/types/src/repeaters.d.ts +16 -0
- package/dist/types/src/search.d.ts +33 -0
- package/dist/types/src/selling-plans.d.ts +64 -0
- package/dist/types/src/server.d.ts +88 -0
- package/dist/types/src/shopify-values.d.ts +46 -0
- package/dist/types/src/status.d.ts +33 -0
- package/dist/types/src/storage.d.ts +12 -0
- package/dist/types/src/store-html.d.ts +39 -0
- package/dist/types/src/storefront/catalog.d.ts +38 -0
- package/dist/types/src/storefront/json-ld.d.ts +20 -0
- package/dist/types/src/variant-render.d.ts +14 -0
- package/package.json +24 -23
- package/src/account/addresses-view.ts +58 -0
- package/src/account/answers.ts +79 -0
- package/src/account/markup.ts +36 -0
- package/src/account/operations.ts +145 -0
- package/src/account/{order-lines.js → order-lines.ts} +20 -41
- package/src/account/order-list.ts +107 -0
- package/src/account/order-page.ts +137 -0
- package/src/account/page-answers.ts +317 -0
- package/src/account/{page-events.js → page-events.ts} +55 -90
- package/src/account/server-client.ts +183 -0
- package/src/account/{subscriptions-view.js → subscriptions-view.ts} +25 -68
- package/src/account-api.ts +98 -0
- package/src/account-marks.ts +35 -0
- package/src/{account-pages.js → account-pages.ts} +29 -37
- package/src/account.ts +284 -0
- package/src/amounts.ts +64 -0
- package/src/{analytics-shopify.js → analytics-shopify.ts} +112 -107
- package/src/analytics.ts +315 -0
- package/src/{api-version.js → api-version.ts} +25 -31
- package/src/{astro.js → astro.ts} +110 -104
- package/src/cart/cart-shape.ts +25 -0
- package/src/cart/checkout.ts +101 -0
- package/src/cart/{drawer.js → drawer.ts} +18 -31
- package/src/cart/mutations.ts +237 -0
- package/src/cart/{render.js → render.ts} +68 -96
- package/src/cart/store.ts +279 -0
- package/src/{cart.js → cart.ts} +82 -128
- package/src/client.ts +391 -0
- package/src/{collection.js → collection.ts} +30 -38
- package/src/config-sections.ts +284 -0
- package/src/config.ts +480 -0
- package/src/controls/{cart-controls.js → cart-controls.ts} +22 -54
- package/src/controls/{discount.js → discount.ts} +13 -34
- package/src/controls/{extras.js → extras.ts} +15 -24
- package/src/controls/{navigation.js → navigation.ts} +24 -30
- package/src/{diagnose-markup.js → diagnose-markup.ts} +37 -72
- package/src/{diagnose.js → diagnose.ts} +95 -96
- package/src/dom.ts +268 -0
- package/src/{extras.js → extras.ts} +43 -52
- package/src/fragments.ts +261 -0
- package/src/globals.d.ts +15 -0
- package/src/{images.js → images.ts} +30 -43
- package/src/index.ts +415 -0
- package/src/initial.ts +101 -0
- package/src/lazy-iife.ts +197 -0
- package/src/lazy.ts +42 -0
- package/src/{loader.js → loader.ts} +149 -123
- package/src/market-codes.ts +6 -0
- package/src/{markets-ui.js → markets-ui.ts} +91 -68
- package/src/markets.ts +246 -0
- package/src/markup.ts +127 -0
- package/src/{mounting.js → mounting.ts} +88 -101
- package/src/{page-market.js → page-market.ts} +32 -38
- package/src/part-integrity.ts +7 -0
- package/src/parts/account-pages.ts +7 -0
- package/src/parts/account.ts +7 -0
- package/src/parts/analytics.ts +7 -0
- package/src/parts/diagnose.ts +7 -0
- package/src/parts/extras.ts +7 -0
- package/src/parts/markets.ts +7 -0
- package/src/parts/search.ts +7 -0
- package/src/presets/barba.ts +194 -0
- package/src/{product-events.js → product-events.ts} +33 -48
- package/src/{product-render.js → product-render.ts} +38 -77
- package/src/product-shape.ts +49 -0
- package/src/product-state.ts +318 -0
- package/src/{product.js → product.ts} +90 -126
- package/src/{repeaters.js → repeaters.ts} +40 -52
- package/src/{search.js → search.ts} +85 -103
- package/src/selling-plans.ts +145 -0
- package/src/server.ts +211 -0
- package/src/{shopify-values.js → shopify-values.ts} +24 -22
- package/src/{status.js → status.ts} +34 -54
- package/src/storage.ts +34 -0
- package/src/store-html.ts +359 -0
- package/src/storefront/catalog.ts +196 -0
- package/src/storefront/{json-ld.js → json-ld.ts} +45 -68
- package/src/{variant-render.js → variant-render.ts} +24 -50
- package/dist/esm/chunks/account-C3NP6HFB.js +0 -1
- package/dist/esm/chunks/account-pages-WLK374FO.js +0 -1
- package/dist/esm/chunks/analytics-G3SAWMNL.js +0 -1
- package/dist/esm/chunks/chunk-27PJJZGC.js +0 -1
- package/dist/esm/chunks/chunk-2FSXS55U.js +0 -1
- package/dist/esm/chunks/chunk-FCSWQDVV.js +0 -1
- package/dist/esm/chunks/chunk-FOMSMIAM.js +0 -1
- package/dist/esm/chunks/chunk-GZJTCLLW.js +0 -1
- package/dist/esm/chunks/chunk-HEQILZVR.js +0 -1
- package/dist/esm/chunks/chunk-JOOEXYF3.js +0 -4
- package/dist/esm/chunks/chunk-MPTQYLSM.js +0 -1
- package/dist/esm/chunks/chunk-NEHZ37TU.js +0 -3
- package/dist/esm/chunks/chunk-V74KGEJF.js +0 -1
- package/dist/esm/chunks/chunk-VT4CDUJJ.js +0 -1
- package/dist/esm/chunks/chunk-YJLWX2HK.js +0 -1
- package/dist/esm/chunks/diagnose-6VVGZNTY.js +0 -14
- package/dist/esm/chunks/extras-GJVRDFM3.js +0 -1
- package/dist/esm/chunks/markets-ui-UNB4PTA7.js +0 -1
- package/dist/esm/chunks/search-OU75RNQL.js +0 -1
- package/src/account/addresses-view.js +0 -92
- package/src/account/browser-session.js +0 -359
- package/src/account/markup.js +0 -48
- package/src/account/operations.js +0 -180
- package/src/account/order-list.js +0 -91
- package/src/account/order-page.js +0 -192
- package/src/account/server-client.js +0 -172
- package/src/account/shared.js +0 -111
- package/src/account/shopify-auth.js +0 -150
- package/src/account-api.js +0 -118
- package/src/account-links.js +0 -35
- package/src/account.js +0 -342
- package/src/amounts.js +0 -36
- package/src/analytics.js +0 -352
- package/src/cart/checkout.js +0 -97
- package/src/cart/mutations.js +0 -234
- package/src/cart/store.js +0 -326
- package/src/client.js +0 -267
- package/src/config.js +0 -349
- package/src/dom.js +0 -287
- package/src/fragments.js +0 -237
- package/src/index.js +0 -353
- package/src/initial.js +0 -87
- package/src/lazy-iife.js +0 -116
- package/src/lazy.js +0 -39
- package/src/market-codes.js +0 -35
- package/src/markets.js +0 -334
- package/src/markup.js +0 -64
- package/src/parts/account-pages.js +0 -6
- package/src/parts/account.js +0 -6
- package/src/parts/analytics.js +0 -6
- package/src/parts/diagnose.js +0 -6
- package/src/parts/extras.js +0 -6
- package/src/parts/markets.js +0 -6
- package/src/parts/search.js +0 -6
- package/src/presets/barba.js +0 -190
- package/src/product-state.js +0 -318
- package/src/selling-plans.js +0 -154
- package/src/server.js +0 -215
- package/src/store-html.js +0 -0
- package/src/storefront/catalog.js +0 -199
- package/types/astro.d.ts +0 -28
- package/types/barba.d.ts +0 -16
- package/types/index.d.ts +0 -577
- package/types/server.d.ts +0 -182
package/src/server.js
DELETED
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `@occluze/tether/server`: Shopify product data at build time or on the server (Astro, Eleventy, Node, Cloudflare
|
|
3
|
-
* Workers). No browser APIs. It sends the browser script's own queries, so products handed over with `hydrate()` are
|
|
4
|
-
* exactly what the script expects and need no request of their own.
|
|
5
|
-
*
|
|
6
|
-
* import { createStorefront } from '@occluze/tether/server';
|
|
7
|
-
* const shop = createStorefront({ domain, token, country: 'NL' });
|
|
8
|
-
* const product = await shop.getProduct('coffee-box');
|
|
9
|
-
* const all = await shop.allProducts();
|
|
10
|
-
*/
|
|
11
|
-
import { DEFAULTS, resolveConfig } from './config.js';
|
|
12
|
-
import { request, withContext, TetherError } from './client.js';
|
|
13
|
-
import * as plans from './selling-plans.js';
|
|
14
|
-
import { imageUrl, formatPercent } from './dom.js';
|
|
15
|
-
import { initialState } from './initial.js';
|
|
16
|
-
import { STORE_ENV_NAMES, formatMoney, productKey, productUrl as productUrlFor } from './shopify-values.js';
|
|
17
|
-
import { createCatalog } from './storefront/catalog.js';
|
|
18
|
-
import { jsonLdData, scriptJson } from './storefront/json-ld.js';
|
|
19
|
-
/** @import { TetherConfig, TetherOptions } from './config.js' */
|
|
20
|
-
/** @import { Product } from './fragments.js' */
|
|
21
|
-
/** @import { Money } from './shopify-values.js' */
|
|
22
|
-
/** @import { InitialOptions } from './initial.js' */
|
|
23
|
-
/** @import { JsonLdOptions } from './storefront/json-ld.js' */
|
|
24
|
-
|
|
25
|
-
export { imageUrl, initialState, formatPercent, formatMoney, productKey };
|
|
26
|
-
|
|
27
|
-
export { TetherError, plans, withContext };
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* @typedef {object} ServerOptions The options only a storefront has.
|
|
31
|
-
* @property {typeof fetch} [fetch] Replaces the global `fetch` (tests, caching).
|
|
32
|
-
* @property {number} [pageSize] Products per request for `allProducts()` (default 25).
|
|
33
|
-
* @property {string} [siteUrl] The site's address, for product URLs in `jsonLd()` without `url`.
|
|
34
|
-
*/
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* @typedef {TetherOptions & ServerOptions & Record<string, unknown>} StorefrontOptions The config options, or an
|
|
38
|
-
* `import.meta.env` spread into them: its `PUBLIC_SHOPIFY_*` names give the domain and token, its other keys are
|
|
39
|
-
* ignored.
|
|
40
|
-
*/
|
|
41
|
-
|
|
42
|
-
/** @typedef {TetherConfig & ServerOptions} ServerConfig */
|
|
43
|
-
|
|
44
|
-
/** @typedef {{ card?: boolean, images?: number }} HydrateOptions */
|
|
45
|
-
|
|
46
|
-
const DEFAULT_PAGE_SIZE = 25;
|
|
47
|
-
/** The images a card keeps: the main one and a hover image (`tether-bind="image:2"`). */
|
|
48
|
-
const DEFAULT_CARD_IMAGES = 2;
|
|
49
|
-
/** The version of the `data-tether-hydrate` payload, for the browser script reading it. */
|
|
50
|
-
const HYDRATE_VERSION = 1;
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* @param {string} handle
|
|
54
|
-
* @param {{ productPath?: string, id?: string }} [options]
|
|
55
|
-
* @returns {string} `productPath` `/products` → `/products/<handle>`; templates with `{handle}` and `{id}` work too.
|
|
56
|
-
*/
|
|
57
|
-
export function productUrl(handle, { productPath = DEFAULTS.productPath, id } = {}) {
|
|
58
|
-
return productUrlFor(productPath, handle, id);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* @param {Record<string, unknown>} options
|
|
63
|
-
* @param {readonly string[]} names
|
|
64
|
-
* @returns {string} The first of the `names` set in `options`, `''` without one.
|
|
65
|
-
*/
|
|
66
|
-
function fromEnv(options, names) {
|
|
67
|
-
for (const name of names) {
|
|
68
|
-
const value = options[name];
|
|
69
|
-
if (typeof value === 'string' && value) return value;
|
|
70
|
-
}
|
|
71
|
-
return '';
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* @param {StorefrontOptions} options
|
|
76
|
-
* @returns {ServerConfig} The config options of `options`, resolved and validated as the browser's are, with the
|
|
77
|
-
* domain and token from the environment names when they aren't set.
|
|
78
|
-
*/
|
|
79
|
-
function storefrontConfig(options) {
|
|
80
|
-
/** @type {Record<string, unknown>} */
|
|
81
|
-
const known = {};
|
|
82
|
-
for (const [key, value] of Object.entries(options)) {
|
|
83
|
-
if (key in DEFAULTS) known[key] = value;
|
|
84
|
-
}
|
|
85
|
-
const domain = typeof options.domain === 'string' && options.domain ? options.domain : '';
|
|
86
|
-
const token = typeof options.token === 'string' && options.token ? options.token : '';
|
|
87
|
-
/** @type {ServerConfig} */
|
|
88
|
-
const conf = resolveConfig({
|
|
89
|
-
// Only config keys: `resolveConfig` checks each value.
|
|
90
|
-
.../** @type {TetherOptions} */ (known),
|
|
91
|
-
domain: domain || fromEnv(options, STORE_ENV_NAMES.domain),
|
|
92
|
-
token: token || fromEnv(options, STORE_ENV_NAMES.token),
|
|
93
|
-
});
|
|
94
|
-
if (options.fetch) conf.fetch = options.fetch;
|
|
95
|
-
if (options.pageSize !== undefined) conf.pageSize = options.pageSize;
|
|
96
|
-
if (options.siteUrl !== undefined) conf.siteUrl = options.siteUrl;
|
|
97
|
-
return conf;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* @param {number | undefined} pageSize
|
|
102
|
-
* @returns {number} `pageSize` when it is a positive integer, else the default.
|
|
103
|
-
*/
|
|
104
|
-
const validPageSize = (pageSize) =>
|
|
105
|
-
typeof pageSize === 'number' && Number.isInteger(pageSize) && pageSize > 0 ? pageSize : DEFAULT_PAGE_SIZE;
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* A product for a list (home, collections, recommendations): without descriptions and variant images, and only its
|
|
109
|
-
* first `images` images. Prices, stock and plans per variant stay.
|
|
110
|
-
*
|
|
111
|
-
* @param {Product} product
|
|
112
|
-
* @param {number} images
|
|
113
|
-
* @returns {Product}
|
|
114
|
-
*/
|
|
115
|
-
function cardOf(product, images) {
|
|
116
|
-
return {
|
|
117
|
-
...product,
|
|
118
|
-
description: '',
|
|
119
|
-
descriptionHtml: '',
|
|
120
|
-
images: { nodes: product.images.nodes.slice(0, Math.max(1, images)) },
|
|
121
|
-
variants: { nodes: product.variants.nodes.map((variant) => ({ ...variant, image: null })) },
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* A storefront: the store's products and collections for the server, and the tags that hand them to the page.
|
|
127
|
-
*
|
|
128
|
-
* @param {StorefrontOptions} [options]
|
|
129
|
-
* @throws {TetherError} `NOT_CONFIGURED` without a domain and a token.
|
|
130
|
-
*/
|
|
131
|
-
export function createStorefront(options = {}) {
|
|
132
|
-
const conf = storefrontConfig(options);
|
|
133
|
-
if (!conf.domain || !conf.token) {
|
|
134
|
-
throw new TetherError(
|
|
135
|
-
'createStorefront needs domain and token: pass them, or import.meta.env with PUBLIC_SHOPIFY_DOMAIN and PUBLIC_SHOPIFY_STOREFRONT_TOKEN',
|
|
136
|
-
{ code: 'NOT_CONFIGURED' },
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
const catalog = createCatalog(conf, validPageSize(conf.pageSize));
|
|
140
|
-
const site = { productPath: conf.productPath, siteUrl: conf.siteUrl };
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* A `<script type="application/json" data-tether-hydrate>` tag that hands products to the browser script: it renders
|
|
144
|
-
* them at once, without a request of its own, then refreshes prices and stock once in the background. Astro:
|
|
145
|
-
* `<Fragment set:html={shop.hydrate(products)} />`.
|
|
146
|
-
*
|
|
147
|
-
* @param {Product | null | undefined | (Product | null | undefined)[]} products
|
|
148
|
-
* @param {HydrateOptions} [options] `card: true` for lists: about 40% smaller (see `cardOf`). The full product
|
|
149
|
-
* comes with the background refresh.
|
|
150
|
-
*/
|
|
151
|
-
function hydrate(products, { card = false, images = DEFAULT_CARD_IMAGES } = {}) {
|
|
152
|
-
const present = (Array.isArray(products) ? products : [products]).filter((product) => product != null);
|
|
153
|
-
const payload = {
|
|
154
|
-
v: HYDRATE_VERSION,
|
|
155
|
-
at: new Date().toISOString(),
|
|
156
|
-
country: conf.country || null,
|
|
157
|
-
language: conf.language || null,
|
|
158
|
-
products: card ? present.map((product) => cardOf(product, images)) : present,
|
|
159
|
-
};
|
|
160
|
-
return `<script type="application/json" data-tether-hydrate>${scriptJson(payload)}</script>`;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* @param {Product | null | undefined} product
|
|
165
|
-
* @param {JsonLdOptions} [jsonLdOptions]
|
|
166
|
-
* @returns {string} The `<script type="application/ld+json">` tag, `''` without a product.
|
|
167
|
-
*/
|
|
168
|
-
function jsonLd(product, jsonLdOptions = {}) {
|
|
169
|
-
if (!product) return '';
|
|
170
|
-
return `<script type="application/ld+json">${scriptJson(jsonLdData(product, jsonLdOptions, site))}</script>`;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return {
|
|
174
|
-
config: conf,
|
|
175
|
-
/**
|
|
176
|
-
* @template T
|
|
177
|
-
* @param {string} query
|
|
178
|
-
* @param {Record<string, unknown>} [variables]
|
|
179
|
-
* @param {{ context?: boolean }} [requestOptions]
|
|
180
|
-
* @returns {Promise<T>}
|
|
181
|
-
*/
|
|
182
|
-
query: (query, variables, requestOptions) => request(conf, query, variables, requestOptions),
|
|
183
|
-
...catalog,
|
|
184
|
-
hydrate,
|
|
185
|
-
/**
|
|
186
|
-
* @param {Money | null | undefined} money
|
|
187
|
-
* @param {string | undefined} [locale]
|
|
188
|
-
*/
|
|
189
|
-
formatMoney: (money, locale = conf.locale) => formatMoney(money, locale),
|
|
190
|
-
/**
|
|
191
|
-
* @param {number} percentage
|
|
192
|
-
* @param {string | undefined} [locale]
|
|
193
|
-
*/
|
|
194
|
-
formatPercent: (percentage, locale = conf.locale) => formatPercent(percentage, locale),
|
|
195
|
-
imageUrl,
|
|
196
|
-
jsonLd,
|
|
197
|
-
/**
|
|
198
|
-
* @param {Product | null | undefined} product
|
|
199
|
-
* @param {JsonLdOptions} [jsonLdOptions]
|
|
200
|
-
*/
|
|
201
|
-
jsonLdData: (product, jsonLdOptions = {}) => jsonLdData(product, jsonLdOptions, site),
|
|
202
|
-
/**
|
|
203
|
-
* @param {Product | null | undefined} product
|
|
204
|
-
* @param {InitialOptions} [initialOptions]
|
|
205
|
-
*/
|
|
206
|
-
initialState: (product, initialOptions = {}) =>
|
|
207
|
-
initialState(product, { defaultPurchase: conf.defaultPurchase, lowStock: conf.lowStock, ...initialOptions }),
|
|
208
|
-
/**
|
|
209
|
-
* @param {string} handle
|
|
210
|
-
* @param {{ productPath?: string, id?: string }} [urlOptions]
|
|
211
|
-
*/
|
|
212
|
-
productUrl: (handle, urlOptions = {}) => productUrl(handle, { productPath: conf.productPath, ...urlOptions }),
|
|
213
|
-
plans,
|
|
214
|
-
};
|
|
215
|
-
}
|
package/src/store-html.js
DELETED
|
Binary file
|
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The catalog reads of `createStorefront()`: products, collections and recommendations, with the browser script's own
|
|
3
|
-
* product fragment, so what the server renders and hands over has the shape the script expects. Runs in Node and
|
|
4
|
-
* Workers: no DOM, no shared config.
|
|
5
|
-
*/
|
|
6
|
-
import { request } from '../client.js';
|
|
7
|
-
import { productFragment, collectionFields } from '../fragments.js';
|
|
8
|
-
import { productKey } from '../shopify-values.js';
|
|
9
|
-
/** @import { StorefrontConfig } from '../client.js' */
|
|
10
|
-
/** @import { ProductFragmentConfig, Product } from '../fragments.js' */
|
|
11
|
-
/** @import { Collection, PageInfo, ProductPage } from '../loader.js' */
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* @typedef {StorefrontConfig & ProductFragmentConfig} CatalogConfig What the catalog needs of a storefront's config.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* @typedef {object} CatalogQuery A page of a collection's products, or of a search.
|
|
19
|
-
* @property {string} [collection] A collection handle; without one, a search.
|
|
20
|
-
* @property {string | null} [query] Storefront search syntax; `null` or `''` is every product.
|
|
21
|
-
* @property {number} [first] Default the storefront's `pageSize`.
|
|
22
|
-
* @property {string | null} [after] The cursor of the page before.
|
|
23
|
-
* @property {string} [sortKey] `ProductCollectionSortKeys` for a collection, `ProductSortKeys` for a search.
|
|
24
|
-
* @property {boolean} [reverse]
|
|
25
|
-
* @property {unknown[]} [filters] `ProductFilter` values, for a collection.
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
/** @typedef {Collection & { products: Product[], pageInfo: PageInfo }} CollectionWithProducts */
|
|
29
|
-
/** @typedef {'RELATED' | 'COMPLEMENTARY'} RecommendationIntent */
|
|
30
|
-
|
|
31
|
-
/** Stops a paging loop on a store that keeps answering `hasNextPage` (500 pages of 25 is 12,500 products). */
|
|
32
|
-
const MAX_PRODUCT_PAGES = 500;
|
|
33
|
-
const COLLECTIONS_PER_PAGE = 100;
|
|
34
|
-
const MAX_COLLECTION_PAGES = 100;
|
|
35
|
-
|
|
36
|
-
const PRODUCT_BY_ID = `query TetherServerProductById($id: ID!) { node(id: $id) { ... on Product { ...TetherProduct } } }`;
|
|
37
|
-
const PRODUCT_BY_HANDLE = `query TetherServerProduct($handle: String!) { product(handle: $handle) { ...TetherProduct } }`;
|
|
38
|
-
|
|
39
|
-
const collectionQuery =
|
|
40
|
-
() => `query TetherServerCollection($handle: String!, $first: Int!, $after: String, $sortKey: ProductCollectionSortKeys, $reverse: Boolean, $filters: [ProductFilter!]) {
|
|
41
|
-
collection(handle: $handle) {
|
|
42
|
-
${collectionFields()}
|
|
43
|
-
products(first: $first, after: $after, sortKey: $sortKey, reverse: $reverse, filters: $filters) {
|
|
44
|
-
nodes { ...TetherProduct }
|
|
45
|
-
pageInfo { hasNextPage endCursor }
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}`;
|
|
49
|
-
|
|
50
|
-
const SEARCH_QUERY = `query TetherServerProducts($first: Int!, $after: String, $query: String, $sortKey: ProductSortKeys, $reverse: Boolean) {
|
|
51
|
-
products(first: $first, after: $after, query: $query, sortKey: $sortKey, reverse: $reverse) {
|
|
52
|
-
nodes { ...TetherProduct }
|
|
53
|
-
pageInfo { hasNextPage endCursor }
|
|
54
|
-
}
|
|
55
|
-
}`;
|
|
56
|
-
|
|
57
|
-
const collectionsQuery = () => `query TetherServerCollections($first: Int!, $after: String) {
|
|
58
|
-
collections(first: $first, after: $after) { nodes { ${collectionFields()} } pageInfo { hasNextPage endCursor } }
|
|
59
|
-
}`;
|
|
60
|
-
|
|
61
|
-
/** @param {boolean} byId */
|
|
62
|
-
const recommendationsQuery = (
|
|
63
|
-
byId,
|
|
64
|
-
) => `query TetherServerRecs(${byId ? '$id: ID!' : '$handle: String!'}, $intent: ProductRecommendationIntent) {
|
|
65
|
-
productRecommendations(${byId ? 'productId: $id' : 'productHandle: $handle'}, intent: $intent) { ...TetherProduct }
|
|
66
|
-
}`;
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* @param {CatalogConfig} conf
|
|
70
|
-
* @param {number} pageSize Products per page when a query doesn't say.
|
|
71
|
-
*/
|
|
72
|
-
export function createCatalog(conf, pageSize) {
|
|
73
|
-
/**
|
|
74
|
-
* @template T The shape of the query's `data`.
|
|
75
|
-
* @param {string} operation The query, without the product fragment.
|
|
76
|
-
* @param {Record<string, unknown>} variables
|
|
77
|
-
* @returns {Promise<T>}
|
|
78
|
-
*/
|
|
79
|
-
const send = (operation, variables) => request(conf, `${operation}\n${productFragment(conf)}`, variables);
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* @param {string | number | null | undefined} raw A handle, numeric id, gid or product URL.
|
|
83
|
-
* @returns {Promise<Product | null>} `null` when the store has no such product.
|
|
84
|
-
*/
|
|
85
|
-
async function getProduct(raw) {
|
|
86
|
-
const key = productKey(raw);
|
|
87
|
-
if (!key) return null;
|
|
88
|
-
if (key.startsWith('gid://')) {
|
|
89
|
-
/** @type {{ node: Partial<Product> | null }} */
|
|
90
|
-
const data = await send(PRODUCT_BY_ID, { id: key });
|
|
91
|
-
// A gid of another type answers with a node that isn't a product: no fields of the fragment.
|
|
92
|
-
return data.node?.id ? /** @type {Product} */ (data.node) : null;
|
|
93
|
-
}
|
|
94
|
-
/** @type {{ product: Product | null }} */
|
|
95
|
-
const data = await send(PRODUCT_BY_HANDLE, { handle: key });
|
|
96
|
-
return data.product;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* @param {string} handle
|
|
101
|
-
* @param {Omit<CatalogQuery, 'collection' | 'query'>} options
|
|
102
|
-
* @returns {Promise<ProductPage>} No collection and no products for a handle the store doesn't have.
|
|
103
|
-
*/
|
|
104
|
-
async function getCollectionPage(handle, { first = pageSize, after = null, sortKey, reverse = false, filters }) {
|
|
105
|
-
/** @type {{ collection: (Collection & { products: { nodes: Product[], pageInfo: PageInfo } }) | null }} */
|
|
106
|
-
const data = await send(collectionQuery(), {
|
|
107
|
-
handle,
|
|
108
|
-
first,
|
|
109
|
-
after,
|
|
110
|
-
sortKey: sortKey || undefined,
|
|
111
|
-
reverse,
|
|
112
|
-
filters: filters ?? undefined,
|
|
113
|
-
});
|
|
114
|
-
if (!data.collection) return { collection: null, products: [], pageInfo: { hasNextPage: false, endCursor: null } };
|
|
115
|
-
const { products, ...collection } = data.collection;
|
|
116
|
-
return { collection, products: products.nodes, pageInfo: products.pageInfo };
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* @param {CatalogQuery} [options]
|
|
121
|
-
* @returns {Promise<ProductPage>}
|
|
122
|
-
*/
|
|
123
|
-
async function getProducts(options = {}) {
|
|
124
|
-
const { collection, query, first = pageSize, after = null, sortKey, reverse = false } = options;
|
|
125
|
-
if (collection) return getCollectionPage(collection, options);
|
|
126
|
-
/** @type {{ products: { nodes: Product[], pageInfo: PageInfo } }} */
|
|
127
|
-
const data = await send(SEARCH_QUERY, {
|
|
128
|
-
first,
|
|
129
|
-
after,
|
|
130
|
-
query: query || undefined,
|
|
131
|
-
sortKey: sortKey || undefined,
|
|
132
|
-
reverse,
|
|
133
|
-
});
|
|
134
|
-
return { collection: null, products: data.products.nodes, pageInfo: data.products.pageInfo };
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Every product of a collection or a search, page by page: for `getStaticPaths()`.
|
|
139
|
-
*
|
|
140
|
-
* @param {Omit<CatalogQuery, 'first' | 'after'>} [options]
|
|
141
|
-
* @returns {Promise<Product[]>}
|
|
142
|
-
*/
|
|
143
|
-
async function allProducts(options = {}) {
|
|
144
|
-
/** @type {Product[]} */
|
|
145
|
-
const products = [];
|
|
146
|
-
/** @type {string | null} */
|
|
147
|
-
let after = null;
|
|
148
|
-
for (let page = 0; page < MAX_PRODUCT_PAGES; page++) {
|
|
149
|
-
const result = await getProducts({ ...options, first: pageSize, after });
|
|
150
|
-
products.push(...result.products);
|
|
151
|
-
if (!result.pageInfo.hasNextPage) break;
|
|
152
|
-
after = result.pageInfo.endCursor;
|
|
153
|
-
}
|
|
154
|
-
return products;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* @param {string} handle
|
|
159
|
-
* @param {Omit<CatalogQuery, 'collection'>} [options]
|
|
160
|
-
* @returns {Promise<CollectionWithProducts | null>} `null` for a handle the store doesn't have.
|
|
161
|
-
*/
|
|
162
|
-
async function getCollection(handle, options = {}) {
|
|
163
|
-
const page = await getProducts({ ...options, collection: handle });
|
|
164
|
-
return page.collection ? { ...page.collection, products: page.products, pageInfo: page.pageInfo } : null;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/** @returns {Promise<Collection[]>} */
|
|
168
|
-
async function allCollections() {
|
|
169
|
-
/** @type {Collection[]} */
|
|
170
|
-
const collections = [];
|
|
171
|
-
/** @type {string | null} */
|
|
172
|
-
let after = null;
|
|
173
|
-
for (let page = 0; page < MAX_COLLECTION_PAGES; page++) {
|
|
174
|
-
/** @type {{ collections: { nodes: Collection[], pageInfo: PageInfo } }} */
|
|
175
|
-
const data = await request(conf, collectionsQuery(), { first: COLLECTIONS_PER_PAGE, after });
|
|
176
|
-
collections.push(...data.collections.nodes);
|
|
177
|
-
if (!data.collections.pageInfo.hasNextPage) break;
|
|
178
|
-
after = data.collections.pageInfo.endCursor;
|
|
179
|
-
}
|
|
180
|
-
return collections;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* By product id (gid or numeric) or handle, so it can run next to `getProduct(handle)`.
|
|
185
|
-
*
|
|
186
|
-
* @param {string | number | { id: string } | null | undefined} product
|
|
187
|
-
* @param {RecommendationIntent} [intent]
|
|
188
|
-
* @returns {Promise<Product[]>}
|
|
189
|
-
*/
|
|
190
|
-
async function getRecommendations(product, intent = 'RELATED') {
|
|
191
|
-
const key = productKey(typeof product === 'object' ? product?.id : product);
|
|
192
|
-
const byId = key?.startsWith('gid://') ?? false;
|
|
193
|
-
/** @type {{ productRecommendations: Product[] | null }} */
|
|
194
|
-
const data = await send(recommendationsQuery(byId), byId ? { id: key, intent } : { handle: key, intent });
|
|
195
|
-
return data.productRecommendations ?? [];
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return { getProduct, getProducts, allProducts, getCollection, allCollections, getRecommendations };
|
|
199
|
-
}
|
package/types/astro.d.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import type { TetherConfig } from './index.js';
|
|
2
|
-
|
|
3
|
-
type Serializable<T> = {
|
|
4
|
-
[
|
|
5
|
-
K in keyof T as T[K] extends ((...args: any[]) => any) | null | undefined
|
|
6
|
-
? Exclude<T[K], null | undefined> extends (...args: any[]) => any
|
|
7
|
-
? never
|
|
8
|
-
: K
|
|
9
|
-
: K
|
|
10
|
-
]: T[K];
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export interface TetherAstroOptions extends Partial<Serializable<TetherConfig>> {
|
|
14
|
-
/** Add the Tether script to every page (default true). False: import and init it yourself. */
|
|
15
|
-
inject?: boolean;
|
|
16
|
-
/** Files that restart the dev server when they change (default: src/shop.config.js/.mjs/.ts). */
|
|
17
|
-
watch?: string[];
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Astro integration: Tether on every page, store and token from .env
|
|
22
|
-
* (PUBLIC_SHOPIFY_DOMAIN, PUBLIC_SHOPIFY_STOREFRONT_TOKEN). Functions (onError, hooks) go in
|
|
23
|
-
* window.TETHER_CONFIG in your layout.
|
|
24
|
-
*/
|
|
25
|
-
export default function tether(options?: TetherAstroOptions): {
|
|
26
|
-
name: string;
|
|
27
|
-
hooks: Record<string, (...args: any[]) => unknown>;
|
|
28
|
-
};
|
package/types/barba.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { TetherConfig } from './index.js';
|
|
2
|
-
|
|
3
|
-
export interface BarbaPresetOptions {
|
|
4
|
-
gsap?: () => unknown;
|
|
5
|
-
lenis?: (() => { resize?(): void; stop?(): void; start?(): void } | null | undefined) | null;
|
|
6
|
-
scrollTrigger?: () => unknown;
|
|
7
|
-
lockScroll?: ((drawer: HTMLElement) => void) | null;
|
|
8
|
-
unlockScroll?: (() => void) | null;
|
|
9
|
-
onResize?: (() => void) | null;
|
|
10
|
-
onValueChange?: ((el: HTMLElement, previousText: string) => void) | null;
|
|
11
|
-
animateLines?: boolean;
|
|
12
|
-
lineDuration?: number;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** Hooks for sites with page transitions, smooth scroll and GSAP: spread into Tether.init(). */
|
|
16
|
-
export declare function barbaPreset(options?: BarbaPresetOptions): Partial<TetherConfig>;
|