@cobrastyle/adapter-magento2 1.0.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.
@@ -0,0 +1,42 @@
1
+ export interface MagentoStoreConfig {
2
+ rootCategoryId: number;
3
+ rootCategoryUid: string;
4
+ storeCode: string;
5
+ storeName: string;
6
+ baseCurrencyCode: string;
7
+ defaultDisplayCurrencyCode: string;
8
+ locale: string;
9
+ timezone: string;
10
+ /** Suffix Magento appends to category URLs (e.g. ".html"; "" when disabled). */
11
+ categoryUrlSuffix: string;
12
+ /** Suffix Magento appends to product URLs (e.g. ".html"; "" when disabled). */
13
+ productUrlSuffix: string;
14
+ /** Magento "Add Store Code to URLs" (web/url/use_store). */
15
+ useStoreInUrl: boolean;
16
+ /**
17
+ * Identifier of the CMS page configured as the store home page
18
+ * (web/default/cms_home_page). Undefined when not set.
19
+ */
20
+ homePageIdentifier?: string;
21
+ /**
22
+ * Identifier of the CMS page configured as the 404 / no-route page
23
+ * (web/default/cms_no_route). Undefined when not set.
24
+ */
25
+ notFoundPageIdentifier?: string;
26
+ /** Default SEO title (design/head/default_title). */
27
+ defaultTitle?: string;
28
+ /** Default SEO meta description (design/head/default_description). */
29
+ defaultDescription?: string;
30
+ /** Default SEO meta keywords (design/head/default_keywords). */
31
+ defaultKeywords?: string;
32
+ /** Prefix prepended to page titles (design/head/title_prefix). */
33
+ titlePrefix?: string;
34
+ /** Suffix appended to page titles (design/head/title_suffix). */
35
+ titleSuffix?: string;
36
+ /** Separator between title parts (catalog/seo/title_separator). */
37
+ titleSeparator?: string;
38
+ }
39
+ /** Store config for the current request's store code, cached per code. */
40
+ export declare function getStoreConfig(): Promise<MagentoStoreConfig>;
41
+ /** Clear the per-store config cache (tests / after config changes). */
42
+ export declare function clearStoreConfigCache(): void;
@@ -0,0 +1,59 @@
1
+ import { graphqlFetch } from "./client";
2
+ import { getConfig } from "./config";
3
+ import * as queries from "./queries";
4
+ const cache = new Map();
5
+ const inflight = new Map();
6
+ function currentStoreCode() {
7
+ const config = getConfig();
8
+ let resolved;
9
+ try {
10
+ resolved = config.storeResolver?.();
11
+ }
12
+ catch {
13
+ resolved = undefined;
14
+ }
15
+ return resolved || config.storeCode || "default";
16
+ }
17
+ /** Store config for the current request's store code, cached per code. */
18
+ export async function getStoreConfig() {
19
+ const code = currentStoreCode();
20
+ const cached = cache.get(code);
21
+ if (cached)
22
+ return cached;
23
+ const existing = inflight.get(code);
24
+ if (existing)
25
+ return existing;
26
+ const promise = (async () => {
27
+ const data = await graphqlFetch(queries.GET_STORE_CONFIG, undefined, { storeCode: code });
28
+ const mapped = {
29
+ rootCategoryId: data.storeConfig.root_category_id,
30
+ rootCategoryUid: data.storeConfig.root_category_uid,
31
+ storeCode: data.storeConfig.store_code,
32
+ storeName: data.storeConfig.store_name,
33
+ baseCurrencyCode: data.storeConfig.base_currency_code,
34
+ defaultDisplayCurrencyCode: data.storeConfig.default_display_currency_code,
35
+ locale: data.storeConfig.locale,
36
+ timezone: data.storeConfig.timezone,
37
+ categoryUrlSuffix: data.storeConfig.category_url_suffix ?? "",
38
+ productUrlSuffix: data.storeConfig.product_url_suffix ?? "",
39
+ useStoreInUrl: data.storeConfig.use_store_in_url === true,
40
+ homePageIdentifier: data.storeConfig.cms_home_page || undefined,
41
+ notFoundPageIdentifier: data.storeConfig.cms_no_route || undefined,
42
+ defaultTitle: data.storeConfig.default_title || undefined,
43
+ defaultDescription: data.storeConfig.default_description || undefined,
44
+ defaultKeywords: data.storeConfig.default_keywords || undefined,
45
+ titlePrefix: data.storeConfig.title_prefix || undefined,
46
+ titleSuffix: data.storeConfig.title_suffix || undefined,
47
+ titleSeparator: data.storeConfig.title_separator || undefined,
48
+ };
49
+ cache.set(code, mapped);
50
+ return mapped;
51
+ })().finally(() => inflight.delete(code));
52
+ inflight.set(code, promise);
53
+ return promise;
54
+ }
55
+ /** Clear the per-store config cache (tests / after config changes). */
56
+ export function clearStoreConfigCache() {
57
+ cache.clear();
58
+ inflight.clear();
59
+ }
@@ -0,0 +1,10 @@
1
+ import type { StoreView } from "@cobrastyle/shared-types";
2
+ /** Map a raw Magento `availableStores` entry to a StoreView. */
3
+ export declare function mapStoreView(raw: any): StoreView;
4
+ /**
5
+ * All store views, fetched once and cached for the process lifetime (the store
6
+ * list is static config). Returns [] on failure without caching the failure.
7
+ */
8
+ export declare function getStores(): Promise<StoreView[]>;
9
+ /** Clear the store registry cache (tests / after config changes). */
10
+ export declare function clearStoresCache(): void;
package/dist/stores.js ADDED
@@ -0,0 +1,46 @@
1
+ import { graphqlFetch } from "./client";
2
+ import * as queries from "./queries";
3
+ /** Map a raw Magento `availableStores` entry to a StoreView. */
4
+ export function mapStoreView(raw) {
5
+ return {
6
+ code: raw.store_code,
7
+ name: raw.store_name ?? raw.store_code,
8
+ locale: raw.locale,
9
+ currencyCode: raw.default_display_currency_code,
10
+ baseCurrencyCode: raw.base_currency_code,
11
+ baseUrl: raw.base_url,
12
+ isDefault: raw.is_default_store === true,
13
+ };
14
+ }
15
+ let cachedStores = null;
16
+ let storesPromise = null;
17
+ /**
18
+ * All store views, fetched once and cached for the process lifetime (the store
19
+ * list is static config). Returns [] on failure without caching the failure.
20
+ */
21
+ export async function getStores() {
22
+ if (cachedStores)
23
+ return cachedStores;
24
+ if (storesPromise)
25
+ return storesPromise;
26
+ storesPromise = (async () => {
27
+ try {
28
+ const data = await graphqlFetch(queries.GET_AVAILABLE_STORES);
29
+ cachedStores = (data.availableStores ?? []).map(mapStoreView);
30
+ return cachedStores;
31
+ }
32
+ catch (error) {
33
+ console.warn("Failed to fetch store views:", error);
34
+ return [];
35
+ }
36
+ finally {
37
+ storesPromise = null;
38
+ }
39
+ })();
40
+ return storesPromise;
41
+ }
42
+ /** Clear the store registry cache (tests / after config changes). */
43
+ export function clearStoresCache() {
44
+ cachedStores = null;
45
+ storesPromise = null;
46
+ }
package/dist/urls.d.ts ADDED
@@ -0,0 +1,32 @@
1
+ /**
2
+ * URL helpers for the Magento adapter.
3
+ *
4
+ * Magento categories/products are addressed by a URL suffix (commonly ".html",
5
+ * configured per store via `storeConfig.category_url_suffix` /
6
+ * `product_url_suffix`). Magento's `route()` resolver only matches paths that
7
+ * include this suffix, so generated links must carry it and it must be present
8
+ * when resolving.
9
+ *
10
+ * We also deliberately build category paths from the `url_key` hierarchy rather
11
+ * than the entity's `url_path` field: `url_path` can be stale in Magento (e.g.
12
+ * a category named "22lr Ammunition" with url_key "22lr-ammunition" may report
13
+ * url_path "ammunition/lerduvor"), whereas the `url_key` chain always matches
14
+ * the store's canonical URL.
15
+ */
16
+ /** Join path segments with "/", dropping empty/null/undefined ones. */
17
+ export declare function joinSegments(segments: Array<string | null | undefined>): string;
18
+ /**
19
+ * Append `suffix` to `path` unless the path is empty or already ends with it.
20
+ * An empty suffix (store with clean URLs) is a no-op.
21
+ */
22
+ export declare function applySuffix(path: string, suffix: string): string;
23
+ /**
24
+ * Remove a trailing `suffix` from `path` if present. Empty suffix is a no-op.
25
+ * Only the trailing occurrence is removed (internal matches are preserved).
26
+ */
27
+ export declare function stripSuffix(path: string, suffix: string): string;
28
+ /**
29
+ * Build a category's canonical relative URL from its ancestor url_key chain,
30
+ * its own url_key, and the store's category URL suffix.
31
+ */
32
+ export declare function buildCategoryUrl(ancestorUrlKeys: Array<string | null | undefined>, urlKey: string | null | undefined, suffix: string): string;
package/dist/urls.js ADDED
@@ -0,0 +1,44 @@
1
+ /**
2
+ * URL helpers for the Magento adapter.
3
+ *
4
+ * Magento categories/products are addressed by a URL suffix (commonly ".html",
5
+ * configured per store via `storeConfig.category_url_suffix` /
6
+ * `product_url_suffix`). Magento's `route()` resolver only matches paths that
7
+ * include this suffix, so generated links must carry it and it must be present
8
+ * when resolving.
9
+ *
10
+ * We also deliberately build category paths from the `url_key` hierarchy rather
11
+ * than the entity's `url_path` field: `url_path` can be stale in Magento (e.g.
12
+ * a category named "22lr Ammunition" with url_key "22lr-ammunition" may report
13
+ * url_path "ammunition/lerduvor"), whereas the `url_key` chain always matches
14
+ * the store's canonical URL.
15
+ */
16
+ /** Join path segments with "/", dropping empty/null/undefined ones. */
17
+ export function joinSegments(segments) {
18
+ return segments.filter((s) => Boolean(s && s.length)).join("/");
19
+ }
20
+ /**
21
+ * Append `suffix` to `path` unless the path is empty or already ends with it.
22
+ * An empty suffix (store with clean URLs) is a no-op.
23
+ */
24
+ export function applySuffix(path, suffix) {
25
+ if (!path || !suffix)
26
+ return path;
27
+ return path.endsWith(suffix) ? path : `${path}${suffix}`;
28
+ }
29
+ /**
30
+ * Remove a trailing `suffix` from `path` if present. Empty suffix is a no-op.
31
+ * Only the trailing occurrence is removed (internal matches are preserved).
32
+ */
33
+ export function stripSuffix(path, suffix) {
34
+ if (!path || !suffix)
35
+ return path;
36
+ return path.endsWith(suffix) ? path.slice(0, -suffix.length) : path;
37
+ }
38
+ /**
39
+ * Build a category's canonical relative URL from its ancestor url_key chain,
40
+ * its own url_key, and the store's category URL suffix.
41
+ */
42
+ export function buildCategoryUrl(ancestorUrlKeys, urlKey, suffix) {
43
+ return applySuffix(joinSegments([...ancestorUrlKeys, urlKey]), suffix);
44
+ }
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@cobrastyle/adapter-magento2",
3
+ "version": "1.0.1",
4
+ "main": "./dist/index.js",
5
+ "types": "./dist/index.d.ts",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "default": "./dist/index.js"
10
+ }
11
+ },
12
+ "files": [
13
+ "dist"
14
+ ],
15
+ "dependencies": {
16
+ "@cobrastyle/shared-types": "1.0.1"
17
+ },
18
+ "devDependencies": {
19
+ "@types/node": "^20.0.0",
20
+ "typescript": "^5.3.0",
21
+ "@cobrastyle/adapter-conformance": "1.0.1"
22
+ },
23
+ "license": "MIT",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/eComero/cobrastyle-express-next.git",
27
+ "directory": "packages/adapters/magento2"
28
+ },
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "scripts": {
33
+ "build": "tsc --project tsconfig.build.json",
34
+ "type-check": "tsc --noEmit"
35
+ }
36
+ }