@rudra-js/core 0.7.4 → 0.7.6

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 (55) hide show
  1. package/README.md +14 -4
  2. package/dist/claim-screening.d.ts +3 -3
  3. package/dist/claim-screening.d.ts.map +1 -1
  4. package/dist/claim-screening.js +30 -93
  5. package/dist/claim-screening.js.map +1 -1
  6. package/dist/component-generator.d.ts.map +1 -1
  7. package/dist/component-generator.js +161 -176
  8. package/dist/component-generator.js.map +1 -1
  9. package/dist/component-spec.d.ts +1 -0
  10. package/dist/component-spec.d.ts.map +1 -1
  11. package/dist/component-spec.js +3 -0
  12. package/dist/component-spec.js.map +1 -1
  13. package/dist/fallback-component.d.ts +1 -1
  14. package/dist/fallback-component.d.ts.map +1 -1
  15. package/dist/fallback-component.js +9 -17
  16. package/dist/fallback-component.js.map +1 -1
  17. package/dist/fit-to-shopper.d.ts.map +1 -1
  18. package/dist/fit-to-shopper.js +19 -19
  19. package/dist/fit-to-shopper.js.map +1 -1
  20. package/dist/model-prompt.d.ts.map +1 -1
  21. package/dist/model-prompt.js +56 -39
  22. package/dist/model-prompt.js.map +1 -1
  23. package/dist/product-selection.d.ts.map +1 -1
  24. package/dist/product-selection.js +15 -31
  25. package/dist/product-selection.js.map +1 -1
  26. package/dist/provider.d.ts.map +1 -1
  27. package/dist/provider.js.map +1 -1
  28. package/dist/reconciliation.d.ts +3 -3
  29. package/dist/reconciliation.d.ts.map +1 -1
  30. package/dist/reconciliation.js +133 -171
  31. package/dist/reconciliation.js.map +1 -1
  32. package/dist/signal-digest.d.ts +1 -1
  33. package/dist/signal-digest.d.ts.map +1 -1
  34. package/dist/signal-digest.js +51 -52
  35. package/dist/signal-digest.js.map +1 -1
  36. package/dist/spec-cache.d.ts +0 -4
  37. package/dist/spec-cache.d.ts.map +1 -1
  38. package/dist/spec-cache.js +15 -35
  39. package/dist/spec-cache.js.map +1 -1
  40. package/dist/tracking-input.d.ts.map +1 -1
  41. package/dist/tracking-input.js +16 -19
  42. package/dist/tracking-input.js.map +1 -1
  43. package/package.json +8 -6
  44. package/src/claim-screening.ts +34 -114
  45. package/src/component-generator.ts +223 -220
  46. package/src/component-spec.ts +4 -15
  47. package/src/fallback-component.ts +11 -20
  48. package/src/fit-to-shopper.ts +19 -21
  49. package/src/model-prompt.ts +55 -54
  50. package/src/product-selection.ts +21 -50
  51. package/src/provider.ts +0 -4
  52. package/src/reconciliation.ts +159 -268
  53. package/src/signal-digest.ts +50 -79
  54. package/src/spec-cache.ts +15 -48
  55. package/src/tracking-input.ts +21 -36
package/README.md CHANGED
@@ -1,7 +1,16 @@
1
1
  # @rudra-js/core
2
2
 
