@brytdesigns/shopify-ajax-client 1.0.7 → 3.0.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.
@@ -1,10 +1,20 @@
1
- import { Logging } from '@brytdesigns/shopify-utils/effect';
1
+ import { Logging } from "@brytdesigns/shopify-utils/effect";
2
+ //#region src/LoggerUtils.ts
3
+ /**
4
+ * Creates a function that adds a namespaced prefix to log messages.
5
+ *
6
+ * @param namespace - The prefix to be added to log messages.
7
+ * @returns A function that takes a log message and returns the message with the namespace prefix.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * const namespacedLog = withNamespacedLogSpan("[MyApp]");
12
+ * console.log(namespacedLog("Hello, world!")); // Output: "[MyApp] Hello, world!"
13
+ * ```
14
+ */
15
+ var withNamespacedLogSpan = Logging.makeNamespacedLogSpan("[@brytdesigns/shopify-ajax-client]");
16
+ Logging.filterLevelOrNever;
17
+ //#endregion
18
+ export { withNamespacedLogSpan };
2
19
 
3
- const withNamespacedLogSpan = Logging.makeNamespacedLogSpan(
4
- "[@brytdesigns/shopify-ajax-client]"
5
- );
6
- const filterLevelOrNever = Logging.filterLevelOrNever;
7
-
8
- export { filterLevelOrNever, withNamespacedLogSpan };
9
- //# sourceMappingURL=LoggerUtils.js.map
10
20
  //# sourceMappingURL=LoggerUtils.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/LoggerUtils.ts"],"names":[],"mappings":";;AAcO,MAAM,wBAAwB,OAAQ,CAAA,qBAAA;AAAA,EAC3C;AACF;AAeO,MAAM,qBAAqB,OAAQ,CAAA","file":"LoggerUtils.js","sourcesContent":["import { Logging } from \"@brytdesigns/shopify-utils/effect\";\n\n/**\n * Creates a function that adds a namespaced prefix to log messages.\n *\n * @param namespace - The prefix to be added to log messages.\n * @returns A function that takes a log message and returns the message with the namespace prefix.\n *\n * @example\n * ```typescript\n * const namespacedLog = withNamespacedLogSpan(\"[MyApp]\");\n * console.log(namespacedLog(\"Hello, world!\")); // Output: \"[MyApp] Hello, world!\"\n * ```\n */\nexport const withNamespacedLogSpan = Logging.makeNamespacedLogSpan(\n \"[@brytdesigns/shopify-ajax-client]\"\n);\n\n/**\n * Filters log messages based on a specified log level.\n *\n * @param level - The log level to filter messages by.\n * @returns A function that takes a log message and returns the message if its level is greater than or equal to the specified level.\n *\n * @example\n * ```typescript\n * const filteredLog = filterLevelOrNever(LogLevel.INFO);\n * console.log(filteredLog(LogLevel.DEBUG, \"This message will be filtered\")); // Output: (nothing)\n * console.log(filteredLog(LogLevel.INFO, \"This message will be logged\")); // Output: \"This message will be logged\"\n * ```\n */\nexport const filterLevelOrNever = Logging.filterLevelOrNever;\n"]}
