Compare commits
2 Commits
b209d710b8
...
e73c6e75e9
| Author | SHA1 | Date | |
|---|---|---|---|
| e73c6e75e9 | |||
| 975a543027 |
@@ -1,13 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "platform-ts",
|
"name": "platform-ts",
|
||||||
"module": "index.ts",
|
"module": "index.ts",
|
||||||
|
"version": "0.0.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"lint": "eslint --max-warnings 0 ./src",
|
"lint": "eslint --max-warnings 0 ./src",
|
||||||
"prelint:fix": "bun run format",
|
"prelint:fix": "bun run format",
|
||||||
"lint:fix": "eslint --fix ./src"
|
"lint:fix": "eslint --fix ./src",
|
||||||
|
"build": "tsc"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/css": "0.13.0",
|
"@eslint/css": "0.13.0",
|
||||||
|
|||||||
84
src/di/DependencyProvider.ts
Normal file
84
src/di/DependencyProvider.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import type IDependencyProvider from './IDependencyProvider';
|
||||||
|
import type {Milliseconds} from '../types/Units';
|
||||||
|
import type IDependencyManager from './IDependencyManager';
|
||||||
|
import {Lifecycle} from './Lifecycle';
|
||||||
|
import Type from './Type';
|
||||||
|
|
||||||
|
interface IStartable {
|
||||||
|
start(): Promise<void> | void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SERVICE_START_TIMEOUT: Milliseconds = 30_000; // 30 seconds
|
||||||
|
|
||||||
|
function isStartable(obj: unknown): obj is IStartable {
|
||||||
|
return obj !== null && typeof obj === 'object' && 'start' in obj && typeof (obj as IStartable).start === 'function';
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class DependencyProvider implements IDependencyProvider {
|
||||||
|
private readonly _dependencyManager: IDependencyManager;
|
||||||
|
private readonly _type: Type;
|
||||||
|
private readonly _instanceType: Type;
|
||||||
|
private readonly _lifecycle: Lifecycle;
|
||||||
|
private _instancePromise?: Promise<unknown>;
|
||||||
|
|
||||||
|
constructor(dependencyManager: IDependencyManager, type: Type, instanceType: Type, lifecycle: Lifecycle) {
|
||||||
|
this._dependencyManager = dependencyManager;
|
||||||
|
this._type = type;
|
||||||
|
this._instanceType = instanceType;
|
||||||
|
this._lifecycle = lifecycle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public canProvide(type: Type): boolean {
|
||||||
|
if (!(type instanceof Type)) {
|
||||||
|
throw new Error('Must provide a valid type');
|
||||||
|
}
|
||||||
|
|
||||||
|
return type.equals(this._type) && this._type.equals(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async provide(type: Type): Promise<unknown> {
|
||||||
|
if (!this.canProvide(type)) {
|
||||||
|
throw new Error(`Provider for ${this._type} cannot provide ${type}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._instancePromise) {
|
||||||
|
return this._instancePromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
const instancePromise = this._createInstance();
|
||||||
|
|
||||||
|
if (this._lifecycle === Lifecycle.Singleton || this._lifecycle === Lifecycle.Service) {
|
||||||
|
this._instancePromise = instancePromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
return instancePromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _createInstance(): Promise<unknown> {
|
||||||
|
const instance = await this._dependencyManager.instantiateType(this._instanceType);
|
||||||
|
|
||||||
|
if (this._lifecycle === Lifecycle.Service) {
|
||||||
|
if (!isStartable(instance)) {
|
||||||
|
throw new Error(`Instance for [${this._type.toString()}] is missing start() method`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this._startWithTimeout(instance, SERVICE_START_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _startWithTimeout(instance: IStartable, timeout: number): Promise<void> {
|
||||||
|
const timeoutPromise = new Promise<never>((_, reject) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
reject(new Error(`Timed out starting [${this._instanceType}]`));
|
||||||
|
}, timeout);
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.race([instance.start(), timeoutPromise]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public toString(): string {
|
||||||
|
return `DependencyProvider[${this._type}]`;
|
||||||
|
}
|
||||||
|
}
|
||||||
37
src/di/Lifecycle.ts
Normal file
37
src/di/Lifecycle.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import assertUnreachable from '../lang/assertUnreachable';
|
||||||
|
|
||||||
|
export enum Lifecycle {
|
||||||
|
Singleton = 0,
|
||||||
|
Service = 1,
|
||||||
|
Instance = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LifecycleType = 'signleton' | 'service' | 'instance';
|
||||||
|
|
||||||
|
export class LifecycleMapping {
|
||||||
|
public static convertLifecycleToLifecycleType(lifecycle: Lifecycle): LifecycleType {
|
||||||
|
switch (lifecycle) {
|
||||||
|
case Lifecycle.Singleton:
|
||||||
|
return 'signleton';
|
||||||
|
case Lifecycle.Service:
|
||||||
|
return 'service';
|
||||||
|
case Lifecycle.Instance:
|
||||||
|
return 'instance';
|
||||||
|
default:
|
||||||
|
return assertUnreachable(lifecycle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static convertLifecycleTypeToLifecycle(lifecycleType: LifecycleType): Lifecycle {
|
||||||
|
switch (lifecycleType) {
|
||||||
|
case 'signleton':
|
||||||
|
return Lifecycle.Singleton;
|
||||||
|
case 'service':
|
||||||
|
return Lifecycle.Service;
|
||||||
|
case 'instance':
|
||||||
|
return Lifecycle.Instance;
|
||||||
|
default:
|
||||||
|
return assertUnreachable(lifecycleType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,8 +12,8 @@ export default class NamedType extends Type {
|
|||||||
return this._name;
|
return this._name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override equals(other: Type) {
|
public override equals(other: Type): boolean {
|
||||||
return super.equals(other) && other instanceof Type && this.getName() === (other as NamedType).getName();
|
return super.equals(other) && other instanceof NamedType && this.getName() === other.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
public overridetoURN(): string {
|
public overridetoURN(): string {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import IDisposable from './IDisposable';
|
import type IDisposable from './IDisposable';
|
||||||
|
|
||||||
export default class Disposable implements IDisposable {
|
export default class Disposable implements IDisposable {
|
||||||
private readonly _cleanup: () => void;
|
private readonly _cleanup: () => void;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import IDisposable from './IDisposable';
|
import type IDisposable from './IDisposable';
|
||||||
|
|
||||||
export default class DisposableList implements IDisposable {
|
export default class DisposableList implements IDisposable {
|
||||||
private readonly _list: IDisposable[];
|
private readonly _list: IDisposable[];
|
||||||
@@ -15,7 +15,7 @@ export default class DisposableList implements IDisposable {
|
|||||||
while (this._list.length) {
|
while (this._list.length) {
|
||||||
const disposable = this._list.shift();
|
const disposable = this._list.shift();
|
||||||
|
|
||||||
disposable.dispose();
|
disposable!.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
3
src/lang/assertUnreachable.ts
Normal file
3
src/lang/assertUnreachable.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export default function assertUnreachable(x: never): never {
|
||||||
|
throw new Error(`Unreachable code: [${x}]`);
|
||||||
|
}
|
||||||
12
src/types/Units.ts
Normal file
12
src/types/Units.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
export type Seconds = number;
|
||||||
|
export type Milliseconds = number;
|
||||||
|
export type Microseconds = number;
|
||||||
|
export type Nanoseconds = number;
|
||||||
|
|
||||||
|
export type Bytes = number;
|
||||||
|
export type Kilobytes = number;
|
||||||
|
export type Megabytes = number;
|
||||||
|
export type Gigabytes = number;
|
||||||
|
export type Terabytes = number;
|
||||||
|
export type Petabytes = number;
|
||||||
|
export type Exabytes = number;
|
||||||
@@ -7,12 +7,18 @@
|
|||||||
"moduleDetection": "force",
|
"moduleDetection": "force",
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"sourceMap": true,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"declarationDir": "./dist/types",
|
||||||
|
|
||||||
// Bundler mode
|
// Bundler mode
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": false,
|
||||||
"verbatimModuleSyntax": true,
|
"verbatimModuleSyntax": true,
|
||||||
"noEmit": true,
|
"noEmit": false,
|
||||||
|
|
||||||
// Best practices
|
// Best practices
|
||||||
"strict": true,
|
"strict": true,
|
||||||
@@ -25,5 +31,7 @@
|
|||||||
"noUnusedLocals": false,
|
"noUnusedLocals": false,
|
||||||
"noUnusedParameters": false,
|
"noUnusedParameters": false,
|
||||||
"noPropertyAccessFromIndexSignature": false
|
"noPropertyAccessFromIndexSignature": false
|
||||||
}
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules/**/*", "test/**/*"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user