55using CommunityToolkit . Aspire . SurrealDb ;
66using Microsoft . Extensions . DependencyInjection ;
77using Microsoft . Extensions . Diagnostics . HealthChecks ;
8+ using Microsoft . Extensions . Logging ;
89using SurrealDb . Net ;
10+ using SurrealDb . Net . Models . Response ;
911using System . Text ;
1012using System . Text . Json ;
1113
@@ -80,6 +82,59 @@ public static IResourceBuilder<SurrealDbServerResource> AddSurrealServer(
8082 : SurrealDbContainerImageTags . Tag ;
8183
8284 var surrealServer = new SurrealDbServerResource ( name , userName ? . Resource , passwordParameter ) ;
85+
86+ string ? connectionString = null ;
87+
88+ builder . Eventing . Subscribe < ConnectionStringAvailableEvent > ( surrealServer , async ( _ , ct ) =>
89+ {
90+ connectionString = await surrealServer . GetConnectionStringAsync ( ct ) . ConfigureAwait ( false ) ;
91+
92+ if ( connectionString == null )
93+ {
94+ throw new DistributedApplicationException ( $ "ConnectionStringAvailableEvent was published for the '{ surrealServer . Name } ' resource but the connection string was null.") ;
95+ }
96+ } ) ;
97+
98+ builder . Eventing . Subscribe < ResourceReadyEvent > ( surrealServer , async ( @event , ct ) =>
99+ {
100+ if ( connectionString is null )
101+ {
102+ throw new DistributedApplicationException ( $ "ResourceReadyEvent was published for the '{ surrealServer . Name } ' resource but the connection string was null.") ;
103+ }
104+
105+ if ( ! strictMode )
106+ {
107+ return ;
108+ }
109+
110+ var options = new SurrealDbOptionsBuilder ( ) . FromConnectionString ( connectionString ) . Build ( ) ;
111+ await using var surrealClient = new SurrealDbClient ( options ) ;
112+
113+ foreach ( var nsResourceName in surrealServer . Namespaces . Keys )
114+ {
115+ if ( builder . Resources . FirstOrDefault ( n =>
116+ string . Equals ( n . Name , nsResourceName , StringComparison . OrdinalIgnoreCase ) ) is
117+ SurrealDbNamespaceResource surrealDbNamespace )
118+ {
119+ await CreateNamespaceAsync ( surrealClient , surrealDbNamespace , @event . Services , ct )
120+ . ConfigureAwait ( false ) ;
121+
122+ await surrealClient . Use ( surrealDbNamespace . NamespaceName , null ! , ct ) . ConfigureAwait ( false ) ;
123+
124+ foreach ( var dbResourceName in surrealDbNamespace . Databases . Keys )
125+ {
126+ if ( builder . Resources . FirstOrDefault ( n =>
127+ string . Equals ( n . Name , dbResourceName , StringComparison . OrdinalIgnoreCase ) ) is
128+ SurrealDbDatabaseResource surrealDbDatabase )
129+ {
130+ await CreateDatabaseAsync ( surrealClient , surrealDbDatabase , @event . Services , ct )
131+ . ConfigureAwait ( false ) ;
132+ }
133+ }
134+ }
135+ }
136+ } ) ;
137+
83138 return builder . AddResource ( surrealServer )
84139 . WithEndpoint ( port : port , targetPort : SurrealDbPort , name : SurrealDbServerResource . PrimaryEndpointName )
85140 . WithImage ( SurrealDbContainerImageTags . Image , imageTag )
@@ -132,6 +187,25 @@ public static IResourceBuilder<SurrealDbNamespaceResource> AddNamespace(
132187 var surrealServerNamespace = new SurrealDbNamespaceResource ( name , namespaceName , builder . Resource ) ;
133188 return builder . ApplicationBuilder . AddResource ( surrealServerNamespace ) ;
134189 }
190+
191+ /// <summary>
192+ /// Defines the SQL script used to create the namespace.
193+ /// </summary>
194+ /// <param name="builder">The builder for the <see cref="SurrealDbNamespaceResource"/>.</param>
195+ /// <param name="script">The SQL script used to create the namespace.</param>
196+ /// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
197+ /// <remarks>
198+ /// <value>Default script is <code>DEFINE NAMESPACE IF NOT EXISTS `QUOTED_NAMESPACE_NAME`;</code></value>
199+ /// </remarks>
200+ public static IResourceBuilder < SurrealDbNamespaceResource > WithCreationScript ( this IResourceBuilder < SurrealDbNamespaceResource > builder , string script )
201+ {
202+ ArgumentNullException . ThrowIfNull ( builder ) ;
203+ ArgumentNullException . ThrowIfNull ( script ) ;
204+
205+ builder . WithAnnotation ( new SurrealDbCreateNamespaceScriptAnnotation ( script ) ) ;
206+
207+ return builder ;
208+ }
135209
136210 /// <summary>
137211 /// Adds a SurrealDB database to the application model. This is a child resource of a <see cref="SurrealDbNamespaceResource"/>.
@@ -202,6 +276,25 @@ public static IResourceBuilder<SurrealDbDatabaseResource> AddDatabase(
202276 return builder . ApplicationBuilder . AddResource ( surrealServerDatabase )
203277 . WithHealthCheck ( healthCheckKey ) ;
204278 }
279+
280+ /// <summary>
281+ /// Defines the SQL script used to create the database.
282+ /// </summary>
283+ /// <param name="builder">The builder for the <see cref="SurrealDbDatabaseResource"/>.</param>
284+ /// <param name="script">The SQL script used to create the database.</param>
285+ /// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
286+ /// <remarks>
287+ /// <value>Default script is <code>DEFINE DATABASE IF NOT EXISTS `QUOTED_DATABASE_NAME`;</code></value>
288+ /// </remarks>
289+ public static IResourceBuilder < SurrealDbDatabaseResource > WithCreationScript ( this IResourceBuilder < SurrealDbDatabaseResource > builder , string script )
290+ {
291+ ArgumentNullException . ThrowIfNull ( builder ) ;
292+ ArgumentNullException . ThrowIfNull ( script ) ;
293+
294+ builder . WithAnnotation ( new SurrealDbCreateDatabaseScriptAnnotation ( script ) ) ;
295+
296+ return builder ;
297+ }
205298
206299 /// <summary>
207300 /// Adds a named volume for the data folder to a SurrealDB resource.
@@ -447,4 +540,62 @@ CancellationToken cancellationToken
447540
448541 return Encoding . UTF8 . GetString ( stream . ToArray ( ) ) ;
449542 }
543+
544+ private static async Task CreateNamespaceAsync (
545+ SurrealDbClient surrealClient ,
546+ SurrealDbNamespaceResource namespaceResource ,
547+ IServiceProvider serviceProvider ,
548+ CancellationToken cancellationToken
549+ )
550+ {
551+ var scriptAnnotation = namespaceResource . Annotations . OfType < SurrealDbCreateNamespaceScriptAnnotation > ( ) . LastOrDefault ( ) ;
552+
553+ var logger = serviceProvider . GetRequiredService < ResourceLoggerService > ( ) . GetLogger ( namespaceResource . Parent ) ;
554+ logger . LogDebug ( "Creating namespace '{NamespaceName}'" , namespaceResource . NamespaceName ) ;
555+
556+ try
557+ {
558+ var response = await surrealClient . RawQuery (
559+ scriptAnnotation ? . Script ?? $ "DEFINE NAMESPACE IF NOT EXISTS `{ namespaceResource . NamespaceName } `;",
560+ cancellationToken : cancellationToken
561+ ) . ConfigureAwait ( false ) ;
562+
563+ response . EnsureAllOks ( ) ;
564+
565+ logger . LogDebug ( "Namespace '{NamespaceName}' created successfully" , namespaceResource . NamespaceName ) ;
566+ }
567+ catch ( Exception e )
568+ {
569+ logger . LogError ( e , "Failed to create namespace '{NamespaceName}'" , namespaceResource . NamespaceName ) ;
570+ }
571+ }
572+
573+ private static async Task CreateDatabaseAsync (
574+ SurrealDbClient surrealClient ,
575+ SurrealDbDatabaseResource databaseResource ,
576+ IServiceProvider serviceProvider ,
577+ CancellationToken cancellationToken
578+ )
579+ {
580+ var scriptAnnotation = databaseResource . Annotations . OfType < SurrealDbCreateDatabaseScriptAnnotation > ( ) . LastOrDefault ( ) ;
581+
582+ var logger = serviceProvider . GetRequiredService < ResourceLoggerService > ( ) . GetLogger ( databaseResource . Parent . Parent ) ;
583+ logger . LogDebug ( "Creating database '{DatabaseName}'" , databaseResource . DatabaseName ) ;
584+
585+ try
586+ {
587+ var response = await surrealClient . RawQuery (
588+ scriptAnnotation ? . Script ?? $ "DEFINE DATABASE IF NOT EXISTS `{ databaseResource . DatabaseName } `;",
589+ cancellationToken : cancellationToken
590+ ) . ConfigureAwait ( false ) ;
591+
592+ response . EnsureAllOks ( ) ;
593+
594+ logger . LogDebug ( "Database '{DatabaseName}' created successfully" , databaseResource . DatabaseName ) ;
595+ }
596+ catch ( Exception e )
597+ {
598+ logger . LogError ( e , "Failed to create database '{DatabaseName}'" , databaseResource . DatabaseName ) ;
599+ }
600+ }
450601}
0 commit comments