1
+ {"version":3,"file":"LoggerUtils.js","names":[],"sources":["../../src/LoggerUtils.ts"],"sourcesContent":["import { Logging } from \"@brytdesigns/shopify-utils/effect\";\n\n/**\n * Creates a function that adds a namespaced prefix to log messages.\n *\n * @param namespace - The prefix to be added to log messages.\n * @returns A function that takes a log message and returns the message with the namespace prefix.\n *\n * @example\n * ```typescript\n * const namespacedLog = withNamespacedLogSpan(\"[MyApp]\");\n * console.log(namespacedLog(\"Hello, world!\")); // Output: \"[MyApp] Hello, world!\"\n * ```\n */\nexport const withNamespacedLogSpan = Logging.makeNamespacedLogSpan(\n\t\"[@brytdesigns/shopify-ajax-client]\",\n);\n\n/**\n * Filters log messages based on a specified log level.\n *\n * @param level - The log level to filter messages by.\n * @returns A function that takes a log message and returns the message if its level is greater than or equal to the specified level.\n *\n * @example\n * ```typescript\n * const filteredLog = filterLevelOrNever(LogLevel.INFO);\n * console.log(filteredLog(LogLevel.DEBUG, \"This message will be filtered\")); // Output: (nothing)\n * console.log(filteredLog(LogLevel.INFO, \"This message will be logged\")); // Output: \"This message will be logged\"\n * ```\n */\nexport const filterLevelOrNever = Logging.filterLevelOrNever;\n"],"mappings":";;;;;;;;;;;;;;AAcA,IAAa,wBAAwB,QAAQ,sBAC5C,oCACD;AAekC,QAAQ"}
@@ -0,0 +1,200 @@
1
+ import { __exportAll } from "./_virtual/_rolldown/runtime.js";
2
+ import { AjaxResponseError, InvalidAjaxMethodError, UnsafeAjaxRouteError } from "./errors.js";
3
+ import { withNamespacedLogSpan } from "./LoggerUtils.js";
4
+ import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/http";
5
+ import { Ajax } from "@brytdesigns/shopify-utils/effect";
6
+ import * as Cause from "effect/Cause";
7
+ import * as Effect from "effect/Effect";
8
+ import { dual } from "effect/Function";
9
+ import * as LogLevel from "effect/LogLevel";
10
+ import * as Pipeable from "effect/Pipeable";
11
+ import * as Predicate from "effect/Predicate";
12
+ import * as Schema from "effect/Schema";
13
+ //#region src/Request.ts
14
+ var Request_exports = /* @__PURE__ */ __exportAll({
15
+ execute: () => execute,
16
+ isAjaxRequest: () => isAjaxRequest,
17
+ makeRoute: () => makeRoute,
18
+ withHeader: () => withHeader,
19
+ withHeaders: () => withHeaders,
20
+ withInput: () => withInput,
21
+ withSections: () => withSections
22
+ });
23
+ Schema.Struct({});
24
+ Ajax.Sections.Input.pipe(Schema.fieldsAssign({ handle: Schema.String }));
25
+ var METHOD = {
26
+ GET: "get",
27
+ POST: "post"
28
+ };
29
+ var METHODS_BY_URL = {
30
+ products_url: METHOD.GET,
31
+ product_url: METHOD.GET,
32
+ predictive_search_url: METHOD.GET,
33
+ product_recommendations_url: METHOD.GET,
34
+ cart_add_url: METHOD.POST,
35
+ cart_update_url: METHOD.POST,
36
+ cart_url: METHOD.GET,
37
+ cart_change_url: METHOD.POST,
38
+ cart_clear_url: METHOD.POST
39
+ };
40
+ var TypeIdSymbol = Symbol.for("@brytdesigns/shopify-ajax-client/AjaxRequest");
41
+ /** Narrows an unknown value to an {@link AjaxRequest}. */
42
+ var isAjaxRequest = (u) => Predicate.hasProperty(u, TypeIdSymbol);
43
+ var makeRequest = (fields) => ({
44
+ ...fields,
45
+ [TypeIdSymbol]: TypeIdSymbol,
46
+ pipe() {
47
+ return Pipeable.pipeArguments(this, arguments);
48
+ }
49
+ });
50
+ /**
51
+ * Builds a route constructor.
52
+ *
53
+ * Replaces `makeFactory`. The difference is only in what the returned function
54
+ * gives back: a description that can be piped through the combinators below,
55
+ * rather than an `Effect` that is already closed over its headers.
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * const getCart = Request.makeRoute({
60
+ * routeName: "cart_url",
61
+ * inputSchema: CartGetInput,
62
+ * outputSchema: makeCartOutput,
63
+ * });
64
+ *
65
+ * pipe(
66
+ * getCart(),
67
+ * Request.withSections("cart-drawer"),
68
+ * Request.withHeader("X-Foo", "1"),
69
+ * Request.execute,
70
+ * );
71
+ * ```
72
+ */
73
+ var makeRoute = ({ routeName, inputSchema, outputSchema }) => (input) => makeRequest({
74
+ routeName,
75
+ inputSchema,
76
+ outputSchema,
77
+ input: input ?? {},
78
+ headers: {}
79
+ });
80
+ /**
81
+ * Merges headers into the request, right-biased.
82
+ *
83
+ * Dual: `withHeaders(request, h)` and `pipe(request, withHeaders(h))` are the
84
+ * same call.
85
+ */
86
+ var withHeaders = dual(2, (self, headers) => makeRequest({
87
+ ...self,
88
+ headers: {
89
+ ...self.headers,
90
+ ...headers
91
+ }
92
+ }));
93
+ /** Sets a single header. Dual. */
94
+ var withHeader = dual(3, (self, name, value) => makeRequest({
95
+ ...self,
96
+ headers: {
97
+ ...self.headers,
98
+ [name]: value
99
+ }
100
+ }));
101
+ /** Replaces the request input wholesale. Dual. */
102
+ var withInput = dual(2, (self, input) => makeRequest({
103
+ ...self,
104
+ input
105
+ }));
106
+ /**
107
+ * Asks Shopify to re-render `sections` alongside the response.
108
+ *
109
+ * Sugar over {@link withInput} for the one input field every route shares, and
110
+ * the field {@link AjaxRequest.outputSchema} is keyed on. Dual.
111
+ */
112
+ var withSections = dual(2, (self, sections) => makeRequest({
113
+ ...self,
114
+ input: {
115
+ ...self.input,
116
+ sections: Predicate.isString(sections) ? [sections] : sections
117
+ }
118
+ }));
119
+ /**
120
+ * Runs the description.
121
+ *
122
+ * Named rather than untraced: this is the one place in the package worth a
123
+ * span, and upstream's guidance is to reserve `Effect.fn` for genuine tracing
124
+ * boundaries and prefer `fnUntraced` inside library internals. Transforms are
125
+ * passed as trailing arguments because `Effect.fn` explicitly must not be
126
+ * `.pipe`d.
127
+ *
128
+ * Errors are left as they come. `RequestError`, `ResponseError`, `ParseError`
129
+ * and `HttpBodyError` are already tagged, and flattening them into a plain
130
+ * `Error` — which this package used to do — destroyed the only machine-readable
131
+ * thing about them.
132
+ */
133
+ var execute = Effect.fn("AjaxClient.execute")(function* (self) {
134
+ const client = yield* HttpClient.HttpClient;
135
+ const routes = yield* Ajax.Window.ShopifyRoutes.Routes;
136
+ const origin = yield* Ajax.Window.StorefrontOrigin.Origin;
137
+ const decodedInput = yield* Schema.decodeUnknownEffect(self.inputSchema)(self.input);
138
+ const route = self.routeName === "product_url" && "handle" in decodedInput ? routes[self.routeName].replaceAll(":handle", encodeURIComponent(String(decodedInput.handle))) : routes[self.routeName];
139
+ let url;
140
+ if (origin === "") url = route;
141
+ else {
142
+ const resolved = yield* Effect.try({
143
+ try: () => new URL(route, origin),
144
+ catch: () => new UnsafeAjaxRouteError({
145
+ route,
146
+ routeName: self.routeName,
147
+ origin
148
+ })
149
+ });
150
+ const allowed = yield* Ajax.Window.AllowedRouteOrigins.Origins;
151
+ if (resolved.origin !== new URL(origin).origin && !allowed.includes(resolved.origin)) return yield* new UnsafeAjaxRouteError({
152
+ route,
153
+ routeName: self.routeName,
154
+ origin
155
+ });
156
+ url = resolved.toString();
157
+ }
158
+ const method = METHODS_BY_URL[self.routeName];
159
+ const headers = {
160
+ "X-SDK-Variant": "brytdesigns",
161
+ "X-SDK-Variant-Source": "shopify-ajax-client",
162
+ ...self.headers
163
+ };
164
+ let request;
165
+ if (method === METHOD.POST) request = yield* HttpClientRequest.post(url, {
166
+ acceptJson: true,
167
+ headers
168
+ }).pipe(HttpClientRequest.bodyJson(decodedInput));
169
+ else if (method === METHOD.GET) request = HttpClientRequest.get(url, {
170
+ acceptJson: true,
171
+ headers
172
+ }).pipe(HttpClientRequest.setUrlParams(decodedInput));
173
+ else return yield* new InvalidAjaxMethodError();
174
+ const withBody = yield* LogLevel.isEnabled("Debug");
175
+ yield* Effect.annotateLogsScoped(withBody ? {
176
+ method,
177
+ route,
178
+ input: decodedInput
179
+ } : {
180
+ method,
181
+ route
182
+ });
183
+ const response = yield* client.execute(request);
184
+ if (response.status !== 200) {
185
+ const parsed = (yield* response.json.pipe(Effect.catchCause(() => Effect.succeed({})))) ?? {};
186
+ return yield* new AjaxResponseError({
187
+ status: Predicate.isNumber(parsed.status) ? parsed.status : response.status,
188
+ message: parsed.message ?? `Request failed with status ${response.status}`,
189
+ description: parsed.description,
190
+ route
191
+ });
192
+ }
193
+ const output = yield* HttpClientResponse.schemaBodyJson(self.outputSchema(decodedInput.sections))(response);
194
+ yield* Effect.logInfo("Request");
195
+ return output;
196
+ }, Effect.tapCause((cause) => Cause.squash(cause) instanceof AjaxResponseError ? Effect.logDebug("Ajax request returned a non-200", cause) : Effect.logError("Ajax request failed", cause)), Effect.scoped, withNamespacedLogSpan("execute"));
197
+ //#endregion
198
+ export { Request_exports, execute, isAjaxRequest, makeRoute, withHeader, withHeaders, withInput, withSections };
199
+
200
+ //# sourceMappingURL=Request.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Request.js","names":[],"sources":["../../src/Request.ts"],"sourcesContent":["import { Ajax } from \"@brytdesigns/shopify-utils/effect\";\nimport * as Cause from \"effect/Cause\";\nimport * as Effect from \"effect/Effect\";\nimport { dual } from \"effect/Function\";\nimport { HttpClient, HttpClientRequest, HttpClientResponse } from \"effect/http\";\nimport * as LogLevel from \"effect/LogLevel\";\nimport * as Pipeable from \"effect/Pipeable\";\nimport * as Predicate from \"effect/Predicate\";\nimport * as Schema from \"effect/Schema\";\nimport {\n\ttype AjaxErrorBody,\n\tAjaxResponseError,\n\tInvalidAjaxMethodError,\n\tUnsafeAjaxRouteError,\n} from \"./errors.js\";\nimport * as LoggerUtils from \"./LoggerUtils.js\";\n\n/**\n * Every route's input extends Shopify's `sections` pair, decoded.\n *\n * Constraining the descriptor on this is what lets {@link execute} read\n * `decodedInput.sections` and {@link withSections} write it without a cast.\n */\nexport type AjaxInput = typeof Ajax.Sections.Input.Type;\n\n/**\n * The same input, encoded — which is what a descriptor actually carries.\n *\n * Worth stating plainly because the two differ in a way that bites:\n * `sections` decodes from `ReadonlyArray<string>` to a comma-joined `string`,\n * so the encoded side is the array. A descriptor holds encoded input, so\n * {@link withSections} takes the array form.\n */\nexport type AjaxInputEncoded = typeof Ajax.Sections.Input.Encoded;\n\nconst BaseOutputSchema = Schema.Struct({});\nconst ProductInputSchema = Ajax.Sections.Input.pipe(\n\tSchema.fieldsAssign({\n\t\thandle: Schema.String,\n\t}),\n);\n\nconst METHOD = {\n\tGET: \"get\",\n\tPOST: \"post\",\n} as const;\n\nconst METHODS_BY_URL = {\n\tproducts_url: METHOD.GET,\n\tproduct_url: METHOD.GET,\n\tpredictive_search_url: METHOD.GET,\n\tproduct_recommendations_url: METHOD.GET,\n\tcart_add_url: METHOD.POST,\n\tcart_update_url: METHOD.POST,\n\tcart_url: METHOD.GET,\n\tcart_change_url: METHOD.POST,\n\tcart_clear_url: METHOD.POST,\n} as const;\n\nexport type ValidAjaxRoutes = keyof typeof METHODS_BY_URL;\n\nconst TypeIdSymbol: unique symbol = Symbol.for(\n\t\"@brytdesigns/shopify-ajax-client/AjaxRequest\",\n);\nexport type TypeId = typeof TypeIdSymbol;\n\n/**\n * A description of a Shopify Ajax request, before it is run.\n *\n * The reason this type exists at all: headers, sections and input are consumed\n * *inside* the request effect, so there is nothing to modify once that effect\n * has been constructed. `pipe(getCart(), withHeaders(...))` can only work if\n * the thing in the pipe is a description rather than an `Effect`. {@link execute}\n * is where the description becomes one.\n */\nexport interface AjaxRequest<\n\tA extends AjaxInput,\n\tI extends AjaxInputEncoded,\n\tRD,\n\tB,\n\tJ,\n\tSD,\n> extends Pipeable.Pipeable {\n\treadonly [TypeIdSymbol]: TypeId;\n\treadonly routeName: ValidAjaxRoutes;\n\treadonly inputSchema: Schema.Codec<A, I, RD>;\n\treadonly outputSchema: (sections?: string) => Schema.Codec<B, J, SD>;\n\treadonly input: I;\n\treadonly headers: Readonly<Record<string, string>>;\n}\n\n/** Narrows an unknown value to an {@link AjaxRequest}. */\nexport const isAjaxRequest = (\n\tu: unknown,\n): u is AjaxRequest<\n\tAjaxInput,\n\tAjaxInputEncoded,\n\tunknown,\n\tunknown,\n\tunknown,\n\tunknown\n> => Predicate.hasProperty(u, TypeIdSymbol);\n\nconst makeRequest = <\n\tA extends AjaxInput,\n\tI extends AjaxInputEncoded,\n\tRD,\n\tB,\n\tJ,\n\tSD,\n>(fields: {\n\treadonly routeName: ValidAjaxRoutes;\n\treadonly inputSchema: Schema.Codec<A, I, RD>;\n\treadonly outputSchema: (sections?: string) => Schema.Codec<B, J, SD>;\n\treadonly input: I;\n\treadonly headers: Readonly<Record<string, string>>;\n}): AjaxRequest<A, I, RD, B, J, SD> => ({\n\t...fields,\n\t[TypeIdSymbol]: TypeIdSymbol,\n\tpipe() {\n\t\t// biome-ignore lint/complexity/noArguments: Pipeable's contract is arguments-based.\n\t\treturn Pipeable.pipeArguments(this, arguments);\n\t},\n});\n\n/**\n * Builds a route constructor.\n *\n * Replaces `makeFactory`. The difference is only in what the returned function\n * gives back: a description that can be piped through the combinators below,\n * rather than an `Effect` that is already closed over its headers.\n *\n * @example\n * ```ts\n * const getCart = Request.makeRoute({\n * routeName: \"cart_url\",\n * inputSchema: CartGetInput,\n * outputSchema: makeCartOutput,\n * });\n *\n * pipe(\n * getCart(),\n * Request.withSections(\"cart-drawer\"),\n * Request.withHeader(\"X-Foo\", \"1\"),\n * Request.execute,\n * );\n * ```\n */\nexport const makeRoute =\n\t<\n\t\tRouteName extends ValidAjaxRoutes,\n\t\tA extends RouteName extends \"product_url\"\n\t\t\t? typeof ProductInputSchema.Type\n\t\t\t: AjaxInput,\n\t\tI extends AjaxInputEncoded,\n\t\tRD,\n\t\tB extends typeof BaseOutputSchema.Type,\n\t\tJ,\n\t\tSD,\n\t>({\n\t\trouteName,\n\t\tinputSchema,\n\t\toutputSchema,\n\t}: {\n\t\trouteName: RouteName;\n\t\tinputSchema: Schema.Codec<A, I, RD>;\n\t\t/**\n\t\t * Built per request from the caller's `sections` input.\n\t\t *\n\t\t * v4's schema extension (`fieldsAssign` / `extendTo`) only applies to a\n\t\t * `Struct`, and output schemas here are transforms. Composing the\n\t\t * sections field is therefore the caller's job - it holds the\n\t\t * pre-transform struct and can extend that instead.\n\t\t */\n\t\toutputSchema: (sections?: string) => Schema.Codec<B, J, SD>;\n\t}) =>\n\t(input?: I): AjaxRequest<A, I, RD, B, J, SD> =>\n\t\tmakeRequest({\n\t\t\trouteName,\n\t\t\tinputSchema,\n\t\t\toutputSchema,\n\t\t\tinput: input ?? ({} as I),\n\t\t\theaders: {},\n\t\t});\n\n/**\n * Merges headers into the request, right-biased.\n *\n * Dual: `withHeaders(request, h)` and `pipe(request, withHeaders(h))` are the\n * same call.\n */\nexport const withHeaders: {\n\t(\n\t\theaders: Readonly<Record<string, string>>,\n\t): <A extends AjaxInput, I extends AjaxInputEncoded, RD, B, J, SD>(\n\t\tself: AjaxRequest<A, I, RD, B, J, SD>,\n\t) => AjaxRequest<A, I, RD, B, J, SD>;\n\t<A extends AjaxInput, I extends AjaxInputEncoded, RD, B, J, SD>(\n\t\tself: AjaxRequest<A, I, RD, B, J, SD>,\n\t\theaders: Readonly<Record<string, string>>,\n\t): AjaxRequest<A, I, RD, B, J, SD>;\n} = dual(\n\t2,\n\t<A extends AjaxInput, I extends AjaxInputEncoded, RD, B, J, SD>(\n\t\tself: AjaxRequest<A, I, RD, B, J, SD>,\n\t\theaders: Readonly<Record<string, string>>,\n\t): AjaxRequest<A, I, RD, B, J, SD> =>\n\t\tmakeRequest({ ...self, headers: { ...self.headers, ...headers } }),\n);\n\n/** Sets a single header. Dual. */\nexport const withHeader: {\n\t(\n\t\tname: string,\n\t\tvalue: string,\n\t): <A extends AjaxInput, I extends AjaxInputEncoded, RD, B, J, SD>(\n\t\tself: AjaxRequest<A, I, RD, B, J, SD>,\n\t) => AjaxRequest<A, I, RD, B, J, SD>;\n\t<A extends AjaxInput, I extends AjaxInputEncoded, RD, B, J, SD>(\n\t\tself: AjaxRequest<A, I, RD, B, J, SD>,\n\t\tname: string,\n\t\tvalue: string,\n\t): AjaxRequest<A, I, RD, B, J, SD>;\n} = dual(\n\t3,\n\t<A extends AjaxInput, I extends AjaxInputEncoded, RD, B, J, SD>(\n\t\tself: AjaxRequest<A, I, RD, B, J, SD>,\n\t\tname: string,\n\t\tvalue: string,\n\t): AjaxRequest<A, I, RD, B, J, SD> =>\n\t\tmakeRequest({ ...self, headers: { ...self.headers, [name]: value } }),\n);\n\n/** Replaces the request input wholesale. Dual. */\nexport const withInput: {\n\t<I extends AjaxInputEncoded>(\n\t\tinput: I,\n\t): <A extends AjaxInput, RD, B, J, SD>(\n\t\tself: AjaxRequest<A, I, RD, B, J, SD>,\n\t) => AjaxRequest<A, I, RD, B, J, SD>;\n\t<A extends AjaxInput, I extends AjaxInputEncoded, RD, B, J, SD>(\n\t\tself: AjaxRequest<A, I, RD, B, J, SD>,\n\t\tinput: I,\n\t): AjaxRequest<A, I, RD, B, J, SD>;\n} = dual(\n\t2,\n\t<A extends AjaxInput, I extends AjaxInputEncoded, RD, B, J, SD>(\n\t\tself: AjaxRequest<A, I, RD, B, J, SD>,\n\t\tinput: I,\n\t): AjaxRequest<A, I, RD, B, J, SD> => makeRequest({ ...self, input }),\n);\n\n/**\n * Asks Shopify to re-render `sections` alongside the response.\n *\n * Sugar over {@link withInput} for the one input field every route shares, and\n * the field {@link AjaxRequest.outputSchema} is keyed on. Dual.\n */\nexport const withSections: {\n\t(\n\t\tsections: string | ReadonlyArray<string>,\n\t): <A extends AjaxInput, I extends AjaxInputEncoded, RD, B, J, SD>(\n\t\tself: AjaxRequest<A, I, RD, B, J, SD>,\n\t) => AjaxRequest<A, I, RD, B, J, SD>;\n\t<A extends AjaxInput, I extends AjaxInputEncoded, RD, B, J, SD>(\n\t\tself: AjaxRequest<A, I, RD, B, J, SD>,\n\t\tsections: string | ReadonlyArray<string>,\n\t): AjaxRequest<A, I, RD, B, J, SD>;\n} = dual(\n\t2,\n\t<A extends AjaxInput, I extends AjaxInputEncoded, RD, B, J, SD>(\n\t\tself: AjaxRequest<A, I, RD, B, J, SD>,\n\t\tsections: string | ReadonlyArray<string>,\n\t): AjaxRequest<A, I, RD, B, J, SD> =>\n\t\tmakeRequest({\n\t\t\t...self,\n\t\t\t// `as unknown as I` rather than `as I`: `I` is only known to extend\n\t\t\t// the sections pair, so TypeScript cannot see that adding the field\n\t\t\t// back preserves whatever else the route's input carries. The spread\n\t\t\t// above is what actually preserves it.\n\t\t\tinput: {\n\t\t\t\t...self.input,\n\t\t\t\t// A single section name is the common case. The schema's encoded\n\t\t\t\t// side is the array either way, so normalise instead of\n\t\t\t\t// overloading the combinator.\n\t\t\t\tsections: Predicate.isString(sections) ? [sections] : sections,\n\t\t\t} as unknown as I,\n\t\t}),\n);\n\n/**\n * Runs the description.\n *\n * Named rather than untraced: this is the one place in the package worth a\n * span, and upstream's guidance is to reserve `Effect.fn` for genuine tracing\n * boundaries and prefer `fnUntraced` inside library internals. Transforms are\n * passed as trailing arguments because `Effect.fn` explicitly must not be\n * `.pipe`d.\n *\n * Errors are left as they come. `RequestError`, `ResponseError`, `ParseError`\n * and `HttpBodyError` are already tagged, and flattening them into a plain\n * `Error` — which this package used to do — destroyed the only machine-readable\n * thing about them.\n */\nexport const execute = Effect.fn(\"AjaxClient.execute\")(\n\tfunction* <A extends AjaxInput, I extends AjaxInputEncoded, RD, B, J, SD>(\n\t\tself: AjaxRequest<A, I, RD, B, J, SD>,\n\t) {\n\t\tconst client = yield* HttpClient.HttpClient;\n\t\tconst routes = yield* Ajax.Window.ShopifyRoutes.Routes;\n\t\tconst origin = yield* Ajax.Window.StorefrontOrigin.Origin;\n\n\t\tconst decodedInput = yield* Schema.decodeUnknownEffect(self.inputSchema)(\n\t\t\tself.input,\n\t\t);\n\n\t\tconst route =\n\t\t\tself.routeName === \"product_url\" && \"handle\" in decodedInput\n\t\t\t\t? // `encodeURIComponent` rather than raw interpolation, for two\n\t\t\t\t\t// separate reasons. A handle of `../cart/clear.js` would resolve\n\t\t\t\t\t// to a different same-origin endpoint, carrying the shopper's\n\t\t\t\t\t// session cookies to it - and `/cart/clear.js` answers GET. And\n\t\t\t\t\t// `String.prototype.replace` expands `$&`, `` $` `` and `$'` in\n\t\t\t\t\t// the *replacement*, so a handle containing `$` rewrites the path\n\t\t\t\t\t// on its own. Encoding closes both: the output never contains\n\t\t\t\t\t// `/`, `.` is harmless without `/`, and `$` is escaped.\n\t\t\t\t\troutes[self.routeName].replaceAll(\n\t\t\t\t\t\t\":handle\",\n\t\t\t\t\t\tencodeURIComponent(String(decodedInput.handle)),\n\t\t\t\t\t)\n\t\t\t\t: routes[self.routeName];\n\n\t\t// Resolved rather than concatenated, and then checked.\n\t\t//\n\t\t// `new URL(route, origin)` treats the route as a path relative to the\n\t\t// origin, so the userinfo trick that `` `${origin}${route}` `` fell for\n\t\t// (\"@evil.com/collect\") lands harmlessly under the storefront. The\n\t\t// explicit origin comparison is still needed for the case `URL` honours\n\t\t// rather than neutralises: a fully absolute route like\n\t\t// \"https://evil.com/collect\".\n\t\t//\n\t\t// `origin` is \"\" outside a DOM — ajax-client's own tests run without\n\t\t// jsdom — and `new URL(route, \"\")` throws, so that case keeps the old\n\t\t// relative-URL behaviour. There is no origin to escape from.\n\t\tlet url: string;\n\t\tif (origin === \"\") {\n\t\t\turl = route;\n\t\t} else {\n\t\t\tconst resolved = yield* Effect.try({\n\t\t\t\ttry: () => new URL(route, origin),\n\t\t\t\tcatch: () =>\n\t\t\t\t\tnew UnsafeAjaxRouteError({\n\t\t\t\t\t\troute,\n\t\t\t\t\t\trouteName: self.routeName,\n\t\t\t\t\t\torigin,\n\t\t\t\t\t}),\n\t\t\t});\n\t\t\t// Off-origin is allowed, but only when the theme has said so. A\n\t\t\t// storefront fronting its cart through a proxy or an edge worker is\n\t\t\t// a real case; a route that arrives off-origin without the matching\n\t\t\t// entry is either a typo or smuggled, and both should fail.\n\t\t\tconst allowed = yield* Ajax.Window.AllowedRouteOrigins.Origins;\n\t\t\tif (\n\t\t\t\tresolved.origin !== new URL(origin).origin &&\n\t\t\t\t!allowed.includes(resolved.origin)\n\t\t\t) {\n\t\t\t\treturn yield* new UnsafeAjaxRouteError({\n\t\t\t\t\troute,\n\t\t\t\t\trouteName: self.routeName,\n\t\t\t\t\torigin,\n\t\t\t\t});\n\t\t\t}\n\t\t\turl = resolved.toString();\n\t\t}\n\n\t\tconst method = METHODS_BY_URL[self.routeName];\n\t\tconst headers = {\n\t\t\t\"X-SDK-Variant\": \"brytdesigns\",\n\t\t\t\"X-SDK-Variant-Source\": \"shopify-ajax-client\",\n\t\t\t...self.headers,\n\t\t};\n\n\t\tlet request: HttpClientRequest.HttpClientRequest;\n\n\t\t// An explicit three-way rather than POST-or-else-GET: the fallback\n\t\t// branch silently turned any future method into a GET, and made\n\t\t// `InvalidAjaxMethodError` unreachable decoration.\n\t\tif (method === METHOD.POST) {\n\t\t\trequest = yield* HttpClientRequest.post(url, {\n\t\t\t\tacceptJson: true,\n\t\t\t\theaders,\n\t\t\t}).pipe(HttpClientRequest.bodyJson(decodedInput));\n\t\t} else if (method === METHOD.GET) {\n\t\t\trequest = HttpClientRequest.get(url, {\n\t\t\t\tacceptJson: true,\n\t\t\t\theaders,\n\t\t\t}).pipe(HttpClientRequest.setUrlParams(decodedInput));\n\t\t} else {\n\t\t\treturn yield* new InvalidAjaxMethodError();\n\t\t}\n\n\t\t// Annotated before the request goes out rather than after it comes back:\n\t\t// the `tapCause` below is the whole reason this package is debuggable,\n\t\t// and a failure that does not say which route it was is most of the way\n\t\t// to useless.\n\t\t//\n\t\t// `input` rides along only when Debug logging is actually on.\n\t\t//\n\t\t// It is the decoded request body, which for a cart write carries\n\t\t// line-item `properties` (gift messages, engraving text), the cart\n\t\t// `note` and `attributes` — shopper PII. Annotations attach\n\t\t// unconditionally and surface on any log the effect emits, and\n\t\t// `MinimumLogLevel` is Info by default for anyone on the documented\n\t\t// `./effect` path, so annotating it outright logged that payload on an\n\t\t// ordinary non-debug page.\n\t\t//\n\t\t// `isEnabled` rather than a flag of our own, so the gate is the one the\n\t\t// caller already set: the Promise facades pin \"None\" (nothing logs) or\n\t\t// \"All\" with `debug: true` (body included, which is the point of asking\n\t\t// for debug), and the bare Effect path sits at Info, where Debug is off\n\t\t// and the body stays out.\n\t\tconst withBody = yield* LogLevel.isEnabled(\"Debug\");\n\t\tyield* Effect.annotateLogsScoped(\n\t\t\twithBody ? { method, route, input: decodedInput } : { method, route },\n\t\t);\n\n\t\tconst response = yield* client.execute(request);\n\n\t\tif (response.status !== 200) {\n\t\t\t// Shopify's Ajax routes answer with a JSON error body, but the things\n\t\t\t// in front of them do not: a 502 from an edge or a WAF challenge is\n\t\t\t// an HTML page, and `response.json` fails on it. Letting that failure\n\t\t\t// through would replace the error consumers are told to catch with a\n\t\t\t// `ResponseError` carrying neither the status nor the route - which\n\t\t\t// is exactly the case where knowing the route matters most.\n\t\t\tconst body = yield* response.json.pipe(\n\t\t\t\tEffect.catchCause(() => Effect.succeed({} as Partial<AjaxErrorBody>)),\n\t\t\t);\n\t\t\tconst parsed = (body ?? {}) as Partial<AjaxErrorBody>;\n\n\t\t\treturn yield* new AjaxResponseError({\n\t\t\t\t// The body is whatever the server sent. Taking `status` from it\n\t\t\t\t// unchecked lets `{\"status\":\"500\"}` put a string behind a field\n\t\t\t\t// typed `number`, so a consumer's `error.status === 422` silently\n\t\t\t\t// never matches.\n\t\t\t\tstatus: Predicate.isNumber(parsed.status)\n\t\t\t\t\t? parsed.status\n\t\t\t\t\t: response.status,\n\t\t\t\tmessage:\n\t\t\t\t\tparsed.message ?? `Request failed with status ${response.status}`,\n\t\t\t\tdescription: parsed.description,\n\t\t\t\troute,\n\t\t\t});\n\t\t}\n\n\t\tconst output = yield* HttpClientResponse.schemaBodyJson(\n\t\t\tself.outputSchema(decodedInput.sections),\n\t\t)(response);\n\n\t\tyield* Effect.logInfo(\"Request\");\n\n\t\treturn output;\n\t},\n\t// --- Transform order matters here. ---\n\t// `Effect.fn` applies these left to right, so `tapCause` has to come first\n\t// to sit *inside* the scope and the log span. Outside them, the failure\n\t// line carries neither the span nor the `method`/`route` annotations - the\n\t// precise context it exists to record.\n\tEffect.tapCause((cause) =>\n\t\t// A non-200 is an ordinary outcome on a storefront: a sold-out variant\n\t\t// answers 422 on every add-to-cart. Logging that at ERROR would put a\n\t\t// red line in every shopper's console for a case the caller is about to\n\t\t// handle. Anything else - a transport failure, a schema mismatch, a\n\t\t// defect - is genuinely unexpected and keeps ERROR.\n\t\tCause.squash(cause) instanceof AjaxResponseError\n\t\t\t? Effect.logDebug(\"Ajax request returned a non-200\", cause)\n\t\t\t: Effect.logError(\"Ajax request failed\", cause),\n\t),\n\tEffect.scoped,\n\tLoggerUtils.withNamespacedLogSpan(\"execute\"),\n);\n"],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ //#region \0rolldown/runtime.js
2
+ var __defProp = Object.defineProperty;
3
+ var __exportAll = (all, no_symbols) => {
4
+ let target = {};
5
+ for (var name in all) __defProp(target, name, {
6
+ get: all[name],
7
+ enumerable: true
8
+ });
9
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
10
+ return target;
11
+ };
12
+ //#endregion
13
+ export { __exportAll };
@@ -1,20 +1,60 @@
1
- import * as Data from 'effect/Data';
1
+ import * as Data from "effect/Data";
2
+ //#region src/errors.ts
3
+ /**
4
+ * A non-200 response from a Shopify Ajax route.
5
+ *
6
+ * This is the error channel's version of what used to be
7
+ * `AjaxClientResponse.error`. The envelope made every failure look like a
8
+ * success carrying an error payload, so each of the nine call sites in
9
+ * `shopify-ajax-cart` re-raised it by hand — with `throw response.error`,
10
+ * which threw a plain object with no stack and no `_tag`. Failing properly
11
+ * means `Effect.catchTag("...AjaxResponseError", ...)` works upstream, and
12
+ * `instanceof` works for plain-JS consumers once the facade squashes it.
13
+ *
14
+ * `route` is carried because the status and message alone rarely identify
15
+ * which request failed when several are in flight.
16
+ */
17
+ var AjaxResponseError = class extends Data.TaggedError("@brytdesigns/ajax-client/AjaxResponseError") {};
18
+ /**
19
+ * A route was declared with an HTTP method this client cannot issue.
20
+ *
21
+ * Not reachable while every route in `METHODS_BY_URL` is `get` or `post`, which
22
+ * is all of them today. It is what `execute`'s method dispatch fails with if
23
+ * that stops being true. The alternative — a POST-or-else-GET fallback — would
24
+ * quietly issue a GET for a route declared as anything else.
25
+ */
26
+ var InvalidAjaxMethodError = class extends Data.TaggedError("@brytdesigns/ajax-client/InvalidAjaxMethodError") {
27
+ constructor() {
28
+ super({ message: "Invalid HTTP method | Allowed: 'get' or 'post'" });
29
+ }
30
+ };
31
+ /**
32
+ * A route in the table did not resolve to the storefront origin.
33
+ *
34
+ * The table is `{ ...Defaults, ...window.BrytDesigns.routes }`, so anything
35
+ * that can set that global contributes paths which every cart request is then
36
+ * sent to — with the shopper's session cookies attached. The old
37
+ * `` `${origin}${route}` `` concatenation made that an off-origin POST for a
38
+ * route as unremarkable-looking as `"@evil.com/collect"`, because
39
+ * `https://shop.myshopify.com@evil.com/collect` parses `shop.myshopify.com`
40
+ * as userinfo and `evil.com` as the host.
41
+ *
42
+ * Calibration: whoever can write `window.BrytDesigns.routes` can usually
43
+ * patch `fetch` too, so this is defence in depth rather than a privilege
44
+ * boundary. It earns its place by also catching the honest typo — a route of
45
+ * `"cart.js"` concatenated to an origin yielded
46
+ * `https://shop.myshopify.comcart.js`, which fails as a DNS error somewhere
47
+ * far from the mistake.
48
+ */
49
+ var UnsafeAjaxRouteError = class extends Data.TaggedError("@brytdesigns/ajax-client/UnsafeAjaxRouteError") {
50
+ constructor(options) {
51
+ super({
52
+ ...options,
53
+ message: `Route "${options.routeName}" resolves outside the storefront origin ${options.origin}: ${options.route}. If that is deliberate, list the origin in window.BrytDesigns.allowedRouteOrigins (or provide Ajax.Window.AllowedRouteOrigins.Origins).`
54
+ });
55
+ }
56
+ };
57
+ //#endregion
58
+ export { AjaxResponseError, InvalidAjaxMethodError, UnsafeAjaxRouteError };
2
59
 
