hanzio 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/README.md +510 -0
- package/dist/api-wrapper/index.d.ts +84 -0
- package/dist/api-wrapper/index.js +250 -0
- package/dist/array/index.d.ts +22 -0
- package/dist/cache/index.d.ts +19 -0
- package/dist/date/index.d.ts +11 -0
- package/dist/gzip/index.d.ts +2 -0
- package/dist/gzip/index.js +16 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +1565 -0
- package/dist/jwt/index.d.ts +3 -0
- package/dist/jwt/index.js +19 -0
- package/dist/math/index.d.ts +2 -0
- package/dist/math/linearRegressionTrend.d.ts +5 -0
- package/dist/math/metricsSeriesColors.d.ts +42 -0
- package/dist/p-queue/index.d.ts +95 -0
- package/dist/p-queue/index.js +439 -0
- package/dist/promise/index.d.ts +21 -0
- package/dist/sitemap/index.d.ts +14 -0
- package/dist/sitemap/index.js +552 -0
- package/dist/state/index.d.ts +101 -0
- package/dist/string/index.d.ts +8 -0
- package/dist/typedswitch/index.d.ts +124 -0
- package/dist/url/index.d.ts +1 -0
- package/dist/zod/index.d.ts +8 -0
- package/dist/zod/index.js +210 -0
- package/dist/zod/jsonToZodSchemaMapper.d.ts +6 -0
- package/dist/zod/zodMinMaxFilter.d.ts +14 -0
- package/dist/zod/zodToTypeString.d.ts +9 -0
- package/package.json +102 -0
- package/src/api-wrapper/index.ts +492 -0
- package/src/array/index.ts +187 -0
- package/src/cache/index.ts +154 -0
- package/src/date/index.ts +54 -0
- package/src/gzip/index.ts +29 -0
- package/src/index.ts +12 -0
- package/src/jwt/index.ts +25 -0
- package/src/math/index.ts +11 -0
- package/src/math/linearRegressionTrend.ts +36 -0
- package/src/math/metricsSeriesColors.ts +208 -0
- package/src/p-queue/index.ts +690 -0
- package/src/promise/index.ts +73 -0
- package/src/sitemap/index.ts +195 -0
- package/src/state/index.ts +331 -0
- package/src/string/index.ts +86 -0
- package/src/typedswitch/index.ts +309 -0
- package/src/url/index.ts +5 -0
- package/src/zod/index.ts +29 -0
- package/src/zod/jsonToZodSchemaMapper.ts +44 -0
- package/src/zod/zodMinMaxFilter.ts +28 -0
- package/src/zod/zodToTypeString.ts +222 -0
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
// src/p-queue/index.ts
|
|
2
|
+
class TimeoutError extends Error {
|
|
3
|
+
constructor(message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "TimeoutError";
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
class PriorityQueue {
|
|
10
|
+
#queue = [];
|
|
11
|
+
enqueue(run, options = {}) {
|
|
12
|
+
const element = {
|
|
13
|
+
priority: options.priority ?? 0,
|
|
14
|
+
id: options.id,
|
|
15
|
+
run
|
|
16
|
+
};
|
|
17
|
+
const index = this.#queue.findIndex((item) => item.priority < element.priority);
|
|
18
|
+
if (index === -1) {
|
|
19
|
+
this.#queue.push(element);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
this.#queue.splice(index, 0, element);
|
|
23
|
+
}
|
|
24
|
+
dequeue() {
|
|
25
|
+
return this.#queue.shift()?.run;
|
|
26
|
+
}
|
|
27
|
+
filter(options) {
|
|
28
|
+
return this.#queue.filter((item) => Object.entries(options).every(([key, value]) => item[key] === value)).map((item) => item.run);
|
|
29
|
+
}
|
|
30
|
+
setPriority(id, priority) {
|
|
31
|
+
const index = this.#queue.findIndex((item2) => item2.id === id);
|
|
32
|
+
if (index === -1) {
|
|
33
|
+
throw new ReferenceError(`No promise function with the id "${id}" exists in the queue.`);
|
|
34
|
+
}
|
|
35
|
+
const [item] = this.#queue.splice(index, 1);
|
|
36
|
+
this.enqueue(item.run, { ...item, priority });
|
|
37
|
+
}
|
|
38
|
+
remove(id) {
|
|
39
|
+
const index = this.#queue.findIndex((item) => item.id === id);
|
|
40
|
+
if (index !== -1) {
|
|
41
|
+
this.#queue.splice(index, 1);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
get size() {
|
|
45
|
+
return this.#queue.length;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
class PQueue {
|
|
50
|
+
#listeners = new Map;
|
|
51
|
+
#queue;
|
|
52
|
+
#queueClass;
|
|
53
|
+
#pending = 0;
|
|
54
|
+
#concurrency;
|
|
55
|
+
#isPaused;
|
|
56
|
+
#intervalCap;
|
|
57
|
+
#interval;
|
|
58
|
+
#carryoverIntervalCount;
|
|
59
|
+
#strict;
|
|
60
|
+
#intervalCount = 0;
|
|
61
|
+
#intervalEnd = 0;
|
|
62
|
+
#intervalTimer;
|
|
63
|
+
#strictTimestamps = [];
|
|
64
|
+
#rateLimited = false;
|
|
65
|
+
#idCounter = 1n;
|
|
66
|
+
#runningTasks = new Map;
|
|
67
|
+
timeout;
|
|
68
|
+
constructor(options = {}) {
|
|
69
|
+
const {
|
|
70
|
+
concurrency = Number.POSITIVE_INFINITY,
|
|
71
|
+
autoStart = true,
|
|
72
|
+
queueClass = PriorityQueue,
|
|
73
|
+
intervalCap = Number.POSITIVE_INFINITY,
|
|
74
|
+
interval = 0,
|
|
75
|
+
carryoverIntervalCount,
|
|
76
|
+
carryoverConcurrencyCount,
|
|
77
|
+
strict = false,
|
|
78
|
+
timeout
|
|
79
|
+
} = options;
|
|
80
|
+
if (!(typeof concurrency === "number" && concurrency >= 1)) {
|
|
81
|
+
throw new TypeError("Expected `concurrency` to be a number from 1 and up");
|
|
82
|
+
}
|
|
83
|
+
if (!(typeof intervalCap === "number" && intervalCap >= 1)) {
|
|
84
|
+
throw new TypeError("Expected `intervalCap` to be a number from 1 and up");
|
|
85
|
+
}
|
|
86
|
+
if (!(typeof interval === "number" && Number.isFinite(interval) && interval >= 0)) {
|
|
87
|
+
throw new TypeError("Expected `interval` to be a finite number >= 0");
|
|
88
|
+
}
|
|
89
|
+
if (strict && interval === 0) {
|
|
90
|
+
throw new TypeError("The `strict` option requires a non-zero `interval`");
|
|
91
|
+
}
|
|
92
|
+
if (strict && intervalCap === Number.POSITIVE_INFINITY) {
|
|
93
|
+
throw new TypeError("The `strict` option requires a finite `intervalCap`");
|
|
94
|
+
}
|
|
95
|
+
if (timeout !== undefined && !(Number.isFinite(timeout) && timeout > 0)) {
|
|
96
|
+
throw new TypeError("Expected `timeout` to be a positive finite number");
|
|
97
|
+
}
|
|
98
|
+
this.#queueClass = queueClass;
|
|
99
|
+
this.#queue = new queueClass;
|
|
100
|
+
this.#concurrency = concurrency;
|
|
101
|
+
this.#isPaused = !autoStart;
|
|
102
|
+
this.#intervalCap = intervalCap;
|
|
103
|
+
this.#interval = interval;
|
|
104
|
+
this.#carryoverIntervalCount = carryoverIntervalCount ?? carryoverConcurrencyCount ?? false;
|
|
105
|
+
this.#strict = strict;
|
|
106
|
+
this.timeout = timeout;
|
|
107
|
+
}
|
|
108
|
+
on(event, listener) {
|
|
109
|
+
const listeners = this.#listeners.get(event) ?? new Set;
|
|
110
|
+
listeners.add(listener);
|
|
111
|
+
this.#listeners.set(event, listeners);
|
|
112
|
+
return this;
|
|
113
|
+
}
|
|
114
|
+
off(event, listener) {
|
|
115
|
+
this.#listeners.get(event)?.delete(listener);
|
|
116
|
+
return this;
|
|
117
|
+
}
|
|
118
|
+
emit(event, ...arguments_) {
|
|
119
|
+
const listeners = this.#listeners.get(event);
|
|
120
|
+
if (!listeners || listeners.size === 0) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
for (const listener of [...listeners]) {
|
|
124
|
+
listener(...arguments_);
|
|
125
|
+
}
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
get concurrency() {
|
|
129
|
+
return this.#concurrency;
|
|
130
|
+
}
|
|
131
|
+
set concurrency(newConcurrency) {
|
|
132
|
+
if (!(typeof newConcurrency === "number" && newConcurrency >= 1)) {
|
|
133
|
+
throw new TypeError("Expected `concurrency` to be a number from 1 and up");
|
|
134
|
+
}
|
|
135
|
+
this.#concurrency = newConcurrency;
|
|
136
|
+
this.#processQueue();
|
|
137
|
+
}
|
|
138
|
+
get size() {
|
|
139
|
+
return this.#queue.size;
|
|
140
|
+
}
|
|
141
|
+
sizeBy(options) {
|
|
142
|
+
return this.#queue.filter(options).length;
|
|
143
|
+
}
|
|
144
|
+
get pending() {
|
|
145
|
+
return this.#pending;
|
|
146
|
+
}
|
|
147
|
+
get isPaused() {
|
|
148
|
+
return this.#isPaused;
|
|
149
|
+
}
|
|
150
|
+
get isRateLimited() {
|
|
151
|
+
this.#updateRateLimitState();
|
|
152
|
+
return this.#rateLimited;
|
|
153
|
+
}
|
|
154
|
+
get isSaturated() {
|
|
155
|
+
return this.#pending >= this.#concurrency && this.#queue.size > 0 || this.isRateLimited && this.#queue.size > 0;
|
|
156
|
+
}
|
|
157
|
+
get runningTasks() {
|
|
158
|
+
return [...this.#runningTasks.values()].map((task) => ({ ...task }));
|
|
159
|
+
}
|
|
160
|
+
setPriority(id, priority) {
|
|
161
|
+
if (typeof priority !== "number" || !Number.isFinite(priority)) {
|
|
162
|
+
throw new TypeError("Expected `priority` to be a finite number");
|
|
163
|
+
}
|
|
164
|
+
this.#queue.setPriority(id, priority);
|
|
165
|
+
}
|
|
166
|
+
async add(function_, options = {}) {
|
|
167
|
+
const taskOptions = {
|
|
168
|
+
timeout: this.timeout,
|
|
169
|
+
...options,
|
|
170
|
+
id: options.id ?? (this.#idCounter++).toString()
|
|
171
|
+
};
|
|
172
|
+
return new Promise((resolve, reject) => {
|
|
173
|
+
const taskSymbol = Symbol(taskOptions.id);
|
|
174
|
+
let didRun = false;
|
|
175
|
+
let queuedAbortHandler;
|
|
176
|
+
let runningAbortHandler;
|
|
177
|
+
const cleanupQueuedAbort = () => {
|
|
178
|
+
if (queuedAbortHandler) {
|
|
179
|
+
taskOptions.signal?.removeEventListener("abort", queuedAbortHandler);
|
|
180
|
+
queuedAbortHandler = undefined;
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
const run = async () => {
|
|
184
|
+
didRun = true;
|
|
185
|
+
cleanupQueuedAbort();
|
|
186
|
+
this.#pending++;
|
|
187
|
+
this.#runningTasks.set(taskSymbol, {
|
|
188
|
+
id: taskOptions.id,
|
|
189
|
+
priority: taskOptions.priority ?? 0,
|
|
190
|
+
startTime: Date.now(),
|
|
191
|
+
timeout: taskOptions.timeout
|
|
192
|
+
});
|
|
193
|
+
try {
|
|
194
|
+
taskOptions.signal?.throwIfAborted();
|
|
195
|
+
const operation = Promise.resolve(function_({ signal: taskOptions.signal }));
|
|
196
|
+
const result = await this.#withTimeoutAndAbort(operation, taskOptions, (listener) => {
|
|
197
|
+
runningAbortHandler = listener;
|
|
198
|
+
});
|
|
199
|
+
resolve(result);
|
|
200
|
+
this.emit("completed", result);
|
|
201
|
+
} catch (error) {
|
|
202
|
+
reject(error);
|
|
203
|
+
this.emit("error", error);
|
|
204
|
+
} finally {
|
|
205
|
+
if (runningAbortHandler) {
|
|
206
|
+
taskOptions.signal?.removeEventListener("abort", runningAbortHandler);
|
|
207
|
+
}
|
|
208
|
+
this.#runningTasks.delete(taskSymbol);
|
|
209
|
+
queueMicrotask(() => {
|
|
210
|
+
this.#next();
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
if (taskOptions.signal?.aborted) {
|
|
215
|
+
reject(taskOptions.signal.reason);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (taskOptions.signal) {
|
|
219
|
+
queuedAbortHandler = () => {
|
|
220
|
+
if (didRun) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
this.#queue.remove?.(taskOptions.id);
|
|
224
|
+
reject(taskOptions.signal?.reason);
|
|
225
|
+
this.emit("next");
|
|
226
|
+
this.#tryToStartAnother();
|
|
227
|
+
};
|
|
228
|
+
taskOptions.signal.addEventListener("abort", queuedAbortHandler, {
|
|
229
|
+
once: true
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
this.#queue.enqueue(run, taskOptions);
|
|
233
|
+
this.emit("add");
|
|
234
|
+
this.#processQueue();
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
async addAll(functions, options) {
|
|
238
|
+
return Promise.all(functions.map(async (function_) => this.add(function_, options)));
|
|
239
|
+
}
|
|
240
|
+
start() {
|
|
241
|
+
if (!this.#isPaused) {
|
|
242
|
+
return this;
|
|
243
|
+
}
|
|
244
|
+
this.#isPaused = false;
|
|
245
|
+
this.#processQueue();
|
|
246
|
+
return this;
|
|
247
|
+
}
|
|
248
|
+
pause() {
|
|
249
|
+
this.#isPaused = true;
|
|
250
|
+
}
|
|
251
|
+
clear() {
|
|
252
|
+
this.#queue = new this.#queueClass;
|
|
253
|
+
this.#clearIntervalTimer();
|
|
254
|
+
this.#updateRateLimitState();
|
|
255
|
+
this.emit("empty");
|
|
256
|
+
if (this.#pending === 0) {
|
|
257
|
+
this.emit("idle");
|
|
258
|
+
}
|
|
259
|
+
this.emit("next");
|
|
260
|
+
}
|
|
261
|
+
async onEmpty() {
|
|
262
|
+
if (this.#queue.size === 0) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
await this.#onEvent("empty");
|
|
266
|
+
}
|
|
267
|
+
async onIdle() {
|
|
268
|
+
if (this.#queue.size === 0 && this.#pending === 0) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
await this.#onEvent("idle");
|
|
272
|
+
}
|
|
273
|
+
async onPendingZero() {
|
|
274
|
+
if (this.#pending === 0) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
await this.#onEvent("pendingZero");
|
|
278
|
+
}
|
|
279
|
+
async onRateLimit() {
|
|
280
|
+
if (this.isRateLimited) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
await this.#onEvent("rateLimit");
|
|
284
|
+
}
|
|
285
|
+
async onRateLimitCleared() {
|
|
286
|
+
if (!this.isRateLimited) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
await this.#onEvent("rateLimitCleared");
|
|
290
|
+
}
|
|
291
|
+
onError() {
|
|
292
|
+
return new Promise((_resolve, reject) => {
|
|
293
|
+
const listener = (error) => {
|
|
294
|
+
this.off("error", listener);
|
|
295
|
+
reject(error);
|
|
296
|
+
};
|
|
297
|
+
this.on("error", listener);
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
async onSizeLessThan(limit) {
|
|
301
|
+
if (this.#queue.size < limit) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
await this.#onEvent("next", () => this.#queue.size < limit);
|
|
305
|
+
}
|
|
306
|
+
async#onEvent(event, filter) {
|
|
307
|
+
return new Promise((resolve) => {
|
|
308
|
+
const listener = () => {
|
|
309
|
+
if (filter && !filter()) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
this.off(event, listener);
|
|
313
|
+
resolve();
|
|
314
|
+
};
|
|
315
|
+
this.on(event, listener);
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
async#withTimeoutAndAbort(operation, options, setAbortListener) {
|
|
319
|
+
const races = [operation];
|
|
320
|
+
let timeoutId;
|
|
321
|
+
if (options.timeout) {
|
|
322
|
+
races.push(new Promise((_resolve, reject) => {
|
|
323
|
+
timeoutId = setTimeout(() => {
|
|
324
|
+
reject(new TimeoutError(`Task timed out after ${options.timeout}ms`));
|
|
325
|
+
}, options.timeout);
|
|
326
|
+
}));
|
|
327
|
+
}
|
|
328
|
+
if (options.signal) {
|
|
329
|
+
races.push(new Promise((_resolve, reject) => {
|
|
330
|
+
const listener = () => {
|
|
331
|
+
reject(options.signal?.reason);
|
|
332
|
+
};
|
|
333
|
+
setAbortListener(listener);
|
|
334
|
+
options.signal?.addEventListener("abort", listener, { once: true });
|
|
335
|
+
}));
|
|
336
|
+
}
|
|
337
|
+
try {
|
|
338
|
+
return await Promise.race(races);
|
|
339
|
+
} finally {
|
|
340
|
+
if (timeoutId) {
|
|
341
|
+
clearTimeout(timeoutId);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
#next() {
|
|
346
|
+
this.#pending--;
|
|
347
|
+
if (this.#pending === 0) {
|
|
348
|
+
this.emit("pendingZero");
|
|
349
|
+
}
|
|
350
|
+
this.#processQueue();
|
|
351
|
+
this.emit("next");
|
|
352
|
+
}
|
|
353
|
+
#processQueue() {
|
|
354
|
+
while (this.#tryToStartAnother()) {}
|
|
355
|
+
this.#updateRateLimitState();
|
|
356
|
+
}
|
|
357
|
+
#tryToStartAnother() {
|
|
358
|
+
if (this.#queue.size === 0) {
|
|
359
|
+
this.#clearIntervalTimer();
|
|
360
|
+
this.emit("empty");
|
|
361
|
+
if (this.#pending === 0) {
|
|
362
|
+
this.emit("idle");
|
|
363
|
+
}
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
if (this.#isPaused || this.#pending >= this.#concurrency || !this.#doesIntervalAllowAnother()) {
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
const job = this.#queue.dequeue();
|
|
370
|
+
if (!job) {
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
this.#consumeIntervalSlot();
|
|
374
|
+
this.emit("active");
|
|
375
|
+
job();
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
378
|
+
#doesIntervalAllowAnother() {
|
|
379
|
+
if (this.#intervalCap === Number.POSITIVE_INFINITY || this.#interval === 0) {
|
|
380
|
+
return true;
|
|
381
|
+
}
|
|
382
|
+
const now = Date.now();
|
|
383
|
+
if (this.#strict) {
|
|
384
|
+
this.#strictTimestamps = this.#strictTimestamps.filter((timestamp) => now - timestamp < this.#interval);
|
|
385
|
+
if (this.#strictTimestamps.length < this.#intervalCap) {
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
this.#scheduleIntervalResume(this.#interval - (now - this.#strictTimestamps[0]));
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
if (this.#intervalEnd === 0 || now >= this.#intervalEnd) {
|
|
392
|
+
this.#intervalCount = this.#carryoverIntervalCount ? this.#pending : 0;
|
|
393
|
+
this.#intervalEnd = now + this.#interval;
|
|
394
|
+
}
|
|
395
|
+
if (this.#intervalCount < this.#intervalCap) {
|
|
396
|
+
return true;
|
|
397
|
+
}
|
|
398
|
+
this.#scheduleIntervalResume(this.#intervalEnd - now);
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
#consumeIntervalSlot() {
|
|
402
|
+
if (this.#intervalCap === Number.POSITIVE_INFINITY || this.#interval === 0) {
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
if (this.#strict) {
|
|
406
|
+
this.#strictTimestamps.push(Date.now());
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
this.#intervalCount++;
|
|
410
|
+
}
|
|
411
|
+
#scheduleIntervalResume(delay) {
|
|
412
|
+
if (this.#intervalTimer) {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
this.#intervalTimer = setTimeout(() => {
|
|
416
|
+
this.#intervalTimer = undefined;
|
|
417
|
+
this.#processQueue();
|
|
418
|
+
}, Math.max(0, delay));
|
|
419
|
+
}
|
|
420
|
+
#clearIntervalTimer() {
|
|
421
|
+
if (this.#intervalTimer) {
|
|
422
|
+
clearTimeout(this.#intervalTimer);
|
|
423
|
+
this.#intervalTimer = undefined;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
#updateRateLimitState() {
|
|
427
|
+
const previous = this.#rateLimited;
|
|
428
|
+
const isRateLimited = this.#queue.size > 0 && this.#intervalCap !== Number.POSITIVE_INFINITY && this.#interval > 0 && !this.#doesIntervalAllowAnother();
|
|
429
|
+
this.#rateLimited = isRateLimited;
|
|
430
|
+
if (previous !== isRateLimited) {
|
|
431
|
+
this.emit(isRateLimited ? "rateLimit" : "rateLimitCleared");
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// src/sitemap/index.ts
|
|
437
|
+
var createTimeoutController = (timeout) => {
|
|
438
|
+
const controller = new AbortController;
|
|
439
|
+
setTimeout(() => controller.abort(), timeout);
|
|
440
|
+
return controller;
|
|
441
|
+
};
|
|
442
|
+
var discoverSitemaps = async (domain) => {
|
|
443
|
+
const normalizedDomain = domain.replace(/\/$/, "");
|
|
444
|
+
const commonLocations = [
|
|
445
|
+
"/sitemap.xml",
|
|
446
|
+
"/sitemap_index.xml",
|
|
447
|
+
"/sitemap/",
|
|
448
|
+
"/sitemaps.xml",
|
|
449
|
+
"/sitemap/sitemap.xml"
|
|
450
|
+
];
|
|
451
|
+
const sitemaps = new Set;
|
|
452
|
+
const requestOptions = {
|
|
453
|
+
headers: {
|
|
454
|
+
"User-Agent": "SitemapCrawler/1.0"
|
|
455
|
+
},
|
|
456
|
+
signal: createTimeoutController(5000).signal
|
|
457
|
+
};
|
|
458
|
+
try {
|
|
459
|
+
const robotsTxt = await fetch(`${normalizedDomain}/robots.txt`, {
|
|
460
|
+
...requestOptions
|
|
461
|
+
});
|
|
462
|
+
if (robotsTxt.ok) {
|
|
463
|
+
const text = await robotsTxt.text();
|
|
464
|
+
const sitemapMatches = text.match(/Sitemap: (.*)/gi);
|
|
465
|
+
if (sitemapMatches) {
|
|
466
|
+
sitemapMatches.forEach((match) => {
|
|
467
|
+
const url = match.replace("Sitemap:", "").trim();
|
|
468
|
+
sitemaps.add(url);
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
if (sitemaps.size > 0) {
|
|
472
|
+
return [...sitemaps];
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
} catch (error) {
|
|
476
|
+
console.warn("Could not fetch robots.txt:", error instanceof Error ? error.message : "Unknown error");
|
|
477
|
+
}
|
|
478
|
+
for (const location of commonLocations) {
|
|
479
|
+
try {
|
|
480
|
+
const response = await fetch(`${normalizedDomain}${location}`, {
|
|
481
|
+
...requestOptions,
|
|
482
|
+
method: "HEAD"
|
|
483
|
+
});
|
|
484
|
+
if (response.ok) {
|
|
485
|
+
return [`${normalizedDomain}${location}`];
|
|
486
|
+
}
|
|
487
|
+
} catch (_error) {}
|
|
488
|
+
}
|
|
489
|
+
return [];
|
|
490
|
+
};
|
|
491
|
+
var getDomainSitemap = async (domain, options = {}) => {
|
|
492
|
+
const {
|
|
493
|
+
filterIndexes,
|
|
494
|
+
concurrency = 10,
|
|
495
|
+
maxDepth = 5,
|
|
496
|
+
timeout = 1e4
|
|
497
|
+
} = options;
|
|
498
|
+
let normalizedDomain = domain.replace(/\/$/, "");
|
|
499
|
+
if (!normalizedDomain.startsWith("http")) {
|
|
500
|
+
normalizedDomain = `https://${normalizedDomain}`;
|
|
501
|
+
}
|
|
502
|
+
const sitemapUrls = await discoverSitemaps(normalizedDomain);
|
|
503
|
+
if (sitemapUrls.length === 0) {
|
|
504
|
+
throw new Error("No sitemaps found for the domain");
|
|
505
|
+
}
|
|
506
|
+
const queue = new PQueue({ concurrency });
|
|
507
|
+
const result = {
|
|
508
|
+
links: new Set,
|
|
509
|
+
visited: new Set
|
|
510
|
+
};
|
|
511
|
+
const locRegex = /<loc>([^<]+)<\/loc>/g;
|
|
512
|
+
const isXmlSitemap = (url) => url.endsWith(".xml") || url.toLowerCase().includes("sitemap");
|
|
513
|
+
const processSitemap = async (sitemapUrl, depth = 0) => {
|
|
514
|
+
if (depth > maxDepth || result.visited.has(sitemapUrl)) {
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
result.visited.add(sitemapUrl);
|
|
518
|
+
try {
|
|
519
|
+
const controller = createTimeoutController(timeout);
|
|
520
|
+
const response = await fetch(sitemapUrl, {
|
|
521
|
+
headers: {
|
|
522
|
+
"User-Agent": "SitemapCrawler/1.0"
|
|
523
|
+
},
|
|
524
|
+
signal: controller.signal
|
|
525
|
+
});
|
|
526
|
+
if (!response.ok)
|
|
527
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
528
|
+
const data = await response.text();
|
|
529
|
+
const matches = [...data.matchAll(locRegex)].map((match) => match[1].trim()).filter((link) => {
|
|
530
|
+
if (filterIndexes && isXmlSitemap(link)) {
|
|
531
|
+
return link.includes(filterIndexes);
|
|
532
|
+
}
|
|
533
|
+
return true;
|
|
534
|
+
});
|
|
535
|
+
for (const link of matches) {
|
|
536
|
+
if (isXmlSitemap(link)) {
|
|
537
|
+
queue.add(() => processSitemap(link, depth + 1));
|
|
538
|
+
} else {
|
|
539
|
+
result.links.add(link);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
} catch (error) {
|
|
543
|
+
console.warn(`Failed to process sitemap ${sitemapUrl}:`, error instanceof Error ? error.message : "Unknown error");
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
await Promise.all(sitemapUrls.map((sitemapUrl) => queue.add(() => processSitemap(sitemapUrl))));
|
|
547
|
+
await queue.onIdle();
|
|
548
|
+
return [...result.links];
|
|
549
|
+
};
|
|
550
|
+
export {
|
|
551
|
+
getDomainSitemap
|
|
552
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple State Machine - A lightweight state machine implementation
|
|
3
|
+
*
|
|
4
|
+
* This provides a generic state machine abstraction that can be used
|
|
5
|
+
* for various workflows and business processes.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Type for validation result
|
|
9
|
+
* - true: transition is valid
|
|
10
|
+
* - string: transition is invalid with reason
|
|
11
|
+
*/
|
|
12
|
+
export type ValidationResult = true | string;
|
|
13
|
+
/**
|
|
14
|
+
* Type for validation function
|
|
15
|
+
* @param context The context object containing data needed for validation
|
|
16
|
+
* @param fromState The state transitioning from
|
|
17
|
+
* @param toState The state transitioning to
|
|
18
|
+
* @param action When validating a specific action, the action key (for per-action rules)
|
|
19
|
+
*/
|
|
20
|
+
export type ValidatorFn<TState extends string, TContext, TAction extends string = string> = (context: TContext, fromState: TState, toState: TState, action?: TAction) => Promise<ValidationResult> | ValidationResult;
|
|
21
|
+
/**
|
|
22
|
+
* Type for action handler function
|
|
23
|
+
* @param context The context object containing data needed for validation
|
|
24
|
+
* @param data Additional data passed to the action
|
|
25
|
+
* @returns Result of executing the action
|
|
26
|
+
*/
|
|
27
|
+
export type ActionHandlerFn<TContext, TData = unknown, TResult = unknown> = (context: TContext, data: TData) => Promise<TResult> | TResult;
|
|
28
|
+
export type ActionDataMap<TAction extends string> = Record<TAction, unknown>;
|
|
29
|
+
export type ActionResultMap<TAction extends string> = Record<TAction, unknown>;
|
|
30
|
+
export type StateKey<TContext, TState extends string> = {
|
|
31
|
+
[K in keyof TContext]: TContext[K] extends TState ? K : never;
|
|
32
|
+
}[keyof TContext];
|
|
33
|
+
export type ActionExecutionResult<TState extends string, TResult = unknown> = {
|
|
34
|
+
success: true;
|
|
35
|
+
newState: TState;
|
|
36
|
+
result?: TResult;
|
|
37
|
+
} | {
|
|
38
|
+
success: false;
|
|
39
|
+
error: string;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Interface for state machine configuration
|
|
43
|
+
*/
|
|
44
|
+
export interface StateMachineConfig<TState extends string, TAction extends string, TContext, TActionDataMap extends ActionDataMap<TAction> = ActionDataMap<TAction>, TActionResultMap extends ActionResultMap<TAction> = ActionResultMap<TAction>> {
|
|
45
|
+
stateActionMap: Record<TState, Partial<Record<TAction, TState>>>;
|
|
46
|
+
stateTransitions: Record<TState, readonly TState[]>;
|
|
47
|
+
validators?: Partial<Record<TState, ValidatorFn<TState, TContext, TAction>>>;
|
|
48
|
+
globalValidator?: ValidatorFn<TState, TContext, TAction>;
|
|
49
|
+
actions?: {
|
|
50
|
+
[K in TAction]?: ActionHandlerFn<TContext, TActionDataMap[K], TActionResultMap[K]>;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export type ValidateTransitionOptions<TAction extends string> = {
|
|
54
|
+
action?: TAction;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Creates a simple state machine with validation capabilities
|
|
58
|
+
*/
|
|
59
|
+
export declare class SimpleStateMachine<TState extends string, TAction extends string, TContext, TActionDataMap extends ActionDataMap<TAction> = ActionDataMap<TAction>, TActionResultMap extends ActionResultMap<TAction> = ActionResultMap<TAction>> {
|
|
60
|
+
private config;
|
|
61
|
+
private item;
|
|
62
|
+
private stateKey;
|
|
63
|
+
constructor(config: StateMachineConfig<TState, TAction, TContext, TActionDataMap, TActionResultMap>, item: TContext, stateKey: StateKey<TContext, TState>);
|
|
64
|
+
getCurrentStatus(): TState;
|
|
65
|
+
/**
|
|
66
|
+
* Get available actions for the current state with validation results
|
|
67
|
+
*
|
|
68
|
+
* @returns Map of actions to validation results
|
|
69
|
+
*/
|
|
70
|
+
getAvailableActions(): Promise<Partial<Record<TAction, ValidationResult>>>;
|
|
71
|
+
/**
|
|
72
|
+
* Get all possible state transitions from the current state
|
|
73
|
+
*
|
|
74
|
+
* @returns Map of target states to validation results (true if any action to that state validates)
|
|
75
|
+
*/
|
|
76
|
+
getPossibleTransitions(): Promise<Partial<Record<TState, ValidationResult>>>;
|
|
77
|
+
/**
|
|
78
|
+
* Executes an action with additional data
|
|
79
|
+
*
|
|
80
|
+
* @param action The action to execute
|
|
81
|
+
* @param data The data for the action
|
|
82
|
+
* @returns Promise with the execution result
|
|
83
|
+
*/
|
|
84
|
+
executeAction<A extends TAction>(action: A, data: TActionDataMap[A]): Promise<ActionExecutionResult<TState, TActionResultMap[A]>>;
|
|
85
|
+
/**
|
|
86
|
+
* Validates a transition between states
|
|
87
|
+
*
|
|
88
|
+
* @param toState The state transitioning to
|
|
89
|
+
* @param options When validating a specific action, pass `action` for per-action rules
|
|
90
|
+
*/
|
|
91
|
+
validateTransition(toState: TState, options?: ValidateTransitionOptions<TAction>): Promise<ValidationResult>;
|
|
92
|
+
/**
|
|
93
|
+
* Get all states in the state machine
|
|
94
|
+
*/
|
|
95
|
+
getStates(): TState[];
|
|
96
|
+
/**
|
|
97
|
+
* Get initial state of the state machine
|
|
98
|
+
* @returns The initial state
|
|
99
|
+
*/
|
|
100
|
+
getInitialState(): TState | undefined;
|
|
101
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type JsonValue = string | number | boolean | null | JsonObject | JsonArray;
|
|
2
|
+
export type JsonObject = {
|
|
3
|
+
[key: string]: JsonValue | undefined;
|
|
4
|
+
};
|
|
5
|
+
export type JsonArray = Array<JsonValue | undefined>;
|
|
6
|
+
export declare function extractNumber(value: string | null | undefined): number | undefined;
|
|
7
|
+
export declare function generateId(input: JsonValue | undefined): string;
|
|
8
|
+
export declare function slugify(value: string): string;
|