@happyvertical/smrt-events 0.37.2 → 0.37.3

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/utils.js CHANGED
@@ -1,127 +1,96 @@
1
+ //#region src/utils.ts
1
2
  function validateEventStatus(currentStatus, newStatus) {
2
- const validTransitions = {
3
- scheduled: ["in_progress", "cancelled", "postponed"],
4
- in_progress: ["completed", "cancelled"],
5
- completed: [],
6
- // Cannot transition from completed
7
- cancelled: ["scheduled"],
8
- // Can reschedule
9
- postponed: ["scheduled", "cancelled"]
10
- };
11
- return validTransitions[currentStatus]?.includes(newStatus) || false;
3
+ return {
4
+ scheduled: [
5
+ "in_progress",
6
+ "cancelled",
7
+ "postponed"
8
+ ],
9
+ in_progress: ["completed", "cancelled"],
10
+ completed: [],
11
+ cancelled: ["scheduled"],
12
+ postponed: ["scheduled", "cancelled"]
13
+ }[currentStatus]?.includes(newStatus) || false;
12
14
  }
13
15
  function formatEventDateRange(startDate, endDate) {
14
- const start = startDate.toLocaleDateString();
15
- if (!endDate) {
16
- return start;
17
- }
18
- if (startDate.toDateString() === endDate.toDateString()) {
19
- return `${start} ${startDate.toLocaleTimeString()} - ${endDate.toLocaleTimeString()}`;
20
- }
21
- return `${start} - ${endDate.toLocaleDateString()}`;
16
+ const start = startDate.toLocaleDateString();
17
+ if (!endDate) return start;
18
+ if (startDate.toDateString() === endDate.toDateString()) return `${start} ${startDate.toLocaleTimeString()} - ${endDate.toLocaleTimeString()}`;
19
+ return `${start} - ${endDate.toLocaleDateString()}`;
22
20
  }
23
21
  function generateEventSlug(name, date) {
24
- const namePart = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
25
- const datePart = date.toISOString().split("T")[0];
26
- return `${namePart}-${datePart}`;
22
+ return `${name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")}-${date.toISOString().split("T")[0]}`;
27
23
  }
28
24
  function checkSchedulingConflict(event1Start, event1End, event2Start, event2End) {
29
- const e1End = event1End || event1Start;
30
- const e2End = event2End || event2Start;
31
- return event1Start < e2End && e1End > event2Start;
25
+ return event1Start < (event2End || event2Start) && (event1End || event1Start) > event2Start;
32
26
  }
33
27
  function parseRecurrencePattern(pattern) {
34
- if (!pattern) return null;
35
- if (typeof pattern === "string") {
36
- try {
37
- return JSON.parse(pattern);
38
- } catch {
39
- return null;
40
- }
41
- }
42
- return pattern;
28
+ if (!pattern) return null;
29
+ if (typeof pattern === "string") try {
30
+ return JSON.parse(pattern);
31
+ } catch {
32
+ return null;
33
+ }
34
+ return pattern;
43
35
  }
44
36
  function calculateNextOccurrence(pattern, fromDate) {
45
- const { frequency, interval = 1, until, count } = pattern;
46
- if (until && fromDate >= until) return null;
47
- const nextDate = new Date(fromDate);
48
- switch (frequency) {
49
- case "daily":
50
- nextDate.setDate(nextDate.getDate() + interval);
51
- break;
52
- case "weekly":
53
- nextDate.setDate(nextDate.getDate() + interval * 7);
54
- break;
55
- case "monthly":
56
- nextDate.setMonth(nextDate.getMonth() + interval);
57
- break;
58
- case "yearly":
59
- nextDate.setFullYear(nextDate.getFullYear() + interval);
60
- break;
61
- default:
62
- return null;
63
- }
64
- if (until && nextDate > until) return null;
65
- return nextDate;
37
+ const { frequency, interval = 1, until, count } = pattern;
38
+ if (until && fromDate >= until) return null;
39
+ const nextDate = new Date(fromDate);
40
+ switch (frequency) {
41
+ case "daily":
42
+ nextDate.setDate(nextDate.getDate() + interval);
43
+ break;
44
+ case "weekly":
45
+ nextDate.setDate(nextDate.getDate() + interval * 7);
46
+ break;
47
+ case "monthly":
48
+ nextDate.setMonth(nextDate.getMonth() + interval);
49
+ break;
50
+ case "yearly":
51
+ nextDate.setFullYear(nextDate.getFullYear() + interval);
52
+ break;
53
+ default: return null;
54
+ }
55
+ if (until && nextDate > until) return null;
56
+ return nextDate;
66
57
  }
