Skip to content

Commit ce0f3e3

Browse files
committed
docs: substantiate PR #3437 analysis with code links, document stackMappings infrastructure
- Add precise GitHub links for every claim about the call chain - Document existing stackMappings feature as the foundation to build on - Trace the full flow: AmplifyGraphqlApiProps → ExecuteTransformConfig → GraphQLTransform → TransformerContext → StackManager.resourceToStackMap - Key insight: we just need to compute stackMappings automatically, zero changes needed to StackManager or any transformer
1 parent 7a3cae9 commit ce0f3e3

1 file changed

Lines changed: 89 additions & 19 deletions

File tree

.agent-docs/ACTIVE_nested-stack-partitioning.md

Lines changed: 89 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Active Work: Nested Stack Partitioning
22

3-
**Status:** Design
4-
**Branch:** `wirej/nested-stack-partitioning`
3+
**Status:** Design
4+
**Branch:** `wirej/nested-stack-partitioning`
55
**Supersedes:** [PR #3437](https://github.com/aws-amplify/amplify-category-api/pull/3437) (external, will not merge)
66

77
## Problem
@@ -10,24 +10,86 @@ Customers with large schemas (100+ types) hit CloudFormation's 1MB template size
1010

1111
## Why PR #3437 Won't Work
1212

13-
PR #3437 adds a `PartitioningNestedStackProvider` that tries to route resources by inspecting the `name` parameter in `provide(scope, name)`. The fatal flaw:
13+
PR #3437 adds a `PartitioningNestedStackProvider` that tries to route resources by inspecting the `name` parameter in `provide(scope, name)`. The fatal flaw is a misunderstanding of how `provide()` is called.
1414

15-
- `StackManager` calls `provide()` once per **stack name** (e.g., `"Todo"`, `"ConnectionStack"`), not once per resource
16-
- `StackManager` caches the returned stack — all resources for that stack name reuse the cached result
17-
- The PR's `categorizeResource()` checks for patterns like `'resolver'`, `'table'`, `'datasource'` — but it receives model names like `"Todo"`, which match none of those patterns
18-
- Everything falls through to `OTHER` category → single stack → no partitioning
15+
### The actual call chain
1916

20-
The unit tests pass because they call `provide()` directly with synthetic names like `"QueryGetTodoResolver"`, which never happen in real usage.
17+
1. Transformers call `stackManager.getScopeFor(resourceId, defaultStackName)` — e.g., `getScopeFor('GetTodoResolver', 'Todo')`
2118

22-
## Correct Integration Point
19+
- [model-resource-generator.ts:135](https://github.com/aws-amplify/amplify-category-api/blob/main/packages/amplify-graphql-model-transformer/src/resources/model-resource-generator.ts#L135): `resolver.setScope(context.stackManager.getScopeFor(query.resolverLogicalId, def!.name.value))`
20+
- [model-resource-generator.ts:169](https://github.com/aws-amplify/amplify-category-api/blob/main/packages/amplify-graphql-model-transformer/src/resources/model-resource-generator.ts#L169): same for mutations
21+
- [model-resource-generator.ts:216](https://github.com/aws-amplify/amplify-category-api/blob/main/packages/amplify-graphql-model-transformer/src/resources/model-resource-generator.ts#L216): same for subscriptions
2322

24-
The partitioning logic must live at the `StackManager` level (`packages/amplify-graphql-transformer-core/src/transformer-context/stack-manager.ts`), not the `NestedStackProvider` level. `StackManager` is where stack assignment decisions are made — it knows about resource IDs and default stack names.
23+
2. `StackManager.getScopeFor` resolves the stack name (usually the model name like `"Todo"`) and **caches** the result:
2524

26-
Key files:
25+
- [stack-manager.ts:38-48](https://github.com/aws-amplify/amplify-category-api/blob/main/packages/amplify-graphql-transformer-core/src/transformer-context/stack-manager.ts#L38-L48): if the stack exists, return cached; otherwise call `createStack` once
2726

28-
- `packages/amplify-graphql-transformer-core/src/transformer-context/stack-manager.ts` — stack assignment logic
29-
- `packages/amplify-graphql-transformer-interfaces/src/nested-stack-provider.ts``NestedStackProvider` type (thin, just creates stacks)
30-
- `packages/amplify-graphql-model-transformer/src/resources/model-resource-generator.ts` — how resolvers get assigned to stacks (via `getScopeFor(resolverLogicalId, modelName)`)
27+
3. `createStack` calls `nestedStackProvider.provide(this.scope, stackName)` **once per unique stack name**, then caches:
28+
- [stack-manager.ts:24-28](https://github.com/aws-amplify/amplify-category-api/blob/main/packages/amplify-graphql-transformer-core/src/transformer-context/stack-manager.ts#L24-L28)
29+
30+
### What `provide()` actually receives
31+
32+
The `name` parameter is a **stack name**, not a resource name:
33+
34+
- Model stacks: `"Todo"`, `"Note"`, `"Comment"` (from `def!.name.value`)
35+
- Relational resolvers: `"ConnectionStack"` ([ddb-generator.ts:47](https://github.com/aws-amplify/amplify-category-api/blob/main/packages/amplify-graphql-relational-transformer/src/resolver/ddb-generator.ts#L47))
36+
- Function resolvers: `"FunctionDirectiveStack"` ([graphql-function-transformer.ts:39](https://github.com/aws-amplify/amplify-category-api/blob/main/packages/amplify-graphql-function-transformer/src/graphql-function-transformer.ts#L39))
37+
- SQL resolvers: `"CustomSQLStack"` ([graphql-sql-transformer.ts:41](https://github.com/aws-amplify/amplify-category-api/blob/main/packages/amplify-graphql-sql-transformer/src/graphql-sql-transformer.ts#L41))
38+
- Table manager: `"AmplifyTableManager"` ([amplify-dynamo-model-resource-generator.ts:20](https://github.com/aws-amplify/amplify-category-api/blob/main/packages/amplify-graphql-model-transformer/src/resources/amplify-dynamodb-table/amplify-dynamo-model-resource-generator.ts#L20))
39+
40+
### Why the PR's categorization fails
41+
42+
The PR's `categorizeResource()` checks for patterns like `'resolver'`, `'table'`, `'datasource'`, `'graphqlapi'` — but it receives model names like `"Todo"`, which match **none** of those patterns. Everything falls through to `ResourceCategory.OTHER` → single `"DataOther"` stack → no partitioning.
43+
44+
The one accidental match: `"FunctionDirectiveStack"` contains `"function"` and would be categorized as `RESOLVERS`, but that stack also contains Lambda data sources, IAM roles, and conditions — not just resolvers.
45+
46+
### Why the PR's tests pass anyway
47+
48+
The unit tests call `provide()` directly with synthetic names like `"QueryGetTodoResolver"` and `"TodoTable"` — names that never occur in real usage. The integration tests create real `AmplifyGraphqlApi` instances but only assert on nested stack counts, which may pass for the wrong reasons.
49+
50+
## Existing Infrastructure: `stackMappings`
51+
52+
There is already a `stackMappings` feature that does exactly the resource-to-stack routing we need. It's the right foundation to build on.
53+
54+
### How it works today
55+
56+
`stackMappings` is a `Record<string, string>` mapping `{ resolverLogicalId: stackName }`. It's a **manual** override — users specify which resolvers go to which stacks.
57+
58+
**User-facing prop:**
59+
60+
- [types.ts:836-842](https://github.com/aws-amplify/amplify-category-api/blob/main/packages/amplify-graphql-api-construct/src/types.ts#L836-L842): `readonly stackMappings?: Record<string, string>` on `AmplifyGraphqlApiProps`
61+
- JSDoc warns: "after initial deployment AppSync resolvers cannot be moved between nested stacks, they will need to be removed from the app, then re-added from a new stack"
62+
63+
**Flow through the system:**
64+
65+
1. [amplify-graphql-api.ts:237](https://github.com/aws-amplify/amplify-category-api/blob/main/packages/amplify-graphql-api-construct/src/amplify-graphql-api.ts#L237): `stackMapping: stackMappings ?? {}` passed to `ExecuteTransformConfig`
66+
2. [graphql-transformer.ts:111-117](https://github.com/aws-amplify/amplify-category-api/blob/main/packages/amplify-graphql-transformer/src/graphql-transformer.ts#L111-L117): destructured and passed to `GraphQLTransform` constructor
67+
3. [transform.ts:145](https://github.com/aws-amplify/amplify-category-api/blob/main/packages/amplify-graphql-transformer-core/src/transformation/transform.ts#L145): stored as `this.stackMappingOverrides`
68+
4. [transform.ts:217](https://github.com/aws-amplify/amplify-category-api/blob/main/packages/amplify-graphql-transformer-core/src/transformation/transform.ts#L217): passed to `TransformerContext`
69+
5. [index.ts:142](https://github.com/aws-amplify/amplify-category-api/blob/main/packages/amplify-graphql-transformer-core/src/transformer-context/index.ts#L142): `new StackManager(scope, nestedStackProvider, parameterProvider, stackMapping)`
70+
6. [stack-manager.ts:21](https://github.com/aws-amplify/amplify-category-api/blob/main/packages/amplify-graphql-transformer-core/src/transformer-context/stack-manager.ts#L21): stored as `this.resourceToStackMap`
71+
7. [stack-manager.ts:39](https://github.com/aws-amplify/amplify-category-api/blob/main/packages/amplify-graphql-transformer-core/src/transformer-context/stack-manager.ts#L39): **checked first** in `getScopeFor` — if a resource has a mapping, it overrides the default stack
72+
73+
**Resolver logical ID format** (the keys for `stackMappings`):
74+
75+
- [ResolverResourceIDs.ts:4-22](https://github.com/aws-amplify/amplify-category-api/blob/main/packages/graphql-transformer-common/src/ResolverResourceIDs.ts#L4-L22): `Create${typeName}Resolver`, `Update${typeName}Resolver`, `Delete${typeName}Resolver`, `Get${typeName}Resolver`, `List${typeName}Resolver`
76+
- Example: for a `Todo` model → `GetTodoResolver`, `ListTodoResolver`, `CreateTodoResolver`, `UpdateTodoResolver`, `DeleteTodoResolver`
77+
78+
**Existing e2e test:**
79+
80+
- [index-with-stack-mappings.test.ts](https://github.com/aws-amplify/amplify-category-api/blob/main/packages/amplify-e2e-tests/src/__tests__/graphql-v2/index-with-stack-mappings.test.ts): tests moving index resolvers to a `MappedResolvers` stack, deploys, and validates queries still work
81+
82+
### Why this is the right foundation
83+
84+
The `stackMappings` mechanism already:
85+
86+
- Routes individual resolvers to named stacks via `resourceToStackMap`
87+
- Is checked **first** in `getScopeFor` (overrides the default model-name stack)
88+
- Creates stacks lazily on first use
89+
- Has an existing e2e test proving resolvers work from non-default stacks
90+
- Doesn't touch tables or data sources (they stay in their default stacks)
91+
92+
**Our job is to compute the `stackMappings` automatically** instead of requiring users to specify them manually. The partitioning logic generates a `Record<string, string>` and passes it as `stackMappings`. No changes needed to `StackManager`, `NestedStackProvider`, or any transformer.
3193

3294
## How Stacks Are Assigned Today
3395

@@ -46,7 +108,14 @@ Key files:
46108

47109
## Design Direction
48110

49-
Split model stacks: keep table + data source in the model's stack, but allow resolvers to overflow into numbered resolver stacks. Use deterministic assignment (e.g., hash of resolver logical ID mod N) rather than sequential bin-packing.
111+
**Compute `stackMappings` automatically.** Given the schema, enumerate all resolver logical IDs and assign them to overflow stacks using deterministic hashing. Pass the result as `stackMappings` into the existing pipeline. Tables and data sources stay in their default model stacks untouched.
112+
113+
This means:
114+
115+
- Zero changes to `StackManager` or `NestedStackProvider`
116+
- Zero changes to any transformer
117+
- The only new code is the mapping computation + the opt-in prop on `AmplifyGraphqlApiProps`
118+
- We build on a mechanism that already has e2e test coverage
50119

51120
## E2E Test Plan: Data Loss & Migration Safety
52121

@@ -133,12 +202,13 @@ CDK context keys (e.g., `amplify-data-max-resolvers-per-stack`) would let e2e te
133202
- [x] Analyzed PR #3437 and identified architectural issues
134203
- [x] Mapped the real call flow through StackManager → NestedStackProvider
135204
- [x] Identified all stack assignment patterns across transformers
205+
- [x] Found existing `stackMappings` infrastructure to build on
136206

137207
## What's Next
138208

139-
- [ ] Design the StackManager-level partitioning approach
140-
- [ ] Prototype in `stack-manager.ts`
141-
- [ ] Unit tests against real transformer flow (not synthetic `provide()` calls)
209+
- [ ] Design the automatic `stackMappings` computation (deterministic hash of resolver logical ID → overflow stack name)
210+
- [ ] Implement in `amplify-graphql-api-construct` (compute mapping, pass as `stackMappings`)
211+
- [ ] Unit tests against real transformer flow
142212
- [ ] Integration tests with actual large schemas
143-
- [ ] E2E tests with deployment
213+
- [ ] E2E tests per the plan above
144214
- [ ] Migration safety testing (enable → deploy → disable → deploy)

0 commit comments

Comments
 (0)