You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
- 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
Copy file name to clipboardExpand all lines: .agent-docs/ACTIVE_nested-stack-partitioning.md
+89-19Lines changed: 89 additions & 19 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,7 +1,7 @@
1
1
# Active Work: Nested Stack Partitioning
2
2
3
-
**Status:** Design
4
-
**Branch:**`wirej/nested-stack-partitioning`
3
+
**Status:** Design
4
+
**Branch:**`wirej/nested-stack-partitioning`
5
5
**Supersedes:**[PR #3437](https://github.com/aws-amplify/amplify-category-api/pull/3437) (external, will not merge)
6
6
7
7
## Problem
@@ -10,24 +10,86 @@ Customers with large schemas (100+ types) hit CloudFormation's 1MB template size
10
10
11
11
## Why PR #3437 Won't Work
12
12
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.
14
14
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
19
16
20
-
The unit tests pass because they call `provide()`directly with synthetic names like `"QueryGetTodoResolver"`, which never happen in real usage.
-[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
23
22
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:
25
24
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
-`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:
- 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))
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`
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`):
- 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.
31
93
32
94
## How Stacks Are Assigned Today
33
95
@@ -46,7 +108,14 @@ Key files:
46
108
47
109
## Design Direction
48
110
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
50
119
51
120
## E2E Test Plan: Data Loss & Migration Safety
52
121
@@ -133,12 +202,13 @@ CDK context keys (e.g., `amplify-data-max-resolvers-per-stack`) would let e2e te
133
202
-[x] Analyzed PR #3437 and identified architectural issues
134
203
-[x] Mapped the real call flow through StackManager → NestedStackProvider
135
204
-[x] Identified all stack assignment patterns across transformers
205
+
-[x] Found existing `stackMappings` infrastructure to build on
136
206
137
207
## What's Next
138
208
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`)
0 commit comments