67
58
  function calculateDuration(startDate, endDate) {
68
- return endDate.getTime() - startDate.getTime();
59
+ return endDate.getTime() - startDate.getTime();
69
60
  }
70
61
  function formatDuration(durationMs) {
71
- const seconds = Math.floor(durationMs / 1e3);
72
- const minutes = Math.floor(seconds / 60);
73
- const hours = Math.floor(minutes / 60);
74
- const days = Math.floor(hours / 24);
75
- if (days > 0) {
76
- return `${days}d ${hours % 24}h`;
77
- }
78
- if (hours > 0) {
79
- return `${hours}h ${minutes % 60}m`;
80
- }
81
- if (minutes > 0) {
82
- return `${minutes}m ${seconds % 60}s`;
83
- }
84
- return `${seconds}s`;
62
+ const seconds = Math.floor(durationMs / 1e3);
63
+ const minutes = Math.floor(seconds / 60);
64
+ const hours = Math.floor(minutes / 60);
65
+ const days = Math.floor(hours / 24);
66
+ if (days > 0) return `${days}d ${hours % 24}h`;
67
+ if (hours > 0) return `${hours}h ${minutes % 60}m`;
68
+ if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
69
+ return `${seconds}s`;
85
70
  }
86
71
  function isEventNow(startDate, endDate) {
87
- const now = /* @__PURE__ */ new Date();
88
- if (now < startDate) return false;
89
- if (endDate && now > endDate) return false;
90
- return true;
72
+ const now = /* @__PURE__ */ new Date();
73
+ if (now < startDate) return false;
74
+ if (endDate && now > endDate) return false;
75
+ return true;
91
76
  }
92
77
  function getEventStatusFromDates(startDate, endDate, currentStatus) {
93
- if (currentStatus === "cancelled" || currentStatus === "postponed") {
94
- return currentStatus;
95
- }
96
- const now = /* @__PURE__ */ new Date();
97
- if (now < startDate) {
98
- return "scheduled";
99
- }
100
- if (endDate && now > endDate) {
101
- return "completed";
102
- }
103
- return "in_progress";
78
+ if (currentStatus === "cancelled" || currentStatus === "postponed") return currentStatus;
79
+ const now = /* @__PURE__ */ new Date();
80
+ if (now < startDate) return "scheduled";
81
+ if (endDate && now > endDate) return "completed";
82
+ return "in_progress";
104
83
  }
105
84
  function sortEventsByDate(events, ascending = true) {
106
- return events.sort((a, b) => {
107
- if (!a.startDate && !b.startDate) return 0;
108
- if (!a.startDate) return 1;
109
- if (!b.startDate) return -1;
110
- const diff = a.startDate.getTime() - b.startDate.getTime();
111
- return ascending ? diff : -diff;
112
- });
85
+ return events.sort((a, b) => {
86
+ if (!a.startDate && !b.startDate) return 0;
87
+ if (!a.startDate) return 1;
88
+ if (!b.startDate) return -1;
89
+ const diff = a.startDate.getTime() - b.startDate.getTime();
90
+ return ascending ? diff : -diff;
91
+ });
113
92
  }
