@arrai-innovations/reactive-helpers 1.0.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 (53) hide show
  1. package/.circleci/config.yml +91 -0
  2. package/.commitlintrc.json +3 -0
  3. package/.editorconfig +5 -0
  4. package/.eslintignore +2 -0
  5. package/.eslintrc.js +26 -0
  6. package/.husky/commit-msg +2 -0
  7. package/.husky/pre-commit +2 -0
  8. package/.idea/encodings.xml +6 -0
  9. package/.idea/inspectionProfiles/Project_Default.xml +6 -0
  10. package/.idea/modules.xml +8 -0
  11. package/.idea/prettier.xml +8 -0
  12. package/.idea/reactive-helpers.iml +15 -0
  13. package/.lintstagedrc +11 -0
  14. package/.prettierignore +4 -0
  15. package/.prettierrc.js +5 -0
  16. package/README.md +598 -0
  17. package/babel.config.js +15 -0
  18. package/index.js +2 -0
  19. package/jest.config.mjs +27 -0
  20. package/package.json +59 -0
  21. package/tests/unit/.eslintrc.js +10 -0
  22. package/tests/unit/expectHelpers.js +6 -0
  23. package/tests/unit/mockOnUnmounted.js +9 -0
  24. package/tests/unit/use/index.spec.js +18 -0
  25. package/tests/unit/use/listFilter.spec.js +332 -0
  26. package/tests/unit/use/listInstance.spec.js +424 -0
  27. package/tests/unit/use/listRelated.spec.js +73 -0
  28. package/tests/unit/use/listSort.spec.js +220 -0
  29. package/tests/unit/use/listSubscription.spec.js +540 -0
  30. package/tests/unit/use/objectInstance.spec.js +897 -0
  31. package/tests/unit/use/objectSubscription.spec.js +671 -0
  32. package/tests/unit/use/search.spec.js +67 -0
  33. package/tests/unit/use/watches.spec.js +252 -0
  34. package/tests/unit/utils/assignReactiveObject.spec.js +127 -0
  35. package/tests/unit/utils/index.spec.js +18 -0
  36. package/tests/unit/utils/keyDiff.spec.js +67 -0
  37. package/tests/unit/utils/set.spec.js +68 -0
  38. package/tests/unit/utils/unrefAndToRawDeep.spec.js +170 -0
  39. package/use/index.js +8 -0
  40. package/use/listFilter.js +157 -0
  41. package/use/listInstance.js +144 -0
  42. package/use/listRelated.js +135 -0
  43. package/use/listSort.js +190 -0
  44. package/use/listSubscription.js +143 -0
  45. package/use/objectInstance.js +222 -0
  46. package/use/objectSubscription.js +188 -0
  47. package/use/search.js +104 -0
  48. package/utils/assignReactiveObject.js +62 -0
  49. package/utils/index.js +5 -0
  50. package/utils/keyDiff.js +10 -0
  51. package/utils/set.js +48 -0
  52. package/utils/unrefAndToRawDeep.js +49 -0
  53. package/utils/watches.js +170 -0
