@woosh/meep-engine 2.39.33 → 2.39.36

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 (25) hide show
  1. package/core/binary/BinaryBuffer.js +825 -827
  2. package/core/binary/EncodingBinaryBuffer.js +38 -41
  3. package/core/bvh2/binary/IndexedBinaryBVH.js +8 -0
  4. package/core/bvh2/bvh3/ExplicitBinaryBoundingVolumeHierarchy.d.ts +20 -0
  5. package/core/bvh2/bvh3/ExplicitBinaryBoundingVolumeHierarchy.js +5 -35
  6. package/core/bvh2/bvh3/ExplicitBinaryBoundingVolumeHierarchy.spec.js +19 -0
  7. package/core/bvh2/bvh3/bvh_query_user_data_overlaps_frustum.js +12 -11
  8. package/core/collection/array/computeHashIntegerArray.d.ts +1 -0
  9. package/core/collection/array/computeHashIntegerArray.js +4 -9
  10. package/core/events/signal/Signal.js +2 -2
  11. package/core/events/signal/SignalHandler.js +1 -1
  12. package/core/function/FunctionCompiler.js +1 -1
  13. package/core/land/reactive/compileReactiveExpression.js +1 -1
  14. package/core/land/reactive/compiler/ReactiveCompiler.js +1 -1
  15. package/core/primitives/array/computeIntegerArrayHash.js +1 -1
  16. package/editor/tools/v2/BlenderCameraOrientationGizmo.js +3 -3
  17. package/engine/ecs/EntityComponentDataset.js +2 -2
  18. package/engine/ecs/speaker/VoiceSystem.js +8 -0
  19. package/engine/graphics/ecs/animation/animator/AnimationGraphSystem.js +5 -0
  20. package/engine/graphics/ecs/animation/animator/graph/AnimationGraph.js +8 -2
  21. package/engine/graphics/geometry/buffered/query/GeometrySpatialQueryAccelerator.js +1 -1
  22. package/engine/knowledge/database/StaticKnowledgeDataTable.js +27 -5
  23. package/engine/metrics/GoogleAnalyticsMetrics.js +2 -1
  24. package/engine/plugin/EnginePluginManager.js +1 -2
  25. package/package.json +1 -1
@@ -36,62 +36,59 @@ class Dictionary {
36
36
  }
37
37
 
38
38
 
