@rudra-js/react 0.5.0 → 0.7.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,17 +1,20 @@
1
1
  # @rudra-js/react
2
2
 
3
3
  Renders a component specification from
4
- [`@rudra-js/core`](https://github.com/clivedsouza1010/rudra-js/tree/main/packages/core) as React
5
- Server Components.
4
+ [`@rudra-js/core`](https://github.com/clivedsouza1010/rudra-js/tree/main/packages/core) with React
5
+ 18 or 19.
6
6
 
7
- No client JavaScript. The recommendation area arrives in the initial HTML response and needs no
8
- hydration, so it never pops in the way a client-fetched recommendation rail does, and a crawler that
9
- doesn't run JavaScript still reads it.
7
+ The components are plain functions with no hooks, state or effects. They work with
8
+ `renderToString`, in a client-rendered app, and as Server Components in a setup that supports them,
9
+ such as the Next.js App Router. Rendered on the server, the
10
+ recommendation area arrives in the initial HTML response and needs no client JavaScript, so it never
11
+ pops in the way a client-fetched recommendation rail does, and a crawler that doesn't run JavaScript
12
+ still reads it.
10
13
 
11
14
  ## Install
12
15
 
13
16
  ```sh
14
- npm install @rudra-js/react @rudra-js/core react zod@^4
17
+ npm install @rudra-js/react @rudra-js/core react
15
18
  ```
16
19
 
17
20
  Both `@rudra-js/core` and `react` are peer dependencies. The specification you pass in comes from
@@ -51,9 +54,16 @@ resolved at render time from `products`, keyed by a SKU reconciliation has alrea
51
54
  **Validate `products` with `productSchema` from `@rudra-js/core`**, the same schema your candidates
52
55
  already passed. This prop is a second door into the framework. `imageUrl` lands in an `<img src>`,
53
56
  and `productSchema` is what rejects a protocol-relative `//evil.example/pixel.png` or a `data:` URL.
54
- React neutralises a `javascript:` URL by itself, but not those two. And if a price isn't a finite
57
+ It reads a path the way a browser does, so `/\evil.example/pixel.png` and the same trick written
58
+ with a tab or a newline in it are rejected too — each one resolves to someone else's host. React
59
+ neutralises a `javascript:` URL by itself, but not any of those. And if a price isn't a finite
55
60
  number, it throws. A product that looks free is worse than a stack trace.
56
61
 
62
+ A row flagged `isInStock: false` counts as a row that isn't there. Cards for it are dropped, a hero
63
+ loses its link, and a bundle with one sold-out member disappears — the same as if you'd left the
64
+ row out. Core only ever names a product that was in stock in the payload, but this catalog is read
65
+ later and may be the fresher of the two.
66
+
57
67
  ## Styling
58
68
 
59
69
  We ship no CSS. A stylesheet of ours would only fight whatever your site already has. So out of the
@@ -127,6 +137,11 @@ const registry = extendRegistry({
127
137
  <RudraComponent spec={spec} products={catalog} registry={registry} />;
128
138
  ```
129
139
 
140
+ A grid, carousel or bundle block with nothing left in your catalog is dropped before any renderer
141
+ runs — yours as well as ours. The component has to decide whether to draw the wrapper and its
142
+ headline at all, and the only way to decide is to ask each block first. Hero, banner and copy always
143
+ reach the renderer, since they don't depend on the catalog.
144
+
130
145
  ## Props
131
146
 
132
147
  | Prop | Notes |
@@ -157,9 +172,10 @@ That's a `Set` of products, a plain object, or a `Map` that has been through JSO
157
172
  error than a quietly empty recommendation area.
158
173
 
159
174
  When there's nothing left to show, the component renders nothing at all. That covers a spec with no
160
- blocks, and one whose every product has left your catalog since it was generated. An empty
161
- recommendation area, or a headline over an empty box, takes up space and tells the shopper the page
162
- is broken.
175
+ blocks, and one whose every product has left your catalog since it was generated. A single block
176
+ goes the same way on its own: it drops out and the rest of the component carries on without it. An
177
+ empty recommendation area, or a headline over an empty box, takes up space and tells the shopper the
178
+ page is broken.
163
179
 
164
180
  ### What `bundles` is
165
181
 
@@ -174,9 +190,10 @@ in, and your name for it, so the component has something to draw.
174
190
 
175
191
  **Validate `bundles` with `bundleSchema` from `@rudra-js/core`, and pass the same list you sent to
176
192
  `parseTrackingInput`.** Core already checked that list — every member in stock, none of them
177
- disliked, no repeats, and the whole set inside the item budget — and then hands the renderer nothing
178
- but the id it chose. Pass a stale or different list here and you'll draw a set none of those checks
179
- ever saw, under an id that was proved against another one.
193
+ disliked, none of them the product being looked at, no repeats, and the whole set inside the item
194
+ budget — and then hands the renderer nothing but the id it chose. Pass a stale or different list
195
+ here and you'll draw a set none of those checks ever saw, under an id that was proved against
196
+ another one.
180
197
 
181
198
  ## Licence
182
199
 
@@ -8,7 +8,7 @@
8
8
  * and wording.
9
9
  */
10
10
  import type { BannerBlock, CarouselBlock, CopyBlock, GridBlock, HeroBlock } from '@rudra-js/core';
11
- import type { BlockRenderContext } from '../render-context.js';
11
+ import { type BlockRenderContext } from '../render-context.js';
12
12
  export declare function HeroRenderer({ block, context, }: {
13
13
  block: HeroBlock;
14
14
  context: BlockRenderContext;
@@ -1 +1 @@
1
- {"version":3,"file":"block-renderers.d.ts","sourceRoot":"","sources":["../../src/blocks/block-renderers.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAClG,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAG/D,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,OAAO,GACR,EAAE;IACD,KAAK,EAAE,SAAS,CAAC;IACjB,OAAO,EAAE,kBAAkB,CAAC;CAC7B,+BAuBA;AAED,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,OAAO,GACR,EAAE;IACD,KAAK,EAAE,SAAS,CAAC;IACjB,OAAO,EAAE,kBAAkB,CAAC;CAC7B,+BAWA;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EACL,OAAO,GACR,EAAE;IACD,KAAK,EAAE,aAAa,CAAC;IACrB,OAAO,EAAE,kBAAkB,CAAC;CAC7B,+BAaA;AAED,wBAAgB,cAAc,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,WAAW,CAAA;CAAE,+BAO/D;AAED,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,SAAS,CAAA;CAAE,+BAO3D"}
1
+ {"version":3,"file":"block-renderers.d.ts","sourceRoot":"","sources":["../../src/blocks/block-renderers.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAClG,OAAO,EAAmB,KAAK,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAGhF,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,OAAO,GACR,EAAE;IACD,KAAK,EAAE,SAAS,CAAC;IACjB,OAAO,EAAE,kBAAkB,CAAC;CAC7B,+BAoBA;AAED,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,OAAO,GACR,EAAE;IACD,KAAK,EAAE,SAAS,CAAC;IACjB,OAAO,EAAE,kBAAkB,CAAC;CAC7B,+BAWA;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EACL,OAAO,GACR,EAAE;IACD,KAAK,EAAE,aAAa,CAAC;IACrB,OAAO,EAAE,kBAAkB,CAAC;CAC7B,+BAaA;AAED,wBAAgB,cAAc,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,WAAW,CAAA;CAAE,+BAO/D;AAED,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,SAAS,CAAA;CAAE,+BAO3D"}
@@ -1,8 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { sellableProduct } from '../render-context.js';
2
3
  import { ProductCard } from './product-card.js';
3
4
  export function HeroRenderer({ block, context, }) {
4
- const product = block.sku ? context.products.get(block.sku) : undefined;
5
- return (_jsxs("section", { className: "rudra-hero", children: [_jsx("h3", { className: "rudra-hero__headline", children: block.headline }), block.body ? _jsx("p", { className: "rudra-hero__body", children: block.body }) : null, product ? (_jsxs("a", { className: "rudra-hero__link", href: context.hrefForSku(product.sku), "data-rudra-sku": product.sku, children: [product.title, _jsx("span", { className: "rudra-hero__price", children: context.formatPrice(product) })] })) : null, product && block.ctaLabel ? _jsx("span", { className: "rudra-hero__cta", children: block.ctaLabel }) : null] }));
5
+ const product = block.sku ? sellableProduct(context.products, block.sku) : undefined;
6
+ return (_jsxs("section", { className: "rudra-hero", children: [_jsx("h3", { className: "rudra-hero__headline", children: block.headline }), block.body ? _jsx("p", { className: "rudra-hero__body", children: block.body }) : null, product ? (_jsxs("a", { className: "rudra-hero__link", href: context.hrefForSku(product.sku), "data-rudra-sku": product.sku, children: [product.title, _jsx("span", { className: "rudra-hero__price", children: context.formatPrice(product) }), block.ctaLabel ? _jsx("span", { className: "rudra-hero__cta", children: block.ctaLabel }) : null] })) : null] }));
6
7
  }
7
8
  export function GridRenderer({ block, context, }) {
8
9
  return (_jsxs("section", { className: "rudra-grid", "data-rudra-columns": block.columns, children: [block.title ? _jsx("h3", { className: "rudra-grid__title", children: block.title }) : null, _jsx("div", { className: "rudra-grid__items", children: block.items.map((reference) => (_jsx(ProductCard, { reference: reference, context: context }, reference.sku))) })] }));
@@ -1 +1 @@
1
- {"version":3,"file":"block-renderers.js","sourceRoot":"","sources":["../../src/blocks/block-renderers.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"block-renderers.js","sourceRoot":"","sources":["../../src/blocks/block-renderers.tsx"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import type { BundleBlock } from '@rudra-js/core';
2
- import type { BlockRenderContext } from '../render-context.js';
2
+ import { type BlockRenderContext } from '../render-context.js';
3
3
  export declare function BundleRenderer({ block, context, }: {
4
4
  block: BundleBlock;
5
5
  context: BlockRenderContext;
@@ -1 +1 @@
1
- {"version":3,"file":"bundle-block.d.ts","sourceRoot":"","sources":["../../src/blocks/bundle-block.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAW,MAAM,gBAAgB,CAAC;AAC3D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE/D,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,OAAO,GACR,EAAE;IACD,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,EAAE,kBAAkB,CAAC;CAC7B,sCAiCA"}
1
+ {"version":3,"file":"bundle-block.d.ts","sourceRoot":"","sources":["../../src/blocks/bundle-block.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAW,MAAM,gBAAgB,CAAC;AAC3D,OAAO,EAAmB,KAAK,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAEhF,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,OAAO,GACR,EAAE;IACD,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,EAAE,kBAAkB,CAAC;CAC7B,sCAiCA"}
@@ -1,11 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { sellableProduct } from '../render-context.js';
2
3
  export function BundleRenderer({ block, context, }) {
3
4
  const bundle = block.bundleId === null ? undefined : context.bundles.get(block.bundleId);
4
5
  if (!bundle)
5
6
  return null;
6
7
  const products = [];
7
8
  for (const sku of bundle.skus) {
8
- const product = context.products.get(sku);
9
+ const product = sellableProduct(context.products, sku);
9
10
  // A set missing one of its parts is not that set.
10
11
  if (!product)
11
12
  return null;
@@ -1 +1 @@
1
- {"version":3,"file":"bundle-block.js","sourceRoot":"","sources":["../../src/blocks/bundle-block.tsx"],"names":[],"mappings":";AAGA,MAAM,UAAU,cAAc,CAAC,EAC7B,KAAK,EACL,OAAO,GAIR;IACC,MAAM,MAAM,GAAG,KAAK,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACzF,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAEzB,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC1C,kDAAkD;QAClD,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC;QAC1B,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACzB,CAAC;IAED,OAAO,CACL,mBAAS,SAAS,EAAC,cAAc,aAE9B,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,aAAI,SAAS,EAAC,qBAAqB,YAAE,MAAM,CAAC,KAAK,GAAM,CAAC,CAAC,CAAC,IAAI,EAC7E,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,YAAG,SAAS,EAAC,qBAAqB,YAAE,KAAK,CAAC,KAAK,GAAK,CAAC,CAAC,CAAC,IAAI,EACzE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,YAAG,SAAS,EAAC,oBAAoB,YAAE,KAAK,CAAC,IAAI,GAAK,CAAC,CAAC,CAAC,IAAI,EAEvE,aAAI,SAAS,EAAC,qBAAqB,YAChC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CACzB,aAAsB,SAAS,EAAC,oBAAoB,oBAAiB,OAAO,CAAC,GAAG,YAC9E,YAAG,SAAS,EAAC,oBAAoB,EAAC,IAAI,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,YACpE,OAAO,CAAC,KAAK,GACZ,IAHG,OAAO,CAAC,GAAG,CAIf,CACN,CAAC,GACC,EAEL,YAAG,SAAS,EAAC,qBAAqB,YAAE,OAAO,CAAC,iBAAiB,CAAC,MAAM,CAAC,GAAK,EACzE,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,eAAM,SAAS,EAAC,mBAAmB,YAAE,KAAK,CAAC,QAAQ,GAAQ,CAAC,CAAC,CAAC,IAAI,IAC5E,CACX,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"bundle-block.js","sourceRoot":"","sources":["../../src/blocks/bundle-block.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,eAAe,EAA2B,MAAM,sBAAsB,CAAC;AAEhF,MAAM,UAAU,cAAc,CAAC,EAC7B,KAAK,EACL,OAAO,GAIR;IACC,MAAM,MAAM,GAAG,KAAK,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACzF,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAEzB,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;QACvD,kDAAkD;QAClD,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC;QAC1B,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACzB,CAAC;IAED,OAAO,CACL,mBAAS,SAAS,EAAC,cAAc,aAE9B,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,aAAI,SAAS,EAAC,qBAAqB,YAAE,MAAM,CAAC,KAAK,GAAM,CAAC,CAAC,CAAC,IAAI,EAC7E,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,YAAG,SAAS,EAAC,qBAAqB,YAAE,KAAK,CAAC,KAAK,GAAK,CAAC,CAAC,CAAC,IAAI,EACzE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,YAAG,SAAS,EAAC,oBAAoB,YAAE,KAAK,CAAC,IAAI,GAAK,CAAC,CAAC,CAAC,IAAI,EAEvE,aAAI,SAAS,EAAC,qBAAqB,YAChC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CACzB,aAAsB,SAAS,EAAC,oBAAoB,oBAAiB,OAAO,CAAC,GAAG,YAC9E,YAAG,SAAS,EAAC,oBAAoB,EAAC,IAAI,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,YACpE,OAAO,CAAC,KAAK,GACZ,IAHG,OAAO,CAAC,GAAG,CAIf,CACN,CAAC,GACC,EAEL,YAAG,SAAS,EAAC,qBAAqB,YAAE,OAAO,CAAC,iBAAiB,CAAC,MAAM,CAAC,GAAK,EACzE,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,eAAM,SAAS,EAAC,mBAAmB,YAAE,KAAK,CAAC,QAAQ,GAAQ,CAAC,CAAC,CAAC,IAAI,IAC5E,CACX,CAAC;AACJ,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import type { ProductReference } from '@rudra-js/core';
2
- import type { BlockRenderContext } from '../render-context.js';
2
+ import { type BlockRenderContext } from '../render-context.js';
3
3
  /**
4
4
  * One product.
5
5
  *
@@ -1 +1 @@
1
- {"version":3,"file":"product-card.d.ts","sourceRoot":"","sources":["../../src/blocks/product-card.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE/D;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,EAC1B,SAAS,EACT,OAAO,GACR,EAAE;IACD,SAAS,EAAE,gBAAgB,CAAC;IAC5B,OAAO,EAAE,kBAAkB,CAAC;CAC7B,sCA2BA"}
1
+ {"version":3,"file":"product-card.d.ts","sourceRoot":"","sources":["../../src/blocks/product-card.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAmB,KAAK,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAEhF;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,EAC1B,SAAS,EACT,OAAO,GACR,EAAE;IACD,SAAS,EAAE,gBAAgB,CAAC;IAC5B,OAAO,EAAE,kBAAkB,CAAC;CAC7B,sCA2BA"}
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { sellableProduct } from '../render-context.js';
2
3
  /**
3
4
  * One product.
4
5
  *
@@ -8,7 +9,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
8
9
  * into the markup.
9
10
  */
10
11
  export function ProductCard({ reference, context, }) {
11
- const product = context.products.get(reference.sku);
12
+ const product = sellableProduct(context.products, reference.sku);
12
13
  // Reconciliation drops a SKU the catalog does not have, so this should be
13
14
  // unreachable. Rendering nothing beats rendering a card with holes in it.
14
15
  if (!product)
@@ -1 +1 @@
1
- {"version":3,"file":"product-card.js","sourceRoot":"","sources":["../../src/blocks/product-card.tsx"],"names":[],"mappings":";AAGA;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,EAC1B,SAAS,EACT,OAAO,GAIR;IACC,MAAM,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IACpD,0EAA0E;IAC1E,0EAA0E;IAC1E,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAE1B,MAAM,UAAU,GAAG,SAAS,CAAC,QAAQ,KAAK,UAAU,CAAC;IAErD,OAAO,CACL,aACE,IAAI,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,oBACrB,OAAO,CAAC,GAAG,sBACT,SAAS,CAAC,KAAK,EACjC,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC,iCAAiC,CAAC,CAAC,CAAC,YAAY,aAEvE,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAClB,cAAK,SAAS,EAAC,mBAAmB,EAAC,GAAG,EAAE,OAAO,CAAC,QAAQ,EAAE,GAAG,EAAC,EAAE,EAAC,OAAO,EAAC,MAAM,GAAG,CACnF,CAAC,CAAC,CAAC,IAAI,EAER,gBAAM,SAAS,EAAC,kBAAkB,aAC/B,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,eAAM,SAAS,EAAC,mBAAmB,YAAE,SAAS,CAAC,KAAK,GAAQ,CAAC,CAAC,CAAC,IAAI,EACtF,eAAM,SAAS,EAAC,mBAAmB,YAAE,OAAO,CAAC,KAAK,GAAQ,EAC1D,eAAM,SAAS,EAAC,mBAAmB,YAAE,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,GAAQ,EACxE,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,eAAM,SAAS,EAAC,oBAAoB,YAAE,SAAS,CAAC,MAAM,GAAQ,CAAC,CAAC,CAAC,IAAI,IACpF,IACL,CACL,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"product-card.js","sourceRoot":"","sources":["../../src/blocks/product-card.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,eAAe,EAA2B,MAAM,sBAAsB,CAAC;AAEhF;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,EAC1B,SAAS,EACT,OAAO,GAIR;IACC,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,CAAC,QAAQ,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC;IACjE,0EAA0E;IAC1E,0EAA0E;IAC1E,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAE1B,MAAM,UAAU,GAAG,SAAS,CAAC,QAAQ,KAAK,UAAU,CAAC;IAErD,OAAO,CACL,aACE,IAAI,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,oBACrB,OAAO,CAAC,GAAG,sBACT,SAAS,CAAC,KAAK,EACjC,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC,iCAAiC,CAAC,CAAC,CAAC,YAAY,aAEvE,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAClB,cAAK,SAAS,EAAC,mBAAmB,EAAC,GAAG,EAAE,OAAO,CAAC,QAAQ,EAAE,GAAG,EAAC,EAAE,EAAC,OAAO,EAAC,MAAM,GAAG,CACnF,CAAC,CAAC,CAAC,IAAI,EAER,gBAAM,SAAS,EAAC,kBAAkB,aAC/B,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,eAAM,SAAS,EAAC,mBAAmB,YAAE,SAAS,CAAC,KAAK,GAAQ,CAAC,CAAC,CAAC,IAAI,EACtF,eAAM,SAAS,EAAC,mBAAmB,YAAE,OAAO,CAAC,KAAK,GAAQ,EAC1D,eAAM,SAAS,EAAC,mBAAmB,YAAE,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,GAAQ,EACxE,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,eAAM,SAAS,EAAC,oBAAoB,YAAE,SAAS,CAAC,MAAM,GAAQ,CAAC,CAAC,CAAC,IAAI,IACpF,IACL,CACL,CAAC;AACJ,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,9 +1,3 @@
1
- /**
2
- * @rudra-js/react — renders a component specification as React Server Components.
3
- *
4
- * Carries no client JavaScript. The recommendation area arrives in the initial
5
- * HTML response and needs no hydration.
6
- */
7
1
  export { RudraComponent, type ProductCatalog, type RudraComponentProps, } from './rudra-component.js';
8
2
  export { defaultRegistry, extendRegistry, type BlockRegistry, type BlockRenderer, } from './registry.js';
9
3
  export { defaultFormatBundlePrice, defaultFormatPrice, defaultHrefForSku, type BlockRenderContext, } from './render-context.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,cAAc,EACd,KAAK,cAAc,EACnB,KAAK,mBAAmB,GACzB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,eAAe,EACf,cAAc,EACd,KAAK,aAAa,EAClB,KAAK,aAAa,GACnB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,wBAAwB,EACxB,kBAAkB,EAClB,iBAAiB,EACjB,KAAK,kBAAkB,GACxB,MAAM,qBAAqB,CAAC;AAK7B,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,KAAK,cAAc,EACnB,KAAK,mBAAmB,GACzB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,eAAe,EACf,cAAc,EACd,KAAK,aAAa,EAClB,KAAK,aAAa,GACnB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,wBAAwB,EACxB,kBAAkB,EAClB,iBAAiB,EACjB,KAAK,kBAAkB,GACxB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC"}
package/dist/index.js CHANGED
@@ -1,14 +1,5 @@
1
- /**
2
- * @rudra-js/react — renders a component specification as React Server Components.
3
- *
4
- * Carries no client JavaScript. The recommendation area arrives in the initial
5
- * HTML response and needs no hydration.
6
- */
7
1
  export { RudraComponent, } from './rudra-component.js';
8
2
  export { defaultRegistry, extendRegistry, } from './registry.js';
9
3
  export { defaultFormatBundlePrice, defaultFormatPrice, defaultHrefForSku, } from './render-context.js';
10
- // The six default renderers reach hosts through `defaultRegistry`, which is
11
- // also the thing they need in order to override one. `ProductCard` is exported
12
- // on its own because a custom grid renderer still wants the standard card.
13
4
  export { ProductCard } from './blocks/product-card.js';
14
5
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,cAAc,GAGf,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,eAAe,EACf,cAAc,GAGf,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,wBAAwB,EACxB,kBAAkB,EAClB,iBAAiB,GAElB,MAAM,qBAAqB,CAAC;AAE7B,4EAA4E;AAC5E,+EAA+E;AAC/E,2EAA2E;AAC3E,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,GAGf,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,eAAe,EACf,cAAc,GAGf,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,wBAAwB,EACxB,kBAAkB,EAClB,iBAAiB,GAElB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC"}
@@ -1,13 +1,3 @@
1
- /**
2
- * What each kind of block actually renders.
3
- *
4
- * This is the seam that makes a generated component safe *and* usable. The
5
- * model picks from a fixed vocabulary of block kinds; the registry decides what
6
- * those kinds look like. A shop can swap any entry for its own design-system
7
- * component without the model knowing, without the specification changing, and
8
- * without giving the model any new ability — it still cannot say anything
9
- * outside the vocabulary.
10
- */
11
1
  import type { ReactNode } from 'react';
12
2
  import type { Block, BlockKind } from '@rudra-js/core';
13
3
  import type { BlockRenderContext } from './render-context.js';
@@ -21,13 +11,5 @@ export type BlockRegistry = {
21
11
  [Kind in BlockKind]: BlockRenderer<Kind>;
22
12
  };
23
13
  export declare const defaultRegistry: BlockRegistry;
24
- /**
25
- * Replaces some renderers and keeps the rest.
26
- *
27
- * Written out kind by kind rather than spread: a spread lets
28
- * `{ hero: maybeRenderer }` put an explicit `undefined` over the default, and
29
- * the page then renders every hero as nothing. Here an absent override means
30
- * the default, however it was written.
31
- */
32
14
  export declare function extendRegistry(overrides: Partial<BlockRegistry>): BlockRegistry;
33
15
  //# sourceMappingURL=registry.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAU9D,MAAM,MAAM,aAAa,CAAC,IAAI,SAAS,SAAS,IAAI,CAAC,KAAK,EAAE;IAC1D,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,IAAI,CAAA;KAAE,CAAC,CAAC;IACtC,OAAO,EAAE,kBAAkB,CAAC;CAC7B,KAAK,SAAS,CAAC;AAEhB,MAAM,MAAM,aAAa,GAAG;KACzB,IAAI,IAAI,SAAS,GAAG,aAAa,CAAC,IAAI,CAAC;CACzC,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,aAO7B,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,aAAa,CAS/E"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAU9D,MAAM,MAAM,aAAa,CAAC,IAAI,SAAS,SAAS,IAAI,CAAC,KAAK,EAAE;IAC1D,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,IAAI,CAAA;KAAE,CAAC,CAAC;IACtC,OAAO,EAAE,kBAAkB,CAAC;CAC7B,KAAK,SAAS,CAAC;AAEhB,MAAM,MAAM,aAAa,GAAG;KACzB,IAAI,IAAI,SAAS,GAAG,aAAa,CAAC,IAAI,CAAC;CACzC,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,aAO7B,CAAC;AAEF,wBAAgB,cAAc,CAAC,SAAS,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,aAAa,CAS/E"}
package/dist/registry.js CHANGED
@@ -1,13 +1,3 @@
1
- /**
2
- * What each kind of block actually renders.
3
- *
4
- * This is the seam that makes a generated component safe *and* usable. The
5
- * model picks from a fixed vocabulary of block kinds; the registry decides what
6
- * those kinds look like. A shop can swap any entry for its own design-system
7
- * component without the model knowing, without the specification changing, and
8
- * without giving the model any new ability — it still cannot say anything
9
- * outside the vocabulary.
10
- */
11
1
  import { BannerRenderer, CarouselRenderer, CopyRenderer, GridRenderer, HeroRenderer, } from './blocks/block-renderers.js';
12
2
  import { BundleRenderer } from './blocks/bundle-block.js';
13
3
  export const defaultRegistry = {
@@ -18,14 +8,6 @@ export const defaultRegistry = {
18
8
  copy: CopyRenderer,
19
9
  bundle: BundleRenderer,
20
10
  };
21
- /**
22
- * Replaces some renderers and keeps the rest.
23
- *
24
- * Written out kind by kind rather than spread: a spread lets
25
- * `{ hero: maybeRenderer }` put an explicit `undefined` over the default, and
26
- * the page then renders every hero as nothing. Here an absent override means
27
- * the default, however it was written.
28
- */
29
11
  export function extendRegistry(overrides) {
30
12
  return {
31
13
  hero: overrides.hero ?? defaultRegistry.hero,
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,YAAY,GACb,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAW1D,MAAM,CAAC,MAAM,eAAe,GAAkB;IAC5C,IAAI,EAAE,YAAY;IAClB,IAAI,EAAE,YAAY;IAClB,QAAQ,EAAE,gBAAgB;IAC1B,MAAM,EAAE,cAAc;IACtB,IAAI,EAAE,YAAY;IAClB,MAAM,EAAE,cAAc;CACvB,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,SAAiC;IAC9D,OAAO;QACL,IAAI,EAAE,SAAS,CAAC,IAAI,IAAI,eAAe,CAAC,IAAI;QAC5C,IAAI,EAAE,SAAS,CAAC,IAAI,IAAI,eAAe,CAAC,IAAI;QAC5C,QAAQ,EAAE,SAAS,CAAC,QAAQ,IAAI,eAAe,CAAC,QAAQ;QACxD,MAAM,EAAE,SAAS,CAAC,MAAM,IAAI,eAAe,CAAC,MAAM;QAClD,IAAI,EAAE,SAAS,CAAC,IAAI,IAAI,eAAe,CAAC,IAAI;QAC5C,MAAM,EAAE,SAAS,CAAC,MAAM,IAAI,eAAe,CAAC,MAAM;KACnD,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,YAAY,GACb,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAW1D,MAAM,CAAC,MAAM,eAAe,GAAkB;IAC5C,IAAI,EAAE,YAAY;IAClB,IAAI,EAAE,YAAY;IAClB,QAAQ,EAAE,gBAAgB;IAC1B,MAAM,EAAE,cAAc;IACtB,IAAI,EAAE,YAAY;IAClB,MAAM,EAAE,cAAc;CACvB,CAAC;AAEF,MAAM,UAAU,cAAc,CAAC,SAAiC;IAC9D,OAAO;QACL,IAAI,EAAE,SAAS,CAAC,IAAI,IAAI,eAAe,CAAC,IAAI;QAC5C,IAAI,EAAE,SAAS,CAAC,IAAI,IAAI,eAAe,CAAC,IAAI;QAC5C,QAAQ,EAAE,SAAS,CAAC,QAAQ,IAAI,eAAe,CAAC,QAAQ;QACxD,MAAM,EAAE,SAAS,CAAC,MAAM,IAAI,eAAe,CAAC,MAAM;QAClD,IAAI,EAAE,SAAS,CAAC,IAAI,IAAI,eAAe,CAAC,IAAI;QAC5C,MAAM,EAAE,SAAS,CAAC,MAAM,IAAI,eAAe,CAAC,MAAM;KACnD,CAAC;AACJ,CAAC"}
@@ -1,46 +1,13 @@
1
1
  import type { Bundle, Product } from '@rudra-js/core';
2
- /**
3
- * Everything a block renderer needs that does not come from the specification.
4
- *
5
- * Note what is absent from the spec and present here: a price, a title, an
6
- * image, a link. Those are resolved from the host's own catalog, keyed by a SKU
7
- * that `selectProducts` drew from that same catalog and `reconcileSpec` proved
8
- * the model did not invent — both in stock at the time. A bundle member's SKU
9
- * comes from the set the shop supplied, not from the model. That division
10
- * is the whole reason a generated component is safe to put in a page — the
11
- * model decides how the component reads, and the shop decides what is true
12
- * about a product.
13
- */
14
2
  export interface BlockRenderContext {
15
- /** The host's catalog, keyed by SKU. Read-only: it is the caller's own map. */
16
3
  readonly products: ReadonlyMap<string, Product>;
17
- /** Sets the shop sells together, keyed by id. */
18
4
  readonly bundles: ReadonlyMap<string, Bundle>;
19
- /** Host-owned link construction. */
20
5
  readonly hrefForSku: (sku: string) => string;
21
6
  readonly formatPrice: (product: Product) => string;
22
7
  readonly formatBundlePrice: (bundle: Bundle) => string;
23
8
  }
24
9
  export declare function defaultHrefForSku(sku: string): string;
25
- /**
26
- * Formats a price the way the currency itself is written.
27
- *
28
- * No digit count is specified on purpose. Two decimal places is a dollar-and-
29
- * cent assumption, and forcing it is wrong in both directions: Kuwaiti dinar and
30
- * Bahraini dinar have three, so a real digit of the price disappears, and yen has
31
- * none, so a price gains a fraction that does not exist. Intl already knows the
32
- * right number for each currency.
33
- *
34
- * `locale` decides how the number is punctuated and where the symbol sits.
35
- * Left undefined it falls back to the server's locale, which is almost never
36
- * the shopper's — a shop serving more than one should pass the shopper's.
37
- */
10
+ export declare function sellableProduct(products: ReadonlyMap<string, Product>, sku: string): Product | undefined;
38
11
  export declare function defaultFormatPrice(product: Product, locale?: string): string;
39
- /**
40
- * Formats a bundle's price, in the currency the shop put on the bundle.
41
- *
42
- * The price and the currency come from the same object, so a set whose members
43
- * are priced in another currency still shows the shop's own price correctly.
44
- */
45
12
  export declare function defaultFormatBundlePrice(bundle: Bundle, locale?: string): string;
46
13
  //# sourceMappingURL=render-context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"render-context.d.ts","sourceRoot":"","sources":["../src/render-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAEtD;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,kBAAkB;IACjC,+EAA+E;IAC/E,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChD,iDAAiD;IACjD,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9C,oCAAoC;IACpC,QAAQ,CAAC,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IAC7C,QAAQ,CAAC,WAAW,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC;IACnD,QAAQ,CAAC,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;CACxD;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAErD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CA8B5E;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAkBhF"}
1
+ {"version":3,"file":"render-context.d.ts","sourceRoot":"","sources":["../src/render-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAEtD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEhD,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE9C,QAAQ,CAAC,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IAC7C,QAAQ,CAAC,WAAW,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC;IACnD,QAAQ,CAAC,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;CACxD;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,EACtC,GAAG,EAAE,MAAM,GACV,OAAO,GAAG,SAAS,CAGrB;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAgB5E;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAgBhF"}
@@ -1,53 +1,24 @@
1
1
  export function defaultHrefForSku(sku) {
2
2
  return `/product/${encodeURIComponent(sku)}`;
3
3
  }
4
- /**
5
- * Formats a price the way the currency itself is written.
6
- *
7
- * No digit count is specified on purpose. Two decimal places is a dollar-and-
8
- * cent assumption, and forcing it is wrong in both directions: Kuwaiti dinar and
9
- * Bahraini dinar have three, so a real digit of the price disappears, and yen has
10
- * none, so a price gains a fraction that does not exist. Intl already knows the
11
- * right number for each currency.
12
- *
13
- * `locale` decides how the number is punctuated and where the symbol sits.
14
- * Left undefined it falls back to the server's locale, which is almost never
15
- * the shopper's — a shop serving more than one should pass the shopper's.
16
- */
4
+ export function sellableProduct(products, sku) {
5
+ const product = products.get(sku);
6
+ return product?.isInStock === false ? undefined : product;
7
+ }
17
8
  export function defaultFormatPrice(product, locale) {
18
- // A price that is not a finite number is a broken catalog, not a formatting
19
- // problem, and Intl will happily render it: null becomes 0 and prints as a
20
- // free product, undefined prints as NaN. Neither belongs on a shop page, and
21
- // both mean the catalog skipped the validation this package documents as a
22
- // precondition. Failing here is a bug report; rendering it is an incident.
23
9
  if (!Number.isFinite(product.price)) {
24
10
  throw new TypeError(`price for SKU ${product.sku} is ${String(product.price)}, not a finite number — ` +
25
11
  'catalog objects must satisfy productSchema from @rudra-js/core');
26
12
  }
27
13
  let formatter;
28
14
  try {
29
- // Only the constructor is guarded. Wrapping format() as well would swallow
30
- // a throwing getter on a host's own Product object and render the resulting
31
- // mess as a price.
32
15
  formatter = new Intl.NumberFormat(locale, { style: 'currency', currency: product.currency });
33
16
  }
34
17
  catch {
35
- // Two things reach this. A malformed `locale`, which is a host prop nothing
36
- // validates. And a malformed `currency` — which productSchema would have
37
- // rejected, but this package takes a catalog directly, so a hand-built
38
- // object can carry one. Intl accepts any three-letter code it does not
39
- // know, so 'ZZZ' formats; 'us$' and '' do not. A price nobody can
40
- // punctuate is still a price worth showing.
41
18
  return `${product.currency} ${product.price}`;
42
19
  }
43
20
  return formatter.format(product.price);
44
21
  }
45
- /**
46
- * Formats a bundle's price, in the currency the shop put on the bundle.
47
- *
48
- * The price and the currency come from the same object, so a set whose members
49
- * are priced in another currency still shows the shop's own price correctly.
50
- */
51
22
  export function defaultFormatBundlePrice(bundle, locale) {
52
23
  if (!Number.isFinite(bundle.price)) {
53
24
  throw new TypeError(`price for bundle ${bundle.id} is ${String(bundle.price)}, not a finite number — ` +
@@ -58,8 +29,6 @@ export function defaultFormatBundlePrice(bundle, locale) {
58
29
  formatter = new Intl.NumberFormat(locale, { style: 'currency', currency: bundle.currency });
59
30
  }
60
31
  catch {
61
- // A bad locale, or a currency code Intl rejects on a hand-built bundle,
62
- // should not crash the page. A price nobody can punctuate is still a price.
63
32
  return `${bundle.currency} ${bundle.price}`;
64
33
  }
65
34
  return formatter.format(bundle.price);
@@ -1 +1 @@
1
- {"version":3,"file":"render-context.js","sourceRoot":"","sources":["../src/render-context.ts"],"names":[],"mappings":"AAyBA,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,OAAO,YAAY,kBAAkB,CAAC,GAAG,CAAC,EAAE,CAAC;AAC/C,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAgB,EAAE,MAAe;IAClE,4EAA4E;IAC5E,2EAA2E;IAC3E,6EAA6E;IAC7E,2EAA2E;IAC3E,2EAA2E;IAC3E,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,0BAA0B;YAChF,gEAAgE,CACnE,CAAC;IACJ,CAAC;IAED,IAAI,SAA4B,CAAC;IACjC,IAAI,CAAC;QACH,2EAA2E;QAC3E,4EAA4E;QAC5E,mBAAmB;QACnB,SAAS,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC/F,CAAC;IAAC,MAAM,CAAC;QACP,4EAA4E;QAC5E,yEAAyE;QACzE,uEAAuE;QACvE,uEAAuE;QACvE,kEAAkE;QAClE,4CAA4C;QAC5C,OAAO,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;IAChD,CAAC;IAED,OAAO,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AACzC,CAAC;AAED;;;;;GAKG;AACH,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,0BAA0B;YAChF,8DAA8D,CACjE,CAAC;IACJ,CAAC;IAED,IAAI,SAA4B,CAAC;IACjC,IAAI,CAAC;QACH,SAAS,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC9F,CAAC;IAAC,MAAM,CAAC;QACP,wEAAwE;QACxE,4EAA4E;QAC5E,OAAO,GAAG,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;IAC9C,CAAC;IAED,OAAO,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACxC,CAAC"}
1
+ {"version":3,"file":"render-context.js","sourceRoot":"","sources":["../src/render-context.ts"],"names":[],"mappings":"AAYA,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,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,0BAA0B;YAChF,gEAAgE,CACnE,CAAC;IACJ,CAAC;IAED,IAAI,SAA4B,CAAC;IACjC,IAAI,CAAC;QACH,SAAS,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC/F,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;IAChD,CAAC;IAED,OAAO,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AACzC,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,0BAA0B;YAChF,8DAA8D,CACjE,CAAC;IACJ,CAAC;IAED,IAAI,SAA4B,CAAC;IACjC,IAAI,CAAC;QACH,SAAS,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC9F,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;IAC9C,CAAC;IAED,OAAO,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACxC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"rudra-component.d.ts","sourceRoot":"","sources":["../src/rudra-component.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAS,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAO5E,OAAO,EAAmB,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAEpE,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,aAAa,CAAC;IACpB;;;;;;;;;;;;;;;;;OAiBG;IACH,QAAQ,EAAE,cAAc,CAAC;IACzB,oFAAoF;IACpF,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IACrC,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC;IAC3C,0FAA0F;IAC1F,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;IAC/C;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,iFAAiF;AACjF,MAAM,MAAM,cAAc,GAAG,SAAS,OAAO,EAAE,GAAG,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAmG/E;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,QAAQ,EACR,OAAO,EACP,QAA0B,EAC1B,UAA8B,EAC9B,WAAW,EACX,iBAAiB,EACjB,MAAM,EACN,cAAsB,EACtB,SAAS,GACV,EAAE,mBAAmB,sCA0DrB"}
1
+ {"version":3,"file":"rudra-component.d.ts","sourceRoot":"","sources":["../src/rudra-component.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAS,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAQ5E,OAAO,EAAmB,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAEpE,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,aAAa,CAAC;IACpB;;;;;;;;;;;;;;;;;OAiBG;IACH,QAAQ,EAAE,cAAc,CAAC;IACzB,oFAAoF;IACpF,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IACrC,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC;IAC3C,0FAA0F;IAC1F,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;IAC/C;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,iFAAiF;AACjF,MAAM,MAAM,cAAc,GAAG,SAAS,OAAO,EAAE,GAAG,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAiF/E;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,QAAQ,EACR,OAAO,EACP,QAA0B,EAC1B,UAA8B,EAC9B,WAAW,EACX,iBAAiB,EACjB,MAAM,EACN,cAAsB,EACtB,SAAS,GACV,EAAE,mBAAmB,sCA8CrB"}
@@ -1,21 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { defaultFormatBundlePrice, defaultFormatPrice, defaultHrefForSku, } from './render-context.js';
2
+ import { defaultFormatBundlePrice, defaultFormatPrice, defaultHrefForSku, sellableProduct, } from './render-context.js';
3
3
  import { defaultRegistry } from './registry.js';
4
- /**
5
- * Whether the catalog is already keyed by SKU.
6
- *
7
- * Asks what the renderers actually call rather than which class the host
8
- * happened to construct. `instanceof Map` was wrong twice over: a Map that
9
- * crossed a realm boundary — a `node:vm` context, a worker — fails it, and so
10
- * does a host's own `ReadonlyMap`, which the prop type has always allowed. Both
11
- * then fell into the list branch, where `catalog.map is not a function` throws
12
- * while the render context is being built, before any block renders. That takes
13
- * down the whole page, not just this component.
14
- *
15
- * Keyed before list, because a collection can answer both: an Immutable.js map
16
- * has `map`, and converting through it yields a catalog whose every value is a
17
- * `[sku, product]` pair rather than a product.
18
- */
4
+ // Asks what the renderers call rather than which class the host built, since
5
+ // `instanceof Map` is per-realm. Checked before the list branch because a
6
+ // collection can answer both: an Immutable.js map has `map`, and converting
7
+ // through it yields `[sku, product]` pairs rather than products.
19
8
  function isKeyedBySku(catalog) {
20
9
  const candidate = catalog;
21
10
  return typeof candidate.get === 'function' && typeof candidate.has === 'function';
@@ -24,29 +13,20 @@ function toProductMap(catalog) {
24
13
  if (isKeyedBySku(catalog))
25
14
  return catalog;
26
15
  if (typeof catalog.map !== 'function') {
27
- // A Set of products, a plain object keyed by SKU, a Map that has been
28
- // through JSON. Refusing here names the prop while the stack still points
29
- // at it. Carried through instead, a grid renders nothing and a banner
30
- // renders a healthy-looking page, and the shop finds out from a dashboard.
31
16
  throw new TypeError('the `products` prop must be a list of products, or keyed by SKU with `get` and `has` — ' +
32
17
  `received ${Object.prototype.toString.call(catalog)}`);
33
18
  }
34
19
  return new Map(catalog.map((product) => [product.sku, product]));
35
20
  }
36
- /**
37
- * Whether a block still has anything to say once the catalog is applied.
38
- *
39
- * Three block kinds can come up empty: reconciliation ran against the catalog
40
- * as it was when the spec was generated, and a SKU can sell out between then
41
- * and this render. Grid and carousel lose just the products that did; a
42
- * bundle loses itself entirely if any one of its members did. The rest carry
43
- * their own words.
44
- */
21
+ // Whether a block still has anything to say once the catalog is applied. A SKU
22
+ // can sell out between generating a spec and rendering it — the row goes, or it
23
+ // is flagged out of stock, and both read the same here: grid and carousel lose
24
+ // the products that did, a bundle loses itself if any one member did.
45
25
  function hasContent(block, products, bundles) {
46
26
  switch (block.kind) {
47
27
  case 'grid':
48
28
  case 'carousel':
49
- return block.items.some((reference) => products.has(reference.sku));
29
+ return block.items.some((reference) => sellableProduct(products, reference.sku) !== undefined);
50
30
  case 'hero':
51
31
  case 'banner':
52
32
  case 'copy':
@@ -55,10 +35,10 @@ function hasContent(block, products, bundles) {
55
35
  if (block.bundleId === null)
56
36
  return false;
57
37
  const bundle = bundles.get(block.bundleId);
58
- return bundle !== undefined && bundle.skus.every((sku) => products.has(sku));
38
+ return (bundle !== undefined &&
39
+ bundle.skus.every((sku) => sellableProduct(products, sku) !== undefined));
59
40
  }
60
41
  default:
61
- // A kind this renderer predates renders nothing, so it counts as nothing.
62
42
  block;
63
43
  return false;
64
44
  }
@@ -78,9 +58,7 @@ function renderBlock(block, context, registry, index) {
78
58
  case 'bundle':
79
59
  return _jsx(registry.bundle, { block: block, context: context }, index);
80
60
  default:
81
- // A newer core carrying a block kind this renderer predates loses that
82
- // block rather than the page. In this repo the assertion below fails the
83
- // build instead, which is the moment it is cheap to notice.
61
+ // A block kind this renderer predates loses that block, not the page.
84
62
  block;
85
63
  return null;
86
64
  }
@@ -106,15 +84,10 @@ export function RudraComponent({ spec, products, bundles, registry = defaultRegi
106
84
  formatPrice: formatPrice ?? ((product) => defaultFormatPrice(product, locale)),
107
85
  formatBundlePrice: formatBundlePrice ?? ((bundle) => defaultFormatBundlePrice(bundle, locale)),
108
86
  };
109
- // An empty recommendation area is worse than none: it takes up space and
110
- // tells the shopper the page is broken. That includes the subtler version —
111
- // a headline and an empty box, because every product in the spec has sold out
112
- // since it was generated — which is why this asks what is left rather than
113
- // how many blocks arrived.
114
87
  const visible = spec.blocks.filter((block) => hasContent(block, context.products, context.bundles));
115
88
  if (visible.length === 0)
116
89
  return null;
117
- // React omits a data-* attribute whose value is undefined, so degradedReason
90
+ // React drops a data-* attribute whose value is undefined, so degradedReason
118
91
  // needs no branch of its own.
119
92
  const diagnosticAttributes = hasDiagnostics
120
93
  ? {
@@ -124,9 +97,6 @@ export function RudraComponent({ spec, products, bundles, registry = defaultRegi
124
97
  'data-rudra-degraded': spec.degradedReason,
125
98
  }
126
99
  : undefined;
127
- return (_jsxs("section", {
128
- // Extended rather than replaced: every child class is namespaced under
129
- // `rudra`, and the package ships no stylesheet, so a host will pass one.
130
- className: className ? `rudra ${className}` : 'rudra', "data-rudra-slot": spec.slot, "data-rudra-source": spec.source, "data-rudra-tone": spec.tone, ...diagnosticAttributes, children: [_jsxs("header", { className: "rudra__header", children: [_jsx("h2", { className: "rudra__headline", children: spec.headline }), spec.subheadline ? _jsx("p", { className: "rudra__subheadline", children: spec.subheadline }) : null] }), visible.map((block, index) => renderBlock(block, context, registry, index)), hasDiagnostics ? _jsx("p", { className: "rudra__rationale", children: spec.rationale }) : null] }));
100
+ return (_jsxs("section", { className: className ? `rudra ${className}` : 'rudra', "data-rudra-slot": spec.slot, "data-rudra-source": spec.source, "data-rudra-tone": spec.tone, ...diagnosticAttributes, children: [_jsxs("header", { className: "rudra__header", children: [_jsx("h2", { className: "rudra__headline", children: spec.headline }), spec.subheadline ? _jsx("p", { className: "rudra__subheadline", children: spec.subheadline }) : null] }), visible.map((block, index) => renderBlock(block, context, registry, index)), hasDiagnostics ? _jsx("p", { className: "rudra__rationale", children: spec.rationale }) : null] }));
131
101
  }
132
102
  //# sourceMappingURL=rudra-component.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"rudra-component.js","sourceRoot":"","sources":["../src/rudra-component.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"rudra-component.js","sourceRoot":"","sources":["../src/rudra-component.tsx"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rudra-js/react",
3
- "version": "0.5.0",
4
- "description": "Renders a rudra component specification as React Server Components",
3
+ "version": "0.7.0",
4
+ "description": "Renders a rudra component specification with React 18 or 19, on the server or in the browser",
5
5
  "keywords": [
6
6
  "react",
7
7
  "react-server-components",
@@ -51,14 +51,14 @@
51
51
  "access": "public"
52
52
  },
53
53
  "peerDependencies": {
54
- "@rudra-js/core": "^0.5.0",
54
+ "@rudra-js/core": "^0.7.0",
55
55
  "react": "^18.2.0 || ^19.0.0"
56
56
  },
57
57
  "devDependencies": {
58
- "@rudra-js/core": "0.5.0",
59
- "@types/react": "^19.2.18",
60
- "@types/react-dom": "^19.2.7",
61
- "react": "^19.2.8",
62
- "react-dom": "^19.2.8"
58
+ "@rudra-js/core": "0.7.0",
59
+ "@types/react": "^19.3.0",
60
+ "@types/react-dom": "^19.3.0",
61
+ "react": "^19.3.0",
62
+ "react-dom": "^19.3.0"
63
63
  }
64
64
  }
@@ -9,7 +9,7 @@
9
9
  */
10
10
 
11
11
  import type { BannerBlock, CarouselBlock, CopyBlock, GridBlock, HeroBlock } from '@rudra-js/core';
12
- import type { BlockRenderContext } from '../render-context.js';
12
+ import { sellableProduct, type BlockRenderContext } from '../render-context.js';
13
13
  import { ProductCard } from './product-card.js';
14
14
 
15
15
  export function HeroRenderer({
@@ -19,7 +19,7 @@ export function HeroRenderer({
19
19
  block: HeroBlock;
20
20
  context: BlockRenderContext;
21
21
  }) {
22
- const product = block.sku ? context.products.get(block.sku) : undefined;
22
+ const product = block.sku ? sellableProduct(context.products, block.sku) : undefined;
23
23
 
24
24
  return (
25
25
  <section className="rudra-hero">
@@ -33,12 +33,9 @@ export function HeroRenderer({
33
33
  >
34
34
  {product.title}
35
35
  <span className="rudra-hero__price">{context.formatPrice(product)}</span>
36
+ {block.ctaLabel ? <span className="rudra-hero__cta">{block.ctaLabel}</span> : null}
36
37
  </a>
37
38
  ) : null}
38
- {/* Only alongside a product: a "Shop now" with no destination is worse
39
- than no call to action, and the model can set one for a SKU the
40
- catalog does not have. */}
41
- {product && block.ctaLabel ? <span className="rudra-hero__cta">{block.ctaLabel}</span> : null}
42
39
  </section>
43
40
  );
44
41
  }
@@ -1,5 +1,5 @@
1
1
  import type { BundleBlock, Product } from '@rudra-js/core';
2
- import type { BlockRenderContext } from '../render-context.js';
2
+ import { sellableProduct, type BlockRenderContext } from '../render-context.js';
3
3
 
4
4
  export function BundleRenderer({
5
5
  block,
@@ -13,7 +13,7 @@ export function BundleRenderer({
13
13
 
14
14
  const products: Product[] = [];
15
15
  for (const sku of bundle.skus) {
16
- const product = context.products.get(sku);
16
+ const product = sellableProduct(context.products, sku);
17
17
  // A set missing one of its parts is not that set.
18
18
  if (!product) return null;
19
19
  products.push(product);
@@ -1,5 +1,5 @@
1
1
  import type { ProductReference } from '@rudra-js/core';
2
- import type { BlockRenderContext } from '../render-context.js';
2
+ import { sellableProduct, type BlockRenderContext } from '../render-context.js';
3
3
 
4
4
  /**
5
5
  * One product.
@@ -16,7 +16,7 @@ export function ProductCard({
16
16
  reference: ProductReference;
17
17
  context: BlockRenderContext;
18
18
  }) {
19
- const product = context.products.get(reference.sku);
19
+ const product = sellableProduct(context.products, reference.sku);
20
20
  // Reconciliation drops a SKU the catalog does not have, so this should be
21
21
  // unreachable. Rendering nothing beats rendering a card with holes in it.
22
22
  if (!product) return null;
package/src/index.ts CHANGED
@@ -1,10 +1,3 @@
1
- /**
2
- * @rudra-js/react — renders a component specification as React Server Components.
3
- *
4
- * Carries no client JavaScript. The recommendation area arrives in the initial
5
- * HTML response and needs no hydration.
6
- */
7
-
8
1
  export {
9
2
  RudraComponent,
10
3
  type ProductCatalog,
@@ -25,7 +18,4 @@ export {
25
18
  type BlockRenderContext,
26
19
  } from './render-context.js';
27
20
 
28
- // The six default renderers reach hosts through `defaultRegistry`, which is
29
- // also the thing they need in order to override one. `ProductCard` is exported
30
- // on its own because a custom grid renderer still wants the standard card.
31
21
  export { ProductCard } from './blocks/product-card.js';
package/src/registry.ts CHANGED
@@ -1,14 +1,3 @@
1
- /**
2
- * What each kind of block actually renders.
3
- *
4
- * This is the seam that makes a generated component safe *and* usable. The
5
- * model picks from a fixed vocabulary of block kinds; the registry decides what
6
- * those kinds look like. A shop can swap any entry for its own design-system
7
- * component without the model knowing, without the specification changing, and
8
- * without giving the model any new ability — it still cannot say anything
9
- * outside the vocabulary.
10
- */
11
-
12
1
  import type { ReactNode } from 'react';
13
2
  import type { Block, BlockKind } from '@rudra-js/core';
14
3
  import type { BlockRenderContext } from './render-context.js';
@@ -39,14 +28,6 @@ export const defaultRegistry: BlockRegistry = {
39
28
  bundle: BundleRenderer,
40
29
  };
41
30
 
42
- /**
43
- * Replaces some renderers and keeps the rest.
44
- *
45
- * Written out kind by kind rather than spread: a spread lets
46
- * `{ hero: maybeRenderer }` put an explicit `undefined` over the default, and
47
- * the page then renders every hero as nothing. Here an absent override means
48
- * the default, however it was written.
49
- */
50
31
  export function extendRegistry(overrides: Partial<BlockRegistry>): BlockRegistry {
51
32
  return {
52
33
  hero: overrides.hero ?? defaultRegistry.hero,
@@ -1,23 +1,10 @@
1
1
  import type { Bundle, Product } from '@rudra-js/core';
2
2
 
3
- /**
4
- * Everything a block renderer needs that does not come from the specification.
5
- *
6
- * Note what is absent from the spec and present here: a price, a title, an
7
- * image, a link. Those are resolved from the host's own catalog, keyed by a SKU
8
- * that `selectProducts` drew from that same catalog and `reconcileSpec` proved
9
- * the model did not invent — both in stock at the time. A bundle member's SKU
10
- * comes from the set the shop supplied, not from the model. That division
11
- * is the whole reason a generated component is safe to put in a page — the
12
- * model decides how the component reads, and the shop decides what is true
13
- * about a product.
14
- */
15
3
  export interface BlockRenderContext {
16
- /** The host's catalog, keyed by SKU. Read-only: it is the caller's own map. */
17
4
  readonly products: ReadonlyMap<string, Product>;
18
- /** Sets the shop sells together, keyed by id. */
5
+
19
6
  readonly bundles: ReadonlyMap<string, Bundle>;
20
- /** Host-owned link construction. */
7
+
21
8
  readonly hrefForSku: (sku: string) => string;
22
9
  readonly formatPrice: (product: Product) => string;
23
10
  readonly formatBundlePrice: (bundle: Bundle) => string;
@@ -27,25 +14,15 @@ export function defaultHrefForSku(sku: string): string {
27
14
  return `/product/${encodeURIComponent(sku)}`;
28
15
  }
29
16
 
30
- /**
31
- * Formats a price the way the currency itself is written.
32
- *
33
- * No digit count is specified on purpose. Two decimal places is a dollar-and-
34
- * cent assumption, and forcing it is wrong in both directions: Kuwaiti dinar and
35
- * Bahraini dinar have three, so a real digit of the price disappears, and yen has
36
- * none, so a price gains a fraction that does not exist. Intl already knows the
37
- * right number for each currency.
38
- *
39
- * `locale` decides how the number is punctuated and where the symbol sits.
40
- * Left undefined it falls back to the server's locale, which is almost never
41
- * the shopper's — a shop serving more than one should pass the shopper's.
42
- */
17
+ export function sellableProduct(
18
+ products: ReadonlyMap<string, Product>,
19
+ sku: string,
20
+ ): Product | undefined {
21
+ const product = products.get(sku);
22
+ return product?.isInStock === false ? undefined : product;
23
+ }
24
+
43
25
  export function defaultFormatPrice(product: Product, locale?: string): string {
44
- // A price that is not a finite number is a broken catalog, not a formatting
45
- // problem, and Intl will happily render it: null becomes 0 and prints as a
46
- // free product, undefined prints as NaN. Neither belongs on a shop page, and
47
- // both mean the catalog skipped the validation this package documents as a
48
- // precondition. Failing here is a bug report; rendering it is an incident.
49
26
  if (!Number.isFinite(product.price)) {
50
27
  throw new TypeError(
51
28
  `price for SKU ${product.sku} is ${String(product.price)}, not a finite number — ` +
@@ -55,29 +32,14 @@ export function defaultFormatPrice(product: Product, locale?: string): string {
55
32
 
56
33
  let formatter: Intl.NumberFormat;
57
34
  try {
58
- // Only the constructor is guarded. Wrapping format() as well would swallow
59
- // a throwing getter on a host's own Product object and render the resulting
60
- // mess as a price.
61
35
  formatter = new Intl.NumberFormat(locale, { style: 'currency', currency: product.currency });
62
36
  } catch {
63
- // Two things reach this. A malformed `locale`, which is a host prop nothing
64
- // validates. And a malformed `currency` — which productSchema would have
65
- // rejected, but this package takes a catalog directly, so a hand-built
66
- // object can carry one. Intl accepts any three-letter code it does not
67
- // know, so 'ZZZ' formats; 'us$' and '' do not. A price nobody can
68
- // punctuate is still a price worth showing.
69
37
  return `${product.currency} ${product.price}`;
70
38
  }
71
39
 
72
40
  return formatter.format(product.price);
73
41
  }
74
42
 
75
- /**
76
- * Formats a bundle's price, in the currency the shop put on the bundle.
77
- *
78
- * The price and the currency come from the same object, so a set whose members
79
- * are priced in another currency still shows the shop's own price correctly.
80
- */
81
43
  export function defaultFormatBundlePrice(bundle: Bundle, locale?: string): string {
82
44
  if (!Number.isFinite(bundle.price)) {
83
45
  throw new TypeError(
@@ -90,8 +52,6 @@ export function defaultFormatBundlePrice(bundle: Bundle, locale?: string): strin
90
52
  try {
91
53
  formatter = new Intl.NumberFormat(locale, { style: 'currency', currency: bundle.currency });
92
54
  } catch {
93
- // A bad locale, or a currency code Intl rejects on a hand-built bundle,
94
- // should not crash the page. A price nobody can punctuate is still a price.
95
55
  return `${bundle.currency} ${bundle.price}`;
96
56
  }
97
57
 
@@ -3,6 +3,7 @@ import {
3
3
  defaultFormatBundlePrice,
4
4
  defaultFormatPrice,
5
5
  defaultHrefForSku,
6
+ sellableProduct,
6
7
  type BlockRenderContext,
7
8
  } from './render-context.js';
8
9
  import { defaultRegistry, type BlockRegistry } from './registry.js';
@@ -54,21 +55,10 @@ export interface RudraComponentProps {
54
55
  /** A list of products, or anything keyed by SKU that answers `get` and `has`. */
55
56
  export type ProductCatalog = readonly Product[] | ReadonlyMap<string, Product>;
56
57
 
57
- /**
58
- * Whether the catalog is already keyed by SKU.
59
- *
60
- * Asks what the renderers actually call rather than which class the host
61
- * happened to construct. `instanceof Map` was wrong twice over: a Map that
62
- * crossed a realm boundary — a `node:vm` context, a worker — fails it, and so
63
- * does a host's own `ReadonlyMap`, which the prop type has always allowed. Both
64
- * then fell into the list branch, where `catalog.map is not a function` throws
65
- * while the render context is being built, before any block renders. That takes
66
- * down the whole page, not just this component.
67
- *
68
- * Keyed before list, because a collection can answer both: an Immutable.js map
69
- * has `map`, and converting through it yields a catalog whose every value is a
70
- * `[sku, product]` pair rather than a product.
71
- */
58
+ // Asks what the renderers call rather than which class the host built, since
59
+ // `instanceof Map` is per-realm. Checked before the list branch because a
60
+ // collection can answer both: an Immutable.js map has `map`, and converting
61
+ // through it yields `[sku, product]` pairs rather than products.
72
62
  function isKeyedBySku(catalog: ProductCatalog): catalog is ReadonlyMap<string, Product> {
73
63
  const candidate = catalog as { get?: unknown; has?: unknown };
74
64
  return typeof candidate.get === 'function' && typeof candidate.has === 'function';
@@ -77,10 +67,6 @@ function isKeyedBySku(catalog: ProductCatalog): catalog is ReadonlyMap<string, P
77
67
  function toProductMap(catalog: ProductCatalog): ReadonlyMap<string, Product> {
78
68
  if (isKeyedBySku(catalog)) return catalog;
79
69
  if (typeof (catalog as { map?: unknown }).map !== 'function') {
80
- // A Set of products, a plain object keyed by SKU, a Map that has been
81
- // through JSON. Refusing here names the prop while the stack still points
82
- // at it. Carried through instead, a grid renders nothing and a banner
83
- // renders a healthy-looking page, and the shop finds out from a dashboard.
84
70
  throw new TypeError(
85
71
  'the `products` prop must be a list of products, or keyed by SKU with `get` and `has` — ' +
86
72
  `received ${Object.prototype.toString.call(catalog)}`,
@@ -89,15 +75,10 @@ function toProductMap(catalog: ProductCatalog): ReadonlyMap<string, Product> {
89
75
  return new Map(catalog.map((product) => [product.sku, product]));
90
76
  }
91
77
 
92
- /**
93
- * Whether a block still has anything to say once the catalog is applied.
94
- *
95
- * Three block kinds can come up empty: reconciliation ran against the catalog
96
- * as it was when the spec was generated, and a SKU can sell out between then
97
- * and this render. Grid and carousel lose just the products that did; a
98
- * bundle loses itself entirely if any one of its members did. The rest carry
99
- * their own words.
100
- */
78
+ // Whether a block still has anything to say once the catalog is applied. A SKU
79
+ // can sell out between generating a spec and rendering it — the row goes, or it
80
+ // is flagged out of stock, and both read the same here: grid and carousel lose
81
+ // the products that did, a bundle loses itself if any one member did.
101
82
  function hasContent(
102
83
  block: Block,
103
84
  products: ReadonlyMap<string, Product>,
@@ -106,7 +87,9 @@ function hasContent(
106
87
  switch (block.kind) {
107
88
  case 'grid':
108
89
  case 'carousel':
109
- return block.items.some((reference) => products.has(reference.sku));
90
+ return block.items.some(
91
+ (reference) => sellableProduct(products, reference.sku) !== undefined,
92
+ );
110
93
  case 'hero':
111
94
  case 'banner':
112
95
  case 'copy':
@@ -114,10 +97,12 @@ function hasContent(
114
97
  case 'bundle': {
115
98
  if (block.bundleId === null) return false;
116
99
  const bundle = bundles.get(block.bundleId);
117
- return bundle !== undefined && bundle.skus.every((sku) => products.has(sku));
100
+ return (
101
+ bundle !== undefined &&
102
+ bundle.skus.every((sku) => sellableProduct(products, sku) !== undefined)
103
+ );
118
104
  }
119
105
  default:
120
- // A kind this renderer predates renders nothing, so it counts as nothing.
121
106
  block satisfies never;
122
107
  return false;
123
108
  }
@@ -143,9 +128,7 @@ function renderBlock(
143
128
  case 'bundle':
144
129
  return <registry.bundle key={index} block={block} context={context} />;
145
130
  default:
146
- // A newer core carrying a block kind this renderer predates loses that
147
- // block rather than the page. In this repo the assertion below fails the
148
- // build instead, which is the moment it is cheap to notice.
131
+ // A block kind this renderer predates loses that block, not the page.
149
132
  block satisfies never;
150
133
  return null;
151
134
  }
@@ -185,17 +168,12 @@ export function RudraComponent({
185
168
  formatBundlePrice: formatBundlePrice ?? ((bundle) => defaultFormatBundlePrice(bundle, locale)),
186
169
  };
187
170
 
188
- // An empty recommendation area is worse than none: it takes up space and
189
- // tells the shopper the page is broken. That includes the subtler version —
190
- // a headline and an empty box, because every product in the spec has sold out
191
- // since it was generated — which is why this asks what is left rather than
192
- // how many blocks arrived.
193
171
  const visible = spec.blocks.filter((block) =>
194
172
  hasContent(block, context.products, context.bundles),
195
173
  );
196
174
  if (visible.length === 0) return null;
197
175
 
198
- // React omits a data-* attribute whose value is undefined, so degradedReason
176
+ // React drops a data-* attribute whose value is undefined, so degradedReason
199
177
  // needs no branch of its own.
200
178
  const diagnosticAttributes = hasDiagnostics
201
179
  ? {
@@ -208,15 +186,8 @@ export function RudraComponent({
208
186
 
209
187
  return (
210
188
  <section
211
- // Extended rather than replaced: every child class is namespaced under
212
- // `rudra`, and the package ships no stylesheet, so a host will pass one.
213
189
  className={className ? `rudra ${className}` : 'rudra'}
214
190
  data-rudra-slot={spec.slot}
215
- // Where the component came from travels with the markup on purpose, so
216
- // hit rate and fallback share can be read off a rendered page — which
217
- // means `source="fallback"` is public. What stays behind the diagnostics
218
- // flag is everything more specific than that: which vendor, which model,
219
- // how slow, and why it fell back.
220
191
  data-rudra-source={spec.source}
221
192
  data-rudra-tone={spec.tone}
222
193
  {...diagnosticAttributes}