Skip to content

Commit ed9d808

Browse files
docs: improve type safety in ISV Template System Design
1 parent ef92cf3 commit ed9d808

File tree

1 file changed

+98
-39
lines changed

1 file changed

+98
-39
lines changed

docs/ISV_TEMPLATE_SYSTEM_DESIGN.md

Lines changed: 98 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ type MutationDef = SingleMutation | LoopMutation;
135135
interface SingleMutation {
136136
id: string;
137137
label: string;
138-
action: string; // Built-in action name
138+
action: ActionName; // Type-safe action name from registry
139139
variables: VariablesConfig;
140140
when?: (form: FormData) => boolean;
141141
}
@@ -149,7 +149,7 @@ interface LoopMutation {
149149
interface LoopStep {
150150
id: string;
151151
label: string; // Supports template syntax: {{name}}, {{capacity}}
152-
action: string;
152+
action: ActionName; // Type-safe action name from registry
153153
variables: LoopVariablesConfig;
154154
when?: (form: FormData, item: any) => boolean;
155155
}
@@ -163,7 +163,33 @@ type LoopVariablesConfig =
163163
(form: FormData, prev: PreviousResults, item: any, index: number) => Record<string, any>;
164164
```
165165

166-
### 3.4 Summary Configuration
166+
### 3.4 Runtime Context
167+
168+
System-injected fields are explicitly typed via `RuntimeContext`:
169+
170+
```typescript
171+
// types/template.ts
172+
interface RuntimeContext {
173+
/** Whether SOS API is available (from useCheckSOSAPIStatus) */
174+
_sosApiAvailable: boolean;
175+
/** Selected existing account's role ARN (from account selection) */
176+
_existingAccountArn?: string;
177+
/** Selected existing account's ID (from account selection) */
178+
_existingAccountId?: string;
179+
/** IAM user type: 'create' or 'existing' (from IAM user selection) */
180+
_iamUserType?: 'create' | 'existing';
181+
}
182+
183+
// FormData includes both user input and runtime context
184+
type FormData<T extends Record<string, any> = Record<string, any>> = T & RuntimeContext;
185+
```
186+
187+
This ensures:
188+
- **Type safety**: Typos like `form._sosApiAvaialble` are caught at compile time
189+
- **Discoverability**: IDE autocomplete shows all available context fields
190+
- **Explicit contract**: Template authors can see exactly what's injected
191+
192+
### 3.5 Summary Configuration
167193

168194
```typescript
169195
interface SummaryConfig {
@@ -190,7 +216,39 @@ interface SummaryConfig {
190216

191217
---
192218

193-
## 4. Built-in Actions
219+
## 4. Action Registry
220+
221+
Actions are defined in a central registry, providing type safety and a single source of truth:
222+
223+
```typescript
224+
// engine/actions.ts
225+
export const ACTION_REGISTRY = {
226+
enableSOSAPI: useEnableSOSAPIMutation,
227+
createAccount: useCreateAccountMutation,
228+
refetchConfig: useRefetchConfig,
229+
assumeRole: useAssumeRole,
230+
createBucket: useCreateBucket,
231+
tagBucket: useSetBucketTagging,
232+
putObject: usePutObject,
233+
createIAMUser: useCreateIAMUserMutation,
234+
createAccessKey: useCreateUserAccessKeyMutation,
235+
createPolicy: useCreateOrAddBucketToPolicyMutation,
236+
attachPolicy: useAttachPolicyToUserMutation,
237+
} as const;
238+
239+
// Type automatically derived from registry
240+
export type ActionName = keyof typeof ACTION_REGISTRY;
241+
```
242+
243+
**Benefits:**
244+
- **Compile-time safety**: Typos like `'createAcount'` are caught at build time
245+
- **IDE autocomplete**: Full IntelliSense support for action names
246+
- **Single source of truth**: Adding a new action to the registry automatically updates the type
247+
- **No sync issues**: The type and implementation can never get out of sync
248+
249+
---
250+
251+
## 5. Built-in Actions
194252

195253
The execution engine provides these built-in actions that map to existing mutation hooks:
196254

@@ -210,9 +268,9 @@ The execution engine provides these built-in actions that map to existing mutati
210268

211269
---
212270

213-
## 5. Mutation Execution Mechanism
271+
## 6. Mutation Execution Mechanism
214272

215-
### 5.1 Compilation Phase
273+
### 6.1 Compilation Phase
216274

217275
The Template Compiler transforms the declarative mutation array into the format required by `useChainedMutations`:
218276

@@ -249,7 +307,7 @@ mutations: [
249307
}
250308
```
251309

252-
### 5.2 Loop Expansion
310+
### 6.2 Loop Expansion
253311

254312
Loop mutations (`LoopMutation`) are expanded **per item**, meaning all steps for one item complete before moving to the next:
255313

@@ -269,7 +327,7 @@ Loop mutations (`LoopMutation`) are expanded **per item**, meaning all steps for
269327
4. tagBucket-1 (backup-2)
270328
```
271329

272-
### 5.3 Execution Phase
330+
### 6.3 Execution Phase
273331

274332
Execution is handled by `useChainedMutations`:
275333

@@ -278,7 +336,7 @@ Execution is handled by `useChainedMutations`:
278336
3. **Error Handling**: On failure, stops execution and enables retry
279337
4. **Result Passing**: Previous results are available via `PreviousResults`
280338

281-
### 5.4 Accessing Previous Results
339+
### 6.4 Accessing Previous Results
282340

283341
The `prev` parameter in variables resolvers provides access to completed mutation results:
284342

@@ -305,7 +363,7 @@ variables: (form, prev) => ({
305363
})
306364
```
307365

308-
### 5.5 Conditional Execution
366+
### 6.5 Conditional Execution
309367

310368
Use `when` to control whether a mutation executes:
311369

@@ -332,7 +390,7 @@ Use `when` to control whether a mutation executes:
332390
}
333391
```
334392

335-
### 5.6 Label Templates
393+
### 6.6 Label Templates
336394

337395
Loop mutation labels support template syntax for dynamic text:
338396

@@ -356,22 +414,23 @@ Loop mutation labels support template syntax for dynamic text:
356414

357415
---
358416

359-
## 6. Runtime Context Injection
417+
## 7. Runtime Context Injection
360418

361-
Some values are injected at runtime before form submission:
419+
Runtime context fields (defined in `RuntimeContext` interface, see [Section 3.4](#34-runtime-context)) are injected into `FormData` before mutation execution.
362420

363-
| Field | Description | Source |
364-
|-------|-------------|--------|
365-
| `_sosApiAvailable` | Whether SOS API is available | `useCheckSOSAPIStatus` |
366-
| `_existingAccountArn` | Selected existing account's role ARN | Account selection |
367-
| `_existingAccountId` | Selected existing account's ID | Account selection |
368-
| `_iamUserType` | 'create' or 'existing' | IAM user selection |
421+
**Injection timing:**
422+
1. User completes form input
423+
2. System evaluates runtime conditions (SOS API status, account selection, etc.)
424+
3. Context fields are merged into form data
425+
4. Mutations execute with the complete `FormData`
369426

370-
These are prefixed with `_` to indicate they are system-injected, not user input.
427+
**Convention:**
428+
- All injected fields are prefixed with `_` to distinguish from user input
429+
- Fields are fully typed - IDE autocomplete and compile-time checks are available
371430

372431
---
373432

374-
## 7. Complete Example: Veeam
433+
## 8. Complete Example: Veeam
375434

376435
```typescript
377436
import Joi from 'joi';
@@ -682,7 +741,7 @@ export const VeeamTemplate: ISVTemplate = {
682741

683742
---
684743

685-
## 8. Comparison with Current System
744+
## 9. Comparison with Current System
686745

687746
| Aspect | Current System | New Template System |
688747
|--------|----------------|---------------------|
@@ -696,9 +755,9 @@ export const VeeamTemplate: ISVTemplate = {
696755

697756
---
698757

699-
## 9. Implementation Plan
758+
## 10. Implementation Plan
700759

701-
### 9.1 Directory Structure
760+
### 10.1 Directory Structure
702761

703762
```
704763
src/react/ISV/
@@ -709,7 +768,7 @@ src/react/ISV/
709768
│ └── commvault.tsx
710769
├── engine/ # NEW: Template engine
711770
│ ├── compiler.ts # Compiles template to mutations
712-
│ ├── actions.ts # Action -> Hook mapping
771+
│ ├── actions.ts # Action registry with type-safe ActionName
713772
│ ├── FormRenderer.tsx # Renders fields to UI
714773
│ └── useTemplateMutations.ts # Wrapper around useChainedMutations
715774
├── components/
@@ -721,7 +780,7 @@ src/react/ISV/
721780
└── template.ts # Template type definitions
722781
```
723782

724-
### 9.2 Migration Steps
783+
### 10.2 Migration Steps
725784

726785
1. **Phase 1**: Create type definitions and template engine
727786
2. **Phase 2**: Implement FormRenderer for field rendering
@@ -730,7 +789,7 @@ src/react/ISV/
730789
5. **Phase 5**: Migrate remaining platforms
731790
6. **Phase 6**: Remove legacy code
732791

733-
### 9.3 Compatibility
792+
### 10.3 Compatibility
734793

735794
- New system can coexist with old system
736795
- Platforms can be migrated one by one
@@ -739,9 +798,9 @@ src/react/ISV/
739798

740799
---
741800

742-
## 10. Error Handling
801+
## 11. Error Handling
743802

744-
### 10.1 Compilation Errors
803+
### 11.1 Compilation Errors
745804

746805
Detected at compile/render time:
747806

@@ -750,15 +809,15 @@ Detected at compile/render time:
750809
- Invalid field type
751810
- Loop over non-array field
752811

753-
### 10.2 Runtime Errors
812+
### 11.2 Runtime Errors
754813

755814
Handled by `useChainedMutations`:
756815

757816
- Mutation failure → Shows error status, enables retry
758817
- Variables resolver throws → Shows error, enables retry
759818
- Network errors → Caught and displayed
760819

761-
### 10.3 Retry Mechanism
820+
### 11.3 Retry Mechanism
762821

763822
Each step has a `retry` function that:
764823

@@ -769,11 +828,11 @@ Each step has a `retry` function that:
769828

770829
---
771830

772-
## 11. Composable Template Pattern
831+
## 12. Composable Template Pattern
773832

774833
To avoid code duplication across platforms, we use a **Composition Pattern** with reusable blocks.
775834

776-
### 11.1 Block Types
835+
### 12.1 Block Types
777836

778837
```typescript
779838
// ============ Block Type Definitions ============
@@ -789,7 +848,7 @@ interface MutationBlock {
789848
type Block = FieldBlock | MutationBlock | (FieldBlock & MutationBlock);
790849
```
791850

792-
### 11.2 Pre-built Blocks
851+
### 12.2 Pre-built Blocks
793852

794853
```typescript
795854
// ============ blocks/account.ts ============
@@ -981,7 +1040,7 @@ export const BucketsBlock: FieldBlock & MutationBlock = {
9811040
};
9821041
```
9831042

984-
### 11.3 Compose Function
1043+
### 12.3 Compose Function
9851044

9861045
```typescript
9871046
// ============ engine/composeTemplate.ts ============
@@ -1104,7 +1163,7 @@ function mergeFields(base: FieldDef[], overrides: FieldDef[]): FieldDef[] {
11041163
}
11051164
```
11061165

1107-
### 11.4 Platform Definitions (Minimal Code)
1166+
### 12.4 Platform Definitions (Minimal Code)
11081167

11091168
```typescript
11101169
// ============ templates/veeam-vbr.ts ============
@@ -1261,23 +1320,23 @@ export const VeeamVBOTemplate = composeTemplate(
12611320
);
12621321
```
12631322

1264-
### 11.5 Code Comparison
1323+
### 12.5 Code Comparison
12651324

12661325
| Platform | Without Composition | With Composition | Reduction |
12671326
|----------|---------------------|------------------|-----------|
12681327
| Veeam VBR | ~250 lines | ~80 lines | **68%** |
12691328
| Commvault | ~200 lines | ~20 lines | **90%** |
12701329
| Veeam VBO | ~220 lines | ~25 lines | **89%** |
12711330

1272-
### 11.6 Block Responsibility
1331+
### 12.6 Block Responsibility
12731332

12741333
| Block | Fields | Validation | Mutations |
12751334
|-------|--------|------------|-----------|
12761335
| `AccountBlock` | accountName, isNewAccount | Account name rules | createAccount, refetchConfig, assumeRole |
12771336
| `IAMBlock` | IAMUserName, generateKey | IAM user rules | createIAMUser, createAccessKey, createPolicy, attachPolicy |
12781337
| `BucketsBlock` | buckets, enableImmutableBackup | Bucket name rules | createBucket, tagBucket (loop) |
12791338

1280-
### 11.7 Directory Structure
1339+
### 12.7 Directory Structure
12811340

12821341
```
12831342
src/react/ISV/

0 commit comments

Comments
 (0)