@servlyadmin/runtime-core 0.1.0 → 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
@@ -1,3 +1,522 @@
1
+ import {
2
+ buildRegistryFromBundle,
3
+ collectAllDependencies,
4
+ createRegistry,
5
+ detectCircularDependencies,
6
+ extractDependencies,
7
+ extractDependenciesFromCode
8
+ } from "./chunk-CIUQK4GA.js";
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
+
1
520
  // src/bindings.ts
2
521
  var BINDING_SOURCES = [
3
522
  "props",
@@ -104,44 +623,64 @@ function resolveTernaryValue(value, context) {
104
623
  if (value === "undefined") return void 0;
105
624
  return resolveExpression(value, context);
106
625
  }
107
- function resolveTemplate(template, context) {
626
+ function resolveTemplate(template, context, componentId) {
108
627
  if (!template || typeof template !== "string") {
109
628
  return template;
110
629
  }
111
630
  if (!hasTemplateSyntax(template)) {
112
631
  return template;
113
632
  }
114
- const singleMatch = template.match(/^\{\{([^}]+)\}\}$/);
115
- if (singleMatch) {
116
- const value = resolveExpression(singleMatch[1], context);
117
- if (value === void 0 || value === null) {
118
- return "";
119
- }
120
- return String(value);
121
- }
122
- return template.replace(TEMPLATE_REGEX, (match, expression) => {
123
- const value = resolveExpression(expression, context);
124
- if (value === void 0 || value === null) {
125
- 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);
126
641
  }
127
- if (typeof value === "object") {
128
- 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
+ });
129
658
  }
130
- return String(value);
131
- });
659
+ return "";
660
+ }
132
661
  }
133
- function resolveTemplateValue(template, context) {
662
+ function resolveTemplateValue(template, context, componentId) {
134
663
  if (!template || typeof template !== "string") {
135
664
  return template;
136
665
  }
137
666
  if (!hasTemplateSyntax(template)) {
138
667
  return template;
139
668
  }
140
- const singleMatch = template.match(/^\{\{([^}]+)\}\}$/);
141
- if (singleMatch) {
142
- 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;
143
683
  }
144
- return resolveTemplate(template, context);
145
684
  }
146
685
  function isPlainObject(value) {
147
686
  return Object.prototype.toString.call(value) === "[object Object]";
@@ -294,17 +833,39 @@ function applyStyles(element, styles) {
294
833
  }
295
834
  }
296
835
  }
836
+ var CANVAS_ONLY_STYLES = /* @__PURE__ */ new Set([
837
+ "transform",
838
+ "--translate-x",
839
+ "--translate-y",
840
+ "--width",
841
+ "--height",
842
+ "z-index",
843
+ "zIndex"
844
+ ]);
845
+ function filterCanvasStyles(style) {
846
+ const filtered = {};
847
+ for (const [key, value] of Object.entries(style)) {
848
+ if (CANVAS_ONLY_STYLES.has(key)) {
849
+ continue;
850
+ }
851
+ if (key === "transform" && typeof value === "string" && value.includes("translate(")) {
852
+ continue;
853
+ }
854
+ filtered[key] = value;
855
+ }
856
+ return filtered;
857
+ }
297
858
  function buildElementStyles(element, context) {
298
859
  const config = element.configuration || {};
299
860
  const combined = {};
300
861
  if (element.style) {
301
- Object.assign(combined, element.style);
862
+ Object.assign(combined, filterCanvasStyles(element.style));
302
863
  }
303
864
  if (config.style) {
304
- Object.assign(combined, config.style);
865
+ Object.assign(combined, filterCanvasStyles(config.style));
305
866
  }
306
867
  if (config.cssVariables) {
307
- Object.assign(combined, config.cssVariables);
868
+ Object.assign(combined, filterCanvasStyles(config.cssVariables));
308
869
  }
309
870
  return processStyles(combined, context);
310
871
  }
@@ -352,6 +913,162 @@ function updateStyles(element, oldStyles, newStyles) {
352
913
  }
353
914
  }
354
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
+
355
1072
  // src/renderer.ts
