- Static compiler with STRC pattern (Static Template Resolution with
Compartmentalized Layers)
- Template syntax: { } interpolation, { s-for }, { s-if/s-elif/s-else
}
- File types: .strata, .compiler.sts, .service.sts, .api.sts, .sts,
.scss
- CLI tools: strata dev, strata build, strata g (generators)
- create-strata scaffolding CLI with Pokemon API example
- Dev server with WebSocket HMR (Hot Module Replacement)
- Documentation: README, ARCHITECTURE, CHANGELOG, CONTRIBUTING,
LICENSE
232 lines
5.8 KiB
Plaintext
232 lines
5.8 KiB
Plaintext
/**
|
|
* Strata Core Runtime
|
|
* Main entry point for browser runtime
|
|
*/
|
|
|
|
import { StrataStore, createStore, useStore } from '../store/store.sts';
|
|
import { StrataFetch } from '../fetch/fetch.sts';
|
|
|
|
interface StrataConfig {
|
|
encryptionKey?: number[];
|
|
apiBaseUrl?: string;
|
|
devMode?: boolean;
|
|
}
|
|
|
|
interface BroadcastHandler {
|
|
(data: any): void;
|
|
}
|
|
|
|
class Strata {
|
|
private worker: SharedWorker | null = null;
|
|
private _tabId: string = '';
|
|
private messageHandlers: Map<string, Function[]> = new Map();
|
|
private broadcastHandlers: Map<string, BroadcastHandler[]> = new Map();
|
|
private pendingRequests: Map<string, { resolve: Function; reject: Function }> = new Map();
|
|
private config: StrataConfig = {};
|
|
private _fetch: StrataFetch | null = null;
|
|
|
|
get tabId(): string {
|
|
return this._tabId;
|
|
}
|
|
|
|
get fetch(): StrataFetch {
|
|
if (!this._fetch) {
|
|
throw new Error('Strata not initialized. Call strata.init() first.');
|
|
}
|
|
return this._fetch;
|
|
}
|
|
|
|
async init(config: StrataConfig = {}): Promise<void> {
|
|
this.config = config;
|
|
|
|
// Initialize SharedWorker
|
|
if (typeof SharedWorker !== 'undefined') {
|
|
this.worker = new SharedWorker(
|
|
new URL('../worker/shared-worker.sts', import.meta.url),
|
|
{ type: 'module', name: 'strata-worker' }
|
|
);
|
|
|
|
this.worker.port.onmessage = (event) => this.handleWorkerMessage(event.data);
|
|
this.worker.port.start();
|
|
|
|
// Send encryption key if provided
|
|
if (config.encryptionKey) {
|
|
this.worker.port.postMessage({
|
|
type: 'setEncryptionKey',
|
|
key: config.encryptionKey,
|
|
});
|
|
}
|
|
|
|
// Wait for connection
|
|
await this.waitForConnection();
|
|
} else {
|
|
// Fallback for browsers without SharedWorker support
|
|
this._tabId = 'tab_' + Math.random().toString(36).slice(2, 10);
|
|
console.warn('SharedWorker not supported, falling back to single-tab mode');
|
|
}
|
|
|
|
// Initialize fetch
|
|
this._fetch = new StrataFetch(this);
|
|
|
|
// Handle page unload
|
|
window.addEventListener('beforeunload', () => {
|
|
this.disconnect();
|
|
});
|
|
|
|
if (config.devMode) {
|
|
(window as any).__STRATA__ = this;
|
|
console.log(`Strata initialized (tabId: ${this._tabId})`);
|
|
}
|
|
}
|
|
|
|
private waitForConnection(): Promise<void> {
|
|
return new Promise((resolve) => {
|
|
const handler = (message: any) => {
|
|
if (message.type === 'connected') {
|
|
this._tabId = message.tabId;
|
|
resolve();
|
|
}
|
|
};
|
|
|
|
this.addMessageHandler('connected', handler);
|
|
});
|
|
}
|
|
|
|
private handleWorkerMessage(message: any) {
|
|
// Handle specific message types
|
|
switch (message.type) {
|
|
case 'connected':
|
|
this._tabId = message.tabId;
|
|
break;
|
|
case 'fetch:response':
|
|
case 'fetch:error':
|
|
this.handleFetchResponse(message);
|
|
break;
|
|
case 'store:value':
|
|
this.handleStoreValue(message);
|
|
break;
|
|
case 'broadcast':
|
|
this.handleBroadcast(message);
|
|
break;
|
|
}
|
|
|
|
// Call registered handlers
|
|
const handlers = this.messageHandlers.get(message.type) || [];
|
|
for (const handler of handlers) {
|
|
handler(message);
|
|
}
|
|
}
|
|
|
|
private handleFetchResponse(message: any) {
|
|
const pending = this.pendingRequests.get(message.requestId);
|
|
if (pending) {
|
|
if (message.type === 'fetch:error') {
|
|
pending.reject(new Error(message.error));
|
|
} else {
|
|
pending.resolve(message.data);
|
|
}
|
|
this.pendingRequests.delete(message.requestId);
|
|
}
|
|
}
|
|
|
|
private handleStoreValue(message: any) {
|
|
// Handled by store subscriptions
|
|
}
|
|
|
|
private handleBroadcast(message: any) {
|
|
const handlers = this.broadcastHandlers.get(message.event) || [];
|
|
for (const handler of handlers) {
|
|
handler(message.data);
|
|
}
|
|
|
|
// Also call wildcard handlers
|
|
const wildcardHandlers = this.broadcastHandlers.get('*') || [];
|
|
for (const handler of wildcardHandlers) {
|
|
handler({ event: message.event, data: message.data });
|
|
}
|
|
}
|
|
|
|
private addMessageHandler(type: string, handler: Function) {
|
|
if (!this.messageHandlers.has(type)) {
|
|
this.messageHandlers.set(type, []);
|
|
}
|
|
this.messageHandlers.get(type)!.push(handler);
|
|
}
|
|
|
|
// Public API
|
|
|
|
sendToWorker(message: any): void {
|
|
if (this.worker) {
|
|
this.worker.port.postMessage(message);
|
|
}
|
|
}
|
|
|
|
async fetchViaWorker(url: string, options?: any): Promise<any> {
|
|
const requestId = crypto.randomUUID();
|
|
|
|
return new Promise((resolve, reject) => {
|
|
this.pendingRequests.set(requestId, { resolve, reject });
|
|
|
|
this.sendToWorker({
|
|
type: 'fetch',
|
|
url,
|
|
options,
|
|
requestId,
|
|
});
|
|
|
|
// Timeout after 30 seconds
|
|
setTimeout(() => {
|
|
if (this.pendingRequests.has(requestId)) {
|
|
this.pendingRequests.delete(requestId);
|
|
reject(new Error('Request timeout'));
|
|
}
|
|
}, 30000);
|
|
});
|
|
}
|
|
|
|
broadcast(event: string, data?: any, targetTabs?: string[]): void {
|
|
this.sendToWorker({
|
|
type: 'broadcast',
|
|
event,
|
|
data,
|
|
targetTabs,
|
|
});
|
|
}
|
|
|
|
onBroadcast(event: string, handler: BroadcastHandler): () => void {
|
|
if (!this.broadcastHandlers.has(event)) {
|
|
this.broadcastHandlers.set(event, []);
|
|
}
|
|
this.broadcastHandlers.get(event)!.push(handler);
|
|
|
|
// Return unsubscribe function
|
|
return () => {
|
|
const handlers = this.broadcastHandlers.get(event);
|
|
if (handlers) {
|
|
const index = handlers.indexOf(handler);
|
|
if (index > -1) {
|
|
handlers.splice(index, 1);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
invalidateCache(pattern: string = '*'): void {
|
|
this.sendToWorker({
|
|
type: 'cache:invalidate',
|
|
pattern,
|
|
});
|
|
}
|
|
|
|
private disconnect(): void {
|
|
this.sendToWorker({ type: 'disconnect' });
|
|
}
|
|
}
|
|
|
|
// Singleton instance
|
|
export const strata = new Strata();
|
|
|
|
// Re-exports
|
|
export { createStore, useStore };
|
|
export type { StrataStore };
|