lissa 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.
- package/LICENSE +21 -0
- package/README.md +715 -0
- package/lib/core/defaults.js +22 -0
- package/lib/core/errors.js +13 -0
- package/lib/core/lissa.js +182 -0
- package/lib/core/request.js +488 -0
- package/lib/exports.js +3 -0
- package/lib/index.d.ts +510 -0
- package/lib/index.js +28 -0
- package/lib/plugins/dedupe.js +45 -0
- package/lib/plugins/index.js +2 -0
- package/lib/plugins/retry.js +65 -0
- package/lib/utils/OpenPromise.js +88 -0
- package/lib/utils/helper.js +195 -0
- package/package.json +72 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export default class OpenPromise extends Promise {
|
|
2
|
+
#resolveSelf;
|
|
3
|
+
#rejectSelf;
|
|
4
|
+
|
|
5
|
+
#listeners;
|
|
6
|
+
|
|
7
|
+
#status;
|
|
8
|
+
#value;
|
|
9
|
+
#reason;
|
|
10
|
+
|
|
11
|
+
constructor(executor = () => {}) {
|
|
12
|
+
let resolve, reject;
|
|
13
|
+
super((res, rej) => {
|
|
14
|
+
resolve = res;
|
|
15
|
+
reject = rej;
|
|
16
|
+
return executor(res, rej);
|
|
17
|
+
});
|
|
18
|
+
this.#resolveSelf = resolve;
|
|
19
|
+
this.#rejectSelf = reject;
|
|
20
|
+
|
|
21
|
+
this.#listeners = new Map([
|
|
22
|
+
['resolve', new Set()],
|
|
23
|
+
['reject', new Set()],
|
|
24
|
+
['settle', new Set()],
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
this.#status = 'pending';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get status() {
|
|
31
|
+
return this.#status;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get value() {
|
|
35
|
+
return this.#value;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get reason() {
|
|
39
|
+
return this.#reason;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
on(event, listener) {
|
|
43
|
+
this.#listeners.get(event)?.add(listener);
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
off(event, listener) {
|
|
48
|
+
this.#listeners.get(event)?.delete(listener);
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
#emit(event, value) {
|
|
53
|
+
this.#listeners.get(event).forEach(listener => listener(value));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
resolve(value) {
|
|
57
|
+
if (this.#status !== 'pending') return;
|
|
58
|
+
|
|
59
|
+
this.#status = 'fulfilled';
|
|
60
|
+
this.#value = value;
|
|
61
|
+
|
|
62
|
+
this.#emit('resolve', value);
|
|
63
|
+
this.#emit('settle', {
|
|
64
|
+
status: 'fulfilled',
|
|
65
|
+
value,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
this.#resolveSelf(value);
|
|
69
|
+
this.#listeners.clear();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
reject(reason) {
|
|
73
|
+
if (this.#status !== 'pending') return;
|
|
74
|
+
|
|
75
|
+
this.#status = 'rejected';
|
|
76
|
+
this.#reason = reason;
|
|
77
|
+
|
|
78
|
+
this.#emit('reject', reason);
|
|
79
|
+
this.#emit('settle', {
|
|
80
|
+
status: 'rejected',
|
|
81
|
+
reason,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
this.#rejectSelf(reason);
|
|
85
|
+
this.#listeners.clear();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
export function normalizeOptions(options = {}) {
|
|
2
|
+
const { get = {}, post = {}, put = {}, patch = {}, delete: del = {}, ...defaults } = options;
|
|
3
|
+
|
|
4
|
+
function normalize(options) {
|
|
5
|
+
const normalized = { ...options };
|
|
6
|
+
normalized.headers = new Headers(options.headers || {});
|
|
7
|
+
normalized.params = options.params || {};
|
|
8
|
+
return normalized;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
...normalize(defaults),
|
|
13
|
+
'get': normalize(get),
|
|
14
|
+
'post': normalize(post),
|
|
15
|
+
'put': normalize(put),
|
|
16
|
+
'patch': normalize(patch),
|
|
17
|
+
'delete': normalize(del),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function deepMerge(...objects) {
|
|
22
|
+
let merged;
|
|
23
|
+
|
|
24
|
+
for (const obj of objects) {
|
|
25
|
+
if (obj == null) continue;
|
|
26
|
+
|
|
27
|
+
if (obj instanceof AbortSignal) {
|
|
28
|
+
if (!merged) merged = obj;
|
|
29
|
+
else merged = AbortSignal.any([merged, obj]);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (obj instanceof Headers) {
|
|
34
|
+
merged = new Headers(merged);
|
|
35
|
+
|
|
36
|
+
obj.forEach((value, key) => {
|
|
37
|
+
merged.set(key, value);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (Array.isArray(obj)) {
|
|
44
|
+
merged = [...(merged || []), ...obj];
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (typeof obj === 'object' && obj.constructor === Object) {
|
|
49
|
+
merged = merged || {};
|
|
50
|
+
|
|
51
|
+
Object.keys(obj).forEach((key) => {
|
|
52
|
+
merged[key] = deepMerge(merged[key], obj[key]);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
merged = obj;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return merged;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function stringToBase64(str) {
|
|
65
|
+
// Browser environment
|
|
66
|
+
if (typeof window !== 'undefined' && typeof window.btoa === 'function') {
|
|
67
|
+
let binary = '';
|
|
68
|
+
new TextEncoder().encode(str).forEach((byte) => {
|
|
69
|
+
binary += String.fromCharCode(byte);
|
|
70
|
+
});
|
|
71
|
+
return window.btoa(binary);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Node.js environment
|
|
75
|
+
if (typeof Buffer !== 'undefined') {
|
|
76
|
+
return Buffer.from(str, 'utf-8').toString('base64');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
throw new Error('Base64 encoding not supported in this environment');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function stringifyParams(params, serializer) {
|
|
83
|
+
if (typeof serializer === 'function') return serializer(params);
|
|
84
|
+
if (serializer === 'extended') return extendedParamsSerializer(params);
|
|
85
|
+
return simpleParamsSerializer(params);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function simpleParamsSerializer(params) {
|
|
89
|
+
|
|
90
|
+
function stringifyPrimitive(v) {
|
|
91
|
+
switch (typeof v) {
|
|
92
|
+
case 'string': return encodeURIComponent(v);
|
|
93
|
+
case 'number': return Number.isFinite(v) ? `${v}` : '';
|
|
94
|
+
case 'bigint':
|
|
95
|
+
case 'boolean': return `${v}`;
|
|
96
|
+
default: return '';
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function* generator(params) {
|
|
101
|
+
for (const [key, val] of Object.entries(params)) {
|
|
102
|
+
if (Array.isArray(val)) {
|
|
103
|
+
for (const arrVal of val) {
|
|
104
|
+
yield `${encodeURIComponent(key)}=${stringifyPrimitive(arrVal)}`;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
yield `${encodeURIComponent(key)}=${stringifyPrimitive(val)}`;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return generator(params).toArray().join('&');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function extendedParamsSerializer(params) {
|
|
117
|
+
|
|
118
|
+
function* recursiveGenerator(key, value) {
|
|
119
|
+
if (value === null) {
|
|
120
|
+
return yield `${key}=`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const type = typeof value;
|
|
124
|
+
|
|
125
|
+
if (type === 'object' && value instanceof Date) {
|
|
126
|
+
yield `${key}=${encodeURIComponent(value.toISOString())}`;
|
|
127
|
+
}
|
|
128
|
+
else if (type === 'object') {
|
|
129
|
+
for (const [subKey, val] of Object.entries(value)) {
|
|
130
|
+
yield* recursiveGenerator(`${key}%5B${encodeURIComponent(subKey)}%5D`, val);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else if (type === 'string') {
|
|
134
|
+
yield `${key}=${encodeURIComponent(value)}`;
|
|
135
|
+
}
|
|
136
|
+
else if (type === 'number' || type === 'bigint' || type === 'boolean') {
|
|
137
|
+
yield `${key}=${value}`;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function* startGenerator(params) {
|
|
142
|
+
for (const [key, val] of Object.entries(params)) {
|
|
143
|
+
yield* recursiveGenerator(encodeURIComponent(key), val);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return startGenerator(params).toArray().join('&');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function resolveCause(error, last) {
|
|
151
|
+
const data = {};
|
|
152
|
+
|
|
153
|
+
function* getStack(error) {
|
|
154
|
+
if (!error) return;
|
|
155
|
+
Object.assign(data, error);
|
|
156
|
+
|
|
157
|
+
yield error.stack;
|
|
158
|
+
yield* getStack(error.cause);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
error.stack = [
|
|
162
|
+
...getStack(error),
|
|
163
|
+
...getStack(last),
|
|
164
|
+
].join('\nCaused by ');
|
|
165
|
+
|
|
166
|
+
delete error.cause;
|
|
167
|
+
|
|
168
|
+
Object.entries(data).forEach(([key, value]) => {
|
|
169
|
+
if (key in error || key === 'name') return;
|
|
170
|
+
error[key] = value;
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
return error;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function throttle(fn, delay = 50) {
|
|
177
|
+
let lastCall = 0;
|
|
178
|
+
let timeoutId;
|
|
179
|
+
|
|
180
|
+
return (...args) => {
|
|
181
|
+
const now = performance.now();
|
|
182
|
+
|
|
183
|
+
if (now - lastCall >= delay) {
|
|
184
|
+
lastCall = now;
|
|
185
|
+
fn.apply(this, args);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
clearTimeout(timeoutId);
|
|
189
|
+
timeoutId = setTimeout(() => {
|
|
190
|
+
lastCall = performance.now();
|
|
191
|
+
fn.apply(this, args);
|
|
192
|
+
}, delay - (now - lastCall));
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "lissa",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A lightweight, extensible HTTP client for modern JavaScript applications with fluent api syntax, hooks, plugins and more.",
|
|
5
|
+
"main": "./lib/index.js",
|
|
6
|
+
"types": "./lib/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"start": "npm run dev",
|
|
10
|
+
"dev": "nodemon examples/index.js --exec \"npm run lint && node\"",
|
|
11
|
+
"dev:inspect": "nodemon --inspect-brk examples/index.js",
|
|
12
|
+
"lint": "eslint .",
|
|
13
|
+
"lint:fix": "npm run lint -- --fix",
|
|
14
|
+
"pub": "node scripts/publish.js",
|
|
15
|
+
"pub:patch": "npm version patch",
|
|
16
|
+
"pub:feature": "npm version minor",
|
|
17
|
+
"preversion": "npm run lint",
|
|
18
|
+
"postversion": "git push && git push --tags"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/richterdennis/lissa.git"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"http",
|
|
26
|
+
"https",
|
|
27
|
+
"request",
|
|
28
|
+
"client",
|
|
29
|
+
"fetch",
|
|
30
|
+
"api",
|
|
31
|
+
"rest",
|
|
32
|
+
"ajax",
|
|
33
|
+
"xhr",
|
|
34
|
+
"promise",
|
|
35
|
+
"async",
|
|
36
|
+
"await",
|
|
37
|
+
"fluent",
|
|
38
|
+
"plugin",
|
|
39
|
+
"retry",
|
|
40
|
+
"dedupe",
|
|
41
|
+
"deduplication",
|
|
42
|
+
"upload",
|
|
43
|
+
"download",
|
|
44
|
+
"progress",
|
|
45
|
+
"timeout",
|
|
46
|
+
"abort",
|
|
47
|
+
"browser",
|
|
48
|
+
"node",
|
|
49
|
+
"universal",
|
|
50
|
+
"typescript",
|
|
51
|
+
"lightweight",
|
|
52
|
+
"zero-dependencies",
|
|
53
|
+
"modular",
|
|
54
|
+
"extensible"
|
|
55
|
+
],
|
|
56
|
+
"author": "Dennis Richter <lissa@richterdennis.de>",
|
|
57
|
+
"license": "MIT",
|
|
58
|
+
"bugs": {
|
|
59
|
+
"url": "https://github.com/richterdennis/lissa/issues"
|
|
60
|
+
},
|
|
61
|
+
"homepage": "https://github.com/richterdennis/lissa#readme",
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@eslint/js": "9.34.0",
|
|
64
|
+
"@stylistic/eslint-plugin": "5.3.1",
|
|
65
|
+
"eslint": "9.34.0",
|
|
66
|
+
"express": "^5.1.0",
|
|
67
|
+
"globals": "^16.3.0",
|
|
68
|
+
"lissa": "file:",
|
|
69
|
+
"nodemon": "^3.1.10",
|
|
70
|
+
"prompts": "^2.4.2"
|
|
71
|
+
}
|
|
72
|
+
}
|