@b9g/platform 0.1.4 → 0.1.6

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,245 +0,0 @@
1
- /// <reference types="./service-worker.d.ts" />
2
- // src/service-worker.ts
3
- var ExtendableEvent = class extends Event {
4
- promises = [];
5
- pendingPromises;
6
- constructor(type, pendingPromises) {
7
- super(type);
8
- this.pendingPromises = pendingPromises;
9
- }
10
- waitUntil(promise) {
11
- this.promises.push(promise);
12
- this.pendingPromises.add(promise);
13
- promise.finally(() => this.pendingPromises.delete(promise));
14
- }
15
- getPromises() {
16
- return [...this.promises];
17
- }
18
- };
19
- var FetchEvent = class extends ExtendableEvent {
20
- request;
21
- responsePromise = null;
22
- responded = false;
23
- constructor(request, pendingPromises) {
24
- super("fetch", pendingPromises);
25
- this.request = request;
26
- }
27
- respondWith(response) {
28
- if (this.responded) {
29
- throw new Error("respondWith() already called");
30
- }
31
- this.responded = true;
32
- this.responsePromise = Promise.resolve(response);
33
- }
34
- getResponse() {
35
- return this.responsePromise;
36
- }
37
- hasResponded() {
38
- return this.responded;
39
- }
40
- };
41
- var InstallEvent = class extends ExtendableEvent {
42
- constructor(pendingPromises) {
43
- super("install", pendingPromises);
44
- }
45
- };
46
- var ActivateEvent = class extends ExtendableEvent {
47
- constructor(pendingPromises) {
48
- super("activate", pendingPromises);
49
- }
50
- };
51
- var ServiceWorkerRuntime = class extends EventTarget {
52
- pendingPromises = /* @__PURE__ */ new Set();
53
- isInstalled = false;
54
- isActivated = false;
55
- eventListeners = /* @__PURE__ */ new Map();
56
- constructor() {
57
- super();
58
- }
59
- addEventListener(type, listener) {
60
- super.addEventListener(type, listener);
61
- if (!this.eventListeners.has(type)) {
62
- this.eventListeners.set(type, []);
63
- }
64
- this.eventListeners.get(type).push(listener);
65
- }
66
- removeEventListener(type, listener) {
67
- super.removeEventListener(type, listener);
68
- if (this.eventListeners.has(type)) {
69
- const listeners = this.eventListeners.get(type);
70
- const index = listeners.indexOf(listener);
71
- if (index > -1) {
72
- listeners.splice(index, 1);
73
- if (listeners.length === 0) {
74
- this.eventListeners.delete(type);
75
- }
76
- }
77
- }
78
- }
79
- /**
80
- * Create a fetch event and dispatch it
81
- */
82
- async handleRequest(request) {
83
- if (!this.isActivated) {
84
- throw new Error("ServiceWorker not activated");
85
- }
86
- return new Promise((resolve, reject) => {
87
- const event = new FetchEvent(request, this.pendingPromises);
88
- process.nextTick(() => {
89
- this.dispatchEvent(event);
90
- const promises = event.getPromises();
91
- if (promises.length > 0) {
92
- Promise.allSettled(promises).catch(console.error);
93
- }
94
- });
95
- setTimeout(() => {
96
- if (event.hasResponded()) {
97
- const responsePromise = event.getResponse();
98
- responsePromise.then(resolve).catch(reject);
99
- } else {
100
- reject(new Error("No response provided for fetch event"));
101
- }
102
- }, 0);
103
- });
104
- }
105
- /**
106
- * Install the ServiceWorker
107
- */
108
- async install() {
109
- if (this.isInstalled)
110
- return;
111
- return new Promise((resolve, reject) => {
112
- const event = new InstallEvent(this.pendingPromises);
113
- process.nextTick(() => {
114
- this.dispatchEvent(event);
115
- const promises = event.getPromises();
116
- if (promises.length === 0) {
117
- this.isInstalled = true;
118
- resolve();
119
- } else {
120
- Promise.all(promises).then(() => {
121
- this.isInstalled = true;
122
- resolve();
123
- }).catch(reject);
124
- }
125
- });
126
- });
127
- }
128
- /**
129
- * Activate the ServiceWorker
130
- */
131
- async activate() {
132
- if (!this.isInstalled) {
133
- throw new Error("ServiceWorker must be installed before activation");
134
- }
135
- if (this.isActivated)
136
- return;
137
- return new Promise((resolve, reject) => {
138
- const event = new ActivateEvent(this.pendingPromises);
139
- process.nextTick(() => {
140
- this.dispatchEvent(event);
141
- const promises = event.getPromises();
142
- if (promises.length === 0) {
143
- this.isActivated = true;
144
- resolve();
145
- } else {
146
- Promise.all(promises).then(() => {
147
- this.isActivated = true;
148
- resolve();
149
- }).catch(reject);
150
- }
151
- });
152
- });
153
- }
154
- /**
155
- * Check if ready to handle requests
156
- */
157
- get ready() {
158
- return this.isInstalled && this.isActivated;
159
- }
160
- /**
161
- * Wait for all pending promises to resolve
162
- */
163
- async waitForPending() {
164
- if (this.pendingPromises.size > 0) {
165
- await Promise.allSettled([...this.pendingPromises]);
166
- }
167
- }
168
- /**
169
- * Reset the ServiceWorker state (for hot reloading)
170
- */
171
- reset() {
172
- this.isInstalled = false;
173
- this.isActivated = false;
174
- this.pendingPromises.clear();
175
- for (const [type, listeners] of this.eventListeners) {
176
- for (const listener of listeners) {
177
- super.removeEventListener(type, listener);
178
- }
179
- }
180
- this.eventListeners.clear();
181
- }
182
- };
183
- function createServiceWorkerGlobals(runtime, options = {}) {
184
- if (options.caches) {
185
- runtime.caches = options.caches;
186
- }
187
- if (options.buckets) {
188
- runtime.buckets = options.buckets;
189
- }
190
- const skipWaiting = async () => {
191
- if (options.isDevelopment && options.hotReload) {
192
- console.info("[ServiceWorker] skipWaiting() - triggering hot reload");
193
- await options.hotReload();
194
- } else if (!options.isDevelopment) {
195
- console.info("[ServiceWorker] skipWaiting() - production graceful restart not implemented");
196
- }
197
- };
198
- const clients = {
199
- async claim() {
200
- },
201
- async get(id) {
202
- return void 0;
203
- },
204
- async matchAll(options2) {
205
- return [];
206
- },
207
- async openWindow(url) {
208
- return null;
209
- }
210
- };
211
- const globals = {
212
- self: runtime,
213
- addEventListener: runtime.addEventListener.bind(runtime),
214
- removeEventListener: runtime.removeEventListener.bind(runtime),
215
- dispatchEvent: runtime.dispatchEvent.bind(runtime),
216
- // ServiceWorker-specific globals with proper implementations
217
- skipWaiting,
218
- clients,
219
- // Platform resources
220
- ...options.buckets && { buckets: options.buckets },
221
- ...options.caches && { caches: options.caches },
222
- // Standard globals
223
- console,
224
- setTimeout,
225
- clearTimeout,
226
- setInterval,
227
- clearInterval,
228
- fetch,
229
- Request,
230
- Response,
231
- Headers,
232
- URL,
233
- URLSearchParams
234
- };
235
- Object.assign(globalThis, globals);
236
- return globals;
237
- }
238
- export {
239
- ActivateEvent,
240
- ExtendableEvent,
241
- FetchEvent,
242
- InstallEvent,
243
- ServiceWorkerRuntime,
244
- createServiceWorkerGlobals
245
- };
package/src/types.d.ts DELETED
@@ -1,246 +0,0 @@
1
- /**
2
- * Core Platform interface and configuration types for Shovel deployment adapters
3
- */
4
- import type { CacheStorage } from "@b9g/cache/cache-storage";
5
- export { loadCacheAdapter, loadFilesystemAdapter, resolveCacheAdapter, resolveFilesystemAdapter, CACHE_ALIASES, FILESYSTEM_ALIASES, } from "./adapter-registry.js";
6
- export { BasePlatform } from "./base-platform.js";
7
- export { detectRuntime, detectDevelopmentPlatform, detectPlatforms, getBestPlatformDetection, resolvePlatform, createPlatform, displayPlatformInfo, } from "./detection.js";
8
- /**
9
- * Cache backend configuration
10
- * Type can be a blessed alias or full package name
11
- */
12
- export interface CacheBackendConfig {
13
- /** Cache backend type - blessed alias (memory, redis, kv) or package name (@custom/cache) */
14
- type?: string;
15
- /** Maximum number of entries (for memory/LRU caches) */
16
- maxEntries?: number;
17
- /** Time-to-live in milliseconds or string format (e.g., '5m', '1h') */
18
- ttl?: number | string;
19
- /** Directory for filesystem cache */
20
- dir?: string;
21
- /** Redis connection string */
22
- url?: string;
23
- /** Custom cache factory function */
24
- factory?: () => any;
25
- }
26
- /**
27
- * Cache configuration for different cache types
28
- */
29
- export interface CacheConfig {
30
- /** Page/HTML cache configuration */
31
- pages?: CacheBackendConfig;
32
- /** API response cache configuration */
33
- api?: CacheBackendConfig;
34
- /** Static file cache configuration */
35
- static?: CacheBackendConfig;
36
- /** Custom named caches */
37
- [name: string]: CacheBackendConfig | undefined;
38
- }
39
- /**
40
- * Static file serving configuration
41
- */
42
- export interface StaticConfig {
43
- /** Public URL path prefix */
44
- publicPath?: string;
45
- /** Output directory for built assets */
46
- outputDir?: string;
47
- /** Asset manifest file path */
48
- manifest?: string;
49
- /** Development mode (serve from source) */
50
- dev?: boolean;
51
- /** Source directory for development */
52
- sourceDir?: string;
53
- /** Cache configuration for static files */
54
- cache?: {
55
- name?: string;
56
- ttl?: string | number;
57
- };
58
- }
59
- /**
60
- * CORS configuration
61
- */
62
- export interface CorsConfig {
63
- /** Allowed origins */
64
- origin?: boolean | string | string[] | RegExp | ((origin: string) => boolean);
65
- /** Allowed methods */
66
- methods?: string[];
67
- /** Allowed headers */
68
- allowedHeaders?: string[];
69
- /** Exposed headers */
70
- exposedHeaders?: string[];
71
- /** Allow credentials */
72
- credentials?: boolean;
73
- /** Preflight cache duration */
74
- maxAge?: number;
75
- }
76
- /**
77
- * Filesystem adapter configuration
78
- */
79
- export interface FilesystemConfig {
80
- /** Filesystem adapter type - blessed alias (memory, s3, r2) or package name (@custom/fs) */
81
- type?: string;
82
- /** Region for cloud storage */
83
- region?: string;
84
- /** Access credentials */
85
- credentials?: {
86
- accessKeyId?: string;
87
- secretAccessKey?: string;
88
- token?: string;
89
- };
90
- /** Factory function for creating directory storage */
91
- factory?: any;
92
- /** Additional adapter-specific options */
93
- [key: string]: any;
94
- }
95
- /**
96
- * Platform configuration from CLI flags
97
- */
98
- export interface PlatformConfig {
99
- /** Cache configuration */
100
- caches?: CacheConfig;
101
- /** Filesystem adapter configuration */
102
- filesystem?: FilesystemConfig;
103
- }
104
- /**
105
- * Server options for platform implementations
106
- */
107
- export interface ServerOptions {
108
- /** Port to listen on */
109
- port?: number;
110
- /** Host to bind to */
111
- host?: string;
112
- /** Hostname for URL generation */
113
- hostname?: string;
114
- /** Enable compression */
115
- compression?: boolean;
116
- /** CORS configuration */
117
- cors?: CorsConfig;
118
- /** Custom headers to add to all responses */
119
- headers?: Record<string, string>;
120
- /** Request timeout in milliseconds */
121
- timeout?: number;
122
- /** Development mode settings */
123
- development?: {
124
- /** Enable hot reloading */
125
- hotReload?: boolean;
126
- /** Source maps support */
127
- sourceMaps?: boolean;
128
- /** Verbose logging */
129
- verbose?: boolean;
130
- };
131
- }
132
- /**
133
- * Request handler function (Web Fetch API compatible)
134
- */
135
- export type Handler = (request: Request, context?: any) => Promise<Response> | Response;
136
- /**
137
- * ServiceWorker entrypoint options
138
- */
139
- export interface ServiceWorkerOptions {
140
- /** Enable hot reloading */
141
- hotReload?: boolean;
142
- /** Cache configuration to pass to platform event */
143
- caches?: CacheConfig;
144
- /** Additional context to provide */
145
- context?: any;
146
- }
147
- /**
148
- * ServiceWorker instance returned by platform
149
- */
150
- export interface ServiceWorkerInstance {
151
- /** The ServiceWorker runtime */
152
- runtime: any;
153
- /** Handle HTTP request */
154
- handleRequest(request: Request): Promise<Response>;
155
- /** Install the ServiceWorker */
156
- install(): Promise<void>;
157
- /** Activate the ServiceWorker */
158
- activate(): Promise<void>;
159
- /** Collect routes for static generation */
160
- collectStaticRoutes(outDir: string, baseUrl?: string): Promise<string[]>;
161
- /** Check if ready to handle requests */
162
- readonly ready: boolean;
163
- /** Dispose of resources */
164
- dispose(): Promise<void>;
165
- }
166
- /**
167
- * Server instance returned by platform.createServer()
168
- */
169
- export interface Server {
170
- /** Start listening for requests */
171
- listen(): Promise<void>;
172
- /** Stop the server */
173
- close(): Promise<void>;
174
- /** Get server address information */
175
- address(): {
176
- port: number;
177
- host: string;
178
- };
179
- /** Get server URL */
180
- readonly url: string;
181
- /** Whether server is ready to accept requests */
182
- readonly ready: boolean;
183
- }
184
- /**
185
- * Platform interface - ServiceWorker entrypoint loader for JavaScript runtimes
186
- *
187
- * The core responsibility: "Take a ServiceWorker-style app file and make it run in this environment"
188
- */
189
- export interface Platform {
190
- /**
191
- * Platform name for identification
192
- */
193
- readonly name: string;
194
- /**
195
- * THE MAIN JOB - Load and run a ServiceWorker-style entrypoint
196
- * This is where all the platform-specific complexity lives
197
- */
198
- loadServiceWorker(entrypoint: string, options?: ServiceWorkerOptions): Promise<ServiceWorkerInstance>;
199
- /**
200
- * SUPPORTING UTILITY - Create cache storage with platform-optimized backends
201
- * Automatically selects optimal cache types when not specified:
202
- * - Node.js: filesystem for persistence, memory for API
203
- * - Cloudflare: KV for persistence, memory for fast access
204
- * - Bun: filesystem with optimized writes
205
- */
206
- createCaches(config?: CacheConfig): Promise<CacheStorage>;
207
- /**
208
- * SUPPORTING UTILITY - Create bucket storage with platform-optimized backends
209
- * Uses factory pattern to route bucket names to different filesystem adapters
210
- */
211
- createBuckets(config?: FilesystemConfig): Promise<any>;
212
- /**
213
- * SUPPORTING UTILITY - Create server instance for this platform
214
- */
215
- createServer(handler: Handler, options?: ServerOptions): Server;
216
- /**
217
- * SUPPORTING UTILITY - Get filesystem directory handle
218
- * Maps directly to cloud storage buckets (S3, R2) or local directories
219
- * @param name - Directory name. Use "" for root directory
220
- */
221
- getDirectoryHandle(name: string): Promise<FileSystemDirectoryHandle>;
222
- }
223
- /**
224
- * Platform detection result
225
- */
226
- export interface PlatformDetection {
227
- /** Detected platform name */
228
- platform: string;
229
- /** Confidence level (0-1) */
230
- confidence: number;
231
- /** Detection reasons */
232
- reasons: string[];
233
- }
234
- /**
235
- * Platform registry for auto-detection
236
- */
237
- export interface PlatformRegistry {
238
- /** Register a platform implementation */
239
- register(name: string, platform: Platform): void;
240
- /** Get platform by name */
241
- get(name: string): Platform | undefined;
242
- /** Detect current platform */
243
- detect(): PlatformDetection;
244
- /** Get all registered platforms */
245
- list(): string[];
246
- }
package/src/types.js DELETED
@@ -1,36 +0,0 @@
1
- /// <reference types="./types.d.ts" />
2
- // src/types.ts
3
- import {
4
- loadCacheAdapter,
5
- loadFilesystemAdapter,
6
- resolveCacheAdapter,
7
- resolveFilesystemAdapter,
8
- CACHE_ALIASES,
9
- FILESYSTEM_ALIASES
10
- } from "./adapter-registry.js";
11
- import { BasePlatform } from "./base-platform.js";
12
- import {
13
- detectRuntime,
14
- detectDevelopmentPlatform,
15
- detectPlatforms,
16
- getBestPlatformDetection,
17
- resolvePlatform,
18
- createPlatform,
19
- displayPlatformInfo
20
- } from "./detection.js";
21
- export {
22
- BasePlatform,
23
- CACHE_ALIASES,
24
- FILESYSTEM_ALIASES,
25
- createPlatform,
26
- detectDevelopmentPlatform,
27
- detectPlatforms,
28
- detectRuntime,
29
- displayPlatformInfo,
30
- getBestPlatformDetection,
31
- loadCacheAdapter,
32
- loadFilesystemAdapter,
33
- resolveCacheAdapter,
34
- resolveFilesystemAdapter,
35
- resolvePlatform
36
- };
package/src/utils.d.ts DELETED
@@ -1,33 +0,0 @@
1
- /**
2
- * Utility functions for platform implementations
3
- */
4
- import type { CacheBackendConfig } from "./types.js";
5
- /**
6
- * Parse TTL string to milliseconds
7
- */
8
- export declare function parseTTL(ttl: string | number | undefined): number | undefined;
9
- /**
10
- * Merge cache configurations with defaults
11
- */
12
- export declare function mergeCacheConfig(userConfig: CacheBackendConfig | undefined, defaults: Partial<CacheBackendConfig>): CacheBackendConfig;
13
- /**
14
- * Validate cache backend configuration
15
- */
16
- export declare function validateCacheConfig(config: CacheBackendConfig): void;
17
- /**
18
- * Create CORS headers from configuration
19
- */
20
- export declare function createCorsHeaders(corsConfig: any, // CorsConfig but avoiding circular import
21
- request: Request): Headers;
22
- /**
23
- * Merge headers from multiple sources
24
- */
25
- export declare function mergeHeaders(...headerSources: (Headers | Record<string, string> | undefined)[]): Headers;
26
- /**
27
- * Check if request is a preflight CORS request
28
- */
29
- export declare function isPreflightRequest(request: Request): boolean;
30
- /**
31
- * Create a preflight response
32
- */
33
- export declare function createPreflightResponse(corsConfig: any, request: Request): Response;
package/src/utils.js DELETED
@@ -1,139 +0,0 @@
1
- /// <reference types="./utils.d.ts" />
2
- // src/utils.ts
3
- function parseTTL(ttl) {
4
- if (typeof ttl === "number") {
5
- return ttl;
6
- }
7
- if (typeof ttl !== "string") {
8
- return void 0;
9
- }
10
- const match = ttl.match(/^(\d+)\s*(ms|s|m|h|d)?$/);
11
- if (!match) {
12
- throw new Error(
13
- `Invalid TTL format: ${ttl}. Use format like '5m', '1h', '30s'`
14
- );
15
- }
16
- const value = parseInt(match[1], 10);
17
- const unit = match[2] || "ms";
18
- const multipliers = {
19
- ms: 1,
20
- s: 1e3,
21
- m: 60 * 1e3,
22
- h: 60 * 60 * 1e3,
23
- d: 24 * 60 * 60 * 1e3
24
- };
25
- return value * multipliers[unit];
26
- }
27
- function mergeCacheConfig(userConfig, defaults) {
28
- return {
29
- type: "memory",
30
- ...defaults,
31
- ...userConfig
32
- };
33
- }
34
- function validateCacheConfig(config) {
35
- const validTypes = ["memory", "filesystem", "redis", "kv", "custom"];
36
- if (!validTypes.includes(config.type)) {
37
- throw new Error(
38
- `Invalid cache type '${config.type}'. Must be one of: ${validTypes.join(", ")}`
39
- );
40
- }
41
- if (config.type === "filesystem" && !config.dir) {
42
- throw new Error("Filesystem cache requires a directory (dir) option");
43
- }
44
- if (config.type === "redis" && !config.url) {
45
- throw new Error("Redis cache requires a connection URL (url) option");
46
- }
47
- if (config.type === "custom" && !config.factory) {
48
- throw new Error("Custom cache requires a factory function");
49
- }
50
- if (config.maxEntries !== void 0 && config.maxEntries <= 0) {
51
- throw new Error("maxEntries must be a positive number");
52
- }
53
- if (config.ttl !== void 0) {
54
- try {
55
- parseTTL(config.ttl);
56
- } catch (error) {
57
- throw new Error(`Invalid TTL configuration: ${error.message}`);
58
- }
59
- }
60
- }
61
- function createCorsHeaders(corsConfig, request) {
62
- const headers = new Headers();
63
- if (!corsConfig) {
64
- return headers;
65
- }
66
- const origin = request.headers.get("origin");
67
- if (corsConfig.origin === true) {
68
- headers.set("Access-Control-Allow-Origin", "*");
69
- } else if (typeof corsConfig.origin === "string") {
70
- headers.set("Access-Control-Allow-Origin", corsConfig.origin);
71
- } else if (Array.isArray(corsConfig.origin)) {
72
- if (origin && corsConfig.origin.includes(origin)) {
73
- headers.set("Access-Control-Allow-Origin", origin);
74
- }
75
- } else if (corsConfig.origin instanceof RegExp) {
76
- if (origin && corsConfig.origin.test(origin)) {
77
- headers.set("Access-Control-Allow-Origin", origin);
78
- }
79
- } else if (typeof corsConfig.origin === "function") {
80
- if (origin && corsConfig.origin(origin)) {
81
- headers.set("Access-Control-Allow-Origin", origin);
82
- }
83
- }
84
- if (corsConfig.methods) {
85
- headers.set("Access-Control-Allow-Methods", corsConfig.methods.join(", "));
86
- }
87
- if (corsConfig.allowedHeaders) {
88
- headers.set(
89
- "Access-Control-Allow-Headers",
90
- corsConfig.allowedHeaders.join(", ")
91
- );
92
- }
93
- if (corsConfig.exposedHeaders) {
94
- headers.set(
95
- "Access-Control-Expose-Headers",
96
- corsConfig.exposedHeaders.join(", ")
97
- );
98
- }
99
- if (corsConfig.credentials) {
100
- headers.set("Access-Control-Allow-Credentials", "true");
101
- }
102
- if (corsConfig.maxAge !== void 0) {
103
- headers.set("Access-Control-Max-Age", corsConfig.maxAge.toString());
104
- }
105
- return headers;
106
- }
107
- function mergeHeaders(...headerSources) {
108
- const result = new Headers();
109
- for (const source of headerSources) {
110
- if (!source)
111
- continue;
112
- if (source instanceof Headers) {
113
- for (const [key, value] of source.entries()) {
114
- result.set(key, value);
115
- }
116
- } else {
117
- for (const [key, value] of Object.entries(source)) {
118
- result.set(key, value);
119
- }
120
- }
121
- }
122
- return result;
123
- }
124
- function isPreflightRequest(request) {
125
- return request.method === "OPTIONS" && request.headers.has("access-control-request-method");
126
- }
127
- function createPreflightResponse(corsConfig, request) {
128
- const headers = createCorsHeaders(corsConfig, request);
129
- return new Response(null, { status: 204, headers });
130
- }
131
- export {
132
- createCorsHeaders,
133
- createPreflightResponse,
134
- isPreflightRequest,
135
- mergeCacheConfig,
136
- mergeHeaders,
137
- parseTTL,
138
- validateCacheConfig
139
- };