@laconius/cart 0.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.
- package/LICENSE +73 -0
- package/README.md +24 -0
- package/lib/module/adapter.js +167 -0
- package/lib/module/adapter.js.map +1 -0
- package/lib/module/components.js +212 -0
- package/lib/module/components.js.map +1 -0
- package/lib/module/config.js +53 -0
- package/lib/module/config.js.map +1 -0
- package/lib/module/defaults.js +57 -0
- package/lib/module/defaults.js.map +1 -0
- package/lib/module/index.js +11 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/models.js +4 -0
- package/lib/module/models.js.map +1 -0
- package/lib/module/normalizer.js +36 -0
- package/lib/module/normalizer.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/queries.js +287 -0
- package/lib/module/queries.js.map +1 -0
- package/lib/module/store.js +42 -0
- package/lib/module/store.js.map +1 -0
- package/lib/module/translations.js +31 -0
- package/lib/module/translations.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/adapter.d.ts +25 -0
- package/lib/typescript/src/adapter.d.ts.map +1 -0
- package/lib/typescript/src/components.d.ts +49 -0
- package/lib/typescript/src/components.d.ts.map +1 -0
- package/lib/typescript/src/config.d.ts +57 -0
- package/lib/typescript/src/config.d.ts.map +1 -0
- package/lib/typescript/src/defaults.d.ts +16 -0
- package/lib/typescript/src/defaults.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +10 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/models.d.ts +122 -0
- package/lib/typescript/src/models.d.ts.map +1 -0
- package/lib/typescript/src/normalizer.d.ts +24 -0
- package/lib/typescript/src/normalizer.d.ts.map +1 -0
- package/lib/typescript/src/queries.d.ts +47 -0
- package/lib/typescript/src/queries.d.ts.map +1 -0
- package/lib/typescript/src/store.d.ts +25 -0
- package/lib/typescript/src/store.d.ts.map +1 -0
- package/lib/typescript/src/translations.d.ts +29 -0
- package/lib/typescript/src/translations.d.ts.map +1 -0
- package/package.json +78 -0
- package/src/adapter.ts +187 -0
- package/src/components.tsx +237 -0
- package/src/config.ts +81 -0
- package/src/defaults.tsx +53 -0
- package/src/index.ts +61 -0
- package/src/models.ts +142 -0
- package/src/normalizer.ts +39 -0
- package/src/queries.ts +288 -0
- package/src/store.ts +46 -0
- package/src/translations.ts +28 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { Currency, Price, Principal, Product, Promotion } from '@laconius/core';
|
|
2
|
+
/**
|
|
3
|
+
* Cart models, ported from Spartacus `feature-libs/cart/base/root/models/cart.model.ts`
|
|
4
|
+
* (Apache-2.0, see NOTICE), minus the B2B, saved-cart and pickup-in-store noise
|
|
5
|
+
* ([chapter 03](../../../docs/spec/03-occ-layer.md)).
|
|
6
|
+
*
|
|
7
|
+
* `CartModification` is copied **exactly**: it encodes protocol rather than data — a cart
|
|
8
|
+
* mutation answers with an adjusted quantity and a status, and an API returning `void` there
|
|
9
|
+
* would be wrong.
|
|
10
|
+
*/
|
|
11
|
+
export interface Voucher {
|
|
12
|
+
appliedValue?: Price;
|
|
13
|
+
code?: string;
|
|
14
|
+
currency?: Currency;
|
|
15
|
+
description?: string;
|
|
16
|
+
freeShipping?: boolean;
|
|
17
|
+
name?: string;
|
|
18
|
+
value?: number;
|
|
19
|
+
valueFormatted?: string;
|
|
20
|
+
valueString?: string;
|
|
21
|
+
voucherCode?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface PromotionOrderEntryConsumed {
|
|
24
|
+
adjustedUnitPrice?: number;
|
|
25
|
+
code?: string;
|
|
26
|
+
orderEntryNumber?: number;
|
|
27
|
+
quantity?: number;
|
|
28
|
+
}
|
|
29
|
+
export interface PromotionResult {
|
|
30
|
+
consumedEntries?: PromotionOrderEntryConsumed[];
|
|
31
|
+
description?: string;
|
|
32
|
+
promotion?: Promotion;
|
|
33
|
+
}
|
|
34
|
+
export interface DeliveryMode {
|
|
35
|
+
code?: string;
|
|
36
|
+
deliveryCost?: Price;
|
|
37
|
+
description?: string;
|
|
38
|
+
name?: string;
|
|
39
|
+
}
|
|
40
|
+
export interface OrderEntry {
|
|
41
|
+
basePrice?: Price;
|
|
42
|
+
deliveryMode?: DeliveryMode;
|
|
43
|
+
entryNumber?: number;
|
|
44
|
+
product?: Product;
|
|
45
|
+
quantity?: number;
|
|
46
|
+
totalPrice?: Price;
|
|
47
|
+
updateable?: boolean;
|
|
48
|
+
promotions?: PromotionResult[];
|
|
49
|
+
}
|
|
50
|
+
export interface Cart {
|
|
51
|
+
appliedOrderPromotions?: PromotionResult[];
|
|
52
|
+
appliedProductPromotions?: PromotionResult[];
|
|
53
|
+
appliedVouchers?: Voucher[];
|
|
54
|
+
calculated?: boolean;
|
|
55
|
+
code?: string;
|
|
56
|
+
deliveryCost?: Price;
|
|
57
|
+
deliveryItemsQuantity?: number;
|
|
58
|
+
deliveryMode?: DeliveryMode;
|
|
59
|
+
description?: string;
|
|
60
|
+
entries?: OrderEntry[];
|
|
61
|
+
/** ISO string: OCC returns strings and never `Date`. */
|
|
62
|
+
expirationTime?: string;
|
|
63
|
+
guid?: string;
|
|
64
|
+
name?: string;
|
|
65
|
+
net?: boolean;
|
|
66
|
+
orderDiscounts?: Price;
|
|
67
|
+
pickupItemsQuantity?: number;
|
|
68
|
+
potentialOrderPromotions?: PromotionResult[];
|
|
69
|
+
potentialProductPromotions?: PromotionResult[];
|
|
70
|
+
productDiscounts?: Price;
|
|
71
|
+
site?: string;
|
|
72
|
+
store?: string;
|
|
73
|
+
subTotal?: Price;
|
|
74
|
+
totalDiscounts?: Price;
|
|
75
|
+
totalItems?: number;
|
|
76
|
+
totalPrice?: Price;
|
|
77
|
+
totalPriceWithTax?: Price;
|
|
78
|
+
totalTax?: Price;
|
|
79
|
+
totalUnitCount?: number;
|
|
80
|
+
user?: Principal;
|
|
81
|
+
}
|
|
82
|
+
export interface CartModification {
|
|
83
|
+
deliveryModeChanged?: boolean;
|
|
84
|
+
entry?: OrderEntry;
|
|
85
|
+
quantity?: number;
|
|
86
|
+
quantityAdded?: number;
|
|
87
|
+
statusCode?: string;
|
|
88
|
+
statusMessage?: string;
|
|
89
|
+
}
|
|
90
|
+
export interface CartModificationList {
|
|
91
|
+
cartModifications?: CartModification[];
|
|
92
|
+
}
|
|
93
|
+
export type CartValidationStatusCode = 'noStock' | 'lowStock' | 'reviewConfiguration' | 'pricingError' | 'unresolvableIssues' | 'below_min_quantity' | 'exceed_max_quantity';
|
|
94
|
+
export type AddToCartInput = {
|
|
95
|
+
productCode: string;
|
|
96
|
+
quantity?: number;
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* What every cart mutation resolves to. It carries the cart id it acted on: with lazy creation,
|
|
100
|
+
* an `onSuccess` closing over `activeCartId` writes the wrong cache entry
|
|
101
|
+
* ([chapter 04](../../../docs/spec/04-state.md)).
|
|
102
|
+
*/
|
|
103
|
+
export type CartMutationResult = {
|
|
104
|
+
cart: Cart;
|
|
105
|
+
cartId: string;
|
|
106
|
+
modification?: CartModification;
|
|
107
|
+
};
|
|
108
|
+
export type CartMergeFailure = {
|
|
109
|
+
entry: OrderEntry;
|
|
110
|
+
/** The `LaconiusHttpError` that rejected the replay. */
|
|
111
|
+
error: unknown;
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* The one place Laconius returns errors as data rather than throwing them: a partial replay is
|
|
115
|
+
* not a failure, and silently dropping the rejected entries is exactly what users notice
|
|
116
|
+
* ([chapter 09](../../../docs/spec/09-i18n-and-errors.md)).
|
|
117
|
+
*/
|
|
118
|
+
export type CartMergeResult = {
|
|
119
|
+
replayed: OrderEntry[];
|
|
120
|
+
failed: CartMergeFailure[];
|
|
121
|
+
};
|
|
122
|
+
//# sourceMappingURL=models.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../../../src/models.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type Image, type Product } from '@laconius/core';
|
|
2
|
+
import type { Cart, OrderEntry } from './models.js';
|
|
3
|
+
/** An entry as OCC answers it: the product's images are still a flat list. */
|
|
4
|
+
export type OccOrderEntry = Omit<OrderEntry, 'product'> & {
|
|
5
|
+
product?: Omit<Product, 'images'> & {
|
|
6
|
+
images?: Image[];
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
export type OccCart = Omit<Cart, 'entries'> & {
|
|
10
|
+
entries?: OccOrderEntry[];
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Reshapes every entry product's flat image list through core's `normalizeImages` — the same
|
|
14
|
+
* mechanism the product adapter uses ([chapter 08](../../../docs/spec/08-media.md)). URLs are
|
|
15
|
+
* not absolutised here: converters are pure, so the adapter absolutises the whole payload
|
|
16
|
+
* afterwards.
|
|
17
|
+
*/
|
|
18
|
+
export declare function normalizeCartImages(source: OccCart, target?: Cart): Cart;
|
|
19
|
+
/**
|
|
20
|
+
* Ported from `feature-libs/cart/base/core/utils/utils.ts`: an anonymous cart is addressed by
|
|
21
|
+
* its `guid`, a logged-in user's cart by its `code`.
|
|
22
|
+
*/
|
|
23
|
+
export declare function getCartId(cart: Cart | undefined, userId: string): string;
|
|
24
|
+
//# sourceMappingURL=normalizer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalizer.d.ts","sourceRoot":"","sources":["../../../src/normalizer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,KAAK,EAAE,KAAK,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAE3E,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,aAAU,CAAC;AAEjD,8EAA8E;AAC9E,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,GAAG;IACxD,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,GAAG;QAAE,MAAM,CAAC,EAAE,KAAK,EAAE,CAAA;KAAE,CAAC;CAC1D,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,GAAG;IAAE,OAAO,CAAC,EAAE,aAAa,EAAE,CAAA;CAAE,CAAC;AAE5E;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,IAAI,GAAG,IAAI,CAaxE;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAExE"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { AddToCartInput, Cart, CartMergeResult, CartMutationResult } from './models.js';
|
|
2
|
+
/**
|
|
3
|
+
* `queryOptions` factory, so an app can `prefetchQuery` the cart without forking the library.
|
|
4
|
+
* The key is `['laconius','cart',{ctx},cartId]` — the context object carries the user, which is
|
|
5
|
+
* what keeps an anonymous cart and a user cart apart in the cache.
|
|
6
|
+
*/
|
|
7
|
+
export declare const cartQueries: {
|
|
8
|
+
detail: (cartId: string) => import("@tanstack/react-query").OmitKeyof<import("@tanstack/react-query").UseQueryOptions<Cart, Error, Cart, readonly unknown[]>, "queryFn"> & {
|
|
9
|
+
queryFn?: import("@tanstack/react-query").QueryFunction<Cart, readonly unknown[], never> | undefined;
|
|
10
|
+
} & import("@tanstack/react-query").QueryKeyWithDataTag<readonly unknown[], Cart, Error>;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* The active cart. Empty (no request) until an add creates one — the anonymous cart is lazy —
|
|
14
|
+
* except for a logged-in user with no stored id, whose latest cart OCC addresses as the literal
|
|
15
|
+
* `current`.
|
|
16
|
+
*/
|
|
17
|
+
export declare function useActiveCart(): import("@tanstack/react-query").UseQueryResult<Cart, Error>;
|
|
18
|
+
/**
|
|
19
|
+
* Not optimistic: stock and price come from the server, and rolling back a `lowStock` result is
|
|
20
|
+
* ugly ([chapter 04](../../../docs/spec/04-state.md)). OCC answers the POST with a
|
|
21
|
+
* `CartModification`, so one follow-up read is what lets `setQueryData` replace invalidation.
|
|
22
|
+
*/
|
|
23
|
+
export declare function useAddToCart(): import("@tanstack/react-query").UseMutationResult<CartMutationResult, Error, AddToCartInput, unknown>;
|
|
24
|
+
type EntrySnapshot = {
|
|
25
|
+
previous?: Cart;
|
|
26
|
+
queryKey?: unknown[];
|
|
27
|
+
};
|
|
28
|
+
/** Optimistic: the quantity flips immediately, the totals arrive with the response. */
|
|
29
|
+
export declare function useUpdateCartEntry(): import("@tanstack/react-query").UseMutationResult<CartMutationResult, Error, {
|
|
30
|
+
entryNumber: number;
|
|
31
|
+
quantity: number;
|
|
32
|
+
}, EntrySnapshot>;
|
|
33
|
+
/** Optimistic: the row disappears immediately, the totals arrive with the response. */
|
|
34
|
+
export declare function useRemoveCartEntry(): import("@tanstack/react-query").UseMutationResult<CartMutationResult, Error, {
|
|
35
|
+
entryNumber: number;
|
|
36
|
+
}, EntrySnapshot>;
|
|
37
|
+
export declare function useApplyVoucher(): import("@tanstack/react-query").UseMutationResult<CartMutationResult, Error, string, unknown>;
|
|
38
|
+
export declare function useRemoveVoucher(): import("@tanstack/react-query").UseMutationResult<CartMutationResult, Error, string, unknown>;
|
|
39
|
+
/**
|
|
40
|
+
* Persist-then-replay, never the native merge — the backend rejects a native merge after a
|
|
41
|
+
* redirect login, and every React Native login is one
|
|
42
|
+
* ([chapter 05](../../../docs/spec/05-auth-session.md)). Call it after a successful login;
|
|
43
|
+
* `useLogin()` takes an `onLoggedIn` callback and the template joins them.
|
|
44
|
+
*/
|
|
45
|
+
export declare function useMergeAnonymousCart(): import("@tanstack/react-query").UseMutationResult<CartMergeResult, Error, void, unknown>;
|
|
46
|
+
export {};
|
|
47
|
+
//# sourceMappingURL=queries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../../../src/queries.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EACV,cAAc,EACd,IAAI,EACJ,eAAe,EACf,kBAAkB,EAEnB,MAAM,aAAU,CAAC;AAIlB;;;;GAIG;AACH,eAAO,MAAM,WAAW;qBACL,MAAM;;;CAwBxB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,aAAa,gEAQ5B;AAeD;;;;GAIG;AACH,wBAAgB,YAAY,0GAsB3B;AAED,KAAK,aAAa,GAAG;IAAE,QAAQ,CAAC,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAA;CAAE,CAAC;AA0B/D,uFAAuF;AACvF,wBAAgB,kBAAkB;iBAOf,MAAM;cACT,MAAM;kBAkBrB;AAED,uFAAuF;AACvF,wBAAgB,kBAAkB;iBAMf,MAAM;kBAgBxB;AAED,wBAAgB,eAAe,kGAa9B;AAED,wBAAgB,gBAAgB,kGAa/B;AAkBD;;;;;GAKG;AACH,wBAAgB,qBAAqB,6FAyCpC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type ActiveCartState = {
|
|
2
|
+
/** Cart `code` for a logged-in user, `guid` for an anonymous one. */
|
|
3
|
+
cartId?: string;
|
|
4
|
+
setCartId: (cartId: string | undefined) => void;
|
|
5
|
+
clearCartId: () => void;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* The only client-side cart state: the entity itself is server state in TanStack Query
|
|
9
|
+
* ([chapter 04](../../../docs/spec/04-state.md)). Persisted, so the id survives a restart and
|
|
10
|
+
* OCC reprices; a dead id resets itself through the 404 path in the query.
|
|
11
|
+
*/
|
|
12
|
+
export declare const useActiveCartIdStore: import("zustand").UseBoundStore<Omit<import("zustand").StoreApi<ActiveCartState>, "setState" | "persist"> & {
|
|
13
|
+
setState(partial: ActiveCartState | Partial<ActiveCartState> | ((state: ActiveCartState) => ActiveCartState | Partial<ActiveCartState>), replace?: false | undefined): unknown;
|
|
14
|
+
setState(state: ActiveCartState | ((state: ActiveCartState) => ActiveCartState), replace: true): unknown;
|
|
15
|
+
persist: {
|
|
16
|
+
setOptions: (options: Partial<import("zustand/middleware").PersistOptions<ActiveCartState, unknown, unknown>>) => void;
|
|
17
|
+
clearStorage: () => void;
|
|
18
|
+
rehydrate: () => Promise<void> | void;
|
|
19
|
+
hasHydrated: () => boolean;
|
|
20
|
+
onHydrate: (fn: (state: ActiveCartState) => void) => () => void;
|
|
21
|
+
onFinishHydration: (fn: (state: ActiveCartState) => void) => () => void;
|
|
22
|
+
getOptions: () => Partial<import("zustand/middleware").PersistOptions<ActiveCartState, unknown, unknown>>;
|
|
23
|
+
};
|
|
24
|
+
}>;
|
|
25
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/store.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,eAAe,GAAG;IAC5B,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IAChD,WAAW,EAAE,MAAM,IAAI,CAAC;CACzB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;EAYhC,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** English strings for the defaults in this package. Other languages are the app's. */
|
|
2
|
+
export declare const defaultCartTranslations: {
|
|
3
|
+
en: {
|
|
4
|
+
cart: {
|
|
5
|
+
title: string;
|
|
6
|
+
addToCart: string;
|
|
7
|
+
itemAdded: string;
|
|
8
|
+
itemRemoved: string;
|
|
9
|
+
quantityUpdated: string;
|
|
10
|
+
remove: string;
|
|
11
|
+
removeItem: string;
|
|
12
|
+
empty: string;
|
|
13
|
+
entryCount_one: string;
|
|
14
|
+
entryCount_other: string;
|
|
15
|
+
subtotal: string;
|
|
16
|
+
discounts: string;
|
|
17
|
+
delivery: string;
|
|
18
|
+
tax: string;
|
|
19
|
+
total: string;
|
|
20
|
+
voucher: {
|
|
21
|
+
placeholder: string;
|
|
22
|
+
apply: string;
|
|
23
|
+
applied: string;
|
|
24
|
+
remove: string;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=translations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"translations.d.ts","sourceRoot":"","sources":["../../../src/translations.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;CA0BnC,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@laconius/cart",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Laconius cart: cart queries and mutations with a lazily created anonymous cart, optimistic quantity and remove, vouchers, the anonymous-cart replay merge, the activeCartId store, the cart components and the MiniCart CMS default.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./lib/module/index.js",
|
|
8
|
+
"types": "./lib/typescript/src/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"source": "./src/index.ts",
|
|
12
|
+
"types": "./lib/typescript/src/index.d.ts",
|
|
13
|
+
"default": "./lib/module/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./package.json": "./package.json"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"src",
|
|
19
|
+
"lib",
|
|
20
|
+
"!**/*.test.*"
|
|
21
|
+
],
|
|
22
|
+
"sideEffects": [
|
|
23
|
+
"./src/store.ts",
|
|
24
|
+
"./lib/module/store.js"
|
|
25
|
+
],
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"@laconius/cms": "0.1.0",
|
|
31
|
+
"@laconius/core": "0.1.0",
|
|
32
|
+
"@laconius/ui": "0.1.0",
|
|
33
|
+
"@tanstack/react-query": "^5",
|
|
34
|
+
"expo-sqlite": "*",
|
|
35
|
+
"react": ">=19.0.0",
|
|
36
|
+
"react-native": "*",
|
|
37
|
+
"zustand": "^5"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@laconius/cms": "0.1.0",
|
|
41
|
+
"@laconius/core": "0.1.0",
|
|
42
|
+
"@laconius/ui": "0.1.0",
|
|
43
|
+
"@tanstack/react-query": "^5.102.8",
|
|
44
|
+
"@types/react": "~19.2.2",
|
|
45
|
+
"expo-sqlite": "~57.0.2",
|
|
46
|
+
"react": "19.2.3",
|
|
47
|
+
"react-native": "0.86.3",
|
|
48
|
+
"react-native-builder-bob": "^0.43.0",
|
|
49
|
+
"typescript": "~6.0.3",
|
|
50
|
+
"vitest": "^3.2.4",
|
|
51
|
+
"zustand": "^5.0.15"
|
|
52
|
+
},
|
|
53
|
+
"react-native-builder-bob": {
|
|
54
|
+
"source": "src",
|
|
55
|
+
"output": "lib",
|
|
56
|
+
"targets": [
|
|
57
|
+
[
|
|
58
|
+
"module",
|
|
59
|
+
{
|
|
60
|
+
"esm": true
|
|
61
|
+
}
|
|
62
|
+
],
|
|
63
|
+
[
|
|
64
|
+
"typescript",
|
|
65
|
+
{
|
|
66
|
+
"project": "tsconfig.build.json"
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
],
|
|
70
|
+
"exclude": "**/*.test.{ts,tsx}"
|
|
71
|
+
},
|
|
72
|
+
"scripts": {
|
|
73
|
+
"build": "bob build",
|
|
74
|
+
"clean": "rm -rf lib",
|
|
75
|
+
"typecheck": "tsc --noEmit",
|
|
76
|
+
"test": "vitest run"
|
|
77
|
+
}
|
|
78
|
+
}
|
package/src/adapter.ts
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import {
|
|
2
|
+
absolutizeMedia,
|
|
3
|
+
getMediaBaseUrl,
|
|
4
|
+
type Converter,
|
|
5
|
+
type LaconiusRuntime,
|
|
6
|
+
} from '@laconius/core';
|
|
7
|
+
|
|
8
|
+
import type { Cart, CartModification, CartModificationList } from './models';
|
|
9
|
+
import { normalizeCartImages, type OccCart } from './normalizer';
|
|
10
|
+
|
|
11
|
+
export type CartAdapter = {
|
|
12
|
+
load(runtime: LaconiusRuntime, cartId: string): Promise<Cart>;
|
|
13
|
+
loadAll(runtime: LaconiusRuntime): Promise<Cart[]>;
|
|
14
|
+
create(
|
|
15
|
+
runtime: LaconiusRuntime,
|
|
16
|
+
options?: { oldCartId?: string; toMergeCartGuid?: string },
|
|
17
|
+
): Promise<Cart>;
|
|
18
|
+
remove(runtime: LaconiusRuntime, cartId: string): Promise<void>;
|
|
19
|
+
addEntry(
|
|
20
|
+
runtime: LaconiusRuntime,
|
|
21
|
+
cartId: string,
|
|
22
|
+
productCode: string,
|
|
23
|
+
quantity: number,
|
|
24
|
+
): Promise<CartModification>;
|
|
25
|
+
updateEntry(
|
|
26
|
+
runtime: LaconiusRuntime,
|
|
27
|
+
cartId: string,
|
|
28
|
+
entryNumber: number,
|
|
29
|
+
quantity: number,
|
|
30
|
+
): Promise<CartModification>;
|
|
31
|
+
removeEntry(
|
|
32
|
+
runtime: LaconiusRuntime,
|
|
33
|
+
cartId: string,
|
|
34
|
+
entryNumber: number,
|
|
35
|
+
): Promise<void>;
|
|
36
|
+
applyVoucher(
|
|
37
|
+
runtime: LaconiusRuntime,
|
|
38
|
+
cartId: string,
|
|
39
|
+
voucherId: string,
|
|
40
|
+
): Promise<void>;
|
|
41
|
+
removeVoucher(
|
|
42
|
+
runtime: LaconiusRuntime,
|
|
43
|
+
cartId: string,
|
|
44
|
+
voucherId: string,
|
|
45
|
+
): Promise<void>;
|
|
46
|
+
validate(runtime: LaconiusRuntime, cartId: string): Promise<CartModificationList>;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/** Runs the `cart` stack and absolutises the media it reshaped, in that order. */
|
|
50
|
+
function toCart(runtime: LaconiusRuntime, source: OccCart | undefined): Cart {
|
|
51
|
+
const cart = runtime.converters.convert<OccCart, Cart>(source ?? {}, 'cart');
|
|
52
|
+
return absolutizeMedia(cart, getMediaBaseUrl(runtime.config));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function toModification(runtime: LaconiusRuntime, source: unknown): CartModification {
|
|
56
|
+
return runtime.converters.convert<unknown, CartModification>(
|
|
57
|
+
source ?? {},
|
|
58
|
+
'cartModification',
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const defaultCartAdapter: CartAdapter = {
|
|
63
|
+
/** `cartId` may be the literal `current` for a logged-in user. */
|
|
64
|
+
async load(runtime, cartId) {
|
|
65
|
+
const payload = await runtime.client.request<OccCart>({
|
|
66
|
+
endpoint: 'cart',
|
|
67
|
+
urlParams: { cartId },
|
|
68
|
+
});
|
|
69
|
+
return toCart(runtime, payload);
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
async loadAll(runtime) {
|
|
73
|
+
const response = await runtime.client.request<{ carts?: OccCart[] }>({
|
|
74
|
+
endpoint: 'carts',
|
|
75
|
+
});
|
|
76
|
+
return (response?.carts ?? []).map((cart) => toCart(runtime, cart));
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* `oldCartId` / `toMergeCartGuid` exist for endpoint parity, but Laconius never merges
|
|
81
|
+
* natively: every React Native login is a redirect login, and the backend rejects a native
|
|
82
|
+
* merge after one — the merge is always persist-then-replay
|
|
83
|
+
* ([chapter 05](../../../docs/spec/05-auth-session.md)).
|
|
84
|
+
*/
|
|
85
|
+
async create(runtime, options = {}) {
|
|
86
|
+
const payload = await runtime.client.request<OccCart>({
|
|
87
|
+
endpoint: 'createCart',
|
|
88
|
+
method: 'POST',
|
|
89
|
+
queryParams: {
|
|
90
|
+
oldCartId: options.oldCartId,
|
|
91
|
+
toMergeCartGuid: options.toMergeCartGuid,
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
return toCart(runtime, payload);
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
async remove(runtime, cartId) {
|
|
98
|
+
await runtime.client.request<void>({
|
|
99
|
+
endpoint: 'deleteCart',
|
|
100
|
+
method: 'DELETE',
|
|
101
|
+
urlParams: { cartId },
|
|
102
|
+
});
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
async addEntry(runtime, cartId, productCode, quantity) {
|
|
106
|
+
const payload = await runtime.client.request<unknown>({
|
|
107
|
+
endpoint: 'addEntries',
|
|
108
|
+
method: 'POST',
|
|
109
|
+
urlParams: { cartId },
|
|
110
|
+
body: { quantity, product: { code: productCode } },
|
|
111
|
+
});
|
|
112
|
+
return toModification(runtime, payload);
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
async updateEntry(runtime, cartId, entryNumber, quantity) {
|
|
116
|
+
const payload = await runtime.client.request<unknown>({
|
|
117
|
+
endpoint: 'updateEntries',
|
|
118
|
+
method: 'PATCH',
|
|
119
|
+
urlParams: { cartId, entryNumber },
|
|
120
|
+
body: { quantity },
|
|
121
|
+
});
|
|
122
|
+
return toModification(runtime, payload);
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
async removeEntry(runtime, cartId, entryNumber) {
|
|
126
|
+
await runtime.client.request<void>({
|
|
127
|
+
endpoint: 'removeEntries',
|
|
128
|
+
method: 'DELETE',
|
|
129
|
+
urlParams: { cartId, entryNumber },
|
|
130
|
+
});
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
/** The JSON pair when `capabilities.jsonVouchers` is on, the legacy pair when it is off. */
|
|
134
|
+
async applyVoucher(runtime, cartId, voucherId) {
|
|
135
|
+
if (runtime.config.capabilities?.jsonVouchers) {
|
|
136
|
+
await runtime.client.request<void>({
|
|
137
|
+
endpoint: 'cartApplyVoucher',
|
|
138
|
+
method: 'POST',
|
|
139
|
+
urlParams: { cartId },
|
|
140
|
+
body: { voucherId },
|
|
141
|
+
});
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
await runtime.client.request<void>({
|
|
145
|
+
endpoint: 'cartVoucher',
|
|
146
|
+
method: 'POST',
|
|
147
|
+
urlParams: { cartId },
|
|
148
|
+
queryParams: { voucherId },
|
|
149
|
+
});
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
async removeVoucher(runtime, cartId, voucherId) {
|
|
153
|
+
if (runtime.config.capabilities?.jsonVouchers) {
|
|
154
|
+
await runtime.client.request<void>({
|
|
155
|
+
endpoint: 'cartRemoveVoucher',
|
|
156
|
+
method: 'POST',
|
|
157
|
+
urlParams: { cartId },
|
|
158
|
+
body: { voucherId },
|
|
159
|
+
});
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
await runtime.client.request<void>({
|
|
163
|
+
endpoint: 'cartVoucher',
|
|
164
|
+
scope: 'delete',
|
|
165
|
+
method: 'DELETE',
|
|
166
|
+
urlParams: { cartId, voucherId },
|
|
167
|
+
});
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
async validate(runtime, cartId) {
|
|
171
|
+
const payload = await runtime.client.request<CartModificationList>({
|
|
172
|
+
endpoint: 'validate',
|
|
173
|
+
method: 'POST',
|
|
174
|
+
urlParams: { cartId },
|
|
175
|
+
});
|
|
176
|
+
return payload ?? {};
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
export function cartAdapter(runtime: LaconiusRuntime): CartAdapter {
|
|
181
|
+
return { ...defaultCartAdapter, ...runtime.config.backend.adapters?.cart };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export const defaultCartConverters = {
|
|
185
|
+
cart: [normalizeCartImages] as Converter<OccCart, Cart>[],
|
|
186
|
+
cartModification: [] as Converter<unknown, CartModification>[],
|
|
187
|
+
};
|