@unchainedshop/cockpit-api 1.0.34 → 2.1.1

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.
Files changed (72) hide show
  1. package/README.md +330 -65
  2. package/dist/client.d.ts +72 -0
  3. package/dist/client.js +101 -0
  4. package/dist/cockpit-logger.d.ts +8 -5
  5. package/dist/cockpit-logger.js +7 -20
  6. package/dist/core/cache.d.ts +19 -0
  7. package/dist/core/cache.js +32 -0
  8. package/dist/core/config.d.ts +44 -0
  9. package/dist/core/config.js +32 -0
  10. package/dist/core/http.d.ts +34 -0
  11. package/dist/core/http.js +98 -0
  12. package/dist/core/index.d.ts +14 -0
  13. package/dist/core/index.js +10 -0
  14. package/dist/core/locale.d.ts +11 -0
  15. package/dist/core/locale.js +19 -0
  16. package/dist/core/query-string.d.ts +18 -0
  17. package/dist/core/query-string.js +20 -0
  18. package/dist/core/url-builder.d.ts +22 -0
  19. package/dist/core/url-builder.js +35 -0
  20. package/dist/core/validation.d.ts +13 -0
  21. package/dist/core/validation.js +22 -0
  22. package/dist/fetch/client.d.ts +85 -0
  23. package/dist/fetch/client.js +143 -0
  24. package/dist/fetch/index.d.ts +19 -0
  25. package/dist/fetch/index.js +18 -0
  26. package/dist/index.d.ts +28 -2
  27. package/dist/index.js +13 -2
  28. package/dist/methods/assets.d.ts +65 -0
  29. package/dist/methods/assets.js +37 -0
  30. package/dist/methods/content.d.ts +77 -0
  31. package/dist/methods/content.js +79 -0
  32. package/dist/methods/graphql.d.ts +9 -0
  33. package/dist/methods/graphql.js +13 -0
  34. package/dist/methods/index.d.ts +22 -0
  35. package/dist/methods/index.js +12 -0
  36. package/dist/methods/localize.d.ts +12 -0
  37. package/dist/methods/localize.js +17 -0
  38. package/dist/methods/menus.d.ts +42 -0
  39. package/dist/methods/menus.js +25 -0
  40. package/dist/methods/pages.d.ts +49 -0
  41. package/dist/methods/pages.js +34 -0
  42. package/dist/methods/routes.d.ts +46 -0
  43. package/dist/methods/routes.js +24 -0
  44. package/dist/methods/search.d.ts +27 -0
  45. package/dist/methods/search.js +16 -0
  46. package/dist/methods/system.d.ts +15 -0
  47. package/dist/methods/system.js +14 -0
  48. package/dist/schema/executor.d.ts +67 -0
  49. package/dist/schema/executor.js +81 -0
  50. package/dist/schema/index.d.ts +26 -0
  51. package/dist/schema/index.js +26 -0
  52. package/dist/schema/schema-builder.d.ts +42 -0
  53. package/dist/schema/schema-builder.js +77 -0
  54. package/dist/transformers/asset-path.d.ts +20 -0
  55. package/dist/transformers/asset-path.js +40 -0
  56. package/dist/transformers/compose.d.ts +9 -0
  57. package/dist/transformers/compose.js +14 -0
  58. package/dist/transformers/image-path.d.ts +28 -0
  59. package/dist/transformers/image-path.js +60 -0
  60. package/dist/transformers/index.d.ts +8 -0
  61. package/dist/transformers/index.js +9 -0
  62. package/dist/transformers/page-link.d.ts +18 -0
  63. package/dist/transformers/page-link.js +45 -0
  64. package/dist/utils/index.d.ts +5 -0
  65. package/dist/utils/index.js +5 -0
  66. package/dist/utils/route-map.d.ts +12 -0
  67. package/dist/utils/route-map.js +86 -0
  68. package/dist/utils/tenant.d.ts +71 -0
  69. package/dist/utils/tenant.js +88 -0
  70. package/package.json +49 -7
  71. package/dist/api.d.ts +0 -69
  72. package/dist/api.js +0 -303
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Cache management with LRU cache and tenant isolation
3
+ */
4
+ import { LRUCache } from "lru-cache";
5
+ /**
6
+ * Creates a cache manager with prefixed keys
7
+ * Each call creates a new LRU cache instance - no shared state
8
+ */
9
+ export function createCacheManager(cachePrefix, options = {}) {
10
+ const cache = new LRUCache({
11
+ max: options.max ?? 100,
12
+ ttl: options.ttl ?? 100000,
13
+ allowStale: false,
14
+ });
15
+ const prefixedKey = (key) => `${cachePrefix}${key}`;
16
+ return {
17
+ get(key) {
18
+ return cache.get(prefixedKey(key));
19
+ },
20
+ set(key, value) {
21
+ cache.set(prefixedKey(key), value);
22
+ },
23
+ clear(pattern) {
24
+ const prefix = pattern !== undefined ? `${cachePrefix}${pattern}` : cachePrefix;
25
+ for (const key of cache.keys()) {
26
+ if (key.startsWith(prefix)) {
27
+ cache.delete(key);
28
+ }
29
+ }
30
+ },
31
+ };
32
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Configuration management for Cockpit API client
3
+ */
4
+ export interface CockpitAPIOptions {
5
+ /** Cockpit CMS endpoint URL (falls back to COCKPIT_GRAPHQL_ENDPOINT env var) */
6
+ endpoint?: string;
7
+ /** Tenant name for multi-tenant setups */
8
+ tenant?: string;
9
+ /** API key (falls back to COCKPIT_SECRET env var) */
10
+ apiKey?: string;
11
+ /** Use admin access with API key */
12
+ useAdminAccess?: boolean;
13
+ /**
14
+ * Default language that maps to Cockpit's "default" locale.
15
+ * When a request uses this language, it will be sent as "default" to Cockpit.
16
+ */
17
+ defaultLanguage?: string | null;
18
+ /** Cache configuration */
19
+ cache?: {
20
+ /** Max entries (falls back to COCKPIT_CACHE_MAX env var, default: 100) */
21
+ max?: number;
22
+ /** TTL in ms (falls back to COCKPIT_CACHE_TTL env var, default: 100000) */
23
+ ttl?: number;
24
+ };
25
+ /**
26
+ * Preload route replacements during client initialization.
27
+ * When true, fetches page routes to enable `pages://id` link resolution in responses.
28
+ * When false (default), skips the network request for faster cold starts.
29
+ * @default false
30
+ */
31
+ preloadRoutes?: boolean;
32
+ }
33
+ export interface CockpitConfig {
34
+ readonly endpoint: URL;
35
+ readonly tenant?: string;
36
+ readonly apiKey?: string;
37
+ readonly useAdminAccess: boolean;
38
+ readonly defaultLanguage: string | null;
39
+ readonly cachePrefix: string;
40
+ }
41
+ /**
42
+ * Creates an immutable configuration object for the Cockpit API client
43
+ */
44
+ export declare function createConfig(options?: CockpitAPIOptions): CockpitConfig;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Configuration management for Cockpit API client
3
+ */
4
+ import { resolveApiKey } from "../utils/tenant.js";
5
+ /** Valid tenant format: alphanumeric, hyphens, underscores only */
6
+ const VALID_TENANT_PATTERN = /^[a-z0-9_-]+$/i;
7
+ /**
8
+ * Creates an immutable configuration object for the Cockpit API client
9
+ */
10
+ export function createConfig(options = {}) {
11
+ const endpointStr = options.endpoint ?? process.env["COCKPIT_GRAPHQL_ENDPOINT"];
12
+ if (endpointStr === undefined || endpointStr === "") {
13
+ throw new Error("Cockpit: endpoint is required (provide via options or COCKPIT_GRAPHQL_ENDPOINT env var)");
14
+ }
15
+ // Validate tenant format to prevent path traversal
16
+ if (options.tenant !== undefined &&
17
+ !VALID_TENANT_PATTERN.test(options.tenant)) {
18
+ throw new Error("Cockpit: Invalid tenant format (only alphanumeric, hyphens, and underscores allowed)");
19
+ }
20
+ const endpoint = new URL(endpointStr);
21
+ const apiKey = resolveApiKey(options.tenant, options);
22
+ // Build config object with all properties before freezing
23
+ const config = Object.freeze({
24
+ endpoint,
25
+ useAdminAccess: options.useAdminAccess ?? false,
26
+ defaultLanguage: options.defaultLanguage ?? null,
27
+ cachePrefix: `${endpointStr}:${options.tenant ?? "default"}:`,
28
+ ...(options.tenant !== undefined && { tenant: options.tenant }),
29
+ ...(apiKey !== undefined && { apiKey }),
30
+ });
31
+ return config;
32
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * HTTP client for Cockpit API requests
3
+ */
4
+ import type { CockpitConfig } from "./config.ts";
5
+ import type { ResponseTransformer } from "../transformers/image-path.ts";
6
+ /**
7
+ * Per-request options that can override client-level settings
8
+ */
9
+ export interface HttpFetchOptions extends RequestInit {
10
+ /** Override the client-level useAdminAccess setting for this request */
11
+ useAdminAccess?: boolean;
12
+ }
13
+ export interface HttpClient {
14
+ /**
15
+ * Make a GET request expecting JSON response
16
+ */
17
+ fetch<T>(url: URL | string, options?: HttpFetchOptions): Promise<T | null>;
18
+ /**
19
+ * Make a GET request expecting text response (e.g., URL strings)
20
+ */
21
+ fetchText(url: URL | string, options?: HttpFetchOptions): Promise<string | null>;
22
+ /**
23
+ * Make a POST request with JSON body
24
+ */
25
+ post<T>(url: URL | string, body: unknown, options?: HttpFetchOptions): Promise<T | null>;
26
+ /**
27
+ * Make a DELETE request
28
+ */
29
+ delete<T>(url: URL | string, options?: HttpFetchOptions): Promise<T | null>;
30
+ }
31
+ /**
32
+ * Creates an HTTP client with authentication and response transformation
33
+ */
34
+ export declare function createHttpClient(config: CockpitConfig, transformer: ResponseTransformer): HttpClient;
@@ -0,0 +1,98 @@
1
+ /**
2
+ * HTTP client for Cockpit API requests
3
+ */
4
+ import { logger } from "../cockpit-logger.js";
5
+ /**
6
+ * Normalizes headers to Record<string, string>
7
+ */
8
+ function normalizeHeaders(headers) {
9
+ if (!headers || typeof headers !== "object" || Array.isArray(headers)) {
10
+ return {};
11
+ }
12
+ return headers;
13
+ }
14
+ /**
15
+ * Prepares request options for POST/DELETE requests
16
+ */
17
+ function prepareJsonRequestOptions(options, method, body) {
18
+ const { useAdminAccess, headers, ...restOptions } = options;
19
+ const customHeaders = normalizeHeaders(headers);
20
+ const fetchOpts = {
21
+ ...restOptions,
22
+ method,
23
+ headers: { "Content-Type": "application/json", ...customHeaders },
24
+ };
25
+ if (body !== undefined)
26
+ fetchOpts.body = JSON.stringify(body);
27
+ if (useAdminAccess !== undefined)
28
+ fetchOpts.useAdminAccess = useAdminAccess;
29
+ return fetchOpts;
30
+ }
31
+ /**
32
+ * Creates an HTTP client with authentication and response transformation
33
+ */
34
+ export function createHttpClient(config, transformer) {
35
+ /**
36
+ * Build headers with optional admin access override
37
+ * @param custom - Custom headers to include
38
+ * @param useAdminAccess - Per-request override (undefined = use config default)
39
+ */
40
+ const buildHeaders = (custom = {}, useAdminAccess) => {
41
+ const headers = { ...custom };
42
+ // Use per-request setting if provided, otherwise fall back to config
43
+ const shouldUseAdmin = useAdminAccess ?? config.useAdminAccess;
44
+ if (shouldUseAdmin && config.apiKey !== undefined) {
45
+ headers["api-Key"] = config.apiKey;
46
+ }
47
+ return headers;
48
+ };
49
+ const handleErrorResponse = async (response) => {
50
+ const errorText = await response.text();
51
+ logger.error(`Cockpit: Error accessing ${response.url}`, {
52
+ status: response.status,
53
+ body: errorText,
54
+ });
55
+ throw new Error(`Cockpit: Error accessing ${response.url} (${String(response.status)}): ${errorText}`);
56
+ };
57
+ const handleJsonResponse = async (response) => {
58
+ if (response.status === 404)
59
+ return null;
60
+ if (!response.ok)
61
+ return handleErrorResponse(response);
62
+ const json = await response.json();
63
+ return transformer.transform(json);
64
+ };
65
+ const handleTextResponse = async (response) => {
66
+ if (response.status === 404)
67
+ return null;
68
+ if (!response.ok)
69
+ return handleErrorResponse(response);
70
+ return response.text();
71
+ };
72
+ const doFetch = async (url, options = {}) => {
73
+ const { useAdminAccess, ...fetchOptions } = options;
74
+ logger.debug(`Cockpit: Requesting ${String(url)}`);
75
+ return fetch(url, {
76
+ ...fetchOptions,
77
+ headers: buildHeaders(fetchOptions.headers, useAdminAccess),
78
+ });
79
+ };
80
+ const fetchData = async (url, options = {}) => {
81
+ const response = await doFetch(url, options);
82
+ return handleJsonResponse(response);
83
+ };
84
+ const fetchTextData = async (url, options = {}) => {
85
+ const response = await doFetch(url, options);
86
+ return handleTextResponse(response);
87
+ };
88
+ return {
89
+ fetch: fetchData,
90
+ fetchText: fetchTextData,
91
+ async post(url, body, options = {}) {
92
+ return fetchData(url, prepareJsonRequestOptions(options, "POST", body));
93
+ },
94
+ async delete(url, options = {}) {
95
+ return fetchData(url, prepareJsonRequestOptions(options, "DELETE"));
96
+ },
97
+ };
98
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Core module exports
3
+ */
4
+ export { buildQueryString, encodeQueryParam } from "./query-string.ts";
5
+ export type { CacheManager, CacheOptions } from "./cache.ts";
6
+ export { createCacheManager } from "./cache.ts";
7
+ export type { CockpitConfig } from "./config.ts";
8
+ export { createConfig } from "./config.ts";
9
+ export type { UrlBuilder, UrlBuildOptions } from "./url-builder.ts";
10
+ export { createUrlBuilder } from "./url-builder.ts";
11
+ export type { HttpClient } from "./http.ts";
12
+ export { createHttpClient } from "./http.ts";
13
+ export { requireParam, validatePathSegment } from "./validation.ts";
14
+ export { createLocaleNormalizer } from "./locale.ts";
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Core module exports
3
+ */
4
+ export { buildQueryString, encodeQueryParam } from "./query-string.js";
5
+ export { createCacheManager } from "./cache.js";
6
+ export { createConfig } from "./config.js";
7
+ export { createUrlBuilder } from "./url-builder.js";
8
+ export { createHttpClient } from "./http.js";
9
+ export { requireParam, validatePathSegment } from "./validation.js";
10
+ export { createLocaleNormalizer } from "./locale.js";
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Shared locale utilities
3
+ */
4
+ /**
5
+ * Creates a locale normalizer for the given default language.
6
+ * Maps the default language to Cockpit's "default" locale.
7
+ *
8
+ * @param defaultLanguage - The language that should map to "default", or null to disable mapping
9
+ * @returns A function that normalizes locale strings
10
+ */
11
+ export declare function createLocaleNormalizer(defaultLanguage: string | null): (locale?: string) => string;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Shared locale utilities
3
+ */
4
+ /**
5
+ * Creates a locale normalizer for the given default language.
6
+ * Maps the default language to Cockpit's "default" locale.
7
+ *
8
+ * @param defaultLanguage - The language that should map to "default", or null to disable mapping
9
+ * @returns A function that normalizes locale strings
10
+ */
11
+ export function createLocaleNormalizer(defaultLanguage) {
12
+ return (locale) => {
13
+ if (locale === undefined)
14
+ return "default";
15
+ if (defaultLanguage !== null && locale === defaultLanguage)
16
+ return "default";
17
+ return locale;
18
+ };
19
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Query string encoding utilities
3
+ */
4
+ /** Primitive values that can be used as query parameters */
5
+ export type QueryParamPrimitive = string | number | boolean | null | undefined;
6
+ /** Object values that will be JSON stringified */
7
+ export type QueryParamObject = Record<string, QueryParamPrimitive | QueryParamPrimitive[]>;
8
+ /** All allowed query parameter value types */
9
+ export type QueryParamValue = QueryParamPrimitive | QueryParamObject | QueryParamValue[];
10
+ /**
11
+ * Encodes a single query parameter key-value pair
12
+ */
13
+ export declare const encodeQueryParam: (key: string, value: QueryParamValue) => string;
14
+ /**
15
+ * Builds a query string from an object of parameters
16
+ * Filters out null and undefined values
17
+ */
18
+ export declare const buildQueryString: (params: Record<string, QueryParamValue>) => string | null;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Query string encoding utilities
3
+ */
4
+ /**
5
+ * Encodes a single query parameter key-value pair
6
+ */
7
+ export const encodeQueryParam = (key, value) => {
8
+ const encodedValue = typeof value === "string" ? value : JSON.stringify(value);
9
+ return `${encodeURIComponent(key)}=${encodeURIComponent(encodedValue)}`;
10
+ };
11
+ /**
12
+ * Builds a query string from an object of parameters
13
+ * Filters out null and undefined values
14
+ */
15
+ export const buildQueryString = (params) => {
16
+ const entries = Object.entries(params).filter(([, v]) => v !== undefined && v !== null);
17
+ if (!entries.length)
18
+ return null;
19
+ return entries.map(([key, value]) => encodeQueryParam(key, value)).join("&");
20
+ };
@@ -0,0 +1,22 @@
1
+ /**
2
+ * URL construction utilities
3
+ */
4
+ import type { CockpitConfig } from "./config.ts";
5
+ export interface UrlBuildOptions {
6
+ locale?: string;
7
+ queryParams?: Record<string, unknown>;
8
+ }
9
+ export interface UrlBuilder {
10
+ /**
11
+ * Build a URL for an API endpoint
12
+ */
13
+ build(path: string, options?: UrlBuildOptions): URL;
14
+ /**
15
+ * Get the GraphQL endpoint URL
16
+ */
17
+ graphqlEndpoint(): URL;
18
+ }
19
+ /**
20
+ * Creates a URL builder for the given configuration
21
+ */
22
+ export declare function createUrlBuilder(config: CockpitConfig): UrlBuilder;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * URL construction utilities
3
+ */
4
+ import { buildQueryString } from "./query-string.js";
5
+ import { createLocaleNormalizer } from "./locale.js";
6
+ /**
7
+ * Creates a URL builder for the given configuration
8
+ */
9
+ export function createUrlBuilder(config) {
10
+ const apiBasePath = config.tenant !== undefined ? `/:${config.tenant}/api` : "/api";
11
+ const normalizeLocale = createLocaleNormalizer(config.defaultLanguage);
12
+ return {
13
+ build(path, options = {}) {
14
+ const { locale = "default", queryParams = {} } = options;
15
+ const normalizedLocale = normalizeLocale(locale);
16
+ const url = new URL(config.endpoint);
17
+ url.pathname = `${apiBasePath}${path}`;
18
+ const queryString = buildQueryString({
19
+ ...queryParams,
20
+ locale: normalizedLocale,
21
+ });
22
+ if (queryString !== null) {
23
+ url.search = queryString;
24
+ }
25
+ return url;
26
+ },
27
+ graphqlEndpoint() {
28
+ const url = new URL(config.endpoint);
29
+ if (config.tenant !== undefined) {
30
+ url.pathname = `/:${config.tenant}${url.pathname}`;
31
+ }
32
+ return url;
33
+ },
34
+ };
35
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Shared validation utilities
3
+ */
4
+ /**
5
+ * Validates that a required parameter is present
6
+ * @throws Error if value is undefined, null, or empty string
7
+ */
8
+ export declare const requireParam: (value: unknown, name: string) => void;
9
+ /**
10
+ * Validates path segment format to prevent path traversal
11
+ * @throws Error if value contains invalid characters
12
+ */
13
+ export declare const validatePathSegment: (value: string, name: string) => void;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Shared validation utilities
3
+ */
4
+ /** Valid path segment format: alphanumeric, hyphens, underscores only */
5
+ const VALID_PATH_SEGMENT = /^[a-zA-Z0-9_-]+$/;
6
+ /**
7
+ * Validates that a required parameter is present
8
+ * @throws Error if value is undefined, null, or empty string
9
+ */
10
+ export const requireParam = (value, name) => {
11
+ if (value === undefined || value === null || value === "")
12
+ throw new Error(`Cockpit: Please provide ${name}`);
13
+ };
14
+ /**
15
+ * Validates path segment format to prevent path traversal
16
+ * @throws Error if value contains invalid characters
17
+ */
18
+ export const validatePathSegment = (value, name) => {
19
+ if (!VALID_PATH_SEGMENT.test(value)) {
20
+ throw new Error(`Cockpit: Invalid ${name} format (only alphanumeric, hyphens, and underscores allowed)`);
21
+ }
22
+ };
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Lightweight fetch client for Cockpit CMS
3
+ *
4
+ * Designed for edge/RSC environments where the full CockpitAPI
5
+ * factory is too heavy. This client:
6
+ * - Has no async initialization
7
+ * - No caching (relies on platform caching)
8
+ * - No response transformation
9
+ * - Minimal memory footprint
10
+ */
11
+ import type { CockpitPage } from "../methods/pages.ts";
12
+ import type { CockpitContentItem } from "../methods/content.ts";
13
+ /**
14
+ * Request cache mode for fetch requests
15
+ */
16
+ export type FetchCacheMode = "default" | "force-cache" | "no-cache" | "no-store" | "only-if-cached" | "reload";
17
+ /**
18
+ * Options for creating a lightweight fetch client
19
+ */
20
+ export interface FetchClientOptions {
21
+ /** Cockpit CMS endpoint URL */
22
+ endpoint?: string;
23
+ /** Tenant ID for multi-tenant setups */
24
+ tenant?: string | null;
25
+ /**
26
+ * Default language that maps to Cockpit's "default" locale.
27
+ * When a request uses this language, it will be sent as "default" to Cockpit.
28
+ * @default "de"
29
+ */
30
+ defaultLanguage?: string;
31
+ /** Request cache mode (default: "no-store") */
32
+ cache?: FetchCacheMode;
33
+ /** Additional request headers */
34
+ headers?: Record<string, string>;
35
+ /** API key for authenticated requests */
36
+ apiKey?: string;
37
+ }
38
+ /**
39
+ * Query parameters for page/content requests
40
+ */
41
+ export interface PageFetchParams {
42
+ /** Locale for the request (the configured defaultLanguage maps to "default") */
43
+ locale?: string;
44
+ /** Populate depth for linked content */
45
+ populate?: number;
46
+ /** Additional query parameters */
47
+ [key: string]: string | number | boolean | undefined;
48
+ }
49
+ /**
50
+ * Lightweight fetch client interface
51
+ */
52
+ export interface FetchClient {
53
+ /** Fetch a page by route */
54
+ pageByRoute<T = CockpitPage>(route: string, params?: PageFetchParams): Promise<T | null>;
55
+ /** Fetch pages list */
56
+ pages<T = CockpitPage>(params?: PageFetchParams): Promise<T[] | null>;
57
+ /** Fetch a page by ID */
58
+ pageById<T = CockpitPage>(id: string, params?: PageFetchParams): Promise<T | null>;
59
+ /** Fetch content items */
60
+ getContentItems<T = CockpitContentItem>(model: string, params?: PageFetchParams): Promise<T[] | null>;
61
+ /** Fetch a single content item */
62
+ getContentItem<T = unknown>(model: string, id?: string, params?: PageFetchParams): Promise<T | null>;
63
+ /** Raw fetch for custom paths */
64
+ fetchRaw<T = unknown>(path: string, params?: Record<string, string | number | boolean | undefined>): Promise<T>;
65
+ }
66
+ /**
67
+ * Creates a lightweight fetch client for Cockpit CMS
68
+ *
69
+ * @example
70
+ * ```typescript
71
+ * import { createFetchClient } from "@unchainedshop/cockpit-api/fetch";
72
+ *
73
+ * const cockpit = createFetchClient({
74
+ * endpoint: process.env.NEXT_PUBLIC_COCKPIT_ENDPOINT,
75
+ * tenant: "mytenant",
76
+ * });
77
+ *
78
+ * // Fetch a page by route
79
+ * const page = await cockpit.pageByRoute("/about", { locale: "en" });
80
+ *
81
+ * // Fetch content items
82
+ * const items = await cockpit.getContentItems("news", { locale: "de", limit: 10 });
83
+ * ```
84
+ */
85
+ export declare function createFetchClient(options?: FetchClientOptions): FetchClient;
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Lightweight fetch client for Cockpit CMS
3
+ *
4
+ * Designed for edge/RSC environments where the full CockpitAPI
5
+ * factory is too heavy. This client:
6
+ * - Has no async initialization
7
+ * - No caching (relies on platform caching)
8
+ * - No response transformation
9
+ * - Minimal memory footprint
10
+ */
11
+ import { createLocaleNormalizer } from "../core/locale.js";
12
+ /**
13
+ * Build the API base URL for the given endpoint and tenant
14
+ */
15
+ function buildApiBaseUrl(endpoint, tenant) {
16
+ const url = new URL(endpoint);
17
+ const basePath = tenant !== undefined && tenant !== null ? `/:${tenant}/api` : "/api";
18
+ return `${url.origin}${basePath}`;
19
+ }
20
+ /**
21
+ * Build query string from params object, filtering undefined values
22
+ */
23
+ function buildQueryString(params) {
24
+ const filtered = Object.entries(params)
25
+ .filter(([, value]) => value !== undefined)
26
+ .map(([key, value]) => [key, String(value)]);
27
+ return new URLSearchParams(filtered).toString();
28
+ }
29
+ /**
30
+ * Creates a lightweight fetch client for Cockpit CMS
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * import { createFetchClient } from "@unchainedshop/cockpit-api/fetch";
35
+ *
36
+ * const cockpit = createFetchClient({
37
+ * endpoint: process.env.NEXT_PUBLIC_COCKPIT_ENDPOINT,
38
+ * tenant: "mytenant",
39
+ * });
40
+ *
41
+ * // Fetch a page by route
42
+ * const page = await cockpit.pageByRoute("/about", { locale: "en" });
43
+ *
44
+ * // Fetch content items
45
+ * const items = await cockpit.getContentItems("news", { locale: "de", limit: 10 });
46
+ * ```
47
+ */
48
+ export function createFetchClient(options = {}) {
49
+ const { endpoint = process.env["COCKPIT_GRAPHQL_ENDPOINT"] ??
50
+ process.env["NEXT_PUBLIC_COCKPIT_ENDPOINT"], tenant = null, defaultLanguage = "de", cache = "no-store", headers = {}, apiKey, } = options;
51
+ if (endpoint === undefined || endpoint === "") {
52
+ throw new Error("Cockpit: endpoint is required (provide via options or COCKPIT_GRAPHQL_ENDPOINT env var)");
53
+ }
54
+ const baseUrl = buildApiBaseUrl(endpoint, tenant);
55
+ const normalizeLocale = createLocaleNormalizer(defaultLanguage);
56
+ const requestHeaders = { ...headers };
57
+ if (apiKey !== undefined) {
58
+ requestHeaders["api-Key"] = apiKey;
59
+ }
60
+ /**
61
+ * Fetch raw JSON from a Cockpit API path
62
+ */
63
+ async function fetchRaw(path, params = {}) {
64
+ const queryString = buildQueryString(params);
65
+ const url = queryString
66
+ ? `${baseUrl}${path}?${queryString}`
67
+ : `${baseUrl}${path}`;
68
+ const fetchInit = { cache };
69
+ if (Object.keys(requestHeaders).length > 0) {
70
+ fetchInit.headers = requestHeaders;
71
+ }
72
+ const response = await fetch(url, fetchInit);
73
+ if (!response.ok) {
74
+ if (response.status === 404) {
75
+ return null;
76
+ }
77
+ throw new Error(`Cockpit: Error fetching ${url} (${String(response.status)})`);
78
+ }
79
+ return response.json();
80
+ }
81
+ return {
82
+ /**
83
+ * Fetch a page by route
84
+ */
85
+ async pageByRoute(route, params = {}) {
86
+ const { locale, populate, ...rest } = params;
87
+ return fetchRaw("/pages/page", {
88
+ route,
89
+ locale: normalizeLocale(locale),
90
+ populate,
91
+ ...rest,
92
+ });
93
+ },
94
+ /**
95
+ * Fetch pages list
96
+ */
97
+ async pages(params = {}) {
98
+ const { locale, ...rest } = params;
99
+ return fetchRaw("/pages/pages", {
100
+ locale: normalizeLocale(locale),
101
+ ...rest,
102
+ });
103
+ },
104
+ /**
105
+ * Fetch a page by ID
106
+ */
107
+ async pageById(id, params = {}) {
108
+ const { locale, populate, ...rest } = params;
109
+ return fetchRaw(`/pages/page/${id}`, {
110
+ locale: normalizeLocale(locale),
111
+ populate,
112
+ ...rest,
113
+ });
114
+ },
115
+ /**
116
+ * Fetch content items
117
+ */
118
+ async getContentItems(model, params = {}) {
119
+ const { locale, ...rest } = params;
120
+ return fetchRaw(`/content/items/${model}`, {
121
+ locale: normalizeLocale(locale),
122
+ ...rest,
123
+ });
124
+ },
125
+ /**
126
+ * Fetch a single content item
127
+ */
128
+ async getContentItem(model, id, params = {}) {
129
+ const { locale, ...rest } = params;
130
+ const path = id !== undefined
131
+ ? `/content/item/${model}/${id}`
132
+ : `/content/item/${model}`;
133
+ return fetchRaw(path, {
134
+ locale: normalizeLocale(locale),
135
+ ...rest,
136
+ });
137
+ },
138
+ /**
139
+ * Raw fetch for custom paths
140
+ */
141
+ fetchRaw: fetchRaw,
142
+ };
143
+ }