Summary
Research the minimal plugin transformation contract, its TypeScript and Python API shape, and how protocol-defined mappings and runtime utilities should integrate with that contract.
Context
ADR 0017 defines a JSON mapping format for declarative transformations. Runtime implementations already exist:
- Python SDK:
transform_from_mapping() with field and switch handlers
- Website:
transformWithMapping() with the same handler set
However, these utilities exist in isolation — there's no formal plugin contract that defines what a transformation interface should look like. The PA and CA example APIs use imperative class-based transformers (OpportunityTransformer) that don't use the mapping utilities at all.
This spike should define the transform contract first, then determine how mapping utilities fit behind that contract.
Design constraints
The repo's public API style leans structural and simple:
- TypeScript plugin system uses structural typing
- Python uses simple dataclasses and config objects
- Avoid inheritance-heavy abstract base class hierarchies
The transform contract should feel like a small interface or plain object contract, not an elaborate class hierarchy.
Questions to answer
-
Minimal contract shape: What is the minimal transform interface? A single function signature? A config object with transform methods?
-
Directionality: Should the contract be directional?
transformToCommonGrants(source: S) -> T (required)
transformFromCommonGrants(cg: T) -> S (optional, for round-trip scenarios)
-
Structural vs class-based: Should the contract be:
- A structural interface (TS) / Protocol (Python) that any conforming object satisfies?
- A factory function pattern?
- A class-based approach (like the existing
OpportunityTransformer in examples)?
-
Mapping integration: How does the JSON mapping format fit into the contract?
- Required (all plugins must use mappings)?
- Optional utility (one way to implement the contract)?
- Separate concern (mapping runtime is independent of adapter contract)?
-
Porting the website utility: Can website/src/lib/transformation.ts be ported directly to @common-grants/sdk, or should it be adapted to fit behind the contract interface?
-
Validation boundaries: What should the SDK validate, and at what layer?
- plugin transform output validation: How should the SDK verify that transformed output conforms to CommonGrants models?
- Compile-time type checking only?
- Runtime schema validation?
- Both?
- Mapping definition validation: If mapping utilities are exposed, how should the SDK validate mapping definitions?
- Validate only against the ADR 0017 /
MappingSchema structure?
- Also validate compatibility with the runtime's supported handler set?
- When should validation happen: authoring time, load time, or execution time?
-
Handler extensibility: If mapping utilities are exposed, how do users register custom handlers? What's the DX for adding a myCustomHandler transformation?
-
Error handling: What context should transformation errors include? (path, handler name, input value, expected type?)
Acceptance criteria
Related
Summary
Research the minimal plugin transformation contract, its TypeScript and Python API shape, and how protocol-defined mappings and runtime utilities should integrate with that contract.
Context
ADR 0017 defines a JSON mapping format for declarative transformations. Runtime implementations already exist:
transform_from_mapping()withfieldandswitchhandlerstransformWithMapping()with the same handler setHowever, these utilities exist in isolation — there's no formal plugin contract that defines what a transformation interface should look like. The PA and CA example APIs use imperative class-based transformers (
OpportunityTransformer) that don't use the mapping utilities at all.This spike should define the transform contract first, then determine how mapping utilities fit behind that contract.
Design constraints
The repo's public API style leans structural and simple:
The transform contract should feel like a small interface or plain object contract, not an elaborate class hierarchy.
Questions to answer
Minimal contract shape: What is the minimal transform interface? A single function signature? A config object with transform methods?
Directionality: Should the contract be directional?
transformToCommonGrants(source: S) -> T(required)transformFromCommonGrants(cg: T) -> S(optional, for round-trip scenarios)Structural vs class-based: Should the contract be:
OpportunityTransformerin examples)?Mapping integration: How does the JSON mapping format fit into the contract?
Porting the website utility: Can
website/src/lib/transformation.tsbe ported directly to@common-grants/sdk, or should it be adapted to fit behind the contract interface?Validation boundaries: What should the SDK validate, and at what layer?
MappingSchemastructure?Handler extensibility: If mapping utilities are exposed, how do users register custom handlers? What's the DX for adding a
myCustomHandlertransformation?Error handling: What context should transformation errors include? (path, handler name, input value, expected type?)
Acceptance criteria
transformWithMapping()can be ported directlyRelated
website/src/lib/transformation.tsexamples/pa-opportunity-example/src/common_grants/utils/opp_transform.py