@difizen/libro-kernel 0.0.0-snapshot-20241017072317

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 (188) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1 -0
  3. package/es/basemanager.d.ts +94 -0
  4. package/es/basemanager.d.ts.map +1 -0
  5. package/es/basemanager.js +110 -0
  6. package/es/contents/contents-drive.d.ts +189 -0
  7. package/es/contents/contents-drive.d.ts.map +1 -0
  8. package/es/contents/contents-drive.js +802 -0
  9. package/es/contents/contents-manager.d.ts +229 -0
  10. package/es/contents/contents-manager.d.ts.map +1 -0
  11. package/es/contents/contents-manager.js +551 -0
  12. package/es/contents/contents-module.d.ts +3 -0
  13. package/es/contents/contents-module.d.ts.map +1 -0
  14. package/es/contents/contents-module.js +4 -0
  15. package/es/contents/contents-protocol.d.ts +487 -0
  16. package/es/contents/contents-protocol.d.ts.map +1 -0
  17. package/es/contents/contents-protocol.js +1 -0
  18. package/es/contents/index.d.ts +6 -0
  19. package/es/contents/index.d.ts.map +1 -0
  20. package/es/contents/index.js +5 -0
  21. package/es/contents/validate.d.ts +10 -0
  22. package/es/contents/validate.d.ts.map +1 -0
  23. package/es/contents/validate.js +22 -0
  24. package/es/index.d.ts +10 -0
  25. package/es/index.d.ts.map +1 -0
  26. package/es/index.js +9 -0
  27. package/es/kernel/comm.d.ts +92 -0
  28. package/es/kernel/comm.d.ts.map +1 -0
  29. package/es/kernel/comm.js +216 -0
  30. package/es/kernel/future.d.ts +178 -0
  31. package/es/kernel/future.d.ts.map +1 -0
  32. package/es/kernel/future.js +593 -0
  33. package/es/kernel/index.d.ts +8 -0
  34. package/es/kernel/index.d.ts.map +1 -0
  35. package/es/kernel/index.js +8 -0
  36. package/es/kernel/kernel-connection.d.ts +553 -0
  37. package/es/kernel/kernel-connection.d.ts.map +1 -0
  38. package/es/kernel/kernel-connection.js +1974 -0
  39. package/es/kernel/kernel-module.d.ts +3 -0
  40. package/es/kernel/kernel-module.d.ts.map +1 -0
  41. package/es/kernel/kernel-module.js +32 -0
  42. package/es/kernel/libro-kernel-manager.d.ts +89 -0
  43. package/es/kernel/libro-kernel-manager.d.ts.map +1 -0
  44. package/es/kernel/libro-kernel-manager.js +469 -0
  45. package/es/kernel/libro-kernel-protocol.d.ts +678 -0
  46. package/es/kernel/libro-kernel-protocol.d.ts.map +1 -0
  47. package/es/kernel/libro-kernel-protocol.js +60 -0
  48. package/es/kernel/libro-kernel-utils.d.ts +95 -0
  49. package/es/kernel/libro-kernel-utils.d.ts.map +1 -0
  50. package/es/kernel/libro-kernel-utils.js +130 -0
  51. package/es/kernel/libro-kernel.d.ts +14 -0
  52. package/es/kernel/libro-kernel.d.ts.map +1 -0
  53. package/es/kernel/libro-kernel.js +54 -0
  54. package/es/kernel/messages.d.ts +845 -0
  55. package/es/kernel/messages.d.ts.map +1 -0
  56. package/es/kernel/messages.js +513 -0
  57. package/es/kernel/restapi.d.ts +78 -0
  58. package/es/kernel/restapi.d.ts.map +1 -0
  59. package/es/kernel/restapi.js +372 -0
  60. package/es/kernel/serialize.d.ts +10 -0
  61. package/es/kernel/serialize.d.ts.map +1 -0
  62. package/es/kernel/serialize.js +213 -0
  63. package/es/kernel/validate.d.ts +15 -0
  64. package/es/kernel/validate.d.ts.map +1 -0
  65. package/es/kernel/validate.js +125 -0
  66. package/es/kernelspec/index.d.ts +5 -0
  67. package/es/kernelspec/index.d.ts.map +1 -0
  68. package/es/kernelspec/index.js +4 -0
  69. package/es/kernelspec/kernelspec-module.d.ts +3 -0
  70. package/es/kernelspec/kernelspec-module.d.ts.map +1 -0
  71. package/es/kernelspec/kernelspec-module.js +4 -0
  72. package/es/kernelspec/kernelspec.d.ts +33 -0
  73. package/es/kernelspec/kernelspec.d.ts.map +1 -0
  74. package/es/kernelspec/kernelspec.js +1 -0
  75. package/es/kernelspec/manager.d.ts +81 -0
  76. package/es/kernelspec/manager.d.ts.map +1 -0
  77. package/es/kernelspec/manager.js +245 -0
  78. package/es/kernelspec/restapi.d.ts +71 -0
  79. package/es/kernelspec/restapi.d.ts.map +1 -0
  80. package/es/kernelspec/restapi.js +107 -0
  81. package/es/kernelspec/validate.d.ts +10 -0
  82. package/es/kernelspec/validate.d.ts.map +1 -0
  83. package/es/kernelspec/validate.js +70 -0
  84. package/es/libro-kernel-connection-manager.d.ts +20 -0
  85. package/es/libro-kernel-connection-manager.d.ts.map +1 -0
  86. package/es/libro-kernel-connection-manager.js +146 -0
  87. package/es/module.d.ts +3 -0
  88. package/es/module.d.ts.map +1 -0
  89. package/es/module.js +9 -0
  90. package/es/page-config.d.ts +36 -0
  91. package/es/page-config.d.ts.map +1 -0
  92. package/es/page-config.js +129 -0
  93. package/es/protocol.d.ts +13 -0
  94. package/es/protocol.d.ts.map +1 -0
  95. package/es/protocol.js +8 -0
  96. package/es/server/connection-error.d.ts +36 -0
  97. package/es/server/connection-error.d.ts.map +1 -0
  98. package/es/server/connection-error.js +109 -0
  99. package/es/server/index.d.ts +6 -0
  100. package/es/server/index.d.ts.map +1 -0
  101. package/es/server/index.js +5 -0
  102. package/es/server/server-connection-protocol.d.ts +49 -0
  103. package/es/server/server-connection-protocol.d.ts.map +1 -0
  104. package/es/server/server-connection-protocol.js +0 -0
  105. package/es/server/server-connection.d.ts +25 -0
  106. package/es/server/server-connection.d.ts.map +1 -0
  107. package/es/server/server-connection.js +159 -0
  108. package/es/server/server-manager.d.ts +23 -0
  109. package/es/server/server-manager.d.ts.map +1 -0
  110. package/es/server/server-manager.js +178 -0
  111. package/es/server/server-module.d.ts +3 -0
  112. package/es/server/server-module.d.ts.map +1 -0
  113. package/es/server/server-module.js +4 -0
  114. package/es/session/index.d.ts +5 -0
  115. package/es/session/index.d.ts.map +1 -0
  116. package/es/session/index.js +4 -0
  117. package/es/session/libro-session-manager.d.ts +73 -0
  118. package/es/session/libro-session-manager.d.ts.map +1 -0
  119. package/es/session/libro-session-manager.js +568 -0
  120. package/es/session/libro-session-protocol.d.ts +50 -0
  121. package/es/session/libro-session-protocol.d.ts.map +1 -0
  122. package/es/session/libro-session-protocol.js +21 -0
  123. package/es/session/libro-session.d.ts +12 -0
  124. package/es/session/libro-session.d.ts.map +1 -0
  125. package/es/session/libro-session.js +19 -0
  126. package/es/session/restapi.d.ts +28 -0
  127. package/es/session/restapi.d.ts.map +1 -0
  128. package/es/session/restapi.js +215 -0
  129. package/es/session/session-module.d.ts +3 -0
  130. package/es/session/session-module.d.ts.map +1 -0
  131. package/es/session/session-module.js +18 -0
  132. package/es/session/validate.d.ts +14 -0
  133. package/es/session/validate.d.ts.map +1 -0
  134. package/es/session/validate.js +38 -0
  135. package/es/utils.d.ts +4 -0
  136. package/es/utils.d.ts.map +1 -0
  137. package/es/utils.js +29 -0
  138. package/es/validate-property.d.ts +2 -0
  139. package/es/validate-property.d.ts.map +1 -0
  140. package/es/validate-property.js +35 -0
  141. package/package.json +62 -0
  142. package/src/basemanager.ts +133 -0
  143. package/src/contents/contents-drive.ts +496 -0
  144. package/src/contents/contents-manager.ts +465 -0
  145. package/src/contents/contents-module.ts +6 -0
  146. package/src/contents/contents-protocol.ts +604 -0
  147. package/src/contents/index.ts +5 -0
  148. package/src/contents/validate.ts +29 -0
  149. package/src/index.spec.ts +16 -0
  150. package/src/index.tsx +9 -0
  151. package/src/kernel/comm.ts +220 -0
  152. package/src/kernel/future.ts +477 -0
  153. package/src/kernel/index.ts +7 -0
  154. package/src/kernel/kernel-connection.ts +1780 -0
  155. package/src/kernel/kernel-module.ts +50 -0
  156. package/src/kernel/libro-kernel-manager.ts +274 -0
  157. package/src/kernel/libro-kernel-protocol.ts +861 -0
  158. package/src/kernel/libro-kernel-utils.ts +152 -0
  159. package/src/kernel/libro-kernel.ts +39 -0
  160. package/src/kernel/messages.ts +1104 -0
  161. package/src/kernel/restapi.ts +183 -0
  162. package/src/kernel/serialize.ts +262 -0
  163. package/src/kernel/validate.ts +101 -0
  164. package/src/kernelspec/index.ts +5 -0
  165. package/src/kernelspec/kernelspec-module.ts +9 -0
  166. package/src/kernelspec/kernelspec.ts +37 -0
  167. package/src/kernelspec/manager.ts +162 -0
  168. package/src/kernelspec/restapi.ts +104 -0
  169. package/src/kernelspec/validate.ts +80 -0
  170. package/src/libro-kernel-connection-manager.ts +76 -0
  171. package/src/module.ts +19 -0
  172. package/src/page-config.ts +106 -0
  173. package/src/protocol.ts +24 -0
  174. package/src/server/connection-error.ts +60 -0
  175. package/src/server/index.ts +5 -0
  176. package/src/server/server-connection-protocol.ts +57 -0
  177. package/src/server/server-connection.ts +144 -0
  178. package/src/server/server-manager.ts +90 -0
  179. package/src/server/server-module.ts +9 -0
  180. package/src/session/index.ts +4 -0
  181. package/src/session/libro-session-manager.ts +406 -0
  182. package/src/session/libro-session-protocol.ts +61 -0
  183. package/src/session/libro-session.ts +33 -0
  184. package/src/session/restapi.ts +126 -0
  185. package/src/session/session-module.ts +26 -0
  186. package/src/session/validate.ts +39 -0
  187. package/src/utils.ts +28 -0
  188. package/src/validate-property.ts +38 -0
