@webround/commerce 0.0.0-stage → 0.3.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 (138) hide show
  1. package/README.md +192 -2
  2. package/dist/account/account.d.ts +54 -0
  3. package/dist/account/account.js +40 -0
  4. package/dist/account/index.d.ts +3 -0
  5. package/dist/account/index.js +2 -0
  6. package/dist/addresses/api.d.ts +16 -0
  7. package/dist/addresses/api.js +13 -0
  8. package/dist/addresses/book.d.ts +40 -0
  9. package/dist/addresses/book.js +144 -0
  10. package/dist/addresses/index.d.ts +8 -0
  11. package/dist/addresses/index.js +5 -0
  12. package/dist/addresses/rules.d.ts +4 -0
  13. package/dist/addresses/rules.js +9 -0
  14. package/dist/addresses/types.d.ts +48 -0
  15. package/dist/addresses/types.js +6 -0
  16. package/dist/auth/api.d.ts +43 -0
  17. package/dist/auth/api.js +22 -0
  18. package/dist/auth/auth.d.ts +32 -0
  19. package/dist/auth/auth.js +23 -0
  20. package/dist/auth/cookies.d.ts +46 -0
  21. package/dist/auth/cookies.js +156 -0
  22. package/dist/auth/index.d.ts +14 -0
  23. package/dist/auth/index.js +8 -0
  24. package/dist/auth/renewal.d.ts +4 -0
  25. package/dist/auth/renewal.js +37 -0
  26. package/dist/auth/store.d.ts +70 -0
  27. package/dist/auth/store.js +284 -0
  28. package/dist/auth/types.d.ts +105 -0
  29. package/dist/auth/types.js +5 -0
  30. package/dist/auth/wake.d.ts +18 -0
  31. package/dist/auth/wake.js +23 -0
  32. package/dist/cache.d.ts +49 -0
  33. package/dist/cache.js +144 -0
  34. package/dist/canonical.d.ts +7 -0
  35. package/dist/canonical.js +32 -0
  36. package/dist/cart/api.d.ts +25 -0
  37. package/dist/cart/api.js +16 -0
  38. package/dist/cart/index.d.ts +10 -0
  39. package/dist/cart/index.js +6 -0
  40. package/dist/cart/lines.d.ts +15 -0
  41. package/dist/cart/lines.js +23 -0
  42. package/dist/cart/storage.d.ts +30 -0
  43. package/dist/cart/storage.js +82 -0
  44. package/dist/cart/store.d.ts +58 -0
  45. package/dist/cart/store.js +218 -0
  46. package/dist/cart/types.d.ts +194 -0
  47. package/dist/cart/types.js +11 -0
  48. package/dist/catalog/cached.d.ts +47 -0
  49. package/dist/catalog/cached.js +52 -0
  50. package/dist/catalog/catalog.d.ts +10 -0
  51. package/dist/catalog/catalog.js +7 -0
  52. package/dist/catalog/named.d.ts +3 -0
  53. package/dist/catalog/named.js +7 -0
  54. package/dist/catalog/products.d.ts +28 -0
  55. package/dist/catalog/products.js +12 -0
  56. package/dist/catalog/store-data.d.ts +13 -0
  57. package/dist/catalog/store-data.js +6 -0
  58. package/dist/catalog/types.d.ts +430 -0
  59. package/dist/catalog/types.js +4 -0
  60. package/dist/checkout/api.d.ts +14 -0
  61. package/dist/checkout/api.js +7 -0
  62. package/dist/checkout/errors.d.ts +23 -0
  63. package/dist/checkout/errors.js +18 -0
  64. package/dist/checkout/index.d.ts +10 -0
  65. package/dist/checkout/index.js +6 -0
  66. package/dist/checkout/return-urls.d.ts +7 -0
  67. package/dist/checkout/return-urls.js +11 -0
  68. package/dist/checkout/store.d.ts +98 -0
  69. package/dist/checkout/store.js +179 -0
  70. package/dist/checkout/types.d.ts +225 -0
  71. package/dist/checkout/types.js +28 -0
  72. package/dist/commerce.d.ts +55 -0
  73. package/dist/commerce.js +55 -0
  74. package/dist/customer-resources.d.ts +18 -0
  75. package/dist/customer-resources.js +40 -0
  76. package/dist/errors.d.ts +65 -0
  77. package/dist/errors.js +96 -0
  78. package/dist/http.d.ts +49 -0
  79. package/dist/http.js +94 -0
  80. package/dist/index.d.ts +32 -0
  81. package/dist/index.js +22 -0
  82. package/dist/orders/api.d.ts +16 -0
  83. package/dist/orders/api.js +13 -0
  84. package/dist/orders/index.d.ts +6 -0
  85. package/dist/orders/index.js +3 -0
  86. package/dist/orders/orders.d.ts +19 -0
  87. package/dist/orders/orders.js +18 -0
  88. package/dist/orders/types.d.ts +88 -0
  89. package/dist/orders/types.js +4 -0
  90. package/dist/resource.d.ts +19 -0
  91. package/dist/resource.js +15 -0
  92. package/dist/returns/api.d.ts +27 -0
  93. package/dist/returns/api.js +19 -0
  94. package/dist/returns/index.d.ts +6 -0
  95. package/dist/returns/index.js +3 -0
  96. package/dist/returns/returns.d.ts +30 -0
  97. package/dist/returns/returns.js +31 -0
  98. package/dist/returns/types.d.ts +99 -0
  99. package/dist/returns/types.js +2 -0
  100. package/dist/reviews/api.d.ts +19 -0
  101. package/dist/reviews/api.js +15 -0
  102. package/dist/reviews/index.d.ts +9 -0
  103. package/dist/reviews/index.js +5 -0
  104. package/dist/reviews/reviews.d.ts +23 -0
  105. package/dist/reviews/reviews.js +37 -0
  106. package/dist/reviews/rules.d.ts +8 -0
  107. package/dist/reviews/rules.js +7 -0
  108. package/dist/reviews/types.d.ts +54 -0
  109. package/dist/reviews/types.js +10 -0
  110. package/dist/shop/address-store.d.ts +14 -0
  111. package/dist/shop/address-store.js +28 -0
  112. package/dist/shop/address.d.ts +12 -0
  113. package/dist/shop/address.js +95 -0
  114. package/dist/shop/index.d.ts +16 -0
  115. package/dist/shop/index.js +9 -0
  116. package/dist/shop/question.d.ts +15 -0
  117. package/dist/shop/question.js +12 -0
  118. package/dist/shop/range.d.ts +7 -0
  119. package/dist/shop/range.js +38 -0
  120. package/dist/shop/sort.d.ts +19 -0
  121. package/dist/shop/sort.js +18 -0
  122. package/dist/shop/state.d.ts +47 -0
  123. package/dist/shop/state.js +51 -0
  124. package/dist/shop/store.d.ts +26 -0
  125. package/dist/shop/store.js +47 -0
  126. package/dist/shop/to-search.d.ts +14 -0
  127. package/dist/shop/to-search.js +53 -0
  128. package/dist/store-kit.d.ts +39 -0
  129. package/dist/store-kit.js +81 -0
  130. package/dist/wishlist/api.d.ts +12 -0
  131. package/dist/wishlist/api.js +10 -0
  132. package/dist/wishlist/index.d.ts +7 -0
  133. package/dist/wishlist/index.js +4 -0
  134. package/dist/wishlist/types.d.ts +30 -0
  135. package/dist/wishlist/types.js +4 -0
  136. package/dist/wishlist/wishlist.d.ts +45 -0
  137. package/dist/wishlist/wishlist.js +84 -0
  138. package/package.json +39 -5
