@stacksjs/stx
Version:
A performant UI Framework. Powered by Bun.
1,319 lines (1,144 loc) • 523 kB
JavaScript
// @bun
var{defineProperty:T7,getOwnPropertyNames:M1,getOwnPropertyDescriptor:T1}=Object,w1=Object.prototype.hasOwnProperty;var Q6=new WeakMap,K6=($)=>{var Z=Q6.get($),Y;if(Z)return Z;if(Z=T7({},"__esModule",{value:!0}),$&&typeof $==="object"||typeof $==="function")M1($).map((J)=>!w1.call(Z,J)&&T7(Z,J,{get:()=>$[J],enumerable:!(Y=T1($,J))||Y.enumerable}));return Q6.set($,Z),Z};var o=($,Z)=>{for(var Y in Z)T7($,Y,{get:Z[Y],enumerable:!0,configurable:!0,set:(J)=>Z[Y]=()=>J})};var I=($,Z)=>()=>($&&(Z=$($=0)),Z);var t=import.meta.require;import I5 from"fs";import D5 from"path";function E1($){let Z=0;for(let Y=0;Y<$.length;Y++){let J=$[Y];Z=(Z<<5)-Z+J,Z=Z&Z}return Math.abs(Z).toString(16).padStart(8,"0")}function k1($,Z){let Y=D5.basename($);for(let J of Z)if(J.startsWith("*.")){let G=J.slice(1);if(Y.endsWith(G))return!0}else if(J.includes("*")){let G=new RegExp(`^${J.replace(/\*/g,".*")}$`);if(G.test(Y)||G.test($))return!0}else if(Y===J||$.includes(J))return!0;return!1}function S1($,Z,Y){let J=D5.extname($).toLowerCase(),G=D1[J],X=(Y.maxFileSizeKB||500)*1024;if(Z>X)return!1;if(Y.exclude&&k1($,Y.exclude))return!1;if(Y.include&&Y.include.length>0){for(let Q of Y.include)if(Q.startsWith("*.")){if($.endsWith(Q.slice(1)))return!0}else if($.includes(Q))return!0}if(G&&Y[G]===!0)return!0;if(!G)return!1;return!1}function z6($,Z,Y,J){let G=I5.readdirSync($,{withFileTypes:!0});for(let X of G){let Q=D5.join($,X.name);if(X.isDirectory()){if(["node_modules",".git",".stx"].includes(X.name))continue;z6(Q,Z,Y,J)}else if(X.isFile()){let K=I5.statSync(Q),q=D5.relative(Z,Q);if(S1(q,K.size,Y)){let _=I5.readFileSync(Q),j=E1(_),B=`/${q.replace(/\\/g,"/")}`;J.push({url:B,revision:j,size:K.size})}}}}function b4($,Z){let Y=Z.pwa?.precache,J=[];if(!Y?.enabled)return{entries:[],totalSize:0,fileCount:0};if(!I5.existsSync($))return{entries:[],totalSize:0,fileCount:0};z6($,$,Y,J),J.sort((X,Q)=>X.url.localeCompare(Q.url));let G=J.reduce((X,Q)=>X+Q.size,0);return{entries:J,totalSize:G,fileCount:J.length}}function E5($){if($.entries.length===0)return"[]";return`[
${$.entries.map((Y)=>` { url: '${Y.url}', revision: '${Y.revision}' }`).join(`,
`)}
]`}function H6($){if($<1024)return`${$} B`;if($<1048576)return`${($/1024).toFixed(1)} KB`;return`${($/1048576).toFixed(2)} MB`}var D1;var G5=I(()=>{D1={".html":"includeHtml",".htm":"includeHtml",".js":"includeJs",".mjs":"includeJs",".css":"includeCss",".png":"includeImages",".jpg":"includeImages",".jpeg":"includeImages",".gif":"includeImages",".webp":"includeImages",".svg":"includeImages",".ico":"includeImages",".woff":"includeFonts",".woff2":"includeFonts",".ttf":"includeFonts",".eot":"includeFonts",".otf":"includeFonts"}});function b1($){let Z={"cache-first":{className:"CacheFirst",importFrom:"workbox-strategies"},"network-first":{className:"NetworkFirst",importFrom:"workbox-strategies"},"stale-while-revalidate":{className:"StaleWhileRevalidate",importFrom:"workbox-strategies"},"network-only":{className:"NetworkOnly",importFrom:"workbox-strategies"},"cache-only":{className:"CacheOnly",importFrom:"workbox-strategies"}};return Z[$]||Z["network-first"]}function w7($){return b1($).className}function O6($){let Z=$.pwa;if(!Z?.enabled)return"";let Y=Z.routes||[],J=Z.cacheStorage,G=[];for(let X of Y){let Q=w7(X.strategy),K=[];if(X.maxAgeSeconds||X.maxEntries||J?.maxEntries){let _=X.maxEntries||J?.maxEntries||100,j=X.maxAgeSeconds||(J?.maxAge?J.maxAge*24*60*60:void 0);K.push(`new ExpirationPlugin({
maxEntries: ${_},
${j?`maxAgeSeconds: ${j},`:""}
purgeOnQuotaError: true,
})`)}if(X.strategy==="network-first"||X.strategy==="stale-while-revalidate")K.push(`new CacheableResponsePlugin({
statuses: [0, 200],
})`);let q=P1(X.pattern);G.push(`
// ${X.pattern}
registerRoute(
${q},
new ${Q}({
cacheName: '${X.cacheName||C1(X.pattern)}',
${K.length>0?`plugins: [
${K.join(`,
`)}
],`:""}
})
);`)}return G.join(`
`)}function P1($){if($.startsWith("*.")){let Z=$.slice(2);return`({ request }) => request.destination === '${y1(Z)}'`}if($.endsWith("/**"))return`({ url }) => url.pathname.startsWith('${$.slice(0,-3)}')`;if($.endsWith("/*")){let Z=$.slice(0,-2);return`({ url }) => url.pathname.startsWith('${Z}') && !url.pathname.slice(${Z.length+1}).includes('/')`}if($.includes("*")&&!$.startsWith("*")){let[Z,Y]=$.split("*");return`({ url }) => url.pathname.startsWith('${Z}') && url.pathname.endsWith('${Y}')`}if($==="/"||!$.includes("*"))return`({ url }) => url.pathname === '${$}'`;return`new RegExp('${v1($).replace(/\\\*/g,".*")}')`}function y1($){return{js:"script",mjs:"script",css:"style",html:"document",htm:"document",png:"image",jpg:"image",jpeg:"image",gif:"image",svg:"image",webp:"image",avif:"image",ico:"image",woff:"font",woff2:"font",ttf:"font",eot:"font",otf:"font"}[$.toLowerCase()]||"empty"}function C1($){return"stx-"+$.replace(/[^\w]/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"").toLowerCase().slice(0,30)+"-cache"}function v1($){return $.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}var A6={};o(A6,{isWorkboxEnabled:()=>R6,getServiceWorkerGenerator:()=>d1,generateWorkboxServiceWorker:()=>F6,generateWorkboxConfig:()=>L6});function L6($,Z){let Y=$.pwa;if(!Y?.enabled)return{mode:"generate",modules:[],skipWaiting:!0,clientsClaim:!0};let J=Y.serviceWorker||{},G=Y.routes||[],X=[{name:"workbox-precaching",exports:["precacheAndRoute","cleanupOutdatedCaches"]},{name:"workbox-routing",exports:["registerRoute","NavigationRoute","setDefaultHandler"]}],Q=new Set;for(let _ of G)Q.add(w7(_.strategy));if(Q.size>0)X.push({name:"workbox-strategies",exports:Array.from(Q)});if(G.some((_)=>_.maxAgeSeconds||_.maxEntries)||Y.cacheStorage)X.push({name:"workbox-expiration",exports:["ExpirationPlugin"]});if(G.some((_)=>_.strategy==="network-first"||_.strategy==="stale-while-revalidate"))X.push({name:"workbox-cacheable-response",exports:["CacheableResponsePlugin"]});if(Y.backgroundSync?.enabled)X.push({name:"workbox-background-sync",exports:["BackgroundSyncPlugin","Queue"]});if(J.navigationPreload)X.push({name:"workbox-navigation-preload",exports:["enable"]});let q=Z?b4(Z,$):void 0;return{mode:"generate",modules:X,precacheManifest:q?.entries,skipWaiting:J.skipWaiting??!0,clientsClaim:J.clientsClaim??!0,offlineFallback:Y.offline?.enabled?"/offline.html":void 0,navigationPreload:J.navigationPreload??!1,cacheVersion:J.cacheVersion||"1.0.0"}}function F6($,Z){let Y=$.pwa;if(!Y?.enabled)return"";let J=L6($,Z),G=Y.serviceWorker||{},X=Y.push,Q=Y.backgroundSync,K=Z?b4(Z,$):null,q=K?E5(K):"[]",_=x1(J.modules),j=O6($);return`/**
* Service Worker - Generated by stx with Workbox
* Cache Version: ${J.cacheVersion}
*
* This service worker uses Google's Workbox library for robust caching.
* https://developers.google.com/web/tools/workbox
*
* Features:
* - Precaching with automatic revision management
* - Runtime caching with multiple strategies
* - Offline fallback support
* - Push notifications: ${X?.enabled?"enabled":"disabled"}
* - Background sync: ${Q?.enabled?"enabled":"disabled"}
*/
// Import Workbox modules from CDN
importScripts('https://storage.googleapis.com/workbox-cdn/releases/7.0.0/workbox-sw.js');
// Disable Workbox logging in production
workbox.setConfig({ debug: true });
// Extract modules from workbox global
${_}
const CACHE_VERSION = '${J.cacheVersion}';
const OFFLINE_PAGE = '${J.offlineFallback||"/offline.html"}';
// ============================================================================
// Precaching
// ============================================================================
// Clean up outdated caches from previous versions
cleanupOutdatedCaches();
// Precache static assets
// The manifest is injected during build or defined below
const PRECACHE_MANIFEST = ${q};
// Add essential assets
PRECACHE_MANIFEST.push(
{ url: '/', revision: CACHE_VERSION },
{ url: '/manifest.json', revision: CACHE_VERSION },
${J.offlineFallback?`{ url: '${J.offlineFallback}', revision: CACHE_VERSION },`:""}
);
// Precache and route all manifest entries
precacheAndRoute(PRECACHE_MANIFEST);
// ============================================================================
// Navigation Preload
// ============================================================================
${J.navigationPreload?`// Enable navigation preload for faster page loads
enable();`:"// Navigation preload disabled"}
// ============================================================================
// Runtime Caching
// ============================================================================
${j}
// ============================================================================
// Offline Fallback
// ============================================================================
${J.offlineFallback?`
// Set up offline fallback for navigation requests
const navigationRoute = new NavigationRoute(
new NetworkFirst({
cacheName: 'stx-pages-cache',
plugins: [
new CacheableResponsePlugin({ statuses: [0, 200] }),
],
}),
{
// Return offline page when navigation fails
async allowedHandler(request) {
try {
const response = await this.handle(request);
return response;
} catch (error) {
const cache = await caches.open('stx-offline-cache');
return cache.match(OFFLINE_PAGE) || new Response('You are offline', {
status: 503,
statusText: 'Service Unavailable',
});
}
},
}
);
registerRoute(navigationRoute);
`:"// No offline fallback configured"}
// Default handler for unmatched requests
setDefaultHandler(new NetworkFirst({
cacheName: 'stx-default-cache',
plugins: [
new CacheableResponsePlugin({ statuses: [0, 200] }),
new ExpirationPlugin({ maxEntries: 100, maxAgeSeconds: 60 * 60 * 24 }), // 1 day
],
}));
// ============================================================================
// Service Worker Lifecycle
// ============================================================================
${J.skipWaiting?`// Skip waiting - activate immediately
self.skipWaiting();`:"// Manual skipWaiting - user must trigger update"}
${J.clientsClaim?`// Claim clients - take control immediately
self.addEventListener('activate', () => {
self.clients.claim();
});`:"// Manual clients claim"}
// ============================================================================
// Message Handling
// ============================================================================
self.addEventListener('message', (event) => {
const { type, payload } = event.data || {};
switch (type) {
case 'SKIP_WAITING':
self.skipWaiting();
break;
case 'GET_VERSION':
event.source?.postMessage({ type: 'VERSION', payload: CACHE_VERSION });
break;
case 'CACHE_URLS':
if (Array.isArray(payload)) {
event.waitUntil(
caches.open('stx-dynamic-cache').then(cache => cache.addAll(payload))
);
}
break;
case 'CLEAR_CACHE':
event.waitUntil(
caches.keys().then(names =>
Promise.all(names.map(name => caches.delete(name)))
)
);
break;
}
});
${X?.enabled?g1(X):"// Push notifications disabled"}
${Q?.enabled?h1(Q):"// Background sync disabled"}
console.log('[Workbox SW] Service Worker loaded - Version:', CACHE_VERSION);
`}function x1($){let Z=[];for(let Y of $){let J=Y.name.replace("workbox-","");Z.push(`const { ${Y.exports.join(", ")} } = workbox.${f1(J)};`)}return Z.join(`
`)}function f1($){return $.replace(/-([a-z])/g,(Z,Y)=>Y.toUpperCase())}function g1($){return`
// ============================================================================
// Push Notifications
// ============================================================================
const PUSH_CONFIG = {
defaultIcon: '${$?.defaultIcon||"/pwa-icons/icon-192x192.png"}',
defaultBadge: '${$?.defaultBadge||"/pwa-icons/icon-72x72.png"}',
};
self.addEventListener('push', (event) => {
console.log('[Workbox SW] Push notification received');
let data = {
title: 'Notification',
body: 'You have a new notification',
icon: PUSH_CONFIG.defaultIcon,
badge: PUSH_CONFIG.defaultBadge,
};
if (event.data) {
try {
const payload = event.data.json();
data = { ...data, ...payload };
} catch {
data.body = event.data.text();
}
}
const options = {
body: data.body,
icon: data.icon || PUSH_CONFIG.defaultIcon,
badge: data.badge || PUSH_CONFIG.defaultBadge,
vibrate: data.vibrate || [100, 50, 100],
data: {
url: data.url || '/',
...data.data,
},
actions: data.actions || [],
tag: data.tag,
renotify: data.renotify || false,
requireInteraction: data.requireInteraction || false,
};
event.waitUntil(
self.registration.showNotification(data.title, options)
);
});
self.addEventListener('notificationclick', (event) => {
console.log('[Workbox SW] Notification clicked');
event.notification.close();
const url = event.notification.data?.url || '/';
event.waitUntil(
clients.matchAll({ type: 'window', includeUncontrolled: true }).then(windowClients => {
for (const client of windowClients) {
if (client.url === url && 'focus' in client) {
return client.focus();
}
}
if (clients.openWindow) {
return clients.openWindow(url);
}
})
);
});
`}function h1($){return`
// ============================================================================
// Background Sync
// ============================================================================
const syncQueue = new Queue('${$?.queueName||"stx-sync-queue"}', {
maxRetentionTime: ${$?.maxRetentionMinutes||1440}, // minutes
onSync: async ({ queue }) => {
let entry;
while ((entry = await queue.shiftRequest())) {
try {
await fetch(entry.request.clone());
console.log('[Workbox SW] Sync succeeded:', entry.request.url);
// Notify clients
const allClients = await self.clients.matchAll();
allClients.forEach(client => {
client.postMessage({
type: 'SYNC_COMPLETE',
url: entry.request.url,
success: true,
});
});
} catch (error) {
console.log('[Workbox SW] Sync failed, re-queuing:', entry.request.url);
await queue.unshiftRequest(entry);
throw error;
}
}
},
});
// Register routes that should use background sync
${$?.endpoints?.map((Z)=>`
registerRoute(
({ url }) => url.pathname.startsWith('${Z}'),
new NetworkOnly({
plugins: [
new BackgroundSyncPlugin('${$?.queueName||"stx-sync-queue"}', {
maxRetentionTime: ${$?.maxRetentionMinutes||1440},
}),
],
}),
'POST'
);`).join(`
`)||"// No background sync endpoints configured"}
// Handle form sync messages
self.addEventListener('message', (event) => {
if (event.data?.type === 'REGISTER_FORM_SYNC') {
const { action, method, data } = event.data.payload;
const request = new Request(action, {
method: method || 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams(data).toString(),
});
event.waitUntil(syncQueue.pushRequest({ request }));
}
});
`}function R6($){return $.pwa?.enabled===!0&&$.pwa?.serviceWorker?.useWorkbox===!0}function d1($){if(R6($))return F6;let{generateServiceWorker:Z}=(Q5(),K6(V6));return Z}var N6=I(()=>{G5()});var V6={};o(V6,{isWorkboxEnabled:()=>M6,getServiceWorkerFileName:()=>m4,generateServiceWorkerAuto:()=>p1,generateServiceWorker:()=>k5});function k5($,Z){let Y=$.pwa;if(!Y?.enabled)return"";let J=Y.serviceWorker||{},G=Y.routes||[],X=J.cacheVersion||"1.0.0",Q=Y.offline,K=Q?.page?"/offline.html":"/offline.html",q=Y.push,_=Y.backgroundSync,j=Y.cacheStorage,B=Z?b4(Z,$):null,U=B?E5(B):"[]",z=["/","/manifest.json",...Q?.enabled?[K]:[],...Q?.precacheAssets||[]];return`/**
* Service Worker - Auto-generated by stx
* Cache Version: ${X}
*
* This service worker implements configurable caching strategies:
* - cache-first: Serve from cache, fall back to network
* - network-first: Try network, fall back to cache
* - stale-while-revalidate: Serve cached, update in background
* - network-only: Always fetch from network
* - cache-only: Only serve from cache
*
* Additional features:
* - Push notifications: ${q?.enabled?"enabled":"disabled"}
* - Background sync: ${_?.enabled?"enabled":"disabled"}
* - Cache limits: ${j?.maxSize?`${H6(j.maxSize*1024*1024)}`:"unlimited"}
*/
const CACHE_VERSION = '${X}';
const CACHE_NAME = 'stx-pwa-v' + CACHE_VERSION;
const OFFLINE_PAGE = '${K}';
// Assets to precache on install
const PRECACHE_ASSETS = ${JSON.stringify(z,null,2)};
// Auto-generated precache manifest from build output
const BUILD_PRECACHE_MANIFEST = ${U};
// Route caching strategies
const ROUTE_STRATEGIES = ${JSON.stringify(G,null,2)};
// Routes to exclude from caching
const EXCLUDED_ROUTES = ${JSON.stringify(J.excludeRoutes||[],null,2)};
// Cache storage configuration
const CACHE_CONFIG = {
maxSize: ${j?.maxSize||0} * 1024 * 1024, // Convert MB to bytes
maxAge: ${j?.maxAge||0} * 24 * 60 * 60 * 1000, // Convert days to ms
maxEntries: ${j?.maxEntries||0},
purgeStrategy: '${j?.purgeStrategy||"lru"}',
};
// Background sync configuration
const SYNC_CONFIG = {
enabled: ${_?.enabled||!1},
queueName: '${_?.queueName||"stx-sync-queue"}',
maxRetries: ${_?.maxRetries||3},
minInterval: ${_?.minInterval||1000},
};
// Push notification configuration
const PUSH_CONFIG = {
enabled: ${q?.enabled||!1},
defaultIcon: '${q?.defaultIcon||"/pwa-icons/icon-192x192.png"}',
defaultBadge: '${q?.defaultBadge||"/pwa-icons/icon-72x72.png"}',
};
/**
* Install event - precache essential assets
*/
self.addEventListener('install', (event) => {
console.log('[SW] Installing service worker...');
${J.skipWaiting?"self.skipWaiting();":""}
event.waitUntil(
caches.open(CACHE_NAME).then(async (cache) => {
// Combine manual precache assets with auto-generated manifest
const allAssets = [...PRECACHE_ASSETS];
// Add build precache manifest entries
for (const entry of BUILD_PRECACHE_MANIFEST) {
if (!allAssets.includes(entry.url)) {
allAssets.push(entry.url);
}
}
console.log('[SW] Precaching assets:', allAssets.length);
// Cache assets one by one to handle failures gracefully
const results = await Promise.allSettled(
allAssets.map(async (url) => {
try {
const response = await fetch(url);
if (response.ok) {
await cache.put(url, response);
return { url, success: true };
}
return { url, success: false, reason: response.status };
} catch (error) {
return { url, success: false, reason: error.message };
}
})
);
const failed = results.filter(r => r.status === 'rejected' || (r.status === 'fulfilled' && !r.value.success));
if (failed.length > 0) {
console.warn('[SW] Failed to precache some assets:', failed.length);
}
})
);
});
/**
* Activate event - clean up old caches
*/
self.addEventListener('activate', (event) => {
console.log('[SW] Activating service worker...');
${J.clientsClaim?"self.clients.claim();":""}
event.waitUntil(
(async () => {
// Clean up old caches
const cacheNames = await caches.keys();
await Promise.all(
cacheNames
.filter((name) => name.startsWith('stx-pwa-') && name !== CACHE_NAME)
.map((name) => {
console.log('[SW] Deleting old cache:', name);
return caches.delete(name);
})
);
// Enforce cache storage limits
if (CACHE_CONFIG.maxSize > 0 || CACHE_CONFIG.maxEntries > 0) {
await enforceCacheLimits();
}
})()
);
});
/**
* Fetch event - apply caching strategies
*/
self.addEventListener('fetch', (event) => {
const { request } = event;
const url = new URL(request.url);
// Only handle GET requests
if (request.method !== 'GET') {
return;
}
// Skip cross-origin requests
if (url.origin !== location.origin) {
return;
}
// Skip excluded routes
if (isExcludedRoute(url.pathname)) {
return;
}
// Find matching strategy and apply
const routeConfig = getRouteConfig(url.pathname);
event.respondWith(applyStrategy(request, routeConfig));
});
/**
* Message event - handle messages from main thread
*/
self.addEventListener('message', (event) => {
const { type, payload } = event.data || {};
switch (type) {
case 'SKIP_WAITING':
console.log('[SW] Received SKIP_WAITING message');
self.skipWaiting();
break;
case 'CACHE_URLS':
if (Array.isArray(payload)) {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => cache.addAll(payload))
);
}
break;
case 'CLEAR_CACHE':
event.waitUntil(
caches.keys().then((names) =>
Promise.all(names.map((name) => caches.delete(name)))
)
);
break;
case 'GET_CACHE_SIZE':
event.waitUntil(
getCacheSize().then((size) => {
event.source?.postMessage({ type: 'CACHE_SIZE', payload: size });
})
);
break;
}
});
${q?.enabled?m1():"// Push notifications disabled"}
${_?.enabled?u1():"// Background sync disabled"}
/**
* Check if a route should be excluded from caching
*/
function isExcludedRoute(pathname) {
return EXCLUDED_ROUTES.some((pattern) => matchPattern(pathname, pattern));
}
/**
* Match a path against a glob pattern
* Supports: *.ext, /path/*, /path/**, exact matches
*/
function matchPattern(path, pattern) {
// Handle file extension patterns like *.js, *.css
if (pattern.startsWith('*.')) {
return path.endsWith(pattern.slice(1));
}
// Handle patterns ending with /**
if (pattern.endsWith('/**')) {
const prefix = pattern.slice(0, -3);
return path.startsWith(prefix);
}
// Handle patterns ending with /*
if (pattern.endsWith('/*')) {
const prefix = pattern.slice(0, -2);
if (!path.startsWith(prefix)) return false;
const rest = path.slice(prefix.length);
return rest.length > 0 && !rest.slice(1).includes('/');
}
// Handle patterns with * in the middle
if (pattern.includes('*')) {
const parts = pattern.split('*');
if (parts.length === 2) {
return path.startsWith(parts[0]) && path.endsWith(parts[1]);
}
}
// Exact match
return path === pattern;
}
/**
* Get the cache configuration for a given path
*/
function getRouteConfig(pathname) {
for (const route of ROUTE_STRATEGIES) {
if (matchPattern(pathname, route.pattern)) {
return route;
}
}
// Default to network-first for unmatched routes
return { strategy: 'network-first' };
}
/**
* Apply the appropriate caching strategy
*/
async function applyStrategy(request, routeConfig) {
const { strategy, cacheName, maxAgeSeconds, maxEntries } = routeConfig;
const cache = await caches.open(cacheName || CACHE_NAME);
switch (strategy) {
case 'cache-first':
return cacheFirst(request, cache, maxAgeSeconds);
case 'network-first':
return networkFirst(request, cache);
case 'stale-while-revalidate':
return staleWhileRevalidate(request, cache);
case 'network-only':
return networkOnly(request);
case 'cache-only':
return cacheOnly(request, cache);
default:
return networkFirst(request, cache);
}
}
/**
* Cache-first strategy
* Serve from cache if available, otherwise fetch from network
*/
async function cacheFirst(request, cache, maxAge) {
const cached = await cache.match(request);
if (cached) {
// Check if cache is still valid (if maxAge is set)
if (maxAge) {
const cachedDate = cached.headers.get('sw-cache-date');
if (cachedDate) {
const age = (Date.now() - new Date(cachedDate).getTime()) / 1000;
if (age > maxAge) {
// Cache expired, fetch fresh
return fetchAndCache(request, cache);
}
}
}
return cached;
}
return fetchAndCache(request, cache);
}
/**
* Network-first strategy
* Try network first, fall back to cache if offline
*/
async function networkFirst(request, cache) {
try {
const response = await fetch(request);
if (response.ok) {
const cloned = response.clone();
cache.put(request, cloned);
}
return response;
} catch (error) {
const cached = await cache.match(request);
if (cached) {
return cached;
}
return offlineResponse(request);
}
}
/**
* Stale-while-revalidate strategy
* Serve cached immediately, update cache in background
*/
async function staleWhileRevalidate(request, cache) {
const cached = await cache.match(request);
// Fetch in background to update cache
const fetchPromise = fetch(request)
.then((response) => {
if (response.ok) {
cache.put(request, response.clone());
}
return response;
})
.catch(() => null);
// Return cached immediately if available, otherwise wait for fetch
if (cached) {
return cached;
}
const response = await fetchPromise;
return response || offlineResponse(request);
}
/**
* Network-only strategy
* Always fetch from network, no caching
*/
async function networkOnly(request) {
try {
return await fetch(request);
} catch (error) {
return offlineResponse(request);
}
}
/**
* Cache-only strategy
* Only serve from cache, never fetch
*/
async function cacheOnly(request, cache) {
const cached = await cache.match(request);
return cached || offlineResponse(request);
}
/**
* Fetch and cache a request
*/
async function fetchAndCache(request, cache) {
try {
const response = await fetch(request);
if (response.ok) {
// Add cache timestamp header
const headers = new Headers(response.headers);
headers.set('sw-cache-date', new Date().toISOString());
const cachedResponse = new Response(response.clone().body, {
status: response.status,
statusText: response.statusText,
headers,
});
cache.put(request, cachedResponse);
}
return response;
} catch (error) {
return offlineResponse(request);
}
}
/**
* Generate offline response
*/
async function offlineResponse(request) {
// For navigation requests, return offline page
if (request.mode === 'navigate') {
const cache = await caches.open(CACHE_NAME);
const offlinePage = await cache.match(OFFLINE_PAGE);
if (offlinePage) {
return offlinePage;
}
}
// Return a basic offline response
return new Response('You are offline', {
status: 503,
statusText: 'Service Unavailable',
headers: {
'Content-Type': 'text/plain',
},
});
}
/**
* Get total cache size
*/
async function getCacheSize() {
let totalSize = 0;
const cacheNames = await caches.keys();
for (const cacheName of cacheNames) {
const cache = await caches.open(cacheName);
const keys = await cache.keys();
for (const request of keys) {
const response = await cache.match(request);
if (response) {
const blob = await response.clone().blob();
totalSize += blob.size;
}
}
}
return totalSize;
}
/**
* Enforce cache storage limits
*/
async function enforceCacheLimits() {
const cache = await caches.open(CACHE_NAME);
const keys = await cache.keys();
// Get cache entries with metadata
const entries = [];
for (const request of keys) {
const response = await cache.match(request);
if (response) {
const blob = await response.clone().blob();
const cachedDate = response.headers.get('sw-cache-date');
entries.push({
request,
size: blob.size,
date: cachedDate ? new Date(cachedDate) : new Date(),
});
}
}
// Check total size limit
if (CACHE_CONFIG.maxSize > 0) {
const totalSize = entries.reduce((sum, e) => sum + e.size, 0);
if (totalSize > CACHE_CONFIG.maxSize) {
console.log('[SW] Cache size exceeded, purging...');
await purgeCache(entries, cache, CACHE_CONFIG.maxSize);
}
}
// Check max entries limit
if (CACHE_CONFIG.maxEntries > 0 && entries.length > CACHE_CONFIG.maxEntries) {
console.log('[SW] Cache entries exceeded, purging...');
await purgeByEntryCount(entries, cache, CACHE_CONFIG.maxEntries);
}
// Check max age limit
if (CACHE_CONFIG.maxAge > 0) {
const now = Date.now();
const expiredEntries = entries.filter(e => (now - e.date.getTime()) > CACHE_CONFIG.maxAge);
for (const entry of expiredEntries) {
console.log('[SW] Removing expired cache entry:', entry.request.url);
await cache.delete(entry.request);
}
}
}
/**
* Purge cache to meet size limit
*/
async function purgeCache(entries, cache, maxSize) {
// Sort by purge strategy
const sorted = [...entries];
switch (CACHE_CONFIG.purgeStrategy) {
case 'lru':
// Least Recently Used - remove oldest first
sorted.sort((a, b) => a.date.getTime() - b.date.getTime());
break;
case 'fifo':
// First In First Out - same as lru for our purposes
sorted.sort((a, b) => a.date.getTime() - b.date.getTime());
break;
case 'lfu':
// Least Frequently Used - we don't track frequency, use size as proxy
sorted.sort((a, b) => b.size - a.size);
break;
default:
sorted.sort((a, b) => a.date.getTime() - b.date.getTime());
}
let currentSize = entries.reduce((sum, e) => sum + e.size, 0);
for (const entry of sorted) {
if (currentSize <= maxSize * 0.8) { // Keep 80% buffer
break;
}
// Don't delete essential assets
const url = entry.request.url;
if (PRECACHE_ASSETS.some(asset => url.endsWith(asset))) {
continue;
}
console.log('[SW] Purging cache entry:', url);
await cache.delete(entry.request);
currentSize -= entry.size;
}
}
/**
* Purge cache by entry count
*/
async function purgeByEntryCount(entries, cache, maxEntries) {
const sorted = [...entries].sort((a, b) => a.date.getTime() - b.date.getTime());
const toRemove = sorted.slice(0, entries.length - maxEntries);
for (const entry of toRemove) {
// Don't delete essential assets
const url = entry.request.url;
if (PRECACHE_ASSETS.some(asset => url.endsWith(asset))) {
continue;
}
await cache.delete(entry.request);
}
}
console.log('[SW] Service Worker loaded - Version:', CACHE_VERSION);
`}function m1(){return`
/**
* Push event - handle incoming push notifications
*/
self.addEventListener('push', (event) => {
console.log('[SW] Push notification received');
let data = {
title: 'Notification',
body: 'You have a new notification',
icon: PUSH_CONFIG.defaultIcon,
badge: PUSH_CONFIG.defaultBadge,
};
if (event.data) {
try {
const payload = event.data.json();
data = { ...data, ...payload };
} catch {
data.body = event.data.text();
}
}
const options = {
body: data.body,
icon: data.icon || PUSH_CONFIG.defaultIcon,
badge: data.badge || PUSH_CONFIG.defaultBadge,
vibrate: data.vibrate || [100, 50, 100],
data: {
url: data.url || '/',
...data.data,
},
actions: data.actions || [],
tag: data.tag,
renotify: data.renotify || false,
requireInteraction: data.requireInteraction || false,
silent: data.silent || false,
};
event.waitUntil(
self.registration.showNotification(data.title, options)
);
});
/**
* Notification click event - handle notification interactions
*/
self.addEventListener('notificationclick', (event) => {
console.log('[SW] Notification clicked:', event.action);
event.notification.close();
const url = event.notification.data?.url || '/';
event.waitUntil(
clients.matchAll({ type: 'window', includeUncontrolled: true }).then((windowClients) => {
// Check if there's already a window open with this URL
for (const client of windowClients) {
if (client.url === url && 'focus' in client) {
return client.focus();
}
}
// Open a new window
if (clients.openWindow) {
return clients.openWindow(url);
}
})
);
});
/**
* Notification close event
*/
self.addEventListener('notificationclose', (event) => {
console.log('[SW] Notification closed');
// Track notification dismissal if needed
const data = event.notification.data;
if (data?.trackDismiss) {
// Could send analytics here
}
});
`}function u1(){return`
/**
* Background sync queue storage (IndexedDB)
*/
const SYNC_STORE = 'stx-sync-store';
let syncDb = null;
async function getSyncDb() {
if (syncDb) return syncDb;
return new Promise((resolve, reject) => {
const request = indexedDB.open(SYNC_STORE, 1);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
syncDb = request.result;
resolve(syncDb);
};
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains('queue')) {
db.createObjectStore('queue', { keyPath: 'id', autoIncrement: true });
}
};
});
}
/**
* Add request to sync queue
*/
async function addToSyncQueue(request) {
const db = await getSyncDb();
const tx = db.transaction('queue', 'readwrite');
const store = tx.objectStore('queue');
const data = {
url: request.url,
method: request.method,
headers: Object.fromEntries(request.headers.entries()),
body: await request.clone().text(),
timestamp: Date.now(),
retries: 0,
};
return new Promise((resolve, reject) => {
const req = store.add(data);
req.onsuccess = () => resolve(req.result);
req.onerror = () => reject(req.error);
});
}
/**
* Get all pending sync requests
*/
async function getPendingSyncRequests() {
const db = await getSyncDb();
const tx = db.transaction('queue', 'readonly');
const store = tx.objectStore('queue');
return new Promise((resolve, reject) => {
const req = store.getAll();
req.onsuccess = () => resolve(req.result);
req.onerror = () => reject(req.error);
});
}
/**
* Remove request from sync queue
*/
async function removeSyncRequest(id) {
const db = await getSyncDb();
const tx = db.transaction('queue', 'readwrite');
const store = tx.objectStore('queue');
return new Promise((resolve, reject) => {
const req = store.delete(id);
req.onsuccess = () => resolve();
req.onerror = () => reject(req.error);
});
}
/**
* Update sync request retry count
*/
async function updateSyncRetries(id, retries) {
const db = await getSyncDb();
const tx = db.transaction('queue', 'readwrite');
const store = tx.objectStore('queue');
const req = store.get(id);
req.onsuccess = () => {
const data = req.result;
if (data) {
data.retries = retries;
store.put(data);
}
};
}
/**
* Sync event - process queued requests when online
*/
self.addEventListener('sync', (event) => {
console.log('[SW] Sync event:', event.tag);
if (event.tag === SYNC_CONFIG.queueName || event.tag === 'stx-form-sync') {
event.waitUntil(processBackgroundSync());
}
});
/**
* Process all queued sync requests
*/
async function processBackgroundSync() {
console.log('[SW] Processing background sync queue');
const requests = await getPendingSyncRequests();
console.log('[SW] Pending sync requests:', requests.length);
for (const request of requests) {
if (request.retries >= SYNC_CONFIG.maxRetries) {
console.log('[SW] Max retries reached, removing:', request.url);
await removeSyncRequest(request.id);
continue;
}
try {
const response = await fetch(request.url, {
method: request.method,
headers: request.headers,
body: request.method !== 'GET' ? request.body : undefined,
});
if (response.ok) {
console.log('[SW] Sync succeeded:', request.url);
await removeSyncRequest(request.id);
// Notify clients of successful sync
const clients = await self.clients.matchAll();
clients.forEach(client => {
client.postMessage({
type: 'SYNC_COMPLETE',
url: request.url,
success: true,
});
});
} else {
throw new Error('Response not ok: ' + response.status);
}
} catch (error) {
console.log('[SW] Sync failed, will retry:', request.url, error.message);
await updateSyncRetries(request.id, request.retries + 1);
}
}
}
/**
* Register a form for background sync
* Called from main thread via message
*/
async function registerFormSync(formData) {
// Store form data in IndexedDB
await addToSyncQueue({
url: formData.action,
method: formData.method || 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
clone: () => ({ text: async () => new URLSearchParams(formData.data).toString() }),
});
// Register for background sync
await self.registration.sync.register(SYNC_CONFIG.queueName);
}
// Listen for form sync requests
self.addEventListener('message', (event) => {
if (event.data?.type === 'REGISTER_FORM_SYNC') {
event.waitUntil(registerFormSync(event.data.payload));
}
});
`}function m4($){return $.pwa?.serviceWorker?.fileName||"sw.js"}function M6($){return $.pwa?.enabled===!0&&$.pwa?.serviceWorker?.useWorkbox===!0}function p1($,Z){if(!$.pwa?.enabled)return"";if(M6($)){let{generateWorkboxServiceWorker:Y}=(N6(),K6(A6));return Y($,Z)}return k5($,Z)}var Q5=I(()=>{G5()});var w6={};o(w6,{injectPwaTags:()=>I7,generateThemeColorMeta:()=>i1,generateSwRegistrationScript:()=>c1,generatePwaTags:()=>S5,generateManifestLink:()=>l1});function S5($){let Z=$.pwa;if(!Z?.enabled)return"";let Y=Z.manifest,J=Z.icons?.outputDir?`/${Z.icons.outputDir}`:"/pwa-icons",G=m4($),X=`
<!-- stx PWA Meta Tags -->
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="${Y?.themeColor||"#000000"}">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="${T6(Y?.shortName||Y?.name||"App")}">`;if(Z.icons?.generateAppleIcons!==!1)X+=`
<link rel="apple-touch-icon" href="${J}/apple-touch-icon.png">
<link rel="apple-touch-icon" sizes="120x120" href="${J}/apple-touch-icon-120x120.png">
<link rel="apple-touch-icon" sizes="152x152" href="${J}/apple-touch-icon-152x152.png">
<link rel="apple-touch-icon" sizes="167x167" href="${J}/apple-touch-icon-167x167.png">
<link rel="apple-touch-icon" sizes="180x180" href="${J}/apple-touch-icon-180x180.png">`;return X+=`
<link rel="icon" type="image/png" sizes="32x32" href="${J}/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="${J}/favicon-16x16.png">
<link rel="icon" type="image/png" sizes="192x192" href="${J}/icon-192x192.png">`,X+=`
<!-- stx Service Worker Registration -->
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/${G}')
.then(function(registration) {
console.log('[PWA] Service Worker registered:', registration.scope);
// Check for updates
registration.addEventListener('updatefound', function() {
const newWorker = registration.installing;
if (newWorker) {
newWorker.addEventListener('statechange', function() {
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
console.log('[PWA] New content available, refresh to update');
// Optionally dispatch a custom event for UI notification
window.dispatchEvent(new CustomEvent('pwa-update-available'));
}
});
}
});
})
.catch(function(error) {
console.error('[PWA] Service Worker registration failed:', error);
});
});
}
</script>
<!-- End stx PWA -->`,X}function I7($,Z){let Y=Z.pwa;if(!Y?.enabled||Y.autoInject===!1)return $;if($.includes("<!-- stx PWA Meta Tags -->"))return $;if(!$.includes("</head>")){if($.includes("</body>")){let G=S5(Z);return $.replace("</body>",`${G}
</body>`)}return $+S5(Z)}let J=S5(Z);return $.replace("</head>",`${J}
</head>`)}function c1($){if(!$.pwa?.enabled)return"";return`<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/${m4($)}')
.then(function(registration) {
console.log('[PWA] Service Worker registered:', registration.scope);
})
.catch(function(error) {
console.error('[PWA] Service Worker registration failed:', error);
});
});
}
</script>`}function l1(){return'<link rel="manifest" href="/manifest.json">'}function i1($){return`<meta name="theme-color" content="${T6($)}">`}function T6($){return $.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}var b5=I(()=>{Q5()});var U4;var D6=I(()=>{U4={reset:"\x1B[0m",bright:"\x1B[1m",dim:"\x1B[2m",underscore:"\x1B[4m",blink:"\x1B[5m",reverse:"\x1B[7m",hidden:"\x1B[8m",black:"\x1B[30m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",magenta:"\x1B[35m",cyan:"\x1B[36m",white:"\x1B[37m",gray:"\x1B[90m",bgBlack:"\x1B[40m",bgRed:"\x1B[41m",bgGreen:"\x1B[42m",bgYellow:"\x1B[43m",bgBlue:"\x1B[44m",bgMagenta:"\x1B[45m",bgCyan:"\x1B[46m",bgWhite:"\x1B[47m",bgGray:"\x1B[100m"}});import S6 from"fs";import K4 from"path";async function k6($){try{let Z=await import($);if(Z&&Z.CSSGenerator)return{CSSGenerator:Z.CSSGenerator,config:Z.config,build:Z.build,defaultConfig:Z.defaultConfig}}catch{}return null}function e1(){let $=[],Z=process.env.HOME||process.env.USERPROFILE||"",Y=process.cwd();while(Y!==K4.dirname(Y))$.push(K4.join(Y,"node_modules","@cwcss","crosswind","dist","index.js")),$.push(K4.join(Y,"node_modules","@cwcss","crosswind","src","index.ts")),$.push(K4.join(Y,"node_modules","@stacksjs","crosswind","dist","index.js")),Y=K4.dirname(Y);if(Z){let J=[K4.join(Z,"Code","Tools","crosswind","packages","crosswind","dist","index.js"),K4.join(Z,"Code","Tools","crosswind","packages","crosswind","src","index.ts"),K4.join(Z,"repos","stacks-org","crosswind","packages","crosswind","dist","index.js"),K4.join(Z,"repos","stacks-org","crosswind","packages","crosswind","src","index.ts"),K4.join(Z,"Code","Tools","stx","packages","stx","node_modules","@cwcss","crosswind","dist","index.js"),K4.join(Z,"Code","Tools","stx","packages","stx","node_modules","@stacksjs","crosswind","dist","index.js")];$.push(...J)}return $.push(K4.join(process.cwd(),"..","crosswind","packages","crosswind","dist","index.js")),$.push(K4.join(process.cwd(),"..","crosswind","packages","crosswind","src","index.ts")),$}async function $Y(){if(E6)return K5;E6=!0;try{let $=["@cwcss/crosswind","@cwcss/crosswind/dist/index.js","@stacksjs/crosswind","@stacksjs/crosswind/dist/index.js"];for(let Y of $){let J=await k6(Y);if(J)return K5=J,console.log(`${U4.green}[Crosswind]${U4.reset} CSS engine loaded`),K5}let Z=e1();for(let Y of Z)if(S6.existsSync(Y)){let J=await k6(Y);if(J)return K5=J,console.log(`${U4.green}[Crosswind]${U4.reset} CSS engine loaded from ${K4.dirname(K4.dirname(Y))}`),K5}throw Error("Crosswind CSSGenerator not found in any location")}catch{return console.warn(`${U4.yellow}[Crosswind] CSS engine not available, Tailwind styles will not be generated${U4.reset}`),console.warn(`${U4.yellow}Run 'bun add @stacksjs/crosswind' to enable CSS generation${U4.reset}`),null}}async function ZY($){let Z=["crosswind.config.ts","crosswind.config.js","crosswind.config.mjs"];for(let Y of Z){let J=K4.join($,Y);if(S6.existsSync(J))try{let G=await import(J),X=G.default||G;return console.log(`${U4.green}[Crosswind]${U4.reset} Loaded config from ${Y}`),X}catch(G){console.warn(`${U4.yellow}[Crosswind]${U4.reset} Failed to load ${Y}:`,G)}}return null}function YY($){let Z=/class\s*=\s*["']([^"']+)["']/gi,Y=new Set,J=Z.exec($);while(J!==null){let G=J[1];for(let X of G.split(/\s+/))if(X.trim())Y.add(X.trim());J=Z.exec($)}return Y}async function JY($){try{let Z=await $Y();if(!Z)return"";let Y=YY($);if(Y.size===0)return"";if(!D7)D7=await ZY(process.cwd());let J=Z.defaultConfig||Z.config,G=D7||{},X=J.theme||{},K=(G.theme||{}).extend||{},q={...X};if(Object.keys(K).length>0)q.extend=K;let _=[...J.safelist||[],...G.safelist||[]],j={...J,...G,content:[],output:"",preflight:!0,minify:!1,theme:q,safelist:_},B=new Z.CSSGenerator(j);for(let U of _)B.generate(U);for(let U of Y)B.generate(U);return B.toCSS(!0,!1)}catch(Z){return console.warn("Failed to generate Crosswind CSS:",Z),""}}async function b6($){let Z=await JY($);if(!Z)return $;let Y=`<style data-crosswind="generated">
${Z}
</style>`;if($.includes("</head>"))return $.replace("</head>",`${Y}
</head>`);if($.includes("<body"))return $.replace(/<body([^>]*)>/,`<body$1>
${Y}`);return Y+$}var K5=null,E6=!1,D7=null;var P6=I(()=>{D6()});function y6($,Z,Y,J){let G=$;G=G.replace(/@a11y\(\s*['"]([^'"]+)['"]\s*(?:,\s*['"]([^'"]+)['"]\s*)?\)/g,(Q,K,q)=>{let j={"aria-label":"Ensure interactive elements have accessible labels","alt-text":"Provide alternative text for images",focus:"Ensure the element can receive keyboard focus",landmark:"Use appropriate landmark roles","heading-order":"Maintain proper heading hierarchy","color-contrast":"Ensure sufficient color contrast","keyboard-nav":"Make sure element is keyboard navigable","screen-reader":"Optimize for screen reader users"}[K]||"Make this element accessible";return`<!-- a11y-hint: ${q||j} -->`});let X=/@screenReader\(([^@]*)\)@endScreenReader/g;return G=G.replace(X,(Q,K)=>{return`<span class="sr-only">${K.trim()}</span>`}),G=G.replace(/@ariaDescribe\(\s*['"]([^'"]+)['"]\s*,\s*['"]([^'"]+)['"]\s*\)/g,(Q,K,q)=>{return`<span id="${`desc-${K}`}" class="sr-only">${q}</span>`}),G}var C6,v6;var E7=I(()=>{C6={name:"a11y",handler:($,Z,Y,J)=>{if(!Z.length)return $;let G=Z[0].replace(/['"]/g,""),X=Z.length>1?Z[1].replace(/['"]/g,""):"",K={"aria-label":"Ensure interactive elements have accessible labels","alt-text":"Provide alternative text for images",focus:"Ensure the element can receive keyboard focus",landmark:"Use appropriate landmark roles","heading-order":"Maintain proper heading hierarchy","color-contrast":"Ensure sufficient color contrast","keyboard-nav":"Make sure element is keyboard navigable","screen-reader":"Optimize for screen reader users"}[G]||"Make this element accessible";return`<!-- a11y-hint: ${X||K} -->${$}`},hasEndTag:!1},v6={name:"screenReader",handler:($)=>{return`<span class="sr-only">${$}</span>`},hasEndTag:!0}});function x6(){return`
// STX Lifecycle Runtime
(function() {
if (typeof window === 'undefined') return;
const instances = new Map();
let currentInstance = null;
let idCounter = 0;
function generateId() {
return 'stx-' + (++idCounter) + '-' + Date.now().toString(36);
}
function createInstance(element) {
const instance = {
id: generateId(),
element: element,
mountHooks: [],
destroyHooks: [],
updateHooks: [],
refs: new Map(),
watchers: [],
isMounted: false
};
instances.set(instance.id, instance);
return instance;
}
window.STX = window.STX || {};
// Lifecycle hooks
window.STX.onMount = function(hook) {
if (currentInstance) currentInstance.mountHooks.push(hook);
};
window.STX.onDestroy = function(hook) {
if (currentInstance) currentInstance.destroyHooks.push(hook);
};
window.STX.onUpdate = function(hook) {
if (currentInstance) currentInstance.updateHooks.push(hook);
};
// Aliases
window.STX.onMounted = window.STX.onMount;
window.STX.onUnmounted = window.STX.onDestroy;
window.STX.onUpdated = window.STX.onUpdate;
// Refs
window.STX.ref = function(initialValue) {
const r = { value: initialValue || null };
Object.defineProperty(r, 'current', {
get: function() { return this.value; }
});
if (currentInstance) {
currentInstance.refs.set('ref-' + currentInstance.refs.size, r);
}
return r;
};
// Watch
window.STX.watch = function(source, callback, options) {
options = options || {};
let oldValue;
let cleanup;
let isActive = true;
function run() {
if (!isActive) return;
const newValue = source();
if (oldValue !== newValue || options.immediate) {
if (cleanup) cleanup();
cleanup = callback(newValue, oldValue);
oldValue = newValue;
}
}
if (options.immediate) {
run();
} else {
oldValue = source();
}
const intervalId = setInterval(run, 16);
function stop() {
isActive = false;
clearInterval(intervalId);
if (cleanup) cleanup();
}
if (currentInstance) {
currentInstance.watchers.push({ stop: stop });
currentInstance.destroyHooks.push(stop);
}
return stop;
};
// Computed
window.STX.computed = function(getter) {
let cached;
let dirty = true;
return {
get value() {
if (dirty) {
cached = getter();
dirty = false;
queueMicrotask(function() { dirty = true; });
}
return cached;
}
};
};
// Component management
window.STX.setupComponent = function(element, setup) {
const instance = createInstance(element);
currentInstance = instance;
try {
setup();
} finally {
currentInstance = null;
}
return instance;
};
window.STX.mountComponent = function(instance) {
if (instance.isMounted) return;
instance.isMounted = true;
instance.mountHooks.forEach(function(hook) {
try {
const