114
- export {
115
- calculateDuration,
116
- calculateNextOccurrence,
117
- checkSchedulingConflict,
118
- formatDuration,
119
- formatEventDateRange,
120
- generateEventSlug,
121
- getEventStatusFromDates,
122
- isEventNow,
123
- parseRecurrencePattern,
124
- sortEventsByDate,
125
- validateEventStatus
126
- };
127
- //# sourceMappingURL=utils.js.map
93
+ //#endregion
94
+ export { calculateDuration, calculateNextOccurrence, checkSchedulingConflict, formatDuration, formatEventDateRange, generateEventSlug, getEventStatusFromDates, isEventNow, parseRecurrencePattern, sortEventsByDate, validateEventStatus };
95
+
96
+ //# sourceMappingURL=utils.js.map
package/dist/utils.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sources":["../src/utils.ts"],"sourcesContent":["/**\n * Utility functions for @have/events package\n */\n\nimport type { EventStatus, RecurrencePattern } from './types';\n\n/**\n * Validate event status transition\n *\n * @param currentStatus - Current event status\n * @param newStatus - Proposed new status\n * @returns True if transition is valid\n */\nexport function validateEventStatus(\n currentStatus: EventStatus,\n newStatus: EventStatus,\n): boolean {\n // Define valid transitions\n const validTransitions: Record<EventStatus, EventStatus[]> = {\n scheduled: ['in_progress', 'cancelled', 'postponed'],\n in_progress: ['completed', 'cancelled'],\n completed: [], // Cannot transition from completed\n cancelled: ['scheduled'], // Can reschedule\n postponed: ['scheduled', 'cancelled'],\n };\n\n return validTransitions[currentStatus]?.includes(newStatus) || false;\n}\n\n/**\n * Format event date range as string\n *\n * @param startDate - Event start date\n * @param endDate - Event end date (optional)\n * @returns Formatted date range string\n */\nexport function formatEventDateRange(\n startDate: Date,\n endDate?: Date | null,\n): string {\n const start = startDate.toLocaleDateString();\n\n if (!endDate) {\n return start;\n }\n\n // Same day\n if (startDate.toDateString() === endDate.toDateString()) {\n return `${start} ${startDate.toLocaleTimeString()} - ${endDate.toLocaleTimeString()}`;\n }\n\n // Multi-day\n return `${start} - ${endDate.toLocaleDateString()}`;\n}\n\n/**\n * Generate unique slug for an event\n *\n * @param name - Event name\n * @param date - Event date\n * @returns URL-friendly slug\n */\nexport function generateEventSlug(name: string, date: Date): string {\n const namePart = name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-|-$/g, '');\n\n const datePart = date.toISOString().split('T')[0]; // YYYY-MM-DD\n\n return `${namePart}-${datePart}`;\n}\n\n/**\n * Check if events have scheduling conflict\n *\n * @param event1Start - First event start\n * @param event1End - First event end\n * @param event2Start - Second event start\n * @param event2End - Second event end\n * @returns True if events overlap\n */\nexport function checkSchedulingConflict(\n event1Start: Date,\n event1End: Date | null,\n event2Start: Date,\n event2End: Date | null,\n): boolean {\n // If either event has no end date, assume it's instantaneous\n const e1End = event1End || event1Start;\n const e2End = event2End || event2Start;\n\n // Check for overlap\n return event1Start < e2End && e1End > event2Start;\n}\n\n/**\n * Parse recurrence pattern from various formats\n *\n * @param pattern - Recurrence pattern (object or string)\n * @returns Parsed RecurrencePattern or null\n */\nexport function parseRecurrencePattern(\n pattern: RecurrencePattern | string | null,\n): RecurrencePattern | null {\n if (!pattern) return null;\n\n if (typeof pattern === 'string') {\n try {\n return JSON.parse(pattern) as RecurrencePattern;\n } catch {\n return null;\n }\n }\n\n return pattern;\n}\n\n/**\n * Calculate next occurrence for a recurring event\n *\n * @param pattern - Recurrence pattern\n * @param fromDate - Date to calculate from\n * @returns Next occurrence date or null\n */\nexport function calculateNextOccurrence(\n pattern: RecurrencePattern,\n fromDate: Date,\n): Date | null {\n const { frequency, interval = 1, until, count } = pattern;\n\n // Check if recurrence has ended\n if (until && fromDate >= until) return null;\n\n // Calculate next date based on frequency\n const nextDate = new Date(fromDate);\n\n switch (frequency) {\n case 'daily':\n nextDate.setDate(nextDate.getDate() + interval);\n break;\n case 'weekly':\n nextDate.setDate(nextDate.getDate() + interval * 7);\n break;\n case 'monthly':\n nextDate.setMonth(nextDate.getMonth() + interval);\n break;\n case 'yearly':\n nextDate.setFullYear(nextDate.getFullYear() + interval);\n break;\n default:\n return null;\n }\n\n // Check if we've exceeded until date\n if (until && nextDate > until) return null;\n\n return nextDate;\n}\n\n/**\n * Calculate duration between two dates\n *\n * @param startDate - Start date\n * @param endDate - End date\n * @returns Duration in milliseconds\n */\nexport function calculateDuration(startDate: Date, endDate: Date): number {\n return endDate.getTime() - startDate.getTime();\n}\n\n/**\n * Format duration as human-readable string\n *\n * @param durationMs - Duration in milliseconds\n * @returns Formatted duration string\n */\nexport function formatDuration(durationMs: number): string {\n const seconds = Math.floor(durationMs / 1000);\n const minutes = Math.floor(seconds / 60);\n const hours = Math.floor(minutes / 60);\n const days = Math.floor(hours / 24);\n\n if (days > 0) {\n return `${days}d ${hours % 24}h`;\n }\n if (hours > 0) {\n return `${hours}h ${minutes % 60}m`;\n }\n if (minutes > 0) {\n return `${minutes}m ${seconds % 60}s`;\n }\n return `${seconds}s`;\n}\n\n/**\n * Check if an event is happening now\n *\n * @param startDate - Event start date\n * @param endDate - Event end date (optional)\n * @returns True if event is currently in progress\n */\nexport function isEventNow(startDate: Date, endDate?: Date | null): boolean {\n const now = new Date();\n\n if (now < startDate) return false;\n if (endDate && now > endDate) return false;\n\n return true;\n}\n\n/**\n * Get event status based on dates\n *\n * @param startDate - Event start date\n * @param endDate - Event end date (optional)\n * @param currentStatus - Current status (optional)\n * @returns Suggested event status\n */\nexport function getEventStatusFromDates(\n startDate: Date,\n endDate?: Date | null,\n currentStatus?: EventStatus,\n): EventStatus {\n // Don't override cancelled or postponed status\n if (currentStatus === 'cancelled' || currentStatus === 'postponed') {\n return currentStatus;\n }\n\n const now = new Date();\n\n if (now < startDate) {\n return 'scheduled';\n }\n\n if (endDate && now > endDate) {\n return 'completed';\n }\n\n return 'in_progress';\n}\n\n/**\n * Sort events by start date\n *\n * @param events - Array of objects with startDate property\n * @param ascending - Sort ascending (default: true)\n * @returns Sorted array\n */\nexport function sortEventsByDate<T extends { startDate: Date | null }>(\n events: T[],\n ascending: boolean = true,\n): T[] {\n return events.sort((a, b) => {\n if (!a.startDate && !b.startDate) return 0;\n if (!a.startDate) return 1;\n if (!b.startDate) return -1;\n\n const diff = a.startDate.getTime() - b.startDate.getTime();\n return ascending ? diff : -diff;\n });\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"utils.js","names":[],"sources":["../src/utils.ts"],"sourcesContent":["/**\n * Utility functions for @have/events package\n */\n\nimport type { EventStatus, RecurrencePattern } from './types';\n\n/**\n * Validate event status transition\n *\n * @param currentStatus - Current event status\n * @param newStatus - Proposed new status\n * @returns True if transition is valid\n */\nexport function validateEventStatus(\n currentStatus: EventStatus,\n newStatus: EventStatus,\n): boolean {\n // Define valid transitions\n const validTransitions: Record<EventStatus, EventStatus[]> = {\n scheduled: ['in_progress', 'cancelled', 'postponed'],\n in_progress: ['completed', 'cancelled'],\n completed: [], // Cannot transition from completed\n cancelled: ['scheduled'], // Can reschedule\n postponed: ['scheduled', 'cancelled'],\n };\n\n return validTransitions[currentStatus]?.includes(newStatus) || false;\n}\n\n/**\n * Format event date range as string\n *\n * @param startDate - Event start date\n * @param endDate - Event end date (optional)\n * @returns Formatted date range string\n */\nexport function formatEventDateRange(\n startDate: Date,\n endDate?: Date | null,\n): string {\n const start = startDate.toLocaleDateString();\n\n if (!endDate) {\n return start;\n }\n\n // Same day\n if (startDate.toDateString() === endDate.toDateString()) {\n return `${start} ${startDate.toLocaleTimeString()} - ${endDate.toLocaleTimeString()}`;\n }\n\n // Multi-day\n return `${start} - ${endDate.toLocaleDateString()}`;\n}\n\n/**\n * Generate unique slug for an event\n *\n * @param name - Event name\n * @param date - Event date\n * @returns URL-friendly slug\n */\nexport function generateEventSlug(name: string, date: Date): string {\n const namePart = name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-|-$/g, '');\n\n const datePart = date.toISOString().split('T')[0]; // YYYY-MM-DD\n\n return `${namePart}-${datePart}`;\n}\n\n/**\n * Check if events have scheduling conflict\n *\n * @param event1Start - First event start\n * @param event1End - First event end\n * @param event2Start - Second event start\n * @param event2End - Second event end\n * @returns True if events overlap\n */\nexport function checkSchedulingConflict(\n event1Start: Date,\n event1End: Date | null,\n event2Start: Date,\n event2End: Date | null,\n): boolean {\n // If either event has no end date, assume it's instantaneous\n const e1End = event1End || event1Start;\n const e2End = event2End || event2Start;\n\n // Check for overlap\n return event1Start < e2End && e1End > event2Start;\n}\n\n/**\n * Parse recurrence pattern from various formats\n *\n * @param pattern - Recurrence pattern (object or string)\n * @returns Parsed RecurrencePattern or null\n */\nexport function parseRecurrencePattern(\n pattern: RecurrencePattern | string | null,\n): RecurrencePattern | null {\n if (!pattern) return null;\n\n if (typeof pattern === 'string') {\n try {\n return JSON.parse(pattern) as RecurrencePattern;\n } catch {\n return null;\n }\n }\n\n return pattern;\n}\n\n/**\n * Calculate next occurrence for a recurring event\n *\n * @param pattern - Recurrence pattern\n * @param fromDate - Date to calculate from\n * @returns Next occurrence date or null\n */\nexport function calculateNextOccurrence(\n pattern: RecurrencePattern,\n fromDate: Date,\n): Date | null {\n const { frequency, interval = 1, until, count } = pattern;\n\n // Check if recurrence has ended\n if (until && fromDate >= until) return null;\n\n // Calculate next date based on frequency\n const nextDate = new Date(fromDate);\n\n switch (frequency) {\n case 'daily':\n nextDate.setDate(nextDate.getDate() + interval);\n break;\n case 'weekly':\n nextDate.setDate(nextDate.getDate() + interval * 7);\n break;\n case 'monthly':\n nextDate.setMonth(nextDate.getMonth() + interval);\n break;\n case 'yearly':\n nextDate.setFullYear(nextDate.getFullYear() + interval);\n break;\n default:\n return null;\n }\n\n // Check if we've exceeded until date\n if (until && nextDate > until) return null;\n\n return nextDate;\n}\n\n/**\n * Calculate duration between two dates\n *\n * @param startDate - Start date\n * @param endDate - End date\n * @returns Duration in milliseconds\n */\nexport function calculateDuration(startDate: Date, endDate: Date): number {\n return endDate.getTime() - startDate.getTime();\n}\n\n/**\n * Format duration as human-readable string\n *\n * @param durationMs - Duration in milliseconds\n * @returns Formatted duration string\n */\nexport function formatDuration(durationMs: number): string {\n const seconds = Math.floor(durationMs / 1000);\n const minutes = Math.floor(seconds / 60);\n const hours = Math.floor(minutes / 60);\n const days = Math.floor(hours / 24);\n\n if (days > 0) {\n return `${days}d ${hours % 24}h`;\n }\n if (hours > 0) {\n return `${hours}h ${minutes % 60}m`;\n }\n if (minutes > 0) {\n return `${minutes}m ${seconds % 60}s`;\n }\n return `${seconds}s`;\n}\n\n/**\n * Check if an event is happening now\n *\n * @param startDate - Event start date\n * @param endDate - Event end date (optional)\n * @returns True if event is currently in progress\n */\nexport function isEventNow(startDate: Date, endDate?: Date | null): boolean {\n const now = new Date();\n\n if (now < startDate) return false;\n if (endDate && now > endDate) return false;\n\n return true;\n}\n\n/**\n * Get event status based on dates\n *\n * @param startDate - Event start date\n * @param endDate - Event end date (optional)\n * @param currentStatus - Current status (optional)\n * @returns Suggested event status\n */\nexport function getEventStatusFromDates(\n startDate: Date,\n endDate?: Date | null,\n currentStatus?: EventStatus,\n): EventStatus {\n // Don't override cancelled or postponed status\n if (currentStatus === 'cancelled' || currentStatus === 'postponed') {\n return currentStatus;\n }\n\n const now = new Date();\n\n if (now < startDate) {\n return 'scheduled';\n }\n\n if (endDate && now > endDate) {\n return 'completed';\n }\n\n return 'in_progress';\n}\n\n/**\n * Sort events by start date\n *\n * @param events - Array of objects with startDate property\n * @param ascending - Sort ascending (default: true)\n * @returns Sorted array\n */\nexport function sortEventsByDate<T extends { startDate: Date | null }>(\n events: T[],\n ascending: boolean = true,\n): T[] {\n return events.sort((a, b) => {\n if (!a.startDate && !b.startDate) return 0;\n if (!a.startDate) return 1;\n if (!b.startDate) return -1;\n\n const diff = a.startDate.getTime() - b.startDate.getTime();\n return ascending ? diff : -diff;\n });\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/smrt-events",
3
- "version": "0.37.2",
3
+ "version": "0.37.3",
4
4
  "description": "Hierarchical event management with participant tracking and SMRT framework support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -36,7 +36,7 @@
36
36
  }
