|
| 1 | +# Composition Architecture and Onboarding (TypeScript) |
| 2 | + |
| 3 | +This is a beginner-friendly guide to the `composition` package. |
| 4 | + |
| 5 | +Scope: |
| 6 | + |
| 7 | +- In scope: `composition/*` |
| 8 | +- Out of scope: `composition-go/*` |
| 9 | + |
| 10 | +## Who this is for |
| 11 | + |
| 12 | +Use this doc if you are: |
| 13 | + |
| 14 | +- new to this repository, |
| 15 | +- trying to understand where composition errors come from, |
| 16 | +- adding or shipping custom directives. |
| 17 | + |
| 18 | +## 1. What this package produces |
| 19 | + |
| 20 | +Given one or more subgraph SDLs, composition produces: |
| 21 | + |
| 22 | +- a federated router schema, |
| 23 | +- a federated client schema, |
| 24 | +- router configuration metadata, |
| 25 | +- warnings and errors. |
| 26 | + |
| 27 | +Main API exports: |
| 28 | + |
| 29 | +- `composition/src/index.ts` |
| 30 | + |
| 31 | +Main functions: |
| 32 | + |
| 33 | +- `federateSubgraphs(...)` |
| 34 | +- `federateSubgraphsWithContracts(...)` |
| 35 | +- `federateSubgraphsContract(...)` |
| 36 | +- `normalizeSubgraph(...)` |
| 37 | +- `batchNormalize(...)` |
| 38 | + |
| 39 | +## 2. Mental model (simple) |
| 40 | + |
| 41 | +Think of composition as a 4-stage pipeline: |
| 42 | + |
| 43 | +1. Normalize each subgraph. |
| 44 | +2. Batch-check all normalized subgraphs together. |
| 45 | +3. Merge everything into one federated model. |
| 46 | +4. Validate resolvability and emit final schemas. |
| 47 | + |
| 48 | +```mermaid |
| 49 | +flowchart LR |
| 50 | + A["Subgraph SDLs"] --> B["NormalizationFactory<br/>(per subgraph)"] |
| 51 | + B --> C["batchNormalize<br/>(cross-subgraph checks)"] |
| 52 | + C --> D["FederationFactory<br/>(merge + build schemas)"] |
| 53 | + D --> E["Graph.validate<br/>(resolvability)"] |
| 54 | + E --> F["FederationResult<br/>AST + schema + config + warnings/errors"] |
| 55 | +``` |
| 56 | + |
| 57 | +## 3. Where each stage lives |
| 58 | + |
| 59 | +### Stage 1: Normalize one subgraph |
| 60 | + |
| 61 | +Main files: |
| 62 | + |
| 63 | +- `composition/src/v1/normalization/normalization-factory.ts` |
| 64 | +- `composition/src/v1/normalization/walkers.ts` |
| 65 | +- `composition/src/v1/normalization/utils.ts` |
| 66 | + |
| 67 | +What happens here: |
| 68 | + |
| 69 | +- Parse and validate SDL and directives. |
| 70 | +- Build internal type/field model (`ParentDefinitionData`, `FieldData`, etc.). |
| 71 | +- Validate federation directives (`@key`, `@requires`, `@provides`, `@override`, etc.). |
| 72 | +- Build type-level router configuration (`ConfigurationData`). |
| 73 | +- Record graph edges for later resolvability checks. |
| 74 | + |
| 75 | +### Stage 2: Batch normalization |
| 76 | + |
| 77 | +Main file: |
| 78 | + |
| 79 | +- `composition/src/v1/normalization/normalization-factory.ts` (`batchNormalize`) |
| 80 | + |
| 81 | +What happens here: |
| 82 | + |
| 83 | +- Validate subgraph naming/uniqueness. |
| 84 | +- Normalize each subgraph into shared structures. |
| 85 | +- Merge cross-subgraph metadata (entities, auth, overrides, coordinates). |
| 86 | +- Produce `internalSubgraphBySubgraphName` used by federation. |
| 87 | + |
| 88 | +### Stage 3: Federation merge |
| 89 | + |
| 90 | +Main file: |
| 91 | + |
| 92 | +- `composition/src/v1/federation/federation-factory.ts` |
| 93 | + |
| 94 | +What happens here: |
| 95 | + |
| 96 | +- Merge all normalized types and fields. |
| 97 | +- Reconcile incompatible shapes and directive usage. |
| 98 | +- Build router/client schema definitions. |
| 99 | +- Build final field configuration output. |
| 100 | + |
| 101 | +### Stage 4: Resolvability validation |
| 102 | + |
| 103 | +Main files: |
| 104 | + |
| 105 | +- `composition/src/resolvability-graph/graph.ts` |
| 106 | +- `composition/src/resolvability-graph/graph-nodes.ts` |
| 107 | + |
| 108 | +What happens here: |
| 109 | + |
| 110 | +- Validate that required field paths are actually reachable. |
| 111 | +- Return errors if fields cannot be resolved across subgraphs. |
| 112 | + |
| 113 | +## 4. Request flow from public API |
| 114 | + |
| 115 | +Top-level function: |
| 116 | + |
| 117 | +- `composition/src/federation/federation.ts` |
| 118 | + |
| 119 | +Current compatibility: |
| 120 | + |
| 121 | +- only version `"1"` is supported (`composition/src/router-compatibility-version/router-compatibility-version.ts`). |
| 122 | + |
| 123 | +Effective flow: |
| 124 | + |
| 125 | +1. `federateSubgraphs(...)` |
| 126 | +2. `initializeFederationFactory(...)` |
| 127 | +3. `batchNormalize(...)` |
| 128 | +4. `FederationFactory.federateSubgraphData()` |
| 129 | +5. `FederationFactory.buildFederationResult()` |
| 130 | + |
| 131 | +## 5. Core data structures (quick glossary) |
| 132 | + |
| 133 | +Result types: |
| 134 | + |
| 135 | +- `composition/src/federation/types.ts` |
| 136 | +- `composition/src/normalization/types.ts` |
| 137 | +- `composition/src/subgraph/types.ts` |
| 138 | + |
| 139 | +Most important internals: |
| 140 | + |
| 141 | +- `InternalSubgraph`: normalized per-subgraph state fed into federation. |
| 142 | +- `ParentDefinitionData`: canonical representation of a type in normalization/federation. |
| 143 | +- `PersistedDirectiveDefinitionData`: executable directive definition info considered for federated schema persistence. |
| 144 | +- `ConfigurationData`: per-type router configuration source. |
| 145 | + |
| 146 | +## 6. Directives: how the system is designed |
| 147 | + |
| 148 | +There are two broad categories: |
| 149 | + |
| 150 | +1. Built-in directives with composition semantics. |
| 151 | +2. Custom executable directives that can be validated and persisted. |
| 152 | + |
| 153 | +### Built-in directives |
| 154 | + |
| 155 | +Definition and registration: |
| 156 | + |
| 157 | +- `composition/src/v1/constants/directive-definitions.ts` |
| 158 | +- `composition/src/v1/constants/constants.ts` |
| 159 | +- `composition/src/v1/constants/strings.ts` |
| 160 | +- `composition/src/utils/string-constants.ts` |
| 161 | + |
| 162 | +Normalization uses a directive registry (`directiveDefinitionDataByName`) to validate use sites and arguments. |
| 163 | + |
| 164 | +### Custom directives |
| 165 | + |
| 166 | +During normalization, unknown directive definitions are treated as custom: |
| 167 | + |
| 168 | +- parsed and validated, |
| 169 | +- added to normalized directive maps, |
| 170 | +- included in normalized subgraph AST. |
| 171 | + |
| 172 | +Key implementation points: |
| 173 | + |
| 174 | +- `upsertDirectiveSchemaAndEntityDefinitions(...)` |
| 175 | +- `addDirectiveDefinitionDataByNode(...)` |
| 176 | + |
| 177 | +Both are in: |
| 178 | + |
| 179 | +- `composition/src/v1/normalization/normalization-factory.ts` |
| 180 | +- `composition/src/v1/normalization/walkers.ts` |
| 181 | + |
| 182 | +### Directive persistence across subgraphs |
| 183 | + |
| 184 | +During federation, executable directive definitions are only persisted when compatible across subgraphs. |
| 185 | + |
| 186 | +Compatibility rules (important): |
| 187 | + |
| 188 | +- The definition must exist consistently across participating subgraphs. |
| 189 | +- Executable locations are intersected (must remain non-empty). |
| 190 | +- Argument definitions must merge cleanly. |
| 191 | +- Repeatability must remain compatible. |
| 192 | + |
| 193 | +Core methods: |
| 194 | + |
| 195 | +- `upsertPersistedDirectiveDefinitionData(...)` |
| 196 | +- `addValidPersistedDirectiveDefinitionNodeByData(...)` |
| 197 | + |
| 198 | +Files: |
| 199 | + |
| 200 | +- `composition/src/v1/federation/federation-factory.ts` |
| 201 | +- `composition/src/schema-building/utils.ts` |
| 202 | + |
| 203 | +### Caveat: `@composeDirective` |
| 204 | + |
| 205 | +`@composeDirective` is declared but currently unimplemented in behavior. |
| 206 | + |
| 207 | +Reference: |
| 208 | + |
| 209 | +- `composition/src/v1/constants/directive-definitions.ts` |
| 210 | + |
| 211 | +Treat it as not available for feature design right now. |
| 212 | + |
| 213 | +## 7. Shipping a custom directive (practical playbook) |
| 214 | + |
| 215 | +If you want a custom executable directive to survive composition: |
| 216 | + |
| 217 | +1. Define the directive in every relevant subgraph SDL. |
| 218 | +2. Keep argument names/types/defaults compatible. |
| 219 | +3. Keep executable locations compatible. |
| 220 | +4. Use it where needed. |
| 221 | +5. Compose and verify in the federated output. |
| 222 | + |
| 223 | +Validation checklist: |
| 224 | + |
| 225 | +- Definition exists in federated schema. |
| 226 | +- Usages appear on expected nodes. |
| 227 | +- No invalid repeatability errors. |
| 228 | +- No cross-subgraph definition merge errors. |
| 229 | + |
| 230 | +Best tests to copy: |
| 231 | + |
| 232 | +- `composition/tests/v1/directives/directives.test.ts` |
| 233 | +- `composition/tests/v1/normalization.test.ts` |
| 234 | + |
| 235 | +## 8. Adding a new built-in directive (when persistence is not enough) |
| 236 | + |
| 237 | +Use this path if you need new behavior (not just persistence), for example router config generation. |
| 238 | + |
| 239 | +Implementation sequence: |
| 240 | + |
| 241 | +1. Add directive name constants. |
| 242 | +2. Add canonical directive definition node. |
| 243 | +3. Add directive definition data (arg/location validation model). |
| 244 | +4. Register in directive maps. |
| 245 | +5. Add validation/extraction behavior in normalization. |
| 246 | +6. Add federation persistence/merge behavior. |
| 247 | +7. Add dependencies for helper scalars/inputs if needed. |
| 248 | +8. Add tests (invalid, valid, merge, config impact). |
| 249 | + |
| 250 | +Files usually touched: |
| 251 | + |
| 252 | +- `composition/src/utils/string-constants.ts` |
| 253 | +- `composition/src/v1/constants/directive-definitions.ts` |
| 254 | +- `composition/src/v1/normalization/directive-definition-data.ts` |
| 255 | +- `composition/src/v1/constants/constants.ts` |
| 256 | +- `composition/src/v1/constants/strings.ts` |
| 257 | +- `composition/src/v1/normalization/normalization-factory.ts` |
| 258 | +- `composition/src/v1/federation/federation-factory.ts` |
| 259 | +- `composition/tests/v1/directives/*.test.ts` |
| 260 | + |
| 261 | +## 9. Contracts in one minute |
| 262 | + |
| 263 | +`federateSubgraphsWithContracts(...)` flow: |
| 264 | + |
| 265 | +1. Build the base federated graph. |
| 266 | +2. Clone federation state per contract. |
| 267 | +3. Apply tag include/exclude options per contract. |
| 268 | +4. Return one result per contract. |
| 269 | + |
| 270 | +If base federation fails, contracts are not attempted. |
| 271 | + |
| 272 | +Main implementation: |
| 273 | + |
| 274 | +- `composition/src/v1/federation/federation-factory.ts` |
| 275 | + |
| 276 | +## 10. Debugging workflow for beginners |
| 277 | + |
| 278 | +When composition fails: |
| 279 | + |
| 280 | +1. Start at API entry: |
| 281 | + |
| 282 | +- `composition/src/federation/federation.ts` |
| 283 | + |
| 284 | +2. Reproduce with minimal SDL in tests: |
| 285 | + |
| 286 | +- `composition/tests/v1/directives/*` |
| 287 | + |
| 288 | +3. Inspect normalization output first: |
| 289 | + |
| 290 | +- `directiveDefinitionByName` |
| 291 | +- `parentDefinitionDataByTypeName` |
| 292 | +- `configurationDataByTypeName` |
| 293 | + |
| 294 | +4. Inspect federation state second: |
| 295 | + |
| 296 | +- `parentDefinitionDataByTypeName` |
| 297 | +- `potentialPersistedDirectiveDefinitionDataByDirectiveName` |
| 298 | +- `referencedPersistedDirectiveNames` |
| 299 | +- `errors` / `warnings` |
| 300 | + |
| 301 | +5. If merge looks fine but final result fails, inspect resolvability: |
| 302 | + |
| 303 | +- `Graph.validate()` path in `composition/src/resolvability-graph/graph.ts` |
| 304 | + |
| 305 | +## 11. Recommended reading order (first week) |
| 306 | + |
| 307 | +1. `composition/src/federation/federation.ts` |
| 308 | +2. `composition/src/v1/federation/federation-factory.ts` (start from exported functions at the bottom, then move upward) |
| 309 | +3. `composition/src/v1/normalization/normalization-factory.ts` |
| 310 | +4. `composition/src/v1/normalization/walkers.ts` |
| 311 | +5. `composition/src/resolvability-graph/graph.ts` |
| 312 | +6. `composition/tests/v1/directives/*` |
| 313 | + |
| 314 | +## 12. Quick FAQ |
| 315 | + |
| 316 | +### Why do I get normalization errors before federation errors? |
| 317 | + |
| 318 | +Because federation only runs after batch normalization succeeds. Normalization is the gate. |
| 319 | + |
| 320 | +### Why did my custom directive disappear from federated output? |
| 321 | + |
| 322 | +Usually because the definition is incompatible across subgraphs (locations, args, repeatability, or presence). |
| 323 | + |
| 324 | +### Can I rely on `@composeDirective` behavior? |
| 325 | + |
| 326 | +Not yet. It is declared, but composition behavior for it is not implemented. |
0 commit comments