@likyly/react 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +56 -130
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,44 +1,27 @@
1
1
  # @likyly/react
2
2
 
3
- React bindings for [`@likyly/sdk`](../js): `useRecommendations` (headless) and
4
- `<LikylyRecommendations>` (ready-to-use) for rendering a LIKYLY [placement](../../docs/placements.md)
5
- and tracking the impression/click events it needs to learn from.
6
-
7
- ## Why a separate package (not React support inside `@likyly/sdk`)
8
-
9
- - `@likyly/sdk` is dependency-free and runs in Node, edge runtimes and browsers alike (it's used
10
- server-side for `items`/`users` too). Adding React to it would force that dependency - and the
11
- `"use client"` boundary Next.js's App Router needs - onto every consumer, including
12
- server-side-only integrations that never render anything.
13
- - This repo's own convention is one package per surface (`sdk/js`, `sdk/python`, `sdk/php`,
14
- `sdk/mcp`, ...) - `@likyly/react` is that same pattern applied to a second JS surface, not a
15
- new idea.
16
- - It matches how this kind of binding is normally split in the ecosystem (a core client plus a
17
- thin framework package: `stripe` / `@stripe/react-stripe-js`, `algoliasearch` /
18
- `react-instantsearch`).
19
- - `@likyly/react` depends on `@likyly/sdk` and adds **zero** wire/HTTP logic of its own - every
20
- method here calls straight through to the `Likyly` client underneath (see `src/controller.ts`,
21
- the plain-JS state machine both `useRecommendations` and `<LikylyRecommendations>` share - it
22
- has no React import at all, which is also why it's unit-tested with no DOM/renderer needed).
3
+ Show LIKYLY recommendations in a React app, with the impressions and clicks LIKYLY learns from
4
+ tracked for you.
5
+
6
+ The fastest way to set it up is the wizard, which installs this package, writes your public key
7
+ and connects your AI coding assistant:
8
+
9
+ ```sh
10
+ npx @likyly/wizard
11
+ ```
23
12
 
24
13
  ## Install
25
14
 
26
15
  ```sh
27
- npm install @likyly/react @likyly/sdk react
16
+ npm install @likyly/react
28
17
  ```
29
18
 
30
- > During local development in the `likyly-recsys` monorepo (before `@likyly/sdk` is published
31
- > to npm), this package's `dependencies` point at it via a relative `file:../js` reference.
32
- > Once published, that becomes a normal semver range - nothing a consumer needs to think about.
33
- > One consequence in the meantime: npm installs a `file:` dependency as a symlink pointing
34
- > outside the consuming app's own folder, which **Turbopack fails to resolve** ("Module not
35
- > found"). Run `next dev`/`next build` with `--webpack` until both packages are on npm - see
36
- > `examples/demo-storefront` for a working setup with this already applied.
19
+ React 18 or 19. Works with Next.js (App Router included), Vite, Remix and others.
37
20
 
38
21
  ## Quick start
39
22
 
40
23
  ```tsx
41
- import { LikylyProvider, LikylyRecommendations } from "@likyly/react";
24
+ import { LikylyProvider, LikylyRecommendations, useLikyly } from "@likyly/react";
42
25
 
43
26
  export function App() {
44
27
  return (
@@ -48,133 +31,76 @@ export function App() {
48
31
  );
49
32
  }
50
33
 
51
- function ProductPage({ product, user, sessionId }) {
34
+ function ProductPage({ product, user }) {
35
+ const { session } = useLikyly();
52
36
  return (
53
- <LikylyRecommendations
54
- placement="pdp-related"
55
- itemId={product.id}
56
- userId={user?.id}
57
- sessionId={sessionId}
58
- limit={4}
59
- />
37
+ <LikylyRecommendations placement="pdp-related" itemId={product.id} limit={4}
38
+ userId={user?.id} sessionId={session.getAnonymousId()} />
60
39
  );
61
40
  }
62
41
  ```
63
42
 
64
- `apiKey` must be your **restricted public key** - this runs in the browser. `Likyly`'s own
65
- constructor (from `@likyly/sdk`) refuses a secret- or developer-shaped key when it detects it's
66
- running in one, so passing the wrong key here fails loudly and immediately rather than shipping
67
- quietly. See [Security](#security) below.
43
+ - `apiKey` is your **public key** (`pk_…`). A secret or developer key is refused in the browser.
44
+ - `pdp-related` is a placement you create once in LIKYLY (console or AI assistant), not in code.
45
+ - `sessionId` is the visitor's anonymous id, kept in the browser (first-party, no cookie). Pass it
46
+ so visitors who are not signed in count too.
68
47
 
69
- Configure the placement itself (`pdp-related` above) once via the LIKYLY MCP admin tools
70
- (`create_placement`) or dashboard - never from this package, which is intentionally
71
- runtime-only. `get_placement_requirements` tells you exactly which of `itemId`/`userId`/
72
- `sessionId`/... a given placement actually needs.
48
+ ## Your own markup
73
49
 
74
- ## Level 1: headless (`useRecommendations`)
50
+ Keep your own cards; impressions and clicks are still tracked:
75
51
 
76
- 100% of the markup is yours; the hook fetches and gives you tracking helpers to call yourself:
52
+ ```tsx
53
+ <LikylyRecommendations
54
+ placement="pdp-related"
55
+ itemId={product.id}
56
+ renderItem={(item, { onClick }) => <ProductCard product={item} onClick={onClick} />}
57
+ />
58
+ ```
59
+
60
+ Or take full control with the hook, and report what was seen and clicked:
77
61
 
78
62
  ```tsx
79
- import { useRecommendations } from "@likyly/react";
63
+ import { useLikyly, useRecommendations } from "@likyly/react";
80
64
 
81
- function RelatedProducts({ product, user, sessionId }) {
82
- const { items, isLoading, strategyUsed, trackImpression, trackClick } = useRecommendations({
65
+ function Related({ product }) {
66
+ const { session } = useLikyly();
67
+ const { items, isLoading, trackImpression, trackClick } = useRecommendations({
83
68
  placement: "pdp-related",
84
- context: { itemId: product.id, userId: user?.id, sessionId },
69
+ context: { itemId: product.id, sessionId: session.getAnonymousId() },
85
70
  limit: 4,
86
71
  });
87
-
88
- if (isLoading) return <Skeleton />;
89
- return (
90
- <div className="my-own-grid">
91
- {items.map((item) => (
92
- <MyOwnCard
93
- key={item.itemId}
94
- item={item}
95
- onVisible={() => trackImpression(item.itemId)}
96
- onClick={() => trackClick(item.itemId)}
97
- />
98
- ))}
99
- </div>
100
- );
72
+ if (isLoading) return null;
73
+ return items.map((item) => (
74
+ <ProductCard key={item.itemId} product={item}
75
+ onVisible={() => trackImpression(item.itemId)} onClick={() => trackClick(item.itemId)} />
76
+ ));
101
77
  }
102
78
  ```
103
79
 
104
- `trackImpression`/`trackClick` are manual here - a headless hook has no DOM for LIKYLY to
105
- observe, so it can't tell what the visitor actually saw. Call `trackImpression` once your own
106
- card is actually visible (e.g. from your own `IntersectionObserver`, or eagerly on render if
107
- that's an acceptable approximation for your UI), and `trackClick` from your card's click handler.
108
-
109
- ## Level 2: ready-to-use (`<LikylyRecommendations>`)
80
+ ## Other events
110
81
 
111
- Fetches, renders, **and auto-tracks** `recommendationImpression` (via `IntersectionObserver` -
112
- fired when a card is actually scrolled into view, not merely fetched) and `recommendationClick`.
113
- Doesn't lock you out of styling it:
114
-
115
- ```tsx
116
- // Default markup, styled via className / the data-likyly-* attributes it sets:
117
- <LikylyRecommendations placement="pdp-related" itemId={product.id} userId={user?.id} className="related-grid" />
118
-
119
- // Full control over each card, while impression/click tracking stays automatic:
120
- <LikylyRecommendations
121
- placement="pdp-related"
122
- itemId={product.id}
123
- userId={user?.id}
124
- renderItem={(item, { onClick }) => (
125
- <MyExistingProductCard product={item} onClick={onClick} />
126
- )}
127
- />
128
- ```
129
-
130
- ## What's never auto-tracked
131
-
132
- `product_view`, `add_to_cart` and `purchase` are **never** fired by this package, on purpose -
133
- LIKYLY has no reliable, universal way to know when your app navigated to a product page, added
134
- something to a cart, or completed a checkout across every possible stack. Instrument these
135
- yourself with the plain SDK helpers, at the point in your own code where each actually happens:
82
+ Product views, add-to-cart and purchases happen in your own code, so send them from there:
136
83
 
137
84
  ```ts
138
- await likyly.events.productView({ itemId: product.id, userId: user?.id, sessionId });
139
- await likyly.events.addToCart({ itemId: product.id, userId: user?.id, sessionId, quantity: 1 });
140
- await likyly.events.purchase({ eventId: `purchase_${orderId}_${item.id}`, itemId: item.id, userId: user?.id, quantity: item.quantity, properties: { price: item.price, currency: "EUR", orderId } });
85
+ const { client } = useLikyly();
86
+ await client.events.productView({ itemId: product.id, userId: user?.id });
87
+ await client.events.addToCart({ itemId: product.id, userId: user?.id, quantity: 1 });
88
+ await client.events.purchase({ eventId: `purchase_${order.id}_${item.id}`, itemId: item.id, userId: user?.id,
89
+ quantity: item.quantity, properties: { price: item.price, currency: "EUR" } });
141
90
  ```
142
91
 
143
- Call `get_tracking_requirements` (an MCP tool, or `GET /placements/{slug}/tracking-requirements`)
144
- for exactly which events a given placement needs, and `get_placement_health` /
145
- `validate_integration` to confirm they're actually arriving once wired up.
92
+ Set `eventId` on purchases: a retried purchase is then never counted twice.
146
93
 
147
- ## Anonymous sessions
94
+ ## Sign-in
148
95
 
149
- `@likyly/sdk`'s `LikylySession` (held by `<LikylyProvider>`, reachable via `useLikyly().session`)
150
- is a first-party, cookieless anonymous id - no need to invent your own:
96
+ At sign-in, attach what the visitor did anonymously to their account:
151
97
 
152
98
  ```tsx
153
- import { useLikyly } from "@likyly/react";
154
-
155
- function LoginForm() {
156
- const { client, session } = useLikyly();
157
- async function onLogin(user) {
158
- await session.identify(client, user.id); // attaches this session's past interactions to them
159
- }
160
- }
99
+ const { client, session } = useLikyly();
100
+ await session.identify(client, user.id);
161
101
  ```
162
102
 
163
- ## Security
164
-
165
- - `<LikylyProvider apiKey>` must be the **public** key. `@likyly/sdk`'s `Likyly` constructor
166
- throws immediately if it detects a secret/developer-shaped key (`sk_`/`lk_` prefix) running
167
- where `window`/`document` exist - i.e. exactly the "this ended up in a browser" mistake.
168
- - Never read that key from a server-only env var, and never name the env var something like
169
- `NEXT_PUBLIC_LIKYLY_SECRET_KEY` - only the public key belongs in a `NEXT_PUBLIC_*`/
170
- `VITE_PUBLIC_*`-style variable at all.
171
- - This package has no method that accepts or needs a secret/developer key - there is
172
- structurally nothing here that would tempt using one. Placement/data-source configuration is
173
- the LIKYLY MCP admin tools' job (secret/developer key, server-side, from your coding agent),
174
- never this runtime package's.
175
-
176
- ## Requirements
103
+ ## Links
177
104
 
178
- React 18 or 19 (`useSyncExternalStore`). Server-rendered frameworks (Next.js, Remix, ...): the
179
- files that use hooks or browser APIs are marked `"use client"` already - use `<LikylyRecommendations>`/
180
- `useRecommendations` from a Client Component.
105
+ - [`@likyly/sdk`](https://www.npmjs.com/package/@likyly/sdk): the JavaScript client this package is built on
106
+ - [likyly.com](https://likyly.com)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@likyly/react",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "React bindings for @likyly/sdk: useRecommendations (headless) and <LikylyRecommendations> (ready-to-use) for LIKYLY placements.",
5
5
  "license": "MIT",
6
6
  "type": "module",