@unchainedshop/cockpit-api 1.0.34 → 2.1.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.
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 +45 -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 +17 -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,5 @@
1
+ /**
2
+ * Utility exports
3
+ */
4
+ export { getTenantIds, resolveApiKey } from "./tenant.js";
5
+ export { generateCmsRouteReplacements, generateCollectionAndSingletonSlugRouteMap, } from "./route-map.js";
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Route map utilities for CMS page link resolution
3
+ */
4
+ import type { CacheManager } from "../core/cache.ts";
5
+ /**
6
+ * Generate route replacements for page links (pages://id -> actual route)
7
+ */
8
+ export declare function generateCmsRouteReplacements(endpoint: string, tenant?: string, cache?: CacheManager): Promise<Record<string, string>>;
9
+ /**
10
+ * Generate slug to route map for collections and singletons
11
+ */
12
+ export declare function generateCollectionAndSingletonSlugRouteMap(endpoint: string, tenant?: string, cache?: CacheManager): Promise<Record<string, string>>;
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Route map utilities for CMS page link resolution
3
+ */
4
+ import { logger } from "../cockpit-logger.js";
5
+ /**
6
+ * Generate route replacements for page links (pages://id -> actual route)
7
+ */
8
+ export async function generateCmsRouteReplacements(endpoint, tenant, cache) {
9
+ const cacheKey = `ROUTE_REPLACEMENT_MAP:${tenant ?? "default"}`;
10
+ if (cache) {
11
+ const cached = cache.get(cacheKey);
12
+ if (cached)
13
+ return cached;
14
+ }
15
+ const filterParams = {
16
+ fields: JSON.stringify({ _id: 1, slug: 1, _r: 1 }),
17
+ };
18
+ try {
19
+ const origin = new URL(endpoint).origin;
20
+ const apiPath = tenant !== undefined ? `/:${tenant}/api` : "/api";
21
+ const response = await fetch(`${origin}${apiPath}/pages/pages?${new URLSearchParams(filterParams).toString()}`);
22
+ if (!response.ok) {
23
+ logger.warn(`Cockpit: Failed to fetch route replacements (status ${String(response.status)})`);
24
+ return {};
25
+ }
26
+ const pagesResponse = await response.json();
27
+ const pagesArr = (Array.isArray(pagesResponse) ? pagesResponse : []);
28
+ const replacement = pagesArr.reduce((result, item) => {
29
+ const key = `pages://${item._id}`;
30
+ const value = item._r;
31
+ return { ...result, [key]: value };
32
+ }, {});
33
+ if (cache) {
34
+ cache.set(cacheKey, replacement);
35
+ }
36
+ return replacement;
37
+ }
38
+ catch (e) {
39
+ logger.warn("Cockpit: Failed to fetch route replacements", e);
40
+ return {};
41
+ }
42
+ }
43
+ /**
44
+ * Generate slug to route map for collections and singletons
45
+ */
46
+ export async function generateCollectionAndSingletonSlugRouteMap(endpoint, tenant, cache) {
47
+ const cacheKey = `SLUG_ROUTE_MAP:${tenant ?? "default"}`;
48
+ if (cache) {
49
+ const cached = cache.get(cacheKey);
50
+ if (cached)
51
+ return cached;
52
+ }
53
+ const filterParams = {
54
+ fields: JSON.stringify({
55
+ data: { collection: 1, singleton: 1 },
56
+ _r: 1,
57
+ type: 1,
58
+ }),
59
+ filter: JSON.stringify({ "data.collection": { $ne: null } }),
60
+ };
61
+ try {
62
+ const origin = new URL(endpoint).origin;
63
+ const apiPath = tenant !== undefined ? `/:${tenant}/api` : "/api";
64
+ const response = await fetch(`${origin}${apiPath}/pages/pages?locale=default&${new URLSearchParams(filterParams).toString()}`);
65
+ if (!response.ok) {
66
+ logger.warn(`Cockpit: Failed to fetch slug route map (status ${String(response.status)})`);
67
+ return {};
68
+ }
69
+ const pagesResponse = await response.json();
70
+ const pagesArr = (Array.isArray(pagesResponse) ? pagesResponse : []);
71
+ const pageMap = pagesArr.reduce((result, { data, _r }) => {
72
+ const entityName = data?.collection ?? data?.singleton;
73
+ if (entityName === undefined)
74
+ return result;
75
+ return { ...result, [entityName]: _r };
76
+ }, {});
77
+ if (cache) {
78
+ cache.set(cacheKey, pageMap);
79
+ }
80
+ return pageMap;
81
+ }
82
+ catch (e) {
83
+ logger.warn("Cockpit: Failed to fetch slug route map", e);
84
+ return {};
85
+ }
86
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Tenant utilities for multi-tenant Cockpit CMS support
3
+ */
4
+ import type { CockpitAPIOptions } from "../core/config.ts";
5
+ /**
6
+ * Options for URL-based tenant resolution
7
+ */
8
+ export interface ResolveTenantFromUrlOptions {
9
+ /** Default hostname to exclude from tenant matching (e.g., "gastro") */
10
+ defaultHost?: string;
11
+ }
12
+ /**
13
+ * Result of tenant resolution from URL
14
+ */
15
+ export interface TenantUrlResult {
16
+ /** Resolved tenant ID or null if not found */
17
+ tenant: string | null;
18
+ /** Slug extracted from pathname (last path segment) */
19
+ slug: string | null;
20
+ /** The parsed hostname */
21
+ hostname: string;
22
+ }
23
+ /**
24
+ * Options for subdomain-based tenant resolution
25
+ */
26
+ export interface ResolveTenantFromSubdomainOptions {
27
+ /** Default hostname to exclude from tenant matching */
28
+ defaultHost?: string;
29
+ }
30
+ /**
31
+ * Get all configured tenant IDs from environment variables
32
+ * Looks for COCKPIT_SECRET_<TENANT> patterns
33
+ */
34
+ export declare const getTenantIds: () => string[];
35
+ /**
36
+ * Resolve the API key for a tenant
37
+ * Priority: options.apiKey > COCKPIT_SECRET_<TENANT> > COCKPIT_SECRET
38
+ */
39
+ export declare const resolveApiKey: (tenant?: string, options?: CockpitAPIOptions) => string | undefined;
40
+ /**
41
+ * Resolve tenant ID from a subdomain
42
+ *
43
+ * Checks if the subdomain matches any configured tenant ID (from COCKPIT_SECRET_* env vars).
44
+ * Returns null if subdomain matches defaultHost or is not a valid tenant.
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * // With COCKPIT_SECRET_MYTENANT env var set:
49
+ * resolveTenantFromSubdomain("mytenant") // Returns: "mytenant"
50
+ * resolveTenantFromSubdomain("unknown") // Returns: null
51
+ * resolveTenantFromSubdomain("gastro", { defaultHost: "gastro" }) // Returns: null
52
+ * ```
53
+ */
54
+ export declare function resolveTenantFromSubdomain(subdomain: string | undefined, options?: ResolveTenantFromSubdomainOptions): string | null;
55
+ /**
56
+ * Resolve tenant ID and slug from a URL
57
+ *
58
+ * Examines the subdomain of the URL against configured tenant IDs.
59
+ * Returns null for tenant if subdomain matches defaultHost or is not a valid tenant.
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * // With COCKPIT_SECRET_MYTENANT env var set:
64
+ * resolveTenantFromUrl("https://mytenant.example.com/some/page")
65
+ * // Returns: { tenant: "mytenant", slug: "page", hostname: "mytenant.example.com" }
66
+ *
67
+ * resolveTenantFromUrl("https://gastro.example.com/page", { defaultHost: "gastro" })
68
+ * // Returns: { tenant: null, slug: "page", hostname: "gastro.example.com" }
69
+ * ```
70
+ */
71
+ export declare function resolveTenantFromUrl(url: string, options?: ResolveTenantFromUrlOptions): TenantUrlResult;
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Tenant utilities for multi-tenant Cockpit CMS support
3
+ */
4
+ /**
5
+ * Get all configured tenant IDs from environment variables
6
+ * Looks for COCKPIT_SECRET_<TENANT> patterns
7
+ */
8
+ export const getTenantIds = () => {
9
+ const env = { ...process.env };
10
+ const cockpitSecretKeys = Object.keys(env).filter((key) => key.includes("COCKPIT_SECRET") &&
11
+ key !== "COCKPIT_SECRET" &&
12
+ !key.endsWith("_FILE"));
13
+ return cockpitSecretKeys.map((key) => {
14
+ return key.slice("COCKPIT_SECRET_".length).toLowerCase();
15
+ });
16
+ };
17
+ /**
18
+ * Resolve the API key for a tenant
19
+ * Priority: options.apiKey > COCKPIT_SECRET_<TENANT> > COCKPIT_SECRET
20
+ */
21
+ export const resolveApiKey = (tenant, options) => {
22
+ if (options?.apiKey !== undefined)
23
+ return options.apiKey;
24
+ const secretName = ["COCKPIT_SECRET", tenant]
25
+ .filter(Boolean)
26
+ .join("_")
27
+ .toUpperCase();
28
+ return process.env[secretName];
29
+ };
30
+ /**
31
+ * Resolve tenant ID from a subdomain
32
+ *
33
+ * Checks if the subdomain matches any configured tenant ID (from COCKPIT_SECRET_* env vars).
34
+ * Returns null if subdomain matches defaultHost or is not a valid tenant.
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * // With COCKPIT_SECRET_MYTENANT env var set:
39
+ * resolveTenantFromSubdomain("mytenant") // Returns: "mytenant"
40
+ * resolveTenantFromSubdomain("unknown") // Returns: null
41
+ * resolveTenantFromSubdomain("gastro", { defaultHost: "gastro" }) // Returns: null
42
+ * ```
43
+ */
44
+ export function resolveTenantFromSubdomain(subdomain, options = {}) {
45
+ if (subdomain === undefined)
46
+ return null;
47
+ const { defaultHost } = options;
48
+ const normalizedSubdomain = subdomain.toLowerCase();
49
+ // Skip if it matches the default host
50
+ if (normalizedSubdomain === defaultHost?.toLowerCase()) {
51
+ return null;
52
+ }
53
+ // Check against configured tenant IDs
54
+ const tenantIds = getTenantIds();
55
+ const matchedTenant = tenantIds.find((id) => id === normalizedSubdomain);
56
+ return matchedTenant ?? null;
57
+ }
58
+ /**
59
+ * Resolve tenant ID and slug from a URL
60
+ *
61
+ * Examines the subdomain of the URL against configured tenant IDs.
62
+ * Returns null for tenant if subdomain matches defaultHost or is not a valid tenant.
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * // With COCKPIT_SECRET_MYTENANT env var set:
67
+ * resolveTenantFromUrl("https://mytenant.example.com/some/page")
68
+ * // Returns: { tenant: "mytenant", slug: "page", hostname: "mytenant.example.com" }
69
+ *
70
+ * resolveTenantFromUrl("https://gastro.example.com/page", { defaultHost: "gastro" })
71
+ * // Returns: { tenant: null, slug: "page", hostname: "gastro.example.com" }
72
+ * ```
73
+ */
74
+ export function resolveTenantFromUrl(url, options = {}) {
75
+ const parsedUrl = new URL(url);
76
+ const hostname = parsedUrl.hostname;
77
+ // Extract slug from pathname (last segment)
78
+ const pathSegments = parsedUrl.pathname.split("/").filter(Boolean);
79
+ const lastSegment = pathSegments[pathSegments.length - 1];
80
+ const slug = lastSegment ?? null;
81
+ // Extract subdomain (first part of hostname)
82
+ const hostnameParts = hostname.split(".");
83
+ const firstPart = hostnameParts[0];
84
+ const subdomain = firstPart?.toLowerCase();
85
+ // Resolve tenant from subdomain
86
+ const tenant = resolveTenantFromSubdomain(subdomain, options);
87
+ return { tenant, slug, hostname };
88
+ }
package/package.json CHANGED
@@ -1,15 +1,33 @@
1
1
  {
2
2
  "name": "@unchainedshop/cockpit-api",
3
- "version": "1.0.34",
3
+ "version": "2.1.0",
4
4
  "description": "A package to interact with the Cockpit CMS API, including functionalities to handle GraphQL requests and various CMS content manipulations.",
5
5
  "main": "dist/index.js",
6
6
  "homepage": "https://unchained.shop",
7
7
  "type": "module",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./schema": {
14
+ "types": "./dist/schema/index.d.ts",
15
+ "import": "./dist/schema/index.js"
16
+ },
17
+ "./fetch": {
18
+ "types": "./dist/fetch/index.d.ts",
19
+ "import": "./dist/fetch/index.js"
20
+ }
21
+ },
8
22
  "publishConfig": {
9
23
  "registry": "https://registry.npmjs.org"
10
24
  },
