@servlyadmin/runtime-core 0.1.2 → 0.1.4

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.cjs CHANGED
@@ -211,8 +211,13 @@ var init_registry = __esm({
211
211
  // src/index.ts
212
212
  var index_exports = {};
213
213
  __export(index_exports, {
214
+ AnalyticsCollector: () => AnalyticsCollector,
214
215
  DEFAULT_CACHE_CONFIG: () => DEFAULT_CACHE_CONFIG,
215
216
  DEFAULT_RETRY_CONFIG: () => DEFAULT_RETRY_CONFIG,
217
+ LongTaskObserver: () => LongTaskObserver,
218
+ MemorySampler: () => MemorySampler,
219
+ SessionManager: () => SessionManager,
220
+ analytics: () => analytics,
216
221
  applyStyles: () => applyStyles,
217
222
  batchFetchComponents: () => batchFetchComponents,
218
223
  buildClassName: () => buildClassName,
@@ -226,6 +231,7 @@ __export(index_exports, {
226
231
  clearStyles: () => clearStyles,
227
232
  collectAllDependencies: () => collectAllDependencies,
228
233
  compareVersions: () => compareVersions,
234
+ configureAnalytics: () => configureAnalytics,
229
235
  createRegistry: () => createRegistry,
230
236
  detectCircularDependencies: () => detectCircularDependencies,
231
237
  extractBindingKeys: () => extractBindingKeys,
@@ -236,11 +242,15 @@ __export(index_exports, {
236
242
  formatStyleValue: () => formatStyleValue,
237
243
  formatVersion: () => formatVersion,
238
244
  generateTestCases: () => generateTestCases,
245
+ getAnalytics: () => getAnalytics,
239
246
  getCacheKey: () => getCacheKey,
240
247
  getDependencyTree: () => getDependencyTree,
241
248
  getFromCache: () => getFromCache,
249
+ getLongTaskObserver: () => getLongTaskObserver,
242
250
  getMemoryCacheSize: () => getMemoryCacheSize,
251
+ getMemorySampler: () => getMemorySampler,
243
252
  getRegistryUrl: () => getRegistryUrl,
253
+ getSessionManager: () => getSessionManager,
244
254
  hasTemplateSyntax: () => hasTemplateSyntax,
245
255
  invalidateCache: () => invalidateCache,
246
256
  isComponentAvailable: () => isComponentAvailable,
@@ -250,6 +260,10 @@ __export(index_exports, {
250
260
  processStyles: () => processStyles,
251
261
  render: () => render,
252
262
  renderDynamicList: () => renderDynamicList,
263
+ resetAnalytics: () => resetAnalytics,
264
+ resetLongTaskObserver: () => resetLongTaskObserver,
265
+ resetMemorySampler: () => resetMemorySampler,
266
+ resetSessionManager: () => resetSessionManager,
253
267
  resolveBindingPath: () => resolveBindingPath,
254
268
  resolveTemplate: () => resolveTemplate,
255
269
  resolveTemplateValue: () => resolveTemplateValue,
@@ -266,6 +280,516 @@ __export(index_exports, {
266
280
  });
267
281
  module.exports = __toCommonJS(index_exports);
268
282
 
283
+ // src/analyticsTypes.ts
284
+ var DEFAULT_ANALYTICS_CONFIG = {
285
+ enabled: true,
286
+ endpoint: "/api/v1/analytics/events",
287
+ batchSize: 50,
288
+ flushInterval: 3e4,
289
+ // 30 seconds
290
+ sampleRate: 1,
291
+ environment: "production",
292
+ debug: false
293
+ };
294
+ var MAX_ERROR_MESSAGE_LENGTH = 1e3;
295
+ var MAX_STACK_TRACE_LENGTH = 500;
296
+ var MAX_QUEUE_SIZE = 500;
297
+ var MAX_RETRY_ATTEMPTS = 3;
298
+ var SESSION_TIMEOUT_MS = 30 * 60 * 1e3;
299
+ var SDK_VERSION = "1.0.0";
300
+
301
+ // src/sessionManager.ts
302
+ var SESSION_STORAGE_KEY = "servly_analytics_session";
303
+ function generateUUID() {
304
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
305
+ return crypto.randomUUID();
306
+ }
307
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
308
+ const r = Math.random() * 16 | 0;
309
+ const v = c === "x" ? r : r & 3 | 8;
310
+ return v.toString(16);
311
+ });
312
+ }
313
+ function isSessionStorageAvailable() {
314
+ try {
315
+ if (typeof sessionStorage === "undefined") {
316
+ return false;
317
+ }
318
+ const testKey = "__servly_test__";
319
+ sessionStorage.setItem(testKey, "test");
320
+ sessionStorage.removeItem(testKey);
321
+ return true;
322
+ } catch {
323
+ return false;
324
+ }
325
+ }
326
+ function loadSession() {
327
+ if (!isSessionStorageAvailable()) {
328
+ return null;
329
+ }
330
+ try {
331
+ const stored = sessionStorage.getItem(SESSION_STORAGE_KEY);
332
+ if (!stored) {
333
+ return null;
334
+ }
335
+ return JSON.parse(stored);
336
+ } catch {
337
+ return null;
338
+ }
339
+ }
340
+ function saveSession(session) {
341
+ if (!isSessionStorageAvailable()) {
342
+ return;
343
+ }
344
+ try {
345
+ sessionStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(session));
346
+ } catch {
347
+ }
348
+ }
349
+ function clearSession() {
350
+ if (!isSessionStorageAvailable()) {
351
+ return;
352
+ }
353
+ try {
354
+ sessionStorage.removeItem(SESSION_STORAGE_KEY);
355
+ } catch {
356
+ }
357
+ }
358
+ function isSessionExpired(session) {
359
+ const now = Date.now();
360
+ return now - session.lastActivityAt > SESSION_TIMEOUT_MS;
361
+ }
362
+ var SessionManager = class {
363
+ constructor() {
364
+ this.session = null;
365
+ this.initialize();
366
+ }
367
+ /**
368
+ * Initialize session manager
369
+ */
370
+ initialize() {
371
+ const stored = loadSession();
372
+ if (stored && !isSessionExpired(stored)) {
373
+ this.session = stored;
374
+ this.touch();
375
+ } else {
376
+ this.createNewSession();
377
+ }
378
+ }
379
+ /**
380
+ * Create a new session
381
+ */
382
+ createNewSession() {
383
+ const now = Date.now();
384
+ this.session = {
385
+ id: generateUUID(),
386
+ createdAt: now,
387
+ lastActivityAt: now
388
+ };
389
+ saveSession(this.session);
390
+ }
391
+ /**
392
+ * Get current session ID
393
+ * Creates new session if expired
394
+ */
395
+ getSessionId() {
396
+ if (!this.session || isSessionExpired(this.session)) {
397
+ this.createNewSession();
398
+ }
399
+ return this.session.id;
400
+ }
401
+ /**
402
+ * Update last activity timestamp
403
+ */
404
+ touch() {
405
+ if (this.session) {
406
+ this.session.lastActivityAt = Date.now();
407
+ saveSession(this.session);
408
+ }
409
+ }
410
+ /**
411
+ * Force create a new session
412
+ */
413
+ rotate() {
414
+ this.createNewSession();
415
+ }
416
+ /**
417
+ * Clear current session
418
+ */
419
+ clear() {
420
+ this.session = null;
421
+ clearSession();
422
+ }
423
+ /**
424
+ * Get session info (for debugging)
425
+ */
426
+ getSessionInfo() {
427
+ return this.session ? { ...this.session } : null;
428
+ }
429
+ };
430
+ var sessionManagerInstance = null;
431
+ function getSessionManager() {
432
+ if (!sessionManagerInstance) {
433
+ sessionManagerInstance = new SessionManager();
434
+ }
435
+ return sessionManagerInstance;
436
+ }
437
+ function resetSessionManager() {
438
+ if (sessionManagerInstance) {
439
+ sessionManagerInstance.clear();
440
+ }
441
+ sessionManagerInstance = null;
442
+ }
443
+
444
+ // src/analytics.ts
445
+ var AnalyticsCollector = class {
446
+ constructor(config) {
447
+ this.eventQueue = [];
448
+ this.flushTimer = null;
449
+ this.isFlushing = false;
450
+ this.retryDelay = 1e3;
451
+ this.config = { ...DEFAULT_ANALYTICS_CONFIG, ...config };
452
+ this.isEnabled = this.config.enabled;
453
+ this.startFlushTimer();
454
+ }
455
+ // ============================================
456
+ // Configuration
457
+ // ============================================
458
+ /**
459
+ * Configure analytics
460
+ */
461
+ configure(config) {
462
+ this.config = { ...this.config, ...config };
463
+ this.isEnabled = this.config.enabled;
464
+ this.stopFlushTimer();
465
+ if (this.isEnabled) {
466
+ this.startFlushTimer();
467
+ }
468
+ }
469
+ /**
470
+ * Disable analytics collection
471
+ */
472
+ disable() {
473
+ this.isEnabled = false;
474
+ this.stopFlushTimer();
475
+ this.eventQueue = [];
476
+ }
477
+ /**
478
+ * Enable analytics collection
479
+ */
480
+ enable() {
481
+ this.isEnabled = true;
482
+ this.startFlushTimer();
483
+ }
484
+ /**
485
+ * Destroy and cleanup
486
+ */
487
+ destroy() {
488
+ this.disable();
489
+ }
490
+ // ============================================
491
+ // Event Tracking
492
+ // ============================================
493
+ /**
494
+ * Track component render
495
+ */
496
+ trackRender(componentId, version, duration, metadata) {
497
+ if (!this.shouldTrack()) {
498
+ return;
499
+ }
500
+ const event = {
501
+ type: "render",
502
+ componentId,
503
+ version,
504
+ timestamp: Date.now(),
505
+ sessionId: getSessionManager().getSessionId(),
506
+ appId: this.config.appId,
507
+ duration: Math.round(duration),
508
+ metadata
509
+ };
510
+ this.queueEvent(event);
511
+ }
512
+ /**
513
+ * Track component fetch
514
+ */
515
+ trackFetch(componentId, version, duration, fromCache, metadata) {
516
+ if (!this.shouldTrack()) {
517
+ return;
518
+ }
519
+ const event = {
520
+ type: "fetch",
521
+ componentId,
522
+ version,
523
+ timestamp: Date.now(),
524
+ sessionId: getSessionManager().getSessionId(),
525
+ appId: this.config.appId,
526
+ duration: Math.round(duration),
527
+ metadata: {
528
+ cacheHit: fromCache,
529
+ fetchDuration: Math.round(duration),
530
+ ...metadata
531
+ }
532
+ };
533
+ this.queueEvent(event);
534
+ }
535
+ /**
536
+ * Track error
537
+ */
538
+ trackError(componentId, version, error, context) {
539
+ if (!this.shouldTrack()) {
540
+ return;
541
+ }
542
+ const errorMessage = this.truncateString(
543
+ error.message || "Unknown error",
544
+ MAX_ERROR_MESSAGE_LENGTH
545
+ );
546
+ const stackTrace = error.stack ? this.truncateString(error.stack, MAX_STACK_TRACE_LENGTH) : void 0;
547
+ const event = {
548
+ type: "error",
549
+ componentId,
550
+ version,
551
+ timestamp: Date.now(),
552
+ sessionId: getSessionManager().getSessionId(),
553
+ appId: this.config.appId,
554
+ metadata: {
555
+ errorType: context?.errorType || "render",
556
+ errorMessage,
557
+ stackTrace,
558
+ bindingPath: context?.bindingPath,
559
+ elementId: context?.elementId
560
+ }
561
+ };
562
+ this.queueEvent(event);
563
+ }
564
+ // ============================================
565
+ // Queue Management
566
+ // ============================================
567
+ /**
568
+ * Check if event should be tracked (sampling + enabled)
569
+ */
570
+ shouldTrack() {
571
+ if (!this.isEnabled) {
572
+ return false;
573
+ }
574
+ if (this.config.sampleRate < 1) {
575
+ return Math.random() < this.config.sampleRate;
576
+ }
577
+ return true;
578
+ }
579
+ /**
580
+ * Add event to queue
581
+ */
582
+ queueEvent(event) {
583
+ if (this.eventQueue.length >= MAX_QUEUE_SIZE) {
584
+ this.eventQueue.shift();
585
+ this.log("Queue full, dropping oldest event");
586
+ }
587
+ this.eventQueue.push({
588
+ event,
589
+ retryCount: 0,
590
+ addedAt: Date.now()
591
+ });
592
+ if (this.eventQueue.length >= this.config.batchSize) {
593
+ this.flush();
594
+ }
595
+ }
596
+ /**
597
+ * Get current queue size
598
+ */
599
+ getQueueSize() {
600
+ return this.eventQueue.length;
601
+ }
602
+ // ============================================
603
+ // Flush Logic
604
+ // ============================================
605
+ /**
606
+ * Start the flush timer
607
+ */
608
+ startFlushTimer() {
609
+ if (this.flushTimer) {
610
+ return;
611
+ }
612
+ this.flushTimer = setInterval(() => {
613
+ this.flush();
614
+ }, this.config.flushInterval);
615
+ }
616
+ /**
617
+ * Stop the flush timer
618
+ */
619
+ stopFlushTimer() {
620
+ if (this.flushTimer) {
621
+ clearInterval(this.flushTimer);
622
+ this.flushTimer = null;
623
+ }
624
+ }
625
+ /**
626
+ * Flush events to server
627
+ */
628
+ async flush() {
629
+ if (this.isFlushing || this.eventQueue.length === 0) {
630
+ return;
631
+ }
632
+ this.isFlushing = true;
633
+ if (typeof requestIdleCallback !== "undefined") {
634
+ requestIdleCallback(
635
+ () => {
636
+ this.doFlush();
637
+ },
638
+ { timeout: 5e3 }
639
+ );
640
+ } else {
641
+ setTimeout(() => {
642
+ this.doFlush();
643
+ }, 0);
644
+ }
645
+ }
646
+ /**
647
+ * Perform the actual flush
648
+ */
649
+ async doFlush() {
650
+ const eventsToSend = this.eventQueue.splice(0, this.config.batchSize);
651
+ if (eventsToSend.length === 0) {
652
+ this.isFlushing = false;
653
+ return;
654
+ }
655
+ const request = {
656
+ events: eventsToSend.map((q) => q.event),
657
+ clientInfo: {
658
+ sdkVersion: SDK_VERSION,
659
+ environment: this.config.environment
660
+ }
661
+ };
662
+ try {
663
+ const response = await this.sendEvents(request);
664
+ if (response.success) {
665
+ this.log(`Flushed ${response.accepted} events`);
666
+ this.retryDelay = 1e3;
667
+ } else {
668
+ this.handleFailedEvents(eventsToSend, response);
669
+ }
670
+ } catch (error) {
671
+ this.handleNetworkError(eventsToSend, error);
672
+ } finally {
673
+ this.isFlushing = false;
674
+ }
675
+ }
676
+ /**
677
+ * Send events to the API
678
+ */
679
+ async sendEvents(request) {
680
+ const headers = {
681
+ "Content-Type": "application/json"
682
+ };
683
+ if (this.config.apiKey) {
684
+ headers["Authorization"] = `Bearer ${this.config.apiKey}`;
685
+ }
686
+ const response = await fetch(this.config.endpoint, {
687
+ method: "POST",
688
+ headers,
689
+ body: JSON.stringify(request)
690
+ });
691
+ if (response.status === 429) {
692
+ const retryAfter = parseInt(response.headers.get("Retry-After") || "60", 10);
693
+ return {
694
+ success: false,
695
+ accepted: 0,
696
+ rejected: request.events.length,
697
+ retryAfter
698
+ };
699
+ }
700
+ if (!response.ok) {
701
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
702
+ }
703
+ return await response.json();
704
+ }
705
+ /**
706
+ * Handle failed events (partial success)
707
+ */
708
+ handleFailedEvents(events, response) {
709
+ const eventsToRetry = events.filter((e) => e.retryCount < MAX_RETRY_ATTEMPTS);
710
+ eventsToRetry.forEach((e) => {
711
+ e.retryCount++;
712
+ this.eventQueue.unshift(e);
713
+ });
714
+ const dropped = events.length - eventsToRetry.length;
715
+ if (dropped > 0) {
716
+ this.log(`Dropped ${dropped} events after max retries`);
717
+ }
718
+ if (response.retryAfter) {
719
+ this.scheduleRetry(response.retryAfter * 1e3);
720
+ }
721
+ }
722
+ /**
723
+ * Handle network error
724
+ */
725
+ handleNetworkError(events, error) {
726
+ this.log(`Network error: ${error}`);
727
+ const eventsToRetry = events.filter((e) => e.retryCount < MAX_RETRY_ATTEMPTS);
728
+ eventsToRetry.forEach((e) => {
729
+ e.retryCount++;
730
+ this.eventQueue.unshift(e);
731
+ });
732
+ this.scheduleRetry(this.retryDelay);
733
+ this.retryDelay = Math.min(this.retryDelay * 2, 3e4);
734
+ }
735
+ /**
736
+ * Schedule a retry flush
737
+ */
738
+ scheduleRetry(delayMs) {
739
+ setTimeout(() => {
740
+ this.flush();
741
+ }, delayMs);
742
+ }
743
+ // ============================================
744
+ // Utilities
745
+ // ============================================
746
+ /**
747
+ * Truncate string to max length
748
+ */
749
+ truncateString(str, maxLength) {
750
+ if (str.length <= maxLength) {
751
+ return str;
752
+ }
753
+ return str.substring(0, maxLength - 3) + "...";
754
+ }
755
+ /**
756
+ * Log debug message
757
+ */
758
+ log(message) {
759
+ if (this.config.debug) {
760
+ console.log(`[Analytics] ${message}`);
761
+ }
762
+ }
763
+ };
764
+ var analyticsInstance = null;
765
+ function getAnalytics() {
766
+ if (!analyticsInstance) {
767
+ analyticsInstance = new AnalyticsCollector();
768
+ }
769
+ return analyticsInstance;
770
+ }
771
+ function configureAnalytics(config) {
772
+ getAnalytics().configure(config);
773
+ }
774
+ function resetAnalytics() {
775
+ if (analyticsInstance) {
776
+ analyticsInstance.destroy();
777
+ }
778
+ analyticsInstance = null;
779
+ }
780
+ var analytics = {
781
+ get instance() {
782
+ return getAnalytics();
783
+ },
784
+ configure: configureAnalytics,
785
+ trackRender: (componentId, version, duration, metadata) => getAnalytics().trackRender(componentId, version, duration, metadata),
786
+ trackFetch: (componentId, version, duration, fromCache, metadata) => getAnalytics().trackFetch(componentId, version, duration, fromCache, metadata),
787
+ trackError: (componentId, version, error, context) => getAnalytics().trackError(componentId, version, error, context),
788
+ flush: () => getAnalytics().flush(),
789
+ disable: () => getAnalytics().disable(),
790
+ enable: () => getAnalytics().enable()
791
+ };
792
+
269
793
  // src/bindings.ts
270
794
  var BINDING_SOURCES = [
271
795
  "props",
@@ -372,44 +896,64 @@ function resolveTernaryValue(value, context) {
372
896
  if (value === "undefined") return void 0;
373
897
  return resolveExpression(value, context);
374
898
  }
375
- function resolveTemplate(template, context) {
899
+ function resolveTemplate(template, context, componentId) {
376
900
  if (!template || typeof template !== "string") {
377
901
  return template;
378
902
  }
379
903
  if (!hasTemplateSyntax(template)) {
380
904
  return template;
381
905
  }
382
- const singleMatch = template.match(/^\{\{([^}]+)\}\}$/);
383
- if (singleMatch) {
384
- const value = resolveExpression(singleMatch[1], context);
385
- if (value === void 0 || value === null) {
386
- return "";
387
- }
388
- return String(value);
389
- }
390
- return template.replace(TEMPLATE_REGEX, (match, expression) => {
391
- const value = resolveExpression(expression, context);
392
- if (value === void 0 || value === null) {
393
- return "";
906
+ try {
907
+ const singleMatch = template.match(/^\{\{([^}]+)\}\}$/);
908
+ if (singleMatch) {
909
+ const value = resolveExpression(singleMatch[1], context);
910
+ if (value === void 0 || value === null) {
911
+ return "";
912
+ }
913
+ return String(value);
394
914
  }
395
- if (typeof value === "object") {
396
- return JSON.stringify(value);
915
+ return template.replace(TEMPLATE_REGEX, (match, expression) => {
916
+ const value = resolveExpression(expression, context);
917
+ if (value === void 0 || value === null) {
918
+ return "";
919
+ }
920
+ if (typeof value === "object") {
921
+ return JSON.stringify(value);
922
+ }
923
+ return String(value);
924
+ });
925
+ } catch (error) {
926
+ if (componentId) {
927
+ analytics.trackError(componentId, "latest", error, {
928
+ errorType: "binding",
929
+ bindingPath: template
930
+ });
397
931
  }
398
- return String(value);
399
- });
932
+ return "";
933
+ }
400
934
  }
401
- function resolveTemplateValue(template, context) {
935
+ function resolveTemplateValue(template, context, componentId) {
402
936
  if (!template || typeof template !== "string") {
403
937
  return template;
404
938
  }
405
939
  if (!hasTemplateSyntax(template)) {
406
940
  return template;
407
941
  }
408
- const singleMatch = template.match(/^\{\{([^}]+)\}\}$/);
409
- if (singleMatch) {
410
- return resolveExpression(singleMatch[1], context);
942
+ try {
943
+ const singleMatch = template.match(/^\{\{([^}]+)\}\}$/);
944
+ if (singleMatch) {
945
+ return resolveExpression(singleMatch[1], context);
946
+ }
947
+ return resolveTemplate(template, context, componentId);
948
+ } catch (error) {
949
+ if (componentId) {
950
+ analytics.trackError(componentId, "latest", error, {
951
+ errorType: "binding",
952
+ bindingPath: template
953
+ });
954
+ }
955
+ return void 0;
411
956
  }
412
- return resolveTemplate(template, context);
413
957
  }
414
958
  function isPlainObject(value) {
415
959
  return Object.prototype.toString.call(value) === "[object Object]";
@@ -642,6 +1186,162 @@ function updateStyles(element, oldStyles, newStyles) {
642
1186
  }
643
1187
  }
644
1188
 
1189
+ // src/memorySampler.ts
1190
+ var MemorySampler = class {
1191
+ constructor() {
1192
+ this.isSupported = this.checkSupport();
1193
+ }
1194
+ /**
1195
+ * Check if memory API is available
1196
+ */
1197
+ checkSupport() {
1198
+ if (typeof performance === "undefined") {
1199
+ return false;
1200
+ }
1201
+ const perf = performance;
1202
+ return typeof perf.memory !== "undefined";
1203
+ }
1204
+ /**
1205
+ * Check if memory sampling is available
1206
+ */
1207
+ isAvailable() {
1208
+ return this.isSupported;
1209
+ }
1210
+ /**
1211
+ * Take a memory sample
1212
+ * Returns null if memory API is not available
1213
+ */
1214
+ sample() {
1215
+ if (!this.isSupported) {
1216
+ return null;
1217
+ }
1218
+ const perf = performance;
1219
+ if (!perf.memory) {
1220
+ return null;
1221
+ }
1222
+ return {
1223
+ heapUsedKB: Math.round(perf.memory.usedJSHeapSize / 1024),
1224
+ heapTotalKB: Math.round(perf.memory.totalJSHeapSize / 1024),
1225
+ timestamp: Date.now()
1226
+ };
1227
+ }
1228
+ /**
1229
+ * Calculate heap delta between two samples
1230
+ * Returns the difference in KB (positive = memory increased)
1231
+ */
1232
+ calculateDelta(before, after) {
1233
+ if (!before || !after) {
1234
+ return null;
1235
+ }
1236
+ return after.heapUsedKB - before.heapUsedKB;
1237
+ }
1238
+ };
1239
+ var memorySamplerInstance = null;
1240
+ function getMemorySampler() {
1241
+ if (!memorySamplerInstance) {
1242
+ memorySamplerInstance = new MemorySampler();
1243
+ }
1244
+ return memorySamplerInstance;
1245
+ }
1246
+ function resetMemorySampler() {
1247
+ memorySamplerInstance = null;
1248
+ }
1249
+
1250
+ // src/longTaskObserver.ts
1251
+ var LongTaskObserver = class {
1252
+ constructor() {
1253
+ this.observer = null;
1254
+ this.longTaskCount = 0;
1255
+ this.isObserving = false;
1256
+ this.isSupported = this.checkSupport();
1257
+ }
1258
+ /**
1259
+ * Check if PerformanceObserver with longtask support is available
1260
+ */
1261
+ checkSupport() {
1262
+ if (typeof PerformanceObserver === "undefined") {
1263
+ return false;
1264
+ }
1265
+ try {
1266
+ const supportedTypes = PerformanceObserver.supportedEntryTypes;
1267
+ return Array.isArray(supportedTypes) && supportedTypes.includes("longtask");
1268
+ } catch {
1269
+ return false;
1270
+ }
1271
+ }
1272
+ /**
1273
+ * Check if long task observation is available
1274
+ */
1275
+ isAvailable() {
1276
+ return this.isSupported;
1277
+ }
1278
+ /**
1279
+ * Start observing long tasks
1280
+ */
1281
+ start() {
1282
+ if (!this.isSupported || this.isObserving) {
1283
+ return;
1284
+ }
1285
+ this.longTaskCount = 0;
1286
+ try {
1287
+ this.observer = new PerformanceObserver((list) => {
1288
+ const entries = list.getEntries();
1289
+ this.longTaskCount += entries.length;
1290
+ });
1291
+ this.observer.observe({ entryTypes: ["longtask"] });
1292
+ this.isObserving = true;
1293
+ } catch {
1294
+ this.isSupported = false;
1295
+ }
1296
+ }
1297
+ /**
1298
+ * Stop observing and return the count of long tasks
1299
+ */
1300
+ stop() {
1301
+ if (!this.isObserving || !this.observer) {
1302
+ return this.longTaskCount;
1303
+ }
1304
+ try {
1305
+ this.observer.disconnect();
1306
+ } catch {
1307
+ }
1308
+ this.observer = null;
1309
+ this.isObserving = false;
1310
+ return this.longTaskCount;
1311
+ }
1312
+ /**
1313
+ * Reset the long task counter
1314
+ */
1315
+ reset() {
1316
+ this.longTaskCount = 0;
1317
+ }
1318
+ /**
1319
+ * Get current count without stopping observation
1320
+ */
1321
+ getCount() {
1322
+ return this.longTaskCount;
1323
+ }
1324
+ /**
1325
+ * Check if currently observing
1326
+ */
1327
+ isActive() {
1328
+ return this.isObserving;
1329
+ }
1330
+ };
1331
+ var longTaskObserverInstance = null;
1332
+ function getLongTaskObserver() {
1333
+ if (!longTaskObserverInstance) {
1334
+ longTaskObserverInstance = new LongTaskObserver();
1335
+ }
1336
+ return longTaskObserverInstance;
1337
+ }
1338
+ function resetLongTaskObserver() {
1339
+ if (longTaskObserverInstance) {
1340
+ longTaskObserverInstance.stop();
1341
+ }
1342
+ longTaskObserverInstance = null;
1343
+ }
1344
+
645
1345
  // src/renderer.ts
646
1346
  var COMPONENT_TO_TAG = {
647
1347
  container: "div",
@@ -895,52 +1595,84 @@ function renderElement(element, tree, context, eventHandlers, elementStates, sta
895
1595
  }
896
1596
  function render(options) {
897
1597
  const { container, elements, context, eventHandlers, componentRegistry, onDependencyNeeded } = options;
898
- const tree = buildTree(elements);
1598
+ const startTime = performance.now();
1599
+ const memorySampler = getMemorySampler();
1600
+ const longTaskObserver = getLongTaskObserver();
1601
+ const memoryBefore = memorySampler.sample();
1602
+ longTaskObserver.start();
899
1603
  const rootElements = elements.filter((el) => !el.parent || el.parent === null);
900
- const state = {
901
- container,
902
- elements,
903
- context,
904
- eventHandlers,
905
- elementStates: /* @__PURE__ */ new Map(),
906
- rootElement: null,
907
- componentRegistry,
908
- onDependencyNeeded,
909
- renderingStack: /* @__PURE__ */ new Set()
910
- };
911
- container.innerHTML = "";
912
- if (rootElements.length === 0) {
913
- return {
1604
+ const componentId = rootElements[0]?.componentId || "unknown";
1605
+ const version = "latest";
1606
+ try {
1607
+ const tree = buildTree(elements);
1608
+ const state = {
1609
+ container,
1610
+ elements,
1611
+ context,
1612
+ eventHandlers,
1613
+ elementStates: /* @__PURE__ */ new Map(),
914
1614
  rootElement: null,
1615
+ componentRegistry,
1616
+ onDependencyNeeded,
1617
+ renderingStack: /* @__PURE__ */ new Set()
1618
+ };
1619
+ container.innerHTML = "";
1620
+ if (rootElements.length === 0) {
1621
+ const duration2 = performance.now() - startTime;
1622
+ const longTasks2 = longTaskObserver.stop();
1623
+ analytics.trackRender(componentId, version, duration2, {
1624
+ elementCount: 0,
1625
+ longTaskCount: longTasks2
1626
+ });
1627
+ return {
1628
+ rootElement: null,
1629
+ update: (newContext) => update(state, newContext),
1630
+ destroy: () => destroy(state)
1631
+ };
1632
+ }
1633
+ if (rootElements.length === 1) {
1634
+ state.rootElement = renderElement(
1635
+ rootElements[0],
1636
+ tree,
1637
+ context,
1638
+ eventHandlers,
1639
+ state.elementStates,
1640
+ state
1641
+ );
1642
+ container.appendChild(state.rootElement);
1643
+ } else {
1644
+ const wrapper = document.createElement("div");
1645
+ wrapper.setAttribute("data-servly-wrapper", "true");
1646
+ for (const root of rootElements) {
1647
+ const rootElement = renderElement(root, tree, context, eventHandlers, state.elementStates, state);
1648
+ wrapper.appendChild(rootElement);
1649
+ }
1650
+ state.rootElement = wrapper;
1651
+ container.appendChild(wrapper);
1652
+ }
1653
+ const duration = performance.now() - startTime;
1654
+ const memoryAfter = memorySampler.sample();
1655
+ const longTasks = longTaskObserver.stop();
1656
+ const heapDelta = memorySampler.calculateDelta(memoryBefore, memoryAfter);
1657
+ analytics.trackRender(componentId, version, duration, {
1658
+ elementCount: elements.length,
1659
+ heapDeltaKB: heapDelta ?? void 0,
1660
+ longTaskCount: longTasks,
1661
+ hasEventHandlers: !!eventHandlers && Object.keys(eventHandlers).length > 0,
1662
+ hasDependencies: !!componentRegistry
1663
+ });
1664
+ return {
1665
+ rootElement: state.rootElement,
915
1666
  update: (newContext) => update(state, newContext),
916
1667
  destroy: () => destroy(state)
917
1668
  };
1669
+ } catch (error) {
1670
+ longTaskObserver.stop();
1671
+ analytics.trackError(componentId, version, error, {
1672
+ errorType: "render"
1673
+ });
1674
+ throw error;
918
1675
  }
919
- if (rootElements.length === 1) {
920
- state.rootElement = renderElement(
921
- rootElements[0],
922
- tree,
923
- context,
924
- eventHandlers,
925
- state.elementStates,
926
- state
927
- );
928
- container.appendChild(state.rootElement);
929
- } else {
930
- const wrapper = document.createElement("div");
931
- wrapper.setAttribute("data-servly-wrapper", "true");
932
- for (const root of rootElements) {
933
- const rootElement = renderElement(root, tree, context, eventHandlers, state.elementStates, state);
934
- wrapper.appendChild(rootElement);
935
- }
936
- state.rootElement = wrapper;
937
- container.appendChild(wrapper);
938
- }
939
- return {
940
- rootElement: state.rootElement,
941
- update: (newContext) => update(state, newContext),
942
- destroy: () => destroy(state)
943
- };
944
1676
  }
945
1677
  function update(state, newContext) {
946
1678
  state.context = newContext;
@@ -1399,6 +2131,7 @@ async function fetchComponent(id, options = {}) {
1399
2131
  ...DEFAULT_RETRY_CONFIG,
1400
2132
  ...retryConfig
1401
2133
  };
2134
+ const startTime = performance.now();
1402
2135
  if (!forceRefresh) {
1403
2136
  const cached = getFromCache(id, version, cacheStrategy, cacheConfig);
1404
2137
  if (cached) {
@@ -1406,6 +2139,11 @@ async function fetchComponent(id, options = {}) {
1406
2139
  if (cached.bundle) {
1407
2140
  registry = buildRegistryFromBundle(cached);
1408
2141
  }
2142
+ const duration = performance.now() - startTime;
2143
+ analytics.trackFetch(id, cached.version, duration, true, {
2144
+ cacheHit: true,
2145
+ fetchDuration: Math.round(duration)
2146
+ });
1409
2147
  return {
1410
2148
  data: cached,
1411
2149
  fromCache: true,
@@ -1452,6 +2190,12 @@ async function fetchComponent(id, options = {}) {
1452
2190
  version: entry.resolved || entry.version
1453
2191
  }));
1454
2192
  }
2193
+ const duration = performance.now() - startTime;
2194
+ analytics.trackFetch(id, resolvedVersion, duration, false, {
2195
+ cacheHit: false,
2196
+ fetchDuration: Math.round(duration),
2197
+ dependencyCount: data.dependencies ? Object.keys(data.dependencies).length : 0
2198
+ });
1455
2199
  return {
1456
2200
  data,
1457
2201
  fromCache: false,
@@ -1470,7 +2214,11 @@ async function fetchComponent(id, options = {}) {
1470
2214
  }
1471
2215
  }
1472
2216
  }
1473
- throw lastError || new Error("Failed to fetch component");
2217
+ const finalError = lastError || new Error("Failed to fetch component");
2218
+ analytics.trackError(id, version, finalError, {
2219
+ errorType: "fetch"
2220
+ });
2221
+ throw finalError;
1474
2222
  }
1475
2223
  async function fetchComponentWithDependencies(id, options = {}) {
1476
2224
  const result = await fetchComponent(id, { ...options, includeBundle: true });
@@ -1952,8 +2700,13 @@ function getSampleValue(def) {
1952
2700
  init_registry();
1953
2701
  // Annotate the CommonJS export names for ESM import in node:
1954
2702
  0 && (module.exports = {
2703
+ AnalyticsCollector,
1955
2704
  DEFAULT_CACHE_CONFIG,
1956
2705
  DEFAULT_RETRY_CONFIG,
2706
+ LongTaskObserver,
2707
+ MemorySampler,
2708
+ SessionManager,
2709
+ analytics,
1957
2710
  applyStyles,
1958
2711
  batchFetchComponents,
1959
2712
  buildClassName,
@@ -1967,6 +2720,7 @@ init_registry();
1967
2720
  clearStyles,
1968
2721
  collectAllDependencies,
1969
2722
  compareVersions,
2723
+ configureAnalytics,
1970
2724
  createRegistry,
1971
2725
  detectCircularDependencies,
1972
2726
  extractBindingKeys,
@@ -1977,11 +2731,15 @@ init_registry();
1977
2731
  formatStyleValue,
1978
2732
  formatVersion,
1979
2733
  generateTestCases,
2734
+ getAnalytics,
1980
2735
  getCacheKey,
1981
2736
  getDependencyTree,
1982
2737
  getFromCache,
2738
+ getLongTaskObserver,
1983
2739
  getMemoryCacheSize,
2740
+ getMemorySampler,
1984
2741
  getRegistryUrl,
2742
+ getSessionManager,
1985
2743
  hasTemplateSyntax,
1986
2744
  invalidateCache,
1987
2745
  isComponentAvailable,
@@ -1991,6 +2749,10 @@ init_registry();
1991
2749
  processStyles,
1992
2750
  render,
1993
2751
  renderDynamicList,
2752
+ resetAnalytics,
2753
+ resetLongTaskObserver,
2754
+ resetMemorySampler,
2755
+ resetSessionManager,
1994
2756
  resolveBindingPath,
1995
2757
  resolveTemplate,
1996
2758
  resolveTemplateValue,