@neuralinnovations/dataisland-sdk 0.0.1-dev1 → 0.0.1-dev2

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.
@@ -0,0 +1,126 @@
1
+ import { Service } from './service'
2
+ import {
3
+ OrganizationEvent,
4
+ OrganizationId,
5
+ Organizations
6
+ } from '../storages/organizations'
7
+ import { OrganizationDto, UserSettings } from '../dto/userInfoResponse'
8
+ import { RpcService } from './rpcService'
9
+ import { OrganizationImpl } from './organizationImpl'
10
+ import { OrganizationsImpl } from './organizationsImpl'
11
+
12
+ export class OrganizationService extends Service {
13
+ private impl: OrganizationsImpl = new OrganizationsImpl(this)
14
+
15
+ get organizations(): Organizations {
16
+ return this.impl
17
+ }
18
+
19
+ initFrom(
20
+ settings: UserSettings,
21
+ adminInOrganization: string[],
22
+ organizations: OrganizationDto[]
23
+ ) {
24
+ this.impl.currentOrganizationId = settings.activeOrganizationId
25
+ for (const organization of organizations) {
26
+ const org = new OrganizationImpl(this, this.impl).initFrom(
27
+ organization,
28
+ adminInOrganization.includes(organization.id)
29
+ )
30
+ // add organization to collection
31
+ this.impl.organizations.push(org)
32
+
33
+ // dispatch event, organization added
34
+ this.impl.dispatch({
35
+ type: OrganizationEvent.ADDED,
36
+ data: org
37
+ })
38
+ }
39
+ }
40
+
41
+ async deleteOrganization(id: OrganizationId): Promise<void> {
42
+ if (id === undefined || id === null) {
43
+ throw new Error('Organization delete, id is undefined or null')
44
+ }
45
+ if (id.length === 0 || id.trim().length === 0) {
46
+ throw new Error('Organization delete, id is empty')
47
+ }
48
+ if (!this.impl.contains(id)) {
49
+ throw new Error(`Organization delete, id: ${id} is not found`)
50
+ }
51
+ const response = await this.resolve(RpcService)
52
+ ?.requestBuilder('/api/v1/Organizations')
53
+ .searchParam('id', id)
54
+ .sendDelete()
55
+ if (!response?.ok) {
56
+ let text: string = ''
57
+ try {
58
+ text = (await response?.text()) ?? ''
59
+ } catch (e) {
60
+ console.error(e)
61
+ }
62
+
63
+ throw new Error(
64
+ `Organization delete, response is not ok, status: ${response?.status},${response?.statusText} ${text}`
65
+ )
66
+ }
67
+ const org = <OrganizationImpl>this.impl.get(id)
68
+ const index = this.impl.organizations.indexOf(org)
69
+ if (index < 0) {
70
+ throw new Error('Organization delete, index is not found')
71
+ }
72
+
73
+ // remove organization from collection
74
+ this.impl.organizations.splice(index, 1)
75
+
76
+ // dispatch event, organization removed
77
+ this.impl.dispatch({
78
+ type: OrganizationEvent.REMOVED,
79
+ data: org
80
+ })
81
+
82
+ // dispose organization
83
+ org.dispose()
84
+ }
85
+
86
+ async createOrganization(
87
+ name: string,
88
+ description: string
89
+ ): Promise<OrganizationImpl> {
90
+ if (name === undefined || name === null) {
91
+ throw new Error('Organization create, name is undefined or null')
92
+ }
93
+ if (description === undefined || description === null) {
94
+ throw new Error('Organization create, description is undefined or null')
95
+ }
96
+ if (name.length === 0 || name.trim().length === 0) {
97
+ throw new Error('Organization create, name is empty')
98
+ }
99
+ const response = await this.resolve(RpcService)
100
+ ?.requestBuilder('api/v1/Organizations')
101
+ .sendPost({
102
+ profile: {
103
+ name: name,
104
+ description: description
105
+ }
106
+ })
107
+ if (!response?.ok) {
108
+ throw new Error('Organization create, response is not ok')
109
+ }
110
+ const content = (await response.json())['organization'] as OrganizationDto
111
+
112
+ // create organization and init from content
113
+ const org = new OrganizationImpl(this, this.impl).initFrom(content, true)
114
+
115
+ // add organization to collection
116
+ this.impl.organizations.push(org)
117
+
118
+ // dispatch event, organization added
119
+ this.impl.dispatch({
120
+ type: OrganizationEvent.ADDED,
121
+ data: org
122
+ })
123
+
124
+ return org
125
+ }
126
+ }
@@ -0,0 +1,55 @@
1
+ import {
2
+ Organization,
3
+ OrganizationEvent,
4
+ OrganizationId,
5
+ Organizations
6
+ } from '../storages/organizations'
7
+ import { OrganizationImpl } from './organizationImpl'
8
+ import { OrganizationService } from './organizationService'
9
+
10
+ export class OrganizationsImpl extends Organizations {
11
+ constructor(public readonly service: OrganizationService) {
12
+ super()
13
+ }
14
+
15
+ public organizations: OrganizationImpl[] = []
16
+ public currentOrganizationId?: OrganizationId
17
+
18
+ get collection(): readonly Organization[] {
19
+ return this.organizations
20
+ }
21
+
22
+ get current(): OrganizationId {
23
+ return <OrganizationId>this.currentOrganizationId
24
+ }
25
+
26
+ set current(value: OrganizationId) {
27
+ if (this.currentOrganizationId !== value) {
28
+ this.currentOrganizationId = value
29
+ this.dispatch({
30
+ type: OrganizationEvent.CURRENT_CHANGED,
31
+ data: this
32
+ })
33
+ }
34
+ }
35
+
36
+ get(id: OrganizationId): Organization {
37
+ return <Organization>this.tryGet(id)
38
+ }
39
+
40
+ tryGet(id: OrganizationId): Organization | undefined {
41
+ return this.organizations.find(organization => organization.id === id)
42
+ }
43
+
44
+ contains(id: OrganizationId): boolean {
45
+ return this.organizations.some(organization => organization.id === id)
46
+ }
47
+
48
+ async create(name: string, description: string): Promise<Organization> {
49
+ return this.service.createOrganization(name, description)
50
+ }
51
+
52
+ delete(id: string): Promise<void> {
53
+ return this.service.deleteOrganization(id)
54
+ }
55
+ }
@@ -0,0 +1,102 @@
1
+ export class RequestBuilder {
2
+ private readonly _headers: Headers
3
+ private readonly _searchParams: URLSearchParams
4
+
5
+ constructor(
6
+ private readonly _url: URL,
7
+ private readonly _request: (req: Request) => Promise<Response>
8
+ ) {
9
+ this._headers = new Headers()
10
+ this._searchParams = new URLSearchParams()
11
+ }
12
+
13
+ public header(name: string, value: string): RequestBuilder {
14
+ this._headers.set(name, value)
15
+ return this
16
+ }
17
+
18
+ public headers(
19
+ headers?: [string, string][] | Record<string, string> | Headers
20
+ ): RequestBuilder {
21
+ if (headers === undefined) {
22
+ return this
23
+ }
24
+ if (headers instanceof Headers) {
25
+ headers.forEach((value, name) => {
26
+ this._headers.set(name, value)
27
+ })
28
+ } else {
29
+ Object.entries(headers).forEach(([name, value]) => {
30
+ this._headers.set(name, value)
31
+ })
32
+ }
33
+ return this
34
+ }
35
+
36
+ public searchParam(name: string, value: string): RequestBuilder {
37
+ this._searchParams.set(name, value)
38
+ return this
39
+ }
40
+
41
+ public searchParams(searchParams?: Map<string, string>): RequestBuilder {
42
+ if (searchParams === undefined) {
43
+ return this
44
+ }
45
+ searchParams.forEach((value, name) => {
46
+ this._searchParams.set(name, value)
47
+ })
48
+ return this
49
+ }
50
+
51
+ public async sendPost(body?: BodyInit | null | object): Promise<Response> {
52
+ const url = this._url
53
+ url.search = this._searchParams.toString()
54
+ if (body !== undefined && body !== null && typeof body === 'object') {
55
+ body = JSON.stringify(body)
56
+ }
57
+ return await this._request(
58
+ new Request(url, {
59
+ method: 'POST',
60
+ headers: this._headers,
61
+ body
62
+ })
63
+ )
64
+ }
65
+
66
+ public async sendGet(): Promise<Response> {
67
+ const url = this._url
68
+ url.search = this._searchParams.toString()
69
+ return await this._request(
70
+ new Request(url, {
71
+ method: 'GET',
72
+ headers: this._headers
73
+ })
74
+ )
75
+ }
76
+
77
+ public async sendDelete(): Promise<Response> {
78
+ const url = this._url
79
+ url.search = this._searchParams.toString()
80
+ return await this._request(
81
+ new Request(url, {
82
+ method: 'DELETE',
83
+ headers: this._headers
84
+ })
85
+ )
86
+ }
87
+
88
+ public async sendPut(body?: BodyInit | null | object): Promise<Response> {
89
+ const url = this._url
90
+ url.search = this._searchParams.toString()
91
+ if (body !== undefined && body !== null && typeof body === 'object') {
92
+ body = JSON.stringify(body)
93
+ }
94
+ return await this._request(
95
+ new Request(url, {
96
+ method: 'PUT',
97
+ headers: this._headers,
98
+ body
99
+ })
100
+ )
101
+ }
102
+ }
@@ -1,72 +1,132 @@
1
1
  import { Service, type ServiceContext } from './service'
