@startsimpli/funnels 0.1.4 → 0.1.5

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 (151) hide show
  1. package/package.json +9 -31
  2. package/src/api/README.md +507 -0
  3. package/src/api/adapter.ts +106 -0
  4. package/src/api/client.test.ts +640 -0
  5. package/src/api/client.ts +385 -0
  6. package/src/api/default-adapter.ts +243 -0
  7. package/src/api/index.ts +24 -0
  8. package/src/components/FilterRuleEditor/ARCHITECTURE.md +354 -0
  9. package/src/components/FilterRuleEditor/FieldSelector.tsx +91 -0
  10. package/src/components/FilterRuleEditor/FilterRuleEditor.stories.tsx +462 -0
  11. package/src/components/FilterRuleEditor/FilterRuleEditor.test.tsx +520 -0
  12. package/src/components/FilterRuleEditor/FilterRuleEditor.tsx +225 -0
  13. package/src/components/FilterRuleEditor/LogicToggle.tsx +64 -0
  14. package/src/components/FilterRuleEditor/OperatorSelector.tsx +75 -0
  15. package/src/components/FilterRuleEditor/README.md +291 -0
  16. package/src/components/FilterRuleEditor/RuleRow.tsx +246 -0
  17. package/src/components/FilterRuleEditor/ValueInputs/BooleanValueInput.tsx +54 -0
  18. package/src/components/FilterRuleEditor/ValueInputs/ChoiceValueInput.tsx +83 -0
  19. package/src/components/FilterRuleEditor/ValueInputs/DateValueInput.tsx +70 -0
  20. package/src/components/FilterRuleEditor/ValueInputs/MultiChoiceValueInput.tsx +132 -0
  21. package/src/components/FilterRuleEditor/ValueInputs/NumberValueInput.tsx +73 -0
  22. package/src/components/FilterRuleEditor/ValueInputs/TextValueInput.tsx +50 -0
  23. package/src/components/FilterRuleEditor/ValueInputs/index.ts +12 -0
  24. package/src/components/FilterRuleEditor/constants.ts +64 -0
  25. package/src/components/FilterRuleEditor/index.ts +14 -0
  26. package/src/components/FunnelCard/DESIGN.md +447 -0
  27. package/src/components/FunnelCard/FunnelCard.stories.tsx +484 -0
  28. package/src/components/FunnelCard/FunnelCard.test.ts +257 -0
  29. package/src/components/FunnelCard/FunnelCard.test.tsx +336 -0
  30. package/src/components/FunnelCard/FunnelCard.tsx +204 -0
  31. package/src/components/FunnelCard/FunnelStats.tsx +68 -0
  32. package/src/components/FunnelCard/IMPLEMENTATION_SUMMARY.md +505 -0
  33. package/src/components/FunnelCard/INSTALLATION.md +304 -0
  34. package/src/components/FunnelCard/MatchBar.tsx +49 -0
  35. package/src/components/FunnelCard/README.md +294 -0
  36. package/src/components/FunnelCard/StageIndicator.tsx +62 -0
  37. package/src/components/FunnelCard/StatusBadge.tsx +52 -0
  38. package/src/components/FunnelCard/index.ts +14 -0
  39. package/src/components/FunnelPreview/EntityCard.tsx +72 -0
  40. package/src/components/FunnelPreview/FunnelPreview.stories.tsx +227 -0
  41. package/src/components/FunnelPreview/FunnelPreview.test.tsx +316 -0
  42. package/src/components/FunnelPreview/FunnelPreview.tsx +249 -0
  43. package/src/components/FunnelPreview/LoadingPreview.tsx +60 -0
  44. package/src/components/FunnelPreview/PreviewStats.tsx +78 -0
  45. package/src/components/FunnelPreview/README.md +337 -0
  46. package/src/components/FunnelPreview/StageBreakdown.tsx +94 -0
  47. package/src/components/FunnelPreview/example.tsx +286 -0
  48. package/src/components/FunnelPreview/index.ts +14 -0
  49. package/src/components/FunnelRunHistory/COMPONENT_SUMMARY.md +246 -0
  50. package/src/components/FunnelRunHistory/FunnelRunHistory.stories.tsx +272 -0
  51. package/src/components/FunnelRunHistory/FunnelRunHistory.test.tsx +323 -0
  52. package/src/components/FunnelRunHistory/FunnelRunHistory.tsx +329 -0
  53. package/src/components/FunnelRunHistory/README.md +325 -0
  54. package/src/components/FunnelRunHistory/RunActions.tsx +168 -0
  55. package/src/components/FunnelRunHistory/RunDetailsModal.tsx +221 -0
  56. package/src/components/FunnelRunHistory/RunFilters.tsx +128 -0
  57. package/src/components/FunnelRunHistory/RunRow.tsx +122 -0
  58. package/src/components/FunnelRunHistory/RunStatusBadge.tsx +75 -0
  59. package/src/components/FunnelRunHistory/StageBreakdownList.tsx +110 -0
  60. package/src/components/FunnelRunHistory/index.ts +51 -0
  61. package/src/components/FunnelRunHistory/types.ts +40 -0
  62. package/src/components/FunnelRunHistory/utils.test.ts +126 -0
  63. package/src/components/FunnelRunHistory/utils.ts +100 -0
  64. package/src/components/FunnelStageBuilder/AddStageButton.tsx +52 -0
  65. package/src/components/FunnelStageBuilder/FunnelStageBuilder.css +413 -0
  66. package/src/components/FunnelStageBuilder/FunnelStageBuilder.stories.tsx +312 -0
  67. package/src/components/FunnelStageBuilder/FunnelStageBuilder.test.tsx +304 -0
  68. package/src/components/FunnelStageBuilder/FunnelStageBuilder.tsx +321 -0
  69. package/src/components/FunnelStageBuilder/README.md +341 -0
  70. package/src/components/FunnelStageBuilder/StageActions.test.tsx +205 -0
  71. package/src/components/FunnelStageBuilder/StageActions.tsx +126 -0
  72. package/src/components/FunnelStageBuilder/StageCard.tsx +202 -0
  73. package/src/components/FunnelStageBuilder/StageForm.tsx +262 -0
  74. package/src/components/FunnelStageBuilder/TagInput.test.tsx +178 -0
  75. package/src/components/FunnelStageBuilder/TagInput.tsx +129 -0
  76. package/src/components/FunnelStageBuilder/index.ts +21 -0
  77. package/src/components/FunnelVisualFlow/FlowLegend.tsx +77 -0
  78. package/{dist/components/index.css → src/components/FunnelVisualFlow/FunnelVisualFlow.css} +89 -13
  79. package/src/components/FunnelVisualFlow/FunnelVisualFlow.stories.tsx +254 -0
  80. package/src/components/FunnelVisualFlow/FunnelVisualFlow.test.tsx +208 -0
  81. package/src/components/FunnelVisualFlow/FunnelVisualFlow.tsx +229 -0
  82. package/src/components/FunnelVisualFlow/README.md +323 -0
  83. package/src/components/FunnelVisualFlow/StageNode.tsx +188 -0
  84. package/src/components/FunnelVisualFlow/example.tsx +227 -0
  85. package/src/components/FunnelVisualFlow/index.ts +10 -0
  86. package/src/components/index.ts +102 -0
  87. package/src/core/README.md +307 -0
  88. package/src/core/engine.test.ts +1087 -0
  89. package/src/core/engine.ts +329 -0
  90. package/src/core/evaluator.example.ts +353 -0
  91. package/src/core/evaluator.test.ts +639 -0
  92. package/src/core/evaluator.ts +261 -0
  93. package/src/core/field-resolver.example.ts +175 -0
  94. package/src/core/field-resolver.test.ts +541 -0
  95. package/src/core/field-resolver.ts +247 -0
  96. package/src/core/index.ts +34 -0
  97. package/src/core/operators.test.ts +539 -0
  98. package/src/core/operators.ts +241 -0
  99. package/src/hooks/index.ts +5 -0
  100. package/src/hooks/useDebouncedValue.ts +28 -0
  101. package/src/index.ts +155 -0
  102. package/src/store/README.md +342 -0
  103. package/src/store/create-funnel-store.test.ts +686 -0
  104. package/src/store/create-funnel-store.ts +538 -0
  105. package/src/store/index.ts +9 -0
  106. package/src/store/types.ts +294 -0
  107. package/src/stories/CrossDomain.stories.tsx +149 -0
  108. package/src/stories/Welcome.stories.tsx +81 -0
  109. package/src/stories/demo-data/index.ts +3 -0
  110. package/src/stories/demo-data/investors.ts +216 -0
  111. package/src/stories/demo-data/leads.ts +223 -0
  112. package/src/stories/demo-data/recipes.ts +217 -0
  113. package/src/test/setup.ts +5 -0
  114. package/src/types/index.ts +843 -0
  115. package/dist/client-3ESO2NHy.d.ts +0 -310
  116. package/dist/client-CZu03ACp.d.cts +0 -310
  117. package/dist/components/index.cjs +0 -3241
  118. package/dist/components/index.cjs.map +0 -1
  119. package/dist/components/index.css.map +0 -1
  120. package/dist/components/index.d.cts +0 -726
  121. package/dist/components/index.d.ts +0 -726
  122. package/dist/components/index.js +0 -3194
  123. package/dist/components/index.js.map +0 -1
  124. package/dist/core/index.cjs +0 -500
  125. package/dist/core/index.cjs.map +0 -1
  126. package/dist/core/index.d.cts +0 -359
  127. package/dist/core/index.d.ts +0 -359
  128. package/dist/core/index.js +0 -486
  129. package/dist/core/index.js.map +0 -1
  130. package/dist/hooks/index.cjs +0 -20
  131. package/dist/hooks/index.cjs.map +0 -1
  132. package/dist/hooks/index.d.cts +0 -11
  133. package/dist/hooks/index.d.ts +0 -11
  134. package/dist/hooks/index.js +0 -18
  135. package/dist/hooks/index.js.map +0 -1
  136. package/dist/index-BGDEXbuz.d.cts +0 -434
  137. package/dist/index-BGDEXbuz.d.ts +0 -434
  138. package/dist/index.cjs +0 -4499
  139. package/dist/index.cjs.map +0 -1
  140. package/dist/index.css +0 -198
  141. package/dist/index.css.map +0 -1
  142. package/dist/index.d.cts +0 -99
  143. package/dist/index.d.ts +0 -99
  144. package/dist/index.js +0 -4421
  145. package/dist/index.js.map +0 -1
  146. package/dist/store/index.cjs +0 -389
  147. package/dist/store/index.cjs.map +0 -1
  148. package/dist/store/index.d.cts +0 -225
  149. package/dist/store/index.d.ts +0 -225
  150. package/dist/store/index.js +0 -386
  151. package/dist/store/index.js.map +0 -1
