@reactionary/feeds 0.0.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 +257 -0
- package/bin/reactionary-feeds +2 -0
- package/cli.cjs +170941 -0
- package/cli.js +340 -0
- package/documentation/basic-product-feeds.config.mjs +32 -0
- package/documentation/multilingual-feeds.config.mjs +59 -0
- package/documentation/nextjs-route-example.mjs +31 -0
- package/documentation/sitemaps.config.mjs +77 -0
- package/index.js +13 -0
- package/lib/default-registry.js +18 -0
- package/lib/env-client-builder.js +244 -0
- package/lib/feed-generator.js +395 -0
- package/lib/feed-server.js +305 -0
- package/lib/feed-transformer.js +21 -0
- package/lib/feed-types.js +45 -0
- package/lib/load-cwd-env.js +16 -0
- package/lib/sitemap-generator.js +173 -0
- package/lib/sitemap-xml.js +32 -0
- package/lib/transformers/acp-product-feed.transformer.js +128 -0
- package/lib/transformers/google-merchant-feed.transformer.js +60 -0
- package/lib/transformers/pricerunner-feed.transformer.js +60 -0
- package/lib/transformers/shared.js +25 -0
- package/lib/transformers/sitemap-feed.transformer.js +26 -0
- package/package.json +32 -0
- package/src/cli.d.ts +2 -0
- package/src/index.d.ts +13 -0
- package/src/lib/default-registry.d.ts +2 -0
- package/src/lib/env-client-builder.d.ts +18 -0
- package/src/lib/feed-generator.d.ts +17 -0
- package/src/lib/feed-server.d.ts +28 -0
- package/src/lib/feed-transformer.d.ts +30 -0
- package/src/lib/feed-types.d.ts +185 -0
- package/src/lib/load-cwd-env.d.ts +4 -0
- package/src/lib/sitemap-generator.d.ts +20 -0
- package/src/lib/sitemap-xml.d.ts +3 -0
- package/src/lib/transformers/acp-product-feed.transformer.d.ts +4 -0
- package/src/lib/transformers/google-merchant-feed.transformer.d.ts +2 -0
- package/src/lib/transformers/pricerunner-feed.transformer.d.ts +2 -0
- package/src/lib/transformers/shared.d.ts +6 -0
- package/src/lib/transformers/sitemap-feed.transformer.d.ts +2 -0
package/README.md
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# @reactionary/feeds
|
|
2
|
+
|
|
3
|
+
Reusable product-feed generation for Reactionary clients.
|
|
4
|
+
|
|
5
|
+
The package keeps feed generation separate from protocol adapters. A host can provide a ready-made request-scoped Reactionary client and expose the same feed definitions through:
|
|
6
|
+
|
|
7
|
+
- a mountable fetch-compatible HTTP handler,
|
|
8
|
+
- a Node HTTP handler,
|
|
9
|
+
- the `reactionary-feeds` CLI,
|
|
10
|
+
- direct TypeScript APIs.
|
|
11
|
+
|
|
12
|
+
## Built-in feed transformers
|
|
13
|
+
|
|
14
|
+
The default registry includes:
|
|
15
|
+
|
|
16
|
+
- `acp-product-feed` — ACP product feed JSONL by default, with JSON support in protocol adapters.
|
|
17
|
+
- `google-merchant-feed` — Google Merchant RSS XML.
|
|
18
|
+
- `sitemap-feed` — sitemap XML of product URLs.
|
|
19
|
+
- `pricerunner-feed` — PriceRunner-style product XML.
|
|
20
|
+
|
|
21
|
+
All transformers use the shared normalized feed product model from `ReactionaryFeedGenerator`. XML outputs are generated through XML libraries (`sitemap` for sitemaps and `xmlbuilder2` for RSS/product XML), not by manually concatenating escaped XML strings.
|
|
22
|
+
|
|
23
|
+
## Default environment client builder
|
|
24
|
+
|
|
25
|
+
The package exports `createReactionaryFeedClientFactoryFromEnv()` for setups that should use the same environment-variable driven provider selection model as the MCP CLI, without depending on `@reactionary/mcp`.
|
|
26
|
+
|
|
27
|
+
The helper reads `.env` from the current working directory by default, or `DOTENV_CONFIG_PATH` when explicitly set. It supports the same provider switches and provider variables as the MCP command line package, for example:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
ENABLED_MEDUSA=true
|
|
31
|
+
MEDUSA_API_URL=https://medusa.example
|
|
32
|
+
MEDUSA_PUBLISHABLE_KEY=pk_test_...
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
CLI config files may omit `createClient`; `reactionary-feeds generate` will then use `createReactionaryFeedClientFactoryFromEnv()` automatically.
|
|
36
|
+
|
|
37
|
+
The default client builder only enables the provider capabilities needed by feeds and sitemap sources. For full commerce providers this is product search, product details, price, inventory, categories, and stores where supported.
|
|
38
|
+
|
|
39
|
+
`reactionary-feeds generate` can write one or more outputs in a single source pass by repeating `--output transformer=path`. It writes a progress report to stderr by default, including processed products, pages, elapsed time, and estimated time to completion once the first search result exposes the total product count. Use `--no-progress` to disable it.
|
|
40
|
+
|
|
41
|
+
Products are normalized concurrently, up to 10 products at a time by default. Set `productConcurrency` in the config or pass `--product-concurrency <count>` to the CLI to tune this.
|
|
42
|
+
|
|
43
|
+
Use `--testMode` to cap source reads to at most 2 pages while smoke-testing a feed configuration.
|
|
44
|
+
|
|
45
|
+
You can still provide a project-specific client factory when needed:
|
|
46
|
+
|
|
47
|
+
```js
|
|
48
|
+
import { createReactionaryFeedClientFactoryFromEnv } from '@reactionary/feeds';
|
|
49
|
+
|
|
50
|
+
export default {
|
|
51
|
+
createClient: createReactionaryFeedClientFactoryFromEnv(),
|
|
52
|
+
feeds: {
|
|
53
|
+
// ...
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Required Reactionary operations
|
|
59
|
+
|
|
60
|
+
The generator validates the supplied client before use. The client must expose:
|
|
61
|
+
|
|
62
|
+
- `productSearch.queryByTerm`
|
|
63
|
+
- `product.getBySKU`
|
|
64
|
+
- `price.getCustomerPrice`
|
|
65
|
+
- `price.getListPrice`
|
|
66
|
+
- `inventory.getBySKU`
|
|
67
|
+
|
|
68
|
+
`price.getCustomerPrice` is used as the effective feed price. `price.getListPrice` is exposed separately where the target feed supports it.
|
|
69
|
+
|
|
70
|
+
## Feed definitions
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
import type { ReactionaryFeedDefinition } from '@reactionary/feeds';
|
|
74
|
+
|
|
75
|
+
export const finnishFeed: ReactionaryFeedDefinition = {
|
|
76
|
+
languageContext: {
|
|
77
|
+
locale: 'fi-FI',
|
|
78
|
+
currencyCode: 'EUR',
|
|
79
|
+
},
|
|
80
|
+
search: {
|
|
81
|
+
term: '',
|
|
82
|
+
facets: [],
|
|
83
|
+
filters: ['market:fi'],
|
|
84
|
+
paginationOptions: {
|
|
85
|
+
pageNumber: 1,
|
|
86
|
+
pageSize: 50,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
productUrlBase: 'https://shop.example/{lang}/products/{slug}',
|
|
90
|
+
sellerName: 'Example Shop',
|
|
91
|
+
};
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The `search` value follows Reactionary core’s product-search identifier shape. `languageContext` is applied to the request context before the client is used.
|
|
95
|
+
|
|
96
|
+
Inventory requests use `fulfillmentCenterKeys` when configured on the feed. If the feed omits them, the generator uses `defaultFulfillmentCenterKeys` from the server or CLI config. The legacy singular `fulfillmentCenterKey` is still supported for existing configs.
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
export default {
|
|
100
|
+
defaultFulfillmentCenterKeys: ['helsinki', 'tampere'],
|
|
101
|
+
productConcurrency: 10,
|
|
102
|
+
feeds: {
|
|
103
|
+
finnish: {
|
|
104
|
+
...finnishFeed,
|
|
105
|
+
fulfillmentCenterKeys: ['turku'],
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`productUrlBase` can either be a normal base URL or a template supporting:
|
|
112
|
+
|
|
113
|
+
- `{lang}` — the lowercase language part of `languageContext.locale`, e.g. `fi-FI` -> `fi`.
|
|
114
|
+
- `{slug}` — the URL-encoded product slug.
|
|
115
|
+
|
|
116
|
+
## HTTP usage
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
import { ReactionaryFeedServer } from '@reactionary/feeds';
|
|
120
|
+
|
|
121
|
+
const feeds = new ReactionaryFeedServer(createClient, {
|
|
122
|
+
feeds: {
|
|
123
|
+
finnish: finnishFeed,
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
export const GET = (request: Request) => feeds.fetch(request);
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Routes:
|
|
131
|
+
|
|
132
|
+
- `GET /feeds` lists configured feed ids.
|
|
133
|
+
- `GET /feeds/transformers` lists available transformers.
|
|
134
|
+
- `GET /feeds/{feedId}/outputs/{transformerId}` streams generated output.
|
|
135
|
+
- `GET /sitemaps.xml` returns a sitemap index when `sitemaps` are configured.
|
|
136
|
+
- `GET /sitemaps/{sourceId}.xml` streams a configured sitemap source.
|
|
137
|
+
|
|
138
|
+
For Node HTTP:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
import { createServer } from 'node:http';
|
|
142
|
+
import { ReactionaryFeedServer } from '@reactionary/feeds';
|
|
143
|
+
|
|
144
|
+
const handler = new ReactionaryFeedServer(createClient, {
|
|
145
|
+
feeds: {
|
|
146
|
+
finnish: finnishFeed,
|
|
147
|
+
},
|
|
148
|
+
}).toNodeHandler();
|
|
149
|
+
|
|
150
|
+
createServer((request, response) => {
|
|
151
|
+
void handler(request, response);
|
|
152
|
+
}).listen(3000);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Sitemap sources
|
|
156
|
+
|
|
157
|
+
Sitemaps use a separate source configuration so product, category, and store URLs can be exposed independently. Use `include` to choose which configured sources should be listed and served.
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
new ReactionaryFeedServer(createClient, {
|
|
161
|
+
feeds: {
|
|
162
|
+
finnish: finnishFeed,
|
|
163
|
+
},
|
|
164
|
+
sitemaps: {
|
|
165
|
+
baseUrl: 'https://shop.example',
|
|
166
|
+
include: ['products-fi', 'categories-fi', 'stores-fi'],
|
|
167
|
+
sources: {
|
|
168
|
+
'products-fi': {
|
|
169
|
+
type: 'products',
|
|
170
|
+
feed: 'finnish',
|
|
171
|
+
changefreq: 'daily',
|
|
172
|
+
priority: 0.8,
|
|
173
|
+
},
|
|
174
|
+
'categories-fi': {
|
|
175
|
+
type: 'categories',
|
|
176
|
+
languageContext: {
|
|
177
|
+
locale: 'fi-FI',
|
|
178
|
+
currencyCode: 'EUR',
|
|
179
|
+
},
|
|
180
|
+
urlTemplate: 'https://shop.example/{lang}/categories/{slug}',
|
|
181
|
+
pageSize: 50,
|
|
182
|
+
maxDepth: 10,
|
|
183
|
+
},
|
|
184
|
+
'stores-fi': {
|
|
185
|
+
type: 'stores',
|
|
186
|
+
languageContext: {
|
|
187
|
+
locale: 'fi-FI',
|
|
188
|
+
currencyCode: 'EUR',
|
|
189
|
+
},
|
|
190
|
+
urlTemplate: 'https://shop.example/{lang}/stores/{slug}',
|
|
191
|
+
proximity: {
|
|
192
|
+
longitude: 12.5683,
|
|
193
|
+
latitude: 55.6761,
|
|
194
|
+
distance: 100,
|
|
195
|
+
limit: 100,
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Product sitemap sources reuse an existing product feed definition. Category sitemap sources enumerate top categories and, by default, recursively include child categories through `category.findChildCategories`. Store sitemap sources use `store.queryByProximity`; Reactionary core does not currently expose a provider-neutral “list all stores” operation, so the source requires an explicit proximity query.
|
|
204
|
+
|
|
205
|
+
Category and store URL templates support `{lang}`, `{slug}`, and `{id}` placeholders.
|
|
206
|
+
|
|
207
|
+
## CLI usage
|
|
208
|
+
|
|
209
|
+
The CLI loads an ESM config module that default-exports `{ feeds }` or `{ createClient, feeds }`.
|
|
210
|
+
Sample config files are available in [`documentation/`](./documentation/), including a minimal single-feed config and a multilingual da/nb/sv/fi/en config.
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
// feeds.config.mjs
|
|
214
|
+
export default {
|
|
215
|
+
feeds: {
|
|
216
|
+
finnish: {
|
|
217
|
+
languageContext: {
|
|
218
|
+
locale: 'fi-FI',
|
|
219
|
+
currencyCode: 'EUR',
|
|
220
|
+
},
|
|
221
|
+
search: {
|
|
222
|
+
term: '',
|
|
223
|
+
facets: [],
|
|
224
|
+
filters: [],
|
|
225
|
+
paginationOptions: {
|
|
226
|
+
pageNumber: 1,
|
|
227
|
+
pageSize: 50,
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
productUrlBase: 'https://shop.example/{lang}/products/{slug}',
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Commands:
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
reactionary-feeds list-feeds --config ./feeds.config.mjs
|
|
240
|
+
reactionary-feeds list-transformers --config ./feeds.config.mjs
|
|
241
|
+
reactionary-feeds generate \
|
|
242
|
+
--config ./feeds.config.mjs \
|
|
243
|
+
--feed finnish \
|
|
244
|
+
--testMode \
|
|
245
|
+
--output acp-product-feed=./products.jsonl \
|
|
246
|
+
--output google-merchant-feed=./google-merchant.xml \
|
|
247
|
+
--output sitemap-feed=./sitemap.xml
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
When running directly from this repository before the package is installed, build the package first and call the built CLI:
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
pnpm exec nx run feeds:build
|
|
254
|
+
node dist/packages/feeds/cli.js list-feeds --config ./packages/feeds/documentation/basic-product-feeds.config.mjs
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
When `@reactionary/feeds` is installed as a package, npm/pnpm/yarn exposes the `reactionary-feeds` command from the package `bin` entry.
|