2
2
  import { MiddlewareService } from './middlewareService'
3
+ import { RequestBuilder } from './requestBuilder'
3
4
 
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
- }
5
+ /**
6
+ * Options for the RpcService.
7
+ */
8
+ export interface RequestOptions {
9
+ searchParams?: Map<string, string>
10
+ headers?: [string, string][] | Record<string, string> | Headers
11
+ }
9
12
 
10
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
11
- buildUrl(path: string): string {
12
- throw new Error('Not implemented')
13
+ /**
14
+ * RPC service.
15
+ */
16
+ export class RpcService extends Service {
17
+ constructor(
18
+ serviceContext: ServiceContext,
19
+ /**
20
+ * Host of the RPC service.
21
+ * It is not used if you use the `urlBuilder` option.
22
+ */
23
+ public readonly host: string,
24
+ /**
25
+ * Options for the RpcService.
26
+ */
27
+ private readonly options?: {
28
+ // make it possible to override the url builder
29
+ urlBuilder?: (path: string) => URL
30
+ // make it possible to override the fetch method
31
+ fetchMethod?: (uri: Request) => Promise<Response>
32
+ }
33
+ ) {
34
+ super(serviceContext)
13
35
  }
14
36
 
15
- async get(path: string): Promise<Response> {
16
- return await this.request(
17
- new Request(this.buildUrl(path), {
18
- method: 'GET'
37
+ /**
38
+ * Request method.
39
+ */
40
+ async request(req: Request): Promise<Response> {
41
+ const middlewareService = this.resolve(MiddlewareService)
42
+ if (middlewareService !== undefined) {
43
+ return await middlewareService.process(req, async req => {
44
+ return (await this.options?.fetchMethod?.(req)) ?? (await fetch(req))
19
45
  })
20
- )
46
+ }
47
+ return (await this.options?.fetchMethod?.(req)) ?? (await fetch(req))
21
48
  }
22
49
 
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
- )
50
+ /**
51
+ * Build URL.
52
+ * @param path
53
+ */
54
+ buildUrl(path: string): URL {
55
+ if (this.options !== undefined && this.options.urlBuilder !== undefined) {
56
+ return this.options.urlBuilder(path)
57
+ }
58
+ if (this.host.endsWith('/') && path.startsWith('/')) {
59
+ return new URL(`${this.host}${path.slice(1)}`)
60
+ }
61
+ if (!this.host.endsWith('/') && !path.startsWith('/')) {
62
+ return new URL(`${this.host}/${path}`)
63
+ }
64
+ return new URL(`${this.host}${path}`)
30
65
  }
31
66
 
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
- )
67
+ /**
68
+ * Create a request builder.
69
+ * @param path
70
+ */
71
+ requestBuilder(path: string): RequestBuilder {
72
+ return new RequestBuilder(this.buildUrl(path), this.request.bind(this))
39
73
  }
40
74
 
41
- async delete(path: string): Promise<Response> {
42
- return await this.request(
43
- new Request(this.buildUrl(path), {
44
- method: 'DELETE'
45
- })
46
- )
75
+ /**
76
+ * Send a GET request.
77
+ * @param path
78
+ * @param options
79
+ */
80
+ async get(path: string, options?: RequestOptions): Promise<Response> {
81
+ return this.requestBuilder(path)
82
+ .searchParams(options?.searchParams)
83
+ .headers(options?.headers)
84
+ .sendGet()
47
85
  }
48
- }
49
86
 
50
- export class RpcServiceImpl extends RpcService {
51
- constructor(serviceContext: ServiceContext, public readonly host: string) {
52
- super(serviceContext)
87
+ /**
88
+ * Send a POST request.
89
+ * @param path
90
+ * @param body
91
+ * @param options
92
+ */
93
+ async post(
94
+ path: string,
95
+ body?: BodyInit | null,
96
+ options?: RequestOptions
97
+ ): Promise<Response> {
98
+ return this.requestBuilder(path)
99
+ .searchParams(options?.searchParams)
100
+ .headers(options?.headers)
101
+ .sendPost(body)
53
102
  }
54
103
 
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
- }
104
+ /**
105
+ * Send a PUT request.
106
+ * @param path
107
+ * @param body
108
+ * @param options
109
+ */
110
+ async put(
111
+ path: string,
112
+ body?: BodyInit | null,
113
+ options?: RequestOptions
114
+ ): Promise<Response> {
115
+ return this.requestBuilder(path)
116
+ .searchParams(options?.searchParams)
117
+ .headers(options?.headers)
118
+ .sendPut(body)
64
119
  }
65
120
 
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}`
121
+ /**
122
+ * Send a DELETE request.
123
+ * @param path
124
+ * @param options
125
+ */
126
+ async delete(path: string, options?: RequestOptions): Promise<Response> {
127
+ return this.requestBuilder(path)
128
+ .searchParams(options?.searchParams)
129
+ .headers(options?.headers)
130
+ .sendDelete()
71
131
  }
72
132
  }
@@ -1,4 +1,4 @@
1
- import { type Context } from '../internal/context'
1
+ import { type Context } from '../context'
2
2
  import { type Constructor } from '../internal/registry'
3
3
  import { type DisposableContainer, type Lifetime } from '../disposable'
4
4
 
@@ -0,0 +1,86 @@
1
+ import { Service } from './service'
2
+ import { RpcService } from './rpcService'
3
+ import { UserEvent, UserProfile } from '../storages/userProfile'
4
+ import { UserInfoResponse } from '../dto/userInfoResponse'
5
+ import { OrganizationService } from './organizationService'
6
+
7
+ class UserProfileImpl extends UserProfile {
8
+ private content?: UserInfoResponse
9
+
10
+ get id(): string {
11
+ if (this.content) {
12
+ return this.content.user.id
13
+ }
14
+ throw new Error('The profile is not loaded.')
15
+ }
16
+
17
+ get name(): string {
18
+ if (this.content) {
19
+ return this.content.user.profile.name
20
+ }
21
+ throw new Error('The profile is not loaded.')
22
+ }
23
+
24
+ get email(): string {
25
+ if (this.content) {
26
+ return this.content.user.profile.email
27
+ }
28
+ throw new Error('The profile is not loaded.')
29
+ }
30
+
31
+ get isDeleted(): boolean {
32
+ if (this.content) {
33
+ return this.content.user.isDeleted
34
+ }
35
+ throw new Error('The profile is not loaded.')
36
+ }
37
+
38
+ get createdAt(): Date {
39
+ if (this.content) {
40
+ return new Date(this.content.user.created_at)
41
+ }
42
+ throw new Error('The profile is not loaded.')
43
+ }
44
+
45
+ get modifiedAt(): Date {
46
+ if (this.content) {
47
+ return new Date(this.content.user.modified_at)
48
+ }
49
+ throw new Error('The profile is not loaded.')
50
+ }
51
+
52
+ initFrom(content: UserInfoResponse) {
53
+ this.content = content
54
+ this.dispatch({
55
+ type: UserEvent.CHANGED,
56
+ data: this
57
+ })
58
+ }
59
+ }
60
+
61
+ export class UserProfileService extends Service {
62
+ private readonly impl: UserProfileImpl = new UserProfileImpl()
63
+
64
+ get userProfile(): UserProfile {
65
+ return this.impl
66
+ }
67
+
68
+ async fetch(fireError: boolean = true) {
69
+ const rpc = this.resolve(RpcService) as RpcService
70
+ const response = await rpc.requestBuilder('api/v1/Users/self2').sendGet()
71
+ if (fireError && !response.ok) {
72
+ throw new Error('Failed to fetch user profile.')
73
+ }
74
+ const content = (await response.json()) as UserInfoResponse
75
+ this.impl.initFrom(content)
76
+
77
+ const organizationService = this.resolve(
78
+ OrganizationService
79
+ ) as OrganizationService
80
+ organizationService.initFrom(
81
+ content.user.settings,
82
+ content.adminInOrganization,
83
+ content.organizations
84
+ )
85
+ }
86
+ }
@@ -0,0 +1,76 @@
1
+ import { EventDispatcher } from '../events'
2
+
3
+ /**
4
+ * Organization id.
5
+ */
6
+ export type OrganizationId = string
7
+
8
+ /**
9
+ * Organization.
10
+ */
11
+ export abstract class Organization {
12
+ /**
13
+ * Organization id.
14
+ */
15
+ abstract get id(): OrganizationId
16
+
17
+ /**
18
+ * Organization name.
19
+ */
20
+ abstract get name(): string
21
+
22
+ /**
23
+ * Organization description.
24
+ */
25
+ abstract get description(): string
26
+ }
27
+
28
+ /**
29
+ * Organization event.
30
+ */
31
+ export enum OrganizationEvent {
32
+ ADDED = 'added',
33
+ REMOVED = 'removed',
34
+ CHANGED = 'changed',
35
+ CURRENT_CHANGED = 'currentChanged'
36
+ }
37
+
38
+ /**
39
+ * Organizations storage.
40
+ */
41
+ export abstract class Organizations extends EventDispatcher<
42
+ OrganizationEvent,
43
+ Organization | Organizations
44
+ > {
45
+ /**
46
+ * User's organizations.
47
+ */
48
+ abstract get collection(): ReadonlyArray<Organization>
49
+
50
+ /**
51
+ * Current organization.
52
+ */
53
+ abstract get current(): OrganizationId
54
+ abstract set current(value: OrganizationId)
55
+
56
+ /**
57
+ * Get organization by id.
58
+ */
59
+ abstract get(id: OrganizationId): Organization
60
+
61
+ /**
62
+ * Try to get organization by id.
63
+ * @param id
64
+ */
65
+ abstract tryGet(id: OrganizationId): Organization | undefined
66
+
67
+ /**
68
+ * Create new organization.
69
+ */
70
+ abstract create(name: string, description: string): Promise<Organization>
71
+
72
+ /**
73
+ * Delete organization.
74
+ */
75
+ abstract delete(id: OrganizationId): Promise<void>
76
+ }
@@ -0,0 +1,42 @@
1
+ import { EventDispatcher } from '../events'
2
+
3
+ export type UserId = string
4
+
5
+ export enum UserEvent {
6
+ CHANGED = 'changed'
7
+ }
8
+
9
+ export abstract class UserProfile extends EventDispatcher<
10
+ UserEvent,
11
+ UserProfile
12
+ > {
13
+ /**
14
+ * User id.
15
+ */
16
+ abstract get id(): UserId
17
+
18
+ /**
19
+ * User name.
20
+ */
21
+ abstract get name(): string
22
+
23
+ /**
24
+ * User email.
25
+ */
26
+ abstract get email(): string
27
+
28
+ /**
29
+ * Is user deleted.
30
+ */
31
+ abstract get isDeleted(): boolean
32
+
33
+ /**
34
+ * Created at.
35
+ */
36
+ abstract get createdAt(): Date
37
+
38
+ /**
39
+ * Modified at.
40
+ */
41
+ abstract get modifiedAt(): Date
42
+ }