@cloff/sdk 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.
package/src/widget.ts ADDED
@@ -0,0 +1,664 @@
1
+ // Default database connection credentials
2
+ const DEFAULT_SUPABASE_URL = 'https://edhkrzyiiznotsmblbrt.supabase.co';
3
+ const DEFAULT_SUPABASE_ANON_KEY = 'sb_publishable_EzjTe8cJu-8gZmdccQr4aA_iZY8WIxL';
4
+
5
+ export class ClofWidget extends HTMLElement {
6
+ private shadow: ShadowRoot;
7
+ private apiKey: string | null = null;
8
+ private days: number = 30;
9
+ private theme: 'light' | 'dark' = 'dark';
10
+ private accentColor: string = '#8b5cf6'; // Indigo/violet default
11
+
12
+ constructor() {
13
+ super();
14
+ this.shadow = this.attachShadow({ mode: 'open' });
15
+ }
16
+
17
+ static get observedAttributes() {
18
+ return ['api-key', 'days', 'theme', 'accent-color'];
19
+ }
20
+
21
+ attributeChangedCallback(name: string, oldValue: string, newValue: string) {
22
+ if (oldValue === newValue) return;
23
+
24
+ if (name === 'api-key') this.apiKey = newValue;
25
+ if (name === 'days') this.days = parseInt(newValue, 10) || 30;
26
+ if (name === 'theme') this.theme = newValue === 'light' ? 'light' : 'dark';
27
+ if (name === 'accent-color') this.accentColor = newValue || '#8b5cf6';
28
+
29
+ if (this.isConnected && this.apiKey) {
30
+ this.render();
31
+ }
32
+ }
33
+
34
+ connectedCallback() {
35
+ this.apiKey = this.getAttribute('api-key');
36
+ this.days = parseInt(this.getAttribute('days') || '30', 10);
37
+ this.accentColor = this.getAttribute('accent-color') || '#8b5cf6';
38
+
39
+ const themeAttr = this.getAttribute('theme');
40
+ if (themeAttr === 'light' || themeAttr === 'dark') {
41
+ this.theme = themeAttr;
42
+ } else {
43
+ // Auto-detect dark mode from page classes or system preferences
44
+ const hasDarkClass = document.documentElement.classList.contains('dark') || document.body.classList.contains('dark');
45
+ const hasLightClass = document.documentElement.classList.contains('light') || document.body.classList.contains('light');
46
+
47
+ if (hasDarkClass) {
48
+ this.theme = 'dark';
49
+ } else if (hasLightClass) {
50
+ this.theme = 'light';
51
+ } else {
52
+ this.theme = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
53
+ }
54
+ }
55
+
56
+ this.render();
57
+ }
58
+
59
+ private async fetchMetrics(): Promise<any> {
60
+ if (!this.apiKey) {
61
+ throw new Error('API Key is missing');
62
+ }
63
+
64
+ // Intercept Demo/Mock keys for local sandboxes or developer previews
65
+ if (this.apiKey.startsWith('socio_dau_live_demo_key_')) {
66
+ const seed = this.apiKey.includes('socionetwork') ? 1.5 : 0.8;
67
+ const metrics = [];
68
+ const today = new Date();
69
+
70
+ for (let i = this.days - 1; i >= 0; i--) {
71
+ const date = new Date(today);
72
+ date.setDate(today.getDate() - i);
73
+ const dateStr = date.toISOString().split('T')[0];
74
+
75
+ const base = 50 + (this.days - 1 - i) * 8 * seed;
76
+ const dayOfWeek = date.getDay();
77
+ const weekendDip = (dayOfWeek === 0 || dayOfWeek === 6) ? 0.7 : 1.0;
78
+ const randomNoise = 0.9 + Math.random() * 0.2;
79
+
80
+ metrics.push({
81
+ activity_date: dateStr,
82
+ active_users: Math.round(base * weekendDip * randomNoise)
83
+ });
84
+ }
85
+
86
+ const devices = [
87
+ { device_type: 'Mobile', count: Math.round(1482 * seed) },
88
+ { device_type: 'Desktop', count: Math.round(853 * seed) },
89
+ { device_type: 'Tablet', count: Math.round(124 * seed) }
90
+ ];
91
+
92
+ const locations = [
93
+ { location: 'America/New_York', count: Math.round(980 * seed) },
94
+ { location: 'Europe/London', count: Math.round(642 * seed) },
95
+ { location: 'Asia/Tokyo', count: Math.round(412 * seed) },
96
+ { location: 'Europe/Paris', count: Math.round(280 * seed) },
97
+ { location: 'Australia/Sydney', count: Math.round(144 * seed) }
98
+ ];
99
+
100
+ // Simulate a small network delay
101
+ await new Promise(resolve => setTimeout(resolve, 600));
102
+
103
+ return {
104
+ ok: true,
105
+ metrics,
106
+ devices,
107
+ locations
108
+ };
109
+ }
110
+
111
+ const response = await fetch(`${DEFAULT_SUPABASE_URL}/rest/v1/rpc/get_public_dau_metrics`, {
112
+ method: 'POST',
113
+ headers: {
114
+ 'apikey': DEFAULT_SUPABASE_ANON_KEY,
115
+ 'Authorization': `Bearer ${DEFAULT_SUPABASE_ANON_KEY}`,
116
+ 'Content-Type': 'application/json',
117
+ },
118
+ body: JSON.stringify({
119
+ p_api_key: this.apiKey,
120
+ p_days: this.days
121
+ })
122
+ });
123
+
124
+ if (!response.ok) {
125
+ const errText = await response.text();
126
+ throw new Error(errText || 'Failed to fetch public metrics');
127
+ }
128
+
129
+ const data = await response.json();
130
+ if (data && data.ok === false) {
131
+ throw new Error(data.error || 'Server rejected metrics request');
132
+ }
133
+
134
+ return data;
135
+ }
136
+
137
+ private getStyles(): string {
138
+ const isDark = this.theme === 'dark';
139
+ const bg = isDark ? '#0f172a' : '#ffffff';
140
+ const text = isDark ? '#f8fafc' : '#0f172a';
141
+ const border = isDark ? '#1e293b' : '#e2e8f0';
142
+ const subtext = isDark ? '#94a3b8' : '#64748b';
143
+ const cardBg = isDark ? '#1e293b' : '#f8fafc';
144
+ const gridLine = isDark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.04)';
145
+
146
+ return `
147
+ :host {
148
+ display: block;
149
+ width: 100%;
150
+ max-width: 600px;
151
+ font-family: 'Outfit', 'Inter', system-ui, -apple-system, sans-serif;
152
+ box-sizing: border-box;
153
+ }
154
+ .widget-container {
155
+ background: ${bg};
156
+ color: ${text};
157
+ border: 1px solid ${border};
158
+ border-radius: 16px;
159
+ padding: 20px;
160
+ box-shadow: 0 4px 20px -2px rgba(0, 0, 0, 0.08);
161
+ display: flex;
162
+ flex-direction: column;
163
+ gap: 20px;
164
+ overflow: hidden;
165
+ transition: all 0.3s ease;
166
+ }
167
+ .header {
168
+ display: flex;
169
+ justify-content: space-between;
170
+ align-items: flex-start;
171
+ border-bottom: 1px solid ${border};
172
+ padding-bottom: 15px;
173
+ }
174
+ .title-section {
175
+ display: flex;
176
+ flex-direction: column;
177
+ gap: 4px;
178
+ }
179
+ .title {
180
+ font-size: 11px;
181
+ text-transform: uppercase;
182
+ font-weight: 700;
183
+ letter-spacing: 1.5px;
184
+ color: ${subtext};
185
+ }
186
+ .dau-count-container {
187
+ display: flex;
188
+ align-items: baseline;
189
+ gap: 8px;
190
+ }
191
+ .dau-count {
192
+ font-size: 32px;
193
+ font-weight: 800;
194
+ letter-spacing: -1px;
195
+ background: linear-gradient(to right, ${this.accentColor}, #6366f1);
196
+ -webkit-background-clip: text;
197
+ -webkit-text-fill-color: transparent;
198
+ }
199
+ .dau-label {
200
+ font-size: 12px;
201
+ color: ${subtext};
202
+ font-weight: 600;
203
+ }
204
+ .badge {
205
+ display: flex;
206
+ align-items: center;
207
+ gap: 4px;
208
+ background: ${cardBg};
209
+ border: 1px solid ${border};
210
+ padding: 6px 12px;
211
+ border-radius: 10px;
212
+ font-size: 11px;
213
+ font-weight: 700;
214
+ color: ${this.accentColor};
215
+ }
216
+ .badge-dot {
217
+ width: 6px;
218
+ height: 6px;
219
+ background: ${this.accentColor};
220
+ border-radius: 50%;
221
+ animation: pulse 2s infinite;
222
+ }
223
+ .chart-container {
224
+ position: relative;
225
+ width: 100%;
226
+ height: 180px;
227
+ }
228
+ svg {
229
+ width: 100%;
230
+ height: 100%;
231
+ overflow: visible;
232
+ }
233
+ .grid-line {
234
+ stroke: ${gridLine};
235
+ stroke-dasharray: 4;
236
+ }
237
+ .chart-line {
238
+ stroke: ${this.accentColor};
239
+ stroke-width: 3;
240
+ stroke-linecap: round;
241
+ stroke-linejoin: round;
242
+ fill: none;
243
+ }
244
+ .chart-area {
245
+ fill: url(#areaGradient);
246
+ }
247
+ .chart-dot {
248
+ fill: ${this.accentColor};
249
+ stroke: ${bg};
250
+ stroke-width: 2;
251
+ }
252
+ .stats-grid {
253
+ display: grid;
254
+ grid-template-columns: 1fr 1fr;
255
+ gap: 16px;
256
+ }
257
+ @media (max-width: 480px) {
258
+ .stats-grid {
259
+ grid-template-columns: 1fr;
260
+ }
261
+ }
262
+ .card {
263
+ background: ${cardBg};
264
+ border: 1px solid ${border};
265
+ border-radius: 12px;
266
+ padding: 14px;
267
+ display: flex;
268
+ flex-direction: column;
269
+ gap: 12px;
270
+ }
271
+ .card-title {
272
+ font-size: 12px;
273
+ font-weight: 700;
274
+ color: ${subtext};
275
+ text-transform: uppercase;
276
+ letter-spacing: 0.5px;
277
+ }
278
+ .device-bar {
279
+ display: flex;
280
+ height: 8px;
281
+ border-radius: 4px;
282
+ overflow: hidden;
283
+ background: ${border};
284
+ }
285
+ .device-segment {
286
+ height: 100%;
287
+ transition: width 0.3s;
288
+ }
289
+ .legend-list {
290
+ display: flex;
291
+ flex-direction: column;
292
+ gap: 6px;
293
+ }
294
+ .legend-item {
295
+ display: flex;
296
+ justify-content: space-between;
297
+ font-size: 11px;
298
+ font-weight: 600;
299
+ }
300
+ .legend-label {
301
+ display: flex;
302
+ align-items: center;
303
+ gap: 6px;
304
+ color: ${subtext};
305
+ }
306
+ .legend-dot {
307
+ width: 8px;
308
+ height: 8px;
309
+ border-radius: 50%;
310
+ }
311
+ .location-list {
312
+ display: flex;
313
+ flex-direction: column;
314
+ gap: 8px;
315
+ }
316
+ .location-row {
317
+ display: flex;
318
+ flex-direction: column;
319
+ gap: 4px;
320
+ }
321
+ .location-meta {
322
+ display: flex;
323
+ justify-content: space-between;
324
+ font-size: 11px;
325
+ font-weight: 600;
326
+ }
327
+ .location-name {
328
+ color: ${text};
329
+ white-space: nowrap;
330
+ overflow: hidden;
331
+ text-overflow: ellipsis;
332
+ max-width: 140px;
333
+ }
334
+ .location-bar-bg {
335
+ height: 4px;
336
+ background: ${border};
337
+ border-radius: 2px;
338
+ overflow: hidden;
339
+ }
340
+ .location-bar-fill {
341
+ height: 100%;
342
+ background: ${this.accentColor};
343
+ border-radius: 2px;
344
+ }
345
+ .footer {
346
+ display: flex;
347
+ justify-content: space-between;
348
+ font-size: 10px;
349
+ color: ${subtext};
350
+ border-top: 1px solid ${border};
351
+ padding-top: 12px;
352
+ margin-top: 5px;
353
+ }
354
+ .footer a {
355
+ color: ${this.accentColor};
356
+ text-decoration: none;
357
+ font-weight: 700;
358
+ }
359
+ .footer-badge {
360
+ display: flex;
361
+ align-items: center;
362
+ gap: 4px;
363
+ font-weight: 600;
364
+ }
365
+ .check-icon {
366
+ color: #10b981;
367
+ }
368
+ .loader-container {
369
+ display: flex;
370
+ flex-direction: column;
371
+ align-items: center;
372
+ justify-content: center;
373
+ height: 300px;
374
+ gap: 12px;
375
+ }
376
+ .spinner {
377
+ width: 32px;
378
+ height: 32px;
379
+ border: 3px solid ${border};
380
+ border-top-color: ${this.accentColor};
381
+ border-radius: 50%;
382
+ animation: spin 1s linear infinite;
383
+ }
384
+ .loader-text {
385
+ font-size: 13px;
386
+ color: ${subtext};
387
+ font-weight: 600;
388
+ }
389
+ .error-container {
390
+ display: flex;
391
+ flex-direction: column;
392
+ align-items: center;
393
+ justify-content: center;
394
+ height: 250px;
395
+ color: #ef4444;
396
+ text-align: center;
397
+ padding: 20px;
398
+ gap: 8px;
399
+ }
400
+ .error-title {
401
+ font-weight: 700;
402
+ font-size: 16px;
403
+ }
404
+ .error-desc {
405
+ font-size: 12px;
406
+ color: ${subtext};
407
+ }
408
+ @keyframes pulse {
409
+ 0%, 100% { opacity: 1; transform: scale(1); }
410
+ 50% { opacity: 0.4; transform: scale(1.3); }
411
+ }
412
+ @keyframes spin {
413
+ to { transform: rotate(360deg); }
414
+ }
415
+ `;
416
+ }
417
+
418
+ private renderError(message: string) {
419
+ const isDark = this.theme === 'dark';
420
+ const bg = isDark ? '#0f172a' : '#ffffff';
421
+ const border = isDark ? '#1e293b' : '#e2e8f0';
422
+ const subtext = isDark ? '#94a3b8' : '#64748b';
423
+
424
+ this.shadow.innerHTML = `
425
+ <style>
426
+ ${this.getStyles()}
427
+ </style>
428
+ <div class="widget-container" style="background: ${bg}; border-color: ${border};">
429
+ <div class="error-container">
430
+ <div class="error-title">Unable to Load Metrics</div>
431
+ <div class="error-desc">${message}</div>
432
+ </div>
433
+ </div>
434
+ `;
435
+ }
436
+
437
+ private renderLoading() {
438
+ const isDark = this.theme === 'dark';
439
+ const bg = isDark ? '#0f172a' : '#ffffff';
440
+ const border = isDark ? '#1e293b' : '#e2e8f0';
441
+
442
+ this.shadow.innerHTML = `
443
+ <style>
444
+ ${this.getStyles()}
445
+ </style>
446
+ <div class="widget-container" style="background: ${bg}; border-color: ${border};">
447
+ <div class="loader-container">
448
+ <div class="spinner"></div>
449
+ <div class="loader-text">Fetching verified metrics...</div>
450
+ </div>
451
+ </div>
452
+ `;
453
+ }
454
+
455
+ private renderWidget(data: any) {
456
+ const metrics: any[] = data.metrics || [];
457
+ const devices: any[] = data.devices || [];
458
+ const locations: any[] = data.locations || [];
459
+
460
+ // Chronological order for chart
461
+ const sortedMetrics = [...metrics].sort(
462
+ (a, b) => new Date(a.activity_date).getTime() - new Date(b.activity_date).getTime()
463
+ );
464
+
465
+ const latestDau = sortedMetrics.length > 0 ? sortedMetrics[sortedMetrics.length - 1].active_users : 0;
466
+
467
+ // SVG coordinates setup
468
+ const width = 500;
469
+ const height = 150;
470
+ const paddingLeft = 30;
471
+ const paddingRight = 10;
472
+ const paddingTop = 15;
473
+ const paddingBottom = 15;
474
+
475
+ const chartWidth = width - paddingLeft - paddingRight;
476
+ const chartHeight = height - paddingTop - paddingBottom;
477
+
478
+ const maxVal = Math.max(...sortedMetrics.map(m => m.active_users), 0);
479
+ const scaleMax = maxVal === 0 ? 10 : Math.ceil(maxVal * 1.25);
480
+
481
+ const coordPoints = sortedMetrics.map((p, i) => {
482
+ const x = paddingLeft + (i / Math.max(sortedMetrics.length - 1, 1)) * chartWidth;
483
+ const y = paddingTop + chartHeight - (p.active_users / scaleMax) * chartHeight;
484
+ return { x, y };
485
+ });
486
+
487
+ // Generate path strings
488
+ let linePath = '';
489
+ let areaPath = '';
490
+ if (coordPoints.length > 0) {
491
+ linePath = coordPoints.map((p, i) => `${i === 0 ? 'M' : 'L'} ${p.x} ${p.y}`).join(' ');
492
+ const lastX = coordPoints[coordPoints.length - 1].x;
493
+ const firstX = coordPoints[0].x;
494
+ const bottomY = paddingTop + chartHeight;
495
+ areaPath = `${linePath} L ${lastX} ${bottomY} L ${firstX} ${bottomY} Z`;
496
+ }
497
+
498
+ // Grid lines calculation
499
+ const gridLinesY = [0.25, 0.5, 0.75, 1.0].map(pct => {
500
+ return paddingTop + chartHeight - pct * chartHeight;
501
+ });
502
+
503
+ // Devices Calculations
504
+ const totalDevices = devices.reduce((sum, item) => sum + Number(item.count), 0);
505
+ const deviceColors = ['#8b5cf6', '#6366f1', '#10b981', '#f59e0b'];
506
+
507
+ // Locations Calculations
508
+ const totalLocations = locations.reduce((sum, item) => sum + Number(item.count), 0);
509
+
510
+ const isDark = this.theme === 'dark';
511
+ const subtext = isDark ? '#94a3b8' : '#64748b';
512
+
513
+ this.shadow.innerHTML = `
514
+ <style>
515
+ ${this.getStyles()}
516
+ </style>
517
+ <div class="widget-container">
518
+ <div class="header">
519
+ <div class="title-section">
520
+ <div class="title">Active Users History</div>
521
+ <div class="dau-count-container">
522
+ <span class="dau-count">${latestDau.toLocaleString()}</span>
523
+ <span class="dau-label">DAU today</span>
524
+ </div>
525
+ </div>
526
+ <div class="badge">
527
+ <div class="badge-dot"></div>
528
+ <span>Verified Metrics</span>
529
+ </div>
530
+ </div>
531
+
532
+ <!-- SVG Interactive Area Chart -->
533
+ <div class="chart-container">
534
+ <svg viewBox="0 0 ${width} ${height}" preserveAspectRatio="none">
535
+ <defs>
536
+ <linearGradient id="areaGradient" x1="0" y1="0" x2="0" y2="1">
537
+ <stop offset="0%" stop-color="${this.accentColor}" stop-opacity="0.3"/>
538
+ <stop offset="100%" stop-color="${this.accentColor}" stop-opacity="0.0"/>
539
+ </linearGradient>
540
+ </defs>
541
+
542
+ <!-- Grid Lines -->
543
+ ${gridLinesY.map((y) => `
544
+ <line class="grid-line" x1="${paddingLeft}" y1="${y}" x2="${width - paddingRight}" y2="${y}" />
545
+ `).join('')}
546
+
547
+ <!-- Y Axis bounds text -->
548
+ <text x="${paddingLeft - 5}" y="${paddingTop + 4}" text-anchor="end" font-size="8" fill="${subtext}" font-weight="600">${Math.round(scaleMax)}</text>
549
+ <text x="${paddingLeft - 5}" y="${paddingTop + chartHeight + 4}" text-anchor="end" font-size="8" fill="${subtext}" font-weight="600">0</text>
550
+
551
+ ${coordPoints.length > 0 ? `
552
+ <!-- Area Path -->
553
+ <path class="chart-area" d="${areaPath}" />
554
+ <!-- Line Path -->
555
+ <path class="chart-line" d="${linePath}" />
556
+ <!-- Endpoint Dot -->
557
+ <circle class="chart-dot" cx="${coordPoints[coordPoints.length - 1].x}" cy="${coordPoints[coordPoints.length - 1].y}" r="5" />
558
+ ` : ''}
559
+ </svg>
560
+ </div>
561
+
562
+ <!-- Breakdown Grid -->
563
+ <div class="stats-grid">
564
+
565
+ <!-- Devices Breakdown -->
566
+ <div class="card">
567
+ <div class="card-title">Environments</div>
568
+ ${totalDevices === 0 ? `
569
+ <div style="font-size: 11px; color: ${subtext}; text-align: center; margin: auto;">No device data</div>
570
+ ` : `
571
+ <div class="device-bar">
572
+ ${devices.map((dev, idx) => {
573
+ const pct = (dev.count / totalDevices) * 100;
574
+ return `<div class="device-segment" style="width: ${pct}%; background: ${deviceColors[idx % deviceColors.length]}"></div>`;
575
+ }).join('')}
576
+ </div>
577
+ <div class="legend-list">
578
+ ${devices.map((dev, idx) => {
579
+ const pct = Math.round((dev.count / totalDevices) * 100);
580
+ return `
581
+ <div class="legend-item">
582
+ <div class="legend-label">
583
+ <div class="legend-dot" style="background: ${deviceColors[idx % deviceColors.length]}"></div>
584
+ <span>${dev.device_type}</span>
585
+ </div>
586
+ <div>${pct}%</div>
587
+ </div>
588
+ `;
589
+ }).join('')}
590
+ </div>
591
+ `}
592
+ </div>
593
+
594
+ <!-- Locations Breakdown -->
595
+ <div class="card">
596
+ <div class="card-title">Top Locations</div>
597
+ ${totalLocations === 0 ? `
598
+ <div style="font-size: 11px; color: ${subtext}; text-align: center; margin: auto;">No location data</div>
599
+ ` : `
600
+ <div class="location-list">
601
+ ${locations.slice(0, 3).map((loc) => {
602
+ const pct = Math.round((loc.count / totalLocations) * 100);
603
+ const locVal = loc.location || loc.country || 'Unknown';
604
+ const shortName = locVal.split('/').pop()?.replace('_', ' ') || locVal;
605
+ return `
606
+ <div class="location-row">
607
+ <div class="location-meta">
608
+ <span class="location-name">${shortName}</span>
609
+ <span>${pct}%</span>
610
+ </div>
611
+ <div class="location-bar-bg">
612
+ <div class="location-bar-fill" style="width: ${pct}%; background: ${this.accentColor}"></div>
613
+ </div>
614
+ </div>
615
+ `;
616
+ }).join('')}
617
+ </div>
618
+ `}
619
+ </div>
620
+
621
+ </div>
622
+
623
+ <!-- Verified Footer -->
624
+ <div class="footer">
625
+ <div class="footer-badge">
626
+ <span class="check-icon">✓</span> Verified by <strong>Analytics</strong>
627
+ </div>
628
+ <div>
629
+ Powered by <a href="https://socio.kim" target="_blank" rel="noopener">Socio.kim</a>
630
+ </div>
631
+ </div>
632
+ </div>
633
+ `;
634
+ }
635
+
636
+ private async render() {
637
+ if (!this.apiKey) {
638
+ this.renderError('API key attribute "api-key" is required.');
639
+ return;
640
+ }
641
+
642
+ this.renderLoading();
643
+
644
+ try {
645
+ const data = await this.fetchMetrics();
646
+ this.renderWidget(data);
647
+ } catch (error: any) {
648
+ console.error('ClofWidget error:', error);
649
+ this.renderError(error.message || 'Network error fetching data.');
650
+ }
651
+ }
652
+ }
653
+
654
+ // Register the custom elements globally if in browser environment
655
+ if (typeof window !== 'undefined') {
656
+ if (!window.customElements.get('clof-widget')) {
657
+ window.customElements.define('clof-widget', ClofWidget);
658
+ }
659
+ if (!window.customElements.get('socio-dau-widget')) {
660
+ window.customElements.define('socio-dau-widget', ClofWidget);
661
+ }
662
+ }
663
+
664
+ export { ClofWidget as SocioDauWidget };
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2020",
4
+ "module": "esnext",
5
+ "lib": ["es2020", "dom"],
6
+ "declaration": true,
7
+ "outDir": "./dist",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "moduleResolution": "node"
13
+ },
14
+ "include": ["src/**/*"]
15
+ }