diff --git a/playground/TypeScriptAppHost/apphost.ts b/playground/TypeScriptAppHost/apphost.ts
index b169215d431..b7fa2419bda 100644
--- a/playground/TypeScriptAppHost/apphost.ts
+++ b/playground/TypeScriptAppHost/apphost.ts
@@ -10,7 +10,7 @@ console.log("Aspire TypeScript AppHost starting...\n");
// Create the distributed application builder
const builder = await createBuilder();
-var ec = await builder.executionContext.get();
+const ec = await builder.executionContext.get();
const isPublishMode = await ec.isPublishMode.get();
console.log(`isRunMode: ${await ec.isRunMode.get()}`);
@@ -19,20 +19,18 @@ console.log(`isPublishMode: ${isPublishMode}`);
// Add Docker Compose environment for publishing
await builder.addDockerComposeEnvironment("compose");
-var dir = await builder.appHostDirectory.get();
+const dir = await builder.appHostDirectory.get();
console.log(`AppHost directory: ${dir}`);
// Add PostgreSQL server and database
-const postgres = await builder.addPostgres("postgres");
-const db = await postgres.addDatabase("db");
-const localCancellation = new AbortController();
-const dbUriExpression = await db.uriExpression.get();
-const _dbUri = await dbUriExpression.getValue(localCancellation.signal);
+const postgres = builder.addPostgres("postgres");
+const db = postgres.addDatabase("db");
console.log("Added PostgreSQL server with database 'db'");
// Add Express API that connects to PostgreSQL (uses npm run dev with tsx)
-const api = await builder
+// No await needed — withReference/waitFor accept promises directly
+const api = builder
.addNodeApp("api", "./express-api", "src/server.ts")
.withRunScript("dev")
.withHttpEndpoint({ env: "PORT" })
@@ -41,26 +39,26 @@ const api = await builder
console.log("Added Express API with reference to PostgreSQL database");
-// Also keep Redis as an example of another service with persistent lifetime
-const cache = await builder
+// Redis
+builder
.addRedis("cache")
.withLifetime(ContainerLifetime.Persistent);
console.log("Added Redis cache");
-// Add Vite frontend that connects to the API using the unified reference API
-await builder
+// Vite frontend — withReference/waitFor accept the un-awaited 'api' promise
+builder
.addViteApp("frontend", "./vite-frontend")
.withReference(api)
.waitFor(api)
.withEnvironment("CUSTOM_ENV", "value")
.withEnvironmentCallback(async (ctx: EnvironmentCallbackContext) => {
- // Custom environment callback logic
+ // await needed here because getEndpoint returns a value we use
var ep = await api.getEndpoint("http");
-
await ctx.environmentVariables.set("API_ENDPOINT", refExpr`${ep}`);
});
console.log("Added Vite frontend with reference to API");
+// build() flushes all pending promises before running
await builder.build().run();
diff --git a/src/Aspire.Hosting.CodeGeneration.TypeScript/AtsTypeScriptCodeGenerator.cs b/src/Aspire.Hosting.CodeGeneration.TypeScript/AtsTypeScriptCodeGenerator.cs
index eee09d3b7bb..f3e2a280b59 100644
--- a/src/Aspire.Hosting.CodeGeneration.TypeScript/AtsTypeScriptCodeGenerator.cs
+++ b/src/Aspire.Hosting.CodeGeneration.TypeScript/AtsTypeScriptCodeGenerator.cs
@@ -267,6 +267,23 @@ private static string GetDtoInterfaceName(string typeId)
/// For interface handle types, generated APIs accept any handle-bearing wrapper instance.
/// For cancellation tokens, generated APIs accept either an AbortSignal or a transport-safe CancellationToken.
///
+ ///
+ /// Handle types are widened to accept Awaitable<T> so callers can pass un-awaited
+ /// fluent chains directly. Examples:
+ ///
+ /// // Input: RedisResource handle type
+ /// // Output: "Awaitable<RedisResource>"
+ ///
+ /// // Input: Union of string | RedisResource
+ /// // Output: "string | Awaitable<RedisResource>"
+ ///
+ /// // Input: CancellationToken type
+ /// // Output: "AbortSignal | CancellationToken"
+ ///
+ /// // Input: plain string type
+ /// // Output: "string"
+ ///
+ ///
private string MapInputTypeToTypeScript(AtsTypeRef? typeRef)
{
if (typeRef?.Category == AtsTypeCategory.Union)
@@ -278,10 +295,17 @@ private string MapInputTypeToTypeScript(AtsTypeRef? typeRef)
{
if (TryMapInterfaceInputTypeToTypeScript(typeRef!) is { } interfaceInputType)
{
- return interfaceInputType;
+ return $"Awaitable<{interfaceInputType}>";
}
- return GetHandleReferenceInterfaceName();
+ var handleName = GetHandleReferenceInterfaceName();
+ return $"Awaitable<{handleName}>";
+ }
+
+ if (IsHandleType(typeRef) && _wrapperClassNames.TryGetValue(typeRef!.TypeId, out var className))
+ {
+ var ifaceName = GetInterfaceName(className);
+ return $"Awaitable<{ifaceName}>";
}
if (IsCancellationTokenType(typeRef))
@@ -299,11 +323,42 @@ private string MapInputUnionTypeToTypeScript(AtsTypeRef typeRef)
throw new InvalidOperationException("Union input types must define at least one member type.");
}
- var memberTypes = typeRef.UnionTypes
- .Select(MapInputTypeToTypeScript)
- .Distinct();
+ // Build union structurally: each member is mapped individually.
+ // Handle types become Awaitable, non-handle types pass through as-is.
+ var nonHandleTypes = new List();
+ var handleTypeNames = new List();
- return string.Join(" | ", memberTypes);
+ foreach (var memberRef in typeRef.UnionTypes)
+ {
+ if (IsWidenedHandleType(memberRef))
+ {
+ // Get the base type name without Awaitable wrapper for combining
+ var baseName = IsInterfaceHandleType(memberRef) && TryMapInterfaceInputTypeToTypeScript(memberRef) is { } expanded
+ ? expanded
+ : MapTypeRefToTypeScript(memberRef);
+ nonHandleTypes.Add(baseName);
+ handleTypeNames.Add(baseName);
+ }
+ else
+ {
+ nonHandleTypes.Add(MapInputTypeToTypeScript(memberRef));
+ }
+ }
+
+ var allBaseTypes = nonHandleTypes
+ .SelectMany(t => t.Split(" | ", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries))
+ .Distinct(StringComparer.Ordinal)
+ .ToList();
+
+ if (handleTypeNames.Count > 0)
+ {
+ var handleUnion = string.Join(" | ", handleTypeNames
+ .SelectMany(t => t.Split(" | ", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries))
+ .Distinct(StringComparer.Ordinal));
+ return string.Join(" | ", allBaseTypes) + $" | Awaitable<{handleUnion}>";
+ }
+
+ return string.Join(" | ", allBaseTypes);
}
///
@@ -316,13 +371,6 @@ private string MapParameterToTypeScript(AtsParameterInfo param)
return GenerateCallbackTypeSignature(param.CallbackParameters, param.CallbackReturnType);
}
- if (param.Type?.Category == AtsTypeCategory.Union && param.Type.UnionTypes is { Count: > 0 })
- {
- return string.Join(" | ", param.Type.UnionTypes
- .SelectMany(t => MapInputTypeToTypeScript(t).Split(" | ", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries))
- .Distinct(StringComparer.Ordinal));
- }
-
return MapInputTypeToTypeScript(param.Type);
}
@@ -424,9 +472,14 @@ private static bool IsInterfaceHandleType(AtsTypeRef? typeRef)
private static bool IsCancellationTokenType(AtsTypeRef? typeRef) => typeRef?.TypeId == AtsConstants.CancellationToken;
private static string GetRpcArgumentValueExpression(string parameterName, AtsTypeRef? typeRef)
- => IsCancellationTokenType(typeRef)
- ? $"CancellationToken.fromValue({parameterName})"
- : parameterName;
+ {
+ if (IsCancellationTokenType(typeRef))
+ {
+ return $"CancellationToken.fromValue({parameterName})";
+ }
+
+ return parameterName;
+ }
private static string GetRpcArgumentEntry(string parameterName, AtsTypeRef? typeRef)
{
@@ -525,7 +578,8 @@ private string GenerateAspireSdk(AtsContext context)
CapabilityError,
registerCallback,
wrapIfHandle,
- registerHandleWrapper
+ registerHandleWrapper,
+ isPromiseLike
} from './transport.js';
import type { AspireClientRpc } from './transport.js';
@@ -538,6 +592,8 @@ private string GenerateAspireSdk(AtsContext context)
AspireDict,
AspireList
} from './base.js';
+
+ import type { Awaitable } from './base.js';
""");
WriteLine();
@@ -799,6 +855,13 @@ private void GenerateDtoInterfaces(IReadOnlyList dtoTypes)
WriteLine($" {propName}?: {tsType};");
}
+ // Add client-only properties that don't exist in the C# DTO
+ if (dto.Name == "CreateBuilderOptions")
+ {
+ WriteLine(" /** When false, pre-flush rejected promises are not re-thrown by build(). Default: true. */");
+ WriteLine(" throwOnPendingRejections?: boolean;");
+ }
+
WriteLine("}");
WriteLine();
}
@@ -1346,6 +1409,35 @@ private void GenerateBuilderClass(BuilderModel builder)
GenerateThenableClass(builder);
}
+ ///
+ /// Generates both an internal async method and a public fluent method for a builder capability.
+ ///
+ ///
+ /// Produces a pair of methods: a private _*Internal method that performs the RPC call,
+ /// and a public method that wraps it in a thenable promise class for fluent chaining.
+ /// Generated TypeScript (example for withEnvironment on RedisResource):
+ ///
+ /// /** @internal */
+ /// private async _withEnvironmentInternal(name: string, value: string): Promise<RedisResource> {
+ /// const rpcArgs: Record<string, unknown> = { builder: this._handle, name, value };
+ /// const result = await this._client.invokeCapability<RedisResourceHandle>('...', rpcArgs);
+ /// return new RedisResourceImpl(result, this._client);
+ /// }
+ ///
+ /// withEnvironment(name: string, value: string): RedisResourcePromise {
+ /// return new RedisResourcePromiseImpl(
+ /// this._withEnvironmentInternal(name, value), this._client);
+ /// }
+ ///
+ /// // For build(), the public wrapper flushes pending promises first:
+ /// build(): DistributedApplicationPromise {
+ /// const flushAndBuild = async () => { await this._client.flushPendingPromises(); return this._buildInternal(); };
+ /// return new DistributedApplicationPromiseImpl(flushAndBuild(), this._client, false);
+ /// }
+ ///
+ /// When a parameter is a handle type, promise resolution is emitted before the RPC args
+ /// (e.g. db = isPromiseLike(db) ? await db : db;).
+ ///
private void GenerateBuilderMethod(BuilderModel builder, AtsCapabilityInfo capability)
{
var methodName = capability.MethodName;
@@ -1419,7 +1511,7 @@ private void GenerateBuilderMethod(BuilderModel builder, AtsCapabilityInfo capab
// Extract optional params from options object
foreach (var param in optionalParams)
{
- WriteLine($" const {param.Name} = options?.{param.Name};");
+ WriteLine($" {(IsWidenedHandleType(param.Type) ? "let" : "const")} {param.Name} = options?.{param.Name};");
}
// Handle callback registration if any
@@ -1429,6 +1521,9 @@ private void GenerateBuilderMethod(BuilderModel builder, AtsCapabilityInfo capab
GenerateCallbackRegistration(callbackParam);
}
+ // Resolve any promise-like handle parameters before building rpcArgs
+ GeneratePromiseResolution(capability.Parameters);
+
// Build args object with conditional inclusion
GenerateArgsObjectWithConditionals(targetParamName, requiredParams, optionalParams);
@@ -1466,6 +1561,9 @@ private void GenerateBuilderMethod(BuilderModel builder, AtsCapabilityInfo capab
GenerateCallbackRegistration(callbackParam);
}
+ // Resolve any promise-like handle parameters before building rpcArgs
+ GeneratePromiseResolution(capability.Parameters);
+
// Build args object with conditional inclusion
GenerateArgsObjectWithConditionals(targetParamName, requiredParams, optionalParams);
@@ -1500,18 +1598,154 @@ private void GenerateBuilderMethod(BuilderModel builder, AtsCapabilityInfo capab
// Extract optional params from options object and forward to internal method
foreach (var param in optionalParams)
{
- WriteLine($" const {param.Name} = options?.{param.Name};");
+ WriteLine($" {(IsWidenedHandleType(param.Type) ? "let" : "const")} {param.Name} = options?.{param.Name};");
}
// Forward all params to internal method
var allParamNames = capability.Parameters.Select(p => p.Name);
- Write($" return new {promiseImplementationClass}(this.{internalMethodName}(");
- Write(string.Join(", ", allParamNames));
- WriteLine("));");
+ var internalCall = $"this.{internalMethodName}({string.Join(", ", allParamNames)})";
+
+ // For build(), flush pending promises before invoking the internal method.
+ // This must happen in the public wrapper (not _buildInternal) to avoid deadlock:
+ // the PromiseImpl constructor tracks the build promise, and if _buildInternal
+ // awaited flushPendingPromises, the flush would re-await the tracked build promise.
+ if (string.Equals(capability.MethodName, "build", StringComparison.OrdinalIgnoreCase))
+ {
+ WriteLine($" const flushAndBuild = async () => {{ await this._client.flushPendingPromises(); return {internalCall}; }};");
+ // Don't track the build promise — it wraps flushPendingPromises which
+ // may throw AggregateError. Tracking it would re-add that error to
+ // _rejectedErrors, poisoning subsequent build() calls.
+ WriteLine($" return new {promiseImplementationClass}(flushAndBuild(), this._client, false);");
+ }
+ else
+ {
+ WriteLine($" return new {promiseImplementationClass}({internalCall}, this._client);");
+ }
WriteLine(" }");
WriteLine();
}
+ ///
+ /// Generates promise resolution code for handle-type parameters that may be PromiseLike.
+ ///
+ ///
+ /// For each parameter whose type is a handle (or union containing handles), emits a line
+ /// that awaits it if it is a PromiseLike. Non-handle and callback parameters are skipped.
+ ///
+ /// // For a handle-type param 'db':
+ /// db = isPromiseLike(db) ? await db : db;
+ ///
+ /// // For a non-handle param 'name' (string): nothing emitted
+ ///
+ ///
+ private void GeneratePromiseResolution(IReadOnlyList parameters, string indent = " ")
+ {
+ foreach (var param in parameters)
+ {
+ if (param.IsCallback)
+ {
+ continue;
+ }
+
+ if (IsWidenedHandleType(param.Type))
+ {
+ WriteLine($"{indent}{param.Name} = isPromiseLike({param.Name}) ? await {param.Name} : {param.Name};");
+ }
+ }
+ }
+
+ ///
+ /// Generates promise resolution for a single named parameter.
+ ///
+ ///
+ /// Used for property setters where only the value parameter needs resolution.
+ ///
+ /// // For a handle-type 'value' parameter:
+ /// value = isPromiseLike(value) ? await value : value;
+ ///
+ ///
+ private void GeneratePromiseResolutionForParam(string paramName, AtsTypeRef? paramType, string indent = " ")
+ {
+ if (IsWidenedHandleType(paramType))
+ {
+ WriteLine($"{indent}{paramName} = isPromiseLike({paramName}) ? await {paramName} : {paramName};");
+ }
+ }
+
+ ///
+ /// Checks if a type was widened to accept Awaitable<T> in input position.
+ /// Must match the widening logic in MapInputTypeToTypeScript exactly.
+ ///
+ private bool IsWidenedHandleType(AtsTypeRef? typeRef)
+ {
+ if (typeRef == null)
+ {
+ return false;
+ }
+
+ // Interface handles are always widened
+ if (IsInterfaceHandleType(typeRef))
+ {
+ return true;
+ }
+
+ // Concrete handles are only widened if they have a wrapper class name
+ // (excludes special types like ReferenceExpression that bypass widening)
+ if (IsHandleType(typeRef) && _wrapperClassNames.ContainsKey(typeRef.TypeId))
+ {
+ return true;
+ }
+
+ if (typeRef.Category == AtsTypeCategory.Union && typeRef.UnionTypes is { Count: > 0 })
+ {
+ return typeRef.UnionTypes.Any(IsWidenedHandleType);
+ }
+
+ return false;
+ }
+
+ ///
+ /// Generates promise resolution and args object construction in one step.
+ /// This is the unified helper used by builder methods, type class methods, context methods, and wrapper methods.
+ ///
+ ///
+ /// Combines with RPC args construction.
+ /// Required parameters are inlined in the object literal; optional parameters
+ /// are added conditionally.
+ ///
+ /// // Example output for a method with required 'name', handle-type 'db', and optional 'timeout':
+ /// db = isPromiseLike(db) ? await db : db;
+ /// const rpcArgs: Record<string, unknown> = { builder: this._handle, name, db };
+ /// if (timeout !== undefined) rpcArgs.timeout = timeout;
+ ///
+ ///
+ private void GenerateResolveAndBuildArgs(
+ string targetParamName,
+ IReadOnlyList allParams,
+ List requiredParams,
+ List optionalParams,
+ string indent = " ")
+ {
+ // Resolve any promise-like handle parameters
+ GeneratePromiseResolution(allParams, indent);
+
+ // Build the required args inline
+ var requiredArgs = new List { $"{targetParamName}: this._handle" };
+ foreach (var param in requiredParams)
+ {
+ requiredArgs.Add(GetRpcArgumentEntry(param));
+ }
+
+ WriteLine($"{indent}const rpcArgs: Record = {{ {string.Join(", ", requiredArgs)} }};");
+
+ // Conditionally add optional params
+ foreach (var param in optionalParams)
+ {
+ var rpcExpression = GetRpcArgumentExpression(param);
+ WriteLine($"{indent}if ({param.Name} !== undefined) rpcArgs.{param.Name} = {rpcExpression};");
+ }
+ }
+
///
/// Generates an args object with conditional inclusion of optional parameters.
///
@@ -1537,6 +1771,28 @@ private void GenerateArgsObjectWithConditionals(
}
}
+ ///
+ /// Generates a thenable wrapper class for a builder that enables fluent chaining.
+ ///
+ ///
+ /// The generated class implements both PromiseLike (via then()) and
+ /// all fluent methods of the builder, forwarding each call through the inner promise.
+ /// Generated TypeScript (example for RedisResource):
+ ///
+ /// class RedisResourcePromiseImpl implements RedisResourcePromise {
+ /// constructor(private _promise: Promise<RedisResource>, private _client: AspireClientRpc) {
+ /// _client.trackPromise(_promise);
+ /// }
+ /// then<T1, T2>(...): PromiseLike<T1 | T2> {
+ /// return this._promise.then(...);
+ /// }
+ /// withEnvironment(name: string, value: string): RedisResourcePromise {
+ /// return new RedisResourcePromiseImpl(
+ /// this._promise.then(obj => obj.withEnvironment(name, value)), this._client);
+ /// }
+ /// }
+ ///
+ ///
private void GenerateThenableClass(BuilderModel builder)
{
var capabilities = builder.Capabilities.Where(c =>
@@ -1557,7 +1813,9 @@ private void GenerateThenableClass(BuilderModel builder)
WriteLine($" * await builder.addSomething().withX().withY();");
WriteLine($" */");
WriteLine($"class {promiseImplementationClass} implements {promiseClass} {{");
- WriteLine($" constructor(private _promise: Promise<{builder.BuilderClassName}>) {{}}");
+ WriteLine($" constructor(private _promise: Promise<{builder.BuilderClassName}>, private _client: AspireClientRpc, track = true) {{");
+ WriteLine($" if (track) {{ _client.trackPromise(_promise); }}");
+ WriteLine($" }}");
WriteLine();
// Generate then() for PromiseLike interface
@@ -1646,7 +1904,7 @@ private void GenerateThenableClass(BuilderModel builder)
// Forward to the public method on the underlying object, wrapping result in promise class
Write($" return new {methodPromiseImplementationClass}(this._promise.then(obj => obj.{methodName}(");
Write(argsString);
- WriteLine(")));");
+ WriteLine($")), this._client);");
WriteLine(" }");
}
WriteLine();
@@ -1675,6 +1933,25 @@ private void GenerateAspireClient(List entryPoints)
}
}
+ ///
+ /// Generates an exported entry-point function that creates a builder via an async IIFE.
+ ///
+ ///
+ /// Entry-point functions are standalone exports (not class methods). They wrap the
+ /// RPC call in an async IIFE and return a thenable promise class for immediate chaining.
+ /// Generated TypeScript (example for createBuilder):
+ ///
+ /// export function createBuilder(client: AspireClientRpc): DistributedApplicationBuilderPromise {
+ /// const promise = (async () => {
+ /// const rpcArgs: Record<string, unknown> = { };
+ /// const handle = await client.invokeCapability<DistributedApplicationBuilderHandle>(
+ /// 'aspire.capability.createBuilder', rpcArgs);
+ /// return new DistributedApplicationBuilderImpl(handle, client);
+ /// })();
+ /// return new DistributedApplicationBuilderPromiseImpl(promise, client);
+ /// }
+ ///
+ ///
private void GenerateEntryPointFunction(AtsCapabilityInfo capability)
{
var methodName = capability.MethodName;
@@ -1711,19 +1988,31 @@ private void GenerateEntryPointFunction(AtsCapabilityInfo capability)
Write($"export function {methodName}(");
Write(paramsString);
WriteLine($"): {returnPromiseWrapper} {{");
+ // Use async IIFE to resolve promise-like handle params before RPC
+ WriteLine($" const promise = (async () => {{");
+ // Resolve promise-like handle params
+ foreach (var param in capability.Parameters)
+ {
+ if (!param.IsCallback && IsWidenedHandleType(param.Type))
+ {
+ WriteLine($" {param.Name} = isPromiseLike({param.Name}) ? await {param.Name} : {param.Name};");
+ }
+ }
var requiredArgs = requiredParams
.Select(param => GetRpcArgumentEntry(param, useRegisteredCallback: false))
.ToList();
- WriteLine($" const rpcArgs: Record = {{ {string.Join(", ", requiredArgs)} }};");
+ WriteLine($" const rpcArgs: Record = {{ {string.Join(", ", requiredArgs)} }};");
foreach (var param in optionalParams)
{
- WriteLine($" if ({param.Name} !== undefined) rpcArgs.{param.Name} = {GetRpcArgumentExpression(param, useRegisteredCallback: false)};");
+ WriteLine($" if ({param.Name} !== undefined) rpcArgs.{param.Name} = {GetRpcArgumentExpression(param, useRegisteredCallback: false)};");
}
- WriteLine($" const promise = client.invokeCapability<{handleType}>(");
- WriteLine($" '{capability.CapabilityId}',");
- WriteLine(" rpcArgs");
- WriteLine($" ).then(handle => new {returnWrapperImplementationClass}(handle, client));");
- WriteLine($" return new {returnPromiseImplementationClass}(promise);");
+ WriteLine($" const handle = await client.invokeCapability<{handleType}>(");
+ WriteLine($" '{capability.CapabilityId}',");
+ WriteLine(" rpcArgs");
+ WriteLine(" );");
+ WriteLine($" return new {returnWrapperImplementationClass}(handle, client);");
+ WriteLine($" }})();");
+ WriteLine($" return new {returnPromiseImplementationClass}(promise, client);");
WriteLine("}");
}
else
@@ -1736,6 +2025,14 @@ private void GenerateEntryPointFunction(AtsCapabilityInfo capability)
Write($"export async function {methodName}(");
Write(paramsString);
WriteLine($"): Promise<{returnType}> {{");
+ // Resolve promise-like handle params
+ foreach (var param in capability.Parameters)
+ {
+ if (!param.IsCallback && IsWidenedHandleType(param.Type))
+ {
+ WriteLine($" {param.Name} = isPromiseLike({param.Name}) ? await {param.Name} : {param.Name};");
+ }
+ }
var requiredArgs = requiredParams
.Select(param => GetRpcArgumentEntry(param, useRegisteredCallback: false))
.ToList();
@@ -1967,6 +2264,11 @@ export async function connect(): Promise {
export async function createBuilder(options?: CreateBuilderOptions): Promise {
const client = await connect();
+ // Apply client-side options before any tracking begins
+ if (options?.throwOnPendingRejections === false) {
+ client.throwOnPendingRejections = false;
+ }
+
// Default args, projectDirectory, and appHostFilePath if not provided
// ASPIRE_APPHOST_FILEPATH is set by the CLI for consistent socket hash computation
const effectiveOptions: CreateBuilderOptions = {
@@ -1976,6 +2278,9 @@ export async function createBuilder(options?: CreateBuilderOptions): Promise(
'Aspire.Hosting/createBuilderWithOptions',
{ options: effectiveOptions }
@@ -1986,7 +2291,7 @@ export async function createBuilder(options?: CreateBuilderOptions): Promise
+ ///
+ /// Scalar properties produce an object with async get/set functions.
+ /// Dictionary and list properties delegate to AspireDict/AspireList helpers.
+ /// Wrapper-typed properties delegate to .
+ /// Generated TypeScript (example for a string property connectionString):
+ ///
+ /// connectionString = {
+ /// get: async (): Promise<string> => {
+ /// return await this._client.invokeCapability<string>(
+ /// 'aspire.resource.connectionString.get', { context: this._handle });
+ /// },
+ /// set: async (value: string | PromiseLike<string>): Promise<void> => {
+ /// value = isPromiseLike(value) ? await value : value;
+ /// await this._client.invokeCapability<void>(
+ /// 'aspire.resource.connectionString.set', { context: this._handle, value });
+ /// }
+ /// };
+ ///
+ ///
private void GeneratePropertyLikeObject(string propertyName, AtsCapabilityInfo? getter, AtsCapabilityInfo? setter)
{
// Determine the return type from getter
@@ -2288,6 +2612,7 @@ private void GeneratePropertyLikeObject(string propertyName, AtsCapabilityInfo?
{
var valueType = MapInputTypeToTypeScript(valueParam.Type);
WriteLine($" set: async (value: {valueType}): Promise => {{");
+ GeneratePromiseResolutionForParam("value", valueParam.Type, " ");
WriteLine($" await this._client.invokeCapability(");
WriteLine($" '{setter.CapabilityId}',");
WriteLine($" {{ context: this._handle, {GetRpcArgumentEntry("value", valueParam.Type)} }}");
@@ -2303,6 +2628,26 @@ private void GeneratePropertyLikeObject(string propertyName, AtsCapabilityInfo?
///
/// Generates a property-like object that returns a wrapper class.
///
+ ///
+ /// Similar to but the getter returns a wrapper
+ /// class instance instead of a scalar value. The RPC result is a handle that gets
+ /// wrapped in the implementation class.
+ ///
+ /// // Example: a property 'primaryEndpoint' returning EndpointReference
+ /// primaryEndpoint = {
+ /// get: async (): Promise<EndpointReference> => {
+ /// const handle = await this._client.invokeCapability<EndpointReferenceHandle>(
+ /// 'aspire.resource.primaryEndpoint.get', { context: this._handle });
+ /// return new EndpointReferenceImpl(handle, this._client);
+ /// },
+ /// set: async (value: EndpointReference | PromiseLike<EndpointReference>): Promise<void> => {
+ /// value = isPromiseLike(value) ? await value : value;
+ /// await this._client.invokeCapability<void>(
+ /// 'aspire.resource.primaryEndpoint.set', { context: this._handle, value });
+ /// }
+ /// };
+ ///
+ ///
private void GenerateWrapperPropertyObject(string propertyName, AtsCapabilityInfo getter, AtsCapabilityInfo? setter, string wrapperClassName)
{
var handleType = GetHandleTypeName(getter.ReturnType!.TypeId);
@@ -2329,6 +2674,7 @@ private void GenerateWrapperPropertyObject(string propertyName, AtsCapabilityInf
{
var valueType = MapInputTypeToTypeScript(valueParam.Type);
WriteLine($" set: async (value: {valueType}): Promise => {{");
+ GeneratePromiseResolutionForParam("value", valueParam.Type, " ");
WriteLine($" await this._client.invokeCapability(");
WriteLine($" '{setter.CapabilityId}',");
WriteLine($" {{ context: this._handle, {GetRpcArgumentEntry("value", valueParam.Type)} }}");
@@ -2443,6 +2789,19 @@ private void GenerateListProperty(string propertyName, AtsCapabilityInfo getter)
///
/// Generates a context instance method (from ExposeMethods=true).
///
+ ///
+ /// Context methods are async methods on wrapper classes that pass this._handle
+ /// as the context argument. They use for parameter
+ /// handling.
+ /// Generated TypeScript (example for getEndpoint on PostgresResource):
+ ///
+ /// async getEndpoint(name: string): Promise<EndpointReference> {
+ /// const rpcArgs: Record<string, unknown> = { context: this._handle, name };
+ /// return await this._client.invokeCapability<EndpointReference>(
+ /// 'aspire.resource.getEndpoint', rpcArgs);
+ /// }
+ ///
+ ///
private void GenerateContextMethod(AtsCapabilityInfo method)
{
// Use OwningTypeName if available to extract method name, otherwise parse from MethodName
@@ -2488,20 +2847,11 @@ private void GenerateContextMethod(AtsCapabilityInfo method)
// Extract optional params from options object
foreach (var param in optionalParams)
{
- WriteLine($" const {param.Name} = options?.{param.Name};");
+ WriteLine($" {(IsWidenedHandleType(param.Type) ? "let" : "const")} {param.Name} = options?.{param.Name};");
}
- // Build args object with conditional inclusion
- var requiredArgs = new List { $"{targetParamName}: this._handle" };
- foreach (var param in requiredParams)
- {
- requiredArgs.Add(GetRpcArgumentEntry(param.Name, param.Type));
- }
- WriteLine($" const rpcArgs: Record = {{ {string.Join(", ", requiredArgs)} }};");
- foreach (var param in optionalParams)
- {
- WriteLine($" if ({param.Name} !== undefined) rpcArgs.{param.Name} = {GetRpcArgumentValueExpression(param.Name, param.Type)};");
- }
+ // Resolve promise-like params and build args
+ GenerateResolveAndBuildArgs(targetParamName, userParams, requiredParams, optionalParams);
if (returnType == "void")
{
@@ -2532,6 +2882,18 @@ private void GenerateContextMethod(AtsCapabilityInfo method)
///
/// Generates a method on a wrapper class.
///
+ ///
+ /// Similar to but designed for wrapper classes
+ /// that expose RPC methods without the thenable/fluent pattern.
+ /// Generated TypeScript (example for getExpression on EndpointReference):
+ ///
+ /// async getExpression(name: string): Promise<string> {
+ /// const rpcArgs: Record<string, unknown> = { builder: this._handle, name };
+ /// return await this._client.invokeCapability<string>(
+ /// 'aspire.endpoint.getExpression', rpcArgs);
+ /// }
+ ///
+ ///
private void GenerateWrapperMethod(AtsCapabilityInfo capability)
{
var methodName = GetTypeScriptMethodName(capability.MethodName);
@@ -2574,20 +2936,11 @@ private void GenerateWrapperMethod(AtsCapabilityInfo capability)
// Extract optional params from options object
foreach (var param in optionalParams)
{
- WriteLine($" const {param.Name} = options?.{param.Name};");
+ WriteLine($" {(IsWidenedHandleType(param.Type) ? "let" : "const")} {param.Name} = options?.{param.Name};");
}
- // Build args object with conditional inclusion
- var requiredArgs = new List { $"{firstParamName}: this._handle" };
- foreach (var param in requiredParams)
- {
- requiredArgs.Add(GetRpcArgumentEntry(param.Name, param.Type));
- }
- WriteLine($" const rpcArgs: Record = {{ {string.Join(", ", requiredArgs)} }};");
- foreach (var param in optionalParams)
- {
- WriteLine($" if ({param.Name} !== undefined) rpcArgs.{param.Name} = {GetRpcArgumentValueExpression(param.Name, param.Type)};");
- }
+ // Resolve promise-like params and build args
+ GenerateResolveAndBuildArgs(firstParamName, userParams, requiredParams, optionalParams);
if (returnType == "void")
{
@@ -2608,6 +2961,26 @@ private void GenerateWrapperMethod(AtsCapabilityInfo capability)
/// Generates a method on a type class using the thenable pattern.
/// Generates both an internal async method and a public fluent method.
///
+ ///
+ /// Follows the same internal/public pair pattern as
+ /// but operates on type classes (resources exposed via ExposeMethods).
+ /// Generated TypeScript (example for withEnvironment on PostgresResource):
+ ///
+ /// /** @internal */
+ /// async _withEnvironmentInternal(name: string, value: string): Promise<PostgresResource> {
+ /// const rpcArgs: Record<string, unknown> = { context: this._handle, name, value };
+ /// await this._client.invokeCapability<void>('...', rpcArgs);
+ /// return this;
+ /// }
+ ///
+ /// withEnvironment(name: string, value: string): PostgresResourcePromise {
+ /// return new PostgresResourcePromiseImpl(
+ /// this._withEnvironmentInternal(name, value), this._client);
+ /// }
+ ///
+ /// For methods returning a different wrapper type, the internal method returns that
+ /// wrapper and the public method returns its promise class.
+ ///
private void GenerateTypeClassMethod(BuilderModel model, AtsCapabilityInfo capability)
{
var className = DeriveClassName(model.TypeId);
@@ -2683,6 +3056,9 @@ private void GenerateTypeClassMethod(BuilderModel model, AtsCapabilityInfo capab
GenerateCallbackRegistration(callbackParam);
}
+ // Resolve any promise-like handle parameters
+ GeneratePromiseResolution(userParams);
+
// Build args with conditional inclusion
GenerateArgsObjectWithConditionals(targetParamName, requiredParams, optionalParams);
@@ -2702,12 +3078,21 @@ private void GenerateTypeClassMethod(BuilderModel model, AtsCapabilityInfo capab
// Extract optional params and forward
foreach (var param in optionalParams)
{
- WriteLine($" const {param.Name} = options?.{param.Name};");
+ WriteLine($" {(IsWidenedHandleType(param.Type) ? "let" : "const")} {param.Name} = options?.{param.Name};");
}
- Write($" return new {returnPromiseImplementationClass}(this.{internalMethodName}(");
- Write(string.Join(", ", userParams.Select(p => p.Name)));
- WriteLine("));");
+ var internalCall = $"this.{internalMethodName}({string.Join(", ", userParams.Select(p => p.Name))})";
+
+ // For build(), flush pending promises before invoking the internal method to avoid deadlock
+ if (string.Equals(methodName, "build", StringComparison.OrdinalIgnoreCase))
+ {
+ WriteLine($" const flushAndBuild = async () => {{ await this._client.flushPendingPromises(); return {internalCall}; }};");
+ WriteLine($" return new {returnPromiseImplementationClass}(flushAndBuild(), this._client, false);");
+ }
+ else
+ {
+ WriteLine($" return new {returnPromiseImplementationClass}({internalCall}, this._client);");
+ }
WriteLine(" }");
}
else if (isVoid)
@@ -2726,6 +3111,9 @@ private void GenerateTypeClassMethod(BuilderModel model, AtsCapabilityInfo capab
GenerateCallbackRegistration(callbackParam);
}
+ // Resolve any promise-like handle parameters
+ GeneratePromiseResolution(userParams);
+
// Build args with conditional inclusion
GenerateArgsObjectWithConditionals(targetParamName, requiredParams, optionalParams);
@@ -2745,12 +3133,12 @@ private void GenerateTypeClassMethod(BuilderModel model, AtsCapabilityInfo capab
// Extract optional params and forward
foreach (var param in optionalParams)
{
- WriteLine($" const {param.Name} = options?.{param.Name};");
+ WriteLine($" {(IsWidenedHandleType(param.Type) ? "let" : "const")} {param.Name} = options?.{param.Name};");
}
Write($" return new {promiseImplementationClass}(this.{internalMethodName}(");
Write(string.Join(", ", userParams.Select(p => p.Name)));
- WriteLine("));");
+ WriteLine("), this._client);");
WriteLine(" }");
}
else
@@ -2763,7 +3151,7 @@ private void GenerateTypeClassMethod(BuilderModel model, AtsCapabilityInfo capab
// Extract optional params from options object
foreach (var param in optionalParams)
{
- WriteLine($" const {param.Name} = options?.{param.Name};");
+ WriteLine($" {(IsWidenedHandleType(param.Type) ? "let" : "const")} {param.Name} = options?.{param.Name};");
}
// Handle callback registration if any
@@ -2773,6 +3161,9 @@ private void GenerateTypeClassMethod(BuilderModel model, AtsCapabilityInfo capab
GenerateCallbackRegistration(callbackParam);
}
+ // Resolve any promise-like handle parameters
+ GeneratePromiseResolution(userParams);
+
// Build args with conditional inclusion
GenerateArgsObjectWithConditionals(targetParamName, requiredParams, optionalParams);
@@ -2799,6 +3190,25 @@ private void GenerateTypeClassMethod(BuilderModel model, AtsCapabilityInfo capab
///
/// Generates a thenable wrapper class for a type class.
///
+ ///
+ /// Identical in structure to but generated for
+ /// type classes (resources with ExposeMethods) rather than builder classes.
+ /// Generated TypeScript (example for PostgresResource):
+ ///
+ /// class PostgresResourcePromiseImpl implements PostgresResourcePromise {
+ /// constructor(private _promise: Promise<PostgresResource>, private _client: AspireClientRpc) {
+ /// _client.trackPromise(_promise);
+ /// }
+ /// then<T1, T2>(...): PromiseLike<T1 | T2> {
+ /// return this._promise.then(...);
+ /// }
+ /// withEnvironment(name: string, value: string): PostgresResourcePromise {
+ /// return new PostgresResourcePromiseImpl(
+ /// this._promise.then(obj => obj.withEnvironment(name, value)), this._client);
+ /// }
+ /// }
+ ///
+ ///
private void GenerateTypeClassThenableWrapper(BuilderModel model, List methods)
{
var className = DeriveClassName(model.TypeId);
@@ -2809,7 +3219,9 @@ private void GenerateTypeClassThenableWrapper(BuilderModel model, List) {{}}");
+ WriteLine($" constructor(private _promise: Promise<{className}>, private _client: AspireClientRpc, track = true) {{");
+ WriteLine($" if (track) {{ _client.trackPromise(_promise); }}");
+ WriteLine($" }}");
WriteLine();
// Generate then() for PromiseLike interface
@@ -2879,7 +3291,7 @@ private void GenerateTypeClassThenableWrapper(BuilderModel model, List obj.{methodName}(");
Write(argsString);
- WriteLine(")));");
+ WriteLine($")), this._client);");
WriteLine(" }");
}
else if (isVoid)
@@ -2890,7 +3302,7 @@ private void GenerateTypeClassThenableWrapper(BuilderModel model, List obj.{methodName}(");
Write(argsString);
- WriteLine(")));");
+ WriteLine($")), this._client);");
WriteLine(" }");
}
else
diff --git a/src/Aspire.Hosting.CodeGeneration.TypeScript/Resources/base.ts b/src/Aspire.Hosting.CodeGeneration.TypeScript/Resources/base.ts
index 8d7033494fb..219eee04175 100644
--- a/src/Aspire.Hosting.CodeGeneration.TypeScript/Resources/base.ts
+++ b/src/Aspire.Hosting.CodeGeneration.TypeScript/Resources/base.ts
@@ -7,6 +7,12 @@ export { Handle, AspireClient, CapabilityError, CancellationToken, registerCallb
export type { MarshalledHandle, AtsError, AtsErrorDetails, CallbackFunction } from './transport.js';
export { AtsErrorCodes, isMarshalledHandle, isAtsError, wrapIfHandle } from './transport.js';
+/**
+ * Utility type for parameters that accept either a resolved value or a promise of that value.
+ * Used by generated APIs to allow passing un-awaited resource builders directly.
+ */
+export type Awaitable = T | PromiseLike;
+
// ============================================================================
// Reference Expression
// ============================================================================
diff --git a/src/Aspire.Hosting.CodeGeneration.TypeScript/Resources/transport.ts b/src/Aspire.Hosting.CodeGeneration.TypeScript/Resources/transport.ts
index 9ebab0397fa..4d955e7927d 100644
--- a/src/Aspire.Hosting.CodeGeneration.TypeScript/Resources/transport.ts
+++ b/src/Aspire.Hosting.CodeGeneration.TypeScript/Resources/transport.ts
@@ -14,6 +14,10 @@ export interface AspireClientRpc {
readonly connected: boolean;
invokeCapability(capabilityId: string, args?: Record): Promise;
cancelToken(cancellationId: string): Promise;
+ trackPromise(promise: Promise): void;
+ flushPendingPromises(): Promise;
+ /** When true (default), rejected tracked promises are collected and re-thrown by flushPendingPromises. */
+ throwOnPendingRejections: boolean;
}
/**
@@ -385,7 +389,7 @@ export class AppHostUsageError extends Error {
}
}
-function isPromiseLike(value: unknown): value is PromiseLike {
+export function isPromiseLike(value: unknown): value is PromiseLike {
return (
value !== null &&
(typeof value === 'object' || typeof value === 'function') &&
@@ -755,9 +759,57 @@ export class AspireClient implements AspireClientRpc {
private _pendingCalls = 0;
private _connectPromise: Promise | null = null;
private _disconnectNotified = false;
+ private _pendingPromises: Set> = new Set();
+ private _rejectedErrors: Set = new Set();
+ throwOnPendingRejections = true;
constructor(private socketPath: string) { }
+ trackPromise(promise: Promise): void {
+ this._pendingPromises.add(promise);
+ // Remove on both resolve and reject. The reject handler swallows the
+ // error to prevent Node.js unhandled-rejection crashes.
+ //
+ // When throwOnPendingRejections is true (default), rejection errors are
+ // eagerly collected so that flushPendingPromises can re-throw them even
+ // if the promise settles before flush is called.
+ //
+ // Limitation: JavaScript provides no way to detect whether a rejection
+ // was already observed by the caller (e.g., try { await p } catch {}).
+ // The .then() reject handler fires regardless. This means user-caught
+ // rejections will also be re-thrown by build(). We accept this tradeoff
+ // because the common case — an un-awaited chain fails silently — should
+ // fail loud. The uncommon case (catch an error from an un-awaited chain,
+ // then continue to build) can opt out with:
+ // createBuilder({ throwOnPendingRejections: false })
+ promise.then(
+ () => this._pendingPromises.delete(promise),
+ (err) => {
+ this._pendingPromises.delete(promise);
+ if (this.throwOnPendingRejections) {
+ this._rejectedErrors.add(err);
+ }
+ }
+ );
+ }
+
+ async flushPendingPromises(): Promise {
+ if (this._pendingPromises.size > 0) {
+ console.warn(`Flushing ${this._pendingPromises.size} pending promise(s). Consider awaiting fluent calls to avoid implicit flushing.`);
+ // Snapshot the current set before awaiting. Promises tracked after
+ // flush starts (e.g. by .then() callbacks or the build PromiseImpl
+ // constructor) are excluded. This prevents deadlocks where a tracked
+ // promise depends on flush completing.
+ const pending = [...this._pendingPromises];
+ await Promise.allSettled(pending);
+ }
+ if (this._rejectedErrors.size > 0) {
+ const errors = [...this._rejectedErrors];
+ this._rejectedErrors.clear();
+ throw new AggregateError(errors, 'One or more unawaited fluent calls failed');
+ }
+ }
+
/**
* Register a callback to be called when the connection is lost
*/
diff --git a/tests/Aspire.Cli.EndToEnd.Tests/Helpers/CliE2EAutomatorHelpers.cs b/tests/Aspire.Cli.EndToEnd.Tests/Helpers/CliE2EAutomatorHelpers.cs
index 57f388cf504..3b7924884cc 100644
--- a/tests/Aspire.Cli.EndToEnd.Tests/Helpers/CliE2EAutomatorHelpers.cs
+++ b/tests/Aspire.Cli.EndToEnd.Tests/Helpers/CliE2EAutomatorHelpers.cs
@@ -359,6 +359,57 @@ internal static async Task AspireStopAsync(
await auto.WaitForSuccessPromptAsync(counter);
}
+ ///
+ /// Asserts that the specified resources exist in the running AppHost by running
+ /// aspire describe <resource> --format json for each expected resource.
+ /// The CLI handles name/displayName resolution internally.
+ /// On failure, the error output from the CLI is visible in the terminal recording.
+ ///
+ internal static async Task AssertResourcesExistAsync(
+ this Hex1bTerminalAutomator auto,
+ SequenceCounter counter,
+ params string[] expectedResourceNames)
+ {
+ foreach (var resource in expectedResourceNames)
+ {
+ var expectedCounter = counter.Value;
+ await auto.TypeAsync($"aspire describe {resource} --format json");
+ await auto.EnterAsync();
+
+ var succeeded = false;
+ await auto.WaitUntilAsync(s =>
+ {
+ var successSearcher = new CellPatternSearcher()
+ .FindPattern(expectedCounter.ToString())
+ .RightText(" OK] $ ");
+ if (successSearcher.Search(s).Count > 0)
+ {
+ succeeded = true;
+ return true;
+ }
+
+ var errorSearcher = new CellPatternSearcher()
+ .FindPattern(expectedCounter.ToString())
+ .RightText(" ERR:");
+ return errorSearcher.Search(s).Count > 0;
+ }, timeout: TimeSpan.FromSeconds(30), description: $"aspire describe {resource}");
+
+ counter.Increment();
+
+ if (!succeeded)
+ {
+ // Dump all resources so we can see what's actually running
+ await auto.TypeAsync("aspire describe --format json");
+ await auto.EnterAsync();
+ await auto.WaitForAnyPromptAsync(counter);
+
+ throw new InvalidOperationException(
+ $"Resource '{resource}' not found. 'aspire describe {resource}' exited with an error. " +
+ "Check the terminal recording for the full resource list above.");
+ }
+ }
+ }
+
///
/// Copies interesting diagnostic directories from ~/.aspire to the mounted workspace
/// so they are captured by . Call this before
diff --git a/tests/Aspire.Cli.EndToEnd.Tests/TypeScriptCodegenValidationTests.cs b/tests/Aspire.Cli.EndToEnd.Tests/TypeScriptCodegenValidationTests.cs
index df2ee140f7c..e406ca19fa5 100644
--- a/tests/Aspire.Cli.EndToEnd.Tests/TypeScriptCodegenValidationTests.cs
+++ b/tests/Aspire.Cli.EndToEnd.Tests/TypeScriptCodegenValidationTests.cs
@@ -3,7 +3,6 @@
using Aspire.Cli.EndToEnd.Tests.Helpers;
using Aspire.Cli.Tests.Utils;
-using Aspire.TestUtilities;
using Hex1b.Automation;
using Xunit;
@@ -98,30 +97,21 @@ public async Task RestoreGeneratesSdkFiles()
}
[Fact]
- [QuarantinedTest("https://github.com/microsoft/aspire/issues/15975")]
- public async Task RunWithMissingAwaitShowsHelpfulError()
+ [CaptureWorkspaceOnFailure]
+ public async Task UnAwaitedChainsCompileWithAutoResolvePromises()
{
- using var workspace = TemporaryWorkspace.Create(output);
-
- var prNumber = CliE2ETestHelpers.GetRequiredPrNumber();
- var commitSha = CliE2ETestHelpers.GetRequiredCommitSha();
- var isCI = CliE2ETestHelpers.IsRunningInCI;
+ var repoRoot = CliE2ETestHelpers.GetRepoRoot();
+ var installMode = CliE2ETestHelpers.DetectDockerInstallMode(repoRoot);
+ var workspace = TemporaryWorkspace.Create(output);
- using var terminal = CliE2ETestHelpers.CreateTestTerminal();
+ using var terminal = CliE2ETestHelpers.CreateDockerTestTerminal(repoRoot, installMode, output, mountDockerSocket: true, workspace: workspace);
var pendingRun = terminal.RunAsync(TestContext.Current.CancellationToken);
var counter = new SequenceCounter();
var auto = new Hex1bTerminalAutomator(terminal, defaultTimeout: TimeSpan.FromSeconds(500));
- // PrepareEnvironment
- await auto.PrepareEnvironmentAsync(workspace, counter);
-
- if (isCI)
- {
- await auto.InstallAspireBundleFromPullRequestAsync(prNumber, counter);
- await auto.SourceAspireBundleEnvironmentAsync(counter);
- await auto.VerifyAspireCliVersionAsync(commitSha, counter);
- }
+ await auto.PrepareDockerEnvironmentAsync(counter, workspace);
+ await auto.InstallAspireCliInDockerAsync(installMode, counter);
await auto.TypeAsync("aspire init --language typescript --non-interactive");
await auto.EnterAsync();
@@ -144,24 +134,47 @@ public async Task RunWithMissingAwaitShowsHelpfulError()
const builder = await createBuilder();
+ // None of these are awaited — they return PromiseLike wrappers whose
+ // underlying RPC calls are tracked by trackPromise().
const postgres = builder.addPostgres("postgres");
const db = postgres.addDatabase("db");
- await builder.addContainer("consumer", "nginx")
+ // This chain is also NOT awaited. The withReference(db) call accepts
+ // db as a PromiseLike and resolves it internally, but the outer
+ // addContainer().withReference() promise itself stays un-awaited.
+ // This is the key scenario: build() must call flushPendingPromises()
+ // to ensure this chain's RPC calls actually execute.
+ builder.addContainer("consumer", "nginx")
.withReference(db);
+ // build() flushes all pending promises before proceeding. If the
+ // flush implementation deadlocks (e.g. re-awaiting a promise tracked
+ // after flush starts), this line hangs and the test times out.
await builder.build().run();
""";
File.WriteAllText(appHostPath, newContent);
- await auto.TypeAsync("aspire run");
+ // Validate that un-awaited chains compile without type errors.
+ // withReference(db) should accept PromiseLike from the un-awaited addDatabase().
+ await auto.TypeAsync("npx tsc --noEmit");
await auto.EnterAsync();
- await auto.WaitUntilAsync(s =>
- s.ContainsText("❌ AppHost Error:") &&
- s.ContainsText("Did you forget 'await'"),
- timeout: TimeSpan.FromMinutes(3), description: "waiting for AppHost error with await hint");
- await auto.WaitForAnyPromptAsync(counter);
+ await auto.WaitForSuccessPromptFailFastAsync(counter, TimeSpan.FromMinutes(2));
+
+ // Validate runtime behavior: aspire start launches the apphost, which calls
+ // build() and triggers flushPendingPromises(). If the flush deadlocks (e.g. the
+ // build promise is re-awaited in a while loop), the process hangs and this test
+ // times out. A successful start proves un-awaited chains execute their RPC calls.
+ await auto.AspireStartAsync(counter);
+
+ // Verify the un-awaited resources were actually materialized — not silently dropped.
+ // If flushPendingPromises skipped the pending chains, these resources wouldn't exist.
+ await auto.AssertResourcesExistAsync(counter, "postgres", "db", "consumer");
+
+ await auto.AspireStopAsync(counter);
+
+ await auto.CaptureAspireDiagnosticsAsync(counter, workspace);
+
await auto.TypeAsync("exit");
await auto.EnterAsync();
diff --git a/tests/Aspire.Hosting.CodeGeneration.TypeScript.JsTests/tests/transport.test.ts b/tests/Aspire.Hosting.CodeGeneration.TypeScript.JsTests/tests/transport.test.ts
index 8a157d9ec2c..53b8590f05e 100644
--- a/tests/Aspire.Hosting.CodeGeneration.TypeScript.JsTests/tests/transport.test.ts
+++ b/tests/Aspire.Hosting.CodeGeneration.TypeScript.JsTests/tests/transport.test.ts
@@ -773,3 +773,229 @@ describe('callback invocation protocol', () => {
}
});
});
+
+// ============================================================================
+// Promise Tracking (trackPromise / flushPendingPromises)
+// ============================================================================
+
+describe('trackPromise / flushPendingPromises', () => {
+ function createClient(): AspireClient {
+ return new AspireClient('/dev/null');
+ }
+
+ it('removes promise from pending set on resolve', async () => {
+ const client = createClient();
+ const p = Promise.resolve();
+ client.trackPromise(p);
+ await Promise.resolve();
+ // No pending promises — flush completes immediately
+ await client.flushPendingPromises();
+ });
+
+ it('re-throws pre-flush rejected promise errors by default (throwOnPendingRejections)', async () => {
+ const client = createClient();
+ const p = Promise.reject(new Error('test error'));
+ client.trackPromise(p);
+ await Promise.resolve();
+ // Default: throwOnPendingRejections = true — flush re-throws even pre-settled rejections
+ await expect(client.flushPendingPromises()).rejects.toThrow(AggregateError);
+ });
+
+ it('suppresses pre-flush rejection errors when throwOnPendingRejections is false', async () => {
+ const client = createClient();
+ client.throwOnPendingRejections = false;
+ const p = Promise.reject(new Error('test error'));
+ client.trackPromise(p);
+ await Promise.resolve();
+ // With throwOnPendingRejections = false, pre-flush rejections are silently removed
+ await client.flushPendingPromises();
+ });
+
+ it('resolves immediately when no promises are tracked', async () => {
+ const client = createClient();
+ await client.flushPendingPromises();
+ });
+
+ it('waits for pending promises to resolve', async () => {
+ const client = createClient();
+ let resolved = false;
+ const p = new Promise(resolve => {
+ setTimeout(() => { resolved = true; resolve(); }, 10);
+ });
+ client.trackPromise(p);
+ await client.flushPendingPromises();
+ expect(resolved).toBe(true);
+ });
+
+ it('throws AggregateError when a pending promise rejects during flush', async () => {
+ const client = createClient();
+ const p = new Promise((_, reject) => {
+ setTimeout(() => reject(new Error('boom')), 10);
+ });
+ client.trackPromise(p);
+ await expect(client.flushPendingPromises()).rejects.toThrow(AggregateError);
+ });
+
+ it('collects multiple errors from multiple rejected promises', async () => {
+ const client = createClient();
+ const p1 = new Promise((_, reject) => setTimeout(() => reject(new Error('err1')), 10));
+ const p2 = new Promise((_, reject) => setTimeout(() => reject(new Error('err2')), 10));
+ client.trackPromise(p1);
+ client.trackPromise(p2);
+ try {
+ await client.flushPendingPromises();
+ expect.unreachable('should have thrown');
+ } catch (err) {
+ expect(err).toBeInstanceOf(AggregateError);
+ const msgs = (err as AggregateError).errors.map((e: Error) => e.message).sort();
+ expect(msgs).toEqual(['err1', 'err2']);
+ }
+ });
+
+ it('does not duplicate errors from chained promise wrappers', async () => {
+ // Simulates the generated pattern: inner promise is tracked by
+ // PromiseImpl, then wrapped in .then() which is also tracked by
+ // the chained PromiseImpl. Both adopt the same rejection.
+ const client = createClient();
+
+ const inner = new Promise((_, reject) =>
+ setTimeout(() => reject(new Error('rpc failed')), 10));
+ client.trackPromise(inner);
+
+ // Outer .then() adopts the inner rejection — also tracked
+ const outer = inner.then(val => val);
+ client.trackPromise(outer);
+
+ try {
+ await client.flushPendingPromises();
+ expect.unreachable('should have thrown');
+ } catch (err) {
+ expect(err).toBeInstanceOf(AggregateError);
+ // Should be 1 unique error, not 2 duplicates
+ expect((err as AggregateError).errors.length).toBe(1);
+ expect((err as AggregateError).errors[0].message).toBe('rpc failed');
+ }
+ });
+
+ it('does not poison the client after a failed flush', async () => {
+ // Simulates the build() poisoning scenario: flushPendingPromises throws
+ // AggregateError, the build promise rejects with it, and if tracked,
+ // the reject handler would add the AggregateError back into _rejectedErrors.
+ // A subsequent flush should be clean.
+ const client = createClient();
+
+ const p = new Promise((_, reject) =>
+ setTimeout(() => reject(new Error('chain failed')), 10));
+ client.trackPromise(p);
+
+ // First flush throws
+ await expect(client.flushPendingPromises()).rejects.toThrow(AggregateError);
+
+ // Second flush should be clean — no stale errors
+ await client.flushPendingPromises();
+ });
+
+ it('does not flush promises created by .then() callbacks during flush (known limitation)', async () => {
+ // The snapshot-based flush only awaits promises tracked before flush starts.
+ // Promises tracked by .then() callbacks during flush are excluded to prevent
+ // deadlocks. This is acceptable because the generated fluent API tracks
+ // promises directly (resource.withX()), not via .then() callbacks.
+ const client = createClient();
+
+ let innerResolved = false;
+ const outer = new Promise(resolve =>
+ setTimeout(() => resolve('done'), 10));
+ client.trackPromise(outer);
+
+ outer.then(() => {
+ const inner = new Promise(resolve =>
+ setTimeout(() => { innerResolved = true; resolve(); }, 50));
+ client.trackPromise(inner);
+ });
+
+ await client.flushPendingPromises();
+ expect(innerResolved).toBe(false);
+ });
+
+ it('does not deadlock when build promise depends on flush completing', async () => {
+ // Simulates the build() pattern: the build promise depends on flush
+ // completing but is NOT tracked (build uses track=false in PromiseImpl).
+ const client = createClient();
+
+ let resolveSlowPromise: () => void;
+ const slowPromise = new Promise(resolve => { resolveSlowPromise = resolve; });
+ client.trackPromise(slowPromise);
+
+ const flushPromise = client.flushPendingPromises();
+
+ // Build promise depends on flush but is NOT tracked (mirrors track=false)
+ const buildPromise = new Promise(resolve => {
+ flushPromise.then(() => resolve());
+ });
+
+ resolveSlowPromise!();
+
+ const result = await Promise.race([
+ flushPromise.then(() => 'completed'),
+ new Promise(resolve => setTimeout(() => resolve('deadlocked'), 1000)),
+ ]);
+ expect(result).toBe('completed');
+
+ await buildPromise;
+ });
+
+ it('snapshot excludes promises tracked after flush starts', async () => {
+ // Even if a promise is tracked during flush (e.g. by a PromiseImpl
+ // constructor), the snapshot approach excludes it. This test verifies
+ // that tracking during flush doesn't cause a deadlock.
+ const client = createClient();
+
+ let resolveSlowPromise: () => void;
+ const slowPromise = new Promise(resolve => { resolveSlowPromise = resolve; });
+ client.trackPromise(slowPromise);
+
+ const flushPromise = client.flushPendingPromises();
+
+ // Track a promise during flush — snapshot already taken, won't be awaited
+ const latePromise = new Promise(resolve => {
+ flushPromise.then(() => resolve());
+ });
+ client.trackPromise(latePromise);
+
+ resolveSlowPromise!();
+
+ const result = await Promise.race([
+ flushPromise.then(() => 'completed'),
+ new Promise(resolve => setTimeout(() => resolve('deadlocked'), 1000)),
+ ]);
+ expect(result).toBe('completed');
+
+ await latePromise;
+ });
+
+ it('re-throws user-caught rejections in strict mode (accepted tradeoff)', async () => {
+ // With throwOnPendingRejections (default), even user-caught rejections are
+ // collected and re-thrown. This is the accepted tradeoff: we prefer
+ // failing loud for genuinely un-awaited errors over silently losing them.
+ const client = createClient();
+ const p = Promise.reject(new Error('user handled'));
+ client.trackPromise(p);
+
+ try { await p; } catch { /* handled */ }
+ await Promise.resolve();
+
+ await expect(client.flushPendingPromises()).rejects.toThrow(AggregateError);
+ });
+
+ it('does not re-throw user-caught rejections when throwOnPendingRejections is false', async () => {
+ const client = createClient();
+ client.throwOnPendingRejections = false;
+ const p = Promise.reject(new Error('user handled'));
+ client.trackPromise(p);
+
+ try { await p; } catch { /* handled */ }
+ await Promise.resolve();
+
+ await client.flushPendingPromises();
+ });
+});
diff --git a/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/AtsTypeScriptCodeGeneratorTests.cs b/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/AtsTypeScriptCodeGeneratorTests.cs
index ec2af5d0102..53a7dea12c1 100644
--- a/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/AtsTypeScriptCodeGeneratorTests.cs
+++ b/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/AtsTypeScriptCodeGeneratorTests.cs
@@ -591,7 +591,7 @@ public void Pattern4_InterfaceParameterType_GeneratesUnionType()
var files = _generator.GenerateDistributedApplication(atsContext);
var aspireTs = files["aspire.ts"];
- Assert.Contains("withDependency(dependency: ResourceWithConnectionString | TestRedisResource)", aspireTs);
+ Assert.Contains("withDependency(dependency: Awaitable)", aspireTs);
Assert.DoesNotContain("withDependency(dependency: HandleReference)", aspireTs);
}
@@ -603,7 +603,7 @@ public void AspireUnion_InterfaceHandleInput_GeneratesExpandedUnion()
var files = _generator.GenerateDistributedApplication(atsContext);
var aspireTs = files["aspire.ts"];
- Assert.Contains("withUnionDependency(dependency: string | ResourceWithConnectionString | TestRedisResource)", aspireTs);
+ Assert.Contains("withUnionDependency(dependency: string | ResourceWithConnectionString | TestRedisResource | Awaitable)", aspireTs);
}
[Fact]
diff --git a/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/Snapshots/AtsGeneratedAspire.verified.ts b/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/Snapshots/AtsGeneratedAspire.verified.ts
index 94667f6a926..e2552700c3f 100644
--- a/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/Snapshots/AtsGeneratedAspire.verified.ts
+++ b/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/Snapshots/AtsGeneratedAspire.verified.ts
@@ -13,7 +13,8 @@ import {
CapabilityError,
registerCallback,
wrapIfHandle,
- registerHandleWrapper
+ registerHandleWrapper,
+ isPromiseLike
} from './transport.js';
import type { AspireClientRpc } from './transport.js';
@@ -27,6 +28,8 @@ import {
AspireList
} from './base.js';
+import type { Awaitable } from './base.js';
+
// ============================================================================
// Handle Type Aliases (Internal - not exported to users)
// ============================================================================
@@ -474,7 +477,7 @@ class TestResourceContextImpl implements TestResourceContext {
}
setValueAsync(value: string): TestResourceContextPromise {
- return new TestResourceContextPromiseImpl(this._setValueAsyncInternal(value));
+ return new TestResourceContextPromiseImpl(this._setValueAsyncInternal(value), this._client);
}
/** Invokes the ValidateAsync method */
@@ -492,7 +495,9 @@ class TestResourceContextImpl implements TestResourceContext {
* Thenable wrapper for TestResourceContext that enables fluent chaining.
*/
class TestResourceContextPromiseImpl implements TestResourceContextPromise {
- constructor(private _promise: Promise) {}
+ constructor(private _promise: Promise, private _client: AspireClientRpc, track = true) {
+ if (track) { _client.trackPromise(_promise); }
+ }
then(
onfulfilled?: ((value: TestResourceContext) => TResult1 | PromiseLike) | null,
@@ -508,7 +513,7 @@ class TestResourceContextPromiseImpl implements TestResourceContextPromise {
/** Invokes the SetValueAsync method */
setValueAsync(value: string): TestResourceContextPromise {
- return new TestResourceContextPromiseImpl(this._promise.then(obj => obj.setValueAsync(value)));
+ return new TestResourceContextPromiseImpl(this._promise.then(obj => obj.setValueAsync(value)), this._client);
}
/** Invokes the ValidateAsync method */
@@ -560,7 +565,7 @@ class DistributedApplicationBuilderImpl implements DistributedApplicationBuilder
addTestRedis(name: string, options?: AddTestRedisOptions): TestRedisResourcePromise {
const port = options?.port;
- return new TestRedisResourcePromiseImpl(this._addTestRedisInternal(name, port));
+ return new TestRedisResourcePromiseImpl(this._addTestRedisInternal(name, port), this._client);
}
/** Adds a test vault resource */
@@ -575,7 +580,7 @@ class DistributedApplicationBuilderImpl implements DistributedApplicationBuilder
}
addTestVault(name: string): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._addTestVaultInternal(name));
+ return new TestVaultResourcePromiseImpl(this._addTestVaultInternal(name), this._client);
}
}
@@ -584,7 +589,9 @@ class DistributedApplicationBuilderImpl implements DistributedApplicationBuilder
* Thenable wrapper for DistributedApplicationBuilder that enables fluent chaining.
*/
class DistributedApplicationBuilderPromiseImpl implements DistributedApplicationBuilderPromise {
- constructor(private _promise: Promise) {}
+ constructor(private _promise: Promise, private _client: AspireClientRpc, track = true) {
+ if (track) { _client.trackPromise(_promise); }
+ }
then(
onfulfilled?: ((value: DistributedApplicationBuilder) => TResult1 | PromiseLike) | null,
@@ -595,12 +602,12 @@ class DistributedApplicationBuilderPromiseImpl implements DistributedApplication
/** Adds a test Redis resource */
addTestRedis(name: string, options?: AddTestRedisOptions): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.addTestRedis(name, options)));
+ return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.addTestRedis(name, options)), this._client);
}
/** Adds a test vault resource */
addTestVault(name: string): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.addTestVault(name)));
+ return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.addTestVault(name)), this._client);
}
}
@@ -621,9 +628,9 @@ export interface TestDatabaseResource {
withStatus(status: TestResourceStatus): TestDatabaseResourcePromise;
withNestedConfig(config: TestNestedDto): TestDatabaseResourcePromise;
withValidator(validator: (arg: TestResourceContext) => Promise): TestDatabaseResourcePromise;
- testWaitFor(dependency: Resource | ResourceWithConnectionString | ResourceWithEnvironment | TestDatabaseResource | TestRedisResource | TestVaultResource): TestDatabaseResourcePromise;
- withDependency(dependency: ResourceWithConnectionString | TestRedisResource): TestDatabaseResourcePromise;
- withUnionDependency(dependency: string | ResourceWithConnectionString | TestRedisResource): TestDatabaseResourcePromise;
+ testWaitFor(dependency: Awaitable): TestDatabaseResourcePromise;
+ withDependency(dependency: Awaitable): TestDatabaseResourcePromise;
+ withUnionDependency(dependency: string | ResourceWithConnectionString | TestRedisResource | Awaitable): TestDatabaseResourcePromise;
withEndpoints(endpoints: string[]): TestDatabaseResourcePromise;
withEnvironmentVariables(variables: Record): TestDatabaseResourcePromise;
withCancellableOperation(operation: (arg: CancellationToken) => Promise): TestDatabaseResourcePromise;
@@ -649,9 +656,9 @@ export interface TestDatabaseResourcePromise extends PromiseLike Promise): TestDatabaseResourcePromise;
- testWaitFor(dependency: Resource | ResourceWithConnectionString | ResourceWithEnvironment | TestDatabaseResource | TestRedisResource | TestVaultResource): TestDatabaseResourcePromise;
- withDependency(dependency: ResourceWithConnectionString | TestRedisResource): TestDatabaseResourcePromise;
- withUnionDependency(dependency: string | ResourceWithConnectionString | TestRedisResource): TestDatabaseResourcePromise;
+ testWaitFor(dependency: Awaitable): TestDatabaseResourcePromise;
+ withDependency(dependency: Awaitable): TestDatabaseResourcePromise;
+ withUnionDependency(dependency: string | ResourceWithConnectionString | TestRedisResource | Awaitable): TestDatabaseResourcePromise;
withEndpoints(endpoints: string[]): TestDatabaseResourcePromise;
withEnvironmentVariables(variables: Record): TestDatabaseResourcePromise;
withCancellableOperation(operation: (arg: CancellationToken) => Promise): TestDatabaseResourcePromise;
@@ -691,7 +698,7 @@ class TestDatabaseResourceImpl extends ResourceBuilderBase Promise): TestDatabaseResourcePromise {
- return new TestDatabaseResourcePromiseImpl(this._testWithEnvironmentCallbackInternal(callback));
+ return new TestDatabaseResourcePromiseImpl(this._testWithEnvironmentCallbackInternal(callback), this._client);
}
/** @internal */
@@ -741,7 +748,7 @@ class TestDatabaseResourceImpl extends ResourceBuilderBase Promise): TestDatabaseResourcePromise {
- return new TestDatabaseResourcePromiseImpl(this._withValidatorInternal(validator));
+ return new TestDatabaseResourcePromiseImpl(this._withValidatorInternal(validator), this._client);
}
/** @internal */
- private async _testWaitForInternal(dependency: Resource | ResourceWithConnectionString | ResourceWithEnvironment | TestDatabaseResource | TestRedisResource | TestVaultResource): Promise {
+ private async _testWaitForInternal(dependency: Awaitable): Promise {
+ dependency = isPromiseLike(dependency) ? await dependency : dependency;
const rpcArgs: Record = { builder: this._handle, dependency };
const result = await this._client.invokeCapability(
'Aspire.Hosting.CodeGeneration.TypeScript.Tests/testWaitFor',
@@ -857,12 +865,13 @@ class TestDatabaseResourceImpl extends ResourceBuilderBase): TestDatabaseResourcePromise {
+ return new TestDatabaseResourcePromiseImpl(this._testWaitForInternal(dependency), this._client);
}
/** @internal */
- private async _withDependencyInternal(dependency: ResourceWithConnectionString | TestRedisResource): Promise {
+ private async _withDependencyInternal(dependency: Awaitable): Promise {
+ dependency = isPromiseLike(dependency) ? await dependency : dependency;
const rpcArgs: Record = { builder: this._handle, dependency };
const result = await this._client.invokeCapability(
'Aspire.Hosting.CodeGeneration.TypeScript.Tests/withDependency',
@@ -872,12 +881,13 @@ class TestDatabaseResourceImpl extends ResourceBuilderBase): TestDatabaseResourcePromise {
+ return new TestDatabaseResourcePromiseImpl(this._withDependencyInternal(dependency), this._client);
}
/** @internal */
- private async _withUnionDependencyInternal(dependency: string | ResourceWithConnectionString | TestRedisResource): Promise {
+ private async _withUnionDependencyInternal(dependency: string | ResourceWithConnectionString | TestRedisResource | Awaitable): Promise {
+ dependency = isPromiseLike(dependency) ? await dependency : dependency;
const rpcArgs: Record = { builder: this._handle, dependency };
const result = await this._client.invokeCapability(
'Aspire.Hosting.CodeGeneration.TypeScript.Tests/withUnionDependency',
@@ -887,8 +897,8 @@ class TestDatabaseResourceImpl extends ResourceBuilderBase): TestDatabaseResourcePromise {
+ return new TestDatabaseResourcePromiseImpl(this._withUnionDependencyInternal(dependency), this._client);
}
/** @internal */
@@ -903,7 +913,7 @@ class TestDatabaseResourceImpl extends ResourceBuilderBase): TestDatabaseResourcePromise {
- return new TestDatabaseResourcePromiseImpl(this._withEnvironmentVariablesInternal(variables));
+ return new TestDatabaseResourcePromiseImpl(this._withEnvironmentVariablesInternal(variables), this._client);
}
/** @internal */
@@ -937,7 +947,7 @@ class TestDatabaseResourceImpl extends ResourceBuilderBase Promise): TestDatabaseResourcePromise {
- return new TestDatabaseResourcePromiseImpl(this._withCancellableOperationInternal(operation));
+ return new TestDatabaseResourcePromiseImpl(this._withCancellableOperationInternal(operation), this._client);
}
/** @internal */
@@ -954,7 +964,7 @@ class TestDatabaseResourceImpl extends ResourceBuilderBase) {}
+ constructor(private _promise: Promise, private _client: AspireClientRpc, track = true) {
+ if (track) { _client.trackPromise(_promise); }
+ }
then(
onfulfilled?: ((value: TestDatabaseResource) => TResult1 | PromiseLike) | null,
@@ -1104,127 +1116,127 @@ class TestDatabaseResourcePromiseImpl implements TestDatabaseResourcePromise {
/** Adds an optional string parameter */
withOptionalString(options?: WithOptionalStringOptions): TestDatabaseResourcePromise {
- return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withOptionalString(options)));
+ return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withOptionalString(options)), this._client);
}
/** Configures the resource with a DTO */
withConfig(config: TestConfigDto): TestDatabaseResourcePromise {
- return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withConfig(config)));
+ return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withConfig(config)), this._client);
}
/** Configures environment with callback (test version) */
testWithEnvironmentCallback(callback: (arg: TestEnvironmentContext) => Promise): TestDatabaseResourcePromise {
- return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.testWithEnvironmentCallback(callback)));
+ return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.testWithEnvironmentCallback(callback)), this._client);
}
/** Sets the created timestamp */
withCreatedAt(createdAt: string): TestDatabaseResourcePromise {
- return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withCreatedAt(createdAt)));
+ return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withCreatedAt(createdAt)), this._client);
}
/** Sets the modified timestamp */
withModifiedAt(modifiedAt: string): TestDatabaseResourcePromise {
- return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withModifiedAt(modifiedAt)));
+ return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withModifiedAt(modifiedAt)), this._client);
}
/** Sets the correlation ID */
withCorrelationId(correlationId: string): TestDatabaseResourcePromise {
- return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withCorrelationId(correlationId)));
+ return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withCorrelationId(correlationId)), this._client);
}
/** Configures with optional callback */
withOptionalCallback(options?: WithOptionalCallbackOptions): TestDatabaseResourcePromise {
- return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withOptionalCallback(options)));
+ return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withOptionalCallback(options)), this._client);
}
/** Sets the resource status */
withStatus(status: TestResourceStatus): TestDatabaseResourcePromise {
- return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withStatus(status)));
+ return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withStatus(status)), this._client);
}
/** Configures with nested DTO */
withNestedConfig(config: TestNestedDto): TestDatabaseResourcePromise {
- return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withNestedConfig(config)));
+ return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withNestedConfig(config)), this._client);
}
/** Adds validation callback */
withValidator(validator: (arg: TestResourceContext) => Promise): TestDatabaseResourcePromise {
- return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withValidator(validator)));
+ return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withValidator(validator)), this._client);
}
/** Waits for another resource (test version) */
- testWaitFor(dependency: Resource | ResourceWithConnectionString | ResourceWithEnvironment | TestDatabaseResource | TestRedisResource | TestVaultResource): TestDatabaseResourcePromise {
- return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.testWaitFor(dependency)));
+ testWaitFor(dependency: Awaitable): TestDatabaseResourcePromise {
+ return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.testWaitFor(dependency)), this._client);
}
/** Adds a dependency on another resource */
- withDependency(dependency: ResourceWithConnectionString | TestRedisResource): TestDatabaseResourcePromise {
- return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withDependency(dependency)));
+ withDependency(dependency: Awaitable): TestDatabaseResourcePromise {
+ return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withDependency(dependency)), this._client);
}
/** Adds a dependency from a string or another resource */
- withUnionDependency(dependency: string | ResourceWithConnectionString | TestRedisResource): TestDatabaseResourcePromise {
- return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withUnionDependency(dependency)));
+ withUnionDependency(dependency: string | ResourceWithConnectionString | TestRedisResource | Awaitable): TestDatabaseResourcePromise {
+ return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withUnionDependency(dependency)), this._client);
}
/** Sets the endpoints */
withEndpoints(endpoints: string[]): TestDatabaseResourcePromise {
- return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withEndpoints(endpoints)));
+ return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withEndpoints(endpoints)), this._client);
}
/** Sets environment variables */
withEnvironmentVariables(variables: Record): TestDatabaseResourcePromise {
- return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withEnvironmentVariables(variables)));
+ return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withEnvironmentVariables(variables)), this._client);
}
/** Performs a cancellable operation */
withCancellableOperation(operation: (arg: CancellationToken) => Promise): TestDatabaseResourcePromise {
- return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withCancellableOperation(operation)));
+ return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withCancellableOperation(operation)), this._client);
}
/** Adds a data volume */
withDataVolume(options?: WithDataVolumeOptions): TestDatabaseResourcePromise {
- return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withDataVolume(options)));
+ return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withDataVolume(options)), this._client);
}
/** Adds a label to the resource */
withMergeLabel(label: string): TestDatabaseResourcePromise {
- return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withMergeLabel(label)));
+ return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withMergeLabel(label)), this._client);
}
/** Adds a categorized label to the resource */
withMergeLabelCategorized(label: string, category: string): TestDatabaseResourcePromise {
- return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withMergeLabelCategorized(label, category)));
+ return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withMergeLabelCategorized(label, category)), this._client);
}
/** Configures a named endpoint */
withMergeEndpoint(endpointName: string, port: number): TestDatabaseResourcePromise {
- return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withMergeEndpoint(endpointName, port)));
+ return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withMergeEndpoint(endpointName, port)), this._client);
}
/** Configures a named endpoint with scheme */
withMergeEndpointScheme(endpointName: string, port: number, scheme: string): TestDatabaseResourcePromise {
- return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withMergeEndpointScheme(endpointName, port, scheme)));
+ return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withMergeEndpointScheme(endpointName, port, scheme)), this._client);
}
/** Configures resource logging */
withMergeLogging(logLevel: string, options?: WithMergeLoggingOptions): TestDatabaseResourcePromise {
- return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withMergeLogging(logLevel, options)));
+ return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withMergeLogging(logLevel, options)), this._client);
}
/** Configures resource logging with file path */
withMergeLoggingPath(logLevel: string, logPath: string, options?: WithMergeLoggingPathOptions): TestDatabaseResourcePromise {
- return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withMergeLoggingPath(logLevel, logPath, options)));
+ return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withMergeLoggingPath(logLevel, logPath, options)), this._client);
}
/** Configures a route */
withMergeRoute(path: string, method: string, handler: string, priority: number): TestDatabaseResourcePromise {
- return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withMergeRoute(path, method, handler, priority)));
+ return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withMergeRoute(path, method, handler, priority)), this._client);
}
/** Configures a route with middleware */
withMergeRouteMiddleware(path: string, method: string, handler: string, priority: number, middleware: string): TestDatabaseResourcePromise {
- return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withMergeRouteMiddleware(path, method, handler, priority, middleware)));
+ return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.withMergeRouteMiddleware(path, method, handler, priority, middleware)), this._client);
}
}
@@ -1250,12 +1262,12 @@ export interface TestRedisResource {
withStatus(status: TestResourceStatus): TestRedisResourcePromise;
withNestedConfig(config: TestNestedDto): TestRedisResourcePromise;
withValidator(validator: (arg: TestResourceContext) => Promise): TestRedisResourcePromise;
- testWaitFor(dependency: Resource | ResourceWithConnectionString | ResourceWithEnvironment | TestDatabaseResource | TestRedisResource | TestVaultResource): TestRedisResourcePromise;
+ testWaitFor(dependency: Awaitable): TestRedisResourcePromise;
getEndpoints(): Promise;
withConnectionStringDirect(connectionString: string): TestRedisResourcePromise;
withRedisSpecific(option: string): TestRedisResourcePromise;
- withDependency(dependency: ResourceWithConnectionString | TestRedisResource): TestRedisResourcePromise;
- withUnionDependency(dependency: string | ResourceWithConnectionString | TestRedisResource): TestRedisResourcePromise;
+ withDependency(dependency: Awaitable): TestRedisResourcePromise;
+ withUnionDependency(dependency: string | ResourceWithConnectionString | TestRedisResource | Awaitable): TestRedisResourcePromise;
withEndpoints(endpoints: string[]): TestRedisResourcePromise;
withEnvironmentVariables(variables: Record): TestRedisResourcePromise;
getStatusAsync(options?: GetStatusAsyncOptions): Promise;
@@ -1289,12 +1301,12 @@ export interface TestRedisResourcePromise extends PromiseLike
withStatus(status: TestResourceStatus): TestRedisResourcePromise;
withNestedConfig(config: TestNestedDto): TestRedisResourcePromise;
withValidator(validator: (arg: TestResourceContext) => Promise): TestRedisResourcePromise;
- testWaitFor(dependency: Resource | ResourceWithConnectionString | ResourceWithEnvironment | TestDatabaseResource | TestRedisResource | TestVaultResource): TestRedisResourcePromise;
+ testWaitFor(dependency: Awaitable): TestRedisResourcePromise;
getEndpoints(): Promise;
withConnectionStringDirect(connectionString: string): TestRedisResourcePromise;
withRedisSpecific(option: string): TestRedisResourcePromise;
- withDependency(dependency: ResourceWithConnectionString | TestRedisResource): TestRedisResourcePromise;
- withUnionDependency(dependency: string | ResourceWithConnectionString | TestRedisResource): TestRedisResourcePromise;
+ withDependency(dependency: Awaitable): TestRedisResourcePromise;
+ withUnionDependency(dependency: string | ResourceWithConnectionString | TestRedisResource | Awaitable): TestRedisResourcePromise;
withEndpoints(endpoints: string[]): TestRedisResourcePromise;
withEnvironmentVariables(variables: Record): TestRedisResourcePromise;
getStatusAsync(options?: GetStatusAsyncOptions): Promise;
@@ -1335,7 +1347,7 @@ class TestRedisResourceImpl extends ResourceBuilderBase
/** Adds a child database to a test Redis resource */
addTestChildDatabase(name: string, options?: AddTestChildDatabaseOptions): TestDatabaseResourcePromise {
const databaseName = options?.databaseName;
- return new TestDatabaseResourcePromiseImpl(this._addTestChildDatabaseInternal(name, databaseName));
+ return new TestDatabaseResourcePromiseImpl(this._addTestChildDatabaseInternal(name, databaseName), this._client);
}
/** @internal */
@@ -1352,7 +1364,7 @@ class TestRedisResourceImpl extends ResourceBuilderBase
/** Configures the Redis resource with persistence */
withPersistence(options?: WithPersistenceOptions): TestRedisResourcePromise {
const mode = options?.mode;
- return new TestRedisResourcePromiseImpl(this._withPersistenceInternal(mode));
+ return new TestRedisResourcePromiseImpl(this._withPersistenceInternal(mode), this._client);
}
/** @internal */
@@ -1371,7 +1383,7 @@ class TestRedisResourceImpl extends ResourceBuilderBase
withOptionalString(options?: WithOptionalStringOptions): TestRedisResourcePromise {
const value = options?.value;
const enabled = options?.enabled;
- return new TestRedisResourcePromiseImpl(this._withOptionalStringInternal(value, enabled));
+ return new TestRedisResourcePromiseImpl(this._withOptionalStringInternal(value, enabled), this._client);
}
/** @internal */
@@ -1386,7 +1398,7 @@ class TestRedisResourceImpl extends ResourceBuilderBase
/** Configures the resource with a DTO */
withConfig(config: TestConfigDto): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._withConfigInternal(config));
+ return new TestRedisResourcePromiseImpl(this._withConfigInternal(config), this._client);
}
/** Gets the tags for the resource */
@@ -1419,7 +1431,7 @@ class TestRedisResourceImpl extends ResourceBuilderBase
/** Sets the connection string using a reference expression */
withConnectionString(connectionString: ReferenceExpression): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._withConnectionStringInternal(connectionString));
+ return new TestRedisResourcePromiseImpl(this._withConnectionStringInternal(connectionString), this._client);
}
/** @internal */
@@ -1439,7 +1451,7 @@ class TestRedisResourceImpl extends ResourceBuilderBase
/** Configures environment with callback (test version) */
testWithEnvironmentCallback(callback: (arg: TestEnvironmentContext) => Promise): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._testWithEnvironmentCallbackInternal(callback));
+ return new TestRedisResourcePromiseImpl(this._testWithEnvironmentCallbackInternal(callback), this._client);
}
/** @internal */
@@ -1454,7 +1466,7 @@ class TestRedisResourceImpl extends ResourceBuilderBase
/** Sets the created timestamp */
withCreatedAt(createdAt: string): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._withCreatedAtInternal(createdAt));
+ return new TestRedisResourcePromiseImpl(this._withCreatedAtInternal(createdAt), this._client);
}
/** @internal */
@@ -1469,7 +1481,7 @@ class TestRedisResourceImpl extends ResourceBuilderBase
/** Sets the modified timestamp */
withModifiedAt(modifiedAt: string): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._withModifiedAtInternal(modifiedAt));
+ return new TestRedisResourcePromiseImpl(this._withModifiedAtInternal(modifiedAt), this._client);
}
/** @internal */
@@ -1484,7 +1496,7 @@ class TestRedisResourceImpl extends ResourceBuilderBase
/** Sets the correlation ID */
withCorrelationId(correlationId: string): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._withCorrelationIdInternal(correlationId));
+ return new TestRedisResourcePromiseImpl(this._withCorrelationIdInternal(correlationId), this._client);
}
/** @internal */
@@ -1506,7 +1518,7 @@ class TestRedisResourceImpl extends ResourceBuilderBase
/** Configures with optional callback */
withOptionalCallback(options?: WithOptionalCallbackOptions): TestRedisResourcePromise {
const callback = options?.callback;
- return new TestRedisResourcePromiseImpl(this._withOptionalCallbackInternal(callback));
+ return new TestRedisResourcePromiseImpl(this._withOptionalCallbackInternal(callback), this._client);
}
/** @internal */
@@ -1521,7 +1533,7 @@ class TestRedisResourceImpl extends ResourceBuilderBase
/** Sets the resource status */
withStatus(status: TestResourceStatus): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._withStatusInternal(status));
+ return new TestRedisResourcePromiseImpl(this._withStatusInternal(status), this._client);
}
/** @internal */
@@ -1536,7 +1548,7 @@ class TestRedisResourceImpl extends ResourceBuilderBase
/** Configures with nested DTO */
withNestedConfig(config: TestNestedDto): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._withNestedConfigInternal(config));
+ return new TestRedisResourcePromiseImpl(this._withNestedConfigInternal(config), this._client);
}
/** @internal */
@@ -1556,11 +1568,12 @@ class TestRedisResourceImpl extends ResourceBuilderBase
/** Adds validation callback */
withValidator(validator: (arg: TestResourceContext) => Promise): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._withValidatorInternal(validator));
+ return new TestRedisResourcePromiseImpl(this._withValidatorInternal(validator), this._client);
}
/** @internal */
- private async _testWaitForInternal(dependency: Resource | ResourceWithConnectionString | ResourceWithEnvironment | TestDatabaseResource | TestRedisResource | TestVaultResource): Promise {
+ private async _testWaitForInternal(dependency: Awaitable): Promise {
+ dependency = isPromiseLike(dependency) ? await dependency : dependency;
const rpcArgs: Record = { builder: this._handle, dependency };
const result = await this._client.invokeCapability(
'Aspire.Hosting.CodeGeneration.TypeScript.Tests/testWaitFor',
@@ -1570,8 +1583,8 @@ class TestRedisResourceImpl extends ResourceBuilderBase
}
/** Waits for another resource (test version) */
- testWaitFor(dependency: Resource | ResourceWithConnectionString | ResourceWithEnvironment | TestDatabaseResource | TestRedisResource | TestVaultResource): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._testWaitForInternal(dependency));
+ testWaitFor(dependency: Awaitable): TestRedisResourcePromise {
+ return new TestRedisResourcePromiseImpl(this._testWaitForInternal(dependency), this._client);
}
/** Gets the endpoints */
@@ -1595,7 +1608,7 @@ class TestRedisResourceImpl extends ResourceBuilderBase
/** Sets connection string using direct interface target */
withConnectionStringDirect(connectionString: string): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._withConnectionStringDirectInternal(connectionString));
+ return new TestRedisResourcePromiseImpl(this._withConnectionStringDirectInternal(connectionString), this._client);
}
/** @internal */
@@ -1610,11 +1623,12 @@ class TestRedisResourceImpl extends ResourceBuilderBase
/** Redis-specific configuration */
withRedisSpecific(option: string): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._withRedisSpecificInternal(option));
+ return new TestRedisResourcePromiseImpl(this._withRedisSpecificInternal(option), this._client);
}
/** @internal */
- private async _withDependencyInternal(dependency: ResourceWithConnectionString | TestRedisResource): Promise {
+ private async _withDependencyInternal(dependency: Awaitable): Promise {
+ dependency = isPromiseLike(dependency) ? await dependency : dependency;
const rpcArgs: Record = { builder: this._handle, dependency };
const result = await this._client.invokeCapability(
'Aspire.Hosting.CodeGeneration.TypeScript.Tests/withDependency',
@@ -1624,12 +1638,13 @@ class TestRedisResourceImpl extends ResourceBuilderBase
}
/** Adds a dependency on another resource */
- withDependency(dependency: ResourceWithConnectionString | TestRedisResource): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._withDependencyInternal(dependency));
+ withDependency(dependency: Awaitable): TestRedisResourcePromise {
+ return new TestRedisResourcePromiseImpl(this._withDependencyInternal(dependency), this._client);
}
/** @internal */
- private async _withUnionDependencyInternal(dependency: string | ResourceWithConnectionString | TestRedisResource): Promise {
+ private async _withUnionDependencyInternal(dependency: string | ResourceWithConnectionString | TestRedisResource | Awaitable): Promise {
+ dependency = isPromiseLike(dependency) ? await dependency : dependency;
const rpcArgs: Record = { builder: this._handle, dependency };
const result = await this._client.invokeCapability(
'Aspire.Hosting.CodeGeneration.TypeScript.Tests/withUnionDependency',
@@ -1639,8 +1654,8 @@ class TestRedisResourceImpl extends ResourceBuilderBase
}
/** Adds a dependency from a string or another resource */
- withUnionDependency(dependency: string | ResourceWithConnectionString | TestRedisResource): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._withUnionDependencyInternal(dependency));
+ withUnionDependency(dependency: string | ResourceWithConnectionString | TestRedisResource | Awaitable): TestRedisResourcePromise {
+ return new TestRedisResourcePromiseImpl(this._withUnionDependencyInternal(dependency), this._client);
}
/** @internal */
@@ -1655,7 +1670,7 @@ class TestRedisResourceImpl extends ResourceBuilderBase
/** Sets the endpoints */
withEndpoints(endpoints: string[]): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._withEndpointsInternal(endpoints));
+ return new TestRedisResourcePromiseImpl(this._withEndpointsInternal(endpoints), this._client);
}
/** @internal */
@@ -1670,7 +1685,7 @@ class TestRedisResourceImpl extends ResourceBuilderBase
/** Sets environment variables */
withEnvironmentVariables(variables: Record): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._withEnvironmentVariablesInternal(variables));
+ return new TestRedisResourcePromiseImpl(this._withEnvironmentVariablesInternal(variables), this._client);
}
/** Gets the status of the resource asynchronously */
@@ -1700,7 +1715,7 @@ class TestRedisResourceImpl extends ResourceBuilderBase
/** Performs a cancellable operation */
withCancellableOperation(operation: (arg: CancellationToken) => Promise): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._withCancellableOperationInternal(operation));
+ return new TestRedisResourcePromiseImpl(this._withCancellableOperationInternal(operation), this._client);
}
/** Waits for the resource to be ready */
@@ -1733,7 +1748,7 @@ class TestRedisResourceImpl extends ResourceBuilderBase
/** Tests multi-param callback destructuring */
withMultiParamHandleCallback(callback: (arg1: TestCallbackContext, arg2: TestEnvironmentContext) => Promise): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._withMultiParamHandleCallbackInternal(callback));
+ return new TestRedisResourcePromiseImpl(this._withMultiParamHandleCallbackInternal(callback), this._client);
}
/** @internal */
@@ -1752,7 +1767,7 @@ class TestRedisResourceImpl extends ResourceBuilderBase
withDataVolume(options?: WithDataVolumeOptions): TestRedisResourcePromise {
const name = options?.name;
const isReadOnly = options?.isReadOnly;
- return new TestRedisResourcePromiseImpl(this._withDataVolumeInternal(name, isReadOnly));
+ return new TestRedisResourcePromiseImpl(this._withDataVolumeInternal(name, isReadOnly), this._client);
}
/** @internal */
@@ -1767,7 +1782,7 @@ class TestRedisResourceImpl extends ResourceBuilderBase
/** Adds a label to the resource */
withMergeLabel(label: string): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._withMergeLabelInternal(label));
+ return new TestRedisResourcePromiseImpl(this._withMergeLabelInternal(label), this._client);
}
/** @internal */
@@ -1782,7 +1797,7 @@ class TestRedisResourceImpl extends ResourceBuilderBase
/** Adds a categorized label to the resource */
withMergeLabelCategorized(label: string, category: string): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._withMergeLabelCategorizedInternal(label, category));
+ return new TestRedisResourcePromiseImpl(this._withMergeLabelCategorizedInternal(label, category), this._client);
}
/** @internal */
@@ -1797,7 +1812,7 @@ class TestRedisResourceImpl extends ResourceBuilderBase
/** Configures a named endpoint */
withMergeEndpoint(endpointName: string, port: number): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._withMergeEndpointInternal(endpointName, port));
+ return new TestRedisResourcePromiseImpl(this._withMergeEndpointInternal(endpointName, port), this._client);
}
/** @internal */
@@ -1812,7 +1827,7 @@ class TestRedisResourceImpl extends ResourceBuilderBase
/** Configures a named endpoint with scheme */
withMergeEndpointScheme(endpointName: string, port: number, scheme: string): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._withMergeEndpointSchemeInternal(endpointName, port, scheme));
+ return new TestRedisResourcePromiseImpl(this._withMergeEndpointSchemeInternal(endpointName, port, scheme), this._client);
}
/** @internal */
@@ -1831,7 +1846,7 @@ class TestRedisResourceImpl extends ResourceBuilderBase
withMergeLogging(logLevel: string, options?: WithMergeLoggingOptions): TestRedisResourcePromise {
const enableConsole = options?.enableConsole;
const maxFiles = options?.maxFiles;
- return new TestRedisResourcePromiseImpl(this._withMergeLoggingInternal(logLevel, enableConsole, maxFiles));
+ return new TestRedisResourcePromiseImpl(this._withMergeLoggingInternal(logLevel, enableConsole, maxFiles), this._client);
}
/** @internal */
@@ -1850,7 +1865,7 @@ class TestRedisResourceImpl extends ResourceBuilderBase
withMergeLoggingPath(logLevel: string, logPath: string, options?: WithMergeLoggingPathOptions): TestRedisResourcePromise {
const enableConsole = options?.enableConsole;
const maxFiles = options?.maxFiles;
- return new TestRedisResourcePromiseImpl(this._withMergeLoggingPathInternal(logLevel, logPath, enableConsole, maxFiles));
+ return new TestRedisResourcePromiseImpl(this._withMergeLoggingPathInternal(logLevel, logPath, enableConsole, maxFiles), this._client);
}
/** @internal */
@@ -1865,7 +1880,7 @@ class TestRedisResourceImpl extends ResourceBuilderBase
/** Configures a route */
withMergeRoute(path: string, method: string, handler: string, priority: number): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._withMergeRouteInternal(path, method, handler, priority));
+ return new TestRedisResourcePromiseImpl(this._withMergeRouteInternal(path, method, handler, priority), this._client);
}
/** @internal */
@@ -1880,7 +1895,7 @@ class TestRedisResourceImpl extends ResourceBuilderBase
/** Configures a route with middleware */
withMergeRouteMiddleware(path: string, method: string, handler: string, priority: number, middleware: string): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._withMergeRouteMiddlewareInternal(path, method, handler, priority, middleware));
+ return new TestRedisResourcePromiseImpl(this._withMergeRouteMiddlewareInternal(path, method, handler, priority, middleware), this._client);
}
}
@@ -1891,7 +1906,9 @@ class TestRedisResourceImpl extends ResourceBuilderBase
* await builder.addSomething().withX().withY();
*/
class TestRedisResourcePromiseImpl implements TestRedisResourcePromise {
- constructor(private _promise: Promise) {}
+ constructor(private _promise: Promise, private _client: AspireClientRpc, track = true) {
+ if (track) { _client.trackPromise(_promise); }
+ }
then(
onfulfilled?: ((value: TestRedisResource) => TResult1 | PromiseLike) | null,
@@ -1902,22 +1919,22 @@ class TestRedisResourcePromiseImpl implements TestRedisResourcePromise {
/** Adds a child database to a test Redis resource */
addTestChildDatabase(name: string, options?: AddTestChildDatabaseOptions): TestDatabaseResourcePromise {
- return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.addTestChildDatabase(name, options)));
+ return new TestDatabaseResourcePromiseImpl(this._promise.then(obj => obj.addTestChildDatabase(name, options)), this._client);
}
/** Configures the Redis resource with persistence */
withPersistence(options?: WithPersistenceOptions): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withPersistence(options)));
+ return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withPersistence(options)), this._client);
}
/** Adds an optional string parameter */
withOptionalString(options?: WithOptionalStringOptions): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withOptionalString(options)));
+ return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withOptionalString(options)), this._client);
}
/** Configures the resource with a DTO */
withConfig(config: TestConfigDto): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withConfig(config)));
+ return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withConfig(config)), this._client);
}
/** Gets the tags for the resource */
@@ -1932,52 +1949,52 @@ class TestRedisResourcePromiseImpl implements TestRedisResourcePromise {
/** Sets the connection string using a reference expression */
withConnectionString(connectionString: ReferenceExpression): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withConnectionString(connectionString)));
+ return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withConnectionString(connectionString)), this._client);
}
/** Configures environment with callback (test version) */
testWithEnvironmentCallback(callback: (arg: TestEnvironmentContext) => Promise): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.testWithEnvironmentCallback(callback)));
+ return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.testWithEnvironmentCallback(callback)), this._client);
}
/** Sets the created timestamp */
withCreatedAt(createdAt: string): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withCreatedAt(createdAt)));
+ return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withCreatedAt(createdAt)), this._client);
}
/** Sets the modified timestamp */
withModifiedAt(modifiedAt: string): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withModifiedAt(modifiedAt)));
+ return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withModifiedAt(modifiedAt)), this._client);
}
/** Sets the correlation ID */
withCorrelationId(correlationId: string): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withCorrelationId(correlationId)));
+ return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withCorrelationId(correlationId)), this._client);
}
/** Configures with optional callback */
withOptionalCallback(options?: WithOptionalCallbackOptions): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withOptionalCallback(options)));
+ return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withOptionalCallback(options)), this._client);
}
/** Sets the resource status */
withStatus(status: TestResourceStatus): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withStatus(status)));
+ return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withStatus(status)), this._client);
}
/** Configures with nested DTO */
withNestedConfig(config: TestNestedDto): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withNestedConfig(config)));
+ return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withNestedConfig(config)), this._client);
}
/** Adds validation callback */
withValidator(validator: (arg: TestResourceContext) => Promise): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withValidator(validator)));
+ return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withValidator(validator)), this._client);
}
/** Waits for another resource (test version) */
- testWaitFor(dependency: Resource | ResourceWithConnectionString | ResourceWithEnvironment | TestDatabaseResource | TestRedisResource | TestVaultResource): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.testWaitFor(dependency)));
+ testWaitFor(dependency: Awaitable): TestRedisResourcePromise {
+ return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.testWaitFor(dependency)), this._client);
}
/** Gets the endpoints */
@@ -1987,32 +2004,32 @@ class TestRedisResourcePromiseImpl implements TestRedisResourcePromise {
/** Sets connection string using direct interface target */
withConnectionStringDirect(connectionString: string): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withConnectionStringDirect(connectionString)));
+ return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withConnectionStringDirect(connectionString)), this._client);
}
/** Redis-specific configuration */
withRedisSpecific(option: string): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withRedisSpecific(option)));
+ return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withRedisSpecific(option)), this._client);
}
/** Adds a dependency on another resource */
- withDependency(dependency: ResourceWithConnectionString | TestRedisResource): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withDependency(dependency)));
+ withDependency(dependency: Awaitable): TestRedisResourcePromise {
+ return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withDependency(dependency)), this._client);
}
/** Adds a dependency from a string or another resource */
- withUnionDependency(dependency: string | ResourceWithConnectionString | TestRedisResource): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withUnionDependency(dependency)));
+ withUnionDependency(dependency: string | ResourceWithConnectionString | TestRedisResource | Awaitable): TestRedisResourcePromise {
+ return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withUnionDependency(dependency)), this._client);
}
/** Sets the endpoints */
withEndpoints(endpoints: string[]): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withEndpoints(endpoints)));
+ return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withEndpoints(endpoints)), this._client);
}
/** Sets environment variables */
withEnvironmentVariables(variables: Record): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withEnvironmentVariables(variables)));
+ return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withEnvironmentVariables(variables)), this._client);
}
/** Gets the status of the resource asynchronously */
@@ -2022,7 +2039,7 @@ class TestRedisResourcePromiseImpl implements TestRedisResourcePromise {
/** Performs a cancellable operation */
withCancellableOperation(operation: (arg: CancellationToken) => Promise): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withCancellableOperation(operation)));
+ return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withCancellableOperation(operation)), this._client);
}
/** Waits for the resource to be ready */
@@ -2032,52 +2049,52 @@ class TestRedisResourcePromiseImpl implements TestRedisResourcePromise {
/** Tests multi-param callback destructuring */
withMultiParamHandleCallback(callback: (arg1: TestCallbackContext, arg2: TestEnvironmentContext) => Promise): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withMultiParamHandleCallback(callback)));
+ return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withMultiParamHandleCallback(callback)), this._client);
}
/** Adds a data volume with persistence */
withDataVolume(options?: WithDataVolumeOptions): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withDataVolume(options)));
+ return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withDataVolume(options)), this._client);
}
/** Adds a label to the resource */
withMergeLabel(label: string): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withMergeLabel(label)));
+ return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withMergeLabel(label)), this._client);
}
/** Adds a categorized label to the resource */
withMergeLabelCategorized(label: string, category: string): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withMergeLabelCategorized(label, category)));
+ return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withMergeLabelCategorized(label, category)), this._client);
}
/** Configures a named endpoint */
withMergeEndpoint(endpointName: string, port: number): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withMergeEndpoint(endpointName, port)));
+ return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withMergeEndpoint(endpointName, port)), this._client);
}
/** Configures a named endpoint with scheme */
withMergeEndpointScheme(endpointName: string, port: number, scheme: string): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withMergeEndpointScheme(endpointName, port, scheme)));
+ return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withMergeEndpointScheme(endpointName, port, scheme)), this._client);
}
/** Configures resource logging */
withMergeLogging(logLevel: string, options?: WithMergeLoggingOptions): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withMergeLogging(logLevel, options)));
+ return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withMergeLogging(logLevel, options)), this._client);
}
/** Configures resource logging with file path */
withMergeLoggingPath(logLevel: string, logPath: string, options?: WithMergeLoggingPathOptions): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withMergeLoggingPath(logLevel, logPath, options)));
+ return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withMergeLoggingPath(logLevel, logPath, options)), this._client);
}
/** Configures a route */
withMergeRoute(path: string, method: string, handler: string, priority: number): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withMergeRoute(path, method, handler, priority)));
+ return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withMergeRoute(path, method, handler, priority)), this._client);
}
/** Configures a route with middleware */
withMergeRouteMiddleware(path: string, method: string, handler: string, priority: number, middleware: string): TestRedisResourcePromise {
- return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withMergeRouteMiddleware(path, method, handler, priority, middleware)));
+ return new TestRedisResourcePromiseImpl(this._promise.then(obj => obj.withMergeRouteMiddleware(path, method, handler, priority, middleware)), this._client);
}
}
@@ -2098,9 +2115,9 @@ export interface TestVaultResource {
withStatus(status: TestResourceStatus): TestVaultResourcePromise;
withNestedConfig(config: TestNestedDto): TestVaultResourcePromise;
withValidator(validator: (arg: TestResourceContext) => Promise): TestVaultResourcePromise;
- testWaitFor(dependency: Resource | ResourceWithConnectionString | ResourceWithEnvironment | TestDatabaseResource | TestRedisResource | TestVaultResource): TestVaultResourcePromise;
- withDependency(dependency: ResourceWithConnectionString | TestRedisResource): TestVaultResourcePromise;
- withUnionDependency(dependency: string | ResourceWithConnectionString | TestRedisResource): TestVaultResourcePromise;
+ testWaitFor(dependency: Awaitable): TestVaultResourcePromise;
+ withDependency(dependency: Awaitable): TestVaultResourcePromise;
+ withUnionDependency(dependency: string | ResourceWithConnectionString | TestRedisResource | Awaitable): TestVaultResourcePromise;
withEndpoints(endpoints: string[]): TestVaultResourcePromise;
withEnvironmentVariables(variables: Record): TestVaultResourcePromise;
withCancellableOperation(operation: (arg: CancellationToken) => Promise): TestVaultResourcePromise;
@@ -2126,9 +2143,9 @@ export interface TestVaultResourcePromise extends PromiseLike
withStatus(status: TestResourceStatus): TestVaultResourcePromise;
withNestedConfig(config: TestNestedDto): TestVaultResourcePromise;
withValidator(validator: (arg: TestResourceContext) => Promise): TestVaultResourcePromise;
- testWaitFor(dependency: Resource | ResourceWithConnectionString | ResourceWithEnvironment | TestDatabaseResource | TestRedisResource | TestVaultResource): TestVaultResourcePromise;
- withDependency(dependency: ResourceWithConnectionString | TestRedisResource): TestVaultResourcePromise;
- withUnionDependency(dependency: string | ResourceWithConnectionString | TestRedisResource): TestVaultResourcePromise;
+ testWaitFor(dependency: Awaitable): TestVaultResourcePromise;
+ withDependency(dependency: Awaitable): TestVaultResourcePromise;
+ withUnionDependency(dependency: string | ResourceWithConnectionString | TestRedisResource | Awaitable): TestVaultResourcePromise;
withEndpoints(endpoints: string[]): TestVaultResourcePromise;
withEnvironmentVariables(variables: Record): TestVaultResourcePromise;
withCancellableOperation(operation: (arg: CancellationToken) => Promise): TestVaultResourcePromise;
@@ -2168,7 +2185,7 @@ class TestVaultResourceImpl extends ResourceBuilderBase
withOptionalString(options?: WithOptionalStringOptions): TestVaultResourcePromise {
const value = options?.value;
const enabled = options?.enabled;
- return new TestVaultResourcePromiseImpl(this._withOptionalStringInternal(value, enabled));
+ return new TestVaultResourcePromiseImpl(this._withOptionalStringInternal(value, enabled), this._client);
}
/** @internal */
@@ -2183,7 +2200,7 @@ class TestVaultResourceImpl extends ResourceBuilderBase
/** Configures the resource with a DTO */
withConfig(config: TestConfigDto): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._withConfigInternal(config));
+ return new TestVaultResourcePromiseImpl(this._withConfigInternal(config), this._client);
}
/** @internal */
@@ -2203,7 +2220,7 @@ class TestVaultResourceImpl extends ResourceBuilderBase
/** Configures environment with callback (test version) */
testWithEnvironmentCallback(callback: (arg: TestEnvironmentContext) => Promise): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._testWithEnvironmentCallbackInternal(callback));
+ return new TestVaultResourcePromiseImpl(this._testWithEnvironmentCallbackInternal(callback), this._client);
}
/** @internal */
@@ -2218,7 +2235,7 @@ class TestVaultResourceImpl extends ResourceBuilderBase
/** Sets the created timestamp */
withCreatedAt(createdAt: string): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._withCreatedAtInternal(createdAt));
+ return new TestVaultResourcePromiseImpl(this._withCreatedAtInternal(createdAt), this._client);
}
/** @internal */
@@ -2233,7 +2250,7 @@ class TestVaultResourceImpl extends ResourceBuilderBase
/** Sets the modified timestamp */
withModifiedAt(modifiedAt: string): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._withModifiedAtInternal(modifiedAt));
+ return new TestVaultResourcePromiseImpl(this._withModifiedAtInternal(modifiedAt), this._client);
}
/** @internal */
@@ -2248,7 +2265,7 @@ class TestVaultResourceImpl extends ResourceBuilderBase
/** Sets the correlation ID */
withCorrelationId(correlationId: string): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._withCorrelationIdInternal(correlationId));
+ return new TestVaultResourcePromiseImpl(this._withCorrelationIdInternal(correlationId), this._client);
}
/** @internal */
@@ -2270,7 +2287,7 @@ class TestVaultResourceImpl extends ResourceBuilderBase
/** Configures with optional callback */
withOptionalCallback(options?: WithOptionalCallbackOptions): TestVaultResourcePromise {
const callback = options?.callback;
- return new TestVaultResourcePromiseImpl(this._withOptionalCallbackInternal(callback));
+ return new TestVaultResourcePromiseImpl(this._withOptionalCallbackInternal(callback), this._client);
}
/** @internal */
@@ -2285,7 +2302,7 @@ class TestVaultResourceImpl extends ResourceBuilderBase
/** Sets the resource status */
withStatus(status: TestResourceStatus): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._withStatusInternal(status));
+ return new TestVaultResourcePromiseImpl(this._withStatusInternal(status), this._client);
}
/** @internal */
@@ -2300,7 +2317,7 @@ class TestVaultResourceImpl extends ResourceBuilderBase
/** Configures with nested DTO */
withNestedConfig(config: TestNestedDto): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._withNestedConfigInternal(config));
+ return new TestVaultResourcePromiseImpl(this._withNestedConfigInternal(config), this._client);
}
/** @internal */
@@ -2320,11 +2337,12 @@ class TestVaultResourceImpl extends ResourceBuilderBase
/** Adds validation callback */
withValidator(validator: (arg: TestResourceContext) => Promise): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._withValidatorInternal(validator));
+ return new TestVaultResourcePromiseImpl(this._withValidatorInternal(validator), this._client);
}
/** @internal */
- private async _testWaitForInternal(dependency: Resource | ResourceWithConnectionString | ResourceWithEnvironment | TestDatabaseResource | TestRedisResource | TestVaultResource): Promise {
+ private async _testWaitForInternal(dependency: Awaitable): Promise {
+ dependency = isPromiseLike(dependency) ? await dependency : dependency;
const rpcArgs: Record = { builder: this._handle, dependency };
const result = await this._client.invokeCapability(
'Aspire.Hosting.CodeGeneration.TypeScript.Tests/testWaitFor',
@@ -2334,12 +2352,13 @@ class TestVaultResourceImpl extends ResourceBuilderBase
}
/** Waits for another resource (test version) */
- testWaitFor(dependency: Resource | ResourceWithConnectionString | ResourceWithEnvironment | TestDatabaseResource | TestRedisResource | TestVaultResource): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._testWaitForInternal(dependency));
+ testWaitFor(dependency: Awaitable): TestVaultResourcePromise {
+ return new TestVaultResourcePromiseImpl(this._testWaitForInternal(dependency), this._client);
}
/** @internal */
- private async _withDependencyInternal(dependency: ResourceWithConnectionString | TestRedisResource): Promise {
+ private async _withDependencyInternal(dependency: Awaitable): Promise {
+ dependency = isPromiseLike(dependency) ? await dependency : dependency;
const rpcArgs: Record = { builder: this._handle, dependency };
const result = await this._client.invokeCapability(
'Aspire.Hosting.CodeGeneration.TypeScript.Tests/withDependency',
@@ -2349,12 +2368,13 @@ class TestVaultResourceImpl extends ResourceBuilderBase
}
/** Adds a dependency on another resource */
- withDependency(dependency: ResourceWithConnectionString | TestRedisResource): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._withDependencyInternal(dependency));
+ withDependency(dependency: Awaitable): TestVaultResourcePromise {
+ return new TestVaultResourcePromiseImpl(this._withDependencyInternal(dependency), this._client);
}
/** @internal */
- private async _withUnionDependencyInternal(dependency: string | ResourceWithConnectionString | TestRedisResource): Promise {
+ private async _withUnionDependencyInternal(dependency: string | ResourceWithConnectionString | TestRedisResource | Awaitable): Promise {
+ dependency = isPromiseLike(dependency) ? await dependency : dependency;
const rpcArgs: Record = { builder: this._handle, dependency };
const result = await this._client.invokeCapability(
'Aspire.Hosting.CodeGeneration.TypeScript.Tests/withUnionDependency',
@@ -2364,8 +2384,8 @@ class TestVaultResourceImpl extends ResourceBuilderBase
}
/** Adds a dependency from a string or another resource */
- withUnionDependency(dependency: string | ResourceWithConnectionString | TestRedisResource): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._withUnionDependencyInternal(dependency));
+ withUnionDependency(dependency: string | ResourceWithConnectionString | TestRedisResource | Awaitable): TestVaultResourcePromise {
+ return new TestVaultResourcePromiseImpl(this._withUnionDependencyInternal(dependency), this._client);
}
/** @internal */
@@ -2380,7 +2400,7 @@ class TestVaultResourceImpl extends ResourceBuilderBase
/** Sets the endpoints */
withEndpoints(endpoints: string[]): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._withEndpointsInternal(endpoints));
+ return new TestVaultResourcePromiseImpl(this._withEndpointsInternal(endpoints), this._client);
}
/** @internal */
@@ -2395,7 +2415,7 @@ class TestVaultResourceImpl extends ResourceBuilderBase
/** Sets environment variables */
withEnvironmentVariables(variables: Record): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._withEnvironmentVariablesInternal(variables));
+ return new TestVaultResourcePromiseImpl(this._withEnvironmentVariablesInternal(variables), this._client);
}
/** @internal */
@@ -2414,7 +2434,7 @@ class TestVaultResourceImpl extends ResourceBuilderBase
/** Performs a cancellable operation */
withCancellableOperation(operation: (arg: CancellationToken) => Promise): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._withCancellableOperationInternal(operation));
+ return new TestVaultResourcePromiseImpl(this._withCancellableOperationInternal(operation), this._client);
}
/** @internal */
@@ -2429,7 +2449,7 @@ class TestVaultResourceImpl extends ResourceBuilderBase
/** Configures vault using direct interface target */
withVaultDirect(option: string): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._withVaultDirectInternal(option));
+ return new TestVaultResourcePromiseImpl(this._withVaultDirectInternal(option), this._client);
}
/** @internal */
@@ -2444,7 +2464,7 @@ class TestVaultResourceImpl extends ResourceBuilderBase
/** Adds a label to the resource */
withMergeLabel(label: string): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._withMergeLabelInternal(label));
+ return new TestVaultResourcePromiseImpl(this._withMergeLabelInternal(label), this._client);
}
/** @internal */
@@ -2459,7 +2479,7 @@ class TestVaultResourceImpl extends ResourceBuilderBase
/** Adds a categorized label to the resource */
withMergeLabelCategorized(label: string, category: string): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._withMergeLabelCategorizedInternal(label, category));
+ return new TestVaultResourcePromiseImpl(this._withMergeLabelCategorizedInternal(label, category), this._client);
}
/** @internal */
@@ -2474,7 +2494,7 @@ class TestVaultResourceImpl extends ResourceBuilderBase
/** Configures a named endpoint */
withMergeEndpoint(endpointName: string, port: number): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._withMergeEndpointInternal(endpointName, port));
+ return new TestVaultResourcePromiseImpl(this._withMergeEndpointInternal(endpointName, port), this._client);
}
/** @internal */
@@ -2489,7 +2509,7 @@ class TestVaultResourceImpl extends ResourceBuilderBase
/** Configures a named endpoint with scheme */
withMergeEndpointScheme(endpointName: string, port: number, scheme: string): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._withMergeEndpointSchemeInternal(endpointName, port, scheme));
+ return new TestVaultResourcePromiseImpl(this._withMergeEndpointSchemeInternal(endpointName, port, scheme), this._client);
}
/** @internal */
@@ -2508,7 +2528,7 @@ class TestVaultResourceImpl extends ResourceBuilderBase
withMergeLogging(logLevel: string, options?: WithMergeLoggingOptions): TestVaultResourcePromise {
const enableConsole = options?.enableConsole;
const maxFiles = options?.maxFiles;
- return new TestVaultResourcePromiseImpl(this._withMergeLoggingInternal(logLevel, enableConsole, maxFiles));
+ return new TestVaultResourcePromiseImpl(this._withMergeLoggingInternal(logLevel, enableConsole, maxFiles), this._client);
}
/** @internal */
@@ -2527,7 +2547,7 @@ class TestVaultResourceImpl extends ResourceBuilderBase
withMergeLoggingPath(logLevel: string, logPath: string, options?: WithMergeLoggingPathOptions): TestVaultResourcePromise {
const enableConsole = options?.enableConsole;
const maxFiles = options?.maxFiles;
- return new TestVaultResourcePromiseImpl(this._withMergeLoggingPathInternal(logLevel, logPath, enableConsole, maxFiles));
+ return new TestVaultResourcePromiseImpl(this._withMergeLoggingPathInternal(logLevel, logPath, enableConsole, maxFiles), this._client);
}
/** @internal */
@@ -2542,7 +2562,7 @@ class TestVaultResourceImpl extends ResourceBuilderBase
/** Configures a route */
withMergeRoute(path: string, method: string, handler: string, priority: number): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._withMergeRouteInternal(path, method, handler, priority));
+ return new TestVaultResourcePromiseImpl(this._withMergeRouteInternal(path, method, handler, priority), this._client);
}
/** @internal */
@@ -2557,7 +2577,7 @@ class TestVaultResourceImpl extends ResourceBuilderBase
/** Configures a route with middleware */
withMergeRouteMiddleware(path: string, method: string, handler: string, priority: number, middleware: string): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._withMergeRouteMiddlewareInternal(path, method, handler, priority, middleware));
+ return new TestVaultResourcePromiseImpl(this._withMergeRouteMiddlewareInternal(path, method, handler, priority, middleware), this._client);
}
}
@@ -2568,7 +2588,9 @@ class TestVaultResourceImpl extends ResourceBuilderBase
* await builder.addSomething().withX().withY();
*/
class TestVaultResourcePromiseImpl implements TestVaultResourcePromise {
- constructor(private _promise: Promise) {}
+ constructor(private _promise: Promise, private _client: AspireClientRpc, track = true) {
+ if (track) { _client.trackPromise(_promise); }
+ }
then(
onfulfilled?: ((value: TestVaultResource) => TResult1 | PromiseLike) | null,
@@ -2579,127 +2601,127 @@ class TestVaultResourcePromiseImpl implements TestVaultResourcePromise {
/** Adds an optional string parameter */
withOptionalString(options?: WithOptionalStringOptions): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withOptionalString(options)));
+ return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withOptionalString(options)), this._client);
}
/** Configures the resource with a DTO */
withConfig(config: TestConfigDto): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withConfig(config)));
+ return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withConfig(config)), this._client);
}
/** Configures environment with callback (test version) */
testWithEnvironmentCallback(callback: (arg: TestEnvironmentContext) => Promise): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.testWithEnvironmentCallback(callback)));
+ return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.testWithEnvironmentCallback(callback)), this._client);
}
/** Sets the created timestamp */
withCreatedAt(createdAt: string): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withCreatedAt(createdAt)));
+ return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withCreatedAt(createdAt)), this._client);
}
/** Sets the modified timestamp */
withModifiedAt(modifiedAt: string): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withModifiedAt(modifiedAt)));
+ return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withModifiedAt(modifiedAt)), this._client);
}
/** Sets the correlation ID */
withCorrelationId(correlationId: string): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withCorrelationId(correlationId)));
+ return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withCorrelationId(correlationId)), this._client);
}
/** Configures with optional callback */
withOptionalCallback(options?: WithOptionalCallbackOptions): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withOptionalCallback(options)));
+ return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withOptionalCallback(options)), this._client);
}
/** Sets the resource status */
withStatus(status: TestResourceStatus): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withStatus(status)));
+ return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withStatus(status)), this._client);
}
/** Configures with nested DTO */
withNestedConfig(config: TestNestedDto): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withNestedConfig(config)));
+ return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withNestedConfig(config)), this._client);
}
/** Adds validation callback */
withValidator(validator: (arg: TestResourceContext) => Promise): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withValidator(validator)));
+ return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withValidator(validator)), this._client);
}
/** Waits for another resource (test version) */
- testWaitFor(dependency: Resource | ResourceWithConnectionString | ResourceWithEnvironment | TestDatabaseResource | TestRedisResource | TestVaultResource): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.testWaitFor(dependency)));
+ testWaitFor(dependency: Awaitable): TestVaultResourcePromise {
+ return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.testWaitFor(dependency)), this._client);
}
/** Adds a dependency on another resource */
- withDependency(dependency: ResourceWithConnectionString | TestRedisResource): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withDependency(dependency)));
+ withDependency(dependency: Awaitable): TestVaultResourcePromise {
+ return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withDependency(dependency)), this._client);
}
/** Adds a dependency from a string or another resource */
- withUnionDependency(dependency: string | ResourceWithConnectionString | TestRedisResource): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withUnionDependency(dependency)));
+ withUnionDependency(dependency: string | ResourceWithConnectionString | TestRedisResource | Awaitable): TestVaultResourcePromise {
+ return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withUnionDependency(dependency)), this._client);
}
/** Sets the endpoints */
withEndpoints(endpoints: string[]): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withEndpoints(endpoints)));
+ return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withEndpoints(endpoints)), this._client);
}
/** Sets environment variables */
withEnvironmentVariables(variables: Record): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withEnvironmentVariables(variables)));
+ return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withEnvironmentVariables(variables)), this._client);
}
/** Performs a cancellable operation */
withCancellableOperation(operation: (arg: CancellationToken) => Promise): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withCancellableOperation(operation)));
+ return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withCancellableOperation(operation)), this._client);
}
/** Configures vault using direct interface target */
withVaultDirect(option: string): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withVaultDirect(option)));
+ return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withVaultDirect(option)), this._client);
}
/** Adds a label to the resource */
withMergeLabel(label: string): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withMergeLabel(label)));
+ return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withMergeLabel(label)), this._client);
}
/** Adds a categorized label to the resource */
withMergeLabelCategorized(label: string, category: string): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withMergeLabelCategorized(label, category)));
+ return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withMergeLabelCategorized(label, category)), this._client);
}
/** Configures a named endpoint */
withMergeEndpoint(endpointName: string, port: number): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withMergeEndpoint(endpointName, port)));
+ return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withMergeEndpoint(endpointName, port)), this._client);
}
/** Configures a named endpoint with scheme */
withMergeEndpointScheme(endpointName: string, port: number, scheme: string): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withMergeEndpointScheme(endpointName, port, scheme)));
+ return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withMergeEndpointScheme(endpointName, port, scheme)), this._client);
}
/** Configures resource logging */
withMergeLogging(logLevel: string, options?: WithMergeLoggingOptions): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withMergeLogging(logLevel, options)));
+ return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withMergeLogging(logLevel, options)), this._client);
}
/** Configures resource logging with file path */
withMergeLoggingPath(logLevel: string, logPath: string, options?: WithMergeLoggingPathOptions): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withMergeLoggingPath(logLevel, logPath, options)));
+ return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withMergeLoggingPath(logLevel, logPath, options)), this._client);
}
/** Configures a route */
withMergeRoute(path: string, method: string, handler: string, priority: number): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withMergeRoute(path, method, handler, priority)));
+ return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withMergeRoute(path, method, handler, priority)), this._client);
}
/** Configures a route with middleware */
withMergeRouteMiddleware(path: string, method: string, handler: string, priority: number, middleware: string): TestVaultResourcePromise {
- return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withMergeRouteMiddleware(path, method, handler, priority, middleware)));
+ return new TestVaultResourcePromiseImpl(this._promise.then(obj => obj.withMergeRouteMiddleware(path, method, handler, priority, middleware)), this._client);
}
}
@@ -2719,9 +2741,9 @@ export interface Resource {
withStatus(status: TestResourceStatus): ResourcePromise;
withNestedConfig(config: TestNestedDto): ResourcePromise;
withValidator(validator: (arg: TestResourceContext) => Promise): ResourcePromise;
- testWaitFor(dependency: Resource | ResourceWithConnectionString | ResourceWithEnvironment | TestDatabaseResource | TestRedisResource | TestVaultResource): ResourcePromise;
- withDependency(dependency: ResourceWithConnectionString | TestRedisResource): ResourcePromise;
- withUnionDependency(dependency: string | ResourceWithConnectionString | TestRedisResource): ResourcePromise;
+ testWaitFor(dependency: Awaitable): ResourcePromise;
+ withDependency(dependency: Awaitable