3
- class CartError extends Data.TaggedError(
4
- "@brytdesigns/ajax-client/CartError"
5
- ) {
6
- }
7
- class InvalidAjaxMethodError extends Data.TaggedError(
8
- "@brytdesigns/ajax-client/InvalidAjaxMethodError"
9
- ) {
10
- /**
11
- * Constructs a new instance of InvalidAjaxMethodError with a default error message.
12
- */
13
- constructor() {
14
- super({ message: "Invalid HTTP method | Allowed: 'get' or 'post'" });
15
- }
16
- }
17
-
18
- export { CartError, InvalidAjaxMethodError };
19
- //# sourceMappingURL=errors.js.map
20
60
  //# sourceMappingURL=errors.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/errors.ts"],"names":[],"mappings":";;AAQO,MAAM,kBAAkB,IAAK,CAAA,WAAA;AAAA,EAClC;AACF,CAIG,CAAA;AAAC;AAQG,MAAM,+BAA+B,IAAK,CAAA,WAAA;AAAA,EAC/C;AACF,CAEG,CAAA;AAAA;AAAA;AAAA;AAAA,EAID,WAAc,GAAA;AACZ,IAAM,KAAA,CAAA,EAAE,OAAS,EAAA,gDAAA,EAAkD,CAAA;AAAA;AAEvE","file":"errors.js","sourcesContent":["import * as Data from \"effect/Data\";\n\n/**\n * Represents an error related to a cart operation.\n *\n * @extends Data.TaggedError\n * @template T - The type of the error data.\n */\nexport class CartError extends Data.TaggedError(\n \"@brytdesigns/ajax-client/CartError\"\n)<{\n status: number;\n message: string;\n description: string;\n}> {}\n\n/**\n * Represents an error when an invalid HTTP method is used.\n *\n * @extends Data.TaggedError\n * @template T - The type of the error data.\n */\nexport class InvalidAjaxMethodError extends Data.TaggedError(\n \"@brytdesigns/ajax-client/InvalidAjaxMethodError\"\n)<{\n message: string;\n}> {\n /**\n * Constructs a new instance of InvalidAjaxMethodError with a default error message.\n */\n constructor() {\n super({ message: \"Invalid HTTP method | Allowed: 'get' or 'post'\" });\n }\n}\n"]}
