@rudra-js/core 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.
Files changed (67) hide show
  1. package/README.md +64 -16
  2. package/dist/claim-screening.d.ts +10 -0
  3. package/dist/claim-screening.d.ts.map +1 -0
  4. package/dist/claim-screening.js +157 -0
  5. package/dist/claim-screening.js.map +1 -0
  6. package/dist/component-generator.d.ts +0 -70
  7. package/dist/component-generator.d.ts.map +1 -1
  8. package/dist/component-generator.js +27 -135
  9. package/dist/component-generator.js.map +1 -1
  10. package/dist/component-spec.d.ts +4 -159
  11. package/dist/component-spec.d.ts.map +1 -1
  12. package/dist/component-spec.js +0 -92
  13. package/dist/component-spec.js.map +1 -1
  14. package/dist/fallback-component.d.ts +0 -6
  15. package/dist/fallback-component.d.ts.map +1 -1
  16. package/dist/fallback-component.js +0 -21
  17. package/dist/fallback-component.js.map +1 -1
  18. package/dist/fit-to-shopper.d.ts +5 -1
  19. package/dist/fit-to-shopper.d.ts.map +1 -1
  20. package/dist/fit-to-shopper.js +5 -14
  21. package/dist/fit-to-shopper.js.map +1 -1
  22. package/dist/index.d.ts +1 -8
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +0 -7
  25. package/dist/index.js.map +1 -1
  26. package/dist/model-prompt.d.ts +1 -30
  27. package/dist/model-prompt.d.ts.map +1 -1
  28. package/dist/model-prompt.js +19 -78
  29. package/dist/model-prompt.js.map +1 -1
  30. package/dist/product-selection.d.ts +0 -29
  31. package/dist/product-selection.d.ts.map +1 -1
  32. package/dist/product-selection.js +1 -22
  33. package/dist/product-selection.js.map +1 -1
  34. package/dist/provider.d.ts +0 -55
  35. package/dist/provider.d.ts.map +1 -1
  36. package/dist/provider.js +0 -12
  37. package/dist/provider.js.map +1 -1
  38. package/dist/reconciliation.d.ts +6 -61
  39. package/dist/reconciliation.d.ts.map +1 -1
  40. package/dist/reconciliation.js +76 -466
  41. package/dist/reconciliation.js.map +1 -1
  42. package/dist/signal-digest.d.ts +1 -18
  43. package/dist/signal-digest.d.ts.map +1 -1
  44. package/dist/signal-digest.js +27 -66
  45. package/dist/signal-digest.js.map +1 -1
  46. package/dist/spec-cache.d.ts +8 -70
  47. package/dist/spec-cache.d.ts.map +1 -1
  48. package/dist/spec-cache.js +5 -68
  49. package/dist/spec-cache.js.map +1 -1
  50. package/dist/tracking-input.d.ts +0 -49
  51. package/dist/tracking-input.d.ts.map +1 -1
  52. package/dist/tracking-input.js +5 -105
  53. package/dist/tracking-input.js.map +1 -1
  54. package/package.json +7 -6
  55. package/src/claim-screening.ts +190 -0
  56. package/src/component-generator.ts +45 -240
  57. package/src/component-spec.ts +17 -118
  58. package/src/fallback-component.ts +0 -22
  59. package/src/fit-to-shopper.ts +12 -16
  60. package/src/index.ts +1 -8
  61. package/src/model-prompt.ts +25 -84
  62. package/src/product-selection.ts +6 -52
  63. package/src/provider.ts +4 -60
  64. package/src/reconciliation.ts +104 -499
  65. package/src/signal-digest.ts +34 -83
  66. package/src/spec-cache.ts +10 -97
  67. package/src/tracking-input.ts +17 -111
@@ -1,34 +1,4 @@
1
1
  import { z } from 'zod';
