@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.
@@ -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;
@@ -4,6 +4,9 @@ export type ErrorUserFacingDetailsWithMessage = {
4
4
  isRecoverable?: boolean;
5
5
  detail?: string[] | string;
6
6
  key: string;
7
+ details?: {
8
+ [x: string]: unknown;
9
+ };
7
10
  usage?: {
8
11
  current: number;
9
12
  maximum: number;
@@ -1,4 +1,4 @@
1
- import { ReploError } from '../../chunk-5M7VU32I.mjs';
1
+ import { ReploError } from '../../chunk-YJQVNUW7.mjs';
2
2
  import '../../chunk-AL3F36PH.mjs';
3
3
  import '../../chunk-GV5FY7ZZ.mjs';
4
4
  import { __commonJS, __toESM } from '../../chunk-UJCSKKID.mjs';
@@ -1,4 +1,4 @@
1
- import { ReploError } from '../../../chunk-5M7VU32I.mjs';
1
+ import { ReploError } from '../../../chunk-YJQVNUW7.mjs';
2
2
  import '../../../chunk-AL3F36PH.mjs';
3
3
  import '../../../chunk-GV5FY7ZZ.mjs';
4
4
  import '../../../chunk-UJCSKKID.mjs';
@@ -1,5 +1,5 @@
1
1
  import { getParamSegment, getRouteParams, hasRouteParam, matchSource } from '../../../chunk-VPEVERWE.mjs';
2
- import { ReploError } from '../../../chunk-5M7VU32I.mjs';
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()
@@ -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;
@@ -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
- // The builder preview must render a product the merchant hasn't launched
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 // 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.\n includeInactiveProducts: Boolean(process.env.IS_REPLO_SANDBOX),\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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",
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": "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;AAOM,SAAS,gCAAgC;AAC9C,SAAO,QAAQ,QAAQ,IAAI,gBAAgB;AAC7C;AAEA,MAAM,iBAAiB;AAAA,EACb;AAAA,EACA;AAAA,EACR,YAAY,SAAiB;AAC3B,SAAK,UAAU;AACf,SAAK,UAAU,aAAa,OAAO;AAAA,EACrC;AAAA,EAEA,OAAwB,+BAA+B;AAAA,EAEvD,aAAa,QAAQ;AACnB,UAAM,MAAM,MAAM,OAAO;AAEzB,WAAO,IAAI,iBAAiB,IAAI,eAAe;AAAA,EACjD;AAAA,EAEA,MAAc,MAAM,KAAa,MAAe;AAC9C,UAAM,kBAAkB,IAAI,gBAAgB;AAC5C,UAAM,YAAY,WAAW,MAAM;AACjC,sBAAgB,MAAM;AAAA,IACxB,GAAG,iBAAiB,4BAA4B;AAEhD,QAAI;AACF,aAAO,MAAM,MAAM,KAAK;AAAA,QACtB,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU,IAAI;AAAA,QACzB,QAAQ,gBAAgB;AAAA,MAC1B,CAAC;AAAA,IACH,SAAS,OAAO;AACd,UAAI,UAAU,mCAAmC,GAAG,KAAK,KAAK;AAC9D,UAAI,QAAQ,IAAI,aAAa,cAAc;AACzC,mBAAW;AAAA;AAAA,WAAgB,KAAK,OAAO;AAAA;AAAA,MACzC;AACA,YAAM,IAAI,sBAAsB,EAAE,SAAS,OAAO,MAAM,CAAC;AAAA,IAC3D,UAAE;AACA,mBAAa,SAAS;AAAA,IACxB;AAAA,EACF;AAAA,EAEA,MAAc,KAAK,KAAa;AAC9B,UAAM,kBAAkB,IAAI,gBAAgB;AAC5C,UAAM,YAAY,WAAW,MAAM;AACjC,sBAAgB,MAAM;AAAA,IACxB,GAAG,iBAAiB,4BAA4B;AAEhD,QAAI;AACF,aAAO,MAAM,MAAM,KAAK,EAAE,QAAQ,gBAAgB,OAAO,CAAC;AAAA,IAC5D,SAAS,OAAO;AACd,UAAI,UAAU,oCAAoC,GAAG,KAAK,KAAK;AAC/D,UAAI,QAAQ,IAAI,aAAa,cAAc;AACzC,mBAAW;AAAA;AAAA,WAAgB,KAAK,OAAO;AAAA;AAAA,MACzC;AACA,YAAM,IAAI,sBAAsB,EAAE,SAAS,OAAO,MAAM,CAAC;AAAA,IAC3D,UAAE;AACA,mBAAa,SAAS;AAAA,IACxB;AAAA,EACF;AAAA,EAEA,MAAM,aAAa;AAAA,IACjB;AAAA,IACA;AAAA,EACF,GAGG;AACD,UAAM,MAAM,MAAM,OAAO;AACzB,UAAM,WAAW,MAAM,KAAK,MAAM,GAAG,KAAK,OAAO,0BAA0B;AAAA,MACzE,WAAW,IAAI;AAAA,MACf;AAAA,MACA;AAAA,MACA,yBAAyB,8BAA8B;AAAA,IACzD,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,YAAY,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,EAAE;AACtD,YAAM,IAAI,sBAAsB;AAAA,QAC9B,SAAS,2BAA2B,SAAS,KAAK,SAAS,MAAM,IAAI,SAAS,UAAU,GAAG,YAAY,WAAM,SAAS,KAAK,EAAE;AAAA,QAC7H,YAAY,SAAS;AAAA,MACvB,CAAC;AAAA,IACH;AAEA,WAAO,2BAA2B,MAAM,MAAM,SAAS,KAAK,CAAC;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,mBAAmB;AAAA,IACvB;AAAA,EACF,GAEkC;AAChC,UAAM,MAAM,MAAM,OAAO;AACzB,UAAM,WAAW,MAAM,KAAK;AAAA,MAC1B,GAAG,KAAK,OAAO;AAAA,MACf,EAAE,WAAW,IAAI,YAAY,MAAM;AAAA,IACrC;AAEA,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,YAAY,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,EAAE;AACtD,YAAM,IAAI,sBAAsB;AAAA,QAC9B,SAAS,mCAAmC,SAAS,MAAM,IAAI,SAAS,UAAU,GAAG,YAAY,WAAM,SAAS,KAAK,EAAE;AAAA,QACvH,YAAY,SAAS;AAAA,MACvB,CAAC;AAAA,IACH;AAEA,WAAO,iCAAiC,MAAM,MAAM,SAAS,KAAK,CAAC,EAAE;AAAA,EACvE;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,UACZ,MACA,MACe;AACf,UAAM,MAAM,MAAM,OAAO;AACzB,UAAM,MAAM,GAAG,KAAK,OAAO,eAAe,IAAI;AAE9C,UAAM,kBAAkB,IAAI,gBAAgB;AAC5C,UAAM,YAAY,WAAW,MAAM;AACjC,sBAAgB,MAAM;AAAA,IACxB,GAAG,iBAAiB,4BAA4B;AAEhD,QAAI;AACJ,QAAI;AACF,iBAAW,MAAM,MAAM,KAAK;AAAA,QAC1B,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,QAClB;AAAA,QACA,MAAM,KAAK,UAAU,EAAE,GAAG,MAAM,WAAW,IAAI,WAAW,CAAC;AAAA,QAC3D,QAAQ,gBAAgB;AAAA,MAC1B,CAAC;AAAA,IACH,SAAS,OAAO;AACd,YAAM,IAAI,sBAAsB;AAAA,QAC9B,SAAS,oCAAoC,GAAG,KAAK,KAAK;AAAA,QAC1D,OAAO;AAAA,MACT,CAAC;AAAA,IACH,UAAE;AACA,mBAAa,SAAS;AAAA,IACxB;AAEA,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,OAAO,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,EAAE;AACjD,YAAM,IAAI,sBAAsB;AAAA,QAC9B,SAAS,kBAAkB,IAAI,YAAY,SAAS,MAAM,IAAI,SAAS,UAAU,WAAM,IAAI;AAAA,QAC3F,YAAY,SAAS;AAAA,MACvB,CAAC;AAAA,IACH;AAIA,UAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,WAAO,WAAW,MAAM,KAAK,IAAI;AAAA,EACnC;AAAA,EAEA,MAAM,QAAQ,EAAE,OAAO,GAA6C;AAClE,QAAI;AACF,aAAO,MAAM,KAAK,UAAU,QAAQ,EAAE,OAAO,CAAC;AAAA,IAChD,SAAS,OAAO;AACd,UAAI,iBAAiB,yBAAyB,MAAM,eAAe,KAAK;AACtE,eAAO;AAAA,MACT;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,WAAW,SAQC;AAChB,WAAO,KAAK,UAAU,WAAW;AAAA,MAC/B,OAAO,SAAS;AAAA,MAChB,eAAe,SAAS;AAAA,IAC1B,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,aAAa;AAAA,IACjB;AAAA,IACA;AAAA,EACF,GAQkB;AAChB,WAAO,KAAK,UAAU,cAAc,EAAE,QAAQ,MAAM,CAAC;AAAA,EACvD;AAAA,EAEA,MAAM,gBAAgB;AAAA,IACpB;AAAA,IACA;AAAA,EACF,GAQkB;AAChB,WAAO,KAAK,UAAU,iBAAiB,EAAE,QAAQ,MAAM,CAAC;AAAA,EAC1D;AAAA,EAEA,MAAM,gBAAgB;AAAA,IACpB;AAAA,IACA;AAAA,EACF,GAGkB;AAChB,WAAO,KAAK,UAAU,iBAAiB,EAAE,QAAQ,QAAQ,CAAC;AAAA,EAC5D;AAAA,EAEA,MAAM,wBAAwB;AAAA,IAC5B;AAAA,IACA;AAAA,EACF,GAGkB;AAChB,WAAO,KAAK,UAAU,0BAA0B,EAAE,QAAQ,cAAc,CAAC;AAAA,EAC3E;AACF;AAEA,eAAsB,sBAAsB;AAC1C,SAAO,iBAAiB,MAAM;AAChC;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  export const BUILD_METADATA = {
2
2
  "packageName": "@replohq/sdk",
3
- "version": "1.7.0",
3
+ "version": "1.7.2",
4
4
  "branch": "HEAD",
5
- "commit": "4428df5cda43875d380e6f03aeb24ce9a2dc6663",
6
- "builtAt": "2026-09-10T23:04:53.730Z"
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 { getCanopyApiGateway } from "../gateways/canopy-api";
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 ["loader", loaderKey, sortedArgsJson];
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({ loaderKey, args });
24
- return unstable_cache(
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
- const response = await gateway.invokeLoader({ loaderKey, args });
28
- return response.data;
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 { getCanopyApiGateway } from \"../gateways/canopy-api\";\nimport { resolvePreviewPlaceholderData } from \"./preview-placeholder\";\n\nconst DEFAULT_REVALIDATE_SECONDS = 60;\n\nfunction buildStableCacheKeyParts({\n loaderKey,\n args,\n}: {\n loaderKey: string;\n args: Record<string, unknown>;\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 [\"loader\", loaderKey, sortedArgsJson];\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 * @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({ loaderKey, args });\n\n return unstable_cache(\n async () => {\n const gateway = await getCanopyApiGateway();\n const response = await gateway.invokeLoader({ loaderKey, args });\n return response.data;\n },\n cacheKeyParts,\n {\n revalidate: revalidateSeconds,\n tags: [`loader:${loaderKey}`, ...cacheKeyParts],\n },\n )();\n}\n"],
5
- "mappings": "AAAA,SAAS,sBAAsB;AAE/B,SAAS,2BAA2B;AACpC,SAAS,qCAAqC;AAE9C,MAAM,6BAA6B;AAEnC,SAAS,yBAAyB;AAAA,EAChC;AAAA,EACA;AACF,GAGa;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,CAAC,UAAU,WAAW,cAAc;AAC7C;AAeA,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,EAAE,WAAW,KAAK,CAAC;AAElE,SAAO;AAAA,IACL,YAAY;AACV,YAAM,UAAU,MAAM,oBAAoB;AAC1C,YAAM,WAAW,MAAM,QAAQ,aAAa,EAAE,WAAW,KAAK,CAAC;AAC/D,aAAO,SAAS;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,MACE,YAAY;AAAA,MACZ,MAAM,CAAC,UAAU,SAAS,IAAI,GAAG,aAAa;AAAA,IAChD;AAAA,EACF,EAAE;AACJ;",
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
  }
@@ -20,9 +20,7 @@ function cloudflareLoader({
20
20
  width,
21
21
  quality
22
22
  }) {
23
- if (process.env.NODE_ENV === "development") {
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
- return `/cdn-cgi/image/${params.join(",")}/${src}`;
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
  }
@@ -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 rewritten URLs would 404.\n if (process.env.NODE_ENV === \"development\") {\n return src;\n }\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 return `/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 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;AAI3B,MAAI,QAAQ,IAAI,aAAa,eAAe;AAC1C,WAAO;AAAA,EACT;AAEA,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,aAAO,kBAAkB,OAAO,KAAK,GAAG,CAAC,IAAI,GAAG;AAAA,IAClD;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,QAAM,gBAAgB,IAAI,WAAW,GAAG,IAAI,IAAI,MAAM,CAAC,IAAI;AAC3D,SAAO,kBAAkB,OAAO,KAAK,GAAG,CAAC,IAAI,aAAa;AAC5D;",
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.0",
3
+ "version": "1.7.2",
4
4
  "reploBuild": {
5
5
  "packageName": "@replohq/sdk",
6
- "version": "1.7.0",
6
+ "version": "1.7.2",
7
7
  "branch": "HEAD",
8
- "commit": "4428df5cda43875d380e6f03aeb24ce9a2dc6663",
9
- "builtAt": "2026-09-10T23:04:53.730Z"
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",