11
25
  "scripts": {
12
- "build": "tsc"
26
+ "build": "tsc",
27
+ "lint": "eslint src",
28
+ "lint:fix": "eslint src --fix",
29
+ "test": "node --experimental-transform-types --test --experimental-test-coverage --test-coverage-exclude='src/index.ts' --test-coverage-exclude='src/**/*.test.ts' --test-coverage-exclude='src/__tests__/*' src/*.test.ts src/**/*.test.ts",
30
+ "test:watch": "node --experimental-transform-types --test --watch src/*.test.ts src/**/*.test.ts"
13
31
  },
14
32
  "repository": {
15
33
  "type": "git",
@@ -24,15 +42,39 @@
24
42
  "api"
25
43
  ],
26
44
  "author": "Mikael Araya",
27
- "license": "ISC",
45
+ "contributors": [
46
+ "Pascal Kaufmann"
47
+ ],
48
+ "license": "MIT",
49
+ "engines": {
50
+ "node": ">=20"
51
+ },
28
52
  "dependencies": {
29
- "@unchainedshop/logger": "^4.4.0",
30
- "graphql": "^16.12.0",
53
+ "@unchainedshop/logger": "^4.6.0",
31
54
  "lru-cache": "^11.2.4"
32
55
  },
33
56
  "types": "dist/index.d.ts",
