55using CommunityToolkit . Aspire . SurrealDb ;
66using Microsoft . Extensions . DependencyInjection ;
77using Microsoft . Extensions . Diagnostics . HealthChecks ;
8+ using Microsoft . Extensions . Logging ;
89using SurrealDb . Net ;
910using System . Text ;
1011using System . Text . Json ;
@@ -80,6 +81,7 @@ public static IResourceBuilder<SurrealDbServerResource> AddSurrealServer(
8081 : SurrealDbContainerImageTags . Tag ;
8182
8283 var surrealServer = new SurrealDbServerResource ( name , userName ? . Resource , passwordParameter ) ;
84+
8385 return builder . AddResource ( surrealServer )
8486 . WithEndpoint ( port : port , targetPort : SurrealDbPort , name : SurrealDbServerResource . PrimaryEndpointName )
8587 . WithImage ( SurrealDbContainerImageTags . Image , imageTag )
@@ -90,7 +92,47 @@ public static IResourceBuilder<SurrealDbServerResource> AddSurrealServer(
9092 context . EnvironmentVariables [ PasswordEnvVarName ] = surrealServer . PasswordParameter ;
9193 } )
9294 . WithEntrypoint ( "/surreal" )
93- . WithArgs ( [ .. args ] ) ;
95+ . WithArgs ( [ .. args ] )
96+ . OnResourceReady ( async ( _ , @event , ct ) =>
97+ {
98+ if ( ! strictMode )
99+ {
100+ return ;
101+ }
102+
103+ var connectionString = await surrealServer . GetConnectionStringAsync ( ct ) . ConfigureAwait ( false ) ;
104+ if ( connectionString is null )
105+ {
106+ throw new DistributedApplicationException ( $ "ResourceReadyEvent was published for the '{ surrealServer . Name } ' resource but the connection string was null.") ;
107+ }
108+
109+ var options = new SurrealDbOptionsBuilder ( ) . FromConnectionString ( connectionString ) . Build ( ) ;
110+ await using var surrealClient = new SurrealDbClient ( options ) ;
111+
112+ foreach ( var nsResourceName in surrealServer . Namespaces . Keys )
113+ {
114+ if ( builder . Resources . FirstOrDefault ( n =>
115+ string . Equals ( n . Name , nsResourceName , StringComparison . OrdinalIgnoreCase ) ) is
116+ SurrealDbNamespaceResource surrealDbNamespace )
117+ {
118+ await CreateNamespaceAsync ( surrealClient , surrealDbNamespace , @event . Services , ct )
119+ . ConfigureAwait ( false ) ;
120+
121+ await surrealClient . Use ( surrealDbNamespace . NamespaceName , null ! , ct ) . ConfigureAwait ( false ) ;
122+
123+ foreach ( var dbResourceName in surrealDbNamespace . Databases . Keys )
124+ {
125+ if ( builder . Resources . FirstOrDefault ( n =>
126+ string . Equals ( n . Name , dbResourceName , StringComparison . OrdinalIgnoreCase ) ) is
127+ SurrealDbDatabaseResource surrealDbDatabase )
128+ {
129+ await CreateDatabaseAsync ( surrealClient , surrealDbDatabase , @event . Services , ct )
130+ . ConfigureAwait ( false ) ;
131+ }
132+ }
133+ }
134+ }
135+ } ) ;
94136 }
95137
96138 /// <summary>
@@ -132,6 +174,25 @@ public static IResourceBuilder<SurrealDbNamespaceResource> AddNamespace(
132174 var surrealServerNamespace = new SurrealDbNamespaceResource ( name , namespaceName , builder . Resource ) ;
133175 return builder . ApplicationBuilder . AddResource ( surrealServerNamespace ) ;
134176 }
177+
178+ /// <summary>
179+ /// Defines the SQL script used to create the namespace.
180+ /// </summary>
181+ /// <param name="builder">The builder for the <see cref="SurrealDbNamespaceResource"/>.</param>
182+ /// <param name="script">The SQL script used to create the namespace.</param>
183+ /// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
184+ /// <remarks>
185+ /// <value>Default script is <code>DEFINE NAMESPACE IF NOT EXISTS `QUOTED_NAMESPACE_NAME`;</code></value>
186+ /// </remarks>
187+ public static IResourceBuilder < SurrealDbNamespaceResource > WithCreationScript ( this IResourceBuilder < SurrealDbNamespaceResource > builder , string script )
188+ {
189+ ArgumentNullException . ThrowIfNull ( builder ) ;
190+ ArgumentNullException . ThrowIfNull ( script ) ;
191+
192+ builder . WithAnnotation ( new SurrealDbCreateNamespaceScriptAnnotation ( script ) ) ;
193+
194+ return builder ;
195+ }
135196
136197 /// <summary>
137198 /// Adds a SurrealDB database to the application model. This is a child resource of a <see cref="SurrealDbNamespaceResource"/>.
@@ -202,6 +263,25 @@ public static IResourceBuilder<SurrealDbDatabaseResource> AddDatabase(
202263 return builder . ApplicationBuilder . AddResource ( surrealServerDatabase )
203264 . WithHealthCheck ( healthCheckKey ) ;
204265 }
266+
267+ /// <summary>
268+ /// Defines the SQL script used to create the database.
269+ /// </summary>
270+ /// <param name="builder">The builder for the <see cref="SurrealDbDatabaseResource"/>.</param>
271+ /// <param name="script">The SQL script used to create the database.</param>
272+ /// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
273+ /// <remarks>
274+ /// <value>Default script is <code>DEFINE DATABASE IF NOT EXISTS `QUOTED_DATABASE_NAME`;</code></value>
275+ /// </remarks>
276+ public static IResourceBuilder < SurrealDbDatabaseResource > WithCreationScript ( this IResourceBuilder < SurrealDbDatabaseResource > builder , string script )
277+ {
278+ ArgumentNullException . ThrowIfNull ( builder ) ;
279+ ArgumentNullException . ThrowIfNull ( script ) ;
280+
281+ builder . WithAnnotation ( new SurrealDbCreateDatabaseScriptAnnotation ( script ) ) ;
282+
283+ return builder ;
284+ }
205285
206286 /// <summary>
207287 /// Adds a named volume for the data folder to a SurrealDB resource.
@@ -447,4 +527,62 @@ CancellationToken cancellationToken
447527
448528 return Encoding . UTF8 . GetString ( stream . ToArray ( ) ) ;
449529 }
530+
531+ private static async Task CreateNamespaceAsync (
532+ SurrealDbClient surrealClient ,
533+ SurrealDbNamespaceResource namespaceResource ,
534+ IServiceProvider serviceProvider ,
535+ CancellationToken cancellationToken
536+ )
537+ {
538+ var scriptAnnotation = namespaceResource . Annotations . OfType < SurrealDbCreateNamespaceScriptAnnotation > ( ) . LastOrDefault ( ) ;
539+
540+ var logger = serviceProvider . GetRequiredService < ResourceLoggerService > ( ) . GetLogger ( namespaceResource . Parent ) ;
541+ logger . LogDebug ( "Creating namespace '{NamespaceName}'" , namespaceResource . NamespaceName ) ;
542+
543+ try
544+ {
545+ var response = await surrealClient . RawQuery (
546+ scriptAnnotation ? . Script ?? $ "DEFINE NAMESPACE IF NOT EXISTS `{ namespaceResource . NamespaceName } `;",
547+ cancellationToken : cancellationToken
548+ ) . ConfigureAwait ( false ) ;
549+
550+ response . EnsureAllOks ( ) ;
551+
552+ logger . LogDebug ( "Namespace '{NamespaceName}' created successfully" , namespaceResource . NamespaceName ) ;
553+ }
554+ catch ( Exception e )
555+ {
556+ logger . LogError ( e , "Failed to create namespace '{NamespaceName}'" , namespaceResource . NamespaceName ) ;
557+ }
558+ }
559+
560+ private static async Task CreateDatabaseAsync (
561+ SurrealDbClient surrealClient ,
562+ SurrealDbDatabaseResource databaseResource ,
563+ IServiceProvider serviceProvider ,
564+ CancellationToken cancellationToken
565+ )
566+ {
567+ var scriptAnnotation = databaseResource . Annotations . OfType < SurrealDbCreateDatabaseScriptAnnotation > ( ) . LastOrDefault ( ) ;
568+
569+ var logger = serviceProvider . GetRequiredService < ResourceLoggerService > ( ) . GetLogger ( databaseResource . Parent . Parent ) ;
570+ logger . LogDebug ( "Creating database '{DatabaseName}'" , databaseResource . DatabaseName ) ;
571+
572+ try
573+ {
574+ var response = await surrealClient . RawQuery (
575+ scriptAnnotation ? . Script ?? $ "DEFINE DATABASE IF NOT EXISTS `{ databaseResource . DatabaseName } `;",
576+ cancellationToken : cancellationToken
577+ ) . ConfigureAwait ( false ) ;
578+
579+ response . EnsureAllOks ( ) ;
580+
581+ logger . LogDebug ( "Database '{DatabaseName}' created successfully" , databaseResource . DatabaseName ) ;
582+ }
583+ catch ( Exception e )
584+ {
585+ logger . LogError ( e , "Failed to create database '{DatabaseName}'" , databaseResource . DatabaseName ) ;
586+ }
587+ }
450588}
0 commit comments