@servlyadmin/runtime-core 0.1.2 → 0.1.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/index.js CHANGED
@@ -7,6 +7,516 @@ import {
7
7
  extractDependenciesFromCode
8
8
  } from "./chunk-CIUQK4GA.js";
9
9
 
10
+ // src/analyticsTypes.ts
11
+ var DEFAULT_ANALYTICS_CONFIG = {
12
+ enabled: true,
13
+ endpoint: "/api/v1/analytics/events",
14
+ batchSize: 50,
15
+ flushInterval: 3e4,
16
+ // 30 seconds
17
+ sampleRate: 1,
18
+ environment: "production",
19
+ debug: false
20
+ };
21
+ var MAX_ERROR_MESSAGE_LENGTH = 1e3;
22
+ var MAX_STACK_TRACE_LENGTH = 500;
23
+ var MAX_QUEUE_SIZE = 500;
24
+ var MAX_RETRY_ATTEMPTS = 3;
25
+ var SESSION_TIMEOUT_MS = 30 * 60 * 1e3;
26
+ var SDK_VERSION = "1.0.0";
27
+
28
+ // src/sessionManager.ts
29
+ var SESSION_STORAGE_KEY = "servly_analytics_session";
30
+ function generateUUID() {
31
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
32
+ return crypto.randomUUID();
33
+ }
34
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
35
+ const r = Math.random() * 16 | 0;
36
+ const v = c === "x" ? r : r & 3 | 8;
37
+ return v.toString(16);
38
+ });
39
+ }
40
+ function isSessionStorageAvailable() {
41
+ try {
42
+ if (typeof sessionStorage === "undefined") {
43
+ return false;
44
+ }
45
+ const testKey = "__servly_test__";
46
+ sessionStorage.setItem(testKey, "test");
47
+ sessionStorage.removeItem(testKey);
48
+ return true;
49
+ } catch {
50
+ return false;
51
+ }
52
+ }
53
+ function loadSession() {
54
+ if (!isSessionStorageAvailable()) {
55
+ return null;
56
+ }
57
+ try {
58
+ const stored = sessionStorage.getItem(SESSION_STORAGE_KEY);
59
+ if (!stored) {
60
+ return null;
61
+ }
62
+ return JSON.parse(stored);
63
+ } catch {
64
+ return null;
65
+ }
66
+ }
67
+ function saveSession(session) {
68
+ if (!isSessionStorageAvailable()) {
69
+ return;
70
+ }
71
+ try {
72
+ sessionStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(session));
73
+ } catch {
74
+ }
75
+ }
76
+ function clearSession() {
77
+ if (!isSessionStorageAvailable()) {
78
+ return;
79
+ }
80
+ try {
81
+ sessionStorage.removeItem(SESSION_STORAGE_KEY);
82
+ } catch {
83
+ }
84
+ }
85
+ function isSessionExpired(session) {
86
+ const now = Date.now();
87
+ return now - session.lastActivityAt > SESSION_TIMEOUT_MS;
88
+ }
89
+ var SessionManager = class {
90
+ constructor() {
91
+ this.session = null;
92
+ this.initialize();
93
+ }
94
+ /**
95
+ * Initialize session manager
96
+ */
97
+ initialize() {
98
+ const stored = loadSession();
99
+ if (stored && !isSessionExpired(stored)) {
100
+ this.session = stored;
101
+ this.touch();
102
+ } else {
103
+ this.createNewSession();
104
+ }
105
+ }
106
+ /**
107
+ * Create a new session
108
+ */
109
+ createNewSession() {
110
+ const now = Date.now();
111
+ this.session = {
112
+ id: generateUUID(),
113
+ createdAt: now,
114
+ lastActivityAt: now
115
+ };
116
+ saveSession(this.session);
117
+ }
118
+ /**
119
+ * Get current session ID
120
+ * Creates new session if expired
121
+ */
122
+ getSessionId() {
123
+ if (!this.session || isSessionExpired(this.session)) {
124
+ this.createNewSession();
125
+ }
126
+ return this.session.id;
127
+ }
128
+ /**
129
+ * Update last activity timestamp
130
+ */
131
+ touch() {
132
+ if (this.session) {
133
+ this.session.lastActivityAt = Date.now();
134
+ saveSession(this.session);
135
+ }
136
+ }
137
+ /**
138
+ * Force create a new session
139
+ */
140
+ rotate() {
141
+ this.createNewSession();
142
+ }
143
+ /**
144
+ * Clear current session
145
+ */
146
+ clear() {
147
+ this.session = null;
148
+ clearSession();
149
+ }
150
+ /**
151
+ * Get session info (for debugging)
152
+ */
153
+ getSessionInfo() {
154
+ return this.session ? { ...this.session } : null;
155
+ }
156
+ };
157
+ var sessionManagerInstance = null;
158
+ function getSessionManager() {
159
+ if (!sessionManagerInstance) {
160
+ sessionManagerInstance = new SessionManager();
161
+ }
162
+ return sessionManagerInstance;
163
+ }
164
+ function resetSessionManager() {
165
+ if (sessionManagerInstance) {
166
+ sessionManagerInstance.clear();
167
+ }
168
+ sessionManagerInstance = null;
169
+ }
170
+
171
+ // src/analytics.ts
172
+ var AnalyticsCollector = class {
173
+ constructor(config) {
174
+ this.eventQueue = [];
175
+ this.flushTimer = null;
176
+ this.isFlushing = false;
177
+ this.retryDelay = 1e3;
178
+ this.config = { ...DEFAULT_ANALYTICS_CONFIG, ...config };
179
+ this.isEnabled = this.config.enabled;
180
+ this.startFlushTimer();
181
+ }
182
+ // ============================================
183
+ // Configuration
184
+ // ============================================
185
+ /**
186
+ * Configure analytics
187
+ */
188
+ configure(config) {
189
+ this.config = { ...this.config, ...config };
190
+ this.isEnabled = this.config.enabled;
191
+ this.stopFlushTimer();
192
+ if (this.isEnabled) {
193
+ this.startFlushTimer();
194
+ }
195
+ }
196
+ /**
197
+ * Disable analytics collection
198
+ */
199
+ disable() {
200
+ this.isEnabled = false;
201
+ this.stopFlushTimer();
202
+ this.eventQueue = [];
203
+ }
204
+ /**
205
+ * Enable analytics collection
206
+ */
207
+ enable() {
208
+ this.isEnabled = true;
209
+ this.startFlushTimer();
210
+ }
211
+ /**
212
+ * Destroy and cleanup
213
+ */
214
+ destroy() {
215
+ this.disable();
216
+ }
217
+ // ============================================
218
+ // Event Tracking
219
+ // ============================================
220
+ /**
221
+ * Track component render
222
+ */
223
+ trackRender(componentId, version, duration, metadata) {
224
+ if (!this.shouldTrack()) {
225
+ return;
226
+ }
227
+ const event = {
228
+ type: "render",
229
+ componentId,
230
+ version,
231
+ timestamp: Date.now(),
232
+ sessionId: getSessionManager().getSessionId(),
233
+ appId: this.config.appId,
234
+ duration: Math.round(duration),
235
+ metadata
236
+ };
237
+ this.queueEvent(event);
238
+ }
239
+ /**
240
+ * Track component fetch
241
+ */
242
+ trackFetch(componentId, version, duration, fromCache, metadata) {
243
+ if (!this.shouldTrack()) {
244
+ return;
245
+ }
246
+ const event = {
247
+ type: "fetch",
248
+ componentId,
249
+ version,
250
+ timestamp: Date.now(),
251
+ sessionId: getSessionManager().getSessionId(),
252
+ appId: this.config.appId,
253
+ duration: Math.round(duration),
254
+ metadata: {
255
+ cacheHit: fromCache,
256
+ fetchDuration: Math.round(duration),
257
+ ...metadata
258
+ }
259
+ };
260
+ this.queueEvent(event);
261
+ }
262
+ /**
263
+ * Track error
264
+ */
265
+ trackError(componentId, version, error, context) {
266
+ if (!this.shouldTrack()) {
267
+ return;
268
+ }
269
+ const errorMessage = this.truncateString(
270
+ error.message || "Unknown error",
271
+ MAX_ERROR_MESSAGE_LENGTH
272
+ );
273
+ const stackTrace = error.stack ? this.truncateString(error.stack, MAX_STACK_TRACE_LENGTH) : void 0;
274
+ const event = {
275
+ type: "error",
276
+ componentId,
277
+ version,
278
+ timestamp: Date.now(),
279
+ sessionId: getSessionManager().getSessionId(),
280
+ appId: this.config.appId,
281
+ metadata: {
282
+ errorType: context?.errorType || "render",
283
+ errorMessage,
284
+ stackTrace,
285
+ bindingPath: context?.bindingPath,
286
+ elementId: context?.elementId
287
+ }
288
+ };
289
+ this.queueEvent(event);
290
+ }
291
+ // ============================================
292
+ // Queue Management
293
+ // ============================================
294
+ /**
295
+ * Check if event should be tracked (sampling + enabled)
296
+ */
297
+ shouldTrack() {
298
+ if (!this.isEnabled) {
299
+ return false;
300
+ }
301
+ if (this.config.sampleRate < 1) {
302
+ return Math.random() < this.config.sampleRate;
303
+ }
304
+ return true;
305
+ }
306
+ /**
307
+ * Add event to queue
308
+ */
309
+ queueEvent(event) {
310
+ if (this.eventQueue.length >= MAX_QUEUE_SIZE) {
311
+ this.eventQueue.shift();
312
+ this.log("Queue full, dropping oldest event");
313
+ }
314
+ this.eventQueue.push({
315
+ event,
316
+ retryCount: 0,
317
+ addedAt: Date.now()
318
+ });
319
+ if (this.eventQueue.length >= this.config.batchSize) {
320
+ this.flush();
321
+ }
322
+ }
323
+ /**
324
+ * Get current queue size
325
+ */
326
+ getQueueSize() {
327
+ return this.eventQueue.length;
328
+ }
329
+ // ============================================
330
+ // Flush Logic
331
+ // ============================================
332
+ /**
333
+ * Start the flush timer
334
+ */
335
+ startFlushTimer() {
336
+ if (this.flushTimer) {
337
+ return;
338
+ }
339
+ this.flushTimer = setInterval(() => {
340
+ this.flush();
341
+ }, this.config.flushInterval);
342
+ }
343
+ /**
344
+ * Stop the flush timer
345
+ */
346
+ stopFlushTimer() {
347
+ if (this.flushTimer) {
348
+ clearInterval(this.flushTimer);
349
+ this.flushTimer = null;
350
+ }
351
+ }
352
+ /**
353
+ * Flush events to server
354
+ */
355
+ async flush() {
356
+ if (this.isFlushing || this.eventQueue.length === 0) {
357
+ return;
358
+ }
359
+ this.isFlushing = true;
360
+ if (typeof requestIdleCallback !== "undefined") {
361
+ requestIdleCallback(
362
+ () => {
363
+ this.doFlush();
364
+ },
365
+ { timeout: 5e3 }
366
+ );
367
+ } else {
368
+ setTimeout(() => {
369
+ this.doFlush();
370
+ }, 0);
371
+ }
372
+ }
373
+ /**
374
+ * Perform the actual flush
375
+ */
376
+ async doFlush() {
377
+ const eventsToSend = this.eventQueue.splice(0, this.config.batchSize);
378
+ if (eventsToSend.length === 0) {
379
+ this.isFlushing = false;
380
+ return;
381
+ }
382
+ const request = {
383
+ events: eventsToSend.map((q) => q.event),
384
+ clientInfo: {
385
+ sdkVersion: SDK_VERSION,
386
+ environment: this.config.environment
387
+ }
388
+ };
389
+ try {
390
+ const response = await this.sendEvents(request);
391
+ if (response.success) {
392
+ this.log(`Flushed ${response.accepted} events`);
393
+ this.retryDelay = 1e3;
394
+ } else {
395
+ this.handleFailedEvents(eventsToSend, response);
396
+ }
397
+ } catch (error) {
398
+ this.handleNetworkError(eventsToSend, error);
399
+ } finally {
400
+ this.isFlushing = false;
401
+ }
402
+ }
403
+ /**
404
+ * Send events to the API
405
+ */
406
+ async sendEvents(request) {
407
+ const headers = {
408
+ "Content-Type": "application/json"
409
+ };
410
+ if (this.config.apiKey) {
411
+ headers["Authorization"] = `Bearer ${this.config.apiKey}`;
412
+ }
413
+ const response = await fetch(this.config.endpoint, {
414
+ method: "POST",
415
+ headers,
416
+ body: JSON.stringify(request)
417
+ });
418
+ if (response.status === 429) {
419
+ const retryAfter = parseInt(response.headers.get("Retry-After") || "60", 10);
420
+ return {
421
+ success: false,
422
+ accepted: 0,
423
+ rejected: request.events.length,
424
+ retryAfter
425
+ };
426
+ }
427
+ if (!response.ok) {
428
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
429
+ }
430
+ return await response.json();
431
+ }
432
+ /**
433
+ * Handle failed events (partial success)
434
+ */
435
+ handleFailedEvents(events, response) {
436
+ const eventsToRetry = events.filter((e) => e.retryCount < MAX_RETRY_ATTEMPTS);
437
+ eventsToRetry.forEach((e) => {
438
+ e.retryCount++;
439
+ this.eventQueue.unshift(e);
440
+ });
441
+ const dropped = events.length - eventsToRetry.length;
442
+ if (dropped > 0) {
443
+ this.log(`Dropped ${dropped} events after max retries`);
444
+ }
445
+ if (response.retryAfter) {
446
+ this.scheduleRetry(response.retryAfter * 1e3);
447
+ }
448
+ }
449
+ /**
450
+ * Handle network error
451
+ */
452
+ handleNetworkError(events, error) {
453
+ this.log(`Network error: ${error}`);
454
+ const eventsToRetry = events.filter((e) => e.retryCount < MAX_RETRY_ATTEMPTS);
455
+ eventsToRetry.forEach((e) => {
456
+ e.retryCount++;
457
+ this.eventQueue.unshift(e);
458
+ });
459
+ this.scheduleRetry(this.retryDelay);
460
+ this.retryDelay = Math.min(this.retryDelay * 2, 3e4);
461
+ }
462
+ /**
463
+ * Schedule a retry flush
464
+ */
465
+ scheduleRetry(delayMs) {
466
+ setTimeout(() => {
467
+ this.flush();
468
+ }, delayMs);
469
+ }
470
+ // ============================================
471
+ // Utilities
472
+ // ============================================
473
+ /**
474
+ * Truncate string to max length
475
+ */
476
+ truncateString(str, maxLength) {
477
+ if (str.length <= maxLength) {
478
+ return str;
479
+ }
480
+ return str.substring(0, maxLength - 3) + "...";
481
+ }
482
+ /**
483
+ * Log debug message
484
+ */
485
+ log(message) {
486
+ if (this.config.debug) {
487
+ console.log(`[Analytics] ${message}`);
488
+ }
489
+ }
490
+ };
491
+ var analyticsInstance = null;
492
+ function getAnalytics() {
493
+ if (!analyticsInstance) {
494
+ analyticsInstance = new AnalyticsCollector();
495
+ }
496
+ return analyticsInstance;
497
+ }
498
+ function configureAnalytics(config) {
499
+ getAnalytics().configure(config);
500
+ }
501
+ function resetAnalytics() {
502
+ if (analyticsInstance) {
503
+ analyticsInstance.destroy();
504
+ }
505
+ analyticsInstance = null;
506
+ }
507
+ var analytics = {
508
+ get instance() {
509
+ return getAnalytics();
510
+ },
511
+ configure: configureAnalytics,
512
+ trackRender: (componentId, version, duration, metadata) => getAnalytics().trackRender(componentId, version, duration, metadata),
513
+ trackFetch: (componentId, version, duration, fromCache, metadata) => getAnalytics().trackFetch(componentId, version, duration, fromCache, metadata),
514
+ trackError: (componentId, version, error, context) => getAnalytics().trackError(componentId, version, error, context),
515
+ flush: () => getAnalytics().flush(),
516
+ disable: () => getAnalytics().disable(),
517
+ enable: () => getAnalytics().enable()
518
+ };
519
+
10
520
  // src/bindings.ts
