@b9g/platform 0.1.12 → 0.1.13
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/package.json +24 -16
- package/src/config.d.ts +24 -0
- package/src/config.js +29 -0
- package/src/globals.d.ts +119 -0
- package/src/index.d.ts +105 -41
- package/src/index.js +174 -219
- package/src/runtime.d.ts +315 -62
- package/src/runtime.js +424 -129
- package/chunk-P57PW2II.js +0 -11
- package/src/worker.d.ts +0 -39
- package/src/worker.js +0 -285
package/src/runtime.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
/// <reference types="./runtime.d.ts" />
|
|
2
|
-
import "../chunk-P57PW2II.js";
|
|
3
|
-
|
|
4
2
|
// src/runtime.ts
|
|
5
3
|
import { AsyncContext } from "@b9g/async-context";
|
|
4
|
+
import { getLogger, getConsoleSink } from "@logtape/logtape";
|
|
5
|
+
import { CustomDirectoryStorage } from "@b9g/filesystem";
|
|
6
|
+
import { CustomCacheStorage } from "@b9g/cache";
|
|
7
|
+
import { handleCacheResponse, PostMessageCache } from "@b9g/cache/postmessage";
|
|
6
8
|
import {
|
|
7
9
|
configure
|
|
8
10
|
} from "@logtape/logtape";
|
|
11
|
+
import { Database } from "@b9g/zen";
|
|
9
12
|
function parseCookieHeader(cookieHeader) {
|
|
10
13
|
const cookies = /* @__PURE__ */ new Map();
|
|
11
|
-
if (!cookieHeader)
|
|
12
|
-
return cookies;
|
|
14
|
+
if (!cookieHeader) return cookies;
|
|
13
15
|
const pairs = cookieHeader.split(/;\s*/);
|
|
14
16
|
for (const pair of pairs) {
|
|
15
17
|
const [name, ...valueParts] = pair.split("=");
|
|
@@ -114,8 +116,7 @@ var RequestCookieStore = class extends EventTarget {
|
|
|
114
116
|
}
|
|
115
117
|
if (this.#changes.has(name)) {
|
|
116
118
|
const change = this.#changes.get(name);
|
|
117
|
-
if (change === null || change === void 0)
|
|
118
|
-
return null;
|
|
119
|
+
if (change === null || change === void 0) return null;
|
|
119
120
|
return {
|
|
120
121
|
name: change.name,
|
|
121
122
|
value: change.value,
|
|
@@ -136,8 +137,7 @@ var RequestCookieStore = class extends EventTarget {
|
|
|
136
137
|
...this.#changes.keys()
|
|
137
138
|
]);
|
|
138
139
|
for (const cookieName of allNames) {
|
|
139
|
-
if (name && cookieName !== name)
|
|
140
|
-
continue;
|
|
140
|
+
if (name && cookieName !== name) continue;
|
|
141
141
|
if (this.#changes.has(cookieName) && this.#changes.get(cookieName) === null) {
|
|
142
142
|
continue;
|
|
143
143
|
}
|
|
@@ -216,10 +216,122 @@ var CustomLoggerStorage = class {
|
|
|
216
216
|
constructor(factory) {
|
|
217
217
|
this.#factory = factory;
|
|
218
218
|
}
|
|
219
|
-
|
|
220
|
-
return this.#factory(
|
|
219
|
+
get(categories) {
|
|
220
|
+
return this.#factory(categories);
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
var CustomDatabaseStorage = class {
|
|
224
|
+
#factory;
|
|
225
|
+
#databases;
|
|
226
|
+
#closers;
|
|
227
|
+
#pending;
|
|
228
|
+
constructor(factory) {
|
|
229
|
+
this.#factory = factory;
|
|
230
|
+
this.#databases = /* @__PURE__ */ new Map();
|
|
231
|
+
this.#closers = /* @__PURE__ */ new Map();
|
|
232
|
+
this.#pending = /* @__PURE__ */ new Map();
|
|
233
|
+
}
|
|
234
|
+
async open(name, version, onUpgrade) {
|
|
235
|
+
const existing = this.#databases.get(name);
|
|
236
|
+
if (existing) {
|
|
237
|
+
return existing;
|
|
238
|
+
}
|
|
239
|
+
const pending = this.#pending.get(name);
|
|
240
|
+
if (pending) {
|
|
241
|
+
return pending;
|
|
242
|
+
}
|
|
243
|
+
const promise = (async () => {
|
|
244
|
+
const { db, close } = await this.#factory(name);
|
|
245
|
+
if (onUpgrade) {
|
|
246
|
+
db.addEventListener("upgradeneeded", (e) => {
|
|
247
|
+
const event = e;
|
|
248
|
+
onUpgrade({
|
|
249
|
+
db,
|
|
250
|
+
oldVersion: event.oldVersion,
|
|
251
|
+
newVersion: event.newVersion,
|
|
252
|
+
waitUntil: (p) => event.waitUntil(p)
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
try {
|
|
257
|
+
await db.open(version);
|
|
258
|
+
} catch (err) {
|
|
259
|
+
await close();
|
|
260
|
+
throw err;
|
|
261
|
+
}
|
|
262
|
+
this.#databases.set(name, db);
|
|
263
|
+
this.#closers.set(name, close);
|
|
264
|
+
return db;
|
|
265
|
+
})().finally(() => {
|
|
266
|
+
this.#pending.delete(name);
|
|
267
|
+
});
|
|
268
|
+
this.#pending.set(name, promise);
|
|
269
|
+
return promise;
|
|
270
|
+
}
|
|
271
|
+
get(name) {
|
|
272
|
+
const db = this.#databases.get(name);
|
|
273
|
+
if (!db) {
|
|
274
|
+
throw new Error(
|
|
275
|
+
`Database "${name}" has not been opened. Call self.databases.open("${name}", version) in your activate handler first.`
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
return db;
|
|
279
|
+
}
|
|
280
|
+
async close(name) {
|
|
281
|
+
const pending = this.#pending.get(name);
|
|
282
|
+
if (pending) {
|
|
283
|
+
try {
|
|
284
|
+
await pending;
|
|
285
|
+
} catch (_err) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
const closer = this.#closers.get(name);
|
|
290
|
+
if (closer) {
|
|
291
|
+
await closer();
|
|
292
|
+
this.#databases.delete(name);
|
|
293
|
+
this.#closers.delete(name);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
async closeAll() {
|
|
297
|
+
if (this.#pending.size > 0) {
|
|
298
|
+
await Promise.allSettled(this.#pending.values());
|
|
299
|
+
}
|
|
300
|
+
const promises = Array.from(this.#databases.keys()).map(
|
|
301
|
+
(name) => this.close(name)
|
|
302
|
+
);
|
|
303
|
+
await Promise.allSettled(promises);
|
|
221
304
|
}
|
|
222
305
|
};
|
|
306
|
+
function createDatabaseFactory(configs) {
|
|
307
|
+
return async (name) => {
|
|
308
|
+
const config = configs[name];
|
|
309
|
+
if (!config) {
|
|
310
|
+
throw new Error(
|
|
311
|
+
`Database "${name}" is not configured. Available databases: ${Object.keys(configs).join(", ") || "(none)"}`
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
const { impl, url, ...driverOptions } = config;
|
|
315
|
+
if (!impl) {
|
|
316
|
+
throw new Error(
|
|
317
|
+
`Database "${name}" has no impl. Ensure the database module is configured in shovel.json.`
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
if (!url) {
|
|
321
|
+
throw new Error(
|
|
322
|
+
`Database "${name}" has no url. Ensure the database URL is configured.`
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
const driver = new impl(url, driverOptions);
|
|
326
|
+
const db = new Database(driver);
|
|
327
|
+
return {
|
|
328
|
+
db,
|
|
329
|
+
close: async () => {
|
|
330
|
+
await driver.close();
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
};
|
|
334
|
+
}
|
|
223
335
|
var SERVICE_WORKER_EVENTS = ["fetch", "install", "activate"];
|
|
224
336
|
function isServiceWorkerEvent(type) {
|
|
225
337
|
return SERVICE_WORKER_EVENTS.includes(type);
|
|
@@ -235,6 +347,7 @@ var PATCHED_KEYS = [
|
|
|
235
347
|
"fetch",
|
|
236
348
|
"caches",
|
|
237
349
|
"directories",
|
|
350
|
+
"databases",
|
|
238
351
|
"loggers",
|
|
239
352
|
"registration",
|
|
240
353
|
"clients",
|
|
@@ -254,9 +367,9 @@ function promiseWithTimeout(promise, timeoutMs, errorMessage) {
|
|
|
254
367
|
)
|
|
255
368
|
]);
|
|
256
369
|
}
|
|
257
|
-
var kEndDispatchPhase = Symbol.for("shovel.endDispatchPhase");
|
|
258
|
-
var kCanExtend = Symbol.for("shovel.canExtend");
|
|
259
|
-
var
|
|
370
|
+
var kEndDispatchPhase = /* @__PURE__ */ Symbol.for("shovel.endDispatchPhase");
|
|
371
|
+
var kCanExtend = /* @__PURE__ */ Symbol.for("shovel.canExtend");
|
|
372
|
+
var ShovelExtendableEvent = class extends Event {
|
|
260
373
|
#promises;
|
|
261
374
|
#dispatchPhase;
|
|
262
375
|
#pendingCount;
|
|
@@ -293,17 +406,33 @@ var ExtendableEvent = class extends Event {
|
|
|
293
406
|
return this.#dispatchPhase || this.#pendingCount > 0;
|
|
294
407
|
}
|
|
295
408
|
};
|
|
296
|
-
var
|
|
409
|
+
var ShovelFetchEvent = class extends ShovelExtendableEvent {
|
|
297
410
|
request;
|
|
298
411
|
cookieStore;
|
|
412
|
+
clientId;
|
|
413
|
+
handled;
|
|
414
|
+
preloadResponse;
|
|
415
|
+
resultingClientId;
|
|
299
416
|
#responsePromise;
|
|
300
417
|
#responded;
|
|
301
|
-
|
|
302
|
-
|
|
418
|
+
#platformWaitUntil;
|
|
419
|
+
constructor(request, options) {
|
|
420
|
+
super("fetch", options);
|
|
303
421
|
this.request = request;
|
|
304
422
|
this.cookieStore = new RequestCookieStore(request);
|
|
423
|
+
this.clientId = "";
|
|
424
|
+
this.handled = Promise.resolve(void 0);
|
|
425
|
+
this.preloadResponse = Promise.resolve(void 0);
|
|
426
|
+
this.resultingClientId = "";
|
|
305
427
|
this.#responsePromise = null;
|
|
306
428
|
this.#responded = false;
|
|
429
|
+
this.#platformWaitUntil = options?.platformWaitUntil;
|
|
430
|
+
}
|
|
431
|
+
waitUntil(promise) {
|
|
432
|
+
if (this.#platformWaitUntil) {
|
|
433
|
+
this.#platformWaitUntil(promise);
|
|
434
|
+
}
|
|
435
|
+
super.waitUntil(promise);
|
|
307
436
|
}
|
|
308
437
|
respondWith(response) {
|
|
309
438
|
if (this.#responded) {
|
|
@@ -325,13 +454,17 @@ var FetchEvent = class extends ExtendableEvent {
|
|
|
325
454
|
hasResponded() {
|
|
326
455
|
return this.#responded;
|
|
327
456
|
}
|
|
457
|
+
/** The URL of the request (convenience property) */
|
|
458
|
+
get url() {
|
|
459
|
+
return this.request.url;
|
|
460
|
+
}
|
|
328
461
|
};
|
|
329
|
-
var
|
|
462
|
+
var ShovelInstallEvent = class extends ShovelExtendableEvent {
|
|
330
463
|
constructor(eventInitDict) {
|
|
331
464
|
super("install", eventInitDict);
|
|
332
465
|
}
|
|
333
466
|
};
|
|
334
|
-
var
|
|
467
|
+
var ShovelActivateEvent = class extends ShovelExtendableEvent {
|
|
335
468
|
constructor(eventInitDict) {
|
|
336
469
|
super("activate", eventInitDict);
|
|
337
470
|
}
|
|
@@ -383,7 +516,7 @@ var ShovelClients = class {
|
|
|
383
516
|
return null;
|
|
384
517
|
}
|
|
385
518
|
};
|
|
386
|
-
var ExtendableMessageEvent = class extends
|
|
519
|
+
var ExtendableMessageEvent = class extends ShovelExtendableEvent {
|
|
387
520
|
data;
|
|
388
521
|
origin;
|
|
389
522
|
lastEventId;
|
|
@@ -485,11 +618,10 @@ var ShovelServiceWorkerRegistration = class extends EventTarget {
|
|
|
485
618
|
* Install the ServiceWorker (Shovel extension)
|
|
486
619
|
*/
|
|
487
620
|
async install() {
|
|
488
|
-
if (this._serviceWorker.state !== "parsed")
|
|
489
|
-
return;
|
|
621
|
+
if (this._serviceWorker.state !== "parsed") return;
|
|
490
622
|
this._serviceWorker._setState("installing");
|
|
491
623
|
return new Promise((resolve, reject) => {
|
|
492
|
-
const event = new
|
|
624
|
+
const event = new ShovelInstallEvent();
|
|
493
625
|
process.nextTick(() => {
|
|
494
626
|
try {
|
|
495
627
|
this.dispatchEvent(event);
|
|
@@ -524,7 +656,7 @@ var ShovelServiceWorkerRegistration = class extends EventTarget {
|
|
|
524
656
|
}
|
|
525
657
|
this._serviceWorker._setState("activating");
|
|
526
658
|
return new Promise((resolve, reject) => {
|
|
527
|
-
const event = new
|
|
659
|
+
const event = new ShovelActivateEvent();
|
|
528
660
|
process.nextTick(() => {
|
|
529
661
|
try {
|
|
530
662
|
this.dispatchEvent(event);
|
|
@@ -552,12 +684,16 @@ var ShovelServiceWorkerRegistration = class extends EventTarget {
|
|
|
552
684
|
}
|
|
553
685
|
/**
|
|
554
686
|
* Handle a fetch request (Shovel extension)
|
|
687
|
+
*
|
|
688
|
+
* Platforms create a ShovelFetchEvent (or subclass) with platform-specific
|
|
689
|
+
* properties and hooks, then pass it to this method for dispatching.
|
|
690
|
+
*
|
|
691
|
+
* @param event - The fetch event to handle (created by platform adapter)
|
|
555
692
|
*/
|
|
556
|
-
async handleRequest(
|
|
693
|
+
async handleRequest(event) {
|
|
557
694
|
if (this._serviceWorker.state !== "activated") {
|
|
558
695
|
throw new Error("ServiceWorker not activated");
|
|
559
696
|
}
|
|
560
|
-
const event = new FetchEvent(request);
|
|
561
697
|
return cookieStoreStorage.run(event.cookieStore, async () => {
|
|
562
698
|
this.dispatchEvent(event);
|
|
563
699
|
event[kEndDispatchPhase]();
|
|
@@ -567,12 +703,6 @@ var ShovelServiceWorkerRegistration = class extends EventTarget {
|
|
|
567
703
|
);
|
|
568
704
|
}
|
|
569
705
|
const response = await event.getResponse();
|
|
570
|
-
const promises = event.getPromises();
|
|
571
|
-
if (promises.length > 0) {
|
|
572
|
-
Promise.allSettled(promises).catch(
|
|
573
|
-
(err) => self.loggers.open("platform").error`waitUntil error: ${err}`
|
|
574
|
-
);
|
|
575
|
-
}
|
|
576
706
|
if (event.cookieStore.hasChanges()) {
|
|
577
707
|
const setCookieHeaders = event.cookieStore.getSetCookieHeaders();
|
|
578
708
|
const headers = new Headers(response.headers);
|
|
@@ -666,7 +796,8 @@ var ShovelServiceWorkerContainer = class extends EventTarget {
|
|
|
666
796
|
if (matchingScope) {
|
|
667
797
|
const registration = this.#registrations.get(matchingScope);
|
|
668
798
|
if (registration && registration.ready) {
|
|
669
|
-
|
|
799
|
+
const event = new ShovelFetchEvent(request);
|
|
800
|
+
return await registration.handleRequest(event);
|
|
670
801
|
}
|
|
671
802
|
}
|
|
672
803
|
return null;
|
|
@@ -775,7 +906,7 @@ var Notification = class extends EventTarget {
|
|
|
775
906
|
// Events: click, close, error, show
|
|
776
907
|
};
|
|
777
908
|
Notification.permission = "denied";
|
|
778
|
-
var NotificationEvent = class extends
|
|
909
|
+
var NotificationEvent = class extends ShovelExtendableEvent {
|
|
779
910
|
action;
|
|
780
911
|
notification;
|
|
781
912
|
reply;
|
|
@@ -786,7 +917,7 @@ var NotificationEvent = class extends ExtendableEvent {
|
|
|
786
917
|
this.reply = eventInitDict.reply ?? null;
|
|
787
918
|
}
|
|
788
919
|
};
|
|
789
|
-
var PushEvent = class extends
|
|
920
|
+
var PushEvent = class extends ShovelExtendableEvent {
|
|
790
921
|
data;
|
|
791
922
|
constructor(type, eventInitDict) {
|
|
792
923
|
super(type, eventInitDict);
|
|
@@ -819,7 +950,7 @@ var ShovelPushMessageData = class {
|
|
|
819
950
|
return new TextDecoder().decode(this._data);
|
|
820
951
|
}
|
|
821
952
|
};
|
|
822
|
-
var SyncEvent = class extends
|
|
953
|
+
var SyncEvent = class extends ShovelExtendableEvent {
|
|
823
954
|
tag;
|
|
824
955
|
lastChance;
|
|
825
956
|
constructor(type, eventInitDict) {
|
|
@@ -842,6 +973,7 @@ var ServiceWorkerGlobals = class {
|
|
|
842
973
|
// Storage APIs
|
|
843
974
|
caches;
|
|
844
975
|
directories;
|
|
976
|
+
databases;
|
|
845
977
|
loggers;
|
|
846
978
|
// Clients API
|
|
847
979
|
// Our custom Clients implementation provides core functionality compatible with the Web API
|
|
@@ -903,14 +1035,15 @@ var ServiceWorkerGlobals = class {
|
|
|
903
1035
|
}
|
|
904
1036
|
const request = new Request(new URL(urlString, "http://localhost"), init);
|
|
905
1037
|
return fetchDepthStorage.run(currentDepth + 1, () => {
|
|
906
|
-
|
|
1038
|
+
const event = new ShovelFetchEvent(request);
|
|
1039
|
+
return this.registration.handleRequest(event);
|
|
907
1040
|
});
|
|
908
1041
|
}
|
|
909
1042
|
queueMicrotask(callback) {
|
|
910
1043
|
globalThis.queueMicrotask(callback);
|
|
911
1044
|
}
|
|
912
1045
|
reportError(e) {
|
|
913
|
-
|
|
1046
|
+
getLogger(["shovel", "platform"]).error`reportError: ${e}`;
|
|
914
1047
|
}
|
|
915
1048
|
setInterval(handler, timeout, ...args) {
|
|
916
1049
|
return globalThis.setInterval(handler, timeout, ...args);
|
|
@@ -951,6 +1084,7 @@ var ServiceWorkerGlobals = class {
|
|
|
951
1084
|
this.registration = options.registration;
|
|
952
1085
|
this.caches = options.caches;
|
|
953
1086
|
this.directories = options.directories;
|
|
1087
|
+
this.databases = options.databases;
|
|
954
1088
|
this.loggers = options.loggers;
|
|
955
1089
|
this.#isDevelopment = options.isDevelopment ?? false;
|
|
956
1090
|
this.clients = this.#createClientsAPI();
|
|
@@ -987,9 +1121,11 @@ var ServiceWorkerGlobals = class {
|
|
|
987
1121
|
* Allows the ServiceWorker to activate immediately
|
|
988
1122
|
*/
|
|
989
1123
|
async skipWaiting() {
|
|
990
|
-
|
|
1124
|
+
getLogger(["shovel", "platform"]).info("skipWaiting() called");
|
|
991
1125
|
if (!this.#isDevelopment) {
|
|
992
|
-
|
|
1126
|
+
getLogger(["shovel", "platform"]).info(
|
|
1127
|
+
"skipWaiting() - production graceful restart not implemented"
|
|
1128
|
+
);
|
|
993
1129
|
}
|
|
994
1130
|
}
|
|
995
1131
|
/**
|
|
@@ -997,8 +1133,7 @@ var ServiceWorkerGlobals = class {
|
|
|
997
1133
|
* other events (like "message" for worker threads) go to native handler
|
|
998
1134
|
*/
|
|
999
1135
|
addEventListener(type, listener, options) {
|
|
1000
|
-
if (!listener)
|
|
1001
|
-
return;
|
|
1136
|
+
if (!listener) return;
|
|
1002
1137
|
if (isServiceWorkerEvent(type)) {
|
|
1003
1138
|
this.registration.addEventListener(type, listener, options);
|
|
1004
1139
|
} else {
|
|
@@ -1009,8 +1144,7 @@ var ServiceWorkerGlobals = class {
|
|
|
1009
1144
|
}
|
|
1010
1145
|
}
|
|
1011
1146
|
removeEventListener(type, listener, options) {
|
|
1012
|
-
if (!listener)
|
|
1013
|
-
return;
|
|
1147
|
+
if (!listener) return;
|
|
1014
1148
|
if (isServiceWorkerEvent(type)) {
|
|
1015
1149
|
this.registration.removeEventListener(type, listener, options);
|
|
1016
1150
|
} else {
|
|
@@ -1053,6 +1187,9 @@ var ServiceWorkerGlobals = class {
|
|
|
1053
1187
|
g.dispatchEvent = this.dispatchEvent.bind(this);
|
|
1054
1188
|
g.caches = this.caches;
|
|
1055
1189
|
g.directories = this.directories;
|
|
1190
|
+
if (this.databases) {
|
|
1191
|
+
g.databases = this.databases;
|
|
1192
|
+
}
|
|
1056
1193
|
g.loggers = this.loggers;
|
|
1057
1194
|
g.registration = this.registration;
|
|
1058
1195
|
g.skipWaiting = this.skipWaiting.bind(this);
|
|
@@ -1079,117 +1216,270 @@ var ServiceWorkerGlobals = class {
|
|
|
1079
1216
|
}
|
|
1080
1217
|
}
|
|
1081
1218
|
};
|
|
1082
|
-
|
|
1083
|
-
"
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1219
|
+
function isClass(fn) {
|
|
1220
|
+
return typeof fn === "function" && fn.prototype !== void 0;
|
|
1221
|
+
}
|
|
1222
|
+
function createDirectoryFactory(configs) {
|
|
1223
|
+
return async (name) => {
|
|
1224
|
+
const config = configs[name];
|
|
1225
|
+
if (!config) {
|
|
1226
|
+
throw new Error(
|
|
1227
|
+
`Directory "${name}" is not configured. Available directories: ${Object.keys(configs).join(", ") || "(none)"}`
|
|
1228
|
+
);
|
|
1229
|
+
}
|
|
1230
|
+
const { impl, ...dirOptions } = config;
|
|
1231
|
+
if (!impl) {
|
|
1232
|
+
throw new Error(
|
|
1233
|
+
`Directory "${name}" has no impl. Ensure the directory module is configured.`
|
|
1234
|
+
);
|
|
1235
|
+
}
|
|
1236
|
+
if (isClass(impl)) {
|
|
1237
|
+
return new impl(name, dirOptions);
|
|
1238
|
+
} else {
|
|
1239
|
+
return impl(name, dirOptions);
|
|
1240
|
+
}
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
function createCacheFactory(options) {
|
|
1244
|
+
const { configs, usePostMessage = false } = options;
|
|
1245
|
+
return async (name) => {
|
|
1246
|
+
if (usePostMessage) {
|
|
1247
|
+
return new PostMessageCache(name);
|
|
1248
|
+
}
|
|
1249
|
+
const config = configs[name];
|
|
1250
|
+
if (!config) {
|
|
1251
|
+
throw new Error(
|
|
1252
|
+
`Cache "${name}" is not configured. Available caches: ${Object.keys(configs).join(", ") || "(none)"}`
|
|
1253
|
+
);
|
|
1254
|
+
}
|
|
1255
|
+
const { impl, ...cacheOptions } = config;
|
|
1256
|
+
if (!impl) {
|
|
1257
|
+
throw new Error(
|
|
1258
|
+
`Cache "${name}" has no impl. Ensure the cache module is configured.`
|
|
1259
|
+
);
|
|
1260
|
+
}
|
|
1261
|
+
if (isClass(impl)) {
|
|
1262
|
+
return new impl(name, cacheOptions);
|
|
1263
|
+
} else {
|
|
1264
|
+
return impl(name, cacheOptions);
|
|
1265
|
+
}
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
async function initWorkerRuntime(options) {
|
|
1269
|
+
const { config } = options;
|
|
1270
|
+
const runtimeLogger = getLogger(["shovel", "platform"]);
|
|
1271
|
+
if (config?.logging) {
|
|
1272
|
+
await configureLogging(config.logging);
|
|
1273
|
+
}
|
|
1274
|
+
runtimeLogger.debug("Initializing worker runtime");
|
|
1275
|
+
const caches = new CustomCacheStorage(
|
|
1276
|
+
createCacheFactory({
|
|
1277
|
+
configs: config?.caches ?? {},
|
|
1278
|
+
usePostMessage: true
|
|
1279
|
+
})
|
|
1280
|
+
);
|
|
1281
|
+
const directories = new CustomDirectoryStorage(
|
|
1282
|
+
createDirectoryFactory(config?.directories ?? {})
|
|
1283
|
+
);
|
|
1284
|
+
let databases;
|
|
1285
|
+
if (config?.databases && Object.keys(config.databases).length > 0) {
|
|
1286
|
+
const factory = createDatabaseFactory(config.databases);
|
|
1287
|
+
databases = new CustomDatabaseStorage(factory);
|
|
1288
|
+
}
|
|
1289
|
+
const loggers = new CustomLoggerStorage(
|
|
1290
|
+
(categories) => getLogger(categories)
|
|
1291
|
+
);
|
|
1292
|
+
const registration = new ShovelServiceWorkerRegistration();
|
|
1293
|
+
const scope = new ServiceWorkerGlobals({
|
|
1294
|
+
registration,
|
|
1295
|
+
caches,
|
|
1296
|
+
directories,
|
|
1297
|
+
databases,
|
|
1298
|
+
loggers
|
|
1299
|
+
});
|
|
1300
|
+
scope.install();
|
|
1301
|
+
runtimeLogger.debug("Worker runtime initialized");
|
|
1302
|
+
return { registration, scope, caches, directories, databases, loggers };
|
|
1126
1303
|
}
|
|
1127
|
-
|
|
1128
|
-
const
|
|
1129
|
-
const
|
|
1130
|
-
const
|
|
1131
|
-
const
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1304
|
+
function startWorkerMessageLoop(options) {
|
|
1305
|
+
const registration = options instanceof ShovelServiceWorkerRegistration ? options : options.registration;
|
|
1306
|
+
const databases = options instanceof ShovelServiceWorkerRegistration ? void 0 : options.databases;
|
|
1307
|
+
const messageLogger = getLogger(["shovel", "platform"]);
|
|
1308
|
+
const workerId = Math.random().toString(36).substring(2, 8);
|
|
1309
|
+
function sendMessage(message, transfer) {
|
|
1310
|
+
if (transfer && transfer.length > 0) {
|
|
1311
|
+
postMessage(message, transfer);
|
|
1312
|
+
} else {
|
|
1313
|
+
postMessage(message);
|
|
1137
1314
|
}
|
|
1138
1315
|
}
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1316
|
+
async function handleFetchRequest(message) {
|
|
1317
|
+
try {
|
|
1318
|
+
const request = new Request(message.request.url, {
|
|
1319
|
+
method: message.request.method,
|
|
1320
|
+
headers: message.request.headers,
|
|
1321
|
+
body: message.request.body
|
|
1322
|
+
});
|
|
1323
|
+
const event = new ShovelFetchEvent(request);
|
|
1324
|
+
const response = await registration.handleRequest(event);
|
|
1325
|
+
const body = await response.arrayBuffer();
|
|
1326
|
+
const headers = Object.fromEntries(response.headers.entries());
|
|
1327
|
+
if (!headers["Content-Type"] && !headers["content-type"]) {
|
|
1328
|
+
headers["Content-Type"] = "text/plain; charset=utf-8";
|
|
1146
1329
|
}
|
|
1330
|
+
const responseMsg = {
|
|
1331
|
+
type: "response",
|
|
1332
|
+
response: {
|
|
1333
|
+
status: response.status,
|
|
1334
|
+
statusText: response.statusText,
|
|
1335
|
+
headers,
|
|
1336
|
+
body
|
|
1337
|
+
},
|
|
1338
|
+
requestID: message.requestID
|
|
1339
|
+
};
|
|
1340
|
+
sendMessage(responseMsg, [body]);
|
|
1341
|
+
} catch (error) {
|
|
1342
|
+
messageLogger.error(`[Worker-${workerId}] Request failed: {error}`, {
|
|
1343
|
+
error
|
|
1344
|
+
});
|
|
1345
|
+
const errorMsg = {
|
|
1346
|
+
type: "error",
|
|
1347
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1348
|
+
stack: error instanceof Error ? error.stack : void 0,
|
|
1349
|
+
requestID: message.requestID
|
|
1350
|
+
};
|
|
1351
|
+
sendMessage(errorMsg);
|
|
1147
1352
|
}
|
|
1148
1353
|
}
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1354
|
+
function handleMessage(event) {
|
|
1355
|
+
const message = event.data;
|
|
1356
|
+
if (message?.type === "cache:response" || message?.type === "cache:error") {
|
|
1357
|
+
messageLogger.debug(`[Worker-${workerId}] Forwarding cache message`, {
|
|
1358
|
+
type: message.type,
|
|
1359
|
+
requestID: message.requestID
|
|
1360
|
+
});
|
|
1361
|
+
handleCacheResponse(message);
|
|
1362
|
+
return;
|
|
1363
|
+
}
|
|
1364
|
+
if (message?.type === "request") {
|
|
1365
|
+
handleFetchRequest(message).catch((error) => {
|
|
1366
|
+
messageLogger.error(`[Worker-${workerId}] Unhandled error: {error}`, {
|
|
1367
|
+
error
|
|
1368
|
+
});
|
|
1369
|
+
});
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
if (message?.type === "shutdown") {
|
|
1373
|
+
messageLogger.debug(`[Worker-${workerId}] Received shutdown signal`);
|
|
1374
|
+
(async () => {
|
|
1375
|
+
try {
|
|
1376
|
+
if (databases) {
|
|
1377
|
+
await databases.closeAll();
|
|
1378
|
+
messageLogger.debug(`[Worker-${workerId}] Databases closed`);
|
|
1379
|
+
}
|
|
1380
|
+
sendMessage({ type: "shutdown-complete" });
|
|
1381
|
+
messageLogger.debug(`[Worker-${workerId}] Shutdown complete`);
|
|
1382
|
+
} catch (error) {
|
|
1383
|
+
messageLogger.error(`[Worker-${workerId}] Shutdown error: {error}`, {
|
|
1384
|
+
error
|
|
1385
|
+
});
|
|
1386
|
+
sendMessage({ type: "shutdown-complete" });
|
|
1387
|
+
}
|
|
1388
|
+
})();
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
1391
|
+
if (message?.type) {
|
|
1392
|
+
messageLogger.debug(`[Worker-${workerId}] Unknown message type`, {
|
|
1393
|
+
type: message.type
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
self.addEventListener("message", handleMessage);
|
|
1398
|
+
sendMessage({ type: "ready" });
|
|
1399
|
+
messageLogger.debug(`[Worker-${workerId}] Message loop started`);
|
|
1400
|
+
}
|
|
1401
|
+
var SHOVEL_DEFAULT_LOGGERS = [
|
|
1402
|
+
{ category: ["shovel"], level: "info", sinks: ["console"] },
|
|
1403
|
+
{ category: ["logtape", "meta"], level: "warning", sinks: ["console"] }
|
|
1404
|
+
];
|
|
1405
|
+
async function createSink(config) {
|
|
1406
|
+
const {
|
|
1407
|
+
impl,
|
|
1408
|
+
path,
|
|
1409
|
+
// Extract path for file-based sinks
|
|
1410
|
+
...sinkOptions
|
|
1411
|
+
} = config;
|
|
1412
|
+
if (!impl) {
|
|
1413
|
+
throw new Error(
|
|
1414
|
+
`Sink has no impl. Ensure the sink module is configured in shovel.json.`
|
|
1415
|
+
);
|
|
1152
1416
|
}
|
|
1153
|
-
|
|
1154
|
-
return
|
|
1417
|
+
if (path !== void 0) {
|
|
1418
|
+
return impl(path, sinkOptions);
|
|
1419
|
+
} else if (Object.keys(sinkOptions).length > 0) {
|
|
1420
|
+
return impl(sinkOptions);
|
|
1421
|
+
} else {
|
|
1422
|
+
return impl();
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
function normalizeCategory(category) {
|
|
1426
|
+
return typeof category === "string" ? [category] : category;
|
|
1427
|
+
}
|
|
1428
|
+
async function configureLogging(loggingConfig) {
|
|
1429
|
+
const userSinks = loggingConfig.sinks || {};
|
|
1430
|
+
const userLoggers = loggingConfig.loggers || [];
|
|
1431
|
+
const sinks = {
|
|
1432
|
+
console: getConsoleSink()
|
|
1155
1433
|
};
|
|
1156
|
-
const
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1434
|
+
for (const [name, config] of Object.entries(userSinks)) {
|
|
1435
|
+
sinks[name] = await createSink(config);
|
|
1436
|
+
}
|
|
1437
|
+
const userCategoryKeys = new Set(
|
|
1438
|
+
userLoggers.map((l) => JSON.stringify(normalizeCategory(l.category)))
|
|
1439
|
+
);
|
|
1440
|
+
const mergedLoggers = [
|
|
1441
|
+
// Shovel defaults (unless overridden by user)
|
|
1442
|
+
...SHOVEL_DEFAULT_LOGGERS.filter(
|
|
1443
|
+
(l) => !userCategoryKeys.has(JSON.stringify(normalizeCategory(l.category)))
|
|
1444
|
+
),
|
|
1445
|
+
// User loggers
|
|
1446
|
+
...userLoggers
|
|
1447
|
+
];
|
|
1448
|
+
const loggers = mergedLoggers.map((loggerConfig) => {
|
|
1449
|
+
const result = {
|
|
1450
|
+
category: normalizeCategory(loggerConfig.category)
|
|
1165
1451
|
};
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1452
|
+
if (loggerConfig.level) {
|
|
1453
|
+
result.lowestLevel = loggerConfig.level;
|
|
1454
|
+
}
|
|
1455
|
+
result.sinks = loggerConfig.sinks ?? ["console"];
|
|
1456
|
+
if (loggerConfig.parentSinks) {
|
|
1457
|
+
result.parentSinks = loggerConfig.parentSinks;
|
|
1458
|
+
}
|
|
1459
|
+
return result;
|
|
1171
1460
|
});
|
|
1172
1461
|
await configure({
|
|
1173
|
-
reset,
|
|
1462
|
+
reset: true,
|
|
1174
1463
|
sinks,
|
|
1175
1464
|
loggers
|
|
1176
1465
|
});
|
|
1177
1466
|
}
|
|
1178
1467
|
export {
|
|
1179
|
-
|
|
1468
|
+
CustomDatabaseStorage,
|
|
1180
1469
|
CustomLoggerStorage,
|
|
1181
1470
|
DedicatedWorkerGlobalScope,
|
|
1182
|
-
ExtendableEvent,
|
|
1183
1471
|
ExtendableMessageEvent,
|
|
1184
|
-
FetchEvent,
|
|
1185
|
-
InstallEvent,
|
|
1186
1472
|
Notification,
|
|
1187
1473
|
NotificationEvent,
|
|
1188
1474
|
PushEvent,
|
|
1189
1475
|
RequestCookieStore,
|
|
1190
1476
|
ServiceWorkerGlobals,
|
|
1477
|
+
ShovelActivateEvent,
|
|
1191
1478
|
ShovelClient,
|
|
1192
1479
|
ShovelClients,
|
|
1480
|
+
ShovelExtendableEvent,
|
|
1481
|
+
ShovelFetchEvent,
|
|
1482
|
+
ShovelInstallEvent,
|
|
1193
1483
|
ShovelNavigationPreloadManager,
|
|
1194
1484
|
ShovelPushMessageData,
|
|
1195
1485
|
ShovelServiceWorker,
|
|
@@ -1199,7 +1489,12 @@ export {
|
|
|
1199
1489
|
SyncEvent,
|
|
1200
1490
|
WorkerGlobalScope,
|
|
1201
1491
|
configureLogging,
|
|
1492
|
+
createCacheFactory,
|
|
1493
|
+
createDatabaseFactory,
|
|
1494
|
+
createDirectoryFactory,
|
|
1495
|
+
initWorkerRuntime,
|
|
1202
1496
|
parseCookieHeader,
|
|
1203
1497
|
parseSetCookieHeader,
|
|
1204
|
-
serializeCookie
|
|
1498
|
+
serializeCookie,
|
|
1499
|
+
startWorkerMessageLoop
|
|
1205
1500
|
};
|