Skip to content

Commit

Permalink
feat(core): add a $ready service
Browse files Browse the repository at this point in the history
It allows a service to know when its silo context is fully loaded.

fix #135
  • Loading branch information
nfroidure committed Dec 2, 2024
1 parent e2643a2 commit 0cf2fd5
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 19 deletions.
16 changes: 8 additions & 8 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ It is designed to have a low footprint on services code.
In fact, the Knifecycle API is aimed to allow to statically
build its services load/unload code once in production.

[See in context](./src/index.ts#L204-L223)
[See in context](./src/index.ts#L213-L232)



Expand All @@ -52,7 +52,7 @@ A service provider is full of state since its concern is
[encapsulate](https://en.wikipedia.org/wiki/Encapsulation_(computer_programming))
your application global states.

[See in context](./src/index.ts#L225-L234)
[See in context](./src/index.ts#L234-L243)



Expand All @@ -78,7 +78,7 @@ A service provider is full of state since its concern is
`Knifecycle` provides a set of decorators that allows you to simply
create new initializers.

[See in context](./src/util.ts#L14-L35)
[See in context](./src/util.ts#L15-L36)



Expand All @@ -92,7 +92,7 @@ The `?` flag indicates an optional dependency.
It allows to write generic services with fixed
dependencies and remap their name at injection time.

[See in context](./src/util.ts#L1371-L1380)
[See in context](./src/util.ts#L1372-L1381)



Expand Down Expand Up @@ -121,7 +121,7 @@ Initializers can be of three types:
instanciated once for all for each executions silos using
them (we will cover this topic later on).

[See in context](./src/index.ts#L315-L339)
[See in context](./src/index.ts#L332-L356)



Expand All @@ -137,7 +137,7 @@ Depending on your application design, you could run it
in only one execution silo or into several ones
according to the isolation level your wish to reach.

[See in context](./src/index.ts#L671-L681)
[See in context](./src/index.ts#L688-L698)



Expand All @@ -157,7 +157,7 @@ For the build to work, we need:
- the dependencies list you want to
initialize

[See in context](./src/build.ts#L36-L51)
[See in context](./src/build.ts#L37-L52)



Expand All @@ -173,5 +173,5 @@ Sadly TypeScript does not allow to add generic types
For more details, see:
https://stackoverflow.com/questions/64948037/generics-type-loss-while-infering/64950184#64950184

[See in context](./src/util.ts#L1441-L1452)
[See in context](./src/util.ts#L1442-L1453)

36 changes: 27 additions & 9 deletions src/build.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ describe('buildInitializer', () => {
),
dep5: initializer(
{
inject: [],
inject: ['$ready'],
type: 'service',
name: 'dep5',
},
Expand Down Expand Up @@ -111,17 +111,21 @@ async function $dispose() {
}
}
let resolveReady;
const $ready = new Promise<void>((resolve) => {
resolveReady = resolve;
});
const $instance = {
destroy: $dispose,
};
// Definition batch #0
import initDep1 from './services/dep1';
import initDep5 from './services/dep5';
const NODE_ENV = "development";
// Definition batch #1
import initDep5 from './services/dep5';
import initDep2 from './services/dep2';
// Definition batch #2
Expand All @@ -135,8 +139,7 @@ export async function initialize(services = {}) {
const batch0 = {
dep1: initDep1({
}),
dep5: initDep5({
}),
$ready: Promise.resolve($ready),
NODE_ENV: Promise.resolve(NODE_ENV),
};
Expand All @@ -146,12 +149,15 @@ export async function initialize(services = {}) {
);
services['dep1'] = await batch0['dep1'];
services['dep5'] = await batch0['dep5'];
services['$ready'] = await batch0['$ready'];
services['NODE_ENV'] = await batch0['NODE_ENV'];
// Initialization batch #1
batchsDisposers[1] = [];
const batch1 = {
dep5: initDep5({
$ready: services['$ready'],
}),
dep2: initDep2({
dep1: services['dep1'],
NODE_ENV: services['NODE_ENV'],
Expand All @@ -171,6 +177,7 @@ export async function initialize(services = {}) {
.map(key => batch1[key])
);
services['dep5'] = await batch1['dep5'];
services['dep2'] = await batch1['dep2'];
// Initialization batch #2
Expand Down Expand Up @@ -235,6 +242,10 @@ async function $dispose() {
}
}
let resolveReady;
const $ready = new Promise<void>((resolve) => {
resolveReady = resolve;
});
const $instance = {
destroy: $dispose,
};
Expand Down Expand Up @@ -357,18 +368,22 @@ async function $dispose() {
}
}
let resolveReady;
const $ready = new Promise<void>((resolve) => {
resolveReady = resolve;
});
const $instance = {
destroy: $dispose,
};
// Definition batch #0
import initDep1 from './services/dep1';
import initDep5 from './services/dep5';
const NODE_ENV = "development";
const $siloContext = undefined;
// Definition batch #1
import initDep5 from './services/dep5';
import initDep2 from './services/dep2';
// Definition batch #2
Expand All @@ -382,8 +397,7 @@ export async function initialize(services = {}) {
const batch0 = {
dep1: initDep1({
}),
dep5: initDep5({
}),
$ready: Promise.resolve($ready),
NODE_ENV: Promise.resolve(NODE_ENV),
$fatalError: Promise.resolve($fatalError),
$dispose: Promise.resolve($dispose),
Expand All @@ -397,7 +411,7 @@ export async function initialize(services = {}) {
);
services['dep1'] = await batch0['dep1'];
services['dep5'] = await batch0['dep5'];
services['$ready'] = await batch0['$ready'];
services['NODE_ENV'] = await batch0['NODE_ENV'];
services['$fatalError'] = await batch0['$fatalError'];
services['$dispose'] = await batch0['$dispose'];
Expand All @@ -407,6 +421,9 @@ export async function initialize(services = {}) {
// Initialization batch #1
batchsDisposers[1] = [];
const batch1 = {
dep5: initDep5({
$ready: services['$ready'],
}),
dep2: initDep2({
dep1: services['dep1'],
NODE_ENV: services['NODE_ENV'],
Expand All @@ -426,6 +443,7 @@ export async function initialize(services = {}) {
.map(key => batch1[key])
);
services['dep5'] = await batch1['dep5'];
services['dep2'] = await batch1['dep2'];
// Initialization batch #2
Expand Down
21 changes: 20 additions & 1 deletion src/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
AUTOLOAD,
parseDependencyDeclaration,
initializer,
READY,
} from './util.js';
import { buildInitializationSequence } from './sequence.js';
import { FATAL_ERROR } from './fatalError.js';
Expand All @@ -16,7 +17,7 @@ import type {
} from './util.js';
import { OVERRIDES, pickOverridenName } from './overrides.js';

export const MANAGED_SERVICES = [FATAL_ERROR, DISPOSE, INSTANCE];
export const MANAGED_SERVICES = [FATAL_ERROR, DISPOSE, INSTANCE, READY];

type DependencyTreeNode = {
__name: string;
Expand Down Expand Up @@ -132,6 +133,10 @@ async function $dispose() {
}
}
let resolveReady;
const $ready = new Promise<void>((resolve) => {
resolveReady = resolve;
});
const $instance = {
destroy: $dispose,
};
Expand Down Expand Up @@ -260,6 +265,20 @@ async function buildDependencyTree(
mappedName,
]);

if(MANAGED_SERVICES.includes(finalName)) {
return {

__name: finalName,
__initializer: async() => {},
__inject: [],
__type: 'constant',
__initializerName: 'init' + upperCaseFirst(finalName.slice(1)),
__path: `internal://managed/${finalName}`,
__childNodes: [],
__parentsNames: [...parentsNames, finalName],
};
}

try {
const { path, initializer } = await $autoload(finalName);
const node: DependencyTreeNode = {
Expand Down
6 changes: 6 additions & 0 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@ describe('Knifecycle', () => {
});
});

test('should work with the $ready service', async () => {
const $ready = await $.run<{ $ready: Promise<void> }>(['$ready']);

await $ready;
});

test('should fail when overriding an initialized constant', async () => {
$.register(constant('TEST', 1));
expect(await $.run<any>(['TEST'])).toEqual({
Expand Down
36 changes: 35 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
NO_PROVIDER,
INSTANCE,
SILO_CONTEXT,
READY,
AUTOLOAD,
SPECIAL_PROPS,
SPECIAL_PROPS_PREFIX,
Expand Down Expand Up @@ -192,7 +193,15 @@ export type InternalDependencies = {

const debug = initDebug('knifecycle');

export { DISPOSE, FATAL_ERROR, INSTANCE, SILO_CONTEXT, AUTOLOAD, OVERRIDES };
export {
DISPOSE,
FATAL_ERROR,
INSTANCE,
SILO_CONTEXT,
READY,
AUTOLOAD,
OVERRIDES,
};
export const INJECTOR = '$injector';
export const UNBUILDABLE_SERVICES = [
AUTOLOAD,
Expand Down Expand Up @@ -272,6 +281,14 @@ class Knifecycle {
dependents: [],
silosInstances: {},
},
[READY]: {
initializer: service(async () => {
throw new YError('E_UNEXPECTED_INIT', READY);
}, READY),
autoloaded: false,
dependents: [],
silosInstances: {},
},
[DISPOSE]: {
initializer: initDispose as any,
autoloaded: false,
Expand Down Expand Up @@ -708,6 +725,11 @@ class Knifecycle {
loadingSequences: [],
};

let resolveReady;
const ready = new Promise<void>((resolve) => {
resolveReady = resolve;
});

if (this._shutdownPromise) {
throw new YError('E_INSTANCE_DESTROYED');
}
Expand All @@ -721,6 +743,16 @@ class Knifecycle {
provider: { service: siloContext },
};

// Make the ready service available for internal injections
(
this._initializersStates[READY] as SiloedInitializerStateDescriptor<
Promise<void>,
Dependencies<unknown>
>
).silosInstances[siloIndex] = {
provider: { service: ready },
};

this._silosContexts[siloContext.index] = siloContext;

const services = await this._loadInitializerDependencies(
Expand All @@ -732,6 +764,8 @@ class Knifecycle {

debug('All dependencies now loaded:', siloContext.loadingSequences);

resolveReady();

return services as ID;
}

Expand Down
1 change: 1 addition & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const debug = initDebug('knifecycle');
export const NO_PROVIDER = Symbol('NO_PROVIDER');
export const INSTANCE = '$instance';
export const SILO_CONTEXT = '$siloContext';
export const READY = '$ready';
export const AUTOLOAD = '$autoload';

/* Architecture Note #1.2: Creating initializers
Expand Down

0 comments on commit 0cf2fd5

Please sign in to comment.