@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.cjs CHANGED
@@ -3,6 +3,9 @@ var __defProp = Object.defineProperty;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __esm = (fn, res) => function __init() {
7
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
+ };
6
9
  var __export = (target, all) => {
7
10
  for (var name in all)
8
11
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -17,30 +20,237 @@ var __copyProps = (to, from, except, desc) => {
17
20
  };
18
21
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
22
 
23
+ // src/registry.ts
24
+ var registry_exports = {};
25
+ __export(registry_exports, {
26
+ buildRegistryFromBundle: () => buildRegistryFromBundle,
27
+ collectAllDependencies: () => collectAllDependencies,
28
+ createRegistry: () => createRegistry,
29
+ detectCircularDependencies: () => detectCircularDependencies,
30
+ extractDependencies: () => extractDependencies,
31
+ extractDependenciesFromCode: () => extractDependenciesFromCode
32
+ });
33
+ function createRegistry() {
34
+ const components = /* @__PURE__ */ new Map();
35
+ return {
36
+ get(id, version) {
37
+ if (version) {
38
+ const key = `${id}@${version}`;
39
+ if (components.has(key)) {
40
+ return components.get(key);
41
+ }
42
+ }
43
+ for (const [key, component] of components) {
44
+ if (key.startsWith(`${id}@`)) {
45
+ return component;
46
+ }
47
+ }
48
+ return components.get(id);
49
+ },
50
+ has(id, version) {
51
+ if (version) {
52
+ return components.has(`${id}@${version}`);
53
+ }
54
+ for (const key of components.keys()) {
55
+ if (key.startsWith(`${id}@`) || key === id) {
56
+ return true;
57
+ }
58
+ }
59
+ return false;
60
+ },
61
+ set(id, version, component) {
62
+ components.set(`${id}@${version}`, component);
63
+ }
64
+ };
65
+ }
66
+ function buildRegistryFromBundle(data) {
67
+ const registry = createRegistry();
68
+ registry.set(data.id, data.version, {
69
+ layout: data.layout,
70
+ propsInterface: data.propsInterface
71
+ });
72
+ if (data.bundle) {
73
+ for (const [key, component] of Object.entries(data.bundle)) {
74
+ const [id, version] = key.split("@");
75
+ if (id && version) {
76
+ registry.set(id, version, component);
77
+ }
78
+ }
79
+ }
80
+ return registry;
81
+ }
82
+ function extractDependencies(elements) {
83
+ const dependencies = [];
84
+ for (const element of elements) {
85
+ const config = element.configuration;
86
+ if (!config) continue;
87
+ if (config.componentViewRef) {
88
+ dependencies.push({
89
+ id: config.componentViewRef,
90
+ version: config.componentViewVersion,
91
+ type: "viewRef",
92
+ elementId: element.i
93
+ });
94
+ }
95
+ if (config.blueprint) {
96
+ dependencies.push({
97
+ id: config.blueprint,
98
+ version: config.blueprintVersion,
99
+ type: "blueprint",
100
+ elementId: element.i
101
+ });
102
+ }
103
+ }
104
+ return dependencies;
105
+ }
106
+ function extractDependenciesFromCode(code) {
107
+ const dependencies = [];
108
+ const pattern = /renderDynamicList\s*\(\s*\{[^}]*blueprint\s*:\s*["']([^"']+)["']/g;
109
+ let match;
110
+ while ((match = pattern.exec(code)) !== null) {
111
+ dependencies.push({
112
+ id: match[1],
113
+ type: "blueprint"
114
+ });
115
+ }
116
+ return dependencies;
117
+ }
118
+ async function collectAllDependencies(rootId, rootVersion, fetchComponent2, maxDepth = 10) {
119
+ const manifest = {};
120
+ const visited = /* @__PURE__ */ new Set();
121
+ async function collect(id, version, via, depth) {
122
+ if (depth > maxDepth) {
123
+ console.warn(`Max dependency depth (${maxDepth}) reached for ${id}`);
124
+ return;
125
+ }
126
+ const key = `${id}@${version || "latest"}`;
127
+ if (visited.has(key)) {
128
+ return;
129
+ }
130
+ visited.add(key);
131
+ try {
132
+ const component = await fetchComponent2(id, version);
133
+ if (!component) {
134
+ console.warn(`Dependency not found: ${id}@${version || "latest"}`);
135
+ return;
136
+ }
137
+ manifest[id] = {
138
+ version: version || "latest",
139
+ resolved: component.version,
140
+ type: via ? "viewRef" : "viewRef",
141
+ // Will be set by caller
142
+ via
143
+ };
144
+ const nestedDeps = extractDependencies(component.layout);
145
+ for (const dep of nestedDeps) {
146
+ await collect(dep.id, dep.version, id, depth + 1);
147
+ }
148
+ } catch (error) {
149
+ console.error(`Failed to fetch dependency ${id}:`, error);
150
+ }
151
+ }
152
+ const rootComponent = await fetchComponent2(rootId, rootVersion);
153
+ if (rootComponent) {
154
+ const rootDeps = extractDependencies(rootComponent.layout);
155
+ for (const dep of rootDeps) {
156
+ manifest[dep.id] = {
157
+ version: dep.version || "latest",
158
+ resolved: "",
159
+ // Will be filled when fetched
160
+ type: dep.type
161
+ };
162
+ await collect(dep.id, dep.version, void 0, 1);
163
+ }
164
+ }
165
+ return manifest;
166
+ }
167
+ function detectCircularDependencies(manifest) {
168
+ const graph = /* @__PURE__ */ new Map();
169
+ for (const [id, entry] of Object.entries(manifest)) {
170
+ if (entry.via) {
171
+ const deps = graph.get(entry.via) || [];
172
+ deps.push(id);
173
+ graph.set(entry.via, deps);
174
+ }
175
+ }
176
+ const visited = /* @__PURE__ */ new Set();
177
+ const stack = /* @__PURE__ */ new Set();
178
+ const path = [];
179
+ function dfs(node) {
180
+ if (stack.has(node)) {
181
+ const cycleStart = path.indexOf(node);
182
+ return [...path.slice(cycleStart), node];
183
+ }
184
+ if (visited.has(node)) {
185
+ return null;
186
+ }
187
+ visited.add(node);
188
+ stack.add(node);
189
+ path.push(node);
190
+ const neighbors = graph.get(node) || [];
191
+ for (const neighbor of neighbors) {
192
+ const cycle = dfs(neighbor);
193
+ if (cycle) return cycle;
194
+ }
195
+ stack.delete(node);
196
+ path.pop();
197
+ return null;
198
+ }
199
+ for (const node of graph.keys()) {
200
+ const cycle = dfs(node);
201
+ if (cycle) return cycle;
202
+ }
203
+ return null;
204
+ }
205
+ var init_registry = __esm({
206
+ "src/registry.ts"() {
207
+ "use strict";
208
+ }
209
+ });
210
+
20
211
  // src/index.ts
21
212
  var index_exports = {};
22
213
  __export(index_exports, {
214
+ AnalyticsCollector: () => AnalyticsCollector,
23
215
  DEFAULT_CACHE_CONFIG: () => DEFAULT_CACHE_CONFIG,
24
216
  DEFAULT_RETRY_CONFIG: () => DEFAULT_RETRY_CONFIG,
217
+ LongTaskObserver: () => LongTaskObserver,
218
+ MemorySampler: () => MemorySampler,
219
+ SessionManager: () => SessionManager,
220
+ analytics: () => analytics,
25
221
  applyStyles: () => applyStyles,
222
+ batchFetchComponents: () => batchFetchComponents,
26
223
  buildClassName: () => buildClassName,
27
224
  buildElementStyles: () => buildElementStyles,
225
+ buildRegistryFromBundle: () => buildRegistryFromBundle,
28
226
  bumpVersion: () => bumpVersion,
29
227
  camelToKebab: () => camelToKebab,
30
228
  clearAllCaches: () => clearAllCaches,
31
229
  clearLocalStorageCache: () => clearLocalStorageCache,
32
230
  clearMemoryCache: () => clearMemoryCache,
33
231
  clearStyles: () => clearStyles,
232
+ collectAllDependencies: () => collectAllDependencies,
34
233
  compareVersions: () => compareVersions,
234
+ configureAnalytics: () => configureAnalytics,
235
+ createRegistry: () => createRegistry,
236
+ detectCircularDependencies: () => detectCircularDependencies,
35
237
  extractBindingKeys: () => extractBindingKeys,
238
+ extractDependencies: () => extractDependencies,
239
+ extractDependenciesFromCode: () => extractDependenciesFromCode,
36
240
  fetchComponent: () => fetchComponent,
241
+ fetchComponentWithDependencies: () => fetchComponentWithDependencies,
37
242
  formatStyleValue: () => formatStyleValue,
38
243
  formatVersion: () => formatVersion,
39
244
  generateTestCases: () => generateTestCases,
245
+ getAnalytics: () => getAnalytics,
40
246
  getCacheKey: () => getCacheKey,
247
+ getDependencyTree: () => getDependencyTree,
41
248
  getFromCache: () => getFromCache,
249
+ getLongTaskObserver: () => getLongTaskObserver,
42
250
  getMemoryCacheSize: () => getMemoryCacheSize,
251
+ getMemorySampler: () => getMemorySampler,
43
252
  getRegistryUrl: () => getRegistryUrl,
253
+ getSessionManager: () => getSessionManager,
44
254
  hasTemplateSyntax: () => hasTemplateSyntax,
45
255
  invalidateCache: () => invalidateCache,
46
256
  isComponentAvailable: () => isComponentAvailable,
@@ -49,6 +259,11 @@ __export(index_exports, {
49
259
  prefetchComponents: () => prefetchComponents,
50
260
  processStyles: () => processStyles,
51
261
  render: () => render,
262
+ renderDynamicList: () => renderDynamicList,
263
+ resetAnalytics: () => resetAnalytics,
264
+ resetLongTaskObserver: () => resetLongTaskObserver,
265
+ resetMemorySampler: () => resetMemorySampler,
266
+ resetSessionManager: () => resetSessionManager,
52
267
  resolveBindingPath: () => resolveBindingPath,
53
268
  resolveTemplate: () => resolveTemplate,
54
269
  resolveTemplateValue: () => resolveTemplateValue,
@@ -65,6 +280,516 @@ __export(index_exports, {
65
280
  });
66
281
  module.exports = __toCommonJS(index_exports);
67
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
+
68
793
  // src/bindings.ts
69
794
  var BINDING_SOURCES = [
70
795
  "props",
@@ -171,44 +896,64 @@ function resolveTernaryValue(value, context) {
171
896
  if (value === "undefined") return void 0;
172
897
  return resolveExpression(value, context);
173
898
  }
174
- function resolveTemplate(template, context) {
899
+ function resolveTemplate(template, context, componentId) {
175
900
  if (!template || typeof template !== "string") {
176
901
  return template;
177
902
  }
178
903
  if (!hasTemplateSyntax(template)) {
179
904
  return template;
180
905
  }
181
- const singleMatch = template.match(/^\{\{([^}]+)\}\}$/);
182
- if (singleMatch) {
183
- const value = resolveExpression(singleMatch[1], context);
184
- if (value === void 0 || value === null) {
185
- return "";
186
- }
187
- return String(value);
188
- }
189
- return template.replace(TEMPLATE_REGEX, (match, expression) => {
190
- const value = resolveExpression(expression, context);
191
- if (value === void 0 || value === null) {
192
- 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);
193
914
  }
194
- if (typeof value === "object") {
195
- 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
+ });
196
931
  }
197
- return String(value);
198
- });
932
+ return "";
933
+ }
199
934
  }
200
- function resolveTemplateValue(template, context) {
935
+ function resolveTemplateValue(template, context, componentId) {
201
936
  if (!template || typeof template !== "string") {
202
937
  return template;
203
938
  }
204
939
  if (!hasTemplateSyntax(template)) {
205
940
  return template;
206
941
  }
207
- const singleMatch = template.match(/^\{\{([^}]+)\}\}$/);
208
- if (singleMatch) {
209
- 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;
210
956
  }
211
- return resolveTemplate(template, context);
212
957
  }
213
958
  function isPlainObject(value) {
214
959
  return Object.prototype.toString.call(value) === "[object Object]";
@@ -361,17 +1106,39 @@ function applyStyles(element, styles) {
361
1106
  }
362
1107
  }
363
1108
  }
1109
+ var CANVAS_ONLY_STYLES = /* @__PURE__ */ new Set([
1110
+ "transform",
1111
+ "--translate-x",
1112
+ "--translate-y",
1113
+ "--width",
1114
+ "--height",
1115
+ "z-index",
1116
+ "zIndex"
1117
+ ]);
1118
+ function filterCanvasStyles(style) {
1119
+ const filtered = {};
1120
+ for (const [key, value] of Object.entries(style)) {
1121
+ if (CANVAS_ONLY_STYLES.has(key)) {
1122
+ continue;
1123
+ }
1124
+ if (key === "transform" && typeof value === "string" && value.includes("translate(")) {
1125
+ continue;
1126
+ }
1127
+ filtered[key] = value;
1128
+ }
1129
+ return filtered;
1130
+ }
364
1131
  function buildElementStyles(element, context) {
365
1132
  const config = element.configuration || {};
366
1133
  const combined = {};
367
1134
  if (element.style) {
368
- Object.assign(combined, element.style);
1135
+ Object.assign(combined, filterCanvasStyles(element.style));
369
1136
  }
370
1137
  if (config.style) {
371
- Object.assign(combined, config.style);
1138
+ Object.assign(combined, filterCanvasStyles(config.style));
372
1139
  }
373
1140
  if (config.cssVariables) {
374
- Object.assign(combined, config.cssVariables);
1141
+ Object.assign(combined, filterCanvasStyles(config.cssVariables));
375
1142
  }
376
1143
  return processStyles(combined, context);
377
1144
  }
@@ -419,6 +1186,162 @@ function updateStyles(element, oldStyles, newStyles) {
419
1186
  }
420
1187
  }
421
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
+
422
1345
  // src/renderer.ts
423
1346
  var COMPONENT_TO_TAG = {
424
1347
  container: "div",
@@ -594,60 +1517,162 @@ function createElement(element, context, eventHandlers) {
594
1517
  attachEventHandlers(domElement, element.i, eventHandlers, elementState);
595
1518
  return elementState;
596
1519
  }
597
- function renderElement(element, tree, context, eventHandlers, elementStates) {
1520
+ var globalRenderingStack = /* @__PURE__ */ new Set();
1521
+ function renderComponentRef(element, container, context, state) {
1522
+ const config = element.configuration;
1523
+ if (!config?.componentViewRef) return void 0;
1524
+ const refId = config.componentViewRef;
1525
+ const refVersion = config.componentViewVersion;
1526
+ if (globalRenderingStack.has(refId)) {
1527
+ console.warn(`Circular dependency detected: ${refId} is already being rendered`);
1528
+ const placeholder = document.createElement("div");
1529
+ placeholder.setAttribute("data-servly-circular", refId);
1530
+ placeholder.textContent = `[Circular: ${refId}]`;
1531
+ container.appendChild(placeholder);
1532
+ return void 0;
1533
+ }
1534
+ let component;
1535
+ if (state.componentRegistry) {
1536
+ component = state.componentRegistry.get(refId, refVersion);
1537
+ }
1538
+ if (!component) {
1539
+ const placeholder = document.createElement("div");
1540
+ placeholder.setAttribute("data-servly-loading", refId);
1541
+ placeholder.className = "servly-loading";
1542
+ container.appendChild(placeholder);
1543
+ if (state.onDependencyNeeded) {
1544
+ state.onDependencyNeeded(refId, refVersion).then((loaded) => {
1545
+ if (loaded && state.componentRegistry) {
1546
+ state.componentRegistry.set(refId, refVersion || "latest", loaded);
1547
+ container.innerHTML = "";
1548
+ renderComponentRef(element, container, context, state);
1549
+ }
1550
+ }).catch((err) => {
1551
+ console.error(`Failed to load dependency ${refId}:`, err);
1552
+ placeholder.textContent = `[Failed to load: ${refId}]`;
1553
+ placeholder.className = "servly-error";
1554
+ });
1555
+ }
1556
+ return void 0;
1557
+ }
1558
+ const refProps = config.componentViewProps ? resolveTemplatesDeep(config.componentViewProps, context) : {};
1559
+ globalRenderingStack.add(refId);
1560
+ const refContext = {
1561
+ props: refProps,
1562
+ state: context.state,
1563
+ context: context.context
1564
+ };
1565
+ try {
1566
+ const result = render({
1567
+ container,
1568
+ elements: component.layout,
1569
+ context: refContext,
1570
+ componentRegistry: state.componentRegistry,
1571
+ onDependencyNeeded: state.onDependencyNeeded
1572
+ });
1573
+ return result;
1574
+ } finally {
1575
+ globalRenderingStack.delete(refId);
1576
+ }
1577
+ }
1578
+ function renderElement(element, tree, context, eventHandlers, elementStates, state) {
598
1579
  const elementState = createElement(element, context, eventHandlers);
599
1580
  elementStates.set(element.i, elementState);
1581
+ const config = element.configuration;
1582
+ if (config?.componentViewRef) {
1583
+ const nestedResult = renderComponentRef(element, elementState.domElement, context, state);
1584
+ if (nestedResult) {
1585
+ elementState.nestedRender = nestedResult;
1586
+ }
1587
+ return elementState.domElement;
1588
+ }
600
1589
  const children = tree.get(element.i) || [];
601
1590
  for (const child of children) {
602
- const childElement = renderElement(child, tree, context, eventHandlers, elementStates);
1591
+ const childElement = renderElement(child, tree, context, eventHandlers, elementStates, state);
603
1592
  elementState.domElement.appendChild(childElement);
604
1593
  }
605
1594
  return elementState.domElement;
606
1595
  }
607
1596
  function render(options) {
608
- const { container, elements, context, eventHandlers } = options;
609
- const tree = buildTree(elements);
1597
+ const { container, elements, context, eventHandlers, componentRegistry, onDependencyNeeded } = options;
1598
+ const startTime = performance.now();
1599
+ const memorySampler = getMemorySampler();
1600
+ const longTaskObserver = getLongTaskObserver();
1601
+ const memoryBefore = memorySampler.sample();
1602
+ longTaskObserver.start();
610
1603
  const rootElements = elements.filter((el) => !el.parent || el.parent === null);
611
- const state = {
612
- container,
613
- elements,
614
- context,
615
- eventHandlers,
616
- elementStates: /* @__PURE__ */ new Map(),
617
- rootElement: null
618
- };
619
- container.innerHTML = "";
620
- if (rootElements.length === 0) {
621
- return {
622
- rootElement: null,
623
- update: (newContext) => update(state, newContext),
624
- destroy: () => destroy(state)
625
- };
626
- }
627
- if (rootElements.length === 1) {
628
- state.rootElement = renderElement(
629
- rootElements[0],
630
- tree,
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,
631
1611
  context,
632
1612
  eventHandlers,
633
- state.elementStates
634
- );
635
- container.appendChild(state.rootElement);
636
- } else {
637
- const wrapper = document.createElement("div");
638
- wrapper.setAttribute("data-servly-wrapper", "true");
639
- for (const root of rootElements) {
640
- const rootElement = renderElement(root, tree, context, eventHandlers, state.elementStates);
641
- wrapper.appendChild(rootElement);
1613
+ elementStates: /* @__PURE__ */ new Map(),
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
+ };
642
1632
  }
643
- state.rootElement = wrapper;
644
- container.appendChild(wrapper);
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,
1666
+ update: (newContext) => update(state, newContext),
1667
+ destroy: () => destroy(state)
1668
+ };
1669
+ } catch (error) {
1670
+ longTaskObserver.stop();
1671
+ analytics.trackError(componentId, version, error, {
1672
+ errorType: "render"
1673
+ });
1674
+ throw error;
645
1675
  }
646
- return {
647
- rootElement: state.rootElement,
648
- update: (newContext) => update(state, newContext),
649
- destroy: () => destroy(state)
650
- };
651
1676
  }
652
1677
  function update(state, newContext) {
653
1678
  state.context = newContext;
@@ -675,6 +1700,9 @@ function update(state, newContext) {
675
1700
  function destroy(state) {
676
1701
  for (const elementState of state.elementStates.values()) {
677
1702
  detachEventHandlers(elementState);
1703
+ if (elementState.nestedRender) {
1704
+ elementState.nestedRender.destroy();
1705
+ }
678
1706
  }
679
1707
  state.elementStates.clear();
680
1708
  if (state.rootElement && state.rootElement.parentNode) {
@@ -682,6 +1710,66 @@ function destroy(state) {
682
1710
  }
683
1711
  state.rootElement = null;
684
1712
  }
1713
+ function renderDynamicList(options) {
1714
+ const {
1715
+ targetContainer,
1716
+ blueprint,
1717
+ blueprintVersion,
1718
+ data,
1719
+ renderType = "renderInto",
1720
+ itemKey = "item",
1721
+ indexKey = "index",
1722
+ componentRegistry,
1723
+ context = { props: {} }
1724
+ } = options;
1725
+ let container;
1726
+ if (typeof targetContainer === "string") {
1727
+ container = document.querySelector(targetContainer);
1728
+ } else {
1729
+ container = targetContainer;
1730
+ }
1731
+ if (!container) {
1732
+ console.error(`renderDynamicList: Container not found: ${targetContainer}`);
1733
+ return [];
1734
+ }
1735
+ const blueprintComponent = componentRegistry.get(blueprint, blueprintVersion);
1736
+ if (!blueprintComponent) {
1737
+ console.error(`renderDynamicList: Blueprint not found: ${blueprint}`);
1738
+ return [];
1739
+ }
1740
+ if (renderType === "renderInto") {
1741
+ container.innerHTML = "";
1742
+ }
1743
+ const results = [];
1744
+ const fragment = document.createDocumentFragment();
1745
+ data.forEach((item, index) => {
1746
+ const itemContainer = document.createElement("div");
1747
+ itemContainer.setAttribute("data-servly-list-item", String(index));
1748
+ const itemContext = {
1749
+ props: {
1750
+ ...context.props,
1751
+ [itemKey]: item,
1752
+ [indexKey]: index
1753
+ },
1754
+ state: context.state,
1755
+ context: context.context
1756
+ };
1757
+ const result = render({
1758
+ container: itemContainer,
1759
+ elements: blueprintComponent.layout,
1760
+ context: itemContext,
1761
+ componentRegistry
1762
+ });
1763
+ results.push(result);
1764
+ fragment.appendChild(itemContainer);
1765
+ });
1766
+ if (renderType === "prepend") {
1767
+ container.insertBefore(fragment, container.firstChild);
1768
+ } else {
1769
+ container.appendChild(fragment);
1770
+ }
1771
+ return results;
1772
+ }
685
1773
 
686
1774
  // src/cache.ts
687
1775
  var DEFAULT_CACHE_CONFIG = {
@@ -927,126 +2015,15 @@ function invalidateCache(id, version, config = DEFAULT_CACHE_CONFIG) {
927
2015
  }
928
2016
  }
929
2017
 
930
- // src/version.ts
931
- function parseVersion(version) {
932
- const match = version.match(/^(\d+)\.(\d+)\.(\d+)$/);
933
- if (!match) return null;
934
- return {
935
- major: parseInt(match[1], 10),
936
- minor: parseInt(match[2], 10),
937
- patch: parseInt(match[3], 10)
938
- };
939
- }
940
- function compareVersions(a, b) {
941
- const parsedA = parseVersion(a);
942
- const parsedB = parseVersion(b);
943
- if (!parsedA || !parsedB) return 0;
944
- if (parsedA.major !== parsedB.major) {
945
- return parsedA.major > parsedB.major ? 1 : -1;
946
- }
947
- if (parsedA.minor !== parsedB.minor) {
948
- return parsedA.minor > parsedB.minor ? 1 : -1;
949
- }
950
- if (parsedA.patch !== parsedB.patch) {
951
- return parsedA.patch > parsedB.patch ? 1 : -1;
952
- }
953
- return 0;
954
- }
955
- function satisfiesVersion(version, specifier) {
956
- if (specifier === "latest" || specifier === "*") {
957
- return true;
958
- }
959
- const parsed = parseVersion(version);
960
- if (!parsed) return false;
961
- if (/^\d+\.\d+\.\d+$/.test(specifier)) {
962
- return version === specifier;
963
- }
964
- if (specifier.startsWith("^")) {
965
- const specParsed = parseVersion(specifier.slice(1));
966
- if (!specParsed) return false;
967
- if (parsed.major !== specParsed.major) return false;
968
- if (parsed.major === 0) {
969
- return parsed.minor === specParsed.minor && parsed.patch >= specParsed.patch;
970
- }
971
- return compareVersions(version, specifier.slice(1)) >= 0;
972
- }
973
- if (specifier.startsWith("~")) {
974
- const specParsed = parseVersion(specifier.slice(1));
975
- if (!specParsed) return false;
976
- return parsed.major === specParsed.major && parsed.minor === specParsed.minor && parsed.patch >= specParsed.patch;
977
- }
978
- if (specifier.startsWith(">=")) {
979
- return compareVersions(version, specifier.slice(2)) >= 0;
980
- }
981
- if (specifier.startsWith(">")) {
982
- return compareVersions(version, specifier.slice(1)) > 0;
983
- }
984
- if (specifier.startsWith("<=")) {
985
- return compareVersions(version, specifier.slice(2)) <= 0;
986
- }
987
- if (specifier.startsWith("<")) {
988
- return compareVersions(version, specifier.slice(1)) < 0;
989
- }
990
- return false;
991
- }
992
- function resolveVersion(versions, specifier = "latest") {
993
- if (versions.length === 0) {
994
- return null;
995
- }
996
- const sorted = [...versions].sort((a, b) => compareVersions(b, a));
997
- if (specifier === "latest" || specifier === "*") {
998
- return sorted[0];
999
- }
1000
- if (/^\d+\.\d+\.\d+$/.test(specifier)) {
1001
- return versions.includes(specifier) ? specifier : null;
1002
- }
1003
- for (const version of sorted) {
1004
- if (satisfiesVersion(version, specifier)) {
1005
- return version;
1006
- }
1007
- }
1008
- return null;
1009
- }
1010
- function bumpVersion(currentVersion, bumpType) {
1011
- const parsed = parseVersion(currentVersion);
1012
- if (!parsed) return "1.0.0";
1013
- switch (bumpType) {
1014
- case "major":
1015
- return `${parsed.major + 1}.0.0`;
1016
- case "minor":
1017
- return `${parsed.major}.${parsed.minor + 1}.0`;
1018
- case "patch":
1019
- return `${parsed.major}.${parsed.minor}.${parsed.patch + 1}`;
1020
- default:
1021
- return currentVersion;
1022
- }
1023
- }
1024
- function isValidSpecifier(specifier) {
1025
- if (specifier === "latest" || specifier === "*") {
1026
- return true;
1027
- }
1028
- if (/^\d+\.\d+\.\d+$/.test(specifier)) {
1029
- return true;
1030
- }
1031
- if (/^[\^~><]=?\d+\.\d+\.\d+$/.test(specifier)) {
1032
- return true;
1033
- }
1034
- return false;
1035
- }
1036
- function formatVersion(version) {
1037
- const parsed = parseVersion(version);
1038
- if (!parsed) return version;
1039
- return `v${parsed.major}.${parsed.minor}.${parsed.patch}`;
1040
- }
1041
-
1042
2018
  // src/fetcher.ts
2019
+ init_registry();
1043
2020
  var DEFAULT_RETRY_CONFIG = {
1044
2021
  maxRetries: 3,
1045
2022
  initialDelay: 1e3,
1046
2023
  maxDelay: 1e4,
1047
2024
  backoffMultiplier: 2
1048
2025
  };
1049
- var registryBaseUrl = "/api/components";
2026
+ var registryBaseUrl = "/api/views/registry";
1050
2027
  function setRegistryUrl(url) {
1051
2028
  registryBaseUrl = url;
1052
2029
  }
@@ -1080,19 +2057,15 @@ async function resolveVersionFromApi(id, specifier, apiKey) {
1080
2057
  }
1081
2058
  try {
1082
2059
  const response = await fetch(
1083
- `${baseUrl}/${id}/versions?specifier=${encodeURIComponent(specifier)}`,
2060
+ `${baseUrl}/${id}/resolve?specifier=${encodeURIComponent(specifier)}`,
1084
2061
  { headers }
1085
2062
  );
1086
2063
  if (!response.ok) {
1087
2064
  throw new Error(`Failed to resolve version: ${response.statusText}`);
1088
2065
  }
1089
2066
  const data = await response.json();
1090
- if (data.success && data.data?.resolvedVersion) {
1091
- return data.data.resolvedVersion;
1092
- }
1093
- if (data.data?.versions) {
1094
- const resolved = resolveVersion(data.data.versions, specifier);
1095
- if (resolved) return resolved;
2067
+ if (data.success && data.data?.resolved) {
2068
+ return data.data.resolved;
1096
2069
  }
1097
2070
  throw new Error(data.error || "Failed to resolve version");
1098
2071
  } catch (error) {
@@ -1100,7 +2073,7 @@ async function resolveVersionFromApi(id, specifier, apiKey) {
1100
2073
  return "latest";
1101
2074
  }
1102
2075
  }
1103
- async function fetchFromRegistry(id, version, apiKey) {
2076
+ async function fetchFromRegistry(id, version, apiKey, includeBundle) {
1104
2077
  const baseUrl = getRegistryUrl();
1105
2078
  const headers = {
1106
2079
  "Content-Type": "application/json"
@@ -1108,25 +2081,39 @@ async function fetchFromRegistry(id, version, apiKey) {
1108
2081
  if (apiKey) {
1109
2082
  headers["Authorization"] = `Bearer ${apiKey}`;
1110
2083
  }
1111
- const url = version && version !== "latest" ? `${baseUrl}/${id}/versions/${version}` : `${baseUrl}/${id}`;
2084
+ let url;
2085
+ if (version && version !== "latest" && /^\d+\.\d+\.\d+/.test(version)) {
2086
+ url = `${baseUrl}/${id}/versions/${version}`;
2087
+ } else if (version && version !== "latest") {
2088
+ url = `${baseUrl}/${id}?version=${encodeURIComponent(version)}`;
2089
+ } else {
2090
+ url = `${baseUrl}/${id}`;
2091
+ }
2092
+ if (includeBundle) {
2093
+ url += (url.includes("?") ? "&" : "?") + "bundle=true";
2094
+ }
1112
2095
  const response = await fetch(url, { headers });
1113
2096
  if (!response.ok) {
1114
2097
  if (response.status === 404) {
1115
- throw new Error(`Component not found: ${id}@${version}`);
2098
+ throw new Error(`View not found: ${id}@${version}`);
1116
2099
  }
1117
2100
  if (response.status === 401) {
1118
2101
  throw new Error("Unauthorized: Invalid or missing API key");
1119
2102
  }
1120
2103
  if (response.status === 403) {
1121
- throw new Error("Forbidden: Access denied to this component");
2104
+ throw new Error("Forbidden: Access denied to this view");
1122
2105
  }
1123
- throw new Error(`Failed to fetch component: ${response.statusText}`);
2106
+ throw new Error(`Failed to fetch view: ${response.statusText}`);
1124
2107
  }
1125
2108
  const data = await response.json();
1126
2109
  if (!data.success || !data.data) {
1127
- throw new Error(data.error || "Failed to fetch component data");
2110
+ throw new Error(data.error || "Failed to fetch view data");
1128
2111
  }
1129
- return data.data;
2112
+ const result = data.data;
2113
+ if (result.viewId && !result.id) {
2114
+ result.id = result.viewId;
2115
+ }
2116
+ return result;
1130
2117
  }
1131
2118
  async function fetchComponent(id, options = {}) {
1132
2119
  const {
@@ -1136,19 +2123,32 @@ async function fetchComponent(id, options = {}) {
1136
2123
  cacheConfig = DEFAULT_CACHE_CONFIG,
1137
2124
  retryConfig = {},
1138
2125
  forceRefresh = false,
1139
- signal
2126
+ signal,
2127
+ bundleStrategy = "eager",
2128
+ includeBundle = true
1140
2129
  } = options;
1141
2130
  const fullRetryConfig = {
1142
2131
  ...DEFAULT_RETRY_CONFIG,
1143
2132
  ...retryConfig
1144
2133
  };
2134
+ const startTime = performance.now();
1145
2135
  if (!forceRefresh) {
1146
2136
  const cached = getFromCache(id, version, cacheStrategy, cacheConfig);
1147
2137
  if (cached) {
2138
+ let registry;
2139
+ if (cached.bundle) {
2140
+ registry = buildRegistryFromBundle(cached);
2141
+ }
2142
+ const duration = performance.now() - startTime;
2143
+ analytics.trackFetch(id, cached.version, duration, true, {
2144
+ cacheHit: true,
2145
+ fetchDuration: Math.round(duration)
2146
+ });
1148
2147
  return {
1149
2148
  data: cached,
1150
2149
  fromCache: true,
1151
- version: cached.version
2150
+ version: cached.version,
2151
+ registry
1152
2152
  };
1153
2153
  }
1154
2154
  }
@@ -1156,28 +2156,52 @@ async function fetchComponent(id, options = {}) {
1156
2156
  if (!forceRefresh && resolvedVersion !== version) {
1157
2157
  const cached = getFromCache(id, resolvedVersion, cacheStrategy, cacheConfig);
1158
2158
  if (cached) {
2159
+ let registry;
2160
+ if (cached.bundle) {
2161
+ registry = buildRegistryFromBundle(cached);
2162
+ }
1159
2163
  return {
1160
2164
  data: cached,
1161
2165
  fromCache: true,
1162
- version: resolvedVersion
2166
+ version: resolvedVersion,
2167
+ registry
1163
2168
  };
1164
2169
  }
1165
2170
  }
1166
2171
  let lastError = null;
2172
+ const shouldIncludeBundle = bundleStrategy !== "none" && includeBundle;
1167
2173
  for (let attempt = 0; attempt <= fullRetryConfig.maxRetries; attempt++) {
1168
2174
  if (signal?.aborted) {
1169
2175
  throw new Error("Fetch aborted");
1170
2176
  }
1171
2177
  try {
1172
- const data = await fetchFromRegistry(id, resolvedVersion, apiKey);
2178
+ const data = await fetchFromRegistry(id, resolvedVersion, apiKey, shouldIncludeBundle);
1173
2179
  setInCache(id, resolvedVersion, data, cacheStrategy, cacheConfig);
1174
2180
  if (version !== resolvedVersion) {
1175
2181
  setInCache(id, version, data, cacheStrategy, cacheConfig);
1176
2182
  }
2183
+ let registry;
2184
+ let pendingDependencies;
2185
+ if (data.bundle) {
2186
+ registry = buildRegistryFromBundle(data);
2187
+ } else if (data.dependencies && bundleStrategy === "lazy") {
2188
+ pendingDependencies = Object.entries(data.dependencies).map(([depId, entry]) => ({
2189
+ id: depId,
2190
+ version: entry.resolved || entry.version
2191
+ }));
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
+ });
1177
2199
  return {
1178
2200
  data,
1179
2201
  fromCache: false,
1180
- version: resolvedVersion
2202
+ version: resolvedVersion,
2203
+ registry,
2204
+ pendingDependencies
1181
2205
  };
1182
2206
  } catch (error) {
1183
2207
  lastError = error instanceof Error ? error : new Error(String(error));
@@ -1190,12 +2214,66 @@ async function fetchComponent(id, options = {}) {
1190
2214
  }
1191
2215
  }
1192
2216
  }
1193
- 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;
2222
+ }
2223
+ async function fetchComponentWithDependencies(id, options = {}) {
2224
+ const result = await fetchComponent(id, { ...options, includeBundle: true });
2225
+ if (result.pendingDependencies && result.pendingDependencies.length > 0) {
2226
+ const { createRegistry: createRegistry2 } = await Promise.resolve().then(() => (init_registry(), registry_exports));
2227
+ const registry = result.registry || createRegistry2();
2228
+ await Promise.all(
2229
+ result.pendingDependencies.map(async (dep) => {
2230
+ try {
2231
+ const depResult = await fetchComponent(dep.id, {
2232
+ ...options,
2233
+ version: dep.version,
2234
+ bundleStrategy: "none"
2235
+ // Don't recursively bundle
2236
+ });
2237
+ registry.set(dep.id, depResult.version, {
2238
+ layout: depResult.data.layout,
2239
+ propsInterface: depResult.data.propsInterface
2240
+ });
2241
+ } catch (err) {
2242
+ console.warn(`Failed to fetch dependency ${dep.id}:`, err);
2243
+ }
2244
+ })
2245
+ );
2246
+ result.registry = registry;
2247
+ result.pendingDependencies = void 0;
2248
+ }
2249
+ return result;
2250
+ }
2251
+ async function batchFetchComponents(components, options = {}) {
2252
+ const baseUrl = getRegistryUrl();
2253
+ const headers = {
2254
+ "Content-Type": "application/json"
2255
+ };
2256
+ if (options.apiKey) {
2257
+ headers["Authorization"] = `Bearer ${options.apiKey}`;
2258
+ }
2259
+ const response = await fetch(`${baseUrl}/batch`, {
2260
+ method: "POST",
2261
+ headers,
2262
+ body: JSON.stringify({ components })
2263
+ });
2264
+ if (!response.ok) {
2265
+ throw new Error(`Batch fetch failed: ${response.statusText}`);
2266
+ }
2267
+ const data = await response.json();
2268
+ if (!data.success || !data.data) {
2269
+ throw new Error(data.error || "Batch fetch failed");
2270
+ }
2271
+ return data.data;
1194
2272
  }
1195
2273
  async function prefetchComponents(ids, options = {}) {
1196
2274
  const promises = ids.map(
1197
2275
  ({ id, version }) => fetchComponent(id, { ...options, version }).catch((error) => {
1198
- console.warn(`Failed to prefetch component ${id}:`, error);
2276
+ console.warn(`Failed to prefetch view ${id}:`, error);
1199
2277
  })
1200
2278
  );
1201
2279
  await Promise.all(promises);
@@ -1204,19 +2282,164 @@ async function isComponentAvailable(id, version = "latest", options = {}) {
1204
2282
  const { cacheStrategy = "memory", cacheConfig = DEFAULT_CACHE_CONFIG } = options;
1205
2283
  const cached = getFromCache(id, version, cacheStrategy, cacheConfig);
1206
2284
  if (cached) {
1207
- return { available: true, cached: true };
2285
+ return { available: true, cached: true, version: cached.version };
2286
+ }
2287
+ const baseUrl = getRegistryUrl();
2288
+ const headers = {
2289
+ "Content-Type": "application/json"
2290
+ };
2291
+ if (options.apiKey) {
2292
+ headers["Authorization"] = `Bearer ${options.apiKey}`;
1208
2293
  }
1209
2294
  try {
1210
- await fetchComponent(id, {
1211
- ...options,
1212
- version,
1213
- retryConfig: { maxRetries: 0 }
1214
- });
1215
- return { available: true, cached: false };
2295
+ const url = version && version !== "latest" ? `${baseUrl}/${id}/available?version=${encodeURIComponent(version)}` : `${baseUrl}/${id}/available`;
2296
+ const response = await fetch(url, { headers });
2297
+ if (!response.ok) {
2298
+ return { available: false, cached: false };
2299
+ }
2300
+ const data = await response.json();
2301
+ if (data.success && data.data) {
2302
+ return data.data;
2303
+ }
2304
+ return { available: false, cached: false };
1216
2305
  } catch {
1217
2306
  return { available: false, cached: false };
1218
2307
  }
1219
2308
  }
2309
+ async function getDependencyTree(id, options = {}) {
2310
+ const baseUrl = getRegistryUrl();
2311
+ const headers = {
2312
+ "Content-Type": "application/json"
2313
+ };
2314
+ if (options.apiKey) {
2315
+ headers["Authorization"] = `Bearer ${options.apiKey}`;
2316
+ }
2317
+ const params = new URLSearchParams();
2318
+ if (options.version) params.set("version", options.version);
2319
+ if (options.depth) params.set("depth", String(options.depth));
2320
+ const url = `${baseUrl}/${id}/dependencies${params.toString() ? "?" + params.toString() : ""}`;
2321
+ const response = await fetch(url, { headers });
2322
+ if (!response.ok) {
2323
+ throw new Error(`Failed to get dependency tree: ${response.statusText}`);
2324
+ }
2325
+ const data = await response.json();
2326
+ if (!data.success || !data.data) {
2327
+ throw new Error(data.error || "Failed to get dependency tree");
2328
+ }
2329
+ return data.data;
2330
+ }
2331
+
2332
+ // src/version.ts
2333
+ function parseVersion(version) {
2334
+ const match = version.match(/^(\d+)\.(\d+)\.(\d+)$/);
2335
+ if (!match) return null;
2336
+ return {
2337
+ major: parseInt(match[1], 10),
2338
+ minor: parseInt(match[2], 10),
2339
+ patch: parseInt(match[3], 10)
2340
+ };
2341
+ }
2342
+ function compareVersions(a, b) {
2343
+ const parsedA = parseVersion(a);
2344
+ const parsedB = parseVersion(b);
2345
+ if (!parsedA || !parsedB) return 0;
2346
+ if (parsedA.major !== parsedB.major) {
2347
+ return parsedA.major > parsedB.major ? 1 : -1;
2348
+ }
2349
+ if (parsedA.minor !== parsedB.minor) {
2350
+ return parsedA.minor > parsedB.minor ? 1 : -1;
2351
+ }
2352
+ if (parsedA.patch !== parsedB.patch) {
2353
+ return parsedA.patch > parsedB.patch ? 1 : -1;
2354
+ }
2355
+ return 0;
2356
+ }
2357
+ function satisfiesVersion(version, specifier) {
2358
+ if (specifier === "latest" || specifier === "*") {
2359
+ return true;
2360
+ }
2361
+ const parsed = parseVersion(version);
2362
+ if (!parsed) return false;
2363
+ if (/^\d+\.\d+\.\d+$/.test(specifier)) {
2364
+ return version === specifier;
2365
+ }
2366
+ if (specifier.startsWith("^")) {
2367
+ const specParsed = parseVersion(specifier.slice(1));
2368
+ if (!specParsed) return false;
2369
+ if (parsed.major !== specParsed.major) return false;
2370
+ if (parsed.major === 0) {
2371
+ return parsed.minor === specParsed.minor && parsed.patch >= specParsed.patch;
2372
+ }
2373
+ return compareVersions(version, specifier.slice(1)) >= 0;
2374
+ }
2375
+ if (specifier.startsWith("~")) {
2376
+ const specParsed = parseVersion(specifier.slice(1));
2377
+ if (!specParsed) return false;
2378
+ return parsed.major === specParsed.major && parsed.minor === specParsed.minor && parsed.patch >= specParsed.patch;
2379
+ }
2380
+ if (specifier.startsWith(">=")) {
2381
+ return compareVersions(version, specifier.slice(2)) >= 0;
2382
+ }
2383
+ if (specifier.startsWith(">")) {
2384
+ return compareVersions(version, specifier.slice(1)) > 0;
2385
+ }
2386
+ if (specifier.startsWith("<=")) {
2387
+ return compareVersions(version, specifier.slice(2)) <= 0;
2388
+ }
2389
+ if (specifier.startsWith("<")) {
2390
+ return compareVersions(version, specifier.slice(1)) < 0;
2391
+ }
2392
+ return false;
2393
+ }
2394
+ function resolveVersion(versions, specifier = "latest") {
2395
+ if (versions.length === 0) {
2396
+ return null;
2397
+ }
2398
+ const sorted = [...versions].sort((a, b) => compareVersions(b, a));
2399
+ if (specifier === "latest" || specifier === "*") {
2400
+ return sorted[0];
2401
+ }
2402
+ if (/^\d+\.\d+\.\d+$/.test(specifier)) {
2403
+ return versions.includes(specifier) ? specifier : null;
2404
+ }
2405
+ for (const version of sorted) {
2406
+ if (satisfiesVersion(version, specifier)) {
2407
+ return version;
2408
+ }
2409
+ }
2410
+ return null;
2411
+ }
2412
+ function bumpVersion(currentVersion, bumpType) {
2413
+ const parsed = parseVersion(currentVersion);
2414
+ if (!parsed) return "1.0.0";
2415
+ switch (bumpType) {
2416
+ case "major":
2417
+ return `${parsed.major + 1}.0.0`;
2418
+ case "minor":
2419
+ return `${parsed.major}.${parsed.minor + 1}.0`;
2420
+ case "patch":
2421
+ return `${parsed.major}.${parsed.minor}.${parsed.patch + 1}`;
2422
+ default:
2423
+ return currentVersion;
2424
+ }
2425
+ }
2426
+ function isValidSpecifier(specifier) {
2427
+ if (specifier === "latest" || specifier === "*") {
2428
+ return true;
2429
+ }
2430
+ if (/^\d+\.\d+\.\d+$/.test(specifier)) {
2431
+ return true;
2432
+ }
2433
+ if (/^[\^~><]=?\d+\.\d+\.\d+$/.test(specifier)) {
2434
+ return true;
2435
+ }
2436
+ return false;
2437
+ }
2438
+ function formatVersion(version) {
2439
+ const parsed = parseVersion(version);
2440
+ if (!parsed) return version;
2441
+ return `v${parsed.major}.${parsed.minor}.${parsed.patch}`;
2442
+ }
1220
2443
 
1221
2444
  // src/testRunner.ts
1222
2445
  function runTestCase(elements, testCase, container) {
@@ -1472,29 +2695,51 @@ function getSampleValue(def) {
1472
2695
  return def.defaultValue;
1473
2696
  }
1474
2697
  }
2698
+
2699
+ // src/index.ts
2700
+ init_registry();
1475
2701
  // Annotate the CommonJS export names for ESM import in node:
1476
2702
  0 && (module.exports = {
2703
+ AnalyticsCollector,
1477
2704
  DEFAULT_CACHE_CONFIG,
1478
2705
  DEFAULT_RETRY_CONFIG,
2706
+ LongTaskObserver,
2707
+ MemorySampler,
2708
+ SessionManager,
2709
+ analytics,
1479
2710
  applyStyles,
2711
+ batchFetchComponents,
1480
2712
  buildClassName,
1481
2713
  buildElementStyles,
2714
+ buildRegistryFromBundle,
1482
2715
  bumpVersion,
1483
2716
  camelToKebab,
1484
2717
  clearAllCaches,
1485
2718
  clearLocalStorageCache,
1486
2719
  clearMemoryCache,
1487
2720
  clearStyles,
2721
+ collectAllDependencies,
1488
2722
  compareVersions,
2723
+ configureAnalytics,
2724
+ createRegistry,
2725
+ detectCircularDependencies,
1489
2726
  extractBindingKeys,
2727
+ extractDependencies,
2728
+ extractDependenciesFromCode,
1490
2729
  fetchComponent,
2730
+ fetchComponentWithDependencies,
1491
2731
  formatStyleValue,
1492
2732
  formatVersion,
1493
2733
  generateTestCases,
2734
+ getAnalytics,
1494
2735
  getCacheKey,
2736
+ getDependencyTree,
1495
2737
  getFromCache,
2738
+ getLongTaskObserver,
1496
2739
  getMemoryCacheSize,
2740
+ getMemorySampler,
1497
2741
  getRegistryUrl,
2742
+ getSessionManager,
1498
2743
  hasTemplateSyntax,
1499
2744
  invalidateCache,
1500
2745
  isComponentAvailable,
@@ -1503,6 +2748,11 @@ function getSampleValue(def) {
1503
2748
  prefetchComponents,
1504
2749
  processStyles,
1505
2750
  render,
2751
+ renderDynamicList,
2752
+ resetAnalytics,
2753
+ resetLongTaskObserver,
2754
+ resetMemorySampler,
2755
+ resetSessionManager,
1506
2756
  resolveBindingPath,
1507
2757
  resolveTemplate,
1508
2758
  resolveTemplateValue,