@@ -0,0 +1,188 @@
1
+ import { identity } from "lodash";
2
+ import { computed, effectScope, onScopeDispose, reactive, watch } from "vue";
3
+ import { assignReactiveObject } from "../utils/assignReactiveObject";
4
+ import { useObjectInstance } from "./objectInstance";
5
+
6
+ export class ObjectSubscriptionError extends Error {
7
+ constructor(message) {
8
+ super(message);
9
+ this.name = "ObjectSubscriptionError";
10
+ }
11
+ }
12
+
13
+ const defaultCrud = reactive({
14
+ subscribe: undefined,
15
+ });
16
+
17
+ export function setObjectSubscriptionCrud({ subscribe }) {
18
+ defaultCrud.subscribe = subscribe;
19
+ }
20
+
21
+ export function useObjectSubscriptions(subscriptionArgs) {
22
+ const subscriptions = {};
23
+ for (const [key, value] of Object.entries(subscriptionArgs)) {
24
+ subscriptions[key] = useObjectSubscription(value);
25
+ }
26
+ return subscriptions;
27
+ }
28
+
29
+ export function useObjectSubscription({ objectInstance, crudArgs, id, retrieveArgs = {} }) {
30
+ if (!objectInstance) {
31
+ objectInstance = useObjectInstance({ crudArgs, retrieveArgs });
32
+ }
33
+ const state = reactive({
34
+ objectSubscriptionCrud: {
35
+ subscribe: undefined,
36
+ },
37
+ id,
38
+ retrieveArgs,
39
+ subscriptionLoading: undefined,
40
+ subscriptionErrored: false,
41
+ subscriptionError: null,
42
+ subscribed: false,
43
+ intendToSubscribe: false,
44
+ intendToRetrieve: false,
45
+ });
46
+ assignReactiveObject(state.objectSubscriptionCrud, defaultCrud);
47
+ let cancelSubscription;
48
+
49
+ function updateFromSubscription(data) {
50
+ assignReactiveObject(objectInstance.state.object, data);
51
+ }
52
+
53
+ function deleteFromSubscription() {
54
+ objectInstance.state.deleted = true;
55
+ assignReactiveObject(objectInstance.state.object, {});
56
+ }
57
+
58
+ async function publicSubscribe({ retrieve = true } = {}) {
59
+ if (!state.intendToSubscribe) {
60
+ state.intendToSubscribe = true;
61
+ }
62
+ if (retrieve) {
63
+ if (!state.intendToRetrieve) {
64
+ state.intendToRetrieve = true;
65
+ }
66
+ }
67
+ return subscribe();
68
+ }
69
+
70
+ async function subscribe() {
71
+ if (cancelSubscription || state.subscribed) {
72
+ throw new ObjectSubscriptionError("already subscribed.");
73
+ }
74
+ if (![state.id, state.retrieveArgs].every(identity)) {
75
+ // delayed until stuff is true;
76
+ return false;
77
+ }
78
+ state.subscriptionLoading = true;
79
+ state.subscriptionErrored = false;
80
+ state.subscriptionError = null;
81
+ let subscribePromise;
82
+ cancelSubscription = () => subscribePromise.cancel();
83
+ subscribePromise = state.objectSubscriptionCrud.subscribe({
84
+ crudArgs: objectInstance.state.objectInstanceCrud.args,
85
+ id,
86
+ retrieveArgs: state.retrieveArgs,
87
+ callback: (data, action) => {
88
+ if (action === "delete") {
89
+ objectInstance.deleteFromSubscription();
90
+ } else {
91
+ objectInstance.updateFromSubscription(data);
92
+ }
93
+ },
94
+ });
95
+ return subscribePromise
96
+ .then(() => {
97
+ state.subscribed = true;
98
+ return Promise.resolve(true);
99
+ })
100
+ .catch((error) => {
101
+ state.subscriptionErrored = true;
102
+ state.subscriptionError = error;
103
+ if (cancelSubscription) {
104
+ cancelSubscription();
105
+ cancelSubscription = null;
106
+ state.subscribed = false;
107
+ }
108
+ return Promise.resolve(false);
109
+ })
110
+ .finally(() => {
111
+ state.subscriptionLoading = false;
112
+ });
113
+ }
114
+
115
+ async function publicUnsubscribe() {
116
+ if (state.intendToSubscribe) {
117
+ state.intendToSubscribe = false;
118
+ }
119
+ if (state.intendToRetrieve) {
120
+ state.intendToRetrieve = false;
121
+ }
122
+ return unsubscribe();
123
+ }
124
+
125
+ async function unsubscribe() {
126
+ if (cancelSubscription) {
127
+ state.subscribed = false;
128
+ let returnPromise = cancelSubscription();
129
+ cancelSubscription = null;
130
+ return returnPromise;
131
+ }
132
+ return false;
133
+ }
134
+
135
+ const es = effectScope();
136
+
137
+ es.run(() => {
138
+ state.loading = computed(() => objectInstance.state.loading || state.subscriptionLoading);
139
+ state.errored = computed(() => objectInstance.state.errored || state.subscriptionErrored);
140
+ state.error = computed(() => objectInstance.state.error || state.subscriptionError);
141
+
142
+ watch(
143
+ [() => state.intendToSubscribe, () => state.id, () => state.retrieveArgs],
144
+ async (newArgs, oldArgs) => {
145
+ const everyNew = newArgs.every((e) => e);
146
+ const everyOld = oldArgs.every((e) => e);
147
+ if (everyOld) {
148
+ await unsubscribe();
149
+ }
150
+ if (everyNew) {
151
+ if (!cancelSubscription && !state.subscribed) {
152
+ await subscribe().catch(console.error);
153
+ }
154
+ }
155
+ },
156
+ {
157
+ deep: true,
158
+ }
159
+ );
160
+
161
+ watch(
162
+ [() => state.intendToRetrieve, () => state.id, () => state.retrieveArgs],
163
+ async (newArgs) => {
164
+ const everyNew = newArgs.every((e) => e);
165
+ if (everyNew) {
166
+ if (!objectInstance.state.loading) {
167
+ await objectInstance.retrieve({ id: state.id, ...state.retrieveArgs }).catch(console.error);
168
+ }
169
+ }
170
+ },
171
+ { deep: true }
172
+ );
173
+
174
+ onScopeDispose(async () => {
175
+ await unsubscribe();
176
+ });
177
+ });
178
+
179
+ return {
180
+ state,
181
+ objectInstance,
182
+ subscribe: publicSubscribe,
183
+ unsubscribe: publicUnsubscribe,
184
+ updateFromSubscription,
185
+ deleteFromSubscription,
186
+ effectScope: es,
187
+ };
188
+ }
package/use/search.js ADDED
@@ -0,0 +1,104 @@
1
+ import FlexSearch from "flexsearch";
2
+ import { fromPairs, throttle } from "lodash";
3
+ import { effectScope, reactive, toRef, watch } from "vue";
4
+ import { assignReactiveObject } from "../utils/assignReactiveObject";
5
+
6
+ const indexOptions = {
7
+ tokenize: "forward",
8
+ };
9
+
10
+ const searchOptions = {
11
+ throttle: 500,
12
+ limit: 1000,
13
+ };
14
+
15
+ export function setDefaultIndexOptions(newDefaultIndexOptions = {}) {
16
+ Object.assign(indexOptions, newDefaultIndexOptions);
17
+ }
18
+
19
+ export function setDefaultSearchOptions(newDefaultSearchOptions = {}) {
20
+ Object.assign(searchOptions, newDefaultSearchOptions);
21
+ }
22
+
23
+ export function useSearch(
24
+ customIndexOptions = {}, // custom index options are not reactive.
25
+ customSearchOptions = {} // custom search options are reactive.
26
+ ) {
27
+ let searchIndex = new FlexSearch.Index({
28
+ ...indexOptions,
29
+ ...customIndexOptions,
30
+ });
31
+ const mySearchOptions = {
32
+ ...searchOptions,
33
+ ...customSearchOptions,
34
+ };
35
+ const state = reactive({
36
+ search: "",
37
+ results: {},
38
+ searched: false,
39
+ searching: false,
40
+ });
41
+ const addIndex = (id, indexValue) => {
42
+ // numeric ids consume less memory in flexsearch.
43
+ const numericId = +id;
44
+ const numericIfPossible = isNaN(numericId) ? id : numericId;
45
+ searchIndex.add(numericIfPossible, indexValue);
46
+ throttledDoSearch();
47
+ };
48
+ const updateIndex = (id, indexValue) => {
49
+ // numeric ids consume less memory in flexsearch.
50
+ const numericId = +id;
51
+ const numericIfPossible = isNaN(numericId) ? id : numericId;
52
+ searchIndex.update(numericIfPossible, indexValue);
53
+ throttledDoSearch();
54
+ };
55
+ const removeIndex = (id) => {
56
+ // numeric ids consume less memory in flexsearch.
57
+ const numericId = +id;
58
+ const numericIfPossible = isNaN(numericId) ? id : numericId;
59
+ searchIndex.remove(numericIfPossible);
60
+ throttledDoSearch();
61
+ };
62
+ const clearIndex = () => {
63
+ searchIndex = new FlexSearch.Index(indexOptions);
64
+ assignReactiveObject(state.results, {});
65
+ state.searched = false;
66
+ };
67
+
68
+ async function doSearch() {
69
+ if (!state.search) {
70
+ assignReactiveObject(state.results, {});
71
+ state.searched = false;
72
+ return;
73
+ }
74
+ state.searching = true;
75
+ const results = await searchIndex.searchAsync(state.search, {
76
+ limit: mySearchOptions.limit,
77
+ });
78
+ state.searched = true;
79
+ assignReactiveObject(state.results, fromPairs(results.map((e) => [e, true])) || {});
80
+ state.searching = false;
81
+ }
82
+
83
+ const throttledDoSearch = throttle(doSearch, mySearchOptions.throttle);
84
+
85
+ const es = effectScope();
86
+
87
+ es.run(() => {
88
+ watch(
89
+ toRef(state, "search"),
90
+ function () {
91
+ if (!indexOptions.minlength || state.search.length >= indexOptions.minlength) {
92
+ throttledDoSearch();
93
+ } else {
94
+ assignReactiveObject(state.results, {});
95
+ state.searched = false;
96
+ }
97
+ },
98
+ {
99
+ immediate: true,
100
+ }
101
+ );
102
+ });
103
+ return { state, addIndex, updateIndex, removeIndex, clearIndex, effectScope: es };
104
+ }
@@ -0,0 +1,62 @@
1
+ import { keyDiff } from "./keyDiff.js";
2
+ import { union } from "./set.js";
3
+ import { isReactive, toRef } from "vue";
4
+ import { isArray, isObject } from "lodash";
5
+ import { inspect } from "util";
6
+
7
+ export class AssignReactiveObjectError extends Error {
8
+ constructor(message) {
9
+ super(message);
10
+ this.name = "AssignReactiveObjectError";
11
+ }
12
+ }
13
+
14
+ function isArrayOrObject(key, value) {
15
+ if (!(isArray(value) || isObject(value))) {
16
+ throw new AssignReactiveObjectError(`${key} must be an object or an array, not ${inspect(value)}`);
17
+ }
18
+ }
19
+
20
+ export function addOrUpdateReactiveObject(target, source, exclude = [], addedKeys = null, sameKeys = null) {
21
+ isArrayOrObject("target", target);
22
+ isArrayOrObject("source", source);
23
+ if (!addedKeys && !sameKeys) {
24
+ ({ addedKeys, sameKeys } = keyDiff(Object.keys(source) || [], Object.keys(target) || []));
25
+ }
26
+ const targetIsReactive = isReactive(target);
27
+ const sourceIsReactive = isReactive(source);
28
+ for (const key of union(addedKeys, sameKeys)) {
29
+ if (!exclude.includes(key)) {
30
+ if (targetIsReactive && sourceIsReactive) {
31
+ target[key] = toRef(source, key);
32
+ } else if (target[key] !== source[key]) {
33
+ target[key] = source[key];
34
+ }
35
+ }
36
+ }
37
+ }
38
+
39
+ export function assignReactiveObject(target, source, exclude = []) {
40
+ if (target === source) {
41
+ return;
42
+ }
43
+ isArrayOrObject("target", target);
44
+ isArrayOrObject("source", source);
45
+ const targetIsArray = isArray(target);
46
+ const { addedKeys, sameKeys, removedKeys } = keyDiff(Object.keys(source) || [], Object.keys(target) || []);
47
+ if (targetIsArray) {
48
+ // Remove indices in reverse (descending) order to keep them stable
49
+ for (const removedKey of [...removedKeys].map((key) => parseInt(key, 10)).sort((a, b) => b - a)) {
50
+ if (!exclude.includes(removedKey)) {
51
+ target.splice(removedKey, 1);
52
+ }
53
+ }
54
+ } else {
55
+ for (const removedKey of removedKeys) {
56
+ if (!exclude.includes(removedKey)) {
57
+ delete target[removedKey];
58
+ }
59
+ }
60
+ }
61
+ addOrUpdateReactiveObject(target, source, exclude, addedKeys, sameKeys);
62
+ }
package/utils/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export * from "./assignReactiveObject";
2
+ export * from "./keyDiff";
3
+ export * from "./set";
4
+ export * from "./unrefAndToRawDeep";
5
+ export * from "./watches";
@@ -0,0 +1,10 @@
1
+ import { difference, intersection } from "./set.js";
2
+
3
+ export function keyDiff(newKeys, oldKeys) {
4
+ const newKeysSet = new Set(newKeys);
5
+ const oldKeysSet = new Set(oldKeys);
6
+ const sameKeys = intersection(newKeysSet, oldKeysSet);
7
+ const removedKeys = difference(oldKeysSet, newKeysSet);
8
+ const addedKeys = difference(newKeysSet, oldKeysSet);
9
+ return { sameKeys, removedKeys, addedKeys };
10
+ }
package/utils/set.js ADDED
@@ -0,0 +1,48 @@
1
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set#implementing_basic_set_operations
2
+
3
+ export const isSuperset = (set, subset) => {
4
+ for (const elem of subset) {
5
+ if (!set.has(elem)) {
6
+ return false;
7
+ }
8
+ }
9
+ return true;
10
+ };
11
+
12
+ export const union = (setA, setB) => {
13
+ const _union = new Set(setA);
14
+ for (let elem of setB) {
15
+ _union.add(elem);
16
+ }
17
+ return _union;
18
+ };
19
+
20
+ export const intersection = (setA, setB) => {
21
+ const _intersection = new Set();
22
+ for (const elem of setB) {
23
+ if (setA.has(elem)) {
24
+ _intersection.add(elem);
25
+ }
26
+ }
27
+ return _intersection;
28
+ };
29
+
30
+ export const symmetricDifference = (setA, setB) => {
31
+ const _difference = new Set(setA);
32
+ for (const elem of setB) {
33
+ if (_difference.has(elem)) {
34
+ _difference.delete(elem);
35
+ } else {
36
+ _difference.add(elem);
37
+ }
38
+ }
39
+ return _difference;
40
+ };
41
+
42
+ export const difference = (setA, setB) => {
43
+ const _difference = new Set(setA);
44
+ for (const elem of setB) {
45
+ _difference.delete(elem);
46
+ }
47
+ return _difference;
48
+ };
@@ -0,0 +1,49 @@
1
+ import { isProxy, isRef, toRaw, unref } from "vue";
2
+ import { isArray, isUndefined } from "lodash";
3
+ import { isObjectLike } from "lodash/lang";
4
+
5
+ export const circular = Symbol("circular");
6
+
7
+ export function unrefAndToRawDeep(obj) {
8
+ if (isUndefined(obj)) {
9
+ return obj;
10
+ }
11
+ const seen = new Set();
12
+ const returnValue = isArray(obj) ? [] : {};
13
+ const queue = [];
14
+ for (const [key, value] of Object.entries(obj)) {
15
+ queue.push([returnValue, key, value]);
16
+ }
17
+ while (queue.length) {
18
+ const [writePosition, key, value] = queue.shift();
19
+ if (seen.has(value)) {
20
+ // if (seen.some((item) => item === value)) {
21
+ writePosition[key] = circular;
22
+ continue;
23
+ }
24
+ let raw = value;
25
+ if (isRef(raw)) {
26
+ const refValue = unref(raw);
27
+ // primitive values are not added to the seen set
28
+ if (isObjectLike(refValue)) {
29
+ seen.add(raw);
30
+ }
31
+ raw = unref(raw);
32
+ }
33
+ if (isProxy(raw)) {
34
+ // this doesn't seem to do anything for the current test case.
35
+ // seen.add(raw);
36
+ raw = toRaw(raw);
37
+ }
38
+ if (!isObjectLike(raw)) {
39
+ // primitive values don't need to add to the queue
40
+ writePosition[key] = raw;
41
+ continue;
42
+ }
43
+ writePosition[key] = isArray(raw) ? [] : {};
44
+ for (const [nextKey, nextValue] of Object.entries(raw)) {
45
+ queue.push([writePosition[key], nextKey, nextValue]);
46
+ }
47
+ }
48
+ return returnValue;
49
+ }
@@ -0,0 +1,170 @@
1
+ import { watch } from "vue";
2
+
3
+ // If you want an immediate watch, but you can't use immediate because you want to stop the watch in the function.
4
+ export class ImmediateStopWatch {
5
+ constructor() {}
6
+
7
+ // watchFuncArgs are for immediate call only.
8
+ start(watchSources, watchFunc, watchFuncArgs = []) {
9
+ this.stopWatch = watch(watchSources, watchFunc);
10
+ watchFunc(...watchFuncArgs);
11
+ }
12
+
13
+ stop() {
14
+ if (this.stopWatch) {
15
+ this.stopWatch();
16
+ delete this.stopWatch;
17
+ }
18
+ }
19
+ }
20
+
21
+ export class AwaitTimeoutError extends Error {
22
+ constructor(message, code) {
23
+ super(message);
24
+ this.name = "AwaitTimeoutError";
25
+ this.code = code;
26
+ }
27
+ }
28
+
29
+ export class AwaitNotError extends Error {
30
+ constructor(message, code) {
31
+ super(message);
32
+ this.name = "AwaitNotError";
33
+ this.code = code;
34
+ }
35
+ }
36
+
37
+ export class AwaitTimeout {
38
+ constructor({ timeout = 1000 }) {
39
+ this.promise = new Promise((resolve, reject) => {
40
+ this.resolve = resolve;
41
+ this.reject = reject;
42
+ });
43
+ this.timeout = timeout;
44
+ this.timeoutId = null;
45
+ }
46
+
47
+ start() {
48
+ if (this.timeout) {
49
+ this.timeoutId = setTimeout(this.doTimeout.bind(this), this.timeout);
50
+ } else {
51
+ this.doTimeout();
52
+ }
53
+ }
54
+
55
+ doTimeout() {
56
+ delete this.timeoutId;
57
+ if (this.resolve) {
58
+ this.resolve();
59
+ delete this.resolve;
60
+ }
61
+ this.stop();
62
+ }
63
+
64
+ stop() {
65
+ if (this.timeoutId) {
66
+ clearTimeout(this.timeoutId);
67
+ delete this.timeoutId;
68
+ this.reject(new AwaitTimeoutError("Cancelled", "timeout_cancelled"));
69
+ }
70
+ if (this.resolve) {
71
+ delete this.resolve;
72
+ }
73
+ if (this.reject) {
74
+ delete this.reject;
75
+ }
76
+ }
77
+ }
78
+
79
+ export function doAwaitTimeout(timeout) {
80
+ const newAwaitTimeout = new AwaitTimeout({ timeout });
81
+ newAwaitTimeout.start();
82
+ return newAwaitTimeout.promise;
83
+ }
84
+
85
+ export class AwaitNot {
86
+ constructor({ obj, prop, couldAlreadyBeFalse = false, timeout = 1000 }) {
87
+ this.timeout = new AwaitTimeout({ timeout });
88
+ this.promise = new Promise((resolve, reject) => {
89
+ this.resolve = resolve;
90
+ this.reject = reject;
91
+ });
92
+ this.couldAlreadyBeFalse = couldAlreadyBeFalse;
93
+ this.obj = obj;
94
+ this.prop = prop;
95
+ this.trueISW = new ImmediateStopWatch();
96
+ this.falseISW = new ImmediateStopWatch();
97
+ }
98
+
99
+ start() {
100
+ this.timeout.promise
101
+ .then(() => {
102
+ this.stop();
103
+ this.reject(new AwaitNotError("Timeout", "timeout"));
104
+ })
105
+ .catch((err) => {
106
+ this.stop();
107
+ if (!(err instanceof AwaitTimeoutError)) {
108
+ this.reject(err);
109
+ }
110
+ });
111
+ this.timeout.start();
112
+ if (this.obj[this.prop] === false && this.couldAlreadyBeFalse) {
113
+ this.waitForTrue();
114
+ } else {
115
+ this.waitForFalse();
116
+ }
117
+ }
118
+
119
+ waitForTrue() {
120
+ this.trueISW.start(
121
+ () => this.obj[this.prop],
122
+ (newValue) => {
123
+ if (newValue === true) {
124
+ this.stopTrue();
125
+ this.waitForFalse();
126
+ }
127
+ },
128
+ [this.obj[this.prop]]
129
+ );
130
+ }
131
+
132
+ stopTrue() {
133
+ if (this.trueISW) {
134
+ this.trueISW.stop();
135
+ delete this.trueISW;
136
+ }
137
+ }
138
+
139
+ waitForFalse() {
140
+ this.falseISW.start(
141
+ () => this.obj[this.prop],
142
+ (newValue) => {
143
+ if (newValue === false) {
144
+ this.stop();
145
+ this.resolve();
146
+ }
147
+ },
148
+ [this.obj[this.prop]]
149
+ );
150
+ }
151
+
152
+ stopFalse() {
153
+ if (this.falseISW) {
154
+ this.falseISW.stop();
155
+ delete this.falseISW;
156
+ }
157
+ }
158
+
159
+ stop() {
160
+ this.timeout.stop();
161
+ this.stopTrue();
162
+ this.stopFalse();
163
+ }
164
+ }
165
+
166
+ export function doAwaitNot({ obj, prop, couldAlreadyBeFalse = true, timeout = 1000 }) {
167
+ const awaitNot = new AwaitNot({ obj, prop, couldAlreadyBeFalse, timeout });
168
+ awaitNot.start();
169
+ return awaitNot.promise;
170
+ }