s3mini 0.1.1 → 0.3.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/README.md +22 -8
- package/dist/s3mini.d.ts +235 -7
- package/dist/s3mini.js +425 -62
- package/dist/s3mini.js.map +1 -1
- package/dist/s3mini.min.js +1 -1
- package/dist/s3mini.min.js.map +1 -1
- package/package.json +4 -1
- package/src/S3.ts +413 -63
- package/src/utils.ts +48 -24
package/src/utils.ts
CHANGED
|
@@ -17,6 +17,10 @@ export const hash = (content: string | Buffer): string => {
|
|
|
17
17
|
return _createHash('sha256').update(content).digest('hex') as string;
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
+
export const md5base64 = (data: string | Buffer): string => {
|
|
21
|
+
return _createHash('md5').update(data).digest('base64') as string;
|
|
22
|
+
};
|
|
23
|
+
|
|
20
24
|
/**
|
|
21
25
|
* Compute HMAC-SHA-256 of arbitrary data and return a hex string.
|
|
22
26
|
* @param {string|Buffer} key – secret key
|
|
@@ -53,6 +57,20 @@ const entityMap = {
|
|
|
53
57
|
'&': '&',
|
|
54
58
|
} as const;
|
|
55
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Escape special characters for XML
|
|
62
|
+
* @param value String to escape
|
|
63
|
+
* @returns XML-escaped string
|
|
64
|
+
*/
|
|
65
|
+
export const escapeXml = (value: string): string => {
|
|
66
|
+
return value
|
|
67
|
+
.replace(/&/g, '&')
|
|
68
|
+
.replace(/</g, '<')
|
|
69
|
+
.replace(/>/g, '>')
|
|
70
|
+
.replace(/"/g, '"')
|
|
71
|
+
.replace(/'/g, ''');
|
|
72
|
+
};
|
|
73
|
+
|
|
56
74
|
const unescapeXml = (value: string): string =>
|
|
57
75
|
value.replace(/&(quot|apos|lt|gt|amp);/g, m => entityMap[m as keyof typeof entityMap] ?? m);
|
|
58
76
|
|
|
@@ -62,31 +80,35 @@ const unescapeXml = (value: string): string =>
|
|
|
62
80
|
* @param input raw XML string
|
|
63
81
|
* @returns string for leaf nodes, otherwise a map of children
|
|
64
82
|
*/
|
|
83
|
+
|
|
65
84
|
export const parseXml = (input: string): XmlValue => {
|
|
66
|
-
const
|
|
85
|
+
const xmlContent = input.replace(/<\?xml[^?]*\?>\s*/, '');
|
|
86
|
+
const RE_TAG = /<([A-Za-z_][\w\-.]*)[^>]*>([\s\S]*?)<\/\1>/gm;
|
|
67
87
|
const result: XmlMap = {}; // strong type, no `any`
|
|
68
88
|
let match: RegExpExecArray | null;
|
|
69
89
|
|
|
70
|
-
while ((match = RE_TAG.exec(
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
const node: XmlValue =
|
|
74
|
-
|
|
75
|
-
|
|
90
|
+
while ((match = RE_TAG.exec(xmlContent)) !== null) {
|
|
91
|
+
const tagName = match[1];
|
|
92
|
+
const innerContent = match[2];
|
|
93
|
+
const node: XmlValue = innerContent ? parseXml(innerContent) : unescapeXml(innerContent?.trim() || '');
|
|
94
|
+
if (!tagName) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const current = result[tagName];
|
|
76
98
|
if (current === undefined) {
|
|
77
|
-
//
|
|
78
|
-
result[
|
|
99
|
+
// First occurrence
|
|
100
|
+
result[tagName] = node;
|
|
79
101
|
} else if (Array.isArray(current)) {
|
|
80
|
-
//
|
|
102
|
+
// Already an array
|
|
81
103
|
current.push(node);
|
|
82
104
|
} else {
|
|
83
|
-
//
|
|
84
|
-
result[
|
|
105
|
+
// Promote to array on the second occurrence
|
|
106
|
+
result[tagName] = [current, node];
|
|
85
107
|
}
|
|
86
108
|
}
|
|
87
109
|
|
|
88
110
|
// No child tags? — return the text, after entity decode
|
|
89
|
-
return Object.keys(result).length > 0 ? result : unescapeXml(
|
|
111
|
+
return Object.keys(result).length > 0 ? result : unescapeXml(xmlContent.trim());
|
|
90
112
|
};
|
|
91
113
|
|
|
92
114
|
/**
|
|
@@ -159,15 +181,15 @@ export class S3ServiceError extends S3Error {
|
|
|
159
181
|
* @param {Iterable<() => Promise<unknonw>>} tasks – functions returning Promises
|
|
160
182
|
* @param {number} [batchSize=30] – max concurrent requests
|
|
161
183
|
* @param {number} [minIntervalMs=0] – ≥0; 0 means “no pacing”
|
|
162
|
-
* @returns {Promise<Array<PromiseSettledResult<
|
|
184
|
+
* @returns {Promise<Array<PromiseSettledResult<T>>>}
|
|
163
185
|
*/
|
|
164
|
-
export const runInBatches = async (
|
|
165
|
-
tasks: Iterable<() => Promise<
|
|
186
|
+
export const runInBatches = async <T = unknown>(
|
|
187
|
+
tasks: Iterable<() => Promise<T>>,
|
|
166
188
|
batchSize: number = 30,
|
|
167
189
|
minIntervalMs: number = 0,
|
|
168
|
-
): Promise<Array<PromiseSettledResult<
|
|
169
|
-
const allResults: PromiseSettledResult<
|
|
170
|
-
let batch: Array<() => Promise<
|
|
190
|
+
): Promise<Array<PromiseSettledResult<T>>> => {
|
|
191
|
+
const allResults: PromiseSettledResult<T>[] = [];
|
|
192
|
+
let batch: Array<() => Promise<T>> = [];
|
|
171
193
|
|
|
172
194
|
for (const task of tasks) {
|
|
173
195
|
batch.push(task);
|
|
@@ -182,16 +204,18 @@ export const runInBatches = async (
|
|
|
182
204
|
return allResults;
|
|
183
205
|
|
|
184
206
|
// ───────── helpers ──────────
|
|
185
|
-
async function executeBatch(batchFns:
|
|
186
|
-
const start = Date.now();
|
|
207
|
+
async function executeBatch(batchFns: ReadonlyArray<() => Promise<T>>): Promise<void> {
|
|
208
|
+
const start: number = Date.now();
|
|
187
209
|
|
|
188
|
-
const settled = await Promise.allSettled(
|
|
210
|
+
const settled: Array<PromiseSettledResult<T>> = await Promise.allSettled(
|
|
211
|
+
batchFns.map((fn: () => Promise<T>) => fn()),
|
|
212
|
+
);
|
|
189
213
|
allResults.push(...settled);
|
|
190
214
|
|
|
191
215
|
if (minIntervalMs > 0) {
|
|
192
|
-
const wait = minIntervalMs - (Date.now() - start);
|
|
216
|
+
const wait: number = minIntervalMs - (Date.now() - start);
|
|
193
217
|
if (wait > 0) {
|
|
194
|
-
await new Promise(
|
|
218
|
+
await new Promise<void>((resolve: () => void) => setTimeout(resolve, wait));
|
|
195
219
|
}
|
|
196
220
|
}
|
|
197
221
|
}
|