@rudra-js/react 0.8.3 → 0.9.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.
- package/README.md +105 -94
- package/dist/render-context.js +4 -4
- package/dist/render-context.js.map +1 -1
- package/package.json +3 -3
- package/src/render-context.ts +4 -4
package/README.md
CHANGED
|
@@ -6,21 +6,21 @@ rudra-js is an open-source TypeScript library (MIT) you install from npm. A lang
|
|
|
6
6
|
|
|
7
7
|
It is not a hosted platform, a visual or low-code app builder, or a workspace you sign up for.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
You can use this package to render a component spec with React 18 or 19. This spec is a description
|
|
10
|
+
of the block generated by
|
|
11
|
+
[`@rudra-js/core`](https://github.com/clivedsouza1010/rudra-js/tree/main/packages/core).
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
For more on [`RudraComponent`](https://rudrajs.com/reference/react/RudraComponent) and [`extendRegistry`](https://rudrajs.com/reference/react/extendRegistry), see the reference on rudrajs.com.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
All the components are just plain functions with no hooks, state or effects. They can be rendered:
|
|
16
16
|
|
|
17
17
|
- with `renderToString`
|
|
18
18
|
- in a client-rendered app
|
|
19
|
-
- as Server Components, in a setup that supports them
|
|
19
|
+
- as Server Components, in a setup that supports them (like the Next.js App Router)
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
JavaScript. They never
|
|
23
|
-
doesn't run JavaScript
|
|
21
|
+
If you render on the server, the recommendations will be in the initial HTML response and won't need
|
|
22
|
+
any client JavaScript. They never show up late like a client-fetched recommendation rail does. Even
|
|
23
|
+
a crawler that doesn't run JavaScript reads the recommendations.
|
|
24
24
|
|
|
25
25
|
## Install
|
|
26
26
|
|
|
@@ -28,28 +28,30 @@ doesn't run JavaScript still reads them.
|
|
|
28
28
|
npm install @rudra-js/react@latest @rudra-js/core@latest @rudra-js/attested@latest react react-dom
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
You will need to use this same command to install or to upgrade, even if your app already uses
|
|
32
|
+
rudra-js.
|
|
32
33
|
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
names the two A2UI packages.
|
|
34
|
+
- For an app with `@rudra-js/anthropic`, you have to add it to the command with `@latest` too.
|
|
35
|
+
- For an app with `@rudra-js/a2ui`, you have to use the install line in that package's README
|
|
36
|
+
instead. It also names the two A2UI packages.
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
ranges on the others start at its own version. With a bare name, npm
|
|
39
|
-
`package.json
|
|
38
|
+
You have to keep `@latest` on all the `@rudra-js` packages. These packages are published together.
|
|
39
|
+
For each package, the peer ranges on the others start at its own version. With just a bare name, npm
|
|
40
|
+
will stay inside the range you already have in `package.json`. This means that without `@latest`, an
|
|
41
|
+
app on an older core can get an `ERESOLVE`.
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
`react-dom` at a different version from `react
|
|
43
|
+
You need to leave `react` and `react-dom` without a version. It's how npm and pnpm keep a React 18
|
|
44
|
+
app on 18. But do name both, because npm will then move each to the newest release in its range. In
|
|
45
|
+
React 19, a `react-dom` at a different version from `react` won't load.
|
|
44
46
|
|
|
45
|
-
`@rudra-js/core` and `react` are peer dependencies.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
component tree.
|
|
47
|
+
`@rudra-js/core` and `react` are peer dependencies. Your spec comes from your copy of core. This
|
|
48
|
+
package's elements have to come from your app's copy of React. If two copies of core have
|
|
49
|
+
different types, `spec` or another prop can fail its type check. With two copies of React, React
|
|
50
|
+
can refuse to render the component tree.
|
|
49
51
|
|
|
50
|
-
`@rudra-js/attested` is core's peer dependency. npm and pnpm add it for you
|
|
51
|
-
move a bare name to its latest version, so with Yarn, leave `react` and
|
|
52
|
-
if your app already has React.
|
|
52
|
+
`@rudra-js/attested` is core's peer dependency. npm and pnpm will add it for you, but Yarn won't.
|
|
53
|
+
Yarn will move a bare name to its latest version though, so with Yarn, leave `react` and
|
|
54
|
+
`react-dom` out of the command if your app already has React.
|
|
53
55
|
|
|
54
56
|
```tsx
|
|
55
57
|
import { RudraComponent } from '@rudra-js/react';
|
|
@@ -57,12 +59,13 @@ import { RudraComponent } from '@rudra-js/react';
|
|
|
57
59
|
<RudraComponent spec={spec} products={catalog} locale="en-GB" />;
|
|
58
60
|
```
|
|
59
61
|
|
|
60
|
-
`spec` is
|
|
62
|
+
`spec` is the value returned by `createComponentGenerator().generate()`. `products` is your catalog.
|
|
61
63
|
|
|
62
64
|
## What comes from where
|
|
63
65
|
|
|
64
|
-
|
|
65
|
-
read from your catalog when the page is served.
|
|
66
|
+
It's up to the model to decide the layout of the block and the words in it. All of the facts about a
|
|
67
|
+
product will be read from your catalog when the page is served. Anything the model wrote will be
|
|
68
|
+
rendered as escaped text.
|
|
66
69
|
|
|
67
70
|
| Decided by the model | Decided by your catalog |
|
|
68
71
|
| ------------------------------------------------------------------- | ----------------------- |
|
|
@@ -70,41 +73,44 @@ read from your catalog when the page is served. Whatever the model wrote is rend
|
|
|
70
73
|
| Tone, headline, the words in each block | Every price |
|
|
71
74
|
| Only in per-shopper mode: which products, and how each is described | Every image and link |
|
|
72
75
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
itself. In both modes, the model chooses the product a hero names.
|
|
76
|
+
By default you get cohort mode: shoppers who look alike share a spec. In this mode, core will fill
|
|
77
|
+
in the products and their order for each request. It will also write the reason under each product
|
|
78
|
+
and drop any badge the model wrote. If you set `generation: 'per-shopper'`, the model will make
|
|
79
|
+
those picks itself. In both modes, it's the model that chooses the product a hero names. For the
|
|
80
|
+
full split, see
|
|
77
81
|
[What the model decides, by mode](https://github.com/clivedsouza1010/rudra-js/tree/main/packages/core#what-the-model-decides-by-mode).
|
|
78
82
|
|
|
79
|
-
|
|
80
|
-
`products` by a SKU that core has already checked.
|
|
83
|
+
There is no field in the spec for a title, a price, an image or a URL. Instead, the component will
|
|
84
|
+
look up each product in `products` by a SKU that core has already checked.
|
|
81
85
|
|
|
82
|
-
**Validate `products` with `productSchema` from `@rudra-js/core
|
|
83
|
-
products you sent to core already passed. This prop is a second way for data to reach
|
|
86
|
+
**Validate `products` with `productSchema` from `@rudra-js/core`**. It's the same schema that the
|
|
87
|
+
candidate products you sent to core have already passed. This prop is a second way for data to reach
|
|
88
|
+
the page.
|
|
84
89
|
|
|
85
|
-
`imageUrl`
|
|
86
|
-
`//evil.example/pixel.png
|
|
87
|
-
also
|
|
88
|
-
browser
|
|
89
|
-
but none of these.
|
|
90
|
+
`imageUrl` will end up in an `<img src>`. `productSchema` rejects a protocol-relative URL such as
|
|
91
|
+
`//evil.example/pixel.png`. It also rejects a `data:` URL. Because it reads a path the way a browser
|
|
92
|
+
does, it will also reject `/\evil.example/pixel.png` and the same trick written with a tab or a
|
|
93
|
+
newline in it. In a browser, all of these resolve to someone else's host. React will block a
|
|
94
|
+
`javascript:` URL on its own, but none of these.
|
|
90
95
|
|
|
91
|
-
If a price isn't a finite number, rendering
|
|
96
|
+
If a price isn't a finite number, rendering will throw an error.
|
|
92
97
|
|
|
93
|
-
|
|
94
|
-
names it
|
|
95
|
-
in stock in the data you sent it.
|
|
98
|
+
Any row with `isInStock: false` counts as a row that isn't there. Its cards are dropped. If a hero
|
|
99
|
+
names it, the hero will lose its link. If a bundle has it as a member, the bundle will disappear.
|
|
100
|
+
Core will only name products that were in stock in the data you sent it. But this catalog is read
|
|
101
|
+
later, so it may be the fresher of the two.
|
|
96
102
|
|
|
97
103
|
## Styling
|
|
98
104
|
|
|
99
|
-
This package
|
|
100
|
-
inline
|
|
101
|
-
the next. You style it from there.
|
|
105
|
+
This package doesn't ship any CSS. Without styles, the block will render as a single run-on line.
|
|
106
|
+
All of the card elements are inline. This means a card's parts sit on a single line with spaces in
|
|
107
|
+
between. There is nothing to separate a card from the next. You style it from there.
|
|
102
108
|
|
|
103
|
-
For a working stylesheet, see `examples/shop/public/demo-styles.css`. It uses
|
|
104
|
-
|
|
105
|
-
it by default
|
|
109
|
+
For a working stylesheet, see `examples/shop/public/demo-styles.css`. It uses just the classes in
|
|
110
|
+
the table below. You can copy it as a starting point. It isn't a supported API though. The example
|
|
111
|
+
shop applies it by default. You can use `?styles=off` to see the raw markup.
|
|
106
112
|
|
|
107
|
-
|
|
113
|
+
All of the elements the component renders have a class. Here is the full list:
|
|
108
114
|
|
|
109
115
|
| Where | Classes |
|
|
110
116
|
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -120,26 +126,26 @@ Every element the component renders carries a class. Here is the full list:
|
|
|
120
126
|
A few notes on those:
|
|
121
127
|
|
|
122
128
|
- `.rudra__rationale` appears only under `hasDiagnostics`.
|
|
123
|
-
- `.rudra-bundle__label` holds your
|
|
129
|
+
- `.rudra-bundle__label` holds your name for the set. It will only show up for a bundle you gave a
|
|
124
130
|
`label`.
|
|
125
|
-
- Your `className`
|
|
131
|
+
- Your `className` will be added alongside `rudra`, never in place of it, to keep the child classes
|
|
126
132
|
working.
|
|
127
|
-
- `.rudra-carousel__track` is meant to scroll horizontally, so give it `overflow-x: auto`.
|
|
128
|
-
JavaScript
|
|
133
|
+
- `.rudra-carousel__track` is meant to scroll horizontally, so give it `overflow-x: auto`. There is
|
|
134
|
+
no JavaScript to scroll it for you.
|
|
129
135
|
- `.rudra-card--featured` is applied alongside `.rudra-card`. Write the `.rudra-card--featured { ... }`
|
|
130
|
-
rule after the base rule
|
|
136
|
+
rule after the base rule to layer it on top.
|
|
131
137
|
|
|
132
|
-
`.rudra-banner__cta` and `.rudra-bundle__cta` are plain text
|
|
133
|
-
them as buttons unless a renderer of your own wraps them in one.
|
|
134
|
-
the product link
|
|
138
|
+
`.rudra-banner__cta` and `.rudra-bundle__cta` are plain text. They are not links or buttons. Don't
|
|
139
|
+
style them as buttons unless a renderer of your own wraps them in one. For the hero, the call to
|
|
140
|
+
action sits inside the product link. This means the hero's call to action is clickable.
|
|
135
141
|
|
|
136
|
-
|
|
137
|
-
|
|
142
|
+
All of the cards in a carousel are links. If you tab from card to card, the track will scroll. This
|
|
143
|
+
way you can use the carousel by keyboard, even with the scrollbar hidden.
|
|
138
144
|
|
|
139
145
|
### Attributes
|
|
140
146
|
|
|
141
|
-
|
|
142
|
-
|
|
147
|
+
In the markup you will also find the model's decisions. You can use these attributes for styling or
|
|
148
|
+
analytics.
|
|
143
149
|
|
|
144
150
|
| Attribute | On | Value |
|
|
145
151
|
| ------------------------ | ------------------------------ | ---------------------------------------------------------- |
|
|
@@ -151,9 +157,9 @@ attributes.
|
|
|
151
157
|
| `data-rudra-sku` | a card, hero link, bundle item | The product, for click attribution |
|
|
152
158
|
| `data-rudra-basis` | a card | Why the product was picked: `most_viewed`, `popular`, … |
|
|
153
159
|
|
|
154
|
-
`data-rudra-source` is always there
|
|
155
|
-
|
|
156
|
-
tells a visitor what you run and when it's failing:
|
|
160
|
+
`data-rudra-source` is always there. This lets you read the cache hit rate and the fallback share
|
|
161
|
+
directly from a rendered page. Anything more specific will only appear under `hasDiagnostics`,
|
|
162
|
+
because it tells a visitor what you run and when it's failing:
|
|
157
163
|
|
|
158
164
|
| Attribute | On | Value |
|
|
159
165
|
| ----------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -164,8 +170,8 @@ tells a visitor what you run and when it's failing:
|
|
|
164
170
|
|
|
165
171
|
## Replacing a renderer
|
|
166
172
|
|
|
167
|
-
|
|
168
|
-
spec
|
|
173
|
+
You can swap any block's renderer for a component from your design system. Only the drawing changes.
|
|
174
|
+
Neither the spec nor the block kinds the model picks from will change.
|
|
169
175
|
|
|
170
176
|
```tsx
|
|
171
177
|
import { RudraComponent, extendRegistry } from '@rudra-js/react';
|
|
@@ -177,9 +183,10 @@ const registry = extendRegistry({
|
|
|
177
183
|
<RudraComponent spec={spec} products={catalog} registry={registry} />;
|
|
178
184
|
```
|
|
179
185
|
|
|
180
|
-
|
|
181
|
-
runs, yours or ours.
|
|
182
|
-
|
|
186
|
+
If a grid, carousel or bundle block has nothing left in your catalog, it will be dropped before any
|
|
187
|
+
renderer runs, yours or ours. But the rest of the blocks will still render. Banner and copy blocks
|
|
188
|
+
will always reach the renderer because they don't depend on the catalog. Heroes without a headline
|
|
189
|
+
or body will need an in-stock SKU in your catalog to get there.
|
|
183
190
|
|
|
184
191
|
## Props
|
|
185
192
|
|
|
@@ -198,33 +205,36 @@ depend on the catalog, so they always reach the renderer.
|
|
|
198
205
|
|
|
199
206
|
### What `products` may be
|
|
200
207
|
|
|
201
|
-
`products`
|
|
202
|
-
`
|
|
203
|
-
component tells a keyed catalog from a list. If your catalog is too big to
|
|
204
|
-
request, pass a view of your
|
|
205
|
-
TypeScript either implement `ReadonlyMap` or cast
|
|
208
|
+
`products` has to be a list of products, or anything keyed by SKU that answers `get(sku)` and
|
|
209
|
+
`has(sku)`. This means you can use a `Map` or your own index. The renderers will only ever call
|
|
210
|
+
`get`. `has` is how the component tells a keyed catalog from a list. If your catalog is too big to
|
|
211
|
+
copy into a `Map` on every request, pass a view of your store instead. This prop is typed as a
|
|
212
|
+
`ReadonlyMap`. In TypeScript that means your view has to either implement `ReadonlyMap` or be cast
|
|
213
|
+
with `as unknown as ReadonlyMap<string, Product>`.
|
|
206
214
|
|
|
207
|
-
|
|
215
|
+
You can also pass a `Map` from a worker or a `node:vm` sandbox, even though it fails
|
|
216
|
+
`instanceof Map`.
|
|
208
217
|
|
|
209
|
-
Anything that is neither a list nor keyed
|
|
210
|
-
|
|
218
|
+
Anything that is neither a list nor keyed will be refused right away, with an error that names the
|
|
219
|
+
prop. This covers a `Set` of products, a plain object or a `Map` that has been through JSON.
|
|
211
220
|
|
|
212
|
-
|
|
213
|
-
blocks
|
|
221
|
+
If there's nothing left to show, the component will render nothing at all. This covers a spec with
|
|
222
|
+
no blocks. It also covers a spec whose products have all left your catalog since it was generated.
|
|
214
223
|
|
|
215
224
|
### What `bundles` is
|
|
216
225
|
|
|
217
|
-
`bundles`
|
|
218
|
-
model only
|
|
226
|
+
`bundles` is a list of the sets your shop sells together, just like `products` is your catalog. As
|
|
227
|
+
for the model, it will only ask for a bundle block. It never invents a set and it never sees a
|
|
228
|
+
price.
|
|
219
229
|
|
|
220
|
-
You offer the sets,
|
|
221
|
-
as the page is served, after the spec was generated. It goes by what this shopper
|
|
222
|
-
basket, has looked at
|
|
223
|
-
|
|
224
|
-
|
|
230
|
+
You offer the sets, but it's core that picks which set fills each bundle block. It does this in
|
|
231
|
+
`reconcileSpec` as the page is served, after the spec was generated. It goes by what this shopper
|
|
232
|
+
has in their basket, has looked at or is browsing right now. After this, the spec will carry the id
|
|
233
|
+
of the set core picked. You supply the rest with this prop (the set's members, its price, the
|
|
234
|
+
currency of that price and your name for it) to give the component something to draw.
|
|
225
235
|
|
|
226
|
-
**Validate `bundles` with `bundleSchema` from `@rudra-js/core
|
|
227
|
-
`parseTrackingInput`.**
|
|
236
|
+
**Validate `bundles` with `bundleSchema` from `@rudra-js/core` and pass the same list you sent to
|
|
237
|
+
`parseTrackingInput`.** It's the list core checked before it picked a set:
|
|
228
238
|
|
|
229
239
|
- every member is in stock
|
|
230
240
|
- no member is one the shopper disliked
|
|
@@ -232,8 +242,9 @@ the currency of that price, and your name for it.
|
|
|
232
242
|
- no product shows up twice
|
|
233
243
|
- the whole set fits in what's left of `context.maxItems`, the most products the component may show
|
|
234
244
|
|
|
235
|
-
|
|
236
|
-
draw a set none of those checks saw, under an id that was checked against another
|
|
245
|
+
After that, core will only give the renderer the id it chose. If you pass a stale or different list
|
|
246
|
+
here, you'll draw a set that none of those checks saw, under an id that was checked against another
|
|
247
|
+
list.
|
|
237
248
|
|
|
238
249
|
## Licence
|
|
239
250
|
|
package/dist/render-context.js
CHANGED
|
@@ -17,15 +17,15 @@ function formatMoney(price, currency, locale) {
|
|
|
17
17
|
}
|
|
18
18
|
export function defaultFormatPrice(product, locale) {
|
|
19
19
|
if (!Number.isFinite(product.price)) {
|
|
20
|
-
throw new TypeError(`price for SKU ${product.sku} is ${String(product.price)}, not a finite number
|
|
21
|
-
'
|
|
20
|
+
throw new TypeError(`price for SKU ${product.sku} is ${String(product.price)}, not a finite number. ` +
|
|
21
|
+
'Catalog objects must satisfy productSchema from @rudra-js/core');
|
|
22
22
|
}
|
|
23
23
|
return formatMoney(product.price, product.currency, locale);
|
|
24
24
|
}
|
|
25
25
|
export function defaultFormatBundlePrice(bundle, locale) {
|
|
26
26
|
if (!Number.isFinite(bundle.price)) {
|
|
27
|
-
throw new TypeError(`price for bundle ${bundle.id} is ${String(bundle.price)}, not a finite number
|
|
28
|
-
'
|
|
27
|
+
throw new TypeError(`price for bundle ${bundle.id} is ${String(bundle.price)}, not a finite number. ` +
|
|
28
|
+
'Bundle objects must satisfy bundleSchema from @rudra-js/core');
|
|
29
29
|
}
|
|
30
30
|
return formatMoney(bundle.price, bundle.currency, locale);
|
|
31
31
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render-context.js","sourceRoot":"","sources":["../src/render-context.ts"],"names":[],"mappings":"AAUA,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,OAAO,YAAY,kBAAkB,CAAC,GAAG,CAAC,EAAE,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,QAAsC,EACtC,GAAW;IAEX,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAClC,OAAO,OAAO,EAAE,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;AAC5D,CAAC;AAED,SAAS,WAAW,CAAC,KAAa,EAAE,QAAgB,EAAE,MAAe;IACnE,IAAI,SAA4B,CAAC;IACjC,IAAI,CAAC;QACH,SAAS,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC7E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,QAAQ,IAAI,KAAK,EAAE,CAAC;IAChC,CAAC;IACD,OAAO,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,OAAgB,EAAE,MAAe;IAClE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,SAAS,CACjB,iBAAiB,OAAO,CAAC,GAAG,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,
|
|
1
|
+
{"version":3,"file":"render-context.js","sourceRoot":"","sources":["../src/render-context.ts"],"names":[],"mappings":"AAUA,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,OAAO,YAAY,kBAAkB,CAAC,GAAG,CAAC,EAAE,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,QAAsC,EACtC,GAAW;IAEX,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAClC,OAAO,OAAO,EAAE,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;AAC5D,CAAC;AAED,SAAS,WAAW,CAAC,KAAa,EAAE,QAAgB,EAAE,MAAe;IACnE,IAAI,SAA4B,CAAC;IACjC,IAAI,CAAC;QACH,SAAS,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC7E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,QAAQ,IAAI,KAAK,EAAE,CAAC;IAChC,CAAC;IACD,OAAO,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,OAAgB,EAAE,MAAe;IAClE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,SAAS,CACjB,iBAAiB,OAAO,CAAC,GAAG,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,yBAAyB;YAC/E,gEAAgE,CACnE,CAAC;IACJ,CAAC;IACD,OAAO,WAAW,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;AAC9D,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,MAAc,EAAE,MAAe;IACtE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,SAAS,CACjB,oBAAoB,MAAM,CAAC,EAAE,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,yBAAyB;YAC/E,8DAA8D,CACjE,CAAC;IACJ,CAAC;IACD,OAAO,WAAW,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;AAC5D,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rudra-js/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "React renderer for rudra-js: renders an LLM-designed product-recommendation block from your own catalog, on the server or in the browser",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"rudra-js",
|
|
@@ -54,11 +54,11 @@
|
|
|
54
54
|
"access": "public"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
|
-
"@rudra-js/core": "^0.
|
|
57
|
+
"@rudra-js/core": "^0.9.1",
|
|
58
58
|
"react": "^18.2.0 || ^19.0.0"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
|
-
"@rudra-js/core": "0.
|
|
61
|
+
"@rudra-js/core": "0.9.1",
|
|
62
62
|
"@types/react": "^19.3.0",
|
|
63
63
|
"@types/react-dom": "^19.3.0",
|
|
64
64
|
"react": "^19.3.1",
|
package/src/render-context.ts
CHANGED
|
@@ -33,8 +33,8 @@ function formatMoney(price: number, currency: string, locale?: string): string {
|
|
|
33
33
|
export function defaultFormatPrice(product: Product, locale?: string): string {
|
|
34
34
|
if (!Number.isFinite(product.price)) {
|
|
35
35
|
throw new TypeError(
|
|
36
|
-
`price for SKU ${product.sku} is ${String(product.price)}, not a finite number
|
|
37
|
-
'
|
|
36
|
+
`price for SKU ${product.sku} is ${String(product.price)}, not a finite number. ` +
|
|
37
|
+
'Catalog objects must satisfy productSchema from @rudra-js/core',
|
|
38
38
|
);
|
|
39
39
|
}
|
|
40
40
|
return formatMoney(product.price, product.currency, locale);
|
|
@@ -43,8 +43,8 @@ export function defaultFormatPrice(product: Product, locale?: string): string {
|
|
|
43
43
|
export function defaultFormatBundlePrice(bundle: Bundle, locale?: string): string {
|
|
44
44
|
if (!Number.isFinite(bundle.price)) {
|
|
45
45
|
throw new TypeError(
|
|
46
|
-
`price for bundle ${bundle.id} is ${String(bundle.price)}, not a finite number
|
|
47
|
-
'
|
|
46
|
+
`price for bundle ${bundle.id} is ${String(bundle.price)}, not a finite number. ` +
|
|
47
|
+
'Bundle objects must satisfy bundleSchema from @rudra-js/core',
|
|
48
48
|
);
|
|
49
49
|
}
|
|
50
50
|
return formatMoney(bundle.price, bundle.currency, locale);
|