@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,119 @@
1
+ /// <reference lib="webworker" />
2
+
3
+ /**
4
+ * Global type declarations for Shovel ServiceWorker environment.
5
+ *
6
+ * These types augment the global scope with Shovel-specific APIs
7
+ * that are installed by ServiceWorkerGlobals.
8
+ *
9
+ * Usage: Include this file in your tsconfig.json "include" array
10
+ * or reference it with /// <reference types="@b9g/platform/globals" />
11
+ */
12
+
13
+ import type {Logger} from "@logtape/logtape";
14
+ import type {DirectoryStorage} from "@b9g/filesystem";
15
+
16
+ declare global {
17
+ /**
18
+ * Logger storage API for accessing named loggers.
19
+ * @example const logger = self.loggers.get(["app"]);
20
+ * @example const dbLogger = self.loggers.get(["app", "db"]);
21
+ */
22
+ interface LoggerStorage {
23
+ get(categories: string[]): Logger;
24
+ }
25
+
26
+ /**
27
+ * Upgrade event passed to onUpgrade callback during database.open().
28
+ */
29
+ interface DatabaseUpgradeEvent {
30
+ /** The database being upgraded */
31
+ db: unknown;
32
+ /** Previous database version (0 if new) */
33
+ oldVersion: number;
34
+ /** Target version being opened */
35
+ newVersion: number;
36
+ /** Register a promise that must complete before open() resolves */
37
+ waitUntil(promise: Promise<unknown>): void;
38
+ }
39
+
40
+ /**
41
+ * Database storage API for accessing named database instances.
42
+ *
43
+ * @example
44
+ * // In activate - open with migrations
45
+ * await self.databases.open("main", 2, (e) => {
46
+ * e.waitUntil(runMigrations(e));
47
+ * });
48
+ *
49
+ * // In fetch - get opened database (sync)
50
+ * const db = self.databases.get("main");
51
+ */
52
+ interface DatabaseStorage {
53
+ /** Open a database at a specific version, running migrations if needed */
54
+ open(
55
+ name: string,
56
+ version: number,
57
+ onUpgrade?: (event: DatabaseUpgradeEvent) => void,
58
+ ): Promise<unknown>;
59
+ /** Get an already-opened database (throws if not opened) */
60
+ get(name: string): unknown;
61
+ /** Close a specific database */
62
+ close(name: string): Promise<void>;
63
+ /** Close all databases */
64
+ closeAll(): Promise<void>;
65
+ }
66
+
67
+ /**
68
+ * Directory storage API for accessing named directories.
69
+ * @example const uploads = await directories.open("uploads");
70
+ */
71
+ var directories: DirectoryStorage;
72
+
73
+ /**
74
+ * Logger storage API for accessing named loggers.
75
+ * @example const logger = self.loggers.get(["app"]);
76
+ * @example const dbLogger = self.loggers.get(["app", "db"]);
77
+ */
78
+ var loggers: LoggerStorage;
79
+
80
+ /**
81
+ * Database storage API for accessing named database instances.
82
+ * @example const db = self.databases.get("main");
83
+ */
84
+ var databases: DatabaseStorage;
85
+
86
+ /**
87
+ * Environment variables available via import.meta.env
88
+ * Works across all platforms (Node/Bun via esbuild shim, Cloudflare natively)
89
+ */
90
+ interface ImportMetaEnv {
91
+ readonly [key: string]: string | undefined;
92
+ }
93
+
94
+ interface ImportMeta {
95
+ readonly env: ImportMetaEnv;
96
+ }
97
+
98
+ /**
99
+ * Augment WorkerGlobalScopeEventMap with ServiceWorker events.
100
+ *
101
+ * TypeScript's lib.webworker.d.ts declares `self` as `WorkerGlobalScope`, not
102
+ * `ServiceWorkerGlobalScope`. This means `self.addEventListener("fetch", ...)`
103
+ * doesn't know about FetchEvent. See: https://github.com/microsoft/TypeScript/issues/14877
104
+ *
105
+ * Rather than trying to redeclare `self` (which causes conflicts), we augment
106
+ * the base WorkerGlobalScopeEventMap to include ServiceWorker-specific events.
107
+ * This allows `self.addEventListener("fetch", (event) => ...)` to correctly
108
+ * infer `event` as `FetchEvent`.
109
+ */
110
+ interface WorkerGlobalScopeEventMap {
111
+ fetch: FetchEvent;
112
+ install: ExtendableEvent;
113
+ activate: ExtendableEvent;
114
+ message: ExtendableMessageEvent;
115
+ messageerror: MessageEvent;
116
+ }
117
+ }
118
+
119
+ export {};
package/src/index.d.ts CHANGED
@@ -1,9 +1,19 @@
1
+ /// <reference path="./globals.d.ts" />
2
+ /// <reference path="./shovel-config.d.ts" />
1
3
  /**
2
4
  * @b9g/platform - Platform interface for ServiceWorker entrypoint loading
3
5
  *
4
6
  * Platform = "ServiceWorker entrypoint loader for JavaScript runtimes"
5
7
  * Core responsibility: Take a ServiceWorker-style app file and make it run in this environment.
8
+ *
9
+ * This module contains:
10
+ * - Platform interface and base classes
11
+ * - SingleThreadedRuntime for main-thread execution
12
+ * - ServiceWorkerPool for multi-worker execution
6
13
  */
