@servlyadmin/runtime-core 0.1.0

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 ADDED
@@ -0,0 +1,1519 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ DEFAULT_CACHE_CONFIG: () => DEFAULT_CACHE_CONFIG,
24
+ DEFAULT_RETRY_CONFIG: () => DEFAULT_RETRY_CONFIG,
25
+ applyStyles: () => applyStyles,
26
+ buildClassName: () => buildClassName,
27
+ buildElementStyles: () => buildElementStyles,
28
+ bumpVersion: () => bumpVersion,
29
+ camelToKebab: () => camelToKebab,
30
+ clearAllCaches: () => clearAllCaches,
31
+ clearLocalStorageCache: () => clearLocalStorageCache,
32
+ clearMemoryCache: () => clearMemoryCache,
33
+ clearStyles: () => clearStyles,
34
+ compareVersions: () => compareVersions,
35
+ extractBindingKeys: () => extractBindingKeys,
36
+ fetchComponent: () => fetchComponent,
37
+ formatStyleValue: () => formatStyleValue,
38
+ formatVersion: () => formatVersion,
39
+ generateTestCases: () => generateTestCases,
40
+ getCacheKey: () => getCacheKey,
41
+ getFromCache: () => getFromCache,
42
+ getMemoryCacheSize: () => getMemoryCacheSize,
43
+ getRegistryUrl: () => getRegistryUrl,
44
+ hasTemplateSyntax: () => hasTemplateSyntax,
45
+ invalidateCache: () => invalidateCache,
46
+ isComponentAvailable: () => isComponentAvailable,
47
+ isValidSpecifier: () => isValidSpecifier,
48
+ parseVersion: () => parseVersion,
49
+ prefetchComponents: () => prefetchComponents,
50
+ processStyles: () => processStyles,
51
+ render: () => render,
52
+ resolveBindingPath: () => resolveBindingPath,
53
+ resolveTemplate: () => resolveTemplate,
54
+ resolveTemplateValue: () => resolveTemplateValue,
55
+ resolveTemplatesDeep: () => resolveTemplatesDeep,
56
+ resolveVersion: () => resolveVersion,
57
+ runAllTests: () => runAllTests,
58
+ runTestCase: () => runTestCase,
59
+ satisfiesVersion: () => satisfiesVersion,
60
+ setInCache: () => setInCache,
61
+ setRegistryUrl: () => setRegistryUrl,
62
+ updateStyles: () => updateStyles,
63
+ validateAssertion: () => validateAssertion,
64
+ validateProps: () => validateProps
65
+ });
66
+ module.exports = __toCommonJS(index_exports);
67
+
68
+ // src/bindings.ts
69
+ var BINDING_SOURCES = [
70
+ "props",
71
+ "state",
72
+ "appState",
73
+ "context",
74
+ "input",
75
+ "currentItem",
76
+ "localStore",
77
+ "config",
78
+ "element",
79
+ "self",
80
+ "params",
81
+ "query"
82
+ ];
83
+ var TEMPLATE_REGEX = /\{\{([^}]+)\}\}/g;
84
+ function hasTemplateSyntax(value) {
85
+ return typeof value === "string" && value.includes("{{") && value.includes("}}");
86
+ }
87
+ function resolveBindingPath(path, context) {
88
+ const trimmed = path.trim();
89
+ const parts = trimmed.split(".");
90
+ if (parts.length === 0) {
91
+ return void 0;
92
+ }
93
+ const prefix = parts[0].toLowerCase();
94
+ let source;
95
+ let startIndex = 0;
96
+ if (prefix === "props" || prefix === "input") {
97
+ source = context.props;
98
+ startIndex = 1;
99
+ } else if (prefix === "state" || prefix === "appstate") {
100
+ source = context.state;
101
+ startIndex = 1;
102
+ } else if (prefix === "context") {
103
+ source = context.context;
104
+ startIndex = 1;
105
+ } else if (BINDING_SOURCES.includes(prefix)) {
106
+ source = context.props;
107
+ startIndex = 1;
108
+ } else {
109
+ source = context.props;
110
+ startIndex = 0;
111
+ }
112
+ if (!source) {
113
+ return void 0;
114
+ }
115
+ let value = source;
116
+ for (let i = startIndex; i < parts.length; i++) {
117
+ if (value === null || value === void 0) {
118
+ return void 0;
119
+ }
120
+ value = value[parts[i]];
121
+ }
122
+ return value;
123
+ }
124
+ function resolveExpression(expression, context) {
125
+ const trimmed = expression.trim();
126
+ const defaultMatch = trimmed.match(/^(.+?)\|\|(.+)$/);
127
+ if (defaultMatch) {
128
+ const mainValue = resolveExpression(defaultMatch[1].trim(), context);
129
+ if (mainValue !== void 0 && mainValue !== null && mainValue !== "") {
130
+ return mainValue;
131
+ }
132
+ const defaultVal = defaultMatch[2].trim();
133
+ if (defaultVal.startsWith('"') && defaultVal.endsWith('"') || defaultVal.startsWith("'") && defaultVal.endsWith("'")) {
134
+ return defaultVal.slice(1, -1);
135
+ }
136
+ if (!isNaN(Number(defaultVal))) {
137
+ return Number(defaultVal);
138
+ }
139
+ if (defaultVal === "true") return true;
140
+ if (defaultVal === "false") return false;
141
+ return resolveExpression(defaultVal, context);
142
+ }
143
+ const ternaryMatch = trimmed.match(/^(.+?)\s*\?\s*(.+?)\s*:\s*(.+)$/);
144
+ if (ternaryMatch) {
145
+ const condition = resolveExpression(ternaryMatch[1].trim(), context);
146
+ if (condition) {
147
+ return resolveTernaryValue(ternaryMatch[2].trim(), context);
148
+ }
149
+ return resolveTernaryValue(ternaryMatch[3].trim(), context);
150
+ }
151
+ if (trimmed.includes("&&")) {
152
+ const parts = trimmed.split("&&");
153
+ for (const part of parts) {
154
+ const value = resolveExpression(part.trim(), context);
155
+ if (!value) return value;
156
+ }
157
+ return resolveExpression(parts[parts.length - 1].trim(), context);
158
+ }
159
+ return resolveBindingPath(trimmed, context);
160
+ }
161
+ function resolveTernaryValue(value, context) {
162
+ if (value.startsWith("'") && value.endsWith("'") || value.startsWith('"') && value.endsWith('"')) {
163
+ return value.slice(1, -1);
164
+ }
165
+ if (!isNaN(Number(value))) {
166
+ return Number(value);
167
+ }
168
+ if (value === "true") return true;
169
+ if (value === "false") return false;
170
+ if (value === "null") return null;
171
+ if (value === "undefined") return void 0;
172
+ return resolveExpression(value, context);
173
+ }
174
+ function resolveTemplate(template, context) {
175
+ if (!template || typeof template !== "string") {
176
+ return template;
177
+ }
178
+ if (!hasTemplateSyntax(template)) {
179
+ return template;
180
+ }
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 "";
193
+ }
194
+ if (typeof value === "object") {
195
+ return JSON.stringify(value);
196
+ }
197
+ return String(value);
198
+ });
199
+ }
200
+ function resolveTemplateValue(template, context) {
201
+ if (!template || typeof template !== "string") {
202
+ return template;
203
+ }
204
+ if (!hasTemplateSyntax(template)) {
205
+ return template;
206
+ }
207
+ const singleMatch = template.match(/^\{\{([^}]+)\}\}$/);
208
+ if (singleMatch) {
209
+ return resolveExpression(singleMatch[1], context);
210
+ }
211
+ return resolveTemplate(template, context);
212
+ }
213
+ function isPlainObject(value) {
214
+ return Object.prototype.toString.call(value) === "[object Object]";
215
+ }
216
+ function resolveTemplatesDeep(input, context) {
217
+ if (typeof input === "string") {
218
+ return resolveTemplateValue(input, context);
219
+ }
220
+ if (Array.isArray(input)) {
221
+ let changed = false;
222
+ const result = input.map((item) => {
223
+ const resolved = resolveTemplatesDeep(item, context);
224
+ if (resolved !== item) {
225
+ changed = true;
226
+ }
227
+ return resolved;
228
+ });
229
+ return changed ? result : input;
230
+ }
231
+ if (input && typeof input === "object") {
232
+ if (!isPlainObject(input)) {
233
+ return input;
234
+ }
235
+ let mutable = null;
236
+ const original = input;
237
+ for (const key of Object.keys(original)) {
238
+ const current = original[key];
239
+ const resolved = resolveTemplatesDeep(current, context);
240
+ if (resolved !== current) {
241
+ const target = mutable ?? (mutable = { ...original });
242
+ target[key] = resolved;
243
+ }
244
+ }
245
+ return mutable ?? input;
246
+ }
247
+ return input;
248
+ }
249
+ function extractBindingKeys(template) {
250
+ if (!template || typeof template !== "string") {
251
+ return [];
252
+ }
253
+ const keys = /* @__PURE__ */ new Set();
254
+ const matches = template.matchAll(TEMPLATE_REGEX);
255
+ for (const match of matches) {
256
+ const expression = match[1].trim();
257
+ const pathMatch = expression.match(/^([a-zA-Z_][a-zA-Z0-9_.]*)/);
258
+ if (pathMatch) {
259
+ const parts = pathMatch[1].split(".");
260
+ if (parts.length > 1 && BINDING_SOURCES.includes(parts[0].toLowerCase())) {
261
+ keys.add(parts[parts.length - 1]);
262
+ } else {
263
+ keys.add(parts[0]);
264
+ }
265
+ }
266
+ }
267
+ return Array.from(keys);
268
+ }
269
+
270
+ // src/styles.ts
271
+ var UNITLESS_PROPERTIES = /* @__PURE__ */ new Set([
272
+ "animationIterationCount",
273
+ "borderImageOutset",
274
+ "borderImageSlice",
275
+ "borderImageWidth",
276
+ "boxFlex",
277
+ "boxFlexGroup",
278
+ "boxOrdinalGroup",
279
+ "columnCount",
280
+ "columns",
281
+ "flex",
282
+ "flexGrow",
283
+ "flexPositive",
284
+ "flexShrink",
285
+ "flexNegative",
286
+ "flexOrder",
287
+ "gridRow",
288
+ "gridRowEnd",
289
+ "gridRowSpan",
290
+ "gridRowStart",
291
+ "gridColumn",
292
+ "gridColumnEnd",
293
+ "gridColumnSpan",
294
+ "gridColumnStart",
295
+ "fontWeight",
296
+ "lineClamp",
297
+ "lineHeight",
298
+ "opacity",
299
+ "order",
300
+ "orphans",
301
+ "tabSize",
302
+ "widows",
303
+ "zIndex",
304
+ "zoom",
305
+ "fillOpacity",
306
+ "floodOpacity",
307
+ "stopOpacity",
308
+ "strokeDasharray",
309
+ "strokeDashoffset",
310
+ "strokeMiterlimit",
311
+ "strokeOpacity",
312
+ "strokeWidth"
313
+ ]);
314
+ function camelToKebab(str) {
315
+ return str.replace(/([A-Z])/g, "-$1").toLowerCase();
316
+ }
317
+ function needsUnits(property) {
318
+ if (property.startsWith("--")) {
319
+ return false;
320
+ }
321
+ return !UNITLESS_PROPERTIES.has(property);
322
+ }
323
+ function formatStyleValue(property, value) {
324
+ if (value === null || value === void 0) {
325
+ return "";
326
+ }
327
+ if (typeof value === "number" && needsUnits(property)) {
328
+ return `${value}px`;
329
+ }
330
+ return String(value);
331
+ }
332
+ function processStyles(style, context) {
333
+ if (!style || Object.keys(style).length === 0) {
334
+ return {};
335
+ }
336
+ const processed = {};
337
+ for (const [key, value] of Object.entries(style)) {
338
+ if (value === void 0 || value === null || value === "") {
339
+ continue;
340
+ }
341
+ let resolvedValue = value;
342
+ if (typeof value === "string" && hasTemplateSyntax(value)) {
343
+ resolvedValue = resolveTemplate(value, context);
344
+ }
345
+ if (resolvedValue === void 0 || resolvedValue === null || resolvedValue === "") {
346
+ continue;
347
+ }
348
+ const formattedValue = formatStyleValue(key, resolvedValue);
349
+ if (formattedValue) {
350
+ processed[key] = formattedValue;
351
+ }
352
+ }
353
+ return processed;
354
+ }
355
+ function applyStyles(element, styles) {
356
+ for (const [property, value] of Object.entries(styles)) {
357
+ if (property.startsWith("--")) {
358
+ element.style.setProperty(property, value);
359
+ } else {
360
+ element.style[property] = value;
361
+ }
362
+ }
363
+ }
364
+ function buildElementStyles(element, context) {
365
+ const config = element.configuration || {};
366
+ const combined = {};
367
+ if (element.style) {
368
+ Object.assign(combined, element.style);
369
+ }
370
+ if (config.style) {
371
+ Object.assign(combined, config.style);
372
+ }
373
+ if (config.cssVariables) {
374
+ Object.assign(combined, config.cssVariables);
375
+ }
376
+ return processStyles(combined, context);
377
+ }
378
+ function buildClassName(element, context) {
379
+ const config = element.configuration || {};
380
+ const classes = [];
381
+ if (element.className) {
382
+ classes.push(element.className);
383
+ }
384
+ if (config.className) {
385
+ const resolved = hasTemplateSyntax(config.className) ? resolveTemplate(config.className, context) : config.className;
386
+ if (resolved) classes.push(resolved);
387
+ }
388
+ if (config.classNames) {
389
+ const resolved = hasTemplateSyntax(config.classNames) ? resolveTemplate(config.classNames, context) : config.classNames;
390
+ if (resolved) classes.push(resolved);
391
+ }
392
+ if (config.dynamicClassName) {
393
+ const resolved = resolveTemplate(config.dynamicClassName, context);
394
+ if (resolved) classes.push(resolved);
395
+ }
396
+ return classes.filter(Boolean).join(" ").trim();
397
+ }
398
+ function clearStyles(element) {
399
+ element.removeAttribute("style");
400
+ }
401
+ function updateStyles(element, oldStyles, newStyles) {
402
+ for (const property of Object.keys(oldStyles)) {
403
+ if (!(property in newStyles)) {
404
+ if (property.startsWith("--")) {
405
+ element.style.removeProperty(property);
406
+ } else {
407
+ element.style[property] = "";
408
+ }
409
+ }
410
+ }
411
+ for (const [property, value] of Object.entries(newStyles)) {
412
+ if (oldStyles[property] !== value) {
413
+ if (property.startsWith("--")) {
414
+ element.style.setProperty(property, value);
415
+ } else {
416
+ element.style[property] = value;
417
+ }
418
+ }
419
+ }
420
+ }
421
+
422
+ // src/renderer.ts
423
+ var COMPONENT_TO_TAG = {
424
+ container: "div",
425
+ text: "span",
426
+ button: "button",
427
+ input: "input",
428
+ image: "img",
429
+ link: "a",
430
+ form: "form",
431
+ label: "label",
432
+ textarea: "textarea",
433
+ select: "select",
434
+ option: "option",
435
+ list: "ul",
436
+ listItem: "li",
437
+ heading: "h1",
438
+ paragraph: "p",
439
+ section: "section",
440
+ article: "article",
441
+ header: "header",
442
+ footer: "footer",
443
+ nav: "nav",
444
+ aside: "aside",
445
+ main: "main",
446
+ span: "span",
447
+ div: "div"
448
+ };
449
+ var SELF_CLOSING_TAGS = /* @__PURE__ */ new Set([
450
+ "input",
451
+ "img",
452
+ "br",
453
+ "hr",
454
+ "area",
455
+ "base",
456
+ "col",
457
+ "embed",
458
+ "link",
459
+ "meta",
460
+ "param",
461
+ "source",
462
+ "track",
463
+ "wbr"
464
+ ]);
465
+ function getElementTag(element) {
466
+ const config = element.configuration;
467
+ if (config?.tag) {
468
+ return config.tag;
469
+ }
470
+ if (element.componentId && COMPONENT_TO_TAG[element.componentId]) {
471
+ return COMPONENT_TO_TAG[element.componentId];
472
+ }
473
+ if (element.isGroup) {
474
+ return "div";
475
+ }
476
+ return "div";
477
+ }
478
+ function isSelfClosing(tag) {
479
+ return SELF_CLOSING_TAGS.has(tag.toLowerCase());
480
+ }
481
+ function buildTree(elements) {
482
+ const tree = /* @__PURE__ */ new Map();
483
+ for (const element of elements) {
484
+ const parentId = element.parent || null;
485
+ if (!tree.has(parentId)) {
486
+ tree.set(parentId, []);
487
+ }
488
+ tree.get(parentId).push(element);
489
+ }
490
+ return tree;
491
+ }
492
+ function getTextContent(element, context) {
493
+ const config = element.configuration;
494
+ if (config?.dynamicText) {
495
+ return resolveTemplate(config.dynamicText, context);
496
+ }
497
+ if (config?.text) {
498
+ if (hasTemplateSyntax(config.text)) {
499
+ return resolveTemplate(config.text, context);
500
+ }
501
+ return config.text;
502
+ }
503
+ return "";
504
+ }
505
+ function applyAttributes(domElement, element, context) {
506
+ const config = element.configuration || {};
507
+ const attributeMap = [
508
+ { key: "id", attr: "id" },
509
+ { key: "src", dynamicKey: "dynamicSrc", attr: "src" },
510
+ { key: "alt", attr: "alt" },
511
+ { key: "href", dynamicKey: "dynamicHref", attr: "href" },
512
+ { key: "target", attr: "target" },
513
+ { key: "placeholder", attr: "placeholder" },
514
+ { key: "type", attr: "type" },
515
+ { key: "name", attr: "name" },
516
+ { key: "value", dynamicKey: "dynamicValue", attr: "value" }
517
+ ];
518
+ for (const { key, dynamicKey, attr } of attributeMap) {
519
+ const dynamicValue = dynamicKey ? config[dynamicKey] : void 0;
520
+ if (dynamicValue !== void 0 && dynamicValue !== null && dynamicValue !== "") {
521
+ const resolved = resolveTemplate(String(dynamicValue), context);
522
+ if (resolved) {
523
+ domElement.setAttribute(attr, resolved);
524
+ }
525
+ continue;
526
+ }
527
+ const staticValue = config[key];
528
+ if (staticValue !== void 0 && staticValue !== null && staticValue !== "") {
529
+ const resolved = hasTemplateSyntax(String(staticValue)) ? resolveTemplate(String(staticValue), context) : String(staticValue);
530
+ if (resolved) {
531
+ domElement.setAttribute(attr, resolved);
532
+ }
533
+ }
534
+ }
535
+ if (config.disabled) domElement.setAttribute("disabled", "");
536
+ if (config.required) domElement.setAttribute("required", "");
537
+ if (config.readOnly) domElement.setAttribute("readonly", "");
538
+ for (const [key, value] of Object.entries(config)) {
539
+ if (key.startsWith("data-") && value !== void 0) {
540
+ const resolved = hasTemplateSyntax(String(value)) ? resolveTemplate(String(value), context) : String(value);
541
+ domElement.setAttribute(key, resolved);
542
+ }
543
+ }
544
+ for (const [key, value] of Object.entries(config)) {
545
+ if (key.startsWith("aria-") && value !== void 0) {
546
+ const resolved = hasTemplateSyntax(String(value)) ? resolveTemplate(String(value), context) : String(value);
547
+ domElement.setAttribute(key, resolved);
548
+ }
549
+ }
550
+ }
551
+ function attachEventHandlers(domElement, elementId, eventHandlers, elementState) {
552
+ if (!eventHandlers || !eventHandlers[elementId]) {
553
+ return;
554
+ }
555
+ const handlers = eventHandlers[elementId];
556
+ for (const [eventName, handler] of Object.entries(handlers)) {
557
+ const domEventName = eventName.replace(/^on/, "").toLowerCase();
558
+ elementState.eventListeners.set(domEventName, handler);
559
+ domElement.addEventListener(domEventName, handler);
560
+ }
561
+ }
562
+ function detachEventHandlers(elementState) {
563
+ for (const [eventName, handler] of elementState.eventListeners) {
564
+ elementState.domElement.removeEventListener(eventName, handler);
565
+ }
566
+ elementState.eventListeners.clear();
567
+ }
568
+ function createElement(element, context, eventHandlers) {
569
+ const tag = getElementTag(element);
570
+ const domElement = document.createElement(tag);
571
+ domElement.setAttribute("data-servly-id", element.i);
572
+ const styles = buildElementStyles(element, context);
573
+ applyStyles(domElement, styles);
574
+ const className = buildClassName(element, context);
575
+ if (className) {
576
+ domElement.className = className;
577
+ }
578
+ applyAttributes(domElement, element, context);
579
+ let textContent = "";
580
+ if (element.componentId === "text" || !isSelfClosing(tag)) {
581
+ textContent = getTextContent(element, context);
582
+ if (textContent && !element.children?.length) {
583
+ domElement.textContent = textContent;
584
+ }
585
+ }
586
+ const elementState = {
587
+ element,
588
+ domElement,
589
+ styles,
590
+ className,
591
+ textContent,
592
+ eventListeners: /* @__PURE__ */ new Map()
593
+ };
594
+ attachEventHandlers(domElement, element.i, eventHandlers, elementState);
595
+ return elementState;
596
+ }
597
+ function renderElement(element, tree, context, eventHandlers, elementStates) {
598
+ const elementState = createElement(element, context, eventHandlers);
599
+ elementStates.set(element.i, elementState);
600
+ const children = tree.get(element.i) || [];
601
+ for (const child of children) {
602
+ const childElement = renderElement(child, tree, context, eventHandlers, elementStates);
603
+ elementState.domElement.appendChild(childElement);
604
+ }
605
+ return elementState.domElement;
606
+ }
607
+ function render(options) {
608
+ const { container, elements, context, eventHandlers } = options;
609
+ const tree = buildTree(elements);
610
+ 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,
631
+ context,
632
+ 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);
642
+ }
643
+ state.rootElement = wrapper;
644
+ container.appendChild(wrapper);
645
+ }
646
+ return {
647
+ rootElement: state.rootElement,
648
+ update: (newContext) => update(state, newContext),
649
+ destroy: () => destroy(state)
650
+ };
651
+ }
652
+ function update(state, newContext) {
653
+ state.context = newContext;
654
+ for (const [elementId, elementState] of state.elementStates) {
655
+ const { element, domElement } = elementState;
656
+ const newStyles = buildElementStyles(element, newContext);
657
+ updateStyles(domElement, elementState.styles, newStyles);
658
+ elementState.styles = newStyles;
659
+ const newClassName = buildClassName(element, newContext);
660
+ if (newClassName !== elementState.className) {
661
+ domElement.className = newClassName;
662
+ elementState.className = newClassName;
663
+ }
664
+ const newTextContent = getTextContent(element, newContext);
665
+ if (newTextContent !== elementState.textContent) {
666
+ const children = state.elements.filter((el) => el.parent === elementId);
667
+ if (children.length === 0) {
668
+ domElement.textContent = newTextContent;
669
+ }
670
+ elementState.textContent = newTextContent;
671
+ }
672
+ applyAttributes(domElement, element, newContext);
673
+ }
674
+ }
675
+ function destroy(state) {
676
+ for (const elementState of state.elementStates.values()) {
677
+ detachEventHandlers(elementState);
678
+ }
679
+ state.elementStates.clear();
680
+ if (state.rootElement && state.rootElement.parentNode) {
681
+ state.rootElement.parentNode.removeChild(state.rootElement);
682
+ }
683
+ state.rootElement = null;
684
+ }
685
+
686
+ // src/cache.ts
687
+ var DEFAULT_CACHE_CONFIG = {
688
+ maxEntries: 50,
689
+ ttl: 5 * 60 * 1e3,
690
+ // 5 minutes
691
+ storageKeyPrefix: "servly_component_"
692
+ };
693
+ var memoryCache = /* @__PURE__ */ new Map();
694
+ function getCacheKey(id, version = "latest") {
695
+ return `${id}@${version}`;
696
+ }
697
+ function getFromMemoryCache(id, version = "latest", config = DEFAULT_CACHE_CONFIG) {
698
+ const key = getCacheKey(id, version);
699
+ const entry = memoryCache.get(key);
700
+ if (!entry) {
701
+ return null;
702
+ }
703
+ const ttl = config.ttl ?? DEFAULT_CACHE_CONFIG.ttl;
704
+ if (Date.now() - entry.timestamp > ttl) {
705
+ memoryCache.delete(key);
706
+ return null;
707
+ }
708
+ entry.accessCount++;
709
+ entry.lastAccessed = Date.now();
710
+ return entry.data;
711
+ }
712
+ function setInMemoryCache(id, version, data, config = DEFAULT_CACHE_CONFIG) {
713
+ const key = getCacheKey(id, version);
714
+ const maxEntries = config.maxEntries ?? DEFAULT_CACHE_CONFIG.maxEntries;
715
+ if (memoryCache.size >= maxEntries && !memoryCache.has(key)) {
716
+ evictLRUFromMemory();
717
+ }
718
+ memoryCache.set(key, {
719
+ data,
720
+ timestamp: Date.now(),
721
+ version,
722
+ accessCount: 1,
723
+ lastAccessed: Date.now()
724
+ });
725
+ }
726
+ function evictLRUFromMemory() {
727
+ let lruKey = null;
728
+ let lruTime = Infinity;
729
+ for (const [key, entry] of memoryCache.entries()) {
730
+ if (entry.lastAccessed < lruTime) {
731
+ lruTime = entry.lastAccessed;
732
+ lruKey = key;
733
+ }
734
+ }
735
+ if (lruKey) {
736
+ memoryCache.delete(lruKey);
737
+ }
738
+ }
739
+ function clearMemoryCache() {
740
+ memoryCache.clear();
741
+ }
742
+ function getMemoryCacheSize() {
743
+ return memoryCache.size;
744
+ }
745
+ function isLocalStorageAvailable() {
746
+ try {
747
+ const test = "__servly_test__";
748
+ localStorage.setItem(test, test);
749
+ localStorage.removeItem(test);
750
+ return true;
751
+ } catch {
752
+ return false;
753
+ }
754
+ }
755
+ function getStorageKey(id, version, config) {
756
+ const prefix = config.storageKeyPrefix ?? DEFAULT_CACHE_CONFIG.storageKeyPrefix;
757
+ return `${prefix}${getCacheKey(id, version)}`;
758
+ }
759
+ function getFromLocalStorage(id, version = "latest", config = DEFAULT_CACHE_CONFIG) {
760
+ if (!isLocalStorageAvailable()) {
761
+ return null;
762
+ }
763
+ try {
764
+ const key = getStorageKey(id, version, config);
765
+ const stored = localStorage.getItem(key);
766
+ if (!stored) {
767
+ return null;
768
+ }
769
+ const entry = JSON.parse(stored);
770
+ const ttl = config.ttl ?? DEFAULT_CACHE_CONFIG.ttl;
771
+ if (Date.now() - entry.timestamp > ttl) {
772
+ localStorage.removeItem(key);
773
+ return null;
774
+ }
775
+ entry.accessCount++;
776
+ entry.lastAccessed = Date.now();
777
+ localStorage.setItem(key, JSON.stringify(entry));
778
+ return entry.data;
779
+ } catch (error) {
780
+ console.warn("Error reading from localStorage cache:", error);
781
+ return null;
782
+ }
783
+ }
784
+ function setInLocalStorage(id, version, data, config = DEFAULT_CACHE_CONFIG) {
785
+ if (!isLocalStorageAvailable()) {
786
+ return;
787
+ }
788
+ try {
789
+ const key = getStorageKey(id, version, config);
790
+ const maxEntries = config.maxEntries ?? DEFAULT_CACHE_CONFIG.maxEntries;
791
+ const currentCount = getLocalStorageCacheCount(config);
792
+ if (currentCount >= maxEntries) {
793
+ evictLRUFromLocalStorage(config);
794
+ }
795
+ const entry = {
796
+ data,
797
+ timestamp: Date.now(),
798
+ version,
799
+ accessCount: 1,
800
+ lastAccessed: Date.now()
801
+ };
802
+ localStorage.setItem(key, JSON.stringify(entry));
803
+ } catch (error) {
804
+ console.warn("Error writing to localStorage cache:", error);
805
+ if (error instanceof DOMException && error.name === "QuotaExceededError") {
806
+ evictLRUFromLocalStorage(config);
807
+ try {
808
+ const key = getStorageKey(id, version, config);
809
+ const entry = {
810
+ data,
811
+ timestamp: Date.now(),
812
+ version,
813
+ accessCount: 1,
814
+ lastAccessed: Date.now()
815
+ };
816
+ localStorage.setItem(key, JSON.stringify(entry));
817
+ } catch {
818
+ }
819
+ }
820
+ }
821
+ }
822
+ function getLocalStorageCacheCount(config) {
823
+ const prefix = config.storageKeyPrefix ?? DEFAULT_CACHE_CONFIG.storageKeyPrefix;
824
+ let count = 0;
825
+ for (let i = 0; i < localStorage.length; i++) {
826
+ const key = localStorage.key(i);
827
+ if (key?.startsWith(prefix)) {
828
+ count++;
829
+ }
830
+ }
831
+ return count;
832
+ }
833
+ function evictLRUFromLocalStorage(config) {
834
+ const prefix = config.storageKeyPrefix ?? DEFAULT_CACHE_CONFIG.storageKeyPrefix;
835
+ let lruKey = null;
836
+ let lruTime = Infinity;
837
+ for (let i = 0; i < localStorage.length; i++) {
838
+ const key = localStorage.key(i);
839
+ if (!key?.startsWith(prefix)) continue;
840
+ try {
841
+ const stored = localStorage.getItem(key);
842
+ if (stored) {
843
+ const entry = JSON.parse(stored);
844
+ if (entry.lastAccessed < lruTime) {
845
+ lruTime = entry.lastAccessed;
846
+ lruKey = key;
847
+ }
848
+ }
849
+ } catch {
850
+ if (key) localStorage.removeItem(key);
851
+ }
852
+ }
853
+ if (lruKey) {
854
+ localStorage.removeItem(lruKey);
855
+ }
856
+ }
857
+ function clearLocalStorageCache(config = DEFAULT_CACHE_CONFIG) {
858
+ if (!isLocalStorageAvailable()) {
859
+ return;
860
+ }
861
+ const prefix = config.storageKeyPrefix ?? DEFAULT_CACHE_CONFIG.storageKeyPrefix;
862
+ const keysToRemove = [];
863
+ for (let i = 0; i < localStorage.length; i++) {
864
+ const key = localStorage.key(i);
865
+ if (key?.startsWith(prefix)) {
866
+ keysToRemove.push(key);
867
+ }
868
+ }
869
+ keysToRemove.forEach((key) => localStorage.removeItem(key));
870
+ }
871
+ function getFromCache(id, version = "latest", strategy = "memory", config = DEFAULT_CACHE_CONFIG) {
872
+ if (strategy === "none") {
873
+ return null;
874
+ }
875
+ const memoryResult = getFromMemoryCache(id, version, config);
876
+ if (memoryResult) {
877
+ return memoryResult;
878
+ }
879
+ if (strategy === "localStorage") {
880
+ const localResult = getFromLocalStorage(id, version, config);
881
+ if (localResult) {
882
+ setInMemoryCache(id, version, localResult, config);
883
+ return localResult;
884
+ }
885
+ }
886
+ return null;
887
+ }
888
+ function setInCache(id, version, data, strategy = "memory", config = DEFAULT_CACHE_CONFIG) {
889
+ if (strategy === "none") {
890
+ return;
891
+ }
892
+ setInMemoryCache(id, version, data, config);
893
+ if (strategy === "localStorage") {
894
+ setInLocalStorage(id, version, data, config);
895
+ }
896
+ }
897
+ function clearAllCaches(config = DEFAULT_CACHE_CONFIG) {
898
+ clearMemoryCache();
899
+ clearLocalStorageCache(config);
900
+ }
901
+ function invalidateCache(id, version, config = DEFAULT_CACHE_CONFIG) {
902
+ if (version) {
903
+ const key = getCacheKey(id, version);
904
+ memoryCache.delete(key);
905
+ if (isLocalStorageAvailable()) {
906
+ const storageKey = getStorageKey(id, version, config);
907
+ localStorage.removeItem(storageKey);
908
+ }
909
+ } else {
910
+ const prefix = `${id}@`;
911
+ for (const key of memoryCache.keys()) {
912
+ if (key.startsWith(prefix)) {
913
+ memoryCache.delete(key);
914
+ }
915
+ }
916
+ if (isLocalStorageAvailable()) {
917
+ const storagePrefix = (config.storageKeyPrefix ?? DEFAULT_CACHE_CONFIG.storageKeyPrefix) + prefix;
918
+ const keysToRemove = [];
919
+ for (let i = 0; i < localStorage.length; i++) {
920
+ const key = localStorage.key(i);
921
+ if (key?.startsWith(storagePrefix)) {
922
+ keysToRemove.push(key);
923
+ }
924
+ }
925
+ keysToRemove.forEach((key) => localStorage.removeItem(key));
926
+ }
927
+ }
928
+ }
929
+
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
+ // src/fetcher.ts
1043
+ var DEFAULT_RETRY_CONFIG = {
1044
+ maxRetries: 3,
1045
+ initialDelay: 1e3,
1046
+ maxDelay: 1e4,
1047
+ backoffMultiplier: 2
1048
+ };
1049
+ var registryBaseUrl = "/api/components";
1050
+ function setRegistryUrl(url) {
1051
+ registryBaseUrl = url;
1052
+ }
1053
+ function getRegistryUrl() {
1054
+ if (typeof window !== "undefined") {
1055
+ const envUrl = window.__SERVLY_REGISTRY_URL__;
1056
+ if (envUrl) return envUrl;
1057
+ }
1058
+ if (typeof process !== "undefined" && process.env?.SERVLY_REGISTRY_URL) {
1059
+ return process.env.SERVLY_REGISTRY_URL;
1060
+ }
1061
+ return registryBaseUrl;
1062
+ }
1063
+ function calculateBackoffDelay(retryCount, config) {
1064
+ const delay = config.initialDelay * Math.pow(config.backoffMultiplier, retryCount);
1065
+ return Math.min(delay, config.maxDelay);
1066
+ }
1067
+ function sleep(ms) {
1068
+ return new Promise((resolve) => setTimeout(resolve, ms));
1069
+ }
1070
+ async function resolveVersionFromApi(id, specifier, apiKey) {
1071
+ if (/^\d+\.\d+\.\d+$/.test(specifier)) {
1072
+ return specifier;
1073
+ }
1074
+ const baseUrl = getRegistryUrl();
1075
+ const headers = {
1076
+ "Content-Type": "application/json"
1077
+ };
1078
+ if (apiKey) {
1079
+ headers["Authorization"] = `Bearer ${apiKey}`;
1080
+ }
1081
+ try {
1082
+ const response = await fetch(
1083
+ `${baseUrl}/${id}/versions?specifier=${encodeURIComponent(specifier)}`,
1084
+ { headers }
1085
+ );
1086
+ if (!response.ok) {
1087
+ throw new Error(`Failed to resolve version: ${response.statusText}`);
1088
+ }
1089
+ 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;
1096
+ }
1097
+ throw new Error(data.error || "Failed to resolve version");
1098
+ } catch (error) {
1099
+ console.warn(`Failed to resolve version for ${id}@${specifier}:`, error);
1100
+ return "latest";
1101
+ }
1102
+ }
1103
+ async function fetchFromRegistry(id, version, apiKey) {
1104
+ const baseUrl = getRegistryUrl();
1105
+ const headers = {
1106
+ "Content-Type": "application/json"
1107
+ };
1108
+ if (apiKey) {
1109
+ headers["Authorization"] = `Bearer ${apiKey}`;
1110
+ }
1111
+ const url = version && version !== "latest" ? `${baseUrl}/${id}/versions/${version}` : `${baseUrl}/${id}`;
1112
+ const response = await fetch(url, { headers });
1113
+ if (!response.ok) {
1114
+ if (response.status === 404) {
1115
+ throw new Error(`Component not found: ${id}@${version}`);
1116
+ }
1117
+ if (response.status === 401) {
1118
+ throw new Error("Unauthorized: Invalid or missing API key");
1119
+ }
1120
+ if (response.status === 403) {
1121
+ throw new Error("Forbidden: Access denied to this component");
1122
+ }
1123
+ throw new Error(`Failed to fetch component: ${response.statusText}`);
1124
+ }
1125
+ const data = await response.json();
1126
+ if (!data.success || !data.data) {
1127
+ throw new Error(data.error || "Failed to fetch component data");
1128
+ }
1129
+ return data.data;
1130
+ }
1131
+ async function fetchComponent(id, options = {}) {
1132
+ const {
1133
+ version = "latest",
1134
+ apiKey,
1135
+ cacheStrategy = "memory",
1136
+ cacheConfig = DEFAULT_CACHE_CONFIG,
1137
+ retryConfig = {},
1138
+ forceRefresh = false,
1139
+ signal
1140
+ } = options;
1141
+ const fullRetryConfig = {
1142
+ ...DEFAULT_RETRY_CONFIG,
1143
+ ...retryConfig
1144
+ };
1145
+ if (!forceRefresh) {
1146
+ const cached = getFromCache(id, version, cacheStrategy, cacheConfig);
1147
+ if (cached) {
1148
+ return {
1149
+ data: cached,
1150
+ fromCache: true,
1151
+ version: cached.version
1152
+ };
1153
+ }
1154
+ }
1155
+ const resolvedVersion = await resolveVersionFromApi(id, version, apiKey);
1156
+ if (!forceRefresh && resolvedVersion !== version) {
1157
+ const cached = getFromCache(id, resolvedVersion, cacheStrategy, cacheConfig);
1158
+ if (cached) {
1159
+ return {
1160
+ data: cached,
1161
+ fromCache: true,
1162
+ version: resolvedVersion
1163
+ };
1164
+ }
1165
+ }
1166
+ let lastError = null;
1167
+ for (let attempt = 0; attempt <= fullRetryConfig.maxRetries; attempt++) {
1168
+ if (signal?.aborted) {
1169
+ throw new Error("Fetch aborted");
1170
+ }
1171
+ try {
1172
+ const data = await fetchFromRegistry(id, resolvedVersion, apiKey);
1173
+ setInCache(id, resolvedVersion, data, cacheStrategy, cacheConfig);
1174
+ if (version !== resolvedVersion) {
1175
+ setInCache(id, version, data, cacheStrategy, cacheConfig);
1176
+ }
1177
+ return {
1178
+ data,
1179
+ fromCache: false,
1180
+ version: resolvedVersion
1181
+ };
1182
+ } catch (error) {
1183
+ lastError = error instanceof Error ? error : new Error(String(error));
1184
+ if (lastError.message.includes("not found") || lastError.message.includes("Unauthorized") || lastError.message.includes("Forbidden")) {
1185
+ throw lastError;
1186
+ }
1187
+ if (attempt < fullRetryConfig.maxRetries) {
1188
+ const delay = calculateBackoffDelay(attempt, fullRetryConfig);
1189
+ await sleep(delay);
1190
+ }
1191
+ }
1192
+ }
1193
+ throw lastError || new Error("Failed to fetch component");
1194
+ }
1195
+ async function prefetchComponents(ids, options = {}) {
1196
+ const promises = ids.map(
1197
+ ({ id, version }) => fetchComponent(id, { ...options, version }).catch((error) => {
1198
+ console.warn(`Failed to prefetch component ${id}:`, error);
1199
+ })
1200
+ );
1201
+ await Promise.all(promises);
1202
+ }
1203
+ async function isComponentAvailable(id, version = "latest", options = {}) {
1204
+ const { cacheStrategy = "memory", cacheConfig = DEFAULT_CACHE_CONFIG } = options;
1205
+ const cached = getFromCache(id, version, cacheStrategy, cacheConfig);
1206
+ if (cached) {
1207
+ return { available: true, cached: true };
1208
+ }
1209
+ try {
1210
+ await fetchComponent(id, {
1211
+ ...options,
1212
+ version,
1213
+ retryConfig: { maxRetries: 0 }
1214
+ });
1215
+ return { available: true, cached: false };
1216
+ } catch {
1217
+ return { available: false, cached: false };
1218
+ }
1219
+ }
1220
+
1221
+ // src/testRunner.ts
1222
+ function runTestCase(elements, testCase, container) {
1223
+ const startTime = performance.now();
1224
+ const assertionResults = [];
1225
+ let renderResult = null;
1226
+ let error;
1227
+ const testContainer = container || document.createElement("div");
1228
+ if (!container) {
1229
+ document.body.appendChild(testContainer);
1230
+ }
1231
+ try {
1232
+ const context = {
1233
+ props: testCase.props
1234
+ };
1235
+ renderResult = render({
1236
+ container: testContainer,
1237
+ elements,
1238
+ context
1239
+ });
1240
+ for (const assertion of testCase.assertions) {
1241
+ const result = validateAssertion(testContainer, assertion);
1242
+ assertionResults.push(result);
1243
+ }
1244
+ } catch (err) {
1245
+ error = err instanceof Error ? err.message : String(err);
1246
+ } finally {
1247
+ if (renderResult) {
1248
+ renderResult.destroy();
1249
+ }
1250
+ if (!container) {
1251
+ document.body.removeChild(testContainer);
1252
+ }
1253
+ }
1254
+ const duration = performance.now() - startTime;
1255
+ const passed = !error && assertionResults.every((r) => r.passed);
1256
+ return {
1257
+ testId: testCase.id,
1258
+ testName: testCase.name,
1259
+ passed,
1260
+ assertions: assertionResults,
1261
+ duration,
1262
+ error
1263
+ };
1264
+ }
1265
+ function runAllTests(elements, testCases) {
1266
+ const startTime = performance.now();
1267
+ const results = [];
1268
+ for (const testCase of testCases) {
1269
+ const result = runTestCase(elements, testCase);
1270
+ results.push(result);
1271
+ }
1272
+ const duration = performance.now() - startTime;
1273
+ const passed = results.filter((r) => r.passed).length;
1274
+ const failed = results.filter((r) => !r.passed).length;
1275
+ return {
1276
+ total: testCases.length,
1277
+ passed,
1278
+ failed,
1279
+ duration,
1280
+ results
1281
+ };
1282
+ }
1283
+ function validateAssertion(container, assertion) {
1284
+ const elements = container.querySelectorAll(assertion.selector);
1285
+ switch (assertion.type) {
1286
+ case "exists":
1287
+ return {
1288
+ assertion,
1289
+ passed: elements.length > 0,
1290
+ actual: elements.length > 0,
1291
+ expected: true,
1292
+ message: elements.length > 0 ? `Element "${assertion.selector}" exists` : `Element "${assertion.selector}" not found`
1293
+ };
1294
+ case "count":
1295
+ const count = elements.length;
1296
+ const expectedCount = assertion.expected;
1297
+ return {
1298
+ assertion,
1299
+ passed: count === expectedCount,
1300
+ actual: count,
1301
+ expected: expectedCount,
1302
+ message: count === expectedCount ? `Found ${count} elements matching "${assertion.selector}"` : `Expected ${expectedCount} elements, found ${count}`
1303
+ };
1304
+ case "text":
1305
+ if (elements.length === 0) {
1306
+ return {
1307
+ assertion,
1308
+ passed: false,
1309
+ actual: null,
1310
+ expected: assertion.expected,
1311
+ message: `Element "${assertion.selector}" not found`
1312
+ };
1313
+ }
1314
+ const textContent = elements[0].textContent?.trim();
1315
+ const expectedText = assertion.expected;
1316
+ return {
1317
+ assertion,
1318
+ passed: textContent === expectedText,
1319
+ actual: textContent,
1320
+ expected: expectedText,
1321
+ message: textContent === expectedText ? `Text matches: "${expectedText}"` : `Expected text "${expectedText}", got "${textContent}"`
1322
+ };
1323
+ case "attribute":
1324
+ if (elements.length === 0) {
1325
+ return {
1326
+ assertion,
1327
+ passed: false,
1328
+ actual: null,
1329
+ expected: assertion.expected,
1330
+ message: `Element "${assertion.selector}" not found`
1331
+ };
1332
+ }
1333
+ const attrValue = elements[0].getAttribute(assertion.attribute || "");
1334
+ const expectedAttr = assertion.expected;
1335
+ return {
1336
+ assertion,
1337
+ passed: attrValue === expectedAttr,
1338
+ actual: attrValue,
1339
+ expected: expectedAttr,
1340
+ message: attrValue === expectedAttr ? `Attribute "${assertion.attribute}" matches` : `Expected attribute "${assertion.attribute}" to be "${expectedAttr}", got "${attrValue}"`
1341
+ };
1342
+ case "class":
1343
+ if (elements.length === 0) {
1344
+ return {
1345
+ assertion,
1346
+ passed: false,
1347
+ actual: null,
1348
+ expected: assertion.expected,
1349
+ message: `Element "${assertion.selector}" not found`
1350
+ };
1351
+ }
1352
+ const hasClass = elements[0].classList.contains(assertion.expected);
1353
+ return {
1354
+ assertion,
1355
+ passed: hasClass,
1356
+ actual: Array.from(elements[0].classList),
1357
+ expected: assertion.expected,
1358
+ message: hasClass ? `Element has class "${assertion.expected}"` : `Element does not have class "${assertion.expected}"`
1359
+ };
1360
+ case "style":
1361
+ if (elements.length === 0) {
1362
+ return {
1363
+ assertion,
1364
+ passed: false,
1365
+ actual: null,
1366
+ expected: assertion.expected,
1367
+ message: `Element "${assertion.selector}" not found`
1368
+ };
1369
+ }
1370
+ const el = elements[0];
1371
+ const styleValue = el.style.getPropertyValue(assertion.property || "");
1372
+ const expectedStyle = assertion.expected;
1373
+ return {
1374
+ assertion,
1375
+ passed: styleValue === expectedStyle,
1376
+ actual: styleValue,
1377
+ expected: expectedStyle,
1378
+ message: styleValue === expectedStyle ? `Style "${assertion.property}" matches` : `Expected style "${assertion.property}" to be "${expectedStyle}", got "${styleValue}"`
1379
+ };
1380
+ case "visible":
1381
+ if (elements.length === 0) {
1382
+ return {
1383
+ assertion,
1384
+ passed: false,
1385
+ actual: null,
1386
+ expected: true,
1387
+ message: `Element "${assertion.selector}" not found`
1388
+ };
1389
+ }
1390
+ const visible = isElementVisible(elements[0]);
1391
+ const expectedVisible = assertion.expected !== false;
1392
+ return {
1393
+ assertion,
1394
+ passed: visible === expectedVisible,
1395
+ actual: visible,
1396
+ expected: expectedVisible,
1397
+ message: visible === expectedVisible ? `Element visibility matches expected` : `Expected element to be ${expectedVisible ? "visible" : "hidden"}`
1398
+ };
1399
+ default:
1400
+ return {
1401
+ assertion,
1402
+ passed: false,
1403
+ message: `Unknown assertion type: ${assertion.type}`
1404
+ };
1405
+ }
1406
+ }
1407
+ function isElementVisible(element) {
1408
+ const style = window.getComputedStyle(element);
1409
+ return style.display !== "none" && style.visibility !== "hidden" && style.opacity !== "0" && element.offsetWidth > 0 && element.offsetHeight > 0;
1410
+ }
1411
+ function validateProps(props, definitions) {
1412
+ const errors = [];
1413
+ for (const def of definitions) {
1414
+ const value = props[def.name];
1415
+ if (def.required && (value === void 0 || value === null)) {
1416
+ errors.push(`Required prop "${def.name}" is missing`);
1417
+ continue;
1418
+ }
1419
+ if (value === void 0) continue;
1420
+ const actualType = Array.isArray(value) ? "array" : typeof value;
1421
+ if (def.type === "array" && !Array.isArray(value)) {
1422
+ errors.push(`Prop "${def.name}" should be an array, got ${actualType}`);
1423
+ } else if (def.type !== "array" && actualType !== def.type) {
1424
+ errors.push(`Prop "${def.name}" should be ${def.type}, got ${actualType}`);
1425
+ }
1426
+ }
1427
+ return {
1428
+ valid: errors.length === 0,
1429
+ errors
1430
+ };
1431
+ }
1432
+ function generateTestCases(definitions) {
1433
+ const testCases = [];
1434
+ const defaultProps = {};
1435
+ for (const def of definitions) {
1436
+ if (def.defaultValue !== void 0) {
1437
+ defaultProps[def.name] = def.defaultValue;
1438
+ }
1439
+ }
1440
+ testCases.push({
1441
+ id: "default-props",
1442
+ name: "Render with default props",
1443
+ props: defaultProps,
1444
+ assertions: [{ type: "exists", selector: "*" }]
1445
+ });
1446
+ for (const def of definitions) {
1447
+ const sampleValue = getSampleValue(def);
1448
+ if (sampleValue !== void 0) {
1449
+ testCases.push({
1450
+ id: `prop-${def.name}`,
1451
+ name: `Test prop: ${def.name}`,
1452
+ props: { ...defaultProps, [def.name]: sampleValue },
1453
+ assertions: [{ type: "exists", selector: "*" }]
1454
+ });
1455
+ }
1456
+ }
1457
+ return testCases;
1458
+ }
1459
+ function getSampleValue(def) {
1460
+ switch (def.type) {
1461
+ case "string":
1462
+ return "Sample Text";
1463
+ case "number":
1464
+ return 42;
1465
+ case "boolean":
1466
+ return true;
1467
+ case "array":
1468
+ return [];
1469
+ case "object":
1470
+ return {};
1471
+ default:
1472
+ return def.defaultValue;
1473
+ }
1474
+ }
1475
+ // Annotate the CommonJS export names for ESM import in node:
1476
+ 0 && (module.exports = {
1477
+ DEFAULT_CACHE_CONFIG,
1478
+ DEFAULT_RETRY_CONFIG,
1479
+ applyStyles,
1480
+ buildClassName,
1481
+ buildElementStyles,
1482
+ bumpVersion,
1483
+ camelToKebab,
1484
+ clearAllCaches,
1485
+ clearLocalStorageCache,
1486
+ clearMemoryCache,
1487
+ clearStyles,
1488
+ compareVersions,
1489
+ extractBindingKeys,
1490
+ fetchComponent,
1491
+ formatStyleValue,
1492
+ formatVersion,
1493
+ generateTestCases,
1494
+ getCacheKey,
1495
+ getFromCache,
1496
+ getMemoryCacheSize,
1497
+ getRegistryUrl,
1498
+ hasTemplateSyntax,
1499
+ invalidateCache,
1500
+ isComponentAvailable,
1501
+ isValidSpecifier,
1502
+ parseVersion,
1503
+ prefetchComponents,
1504
+ processStyles,
1505
+ render,
1506
+ resolveBindingPath,
1507
+ resolveTemplate,
1508
+ resolveTemplateValue,
1509
+ resolveTemplatesDeep,
1510
+ resolveVersion,
1511
+ runAllTests,
1512
+ runTestCase,
1513
+ satisfiesVersion,
1514
+ setInCache,
1515
+ setRegistryUrl,
1516
+ updateStyles,
1517
+ validateAssertion,
1518
+ validateProps
1519
+ });