@tolgee/core 5.33.2 → 5.33.3-prerelease.2efc0e6b.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/tolgee.cjs.js +227 -186
  2. package/dist/tolgee.cjs.js.map +1 -1
  3. package/dist/tolgee.cjs.min.js +1 -1
  4. package/dist/tolgee.cjs.min.js.map +1 -1
  5. package/dist/tolgee.esm.js +227 -186
  6. package/dist/tolgee.esm.js.map +1 -1
  7. package/dist/tolgee.esm.min.js +1 -1
  8. package/dist/tolgee.esm.min.js.map +1 -1
  9. package/dist/tolgee.esm.min.mjs +1 -1
  10. package/dist/tolgee.esm.min.mjs.map +1 -1
  11. package/dist/tolgee.esm.mjs +227 -186
  12. package/dist/tolgee.esm.mjs.map +1 -1
  13. package/dist/tolgee.umd.js +227 -186
  14. package/dist/tolgee.umd.js.map +1 -1
  15. package/dist/tolgee.umd.min.js +1 -1
  16. package/dist/tolgee.umd.min.js.map +1 -1
  17. package/lib/Controller/Cache/Cache.d.ts +8 -12
  18. package/lib/Controller/Cache/helpers.d.ts +3 -2
  19. package/lib/Controller/Controller.d.ts +57 -26
  20. package/lib/Controller/Events/EventEmitter.d.ts +5 -5
  21. package/lib/Controller/Events/EventEmitterCombined.d.ts +6 -0
  22. package/lib/Controller/Events/Events.d.ts +39 -12
  23. package/lib/Controller/State/State.d.ts +10 -4
  24. package/lib/Controller/State/initState.d.ts +29 -7
  25. package/lib/TolgeeCore.d.ts +25 -28
  26. package/lib/helpers.d.ts +2 -2
  27. package/lib/types/cache.d.ts +19 -1
  28. package/lib/types/events.d.ts +24 -23
  29. package/lib/types/index.d.ts +1 -1
  30. package/package.json +2 -2
  31. package/src/Controller/Cache/Cache.test.ts +189 -0
  32. package/src/Controller/Cache/Cache.ts +118 -57
  33. package/src/Controller/Cache/helpers.ts +12 -3
  34. package/src/Controller/Controller.ts +81 -27
  35. package/src/Controller/Events/EventEmitter.ts +23 -15
  36. package/src/Controller/Events/EventEmitterCombined.ts +55 -0
  37. package/src/Controller/Events/Events.ts +31 -23
  38. package/src/Controller/Plugins/Plugins.ts +5 -0
  39. package/src/Controller/State/State.ts +26 -6
  40. package/src/Controller/State/initState.ts +35 -7
  41. package/src/TolgeeCore.ts +14 -17
  42. package/src/__test/cache.test.ts +2 -26
  43. package/src/__test/client.test.ts +3 -3
  44. package/src/__test/events.test.ts +40 -13
  45. package/src/__test/load.matrix.test.ts +123 -0
  46. package/src/__test/load.required.test.ts +71 -0
  47. package/src/__test/namespaces.required.test.ts +52 -0
  48. package/src/__test/options.test.ts +1 -1
  49. package/src/__test/testTools.ts +39 -0
  50. package/src/helpers.ts +2 -1
  51. package/src/types/cache.ts +24 -1
  52. package/src/types/events.ts +33 -24
  53. package/src/types/index.ts +1 -0
  54. package/lib/Controller/Events/EventEmitterSelective.d.ts +0 -7
  55. package/src/Controller/Events/EventEmitterSelective.test.ts +0 -110
  56. package/src/Controller/Events/EventEmitterSelective.ts +0 -133
@@ -2,14 +2,16 @@ import {
2
2
  CacheDescriptor,
3
3
  CacheDescriptorInternal,
4
4
  NsFallback,
5
- TranslationsFlat,
6
5
  TranslationValue,
7
6
  TreeTranslationsData,
8
7
  BackendGetRecordInternal,
9
8
  RecordFetchError,
9
+ LoadOptions,
10
+ CacheInternalRecord,
11
+ TranslationsFlat,
10
12
  } from '../../types';
11
13
  import { getFallbackArray, isPromise, unique } from '../../helpers';