37
37
  },
38
38
  "peerDependencies": {
39
- "svelte": "^5.0.0"
39
+ "svelte": "^5.56.4"
40
40
  },
41
41
  "peerDependenciesMeta": {
42
42
  "svelte": {
@@ -44,30 +44,30 @@
44
44
  }
45
45
  },
46
46
  "dependencies": {
47
- "@happyvertical/ai": "^0.74.10",
48
- "@happyvertical/files": "^0.74.10",
49
- "@happyvertical/logger": "^0.74.10",
50
- "@happyvertical/sql": "^0.74.10",
51
- "@happyvertical/utils": "^0.74.10",
52
- "@happyvertical/smrt-assets": "0.37.2",
53
- "@happyvertical/smrt-core": "0.37.2",
54
- "@happyvertical/smrt-profiles": "0.37.2",
55
- "@happyvertical/smrt-places": "0.37.2",
56
- "@happyvertical/smrt-tenancy": "0.37.2",
57
- "@happyvertical/smrt-types": "0.37.2",
58
- "@happyvertical/smrt-ui": "0.37.2"
47
+ "@happyvertical/ai": "^0.74.11",
48
+ "@happyvertical/files": "^0.74.11",
49
+ "@happyvertical/logger": "^0.74.11",
50
+ "@happyvertical/sql": "^0.74.11",
51
+ "@happyvertical/utils": "^0.74.11",
52
+ "@happyvertical/smrt-assets": "0.37.3",
53
+ "@happyvertical/smrt-core": "0.37.3",
54
+ "@happyvertical/smrt-places": "0.37.3",
55
+ "@happyvertical/smrt-tenancy": "0.37.3",
56
+ "@happyvertical/smrt-profiles": "0.37.3",
57
+ "@happyvertical/smrt-types": "0.37.3",
58
+ "@happyvertical/smrt-ui": "0.37.3"
59
59
  },
60
60
  "devDependencies": {
61
- "@sveltejs/package": "^2.5.7",
62
- "@sveltejs/vite-plugin-svelte": "^6.2.4",
63
- "@types/node": "25.0.9",
61
+ "@sveltejs/package": "^2.5.8",
62
+ "@sveltejs/vite-plugin-svelte": "^7.1.2",
63
+ "@types/node": "24.13.2",
64
64
  "fast-glob": "^3.3.3",
65
- "svelte": "^5.46.4",
66
- "svelte-check": "^4.3.5",
65
+ "svelte": "^5.56.4",
66
+ "svelte-check": "^4.7.1",
67
67
  "typescript": "^5.9.3",
68
- "vite": "^7.3.6",
69
- "vitest": "^4.0.17",
70
- "@happyvertical/smrt-vitest": "0.37.2"
68
+ "vite": "^8.1.2",
69
+ "vitest": "^4.1.9",
70
+ "@happyvertical/smrt-vitest": "0.37.3"
71
71
  },
72
72
  "keywords": [
73
73
  "events",
package/dist/types.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}