@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.
- package/package.json +9 -31
- package/src/api/README.md +507 -0
- package/src/api/adapter.ts +106 -0
- package/src/api/client.test.ts +640 -0
- package/src/api/client.ts +385 -0
- package/src/api/default-adapter.ts +243 -0
- package/src/api/index.ts +24 -0
- package/src/components/FilterRuleEditor/ARCHITECTURE.md +354 -0
- package/src/components/FilterRuleEditor/FieldSelector.tsx +91 -0
- package/src/components/FilterRuleEditor/FilterRuleEditor.stories.tsx +462 -0
- package/src/components/FilterRuleEditor/FilterRuleEditor.test.tsx +520 -0
- package/src/components/FilterRuleEditor/FilterRuleEditor.tsx +225 -0
- package/src/components/FilterRuleEditor/LogicToggle.tsx +64 -0
- package/src/components/FilterRuleEditor/OperatorSelector.tsx +75 -0
- package/src/components/FilterRuleEditor/README.md +291 -0
- package/src/components/FilterRuleEditor/RuleRow.tsx +246 -0
- package/src/components/FilterRuleEditor/ValueInputs/BooleanValueInput.tsx +54 -0
- package/src/components/FilterRuleEditor/ValueInputs/ChoiceValueInput.tsx +83 -0
- package/src/components/FilterRuleEditor/ValueInputs/DateValueInput.tsx +70 -0
- package/src/components/FilterRuleEditor/ValueInputs/MultiChoiceValueInput.tsx +132 -0
- package/src/components/FilterRuleEditor/ValueInputs/NumberValueInput.tsx +73 -0
- package/src/components/FilterRuleEditor/ValueInputs/TextValueInput.tsx +50 -0
- package/src/components/FilterRuleEditor/ValueInputs/index.ts +12 -0
- package/src/components/FilterRuleEditor/constants.ts +64 -0
- package/src/components/FilterRuleEditor/index.ts +14 -0
- package/src/components/FunnelCard/DESIGN.md +447 -0
- package/src/components/FunnelCard/FunnelCard.stories.tsx +484 -0
- package/src/components/FunnelCard/FunnelCard.test.ts +257 -0
- package/src/components/FunnelCard/FunnelCard.test.tsx +336 -0
- package/src/components/FunnelCard/FunnelCard.tsx +204 -0
- package/src/components/FunnelCard/FunnelStats.tsx +68 -0
- package/src/components/FunnelCard/IMPLEMENTATION_SUMMARY.md +505 -0
- package/src/components/FunnelCard/INSTALLATION.md +304 -0
- package/src/components/FunnelCard/MatchBar.tsx +49 -0
- package/src/components/FunnelCard/README.md +294 -0
- package/src/components/FunnelCard/StageIndicator.tsx +62 -0
- package/src/components/FunnelCard/StatusBadge.tsx +52 -0
- package/src/components/FunnelCard/index.ts +14 -0
- package/src/components/FunnelPreview/EntityCard.tsx +72 -0
- package/src/components/FunnelPreview/FunnelPreview.stories.tsx +227 -0
- package/src/components/FunnelPreview/FunnelPreview.test.tsx +316 -0
- package/src/components/FunnelPreview/FunnelPreview.tsx +249 -0
- package/src/components/FunnelPreview/LoadingPreview.tsx +60 -0
- package/src/components/FunnelPreview/PreviewStats.tsx +78 -0
- package/src/components/FunnelPreview/README.md +337 -0
- package/src/components/FunnelPreview/StageBreakdown.tsx +94 -0
- package/src/components/FunnelPreview/example.tsx +286 -0
- package/src/components/FunnelPreview/index.ts +14 -0
- package/src/components/FunnelRunHistory/COMPONENT_SUMMARY.md +246 -0
- package/src/components/FunnelRunHistory/FunnelRunHistory.stories.tsx +272 -0
- package/src/components/FunnelRunHistory/FunnelRunHistory.test.tsx +323 -0
- package/src/components/FunnelRunHistory/FunnelRunHistory.tsx +329 -0
- package/src/components/FunnelRunHistory/README.md +325 -0
- package/src/components/FunnelRunHistory/RunActions.tsx +168 -0
- package/src/components/FunnelRunHistory/RunDetailsModal.tsx +221 -0
- package/src/components/FunnelRunHistory/RunFilters.tsx +128 -0
- package/src/components/FunnelRunHistory/RunRow.tsx +122 -0
- package/src/components/FunnelRunHistory/RunStatusBadge.tsx +75 -0
- package/src/components/FunnelRunHistory/StageBreakdownList.tsx +110 -0
- package/src/components/FunnelRunHistory/index.ts +51 -0
- package/src/components/FunnelRunHistory/types.ts +40 -0
- package/src/components/FunnelRunHistory/utils.test.ts +126 -0
- package/src/components/FunnelRunHistory/utils.ts +100 -0
- package/src/components/FunnelStageBuilder/AddStageButton.tsx +52 -0
- package/src/components/FunnelStageBuilder/FunnelStageBuilder.css +413 -0
- package/src/components/FunnelStageBuilder/FunnelStageBuilder.stories.tsx +312 -0
- package/src/components/FunnelStageBuilder/FunnelStageBuilder.test.tsx +304 -0
- package/src/components/FunnelStageBuilder/FunnelStageBuilder.tsx +321 -0
- package/src/components/FunnelStageBuilder/README.md +341 -0
- package/src/components/FunnelStageBuilder/StageActions.test.tsx +205 -0
- package/src/components/FunnelStageBuilder/StageActions.tsx +126 -0
- package/src/components/FunnelStageBuilder/StageCard.tsx +202 -0
- package/src/components/FunnelStageBuilder/StageForm.tsx +262 -0
- package/src/components/FunnelStageBuilder/TagInput.test.tsx +178 -0
- package/src/components/FunnelStageBuilder/TagInput.tsx +129 -0
- package/src/components/FunnelStageBuilder/index.ts +21 -0
- package/src/components/FunnelVisualFlow/FlowLegend.tsx +77 -0
- package/{dist/components/index.css → src/components/FunnelVisualFlow/FunnelVisualFlow.css} +89 -13
- package/src/components/FunnelVisualFlow/FunnelVisualFlow.stories.tsx +254 -0
- package/src/components/FunnelVisualFlow/FunnelVisualFlow.test.tsx +208 -0
- package/src/components/FunnelVisualFlow/FunnelVisualFlow.tsx +229 -0
- package/src/components/FunnelVisualFlow/README.md +323 -0
- package/src/components/FunnelVisualFlow/StageNode.tsx +188 -0
- package/src/components/FunnelVisualFlow/example.tsx +227 -0
- package/src/components/FunnelVisualFlow/index.ts +10 -0
- package/src/components/index.ts +102 -0
- package/src/core/README.md +307 -0
- package/src/core/engine.test.ts +1087 -0
- package/src/core/engine.ts +329 -0
- package/src/core/evaluator.example.ts +353 -0
- package/src/core/evaluator.test.ts +639 -0
- package/src/core/evaluator.ts +261 -0
- package/src/core/field-resolver.example.ts +175 -0
- package/src/core/field-resolver.test.ts +541 -0
- package/src/core/field-resolver.ts +247 -0
- package/src/core/index.ts +34 -0
- package/src/core/operators.test.ts +539 -0
- package/src/core/operators.ts +241 -0
- package/src/hooks/index.ts +5 -0
- package/src/hooks/useDebouncedValue.ts +28 -0
- package/src/index.ts +155 -0
- package/src/store/README.md +342 -0
- package/src/store/create-funnel-store.test.ts +686 -0
- package/src/store/create-funnel-store.ts +538 -0
- package/src/store/index.ts +9 -0
- package/src/store/types.ts +294 -0
- package/src/stories/CrossDomain.stories.tsx +149 -0
- package/src/stories/Welcome.stories.tsx +81 -0
- package/src/stories/demo-data/index.ts +3 -0
- package/src/stories/demo-data/investors.ts +216 -0
- package/src/stories/demo-data/leads.ts +223 -0
- package/src/stories/demo-data/recipes.ts +217 -0
- package/src/test/setup.ts +5 -0
- package/src/types/index.ts +843 -0
- package/dist/client-3ESO2NHy.d.ts +0 -310
- package/dist/client-CZu03ACp.d.cts +0 -310
- package/dist/components/index.cjs +0 -3241
- package/dist/components/index.cjs.map +0 -1
- package/dist/components/index.css.map +0 -1
- package/dist/components/index.d.cts +0 -726
- package/dist/components/index.d.ts +0 -726
- package/dist/components/index.js +0 -3194
- package/dist/components/index.js.map +0 -1
- package/dist/core/index.cjs +0 -500
- package/dist/core/index.cjs.map +0 -1
- package/dist/core/index.d.cts +0 -359
- package/dist/core/index.d.ts +0 -359
- package/dist/core/index.js +0 -486
- package/dist/core/index.js.map +0 -1
- package/dist/hooks/index.cjs +0 -20
- package/dist/hooks/index.cjs.map +0 -1
- package/dist/hooks/index.d.cts +0 -11
- package/dist/hooks/index.d.ts +0 -11
- package/dist/hooks/index.js +0 -18
- package/dist/hooks/index.js.map +0 -1
- package/dist/index-BGDEXbuz.d.cts +0 -434
- package/dist/index-BGDEXbuz.d.ts +0 -434
- package/dist/index.cjs +0 -4499
- package/dist/index.cjs.map +0 -1
- package/dist/index.css +0 -198
- package/dist/index.css.map +0 -1
- package/dist/index.d.cts +0 -99
- package/dist/index.d.ts +0 -99
- package/dist/index.js +0 -4421
- package/dist/index.js.map +0 -1
- package/dist/store/index.cjs +0 -389
- package/dist/store/index.cjs.map +0 -1
- package/dist/store/index.d.cts +0 -225
- package/dist/store/index.d.ts +0 -225
- package/dist/store/index.js +0 -386
- 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';
|