14
+ import type { DirectoryStorage } from "@b9g/filesystem";
15
+ import { CustomLoggerStorage, type LoggerStorage, type DatabaseStorage } from "./runtime.js";
16
+ export { validateConfig, ConfigValidationError } from "./config.js";
7
17
  /**
8
18
  * Platform configuration
9
19
  * Extended by platform-specific implementations (NodePlatformOptions, etc.)
@@ -18,13 +28,6 @@ export interface ServerOptions {
18
28
  port?: number;
19
29
  /** Host to bind to */
20
30
  host?: string;
21
- /** Development mode settings */
22
- development?: {
23
- /** Source maps support */
24
- sourceMaps?: boolean;
25
- /** Verbose logging */
26
- verbose?: boolean;
27
- };
28
31
  }
29
32
  /**
30
33
  * Request handler function (Web Fetch API compatible)
@@ -76,6 +79,68 @@ export interface ServiceWorkerInstance {
76
79
  /** Dispose of resources */
77
80
  dispose(): Promise<void>;
78
81
  }
82
+ /**
83
+ * Options for getEntryWrapper()
84
+ */
85
+ export interface EntryWrapperOptions {
86
+ /**
87
+ * Type of entry wrapper to generate:
88
+ * - "production": Production server entry (default) - runs the server directly
89
+ * - "worker": Worker entry for ServiceWorkerPool - sets up runtime and message loop
90
+ */
91
+ type?: "production" | "worker";
92
+ /**
93
+ * Output directory for the build. Used to generate absolute paths for
94
+ * directory defaults (server, public). Required for "worker" type.
95
+ */
96
+ outDir?: string;
97
+ }
98
+ /**
99
+ * ESBuild configuration subset that platforms can customize
100
+ */
101
+ export interface PlatformESBuildConfig {
102
+ /** Target platform: "node" or "browser" */
103
+ platform?: "node" | "browser" | "neutral";
104
+ /** Export conditions for package.json resolution */
105
+ conditions?: string[];
106
+ /** External modules to exclude from bundle */
107
+ external?: string[];
108
+ /** Compile-time defines */
109
+ define?: Record<string, string>;
110
+ /**
111
+ * Whether the entry wrapper imports user code inline (bundled together)
112
+ * or references it as a separate file (loaded at runtime).
113
+ *
114
+ * - true: User code is imported inline (e.g., Cloudflare: `import "user-entry"`)
115
+ * - false: User code is loaded separately (e.g., Node/Bun: `loadServiceWorker("./server.js")`)
116
+ *
117
+ * Default: false (separate build)
118
+ */
119
+ bundlesUserCodeInline?: boolean;
120
+ }
121
+ /**
122
+ * Default resource configuration for a named resource (cache, directory, etc.)
123
+ * Used by platforms to define built-in defaults that get merged with user config.
124
+ */
125
+ export interface ResourceDefault {
126
+ /** Module path to import (e.g., "@b9g/cache/memory") */
127
+ module: string;
128
+ /** Named export to use (defaults to "default") */
129
+ export?: string;
130
+ /** Additional options (e.g., path for directories) */
131
+ [key: string]: unknown;
132
+ }
133
+ /**
134
+ * Platform-specific defaults for config generation.
135
+ * These are merged with user config at build time to provide
136
+ * sensible defaults for each platform.
137
+ */
138
+ export interface PlatformDefaults {
139
+ /** Default directory configurations (server, public, tmp, etc.) */
140
+ directories?: Record<string, ResourceDefault>;
141
+ /** Default cache configuration (e.g., memory cache) */
142
+ caches?: Record<string, ResourceDefault>;
143
+ }
79
144
  /**
80
145
  * Platform interface - ServiceWorker entrypoint loader for JavaScript runtimes
81
146
  *
@@ -92,14 +157,54 @@ export interface Platform {
92
157
  */
