@shop-on/theme-server 0.5.0 → 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/dist/search.d.ts CHANGED
@@ -37,6 +37,9 @@ type FetchProductSearchPageInput = {
37
37
  includeOptions?: boolean;
38
38
  includeVariants?: boolean;
39
39
  locale?: string;
40
+ /** Storefront-visible metafields (`"namespace.key"`) each card carries, for
41
+ * the `closest_product_metafield` sources bound on it. Omitted ⇒ none. */
42
+ metafieldKeys?: readonly string[];
40
43
  };
41
44
  /** Server-driven /search page. Returns `null` for blank query (caller
42
45
  * route renders the empty-prompt UI) or on GraphQL error. Uncached because
package/dist/search.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { graphql, request } from "./graphql.js";
2
2
  import { toFileInfo } from "./files.js";
3
+ import { metafieldKeysVariables } from "./product.js";
3
4
  import { CollectionImageFragment, CollectionProductNodeFragment, toCollectionProduct, toPriceBounds, variantsVariable } from "./collection.js";
4
5
  //#region src/search.ts
5
6
  const SEARCH_PAGE_SIZE = 24;
@@ -10,6 +11,9 @@ const FetchProductSearchPageQuery = graphql(`
10
11
  $mediaFirst: Int!
11
12
  $includeOptions: Boolean!
12
13
  $includeVariants: Boolean = false
14
+ $withMetafields: Boolean = false
15
+ $metafieldFirst: Int = 0
16
+ $metafieldKeys: [MetafieldKeyInput!] = []
13
17
  $after: String
14
18
  $filter: CollectionProductFilterInput
15
19
  $sort: ProductSearchSortEnum
@@ -81,6 +85,7 @@ const fetchProductSearchPage = async (data) => {
81
85
  mediaFirst,
82
86
  includeOptions,
83
87
  ...variantsVariable(data.includeVariants),
88
+ ...metafieldKeysVariables(data.metafieldKeys),
84
89
  after: after ?? null,
85
90
  filter: filter ?? null,
86
91
  sort: sort ?? null
@@ -0,0 +1,47 @@
1
+ import { ResultOf } from "./graphql.js";
2
+ import { OrganizationSchemaSource, ReturnPolicySchemaSource } from "@shop-on/theme-runtime/template-types";
3
+ //#region src/structured-data.d.ts
4
+ declare const ShopStructuredDataQuery: import("gql.tada").TadaDocumentNode<{
5
+ shop: {
6
+ branding: {
7
+ logo: {
8
+ url: string | null;
9
+ } | null;
10
+ } | null;
11
+ countryCode: string | null;
12
+ refundPolicy: {
13
+ url: string;
14
+ } | null;
15
+ returnPolicy: {
16
+ configured: boolean;
17
+ flatRateAmount: {
18
+ amount: string;
19
+ currencyCode: "CNY" | "EUR" | "GBP" | "JPY" | "KRW" | "MNT" | "RUB" | "USD";
20
+ } | null;
21
+ restockingFeePercentage: number | null;
22
+ returnShippingCost: "CUSTOMER_PROVIDED" | "FLAT_RATE" | "FREE";
23
+ returnWindowDays: number | null;
24
+ };
25
+ } | null;
26
+ }, {}, void>;
27
+ type ShopRecord = NonNullable<ResultOf<typeof ShopStructuredDataQuery>["shop"]>;
28
+ /**
29
+ * The shop's return policy as schema.org reads it, for one product or (with
30
+ * `finalSale` false) the shop as a whole. Undefined until the merchant saves a
31
+ * policy: the defaults an unconfigured shop reads back ("unlimited window,
32
+ * free returns") are no promise the merchant made, and publishing them would
33
+ * tell Google and every agent exactly that. Also undefined without a country,
34
+ * which Google requires.
35
+ */
36
+ export declare function returnPolicySource(shop: ShopRecord, { finalSale, base }: {
37
+ finalSale: boolean;
38
+ base: string;
39
+ }): ReturnPolicySchemaSource | undefined;
40
+ /** A product's return policy: the shop's, or "not permitted" when the
41
+ * product is final sale (`ShopProduct.finalSale`). */
42
+ export declare function fetchProductReturnPolicy(finalSale: boolean, base: string): Promise<ReturnPolicySchemaSource | undefined>;
43
+ /** The homepage's Organization node. Undefined without a name or origin —
44
+ * schema.org needs both and the homepage then emits no node at all. */
45
+ export declare function fetchOrganizationSchema(name: string, base: string): Promise<OrganizationSchemaSource | undefined>;
46
+ //#endregion
47
+ export {};
@@ -0,0 +1,110 @@
1
+ import { memoTTL } from "./cache.js";
2
+ import { graphql, request } from "./graphql.js";
3
+ import { getContentVersion } from "./content-version.js";
4
+ //#region src/structured-data.ts
5
+ const ShopStructuredDataQuery = graphql(`
6
+ query StorefrontShopStructuredData {
7
+ shop {
8
+ countryCode
9
+ branding {
10
+ logo {
11
+ url
12
+ }
13
+ }
14
+ refundPolicy {
15
+ url
16
+ }
17
+ returnPolicy {
18
+ configured
19
+ returnWindowDays
20
+ restockingFeePercentage
21
+ returnShippingCost
22
+ flatRateAmount {
23
+ amount
24
+ currencyCode
25
+ }
26
+ }
27
+ }
28
+ }
29
+ `);
30
+ const fetchShopRecord = memoTTL(async () => (await request(ShopStructuredDataQuery)).shop ?? null, {
31
+ key: () => "",
32
+ version: getContentVersion
33
+ });
34
+ async function safeShopRecord() {
35
+ try {
36
+ return await fetchShopRecord();
37
+ } catch (err) {
38
+ console.warn("[structured-data] shop fetch failed:", err);
39
+ return null;
40
+ }
41
+ }
42
+ const FEES = {
43
+ FREE: "FreeReturn",
44
+ CUSTOMER_PROVIDED: "ReturnFeesCustomerResponsibility",
45
+ FLAT_RATE: "ReturnShippingFees"
46
+ };
47
+ /**
48
+ * The shop's return policy as schema.org reads it, for one product or (with
49
+ * `finalSale` false) the shop as a whole. Undefined until the merchant saves a
50
+ * policy: the defaults an unconfigured shop reads back ("unlimited window,
51
+ * free returns") are no promise the merchant made, and publishing them would
52
+ * tell Google and every agent exactly that. Also undefined without a country,
53
+ * which Google requires.
54
+ */
55
+ function returnPolicySource(shop, { finalSale, base }) {
56
+ const policy = shop.returnPolicy;
57
+ if (!policy.configured || !shop.countryCode) return void 0;
58
+ const url = shop.refundPolicy && base ? `${base}${shop.refundPolicy.url}` : void 0;
59
+ const country = shop.countryCode;
60
+ if (finalSale) return {
61
+ country,
62
+ category: "NotPermitted",
63
+ ...url ? { url } : {}
64
+ };
65
+ const days = policy.returnWindowDays;
66
+ const fee = policy.flatRateAmount;
67
+ const restocking = policy.restockingFeePercentage;
68
+ return {
69
+ country,
70
+ ...days ? {
71
+ category: "FiniteReturnWindow",
72
+ days
73
+ } : { category: "UnlimitedWindow" },
74
+ fees: FEES[policy.returnShippingCost],
75
+ ...policy.returnShippingCost === "FLAT_RATE" && fee ? { shippingFee: {
76
+ amount: Number(fee.amount),
77
+ currency: fee.currencyCode
78
+ } } : {},
79
+ ...restocking ? { restockingFeePercent: restocking } : {},
80
+ ...url ? { url } : {}
81
+ };
82
+ }
83
+ /** A product's return policy: the shop's, or "not permitted" when the
84
+ * product is final sale (`ShopProduct.finalSale`). */
85
+ async function fetchProductReturnPolicy(finalSale, base) {
86
+ const shop = await safeShopRecord();
87
+ return shop ? returnPolicySource(shop, {
88
+ finalSale,
89
+ base
90
+ }) : void 0;
91
+ }
92
+ /** The homepage's Organization node. Undefined without a name or origin —
93
+ * schema.org needs both and the homepage then emits no node at all. */
94
+ async function fetchOrganizationSchema(name, base) {
95
+ if (!name || !base) return void 0;
96
+ const shop = await safeShopRecord();
97
+ const logo = shop?.branding?.logo?.url;
98
+ const returnPolicy = shop ? returnPolicySource(shop, {
99
+ finalSale: false,
100
+ base
101
+ }) : void 0;
102
+ return {
103
+ name,
104
+ url: `${base}/`,
105
+ ...logo ? { logo } : {},
106
+ ...returnPolicy ? { returnPolicy } : {}
107
+ };
108
+ }
109
+ //#endregion
110
+ export { fetchOrganizationSchema, fetchProductReturnPolicy, returnPolicySource };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shop-on/theme-server",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "license": "SEE LICENSE IN dist/LICENSE.md",
5
5
  "files": [
6
6
  "dist"
@@ -51,6 +51,7 @@
51
51
  "./shop": "./dist/shop.js",
52
52
  "./shop-seo": "./dist/shop-seo.js",
53
53
  "./store-availability": "./dist/store-availability.js",
54
+ "./structured-data": "./dist/structured-data.js",
54
55
  "./translations": "./dist/translations.js",
55
56
  "./package.json": "./package.json"
56
57
  },
@@ -59,8 +60,8 @@
59
60
  "graphql": "17.0.2",
60
61
  "graphql-request": "7.4.0",
61
62
  "liquidjs": "10.30.0",
62
- "@shop-on/customer-runtime": "0.5.0",
63
- "@shop-on/theme-runtime": "0.5.0"
63
+ "@shop-on/theme-runtime": "0.7.0",
64
+ "@shop-on/customer-runtime": "0.7.0"
64
65
  },
65
66
  "devDependencies": {
66
67
  "@tanstack/react-start": "1.169.3",