@ckeditor/ckeditor5-watchdog 38.1.1 → 38.2.0-alpha.1

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.
@@ -1,410 +1,410 @@
1
- /**
2
- * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
- */
5
- import Watchdog from './watchdog';
6
- import EditorWatchdog from './editorwatchdog';
7
- import areConnectedThroughProperties from './utils/areconnectedthroughproperties';
8
- import getSubNodes from './utils/getsubnodes';
9
- const mainQueueId = Symbol('MainQueueId');
10
- /**
11
- * A watchdog for the {@link module:core/context~Context} class.
12
- *
13
- * See the {@glink features/watchdog Watchdog feature guide} to learn the rationale behind it and
14
- * how to use it.
15
- */
16
- export default class ContextWatchdog extends Watchdog {
17
- /**
18
- * The context watchdog class constructor.
19
- *
20
- * ```ts
21
- * const watchdog = new ContextWatchdog( Context );
22
- *
23
- * await watchdog.create( contextConfiguration );
24
- *
25
- * await watchdog.add( item );
26
- * ```
27
- *
28
- * See the {@glink features/watchdog Watchdog feature guide} to learn more how to use this feature.
29
- *
30
- * @param Context The {@link module:core/context~Context} class.
31
- * @param watchdogConfig The watchdog configuration.
32
- */
33
- constructor(Context, watchdogConfig = {}) {
34
- super(watchdogConfig);
35
- /**
36
- * A map of internal watchdogs for added items.
37
- */
38
- this._watchdogs = new Map();
39
- /**
40
- * The current context instance.
41
- */
42
- this._context = null;
43
- /**
44
- * Context properties (nodes/references) that are gathered during the initial context creation
45
- * and are used to distinguish the origin of an error.
46
- */
47
- this._contextProps = new Set();
48
- /**
49
- * An action queue, which is used to handle async functions queuing.
50
- */
51
- this._actionQueues = new ActionQueues();
52
- this._watchdogConfig = watchdogConfig;
53
- // Default creator and destructor.
54
- this._creator = contextConfig => Context.create(contextConfig);
55
- this._destructor = context => context.destroy();
56
- this._actionQueues.onEmpty(() => {
57
- if (this.state === 'initializing') {
58
- this.state = 'ready';
59
- this._fire('stateChange');
60
- }
61
- });
62
- }
63
- /**
64
- * Sets the function that is responsible for the context creation.
65
- * It expects a function that should return a promise (or `undefined`).
66
- *
67
- * ```ts
68
- * watchdog.setCreator( config => Context.create( config ) );
69
- * ```
70
- */
71
- setCreator(creator) {
72
- this._creator = creator;
73
- }
74
- /**
75
- * Sets the function that is responsible for the context destruction.
76
- * Overrides the default destruction function, which destroys only the context instance.
77
- * It expects a function that should return a promise (or `undefined`).
78
- *
79
- * ```ts
80
- * watchdog.setDestructor( context => {
81
- * // Do something before the context is destroyed.
82
- *
83
- * return context
84
- * .destroy()
85
- * .then( () => {
86
- * // Do something after the context is destroyed.
87
- * } );
88
- * } );
89
- * ```
90
- */
91
- setDestructor(destructor) {
92
- this._destructor = destructor;
93
- }
94
- /**
95
- * The context instance. Keep in mind that this property might be changed when the context watchdog restarts,
96
- * so do not keep this instance internally. Always operate on the `ContextWatchdog#context` property.
97
- */
98
- get context() {
99
- return this._context;
100
- }
101
- /**
102
- * Initializes the context watchdog. Once it is created, the watchdog takes care about
103
- * recreating the context and the provided items, and starts the error handling mechanism.
104
- *
105
- * ```ts
106
- * await watchdog.create( {
107
- * plugins: []
108
- * } );
109
- * ```
110
- *
111
- * @param contextConfig The context configuration. See {@link module:core/context~Context}.
112
- */
113
- create(contextConfig = {}) {
114
- return this._actionQueues.enqueue(mainQueueId, () => {
115
- this._contextConfig = contextConfig;
116
- return this._create();
117
- });
118
- }
119
- /**
120
- * Returns an item instance with the given `itemId`.
121
- *
122
- * ```ts
123
- * const editor1 = watchdog.getItem( 'editor1' );
124
- * ```
125
- *
126
- * @param itemId The item ID.
127
- * @returns The item instance or `undefined` if an item with a given ID has not been found.
128
- */
129
- getItem(itemId) {
130
- const watchdog = this._getWatchdog(itemId);
131
- return watchdog._item;
132
- }
133
- /**
134
- * Gets the state of the given item. See {@link #state} for a list of available states.
135
- *
136
- * ```ts
137
- * const editor1State = watchdog.getItemState( 'editor1' );
138
- * ```
139
- *
140
- * @param itemId Item ID.
141
- * @returns The state of the item.
142
- */
143
- getItemState(itemId) {
144
- const watchdog = this._getWatchdog(itemId);
145
- return watchdog.state;
146
- }
147
- /**
148
- * Adds items to the watchdog. Once created, instances of these items will be available using the {@link #getItem} method.
149
- *
150
- * Items can be passed together as an array of objects:
151
- *
152
- * ```ts
153
- * await watchdog.add( [ {
154
- * id: 'editor1',
155
- * type: 'editor',
156
- * sourceElementOrData: document.querySelector( '#editor' ),
157
- * config: {
158
- * plugins: [ Essentials, Paragraph, Bold, Italic ],
159
- * toolbar: [ 'bold', 'italic', 'alignment' ]
160
- * },
161
- * creator: ( element, config ) => ClassicEditor.create( element, config )
162
- * } ] );
163
- * ```
164
- *
165
- * Or one by one as objects:
166
- *
167
- * ```ts
168
- * await watchdog.add( {
169
- * id: 'editor1',
170
- * type: 'editor',
171
- * sourceElementOrData: document.querySelector( '#editor' ),
172
- * config: {
173
- * plugins: [ Essentials, Paragraph, Bold, Italic ],
174
- * toolbar: [ 'bold', 'italic', 'alignment' ]
175
- * },
176
- * creator: ( element, config ) => ClassicEditor.create( element, config )
177
- * ] );
178
- * ```
179
- *
180
- * Then an instance can be retrieved using the {@link #getItem} method:
181
- *
182
- * ```ts
183
- * const editor1 = watchdog.getItem( 'editor1' );
184
- * ```
185
- *
186
- * Note that this method can be called multiple times, but for performance reasons it is better
187
- * to pass all items together.
188
- *
189
- * @param itemConfigurationOrItemConfigurations An item configuration object or an array of item configurations.
190
- */
191
- add(itemConfigurationOrItemConfigurations) {
192
- const itemConfigurations = toArray(itemConfigurationOrItemConfigurations);
193
- return Promise.all(itemConfigurations.map(item => {
194
- return this._actionQueues.enqueue(item.id, () => {
195
- if (this.state === 'destroyed') {
196
- throw new Error('Cannot add items to destroyed watchdog.');
197
- }
198
- if (!this._context) {
199
- throw new Error('Context was not created yet. You should call the `ContextWatchdog#create()` method first.');
200
- }
201
- let watchdog;
202
- if (this._watchdogs.has(item.id)) {
203
- throw new Error(`Item with the given id is already added: '${item.id}'.`);
204
- }
205
- if (item.type === 'editor') {
206
- watchdog = new EditorWatchdog(null, this._watchdogConfig);
207
- watchdog.setCreator(item.creator);
208
- watchdog._setExcludedProperties(this._contextProps);
209
- if (item.destructor) {
210
- watchdog.setDestructor(item.destructor);
211
- }
212
- this._watchdogs.set(item.id, watchdog);
213
- // Enqueue the internal watchdog errors within the main queue.
214
- // And propagate the internal `error` events as `itemError` event.
215
- watchdog.on('error', (evt, { error, causesRestart }) => {
216
- this._fire('itemError', { itemId: item.id, error });
217
- // Do not enqueue the item restart action if the item will not restart.
218
- if (!causesRestart) {
219
- return;
220
- }
221
- this._actionQueues.enqueue(item.id, () => new Promise(res => {
222
- const rethrowRestartEventOnce = () => {
223
- watchdog.off('restart', rethrowRestartEventOnce);
224
- this._fire('itemRestart', { itemId: item.id });
225
- res();
226
- };
227
- watchdog.on('restart', rethrowRestartEventOnce);
228
- }));
229
- });
230
- return watchdog.create(item.sourceElementOrData, item.config, this._context);
231
- }
232
- else {
233
- throw new Error(`Not supported item type: '${item.type}'.`);
234
- }
235
- });
236
- }));
237
- }
238
- /**
239
- * Removes and destroys item(s) with given ID(s).
240
- *
241
- * ```ts
242
- * await watchdog.remove( 'editor1' );
243
- * ```
244
- *
245
- * Or
246
- *
247
- * ```ts
248
- * await watchdog.remove( [ 'editor1', 'editor2' ] );
249
- * ```
250
- *
251
- * @param itemIdOrItemIds Item ID or an array of item IDs.
252
- */
253
- remove(itemIdOrItemIds) {
254
- const itemIds = toArray(itemIdOrItemIds);
255
- return Promise.all(itemIds.map(itemId => {
256
- return this._actionQueues.enqueue(itemId, () => {
257
- const watchdog = this._getWatchdog(itemId);
258
- this._watchdogs.delete(itemId);
259
- return watchdog.destroy();
260
- });
261
- }));
262
- }
263
- /**
264
- * Destroys the context watchdog and all added items.
265
- * Once the context watchdog is destroyed, new items cannot be added.
266
- *
267
- * ```ts
268
- * await watchdog.destroy();
269
- * ```
270
- */
271
- destroy() {
272
- return this._actionQueues.enqueue(mainQueueId, () => {
273
- this.state = 'destroyed';
274
- this._fire('stateChange');
275
- super.destroy();
276
- return this._destroy();
277
- });
278
- }
279
- /**
280
- * Restarts the context watchdog.
281
- */
282
- _restart() {
283
- return this._actionQueues.enqueue(mainQueueId, () => {
284
- this.state = 'initializing';
285
- this._fire('stateChange');
286
- return this._destroy()
287
- .catch(err => {
288
- console.error('An error happened during destroying the context or items.', err);
289
- })
290
- .then(() => this._create())
291
- .then(() => this._fire('restart'));
292
- });
293
- }
294
- /**
295
- * Initializes the context watchdog.
296
- */
297
- _create() {
298
- return Promise.resolve()
299
- .then(() => {
300
- this._startErrorHandling();
301
- return this._creator(this._contextConfig);
302
- })
303
- .then(context => {
304
- this._context = context;
305
- this._contextProps = getSubNodes(this._context);
306
- return Promise.all(Array.from(this._watchdogs.values())
307
- .map(watchdog => {
308
- watchdog._setExcludedProperties(this._contextProps);
309
- return watchdog.create(undefined, undefined, this._context);
310
- }));
311
- });
312
- }
313
- /**
314
- * Destroys the context instance and all added items.
315
- */
316
- _destroy() {
317
- return Promise.resolve()
318
- .then(() => {
319
- this._stopErrorHandling();
320
- const context = this._context;
321
- this._context = null;
322
- this._contextProps = new Set();
323
- return Promise.all(Array.from(this._watchdogs.values())
324
- .map(watchdog => watchdog.destroy()))
325
- // Context destructor destroys each editor.
326
- .then(() => this._destructor(context));
327
- });
328
- }
329
- /**
330
- * Returns the watchdog for a given item ID.
331
- *
332
- * @param itemId Item ID.
333
- */
334
- _getWatchdog(itemId) {
335
- const watchdog = this._watchdogs.get(itemId);
336
- if (!watchdog) {
337
- throw new Error(`Item with the given id was not registered: ${itemId}.`);
338
- }
339
- return watchdog;
340
- }
341
- /**
342
- * Checks whether an error comes from the context instance and not from the item instances.
343
- *
344
- * @internal
345
- */
346
- _isErrorComingFromThisItem(error) {
347
- for (const watchdog of this._watchdogs.values()) {
348
- if (watchdog._isErrorComingFromThisItem(error)) {
349
- return false;
350
- }
351
- }
352
- return areConnectedThroughProperties(this._context, error.context);
353
- }
354
- }
355
- /**
356
- * Manager of action queues that allows queuing async functions.
357
- */
358
- class ActionQueues {
359
- constructor() {
360
- this._onEmptyCallbacks = [];
361
- this._queues = new Map();
362
- this._activeActions = 0;
363
- }
364
- /**
365
- * Used to register callbacks that will be run when the queue becomes empty.
366
- *
367
- * @param onEmptyCallback A callback that will be run whenever the queue becomes empty.
368
- */
369
- onEmpty(onEmptyCallback) {
370
- this._onEmptyCallbacks.push(onEmptyCallback);
371
- }
372
- /**
373
- * It adds asynchronous actions (functions) to the proper queue and runs them one by one.
374
- *
375
- * @param queueId The action queue ID.
376
- * @param action A function that should be enqueued.
377
- */
378
- enqueue(queueId, action) {
379
- const isMainAction = queueId === mainQueueId;
380
- this._activeActions++;
381
- if (!this._queues.get(queueId)) {
382
- this._queues.set(queueId, Promise.resolve());
383
- }
384
- // List all sources of actions that the current action needs to await for.
385
- // For the main action wait for all other actions.
386
- // For the item action wait only for the item queue and the main queue.
387
- const awaitedActions = isMainAction ?
388
- Promise.all(this._queues.values()) :
389
- Promise.all([this._queues.get(mainQueueId), this._queues.get(queueId)]);
390
- const queueWithAction = awaitedActions.then(action);
391
- // Catch all errors in the main queue to stack promises even if an error occurred in the past.
392
- const nonErrorQueue = queueWithAction.catch(() => { });
393
- this._queues.set(queueId, nonErrorQueue);
394
- return queueWithAction.finally(() => {
395
- this._activeActions--;
396
- if (this._queues.get(queueId) === nonErrorQueue && this._activeActions === 0) {
397
- this._onEmptyCallbacks.forEach(cb => cb());
398
- }
399
- });
400
- }
401
- }
402
- /**
403
- * Transforms any value to an array. If the provided value is already an array, it is returned unchanged.
404
- *
405
- * @param elementOrArray The value to transform to an array.
406
- * @returns An array created from data.
407
- */
408
- function toArray(elementOrArray) {
409
- return Array.isArray(elementOrArray) ? elementOrArray : [elementOrArray];
410
- }
1
+ /**
2
+ * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+ import Watchdog from './watchdog.js';
6
+ import EditorWatchdog from './editorwatchdog.js';
7
+ import areConnectedThroughProperties from './utils/areconnectedthroughproperties.js';
8
+ import getSubNodes from './utils/getsubnodes.js';
9
+ const mainQueueId = Symbol('MainQueueId');
10
+ /**
11
+ * A watchdog for the {@link module:core/context~Context} class.
12
+ *
13
+ * See the {@glink features/watchdog Watchdog feature guide} to learn the rationale behind it and
14
+ * how to use it.
15
+ */
16
+ export default class ContextWatchdog extends Watchdog {
17
+ /**
18
+ * The context watchdog class constructor.
19
+ *
20
+ * ```ts
21
+ * const watchdog = new ContextWatchdog( Context );
22
+ *
23
+ * await watchdog.create( contextConfiguration );
24
+ *
25
+ * await watchdog.add( item );
26
+ * ```
27
+ *
28
+ * See the {@glink features/watchdog Watchdog feature guide} to learn more how to use this feature.
29
+ *
30
+ * @param Context The {@link module:core/context~Context} class.
31
+ * @param watchdogConfig The watchdog configuration.
32
+ */
33
+ constructor(Context, watchdogConfig = {}) {
34
+ super(watchdogConfig);
35
+ /**
36
+ * A map of internal watchdogs for added items.
37
+ */
38
+ this._watchdogs = new Map();
39
+ /**
40
+ * The current context instance.
41
+ */
42
+ this._context = null;
43
+ /**
44
+ * Context properties (nodes/references) that are gathered during the initial context creation
45
+ * and are used to distinguish the origin of an error.
46
+ */
47
+ this._contextProps = new Set();
48
+ /**
49
+ * An action queue, which is used to handle async functions queuing.
50
+ */
51
+ this._actionQueues = new ActionQueues();
52
+ this._watchdogConfig = watchdogConfig;
53
+ // Default creator and destructor.
54
+ this._creator = contextConfig => Context.create(contextConfig);
55
+ this._destructor = context => context.destroy();
56
+ this._actionQueues.onEmpty(() => {
57
+ if (this.state === 'initializing') {
58
+ this.state = 'ready';
59
+ this._fire('stateChange');
60
+ }
61
+ });
62
+ }
63
+ /**
64
+ * Sets the function that is responsible for the context creation.
65
+ * It expects a function that should return a promise (or `undefined`).
66
+ *
67
+ * ```ts
68
+ * watchdog.setCreator( config => Context.create( config ) );
69
+ * ```
70
+ */
71
+ setCreator(creator) {
72
+ this._creator = creator;
73
+ }
74
+ /**
75
+ * Sets the function that is responsible for the context destruction.
76
+ * Overrides the default destruction function, which destroys only the context instance.
77
+ * It expects a function that should return a promise (or `undefined`).
78
+ *
79
+ * ```ts
80
+ * watchdog.setDestructor( context => {
81
+ * // Do something before the context is destroyed.
82
+ *
83
+ * return context
84
+ * .destroy()
85
+ * .then( () => {
86
+ * // Do something after the context is destroyed.
87
+ * } );
88
+ * } );
89
+ * ```
90
+ */
91
+ setDestructor(destructor) {
92
+ this._destructor = destructor;
93
+ }
94
+ /**
95
+ * The context instance. Keep in mind that this property might be changed when the context watchdog restarts,
96
+ * so do not keep this instance internally. Always operate on the `ContextWatchdog#context` property.
97
+ */
98
+ get context() {
99
+ return this._context;
100
+ }
101
+ /**
102
+ * Initializes the context watchdog. Once it is created, the watchdog takes care about
103
+ * recreating the context and the provided items, and starts the error handling mechanism.
104
+ *
105
+ * ```ts
106
+ * await watchdog.create( {
107
+ * plugins: []
108
+ * } );
109
+ * ```
110
+ *
111
+ * @param contextConfig The context configuration. See {@link module:core/context~Context}.
112
+ */
113
+ create(contextConfig = {}) {
114
+ return this._actionQueues.enqueue(mainQueueId, () => {
115
+ this._contextConfig = contextConfig;
116
+ return this._create();
117
+ });
118
+ }
119
+ /**
120
+ * Returns an item instance with the given `itemId`.
121
+ *
122
+ * ```ts
123
+ * const editor1 = watchdog.getItem( 'editor1' );
124
+ * ```
125
+ *
126
+ * @param itemId The item ID.
127
+ * @returns The item instance or `undefined` if an item with a given ID has not been found.
128
+ */
129
+ getItem(itemId) {
130
+ const watchdog = this._getWatchdog(itemId);
131
+ return watchdog._item;
132
+ }
133
+ /**
134
+ * Gets the state of the given item. See {@link #state} for a list of available states.
135
+ *
136
+ * ```ts
137
+ * const editor1State = watchdog.getItemState( 'editor1' );
138
+ * ```
139
+ *
140
+ * @param itemId Item ID.
141
+ * @returns The state of the item.
142
+ */
143
+ getItemState(itemId) {
144
+ const watchdog = this._getWatchdog(itemId);
145
+ return watchdog.state;
146
+ }
147
+ /**
148
+ * Adds items to the watchdog. Once created, instances of these items will be available using the {@link #getItem} method.
149
+ *
150
+ * Items can be passed together as an array of objects:
151
+ *
152
+ * ```ts
153
+ * await watchdog.add( [ {
154
+ * id: 'editor1',
155
+ * type: 'editor',
156
+ * sourceElementOrData: document.querySelector( '#editor' ),
157
+ * config: {
158
+ * plugins: [ Essentials, Paragraph, Bold, Italic ],
159
+ * toolbar: [ 'bold', 'italic', 'alignment' ]
160
+ * },
161
+ * creator: ( element, config ) => ClassicEditor.create( element, config )
162
+ * } ] );
163
+ * ```
164
+ *
165
+ * Or one by one as objects:
166
+ *
167
+ * ```ts
168
+ * await watchdog.add( {
169
+ * id: 'editor1',
170
+ * type: 'editor',
171
+ * sourceElementOrData: document.querySelector( '#editor' ),
172
+ * config: {
173
+ * plugins: [ Essentials, Paragraph, Bold, Italic ],
174
+ * toolbar: [ 'bold', 'italic', 'alignment' ]
175
+ * },
176
+ * creator: ( element, config ) => ClassicEditor.create( element, config )
177
+ * ] );
178
+ * ```
179
+ *
180
+ * Then an instance can be retrieved using the {@link #getItem} method:
181
+ *
182
+ * ```ts
183
+ * const editor1 = watchdog.getItem( 'editor1' );
184
+ * ```
185
+ *
186
+ * Note that this method can be called multiple times, but for performance reasons it is better
187
+ * to pass all items together.
188
+ *
189
+ * @param itemConfigurationOrItemConfigurations An item configuration object or an array of item configurations.
190
+ */
191
+ add(itemConfigurationOrItemConfigurations) {
192
+ const itemConfigurations = toArray(itemConfigurationOrItemConfigurations);
193
+ return Promise.all(itemConfigurations.map(item => {
194
+ return this._actionQueues.enqueue(item.id, () => {
195
+ if (this.state === 'destroyed') {
196
+ throw new Error('Cannot add items to destroyed watchdog.');
197
+ }
198
+ if (!this._context) {
199
+ throw new Error('Context was not created yet. You should call the `ContextWatchdog#create()` method first.');
200
+ }
201
+ let watchdog;
202
+ if (this._watchdogs.has(item.id)) {
203
+ throw new Error(`Item with the given id is already added: '${item.id}'.`);
204
+ }
205
+ if (item.type === 'editor') {
206
+ watchdog = new EditorWatchdog(null, this._watchdogConfig);
207
+ watchdog.setCreator(item.creator);
208
+ watchdog._setExcludedProperties(this._contextProps);
209
+ if (item.destructor) {
210
+ watchdog.setDestructor(item.destructor);
211
+ }
212
+ this._watchdogs.set(item.id, watchdog);
213
+ // Enqueue the internal watchdog errors within the main queue.
214
+ // And propagate the internal `error` events as `itemError` event.
215
+ watchdog.on('error', (evt, { error, causesRestart }) => {
216
+ this._fire('itemError', { itemId: item.id, error });
217
+ // Do not enqueue the item restart action if the item will not restart.
218
+ if (!causesRestart) {
219
+ return;
220
+ }
221
+ this._actionQueues.enqueue(item.id, () => new Promise(res => {
222
+ const rethrowRestartEventOnce = () => {
223
+ watchdog.off('restart', rethrowRestartEventOnce);
224
+ this._fire('itemRestart', { itemId: item.id });
225
+ res();
226
+ };
227
+ watchdog.on('restart', rethrowRestartEventOnce);
228
+ }));
229
+ });
230
+ return watchdog.create(item.sourceElementOrData, item.config, this._context);
231
+ }
232
+ else {
233
+ throw new Error(`Not supported item type: '${item.type}'.`);
234
+ }
235
+ });
236
+ }));
237
+ }
238
+ /**
239
+ * Removes and destroys item(s) with given ID(s).
240
+ *
241
+ * ```ts
242
+ * await watchdog.remove( 'editor1' );
243
+ * ```
244
+ *
245
+ * Or
246
+ *
247
+ * ```ts
248
+ * await watchdog.remove( [ 'editor1', 'editor2' ] );
249
+ * ```
250
+ *
251
+ * @param itemIdOrItemIds Item ID or an array of item IDs.
252
+ */
253
+ remove(itemIdOrItemIds) {
254
+ const itemIds = toArray(itemIdOrItemIds);
255
+ return Promise.all(itemIds.map(itemId => {
256
+ return this._actionQueues.enqueue(itemId, () => {
257
+ const watchdog = this._getWatchdog(itemId);
258
+ this._watchdogs.delete(itemId);
259
+ return watchdog.destroy();
260
+ });
261
+ }));
262
+ }
263
+ /**
264
+ * Destroys the context watchdog and all added items.
265
+ * Once the context watchdog is destroyed, new items cannot be added.
266
+ *
267
+ * ```ts
268
+ * await watchdog.destroy();
269
+ * ```
270
+ */
271
+ destroy() {
272
+ return this._actionQueues.enqueue(mainQueueId, () => {
273
+ this.state = 'destroyed';
274
+ this._fire('stateChange');
275
+ super.destroy();
276
+ return this._destroy();
277
+ });
278
+ }
279
+ /**
280
+ * Restarts the context watchdog.
281
+ */
282
+ _restart() {
283
+ return this._actionQueues.enqueue(mainQueueId, () => {
284
+ this.state = 'initializing';
285
+ this._fire('stateChange');
286
+ return this._destroy()
287
+ .catch(err => {
288
+ console.error('An error happened during destroying the context or items.', err);
289
+ })
290
+ .then(() => this._create())
291
+ .then(() => this._fire('restart'));
292
+ });
293
+ }
294
+ /**
295
+ * Initializes the context watchdog.
296
+ */
297
+ _create() {
298
+ return Promise.resolve()
299
+ .then(() => {
300
+ this._startErrorHandling();
301
+ return this._creator(this._contextConfig);
302
+ })
303
+ .then(context => {
304
+ this._context = context;
305
+ this._contextProps = getSubNodes(this._context);
306
+ return Promise.all(Array.from(this._watchdogs.values())
307
+ .map(watchdog => {
308
+ watchdog._setExcludedProperties(this._contextProps);
309
+ return watchdog.create(undefined, undefined, this._context);
310
+ }));
311
+ });
312
+ }
313
+ /**
314
+ * Destroys the context instance and all added items.
315
+ */
316
+ _destroy() {
317
+ return Promise.resolve()
318
+ .then(() => {
319
+ this._stopErrorHandling();
320
+ const context = this._context;
321
+ this._context = null;
322
+ this._contextProps = new Set();
323
+ return Promise.all(Array.from(this._watchdogs.values())
324
+ .map(watchdog => watchdog.destroy()))
325
+ // Context destructor destroys each editor.
326
+ .then(() => this._destructor(context));
327
+ });
328
+ }
329
+ /**
330
+ * Returns the watchdog for a given item ID.
331
+ *
332
+ * @param itemId Item ID.
333
+ */
334
+ _getWatchdog(itemId) {
335
+ const watchdog = this._watchdogs.get(itemId);
336
+ if (!watchdog) {
337
+ throw new Error(`Item with the given id was not registered: ${itemId}.`);
338
+ }
339
+ return watchdog;
340
+ }
341
+ /**
342
+ * Checks whether an error comes from the context instance and not from the item instances.
343
+ *
344
+ * @internal
345
+ */
346
+ _isErrorComingFromThisItem(error) {
347
+ for (const watchdog of this._watchdogs.values()) {
348
+ if (watchdog._isErrorComingFromThisItem(error)) {
349
+ return false;
350
+ }
351
+ }
352
+ return areConnectedThroughProperties(this._context, error.context);
353
+ }
354
+ }
355
+ /**
356
+ * Manager of action queues that allows queuing async functions.
357
+ */
358
+ class ActionQueues {
359
+ constructor() {
360
+ this._onEmptyCallbacks = [];
361
+ this._queues = new Map();
362
+ this._activeActions = 0;
363
+ }
364
+ /**
365
+ * Used to register callbacks that will be run when the queue becomes empty.
366
+ *
367
+ * @param onEmptyCallback A callback that will be run whenever the queue becomes empty.
368
+ */
369
+ onEmpty(onEmptyCallback) {
370
+ this._onEmptyCallbacks.push(onEmptyCallback);
371
+ }
372
+ /**
373
+ * It adds asynchronous actions (functions) to the proper queue and runs them one by one.
374
+ *
375
+ * @param queueId The action queue ID.
376
+ * @param action A function that should be enqueued.
377
+ */
378
+ enqueue(queueId, action) {
379
+ const isMainAction = queueId === mainQueueId;
380
+ this._activeActions++;
381
+ if (!this._queues.get(queueId)) {
382
+ this._queues.set(queueId, Promise.resolve());
383
+ }
384
+ // List all sources of actions that the current action needs to await for.
385
+ // For the main action wait for all other actions.
386
+ // For the item action wait only for the item queue and the main queue.
387
+ const awaitedActions = isMainAction ?
388
+ Promise.all(this._queues.values()) :
389
+ Promise.all([this._queues.get(mainQueueId), this._queues.get(queueId)]);
390
+ const queueWithAction = awaitedActions.then(action);
391
+ // Catch all errors in the main queue to stack promises even if an error occurred in the past.
392
+ const nonErrorQueue = queueWithAction.catch(() => { });
393
+ this._queues.set(queueId, nonErrorQueue);
394
+ return queueWithAction.finally(() => {
395
+ this._activeActions--;
396
+ if (this._queues.get(queueId) === nonErrorQueue && this._activeActions === 0) {
397
+ this._onEmptyCallbacks.forEach(cb => cb());
398
+ }
399
+ });
400
+ }
401
+ }
402
+ /**
403
+ * Transforms any value to an array. If the provided value is already an array, it is returned unchanged.
404
+ *
405
+ * @param elementOrArray The value to transform to an array.
406
+ * @returns An array created from data.
407
+ */
408
+ function toArray(elementOrArray) {
409
+ return Array.isArray(elementOrArray) ? elementOrArray : [elementOrArray];
410
+ }