@b9g/platform 0.1.4 → 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/README.md +94 -28
- package/package.json +44 -86
- package/src/config.d.ts +105 -0
- package/src/config.js +463 -0
- package/src/cookie-store.d.ts +80 -0
- package/src/cookie-store.js +231 -0
- package/src/index.d.ts +189 -9
- package/src/index.js +242 -49
- package/src/runtime.d.ts +426 -0
- package/src/runtime.js +997 -0
- package/src/single-threaded.d.ts +57 -0
- package/src/single-threaded.js +107 -0
- package/src/worker-pool.d.ts +22 -32
- package/src/worker-pool.js +214 -100
- package/src/adapter-registry.d.ts +0 -53
- package/src/adapter-registry.js +0 -71
- package/src/base-platform.d.ts +0 -49
- package/src/base-platform.js +0 -114
- package/src/detection.d.ts +0 -36
- package/src/detection.js +0 -126
- package/src/directory-storage.d.ts +0 -41
- package/src/directory-storage.js +0 -53
- package/src/filesystem.d.ts +0 -16
- package/src/filesystem.js +0 -20
- package/src/registry.d.ts +0 -31
- package/src/registry.js +0 -74
- package/src/service-worker.d.ts +0 -175
- package/src/service-worker.js +0 -245
- package/src/types.d.ts +0 -246
- package/src/types.js +0 -36
- package/src/utils.d.ts +0 -33
- package/src/utils.js +0 -139
- package/src/worker-web.js +0 -119
|
@@ -0,0 +1,231 @@
|
|
|
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
|
+
};
|
package/src/index.d.ts
CHANGED
|
@@ -4,12 +4,192 @@
|
|
|
4
4
|
* Platform = "ServiceWorker entrypoint loader for JavaScript runtimes"
|
|
5
5
|
* Core responsibility: Take a ServiceWorker-style app file and make it run in this environment.
|
|
6
6
|
*/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Platform configuration
|
|
9
|
+
* Extended by platform-specific implementations (NodePlatformOptions, etc.)
|
|
10
|
+
*/
|
|
11
|
+
export interface PlatformConfig {
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Server options for platform implementations
|
|
15
|
+
*/
|
|
16
|
+
export interface ServerOptions {
|
|
17
|
+
/** Port to listen on */
|
|
18
|
+
port?: number;
|
|
19
|
+
/** Host to bind to */
|
|
20
|
+
host?: string;
|
|
21
|
+
/** Development mode settings */
|
|
22
|
+
development?: {
|
|
23
|
+
/** Source maps support */
|
|
24
|
+
sourceMaps?: boolean;
|
|
25
|
+
/** Verbose logging */
|
|
26
|
+
verbose?: boolean;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Request handler function (Web Fetch API compatible)
|
|
31
|
+
*/
|
|
32
|
+
export type Handler = (request: Request, context?: any) => Promise<Response> | Response;
|
|
33
|
+
/**
|
|
34
|
+
* Server instance returned by platform.createServer()
|
|
35
|
+
*/
|
|
36
|
+
export interface Server {
|
|
37
|
+
/** Start listening for requests */
|
|
38
|
+
listen(): Promise<void>;
|
|
39
|
+
/** Stop the server */
|
|
40
|
+
close(): Promise<void>;
|
|
41
|
+
/** Get server address information */
|
|
42
|
+
address(): {
|
|
43
|
+
port: number;
|
|
44
|
+
host: string;
|
|
45
|
+
};
|
|
46
|
+
/** Get server URL */
|
|
47
|
+
readonly url: string;
|
|
48
|
+
/** Whether server is ready to accept requests */
|
|
49
|
+
readonly ready: boolean;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* ServiceWorker entrypoint options
|
|
53
|
+
*/
|
|
54
|
+
export interface ServiceWorkerOptions {
|
|
55
|
+
/** Additional context to provide */
|
|
56
|
+
context?: any;
|
|
57
|
+
/** Number of worker threads (Node/Bun only) */
|
|
58
|
+
workerCount?: number;
|
|
59
|
+
/** Enable hot reload (dev mode) - forces worker mode for reliable reloading */
|
|
60
|
+
hotReload?: boolean;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* ServiceWorker instance returned by platform
|
|
64
|
+
*/
|
|
65
|
+
export interface ServiceWorkerInstance {
|
|
66
|
+
/** The ServiceWorker runtime */
|
|
67
|
+
runtime: any;
|
|
68
|
+
/** Handle HTTP request */
|
|
69
|
+
handleRequest(request: Request): Promise<Response>;
|
|
70
|
+
/** Install the ServiceWorker */
|
|
71
|
+
install(): Promise<void>;
|
|
72
|
+
/** Activate the ServiceWorker */
|
|
73
|
+
activate(): Promise<void>;
|
|
74
|
+
/** Check if ready to handle requests */
|
|
75
|
+
readonly ready: boolean;
|
|
76
|
+
/** Dispose of resources */
|
|
77
|
+
dispose(): Promise<void>;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Platform interface - ServiceWorker entrypoint loader for JavaScript runtimes
|
|
81
|
+
*
|
|
82
|
+
* The core responsibility: "Take a ServiceWorker-style app file and make it run in this environment"
|
|
83
|
+
*/
|
|
84
|
+
export interface Platform {
|
|
85
|
+
/**
|
|
86
|
+
* Platform name for identification
|
|
87
|
+
*/
|
|
88
|
+
readonly name: string;
|
|
89
|
+
/**
|
|
90
|
+
* Load and run a ServiceWorker-style entrypoint
|
|
91
|
+
* This is where all the platform-specific complexity lives
|
|
92
|
+
*/
|
|
93
|
+
loadServiceWorker(entrypoint: string, options?: ServiceWorkerOptions): Promise<ServiceWorkerInstance>;
|
|
94
|
+
/**
|
|
95
|
+
* SUPPORTING UTILITY - Create cache storage
|
|
96
|
+
* Returns empty CacheStorage - applications create caches on-demand via caches.open()
|
|
97
|
+
*/
|
|
98
|
+
createCaches(): Promise<CacheStorage>;
|
|
99
|
+
/**
|
|
100
|
+
* SUPPORTING UTILITY - Create server instance for this platform
|
|
101
|
+
*/
|
|
102
|
+
createServer(handler: Handler, options?: ServerOptions): Server;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Platform registry - internal implementation
|
|
106
|
+
*/
|
|
107
|
+
interface PlatformRegistry {
|
|
108
|
+
/** Register a platform implementation */
|
|
109
|
+
register(name: string, platform: any): void;
|
|
110
|
+
/** Get platform by name */
|
|
111
|
+
get(name: string): any | undefined;
|
|
112
|
+
/** Get all registered platforms */
|
|
113
|
+
list(): string[];
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Detect the current JavaScript runtime
|
|
117
|
+
*/
|
|
118
|
+
export declare function detectRuntime(): "bun" | "deno" | "node";
|
|
119
|
+
/**
|
|
120
|
+
* Detect deployment platform from environment
|
|
121
|
+
*
|
|
122
|
+
* Supports:
|
|
123
|
+
* - Cloudflare Workers
|
|
124
|
+
*
|
|
125
|
+
* Future platforms (Lambda, Vercel, Netlify, Deno) will be added post-launch
|
|
126
|
+
*/
|
|
127
|
+
export declare function detectDeploymentPlatform(): string | null;
|
|
128
|
+
/**
|
|
129
|
+
* Detect platform for development
|
|
130
|
+
*
|
|
131
|
+
* Priority:
|
|
132
|
+
* 1. Check package.json for installed @b9g/platform-* package
|
|
133
|
+
* 2. Fallback to current runtime (bun/node/deno)
|
|
134
|
+
*/
|
|
135
|
+
export declare function detectDevelopmentPlatform(): string;
|
|
136
|
+
/**
|
|
137
|
+
* Resolve platform name from options or auto-detect
|
|
138
|
+
*
|
|
139
|
+
* Priority:
|
|
140
|
+
* 1. Explicit --platform or --target flag
|
|
141
|
+
* 2. Deployment platform detection (production environments)
|
|
142
|
+
* 3. Development platform detection (local runtime)
|
|
143
|
+
*/
|
|
144
|
+
export declare function resolvePlatform(options: {
|
|
145
|
+
platform?: string;
|
|
146
|
+
target?: string;
|
|
147
|
+
}): string;
|
|
148
|
+
/**
|
|
149
|
+
* Create platform instance based on name
|
|
150
|
+
*/
|
|
151
|
+
export declare function createPlatform(platformName: string, options?: any): Promise<any>;
|
|
152
|
+
/**
|
|
153
|
+
* Base platform class with shared adapter loading logic
|
|
154
|
+
* Platform implementations extend this and provide platform-specific methods
|
|
155
|
+
*/
|
|
156
|
+
export declare abstract class BasePlatform implements Platform {
|
|
157
|
+
config: PlatformConfig;
|
|
158
|
+
constructor(config?: PlatformConfig);
|
|
159
|
+
abstract readonly name: string;
|
|
160
|
+
abstract loadServiceWorker(entrypoint: string, options?: any): Promise<any>;
|
|
161
|
+
abstract createServer(handler: any, options?: any): any;
|
|
162
|
+
/**
|
|
163
|
+
* Create cache storage
|
|
164
|
+
* Returns empty CacheStorage - applications create caches on-demand via caches.open()
|
|
165
|
+
*/
|
|
166
|
+
createCaches(): Promise<CacheStorage>;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Global platform registry
|
|
170
|
+
*/
|
|
171
|
+
declare class DefaultPlatformRegistry implements PlatformRegistry {
|
|
172
|
+
#private;
|
|
173
|
+
constructor();
|
|
174
|
+
register(name: string, platform: Platform): void;
|
|
175
|
+
get(name: string): Platform | undefined;
|
|
176
|
+
list(): string[];
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Global platform registry instance
|
|
180
|
+
*/
|
|
181
|
+
export declare const platformRegistry: DefaultPlatformRegistry;
|
|
182
|
+
/**
|
|
183
|
+
* Get platform by name with error handling
|
|
184
|
+
*/
|
|
185
|
+
export declare function getPlatform(name?: string): Platform;
|
|
186
|
+
/**
|
|
187
|
+
* Get platform with async auto-registration fallback
|
|
188
|
+
*/
|
|
189
|
+
export declare function getPlatformAsync(name?: string): Promise<Platform>;
|
|
190
|
+
export { ServiceWorkerPool, type WorkerPoolOptions, type WorkerMessage, type WorkerRequest, type WorkerResponse, type WorkerLoadMessage, type WorkerReadyMessage, type WorkerErrorMessage, type WorkerInitMessage, type WorkerInitializedMessage, } from "./worker-pool.js";
|
|
191
|
+
export { SingleThreadedRuntime, type SingleThreadedRuntimeOptions, } from "./single-threaded.js";
|
|
192
|
+
export { ShovelServiceWorkerRegistration, ShovelGlobalScope, FetchEvent, InstallEvent, ActivateEvent, ExtendableEvent, } from "./runtime.js";
|
|
193
|
+
export { RequestCookieStore, type CookieListItem, type CookieInit, type CookieStoreGetOptions, type CookieStoreDeleteOptions, type CookieSameSite, type CookieList, parseCookieHeader, serializeCookie, parseSetCookieHeader, } from "./cookie-store.js";
|
|
194
|
+
export { CustomBucketStorage } from "@b9g/filesystem";
|
|
195
|
+
export { loadConfig, getCacheConfig, getBucketConfig, parseConfigExpr, processConfigValue, matchPattern, createBucketFactory, createCacheFactory, type ShovelConfig, type CacheConfig, type BucketConfig, type BucketFactoryOptions, type CacheFactoryOptions, type ProcessedShovelConfig, } from "./config.js";
|