@b9g/platform 0.1.3 → 0.1.5

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/src/runtime.js ADDED
@@ -0,0 +1,997 @@
1
+ /// <reference types="./runtime.d.ts" />
2
+ // src/runtime.ts
3
+ import { RequestCookieStore } from "./cookie-store.js";
4
+ import { AsyncContext } from "@b9g/async-context";
5
+ import { CustomBucketStorage } from "@b9g/filesystem";
6
+ import { CustomCacheStorage } from "@b9g/cache";
7
+ import { createBucketFactory, createCacheFactory } from "./config.js";
8
+ import { getLogger } from "@logtape/logtape";
9
+ if (import.meta.env && !import.meta.env.MODE && import.meta.env.NODE_ENV) {
10
+ import.meta.env.MODE = import.meta.env.NODE_ENV;
11
+ }
12
+ var cookieStoreStorage = new AsyncContext.Variable();
13
+ function promiseWithTimeout(promise, timeoutMs, errorMessage) {
14
+ return Promise.race([
15
+ promise,
16
+ new Promise(
17
+ (_, reject) => setTimeout(() => reject(new Error(errorMessage)), timeoutMs)
18
+ )
19
+ ]);
20
+ }
21
+ var kEndDispatchPhase = Symbol.for("shovel.endDispatchPhase");
22
+ var kCanExtend = Symbol.for("shovel.canExtend");
23
+ var ExtendableEvent = class extends Event {
24
+ #promises;
25
+ #dispatchPhase;
26
+ #pendingCount;
27
+ constructor(type, eventInitDict) {
28
+ super(type, eventInitDict);
29
+ this.#promises = [];
30
+ this.#dispatchPhase = true;
31
+ this.#pendingCount = 0;
32
+ }
33
+ waitUntil(promise) {
34
+ if (!this.#dispatchPhase && this.#pendingCount === 0) {
35
+ throw new DOMException(
36
+ "waitUntil() must be called synchronously during event dispatch, or while there are pending promises from respondWith()/waitUntil()",
37
+ "InvalidStateError"
38
+ );
39
+ }
40
+ this.#pendingCount++;
41
+ const trackedPromise = promise.finally(() => {
42
+ this.#pendingCount--;
43
+ });
44
+ trackedPromise.catch(() => {
45
+ });
46
+ this.#promises.push(trackedPromise);
47
+ }
48
+ getPromises() {
49
+ return [...this.#promises];
50
+ }
51
+ /** @internal Called after synchronous dispatch completes */
52
+ [kEndDispatchPhase]() {
53
+ this.#dispatchPhase = false;
54
+ }
55
+ /** @internal Check if extensions are still allowed */
56
+ [kCanExtend]() {
57
+ return this.#dispatchPhase || this.#pendingCount > 0;
58
+ }
59
+ };
60
+ var FetchEvent = class extends ExtendableEvent {
61
+ request;
62
+ cookieStore;
63
+ #responsePromise;
64
+ #responded;
65
+ constructor(request, eventInitDict) {
66
+ super("fetch", eventInitDict);
67
+ this.request = request;
68
+ this.cookieStore = new RequestCookieStore(request);
69
+ this.#responsePromise = null;
70
+ this.#responded = false;
71
+ }
72
+ respondWith(response) {
73
+ if (this.#responded) {
74
+ throw new Error("respondWith() already called");
75
+ }
76
+ if (!this[kCanExtend]()) {
77
+ throw new DOMException(
78
+ "respondWith() must be called synchronously during event dispatch",
79
+ "InvalidStateError"
80
+ );
81
+ }
82
+ this.#responded = true;
83
+ this.#responsePromise = Promise.resolve(response);
84
+ this.waitUntil(this.#responsePromise);
85
+ }
86
+ getResponse() {
87
+ return this.#responsePromise;
88
+ }
89
+ hasResponded() {
90
+ return this.#responded;
91
+ }
92
+ };
93
+ var InstallEvent = class extends ExtendableEvent {
94
+ constructor(eventInitDict) {
95
+ super("install", eventInitDict);
96
+ }
97
+ };
98
+ var ActivateEvent = class extends ExtendableEvent {
99
+ constructor(eventInitDict) {
100
+ super("activate", eventInitDict);
101
+ }
102
+ };
103
+ var ShovelClient = class {
104
+ frameType;
105
+ id;
106
+ type;
107
+ url;
108
+ constructor(options) {
109
+ this.frameType = "none";
110
+ this.id = options.id;
111
+ this.url = options.url;
112
+ this.type = options.type || "worker";
113
+ }
114
+ // Implementation
115
+ postMessage(_message, _transferOrOptions) {
116
+ logger.warn(
117
+ "[ServiceWorker] Client.postMessage() not supported in server context"
118
+ );
119
+ }
120
+ };
121
+ var ShovelWindowClient = class extends ShovelClient {
122
+ focused;
123
+ visibilityState;
124
+ constructor(options) {
125
+ super({ ...options, type: "window" });
126
+ this.focused = options.focused || false;
127
+ this.visibilityState = options.visibilityState || "hidden";
128
+ }
129
+ async focus() {
130
+ logger.warn(
131
+ "[ServiceWorker] WindowClient.focus() not supported in server context"
132
+ );
133
+ return this;
134
+ }
135
+ async navigate(_url) {
136
+ logger.warn(
137
+ "[ServiceWorker] WindowClient.navigate() not supported in server context"
138
+ );
139
+ return null;
140
+ }
141
+ };
142
+ var ShovelClients = class {
143
+ async claim() {
144
+ }
145
+ async get(_id) {
146
+ return void 0;
147
+ }
148
+ async matchAll(_options) {
149
+ return [];
150
+ }
151
+ async openWindow(_url) {
152
+ logger.warn(
153
+ "[ServiceWorker] Clients.openWindow() not supported in server context"
154
+ );
155
+ return null;
156
+ }
157
+ };
158
+ var ExtendableMessageEvent = class extends ExtendableEvent {
159
+ data;
160
+ origin;
161
+ lastEventId;
162
+ source;
163
+ ports;
164
+ constructor(type, eventInitDict) {
165
+ super(type, eventInitDict);
166
+ this.data = eventInitDict?.data ?? null;
167
+ this.origin = eventInitDict?.origin ?? "";
168
+ this.lastEventId = eventInitDict?.lastEventId ?? "";
169
+ this.source = eventInitDict?.source ?? null;
170
+ this.ports = Object.freeze([...eventInitDict?.ports ?? []]);
171
+ }
172
+ };
173
+ var ShovelServiceWorker = class extends EventTarget {
174
+ scriptURL;
175
+ state;
176
+ // Event handlers required by Web API
177
+ onstatechange;
178
+ onerror;
179
+ constructor(scriptURL, state = "parsed") {
180
+ super();
181
+ this.scriptURL = scriptURL;
182
+ this.state = state;
183
+ this.onstatechange = null;
184
+ this.onerror = null;
185
+ }
186
+ // Implementation
187
+ postMessage(_message, _transferOrOptions) {
188
+ logger.warn(
189
+ "[ServiceWorker] ServiceWorker.postMessage() not implemented in server context"
190
+ );
191
+ }
192
+ // Internal method to update state and dispatch statechange event
193
+ _setState(newState) {
194
+ if (this.state !== newState) {
195
+ this.state = newState;
196
+ this.dispatchEvent(new Event("statechange"));
197
+ }
198
+ }
199
+ // Events: statechange, error
200
+ };
201
+ var ShovelNavigationPreloadManager = class {
202
+ async disable() {
203
+ }
204
+ async enable() {
205
+ }
206
+ async getState() {
207
+ return { enabled: false, headerValue: "" };
208
+ }
209
+ async setHeaderValue(_value) {
210
+ }
211
+ };
212
+ var ShovelServiceWorkerRegistration = class extends EventTarget {
213
+ scope;
214
+ updateViaCache;
215
+ navigationPreload;
216
+ // ServiceWorker instances representing different lifecycle states
217
+ _serviceWorker;
218
+ // Web API properties (not supported in server context, but required by interface)
219
+ cookies;
220
+ pushManager;
221
+ onupdatefound;
222
+ constructor(scope2 = "/", scriptURL = "/") {
223
+ super();
224
+ this.scope = scope2;
225
+ this.updateViaCache = "imports";
226
+ this.navigationPreload = new ShovelNavigationPreloadManager();
227
+ this._serviceWorker = new ShovelServiceWorker(scriptURL, "parsed");
228
+ this.cookies = null;
229
+ this.pushManager = null;
230
+ this.onupdatefound = null;
231
+ }
232
+ // Standard ServiceWorkerRegistration properties
233
+ get active() {
234
+ return this._serviceWorker.state === "activated" ? this._serviceWorker : null;
235
+ }
236
+ get installing() {
237
+ return this._serviceWorker.state === "installing" ? this._serviceWorker : null;
238
+ }
239
+ get waiting() {
240
+ return this._serviceWorker.state === "installed" ? this._serviceWorker : null;
241
+ }
242
+ // Standard ServiceWorkerRegistration methods
243
+ async getNotifications(_options) {
244
+ return [];
245
+ }
246
+ async showNotification(_title, _options) {
247
+ logger.warn(
248
+ "[ServiceWorker] Notifications not supported in server context"
249
+ );
250
+ }
251
+ async sync() {
252
+ }
253
+ async unregister() {
254
+ return false;
255
+ }
256
+ async update() {
257
+ return this;
258
+ }
259
+ // Shovel runtime extensions (non-standard but needed for platforms)
260
+ /**
261
+ * Install the ServiceWorker (Shovel extension)
262
+ */
263
+ async install() {
264
+ if (this._serviceWorker.state !== "parsed")
265
+ return;
266
+ this._serviceWorker._setState("installing");
267
+ return new Promise((resolve, reject) => {
268
+ const event = new InstallEvent();
269
+ process.nextTick(() => {
270
+ try {
271
+ this.dispatchEvent(event);
272
+ } catch (error) {
273
+ process.nextTick(() => {
274
+ throw error;
275
+ });
276
+ }
277
+ const promises = event.getPromises();
278
+ if (promises.length === 0) {
279
+ this._serviceWorker._setState("installed");
280
+ resolve();
281
+ } else {
282
+ promiseWithTimeout(
283
+ Promise.all(promises),
284
+ 3e4,
285
+ "ServiceWorker install event timed out after 30s - waitUntil promises did not resolve"
286
+ ).then(() => {
287
+ this._serviceWorker._setState("installed");
288
+ resolve();
289
+ }).catch(reject);
290
+ }
291
+ });
292
+ });
293
+ }
294
+ /**
295
+ * Activate the ServiceWorker (Shovel extension)
296
+ */
297
+ async activate() {
298
+ if (this._serviceWorker.state !== "installed") {
299
+ throw new Error("ServiceWorker must be installed before activation");
300
+ }
301
+ this._serviceWorker._setState("activating");
302
+ return new Promise((resolve, reject) => {
303
+ const event = new ActivateEvent();
304
+ process.nextTick(() => {
305
+ try {
306
+ this.dispatchEvent(event);
307
+ } catch (error) {
308
+ process.nextTick(() => {
309
+ throw error;
310
+ });
311
+ }
312
+ const promises = event.getPromises();
313
+ if (promises.length === 0) {
314
+ this._serviceWorker._setState("activated");
315
+ resolve();
316
+ } else {
317
+ promiseWithTimeout(
318
+ Promise.all(promises),
319
+ 3e4,
320
+ "ServiceWorker activate event timed out after 30s - waitUntil promises did not resolve"
321
+ ).then(() => {
322
+ this._serviceWorker._setState("activated");
323
+ resolve();
324
+ }).catch(reject);
325
+ }
326
+ });
327
+ });
328
+ }
329
+ /**
330
+ * Handle a fetch request (Shovel extension)
331
+ */
332
+ async handleRequest(request) {
333
+ if (this._serviceWorker.state !== "activated") {
334
+ throw new Error("ServiceWorker not activated");
335
+ }
336
+ const event = new FetchEvent(request);
337
+ return cookieStoreStorage.run(event.cookieStore, async () => {
338
+ this.dispatchEvent(event);
339
+ event[kEndDispatchPhase]();
340
+ if (!event.hasResponded()) {
341
+ throw new Error(
342
+ "No response provided for fetch event. respondWith() must be called synchronously during event dispatch."
343
+ );
344
+ }
345
+ const response = await event.getResponse();
346
+ const promises = event.getPromises();
347
+ if (promises.length > 0) {
348
+ Promise.allSettled(promises).catch(logger.error);
349
+ }
350
+ if (event.cookieStore.hasChanges()) {
351
+ const setCookieHeaders = event.cookieStore.getSetCookieHeaders();
352
+ const headers = new Headers(response.headers);
353
+ for (const setCookie of setCookieHeaders) {
354
+ headers.append("Set-Cookie", setCookie);
355
+ }
356
+ return new Response(response.body, {
357
+ status: response.status,
358
+ statusText: response.statusText,
359
+ headers
360
+ });
361
+ }
362
+ return response;
363
+ });
364
+ }
365
+ /**
366
+ * Check if ready to handle requests (Shovel extension)
367
+ */
368
+ get ready() {
369
+ return this._serviceWorker.state === "activated";
370
+ }
371
+ // Events: updatefound (standard), plus Shovel lifecycle events
372
+ };
373
+ var ShovelServiceWorkerContainer = class extends EventTarget {
374
+ #registrations;
375
+ controller;
376
+ ready;
377
+ // Event handlers required by Web API
378
+ oncontrollerchange;
379
+ onmessage;
380
+ onmessageerror;
381
+ constructor() {
382
+ super();
383
+ this.#registrations = /* @__PURE__ */ new Map();
384
+ this.controller = null;
385
+ this.oncontrollerchange = null;
386
+ this.onmessage = null;
387
+ this.onmessageerror = null;
388
+ const defaultRegistration = new ShovelServiceWorkerRegistration("/", "/");
389
+ this.#registrations.set("/", defaultRegistration);
390
+ this.ready = Promise.resolve(defaultRegistration);
391
+ }
392
+ /**
393
+ * Get registration for a specific scope
394
+ */
395
+ async getRegistration(scope2 = "/") {
396
+ return this.#registrations.get(scope2);
397
+ }
398
+ /**
399
+ * Get all registrations
400
+ */
401
+ async getRegistrations() {
402
+ return Array.from(this.#registrations.values());
403
+ }
404
+ /**
405
+ * Register a new ServiceWorker for a specific scope
406
+ */
407
+ async register(scriptURL, options) {
408
+ const url = typeof scriptURL === "string" ? scriptURL : scriptURL.toString();
409
+ const scope2 = this.#normalizeScope(options?.scope || "/");
410
+ let registration2 = this.#registrations.get(scope2);
411
+ if (registration2) {
412
+ registration2._serviceWorker.scriptURL = url;
413
+ registration2._serviceWorker._setState("parsed");
414
+ } else {
415
+ registration2 = new ShovelServiceWorkerRegistration(scope2, url);
416
+ this.#registrations.set(scope2, registration2);
417
+ this.dispatchEvent(new Event("updatefound"));
418
+ }
419
+ return registration2;
420
+ }
421
+ /**
422
+ * Unregister a ServiceWorker registration
423
+ */
424
+ async unregister(scope2) {
425
+ const registration2 = this.#registrations.get(scope2);
426
+ if (registration2) {
427
+ await registration2.unregister();
428
+ this.#registrations.delete(scope2);
429
+ return true;
430
+ }
431
+ return false;
432
+ }
433
+ /**
434
+ * Route a request to the appropriate registration based on scope matching
435
+ */
436
+ async handleRequest(request) {
437
+ const url = new URL(request.url);
438
+ const pathname = url.pathname;
439
+ const matchingScope = this.#findMatchingScope(pathname);
440
+ if (matchingScope) {
441
+ const registration2 = this.#registrations.get(matchingScope);
442
+ if (registration2 && registration2.ready) {
443
+ return await registration2.handleRequest(request);
444
+ }
445
+ }
446
+ return null;
447
+ }
448
+ /**
449
+ * Install and activate all registrations
450
+ */
451
+ async installAll() {
452
+ const installations = Array.from(this.#registrations.values()).map(
453
+ async (registration2) => {
454
+ await registration2.install();
455
+ await registration2.activate();
456
+ }
457
+ );
458
+ await promiseWithTimeout(
459
+ Promise.all(installations),
460
+ 65e3,
461
+ "ServiceWorker installAll timed out after 65s - some registrations failed to install/activate"
462
+ );
463
+ }
464
+ /**
465
+ * Get list of all scopes
466
+ */
467
+ getScopes() {
468
+ return Array.from(this.#registrations.keys());
469
+ }
470
+ startMessages() {
471
+ }
472
+ /**
473
+ * Normalize scope to ensure it starts and ends correctly
474
+ */
475
+ #normalizeScope(scope2) {
476
+ if (!scope2.startsWith("/")) {
477
+ scope2 = "/" + scope2;
478
+ }
479
+ if (scope2 !== "/" && !scope2.endsWith("/")) {
480
+ scope2 = scope2 + "/";
481
+ }
482
+ return scope2;
483
+ }
484
+ /**
485
+ * Find the most specific scope that matches a pathname
486
+ */
487
+ #findMatchingScope(pathname) {
488
+ const scopes = Array.from(this.#registrations.keys());
489
+ scopes.sort((a, b) => b.length - a.length);
490
+ for (const scope2 of scopes) {
491
+ if (pathname.startsWith(scope2 === "/" ? "/" : scope2)) {
492
+ return scope2;
493
+ }
494
+ }
495
+ return null;
496
+ }
497
+ // Events: controllerchange, message, messageerror, updatefound
498
+ };
499
+ var Notification = class extends EventTarget {
500
+ actions;
501
+ badge;
502
+ body;
503
+ data;
504
+ dir;
505
+ icon;
506
+ image;
507
+ lang;
508
+ renotify;
509
+ requireInteraction;
510
+ silent;
511
+ tag;
512
+ timestamp;
513
+ title;
514
+ vibrate;
515
+ // Event handlers required by Web API
516
+ onclick;
517
+ onclose;
518
+ onerror;
519
+ onshow;
520
+ static permission;
521
+ constructor(title, options = {}) {
522
+ super();
523
+ this.title = title;
524
+ this.actions = Object.freeze([...options.actions || []]);
525
+ this.badge = options.badge || "";
526
+ this.body = options.body || "";
527
+ this.data = options.data;
528
+ this.dir = options.dir || "auto";
529
+ this.icon = options.icon || "";
530
+ this.image = options.image || "";
531
+ this.lang = options.lang || "";
532
+ this.renotify = options.renotify || false;
533
+ this.requireInteraction = options.requireInteraction || false;
534
+ this.silent = options.silent || false;
535
+ this.tag = options.tag || "";
536
+ this.timestamp = options.timestamp || Date.now();
537
+ this.vibrate = Object.freeze([...options.vibrate || []]);
538
+ this.onclick = null;
539
+ this.onclose = null;
540
+ this.onerror = null;
541
+ this.onshow = null;
542
+ }
543
+ close() {
544
+ logger.warn(
545
+ "[ServiceWorker] Notification.close() not supported in server context"
546
+ );
547
+ }
548
+ static async requestPermission() {
549
+ return "denied";
550
+ }
551
+ // Events: click, close, error, show
552
+ };
553
+ Notification.permission = "denied";
554
+ var NotificationEvent = class extends ExtendableEvent {
555
+ action;
556
+ notification;
557
+ reply;
558
+ constructor(type, eventInitDict) {
559
+ super(type, eventInitDict);
560
+ this.action = eventInitDict.action ?? "";
561
+ this.notification = eventInitDict.notification;
562
+ this.reply = eventInitDict.reply ?? null;
563
+ }
564
+ };
565
+ var PushEvent = class extends ExtendableEvent {
566
+ data;
567
+ constructor(type, eventInitDict) {
568
+ super(type, eventInitDict);
569
+ this.data = eventInitDict?.data ?? null;
570
+ }
571
+ };
572
+ var ShovelPushMessageData = class {
573
+ constructor(_data) {
574
+ this._data = _data;
575
+ }
576
+ arrayBuffer() {
577
+ if (this._data instanceof ArrayBuffer) {
578
+ return this._data;
579
+ }
580
+ return new TextEncoder().encode(this._data).buffer;
581
+ }
582
+ blob() {
583
+ return new Blob([this.arrayBuffer()]);
584
+ }
585
+ bytes() {
586
+ return new Uint8Array(this.arrayBuffer());
587
+ }
588
+ json() {
589
+ return JSON.parse(this.text());
590
+ }
591
+ text() {
592
+ if (typeof this._data === "string") {
593
+ return this._data;
594
+ }
595
+ return new TextDecoder().decode(this._data);
596
+ }
597
+ };
598
+ var SyncEvent = class extends ExtendableEvent {
599
+ tag;
600
+ lastChance;
601
+ constructor(type, eventInitDict) {
602
+ super(type, eventInitDict);
603
+ this.tag = eventInitDict.tag;
604
+ this.lastChance = eventInitDict.lastChance ?? false;
605
+ }
606
+ };
607
+ var WorkerGlobalScope = class {
608
+ };
609
+ var DedicatedWorkerGlobalScope = class extends WorkerGlobalScope {
610
+ };
611
+ var ShovelGlobalScope = class {
612
+ // Self-reference (standard in ServiceWorkerGlobalScope)
613
+ // Type assertion: we provide a compatible subset of WorkerGlobalScope
614
+ self;
615
+ // ServiceWorker standard properties
616
+ // Our custom ServiceWorkerRegistration provides core functionality compatible with the Web API
617
+ registration;
618
+ // Storage APIs
619
+ caches;
620
+ buckets;
621
+ // Clients API
622
+ // Our custom Clients implementation provides core functionality compatible with the Web API
623
+ clients;
624
+ // Shovel-specific development features
625
+ #isDevelopment;
626
+ // Web API required properties
627
+ // Note: Using RequestCookieStore but typing as any for flexibility with global CookieStore type
628
+ // cookieStore is retrieved from AsyncContext for per-request isolation
629
+ get cookieStore() {
630
+ return cookieStoreStorage.get();
631
+ }
632
+ serviceWorker;
633
+ // WorkerGlobalScope required properties (stubs for server context)
634
+ location;
635
+ navigator;
636
+ fonts;
637
+ indexedDB;
638
+ isSecureContext;
639
+ crossOriginIsolated;
640
+ origin;
641
+ performance;
642
+ crypto;
643
+ // WorkerGlobalScope methods (stubs for server context)
644
+ importScripts(..._urls) {
645
+ logger.warn(
646
+ "[ServiceWorker] importScripts() not supported in server context"
647
+ );
648
+ }
649
+ atob(data) {
650
+ return globalThis.atob(data);
651
+ }
652
+ btoa(data) {
653
+ return globalThis.btoa(data);
654
+ }
655
+ clearInterval(id) {
656
+ globalThis.clearInterval(id);
657
+ }
658
+ clearTimeout(id) {
659
+ globalThis.clearTimeout(id);
660
+ }
661
+ createImageBitmap(..._args) {
662
+ throw new Error(
663
+ "[ServiceWorker] createImageBitmap() not supported in server context"
664
+ );
665
+ }
666
+ fetch(input, init) {
667
+ return globalThis.fetch(input, init);
668
+ }
669
+ queueMicrotask(callback) {
670
+ globalThis.queueMicrotask(callback);
671
+ }
672
+ reportError(e) {
673
+ logger.error("[ServiceWorker] reportError:", e);
674
+ }
675
+ setInterval(handler, timeout, ...args) {
676
+ return globalThis.setInterval(handler, timeout, ...args);
677
+ }
678
+ setTimeout(handler, timeout, ...args) {
679
+ return globalThis.setTimeout(handler, timeout, ...args);
680
+ }
681
+ structuredClone(value, options) {
682
+ return globalThis.structuredClone(value, options);
683
+ }
684
+ // Event handlers required by ServiceWorkerGlobalScope
685
+ // Use Web API types (not our custom implementations) for event handler signatures
686
+ onactivate;
687
+ oncookiechange;
688
+ onfetch;
689
+ oninstall;
690
+ onmessage;
691
+ onmessageerror;
692
+ onnotificationclick;
693
+ onnotificationclose;
694
+ onpush;
695
+ onpushsubscriptionchange;
696
+ onsync;
697
+ // WorkerGlobalScope event handlers (inherited by ServiceWorkerGlobalScope)
698
+ onerror;
699
+ onlanguagechange;
700
+ onoffline;
701
+ ononline;
702
+ onrejectionhandled;
703
+ onunhandledrejection;
704
+ constructor(options) {
705
+ this.self = this;
706
+ this.registration = options.registration;
707
+ this.caches = options.caches;
708
+ this.buckets = options.buckets;
709
+ this.#isDevelopment = options.isDevelopment ?? false;
710
+ this.clients = this.#createClientsAPI();
711
+ this.serviceWorker = null;
712
+ this.location = {};
713
+ this.navigator = {};
714
+ this.fonts = {};
715
+ this.indexedDB = {};
716
+ this.isSecureContext = true;
717
+ this.crossOriginIsolated = false;
718
+ this.origin = "";
719
+ this.performance = {};
720
+ this.crypto = {};
721
+ this.onactivate = null;
722
+ this.oncookiechange = null;
723
+ this.onfetch = null;
724
+ this.oninstall = null;
725
+ this.onmessage = null;
726
+ this.onmessageerror = null;
727
+ this.onnotificationclick = null;
728
+ this.onnotificationclose = null;
729
+ this.onpush = null;
730
+ this.onpushsubscriptionchange = null;
731
+ this.onsync = null;
732
+ this.onerror = null;
733
+ this.onlanguagechange = null;
734
+ this.onoffline = null;
735
+ this.ononline = null;
736
+ this.onrejectionhandled = null;
737
+ this.onunhandledrejection = null;
738
+ }
739
+ /**
740
+ * Standard ServiceWorker skipWaiting() implementation
741
+ * Allows the ServiceWorker to activate immediately
742
+ */
743
+ async skipWaiting() {
744
+ logger.info("[ServiceWorker] skipWaiting() called");
745
+ if (!this.#isDevelopment) {
746
+ logger.info(
747
+ "[ServiceWorker] skipWaiting() - production graceful restart not implemented"
748
+ );
749
+ }
750
+ }
751
+ /**
752
+ * Event target delegation to registration
753
+ */
754
+ addEventListener(type, listener, options) {
755
+ if (listener) {
756
+ this.registration.addEventListener(type, listener, options);
757
+ }
758
+ }
759
+ removeEventListener(type, listener, options) {
760
+ if (listener) {
761
+ this.registration.removeEventListener(type, listener, options);
762
+ }
763
+ }
764
+ dispatchEvent(event) {
765
+ return this.registration.dispatchEvent(event);
766
+ }
767
+ /**
768
+ * Create Clients API implementation
769
+ * Note: HTTP requests are stateless, so most client operations are no-ops
770
+ */
771
+ #createClientsAPI() {
772
+ return new ShovelClients();
773
+ }
774
+ /**
775
+ * Install this scope as the global scope
776
+ * Sets up globalThis with all ServiceWorker globals
777
+ */
778
+ install() {
779
+ globalThis.WorkerGlobalScope = WorkerGlobalScope;
780
+ globalThis.DedicatedWorkerGlobalScope = DedicatedWorkerGlobalScope;
781
+ globalThis.self = this;
782
+ const isWorker = "onmessage" in globalThis;
783
+ if (isWorker && typeof postMessage === "function") {
784
+ this.postMessage = postMessage.bind(globalThis);
785
+ }
786
+ globalThis.addEventListener = this.addEventListener.bind(this);
787
+ globalThis.removeEventListener = this.removeEventListener.bind(this);
788
+ globalThis.dispatchEvent = this.dispatchEvent.bind(this);
789
+ if (this.caches) {
790
+ globalThis.caches = this.caches;
791
+ }
792
+ if (this.buckets) {
793
+ globalThis.buckets = this.buckets;
794
+ }
795
+ globalThis.registration = this.registration;
796
+ globalThis.skipWaiting = this.skipWaiting.bind(this);
797
+ globalThis.clients = this.clients;
798
+ }
799
+ };
800
+ var logger = getLogger(["worker"]);
801
+ async function initializeWorker() {
802
+ const messagePort = globalThis;
803
+ const sendMessage2 = (message, transfer) => {
804
+ if (transfer && transfer.length > 0) {
805
+ postMessage(message, transfer);
806
+ } else {
807
+ postMessage(message);
808
+ }
809
+ };
810
+ onmessage = function(event) {
811
+ void handleMessage(event.data);
812
+ };
813
+ return { messagePort, sendMessage: sendMessage2 };
814
+ }
815
+ var registration = null;
816
+ var scope = null;
817
+ var _workerSelf = null;
818
+ var currentApp = null;
819
+ var serviceWorkerReady = false;
820
+ var loadedVersion = null;
821
+ var caches;
822
+ var buckets;
823
+ async function handleFetchEvent(request) {
824
+ if (!currentApp || !serviceWorkerReady) {
825
+ throw new Error("ServiceWorker not ready");
826
+ }
827
+ if (!registration) {
828
+ throw new Error("ServiceWorker runtime not initialized");
829
+ }
830
+ try {
831
+ const response = await registration.handleRequest(request);
832
+ return response;
833
+ } catch (error) {
834
+ logger.error("[Worker] ServiceWorker request failed", { error });
835
+ const response = new Response("ServiceWorker request failed", {
836
+ status: 500
837
+ });
838
+ return response;
839
+ }
840
+ }
841
+ async function loadServiceWorker(version, entrypoint) {
842
+ try {
843
+ logger.debug("loadServiceWorker called", {
844
+ version,
845
+ loadedVersion
846
+ });
847
+ if (!entrypoint) {
848
+ throw new Error(
849
+ "ServiceWorker entrypoint must be provided via loadServiceWorker() call"
850
+ );
851
+ }
852
+ logger.info("[Worker] Loading from", { entrypoint });
853
+ if (loadedVersion !== null && loadedVersion !== version) {
854
+ logger.info(
855
+ `[Worker] Hot reload detected: ${loadedVersion} -> ${version}`
856
+ );
857
+ logger.info("[Worker] Creating completely fresh ServiceWorker context");
858
+ registration = new ShovelServiceWorkerRegistration();
859
+ if (!caches || !buckets) {
860
+ throw new Error("Runtime not initialized - missing caches or buckets");
861
+ }
862
+ scope = new ShovelGlobalScope({
863
+ registration,
864
+ caches,
865
+ buckets
866
+ });
867
+ scope.install();
868
+ _workerSelf = scope;
869
+ currentApp = null;
870
+ serviceWorkerReady = false;
871
+ }
872
+ if (loadedVersion === version) {
873
+ logger.info("[Worker] ServiceWorker already loaded for version", {
874
+ version
875
+ });
876
+ return;
877
+ }
878
+ const appModule = await import(`${entrypoint}?v=${version}`);
879
+ loadedVersion = version;
880
+ currentApp = appModule;
881
+ if (!registration) {
882
+ throw new Error("ServiceWorker runtime not initialized");
883
+ }
884
+ await registration.install();
885
+ await registration.activate();
886
+ serviceWorkerReady = true;
887
+ logger.info(
888
+ `[Worker] ServiceWorker loaded and activated (v${version}) from ${entrypoint}`
889
+ );
890
+ } catch (error) {
891
+ const errorMessage = error instanceof Error ? error.message : String(error);
892
+ const errorStack = error instanceof Error ? error.stack : void 0;
893
+ logger.error("[Worker] Failed to load ServiceWorker", {
894
+ error: errorMessage,
895
+ stack: errorStack,
896
+ entrypoint
897
+ });
898
+ serviceWorkerReady = false;
899
+ throw error;
900
+ }
901
+ }
902
+ var workerId = Math.random().toString(36).substring(2, 8);
903
+ var sendMessage;
904
+ async function initializeRuntime(config, baseDir) {
905
+ try {
906
+ logger.info(`[Worker-${workerId}] Initializing runtime with config`, {
907
+ config,
908
+ baseDir
909
+ });
910
+ logger.info(`[Worker-${workerId}] Creating cache storage`);
911
+ caches = new CustomCacheStorage(createCacheFactory({ config }));
912
+ logger.info(`[Worker-${workerId}] Creating bucket storage`);
913
+ buckets = new CustomBucketStorage(createBucketFactory({ baseDir, config }));
914
+ logger.info(`[Worker-${workerId}] Creating and installing scope`);
915
+ registration = new ShovelServiceWorkerRegistration();
916
+ scope = new ShovelGlobalScope({ registration, caches, buckets });
917
+ scope.install();
918
+ _workerSelf = scope;
919
+ logger.info(`[Worker-${workerId}] Runtime initialized successfully`);
920
+ } catch (error) {
921
+ logger.error(`[Worker-${workerId}] Failed to initialize runtime`, { error });
922
+ throw error;
923
+ }
924
+ }
925
+ async function handleMessage(message) {
926
+ try {
927
+ logger.info(`[Worker-${workerId}] Received message`, { type: message.type });
928
+ if (message.type === "init") {
929
+ const initMsg = message;
930
+ await initializeRuntime(initMsg.config, initMsg.baseDir);
931
+ logger.info(`[Worker-${workerId}] Sending initialized message`);
932
+ sendMessage({ type: "initialized" });
933
+ } else if (message.type === "load") {
934
+ const loadMsg = message;
935
+ await loadServiceWorker(loadMsg.version, loadMsg.entrypoint);
936
+ sendMessage({ type: "ready", version: loadMsg.version });
937
+ } else if (message.type === "request") {
938
+ const reqMsg = message;
939
+ const request = new Request(reqMsg.request.url, {
940
+ method: reqMsg.request.method,
941
+ headers: reqMsg.request.headers,
942
+ body: reqMsg.request.body
943
+ });
944
+ const response = await handleFetchEvent(request);
945
+ const body = await response.arrayBuffer();
946
+ const responseMsg = {
947
+ type: "response",
948
+ response: {
949
+ status: response.status,
950
+ statusText: response.statusText,
951
+ headers: Object.fromEntries(response.headers.entries()),
952
+ body
953
+ },
954
+ requestID: reqMsg.requestID
955
+ };
956
+ sendMessage(responseMsg, [body]);
957
+ }
958
+ } catch (error) {
959
+ const errorMsg = {
960
+ type: "error",
961
+ error: error instanceof Error ? error.message : String(error),
962
+ stack: error instanceof Error ? error.stack : void 0,
963
+ requestID: message.requestID
964
+ };
965
+ sendMessage(errorMsg);
966
+ }
967
+ }
968
+ if (typeof onmessage !== "undefined") {
969
+ initializeWorker().then(({ messagePort: _messagePort, sendMessage: send }) => {
970
+ sendMessage = send;
971
+ sendMessage({ type: "worker-ready" });
972
+ }).catch((error) => {
973
+ logger.error("[Worker] Failed to initialize:", error);
974
+ });
975
+ }
976
+ export {
977
+ ActivateEvent,
978
+ DedicatedWorkerGlobalScope,
979
+ ExtendableEvent,
980
+ ExtendableMessageEvent,
981
+ FetchEvent,
982
+ InstallEvent,
983
+ Notification,
984
+ NotificationEvent,
985
+ PushEvent,
986
+ ShovelClient,
987
+ ShovelClients,
988
+ ShovelGlobalScope,
989
+ ShovelNavigationPreloadManager,
990
+ ShovelPushMessageData,
991
+ ShovelServiceWorker,
992
+ ShovelServiceWorkerContainer,
993
+ ShovelServiceWorkerRegistration,
994
+ ShovelWindowClient,
995
+ SyncEvent,
996
+ WorkerGlobalScope
997
+ };