@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.
@@ -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
- };
@@ -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
- }
@@ -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
- };
@@ -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
- }
@@ -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
- };
@@ -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
- }