aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2022-08-25 01:25:40 +0200
committerLibravatar Kristóf Marussy <kristof@marussy.com>2022-08-25 16:44:01 +0200
commitf6de69943df6c669c81b10279bbfaf3158f1fd61 (patch)
treee19465fd9ae4dc6905ad1aa1f8dbbc92ae43ea62
parentrefactor(frontend): xtext update improvements (diff)
downloadrefinery-f6de69943df6c669c81b10279bbfaf3158f1fd61.tar.gz
refinery-f6de69943df6c669c81b10279bbfaf3158f1fd61.tar.zst
refinery-f6de69943df6c669c81b10279bbfaf3158f1fd61.zip
fix(frontend): UpdateService synchronization
Also bumps frontend dependencies
-rw-r--r--subprojects/frontend/package.json18
-rw-r--r--subprojects/frontend/src/utils/ConditionVariable.ts64
-rw-r--r--subprojects/frontend/src/utils/PendingTask.ts20
-rw-r--r--subprojects/frontend/src/xtext/ContentAssistService.ts8
-rw-r--r--subprojects/frontend/src/xtext/HighlightingService.ts12
-rw-r--r--subprojects/frontend/src/xtext/OccurrencesService.ts119
-rw-r--r--subprojects/frontend/src/xtext/UpdateService.ts296
-rw-r--r--subprojects/frontend/src/xtext/ValidationService.ts12
-rw-r--r--subprojects/frontend/src/xtext/XtextClient.ts6
-rw-r--r--subprojects/frontend/src/xtext/xtextServiceResults.ts5
-rw-r--r--yarn.lock232
11 files changed, 417 insertions, 375 deletions
diff --git a/subprojects/frontend/package.json b/subprojects/frontend/package.json
index 448b9710..7cef17fe 100644
--- a/subprojects/frontend/package.json
+++ b/subprojects/frontend/package.json
@@ -31,17 +31,16 @@
31 "@codemirror/view": "^6.2.0", 31 "@codemirror/view": "^6.2.0",
32 "@emotion/react": "^11.10.0", 32 "@emotion/react": "^11.10.0",
33 "@emotion/styled": "^11.10.0", 33 "@emotion/styled": "^11.10.0",
34 "@fontsource/inter": "^4.5.12",
35 "@fontsource/jetbrains-mono": "^4.5.10", 34 "@fontsource/jetbrains-mono": "^4.5.10",
36 "@fontsource/roboto": "^4.5.8", 35 "@fontsource/roboto": "^4.5.8",
37 "@lezer/common": "^1.0.0", 36 "@lezer/common": "^1.0.0",
38 "@lezer/highlight": "^1.0.0", 37 "@lezer/highlight": "^1.0.0",
39 "@lezer/lr": "^1.2.3", 38 "@lezer/lr": "^1.2.3",
40 "@material-icons/svg": "^1.0.32", 39 "@material-icons/svg": "^1.0.32",
41 "@mui/icons-material": "5.8.4", 40 "@mui/icons-material": "5.10.2",
42 "@mui/material": "5.10.1", 41 "@mui/material": "5.10.2",
43 "@types/lodash-es": "^4.17.6",
44 "ansi-styles": "^6.1.0", 42 "ansi-styles": "^6.1.0",
43 "async-mutex": "^0.3.2",
45 "escape-string-regexp": "^5.0.0", 44 "escape-string-regexp": "^5.0.0",
46 "lodash-es": "^4.17.21", 45 "lodash-es": "^4.17.21",
47 "loglevel": "^1.8.0", 46 "loglevel": "^1.8.0",
@@ -58,19 +57,20 @@
58 "@lezer/generator": "^1.1.1", 57 "@lezer/generator": "^1.1.1",
59 "@types/eslint": "^8.4.6", 58 "@types/eslint": "^8.4.6",
60 "@types/html-minifier-terser": "^7.0.0", 59 "@types/html-minifier-terser": "^7.0.0",
61 "@types/node": "^18.7.8", 60 "@types/lodash-es": "^4.17.6",
61 "@types/node": "^18.7.13",
62 "@types/prettier": "^2.7.0", 62 "@types/prettier": "^2.7.0",
63 "@types/react": "^18.0.17", 63 "@types/react": "^18.0.17",
64 "@types/react-dom": "^18.0.6", 64 "@types/react-dom": "^18.0.6",
65 "@typescript-eslint/eslint-plugin": "^5.33.1", 65 "@typescript-eslint/eslint-plugin": "^5.35.1",
66 "@typescript-eslint/parser": "^5.33.1", 66 "@typescript-eslint/parser": "^5.35.1",
67 "@vitejs/plugin-react": "^2.0.1", 67 "@vitejs/plugin-react": "^2.0.1",
68 "cross-env": "^7.0.3", 68 "cross-env": "^7.0.3",
69 "eslint": "^8.22.0", 69 "eslint": "^8.22.0",
70 "eslint-config-airbnb": "^19.0.4", 70 "eslint-config-airbnb": "^19.0.4",
71 "eslint-config-airbnb-typescript": "^17.0.0", 71 "eslint-config-airbnb-typescript": "^17.0.0",
72 "eslint-config-prettier": "^8.5.0", 72 "eslint-config-prettier": "^8.5.0",
73 "eslint-import-resolver-typescript": "^3.4.2", 73 "eslint-import-resolver-typescript": "^3.5.0",
74 "eslint-plugin-import": "^2.26.0", 74 "eslint-plugin-import": "^2.26.0",
75 "eslint-plugin-jsx-a11y": "^6.6.1", 75 "eslint-plugin-jsx-a11y": "^6.6.1",
76 "eslint-plugin-prettier": "^4.2.1", 76 "eslint-plugin-prettier": "^4.2.1",
@@ -80,7 +80,7 @@
80 "prettier": "^2.7.1", 80 "prettier": "^2.7.1",
81 "typescript": "~4.7.4", 81 "typescript": "~4.7.4",
82 "vite": "^3.0.9", 82 "vite": "^3.0.9",
83 "vite-plugin-inject-preload": "^1.0.1", 83 "vite-plugin-inject-preload": "^1.1.0",
84 "vite-plugin-pwa": "^0.12.3", 84 "vite-plugin-pwa": "^0.12.3",
85 "workbox-window": "^6.5.4" 85 "workbox-window": "^6.5.4"
86 } 86 }
diff --git a/subprojects/frontend/src/utils/ConditionVariable.ts b/subprojects/frontend/src/utils/ConditionVariable.ts
deleted file mode 100644
index 1d3431f7..00000000
--- a/subprojects/frontend/src/utils/ConditionVariable.ts
+++ /dev/null
@@ -1,64 +0,0 @@
1import PendingTask from './PendingTask';
2import getLogger from './getLogger';
3
4const log = getLogger('utils.ConditionVariable');
5
6export type Condition = () => boolean;
7
8export default class ConditionVariable {
9 private readonly condition: Condition;
10
11 private readonly defaultTimeout: number;
12
13 private listeners: PendingTask<void>[] = [];
14
15 constructor(condition: Condition, defaultTimeout = 0) {
16 this.condition = condition;
17 this.defaultTimeout = defaultTimeout;
18 }
19
20 async waitFor(timeoutMs?: number | undefined): Promise<void> {
21 if (this.condition()) {
22 return;
23 }
24 const timeoutOrDefault = timeoutMs ?? this.defaultTimeout;
25 let nowMs = Date.now();
26 const endMs = nowMs + timeoutOrDefault;
27 while (!this.condition() && nowMs < endMs) {
28 const remainingMs = endMs - nowMs;
29 const promise = new Promise<void>((resolve, reject) => {
30 if (this.condition()) {
31 resolve();
32 return;
33 }
34 const task = new PendingTask(resolve, reject, remainingMs);
35 this.listeners.push(task);
36 });
37 // We must keep waiting until the update has completed,
38 // so the tasks can't be started in parallel.
39 // eslint-disable-next-line no-await-in-loop
40 await promise;
41 nowMs = Date.now();
42 }
43 if (!this.condition()) {
44 log.error('Condition still does not hold after', timeoutOrDefault, 'ms');
45 throw new Error('Failed to wait for condition');
46 }
47 }
48
49 notifyAll(): void {
50 this.clearListenersWith((listener) => listener.resolve());
51 }
52
53 rejectAll(error: unknown): void {
54 this.clearListenersWith((listener) => listener.reject(error));
55 }
56
57 private clearListenersWith(callback: (listener: PendingTask<void>) => void) {
58 // Copy `listeners` so that we don't get into a race condition
59 // if one of the listeners adds another listener.
60 const { listeners } = this;
61 this.listeners = [];
62 listeners.forEach(callback);
63 }
64}
diff --git a/subprojects/frontend/src/utils/PendingTask.ts b/subprojects/frontend/src/utils/PendingTask.ts
index 3976bdf9..205c8452 100644
--- a/subprojects/frontend/src/utils/PendingTask.ts
+++ b/subprojects/frontend/src/utils/PendingTask.ts
@@ -14,21 +14,19 @@ export default class PendingTask<T> {
14 constructor( 14 constructor(
15 resolveCallback: (value: T) => void, 15 resolveCallback: (value: T) => void,
16 rejectCallback: (reason?: unknown) => void, 16 rejectCallback: (reason?: unknown) => void,
17 timeoutMs?: number | undefined, 17 timeoutMs: number | undefined,
18 timeoutCallback?: () => void | undefined, 18 timeoutCallback: () => void | undefined,
19 ) { 19 ) {
20 this.resolveCallback = resolveCallback; 20 this.resolveCallback = resolveCallback;
21 this.rejectCallback = rejectCallback; 21 this.rejectCallback = rejectCallback;
22 if (timeoutMs) { 22 this.timeout = setTimeout(() => {
23 this.timeout = setTimeout(() => { 23 if (!this.resolved) {
24 if (!this.resolved) { 24 this.reject(new Error('Request timed out'));
25 this.reject(new Error('Request timed out')); 25 if (timeoutCallback) {
26 if (timeoutCallback) { 26 timeoutCallback();
27 timeoutCallback();
28 }
29 } 27 }
30 }, timeoutMs); 28 }
31 } 29 }, timeoutMs);
32 } 30 }
33 31
34 resolve(value: T): void { 32 resolve(value: T): void {
diff --git a/subprojects/frontend/src/xtext/ContentAssistService.ts b/subprojects/frontend/src/xtext/ContentAssistService.ts
index 39042812..9e41f57b 100644
--- a/subprojects/frontend/src/xtext/ContentAssistService.ts
+++ b/subprojects/frontend/src/xtext/ContentAssistService.ts
@@ -104,13 +104,9 @@ function createCompletion(entry: ContentAssistEntry): Completion {
104} 104}
105 105
106export default class ContentAssistService { 106export default class ContentAssistService {
107 private readonly updateService: UpdateService;
108
109 private lastCompletion: CompletionResult | undefined; 107 private lastCompletion: CompletionResult | undefined;
110 108
111 constructor(updateService: UpdateService) { 109 constructor(private readonly updateService: UpdateService) {}
112 this.updateService = updateService;
113 }
114 110
115 onTransaction(transaction: Transaction): void { 111 onTransaction(transaction: Transaction): void {
116 if (this.shouldInvalidateCachedCompletion(transaction)) { 112 if (this.shouldInvalidateCachedCompletion(transaction)) {
@@ -159,8 +155,6 @@ export default class ContentAssistService {
159 this.lastCompletion = undefined; 155 this.lastCompletion = undefined;
160 const entries = await this.updateService.fetchContentAssist( 156 const entries = await this.updateService.fetchContentAssist(
161 { 157 {
162 resource: this.updateService.resourceName,
163 serviceType: 'assist',
164 caretOffset: context.pos, 158 caretOffset: context.pos,
165 proposalsLimit: PROPOSALS_LIMIT, 159 proposalsLimit: PROPOSALS_LIMIT,
166 }, 160 },
diff --git a/subprojects/frontend/src/xtext/HighlightingService.ts b/subprojects/frontend/src/xtext/HighlightingService.ts
index cf618b96..f9ab7b7e 100644
--- a/subprojects/frontend/src/xtext/HighlightingService.ts
+++ b/subprojects/frontend/src/xtext/HighlightingService.ts
@@ -5,14 +5,10 @@ import type UpdateService from './UpdateService';
5import { highlightingResult } from './xtextServiceResults'; 5import { highlightingResult } from './xtextServiceResults';
6 6
7export default class HighlightingService { 7export default class HighlightingService {
8 private readonly store: EditorStore; 8 constructor(
9 9 private readonly store: EditorStore,
10 private readonly updateService: UpdateService; 10 private readonly updateService: UpdateService,
11 11 ) {}
12 constructor(store: EditorStore, updateService: UpdateService) {
13 this.store = store;
14 this.updateService = updateService;
15 }
16 12
17 onPush(push: unknown): void { 13 onPush(push: unknown): void {
18 const { regions } = highlightingResult.parse(push); 14 const { regions } = highlightingResult.parse(push);
diff --git a/subprojects/frontend/src/xtext/OccurrencesService.ts b/subprojects/frontend/src/xtext/OccurrencesService.ts
index 35913f43..c8d6fd7b 100644
--- a/subprojects/frontend/src/xtext/OccurrencesService.ts
+++ b/subprojects/frontend/src/xtext/OccurrencesService.ts
@@ -1,20 +1,15 @@
1import { Transaction } from '@codemirror/state'; 1import { Transaction } from '@codemirror/state';
2import { debounce } from 'lodash-es';
2 3
3import type EditorStore from '../editor/EditorStore'; 4import type EditorStore from '../editor/EditorStore';
4import { 5import {
5 type IOccurrence, 6 type IOccurrence,
6 isCursorWithinOccurence, 7 isCursorWithinOccurence,
7} from '../editor/findOccurrences'; 8} from '../editor/findOccurrences';
8import Timer from '../utils/Timer';
9import getLogger from '../utils/getLogger'; 9import getLogger from '../utils/getLogger';
10 10
11import type UpdateService from './UpdateService'; 11import type UpdateService from './UpdateService';
12import type XtextWebSocketClient from './XtextWebSocketClient'; 12import type { TextRegion } from './xtextServiceResults';
13import {
14 isConflictResult,
15 OccurrencesResult,
16 type TextRegion,
17} from './xtextServiceResults';
18 13
19const FIND_OCCURRENCES_TIMEOUT_MS = 1000; 14const FIND_OCCURRENCES_TIMEOUT_MS = 1000;
20 15
@@ -34,38 +29,23 @@ function transformOccurrences(regions: TextRegion[]): IOccurrence[] {
34} 29}
35 30
36export default class OccurrencesService { 31export default class OccurrencesService {
37 private readonly store: EditorStore;
38
39 private readonly webSocketClient: XtextWebSocketClient;
40
41 private readonly updateService: UpdateService;
42
43 private hasOccurrences = false; 32 private hasOccurrences = false;
44 33
45 private readonly findOccurrencesTimer = new Timer(() => { 34 private readonly findOccurrencesLater = debounce(
46 this.handleFindOccurrences(); 35 () => this.findOccurrences(),
47 }, FIND_OCCURRENCES_TIMEOUT_MS); 36 FIND_OCCURRENCES_TIMEOUT_MS,
48 37 );
49 private readonly clearOccurrencesTimer = new Timer(() => {
50 this.clearOccurrences();
51 });
52 38
53 constructor( 39 constructor(
54 store: EditorStore, 40 private readonly store: EditorStore,
55 webSocketClient: XtextWebSocketClient, 41 private readonly updateService: UpdateService,
56 updateService: UpdateService, 42 ) {}
57 ) {
58 this.store = store;
59 this.webSocketClient = webSocketClient;
60 this.updateService = updateService;
61 }
62 43
63 onTransaction(transaction: Transaction): void { 44 onTransaction(transaction: Transaction): void {
64 if (transaction.docChanged) { 45 if (transaction.docChanged) {
65 // Must clear occurrences asynchronously from `onTransaction`, 46 // Must clear occurrences asynchronously from `onTransaction`,
66 // because we must not emit a conflicting transaction when handling the pending transaction. 47 // because we must not emit a conflicting transaction when handling the pending transaction.
67 this.clearOccurrencesTimer.schedule(); 48 this.clearAndFindOccurrencesLater();
68 this.findOccurrencesTimer.reschedule();
69 return; 49 return;
70 } 50 }
71 if (!transaction.isUserEvent('select')) { 51 if (!transaction.isUserEvent('select')) {
@@ -73,11 +53,10 @@ export default class OccurrencesService {
73 } 53 }
74 if (this.needsOccurrences) { 54 if (this.needsOccurrences) {
75 if (!isCursorWithinOccurence(this.store.state)) { 55 if (!isCursorWithinOccurence(this.store.state)) {
76 this.clearOccurrencesTimer.schedule(); 56 this.clearAndFindOccurrencesLater();
77 this.findOccurrencesTimer.reschedule();
78 } 57 }
79 } else { 58 } else {
80 this.clearOccurrencesTimer.schedule(); 59 this.clearOccurrencesLater();
81 } 60 }
82 } 61 }
83 62
@@ -85,8 +64,26 @@ export default class OccurrencesService {
85 return this.store.state.selection.main.empty; 64 return this.store.state.selection.main.empty;
86 } 65 }
87 66
88 private handleFindOccurrences() { 67 private clearAndFindOccurrencesLater(): void {
89 this.clearOccurrencesTimer.cancel(); 68 this.clearOccurrencesLater();
69 this.findOccurrencesLater();
70 }
71
72 /**
73 * Clears the occurences from a new immediate task to let the current editor transaction finish.
74 */
75 private clearOccurrencesLater() {
76 setTimeout(() => this.clearOccurrences(), 0);
77 }
78
79 private clearOccurrences() {
80 if (this.hasOccurrences) {
81 this.store.updateOccurrences([], []);
82 this.hasOccurrences = false;
83 }
84 }
85
86 private findOccurrences() {
90 this.updateOccurrences().catch((error) => { 87 this.updateOccurrences().catch((error) => {
91 log.error('Unexpected error while updating occurrences', error); 88 log.error('Unexpected error while updating occurrences', error);
92 this.clearOccurrences(); 89 this.clearOccurrences();
@@ -98,43 +95,26 @@ export default class OccurrencesService {
98 this.clearOccurrences(); 95 this.clearOccurrences();
99 return; 96 return;
100 } 97 }
101 await this.updateService.update(); 98 const fetchResult = await this.updateService.fetchOccurrences(() => {
102 const result = await this.webSocketClient.send({ 99 return this.needsOccurrences
103 resource: this.updateService.resourceName, 100 ? {
104 serviceType: 'occurrences', 101 cancelled: false,
105 expectedStateId: this.updateService.xtextStateId, 102 data: this.store.state.selection.main.head,
106 caretOffset: this.store.state.selection.main.head, 103 }
104 : { cancelled: true };
107 }); 105 });
108 const allChanges = this.updateService.computeChangesSinceLastUpdate(); 106 if (fetchResult.cancelled) {
109 if (!allChanges.empty || isConflictResult(result, 'canceled')) {
110 // Stale occurrences result, the user already made some changes. 107 // Stale occurrences result, the user already made some changes.
111 // We can safely ignore the occurrences and schedule a new find occurrences call. 108 // We can safely ignore the occurrences and schedule a new find occurrences call.
112 this.clearOccurrences(); 109 this.clearOccurrences();
113 this.findOccurrencesTimer.schedule(); 110 if (this.needsOccurrences) {
114 return; 111 this.findOccurrencesLater();
115 } 112 }
116 const parsedOccurrencesResult = OccurrencesResult.safeParse(result);
117 if (!parsedOccurrencesResult.success) {
118 log.error(
119 'Unexpected occurences result',
120 result,
121 'not an OccurrencesResult: ',
122 parsedOccurrencesResult.error,
123 );
124 this.clearOccurrences();
125 return;
126 }
127 const { stateId, writeRegions, readRegions } = parsedOccurrencesResult.data;
128 if (stateId !== this.updateService.xtextStateId) {
129 log.error(
130 'Unexpected state id, expected:',
131 this.updateService.xtextStateId,
132 'got:',
133 stateId,
134 );
135 this.clearOccurrences();
136 return; 113 return;
137 } 114 }
115 const {
116 data: { writeRegions, readRegions },
117 } = fetchResult;
138 const write = transformOccurrences(writeRegions); 118 const write = transformOccurrences(writeRegions);
139 const read = transformOccurrences(readRegions); 119 const read = transformOccurrences(readRegions);
140 this.hasOccurrences = write.length > 0 || read.length > 0; 120 this.hasOccurrences = write.length > 0 || read.length > 0;
@@ -147,11 +127,4 @@ export default class OccurrencesService {
147 ); 127 );
148 this.store.updateOccurrences(write, read); 128 this.store.updateOccurrences(write, read);
149 } 129 }
150
151 private clearOccurrences() {
152 if (this.hasOccurrences) {
153 this.store.updateOccurrences([], []);
154 this.hasOccurrences = false;
155 }
156 }
157} 130}
diff --git a/subprojects/frontend/src/xtext/UpdateService.ts b/subprojects/frontend/src/xtext/UpdateService.ts
index f8b71160..3b4ae259 100644
--- a/subprojects/frontend/src/xtext/UpdateService.ts
+++ b/subprojects/frontend/src/xtext/UpdateService.ts
@@ -5,11 +5,11 @@ import {
5 StateEffect, 5 StateEffect,
6 type Transaction, 6 type Transaction,
7} from '@codemirror/state'; 7} from '@codemirror/state';
8import { E_CANCELED, E_TIMEOUT, Mutex, withTimeout } from 'async-mutex';
9import { debounce } from 'lodash-es';
8import { nanoid } from 'nanoid'; 10import { nanoid } from 'nanoid';
9 11
10import type EditorStore from '../editor/EditorStore'; 12import type EditorStore from '../editor/EditorStore';
11import ConditionVariable from '../utils/ConditionVariable';
12import Timer from '../utils/Timer';
13import getLogger from '../utils/getLogger'; 13import getLogger from '../utils/getLogger';
14 14
15import type XtextWebSocketClient from './XtextWebSocketClient'; 15import type XtextWebSocketClient from './XtextWebSocketClient';
@@ -19,12 +19,15 @@ import {
19 DocumentStateResult, 19 DocumentStateResult,
20 FormattingResult, 20 FormattingResult,
21 isConflictResult, 21 isConflictResult,
22 OccurrencesResult,
22} from './xtextServiceResults'; 23} from './xtextServiceResults';
23 24
24const UPDATE_TIMEOUT_MS = 500; 25const UPDATE_TIMEOUT_MS = 500;
25 26
26const WAIT_FOR_UPDATE_TIMEOUT_MS = 1000; 27const WAIT_FOR_UPDATE_TIMEOUT_MS = 1000;
27 28
29const FORMAT_TEXT_RETRIES = 5;
30
28const log = getLogger('xtext.UpdateService'); 31const log = getLogger('xtext.UpdateService');
29 32
30/** 33/**
@@ -38,10 +41,20 @@ const log = getLogger('xtext.UpdateService');
38 */ 41 */
39const setDirtyChanges = StateEffect.define<ChangeSet>(); 42const setDirtyChanges = StateEffect.define<ChangeSet>();
40 43
41export interface IAbortSignal { 44export interface AbortSignal {
42 aborted: boolean; 45 aborted: boolean;
43} 46}
44 47
48export interface ContentAssistParams {
49 caretOffset: number;
50
51 proposalsLimit: number;
52}
53
54export type CancellableResult<T> =
55 | { cancelled: false; data: T }
56 | { cancelled: true };
57
45interface StateUpdateResult<T> { 58interface StateUpdateResult<T> {
46 newStateId: string; 59 newStateId: string;
47 60
@@ -57,15 +70,27 @@ interface Delta {
57} 70}
58 71
59export default class UpdateService { 72export default class UpdateService {
60 resourceName: string; 73 readonly resourceName: string;
61 74
62 xtextStateId: string | undefined; 75 xtextStateId: string | undefined;
63 76
64 private readonly store: EditorStore; 77 private readonly store: EditorStore;
65 78
79 private readonly mutex = withTimeout(new Mutex(), WAIT_FOR_UPDATE_TIMEOUT_MS);
80
66 /** 81 /**
67 * The changes being synchronized to the server if a full or delta text update is running, 82 * The changes being synchronized to the server if a full or delta text update is running
68 * `undefined` otherwise. 83 * withing a `withUpdateExclusive` block, `undefined` otherwise.
84 *
85 * Must be `undefined` before and after entering the critical section of `mutex`
86 * and may only be changes in the critical section of `mutex`.
87 *
88 * Methods named with an `Exclusive` suffix in this class assume that the mutex is held
89 * and may call `withUpdateExclusive` or `doFallbackUpdateFullTextExclusive`
90 * to mutate this field.
91 *
92 * Methods named with a `do` suffix assume that they are called in a `withUpdateExclusive`
93 * block and require this field to be non-`undefined`.
69 */ 94 */
70 private pendingUpdate: ChangeSet | undefined; 95 private pendingUpdate: ChangeSet | undefined;
71 96
@@ -76,15 +101,11 @@ export default class UpdateService {
76 101
77 private readonly webSocketClient: XtextWebSocketClient; 102 private readonly webSocketClient: XtextWebSocketClient;
78 103
79 private readonly updatedCondition = new ConditionVariable( 104 private readonly idleUpdateLater = debounce(
80 () => this.pendingUpdate === undefined && this.xtextStateId !== undefined, 105 () => this.idleUpdate(),
81 WAIT_FOR_UPDATE_TIMEOUT_MS, 106 UPDATE_TIMEOUT_MS,
82 ); 107 );
83 108
84 private readonly idleUpdateTimer = new Timer(() => {
85 this.handleIdleUpdate();
86 }, UPDATE_TIMEOUT_MS);
87
88 constructor(store: EditorStore, webSocketClient: XtextWebSocketClient) { 109 constructor(store: EditorStore, webSocketClient: XtextWebSocketClient) {
89 this.resourceName = `${nanoid(7)}.problem`; 110 this.resourceName = `${nanoid(7)}.problem`;
90 this.store = store; 111 this.store = store;
@@ -95,6 +116,13 @@ export default class UpdateService {
95 onReconnect(): void { 116 onReconnect(): void {
96 this.xtextStateId = undefined; 117 this.xtextStateId = undefined;
97 this.updateFullText().catch((error) => { 118 this.updateFullText().catch((error) => {
119 // Let E_TIMEOUT errors propagate, since if the first update times out,
120 // we can't use the connection.
121 if (error === E_CANCELED) {
122 // Content assist will perform a full-text update anyways.
123 log.debug('Full text update cancelled');
124 return;
125 }
98 log.error('Unexpected error during initial update', error); 126 log.error('Unexpected error during initial update', error);
99 }); 127 });
100 } 128 }
@@ -106,6 +134,8 @@ export default class UpdateService {
106 if (setDirtyChangesEffect) { 134 if (setDirtyChangesEffect) {
107 const { value } = setDirtyChangesEffect; 135 const { value } = setDirtyChangesEffect;
108 if (this.pendingUpdate !== undefined) { 136 if (this.pendingUpdate !== undefined) {
137 // Do not clear `pendingUpdate`, because that would indicate an update failure
138 // to `withUpdateExclusive`.
109 this.pendingUpdate = ChangeSet.empty(value.length); 139 this.pendingUpdate = ChangeSet.empty(value.length);
110 } 140 }
111 this.dirtyChanges = value; 141 this.dirtyChanges = value;
@@ -113,7 +143,7 @@ export default class UpdateService {
113 } 143 }
114 if (transaction.docChanged) { 144 if (transaction.docChanged) {
115 this.dirtyChanges = this.dirtyChanges.compose(transaction.changes); 145 this.dirtyChanges = this.dirtyChanges.compose(transaction.changes);
116 this.idleUpdateTimer.reschedule(); 146 this.idleUpdateLater();
117 } 147 }
118 } 148 }
119 149
@@ -132,34 +162,42 @@ export default class UpdateService {
132 ); 162 );
133 } 163 }
134 164
135 private handleIdleUpdate(): void { 165 private idleUpdate(): void {
136 if (!this.webSocketClient.isOpen || this.dirtyChanges.empty) { 166 if (!this.webSocketClient.isOpen || this.dirtyChanges.empty) {
137 return; 167 return;
138 } 168 }
139 if (this.pendingUpdate === undefined) { 169 if (!this.mutex.isLocked()) {
140 this.update().catch((error) => { 170 this.update().catch((error) => {
171 if (error === E_CANCELED || error === E_TIMEOUT) {
172 log.debug('Idle update cancelled');
173 return;
174 }
141 log.error('Unexpected error during scheduled update', error); 175 log.error('Unexpected error during scheduled update', error);
142 }); 176 });
143 } 177 }
144 this.idleUpdateTimer.reschedule(); 178 this.idleUpdateLater();
145 } 179 }
146 180
147 private newEmptyChangeSet(): ChangeSet { 181 private newEmptyChangeSet(): ChangeSet {
148 return ChangeSet.of([], this.store.state.doc.length); 182 return ChangeSet.of([], this.store.state.doc.length);
149 } 183 }
150 184
151 async updateFullText(): Promise<void> { 185 private updateFullText(): Promise<void> {
152 await this.withUpdate(() => this.doUpdateFullText()); 186 return this.runExclusive(() => this.updateFullTextExclusive());
153 } 187 }
154 188
155 private async doUpdateFullText(): Promise<StateUpdateResult<void>> { 189 private async updateFullTextExclusive(): Promise<void> {
190 await this.withVoidUpdateExclusive(() => this.doUpdateFullTextExclusive());
191 }
192
193 private async doUpdateFullTextExclusive(): Promise<string> {
156 const result = await this.webSocketClient.send({ 194 const result = await this.webSocketClient.send({
157 resource: this.resourceName, 195 resource: this.resourceName,
158 serviceType: 'update', 196 serviceType: 'update',
159 fullText: this.store.state.doc.sliceString(0), 197 fullText: this.store.state.doc.sliceString(0),
160 }); 198 });
161 const { stateId } = DocumentStateResult.parse(result); 199 const { stateId } = DocumentStateResult.parse(result);
162 return { newStateId: stateId, data: undefined }; 200 return stateId;
163 } 201 }
164 202
165 /** 203 /**
@@ -171,14 +209,26 @@ export default class UpdateService {
171 * 209 *
172 * @returns a promise resolving when the update is completed 210 * @returns a promise resolving when the update is completed
173 */ 211 */
174 async update(): Promise<void> { 212 private async update(): Promise<void> {
175 await this.prepareForDeltaUpdate(); 213 // We may check here for the delta to avoid locking,
214 // but we'll need to recompute the delta in the critical section,
215 // because it may have changed by the time we can acquire the lock.
216 if (this.dirtyChanges.empty) {
217 return;
218 }
219 await this.runExclusive(() => this.updateExclusive());
220 }
221
222 private async updateExclusive(): Promise<void> {
223 if (this.xtextStateId === undefined) {
224 await this.updateFullTextExclusive();
225 }
176 const delta = this.computeDelta(); 226 const delta = this.computeDelta();
177 if (delta === undefined) { 227 if (delta === undefined) {
178 return; 228 return;
179 } 229 }
180 log.trace('Editor delta', delta); 230 log.trace('Editor delta', delta);
181 await this.withUpdate(async () => { 231 await this.withVoidUpdateExclusive(async () => {
182 const result = await this.webSocketClient.send({ 232 const result = await this.webSocketClient.send({
183 resource: this.resourceName, 233 resource: this.resourceName,
184 serviceType: 'update', 234 serviceType: 'update',
@@ -187,34 +237,98 @@ export default class UpdateService {
187 }); 237 });
188 const parsedDocumentStateResult = DocumentStateResult.safeParse(result); 238 const parsedDocumentStateResult = DocumentStateResult.safeParse(result);
189 if (parsedDocumentStateResult.success) { 239 if (parsedDocumentStateResult.success) {
190 return { 240 return parsedDocumentStateResult.data.stateId;
191 newStateId: parsedDocumentStateResult.data.stateId,
192 data: undefined,
193 };
194 } 241 }
195 if (isConflictResult(result, 'invalidStateId')) { 242 if (isConflictResult(result, 'invalidStateId')) {
196 return this.doFallbackToUpdateFullText(); 243 return this.doFallbackUpdateFullTextExclusive();
197 } 244 }
198 throw parsedDocumentStateResult.error; 245 throw parsedDocumentStateResult.error;
199 }); 246 });
200 } 247 }
201 248
202 private doFallbackToUpdateFullText(): Promise<StateUpdateResult<void>> { 249 async fetchOccurrences(
203 if (this.pendingUpdate === undefined) { 250 getCaretOffset: () => CancellableResult<number>,
204 throw new Error('Only a pending update can be extended'); 251 ): Promise<CancellableResult<OccurrencesResult>> {
252 try {
253 await this.update();
254 } catch (error) {
255 if (error === E_CANCELED || error === E_TIMEOUT) {
256 return { cancelled: true };
257 }
258 throw error;
205 } 259 }
206 log.warn('Delta update failed, performing full text update'); 260 if (!this.dirtyChanges.empty || this.mutex.isLocked()) {
207 this.xtextStateId = undefined; 261 // Just give up if another update is in progress.
208 this.pendingUpdate = this.pendingUpdate.compose(this.dirtyChanges); 262 return { cancelled: true };
209 this.dirtyChanges = this.newEmptyChangeSet(); 263 }
210 return this.doUpdateFullText(); 264 const caretOffsetResult = getCaretOffset();
265 if (caretOffsetResult.cancelled) {
266 return { cancelled: true };
267 }
268 const expectedStateId = this.xtextStateId;
269 const data = await this.webSocketClient.send({
270 resource: this.resourceName,
271 serviceType: 'occurrences',
272 caretOffset: caretOffsetResult.data,
273 expectedStateId,
274 });
275 if (
276 // The query must have reached the server without being conflicted with an update
277 // or cancelled server-side.
278 isConflictResult(data) ||
279 // And no state update should have occurred since then.
280 this.xtextStateId !== expectedStateId ||
281 // And there should be no change to the editor text since then.
282 !this.dirtyChanges.empty ||
283 // And there should be no state update in progress.
284 this.mutex.isLocked()
285 ) {
286 return { cancelled: true };
287 }
288 const parsedOccurrencesResult = OccurrencesResult.safeParse(data);
289 if (!parsedOccurrencesResult.success) {
290 log.error(
291 'Unexpected occurences result',
292 data,
293 'not an OccurrencesResult:',
294 parsedOccurrencesResult.error,
295 );
296 throw parsedOccurrencesResult.error;
297 }
298 if (parsedOccurrencesResult.data.stateId !== expectedStateId) {
299 return { cancelled: true };
300 }
301 return { cancelled: false, data: parsedOccurrencesResult.data };
211 } 302 }
212 303
213 async fetchContentAssist( 304 async fetchContentAssist(
214 params: Record<string, unknown>, 305 params: ContentAssistParams,
215 signal: IAbortSignal, 306 signal: AbortSignal,
307 ): Promise<ContentAssistEntry[]> {
308 if (!this.mutex.isLocked && this.xtextStateId !== undefined) {
309 return this.fetchContentAssistFetchOnly(params, this.xtextStateId);
310 }
311 // Content assist updates should have priority over other updates.
312 this.mutex.cancel();
313 try {
314 return await this.runExclusive(() =>
315 this.fetchContentAssistExclusive(params, signal),
316 );
317 } catch (error) {
318 if ((error === E_CANCELED || error === E_TIMEOUT) && signal.aborted) {
319 return [];
320 }
321 throw error;
322 }
323 }
324
325 private async fetchContentAssistExclusive(
326 params: ContentAssistParams,
327 signal: AbortSignal,
216 ): Promise<ContentAssistEntry[]> { 328 ): Promise<ContentAssistEntry[]> {
217 await this.prepareForDeltaUpdate(); 329 if (this.xtextStateId === undefined) {
330 await this.updateFullTextExclusive();
331 }
218 if (signal.aborted) { 332 if (signal.aborted) {
219 return []; 333 return [];
220 } 334 }
@@ -222,8 +336,8 @@ export default class UpdateService {
222 if (delta !== undefined) { 336 if (delta !== undefined) {
223 log.trace('Editor delta', delta); 337 log.trace('Editor delta', delta);
224 // Try to fetch while also performing a delta update. 338 // Try to fetch while also performing a delta update.
225 const fetchUpdateEntries = await this.withUpdate(() => 339 const fetchUpdateEntries = await this.withUpdateExclusive(() =>
226 this.doFetchContentAssistWithDelta(params, delta), 340 this.doFetchContentAssistWithDeltaExclusive(params, delta),
227 ); 341 );
228 if (fetchUpdateEntries !== undefined) { 342 if (fetchUpdateEntries !== undefined) {
229 return fetchUpdateEntries; 343 return fetchUpdateEntries;
@@ -235,15 +349,17 @@ export default class UpdateService {
235 if (this.xtextStateId === undefined) { 349 if (this.xtextStateId === undefined) {
236 throw new Error('failed to obtain Xtext state id'); 350 throw new Error('failed to obtain Xtext state id');
237 } 351 }
238 return this.doFetchContentAssistFetchOnly(params, this.xtextStateId); 352 return this.fetchContentAssistFetchOnly(params, this.xtextStateId);
239 } 353 }
240 354
241 private async doFetchContentAssistWithDelta( 355 private async doFetchContentAssistWithDeltaExclusive(
242 params: Record<string, unknown>, 356 params: ContentAssistParams,
243 delta: Delta, 357 delta: Delta,
244 ): Promise<StateUpdateResult<ContentAssistEntry[] | undefined>> { 358 ): Promise<StateUpdateResult<ContentAssistEntry[] | undefined>> {
245 const fetchUpdateResult = await this.webSocketClient.send({ 359 const fetchUpdateResult = await this.webSocketClient.send({
246 ...params, 360 ...params,
361 resource: this.resourceName,
362 serviceType: 'assist',
247 requiredStateId: this.xtextStateId, 363 requiredStateId: this.xtextStateId,
248 ...delta, 364 ...delta,
249 }); 365 });
@@ -256,7 +372,7 @@ export default class UpdateService {
256 } 372 }
257 if (isConflictResult(fetchUpdateResult, 'invalidStateId')) { 373 if (isConflictResult(fetchUpdateResult, 'invalidStateId')) {
258 log.warn('Server state invalid during content assist'); 374 log.warn('Server state invalid during content assist');
259 const { newStateId } = await this.doFallbackToUpdateFullText(); 375 const newStateId = await this.doFallbackUpdateFullTextExclusive();
260 // We must finish this state update transaction to prepare for any push events 376 // We must finish this state update transaction to prepare for any push events
261 // before querying for content assist, so we just return `undefined` and will query 377 // before querying for content assist, so we just return `undefined` and will query
262 // the content assist service later. 378 // the content assist service later.
@@ -265,14 +381,16 @@ export default class UpdateService {
265 throw parsedContentAssistResult.error; 381 throw parsedContentAssistResult.error;
266 } 382 }
267 383
268 private async doFetchContentAssistFetchOnly( 384 private async fetchContentAssistFetchOnly(
269 params: Record<string, unknown>, 385 params: ContentAssistParams,
270 requiredStateId: string, 386 requiredStateId: string,
271 ): Promise<ContentAssistEntry[]> { 387 ): Promise<ContentAssistEntry[]> {
272 // Fallback to fetching without a delta update. 388 // Fallback to fetching without a delta update.
273 const fetchOnlyResult = await this.webSocketClient.send({ 389 const fetchOnlyResult = await this.webSocketClient.send({
274 ...params, 390 ...params,
275 requiredStateId: this.xtextStateId, 391 resource: this.resourceName,
392 serviceType: 'assist',
393 requiredStateId,
276 }); 394 });
277 const { stateId, entries: fetchOnlyEntries } = 395 const { stateId, entries: fetchOnlyEntries } =
278 ContentAssistResult.parse(fetchOnlyResult); 396 ContentAssistResult.parse(fetchOnlyResult);
@@ -285,14 +403,32 @@ export default class UpdateService {
285 } 403 }
286 404
287 async formatText(): Promise<void> { 405 async formatText(): Promise<void> {
288 await this.update(); 406 let retries = 0;
407 while (retries < FORMAT_TEXT_RETRIES) {
408 try {
409 // eslint-disable-next-line no-await-in-loop -- Use a loop for sequential retries.
410 await this.runExclusive(() => this.formatTextExclusive());
411 return;
412 } catch (error) {
413 // Let timeout errors propagate to give up formatting on a flaky connection.
414 if (error === E_CANCELED && retries < FORMAT_TEXT_RETRIES) {
415 retries += 1;
416 } else {
417 throw error;
418 }
419 }
420 }
421 }
422
423 private async formatTextExclusive(): Promise<void> {
424 await this.updateExclusive();
289 let { from, to } = this.store.state.selection.main; 425 let { from, to } = this.store.state.selection.main;
290 if (to <= from) { 426 if (to <= from) {
291 from = 0; 427 from = 0;
292 to = this.store.state.doc.length; 428 to = this.store.state.doc.length;
293 } 429 }
294 log.debug('Formatting from', from, 'to', to); 430 log.debug('Formatting from', from, 'to', to);
295 await this.withUpdate<void>(async () => { 431 await this.withVoidUpdateExclusive(async () => {
296 const result = await this.webSocketClient.send({ 432 const result = await this.webSocketClient.send({
297 resource: this.resourceName, 433 resource: this.resourceName,
298 serviceType: 'format', 434 serviceType: 'format',
@@ -305,7 +441,7 @@ export default class UpdateService {
305 to, 441 to,
306 insert: formattedText, 442 insert: formattedText,
307 }); 443 });
308 return { newStateId: stateId, data: undefined }; 444 return stateId;
309 }); 445 });
310 } 446 }
311 447
@@ -345,6 +481,28 @@ export default class UpdateService {
345 }); 481 });
346 } 482 }
347 483
484 private runExclusive<T>(callback: () => Promise<T>): Promise<T> {
485 return this.mutex.runExclusive(async () => {
486 if (this.pendingUpdate !== undefined) {
487 throw new Error('Update is pending before entering critical section');
488 }
489 const result = await callback();
490 if (this.pendingUpdate !== undefined) {
491 throw new Error('Update is pending after entering critical section');
492 }
493 return result;
494 });
495 }
496
497 private withVoidUpdateExclusive(
498 callback: () => Promise<string>,
499 ): Promise<void> {
500 return this.withUpdateExclusive<void>(async () => {
501 const newStateId = await callback();
502 return { newStateId, data: undefined };
503 });
504 }
505
348 /** 506 /**
349 * Executes an asynchronous callback that updates the state on the server. 507 * Executes an asynchronous callback that updates the state on the server.
350 * 508 *
@@ -366,20 +524,18 @@ export default class UpdateService {
366 * @param callback the asynchronous callback that updates the server state 524 * @param callback the asynchronous callback that updates the server state
367 * @returns a promise resolving to the second value returned by `callback` 525 * @returns a promise resolving to the second value returned by `callback`
368 */ 526 */
369 private async withUpdate<T>( 527 private async withUpdateExclusive<T>(
370 callback: () => Promise<StateUpdateResult<T>>, 528 callback: () => Promise<StateUpdateResult<T>>,
371 ): Promise<T> { 529 ): Promise<T> {
372 if (this.pendingUpdate !== undefined) { 530 if (this.pendingUpdate !== undefined) {
373 throw new Error('Another update is pending, will not perform update'); 531 throw new Error('Delta updates are not reentrant');
374 } 532 }
375 this.pendingUpdate = this.dirtyChanges; 533 this.pendingUpdate = this.dirtyChanges;
376 this.dirtyChanges = this.newEmptyChangeSet(); 534 this.dirtyChanges = this.newEmptyChangeSet();
535 let data: T;
377 try { 536 try {
378 const { newStateId, data } = await callback(); 537 ({ newStateId: this.xtextStateId, data } = await callback());
379 this.xtextStateId = newStateId;
380 this.pendingUpdate = undefined; 538 this.pendingUpdate = undefined;
381 this.updatedCondition.notifyAll();
382 return data;
383 } catch (e) { 539 } catch (e) {
384 log.error('Error while update', e); 540 log.error('Error while update', e);
385 if (this.pendingUpdate === undefined) { 541 if (this.pendingUpdate === undefined) {
@@ -389,25 +545,19 @@ export default class UpdateService {
389 } 545 }
390 this.pendingUpdate = undefined; 546 this.pendingUpdate = undefined;
391 this.webSocketClient.forceReconnectOnError(); 547 this.webSocketClient.forceReconnectOnError();
392 this.updatedCondition.rejectAll(e);
393 throw e; 548 throw e;
394 } 549 }
550 return data;
395 } 551 }
396 552
397 /** 553 private doFallbackUpdateFullTextExclusive(): Promise<string> {
398 * Ensures that there is some state available on the server (`xtextStateId`) 554 if (this.pendingUpdate === undefined) {
399 * and that there is no pending update. 555 throw new Error('Only a pending update can be extended');
400 *
401 * After this function resolves, a delta text update is possible.
402 *
403 * @returns a promise resolving when there is a valid state id but no pending update
404 */
405 private async prepareForDeltaUpdate(): Promise<void> {
406 // If no update is pending, but the full text hasn't been uploaded to the server yet,
407 // we must start a full text upload.
408 if (this.pendingUpdate === undefined && this.xtextStateId === undefined) {
409 await this.updateFullText();
410 } 556 }
411 await this.updatedCondition.waitFor(); 557 log.warn('Delta update failed, performing full text update');
558 this.xtextStateId = undefined;
559 this.pendingUpdate = this.pendingUpdate.compose(this.dirtyChanges);
560 this.dirtyChanges = this.newEmptyChangeSet();
561 return this.doUpdateFullTextExclusive();
412 } 562 }
413} 563}
diff --git a/subprojects/frontend/src/xtext/ValidationService.ts b/subprojects/frontend/src/xtext/ValidationService.ts
index a0b27251..e78318f7 100644
--- a/subprojects/frontend/src/xtext/ValidationService.ts
+++ b/subprojects/frontend/src/xtext/ValidationService.ts
@@ -6,14 +6,10 @@ import type UpdateService from './UpdateService';
6import { ValidationResult } from './xtextServiceResults'; 6import { ValidationResult } from './xtextServiceResults';
7 7
8export default class ValidationService { 8export default class ValidationService {
9 private readonly store: EditorStore; 9 constructor(
10 10 private readonly store: EditorStore,
11 private readonly updateService: UpdateService; 11 private readonly updateService: UpdateService,
12 12 ) {}
13 constructor(store: EditorStore, updateService: UpdateService) {
14 this.store = store;
15 this.updateService = updateService;
16 }
17 13
18 onPush(push: unknown): void { 14 onPush(push: unknown): void {
19 const { issues } = ValidationResult.parse(push); 15 const { issues } = ValidationResult.parse(push);
diff --git a/subprojects/frontend/src/xtext/XtextClient.ts b/subprojects/frontend/src/xtext/XtextClient.ts
index 7297c674..6351c9fd 100644
--- a/subprojects/frontend/src/xtext/XtextClient.ts
+++ b/subprojects/frontend/src/xtext/XtextClient.ts
@@ -43,11 +43,7 @@ export default class XtextClient {
43 this.updateService, 43 this.updateService,
44 ); 44 );
45 this.validationService = new ValidationService(store, this.updateService); 45 this.validationService = new ValidationService(store, this.updateService);
46 this.occurrencesService = new OccurrencesService( 46 this.occurrencesService = new OccurrencesService(store, this.updateService);
47 store,
48 this.webSocketClient,
49 this.updateService,
50 );
51 } 47 }
52 48
53 onTransaction(transaction: Transaction): void { 49 onTransaction(transaction: Transaction): void {
diff --git a/subprojects/frontend/src/xtext/xtextServiceResults.ts b/subprojects/frontend/src/xtext/xtextServiceResults.ts
index 4cfb9c33..e93c6714 100644
--- a/subprojects/frontend/src/xtext/xtextServiceResults.ts
+++ b/subprojects/frontend/src/xtext/xtextServiceResults.ts
@@ -26,12 +26,13 @@ export type ServiceConflictResult = z.infer<typeof ServiceConflictResult>;
26 26
27export function isConflictResult( 27export function isConflictResult(
28 result: unknown, 28 result: unknown,
29 conflictType: Conflict, 29 conflictType?: Conflict | undefined,
30): boolean { 30): boolean {
31 const parsedConflictResult = ServiceConflictResult.safeParse(result); 31 const parsedConflictResult = ServiceConflictResult.safeParse(result);
32 return ( 32 return (
33 parsedConflictResult.success && 33 parsedConflictResult.success &&
34 parsedConflictResult.data.conflict === conflictType 34 (conflictType === undefined ||
35 parsedConflictResult.data.conflict === conflictType)
35 ); 36 );
36} 37}
37 38
diff --git a/yarn.lock b/yarn.lock
index ca217f1a..a8dc0ca4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1440,16 +1440,16 @@ __metadata:
1440 languageName: node 1440 languageName: node
1441 linkType: hard 1441 linkType: hard
1442 1442
1443"@emotion/cache@npm:^11.10.0, @emotion/cache@npm:^11.9.3": 1443"@emotion/cache@npm:^11.10.0, @emotion/cache@npm:^11.10.1":
1444 version: 11.10.0 1444 version: 11.10.3
1445 resolution: "@emotion/cache@npm:11.10.0" 1445 resolution: "@emotion/cache@npm:11.10.3"
1446 dependencies: 1446 dependencies:
1447 "@emotion/memoize": ^0.8.0 1447 "@emotion/memoize": ^0.8.0
1448 "@emotion/sheet": ^1.2.0 1448 "@emotion/sheet": ^1.2.0
1449 "@emotion/utils": ^1.2.0 1449 "@emotion/utils": ^1.2.0
1450 "@emotion/weak-memoize": ^0.3.0 1450 "@emotion/weak-memoize": ^0.3.0
1451 stylis: 4.0.13 1451 stylis: 4.0.13
1452 checksum: 60786e3108c35d3f79bd434119f1b6e34120fccc92f4a129bb67596944b703c3396efa51ed373f0ee3d71de780d78f4d767c264f7d76b073d06d435a2d3c1edf 1452 checksum: d31291eff1b270d8db6f471b2b9b3bc5d786c296838631f101837747ff5afa8e8890655279457c68ce2cee23256ab02a25c177f5487b5061da82c7354c1bdce5
1453 languageName: node 1453 languageName: node
1454 linkType: hard 1454 linkType: hard
1455 1455
@@ -1460,7 +1460,7 @@ __metadata:
1460 languageName: node 1460 languageName: node
1461 linkType: hard 1461 linkType: hard
1462 1462
1463"@emotion/is-prop-valid@npm:^1.1.3, @emotion/is-prop-valid@npm:^1.2.0": 1463"@emotion/is-prop-valid@npm:^1.2.0":
1464 version: 1.2.0 1464 version: 1.2.0
1465 resolution: "@emotion/is-prop-valid@npm:1.2.0" 1465 resolution: "@emotion/is-prop-valid@npm:1.2.0"
1466 dependencies: 1466 dependencies:
@@ -1586,13 +1586,6 @@ __metadata:
1586 languageName: node 1586 languageName: node
1587 linkType: hard 1587 linkType: hard
1588 1588
1589"@fontsource/inter@npm:^4.5.12":
1590 version: 4.5.12
1591 resolution: "@fontsource/inter@npm:4.5.12"
1592 checksum: 7637978d4355f52cf348bbe40a2e1bfa535c290057bef27c022343234b00c07b138c86a2b52260825c1ecc5cc9f59c68fd6ff08311111c70c04f82ace592920b
1593 languageName: node
1594 linkType: hard
1595
1596"@fontsource/jetbrains-mono@npm:^4.5.10": 1589"@fontsource/jetbrains-mono@npm:^4.5.10":
1597 version: 4.5.10 1590 version: 4.5.10
1598 resolution: "@fontsource/jetbrains-mono@npm:4.5.10" 1591 resolution: "@fontsource/jetbrains-mono@npm:4.5.10"
@@ -1745,12 +1738,12 @@ __metadata:
1745 languageName: node 1738 languageName: node
1746 linkType: hard 1739 linkType: hard
1747 1740
1748"@mui/base@npm:5.0.0-alpha.93": 1741"@mui/base@npm:5.0.0-alpha.94":
1749 version: 5.0.0-alpha.93 1742 version: 5.0.0-alpha.94
1750 resolution: "@mui/base@npm:5.0.0-alpha.93" 1743 resolution: "@mui/base@npm:5.0.0-alpha.94"
1751 dependencies: 1744 dependencies:
1752 "@babel/runtime": ^7.17.2 1745 "@babel/runtime": ^7.17.2
1753 "@emotion/is-prop-valid": ^1.1.3 1746 "@emotion/is-prop-valid": ^1.2.0
1754 "@mui/types": ^7.1.5 1747 "@mui/types": ^7.1.5
1755 "@mui/utils": ^5.9.3 1748 "@mui/utils": ^5.9.3
1756 "@popperjs/core": ^2.11.6 1749 "@popperjs/core": ^2.11.6
@@ -1764,20 +1757,20 @@ __metadata:
1764 peerDependenciesMeta: 1757 peerDependenciesMeta:
1765 "@types/react": 1758 "@types/react":
1766 optional: true 1759 optional: true
1767 checksum: 8e04ac3d7e453d8acea73884fea899db4561218ba48644b04a11dfb905dbf630a84847a28ca62bc03d678c8db749e76e150d9cf99d1fbb5ff892e6b734527c29 1760 checksum: 877c7c1b53a555265332e1e677945de0415db187a7c5a7f088eaacb4f85aa20086a668915ad1885e77fa7ef4df5d35250e55f4aef2b4c5aa82f014406bbdaef3
1768 languageName: node 1761 languageName: node
1769 linkType: hard 1762 linkType: hard
1770 1763
1771"@mui/core-downloads-tracker@npm:^5.10.1": 1764"@mui/core-downloads-tracker@npm:^5.10.2":
1772 version: 5.10.1 1765 version: 5.10.2
1773 resolution: "@mui/core-downloads-tracker@npm:5.10.1" 1766 resolution: "@mui/core-downloads-tracker@npm:5.10.2"
1774 checksum: 275fbabd9beb6d4cbbe5d6cf2a7621fb827bb7ad8be2fdd218ac3b1cc7c2ee9f69cbb71022b8a6f9ecaadbc21225015b33232fc3827dabe8db5c12fbf642cfc9 1767 checksum: 237d25f2cc8f63e4ee5f1cbe7944cb9818784535ed861ab3b8564ec4c7cbfdeb3c044596a2447c49feff987f268da30538fcb2ad194e3f7a0095d2e6edcdfdbd
1775 languageName: node 1768 languageName: node
1776 linkType: hard 1769 linkType: hard
1777 1770
1778"@mui/icons-material@npm:5.8.4": 1771"@mui/icons-material@npm:5.10.2":
1779 version: 5.8.4 1772 version: 5.10.2
1780 resolution: "@mui/icons-material@npm:5.8.4" 1773 resolution: "@mui/icons-material@npm:5.10.2"
1781 dependencies: 1774 dependencies:
1782 "@babel/runtime": ^7.17.2 1775 "@babel/runtime": ^7.17.2
1783 peerDependencies: 1776 peerDependencies:
@@ -1787,18 +1780,18 @@ __metadata:
1787 peerDependenciesMeta: 1780 peerDependenciesMeta:
1788 "@types/react": 1781 "@types/react":
1789 optional: true 1782 optional: true
1790 checksum: 1df0ffb08670628968b803c7048fc645df5e2870860759626d80a355fdef62498e60e4ebed2d47ca8b3600e6ecb6db1829bec5acf3a766ff41e518007c7e2f5b 1783 checksum: 9e694babaa69c52139b7cdd12f449a3464707ae12c3cdfd8af43bcbb1f9040dce08c6cde95ea7812e7b946f49fddbf4df05bc6cf44a30eaeb1c8701786fd309d
1791 languageName: node 1784 languageName: node
1792 linkType: hard 1785 linkType: hard
1793 1786
1794"@mui/material@npm:5.10.1": 1787"@mui/material@npm:5.10.2":
1795 version: 5.10.1 1788 version: 5.10.2
1796 resolution: "@mui/material@npm:5.10.1" 1789 resolution: "@mui/material@npm:5.10.2"
1797 dependencies: 1790 dependencies:
1798 "@babel/runtime": ^7.17.2 1791 "@babel/runtime": ^7.17.2
1799 "@mui/base": 5.0.0-alpha.93 1792 "@mui/base": 5.0.0-alpha.94
1800 "@mui/core-downloads-tracker": ^5.10.1 1793 "@mui/core-downloads-tracker": ^5.10.2
1801 "@mui/system": ^5.10.1 1794 "@mui/system": ^5.10.2
1802 "@mui/types": ^7.1.5 1795 "@mui/types": ^7.1.5
1803 "@mui/utils": ^5.9.3 1796 "@mui/utils": ^5.9.3
1804 "@types/react-transition-group": ^4.4.5 1797 "@types/react-transition-group": ^4.4.5
@@ -1820,7 +1813,7 @@ __metadata:
1820 optional: true 1813 optional: true
1821 "@types/react": 1814 "@types/react":
1822 optional: true 1815 optional: true
1823 checksum: 47c8157757df28863f4788f551ddb55e8268a246b2691fe5f0351d83040d874314a3b483d33359824f918abb861e3c6dca91562a5999ebda456c743890e6978a 1816 checksum: 2d44933c28e25b112a18cdcee4ea4c71d6eeba6b91ee33e3b66ec636c05daf3a9b03715d0a86c34a5670a3e25e39d13b10fc57aac524d52631134db0daca88de
1824 languageName: node 1817 languageName: node
1825 linkType: hard 1818 linkType: hard
1826 1819
@@ -1841,12 +1834,12 @@ __metadata:
1841 languageName: node 1834 languageName: node
1842 linkType: hard 1835 linkType: hard
1843 1836
1844"@mui/styled-engine@npm:^5.10.1": 1837"@mui/styled-engine@npm:^5.10.2":
1845 version: 5.10.1 1838 version: 5.10.2
1846 resolution: "@mui/styled-engine@npm:5.10.1" 1839 resolution: "@mui/styled-engine@npm:5.10.2"
1847 dependencies: 1840 dependencies:
1848 "@babel/runtime": ^7.17.2 1841 "@babel/runtime": ^7.17.2
1849 "@emotion/cache": ^11.9.3 1842 "@emotion/cache": ^11.10.1
1850 csstype: ^3.1.0 1843 csstype: ^3.1.0
1851 prop-types: ^15.8.1 1844 prop-types: ^15.8.1
1852 peerDependencies: 1845 peerDependencies:
@@ -1858,17 +1851,17 @@ __metadata:
1858 optional: true 1851 optional: true
1859 "@emotion/styled": 1852 "@emotion/styled":
1860 optional: true 1853 optional: true
1861 checksum: 27c4003bdb1f8a76f30ed6c458789631a93efee832631edeb63940ca3ede70f8aee8e39987b1da1b8717fcbd907d00554d2b5fecc83266eee70c3637c9b66712 1854 checksum: 337455da69990ef83b10f7e0ee14720b991ae2dca9c616664931edbc17f209f3f9fc0abcad97c01d70cd72afbb0760f44c9aefe27c458fb6104d4a3f0078b812
1862 languageName: node 1855 languageName: node
1863 linkType: hard 1856 linkType: hard
1864 1857
1865"@mui/system@npm:^5.10.1": 1858"@mui/system@npm:^5.10.2":
1866 version: 5.10.1 1859 version: 5.10.2
1867 resolution: "@mui/system@npm:5.10.1" 1860 resolution: "@mui/system@npm:5.10.2"
1868 dependencies: 1861 dependencies:
1869 "@babel/runtime": ^7.17.2 1862 "@babel/runtime": ^7.17.2
1870 "@mui/private-theming": ^5.9.3 1863 "@mui/private-theming": ^5.9.3
1871 "@mui/styled-engine": ^5.10.1 1864 "@mui/styled-engine": ^5.10.2
1872 "@mui/types": ^7.1.5 1865 "@mui/types": ^7.1.5
1873 "@mui/utils": ^5.9.3 1866 "@mui/utils": ^5.9.3
1874 clsx: ^1.2.1 1867 clsx: ^1.2.1
@@ -1886,7 +1879,7 @@ __metadata:
1886 optional: true 1879 optional: true
1887 "@types/react": 1880 "@types/react":
1888 optional: true 1881 optional: true
1889 checksum: 0acb163dec856af3813ff043b4d8441d44431add7cb234cf16d386b2b70e30cba6261b51ac936f886d746f4b3ab3475b58e33e09f76cd92a6f6aadd61dca3927 1882 checksum: 07bdac55e8a29d5397579025a47258b885bd54b60cd9f7bc7214526960ba076ab975ba0a217b2420fd48960aaaec4bceea37ae404f7452cc2048c677fc91790a
1890 languageName: node 1883 languageName: node
1891 linkType: hard 1884 linkType: hard
1892 1885
@@ -1998,7 +1991,6 @@ __metadata:
1998 "@codemirror/view": ^6.2.0 1991 "@codemirror/view": ^6.2.0
1999 "@emotion/react": ^11.10.0 1992 "@emotion/react": ^11.10.0
2000 "@emotion/styled": ^11.10.0 1993 "@emotion/styled": ^11.10.0
2001 "@fontsource/inter": ^4.5.12
2002 "@fontsource/jetbrains-mono": ^4.5.10 1994 "@fontsource/jetbrains-mono": ^4.5.10
2003 "@fontsource/roboto": ^4.5.8 1995 "@fontsource/roboto": ^4.5.8
2004 "@lezer/common": ^1.0.0 1996 "@lezer/common": ^1.0.0
@@ -2006,26 +1998,27 @@ __metadata:
2006 "@lezer/highlight": ^1.0.0 1998 "@lezer/highlight": ^1.0.0
2007 "@lezer/lr": ^1.2.3 1999 "@lezer/lr": ^1.2.3
2008 "@material-icons/svg": ^1.0.32 2000 "@material-icons/svg": ^1.0.32
2009 "@mui/icons-material": 5.8.4 2001 "@mui/icons-material": 5.10.2
2010 "@mui/material": 5.10.1 2002 "@mui/material": 5.10.2
2011 "@types/eslint": ^8.4.6 2003 "@types/eslint": ^8.4.6
2012 "@types/html-minifier-terser": ^7.0.0 2004 "@types/html-minifier-terser": ^7.0.0
2013 "@types/lodash-es": ^4.17.6 2005 "@types/lodash-es": ^4.17.6
2014 "@types/node": ^18.7.8 2006 "@types/node": ^18.7.13
2015 "@types/prettier": ^2.7.0 2007 "@types/prettier": ^2.7.0
2016 "@types/react": ^18.0.17 2008 "@types/react": ^18.0.17
2017 "@types/react-dom": ^18.0.6 2009 "@types/react-dom": ^18.0.6
2018 "@typescript-eslint/eslint-plugin": ^5.33.1 2010 "@typescript-eslint/eslint-plugin": ^5.35.1
2019 "@typescript-eslint/parser": ^5.33.1 2011 "@typescript-eslint/parser": ^5.35.1
2020 "@vitejs/plugin-react": ^2.0.1 2012 "@vitejs/plugin-react": ^2.0.1
2021 ansi-styles: ^6.1.0 2013 ansi-styles: ^6.1.0
2014 async-mutex: ^0.3.2
2022 cross-env: ^7.0.3 2015 cross-env: ^7.0.3
2023 escape-string-regexp: ^5.0.0 2016 escape-string-regexp: ^5.0.0
2024 eslint: ^8.22.0 2017 eslint: ^8.22.0
2025 eslint-config-airbnb: ^19.0.4 2018 eslint-config-airbnb: ^19.0.4
2026 eslint-config-airbnb-typescript: ^17.0.0 2019 eslint-config-airbnb-typescript: ^17.0.0
2027 eslint-config-prettier: ^8.5.0 2020 eslint-config-prettier: ^8.5.0
2028 eslint-import-resolver-typescript: ^3.4.2 2021 eslint-import-resolver-typescript: ^3.5.0
2029 eslint-plugin-import: ^2.26.0 2022 eslint-plugin-import: ^2.26.0
2030 eslint-plugin-jsx-a11y: ^6.6.1 2023 eslint-plugin-jsx-a11y: ^6.6.1
2031 eslint-plugin-prettier: ^4.2.1 2024 eslint-plugin-prettier: ^4.2.1
@@ -2044,7 +2037,7 @@ __metadata:
2044 react-dom: ^18.2.0 2037 react-dom: ^18.2.0
2045 typescript: ~4.7.4 2038 typescript: ~4.7.4
2046 vite: ^3.0.9 2039 vite: ^3.0.9
2047 vite-plugin-inject-preload: ^1.0.1 2040 vite-plugin-inject-preload: ^1.1.0
2048 vite-plugin-pwa: ^0.12.3 2041 vite-plugin-pwa: ^0.12.3
2049 workbox-window: ^6.5.4 2042 workbox-window: ^6.5.4
2050 zod: ^3.18.0 2043 zod: ^3.18.0
@@ -2198,10 +2191,10 @@ __metadata:
2198 languageName: node 2191 languageName: node
2199 linkType: hard 2192 linkType: hard
2200 2193
2201"@types/node@npm:*, @types/node@npm:^18.7.8": 2194"@types/node@npm:*, @types/node@npm:^18.7.13":
2202 version: 18.7.8 2195 version: 18.7.13
2203 resolution: "@types/node@npm:18.7.8" 2196 resolution: "@types/node@npm:18.7.13"
2204 checksum: e0125efefa896083c05f549d93166109959ffdd68cb626aad0d660c0ce9de888fe405b4763b4a3c3e0968560409c272413e0ad07204522543c688e162a617ecb 2197 checksum: 45431e7e89ecaf85c7d2c180d801c132a7c59e2f8ad578726b6d71cc74e3267c18f9ccdcad738bc0479790c078f0c79efb0e58da2c6be535c15995dbb19050c9
2205 languageName: node 2198 languageName: node
2206 linkType: hard 2199 linkType: hard
2207 2200
@@ -2287,13 +2280,13 @@ __metadata:
2287 languageName: node 2280 languageName: node
2288 linkType: hard 2281 linkType: hard
2289 2282
2290"@typescript-eslint/eslint-plugin@npm:^5.33.1": 2283"@typescript-eslint/eslint-plugin@npm:^5.35.1":
2291 version: 5.33.1 2284 version: 5.35.1
2292 resolution: "@typescript-eslint/eslint-plugin@npm:5.33.1" 2285 resolution: "@typescript-eslint/eslint-plugin@npm:5.35.1"
2293 dependencies: 2286 dependencies:
2294 "@typescript-eslint/scope-manager": 5.33.1 2287 "@typescript-eslint/scope-manager": 5.35.1
2295 "@typescript-eslint/type-utils": 5.33.1 2288 "@typescript-eslint/type-utils": 5.35.1
2296 "@typescript-eslint/utils": 5.33.1 2289 "@typescript-eslint/utils": 5.35.1
2297 debug: ^4.3.4 2290 debug: ^4.3.4
2298 functional-red-black-tree: ^1.0.1 2291 functional-red-black-tree: ^1.0.1
2299 ignore: ^5.2.0 2292 ignore: ^5.2.0
@@ -2306,42 +2299,42 @@ __metadata:
2306 peerDependenciesMeta: 2299 peerDependenciesMeta:
2307 typescript: 2300 typescript:
2308 optional: true 2301 optional: true
2309 checksum: d9b6b038f70e4959ad211c84f50a38de2d00b54f0636ad76eea414fb070fa616933690da80de6668e62c8fbbeb227086322001b7d7ad1924421a232547c97936 2302 checksum: 073f4dffd863881f1c87e1c217ac13bda44aaa2db12ef260032b5e8eb6ffd6b9cf6f62c85132dbf84152f353c435c66dd4f75c3bcb86eb23e926737aa4fb66fa
2310 languageName: node 2303 languageName: node
2311 linkType: hard 2304 linkType: hard
2312 2305
2313"@typescript-eslint/parser@npm:^5.33.1": 2306"@typescript-eslint/parser@npm:^5.35.1":
2314 version: 5.33.1 2307 version: 5.35.1
2315 resolution: "@typescript-eslint/parser@npm:5.33.1" 2308 resolution: "@typescript-eslint/parser@npm:5.35.1"
2316 dependencies: 2309 dependencies:
2317 "@typescript-eslint/scope-manager": 5.33.1 2310 "@typescript-eslint/scope-manager": 5.35.1
2318 "@typescript-eslint/types": 5.33.1 2311 "@typescript-eslint/types": 5.35.1
2319 "@typescript-eslint/typescript-estree": 5.33.1 2312 "@typescript-eslint/typescript-estree": 5.35.1
2320 debug: ^4.3.4 2313 debug: ^4.3.4
2321 peerDependencies: 2314 peerDependencies:
2322 eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 2315 eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
2323 peerDependenciesMeta: 2316 peerDependenciesMeta:
2324 typescript: 2317 typescript:
2325 optional: true 2318 optional: true
2326 checksum: fb3a4e000ce6d9583656fc3b3fb80f127a0ec1b7c3872ea469164516d993a588859ded4ec1338e6bbf2151168380d8aa29ec31027af23b50f5107949f8e7b438 2319 checksum: 57ea1a1da60b370f8d5c11c86155f7339359a90f2c59e34c89f626f1a79cb440248f07bd307a27ebbbcc997d2731cb9754cdbc37639770940521a938dd89870c
2327 languageName: node 2320 languageName: node
2328 linkType: hard 2321 linkType: hard
2329 2322
2330"@typescript-eslint/scope-manager@npm:5.33.1": 2323"@typescript-eslint/scope-manager@npm:5.35.1":
2331 version: 5.33.1 2324 version: 5.35.1
2332 resolution: "@typescript-eslint/scope-manager@npm:5.33.1" 2325 resolution: "@typescript-eslint/scope-manager@npm:5.35.1"
2333 dependencies: 2326 dependencies:
2334 "@typescript-eslint/types": 5.33.1 2327 "@typescript-eslint/types": 5.35.1
2335 "@typescript-eslint/visitor-keys": 5.33.1 2328 "@typescript-eslint/visitor-keys": 5.35.1
2336 checksum: b9918d8320ea59081d19070ce952b56984e72fb2c113215e5e6a0f97deac9aae5aa67ec7a07cddb010c0f75cdf8df096ab45e9241e4b7b611acfa6d4cdfb6516 2329 checksum: 5a969a081309bac5962f99ee6dfdfd9c68ea677bc79d9796592dce82a36217f67aa55c7bf421b2c97b46c5149d6a9401bb4c57829595e8c19f47cfa9e8c2dd86
2337 languageName: node 2330 languageName: node
2338 linkType: hard 2331 linkType: hard
2339 2332
2340"@typescript-eslint/type-utils@npm:5.33.1": 2333"@typescript-eslint/type-utils@npm:5.35.1":
2341 version: 5.33.1 2334 version: 5.35.1
2342 resolution: "@typescript-eslint/type-utils@npm:5.33.1" 2335 resolution: "@typescript-eslint/type-utils@npm:5.35.1"
2343 dependencies: 2336 dependencies:
2344 "@typescript-eslint/utils": 5.33.1 2337 "@typescript-eslint/utils": 5.35.1
2345 debug: ^4.3.4 2338 debug: ^4.3.4
2346 tsutils: ^3.21.0 2339 tsutils: ^3.21.0
2347 peerDependencies: 2340 peerDependencies:
@@ -2349,23 +2342,23 @@ __metadata:
2349 peerDependenciesMeta: 2342 peerDependenciesMeta:
2350 typescript: 2343 typescript:
2351 optional: true 2344 optional: true
2352 checksum: ddf88835bc87b3ad946aaeb29b770a49a8e1c3c5e294ee9cb93b1936f432a1016efb97803f197eea1be61545cbc79b5526cc05e9339ca9beada22fc83801ddea 2345 checksum: af317ba156f2767f76a7f97193873a00468370e157fdcc6ac19f664bc6c4c0a6836bd25028d17fdd54d339b6842fda68b82f1ce4142a222de6953625ea6c0a9c
2353 languageName: node 2346 languageName: node
2354 linkType: hard 2347 linkType: hard
2355 2348
2356"@typescript-eslint/types@npm:5.33.1": 2349"@typescript-eslint/types@npm:5.35.1":
2357 version: 5.33.1 2350 version: 5.35.1
2358 resolution: "@typescript-eslint/types@npm:5.33.1" 2351 resolution: "@typescript-eslint/types@npm:5.35.1"
2359 checksum: 122891bd4ab4b930b1d33f3ce43a010825c1e61b9879520a0f3dc34cf92df71e2a873410845ab8d746333511c455c115eaafdec149298a161cef713829dfdb77 2352 checksum: a4e1001867f43f3364b109fc5a07b91ae7a34b78ab191c6c5c4695dac9bb2b80b0a602651c0b807c1c7c1fc3656d2bbd47c637afa08a09e7b1c39eae3c489e00
2360 languageName: node 2353 languageName: node
2361 linkType: hard 2354 linkType: hard
2362 2355
2363"@typescript-eslint/typescript-estree@npm:5.33.1": 2356"@typescript-eslint/typescript-estree@npm:5.35.1":
2364 version: 5.33.1 2357 version: 5.35.1
2365 resolution: "@typescript-eslint/typescript-estree@npm:5.33.1" 2358 resolution: "@typescript-eslint/typescript-estree@npm:5.35.1"
2366 dependencies: 2359 dependencies:
2367 "@typescript-eslint/types": 5.33.1 2360 "@typescript-eslint/types": 5.35.1
2368 "@typescript-eslint/visitor-keys": 5.33.1 2361 "@typescript-eslint/visitor-keys": 5.35.1
2369 debug: ^4.3.4 2362 debug: ^4.3.4
2370 globby: ^11.1.0 2363 globby: ^11.1.0
2371 is-glob: ^4.0.3 2364 is-glob: ^4.0.3
@@ -2374,33 +2367,33 @@ __metadata:
2374 peerDependenciesMeta: 2367 peerDependenciesMeta:
2375 typescript: 2368 typescript:
2376 optional: true 2369 optional: true
2377 checksum: 1418e409b141c2f012bc2dd5c40d95dfd8aa572dd3e9523ed23e4371e4459d10ecd074fda75dc770ce980686b25ffc44725eebf165c494818ed4131d1ac0239f 2370 checksum: a917ca4753a3f92c8d8555c96f5414383a9742761625476fa36a019401543aa74996159afa0f7fc7fae05fe0f904e3c6f4153a55412070c8a94e8171e81084c7
2378 languageName: node 2371 languageName: node
2379 linkType: hard 2372 linkType: hard
2380 2373
2381"@typescript-eslint/utils@npm:5.33.1": 2374"@typescript-eslint/utils@npm:5.35.1":
2382 version: 5.33.1 2375 version: 5.35.1
2383 resolution: "@typescript-eslint/utils@npm:5.33.1" 2376 resolution: "@typescript-eslint/utils@npm:5.35.1"
2384 dependencies: 2377 dependencies:
2385 "@types/json-schema": ^7.0.9 2378 "@types/json-schema": ^7.0.9
2386 "@typescript-eslint/scope-manager": 5.33.1 2379 "@typescript-eslint/scope-manager": 5.35.1
2387 "@typescript-eslint/types": 5.33.1 2380 "@typescript-eslint/types": 5.35.1
2388 "@typescript-eslint/typescript-estree": 5.33.1 2381 "@typescript-eslint/typescript-estree": 5.35.1
2389 eslint-scope: ^5.1.1 2382 eslint-scope: ^5.1.1
2390 eslint-utils: ^3.0.0 2383 eslint-utils: ^3.0.0
2391 peerDependencies: 2384 peerDependencies:
2392 eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 2385 eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
2393 checksum: c550504d62fc72f29bf3d7a651bd3a81f49fb1fccaf47583721c2ab1abd2ef78a1e4bc392cb4be4a61a45a4f24fc14a59d67b98aac8a16a207a7cace86538cab 2386 checksum: 2b04092583c3139dd090727c24fb9d7fdb1fb9f20f2e3f0141cab5b98b6a1934b0fc8cab948f7faae55588385b0f1fb7bbf91f52c705ce4528036a527c3119c6
2394 languageName: node 2387 languageName: node
2395 linkType: hard 2388 linkType: hard
2396 2389
2397"@typescript-eslint/visitor-keys@npm:5.33.1": 2390"@typescript-eslint/visitor-keys@npm:5.35.1":
2398 version: 5.33.1 2391 version: 5.35.1
2399 resolution: "@typescript-eslint/visitor-keys@npm:5.33.1" 2392 resolution: "@typescript-eslint/visitor-keys@npm:5.35.1"
2400 dependencies: 2393 dependencies:
2401 "@typescript-eslint/types": 5.33.1 2394 "@typescript-eslint/types": 5.35.1
2402 eslint-visitor-keys: ^3.3.0 2395 eslint-visitor-keys: ^3.3.0
2403 checksum: 0d32a433450f61e97b5fa6b1e167f06ed395c200b16b4dbd4490a1c4941de420689b622f8a2486f5398806fb24f57b9fab901b4cbc8fdb8853f568264b3a182a 2396 checksum: ef3c8377aac89935b5cc2fcf37bb3e42aa5f98848e7c22bdcbe5bb06c0fe8a1373a6897fd21109be8929b4708ad06c8874d2ef7bba17ff64911964203457330d
2404 languageName: node 2397 languageName: node
2405 linkType: hard 2398 linkType: hard
2406 2399
@@ -2616,6 +2609,15 @@ __metadata:
2616 languageName: node 2609 languageName: node
2617 linkType: hard 2610 linkType: hard
2618 2611
2612"async-mutex@npm:^0.3.2":
2613 version: 0.3.2
2614 resolution: "async-mutex@npm:0.3.2"
2615 dependencies:
2616 tslib: ^2.3.1
2617 checksum: 620b771dfdea1cad0a6b712915c31a1e3ca880a8cf1eae92b4590f435995e0260929c6ebaae0b9126b1456790ea498064b5bb9a506948cda760f48d3d0dcc4c8
2618 languageName: node
2619 linkType: hard
2620
2619"async@npm:^3.2.3": 2621"async@npm:^3.2.3":
2620 version: 3.2.4 2622 version: 3.2.4
2621 resolution: "async@npm:3.2.4" 2623 resolution: "async@npm:3.2.4"
@@ -3610,21 +3612,21 @@ __metadata:
3610 languageName: node 3612 languageName: node
3611 linkType: hard 3613 linkType: hard
3612 3614
3613"eslint-import-resolver-typescript@npm:^3.4.2": 3615"eslint-import-resolver-typescript@npm:^3.5.0":
3614 version: 3.4.2 3616 version: 3.5.0
3615 resolution: "eslint-import-resolver-typescript@npm:3.4.2" 3617 resolution: "eslint-import-resolver-typescript@npm:3.5.0"
3616 dependencies: 3618 dependencies:
3617 debug: ^4.3.4 3619 debug: ^4.3.4
3618 enhanced-resolve: ^5.10.0 3620 enhanced-resolve: ^5.10.0
3619 get-tsconfig: ^4.2.0 3621 get-tsconfig: ^4.2.0
3620 globby: ^13.1.2 3622 globby: ^13.1.2
3621 is-core-module: ^2.9.0 3623 is-core-module: ^2.10.0
3622 is-glob: ^4.0.3 3624 is-glob: ^4.0.3
3623 synckit: ^0.8.3 3625 synckit: ^0.8.3
3624 peerDependencies: 3626 peerDependencies:
3625 eslint: "*" 3627 eslint: "*"
3626 eslint-plugin-import: "*" 3628 eslint-plugin-import: "*"
3627 checksum: e54d8c02542cc85fc9703a064111a25585a7189b61445a209792ae50b718d273e5522be44c61f149f59292570eef6a08036d988c8477da9f6d8f3c6177801319 3629 checksum: 9719d1f68b7bb0eaf8939cff2d3b02b526949f73db744877de781640650dd4d0a17d934222b9ac69e27d9f363ee4569c1aa1a2a2aab6500257517f9bf7d25976
3628 languageName: node 3630 languageName: node
3629 linkType: hard 3631 linkType: hard
3630 3632
@@ -4492,12 +4494,12 @@ __metadata:
4492 languageName: node 4494 languageName: node
4493 linkType: hard 4495 linkType: hard
4494 4496
4495"is-core-module@npm:^2.2.0, is-core-module@npm:^2.8.1, is-core-module@npm:^2.9.0": 4497"is-core-module@npm:^2.10.0, is-core-module@npm:^2.2.0, is-core-module@npm:^2.8.1, is-core-module@npm:^2.9.0":
4496 version: 2.9.0 4498 version: 2.10.0
4497 resolution: "is-core-module@npm:2.9.0" 4499 resolution: "is-core-module@npm:2.10.0"
4498 dependencies: 4500 dependencies:
4499 has: ^1.0.3 4501 has: ^1.0.3
4500 checksum: b27034318b4b462f1c8f1dfb1b32baecd651d891a4e2d1922135daeff4141dfced2b82b07aef83ef54275c4a3526aa38da859223664d0868ca24182badb784ce 4502 checksum: 0f3f77811f430af3256fa7bbc806f9639534b140f8ee69476f632c3e1eb4e28a38be0b9d1b8ecf596179c841b53576129279df95e7051d694dac4ceb6f967593
4501 languageName: node 4503 languageName: node
4502 linkType: hard 4504 linkType: hard
4503 4505
@@ -6410,7 +6412,7 @@ __metadata:
6410 languageName: node 6412 languageName: node
6411 linkType: hard 6413 linkType: hard
6412 6414
6413"tslib@npm:^2.0.3, tslib@npm:^2.4.0": 6415"tslib@npm:^2.0.3, tslib@npm:^2.3.1, tslib@npm:^2.4.0":
6414 version: 2.4.0 6416 version: 2.4.0
6415 resolution: "tslib@npm:2.4.0" 6417 resolution: "tslib@npm:2.4.0"
6416 checksum: 8c4aa6a3c5a754bf76aefc38026134180c053b7bd2f81338cb5e5ebf96fefa0f417bff221592bf801077f5bf990562f6264fecbc42cd3309b33872cb6fc3b113 6418 checksum: 8c4aa6a3c5a754bf76aefc38026134180c053b7bd2f81338cb5e5ebf96fefa0f417bff221592bf801077f5bf990562f6264fecbc42cd3309b33872cb6fc3b113
@@ -6592,14 +6594,14 @@ __metadata:
6592 languageName: node 6594 languageName: node
6593 linkType: hard 6595 linkType: hard
6594 6596
6595"vite-plugin-inject-preload@npm:^1.0.1": 6597"vite-plugin-inject-preload@npm:^1.1.0":
6596 version: 1.0.1 6598 version: 1.1.0
6597 resolution: "vite-plugin-inject-preload@npm:1.0.1" 6599 resolution: "vite-plugin-inject-preload@npm:1.1.0"
6598 dependencies: 6600 dependencies:
6599 mime-types: ^2.1.35 6601 mime-types: ^2.1.35
6600 peerDependencies: 6602 peerDependencies:
6601 vite: ^2.9.0 || ^3.0.0-0 6603 vite: ^2.9.0 || ^3.0.0-0
6602 checksum: 0504be885942752933240f99192b677c27f0f56297546e9db289312784f71f60fa30dff896f89677af7a2c84eed10bc8af33c41bd284c63a3b59379030f383fd 6604 checksum: 51123f1e5bb5cf1d7830e964e0b81b4d64e92a1176b86fea6ec33ea28601673a2315a63a66fc8aa79f6dc3e8c18a847f5dfabbefee19db507aa548318f12913e
6603 languageName: node 6605 languageName: node
6604 linkType: hard 6606 linkType: hard
6605 6607