@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/dist/index.js CHANGED
@@ -1,8 +1,13 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
6
11
  var __export = (target, all) => {
7
12
  for (var name in all)
8
13
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -15,12 +20,39 @@ var __copyProps = (to, from, except, desc) => {
15
20
  }
16
21
  return to;
17
22
  };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
18
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
32
 
33
+ // src/collection/collectionData.ts
34
+ var collectionData_exports = {};
35
+ __export(collectionData_exports, {
36
+ getCollectionData: () => getCollectionData
37
+ });
38
+ async function getCollectionData(collection) {
39
+ console.warn(
40
+ `[collectionData] Stub called for collection: ${collection}. Run prebuild to generate real data fetchers.`
41
+ );
42
+ return [];
43
+ }
44
+ var init_collectionData = __esm({
45
+ "src/collection/collectionData.ts"() {
46
+ "use strict";
47
+ }
48
+ });
49
+
20
50
  // src/index.ts
21
51
  var index_exports = {};
22
52
  __export(index_exports, {
53
+ MetricCardPulse: () => MetricCardPulse,
23
54
  Pulse: () => Pulse,
55
+ PulseCollectionProvider: () => PulseCollectionProvider,
24
56
  PulseEmptyState: () => PulseEmptyState,
25
57
  PulseErrorState: () => PulseErrorState,
26
58
  PulseIndicator: () => PulseIndicator,
@@ -29,8 +61,14 @@ __export(index_exports, {
29
61
  PulseSyncingState: () => PulseSyncingState,
30
62
  PulseValidationError: () => PulseValidationError,
31
63
  createPulseValidator: () => createPulseValidator,
64
+ registerPulseComponents: () => registerPulseComponents,
65
+ resolveTemplate: () => resolveTemplate,
66
+ useCollection: () => useCollection,
67
+ useCollectionField: () => useCollectionField,
32
68
  usePulse: () => usePulse,
33
- useStreaming: () => useStreaming
69
+ usePulseCollections: () => usePulseCollections,
70
+ useStreaming: () => useStreaming,
71
+ useTemplateResolution: () => useTemplateResolution
34
72
  });
35
73
  module.exports = __toCommonJS(index_exports);
36
74
 
@@ -40,6 +78,8 @@ var import_react2 = require("react");
40
78
  // src/hooks/usePulse.ts
41
79
  var import_react = require("react");
42
80
  var import_react_query = require("@tanstack/react-query");
81
+ var MIN_POLL_INTERVAL = 2e3;
82
+ var MAX_POLL_INTERVAL = 3e5;
43
83
  function usePulse(options) {
44
84
  const {
45
85
  fetcher,
@@ -62,7 +102,7 @@ function usePulse(options) {
62
102
  const { data, isLoading, isFetching, isSuccess, isError, error, refetch } = (0, import_react_query.useQuery)({
63
103
  queryKey,
64
104
  queryFn: validatedFetcher,
65
- refetchInterval: enabled ? Math.max(interval, 1e3) : false,
105
+ refetchInterval: enabled ? Math.min(Math.max(interval ?? 5e3, MIN_POLL_INTERVAL), MAX_POLL_INTERVAL) : false,
66
106
  refetchOnWindowFocus,
67
107
  retry: retryCount,
68
108
  retryDelay: (attemptIndex) => Math.min(1e3 * 2 ** attemptIndex, 1e4),
@@ -124,16 +164,15 @@ function Pulse({
124
164
  emptyState,
125
165
  showStaleDataOnError = true
126
166
  }) {
127
- const { data, meta, state } = usePulse({
128
- fetcher,
129
- interval,
130
- staleThreshold,
131
- maxStaleAge,
132
- schema,
133
- enabled,
134
- refetchOnWindowFocus,
135
- retryCount
136
- });
167
+ const pulseOptions = { fetcher };
168
+ if (interval !== void 0) pulseOptions.interval = interval;
169
+ if (staleThreshold !== void 0) pulseOptions.staleThreshold = staleThreshold;
170
+ if (maxStaleAge !== void 0) pulseOptions.maxStaleAge = maxStaleAge;
171
+ if (schema !== void 0) pulseOptions.schema = schema;
172
+ if (enabled !== void 0) pulseOptions.enabled = enabled;
173
+ if (refetchOnWindowFocus !== void 0) pulseOptions.refetchOnWindowFocus = refetchOnWindowFocus;
174
+ if (retryCount !== void 0) pulseOptions.retryCount = retryCount;
175
+ const { data, meta, state } = usePulse(pulseOptions);
137
176
  if (state === "loading" && data === void 0) {
138
177
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: loadingState ?? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DefaultLoading, {}) });
139
178
  }
@@ -350,9 +389,367 @@ var PulseValidationError = class extends Error {
350
389
  }));
351
390
  }
352
391
  };
392
+
393
+ // src/collection/PulseCollectionProvider.tsx
394
+ var import_react5 = __toESM(require("react"));
395
+ var import_jsx_runtime4 = require("react/jsx-runtime");
396
+ var ALLOWED_COLLECTIONS = /* @__PURE__ */ new Set();
397
+ function setAllowedCollections(collections) {
398
+ ALLOWED_COLLECTIONS.clear();
399
+ collections.forEach((c) => ALLOWED_COLLECTIONS.add(c));
400
+ }
401
+ var PulseCollectionContext = (0, import_react5.createContext)(null);
402
+ function resolveTemplate(template, collections) {
403
+ const match = template.match(/\{\{\s*([\w.]+)\s*\}\}/);
404
+ if (!match || match[1] === void 0) return template;
405
+ const path = match[1];
406
+ const parts = path.split(".");
407
+ const collection = parts[0];
408
+ if (!collection) return template;
409
+ if (!ALLOWED_COLLECTIONS.has(collection)) {
410
+ console.warn(`[PulseCollectionProvider] Collection "${collection}" not in allowed list`);
411
+ return template;
412
+ }
413
+ const data = collections[collection];
414
+ if (!data) return template;
415
+ let value = data;
416
+ for (let i = 1; i < parts.length; i++) {
417
+ const key = parts[i];
418
+ if (!key) return template;
419
+ if (value && typeof value === "object") {
420
+ value = value[key];
421
+ } else {
422
+ return template;
423
+ }
424
+ }
425
+ return value ?? template;
426
+ }
427
+ async function fetchCollectionData(collectionName) {
428
+ try {
429
+ const module2 = await Promise.resolve().then(() => (init_collectionData(), collectionData_exports));
430
+ return module2.getCollectionData(collectionName);
431
+ } catch {
432
+ console.warn(`[PulseCollectionProvider] collectionData not generated for: ${collectionName}`);
433
+ return [];
434
+ }
435
+ }
436
+ function PulseCollectionProvider({
437
+ collections: collectionConfigs,
438
+ children,
439
+ fallback
440
+ }) {
441
+ import_react5.default.useEffect(() => {
442
+ setAllowedCollections(collectionConfigs.map((c) => c.collection));
443
+ }, [collectionConfigs]);
444
+ const pulseConfigs = (0, import_react5.useMemo)(() => {
445
+ return collectionConfigs.map((config) => ({
446
+ name: config.collection,
447
+ fetcher: () => fetchCollectionData(config.collection),
448
+ interval: config.refreshInterval || 5e3
449
+ }));
450
+ }, [collectionConfigs]);
451
+ const [collectionsData, setCollectionsData] = import_react5.default.useState({});
452
+ const [allLoading, setAllLoading] = import_react5.default.useState(true);
453
+ const pulseElements = pulseConfigs.map((config) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
454
+ Pulse,
455
+ {
456
+ fetcher: config.fetcher,
457
+ interval: config.interval,
458
+ staleThreshold: 3e4,
459
+ maxStaleAge: 6e4,
460
+ children: (data, meta) => {
461
+ setCollectionsData((prev) => ({
462
+ ...prev,
463
+ [config.name]: {
464
+ items: Array.isArray(data) ? data : [],
465
+ count: Array.isArray(data) ? data.length : 1,
466
+ meta,
467
+ ...data
468
+ }
469
+ }));
470
+ if (pulseConfigs.length === Object.keys({ ...collectionsData, [config.name]: true }).length) {
471
+ setAllLoading(false);
472
+ }
473
+ return null;
474
+ }
475
+ },
476
+ config.name
477
+ ));
478
+ const contextValue = {
479
+ collections: collectionsData,
480
+ isLoading: allLoading,
481
+ getCollection: (name) => collectionsData[name] || null,
482
+ getField: (collection, field) => {
483
+ const data = collectionsData[collection];
484
+ if (!data) return void 0;
485
+ return data[field] ?? resolveTemplate(field, collectionsData);
486
+ }
487
+ };
488
+ if (allLoading) {
489
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: fallback ?? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(DefaultLoading2, {}) });
490
+ }
491
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(PulseCollectionContext.Provider, { value: contextValue, children: [
492
+ pulseElements,
493
+ children
494
+ ] });
495
+ }
496
+ function usePulseCollections() {
497
+ const context = (0, import_react5.useContext)(PulseCollectionContext);
498
+ if (!context) {
499
+ throw new Error("usePulseCollections must be used within PulseCollectionProvider");
500
+ }
501
+ return context;
502
+ }
503
+ function useCollection(collectionName) {
504
+ const { getCollection } = usePulseCollections();
505
+ return getCollection(collectionName);
506
+ }
507
+ function useCollectionField(collectionName, field) {
508
+ const { getField } = usePulseCollections();
509
+ return getField(collectionName, field);
510
+ }
511
+ function useTemplateResolution() {
512
+ const { collections } = usePulseCollections();
513
+ return (template) => {
514
+ return resolveTemplate(template, collections);
515
+ };
516
+ }
517
+ function DefaultLoading2() {
518
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { padding: "2rem", textAlign: "center", color: "#6B7280" }, children: "Loading live data..." });
519
+ }
520
+
521
+ // src/collection/MetricCardPulse.tsx
522
+ var import_react6 = __toESM(require("react"));
523
+ var import_zod = require("zod");
524
+ var import_jsx_runtime5 = require("react/jsx-runtime");
525
+ var MetricCardPulseSchema = import_zod.z.object({
526
+ collection: import_zod.z.string().min(1, "Collection name required"),
527
+ field: import_zod.z.string().min(1, "Field path required"),
528
+ label: import_zod.z.string().optional(),
529
+ icon: import_zod.z.any().optional(),
530
+ // ReactNode
531
+ color: import_zod.z.string().optional(),
532
+ trend: import_zod.z.enum(["up", "down", "stable"]).optional(),
533
+ trendValue: import_zod.z.string().optional(),
534
+ aggregate: import_zod.z.enum(["count", "sum", "avg"]).optional(),
535
+ aggregateField: import_zod.z.string().optional(),
536
+ filter: import_zod.z.string().optional()
537
+ });
538
+ function runInDev(fn) {
539
+ try {
540
+ if (process.env.NODE_ENV !== "production") {
541
+ fn();
542
+ }
543
+ } catch {
544
+ }
545
+ }
546
+ function MetricCard({ label, value, icon, color, trend, trendValue }) {
547
+ const cardColor = color ?? "#0066CC";
548
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
549
+ "div",
550
+ {
551
+ style: {
552
+ backgroundColor: "white",
553
+ borderRadius: "12px",
554
+ padding: "24px",
555
+ boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
556
+ border: "1px solid #E5E7EB",
557
+ position: "relative",
558
+ overflow: "hidden"
559
+ },
560
+ children: [
561
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
562
+ "div",
563
+ {
564
+ style: {
565
+ position: "absolute",
566
+ top: 0,
567
+ left: 0,
568
+ right: 0,
569
+ height: "4px",
570
+ backgroundColor: cardColor
571
+ }
572
+ }
573
+ ),
574
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { display: "flex", alignItems: "flex-start", justifyContent: "space-between" }, children: [
575
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { flex: 1 }, children: [
576
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
577
+ "p",
578
+ {
579
+ style: {
580
+ fontSize: "14px",
581
+ color: "#6B7280",
582
+ margin: "0 0 8px 0",
583
+ fontWeight: 500
584
+ },
585
+ children: label
586
+ }
587
+ ),
588
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
589
+ "p",
590
+ {
591
+ style: {
592
+ fontSize: "36px",
593
+ fontWeight: 700,
594
+ color: "#111827",
595
+ margin: 0,
596
+ lineHeight: 1
597
+ },
598
+ children: typeof value === "number" ? value.toLocaleString() : value
599
+ }
600
+ ),
601
+ trend && trendValue && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
602
+ "div",
603
+ {
604
+ style: {
605
+ display: "flex",
606
+ alignItems: "center",
607
+ gap: "4px",
608
+ marginTop: "12px"
609
+ },
610
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
611
+ "span",
612
+ {
613
+ style: {
614
+ fontSize: "13px",
615
+ color: "#6B7280"
616
+ },
617
+ children: [
618
+ trend === "up" ? "\u2191" : trend === "down" ? "\u2193" : "\u2192",
619
+ " ",
620
+ trendValue
621
+ ]
622
+ }
623
+ )
624
+ }
625
+ )
626
+ ] }),
627
+ icon && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
628
+ "div",
629
+ {
630
+ style: {
631
+ width: "48px",
632
+ height: "48px",
633
+ borderRadius: "12px",
634
+ backgroundColor: `${cardColor}15`,
635
+ display: "flex",
636
+ alignItems: "center",
637
+ justifyContent: "center",
638
+ color: cardColor
639
+ },
640
+ children: icon
641
+ }
642
+ )
643
+ ] })
644
+ ]
645
+ }
646
+ );
647
+ }
648
+ function MetricCardPulse({
649
+ collection,
650
+ field,
651
+ label,
652
+ icon,
653
+ color,
654
+ trend,
655
+ trendValue,
656
+ aggregate,
657
+ aggregateField
658
+ }) {
659
+ runInDev(() => {
660
+ const result = MetricCardPulseSchema.safeParse({
661
+ collection,
662
+ field,
663
+ label,
664
+ icon,
665
+ color,
666
+ trend,
667
+ trendValue,
668
+ aggregate,
669
+ aggregateField
670
+ });
671
+ if (!result.success) {
672
+ console.warn("[MetricCardPulse] Invalid props:", result.error.issues);
673
+ }
674
+ });
675
+ const value = useCollectionField(collection, field);
676
+ const displayValue = import_react6.default.useMemo(() => {
677
+ if (value === null || value === void 0) return 0;
678
+ if (aggregate === "count") {
679
+ return Array.isArray(value) ? value.length : value ?? 0;
680
+ }
681
+ if ((aggregate === "sum" || aggregate === "avg") && aggregateField) {
682
+ const arr = Array.isArray(value) ? value : [];
683
+ if (aggregate === "sum") {
684
+ return arr.reduce((sum2, item) => sum2 + (item[aggregateField] ?? 0), 0);
685
+ }
686
+ if (arr.length === 0) return 0;
687
+ const sum = arr.reduce((s, item) => s + (item[aggregateField] ?? 0), 0);
688
+ return Math.round(sum / arr.length * 10) / 10;
689
+ }
690
+ return typeof value === "number" ? value : 0;
691
+ }, [value, aggregate, aggregateField]);
692
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
693
+ MetricCard,
694
+ {
695
+ label: label ?? field,
696
+ value: typeof displayValue === "number" ? displayValue : 0,
697
+ icon,
698
+ color,
699
+ trend,
700
+ trendValue
701
+ }
702
+ );
703
+ }
704
+
705
+ // src/collection/DataTablePulse.tsx
706
+ var import_react7 = require("react");
707
+ var import_zod2 = require("zod");
708
+ var import_jsx_runtime6 = require("react/jsx-runtime");
709
+ var ColumnSchema = import_zod2.z.object({
710
+ field: import_zod2.z.string().min(1),
711
+ header: import_zod2.z.string(),
712
+ type: import_zod2.z.enum(["text", "badge", "date", "number"]).optional(),
713
+ sortable: import_zod2.z.boolean().optional(),
714
+ filterable: import_zod2.z.boolean().optional()
715
+ });
716
+ var DataTablePulseSchema = import_zod2.z.object({
717
+ collection: import_zod2.z.string().min(1),
718
+ columns: import_zod2.z.array(ColumnSchema).min(1, "At least one column required"),
719
+ filter: import_zod2.z.string().optional(),
720
+ sortBy: import_zod2.z.string().optional(),
721
+ sortDirection: import_zod2.z.enum(["asc", "desc"]).optional(),
722
+ limit: import_zod2.z.number().int().positive().max(1e3).optional(),
723
+ onRowClick: import_zod2.z.function().optional(),
724
+ emptyMessage: import_zod2.z.string().optional()
725
+ });
726
+
727
+ // src/collection/StatusBadgePulse.tsx
728
+ var import_react8 = __toESM(require("react"));
729
+ var import_zod3 = require("zod");
730
+ var import_display_components = require("@stackwright-pro/display-components");
731
+ var import_jsx_runtime7 = require("react/jsx-runtime");
732
+ var StatusBadgePulseSchema = import_zod3.z.object({
733
+ collection: import_zod3.z.string().min(1),
734
+ field: import_zod3.z.string().min(1),
735
+ label: import_zod3.z.string().optional(),
736
+ pulse: import_zod3.z.boolean().optional(),
737
+ statusMap: import_zod3.z.record(import_zod3.z.string(), import_zod3.z.enum(["operational", "degraded", "outage", "maintenance"])).optional()
738
+ });
739
+
740
+ // src/registration.ts
741
+ var import_core = require("@stackwright/core");
742
+ function registerPulseComponents() {
743
+ (0, import_core.registerComponent)("pulse_provider", () => null);
744
+ (0, import_core.registerComponent)("metric_card_pulse", () => null);
745
+ (0, import_core.registerComponent)("data_table_pulse", () => null);
746
+ (0, import_core.registerComponent)("status_badge_pulse", () => null);
747
+ }
353
748
  // Annotate the CommonJS export names for ESM import in node:
354
749
  0 && (module.exports = {
750
+ MetricCardPulse,
355
751
  Pulse,
752
+ PulseCollectionProvider,
356
753
  PulseEmptyState,
357
754
  PulseErrorState,
358
755
  PulseIndicator,
@@ -361,6 +758,12 @@ var PulseValidationError = class extends Error {
361
758
  PulseSyncingState,
362
759
  PulseValidationError,
363
760
  createPulseValidator,
761
+ registerPulseComponents,
762
+ resolveTemplate,
763
+ useCollection,
764
+ useCollectionField,
364
765
  usePulse,
365
- useStreaming
766
+ usePulseCollections,
767
+ useStreaming,
768
+ useTemplateResolution
366
769
  });