Skip to content

Commit 3eaec16

Browse files
committed
Merge branch 'redesign-config-loading' into add-core
2 parents 241f920 + 6ad896d commit 3eaec16

34 files changed

+2092
-994
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2424
* `isBindingTuple` util in `@aedart/container`.
2525
* `test:fast` script in `packages.json`, to allow testing without (re)transpiling all packages.
2626
* `@types/jasmine` as development dependency (_in root package only_).
27+
* `@babel/plugin-syntax-dynamic-import` as development dependency (_in root package, for testing_).
2728

2829
### Changed
2930

babel.config.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
],
1818
"@babel/plugin-proposal-class-properties",
1919
"@babel/plugin-proposal-private-methods",
20-
"@babel/plugin-transform-class-static-block"
20+
"@babel/plugin-transform-class-static-block",
21+
"@babel/plugin-syntax-dynamic-import"
2122
]
2223
}

package-lock.json

+1,536-951
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"@babel/plugin-proposal-decorators": "^7.24.7",
3030
"@babel/plugin-proposal-private-methods": "^7.18.6",
3131
"@babel/plugin-transform-class-static-block": "^7.24.7",
32+
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
3233
"@babel/preset-env": "^7.25.4",
3334
"@babel/runtime": "^7.25.6",
3435
"@lerna-lite/changed": "^3.9.1",