57
+ "peerDependencies": {
58
+ "@graphql-tools/wrap": "11.1.4",
59
+ "graphql": "^15.0.0 || ^16.0.0"
60
+ },
61
+ "peerDependenciesMeta": {
62
+ "graphql": {
63
+ "optional": true
64
+ },
65
+ "@graphql-tools/wrap": {
66
+ "optional": true
67
+ }
68
+ },
34
69
  "devDependencies": {
35
- "@types/node": "^25.0.2",
36
- "typescript": "^5.9.3"
70
+ "@eslint/js": "^9.39.2",
71
+ "@types/node": "^24.10.7",
72
+ "eslint": "^9.39.2",
73
+ "eslint-config-prettier": "^10.1.8",
74
+ "eslint-plugin-prettier": "^5.5.4",
75
+ "graphql": "^16.12.0",
76
+ "prettier": "^3.7.4",
77
+ "typescript": "^5.9.3",
78
+ "typescript-eslint": "^8.52.0"
37
79
  }
38
80
  }
package/dist/api.d.ts DELETED
@@ -1,69 +0,0 @@
1
- interface CockpitAPIOptions {
2
- endpoint?: string;
3
- apiKey?: string;
4
- useAdminAccess?: boolean;
5
- }
6
- export declare enum ImageSizeMode {
7
- Thumbnail = "thumbnail",
8
- BestFit = "bestFit",
9
- Resize = "resize",
10
- FitToWidth = "fitToWidth",
11
- FitToHeight = "fitToHeight"
12
- }
13
- export declare enum MimeType {
14
- AUTO = "auto",
15
- GIF = "gif",
16
- JPEG = "jpeg",
17
- PNG = "png",
18
- WEBP = "webp",
19
- BMP = "bmp"
20
- }
21
- export declare const getTenantIds: () => string[];
22
- export declare const generateCmsRouteReplacements: (tenant?: string) => Promise<any>;
23
- export declare const generateCollectionAndSingletonSlugRouteMap: (tenant?: string) => Promise<any>;
24
- export declare const FixImagePaths: (replacements: any, tenant?: string) => {
25
- transformResult(originalResponse: any): any;
26
- };
27
- export declare const CockpitAPI: (tenant?: string, cockpitOptions?: CockpitAPIOptions) => Promise<{
28
- graphQL(document: any, variables: any): Promise<any>;
29
- getContentItem({ model, id }: {
30
- model: string;
31
- id?: string;
32
- }, locale?: string, queryParams?: {}): Promise<any>;
33
- getAggregateModel({ model, pipeline }: {
34
- model: string;
35
- pipeline: any[];
36
- }, locale?: string): Promise<any>;
37
- getContentItems(model: string, locale?: string, queryParams?: any): Promise<any>;
38
- getContentTree(model: string, locale?: string, queryParams?: {
39
- filter: {};
40
- }): Promise<any>;
41
- postContentItem(model: string, item: any): Promise<any>;
42
- deleteContentItem(model: string, id?: string): Promise<any>;
43
- pages(locale?: string, queryParams?: {}): Promise<any>;
44
- pageById({ page, id }: {
45
- page: string;
46
- id: string;
47
- }, locale?: string, queryParams?: {}): Promise<any>;
48
- pageByRoute(route: string, locale?: string): Promise<any>;
49
- pagesMenus(locale?: string): Promise<any>;
50
- pagesMenu(name: string, locale?: string): Promise<any>;
51
- pagesRoutes(locale?: string): Promise<any>;
52
- pagesSitemap(): Promise<any>;
53
- pagesSetting(locale?: string): Promise<any>;
54
- healthCheck(): Promise<any>;
55
- lokalize(projectName: string, locale?: string, nested?: any): Promise<any>;
56
- assetById(assetId: string): Promise<any>;
57
- imageAssetById(assetId: string, queryParams?: {
58
- m?: ImageSizeMode;
59
- w?: number;
60
- h?: number;
61
- q?: number;
62
- mime?: MimeType;
63
- re?: number;
64
- t?: string;
65
- o?: number;
66
- }): Promise<any>;
67
- getFullRouteForSlug(slug: string): Promise<any>;
68
- }>;
69
- export {};