@shop-on/theme-server 0.6.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/graphql-env.d.ts +90 -0
- package/dist/product.d.ts +2 -0
- package/dist/product.js +5 -1
- package/dist/schema.storefront.graphql +65 -0
- package/dist/structured-data.d.ts +47 -0
- package/dist/structured-data.js +110 -0
- package/package.json +4 -3
package/dist/graphql-env.d.ts
CHANGED
|
@@ -9309,6 +9309,18 @@ export type introspection_types = {
|
|
|
9309
9309
|
ofType: null;
|
|
9310
9310
|
};
|
|
9311
9311
|
};
|
|
9312
|
+
'returnPolicy': {
|
|
9313
|
+
name: 'returnPolicy';
|
|
9314
|
+
type: {
|
|
9315
|
+
kind: 'NON_NULL';
|
|
9316
|
+
name: never;
|
|
9317
|
+
ofType: {
|
|
9318
|
+
kind: 'OBJECT';
|
|
9319
|
+
name: 'ShopReturnPolicy';
|
|
9320
|
+
ofType: null;
|
|
9321
|
+
};
|
|
9322
|
+
};
|
|
9323
|
+
};
|
|
9312
9324
|
'seo': {
|
|
9313
9325
|
name: 'seo';
|
|
9314
9326
|
type: {
|
|
@@ -16538,6 +16550,18 @@ export type introspection_types = {
|
|
|
16538
16550
|
ofType: null;
|
|
16539
16551
|
};
|
|
16540
16552
|
};
|
|
16553
|
+
'finalSale': {
|
|
16554
|
+
name: 'finalSale';
|
|
16555
|
+
type: {
|
|
16556
|
+
kind: 'NON_NULL';
|
|
16557
|
+
name: never;
|
|
16558
|
+
ofType: {
|
|
16559
|
+
kind: 'SCALAR';
|
|
16560
|
+
name: 'Boolean';
|
|
16561
|
+
ofType: null;
|
|
16562
|
+
};
|
|
16563
|
+
};
|
|
16564
|
+
};
|
|
16541
16565
|
'giftCard': {
|
|
16542
16566
|
name: 'giftCard';
|
|
16543
16567
|
type: {
|
|
@@ -17980,6 +18004,14 @@ export type introspection_types = {
|
|
|
17980
18004
|
ofType: null;
|
|
17981
18005
|
};
|
|
17982
18006
|
};
|
|
18007
|
+
'gtin': {
|
|
18008
|
+
name: 'gtin';
|
|
18009
|
+
type: {
|
|
18010
|
+
kind: 'SCALAR';
|
|
18011
|
+
name: 'String';
|
|
18012
|
+
ofType: null;
|
|
18013
|
+
};
|
|
18014
|
+
};
|
|
17983
18015
|
'id': {
|
|
17984
18016
|
name: 'id';
|
|
17985
18017
|
type: {
|
|
@@ -18276,6 +18308,64 @@ export type introspection_types = {
|
|
|
18276
18308
|
};
|
|
18277
18309
|
};
|
|
18278
18310
|
};
|
|
18311
|
+
'ShopReturnPolicy': {
|
|
18312
|
+
kind: 'OBJECT';
|
|
18313
|
+
name: 'ShopReturnPolicy';
|
|
18314
|
+
fields: {
|
|
18315
|
+
'configured': {
|
|
18316
|
+
name: 'configured';
|
|
18317
|
+
type: {
|
|
18318
|
+
kind: 'NON_NULL';
|
|
18319
|
+
name: never;
|
|
18320
|
+
ofType: {
|
|
18321
|
+
kind: 'SCALAR';
|
|
18322
|
+
name: 'Boolean';
|
|
18323
|
+
ofType: null;
|
|
18324
|
+
};
|
|
18325
|
+
};
|
|
18326
|
+
};
|
|
18327
|
+
'flatRateAmount': {
|
|
18328
|
+
name: 'flatRateAmount';
|
|
18329
|
+
type: {
|
|
18330
|
+
kind: 'OBJECT';
|
|
18331
|
+
name: 'Money';
|
|
18332
|
+
ofType: null;
|
|
18333
|
+
};
|
|
18334
|
+
};
|
|
18335
|
+
'restockingFeePercentage': {
|
|
18336
|
+
name: 'restockingFeePercentage';
|
|
18337
|
+
type: {
|
|
18338
|
+
kind: 'SCALAR';
|
|
18339
|
+
name: 'Float';
|
|
18340
|
+
ofType: null;
|
|
18341
|
+
};
|
|
18342
|
+
};
|
|
18343
|
+
'returnShippingCost': {
|
|
18344
|
+
name: 'returnShippingCost';
|
|
18345
|
+
type: {
|
|
18346
|
+
kind: 'NON_NULL';
|
|
18347
|
+
name: never;
|
|
18348
|
+
ofType: {
|
|
18349
|
+
kind: 'ENUM';
|
|
18350
|
+
name: 'ShopReturnShippingCostEnum';
|
|
18351
|
+
ofType: null;
|
|
18352
|
+
};
|
|
18353
|
+
};
|
|
18354
|
+
};
|
|
18355
|
+
'returnWindowDays': {
|
|
18356
|
+
name: 'returnWindowDays';
|
|
18357
|
+
type: {
|
|
18358
|
+
kind: 'SCALAR';
|
|
18359
|
+
name: 'Int';
|
|
18360
|
+
ofType: null;
|
|
18361
|
+
};
|
|
18362
|
+
};
|
|
18363
|
+
};
|
|
18364
|
+
};
|
|
18365
|
+
'ShopReturnShippingCostEnum': {
|
|
18366
|
+
name: 'ShopReturnShippingCostEnum';
|
|
18367
|
+
enumValues: 'CUSTOMER_PROVIDED' | 'FLAT_RATE' | 'FREE';
|
|
18368
|
+
};
|
|
18279
18369
|
'ShopSeo': {
|
|
18280
18370
|
kind: 'OBJECT';
|
|
18281
18371
|
name: 'ShopSeo';
|
package/dist/product.d.ts
CHANGED
package/dist/product.js
CHANGED
|
@@ -196,6 +196,7 @@ const FetchProductFullQuery = graphql(`
|
|
|
196
196
|
themeTemplate
|
|
197
197
|
...BadgeTags
|
|
198
198
|
giftCard
|
|
199
|
+
finalSale
|
|
199
200
|
descriptionHtml
|
|
200
201
|
# Server-side plain-text projection of descriptionHtml — Rails
|
|
201
202
|
# strips tags AND decodes HTML entities, which a client-side regex
|
|
@@ -207,6 +208,7 @@ const FetchProductFullQuery = graphql(`
|
|
|
207
208
|
}
|
|
208
209
|
defaultVariant {
|
|
209
210
|
...ProductVariantNode
|
|
211
|
+
gtin
|
|
210
212
|
}
|
|
211
213
|
options {
|
|
212
214
|
...ProductOptionNode
|
|
@@ -477,7 +479,9 @@ async function loadProductFull(handle, locale) {
|
|
|
477
479
|
title: p.seo.title,
|
|
478
480
|
description: p.seo.description
|
|
479
481
|
},
|
|
480
|
-
metafields: toMetafieldMap(p.metafields.nodes)
|
|
482
|
+
metafields: toMetafieldMap(p.metafields.nodes),
|
|
483
|
+
finalSale: p.finalSale,
|
|
484
|
+
gtin: p.defaultVariant.gtin ?? null
|
|
481
485
|
};
|
|
482
486
|
} catch (err) {
|
|
483
487
|
console.warn("[fetchProductFull] failed:", err);
|
|
@@ -4753,6 +4753,11 @@ type Shop implements Node {
|
|
|
4753
4753
|
"""
|
|
4754
4754
|
refundPolicy: ShopPolicy
|
|
4755
4755
|
|
|
4756
|
+
"""
|
|
4757
|
+
Per-shop return policy defaults. Always present — an unconfigured shop reads back the defaults.
|
|
4758
|
+
"""
|
|
4759
|
+
returnPolicy: ShopReturnPolicy!
|
|
4760
|
+
|
|
4756
4761
|
"""
|
|
4757
4762
|
Shop-level SEO settings (homepage meta, social image, verification tokens). Null until the merchant configures it.
|
|
4758
4763
|
"""
|
|
@@ -7951,6 +7956,11 @@ type ShopProduct implements Node {
|
|
|
7951
7956
|
"""
|
|
7952
7957
|
featuredMedia: FileInterface
|
|
7953
7958
|
|
|
7959
|
+
"""
|
|
7960
|
+
Whether the return policy marks the whole product final sale (no returns). Resolve on one product at a time.
|
|
7961
|
+
"""
|
|
7962
|
+
finalSale: Boolean!
|
|
7963
|
+
|
|
7954
7964
|
"""
|
|
7955
7965
|
True when this is a digital gift-card product (gift-card category + every
|
|
7956
7966
|
variant unshipped). Powers the storefront recipient form and digital fulfillment.
|
|
@@ -8609,6 +8619,11 @@ type ShopProductVariant implements Node {
|
|
|
8609
8619
|
First variant media — used as a thumbnail
|
|
8610
8620
|
"""
|
|
8611
8621
|
featuredMedia: FileInterface
|
|
8622
|
+
|
|
8623
|
+
"""
|
|
8624
|
+
The GTIN for structured data: the one the merchant entered, else a barcode that passes the GS1 check; null otherwise.
|
|
8625
|
+
"""
|
|
8626
|
+
gtin: String
|
|
8612
8627
|
id: ID!
|
|
8613
8628
|
|
|
8614
8629
|
"""
|
|
@@ -8791,6 +8806,56 @@ type ShopProductVariantEdge {
|
|
|
8791
8806
|
node: ShopProductVariant!
|
|
8792
8807
|
}
|
|
8793
8808
|
|
|
8809
|
+
"""
|
|
8810
|
+
Per-shop return policy defaults. Advisory only — nothing here blocks a merchant-created return.
|
|
8811
|
+
"""
|
|
8812
|
+
type ShopReturnPolicy {
|
|
8813
|
+
"""
|
|
8814
|
+
Whether the merchant has saved this policy. False means every value is a column default.
|
|
8815
|
+
"""
|
|
8816
|
+
configured: Boolean!
|
|
8817
|
+
|
|
8818
|
+
"""
|
|
8819
|
+
Flat return-shipping amount in the shop currency. Null unless `returnShippingCost` is FLAT_RATE.
|
|
8820
|
+
"""
|
|
8821
|
+
flatRateAmount: Money
|
|
8822
|
+
|
|
8823
|
+
"""
|
|
8824
|
+
Default restocking fee, 0..100. Null means no restocking fee is suggested.
|
|
8825
|
+
"""
|
|
8826
|
+
restockingFeePercentage: Float
|
|
8827
|
+
|
|
8828
|
+
"""
|
|
8829
|
+
Who pays return shipping by default.
|
|
8830
|
+
"""
|
|
8831
|
+
returnShippingCost: ShopReturnShippingCostEnum!
|
|
8832
|
+
|
|
8833
|
+
"""
|
|
8834
|
+
Days after shipment a buyer may return an item. Null means an unlimited window.
|
|
8835
|
+
"""
|
|
8836
|
+
returnWindowDays: Int
|
|
8837
|
+
}
|
|
8838
|
+
|
|
8839
|
+
"""
|
|
8840
|
+
Who pays to ship a return back, by default.
|
|
8841
|
+
"""
|
|
8842
|
+
enum ShopReturnShippingCostEnum {
|
|
8843
|
+
"""
|
|
8844
|
+
The buyer arranges and pays for return shipping.
|
|
8845
|
+
"""
|
|
8846
|
+
CUSTOMER_PROVIDED
|
|
8847
|
+
|
|
8848
|
+
"""
|
|
8849
|
+
A fixed amount is withheld; see `flatRateAmount`.
|
|
8850
|
+
"""
|
|
8851
|
+
FLAT_RATE
|
|
8852
|
+
|
|
8853
|
+
"""
|
|
8854
|
+
The merchant covers return shipping.
|
|
8855
|
+
"""
|
|
8856
|
+
FREE
|
|
8857
|
+
}
|
|
8858
|
+
|
|
8794
8859
|
"""
|
|
8795
8860
|
Per-shop SEO settings consumed by the storefront theme.
|
|
8796
8861
|
"""
|
|
@@ -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.
|
|
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/
|
|
63
|
-
"@shop-on/
|
|
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",
|