@b9g/platform 0.1.11 → 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/README.md +1 -1
- package/package.json +22 -38
- package/src/config.d.ts +15 -163
- package/src/config.js +18 -630
- package/src/globals.d.ts +119 -0
- package/src/index.d.ts +294 -25
- package/src/index.js +466 -126
- package/src/runtime.d.ts +423 -22
- package/src/runtime.js +693 -250
- package/src/shovel-config.d.ts +10 -0
- package/chunk-P57PW2II.js +0 -11
- package/src/cookie-store.d.ts +0 -80
- package/src/cookie-store.js +0 -233
- package/src/single-threaded.d.ts +0 -59
- package/src/single-threaded.js +0 -114
- package/src/worker-pool.d.ts +0 -93
- package/src/worker-pool.js +0 -390
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type declarations for the shovel:config virtual module.
|
|
3
|
+
* This module is resolved by esbuild at build time.
|
|
4
|
+
*/
|
|
5
|
+
declare module "shovel:config" {
|
|
6
|
+
import type {ShovelConfig} from "./runtime.js";
|
|
7
|
+
|
|
8
|
+
export const config: ShovelConfig;
|
|
9
|
+
export type {ShovelConfig};
|
|
10
|
+
}
|
package/chunk-P57PW2II.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
-
}) : x)(function(x) {
|
|
4
|
-
if (typeof require !== "undefined")
|
|
5
|
-
return require.apply(this, arguments);
|
|
6
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
export {
|
|
10
|
-
__require
|
|
11
|
-
};
|
package/src/cookie-store.d.ts
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cookie Store API Implementation
|
|
3
|
-
* https://cookiestore.spec.whatwg.org/
|
|
4
|
-
*
|
|
5
|
-
* Provides asynchronous cookie management for ServiceWorker contexts
|
|
6
|
-
*/
|
|
7
|
-
declare global {
|
|
8
|
-
interface CookieListItem {
|
|
9
|
-
domain?: string;
|
|
10
|
-
path?: string;
|
|
11
|
-
expires?: number;
|
|
12
|
-
secure?: boolean;
|
|
13
|
-
sameSite?: CookieSameSite;
|
|
14
|
-
partitioned?: boolean;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
export type CookieSameSite = globalThis.CookieSameSite;
|
|
18
|
-
export type CookieInit = globalThis.CookieInit;
|
|
19
|
-
export type CookieStoreGetOptions = globalThis.CookieStoreGetOptions;
|
|
20
|
-
export type CookieStoreDeleteOptions = globalThis.CookieStoreDeleteOptions;
|
|
21
|
-
export type CookieListItem = globalThis.CookieListItem;
|
|
22
|
-
export type CookieList = CookieListItem[];
|
|
23
|
-
/**
|
|
24
|
-
* Parse Cookie header value into key-value pairs
|
|
25
|
-
* Cookie: name=value; name2=value2
|
|
26
|
-
*/
|
|
27
|
-
export declare function parseCookieHeader(cookieHeader: string): Map<string, string>;
|
|
28
|
-
/**
|
|
29
|
-
* Serialize cookie into Set-Cookie header value
|
|
30
|
-
*/
|
|
31
|
-
export declare function serializeCookie(cookie: CookieInit): string;
|
|
32
|
-
/**
|
|
33
|
-
* Parse Set-Cookie header into CookieListItem
|
|
34
|
-
*/
|
|
35
|
-
export declare function parseSetCookieHeader(setCookieHeader: string): CookieListItem;
|
|
36
|
-
/**
|
|
37
|
-
* RequestCookieStore - Cookie Store implementation for ServiceWorker contexts
|
|
38
|
-
*
|
|
39
|
-
* This implementation:
|
|
40
|
-
* - Reads cookies from the incoming Request's Cookie header
|
|
41
|
-
* - Tracks changes (set/delete operations)
|
|
42
|
-
* - Exports changes as Set-Cookie headers for the Response
|
|
43
|
-
*
|
|
44
|
-
* It follows the Cookie Store API spec but is designed for server-side
|
|
45
|
-
* request handling rather than browser contexts.
|
|
46
|
-
*/
|
|
47
|
-
export declare class RequestCookieStore extends EventTarget {
|
|
48
|
-
#private;
|
|
49
|
-
onchange: ((this: RequestCookieStore, ev: Event) => any) | null;
|
|
50
|
-
constructor(request?: Request);
|
|
51
|
-
/**
|
|
52
|
-
* Get a single cookie by name
|
|
53
|
-
*/
|
|
54
|
-
get(nameOrOptions: string | CookieStoreGetOptions): Promise<CookieListItem | null>;
|
|
55
|
-
/**
|
|
56
|
-
* Get all cookies matching the filter
|
|
57
|
-
*/
|
|
58
|
-
getAll(nameOrOptions?: string | CookieStoreGetOptions): Promise<CookieList>;
|
|
59
|
-
/**
|
|
60
|
-
* Set a cookie
|
|
61
|
-
*/
|
|
62
|
-
set(nameOrOptions: string | CookieInit, value?: string): Promise<void>;
|
|
63
|
-
/**
|
|
64
|
-
* Delete a cookie
|
|
65
|
-
*/
|
|
66
|
-
delete(nameOrOptions: string | CookieStoreDeleteOptions): Promise<void>;
|
|
67
|
-
/**
|
|
68
|
-
* Get Set-Cookie headers for all changes
|
|
69
|
-
* This should be called when constructing the Response
|
|
70
|
-
*/
|
|
71
|
-
getSetCookieHeaders(): string[];
|
|
72
|
-
/**
|
|
73
|
-
* Check if there are any pending changes
|
|
74
|
-
*/
|
|
75
|
-
hasChanges(): boolean;
|
|
76
|
-
/**
|
|
77
|
-
* Clear all pending changes (for testing/reset)
|
|
78
|
-
*/
|
|
79
|
-
clearChanges(): void;
|
|
80
|
-
}
|
package/src/cookie-store.js
DELETED
|
@@ -1,233 +0,0 @@
|
|
|
1
|
-
/// <reference types="./cookie-store.d.ts" />
|
|
2
|
-
import "../chunk-P57PW2II.js";
|
|
3
|
-
|
|
4
|
-
// src/cookie-store.ts
|
|
5
|
-
function parseCookieHeader(cookieHeader) {
|
|
6
|
-
const cookies = /* @__PURE__ */ new Map();
|
|
7
|
-
if (!cookieHeader)
|
|
8
|
-
return cookies;
|
|
9
|
-
const pairs = cookieHeader.split(/;\s*/);
|
|
10
|
-
for (const pair of pairs) {
|
|
11
|
-
const [name, ...valueParts] = pair.split("=");
|
|
12
|
-
if (name) {
|
|
13
|
-
const value = valueParts.join("=");
|
|
14
|
-
cookies.set(name.trim(), decodeURIComponent(value || ""));
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
return cookies;
|
|
18
|
-
}
|
|
19
|
-
function serializeCookie(cookie) {
|
|
20
|
-
let header = `${encodeURIComponent(cookie.name)}=${encodeURIComponent(cookie.value)}`;
|
|
21
|
-
if (cookie.expires !== void 0 && cookie.expires !== null) {
|
|
22
|
-
const date = new Date(cookie.expires);
|
|
23
|
-
header += `; Expires=${date.toUTCString()}`;
|
|
24
|
-
}
|
|
25
|
-
if (cookie.domain) {
|
|
26
|
-
header += `; Domain=${cookie.domain}`;
|
|
27
|
-
}
|
|
28
|
-
if (cookie.path) {
|
|
29
|
-
header += `; Path=${cookie.path}`;
|
|
30
|
-
} else {
|
|
31
|
-
header += `; Path=/`;
|
|
32
|
-
}
|
|
33
|
-
if (cookie.sameSite) {
|
|
34
|
-
header += `; SameSite=${cookie.sameSite.charAt(0).toUpperCase() + cookie.sameSite.slice(1)}`;
|
|
35
|
-
} else {
|
|
36
|
-
header += `; SameSite=Strict`;
|
|
37
|
-
}
|
|
38
|
-
if (cookie.partitioned) {
|
|
39
|
-
header += `; Partitioned`;
|
|
40
|
-
}
|
|
41
|
-
header += `; Secure`;
|
|
42
|
-
return header;
|
|
43
|
-
}
|
|
44
|
-
function parseSetCookieHeader(setCookieHeader) {
|
|
45
|
-
const parts = setCookieHeader.split(/;\s*/);
|
|
46
|
-
const [nameValue, ...attributes] = parts;
|
|
47
|
-
const [name, ...valueParts] = nameValue.split("=");
|
|
48
|
-
const value = valueParts.join("=");
|
|
49
|
-
const cookie = {
|
|
50
|
-
name: decodeURIComponent(name.trim()),
|
|
51
|
-
value: decodeURIComponent(value || "")
|
|
52
|
-
};
|
|
53
|
-
for (const attr of attributes) {
|
|
54
|
-
const [key, ...attrValueParts] = attr.split("=");
|
|
55
|
-
const attrKey = key.trim().toLowerCase();
|
|
56
|
-
const attrValue = attrValueParts.join("=").trim();
|
|
57
|
-
switch (attrKey) {
|
|
58
|
-
case "expires":
|
|
59
|
-
cookie.expires = new Date(attrValue).getTime();
|
|
60
|
-
break;
|
|
61
|
-
case "max-age":
|
|
62
|
-
cookie.expires = Date.now() + parseInt(attrValue, 10) * 1e3;
|
|
63
|
-
break;
|
|
64
|
-
case "domain":
|
|
65
|
-
cookie.domain = attrValue;
|
|
66
|
-
break;
|
|
67
|
-
case "path":
|
|
68
|
-
cookie.path = attrValue;
|
|
69
|
-
break;
|
|
70
|
-
case "secure":
|
|
71
|
-
cookie.secure = true;
|
|
72
|
-
break;
|
|
73
|
-
case "samesite":
|
|
74
|
-
cookie.sameSite = attrValue.toLowerCase();
|
|
75
|
-
break;
|
|
76
|
-
case "partitioned":
|
|
77
|
-
cookie.partitioned = true;
|
|
78
|
-
break;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
return cookie;
|
|
82
|
-
}
|
|
83
|
-
var RequestCookieStore = class extends EventTarget {
|
|
84
|
-
#cookies;
|
|
85
|
-
#changes;
|
|
86
|
-
// null = deleted
|
|
87
|
-
#request;
|
|
88
|
-
// Event handler for cookie changes (spec compliance)
|
|
89
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
90
|
-
onchange = null;
|
|
91
|
-
constructor(request) {
|
|
92
|
-
super();
|
|
93
|
-
this.#cookies = /* @__PURE__ */ new Map();
|
|
94
|
-
this.#changes = /* @__PURE__ */ new Map();
|
|
95
|
-
this.#request = request || null;
|
|
96
|
-
if (request) {
|
|
97
|
-
const cookieHeader = request.headers.get("cookie");
|
|
98
|
-
if (cookieHeader) {
|
|
99
|
-
const parsed = parseCookieHeader(cookieHeader);
|
|
100
|
-
for (const [name, value] of parsed) {
|
|
101
|
-
this.#cookies.set(name, { name, value });
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Get a single cookie by name
|
|
108
|
-
*/
|
|
109
|
-
async get(nameOrOptions) {
|
|
110
|
-
const name = typeof nameOrOptions === "string" ? nameOrOptions : nameOrOptions.name;
|
|
111
|
-
if (!name) {
|
|
112
|
-
throw new TypeError("Cookie name is required");
|
|
113
|
-
}
|
|
114
|
-
if (this.#changes.has(name)) {
|
|
115
|
-
const change = this.#changes.get(name);
|
|
116
|
-
if (change === null || change === void 0)
|
|
117
|
-
return null;
|
|
118
|
-
return {
|
|
119
|
-
name: change.name,
|
|
120
|
-
value: change.value,
|
|
121
|
-
domain: change.domain ?? void 0,
|
|
122
|
-
path: change.path,
|
|
123
|
-
expires: change.expires ?? void 0,
|
|
124
|
-
sameSite: change.sameSite,
|
|
125
|
-
partitioned: change.partitioned
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
return this.#cookies.get(name) || null;
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Get all cookies matching the filter
|
|
132
|
-
*/
|
|
133
|
-
async getAll(nameOrOptions) {
|
|
134
|
-
const name = typeof nameOrOptions === "string" ? nameOrOptions : nameOrOptions?.name;
|
|
135
|
-
const result = [];
|
|
136
|
-
const allNames = /* @__PURE__ */ new Set([
|
|
137
|
-
...this.#cookies.keys(),
|
|
138
|
-
...this.#changes.keys()
|
|
139
|
-
]);
|
|
140
|
-
for (const cookieName of allNames) {
|
|
141
|
-
if (name && cookieName !== name)
|
|
142
|
-
continue;
|
|
143
|
-
if (this.#changes.has(cookieName) && this.#changes.get(cookieName) === null) {
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
const cookie = await this.get(cookieName);
|
|
147
|
-
if (cookie) {
|
|
148
|
-
result.push(cookie);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
return result;
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* Set a cookie
|
|
155
|
-
*/
|
|
156
|
-
async set(nameOrOptions, value) {
|
|
157
|
-
let cookie;
|
|
158
|
-
if (typeof nameOrOptions === "string") {
|
|
159
|
-
if (value === void 0) {
|
|
160
|
-
throw new TypeError("Cookie value is required");
|
|
161
|
-
}
|
|
162
|
-
cookie = {
|
|
163
|
-
name: nameOrOptions,
|
|
164
|
-
value,
|
|
165
|
-
path: "/",
|
|
166
|
-
sameSite: "strict"
|
|
167
|
-
};
|
|
168
|
-
} else {
|
|
169
|
-
cookie = {
|
|
170
|
-
path: "/",
|
|
171
|
-
sameSite: "strict",
|
|
172
|
-
...nameOrOptions
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
const size = cookie.name.length + cookie.value.length;
|
|
176
|
-
if (size > 4096) {
|
|
177
|
-
throw new TypeError(
|
|
178
|
-
`Cookie name+value too large: ${size} bytes (max 4096)`
|
|
179
|
-
);
|
|
180
|
-
}
|
|
181
|
-
this.#changes.set(cookie.name, cookie);
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* Delete a cookie
|
|
185
|
-
*/
|
|
186
|
-
async delete(nameOrOptions) {
|
|
187
|
-
const name = typeof nameOrOptions === "string" ? nameOrOptions : nameOrOptions.name;
|
|
188
|
-
if (!name) {
|
|
189
|
-
throw new TypeError("Cookie name is required");
|
|
190
|
-
}
|
|
191
|
-
this.#changes.set(name, null);
|
|
192
|
-
}
|
|
193
|
-
/**
|
|
194
|
-
* Get Set-Cookie headers for all changes
|
|
195
|
-
* This should be called when constructing the Response
|
|
196
|
-
*/
|
|
197
|
-
getSetCookieHeaders() {
|
|
198
|
-
const headers = [];
|
|
199
|
-
for (const [name, change] of this.#changes) {
|
|
200
|
-
if (change === null) {
|
|
201
|
-
headers.push(
|
|
202
|
-
serializeCookie({
|
|
203
|
-
name,
|
|
204
|
-
value: "",
|
|
205
|
-
expires: 0,
|
|
206
|
-
path: "/"
|
|
207
|
-
})
|
|
208
|
-
);
|
|
209
|
-
} else {
|
|
210
|
-
headers.push(serializeCookie(change));
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
return headers;
|
|
214
|
-
}
|
|
215
|
-
/**
|
|
216
|
-
* Check if there are any pending changes
|
|
217
|
-
*/
|
|
218
|
-
hasChanges() {
|
|
219
|
-
return this.#changes.size > 0;
|
|
220
|
-
}
|
|
221
|
-
/**
|
|
222
|
-
* Clear all pending changes (for testing/reset)
|
|
223
|
-
*/
|
|
224
|
-
clearChanges() {
|
|
225
|
-
this.#changes.clear();
|
|
226
|
-
}
|
|
227
|
-
};
|
|
228
|
-
export {
|
|
229
|
-
RequestCookieStore,
|
|
230
|
-
parseCookieHeader,
|
|
231
|
-
parseSetCookieHeader,
|
|
232
|
-
serializeCookie
|
|
233
|
-
};
|
package/src/single-threaded.d.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @b9g/platform/single-threaded - Single-threaded ServiceWorker runtime
|
|
3
|
-
*
|
|
4
|
-
* Runs ServiceWorker code directly in the main thread without spawning workers.
|
|
5
|
-
* Used when workerCount === 1 for maximum performance (no postMessage overhead).
|
|
6
|
-
*/
|
|
7
|
-
import { CustomBucketStorage } from "@b9g/filesystem";
|
|
8
|
-
import { type ProcessedShovelConfig } from "./config.js";
|
|
9
|
-
export interface SingleThreadedRuntimeOptions {
|
|
10
|
-
/** Base directory for bucket path resolution (entrypoint directory) - REQUIRED */
|
|
11
|
-
baseDir: string;
|
|
12
|
-
/** Optional pre-created cache storage (for sharing across reloads) */
|
|
13
|
-
cacheStorage?: CacheStorage;
|
|
14
|
-
/** Optional pre-created bucket storage */
|
|
15
|
-
bucketStorage?: CustomBucketStorage;
|
|
16
|
-
/** Shovel configuration for bucket/cache settings */
|
|
17
|
-
config?: ProcessedShovelConfig;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Single-threaded ServiceWorker runtime
|
|
21
|
-
*
|
|
22
|
-
* Provides the same interface as ServiceWorkerPool but runs everything
|
|
23
|
-
* in the main thread for zero postMessage overhead.
|
|
24
|
-
*/
|
|
25
|
-
export declare class SingleThreadedRuntime {
|
|
26
|
-
#private;
|
|
27
|
-
constructor(options: SingleThreadedRuntimeOptions);
|
|
28
|
-
/**
|
|
29
|
-
* Initialize the runtime (install ServiceWorker globals)
|
|
30
|
-
*/
|
|
31
|
-
init(): Promise<void>;
|
|
32
|
-
/**
|
|
33
|
-
* Load and run a ServiceWorker entrypoint
|
|
34
|
-
* @param entrypoint - Path to the new entrypoint (hashed filename for cache busting)
|
|
35
|
-
*/
|
|
36
|
-
reloadWorkers(entrypoint: string): Promise<void>;
|
|
37
|
-
/**
|
|
38
|
-
* Load a ServiceWorker entrypoint for the first time
|
|
39
|
-
* @param entrypoint - Path to the entrypoint file (content-hashed filename)
|
|
40
|
-
*/
|
|
41
|
-
loadEntrypoint(entrypoint: string): Promise<void>;
|
|
42
|
-
/**
|
|
43
|
-
* Handle an HTTP request
|
|
44
|
-
* This is the key method - direct call, no postMessage!
|
|
45
|
-
*/
|
|
46
|
-
handleRequest(request: Request): Promise<Response>;
|
|
47
|
-
/**
|
|
48
|
-
* Graceful shutdown
|
|
49
|
-
*/
|
|
50
|
-
terminate(): Promise<void>;
|
|
51
|
-
/**
|
|
52
|
-
* Get the number of workers (always 1 for single-threaded)
|
|
53
|
-
*/
|
|
54
|
-
get workerCount(): number;
|
|
55
|
-
/**
|
|
56
|
-
* Check if ready to handle requests
|
|
57
|
-
*/
|
|
58
|
-
get ready(): boolean;
|
|
59
|
-
}
|
package/src/single-threaded.js
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
/// <reference types="./single-threaded.d.ts" />
|
|
2
|
-
import "../chunk-P57PW2II.js";
|
|
3
|
-
|
|
4
|
-
// src/single-threaded.ts
|
|
5
|
-
import { getLogger } from "@logtape/logtape";
|
|
6
|
-
import {
|
|
7
|
-
ServiceWorkerGlobals,
|
|
8
|
-
ShovelServiceWorkerRegistration
|
|
9
|
-
} from "./runtime.js";
|
|
10
|
-
import { CustomBucketStorage } from "@b9g/filesystem";
|
|
11
|
-
import { CustomCacheStorage } from "@b9g/cache";
|
|
12
|
-
import {
|
|
13
|
-
configureLogging,
|
|
14
|
-
createBucketFactory,
|
|
15
|
-
createCacheFactory
|
|
16
|
-
} from "./config.js";
|
|
17
|
-
var logger = getLogger(["single-threaded"]);
|
|
18
|
-
var SingleThreadedRuntime = class {
|
|
19
|
-
#registration;
|
|
20
|
-
#scope;
|
|
21
|
-
#ready;
|
|
22
|
-
#entrypoint;
|
|
23
|
-
#config;
|
|
24
|
-
constructor(options) {
|
|
25
|
-
this.#ready = false;
|
|
26
|
-
this.#config = options.config;
|
|
27
|
-
const cacheStorage = options.cacheStorage || new CustomCacheStorage(createCacheFactory({ config: options.config }));
|
|
28
|
-
const bucketStorage = options.bucketStorage || new CustomBucketStorage(
|
|
29
|
-
createBucketFactory({ baseDir: options.baseDir, config: options.config })
|
|
30
|
-
);
|
|
31
|
-
this.#registration = new ShovelServiceWorkerRegistration();
|
|
32
|
-
this.#scope = new ServiceWorkerGlobals({
|
|
33
|
-
registration: this.#registration,
|
|
34
|
-
caches: cacheStorage,
|
|
35
|
-
buckets: bucketStorage
|
|
36
|
-
});
|
|
37
|
-
logger.info("SingleThreadedRuntime created", { baseDir: options.baseDir });
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Initialize the runtime (install ServiceWorker globals)
|
|
41
|
-
*/
|
|
42
|
-
async init() {
|
|
43
|
-
if (this.#config?.logging) {
|
|
44
|
-
await configureLogging(this.#config.logging);
|
|
45
|
-
}
|
|
46
|
-
this.#scope.install();
|
|
47
|
-
logger.info("SingleThreadedRuntime initialized - globals installed");
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Load and run a ServiceWorker entrypoint
|
|
51
|
-
* @param entrypoint - Path to the new entrypoint (hashed filename for cache busting)
|
|
52
|
-
*/
|
|
53
|
-
async reloadWorkers(entrypoint) {
|
|
54
|
-
logger.info("Reloading ServiceWorker", {
|
|
55
|
-
oldEntrypoint: this.#entrypoint,
|
|
56
|
-
newEntrypoint: entrypoint
|
|
57
|
-
});
|
|
58
|
-
this.#entrypoint = entrypoint;
|
|
59
|
-
this.#registration._serviceWorker._setState("parsed");
|
|
60
|
-
this.#ready = false;
|
|
61
|
-
await import(entrypoint);
|
|
62
|
-
await this.#registration.install();
|
|
63
|
-
await this.#registration.activate();
|
|
64
|
-
this.#ready = true;
|
|
65
|
-
logger.info("ServiceWorker loaded and activated", { entrypoint });
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Load a ServiceWorker entrypoint for the first time
|
|
69
|
-
* @param entrypoint - Path to the entrypoint file (content-hashed filename)
|
|
70
|
-
*/
|
|
71
|
-
async loadEntrypoint(entrypoint) {
|
|
72
|
-
this.#entrypoint = entrypoint;
|
|
73
|
-
logger.info("Loading ServiceWorker entrypoint", { entrypoint });
|
|
74
|
-
await import(entrypoint);
|
|
75
|
-
await this.#registration.install();
|
|
76
|
-
await this.#registration.activate();
|
|
77
|
-
this.#ready = true;
|
|
78
|
-
logger.info("ServiceWorker loaded and activated", { entrypoint });
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Handle an HTTP request
|
|
82
|
-
* This is the key method - direct call, no postMessage!
|
|
83
|
-
*/
|
|
84
|
-
async handleRequest(request) {
|
|
85
|
-
if (!this.#ready) {
|
|
86
|
-
throw new Error(
|
|
87
|
-
"SingleThreadedRuntime not ready - ServiceWorker not loaded"
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
return this.#registration.handleRequest(request);
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Graceful shutdown
|
|
94
|
-
*/
|
|
95
|
-
async terminate() {
|
|
96
|
-
this.#ready = false;
|
|
97
|
-
logger.info("SingleThreadedRuntime terminated");
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* Get the number of workers (always 1 for single-threaded)
|
|
101
|
-
*/
|
|
102
|
-
get workerCount() {
|
|
103
|
-
return 1;
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* Check if ready to handle requests
|
|
107
|
-
*/
|
|
108
|
-
get ready() {
|
|
109
|
-
return this.#ready;
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
export {
|
|
113
|
-
SingleThreadedRuntime
|
|
114
|
-
};
|
package/src/worker-pool.d.ts
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @b9g/platform/worker-pool - ServiceWorker pool implementation
|
|
3
|
-
*
|
|
4
|
-
* Manages a pool of workers that run the ServiceWorker runtime.
|
|
5
|
-
* Handles worker lifecycle, message passing, and request routing.
|
|
6
|
-
*/
|
|
7
|
-
export interface WorkerPoolOptions {
|
|
8
|
-
/** Number of workers in the pool (default: 1) */
|
|
9
|
-
workerCount?: number;
|
|
10
|
-
/** Request timeout in milliseconds (default: 30000) */
|
|
11
|
-
requestTimeout?: number;
|
|
12
|
-
/** Working directory for file resolution */
|
|
13
|
-
cwd?: string;
|
|
14
|
-
}
|
|
15
|
-
export interface WorkerMessage {
|
|
16
|
-
type: string;
|
|
17
|
-
[key: string]: any;
|
|
18
|
-
}
|
|
19
|
-
export interface WorkerRequest extends WorkerMessage {
|
|
20
|
-
type: "request";
|
|
21
|
-
request: {
|
|
22
|
-
url: string;
|
|
23
|
-
method: string;
|
|
24
|
-
headers: Record<string, string>;
|
|
25
|
-
body?: ArrayBuffer | null;
|
|
26
|
-
};
|
|
27
|
-
requestID: number;
|
|
28
|
-
}
|
|
29
|
-
export interface WorkerResponse extends WorkerMessage {
|
|
30
|
-
type: "response";
|
|
31
|
-
response: {
|
|
32
|
-
status: number;
|
|
33
|
-
statusText: string;
|
|
34
|
-
headers: Record<string, string>;
|
|
35
|
-
body: ArrayBuffer;
|
|
36
|
-
};
|
|
37
|
-
requestID: number;
|
|
38
|
-
}
|
|
39
|
-
export interface WorkerLoadMessage extends WorkerMessage {
|
|
40
|
-
type: "load";
|
|
41
|
-
entrypoint: string;
|
|
42
|
-
}
|
|
43
|
-
export interface WorkerReadyMessage extends WorkerMessage {
|
|
44
|
-
type: "ready" | "worker-ready";
|
|
45
|
-
entrypoint?: string;
|
|
46
|
-
}
|
|
47
|
-
export interface WorkerErrorMessage extends WorkerMessage {
|
|
48
|
-
type: "error";
|
|
49
|
-
error: string;
|
|
50
|
-
stack?: string;
|
|
51
|
-
requestID?: number;
|
|
52
|
-
}
|
|
53
|
-
export interface WorkerInitMessage extends WorkerMessage {
|
|
54
|
-
type: "init";
|
|
55
|
-
config: any;
|
|
56
|
-
baseDir: string;
|
|
57
|
-
}
|
|
58
|
-
export interface WorkerInitializedMessage extends WorkerMessage {
|
|
59
|
-
type: "initialized";
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* ServiceWorkerPool - manages a pool of ServiceWorker instances
|
|
63
|
-
* Handles HTTP request/response routing, cache coordination, and hot reloading
|
|
64
|
-
*/
|
|
65
|
-
export declare class ServiceWorkerPool {
|
|
66
|
-
#private;
|
|
67
|
-
constructor(options?: WorkerPoolOptions, appEntrypoint?: string, cacheStorage?: CacheStorage, config?: any);
|
|
68
|
-
/**
|
|
69
|
-
* Initialize workers (must be called after construction)
|
|
70
|
-
*/
|
|
71
|
-
init(): Promise<void>;
|
|
72
|
-
/**
|
|
73
|
-
* Handle HTTP request using round-robin worker selection
|
|
74
|
-
*/
|
|
75
|
-
handleRequest(request: Request): Promise<Response>;
|
|
76
|
-
/**
|
|
77
|
-
* Reload ServiceWorker with new entrypoint (hot reload)
|
|
78
|
-
* The entrypoint path contains a content hash for cache busting
|
|
79
|
-
*/
|
|
80
|
-
reloadWorkers(entrypoint: string): Promise<void>;
|
|
81
|
-
/**
|
|
82
|
-
* Graceful shutdown of all workers
|
|
83
|
-
*/
|
|
84
|
-
terminate(): Promise<void>;
|
|
85
|
-
/**
|
|
86
|
-
* Get the number of active workers
|
|
87
|
-
*/
|
|
88
|
-
get workerCount(): number;
|
|
89
|
-
/**
|
|
90
|
-
* Check if the pool is ready to handle requests
|
|
91
|
-
*/
|
|
92
|
-
get ready(): boolean;
|
|
93
|
-
}
|