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/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, '&lt;')
69
+ .replace(/>/g, '&gt;')
70
+ .replace(/"/g, '&quot;')
71
+ .replace(/'/g, '&apos;');
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 RE_TAG = /<(\w)([-\w]+)(?:\/|[^>]*>((?:(?!<\1)[\s\S])*)<\/\1\2)>/gm;
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(input)) !== null) {
71
- const [, prefix = '', key, inner] = match;
72
- const fullKey = `${prefix.toLowerCase()}${key}`;
73
- const node: XmlValue = inner ? parseXml(inner) : '';
74
-
75
- const current = result[fullKey];
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
- // first occurrence
78
- result[fullKey] = node;
99
+ // First occurrence
100
+ result[tagName] = node;
79
101
  } else if (Array.isArray(current)) {
80
- // already an array
102
+ // Already an array
81
103
  current.push(node);
82
104
  } else {
83
- // promote to array on the second occurrence
84
- result[fullKey] = [current, node];
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(input);
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<unknonw>>>}
184
+ * @returns {Promise<Array<PromiseSettledResult<T>>>}
163
185
  */
164
- export const runInBatches = async (
165
- tasks: Iterable<() => Promise<unknown>>,
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<unknown>>> => {
169
- const allResults: PromiseSettledResult<unknown>[] = [];
170
- let batch: Array<() => Promise<unknown>> = [];
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: Array<() => Promise<unknown>>): Promise<void> {
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(batchFns.map(fn => fn()));
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(r => setTimeout(r, wait));
218
+ await new Promise<void>((resolve: () => void) => setTimeout(resolve, wait));
195
219
  }
196
220
  }
197
221
  }