@startsimpli/funnels 0.4.13 → 0.4.14

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 (31) hide show
  1. package/README.md +326 -281
  2. package/package.json +14 -14
  3. package/src/api/client.paths.test.ts +162 -0
  4. package/src/api/client.ts +72 -19
  5. package/src/api/index.ts +11 -0
  6. package/src/api/paths.test.ts +125 -0
  7. package/src/api/paths.ts +146 -0
  8. package/src/components/FilterRuleEditor/FieldSelector.tsx +7 -5
  9. package/src/components/FilterRuleEditor/FilterRuleEditor.stories.tsx +3 -3
  10. package/src/components/FilterRuleEditor/FilterRuleEditor.test.tsx +13 -8
  11. package/src/components/FilterRuleEditor/FilterRuleEditor.tsx +7 -2
  12. package/src/components/FilterRuleEditor/OperatorSelector.tsx +1 -1
  13. package/src/components/FilterRuleEditor/RuleRow.test.tsx +166 -0
  14. package/src/components/FilterRuleEditor/RuleRow.tsx +73 -10
  15. package/src/components/FilterRuleEditor/constants.ts +12 -0
  16. package/src/components/FunnelPreview/example.tsx +15 -15
  17. package/src/components/FunnelStageBuilder/FunnelStageBuilder.stories.tsx +2 -2
  18. package/src/components/FunnelStageBuilder/FunnelStageBuilder.test.tsx +2 -2
  19. package/src/components/FunnelStageBuilder/FunnelStageBuilder.tsx +2 -2
  20. package/src/components/FunnelStageBuilder/StageCard.tsx +2 -2
  21. package/src/components/FunnelStageBuilder/StageForm.tsx +2 -2
  22. package/src/core/evaluator.example.ts +23 -23
  23. package/src/core/evaluator.test.ts +4 -1
  24. package/src/core/evaluator.ts +11 -5
  25. package/src/core/operators.ts +35 -0
  26. package/src/store/create-funnel-store.ts +1 -0
  27. package/src/stories/demo-data/investors.ts +2 -2
  28. package/src/stories/demo-data/leads.ts +2 -2
  29. package/src/stories/demo-data/recipes.ts +2 -2
  30. package/src/types/contract.test.ts +284 -0
  31. package/src/types/index.ts +339 -49