packages/config/rollup.config.mjs

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export default createConfig({
55
external: [
66
'@aedart/contracts/config',
77
'@aedart/contracts/container',
8+
'@aedart/contracts/core',
89
'@aedart/contracts/support',
910
'@aedart/contracts/support/arrays',
1011
'@aedart/contracts/support/container',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import type { ResolveException } from "@aedart/contracts/config";
2+
import { configureCustomError } from "@aedart/support/exceptions";
3+
4+
/**
5+
* Configuration Resolve Error
6+
*
7+
* @see {ResolveException}
8+
*/
9+
export default class ResolveError extends Error implements ResolveException
10+
{
11+
/**
12+
* Create new Resolve Error instance
13+
*
14+
* @param {string} [message]
15+
* @param {ErrorOptions} [options]
16+
*/
17+
constructor(message?: string, options?: ErrorOptions)
18+
{
19+
super(message, options);
20+
21+
configureCustomError(this);
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import type { UnsupportedSourceException } from "@aedart/contracts/config";
2+
import { configureCustomError } from "@aedart/support/exceptions";
3+
import ResolveError from "./ResolveError";
4+
5+
/**
6+
* Unsupported Configuration Source Error
7+
*
8+
* @see {UnsupportedSourceException}
9+
*/
10+
export default class UnsupportedSourceError extends ResolveError implements UnsupportedSourceException
11+
{
12+
/**
13+
* Create new Unsupported Source Error instance
14+
*
15+
* @param {string} [message]
16+
* @param {ErrorOptions} [options]
17+
*/
18+
constructor(message?: string, options?: ErrorOptions)
19+
{
20+
super(message, options);
21+
22+
configureCustomError(this);
23+
}
24+
}
+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import ResolveError from './ResolveError';
2+
import UnsupportedSourceError from "./UnsupportedSourceError";
3+
export {
4+
ResolveError,
5+
UnsupportedSourceError
6+
}

packages/config/src/index.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,6 @@ export {
33
Repository
44
}
55

6-
export * from './providers/index';
6+
export * from './resolvers/index';
7+
export * from './providers/index';
8+
export * from './exceptions/index';

packages/config/src/providers/ConfigServiceProvider.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { CONFIG } from "@aedart/contracts/config";
1+
import { CONFIG, CONFIG_RESOLVER } from "@aedart/contracts/config";
22
import { ServiceProvider } from "@aedart/support/services";
33
import Repository from '../Repository';
4+
import Resolver from '../resolvers/DefaultResolver';
45

56
/**
67
* Configuration Service Provider
@@ -14,6 +15,8 @@ export default class ConfigServiceProvider extends ServiceProvider
1415
{
1516
this.app
1617
.singleton(CONFIG, Repository)
17-
.alias(CONFIG, 'config');
18+
.alias(CONFIG, 'config')
19+
20+
.singleton(CONFIG_RESOLVER, Resolver);
1821
}
1922
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import type { Items, Resolver, Source, ResolveCallback } from "@aedart/contracts/config";
2+
import { isPromise, isCallable } from "@aedart/support/reflections";
3+
import { getErrorMessage } from "@aedart/support/exceptions";
4+
import ResolveError from "../exceptions/ResolveError";
5+
import UnsupportedSourceError from "../exceptions/UnsupportedSourceError";
6+
7+
/**
8+
* Default Configuration Resolver
9+
*
10+
* @see {Resolver}
11+
*/
12+
export default class DefaultResolver implements Resolver
13+
{
14+
/**
15+
* Resolves configuration items from the given source
16+
*
17+
* @param {Source} source
18+
*
19+
* @returns {Promise<Items>}
20+
*
21+
* @throws {ResolveException}
22+
*/
23+
public async resolve(source: Source): Promise<Items>
24+
{
25+
if (isPromise(source)) {
26+
return this.resolveItems(source as Promise<Items>);
27+
}
28+
29+
if (typeof source === 'object') {
30+
return this.resolveItems(new Promise<Items>((resolve) => {
31+
resolve(source as Items);
32+
}));
33+
}
34+
35+
if (isCallable(source)) {
36+
return this.resolveItems((source as ResolveCallback)());
37+
}
38+
39+
throw new UnsupportedSourceError('Unable to resolve configuration items from source', { cause: { source: source } });
40+
}
41+
42+
/**
43+
* Resolves configuration items from the given promise
44+
*
45+
* @param {Promise<Items>} promise
46+
*
47+
* @returns {Promise<Items>}
48+
*
49+
* @protected
50+
*/
51+
protected async resolveItems(promise: Promise<Items>): Promise<Items>
52+
{
53+
try {
54+
return await promise;
55+
} catch (e) {
56+
const reason: string = getErrorMessage(e);
57+
throw new ResolveError(`Unable to resolve configuration items: ${reason}`, { cause: { source: promise } });
58+
}
59+
}
60+
}
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import DefaultResolver from "./DefaultResolver";
2+
export {
3+
DefaultResolver
4+
}
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Items, Source } from './types';
2+
3+
/**
4+
* Configuration Resolver
5+
*/
6+
export default interface Resolver
7+
{
8+
/**
9+
* Resolves configuration items from the given source
10+
*
11+
* @param {Source} source
12+
*
13+
* @returns {Promise<Items>}
14+
*
15+
* @throws {ResolveException}
16+
*/
17+
resolve(source: Source): Promise<Items>;
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Throwable } from "@aedart/contracts/support/exceptions";
2+
3+
/**
4+
* Configuration Resolve Exception
5+
*
6+
* To be thrown whenever configuration [items]{@link import('@aedart/contracts/config').Items}
7+
* cannot be resolved.
8+
*/
9+
export default interface ResolveException extends Throwable
10+
{}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import ResolveException from './ResolveException';
2+
3+
/**
4+
* Unsupported Configuration Source Exception
5+
*
6+
* To be thrown whenever a [Resolver]{@link import('@aedart/contracts/config').Resolver} is unable to
7+
* resolve configuration items, due to an unsupported [source]{@link import('@aedart/contracts/config').Source}.
8+
*/
9+
export default interface UnsupportedSourceException extends ResolveException
10+
{}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import ResolveException from "./ResolveException";
2+
import UnsupportedSourceException from "./UnsupportedSourceException";
3+
export {
4+
type ResolveException,
5+
type UnsupportedSourceException
6+
}

packages/contracts/src/config/index.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,19 @@
55
*/
66
export const CONFIG: unique symbol = Symbol('@aedart/contracts/config');
77

8+
/**
9+
* Configuration Resolver identifier
10+
*
11+
* @type {Symbol}
12+
*/
13+
export const CONFIG_RESOLVER: unique symbol = Symbol('@aedart/contracts/config/resolver');
14+
815
import Repository from './Repository';
16+
import Resolver from './Resolver';
917
export {
10-
type Repository
18+
type Repository,
19+
type Resolver
1120
}
1221

22+
export * from './exceptions/index';
1323
export type * from './types';
+12-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
/**
2-
* Configuration Items
3-
*
4-
* A general type for describing a key-value store containing configuration items
5-
* for an application or component.
2+
* A key-value store containing configuration items for an application, its service, and or component.
63
*/
7-
export type Items = Record<PropertyKey, any>; /* eslint-disable-line @typescript-eslint/no-explicit-any */
4+
export type Items = Record<PropertyKey, any>; /* eslint-disable-line @typescript-eslint/no-explicit-any */
5+
6+
/**
7+
* Callback that is responsible for resolving configuration {@link Items}.
8+
*/
9+
export type ResolveCallback = () => Promise<Items>;
10+
11+
/**
12+
* A source that ultimately must resolve into configuration {@link Items}.
13+
*/
14+
export type Source = Items | Promise<Items> | ResolveCallback;

packages/contracts/src/core/Application.ts

+24-1
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ import {
22
Callback,
33
ClassMethodReference
44
} from "@aedart/contracts";
5+
import { Source } from "@aedart/contracts/config";
56
import { Container } from "@aedart/contracts/container";
67
import { CallbackWrapper } from "@aedart/contracts/support";
78
import {
89
Registrar,
910
ServiceProvider,
1011
ServiceProviderConstructor,
11-
BootException,
1212
} from "@aedart/contracts/support/services";
1313
import {
1414
ConfiguratorCallback,
@@ -30,6 +30,29 @@ import BootstrapperConstructor from "./BootstrapperConstructor";
3030
*/
3131
export default interface Application extends Container
3232
{
33+
/**
34+
* Prepare this application using the given configuration source.
35+
*
36+
* **Note**: _Method is shorthand for [configuring]{@link configure} this application
37+
* using a default {@link Configurator} with a configuration {@link Source}:_
38+
*
39+
* @example
40+
* const source = {}; // not shown here...
41+
*
42+
* // Prepare using configuration source
43+
* app.prepare(source);
44+
*
45+
* // Above is equivalent to:
46+
* app.configure( (configurator) => configurator.with(...) )
47+
*
48+
* @param {Source} using
49+
*
50+
* @returns {this}
51+
*
52+
* @see {Configurator.with}
53+
*/
54+
prepare(using: Source): this;
55+
3356
/**
3457
* Configure this application using given configurator
3558
*

packages/contracts/src/core/configuration/Configurator.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
IdentifierAliasTuple,
88
IdentifierInstanceTuple,
99
} from "@aedart/contracts/container";
10-
import { Items } from "@aedart/contracts/config";
10+
import { Source } from "@aedart/contracts/config";
1111
import BootstrapperConstructor from "../BootstrapperConstructor";
1212
import Application from '../Application';
1313

@@ -29,15 +29,17 @@ export default interface Configurator
2929
for(app: Application): this;
3030

3131
/**
32-
* Add configuration items for the application
32+
* Set the source of the application's configuration items
33+
*
34+
* **Caution**: _Method overwrites eventual previous set configuration source!_
3335
*
3436
* @see {import('@aedart/contracts/config').Repository}
3537
*
36-
* @param {Items} items
38+
* @param {Source} source A source that resolves into configuration [items]{@link import('@aedart/contracts/config').Items}
3739
*
3840
* @return {this}
3941
*/
40-
with(items: Items): this;
42+
with(source: Source): this;
4143

4244
/**
4345
* Add "core" bindings to be registered

packages/contracts/src/core/index.ts

+9
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ export const APPLICATION: symbol = CORE;
2121
*/
2222
export const APP_ENV: unique symbol = Symbol('APP_ENV');
2323

24+
/**
25+
* Configuration Source identifier
26+
*
27+
* @type {Symbol}
28+
*
29+
* @see {import('@aedart/contracts/config').Source}
30+
*/
31+
export const CONFIG_SOURCE: unique symbol = Symbol('@aedart/contracts/core/config/source');
32+
2433
import Application from "./Application";
2534
import Bootstrapper from "./Bootstrapper";
2635
import BootstrapperConstructor from "./BootstrapperConstructor";

0 commit comments

Comments
 (0)