@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
package/README.md ADDED
@@ -0,0 +1,598 @@
1
+ # reactive-helpers
2
+
3
+ ![Tests for 14.x](https://docs.arrai-dev.com/reactive-helpers/artifacts/main/tests.svg)
4
+ [![Coverage for 14.x](https://docs.arrai-dev.com/reactive-helpers/artifacts/main/tests.coverage.svg)](https://docs.arrai-dev.com/reactive-helpers/artifacts/main/coverage_tests/)
5
+ ![ESLint](https://docs.arrai-dev.com/reactive-helpers/artifacts/main/eslint.svg)
6
+ ![Prettier](https://docs.arrai-dev.com/reactive-helpers/artifacts/main/prettier.svg)
7
+
8
+ VueJS 3 utility composition functions to help manipulate objects and lists.
9
+
10
+ <!-- prettier-ignore-start -->
11
+ <!-- START doctoc generated TOC please keep comment here to allow auto update -->
12
+ <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
13
+
14
+ - [Install](#install)
15
+ - [Usage](#usage)
16
+ - [Import](#import)
17
+ - [List](#list)
18
+ - [Instance](#instance)
19
+ - [Subscription](#subscription)
20
+ - [Related](#related)
21
+ - [Sort](#sort)
22
+ - [Filter](#filter)
23
+ - [All](#all)
24
+ - [Object](#object)
25
+ - [Search](#search)
26
+ - [Utils](#utils)
27
+ - [addOrUpdateReactiveObject & assignReactiveObject](#addorupdatereactiveobject--assignreactiveobject)
28
+ - [keyDiff](#keydiff)
29
+ - [set](#set)
30
+ - [watches](#watches)
31
+ - [Development](#development)
32
+
33
+ <!-- END doctoc generated TOC please keep comment here to allow auto update -->
34
+ <!-- prettier-ignore-end -->
35
+
36
+ ## Install
37
+
38
+ ```console
39
+ $ npm install @arrai-innovations/reactive-helpers
40
+ ```
41
+
42
+ ## Usage
43
+
44
+ ### Import
45
+
46
+ ```js
47
+ // base import contains all exports
48
+ import { useListInstance, useObjectInstance } from "@arrai-innovations/reactive-helpers";
49
+ ```
50
+
51
+ ### List
52
+
53
+ #### Instance
54
+
55
+ The container for your list of objects, providing loading or error status.
56
+
57
+ ```js
58
+ // do this in your main.js
59
+ import { setListInstanceCrud } from "@arrai-innovations/reactive-helpers";
60
+ setListInstanceCrud({
61
+ list: async function listCrudAdaptor({ crudArgs, retrieveArgs, listArgs, pageCallback }) {
62
+ // todo: your implemenation here.
63
+ const listOfObjects = await talkToServer(crudArgs, retrieveArgs, listArgs);
64
+ pageCallback(listOfObjects);
65
+ const nextListOfObjects = await talkToServerAgain(crudArgs, retrieveArgs, listArgs);
66
+ pageCallback(nextListOfObjects);
67
+ },
68
+ });
69
+
70
+ // then use in your component
71
+ import { useListInstance } from "@arrai-innovations/reactive-helpers";
72
+ const contacts = useListInstance({
73
+ crudArgs: {
74
+ stream: "contacts",
75
+ },
76
+ defaultRetrieveArgs: {
77
+ fields: ["id", "has_name", "lexical_name", "organization", "phone"],
78
+ },
79
+ defaultListArgs: {
80
+ has_organization: true,
81
+ },
82
+ });
83
+
84
+ await contacts.list();
85
+ console.log(contacts.loading);
86
+ // False
87
+ console.log(contacts.errored);
88
+ // False
89
+ console.log(contacts.error);
90
+ // null
91
+ console.log(contacts.objects);
92
+ // { contacts keyed by 'id' }
93
+ contacts.defaultRetrieveArgs.fields.push("message_count");
94
+ await contacts.list();
95
+ console.log(contacts.objects);
96
+ // { contacts keyed by 'id' with message_count }
97
+ await contacts.list({ listArgs: {} });
98
+ console.log(contacts.objects);
99
+ // { contacts keyed by 'id' with organizationless contacts }
100
+ ```
101
+
102
+ #### Subscription
103
+
104
+ Adds functionality to a list instance to receive updates from the server.
105
+
106
+ ```js
107
+ // do this in your main.js
108
+ import { setListSubscriptionCrud } from "@arrai-innovations/reactive-helpers";
109
+ setListSubscriptionCrud({
110
+ subscribe: function subscribeCrudAdaptor({ crudArgs, retrieveArgs, listArgs, eventCallback }) {
111
+ // todo: your implemenation here.
112
+ const subscription = talkToServer(function (data, action) {
113
+ eventCallback(data, action);
114
+ });
115
+ // return a promise with a cancel action
116
+ subscription.cancel = async () => {
117
+ await cancelSubOnServer();
118
+ };
119
+ return subscription;
120
+ },
121
+ });
122
+
123
+ // then use in your component
124
+ import { useListInstance, useListSubscription } from "@arrai-innovations/reactive-helpers";
125
+ const contacts = useListInstance({
126
+ crudArgs: {
127
+ stream: "contacts",
128
+ },
129
+ defaultRetrieveArgs: {
130
+ fields: ["id", "has_name", "lexical_name", "organization", "phone"],
131
+ },
132
+ defaultListArgs: {
133
+ has_organization: true,
134
+ },
135
+ });
136
+ const contactsSubscription = useListSubscription({
137
+ listInstance: contacts,
138
+ crudArgs: {
139
+ stream: "contacts",
140
+ includeCreateEvents: true,
141
+ subscribeAction: "subscribe_contacts",
142
+ unsubscribeAction: "unsubscribe_contacts",
143
+ },
144
+ });
145
+
146
+ // only get new or updated contacts, not existing.
147
+ contactsSubscription.subscribe({ list: false });
148
+ // or, subscribe and get the existing list.
149
+ contactsSubscription.subscribe();
150
+ // stop getting updates.
151
+ contactsSubscription.unsubscribe();
152
+ // re-retreive the list of existing contacts including another field.
153
+ contacts.defaultRetrieveArgs.fields.push("message_count");
154
+ // re-retreive the list of all existing contacts.
155
+ delete contacts.defaultListArgs.has_organization;
156
+ ```
157
+
158
+ #### Related
159
+
160
+ Lookup foreign keys between list instances via watch, for using dot notation in templates to cross object relations.
161
+
162
+ ```js
163
+ // no main.js setup required.
164
+
165
+ // used in example below.
166
+ import { nextTick } from "vue";
167
+
168
+ // use in your component
169
+ import { useListInstance, useListRelated } from "@arrai-innovations/reactive-helpers";
170
+ const organizations = useListInstance({});
171
+ const contacts = useListInstance({
172
+ defaultRetrieveArgs: {
173
+ fields: ["id", "lexical_name", "organization"],
174
+ },
175
+ });
176
+ const contactsRelated = useListRelated({
177
+ listInstance: contacts,
178
+ relatedObjectsRules: {
179
+ organization: {
180
+ // desired key on relatedObjects
181
+ objects: toRef(organizations.state, "objects"), // organizations by id
182
+ pkKey: "organization", // reference key on contact for org id.
183
+ },
184
+ },
185
+ relatedObjectsPropertyName: "myRelatedObjects",
186
+ });
187
+ await organizations.list();
188
+ await contacts.list();
189
+ console.log(organizations.objects);
190
+ /*
191
+ {
192
+ "24": { "id": 24, "name": "org 24" },
193
+ "42": { "id": 42, "name": "org 42" },
194
+ }
195
+ */
196
+ console.log(contacts.objects);
197
+ /*
198
+ {
199
+ "15": {
200
+ "id": 15,
201
+ "lexical_name": "one, contact",
202
+ "organization": 42,
203
+ "relatedObjects": {
204
+ "organization": { "id": 42, "name": "org 42" }
205
+ }
206
+ }
207
+ }
208
+ */
209
+ contacts.objects["15"].organization = 24;
210
+ await nextTick();
211
+ console.log(contacts.objects["15"].myRelatedObjects);
212
+ /*
213
+ {
214
+ "organization": { "id": 24, "name": "org 24" }
215
+ }
216
+ */
217
+ delete contactsRelated.relatedObjectRules.organization;
218
+ await nextTick();
219
+ console.log(contacts.objects["15"].myRelatedObjects);
220
+ /* {} */
221
+ // manual stopage, inside a setup or another effect scope, there isnt a need to manually call this.
222
+ contactsRelated.effectScope.stop();
223
+ await nextTick();
224
+ console.log(contacts.objects["15"].myRelatedObjects);
225
+ /* undefined */
226
+ ```
227
+
228
+ #### Sort
229
+
230
+ ```js
231
+ // no main.js setup required.
232
+
233
+ // used in example below.
234
+ import { nextTick } from "vue";
235
+
236
+ // use in your component
237
+ import { useListInstance, useListSort } from "@arrai-innovations/reactive-helpers";
238
+ const contacts = useListInstance({
239
+ defaultRetrieveArgs: {
240
+ fields: ["id", "has_name", "lexical_name", "organization"],
241
+ },
242
+ });
243
+ const contactsSort = useListSort({
244
+ listInstance: contacts,
245
+ orderByRules: [
246
+ { key: "has_name", desc: true, localeCompare: false },
247
+ { key: "lexical_name", desc: false, localeCompare: true },
248
+ ],
249
+ });
250
+ await contacts.list();
251
+ console.log(contactsSort.state.order);
252
+ // array of ids in order, based on the specified rules.
253
+ console.log(contactsSort.state.objectsInOrder);
254
+ // computed array of the previous that also looks up the object ids in .objects
255
+ contactsSort.state.orderByRules[0].desc = false;
256
+ await nextTick();
257
+ console.log(contactsSort.state.order);
258
+ // array of ids in order, based on updated rules.
259
+ ```
260
+
261
+ #### Filter
262
+
263
+ ```js
264
+ // no main.js setup required.
265
+
266
+ // used in example below.
267
+ import { nextTick } from "vue";
268
+
269
+ // use in your component
270
+ import { useListInstance, useListFilter } from "@arrai-innovations/reactive-helpers";
271
+ const contacts = useListInstance({
272
+ defaultRetrieveArgs: {
273
+ fields: ["id", "has_name", "lexical_name", "organization"],
274
+ },
275
+ });
276
+ const myAllowedValues = reactive({ 1: true, 2: true, 3: true });
277
+ const contactsFilter = useListFilter({
278
+ listInstance: contacts,
279
+ allowedValues: myAllowedValues,
280
+ allowedFilter: function (object) {
281
+ return object.has_name === true;
282
+ },
283
+ });
284
+ await contacts.list();
285
+ console.log(contactsFilter.state.objects);
286
+ // only contains the objects passing the filter
287
+ delete myAllowedValues[3];
288
+ myAllowedValues[4] = True;
289
+ await nextTick();
290
+ console.log(contactsFilter.state.objects);
291
+ // array of ids in order, based on updated rules.
292
+ ```
293
+
294
+ #### All
295
+
296
+ Example using all of the above.
297
+
298
+ ```js
299
+ import {
300
+ useListInstance,
301
+ useListSubscription,
302
+ useListRelated,
303
+ useListFilter,
304
+ useListSort,
305
+ } from "@arrai-innovations/reactive-helpers";
306
+ const organizationNameSearch = ref("");
307
+ const organizations = useListInstance({
308
+ crudArgs: {
309
+ stream: "organizations",
310
+ },
311
+ });
312
+ const contacts = useListInstance({
313
+ crudArgs: {
314
+ stream: "contacts",
315
+ },
316
+ defaultRetrieveArgs: {
317
+ fields: ["id", "has_name", "lexical_name", "organization", "phone"],
318
+ },
319
+ defaultListArgs: {
320
+ has_organization: true,
321
+ },
322
+ });
323
+ const contactsSubscription = useListSubscription({
324
+ crudArgs: {
325
+ stream: "contacts",
326
+ includeCreateEvents: true,
327
+ },
328
+ listInstance: contacts,
329
+ });
330
+ const contactsRelated = useListRelated({
331
+ parentState: contactsSubscription.state,
332
+ relatedObjectsRules: {
333
+ organization: {
334
+ // desired key on relatedObjects
335
+ objects: toRef(organizations.state, "objects"), // organizations by id
336
+ pkKey: "organization", // reference key on contact for org id.
337
+ },
338
+ },
339
+ });
340
+ const contactsFiltered = useListFilter({
341
+ parentState: contactsRelated.state,
342
+ useTextSearch: true,
343
+ textSearchRules: ["relatedObjects.organization.name"],
344
+ textSearchValue: organizationNameSearch,
345
+ });
346
+ const contactsSorted = useListSort({
347
+ parentState: contactsFiltered.state,
348
+ orderByRules: [
349
+ { key: "relatedObjects.organization.name", desc: false, localeCompare: true },
350
+ { key: "lexical_name", desc: false, localeCompare: true },
351
+ ],
352
+ });
353
+ console.log(contactsSorted.state.value.objects);
354
+ console.log(contactsSorted.state.value.order);
355
+ console.log(contactsSorted.state.value.objectsInOrder);
356
+ // array of contacts, updating as new ones are created, related to organization, filtered by organziation name, sort organization name & lexical name.
357
+ ```
358
+
359
+ ### Object
360
+
361
+ ```js
362
+ import { useObjectInstance } from "@arrai-innovations/reactive-helpers";
363
+ const contact = useObjectInstance({});
364
+ // or
365
+ import { useObjectSubscription } from "@arrai-innovations/reactive-helpers";
366
+ const contact = useObjectSubscription({});
367
+ ```
368
+
369
+ ### Search
370
+
371
+ ```js
372
+ // no main.js setup required.
373
+ import { useSearch } from "@arrai-innovations/reactive-helpers";
374
+ const search = useSearch({});
375
+ ```
376
+
377
+ ### Utils
378
+
379
+ #### addOrUpdateReactiveObject & assignReactiveObject
380
+
381
+ `addOrUpdateReactiveObject` - Assigns properties of a source object onto a target object, using refs if both source and
382
+ target are reactive.
383
+
384
+ `assignReactiveObject` - same as `addOrUpdateReactiveObject`, but deletes keys from target that are not in source.
385
+
386
+ ```js
387
+ import { reactive, toRef, computed } from "vue";
388
+ import { assignReactiveObject, addOrUpdateReactiveObject } from "@arrai-innovations/reactive-helpers";
389
+
390
+ const target = reactive({ a: 1 });
391
+ const source = { a: 3, b: 4 };
392
+ const source2 = reactive({ b: 5 });
393
+
394
+ const a = toRef(target, "a");
395
+ const b = toRef(target, "b");
396
+ const mySum = computed(() => (a.value || 0) + (b.value || 0));
397
+
398
+ console.log(mySum.value); // 1
399
+ assignReactiveObject(target, source);
400
+ console.log(mySum.value); // 7
401
+ addOrUpdateReactiveObject(target, source2);
402
+ console.log({ ...target }); // { a: 3, b: 5 }
403
+ console.log(mySum.value); // 8
404
+ source2.b = 6;
405
+ console.log(mySum.value); // 9
406
+ assignReactiveObject(target, source2);
407
+ console.log({ ...target }); // { b: 6 }
408
+ console.log(mySum.value); // 6
409
+ source2.b = 10;
410
+ console.log(mySum.value); // 10
411
+ ```
412
+
413
+ #### keyDiff
414
+
415
+ `keyDiff` - returns the various set results related to figuring out the changes in keys over time on an object or related objects. Returns the intersection as sameKeys, the difference of old and new as removedKeys, and the difference of new and old as addedKeys.
416
+
417
+ ```js
418
+ import { keyDiff } from "@arrai-innovations/reactive-helpers";
419
+ const newKeys = Object.keys({ a: 1, b: 2 });
420
+ const oldKeys = Object.keys({ a: 1, c: 3 });
421
+ const { addedKeys, removedKeys, sameKeys } = keyDiff(newKeys, oldKeys);
422
+ console.log({ addedKeys, removedKeys, sameKeys });
423
+ // { addedKeys: Set(1) { 'b' }, removedKeys: Set(1) { 'c' }, sameKeys: Set(1) { 'a' } }
424
+ ```
425
+
426
+ #### set
427
+
428
+ We make use of the basic set operations provided as example on mdn:
429
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set#implementing_basic_set_operations
430
+
431
+ ```js
432
+ import { isSuperset, union, intersection, symmetricDifference, difference } from "@arrai-innovations/reactive-helpers";
433
+ isSuperset(new Set([1, 2, 3, 4]), new Set([1, 2, 3])); // true
434
+ union(new Set([1, 2, 3, 4]), new Set([1, 2, 3])); // Set { 1, 2, 3, 4 }
435
+ intersection(new Set([1, 2, 3, 4]), new Set([1, 2, 3])); // Set { 1, 2, 3 }
436
+ symmetricDifference(new Set([1, 2, 3, 4]), new Set([1, 2, 3, 5])); // Set { 4, 5 }
437
+ difference(new Set([1, 2, 3, 4]), new Set([1, 2, 3, 5])); // Set { 4 }
438
+ difference(new Set([1, 2, 3, 5]), new Set([1, 2, 3, 4])); // Set { 5 }
439
+ ```
440
+
441
+ #### watches
442
+
443
+ `ImmediateStopWatch` - a vue.js watch that can be stopped in the first iteration
444
+
445
+ ```js
446
+ import { ImmediateStopWatch } from "@arrai-innovations/reactive-helpers";
447
+ const reactiveObject = reactive({ a: 1 });
448
+ const watch = new ImmediateStopWatch();
449
+ const watch2 = new ImmediateStopWatch();
450
+ watch.start(
451
+ () => reactiveObject.a,
452
+ (newA) => {
453
+ if (newA === 2) {
454
+ console.log("a is now 2!");
455
+ watch.stop();
456
+ }
457
+ },
458
+ [reactiveObject.a]
459
+ );
460
+ watch2.start(
461
+ () => reactiveObject.a,
462
+ (newA) => {
463
+ if (newA === 1) {
464
+ console.log("a is now 1!");
465
+ watch2.stop();
466
+ }
467
+ },
468
+ [reactiveObject.a]
469
+ );
470
+ // a is now 1!
471
+ await nextTick();
472
+ reactiveObject.a = 2;
473
+ await nextTick();
474
+ // a is now 2!
475
+ await nextTick();
476
+ reactiveObject.a = 1;
477
+ await nextTick();
478
+ // no output;
479
+ reactiveObject.a = 2;
480
+ await nextTick();
481
+ // no output;
482
+ ```
483
+
484
+ `AwaitTimeout` - a class that provides a promise that resolves after a timeout.
485
+
486
+ ```js
487
+ import { AwaitTimeout } from "@arrai-innovations/reactive-helpers";
488
+ const awaitTimeout = new AwaitTimeout({ timeout: 1000 });
489
+ awaitTimeout.start();
490
+ await awaitTimeout.promise; // waits 1 second
491
+ const awaitTimeout2 = new AwaitTimeout({ timeout: 1000 });
492
+ awaitTimeout2.start();
493
+ setTimeout(() => awaitTimeout2.stop(), 500);
494
+ await awaitTimeout2.promise; // waits 500 ms then rejects with new AwaitTimeoutError("Cancelled", "timeout_cancelled")
495
+ ```
496
+
497
+ `doAwaitTimeout` - a function that returns a promise that resolves after a timeout.
498
+
499
+ ```js
500
+ import { doAwaitTimeout } from "@arrai-innovations/reactive-helpers";
501
+ // non cancellable AwaitTimeout helper
502
+ await doAwaitTimeout(1000); // waits 1 second
503
+ ```
504
+
505
+ `AwaitNot` - a class that provides a promise that resolves when a watched prop switches to true then to false.
506
+
507
+ ```mermaid
508
+ flowchart TD
509
+ A["new AwaitNot"] --> B["AwaitNot.start()"];
510
+ B -- timeout --> G["AwaitNot.promise is rejected"]
511
+ B --> C{"prop is false"};
512
+ C -- false --> D["AwaitNot.waitForTrue()"];
513
+ C -- true --> H{"canAlreadyBeFalse"}
514
+ H -- true --> D;
515
+ H -- false --> E["AwaitNot.waitForFalse()"];
516
+
517
+ D -- timeout --> G
518
+ E -- timeout --> G
519
+ E -- prop is false --> F["AwaitNot.promise is resolved"]
520
+ D -- prop is true --> E
521
+ ```
522
+
523
+ ```js
524
+ import { reactive } from "vue";
525
+ const reactiveObject = reactive({});
526
+ import { AwaitNot } from "@arrai-innovations/reactive-helpers";
527
+ const awaitNot = new AwaitNot({
528
+ obj: reactiveObject,
529
+ prop: "prop",
530
+ couldAlreadyBeFalse: true, // default
531
+ timeout: 1000, // default
532
+ });
533
+ awaitNot.promise
534
+ .then(() => {
535
+ console.log("resolved");
536
+ })
537
+ .catch(() => {
538
+ console.log("rejected");
539
+ });
540
+ awaitNot.start();
541
+ await nextTick();
542
+ reactiveObject.prop = true;
543
+ await nextTick();
544
+ reactiveObject.prop = false;
545
+ await nextTick();
546
+ // resolved
547
+ const awaitNot2 = new AwaitNot({
548
+ obj: reactiveObject,
549
+ prop: "prop",
550
+ couldAlreadyBeFalse: true, // default
551
+ timeout: 500,
552
+ });
553
+ awaitNot2.promise
554
+ .then(() => {
555
+ console.log("resolved");
556
+ })
557
+ .catch(() => {
558
+ console.log("rejected");
559
+ });
560
+ awaitNot2.start();
561
+ await awaitNot2.promise;
562
+ // rejected
563
+ const awaitNot3 = new AwaitNot({
564
+ obj: reactiveObject,
565
+ prop: "prop",
566
+ couldAlreadyBeFalse: false,
567
+ timeout: 1000, // default
568
+ });
569
+ awaitNot3.promise
570
+ .then(() => {
571
+ console.log("resolved");
572
+ })
573
+ .catch(() => {
574
+ console.log("rejected");
575
+ });
576
+ awaitNot3.start();
577
+ await awaitNot3.promise;
578
+ // resolved
579
+ ```
580
+
581
+ ## Development
582
+
583
+ 1. Checkout this repo:
584
+ ```bash
585
+ $ git clone git@github.com:arrai-innovations/reactive-helpers.git
586
+ ```
587
+ 2. Install dependencies:
588
+ ```bash
589
+ $ npm ci
590
+ ```
591
+ 3. Run tests via jest:
592
+ ```bash
593
+ $ npm test
594
+ ```
595
+ 4. Run tests with coverage output:
596
+ ```bash
597
+ $ npm run coverage
598
+ ```
@@ -0,0 +1,15 @@
1
+ // Only used by Jest
2
+ module.exports = {
3
+ presets: [["@babel/preset-env", { useBuiltIns: "entry", corejs: "2", targets: { node: "current" } }]],
4
+ plugins: [
5
+ function () {
6
+ return {
7
+ visitor: {
8
+ MetaProperty(path) {
9
+ path.replaceWithSourceString("process");
10
+ },
11
+ },
12
+ };
13
+ },
14
+ ],
15
+ };
package/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./use/";
2
+ export * from "./utils/";
@@ -0,0 +1,27 @@
1
+ import path, { dirname } from "path";
2
+ import { fileURLToPath } from "url";
3
+
4
+ export default {
5
+ rootDir: path.resolve(dirname(fileURLToPath(import.meta.url))),
6
+ clearMocks: true,
7
+ coverageDirectory: "coverage",
8
+ coverageProvider: "babel",
9
+ coverageReporters: ["json-summary", "html", "clover", "text"],
10
+ moduleFileExtensions: ["vue", "js", "json"],
11
+ moduleNameMapper: {
12
+ "@/(.*)$": "<rootDir>/src/$1",
13
+ "~/(.*)$": "<rootDir>/node_modules/$1",
14
+ },
15
+ testEnvironment: "jsdom",
16
+ testMatch: ["<rootDir>/tests/unit/**/*.spec.js"],
17
+ testPathIgnorePatterns: [],
18
+ transform: {
19
+ "^.+\\.vue$": "@vue/vue3-jest",
20
+ "^.+\\js$": "babel-jest",
21
+ },
22
+ globals: {
23
+ "@vue/vue3-jest": {
24
+ babelConfig: true,
25
+ },
26
+ },
27
+ };
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@arrai-innovations/reactive-helpers",
3
+ "version": "1.0.0",
4
+ "description": "VueJS 3 utility composition functions to help manipulate objects and lists.",
5
+ "main": "index.js",
6
+ "directories": {
7
+ "use": "use",
8
+ "utils": "utils",
9
+ "tests": "tests"
10
+ },
11
+ "scripts": {
12
+ "test": "jest",
13
+ "coverage": "npm run coverage:clean; npm test -- --coverage=true",
14
+ "coverage:clean": "rm -rf coverage",
15
+ "_postinstall": "if [ \"$NODE_ENV\" != \"production\" ] && [ ./node_modules/.bin/is-ci ]; then ./node_modules/.bin/husky install; fi",
16
+ "prepublishOnly": "pinst --disable",
17
+ "postpublish": "pinst --enable"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/arrai-innovations/reactive-helpers.git"
22
+ },
23
+ "author": "Arrai Innovations Inc. <developers@arrai.com>",
24
+ "license": "UNLICENSED",
25
+ "bugs": {
26
+ "url": "https://github.com/arrai-innovations/reactive-helpers/issues"
27
+ },
28
+ "homepage": "https://github.com/arrai-innovations/reactive-helpers#readme",
29
+ "devDependencies": {
30
+ "@arrai-innovations/commitlint-config": "^1.1.0",
31
+ "@babel/preset-env": "^7.17.10",
32
+ "@commitlint/cli": "^16.2.4",
33
+ "@types/jest": "^27.5.0",
34
+ "@vue/compiler-sfc": "^3.2.33",
35
+ "@vue/eslint-config-prettier": "^7.0.0",
36
+ "@vue/test-utils": "^2.0.0-rc.21",
37
+ "@vue/vue3-jest": "^27.0.0",
38
+ "babel-eslint": "^10.1.0",
39
+ "babel-jest": "^27.5.1",
40
+ "doctoc": "^2.1.0",
41
+ "eslint": "^7.32.0",
42
+ "eslint-plugin-jest": "^26.1.5",
43
+ "eslint-plugin-vue": "^8.7.1",
44
+ "flush-promises": "^1.0.2",
45
+ "lint-staged": "^11.1.2",
46
+ "prettier": "2.6.2"
47
+ },
48
+ "dependencies": {
49
+ "browser-util-inspect": "^0.2.0",
50
+ "flexsearch": "^0.7.21",
51
+ "husky": "^7.0.4",
52
+ "is-ci": "^3.0.1",
53
+ "jest": "^27.5.1",
54
+ "lodash": "^4.17.21",
55
+ "pinst": "^3.0.0",
56
+ "vue": "^3.2.33",
57
+ "vue-router": "^4.0.15"
58
+ }
59
+ }