93
158
  loadServiceWorker(entrypoint: string, options?: ServiceWorkerOptions): Promise<ServiceWorkerInstance>;
94
159
  /**
95
- * SUPPORTING UTILITY - Create cache storage
96
- * Returns empty CacheStorage - applications create caches on-demand via caches.open()
160
+ * SUPPORTING UTILITY - Create server instance for this platform
161
+ */
162
+ createServer(handler: Handler, options?: ServerOptions): Server;
163
+ /**
164
+ * BUILD SUPPORT - Get virtual entry wrapper template for user code
165
+ *
166
+ * Returns a JavaScript/TypeScript string that:
167
+ * 1. Initializes platform-specific runtime (polyfills, globals)
168
+ * 2. Imports the user's entrypoint
169
+ * 3. Exports any required handlers (e.g., ES module export for Cloudflare)
170
+ *
171
+ * The CLI uses this to create a virtual entry point for bundling.
172
+ * Every platform must provide a wrapper - there is no "raw user code" mode.
173
+ *
174
+ * @param entryPath - Absolute path to user's entrypoint file
175
+ * @param options - Additional options
176
+ */
177
+ getEntryWrapper(entryPath: string, options?: EntryWrapperOptions): string;
178
+ /**
179
+ * BUILD SUPPORT - Get platform-specific esbuild configuration
180
+ *
181
+ * Returns partial esbuild config that the CLI merges with common settings.
182
+ * Includes platform target, conditions, externals, and defines.
183
+ */
184
+ getESBuildConfig(): PlatformESBuildConfig;
185
+ /**
186
+ * BUILD SUPPORT - Get platform-specific defaults for config generation
187
+ *
188
+ * Returns defaults for directories, caches, etc. that get merged with
189
+ * user config at build time. These are used by generateConfigModule()
190
+ * to create static imports for the default implementations.
191
+ */
192
+ getDefaults(): PlatformDefaults;
193
+ /**
194
+ * Create cache storage for this platform
195
+ * Uses platform-specific defaults, overridable via shovel.json config
97
196
  */
98
197
  createCaches(): Promise<CacheStorage>;
99
198
  /**
100
- * SUPPORTING UTILITY - Create server instance for this platform
199
+ * Create directory storage for this platform
200
+ * Uses platform-specific defaults, overridable via shovel.json config
101
201
  */
102
- createServer(handler: Handler, options?: ServerOptions): Server;
202
+ createDirectories(): Promise<DirectoryStorage>;
203
+ /**
204
+ * Create logger storage for this platform
205
+ * Uses platform-specific defaults, overridable via shovel.json config
206
+ */
207
+ createLoggers(): Promise<LoggerStorage>;
103
208
  }
104
209
  /**
105
210
  * Platform registry - internal implementation
@@ -126,11 +231,7 @@ export declare function detectRuntime(): "bun" | "deno" | "node";
126
231
  */
127
232
  export declare function detectDeploymentPlatform(): string | null;
128
233
  /**
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)
234
+ * Detect platform for development based on current runtime
134
235
  */
135
236
  export declare function detectDevelopmentPlatform(): string;
136
237
  /**
@@ -164,10 +265,35 @@ export declare abstract class BasePlatform implements Platform {
164
265
  abstract loadServiceWorker(entrypoint: string, options?: any): Promise<any>;
165
266
  abstract createServer(handler: any, options?: any): any;
166
267
  /**
167
- * Create cache storage
168
- * Returns empty CacheStorage - applications create caches on-demand via caches.open()
268
+ * Get virtual entry wrapper template for user code
269
+ * Subclasses must override to provide platform-specific wrappers
169
270
  */
