@scayle/storefront-nuxt 8.60.1 → 8.61.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/module.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scayle/storefront-nuxt",
3
- "version": "8.60.1",
3
+ "version": "8.61.0",
4
4
  "configKey": "storefront",
5
5
  "compatibility": {
6
6
  "nuxt": ">=3.13"
package/dist/module.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { existsSync } from 'node:fs';
2
- import { defineNuxtModule, createResolver, addServerTemplate, addTypeTemplate, extendViteConfig, addPlugin, addImportsDir } from '@nuxt/kit';
2
+ import { defineNuxtModule, createResolver, addServerTemplate, addTypeTemplate, extendViteConfig, addTemplate, updateTemplates, addPlugin, addImportsDir } from '@nuxt/kit';
3
3
  import { rpcMethods } from '@scayle/storefront-core';
4
4
  import { defu } from 'defu';
5
5
  import { genExport, genImport, genSafeVariableName } from 'knitwork';
@@ -54,7 +54,7 @@ export default {
54
54
  }`;
55
55
  }
56
56
  const PACKAGE_NAME = "@scayle/storefront-nuxt";
57
- const PACKAGE_VERSION = "8.60.1";
57
+ const PACKAGE_VERSION = "8.61.0";
58
58
  const logger = createConsola({
59
59
  fancy: true,
60
60
  formatOptions: {
@@ -89,14 +89,70 @@ function createRpcMethodTypeDeclaration(customRpcImports) {
89
89
  }
90
90
  export {}`;
91
91
  }
