hanzio 1.2.0 → 2.1.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.
Files changed (155) hide show
  1. package/README.md +786 -239
  2. package/dist/api-wrapper/builders.d.ts +4 -3
  3. package/dist/api-wrapper/create-client.d.ts +1 -1
  4. package/dist/api-wrapper/describe.d.ts +16 -0
  5. package/dist/api-wrapper/endpoint.d.ts +18 -0
  6. package/dist/api-wrapper/errors.d.ts +39 -2
  7. package/dist/api-wrapper/index.d.ts +5 -0
  8. package/dist/api-wrapper/otel.d.ts +44 -0
  9. package/dist/api-wrapper/paginate.d.ts +12 -0
  10. package/dist/api-wrapper/redaction.d.ts +2 -0
  11. package/dist/api-wrapper/shared.d.ts +16 -2
  12. package/dist/api-wrapper/testing.d.ts +23 -0
  13. package/dist/api-wrapper/transport.d.ts +4 -5
  14. package/dist/api-wrapper/types.d.ts +103 -18
  15. package/dist/array/index.d.ts +50 -4
  16. package/dist/cache/createCache.d.ts +31 -0
  17. package/dist/cache/index.d.ts +19 -2
  18. package/dist/color/contrast.d.ts +40 -0
  19. package/dist/color/index.d.ts +2 -0
  20. package/dist/{math → color}/metricsSeriesColors.d.ts +14 -7
  21. package/dist/cool-console-log/index.d.ts +34 -5
  22. package/dist/crypto/encoding.d.ts +18 -0
  23. package/dist/crypto/hash.d.ts +33 -0
  24. package/dist/crypto/index.d.ts +4 -0
  25. package/dist/crypto/sealer.d.ts +25 -0
  26. package/dist/crypto/webhook.d.ts +20 -0
  27. package/dist/date/index.d.ts +46 -2
  28. package/dist/emitter/index.d.ts +21 -0
  29. package/dist/error/index.d.ts +12 -0
  30. package/dist/format/index.d.ts +102 -0
  31. package/dist/function/index.d.ts +60 -0
  32. package/dist/guard/index.d.ts +20 -0
  33. package/dist/i18n/index.d.ts +37 -0
  34. package/dist/index-0zvtcbzy.js +224 -0
  35. package/dist/index-8r2vdzjg.js +87 -0
  36. package/dist/index-hy7kbb99.js +157 -0
  37. package/dist/index-hyt6c56h.js +290 -0
  38. package/dist/{src/secrets/infisical.js → index-j1ybktbr.js} +500 -538
  39. package/dist/index-k9yx6bne.js +89 -0
  40. package/dist/index-kj1phgrx.js +169 -0
  41. package/dist/index-ktynw3vs.js +37 -0
  42. package/dist/index-mnkcfyx8.js +78 -0
  43. package/dist/{p-queue/index.js → index-pwt0dz63.js} +7 -11
  44. package/dist/index-rp5pqghz.js +157 -0
  45. package/dist/index-w882zry3.js +29 -0
  46. package/dist/index.d.ts +7 -7
  47. package/dist/jwt/index.d.ts +50 -2
  48. package/dist/math/index.d.ts +2 -2
  49. package/dist/math/linearRegressionTrend.d.ts +14 -1
  50. package/dist/math/scale.d.ts +8 -0
  51. package/dist/object/index.d.ts +19 -0
  52. package/dist/p-queue/index.d.ts +1 -1
  53. package/dist/promise/index.d.ts +97 -2
  54. package/dist/secrets/core.d.ts +7 -0
  55. package/dist/secrets/index.d.ts +3 -1
  56. package/dist/secrets/public-config.d.ts +7 -0
  57. package/dist/secrets/vite.d.ts +1 -0
  58. package/dist/secrets/worker.d.ts +1 -0
  59. package/dist/sitemap/index.d.ts +48 -6
  60. package/dist/src/api-wrapper/index.js +60 -1130
  61. package/dist/src/api-wrapper/testing.js +20 -0
  62. package/dist/{cool-console-log → src/cool-console-log}/index.js +80 -33
  63. package/dist/src/crypto/index.js +195 -0
  64. package/dist/src/format/index.js +211 -0
  65. package/dist/src/i18n/index.js +36 -0
  66. package/dist/src/index.js +739 -3145
  67. package/dist/src/jwt/index.js +138 -92
  68. package/dist/src/p-queue/index.js +9 -437
  69. package/dist/src/secrets/index.js +50 -1428
  70. package/dist/src/secrets/vite.js +10 -39
  71. package/dist/src/secrets/worker.js +14 -273
  72. package/dist/src/sitemap/index.js +183 -511
  73. package/dist/src/storage/index.js +119 -0
  74. package/dist/src/zod/index.js +340 -167
  75. package/dist/state/index.d.ts +21 -0
  76. package/dist/storage/index.d.ts +58 -0
  77. package/dist/string/index.d.ts +75 -2
  78. package/dist/typedswitch/index.d.ts +31 -24
  79. package/dist/types/index.d.ts +11 -0
  80. package/dist/url/index.d.ts +50 -1
  81. package/dist/zod/index.d.ts +32 -3
  82. package/dist/zod/introspect.d.ts +19 -0
  83. package/dist/zod/jsonToZodSchemaMapper.d.ts +3 -1
  84. package/dist/zod/zodMinMaxFilter.d.ts +22 -3
  85. package/dist/zod/zodToTypeString.d.ts +8 -3
  86. package/package.json +78 -7
  87. package/src/api-wrapper/actions-impl.ts +11 -72
  88. package/src/api-wrapper/builders.ts +32 -25
  89. package/src/api-wrapper/create-client.ts +171 -41
  90. package/src/api-wrapper/describe.ts +157 -0
  91. package/src/api-wrapper/endpoint.ts +26 -0
  92. package/src/api-wrapper/error-reporting.ts +7 -1
  93. package/src/api-wrapper/errors.ts +107 -2
  94. package/src/api-wrapper/headers.ts +11 -6
  95. package/src/api-wrapper/index.ts +5 -0
  96. package/src/api-wrapper/otel.ts +139 -0
  97. package/src/api-wrapper/paginate.ts +28 -0
  98. package/src/api-wrapper/redaction.ts +42 -0
  99. package/src/api-wrapper/shared.ts +21 -2
  100. package/src/api-wrapper/testing.ts +48 -0
  101. package/src/api-wrapper/transport.ts +41 -43
  102. package/src/api-wrapper/types.ts +185 -18
  103. package/src/array/index.ts +195 -42
  104. package/src/cache/createCache.ts +157 -0
  105. package/src/cache/index.ts +132 -147
  106. package/src/color/contrast.ts +163 -0
  107. package/src/color/index.ts +22 -0
  108. package/src/{math → color}/metricsSeriesColors.ts +47 -49
  109. package/src/cool-console-log/index.ts +128 -41
  110. package/src/crypto/encoding.ts +83 -0
  111. package/src/crypto/hash.ts +170 -0
  112. package/src/crypto/index.ts +26 -0
  113. package/src/crypto/sealer.ts +168 -0
  114. package/src/crypto/webhook.ts +86 -0
  115. package/src/date/index.ts +109 -34
  116. package/src/emitter/index.ts +82 -0
  117. package/src/error/index.ts +63 -0
  118. package/src/format/index.ts +389 -0
  119. package/src/function/index.ts +201 -0
  120. package/src/guard/index.ts +80 -0
  121. package/src/i18n/index.ts +125 -0
  122. package/src/index.ts +11 -7
  123. package/src/jwt/index.ts +199 -125
  124. package/src/math/index.ts +5 -10
  125. package/src/math/linearRegressionTrend.ts +33 -8
  126. package/src/math/scale.ts +17 -0
  127. package/src/object/index.ts +99 -0
  128. package/src/p-queue/index.ts +2 -0
  129. package/src/promise/index.ts +304 -9
  130. package/src/secrets/core.ts +72 -16
  131. package/src/secrets/index.ts +16 -4
  132. package/src/secrets/public-config.ts +47 -0
  133. package/src/secrets/vite.ts +2 -0
  134. package/src/secrets/worker.ts +2 -0
  135. package/src/sitemap/index.ts +293 -130
  136. package/src/state/index.ts +133 -81
  137. package/src/storage/index.ts +187 -0
  138. package/src/string/index.ts +261 -23
  139. package/src/typedswitch/index.ts +58 -38
  140. package/src/types/index.ts +14 -0
  141. package/src/url/index.ts +146 -1
  142. package/src/zod/index.ts +105 -9
  143. package/src/zod/introspect.ts +103 -0
  144. package/src/zod/jsonToZodSchemaMapper.ts +44 -21
  145. package/src/zod/zodMinMaxFilter.ts +37 -13
  146. package/src/zod/zodToTypeString.ts +184 -181
  147. package/dist/api-wrapper/index.js +0 -250
  148. package/dist/gzip/index.d.ts +0 -2
  149. package/dist/gzip/index.js +0 -16
  150. package/dist/index.js +0 -1823
  151. package/dist/jwt/index.js +0 -19
  152. package/dist/sitemap/index.js +0 -552
  153. package/dist/src/gzip/index.js +0 -16
  154. package/dist/src/secrets/loaders.js +0 -1529
  155. package/dist/zod/index.js +0 -210
