@b9g/platform 0.1.10 → 0.1.12

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.
@@ -1,231 +0,0 @@
1
- /// <reference types="./cookie-store.d.ts" />
2
- // src/cookie-store.ts
3
- function parseCookieHeader(cookieHeader) {
4
- const cookies = /* @__PURE__ */ new Map();
5
- if (!cookieHeader)
6
- return cookies;
7
- const pairs = cookieHeader.split(/;\s*/);
8
- for (const pair of pairs) {
9
- const [name, ...valueParts] = pair.split("=");
10
- if (name) {
11
- const value = valueParts.join("=");
12
- cookies.set(name.trim(), decodeURIComponent(value || ""));
13
- }
14
- }
15
- return cookies;
16
- }
17
- function serializeCookie(cookie) {
18
- let header = `${encodeURIComponent(cookie.name)}=${encodeURIComponent(cookie.value)}`;
19
- if (cookie.expires !== void 0 && cookie.expires !== null) {
20
- const date = new Date(cookie.expires);
21
- header += `; Expires=${date.toUTCString()}`;
22
- }
23
- if (cookie.domain) {
24
- header += `; Domain=${cookie.domain}`;
25
- }
26
- if (cookie.path) {
27
- header += `; Path=${cookie.path}`;
28
- } else {
29
- header += `; Path=/`;
30
- }
31
- if (cookie.sameSite) {
32
- header += `; SameSite=${cookie.sameSite.charAt(0).toUpperCase() + cookie.sameSite.slice(1)}`;
33
- } else {
34
- header += `; SameSite=Strict`;
35
- }
36
- if (cookie.partitioned) {
37
- header += `; Partitioned`;
38
- }
39
- header += `; Secure`;
40
- return header;
41
- }
42
- function parseSetCookieHeader(setCookieHeader) {
43
- const parts = setCookieHeader.split(/;\s*/);
44
- const [nameValue, ...attributes] = parts;
45
- const [name, ...valueParts] = nameValue.split("=");
46
- const value = valueParts.join("=");
47
- const cookie = {
48
- name: decodeURIComponent(name.trim()),
49
- value: decodeURIComponent(value || "")
50
- };
51
- for (const attr of attributes) {
52
- const [key, ...attrValueParts] = attr.split("=");
53
- const attrKey = key.trim().toLowerCase();
54
- const attrValue = attrValueParts.join("=").trim();
55
- switch (attrKey) {
56
- case "expires":
57
- cookie.expires = new Date(attrValue).getTime();
58
- break;
59
- case "max-age":
60
- cookie.expires = Date.now() + parseInt(attrValue, 10) * 1e3;
61
- break;
62
- case "domain":
63
- cookie.domain = attrValue;
64
- break;
65
- case "path":
66
- cookie.path = attrValue;
67
- break;
68
- case "secure":
69
- cookie.secure = true;
70
- break;
71
- case "samesite":
72
- cookie.sameSite = attrValue.toLowerCase();
73
- break;
74
- case "partitioned":
75
- cookie.partitioned = true;
76
- break;
77
- }
78
- }
79
- return cookie;
80
- }
81
- var RequestCookieStore = class extends EventTarget {
82
- #cookies;
83
- #changes;
84
- // null = deleted
85
- #request;
86
- // Event handler for cookie changes (spec compliance)
87
- // eslint-disable-next-line no-restricted-syntax
88
- onchange = null;
89
- constructor(request) {
90
- super();
91
- this.#cookies = /* @__PURE__ */ new Map();
92
- this.#changes = /* @__PURE__ */ new Map();
93
- this.#request = request || null;
94
- if (request) {
95
- const cookieHeader = request.headers.get("cookie");
96
- if (cookieHeader) {
97
- const parsed = parseCookieHeader(cookieHeader);
98
- for (const [name, value] of parsed) {
99
- this.#cookies.set(name, { name, value });
100
- }
101
- }
102
- }
103
- }
104
- /**
105
- * Get a single cookie by name
106
- */
107
- async get(nameOrOptions) {
108
- const name = typeof nameOrOptions === "string" ? nameOrOptions : nameOrOptions.name;
109
- if (!name) {
110
- throw new TypeError("Cookie name is required");
111
- }
112
- if (this.#changes.has(name)) {
113
- const change = this.#changes.get(name);
114
- if (change === null || change === void 0)
115
- return null;
116
- return {
117
- name: change.name,
118
- value: change.value,
119
- domain: change.domain ?? void 0,
120
- path: change.path,
121
- expires: change.expires ?? void 0,
122
- sameSite: change.sameSite,
123
- partitioned: change.partitioned
124
- };
125
- }
126
- return this.#cookies.get(name) || null;
127
- }
128
- /**
129
- * Get all cookies matching the filter
130
- */
131
- async getAll(nameOrOptions) {
132
- const name = typeof nameOrOptions === "string" ? nameOrOptions : nameOrOptions?.name;
133
- const result = [];
134
- const allNames = /* @__PURE__ */ new Set([
135
- ...this.#cookies.keys(),
136
- ...this.#changes.keys()
137
- ]);
138
- for (const cookieName of allNames) {
139
- if (name && cookieName !== name)
140
- continue;
141
- if (this.#changes.has(cookieName) && this.#changes.get(cookieName) === null) {
142
- continue;
143
- }
144
- const cookie = await this.get(cookieName);
145
- if (cookie) {
146
- result.push(cookie);
147
- }
148
- }
149
- return result;
150
- }
151
- /**
152
- * Set a cookie
153
- */
154
- async set(nameOrOptions, value) {
155
- let cookie;
156
- if (typeof nameOrOptions === "string") {
157
- if (value === void 0) {
158
- throw new TypeError("Cookie value is required");
159
- }
160
- cookie = {
161
- name: nameOrOptions,
162
- value,
163
- path: "/",
164
- sameSite: "strict"
165
- };
166
- } else {
167
- cookie = {
168
- path: "/",
169
- sameSite: "strict",
170
- ...nameOrOptions
171
- };
172
- }
173
- const size = cookie.name.length + cookie.value.length;
174
- if (size > 4096) {
175
- throw new TypeError(
176
- `Cookie name+value too large: ${size} bytes (max 4096)`
177
- );
178
- }
179
- this.#changes.set(cookie.name, cookie);
180
- }
181
- /**
182
- * Delete a cookie
183
- */
184
- async delete(nameOrOptions) {
185
- const name = typeof nameOrOptions === "string" ? nameOrOptions : nameOrOptions.name;
186
- if (!name) {
187
- throw new TypeError("Cookie name is required");
188
- }
189
- this.#changes.set(name, null);
190
- }
191
- /**
192
- * Get Set-Cookie headers for all changes
193
- * This should be called when constructing the Response
194
- */
195
- getSetCookieHeaders() {
196
- const headers = [];
197
- for (const [name, change] of this.#changes) {
198
- if (change === null) {
199
- headers.push(
200
- serializeCookie({
201
- name,
202
- value: "",
203
- expires: 0,
204
- path: "/"
205
- })
206
- );
207
- } else {
208
- headers.push(serializeCookie(change));
209
- }
210
- }
211
- return headers;
212
- }
213
- /**
214
- * Check if there are any pending changes
215
- */
216
- hasChanges() {
217
- return this.#changes.size > 0;
218
- }
219
- /**
220
- * Clear all pending changes (for testing/reset)
221
- */
222
- clearChanges() {
223
- this.#changes.clear();
224
- }
225
- };
226
- export {
227
- RequestCookieStore,
228
- parseCookieHeader,
229
- parseSetCookieHeader,
230
- serializeCookie
231
- };
@@ -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 scope as globalThis.self)
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,109 +0,0 @@
1
- /// <reference types="./single-threaded.d.ts" />
2
- // src/single-threaded.ts
3
- import { getLogger } from "@logtape/logtape";
4
- import { ShovelGlobalScope, ShovelServiceWorkerRegistration } from "./runtime.js";
5
- import { CustomBucketStorage } from "@b9g/filesystem";
6
- import { CustomCacheStorage } from "@b9g/cache";
7
- import {
8
- configureLogging,
9
- createBucketFactory,
10
- createCacheFactory
11
- } from "./config.js";
12
- var logger = getLogger(["single-threaded"]);
13
- var SingleThreadedRuntime = class {
14
- #registration;
15
- #scope;
16
- #ready;
17
- #entrypoint;
18
- #config;
19
- constructor(options) {
20
- this.#ready = false;
21
- this.#config = options.config;
22
- const cacheStorage = options.cacheStorage || new CustomCacheStorage(createCacheFactory({ config: options.config }));
23
- const bucketStorage = options.bucketStorage || new CustomBucketStorage(
24
- createBucketFactory({ baseDir: options.baseDir, config: options.config })
25
- );
26
- this.#registration = new ShovelServiceWorkerRegistration();
27
- this.#scope = new ShovelGlobalScope({
28
- registration: this.#registration,
29
- caches: cacheStorage,
30
- buckets: bucketStorage
31
- });
32
- logger.info("SingleThreadedRuntime created", { baseDir: options.baseDir });
33
- }
34
- /**
35
- * Initialize the runtime (install scope as globalThis.self)
36
- */
37
- async init() {
38
- if (this.#config?.logging) {
39
- await configureLogging(this.#config.logging);
40
- }
41
- this.#scope.install();
42
- logger.info("SingleThreadedRuntime initialized - scope installed");
43
- }
44
- /**
45
- * Load and run a ServiceWorker entrypoint
46
- * @param entrypoint - Path to the new entrypoint (hashed filename for cache busting)
47
- */
48
- async reloadWorkers(entrypoint) {
49
- logger.info("Reloading ServiceWorker", {
50
- oldEntrypoint: this.#entrypoint,
51
- newEntrypoint: entrypoint
52
- });
53
- this.#entrypoint = entrypoint;
54
- this.#registration._serviceWorker._setState("parsed");
55
- this.#ready = false;
56
- await import(entrypoint);
57
- await this.#registration.install();
58
- await this.#registration.activate();
59
- this.#ready = true;
60
- logger.info("ServiceWorker loaded and activated", { entrypoint });
61
- }
62
- /**
63
- * Load a ServiceWorker entrypoint for the first time
64
- * @param entrypoint - Path to the entrypoint file (content-hashed filename)
65
- */
66
- async loadEntrypoint(entrypoint) {
67
- this.#entrypoint = entrypoint;
68
- logger.info("Loading ServiceWorker entrypoint", { entrypoint });
69
- await import(entrypoint);
70
- await this.#registration.install();
71
- await this.#registration.activate();
72
- this.#ready = true;
73
- logger.info("ServiceWorker loaded and activated", { entrypoint });
74
- }
75
- /**
76
- * Handle an HTTP request
77
- * This is the key method - direct call, no postMessage!
78
- */
79
- async handleRequest(request) {
80
- if (!this.#ready) {
81
- throw new Error(
82
- "SingleThreadedRuntime not ready - ServiceWorker not loaded"
83
- );
84
- }
85
- return this.#registration.handleRequest(request);
86
- }
87
- /**
88
- * Graceful shutdown
89
- */
90
- async terminate() {
91
- this.#ready = false;
92
- logger.info("SingleThreadedRuntime terminated");
93
- }
94
- /**
95
- * Get the number of workers (always 1 for single-threaded)
96
- */
97
- get workerCount() {
98
- return 1;
99
- }
100
- /**
101
- * Check if ready to handle requests
102
- */
103
- get ready() {
104
- return this.#ready;
105
- }
106
- };
107
- export {
108
- SingleThreadedRuntime
109
- };
@@ -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
- }