11
521
  var BINDING_SOURCES = [
12
522
  "props",
@@ -113,44 +623,64 @@ function resolveTernaryValue(value, context) {
113
623
  if (value === "undefined") return void 0;
114
624
  return resolveExpression(value, context);
115
625
  }
116
- function resolveTemplate(template, context) {
626
+ function resolveTemplate(template, context, componentId) {
117
627
  if (!template || typeof template !== "string") {
118
628
  return template;
119
629
  }
120
630
  if (!hasTemplateSyntax(template)) {
121
631
  return template;
122
632
  }
123
- const singleMatch = template.match(/^\{\{([^}]+)\}\}$/);
124
- if (singleMatch) {
125
- const value = resolveExpression(singleMatch[1], context);
126
- if (value === void 0 || value === null) {
127
- return "";
128
- }
129
- return String(value);
130
- }
131
- return template.replace(TEMPLATE_REGEX, (match, expression) => {
132
- const value = resolveExpression(expression, context);
133
- if (value === void 0 || value === null) {
134
- return "";
633
+ try {
634
+ const singleMatch = template.match(/^\{\{([^}]+)\}\}$/);
635
+ if (singleMatch) {
636
+ const value = resolveExpression(singleMatch[1], context);
637
+ if (value === void 0 || value === null) {
638
+ return "";
639
+ }
640
+ return String(value);
135
641
  }
136
- if (typeof value === "object") {
137
- return JSON.stringify(value);
642
+ return template.replace(TEMPLATE_REGEX, (match, expression) => {
643
+ const value = resolveExpression(expression, context);
644
+ if (value === void 0 || value === null) {
645
+ return "";
646
+ }
647
+ if (typeof value === "object") {
648
+ return JSON.stringify(value);
649
+ }
650
+ return String(value);
651
+ });
652
+ } catch (error) {
653
+ if (componentId) {
654
+ analytics.trackError(componentId, "latest", error, {
655
+ errorType: "binding",
656
+ bindingPath: template
657
+ });
138
658
  }
139
- return String(value);
140
- });
659
+ return "";
660
+ }
141
661
  }
142
- function resolveTemplateValue(template, context) {
662
+ function resolveTemplateValue(template, context, componentId) {
143
663
  if (!template || typeof template !== "string") {
144
664
  return template;
145
665
  }
146
666
  if (!hasTemplateSyntax(template)) {
147
667
  return template;
148
668
  }
149
- const singleMatch = template.match(/^\{\{([^}]+)\}\}$/);
150
- if (singleMatch) {
151
- return resolveExpression(singleMatch[1], context);
669
+ try {
670
+ const singleMatch = template.match(/^\{\{([^}]+)\}\}$/);
671
+ if (singleMatch) {
672
+ return resolveExpression(singleMatch[1], context);
673
+ }
674
+ return resolveTemplate(template, context, componentId);
675
+ } catch (error) {
676
+ if (componentId) {
677
+ analytics.trackError(componentId, "latest", error, {
678
+ errorType: "binding",
679
+ bindingPath: template
680
+ });
681
+ }
682
+ return void 0;
152
683
  }
153
- return resolveTemplate(template, context);
154
684
  }
155
685
  function isPlainObject(value) {
156
686
  return Object.prototype.toString.call(value) === "[object Object]";
@@ -383,6 +913,162 @@ function updateStyles(element, oldStyles, newStyles) {
383
913
  }
384
914
  }
385
915
 
916
+ // src/memorySampler.ts
917
+ var MemorySampler = class {
918
+ constructor() {
919
+ this.isSupported = this.checkSupport();
920
+ }
921
+ /**
922
+ * Check if memory API is available
923
+ */
924
+ checkSupport() {
925
+ if (typeof performance === "undefined") {
926
+ return false;
927
+ }
928
+ const perf = performance;
929
+ return typeof perf.memory !== "undefined";
930
+ }
931
+ /**
932
+ * Check if memory sampling is available
933
+ */
934
+ isAvailable() {
935
+ return this.isSupported;
936
+ }
937
+ /**
938
+ * Take a memory sample
939
+ * Returns null if memory API is not available
940
+ */
941
+ sample() {
942
+ if (!this.isSupported) {
943
+ return null;
944
+ }
945
+ const perf = performance;
946
+ if (!perf.memory) {
947
+ return null;
948
+ }
949
+ return {
950
+ heapUsedKB: Math.round(perf.memory.usedJSHeapSize / 1024),
951
+ heapTotalKB: Math.round(perf.memory.totalJSHeapSize / 1024),
952
+ timestamp: Date.now()
953
+ };
954
+ }
955
+ /**
956
+ * Calculate heap delta between two samples
957
+ * Returns the difference in KB (positive = memory increased)
958
+ */
959
+ calculateDelta(before, after) {
960
+ if (!before || !after) {
961
+ return null;
962
+ }
963
+ return after.heapUsedKB - before.heapUsedKB;
964
+ }
965
+ };
966
+ var memorySamplerInstance = null;
967
+ function getMemorySampler() {
968
+ if (!memorySamplerInstance) {
969
+ memorySamplerInstance = new MemorySampler();
970
+ }
971
+ return memorySamplerInstance;
972
+ }
973
+ function resetMemorySampler() {
974
+ memorySamplerInstance = null;
975
+ }
976
+
977
+ // src/longTaskObserver.ts
978
+ var LongTaskObserver = class {
979
+ constructor() {
980
+ this.observer = null;
981
+ this.longTaskCount = 0;
982
+ this.isObserving = false;
983
+ this.isSupported = this.checkSupport();
984
+ }
985
+ /**
986
+ * Check if PerformanceObserver with longtask support is available
987
+ */
988
+ checkSupport() {
989
+ if (typeof PerformanceObserver === "undefined") {
990
+ return false;
991
+ }
992
+ try {
993
+ const supportedTypes = PerformanceObserver.supportedEntryTypes;
994
+ return Array.isArray(supportedTypes) && supportedTypes.includes("longtask");
995
+ } catch {
996
+ return false;
997
+ }
998
+ }
999
+ /**
1000
+ * Check if long task observation is available
1001
+ */
1002
+ isAvailable() {
1003
+ return this.isSupported;
1004
+ }
1005
+ /**
1006
+ * Start observing long tasks
1007
+ */
1008
+ start() {
1009
+ if (!this.isSupported || this.isObserving) {
1010
+ return;
1011
+ }
1012
+ this.longTaskCount = 0;
1013
+ try {
1014
+ this.observer = new PerformanceObserver((list) => {
1015
+ const entries = list.getEntries();
1016
+ this.longTaskCount += entries.length;
1017
+ });
1018
+ this.observer.observe({ entryTypes: ["longtask"] });
1019
+ this.isObserving = true;
1020
+ } catch {
1021
+ this.isSupported = false;
1022
+ }
1023
+ }
1024
+ /**
1025
+ * Stop observing and return the count of long tasks
1026
+ */
1027
+ stop() {
1028
+ if (!this.isObserving || !this.observer) {
1029
+ return this.longTaskCount;
1030
+ }
1031
+ try {
1032
+ this.observer.disconnect();
1033
+ } catch {
1034
+ }
1035
+ this.observer = null;
1036
+ this.isObserving = false;
1037
+ return this.longTaskCount;
1038
+ }
1039
+ /**
1040
+ * Reset the long task counter
1041
+ */
1042
+ reset() {
1043
+ this.longTaskCount = 0;
1044
+ }
1045
+ /**
1046
+ * Get current count without stopping observation
1047
+ */
1048
+ getCount() {
1049
+ return this.longTaskCount;
1050
+ }
1051
+ /**
1052
+ * Check if currently observing
1053
+ */
1054
+ isActive() {
1055
+ return this.isObserving;
1056
+ }
1057
+ };
1058
+ var longTaskObserverInstance = null;
1059
+ function getLongTaskObserver() {
1060
+ if (!longTaskObserverInstance) {
1061
+ longTaskObserverInstance = new LongTaskObserver();
1062
+ }
1063
+ return longTaskObserverInstance;
1064
+ }
1065
+ function resetLongTaskObserver() {
1066
+ if (longTaskObserverInstance) {
1067
+ longTaskObserverInstance.stop();
1068
+ }
1069
+ longTaskObserverInstance = null;
1070
+ }
1071
+
386
1072
  // src/renderer.ts
387
1073
  var COMPONENT_TO_TAG = {
388
1074
  container: "div",
@@ -636,52 +1322,84 @@ function renderElement(element, tree, context, eventHandlers, elementStates, sta
636
1322
  }
637
1323
  function render(options) {
638
1324
  const { container, elements, context, eventHandlers, componentRegistry, onDependencyNeeded } = options;
639
- const tree = buildTree(elements);
1325
+ const startTime = performance.now();
1326
+ const memorySampler = getMemorySampler();
1327
+ const longTaskObserver = getLongTaskObserver();
1328
+ const memoryBefore = memorySampler.sample();
1329
+ longTaskObserver.start();
640
1330
  const rootElements = elements.filter((el) => !el.parent || el.parent === null);
641
- const state = {
642
- container,
643
- elements,
644
- context,
645
- eventHandlers,
646
- elementStates: /* @__PURE__ */ new Map(),
647
- rootElement: null,
648
- componentRegistry,
649
- onDependencyNeeded,
650
- renderingStack: /* @__PURE__ */ new Set()
651
- };
652
- container.innerHTML = "";
653
- if (rootElements.length === 0) {
654
- return {
1331
+ const componentId = rootElements[0]?.componentId || "unknown";
1332
+ const version = "latest";
1333
+ try {
1334
+ const tree = buildTree(elements);
1335
+ const state = {
1336
+ container,
1337
+ elements,
1338
+ context,
1339
+ eventHandlers,
1340
+ elementStates: /* @__PURE__ */ new Map(),
655
1341
  rootElement: null,
1342
+ componentRegistry,
1343
+ onDependencyNeeded,
1344
+ renderingStack: /* @__PURE__ */ new Set()
1345
+ };
1346
+ container.innerHTML = "";
1347
+ if (rootElements.length === 0) {
1348
+ const duration2 = performance.now() - startTime;
1349
+ const longTasks2 = longTaskObserver.stop();
1350
+ analytics.trackRender(componentId, version, duration2, {
1351
+ elementCount: 0,
1352
+ longTaskCount: longTasks2
1353
+ });
1354
+ return {
1355
+ rootElement: null,
1356
+ update: (newContext) => update(state, newContext),
1357
+ destroy: () => destroy(state)
1358
+ };
1359
+ }
1360
+ if (rootElements.length === 1) {
1361
+ state.rootElement = renderElement(
1362
+ rootElements[0],
1363
+ tree,
1364
+ context,
1365
+ eventHandlers,
1366
+ state.elementStates,
1367
+ state
1368
+ );
1369
+ container.appendChild(state.rootElement);
1370
+ } else {
1371
+ const wrapper = document.createElement("div");
1372
+ wrapper.setAttribute("data-servly-wrapper", "true");
1373
+ for (const root of rootElements) {
1374
+ const rootElement = renderElement(root, tree, context, eventHandlers, state.elementStates, state);
1375
+ wrapper.appendChild(rootElement);
1376
+ }
1377
+ state.rootElement = wrapper;
1378
+ container.appendChild(wrapper);
1379
+ }
1380
+ const duration = performance.now() - startTime;
1381
+ const memoryAfter = memorySampler.sample();
1382
+ const longTasks = longTaskObserver.stop();
1383
+ const heapDelta = memorySampler.calculateDelta(memoryBefore, memoryAfter);
1384
+ analytics.trackRender(componentId, version, duration, {
1385
+ elementCount: elements.length,
1386
+ heapDeltaKB: heapDelta ?? void 0,
1387
+ longTaskCount: longTasks,
1388
+ hasEventHandlers: !!eventHandlers && Object.keys(eventHandlers).length > 0,
1389
+ hasDependencies: !!componentRegistry
1390
+ });
1391
+ return {
1392
+ rootElement: state.rootElement,
656
1393
  update: (newContext) => update(state, newContext),
657
1394
  destroy: () => destroy(state)
658
1395
  };
1396
+ } catch (error) {
1397
+ longTaskObserver.stop();
1398
+ analytics.trackError(componentId, version, error, {
1399
+ errorType: "render"
1400
+ });
1401
+ throw error;
659
1402
  }
660
- if (rootElements.length === 1) {
661
- state.rootElement = renderElement(
662
- rootElements[0],
663
- tree,
664
- context,
665
- eventHandlers,
666
- state.elementStates,
667
- state
668
- );
669
- container.appendChild(state.rootElement);
670
- } else {
671
- const wrapper = document.createElement("div");
672
- wrapper.setAttribute("data-servly-wrapper", "true");
673
- for (const root of rootElements) {
674
- const rootElement = renderElement(root, tree, context, eventHandlers, state.elementStates, state);
675
- wrapper.appendChild(rootElement);
676
- }
677
- state.rootElement = wrapper;
678
- container.appendChild(wrapper);
679
- }
680
- return {
681
- rootElement: state.rootElement,
682
- update: (newContext) => update(state, newContext),
683
- destroy: () => destroy(state)
684
- };
685
1403
  }
686
1404
  function update(state, newContext) {
687
1405
  state.context = newContext;
@@ -1139,6 +1857,7 @@ async function fetchComponent(id, options = {}) {
1139
1857
  ...DEFAULT_RETRY_CONFIG,
1140
1858
  ...retryConfig
1141
1859
  };
1860
+ const startTime = performance.now();
1142
1861
  if (!forceRefresh) {
1143
1862
  const cached = getFromCache(id, version, cacheStrategy, cacheConfig);
1144
1863
  if (cached) {
@@ -1146,6 +1865,11 @@ async function fetchComponent(id, options = {}) {
1146
1865
  if (cached.bundle) {
1147
1866
  registry = buildRegistryFromBundle(cached);
1148
1867
  }
1868
+ const duration = performance.now() - startTime;
1869
+ analytics.trackFetch(id, cached.version, duration, true, {
1870
+ cacheHit: true,
1871
+ fetchDuration: Math.round(duration)
1872
+ });
1149
1873
  return {
1150
1874
  data: cached,
1151
1875
  fromCache: true,
@@ -1192,6 +1916,12 @@ async function fetchComponent(id, options = {}) {
1192
1916
  version: entry.resolved || entry.version
1193
1917
  }));
1194
1918
  }
1919
+ const duration = performance.now() - startTime;
1920
+ analytics.trackFetch(id, resolvedVersion, duration, false, {
1921
+ cacheHit: false,
1922
+ fetchDuration: Math.round(duration),
1923
+ dependencyCount: data.dependencies ? Object.keys(data.dependencies).length : 0
1924
+ });
1195
1925
  return {
1196
1926
  data,
1197
1927
  fromCache: false,
@@ -1210,7 +1940,11 @@ async function fetchComponent(id, options = {}) {
1210
1940
  }
1211
1941
  }
1212
1942
  }
1213
- throw lastError || new Error("Failed to fetch component");
1943
+ const finalError = lastError || new Error("Failed to fetch component");
1944
+ analytics.trackError(id, version, finalError, {
1945
+ errorType: "fetch"
1946
+ });
1947
+ throw finalError;
1214
1948
  }
1215
1949
  async function fetchComponentWithDependencies(id, options = {}) {
1216
1950
  const result = await fetchComponent(id, { ...options, includeBundle: true });
@@ -1688,8 +2422,13 @@ function getSampleValue(def) {
1688
2422
  }
1689
2423
  }
1690
2424
  export {
2425
+ AnalyticsCollector,
1691
2426
  DEFAULT_CACHE_CONFIG,
1692
2427
  DEFAULT_RETRY_CONFIG,
2428
+ LongTaskObserver,
2429
+ MemorySampler,
2430
+ SessionManager,
2431
+ analytics,
1693
2432
  applyStyles,
1694
2433
  batchFetchComponents,
1695
2434
  buildClassName,
@@ -1703,6 +2442,7 @@ export {
1703
2442
  clearStyles,
1704
2443
  collectAllDependencies,
1705
2444
  compareVersions,
2445
+ configureAnalytics,
1706
2446
  createRegistry,
1707
2447
  detectCircularDependencies,
1708
2448
  extractBindingKeys,
@@ -1713,11 +2453,15 @@ export {
1713
2453
  formatStyleValue,
1714
2454
  formatVersion,
1715
2455
  generateTestCases,
2456
+ getAnalytics,
1716
2457
  getCacheKey,
1717
2458
  getDependencyTree,
1718
2459
  getFromCache,
2460
+ getLongTaskObserver,
1719
2461
  getMemoryCacheSize,
2462
+ getMemorySampler,
1720
2463
  getRegistryUrl,
2464
+ getSessionManager,
1721
2465
  hasTemplateSyntax,
1722
2466
  invalidateCache,
1723
2467
  isComponentAvailable,
@@ -1727,6 +2471,10 @@ export {
1727
2471
  processStyles,
1728
2472
  render,
1729
2473
  renderDynamicList,
2474
+ resetAnalytics,
2475
+ resetLongTaskObserver,
2476
+ resetMemorySampler,
2477
+ resetSessionManager,
1730
2478
  resolveBindingPath,
1731
2479
  resolveTemplate,
1732
2480
  resolveTemplateValue,