@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/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,7 @@
1
+ /**
2
+ * DataIsland App request middleware.
3
+ */
4
+ export type Middleware = (
5
+ req: Request,
6
+ next: (req: Request) => Promise<Response>
7
+ ) => Promise<void>
@@ -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
+ }