feat(cms-client): add dual-mode Payload client (local + HTTP)

This commit is contained in:
2026-04-06 14:41:05 +02:00
parent 1bd578993b
commit 4bb30f6546
8 changed files with 259 additions and 7 deletions

View File

@@ -10,8 +10,12 @@
"lint": "eslint .", "lint": "eslint .",
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": {
"payload": "^3.14.0"
},
"devDependencies": { "devDependencies": {
"@repo/eslint-config": "workspace:*", "@repo/eslint-config": "workspace:*",
"@repo/typescript-config": "workspace:*" "@repo/typescript-config": "workspace:*",
"@types/node": "^22.0.0"
} }
} }

View File

@@ -0,0 +1,17 @@
import type { Payload } from "payload";
import type { PayloadClient } from "./types.js";
import { LocalPayloadClient } from "./local-client.js";
import { HTTPPayloadClient } from "./http-client.js";
type PayloadClientOptions =
| { mode: "local"; payload: Payload }
| { mode: "http"; baseURL: string };
export function createPayloadClient(
options: PayloadClientOptions
): PayloadClient {
if (options.mode === "local") {
return new LocalPayloadClient(options.payload);
}
return new HTTPPayloadClient(options.baseURL);
}

View File

@@ -0,0 +1,87 @@
import type {
FindOptions,
PayloadClient,
PayloadClientResult,
} from "./types.js";
export class HTTPPayloadClient implements PayloadClient {
constructor(private baseURL: string) {}
private async request<T>(path: string, options?: RequestInit): Promise<T> {
const response = await fetch(`${this.baseURL}${path}`, {
headers: { "Content-Type": "application/json" },
...options,
});
if (!response.ok) {
throw new Error(
`Payload API error: ${response.status} ${response.statusText}`
);
}
return response.json() as Promise<T>;
}
async find<T = Record<string, unknown>>(
collection: string,
options?: FindOptions
): Promise<PayloadClientResult<T>> {
const params = new URLSearchParams();
if (options?.limit) params.set("limit", String(options.limit));
if (options?.page) params.set("page", String(options.page));
if (options?.sort) params.set("sort", options.sort);
if (options?.depth) params.set("depth", String(options.depth));
if (options?.where) params.set("where", JSON.stringify(options.where));
const query = params.toString();
return this.request<PayloadClientResult<T>>(
`/api/${collection}${query ? `?${query}` : ""}`
);
}
async findByID<T = Record<string, unknown>>(
collection: string,
id: string,
options?: { depth?: number }
): Promise<T> {
const params = new URLSearchParams();
if (options?.depth) params.set("depth", String(options.depth));
const query = params.toString();
return this.request<T>(
`/api/${collection}/${id}${query ? `?${query}` : ""}`
);
}
async create<T = Record<string, unknown>>(
collection: string,
data: Record<string, unknown>,
options?: { depth?: number }
): Promise<T> {
const params = new URLSearchParams();
if (options?.depth) params.set("depth", String(options.depth));
const query = params.toString();
return this.request<T>(
`/api/${collection}${query ? `?${query}` : ""}`,
{ method: "POST", body: JSON.stringify(data) }
);
}
async update<T = Record<string, unknown>>(
collection: string,
id: string,
data: Record<string, unknown>,
options?: { depth?: number }
): Promise<T> {
const params = new URLSearchParams();
if (options?.depth) params.set("depth", String(options.depth));
const query = params.toString();
return this.request<T>(
`/api/${collection}/${id}${query ? `?${query}` : ""}`,
{ method: "PATCH", body: JSON.stringify(data) }
);
}
async delete<T = Record<string, unknown>>(
collection: string,
id: string
): Promise<T> {
return this.request<T>(`/api/${collection}/${id}`, { method: "DELETE" });
}
}

View File

@@ -1,2 +1,8 @@
// @repo/cms-client — Dual-mode Payload client (local + HTTP) export { createPayloadClient } from "./client.js";
export {}; export { LocalPayloadClient } from "./local-client.js";
export { HTTPPayloadClient } from "./http-client.js";
export type {
PayloadClient,
PayloadClientResult,
FindOptions,
} from "./types.js";

View File

