@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.
Files changed (40) hide show
  1. package/README.md +257 -0
  2. package/bin/reactionary-feeds +2 -0
  3. package/cli.cjs +170941 -0
  4. package/cli.js +340 -0
  5. package/documentation/basic-product-feeds.config.mjs +32 -0
  6. package/documentation/multilingual-feeds.config.mjs +59 -0
  7. package/documentation/nextjs-route-example.mjs +31 -0
  8. package/documentation/sitemaps.config.mjs +77 -0
  9. package/index.js +13 -0
  10. package/lib/default-registry.js +18 -0
  11. package/lib/env-client-builder.js +244 -0
  12. package/lib/feed-generator.js +395 -0
  13. package/lib/feed-server.js +305 -0
  14. package/lib/feed-transformer.js +21 -0
  15. package/lib/feed-types.js +45 -0
  16. package/lib/load-cwd-env.js +16 -0
  17. package/lib/sitemap-generator.js +173 -0
  18. package/lib/sitemap-xml.js +32 -0
  19. package/lib/transformers/acp-product-feed.transformer.js +128 -0
  20. package/lib/transformers/google-merchant-feed.transformer.js +60 -0
  21. package/lib/transformers/pricerunner-feed.transformer.js +60 -0
  22. package/lib/transformers/shared.js +25 -0
  23. package/lib/transformers/sitemap-feed.transformer.js +26 -0
  24. package/package.json +32 -0
  25. package/src/cli.d.ts +2 -0
  26. package/src/index.d.ts +13 -0
  27. package/src/lib/default-registry.d.ts +2 -0
  28. package/src/lib/env-client-builder.d.ts +18 -0
  29. package/src/lib/feed-generator.d.ts +17 -0
  30. package/src/lib/feed-server.d.ts +28 -0
  31. package/src/lib/feed-transformer.d.ts +30 -0
  32. package/src/lib/feed-types.d.ts +185 -0
  33. package/src/lib/load-cwd-env.d.ts +4 -0
  34. package/src/lib/sitemap-generator.d.ts +20 -0
  35. package/src/lib/sitemap-xml.d.ts +3 -0
  36. package/src/lib/transformers/acp-product-feed.transformer.d.ts +4 -0
  37. package/src/lib/transformers/google-merchant-feed.transformer.d.ts +2 -0
  38. package/src/lib/transformers/pricerunner-feed.transformer.d.ts +2 -0
  39. package/src/lib/transformers/shared.d.ts +6 -0
  40. 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.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../cli.cjs';