@thepassle/app-tools 0.10.3 → 1.0.1
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/api/index.js +129 -91
- package/api/plugins/cache.js +39 -20
- package/api/plugins/retry.js +44 -0
- package/dialog/dialog.test.js +47 -47
- package/package.json +1 -1
- package/types/api/plugins/cache.d.ts +3 -2
- package/types/api/plugins/retry.d.ts +12 -0
- package/types/api/types.d.ts +1 -1
- package/types/state/index copy.d.ts +0 -50
package/api/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createLogger } from
|
|
2
|
-
const log = createLogger(
|
|
1
|
+
import { createLogger } from "../utils/log.js";
|
|
2
|
+
const log = createLogger("api");
|
|
3
3
|
|
|
4
4
|
class StatusError extends Error {
|
|
5
5
|
constructor(response) {
|
|
@@ -10,7 +10,7 @@ class StatusError extends Error {
|
|
|
10
10
|
|
|
11
11
|
function handleStatus(response) {
|
|
12
12
|
if (!response.ok) {
|
|
13
|
-
log(
|
|
13
|
+
log("Response not ok", response);
|
|
14
14
|
throw new StatusError(response);
|
|
15
15
|
}
|
|
16
16
|
return response;
|
|
@@ -24,7 +24,7 @@ function handleStatus(response) {
|
|
|
24
24
|
/** @typedef {import('./types.js').MetaParams} MetaParams */
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
* @example
|
|
27
|
+
* @example
|
|
28
28
|
* const api = new Api({
|
|
29
29
|
* baseURL: 'https://api.foo.com/',
|
|
30
30
|
* responseType: 'text',
|
|
@@ -39,130 +39,168 @@ function handleStatus(response) {
|
|
|
39
39
|
export class Api {
|
|
40
40
|
/** @param {Config} config */
|
|
41
41
|
constructor(config = {}) {
|
|
42
|
-
this.config = {
|
|
42
|
+
this.config = {
|
|
43
43
|
plugins: [],
|
|
44
|
-
responseType:
|
|
45
|
-
...config
|
|
44
|
+
responseType: "json",
|
|
45
|
+
...config,
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
|
-
* @param {string} url
|
|
51
|
-
* @param {Method} method
|
|
52
|
-
* @param {RequestOptions} [opts]
|
|
50
|
+
* @param {string} url
|
|
51
|
+
* @param {Method} method
|
|
52
|
+
* @param {RequestOptions} [opts]
|
|
53
53
|
* @param {object} [data]
|
|
54
|
-
* @returns
|
|
54
|
+
* @returns
|
|
55
55
|
*/
|
|
56
56
|
async fetch(url, method, opts, data) {
|
|
57
57
|
const plugins = [...this.config.plugins, ...(opts?.plugins || [])];
|
|
58
58
|
|
|
59
59
|
let fetchFn = globalThis.fetch;
|
|
60
|
-
let baseURL = opts?.baseURL ?? this.config?.baseURL ??
|
|
60
|
+
let baseURL = opts?.baseURL ?? this.config?.baseURL ?? "";
|
|
61
61
|
let responseType = opts?.responseType ?? this.config.responseType;
|
|
62
62
|
let headers = new Headers({
|
|
63
|
-
|
|
64
|
-
...opts?.headers
|
|
63
|
+
"Content-Type": "application/json",
|
|
64
|
+
...opts?.headers,
|
|
65
65
|
});
|
|
66
66
|
|
|
67
|
-
if(baseURL) {
|
|
68
|
-
url = url.replace(/^(?!.*\/\/)\/?/, baseURL +
|
|
67
|
+
if (baseURL) {
|
|
68
|
+
url = url.replace(/^(?!.*\/\/)\/?/, baseURL + "/");
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
if(opts?.params) {
|
|
72
|
-
url += `${
|
|
71
|
+
if (opts?.params) {
|
|
72
|
+
url += `${~url.indexOf("?") ? "&" : "?"}${new URLSearchParams(opts.params)}`;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
for(const plugin of plugins) {
|
|
75
|
+
for (const plugin of plugins) {
|
|
76
76
|
try {
|
|
77
|
-
const overrides = await plugin?.beforeFetch?.({
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
const overrides = await plugin?.beforeFetch?.({
|
|
78
|
+
responseType,
|
|
79
|
+
headers,
|
|
80
|
+
fetchFn,
|
|
81
|
+
baseURL,
|
|
82
|
+
url,
|
|
83
|
+
method,
|
|
84
|
+
opts,
|
|
85
|
+
data,
|
|
86
|
+
});
|
|
87
|
+
if (overrides) {
|
|
88
|
+
({
|
|
89
|
+
responseType,
|
|
90
|
+
headers,
|
|
91
|
+
fetchFn,
|
|
92
|
+
baseURL,
|
|
93
|
+
url,
|
|
94
|
+
method,
|
|
95
|
+
opts,
|
|
96
|
+
data,
|
|
97
|
+
} = { ...overrides });
|
|
80
98
|
}
|
|
81
|
-
} catch(e) {
|
|
99
|
+
} catch (e) {
|
|
82
100
|
log(`Plugin "${plugin.name}" error on afterFetch hook`);
|
|
83
101
|
throw e;
|
|
84
102
|
}
|
|
85
103
|
}
|
|
86
104
|
|
|
87
|
-
log(`Fetching ${method} ${url}`, {
|
|
88
|
-
responseType,
|
|
105
|
+
log(`Fetching ${method} ${url}`, {
|
|
106
|
+
responseType,
|
|
89
107
|
// @ts-ignore
|
|
90
|
-
headers: Object.fromEntries(headers),
|
|
91
|
-
fetchFn,
|
|
92
|
-
baseURL,
|
|
93
|
-
url,
|
|
94
|
-
method,
|
|
95
|
-
opts,
|
|
96
|
-
data
|
|
97
|
-
});
|
|
98
|
-
return fetchFn(url, {
|
|
108
|
+
headers: Object.fromEntries(headers),
|
|
109
|
+
fetchFn,
|
|
110
|
+
baseURL,
|
|
111
|
+
url,
|
|
99
112
|
method,
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
...(opts?.mode ? { mode: opts.mode } : {}),
|
|
103
|
-
...(opts?.credentials ? { credentials: opts.credentials } : {}),
|
|
104
|
-
...(opts?.cache ? { cache: opts.cache } : {}),
|
|
105
|
-
...(opts?.redirect ? { redirect: opts.redirect } : {}),
|
|
106
|
-
...(opts?.referrer ? { referrer: opts.referrer } : {}),
|
|
107
|
-
...(opts?.referrerPolicy ? { referrerPolicy: opts.referrerPolicy } : {}),
|
|
108
|
-
...(opts?.integrity ? { integrity: opts.integrity } : {}),
|
|
109
|
-
...(opts?.keepalive ? { keepalive: opts.keepalive } : {}),
|
|
110
|
-
...(opts?.signal ? { signal: opts.signal } : {}),
|
|
111
|
-
})
|
|
112
|
-
/** [PLUGINS - AFTERFETCH] */
|
|
113
|
-
.then(async res => {
|
|
114
|
-
for(const plugin of plugins) {
|
|
115
|
-
try {
|
|
116
|
-
const afterFetchResult = await plugin?.afterFetch?.(res) ?? res;
|
|
117
|
-
if(afterFetchResult) {
|
|
118
|
-
res = afterFetchResult;
|
|
119
|
-
}
|
|
120
|
-
} catch(e) {
|
|
121
|
-
log(`Plugin "${plugin.name}" error on afterFetch hook`)
|
|
122
|
-
throw e;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return res;
|
|
127
|
-
})
|
|
128
|
-
/** [STATUS] */
|
|
129
|
-
.then(handleStatus)
|
|
130
|
-
/** [RESPONSETYPE] */
|
|
131
|
-
.then(res => res[responseType]())
|
|
132
|
-
.then(async data => {
|
|
133
|
-
for(const plugin of plugins) {
|
|
134
|
-
try {
|
|
135
|
-
data = await plugin?.transform?.(data) ?? data;
|
|
136
|
-
} catch(e) {
|
|
137
|
-
log(`Plugin "${plugin.name}" error on transform hook`)
|
|
138
|
-
throw e;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
log(`Fetch successful ${method} ${url}`, data);
|
|
142
|
-
return data;
|
|
143
|
-
})
|
|
144
|
-
/** [PLUGINS - HANDLEERROR] */
|
|
145
|
-
.catch(async e => {
|
|
146
|
-
log(`Fetch failed ${method} ${url}`, e);
|
|
147
|
-
const shouldThrow = plugins.length === 0 || (await Promise.all(plugins.map(({ handleError }) => handleError?.(e) ?? true))).every(_ => !!_);
|
|
148
|
-
if(shouldThrow) throw e;
|
|
113
|
+
opts,
|
|
114
|
+
data,
|
|
149
115
|
});
|
|
116
|
+
return (
|
|
117
|
+
fetchFn(url, {
|
|
118
|
+
method,
|
|
119
|
+
headers,
|
|
120
|
+
...(data ? { body: JSON.stringify(data) } : {}),
|
|
121
|
+
...(opts?.mode ? { mode: opts.mode } : {}),
|
|
122
|
+
...(opts?.credentials ? { credentials: opts.credentials } : {}),
|
|
123
|
+
...(opts?.cache ? { cache: opts.cache } : {}),
|
|
124
|
+
...(opts?.redirect ? { redirect: opts.redirect } : {}),
|
|
125
|
+
...(opts?.referrer ? { referrer: opts.referrer } : {}),
|
|
126
|
+
...(opts?.referrerPolicy
|
|
127
|
+
? { referrerPolicy: opts.referrerPolicy }
|
|
128
|
+
: {}),
|
|
129
|
+
...(opts?.integrity ? { integrity: opts.integrity } : {}),
|
|
130
|
+
...(opts?.keepalive ? { keepalive: opts.keepalive } : {}),
|
|
131
|
+
...(opts?.signal ? { signal: opts.signal } : {}),
|
|
132
|
+
})
|
|
133
|
+
/** [PLUGINS - AFTERFETCH] */
|
|
134
|
+
.then(async (res) => {
|
|
135
|
+
for (const plugin of plugins) {
|
|
136
|
+
try {
|
|
137
|
+
const afterFetchResult =
|
|
138
|
+
(await plugin?.afterFetch?.(res, {
|
|
139
|
+
responseType,
|
|
140
|
+
headers,
|
|
141
|
+
fetchFn,
|
|
142
|
+
baseURL,
|
|
143
|
+
url,
|
|
144
|
+
method,
|
|
145
|
+
opts,
|
|
146
|
+
data,
|
|
147
|
+
})) ?? res;
|
|
148
|
+
if (afterFetchResult) {
|
|
149
|
+
res = afterFetchResult;
|
|
150
|
+
}
|
|
151
|
+
} catch (e) {
|
|
152
|
+
log(`Plugin "${plugin.name}" error on afterFetch hook`);
|
|
153
|
+
throw e;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return res;
|
|
158
|
+
})
|
|
159
|
+
/** [STATUS] */
|
|
160
|
+
.then(handleStatus)
|
|
161
|
+
/** [RESPONSETYPE] */
|
|
162
|
+
.then((res) => res[responseType]())
|
|
163
|
+
.then(async (data) => {
|
|
164
|
+
for (const plugin of plugins) {
|
|
165
|
+
try {
|
|
166
|
+
data = (await plugin?.transform?.(data)) ?? data;
|
|
167
|
+
} catch (e) {
|
|
168
|
+
log(`Plugin "${plugin.name}" error on transform hook`);
|
|
169
|
+
throw e;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
log(`Fetch successful ${method} ${url}`, data);
|
|
173
|
+
return data;
|
|
174
|
+
})
|
|
175
|
+
/** [PLUGINS - HANDLEERROR] */
|
|
176
|
+
.catch(async (e) => {
|
|
177
|
+
log(`Fetch failed ${method} ${url}`, e);
|
|
178
|
+
const shouldThrow =
|
|
179
|
+
plugins.length === 0 ||
|
|
180
|
+
(
|
|
181
|
+
await Promise.all(
|
|
182
|
+
plugins.map(({ handleError }) => handleError?.(e) ?? true),
|
|
183
|
+
)
|
|
184
|
+
).every((_) => !!_);
|
|
185
|
+
if (shouldThrow) throw e;
|
|
186
|
+
})
|
|
187
|
+
);
|
|
150
188
|
}
|
|
151
189
|
|
|
152
190
|
/** @type {import('./types.js').BodylessMethod} */
|
|
153
|
-
get = (url, opts) => this.fetch(url,
|
|
191
|
+
get = (url, opts) => this.fetch(url, "GET", opts);
|
|
154
192
|
/** @type {import('./types.js').BodylessMethod} */
|
|
155
|
-
options = (url, opts) => this.fetch(url,
|
|
193
|
+
options = (url, opts) => this.fetch(url, "OPTIONS", opts);
|
|
156
194
|
/** @type {import('./types.js').BodylessMethod} */
|
|
157
|
-
delete = (url, opts) => this.fetch(url,
|
|
195
|
+
delete = (url, opts) => this.fetch(url, "DELETE", opts);
|
|
158
196
|
/** @type {import('./types.js').BodylessMethod} */
|
|
159
|
-
head = (url, opts) => this.fetch(url,
|
|
197
|
+
head = (url, opts) => this.fetch(url, "HEAD", opts);
|
|
160
198
|
/** @type {import('./types.js').BodyMethod} */
|
|
161
|
-
post = (url, data, opts) => this.fetch(url,
|
|
199
|
+
post = (url, data, opts) => this.fetch(url, "POST", opts, data);
|
|
162
200
|
/** @type {import('./types.js').BodyMethod} */
|
|
163
|
-
put = (url, data, opts) => this.fetch(url,
|
|
201
|
+
put = (url, data, opts) => this.fetch(url, "PUT", opts, data);
|
|
164
202
|
/** @type {import('./types.js').BodyMethod} */
|
|
165
|
-
patch = (url, data, opts) => this.fetch(url,
|
|
203
|
+
patch = (url, data, opts) => this.fetch(url, "PATCH", opts, data);
|
|
166
204
|
}
|
|
167
205
|
|
|
168
|
-
export const api = new Api();
|
|
206
|
+
export const api = new Api();
|
package/api/plugins/cache.js
CHANGED
|
@@ -1,39 +1,58 @@
|
|
|
1
1
|
const TEN_MINUTES = 1000 * 60 * 10;
|
|
2
|
+
const DEFAULT_MAX_SIZE = 100;
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
|
-
* @param {{maxAge?: number}} options
|
|
5
|
+
* @param {{maxAge?: number, maxSize?: number}} options
|
|
5
6
|
* @returns {import('../index.js').Plugin}
|
|
6
7
|
*/
|
|
7
|
-
export function cachePlugin({
|
|
8
|
-
|
|
8
|
+
export function cachePlugin({
|
|
9
|
+
maxAge = TEN_MINUTES,
|
|
10
|
+
maxSize = DEFAULT_MAX_SIZE,
|
|
11
|
+
} = {}) {
|
|
9
12
|
const cache = new Map();
|
|
10
13
|
|
|
14
|
+
function evict() {
|
|
15
|
+
const now = Date.now();
|
|
16
|
+
for (const [key, value] of cache) {
|
|
17
|
+
if (value.updatedAt <= now - maxAge) {
|
|
18
|
+
cache.delete(key);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
// if still over maxSize, evict oldest entries
|
|
22
|
+
if (cache.size > maxSize) {
|
|
23
|
+
const overflow = cache.size - maxSize;
|
|
24
|
+
const keys = cache.keys();
|
|
25
|
+
for (let i = 0; i < overflow; i++) {
|
|
26
|
+
cache.delete(keys.next().value);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
11
31
|
return {
|
|
12
|
-
name:
|
|
32
|
+
name: "cache",
|
|
13
33
|
beforeFetch: (meta) => {
|
|
14
34
|
const { method, url } = meta;
|
|
15
|
-
requestId = `${method}:${url}`;
|
|
16
|
-
|
|
17
|
-
if(cache.has(requestId)) {
|
|
35
|
+
const requestId = `${method}:${url}`;
|
|
36
|
+
if (cache.has(requestId)) {
|
|
18
37
|
const cached = cache.get(requestId);
|
|
19
|
-
if(cached.updatedAt > Date.now() -
|
|
20
|
-
meta.fetchFn = () =>
|
|
38
|
+
if (cached.updatedAt > Date.now() - maxAge) {
|
|
39
|
+
meta.fetchFn = () =>
|
|
40
|
+
Promise.resolve(
|
|
41
|
+
new Response(JSON.stringify(cached.data), { status: 200 }),
|
|
42
|
+
);
|
|
21
43
|
return meta;
|
|
22
44
|
}
|
|
23
45
|
}
|
|
24
46
|
},
|
|
25
|
-
afterFetch: async (res) => {
|
|
26
|
-
const
|
|
47
|
+
afterFetch: async (res, meta) => {
|
|
48
|
+
const requestId = `${meta.method}:${meta.url}`;
|
|
49
|
+
const clone = res.clone();
|
|
27
50
|
const data = await clone.json();
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
updatedAt: Date.now(),
|
|
31
|
-
data
|
|
32
|
-
});
|
|
33
|
-
|
|
51
|
+
cache.set(requestId, { updatedAt: Date.now(), data });
|
|
52
|
+
evict();
|
|
34
53
|
return res;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
54
|
+
},
|
|
55
|
+
};
|
|
37
56
|
}
|
|
38
57
|
|
|
39
|
-
export const cache = cachePlugin();
|
|
58
|
+
export const cache = cachePlugin();
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {object} options
|
|
3
|
+
* @param {number} [options.maxRetries=5] - Maximum number of retries
|
|
4
|
+
* @param {number[]} [options.delays=[1000, 2000, 4000, 8000, 16000]] - Delay in ms per retry attempt
|
|
5
|
+
* @param {(e: Error) => boolean} [options.shouldRetry] - Optional predicate to control which errors are retried
|
|
6
|
+
* @returns {import('../index.js').Plugin}
|
|
7
|
+
*/
|
|
8
|
+
export function retry({
|
|
9
|
+
maxRetries = 5,
|
|
10
|
+
delays = [1000, 2000, 4000, 8000, 16000],
|
|
11
|
+
shouldRetry = () => true,
|
|
12
|
+
} = {}) {
|
|
13
|
+
return {
|
|
14
|
+
name: "retry",
|
|
15
|
+
handleError(e) {
|
|
16
|
+
// Returning false suppresses the throw — we handle retrying in beforeFetch
|
|
17
|
+
return true;
|
|
18
|
+
},
|
|
19
|
+
async beforeFetch(context) {
|
|
20
|
+
const { fetchFn } = context;
|
|
21
|
+
// Wrap fetchFn to add retry logic
|
|
22
|
+
context.fetchFn = async (url, opts) => {
|
|
23
|
+
let attempt = 0;
|
|
24
|
+
while (true) {
|
|
25
|
+
try {
|
|
26
|
+
return await fetchFn(url, opts);
|
|
27
|
+
} catch (e) {
|
|
28
|
+
const isRetryable = shouldRetry(/** @type {Error} */ (e));
|
|
29
|
+
const hasAttempts = attempt < maxRetries;
|
|
30
|
+
if (!isRetryable || !hasAttempts) throw e;
|
|
31
|
+
const delay = delays[attempt] ?? delays[delays.length - 1];
|
|
32
|
+
console.warn(
|
|
33
|
+
`[retry] Attempt ${attempt + 1} failed. Retrying in ${delay}ms...`,
|
|
34
|
+
/** @type {Error} */ (e).message,
|
|
35
|
+
);
|
|
36
|
+
await new Promise((res) => setTimeout(res, delay));
|
|
37
|
+
attempt++;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
return context;
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
package/dialog/dialog.test.js
CHANGED
|
@@ -1,66 +1,66 @@
|
|
|
1
|
-
import { expect, oneEvent } from
|
|
2
|
-
import { stub } from
|
|
3
|
-
import { Dialog } from
|
|
1
|
+
import { expect, oneEvent } from "@open-wc/testing";
|
|
2
|
+
import { stub } from "sinon";
|
|
3
|
+
import { Dialog } from "./index.js";
|
|
4
4
|
|
|
5
|
-
describe(
|
|
5
|
+
describe("Dialog", () => {
|
|
6
6
|
let dialog;
|
|
7
7
|
|
|
8
8
|
beforeEach(() => {
|
|
9
9
|
dialog = new Dialog({
|
|
10
|
-
foo: { opening: ({dialog}) => dialog.form.innerHTML =
|
|
10
|
+
foo: { opening: ({ dialog }) => (dialog.form.innerHTML = "hello world") },
|
|
11
11
|
});
|
|
12
12
|
});
|
|
13
13
|
|
|
14
14
|
afterEach(async () => {
|
|
15
|
-
if(dialog.open) await dialog.close();
|
|
15
|
+
if (dialog.open) await dialog.close();
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
-
it('opens', async () => {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
});
|
|
18
|
+
// it('opens', async () => {
|
|
19
|
+
// await dialog.open({id: 'foo'});
|
|
20
|
+
// await dialog.opened;
|
|
21
|
+
// expect(dialog.isOpen).to.be.true;
|
|
22
|
+
// });
|
|
23
23
|
|
|
24
|
-
it('closes', async () => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
});
|
|
24
|
+
// it('closes', async () => {
|
|
25
|
+
// await dialog.open({id: 'foo'});
|
|
26
|
+
// await dialog.opened;
|
|
27
|
+
// await dialog.close();
|
|
28
|
+
// await dialog.closed;
|
|
29
|
+
// expect(dialog.isOpen).to.be.false;
|
|
30
|
+
// });
|
|
31
31
|
|
|
32
|
-
it('modify', async () => {
|
|
33
|
-
|
|
32
|
+
// it('modify', async () => {
|
|
33
|
+
// await dialog.open({id: 'foo'});
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
// const d = await dialog.opened;
|
|
36
|
+
// dialog.modify(node => {node.classList.add('foo')});
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
});
|
|
38
|
+
// expect(d.classList.contains('foo')).to.be.true;
|
|
39
|
+
// });
|
|
40
40
|
|
|
41
|
-
it('runs callbacks', async () => {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
41
|
+
// it('runs callbacks', async () => {
|
|
42
|
+
// const cbs = {
|
|
43
|
+
// opening: stub(),
|
|
44
|
+
// opened: stub(),
|
|
45
|
+
// closing: stub(),
|
|
46
|
+
// closed: stub(),
|
|
47
|
+
// };
|
|
48
|
+
// const dialog = new Dialog({foo: cbs});
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
expect(cbs.opening.called).to.be.true;
|
|
54
|
-
expect(cbs.opened.called).to.be.true;
|
|
55
|
-
expect(cbs.closing.called).to.be.false;
|
|
56
|
-
expect(cbs.closed.called).to.be.false;
|
|
50
|
+
// await dialog.open({id: 'foo'});
|
|
51
|
+
// await dialog.opened;
|
|
57
52
|
|
|
58
|
-
|
|
59
|
-
|
|
53
|
+
// expect(cbs.opening.called).to.be.true;
|
|
54
|
+
// expect(cbs.opened.called).to.be.true;
|
|
55
|
+
// expect(cbs.closing.called).to.be.false;
|
|
56
|
+
// expect(cbs.closed.called).to.be.false;
|
|
60
57
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
58
|
+
// dialog.close()
|
|
59
|
+
// await dialog.closed;
|
|
60
|
+
|
|
61
|
+
// expect(cbs.opening.called).to.be.true;
|
|
62
|
+
// expect(cbs.opened.called).to.be.true;
|
|
63
|
+
// expect(cbs.closing.called).to.be.true;
|
|
64
|
+
// expect(cbs.closed.called).to.be.true;
|
|
65
|
+
// });
|
|
66
|
+
});
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @param {{maxAge?: number}} options
|
|
2
|
+
* @param {{maxAge?: number, maxSize?: number}} options
|
|
3
3
|
* @returns {import('../index.js').Plugin}
|
|
4
4
|
*/
|
|
5
|
-
export function cachePlugin({ maxAge }?: {
|
|
5
|
+
export function cachePlugin({ maxAge, maxSize, }?: {
|
|
6
6
|
maxAge?: number;
|
|
7
|
+
maxSize?: number;
|
|
7
8
|
}): import('../index.js').Plugin;
|
|
8
9
|
export const cache: import("../types.js").Plugin;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {object} options
|
|
3
|
+
* @param {number} [options.maxRetries=5] - Maximum number of retries
|
|
4
|
+
* @param {number[]} [options.delays=[1000, 2000, 4000, 8000, 16000]] - Delay in ms per retry attempt
|
|
5
|
+
* @param {(e: Error) => boolean} [options.shouldRetry] - Optional predicate to control which errors are retried
|
|
6
|
+
* @returns {import('../index.js').Plugin}
|
|
7
|
+
*/
|
|
8
|
+
export function retry({ maxRetries, delays, shouldRetry, }?: {
|
|
9
|
+
maxRetries?: number;
|
|
10
|
+
delays?: number[];
|
|
11
|
+
shouldRetry?: (e: Error) => boolean;
|
|
12
|
+
}): import('../index.js').Plugin;
|
package/types/api/types.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export type BodylessMethod = <R>(url: string, opts?: RequestOptions) => Promise<
|
|
|
9
9
|
export type Method = 'GET' | 'DELETE' | 'HEAD' | 'OPTIONS' | 'POST' | 'PUT' | 'PATCH';
|
|
10
10
|
export interface Plugin {
|
|
11
11
|
beforeFetch?: (meta: MetaParams) => MetaParams | Promise<MetaParams> | void;
|
|
12
|
-
afterFetch?: (res: Response) => void | Promise<void> | Response | Promise<Response>;
|
|
12
|
+
afterFetch?: (res: Response, meta: MetaParams) => void | Promise<void> | Response | Promise<Response>;
|
|
13
13
|
transform?: (data: any) => any;
|
|
14
14
|
name: string;
|
|
15
15
|
handleError?: (e: Error) => boolean;
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `'state-changed'` event
|
|
3
|
-
* @template T
|
|
4
|
-
* @example this.dispatchEvent(new StateEvent(data));
|
|
5
|
-
*/
|
|
6
|
-
export class StateEvent<T> extends Event {
|
|
7
|
-
/**
|
|
8
|
-
* @param {T} state
|
|
9
|
-
*/
|
|
10
|
-
constructor(state: T);
|
|
11
|
-
/** @type {T} */
|
|
12
|
-
state: T;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* @template T
|
|
16
|
-
* @typedef {{
|
|
17
|
-
* name: string,
|
|
18
|
-
* update?: (prevState: T, newState: T) => T,
|
|
19
|
-
* effect?: (prevState: T, newState: T) => void | Promise<void>,
|
|
20
|
-
* }} Plugin
|
|
21
|
-
*/
|
|
22
|
-
/**
|
|
23
|
-
* @template T
|
|
24
|
-
* @extends EventTarget
|
|
25
|
-
*/
|
|
26
|
-
export class State<T> extends EventTarget {
|
|
27
|
-
/**
|
|
28
|
-
* @param {T} initialState
|
|
29
|
-
* @param {Array<{ update: (prevState: T, newState: T) => T }>} [plugins=[]]
|
|
30
|
-
*/
|
|
31
|
-
constructor(initialState: T, plugins?: {
|
|
32
|
-
update: (prevState: T, newState: T) => T;
|
|
33
|
-
}[]);
|
|
34
|
-
/**
|
|
35
|
-
* @param {T | ((prevState: T) => T)} state
|
|
36
|
-
* @param {boolean} [broadcast=true]
|
|
37
|
-
*/
|
|
38
|
-
setState(state: T | ((prevState: T) => T), broadcast?: boolean): void;
|
|
39
|
-
/**
|
|
40
|
-
* @returns {T}
|
|
41
|
-
*/
|
|
42
|
-
getState(): T;
|
|
43
|
-
#private;
|
|
44
|
-
}
|
|
45
|
-
export const state: State<{}>;
|
|
46
|
-
export type Plugin<T> = {
|
|
47
|
-
name: string;
|
|
48
|
-
update?: (prevState: T, newState: T) => T;
|
|
49
|
-
effect?: (prevState: T, newState: T) => void | Promise<void>;
|
|
50
|
-
};
|