2
- /**
3
- * The tracking-input contract — the boundary between the host application and
4
- * rudra-js.
5
- *
6
- * rudra-js does not collect, store, or aggregate anything. The host owns its
7
- * tracking pipeline (an event stream, a CDP, a warehouse) and hands the
8
- * framework one JSON object per render. This module is that contract: one
9
- * schema, validated at the edge, so a malformed payload fails loudly here
10
- * rather than quietly producing a bad prompt several layers later.
11
- *
12
- * Every fixed-shape object below is a `strictObject`, so an unrecognised field
13
- * is an error rather than being dropped. That matters more than it looks: with
14
- * a lenient object, a host that misspells `recentSearches` gets a shopper who
15
- * silently looks like a first-time visitor, and nothing anywhere reports it.
16
- * The only dynamic shape is `interaction.meta`, which is a record by design.
17
- */
18
- /**
19
- * Every free-text field and every array is length-capped.
20
- *
21
- * These caps are not cosmetic. Host-supplied strings end up inside the prompt
22
- * we send to a language model, and a model is billed per token — so an
23
- * unbounded string is an unbounded bill, and an unbounded array of candidates
24
- * is the same problem multiplied.
25
- *
26
- * What this buys is that no single field is unbounded. It is deliberately not
27
- * an aggregate budget: the caps multiply out to far more than any context
28
- * window, because rejecting a large-but-legitimate payload is the wrong
29
- * response to one. Fitting a payload into a prompt is `digest`'s job, and it
30
- * trims rather than throws.
31
- */
32
2
  export declare const FIELD_LIMITS: {
33
3
  readonly identifier: 128;
34
4
  readonly shortText: 200;
@@ -44,7 +14,6 @@ export declare const FIELD_LIMITS: {
44
14
  readonly maxItems: 12;
45
15
  readonly reason: 120;
46
16
  };
47
- /** A product the generated component is permitted to place. */
48
17
  export declare const productSchema: z.ZodObject<{
49
18
  sku: z.ZodString;
50
19
  title: z.ZodString;
@@ -58,7 +27,6 @@ export declare const productSchema: z.ZodObject<{
58
27
  tags: z.ZodDefault<z.ZodArray<z.ZodString>>;
59
28
  }, z.core.$strict>;
60
29
  export type Product = z.infer<typeof productSchema>;
61
- /** Base shape for any signal that points at a single SKU. */
62
30
  export declare const skuSignalSchema: z.ZodObject<{
63
31
  sku: z.ZodString;
64
32
  category: z.ZodOptional<z.ZodString>;
@@ -84,11 +52,6 @@ export declare const purchaseSignalSchema: z.ZodObject<{
84
52
  price: z.ZodOptional<z.ZodNumber>;
85
53
  }, z.core.$strict>;
86
54
  export type PurchaseSignal = z.infer<typeof purchaseSignalSchema>;
87
- /**
88
- * Catch-all for everything else the shopper did. `type` is an open vocabulary
89
- * on purpose — 'scroll_depth', 'wishlist', 'filter_applied', whatever the host
90
- * already emits — so hosts do not have to map their events onto ours.
91
- */
92
55
  export declare const interactionSchema: z.ZodObject<{
93
56
  type: z.ZodString;
94
57
  sku: z.ZodOptional<z.ZodString>;
@@ -98,7 +61,6 @@ export declare const interactionSchema: z.ZodObject<{
98
61
  meta: z.ZodOptional<z.ZodPipe<z.ZodCustom<Record<string, string | number | boolean>, Record<string, string | number | boolean>>, z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>>;
99
62
  }, z.core.$strict>;
100
63
  export type Interaction = z.infer<typeof interactionSchema>;
101
- /** Where on the site this component is being rendered. */
102
64
  export declare const renderContextSchema: z.ZodObject<{
103
65
  surface: z.ZodString;
104
66
  slot: z.ZodDefault<z.ZodString>;
@@ -155,7 +117,6 @@ export declare const trackingSignalsSchema: z.ZodObject<{
155
117
  }, z.core.$strict>>>;
156
118
  }, z.core.$strict>;
157
119
  export type TrackingSignals = z.infer<typeof trackingSignalsSchema>;
158
- /** A set the shop sells together. The model never picks or invents one. */
159
120
  export declare const bundleSchema: z.ZodObject<{
160
121
  id: z.ZodString;
161
122
  skus: z.ZodArray<z.ZodString>;
@@ -246,18 +207,8 @@ export declare const trackingInputSchema: z.ZodObject<{
246
207
  }, z.core.$strict>>>;
247
208
  }, z.core.$strict>;
248
209
  export type TrackingInput = z.infer<typeof trackingInputSchema>;
249
- /** The shape a caller passes in, before defaults are applied. */
250
210
  export type TrackingInputDraft = z.input<typeof trackingInputSchema>;
251
- /**
252
- * The result of a non-throwing parse. Exported so a consumer can type a
253
- * validation failure without depending on zod directly.
254
- */
255
211
  export type TrackingInputResult = z.ZodSafeParseResult<TrackingInput>;
256
- /**
257
- * Validates a payload, throwing a `ZodError` if it does not satisfy the
258
- * contract. An invalid payload is a caller bug, and it should be loud.
259
- */
260
212
  export declare function parseTrackingInput(value: unknown): TrackingInput;
261
- /** Non-throwing variant, for callers that want to inspect the failure. */
262
213
  export declare function safeParseTrackingInput(value: unknown): TrackingInputResult;
263
214
  //# sourceMappingURL=tracking-input.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tracking-input.d.ts","sourceRoot":"","sources":["../src/tracking-input.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;;;;;;GAeG;AAEH;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,YAAY;aACvB,UAAU,EAAE,GAAG;aACf,SAAS,EAAE,GAAG;aACd,WAAW,EAAE,GAAG;aAChB,GAAG,EAAE,EAAE;aACP,cAAc,EAAE,EAAE;aAClB,WAAW,EAAE,EAAE;aACf,kBAAkB,EAAE,GAAG;aACvB,UAAU,EAAE,GAAG;aACf,iBAAiB,EAAE,CAAC;aACpB,OAAO,EAAE,EAAE;aACX,SAAS,EAAE,EAAE;aACb,QAAQ,EAAE,EAAE;aAGZ,MAAM,EAAE,GAAG;CACH,CAAC;AAmCX,+DAA+D;AAC/D,eAAO,MAAM,aAAa;;;;;;;;;;;kBAwBxB,CAAC;AACH,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEpD,6DAA6D;AAC7D,eAAO,MAAM,eAAe;;;;;kBAM1B,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAExD,eAAO,MAAM,gBAAgB;;;;;;;kBAG3B,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,eAAO,MAAM,oBAAoB;;;;;;;kBAG/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAkClE;;;;GAIG;AACH,eAAO,MAAM,iBAAiB;;;;;;;kBAO5B,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,0DAA0D;AAC1D,eAAO,MAAM,mBAAmB;;;;;;;;kBAiB9B,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAKhE,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAQhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,2EAA2E;AAC3E,eAAO,MAAM,YAAY;;;;;;kBAiBvB,CAAC;AACH,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAElD,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAqD7B,CAAC;AAEJ,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,iEAAiE;AACjE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAErE;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,kBAAkB,CAAC,aAAa,CAAC,CAAC;AAEtE;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,aAAa,CAEhE;AAED,0EAA0E;AAC1E,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,mBAAmB,CAE1E"}
1
+ {"version":3,"file":"tracking-input.d.ts","sourceRoot":"","sources":["../src/tracking-input.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,YAAY;aACvB,UAAU,EAAE,GAAG;aACf,SAAS,EAAE,GAAG;aACd,WAAW,EAAE,GAAG;aAChB,GAAG,EAAE,EAAE;aACP,cAAc,EAAE,EAAE;aAClB,WAAW,EAAE,EAAE;aACf,kBAAkB,EAAE,GAAG;aACvB,UAAU,EAAE,GAAG;aACf,iBAAiB,EAAE,CAAC;aACpB,OAAO,EAAE,EAAE;aACX,SAAS,EAAE,EAAE;aACb,QAAQ,EAAE,EAAE;aAEZ,MAAM,EAAE,GAAG;CACH,CAAC;AA0BX,eAAO,MAAM,aAAa;;;;;;;;;;;kBAmBxB,CAAC;AACH,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEpD,eAAO,MAAM,eAAe;;;;;kBAM1B,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAExD,eAAO,MAAM,gBAAgB;;;;;;;kBAG3B,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,eAAO,MAAM,oBAAoB;;;;;;;kBAG/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAmBlE,eAAO,MAAM,iBAAiB;;;;;;;kBAO5B,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,eAAO,MAAM,mBAAmB;;;;;;;;kBAe9B,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAKhE,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAQhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,eAAO,MAAM,YAAY;;;;;;kBAgBvB,CAAC;AACH,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAElD,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA0C7B,CAAC;AAEJ,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAErE,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,kBAAkB,CAAC,aAAa,CAAC,CAAC;AAEtE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,aAAa,CAEhE;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,mBAAmB,CAE1E"}
@@ -1,34 +1,4 @@
1
1
  import { z } from 'zod';
2
- /**
3
- * The tracking-input contract — the boundary between the host application and
4
- * rudra-js.
5
- *
6
- * rudra-js does not collect, store, or aggregate anything. The host owns its
7
- * tracking pipeline (an event stream, a CDP, a warehouse) and hands the
8
- * framework one JSON object per render. This module is that contract: one
9
- * schema, validated at the edge, so a malformed payload fails loudly here
10
- * rather than quietly producing a bad prompt several layers later.
11
- *
12
- * Every fixed-shape object below is a `strictObject`, so an unrecognised field
13
- * is an error rather than being dropped. That matters more than it looks: with
14
- * a lenient object, a host that misspells `recentSearches` gets a shopper who
15
- * silently looks like a first-time visitor, and nothing anywhere reports it.
16
- * The only dynamic shape is `interaction.meta`, which is a record by design.
17
- */
18
- /**
19
- * Every free-text field and every array is length-capped.
20
- *
21
- * These caps are not cosmetic. Host-supplied strings end up inside the prompt
22
- * we send to a language model, and a model is billed per token — so an
23
- * unbounded string is an unbounded bill, and an unbounded array of candidates
24
- * is the same problem multiplied.
25
- *
26
- * What this buys is that no single field is unbounded. It is deliberately not
27
- * an aggregate budget: the caps multiply out to far more than any context
28
- * window, because rejecting a large-but-legitimate payload is the wrong
29
- * response to one. Fitting a payload into a prompt is `digest`'s job, and it
30
- * trims rather than throws.
31
- */
32
2
  export const FIELD_LIMITS = {
33
3
  identifier: 128,
34
4
  shortText: 200,
@@ -42,34 +12,21 @@ export const FIELD_LIMITS = {
42
12
  bundles: 20,
43
13
  localeTag: 35,
44
14
  maxItems: 12,
45
- // Matches CLAMP.reason in reconciliation: a host reason is rendered in the
46
- // same place a model's is, so the same length has to hold.
47
15
  reason: 120,
48
16
  };
49
- /** Assigning this as an object key mutates the prototype instead of the object. */
50
17
  const RESERVED_META_KEY = '__proto__';
51
- /** Upper bound on any timestamp: 2100-01-01. */
52
18
  const MAX_EPOCH_MS = Date.UTC(2100, 0, 1);
53
19
  const identifier = () => z.string().min(1).max(FIELD_LIMITS.identifier);
54
20
  const optionalIdentifier = () => z.string().min(1).max(FIELD_LIMITS.identifier).optional();
55
- /**
56
- * Epoch milliseconds, used only for recency ordering. Bounded because a
57
- * negative or year-3000 timestamp does not fail anywhere downstream — it just
58
- * sorts to one end and silently reorders the shopper's history.
59
- */
60
21
  const epochMs = () => z.number().int().min(0).max(MAX_EPOCH_MS).optional();
61
- /**
62
- * An absolute http(s) URL, or a root-relative path such as `/images/tr-102.png`.
63
- * Anything else — a bare word, a `javascript:` URI, a protocol-relative `//host`
64
- * — is rejected before it can reach an `<img src>`.
65
- */
22
+ const isRootRelative = (value) => {
23
+ const asParsed = value.replace(/[\t\n\r]/g, '');
24
+ return value.startsWith('/') && /^\/(?![/\\])/.test(asParsed);
25
+ };
66
26
  const imageReference = () => z
67
27
  .string()
68
28
  .max(FIELD_LIMITS.shortText)
69
- .refine((value) => value.startsWith('/') && !value.startsWith('//')
70
- ? true
71
- : z.url({ protocol: /^https?$/ }).safeParse(value).success, { message: 'expected an http(s) URL or a root-relative path' });
72
- /** A product the generated component is permitted to place. */
29
+ .refine((value) => isRootRelative(value) ? true : z.url({ protocol: /^https?$/ }).safeParse(value).success, { message: 'expected an http(s) URL or a root-relative path' });
73
30
  export const productSchema = z.strictObject({
74
31
  sku: identifier(),
75
32
  title: z.string().min(1).max(FIELD_LIMITS.shortText),
@@ -79,15 +36,8 @@ export const productSchema = z.strictObject({
79
36
  .string()
80
37
  .regex(/^[A-Z]{3}$/, 'expected a three-letter ISO 4217 code')
81
38
  .default('USD'),
82
- // This lands in an `<img src>` the host did not write, so the scheme matters:
83
- // a bare capped string would accept '' and 'javascript:'. Root-relative paths
84
- // are allowed because most catalogs store images that way, and they carry no
85
- // scheme to abuse.
86
39
  imageUrl: imageReference().optional(),
87
40
  rating: z.number().min(0).max(5).optional(),
88
- // Your phrase for why this product is here, when your own ranking already has
89
- // one. It renders as the item's reason and is not screened: these are your
90
- // words about your product, like the title, so you stand behind them.
91
41
  reason: z.string().min(1).max(FIELD_LIMITS.reason).optional(),
92
42
  isInStock: z.boolean().default(true),
93
43
  tags: z
@@ -95,12 +45,10 @@ export const productSchema = z.strictObject({
95
45
  .max(FIELD_LIMITS.tagsPerProduct)
96
46
  .default([]),
97
47
  });
98
- /** Base shape for any signal that points at a single SKU. */
99
48
  export const skuSignalSchema = z.strictObject({
100
49
  sku: identifier(),
101
50
  category: optionalIdentifier(),
102
51
  at: epochMs(),
103
- /** Caller-supplied strength, 0..1. Defaults to 1 when absent. */
104
52
  weight: z.number().min(0).max(1).optional(),
105
53
  });
106
54
  export const viewSignalSchema = skuSignalSchema.extend({
@@ -111,32 +59,12 @@ export const purchaseSignalSchema = skuSignalSchema.extend({
111
59
  quantity: z.number().int().positive().default(1),
112
60
  price: z.number().nonnegative().optional(),
113
61
  });
114
- /**
115
- * `meta` is the one dynamic shape in the contract, so its key is checked before
116
- * the record is parsed rather than after. Zod builds its result by assigning
117
- * each key, and assigning `__proto__` sets the prototype instead of adding a
118
- * key — so the entry would vanish from the parsed output with no error, which
119
- * is the silent drop this module exists to prevent.
120
- *
121
- * `__proto__` is the only key that behaves this way. `constructor` and
122
- * `prototype` are ordinary own properties: assigning either shadows it on that
123
- * one object and leaves the prototype alone, so both survive a parse intact and
124
- * are accepted. `meta` is a host-defined vocabulary, and a shop with a facet,
125
- * filter, or CMS field by either name should get a component, not a throw.
126
- */
127
62
  const metaKeysAreSafe = z.custom((value) => typeof value === 'object' && value !== null && !Object.hasOwn(value, RESERVED_META_KEY), { message: `meta may not use the reserved key ${RESERVED_META_KEY}` });
128
- // `z.record` bounds key and value shape but not how many entries a record may
129
- // carry, so the count is checked separately.
130
63
  const metaSchema = metaKeysAreSafe.pipe(z
131
64
  .record(z.string().min(1).max(FIELD_LIMITS.identifier), z.union([z.string().max(FIELD_LIMITS.shortText), z.number(), z.boolean()]))
132
65
  .refine((entries) => Object.keys(entries).length <= FIELD_LIMITS.metaEntries, {
133
66
  message: `meta may carry at most ${FIELD_LIMITS.metaEntries} entries`,
134
67
  }));
135
- /**
136
- * Catch-all for everything else the shopper did. `type` is an open vocabulary
137
- * on purpose — 'scroll_depth', 'wishlist', 'filter_applied', whatever the host
138
- * already emits — so hosts do not have to map their events onto ours.
139
- */
140
68
  export const interactionSchema = z.strictObject({
141
69
  type: identifier(),
142
70
  sku: optionalIdentifier(),
@@ -145,23 +73,17 @@ export const interactionSchema = z.strictObject({
145
73
  value: z.union([z.string().max(FIELD_LIMITS.shortText), z.number(), z.boolean()]).optional(),
146
74
  meta: metaSchema.optional(),
147
75
  });
148
- /** Where on the site this component is being rendered. */
149
76
  export const renderContextSchema = z.strictObject({
150
- /** 'pdp', 'home', 'cart', 'search', or any host-defined surface. */
151
77
  surface: identifier(),
152
- /** Named placement, e.g. 'below-fold-recommendations'. */
153
78
  slot: z.string().min(1).max(FIELD_LIMITS.identifier).default('recommendations'),
154
79
  currentSku: optionalIdentifier(),
155
80
  currentCategory: optionalIdentifier(),
156
81
  searchQuery: z.string().max(FIELD_LIMITS.searchQuery).optional(),
157
- // One language tag, not the Accept-Language header it is often copied from:
158
- // the locale is part of the cohort cache key, so a list makes its own cohort.
159
82
  locale: z
160
83
  .string()
161
84
  .max(FIELD_LIMITS.localeTag)
162
85
  .regex(/^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$/, 'expected one language tag, such as en-US')
163
86
  .default('en-US'),
164
- /** Upper bound on products across the whole generated component. */
165
87
  maxItems: z.number().int().min(1).max(FIELD_LIMITS.maxItems).default(4),
166
88
  });
167
89
  const signalArray = (schema) => z.array(schema).max(FIELD_LIMITS.signalsPerCategory).default([]);
@@ -174,7 +96,6 @@ export const trackingSignalsSchema = z.strictObject({
174
96
  recentSearches: signalArray(z.string().min(1).max(FIELD_LIMITS.searchQuery)),
175
97
  interactions: signalArray(interactionSchema),
176
98
  });
177
- /** A set the shop sells together. The model never picks or invents one. */
178
99
  export const bundleSchema = z.strictObject({
179
100
  id: identifier(),
180
101
  skus: z
@@ -184,8 +105,6 @@ export const bundleSchema = z.strictObject({
184
105
  .refine((skus) => new Set(skus).size === skus.length, {
185
106
  message: 'a bundle must not list the same product twice',
186
107
  }),
187
- // The shop's price for the set — not derived from the parts, so the set says
188
- // which money it is in rather than borrowing it from a member.
189
108
  price: z.number().nonnegative(),
190
109
  currency: z
191
110
  .string()
@@ -202,19 +121,7 @@ export const trackingInputSchema = z
202
121
  isReturning: z.boolean().optional(),
203
122
  }),
204
123
  context: renderContextSchema,
205
- // A payload with no `signals` block at all is the cold-start case, not an
206
- // error. Every category defaults to empty, so a first-time visitor needs no
207
- // special handling from the host.
208
124
  signals: trackingSignalsSchema.prefault({}),
209
- /**
210
- * The only products the generated component may place. Merchandising rules
211
- * belong here: whatever the host leaves out cannot be recommended, which is
212
- * what makes it impossible to surface a product that does not exist or is not
213
- * merchandised for this shopper.
214
- *
215
- * SKUs must be unique — a duplicate is a host bug that spends prompt budget
216
- * twice and invites the same product in two slots.
217
- */
218
125
  candidates: z
219
126
  .array(productSchema)
220
127
  .min(1)
@@ -222,8 +129,6 @@ export const trackingInputSchema = z
222
129
  .refine((products) => new Set(products.map((product) => product.sku)).size === products.length, {
223
130
  message: 'candidates must have unique SKUs',
224
131
  }),
225
- // Ids must be unique — the renderer looks a bundle up by id alone, so a
226
- // duplicate would let it draw the wrong set at the wrong price.
227
132
  bundles: z
228
133
  .array(bundleSchema)
229
134
  .max(FIELD_LIMITS.bundles)
@@ -242,14 +147,9 @@ export const trackingInputSchema = z
242
147
  }
243
148
  return true;
244
149
  }, { message: 'every product in a bundle must also be a candidate' });
245
- /**
246
- * Validates a payload, throwing a `ZodError` if it does not satisfy the
247
- * contract. An invalid payload is a caller bug, and it should be loud.
248
- */
249
150
  export function parseTrackingInput(value) {
250
151
  return trackingInputSchema.parse(value);
251
152
  }
252
- /** Non-throwing variant, for callers that want to inspect the failure. */
253
153
  export function safeParseTrackingInput(value) {
254
154
  return trackingInputSchema.safeParse(value);
255
155
  }
@@ -1 +1 @@
1
- {"version":3,"file":"tracking-input.js","sourceRoot":"","sources":["../src/tracking-input.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;;;;;;GAeG;AAEH;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,UAAU,EAAE,GAAG;IACf,SAAS,EAAE,GAAG;IACd,WAAW,EAAE,GAAG;IAChB,GAAG,EAAE,EAAE;IACP,cAAc,EAAE,EAAE;IAClB,WAAW,EAAE,EAAE;IACf,kBAAkB,EAAE,GAAG;IACvB,UAAU,EAAE,GAAG;IACf,iBAAiB,EAAE,CAAC;IACpB,OAAO,EAAE,EAAE;IACX,SAAS,EAAE,EAAE;IACb,QAAQ,EAAE,EAAE;IACZ,2EAA2E;IAC3E,2DAA2D;IAC3D,MAAM,EAAE,GAAG;CACH,CAAC;AAEX,mFAAmF;AACnF,MAAM,iBAAiB,GAAG,WAAW,CAAC;AAEtC,gDAAgD;AAChD,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAE1C,MAAM,UAAU,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;AACxE,MAAM,kBAAkB,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,QAAQ,EAAE,CAAC;AAE3F;;;;GAIG;AACH,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,QAAQ,EAAE,CAAC;AAE3E;;;;GAIG;AACH,MAAM,cAAc,GAAG,GAAG,EAAE,CAC1B,CAAC;KACE,MAAM,EAAE;KACR,GAAG,CAAC,YAAY,CAAC,SAAS,CAAC;KAC3B,MAAM,CACL,CAAC,KAAK,EAAE,EAAE,CACR,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC;IAC9C,CAAC,CAAC,IAAI;IACN,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,EAC9D,EAAE,OAAO,EAAE,iDAAiD,EAAE,CAC/D,CAAC;AAEN,+DAA+D;AAC/D,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAC,YAAY,CAAC;IAC1C,GAAG,EAAE,UAAU,EAAE;IACjB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,SAAS,CAAC;IACpD,QAAQ,EAAE,UAAU,EAAE;IACtB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,WAAW,EAAE;IAC/B,QAAQ,EAAE,CAAC;SACR,MAAM,EAAE;SACR,KAAK,CAAC,YAAY,EAAE,uCAAuC,CAAC;SAC5D,OAAO,CAAC,KAAK,CAAC;IACjB,8EAA8E;IAC9E,8EAA8E;IAC9E,6EAA6E;IAC7E,mBAAmB;IACnB,QAAQ,EAAE,cAAc,EAAE,CAAC,QAAQ,EAAE;IACrC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;IAC3C,8EAA8E;IAC9E,2EAA2E;IAC3E,sEAAsE;IACtE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE;IAC7D,SAAS,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;IACpC,IAAI,EAAE,CAAC;SACJ,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;SAC9C,GAAG,CAAC,YAAY,CAAC,cAAc,CAAC;SAChC,OAAO,CAAC,EAAE,CAAC;CACf,CAAC,CAAC;AAGH,6DAA6D;AAC7D,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,YAAY,CAAC;IAC5C,GAAG,EAAE,UAAU,EAAE;IACjB,QAAQ,EAAE,kBAAkB,EAAE;IAC9B,EAAE,EAAE,OAAO,EAAE;IACb,iEAAiE;IACjE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;CAC5C,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,gBAAgB,GAAG,eAAe,CAAC,MAAM,CAAC;IACrD,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IAC7C,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE;CAC7C,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,oBAAoB,GAAG,eAAe,CAAC,MAAM,CAAC;IACzD,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IAChD,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE;CAC3C,CAAC,CAAC;AAGH;;;;;;;;;;;;GAYG;AACH,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAC9B,CAAC,KAAK,EAAE,EAAE,CACR,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,iBAAiB,CAAC,EACzF,EAAE,OAAO,EAAE,qCAAqC,iBAAiB,EAAE,EAAE,CACtE,CAAC;AAEF,8EAA8E;AAC9E,6CAA6C;AAC7C,MAAM,UAAU,GAAG,eAAe,CAAC,IAAI,CACrC,CAAC;KACE,MAAM,CACL,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,UAAU,CAAC,EAC9C,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,YAAY,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAC3E;KACA,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,IAAI,YAAY,CAAC,WAAW,EAAE;IAC5E,OAAO,EAAE,0BAA0B,YAAY,CAAC,WAAW,UAAU;CACtE,CAAC,CACL,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,YAAY,CAAC;IAC9C,IAAI,EAAE,UAAU,EAAE;IAClB,GAAG,EAAE,kBAAkB,EAAE;IACzB,QAAQ,EAAE,kBAAkB,EAAE;IAC9B,EAAE,EAAE,OAAO,EAAE;IACb,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,YAAY,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE;IAC5F,IAAI,EAAE,UAAU,CAAC,QAAQ,EAAE;CAC5B,CAAC,CAAC;AAGH,0DAA0D;AAC1D,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,YAAY,CAAC;IAChD,oEAAoE;IACpE,OAAO,EAAE,UAAU,EAAE;IACrB,0DAA0D;IAC1D,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,iBAAiB,CAAC;IAC/E,UAAU,EAAE,kBAAkB,EAAE;IAChC,eAAe,EAAE,kBAAkB,EAAE;IACrC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,QAAQ,EAAE;IAChE,4EAA4E;IAC5E,8EAA8E;IAC9E,MAAM,EAAE,CAAC;SACN,MAAM,EAAE;SACR,GAAG,CAAC,YAAY,CAAC,SAAS,CAAC;SAC3B,KAAK,CAAC,uCAAuC,EAAE,0CAA0C,CAAC;SAC1F,OAAO,CAAC,OAAO,CAAC;IACnB,oEAAoE;IACpE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;CACxE,CAAC,CAAC;AAGH,MAAM,WAAW,GAAG,CAA2B,MAAc,EAAE,EAAE,CAC/D,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,kBAAkB,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;AAEnE,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,YAAY,CAAC;IAClD,KAAK,EAAE,WAAW,CAAC,eAAe,CAAC;IACnC,QAAQ,EAAE,WAAW,CAAC,eAAe,CAAC;IACtC,UAAU,EAAE,WAAW,CAAC,gBAAgB,CAAC;IACzC,aAAa,EAAE,WAAW,CAAC,oBAAoB,CAAC;IAChD,IAAI,EAAE,WAAW,CAAC,eAAe,CAAC;IAClC,cAAc,EAAE,WAAW,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;IAC5E,YAAY,EAAE,WAAW,CAAC,iBAAiB,CAAC;CAC7C,CAAC,CAAC;AAGH,2EAA2E;AAC3E,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC,YAAY,CAAC;IACzC,EAAE,EAAE,UAAU,EAAE;IAChB,IAAI,EAAE,CAAC;SACJ,KAAK,CAAC,UAAU,EAAE,CAAC;SACnB,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,YAAY,CAAC,iBAAiB,CAAC;SACnC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,MAAM,EAAE;QACpD,OAAO,EAAE,+CAA+C;KACzD,CAAC;IACJ,6EAA6E;IAC7E,+DAA+D;IAC/D,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,WAAW,EAAE;IAC/B,QAAQ,EAAE,CAAC;SACR,MAAM,EAAE;SACR,KAAK,CAAC,YAAY,EAAE,uCAAuC,CAAC;SAC5D,OAAO,CAAC,KAAK,CAAC;IACjB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,QAAQ,EAAE;CAChE,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC;KACjC,YAAY,CAAC;IACZ,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC;IAC1C,IAAI,EAAE,CAAC,CAAC,YAAY,CAAC;QACnB,EAAE,EAAE,UAAU,EAAE;QAChB,OAAO,EAAE,kBAAkB,EAAE;QAC7B,WAAW,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;KACpC,CAAC;IACF,OAAO,EAAE,mBAAmB;IAC5B,0EAA0E;IAC1E,4EAA4E;IAC5E,kCAAkC;IAClC,OAAO,EAAE,qBAAqB,CAAC,QAAQ,CAAC,EAAE,CAAC;IAC3C;;;;;;;;OAQG;IACH,UAAU,EAAE,CAAC;SACV,KAAK,CAAC,aAAa,CAAC;SACpB,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,YAAY,CAAC,UAAU,CAAC;SAC5B,MAAM,CACL,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,MAAM,EACtF;QACE,OAAO,EAAE,kCAAkC;KAC5C,CACF;IACH,wEAAwE;IACxE,gEAAgE;IAChE,OAAO,EAAE,CAAC;SACP,KAAK,CAAC,YAAY,CAAC;SACnB,GAAG,CAAC,YAAY,CAAC,OAAO,CAAC;SACzB,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,MAAM,EAAE;QACxF,OAAO,EAAE,8BAA8B;KACxC,CAAC;SACD,OAAO,CAAC,EAAE,CAAC;CACf,CAAC;KACD,MAAM,CACL,CAAC,KAAK,EAAE,EAAE;IACR,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9E,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QACnC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;YAC9B,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,OAAO,KAAK,CAAC;QAC5C,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC,EACD,EAAE,OAAO,EAAE,oDAAoD,EAAE,CAClE,CAAC;AAaJ;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAc;IAC/C,OAAO,mBAAmB,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;AAC1C,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,sBAAsB,CAAC,KAAc;IACnD,OAAO,mBAAmB,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;AAC9C,CAAC"}
1
+ {"version":3,"file":"tracking-input.js","sourceRoot":"","sources":["../src/tracking-input.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rudra-js/core",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "The contracts and logic that turn one tracking payload into one renderable component specification",
5
5
  "keywords": [
6
6
  "llm",
@@ -50,13 +50,14 @@
50
50
  "publishConfig": {
51
51
  "access": "public"
52
52
  },
53
+ "dependencies": {
54
+ "zod": "^4.0.0"
55
+ },
53
56
  "peerDependencies": {
54
- "@rudra-js/attested": "^0.5.0",
55
- "zod": "^4.5.0"
57
+ "@rudra-js/attested": "^0.7.0"
56
58
  },
57
59
  "devDependencies": {
58
- "@rudra-js/attested": "0.5.0",
59
- "@types/node": "^26.4.1",
60
- "zod": "^4.5.4"
60
+ "@rudra-js/attested": "0.7.0",
61
+ "@types/node": "^26.4.1"
61
62
  }
62
63
  }
@@ -0,0 +1,190 @@
1
+ import { offeredCandidates } from './model-prompt.js';
2
+ import type { Product, TrackingInput } from './tracking-input.js';
3
+
4
+ const CLAIM_PATTERNS: { kind: string; patterns: RegExp[] }[] = [
5
+ {
6
+ kind: 'rating',
7
+ patterns: [
8
+ /\breviews?\b|\breviewed\b/,
9
+ /\b(?:\d+(?:\.\d+)?|three|four|five)[\s-]?stars?\b/,
10
+ /\bstars?[\s-]?ratings?\b/,
11
+
12
+ /\b(?:customer|shopper|buyer|user|average|overall)[\s-]ratings?\b/,
13
+
14
+ /\bratings?\s+of\s+[0-5]\.\d\b/,
15
+
16
+ /\b[0-5](?:\.\d)?\s+out of\s+(?:5|five)\b/,
17
+ /\b(?:highly|top|best|well|poorly|five|four)[\s-]rated\b/,
18
+
19
+ /\brated\s+(?:[0-5]\.\d|(?:[0-5]|three|four|five)\s+(?:stars?|out of))\b/,
20
+
21
+ /\bbest[\s-]?sell(?:er|ers|ing)\b/,
22
+
23
+ /(?:\bnumber one|\bno\.? ?1|#\s?1)[\s-]?sell(?:er|ers|ing)\b/,
24
+ /\bloved by (?:thousands|hundreds|millions|\d)/,
25
+ ],
26
+ },
27
+ {
28
+ kind: 'price',
29
+ patterns: [
30
+ /[$£€¥₹]/,
31
+ /\b\d+(?:\.\d+)?\s?(?:usd|eur|gbp|dollars?|pounds?|euros?)\b/,
32
+
33
+ /\bdollars?\b|\beuros?\b/,
34
+
35
+ /\b(?:usd|eur|gbp)\s?\d/,
36
+
37
+ /\bkr\s?\d|\d\s?kr\b/,
38
+ /\bpric(?:e|es|ed|ing)\b/,
39
+
40
+ /\bwas\s+[$£€¥]?\s?\d[\d,.]*\s*[,;–—-]?\s*now\s+[$£€¥]?\s?\d/,
41
+
42
+ /\bcheap(?:er|est)\b/,
43
+ /\baffordable\b|\bbargain\b/,
44
+
45
+ /\bcosts?\s+(?:less|more|only|just|about|around|[$£€¥]?\d)/,
46
+ /\blow(?:er)?[\s-]cost\b/,
47
+
48
+ /\bsaves?\s+(?:you\s+)?(?:money|cash|[$£€¥]\s?\d)/,
49
+ ],
50
+ },
51
+ {
52
+ kind: 'discount',
53
+ patterns: [
54
+ /\d+(?:\.\d+)?\s?(?:%|percent)\s*(?:off\b|discount|reduction|less\b)/,
55
+ /\b(?:save|saving|savings|off|discount|extra|up to)\s+(?:up to\s+)?\d+(?:\.\d+)?\s?(?:%|percent)/,
56
+ /\bdiscount(?:s|ed)?\b/,
57
+ /\bsale\b|\bmarked down\b|\bdeal of\b/,
58
+ /\bhalf[\s-]?(?:price|off)\b/,
59
+
60
+ /\bsaves?\s+\d[\d.,]*\s+off\b/,
61
+
62
+ /\bclearance\s+(?:sale|price|event|deal)\b|\bon clearance\b/,
63
+
64
+ /\bprice reduced\b|\breduced price\b|\breduced by \d+\s?(?:%|percent)|\breduced\s+(?:this|next|last)\s+week\b/,
65
+ ],
66
+ },
67
+ {
68
+ kind: 'delivery',
69
+ patterns: [
70
+ /\bdelivery\b|\bshipping\b/,
71
+ /\bnext[\s-]day\b|\bsame[\s-]day\b|\bovernight\b/,
72
+ /\b(?:ships?|arrives?|arriving|delivered|by|before|in time for)\s+(?:on\s+)?(?:today|tomorrow|tonight|this week|next week|the weekend|monday|tuesday|wednesday|thursday|friday|saturday|sunday)\b/,
73
+ /\b(?:ships?|arrives?|arriving)\s+(?:in|within)\s+\d/,
74
+ /\bin time for\b/,
75
+ ],
76
+ },
77
+ {
78
+ kind: 'stock',
79
+ patterns: [
80
+ /\b(?:in|out of|low on) stock\b/,
81
+ /\blimited stock\b/,
82
+ /\brestocked?\b|\bsold out\b/,
83
+
84
+ /\b(?:only\s+)?(?:\d+|a few|a handful|a couple|one|few)\s+(?:left|remain(?:s|ing)?)\b/,
85
+ /\bselling fast\b|\b(?:almost|nearly) gone\b|\bwhile stocks last\b/,
86
+ ],
87
+ },
88
+ ];
89
+
90
+ const CONFUSABLES: Record<string, string> = {
91
+ а: 'a',
92
+ в: 'b',
93
+ е: 'e',
94
+ к: 'k',
95
+ м: 'm',
96
+ н: 'h',
97
+ о: 'o',
98
+ р: 'p',
99
+ с: 'c',
100
+ т: 't',
101
+ х: 'x',
102
+ у: 'y',
103
+ ѕ: 's',
104
+ і: 'i',
105
+ ј: 'j',
106
+ ԁ: 'd',
107
+ һ: 'h',
108
+ ӏ: 'l',
109
+ α: 'a',
110
+ ε: 'e',
111
+ ι: 'i',
112
+ κ: 'k',
113
+ ν: 'v',
114
+ ο: 'o',
115
+ ρ: 'p',
116
+ τ: 't',
117
+ υ: 'u',
118
+ χ: 'x',
119
+ ϲ: 'c',
120
+ };
121
+
122
+ const INVISIBLE = /[\p{Cf}\p{Default_Ignorable_Code_Point}\p{Cc}]/gu;
123
+
124
+ const SPACING = /[\t\n\v\f\r\u0085]/;
125
+
126
+ const MARKS = /[\p{Mn}\p{Me}]/gu;
127
+
128
+ function normaliseForClaims(text: string): string {
129
+ const lowered = text
130
+ .replace(INVISIBLE, (char) => (SPACING.test(char) ? char : ''))
131
+ .normalize('NFKC')
132
+ .toLowerCase()
133
+ .normalize('NFC')
134
+ .replace(MARKS, '');
135
+
136
+ let folded = '';
137
+ for (const char of lowered) folded += CONFUSABLES[char] ?? char;
138
+ return folded;
139
+ }
140
+
141
+ export const ALLOWED_PHRASES = ['does not feel cheap', "doesn't feel cheap", 'last few miles'];
142
+
143
+ const DIGIT = /\p{Nd}/u;
144
+
145
+ const BARE_EXPONENT = /^[+-]?\d+(?:\.\d+)?[eE]([+-]?\d+)$/;
146
+
147
+ const MAX_EXPONENT = 1000;
148
+
149
+ function canLayOut(fact: string): boolean {
150
+ const match = BARE_EXPONENT.exec(fact.trim());
151
+ if (match === null) return true;
152
+ return Math.abs(Number(match[1] ?? '')) <= MAX_EXPONENT;
153
+ }
154
+
155
+ export function productFacts(product: Product): string[] {
156
+ const facts: string[] = [];
157
+ if (DIGIT.test(product.category) && canLayOut(product.category)) facts.push(product.category);
158
+ for (const tag of product.tags) {
159
+ if (DIGIT.test(tag) && canLayOut(tag)) facts.push(tag);
160
+ }
161
+ return facts;
162
+ }
163
+
164
+ export interface HostFacts {
165
+ pooled: string[];
166
+ bySku: Map<string, string[]>;
167
+ }
168
+
169
+ export function hostFacts(input: TrackingInput): HostFacts {
170
+ const pooled = new Set<string>();
171
+ const bySku = new Map<string, string[]>();
172
+
173
+ for (const product of offeredCandidates(input)) {
174
+ const own = productFacts(product);
175
+ bySku.set(product.sku, own);
176
+ for (const fact of own) pooled.add(fact);
177
+ }
178
+
179
+ return { pooled: [...pooled], bySku };
180
+ }
181
+
182
+ export function claimIn(text: string): string | null {
183
+ const normalised = normaliseForClaims(text);
184
+ for (const claim of CLAIM_PATTERNS) {
185
+ for (const pattern of claim.patterns) {
186
+ if (pattern.test(normalised)) return claim.kind;
187
+ }
188
+ }
189
+ return null;
190
+ }