@traffical/core 0.1.2

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 (68) hide show
  1. package/dist/dedup/decision-dedup.d.ts +74 -0
  2. package/dist/dedup/decision-dedup.d.ts.map +1 -0
  3. package/dist/dedup/decision-dedup.js +132 -0
  4. package/dist/dedup/decision-dedup.js.map +1 -0
  5. package/dist/dedup/index.d.ts +5 -0
  6. package/dist/dedup/index.d.ts.map +1 -0
  7. package/dist/dedup/index.js +5 -0
  8. package/dist/dedup/index.js.map +1 -0
  9. package/dist/edge/client.d.ts +109 -0
  10. package/dist/edge/client.d.ts.map +1 -0
  11. package/dist/edge/client.js +154 -0
  12. package/dist/edge/client.js.map +1 -0
  13. package/dist/edge/index.d.ts +7 -0
  14. package/dist/edge/index.d.ts.map +1 -0
  15. package/dist/edge/index.js +7 -0
  16. package/dist/edge/index.js.map +1 -0
  17. package/dist/hashing/bucket.d.ts +56 -0
  18. package/dist/hashing/bucket.d.ts.map +1 -0
  19. package/dist/hashing/bucket.js +89 -0
  20. package/dist/hashing/bucket.js.map +1 -0
  21. package/dist/hashing/fnv1a.d.ts +17 -0
  22. package/dist/hashing/fnv1a.d.ts.map +1 -0
  23. package/dist/hashing/fnv1a.js +27 -0
  24. package/dist/hashing/fnv1a.js.map +1 -0
  25. package/dist/hashing/index.d.ts +8 -0
  26. package/dist/hashing/index.d.ts.map +1 -0
  27. package/dist/hashing/index.js +8 -0
  28. package/dist/hashing/index.js.map +1 -0
  29. package/dist/ids/index.d.ts +83 -0
  30. package/dist/ids/index.d.ts.map +1 -0
  31. package/dist/ids/index.js +165 -0
  32. package/dist/ids/index.js.map +1 -0
  33. package/dist/index.d.ts +20 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +32 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/resolution/conditions.d.ts +81 -0
  38. package/dist/resolution/conditions.d.ts.map +1 -0
  39. package/dist/resolution/conditions.js +197 -0
  40. package/dist/resolution/conditions.js.map +1 -0
  41. package/dist/resolution/engine.d.ts +54 -0
  42. package/dist/resolution/engine.d.ts.map +1 -0
  43. package/dist/resolution/engine.js +382 -0
  44. package/dist/resolution/engine.js.map +1 -0
  45. package/dist/resolution/index.d.ts +8 -0
  46. package/dist/resolution/index.d.ts.map +1 -0
  47. package/dist/resolution/index.js +10 -0
  48. package/dist/resolution/index.js.map +1 -0
  49. package/dist/types/index.d.ts +440 -0
  50. package/dist/types/index.d.ts.map +1 -0
  51. package/dist/types/index.js +8 -0
  52. package/dist/types/index.js.map +1 -0
  53. package/package.json +51 -0
  54. package/src/dedup/decision-dedup.ts +175 -0
  55. package/src/dedup/index.ts +6 -0
  56. package/src/edge/client.ts +256 -0
  57. package/src/edge/index.ts +16 -0
  58. package/src/hashing/bucket.ts +115 -0
  59. package/src/hashing/fnv1a.test.ts +87 -0
  60. package/src/hashing/fnv1a.ts +31 -0
  61. package/src/hashing/index.ts +15 -0
  62. package/src/ids/index.ts +221 -0
  63. package/src/index.ts +136 -0
  64. package/src/resolution/conditions.ts +253 -0
  65. package/src/resolution/engine.test.ts +242 -0
  66. package/src/resolution/engine.ts +480 -0
  67. package/src/resolution/index.ts +32 -0
  68. package/src/types/index.ts +508 -0
