@replohq/sdk 1.7.0 → 1.7.2
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/_vendor/schemas/errors.d.ts +2 -0
- package/_vendor/schemas/generated/errors.d.ts +3 -0
- package/_vendor/schemas/money.mjs +1 -1
- package/_vendor/schemas/routing/locale.mjs +1 -1
- package/_vendor/schemas/routing/rules.mjs +1 -1
- package/{chunk-5M7VU32I.mjs → chunk-YJQVNUW7.mjs} +5 -0
- package/gateways/canopy-api.d.ts +1 -0
- package/gateways/canopy-api.js +6 -5
- package/gateways/canopy-api.js.map +2 -2
- package/lib/buildMetadata.js +3 -3
- package/loaders/invoke-loader-server.d.ts +4 -0
- package/loaders/invoke-loader-server.js +41 -7
- package/loaders/invoke-loader-server.js.map +2 -2
- package/next/image-loader.js +6 -4
- package/next/image-loader.js.map +2 -2
- package/package.json +4 -4
|
@@ -261,6 +261,7 @@ export declare const errorUserFacingDetailsMessageSchema: z.ZodObject<{
|
|
|
261
261
|
isRecoverable: z.ZodOptional<z.ZodBoolean>;
|
|
262
262
|
detail: z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString>, z.ZodString]>>;
|
|
263
263
|
key: z.ZodString;
|
|
264
|
+
details: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
264
265
|
usage: z.ZodOptional<z.ZodObject<{
|
|
265
266
|
current: z.ZodNumber;
|
|
266
267
|
maximum: z.ZodNumber;
|
|
@@ -327,6 +328,7 @@ export declare const errorUserFacingDetailsSchema: z.ZodDiscriminatedUnion<[z.Zo
|
|
|
327
328
|
isRecoverable: z.ZodOptional<z.ZodBoolean>;
|
|
328
329
|
detail: z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString>, z.ZodString]>>;
|
|
329
330
|
key: z.ZodString;
|
|
331
|
+
details: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
330
332
|
usage: z.ZodOptional<z.ZodObject<{
|
|
331
333
|
current: z.ZodNumber;
|
|
332
334
|
maximum: z.ZodNumber;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getParamSegment, getRouteParams, hasRouteParam, matchSource } from '../../../chunk-VPEVERWE.mjs';
|
|
2
|
-
import { ReploError } from '../../../chunk-
|
|
2
|
+
import { ReploError } from '../../../chunk-YJQVNUW7.mjs';
|
|
3
3
|
import '../../../chunk-AL3F36PH.mjs';
|
|
4
4
|
import '../../../chunk-GV5FY7ZZ.mjs';
|
|
5
5
|
import '../../../chunk-UJCSKKID.mjs';
|
|
@@ -46,6 +46,11 @@ var errorUserFacingDetailsMessageSchema = z.object({
|
|
|
46
46
|
// in FullPageModal only.
|
|
47
47
|
detail: z.array(z.string()).or(z.string()).optional(),
|
|
48
48
|
key: z.string(),
|
|
49
|
+
// Structured data for API callers next to the human message, e.g. the
|
|
50
|
+
// current draft version on a project-context 409 or the offending paths on
|
|
51
|
+
// a 422. The worker error handler serializes userFacingDetails as-is, so
|
|
52
|
+
// this is the one place such data can travel without a second envelope.
|
|
53
|
+
details: z.record(z.string(), z.unknown()).optional(),
|
|
49
54
|
usage: z.object({
|
|
50
55
|
current: z.number(),
|
|
51
56
|
maximum: z.number()
|
package/gateways/canopy-api.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ declare const reconciledCartLineSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
23
23
|
status: z.ZodLiteral<"unavailable">;
|
|
24
24
|
}, z.core.$strip>], "status">;
|
|
25
25
|
export type ReconciledCartLine = z.infer<typeof reconciledCartLineSchema>;
|
|
26
|
+
export declare function shouldIncludeInactiveProducts(): boolean;
|
|
26
27
|
declare class CanopyApiGateway {
|
|
27
28
|
private apiHost;
|
|
28
29
|
private baseUrl;
|
package/gateways/canopy-api.js
CHANGED
|
@@ -49,6 +49,9 @@ const reconciledCartLineSchema = z.discriminatedUnion("status", [
|
|
|
49
49
|
const reconcileCartLinesResponseSchema = z.object({
|
|
50
50
|
lines: z.array(reconciledCartLineSchema)
|
|
51
51
|
});
|
|
52
|
+
function shouldIncludeInactiveProducts() {
|
|
53
|
+
return Boolean(process.env.IS_REPLO_SANDBOX);
|
|
54
|
+
}
|
|
52
55
|
class CanopyApiGateway {
|
|
53
56
|
apiHost;
|
|
54
57
|
baseUrl;
|
|
@@ -115,10 +118,7 @@ If you're running locally, make sure the canopy-api cloudflare worker is running
|
|
|
115
118
|
projectId: env.PROJECT_ID,
|
|
116
119
|
loaderKey,
|
|
117
120
|
args,
|
|
118
|
-
|
|
119
|
-
// yet; a published storefront must not. Only the sandbox dev server sets
|
|
120
|
-
// IS_REPLO_SANDBOX, so it is what separates the two.
|
|
121
|
-
includeInactiveProducts: Boolean(process.env.IS_REPLO_SANDBOX)
|
|
121
|
+
includeInactiveProducts: shouldIncludeInactiveProducts()
|
|
122
122
|
});
|
|
123
123
|
if (!response.ok) {
|
|
124
124
|
const errorBody = await response.text().catch(() => "");
|
|
@@ -236,6 +236,7 @@ async function getCanopyApiGateway() {
|
|
|
236
236
|
}
|
|
237
237
|
export {
|
|
238
238
|
CanopyApiGatewayError,
|
|
239
|
-
getCanopyApiGateway
|
|
239
|
+
getCanopyApiGateway,
|
|
240
|
+
shouldIncludeInactiveProducts
|
|
240
241
|
};
|
|
241
242
|
//# sourceMappingURL=canopy-api.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
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\nconst reconciledCartLineSchema = z.discriminatedUnion(\"status\", [\n z.object({\n variantId: z.string(),\n status: z.literal(\"purchasable\"),\n unitPriceMinor: z.number().int(),\n compareAtPriceMinor: z.number().int().nullable(),\n currencyCode: z.string(),\n purchasableQuantity: z.number().int().min(0),\n }),\n z.object({\n variantId: z.string(),\n status: z.literal(\"unavailable\"),\n }),\n]);\n\nconst reconcileCartLinesResponseSchema = z.object({\n lines: z.array(reconciledCartLineSchema),\n});\n\nexport type ReconciledCartLine = z.infer<typeof reconciledCartLineSchema>;\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
|
|
5
|
-
"mappings": "AAEA,SAAS,SAAS;AAElB,SAAS,kBAAkB;AAC3B,SAAS,cAAc;AACvB,SAAS,mBAAmB;AAC5B,SAAS,oBAAoB;AAStB,MAAM,8BAA8B,YAAY;AAAA,EACrD;AAAA,EAEA,YAAY,QAAqC;AAC/C,UAAM,qBAAqB,OAAO,cAAc;AAChD,UAAM;AAAA,MACJ,SAAS,OAAO;AAAA,MAChB,OAAO,OAAO;AAAA,MACd,gBAAgB;AAAA,QACd,GAAG,OAAO;AAAA,QACV,YAAY;AAAA,MACd;AAAA,IACF,CAAC;AACD,SAAK,aAAa;AAAA,EACpB;AACF;AAEA,MAAM,8BAA8B,sBAAsB;AAAA,EACxD,YAAY,QAAyD;AACnE,UAAM,EAAE,GAAG,QAAQ,YAAY,IAAI,CAAC;AAAA,EACtC;AACF;AAEA,MAAM,6BAA6B,EAAE,OAAO;AAAA,EAC1C,MAAM,EAAE,QAAQ;AAAA,EAChB,MAAM,EACH,OAAO;AAAA,IACN,OAAO,EAAE,OAAO;AAAA,MACd,KAAK,EAAE,QAAQ;AAAA,MACf,YAAY,EAAE,OAAO;AAAA,IACvB,CAAC;AAAA,EACH,CAAC,EACA,SAAS;AACd,CAAC;AAED,MAAM,2BAA2B,EAAE,mBAAmB,UAAU;AAAA,EAC9D,EAAE,OAAO;AAAA,IACP,WAAW,EAAE,OAAO;AAAA,IACpB,QAAQ,EAAE,QAAQ,aAAa;AAAA,IAC/B,gBAAgB,EAAE,OAAO,EAAE,IAAI;AAAA,IAC/B,qBAAqB,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS;AAAA,IAC/C,cAAc,EAAE,OAAO;AAAA,IACvB,qBAAqB,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC;AAAA,EAC7C,CAAC;AAAA,EACD,EAAE,OAAO;AAAA,IACP,WAAW,EAAE,OAAO;AAAA,IACpB,QAAQ,EAAE,QAAQ,aAAa;AAAA,EACjC,CAAC;AACH,CAAC;AAED,MAAM,mCAAmC,EAAE,OAAO;AAAA,EAChD,OAAO,EAAE,MAAM,wBAAwB;AACzC,CAAC;
|
|
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\nconst reconciledCartLineSchema = z.discriminatedUnion(\"status\", [\n z.object({\n variantId: z.string(),\n status: z.literal(\"purchasable\"),\n unitPriceMinor: z.number().int(),\n compareAtPriceMinor: z.number().int().nullable(),\n currencyCode: z.string(),\n purchasableQuantity: z.number().int().min(0),\n }),\n z.object({\n variantId: z.string(),\n status: z.literal(\"unavailable\"),\n }),\n]);\n\nconst reconcileCartLinesResponseSchema = z.object({\n lines: z.array(reconciledCartLineSchema),\n});\n\nexport type ReconciledCartLine = z.infer<typeof reconciledCartLineSchema>;\n\n// The builder preview must render a product the merchant hasn't launched\n// yet; a published storefront must not. Only the sandbox dev server sets\n// IS_REPLO_SANDBOX, so it is what separates the two.\nexport function shouldIncludeInactiveProducts() {\n return Boolean(process.env.IS_REPLO_SANDBOX);\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 includeInactiveProducts: shouldIncludeInactiveProducts(),\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 * The catalog's current answer for a set of cookie-cart lines: per line,\n * the live price and how many units are actually purchasable. Used to\n * reconcile the client-persisted cart, which otherwise never re-reads the\n * catalog facts it snapshotted at add-to-cart time.\n */\n async reconcileCartLines({\n lines,\n }: {\n lines: { variantId: string; quantity: number }[];\n }): Promise<ReconciledCartLine[]> {\n const env = await getEnv();\n const response = await this._post(\n `${this.baseUrl}/api/v1/product-catalog/reconcile-cart-lines`,\n { projectId: env.PROJECT_ID, lines },\n );\n\n if (!response.ok) {\n const errorBody = await response.text().catch(() => \"\");\n throw new CanopyApiGatewayError({\n message: `Failed to reconcile cart lines: ${response.status} ${response.statusText}${errorBody ? ` \u2014 ${errorBody}` : \"\"}`,\n statusCode: response.status,\n });\n }\n\n return reconcileCartLinesResponseSchema.parse(await response.json()).lines;\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
6
|
"names": []
|
|
7
7
|
}
|
package/lib/buildMetadata.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const BUILD_METADATA = {
|
|
2
2
|
"packageName": "@replohq/sdk",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.2",
|
|
4
4
|
"branch": "HEAD",
|
|
5
|
-
"commit": "
|
|
6
|
-
"builtAt": "2026-09-
|
|
5
|
+
"commit": "5d0cc60f9bd67d3ed2c181939fca1a8cacc979e9",
|
|
6
|
+
"builtAt": "2026-09-14T22:55:35.132Z"
|
|
7
7
|
};
|
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
* to canopy-api. The canopy-api's own in-memory cache still acts as a second
|
|
8
8
|
* layer for cold starts and cross-instance sharing.
|
|
9
9
|
*
|
|
10
|
+
* A canopy 404 is cached as a not-found marker and rethrown as a 404
|
|
11
|
+
* `CanopyApiGatewayError`, so a successful revalidation can displace a stale
|
|
12
|
+
* entry once the underlying data disappears (e.g. a product goes draft).
|
|
13
|
+
*
|
|
10
14
|
* @param revalidateSeconds - How long (in seconds) the cached result is
|
|
11
15
|
* considered fresh. Defaults to 60. Set to `false` to cache indefinitely
|
|
12
16
|
* (use tag-based invalidation via `revalidateTag` to purge).
|
|
@@ -1,15 +1,34 @@
|
|
|
1
1
|
import { unstable_cache } from "next/cache";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
CanopyApiGatewayError,
|
|
4
|
+
getCanopyApiGateway,
|
|
5
|
+
shouldIncludeInactiveProducts
|
|
6
|
+
} from "../gateways/canopy-api";
|
|
3
7
|
import { resolvePreviewPlaceholderData } from "./preview-placeholder";
|
|
4
8
|
const DEFAULT_REVALIDATE_SECONDS = 60;
|
|
9
|
+
class LoaderDataNotFoundError extends CanopyApiGatewayError {
|
|
10
|
+
constructor({ loaderKey }) {
|
|
11
|
+
super({
|
|
12
|
+
message: "Loader data not found",
|
|
13
|
+
statusCode: 404,
|
|
14
|
+
additionalData: { loaderKey }
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
}
|
|
5
18
|
function buildStableCacheKeyParts({
|
|
6
19
|
loaderKey,
|
|
7
|
-
args
|
|
20
|
+
args,
|
|
21
|
+
includeInactiveProducts
|
|
8
22
|
}) {
|
|
9
23
|
const sortedArgsJson = JSON.stringify(
|
|
10
24
|
Object.entries(args).sort(([keyA], [keyB]) => keyA.localeCompare(keyB)).map(([key, value]) => [key, value])
|
|
11
25
|
);
|
|
12
|
-
return [
|
|
26
|
+
return [
|
|
27
|
+
"loader",
|
|
28
|
+
loaderKey,
|
|
29
|
+
sortedArgsJson,
|
|
30
|
+
`includeInactiveProducts:${includeInactiveProducts}`
|
|
31
|
+
];
|
|
13
32
|
}
|
|
14
33
|
async function invokeLoaderServer({
|
|
15
34
|
loaderKey,
|
|
@@ -20,12 +39,23 @@ async function invokeLoaderServer({
|
|
|
20
39
|
if (placeholderData !== null) {
|
|
21
40
|
return placeholderData;
|
|
22
41
|
}
|
|
23
|
-
const cacheKeyParts = buildStableCacheKeyParts({
|
|
24
|
-
|
|
42
|
+
const cacheKeyParts = buildStableCacheKeyParts({
|
|
43
|
+
loaderKey,
|
|
44
|
+
args,
|
|
45
|
+
includeInactiveProducts: shouldIncludeInactiveProducts()
|
|
46
|
+
});
|
|
47
|
+
const cachedInvocation = await unstable_cache(
|
|
25
48
|
async () => {
|
|
26
49
|
const gateway = await getCanopyApiGateway();
|
|
27
|
-
|
|
28
|
-
|
|
50
|
+
try {
|
|
51
|
+
const response = await gateway.invokeLoader({ loaderKey, args });
|
|
52
|
+
return { kind: "data", data: response.data };
|
|
53
|
+
} catch (error) {
|
|
54
|
+
if (error instanceof CanopyApiGatewayError && error.statusCode === 404) {
|
|
55
|
+
return { kind: "notFound" };
|
|
56
|
+
}
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
29
59
|
},
|
|
30
60
|
cacheKeyParts,
|
|
31
61
|
{
|
|
@@ -33,6 +63,10 @@ async function invokeLoaderServer({
|
|
|
33
63
|
tags: [`loader:${loaderKey}`, ...cacheKeyParts]
|
|
34
64
|
}
|
|
35
65
|
)();
|
|
66
|
+
if (cachedInvocation.kind === "notFound") {
|
|
67
|
+
throw new LoaderDataNotFoundError({ loaderKey });
|
|
68
|
+
}
|
|
69
|
+
return cachedInvocation.data;
|
|
36
70
|
}
|
|
37
71
|
export {
|
|
38
72
|
invokeLoaderServer
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../loaders/invoke-loader-server.ts"],
|
|
4
|
-
"sourcesContent": ["import { unstable_cache } from \"next/cache\";\n\nimport {
|
|
5
|
-
"mappings": "AAAA,SAAS,sBAAsB;AAE/B,
|
|
4
|
+
"sourcesContent": ["import { unstable_cache } from \"next/cache\";\n\nimport {\n CanopyApiGatewayError,\n getCanopyApiGateway,\n shouldIncludeInactiveProducts,\n} from \"../gateways/canopy-api\";\nimport { resolvePreviewPlaceholderData } from \"./preview-placeholder\";\n\nconst DEFAULT_REVALIDATE_SECONDS = 60;\n\n// NOTE (Max, 2026-09-11): cached values are wrapped so the not-found marker\n// stays disjoint from every possible loader payload.\ntype CachedLoaderInvocation =\n | { kind: \"data\"; data: unknown }\n | { kind: \"notFound\" };\n\nclass LoaderDataNotFoundError extends CanopyApiGatewayError {\n constructor({ loaderKey }: { loaderKey: string }) {\n super({\n message: \"Loader data not found\",\n statusCode: 404,\n additionalData: { loaderKey },\n });\n }\n}\n\nfunction buildStableCacheKeyParts({\n loaderKey,\n args,\n includeInactiveProducts,\n}: {\n loaderKey: string;\n args: Record<string, unknown>;\n includeInactiveProducts: boolean;\n}): string[] {\n const sortedArgsJson = JSON.stringify(\n Object.entries(args)\n .sort(([keyA], [keyB]) => keyA.localeCompare(keyB))\n .map(([key, value]) => [key, value]),\n );\n return [\n \"loader\",\n loaderKey,\n sortedArgsJson,\n `includeInactiveProducts:${includeInactiveProducts}`,\n ];\n}\n\n/**\n * Server-only loader invocation that calls the canopy-api gateway directly.\n * Used by PrefetchedLoaders to seed the React Query cache during SSR.\n *\n * Results are cached via Next.js `unstable_cache` so repeated SSR renders\n * for the same loader+args return instantly without a network round-trip\n * to canopy-api. The canopy-api's own in-memory cache still acts as a second\n * layer for cold starts and cross-instance sharing.\n *\n * A canopy 404 is cached as a not-found marker and rethrown as a 404\n * `CanopyApiGatewayError`, so a successful revalidation can displace a stale\n * entry once the underlying data disappears (e.g. a product goes draft).\n *\n * @param revalidateSeconds - How long (in seconds) the cached result is\n * considered fresh. Defaults to 60. Set to `false` to cache indefinitely\n * (use tag-based invalidation via `revalidateTag` to purge).\n */\nexport async function invokeLoaderServer({\n loaderKey,\n args,\n revalidateSeconds = DEFAULT_REVALIDATE_SECONDS,\n}: {\n loaderKey: string;\n args: Record<string, unknown>;\n revalidateSeconds?: number | false;\n}) {\n // Dev-server previews of an unfilled dynamic route prefetch sample data\n // instead of fetching \u2014 a real fetch with the literal bracket param can only\n // 404, and the thrown error marks the whole preview frame as broken. Kept\n // outside unstable_cache so sample data never enters the loader cache.\n const placeholderData = resolvePreviewPlaceholderData({ loaderKey, args });\n if (placeholderData !== null) {\n return placeholderData;\n }\n const cacheKeyParts = buildStableCacheKeyParts({\n loaderKey,\n args,\n includeInactiveProducts: shouldIncludeInactiveProducts(),\n });\n\n const cachedInvocation = await unstable_cache(\n async (): Promise<CachedLoaderInvocation> => {\n const gateway = await getCanopyApiGateway();\n try {\n const response = await gateway.invokeLoader({ loaderKey, args });\n return { kind: \"data\", data: response.data };\n } catch (error) {\n // NOTE (Max, 2026-09-10): unstable_cache serves a stale entry forever\n // when every background revalidation rejects, so a 404 must resolve\n // with a cacheable marker that displaces the stale payload (REPL-31653).\n if (\n error instanceof CanopyApiGatewayError &&\n error.statusCode === 404\n ) {\n return { kind: \"notFound\" };\n }\n throw error;\n }\n },\n cacheKeyParts,\n {\n revalidate: revalidateSeconds,\n tags: [`loader:${loaderKey}`, ...cacheKeyParts],\n },\n )();\n\n if (cachedInvocation.kind === \"notFound\") {\n throw new LoaderDataNotFoundError({ loaderKey });\n }\n return cachedInvocation.data;\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,sBAAsB;AAE/B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,qCAAqC;AAE9C,MAAM,6BAA6B;AAQnC,MAAM,gCAAgC,sBAAsB;AAAA,EAC1D,YAAY,EAAE,UAAU,GAA0B;AAChD,UAAM;AAAA,MACJ,SAAS;AAAA,MACT,YAAY;AAAA,MACZ,gBAAgB,EAAE,UAAU;AAAA,IAC9B,CAAC;AAAA,EACH;AACF;AAEA,SAAS,yBAAyB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AACF,GAIa;AACX,QAAM,iBAAiB,KAAK;AAAA,IAC1B,OAAO,QAAQ,IAAI,EAChB,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC,IAAI,MAAM,KAAK,cAAc,IAAI,CAAC,EACjD,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,KAAK,KAAK,CAAC;AAAA,EACvC;AACA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,2BAA2B,uBAAuB;AAAA,EACpD;AACF;AAmBA,eAAsB,mBAAmB;AAAA,EACvC;AAAA,EACA;AAAA,EACA,oBAAoB;AACtB,GAIG;AAKD,QAAM,kBAAkB,8BAA8B,EAAE,WAAW,KAAK,CAAC;AACzE,MAAI,oBAAoB,MAAM;AAC5B,WAAO;AAAA,EACT;AACA,QAAM,gBAAgB,yBAAyB;AAAA,IAC7C;AAAA,IACA;AAAA,IACA,yBAAyB,8BAA8B;AAAA,EACzD,CAAC;AAED,QAAM,mBAAmB,MAAM;AAAA,IAC7B,YAA6C;AAC3C,YAAM,UAAU,MAAM,oBAAoB;AAC1C,UAAI;AACF,cAAM,WAAW,MAAM,QAAQ,aAAa,EAAE,WAAW,KAAK,CAAC;AAC/D,eAAO,EAAE,MAAM,QAAQ,MAAM,SAAS,KAAK;AAAA,MAC7C,SAAS,OAAO;AAId,YACE,iBAAiB,yBACjB,MAAM,eAAe,KACrB;AACA,iBAAO,EAAE,MAAM,WAAW;AAAA,QAC5B;AACA,cAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA;AAAA,IACA;AAAA,MACE,YAAY;AAAA,MACZ,MAAM,CAAC,UAAU,SAAS,IAAI,GAAG,aAAa;AAAA,IAChD;AAAA,EACF,EAAE;AAEF,MAAI,iBAAiB,SAAS,YAAY;AACxC,UAAM,IAAI,wBAAwB,EAAE,UAAU,CAAC;AAAA,EACjD;AACA,SAAO,iBAAiB;AAC1B;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/next/image-loader.js
CHANGED
|
@@ -20,9 +20,7 @@ function cloudflareLoader({
|
|
|
20
20
|
width,
|
|
21
21
|
quality
|
|
22
22
|
}) {
|
|
23
|
-
|
|
24
|
-
return src;
|
|
25
|
-
}
|
|
23
|
+
const isDevelopment = process.env.NODE_ENV === "development";
|
|
26
24
|
const params = [`width=${width}`, `quality=${quality ?? 75}`, "format=auto"];
|
|
27
25
|
if (/^https:\/\//i.test(src)) {
|
|
28
26
|
let hostname;
|
|
@@ -38,11 +36,15 @@ function cloudflareLoader({
|
|
|
38
36
|
return src;
|
|
39
37
|
}
|
|
40
38
|
if (/(^|\.)replocdn\.com$/i.test(hostname)) {
|
|
41
|
-
|
|
39
|
+
const resizerOrigin = isDevelopment ? REPLO_IMAGE_CDN : "";
|
|
40
|
+
return `${resizerOrigin}/cdn-cgi/image/${params.join(",")}/${src}`;
|
|
42
41
|
}
|
|
43
42
|
const passthroughUrl = `${REPLO_IMAGE_CDN}/images/external?url=${encodeURIComponent(src)}`;
|
|
44
43
|
return `${REPLO_IMAGE_CDN}/cdn-cgi/image/${[...params, "onerror=redirect"].join(",")}/${passthroughUrl}`;
|
|
45
44
|
}
|
|
45
|
+
if (isDevelopment) {
|
|
46
|
+
return src;
|
|
47
|
+
}
|
|
46
48
|
const normalizedSrc = src.startsWith("/") ? src.slice(1) : src;
|
|
47
49
|
return `/cdn-cgi/image/${params.join(",")}/${normalizedSrc}`;
|
|
48
50
|
}
|
package/next/image-loader.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../next/image-loader.ts"],
|
|
4
|
-
"sourcesContent": ["import type { ImageLoaderProps } from \"next/image\";\n\nconst REPLO_IMAGE_CDN = \"https://assets.replocdn.com\";\n\n// Contentful Images API hosts (US + regional, e.g. images.eu.ctfassets.net).\n// These already resize/format via query params \u2014 do not Cloudflare-proxy them.\nfunction isContentfulImagesCdn(hostname: string): boolean {\n return /^images(?:\\.[a-z]+)?\\.ctfassets\\.net$/i.test(hostname);\n}\n\nfunction contentfulImagesApiUrl({\n src,\n width,\n quality,\n}: {\n src: string;\n width: number;\n quality?: number;\n}): string {\n const url = new URL(src);\n url.searchParams.set(\"w\", String(width));\n url.searchParams.set(\"q\", String(quality ?? 75));\n if (!url.searchParams.has(\"fm\")) {\n url.searchParams.set(\"fm\", \"webp\");\n }\n return url.toString();\n}\n\nexport default function cloudflareLoader({\n src,\n width,\n quality,\n}: ImageLoaderProps): string {\n // NOTE (Cole, 2026-05-15, REPL-27122): /cdn-cgi/image/ only exists on\n // Cloudflare's edge. During `next dev` in the sandbox the preview is served\n // through the Daytona proxy, so
|
|
5
|
-
"mappings": "AAEA,MAAM,kBAAkB;AAIxB,SAAS,sBAAsB,UAA2B;AACxD,SAAO,yCAAyC,KAAK,QAAQ;AAC/D;AAEA,SAAS,uBAAuB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AACF,GAIW;AACT,QAAM,MAAM,IAAI,IAAI,GAAG;AACvB,MAAI,aAAa,IAAI,KAAK,OAAO,KAAK,CAAC;AACvC,MAAI,aAAa,IAAI,KAAK,OAAO,WAAW,EAAE,CAAC;AAC/C,MAAI,CAAC,IAAI,aAAa,IAAI,IAAI,GAAG;AAC/B,QAAI,aAAa,IAAI,MAAM,MAAM;AAAA,EACnC;AACA,SAAO,IAAI,SAAS;AACtB;AAEe,SAAR,iBAAkC;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AACF,GAA6B;
|
|
4
|
+
"sourcesContent": ["import type { ImageLoaderProps } from \"next/image\";\n\nconst REPLO_IMAGE_CDN = \"https://assets.replocdn.com\";\n\n// Contentful Images API hosts (US + regional, e.g. images.eu.ctfassets.net).\n// These already resize/format via query params \u2014 do not Cloudflare-proxy them.\nfunction isContentfulImagesCdn(hostname: string): boolean {\n return /^images(?:\\.[a-z]+)?\\.ctfassets\\.net$/i.test(hostname);\n}\n\nfunction contentfulImagesApiUrl({\n src,\n width,\n quality,\n}: {\n src: string;\n width: number;\n quality?: number;\n}): string {\n const url = new URL(src);\n url.searchParams.set(\"w\", String(width));\n url.searchParams.set(\"q\", String(quality ?? 75));\n if (!url.searchParams.has(\"fm\")) {\n url.searchParams.set(\"fm\", \"webp\");\n }\n return url.toString();\n}\n\nexport default function cloudflareLoader({\n src,\n width,\n quality,\n}: ImageLoaderProps): string {\n // NOTE (Cole, 2026-05-15, REPL-27122): /cdn-cgi/image/ only exists on\n // Cloudflare's edge. During `next dev` in the sandbox the preview is served\n // through the Daytona proxy, so site-zone resizer URLs would 404.\n // NOTE (Cole, 2026-09-13, REPL-30607): Only site-zone URLs skip resizing in\n // dev. Returning every src unchanged made Next warn that the loader \"does not\n // implement width\" for each image, and absolute sources can resize on the\n // replocdn zone, which is reachable from dev.\n const isDevelopment = process.env.NODE_ENV === \"development\";\n\n const params = [`width=${width}`, `quality=${quality ?? 75}`, \"format=auto\"];\n\n if (/^https:\\/\\//i.test(src)) {\n let hostname: string;\n try {\n hostname = new URL(src).hostname;\n } catch {\n return src;\n }\n // NOTE (Noah, 2026-07-31): Contentful's own CDN owns transforms\n // (Images API on images*.ctfassets.net). Never route ctfassets through the\n // Cloudflare/replocdn passthrough \u2014 double-proxying fights their params.\n if (/(^|\\.)ctfassets\\.net$/i.test(hostname)) {\n if (isContentfulImagesCdn(hostname)) {\n return contentfulImagesApiUrl({ src, width, quality });\n }\n return src;\n }\n if (/(^|\\.)replocdn\\.com$/i.test(hostname)) {\n const resizerOrigin = isDevelopment ? REPLO_IMAGE_CDN : \"\";\n return `${resizerOrigin}/cdn-cgi/image/${params.join(\",\")}/${src}`;\n }\n // NOTE (Cole, 2026-07-02, REPL-28680): commerce CDNs (Shopify, BigCommerce)\n // reject the Cloudflare resizer's own fetches, so third-party images resize\n // on the replocdn zone from our passthrough endpoint, which re-fetches them\n // with an ordinary request. The replocdn zone only resizes same-zone\n // sources, so the passthrough URL must stay on assets.replocdn.com.\n // onerror=redirect serves the unoptimized original if the transform fails.\n const passthroughUrl = `${REPLO_IMAGE_CDN}/images/external?url=${encodeURIComponent(src)}`;\n return `${REPLO_IMAGE_CDN}/cdn-cgi/image/${[...params, \"onerror=redirect\"].join(\",\")}/${passthroughUrl}`;\n }\n\n if (isDevelopment) {\n return src;\n }\n\n const normalizedSrc = src.startsWith(\"/\") ? src.slice(1) : src;\n return `/cdn-cgi/image/${params.join(\",\")}/${normalizedSrc}`;\n}\n"],
|
|
5
|
+
"mappings": "AAEA,MAAM,kBAAkB;AAIxB,SAAS,sBAAsB,UAA2B;AACxD,SAAO,yCAAyC,KAAK,QAAQ;AAC/D;AAEA,SAAS,uBAAuB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AACF,GAIW;AACT,QAAM,MAAM,IAAI,IAAI,GAAG;AACvB,MAAI,aAAa,IAAI,KAAK,OAAO,KAAK,CAAC;AACvC,MAAI,aAAa,IAAI,KAAK,OAAO,WAAW,EAAE,CAAC;AAC/C,MAAI,CAAC,IAAI,aAAa,IAAI,IAAI,GAAG;AAC/B,QAAI,aAAa,IAAI,MAAM,MAAM;AAAA,EACnC;AACA,SAAO,IAAI,SAAS;AACtB;AAEe,SAAR,iBAAkC;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AACF,GAA6B;AAQ3B,QAAM,gBAAgB,QAAQ,IAAI,aAAa;AAE/C,QAAM,SAAS,CAAC,SAAS,KAAK,IAAI,WAAW,WAAW,EAAE,IAAI,aAAa;AAE3E,MAAI,eAAe,KAAK,GAAG,GAAG;AAC5B,QAAI;AACJ,QAAI;AACF,iBAAW,IAAI,IAAI,GAAG,EAAE;AAAA,IAC1B,QAAQ;AACN,aAAO;AAAA,IACT;AAIA,QAAI,yBAAyB,KAAK,QAAQ,GAAG;AAC3C,UAAI,sBAAsB,QAAQ,GAAG;AACnC,eAAO,uBAAuB,EAAE,KAAK,OAAO,QAAQ,CAAC;AAAA,MACvD;AACA,aAAO;AAAA,IACT;AACA,QAAI,wBAAwB,KAAK,QAAQ,GAAG;AAC1C,YAAM,gBAAgB,gBAAgB,kBAAkB;AACxD,aAAO,GAAG,aAAa,kBAAkB,OAAO,KAAK,GAAG,CAAC,IAAI,GAAG;AAAA,IAClE;AAOA,UAAM,iBAAiB,GAAG,eAAe,wBAAwB,mBAAmB,GAAG,CAAC;AACxF,WAAO,GAAG,eAAe,kBAAkB,CAAC,GAAG,QAAQ,kBAAkB,EAAE,KAAK,GAAG,CAAC,IAAI,cAAc;AAAA,EACxG;AAEA,MAAI,eAAe;AACjB,WAAO;AAAA,EACT;AAEA,QAAM,gBAAgB,IAAI,WAAW,GAAG,IAAI,IAAI,MAAM,CAAC,IAAI;AAC3D,SAAO,kBAAkB,OAAO,KAAK,GAAG,CAAC,IAAI,aAAa;AAC5D;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@replohq/sdk",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.2",
|
|
4
4
|
"reploBuild": {
|
|
5
5
|
"packageName": "@replohq/sdk",
|
|
6
|
-
"version": "1.7.
|
|
6
|
+
"version": "1.7.2",
|
|
7
7
|
"branch": "HEAD",
|
|
8
|
-
"commit": "
|
|
9
|
-
"builtAt": "2026-09-
|
|
8
|
+
"commit": "5d0cc60f9bd67d3ed2c181939fca1a8cacc979e9",
|
|
9
|
+
"builtAt": "2026-09-14T22:55:35.132Z"
|
|
10
10
|
},
|
|
11
11
|
"description": "Replo SDK — cart, analytics, and data loaders for agent-built Next.js sites.",
|
|
12
12
|
"license": "SEE LICENSE IN LICENSE",
|