92
+ const DEFAULT_RPC_HTTP_METHOD = "POST";
93
+ const ALLOWED_RPC_HTTP_METHODS = /* @__PURE__ */ new Set([
94
+ "GET",
95
+ "POST",
96
+ "PUT",
97
+ "DELETE"
98
+ ]);
99
+ function readHttpMethod(handler) {
100
+ if (handler && typeof handler === "function" && "httpMethod" in handler && typeof handler.httpMethod === "string") {
101
+ const method = handler.httpMethod.toUpperCase();
102
+ if (ALLOWED_RPC_HTTP_METHODS.has(method)) {
103
+ return method;
104
+ }
105
+ }
106
+ return DEFAULT_RPC_HTTP_METHOD;
107
+ }
108
+ async function buildRpcHttpMethodManifest(customRpcImports, coreHandlers) {
109
+ const manifest = {};
110
+ for (const [name, handler] of Object.entries(coreHandlers)) {
111
+ manifest[name] = readHttpMethod(handler);
112
+ }
113
+ if (customRpcImports.length === 0) {
114
+ return manifest;
115
+ }
116
+ for (const { source, names } of customRpcImports) {
117
+ let loadedModule;
118
+ try {
119
+ loadedModule = await import(source);
120
+ } catch (error) {
121
+ logger.warn(
122
+ `Failed to load custom RPC module '${source}' while inferring HTTP methods. Methods from this module will default to '${DEFAULT_RPC_HTTP_METHOD}'.`,
123
+ error
124
+ );
125
+ }
126
+ for (const name of names) {
127
+ manifest[name] = loadedModule && name in loadedModule ? readHttpMethod(loadedModule[name]) : DEFAULT_RPC_HTTP_METHOD;
128
+ }
129
+ }
130
+ return manifest;
131
+ }
132
+ function createRpcHttpMethodsSource(manifest) {
133
+ return `// Auto-generated by @scayle/storefront-nuxt
134
+ export const rpcHttpMethods = ${JSON.stringify(manifest, null, 2)}
135
+ `;
136
+ }
92
137
  async function nitroSetup(nitroConfig, config, moduleOptions) {
93
138
  const { resolve, resolvePath } = createResolver(import.meta.url);
94
- const { apiBasePath, customRpcImports, coreMethodNames, usedDrivers } = config;
139
+ const {
140
+ apiBasePath,
141
+ customRpcImports,
142
+ coreMethodNames,
143
+ usedDrivers,
144
+ rpcHttpMethodManifest,
145
+ rpcHttpMethodsPath
146
+ } = config;
95
147
  nitroConfig.replace = nitroConfig.replace || {};
96
148
  nitroConfig.replace["process.env.SFC_OMIT_MD5"] = stringToBoolean(
97
149
  process.env.SFC_OMIT_MD5
98
150
  );
99
151
  nitroConfig.replace.__sfc_version = PACKAGE_VERSION;
152
+ nitroConfig.alias = {
153
+ ...nitroConfig.alias,
154
+ "#virtual/rpcHttpMethods": rpcHttpMethodsPath
155
+ };
100
156
  nitroConfig.virtual = {
101
157
  ...nitroConfig.virtual,
102
158
  "#virtual/storage-drivers": createVirtualDriverImport(
@@ -116,10 +172,11 @@ async function nitroSetup(nitroConfig, config, moduleOptions) {
116
172
  []
117
173
  ).concat(coreMethodNames);
118
174
  for (const methodName of allRpcNames) {
175
+ const httpMethod = rpcHttpMethodManifest[methodName] ?? DEFAULT_RPC_HTTP_METHOD;
119
176
  nitroConfig.handlers.push({
120
177
  route: `${apiBasePath}/rpc/${methodName}`,
121
178
  handler: resolve("./runtime/api/rpcHandler"),
122
- method: "post"
179
+ method: httpMethod.toLowerCase()
123
180
  });
124
181
  }
125
182
  nitroConfig.handlers.push({
@@ -243,7 +300,9 @@ const module$1 = defineNuxtModule({
243
300
  },
244
301
  async setup(options, nuxt) {
245
302
  const { resolve } = createResolver(import.meta.url);
246
- const { resolve: appResolve } = createResolver(nuxt.options.rootDir);
303
+ const { resolve: appResolve, resolvePath } = createResolver(
304
+ nuxt.options.rootDir
305
+ );
247
306
  addServerTemplate({
248
307
  filename: "#internal/storefront-options.mjs",
249
308
  getContents: () => `
@@ -326,6 +385,8 @@ const module$1 = defineNuxtModule({
326
385
  const rpcMethodNames = new Set(Object.keys(rpcMethods));
327
386
  const storefrontRpcMethodNames = options.rpcMethodNames ?? [];
328
387
  const customRpcImports = [];
388
+ let rpcHttpMethodManifest = {};
389
+ let rpcHttpMethodsPath = "";
329
390
  nuxt.hook("modules:done", async () => {
330
391
  await nuxt.callHook("storefront:custom-rpc:extend", customRpcImports);
331
392
  customRpcImports.forEach((customImport) => {
@@ -340,7 +401,9 @@ const module$1 = defineNuxtModule({
340
401
  (name) => !storefrontRpcMethodNames.includes(name)
341
402
  );
342
403
  });
343
- const rpcDir = appResolve(options.rpcDir ?? "./rpcMethods");
404
+ const rpcDir = await resolvePath(
405
+ appResolve(options.rpcDir ?? "./rpcMethods")
406
+ );
344
407
  if (existsSync(rpcDir)) {
345
408
  customRpcImports.push({
346
409
  source: rpcDir,
@@ -355,6 +418,23 @@ const module$1 = defineNuxtModule({
355
418
  ),
356
419
  rpcMethodNames
357
420
  );
421
+ rpcHttpMethodManifest = await buildRpcHttpMethodManifest(
422
+ customRpcImports,
423
+ rpcMethods
424
+ );
425
+ const rpcHttpMethodsSource = createRpcHttpMethodsSource(
426
+ rpcHttpMethodManifest
427
+ );
428
+ const rpcHttpMethodsTemplate = addTemplate({
429
+ filename: "rpc-http-methods.mjs",
430
+ write: true,
431
+ getContents: () => rpcHttpMethodsSource
432
+ });
433
+ rpcHttpMethodsPath = rpcHttpMethodsTemplate.dst;
434
+ nuxt.options.alias["#virtual/rpcHttpMethods"] = rpcHttpMethodsPath;
435
+ await updateTemplates({
436
+ filter: (template) => template.filename === "rpc-http-methods.mjs"
437
+ });
358
438
  addTypeTemplate({
359
439
  filename: "types/storefront-custom-rpc.d.ts",
360
440
  getContents: () => createRpcMethodTypeDeclaration(customRpcImports)
@@ -385,7 +465,9 @@ const module$1 = defineNuxtModule({
385
465
  apiBasePath,
386
466
  customRpcImports,
387
467
  coreMethodNames: [...rpcMethodNames],
388
- usedDrivers: getUsedDrivers(nuxt.options.runtimeConfig.storefront)
468
+ usedDrivers: getUsedDrivers(nuxt.options.runtimeConfig.storefront),
469
+ rpcHttpMethodManifest,
470
+ rpcHttpMethodsPath
389
471
  }, nuxt.options.runtimeConfig.storefront);
390
472
  });
391
473
  nuxt.hooks.hook("nitro:init", async (nitro) => {
@@ -451,6 +533,7 @@ const module$1 = defineNuxtModule({
451
533
  nuxt.options.optimization.keyedComposables.push(
452
534
  ...keyedComposables.map((name) => ({
453
535
  name,
536
+ source: "@scayle/storefront-nuxt",
454
537
  argumentLength: 2
455
538
  }))
456
539
  );
@@ -1,8 +1,15 @@
1
1
  /**
2
2
  * Generic event handler for RPC methods.
3
3
  *
4
- * It retrieves the RPC method name from the request path,
5
- * reads the payload from the request body.
4
+ * Retrieves the RPC method name from the request path and extracts the payload:
5
+ * - For `POST` / `PUT` / `DELETE`, the payload is read from the JSON
6
+ * request body as `{ payload: ... }` (compatible with the RPC client).
7
+ * - For `GET`, expects a JSON-serialized payload in a single query field `payload`.
8
+ * If present, the handler parses the string using `JSON.parse(payload)`.
9
+ * If `payload` is missing the RPC receives `undefined`.
10
+ * A non-string `payload` value (e.g. repeated keys producing an array) or invalid
11
+ * JSON results in a `400` error response.
12
+ *
6
13
  * It utilizes hooks provided by Nitro to allow for custom logic before,
7
14
  * after, and in case of errors during RPC calls.
8
15
  *
@@ -10,7 +17,7 @@
10
17
  *
11
18
  * @returns The result of the RPC call or a `Response` object with an error status.
12
19
  */
13
- declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<string | boolean | void | string[] | import("@scayle/storefront-api").Campaign | Record<string, unknown> | import("@scayle/storefront-core").ShopUser | Response | import("@scayle/storefront-api").Product | import("@scayle/storefront-api").Product[] | import("@scayle/storefront-core").FetchProductsCountResponse | import("@scayle/storefront-core").FetchFiltersResponse | import("@scayle/storefront-core").FetchProductsByCategoryResponse | {
20
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<string | boolean | void | string[] | import("@scayle/storefront-api").Campaign | Record<string, unknown> | import("@scayle/storefront-core").ShopUser | import("@scayle/storefront-api").Product | import("@scayle/storefront-api").Product[] | import("@scayle/storefront-core").FetchProductsCountResponse | import("@scayle/storefront-core").FetchFiltersResponse | import("@scayle/storefront-core").FetchProductsByCategoryResponse | {
14
21
  success: boolean;
15
22
  } | {
16
23
  result: boolean;
@@ -41,7 +48,7 @@ declare const _default: import("h3").EventHandler<import("h3").EventHandlerReque
41
48
  type: undefined;
42
49
  } | import("@scayle/storefront-api").ShopConfiguration | {
43
50
  user?: import("@scayle/storefront-core").ShopUser;
44
- } | import("@scayle/storefront-api").Wishlist | import("@scayle/storefront-core").ShopUserAddress[] | {
51
+ } | import("@scayle/storefront-api").Wishlist | Response | import("@scayle/storefront-core").ShopUserAddress[] | {
45
52
  accessToken: string | undefined;
46
53
  checkoutJwt: string;
47
54
  } | import("@scayle/storefront-api").VariantDetail[] | import("@scayle/storefront-api").NavigationAllEndpointResponseData | import("@scayle/storefront-api").NavigationTree | import("@scayle/storefront-api").PromotionsEndpointResponseData | {
@@ -1,9 +1,21 @@
1
- import { defineEventHandler, readBody, createError, getRequestURL } from "h3";
2
- import { purifySensitiveData, HttpStatusCode } from "@scayle/storefront-core";
1
+ import {
2
+ defineEventHandler,
3
+ readBody,
4
+ createError,
5
+ getQuery,
6
+ getRequestURL,
7
+ appendResponseHeader
8
+ } from "h3";
9
+ import {
10
+ HttpStatusCode,
11
+ purifySensitiveData,
12
+ DEFAULT_RPC_HTTP_METHOD
13
+ } from "@scayle/storefront-core";
3
14
  import { trace, SpanStatusCode } from "@opentelemetry/api";
4
15
  import { invokeRpc } from "../handler.js";
5
16
  import { resolveError } from "../error/handler.js";
6
17
  import { useNitroApp } from "nitropack/runtime";
18
+ import { rpcHttpMethods } from "#virtual/rpcHttpMethods";
7
19
  export default defineEventHandler(async (event) => {
8
20
  const $rpcContext = event.context.$rpcContext;
9
21
  if (!$rpcContext) {
@@ -17,18 +29,44 @@ export default defineEventHandler(async (event) => {
17
29
  );
18
30
  const pathComponents = event.path.split("?")[0].split("/");
19
31
  const method = pathComponents[pathComponents.length - 1];
20
- const body = await readBody(event);
32
+ const httpMethod = rpcHttpMethods[method] ?? DEFAULT_RPC_HTTP_METHOD;
33
+ const isGetMethod = httpMethod === "GET";
34
+ let payload;
35
+ if (isGetMethod) {
36
+ const query = getQuery(event);
37
+ const rawPayload = query.payload;
38
+ if (rawPayload !== void 0) {
39
+ if (typeof rawPayload !== "string") {
40
+ throw createError({
41
+ statusCode: 400,
42
+ statusMessage: "Invalid payload: expected a single query parameter"
43
+ });
44
+ }
45
+ try {
46
+ payload = JSON.parse(rawPayload);
47
+ } catch {
48
+ throw createError({
49
+ statusCode: 400,
50
+ statusMessage: "Invalid payload: malformed JSON"
51
+ });
52
+ }
53
+ }
54
+ } else {
55
+ const body = await readBody(event);
56
+ payload = body?.payload;
57
+ }
21
58
  const url = getRequestURL(event, {
22
59
  xForwardedHost: true,
23
60
  xForwardedProto: true
24
61
  });
25
62
  const payloadToBeLogged = JSON.stringify(
26
63
  purifySensitiveData(
27
- typeof body?.payload !== "object" ? { [typeof body?.payload]: body?.payload } : body?.payload
64
+ typeof payload !== "object" || payload === null ? { [typeof payload]: payload } : payload
28
65
  )
29
66
  );
30
67
  $rpcContext.log.space("sfc").debug(`RPC Handler: ${method}, Payload: ${payloadToBeLogged}`);
31
68
  const nitroApp = useNitroApp();
69
+ appendResponseHeader(event, "Vary", "x-shop-id");
32
70
  return await tracer.startActiveSpan(
33
71
  `storefront-nuxt.rpc/${method}`,
34
72
  {
@@ -37,6 +75,8 @@ export default defineEventHandler(async (event) => {
37
75
  "rpc.method": method,
38
76
  "rpc.service": "storefront-nuxt.rpc",
39
77
  "rpc.payload": payloadToBeLogged,
78
+ // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#http-server
79
+ "http.method": httpMethod,
40
80
  "server.address": url.hostname,
41
81
  "server.port": url.port
42
82
  }
@@ -48,9 +88,9 @@ export default defineEventHandler(async (event) => {
48
88
  "storefront:rpc:before",
49
89
  method,
50
90
  $rpcContext,
51
- body?.payload
91
+ payload
52
92
  );
53
- result = await invokeRpc(method, body?.payload, $rpcContext);
93
+ result = await invokeRpc(method, payload, $rpcContext);
54
94
  await nitroApp.hooks.callHook(
55
95
  "storefront:rpc:after",
56
96
  method,
@@ -1,8 +1,9 @@
1
- import { unwrap } from "@scayle/storefront-core";
1
+ import { unwrap, DEFAULT_RPC_HTTP_METHOD } from "@scayle/storefront-core";
2
2
  import { useAsyncData, useRuntimeConfig, useNuxtApp } from "nuxt/app";
3
3
  import { useCoreLog } from "../useCoreLog.js";
4
4
  import { useCurrentShop } from "./useCurrentShop.js";
5
5
  import { toValue, isRef } from "vue";
6
+ import { rpcHttpMethods as rpcHttpMethodManifest } from "#virtual/rpcHttpMethods";
6
7
  function getFetch(nuxtApp, log) {
7
8
  const fetch = nuxtApp.ssrContext?.event.context.$fetchWithContext ?? $fetch;
8
9
  if (nuxtApp.ssrContext?.event && !nuxtApp.ssrContext?.event.context.$fetchWithContext) {
@@ -28,18 +29,30 @@ export function useRpc(method, key, params, options) {
28
29
  async () => {
29
30
  const apiBasePath = currentShop.value?.apiBasePath ?? "/api";
30
31
  const fetch = getFetch(nuxtApp, log);
31
- return await unwrap(
32
- await fetch(`/rpc/${method}`, {
33
- // @ts-expect-error Type '"POST"' is not assignable to type 'AvailableRouterMethod<`/rpc/${N}`> | Uppercase<AvailableRouterMethod<`/rpc/${N}`>> | undefined'.ts(2322)
34
- method: "POST",
35
- body: {
36
- payload: toValue(params)
37
- },
32
+ const httpMethod = rpcHttpMethodManifest[method] ?? DEFAULT_RPC_HTTP_METHOD;
33
+ const resolvedParams = toValue(params);
34
+ const headers = {
35
+ "x-shop-id": `${currentShop.value?.shopId}`
36
+ };
37
+ const path = `/rpc/${method}`;
38
+ let fetchOptions;
39
+ if (httpMethod === "GET") {
40
+ fetchOptions = {
41
+ method: httpMethod,
42
+ baseURL: apiBasePath,
43
+ headers,
44
+ params: resolvedParams !== void 0 && resolvedParams !== null ? { payload: JSON.stringify(resolvedParams) } : void 0
45
+ };
46
+ } else {
47
+ fetchOptions = {
48
+ method: httpMethod,
49
+ body: { payload: resolvedParams },
38
50
  baseURL: apiBasePath,
39
- headers: {
40
- "x-shop-id": `${currentShop.value?.shopId}`
41
- }
42
- })
51
+ headers
52
+ };
53
+ }
54
+ return await unwrap(
55
+ await fetch(path, fetchOptions)
43
56
  );
44
57
  },
45
58
  {
@@ -4,13 +4,15 @@ import {
4
4
  StorefrontAPIClient,
5
5
  unwrap,
6
6
  generateBasketKey,
7
- generateWishlistKey
7
+ generateWishlistKey,
8
+ DEFAULT_RPC_HTTP_METHOD
8
9
  } from "@scayle/storefront-core";
9
10
  import { useNitroApp } from "nitropack/runtime";
10
11
  import { getCachedFunction } from "./cached.js";
11
12
  import { getApiBasePath } from "./server/middleware/bootstrap-utils.js";
12
13
  import { fetchCampaignKey } from "./campaignKey.js";
13
14
  import * as moduleOptions from "#internal/storefront-options.mjs";
15
+ import { rpcHttpMethods } from "#virtual/rpcHttpMethods";
14
16
  async function getWishlistKey(appKeys, session, $log, $shopConfig) {
15
17
  return session.data.user ? await generateWishlistKey({
16
18
  keyTemplate: appKeys.wishlistKey,
@@ -32,11 +34,17 @@ async function getBasketKey(appKeys, session, $log, $shopConfig) {
32
34
  function buildRpcCall(event, shopConfig, apiBasePath) {
33
35
  const fetch = event.context.$fetchWithContext;
34
36
  return async function rpcCall(method, params = void 0) {
37
+ const httpMethod = rpcHttpMethods[method] ?? DEFAULT_RPC_HTTP_METHOD;
38
+ const isGet = httpMethod === "GET";
35
39
  const data = await fetch(`${apiBasePath}/rpc/${method}`, {
36
- // @ts-expect-error Type '"POST"' is not assignable to type 'AvailableRouterMethod<`${string}/rpc/${N}`> | Uppercase<AvailableRouterMethod<`${string}/rpc/${N}`>> | undefined'.ts(2322)
37
- method: "POST",
38
- body: {
39
- payload: params
40
+ // @ts-expect-error Type 'RpcHttpMethod' is not assignable to type 'AvailableRouterMethod<...>'
41
+ method: httpMethod,
42
+ ...isGet ? {
43
+ query: params !== void 0 ? { payload: JSON.stringify(params) } : void 0
44
+ } : {
45
+ body: {
46
+ payload: params
47
+ }
40
48
  },
41
49
  headers: {
42
50
  "x-shop-id": shopConfig.shopId.toString()
@@ -48,7 +48,10 @@ export default defineNitroPlugin(async () => {
48
48
  }
49
49
  };
50
50
  for (const shop of Object.values(shops)) {
51
- await mountShopStorage(shop.shopId, `${STORAGE_MOUNT_CACHE}:${shop.shopId}`);
51
+ await mountShopStorage(
52
+ shop.shopId,
53
+ `${STORAGE_MOUNT_CACHE}:${shop.shopId}`
54
+ );
52
55
  await mountShopStorage(
53
56
  shop.shopId,
54
57
  `${STORAGE_MOUNT_SESSION}:${shop.shopId}`
@@ -5,6 +5,9 @@ import type { PublicShopConfig } from '../types/module.js';
5
5
  * Invokes an RPC method on the client or server.
6
6
  * Uses an HTTP request on the client and a direct call on the server via `$fetch`.
7
7
  *
8
+ * The HTTP verb is read from the build-time manifest `#virtual/rpcHttpMethods`,
9
+ * from each `defineRpcHandler`; `POST` when not listed.
10
+ *
8
11
  * @see https://nuxt.com/docs/api/utils/dollarfetch
9
12
  *
10
13
  * @template N The RPC method name.
@@ -1,17 +1,41 @@
1
- import { unwrap } from "@scayle/storefront-core";
1
+ import { unwrap, DEFAULT_RPC_HTTP_METHOD } from "@scayle/storefront-core";
2
+ import { rpcHttpMethods } from "#virtual/rpcHttpMethods";
3
+ const buildRequest = (method, params, apiBasePath, shopId, rpcHttpMethods2) => {
4
+ const httpMethod = rpcHttpMethods2[method] ?? DEFAULT_RPC_HTTP_METHOD;
5
+ const headers = {
6
+ "x-shop-id": shopId.toString()
7
+ };
8
+ if (httpMethod === "GET") {
9
+ return {
10
+ path: `${apiBasePath}/rpc/${method}`,
11
+ options: {
12
+ method: httpMethod,
13
+ headers,
14
+ params: params !== void 0 && params !== null ? { payload: JSON.stringify(params) } : void 0
15
+ }
16
+ };
17
+ }
18
+ return {
19
+ path: `${apiBasePath}/rpc/${method}`,
20
+ options: {
21
+ method: httpMethod,
22
+ body: { payload: params },
23
+ headers
24
+ }
25
+ };
26
+ };
2
27
  export const rpcCall = (nuxtApp, method, shop) => {
3
28
  const fetch = nuxtApp.ssrContext?.event.context.$fetchWithContext ?? $fetch;
4
29
  return async (params = void 0) => {
5
- const data = await fetch(`${shop.apiBasePath ?? "/api"}/rpc/${method}`, {
6
- // @ts-expect-error Type '"POST"' is not assignable to type 'AvailableRouterMethod<`${string}/rpc/${N}`> | Uppercase<AvailableRouterMethod<`${string}/rpc/${N}`>> | undefined'.ts(2322)
7
- method: "POST",
8
- body: {
9
- payload: params
10
- },
11
- headers: {
12
- "x-shop-id": shop.shopId.toString()
13
- }
14
- });
30
+ const apiBasePath = shop.apiBasePath ?? "/api";
31
+ const { path, options } = buildRequest(
32
+ method,
33
+ params,
34
+ apiBasePath,
35
+ shop.shopId,
36
+ rpcHttpMethods
37
+ );
38
+ const data = await fetch(path, options);
15
39
  return await unwrap(data);
16
40
  };
17
41
  };
@@ -1,10 +1,11 @@
1
1
  import type { RpcMethods, RpcMethodName, RpcHandler } from '@scayle/storefront-core';
2
2
  /**
3
- * Normalizes a rpc handler function that are not defined with `defineRpcHandler`.
4
- * Normalization sets the `rpcType` property to `WithParam` or `NoParam` based on the number of parameters the handler function takes.
5
- * Handlers that already have this property set are returned without modification.
3
+ * Normalizes a rpc handler function that is not defined with `defineRpcHandler`.
4
+ * Sets the `rpcType` property to `WithParam` or `NoParam` based on the number of parameters
5
+ * the handler function takes, and defaults `httpMethod` to `'POST'` when it is missing.
6
+ * Handlers that already have both properties set are returned without modification.
6
7
  *
7
8
  * @param handler - The handler function to normalize.
8
- * @returns The normalized handler function with an `rpcType` property.
9
+ * @returns The normalized handler function with `rpcType` and `httpMethod` properties.
9
10
  */
10
- export declare const normalizeRpcHandler: <T extends RpcMethods[RpcMethodName], NormalizedHandler = T extends RpcHandler<infer R> ? RpcHandler<R> & Required<Pick<RpcHandler<R>, "rpcType">> : T extends RpcHandler<infer _P, infer R> ? RpcHandler<unknown, R> & Required<Pick<RpcHandler<unknown, R>, "rpcType">> : never>(handler: T) => NormalizedHandler;
11
+ export declare const normalizeRpcHandler: <T extends RpcMethods[RpcMethodName], NormalizedHandler = T extends RpcHandler<infer R> ? RpcHandler<R> & Required<Pick<RpcHandler<R>, "rpcType" | "httpMethod">> : T extends RpcHandler<infer _P, infer R> ? RpcHandler<unknown, R> & Required<Pick<RpcHandler<unknown, R>, "rpcType" | "httpMethod">> : never>(handler: T) => NormalizedHandler;
@@ -1,7 +1,17 @@
1
- import { defineRpcHandler } from "@scayle/storefront-core";
1
+ import {
2
+ defineRpcHandler,
3
+ DEFAULT_RPC_HTTP_METHOD
4
+ } from "@scayle/storefront-core";
2
5
  export const normalizeRpcHandler = (handler) => {
3
- if (handler && !("rpcType" in handler)) {
4
- return defineRpcHandler(handler);
6
+ if (!handler) {
7
+ return handler;
5
8
  }
6
- return handler;
9
+ let normalized = handler;
10
+ if (!("rpcType" in handler)) {
11
+ normalized = defineRpcHandler(handler);
12
+ }
13
+ if (!("httpMethod" in normalized) || !normalized.httpMethod) {
14
+ Object.assign(normalized, { httpMethod: DEFAULT_RPC_HTTP_METHOD });
15
+ }
16
+ return normalized;
7
17
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@scayle/storefront-nuxt",
3
3
  "type": "module",
4
- "version": "8.60.1",
4
+ "version": "8.61.0",
5
5
  "description": "Nuxt integration for the SCAYLE Commerce Engine and Storefront API",
6
6
  "author": "SCAYLE Commerce Engine",
7
7
  "license": "MIT",
@@ -48,7 +48,11 @@
48
48
  }
49
49
  },
50
50
  "imports": {
51
- "#storefront/composables": "./dist/runtime/composables/index.js"
51
+ "#storefront/composables": "./dist/runtime/composables/index.js",
52
+ "#virtual/rpcHttpMethods": {
53
+ "types": "./src/stub/rpc-http-methods.d.mts",
54
+ "default": "./src/stub/rpc-http-methods.mjs"
55
+ }
52
56
  },
53
57
  "main": "./dist/index.mjs",
54
58
  "files": [
@@ -56,7 +60,8 @@
56
60
  "CHANGELOG-V7.md",
57
61
  "LICENSE",
58
62
  "README.md",
59
- "dist"
63
+ "dist",
64
+ "src/stub"
60
65
  ],
61
66
  "engines": {
62
67
  "node": ">= 22.0.0"
@@ -72,7 +77,8 @@
72
77
  "vue": "^3.4.0"
73
78
  },
74
79
  "dependencies": {
75
- "@opentelemetry/api": "1.9.0",
80
+ "@opentelemetry/api": "^1.9.0",
81
+ "@scayle/unstorage-compression-driver": "1.4.0",
76
82
  "@vercel/nft": "1.4.0",
77
83
  "@vueuse/core": "14.2.1",
78
84
  "consola": "^3.4.2",
@@ -90,8 +96,7 @@
90
96
  "vue-router": "^4.4.0",
91
97
  "zod": "^4.0.0",
92
98
  "@scayle/h3-session": "0.7.0",
93
- "@scayle/storefront-core": "8.60.1",
94
- "@scayle/unstorage-compression-driver": "1.4.0"
99
+ "@scayle/storefront-core": "8.61.0"
95
100
  },
96
101
  "devDependencies": {
97
102
  "@arethetypeswrong/cli": "0.18.2",
@@ -101,14 +106,16 @@
101
106
  "@nuxt/module-builder": "1.0.2",
102
107
  "@nuxt/schema": "^3.20.2",
103
108
  "@nuxt/test-utils": "4.0.0",
109
+ "@scayle/eslint-config-storefront": "^4.8.0",
110
+ "@scayle/eslint-plugin-vue-composable": "^1.1.0",
111
+ "@scayle/unstorage-scayle-kv-driver": "^2.1.0",
104
112
  "@types/node": "22.19.17",
105
- "@vitest/coverage-v8": "4.1.2",
106
- "dprint": "0.53.2",
107
- "eslint": "10.2.0",
108
- "eslint-formatter-gitlab": "7.0.1",
113
+ "@vitest/coverage-v8": "4.1.5",
114
+ "eslint": "10.2.1",
115
+ "eslint-formatter-gitlab": "7.1.0",
109
116
  "fishery": "2.4.0",
110
- "h3": "1.15.10",
111
- "happy-dom": "20.8.9",
117
+ "h3": "1.15.11",
118
+ "happy-dom": "20.9.0",
112
119
  "nitro-test-utils": "0.11.2",
113
120
  "nitropack": "2.13.3",
114
121
  "node-mocks-http": "1.17.2",
@@ -116,11 +123,8 @@
116
123
  "publint": "0.3.18",
117
124
  "typescript": "5.9.3",
118
125
  "unbuild": "3.6.1",
119
- "vitest": "4.1.2",
126
+ "vitest": "4.1.5",
120
127
  "vue-tsc": "3.2.6",
121
- "@scayle/eslint-plugin-vue-composable": "1.1.0",
122
- "@scayle/eslint-config-storefront": "4.8.0",
123
- "@scayle/unstorage-scayle-kv-driver": "2.1.1",
124
128
  "@scayle/vitest-config-storefront": "1.0.0"
125
129
  },
126
130
  "scripts": {
@@ -128,16 +132,11 @@
128
132
  "dev": "nuxt dev playground",
129
133
  "dev:build": "nuxt build playground",
130
134
  "prep": "nuxt-module-build prepare && nuxt prepare playground",
131
- "format": "dprint check",
132
- "format:fix": "dprint fmt",
133
135
  "lint": "eslint .",
134
136
  "lint:ci": "eslint . --format gitlab",
135
137
  "lint:fix": "eslint . --fix",
136
138
  "typecheck": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit",
137
139
  "package:lint": "publint",
138
- "verify-packaging": "attw --pack . --profile esm-only",
139
- "test": "vitest run",
140
- "test:ci": "vitest --run --coverage --reporter=default --reporter=junit --outputFile=./coverage/junit.xml",
141
- "test:watch": "vitest watch"
140
+ "verify-packaging": "attw --pack . --profile esm-only"
142
141
  }
143
142
  }
@@ -0,0 +1,11 @@
1
+ import type { RpcHttpMethod } from '@scayle/storefront-core'
2
+
3
+ /**
4
+ * Type surface for `#virtual/rpcHttpMethods`.
5
+ *
6
+ * At build time the consuming Nuxt app replaces this with a generated manifest.
7
+ * The fallback implementation lives in {@link ./rpc-http-methods.mjs}.
8
+ */
9
+ declare module '#virtual/rpcHttpMethods' {
10
+ export const rpcHttpMethods: Record<string, RpcHttpMethod>
11
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Node / jiti fallback for `#virtual/rpcHttpMethods`.
3
+ *
4
+ * The real manifest is generated by the Nuxt module at app build time. Vite and
5
+ * Nitro aliases override this import during a normal Nuxt build. This file exists
6
+ * so `package.json#imports` can satisfy Node's ESM resolver when config-time code
7
+ * loads compiled runtime entry points before Nuxt has installed its aliases
8
+ * (for example when `jiti` loads the package during Nuxt bootstrap).
9
+ *
10
+ * An empty object is safe: callers treat missing keys as `POST`.
11
+ */
12
+ export const rpcHttpMethods = {}