Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[POC] Introduce new stack structure #2157

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions packages/backend-ai/src/conversation/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
import path from 'path';
import { CallerDirectoryExtractor } from '@aws-amplify/platform-core';
import { AiModel } from '@aws-amplify/data-schema-types';
import { Stack } from 'aws-cdk-lib';

class ConversationHandlerFunctionGenerator
implements ConstructContainerEntryGenerator
Expand Down Expand Up @@ -72,11 +73,14 @@ class DefaultConversationHandlerFunctionFactory
private readonly callerStack: string | undefined
) {}

getInstance = ({
constructContainer,
outputStorageStrategy,
resourceNameValidator,
}: ConstructFactoryGetInstanceProps): ConversationHandlerFunction => {
getInstance = (
{
constructContainer,
outputStorageStrategy,
resourceNameValidator,
}: ConstructFactoryGetInstanceProps,
stack?: Stack
): ConversationHandlerFunction => {
resourceNameValidator?.validate(this.props.name);
if (!this.generator) {
const props = { ...this.props };
Expand All @@ -87,7 +91,8 @@ class DefaultConversationHandlerFunctionFactory
);
}
return constructContainer.getOrCompute(
this.generator
this.generator,
stack
) as ConversationHandlerFunction;
};

Expand Down
18 changes: 14 additions & 4 deletions packages/backend-auth/src/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ export class AmplifyAuthFactory implements ConstructFactory<BackendAuth> {
* Get a singleton instance of AmplifyAuth
*/
getInstance = (
getInstanceProps: ConstructFactoryGetInstanceProps
getInstanceProps: ConstructFactoryGetInstanceProps,
stack?: Stack
): BackendAuth => {
const { constructContainer, importPathVerifier, resourceNameValidator } =
getInstanceProps;
Expand All @@ -111,9 +112,16 @@ export class AmplifyAuthFactory implements ConstructFactory<BackendAuth> {
resourceNameValidator?.validate(this.props.name);
}
if (!this.generator) {
this.generator = new AmplifyAuthGenerator(this.props, getInstanceProps);
this.generator = new AmplifyAuthGenerator(
this.props,
getInstanceProps,
stack
);
}
return constructContainer.getOrCompute(this.generator) as BackendAuth;
return constructContainer.getOrCompute(
this.generator,
stack
) as BackendAuth;
};
}