@@ -1,552 +1,224 @@
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((item) => item.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
- }
1
+ import {
2
+ PQueue2
3
+ } from "../../index-pwt0dz63.js";
4
+ import {
5
+ escapeRegExp2
6
+ } from "../../index-kj1phgrx.js";
7
+ import {
8
+ normalizeDomain2
9
+ } from "../../index-mnkcfyx8.js";
48
10
 
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
- }
11
+ // src/sitemap/index.ts
12
+ var COMMON_LOCATIONS = [
13
+ "/sitemap.xml",
14
+ "/sitemap_index.xml",
15
+ "/sitemap/",
16
+ "/sitemaps.xml",
17
+ "/sitemap/sitemap.xml"
18
+ ];
19
+ var XML_ENTITIES = {
20
+ amp: "&",
21
+ lt: "<",
22
+ gt: ">",
23
+ quot: '"',
24
+ apos: "'"
25
+ };
26
+ var decodeXml = (value) => value.replace(/&(#[xX][\da-fA-F]+|#\d+|amp|lt|gt|quot|apos);/g, (_, entity) => entity[0] !== "#" ? XML_ENTITIES[entity] : String.fromCodePoint(entity[1] === "x" || entity[1] === "X" ? Number.parseInt(entity.slice(2), 16) : Number(entity.slice(1))));
27
+ var matchesFilter = (filter, url) => {
28
+ if (filter === undefined)
126
29
  return true;
30
+ if (typeof filter === "string")
31
+ return url.includes(filter);
32
+ if (typeof filter === "function")
33
+ return filter(url);
34
+ return url.search(filter) !== -1;
35
+ };
36
+ var defaultOnError = (url, error) => console.warn(`Failed to fetch ${url}:`, error instanceof Error ? error.message : "Unknown error");
37
+ var readBody = async (response) => {
38
+ const bytes = new Uint8Array(await response.arrayBuffer());
39
+ if (bytes[0] !== 31 || bytes[1] !== 139) {
40
+ return new TextDecoder().decode(bytes);
127
41
  }
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);
42
+ return new Response(new Blob([bytes]).stream().pipeThrough(new DecompressionStream("gzip"))).text();
43
+ };
44
+ var parseSitemap = (xml) => ({
45
+ isIndex: /<sitemapindex[\s>]/i.test(xml),
46
+ entries: [...xml.matchAll(/<(url|sitemap)[\s>][\s\S]*?<\/\1>/gi)].flatMap(([block]) => {
47
+ const loc = block.match(/<loc>\s*(?:<!\[CDATA\[([\s\S]*?)\]\]>|([^<]*?))\s*<\/loc>/i);
48
+ if (!loc)
49
+ return [];
50
+ const lastmod = block.match(/<lastmod>\s*([^<]*?)\s*<\/lastmod>/i)?.[1];
51
+ return [
52
+ {
53
+ loc: (loc[1] ?? decodeXml(loc[2])).trim(),
54
+ ...lastmod ? { lastmod } : {}
342
55
  }
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");
56
+ ];
57
+ })
58
+ });
59
+ var productToken = (userAgent) => userAgent.trim() === "*" ? "*" : /^[a-z_-]+/i.exec(userAgent.trim())?.[0].toLowerCase() ?? "";
60
+ function parseRobotsTxt(body, userAgent = "*") {
61
+ const groups = [];
62
+ const sitemaps = new Set;
63
+ let group;
64
+ let inRules = false;
65
+ for (const line of body.split(/\r\n|\r|\n/)) {
66
+ const match = /^\s*([a-z-]+)\s*:\s*(.*?)\s*$/i.exec(line.replace(/#.*/, ""));
67
+ if (!match)
68
+ continue;
69
+ const field = match[1].toLowerCase();
70
+ const value = match[2];
71
+ if (field === "sitemap") {
72
+ if (value)
73
+ sitemaps.add(value);
74
+ } else if (field === "user-agent") {
75
+ if (!group || inRules) {
76
+ group = { agents: [], allow: [], disallow: [] };
77
+ groups.push(group);
78
+ inRules = false;
363
79
  }
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();
80
+ group.agents.push(productToken(value));
81
+ } else if (group && (field === "allow" || field === "disallow")) {
82
+ inRules = true;
83
+ if (value)
84
+ group[field].push(value);
85
+ } else if (group && field === "crawl-delay") {
86
+ inRules = true;
87
+ const seconds = Number(value);
88
+ if (value && seconds >= 0)
89
+ group.crawlDelaySec ??= seconds;
90
+ }
91
+ }
92
+ const token = productToken(userAgent);
93
+ const named = groups.filter((g) => g.agents.includes(token));
94
+ const selected = named.length > 0 ? named : groups.filter((g) => g.agents.includes("*"));
95
+ const crawlDelaySec = selected.find((g) => g.crawlDelaySec !== undefined)?.crawlDelaySec;
96
+ return {
97
+ allow: selected.flatMap((g) => g.allow),
98
+ disallow: selected.flatMap((g) => g.disallow),
99
+ sitemaps: [...sitemaps],
100
+ ...crawlDelaySec === undefined ? {} : { crawlDelaySec }
101
+ };
102
+ }
103
+ var patternCache = new Map;
104
+ var matchesRobotsPattern = (pattern, path) => {
105
+ let regex = patternCache.get(pattern);
106
+ if (!regex) {
107
+ const anchored = pattern.endsWith("$");
108
+ const source = (anchored ? pattern.slice(0, -1) : pattern).split("*").map(escapeRegExp2).join(".*");
109
+ regex = new RegExp(`^${source}${anchored ? "$" : ""}`);
110
+ if (patternCache.size >= 1000)
111
+ patternCache.clear();
112
+ patternCache.set(pattern, regex);
113
+ }
114
+ return regex.test(path);
115
+ };
116
+ function isAllowedByRobots(rules, url) {
117
+ let path;
118
+ try {
119
+ const parsed = new URL(url, "http://localhost");
120
+ path = parsed.pathname + parsed.search;
121
+ } catch {
376
122
  return true;
377
123
  }
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;
124
+ if (path === "/robots.txt")
125
+ return true;
126
+ let best;
127
+ for (const [allow, patterns] of [
128
+ [true, rules.allow],
129
+ [false, rules.disallow]
130
+ ]) {
131
+ for (const pattern of patterns) {
132
+ if (pattern.length > (best?.length ?? -1) && matchesRobotsPattern(pattern, path)) {
133
+ best = { allow, length: pattern.length };
387
134
  }
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
135
  }
433
136
  }
137
+ return best?.allow ?? true;
434
138
  }
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
- };
139
+ var fetchRobots = async (origin, request, userAgent, onError) => {
140
+ const robotsUrl = `${origin}/robots.txt`;
458
141
  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
- }
142
+ const response = await request(robotsUrl);
143
+ if (response.ok)
144
+ return parseRobotsTxt(await response.text(), userAgent);
475
145
  } catch (error) {
476
- console.warn("Could not fetch robots.txt:", error instanceof Error ? error.message : "Unknown error");
146
+ onError(robotsUrl, error);
477
147
  }
478
- for (const location of commonLocations) {
148
+ return;
149
+ };
150
+ var probeSitemapLocations = async (origin, request) => {
151
+ for (const location of COMMON_LOCATIONS) {
479
152
  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) {}
153
+ const response = await request(`${origin}${location}`, "HEAD");
154
+ if (response.ok)
155
+ return [`${origin}${location}`];
156
+ } catch {}
488
157
  }
