@shop-on/theme-server 0.1.0 → 0.3.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/account-state.js +55 -0
- package/dist/account.js +6 -1
- package/dist/analytics-proxy.js +3 -1
- package/dist/cart-session.d.ts +3 -5
- package/dist/cart-session.js +8 -18
- package/dist/checkout.d.ts +12 -11
- package/dist/checkout.js +64 -11
- package/dist/cookie-secret.js +10 -0
- package/dist/customer-handoff.d.ts +1 -1
- package/dist/customer-handoff.js +15 -3
- package/dist/customer-session.js +8 -18
- package/dist/graphql-env.d.ts +172 -0
- package/dist/graphql.js +16 -12
- package/dist/password-session.js +4 -9
- package/dist/schema.storefront.graphql +137 -5
- package/dist/shop.d.ts +1 -0
- package/dist/shop.js +1 -0
- package/dist/trace.js +5 -0
- package/package.json +10 -10
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { derivedCookieSecret } from "./cookie-secret.js";
|
|
2
|
+
import { createServerOnlyFn } from "@tanstack/react-start";
|
|
3
|
+
import { createShopBoundCookieConfig } from "@shop-on/customer-runtime/session-cookie";
|
|
4
|
+
import { ACCOUNT_STATE_FORMAT } from "@shop-on/customer-runtime/handoff-state";
|
|
5
|
+
//#region src/account-state.ts
|
|
6
|
+
const TTL_SECONDS = 1800;
|
|
7
|
+
const DOMAIN_LABEL = "account-state";
|
|
8
|
+
const sessionConfig = createServerOnlyFn(async () => {
|
|
9
|
+
return createShopBoundCookieConfig({
|
|
10
|
+
name: "account_state",
|
|
11
|
+
password: await derivedCookieSecret(DOMAIN_LABEL),
|
|
12
|
+
maxAge: TTL_SECONDS
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
const loadSession = createServerOnlyFn(async () => {
|
|
16
|
+
const { openSession } = await import("@shop-on/customer-runtime/sealed-session");
|
|
17
|
+
return openSession(await sessionConfig());
|
|
18
|
+
});
|
|
19
|
+
/** The state this browser holds, or a fresh one once it is past its 30
|
|
20
|
+
* minutes. Reused so a second tab bouncing to account doesn't void the state
|
|
21
|
+
* an in-flight sign-in will come back with, and rewritten on reuse: the
|
|
22
|
+
* cookie's expiry counts from its first write, so only clearing first lets
|
|
23
|
+
* it outlast the OTP round trip that reuse starts. */
|
|
24
|
+
const issueAccountState = createServerOnlyFn(async () => {
|
|
25
|
+
const session = await loadSession();
|
|
26
|
+
const { state: kept, issuedAt } = session.data;
|
|
27
|
+
if (kept && issuedAt && Date.now() - issuedAt < TTL_SECONDS * 1e3) {
|
|
28
|
+
await session.clear();
|
|
29
|
+
await session.update({
|
|
30
|
+
state: kept,
|
|
31
|
+
issuedAt
|
|
32
|
+
});
|
|
33
|
+
return kept;
|
|
34
|
+
}
|
|
35
|
+
const { randomBytes } = await import("node:crypto");
|
|
36
|
+
const state = randomBytes(32).toString("base64url");
|
|
37
|
+
await session.clear();
|
|
38
|
+
await session.update({
|
|
39
|
+
state,
|
|
40
|
+
issuedAt: Date.now()
|
|
41
|
+
});
|
|
42
|
+
return state;
|
|
43
|
+
});
|
|
44
|
+
/** Whether `state` is the one this browser was issued. Constant-time. */
|
|
45
|
+
const matchesAccountState = createServerOnlyFn(async (state) => {
|
|
46
|
+
if (!state || !ACCOUNT_STATE_FORMAT.test(state)) return false;
|
|
47
|
+
const issued = (await loadSession()).data.state;
|
|
48
|
+
if (!issued) return false;
|
|
49
|
+
const { timingSafeEqual } = await import("node:crypto");
|
|
50
|
+
const a = Buffer.from(issued);
|
|
51
|
+
const b = Buffer.from(state);
|
|
52
|
+
return a.length === b.length && timingSafeEqual(a, b);
|
|
53
|
+
});
|
|
54
|
+
//#endregion
|
|
55
|
+
export { issueAccountState, matchesAccountState };
|
package/dist/account.js
CHANGED
|
@@ -22,7 +22,12 @@ async function getAccountRedirectUrl(data) {
|
|
|
22
22
|
xForwardedHost: true,
|
|
23
23
|
xForwardedProto: true
|
|
24
24
|
}).origin;
|
|
25
|
-
|
|
25
|
+
const returnTo = new URL(`${origin}${safePath}`);
|
|
26
|
+
const { issueAccountState } = await import("./account-state.js");
|
|
27
|
+
returnTo.searchParams.set("state", await issueAccountState());
|
|
28
|
+
const { markResponsePrivate } = await import("@shop-on/customer-runtime/handoff-state");
|
|
29
|
+
await markResponsePrivate();
|
|
30
|
+
target.searchParams.set("return_to", returnTo.toString());
|
|
26
31
|
}
|
|
27
32
|
return { url: target.toString() };
|
|
28
33
|
}
|
package/dist/analytics-proxy.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { requestTraceHeaders } from "./trace.js";
|
|
1
2
|
import { getStorefrontToken, storefrontApiUrl } from "./graphql.js";
|
|
2
3
|
import { isIP } from "node:net";
|
|
3
4
|
//#region src/analytics-proxy.ts
|
|
@@ -8,7 +9,8 @@ const PATHS = {
|
|
|
8
9
|
async function proxyAnalytics({ endpoint, body, visitToken, visitorToken, userAgent, clientIp, fbp, fbc, eventSourceUrl }) {
|
|
9
10
|
const headers = {
|
|
10
11
|
Authorization: `Bearer ${await getStorefrontToken()}`,
|
|
11
|
-
"Content-Type": "application/json"
|
|
12
|
+
"Content-Type": "application/json",
|
|
13
|
+
...requestTraceHeaders()
|
|
12
14
|
};
|
|
13
15
|
if (visitToken) headers["Ahoy-Visit"] = visitToken;
|
|
14
16
|
if (visitorToken) headers["Ahoy-Visitor"] = visitorToken;
|
package/dist/cart-session.d.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SealedSession } from "@shop-on/customer-runtime/sealed-session";
|
|
2
2
|
//#region src/cart-session.d.ts
|
|
3
3
|
export interface CartSessionData {
|
|
4
4
|
token?: string;
|
|
5
5
|
expiresAtMs?: number;
|
|
6
6
|
}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
//#endregion
|
|
10
|
-
export {};
|
|
7
|
+
export declare const useCartSession: () => Promise<SealedSession<CartSessionData>>;
|
|
8
|
+
//#endregion
|
package/dist/cart-session.js
CHANGED
|
@@ -1,29 +1,19 @@
|
|
|
1
|
+
import { derivedCookieSecret } from "./cookie-secret.js";
|
|
1
2
|
import { createServerOnlyFn } from "@tanstack/react-start";
|
|
3
|
+
import { createShopBoundCookieConfig } from "@shop-on/customer-runtime/session-cookie";
|
|
2
4
|
//#region src/cart-session.ts
|
|
3
5
|
const TTL_SECONDS = 2592e3;
|
|
4
6
|
const DOMAIN_LABEL = "cart";
|
|
5
|
-
const readSecret = createServerOnlyFn(async () => {
|
|
6
|
-
const customer = process.env.STOREFRONT_CUSTOMER_SESSION_SECRET;
|
|
7
|
-
if (!customer) throw new Error("cart-session: STOREFRONT_CUSTOMER_SESSION_SECRET missing — orchestrator did not inject it");
|
|
8
|
-
const { createHmac } = await import("node:crypto");
|
|
9
|
-
return createHmac("sha256", customer).update(DOMAIN_LABEL).digest("hex");
|
|
10
|
-
});
|
|
11
7
|
const sessionConfig = createServerOnlyFn(async () => {
|
|
12
|
-
return {
|
|
13
|
-
password: await readSecret(),
|
|
8
|
+
return createShopBoundCookieConfig({
|
|
14
9
|
name: "cart_session",
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
sameSite: "lax",
|
|
19
|
-
httpOnly: true,
|
|
20
|
-
path: "/"
|
|
21
|
-
}
|
|
22
|
-
};
|
|
10
|
+
password: await derivedCookieSecret(DOMAIN_LABEL),
|
|
11
|
+
maxAge: TTL_SECONDS
|
|
12
|
+
});
|
|
23
13
|
});
|
|
24
14
|
const useCartSession = createServerOnlyFn(async () => {
|
|
25
|
-
const {
|
|
26
|
-
return
|
|
15
|
+
const { openSession } = await import("@shop-on/customer-runtime/sealed-session");
|
|
16
|
+
return openSession(await sessionConfig());
|
|
27
17
|
});
|
|
28
18
|
//#endregion
|
|
29
19
|
export { useCartSession };
|
package/dist/checkout.d.ts
CHANGED
|
@@ -4,21 +4,22 @@ export type CheckoutHandoffResult = {
|
|
|
4
4
|
redirectTo: string;
|
|
5
5
|
} | {
|
|
6
6
|
ok: false;
|
|
7
|
-
reason: "no_cart";
|
|
7
|
+
reason: "no_cart" | "bad_state";
|
|
8
8
|
};
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* on the checkout app that consumes it.
|
|
10
|
+
* The theme's `/checkout`, both passes. `state` is the URL's `checkout_state`,
|
|
11
|
+
* a REQUIRED key even when `undefined`: a caller that never passed it would
|
|
12
|
+
* bounce theme ↔ `/begin` forever, and this way that is a compile error.
|
|
14
13
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
14
|
+
* - No state: the URL of checkout's `/begin`, which binds this browser and
|
|
15
|
+
* returns the buyer here with one. Mints nothing.
|
|
16
|
+
* - A state: mint the cart handoff with it (`mintCheckoutHandoff`).
|
|
17
|
+
*
|
|
18
|
+
* The customer-session token is injected: forwarded as `X-Customer-Session` on
|
|
19
|
+
* the mint so Rails folds the buyer into the handoff payload in one trip.
|
|
20
20
|
*/
|
|
21
|
-
export declare function startCheckoutHandoff(opts
|
|
21
|
+
export declare function startCheckoutHandoff(opts: {
|
|
22
|
+
state: string | undefined;
|
|
22
23
|
customerToken?: string;
|
|
23
24
|
}): Promise<CheckoutHandoffResult>;
|
|
24
25
|
//#endregion
|
package/dist/checkout.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { graphql, request } from "./graphql.js";
|
|
2
|
+
import { fetchShopInfo } from "./shop.js";
|
|
2
3
|
import { useCartSession } from "./cart-session.js";
|
|
3
4
|
import { parseMarketingCookies } from "./marketing-cookies.js";
|
|
4
5
|
import { createServerOnlyFn } from "@tanstack/react-start";
|
|
6
|
+
import { CHECKOUT_STATE_FORMAT, isLoopback, markResponsePrivate } from "@shop-on/customer-runtime/handoff-state";
|
|
5
7
|
//#region src/checkout.ts
|
|
6
8
|
const CartCheckoutMutation = graphql(`
|
|
7
9
|
mutation CartCheckout($input: CartCheckoutInput!) {
|
|
@@ -25,24 +27,73 @@ const readMarketingAttribution = createServerOnlyFn(async () => {
|
|
|
25
27
|
return null;
|
|
26
28
|
}
|
|
27
29
|
});
|
|
30
|
+
function publicCheckoutUrl(origin) {
|
|
31
|
+
const url = new URL("/checkout", origin);
|
|
32
|
+
if (!isLoopback(url.hostname)) url.protocol = "https:";
|
|
33
|
+
return url.toString();
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The theme's `/checkout`, both passes. `state` is the URL's `checkout_state`,
|
|
37
|
+
* a REQUIRED key even when `undefined`: a caller that never passed it would
|
|
38
|
+
* bounce theme ↔ `/begin` forever, and this way that is a compile error.
|
|
39
|
+
*
|
|
40
|
+
* - No state: the URL of checkout's `/begin`, which binds this browser and
|
|
41
|
+
* returns the buyer here with one. Mints nothing.
|
|
42
|
+
* - A state: mint the cart handoff with it (`mintCheckoutHandoff`).
|
|
43
|
+
*
|
|
44
|
+
* The customer-session token is injected: forwarded as `X-Customer-Session` on
|
|
45
|
+
* the mint so Rails folds the buyer into the handoff payload in one trip.
|
|
46
|
+
*/
|
|
47
|
+
function startCheckoutHandoff(opts) {
|
|
48
|
+
return opts.state === void 0 ? beginCheckoutUrl() : mintCheckoutHandoff({
|
|
49
|
+
state: opts.state,
|
|
50
|
+
customerToken: opts.customerToken
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
async function beginCheckoutUrl() {
|
|
54
|
+
if (!(await useCartSession()).data.token) return {
|
|
55
|
+
ok: false,
|
|
56
|
+
reason: "no_cart"
|
|
57
|
+
};
|
|
58
|
+
await markResponsePrivate();
|
|
59
|
+
const shop = await fetchShopInfo();
|
|
60
|
+
if (!shop) throw new Error("[checkout] shop info unavailable from Rails");
|
|
61
|
+
const { getPublicOrigin } = await import("./seo-context.js");
|
|
62
|
+
const url = new URL(`${shop.checkoutUrl}/begin`);
|
|
63
|
+
url.searchParams.set("return_to", publicCheckoutUrl(getPublicOrigin()));
|
|
64
|
+
return {
|
|
65
|
+
ok: true,
|
|
66
|
+
redirectTo: url.toString()
|
|
67
|
+
};
|
|
68
|
+
}
|
|
28
69
|
/**
|
|
29
|
-
* Read this shop's `cart_session` cookie, call Rails `cartCheckout
|
|
30
|
-
* the
|
|
31
|
-
* folds the customer into the handoff payload in one trip), and return the URL
|
|
32
|
-
* on the checkout app that consumes it.
|
|
70
|
+
* Read this shop's `cart_session` cookie, call Rails `cartCheckout`, and return
|
|
71
|
+
* the URL on the checkout app that consumes it, `checkout_state` appended.
|
|
33
72
|
*
|
|
34
|
-
* `no_cart` covers
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
73
|
+
* `no_cart` covers "no cart cookie" and Rails' `NOT_FOUND` (cart digest miss /
|
|
74
|
+
* cross-shop) / `CART_EMPTY`; `bad_state` a state of the wrong shape (never
|
|
75
|
+
* bounced to `/begin` again, so it can't loop). Any other userError code is
|
|
76
|
+
* unexpected and throws (matching `request`'s retry-once-then-throw contract
|
|
77
|
+
* for top-level GraphQL errors); the calling route sends the buyer home rather
|
|
78
|
+
* than render on a URL holding a state.
|
|
39
79
|
*/
|
|
40
|
-
async function
|
|
80
|
+
async function mintCheckoutHandoff(opts) {
|
|
41
81
|
const cartToken = (await useCartSession()).data.token;
|
|
42
82
|
if (!cartToken) return {
|
|
43
83
|
ok: false,
|
|
44
84
|
reason: "no_cart"
|
|
45
85
|
};
|
|
86
|
+
await markResponsePrivate();
|
|
87
|
+
if (!CHECKOUT_STATE_FORMAT.test(opts.state)) {
|
|
88
|
+
console.warn(JSON.stringify({
|
|
89
|
+
event: "theme_checkout_bad_state",
|
|
90
|
+
ts: Date.now()
|
|
91
|
+
}));
|
|
92
|
+
return {
|
|
93
|
+
ok: false,
|
|
94
|
+
reason: "bad_state"
|
|
95
|
+
};
|
|
96
|
+
}
|
|
46
97
|
const attribution = await readMarketingAttribution();
|
|
47
98
|
const result = await request(CartCheckoutMutation, { input: attribution ? {
|
|
48
99
|
cartToken,
|
|
@@ -57,9 +108,11 @@ async function startCheckoutHandoff(opts = {}) {
|
|
|
57
108
|
};
|
|
58
109
|
throw new Error(`[checkout] cartCheckout returned no URL (code=${code ?? "UNKNOWN"})`);
|
|
59
110
|
}
|
|
111
|
+
const bound = new URL(checkoutUrl);
|
|
112
|
+
bound.searchParams.set("checkout_state", opts.state);
|
|
60
113
|
return {
|
|
61
114
|
ok: true,
|
|
62
|
-
redirectTo:
|
|
115
|
+
redirectTo: bound.toString()
|
|
63
116
|
};
|
|
64
117
|
}
|
|
65
118
|
//#endregion
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createServerOnlyFn } from "@tanstack/react-start";
|
|
2
|
+
//#region src/cookie-secret.ts
|
|
3
|
+
const derivedCookieSecret = createServerOnlyFn(async (label) => {
|
|
4
|
+
const secret = process.env.STOREFRONT_CUSTOMER_SESSION_SECRET;
|
|
5
|
+
if (!secret) throw new Error(`${label} cookie: STOREFRONT_CUSTOMER_SESSION_SECRET missing — orchestrator did not inject it`);
|
|
6
|
+
const { createHmac } = await import("node:crypto");
|
|
7
|
+
return createHmac("sha256", secret).update(label).digest("hex");
|
|
8
|
+
});
|
|
9
|
+
//#endregion
|
|
10
|
+
export { derivedCookieSecret };
|
|
@@ -9,5 +9,5 @@ export type ConsumeCustomerHandoffResult = {
|
|
|
9
9
|
* Plain server-only helper. Tested directly; each theme wraps it in a thin
|
|
10
10
|
* `createServerFn` for its root route's `beforeLoad`.
|
|
11
11
|
*/
|
|
12
|
-
export declare const consumeCustomerHandoff: (nonce: string) => Promise<ConsumeCustomerHandoffResult>;
|
|
12
|
+
export declare const consumeCustomerHandoff: (nonce: string, state: string | undefined) => Promise<ConsumeCustomerHandoffResult>;
|
|
13
13
|
//#endregion
|
package/dist/customer-handoff.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { writeCustomerSession } from "./customer-session.js";
|
|
2
|
+
import { matchesAccountState } from "./account-state.js";
|
|
2
3
|
import { createServerOnlyFn } from "@tanstack/react-start";
|
|
3
4
|
//#region src/customer-handoff.ts
|
|
4
5
|
/**
|
|
5
6
|
* Plain server-only helper. Tested directly; each theme wraps it in a thin
|
|
6
7
|
* `createServerFn` for its root route's `beforeLoad`.
|
|
7
8
|
*/
|
|
8
|
-
const consumeCustomerHandoff = createServerOnlyFn(async (nonce) => {
|
|
9
|
+
const consumeCustomerHandoff = createServerOnlyFn(async (nonce, state) => {
|
|
9
10
|
try {
|
|
10
|
-
return await consumeAndStore(nonce);
|
|
11
|
+
return await consumeAndStore(nonce, state);
|
|
11
12
|
} catch (err) {
|
|
12
13
|
console.warn(JSON.stringify({
|
|
13
14
|
event: "theme_customer_handoff_consume_error",
|
|
@@ -20,7 +21,18 @@ const consumeCustomerHandoff = createServerOnlyFn(async (nonce) => {
|
|
|
20
21
|
};
|
|
21
22
|
}
|
|
22
23
|
});
|
|
23
|
-
const consumeAndStore = createServerOnlyFn(async (nonce) => {
|
|
24
|
+
const consumeAndStore = createServerOnlyFn(async (nonce, state) => {
|
|
25
|
+
if (!await matchesAccountState(state)) {
|
|
26
|
+
console.warn(JSON.stringify({
|
|
27
|
+
event: "theme_customer_handoff_unbound",
|
|
28
|
+
hasState: Boolean(state),
|
|
29
|
+
ts: Date.now()
|
|
30
|
+
}));
|
|
31
|
+
return {
|
|
32
|
+
ok: false,
|
|
33
|
+
reason: "unbound"
|
|
34
|
+
};
|
|
35
|
+
}
|
|
24
36
|
const { consumeCustomerSessionHandoff } = await import("@shop-on/customer-runtime/customer-session-handoff");
|
|
25
37
|
const result = await consumeCustomerSessionHandoff(nonce);
|
|
26
38
|
if (!result.ok) {
|
package/dist/customer-session.js
CHANGED
|
@@ -1,29 +1,19 @@
|
|
|
1
|
+
import { derivedCookieSecret } from "./cookie-secret.js";
|
|
1
2
|
import { createServerOnlyFn } from "@tanstack/react-start";
|
|
3
|
+
import { createShopBoundCookieConfig } from "@shop-on/customer-runtime/session-cookie";
|
|
2
4
|
//#region src/customer-session.ts
|
|
3
5
|
const TTL_SECONDS = 2592e3;
|
|
4
6
|
const DOMAIN_LABEL = "customer";
|
|
5
|
-
const readSecret = createServerOnlyFn(async () => {
|
|
6
|
-
const secret = process.env.STOREFRONT_CUSTOMER_SESSION_SECRET;
|
|
7
|
-
if (!secret) throw new Error("customer-session: STOREFRONT_CUSTOMER_SESSION_SECRET missing — orchestrator did not inject it");
|
|
8
|
-
const { createHmac } = await import("node:crypto");
|
|
9
|
-
return createHmac("sha256", secret).update(DOMAIN_LABEL).digest("hex");
|
|
10
|
-
});
|
|
11
7
|
const sessionConfig = createServerOnlyFn(async () => {
|
|
12
|
-
return {
|
|
13
|
-
password: await readSecret(),
|
|
8
|
+
return createShopBoundCookieConfig({
|
|
14
9
|
name: "customer_session",
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
sameSite: "lax",
|
|
19
|
-
httpOnly: true,
|
|
20
|
-
path: "/"
|
|
21
|
-
}
|
|
22
|
-
};
|
|
10
|
+
password: await derivedCookieSecret(DOMAIN_LABEL),
|
|
11
|
+
maxAge: TTL_SECONDS
|
|
12
|
+
});
|
|
23
13
|
});
|
|
24
14
|
const loadSession = createServerOnlyFn(async () => {
|
|
25
|
-
const {
|
|
26
|
-
return
|
|
15
|
+
const { openSession } = await import("@shop-on/customer-runtime/sealed-session");
|
|
16
|
+
return openSession(await sessionConfig());
|
|
27
17
|
});
|
|
28
18
|
const readCustomerSession = createServerOnlyFn(async () => {
|
|
29
19
|
try {
|
package/dist/graphql-env.d.ts
CHANGED
|
@@ -2158,6 +2158,22 @@ export type introspection_types = {
|
|
|
2158
2158
|
ofType: null;
|
|
2159
2159
|
};
|
|
2160
2160
|
defaultValue: null;
|
|
2161
|
+
}, {
|
|
2162
|
+
name: 'latitude';
|
|
2163
|
+
type: {
|
|
2164
|
+
kind: 'SCALAR';
|
|
2165
|
+
name: 'Float';
|
|
2166
|
+
ofType: null;
|
|
2167
|
+
};
|
|
2168
|
+
defaultValue: null;
|
|
2169
|
+
}, {
|
|
2170
|
+
name: 'longitude';
|
|
2171
|
+
type: {
|
|
2172
|
+
kind: 'SCALAR';
|
|
2173
|
+
name: 'Float';
|
|
2174
|
+
ofType: null;
|
|
2175
|
+
};
|
|
2176
|
+
defaultValue: null;
|
|
2161
2177
|
}, {
|
|
2162
2178
|
name: 'phone';
|
|
2163
2179
|
type: {
|
|
@@ -2794,6 +2810,14 @@ export type introspection_types = {
|
|
|
2794
2810
|
};
|
|
2795
2811
|
};
|
|
2796
2812
|
defaultValue: null;
|
|
2813
|
+
}, {
|
|
2814
|
+
name: 'retryKey';
|
|
2815
|
+
type: {
|
|
2816
|
+
kind: 'SCALAR';
|
|
2817
|
+
name: 'String';
|
|
2818
|
+
ofType: null;
|
|
2819
|
+
};
|
|
2820
|
+
defaultValue: null;
|
|
2797
2821
|
}];
|
|
2798
2822
|
};
|
|
2799
2823
|
'CheckoutRecoverPayload': {
|
|
@@ -2838,6 +2862,10 @@ export type introspection_types = {
|
|
|
2838
2862
|
};
|
|
2839
2863
|
};
|
|
2840
2864
|
};
|
|
2865
|
+
'CheckoutRecoveryStatusEnum': {
|
|
2866
|
+
name: 'CheckoutRecoveryStatusEnum';
|
|
2867
|
+
enumValues: 'EXPIRED' | 'INVALID' | 'VALID';
|
|
2868
|
+
};
|
|
2841
2869
|
'CheckoutShippingAddress': {
|
|
2842
2870
|
kind: 'OBJECT';
|
|
2843
2871
|
name: 'CheckoutShippingAddress';
|
|
@@ -2942,6 +2970,22 @@ export type introspection_types = {
|
|
|
2942
2970
|
ofType: null;
|
|
2943
2971
|
};
|
|
2944
2972
|
};
|
|
2973
|
+
'latitude': {
|
|
2974
|
+
name: 'latitude';
|
|
2975
|
+
type: {
|
|
2976
|
+
kind: 'SCALAR';
|
|
2977
|
+
name: 'Float';
|
|
2978
|
+
ofType: null;
|
|
2979
|
+
};
|
|
2980
|
+
};
|
|
2981
|
+
'longitude': {
|
|
2982
|
+
name: 'longitude';
|
|
2983
|
+
type: {
|
|
2984
|
+
kind: 'SCALAR';
|
|
2985
|
+
name: 'Float';
|
|
2986
|
+
ofType: null;
|
|
2987
|
+
};
|
|
2988
|
+
};
|
|
2945
2989
|
'phone': {
|
|
2946
2990
|
name: 'phone';
|
|
2947
2991
|
type: {
|
|
@@ -3654,6 +3698,10 @@ export type introspection_types = {
|
|
|
3654
3698
|
};
|
|
3655
3699
|
};
|
|
3656
3700
|
};
|
|
3701
|
+
'CourierPromptEnum': {
|
|
3702
|
+
name: 'CourierPromptEnum';
|
|
3703
|
+
enumValues: 'PHONE' | 'PIN' | 'UNAVAILABLE';
|
|
3704
|
+
};
|
|
3657
3705
|
'CropRegionEnum': {
|
|
3658
3706
|
name: 'CropRegionEnum';
|
|
3659
3707
|
enumValues: 'BOTTOM' | 'CENTER' | 'LEFT' | 'RIGHT' | 'TOP';
|
|
@@ -6583,6 +6631,22 @@ export type introspection_types = {
|
|
|
6583
6631
|
ofType: null;
|
|
6584
6632
|
};
|
|
6585
6633
|
};
|
|
6634
|
+
'latitude': {
|
|
6635
|
+
name: 'latitude';
|
|
6636
|
+
type: {
|
|
6637
|
+
kind: 'SCALAR';
|
|
6638
|
+
name: 'Float';
|
|
6639
|
+
ofType: null;
|
|
6640
|
+
};
|
|
6641
|
+
};
|
|
6642
|
+
'longitude': {
|
|
6643
|
+
name: 'longitude';
|
|
6644
|
+
type: {
|
|
6645
|
+
kind: 'SCALAR';
|
|
6646
|
+
name: 'Float';
|
|
6647
|
+
ofType: null;
|
|
6648
|
+
};
|
|
6649
|
+
};
|
|
6586
6650
|
'phone': {
|
|
6587
6651
|
name: 'phone';
|
|
6588
6652
|
type: {
|
|
@@ -7133,6 +7197,18 @@ export type introspection_types = {
|
|
|
7133
7197
|
ofType: null;
|
|
7134
7198
|
};
|
|
7135
7199
|
};
|
|
7200
|
+
'checkoutRecoveryStatus': {
|
|
7201
|
+
name: 'checkoutRecoveryStatus';
|
|
7202
|
+
type: {
|
|
7203
|
+
kind: 'NON_NULL';
|
|
7204
|
+
name: never;
|
|
7205
|
+
ofType: {
|
|
7206
|
+
kind: 'ENUM';
|
|
7207
|
+
name: 'CheckoutRecoveryStatusEnum';
|
|
7208
|
+
ofType: null;
|
|
7209
|
+
};
|
|
7210
|
+
};
|
|
7211
|
+
};
|
|
7136
7212
|
'collection': {
|
|
7137
7213
|
name: 'collection';
|
|
7138
7214
|
type: {
|
|
@@ -8693,6 +8769,18 @@ export type introspection_types = {
|
|
|
8693
8769
|
};
|
|
8694
8770
|
};
|
|
8695
8771
|
};
|
|
8772
|
+
'checkoutUrl': {
|
|
8773
|
+
name: 'checkoutUrl';
|
|
8774
|
+
type: {
|
|
8775
|
+
kind: 'NON_NULL';
|
|
8776
|
+
name: never;
|
|
8777
|
+
ofType: {
|
|
8778
|
+
kind: 'SCALAR';
|
|
8779
|
+
name: 'String';
|
|
8780
|
+
ofType: null;
|
|
8781
|
+
};
|
|
8782
|
+
};
|
|
8783
|
+
};
|
|
8696
8784
|
'city': {
|
|
8697
8785
|
name: 'city';
|
|
8698
8786
|
type: {
|
|
@@ -10473,6 +10561,14 @@ export type introspection_types = {
|
|
|
10473
10561
|
ofType: null;
|
|
10474
10562
|
};
|
|
10475
10563
|
};
|
|
10564
|
+
'deliveryCourierPrompt': {
|
|
10565
|
+
name: 'deliveryCourierPrompt';
|
|
10566
|
+
type: {
|
|
10567
|
+
kind: 'ENUM';
|
|
10568
|
+
name: 'CourierPromptEnum';
|
|
10569
|
+
ofType: null;
|
|
10570
|
+
};
|
|
10571
|
+
};
|
|
10476
10572
|
'deliveryOptionHandle': {
|
|
10477
10573
|
name: 'deliveryOptionHandle';
|
|
10478
10574
|
type: {
|
|
@@ -11877,6 +11973,18 @@ export type introspection_types = {
|
|
|
11877
11973
|
};
|
|
11878
11974
|
};
|
|
11879
11975
|
};
|
|
11976
|
+
'cashOnDeliveryAllowed': {
|
|
11977
|
+
name: 'cashOnDeliveryAllowed';
|
|
11978
|
+
type: {
|
|
11979
|
+
kind: 'NON_NULL';
|
|
11980
|
+
name: never;
|
|
11981
|
+
ofType: {
|
|
11982
|
+
kind: 'SCALAR';
|
|
11983
|
+
name: 'Boolean';
|
|
11984
|
+
ofType: null;
|
|
11985
|
+
};
|
|
11986
|
+
};
|
|
11987
|
+
};
|
|
11880
11988
|
'handle': {
|
|
11881
11989
|
name: 'handle';
|
|
11882
11990
|
type: {
|
|
@@ -12196,6 +12304,14 @@ export type introspection_types = {
|
|
|
12196
12304
|
ofType: null;
|
|
12197
12305
|
};
|
|
12198
12306
|
};
|
|
12307
|
+
'totalAmount': {
|
|
12308
|
+
name: 'totalAmount';
|
|
12309
|
+
type: {
|
|
12310
|
+
kind: 'SCALAR';
|
|
12311
|
+
name: 'Decimal';
|
|
12312
|
+
ofType: null;
|
|
12313
|
+
};
|
|
12314
|
+
};
|
|
12199
12315
|
'updatedAt': {
|
|
12200
12316
|
name: 'updatedAt';
|
|
12201
12317
|
type: {
|
|
@@ -15726,6 +15842,18 @@ export type introspection_types = {
|
|
|
15726
15842
|
};
|
|
15727
15843
|
};
|
|
15728
15844
|
};
|
|
15845
|
+
'cashOnDeliveryAllowed': {
|
|
15846
|
+
name: 'cashOnDeliveryAllowed';
|
|
15847
|
+
type: {
|
|
15848
|
+
kind: 'NON_NULL';
|
|
15849
|
+
name: never;
|
|
15850
|
+
ofType: {
|
|
15851
|
+
kind: 'SCALAR';
|
|
15852
|
+
name: 'Boolean';
|
|
15853
|
+
ofType: null;
|
|
15854
|
+
};
|
|
15855
|
+
};
|
|
15856
|
+
};
|
|
15729
15857
|
'handle': {
|
|
15730
15858
|
name: 'handle';
|
|
15731
15859
|
type: {
|
|
@@ -17848,6 +17976,18 @@ export type introspection_types = {
|
|
|
17848
17976
|
};
|
|
17849
17977
|
};
|
|
17850
17978
|
};
|
|
17979
|
+
'cashOnDeliveryAllowed': {
|
|
17980
|
+
name: 'cashOnDeliveryAllowed';
|
|
17981
|
+
type: {
|
|
17982
|
+
kind: 'NON_NULL';
|
|
17983
|
+
name: never;
|
|
17984
|
+
ofType: {
|
|
17985
|
+
kind: 'SCALAR';
|
|
17986
|
+
name: 'Boolean';
|
|
17987
|
+
ofType: null;
|
|
17988
|
+
};
|
|
17989
|
+
};
|
|
17990
|
+
};
|
|
17851
17991
|
'deliveryDetails': {
|
|
17852
17992
|
name: 'deliveryDetails';
|
|
17853
17993
|
type: {
|
|
@@ -20026,6 +20166,14 @@ export type introspection_types = {
|
|
|
20026
20166
|
name: 'ThemeFilter';
|
|
20027
20167
|
isOneOf: false;
|
|
20028
20168
|
inputFields: [{
|
|
20169
|
+
name: 'createdAt';
|
|
20170
|
+
type: {
|
|
20171
|
+
kind: 'INPUT_OBJECT';
|
|
20172
|
+
name: 'DateFilter';
|
|
20173
|
+
ofType: null;
|
|
20174
|
+
};
|
|
20175
|
+
defaultValue: null;
|
|
20176
|
+
}, {
|
|
20029
20177
|
name: 'handleCont';
|
|
20030
20178
|
type: {
|
|
20031
20179
|
kind: 'SCALAR';
|
|
@@ -20057,6 +20205,30 @@ export type introspection_types = {
|
|
|
20057
20205
|
ofType: null;
|
|
20058
20206
|
};
|
|
20059
20207
|
defaultValue: null;
|
|
20208
|
+
}, {
|
|
20209
|
+
name: 'query';
|
|
20210
|
+
type: {
|
|
20211
|
+
kind: 'INPUT_OBJECT';
|
|
20212
|
+
name: 'StringFilter';
|
|
20213
|
+
ofType: null;
|
|
20214
|
+
};
|
|
20215
|
+
defaultValue: null;
|
|
20216
|
+
}, {
|
|
20217
|
+
name: 'status';
|
|
20218
|
+
type: {
|
|
20219
|
+
kind: 'INPUT_OBJECT';
|
|
20220
|
+
name: 'EnumStringFilter';
|
|
20221
|
+
ofType: null;
|
|
20222
|
+
};
|
|
20223
|
+
defaultValue: null;
|
|
20224
|
+
}, {
|
|
20225
|
+
name: 'updatedAt';
|
|
20226
|
+
type: {
|
|
20227
|
+
kind: 'INPUT_OBJECT';
|
|
20228
|
+
name: 'DateFilter';
|
|
20229
|
+
ofType: null;
|
|
20230
|
+
};
|
|
20231
|
+
defaultValue: null;
|
|
20060
20232
|
}];
|
|
20061
20233
|
};
|
|
20062
20234
|
'ThemePreset': {
|
package/dist/graphql.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createLease } from "./lease.js";
|
|
2
2
|
import { requestSlot } from "./request-scope.js";
|
|
3
|
+
import { requestTraceHeaders } from "./trace.js";
|
|
3
4
|
import { initGraphQLTada } from "gql.tada";
|
|
4
5
|
import { Kind, print } from "graphql";
|
|
5
6
|
import { ClientError, GraphQLClient } from "graphql-request";
|
|
@@ -145,17 +146,18 @@ function extraHeaders(options) {
|
|
|
145
146
|
if (options?.acceptLanguage) extra["Accept-Language"] = options.acceptLanguage;
|
|
146
147
|
return extra;
|
|
147
148
|
}
|
|
148
|
-
function bearerHeaders(token, options) {
|
|
149
|
+
function bearerHeaders(token, options, trace) {
|
|
149
150
|
return {
|
|
150
151
|
Authorization: `Bearer ${token}`,
|
|
151
|
-
...extraHeaders(options)
|
|
152
|
+
...extraHeaders(options),
|
|
153
|
+
...trace
|
|
152
154
|
};
|
|
153
155
|
}
|
|
154
156
|
function invalidateToken(used) {
|
|
155
157
|
if (cached?.accessToken === used) cached = null;
|
|
156
158
|
}
|
|
157
|
-
async function executeSingle(document, variables, options) {
|
|
158
|
-
const send = async (token) => await buildClient(bearerHeaders(token, options)).request(document, variables);
|
|
159
|
+
async function executeSingle(document, variables, options, trace) {
|
|
160
|
+
const send = async (token) => await buildClient(bearerHeaders(token, options, trace)).request(document, variables);
|
|
159
161
|
const token = await getToken();
|
|
160
162
|
try {
|
|
161
163
|
return await send(token);
|
|
@@ -179,11 +181,12 @@ function enqueueBatched(queues, query, variables, options) {
|
|
|
179
181
|
let queue = queues.get(key);
|
|
180
182
|
if (!queue) {
|
|
181
183
|
const scheduled = [];
|
|
184
|
+
const trace = requestTraceHeaders();
|
|
182
185
|
queue = scheduled;
|
|
183
186
|
queues.set(key, scheduled);
|
|
184
187
|
setTimeout(() => {
|
|
185
188
|
queues.delete(key);
|
|
186
|
-
flushBatch(scheduled, options);
|
|
189
|
+
flushBatch(scheduled, options, trace);
|
|
187
190
|
}, 0);
|
|
188
191
|
}
|
|
189
192
|
queue.push({
|
|
@@ -194,15 +197,15 @@ function enqueueBatched(queues, query, variables, options) {
|
|
|
194
197
|
});
|
|
195
198
|
});
|
|
196
199
|
}
|
|
197
|
-
async function flushBatch(entries, options) {
|
|
200
|
+
async function flushBatch(entries, options, trace) {
|
|
198
201
|
const chunks = [];
|
|
199
202
|
for (let i = 0; i < entries.length; i += BATCH_MAX_QUERIES) chunks.push(entries.slice(i, i + BATCH_MAX_QUERIES));
|
|
200
203
|
await Promise.all(chunks.map(async (chunk) => {
|
|
201
204
|
try {
|
|
202
205
|
if (chunk.length === 1) {
|
|
203
206
|
const only = chunk[0];
|
|
204
|
-
only.resolve(await executeSingle(only.query, only.variables, options));
|
|
205
|
-
} else await executeBatchChunk(chunk, options);
|
|
207
|
+
only.resolve(await executeSingle(only.query, only.variables, options, trace));
|
|
208
|
+
} else await executeBatchChunk(chunk, options, trace);
|
|
206
209
|
} catch (err) {
|
|
207
210
|
for (const entry of chunk) entry.reject(err);
|
|
208
211
|
}
|
|
@@ -223,7 +226,7 @@ async function postWithAuthRetry(buildInit) {
|
|
|
223
226
|
invalidateToken(token);
|
|
224
227
|
return await post(await getToken());
|
|
225
228
|
}
|
|
226
|
-
async function executeBatchChunk(chunk, options) {
|
|
229
|
+
async function executeBatchChunk(chunk, options, trace) {
|
|
227
230
|
const body = JSON.stringify(chunk.map(({ query, variables }) => ({
|
|
228
231
|
query,
|
|
229
232
|
variables
|
|
@@ -231,7 +234,7 @@ async function executeBatchChunk(chunk, options) {
|
|
|
231
234
|
const res = await postWithAuthRetry((token) => ({
|
|
232
235
|
headers: {
|
|
233
236
|
"Content-Type": "application/json",
|
|
234
|
-
...bearerHeaders(token, options)
|
|
237
|
+
...bearerHeaders(token, options, trace)
|
|
235
238
|
},
|
|
236
239
|
body
|
|
237
240
|
}));
|
|
@@ -266,7 +269,7 @@ async function executeBatchChunk(chunk, options) {
|
|
|
266
269
|
async function request(document, variables, options) {
|
|
267
270
|
assertServerOnly();
|
|
268
271
|
const queues = isBatchableQuery(document) ? requestSlot(BATCH_QUEUES, () => /* @__PURE__ */ new Map()) : void 0;
|
|
269
|
-
if (!queues) return await executeSingle(document, variables, options);
|
|
272
|
+
if (!queues) return await executeSingle(document, variables, options, requestTraceHeaders());
|
|
270
273
|
return await enqueueBatched(queues, print(document), variables, options);
|
|
271
274
|
}
|
|
272
275
|
/**
|
|
@@ -285,6 +288,7 @@ async function request(document, variables, options) {
|
|
|
285
288
|
*/
|
|
286
289
|
async function requestUpload(document, variables, files, filesPath, options) {
|
|
287
290
|
assertServerOnly();
|
|
291
|
+
const trace = requestTraceHeaders();
|
|
288
292
|
const query = print(document);
|
|
289
293
|
const map = {};
|
|
290
294
|
files.forEach((_, i) => {
|
|
@@ -305,7 +309,7 @@ async function requestUpload(document, variables, files, filesPath, options) {
|
|
|
305
309
|
return form;
|
|
306
310
|
};
|
|
307
311
|
const res = await postWithAuthRetry((token) => ({
|
|
308
|
-
headers: bearerHeaders(token, options),
|
|
312
|
+
headers: bearerHeaders(token, options, trace),
|
|
309
313
|
body: buildBody()
|
|
310
314
|
}));
|
|
311
315
|
if (!res.ok) throw batchClientError({
|
package/dist/password-session.js
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
|
+
import { derivedCookieSecret } from "./cookie-secret.js";
|
|
1
2
|
import { createServerOnlyFn } from "@tanstack/react-start";
|
|
2
3
|
//#region src/password-session.ts
|
|
3
4
|
const TTL_SECONDS = 2592e3;
|
|
4
5
|
const DOMAIN_LABEL = "password";
|
|
5
|
-
const readSecret = createServerOnlyFn(async () => {
|
|
6
|
-
const secret = process.env.STOREFRONT_CUSTOMER_SESSION_SECRET;
|
|
7
|
-
if (!secret) throw new Error("password-session: STOREFRONT_CUSTOMER_SESSION_SECRET missing — orchestrator did not inject it");
|
|
8
|
-
const { createHmac } = await import("node:crypto");
|
|
9
|
-
return createHmac("sha256", secret).update(DOMAIN_LABEL).digest("hex");
|
|
10
|
-
});
|
|
11
6
|
const sessionConfig = createServerOnlyFn(async () => {
|
|
12
7
|
return {
|
|
13
|
-
password: await
|
|
8
|
+
password: await derivedCookieSecret(DOMAIN_LABEL),
|
|
14
9
|
name: "storefront_unlock",
|
|
15
10
|
maxAge: TTL_SECONDS,
|
|
16
11
|
cookie: {
|
|
@@ -22,8 +17,8 @@ const sessionConfig = createServerOnlyFn(async () => {
|
|
|
22
17
|
};
|
|
23
18
|
});
|
|
24
19
|
const loadSession = createServerOnlyFn(async () => {
|
|
25
|
-
const {
|
|
26
|
-
return
|
|
20
|
+
const { openSession } = await import("@shop-on/customer-runtime/sealed-session");
|
|
21
|
+
return openSession(await sessionConfig());
|
|
27
22
|
});
|
|
28
23
|
const readPasswordSession = createServerOnlyFn(async () => {
|
|
29
24
|
try {
|
|
@@ -408,6 +408,10 @@ input CartCheckoutInput {
|
|
|
408
408
|
Autogenerated return type of CartCheckout.
|
|
409
409
|
"""
|
|
410
410
|
type CartCheckoutPayload {
|
|
411
|
+
"""
|
|
412
|
+
Where to send the buyer. Append `checkout_state` from the
|
|
413
|
+
`shop.checkoutUrl/begin` bounce before redirecting; checkout rejects a handoff without it.
|
|
414
|
+
"""
|
|
411
415
|
checkoutUrl: String
|
|
412
416
|
|
|
413
417
|
"""
|
|
@@ -714,6 +718,16 @@ input CheckoutAddressInput {
|
|
|
714
718
|
firstName: String
|
|
715
719
|
khorooId: ID
|
|
716
720
|
lastName: String
|
|
721
|
+
|
|
722
|
+
"""
|
|
723
|
+
Delivery pin. Send both or neither.
|
|
724
|
+
"""
|
|
725
|
+
latitude: Float
|
|
726
|
+
|
|
727
|
+
"""
|
|
728
|
+
Delivery pin. Send both or neither.
|
|
729
|
+
"""
|
|
730
|
+
longitude: Float
|
|
717
731
|
phone: String
|
|
718
732
|
provinceId: ID
|
|
719
733
|
zip: String
|
|
@@ -929,6 +943,12 @@ input CheckoutRecoverInput {
|
|
|
929
943
|
"""
|
|
930
944
|
clientMutationId: String
|
|
931
945
|
recoveryToken: String!
|
|
946
|
+
|
|
947
|
+
"""
|
|
948
|
+
A random key the caller keeps for this link. If the reply to the first use is
|
|
949
|
+
lost, the same key may resume the checkout again for a few minutes.
|
|
950
|
+
"""
|
|
951
|
+
retryKey: String
|
|
932
952
|
}
|
|
933
953
|
|
|
934
954
|
"""
|
|
@@ -944,6 +964,26 @@ type CheckoutRecoverPayload {
|
|
|
944
964
|
userErrors: [UserError!]!
|
|
945
965
|
}
|
|
946
966
|
|
|
967
|
+
"""
|
|
968
|
+
Whether a checkout recovery link can still resume its checkout.
|
|
969
|
+
"""
|
|
970
|
+
enum CheckoutRecoveryStatusEnum {
|
|
971
|
+
"""
|
|
972
|
+
The checkout was completed or has expired.
|
|
973
|
+
"""
|
|
974
|
+
EXPIRED
|
|
975
|
+
|
|
976
|
+
"""
|
|
977
|
+
The link is unknown, already used, or replaced by a newer one.
|
|
978
|
+
"""
|
|
979
|
+
INVALID
|
|
980
|
+
|
|
981
|
+
"""
|
|
982
|
+
The link resumes the checkout.
|
|
983
|
+
"""
|
|
984
|
+
VALID
|
|
985
|
+
}
|
|
986
|
+
|
|
947
987
|
"""
|
|
948
988
|
Buyer-facing snapshot of the shipping address on a checkout.
|
|
949
989
|
"""
|
|
@@ -963,6 +1003,8 @@ type CheckoutShippingAddress {
|
|
|
963
1003
|
formatted(locale: String): [String!]!
|
|
964
1004
|
khoroo: AddressKhoroo
|
|
965
1005
|
lastName: String
|
|
1006
|
+
latitude: Float
|
|
1007
|
+
longitude: Float
|
|
966
1008
|
phone: String
|
|
967
1009
|
province: AddressProvince
|
|
968
1010
|
provinceCode: String
|
|
@@ -1346,6 +1388,26 @@ type CourierDriverLoginPayload {
|
|
|
1346
1388
|
userErrors: [UserError!]!
|
|
1347
1389
|
}
|
|
1348
1390
|
|
|
1391
|
+
"""
|
|
1392
|
+
What the buyer must do, or wait for, to see a delivery a courier prices.
|
|
1393
|
+
"""
|
|
1394
|
+
enum CourierPromptEnum {
|
|
1395
|
+
"""
|
|
1396
|
+
Add a phone number the courier can call.
|
|
1397
|
+
"""
|
|
1398
|
+
PHONE
|
|
1399
|
+
|
|
1400
|
+
"""
|
|
1401
|
+
Drop a pin on the map: the courier prices from it.
|
|
1402
|
+
"""
|
|
1403
|
+
PIN
|
|
1404
|
+
|
|
1405
|
+
"""
|
|
1406
|
+
The courier cannot be reached right now; choose another delivery.
|
|
1407
|
+
"""
|
|
1408
|
+
UNAVAILABLE
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1349
1411
|
"""
|
|
1350
1412
|
Crop region for image transforms
|
|
1351
1413
|
"""
|
|
@@ -2404,6 +2466,9 @@ type Mutation {
|
|
|
2404
2466
|
|
|
2405
2467
|
"""
|
|
2406
2468
|
Mint a checkout handoff for the given cart and return the URL to redirect to.
|
|
2469
|
+
Checkout consumes the handoff only in the browser that started it: bounce the
|
|
2470
|
+
buyer through `shop.checkoutUrl` + `/begin` first, and append the
|
|
2471
|
+
`checkout_state` it sends back to `checkoutUrl`, or checkout rejects it.
|
|
2407
2472
|
"""
|
|
2408
2473
|
cartCheckout(
|
|
2409
2474
|
"""
|
|
@@ -2869,6 +2934,8 @@ type OrderShippingAddress {
|
|
|
2869
2934
|
formatted(locale: String): [String!]!
|
|
2870
2935
|
khoroo: AddressKhoroo
|
|
2871
2936
|
lastName: String
|
|
2937
|
+
latitude: Float
|
|
2938
|
+
longitude: Float
|
|
2872
2939
|
phone: String
|
|
2873
2940
|
province: AddressProvince
|
|
2874
2941
|
provinceCode: String
|
|
@@ -3238,6 +3305,18 @@ type Query {
|
|
|
3238
3305
|
"""
|
|
3239
3306
|
checkout(checkoutToken: String!): ShopCheckout
|
|
3240
3307
|
|
|
3308
|
+
"""
|
|
3309
|
+
Whether a recovery link can still resume its checkout. Read-only: it never spends the link.
|
|
3310
|
+
"""
|
|
3311
|
+
checkoutRecoveryStatus(
|
|
3312
|
+
recoveryToken: String!
|
|
3313
|
+
|
|
3314
|
+
"""
|
|
3315
|
+
The key sent with an earlier use of this link.
|
|
3316
|
+
"""
|
|
3317
|
+
retryKey: String
|
|
3318
|
+
): CheckoutRecoveryStatusEnum!
|
|
3319
|
+
|
|
3241
3320
|
"""
|
|
3242
3321
|
Find a collection by ID or handle
|
|
3243
3322
|
"""
|
|
@@ -4392,6 +4471,14 @@ type Shop implements Node {
|
|
|
4392
4471
|
due (PAYMENT_REQUIRED).
|
|
4393
4472
|
"""
|
|
4394
4473
|
cashOnDeliveryEnabled: Boolean!
|
|
4474
|
+
|
|
4475
|
+
"""
|
|
4476
|
+
URL of this shop's section on the central checkout app
|
|
4477
|
+
(`{checkoutHost}/shops/{handle}/checkout`). Theme sends the buyer to
|
|
4478
|
+
`{checkoutUrl}/begin` before minting a cart handoff, so the handoff is bound
|
|
4479
|
+
to the browser that started it.
|
|
4480
|
+
"""
|
|
4481
|
+
checkoutUrl: String!
|
|
4395
4482
|
city: String
|
|
4396
4483
|
|
|
4397
4484
|
"""
|
|
@@ -4432,8 +4519,8 @@ type Shop implements Node {
|
|
|
4432
4519
|
|
|
4433
4520
|
"""
|
|
4434
4521
|
Whether this shop issues eBarimt fiscal receipts (a fiscal engine is enabled).
|
|
4435
|
-
Buyer surfaces read this to decide whether to ask the Иргэн /
|
|
4436
|
-
receipt question at checkout.
|
|
4522
|
+
Buyer surfaces and the register read this to decide whether to ask the Иргэн /
|
|
4523
|
+
Байгууллага receipt question at checkout.
|
|
4437
4524
|
"""
|
|
4438
4525
|
issuesFiscalReceipts: Boolean!
|
|
4439
4526
|
khoroo: AddressKhoroo
|
|
@@ -5061,7 +5148,18 @@ type ShopCheckout implements Node {
|
|
|
5061
5148
|
availableDeliveryOptions: [ShopDeliveryOption!]!
|
|
5062
5149
|
completedAt: ISO8601DateTime
|
|
5063
5150
|
createdAt: ISO8601DateTime!
|
|
5151
|
+
|
|
5152
|
+
"""
|
|
5153
|
+
The checkout's customer. Storefront callers get null without that customer's session.
|
|
5154
|
+
"""
|
|
5064
5155
|
customer: ShopCustomer
|
|
5156
|
+
|
|
5157
|
+
"""
|
|
5158
|
+
What stands between the buyer and a delivery a courier prices (drop a pin, add
|
|
5159
|
+
a phone, or the courier is unavailable). Null when nothing does, or no
|
|
5160
|
+
shipping address is set yet.
|
|
5161
|
+
"""
|
|
5162
|
+
deliveryCourierPrompt: CourierPromptEnum
|
|
5065
5163
|
deliveryOptionHandle: String
|
|
5066
5164
|
|
|
5067
5165
|
"""
|
|
@@ -5597,6 +5695,11 @@ A storefront delivery option.
|
|
|
5597
5695
|
interface ShopDeliveryOption {
|
|
5598
5696
|
amount: Money!
|
|
5599
5697
|
|
|
5698
|
+
"""
|
|
5699
|
+
False for a delivery a courier prices: the courier carries paid orders only, so the buyer pays online.
|
|
5700
|
+
"""
|
|
5701
|
+
cashOnDeliveryAllowed: Boolean!
|
|
5702
|
+
|
|
5600
5703
|
"""
|
|
5601
5704
|
Stable identifier passed back via deliveryOptionHandle to select this option.
|
|
5602
5705
|
"""
|
|
@@ -5861,6 +5964,13 @@ type ShopFiscalReceipt {
|
|
|
5861
5964
|
which is the only one `ShopOrder.fiscalReceipt` ever returns.
|
|
5862
5965
|
"""
|
|
5863
5966
|
supersededAt: ISO8601DateTime
|
|
5967
|
+
|
|
5968
|
+
"""
|
|
5969
|
+
What the receipt declared to the tax authority — less than the order total
|
|
5970
|
+
when gift cards paid part of it. With the lottery number, it is what the buyer
|
|
5971
|
+
registers the receipt by hand with when the QR cannot be scanned. Null until issued.
|
|
5972
|
+
"""
|
|
5973
|
+
totalAmount: Decimal
|
|
5864
5974
|
updatedAt: ISO8601DateTime!
|
|
5865
5975
|
}
|
|
5866
5976
|
|
|
@@ -6827,9 +6937,10 @@ type ShopOrder implements Node {
|
|
|
6827
6937
|
|
|
6828
6938
|
"""
|
|
6829
6939
|
How the buyer is paying the balance — the oldest non-credit payment row’s
|
|
6830
|
-
tender (`manual`, `cash_on_delivery`, `
|
|
6831
|
-
gift cards covered the whole order and
|
|
6832
|
-
it to tell a cash-on-delivery order
|
|
6940
|
+
tender (`manual`, `cash_on_delivery`, `cash`, `bank_card`, `bank_transfer`,
|
|
6941
|
+
`cody_pay`). Null when store credit / gift cards covered the whole order and
|
|
6942
|
+
no such row exists. Buyer surfaces read it to tell a cash-on-delivery order
|
|
6943
|
+
from a gateway one on the thanks page.
|
|
6833
6944
|
"""
|
|
6834
6945
|
paymentMethod: String
|
|
6835
6946
|
phone: String
|
|
@@ -7299,6 +7410,7 @@ type ShopPickupDeliveryOption implements ShopDeliveryOption {
|
|
|
7299
7410
|
"""
|
|
7300
7411
|
addressLines: [String!]!
|
|
7301
7412
|
amount: Money!
|
|
7413
|
+
cashOnDeliveryAllowed: Boolean!
|
|
7302
7414
|
handle: String!
|
|
7303
7415
|
instructions: String!
|
|
7304
7416
|
kind: ShopDeliveryKindEnum!
|
|
@@ -8303,6 +8415,7 @@ A storefront ship delivery option (quote result).
|
|
|
8303
8415
|
"""
|
|
8304
8416
|
type ShopShipDeliveryOption implements ShopDeliveryOption {
|
|
8305
8417
|
amount: Money!
|
|
8418
|
+
cashOnDeliveryAllowed: Boolean!
|
|
8306
8419
|
deliveryDetails: String
|
|
8307
8420
|
handle: String!
|
|
8308
8421
|
kind: ShopDeliveryKindEnum!
|
|
@@ -9099,10 +9212,29 @@ type ThemeEdge {
|
|
|
9099
9212
|
}
|
|
9100
9213
|
|
|
9101
9214
|
input ThemeFilter {
|
|
9215
|
+
"""
|
|
9216
|
+
Bounds on the creation time.
|
|
9217
|
+
"""
|
|
9218
|
+
createdAt: DateFilter
|
|
9102
9219
|
handleCont: String
|
|
9103
9220
|
priceEq: Float
|
|
9104
9221
|
priceGteq: Float
|
|
9105
9222
|
priceLteq: Float
|
|
9223
|
+
|
|
9224
|
+
"""
|
|
9225
|
+
Match against the theme's name or its handle.
|
|
9226
|
+
"""
|
|
9227
|
+
query: StringFilter
|
|
9228
|
+
|
|
9229
|
+
"""
|
|
9230
|
+
Predicates on the status, keyed by its lowercase label ("draft", "published", "archived").
|
|
9231
|
+
"""
|
|
9232
|
+
status: EnumStringFilter
|
|
9233
|
+
|
|
9234
|
+
"""
|
|
9235
|
+
Bounds on the last-update time.
|
|
9236
|
+
"""
|
|
9237
|
+
updatedAt: DateFilter
|
|
9106
9238
|
}
|
|
9107
9239
|
|
|
9108
9240
|
"""
|
package/dist/shop.d.ts
CHANGED
package/dist/shop.js
CHANGED
package/dist/trace.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shop-on/theme-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"license": "SEE LICENSE IN dist/LICENSE.md",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist"
|
|
@@ -54,24 +54,24 @@
|
|
|
54
54
|
"./package.json": "./package.json"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"gql.tada": "1.11.
|
|
57
|
+
"gql.tada": "1.11.4",
|
|
58
58
|
"graphql": "17.0.2",
|
|
59
59
|
"graphql-request": "7.4.0",
|
|
60
|
-
"liquidjs": "10.
|
|
61
|
-
"@shop-on/customer-runtime": "0.
|
|
62
|
-
"@shop-on/theme-runtime": "0.
|
|
60
|
+
"liquidjs": "10.30.0",
|
|
61
|
+
"@shop-on/customer-runtime": "0.3.0",
|
|
62
|
+
"@shop-on/theme-runtime": "0.3.0"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
|
-
"@tanstack/react-start": "1.
|
|
66
|
-
"@types/node": "26.6.
|
|
65
|
+
"@tanstack/react-start": "1.169.3",
|
|
66
|
+
"@types/node": "26.6.5",
|
|
67
67
|
"typescript": "7.0.2",
|
|
68
|
-
"vitest": "5.0.
|
|
68
|
+
"vitest": "5.0.5"
|
|
69
69
|
},
|
|
70
70
|
"peerDependencies": {
|
|
71
|
-
"@tanstack/react-start": "~1.
|
|
71
|
+
"@tanstack/react-start": "~1.169.3"
|
|
72
72
|
},
|
|
73
73
|
"scripts": {
|
|
74
74
|
"check-types": "tsc --noEmit",
|
|
75
|
-
"test": "vitest run"
|
|
75
|
+
"test": "vitest run --maxWorkers=2"
|
|
76
76
|
}
|
|
77
77
|
}
|