39
- function EncodingBinaryBuffer() {
40
- BinaryBuffer.call(this);
41
- this.__dictionary = new Dictionary();
42
- }
43
-
44
- EncodingBinaryBuffer.prototype = Object.create(BinaryBuffer.prototype);
39
+ export class EncodingBinaryBuffer extends BinaryBuffer {
40
+ constructor() {
41
+ super();
42
+ this.__dictionary = new Dictionary();
43
+ }
45
44
 
46
- EncodingBinaryBuffer.prototype.writeUTF8String = function (value) {
47
- const address = this.__dictionary.getAddress(value);
45
+ writeUTF8String(value) {
46
+ const address = this.__dictionary.getAddress(value);
48
47
 
49
- if (address === undefined) {
50
- this.writeUint8(0); //mark as complete value
48
+ if (address === undefined) {
49
+ this.writeUint8(0); //mark as complete value
51
50
 
52
- const address1 = this.position;
51
+ const address1 = this.position;
53
52
 
54
- BinaryBuffer.prototype.writeUTF8String.call(this, value);
53
+ super.writeUTF8String(value);
55
54
 
56
- this.__dictionary.add(value, address1);
57
- } else {
58
- //write as reference
59
- this.writeUint32LE(1 | (address << 1));
55
+ this.__dictionary.add(value, address1);
56
+ } else {
57
+ //write as reference
58
+ this.writeUint32LE(1 | (address << 1));
59
+ }
60
60
  }
61
- };
62
61
 
63
- EncodingBinaryBuffer.prototype.readUTF8String = function () {
64
- const header0 = this.readUint8();
62
+ readUTF8String() {
63
+ const header0 = this.readUint8();
65
64
 
66
- if (header0 === 0) {
67
- //complete value
68
- return BinaryBuffer.prototype.readUTF8String.call(this);
69
- } else {
70
- this.position--;
65
+ if (header0 === 0) {
66
+ //complete value
67
+ return super.readUTF8String();
68
+ } else {
69
+ this.position--;
71
70
 
72
- const header = this.readUint32LE();
71
+ const header = this.readUint32LE();
73
72
 
74
- const address = header >> 1;
73
+ const address = header >> 1;
75
74
 
76
- let value = this.__dictionary.getValue(address);
75
+ let value = this.__dictionary.getValue(address);
77
76
 
78
- if (value === undefined) {
79
- //remember position
80
- const p = this.position;
77
+ if (value === undefined) {
78
+ //remember position
79
+ const p = this.position;
81
80
 
82
- this.position = address;
81
+ this.position = address;
83
82
 
84
- value = BinaryBuffer.prototype.readUTF8String.call(this);
83
+ value = super.readUTF8String();
85
84
 
86
- //restore position
87
- this.position = p;
85
+ //restore position
86
+ this.position = p;
88
87
 
89
- this.__dictionary.add(value, address);
90
- }
88
+ this.__dictionary.add(value, address);
89
+ }
91
90
 
92
- return value;
91
+ return value;
92
+ }
93
93
  }
94
- };
95
-
96
-
97
- export { EncodingBinaryBuffer };
94
+ }
@@ -47,6 +47,14 @@ export default class IndexedBinaryBVH {
47
47
  this.data = new Float32Array(16);
48
48
  }
49
49
 
50
+ /**
51
+ *
52
+ * @returns {number}
53
+ */
54
+ estimateByteSize(){
55
+ return this.data.buffer.byteLength + 248;
56
+ }
57
+
50
58
  /**
51
59
  *
52
60
  * @param {int} leafCount
@@ -1,3 +1,23 @@
1
1
  export class ExplicitBinaryBoundingVolumeHierarchy {
2
+ readonly root: number
2
3
 
4
+ allocate_node(): number
5
+
6
+ release_node(node: number): void
7
+
8
+ insert_leaf(node: number): void
9
+
10
+ remove_leaf(node: number): void
11
+
12
+ collect_nodes_all(destination: ArrayLike<number>, destination_offset: number): void
13
+
14
+ node_set_aabb(node: number, aabb: ArrayLike<number>): void
15
+
16
+ node_get_aabb(node: number, aabb: ArrayLike<number>): void
17
+
18
+ node_set_user_data(node: number, data: number): void
19
+
20
+ node_get_user_data(node: number): number
21
+
22
+ release_all(): void
3
23
  }
@@ -49,12 +49,6 @@ const ELEMENT_WORD_COUNT = 10;
49
49
  */
50
50
  const INITIAL_CAPACITY = 128;
51
51
 
52
- /**
53
- *
54
- * @type {number[]}
55
- */
56
- const scratch_stack = [];
57
-
58
52
  /**
59
53
  * Bounding Volume Hierarchy implementation. Stores unsigned integer values at leaves, these are typically IDs or Index values.
60
54
  * Highly optimized both in terms of memory usage and CPU. Most of the code inlined. No allocation are performed during usage (except for growing the tree capacity).
@@ -300,7 +294,7 @@ export class ExplicitBinaryBoundingVolumeHierarchy {
300
294
  /**
301
295
  *
302
296
  * @param {number} id
303
- * @param {number[]} aabb
297
+ * @param {number[]|ArrayLike<number>} aabb
304
298
  */
305
299
  node_set_aabb(id, aabb) {
306
300
  assert.isNonNegativeInteger(id, 'id');
@@ -882,36 +876,12 @@ export class ExplicitBinaryBoundingVolumeHierarchy {
882
876
 
883
877
  /**
884
878
  * Release all nodes, this essentially resets the tree to empty state
879
+ * NOTE: For performance reasons, released memory is not reset, this means that attempting to access cleared nodes' memory will yield garbage data
885
880
  */
886
881
  release_all() {
887
- let cursor = 0;
888
- const stack = scratch_stack;
889
-
890
- const root = this.__root;
891
-
892
- if (root !== NULL_NODE) {
893
- stack[cursor++] = root;
894
- }
895
-
896
- const uint32 = this.__data_uint32;
897
-
898
- while (cursor > 0) {
899
- cursor--;
900
-
901
- const node = stack[cursor];
902
-
903
- const node_address = node * ELEMENT_WORD_COUNT;
904
-
905
- const child1 = uint32[node_address + COLUMN_CHILD_1];
906
- const child2 = uint32[node_address + COLUMN_CHILD_2];
907
-
908
- if (child1 !== NULL_NODE) {
909
- stack[cursor++] = child2;
910
- stack[cursor++] = child1;
911
- }
912
-
913
- this.release_node(node);
914
- }
882
+ this.__root = NULL_NODE;
883
+ this.__size = 0;
884
+ this.__free_pointer = 0;
915
885
  }
916
886
 
917
887
  /**
@@ -143,3 +143,22 @@ test("add and remove 5 nodes", () => {
143
143
  }
144
144
 
145
145
  });
146
+
147
+
148
+ test("release_all from empty doesn't throw", () => {
149
+ const bvh = new ExplicitBinaryBoundingVolumeHierarchy();
150
+
151
+ expect(() => bvh.release_all()).not.toThrow();
152
+ });
153
+
154
+ test("release_all with 1 leaf", () => {
155
+ const bvh = new ExplicitBinaryBoundingVolumeHierarchy();
156
+
157
+ const a = bvh.allocate_node();
158
+
159
+ bvh.insert_leaf(a);
160
+
161
+ bvh.release_all();
162
+
163
+ expect(bvh.root).toBe(NULL_NODE);
164
+ });
@@ -18,7 +18,8 @@ export function bvh_query_user_data_overlaps_frustum(
18
18
  result,
19
19
  result_offset,
20
20
  bvh,
21
- frustum) {
21
+ frustum
22
+ ) {
22
23
  const root = bvh.root;
23
24
 
24
25
  if (root === NULL_NODE) {
@@ -58,19 +59,19 @@ export function bvh_query_user_data_overlaps_frustum(
58
59
  if (!node_is_leaf) {
59
60
 
60
61
  if (intersection === 2) {
61
- // fully inside
62
+ // fully inside, fast collection path
62
63
  result_cursor += bvh_collect_user_data(result, result_cursor, bvh, node);
63
- continue;
64
+ } else {
65
+ // partially inside
66
+ // read in-order
67
+ const child1 = bvh.node_get_child1(node);
68
+ const child2 = bvh.node_get_child2(node);
69
+
70
+ // write to stack in reverse order, so that fist child ends up being visited first
71
+ traversal_stack[traversal_cursor++] = child1;
72
+ traversal_stack[traversal_cursor++] = child2;
64
73
  }
65
74
 
66
- // read in-order
67
- const child1 = bvh.node_get_child1(node);
68
- const child2 = bvh.node_get_child2(node);
69
-
70
- // write to stack in reverse order, so that fist child ends up being visited first
71
- traversal_stack[traversal_cursor++] = child1;
72
- traversal_stack[traversal_cursor++] = child2;
73
-
74
75
  } else {
75
76
  // leaf node
76
77
 
@@ -0,0 +1 @@
1
+ export function computeHashIntegerArray(...value: number[]): number
@@ -1,15 +1,10 @@
1
+ import { computeIntegerArrayHash } from "../../primitives/array/computeIntegerArrayHash.js";
2
+
1
3
  /**
2
4
  * Computes hash on integer values
3
5
  * @param {...number} value
4
6
  * @returns {number}
5
7
  */
6
- export function computeHashIntegerArray(value) {
7
- let hash = 0;
8
- const numArguments = arguments.length;
9
- for (let i = 0; i < numArguments; i++) {
10
- const singleValue = arguments[i];
11
- hash = ((hash << 5) - hash) + singleValue;
12
- hash |= 0; // Convert to 32bit integer
13
- }
14
- return hash;
8
+ export function computeHashIntegerArray(...value) {
9
+ return computeIntegerArrayHash(value, 0, value.length);
15
10
  }
@@ -182,7 +182,7 @@ export class Signal {
182
182
  * @returns {boolean} true if a handler was removed, false otherwise
183
183
  */
184
184
  remove(h, thisArg) {
185
- assert.typeOf(h, "function", "handler");
185
+ assert.isFunction(h, "handler");
186
186
 
187
187
  if (thisArg === undefined) {
188
188
  return removeHandlerByHandler(this, h);
@@ -707,7 +707,7 @@ function removeHandlerByHandlerAndContext(signal, h, ctx) {
707
707
  }
708
708
 
709
709
  function dispatchCallback(f, context, args) {
710
- assert.typeOf(f, 'function', 'f');
710
+ assert.isFunction(f, 'f');
711
711
 
712
712
  try {
713
713
  f.apply(context, args)
@@ -19,7 +19,7 @@ export class SignalHandler {
19
19
  */
20
20
  constructor(handle, context) {
21
21
  assert.notEqual(handle, undefined, 'handle must be defined');
22
- assert.typeOf(handle, 'function', 'handle');
22
+ assert.isFunction(handle, 'handle');
23
23
 
24
24
 
25
25
  this.handle = handle;
@@ -22,7 +22,7 @@ class FunctionDefinition {
22
22
  * @param {string[]} [args]
23
23
  */
24
24
  constructor({ name, body, args = [] }) {
25
- assert.typeOf(body, 'string', 'body');
25
+ assert.isString(body, 'body');
26
26
 
27
27
  /**
28
28
  *
@@ -8,7 +8,7 @@ import { ReactivePegParser } from "./pegjs/ReactivePegParser.js";
8
8
  * @returns {ReactiveExpression}
9
9
  */
10
10
  export function compileReactiveExpression(code) {
11
- assert.typeOf(code, 'string', 'code');
11
+ assert.isString(code, 'code');
12
12
 
13
13
  const parseTree = ReactivePegParser.INSTANCE.parse(code);
14
14
 
@@ -320,7 +320,7 @@ class CompilingVisitor extends AbstractParseTreeVisitor {
320
320
  * @returns {ReactiveExpression}
321
321
  */
322
322
  export function compileReactiveExpression(code) {
323
- assert.typeOf(code, 'string', 'code');
323
+ assert.isString(code, 'code');
324
324
 
325
325
  const chars = new ANTLRInputStream(code);
326
326
 
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  *
3
- * @param {Uint8Array} data
3
+ * @param {Uint8Array|number[]|ArrayLike<number>|IArguments} data
4
4
  * @param {number} offset
5
5
  * @param {number} length
6
6
  */
@@ -57,7 +57,7 @@ class DirectionStyle {
57
57
  }
58
58
  }
59
59
 
60
- function apply_hierarchical_options(source, target) {
60
+ function apply_hierarchical_options_from_json(source, target) {
61
61
  for (const prop_name in source) {
62
62
  if (!target.hasOwnProperty(prop_name)) {
63
63
  console.warn(`Property '${prop_name}' doesn't exist, valid properties are : ${Object.keys(target)}`);
@@ -71,7 +71,7 @@ function apply_hierarchical_options(source, target) {
71
71
  if (typeof existing_target_value.fromJSON === "function") {
72
72
  existing_target_value.fromJSON(source_value);
73
73
  } else {
74
- apply_hierarchical_options(source_value, existing_target_value);
74
+ apply_hierarchical_options_from_json(source_value, existing_target_value);
75
75
  }
76
76
  } else {
77
77
  target[prop_name] = source_value;
@@ -153,7 +153,7 @@ export class BlenderCameraOrientationGizmo extends CanvasView {
153
153
  };
154
154
 
155
155
  // read options
156
- apply_hierarchical_options(options, this.options);
156
+ apply_hierarchical_options_from_json(options, this.options);
157
157
 
158
158
  // Generate list of axes
159
159
  this.bubbles = [
@@ -1473,8 +1473,8 @@ export class EntityComponentDataset {
1473
1473
  addEntityEventListener(entity, eventName, listener, thisArg) {
1474
1474
 
1475
1475
  if (!ENV_PRODUCTION) {
1476
- assert.typeOf(eventName, "string", "eventName");
1477
- assert.typeOf(listener, "function", "listener");
1476
+ assert.isString(eventName, "eventName");
1477
+ assert.isFunction(listener, "listener");
1478
1478
  }
1479
1479
 
1480
1480
  if (!this.entityExists(entity)) {
@@ -28,6 +28,8 @@ import { AnimationBehavior } from "../../animation/keyed2/behavior/AnimationBeha
28
28
  import AnimationTrack from "../../animation/keyed2/AnimationTrack.js";
29
29
  import TransitionFunctions from "../../animation/TransitionFunctions.js";
30
30
  import AnimationTrackPlayback from "../../animation/keyed2/AnimationTrackPlayback.js";
31
+ import { globalMetrics } from "../../metrics/GlobalMetrics.js";
32
+ import { MetricsCategory } from "../../metrics/MetricsCategory.js";
31
33
 
32
34
  /**
33
35
  * Delay before the user notices the text and begins to read
@@ -443,5 +445,11 @@ export class VoiceSystem extends AbstractContextSystem {
443
445
 
444
446
  entityBuilder
445
447
  .build(ecd);
448
+
449
+ // send metrics
450
+ globalMetrics.record('ecs.system.voice', {
451
+ category: MetricsCategory.System,
452
+ label: line_id
453
+ });
446
454
  }
447
455
  }
@@ -200,6 +200,11 @@ export class AnimationGraphSystem extends System {
200
200
  return false;
201
201
  }
202
202
 
203
+ if (!graph.getFlag(AnimationGraphFlag.Linked)) {
204
+ // not linked this is generally a case of failed linkage, either way - don't animate
205
+ return false;
206
+ }
207
+
203
208
  if (graph.getFlag(AnimationGraphFlag.MeshSizeCulling)) {
204
209
 
205
210
  //check the size of the mesh in screen space, culling animation of tiny objects
@@ -4,7 +4,9 @@ import { AnimationStateType } from "./AnimationStateType.js";
4
4
  import { AnimationMixer } from "three";
5
5
  import { AnimationGraphFlag } from "./AnimationGraphFlag.js";
6
6
  import { writeAnimationGraphDefinitionToJSON } from "./definition/serialization/writeAnimationGraphDefinitionToJSON.js";
7
- import { readAnimationGraphDefinitionFromJSON } from "./definition/serialization/readAnimationGraphDefinitionFromJSON.js";
7
+ import {
8
+ readAnimationGraphDefinitionFromJSON
9
+ } from "./definition/serialization/readAnimationGraphDefinitionFromJSON.js";
8
10
  import { assert } from "../../../../../../core/assert.js";
9
11
  import { threeUpdateTransform } from "../../../../util/threeUpdateTransform.js";
10
12
  import { computeHashIntegerArray } from "../../../../../../core/collection/array/computeHashIntegerArray.js";
@@ -399,7 +401,6 @@ export class AnimationGraph {
399
401
  const clipIndex = graphDefinition.clipIndex;
400
402
  const nClips = clipIndex.length;
401
403
 
402
-
403
404
  /**
404
405
  * @type {AnimationClip[]}
405
406
  */
@@ -433,6 +434,7 @@ export class AnimationGraph {
433
434
 
434
435
  throw new Error(`Animation '${animationClipDefinition.name}' not found`);
435
436
  }
437
+
436
438
  }
437
439
 
438
440
  link(entity, ecd) {
@@ -549,6 +551,10 @@ export class AnimationGraph {
549
551
  */
550
552
  const action = this.__actions[i];
551
553
 
554
+ if (action === undefined) {
555
+ console.warn(`Action[${i}] is undefined`);
556
+ }
557
+
552
558
  action.weight = weight;
553
559
  action.timeScale = timeScale;
554
560
  }
@@ -25,7 +25,7 @@ export class GeometrySpatialQueryAccelerator {
25
25
  * @param {IndexedBinaryBVH} tree
26
26
  */
27
27
  valueWeigher(tree) {
28
- return tree.data.buffer.byteLength + 248;
28
+ return tree.estimateByteSize();
29
29
  }
30
30
  });
31
31
 
@@ -64,6 +64,13 @@ export class StaticKnowledgeDataTable {
64
64
  */
65
65
  __automatic_ids = false;
66
66
 
67
+ /**
68
+ *
69
+ * @type {string}
70
+ * @protected
71
+ */
72
+ __element_type_name = 'element';
73
+
67
74
  reset() {
68
75
  Object.keys(this.elements).forEach(id => {
69
76
  delete this.elements[id];
@@ -134,7 +141,7 @@ export class StaticKnowledgeDataTable {
134
141
  const element = this.get(id);
135
142
 
136
143
  if (element === null) {
137
- throw new Error(`Failed to get element '${id}' from the database'`);
144
+ throw new Error(`Failed to get ${this.__element_type_name} '${id}' from the database'`);
138
145
  }
139
146
 
140
147
  result.push(element);
@@ -248,6 +255,17 @@ export class StaticKnowledgeDataTable {
248
255
 
249
256
  }
250
257
 
258
+ const flag_ignore = datum['@ignore'];
259
+ if (flag_ignore !== undefined) {
260
+ if (typeof flag_ignore !== "boolean") {
261
+ console.warn(`@ignore flag is present on ${this.__element_type_name} '${id}'. @ignore must be a boolean (true/false), instead was '${typeof flag_ignore}'(=${flag_ignore})`);
262
+ } else if (flag_ignore) {
263
+ console.warn(`[${this.__element_type_name}:${id}] @ignore flag is set to true, skipping`);
264
+ } else {
265
+ console.warn(`[${this.__element_type_name}:${id}] @ignore flag is set to false and has no effect, please remove this field from JSON`);
266
+ }
267
+ }
268
+
251
269
  let element;
252
270
 
253
271
  try {
@@ -262,7 +280,7 @@ export class StaticKnowledgeDataTable {
262
280
  _id = 'ERROR';
263
281
  }
264
282
 
265
- console.error(`Failed to parse element (id=${_id})`, e, datum);
283
+ console.error(`Failed to parse ${this.__element_type_name} (id=${_id})`, e, datum);
266
284
  return;
267
285
  }
268
286
 
@@ -291,7 +309,7 @@ export class StaticKnowledgeDataTable {
291
309
  const added = this.add(element);
292
310
 
293
311
  if (!added) {
294
- console.error(`Failed to add element '${id}', most likely this a duplicate key`);
312
+ console.error(`Failed to add ${this.__element_type_name} '${id}', most likely this a duplicate key`);
295
313
  }
296
314
  });
297
315
 
@@ -344,10 +362,14 @@ export class StaticKnowledgeDataTable {
344
362
  try {
345
363
  promise = this.linkOne(element, json, database, assetManager);
346
364
  } catch (e) {
347
- console.error(`.linkOne(#id=${id}) threw unexpectedly:`, e);
365
+ const wrap = new Error(`.linkOne(#id=${id}) threw unexpectedly: ${e.message}`);
366
+
367
+ if (e.stack !== undefined) {
368
+ wrap.stack = e.stack;
369
+ }
348
370
 
349
371
  //re-throw to fail
350
- throw e;
372
+ throw wrap;
351
373
  }
352
374
 
353
375
  if (promise === undefined) {
@@ -1,7 +1,8 @@
1
1
  import { MetricsGateway } from "./MetricsGateway.js";
2
2
 
3
3
  // const GOOGLE_ANALYTICS_KEY = 'UA-66021000-1'; //Lazy-Kitty.com
4
- const GOOGLE_ANALYTICS_KEY = 'UA-138821093-1'; //Itch.com
4
+ // const GOOGLE_ANALYTICS_KEY = 'UA-138821093-1'; //Itch.com
5
+ const GOOGLE_ANALYTICS_KEY = 'G-ZBGZD4ZMZ1'; //ASKARA
5
6
 
6
7
  function loadGTAG(MEASUREMENT_ID) {
7
8
 
@@ -153,7 +153,6 @@ export class EnginePluginManager extends BaseProcess {
153
153
  const removed = this.__plugins.delete(PluginClass);
154
154
 
155
155
  if (!removed) {
156
- console.error('Plugin was not found', PluginClass);
157
156
  return;
158
157
  }
159
158
 
@@ -259,7 +258,7 @@ export class EnginePluginManager extends BaseProcess {
259
258
 
260
259
  const engine = this.engine;
261
260
 
262
- await ctx.transition(manager_state_value,engine);
261
+ await ctx.transition(manager_state_value, engine);
263
262
 
264
263
  return reference;
265
264
 
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "productName": "Meep",
6
6
  "description": "production-ready JavaScript game engine based on Entity Component System Architecture",
7
7
  "author": "Alexander Goldring",
8
- "version": "2.39.33",
8
+ "version": "2.39.36",
9
9
  "dependencies": {
10
10
  "gl-matrix": "3.4.3",
11
11
  "fast-levenshtein": "2.0.6",