489
158
  return [];
490
159
  };
491
- var getDomainSitemap = async (domain, options = {}) => {
160
+ var getDomainSitemapEntries = async (domain, options = {}) => {
492
161
  const {
493
162
  filterIndexes,
163
+ filterUrls,
494
164
  concurrency = 10,
495
165
  maxDepth = 5,
496
- timeout = 1e4
166
+ maxUrls = Number.POSITIVE_INFINITY,
167
+ timeout = 1e4,
168
+ userAgent = "SitemapCrawler/1.0",
169
+ fetch: fetchFn = globalThis.fetch,
170
+ onError = defaultOnError,
171
+ respectRobots = false
497
172
  } = options;
498
- let normalizedDomain = domain.replace(/\/$/, "");
499
- if (!normalizedDomain.startsWith("http")) {
500
- normalizedDomain = `https://${normalizedDomain}`;
501
- }
502
- const sitemapUrls = await discoverSitemaps(normalizedDomain);
173
+ const request = (url, method = "GET") => fetchFn(url, {
174
+ method,
175
+ headers: { "User-Agent": userAgent },
176
+ signal: AbortSignal.timeout(timeout)
177
+ });
178
+ const origin = normalizeDomain2(domain);
179
+ const robots = await fetchRobots(origin, request, userAgent, onError);
180
+ const sitemapUrls = robots?.sitemaps.length ? robots.sitemaps : await probeSitemapLocations(origin, request);
181
+ const keepUrl = (url) => matchesFilter(filterUrls, url) && (!respectRobots || !robots || isAllowedByRobots(robots, url));
503
182
  if (sitemapUrls.length === 0) {
504
183
  throw new Error("No sitemaps found for the domain");
505
184
  }
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");
185
+ const queue = new PQueue2({ concurrency });
186
+ const entries = new Map;
187
+ const visited = new Set;
513
188
  const processSitemap = async (sitemapUrl, depth = 0) => {
514
- if (depth > maxDepth || result.visited.has(sitemapUrl)) {
189
+ if (depth > maxDepth || visited.has(sitemapUrl) || entries.size >= maxUrls) {
515
190
  return;
516
191
  }
517
- result.visited.add(sitemapUrl);
192
+ visited.add(sitemapUrl);
518
193
  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)
194
+ const response = await request(sitemapUrl);
195
+ if (!response.ok) {
527
196
  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);
197
+ }
198
+ const { isIndex, entries: found } = parseSitemap(await readBody(response));
199
+ for (const entry of found) {
200
+ if (isIndex) {
201
+ if (matchesFilter(filterIndexes, entry.loc)) {
202
+ queue.add(() => processSitemap(entry.loc, depth + 1));
203
+ }
204
+ } else if (entries.size >= maxUrls) {
205
+ return;
206
+ } else if (keepUrl(entry.loc)) {
207
+ entries.set(entry.loc, entries.get(entry.loc) ?? entry);
540
208
  }
541
209
  }
542
210
  } catch (error) {
543
- console.warn(`Failed to process sitemap ${sitemapUrl}:`, error instanceof Error ? error.message : "Unknown error");
211
+ onError(sitemapUrl, error);
544
212
  }
545
213
  };
546
214
  await Promise.all(sitemapUrls.map((sitemapUrl) => queue.add(() => processSitemap(sitemapUrl))));
547
215
  await queue.onIdle();
548
- return [...result.links];
216
+ return [...entries.values()];
549
217
  };
218
+ var getDomainSitemap = async (domain, options = {}) => (await getDomainSitemapEntries(domain, options)).map((entry) => entry.loc);
550
219
  export {
551
- getDomainSitemap
220
+ getDomainSitemap,
221
+ getDomainSitemapEntries,
222
+ isAllowedByRobots,
223
+ parseRobotsTxt
552
224
  };