@@ -0,0 +1,242 @@
1
+ /**
2
+ * Resolution Engine Tests
3
+ *
4
+ * Validates parameter resolution using test vectors.
5
+ */
6
+
7
+ import { describe, test, expect } from "bun:test";
8
+ import { resolveParameters, decide } from "./engine.js";
9
+ import type { ConfigBundle } from "../types/index.js";
10
+
11
+ // Load test fixtures
12
+ import bundleBasic from "../../../test-vectors/fixtures/bundle_basic.json";
13
+ import bundleConditions from "../../../test-vectors/fixtures/bundle_conditions.json";
14
+
15
+ // Default values for basic bundle parameters
16
+ const basicDefaults = {
17
+ "ui.primaryColor": "#0000FF",
18
+ "ui.buttonText": "Click Me",
19
+ "pricing.discount": 0,
20
+ };
21
+
22
+ // Default values for conditions bundle parameters
23
+ const conditionsDefaults = {
24
+ "checkout.ctaText": "Complete Purchase",
25
+ "checkout.showUrgency": false,
26
+ };
27
+
28
+ describe("resolveParameters", () => {
29
+ const bundle = bundleBasic as unknown as ConfigBundle;
30
+
31
+ test("resolves user-abc correctly", () => {
32
+ // Bucket 551 for layer_ui (treatment: 500-999)
33
+ // Bucket 913 for layer_pricing (no allocation: outside 0-599)
34
+ const assignments = resolveParameters(bundle, { userId: "user-abc" }, basicDefaults);
35
+
36
+ expect(assignments["ui.primaryColor"]).toBe("#FF0000"); // treatment (bucket 551 >= 500)
37
+ expect(assignments["ui.buttonText"]).toBe("Click Me"); // default
38
+ expect(assignments["pricing.discount"]).toBe(0); // no allocation (bucket 913 >= 600)
39
+ });
40
+
41
+ test("resolves user-xyz correctly", () => {
42
+ // Bucket 214 for layer_ui (control: 0-499)
43
+ // Bucket 42 for layer_pricing (discount_10: 0-299)
44
+ const assignments = resolveParameters(bundle, { userId: "user-xyz" }, basicDefaults);
45
+
46
+ expect(assignments["ui.primaryColor"]).toBe("#0000FF"); // control (bucket 214 < 500)
47
+ expect(assignments["ui.buttonText"]).toBe("Click Me"); // default
48
+ expect(assignments["pricing.discount"]).toBe(10); // discount_10 (bucket 42 in 0-299)
49
+ });
50
+
51
+ test("resolves user-123 correctly", () => {
52
+ // Bucket 871 for layer_ui (treatment: 500-999)
53
+ // Bucket 177 for layer_pricing (discount_10: 0-299)
54
+ const assignments = resolveParameters(bundle, { userId: "user-123" }, basicDefaults);
55
+
56
+ expect(assignments["ui.primaryColor"]).toBe("#FF0000"); // treatment (bucket 871 >= 500)
57
+ expect(assignments["ui.buttonText"]).toBe("Click Me"); // default
58
+ expect(assignments["pricing.discount"]).toBe(10); // discount_10 (bucket 177 in 0-299)
59
+ });
60
+
61
+ test("returns defaults when unit key is missing", () => {
62
+ const assignments = resolveParameters(bundle, {}, basicDefaults);
63
+
64
+ // Should return caller defaults when unit key is missing
65
+ expect(assignments["ui.primaryColor"]).toBe("#0000FF");
66
+ expect(assignments["ui.buttonText"]).toBe("Click Me");
67
+ expect(assignments["pricing.discount"]).toBe(0);
68
+ });
69
+
70
+ test("returns defaults when bundle is null", () => {
71
+ const assignments = resolveParameters(null, { userId: "user-abc" }, basicDefaults);
72
+
73
+ expect(assignments["ui.primaryColor"]).toBe("#0000FF");
74
+ expect(assignments["ui.buttonText"]).toBe("Click Me");
75
+ expect(assignments["pricing.discount"]).toBe(0);
76
+ });
77
+
78
+ test("only resolves requested parameters from defaults", () => {
79
+ const partialDefaults = {
80
+ "ui.primaryColor": "#FFFFFF",
81
+ };
82
+ const assignments = resolveParameters(bundle, { userId: "user-abc" }, partialDefaults);
83
+
84
+ expect(Object.keys(assignments)).toEqual(["ui.primaryColor"]);
85
+ expect(assignments["ui.primaryColor"]).toBe("#FF0000");
86
+ });
87
+ });
88
+
89
+ describe("resolveParameters with conditions", () => {
90
+ const bundle = bundleConditions as unknown as ConfigBundle;
91
+
92
+ test("high value cart triggers urgency", () => {
93
+ const assignments = resolveParameters(
94
+ bundle,
95
+ {
96
+ userId: "user-high-value",
97
+ cartValue: 150,
98
+ deviceType: "desktop",
99
+ },
100
+ conditionsDefaults
101
+ );
102
+
103
+ expect(assignments["checkout.ctaText"]).toBe("Buy Now - Limited Stock!");
104
+ expect(assignments["checkout.showUrgency"]).toBe(true);
105
+ });
106
+
107
+ test("mobile user with low cart gets mobile CTA", () => {
108
+ const assignments = resolveParameters(
109
+ bundle,
110
+ {
111
+ userId: "user-mobile",
112
+ cartValue: 50,
113
+ deviceType: "mobile",
114
+ },
115
+ conditionsDefaults
116
+ );
117
+
118
+ expect(assignments["checkout.ctaText"]).toBe("Buy Now");
119
+ expect(assignments["checkout.showUrgency"]).toBe(false);
120
+ });
121
+
122
+ test("desktop user with low cart gets defaults", () => {
123
+ const assignments = resolveParameters(
124
+ bundle,
125
+ {
126
+ userId: "user-desktop",
127
+ cartValue: 50,
128
+ deviceType: "desktop",
129
+ },
130
+ conditionsDefaults
131
+ );
132
+
133
+ expect(assignments["checkout.ctaText"]).toBe("Complete Purchase");
134
+ expect(assignments["checkout.showUrgency"]).toBe(false);
135
+ });
136
+
137
+ test("high value cart takes precedence over mobile", () => {
138
+ const assignments = resolveParameters(
139
+ bundle,
140
+ {
141
+ userId: "user-mobile-high",
142
+ cartValue: 200,
143
+ deviceType: "mobile",
144
+ },
145
+ conditionsDefaults
146
+ );
147
+
148
+ expect(assignments["checkout.ctaText"]).toBe("Buy Now - Limited Stock!");
149
+ expect(assignments["checkout.showUrgency"]).toBe(true);
150
+ });
151
+ });
152
+
153
+ describe("resolveParameters graceful degradation", () => {
154
+ const bundle = bundleBasic as unknown as ConfigBundle;
155
+
156
+ test("returns defaults when bundle is null", () => {
157
+ const defaults = {
158
+ "ui.primaryColor": "#FFFFFF",
159
+ "ui.fontSize": 16,
160
+ };
161
+
162
+ const result = resolveParameters(null, { userId: "user-abc" }, defaults);
163
+
164
+ expect(result["ui.primaryColor"]).toBe("#FFFFFF");
165
+ expect(result["ui.fontSize"]).toBe(16);
166
+ });
167
+
168
+ test("merges bundle values with caller defaults", () => {
169
+ const defaults = {
170
+ "ui.primaryColor": "#FFFFFF",
171
+ "ui.fontSize": 16,
172
+ };
173
+
174
+ const result = resolveParameters(bundle, { userId: "user-abc" }, defaults);
175
+
176
+ // From bundle (overridden by policy) - bucket 551 = treatment
177
+ expect(result["ui.primaryColor"]).toBe("#FF0000");
178
+ // From caller defaults (not in bundle)
179
+ expect(result["ui.fontSize"]).toBe(16);
180
+ });
181
+
182
+ test("returns caller defaults when unit key is missing", () => {
183
+ const defaults = {
184
+ "ui.primaryColor": "#FFFFFF",
185
+ };
186
+
187
+ const result = resolveParameters(bundle, {}, defaults);
188
+
189
+ expect(result["ui.primaryColor"]).toBe("#FFFFFF");
190
+ });
191
+ });
192
+
193
+ describe("decide", () => {
194
+ const bundle = bundleBasic as unknown as ConfigBundle;
195
+
196
+ test("returns decision with metadata", () => {
197
+ const decision = decide(bundle, { userId: "user-abc" }, basicDefaults);
198
+
199
+ expect(decision.decisionId).toMatch(/^dec_/);
200
+ expect(decision.assignments["ui.primaryColor"]).toBe("#FF0000");
201
+ expect(decision.metadata.unitKeyValue).toBe("user-abc");
202
+ expect(decision.metadata.layers).toHaveLength(2);
203
+ });
204
+
205
+ test("includes correct layer resolution info", () => {
206
+ const decision = decide(bundle, { userId: "user-abc" }, basicDefaults);
207
+
208
+ const uiLayer = decision.metadata.layers.find(
209
+ (l) => l.layerId === "layer_ui"
210
+ );
211
+ expect(uiLayer).toBeDefined();
212
+ expect(uiLayer!.bucket).toBe(551);
213
+ expect(uiLayer!.policyId).toBe("policy_color_test");
214
+ expect(uiLayer!.allocationName).toBe("treatment");
215
+
216
+ const pricingLayer = decision.metadata.layers.find(
217
+ (l) => l.layerId === "layer_pricing"
218
+ );
219
+ expect(pricingLayer).toBeDefined();
220
+ expect(pricingLayer!.bucket).toBe(913);
221
+ // No allocation matched (bucket 913 is outside all ranges 0-599)
222
+ expect(pricingLayer!.policyId).toBeUndefined();
223
+ });
224
+
225
+ test("returns defaults with empty metadata when bundle is null", () => {
226
+ const decision = decide(null, { userId: "user-abc" }, basicDefaults);
227
+
228
+ expect(decision.decisionId).toMatch(/^dec_/);
229
+ expect(decision.assignments["ui.primaryColor"]).toBe("#0000FF");
230
+ expect(decision.metadata.unitKeyValue).toBe("");
231
+ expect(decision.metadata.layers).toHaveLength(0);
232
+ });
233
+
234
+ test("returns defaults with empty metadata when unit key is missing", () => {
235
+ const decision = decide(bundle, {}, basicDefaults);
236
+
237
+ expect(decision.decisionId).toMatch(/^dec_/);
238
+ expect(decision.assignments["ui.primaryColor"]).toBe("#0000FF");
239
+ expect(decision.metadata.unitKeyValue).toBe("");
240
+ expect(decision.metadata.layers).toHaveLength(0);
241
+ });
242
+ });