Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(ust-1435): sdk methods custom config, docs #7184

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
5 changes: 5 additions & 0 deletions .changeset/grumpy-shirts-yell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@vue-storefront/nuxt": minor
---

Handling buildId for better CND caching.
5 changes: 5 additions & 0 deletions .changeset/orange-lizards-peel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@vue-storefront/next": minor
---

Handling buildId for better middleware reuests caching.
5 changes: 5 additions & 0 deletions .changeset/plenty-queens-glow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@vue-storefront/sdk": minor
---

Extending SDK of custom method config
50 changes: 50 additions & 0 deletions docs/content/4.sdk/2.getting-started/2.middleware-module.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ The `middlewareModule` accepts the following options:
- `apiUrl` - the URL of the middleware server,
- `ssrApiUrl` - (Optional) the URL of the middleware server during SSR,
- `defaultRequestConfig` - (Optional) default request config for each request,
- `methodsRequestConfig` - (Optional) custom request config for each request,
- `httpClient` - (Optional) a custom HTTP client,
- `errorHandler` - (Optional) a custom error handler for HTTP requests.

Expand Down Expand Up @@ -156,6 +157,55 @@ export const sdk = initSDK({

Once you have added the `middlewareModule` to your SDK, you can use it to make requests to the Alokai Middleware.

### Defining Methods Custom Config

Under the `methodsRequestConfig` you can register your own configuration for any dedicated method.
Existing or custom one. Specifically useful for defining custom headers, e.g. for caching, like `Cache-Control`, or method type.

Provided config should be of type:

```typescript
type RequestConfig = {
method?: "GET" | "POST";
headers?: Record<string, string | string[]>;
};

type MethodsRequestConfig = Record<string, RequestConfig>;
```

#### Config Definition

```typescript
import type {
ExtendedConfigEndpoints,
MethodsConfig,
} from "@vue-storefront/sdk";
import type { UnifiedEndpoints } from '[backend]/types';

type CustomMethods = 'getUniqueData';

const customMethodsRequestConfig = {
getUniqueData: {
method: "GET",
headers: {
"Cache-Control": "custom-cache-rules",
},
},
} satisfies MethodsConfig<CommerceEndpoints, CustomMethods>;

export const sdk = initSDK({
commerce: buildModule(
middlewareModule<ExtendedConfigEndpoints<CommerceEndpoints>>,
{
// ... other config options
methodsRequestConfig: {
...customMethodsRequestConfig,
},
}
),
});
```

## Usage

Once you have added the `middlewareModule` to your SDK, you can use it to make requests to the Server Middleware.
Expand Down
5 changes: 5 additions & 0 deletions docs/nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
extends: ["sf-docs-base"],
vite: {
define: {
__NUXT_ASYNC_CONTEXT__: false,
},
},
});
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"scripts": {
"prepare": "husky install",
"build": "lerna run build",
"build:middleware": "cd packages/middleware && yarn build",
"test": "lerna run test",
"test:unit": "lerna run test:unit",
"test:integration": "lerna run test:integration",
Expand Down
11 changes: 9 additions & 2 deletions packages/next/src/sdk/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,26 @@ export function createSdk<TConfig extends Record<string, any>>(
options: CreateSdkOptions,
configDefinition: Config<TConfig>
): CreateSdkReturn<TConfig> {
function getSdk(dynamicContext: GetSdkContext = { }) {
function getSdk(dynamicContext: GetSdkContext = {}) {
const { getRequestHeaders } = resolveDynamicContext(dynamicContext);
const middlewareUrl = composeMiddlewareUrl({
options,
headers: getRequestHeaders(),
});
if (dynamicContext.buildId && typeof dynamicContext.buildId === "string") {
middlewareModule({
buildId: dynamicContext.buildId,
apiUrl: "",
cdnCacheBustingId: "",
});
}

const resolvedConfig = configDefinition({
defaults: contextConfig,
buildModule,
middlewareModule,
getRequestHeaders,
middlewareUrl
middlewareUrl,
});

return initSDK(resolvedConfig);
Expand Down
2 changes: 2 additions & 0 deletions packages/next/src/sdk/types.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { SDKApi, buildModule, middlewareModule } from "@vue-storefront/sdk";
import { ReactNode } from "react";
import type { contextConfig } from "@storefront/shared";

export type GetSdkContext = {
/**
* A function that returns the request headers.
*/
getRequestHeaders?: () =>
| Record<string, string | string[] | undefined>
| Headers;
buildId?: string;
};

export type DynamicContext = {
Expand Down
3 changes: 3 additions & 0 deletions packages/nuxt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,8 @@
"nuxt": "3.7.4",
"start-server-and-test": "^2.0.3",
"vitest": "^0.34.6"
},
"resolutions": {
"semver": "7.3.2"
}
}
11 changes: 9 additions & 2 deletions packages/nuxt/src/runtime/defineSdkConfig.template
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const moduleConfig: SdkModuleOptions = <%= options.moduleConfig %>;
* ContentfulModuleType,
* } from "@vsf-enterprise/contentful-sdk";
* import type { UnifiedApiEndpoints } from "../storefront-middleware/types";
*
*
* export default defineSdkConfig(
* ({ buildModule, middlewareModule, middlewareUrl, getCookieHeader }) => ({
* unified: buildModule(middlewareModule<UnifiedApiEndpoints>, {
Expand All @@ -44,7 +44,7 @@ const moduleConfig: SdkModuleOptions = <%= options.moduleConfig %>;
* );
* ```
*/
export function defineSdkConfig<TConfig>(config: Config<TConfig>) {
export function defineSdkConfig<TConfig>(config: Config<TConfig>, dynamicContext?: { buildId?: string }) {
return () => {
const nuxtApp = useNuxtApp()
const runtimeConfig = useRuntimeConfig();
Expand All @@ -59,6 +59,13 @@ export function defineSdkConfig<TConfig>(config: Config<TConfig>) {
options: resolvedOptions,
headers: requestHeaders,
});
if (dynamicContext.buildId && typeof dynamicContext.buildId === "string") {
middlewareModule({
buildId: dynamicContext.buildId,
apiUrl: '',
cdnCacheBustingId: '',
});
}

const getCookieHeader = () => useRequestHeaders(["cookie"]);
const getRequestHeaders = () => useRequestHeaders();
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/src/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const initSDK = <T extends SDKConfig>(sdkConfig: T): SDKApi<T> => {
their property descriptors. The problem is that non-writeable and non-configurable properties cannot be altered by Proxy.
See step 10 of the below algorithm
https://262.ecma-international.org/8.0/#sec-proxy-object-internal-methods-and-internal-slots-get-p-receiver

Due to this, we recreate connector but without property descriptors.
*/

Expand Down
7 changes: 6 additions & 1 deletion packages/sdk/src/modules/middlewareModule/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,15 @@ import { getRequestSender } from "./utils";
* }));
* ```
*/
let storedBuildId: string | undefined;
export const middlewareModule = <Endpoints extends EndpointsConstraint>(
options: Options<Endpoints>
) => {
const requestSender = getRequestSender(options);
storedBuildId = options.buildId ?? storedBuildId;
const requestSender = getRequestSender({
...options,
buildId: storedBuildId,
});

return {
connector: connector<Endpoints>(requestSender),
Expand Down
37 changes: 37 additions & 0 deletions packages/sdk/src/modules/middlewareModule/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,11 @@ export type Options<
* Unique identifier for CDN cache busting.
*/
cdnCacheBustingId: string;

/**
* Dedicated property to control request caching.
*/
buildId?: string;
};

/**
Expand All @@ -280,3 +285,35 @@ export type Methods<Endpoints extends EndpointsConstraint> = {
...params: [...Parameters<Endpoints[Key]>, config?: MethodConfig]
) => ReturnType<Endpoints[Key]>;
};

/**
* Represents an extension of the `EndpointsConstraint` type.
* It allows for additional methods to be added to the endpoints, with their names specified by the `MethodNames` type parameter.
* The methods can be of any function type, as indicated by the `AnyFunction` type.
*
* @template Endpoints - A type that extends `EndpointsConstraint`, representing the existing endpoints.
* @template MethodNames - A string type representing the names of additional methods to be added to the endpoints. Defaults to `string`.
*
* @property {[K in keyof Endpoints | MethodNames]: AnyFunction} - A mapped type, where each key is either a key of `Endpoints` or a `MethodNames` value, and each value is a function of any type.
*/
export type ExtendedConfigEndpoints<
Endpoints extends EndpointsConstraint,
MethodNames extends string = string
> = {
[K in keyof Endpoints | MethodNames]: AnyFunction;
};

/**
* Represents the configuration for methods in the SDK.
* It is derived from the `Options` type, specifically the `methodsRequestConfig` property.
* The `Endpoints` type parameter represents the existing endpoints, while the `MethodNames` type parameter represents additional methods.
*
* @template Endpoints - A type that extends `EndpointsConstraint`, representing the existing endpoints.
* @template MethodNames - A string type representing the names of additional methods to be added to the endpoints. Defaults to `string`.
*/
export type MethodsConfig<
Endpoints extends EndpointsConstraint,
MethodNames extends string = string
> = Options<
ExtendedConfigEndpoints<Endpoints, MethodNames>
>["methodsRequestConfig"];
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const getRequestSender = (options: Options): RequestSender => {
defaultRequestConfig = {},
methodsRequestConfig = {},
cdnCacheBustingId,
buildId,
} = options;

const getUrl = (
Expand Down Expand Up @@ -116,7 +117,11 @@ export const getRequestSender = (options: Options): RequestSender => {
const methodConfig = methodsRequestConfig[methodName] || {};
const finalMethod =
method || methodConfig.method || defaultRequestConfig.method || "POST";
const finalUrl = getUrl(methodName, finalMethod, params);
const finalUrl = getUrl(
methodName,
finalMethod,
buildId ? [buildId, ...params] : params
);
const finalParams = finalMethod === "GET" ? [] : params;
const finalConfig = getConfig(
{ method: finalMethod, headers, ...restConfig },
Expand Down
Loading
Loading