Expand All @@ -124,6 +132,7 @@ class AmplifyAuthGenerator implements ConstructContainerEntryGenerator {
constructor(
private readonly props: AmplifyAuthProps,
private readonly getInstanceProps: ConstructFactoryGetInstanceProps,
private readonly stack?: Stack,
private readonly authAccessBuilder = _authAccessBuilder,
private readonly authAccessPolicyArbiterFactory = new AuthAccessPolicyArbiterFactory()
) {
Expand Down Expand Up @@ -169,7 +178,8 @@ class AmplifyAuthGenerator implements ConstructContainerEntryGenerator {
([triggerEvent, handlerFactory]) => {
(authConstruct.resources.userPool as UserPool).addTrigger(
UserPoolOperation.of(triggerEvent),
handlerFactory.getInstance(this.getInstanceProps).resources.lambda
handlerFactory.getInstance(this.getInstanceProps, this.stack)
.resources.lambda
);
}
);
Expand Down
12 changes: 9 additions & 3 deletions packages/backend-data/src/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import {
CDKContextKey,
TagName,
} from '@aws-amplify/platform-core';
import { Aspects, IAspect, Tags } from 'aws-cdk-lib';
import { Aspects, IAspect, Stack, Tags } from 'aws-cdk-lib';
import { convertJsResolverDefinition } from './convert_js_resolvers.js';
import { AppSyncPolicyGenerator } from './app_sync_policy_generator.js';
import {
Expand Down Expand Up @@ -77,7 +77,10 @@ export class DataFactory implements ConstructFactory<AmplifyData> {
/**
* Gets an instance of the Data construct
*/
getInstance = (props: ConstructFactoryGetInstanceProps): AmplifyData => {
getInstance = (
props: ConstructFactoryGetInstanceProps,
stack?: Stack
): AmplifyData => {
const {
constructContainer,
outputStorageStrategy,
Expand Down Expand Up @@ -106,7 +109,10 @@ export class DataFactory implements ConstructFactory<AmplifyData> {
outputStorageStrategy
);
}
return constructContainer.getOrCompute(this.generator) as AmplifyData;
return constructContainer.getOrCompute(
this.generator,
stack
) as AmplifyData;
};
}

Expand Down
18 changes: 12 additions & 6 deletions packages/backend-function/src/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,18 +177,24 @@ class FunctionFactory implements ConstructFactory<AmplifyFunction> {
/**
* Creates an instance of AmplifyFunction within the provided Amplify context
*/
getInstance = ({
constructContainer,
outputStorageStrategy,
resourceNameValidator,
}: ConstructFactoryGetInstanceProps): AmplifyFunction => {
getInstance = (
{
constructContainer,
outputStorageStrategy,
resourceNameValidator,
}: ConstructFactoryGetInstanceProps,
stack?: Stack
): AmplifyFunction => {
if (!this.generator) {
this.generator = new FunctionGenerator(
this.hydrateDefaults(resourceNameValidator),
outputStorageStrategy
);
}
return constructContainer.getOrCompute(this.generator) as AmplifyFunction;
return constructContainer.getOrCompute(
this.generator,
stack
) as AmplifyFunction;
};

private hydrateDefaults = (
Expand Down
6 changes: 4 additions & 2 deletions packages/backend-storage/src/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ export class AmplifyStorageFactory
* Get a singleton instance of the Bucket
*/
getInstance = (
getInstanceProps: ConstructFactoryGetInstanceProps
getInstanceProps: ConstructFactoryGetInstanceProps,
stack?: Stack
): AmplifyStorage => {
const { constructContainer, importPathVerifier, resourceNameValidator } =
getInstanceProps;
Expand All @@ -50,7 +51,8 @@ export class AmplifyStorageFactory
);
}
const amplifyStorage = constructContainer.getOrCompute(
this.generator
this.generator,
stack
) as AmplifyStorage;

/*
Expand Down
18 changes: 18 additions & 0 deletions packages/backend/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ import { Stack } from 'aws-cdk-lib';

export { a }

// @public (undocumented)
export type AmplifyStackBase = {
readonly stack: Stack;
};

// @public (undocumented)
export type AmplifyStackResources = AmplifyStackBase & {
[K in keyof DefineStackProps]: Omit<ReturnType<DefineStackProps[K]['getInstance']>, keyof ResourceAccessAcceptorFactory>;
};
rtpascual marked this conversation as resolved.
Show resolved Hide resolved

export { AuthCfnResources }

export { AuthResources }
Expand Down Expand Up @@ -82,6 +92,14 @@ export { defineData }

export { defineFunction }

// @public
export const defineStack: (name: string, constructFactories: DefineStackProps) => ConstructFactory<ResourceProvider<AmplifyStackResources>>;

// @public (undocumented)
export type DefineStackProps = Record<string, ConstructFactory<ResourceProvider & Partial<ResourceAccessAcceptorFactory<never>>>> & {
[K in keyof AmplifyStackBase]?: never;
};

export { defineStorage }

export { FunctionResources }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { getBackendIdentifier } from '../backend_identifier.js';
import { DefaultBackendSecretResolver } from './backend-secret/backend_secret_resolver.js';
import { BackendIdScopedSsmEnvironmentEntriesGenerator } from './backend_id_scoped_ssm_environment_entries_generator.js';
import { BackendIdScopedStableBackendIdentifiers } from '../backend_id_scoped_stable_backend_identifiers.js';
import { Stack } from 'aws-cdk-lib';

/**
* Serves as a DI container and shared state store for initializing Amplify constructs
Expand All @@ -33,10 +34,13 @@ export class SingletonConstructContainer implements ConstructContainer {
* Otherwise, the generator is called and the value is cached and returned
*/
getOrCompute = (
generator: ConstructContainerEntryGenerator
generator: ConstructContainerEntryGenerator,
scope?: Stack
): ResourceProvider => {
if (!this.providerCache.has(generator)) {
const scope = this.stackResolver.getStackFor(generator.resourceGroupName);
if (!scope) {
scope = this.stackResolver.getStackFor(generator.resourceGroupName);
}
const backendId = getBackendIdentifier(scope);
const ssmEnvironmentEntriesGenerator =
new BackendIdScopedSsmEnvironmentEntriesGenerator(scope, backendId);
Expand Down
3 changes: 3 additions & 0 deletions packages/backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,6 @@ export { defineStorage } from '@aws-amplify/backend-storage';

// function
export { defineFunction } from '@aws-amplify/backend-function';

// stack
export * from './stack_factory.js';
126 changes: 126 additions & 0 deletions packages/backend/src/stack_factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import {
ConstructContainerEntryGenerator,
ConstructFactory,
ConstructFactoryGetInstanceProps,
GenerateContainerEntryProps,
ResourceAccessAcceptorFactory,
ResourceProvider,
} from '@aws-amplify/plugin-types';
import { Stack } from 'aws-cdk-lib';

export type AmplifyStackBase = {
readonly stack: Stack;
};

export type DefineStackProps = Record<
string,
ConstructFactory<
ResourceProvider & Partial<ResourceAccessAcceptorFactory<never>>
>
> & { [K in keyof AmplifyStackBase]?: never };

export type AmplifyStackResources<T extends DefineStackProps> =
AmplifyStackBase & {
[K in keyof T]: Omit<
ReturnType<T[K]['getInstance']>,
keyof ResourceAccessAcceptorFactory
>;
};

class StackGenerator<
T extends Record<string, ConstructFactory<ResourceProvider>>
> implements ConstructContainerEntryGenerator<AmplifyStackResources<T>>
{
readonly resourceGroupName: string;

constructor(
name: string,
readonly getInstanceProps: ConstructFactoryGetInstanceProps,
readonly constructFactories: T
) {
this.resourceGroupName = name;
}

generateContainerEntry = ({
scope,
}: GenerateContainerEntryProps): ResourceProvider<
AmplifyStackResources<T>
> => {
// register providers but don't actually execute anything yet
Object.values(this.constructFactories).forEach((factory) => {
if (typeof factory.provides === 'string') {
this.getInstanceProps.constructContainer.registerConstructFactory(
factory.provides,
factory
);
}
});
// now invoke all the factories and collect the constructs into nestedResources
const nestedResources: {
[K in keyof Record<
string,
ConstructFactory<ResourceProvider>
>]: ReturnType<
Record<string, ConstructFactory<ResourceProvider>>[K]['getInstance']
>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} = {} as any;
Object.entries(this.constructFactories).forEach(
([resourceName, constructFactory]) => {
// The type inference on this.resources is not happy about this assignment because it doesn't know the exact type of .getInstance()
// However, the assignment is okay because we are iterating over the entries of constructFactories and assigning the resource name to the corresponding instance
nestedResources[
resourceName as keyof Record<
string,
ConstructFactory<ResourceProvider>
>
] = constructFactory.getInstance(
this.getInstanceProps,
scope as Stack
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) as any;
}
);
return {
resources: {
...nestedResources,
stack: scope as Stack,
} as AmplifyStackResources<T>,
};
};
}

class StackFactory<T extends DefineStackProps>
implements ConstructFactory<ResourceProvider<AmplifyStackResources<T>>>
{
private generator: ConstructContainerEntryGenerator;

constructor(
private readonly name: string,
private readonly constructFactories: T
) {}

getInstance(
props: ConstructFactoryGetInstanceProps
): ResourceProvider<AmplifyStackResources<T>> {
if (!this.generator) {
this.generator = new StackGenerator(
this.name,
props,
this.constructFactories
);
}
return props.constructContainer.getOrCompute(
this.generator
) as ResourceProvider<AmplifyStackResources<T>>;
}
}

/**
* TODO
*/
export const defineStack = <T extends DefineStackProps>(
name: string,
constructFactories: T
): ConstructFactory<ResourceProvider<AmplifyStackResources<T>>> =>
new StackFactory(name, constructFactories);
4 changes: 2 additions & 2 deletions packages/plugin-types/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export type BranchName = string;

// @public
export type ConstructContainer = {
getOrCompute: (generator: ConstructContainerEntryGenerator) => ResourceProvider;
getOrCompute: (generator: ConstructContainerEntryGenerator, stack?: Stack) => ResourceProvider;
registerConstructFactory: (token: string, provider: ConstructFactory) => void;
getConstructFactory: <T extends ResourceProvider>(token: string) => ConstructFactory<T> | undefined;
};
Expand All @@ -124,7 +124,7 @@ export type ConstructContainerEntryGenerator<T extends object = object> = {
// @public
export type ConstructFactory<T extends ResourceProvider = ResourceProvider> = {
readonly provides?: string;
getInstance: (props: ConstructFactoryGetInstanceProps) => T;
getInstance: (props: ConstructFactoryGetInstanceProps, stack?: Stack) => T;
};

// @public (undocumented)
Expand Down
4 changes: 3 additions & 1 deletion packages/plugin-types/src/construct_container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { BackendSecretResolver } from './backend_secret_resolver.js';
import { ResourceProvider } from './resource_provider.js';
import { SsmEnvironmentEntriesGenerator } from './ssm_environment_entries_generator.js';
import { StableBackendIdentifiers } from './stable_backend_identifiers.js';
import { Stack } from 'aws-cdk-lib';
/**
* Initializes a CDK Construct in a given scope
*/
Expand Down Expand Up @@ -34,7 +35,8 @@ export type GenerateContainerEntryProps = {
*/
export type ConstructContainer = {
getOrCompute: (
generator: ConstructContainerEntryGenerator
generator: ConstructContainerEntryGenerator,
stack?: Stack
) => ResourceProvider;
registerConstructFactory: (token: string, provider: ConstructFactory) => void;
getConstructFactory: <T extends ResourceProvider>(
Expand Down
Loading
Loading