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

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.
Files changed (56) hide show
  1. package/.editorconfig +4 -1
  2. package/.eslintrc.json +1 -1
  3. package/jest.config.ts +3 -3
  4. package/jest.setup.ts +2 -2
  5. package/package.json +3 -2
  6. package/src/appBuilder.ts +6 -6
  7. package/src/appSdk.ts +6 -6
  8. package/src/commands/startCommandHandler.ts +2 -2
  9. package/src/context.ts +3 -3
  10. package/src/credentials.ts +29 -7
  11. package/src/disposable.ts +1 -1
  12. package/src/dto/accessGroupResponse.ts +35 -0
  13. package/src/dto/chatResponse.ts +104 -0
  14. package/src/dto/userInfoResponse.ts +11 -1
  15. package/src/dto/workspacesResponse.ts +49 -0
  16. package/src/events.ts +1 -1
  17. package/src/index.ts +10 -12
  18. package/src/internal/app.impl.ts +21 -21
  19. package/src/internal/appBuilder.impl.ts +16 -16
  20. package/src/internal/createApp.impl.ts +3 -3
  21. package/src/services/commandService.ts +3 -3
  22. package/src/services/credentialService.ts +3 -3
  23. package/src/services/middlewareService.ts +3 -3
  24. package/src/services/organizationService.ts +18 -116
  25. package/src/services/requestBuilder.ts +6 -6
  26. package/src/services/responseUtils.ts +32 -0
  27. package/src/services/rpcService.ts +5 -5
  28. package/src/services/service.ts +3 -3
  29. package/src/services/userProfileService.ts +18 -66
  30. package/src/storages/chat.ts +37 -0
  31. package/src/storages/file.impl.ts +68 -0
  32. package/src/storages/files.impl.ts +192 -0
  33. package/src/storages/files.ts +67 -0
  34. package/src/storages/groups.impl.ts +337 -0
  35. package/src/storages/groups.ts +43 -0
  36. package/src/storages/organization.impl.ts +68 -0
  37. package/src/storages/organization.ts +33 -0
  38. package/src/storages/organizations.impl.ts +191 -0
  39. package/src/storages/organizations.ts +8 -28
  40. package/src/storages/userProfile.impl.ts +56 -0
  41. package/src/storages/userProfile.ts +2 -2
  42. package/src/storages/workspace.impl.ts +109 -0
  43. package/src/storages/workspace.ts +43 -0
  44. package/src/storages/workspaces.impl.ts +212 -0
  45. package/src/storages/workspaces.ts +53 -0
  46. package/test/commands.test.ts +8 -8
  47. package/test/disposable.test.ts +3 -3
  48. package/test/events.test.ts +4 -4
  49. package/test/index.test.ts +102 -40
  50. package/test/registry.test.ts +8 -8
  51. package/test/services.test.ts +15 -15
  52. package/test/unitTest.test.ts +2 -2
  53. package/test_file.pdf +0 -0
  54. package/src/services/organizationImpl.ts +0 -51
  55. package/src/services/organizationsImpl.ts +0 -55
  56. package/src/types.ts +0 -86
@@ -1,126 +1,28 @@
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'
1
+ import { Service } from "./service"
2
+ import { Organizations } from "../storages/organizations"
3
+ import { OrganizationDto, UserSettings } from "../dto/userInfoResponse"
4
+ import { OrganizationsImpl } from "../storages/organizations.impl"
11
5
 