3
- The contracts and logic that turn one tracking payload into one renderable
4
- component specification.
3
+ This package is part of [rudra-js](https://rudrajs.com).
4
+
5
+ rudra-js is an open-source TypeScript library (MIT) you install from npm. A language model designs one product-recommendation block for a page in your shop, rudra-js checks it against your catalog, and React renders it.
6
+
7
+ It is not a hosted platform, a visual or low-code app builder, or a workspace you sign up for.
8
+
9
+ You give this package one page view: the shopper and what they've been doing,
10
+ the page they're on, and the products you're willing to show. It gives back a
11
+ specification for the recommendations on that page, written by your model or
12
+ built without one, and checked against those products. There's no React in it.
13
+ `@rudra-js/react` turns the specification into markup.
5
14
 
6
15
  Every export of this package is documented at [rudrajs.com](https://rudrajs.com/docs).
7
16
 
@@ -55,8 +64,9 @@ appear, that's why. It isn't your catalog.
55
64
 
56
65
  Want to render a spec you wrote yourself, still without a model? Pass
57
66
  `createFixedSpecProvider(spec)` as the provider and it answers every request with
58
- that spec. That's how our tests exercise the blocks the deterministic component
59
- never emits.
67
+ that spec. It still goes through reconciliation, so it needs a headline that
68
+ survives screening and at least one product on show, or you get the
69
+ deterministic component.
60
70
 
61
71
  ## Options
62
72
 
@@ -1,10 +1,10 @@
1
- import type { Product, TrackingInput } from './tracking-input.js';
1
+ import type { TrackingInput } from './tracking-input.js';
2
2
  export declare const ALLOWED_PHRASES: string[];
3
- export declare function productFacts(product: Product): string[];
3
+ export declare const NO_FACTS: readonly string[];
4
4
  export interface HostFacts {
5
5
  pooled: string[];
6
6
  bySku: Map<string, string[]>;
7
7
  }
8
8
  export declare function hostFacts(input: TrackingInput): HostFacts;
9
- export declare function claimIn(text: string): string | null;
9
+ export declare function unbackedClaim(raw: string, clamped: string, facts: readonly string[]): string | null;
10
10
  //# sourceMappingURL=claim-screening.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"claim-screening.d.ts","sourceRoot":"","sources":["../src/claim-screening.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAoKlE,eAAO,MAAM,eAAe,UAK3B,CAAC;AAIF,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,EAAE,CAOvD;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CAC9B;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,aAAa,GAAG,SAAS,CAWzD;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAWnD"}
1
+ {"version":3,"file":"claim-screening.d.ts","sourceRoot":"","sources":["../src/claim-screening.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AA4EzD,eAAO,MAAM,eAAe,UAK3B,CAAC;AAKF,eAAO,MAAM,QAAQ,EAAE,SAAS,MAAM,EAAO,CAAC;AAE9C,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CAC9B;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,aAAa,GAAG,SAAS,CAWzD;AAaD,wBAAgB,aAAa,CAC3B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,SAAS,MAAM,EAAE,GACvB,MAAM,GAAG,IAAI,CASf"}
@@ -1,3 +1,4 @@
1
+ import { foldLookalikes, verify } from '@rudra-js/attested';
1
2
  import { offeredCandidates } from './model-prompt.js';
2
3
  const CLAIM_PATTERNS = [
3
4
  {
@@ -10,7 +11,8 @@ const CLAIM_PATTERNS = [
10
11
  /\bratings?\s+of\s+[0-5]\.\d\b/,
11
12
  /\b[0-5](?:\.\d)?\s+out of\s+(?:5|five)\b/,
12
13
  /\b(?:highly|top|best|well|poorly|five|four)[\s-]rated\b/,
13
- /\brated\s+(?:[0-5]\.\d|(?:[0-5]|three|four|five)\s+(?:stars?|out of))\b/,
14
+ /\brated\s+[0-5]\.\d\b/,
15
+ /\brated\s+(?:[0-5]|three|four|five)\s+(?:stars?|out of)\b/,
14
16
  /\bbest[\s-]?sell(?:er|ers|ing)\b/,
15
17
  /(?:\bnumber one|\bno\.? ?1|#\s?1)[\s-]?sell(?:er|ers|ing)\b/,
16
18
  /\bloved by (?:thousands|hundreds|millions|\d)/,
@@ -43,7 +45,10 @@ const CLAIM_PATTERNS = [
43
45
  /\bhalf[\s-]?(?:price|off)\b/,
44
46
  /\bsaves?\s+\d[\d.,]*\s+off\b/,
45
47
  /\bclearance\s+(?:sale|price|event|deal)\b|\bon clearance\b/,
46
- /\bprice reduced\b|\breduced price\b|\breduced by \d+\s?(?:%|percent)|\breduced\s+(?:this|next|last)\s+week\b/,
48
+ /\bprice reduced\b/,
49
+ /\breduced price\b/,
50
+ /\breduced by \d+\s?(?:%|percent)/,
51
+ /\breduced\s+(?:this|next|last)\s+week\b/,
47
52
  ],
48
53
  },
49
54
  {
@@ -68,114 +73,46 @@ const CLAIM_PATTERNS = [
68
73
  ],
69
74
  },
70
75
  ];
71
- const CONFUSABLES = {
72
- а: 'a',
73
- в: 'b',
74
- е: 'e',
75
- к: 'k',
76
- м: 'm',
77
- н: 'h',
78
- о: 'o',
79
- р: 'p',
80
- с: 'c',
81
- т: 't',
82
- х: 'x',
83
- у: 'y',
84
- ѕ: 's',
85
- і: 'i',
86
- ј: 'j',
87
- ԁ: 'd',
88
- һ: 'h',
89
- ӏ: 'l',
90
- α: 'a',
91
- ε: 'e',
92
- ι: 'i',
93
- κ: 'k',
94
- ν: 'v',
95
- ο: 'o',
96
- ρ: 'p',
97
- τ: 't',
98
- υ: 'u',
99
- χ: 'x',
100
- ϳ: 'j',
101
- ѵ: 'v',
102
- };
103
- // Capitals that lowercasing reads wrong: Ν becomes ν, which reads as v.
104
- const CAPITALS = {
105
- Β: 'b',
106
- Ζ: 'z',
107
- Η: 'h',
108
- Μ: 'm',
109
- Ν: 'n',
110
- Υ: 'y',
111
- Ү: 'y',
112
- Ӏ: 'i',
113
- Ԛ: 'q',
114
- Ԝ: 'w',
115
- Ϻ: 'm',
116
- Ϝ: 'f',
117
- Ԍ: 'g',
118
- };
119
- const INVISIBLE = /[\p{Cf}\p{Default_Ignorable_Code_Point}\p{Cc}]/gu;
120
- const SPACING = /[\t\n\v\f\r\u0085]/;
121
- const MARKS = /[\p{Mn}\p{Me}]/gu;
122
- function normaliseForClaims(text, byLook) {
123
- // NFKC would turn the lunate sigmas into ς and Σ, which the fold can't read as c.
124
- const compatible = text
125
- .replace(INVISIBLE, (char) => (SPACING.test(char) ? char : ''))
126
- .replace(/[ϲϹ]/g, 'c')
127
- .normalize('NFKC');
128
- let cased = compatible;
129
- if (byLook) {
130
- cased = '';
131
- for (const char of compatible)
132
- cased += CAPITALS[char] ?? char;
133
- }
134
- const lowered = cased.toLowerCase().normalize('NFC').replace(MARKS, '');
135
- let folded = '';
136
- for (const char of lowered)
137
- folded += CONFUSABLES[char] ?? char;
138
- return folded;
139
- }
140
76
  export const ALLOWED_PHRASES = [
141
77
  'does not feel cheap',
142
78
  "doesn't feel cheap",
143
79
  'last few miles',
144
80
  'sustainable pace',
145
81
  ];
146
- const DIGIT = /\p{Nd}/u;
147
- export function productFacts(product) {
148
- const facts = [];
149
- if (DIGIT.test(product.category))
150
- facts.push(product.category);
151
- for (const tag of product.tags) {
152
- if (DIGIT.test(tag))
153
- facts.push(tag);
154
- }
155
- return facts;
156
- }
82
+ const FACT_DIGIT = /\p{Nd}/u;
83
+ const NUMERAL = /[\p{Nd}\p{No}\p{Nl}]/u;
84
+ export const NO_FACTS = [];
157
85
  export function hostFacts(input) {
158
86
  const pooled = new Set();
159
87
  const bySku = new Map();
160
88
  for (const product of offeredCandidates(input)) {
161
- const own = productFacts(product);
89
+ const own = [product.category, ...product.tags].filter((text) => FACT_DIGIT.test(text));
162
90
  bySku.set(product.sku, own);
163
91
  for (const fact of own)
164
92
  pooled.add(fact);
165
93
  }
166
94
  return { pooled: [...pooled], bySku };
167
95
  }
168
- export function claimIn(text) {
169
- // Read twice, since Ν reads as N but ν as v.
170
- for (const byLook of [false, true]) {
171
- const normalised = normaliseForClaims(text, byLook);
172
- for (const claim of CLAIM_PATTERNS) {
173
- for (const pattern of claim.patterns) {
174
- if (pattern.test(normalised))
175
- return claim.kind;
176
- }
177
- }
96
+ function kindIn(reading) {
97
+ for (const claim of CLAIM_PATTERNS) {
98
+ if (claim.patterns.some((pattern) => pattern.test(reading)))
99
+ return claim.kind;
178
100
  }
179
101
  return null;
180
102
  }
103
+ function claimIn(text) {
104
+ return kindIn(foldLookalikes(text)) ?? kindIn(foldLookalikes(text, true));
105
+ }
106
+ export function unbackedClaim(raw, clamped, facts) {
107
+ const kind = claimIn(raw) ?? claimIn(clamped);
108
+ if (kind !== null)
109
+ return kind;
110
+ const values = NUMERAL.test(clamped) ? facts : NO_FACTS;
111
+ const result = verify(clamped, { values, allowedPhrases: ALLOWED_PHRASES });
112
+ if (!result.quantity.supported)
113
+ return 'quantity';
114
+ if (!result.wording.supported)
115
+ return 'wording';
116
+ return null;
117
+ }
181
118
  //# sourceMappingURL=claim-screening.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"claim-screening.js","sourceRoot":"","sources":["../src/claim-screening.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAGtD,MAAM,cAAc,GAA2C;IAC7D;QACE,IAAI,EAAE,QAAQ;QACd,QAAQ,EAAE;YACR,2BAA2B;YAC3B,mDAAmD;YACnD,0BAA0B;YAE1B,kEAAkE;YAElE,+BAA+B;YAE/B,0CAA0C;YAC1C,yDAAyD;YAEzD,yEAAyE;YAEzE,kCAAkC;YAElC,6DAA6D;YAC7D,+CAA+C;SAChD;KACF;IACD;QACE,IAAI,EAAE,OAAO;QACb,QAAQ,EAAE;YACR,SAAS;YACT,6DAA6D;YAE7D,yBAAyB;YAEzB,wBAAwB;YAExB,qBAAqB;YACrB,yBAAyB;YAEzB,6DAA6D;YAE7D,qBAAqB;YACrB,4BAA4B;YAE5B,2DAA2D;YAC3D,yBAAyB;YAEzB,kDAAkD;SACnD;KACF;IACD;QACE,IAAI,EAAE,UAAU;QAChB,QAAQ,EAAE;YACR,qEAAqE;YACrE,iGAAiG;YACjG,uBAAuB;YACvB,sCAAsC;YACtC,6BAA6B;YAE7B,8BAA8B;YAE9B,4DAA4D;YAE5D,8GAA8G;SAC/G;KACF;IACD;QACE,IAAI,EAAE,UAAU;QAChB,QAAQ,EAAE;YACR,2BAA2B;YAC3B,iDAAiD;YACjD,kMAAkM;YAClM,qDAAqD;YACrD,iBAAiB;SAClB;KACF;IACD;QACE,IAAI,EAAE,OAAO;QACb,QAAQ,EAAE;YACR,gCAAgC;YAChC,mBAAmB;YACnB,6BAA6B;YAE7B,sFAAsF;YACtF,yFAAyF;YACzF,mEAAmE;SACpE;KACF;CACF,CAAC;AAEF,MAAM,WAAW,GAA2B;IAC1C,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;CACP,CAAC;AAEF,wEAAwE;AACxE,MAAM,QAAQ,GAA2B;IACvC,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;CACP,CAAC;AAEF,MAAM,SAAS,GAAG,kDAAkD,CAAC;AAErE,MAAM,OAAO,GAAG,oBAAoB,CAAC;AAErC,MAAM,KAAK,GAAG,kBAAkB,CAAC;AAEjC,SAAS,kBAAkB,CAAC,IAAY,EAAE,MAAe;IACvD,kFAAkF;IAClF,MAAM,UAAU,GAAG,IAAI;SACpB,OAAO,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;SAC9D,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC;SACrB,SAAS,CAAC,MAAM,CAAC,CAAC;IAErB,IAAI,KAAK,GAAG,UAAU,CAAC;IACvB,IAAI,MAAM,EAAE,CAAC;QACX,KAAK,GAAG,EAAE,CAAC;QACX,KAAK,MAAM,IAAI,IAAI,UAAU;YAAE,KAAK,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;IACjE,CAAC;IACD,MAAM,OAAO,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAExE,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,KAAK,MAAM,IAAI,IAAI,OAAO;QAAE,MAAM,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;IAChE,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,qBAAqB;IACrB,oBAAoB;IACpB,gBAAgB;IAChB,kBAAkB;CACnB,CAAC;AAEF,MAAM,KAAK,GAAG,SAAS,CAAC;AAExB,MAAM,UAAU,YAAY,CAAC,OAAgB;IAC3C,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC/D,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;QAC/B,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACvC,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAOD,MAAM,UAAU,SAAS,CAAC,KAAoB;IAC5C,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;IACjC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAoB,CAAC;IAE1C,KAAK,MAAM,OAAO,IAAI,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/C,MAAM,GAAG,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;QAClC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAC5B,KAAK,MAAM,IAAI,IAAI,GAAG;YAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC3C,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,CAAC,GAAG,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC,6CAA6C;IAC7C,KAAK,MAAM,MAAM,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC;QACnC,MAAM,UAAU,GAAG,kBAAkB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACpD,KAAK,MAAM,KAAK,IAAI,cAAc,EAAE,CAAC;YACnC,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;gBACrC,IAAI,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC;oBAAE,OAAO,KAAK,CAAC,IAAI,CAAC;YAClD,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
1
+ {"version":3,"file":"claim-screening.js","sourceRoot":"","sources":["../src/claim-screening.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"component-generator.d.ts","sourceRoot":"","sources":["../src/component-generator.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,aAAa,EAClB,KAAK,cAAc,EAEnB,KAAK,UAAU,EAChB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEnE,OAAO,EAAkB,KAAK,SAAS,EAAoB,MAAM,wBAAwB,CAAC;AAG1F,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,qBAAqB,CAAC;AAE7B,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,MAAM,EAAE,UAAU,CAAC;IAEnB,SAAS,EAAE,MAAM,CAAC;IAElB,WAAW,EAAE,OAAO,CAAC;IAErB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;CAC9C;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAEpC,KAAK,CAAC,EAAE,SAAS,CAAC;IAElB,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,UAAU,CAAC,EAAE,QAAQ,GAAG,aAAa,CAAC;IAEtC,IAAI,CAAC,EAAE,SAAS,CAAC;IAEjB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;CAC5C;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IAE5D,qBAAqB,CAAC,KAAK,EAAE,kBAAkB,GAAG,aAAa,CAAC;CACjE;AAED,qBAAa,YAAa,SAAQ,KAAK;IACrC,YAAY,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAG9C;CACF;AAkGD,wBAAgB,wBAAwB,CACtC,OAAO,GAAE,yBAA8B,GACtC,kBAAkB,CA2LpB"}
1
+ {"version":3,"file":"component-generator.d.ts","sourceRoot":"","sources":["../src/component-generator.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,aAAa,EAClB,KAAK,cAAc,EAEnB,KAAK,UAAU,EAChB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEnE,OAAO,EAAkB,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAGxE,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,qBAAqB,CAAC;AAE7B,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,MAAM,EAAE,UAAU,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;CAC9C;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACpC,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,QAAQ,GAAG,aAAa,CAAC;IACtC,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;CAC5C;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IAC5D,qBAAqB,CAAC,KAAK,EAAE,kBAAkB,GAAG,aAAa,CAAC;CACjE;AAED,qBAAa,YAAa,SAAQ,KAAK;IACrC,YAAY,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAG9C;CACF;AAuGD,wBAAgB,wBAAwB,CACtC,OAAO,GAAE,yBAA8B,GACtC,kBAAkB,CAgBpB"}
@@ -6,7 +6,7 @@ import { bundleForShopper, capBlocks, placeableHeroSkus, reconcileSpec } from '.
6
6
  import { selectProducts } from './product-selection.js';
7
7
  import { fitToShopper } from './fit-to-shopper.js';
8
8
  import { buildDigest, toCohortDigest } from './signal-digest.js';
9
- import { createMemorySpecCache, cohortCacheKey, specCacheKey, } from './spec-cache.js';
9
+ import { createMemorySpecCache, specCacheKey, } from './spec-cache.js';
10
10
  import { parseTrackingInput, } from './tracking-input.js';
11
11
  export class TimeoutError extends Error {
12
12
  constructor(label, milliseconds) {
@@ -35,20 +35,6 @@ async function withinBudget(label, milliseconds, start) {
35
35
  clearTimeout(timer);
36
36
  }
37
37
  }
38
- function createSingleFlight() {
39
- const inFlight = new Map();
40
- return {
41
- isRunning: (key) => inFlight.has(key),
42
- run(key, task) {
43
- const existing = inFlight.get(key);
44
- if (existing)
45
- return existing;
46
- const started = task().finally(() => inFlight.delete(key));
47
- inFlight.set(key, started);
48
- return started;
49
- },
50
- };
51
- }
52
38
  const cachedSpecSchema = z.object({
53
39
  spec: generatedSpecSchema,
54
40
  generatedAt: z.number(),
@@ -56,183 +42,182 @@ const cachedSpecSchema = z.object({
56
42
  function fitCohortSpec(spec, input, digest, rank) {
57
43
  const picks = selectProducts(input, digest, { rank });
58
44
  const blocks = capBlocks(spec.blocks);
59
- let hasBundleBlock = false;
60
- const aboveBundle = [];
61
- for (const block of blocks) {
62
- if (block.kind === 'bundle') {
63
- hasBundleBlock = true;
64
- break;
65
- }
66
- aboveBundle.push(block);
67
- }
68
- if (!hasBundleBlock)
45
+ const bundleAt = blocks.findIndex((block) => block.kind === 'bundle');
46
+ if (bundleAt === -1)
69
47
  return fitToShopper(spec, picks, digest.maxItems);
70
- const chosen = bundleForShopper(input, digest, placeableHeroSkus(aboveBundle, input));
71
- if (!chosen)
48
+ const heroesAbove = placeableHeroSkus(blocks.slice(0, bundleAt), input);
49
+ const bundle = bundleForShopper(input, digest, heroesAbove);
50
+ if (!bundle)
72
51
  return fitToShopper(spec, picks, digest.maxItems);
73
- const spokenFor = new Set(chosen.skus);
52
+ const spokenFor = new Set(bundle.skus);
74
53
  for (const sku of placeableHeroSkus(blocks, input))
75
54
  spokenFor.add(sku);
76
55
  const roomLeft = digest.maxItems - spokenFor.size;
77
56
  if (roomLeft <= 0)
78
57
  return fitToShopper(spec, picks, digest.maxItems);
79
- const forGrid = [];
80
- for (const pick of picks) {
81
- if (!spokenFor.has(pick.product.sku))
82
- forGrid.push(pick);
83
- }
58
+ const forGrid = picks.filter((pick) => !spokenFor.has(pick.product.sku));
84
59
  return fitToShopper(spec, forGrid, roomLeft);
85
60
  }
86
61
  export function createComponentGenerator(options = {}) {
87
- const provider = options.provider ?? null;
88
- const cache = options.cache ?? createMemorySpecCache();
89
- const generation = options.generation ?? 'cohort';
90
- const rank = options.rank ?? 'signals';
91
- const modelTimeoutMs = options.modelTimeoutMs ?? 1_500;
92
- const cacheTimeoutMs = options.cacheTimeoutMs ?? 50;
93
- const singleFlight = createSingleFlight();
94
- const report = (event) => {
95
- if (!options.onEvent)
96
- return;
97
- try {
98
- options.onEvent(event);
99
- }
100
- catch { }
62
+ const settings = {
63
+ options,
64
+ provider: options.provider ?? null,
65
+ cache: options.cache ?? createMemorySpecCache(),
66
+ generation: options.generation ?? 'cohort',
67
+ rank: options.rank ?? 'signals',
68
+ modelTimeoutMs: options.modelTimeoutMs ?? 1_500,
69
+ cacheTimeoutMs: options.cacheTimeoutMs ?? 50,
70
+ inFlight: new Map(),
101
71
  };
102
- const buildDeterministic = (input, digest, startedAt, key, degradedReason, modelCall = {
103
- calledModel: false,
104
- }) => {
105
- const finishedAt = Date.now();
106
- report({
107
- key,
108
- source: 'fallback',
109
- elapsedMs: finishedAt - startedAt,
110
- ...modelCall,
111
- degradedReason,
112
- });
113
- return {
114
- ...buildFallbackSpec(input, digest, { rank }),
115
- specVersion: SPEC_VERSION,
116
- slot: digest.slot,
117
- source: 'fallback',
118
- generatedAt: finishedAt,
119
- latencyMs: finishedAt - startedAt,
120
- provider: null,
121
- model: null,
122
- degradedReason,
123
- };
72
+ return {
73
+ generate: (draft) => generate(settings, draft),
74
+ generateDeterministic: (draft) => generateDeterministic(settings, draft),
124
75
  };
125
- const readCache = async (key) => {
126
- try {
127
- const stored = await withinBudget('cache read', cacheTimeoutMs, () => cache.get(key));
128
- const parsed = cachedSpecSchema.safeParse(stored);
129
- return parsed.success ? { outcome: 'hit', entry: parsed.data } : { outcome: 'miss' };
130
- }
131
- catch (error) {
132
- return { outcome: error instanceof TimeoutError ? 'timeout' : 'error' };
133
- }
76
+ }
77
+ async function generate(settings, draft) {
78
+ const run = startRun(settings, draft);
79
+ const provider = settings.provider;
80
+ if (!provider)
81
+ return fallback(settings, run, null, 'no-provider');
82
+ const key = cacheKeyFor(provider, run);
83
+ const found = await findSpec(settings, provider, run, key);
84
+ if ('failed' in found)
85
+ return fallback(settings, run, key, found.failed, eventDetails(found.trace));
86
+ const reconciled = shapeForShopper(settings, run, found.spec);
87
+ const details = eventDetails(found.trace, reconciled.violations);
88
+ if (!reconciled.isUsable)
89
+ return fallback(settings, run, key, 'unusable-on-serve', details);
90
+ const finishedAt = Date.now();
91
+ report(settings, {
92
+ key,
93
+ source: found.source,
94
+ elapsedMs: finishedAt - run.startedAt,
95
+ ...details,
96
+ });
97
+ return {
98
+ ...reconciled.spec,
99
+ specVersion: SPEC_VERSION,
100
+ slot: run.digest.slot,
101
+ source: found.source,
102
+ generatedAt: found.generatedAt,
103
+ latencyMs: finishedAt - run.startedAt,
104
+ provider: provider.name,
105
+ model: provider.model,
134
106
  };
135
- const storeInBackground = (key, cached) => {
136
- void Promise.resolve()
137
- .then(() => cache.set(key, cached))
138
- .catch(() => { });
107
+ }
108
+ function generateDeterministic(settings, draft) {
109
+ return fallback(settings, startRun(settings, draft), null, 'requested');
110
+ }
111
+ function startRun(settings, draft) {
112
+ const startedAt = Date.now();
113
+ const input = parseTrackingInput(draft);
114
+ const digest = buildDigest(input);
115
+ const promptDigest = settings.generation === 'cohort' ? toCohortDigest(digest) : digest;
116
+ return { input, digest, promptDigest, startedAt };
117
+ }
118
+ function cacheKeyFor(provider, run) {
119
+ const identity = { name: provider.name, model: provider.model };
120
+ const skus = run.input.candidates.map((product) => product.sku);
121
+ return specCacheKey(run.promptDigest, skus, identity);
122
+ }
123
+ async function findSpec(settings, provider, run, key) {
124
+ const read = await readCache(settings, key);
125
+ if (read.entry) {
126
+ const trace = { calledModel: false, cache: read.outcome, usage: undefined };
127
+ return { spec: read.entry.spec, generatedAt: read.entry.generatedAt, source: 'cache', trace };
128
+ }
129
+ const calledModel = !settings.inFlight.has(key);
130
+ let call;
131
+ try {
132
+ call = await sharedCall(settings, key, () => askModel(settings, provider, run));
133
+ }
134
+ catch (error) {
135
+ const failed = error instanceof TimeoutError ? 'timeout' : 'provider-error';
136
+ return { failed, trace: { calledModel, cache: read.outcome, usage: undefined, error } };
137
+ }
138
+ const trace = { calledModel, cache: read.outcome, usage: call.usage };
139
+ if (!call.spec)
140
+ return { failed: 'invalid-generation', trace };
141
+ const generatedAt = Date.now();
142
+ if (calledModel)
143
+ storeInBackground(settings, key, { spec: call.spec, generatedAt });
144
+ return { spec: call.spec, generatedAt, source: 'llm', trace };
145
+ }
146
+ async function readCache(settings, key) {
147
+ try {
148
+ const stored = await withinBudget('cache read', settings.cacheTimeoutMs, () => settings.cache.get(key));
149
+ const parsed = cachedSpecSchema.safeParse(stored);
150
+ return parsed.success ? { outcome: 'hit', entry: parsed.data } : { outcome: 'miss' };
151
+ }
152
+ catch (error) {
153
+ return { outcome: error instanceof TimeoutError ? 'timeout' : 'error' };
154
+ }
155
+ }
156
+ async function askModel(settings, provider, run) {
157
+ const { system, user } = buildPrompt(run.input, run.promptDigest);
158
+ const result = await withinBudget('generation', settings.modelTimeoutMs, (signal) => provider.generate({ system, user, schema: generatedSpecSchema, signal }));
159
+ const parsed = generatedSpecSchema.safeParse(result.spec);
160
+ return {
161
+ spec: parsed.success ? parsed.data : null,
162
+ ...(result.usage ? { usage: result.usage } : {}),
139
163
  };
140
- const askModel = async (active, input, promptDigest) => {
141
- const { system, user } = buildPrompt(input, promptDigest);
142
- const result = await withinBudget('generation', modelTimeoutMs, (signal) => active.generate({ system, user, schema: generatedSpecSchema, signal }));
143
- const parsed = generatedSpecSchema.safeParse(result.spec);
144
- return {
145
- spec: parsed.success ? parsed.data : null,
146
- ...(result.usage ? { usage: result.usage } : {}),
147
- };
164
+ }
165
+ function sharedCall(settings, key, ask) {
166
+ const running = settings.inFlight.get(key);
167
+ if (running)
168
+ return running;
169
+ const started = ask().finally(() => settings.inFlight.delete(key));
170
+ settings.inFlight.set(key, started);
171
+ return started;
172
+ }
173
+ function storeInBackground(settings, key, cached) {
174
+ void Promise.resolve()
175
+ .then(() => settings.cache.set(key, cached))
176
+ .catch(() => { });
177
+ }
178
+ function shapeForShopper(settings, run, spec) {
179
+ if (settings.generation !== 'cohort') {
180
+ return reconcileSpec(spec, run.input, run.digest, new Map());
181
+ }
182
+ const fitted = fitCohortSpec(spec, run.input, run.digest, settings.rank);
183
+ return reconcileSpec(fitted.spec, run.input, run.digest, fitted.ourReasons);
184
+ }
185
+ function fallback(settings, run, key, degradedReason, details = { calledModel: false }) {
186
+ const finishedAt = Date.now();
187
+ report(settings, {
188
+ key,
189
+ source: 'fallback',
190
+ elapsedMs: finishedAt - run.startedAt,
191
+ ...details,
192
+ degradedReason,
193
+ });
194
+ return {
195
+ ...buildFallbackSpec(run.input, run.digest, { rank: settings.rank }),
196
+ specVersion: SPEC_VERSION,
197
+ slot: run.digest.slot,
198
+ source: 'fallback',
199
+ generatedAt: finishedAt,
200
+ latencyMs: finishedAt - run.startedAt,
201
+ provider: null,
202
+ model: null,
203
+ degradedReason,
148
204
  };
205
+ }
206
+ function eventDetails(trace, violations) {
149
207
  return {
150
- generateDeterministic(draft) {
151
- const startedAt = Date.now();
152
- const input = parseTrackingInput(draft);
153
- return buildDeterministic(input, buildDigest(input), startedAt, null, 'requested');
154
- },
155
- async generate(draft) {
156
- const startedAt = Date.now();
157
- const input = parseTrackingInput(draft);
158
- const digest = buildDigest(input);
159
- if (!provider) {
160
- return buildDeterministic(input, digest, startedAt, null, 'no-provider');
161
- }
162
- const identity = { name: provider.name, model: provider.model };
163
- const candidateSkus = input.candidates.map((product) => product.sku);
164
- const key = generation === 'cohort'
165
- ? cohortCacheKey(digest, candidateSkus, identity)
166
- : specCacheKey(digest, candidateSkus, identity);
167
- const read = await readCache(key);
168
- const cached = read.entry;
169
- let calledModel = false;
170
- let answer;
171
- let generatedAt;
172
- if (cached) {
173
- answer = { spec: cached.spec };
174
- generatedAt = cached.generatedAt;
175
- }
176
- else {
177
- calledModel = !singleFlight.isRunning(key);
178
- let call;
179
- try {
180
- call = await singleFlight.run(key, () => askModel(provider, input, generation === 'cohort' ? toCohortDigest(digest) : digest));
181
- }
182
- catch (error) {
183
- const reason = error instanceof TimeoutError ? 'timeout' : 'provider-error';
184
- return buildDeterministic(input, digest, startedAt, key, reason, {
185
- calledModel,
186
- cache: read.outcome,
187
- error,
188
- });
189
- }
190
- if (!call.spec) {
191
- return buildDeterministic(input, digest, startedAt, key, 'invalid-generation', {
192
- calledModel,
193
- cache: read.outcome,
194
- ...(call.usage ? { usage: call.usage } : {}),
195
- });
196
- }
197
- answer = { spec: call.spec, ...(call.usage ? { usage: call.usage } : {}) };
198
- generatedAt = Date.now();
199
- if (calledModel)
200
- storeInBackground(key, { spec: answer.spec, generatedAt });
201
- }
202
- const { spec: served, ourReasons } = generation === 'cohort'
203
- ? fitCohortSpec(answer.spec, input, digest, rank)
204
- : { spec: answer.spec, ourReasons: new Map() };
205
- const reconciled = reconcileSpec(served, input, digest, ourReasons);
206
- if (!reconciled.isUsable) {
207
- return buildDeterministic(input, digest, startedAt, key, 'unusable-on-serve', {
208
- calledModel,
209
- cache: read.outcome,
210
- violations: reconciled.violations,
211
- ...(answer.usage ? { usage: answer.usage } : {}),
212
- });
213
- }
214
- const finishedAt = Date.now();
215
- const source = cached ? 'cache' : 'llm';
216
- report({
217
- key,
218
- source,
219
- elapsedMs: finishedAt - startedAt,
220
- calledModel,
221
- cache: read.outcome,
222
- violations: reconciled.violations,
223
- ...(answer.usage ? { usage: answer.usage } : {}),
224
- });
225
- return {
226
- ...reconciled.spec,
227
- specVersion: SPEC_VERSION,
228
- slot: digest.slot,
229
- source,
230
- generatedAt,
231
- latencyMs: finishedAt - startedAt,
232
- provider: provider.name,
233
- model: provider.model,
234
- };
235
- },
208
+ calledModel: trace.calledModel,
209
+ cache: trace.cache,
210
+ ...(violations ? { violations } : {}),
211
+ ...(trace.usage ? { usage: trace.usage } : {}),
212
+ ...('error' in trace ? { error: trace.error } : {}),
236
213
  };
237
214
  }
215
+ function report(settings, event) {
216
+ if (!settings.options.onEvent)
217
+ return;
218
+ try {
219
+ settings.options.onEvent(event);
220
+ }
221
+ catch { }
222
+ }
238
223
  //# sourceMappingURL=component-generator.js.map