@misiki/gocommerce-connector 0.1.0 → 0.2.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/README.md CHANGED
@@ -1,187 +1,190 @@
1
1
  # @misiki/gocommerce-connector
2
2
 
3
- [![NPM Version](https://img.shields.io/npm/v/@misiki/gocommerce-connector.svg)](https://www.npmjs.com/package/@misiki/gocommerce-connector)
4
- [![License](https://img.shields.io/npm/l/@misiki/gocommerce-connector.svg)](https://github.com/misiki-in/gocommerce-connector/blob/main/LICENSE)
5
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
3
+ [Svelte Commerce](https://github.com/itswadesh/svelte-commerce) on a
4
+ [GoCommerce](https://github.com/itswadesh/gocommerce) engine.
6
5
 
7
- **The Official TypeScript API Connector for connecting `svelte-commerce` to a GoCommerce backend.**
6
+ The storefront talks to every backend through a connector: a package that maps
7
+ one platform's API onto a shared surface of some forty services (`product`,
8
+ `cart`, `checkout`, `order`, `user`, `address`, …). This is GoCommerce's. It
9
+ covers the engine's public API and the storefront modules it ships — accounts,
10
+ reviews, wishlists, content pages, menus, FAQ, contact and search — and says
11
+ plainly where the two do not meet.
8
12
 
9
- `@misiki/gocommerce-connector` bridges [svelte-commerce](https://github.com/misiki-in/svelte-commerce) storefronts with [GoCommerce](https://github.com/itswadesh/gocommerce) — "a small, composable commerce engine for Go" — as part of the [Litekart](https://litekart.in) connector suite, mirroring the full 43-service surface of [`@misiki/litekart-connector`](https://github.com/misiki-in/litekart-connector). It is written against the server's own OpenAPI document (`GET /doc` on a running store).
10
-
11
- > **Coverage:** 10 of 43 services are wired to the live GoCommerce API — catalogue, search, cart,
12
- > checkout, payment methods and orders. That is the honest ceiling, not a to-do list: GoCommerce is
13
- > guest-checkout only by design, so there are no accounts, wishlists, reviews, coupons or CMS to
14
- > wire. Those services answer with empty lists, and their writes throw a named message rather than
15
- > succeeding silently.
16
-
17
- ---
18
-
19
- ## 🚀 Step-by-Step Integration Guide
20
-
21
- ### 1. Install the Connector
22
-
23
- Inside your `svelte-commerce` project directory, run:
13
+ ## Install
24
14
 
25
15
  ```bash
26
- bun i @misiki/gocommerce-connector
16
+ bun remove @misiki/litekart-connector @misiki/vendure-connector
17
+ bun add @misiki/gocommerce-connector
27
18
  ```
28
19
 
29
- *(Or using npm / pnpm / yarn):*
20
+ The storefront runs on whichever `@misiki/*-connector` is installed. If you keep
21
+ another one installed, set `PUBLIC_CONNECTOR=@misiki/gocommerce-connector`.
22
+
30
23
  ```bash
31
- npm install @misiki/gocommerce-connector
32
- # or
33
- pnpm add @misiki/gocommerce-connector
24
+ # .env
25
+ PUBLIC_GOCOMMERCE_API_URL=https://api.shop.example # the engine
26
+ PUBLIC_GOCOMMERCE_PROXY_PATH=/gocommerce # see "The browser and CORS"
27
+ # PUBLIC_GOCOMMERCE_CHANNEL=web # a sales channel, if the store has several
28
+ # PUBLIC_GOCOMMERCE_STORE=acme # under `gocommerce platform`, on a shared API host
34
29
  ```
35
30
 
36
- ### 2. Configure `kitcommerce.config.ts`
31
+ **Leave `PUBLIC_LITEKART_API_URL` unset.** The storefront rewrites every
32
+ server-side request whose path starts with `/api` to that host, and the
33
+ engine's routes start with `/api` too.
37
34
 
38
- In `svelte-commerce`, open `kitcommerce.config.ts` and point the `services` export at the GoCommerce
39
- override module:
35
+ ### The engine
40
36
 
41
- ```typescript
42
- // kitcommerce.config.ts
43
- export * as services from './src/lib/core/connectors/gocommerce'
37
+ Start it with the modules the storefront has screens for:
44
38
 
45
- export default {
46
- // store identity overrides — name, logo, favicon, currencyCode, menus, plugins …
47
- }
39
+ ```bash
40
+ gocommerce serve -identity -wishlist -reviews -cms -faq -menus
48
41
  ```
49
42
 
50
- ### 3. Configure Credentials
43
+ Each is optional. Without `-identity` there is no sign-in (guest checkout
44
+ always works); without `-reviews` products show no stars; and so on. Add
45
+ `-meilisearch` (with `MEILI_HOST` and keys) for price sorting and price filters.
51
46
 
52
- The connector needs one thing: the base URL of the GoCommerce server.
53
-
54
- ```env
55
- PUBLIC_GOCOMMERCE_API_URL='http://127.0.0.1:8080'
56
- ```
47
+ ### The browser and CORS
57
48
 
58
- `svelte-commerce` hands that variable to the connector at boot. Outside the storefront, set it
59
- directly:
49
+ The engine sends no CORS headers, so a browser on the storefront's origin cannot
50
+ read a response from the engine's. Server-side rendering is unaffected; it is
51
+ the bag, sign-in and checkout — which run in the browser — that need a route.
60
52
 
61
- ```ts
62
- import { BaseService, productService } from '@misiki/gocommerce-connector'
53
+ Proxy a path on the storefront's own origin to the engine and name it in
54
+ `PUBLIC_GOCOMMERCE_PROXY_PATH`. The browser then calls
55
+ `https://shop.example/gocommerce/api/carts`, and the server keeps calling
56
+ `PUBLIC_GOCOMMERCE_API_URL` directly. With Caddy:
63
57
 
64
- BaseService.setCredentials({ apiUrl: 'http://127.0.0.1:8080' })
65
-
66
- const products = await productService.list({ page: 1 })
58
+ ```caddyfile
59
+ shop.example {
60
+ handle_path /gocommerce/* {
61
+ reverse_proxy engine:8080
62
+ }
63
+ reverse_proxy storefront:3000
64
+ }
67
65
  ```
68
66
 
69
- ### 4. Register store identity
70
-
71
- GoCommerce is a commerce engine, not a CMS: it has no record for the store's name, logo, currency,
72
- menus, plugin toggles or theme variables. The connector reads them from a provider the storefront
73
- registers once at boot:
67
+ or nginx:
74
68
 
75
- ```ts
76
- import { setStaticStore } from '@misiki/gocommerce-connector'
77
-
78
- setStaticStore(async () => ({ name: 'My Store', currencyCode: 'USD', /* … */ }))
69
+ ```nginx
70
+ location /gocommerce/ { proxy_pass http://engine:8080/; }
71
+ location / { proxy_pass http://storefront:3000; }
79
72
  ```
80
73
 
81
- `svelte-commerce` does this for you in `src/lib/core/connectors/gocommerce.ts`, merging
82
- `default-store.json` under the `kitcommerce.config.ts` default export.
83
-
84
- ---
85
-
86
- ## How it works
87
-
88
- ### The browser cannot call GoCommerce directly
89
-
90
- GoCommerce sends no `Access-Control-Allow-Origin` header and answers a preflight with `405`. So in
91
- the browser every call is sent to `API_PROXY_PREFIX` (`/proxy/gocommerce`) on the storefront's own
92
- origin, and the storefront forwards it to the API. Server-side calls go straight to
93
- `PUBLIC_GOCOMMERCE_API_URL`. The rule lives in `BaseService.apiUrl`; media URLs always resolve
94
- through the proxy, because a server-rendered `<img src>` is still fetched by the browser.
95
-
96
- `svelte-commerce` ships that proxy at `src/routes/proxy/gocommerce/[...path]/+server.ts`. Any other
97
- host needs an equivalent route.
74
+ Serving the storefront and the engine from one origin works too; then leave the
75
+ proxy path unset and point the API URL at that origin.
76
+
77
+ ## What maps to what
78
+
79
+ | Storefront | Engine |
80
+ | --- | --- |
81
+ | product page, listings, search | `/api/products`, `/api/products/slug/{slug}` (an id or SKU also works) |
82
+ | category pages and tree | `/api/categories`, `/api/categories/{slug}` — a category sells its descendants |
83
+ | collections | `/api/collections`, `/api/collections/{slug}` |
84
+ | featured / trending shelves | the collection with slug `featured` / `trending`; newest products when there is none |
85
+ | bag | `/api/carts…`, `/api/carts/{token}/discount` |
86
+ | delivery options | `/api/checkout/rates` |
87
+ | payment methods | `/api/checkout` — cash on delivery and Razorpay (see below) |
88
+ | place order | `POST /api/checkout/{code}` with an `Idempotency-Key` |
89
+ | order lookup | `/api/orders/{number}?token=` (guest), `/x/identity/me/orders` (account) |
90
+ | sign-in, sign-up, profile, password reset | `/x/identity/*` |
91
+ | address book | `/x/identity/me/addresses` |
92
+ | wishlist | `/x/wishlist` |
93
+ | reviews | `/x/reviews` |
94
+ | content pages (`/p/…`, legal pages) | `/x/cms/pages` |
95
+ | FAQ | `/x/faq` |
96
+ | menus | `/x/navigation/menus/{header,footer}`, laid over `store.menu` |
97
+ | contact form, product enquiries | `/x/contact/messages` |
98
+ | price sort and filter, type-ahead | `/x/meilisearch/search`, falling back to the engine's own search |
99
+ | store record | the storefront's own config, with the engine's currency, menus and module switches over it |
100
+
101
+ Blogs, banners, reels, chat, warranties, deals and uploads have no engine
102
+ equivalent. Their lists answer empty and anything that would write or open one
103
+ record throws `NotSupportedError`, naming the service and the method — a silent
104
+ success would tell a shopper their message was sent when nothing left the
105
+ browser.
106
+
107
+ ## Two things this package is careful about
108
+
109
+ **Money.** The engine speaks minor units and a currency code and never a
110
+ formatted string. The storefront wants `price: 19.99`. The conversion happens in
111
+ `money.ts` and nowhere else, and it is per currency: the yen has no decimals and
112
+ the dinar has three, so ¥2,500 stays 2500 rather than becoming 25.
113
+
114
+ **Nothing is invented.** There is no popularity score, so "most popular" sorts
115
+ nothing rather than sorting by a made-up number. No compare-at price means `mrp`
116
+ equals the price, not 0 — which would render as a 100% discount. Counts and page
117
+ totals are the engine's own.
118
+
119
+ ## Where the two do not meet yet
120
+
121
+ Each of these is a real gap, listed so nobody has to find it in production.
122
+
123
+ - **The order-success page shows no details.** The storefront looks the order up
124
+ on the server with only the number and cart id from the URL. A guest's order is
125
+ readable only with the access token checkout returned, which lives in the
126
+ browser that placed it, so the server answers "not available yet". The bag
127
+ still empties, and the order is readable from that browser and, for a
128
+ signed-in shopper, under My orders. Using the cart token in the URL as a
129
+ credential would have fixed the page and made that URL — which lands in
130
+ history and referrers — a key to the shopper's address.
131
+ - **A missing product is a 503, not a 404.** The storefront decides by the status
132
+ of a request to exactly `/api/products/<slug>`; the engine's slug route is
133
+ `/api/products/slug/<slug>`, and `/api/products/<slug>` answers 400 for a
134
+ non-numeric id.
135
+ - **Payment.** The storefront drives a fixed list of gateway flows. Of the
136
+ engine's, cash on delivery and Razorpay's modal match. GoCommerce's Stripe is a
137
+ Payment Element (a client secret), not the hosted page the storefront
138
+ redirects to, and the engine's redirect gateways (Adyen, Paddle, Lemon
139
+ Squeezy, Creem, Hyperswitch, RevenueCat) have no matching flow. They are left
140
+ out of the payment list — each reported once in the console — rather than
141
+ shown as a button that fails at the last step. Payment is confirmed by the
142
+ gateway's webhook to the engine; the order shows `paymentStatus` once it lands.
143
+ - **Tax in the bag.** The engine's cart carries no tax or total; tax is computed
144
+ when the order is placed. The bag shows subtotal − discount + delivery, and the
145
+ order shows the real figures.
146
+ - **Facets.** The engine's search returns hits and a total, not distributions, so
147
+ category and tag facets and the price slider are not shown.
148
+ - **Phone sign-in by code, account deletion, guest order lookup by email and
149
+ code** are not engine features and throw.
150
+
151
+ ## State kept in the browser
152
+
153
+ The engine holds a cart's lines and email; the address, phone, delivery choice
154
+ and coupon are sent with the checkout request, so they are kept in the
155
+ shopper's `localStorage` until then. So is the account session token (the
156
+ engine's identity module is bearer-only), each order's access token, and the
157
+ wishlist token. Nothing about a shopper is ever held in module scope: the
158
+ storefront renders some services from a server singleton shared by every
159
+ request.
98
160
 
99
- ### Money
100
-
101
- GoCommerce sends money as an integer count of the currency's minor unit plus its code
102
- (`{ amount_minor: 2500, currency: "USD" }`). `fromMoney` converts once, against the currency the
103
- amount arrived with, into the plain major-unit number the storefront's components print.
104
-
105
- ### The cart
106
-
107
- A cart is an opaque token and possessing it is the whole authorisation. The connector persists it as
108
- `cart_id`. A write that fails is followed by a re-read: if the cart is gone or no longer `open`, the
109
- token is dropped and the item goes into a fresh cart; if the cart is alive, the failure was real and
110
- is passed straight through.
111
-
112
- ### The address step, sessions and the address book
113
-
114
- GoCommerce has no address on a cart — the delivery address is an argument to
115
- `POST /api/checkout/{code}`. The connector holds the shopper's details in the browser against the
116
- cart id (`checkout-details`) and spends them when the order is placed. Saved addresses are likewise
117
- browser-local (`address-book`); they never leave the device except as part of that call.
118
-
119
- Because there are no accounts, a `connect.sid` / `me` cookie can only be left over from another
120
- backend. `hasPhantomSession` / `clearPhantomSession` let the storefront drop them at load.
121
-
122
- ### Checkout, payment and orders
123
-
124
- `GET /api/checkout` lists the installed payment codes; `POST /api/checkout/{code}` validates the
125
- cart, reserves inventory and creates the order in one transaction, with an `Idempotency-Key`.
126
- Gateway fields ride on `payment.client_data` and are spread onto the return value. Only `cod` is
127
- verified end to end. The order's `access_token`, returned once, is stored so "my orders" is every
128
- order placed from this browser.
129
-
130
- ### Catalogue and search
131
-
132
- The shopper-facing catalogue takes `q`, `limit`, `page` and a category or collection slug — no sort,
133
- no tag filter, no price range. Sorting and facets are applied in the connector over one window of
134
- 200 products, the API's own `limit` ceiling. Product images are read from both `image_url` and each
135
- variant's `image`, because the former is missing from the OpenAPI document while the server returns
136
- it on every read.
137
-
138
- ---
139
-
140
- ## Service behaviour
141
-
142
- | Service | Behaviour |
143
- | ------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
144
- | `ProductService`, `SearchService` | `/api/products`, `/api/products/slug/{slug}`, `/api/categories/{slug}`; sort and facets local |
145
- | `CategoryService`, `CollectionService` | `/api/categories`, `/api/collections` and their slug lookups |
146
- | `MeilisearchService`, `AutocompleteService` | `?q=` on the catalogue — GoCommerce ships no separate search engine |
147
- | `CartService` | `/api/carts` and its line-items; the address step is held in the browser |
148
- | `CheckoutService`, `PaymentMethodService` | `GET /api/checkout`, `POST /api/checkout/{code}` |
149
- | `OrderService` | `/api/orders/{number}?token=…`; the confirmation page falls back to the converted cart |
150
- | `StoreService`, `MenuService`, `CurrencyService`, `PluginService`, `SettingService`, `RegionService`, … | The registered static store |
151
- | `AuthService`, `UserService`, `ProfileService` | No accounts exist — reads answer empty, writes explain why |
152
- | `AddressService` | A browser-local address book |
153
- | `PageService`, `BlogService`, `BannerService`, `GalleryService`, `ReelsService` | Empty — no CMS |
154
- | `WishlistService`, `CouponService`, `ReviewService`, `VendorService` | Empty lists; writes throw a named message |
155
- | `ContactService`, `EnquiryService`, `UploadService` | Throw — there is no inbox behind them |
161
+ ## Development
156
162
 
157
- ---
163
+ ```bash
164
+ npm install
165
+ npm run typecheck
166
+ npm test # builds, then runs both suites
167
+ ```
158
168
 
159
- ## Development
169
+ `test/unit.test.mjs` covers the translation with no engine. `test/live.test.mjs`
170
+ runs the connector against a real one, seeding what it needs through the admin
171
+ API under names unique to the run:
160
172
 
161
173
  ```bash
162
- bun install
163
- bun run typecheck
164
- bun run build # tsup → dist/ (ESM + CJS + d.ts)
165
- bun p # build and publish
174
+ gocommerce serve -admin-token dev-token -identity -wishlist -reviews -cms -faq -menus
175
+ GOCOMMERCE_URL=http://127.0.0.1:8080 GOCOMMERCE_ADMIN_TOKEN=dev-token npm test
166
176
  ```
167
177
 
168
- Source layout (`src/services/`):
169
-
170
- | File | Purpose |
171
- | --------------------- | ---------------------------------------------------------- |
172
- | `base.ts` | transport, credentials, the `{ data, meta }` envelope |
173
- | `types.ts` | GoCommerce's wire shapes, from its own OpenAPI document |
174
- | `money.ts` | minor units → the numbers components print |
175
- | `mappers.ts` | GoCommerce → the storefront's (Litekart-shaped) models |
176
- | `catalog.ts` | products, categories, collections, listing search |
177
- | `cart.ts` | the cart token and its lines |
178
- | `checkout-details.ts` | what checkout needs that a GoCommerce cart cannot hold |
179
- | `address-book.ts` | the saved addresses GoCommerce has nowhere to put |
180
- | `checkout.ts` | `POST /api/checkout/{code}` and the installed payment methods |
181
- | `orders.ts` | guest order lookup by access token |
182
- | `store.ts` | store identity from the registered static config |
183
- | `static-store.ts` | the static store provider and the feature toggles forced off |
184
- | `unsupported.ts` | what GoCommerce deliberately does not have |
178
+ It skips, loudly, when there is no engine. CI runs it against GoCommerce's
179
+ `main` on every push and nightly.
180
+
181
+ ## Releasing
182
+
183
+ Bump `version` in `package.json` and push it to `main`. The Publish workflow
184
+ sees a version npm does not have yet, builds, tests, publishes it with
185
+ provenance, and creates the matching `v<version>` tag and GitHub release. It
186
+ needs an `NPM_TOKEN` secret in the repository's `npm-publish` environment, able
187
+ to publish under `@misiki`.
185
188
 
186
189
  ## License
187
190