170
- createCaches(): Promise<CacheStorage>;
271
+ abstract getEntryWrapper(entryPath: string, options?: EntryWrapperOptions): string;
272
+ /**
273
+ * Get platform-specific esbuild configuration
274
+ * Subclasses should override to provide platform-specific config
275
+ */
276
+ abstract getESBuildConfig(): PlatformESBuildConfig;
277
+ /**
278
+ * Get platform-specific defaults for config generation
279
+ * Subclasses should override to provide platform-specific defaults
280
+ */
281
+ abstract getDefaults(): PlatformDefaults;
282
+ /**
283
+ * Create cache storage for this platform
284
+ * Subclasses must override to provide platform-specific implementation
285
+ */
286
+ abstract createCaches(): Promise<CacheStorage>;
287
+ /**
288
+ * Create directory storage for this platform
289
+ * Subclasses must override to provide platform-specific implementation
290
+ */
291
+ abstract createDirectories(): Promise<DirectoryStorage>;
292
+ /**
293
+ * Create logger storage for this platform
294
+ * Subclasses must override to provide platform-specific implementation
295
+ */
296
+ abstract createLoggers(): Promise<LoggerStorage>;
171
297
  }
172
298
  /**
173
299
  * Global platform registry
@@ -191,9 +317,152 @@ export declare function getPlatform(name?: string): Platform;
191
317
  * Get platform with async auto-registration fallback
192
318
  */
193
319
  export declare function getPlatformAsync(name?: string): Promise<Platform>;