@@ -0,0 +1,65 @@
1
+ export type CommerceErrorKind =
2
+ /** The server answered with an error status. */
3
+ 'http'
4
+ /** There was no answer: no connection, DNS, CORS, the server down. */
5
+ | 'network'
6
+ /** There was no answer in time (`timeoutMs`). */
7
+ | 'timeout'
8
+ /** The caller cancelled the request (its own `signal`). */
9
+ | 'aborted'
10
+ /** The answer was not what a store sends (not JSON). */
11
+ | 'parse';
12
+ export interface CommerceErrorInit {
13
+ kind: CommerceErrorKind;
14
+ message: string;
15
+ status?: number | null;
16
+ code?: string | null;
17
+ meta?: unknown;
18
+ issues?: readonly unknown[] | null;
19
+ retryAfterSeconds?: number | null;
20
+ method?: string;
21
+ path?: string;
22
+ cause?: unknown;
23
+ }
24
+ export declare class CommerceError extends Error {
25
+ readonly kind: CommerceErrorKind;
26
+ /** The HTTP status, when the server answered at all. */
27
+ readonly status: number | null;
28
+ /** The server's own code (`INVALID_CREDENTIALS`, `CART_CURRENCY_MISMATCH`…): null when it gave none. What the interface translates. */
29
+ readonly code: string | null;
30
+ /** What the server added to the code, as it sent it. */
31
+ readonly meta: unknown;
32
+ /** The fields a validation refused, as the server listed them. */
33
+ readonly issues: readonly unknown[] | null;
34
+ /** How long to wait before asking again, when the server said (a rate limit). */
35
+ readonly retryAfterSeconds: number | null;
36
+ readonly method: string | null;
37
+ readonly path: string | null;
38
+ readonly cause?: unknown;
39
+ constructor(init: CommerceErrorInit);
40
+ }
41
+ export declare const isCommerceError: (value: unknown) => value is CommerceError;
42
+ /**
43
+ * The error of an answer with an error status. The stores send it in a few shapes, and this reads them all the same way:
44
+ * - commerce-core: `{ message, code | null, meta | null, statusCode }`, whose `message` is a list of `{ path, message, … }` for a validation (the fields it refused)
45
+ * - a validation: `{ message: 'Validation failed', issues: [...] }`
46
+ * - the catalog's rate limit: `{ statusCode: 429, error, message, details: { retryAfterSeconds } }`
47
+ * - what Nest itself says: `{ statusCode, message: string | string[], error }`
48
+ * `body` is the parsed JSON, or null when it was not JSON (a proxy's HTML page): the status then says what happened.
49
+ */
50
+ export declare function commerceErrorFromResponse(input: {
51
+ status: number;
52
+ body: unknown;
53
+ retryAfterHeader?: string | null;
54
+ method: string;
55
+ path: string;
56
+ }): CommerceError;
57
+ /** What went wrong, as the one error of the package: a `CommerceError` is as it is; anything else (a failure that is not the store's answer) is "the store could not be reached", with the cause kept. */
58
+ export declare const toCommerceError: (error: unknown) => CommerceError;
59
+ /**
60
+ * The limit a store puts on how much a customer may keep (addresses, wishlist items: 5, 50…), when `error` is that store refusing to go over it, and null when it is anything else. Today the store answers it with a 429, like
61
+ * a rate limit, and with no code: what tells it apart is the message ("Too many store_customer_addresss. Limit is 5."), where a rate limit says per how long ("Limit is 5 in 86400 seconds") and may say when to ask
62
+ * again. A store that gives it a code of its own (`DATA_LIMIT_REACHED`, with `meta.limit`) is read by that, which is what an interface translates.
63
+ */
64
+ export declare function dataLimitOf(error: unknown): number | null;
65
+ //# sourceMappingURL=errors.d.ts.map
package/dist/errors.js ADDED
@@ -0,0 +1,96 @@
1
+ // The one error of @webround/commerce. Whatever goes wrong talking to a store (it answered no, it did not answer, the answer made no sense, the caller gave up), the
2
+ // caller gets this, with what a screen needs to say something useful: the server's own `code` (a stable word, for the interface to translate), its `message`
3
+ // (a fallback to show when there is no translation), and what the kind of failure adds (the fields of a refused form, how long to wait).
4
+ //
5
+ // It holds the method and the path, never the headers, the body or the query: an error is logged and shown, and a token or a password must never be in one.
6
+ export class CommerceError extends Error {
7
+ constructor(init) {
8
+ super(init.message);
9
+ this.name = 'CommerceError';
10
+ this.kind = init.kind;
11
+ this.status = init.status ?? null;
12
+ this.code = init.code ?? null;
13
+ this.meta = init.meta ?? null;
14
+ this.issues = init.issues ?? null;
15
+ this.retryAfterSeconds = init.retryAfterSeconds ?? null;
16
+ this.method = init.method ?? null;
17
+ this.path = init.path ?? null;
18
+ if (init.cause !== undefined)
19
+ this.cause = init.cause;
20
+ }
21
+ }
22
+ export const isCommerceError = (value) => value instanceof CommerceError;
23
+ const isObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
24
+ /** The text a body says its message is: a string, or a list (of strings, or of objects that have a `message`) joined; undefined when it says none. */
25
+ function messageOf(body) {
26
+ const { message } = body;
27
+ if (typeof message === 'string' && message !== '')
28
+ return message;
29
+ if (Array.isArray(message)) {
30
+ const parts = message
31
+ .map((part) => (typeof part === 'string' ? part : isObject(part) && typeof part.message === 'string' ? part.message : ''))
32
+ .filter((part) => part !== '');
33
+ if (parts.length > 0)
34
+ return parts.join(', ');
35
+ }
36
+ return undefined;
37
+ }
38
+ /**
39
+ * The fields a validation refused: `issues` when the body has them (the catalog's), or the `message` when it is a list of objects that each say which field (`path`), as commerce-core's validation sends
40
+ * it. A message that is a list of texts says no field, so it is no issue.
41
+ */
42
+ function issuesOf(body) {
43
+ if (Array.isArray(body.issues))
44
+ return body.issues;
45
+ const { message } = body;
46
+ return Array.isArray(message) && message.length > 0 && message.every((part) => isObject(part) && Array.isArray(part.path)) ? message : null;
47
+ }
48
+ /** Whole seconds, from a number or a header's text (`Retry-After: 30`); null for anything else (a date, a negative, nonsense). */
49
+ function secondsOf(value) {
50
+ const seconds = typeof value === 'string' && /^\d+$/.test(value.trim()) ? Number(value) : value;
51
+ return typeof seconds === 'number' && Number.isFinite(seconds) && seconds >= 0 ? Math.ceil(seconds) : null;
52
+ }
53
+ /**
54
+ * The error of an answer with an error status. The stores send it in a few shapes, and this reads them all the same way:
55
+ * - commerce-core: `{ message, code | null, meta | null, statusCode }`, whose `message` is a list of `{ path, message, … }` for a validation (the fields it refused)
56
+ * - a validation: `{ message: 'Validation failed', issues: [...] }`
57
+ * - the catalog's rate limit: `{ statusCode: 429, error, message, details: { retryAfterSeconds } }`
58
+ * - what Nest itself says: `{ statusCode, message: string | string[], error }`
59
+ * `body` is the parsed JSON, or null when it was not JSON (a proxy's HTML page): the status then says what happened.
60
+ */
61
+ export function commerceErrorFromResponse(input) {
62
+ const { status, body, method, path } = input;
63
+ const fields = isObject(body) ? body : {};
64
+ const details = isObject(fields.details) ? fields.details : {};
65
+ return new CommerceError({
66
+ kind: 'http',
67
+ status,
68
+ message: messageOf(fields) ?? `The store answered ${status}`,
69
+ code: typeof fields.code === 'string' && fields.code !== '' ? fields.code : null,
70
+ meta: fields.meta ?? null,
71
+ issues: issuesOf(fields),
72
+ retryAfterSeconds: secondsOf(details.retryAfterSeconds) ?? secondsOf(input.retryAfterHeader),
73
+ method,
74
+ path,
75
+ });
76
+ }
77
+ /** What went wrong, as the one error of the package: a `CommerceError` is as it is; anything else (a failure that is not the store's answer) is "the store could not be reached", with the cause kept. */
78
+ export const toCommerceError = (error) => (isCommerceError(error) ? error : new CommerceError({ kind: 'network', message: 'The store could not be reached.', cause: error }));
79
+ /**
80
+ * The limit a store puts on how much a customer may keep (addresses, wishlist items: 5, 50…), when `error` is that store refusing to go over it, and null when it is anything else. Today the store answers it with a 429, like
81
+ * a rate limit, and with no code: what tells it apart is the message ("Too many store_customer_addresss. Limit is 5."), where a rate limit says per how long ("Limit is 5 in 86400 seconds") and may say when to ask
82
+ * again. A store that gives it a code of its own (`DATA_LIMIT_REACHED`, with `meta.limit`) is read by that, which is what an interface translates.
83
+ */
84
+ export function dataLimitOf(error) {
85
+ if (!isCommerceError(error) || error.kind !== 'http')
86
+ return null;
87
+ if (error.code === 'DATA_LIMIT_REACHED') {
88
+ const limit = error.meta?.limit;
89
+ return typeof limit === 'number' ? limit : null;
90
+ }
91
+ if (error.status !== 429 || error.retryAfterSeconds !== null)
92
+ return null;
93
+ const found = /^Too many [\w-]+\. Limit is (\d+)\.?$/.exec(error.message.trim());
94
+ return found ? Number(found[1]) : null;
95
+ }
96
+ //# sourceMappingURL=errors.js.map
package/dist/http.d.ts ADDED
@@ -0,0 +1,49 @@
1
+ export type QueryValue = string | number | boolean | null | undefined | readonly (string | number | boolean)[];
2
+ export type Query = Readonly<Record<string, QueryValue>>;
3
+ export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
4
+ export interface HttpClientConfig {
5
+ /** The backend's address, with or without a trailing `/`. */
6
+ baseUrl: string;
7
+ /** The store the requests are for: it goes in the `store-id` header, and no request can say another. */
8
+ storeId: string;
9
+ /** How long a request may take, answer included. Default 60 seconds. */
10
+ timeoutMs?: number;
11
+ /**
12
+ * Whether cookies travel. `omit` (the default): they do not, and nothing needs them, because everything a customer does is the JWT in `Authorization` (see `getHeaders`). The only
13
+ * cookie there is is commerce-core's refresh token, which is for getting in again (automatic login, a new JWT): a request that needs it says so (`RequestOptions.credentials`).
14
+ */
15
+ credentials?: 'omit' | 'same-origin' | 'include';
16
+ /** Headers added to EVERY request, asked for each time: the JWT (`Authorization: Bearer …`), and the CSRF token where an endpoint asks for it. */
17
+ getHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
18
+ /** Another `fetch` (a test, a runtime without a global one). */
19
+ fetch?: typeof fetch;
20
+ }
21
+ export interface RequestOptions {
22
+ method?: HttpMethod;
23
+ /** The path on the backend, with or without the leading `/`. */
24
+ path: string;
25
+ query?: Query;
26
+ /** Sent as JSON. */
27
+ body?: unknown;
28
+ /** For this request only; they win over `getHeaders`, never over `store-id`. */
29
+ headers?: Record<string, string>;
30
+ /** For this request only: `include` for the few that need the refresh token's cookie (logging in again, renewing the JWT, logging out). Wins over the client's. */
31
+ credentials?: 'omit' | 'same-origin' | 'include';
32
+ signal?: AbortSignal;
33
+ }
34
+ export interface HttpClient {
35
+ /** The backend this client talks to, without a trailing `/`. What a cache key is made of, so that it is always the key of the client that asks. */
36
+ readonly baseUrl: string;
37
+ /** The store this client is for. */
38
+ readonly storeId: string;
39
+ /** The answer's JSON (undefined for an empty one), or a `CommerceError`. */
40
+ request<T = unknown>(options: RequestOptions): Promise<T>;
41
+ }
42
+ /**
43
+ * The query as ONE text for the same query: `a=1&b=2` and `b=2&a=1` are the same query, so both come out `a=1&b=2` (keys in order); `tag=x&tag=y` and `tag=y&tag=x` are the same
44
+ * too (the values of a list are in order). A number and its text are the same (`1`, `'1'`), what is not there (null, undefined, an empty list) is left out, and a list repeats its key.
45
+ * Order is by code point, so it is the same everywhere. An API for which the order of a list matters is the API's mistake, not something to carry here.
46
+ */
47
+ export declare function queryString(query: Query | undefined): string;
48
+ export declare function createHttpClient(config: HttpClientConfig): HttpClient;
49
+ //# sourceMappingURL=http.d.ts.map
package/dist/http.js ADDED
@@ -0,0 +1,94 @@
1
+ import { CommerceError, commerceErrorFromResponse } from './errors.js';
2
+ const DEFAULT_TIMEOUT_MS = 60000;
3
+ /**
4
+ * The query as ONE text for the same query: `a=1&b=2` and `b=2&a=1` are the same query, so both come out `a=1&b=2` (keys in order); `tag=x&tag=y` and `tag=y&tag=x` are the same
5
+ * too (the values of a list are in order). A number and its text are the same (`1`, `'1'`), what is not there (null, undefined, an empty list) is left out, and a list repeats its key.
6
+ * Order is by code point, so it is the same everywhere. An API for which the order of a list matters is the API's mistake, not something to carry here.
7
+ */
8
+ export function queryString(query) {
9
+ const params = new URLSearchParams();
10
+ for (const key of Object.keys(query ?? {}).sort()) {
11
+ const value = query?.[key];
12
+ if (value === undefined || value === null)
13
+ continue;
14
+ const items = (Array.isArray(value) ? value : [value]).map(String);
15
+ for (const item of Array.isArray(value) ? items.sort() : items)
16
+ params.append(key, item);
17
+ }
18
+ return params.toString();
19
+ }
20
+ export function createHttpClient(config) {
21
+ const base = config.baseUrl.replace(/\/+$/, '');
22
+ const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
23
+ async function request(options) {
24
+ const method = options.method ?? 'GET';
25
+ const path = `/${options.path.replace(/^\/+/, '')}`;
26
+ const query = queryString(options.query);
27
+ const url = `${base}${path}${query ? `?${query}` : ''}`;
28
+ const fail = (kind, message, cause) => new CommerceError({ kind, message, method, path, cause });
29
+ if (options.signal?.aborted)
30
+ throw fail('aborted', 'The request was cancelled.');
31
+ // What the caller adds (the JWT) is asked for before the clock starts: it is not the store's time
32
+ const headers = {
33
+ Accept: 'application/json',
34
+ ...(options.body !== undefined ? { 'Content-Type': 'application/json' } : {}),
35
+ ...(await config.getHeaders?.()),
36
+ ...options.headers,
37
+ 'store-id': config.storeId,
38
+ };
39
+ const controller = new AbortController();
40
+ let timedOut = false;
41
+ const timer = setTimeout(() => { timedOut = true; controller.abort(); }, timeoutMs);
42
+ const onCallerAbort = () => controller.abort();
43
+ options.signal?.addEventListener('abort', onCallerAbort, { once: true });
44
+ try {
45
+ // The caller may have given up while its headers (the JWT) were being asked for: that is before the listener, so it is looked at again
46
+ if (options.signal?.aborted)
47
+ throw fail('aborted', 'The request was cancelled.');
48
+ const doFetch = config.fetch ?? ((input, init) => globalThis.fetch(input, init));
49
+ let response;
50
+ let text;
51
+ try {
52
+ response = await doFetch(url, {
53
+ method,
54
+ headers,
55
+ body: options.body !== undefined ? JSON.stringify(options.body) : undefined,
56
+ credentials: options.credentials ?? config.credentials ?? 'omit',
57
+ signal: controller.signal,
58
+ });
59
+ // The clock runs until the answer has been read, not only until it started to come
60
+ text = await response.text();
61
+ }
62
+ catch (err) {
63
+ if (timedOut)
64
+ throw fail('timeout', 'The store did not answer in time.', err);
65
+ if (options.signal?.aborted)
66
+ throw fail('aborted', 'The request was cancelled.', err);
67
+ throw fail('network', 'The store could not be reached.', err);
68
+ }
69
+ let body = null;
70
+ let parsed = true;
71
+ if (text !== '') {
72
+ try {
73
+ body = JSON.parse(text);
74
+ }
75
+ catch {
76
+ parsed = false;
77
+ }
78
+ }
79
+ if (!response.ok) {
80
+ // An error that is not JSON (a proxy's page) says nothing but its status
81
+ throw commerceErrorFromResponse({ status: response.status, body: parsed ? body : null, retryAfterHeader: response.headers.get('retry-after'), method, path });
82
+ }
83
+ if (!parsed)
84
+ throw new CommerceError({ kind: 'parse', message: 'The store answered with something that is not JSON.', status: response.status, method, path });
85
+ return (text === '' ? undefined : body);
86
+ }
87
+ finally {
88
+ clearTimeout(timer);
89
+ options.signal?.removeEventListener('abort', onCallerAbort);
90
+ }
91
+ }
92
+ return { baseUrl: base, storeId: config.storeId, request };
93
+ }
94
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1,32 @@
1
+ export { cacheKey, cachePrefix, createCache } from './cache.js';
2
+ export type { Cache, CacheKeyParts, CacheSnapshot, LoadOptions } from './cache.js';
3
+ export { canonicalize, canonicalJson } from './canonical.js';
4
+ export type { Canonical } from './canonical.js';
5
+ export { CommerceError, commerceErrorFromResponse, dataLimitOf, isCommerceError, toCommerceError } from './errors.js';
6
+ export type { CommerceErrorInit, CommerceErrorKind } from './errors.js';
7
+ export { createHttpClient, queryString } from './http.js';
8
+ export type { HttpClient, HttpClientConfig, HttpMethod, Query, QueryValue, RequestOptions } from './http.js';
9
+ export { createCatalog } from './catalog/catalog.js';
10
+ export type { Catalog } from './catalog/catalog.js';
11
+ export { createCachedCatalog } from './catalog/cached.js';
12
+ export type { CachedCatalog, CatalogCall, CatalogIdentity, CatalogForget, CatalogResources } from './catalog/cached.js';
13
+ export { createResourceFactory } from './resource.js';
14
+ export type { Resource, ResourceIdentity } from './resource.js';
15
+ export { createCustomerResources } from './customer-resources.js';
16
+ export type { CustomerResources } from './customer-resources.js';
17
+ export { createCommerce } from './commerce.js';
18
+ export type { Commerce, CommerceConfig } from './commerce.js';
19
+ export * from './account/index.js';
20
+ export * from './addresses/index.js';
21
+ export * from './auth/index.js';
22
+ export * from './cart/index.js';
23
+ export * from './checkout/index.js';
24
+ export * from './orders/index.js';
25
+ export * from './returns/index.js';
26
+ export * from './reviews/index.js';
27
+ export * from './wishlist/index.js';
28
+ export * from './shop/index.js';
29
+ export type { ProductsApi } from './catalog/products.js';
30
+ export type { StoreDataApi } from './catalog/store-data.js';
31
+ export type { AssetDetail, AssetSummary, AssetType, AttributeDetail, AttributeSummary, Availability, CallOptions, Format, OptionDetail, OptionValueDetail, Pagination, PriceDetail, PriceRange, ProductByIdInput, ProductBySlugInput, ProductDetail, ProductDetailInput, ProductDetailResponse, ProductLoad, ProductPage, ProductRelatedInput, ProductReviewsInput, ProductSearch, ProductSortField, ProductSummary, QuantityTier, Review, ReviewPage, ReviewStats, SortDirection, StoreAttribute, StoreCollection, StoreData, StoreDataResponse, StoreFilter, StoreFilterCondition, StoreSettings, StoreTag, StoreTagValue, TagDetail, TagSummary, TagType, TagValueDetail, TagValueSummary, ValueFormat, VariantDetail, VariantSummary, } from './catalog/types.js';
32
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,22 @@
1
+ // @webround/commerce: the logic of a Webround Commerce store (its catalog, cart, customers, checkout), with no UI and no framework.
2
+ // What a framework needs to use it comes as a package of its own (`@webround/commerce-react`, one day `@webround/commerce-next`…), never in here.
3
+ export { cacheKey, cachePrefix, createCache } from './cache.js';
4
+ export { canonicalize, canonicalJson } from './canonical.js';
5
+ export { CommerceError, commerceErrorFromResponse, dataLimitOf, isCommerceError, toCommerceError } from './errors.js';
6
+ export { createHttpClient, queryString } from './http.js';
7
+ export { createCatalog } from './catalog/catalog.js';
8
+ export { createCachedCatalog } from './catalog/cached.js';
9
+ export { createResourceFactory } from './resource.js';
10
+ export { createCustomerResources } from './customer-resources.js';
11
+ export { createCommerce } from './commerce.js';
12
+ export * from './account/index.js';
13
+ export * from './addresses/index.js';
14
+ export * from './auth/index.js';
15
+ export * from './cart/index.js';
16
+ export * from './checkout/index.js';
17
+ export * from './orders/index.js';
18
+ export * from './returns/index.js';
19
+ export * from './reviews/index.js';
20
+ export * from './wishlist/index.js';
21
+ export * from './shop/index.js';
22
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,16 @@
1
+ import type { HttpClient } from '../http.js';
2
+ import type { CallOptions } from '../catalog/types.js';
3
+ import type { AccessTokenOptions } from '../auth/api.js';
4
+ import type { Order, OrderPage, PageQuery, Subscription, SubscriptionPage } from './types.js';
5
+ export interface OrderApi {
6
+ /** `GET /store-customer-orders`: the customer's orders, a page at a time (the newest first). */
7
+ list(input?: PageQuery, options?: CallOptions & AccessTokenOptions): Promise<OrderPage>;
8
+ /** `GET /store-customer-orders/:id`: one order, with its items. 404 for one that is not the customer's. */
9
+ get(id: string, options?: CallOptions & AccessTokenOptions): Promise<Order>;
10
+ /** `GET /store-customer-orders/subscriptions`: the customer's subscriptions (what they pay for again and again), a page at a time. */
11
+ listSubscriptions(input?: PageQuery, options?: CallOptions & AccessTokenOptions): Promise<SubscriptionPage>;
12
+ /** `GET /store-customer-orders/subscriptions/:id`. */
13
+ getSubscription(id: string, options?: CallOptions & AccessTokenOptions): Promise<Subscription>;
14
+ }
15
+ export declare function createOrderApi(http: HttpClient): OrderApi;
16
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1,13 @@
1
+ // The calls of commerce-core that are about what a customer bought (`/store-customer-orders`), each asking one thing and giving back what the store answers, unchanged. All need the JWT of the customer, and all read.
2
+ const bearer = (options) => (options?.accessToken ? { Authorization: `Bearer ${options.accessToken}` } : {});
3
+ const part = (id) => encodeURIComponent(id);
4
+ const query = (q) => ({ page: q?.page, limit: q?.limit, sortBy: q?.sortBy, sortDirection: q?.sortDirection, lang: q?.lang });
5
+ export function createOrderApi(http) {
6
+ return {
7
+ list: (input, options) => http.request({ path: '/store-customer-orders', query: query(input), headers: bearer(options), signal: options?.signal }),
8
+ get: (id, options) => http.request({ path: `/store-customer-orders/${part(id)}`, headers: bearer(options), signal: options?.signal }),
9
+ listSubscriptions: (input, options) => http.request({ path: '/store-customer-orders/subscriptions', query: query(input), headers: bearer(options), signal: options?.signal }),
10
+ getSubscription: (id, options) => http.request({ path: `/store-customer-orders/subscriptions/${part(id)}`, headers: bearer(options), signal: options?.signal }),
11
+ };
12
+ }
13
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1,6 @@
1
+ export { createOrderApi } from './api.js';
2
+ export type { OrderApi } from './api.js';
3
+ export { createOrders } from './orders.js';
4
+ export type { Orders } from './orders.js';
5
+ export type { Order, OrderAddress, OrderItem, OrderPage, Page, PageInfo, PageQuery, Subscription, SubscriptionPage } from './types.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,3 @@
1
+ export { createOrderApi } from './api.js';
2
+ export { createOrders } from './orders.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,19 @@
1
+ import type { CustomerResources } from '../customer-resources.js';
2
+ import type { Resource } from '../resource.js';
3
+ import type { OrderApi } from './api.js';
4
+ import type { Order, OrderPage, PageQuery, Subscription, SubscriptionPage } from './types.js';
5
+ export interface Orders {
6
+ list(query?: PageQuery): Resource<OrderPage> | null;
7
+ get(id: string): Resource<Order> | null;
8
+ subscriptions: {
9
+ list(query?: PageQuery): Resource<SubscriptionPage> | null;
10
+ get(id: string): Resource<Subscription> | null;
11
+ };
12
+ /** Forgets what was kept of orders and subscriptions, so the next question is asked again. */
13
+ refresh(): void;
14
+ }
15
+ export declare function createOrders(options: {
16
+ api: OrderApi;
17
+ customer: CustomerResources;
18
+ }): Orders;
19
+ //# sourceMappingURL=orders.d.ts.map
@@ -0,0 +1,18 @@
1
+ export function createOrders(options) {
2
+ const { api, customer } = options;
3
+ // Nothing asked and an empty ask are the same question
4
+ const asked = (q) => q ?? {};
5
+ return {
6
+ list: (query) => customer.resource('orders.list', undefined, asked(query), (o) => api.list(query, o)),
7
+ get: (id) => customer.resource('orders.get', id, undefined, (o) => api.get(id, o)),
8
+ subscriptions: {
9
+ list: (query) => customer.resource('subscriptions.list', undefined, asked(query), (o) => api.listSubscriptions(query, o)),
10
+ get: (id) => customer.resource('subscriptions.get', id, undefined, (o) => api.getSubscription(id, o)),
11
+ },
12
+ refresh() {
13
+ for (const call of ['orders.list', 'orders.get', 'subscriptions.list', 'subscriptions.get'])
14
+ customer.forget(call);
15
+ },
16
+ };
17
+ }
18
+ //# sourceMappingURL=orders.js.map
@@ -0,0 +1,88 @@
1
+ export interface PageQuery {
2
+ page?: number;
3
+ /** 1 to 100; 25 by default. */
4
+ limit?: number;
5
+ sortBy?: string;
6
+ sortDirection?: 'asc' | 'desc';
7
+ /** The language of what is named (the products). */
8
+ lang?: string;
9
+ }
10
+ export interface PageInfo {
11
+ page: number;
12
+ limit: number;
13
+ total: number;
14
+ totalPages: number;
15
+ }
16
+ export interface Page<T> {
17
+ data: T[];
18
+ pagination: PageInfo;
19
+ }
20
+ /** An address as an order keeps it: what was given, without the defaults. */
21
+ export interface OrderAddress {
22
+ fullName: string;
23
+ company?: string | null;
24
+ phone?: string | null;
25
+ addressLine1: string;
26
+ addressLine2?: string | null;
27
+ city: string;
28
+ province?: string | null;
29
+ postalCode: string;
30
+ countryCode: string;
31
+ vatCode?: string | null;
32
+ additionalInfo?: string | null;
33
+ }
34
+ export interface OrderItem {
35
+ /** What a return request names (`items[].orderItemId`). */
36
+ orderItemId: string | null;
37
+ productId: string | null;
38
+ variantId: string | null;
39
+ variantName: string | null;
40
+ coverUrl: string | null;
41
+ quantity: number | null;
42
+ deliveryMethod: string | null;
43
+ appliedTaxRate: number | null;
44
+ unitNet: string | null;
45
+ unitGross: string | null;
46
+ unitTax: string | null;
47
+ totalNet: string | null;
48
+ totalGross: string | null;
49
+ totalTax: string | null;
50
+ }
51
+ export interface Order {
52
+ id: string;
53
+ storeId: string;
54
+ customerId: string | null;
55
+ /** The number the customer is given for the order. */
56
+ reference: number | null;
57
+ amount: string | null;
58
+ currencyCode: string;
59
+ paymentStatus: string;
60
+ shippingStatus: string | null;
61
+ stripeInvoiceUrl: string | null;
62
+ shippingTrackingUrl: string | null;
63
+ shippingAddress: OrderAddress | null;
64
+ billingAddress: OrderAddress | null;
65
+ promotionSnapshot?: unknown;
66
+ createdAt: string | null;
67
+ updatedAt: string | null;
68
+ items: OrderItem[];
69
+ }
70
+ export interface Subscription {
71
+ id: string;
72
+ stripeId: string | null;
73
+ status: string | null;
74
+ activeSince: string | null;
75
+ currentPeriodEnd: string | null;
76
+ cancelAtPeriodEnd: boolean | null;
77
+ latestInvoiceId: string | null;
78
+ latestInvoiceUrl: string | null;
79
+ trialStart: string | null;
80
+ trialEnd: string | null;
81
+ createdAt: string | null;
82
+ updatedAt: string | null;
83
+ /** The order the subscription came from. */
84
+ order: Order | null;
85
+ }
86
+ export type OrderPage = Page<Order>;
87
+ export type SubscriptionPage = Page<Subscription>;
88
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,4 @@
1
+ // What the order calls of commerce-core give, as the store says it. A customer sees their own orders and their subscriptions; nothing here is written. Dates come as text, as JSON has them; amounts as text of digits.
2
+ // (Checked against the store's code and the documented shapes: a store with orders of its own is where to hold them against real answers.)
3
+ export {};
4
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,19 @@
1
+ import { type Cache, type CacheSnapshot, type LoadOptions } from './cache.js';
2
+ import type { CallOptions } from './catalog/types.js';
3
+ /** The cache's view of ONE question: its key, how to ask it, how it stands now, and how to be told when that changes. What a framework's binding is made of. */
4
+ export interface Resource<T> {
5
+ key: string;
6
+ /** The answer: from the cache if it has it, joining the request under way if there is one, else asking. */
7
+ load(options?: LoadOptions): Promise<T>;
8
+ /** How the question stands now. It never asks anything, and is the same object until it changes. */
9
+ snapshot(): CacheSnapshot<T>;
10
+ subscribe(listener: () => void): () => void;
11
+ }
12
+ /** What tells one store's questions from another's in the cache: where the backend is, and which store. */
13
+ export type ResourceIdentity = {
14
+ baseUrl: string;
15
+ storeId: string;
16
+ };
17
+ /** Makes the resources of one backend and store, over one cache. */
18
+ export declare function createResourceFactory(cache: Cache, identity: ResourceIdentity): <T>(call: string, subject: string | undefined, params: unknown, fetch: (options: CallOptions) => Promise<T>) => Resource<T>;
19
+ //# sourceMappingURL=resource.d.ts.map
@@ -0,0 +1,15 @@
1
+ import { cacheKey } from './cache.js';
2
+ /** Makes the resources of one backend and store, over one cache. */
3
+ export function createResourceFactory(cache, identity) {
4
+ return function resource(call, subject, params, fetch) {
5
+ const key = cacheKey({ base: identity.baseUrl, storeId: identity.storeId, call, subject, params });
6
+ return {
7
+ key,
8
+ // The signal the request gets is the cache's (aborted when nobody is waiting any more); the caller's own is only for that caller
9
+ load: (options) => cache.load(key, (signal) => fetch({ signal }), options),
10
+ snapshot: () => cache.snapshot(key),
11
+ subscribe: (listener) => cache.subscribe(key, listener),
12
+ };
13
+ };
14
+ }
15
+ //# sourceMappingURL=resource.js.map
@@ -0,0 +1,27 @@
1
+ import type { HttpClient } from '../http.js';
2
+ import type { CallOptions } from '../catalog/types.js';
3
+ import type { AccessTokenOptions } from '../auth/api.js';
4
+ import type { CreateGuestReturnInput, CreateReturnInput, GuestReturnSession, GuestVerifyAnswer, GuestVerifyInput, PageQuery, ReturnRequest, ReturnRequestPage } from './types.js';
5
+ export interface ReturnApi {
6
+ /** `GET /store-customer-return-requests` (JWT): the customer's requests, a page at a time. */
7
+ list(input?: PageQuery, options?: CallOptions & AccessTokenOptions): Promise<ReturnRequestPage>;
8
+ /** `GET /store-customer-return-requests/:id` (JWT). */
9
+ get(id: string, options?: CallOptions & AccessTokenOptions): Promise<ReturnRequest>;
10
+ /** `POST /store-customer-return-requests` (JWT): asks to return some of the items of an order (`201`). The store emails the customer (and the shop). */
11
+ create(input: CreateReturnInput, options?: CallOptions & AccessTokenOptions): Promise<ReturnRequest>;
12
+ /** `DELETE /store-customer-return-requests/:id` (JWT): cancels a request that is still `pending` (400 for any other); gives back the one it cancelled. */
13
+ cancel(id: string, options?: CallOptions & AccessTokenOptions): Promise<ReturnRequest>;
14
+ /**
15
+ * `POST …/guest/verify` (no JWT): asks the store to email a link to the address of the order, for a guest. It answers the same whether the order exists or not. An order that belongs to an account is refused
16
+ * (400): its customer has to log in.
17
+ */
18
+ verifyGuest(input: GuestVerifyInput, options?: CallOptions): Promise<GuestVerifyAnswer>;
19
+ /** `GET …/guest/session?token=` (no JWT): the order the link's token opens. 401 for a token that is not good or has ended. */
20
+ guestSession(token: string, options?: CallOptions): Promise<GuestReturnSession>;
21
+ /** `POST …/guest` (no JWT, the token in the body): a guest asks to return some of the items of their order (`201`). */
22
+ createGuest(input: CreateGuestReturnInput, options?: CallOptions): Promise<ReturnRequest>;
23
+ /** `DELETE …/guest/:id?token=` (no JWT): cancels a guest's request that is still `pending`. */
24
+ cancelGuest(id: string, token: string, options?: CallOptions): Promise<ReturnRequest>;
25
+ }
26
+ export declare function createReturnApi(http: HttpClient): ReturnApi;
27
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1,19 @@
1
+ // The calls of commerce-core that are about returns (`/store-customer-return-requests`), each asking one thing and giving back what the store answers, unchanged. A customer's need their JWT. A guest's need none: what proves
2
+ // who they are is a TOKEN, in the link the store emailed to the address of the order (`guest/verify` asks for the email; the site's page `/returns?token=…` is where the link leads, and reads the token from the address).
3
+ const PATH = '/store-customer-return-requests';
4
+ const bearer = (options) => (options?.accessToken ? { Authorization: `Bearer ${options.accessToken}` } : {});
5
+ const part = (id) => encodeURIComponent(id);
6
+ const query = (q) => ({ page: q?.page, limit: q?.limit, sortBy: q?.sortBy, sortDirection: q?.sortDirection, lang: q?.lang });
7
+ export function createReturnApi(http) {
8
+ return {
9
+ list: (input, options) => http.request({ path: PATH, query: query(input), headers: bearer(options), signal: options?.signal }),
10
+ get: (id, options) => http.request({ path: `${PATH}/${part(id)}`, headers: bearer(options), signal: options?.signal }),
11
+ create: (input, options) => http.request({ method: 'POST', path: PATH, body: input, headers: bearer(options), signal: options?.signal }),
12
+ cancel: (id, options) => http.request({ method: 'DELETE', path: `${PATH}/${part(id)}`, headers: bearer(options), signal: options?.signal }),
13
+ verifyGuest: (input, options) => http.request({ method: 'POST', path: `${PATH}/guest/verify`, body: input, signal: options?.signal }),
14
+ guestSession: (token, options) => http.request({ path: `${PATH}/guest/session`, query: { token }, signal: options?.signal }),
15
+ createGuest: (input, options) => http.request({ method: 'POST', path: `${PATH}/guest`, body: input, signal: options?.signal }),
16
+ cancelGuest: (id, token, options) => http.request({ method: 'DELETE', path: `${PATH}/guest/${part(id)}`, query: { token }, signal: options?.signal }),
17
+ };
18
+ }
19
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1,6 @@
1
+ export { createReturnApi } from './api.js';
2
+ export type { ReturnApi } from './api.js';
3
+ export { createReturns } from './returns.js';
4
+ export type { Returns } from './returns.js';
5
+ export type { CreateGuestReturnInput, CreateReturnInput, GuestOrderItem, GuestReturnSession, GuestVerifyAnswer, GuestVerifyInput, ReturnItem, ReturnItemInput, ReturnRequest, ReturnRequestPage, ReturnStatus, } from './types.js';
6
+ //# sourceMappingURL=index.d.ts.map