diff --git a/packages/backend-auth/API.md b/packages/backend-auth/API.md index b3cf7a91cd..0361f2b084 100644 --- a/packages/backend-auth/API.md +++ b/packages/backend-auth/API.md @@ -5,6 +5,7 @@ ```ts import { AmazonProviderProps } from '@aws-amplify/auth-construct'; +import { AmplifyStackResources } from '@aws-amplify/plugin-types'; import { AppleProviderProps } from '@aws-amplify/auth-construct'; import { AuthProps } from '@aws-amplify/auth-construct'; import { AuthResources } from '@aws-amplify/plugin-types'; @@ -39,6 +40,7 @@ export type AmplifyAuthProps = Expand; triggers?: Partial>>>; access?: AuthAccessGenerator; + scope?: ConstructFactory>; }>; // @public diff --git a/packages/backend-auth/src/factory.ts b/packages/backend-auth/src/factory.ts index 6accb92016..3df21eb9b6 100644 --- a/packages/backend-auth/src/factory.ts +++ b/packages/backend-auth/src/factory.ts @@ -8,6 +8,7 @@ import { TriggerEvent, } from '@aws-amplify/auth-construct'; import { + AmplifyStackResources, AuthResources, AuthRoleName, ConstructContainerEntryGenerator, @@ -58,6 +59,8 @@ export type AmplifyAuthProps = Expand< * access: (allow) => [allow.resource(groupManager).to(["manageGroups"])] */ access?: AuthAccessGenerator; + + scope?: ConstructFactory>; } >; @@ -110,7 +113,10 @@ export class AmplifyAuthFactory implements ConstructFactory { if (!this.generator) { this.generator = new AmplifyAuthGenerator(this.props, getInstanceProps); } - return constructContainer.getOrCompute(this.generator) as BackendAuth; + return constructContainer.getOrCompute( + this.generator, + this.props.scope?.getInstance(getInstanceProps).resources.stack + ) as BackendAuth; }; } diff --git a/packages/backend-data/API.md b/packages/backend-data/API.md index 56e54db507..95dac3515a 100644 --- a/packages/backend-data/API.md +++ b/packages/backend-data/API.md @@ -6,9 +6,11 @@ import { AmplifyData } from '@aws-amplify/data-construct'; import { AmplifyFunction } from '@aws-amplify/plugin-types'; +import { AmplifyStackResources } from '@aws-amplify/plugin-types'; import { ConstructFactory } from '@aws-amplify/plugin-types'; import { DerivedCombinedSchema } from '@aws-amplify/data-schema-types'; import { DerivedModelSchema } from '@aws-amplify/data-schema-types'; +import { ResourceProvider } from '@aws-amplify/plugin-types'; // @public export type ApiKeyAuthorizationModeProps = { @@ -30,6 +32,7 @@ export type DataProps = { name?: string; authorizationModes?: AuthorizationModes; functions?: Record>; + scope?: ConstructFactory>; }; // @public diff --git a/packages/backend-data/src/factory.ts b/packages/backend-data/src/factory.ts index c40ad8db70..07a6578328 100644 --- a/packages/backend-data/src/factory.ts +++ b/packages/backend-data/src/factory.ts @@ -106,7 +106,10 @@ export class DataFactory implements ConstructFactory { outputStorageStrategy ); } - return constructContainer.getOrCompute(this.generator) as AmplifyData; + return constructContainer.getOrCompute( + this.generator, + this.props.scope?.getInstance(props).resources.stack + ) as AmplifyData; }; } diff --git a/packages/backend-data/src/types.ts b/packages/backend-data/src/types.ts index d3ecdf884c..026982f264 100644 --- a/packages/backend-data/src/types.ts +++ b/packages/backend-data/src/types.ts @@ -2,7 +2,13 @@ import { DerivedCombinedSchema, DerivedModelSchema, } from '@aws-amplify/data-schema-types'; -import { AmplifyFunction, ConstructFactory } from '@aws-amplify/plugin-types'; +import { + AmplifyFunction, + AmplifyStackResources, + ConstructFactory, + ResourceProvider, +} from '@aws-amplify/plugin-types'; +import { Stack } from 'aws-cdk-lib'; /** * Authorization modes used in by client side Amplify represented in camelCase. @@ -139,6 +145,8 @@ export type DataProps = { * Functions invokable by the API. The specific input type of the function is subject to change or removal. */ functions?: Record>; + + scope?: ConstructFactory>; }; export type AmplifyDataError = diff --git a/packages/backend-function/API.md b/packages/backend-function/API.md index 86a98d7e1e..897ae4b5d8 100644 --- a/packages/backend-function/API.md +++ b/packages/backend-function/API.md @@ -4,6 +4,7 @@ ```ts +import { AmplifyStackResources } from '@aws-amplify/plugin-types'; import { BackendSecret } from '@aws-amplify/plugin-types'; import { ConstructFactory } from '@aws-amplify/plugin-types'; import { FunctionResources } from '@aws-amplify/plugin-types'; @@ -30,6 +31,7 @@ export type FunctionProps = { environment?: Record; runtime?: NodeVersion; schedule?: FunctionSchedule | FunctionSchedule[]; + scope?: ConstructFactory>; }; // @public (undocumented) diff --git a/packages/backend-function/src/factory.ts b/packages/backend-function/src/factory.ts index e32e72cfb6..21620c1553 100644 --- a/packages/backend-function/src/factory.ts +++ b/packages/backend-function/src/factory.ts @@ -1,4 +1,5 @@ import { + AmplifyStackResources, BackendOutputStorageStrategy, BackendSecret, BackendSecretResolver, @@ -124,6 +125,8 @@ export type FunctionProps = { * schedule: "0 9 ? * 2 *" // every Monday at 9am */ schedule?: FunctionSchedule | FunctionSchedule[]; + + scope?: ConstructFactory>; }; /** @@ -142,18 +145,19 @@ class FunctionFactory implements ConstructFactory { /** * Creates an instance of AmplifyFunction within the provided Amplify context */ - getInstance = ({ - constructContainer, - outputStorageStrategy, - resourceNameValidator, - }: ConstructFactoryGetInstanceProps): AmplifyFunction => { + getInstance = ( + getInstanceProps: ConstructFactoryGetInstanceProps + ): AmplifyFunction => { if (!this.generator) { this.generator = new FunctionGenerator( - this.hydrateDefaults(resourceNameValidator), - outputStorageStrategy + this.hydrateDefaults(getInstanceProps.resourceNameValidator), + getInstanceProps.outputStorageStrategy ); } - return constructContainer.getOrCompute(this.generator) as AmplifyFunction; + return getInstanceProps.constructContainer.getOrCompute( + this.generator, + this.props.scope?.getInstance(getInstanceProps).resources.stack + ) as AmplifyFunction; }; private hydrateDefaults = ( @@ -169,6 +173,7 @@ class FunctionFactory implements ConstructFactory { environment: this.props.environment ?? {}, runtime: this.resolveRuntime(), schedule: this.resolveSchedule(), + scope: this.props.scope, }; }; @@ -276,7 +281,8 @@ class FunctionFactory implements ConstructFactory { }; } -type HydratedFunctionProps = Required; +type HydratedFunctionProps = Required> & + Pick; class FunctionGenerator implements ConstructContainerEntryGenerator { readonly resourceGroupName = 'function'; diff --git a/packages/backend/API.md b/packages/backend/API.md index 1cb83ee09f..add497ffd8 100644 --- a/packages/backend/API.md +++ b/packages/backend/API.md @@ -5,6 +5,7 @@ ```ts import { a } from '@aws-amplify/data-schema'; +import { AmplifyStackResources } from '@aws-amplify/plugin-types'; import { AuthCfnResources } from '@aws-amplify/plugin-types'; import { AuthResources } from '@aws-amplify/plugin-types'; import { AuthRoleName } from '@aws-amplify/plugin-types'; @@ -81,6 +82,9 @@ export { defineData } export { defineFunction } +// @public +export const defineStack: (name: string) => ConstructFactory>; + export { defineStorage } export { FunctionResources } diff --git a/packages/backend/src/engine/singleton_construct_container.ts b/packages/backend/src/engine/singleton_construct_container.ts index 580f22e117..9bb364c0ff 100644 --- a/packages/backend/src/engine/singleton_construct_container.ts +++ b/packages/backend/src/engine/singleton_construct_container.ts @@ -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 @@ -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); diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 46adc6515e..012c536009 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -7,6 +7,7 @@ if (isBrowser()) { } export { defineBackend } from './backend_factory.js'; +export { defineStack } from './stack_factory.js'; export * from './backend.js'; export * from './secret.js'; diff --git a/packages/backend/src/stack_factory.ts b/packages/backend/src/stack_factory.ts new file mode 100644 index 0000000000..0d7f234e44 --- /dev/null +++ b/packages/backend/src/stack_factory.ts @@ -0,0 +1,56 @@ +import { + AmplifyStackResources, + ConstructContainerEntryGenerator, + ConstructFactory, + ConstructFactoryGetInstanceProps, + GenerateContainerEntryProps, + ResourceProvider, +} from '@aws-amplify/plugin-types'; +import { Stack } from 'aws-cdk-lib'; + +class StackGenerator + implements ConstructContainerEntryGenerator +{ + readonly resourceGroupName: string; + + constructor(name: string) { + this.resourceGroupName = name; + } + + generateContainerEntry = ({ + scope, + }: GenerateContainerEntryProps): ResourceProvider => { + return { + resources: { + stack: scope as Stack, + }, + }; + }; +} + +class StackFactory + implements ConstructFactory> +{ + private generator: ConstructContainerEntryGenerator; + + constructor(private readonly name: string) {} + + getInstance( + props: ConstructFactoryGetInstanceProps + ): ResourceProvider { + if (!this.generator) { + this.generator = new StackGenerator(this.name); + } + return props.constructContainer.getOrCompute( + this.generator + ) as ResourceProvider; + } +} + +/** + * TODO + */ +export const defineStack = ( + name: string +): ConstructFactory> => + new StackFactory(name); diff --git a/packages/plugin-types/API.md b/packages/plugin-types/API.md index 58b1302c88..67bc3f5d5f 100644 --- a/packages/plugin-types/API.md +++ b/packages/plugin-types/API.md @@ -26,6 +26,11 @@ import { Stack } from 'aws-cdk-lib'; // @public (undocumented) export type AmplifyFunction = ResourceProvider; +// @public (undocumented) +export type AmplifyStackResources = { + readonly stack: Stack; +}; + // @public export type AppId = string; @@ -110,7 +115,7 @@ export type BranchName = string; // @public export type ConstructContainer = { - getOrCompute: (generator: ConstructContainerEntryGenerator) => ResourceProvider; + getOrCompute: (generator: ConstructContainerEntryGenerator, scope?: Stack) => ResourceProvider; registerConstructFactory: (token: string, provider: ConstructFactory) => void; getConstructFactory: (token: string) => ConstructFactory | undefined; }; diff --git a/packages/plugin-types/src/construct_container.ts b/packages/plugin-types/src/construct_container.ts index e5a2921d0d..481582bf3d 100644 --- a/packages/plugin-types/src/construct_container.ts +++ b/packages/plugin-types/src/construct_container.ts @@ -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 */ @@ -34,7 +35,9 @@ export type GenerateContainerEntryProps = { */ export type ConstructContainer = { getOrCompute: ( - generator: ConstructContainerEntryGenerator + generator: ConstructContainerEntryGenerator, + // TODO: are there better ways? + scope?: Stack ) => ResourceProvider; registerConstructFactory: (token: string, provider: ConstructFactory) => void; getConstructFactory: ( diff --git a/packages/plugin-types/src/index.ts b/packages/plugin-types/src/index.ts index 3b8c7bf18e..828c10c026 100644 --- a/packages/plugin-types/src/index.ts +++ b/packages/plugin-types/src/index.ts @@ -1,3 +1,5 @@ +import { Stack } from 'aws-cdk-lib'; + export * from './backend_stack_creator.js'; export * from './backend_stack_resolver.js'; export * from './construct_container.js'; @@ -20,3 +22,7 @@ export * from './deep_partial.js'; export * from './stable_backend_identifiers.js'; export * from './resource_name_validator.js'; export * from './aws_client_provider.js'; + +export type AmplifyStackResources = { + readonly stack: Stack; +}; diff --git a/test-projects/function-stack-1/amplify/data/resource.ts b/test-projects/function-stack-1/amplify/data/resource.ts index 1e61f66ff8..cd0abe5fd4 100644 --- a/test-projects/function-stack-1/amplify/data/resource.ts +++ b/test-projects/function-stack-1/amplify/data/resource.ts @@ -2,10 +2,14 @@ import { a, defineData, defineFunction, + defineStack, type ClientSchema, } from "@aws-amplify/backend"; +const stack = defineStack('awesome-stack'); + const testHandler = defineFunction({ + scope: stack }); const schema = a @@ -28,4 +32,5 @@ export const data = defineData({ defaultAuthorizationMode: "apiKey", apiKeyAuthorizationMode: { expiresInDays: 30 }, }, + scope: stack }); diff --git a/test-projects/function-stack-2/amplify/functions/test-function/resource.ts b/test-projects/function-stack-2/amplify/functions/test-function/resource.ts index 20f4ce356f..c47509314a 100644 --- a/test-projects/function-stack-2/amplify/functions/test-function/resource.ts +++ b/test-projects/function-stack-2/amplify/functions/test-function/resource.ts @@ -1,7 +1,12 @@ -import { defineFunction } from '@aws-amplify/backend'; +import { defineFunction, defineStack } from '@aws-amplify/backend'; -export const testFunction = defineFunction(); +const stack1 = defineStack('awesome-stack1'); +const stack2 = defineStack('awesome-stack2'); + + +export const testFunction = defineFunction({scope: stack1}); export const myApiFunction = defineFunction({ name: 'api-function', + scope: stack2 });