@replohq/sdk 0.1.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/LICENSE +11 -0
- package/_vendor/replo-utils/lib/crypto.d.ts +30 -0
- package/_vendor/replo-utils/lib/errors.d.ts +83 -0
- package/_vendor/replo-utils/lib/json.d.ts +6 -0
- package/_vendor/replo-utils/lib/json.mjs +43 -0
- package/_vendor/replo-utils/lib/misc.d.ts +145 -0
- package/_vendor/replo-utils/lib/object.d.ts +31 -0
- package/_vendor/schemas/errors.d.ts +459 -0
- package/_vendor/schemas/generated/consent.d.ts +55 -0
- package/_vendor/schemas/generated/errors.d.ts +61 -0
- package/_vendor/schemas/loaderKeys.d.ts +1 -0
- package/_vendor/schemas/loaderKeys.mjs +27 -0
- package/_vendor/schemas/money.d.ts +60 -0
- package/_vendor/schemas/money.mjs +2051 -0
- package/analytics/analytics-provider.d.ts +16 -0
- package/analytics/analytics-provider.js +113 -0
- package/analytics/analytics-provider.js.map +7 -0
- package/analytics/analytics-sink.d.ts +26 -0
- package/analytics/analytics-sink.js +1 -0
- package/analytics/analytics-sink.js.map +7 -0
- package/analytics/get-analytics-sinks.d.ts +27 -0
- package/analytics/get-analytics-sinks.js +52 -0
- package/analytics/get-analytics-sinks.js.map +7 -0
- package/analytics/hooks/use-product-viewed-analytics.d.ts +23 -0
- package/analytics/hooks/use-product-viewed-analytics.js +34 -0
- package/analytics/hooks/use-product-viewed-analytics.js.map +7 -0
- package/analytics/replo-first-party-pixel.d.ts +12 -0
- package/analytics/replo-first-party-pixel.js +31 -0
- package/analytics/replo-first-party-pixel.js.map +7 -0
- package/analytics/replo-pixel-script.d.ts +13 -0
- package/analytics/replo-pixel-script.js +30 -0
- package/analytics/replo-pixel-script.js.map +7 -0
- package/analytics/sinks/ga4-sink.d.ts +15 -0
- package/analytics/sinks/ga4-sink.js +60 -0
- package/analytics/sinks/ga4-sink.js.map +7 -0
- package/analytics/sinks/meta-sink.d.ts +14 -0
- package/analytics/sinks/meta-sink.js +56 -0
- package/analytics/sinks/meta-sink.js.map +7 -0
- package/analytics/sinks/pinterest-sink.d.ts +14 -0
- package/analytics/sinks/pinterest-sink.js +76 -0
- package/analytics/sinks/pinterest-sink.js.map +7 -0
- package/analytics/sinks/reddit-sink.d.ts +14 -0
- package/analytics/sinks/reddit-sink.js +80 -0
- package/analytics/sinks/reddit-sink.js.map +7 -0
- package/analytics/sinks/replo-sink.d.ts +14 -0
- package/analytics/sinks/replo-sink.js +57 -0
- package/analytics/sinks/replo-sink.js.map +7 -0
- package/analytics/sinks/snapchat-sink.d.ts +14 -0
- package/analytics/sinks/snapchat-sink.js +67 -0
- package/analytics/sinks/snapchat-sink.js.map +7 -0
- package/analytics/sinks/tiktok-sink.d.ts +14 -0
- package/analytics/sinks/tiktok-sink.js +79 -0
- package/analytics/sinks/tiktok-sink.js.map +7 -0
- package/analytics/sinks/window-sink.d.ts +18 -0
- package/analytics/sinks/window-sink.js +46 -0
- package/analytics/sinks/window-sink.js.map +7 -0
- package/analytics/utils/analytics-utils.d.ts +7 -0
- package/analytics/utils/analytics-utils.js +130 -0
- package/analytics/utils/analytics-utils.js.map +7 -0
- package/analytics/utils/navigation-tracker.d.ts +16 -0
- package/analytics/utils/navigation-tracker.js +44 -0
- package/analytics/utils/navigation-tracker.js.map +7 -0
- package/analytics/utils/shopify-funnel-gate.d.ts +8 -0
- package/analytics/utils/shopify-funnel-gate.js +22 -0
- package/analytics/utils/shopify-funnel-gate.js.map +7 -0
- package/cart/buy-now-action.d.ts +21 -0
- package/cart/buy-now-action.js +104 -0
- package/cart/buy-now-action.js.map +7 -0
- package/cart/cart-actions.d.ts +63 -0
- package/cart/cart-actions.js +90 -0
- package/cart/cart-actions.js.map +7 -0
- package/cart/cart-loader.d.ts +9 -0
- package/cart/cart-loader.js +11 -0
- package/cart/cart-loader.js.map +7 -0
- package/cart/cart-provider.d.ts +85 -0
- package/cart/cart-provider.js +372 -0
- package/cart/cart-provider.js.map +7 -0
- package/cart/cart-schema.d.ts +74 -0
- package/cart/cart-schema.js +69 -0
- package/cart/cart-schema.js.map +7 -0
- package/cart/cart-types.d.ts +132 -0
- package/cart/cart-types.js +8 -0
- package/cart/cart-types.js.map +7 -0
- package/cart/gateways/cart-constants.d.ts +1 -0
- package/cart/gateways/cart-constants.js +5 -0
- package/cart/gateways/cart-constants.js.map +7 -0
- package/cart/gateways/cart-gateway.d.ts +10 -0
- package/cart/gateways/cart-gateway.js +19 -0
- package/cart/gateways/cart-gateway.js.map +7 -0
- package/cart/gateways/cookie-cart-gateway.d.ts +28 -0
- package/cart/gateways/cookie-cart-gateway.js +109 -0
- package/cart/gateways/cookie-cart-gateway.js.map +7 -0
- package/cart/gateways/default-cart-gateway.d.ts +19 -0
- package/cart/gateways/default-cart-gateway.js +30 -0
- package/cart/gateways/default-cart-gateway.js.map +7 -0
- package/cart/gateways/resolver-cart-gateway.d.ts +2 -0
- package/cart/gateways/resolver-cart-gateway.js +109 -0
- package/cart/gateways/resolver-cart-gateway.js.map +7 -0
- package/cart/gateways/stripe-cart-gateway.d.ts +19 -0
- package/cart/gateways/stripe-cart-gateway.js +28 -0
- package/cart/gateways/stripe-cart-gateway.js.map +7 -0
- package/cart/hooks/use-add-to-cart.d.ts +63 -0
- package/cart/hooks/use-add-to-cart.js +124 -0
- package/cart/hooks/use-add-to-cart.js.map +7 -0
- package/cart/hooks/use-buy-now.d.ts +49 -0
- package/cart/hooks/use-buy-now.js +62 -0
- package/cart/hooks/use-buy-now.js.map +7 -0
- package/cart/utils/cart-utils.d.ts +43 -0
- package/cart/utils/cart-utils.js +46 -0
- package/cart/utils/cart-utils.js.map +7 -0
- package/cart/utils/cookie-cart-persistence.d.ts +55 -0
- package/cart/utils/cookie-cart-persistence.js +166 -0
- package/cart/utils/cookie-cart-persistence.js.map +7 -0
- package/cart/utils/variant-to-cart-line.d.ts +74 -0
- package/cart/utils/variant-to-cart-line.js +162 -0
- package/cart/utils/variant-to-cart-line.js.map +7 -0
- package/chunk-UJCSKKID.mjs +27 -0
- package/consent/consent-action.d.ts +9 -0
- package/consent/consent-action.js +25 -0
- package/consent/consent-action.js.map +7 -0
- package/consent/consent-actions.d.ts +28 -0
- package/consent/consent-actions.js +36 -0
- package/consent/consent-actions.js.map +7 -0
- package/consent/consent-store.d.ts +69 -0
- package/consent/consent-store.js +194 -0
- package/consent/consent-store.js.map +7 -0
- package/consent/cookie.d.ts +36 -0
- package/consent/cookie.js +82 -0
- package/consent/cookie.js.map +7 -0
- package/consent/replo-scripts.d.ts +23 -0
- package/consent/replo-scripts.js +102 -0
- package/consent/replo-scripts.js.map +7 -0
- package/consent/script-registration-store.d.ts +25 -0
- package/consent/script-registration-store.js +61 -0
- package/consent/script-registration-store.js.map +7 -0
- package/consent/script-snippets.d.ts +24 -0
- package/consent/script-snippets.js +122 -0
- package/consent/script-snippets.js.map +7 -0
- package/consent/types.d.ts +34 -0
- package/consent/types.js +1 -0
- package/consent/types.js.map +7 -0
- package/consent/window-api.d.ts +41 -0
- package/consent/window-api.js +73 -0
- package/consent/window-api.js.map +7 -0
- package/env.d.ts +9 -0
- package/env.js +42 -0
- package/env.js.map +7 -0
- package/gateways/canopy-api.d.ts +83 -0
- package/gateways/canopy-api.js +197 -0
- package/gateways/canopy-api.js.map +7 -0
- package/hooks/use-client-value.d.ts +10 -0
- package/hooks/use-client-value.js +12 -0
- package/hooks/use-client-value.js.map +7 -0
- package/hooks/use-formatted-price.d.ts +15 -0
- package/hooks/use-formatted-price.js +20 -0
- package/hooks/use-formatted-price.js.map +7 -0
- package/lib/canopy-error.d.ts +25 -0
- package/lib/canopy-error.js +24 -0
- package/lib/canopy-error.js.map +7 -0
- package/lib/get-query-client.d.ts +2 -0
- package/lib/get-query-client.js +34 -0
- package/lib/get-query-client.js.map +7 -0
- package/lib/integration-utils.d.ts +2 -0
- package/lib/integration-utils.js +9 -0
- package/lib/integration-utils.js.map +7 -0
- package/lib/shopify-utils.d.ts +10 -0
- package/lib/shopify-utils.js +7 -0
- package/lib/shopify-utils.js.map +7 -0
- package/lib/url-utils.d.ts +4 -0
- package/lib/url-utils.js +8 -0
- package/lib/url-utils.js.map +7 -0
- package/loaders/collection-loader.d.ts +11 -0
- package/loaders/collection-loader.js +55 -0
- package/loaders/collection-loader.js.map +7 -0
- package/loaders/collection-products-loader.d.ts +12 -0
- package/loaders/collection-products-loader.js +58 -0
- package/loaders/collection-products-loader.js.map +7 -0
- package/loaders/contentful-entries-loader.d.ts +17 -0
- package/loaders/contentful-entries-loader.js +78 -0
- package/loaders/contentful-entries-loader.js.map +7 -0
- package/loaders/contentful-entry-loader.d.ts +14 -0
- package/loaders/contentful-entry-loader.js +60 -0
- package/loaders/contentful-entry-loader.js.map +7 -0
- package/loaders/invoke-loader-action.d.ts +11 -0
- package/loaders/invoke-loader-action.js +14 -0
- package/loaders/invoke-loader-action.js.map +7 -0
- package/loaders/invoke-loader-server.d.ts +18 -0
- package/loaders/invoke-loader-server.js +40 -0
- package/loaders/invoke-loader-server.js.map +7 -0
- package/loaders/invoke-loader.d.ts +16 -0
- package/loaders/invoke-loader.js +37 -0
- package/loaders/invoke-loader.js.map +7 -0
- package/loaders/levanta-product-loader.d.ts +11 -0
- package/loaders/levanta-product-loader.js +53 -0
- package/loaders/levanta-product-loader.js.map +7 -0
- package/loaders/loader-error-boundary.d.ts +25 -0
- package/loaders/loader-error-boundary.js +24 -0
- package/loaders/loader-error-boundary.js.map +7 -0
- package/loaders/loader-keys.d.ts +34 -0
- package/loaders/loader-keys.js +26 -0
- package/loaders/loader-keys.js.map +7 -0
- package/loaders/loader-schemas.d.ts +946 -0
- package/loaders/loader-schemas.js +353 -0
- package/loaders/loader-schemas.js.map +7 -0
- package/loaders/metaobject-loader.d.ts +12 -0
- package/loaders/metaobject-loader.js +54 -0
- package/loaders/metaobject-loader.js.map +7 -0
- package/loaders/metaobjects-loader.d.ts +14 -0
- package/loaders/metaobjects-loader.js +60 -0
- package/loaders/metaobjects-loader.js.map +7 -0
- package/loaders/okendo-review-aggregate-loader.d.ts +17 -0
- package/loaders/okendo-review-aggregate-loader.js +56 -0
- package/loaders/okendo-review-aggregate-loader.js.map +7 -0
- package/loaders/okendo-reviews-loader.d.ts +23 -0
- package/loaders/okendo-reviews-loader.js +54 -0
- package/loaders/okendo-reviews-loader.js.map +7 -0
- package/loaders/prefetch-loaders.d.ts +33 -0
- package/loaders/prefetch-loaders.js +46 -0
- package/loaders/prefetch-loaders.js.map +7 -0
- package/loaders/preview-placeholder.d.ts +25 -0
- package/loaders/preview-placeholder.js +222 -0
- package/loaders/preview-placeholder.js.map +7 -0
- package/loaders/product-loader-args.d.ts +21 -0
- package/loaders/product-loader-args.js +17 -0
- package/loaders/product-loader-args.js.map +7 -0
- package/loaders/product-loader.d.ts +10 -0
- package/loaders/product-loader.js +52 -0
- package/loaders/product-loader.js.map +7 -0
- package/loaders/rebuy-recommendations-loader.d.ts +165 -0
- package/loaders/rebuy-recommendations-loader.js +57 -0
- package/loaders/rebuy-recommendations-loader.js.map +7 -0
- package/loaders/reviews-io-review-aggregate-loader.d.ts +12 -0
- package/loaders/reviews-io-review-aggregate-loader.js +43 -0
- package/loaders/reviews-io-review-aggregate-loader.js.map +7 -0
- package/loaders/reviews-io-reviews-loader.d.ts +14 -0
- package/loaders/reviews-io-reviews-loader.js +65 -0
- package/loaders/reviews-io-reviews-loader.js.map +7 -0
- package/loaders/smile-earning-rules-loader.d.ts +19 -0
- package/loaders/smile-earning-rules-loader.js +44 -0
- package/loaders/smile-earning-rules-loader.js.map +7 -0
- package/loaders/smile-rewards-loader.d.ts +19 -0
- package/loaders/smile-rewards-loader.js +44 -0
- package/loaders/smile-rewards-loader.js.map +7 -0
- package/loaders/smile-vip-tiers-loader.d.ts +18 -0
- package/loaders/smile-vip-tiers-loader.js +43 -0
- package/loaders/smile-vip-tiers-loader.js.map +7 -0
- package/loaders/statsig-experiment-loader.d.ts +20 -0
- package/loaders/statsig-experiment-loader.js +81 -0
- package/loaders/statsig-experiment-loader.js.map +7 -0
- package/loaders/yotpo-review-aggregate-loader.d.ts +12 -0
- package/loaders/yotpo-review-aggregate-loader.js +50 -0
- package/loaders/yotpo-review-aggregate-loader.js.map +7 -0
- package/loaders/yotpo-reviews-loader.d.ts +14 -0
- package/loaders/yotpo-reviews-loader.js +65 -0
- package/loaders/yotpo-reviews-loader.js.map +7 -0
- package/money.d.ts +9 -0
- package/money.js +13 -0
- package/money.js.map +7 -0
- package/next/config.d.ts +4 -0
- package/next/config.js +91 -0
- package/next/config.js.map +7 -0
- package/next/image-loader.d.ts +2 -0
- package/next/image-loader.js +52 -0
- package/next/image-loader.js.map +7 -0
- package/next/open-next.d.ts +2 -0
- package/next/open-next.js +24 -0
- package/next/open-next.js.map +7 -0
- package/orders/order-status-schema.d.ts +161 -0
- package/orders/order-status-schema.js +83 -0
- package/orders/order-status-schema.js.map +7 -0
- package/orders/order-status.d.ts +11 -0
- package/orders/order-status.js +41 -0
- package/orders/order-status.js.map +7 -0
- package/package.json +216 -0
- package/providers/query-provider.d.ts +1 -0
- package/providers/query-provider.js +12 -0
- package/providers/query-provider.js.map +7 -0
- package/providers/replo-provider.d.ts +1 -0
- package/providers/replo-provider.js +22 -0
- package/providers/replo-provider.js.map +7 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { cartSchema } from "../cart/cart-schema";
|
|
3
|
+
import { getEnv } from "../env";
|
|
4
|
+
import { CanopyError } from "../lib/canopy-error";
|
|
5
|
+
import { buildBaseUrl } from "../lib/url-utils";
|
|
6
|
+
class CanopyApiGatewayError extends CanopyError {
|
|
7
|
+
statusCode;
|
|
8
|
+
constructor(config) {
|
|
9
|
+
const resolvedStatusCode = config.statusCode ?? 500;
|
|
10
|
+
super({
|
|
11
|
+
message: config.message,
|
|
12
|
+
cause: config.cause,
|
|
13
|
+
additionalData: {
|
|
14
|
+
...config.additionalData,
|
|
15
|
+
statusCode: resolvedStatusCode
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
this.statusCode = resolvedStatusCode;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
class UnknownCanopyApiError extends CanopyApiGatewayError {
|
|
22
|
+
constructor(config) {
|
|
23
|
+
super({ ...config, statusCode: 502 });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const loaderInvokeResponseSchema = z.object({
|
|
27
|
+
data: z.unknown(),
|
|
28
|
+
meta: z.object({
|
|
29
|
+
cache: z.object({
|
|
30
|
+
hit: z.boolean(),
|
|
31
|
+
ttlSeconds: z.number()
|
|
32
|
+
})
|
|
33
|
+
}).optional()
|
|
34
|
+
});
|
|
35
|
+
class CanopyApiGateway {
|
|
36
|
+
apiHost;
|
|
37
|
+
baseUrl;
|
|
38
|
+
constructor(apiHost) {
|
|
39
|
+
this.apiHost = apiHost;
|
|
40
|
+
this.baseUrl = buildBaseUrl(apiHost);
|
|
41
|
+
}
|
|
42
|
+
static REQUEST_TIMEOUT_MILLISECONDS = 1e4;
|
|
43
|
+
static async setup() {
|
|
44
|
+
const env = await getEnv();
|
|
45
|
+
return new CanopyApiGateway(env.CANOPY_API_HOST);
|
|
46
|
+
}
|
|
47
|
+
async _post(url, body) {
|
|
48
|
+
const abortController = new AbortController();
|
|
49
|
+
const timeoutId = setTimeout(() => {
|
|
50
|
+
abortController.abort();
|
|
51
|
+
}, CanopyApiGateway.REQUEST_TIMEOUT_MILLISECONDS);
|
|
52
|
+
try {
|
|
53
|
+
return await fetch(url, {
|
|
54
|
+
method: "POST",
|
|
55
|
+
headers: { "Content-Type": "application/json" },
|
|
56
|
+
body: JSON.stringify(body),
|
|
57
|
+
signal: abortController.signal
|
|
58
|
+
});
|
|
59
|
+
} catch (error) {
|
|
60
|
+
let message = `Failed to POST to canopy-api at ${url}: ${error}`;
|
|
61
|
+
if (process.env.NODE_ENV !== "production") {
|
|
62
|
+
message += `
|
|
63
|
+
|
|
64
|
+
apiHost: ${this.apiHost}
|
|
65
|
+
If you're running locally, make sure the canopy-api cloudflare worker is running. You may need to restart the dev server.`;
|
|
66
|
+
}
|
|
67
|
+
throw new UnknownCanopyApiError({ message, cause: error });
|
|
68
|
+
} finally {
|
|
69
|
+
clearTimeout(timeoutId);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async _get(url) {
|
|
73
|
+
const abortController = new AbortController();
|
|
74
|
+
const timeoutId = setTimeout(() => {
|
|
75
|
+
abortController.abort();
|
|
76
|
+
}, CanopyApiGateway.REQUEST_TIMEOUT_MILLISECONDS);
|
|
77
|
+
try {
|
|
78
|
+
return await fetch(url, { signal: abortController.signal });
|
|
79
|
+
} catch (error) {
|
|
80
|
+
let message = `Failed to GET from canopy-api at ${url}: ${error}`;
|
|
81
|
+
if (process.env.NODE_ENV !== "production") {
|
|
82
|
+
message += `
|
|
83
|
+
|
|
84
|
+
apiHost: ${this.apiHost}
|
|
85
|
+
If you're running locally, make sure the canopy-api cloudflare worker is running. You may need to restart the dev server.`;
|
|
86
|
+
}
|
|
87
|
+
throw new UnknownCanopyApiError({ message, cause: error });
|
|
88
|
+
} finally {
|
|
89
|
+
clearTimeout(timeoutId);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async invokeLoader({
|
|
93
|
+
loaderKey,
|
|
94
|
+
args
|
|
95
|
+
}) {
|
|
96
|
+
const env = await getEnv();
|
|
97
|
+
const response = await this._post(`${this.baseUrl}/api/v1/loaders/invoke`, {
|
|
98
|
+
projectId: env.PROJECT_ID,
|
|
99
|
+
loaderKey,
|
|
100
|
+
args
|
|
101
|
+
});
|
|
102
|
+
if (!response.ok) {
|
|
103
|
+
const errorBody = await response.text().catch(() => "");
|
|
104
|
+
throw new CanopyApiGatewayError({
|
|
105
|
+
message: `Failed to invoke loader ${loaderKey}: ${response.status} ${response.statusText}${errorBody ? ` \u2014 ${errorBody}` : ""}`,
|
|
106
|
+
statusCode: response.status
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return loaderInvokeResponseSchema.parse(await response.json());
|
|
110
|
+
}
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
// Cart operations (canopy-api-backed ExternalCart)
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
async _cartPost(path, body) {
|
|
115
|
+
const env = await getEnv();
|
|
116
|
+
const url = `${this.baseUrl}/api/v1/cart${path}`;
|
|
117
|
+
const abortController = new AbortController();
|
|
118
|
+
const timeoutId = setTimeout(() => {
|
|
119
|
+
abortController.abort();
|
|
120
|
+
}, CanopyApiGateway.REQUEST_TIMEOUT_MILLISECONDS);
|
|
121
|
+
let response;
|
|
122
|
+
try {
|
|
123
|
+
response = await fetch(url, {
|
|
124
|
+
method: "POST",
|
|
125
|
+
headers: {
|
|
126
|
+
"Content-Type": "application/json"
|
|
127
|
+
},
|
|
128
|
+
body: JSON.stringify({ ...body, projectId: env.PROJECT_ID }),
|
|
129
|
+
signal: abortController.signal
|
|
130
|
+
});
|
|
131
|
+
} catch (error) {
|
|
132
|
+
throw new UnknownCanopyApiError({
|
|
133
|
+
message: `Failed to POST cart operation to ${url}: ${error}`,
|
|
134
|
+
cause: error
|
|
135
|
+
});
|
|
136
|
+
} finally {
|
|
137
|
+
clearTimeout(timeoutId);
|
|
138
|
+
}
|
|
139
|
+
if (!response.ok) {
|
|
140
|
+
const text = await response.text().catch(() => "");
|
|
141
|
+
throw new CanopyApiGatewayError({
|
|
142
|
+
message: `Cart operation ${path} failed: ${response.status} ${response.statusText} \u2014 ${text}`,
|
|
143
|
+
statusCode: response.status
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
const json = await response.json();
|
|
147
|
+
return cartSchema.parse(json.data);
|
|
148
|
+
}
|
|
149
|
+
async cartGet({ cartId }) {
|
|
150
|
+
try {
|
|
151
|
+
return await this._cartPost("/get", { cartId });
|
|
152
|
+
} catch (error) {
|
|
153
|
+
if (error instanceof CanopyApiGatewayError && error.statusCode === 404) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async cartCreate(options) {
|
|
160
|
+
return this._cartPost("/create", {
|
|
161
|
+
lines: options?.lines,
|
|
162
|
+
discountCodes: options?.discountCodes
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
async cartAddLines({
|
|
166
|
+
cartId,
|
|
167
|
+
lines
|
|
168
|
+
}) {
|
|
169
|
+
return this._cartPost("/add-lines", { cartId, lines });
|
|
170
|
+
}
|
|
171
|
+
async cartUpdateLines({
|
|
172
|
+
cartId,
|
|
173
|
+
lines
|
|
174
|
+
}) {
|
|
175
|
+
return this._cartPost("/update-lines", { cartId, lines });
|
|
176
|
+
}
|
|
177
|
+
async cartRemoveLines({
|
|
178
|
+
cartId,
|
|
179
|
+
lineIds
|
|
180
|
+
}) {
|
|
181
|
+
return this._cartPost("/remove-lines", { cartId, lineIds });
|
|
182
|
+
}
|
|
183
|
+
async cartUpdateDiscountCodes({
|
|
184
|
+
cartId,
|
|
185
|
+
discountCodes
|
|
186
|
+
}) {
|
|
187
|
+
return this._cartPost("/update-discount-codes", { cartId, discountCodes });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
async function getCanopyApiGateway() {
|
|
191
|
+
return CanopyApiGateway.setup();
|
|
192
|
+
}
|
|
193
|
+
export {
|
|
194
|
+
CanopyApiGatewayError,
|
|
195
|
+
getCanopyApiGateway
|
|
196
|
+
};
|
|
197
|
+
//# sourceMappingURL=canopy-api.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../gateways/canopy-api.ts"],
|
|
4
|
+
"sourcesContent": ["import type { Cart } from \"../cart/cart-schema\";\n\nimport { z } from \"zod\";\n\nimport { cartSchema } from \"../cart/cart-schema\";\nimport { getEnv } from \"../env\";\nimport { CanopyError } from \"../lib/canopy-error\";\nimport { buildBaseUrl } from \"../lib/url-utils\";\n\ntype CanopyApiGatewayErrorConfig = {\n message: string;\n statusCode?: number;\n cause?: unknown;\n additionalData?: Record<string, unknown>;\n};\n\nexport class CanopyApiGatewayError extends CanopyError {\n statusCode: number;\n\n constructor(config: CanopyApiGatewayErrorConfig) {\n const resolvedStatusCode = config.statusCode ?? 500;\n super({\n message: config.message,\n cause: config.cause,\n additionalData: {\n ...config.additionalData,\n statusCode: resolvedStatusCode,\n },\n });\n this.statusCode = resolvedStatusCode;\n }\n}\n\nclass UnknownCanopyApiError extends CanopyApiGatewayError {\n constructor(config: Omit<CanopyApiGatewayErrorConfig, \"statusCode\">) {\n super({ ...config, statusCode: 502 });\n }\n}\n\nconst loaderInvokeResponseSchema = z.object({\n data: z.unknown(),\n meta: z\n .object({\n cache: z.object({\n hit: z.boolean(),\n ttlSeconds: z.number(),\n }),\n })\n .optional(),\n});\n\nclass CanopyApiGateway {\n private apiHost: string;\n private baseUrl: string;\n constructor(apiHost: string) {\n this.apiHost = apiHost;\n this.baseUrl = buildBaseUrl(apiHost);\n }\n\n private static readonly REQUEST_TIMEOUT_MILLISECONDS = 10_000;\n\n static async setup() {\n const env = await getEnv();\n\n return new CanopyApiGateway(env.CANOPY_API_HOST);\n }\n\n private async _post(url: string, body: unknown) {\n const abortController = new AbortController();\n const timeoutId = setTimeout(() => {\n abortController.abort();\n }, CanopyApiGateway.REQUEST_TIMEOUT_MILLISECONDS);\n\n try {\n return await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n signal: abortController.signal,\n });\n } catch (error) {\n let message = `Failed to POST to canopy-api at ${url}: ${error}`;\n if (process.env.NODE_ENV !== \"production\") {\n message += `\\n\\napiHost: ${this.apiHost}\\nIf you're running locally, make sure the canopy-api cloudflare worker is running. You may need to restart the dev server.`;\n }\n throw new UnknownCanopyApiError({ message, cause: error });\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n private async _get(url: string) {\n const abortController = new AbortController();\n const timeoutId = setTimeout(() => {\n abortController.abort();\n }, CanopyApiGateway.REQUEST_TIMEOUT_MILLISECONDS);\n\n try {\n return await fetch(url, { signal: abortController.signal });\n } catch (error) {\n let message = `Failed to GET from canopy-api at ${url}: ${error}`;\n if (process.env.NODE_ENV !== \"production\") {\n message += `\\n\\napiHost: ${this.apiHost}\\nIf you're running locally, make sure the canopy-api cloudflare worker is running. You may need to restart the dev server.`;\n }\n throw new UnknownCanopyApiError({ message, cause: error });\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n async invokeLoader({\n loaderKey,\n args,\n }: {\n loaderKey: string;\n args: Record<string, unknown>;\n }) {\n const env = await getEnv();\n const response = await this._post(`${this.baseUrl}/api/v1/loaders/invoke`, {\n projectId: env.PROJECT_ID,\n loaderKey,\n args,\n });\n\n if (!response.ok) {\n const errorBody = await response.text().catch(() => \"\");\n throw new CanopyApiGatewayError({\n message: `Failed to invoke loader ${loaderKey}: ${response.status} ${response.statusText}${errorBody ? ` \u2014 ${errorBody}` : \"\"}`,\n statusCode: response.status,\n });\n }\n\n return loaderInvokeResponseSchema.parse(await response.json());\n }\n\n // ---------------------------------------------------------------------------\n // Cart operations (canopy-api-backed ExternalCart)\n // ---------------------------------------------------------------------------\n\n private async _cartPost(\n path: string,\n body: Record<string, unknown>,\n ): Promise<Cart> {\n const env = await getEnv();\n const url = `${this.baseUrl}/api/v1/cart${path}`;\n\n const abortController = new AbortController();\n const timeoutId = setTimeout(() => {\n abortController.abort();\n }, CanopyApiGateway.REQUEST_TIMEOUT_MILLISECONDS);\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ ...body, projectId: env.PROJECT_ID }),\n signal: abortController.signal,\n });\n } catch (error) {\n throw new UnknownCanopyApiError({\n message: `Failed to POST cart operation to ${url}: ${error}`,\n cause: error,\n });\n } finally {\n clearTimeout(timeoutId);\n }\n\n if (!response.ok) {\n const text = await response.text().catch(() => \"\");\n throw new CanopyApiGatewayError({\n message: `Cart operation ${path} failed: ${response.status} ${response.statusText} \u2014 ${text}`,\n statusCode: response.status,\n });\n }\n\n // Legacy cast - may or may not be necessary\n // eslint-disable-next-line replo/no-as-cast\n const json = (await response.json()) as { data: unknown };\n return cartSchema.parse(json.data);\n }\n\n async cartGet({ cartId }: { cartId: string }): Promise<Cart | null> {\n try {\n return await this._cartPost(\"/get\", { cartId });\n } catch (error) {\n if (error instanceof CanopyApiGatewayError && error.statusCode === 404) {\n return null;\n }\n throw error;\n }\n }\n\n async cartCreate(options?: {\n lines?: {\n merchandiseId: string;\n quantity: number;\n attributes?: { key: string; value: string }[];\n sellingPlanId?: string | null;\n }[];\n discountCodes?: string[];\n }): Promise<Cart> {\n return this._cartPost(\"/create\", {\n lines: options?.lines,\n discountCodes: options?.discountCodes,\n });\n }\n\n async cartAddLines({\n cartId,\n lines,\n }: {\n cartId: string;\n lines: {\n merchandiseId: string;\n quantity: number;\n attributes?: { key: string; value: string }[];\n sellingPlanId?: string | null;\n }[];\n }): Promise<Cart> {\n return this._cartPost(\"/add-lines\", { cartId, lines });\n }\n\n async cartUpdateLines({\n cartId,\n lines,\n }: {\n cartId: string;\n lines: {\n lineId: string;\n quantity: number;\n attributes?: { key: string; value: string }[];\n sellingPlanId?: string | null;\n }[];\n }): Promise<Cart> {\n return this._cartPost(\"/update-lines\", { cartId, lines });\n }\n\n async cartRemoveLines({\n cartId,\n lineIds,\n }: {\n cartId: string;\n lineIds: string[];\n }): Promise<Cart> {\n return this._cartPost(\"/remove-lines\", { cartId, lineIds });\n }\n\n async cartUpdateDiscountCodes({\n cartId,\n discountCodes,\n }: {\n cartId: string;\n discountCodes: string[];\n }): Promise<Cart> {\n return this._cartPost(\"/update-discount-codes\", { cartId, discountCodes });\n }\n}\n\nexport async function getCanopyApiGateway() {\n return CanopyApiGateway.setup();\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NOTE (Max, 2025-09-15): This hook is to be used for values that aren't available on the server (such as window.location).
|
|
3
|
+
*
|
|
4
|
+
* This hook will use a dummy value both on the server & on hydration. Then,
|
|
5
|
+
* after hydration, this useEffect will update the value to the actual value.
|
|
6
|
+
*
|
|
7
|
+
* The useEffect doesn't have any dependencies - it's designed to only run once. So if you expect your read() function to return
|
|
8
|
+
* different values through the lifecycle of the component, you should not use this hook.
|
|
9
|
+
*/
|
|
10
|
+
export declare function useClientValue<ValueType>(readValue: () => ValueType, initialValue: ValueType): ValueType;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
function useClientValue(readValue, initialValue) {
|
|
3
|
+
const [value, setValue] = React.useState(initialValue);
|
|
4
|
+
React.useEffect(() => {
|
|
5
|
+
setValue(readValue());
|
|
6
|
+
}, []);
|
|
7
|
+
return value;
|
|
8
|
+
}
|
|
9
|
+
export {
|
|
10
|
+
useClientValue
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=use-client-value.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../hooks/use-client-value.ts"],
|
|
4
|
+
"sourcesContent": ["import * as React from \"react\";\n\n/**\n * NOTE (Max, 2025-09-15): This hook is to be used for values that aren't available on the server (such as window.location).\n *\n * This hook will use a dummy value both on the server & on hydration. Then,\n * after hydration, this useEffect will update the value to the actual value.\n *\n * The useEffect doesn't have any dependencies - it's designed to only run once. So if you expect your read() function to return\n * different values through the lifecycle of the component, you should not use this hook.\n */\n\nexport function useClientValue<ValueType>(\n readValue: () => ValueType,\n initialValue: ValueType,\n) {\n const [value, setValue] = React.useState<ValueType>(initialValue);\n\n // eslint-disable-next-line replo/no-use-effect -- (Noah, 2026-07-20): Legacy effect - may or may not be necessary\n React.useEffect(() => {\n setValue(readValue());\n }, []);\n return value;\n}\n"],
|
|
5
|
+
"mappings": "AAAA,YAAY,WAAW;AAYhB,SAAS,eACd,WACA,cACA;AACA,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAoB,YAAY;AAGhE,QAAM,UAAU,MAAM;AACpB,aAAS,UAAU,CAAC;AAAA,EACtB,GAAG,CAAC,CAAC;AACL,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Use `useFormattedPrice` to format prices with proper currency and locale.
|
|
3
|
+
* @returns A formatted price string based on the price and currency code. It
|
|
4
|
+
* already takes into account the user's locale/browser.
|
|
5
|
+
* @example
|
|
6
|
+
* ```tsx
|
|
7
|
+
* import { useFormattedPrice } from "@replohq/sdk/hooks/use-formatted-price";
|
|
8
|
+
*
|
|
9
|
+
* const originalPrice = 1999; // integer minor units
|
|
10
|
+
* const originalPriceFormatted = useFormattedPrice(originalPrice, "USD");
|
|
11
|
+
* // Display the formatted price
|
|
12
|
+
* <p>{originalPriceFormatted}</p>;
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export declare const useFormattedPrice: (price: number | null, currency: string | null) => string | null;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { formatAmount } from "../_vendor/schemas/money.mjs";
|
|
2
|
+
import { useClientValue } from "./use-client-value";
|
|
3
|
+
const useFormattedPrice = (price, currency) => {
|
|
4
|
+
const navigatorLanguage = useClientValue(
|
|
5
|
+
() => window.navigator.language,
|
|
6
|
+
"en-US"
|
|
7
|
+
);
|
|
8
|
+
if (price === null) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
return formatAmount({
|
|
12
|
+
amount: price,
|
|
13
|
+
currencyCode: currency ?? "USD",
|
|
14
|
+
locale: navigatorLanguage
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
export {
|
|
18
|
+
useFormattedPrice
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=use-formatted-price.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../hooks/use-formatted-price.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Use useFormattedPrice() hook to display prices with proper currency symbols and formatting. Pass in a price in integer minor units (Stripe convention) and a currency code (e.g., 1999 and \"USD\") and it returns a formatted string like \"$19.99\".\n * @module\n */\nimport { formatAmount } from \"schemas/money\";\n\nimport { useClientValue } from \"./use-client-value\";\n\n/**\n * Use `useFormattedPrice` to format prices with proper currency and locale.\n * @returns A formatted price string based on the price and currency code. It\n * already takes into account the user's locale/browser.\n * @example\n * ```tsx\n * import { useFormattedPrice } from \"@replohq/sdk/hooks/use-formatted-price\";\n *\n * const originalPrice = 1999; // integer minor units\n * const originalPriceFormatted = useFormattedPrice(originalPrice, \"USD\");\n * // Display the formatted price\n * <p>{originalPriceFormatted}</p>;\n * ```\n */\nexport const useFormattedPrice = (\n price: number | null,\n currency: string | null,\n) => {\n const navigatorLanguage = useClientValue(\n () => window.navigator.language,\n \"en-US\",\n );\n\n // NOTE (Max, 2026-08-03): guard on null, not falsiness \u2014 0 is a real amount\n // (a free item), so `if (!price)` would render no price at all for it.\n if (price === null) {\n return null;\n }\n\n return formatAmount({\n amount: price,\n currencyCode: currency ?? \"USD\",\n locale: navigatorLanguage,\n });\n};\n"],
|
|
5
|
+
"mappings": "AAIA,SAAS,oBAAoB;AAE7B,SAAS,sBAAsB;AAgBxB,MAAM,oBAAoB,CAC/B,OACA,aACG;AACH,QAAM,oBAAoB;AAAA,IACxB,MAAM,OAAO,UAAU;AAAA,IACvB;AAAA,EACF;AAIA,MAAI,UAAU,MAAM;AAClB,WAAO;AAAA,EACT;AAEA,SAAO,aAAa;AAAA,IAClB,QAAQ;AAAA,IACR,cAAc,YAAY;AAAA,IAC1B,QAAQ;AAAA,EACV,CAAC;AACH;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type CanopyErrorConfig = {
|
|
2
|
+
/**
|
|
3
|
+
* Message of the error. If not provided, defaults to the error's class name.
|
|
4
|
+
*/
|
|
5
|
+
message?: string;
|
|
6
|
+
/**
|
|
7
|
+
* Additional context about the error (ids, contextual variables, etc.),
|
|
8
|
+
* surfaced when the error is logged.
|
|
9
|
+
*/
|
|
10
|
+
additionalData?: Record<string, unknown>;
|
|
11
|
+
/**
|
|
12
|
+
* Original cause of the error, useful when catching and re-throwing with more
|
|
13
|
+
* context.
|
|
14
|
+
*/
|
|
15
|
+
cause?: unknown;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Base error that all SDK errors inherit from. Deliberately minimal and
|
|
19
|
+
* self-contained; it does not depend on the `schemas` workspace package.
|
|
20
|
+
*/
|
|
21
|
+
export declare abstract class CanopyError extends Error {
|
|
22
|
+
config: CanopyErrorConfig;
|
|
23
|
+
id: string;
|
|
24
|
+
constructor(config: CanopyErrorConfig);
|
|
25
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
class CanopyError extends Error {
|
|
2
|
+
config;
|
|
3
|
+
id;
|
|
4
|
+
constructor(config) {
|
|
5
|
+
super(config.message);
|
|
6
|
+
this.message = config.message ?? this.constructor.name;
|
|
7
|
+
this.config = config;
|
|
8
|
+
this.id = crypto.randomUUID();
|
|
9
|
+
const cause = config.cause;
|
|
10
|
+
if (cause) {
|
|
11
|
+
this.cause = cause;
|
|
12
|
+
if (cause instanceof CanopyError) {
|
|
13
|
+
this.config.additionalData = {
|
|
14
|
+
...this.config.additionalData,
|
|
15
|
+
...cause.config.additionalData
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export {
|
|
22
|
+
CanopyError
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=canopy-error.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../lib/canopy-error.ts"],
|
|
4
|
+
"sourcesContent": ["// NOTE (Ryan, 2026-07-23, REPL-27611): Local error base for the SDK's\n// published-page code. Mirrors the minimal surface of `schemas`' `ReploError`\n// (a `config` bag, a stable `id`, and cause chaining) that canopy's scoped\n// error classes actually use, so nothing shipped to a generated site imports\n// from `schemas`.\n\nexport type CanopyErrorConfig = {\n /**\n * Message of the error. If not provided, defaults to the error's class name.\n */\n message?: string;\n /**\n * Additional context about the error (ids, contextual variables, etc.),\n * surfaced when the error is logged.\n */\n additionalData?: Record<string, unknown>;\n /**\n * Original cause of the error, useful when catching and re-throwing with more\n * context.\n */\n cause?: unknown;\n};\n\n/**\n * Base error that all SDK errors inherit from. Deliberately minimal and\n * self-contained; it does not depend on the `schemas` workspace package.\n */\nexport abstract class CanopyError extends Error {\n config: CanopyErrorConfig;\n id: string;\n\n constructor(config: CanopyErrorConfig) {\n super(config.message);\n this.message = config.message ?? this.constructor.name;\n this.config = config;\n // crypto.randomUUID is natively available in browsers, Node, and the\n // Cloudflare Workers runtime \u2014 the three environments the SDK runs in.\n this.id = crypto.randomUUID();\n\n const cause = config.cause;\n if (cause) {\n this.cause = cause;\n if (cause instanceof CanopyError) {\n this.config.additionalData = {\n ...this.config.additionalData,\n ...cause.config.additionalData,\n };\n }\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "AA2BO,MAAe,oBAAoB,MAAM;AAAA,EAC9C;AAAA,EACA;AAAA,EAEA,YAAY,QAA2B;AACrC,UAAM,OAAO,OAAO;AACpB,SAAK,UAAU,OAAO,WAAW,KAAK,YAAY;AAClD,SAAK,SAAS;AAGd,SAAK,KAAK,OAAO,WAAW;AAE5B,UAAM,QAAQ,OAAO;AACrB,QAAI,OAAO;AACT,WAAK,QAAQ;AACb,UAAI,iBAAiB,aAAa;AAChC,aAAK,OAAO,iBAAiB;AAAA,UAC3B,GAAG,KAAK,OAAO;AAAA,UACf,GAAG,MAAM,OAAO;AAAA,QAClB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defaultShouldDehydrateQuery,
|
|
3
|
+
isServer,
|
|
4
|
+
QueryClient
|
|
5
|
+
} from "@tanstack/react-query";
|
|
6
|
+
const STALE_TIME_MILLISECONDS = 60 * 1e3;
|
|
7
|
+
const makeQueryClient = () => {
|
|
8
|
+
return new QueryClient({
|
|
9
|
+
defaultOptions: {
|
|
10
|
+
queries: {
|
|
11
|
+
staleTime: STALE_TIME_MILLISECONDS
|
|
12
|
+
},
|
|
13
|
+
dehydrate: {
|
|
14
|
+
shouldDehydrateQuery: (query) => {
|
|
15
|
+
return defaultShouldDehydrateQuery(query) || query.state.status === "pending";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
let browserQueryClient;
|
|
22
|
+
function getQueryClient() {
|
|
23
|
+
if (isServer) {
|
|
24
|
+
return makeQueryClient();
|
|
25
|
+
}
|
|
26
|
+
if (!browserQueryClient) {
|
|
27
|
+
browserQueryClient = makeQueryClient();
|
|
28
|
+
}
|
|
29
|
+
return browserQueryClient;
|
|
30
|
+
}
|
|
31
|
+
export {
|
|
32
|
+
getQueryClient
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=get-query-client.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../lib/get-query-client.ts"],
|
|
4
|
+
"sourcesContent": ["import {\n defaultShouldDehydrateQuery,\n isServer,\n QueryClient,\n} from \"@tanstack/react-query\";\n\nconst STALE_TIME_MILLISECONDS = 60 * 1000;\n\nconst makeQueryClient = () => {\n return new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: STALE_TIME_MILLISECONDS,\n },\n dehydrate: {\n shouldDehydrateQuery: (query) => {\n return (\n defaultShouldDehydrateQuery(query) ||\n query.state.status === \"pending\"\n );\n },\n },\n },\n });\n};\n\nlet browserQueryClient: QueryClient | undefined;\n\nexport function getQueryClient() {\n if (isServer) {\n return makeQueryClient();\n }\n\n if (!browserQueryClient) {\n browserQueryClient = makeQueryClient();\n }\n\n return browserQueryClient;\n}\n"],
|
|
5
|
+
"mappings": "AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,MAAM,0BAA0B,KAAK;AAErC,MAAM,kBAAkB,MAAM;AAC5B,SAAO,IAAI,YAAY;AAAA,IACrB,gBAAgB;AAAA,MACd,SAAS;AAAA,QACP,WAAW;AAAA,MACb;AAAA,MACA,WAAW;AAAA,QACT,sBAAsB,CAAC,UAAU;AAC/B,iBACE,4BAA4B,KAAK,KACjC,MAAM,MAAM,WAAW;AAAA,QAE3B;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAEA,IAAI;AAEG,SAAS,iBAAiB;AAC/B,MAAI,UAAU;AACZ,WAAO,gBAAgB;AAAA,EACzB;AAEA,MAAI,CAAC,oBAAoB;AACvB,yBAAqB,gBAAgB;AAAA,EACvC;AAEA,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../lib/integration-utils.ts"],
|
|
4
|
+
"sourcesContent": ["import { getEnv } from \"../env\";\n\nexport type CheckoutProvider = \"shopify\" | \"stripe\" | \"none\";\n\nexport async function getCheckoutProvider(): Promise<CheckoutProvider> {\n const env = await getEnv();\n return env.CHECKOUT_PROVIDER ?? \"none\";\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,cAAc;AAIvB,eAAsB,sBAAiD;AACrE,QAAM,MAAM,MAAM,OAAO;AACzB,SAAO,IAAI,qBAAqB;AAClC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for working with Shopify IDs and GIDs
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Converts a legacy resource ID to a Shopify GraphQL ID
|
|
6
|
+
* @param id - The legacy resource ID (e.g., "123456")
|
|
7
|
+
* @param type - The Shopify resource type
|
|
8
|
+
* @returns The full Shopify GID (e.g., "gid://shopify/ProductVariant/123456")
|
|
9
|
+
*/
|
|
10
|
+
export declare function convertLegacyResourceIdToGraphQLId(id: string, type: "Product" | "ProductVariant"): string;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../lib/shopify-utils.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Utility functions for working with Shopify IDs and GIDs\n */\n\n/**\n * Converts a legacy resource ID to a Shopify GraphQL ID\n * @param id - The legacy resource ID (e.g., \"123456\")\n * @param type - The Shopify resource type\n * @returns The full Shopify GID (e.g., \"gid://shopify/ProductVariant/123456\")\n */\nexport function convertLegacyResourceIdToGraphQLId(\n id: string,\n type: \"Product\" | \"ProductVariant\",\n): string {\n return `gid://shopify/${type}/${id}`;\n}\n"],
|
|
5
|
+
"mappings": "AAUO,SAAS,mCACd,IACA,MACQ;AACR,SAAO,iBAAiB,IAAI,IAAI,EAAE;AACpC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/lib/url-utils.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
function buildBaseUrl(apiHost) {
|
|
2
|
+
const isLocal = apiHost.startsWith("localhost") || apiHost.startsWith("127.0.0.1") || apiHost.startsWith("host.docker.internal");
|
|
3
|
+
return `${isLocal ? "http" : "https"}://${apiHost}`;
|
|
4
|
+
}
|
|
5
|
+
export {
|
|
6
|
+
buildBaseUrl
|
|
7
|
+
};
|
|
8
|
+
//# sourceMappingURL=url-utils.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../lib/url-utils.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Builds a base URL from an API host, using http for local hosts and https otherwise.\n */\nexport function buildBaseUrl(apiHost: string): string {\n const isLocal =\n apiHost.startsWith(\"localhost\") ||\n apiHost.startsWith(\"127.0.0.1\") ||\n apiHost.startsWith(\"host.docker.internal\");\n return `${isLocal ? \"http\" : \"https\"}://${apiHost}`;\n}\n"],
|
|
5
|
+
"mappings": "AAGO,SAAS,aAAa,SAAyB;AACpD,QAAM,UACJ,QAAQ,WAAW,WAAW,KAC9B,QAAQ,WAAW,WAAW,KAC9B,QAAQ,WAAW,sBAAsB;AAC3C,SAAO,GAAG,UAAU,SAAS,OAAO,MAAM,OAAO;AACnD;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { FullCollection } from "./loader-schemas";
|
|
3
|
+
type CollectionLoaderProps = {
|
|
4
|
+
loaderKey: string;
|
|
5
|
+
handle?: string;
|
|
6
|
+
collectionGid?: string;
|
|
7
|
+
children: (data: FullCollection) => ReactNode;
|
|
8
|
+
fallback?: ReactNode;
|
|
9
|
+
};
|
|
10
|
+
export declare function CollectionLoader({ loaderKey, handle, collectionGid, children, fallback, }: CollectionLoaderProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
3
|
+
import { Suspense } from "react";
|
|
4
|
+
import { useSuspenseQuery } from "@tanstack/react-query";
|
|
5
|
+
import { invokeLoader } from "./invoke-loader";
|
|
6
|
+
import { LoaderErrorBoundary } from "./loader-error-boundary";
|
|
7
|
+
import { fullCollectionSchema } from "./loader-schemas";
|
|
8
|
+
import { useReportPreviewPlaceholderRender } from "./preview-placeholder";
|
|
9
|
+
function CollectionLoaderInner({
|
|
10
|
+
loaderKey,
|
|
11
|
+
handle,
|
|
12
|
+
collectionGid,
|
|
13
|
+
children
|
|
14
|
+
}) {
|
|
15
|
+
const queryResult = useSuspenseQuery({
|
|
16
|
+
queryKey: [loaderKey, { handle, collectionGid }],
|
|
17
|
+
queryFn: async () => {
|
|
18
|
+
const loadedData = await invokeLoader({
|
|
19
|
+
loaderKey,
|
|
20
|
+
args: { handle, collectionGid }
|
|
21
|
+
});
|
|
22
|
+
return fullCollectionSchema.parse(loadedData);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
useReportPreviewPlaceholderRender(queryResult.data);
|
|
26
|
+
return /* @__PURE__ */ jsx(Fragment, { children: children(queryResult.data) });
|
|
27
|
+
}
|
|
28
|
+
function CollectionLoader({
|
|
29
|
+
loaderKey,
|
|
30
|
+
handle,
|
|
31
|
+
collectionGid,
|
|
32
|
+
children,
|
|
33
|
+
fallback
|
|
34
|
+
}) {
|
|
35
|
+
return /* @__PURE__ */ jsx(
|
|
36
|
+
LoaderErrorBoundary,
|
|
37
|
+
{
|
|
38
|
+
fallback: fallback ?? null,
|
|
39
|
+
resetKey: `${loaderKey}:${handle ?? ""}:${collectionGid ?? ""}`,
|
|
40
|
+
children: /* @__PURE__ */ jsx(Suspense, { fallback: fallback ?? null, children: /* @__PURE__ */ jsx(
|
|
41
|
+
CollectionLoaderInner,
|
|
42
|
+
{
|
|
43
|
+
loaderKey,
|
|
44
|
+
handle,
|
|
45
|
+
collectionGid,
|
|
46
|
+
children
|
|
47
|
+
}
|
|
48
|
+
) })
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
export {
|
|
53
|
+
CollectionLoader
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=collection-loader.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../loaders/collection-loader.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport type { FullCollection } from \"./loader-schemas\";\n\nimport { Suspense } from \"react\";\n\nimport { useSuspenseQuery } from \"@tanstack/react-query\";\n\nimport { invokeLoader } from \"./invoke-loader\";\nimport { LoaderErrorBoundary } from \"./loader-error-boundary\";\nimport { fullCollectionSchema } from \"./loader-schemas\";\nimport { useReportPreviewPlaceholderRender } from \"./preview-placeholder\";\n\ntype CollectionLoaderProps = {\n loaderKey: string;\n handle?: string;\n collectionGid?: string;\n children: (data: FullCollection) => ReactNode;\n fallback?: ReactNode;\n};\n\nfunction CollectionLoaderInner({\n loaderKey,\n handle,\n collectionGid,\n children,\n}: Omit<CollectionLoaderProps, \"fallback\">) {\n const queryResult = useSuspenseQuery({\n queryKey: [loaderKey, { handle, collectionGid }],\n queryFn: async () => {\n const loadedData = await invokeLoader({\n loaderKey,\n args: { handle, collectionGid },\n });\n return fullCollectionSchema.parse(loadedData);\n },\n });\n useReportPreviewPlaceholderRender(queryResult.data);\n\n return <>{children(queryResult.data)}</>;\n}\n\nexport function CollectionLoader({\n loaderKey,\n handle,\n collectionGid,\n children,\n fallback,\n}: CollectionLoaderProps) {\n return (\n <LoaderErrorBoundary\n fallback={fallback ?? null}\n resetKey={`${loaderKey}:${handle ?? \"\"}:${collectionGid ?? \"\"}`}\n >\n <Suspense fallback={fallback ?? null}>\n <CollectionLoaderInner\n loaderKey={loaderKey}\n handle={handle}\n collectionGid={collectionGid}\n >\n {children}\n </CollectionLoaderInner>\n </Suspense>\n </LoaderErrorBoundary>\n );\n}\n"],
|
|
5
|
+
"mappings": ";AAwCS;AAnCT,SAAS,gBAAgB;AAEzB,SAAS,wBAAwB;AAEjC,SAAS,oBAAoB;AAC7B,SAAS,2BAA2B;AACpC,SAAS,4BAA4B;AACrC,SAAS,yCAAyC;AAUlD,SAAS,sBAAsB;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA4C;AAC1C,QAAM,cAAc,iBAAiB;AAAA,IACnC,UAAU,CAAC,WAAW,EAAE,QAAQ,cAAc,CAAC;AAAA,IAC/C,SAAS,YAAY;AACnB,YAAM,aAAa,MAAM,aAAa;AAAA,QACpC;AAAA,QACA,MAAM,EAAE,QAAQ,cAAc;AAAA,MAChC,CAAC;AACD,aAAO,qBAAqB,MAAM,UAAU;AAAA,IAC9C;AAAA,EACF,CAAC;AACD,oCAAkC,YAAY,IAAI;AAElD,SAAO,gCAAG,mBAAS,YAAY,IAAI,GAAE;AACvC;AAEO,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA0B;AACxB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,UAAU,YAAY;AAAA,MACtB,UAAU,GAAG,SAAS,IAAI,UAAU,EAAE,IAAI,iBAAiB,EAAE;AAAA,MAE7D,8BAAC,YAAS,UAAU,YAAY,MAC9B;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA;AAAA,UACA;AAAA,UAEC;AAAA;AAAA,MACH,GACF;AAAA;AAAA,EACF;AAEJ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { PaginatedProducts } from "./loader-schemas";
|
|
3
|
+
type CollectionProductsLoaderProps = {
|
|
4
|
+
loaderKey: string;
|
|
5
|
+
collectionGid: string;
|
|
6
|
+
first?: number;
|
|
7
|
+
after?: string;
|
|
8
|
+
children: (data: PaginatedProducts) => ReactNode;
|
|
9
|
+
fallback?: ReactNode;
|
|
10
|
+
};
|
|
11
|
+
export declare function CollectionProductsLoader({ loaderKey, collectionGid, first, after, children, fallback, }: CollectionProductsLoaderProps): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export {};
|