@stackwright-pro/pulse 0.1.0 → 0.2.1-alpha.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 +364 -39
- package/dist/collectionData-3ZJA4PEZ.mjs +10 -0
- package/dist/index.d.mts +79 -1
- package/dist/index.d.ts +79 -1
- package/dist/index.js +416 -13
- package/dist/index.mjs +377 -12
- package/package.json +13 -6
package/dist/index.mjs
CHANGED
|
@@ -4,6 +4,8 @@ import { useState as useState2, useEffect as useEffect2 } from "react";
|
|
|
4
4
|
// src/hooks/usePulse.ts
|
|
5
5
|
import { useState, useCallback, useEffect } from "react";
|
|
6
6
|
import { useQuery } from "@tanstack/react-query";
|
|
7
|
+
var MIN_POLL_INTERVAL = 2e3;
|
|
8
|
+
var MAX_POLL_INTERVAL = 3e5;
|
|
7
9
|
function usePulse(options) {
|
|
8
10
|
const {
|
|
9
11
|
fetcher,
|
|
@@ -26,7 +28,7 @@ function usePulse(options) {
|
|
|
26
28
|
const { data, isLoading, isFetching, isSuccess, isError, error, refetch } = useQuery({
|
|
27
29
|
queryKey,
|
|
28
30
|
queryFn: validatedFetcher,
|
|
29
|
-
refetchInterval: enabled ? Math.max(interval,
|
|
31
|
+
refetchInterval: enabled ? Math.min(Math.max(interval ?? 5e3, MIN_POLL_INTERVAL), MAX_POLL_INTERVAL) : false,
|
|
30
32
|
refetchOnWindowFocus,
|
|
31
33
|
retry: retryCount,
|
|
32
34
|
retryDelay: (attemptIndex) => Math.min(1e3 * 2 ** attemptIndex, 1e4),
|
|
@@ -88,16 +90,15 @@ function Pulse({
|
|
|
88
90
|
emptyState,
|
|
89
91
|
showStaleDataOnError = true
|
|
90
92
|
}) {
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
});
|
|
93
|
+
const pulseOptions = { fetcher };
|
|
94
|
+
if (interval !== void 0) pulseOptions.interval = interval;
|
|
95
|
+
if (staleThreshold !== void 0) pulseOptions.staleThreshold = staleThreshold;
|
|
96
|
+
if (maxStaleAge !== void 0) pulseOptions.maxStaleAge = maxStaleAge;
|
|
97
|
+
if (schema !== void 0) pulseOptions.schema = schema;
|
|
98
|
+
if (enabled !== void 0) pulseOptions.enabled = enabled;
|
|
99
|
+
if (refetchOnWindowFocus !== void 0) pulseOptions.refetchOnWindowFocus = refetchOnWindowFocus;
|
|
100
|
+
if (retryCount !== void 0) pulseOptions.retryCount = retryCount;
|
|
101
|
+
const { data, meta, state } = usePulse(pulseOptions);
|
|
101
102
|
if (state === "loading" && data === void 0) {
|
|
102
103
|
return /* @__PURE__ */ jsx(Fragment, { children: loadingState ?? /* @__PURE__ */ jsx(DefaultLoading, {}) });
|
|
103
104
|
}
|
|
@@ -314,8 +315,366 @@ var PulseValidationError = class extends Error {
|
|
|
314
315
|
}));
|
|
315
316
|
}
|
|
316
317
|
};
|
|
318
|
+
|
|
319
|
+
// src/collection/PulseCollectionProvider.tsx
|
|
320
|
+
import React4, { createContext, useContext, useMemo } from "react";
|
|
321
|
+
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
322
|
+
var ALLOWED_COLLECTIONS = /* @__PURE__ */ new Set();
|
|
323
|
+
function setAllowedCollections(collections) {
|
|
324
|
+
ALLOWED_COLLECTIONS.clear();
|
|
325
|
+
collections.forEach((c) => ALLOWED_COLLECTIONS.add(c));
|
|
326
|
+
}
|
|
327
|
+
var PulseCollectionContext = createContext(null);
|
|
328
|
+
function resolveTemplate(template, collections) {
|
|
329
|
+
const match = template.match(/\{\{\s*([\w.]+)\s*\}\}/);
|
|
330
|
+
if (!match || match[1] === void 0) return template;
|
|
331
|
+
const path = match[1];
|
|
332
|
+
const parts = path.split(".");
|
|
333
|
+
const collection = parts[0];
|
|
334
|
+
if (!collection) return template;
|
|
335
|
+
if (!ALLOWED_COLLECTIONS.has(collection)) {
|
|
336
|
+
console.warn(`[PulseCollectionProvider] Collection "${collection}" not in allowed list`);
|
|
337
|
+
return template;
|
|
338
|
+
}
|
|
339
|
+
const data = collections[collection];
|
|
340
|
+
if (!data) return template;
|
|
341
|
+
let value = data;
|
|
342
|
+
for (let i = 1; i < parts.length; i++) {
|
|
343
|
+
const key = parts[i];
|
|
344
|
+
if (!key) return template;
|
|
345
|
+
if (value && typeof value === "object") {
|
|
346
|
+
value = value[key];
|
|
347
|
+
} else {
|
|
348
|
+
return template;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return value ?? template;
|
|
352
|
+
}
|
|
353
|
+
async function fetchCollectionData(collectionName) {
|
|
354
|
+
try {
|
|
355
|
+
const module = await import("./collectionData-3ZJA4PEZ.mjs");
|
|
356
|
+
return module.getCollectionData(collectionName);
|
|
357
|
+
} catch {
|
|
358
|
+
console.warn(`[PulseCollectionProvider] collectionData not generated for: ${collectionName}`);
|
|
359
|
+
return [];
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
function PulseCollectionProvider({
|
|
363
|
+
collections: collectionConfigs,
|
|
364
|
+
children,
|
|
365
|
+
fallback
|
|
366
|
+
}) {
|
|
367
|
+
React4.useEffect(() => {
|
|
368
|
+
setAllowedCollections(collectionConfigs.map((c) => c.collection));
|
|
369
|
+
}, [collectionConfigs]);
|
|
370
|
+
const pulseConfigs = useMemo(() => {
|
|
371
|
+
return collectionConfigs.map((config) => ({
|
|
372
|
+
name: config.collection,
|
|
373
|
+
fetcher: () => fetchCollectionData(config.collection),
|
|
374
|
+
interval: config.refreshInterval || 5e3
|
|
375
|
+
}));
|
|
376
|
+
}, [collectionConfigs]);
|
|
377
|
+
const [collectionsData, setCollectionsData] = React4.useState({});
|
|
378
|
+
const [allLoading, setAllLoading] = React4.useState(true);
|
|
379
|
+
const pulseElements = pulseConfigs.map((config) => /* @__PURE__ */ jsx4(
|
|
380
|
+
Pulse,
|
|
381
|
+
{
|
|
382
|
+
fetcher: config.fetcher,
|
|
383
|
+
interval: config.interval,
|
|
384
|
+
staleThreshold: 3e4,
|
|
385
|
+
maxStaleAge: 6e4,
|
|
386
|
+
children: (data, meta) => {
|
|
387
|
+
setCollectionsData((prev) => ({
|
|
388
|
+
...prev,
|
|
389
|
+
[config.name]: {
|
|
390
|
+
items: Array.isArray(data) ? data : [],
|
|
391
|
+
count: Array.isArray(data) ? data.length : 1,
|
|
392
|
+
meta,
|
|
393
|
+
...data
|
|
394
|
+
}
|
|
395
|
+
}));
|
|
396
|
+
if (pulseConfigs.length === Object.keys({ ...collectionsData, [config.name]: true }).length) {
|
|
397
|
+
setAllLoading(false);
|
|
398
|
+
}
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
401
|
+
},
|
|
402
|
+
config.name
|
|
403
|
+
));
|
|
404
|
+
const contextValue = {
|
|
405
|
+
collections: collectionsData,
|
|
406
|
+
isLoading: allLoading,
|
|
407
|
+
getCollection: (name) => collectionsData[name] || null,
|
|
408
|
+
getField: (collection, field) => {
|
|
409
|
+
const data = collectionsData[collection];
|
|
410
|
+
if (!data) return void 0;
|
|
411
|
+
return data[field] ?? resolveTemplate(field, collectionsData);
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
if (allLoading) {
|
|
415
|
+
return /* @__PURE__ */ jsx4(Fragment2, { children: fallback ?? /* @__PURE__ */ jsx4(DefaultLoading2, {}) });
|
|
416
|
+
}
|
|
417
|
+
return /* @__PURE__ */ jsxs4(PulseCollectionContext.Provider, { value: contextValue, children: [
|
|
418
|
+
pulseElements,
|
|
419
|
+
children
|
|
420
|
+
] });
|
|
421
|
+
}
|
|
422
|
+
function usePulseCollections() {
|
|
423
|
+
const context = useContext(PulseCollectionContext);
|
|
424
|
+
if (!context) {
|
|
425
|
+
throw new Error("usePulseCollections must be used within PulseCollectionProvider");
|
|
426
|
+
}
|
|
427
|
+
return context;
|
|
428
|
+
}
|
|
429
|
+
function useCollection(collectionName) {
|
|
430
|
+
const { getCollection } = usePulseCollections();
|
|
431
|
+
return getCollection(collectionName);
|
|
432
|
+
}
|
|
433
|
+
function useCollectionField(collectionName, field) {
|
|
434
|
+
const { getField } = usePulseCollections();
|
|
435
|
+
return getField(collectionName, field);
|
|
436
|
+
}
|
|
437
|
+
function useTemplateResolution() {
|
|
438
|
+
const { collections } = usePulseCollections();
|
|
439
|
+
return (template) => {
|
|
440
|
+
return resolveTemplate(template, collections);
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
function DefaultLoading2() {
|
|
444
|
+
return /* @__PURE__ */ jsx4("div", { style: { padding: "2rem", textAlign: "center", color: "#6B7280" }, children: "Loading live data..." });
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// src/collection/MetricCardPulse.tsx
|
|
448
|
+
import React5 from "react";
|
|
449
|
+
import { z } from "zod";
|
|
450
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
451
|
+
var MetricCardPulseSchema = z.object({
|
|
452
|
+
collection: z.string().min(1, "Collection name required"),
|
|
453
|
+
field: z.string().min(1, "Field path required"),
|
|
454
|
+
label: z.string().optional(),
|
|
455
|
+
icon: z.any().optional(),
|
|
456
|
+
// ReactNode
|
|
457
|
+
color: z.string().optional(),
|
|
458
|
+
trend: z.enum(["up", "down", "stable"]).optional(),
|
|
459
|
+
trendValue: z.string().optional(),
|
|
460
|
+
aggregate: z.enum(["count", "sum", "avg"]).optional(),
|
|
461
|
+
aggregateField: z.string().optional(),
|
|
462
|
+
filter: z.string().optional()
|
|
463
|
+
});
|
|
464
|
+
function runInDev(fn) {
|
|
465
|
+
try {
|
|
466
|
+
if (process.env.NODE_ENV !== "production") {
|
|
467
|
+
fn();
|
|
468
|
+
}
|
|
469
|
+
} catch {
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
function MetricCard({ label, value, icon, color, trend, trendValue }) {
|
|
473
|
+
const cardColor = color ?? "#0066CC";
|
|
474
|
+
return /* @__PURE__ */ jsxs5(
|
|
475
|
+
"div",
|
|
476
|
+
{
|
|
477
|
+
style: {
|
|
478
|
+
backgroundColor: "white",
|
|
479
|
+
borderRadius: "12px",
|
|
480
|
+
padding: "24px",
|
|
481
|
+
boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
|
|
482
|
+
border: "1px solid #E5E7EB",
|
|
483
|
+
position: "relative",
|
|
484
|
+
overflow: "hidden"
|
|
485
|
+
},
|
|
486
|
+
children: [
|
|
487
|
+
/* @__PURE__ */ jsx5(
|
|
488
|
+
"div",
|
|
489
|
+
{
|
|
490
|
+
style: {
|
|
491
|
+
position: "absolute",
|
|
492
|
+
top: 0,
|
|
493
|
+
left: 0,
|
|
494
|
+
right: 0,
|
|
495
|
+
height: "4px",
|
|
496
|
+
backgroundColor: cardColor
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
),
|
|
500
|
+
/* @__PURE__ */ jsxs5("div", { style: { display: "flex", alignItems: "flex-start", justifyContent: "space-between" }, children: [
|
|
501
|
+
/* @__PURE__ */ jsxs5("div", { style: { flex: 1 }, children: [
|
|
502
|
+
/* @__PURE__ */ jsx5(
|
|
503
|
+
"p",
|
|
504
|
+
{
|
|
505
|
+
style: {
|
|
506
|
+
fontSize: "14px",
|
|
507
|
+
color: "#6B7280",
|
|
508
|
+
margin: "0 0 8px 0",
|
|
509
|
+
fontWeight: 500
|
|
510
|
+
},
|
|
511
|
+
children: label
|
|
512
|
+
}
|
|
513
|
+
),
|
|
514
|
+
/* @__PURE__ */ jsx5(
|
|
515
|
+
"p",
|
|
516
|
+
{
|
|
517
|
+
style: {
|
|
518
|
+
fontSize: "36px",
|
|
519
|
+
fontWeight: 700,
|
|
520
|
+
color: "#111827",
|
|
521
|
+
margin: 0,
|
|
522
|
+
lineHeight: 1
|
|
523
|
+
},
|
|
524
|
+
children: typeof value === "number" ? value.toLocaleString() : value
|
|
525
|
+
}
|
|
526
|
+
),
|
|
527
|
+
trend && trendValue && /* @__PURE__ */ jsx5(
|
|
528
|
+
"div",
|
|
529
|
+
{
|
|
530
|
+
style: {
|
|
531
|
+
display: "flex",
|
|
532
|
+
alignItems: "center",
|
|
533
|
+
gap: "4px",
|
|
534
|
+
marginTop: "12px"
|
|
535
|
+
},
|
|
536
|
+
children: /* @__PURE__ */ jsxs5(
|
|
537
|
+
"span",
|
|
538
|
+
{
|
|
539
|
+
style: {
|
|
540
|
+
fontSize: "13px",
|
|
541
|
+
color: "#6B7280"
|
|
542
|
+
},
|
|
543
|
+
children: [
|
|
544
|
+
trend === "up" ? "\u2191" : trend === "down" ? "\u2193" : "\u2192",
|
|
545
|
+
" ",
|
|
546
|
+
trendValue
|
|
547
|
+
]
|
|
548
|
+
}
|
|
549
|
+
)
|
|
550
|
+
}
|
|
551
|
+
)
|
|
552
|
+
] }),
|
|
553
|
+
icon && /* @__PURE__ */ jsx5(
|
|
554
|
+
"div",
|
|
555
|
+
{
|
|
556
|
+
style: {
|
|
557
|
+
width: "48px",
|
|
558
|
+
height: "48px",
|
|
559
|
+
borderRadius: "12px",
|
|
560
|
+
backgroundColor: `${cardColor}15`,
|
|
561
|
+
display: "flex",
|
|
562
|
+
alignItems: "center",
|
|
563
|
+
justifyContent: "center",
|
|
564
|
+
color: cardColor
|
|
565
|
+
},
|
|
566
|
+
children: icon
|
|
567
|
+
}
|
|
568
|
+
)
|
|
569
|
+
] })
|
|
570
|
+
]
|
|
571
|
+
}
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
function MetricCardPulse({
|
|
575
|
+
collection,
|
|
576
|
+
field,
|
|
577
|
+
label,
|
|
578
|
+
icon,
|
|
579
|
+
color,
|
|
580
|
+
trend,
|
|
581
|
+
trendValue,
|
|
582
|
+
aggregate,
|
|
583
|
+
aggregateField
|
|
584
|
+
}) {
|
|
585
|
+
runInDev(() => {
|
|
586
|
+
const result = MetricCardPulseSchema.safeParse({
|
|
587
|
+
collection,
|
|
588
|
+
field,
|
|
589
|
+
label,
|
|
590
|
+
icon,
|
|
591
|
+
color,
|
|
592
|
+
trend,
|
|
593
|
+
trendValue,
|
|
594
|
+
aggregate,
|
|
595
|
+
aggregateField
|
|
596
|
+
});
|
|
597
|
+
if (!result.success) {
|
|
598
|
+
console.warn("[MetricCardPulse] Invalid props:", result.error.issues);
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
const value = useCollectionField(collection, field);
|
|
602
|
+
const displayValue = React5.useMemo(() => {
|
|
603
|
+
if (value === null || value === void 0) return 0;
|
|
604
|
+
if (aggregate === "count") {
|
|
605
|
+
return Array.isArray(value) ? value.length : value ?? 0;
|
|
606
|
+
}
|
|
607
|
+
if ((aggregate === "sum" || aggregate === "avg") && aggregateField) {
|
|
608
|
+
const arr = Array.isArray(value) ? value : [];
|
|
609
|
+
if (aggregate === "sum") {
|
|
610
|
+
return arr.reduce((sum2, item) => sum2 + (item[aggregateField] ?? 0), 0);
|
|
611
|
+
}
|
|
612
|
+
if (arr.length === 0) return 0;
|
|
613
|
+
const sum = arr.reduce((s, item) => s + (item[aggregateField] ?? 0), 0);
|
|
614
|
+
return Math.round(sum / arr.length * 10) / 10;
|
|
615
|
+
}
|
|
616
|
+
return typeof value === "number" ? value : 0;
|
|
617
|
+
}, [value, aggregate, aggregateField]);
|
|
618
|
+
return /* @__PURE__ */ jsx5(
|
|
619
|
+
MetricCard,
|
|
620
|
+
{
|
|
621
|
+
label: label ?? field,
|
|
622
|
+
value: typeof displayValue === "number" ? displayValue : 0,
|
|
623
|
+
icon,
|
|
624
|
+
color,
|
|
625
|
+
trend,
|
|
626
|
+
trendValue
|
|
627
|
+
}
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// src/collection/DataTablePulse.tsx
|
|
632
|
+
import { useMemo as useMemo2 } from "react";
|
|
633
|
+
import { z as z2 } from "zod";
|
|
634
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
635
|
+
var ColumnSchema = z2.object({
|
|
636
|
+
field: z2.string().min(1),
|
|
637
|
+
header: z2.string(),
|
|
638
|
+
type: z2.enum(["text", "badge", "date", "number"]).optional(),
|
|
639
|
+
sortable: z2.boolean().optional(),
|
|
640
|
+
filterable: z2.boolean().optional()
|
|
641
|
+
});
|
|
642
|
+
var DataTablePulseSchema = z2.object({
|
|
643
|
+
collection: z2.string().min(1),
|
|
644
|
+
columns: z2.array(ColumnSchema).min(1, "At least one column required"),
|
|
645
|
+
filter: z2.string().optional(),
|
|
646
|
+
sortBy: z2.string().optional(),
|
|
647
|
+
sortDirection: z2.enum(["asc", "desc"]).optional(),
|
|
648
|
+
limit: z2.number().int().positive().max(1e3).optional(),
|
|
649
|
+
onRowClick: z2.function().optional(),
|
|
650
|
+
emptyMessage: z2.string().optional()
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
// src/collection/StatusBadgePulse.tsx
|
|
654
|
+
import React7 from "react";
|
|
655
|
+
import { z as z3 } from "zod";
|
|
656
|
+
import { StatusBadge } from "@stackwright-pro/display-components";
|
|
657
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
658
|
+
var StatusBadgePulseSchema = z3.object({
|
|
659
|
+
collection: z3.string().min(1),
|
|
660
|
+
field: z3.string().min(1),
|
|
661
|
+
label: z3.string().optional(),
|
|
662
|
+
pulse: z3.boolean().optional(),
|
|
663
|
+
statusMap: z3.record(z3.string(), z3.enum(["operational", "degraded", "outage", "maintenance"])).optional()
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
// src/registration.ts
|
|
667
|
+
import { registerComponent } from "@stackwright/core";
|
|
668
|
+
function registerPulseComponents() {
|
|
669
|
+
registerComponent("pulse_provider", () => null);
|
|
670
|
+
registerComponent("metric_card_pulse", () => null);
|
|
671
|
+
registerComponent("data_table_pulse", () => null);
|
|
672
|
+
registerComponent("status_badge_pulse", () => null);
|
|
673
|
+
}
|
|
317
674
|
export {
|
|
675
|
+
MetricCardPulse,
|
|
318
676
|
Pulse,
|
|
677
|
+
PulseCollectionProvider,
|
|
319
678
|
PulseEmptyState,
|
|
320
679
|
PulseErrorState,
|
|
321
680
|
PulseIndicator,
|
|
@@ -324,6 +683,12 @@ export {
|
|
|
324
683
|
PulseSyncingState,
|
|
325
684
|
PulseValidationError,
|
|
326
685
|
createPulseValidator,
|
|
686
|
+
registerPulseComponents,
|
|
687
|
+
resolveTemplate,
|
|
688
|
+
useCollection,
|
|
689
|
+
useCollectionField,
|
|
327
690
|
usePulse,
|
|
328
|
-
|
|
691
|
+
usePulseCollections,
|
|
692
|
+
useStreaming,
|
|
693
|
+
useTemplateResolution
|
|
329
694
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stackwright-pro/pulse",
|
|
3
|
-
"version": "0.1.0",
|
|
3
|
+
"version": "0.2.1-alpha.0",
|
|
4
4
|
"description": "Source-agnostic real-time data polling for Stackwright Pro",
|
|
5
5
|
"license": "PROPRIETARY",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -18,16 +18,22 @@
|
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@tanstack/react-query": "^5.0.0",
|
|
21
|
-
"zod": "^4.3.6"
|
|
21
|
+
"zod": "^4.3.6",
|
|
22
|
+
"@stackwright-pro/display-components": "0.1.2-alpha.0"
|
|
22
23
|
},
|
|
23
24
|
"peerDependencies": {
|
|
24
|
-
"
|
|
25
|
-
"react
|
|
25
|
+
"@stackwright/core": ">=0.7.0",
|
|
26
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
27
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
28
|
+
},
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
26
31
|
},
|
|
27
32
|
"devDependencies": {
|
|
33
|
+
"@testing-library/react": "^16.0.0",
|
|
34
|
+
"@types/node": "^24.12.0",
|
|
28
35
|
"@types/react": "^18.3.0",
|
|
29
36
|
"@types/react-dom": "^18.3.0",
|
|
30
|
-
"@testing-library/react": "^16.0.0",
|
|
31
37
|
"jsdom": "^25.0.0",
|
|
32
38
|
"tsup": "^8.5.0",
|
|
33
39
|
"typescript": "^5.8.3",
|
|
@@ -36,6 +42,7 @@
|
|
|
36
42
|
"scripts": {
|
|
37
43
|
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
38
44
|
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
39
|
-
"test": "vitest"
|
|
45
|
+
"test": "vitest",
|
|
46
|
+
"test:coverage": "vitest run --coverage"
|
|
40
47
|
}
|
|
41
48
|
}
|