@stacksjs/commerce 0.75.38 → 0.75.41

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,36 @@
1
+ import type { FetchLike } from './types';
2
+ /**
3
+ * A store URL as a base to append API paths to.
4
+ *
5
+ * Accepts a bare host (`shop.example.com`), a full URL, or a WordPress install
6
+ * in a subdirectory (`https://example.com/shop`), whose path must be kept for
7
+ * `/wp-json` to resolve. Query string and fragment are dropped.
8
+ */
9
+ export declare function normalizeStoreUrl(input: string): string;
10
+ /** The lowercased host of a normalized store URL, used to scope identities. */
11
+ export declare function storeHost(storeUrl: string): string;
12
+ /** Fetch a URL and parse it as JSON (GET unless `request` says otherwise), or throw a `CatalogFetchError` naming it. */
13
+ export declare function fetchJson<T = unknown>(url: string, fetcher?: FetchLike, hint?: string, request?: JsonRequest): Promise<JsonResponse<T>>;
14
+ export declare interface JsonResponse<T> {
15
+ data: T
16
+ headers: Headers
17
+ }
18
+ /** A request other than a plain GET: Shopware's Store API takes criteria as a POST body. */
19
+ export declare interface JsonRequest {
20
+ method?: 'GET' | 'POST'
21
+ headers?: Record<string, string>
22
+ json?: unknown
23
+ }
24
+ /**
25
+ * The importer's only network primitive.
26
+ *
27
+ * Every failure names the URL that failed. "fetch failed" on its own leaves
28
+ * the operator guessing which of forty pages broke, and whether it was the
29
+ * store, the network or the importer.
30
+ */
31
+ export declare class CatalogFetchError extends Error {
32
+ public readonly url: string;
33
+ public readonly status?: number;
34
+ public readonly body?: string;
35
+ constructor(url: string, message: string, status?: number, body?: string);
36
+ }
@@ -0,0 +1,22 @@
1
+ /** RFC 9562 version 5 (SHA-1, name-based) UUID. */
2
+ export declare function uuidV5(name: string, namespace?: string): string;
3
+ /** The deterministic `uuid` for one imported record. */
4
+ export declare function catalogUuid(source: string, host: string, kind: CatalogRecordKind, externalId: string): string;
5
+ /**
6
+ * Stable row identities for imported records, without a schema change.
7
+ *
8
+ * Re-running an import must update what the last run wrote rather than add a
9
+ * second copy, so every imported row needs a key that survives between runs.
10
+ * `products` has no slug, SKU or external-id column to hold one, and names
11
+ * are not unique on any platform.
12
+ *
13
+ * Every commerce table already has a unique `uuid` column (the `useUuid`
14
+ * trait). A name-based UUID (RFC 9562 version 5) of
15
+ * `<source>:<store host>:<kind>:<external id>` is the same value on every run
16
+ * and unique per source record, so the existing column doubles as the
17
+ * external-id index. Rows created any other way keep their random v7 UUIDs and
18
+ * can never collide with these.
19
+ */
20
+ /** Namespace for every UUID the catalog importer derives. Never change it. */
21
+ export declare const CATALOG_IMPORT_NAMESPACE: '5d0c8a3e-6f1b-4c2a-9e47-b18f3d2a6c90';
22
+ export type CatalogRecordKind = 'product' | 'variant';
@@ -0,0 +1,37 @@
1
+ import type { CatalogAdapter } from './types';
2
+ export type {
3
+ CatalogAdapter,
4
+ CatalogCategory,
5
+ CatalogFetchOptions,
6
+ CatalogImage,
7
+ CatalogPage,
8
+ CatalogProduct,
9
+ CatalogSourceName,
10
+ CatalogVariant,
11
+ CatalogWarning,
12
+ FetchLike,
13
+ MappedProduct,
14
+ } from './types';
15
+ /** The adapter registered under `name`, case-insensitively. */
16
+ export declare function catalogAdapter(name: string): CatalogAdapter | undefined;
17
+ /**
18
+ * Catalog import from other commerce platforms (stacksjs/stacks#1319, #1320,
19
+ * #1321).
20
+ *
21
+ * Catalog only, from each platform's public storefront API: no credentials
22
+ * (Shopware's Store API wants its public sales channel access key), and
23
+ * therefore no customers or orders. Run it with
24
+ * `buddy commerce:import <store-url> --from shopify|woocommerce|shopware`.
25
+ *
26
+ * Adding a platform means writing a `CatalogAdapter` (a pure payload mapper
27
+ * plus a pager) and registering it in `catalogAdapters`.
28
+ */
29
+ export declare const catalogAdapters: Readonly<Record<string, CatalogAdapter>>;
30
+ export * from './http';
31
+ export * from './identity';
32
+ export * from './money';
33
+ export * from './shopify';
34
+ export * from './shopware';
35
+ export * from './text';
36
+ export * from './woocommerce';
37
+ export * from './writer';
@@ -0,0 +1,40 @@
1
+ import { decimalToMinor, PriceFormatError } from '../money';
2
+ /**
3
+ * A JSON number as the plain decimal string it was written as.
4
+ *
5
+ * `String()` gives the shortest decimal that parses back to the same double,
6
+ * which is the literal the source serialized: `19.99` is `"19.99"`, not the
7
+ * `19.989999999999998` the double actually holds. Only the exponent form
8
+ * (`1e-7`, `1e+21`) needs expanding, and that is done on the digits, not by
9
+ * arithmetic.
10
+ */
11
+ export declare function numberToDecimalString(value: number): string;
12
+ /**
13
+ * A decimal amount that arrived as a JSON number (or a numeric string) as
14
+ * integer minor units, or null when it is missing.
15
+ */
16
+ export declare function numberToMinor(value: number | string | null | undefined, exponent?: number): number | null;
17
+ /**
18
+ * Money at the import boundary.
19
+ *
20
+ * Sources hand prices over in three shapes. Shopify sends decimal strings in
21
+ * the shop's currency (`"19.99"`); the WooCommerce Store API sends an integer
22
+ * string already in minor units, plus how many decimals the STORE is
23
+ * configured with (`currency_minor_unit`), which is not always the ISO number;
24
+ * the Shopware Store API sends JSON numbers (`19.99`, `0.1`, `1999.5`).
25
+ *
26
+ * Both string shapes are converted by the commerce-wide money module
27
+ * (`../money`), which the dashboard and the order export use as well, so an
28
+ * imported `1999` and a dashboard-entered `19.99` land as the same integer and
29
+ * read back the same way (stacksjs/stacks#2851). JSON numbers are first
30
+ * written out as the decimal string they were parsed from, then go the same
31
+ * way, so no amount is ever multiplied as a float.
32
+ */
33
+ export {
34
+ currencyExponent,
35
+ decimalToMinor,
36
+ formatMinor,
37
+ minorToDecimal,
38
+ PriceFormatError,
39
+ rescaleMinor,
40
+ } from '../money';
@@ -0,0 +1,40 @@
1
+ import type { CatalogAdapter, CatalogFetchOptions, MappedProduct } from './types';
2
+ declare function price(value: unknown, exponent: number, label: string, externalId: string, warnings: CatalogWarning[]): number | null;
3
+ /** One `products.json` entry as a normalized catalog product. Pure. */
4
+ export declare function mapShopifyProduct(raw: ShopifyProductPayload, context: ShopifyMapContext): MappedProduct;
5
+ /** The shop's currency from `/meta.json`, or null if the store will not say. */
6
+ export declare function detectShopifyCurrency(storeUrl: string, fetcher?: CatalogFetchOptions['fetch']): Promise<string | null>;
7
+ export declare const shopifyAdapter: CatalogAdapter;
8
+ export declare interface ShopifyVariantPayload {
9
+ id: number | string
10
+ title?: string | null
11
+ sku?: string | null
12
+ price?: string | number | null
13
+ compare_at_price?: string | number | null
14
+ available?: boolean | null
15
+ inventory_quantity?: number | null
16
+ option1?: string | null
17
+ option2?: string | null
18
+ option3?: string | null
19
+ }
20
+ export declare interface ShopifyImagePayload {
21
+ src?: string | null
22
+ position?: number | null
23
+ alt?: string | null
24
+ }
25
+ export declare interface ShopifyProductPayload {
26
+ id: number | string
27
+ title?: string | null
28
+ handle?: string | null
29
+ body_html?: string | null
30
+ vendor?: string | null
31
+ product_type?: string | null
32
+ tags?: string[] | string | null
33
+ variants?: ShopifyVariantPayload[] | null
34
+ images?: ShopifyImagePayload[] | null
35
+ options?: Array<{ name?: string | null, values?: string[] | null }> | null
36
+ }
37
+ export declare interface ShopifyMapContext {
38
+ storeUrl: string
39
+ currency: string | null
40
+ }
@@ -0,0 +1,80 @@
1
+ import type { CatalogAdapter, FetchLike, MappedProduct } from './types';
2
+ /**
3
+ * `translated.<field>` (the value in the sales channel's language, with
4
+ * inheritance from the parent already applied), else the entity's own field.
5
+ * Shopware serializes an empty `translated` as `[]`, hence the object check.
6
+ */
7
+ declare function translated(entity: object | null | undefined, field: string): string | null;
8
+ /** One Store API parent product, plus its children, as a catalog product. Pure. */
9
+ export declare function mapShopwareProduct(raw: ShopwareProductPayload, context: ShopwareMapContext): MappedProduct;
10
+ /**
11
+ * A store URL as the base the Store API hangs off. Accepts the storefront URL
12
+ * or the API endpoint itself (`https://shop.example.com/store-api`), which is
13
+ * what headless storefront configs list.
14
+ */
15
+ export declare function shopwareBaseUrl(storeUrl: string): string;
16
+ /** Why `--access-key` is needed, before any request is made. */
17
+ export declare function missingAccessKeyMessage(): string;
18
+ /** The sales channel currency from `/store-api/context`, or null if it will not say. */
19
+ export declare function detectShopwareCurrency(base: string, accessKey: string, fetcher?: FetchLike): Promise<string | null>;
20
+ /** Criteria for one page of parent products (variants come separately). */
21
+ export declare function shopwareProductCriteria(page: number, limit: number): Record<string, unknown>;
22
+ /** Criteria for one page of the variants of `parentIds`. */
23
+ export declare function shopwareVariantCriteria(parentIds: string[], page: number): Record<string, unknown>;
24
+ /** Where an operator finds the key, for every message that asks for it. */
25
+ export declare const SHOPWARE_ACCESS_KEY_HELP: 'Find it in the Shopware Administration under Sales Channels: open the storefront or headless sales channel and copy the key from its "API access" card. A headless storefront also ships it in its page source.';
26
+ export declare const shopwareAdapter: CatalogAdapter;
27
+ export declare interface ShopwareNamedPayload {
28
+ id?: string | null
29
+ name?: string | null
30
+ translated?: { name?: string | null } | unknown[] | null
31
+ }
32
+ export declare interface ShopwarePropertyGroupPayload extends ShopwareNamedPayload {
33
+ position?: number | null
34
+ }
35
+ export declare interface ShopwareOptionPayload extends ShopwareNamedPayload {
36
+ groupId?: string | null
37
+ position?: number | null
38
+ group?: ShopwarePropertyGroupPayload | null
39
+ }
40
+ export declare interface ShopwareMediaPayload {
41
+ url?: string | null
42
+ alt?: string | null
43
+ title?: string | null
44
+ translated?: { alt?: string | null, title?: string | null } | unknown[] | null
45
+ }
46
+ export declare interface ShopwareProductMediaPayload {
47
+ position?: number | null
48
+ media?: ShopwareMediaPayload | null
49
+ }
50
+ export declare interface ShopwareCalculatedPricePayload {
51
+ unitPrice?: number | string | null
52
+ quantity?: number | null
53
+ listPrice?: { price?: number | string | null } | null
54
+ }
55
+ export declare interface ShopwareProductPayload {
56
+ id: string
57
+ parentId?: string | null
58
+ productNumber?: string | null
59
+ name?: string | null
60
+ description?: string | null
61
+ translated?: { name?: string | null, description?: string | null } | unknown[] | null
62
+ calculatedPrice?: ShopwareCalculatedPricePayload | null
63
+ stock?: number | null
64
+ availableStock?: number | null
65
+ available?: boolean | null
66
+ active?: boolean | null
67
+ childCount?: number | null
68
+ manufacturer?: ShopwareNamedPayload | null
69
+ seoCategory?: ShopwareNamedPayload | null
70
+ categories?: ShopwareNamedPayload[] | null
71
+ tags?: Array<{ name?: string | null }> | null
72
+ cover?: ShopwareProductMediaPayload | null
73
+ media?: ShopwareProductMediaPayload[] | null
74
+ options?: ShopwareOptionPayload[] | null
75
+ }
76
+ export declare interface ShopwareMapContext {
77
+ storeUrl: string
78
+ currency: string | null
79
+ children?: ShopwareProductPayload[]
80
+ }
@@ -0,0 +1,6 @@
1
+ /** Decode HTML character references (named, decimal and hex) in one pass. */
2
+ export declare function decodeHtmlEntities(input: string): string;
3
+ /** A name for storage: entities decoded, whitespace collapsed, trimmed. */
4
+ export declare function cleanName(input: unknown): string;
5
+ /** A non-empty string, or null. */
6
+ export declare function optionalString(input: unknown): string | null;
@@ -0,0 +1,79 @@
1
+ export declare interface CatalogImage {
2
+ src: string
3
+ alt: string | null
4
+ }
5
+ export declare interface CatalogCategory {
6
+ name: string
7
+ slug: string
8
+ }
9
+ export declare interface CatalogVariant {
10
+ externalId: string
11
+ title: string
12
+ sku: string | null
13
+ priceMinor: number | null
14
+ compareAtMinor: number | null
15
+ available: boolean
16
+ inventory: number | null
17
+ optionValues: string[]
18
+ }
19
+ export declare interface CatalogProduct {
20
+ source: CatalogSourceName
21
+ externalId: string
22
+ name: string
23
+ handle: string
24
+ descriptionHtml: string | null
25
+ vendor: string | null
26
+ categories: CatalogCategory[]
27
+ tags: string[]
28
+ currency: string | null
29
+ images: CatalogImage[]
30
+ optionNames: string[]
31
+ variants: CatalogVariant[]
32
+ hasOptions: boolean
33
+ url: string | null
34
+ }
35
+ /** A non-fatal problem found while mapping or fetching. */
36
+ export declare interface CatalogWarning {
37
+ externalId?: string
38
+ message: string
39
+ }
40
+ /** A source payload mapped to a product, with what the mapping noticed. */
41
+ export declare interface MappedProduct {
42
+ product: CatalogProduct
43
+ warnings: CatalogWarning[]
44
+ }
45
+ /** One page of a source's catalog, already normalized. */
46
+ export declare interface CatalogPage {
47
+ url: string
48
+ products: CatalogProduct[]
49
+ warnings: CatalogWarning[]
50
+ }
51
+ export declare interface CatalogFetchOptions {
52
+ limit?: number
53
+ fetch?: FetchLike
54
+ currency?: string
55
+ accessKey?: string
56
+ }
57
+ /**
58
+ * A source of catalog data. Implement this to add a platform: map its payload
59
+ * to `CatalogProduct` in a pure function, and page through it here.
60
+ */
61
+ export declare interface CatalogAdapter {
62
+ name: CatalogSourceName
63
+ pages: (storeUrl: string, options?: CatalogFetchOptions) => AsyncIterable<CatalogPage>
64
+ }
65
+ /**
66
+ * The normalized catalog every import source maps into.
67
+ *
68
+ * Each source (Shopify, WooCommerce, and whatever comes next) is an adapter
69
+ * that turns its own payloads into these records. The writer only ever sees
70
+ * this shape, so adding a source never touches the code that talks to the
71
+ * database, and the mapping is testable from recorded JSON with no I/O.
72
+ *
73
+ * Money is integer minor units of `currency` throughout. A source that hands
74
+ * out decimal strings converts at its own boundary (see `money.ts`).
75
+ */
76
+ /** A source registered with the importer. */
77
+ export type CatalogSourceName = 'shopify' | 'woocommerce' | 'shopware' | (string & {});
78
+ /** `fetch`, narrowed to what the importer uses, so tests can inject one. */
79
+ export type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;
@@ -0,0 +1,62 @@
1
+ import { slug } from '@stacksjs/strings';
2
+ import type { CatalogAdapter, MappedProduct } from './types';
3
+ /**
4
+ * Store API prices as minor units of the currency's ISO precision.
5
+ *
6
+ * `prices.price` is already an integer, but at the STORE's configured decimals
7
+ * (`currency_minor_unit`), which a merchant can set to anything. A sale price
8
+ * makes the regular price the compare-at price.
9
+ */
10
+ export declare function wooPrices(prices: WooPricesPayload | null | undefined): Prices;
11
+ declare function terms(list: WooTermPayload[] | null | undefined): Array<{ name: string, slug: string }>;
12
+ /** One Store API product, plus its fetched variations, as a catalog product. Pure. */
13
+ export declare function mapWooProduct(raw: WooProductPayload, context?: WooMapContext): MappedProduct;
14
+ export declare const wooCommerceAdapter: CatalogAdapter;
15
+ export declare interface WooPricesPayload {
16
+ price?: string | null
17
+ regular_price?: string | null
18
+ sale_price?: string | null
19
+ currency_code?: string | null
20
+ currency_minor_unit?: number | string | null
21
+ }
22
+ export declare interface WooTermPayload {
23
+ id?: number
24
+ name?: string | null
25
+ slug?: string | null
26
+ }
27
+ export declare interface WooAttributePayload {
28
+ id?: number
29
+ name?: string | null
30
+ taxonomy?: string | null
31
+ has_variations?: boolean
32
+ terms?: WooTermPayload[] | null
33
+ }
34
+ export declare interface WooProductPayload {
35
+ id: number | string
36
+ name?: string | null
37
+ slug?: string | null
38
+ type?: string | null
39
+ parent?: number | null
40
+ permalink?: string | null
41
+ sku?: string | null
42
+ description?: string | null
43
+ short_description?: string | null
44
+ prices?: WooPricesPayload | null
45
+ images?: Array<{ src?: string | null, alt?: string | null }> | null
46
+ categories?: WooTermPayload[] | null
47
+ tags?: WooTermPayload[] | null
48
+ brands?: WooTermPayload[] | null
49
+ attributes?: WooAttributePayload[] | null
50
+ variations?: Array<{ id: number | string, attributes?: Array<{ name?: string | null, value?: string | null }> | null }> | null
51
+ is_in_stock?: boolean | null
52
+ is_purchasable?: boolean | null
53
+ low_stock_remaining?: number | null
54
+ }
55
+ export declare interface WooMapContext {
56
+ variations?: Map<string, WooProductPayload>
57
+ }
58
+ declare interface Prices {
59
+ currency: string | null
60
+ priceMinor: number | null
61
+ compareAtMinor: number | null
62
+ }
@@ -0,0 +1,202 @@
1
+ import type { CatalogAdapter, CatalogFetchOptions, CatalogProduct, CatalogVariant, CatalogWarning } from './types';
2
+ /** The lowest price among a product's priced variants. */
3
+ export declare function lowestPrice(variants: CatalogVariant[]): number | null;
4
+ /** Total stock, or null unless every variant reports its count. */
5
+ export declare function totalInventory(variants: CatalogVariant[]): number | null;
6
+ /**
7
+ * The SKU each of a product's variants may write, keyed by variant id.
8
+ *
9
+ * `product_variants.sku` is unique per product, but no source enforces that:
10
+ * Shopify accepts the same SKU on every variant. The first variant to carry a
11
+ * SKU keeps it; a later duplicate, or a SKU too long for the column, is
12
+ * written as no SKU and reported, rather than failing the whole product.
13
+ */
14
+ export declare function variantSkus(product: CatalogProduct): { skus: Map<string, string | null>, warnings: CatalogWarning[] };
15
+ /** The `products` row for a catalog product. Pure. */
16
+ export declare function productRow(product: CatalogProduct, host: string, refs: ProductRefs, now: string, mode: 'create' | 'update'): ProductRow;
17
+ /**
18
+ * The `product_variants` row for one variant of a catalog product. Pure.
19
+ *
20
+ * `sku` is the SKU this variant may claim (see `variantSkus`), which can be
21
+ * null even when the source reported one.
22
+ */
23
+ export declare function variantRow(product: CatalogProduct, variant: CatalogVariant, host: string, productId: number, now: string, mode: 'create' | 'update', sku?: string | null): VariantRow;
24
+ /**
25
+ * Fetch a store's catalog through `adapter` and upsert it via `repository`.
26
+ *
27
+ * Idempotent: products and variants are matched on their derived `uuid`,
28
+ * categories on slug (then name) and manufacturers on name, so a second run
29
+ * over an unchanged store creates nothing.
30
+ */
31
+ export declare function importCatalog(options: ImportCatalogOptions): Promise<ImportResult>;
32
+ /**
33
+ * A repository over the commerce tables, via the query builder.
34
+ *
35
+ * Inserts are read back by `uuid`, which works on every dialect: Postgres
36
+ * reports no insert id without `RETURNING`, and a row count is never an id.
37
+ */
38
+ export declare function createDatabaseRepository(connection?: any): CatalogRepository;
39
+ /**
40
+ * A repository that keeps rows in memory. Backs the importer's own tests and
41
+ * a `--dry-run` with no database to compare against.
42
+ */
43
+ export declare function createMemoryRepository(): CatalogRepository & { tables: Record<string, Array<Record<string, any>>> };
44
+ /**
45
+ * Reads through to `inner`, writes nothing. What `--dry-run` uses, so its
46
+ * report says "create" or "update" exactly as a real run would.
47
+ */
48
+ export declare function createReadOnlyRepository(inner: CatalogRepository): CatalogRepository;
49
+ /** Manufacturers require a country; the source never says which. */
50
+ export declare const UNKNOWN_COUNTRY: 'Unknown';
51
+ /** `product_variants.sku` is a 100-character column (see the model). */
52
+ export declare const SKU_MAX: 100;
53
+ /**
54
+ * Writing a normalized catalog into the built-in commerce tables.
55
+ *
56
+ * Where each field lands, given the columns those tables actually have:
57
+ *
58
+ * | Source | Column |
59
+ * |-----------------------------|------------------------------------------------|
60
+ * | product id + store host | `products.uuid` (UUIDv5, see `identity.ts`) |
61
+ * | title / name | `products.name` |
62
+ * | description (HTML) | `products.description`, unchanged |
63
+ * | lowest variant price | `products.price`, integer minor units |
64
+ * | first image URL | `products.image_url` (never downloaded) |
65
+ * | any variant available | `products.is_available` |
66
+ * | summed stock, when exposed | `products.inventory_count` |
67
+ * | first product type/category | `categories` by slug, then `category_id` |
68
+ * | vendor / brand | `manufacturers` by name, then `manufacturer_id` |
69
+ * | each real option variant | `product_variants` (uuid from the variant id) |
70
+ * | variant SKU | `product_variants.sku`, unique per product |
71
+ * | variant price / compare-at | `product_variants.price` / `compare_at_price` |
72
+ * | variant stock, when exposed | `product_variants.inventory_count` |
73
+ *
74
+ * Variant money is integer minor units, like `products.price`. A NULL variant
75
+ * `price` means "inherits the product price"; a NULL `inventory_count` means
76
+ * stock is not tracked. `options` holds the option values as the JSON string
77
+ * array the dashboard reads. A variant's `description` is never written: the
78
+ * source has no per-variant description, so the column is the merchant's.
79
+ *
80
+ * Re-running updates the rows a previous run wrote (matched on `uuid`) and
81
+ * leaves alone the columns an operator owns: `preparation_time`, `allergens`,
82
+ * `nutritional_info`, a variant's `description`, and a product's or variant's
83
+ * `inventory_count` or a variant's `sku` when the source does not report one.
84
+ *
85
+ * The source wins a SKU. Before a product's variants are updated, the SKUs it
86
+ * is about to write are released from whichever of that product's variants
87
+ * holds them (see `releaseVariantSkus`), so a SKU that moved between variants
88
+ * at the source, or still sits on a variant the source has since deleted,
89
+ * cannot trip the per-product unique index halfway through the product.
90
+ */
91
+ export declare interface ProductRow {
92
+ uuid: string
93
+ name: string
94
+ description: string | null
95
+ price: number
96
+ image_url: string | null
97
+ is_available: boolean
98
+ inventory_count?: number
99
+ preparation_time?: number
100
+ category_id?: number | null
101
+ manufacturer_id?: number | null
102
+ created_at?: string
103
+ updated_at: string
104
+ }
105
+ export declare interface VariantRow {
106
+ uuid: string
107
+ product_id: number
108
+ variant: string
109
+ type: string
110
+ options: string
111
+ status: 'active' | 'inactive'
112
+ sku?: string | null
113
+ price: number | null
114
+ compare_at_price: number | null
115
+ inventory_count?: number | null
116
+ created_at?: string
117
+ updated_at: string
118
+ }
119
+ export declare interface CategoryRow {
120
+ uuid: string
121
+ name: string
122
+ slug: string
123
+ description: null
124
+ image_url: null
125
+ is_active: boolean
126
+ parent_category_id: null
127
+ display_order: number
128
+ created_at: string
129
+ updated_at: string
130
+ }
131
+ export declare interface ManufacturerRow {
132
+ uuid: string
133
+ manufacturer: string
134
+ description: null
135
+ country: string
136
+ featured: boolean
137
+ created_at: string
138
+ updated_at: string
139
+ }
140
+ /**
141
+ * Where the importer reads and writes. The database implementation is below;
142
+ * tests and `--dry-run` use the in-memory and read-only ones.
143
+ */
144
+ export declare interface CatalogRepository {
145
+ findCategory: (slug: string, name: string) => Promise<number | undefined>
146
+ createCategory: (row: CategoryRow) => Promise<number>
147
+ findManufacturer: (name: string) => Promise<number | undefined>
148
+ createManufacturer: (row: ManufacturerRow) => Promise<number>
149
+ findProducts: (uuids: string[]) => Promise<Map<string, number>>
150
+ createProduct: (row: ProductRow) => Promise<number>
151
+ updateProduct: (id: number, row: Omit<ProductRow, 'uuid'>) => Promise<void>
152
+ findVariants: (uuids: string[]) => Promise<Map<string, number>>
153
+ createVariant: (row: VariantRow) => Promise<number>
154
+ updateVariant: (id: number, row: Omit<VariantRow, 'uuid'>) => Promise<void>
155
+ releaseVariantSkus: (productId: number, skus: string[]) => Promise<void>
156
+ transaction?: <T>(work: (repository: CatalogRepository) => Promise<T>) => Promise<T>
157
+ }
158
+ export declare interface ProductRefs {
159
+ categoryId: number | null
160
+ manufacturerId: number | null
161
+ }
162
+ export declare interface ImportedProduct {
163
+ action: ImportAction
164
+ externalId: string
165
+ name: string
166
+ handle: string
167
+ priceMinor: number | null
168
+ currency: string | null
169
+ variants: { create: number, update: number }
170
+ category: string | null
171
+ vendor: string | null
172
+ imageCount: number
173
+ }
174
+ export declare interface ImportCounts {
175
+ created: number
176
+ updated: number
177
+ }
178
+ export declare interface ImportResult {
179
+ source: string
180
+ storeUrl: string
181
+ dryRun: boolean
182
+ pages: string[]
183
+ products: ImportedProduct[]
184
+ counts: {
185
+ products: ImportCounts
186
+ variants: ImportCounts
187
+ categories: ImportCounts
188
+ manufacturers: ImportCounts
189
+ duplicates: number
190
+ }
191
+ currencies: string[]
192
+ warnings: CatalogWarning[]
193
+ }
194
+ export declare interface ImportCatalogOptions extends CatalogFetchOptions {
195
+ adapter: CatalogAdapter
196
+ storeUrl: string
197
+ repository: CatalogRepository
198
+ dryRun?: boolean
199
+ onProduct?: (product: ImportedProduct) => void
200
+ now?: () => Date
201
+ }
202
+ export type ImportAction = 'create' | 'update';
package/dist/index.d.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  import * as auctions from './auctions/index';
2
2
  import * as carts from './carts/index';
