@neuralinnovations/dataisland-sdk 0.0.1-dev1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.browserslistrc +5 -0
- package/.editorconfig +22 -0
- package/.eslintrc.json +44 -0
- package/.github/workflows/publish-npm.yml +28 -0
- package/.prettierignore +1 -0
- package/.prettierrc +11 -0
- package/.yarnrc +2 -0
- package/README.md +7 -0
- package/babel.config.js +6 -0
- package/jest.config.ts +199 -0
- package/package.json +60 -0
- package/src/appBuilder.ts +39 -0
- package/src/appSdk.ts +40 -0
- package/src/credentials.ts +63 -0
- package/src/disposable.ts +151 -0
- package/src/events.ts +71 -0
- package/src/index.ts +69 -0
- package/src/internal/app.impl.ts +119 -0
- package/src/internal/appBuilder.impl.ts +59 -0
- package/src/internal/context.ts +13 -0
- package/src/internal/createApp.impl.ts +12 -0
- package/src/internal/registry.ts +83 -0
- package/src/middleware.ts +7 -0
- package/src/services/credentialService.ts +24 -0
- package/src/services/middlewareService.ts +33 -0
- package/src/services/rpcService.ts +72 -0
- package/src/services/service.ts +46 -0
- package/src/types.ts +110 -0
- package/test/disposable.test.ts +39 -0
- package/test/events.test.ts +151 -0
- package/test/index.test.ts +83 -0
- package/test/registry.test.ts +44 -0
- package/tsconfig.json +31 -0
package/src/events.ts
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
import { type Disposable, DisposableContainer } from './disposable'
|
2
|
+
|
3
|
+
export interface Events<TE, TD> {
|
4
|
+
get events(): EventDispatcher<TE, TD>
|
5
|
+
}
|
6
|
+
|
7
|
+
export interface Input<ET, DT> {
|
8
|
+
type?: ET
|
9
|
+
data: DT
|
10
|
+
}
|
11
|
+
|
12
|
+
export interface Event<ET, DT> extends Input<ET, DT> {
|
13
|
+
unsubscribe: () => void
|
14
|
+
}
|
15
|
+
|
16
|
+
export interface EventSubscriber<ET, DT> {
|
17
|
+
subscribe: (callback: (event: Event<ET, DT>) => void, type?: ET) => Disposable
|
18
|
+
}
|
19
|
+
|
20
|
+
export class EventDispatcher<ET, DT> implements EventSubscriber<ET, DT> {
|
21
|
+
private _listeners: Array<{
|
22
|
+
callback: (value: Event<ET, DT>) => void
|
23
|
+
disposable: Disposable
|
24
|
+
}> = []
|
25
|
+
|
26
|
+
dispatch(input: Input<ET, DT>): void {
|
27
|
+
this._listeners.slice().forEach(it => {
|
28
|
+
const value = {
|
29
|
+
type: input.type,
|
30
|
+
data: input.data,
|
31
|
+
unsubscribe: () => {
|
32
|
+
it.disposable.dispose()
|
33
|
+
}
|
34
|
+
} satisfies Event<ET, DT>
|
35
|
+
it.callback(value)
|
36
|
+
})
|
37
|
+
}
|
38
|
+
|
39
|
+
subscribe(callback: (event: Event<ET, DT>) => void, type?: ET): Disposable {
|
40
|
+
const container = new DisposableContainer()
|
41
|
+
if (type !== undefined) {
|
42
|
+
const cb = callback
|
43
|
+
const listener = (evt: Event<ET, DT>): void => {
|
44
|
+
if (evt.type === type) {
|
45
|
+
cb(evt)
|
46
|
+
}
|
47
|
+
}
|
48
|
+
const value = {
|
49
|
+
callback: listener,
|
50
|
+
disposable: container
|
51
|
+
}
|
52
|
+
container.addCallback(() => {
|
53
|
+
this._listeners = this._listeners.filter(it => it !== value)
|
54
|
+
}, this)
|
55
|
+
|
56
|
+
this._listeners.push(value)
|
57
|
+
|
58
|
+
return container
|
59
|
+
}
|
60
|
+
|
61
|
+
const value = {
|
62
|
+
callback,
|
63
|
+
disposable: container
|
64
|
+
}
|
65
|
+
container.addCallback(() => {
|
66
|
+
this._listeners = this._listeners.filter(it => it !== value)
|
67
|
+
}, this)
|
68
|
+
this._listeners.push(value)
|
69
|
+
return container
|
70
|
+
}
|
71
|
+
}
|
package/src/index.ts
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
import { version } from '../package.json'
|
2
|
+
import { _createApp } from './internal/createApp.impl'
|
3
|
+
import { type AppBuilder } from './appBuilder'
|
4
|
+
import { type AppSdk } from './appSdk'
|
5
|
+
|
6
|
+
export * from './events'
|
7
|
+
export * from './types'
|
8
|
+
export * from './disposable'
|
9
|
+
export * from './types'
|
10
|
+
export * from './credentials'
|
11
|
+
|
12
|
+
const _appsNotReady = new Map<string, Promise<AppSdk>>()
|
13
|
+
const _appsReady = new Map<string, AppSdk>()
|
14
|
+
|
15
|
+
/**
|
16
|
+
* Current SDK version.
|
17
|
+
*/
|
18
|
+
export const SDK_VERSION = version
|
19
|
+
|
20
|
+
/**
|
21
|
+
* Default DataIsland App name.
|
22
|
+
*/
|
23
|
+
export const DEFAULT_NAME = '[DEFAULT]'
|
24
|
+
|
25
|
+
/**
|
26
|
+
* Default DataIsland App host.
|
27
|
+
*/
|
28
|
+
export const DEFAULT_HOST = 'https://dataisland.com.ua'
|
29
|
+
|
30
|
+
export function sdks(): AppSdk[] {
|
31
|
+
return Array.from(_appsReady.values())
|
32
|
+
}
|
33
|
+
|
34
|
+
/**
|
35
|
+
* Returns a DataIsland App instance.
|
36
|
+
* @param name Optional The name of the app.
|
37
|
+
* @param setup Optional setup function.
|
38
|
+
* @returns A DataIsland App instance.
|
39
|
+
* @example
|
40
|
+
* ```js
|
41
|
+
* import { appSdk } from 'data-island'
|
42
|
+
*
|
43
|
+
* const app = await appSdk("my-app", builder => {
|
44
|
+
* builder.useHost("https://dataisland.com.ua")
|
45
|
+
* builder.useAutomaticDataCollectionEnabled(true)
|
46
|
+
* builder.useCredential(new BasicCredential("email", "password"))
|
47
|
+
* })
|
48
|
+
* ```
|
49
|
+
*/
|
50
|
+
export async function appSdk(
|
51
|
+
name?: string,
|
52
|
+
setup?: (builder: AppBuilder) => Promise<void>
|
53
|
+
): Promise<AppSdk> {
|
54
|
+
name = name ?? DEFAULT_NAME
|
55
|
+
|
56
|
+
let appPromise = _appsNotReady.get(name)
|
57
|
+
if (appPromise === undefined) {
|
58
|
+
appPromise = _createApp(name, setup)
|
59
|
+
appPromise
|
60
|
+
.then(app => {
|
61
|
+
_appsReady.set(name ?? DEFAULT_NAME, app)
|
62
|
+
})
|
63
|
+
.catch(reason => {
|
64
|
+
console.error(`Error: ${reason}`)
|
65
|
+
})
|
66
|
+
_appsNotReady.set(name, appPromise)
|
67
|
+
}
|
68
|
+
return await appPromise
|
69
|
+
}
|
@@ -0,0 +1,119 @@
|
|
1
|
+
import { DEFAULT_HOST } from '../index'
|
2
|
+
import { type AppBuilder } from '../appBuilder'
|
3
|
+
import { AppBuilderImplementation } from './appBuilder.impl'
|
4
|
+
import { type Constructor, Registry } from './registry'
|
5
|
+
import { Context } from './context'
|
6
|
+
import { DisposableContainer, type Lifetime } from '../disposable'
|
7
|
+
import { type Service, ServiceContext } from '../services/service'
|
8
|
+
import { CredentialService } from '../services/credentialService'
|
9
|
+
import { MiddlewareService } from '../services/middlewareService'
|
10
|
+
import { type CredentialBase } from '../credentials'
|
11
|
+
import { type AppSdk } from '../appSdk'
|
12
|
+
import { RpcService, RpcServiceImpl } from '../services/rpcService'
|
13
|
+
|
14
|
+
export class AppImplementation implements AppSdk {
|
15
|
+
readonly name: string
|
16
|
+
private _host: string = DEFAULT_HOST
|
17
|
+
private _automaticDataCollectionEnabled: boolean = true
|
18
|
+
private readonly _registry: Registry
|
19
|
+
private readonly _context: Context
|
20
|
+
private readonly _disposable: DisposableContainer
|
21
|
+
|
22
|
+
constructor(name: string) {
|
23
|
+
this.name = name
|
24
|
+
this._registry = new Registry()
|
25
|
+
this._disposable = new DisposableContainer()
|
26
|
+
this._context = new Context(this._registry, this._disposable.lifetime)
|
27
|
+
}
|
28
|
+
|
29
|
+
get credential(): CredentialBase | undefined {
|
30
|
+
return this.resolve<CredentialService>(CredentialService)?.credential
|
31
|
+
}
|
32
|
+
|
33
|
+
set credential(value: CredentialBase) {
|
34
|
+
this.resolve(CredentialService)?.useCredential(value)
|
35
|
+
}
|
36
|
+
|
37
|
+
get lifetime(): Lifetime {
|
38
|
+
return this._disposable.lifetime
|
39
|
+
}
|
40
|
+
|
41
|
+
resolve = <T>(type: Constructor<T>): T | undefined => this._registry.get(type)
|
42
|
+
|
43
|
+
get automaticDataCollectionEnabled(): boolean {
|
44
|
+
return this._automaticDataCollectionEnabled
|
45
|
+
}
|
46
|
+
|
47
|
+
get host(): string {
|
48
|
+
return this._host
|
49
|
+
}
|
50
|
+
|
51
|
+
async initialize(
|
52
|
+
setup: ((builder: AppBuilder) => Promise<void>) | undefined
|
53
|
+
): Promise<void> {
|
54
|
+
// create app builder
|
55
|
+
const builder = new AppBuilderImplementation()
|
56
|
+
|
57
|
+
// call customer setup
|
58
|
+
if (setup !== undefined) {
|
59
|
+
await setup(builder)
|
60
|
+
}
|
61
|
+
|
62
|
+
// host
|
63
|
+
this._host = builder.host
|
64
|
+
|
65
|
+
// automaticDataCollectionEnabled
|
66
|
+
this._automaticDataCollectionEnabled =
|
67
|
+
builder.automaticDataCollectionEnabled
|
68
|
+
|
69
|
+
// register services
|
70
|
+
builder.registerService(CredentialService, (context: ServiceContext) => {
|
71
|
+
return new CredentialService(context)
|
72
|
+
})
|
73
|
+
builder.registerService(MiddlewareService, (context: ServiceContext) => {
|
74
|
+
return new MiddlewareService(context)
|
75
|
+
})
|
76
|
+
builder.registerService(RpcService, (context: ServiceContext) => {
|
77
|
+
return new RpcServiceImpl(context, builder.host) as RpcService
|
78
|
+
})
|
79
|
+
|
80
|
+
// register services
|
81
|
+
const services: Array<[ServiceContext, Service]> = []
|
82
|
+
builder.services.forEach(serviceFactory => {
|
83
|
+
const serviceContext = new ServiceContext(
|
84
|
+
this._context,
|
85
|
+
this._disposable.defineNested()
|
86
|
+
)
|
87
|
+
serviceContext.lifetime.addCallback(() => {
|
88
|
+
serviceContext.onUnregister()
|
89
|
+
}, serviceContext)
|
90
|
+
const serviceInstance = serviceFactory[1](serviceContext)
|
91
|
+
services.push([serviceContext, serviceInstance])
|
92
|
+
this._registry.set(serviceFactory[0], {
|
93
|
+
provide: () => serviceInstance
|
94
|
+
})
|
95
|
+
})
|
96
|
+
|
97
|
+
builder.middlewares.forEach(middleware => {
|
98
|
+
this.resolve(MiddlewareService)?.useMiddleware(middleware)
|
99
|
+
})
|
100
|
+
|
101
|
+
const waitList: Array<Promise<void>> = []
|
102
|
+
// call onRegister service's callback
|
103
|
+
services.forEach(([serviceContext]) => {
|
104
|
+
waitList.push(serviceContext.onRegister())
|
105
|
+
})
|
106
|
+
|
107
|
+
await Promise.all(waitList)
|
108
|
+
|
109
|
+
waitList.length = 0
|
110
|
+
// call onStart service's callback
|
111
|
+
services.forEach(([serviceContext]) => {
|
112
|
+
waitList.push(serviceContext.onStart())
|
113
|
+
})
|
114
|
+
|
115
|
+
await Promise.all(waitList)
|
116
|
+
|
117
|
+
await Promise.resolve()
|
118
|
+
}
|
119
|
+
}
|
@@ -0,0 +1,59 @@
|
|
1
|
+
import { AppBuilder } from '../appBuilder'
|
2
|
+
import { DEFAULT_HOST } from '../index'
|
3
|
+
import { type CredentialBase, DefaultCredential } from '../credentials'
|
4
|
+
import type { Middleware } from '../middleware'
|
5
|
+
import { type Service, type ServiceContext } from '../services/service'
|
6
|
+
import { type Constructor } from './registry'
|
7
|
+
|
8
|
+
export class AppBuilderImplementation extends AppBuilder {
|
9
|
+
host: string = DEFAULT_HOST
|
10
|
+
automaticDataCollectionEnabled: boolean = true
|
11
|
+
credential: CredentialBase = new DefaultCredential()
|
12
|
+
middlewares: Middleware[] = []
|
13
|
+
services: Array<[Constructor<any>, (context: ServiceContext) => Service]> = []
|
14
|
+
|
15
|
+
useHost(host: string): AppBuilder {
|
16
|
+
this.host = host ?? DEFAULT_HOST
|
17
|
+
return this
|
18
|
+
}
|
19
|
+
|
20
|
+
useAutomaticDataCollectionEnabled(value: boolean): AppBuilder {
|
21
|
+
if (value === undefined || value === null) {
|
22
|
+
throw new Error(
|
23
|
+
'useAutomaticDataCollectionEnabled, value is undefined|null'
|
24
|
+
)
|
25
|
+
}
|
26
|
+
this.automaticDataCollectionEnabled = value
|
27
|
+
return this
|
28
|
+
}
|
29
|
+
|
30
|
+
useCredential(credential: CredentialBase): AppBuilder {
|
31
|
+
if (credential === undefined || credential === null) {
|
32
|
+
throw new Error('useCredential, credential is undefined|null')
|
33
|
+
}
|
34
|
+
this.credential = credential
|
35
|
+
return this
|
36
|
+
}
|
37
|
+
|
38
|
+
addMiddleware(middleware: Middleware): AppBuilder {
|
39
|
+
if (middleware === undefined || middleware === null) {
|
40
|
+
throw new Error('addMiddleware, middleware is undefined|null')
|
41
|
+
}
|
42
|
+
this.middlewares.push(middleware)
|
43
|
+
return this
|
44
|
+
}
|
45
|
+
|
46
|
+
registerService<T extends Service>(
|
47
|
+
type: Constructor<T>,
|
48
|
+
factory: (context: ServiceContext) => T
|
49
|
+
): AppBuilder {
|
50
|
+
if (type === undefined || type === null) {
|
51
|
+
throw new Error('registerService, type is undefined|null')
|
52
|
+
}
|
53
|
+
if (factory === undefined || factory === null) {
|
54
|
+
throw new Error('registerService, factory is undefined|null')
|
55
|
+
}
|
56
|
+
this.services.push([type, factory])
|
57
|
+
return this
|
58
|
+
}
|
59
|
+
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import { type Constructor, type Registry } from './registry'
|
2
|
+
import { type Lifetime } from '../disposable'
|
3
|
+
|
4
|
+
export class Context {
|
5
|
+
constructor(
|
6
|
+
private readonly registry: Registry,
|
7
|
+
public readonly lifetime: Lifetime
|
8
|
+
) {}
|
9
|
+
|
10
|
+
resolve<T>(type: Constructor<T>): T | undefined {
|
11
|
+
return this.registry.get(type)
|
12
|
+
}
|
13
|
+
}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
import { AppImplementation } from './app.impl'
|
2
|
+
import { type AppBuilder } from '../appBuilder'
|
3
|
+
import { AppSdk } from '../appSdk'
|
4
|
+
|
5
|
+
export async function _createApp(
|
6
|
+
name: string,
|
7
|
+
setup?: (builder: AppBuilder) => Promise<void>
|
8
|
+
): Promise<AppSdk> {
|
9
|
+
const app = new AppImplementation(name)
|
10
|
+
await app.initialize(setup)
|
11
|
+
return app
|
12
|
+
}
|
@@ -0,0 +1,83 @@
|
|
1
|
+
export type Constructor<T> = new (...args: any[]) => T
|
2
|
+
|
3
|
+
abstract class Provider {
|
4
|
+
abstract provide(): any | undefined
|
5
|
+
}
|
6
|
+
|
7
|
+
class MethodProvider<T> extends Provider {
|
8
|
+
private instance?: T
|
9
|
+
private provided: boolean = false
|
10
|
+
|
11
|
+
constructor(
|
12
|
+
private readonly provider: () => T,
|
13
|
+
private readonly providerOnce: boolean = false
|
14
|
+
) {
|
15
|
+
super()
|
16
|
+
}
|
17
|
+
|
18
|
+
provide(): T | undefined {
|
19
|
+
if (this.providerOnce && this.provided) {
|
20
|
+
return this.instance
|
21
|
+
}
|
22
|
+
this.provided = true
|
23
|
+
this.instance = this.provider()
|
24
|
+
return this.instance
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
class ValueProvider<T> extends Provider {
|
29
|
+
constructor(private readonly value: T | undefined) {
|
30
|
+
super()
|
31
|
+
}
|
32
|
+
|
33
|
+
provide(): T | undefined {
|
34
|
+
return this.value
|
35
|
+
}
|
36
|
+
}
|
37
|
+
|
38
|
+
export class RegistryItem<T> {
|
39
|
+
constructor(
|
40
|
+
private readonly registry: Map<Constructor<any>, Provider>,
|
41
|
+
private readonly type: Constructor<T>
|
42
|
+
) {}
|
43
|
+
|
44
|
+
asValue(value: T): void {
|
45
|
+
this.registry.set(this.type, new ValueProvider<T>(value))
|
46
|
+
}
|
47
|
+
|
48
|
+
asProvider<T>(provider: () => T, oneTime: boolean = false): void {
|
49
|
+
this.registry.set(this.type, new MethodProvider<T>(provider, oneTime))
|
50
|
+
}
|
51
|
+
|
52
|
+
asFactory<T>(provider: () => T): void {
|
53
|
+
this.registry.set(this.type, new MethodProvider<T>(provider, false))
|
54
|
+
}
|
55
|
+
|
56
|
+
asSingleton<T>(provider: () => T): void {
|
57
|
+
this.registry.set(this.type, new MethodProvider<T>(provider, true))
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
export class Registry {
|
62
|
+
private readonly services: Map<Constructor<any>, Provider>
|
63
|
+
|
64
|
+
constructor() {
|
65
|
+
this.services = new Map()
|
66
|
+
}
|
67
|
+
|
68
|
+
map<T>(type: Constructor<T>): RegistryItem<T> {
|
69
|
+
return new RegistryItem<T>(this.services, type)
|
70
|
+
}
|
71
|
+
|
72
|
+
set<T>(type: Constructor<T>, provider: Provider): void {
|
73
|
+
this.services.set(type, provider)
|
74
|
+
}
|
75
|
+
|
76
|
+
get<T>(type: Constructor<T>): T | undefined {
|
77
|
+
const provider = this.services.get(type)
|
78
|
+
if (provider === undefined) {
|
79
|
+
return undefined
|
80
|
+
}
|
81
|
+
return provider.provide()
|
82
|
+
}
|
83
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import { type CredentialBase } from '../credentials'
|
2
|
+
import { Service } from './service'
|
3
|
+
import { type DisposableContainer } from '../disposable'
|
4
|
+
|
5
|
+
export class CredentialService extends Service {
|
6
|
+
private _credentialDispose?: DisposableContainer = undefined
|
7
|
+
private _credential?: CredentialBase = undefined
|
8
|
+
|
9
|
+
public get credential(): CredentialBase | undefined {
|
10
|
+
return this._credential
|
11
|
+
}
|
12
|
+
|
13
|
+
useCredential(credential: CredentialBase): void {
|
14
|
+
if (credential !== this._credential) {
|
15
|
+
if (this._credentialDispose !== undefined) {
|
16
|
+
this._credentialDispose.dispose()
|
17
|
+
}
|
18
|
+
this._credentialDispose = this.lifetime.defineNested()
|
19
|
+
this._credential = credential
|
20
|
+
|
21
|
+
credential.onRegister(this._credentialDispose.lifetime, this.context)
|
22
|
+
}
|
23
|
+
}
|
24
|
+
}
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import { Service } from './service'
|
2
|
+
import { type Middleware } from '../middleware'
|
3
|
+
import { type Disposable } from '../disposable'
|
4
|
+
|
5
|
+
export class MiddlewareService extends Service {
|
6
|
+
_middlewares: Middleware[] = []
|
7
|
+
|
8
|
+
public useMiddleware(middleware: Middleware): Disposable {
|
9
|
+
this._middlewares.push(middleware)
|
10
|
+
const result = this.lifetime.defineNested()
|
11
|
+
result.addCallback(() => {
|
12
|
+
this._middlewares = this._middlewares.filter(m => m !== middleware)
|
13
|
+
}, this)
|
14
|
+
return result
|
15
|
+
}
|
16
|
+
|
17
|
+
public async process(
|
18
|
+
req: Request,
|
19
|
+
next: (req: Request) => Promise<Response>
|
20
|
+
): Promise<Response> {
|
21
|
+
const middlewares = this._middlewares.slice()
|
22
|
+
let index = -1
|
23
|
+
|
24
|
+
const processNext = async (request: Request): Promise<Response> => {
|
25
|
+
index++
|
26
|
+
if (index < middlewares.length) {
|
27
|
+
await middlewares[index](request, processNext)
|
28
|
+
}
|
29
|
+
return await next(request)
|
30
|
+
}
|
31
|
+
return await processNext(req)
|
32
|
+
}
|
33
|
+
}
|
@@ -0,0 +1,72 @@
|
|
1
|
+
import { Service, type ServiceContext } from './service'
|
2
|
+
import { MiddlewareService } from './middlewareService'
|
3
|
+
|
4
|
+
export class RpcService extends Service {
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
6
|
+
async request(req: Request): Promise<Response> {
|
7
|
+
throw new Error('Not implemented')
|
8
|
+
}
|
9
|
+
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
11
|
+
buildUrl(path: string): string {
|
12
|
+
throw new Error('Not implemented')
|
13
|
+
}
|
14
|
+
|
15
|
+
async get(path: string): Promise<Response> {
|
16
|
+
return await this.request(
|
17
|
+
new Request(this.buildUrl(path), {
|
18
|
+
method: 'GET'
|
19
|
+
})
|
20
|
+
)
|
21
|
+
}
|
22
|
+
|
23
|
+
async post(path: string, body?: BodyInit | null): Promise<Response> {
|
24
|
+
return await this.request(
|
25
|
+
new Request(this.buildUrl(path), {
|
26
|
+
method: 'POST',
|
27
|
+
body
|
28
|
+
})
|
29
|
+
)
|
30
|
+
}
|
31
|
+
|
32
|
+
async put(path: string, body?: BodyInit | null): Promise<Response> {
|
33
|
+
return await this.request(
|
34
|
+
new Request(this.buildUrl(path), {
|
35
|
+
method: 'PUT',
|
36
|
+
body
|
37
|
+
})
|
38
|
+
)
|
39
|
+
}
|
40
|
+
|
41
|
+
async delete(path: string): Promise<Response> {
|
42
|
+
return await this.request(
|
43
|
+
new Request(this.buildUrl(path), {
|
44
|
+
method: 'DELETE'
|
45
|
+
})
|
46
|
+
)
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
export class RpcServiceImpl extends RpcService {
|
51
|
+
constructor(serviceContext: ServiceContext, public readonly host: string) {
|
52
|
+
super(serviceContext)
|
53
|
+
}
|
54
|
+
|
55
|
+
override async request(req: Request): Promise<Response> {
|
56
|
+
const middlewareService = this.resolve(MiddlewareService)
|
57
|
+
if (middlewareService !== undefined) {
|
58
|
+
return await middlewareService.process(req, async req => {
|
59
|
+
return await fetch(req)
|
60
|
+
})
|
61
|
+
} else {
|
62
|
+
return await fetch(req)
|
63
|
+
}
|
64
|
+
}
|
65
|
+
|
66
|
+
override buildUrl(path: string): string {
|
67
|
+
if (this.host.endsWith('/') && path.startsWith('/')) {
|
68
|
+
return `${this.host}${path.slice(1)}`
|
69
|
+
}
|
70
|
+
return `${this.host}${path}`
|
71
|
+
}
|
72
|
+
}
|
@@ -0,0 +1,46 @@
|
|
1
|
+
import { type Context } from '../internal/context'
|
2
|
+
import { type Constructor } from '../internal/registry'
|
3
|
+
import { type DisposableContainer, type Lifetime } from '../disposable'
|
4
|
+
|
5
|
+
export class ServiceContext {
|
6
|
+
constructor(
|
7
|
+
public readonly context: Context,
|
8
|
+
private readonly disposableContainer: DisposableContainer
|
9
|
+
) {}
|
10
|
+
|
11
|
+
public get lifetime(): Lifetime {
|
12
|
+
return this.disposableContainer.lifetime
|
13
|
+
}
|
14
|
+
|
15
|
+
resolve<T>(type: Constructor<T>): T | undefined {
|
16
|
+
return this.context.resolve(type)
|
17
|
+
}
|
18
|
+
|
19
|
+
public async onRegister(): Promise<void> {
|
20
|
+
await Promise.resolve()
|
21
|
+
}
|
22
|
+
|
23
|
+
public async onStart(): Promise<void> {
|
24
|
+
await Promise.resolve()
|
25
|
+
}
|
26
|
+
|
27
|
+
public onUnregister(): void {
|
28
|
+
// do nothing
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
export abstract class Service {
|
33
|
+
public resolve<T>(type: Constructor<T>): T | undefined {
|
34
|
+
return this.serviceContext.resolve(type)
|
35
|
+
}
|
36
|
+
|
37
|
+
public get lifetime(): Lifetime {
|
38
|
+
return this.serviceContext.lifetime
|
39
|
+
}
|
40
|
+
|
41
|
+
public get context(): Context {
|
42
|
+
return this.serviceContext.context
|
43
|
+
}
|
44
|
+
|
45
|
+
public constructor(private readonly serviceContext: ServiceContext) {}
|
46
|
+
}
|