12
- import { TolgeeStaticData } from '../State/initState';
14
+ import { TolgeeStaticData, TolgeeStaticDataProp } from '../State/initState';
13
15
  import { ValueObserverInstance } from '../ValueObserver';
14
16
 
15
17
  import { decodeCacheKey, encodeCacheKey, flattenTranslations } from './helpers';
@@ -33,6 +35,7 @@ export function Cache(
33
35
  backendGetDevRecord: BackendGetRecordInternal,
34
36
  withDefaultNs: (descriptor: CacheDescriptor) => CacheDescriptorInternal,
35
37
  isInitialLoading: () => boolean,
38
+ isCacheDisabled: () => boolean,
36
39
  fetchingObserver: ValueObserverInstance<boolean>,
37
40
  loadingObserver: ValueObserverInstance<boolean>
38
41
  ) {
@@ -43,7 +46,7 @@ export function Cache(
43
46
 
44
47
  function addRecordInternal(
45
48
  descriptor: CacheDescriptorInternal,
46
- data: TreeTranslationsData,
49
+ data: TranslationsFlat,
47
50
  recordVersion: number
48
51
  ) {
49
52
  const cacheKey = encodeCacheKey(descriptor);
@@ -51,7 +54,7 @@ export function Cache(
51
54
  data: flattenTranslations(data),
52
55
  version: recordVersion,
53
56
  });
54
- events.onCacheChange.emit(descriptor);
57
+ events.onCacheChange.emit(decodeCacheKey(cacheKey));
55
58
  }
56
59
 
57
60
  /**
@@ -108,15 +111,23 @@ export function Cache(
108
111
  }
109
112
 
110
113
  const self = Object.freeze({
111
- addStaticData(data: TolgeeStaticData | undefined) {
112
- if (data) {
114
+ addStaticData(data: TolgeeStaticDataProp | undefined) {
115
+ if (Array.isArray(data)) {
116
+ for (const record of data) {
117
+ const key = encodeCacheKey(record);
118
+ const existing = cache.get(key);
119
+ if (!existing || existing.version === 0) {
120
+ addRecordInternal(record, flattenTranslations(record.data), 0);
121
+ }
122
+ }
123
+ } else if (data) {
113
124
  staticData = { ...staticData, ...data };
114
125
  Object.entries(data).forEach(([key, value]) => {
115
126
  if (typeof value !== 'function') {
116
127
  const descriptor = decodeCacheKey(key);
117
128
  const existing = cache.get(key);
118
129
  if (!existing || existing.version === 0) {
119
- addRecordInternal(descriptor, value, 0);
130
+ addRecordInternal(descriptor, flattenTranslations(value), 0);
120
131
  }
121
132
  }
122
133
  });
@@ -129,7 +140,7 @@ export function Cache(
129
140
  },
130
141
 
131
142
  addRecord(descriptor: CacheDescriptorInternal, data: TreeTranslationsData) {
132
- addRecordInternal(descriptor, data, version);
143
+ addRecordInternal(descriptor, flattenTranslations(data), version);
133
144
  },
134
145
 
135
146
  exists(descriptor: CacheDescriptorInternal, strict = false) {
@@ -140,20 +151,34 @@ export function Cache(
140
151
  return Boolean(record);
141
152
  },
142
153
 
143
- getRecord(descriptor: CacheDescriptor) {
144
- return cache.get(encodeCacheKey(withDefaultNs(descriptor)))?.data;
154
+ getRecord(descriptor: CacheDescriptor): CacheInternalRecord | undefined {
155
+ const descriptorWithNs = withDefaultNs(descriptor);
156
+ const cacheKey = encodeCacheKey(descriptorWithNs);
157
+ const cacheRecord = cache.get(cacheKey);
158
+ if (!cacheRecord) {
159
+ return undefined;
160
+ }
161
+ return {
162
+ ...descriptorWithNs,
163
+ cacheKey,
164
+ data: cacheRecord.data,
165
+ };
166
+ },
167
+
168
+ getAllRecords() {
169
+ const entries = Array.from(cache.entries());
170
+ return entries.map(([key]) => self.getRecord(decodeCacheKey(key)));
145
171
  },
146
172
 
147
173
  getTranslation(descriptor: CacheDescriptorInternal, key: string) {
148
- return cache.get(encodeCacheKey(descriptor))?.data.get(key);
174
+ return cache.get(encodeCacheKey(descriptor))?.data[key];
149
175
  },
150
176
 
151
177
  getTranslationNs(namespaces: string[], languages: string[], key: string) {
152
178
  for (const namespace of namespaces) {
153
179
  for (const language of languages) {
154
- const value = cache
155
- .get(encodeCacheKey({ language, namespace }))
156
- ?.data.get(key);
180
+ const value = cache.get(encodeCacheKey({ language, namespace }))
181
+ ?.data[key];
157
182
  if (value !== undefined && value !== null) {
158
183
  return [namespace];
159
184
  }
@@ -169,9 +194,8 @@ export function Cache(
169
194
  ) {
170
195
  for (const namespace of namespaces) {
171
196
  for (const language of languages) {
172
- const value = cache
173
- .get(encodeCacheKey({ language, namespace }))
174
- ?.data.get(key);
197
+ const value = cache.get(encodeCacheKey({ language, namespace }))
198
+ ?.data[key];
175
199
  if (value !== undefined && value !== null) {
176
200
  return value;
177
201
  }
@@ -186,8 +210,10 @@ export function Cache(
186
210
  value: TranslationValue
187
211
  ) {
188
212
  const record = cache.get(encodeCacheKey(descriptor))?.data;
189
- record?.set(key, value);
190
- events.onCacheChange.emit({ ...descriptor, key });
213
+ if (record?.[key]) {
214
+ record[key] = value;
215
+ events.onCacheChange.emit({ ...descriptor, key });
216
+ }
191
217
  },
192
218
 
193
219
  isFetching(ns?: NsFallback) {
@@ -206,84 +232,119 @@ export function Cache(
206
232
  );
207
233
  },
208
234
 
209
- isLoading(language: string | undefined, ns?: NsFallback) {
235
+ isLoading(language: string, ns?: NsFallback) {
210
236
  const namespaces = getFallbackArray(ns);
211
237
 
238
+ if (isInitialLoading()) {
239
+ return true;
240
+ }
241
+
242
+ const pendingCacheKeys = Array.from(asyncRequests.keys());
243
+
212
244
  return Boolean(
213
- isInitialLoading() ||
214
- Array.from(asyncRequests.keys()).find((key) => {
215
- const descriptor = decodeCacheKey(key);
216
- return (
217
- (!namespaces.length ||
218
- namespaces.includes(descriptor.namespace)) &&
219
- !self.exists({
220
- namespace: descriptor.namespace,
221
- language: language!,
222
- })
223
- );
224
- })
245
+ pendingCacheKeys.find((key) => {
246
+ const descriptor = decodeCacheKey(key);
247
+ return (
248
+ (!namespaces.length || namespaces.includes(descriptor.namespace)) &&
249
+ !self.exists({
250
+ namespace: descriptor.namespace,
251
+ language: language,
252
+ })
253
+ );
254
+ })
225
255
  );
226
256
  },
227
257
 
228
- async loadRecords(descriptors: CacheDescriptor[], isDev: boolean) {
229
- const withPromises = descriptors.map((descriptor) => {
258
+ async loadRecords(
259
+ descriptors: CacheDescriptor[],
260
+ options?: LoadOptions
261
+ ): Promise<CacheInternalRecord[]> {
262
+ type WithPromise = {
263
+ new: boolean;
264
+ language: string;
265
+ namespace: string;
266
+ cacheKey: string;
267
+ promise?: Promise<TreeTranslationsData | undefined>;
268
+ data?: TranslationsFlat;
269
+ };
270
+
271
+ const withPromises: WithPromise[] = descriptors.map((descriptor) => {
230
272
  const keyObject = withDefaultNs(descriptor);
231
273
  const cacheKey = encodeCacheKey(keyObject);
274
+ if (options?.useCache) {
275
+ const exists = self.exists(keyObject, true);
276
+
277
+ if (exists) {
278
+ return {
279
+ ...keyObject,
280
+ new: false,
281
+ cacheKey,
282
+ data: self.getRecord(keyObject)!.data,
283
+ } as WithPromise;
284
+ }
285
+ }
286
+
232
287
  const existingPromise = asyncRequests.get(cacheKey);
233
288
 
234
289
  if (existingPromise) {
235
290
  return {
291
+ ...keyObject,
236
292
  new: false,
237
293
  promise: existingPromise,
238
- keyObject,
239
294
  cacheKey,
240
295
  };
241
296
  }
297
+
242
298
  const dataPromise =
243
- fetchData(keyObject, isDev) || Promise.resolve(undefined);
299
+ fetchData(keyObject, !options?.noDev) || Promise.resolve(undefined);
300
+
244
301
  asyncRequests.set(cacheKey, dataPromise);
302
+
245
303
  return {
304
+ ...keyObject,
246
305
  new: true,
247
306
  promise: dataPromise,
248
- keyObject,
249
307
  cacheKey,
250
308
  };
251
309
  });
252
310
  fetchingObserver.notify();
253
311
  loadingObserver.notify();
254
312
 
255
- const results = await Promise.all(withPromises.map((val) => val.promise));
313
+ const promisesToWait = withPromises
314
+ .map((val) => val.promise)
315
+ .filter(Boolean);
256
316
 
257
- withPromises.forEach((value, i) => {
258
- const promiseChanged =
259
- asyncRequests.get(value.cacheKey) !== value.promise;
317
+ const fetchedData = await Promise.all(promisesToWait);
318
+
319
+ withPromises.forEach((value) => {
320
+ if (value.promise) {
321
+ value.data = flattenTranslations(fetchedData[0] ?? {});
322
+ fetchedData.shift();
323
+ }
260
324
  // if promise has changed in between, it means cache been invalidated or
261
325
  // new data are being fetched
326
+ const promiseChanged =
327
+ asyncRequests.get(value.cacheKey) !== value.promise;
262
328
  if (value.new && !promiseChanged) {
263
329
  asyncRequests.delete(value.cacheKey);
264
- const data = results[i];
265
- if (data) {
266
- self.addRecord(value.keyObject, data);
267
- } else if (!self.getRecord(value.keyObject)) {
330
+ if (value.data) {
331
+ self.addRecord(value, value.data);
332
+ } else if (!self.getRecord(value)) {
268
333
  // if no data exist, put empty object
269
- self.addRecord(value.keyObject, {});
334
+ // so we know we don't have to fetch again
335
+ self.addRecord(value, {});
270
336
  }
271
337
  }
272
338
  });
273
339
  fetchingObserver.notify();
274
340
  loadingObserver.notify();
275
341
 
276
- return withPromises.map((val) => self.getRecord(val.keyObject)!);
277
- },
278
-
279
- getAllRecords() {
280
- const entries = Array.from(cache.entries());
281
- return entries.map(([key, entry]) => {
282
- return {
283
- ...decodeCacheKey(key),
284
- data: entry.data,
285
- };
286
- });
342
+ return withPromises.map((val) => ({
343
+ language: val.language,
344
+ namespace: val.namespace,
345
+ data: val.data ?? {},
346
+ cacheKey: val.cacheKey,
347
+ }));
287
348
  },
288
349
  });
289
350
 
@@ -1,6 +1,10 @@
1
- import { CacheDescriptorInternal, TreeTranslationsData } from '../../types';
1
+ import {
2
+ CacheDescriptorInternal,
3
+ TranslationsFlat,
4
+ TreeTranslationsData,
5
+ } from '../../types';
2
6
 
3
- export const flattenTranslations = (
7
+ export const flattenTranslationsToMap = (
4
8
  data: TreeTranslationsData
5
9
  ): Map<string, string> => {
6
10
  const result: Map<string, string> = new Map();
@@ -10,7 +14,7 @@ export const flattenTranslations = (
10
14
  return;
11
15
  }
12
16
  if (typeof value === 'object') {
13
- flattenTranslations(value).forEach((flatValue, flatKey) => {
17
+ flattenTranslationsToMap(value).forEach((flatValue, flatKey) => {
14
18
  result.set(key + '.' + flatKey, flatValue);
15
19
  });
16
20
  return;
@@ -20,6 +24,11 @@ export const flattenTranslations = (
20
24
  return result;
21
25
  };
22
26
 
27
+ export const flattenTranslations = (
28
+ data: TreeTranslationsData
29
+ ): TranslationsFlat => {
30
+ return Object.fromEntries(flattenTranslationsToMap(data).entries());
31
+ };
23
32
  export const decodeCacheKey = (key: string): CacheDescriptorInternal => {
24
33
  const [firstPart, ...rest] = key.split(':');
25
34
  // if namespaces contains ":" it won't get lost
@@ -6,9 +6,14 @@ import {
6
6
  TFnType,
7
7
  NsType,
8
8
  KeyAndNamespacesInternal,
9
+ CacheDescriptorInternal,
10
+ LoadOptions,
11
+ LoadRequiredOptions,
12
+ LoadMatrixOptions,
13
+ MatrixOptions,
9
14
  } from '../types';
10
15
  import { Cache } from './Cache/Cache';
11
- import { getFallbackArray } from '../helpers';
16
+ import { getFallbackArray, unique } from '../helpers';
12
17
  import { Plugins } from './Plugins/Plugins';
13
18
  import { ValueObserver } from './ValueObserver';
14
19
  import { State } from './State/State';
@@ -20,7 +25,7 @@ type StateServiceProps = {
20
25
  };
21
26
 
22
27
  export function Controller({ options }: StateServiceProps) {
23
- const events = Events(getFallbackNs, getDefaultNs);
28
+ const events = Events();
24
29
  const fetchingObserver = ValueObserver<boolean>(
25
30
  false,
26
31
  () => cache.isFetching(),
@@ -55,6 +60,7 @@ export function Controller({ options }: StateServiceProps) {
55
60
  pluginService.getBackendDevRecord,
56
61
  state.withDefaultNs,
57
62
  state.isInitialLoading,
63
+ state.isCacheDisabled,
58
64
  fetchingObserver,
59
65
  loadingObserver
60
66
  );
@@ -82,16 +88,16 @@ export function Controller({ options }: StateServiceProps) {
82
88
  // gets all namespaces where translation could be located
83
89
  // takes (ns|default, fallback ns)
84
90
  function getDefaultAndFallbackNs(ns?: NsType) {
85
- return [...getFallbackArray(getDefaultNs(ns)), ...getFallbackNs()];
91
+ return unique([...getFallbackArray(getDefaultNs(ns)), ...getFallbackNs()]);
86
92
  }
87
93
 
88
94
  // gets all namespaces which need to be loaded
89
95
  // takes (ns|default, initial ns, fallback ns, active ns)
90
- function getRequiredNamespaces(ns: NsFallback) {
91
- return [
96
+ function getRequiredNamespaces(ns?: NsFallback) {
97
+ return unique([
92
98
  ...getFallbackArray(ns ?? getDefaultNs()),
93
99
  ...state.getRequiredNamespaces(),
94
- ];
100
+ ]);
95
101
  }
96
102
 
97
103
  function changeTranslation(
@@ -114,25 +120,55 @@ export function Controller({ options }: StateServiceProps) {
114
120
  cache.addStaticData(state.getInitialOptions().staticData);
115
121
  }
116
122
 
117
- function getRequiredRecords(lang?: string, ns?: NsFallback) {
123
+ function getRequiredDescriptors(lang?: string, ns?: NsFallback) {
118
124
  const languages = state.getFallbackLangs(lang);
119
125
  const namespaces = getRequiredNamespaces(ns);
120
- const result: CacheDescriptor[] = [];
126
+ const result: CacheDescriptorInternal[] = [];
121
127
  languages.forEach((language) => {
122
128
  namespaces.forEach((namespace) => {
123
- if (!cache.exists({ language, namespace }, true)) {
124
- result.push({ language, namespace });
125
- }
129
+ result.push({ language, namespace });
126
130
  });
127
131
  });
128
132
  return result;
129
133
  }
130
134
 
131
- function loadRequiredRecords(lang?: string, ns?: NsFallback) {
132
- const descriptors = getRequiredRecords(lang, ns);
133
- if (descriptors.length) {
134
- return valueOrPromise(self.loadRecords(descriptors), () => {});
135
+ function getMissingDescriptors(lang?: string, ns?: NsFallback) {
136
+ return getRequiredDescriptors(lang, ns).filter(
137
+ (descriptor) => !cache.exists(descriptor, true)
138
+ );
139
+ }
140
+
141
+ function getMatrixRecords(options: MatrixOptions) {
142
+ let languages: string[] = [];
143
+ let namespaces: string[] = [];
144
+ if (Array.isArray(options.languages)) {
145
+ languages = options.languages;
146
+ } else if (options.languages === 'all') {
147
+ const availableLanguages = self.getAvailableLanguages();
148
+ if (!availableLanguages) {
149
+ throw new Error(missingOptionError('availableLanguages'));
150
+ }
151
+ languages = availableLanguages;
152
+ }
153
+
154
+ if (Array.isArray(options.namespaces)) {
155
+ namespaces = options.namespaces;
156
+ } else if (options.namespaces === 'all') {
157
+ const availableNs = self.getAvailableNs();
158
+ if (!availableNs) {
159
+ throw new Error(missingOptionError('availableNs'));
160
+ }
161
+ namespaces = availableNs;
135
162
  }
163
+
164
+ const records: CacheDescriptorInternal[] = [];
165
+
166
+ languages.forEach((language) => {
167
+ namespaces.forEach((namespace) => {
168
+ records.push({ language, namespace });
169
+ });
170
+ });
171
+ return records;
136
172
  }
137
173
 
138
174
  function getTranslationNs({ key, ns }: KeyAndNamespacesInternal) {
@@ -149,8 +185,13 @@ export function Controller({ options }: StateServiceProps) {
149
185
 
150
186
  function loadInitial() {
151
187
  const data = valueOrPromise(initializeLanguage(), () => {
152
- // fail if there is no language
153
- return loadRequiredRecords();
188
+ const missingDescriptors = getMissingDescriptors();
189
+ if (
190
+ missingDescriptors.length &&
191
+ state.getInitialOptions().autoLoadRequiredData
192
+ ) {
193
+ return cache.loadRecords(missingDescriptors, { useCache: true });
194
+ }
154
195
  });
155
196
 
156
197
  if (isPromise(data)) {
@@ -208,7 +249,7 @@ export function Controller({ options }: StateServiceProps) {
208
249
  getTranslationNs: getTranslationNs,
209
250
  getDefaultAndFallbackNs: getDefaultAndFallbackNs,
210
251
  findPositions: pluginService.findPositions,
211
- getRequiredRecords: getRequiredRecords,
252
+ getRequiredDescriptors: getRequiredDescriptors,
212
253
  async changeLanguage(language: string) {
213
254
  if (
214
255
  state.getPendingLanguage() === language &&
@@ -218,8 +259,10 @@ export function Controller({ options }: StateServiceProps) {
218
259
  }
219
260
  state.setPendingLanguage(language);
220
261
 
221
- if (state.isRunning()) {
222
- await loadRequiredRecords(language);
262
+ if (state.isRunning() && state.getInitialOptions().autoLoadRequiredData) {
263
+ await cache.loadRecords(getRequiredDescriptors(language), {
264
+ useCache: true,
265
+ });
223
266
  }
224
267
 
225
268
  if (language === state.getPendingLanguage()) {
@@ -235,16 +278,14 @@ export function Controller({ options }: StateServiceProps) {
235
278
  state.addActiveNs(ns);
236
279
  }
237
280
  if (state.isRunning()) {
238
- await loadRequiredRecords(undefined, ns);
281
+ await cache.loadRecords(getRequiredDescriptors(undefined, ns), {
282
+ useCache: true,
283
+ });
239
284
  }
240
285
  },
241
286
 
242
- loadRecords(descriptors: CacheDescriptor[]) {
243
- return cache.loadRecords(descriptors, self.isDev());
244
- },
245
-
246
- async loadRecord(descriptor: CacheDescriptor) {
247
- return (await self.loadRecords([descriptor]))[0];
287
+ async loadRecord(descriptor: CacheDescriptor, options?: LoadOptions) {
288
+ return (await self.loadRecords([descriptor], options))[0]?.data;
248
289
  },
249
290
 
250
291
  isLoading(ns?: NsFallback) {
@@ -282,6 +323,19 @@ export function Controller({ options }: StateServiceProps) {
282
323
  );
283
324
  },
284
325
 
326
+ async loadRequired(options?: LoadRequiredOptions) {
327
+ if (!options?.language) {
328
+ await initializeLanguage();
329
+ }
330
+ const requiredRecords = getRequiredDescriptors(options?.language);
331
+ return self.loadRecords(requiredRecords, options);
332
+ },
333
+
334
+ async loadMatrix(options: LoadMatrixOptions) {
335
+ const records = getMatrixRecords(options);
336
+ return self.loadRecords(records, options);
337
+ },
338
+
285
339
  run() {
286
340
  checkCorrectConfiguration();
287
341
  if (!state.isRunning()) {
@@ -1,13 +1,14 @@
1
- import { Subscription, Listener } from '../../types';
1
+ import { Subscription, Handler, ListenerEvent } from '../../types';
2
2
 
3
- export function EventEmitter<T>(
3
+ export const EventEmitter = <Event extends ListenerEvent<string, any>>(
4
+ type: Event['type'],
4
5
  isActive: () => boolean
5
- ): EventEmitterInstance<T> {
6
- let handlers: Listener<T>[] = [];
6
+ ): EventEmitterInstance<Event> => {
7
+ let handlers: Handler<Event>[] = [];
7
8
 
8
- return Object.freeze({
9
- listen(handler: Listener<T>): Subscription {
10
- const handlerWrapper: Listener<T> = (e) => {
9
+ return {
10
+ listen(handler: (e: Event) => void): Subscription {
11
+ const handlerWrapper: Handler<Event> = (e) => {
11
12
  handler(e);
12
13
  };
13
14
 
@@ -19,15 +20,22 @@ export function EventEmitter<T>(
19
20
  },
20
21
  };
21
22
  },
22
- emit(data: T) {
23
+ emit(data: Event['value']) {
23
24
  if (isActive()) {
24
- handlers.forEach((handler) => handler({ value: data }));
25
+ handlers.forEach((handler) =>
26
+ handler({ type: type, value: data } as Event)
27
+ );
25
28
  }
26
29
  },
27
- });
28
- }
29
-
30
- export type EventEmitterInstance<T> = {
31
- readonly listen: (handler: Listener<T>) => Subscription;
32
- readonly emit: (data: T) => void;
30
+ } as unknown as EventEmitterInstance<Event>;
33
31
  };
32
+
33
+ export type EventEmitterInstance<Event> =
34
+ Event extends ListenerEvent<infer E, infer T>
35
+ ? {
36
+ readonly listen: (
37
+ handler: Handler<ListenerEvent<E, T>>
38
+ ) => Subscription;
39
+ readonly emit: (data: T) => void;
40
+ }
41
+ : never;
@@ -0,0 +1,55 @@
1
+ import { Subscription, ListenerEvent, CombinedHandler } from '../../types';
2
+
3
+ export function EventEmitterCombined<E extends ListenerEvent<string, any>>(
4
+ isActive: () => boolean
5
+ ): EventEmitterCombinedInstance<E> {
6
+ let handlers: CombinedHandler<E>[] = [];
7
+
8
+ let queue: E[] = [];
9
+
10
+ // merge events in queue into one event
11
+ function solveQueue() {
12
+ if (queue.length === 0) {
13
+ return;
14
+ }
15
+ const queueCopy = queue;
16
+ queue = [];
17
+ handlers.forEach((handler) => {
18
+ handler(queueCopy);
19
+ });
20
+ }
21
+
22
+ return Object.freeze({
23
+ listen(handler: (e: E[]) => void): Subscription {
24
+ const handlerWrapper: CombinedHandler<E> = (events) => {
25
+ handler(events);
26
+ };
27
+
28
+ handlers.push(handlerWrapper);
29
+
30
+ return {
31
+ unsubscribe() {
32
+ handlers = handlers.filter((i) => handlerWrapper !== i);
33
+ },
34
+ };
35
+ },
36
+ emit(e: E, delayed: boolean) {
37
+ if (isActive()) {
38
+ if (isActive()) {
39
+ queue.push(e);
40
+ if (!delayed) {
41
+ solveQueue();
42
+ } else {
43
+ setTimeout(solveQueue, 0);
44
+ }
45
+ }
46
+ }
47
+ },
48
+ });
49
+ }
50
+
51
+ export type EventEmitterCombinedInstance<E extends ListenerEvent<string, any>> =
52
+ {
53
+ readonly listen: (handler: CombinedHandler<E>) => Subscription;
54
+ readonly emit: (e: E, delayed: boolean) => void;
55
+ };