spooder 5.0.1 → 5.0.2

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.
Files changed (3) hide show
  1. package/README.md +159 -3
  2. package/package.json +1 -1
  3. package/src/api.ts +28 -5
package/README.md CHANGED
@@ -526,7 +526,7 @@ pipe.off(event: string): void;
526
526
 
527
527
  // templates
528
528
  parse_template(template: string, replacements: Record<string, string>, drop_missing?: boolean): Promise<string>;
529
- generate_hash_subs(length?: number, prefix?: string, hashes?: Record<string, string>): Promise<Record<string, string>>;
529
+ generate_hash_subs(length?: number, prefix?: string, hashes?: Record<string, string>, format?: string): Promise<Record<string, string>>;
530
530
  get_git_hashes(length: number): Promise<Record<string, string>>;
531
531
 
532
532
  // database interface
@@ -1300,7 +1300,7 @@ server.bootstrap({
1300
1300
  error_page: Bun.file('./html/error.html')
1301
1301
  },
1302
1302
 
1303
- cache_bust: true,
1303
+ hash_subs: true,
1304
1304
 
1305
1305
  static: {
1306
1306
  directory: './static',
@@ -1341,6 +1341,143 @@ server.bootstrap({
1341
1341
  });
1342
1342
  ```
1343
1343
 
1344
+ #### Bootstrap Options
1345
+
1346
+ The `BootstrapOptions` object accepts the following properties:
1347
+
1348
+ ##### `base?: string | BunFile`
1349
+ Optional base template that wraps all route content. The base template should include `{{content}}` where the route content will be inserted.
1350
+
1351
+ ```ts
1352
+ // Base template: base.html
1353
+ <html>
1354
+ <head><title>{{title}}</title></head>
1355
+ <body>{{content}}</body>
1356
+ </html>
1357
+
1358
+ // Usage
1359
+ server.bootstrap({
1360
+ base: Bun.file('./templates/base.html'),
1361
+ routes: {
1362
+ '/': {
1363
+ content: '<h1>Welcome</h1>',
1364
+ subs: { title: 'Home' }
1365
+ }
1366
+ }
1367
+ });
1368
+ ```
1369
+
1370
+ ##### `routes: Record<string, BootstrapRoute>`
1371
+ **Required.** Defines the routes and their content. Each route can have:
1372
+ - `content`: The page content (string or BunFile)
1373
+ - `subs?`: Template substitutions specific to this route
1374
+
1375
+ ```ts
1376
+ routes: {
1377
+ '/about': {
1378
+ content: Bun.file('./pages/about.html'),
1379
+ subs: {
1380
+ title: 'About Us',
1381
+ description: 'Learn more about our company'
1382
+ }
1383
+ }
1384
+ }
1385
+ ```
1386
+
1387
+ ##### `cache?: CacheOptions | ReturnType<typeof cache_http>`
1388
+ Optional HTTP caching configuration. Can be:
1389
+ - A `CacheOptions` object (creates new cache instance)
1390
+ - An existing cache instance from `cache_http()`
1391
+ - Omitted to disable caching
1392
+
1393
+ ```ts
1394
+ cache: {
1395
+ ttl: 5 * 60 * 1000, // 5 minutes
1396
+ max_size: 10 * 1024 * 1024, // 10 MB
1397
+ use_etags: true,
1398
+ use_canary_reporting: true
1399
+ }
1400
+ ```
1401
+
1402
+ ##### `hash_subs?: boolean | object`
1403
+ When enabled, automatically generates git hash-based substitutions. Can be a boolean for defaults or an object for custom options.
1404
+
1405
+ **Boolean usage (uses defaults):**
1406
+ ```ts
1407
+ hash_subs: true // Equivalent to { length: 7, prefix: 'hash=' }
1408
+ ```
1409
+
1410
+ **Object usage (custom options):**
1411
+ ```ts
1412
+ hash_subs: {
1413
+ length: 7, // Hash length (default: 7)
1414
+ prefix: 'asset=', // Substitution prefix (default: 'hash=')
1415
+ format: '$file?v=$hash', // Custom format (default: just hash)
1416
+ hashes: { ... } // Pre-generated hash map from get_git_hashes (optional)
1417
+ }
1418
+ ```
1419
+
1420
+ **Examples:**
1421
+ ```ts
1422
+ // Default hash substitutions
1423
+ hash_subs: true
1424
+ // Creates: {{hash=static/css/style.css}} -> "a1b2c3d"
1425
+ // Usage: <link href="/css/style.css?v={{hash=static/css/style.css}}">
1426
+
1427
+ // Asset-style substitutions (reduces verbosity)
1428
+ hash_subs: { prefix: 'asset=', format: '$file?v=$hash' }
1429
+ // Creates: {{asset=static/css/style.css}} -> "static/css/style.css?v=a1b2c3d"
1430
+ // Usage: <link href="{{asset=static/css/style.css}}">
1431
+ ```
1432
+
1433
+ ##### `error?: object`
1434
+ Optional error page configuration:
1435
+ - `error_page`: Template for error pages (string or BunFile)
1436
+ - `use_canary_reporting?`: Whether to report errors via canary
1437
+
1438
+ Error templates receive `{{error_code}}` and `{{error_text}}` substitutions.
1439
+
1440
+ ```ts
1441
+ error: {
1442
+ error_page: Bun.file('./templates/error.html'),
1443
+ use_canary_reporting: true
1444
+ }
1445
+ ```
1446
+
1447
+ ##### `static?: object`
1448
+ Optional static file serving configuration:
1449
+ - `route`: URL path prefix for static files
1450
+ - `directory`: Local directory containing static files
1451
+ - `sub_ext?`: Array of file extensions that should have template substitution applied
1452
+
1453
+ ```ts
1454
+ static: {
1455
+ route: '/assets',
1456
+ directory: './public',
1457
+ sub_ext: ['.css', '.js'] // These files get template processing
1458
+ }
1459
+ ```
1460
+
1461
+ Files with extensions in `sub_ext` will have template substitutions applied before serving.
1462
+
1463
+ ##### `global_subs?: Record<string, BootstrapSub>`
1464
+ Optional global template substitutions available to all routes, error pages, and static files with `sub_ext`.
1465
+
1466
+ ```ts
1467
+ global_subs: {
1468
+ site_name: 'My Website',
1469
+ version: '1.0.0',
1470
+ api_url: 'https://api.example.com'
1471
+ }
1472
+ ```
1473
+
1474
+ #### Template Processing Order
1475
+
1476
+ 1. Route content is loaded
1477
+ 2. If `base` is defined, content is wrapped using `{{content}}` substitution
1478
+ 3. Route-specific `subs` and `global_subs` are applied
1479
+ 4. Hash substitutions (if enabled) are applied
1480
+
1344
1481
  <a id="api-error-handling"></a>
1345
1482
  ## API > Error Handling
1346
1483
 
@@ -1803,7 +1940,7 @@ await parse_template(..., {
1803
1940
  </t-for>
1804
1941
  ```
1805
1942
 
1806
- ### 🔧 `generate_hash_subs(length: number, prefix: string, hashes?: Record<string, string>): Promise<Record<string, string>>`
1943
+ ### 🔧 `generate_hash_subs(length: number, prefix: string, hashes?: Record<string, string>, format?: string): Promise<Record<string, string>>`
1807
1944
 
1808
1945
  Generate a replacement table for mapping file paths to hashes in templates. This is useful for cache-busting static assets.
1809
1946
 
@@ -1847,6 +1984,25 @@ server.route('/test', (req, url) => {
1847
1984
  });
1848
1985
  ```
1849
1986
 
1987
+ #### Custom Format Parameter
1988
+
1989
+ The optional `format` parameter allows you to customize how the substitution values are formatted. Use `$file` and `$hash` placeholders within the format string:
1990
+
1991
+ ```ts
1992
+ // Asset-style substitutions - reduces verbosity
1993
+ const asset_subs = await generate_hash_subs(7, 'asset=', undefined, '$file?v=$hash');
1994
+
1995
+ // Usage in templates:
1996
+ // <link rel="stylesheet" href="{{asset=static/css/shared.css}}">
1997
+ // Resolves to: static/css/shared.css?v=a1b2c3d
1998
+
1999
+ // Custom format example:
2000
+ await generate_hash_subs(7, 'url=', undefined, 'https://assets.example.com/$file?hash=$hash');
2001
+ // Result: { 'url=app.js': 'https://assets.example.com/app.js?hash=a1b2c3d' }
2002
+ ```
2003
+
2004
+ When `format` is omitted, the default behavior returns just the hash value (backward compatible).
2005
+
1850
2006
  ### 🔧 ``get_git_hashes(length: number): Promise<Record<string, string>>``
1851
2007
 
1852
2008
  Internally, `generate_hash_subs()` uses `get_git_hashes()` to retrieve the hash table from git. This function is exposed for convenience.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "spooder",
3
3
  "type": "module",
4
- "version": "5.0.1",
4
+ "version": "5.0.2",
5
5
  "module": "./src/api.ts",
6
6
  "bin": {
7
7
  "spooder": "./src/cli.ts"
package/src/api.ts CHANGED
@@ -549,14 +549,22 @@ export async function get_git_hashes(length = 7): Promise<Record<string, string>
549
549
  return hash_map;
550
550
  }
551
551
 
552
- export async function generate_hash_subs(length = 7, prefix = 'hash=', hashes?: Record<string, string>): Promise<Record<string, string>> {
552
+ export async function generate_hash_subs(length = 7, prefix = 'hash=', hashes?: Record<string, string>, format?: string): Promise<Record<string, string>> {
553
553
  const hash_map: Record<string, string> = {};
554
554
 
555
555
  if (!hashes)
556
556
  hashes = await get_git_hashes(length);
557
557
 
558
- for (const [file, hash] of Object.entries(hashes))
559
- hash_map[prefix + file] = hash;
558
+ for (const [file, hash] of Object.entries(hashes)) {
559
+ if (format !== undefined) {
560
+ const formatted_value = format
561
+ .replace(/\$file/g, file)
562
+ .replace(/\$hash/g, hash);
563
+ hash_map[prefix + file] = formatted_value;
564
+ } else {
565
+ hash_map[prefix + file] = hash;
566
+ }
567
+ }
560
568
 
561
569
  return hash_map;
562
570
  }
@@ -987,7 +995,12 @@ type BootstrapOptions = {
987
995
  base?: string | BunFile;
988
996
  routes: Record<string, BootstrapRoute>;
989
997
  cache?: ReturnType<typeof cache_http> | CacheOptions;
990
- cache_bust?: boolean;
998
+ hash_subs?: boolean | {
999
+ length?: number;
1000
+ prefix?: string;
1001
+ format?: string;
1002
+ hashes?: Record<string, string>;
1003
+ };
991
1004
  error?: {
992
1005
  use_canary_reporting?: boolean;
993
1006
  error_page: string | BunFile;
@@ -1407,7 +1420,17 @@ export function http_serve(port: number, hostname?: string) {
1407
1420
 
1408
1421
  /* Bootstrap a static web server */
1409
1422
  bootstrap: async function(options: BootstrapOptions) {
1410
- const hash_sub_table = options.cache_bust ? await generate_hash_subs() : {};
1423
+ let hash_sub_table = {};
1424
+
1425
+ if (options.hash_subs) {
1426
+ if (options.hash_subs === true) {
1427
+ hash_sub_table = await generate_hash_subs();
1428
+ } else {
1429
+ const { length, prefix, format, hashes } = options.hash_subs;
1430
+ hash_sub_table = await generate_hash_subs(length, prefix, hashes, format);
1431
+ }
1432
+ }
1433
+
1411
1434
  const global_sub_table = sub_table_merge(hash_sub_table, options.global_subs);
1412
1435
 
1413
1436
  let cache = options.cache;