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.
Files changed (51) hide show
  1. package/README.md +510 -0
  2. package/dist/api-wrapper/index.d.ts +84 -0
  3. package/dist/api-wrapper/index.js +250 -0
  4. package/dist/array/index.d.ts +22 -0
  5. package/dist/cache/index.d.ts +19 -0
  6. package/dist/date/index.d.ts +11 -0
  7. package/dist/gzip/index.d.ts +2 -0
  8. package/dist/gzip/index.js +16 -0
  9. package/dist/index.d.ts +12 -0
  10. package/dist/index.js +1565 -0
  11. package/dist/jwt/index.d.ts +3 -0
  12. package/dist/jwt/index.js +19 -0
  13. package/dist/math/index.d.ts +2 -0
  14. package/dist/math/linearRegressionTrend.d.ts +5 -0
  15. package/dist/math/metricsSeriesColors.d.ts +42 -0
  16. package/dist/p-queue/index.d.ts +95 -0
  17. package/dist/p-queue/index.js +439 -0
  18. package/dist/promise/index.d.ts +21 -0
  19. package/dist/sitemap/index.d.ts +14 -0
  20. package/dist/sitemap/index.js +552 -0
  21. package/dist/state/index.d.ts +101 -0
  22. package/dist/string/index.d.ts +8 -0
  23. package/dist/typedswitch/index.d.ts +124 -0
  24. package/dist/url/index.d.ts +1 -0
  25. package/dist/zod/index.d.ts +8 -0
  26. package/dist/zod/index.js +210 -0
  27. package/dist/zod/jsonToZodSchemaMapper.d.ts +6 -0
  28. package/dist/zod/zodMinMaxFilter.d.ts +14 -0
  29. package/dist/zod/zodToTypeString.d.ts +9 -0
  30. package/package.json +102 -0
  31. package/src/api-wrapper/index.ts +492 -0
  32. package/src/array/index.ts +187 -0
  33. package/src/cache/index.ts +154 -0
  34. package/src/date/index.ts +54 -0
  35. package/src/gzip/index.ts +29 -0
  36. package/src/index.ts +12 -0
  37. package/src/jwt/index.ts +25 -0
  38. package/src/math/index.ts +11 -0
  39. package/src/math/linearRegressionTrend.ts +36 -0
  40. package/src/math/metricsSeriesColors.ts +208 -0
  41. package/src/p-queue/index.ts +690 -0
  42. package/src/promise/index.ts +73 -0
  43. package/src/sitemap/index.ts +195 -0
  44. package/src/state/index.ts +331 -0
  45. package/src/string/index.ts +86 -0
  46. package/src/typedswitch/index.ts +309 -0
  47. package/src/url/index.ts +5 -0
  48. package/src/zod/index.ts +29 -0
  49. package/src/zod/jsonToZodSchemaMapper.ts +44 -0
  50. package/src/zod/zodMinMaxFilter.ts +28 -0
  51. 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;