12
6
  export class OrganizationService extends Service {
13
- private impl: OrganizationsImpl = new OrganizationsImpl(this)
7
+ private _impl?: OrganizationsImpl
8
+
9
+ private get impl(): OrganizationsImpl {
10
+ return this._impl ?? (this._impl = new OrganizationsImpl(this.context))
11
+ }
14
12
 
15
13
  get organizations(): Organizations {
16
14
  return this.impl
17
15
  }
18
16
 
19
- initFrom(
20
- settings: UserSettings,
17
+ async initFrom(
21
18
  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
19
+ organizations: OrganizationDto[],
20
+ settings?: UserSettings | null
21
+ ): Promise<void> {
22
+ await this.impl.internalInitFrom(
23
+ adminInOrganization,
24
+ organizations,
25
+ settings
26
+ )
125
27
  }
126
28
  }
@@ -51,12 +51,12 @@ export class RequestBuilder {
51
51
  public async sendPost(body?: BodyInit | null | object): Promise<Response> {
52
52
  const url = this._url
53
53
  url.search = this._searchParams.toString()
54
- if (body !== undefined && body !== null && typeof body === 'object') {
54
+ if (body !== undefined && body !== null && typeof body === "object") {
55
55
  body = JSON.stringify(body)
56
56
  }
57
57
  return await this._request(
58
58
  new Request(url, {
59
- method: 'POST',
59
+ method: "POST",
60
60
  headers: this._headers,
61
61
  body
62
62
  })
@@ -68,7 +68,7 @@ export class RequestBuilder {
68
68
  url.search = this._searchParams.toString()
69
69
  return await this._request(
70
70
  new Request(url, {
71
- method: 'GET',
71
+ method: "GET",
72
72
  headers: this._headers
73
73
  })
74
74
  )
@@ -79,7 +79,7 @@ export class RequestBuilder {
79
79
  url.search = this._searchParams.toString()
80
80
  return await this._request(
81
81
  new Request(url, {
82
- method: 'DELETE',
82
+ method: "DELETE",
83
83
  headers: this._headers
84
84
  })
85
85
  )
@@ -88,12 +88,12 @@ export class RequestBuilder {
88
88
  public async sendPut(body?: BodyInit | null | object): Promise<Response> {
89
89
  const url = this._url
90
90
  url.search = this._searchParams.toString()
91
- if (body !== undefined && body !== null && typeof body === 'object') {
91
+ if (body !== undefined && body !== null && typeof body === "object") {
92
92
  body = JSON.stringify(body)
93
93
  }
94
94
  return await this._request(
95
95
  new Request(url, {
96
- method: 'PUT',
96
+ method: "PUT",
97
97
  headers: this._headers,
98
98
  body
99
99
  })
@@ -0,0 +1,32 @@
1
+ export class ResponseUtils {
2
+ public static isOk(response?: Response | null): boolean {
3
+ return response !== undefined && response !== null && response.ok
4
+ }
5
+
6
+ public static isFail(response?: Response | null): boolean {
7
+ return !ResponseUtils.isOk(response)
8
+ }
9
+
10
+ public static async throwError(
11
+ message: string,
12
+ response: Response | undefined | null
13
+ ): Promise<void> {
14
+ if (response === undefined) {
15
+ throw new Error(`${message}. Response is undefined`)
16
+ }
17
+ if (response === null) {
18
+ throw new Error(`${message}. Response is null`)
19
+ }
20
+ let errorBody: string = ""
21
+ if (response) {
22
+ try {
23
+ errorBody = (await response.text()) ?? ""
24
+ } catch (e) {
25
+ console.error(e)
26
+ }
27
+ }
28
+ throw new Error(
29
+ `${message}. Response fail. Status: ${response?.status},${response?.statusText}, body: ${errorBody}`
30
+ )
31
+ }
32
+ }
@@ -1,6 +1,6 @@
1
- import { Service, type ServiceContext } from './service'
2
- import { MiddlewareService } from './middlewareService'
3
- import { RequestBuilder } from './requestBuilder'
1
+ import { Service, type ServiceContext } from "./service"
2
+ import { MiddlewareService } from "./middlewareService"
3
+ import { RequestBuilder } from "./requestBuilder"
4
4
 
5
5
  /**
6
6
  * Options for the RpcService.
@@ -55,10 +55,10 @@ export class RpcService extends Service {
55
55
  if (this.options !== undefined && this.options.urlBuilder !== undefined) {
56
56
  return this.options.urlBuilder(path)
57
57
  }
58
- if (this.host.endsWith('/') && path.startsWith('/')) {
58
+ if (this.host.endsWith("/") && path.startsWith("/")) {
59
59
  return new URL(`${this.host}${path.slice(1)}`)
60
60
  }
61
- if (!this.host.endsWith('/') && !path.startsWith('/')) {
61
+ if (!this.host.endsWith("/") && !path.startsWith("/")) {
62
62
  return new URL(`${this.host}/${path}`)
63
63
  }
64
64
  return new URL(`${this.host}${path}`)
@@ -1,6 +1,6 @@
1
- import { type Context } from '../context'
2
- import { type Constructor } from '../internal/registry'
3
- import { type DisposableContainer, type Lifetime } from '../disposable'
1
+ import { type Context } from "../context"
2
+ import { type Constructor } from "../internal/registry"
3
+ import { type DisposableContainer, type Lifetime } from "../disposable"
4
4
 
5
5
  export class ServiceContext {
6
6
  constructor(
@@ -1,62 +1,10 @@
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
- }
1
+ import { Service } from "./service"
2
+ import { RpcService } from "./rpcService"
3
+ import { UserProfile } from "../storages/userProfile"
4
+ import { UserInfoResponse } from "../dto/userInfoResponse"
5
+ import { OrganizationService } from "./organizationService"
6
+ import { UserProfileImpl } from "../storages/userProfile.impl"
7
+ import { ResponseUtils } from "./responseUtils"
60
8
 
61
9
  export class UserProfileService extends Service {
62
10
  private readonly impl: UserProfileImpl = new UserProfileImpl()
@@ -65,22 +13,26 @@ export class UserProfileService extends Service {
65
13
  return this.impl
66
14
  }
67
15
 
68
- async fetch(fireError: boolean = true) {
16
+ async fetch() {
69
17
  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.')
18
+ const response = await rpc.requestBuilder("api/v1/Users/self2").sendGet()
19
+ if (ResponseUtils.isFail(response)) {
20
+ await ResponseUtils.throwError("Failed to fetch user profile", response)
73
21
  }
74
22
  const content = (await response.json()) as UserInfoResponse
23
+
24
+ // init user profile from the server's response
75
25
  this.impl.initFrom(content)
76
26
 
77
27
  const organizationService = this.resolve(
78
28
  OrganizationService
79
29
  ) as OrganizationService
80
- organizationService.initFrom(
81
- content.user.settings,
30
+
31
+ // init organization service from user profile
32
+ await organizationService.initFrom(
82
33
  content.adminInOrganization,
83
- content.organizations
34
+ content.organizations,
35
+ content.user.settings
84
36
  )
85
37
  }
86
38
  }
@@ -0,0 +1,37 @@
1
+ import { EventDispatcher } from "../events"
2
+
3
+ export type ChatId = string
4
+
5
+ export enum ChatsEvent {
6
+ ADDED = "added",
7
+ REMOVED = "removed"
8
+ }
9
+
10
+ export enum ChatAnswer {
11
+ SHORT = "short",
12
+ LONG = "long"
13
+ }
14
+
15
+ export abstract class Chat {
16
+ /**
17
+ * Chat id.
18
+ */
19
+ abstract get id(): ChatId
20
+
21
+ /**
22
+ * Chat name.
23
+ */
24
+ abstract get name(): string
25
+
26
+ abstract question(message: string, answer?: ChatAnswer): Promise<void>
27
+ }
28
+
29
+ /**
30
+ * Chats storage.
31
+ */
32
+ export abstract class Chats extends EventDispatcher<ChatsEvent, Chat> {
33
+ /**
34
+ * Create new chat.
35
+ */
36
+ abstract create(): Promise<Chat>
37
+ }
@@ -0,0 +1,68 @@
1
+ import { Context } from "../context"
2
+ import { Disposable } from "../disposable"
3
+ import { FileDto, FileProgressDto } from "../dto/workspacesResponse"
4
+ import { RpcService } from "../services/rpcService"
5
+ import { File } from "./files"
6
+ import { ResponseUtils } from "../services/responseUtils"
7
+
8
+ export class FileImpl extends File implements Disposable {
9
+ private _isDisposed: boolean = false
10
+ private _content?: FileDto
11
+
12
+ constructor(private readonly context: Context) {
13
+ super()
14
+ }
15
+
16
+ public initFrom(file: FileDto): File {
17
+ this._content = file
18
+
19
+ return this
20
+ }
21
+
22
+ get isDisposed(): boolean {
23
+ return this._isDisposed
24
+ }
25
+
26
+ dispose(): void {
27
+ this._isDisposed = true
28
+ }
29
+
30
+ get id(): string {
31
+ return <string>this._content?.id
32
+ }
33
+
34
+ get name(): string {
35
+ return <string>this._content?.id
36
+ }
37
+
38
+ async url(): Promise<string> {
39
+ const response = await this.context
40
+ .resolve(RpcService)
41
+ ?.requestBuilder("api/v1/Files/url")
42
+ .searchParam("id", this.id)
43
+ .sendGet()
44
+
45
+ if (ResponseUtils.isFail(response)) {
46
+ await ResponseUtils.throwError(
47
+ `Failed to get file ${this.id} url`,
48
+ response
49
+ )
50
+ }
51
+
52
+ return (await response!.json()).url
53
+ }
54
+
55
+ async status(): Promise<FileProgressDto> {
56
+ const response = await this.context
57
+ .resolve(RpcService)
58
+ ?.requestBuilder("api/v1/Files/url")
59
+ .searchParam("id", this.id)
60
+ .sendGet()
61
+
62
+ if (ResponseUtils.isFail(response)) {
63
+ await ResponseUtils.throwError(`Failed to get file ${this.id}`, response)
64
+ }
65
+
66
+ return (await response!.json()).progress as FileProgressDto
67
+ }
68
+ }
@@ -0,0 +1,192 @@
1
+ import { Context } from "../context"
2
+ import { Disposable } from "../disposable"
3
+ import { FileDto, FileListResponse } from "../dto/workspacesResponse"
4
+ import { OrganizationService } from "../services/organizationService"
5
+ import { RpcService } from "../services/rpcService"
6
+ import { FileImpl } from "./file.impl"
7
+ import { File, Files, FilesEvent, FilesList as FilesPage } from "./files"
8
+ import { WorkspaceImpl } from "./workspace.impl"
9
+ import { ResponseUtils } from "../services/responseUtils"
10
+
11
+ export class FilesPageImpl extends FilesPage implements Disposable {
12
+ private _isDisposed: boolean = false
13
+
14
+ public files: File[] = []
15
+ public total: number = 0
16
+ public filesPerPage: number = 0
17
+ public page: number = 0
18
+
19
+ get pages(): number {
20
+ return Math.ceil(Math.max(this.total / this.filesPerPage, 1.0))
21
+ }
22
+
23
+ get isDisposed(): boolean {
24
+ return this._isDisposed
25
+ }
26
+
27
+ dispose(): void {
28
+ this._isDisposed = true
29
+ }
30
+ }
31
+
32
+ export class FilesImpl extends Files {
33
+ constructor(
34
+ private readonly workspace: WorkspaceImpl,
35
+ private readonly context: Context
36
+ ) {
37
+ super()
38
+ }
39
+
40
+ // Object used as files page data, returned by "query"
41
+ public filesList?: FilesPage
42
+
43
+ async upload(file: any): Promise<File> {
44
+ return await this.internalUpload(file)
45
+ }
46
+
47
+ async delete(id: string): Promise<void> {
48
+ return await this.internalDeleteFile(id)
49
+ }
50
+
51
+ async query(query: string, page: number, limit: number): Promise<FilesPage> {
52
+ return await this.internalQuery(query, page, limit)
53
+ }
54
+
55
+ //----------------------------------------------------------------------------
56
+ // INTERNALS
57
+ //----------------------------------------------------------------------------
58
+
59
+ /**
60
+ * Delete organization.
61
+ * @param id
62
+ */
63
+ async internalDeleteFile(id: string): Promise<void> {
64
+ if (id === undefined || id === null) {
65
+ throw new Error("File delete, id is undefined or null")
66
+ }
67
+ if (id.length === 0 || id.trim().length === 0) {
68
+ throw new Error("File delete, id is empty")
69
+ }
70
+
71
+ const response = await this.context
72
+ .resolve(RpcService)
73
+ ?.requestBuilder("/api/v1/Files")
74
+ .searchParam("id", id)
75
+ .sendDelete()
76
+ if (ResponseUtils.isFail(response)) {
77
+ await ResponseUtils.throwError(`File ${id} delete, failed`, response)
78
+ }
79
+ const file = <FileImpl>this.filesList!.files.find(f => f.id === id)
80
+ const index = this.filesList!.files.indexOf(file)
81
+ if (index < 0) {
82
+ throw new Error("Organization delete, index is not found")
83
+ }
84
+
85
+ // remove file from collection
86
+ this.filesList!.files.splice(index, 1)
87
+
88
+ // dispatch event, file removed
89
+ this.dispatch({
90
+ type: FilesEvent.REMOVED,
91
+ data: file
92
+ })
93
+
94
+ // dispose file
95
+ file.dispose()
96
+ }
97
+
98
+ async internalQuery(
99
+ query: string,
100
+ page: number,
101
+ limit: number
102
+ ): Promise<FilesPage> {
103
+ if (page === undefined || page === null) {
104
+ throw new Error("File fetch, page is undefined or null")
105
+ }
106
+ if (limit === undefined || limit === null) {
107
+ throw new Error("File fetch, limit is undefined or null")
108
+ }
109
+ if (limit === 0) {
110
+ throw new Error("File fetch, limit is 0")
111
+ }
112
+
113
+ const orgService = this.context.resolve(OrganizationService)
114
+
115
+ if (orgService === undefined) {
116
+ throw new Error("File fetch, organization service undefined")
117
+ }
118
+
119
+ const response = await this.context
120
+ .resolve(RpcService)
121
+ ?.requestBuilder("api/v1/Files/list")
122
+
123
+ .searchParam("workspaceId", this.workspace.id)
124
+ .searchParam("organizationId", orgService.organizations.current)
125
+ .searchParam("query", query)
126
+ .searchParam("page", page.toString())
127
+ .searchParam("limit", limit.toString())
128
+ .sendGet()
129
+
130
+ if (ResponseUtils.isFail(response)) {
131
+ await ResponseUtils.throwError(
132
+ `Files fetch query:${query}, page:${page}, limit:${limit}, failed`,
133
+ response
134
+ )
135
+ }
136
+
137
+ const files = (await response!.json()) as FileListResponse
138
+
139
+ const filesList = new FilesPageImpl()
140
+ filesList.total = files.totalFilesCount
141
+ filesList.filesPerPage = files.filesPerPage
142
+ filesList.page = page
143
+ for (const fl of files.files) {
144
+ const file = new FileImpl(this.context).initFrom(fl)
145
+
146
+ filesList.files.push(file)
147
+
148
+ this.dispatch({
149
+ type: FilesEvent.ADDED,
150
+ data: file
151
+ })
152
+ }
153
+
154
+ this.filesList = filesList
155
+
156
+ return filesList
157
+ }
158
+
159
+ async internalUpload(file: any): Promise<File> {
160
+ const orgService = this.context.resolve(OrganizationService)
161
+
162
+ if (orgService === undefined) {
163
+ throw new Error("File load, organization service undefined")
164
+ }
165
+
166
+ const form = new FormData()
167
+ form.append("organizationId", orgService.organizations.current)
168
+ form.append("workspaceId", this.workspace.id)
169
+ form.append("name", file.name)
170
+ form.append("file", file, file.name)
171
+
172
+ const response = await this.context
173
+ .resolve(RpcService)
174
+ ?.requestBuilder("api/v1/Files")
175
+ .sendPost(form)
176
+ if (ResponseUtils.isFail(response)) {
177
+ await ResponseUtils.throwError(`File upload ${file}`, response)
178
+ }
179
+ const result = (await response!.json()).file as FileDto
180
+
181
+ const fileImpl = new FileImpl(this.context).initFrom(result)
182
+
183
+ this.filesList!.files.push(file)
184
+
185
+ this.dispatch({
186
+ type: FilesEvent.ADDED,
187
+ data: file
188
+ })
189
+
190
+ return fileImpl
191
+ }
192
+ }