@arrai-innovations/reactive-helpers 1.2.3 → 2.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.
- package/.lintstagedrc +3 -0
- package/README.md +21 -16
- package/package.json +1 -1
- package/tests/unit/crudPromise.js +23 -0
- package/tests/unit/poll.js +50 -0
- package/tests/unit/use/listInstance.spec.js +47 -89
- package/tests/unit/use/listSort.spec.js +1 -1
- package/tests/unit/use/listSubscription.spec.js +184 -104
- package/tests/unit/use/objectInstance.spec.js +156 -147
- package/tests/unit/use/objectSubscription.spec.js +144 -120
- package/tests/unit/use/watches.spec.js +2 -2
- package/use/listInstance.js +39 -28
- package/use/listSubscription.js +80 -34
- package/use/objectInstance.js +34 -43
- package/use/objectSubscription.js +66 -71
- package/use/paginatedListInstance.js +2 -2
- package/utils/cancellableIntent.js +93 -0
- package/utils/index.js +1 -0
package/.lintstagedrc
CHANGED
package/README.md
CHANGED
|
@@ -68,17 +68,19 @@ setListInstanceCrud({
|
|
|
68
68
|
});
|
|
69
69
|
|
|
70
70
|
// then use in your component
|
|
71
|
+
import { reactive } from "vue";
|
|
71
72
|
import { useListInstance } from "@arrai-innovations/reactive-helpers";
|
|
73
|
+
const listArgs = reactive({
|
|
74
|
+
has_organization: true,
|
|
75
|
+
});
|
|
72
76
|
const contacts = useListInstance({
|
|
73
77
|
crudArgs: {
|
|
74
78
|
stream: "contacts",
|
|
75
79
|
},
|
|
76
|
-
|
|
80
|
+
retrieveArgs: {
|
|
77
81
|
fields: ["id", "has_name", "lexical_name", "organization", "phone"],
|
|
78
82
|
},
|
|
79
|
-
|
|
80
|
-
has_organization: true,
|
|
81
|
-
},
|
|
83
|
+
listArgs,
|
|
82
84
|
});
|
|
83
85
|
|
|
84
86
|
await contacts.list();
|
|
@@ -90,11 +92,14 @@ console.log(contacts.error);
|
|
|
90
92
|
// null
|
|
91
93
|
console.log(contacts.objects);
|
|
92
94
|
// { contacts keyed by 'id' }
|
|
93
|
-
|
|
95
|
+
// change list or retrieve args directly
|
|
96
|
+
contacts.state.retrieveArgs.fields.push("message_count");
|
|
94
97
|
await contacts.list();
|
|
95
98
|
console.log(contacts.objects);
|
|
96
99
|
// { contacts keyed by 'id' with message_count }
|
|
97
|
-
|
|
100
|
+
// change list or retrieve args indirectly
|
|
101
|
+
listArgs.has_organization = false;
|
|
102
|
+
await contacts.list();
|
|
98
103
|
console.log(contacts.objects);
|
|
99
104
|
// { contacts keyed by 'id' with organizationless contacts }
|
|
100
105
|
```
|
|
@@ -126,10 +131,10 @@ const contacts = useListInstance({
|
|
|
126
131
|
crudArgs: {
|
|
127
132
|
stream: "contacts",
|
|
128
133
|
},
|
|
129
|
-
|
|
134
|
+
retrieveArgs: {
|
|
130
135
|
fields: ["id", "has_name", "lexical_name", "organization", "phone"],
|
|
131
136
|
},
|
|
132
|
-
|
|
137
|
+
listArgs: {
|
|
133
138
|
has_organization: true,
|
|
134
139
|
},
|
|
135
140
|
});
|
|
@@ -144,15 +149,15 @@ const contactsSubscription = useListSubscription({
|
|
|
144
149
|
});
|
|
145
150
|
|
|
146
151
|
// only get new or updated contacts, not existing.
|
|
147
|
-
contactsSubscription.subscribe(
|
|
152
|
+
contactsSubscription.subscribe();
|
|
148
153
|
// or, subscribe and get the existing list.
|
|
149
154
|
contactsSubscription.subscribe();
|
|
150
155
|
// stop getting updates.
|
|
151
156
|
contactsSubscription.unsubscribe();
|
|
152
157
|
// re-retreive the list of existing contacts including another field.
|
|
153
|
-
contacts.
|
|
158
|
+
contacts.retrieveArgs.fields.push("message_count");
|
|
154
159
|
// re-retreive the list of all existing contacts.
|
|
155
|
-
delete contacts.
|
|
160
|
+
delete contacts.listArgs.has_organization;
|
|
156
161
|
```
|
|
157
162
|
|
|
158
163
|
#### Related
|
|
@@ -169,7 +174,7 @@ import { nextTick } from "vue";
|
|
|
169
174
|
import { useListInstance, useListRelated } from "@arrai-innovations/reactive-helpers";
|
|
170
175
|
const organizations = useListInstance({});
|
|
171
176
|
const contacts = useListInstance({
|
|
172
|
-
|
|
177
|
+
retrieveArgs: {
|
|
173
178
|
fields: ["id", "lexical_name", "organization"],
|
|
174
179
|
},
|
|
175
180
|
});
|
|
@@ -236,7 +241,7 @@ import { nextTick } from "vue";
|
|
|
236
241
|
// use in your component
|
|
237
242
|
import { useListInstance, useListSort } from "@arrai-innovations/reactive-helpers";
|
|
238
243
|
const contacts = useListInstance({
|
|
239
|
-
|
|
244
|
+
retrieveArgs: {
|
|
240
245
|
fields: ["id", "has_name", "lexical_name", "organization"],
|
|
241
246
|
},
|
|
242
247
|
});
|
|
@@ -269,7 +274,7 @@ import { nextTick } from "vue";
|
|
|
269
274
|
// use in your component
|
|
270
275
|
import { useListInstance, useListFilter } from "@arrai-innovations/reactive-helpers";
|
|
271
276
|
const contacts = useListInstance({
|
|
272
|
-
|
|
277
|
+
retrieveArgs: {
|
|
273
278
|
fields: ["id", "has_name", "lexical_name", "organization"],
|
|
274
279
|
},
|
|
275
280
|
});
|
|
@@ -313,10 +318,10 @@ const contacts = useListInstance({
|
|
|
313
318
|
crudArgs: {
|
|
314
319
|
stream: "contacts",
|
|
315
320
|
},
|
|
316
|
-
|
|
321
|
+
retrieveArgs: {
|
|
317
322
|
fields: ["id", "has_name", "lexical_name", "organization", "phone"],
|
|
318
323
|
},
|
|
319
|
-
|
|
324
|
+
listArgs: {
|
|
320
325
|
has_organization: true,
|
|
321
326
|
},
|
|
322
327
|
});
|
package/package.json
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export class Resolvable {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.promise = new Promise((resolve, reject) => {
|
|
4
|
+
this.resolve = resolve;
|
|
5
|
+
this.reject = reject;
|
|
6
|
+
});
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class CancellableResolvable {
|
|
11
|
+
constructor() {
|
|
12
|
+
const newResolvable = new Resolvable();
|
|
13
|
+
const cancelResolvable = new Resolvable();
|
|
14
|
+
newResolvable.promise.cancel = jest
|
|
15
|
+
.fn()
|
|
16
|
+
.mockImplementationOnce(async () => {
|
|
17
|
+
return cancelResolvable.promise;
|
|
18
|
+
})
|
|
19
|
+
.mockRejectedValue(new Error("cancel already called"));
|
|
20
|
+
Object.assign(this, newResolvable);
|
|
21
|
+
this.cancel = cancelResolvable;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Resolvable } from "./crudPromise";
|
|
2
|
+
|
|
3
|
+
export async function poll(condition, interval = 100, timeout = 1000) {
|
|
4
|
+
let intervalId,
|
|
5
|
+
timeoutId,
|
|
6
|
+
resolvable = new Resolvable();
|
|
7
|
+
|
|
8
|
+
const calledStack = new Error().stack.split("\n").slice(2).join("\n");
|
|
9
|
+
|
|
10
|
+
function stop() {
|
|
11
|
+
if (intervalId) {
|
|
12
|
+
clearInterval(intervalId);
|
|
13
|
+
intervalId = null;
|
|
14
|
+
}
|
|
15
|
+
if (timeoutId) {
|
|
16
|
+
clearTimeout(timeoutId);
|
|
17
|
+
timeoutId = null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function doInterval() {
|
|
22
|
+
if (condition()) {
|
|
23
|
+
stop();
|
|
24
|
+
if (resolvable) {
|
|
25
|
+
resolvable.resolve();
|
|
26
|
+
resolvable = null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function doTimeout() {
|
|
32
|
+
stop();
|
|
33
|
+
let conditionValue = condition();
|
|
34
|
+
if (conditionValue) {
|
|
35
|
+
if (resolvable) {
|
|
36
|
+
resolvable.resolve();
|
|
37
|
+
resolvable = null;
|
|
38
|
+
}
|
|
39
|
+
} else if (resolvable) {
|
|
40
|
+
let timedOutError = new Error(`poll timeout: condition was never true (it was last ${conditionValue})`);
|
|
41
|
+
timedOutError.stack = timedOutError.stack.split("\n").slice(0, 1).join("\n") + "\n" + calledStack;
|
|
42
|
+
resolvable.reject(timedOutError);
|
|
43
|
+
resolvable = null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
intervalId = setInterval(doInterval, interval);
|
|
47
|
+
timeoutId = setTimeout(doTimeout, timeout);
|
|
48
|
+
|
|
49
|
+
return resolvable.promise;
|
|
50
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { expectErrorToBeNull } from "../expectHelpers";
|
|
2
|
-
import { isReactive, nextTick } from "vue";
|
|
2
|
+
import { isReactive, nextTick, reactive } from "vue";
|
|
3
3
|
import { keyBy } from "lodash";
|
|
4
4
|
import flushPromises from "flush-promises";
|
|
5
5
|
import { inspect } from "util";
|
|
@@ -68,7 +68,16 @@ describe("use/listInstance.spec.js", function () {
|
|
|
68
68
|
const crudListResolvedObjects = keyBy([...crudListResolvedPage1, ...crudListResolvedPage2], "id");
|
|
69
69
|
describe("list", function () {
|
|
70
70
|
it("success", async function () {
|
|
71
|
-
const
|
|
71
|
+
const listArgs = reactive({
|
|
72
|
+
user: 1,
|
|
73
|
+
});
|
|
74
|
+
const retrieveArgs = reactive({
|
|
75
|
+
fields,
|
|
76
|
+
});
|
|
77
|
+
const listInstance = useListInstance({
|
|
78
|
+
listArgs,
|
|
79
|
+
retrieveArgs,
|
|
80
|
+
});
|
|
72
81
|
let crudListResolve;
|
|
73
82
|
const crudListPromise = new Promise((resolve) => {
|
|
74
83
|
crudListResolve = resolve;
|
|
@@ -84,10 +93,7 @@ describe("use/listInstance.spec.js", function () {
|
|
|
84
93
|
expect(listInstance.state.loading).toBeUndefined();
|
|
85
94
|
expect({ ...listInstance.state.objects }).toEqual({});
|
|
86
95
|
|
|
87
|
-
const liListResolve = listInstance.list(
|
|
88
|
-
listArgs: { user: 1 },
|
|
89
|
-
retrieveArgs: { fields: fields },
|
|
90
|
-
});
|
|
96
|
+
const liListResolve = listInstance.list();
|
|
91
97
|
|
|
92
98
|
expectErrorToBeNull(listInstance.state.error);
|
|
93
99
|
expect(listInstance.state.errored).toBe(false);
|
|
@@ -128,23 +134,24 @@ describe("use/listInstance.spec.js", function () {
|
|
|
128
134
|
expect(globalList).toHaveBeenCalledTimes(1);
|
|
129
135
|
});
|
|
130
136
|
it("already loading", async function () {
|
|
131
|
-
const
|
|
137
|
+
const listArgs = reactive({
|
|
138
|
+
user: 1,
|
|
139
|
+
});
|
|
140
|
+
const retrieveArgs = reactive({
|
|
141
|
+
fields,
|
|
142
|
+
});
|
|
143
|
+
const listInstance = useListInstance({
|
|
144
|
+
listArgs,
|
|
145
|
+
retrieveArgs,
|
|
146
|
+
});
|
|
132
147
|
expectErrorToBeNull(listInstance.state.error);
|
|
133
148
|
expect(listInstance.state.errored).toBe(false);
|
|
134
149
|
expect(listInstance.state.loading).toBeUndefined();
|
|
135
150
|
expect({ ...listInstance.state.objects }).toEqual({});
|
|
136
151
|
globalList.mockImplementation(() => new Promise(() => {}));
|
|
137
152
|
|
|
138
|
-
listInstance.list(
|
|
139
|
-
|
|
140
|
-
retrieveArgs: { fields: fields },
|
|
141
|
-
});
|
|
142
|
-
await expect(
|
|
143
|
-
listInstance.list({
|
|
144
|
-
listArgs: { user: 1 },
|
|
145
|
-
retrieveArgs: { fields: fields },
|
|
146
|
-
})
|
|
147
|
-
).rejects.toThrow(ListError);
|
|
153
|
+
listInstance.list();
|
|
154
|
+
await expect(listInstance.list()).rejects.toThrow(ListError);
|
|
148
155
|
|
|
149
156
|
expect(globalList).toHaveBeenCalledWith({
|
|
150
157
|
crudArgs: { stream: "test_stream" },
|
|
@@ -159,7 +166,16 @@ describe("use/listInstance.spec.js", function () {
|
|
|
159
166
|
expect({ ...listInstance.state.objects }).toEqual({});
|
|
160
167
|
});
|
|
161
168
|
it("errored", async function () {
|
|
162
|
-
const
|
|
169
|
+
const listArgs = reactive({
|
|
170
|
+
user: 1,
|
|
171
|
+
});
|
|
172
|
+
const retrieveArgs = reactive({
|
|
173
|
+
fields,
|
|
174
|
+
});
|
|
175
|
+
const listInstance = useListInstance({
|
|
176
|
+
listArgs,
|
|
177
|
+
retrieveArgs,
|
|
178
|
+
});
|
|
163
179
|
let crudListReject;
|
|
164
180
|
const crudListPromise = new Promise((resolve, reject) => {
|
|
165
181
|
crudListReject = reject;
|
|
@@ -175,10 +191,7 @@ describe("use/listInstance.spec.js", function () {
|
|
|
175
191
|
expect(listInstance.state.loading).toBeUndefined();
|
|
176
192
|
expect({ ...listInstance.state.objects }).toEqual({});
|
|
177
193
|
|
|
178
|
-
const liListResolve = listInstance.list(
|
|
179
|
-
listArgs: { user: 1 },
|
|
180
|
-
retrieveArgs: { fields: fields },
|
|
181
|
-
});
|
|
194
|
+
const liListResolve = listInstance.list();
|
|
182
195
|
|
|
183
196
|
expectErrorToBeNull(listInstance.state.error);
|
|
184
197
|
expect(listInstance.state.errored).toBe(false);
|
|
@@ -205,68 +218,16 @@ describe("use/listInstance.spec.js", function () {
|
|
|
205
218
|
});
|
|
206
219
|
expect(globalList).toHaveBeenCalledTimes(1);
|
|
207
220
|
});
|
|
208
|
-
it("success (
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
defaultRetrieveArgs: { fields: fields },
|
|
212
|
-
});
|
|
213
|
-
let crudListResolve;
|
|
214
|
-
const crudListPromise = new Promise((resolve) => {
|
|
215
|
-
crudListResolve = resolve;
|
|
216
|
-
});
|
|
217
|
-
let passedPageCallback;
|
|
218
|
-
globalList.mockImplementation(({ pageCallback }) => {
|
|
219
|
-
passedPageCallback = pageCallback;
|
|
220
|
-
return crudListPromise;
|
|
221
|
+
it("success (custom stream)", async function () {
|
|
222
|
+
const listArgs = reactive({
|
|
223
|
+
user: 1,
|
|
221
224
|
});
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
expect(listInstance.state.errored).toBe(false);
|
|
225
|
-
expect(listInstance.state.loading).toBeUndefined();
|
|
226
|
-
expect({ ...listInstance.state.objects }).toEqual({});
|
|
227
|
-
|
|
228
|
-
const liListResolve = listInstance.list();
|
|
229
|
-
|
|
230
|
-
expectErrorToBeNull(listInstance.state.error);
|
|
231
|
-
expect(listInstance.state.errored).toBe(false);
|
|
232
|
-
expect(listInstance.state.loading).toBe(true);
|
|
233
|
-
expect({ ...listInstance.state.object }).toEqual({});
|
|
234
|
-
|
|
235
|
-
await nextTick();
|
|
236
|
-
|
|
237
|
-
passedPageCallback(crudListResolvedPage1);
|
|
238
|
-
|
|
239
|
-
expectErrorToBeNull(listInstance.state.error);
|
|
240
|
-
expect(listInstance.state.errored).toBe(false);
|
|
241
|
-
expect(listInstance.state.loading).toBe(true);
|
|
242
|
-
expect({ ...listInstance.state.objects }).toEqual(crudListResolvedObjectsMid);
|
|
243
|
-
|
|
244
|
-
passedPageCallback(crudListResolvedPage2);
|
|
245
|
-
|
|
246
|
-
expectErrorToBeNull(listInstance.state.error);
|
|
247
|
-
expect(listInstance.state.errored).toBe(false);
|
|
248
|
-
expect(listInstance.state.loading).toBe(true);
|
|
249
|
-
expect({ ...listInstance.state.objects }).toEqual(crudListResolvedObjects);
|
|
250
|
-
|
|
251
|
-
crudListResolve();
|
|
252
|
-
await flushPromises();
|
|
253
|
-
|
|
254
|
-
await expect(liListResolve).resolves.toBe(true);
|
|
255
|
-
|
|
256
|
-
expectErrorToBeNull(listInstance.state.error);
|
|
257
|
-
expect(listInstance.state.errored).toBe(false);
|
|
258
|
-
expect(listInstance.state.loading).toBe(false);
|
|
259
|
-
expect({ ...listInstance.state.objects }).toEqual(crudListResolvedObjects);
|
|
260
|
-
expect(globalList).toHaveBeenCalledWith({
|
|
261
|
-
crudArgs: { stream: "test_stream" },
|
|
262
|
-
listArgs: { user: 1 },
|
|
263
|
-
retrieveArgs: { fields: fields },
|
|
264
|
-
pageCallback: passedPageCallback,
|
|
225
|
+
const retrieveArgs = reactive({
|
|
226
|
+
fields,
|
|
265
227
|
});
|
|
266
|
-
expect(globalList).toHaveBeenCalledTimes(1);
|
|
267
|
-
});
|
|
268
|
-
it("success (custom stream)", async function () {
|
|
269
228
|
const listInstance = useListInstance({
|
|
229
|
+
listArgs,
|
|
230
|
+
retrieveArgs,
|
|
270
231
|
crudArgs: { stream: "custom_stream" },
|
|
271
232
|
});
|
|
272
233
|
let crudListResolve;
|
|
@@ -284,10 +245,7 @@ describe("use/listInstance.spec.js", function () {
|
|
|
284
245
|
expect(listInstance.state.loading).toBeUndefined();
|
|
285
246
|
expect({ ...listInstance.state.objects }).toEqual({});
|
|
286
247
|
|
|
287
|
-
const liListResolve = listInstance.list(
|
|
288
|
-
listArgs: { user: 1 },
|
|
289
|
-
retrieveArgs: { fields: fields },
|
|
290
|
-
});
|
|
248
|
+
const liListResolve = listInstance.list();
|
|
291
249
|
|
|
292
250
|
expectErrorToBeNull(listInstance.state.error);
|
|
293
251
|
expect(listInstance.state.errored).toBe(false);
|
|
@@ -390,8 +348,8 @@ describe("use/listInstance.spec.js", function () {
|
|
|
390
348
|
});
|
|
391
349
|
it("succeeds", async function () {
|
|
392
350
|
const listInstance = useListInstance({
|
|
393
|
-
|
|
394
|
-
|
|
351
|
+
listArgs: { user: 1 },
|
|
352
|
+
retrieveArgs: { fields: fields },
|
|
395
353
|
});
|
|
396
354
|
let crudListResolve;
|
|
397
355
|
const crudListPromise = new Promise((resolve) => {
|
|
@@ -445,8 +403,8 @@ describe("use/listInstance.spec.js", function () {
|
|
|
445
403
|
name: "yiuo",
|
|
446
404
|
};
|
|
447
405
|
const listInstance = useListInstance({
|
|
448
|
-
|
|
449
|
-
|
|
406
|
+
listArgs: { user: 1 },
|
|
407
|
+
retrieveArgs: { fields: fields },
|
|
450
408
|
});
|
|
451
409
|
let crudListResolve;
|
|
452
410
|
const crudListPromise = new Promise((resolve) => {
|
|
@@ -45,7 +45,7 @@ describe("use/useListSort", () => {
|
|
|
45
45
|
{ key: "lexical_name", desc: false, localeCompare: true },
|
|
46
46
|
];
|
|
47
47
|
listInstance = useListInstance({
|
|
48
|
-
|
|
48
|
+
retrieveArgs: {
|
|
49
49
|
fields: ["id", "lexical_name", "organization", "relatedObjects"],
|
|
50
50
|
},
|
|
51
51
|
});
|