@@ -0,0 +1,133 @@
1
+ import type { Disposable, Event as ManaEvent } from '@difizen/mana-app';
2
+ import { Emitter } from '@difizen/mana-app';
3
+ import { singleton } from '@difizen/mana-app';
4
+
5
+ import type { ISettings, NetworkError } from './server/index.js';
6
+ /**
7
+ * A disposable object with an observable `disposed` signal.
8
+ */
9
+ export interface ObservableDisposable extends Disposable {
10
+ /**
11
+ * A signal emitted when the object is disposed.
12
+ */
13
+ readonly onDisposed: ManaEvent<void>;
14
+ }
15
+
16
+ /**
17
+ * Object which manages kernel instances for a given base url.
18
+ *
19
+ * #### Notes
20
+ * The manager is responsible for maintaining the state of kernel specs.
21
+ */
22
+ export interface IManager extends ObservableDisposable {
23
+ /**
24
+ * A signal emitted when there is a connection failure.
25
+ */
26
+ connectionFailure: ManaEvent<NetworkError>;
27
+
28
+ /**
29
+ * The server settings for the manager.
30
+ */
31
+ readonly serverSettings: ISettings;
32
+
33
+ /**
34
+ * Whether the manager is ready.
35
+ */
36
+ readonly isReady: boolean;
37
+
38
+ /**
39
+ * A promise that resolves when the manager is initially ready.
40
+ */
41
+ readonly ready: Promise<void>;
42
+
43
+ /**
44
+ * Whether the manager is active.
45
+ */
46
+ readonly isActive: boolean;
47
+ }
48
+
49
+ @singleton()
50
+ export class BaseManager implements IManager {
51
+ // constructor(options: BaseManager.IOptions) {
52
+ // this.serverSettings = options.serverSettings ?? ServerConnection.makeSettings();
53
+ // }
54
+ /**
55
+ * A signal emitted when the delegate is disposed.
56
+ */
57
+ get onDisposed(): ManaEvent<void> {
58
+ return this.onDisposedEmitter.event;
59
+ }
60
+
61
+ /**
62
+ * A signal emitted when there is a connection failure.
63
+ */
64
+ get connectionFailure(): ManaEvent<Error> {
65
+ return this.connectionFailureEmitter.event;
66
+ }
67
+
68
+ /**
69
+ * Test whether the delegate has been disposed.
70
+ */
71
+ get isDisposed(): boolean {
72
+ return this._isDisposed;
73
+ }
74
+
75
+ /**
76
+ * Test whether the manager is ready.
77
+ */
78
+ get isReady(): boolean {
79
+ return this._isReady;
80
+ }
81
+
82
+ /**
83
+ * A promise that fulfills when the manager is ready.
84
+ */
85
+ get ready(): Promise<void> {
86
+ return this._ready;
87
+ }
88
+
89
+ /**
90
+ * Whether the manager is active.
91
+ */
92
+ get isActive(): boolean {
93
+ return true;
94
+ }
95
+
96
+ /**
97
+ * Dispose of the delegate and invoke the callback function.
98
+ */
99
+ dispose(): void {
100
+ if (this.isDisposed) {
101
+ return;
102
+ }
103
+ this.onDisposedEmitter.fire(undefined);
104
+ this.onDisposedEmitter.dispose();
105
+ }
106
+
107
+ /**
108
+ * The server settings of the manager.
109
+ */
110
+ readonly serverSettings: ISettings;
111
+
112
+ protected _isDisposed = false;
113
+ protected onDisposedEmitter = new Emitter<void>();
114
+
115
+ protected connectionFailureEmitter = new Emitter<Error>();
116
+ _isReady = false;
117
+ _ready: Promise<void>;
118
+ }
119
+
120
+ /**
121
+ * The namespace for `BaseManager` class statics.
122
+ */
123
+ export namespace BaseManager {
124
+ /**
125
+ * The options used to initialize a SessionManager.
126
+ */
127
+ export interface IOptions {
128
+ /**
129
+ * The server settings for the manager.
130
+ */
131
+ serverSettings?: ISettings;
132
+ }
133
+ }
@@ -0,0 +1,496 @@
1
+ import type { PartialJSONObject } from '@difizen/libro-common';
2
+ import { URL as URLUtil } from '@difizen/libro-common';
3
+ import type { Event as ManaEvent } from '@difizen/mana-app';
4
+ import { inject, singleton } from '@difizen/mana-app';
5
+ import { Emitter } from '@difizen/mana-app';
6
+ import qs from 'query-string';
7
+
8
+ import type { ISettings } from '../server/index.js';
9
+ import { createResponseError, ServerConnection } from '../server/index.js';
10
+
11
+ import type {
12
+ IContentsChangedArgs,
13
+ IContentsCheckpointModel,
14
+ IContentsCreateOptions,
15
+ IContentsDrive,
16
+ IContentsFetchOptions,
17
+ IContentsModel,
18
+ IContentsRequestOptions,
19
+ } from './contents-protocol.js';
20
+ import * as validate from './validate.js';
21
+
22
+ /**
23
+ * The url for the default drive service.
24
+ */
25
+ const SERVICE_DRIVE_URL = 'api/contents';
26
+
27
+ /**
28
+ * The url for the file access.
29
+ */
30
+ const FILES_URL = 'files';
31
+
32
+ function normalizeExtension(extension: string): string {
33
+ if (extension.length > 0 && extension.indexOf('.') !== 0) {
34
+ // eslint-disable-next-line no-param-reassign
35
+ extension = `.${extension}`;
36
+ }
37
+ return extension;
38
+ }
39
+ /**
40
+ * A default implementation for an `IContentsDrive`, talking to the
41
+ * server using the Jupyter REST API.
42
+ */
43
+ @singleton()
44
+ export class Drive implements IContentsDrive {
45
+ @inject(ServerConnection) serverConnection: ServerConnection;
46
+
47
+ /**
48
+ * Construct a new contents manager object.
49
+ *
50
+ * @param options - The options used to initialize the object.
51
+ */
52
+ constructor() {
53
+ this.apiEndpoint = SERVICE_DRIVE_URL;
54
+ }
55
+
56
+ /**
57
+ * The name of the drive, which is used at the leading
58
+ * component of file paths.
59
+ */
60
+ readonly name: string;
61
+
62
+ /**
63
+ * A signal emitted when a file operation takes place.
64
+ */
65
+ get fileChanged(): ManaEvent<IContentsChangedArgs> {
66
+ return this.fileChangedEmitter.event;
67
+ }
68
+
69
+ /**
70
+ * The server settings of the drive.
71
+ */
72
+ // readonly serverSettings: ISettings;
73
+
74
+ /**
75
+ * Test whether the manager has been disposed.
76
+ */
77
+ get isDisposed(): boolean {
78
+ return this._isDisposed;
79
+ }
80
+
81
+ /**
82
+ * Dispose of the resources held by the manager.
83
+ */
84
+ dispose(): void {
85
+ if (this.isDisposed) {
86
+ return;
87
+ }
88
+ this._isDisposed = true;
89
+ this.fileChangedEmitter.dispose();
90
+ }
91
+
92
+ /**
93
+ * Get a file or directory.
94
+ *
95
+ * @param localPath: The path to the file.
96
+ *
97
+ * @param options: The options used to fetch the file.
98
+ *
99
+ * @returns A promise which resolves with the file content.
100
+ *
101
+ * Uses the [Jupyter Notebook API](http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyter/notebook/master/notebook/services/api/api.yaml#!/contents) and validates the response model.
102
+ */
103
+ async get(
104
+ localPath: string,
105
+ options?: IContentsFetchOptions,
106
+ ): Promise<IContentsModel> {
107
+ let url = this.getUrl(options?.baseUrl, localPath);
108
+ const settings = this._getSettings(options?.baseUrl);
109
+
110
+ if (options) {
111
+ // The notebook type cannot take an format option.
112
+ if (options.type === 'notebook') {
113
+ delete options.format;
114
+ }
115
+ if (options.baseUrl) {
116
+ delete options.baseUrl;
117
+ }
118
+ const content = options.content ? '1' : '0';
119
+ const params: PartialJSONObject = { ...options, content };
120
+ url += `?${qs.stringify(params)}`;
121
+ }
122
+
123
+ const response = await this.serverConnection.makeRequest(url, {}, settings);
124
+ if (response.status !== 200) {
125
+ const err = await createResponseError(response);
126
+ throw err;
127
+ }
128
+ const data = await response.json();
129
+ validate.validateContentsModel(data);
130
+ return data;
131
+ }
132
+
133
+ /**
134
+ * Get an encoded download url given a file path.
135
+ *
136
+ * @param localPath - An absolute POSIX file path on the server.
137
+ *
138
+ * #### Notes
139
+ * It is expected that the path contains no relative paths.
140
+ *
141
+ * The returned URL may include a query parameter.
142
+ */
143
+ getDownloadUrl(
144
+ localPath: string,
145
+ options?: IContentsRequestOptions,
146
+ ): Promise<string> {
147
+ const baseUrl = options?.baseUrl || this.serverConnection.settings.baseUrl;
148
+ let url = URLUtil.join(baseUrl, FILES_URL, URLUtil.encodeParts(localPath));
149
+ const xsrfTokenMatch = document.cookie.match('\\b_xsrf=([^;]*)\\b');
150
+ if (xsrfTokenMatch) {
151
+ const fullUrl = new URL(url);
152
+ fullUrl.searchParams.append('_xsrf', xsrfTokenMatch[1]);
153
+ url = fullUrl.toString();
154
+ }
155
+ return Promise.resolve(url);
156
+ }
157
+
158
+ /**
159
+ * Create a new untitled file or directory in the specified directory path.
160
+ *
161
+ * @param options: The options used to create the file.
162
+ *
163
+ * @returns A promise which resolves with the created file content when the
164
+ * file is created.
165
+ *
166
+ * #### Notes
167
+ * Uses the [Jupyter Notebook API](http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyter/notebook/master/notebook/services/api/api.yaml#!/contents) and validates the response model.
168
+ */
169
+ async newUntitled(options: IContentsCreateOptions = {}): Promise<IContentsModel> {
170
+ let body = '{}';
171
+ const url = this.getUrl(options.baseUrl, options.path ?? '');
172
+ const settings = this._getSettings(options.baseUrl);
173
+
174
+ if (options) {
175
+ if (options.ext) {
176
+ options.ext = normalizeExtension(options.ext);
177
+ }
178
+ if (options.baseUrl) {
179
+ delete options.baseUrl;
180
+ }
181
+ body = JSON.stringify(options);
182
+ }
183
+
184
+ const init = {
185
+ method: 'POST',
186
+ body,
187
+ };
188
+ const response = await this.serverConnection.makeRequest(url, init, settings);
189
+ if (response.status !== 201) {
190
+ const err = await createResponseError(response);
191
+ throw err;
192
+ }
193
+ const data = await response.json();
194
+ validate.validateContentsModel(data);
195
+ this.fileChangedEmitter.fire({
196
+ type: 'new',
197
+ oldValue: null,
198
+ newValue: data,
199
+ });
200
+ return data;
201
+ }
202
+
203
+ /**
204
+ * Delete a file.
205
+ *
206
+ * @param localPath - The path to the file.
207
+ *
208
+ * @returns A promise which resolves when the file is deleted.
209
+ *
210
+ * #### Notes
211
+ * Uses the [Jupyter Notebook API](http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyter/notebook/master/notebook/services/api/api.yaml#!/contents).
212
+ */
213
+ async delete(localPath: string, options?: IContentsRequestOptions): Promise<void> {
214
+ const url = this.getUrl(options?.baseUrl, localPath);
215
+ const init = { method: 'DELETE' };
216
+ const settings = this._getSettings(options?.baseUrl);
217
+ const response = await this.serverConnection.makeRequest(url, init, settings);
218
+ // TODO: update IPEP27 to specify errors more precisely, so
219
+ // that error types can be detected here with certainty.
220
+ if (response.status !== 204) {
221
+ const err = await createResponseError(response);
222
+ throw err;
223
+ }
224
+ this.fileChangedEmitter.fire({
225
+ type: 'delete',
226
+ oldValue: { path: localPath },
227
+ newValue: null,
228
+ });
229
+ }
230
+
231
+ /**
232
+ * Rename a file or directory.
233
+ *
234
+ * @param oldLocalPath - The original file path.
235
+ *
236
+ * @param newLocalPath - The new file path.
237
+ *
238
+ * @returns A promise which resolves with the new file contents model when
239
+ * the file is renamed.
240
+ *
241
+ * #### Notes
242
+ * Uses the [Jupyter Notebook API](http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyter/notebook/master/notebook/services/api/api.yaml#!/contents) and validates the response model.
243
+ */
244
+ async rename(
245
+ oldLocalPath: string,
246
+ newLocalPath: string,
247
+ options?: IContentsRequestOptions,
248
+ ): Promise<IContentsModel> {
249
+ const url = this.getUrl(options?.baseUrl, oldLocalPath);
250
+ const init = {
251
+ method: 'PATCH',
252
+ body: JSON.stringify({ path: newLocalPath }),
253
+ };
254
+ const settings = this._getSettings(options?.baseUrl);
255
+ const response = await this.serverConnection.makeRequest(url, init, settings);
256
+ if (response.status !== 200) {
257
+ const err = await createResponseError(response);
258
+ throw err;
259
+ }
260
+ const data = await response.json();
261
+ validate.validateContentsModel(data);
262
+ this.fileChangedEmitter.fire({
263
+ type: 'rename',
264
+ oldValue: { path: oldLocalPath },
265
+ newValue: data,
266
+ });
267
+ return data;
268
+ }
269
+
270
+ /**
271
+ * Save a file.
272
+ *
273
+ * @param localPath - The desired file path.
274
+ *
275
+ * @param options - Optional overrides to the model.
276
+ *
277
+ * @returns A promise which resolves with the file content model when the
278
+ * file is saved.
279
+ *
280
+ * #### Notes
281
+ * Ensure that `model.content` is populated for the file.
282
+ *
283
+ * Uses the [Jupyter Notebook API](http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyter/notebook/master/notebook/services/api/api.yaml#!/contents) and validates the response model.
284
+ */
285
+ async save(
286
+ localPath: string,
287
+ options: Partial<IContentsModel> = {},
288
+ ): Promise<IContentsModel> {
289
+ const url = this.getUrl(options.baseUrl, localPath);
290
+ const settings = this._getSettings(options.baseUrl);
291
+
292
+ if (options) {
293
+ if (options.baseUrl) {
294
+ delete options.baseUrl;
295
+ }
296
+ }
297
+
298
+ const init = {
299
+ method: 'PUT',
300
+ body: JSON.stringify(options),
301
+ };
302
+ const response = await this.serverConnection.makeRequest(url, init, settings);
303
+ // will return 200 for an existing file and 201 for a new file
304
+ if (response.status !== 200 && response.status !== 201) {
305
+ const err = await createResponseError(response);
306
+ throw err;
307
+ }
308
+ const data = await response.json();
309
+ validate.validateContentsModel(data);
310
+ this.fileChangedEmitter.fire({
311
+ type: 'save',
312
+ oldValue: null,
313
+ newValue: data,
314
+ });
315
+ return data;
316
+ }
317
+
318
+ /**
319
+ * Copy a file into a given directory.
320
+ *
321
+ * @param localPath - The original file path.
322
+ *
323
+ * @param toDir - The destination directory path.
324
+ *
325
+ * @returns A promise which resolves with the new contents model when the
326
+ * file is copied.
327
+ *
328
+ * #### Notes
329
+ * The server will select the name of the copied file.
330
+ *
331
+ * Uses the [Jupyter Notebook API](http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyter/notebook/master/notebook/services/api/api.yaml#!/contents) and validates the response model.
332
+ */
333
+ async copy(
334
+ fromFile: string,
335
+ toDir: string,
336
+ options?: IContentsRequestOptions,
337
+ ): Promise<IContentsModel> {
338
+ const url = this.getUrl(options?.baseUrl, toDir);
339
+ const init = {
340
+ method: 'POST',
341
+ body: JSON.stringify({ copy_from: fromFile }),
342
+ };
343
+ const settings = this._getSettings(options?.baseUrl);
344
+ const response = await this.serverConnection.makeRequest(url, init, settings);
345
+ if (response.status !== 201) {
346
+ const err = await createResponseError(response);
347
+ throw err;
348
+ }
349
+ const data = await response.json();
350
+ validate.validateContentsModel(data);
351
+ this.fileChangedEmitter.fire({
352
+ type: 'new',
353
+ oldValue: null,
354
+ newValue: data,
355
+ });
356
+ return data;
357
+ }
358
+
359
+ /**
360
+ * Create a checkpoint for a file.
361
+ *
362
+ * @param localPath - The path of the file.
363
+ *
364
+ * @returns A promise which resolves with the new checkpoint model when the
365
+ * checkpoint is created.
366
+ *
367
+ * #### Notes
368
+ * Uses the [Jupyter Notebook API](http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyter/notebook/master/notebook/services/api/api.yaml#!/contents) and validates the response model.
369
+ */
370
+ async createCheckpoint(
371
+ localPath: string,
372
+ options?: IContentsRequestOptions,
373
+ ): Promise<IContentsCheckpointModel> {
374
+ const url = this.getUrl(options?.baseUrl, localPath, 'checkpoints');
375
+ const init = { method: 'POST' };
376
+ const settings = this._getSettings(options?.baseUrl);
377
+ const response = await this.serverConnection.makeRequest(url, init, settings);
378
+ if (response.status !== 201) {
379
+ const err = await createResponseError(response);
380
+ throw err;
381
+ }
382
+ const data = await response.json();
383
+ validate.validateCheckpointModel(data);
384
+ return data;
385
+ }
386
+
387
+ /**
388
+ * List available checkpoints for a file.
389
+ *
390
+ * @param localPath - The path of the file.
391
+ *
392
+ * @returns A promise which resolves with a list of checkpoint models for
393
+ * the file.
394
+ *
395
+ * #### Notes
396
+ * Uses the [Jupyter Notebook API](http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyter/notebook/master/notebook/services/api/api.yaml#!/contents) and validates the response model.
397
+ */
398
+ async listCheckpoints(
399
+ localPath: string,
400
+ options?: IContentsRequestOptions,
401
+ ): Promise<IContentsCheckpointModel[]> {
402
+ const url = this.getUrl(options?.baseUrl, localPath, 'checkpoints');
403
+ const settings = this._getSettings(options?.baseUrl);
404
+ const response = await this.serverConnection.makeRequest(url, {}, settings);
405
+ if (response.status !== 200) {
406
+ const err = await createResponseError(response);
407
+ throw err;
408
+ }
409
+ const data = await response.json();
410
+ if (!Array.isArray(data)) {
411
+ throw new Error('Invalid Checkpoint list');
412
+ }
413
+ for (let i = 0; i < data.length; i++) {
414
+ validate.validateCheckpointModel(data[i]);
415
+ }
416
+ return data;
417
+ }
418
+
419
+ /**
420
+ * Restore a file to a known checkpoint state.
421
+ *
422
+ * @param localPath - The path of the file.
423
+ *
424
+ * @param checkpointID - The id of the checkpoint to restore.
425
+ *
426
+ * @returns A promise which resolves when the checkpoint is restored.
427
+ *
428
+ * #### Notes
429
+ * Uses the [Jupyter Notebook API](http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyter/notebook/master/notebook/services/api/api.yaml#!/contents).
430
+ */
431
+ async restoreCheckpoint(
432
+ localPath: string,
433
+ checkpointID: string,
434
+ options?: IContentsRequestOptions,
435
+ ): Promise<void> {
436
+ const url = this.getUrl(options?.baseUrl, localPath, 'checkpoints', checkpointID);
437
+ const settings = this._getSettings(options?.baseUrl);
438
+ const init = { method: 'POST' };
439
+ const response = await this.serverConnection.makeRequest(url, init, settings);
440
+ if (response.status !== 204) {
441
+ const err = await createResponseError(response);
442
+ throw err;
443
+ }
444
+ }
445
+
446
+ /**
447
+ * Delete a checkpoint for a file.
448
+ *
449
+ * @param localPath - The path of the file.
450
+ *
451
+ * @param checkpointID - The id of the checkpoint to delete.
452
+ *
453
+ * @returns A promise which resolves when the checkpoint is deleted.
454
+ *
455
+ * #### Notes
456
+ * Uses the [Jupyter Notebook API](http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyter/notebook/master/notebook/services/api/api.yaml#!/contents).
457
+ */
458
+ async deleteCheckpoint(
459
+ localPath: string,
460
+ checkpointID: string,
461
+ options?: IContentsRequestOptions,
462
+ ): Promise<void> {
463
+ const url = this.getUrl(options?.baseUrl, localPath, 'checkpoints', checkpointID);
464
+ const init = { method: 'DELETE' };
465
+ const settings = this._getSettings(options?.baseUrl);
466
+ const response = await this.serverConnection.makeRequest(url, init, settings);
467
+ if (response.status !== 204) {
468
+ const err = await createResponseError(response);
469
+ throw err;
470
+ }
471
+ }
472
+
473
+ /**
474
+ * Get a REST url for a file given a path.
475
+ */
476
+ protected getUrl(base?: string, ...args: string[]): string {
477
+ let baseUrl = base;
478
+ if (!baseUrl) {
479
+ baseUrl = this.serverConnection.settings.baseUrl;
480
+ }
481
+ const parts = args.map((path) => URLUtil.encodeParts(path));
482
+ return URLUtil.join(baseUrl!, this.apiEndpoint, ...parts);
483
+ }
484
+
485
+ protected _getSettings(baseUrl?: string): ISettings {
486
+ const settings = this.serverConnection.settings;
487
+ if (baseUrl) {
488
+ return { ...settings, baseUrl };
489
+ }
490
+ return settings;
491
+ }
492
+
493
+ protected apiEndpoint: string;
494
+ protected _isDisposed = false;
495
+ protected fileChangedEmitter = new Emitter<IContentsChangedArgs>();
496
+ }