Skip to content

Advanced Usage

In this section, we’ll explore more advanced features of the library, including managing dependencies between containers and handling asynchronous tasks. These features allow you to build more complex container configurations and manage the execution flow in larger applications.

Example 1: Using Dependencies

Here’s an example that demonstrates how to set up containers with dependencies. In this example, we create two containers: userContainer, which fetches user data, and accountsContainer, which depends on userContainer to retrieve the user’s accounts.

import { createContainer, compose } from '@grlt-hub/app-compose';
const user = createContainer({
id: 'user',
start: () => {
console.log('User data loaded');
return { api: { userId: 'user123' } };
},
});
const accounts = createContainer({
id: 'accounts',
dependsOn: [user],
start: (deps) => {
console.log('Accounts loaded for user:', deps.user.userId);
return { api: null };
},
});
compose.up([user, accounts]);

Expected Result

In this example, you should see the following output in the console:

Terminal window
User data loaded
Accounts loaded for user: user123

This output confirms that accountsContainer started only after userContainer initialized and provided the necessary userId.

Example 2: Handling Asynchronous Tasks

You can also manage asynchronous operations within containers. Here’s an example where userContainer performs an asynchronous data fetch for user information, and accountsContainer depends on this data to load user accounts.

import { createContainer, compose } from '@grlt-hub/app-compose';
import { api } from 'shared/transport';
const user = createContainer({
id: 'user',
start: async () => {
const userData = await api.fetchUser();
console.log('User data loaded:', userData);
return { api: { userId: userData.id } };
},
});
const accounts = createContainer({
id: 'accounts',
dependsOn: [user],
start: async (deps) => {
const accounts = await api.fetchAccounts(deps.user.userId);
console.log('Accounts loaded for user:', deps.user.userId, accounts);
return { api: null };
},
});
await compose.up([user, accounts]);

Why await compose.up?

In this example, we use await with compose.up to ensure that all containers complete their asynchronous operations before continuing. Since compose.up returns a promise, awaiting it allows you to know when all containers have finished their startup processes.

The promise returned by compose.up provides an object with the following structure:

type ComposeResult = Promise<{
hasErrors: boolean; // Indicates if any containers encountered errors during compose.up
statuses: Record<ContainerId, ContainerStatus>; // A status record for each container
}>;

Expected Result

When you run this code, you should see output similar to:

Terminal window
User data loaded: { id: 'user123', name: 'Alice' }
Accounts loaded for user: user123 [{ id: 1, balance: 100 }, { id: 2, balance: 200 }]

This confirms that accountsContainer waits for userContainer to complete and uses the fetched user data to load the accounts.

Summary

In Advanced Usage, we explored how to create containers with dependencies and perform asynchronous operations. These features provide more flexibility and control over container initialization, making it easier to manage complex configurations in larger applications.