356
1073
  var COMPONENT_TO_TAG = {
357
1074
  container: "div",
@@ -527,60 +1244,162 @@ function createElement(element, context, eventHandlers) {
527
1244
  attachEventHandlers(domElement, element.i, eventHandlers, elementState);
528
1245
  return elementState;
529
1246
  }
530
- function renderElement(element, tree, context, eventHandlers, elementStates) {
1247
+ var globalRenderingStack = /* @__PURE__ */ new Set();
1248
+ function renderComponentRef(element, container, context, state) {
1249
+ const config = element.configuration;
1250
+ if (!config?.componentViewRef) return void 0;
1251
+ const refId = config.componentViewRef;
1252
+ const refVersion = config.componentViewVersion;
1253
+ if (globalRenderingStack.has(refId)) {
1254
+ console.warn(`Circular dependency detected: ${refId} is already being rendered`);
1255
+ const placeholder = document.createElement("div");
1256
+ placeholder.setAttribute("data-servly-circular", refId);
1257
+ placeholder.textContent = `[Circular: ${refId}]`;
1258
+ container.appendChild(placeholder);
1259
+ return void 0;
1260
+ }
1261
+ let component;
1262
+ if (state.componentRegistry) {
1263
+ component = state.componentRegistry.get(refId, refVersion);
1264
+ }
1265
+ if (!component) {
1266
+ const placeholder = document.createElement("div");
1267
+ placeholder.setAttribute("data-servly-loading", refId);
1268
+ placeholder.className = "servly-loading";
1269
+ container.appendChild(placeholder);
1270
+ if (state.onDependencyNeeded) {
1271
+ state.onDependencyNeeded(refId, refVersion).then((loaded) => {
1272
+ if (loaded && state.componentRegistry) {
1273
+ state.componentRegistry.set(refId, refVersion || "latest", loaded);
1274
+ container.innerHTML = "";
1275
+ renderComponentRef(element, container, context, state);
1276
+ }
1277
+ }).catch((err) => {
1278
+ console.error(`Failed to load dependency ${refId}:`, err);
1279
+ placeholder.textContent = `[Failed to load: ${refId}]`;
1280
+ placeholder.className = "servly-error";
1281
+ });
1282
+ }
1283
+ return void 0;
1284
+ }
1285
+ const refProps = config.componentViewProps ? resolveTemplatesDeep(config.componentViewProps, context) : {};
1286
+ globalRenderingStack.add(refId);
1287
+ const refContext = {
1288
+ props: refProps,
1289
+ state: context.state,
1290
+ context: context.context
1291
+ };
1292
+ try {
1293
+ const result = render({
1294
+ container,
1295
+ elements: component.layout,
1296
+ context: refContext,
1297
+ componentRegistry: state.componentRegistry,
1298
+ onDependencyNeeded: state.onDependencyNeeded
1299
+ });
1300
+ return result;
1301
+ } finally {
1302
+ globalRenderingStack.delete(refId);
1303
+ }
1304
+ }
1305
+ function renderElement(element, tree, context, eventHandlers, elementStates, state) {
531
1306
  const elementState = createElement(element, context, eventHandlers);
532
1307
  elementStates.set(element.i, elementState);
1308
+ const config = element.configuration;
1309
+ if (config?.componentViewRef) {
1310
+ const nestedResult = renderComponentRef(element, elementState.domElement, context, state);
1311
+ if (nestedResult) {
1312
+ elementState.nestedRender = nestedResult;
1313
+ }
1314
+ return elementState.domElement;
1315
+ }
533
1316
  const children = tree.get(element.i) || [];
534
1317
  for (const child of children) {
535
- const childElement = renderElement(child, tree, context, eventHandlers, elementStates);
1318
+ const childElement = renderElement(child, tree, context, eventHandlers, elementStates, state);
536
1319
  elementState.domElement.appendChild(childElement);
537
1320
  }
538
1321
  return elementState.domElement;
539
1322
  }
540
1323
  function render(options) {
541
- const { container, elements, context, eventHandlers } = options;
542
- const tree = buildTree(elements);
1324
+ const { container, elements, context, eventHandlers, componentRegistry, onDependencyNeeded } = options;
1325
+ const startTime = performance.now();
1326
+ const memorySampler = getMemorySampler();
1327
+ const longTaskObserver = getLongTaskObserver();
1328
+ const memoryBefore = memorySampler.sample();
1329
+ longTaskObserver.start();
543
1330
  const rootElements = elements.filter((el) => !el.parent || el.parent === null);
544
- const state = {
545
- container,
546
- elements,
547
- context,
548
- eventHandlers,
549
- elementStates: /* @__PURE__ */ new Map(),
550
- rootElement: null
551
- };
552
- container.innerHTML = "";
553
- if (rootElements.length === 0) {
554
- 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(),
555
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,
556
1393
  update: (newContext) => update(state, newContext),
557
1394
  destroy: () => destroy(state)
558
1395
  };
1396
+ } catch (error) {
1397
+ longTaskObserver.stop();
1398
+ analytics.trackError(componentId, version, error, {
1399
+ errorType: "render"
1400
+ });
1401
+ throw error;
559
1402
  }
560
- if (rootElements.length === 1) {
561
- state.rootElement = renderElement(
562
- rootElements[0],
563
- tree,
564
- context,
565
- eventHandlers,
566
- state.elementStates
567
- );
568
- container.appendChild(state.rootElement);
569
- } else {
570
- const wrapper = document.createElement("div");
571
- wrapper.setAttribute("data-servly-wrapper", "true");
572
- for (const root of rootElements) {
573
- const rootElement = renderElement(root, tree, context, eventHandlers, state.elementStates);
574
- wrapper.appendChild(rootElement);
575
- }
576
- state.rootElement = wrapper;
577
- container.appendChild(wrapper);
578
- }
579
- return {
580
- rootElement: state.rootElement,
581
- update: (newContext) => update(state, newContext),
582
- destroy: () => destroy(state)
583
- };
584
1403
  }
585
1404
  function update(state, newContext) {
586
1405
  state.context = newContext;
@@ -608,6 +1427,9 @@ function update(state, newContext) {
608
1427
  function destroy(state) {
609
1428
  for (const elementState of state.elementStates.values()) {
610
1429
  detachEventHandlers(elementState);
1430
+ if (elementState.nestedRender) {
1431
+ elementState.nestedRender.destroy();
1432
+ }
611
1433
  }
612
1434
  state.elementStates.clear();
613
1435
  if (state.rootElement && state.rootElement.parentNode) {
@@ -615,6 +1437,66 @@ function destroy(state) {
615
1437
  }
616
1438
  state.rootElement = null;
617
1439
  }
1440
+ function renderDynamicList(options) {
1441
+ const {
1442
+ targetContainer,
1443
+ blueprint,
1444
+ blueprintVersion,
1445
+ data,
1446
+ renderType = "renderInto",
1447
+ itemKey = "item",
1448
+ indexKey = "index",
1449
+ componentRegistry,
1450
+ context = { props: {} }
1451
+ } = options;
1452
+ let container;
1453
+ if (typeof targetContainer === "string") {
1454
+ container = document.querySelector(targetContainer);
1455
+ } else {
1456
+ container = targetContainer;
1457
+ }
1458
+ if (!container) {
1459
+ console.error(`renderDynamicList: Container not found: ${targetContainer}`);
1460
+ return [];
1461
+ }
1462
+ const blueprintComponent = componentRegistry.get(blueprint, blueprintVersion);
1463
+ if (!blueprintComponent) {
1464
+ console.error(`renderDynamicList: Blueprint not found: ${blueprint}`);
1465
+ return [];
1466
+ }
1467
+ if (renderType === "renderInto") {
1468
+ container.innerHTML = "";
1469
+ }
1470
+ const results = [];
1471
+ const fragment = document.createDocumentFragment();
1472
+ data.forEach((item, index) => {
1473
+ const itemContainer = document.createElement("div");
1474
+ itemContainer.setAttribute("data-servly-list-item", String(index));
1475
+ const itemContext = {
1476
+ props: {
1477
+ ...context.props,
1478
+ [itemKey]: item,
1479
+ [indexKey]: index
1480
+ },
1481
+ state: context.state,
1482
+ context: context.context
1483
+ };
1484
+ const result = render({
1485
+ container: itemContainer,
1486
+ elements: blueprintComponent.layout,
1487
+ context: itemContext,
1488
+ componentRegistry
1489
+ });
1490
+ results.push(result);
1491
+ fragment.appendChild(itemContainer);
1492
+ });
1493
+ if (renderType === "prepend") {
1494
+ container.insertBefore(fragment, container.firstChild);
1495
+ } else {
1496
+ container.appendChild(fragment);
1497
+ }
1498
+ return results;
1499
+ }
618
1500
 
619
1501
  // src/cache.ts
620
1502
  var DEFAULT_CACHE_CONFIG = {
@@ -860,128 +1742,16 @@ function invalidateCache(id, version, config = DEFAULT_CACHE_CONFIG) {
860
1742
  }
861
1743
  }
862
1744
 
863
- // src/version.ts
864
- function parseVersion(version) {
865
- const match = version.match(/^(\d+)\.(\d+)\.(\d+)$/);
866
- if (!match) return null;
867
- return {
868
- major: parseInt(match[1], 10),
869
- minor: parseInt(match[2], 10),
870
- patch: parseInt(match[3], 10)
871
- };
872
- }
873
- function compareVersions(a, b) {
874
- const parsedA = parseVersion(a);
875
- const parsedB = parseVersion(b);
876
- if (!parsedA || !parsedB) return 0;
877
- if (parsedA.major !== parsedB.major) {
878
- return parsedA.major > parsedB.major ? 1 : -1;
879
- }
880
- if (parsedA.minor !== parsedB.minor) {
881
- return parsedA.minor > parsedB.minor ? 1 : -1;
882
- }
883
- if (parsedA.patch !== parsedB.patch) {
884
- return parsedA.patch > parsedB.patch ? 1 : -1;
885
- }
886
- return 0;
887
- }
888
- function satisfiesVersion(version, specifier) {
889
- if (specifier === "latest" || specifier === "*") {
890
- return true;
891
- }
892
- const parsed = parseVersion(version);
893
- if (!parsed) return false;
894
- if (/^\d+\.\d+\.\d+$/.test(specifier)) {
895
- return version === specifier;
896
- }
897
- if (specifier.startsWith("^")) {
898
- const specParsed = parseVersion(specifier.slice(1));
899
- if (!specParsed) return false;
900
- if (parsed.major !== specParsed.major) return false;
901
- if (parsed.major === 0) {
902
- return parsed.minor === specParsed.minor && parsed.patch >= specParsed.patch;
903
- }
904
- return compareVersions(version, specifier.slice(1)) >= 0;
905
- }
906
- if (specifier.startsWith("~")) {
907
- const specParsed = parseVersion(specifier.slice(1));
908
- if (!specParsed) return false;
909
- return parsed.major === specParsed.major && parsed.minor === specParsed.minor && parsed.patch >= specParsed.patch;
910
- }
911
- if (specifier.startsWith(">=")) {
912
- return compareVersions(version, specifier.slice(2)) >= 0;
913
- }
914
- if (specifier.startsWith(">")) {
915
- return compareVersions(version, specifier.slice(1)) > 0;
916
- }
917
- if (specifier.startsWith("<=")) {
918
- return compareVersions(version, specifier.slice(2)) <= 0;
919
- }
920
- if (specifier.startsWith("<")) {
921
- return compareVersions(version, specifier.slice(1)) < 0;
922
- }
923
- return false;
924
- }
925
- function resolveVersion(versions, specifier = "latest") {
926
- if (versions.length === 0) {
927
- return null;
928
- }
929
- const sorted = [...versions].sort((a, b) => compareVersions(b, a));
930
- if (specifier === "latest" || specifier === "*") {
931
- return sorted[0];
932
- }
933
- if (/^\d+\.\d+\.\d+$/.test(specifier)) {
934
- return versions.includes(specifier) ? specifier : null;
935
- }
936
- for (const version of sorted) {
937
- if (satisfiesVersion(version, specifier)) {
938
- return version;
939
- }
940
- }
941
- return null;
942
- }
943
- function bumpVersion(currentVersion, bumpType) {
944
- const parsed = parseVersion(currentVersion);
945
- if (!parsed) return "1.0.0";
946
- switch (bumpType) {
947
- case "major":
948
- return `${parsed.major + 1}.0.0`;
949
- case "minor":
950
- return `${parsed.major}.${parsed.minor + 1}.0`;
951
- case "patch":
952
- return `${parsed.major}.${parsed.minor}.${parsed.patch + 1}`;
953
- default:
954
- return currentVersion;
955
- }
956
- }
957
- function isValidSpecifier(specifier) {
958
- if (specifier === "latest" || specifier === "*") {
959
- return true;
960
- }
961
- if (/^\d+\.\d+\.\d+$/.test(specifier)) {
962
- return true;
963
- }
964
- if (/^[\^~><]=?\d+\.\d+\.\d+$/.test(specifier)) {
965
- return true;
966
- }
967
- return false;
968
- }
969
- function formatVersion(version) {
970
- const parsed = parseVersion(version);
971
- if (!parsed) return version;
972
- return `v${parsed.major}.${parsed.minor}.${parsed.patch}`;
973
- }
974
-
975
- // src/fetcher.ts
976
- var DEFAULT_RETRY_CONFIG = {
977
- maxRetries: 3,
978
- initialDelay: 1e3,
979
- maxDelay: 1e4,
980
- backoffMultiplier: 2
981
- };
982
- var registryBaseUrl = "/api/components";
983
- function setRegistryUrl(url) {
984
- registryBaseUrl = url;
1745
+ // src/fetcher.ts
1746
+ var DEFAULT_RETRY_CONFIG = {
1747
+ maxRetries: 3,
1748
+ initialDelay: 1e3,
1749
+ maxDelay: 1e4,
1750
+ backoffMultiplier: 2
1751
+ };
1752
+ var registryBaseUrl = "/api/views/registry";
1753
+ function setRegistryUrl(url) {
1754
+ registryBaseUrl = url;
985
1755
  }
986
1756
  function getRegistryUrl() {
987
1757
  if (typeof window !== "undefined") {
@@ -1013,19 +1783,15 @@ async function resolveVersionFromApi(id, specifier, apiKey) {
1013
1783
  }
1014
1784
  try {
1015
1785
  const response = await fetch(
1016
- `${baseUrl}/${id}/versions?specifier=${encodeURIComponent(specifier)}`,
1786
+ `${baseUrl}/${id}/resolve?specifier=${encodeURIComponent(specifier)}`,
1017
1787
  { headers }
1018
1788
  );
1019
1789
  if (!response.ok) {
1020
1790
  throw new Error(`Failed to resolve version: ${response.statusText}`);
1021
1791
  }
1022
1792
  const data = await response.json();
1023
- if (data.success && data.data?.resolvedVersion) {
1024
- return data.data.resolvedVersion;
1025
- }
1026
- if (data.data?.versions) {
1027
- const resolved = resolveVersion(data.data.versions, specifier);
1028
- if (resolved) return resolved;
1793
+ if (data.success && data.data?.resolved) {
1794
+ return data.data.resolved;
1029
1795
  }
1030
1796
  throw new Error(data.error || "Failed to resolve version");
1031
1797
  } catch (error) {
@@ -1033,7 +1799,7 @@ async function resolveVersionFromApi(id, specifier, apiKey) {
1033
1799
  return "latest";
1034
1800
  }
1035
1801
  }
1036
- async function fetchFromRegistry(id, version, apiKey) {
1802
+ async function fetchFromRegistry(id, version, apiKey, includeBundle) {
1037
1803
  const baseUrl = getRegistryUrl();
1038
1804
  const headers = {
1039
1805
  "Content-Type": "application/json"
@@ -1041,25 +1807,39 @@ async function fetchFromRegistry(id, version, apiKey) {
1041
1807
  if (apiKey) {
1042
1808
  headers["Authorization"] = `Bearer ${apiKey}`;
1043
1809
  }
1044
- const url = version && version !== "latest" ? `${baseUrl}/${id}/versions/${version}` : `${baseUrl}/${id}`;
1810
+ let url;
1811
+ if (version && version !== "latest" && /^\d+\.\d+\.\d+/.test(version)) {
1812
+ url = `${baseUrl}/${id}/versions/${version}`;
1813
+ } else if (version && version !== "latest") {
1814
+ url = `${baseUrl}/${id}?version=${encodeURIComponent(version)}`;
1815
+ } else {
1816
+ url = `${baseUrl}/${id}`;
1817
+ }
1818
+ if (includeBundle) {
1819
+ url += (url.includes("?") ? "&" : "?") + "bundle=true";
1820
+ }
1045
1821
  const response = await fetch(url, { headers });
1046
1822
  if (!response.ok) {
1047
1823
  if (response.status === 404) {
1048
- throw new Error(`Component not found: ${id}@${version}`);
1824
+ throw new Error(`View not found: ${id}@${version}`);
1049
1825
  }
1050
1826
  if (response.status === 401) {
1051
1827
  throw new Error("Unauthorized: Invalid or missing API key");
1052
1828
  }
1053
1829
  if (response.status === 403) {
1054
- throw new Error("Forbidden: Access denied to this component");
1830
+ throw new Error("Forbidden: Access denied to this view");
1055
1831
  }
1056
- throw new Error(`Failed to fetch component: ${response.statusText}`);
1832
+ throw new Error(`Failed to fetch view: ${response.statusText}`);
1057
1833
  }
1058
1834
  const data = await response.json();
1059
1835
  if (!data.success || !data.data) {
1060
- throw new Error(data.error || "Failed to fetch component data");
1836
+ throw new Error(data.error || "Failed to fetch view data");
1061
1837
  }
1062
- return data.data;
1838
+ const result = data.data;
1839
+ if (result.viewId && !result.id) {
1840
+ result.id = result.viewId;
1841
+ }
1842
+ return result;
1063
1843
  }
1064
1844
  async function fetchComponent(id, options = {}) {
1065
1845
  const {
@@ -1069,19 +1849,32 @@ async function fetchComponent(id, options = {}) {
1069
1849
  cacheConfig = DEFAULT_CACHE_CONFIG,
1070
1850
  retryConfig = {},
1071
1851
  forceRefresh = false,
1072
- signal
1852
+ signal,
1853
+ bundleStrategy = "eager",
1854
+ includeBundle = true
1073
1855
  } = options;
1074
1856
  const fullRetryConfig = {
1075
1857
  ...DEFAULT_RETRY_CONFIG,
1076
1858
  ...retryConfig
1077
1859
  };
1860
+ const startTime = performance.now();
1078
1861
  if (!forceRefresh) {
1079
1862
  const cached = getFromCache(id, version, cacheStrategy, cacheConfig);
1080
1863
  if (cached) {
1864
+ let registry;
1865
+ if (cached.bundle) {
1866
+ registry = buildRegistryFromBundle(cached);
1867
+ }
1868
+ const duration = performance.now() - startTime;
1869
+ analytics.trackFetch(id, cached.version, duration, true, {
1870
+ cacheHit: true,
1871
+ fetchDuration: Math.round(duration)
1872
+ });
1081
1873
  return {
1082
1874
  data: cached,
1083
1875
  fromCache: true,
1084
- version: cached.version
1876
+ version: cached.version,
1877
+ registry
1085
1878
  };
1086
1879
  }
1087
1880
  }
@@ -1089,28 +1882,52 @@ async function fetchComponent(id, options = {}) {
1089
1882
  if (!forceRefresh && resolvedVersion !== version) {
1090
1883
  const cached = getFromCache(id, resolvedVersion, cacheStrategy, cacheConfig);
1091
1884
  if (cached) {
1885
+ let registry;
1886
+ if (cached.bundle) {
1887
+ registry = buildRegistryFromBundle(cached);
1888
+ }
1092
1889
  return {
1093
1890
  data: cached,
1094
1891
  fromCache: true,
1095
- version: resolvedVersion
1892
+ version: resolvedVersion,
1893
+ registry
1096
1894
  };
1097
1895
  }
1098
1896
  }
1099
1897
  let lastError = null;
1898
+ const shouldIncludeBundle = bundleStrategy !== "none" && includeBundle;
1100
1899
  for (let attempt = 0; attempt <= fullRetryConfig.maxRetries; attempt++) {
1101
1900
  if (signal?.aborted) {
1102
1901
  throw new Error("Fetch aborted");
1103
1902
  }
1104
1903
  try {
1105
- const data = await fetchFromRegistry(id, resolvedVersion, apiKey);
1904
+ const data = await fetchFromRegistry(id, resolvedVersion, apiKey, shouldIncludeBundle);
1106
1905
  setInCache(id, resolvedVersion, data, cacheStrategy, cacheConfig);
1107
1906
  if (version !== resolvedVersion) {
1108
1907
  setInCache(id, version, data, cacheStrategy, cacheConfig);
1109
1908
  }
1909
+ let registry;
1910
+ let pendingDependencies;
1911
+ if (data.bundle) {
1912
+ registry = buildRegistryFromBundle(data);
1913
+ } else if (data.dependencies && bundleStrategy === "lazy") {
1914
+ pendingDependencies = Object.entries(data.dependencies).map(([depId, entry]) => ({
1915
+ id: depId,
1916
+ version: entry.resolved || entry.version
1917
+ }));
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
+ });
1110
1925
  return {
1111
1926
  data,
1112
1927
  fromCache: false,
1113
- version: resolvedVersion
1928
+ version: resolvedVersion,
1929
+ registry,
1930
+ pendingDependencies
1114
1931
  };
1115
1932
  } catch (error) {
1116
1933
  lastError = error instanceof Error ? error : new Error(String(error));
@@ -1123,12 +1940,66 @@ async function fetchComponent(id, options = {}) {
1123
1940
  }
1124
1941
  }
1125
1942
  }
1126
- 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;
1948
+ }
1949
+ async function fetchComponentWithDependencies(id, options = {}) {
1950
+ const result = await fetchComponent(id, { ...options, includeBundle: true });
1951
+ if (result.pendingDependencies && result.pendingDependencies.length > 0) {
1952
+ const { createRegistry: createRegistry2 } = await import("./registry-GCCVK65D.js");
1953
+ const registry = result.registry || createRegistry2();
1954
+ await Promise.all(
1955
+ result.pendingDependencies.map(async (dep) => {
1956
+ try {
1957
+ const depResult = await fetchComponent(dep.id, {
1958
+ ...options,
1959
+ version: dep.version,
1960
+ bundleStrategy: "none"
1961
+ // Don't recursively bundle
1962
+ });
1963
+ registry.set(dep.id, depResult.version, {
1964
+ layout: depResult.data.layout,
1965
+ propsInterface: depResult.data.propsInterface
1966
+ });
1967
+ } catch (err) {
1968
+ console.warn(`Failed to fetch dependency ${dep.id}:`, err);
1969
+ }
1970
+ })
1971
+ );
1972
+ result.registry = registry;
1973
+ result.pendingDependencies = void 0;
1974
+ }
1975
+ return result;
1976
+ }
1977
+ async function batchFetchComponents(components, options = {}) {
1978
+ const baseUrl = getRegistryUrl();
1979
+ const headers = {
1980
+ "Content-Type": "application/json"
1981
+ };
1982
+ if (options.apiKey) {
1983
+ headers["Authorization"] = `Bearer ${options.apiKey}`;
1984
+ }
1985
+ const response = await fetch(`${baseUrl}/batch`, {
1986
+ method: "POST",
1987
+ headers,
1988
+ body: JSON.stringify({ components })
1989
+ });
1990
+ if (!response.ok) {
1991
+ throw new Error(`Batch fetch failed: ${response.statusText}`);
1992
+ }
1993
+ const data = await response.json();
1994
+ if (!data.success || !data.data) {
1995
+ throw new Error(data.error || "Batch fetch failed");
1996
+ }
1997
+ return data.data;
1127
1998
  }
1128
1999
  async function prefetchComponents(ids, options = {}) {
1129
2000
  const promises = ids.map(
1130
2001
  ({ id, version }) => fetchComponent(id, { ...options, version }).catch((error) => {
1131
- console.warn(`Failed to prefetch component ${id}:`, error);
2002
+ console.warn(`Failed to prefetch view ${id}:`, error);
1132
2003
  })
1133
2004
  );
1134
2005
  await Promise.all(promises);
@@ -1137,19 +2008,164 @@ async function isComponentAvailable(id, version = "latest", options = {}) {
1137
2008
  const { cacheStrategy = "memory", cacheConfig = DEFAULT_CACHE_CONFIG } = options;
1138
2009
  const cached = getFromCache(id, version, cacheStrategy, cacheConfig);
1139
2010
  if (cached) {
1140
- return { available: true, cached: true };
2011
+ return { available: true, cached: true, version: cached.version };
2012
+ }
2013
+ const baseUrl = getRegistryUrl();
2014
+ const headers = {
2015
+ "Content-Type": "application/json"
2016
+ };
2017
+ if (options.apiKey) {
2018
+ headers["Authorization"] = `Bearer ${options.apiKey}`;
1141
2019
  }
1142
2020
  try {
1143
- await fetchComponent(id, {
1144
- ...options,
1145
- version,
1146
- retryConfig: { maxRetries: 0 }
1147
- });
1148
- return { available: true, cached: false };
2021
+ const url = version && version !== "latest" ? `${baseUrl}/${id}/available?version=${encodeURIComponent(version)}` : `${baseUrl}/${id}/available`;
2022
+ const response = await fetch(url, { headers });
2023
+ if (!response.ok) {
2024
+ return { available: false, cached: false };
2025
+ }
2026
+ const data = await response.json();
2027
+ if (data.success && data.data) {
2028
+ return data.data;
2029
+ }
2030
+ return { available: false, cached: false };
1149
2031
  } catch {
1150
2032
  return { available: false, cached: false };
1151
2033
  }
1152
2034
  }
2035
+ async function getDependencyTree(id, options = {}) {
2036
+ const baseUrl = getRegistryUrl();
2037
+ const headers = {
2038
+ "Content-Type": "application/json"
2039
+ };
2040
+ if (options.apiKey) {
2041
+ headers["Authorization"] = `Bearer ${options.apiKey}`;
2042
+ }
2043
+ const params = new URLSearchParams();
2044
+ if (options.version) params.set("version", options.version);
2045
+ if (options.depth) params.set("depth", String(options.depth));
2046
+ const url = `${baseUrl}/${id}/dependencies${params.toString() ? "?" + params.toString() : ""}`;
2047
+ const response = await fetch(url, { headers });
2048
+ if (!response.ok) {
2049
+ throw new Error(`Failed to get dependency tree: ${response.statusText}`);
2050
+ }
2051
+ const data = await response.json();
2052
+ if (!data.success || !data.data) {
2053
+ throw new Error(data.error || "Failed to get dependency tree");
2054
+ }
2055
+ return data.data;
2056
+ }
2057
+
2058
+ // src/version.ts
2059
+ function parseVersion(version) {
2060
+ const match = version.match(/^(\d+)\.(\d+)\.(\d+)$/);
2061
+ if (!match) return null;
2062
+ return {
2063
+ major: parseInt(match[1], 10),
2064
+ minor: parseInt(match[2], 10),
2065
+ patch: parseInt(match[3], 10)
2066
+ };
2067
+ }
2068
+ function compareVersions(a, b) {
2069
+ const parsedA = parseVersion(a);
2070
+ const parsedB = parseVersion(b);
2071
+ if (!parsedA || !parsedB) return 0;
2072
+ if (parsedA.major !== parsedB.major) {
2073
+ return parsedA.major > parsedB.major ? 1 : -1;
2074
+ }
2075
+ if (parsedA.minor !== parsedB.minor) {
2076
+ return parsedA.minor > parsedB.minor ? 1 : -1;
2077
+ }
2078
+ if (parsedA.patch !== parsedB.patch) {
2079
+ return parsedA.patch > parsedB.patch ? 1 : -1;
2080
+ }
2081
+ return 0;
2082
+ }
2083
+ function satisfiesVersion(version, specifier) {
2084
+ if (specifier === "latest" || specifier === "*") {
2085
+ return true;
2086
+ }
2087
+ const parsed = parseVersion(version);
2088
+ if (!parsed) return false;
2089
+ if (/^\d+\.\d+\.\d+$/.test(specifier)) {
2090
+ return version === specifier;
2091
+ }
2092
+ if (specifier.startsWith("^")) {
2093
+ const specParsed = parseVersion(specifier.slice(1));
2094
+ if (!specParsed) return false;
2095
+ if (parsed.major !== specParsed.major) return false;
2096
+ if (parsed.major === 0) {
2097
+ return parsed.minor === specParsed.minor && parsed.patch >= specParsed.patch;
2098
+ }
2099
+ return compareVersions(version, specifier.slice(1)) >= 0;
2100
+ }
2101
+ if (specifier.startsWith("~")) {
2102
+ const specParsed = parseVersion(specifier.slice(1));
2103
+ if (!specParsed) return false;
2104
+ return parsed.major === specParsed.major && parsed.minor === specParsed.minor && parsed.patch >= specParsed.patch;
2105
+ }
2106
+ if (specifier.startsWith(">=")) {
2107
+ return compareVersions(version, specifier.slice(2)) >= 0;
2108
+ }
2109
+ if (specifier.startsWith(">")) {
2110
+ return compareVersions(version, specifier.slice(1)) > 0;
2111
+ }
2112
+ if (specifier.startsWith("<=")) {
2113
+ return compareVersions(version, specifier.slice(2)) <= 0;
2114
+ }
2115
+ if (specifier.startsWith("<")) {
2116
+ return compareVersions(version, specifier.slice(1)) < 0;
2117
+ }
2118
+ return false;
2119
+ }
2120
+ function resolveVersion(versions, specifier = "latest") {
2121
+ if (versions.length === 0) {
2122
+ return null;
2123
+ }
2124
+ const sorted = [...versions].sort((a, b) => compareVersions(b, a));
2125
+ if (specifier === "latest" || specifier === "*") {
2126
+ return sorted[0];
2127
+ }
2128
+ if (/^\d+\.\d+\.\d+$/.test(specifier)) {
2129
+ return versions.includes(specifier) ? specifier : null;
2130
+ }
2131
+ for (const version of sorted) {
2132
+ if (satisfiesVersion(version, specifier)) {
2133
+ return version;
2134
+ }
2135
+ }
2136
+ return null;
2137
+ }
2138
+ function bumpVersion(currentVersion, bumpType) {
2139
+ const parsed = parseVersion(currentVersion);
2140
+ if (!parsed) return "1.0.0";
2141
+ switch (bumpType) {
2142
+ case "major":
2143
+ return `${parsed.major + 1}.0.0`;
2144
+ case "minor":
2145
+ return `${parsed.major}.${parsed.minor + 1}.0`;
2146
+ case "patch":
2147
+ return `${parsed.major}.${parsed.minor}.${parsed.patch + 1}`;
2148
+ default:
2149
+ return currentVersion;
2150
+ }
2151
+ }
2152
+ function isValidSpecifier(specifier) {
2153
+ if (specifier === "latest" || specifier === "*") {
2154
+ return true;
2155
+ }
2156
+ if (/^\d+\.\d+\.\d+$/.test(specifier)) {
2157
+ return true;
2158
+ }
2159
+ if (/^[\^~><]=?\d+\.\d+\.\d+$/.test(specifier)) {
2160
+ return true;
2161
+ }
2162
+ return false;
2163
+ }
2164
+ function formatVersion(version) {
2165
+ const parsed = parseVersion(version);
2166
+ if (!parsed) return version;
2167
+ return `v${parsed.major}.${parsed.minor}.${parsed.patch}`;
2168
+ }
1153
2169
 
1154
2170
  // src/testRunner.ts
1155
2171
  function runTestCase(elements, testCase, container) {
@@ -1406,27 +2422,46 @@ function getSampleValue(def) {
1406
2422
  }
1407
2423
  }
1408
2424
  export {
2425
+ AnalyticsCollector,
1409
2426
  DEFAULT_CACHE_CONFIG,
1410
2427
  DEFAULT_RETRY_CONFIG,
2428
+ LongTaskObserver,
2429
+ MemorySampler,
2430
+ SessionManager,
2431
+ analytics,
1411
2432
  applyStyles,
2433
+ batchFetchComponents,
1412
2434
  buildClassName,
1413
2435
  buildElementStyles,
2436
+ buildRegistryFromBundle,
1414
2437
  bumpVersion,
1415
2438
  camelToKebab,
1416
2439
  clearAllCaches,
1417
2440
  clearLocalStorageCache,
1418
2441
  clearMemoryCache,
1419
2442
  clearStyles,
2443
+ collectAllDependencies,
1420
2444
  compareVersions,
2445
+ configureAnalytics,
2446
+ createRegistry,
2447
+ detectCircularDependencies,
1421
2448
  extractBindingKeys,
2449
+ extractDependencies,
2450
+ extractDependenciesFromCode,
1422
2451
  fetchComponent,
2452
+ fetchComponentWithDependencies,
1423
2453
  formatStyleValue,
1424
2454
  formatVersion,
1425
2455
  generateTestCases,
2456
+ getAnalytics,
1426
2457
  getCacheKey,
2458
+ getDependencyTree,
1427
2459
  getFromCache,
2460
+ getLongTaskObserver,
1428
2461
  getMemoryCacheSize,
2462
+ getMemorySampler,
1429
2463
  getRegistryUrl,
2464
+ getSessionManager,
1430
2465
  hasTemplateSyntax,
1431
2466
  invalidateCache,
1432
2467
  isComponentAvailable,
@@ -1435,6 +2470,11 @@ export {
1435
2470
  prefetchComponents,
1436
2471
  processStyles,
1437
2472
  render,
2473
+ renderDynamicList,
2474
+ resetAnalytics,
2475
+ resetLongTaskObserver,
2476
+ resetMemorySampler,
2477
+ resetSessionManager,
1438
2478
  resolveBindingPath,
1439
2479
  resolveTemplate,
1440
2480
  resolveTemplateValue,