@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/chunk-CIUQK4GA.js +182 -0
- package/dist/index.cjs +1456 -206
- package/dist/index.d.cts +629 -7
- package/dist/index.d.ts +629 -7
- package/dist/index.js +1254 -214
- package/dist/registry-GCCVK65D.js +16 -0
- package/package.json +2 -2
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
128
|
-
|
|
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
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
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/
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
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}/
|
|
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?.
|
|
1024
|
-
return data.data.
|
|
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
|
-
|
|
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(`
|
|
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
|
|
1830
|
+
throw new Error("Forbidden: Access denied to this view");
|
|
1055
1831
|
}
|
|
1056
|
-
throw new Error(`Failed to fetch
|
|
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
|
|
1836
|
+
throw new Error(data.error || "Failed to fetch view data");
|
|
1061
1837
|
}
|
|
1062
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
}
|
|
1148
|
-
|
|
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,
|