3
+ import * as catalogImport from './imports/index';
3
4
  import * as coupons from './coupons/index';
4
5
  import * as customers from './customers/index';
5
6
  import * as devices from './devices/index';
6
7
  import * as errors from './errors/index';
7
8
  import * as giftCards from './gift-cards/index';
9
+ import * as money from './money';
8
10
  import * as orders from './orders/index';
9
11
  import * as payments from './payments/index';
10
12
  import * as products from './products/index';
@@ -13,6 +15,7 @@ import * as restaurant from './waitlists/restaurant/index';
13
15
  import * as shippings from './shippings/index';
14
16
  import * as tax from './tax/index';
15
17
  import * as waitlists from './waitlists/index';
18
+ export type { MoneyInputOptions } from './money';
16
19
  export declare const commerce: CommerceNamespace;
17
20
  export declare interface CommerceNamespace {
18
21
  auctions: AuctionsModule
@@ -46,14 +49,18 @@ declare type TaxModule = typeof tax;
46
49
  declare type WaitlistsModule = typeof waitlists;
47
50
  declare type DevicesModule = typeof devices;
48
51
  declare type ReceiptsModule = typeof receipts;
52
+ // Catalog import (Shopify, WooCommerce) is a tool rather than a store module,
53
+ // so it is exported beside the namespace rather than inside it.
49
54
  export {
50
55
  auctions,
51
56
  carts,
57
+ catalogImport,
52
58
  coupons,
53
59
  customers,
54
60
  devices,
55
61
  errors,
56
62
  giftCards,
63
+ money,
57
64
  orders,
58
65
  payments,
59
66
  products,
@@ -63,4 +70,20 @@ export {
63
70
  tax,
64
71
  waitlists,
65
72
  };
73
+ // Money conversion at the display and input boundary: stored amounts are
74
+ // integer minor units everywhere (stacksjs/stacks#2851). Browser code imports
75
+ // the same functions from `@stacksjs/commerce/money`, which pulls in nothing else.
76
+ export {
77
+ currencyExponent,
78
+ decimalToMinor,
79
+ formatCurrency,
80
+ formatMinor,
81
+ minorToDecimal,
82
+ minorToInput,
83
+ minorToMajor,
84
+ moneyInputError,
85
+ moneyInputStep,
86
+ parseMoneyInput,
87
+ PriceFormatError,
88
+ } from './money';
66
89
  export default commerce;