1
+ {"version":3,"file":"errors.js","names":[],"sources":["../../src/errors.ts"],"sourcesContent":["import * as Data from \"effect/Data\";\n\n/**\n * The shape Shopify returns in a non-200 response body.\n *\n * Distinct from {@link AjaxResponseError}: this is plain decoded JSON, that is\n * the tagged error we raise from it. v3's looser response types let the two be\n * conflated by a cast; v4 does not.\n */\nexport interface AjaxErrorBody {\n\tstatus: number;\n\tmessage: string;\n\tdescription: string;\n}\n\n/**\n * A non-200 response from a Shopify Ajax route.\n *\n * This is the error channel's version of what used to be\n * `AjaxClientResponse.error`. The envelope made every failure look like a\n * success carrying an error payload, so each of the nine call sites in\n * `shopify-ajax-cart` re-raised it by hand — with `throw response.error`,\n * which threw a plain object with no stack and no `_tag`. Failing properly\n * means `Effect.catchTag(\"...AjaxResponseError\", ...)` works upstream, and\n * `instanceof` works for plain-JS consumers once the facade squashes it.\n *\n * `route` is carried because the status and message alone rarely identify\n * which request failed when several are in flight.\n */\nexport class AjaxResponseError extends Data.TaggedError(\n\t\"@brytdesigns/ajax-client/AjaxResponseError\",\n)<{\n\tstatus: number;\n\tmessage: string;\n\tdescription?: string;\n\troute: string;\n}> {}\n\n/**\n * A route was declared with an HTTP method this client cannot issue.\n *\n * Not reachable while every route in `METHODS_BY_URL` is `get` or `post`, which\n * is all of them today. It is what `execute`'s method dispatch fails with if\n * that stops being true. The alternative — a POST-or-else-GET fallback — would\n * quietly issue a GET for a route declared as anything else.\n */\nexport class InvalidAjaxMethodError extends Data.TaggedError(\n\t\"@brytdesigns/ajax-client/InvalidAjaxMethodError\",\n)<{\n\tmessage: string;\n}> {\n\tconstructor() {\n\t\tsuper({ message: \"Invalid HTTP method | Allowed: 'get' or 'post'\" });\n\t}\n}\n\n/**\n * A route in the table did not resolve to the storefront origin.\n *\n * The table is `{ ...Defaults, ...window.BrytDesigns.routes }`, so anything\n * that can set that global contributes paths which every cart request is then\n * sent to — with the shopper's session cookies attached. The old\n * `` `${origin}${route}` `` concatenation made that an off-origin POST for a\n * route as unremarkable-looking as `\"@evil.com/collect\"`, because\n * `https://shop.myshopify.com@evil.com/collect` parses `shop.myshopify.com`\n * as userinfo and `evil.com` as the host.\n *\n * Calibration: whoever can write `window.BrytDesigns.routes` can usually\n * patch `fetch` too, so this is defence in depth rather than a privilege\n * boundary. It earns its place by also catching the honest typo — a route of\n * `\"cart.js\"` concatenated to an origin yielded\n * `https://shop.myshopify.comcart.js`, which fails as a DNS error somewhere\n * far from the mistake.\n */\nexport class UnsafeAjaxRouteError extends Data.TaggedError(\n\t\"@brytdesigns/ajax-client/UnsafeAjaxRouteError\",\n)<{\n\tmessage: string;\n\troute: string;\n\trouteName: string;\n\torigin: string;\n}> {\n\tconstructor(options: {\n\t\troute: string;\n\t\trouteName: string;\n\t\torigin: string;\n\t}) {\n\t\tsuper({\n\t\t\t...options,\n\t\t\tmessage:\n\t\t\t\t`Route \"${options.routeName}\" resolves outside the storefront origin ${options.origin}: ${options.route}. ` +\n\t\t\t\t`If that is deliberate, list the origin in window.BrytDesigns.allowedRouteOrigins (or provide Ajax.Window.AllowedRouteOrigins.Origins).`,\n\t\t});\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA6BA,IAAa,oBAAb,cAAuC,KAAK,YAC3C,4CACD,CAAC,CAKE,CAAC;;;;;;;;;AAUJ,IAAa,yBAAb,cAA4C,KAAK,YAChD,iDACD,CAAC,CAEE;CACF,cAAc;EACb,MAAM,EAAE,SAAS,iDAAiD,CAAC;CACpE;AACD;;;;;;;;;;;;;;;;;;;AAoBA,IAAa,uBAAb,cAA0C,KAAK,YAC9C,+CACD,CAAC,CAKE;CACF,YAAY,SAIT;EACF,MAAM;GACL,GAAG;GACH,SACC,UAAU,QAAQ,UAAU,2CAA2C,QAAQ,OAAO,IAAI,QAAQ,MAAM;EAE1G,CAAC;CACF;AACD"}