@b9g/platform 0.1.0

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/src/types.d.ts ADDED
@@ -0,0 +1,239 @@
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
+ /** Additional adapter-specific options */
91
+ [key: string]: any;
92
+ }
93
+ /**
94
+ * Platform configuration from CLI flags
95
+ */
96
+ export interface PlatformConfig {
97
+ /** Cache configuration */
98
+ caches?: CacheConfig;
99
+ /** Filesystem adapter configuration */
100
+ filesystem?: FilesystemConfig;
101
+ }
102
+ /**
103
+ * Server options for platform implementations
104
+ */
105
+ export interface ServerOptions {
106
+ /** Port to listen on */
107
+ port?: number;
108
+ /** Host to bind to */
109
+ host?: string;
110
+ /** Hostname for URL generation */
111
+ hostname?: string;
112
+ /** Enable compression */
113
+ compression?: boolean;
114
+ /** CORS configuration */
115
+ cors?: CorsConfig;
116
+ /** Custom headers to add to all responses */
117
+ headers?: Record<string, string>;
118
+ /** Request timeout in milliseconds */
119
+ timeout?: number;
120
+ /** Development mode settings */
121
+ development?: {
122
+ /** Enable hot reloading */
123
+ hotReload?: boolean;
124
+ /** Source maps support */
125
+ sourceMaps?: boolean;
126
+ /** Verbose logging */
127
+ verbose?: boolean;
128
+ };
129
+ }
130
+ /**
131
+ * Request handler function (Web Fetch API compatible)
132
+ */
133
+ export type Handler = (request: Request, context?: any) => Promise<Response> | Response;
134
+ /**
135
+ * ServiceWorker entrypoint options
136
+ */
137
+ export interface ServiceWorkerOptions {
138
+ /** Enable hot reloading */
139
+ hotReload?: boolean;
140
+ /** Cache configuration to pass to platform event */
141
+ caches?: CacheConfig;
142
+ /** Additional context to provide */
143
+ context?: any;
144
+ }
145
+ /**
146
+ * ServiceWorker instance returned by platform
147
+ */
148
+ export interface ServiceWorkerInstance {
149
+ /** The ServiceWorker runtime */
150
+ runtime: any;
151
+ /** Handle HTTP request */
152
+ handleRequest(request: Request): Promise<Response>;
153
+ /** Install the ServiceWorker */
154
+ install(): Promise<void>;
155
+ /** Activate the ServiceWorker */
156
+ activate(): Promise<void>;
157
+ /** Collect routes for static generation */
158
+ collectStaticRoutes(outDir: string, baseUrl?: string): Promise<string[]>;
159
+ /** Check if ready to handle requests */
160
+ readonly ready: boolean;
161
+ /** Dispose of resources */
162
+ dispose(): Promise<void>;
163
+ }
164
+ /**
165
+ * Server instance returned by platform.createServer()
166
+ */
167
+ export interface Server {
168
+ /** Start listening for requests */
169
+ listen(): Promise<void> | void;
170
+ /** Stop the server */
171
+ close(): Promise<void> | void;
172
+ /** Get server URL */
173
+ url?: string;
174
+ /** Platform-specific server instance */
175
+ instance?: any;
176
+ }
177
+ /**
178
+ * Platform interface - ServiceWorker entrypoint loader for JavaScript runtimes
179
+ *
180
+ * The core responsibility: "Take a ServiceWorker-style app file and make it run in this environment"
181
+ */
182
+ export interface Platform {
183
+ /**
184
+ * Platform name for identification
185
+ */
186
+ readonly name: string;
187
+ /**
188
+ * Build artifacts filesystem (install-time only)
189
+ * Available during install handlers to copy built files to runtime storage
190
+ */
191
+ readonly distDir: FileSystemDirectoryHandle;
192
+ /**
193
+ * THE MAIN JOB - Load and run a ServiceWorker-style entrypoint
194
+ * This is where all the platform-specific complexity lives
195
+ */
196
+ loadServiceWorker(entrypoint: string, options?: ServiceWorkerOptions): Promise<ServiceWorkerInstance>;
197
+ /**
198
+ * SUPPORTING UTILITY - Create cache storage with platform-optimized backends
199
+ * Automatically selects optimal cache types when not specified:
200
+ * - Node.js: filesystem for persistence, memory for API
201
+ * - Cloudflare: KV for persistence, memory for fast access
202
+ * - Bun: filesystem with optimized writes
203
+ */
204
+ createCaches(config?: CacheConfig): Promise<CacheStorage>;
205
+ /**
206
+ * SUPPORTING UTILITY - Create server instance for this platform
207
+ */
208
+ createServer(handler: Handler, options?: ServerOptions): Server;
209
+ /**
210
+ * SUPPORTING UTILITY - Get filesystem root for bucket/container name
211
+ * Maps directly to cloud storage buckets (S3, R2) or local directories
212
+ * @param bucketName - The bucket/container/directory name
213
+ */
214
+ getFileSystemRoot(bucketName: string): Promise<FileSystemDirectoryHandle>;
215
+ }
216
+ /**
217
+ * Platform detection result
218
+ */
219
+ export interface PlatformDetection {
220
+ /** Detected platform name */
221
+ platform: string;
222
+ /** Confidence level (0-1) */
223
+ confidence: number;
224
+ /** Detection reasons */
225
+ reasons: string[];
226
+ }
227
+ /**
228
+ * Platform registry for auto-detection
229
+ */
230
+ export interface PlatformRegistry {
231
+ /** Register a platform implementation */
232
+ register(name: string, platform: Platform): void;
233
+ /** Get platform by name */
234
+ get(name: string): Platform | undefined;
235
+ /** Detect current platform */
236
+ detect(): PlatformDetection;
237
+ /** Get all registered platforms */
238
+ list(): string[];
239
+ }
package/src/types.js ADDED
@@ -0,0 +1,36 @@
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 ADDED
@@ -0,0 +1,33 @@
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 ADDED
@@ -0,0 +1,139 @@
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
+ };