@@ -0,0 +1,247 @@
1
+ /**
2
+ * @startsimpli/funnels - Field Resolver Utility
3
+ *
4
+ * Safely resolves and sets field values using dot-notation paths on arbitrary objects.
5
+ * Used by rule evaluator and execution engine to access entity properties.
6
+ *
7
+ * Examples:
8
+ * - resolveField({name: 'John'}, 'name') → 'John'
9
+ * - resolveField({firm: {stage: 'Seed'}}, 'firm.stage') → 'Seed'
10
+ * - resolveField({tags: ['a', 'b']}, 'tags[0]') → 'a'
11
+ * - resolveField({investors: [{email: 'a@b.com'}]}, 'investors[0].email') → 'a@b.com'
12
+ */
13
+
14
+ /** Segments that would allow prototype pollution. */
15
+ const DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
16
+
17
+ /**
18
+ * Parse a field path into segments
19
+ *
20
+ * Examples:
21
+ * - 'name' → ['name']
22
+ * - 'firm.stage' → ['firm', 'stage']
23
+ * - 'tags[0]' → ['tags', '0']
24
+ * - 'investors[2].email' → ['investors', '2', 'email']
25
+ * - 'company.address.city.zipCode' → ['company', 'address', 'city', 'zipCode']
26
+ */
27
+ function parseFieldPath(fieldPath: string): string[] {
28
+ const segments: string[] = [];
29
+ let current = '';
30
+ let inBracket = false;
31
+
32
+ for (let i = 0; i < fieldPath.length; i++) {
33
+ const char = fieldPath[i];
34
+
35
+ if (char === '[') {
36
+ if (current) {
37
+ segments.push(current);
38
+ current = '';
39
+ }
40
+ inBracket = true;
41
+ } else if (char === ']') {
42
+ if (current) {
43
+ segments.push(current);
44
+ current = '';
45
+ }
46
+ inBracket = false;
47
+ } else if (char === '.' && !inBracket) {
48
+ if (current) {
49
+ segments.push(current);
50
+ current = '';
51
+ }
52
+ } else {
53
+ current += char;
54
+ }
55
+ }
56
+
57
+ if (current) {
58
+ segments.push(current);
59
+ }
60
+
61
+ return segments;
62
+ }
63
+
64
+ /**
65
+ * Resolve a field value from an entity using dot-notation path
66
+ *
67
+ * Features:
68
+ * - Dot notation: 'firm.name', 'recipe.ingredients.name'
69
+ * - Array access: 'tags[0]', 'investors[2].email'
70
+ * - Nested paths: 'company.address.city.zipCode'
71
+ * - Safe navigation: Returns undefined for missing paths (no errors)
72
+ * - Type preservation: Maintains number/boolean/date types
73
+ *
74
+ * @param entity - The object to resolve the field from
75
+ * @param fieldPath - Dot-notation path to the field
76
+ * @returns The resolved value, or undefined if path doesn't exist
77
+ *
78
+ * @example
79
+ * ```typescript
80
+ * const investor = {
81
+ * name: 'John Doe',
82
+ * firm: { stage: 'Seed', aum: 100000000 },
83
+ * tags: ['active', 'qualified'],
84
+ * metadata: { score: 85 }
85
+ * };
86
+ *
87
+ * resolveField(investor, 'name'); // 'John Doe'
88
+ * resolveField(investor, 'firm.stage'); // 'Seed'
89
+ * resolveField(investor, 'tags[0]'); // 'active'
90
+ * resolveField(investor, 'metadata.score'); // 85
91
+ * resolveField(investor, 'firm.missing'); // undefined
92
+ * ```
93
+ */
94
+ export function resolveField<T>(entity: T, fieldPath: string): any {
95
+ // Handle null/undefined entity
96
+ if (entity == null) {
97
+ return undefined;
98
+ }
99
+
100
+ // Handle empty path
101
+ if (!fieldPath || fieldPath.trim() === '') {
102
+ return undefined;
103
+ }
104
+
105
+ const segments = parseFieldPath(fieldPath);
106
+ let current: any = entity;
107
+
108
+ for (const segment of segments) {
109
+ // Check if current is null/undefined before accessing
110
+ if (current == null) {
111
+ return undefined;
112
+ }
113
+
114
+ // Block prototype pollution
115
+ if (DANGEROUS_KEYS.has(segment)) {
116
+ return undefined;
117
+ }
118
+
119
+ // Handle array/object access
120
+ if (typeof current === 'object' && segment in current) {
121
+ current = current[segment];
122
+ } else {
123
+ return undefined;
124
+ }
125
+ }
126
+
127
+ return current;
128
+ }
129
+
130
+ /**
131
+ * Set a field value on an entity using dot-notation path
132
+ *
133
+ * Features:
134
+ * - Creates intermediate objects/arrays as needed
135
+ * - Handles array index assignment
136
+ * - Mutates the entity in place
137
+ * - Safe for missing intermediate paths
138
+ *
139
+ * @param entity - The object to set the field on
140
+ * @param fieldPath - Dot-notation path to the field
141
+ * @param value - The value to set
142
+ *
143
+ * @example
144
+ * ```typescript
145
+ * const investor = { name: 'John' };
146
+ *
147
+ * setField(investor, 'firm.stage', 'Series A');
148
+ * // investor = { name: 'John', firm: { stage: 'Series A' } }
149
+ *
150
+ * setField(investor, 'tags[0]', 'qualified');
151
+ * // investor.tags = ['qualified']
152
+ *
153
+ * setField(investor, 'metadata.score', 85);
154
+ * // investor.metadata = { score: 85 }
155
+ * ```
156
+ */
157
+ export function setField<T>(entity: T, fieldPath: string, value: any): void {
158
+ // Handle null/undefined entity
159
+ if (entity == null) {
160
+ throw new Error('Cannot set field on null or undefined entity');
161
+ }
162
+
163
+ // Handle empty path
164
+ if (!fieldPath || fieldPath.trim() === '') {
165
+ throw new Error('Field path cannot be empty');
166
+ }
167
+
168
+ const segments = parseFieldPath(fieldPath);
169
+ let current: any = entity;
170
+
171
+ // Navigate to parent of target field
172
+ for (let i = 0; i < segments.length - 1; i++) {
173
+ const segment = segments[i];
174
+ const nextSegment = segments[i + 1];
175
+
176
+ // Block prototype pollution
177
+ if (DANGEROUS_KEYS.has(segment)) {
178
+ throw new Error(`Dangerous field path segment: "${segment}"`);
179
+ }
180
+
181
+ // Create intermediate object or array if missing
182
+ if (!(segment in current)) {
183
+ // Check if next segment is numeric (array index)
184
+ const isNextArray = /^\d+$/.test(nextSegment);
185
+ current[segment] = isNextArray ? [] : {};
186
+ }
187
+
188
+ current = current[segment];
189
+ }
190
+
191
+ // Set the final value
192
+ const lastSegment = segments[segments.length - 1];
193
+ if (DANGEROUS_KEYS.has(lastSegment)) {
194
+ throw new Error(`Dangerous field path segment: "${lastSegment}"`);
195
+ }
196
+ current[lastSegment] = value;
197
+ }
198
+
199
+ /**
200
+ * Check if a field path exists on an entity
201
+ *
202
+ * @param entity - The object to check
203
+ * @param fieldPath - Dot-notation path to check
204
+ * @returns true if the path exists and has a defined value
205
+ *
206
+ * @example
207
+ * ```typescript
208
+ * const investor = { name: 'John', firm: { stage: 'Seed' } };
209
+ *
210
+ * hasField(investor, 'name'); // true
211
+ * hasField(investor, 'firm.stage'); // true
212
+ * hasField(investor, 'firm.missing'); // false
213
+ * hasField(investor, 'tags[0]'); // false
214
+ * ```
215
+ */
216
+ export function hasField<T>(entity: T, fieldPath: string): boolean {
217
+ const value = resolveField(entity, fieldPath);
218
+ return value !== undefined;
219
+ }
220
+
221
+ /**
222
+ * Get multiple field values at once
223
+ *
224
+ * @param entity - The object to resolve fields from
225
+ * @param fieldPaths - Array of field paths to resolve
226
+ * @returns Object mapping field paths to their values
227
+ *
228
+ * @example
229
+ * ```typescript
230
+ * const investor = { name: 'John', firm: { stage: 'Seed' }, tags: ['active'] };
231
+ *
232
+ * getFields(investor, ['name', 'firm.stage', 'tags[0]']);
233
+ * // { name: 'John', 'firm.stage': 'Seed', 'tags[0]': 'active' }
234
+ * ```
235
+ */
236
+ export function getFields<T>(
237
+ entity: T,
238
+ fieldPaths: string[]
239
+ ): Record<string, any> {
240
+ const result: Record<string, any> = {};
241
+
242
+ for (const path of fieldPaths) {
243
+ result[path] = resolveField(entity, path);
244
+ }
245
+
246
+ return result;
247
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @startsimpli/funnels/core
3
+ *
4
+ * Core funnel engine - NO React dependencies
5
+ * Use this in server-side workers, CLI tools, or anywhere without React.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+
10
+ // Export field resolver
11
+ export {
12
+ resolveField,
13
+ setField,
14
+ hasField,
15
+ getFields,
16
+ } from './field-resolver';
17
+
18
+ // Export rule evaluator
19
+ export {
20
+ evaluateRule,
21
+ evaluateRuleWithResult,
22
+ evaluateRules,
23
+ evaluateRulesAND,
24
+ evaluateRulesOR,
25
+ evaluateRulesWithResults,
26
+ filterEntities,
27
+ } from './evaluator';
28
+
29
+ // Export operators
30
+ export { applyOperator } from './operators';
31
+
32
+ // Export funnel engine
33
+ export { FunnelEngine } from './engine';
34
+ export type { ExecutionResult } from './engine';