Skip to content

Commit 27549d5

Browse files
committedApr 4, 2020
Redesign subsys architecture
Part of a series on #2. I am more or less just trying out random stuff right now and see how things work out. Rationale: Your plan can't fail if there is no plan to begin with.
1 parent 7b5ef7a commit 27549d5

File tree

9 files changed

+337
-362
lines changed

9 files changed

+337
-362
lines changed
 

‎src/core/abstract-subsys.ts

+72-44
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@
2121

2222
import { IConfigurable } from '../../types/configurable';
2323
import { ISubsys } from '../../types/core/subsys';
24-
import { IVideu } from '../../types/core/videu';
2524
import { ILogger } from '../../types/logger';
2625
import { IObjectSchema } from '../../types/util/object-schema';
2726

27+
import { IllegalAccessError } from '../error/illegal-access-error';
2828
import { InvalidConfigError } from '../error/invalid-config-error';
2929
import { Logger } from '../util/logger';
3030
import { validateConfig } from '../util/validate';
@@ -37,23 +37,12 @@ const subsysIdRegex: RegExp = /^[a-z]([a-z0-9\-]*[a-z0-9])?$/;
3737
*
3838
* @param T The type of the configuration object.
3939
*/
40-
export abstract class AbstractSubsys<T extends object>
41-
implements ISubsys, IConfigurable<T> {
40+
export abstract class AbstractSubsys<InitParams extends any[] = []>
41+
implements ISubsys<InitParams> {
4242

4343
/** @inheritdoc */
4444
public readonly id: string;
4545

46-
/** @inheritdoc */
47-
public readonly config: T;
48-
49-
/** @inheritdoc */
50-
public abstract readonly wants: string[];
51-
52-
/** The main app instance. */
53-
protected get app(): IVideu | null {
54-
return this._app;
55-
}
56-
5746
/** The logging utility for this subsystem. */
5847
protected readonly logger: ILogger;
5948

@@ -63,26 +52,14 @@ implements ISubsys, IConfigurable<T> {
6352
*/
6453
private _isInitialized: boolean = false;
6554

66-
/**
67-
* Internal value for {@link #app} with write access.
68-
*/
69-
private _app: IVideu | null = null;
70-
7155
/**
7256
* Create a new subsystem.
7357
*
74-
* @param id This subsystem's unique id. This must be an all-lowercase
58+
* @param id This subsystem's unique id. This should be an all-lowercase
7559
* alphanumeric string starting with a letter and may contain dashes
7660
* within.
77-
* @param config The configuration object. If `null`, the
78-
* {@link #readConfigFromEnv} is invoked to obtain it. If that returns
79-
* `null` as well, an {@link InvalidConfigError} is thrown.
80-
* @param configSchema A schema describing the exact structure of the
81-
* configuration object.
82-
* @throws An {@link InvalidConfigurationError} if the configuration
83-
* had illegal or missing values.
8461
*/
85-
constructor(id: string, config: T | null, configSchema: IObjectSchema) {
62+
constructor(id: string) {
8663
if (!subsysIdRegex.test(id)) {
8764
throw new Error(
8865
`Subsystem id "${id}" does not match ${subsysIdRegex.toString()}`
@@ -91,8 +68,66 @@ implements ISubsys, IConfigurable<T> {
9168

9269
this.id = id;
9370
this.logger = new Logger(id);
71+
}
72+
73+
/** @inheritdoc */
74+
public get isInitialized(): boolean {
75+
return this._isInitialized;
76+
}
77+
78+
/** @inheritdoc */
79+
public async init() {
80+
this._isInitialized = true;
81+
this.logger.i('Initializing');
82+
}
83+
84+
/** @inheritdoc */
85+
public exit() {
86+
this._isInitialized = false;
87+
}
88+
89+
}
9490

95-
if (config === null) {
91+
/**
92+
* Abstract base class for all subsystems that are configurable.
93+
*/
94+
export abstract class AbstractSubsysConfigurable<
95+
ConfigType extends object = {},
96+
InitParams extends any[] = []
97+
> extends AbstractSubsys<InitParams> implements IConfigurable<ConfigType> {
98+
99+
/** Internal field for {@link #config} w/ write access. */
100+
private _config: ConfigType | null = null;
101+
102+
/** The validation schema for the config object. */
103+
private readonly configSchema: IObjectSchema;
104+
105+
/**
106+
* Create a new configurable subsystem.
107+
*
108+
* @param id This subsystem's unique id. This should be an all-lowercase
109+
* alphanumeric string starting with a letter and may contain dashes
110+
* within.
111+
* @param config The configuration object. If `null`, the
112+
* {@link #readConfigFromEnv} callback is invoked to obtain it. If that
113+
* returns `null` as well, an {@link InvalidConfigError} is thrown.
114+
* @param configSchema A schema describing the exact structure of the
115+
* configuration object.
116+
* @throws An {@link InvalidConfigurationError} if the configuration
117+
* had illegal or missing values.
118+
*/
119+
constructor(id: string, config: ConfigType | null, configSchema: IObjectSchema) {
120+
super(id);
121+
122+
this._config = config;
123+
this.configSchema = configSchema;
124+
}
125+
126+
/** @inheritdoc */
127+
public async init(): Promise<void> {
128+
await super.init();
129+
130+
if (this._config === null) {
96131
const configFromEnv = this.readConfigFromEnv();
97132

98133
if (configFromEnv === null) {
@@ -101,26 +136,19 @@ implements ISubsys, IConfigurable<T> {
101136
);
102137
}
103138

104-
this.config = validateConfig(configFromEnv, configSchema);
139+
this._config = validateConfig(configFromEnv, this.configSchema);
105140
} else {
106-
this.config = validateConfig(config, configSchema);
141+
this._config = validateConfig(this._config, this.configSchema);
107142
}
108143
}
109144

110145
/** @inheritdoc */
111-
public get isInitialized(): boolean {
112-
return this._isInitialized;
113-
}
114-
115-
/** @inheritdoc */
116-
public async init(app: IVideu) {
117-
this._app = app;
118-
this._isInitialized = true;
119-
}
146+
public get config(): ConfigType {
147+
if (!this.isInitialized) {
148+
throw new IllegalAccessError('Cannot access configuration before init()');
149+
}
120150

121-
/** @inheritdoc */
122-
public exit() {
123-
this._isInitialized = false;
151+
return this._config!;
124152
}
125153

126154
/**
@@ -130,7 +158,7 @@ implements ISubsys, IConfigurable<T> {
130158
*
131159
* @return The configuration object, composed from environment variables.
132160
*/
133-
protected readConfigFromEnv(): T | null {
161+
protected readConfigFromEnv(): ConfigType | null {
134162
return null;
135163
}
136164

‎src/error/http-error.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,24 @@
1919
* along with this program. If not, see <https://www.gnu.org/licenses/>.
2020
*/
2121

22+
import { HTTPStatusCode } from '../../types/json/response';
23+
2224
/**
2325
* Represents an HTTP error message that gets caught by the master error handler
2426
* in `./default-error-handler`, where it is sent to the client.
2527
*/
2628
export class HttpError extends Error {
2729

2830
/** The HTTP status code to reply with. */
29-
public readonly status: number;
31+
public readonly status: HTTPStatusCode;
3032

3133
/**
3234
* Create a new HTTP error.
3335
*
3436
* @param msg The error message.
3537
* @param status The HTTP status code.
3638
*/
37-
public constructor(msg: string, status: number = 500) {
39+
public constructor(msg: string, status: HTTPStatusCode = HTTPStatusCode.INTERNAL_SERVER_ERROR) {
3840
super(msg);
3941

4042
this.status = status;

‎src/error/illegal-access-error.ts

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* @file Error class for illegal data accesses.
3+
* @author Felix Kopp <sandtler@sandtler.club>
4+
*
5+
* @license
6+
* Copyright (c) 2020 The videu Project <videu@freetube.eu>
7+
*
8+
* This program is free software: you can redistribute it and/or modify
9+
* it under the terms of the GNU Affero General Public License as
10+
* published by the Free Software Foundation, either version 3 of the
11+
* License, or (at your option) any later version.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
* GNU Affero General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU Affero General Public License
19+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
20+
*/
21+
22+
/**
23+
* Error condition when a property was attempted to be accessed when one was not
24+
* supposed to do so at this time.
25+
*/
26+
export class IllegalAccessError extends Error {}

‎test/core/abstract-subsys.ts

+156-36
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* @file Unit test for the AbstractSubsys class.
2+
* @file Unit test for the AbstractSubsys and AbstractSubsysConfigurable classes.
33
* @author Felix Kopp <sandtler@sandtler.club>
44
*
55
* @license
@@ -22,58 +22,178 @@
2222
import { expect } from 'chai';
2323
import { describe } from 'mocha';
2424

25-
import { Videu } from '../../src/core/videu';
26-
import { InvalidConfigError } from '../../src/error/invalid-config-error';
25+
import { IllegalAccessError } from '../../src/error/illegal-access-error';
2726

28-
import { StubExtAbstractSubsys, StubExtAbstractSubsysFromEnv } from '../dummy/core/subsys';
27+
import { IStubSubsysConfig, StubSubsys, StubSubsysConfigurable } from '../dummy/core/subsys';
28+
29+
before(() => {
30+
if (typeof global.videu !== 'object') {
31+
(global.videu as any) = { logLevel: 0 };
32+
} else {
33+
global.videu.logLevel = 0;
34+
}
35+
});
36+
37+
/** Test result data returned by a {@link FStubSubsysTester} callback. */
38+
interface IStubSubsysTestResult<InitParams extends any[] = []> {
39+
id: string;
40+
initParams: InitParams | null;
41+
isInitialized: boolean;
42+
}
43+
44+
/** Test callback function for testing the {@link AbstractSubsys} class. */
45+
type FStubSubsysTester<InitParams extends any[] = []> =
46+
() => Promise<IStubSubsysTestResult<InitParams>>;
2947

3048
describe('core/abstract-subsys:AbstractSubsys', () => {
31-
let dummy: StubExtAbstractSubsys;
32-
33-
it('should instantiate normally', () => {
34-
const id = 'normal';
35-
dummy = new StubExtAbstractSubsys(id, []);
36-
expect(dummy.config).to.deep.eq(StubExtAbstractSubsys.CONFIG);
37-
expect(dummy.isInitialized).to.eq(false);
38-
expect(dummy.id).to.eq(id);
39-
expect(dummy.getApp()).to.eq(null);
49+
it('should initialize normally without init params', () => {
50+
const fn: FStubSubsysTester<[]> = async () => {
51+
const dummy = new StubSubsys<[]>('stub', false, false);
52+
await dummy.init();
53+
return {
54+
id: dummy.id,
55+
initParams: dummy.initParams,
56+
isInitialized: dummy.isInitialized,
57+
};
58+
};
59+
60+
return expect(fn()).to.eventually.deep.equal({
61+
id: 'stub',
62+
initParams: [],
63+
isInitialized: true,
64+
}, 'Subsys state invalid');
4065
});
4166

42-
it('should instantiate normally with config from env', () => {
43-
const id = 'config-from-env';
44-
dummy = new StubExtAbstractSubsysFromEnv(id, [], false, false, false, true);
45-
expect(dummy.config).to.deep.eq(StubExtAbstractSubsys.CONFIG);
46-
expect(dummy.isInitialized).to.eq(false);
47-
expect(dummy.id).to.eq(id);
48-
expect(dummy.getApp()).to.eq(null);
67+
it('should initialize normally with one init parameter', () => {
68+
const fn: FStubSubsysTester<[number]> = async () => {
69+
const dummy = new StubSubsys<[number]>('stub', false, false);
70+
await dummy.init(420);
71+
return {
72+
id: dummy.id,
73+
initParams: dummy.initParams,
74+
isInitialized: dummy.isInitialized,
75+
};
76+
};
77+
78+
return expect(fn()).to.eventually.deep.equal({
79+
id: 'stub',
80+
initParams: [ 420 ],
81+
isInitialized: true,
82+
}, 'Subsys state invalid');
4983
});
5084

51-
let videu: Videu;
85+
it('should initialize normally with two init parameters', () => {
86+
const fn: FStubSubsysTester<[string, number]> = async () => {
87+
const dummy = new StubSubsys<[string, number]>('stub', false, false);
88+
await dummy.init('param', 420);
89+
return {
90+
id: dummy.id,
91+
initParams: dummy.initParams,
92+
isInitialized: dummy.isInitialized,
93+
};
94+
};
5295

53-
it('should initialize normally', () => {
54-
videu = new Videu([dummy]);
55-
const fn = async () => await videu.init();
56-
expect(videu.init()).to.eventually.become(undefined);
57-
expect(dummy.isInitialized).to.eq(true);
96+
return expect(fn()).to.eventually.deep.equal({
97+
id: 'stub',
98+
initParams: [ 'param', 420 ],
99+
isInitialized: true,
100+
}, 'Subsys state invalid');
58101
});
59102

60103
it('should de-initialize normally', () => {
61-
expect(videu.exit()).to.eq(undefined);
62-
expect(dummy.isInitialized).to.eq(false);
104+
const fn: FStubSubsysTester<[]> = async () => {
105+
const dummy = new StubSubsys<[]>('stub', false, false);
106+
await dummy.init();
107+
dummy.exit();
108+
return {
109+
id: dummy.id,
110+
initParams: dummy.initParams,
111+
isInitialized: dummy.isInitialized,
112+
};
113+
};
114+
115+
return expect(fn()).to.eventually.deep.eq({
116+
id: 'stub',
117+
initParams: [],
118+
isInitialized: false,
119+
}, 'Subsys state invalid');
63120
});
64121

65-
it('should throw an error with invalid ids', () => {
66-
const fn = () => new StubExtAbstractSubsys('INVALID_ID', []);
122+
it('should reject invalid subsystem id', () => {
123+
const fn = () => new StubSubsys<[]>('INVALID NAME', false, false);
67124
return expect(fn).to.throw(Error);
68125
});
126+
});
127+
128+
/**
129+
* Return value of a {@link FStubSubsysConfigurableTester} callback function
130+
* used in the individual test cases for {@link AbstractSubsysConfigurable}.
131+
*/
132+
interface IStubSubsysConfigurableTestResult<InitParams extends any[] = []>
133+
extends IStubSubsysTestResult<InitParams> {
134+
config: IStubSubsysConfig;
135+
}
136+
137+
/** Test callback for testing the {@link AbstractSubsysConfigurable} class */
138+
type FStubSubsysConfigurableTester<InitParams extends any[] = []> =
139+
() => Promise<IStubSubsysConfigurableTestResult<InitParams>>;
140+
141+
describe('core/abstract-subsys:AbstractSubsysConfigurable', () => {
142+
it('should initialize with config from constructor', () => {
143+
const fn: FStubSubsysConfigurableTester<[]> = async () => {
144+
const dummy = new StubSubsysConfigurable<[]>('stub');
145+
await dummy.init();
146+
return {
147+
id: dummy.id,
148+
initParams: dummy.initParams,
149+
config: dummy.config,
150+
isInitialized: dummy.isInitialized,
151+
};
152+
};
153+
154+
return expect(fn()).to.eventually.deep.equal({
155+
id: 'stub',
156+
initParams: [],
157+
config: StubSubsysConfigurable.CONFIG,
158+
isInitialized: true,
159+
}, 'Subsys state invalid');
160+
});
69161

70-
it('should throw an error with invalid config object', () => {
71-
const fn = () => new StubExtAbstractSubsys('a', [], false, false, true);
72-
return expect(fn).to.throw(InvalidConfigError);
162+
it('should initialize with config from env', () => {
163+
const fn: FStubSubsysConfigurableTester<[]> = async () => {
164+
const dummy = new StubSubsysConfigurable<[]>('stub', false, false, false, true);
165+
await dummy.init();
166+
return {
167+
id: dummy.id,
168+
initParams: dummy.initParams,
169+
config: dummy.config,
170+
isInitialized: dummy.isInitialized,
171+
};
172+
};
173+
174+
return expect(fn()).to.eventually.deep.equal({
175+
id: 'stub',
176+
initParams: [],
177+
config: StubSubsysConfigurable.CONFIG,
178+
isInitialized: true,
179+
}, 'Subsys state invalid');
73180
});
74181

75-
it('should throw an error with no config specified', () => {
76-
const fn = () => new StubExtAbstractSubsys('a', [], false, false, false, true);
77-
return expect(fn).to.throw(InvalidConfigError);
182+
it('should throw an error with no config', () => {
183+
const fn = async () => {
184+
const dummy = new StubSubsysConfigurable<[]>('stub', false, false, false, false, true);
185+
await dummy.init();
186+
};
187+
188+
return expect(fn()).to.be.rejectedWith(Error);
189+
});
190+
191+
it('should reject accessing the config before init', () => {
192+
const fn = () => {
193+
const dummy = new StubSubsysConfigurable<[]>('stub');
194+
return dummy.config;
195+
};
196+
197+
return expect(fn).to.throw(IllegalAccessError);
78198
});
79199
});

‎test/core/videu.ts

-184
This file was deleted.

‎test/dummy/core/subsys.ts

+72-72
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* @file Dummy implementation of `ISubsys`.
2+
* @file Stub subsystem implementations.
33
* @author Felix Kopp <sandtler@sandtler.club>
44
*
55
* @license
@@ -19,55 +19,47 @@
1919
* along with this program. If not, see <https://www.gnu.org/licenses/>.
2020
*/
2121

22-
import { ISubsys } from '../../../types/core/subsys';
23-
import { IVideu } from '../../../types/core/videu';
24-
25-
import { AbstractSubsys } from '../../../src/core/abstract-subsys';
22+
import {
23+
AbstractSubsys,
24+
AbstractSubsysConfigurable,
25+
} from '../../../src/core/abstract-subsys';
2626
import { IObjectSchema } from '../../../types/util/object-schema';
2727

2828
/**
29-
* Dummy implementation of a subsystem.
29+
* Dummy implementation of a subsystem for testing the {@link AbstractSubsys}
30+
* class.
3031
*/
31-
export class StubSubsys implements ISubsys {
32-
33-
public readonly id: string;
32+
export class StubSubsys<InitParams extends any[] = []>
33+
extends AbstractSubsys<InitParams> {
3434

35-
public isInitialized: boolean = false;
36-
/** If `false`, the dependencies defined in {@link #wants} were not satisfied. */
37-
public hasDependenciesSatisfied: boolean = true;
38-
39-
public readonly wants: string[];
35+
public initParams: InitParams | null = null;
4036

4137
private readonly shouldInitFail: boolean;
4238
private readonly shouldExitFail: boolean;
4339

44-
/**
45-
* Create a new subsystem dummy.
46-
*
47-
* @param id The subsystem id.
48-
* @param wants All subsystem ids this one depends on.
49-
* @param shouldInitFail Whether the {@link #init} callback should throw an error.
50-
* @param shouldExitFail Whether the {@link #exit} callback should throw an error.
51-
*/
52-
constructor(id: string, wants: string[], shouldInitFail: boolean, shouldExitFail: boolean) {
53-
this.id = id;
54-
this.wants = wants;
40+
constructor(id: string, shouldInitFail: boolean, shouldExitFail: boolean) {
41+
super(id);
42+
5543
this.shouldInitFail = shouldInitFail;
5644
this.shouldExitFail = shouldExitFail;
5745
}
5846

59-
public init(videu: IVideu): Promise<void> {
60-
return new Promise((resolve, reject) => {
61-
/* check if all subsystems this one depends on have been initialized */
62-
for (const subsysId of this.wants) {
63-
const subsys = videu.getSubsys(subsysId);
64-
if (subsys === null || !subsys.isInitialized) {
65-
this.hasDependenciesSatisfied = false;
66-
}
47+
public init(...params: InitParams): Promise<void> {
48+
/*
49+
* Returning a Promise manually instead of declaring init() as async
50+
* allows us to use timeouts. This simulates the actual delay that may
51+
* occur with things like connecting to a server.
52+
*/
53+
return new Promise(async (resolve, reject) => {
54+
try {
55+
await super.init();
56+
} catch (err) {
57+
reject(err);
6758
}
6859

60+
this.initParams = params;
61+
6962
setTimeout(() => {
70-
this.isInitialized = true;
7163
if (this.shouldInitFail) {
7264
reject(new Error('Dummy init failed :('));
7365
} else {
@@ -77,42 +69,48 @@ export class StubSubsys implements ISubsys {
7769
});
7870
}
7971

80-
public exit(): void {
81-
this.isInitialized = false;
72+
public exit() {
73+
super.exit();
74+
8275
if (this.shouldExitFail) {
8376
throw new Error('Dummy exit failed :(');
8477
}
8578
}
86-
8779
}
8880

81+
/**
82+
* Configuration schema for the {@link StubSubsysConfigurable} class.
83+
*/
8984
export interface IStubSubsysConfig {
9085
someProp: string;
9186
}
9287

9388
/**
94-
* Dummy extending the {@link AbstractSubsys} class.
89+
* Dummy subsystem w/ config for testing the {@link AbstractSubsysConfigurable}
90+
* class.
9591
*/
96-
export class StubExtAbstractSubsys
97-
extends AbstractSubsys<IStubSubsysConfig> {
92+
export class StubSubsysConfigurable<InitParams extends any[] = []>
93+
extends AbstractSubsysConfigurable<IStubSubsysConfig, InitParams> {
9894

95+
/** The configuration object that is passed to the super class. */
9996
public static readonly CONFIG: IStubSubsysConfig = {
10097
someProp: 'some-val',
10198
};
10299

100+
/** The config validation schema. */
103101
private static readonly SCHEMA: IObjectSchema = {
104102
someProp: {
105103
type: 'string',
106104
},
107105
};
108106

109-
/** If `false`, the dependencies defined in {@link #wants} were not satisfied. */
110-
public hasDependenciesSatisfied: boolean = true;
111-
112-
public readonly wants: string[];
107+
/** An array of all parameters passed to the {@link #init} callback. */
108+
public initParams: InitParams | null = null;
113109

114110
private readonly shouldInitFail: boolean;
115111
private readonly shouldExitFail: boolean;
112+
private readonly shouldConfigFail: boolean;
113+
private readonly shouldReturnNoConfig: boolean;
116114

117115
/**
118116
* Create a new subsystem dummy.
@@ -123,40 +121,35 @@ extends AbstractSubsys<IStubSubsysConfig> {
123121
* @param shouldExitFail Whether the {@link #exit} callback should throw an error.
124122
* @param shouldConfigFail Whether the configuration should be invalid.
125123
*/
126-
constructor(id: string, wants: string[], shouldInitFail: boolean = false,
127-
shouldExitFail: boolean = false, shouldConfigFail: boolean = false,
128-
configFromEnv: boolean = false) {
124+
constructor(id: string, shouldInitFail: boolean = false, shouldExitFail: boolean = false,
125+
shouldConfigFail: boolean = false, configFromEnv: boolean = false,
126+
shouldReturnNoConfig: boolean = false) {
129127
super(
130128
id,
131-
shouldConfigFail
132-
? {} as any
133-
: configFromEnv
129+
shouldConfigFail && !configFromEnv
130+
? {} as IStubSubsysConfig
131+
: configFromEnv || shouldReturnNoConfig
134132
? null
135-
: StubExtAbstractSubsys.CONFIG,
136-
StubExtAbstractSubsys.SCHEMA
133+
: StubSubsysConfigurable.CONFIG,
134+
StubSubsysConfigurable.SCHEMA
137135
);
138136

139-
this.wants = wants;
140137
this.shouldInitFail = shouldInitFail;
141138
this.shouldExitFail = shouldExitFail;
139+
this.shouldConfigFail = shouldConfigFail;
140+
this.shouldReturnNoConfig = shouldReturnNoConfig;
142141
}
143142

144-
public getApp(): IVideu | null {
145-
return this.app;
146-
}
147-
148-
public init(videu: IVideu): Promise<void> {
143+
public init(...params: InitParams): Promise<void> {
149144
return new Promise(async (resolve, reject) => {
150-
await super.init(videu);
151-
152-
/* check if all subsystems this one depends on have been initialized */
153-
for (const subsysId of this.wants) {
154-
const subsys = videu.getSubsys(subsysId);
155-
if (subsys === null || !subsys.isInitialized) {
156-
this.hasDependenciesSatisfied = false;
157-
}
145+
try {
146+
await super.init();
147+
} catch (err) {
148+
reject(err);
158149
}
159150

151+
this.initParams = params;
152+
160153
setTimeout(() => {
161154
if (this.shouldInitFail) {
162155
reject(new Error('Dummy init failed :('));
@@ -175,12 +168,19 @@ extends AbstractSubsys<IStubSubsysConfig> {
175168
}
176169
}
177170

178-
}
179-
180-
export class StubExtAbstractSubsysFromEnv extends StubExtAbstractSubsys {
181-
182-
protected readConfigFromEnv(): IStubSubsysConfig {
183-
return StubExtAbstractSubsys.CONFIG;
171+
protected readConfigFromEnv(): IStubSubsysConfig | null {
172+
/*
173+
* This can only ever get called if configFromEnv is true as that is the
174+
* only way that the config parameter passed to the super constructor is
175+
* null. That means we don't have to check that flag here.
176+
*/
177+
if (this.shouldReturnNoConfig) {
178+
return null;
179+
} else if (this.shouldConfigFail) {
180+
return {} as IStubSubsysConfig;
181+
} else {
182+
return StubSubsysConfigurable.CONFIG;
183+
}
184184
}
185185

186186
}

‎types/core/lifecycle.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,21 @@
1919
* along with this program. If not, see <https://www.gnu.org/licenses/>.
2020
*/
2121

22+
import { SealedArray } from '../array';
23+
2224
/**
2325
* Something that owns a basic lifecycle, i.e. can be initialized and
2426
* de-initialized.
2527
*
2628
* @param T An optional parameter that is passed to the {@link #init} callback.
2729
*/
28-
export interface ILifecycle<T = void> {
30+
export interface ILifecycle<InitArgs extends any[] = []> {
2931

3032
/** Whether this is currently initialized. */
3133
readonly isInitialized: boolean;
3234

3335
/** Initialize this instance. */
34-
init: T extends void
35-
? () => Promise<void>
36-
: (param: T) => Promise<void>;
36+
init(...args: InitArgs): Promise<void>;
3737

3838
/** De-initialize this instance. */
3939
exit(): void;

‎types/core/subsys.ts

+2-12
Original file line numberDiff line numberDiff line change
@@ -20,30 +20,20 @@
2020
*/
2121

2222
import { ILifecycle } from './lifecycle';
23-
import { IVideu } from './videu';
2423

2524
/**
2625
* Base interface for subsystems.
2726
*
2827
* A subsystem is a major component of the application that requires
2928
* initialization on server start and cleanup on server stop.
3029
*/
31-
export interface ISubsys extends ILifecycle<IVideu> {
30+
export interface ISubsys<InitParams extends any[] = []>
31+
extends ILifecycle<InitParams> {
3232

3333
/** The unique name. Should be an all-lowercase alphanumeric string. */
3434
readonly id: string;
3535

3636
/** Whether this subsystem is currently initialized. */
3737
readonly isInitialized: boolean;
3838

39-
/**
40-
* An array of subsystem ids that this subsys requires in order to function.
41-
* Any subsystem that is in this array is guaranteed to have been
42-
* initialized before this one. Similarily, this subsystem is guaranteed to
43-
* be de-initialized before any in this array. If there is a conflict
44-
* (i.e. two subsystems `want`ing each other), the server will refuse to
45-
* start all together.
46-
*/
47-
readonly wants: string[];
48-
4939
}

‎types/core/videu.ts

+1-8
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,12 @@
2020
*/
2121

2222
import { ILifecycle } from './lifecycle';
23-
import { ISubsys } from './subsys';
2423

2524
/**
2625
* Interface definition for the main application class.
2726
*/
2827
export interface IVideu extends ILifecycle {
2928

30-
/**
31-
* Get a subsystem by its id.
32-
*
33-
* @param id The subsystem id.
34-
* @return The subsystem, or `null` if it does not exist.
35-
*/
36-
getSubsys(id: string): ISubsys | null;
29+
readonly appName: string;
3730

3831
}

0 commit comments

Comments
 (0)
Please sign in to comment.