@@ -0,0 +1,78 @@
import type { Payload } from "payload";
import type {
FindOptions,
PayloadClient,
PayloadClientResult,
} from "./types.js";
export class LocalPayloadClient implements PayloadClient {
constructor(private payload: Payload) {}
async find<T = Record<string, unknown>>(
collection: string,
options?: FindOptions
): Promise<PayloadClientResult<T>> {
const result = await this.payload.find({
collection: collection as any,
where: options?.where as any,
sort: options?.sort,
limit: options?.limit,
page: options?.page,
depth: options?.depth,
locale: options?.locale as any,
});
return result as unknown as PayloadClientResult<T>;
}
async findByID<T = Record<string, unknown>>(
collection: string,
id: string,
options?: { depth?: number }
): Promise<T> {
const result = await this.payload.findByID({
collection: collection as any,
id,
depth: options?.depth,
});
return result as unknown as T;
}
async create<T = Record<string, unknown>>(
collection: string,
data: Record<string, unknown>,
options?: { depth?: number }
): Promise<T> {
const result = await this.payload.create({
collection: collection as any,
data: data as any,
depth: options?.depth,
});
return result as unknown as T;
}
async update<T = Record<string, unknown>>(
collection: string,
id: string,
data: Record<string, unknown>,
options?: { depth?: number }
): Promise<T> {
const result = await this.payload.update({
collection: collection as any,
id,
data: data as any,
depth: options?.depth,
});
return result as unknown as T;
}
async delete<T = Record<string, unknown>>(
collection: string,
id: string
): Promise<T> {
const result = await this.payload.delete({
collection: collection as any,
id,
});
return result as unknown as T;
}
}

View File

@@ -0,0 +1,52 @@
export interface FindOptions {
where?: Record<string, unknown>;
sort?: string;
limit?: number;
page?: number;
depth?: number;
locale?: string;
}
export interface PayloadClientResult<T> {
docs: T[];
totalDocs: number;
limit: number;
totalPages: number;
page: number;
pagingCounter: number;
hasPrevPage: boolean;
hasNextPage: boolean;
prevPage: number | null;
nextPage: number | null;
}
export interface PayloadClient {
find<T = Record<string, unknown>>(
collection: string,
options?: FindOptions
): Promise<PayloadClientResult<T>>;
findByID<T = Record<string, unknown>>(
collection: string,
id: string,
options?: { depth?: number }
): Promise<T>;
create<T = Record<string, unknown>>(
collection: string,
data: Record<string, unknown>,
options?: { depth?: number }
): Promise<T>;
update<T = Record<string, unknown>>(
collection: string,
id: string,
data: Record<string, unknown>,
options?: { depth?: number }
): Promise<T>;
delete<T = Record<string, unknown>>(
collection: string,
id: string
): Promise<T>;
}

View File

@@ -1,6 +1,7 @@
{ {
"extends": "@repo/typescript-config/base.json", "extends": "@repo/typescript-config/base.json",
"compilerOptions": { "compilerOptions": {
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"@/*": ["./src/*"] "@/*": ["./src/*"]

15
pnpm-lock.yaml generated
View File

@@ -95,6 +95,10 @@ importers:
version: link:../typescript-config version: link:../typescript-config
packages/cms-client: packages/cms-client:
dependencies:
payload:
specifier: ^3.14.0
version: 3.81.0(graphql@16.13.2)(typescript@5.9.3)
devDependencies: devDependencies:
'@repo/eslint-config': '@repo/eslint-config':
specifier: workspace:* specifier: workspace:*
@@ -102,6 +106,9 @@ importers:
'@repo/typescript-config': '@repo/typescript-config':
specifier: workspace:* specifier: workspace:*
version: link:../typescript-config version: link:../typescript-config
'@types/node':
specifier: ^22.0.0
version: 22.19.17
packages/cms-core: packages/cms-core:
dependencies: dependencies:
@@ -4310,7 +4317,7 @@ snapshots:
'@types/busboy@1.5.4': '@types/busboy@1.5.4':
dependencies: dependencies:
'@types/node': 22.19.17 '@types/node': 25.5.2
'@types/chai@5.2.3': '@types/chai@5.2.3':
dependencies: dependencies:
@@ -4355,7 +4362,7 @@ snapshots:
'@types/pg@8.10.2': '@types/pg@8.10.2':
dependencies: dependencies:
'@types/node': 22.19.17 '@types/node': 25.5.2
pg-protocol: 1.13.0 pg-protocol: 1.13.0
pg-types: 4.1.0 pg-types: 4.1.0
@@ -4380,7 +4387,7 @@ snapshots:
'@types/ws@8.18.1': '@types/ws@8.18.1':
dependencies: dependencies:
'@types/node': 22.19.17 '@types/node': 25.5.2
'@typescript-eslint/eslint-plugin@8.58.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)': '@typescript-eslint/eslint-plugin@8.58.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)':
dependencies: dependencies:
@@ -5024,7 +5031,7 @@ snapshots:
happy-dom@20.8.9: happy-dom@20.8.9:
dependencies: dependencies:
'@types/node': 22.19.17 '@types/node': 25.5.2
'@types/whatwg-mimetype': 3.0.2 '@types/whatwg-mimetype': 3.0.2
'@types/ws': 8.18.1 '@types/ws': 8.18.1
entities: 7.0.1 entities: 7.0.1