@@ -17,7 +17,7 @@ const investorFunnel: Funnel = {
17
17
  name: 'Investor Qualification',
18
18
  description: 'Filter investors by firm stage and check size',
19
19
  status: 'active',
20
- inputType: 'contacts',
20
+ entityType: 'investor',
21
21
  stages: [
22
22
  {
23
23
  id: 'stage-1',
@@ -25,9 +25,9 @@ const investorFunnel: Funnel = {
25
25
  name: 'Series A or Later',
26
26
  filterLogic: 'OR',
27
27
  rules: [
28
- { fieldPath: 'firm.stage', operator: 'eq', value: 'Series A' },
29
- { fieldPath: 'firm.stage', operator: 'eq', value: 'Series B' },
30
- { fieldPath: 'firm.stage', operator: 'eq', value: 'Series C' },
28
+ { field: 'firm.stage', operator: 'eq', value: 'Series A' },
29
+ { field: 'firm.stage', operator: 'eq', value: 'Series B' },
30
+ { field: 'firm.stage', operator: 'eq', value: 'Series C' },
31
31
  ],
32
32
  matchAction: 'continue',
33
33
  noMatchAction: 'exclude',
@@ -37,7 +37,7 @@ const investorFunnel: Funnel = {
37
37
  order: 1,
38
38
  name: 'High ICP Score',
39
39
  filterLogic: 'AND',
40
- rules: [{ fieldPath: 'icp_score', operator: 'gte', value: 80 }],
40
+ rules: [{ field: 'icp_score', operator: 'gte', value: 80 }],
41
41
  matchAction: 'output',
42
42
  noMatchAction: 'exclude',
43
43
  },
@@ -112,14 +112,14 @@ const recipeFunnel: Funnel = {
112
112
  name: 'Recipe Recommendations',
113
113
  description: 'Filter recipes by dietary restrictions and difficulty',
114
114
  status: 'active',
115
- inputType: 'any',
115
+ entityType: 'recipe',
116
116
  stages: [
117
117
  {
118
118
  id: 'dietary',
119
119
  order: 0,
120
120
  name: 'Vegetarian',
121
121
  filterLogic: 'AND',
122
- rules: [{ fieldPath: 'tags', operator: 'has_tag', value: 'vegetarian' }],
122
+ rules: [{ field: 'tags', operator: 'has_tag', value: 'vegetarian' }],
123
123
  matchAction: 'continue',
124
124
  noMatchAction: 'exclude',
125
125
  },
@@ -129,8 +129,8 @@ const recipeFunnel: Funnel = {
129
129
  name: 'Easy to Moderate',
130
130
  filterLogic: 'OR',
131
131
  rules: [
132
- { fieldPath: 'difficulty', operator: 'eq', value: 'easy' },
133
- { fieldPath: 'difficulty', operator: 'eq', value: 'moderate' },
132
+ { field: 'difficulty', operator: 'eq', value: 'easy' },
133
+ { field: 'difficulty', operator: 'eq', value: 'moderate' },
134
134
  ],
135
135
  matchAction: 'output',
136
136
  noMatchAction: 'exclude',
@@ -198,14 +198,14 @@ const leadFunnel: Funnel = {
198
198
  name: 'Qualified Leads',
199
199
  description: 'Score and filter leads based on company size and engagement',
200
200
  status: 'active',
201
- inputType: 'any',
201
+ entityType: 'lead',
202
202
  stages: [
203
203
  {
204
204
  id: 'company-size',
205
205
  order: 0,
206
206
  name: 'Mid-Market and Above',
207
207
  filterLogic: 'AND',
208
- rules: [{ fieldPath: 'company.employees', operator: 'gte', value: 50 }],
208
+ rules: [{ field: 'company.employees', operator: 'gte', value: 50 }],
209
209
  matchAction: 'continue',
210
210
  noMatchAction: 'exclude',
211
211
  },
@@ -214,7 +214,7 @@ const leadFunnel: Funnel = {
214
214
  order: 1,
215
215
  name: 'High Engagement',
216
216
  filterLogic: 'AND',
217
- rules: [{ fieldPath: 'engagement_score', operator: 'gte', value: 7 }],
217
+ rules: [{ field: 'engagement_score', operator: 'gte', value: 7 }],
218
218
  matchAction: 'tag_continue',
219
219
  noMatchAction: 'continue',
220
220
  matchTags: ['high-engagement'],
@@ -225,9 +225,9 @@ const leadFunnel: Funnel = {
225
225
  name: 'Target Industries',
226
226
  filterLogic: 'OR',
227
227
  rules: [
228
- { fieldPath: 'company.industry', operator: 'eq', value: 'Technology' },
229
- { fieldPath: 'company.industry', operator: 'eq', value: 'SaaS' },
230
- { fieldPath: 'company.industry', operator: 'eq', value: 'FinTech' },
228
+ { field: 'company.industry', operator: 'eq', value: 'Technology' },
229
+ { field: 'company.industry', operator: 'eq', value: 'SaaS' },
230
+ { field: 'company.industry', operator: 'eq', value: 'FinTech' },
231
231
  ],
232
232
  matchAction: 'output',
233
233
  noMatchAction: 'exclude',
@@ -5,11 +5,11 @@
5
5
  import type { Meta, StoryObj } from '@storybook/react';
6
6
  import { useState } from 'react';
7
7
  import { FunnelStageBuilder } from './FunnelStageBuilder';
8
- import { Funnel, FieldDefinition } from '../../types';
8
+ import { Funnel, FieldDefinitionInput } from '../../types';
9
9
  import './FunnelStageBuilder.css';
10
10
 
11
11
  // Sample field registry
12
- const sampleFieldRegistry: FieldDefinition[] = [
12
+ const sampleFieldRegistry: FieldDefinitionInput[] = [
13
13
  // Firm fields
14
14
  {
15
15
  name: 'firm.name',
@@ -5,10 +5,10 @@
5
5
  import { describe, it, expect, vi } from 'vitest';
6
6
  import { render, screen, fireEvent, waitFor } from '@testing-library/react';
7
7
  import { FunnelStageBuilder } from './FunnelStageBuilder';
8
- import { Funnel, FieldDefinition } from '../../types';
8
+ import { Funnel, FieldDefinitionInput } from '../../types';
9
9
 
10
10
  // Mock field registry
11
- const mockFieldRegistry: FieldDefinition[] = [
11
+ const mockFieldRegistry: FieldDefinitionInput[] = [
12
12
  {
13
13
  name: 'firm.stage',
14
14
  label: 'Firm Stage',
@@ -25,7 +25,7 @@ import {
25
25
  sortableKeyboardCoordinates,
26
26
  verticalListSortingStrategy,
27
27
  } from '@dnd-kit/sortable';
28
- import { Funnel, FunnelStage, FieldDefinition } from '../../types';
28
+ import { Funnel, FunnelStage, FieldDefinitionInput } from '../../types';
29
29
  import { StageCard } from './StageCard';
30
30
  import { AddStageButton } from './AddStageButton';
31
31
 
@@ -37,7 +37,7 @@ export interface FunnelStageBuilderProps {
37
37
  onUpdate: (funnel: Funnel) => void;
38
38
 
39
39
  /** Available fields for filter rules */
40
- fieldRegistry: FieldDefinition[];
40
+ fieldRegistry: FieldDefinitionInput[];
41
41
 
42
42
  /** Optional CSS class */
43
43
  className?: string;
@@ -12,7 +12,7 @@
12
12
  import React from 'react';
13
13
  import { useSortable } from '@dnd-kit/sortable';
14
14
  import { CSS } from '@dnd-kit/utilities';
15
- import { FunnelStage, FieldDefinition } from '../../types';
15
+ import { FunnelStage, FieldDefinitionInput } from '../../types';
16
16
  import { StageForm } from './StageForm';
17
17
 
18
18
  export interface StageCardProps {
@@ -32,7 +32,7 @@ export interface StageCardProps {
32
32
  onRemove: () => void;
33
33
 
34
34
  /** Available fields */
35
- fieldRegistry: FieldDefinition[];
35
+ fieldRegistry: FieldDefinitionInput[];
36
36
 
37
37
  /** Validation error */
38
38
  error?: string;
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  import React, { useCallback, useState } from 'react';
14
- import { FunnelStage, FieldDefinition, FilterLogic, MatchAction, NoMatchAction, FilterRule } from '../../types';
14
+ import { FunnelStage, FieldDefinitionInput, FilterLogic, MatchAction, NoMatchAction, FilterRule } from '../../types';
15
15
  import { StageActions } from './StageActions';
16
16
  import { TagInput } from './TagInput';
17
17
  import { FilterRuleEditor } from '../FilterRuleEditor/FilterRuleEditor';
@@ -24,7 +24,7 @@ export interface StageFormProps {
24
24
  onUpdate: (stage: FunnelStage) => void;
25
25
 
26
26
  /** Available fields */
27
- fieldRegistry: FieldDefinition[];
27
+ fieldRegistry: FieldDefinitionInput[];
28
28
  }
29
29
 
30
30
  /**
@@ -72,7 +72,7 @@ const investors: Investor[] = [
72
72
  // Example 1a: Single rule evaluation
73
73
  console.log('=== Example 1a: Single Rule ===');
74
74
  const rule1: FilterRule = {
75
- fieldPath: 'firm.aum',
75
+ field: 'firm.aum',
76
76
  operator: 'gte',
77
77
  value: 100000000,
78
78
  };
@@ -85,9 +85,9 @@ investors.forEach(investor => {
85
85
  // Example 1b: Multiple rules with AND logic
86
86
  console.log('\n=== Example 1b: Multiple Rules (AND) ===');
87
87
  const rules1: FilterRule[] = [
88
- { fieldPath: 'firm.stage', operator: 'in', value: ['Series A', 'Series B'] },
89
- { fieldPath: 'firm.aum', operator: 'gte', value: 100000000 },
90
- { fieldPath: 'tags', operator: 'has_tag', value: 'qualified' },
88
+ { field: 'firm.stage', operator: 'in', value: ['Series A', 'Series B'] },
89
+ { field: 'firm.aum', operator: 'gte', value: 100000000 },
90
+ { field: 'tags', operator: 'has_tag', value: 'qualified' },
91
91
  ];
92
92
 
93
93
  const qualified = filterEntities(investors, rules1, 'AND');
@@ -151,7 +151,7 @@ const recipes: Recipe[] = [
151
151
  // Example 2a: Filter by dietary restrictions
152
152
  console.log('\n=== Example 2: Recipe Filtering ===');
153
153
  const veganRules: FilterRule[] = [
154
- { fieldPath: 'dietary', operator: 'has_tag', value: 'vegan' },
154
+ { field: 'dietary', operator: 'has_tag', value: 'vegan' },
155
155
  ];
156
156
 
157
157
  const veganRecipes = filterEntities(recipes, veganRules);
@@ -159,9 +159,9 @@ console.log('Vegan recipes:', veganRecipes.map(r => r.name));
159
159
 
160
160
  // Example 2b: Filter by multiple criteria (quick meals)
161
161
  const quickMealRules: FilterRule[] = [
162
- { fieldPath: 'prep_time_minutes', operator: 'lte', value: 30 },
163
- { fieldPath: 'difficulty', operator: 'in', value: ['easy', 'medium'] },
164
- { fieldPath: 'rating', operator: 'gte', value: 4.0 },
162
+ { field: 'prep_time_minutes', operator: 'lte', value: 30 },
163
+ { field: 'difficulty', operator: 'in', value: ['easy', 'medium'] },
164
+ { field: 'rating', operator: 'gte', value: 4.0 },
165
165
  ];
166
166
 
167
167
  const quickMeals = filterEntities(recipes, quickMealRules, 'AND');
@@ -222,9 +222,9 @@ const repos: GitHubRepo[] = [
222
222
  // Example 3a: Find popular active repos
223
223
  console.log('\n=== Example 3: GitHub Repos ===');
224
224
  const popularRepoRules: FilterRule[] = [
225
- { fieldPath: 'stars', operator: 'gte', value: 100 },
226
- { fieldPath: 'is_archived', operator: 'is_false', value: null },
227
- { fieldPath: 'last_updated', operator: 'gte', value: new Date('2024-01-01') },
225
+ { field: 'stars', operator: 'gte', value: 100 },
226
+ { field: 'is_archived', operator: 'is_false', value: null },
227
+ { field: 'last_updated', operator: 'gte', value: new Date('2024-01-01') },
228
228
  ];
229
229
 
230
230
  const popularRepos = filterEntities(repos, popularRepoRules, 'AND');
@@ -232,8 +232,8 @@ console.log('Popular active repos:', popularRepos.map(r => r.name));
232
232
 
233
233
  // Example 3b: Find TypeScript projects with React
234
234
  const tsReactRules: FilterRule[] = [
235
- { fieldPath: 'language', operator: 'eq', value: 'TypeScript' },
236
- { fieldPath: 'topics', operator: 'has_tag', value: 'react' },
235
+ { field: 'language', operator: 'eq', value: 'TypeScript' },
236
+ { field: 'topics', operator: 'has_tag', value: 'react' },
237
237
  ];
238
238
 
239
239
  const tsReactRepos = filterEntities(repos, tsReactRules, 'AND');
@@ -247,7 +247,7 @@ console.log('\n=== Example 4: Advanced Patterns ===');
247
247
 
248
248
  // Pattern 1: Negation
249
249
  const notArchivedRule: FilterRule = {
250
- fieldPath: 'is_archived',
250
+ field: 'is_archived',
251
251
  operator: 'is_true',
252
252
  value: null,
253
253
  negate: true, // Negate the result
@@ -258,7 +258,7 @@ console.log('Active repos (using negate):', activeRepos.map(r => r.name));
258
258
 
259
259
  // Pattern 2: Complex string matching
260
260
  const emailDomainRule: FilterRule = {
261
- fieldPath: 'email',
261
+ field: 'email',
262
262
  operator: 'matches',
263
263
  value: '@acme\\.vc$', // Matches emails ending with @acme.vc
264
264
  };
@@ -268,8 +268,8 @@ console.log('Acme VC investors:', acmeInvestors.map(i => i.name));
268
268
 
269
269
  // Pattern 3: OR logic (match any condition)
270
270
  const flexibleStageRules: FilterRule[] = [
271
- { fieldPath: 'firm.stage', operator: 'eq', value: 'Series A' },
272
- { fieldPath: 'firm.aum', operator: 'gte', value: 500000000 }, // OR very large fund
271
+ { field: 'firm.stage', operator: 'eq', value: 'Series A' },
272
+ { field: 'firm.aum', operator: 'gte', value: 500000000 }, // OR very large fund
273
273
  ];
274
274
 
275
275
  const flexibleMatch = filterEntities(investors, flexibleStageRules, 'OR');
@@ -286,7 +286,7 @@ investorsWithoutEmail.push({
286
286
  });
287
287
 
288
288
  const hasEmailRule: FilterRule = {
289
- fieldPath: 'email',
289
+ field: 'email',
290
290
  operator: 'isnotnull',
291
291
  value: null,
292
292
  };
@@ -296,7 +296,7 @@ console.log('Investors with email:', withEmail.map(i => i.name));
296
296
 
297
297
  // Pattern 5: Array operations
298
298
  const multiGeographyRule: FilterRule = {
299
- fieldPath: 'firm.geography',
299
+ field: 'firm.geography',
300
300
  operator: 'has_all',
301
301
  value: ['US', 'Europe'],
302
302
  };
@@ -329,22 +329,22 @@ function simulateFunnel<T>(entities: T[], stages: Array<{ name: string; rules: F
329
329
  const investorFunnelStages = [
330
330
  {
331
331
  name: 'Active & Not Archived',
332
- rules: [{ fieldPath: 'tags', operator: 'has_tag', value: 'active' } as FilterRule],
332
+ rules: [{ field: 'tags', operator: 'has_tag', value: 'active' } as FilterRule],
333
333
  logic: 'AND' as const,
334
334
  },
335
335
  {
336
336
  name: 'Series A+ Stage',
337
- rules: [{ fieldPath: 'firm.stage', operator: 'in', value: ['Series A', 'Series B', 'Series C'] } as FilterRule],
337
+ rules: [{ field: 'firm.stage', operator: 'in', value: ['Series A', 'Series B', 'Series C'] } as FilterRule],
338
338
  logic: 'AND' as const,
339
339
  },
340
340
  {
341
341
  name: 'Minimum AUM $100M',
342
- rules: [{ fieldPath: 'firm.aum', operator: 'gte', value: 100000000 } as FilterRule],
342
+ rules: [{ field: 'firm.aum', operator: 'gte', value: 100000000 } as FilterRule],
343
343
  logic: 'AND' as const,
344
344
  },
345
345
  {
346
346
  name: 'Qualified Tag',
347
- rules: [{ fieldPath: 'tags', operator: 'has_tag', value: 'qualified' } as FilterRule],
347
+ rules: [{ field: 'tags', operator: 'has_tag', value: 'qualified' } as FilterRule],
348
348
  logic: 'AND' as const,
349
349
  },
350
350
  ];
@@ -544,10 +544,13 @@ describe('Multiple Rules', () => {
544
544
 
545
545
  describe('Detailed Results', () => {
546
546
  it('should return detailed rule result', () => {
547
- const rule: FilterRule = { fieldPath: 'firm.stage', operator: 'eq', value: 'Series A' };
547
+ const rule: FilterRule = { field: 'firm.stage', operator: 'eq', value: 'Series A' };
548
548
  const result = evaluateRuleWithResult(sampleEntity, rule);
549
549
 
550
550
  expect(result).toEqual({
551
+ // `field` is the canonical key; `fieldPath` mirrors it for readers
552
+ // written before the one-contract change (bd startsim-pxzlf).
553
+ field: 'firm.stage',
551
554
  fieldPath: 'firm.stage',
552
555
  operator: 'eq',
553
556
  value: 'Series A',
@@ -11,6 +11,7 @@
11
11
  */
12
12
 
13
13
  import type { FilterRule, RuleResult } from '../types';
14
+ import { ruleField } from '../types';
14
15
  import { resolveField } from './field-resolver';
15
16
  import { applyOperator } from './operators';
16
17
 
@@ -40,8 +41,10 @@ import { applyOperator } from './operators';
40
41
  * ```
41
42
  */
42
43
  export function evaluateRule<T>(entity: T, rule: FilterRule): boolean {
43
- // Resolve field value from entity
44
- const actualValue = resolveField(entity, rule.fieldPath);
44
+ // Resolve field value from entity. `ruleField` reads the canonical `field`
45
+ // and falls back to the deprecated `fieldPath`, so a rule persisted under
46
+ // either shape evaluates the same.
47
+ const actualValue = resolveField(entity, ruleField(rule));
45
48
 
46
49
  // Apply operator
47
50
  const result = applyOperator(rule.operator, actualValue, rule.value);
@@ -77,9 +80,10 @@ export function evaluateRuleWithResult<T>(
77
80
  entity: T,
78
81
  rule: FilterRule
79
82
  ): RuleResult {
83
+ const field = ruleField(rule);
80
84
  try {
81
85
  // Resolve field value
82
- const actualValue = resolveField(entity, rule.fieldPath);
86
+ const actualValue = resolveField(entity, field);
83
87
 
84
88
  // Apply operator
85
89
  const operatorResult = applyOperator(rule.operator, actualValue, rule.value);
@@ -88,7 +92,8 @@ export function evaluateRuleWithResult<T>(
88
92
  const matched = rule.negate ? !operatorResult : operatorResult;
89
93
 
90
94
  return {
91
- fieldPath: rule.fieldPath,
95
+ field,
96
+ fieldPath: field,
92
97
  operator: rule.operator,
93
98
  value: rule.value,
94
99
  actualValue: actualValue,
@@ -97,7 +102,8 @@ export function evaluateRuleWithResult<T>(
97
102
  } catch (error) {
98
103
  // Handle evaluation errors gracefully
99
104
  return {
100
- fieldPath: rule.fieldPath,
105
+ field,
106
+ fieldPath: field,
101
107
  operator: rule.operator,
102
108
  value: rule.value,
103
109
  actualValue: undefined,
@@ -46,6 +46,28 @@ export function applyOperator(
46
46
  case 'lte':
47
47
  return compareValues(actual, expected, (a, e) => a <= e);
48
48
 
49
+ // Inclusive range. The backend's ValueShape.TUPLE2: value is [min, max].
50
+ case 'between': {
51
+ if (!Array.isArray(expected) || expected.length !== 2) return false;
52
+ const [min, max] = expected;
53
+ return (
54
+ compareValues(actual, min, (a, e) => a >= e) &&
55
+ compareValues(actual, max, (a, e) => a <= e)
56
+ );
57
+ }
58
+
59
+ // ========================================================================
60
+ // Presence — the backend spelling. A tag category with no value, an
61
+ // attribute that is simply set. Absent, null and '' all count as absent,
62
+ // because a tag row that does not exist and an empty one mean the same
63
+ // thing to a person building a funnel.
64
+ // ========================================================================
65
+ case 'exists':
66
+ return isPresent(actual);
67
+
68
+ case 'not_exists':
69
+ return !isPresent(actual);
70
+
49
71
  // ========================================================================
50
72
  // String operations (case-insensitive)
51
73
  // ========================================================================
@@ -148,6 +170,19 @@ export function applyOperator(
148
170
  }
149
171
  }
150
172
 
173
+ /**
174
+ * Whether a resolved value counts as present for exists / not_exists.
175
+ *
176
+ * An empty array is absent: 'tag.stage_focus' resolving to [] means the entity
177
+ * carries no tag in that category, which is exactly what not_exists asks about.
178
+ */
179
+ function isPresent(actual: any): boolean {
180
+ if (actual === null || actual === undefined) return false;
181
+ if (typeof actual === 'string') return actual.length > 0;
182
+ if (Array.isArray(actual)) return actual.length > 0;
183
+ return true;
184
+ }
185
+
151
186
  /**
152
187
  * Compare values with type normalization
153
188
  *
@@ -204,6 +204,7 @@ export function createFunnelStore<TEntity = any>(
204
204
  name: `${funnel.name} (Copy)`,
205
205
  description: funnel.description,
206
206
  status: 'draft', // Always create as draft
207
+ entityType: funnel.entityType,
207
208
  inputType: funnel.inputType,
208
209
  stages: funnel.stages.map((stage, index) => ({
209
210
  ...stage,
@@ -1,6 +1,6 @@
1
- import type { FieldDefinition } from '../../types';
1
+ import type { FieldDefinitionInput } from '../../types';
2
2
 
3
- export const investorFields: FieldDefinition[] = [
3
+ export const investorFields: FieldDefinitionInput[] = [
4
4
  {
5
5
  name: 'firm.name',
6
6
  type: 'text',
@@ -1,6 +1,6 @@
1
- import type { FieldDefinition } from '../../types';
1
+ import type { FieldDefinitionInput } from '../../types';
2
2
 
3
- export const leadFields: FieldDefinition[] = [
3
+ export const leadFields: FieldDefinitionInput[] = [
4
4
  {
5
5
  name: 'lead.name',
6
6
  type: 'text',
@@ -1,6 +1,6 @@
1
- import type { FieldDefinition } from '../../types';
1
+ import type { FieldDefinitionInput } from '../../types';
2
2
 
3
- export const recipeFields: FieldDefinition[] = [
3
+ export const recipeFields: FieldDefinitionInput[] = [
4
4
  {
5
5
  name: 'recipe.name',
6
6
  type: 'text',