194
- export { ServiceWorkerPool, type WorkerPoolOptions, type WorkerMessage, type WorkerRequest, type WorkerResponse, type WorkerLoadMessage, type WorkerReadyMessage, type WorkerErrorMessage, type WorkerInitMessage, type WorkerInitializedMessage, } from "./worker-pool.js";
195
- export { SingleThreadedRuntime, type SingleThreadedRuntimeOptions, } from "./single-threaded.js";
196
- export { ShovelServiceWorkerRegistration, ServiceWorkerGlobals, FetchEvent, InstallEvent, ActivateEvent, ExtendableEvent, } from "./runtime.js";
197
- export { RequestCookieStore, type CookieListItem, type CookieInit, type CookieStoreGetOptions, type CookieStoreDeleteOptions, type CookieSameSite, type CookieList, parseCookieHeader, serializeCookie, parseSetCookieHeader, } from "./cookie-store.js";
198
- export { CustomBucketStorage } from "@b9g/filesystem";
199
- export { loadConfig, configureLogging, getCacheConfig, getBucketConfig, parseConfigExpr, processConfigValue, matchPattern, createBucketFactory, createCacheFactory, type ShovelConfig, type CacheConfig, type BucketConfig, type LoggingConfig, type LogLevel, type BucketFactoryOptions, type CacheFactoryOptions, type ProcessedShovelConfig, } from "./config.js";
320
+ /**
321
+ * Common interface for ServiceWorker runtimes
322
+ */
323
+ export interface ServiceWorkerRuntime {
324
+ init(): Promise<void>;
325
+ load(entrypoint: string): Promise<void>;
326
+ handleRequest(request: Request): Promise<Response>;
327
+ terminate(): Promise<void>;
328
+ readonly workerCount: number;
329
+ readonly ready: boolean;
330
+ }
331
+ export interface SingleThreadedRuntimeOptions {
332
+ /** Cache storage for the runtime */
333
+ caches: CacheStorage;
334
+ /** Directory storage for the runtime */
335
+ directories: DirectoryStorage;
336
+ /** Database storage for the runtime */
337
+ databases?: DatabaseStorage;
338
+ /** Logger storage for the runtime */
339
+ loggers: LoggerStorage;
340
+ }
341
+ /**
342
+ * Single-threaded ServiceWorker runtime
343
+ *
344
+ * Runs ServiceWorker code directly in the main thread.
345
+ * Implements ServiceWorkerRuntime interface for interchangeability with ServiceWorkerPool.
346
+ */
347
+ export declare class SingleThreadedRuntime implements ServiceWorkerRuntime {
348
+ #private;
349
+ constructor(options: SingleThreadedRuntimeOptions);
350
+ /**
351
+ * Initialize the runtime (install ServiceWorker globals)
352
+ */
353
+ init(): Promise<void>;
354
+ /**
355
+ * Load (or reload) a ServiceWorker entrypoint
356
+ * @param entrypoint - Path to the entrypoint file (content-hashed filename)
357
+ */
358
+ load(entrypoint: string): Promise<void>;
359
+ /**
360
+ * Handle an HTTP request
361
+ * This is the key method - direct call, no postMessage!
362
+ */
363
+ handleRequest(request: Request): Promise<Response>;
364
+ /**
365
+ * Graceful shutdown
366
+ */
367
+ terminate(): Promise<void>;
368
+ /**
369
+ * Get the number of workers (always 1 for single-threaded)
370
+ */
371
+ get workerCount(): number;
372
+ /**
373
+ * Check if ready to handle requests
374
+ */
375
+ get ready(): boolean;
376
+ }
377
+ /**
378
+ * Worker pool options
379
+ */
380
+ export interface WorkerPoolOptions {
381
+ /** Number of workers in the pool (default: 1) */
382
+ workerCount?: number;
383
+ /** Request timeout in milliseconds (default: 30000) */
384
+ requestTimeout?: number;
385
+ /** Working directory for file resolution */
386
+ cwd?: string;
387
+ }
388
+ export interface WorkerMessage {
389
+ type: string;
390
+ [key: string]: any;
391
+ }
392
+ export interface WorkerRequest extends WorkerMessage {
393
+ type: "request";
394
+ request: {
395
+ url: string;
396
+ method: string;
397
+ headers: Record<string, string>;
398
+ body?: ArrayBuffer | null;
399
+ };
400
+ requestID: number;
401
+ }
402
+ export interface WorkerResponse extends WorkerMessage {
403
+ type: "response";
404
+ response: {
405
+ status: number;
406
+ statusText: string;
407
+ headers: Record<string, string>;
408
+ body: ArrayBuffer;
409
+ };
410
+ requestID: number;
411
+ }
412
+ export interface WorkerReadyMessage extends WorkerMessage {
413
+ type: "ready";
414
+ }
415
+ export interface WorkerErrorMessage extends WorkerMessage {
416
+ type: "error";
417
+ error: string;
418
+ stack?: string;
419
+ requestID?: number;
420
+ }
421
+ /**
422
+ * ServiceWorkerPool - manages a pool of ServiceWorker instances
423
+ *
424
+ * With the unified build model, workers are self-contained bundles that:
425
+ * 1. Initialize their own runtime (via initWorkerRuntime)
426
+ * 2. Import user code
427
+ * 3. Run lifecycle events
428
+ * 4. Start message loop (via startWorkerMessageLoop)
429
+ *
430
+ * Hot reload is achieved by terminating old workers and creating new ones
431
+ * with the new bundle path.
432
+ */
433
+ export declare class ServiceWorkerPool {
434
+ #private;
435
+ constructor(options: WorkerPoolOptions, appEntrypoint: string, cacheStorage?: CacheStorage);
436
+ /**
437
+ * Initialize workers (must be called after construction)
438
+ */
439
+ init(): Promise<void>;
440
+ /**
441
+ * Handle HTTP request using round-robin worker selection
442
+ */
443
+ handleRequest(request: Request): Promise<Response>;
444
+ /**
445
+ * Reload workers with new entrypoint (hot reload)
446
+ *
447
+ * With unified builds, hot reload means:
448
+ * 1. Gracefully shutdown existing workers (close databases, etc.)
449
+ * 2. Terminate workers after resources are closed
450
+ * 3. Create new workers with the new bundle
451
+ */
452
+ reloadWorkers(entrypoint: string): Promise<void>;
453
+ /**
454
+ * Graceful shutdown of all workers
455
+ */
456
+ terminate(): Promise<void>;
457
+ /**
458
+ * Get the number of active workers
459
+ */
460
+ get workerCount(): number;
461
+ /**
462
+ * Check if the pool is ready to handle requests
463
+ */
464
+ get ready(): boolean;
465
+ }
466
+ export { CustomLoggerStorage, type LoggerStorage };
467
+ export type { LoggerFactory } from "./runtime.js";
468
+ export { CustomDatabaseStorage, createDatabaseFactory, type DatabaseStorage, type DatabaseConfig, type DatabaseFactory, type DatabaseUpgradeEvent, } from "./runtime.js";