Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d5800f58b4 | |||
| 49949b6776 | |||
| 623e40c5b7 | |||
| 94532c3c68 | |||
| e8e4f81747 | |||
| f8b4c355d5 | |||
| 980ccfe949 | |||
| 4a76c8f738 |
37
changelog.md
37
changelog.md
@@ -1,5 +1,42 @@
|
||||
# Changelog
|
||||
|
||||
## 2025-12-08 - 7.11.1 - fix(dependencies)
|
||||
Upgrade dependencies: bump @design.estate/dees-catalog to v3.1.1 and @push.rocks/smartwatch to v6.0.0; update migration notes in readme.hints.md
|
||||
|
||||
- package.json: @design.estate/dees-catalog updated from ^2.0.3 to ^3.1.1 (includes new icons, components and DeesIcon unified icon property; legacy iconFA deprecated)
|
||||
- package.json: @push.rocks/smartwatch updated from ^5.0.0 to ^6.0.0 (cross-runtime support, native fs.watch, API compatibility maintained: new Smartwatch class methods and events documented)
|
||||
- readme.hints.md: added migration notes for smartwatch v6 and dees-catalog v3, plus other dependency update summaries
|
||||
|
||||
## 2025-12-08 - 7.11.0 - feat(typedserver)
|
||||
Add configurable response compression (Brotli + Gzip) with defaults enabled and documentation
|
||||
|
||||
- Expose a new compression option on IServerOptions (plugins.smartserve.ICompressionConfig | boolean).
|
||||
- Pass the compression setting through to SmartServe (smartServeOptions.compression = this.options.compression).
|
||||
- Add compression option to UtilityWebsiteServer and forward it when creating SmartServe options.
|
||||
- Update README: new Compression section with global config examples, per-route decorator usage, and options reference.
|
||||
- Add a small readme.todo.md with service worker wake/reload TODO notes.
|
||||
|
||||
## 2025-12-05 - 7.10.2 - fix(docs)
|
||||
Update README with routing examples and utility server config; bump @cloudflare/workers-types and @push.rocks/smartserve versions
|
||||
|
||||
- Bumped dependency @cloudflare/workers-types to ^4.20251205.0
|
||||
- Bumped dependency @push.rocks/smartserve to ^1.3.0
|
||||
- Expanded README: added decorator-based routing examples (Route/Get/Post) using smartserve
|
||||
- Added programmatic routing examples (addRoute) and SPA/wildcard route samples
|
||||
- Enhanced UtilityWebsiteServer and UtilityServiceServer docs: default port, ads.txt, feedMetadata, addCustomRoutes example and other config options
|
||||
- Clarified security headers descriptions and configuration reference
|
||||
- Updated Quick Start console message to show running port ("Server running on port 3000!")
|
||||
- Documented EdgeWorker/domain routing caching example and noted service worker version update behavior
|
||||
- Adjusted TypedSocket example tag to use 'allClients' in README
|
||||
|
||||
## 2025-12-05 - 7.10.1 - fix(typedserver)
|
||||
Use smartserve ControllerRegistry for custom routes and remove custom route parsing
|
||||
|
||||
- addRoute now delegates to plugins.smartserve.ControllerRegistry instead of building its own regex-based matcher
|
||||
- Backwards compatibility: incoming smartserve IRequestContext is converted to a Request and ctx.params is attached to request.params before invoking the handler
|
||||
- Removed internal IRegisteredRoute, customRoutes storage, and parseRouteParams helper
|
||||
- Request handling now uses ControllerRegistry.matchRoute and registered controllers are compiled via ControllerRegistry.compileRoutes()
|
||||
|
||||
## 2025-12-05 - 7.10.0 - feat(website-server)
|
||||
Add configurable ads.txt support to website server
|
||||
|
||||
|
||||
10
package.json
10
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@api.global/typedserver",
|
||||
"version": "7.10.0",
|
||||
"version": "7.11.1",
|
||||
"description": "A TypeScript-based project for easy serving of static files with support for live reloading, compression, and typed requests.",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
@@ -61,8 +61,8 @@
|
||||
"@api.global/typedrequest": "^3.2.5",
|
||||
"@api.global/typedrequest-interfaces": "^3.0.19",
|
||||
"@api.global/typedsocket": "^4.1.0",
|
||||
"@cloudflare/workers-types": "^4.20251202.0",
|
||||
"@design.estate/dees-catalog": "^2.0.3",
|
||||
"@cloudflare/workers-types": "^4.20251205.0",
|
||||
"@design.estate/dees-catalog": "^3.1.1",
|
||||
"@design.estate/dees-comms": "^1.0.30",
|
||||
"@push.rocks/lik": "^6.2.2",
|
||||
"@push.rocks/smartdelay": "^3.0.5",
|
||||
@@ -83,11 +83,11 @@
|
||||
"@push.rocks/smartpromise": "^4.2.3",
|
||||
"@push.rocks/smartrequest": "^5.0.1",
|
||||
"@push.rocks/smartrx": "^3.0.10",
|
||||
"@push.rocks/smartserve": "^1.1.2",
|
||||
"@push.rocks/smartserve": "^1.3.0",
|
||||
"@push.rocks/smartsitemap": "^2.0.4",
|
||||
"@push.rocks/smartstream": "^3.2.5",
|
||||
"@push.rocks/smarttime": "^4.1.1",
|
||||
"@push.rocks/smartwatch": "^5.0.0",
|
||||
"@push.rocks/smartwatch": "^6.0.0",
|
||||
"@push.rocks/taskbuffer": "^3.5.0",
|
||||
"@push.rocks/webrequest": "^4.0.1",
|
||||
"@push.rocks/webstore": "^2.0.20",
|
||||
|
||||
64
pnpm-lock.yaml
generated
64
pnpm-lock.yaml
generated
@@ -16,13 +16,13 @@ importers:
|
||||
version: 3.0.19
|
||||
'@api.global/typedsocket':
|
||||
specifier: ^4.1.0
|
||||
version: 4.1.0(@push.rocks/smartserve@1.1.2)
|
||||
version: 4.1.0(@push.rocks/smartserve@1.3.0)
|
||||
'@cloudflare/workers-types':
|
||||
specifier: ^4.20251202.0
|
||||
version: 4.20251202.0
|
||||
specifier: ^4.20251205.0
|
||||
version: 4.20251205.0
|
||||
'@design.estate/dees-catalog':
|
||||
specifier: ^2.0.3
|
||||
version: 2.0.3(@tiptap/pm@2.27.1)
|
||||
specifier: ^3.1.1
|
||||
version: 3.1.1(@tiptap/pm@2.27.1)
|
||||
'@design.estate/dees-comms':
|
||||
specifier: ^1.0.30
|
||||
version: 1.0.30
|
||||
@@ -84,8 +84,8 @@ importers:
|
||||
specifier: ^3.0.10
|
||||
version: 3.0.10
|
||||
'@push.rocks/smartserve':
|
||||
specifier: ^1.1.2
|
||||
version: 1.1.2
|
||||
specifier: ^1.3.0
|
||||
version: 1.3.0
|
||||
'@push.rocks/smartsitemap':
|
||||
specifier: ^2.0.4
|
||||
version: 2.0.4
|
||||
@@ -96,8 +96,8 @@ importers:
|
||||
specifier: ^4.1.1
|
||||
version: 4.1.1
|
||||
'@push.rocks/smartwatch':
|
||||
specifier: ^5.0.0
|
||||
version: 5.0.0
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0
|
||||
'@push.rocks/taskbuffer':
|
||||
specifier: ^3.5.0
|
||||
version: 3.5.0
|
||||
@@ -125,7 +125,7 @@ importers:
|
||||
version: 2.0.0
|
||||
'@git.zone/tstest':
|
||||
specifier: ^3.1.3
|
||||
version: 3.1.3(@aws-sdk/credential-providers@3.787.0)(@push.rocks/smartserve@1.1.2)(socks@2.8.7)(typescript@5.9.3)
|
||||
version: 3.1.3(@aws-sdk/credential-providers@3.787.0)(@push.rocks/smartserve@1.3.0)(socks@2.8.7)(typescript@5.9.3)
|
||||
'@types/node':
|
||||
specifier: ^24.10.1
|
||||
version: 24.10.1
|
||||
@@ -543,14 +543,14 @@ packages:
|
||||
'@borewit/text-codec@0.1.1':
|
||||
resolution: {integrity: sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==}
|
||||
|
||||
'@cloudflare/workers-types@4.20251202.0':
|
||||
resolution: {integrity: sha512-Q7m1Ivu2fbKalOPm00KLpu6GfRaq4TlrPknqugvZgp/gDH96OYKINO4x7jvCIBvCz/aK9vVoOj8tlbSQBervVA==}
|
||||
'@cloudflare/workers-types@4.20251205.0':
|
||||
resolution: {integrity: sha512-7pup7fYkuQW5XD8RUS/vkxF9SXlrGyCXuZ4ro3uVQvca/GTeSa+8bZ8T4wbq1Aea5lmLIGSlKbhl2msME7bRBA==}
|
||||
|
||||
'@configvault.io/interfaces@1.0.17':
|
||||
resolution: {integrity: sha512-bEcCUR2VBDJsTin8HQh8Uw/mlYl2v8A3jMIaQ+MTB9Hrqd6CZL2dL7iJdWyFl/3EIX+LDxWFR+Oq7liIq7w+1Q==}
|
||||
|
||||
'@design.estate/dees-catalog@2.0.3':
|
||||
resolution: {integrity: sha512-+LoCZd2sHlp+8WRTJkDqBg9Ui1tlLcg+K3mYGkqVn+P2JLUCWUM8+LsYxgWEpgGGA8C6n1I7d+y2a63RjXwZ+Q==}
|
||||
'@design.estate/dees-catalog@3.1.1':
|
||||
resolution: {integrity: sha512-w7GMX5L3uF+w1q0oCAA1489HabgwBubbdt48/3FTOhklOZkXZF0Dfbs/hu4LDTjv0IkNPFxWY+4AXUErD+wh2Q==}
|
||||
|
||||
'@design.estate/dees-comms@1.0.30':
|
||||
resolution: {integrity: sha512-KchMlklJfKAjQiJiR0xmofXtQ27VgZtBIxcMwPE9d+h3jJRv+lPZxzBQVOM0eyM0uS44S5vJMZ11IeV4uDXSHg==}
|
||||
@@ -1297,8 +1297,8 @@ packages:
|
||||
'@push.rocks/smarts3@3.0.3':
|
||||
resolution: {integrity: sha512-Y9nXMwurthJ9Z7yi0RwjhPFUC58aY8Mhia8kFo6Xj1tBM4LE8Oxg/ydejF7otHqQGr3QyqV5C4YrDEG17rUuzg==}
|
||||
|
||||
'@push.rocks/smartserve@1.1.2':
|
||||
resolution: {integrity: sha512-NkJNgdDt/rfsd9AMheCxtFd5X+ubzffvxOxjb0Aw1A5JR3xmiWeRifqEV1oN7mMTGL9jyQVvIME6Yrdxr244dA==}
|
||||
'@push.rocks/smartserve@1.3.0':
|
||||
resolution: {integrity: sha512-4ZR9uKVWXVAPzU5wtCQ1mA9jNmOlUl3oGr50EceLT6803UwbNcst7Ek/BhzSaZ0qb2pz0jO5T/V+icgvZ1/5ww==}
|
||||
|
||||
'@push.rocks/smartshell@3.3.0':
|
||||
resolution: {integrity: sha512-m0w618H6YBs+vXGz1CgS4nPi5CUAnqRtckcS9/koGwfcIx1IpjqmiP47BoCTbdgcv0IPUxQVBG1IXTHPuZ8Z5g==}
|
||||
@@ -1339,8 +1339,8 @@ packages:
|
||||
'@push.rocks/smartversion@3.0.5':
|
||||
resolution: {integrity: sha512-8MZSo1yqyaKxKq0Q5N188l4un++9GFWVbhCAX5mXJwewZHn97ujffTeL+eOQYpWFTEpUhaq1QhL4NhqObBCt1Q==}
|
||||
|
||||
'@push.rocks/smartwatch@5.0.0':
|
||||
resolution: {integrity: sha512-uuWUlTo0l5LWOWoOuTMG7zzxpUNKBcyqoB+zyQ24NHTtSYNcaUJtaQzTO2gxMXr5sqiZDkohlThS0KvsBc3g7w==}
|
||||
'@push.rocks/smartwatch@6.0.0':
|
||||
resolution: {integrity: sha512-RVUpIP7rg+C3WFilFQ1W00oO93wA+ht9+Of2795rjFNc68skpSuQ93/1npGBliEHfFFLak2eO9vXBdngQM1N2A==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@push.rocks/smartxml@2.0.0':
|
||||
@@ -4298,12 +4298,12 @@ snapshots:
|
||||
'@push.rocks/webrequest': 3.0.37
|
||||
'@push.rocks/webstream': 1.0.10
|
||||
|
||||
'@api.global/typedserver@3.0.80(@push.rocks/smartserve@1.1.2)':
|
||||
'@api.global/typedserver@3.0.80(@push.rocks/smartserve@1.3.0)':
|
||||
dependencies:
|
||||
'@api.global/typedrequest': 3.2.5
|
||||
'@api.global/typedrequest-interfaces': 3.0.19
|
||||
'@api.global/typedsocket': 3.1.1(@push.rocks/smartserve@1.1.2)
|
||||
'@cloudflare/workers-types': 4.20251202.0
|
||||
'@api.global/typedsocket': 3.1.1(@push.rocks/smartserve@1.3.0)
|
||||
'@cloudflare/workers-types': 4.20251205.0
|
||||
'@design.estate/dees-comms': 1.0.30
|
||||
'@push.rocks/lik': 6.2.2
|
||||
'@push.rocks/smartchok': 1.1.1
|
||||
@@ -4346,7 +4346,7 @@ snapshots:
|
||||
- utf-8-validate
|
||||
- vue
|
||||
|
||||
'@api.global/typedsocket@3.1.1(@push.rocks/smartserve@1.1.2)':
|
||||
'@api.global/typedsocket@3.1.1(@push.rocks/smartserve@1.3.0)':
|
||||
dependencies:
|
||||
'@api.global/typedrequest': 3.2.5
|
||||
'@api.global/typedrequest-interfaces': 3.0.19
|
||||
@@ -4357,7 +4357,7 @@ snapshots:
|
||||
'@push.rocks/smartstring': 4.1.0
|
||||
'@push.rocks/smarturl': 3.1.0
|
||||
optionalDependencies:
|
||||
'@push.rocks/smartserve': 1.1.2
|
||||
'@push.rocks/smartserve': 1.3.0
|
||||
transitivePeerDependencies:
|
||||
- '@nuxt/kit'
|
||||
- bufferutil
|
||||
@@ -4366,7 +4366,7 @@ snapshots:
|
||||
- utf-8-validate
|
||||
- vue
|
||||
|
||||
'@api.global/typedsocket@4.1.0(@push.rocks/smartserve@1.1.2)':
|
||||
'@api.global/typedsocket@4.1.0(@push.rocks/smartserve@1.3.0)':
|
||||
dependencies:
|
||||
'@api.global/typedrequest': 3.2.5
|
||||
'@api.global/typedrequest-interfaces': 3.0.19
|
||||
@@ -4375,7 +4375,7 @@ snapshots:
|
||||
'@push.rocks/smartjson': 5.2.0
|
||||
'@push.rocks/smartpromise': 4.2.3
|
||||
'@push.rocks/smartrx': 3.0.10
|
||||
'@push.rocks/smartserve': 1.1.2
|
||||
'@push.rocks/smartserve': 1.3.0
|
||||
'@push.rocks/smartstring': 4.1.0
|
||||
'@push.rocks/smarturl': 3.1.0
|
||||
|
||||
@@ -5639,13 +5639,13 @@ snapshots:
|
||||
|
||||
'@borewit/text-codec@0.1.1': {}
|
||||
|
||||
'@cloudflare/workers-types@4.20251202.0': {}
|
||||
'@cloudflare/workers-types@4.20251205.0': {}
|
||||
|
||||
'@configvault.io/interfaces@1.0.17':
|
||||
dependencies:
|
||||
'@api.global/typedrequest-interfaces': 3.0.19
|
||||
|
||||
'@design.estate/dees-catalog@2.0.3(@tiptap/pm@2.27.1)':
|
||||
'@design.estate/dees-catalog@3.1.1(@tiptap/pm@2.27.1)':
|
||||
dependencies:
|
||||
'@design.estate/dees-domtools': 2.3.6
|
||||
'@design.estate/dees-element': 2.1.3
|
||||
@@ -6026,9 +6026,9 @@ snapshots:
|
||||
'@push.rocks/smartshell': 3.3.0
|
||||
tsx: 4.20.6
|
||||
|
||||
'@git.zone/tstest@3.1.3(@aws-sdk/credential-providers@3.787.0)(@push.rocks/smartserve@1.1.2)(socks@2.8.7)(typescript@5.9.3)':
|
||||
'@git.zone/tstest@3.1.3(@aws-sdk/credential-providers@3.787.0)(@push.rocks/smartserve@1.3.0)(socks@2.8.7)(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@api.global/typedserver': 3.0.80(@push.rocks/smartserve@1.1.2)
|
||||
'@api.global/typedserver': 3.0.80(@push.rocks/smartserve@1.3.0)
|
||||
'@git.zone/tsbundle': 2.6.3
|
||||
'@git.zone/tsrun': 2.0.0
|
||||
'@push.rocks/consolecolor': 2.0.3
|
||||
@@ -6874,7 +6874,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- aws-crt
|
||||
|
||||
'@push.rocks/smartserve@1.1.2':
|
||||
'@push.rocks/smartserve@1.3.0':
|
||||
dependencies:
|
||||
'@api.global/typedrequest': 3.2.5
|
||||
'@push.rocks/lik': 6.2.2
|
||||
@@ -6907,7 +6907,7 @@ snapshots:
|
||||
'@push.rocks/smartsocket@2.1.0':
|
||||
dependencies:
|
||||
'@api.global/typedrequest-interfaces': 3.0.19
|
||||
'@api.global/typedserver': 3.0.80(@push.rocks/smartserve@1.1.2)
|
||||
'@api.global/typedserver': 3.0.80(@push.rocks/smartserve@1.3.0)
|
||||
'@push.rocks/isohash': 2.0.1
|
||||
'@push.rocks/isounique': 1.0.5
|
||||
'@push.rocks/lik': 6.2.2
|
||||
@@ -7002,7 +7002,7 @@ snapshots:
|
||||
'@types/semver': 7.7.1
|
||||
semver: 7.7.3
|
||||
|
||||
'@push.rocks/smartwatch@5.0.0':
|
||||
'@push.rocks/smartwatch@6.0.0':
|
||||
dependencies:
|
||||
'@push.rocks/lik': 6.2.2
|
||||
'@push.rocks/smartenv': 6.0.0
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
## Recent Changes (December 2025)
|
||||
|
||||
### Dependency Updates
|
||||
- `@push.rocks/smartchok` replaced with `@push.rocks/smartwatch` (renamed package, same API)
|
||||
- `@push.rocks/smartwatch` upgraded to v6.0.0 (cross-runtime, native fs.watch)
|
||||
- `@design.estate/dees-catalog` upgraded to v3.1.1 (new icons, components)
|
||||
- `@push.rocks/smartchok` replaced with `@push.rocks/smartwatch` (renamed package)
|
||||
- `@push.rocks/smartfile` upgraded from v11 to v13 (major API change - `fs` module removed)
|
||||
- `@push.rocks/smartfs` added for filesystem operations (v1.2.0+)
|
||||
- `@push.rocks/smartenv` upgraded to v6.0.0
|
||||
@@ -14,6 +16,26 @@
|
||||
|
||||
### Code Migration Notes
|
||||
|
||||
#### smartwatch v6.0.0
|
||||
- Cross-runtime support: Node.js 20+, Deno, Bun
|
||||
- Uses native `fs.watch({ recursive: true })` for performance
|
||||
- Minimal dependencies (no chokidar, no FSEvents bindings)
|
||||
- API unchanged: `new Smartwatch([patterns])`, `.start()`, `.stop()`, `.getObservableFor(event)`
|
||||
- Events: `add`, `addDir`, `change`, `unlink`, `unlinkDir`, `error`, `ready`
|
||||
- Dynamic watching: `.add(patterns)`, `.remove(pattern)`
|
||||
- Status property: `'idle' | 'starting' | 'watching'`
|
||||
|
||||
#### dees-catalog v3.0.0+ Migration
|
||||
- **DeesIcon**: New unified `icon` property with library prefixes:
|
||||
- FontAwesome: `icon="fa:check"` (prefix `fa:`)
|
||||
- Lucide: `icon="lucide:menu"` (prefix `lucide:`)
|
||||
- Legacy `iconFA` property deprecated but still supported
|
||||
- **DeesToast**: New convenience methods and positioning:
|
||||
- `DeesToast.info()`, `.success()`, `.warning()`, `.error()`
|
||||
- Position options: `top-right`, `top-left`, `bottom-right`, `bottom-left`, `top-center`, `bottom-center`
|
||||
- New components: DeesInputTags, DeesInputDatepicker, DeesStatsGrid, DeesPagination, DeesAppuiBase
|
||||
- DeesAppuiAppbar: Hierarchical menus with keyboard navigation
|
||||
|
||||
#### smartfile v13 Migration
|
||||
- Old: `plugins.smartfile.fs.toStringSync(path)` / `plugins.smartfile.fs.toBufferSync(path)`
|
||||
- New: Use `plugins.fsInstance` (SmartFs instance with Node provider)
|
||||
@@ -24,10 +46,6 @@
|
||||
- Old: `plugins.smartfile.fs.fileTreeToHash(dir, pattern)`
|
||||
- New: `await plugins.fsInstance.directory(dir).recursive().treeHash()`
|
||||
|
||||
#### smartwatch (formerly smartchok)
|
||||
- Class renamed: `Smartchok` → `Smartwatch`
|
||||
- API remains the same: `new Smartwatch([paths])`, `.start()`, `.stop()`, `.getObservableFor(event)`
|
||||
|
||||
#### webrequest v4
|
||||
- Class renamed: `WebRequest` → `WebrequestClient`
|
||||
|
||||
|
||||
238
readme.md
238
readme.md
@@ -1,6 +1,6 @@
|
||||
# @api.global/typedserver
|
||||
|
||||
A TypeScript-first web server framework for building modern full-stack applications. Features static file serving, live reload, type-safe API integration, service worker support, and edge computing capabilities. Part of the `@api.global` ecosystem.
|
||||
A powerful TypeScript-first web server framework for building modern full-stack applications. Features static file serving, live reload, type-safe API integration, decorator-based routing, service worker support, and edge computing capabilities. Part of the `@api.global` ecosystem.
|
||||
|
||||
## Issue Reporting and Security
|
||||
|
||||
@@ -9,13 +9,14 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
|
||||
## ✨ Features
|
||||
|
||||
- 🔒 **Type-Safe API** - Full TypeScript support with `@api.global/typedrequest` and `@api.global/typedsocket`
|
||||
- 🛡️ **Security Headers** - Built-in CSP, HSTS, X-Frame-Options, and more
|
||||
- 🎯 **Decorator Routing** - Clean, expressive routing with `@Route`, `@Get`, `@Post` decorators via smartserve
|
||||
- 🛡️ **Security Headers** - Built-in CSP, HSTS, X-Frame-Options, and comprehensive security configuration
|
||||
- ⚡ **Live Reload** - Automatic browser refresh on file changes during development
|
||||
- 🛠️ **Service Worker** - Advanced caching, offline support, and background sync
|
||||
- ☁️ **Edge Workers** - Cloudflare Workers compatible edge computing with domain routing
|
||||
- 📡 **WebSocket** - Real-time bidirectional communication via TypedSocket
|
||||
- 🗺️ **SEO Tools** - Built-in sitemap, RSS feed, and robots.txt generation
|
||||
- 🎯 **SPA Support** - Single-page application fallback routing (default in UtilityWebsiteServer)
|
||||
- 🎯 **SPA Support** - Single-page application fallback routing
|
||||
- 📱 **PWA Ready** - Web App Manifest generation for progressive web apps
|
||||
|
||||
## 📦 Installation
|
||||
@@ -43,7 +44,7 @@ const server = new TypedServer({
|
||||
});
|
||||
|
||||
await server.start();
|
||||
console.log('Server running!');
|
||||
console.log('Server running on port 3000!');
|
||||
```
|
||||
|
||||
### Full Configuration
|
||||
@@ -86,6 +87,85 @@ const server = new TypedServer({
|
||||
await server.start();
|
||||
```
|
||||
|
||||
## 🛣️ Routing
|
||||
|
||||
TypedServer uses a unified routing system powered by `@push.rocks/smartserve`. You can add routes using decorators or the programmatic API.
|
||||
|
||||
### Decorator-Based Routing
|
||||
|
||||
Create clean, expressive controllers using decorators:
|
||||
|
||||
```typescript
|
||||
import * as smartserve from '@push.rocks/smartserve';
|
||||
|
||||
@smartserve.Route('/api/users')
|
||||
class UserController {
|
||||
@smartserve.Get('/')
|
||||
async listUsers(ctx: smartserve.IRequestContext): Promise<Response> {
|
||||
const users = await getUsersFromDb();
|
||||
return new Response(JSON.stringify(users), {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
|
||||
@smartserve.Get('/:id')
|
||||
async getUser(ctx: smartserve.IRequestContext): Promise<Response> {
|
||||
const userId = ctx.params.id;
|
||||
const user = await getUserById(userId);
|
||||
return new Response(JSON.stringify(user), {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
|
||||
@smartserve.Post('/')
|
||||
async createUser(ctx: smartserve.IRequestContext): Promise<Response> {
|
||||
const userData = ctx.body;
|
||||
const newUser = await createUserInDb(userData);
|
||||
return new Response(JSON.stringify(newUser), {
|
||||
status: 201,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Register the controller
|
||||
smartserve.ControllerRegistry.registerInstance(new UserController());
|
||||
```
|
||||
|
||||
### Programmatic Routes with `addRoute()`
|
||||
|
||||
Add routes dynamically using the `addRoute()` API:
|
||||
|
||||
```typescript
|
||||
import { TypedServer } from '@api.global/typedserver';
|
||||
|
||||
const server = new TypedServer({ serveDir: './public', cors: true });
|
||||
|
||||
// Simple route
|
||||
server.addRoute('/api/health', 'GET', async (request) => {
|
||||
return new Response(JSON.stringify({ status: 'ok' }), {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
});
|
||||
|
||||
// Route with parameters (Express-style :param syntax)
|
||||
server.addRoute('/api/items/:id', 'GET', async (request) => {
|
||||
const itemId = (request as any).params.id;
|
||||
return new Response(JSON.stringify({ id: itemId }), {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
});
|
||||
|
||||
// Wildcard routes
|
||||
server.addRoute('/files/*path', 'GET', async (request) => {
|
||||
const filePath = (request as any).params.path;
|
||||
// Handle file serving logic
|
||||
return new Response(`Requested: ${filePath}`);
|
||||
});
|
||||
|
||||
await server.start();
|
||||
```
|
||||
|
||||
## 🔌 Type-Safe API Integration
|
||||
|
||||
### Adding TypedRequest Handlers
|
||||
@@ -139,20 +219,22 @@ server.typedrouter.addTypedHandler<IChatMessage>(
|
||||
await server.start();
|
||||
|
||||
// Push messages to connected clients
|
||||
const connections = await server.typedsocket.findAllTargetConnectionsByTag('typedserver_frontend');
|
||||
const connections = await server.typedsocket.findAllTargetConnectionsByTag('allClients');
|
||||
for (const conn of connections) {
|
||||
// Push to specific clients
|
||||
// Push to specific clients via TypedSocket
|
||||
}
|
||||
```
|
||||
|
||||
## ☁️ Edge Worker (Cloudflare Workers)
|
||||
|
||||
Deploy your application to the edge with Cloudflare Workers:
|
||||
|
||||
```typescript
|
||||
import { EdgeWorker, DomainRouter } from '@api.global/typedserver/edgeworker';
|
||||
|
||||
const worker = new EdgeWorker();
|
||||
|
||||
// Configure domain routing
|
||||
// Configure domain routing with caching
|
||||
worker.domainRouter.addDomainInstruction({
|
||||
domainPattern: '*.example.com',
|
||||
originUrl: 'https://origin.example.com',
|
||||
@@ -160,10 +242,11 @@ worker.domainRouter.addDomainInstruction({
|
||||
cacheConfig: { maxAge: 3600 },
|
||||
});
|
||||
|
||||
// Pass-through to origin for API routes
|
||||
worker.domainRouter.addDomainInstruction({
|
||||
domainPattern: 'api.example.com',
|
||||
originUrl: 'https://api-origin.example.com',
|
||||
type: 'origin', // Pass through to origin
|
||||
type: 'origin',
|
||||
});
|
||||
|
||||
// Cloudflare Worker entry point
|
||||
@@ -188,6 +271,7 @@ const swClient = await getServiceworkerClient({
|
||||
// - Cache invalidation from server
|
||||
// - Offline support
|
||||
// - Background sync
|
||||
// - Version updates
|
||||
```
|
||||
|
||||
## 🛡️ Security Headers
|
||||
@@ -250,13 +334,82 @@ await server.start();
|
||||
| `Strict-Transport-Security` | `hstsMaxAge`, `hstsIncludeSubDomains`, `hstsPreload` | Forces HTTPS connections |
|
||||
| `X-Frame-Options` | `xFrameOptions` | Prevents clickjacking attacks |
|
||||
| `X-Content-Type-Options` | `xContentTypeOptions` | Prevents MIME-sniffing |
|
||||
| `X-XSS-Protection` | `xXssProtection` | Legacy XSS filter (still useful) |
|
||||
| `X-XSS-Protection` | `xXssProtection` | Legacy XSS filter |
|
||||
| `Referrer-Policy` | `referrerPolicy` | Controls referrer information |
|
||||
| `Permissions-Policy` | `permissionsPolicy` | Controls browser features |
|
||||
| `Cross-Origin-Opener-Policy` | `crossOriginOpenerPolicy` | Isolates browsing context |
|
||||
| `Cross-Origin-Embedder-Policy` | `crossOriginEmbedderPolicy` | Controls cross-origin embedding |
|
||||
| `Cross-Origin-Resource-Policy` | `crossOriginResourcePolicy` | Controls cross-origin resource sharing |
|
||||
|
||||
## 🗜️ Compression
|
||||
|
||||
TypedServer supports automatic response compression using Brotli and Gzip. Compression is powered by smartserve and enabled by default.
|
||||
|
||||
### Global Configuration
|
||||
|
||||
```typescript
|
||||
import { TypedServer } from '@api.global/typedserver';
|
||||
|
||||
const server = new TypedServer({
|
||||
serveDir: './dist',
|
||||
cors: true,
|
||||
|
||||
// Enable with defaults (brotli + gzip, threshold: 1024 bytes)
|
||||
compression: true,
|
||||
|
||||
// Or disable completely
|
||||
compression: false,
|
||||
|
||||
// Or configure in detail
|
||||
compression: {
|
||||
enabled: true,
|
||||
algorithms: ['br', 'gzip'], // Preferred order
|
||||
threshold: 1024, // Min size to compress (bytes)
|
||||
level: 4, // Compression level (1-11 for brotli, 1-9 for gzip)
|
||||
exclude: ['/api/stream/*'], // Skip these paths
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Per-Route Control with Decorators
|
||||
|
||||
Use `@Compress` and `@NoCompress` decorators for fine-grained control:
|
||||
|
||||
```typescript
|
||||
import * as smartserve from '@push.rocks/smartserve';
|
||||
|
||||
@smartserve.Route('/api')
|
||||
class ApiController {
|
||||
// Force maximum compression for this endpoint
|
||||
@smartserve.Get('/large-data')
|
||||
@smartserve.Compress({ level: 11 })
|
||||
async getLargeData(ctx: smartserve.IRequestContext): Promise<Response> {
|
||||
return new Response(JSON.stringify(largeDataset));
|
||||
}
|
||||
|
||||
// Disable compression for streaming endpoint
|
||||
@smartserve.Get('/events')
|
||||
@smartserve.NoCompress()
|
||||
async streamEvents(ctx: smartserve.IRequestContext): Promise<Response> {
|
||||
// Server-Sent Events shouldn't be compressed
|
||||
return new Response(eventStream, {
|
||||
headers: { 'Content-Type': 'text/event-stream' },
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Compression Options Reference
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
|--------|------|---------|-------------|
|
||||
| `enabled` | `boolean` | `true` | Enable/disable compression |
|
||||
| `algorithms` | `string[]` | `['br', 'gzip']` | Preferred algorithms in order |
|
||||
| `threshold` | `number` | `1024` | Minimum response size (bytes) to compress |
|
||||
| `level` | `number` | `4` | Compression level (1-11 for brotli, 1-9 for gzip) |
|
||||
| `compressibleTypes` | `string[]` | auto | MIME types to compress |
|
||||
| `exclude` | `string[]` | `[]` | Path patterns to skip |
|
||||
|
||||
## 📋 Configuration Reference
|
||||
|
||||
### IServerOptions
|
||||
@@ -281,7 +434,8 @@ await server.start();
|
||||
| `defaultAnswer` | `function` | - | Custom default response handler |
|
||||
| `feedMetadata` | `object` | - | RSS feed metadata options |
|
||||
| `blockWaybackMachine` | `boolean` | `false` | Block Wayback Machine archiving |
|
||||
| `securityHeaders` | `ISecurityHeaders` | - | Security headers configuration (CSP, HSTS, etc.) |
|
||||
| `securityHeaders` | `ISecurityHeaders` | - | Security headers configuration |
|
||||
| `compression` | `ICompressionConfig \| boolean` | `true` | Response compression configuration |
|
||||
|
||||
## 🏗️ Package Exports
|
||||
|
||||
@@ -297,7 +451,7 @@ await server.start();
|
||||
|
||||
## 🔄 Utility Servers
|
||||
|
||||
Pre-configured server templates with best practices built-in:
|
||||
Pre-configured server templates with best practices built-in.
|
||||
|
||||
### UtilityWebsiteServer
|
||||
|
||||
@@ -310,10 +464,10 @@ const websiteServer = new utilityservers.UtilityWebsiteServer({
|
||||
serveDir: './dist',
|
||||
domain: 'example.com',
|
||||
|
||||
// SPA fallback enabled by default (serves index.html for client routes)
|
||||
// SPA fallback enabled by default
|
||||
spaFallback: true, // default: true
|
||||
|
||||
// Optional security headers
|
||||
// Security headers
|
||||
securityHeaders: {
|
||||
csp: {
|
||||
defaultSrc: ["'self'"],
|
||||
@@ -324,18 +478,41 @@ const websiteServer = new utilityservers.UtilityWebsiteServer({
|
||||
xContentTypeOptions: true,
|
||||
},
|
||||
|
||||
// Compression (enabled by default)
|
||||
compression: true, // or { level: 6, threshold: 512 }
|
||||
|
||||
// Other options
|
||||
cors: true, // default: true
|
||||
forceSsl: false, // default: false
|
||||
appSemVer: '1.0.0',
|
||||
port: 3000, // default: 3000
|
||||
|
||||
// Optional ads.txt entries (only served if configured)
|
||||
adsTxt: [
|
||||
'google.com, pub-1234567890, DIRECT, f08c47fec0942fa0',
|
||||
],
|
||||
|
||||
// RSS feed metadata
|
||||
feedMetadata: {
|
||||
title: 'My Blog',
|
||||
description: 'A cool blog',
|
||||
link: 'https://example.com',
|
||||
},
|
||||
|
||||
// Add custom routes
|
||||
addCustomRoutes: async (typedserver) => {
|
||||
typedserver.addRoute('/api/custom', 'GET', async () => {
|
||||
return new Response('Custom route!');
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
await websiteServer.start(); // Default port 3000
|
||||
await websiteServer.start();
|
||||
```
|
||||
|
||||
### UtilityServiceServer
|
||||
|
||||
Optimized for API services:
|
||||
Optimized for API services with auto-generated info page:
|
||||
|
||||
```typescript
|
||||
import { utilityservers } from '@api.global/typedserver';
|
||||
@@ -345,11 +522,42 @@ const serviceServer = new utilityservers.UtilityServiceServer({
|
||||
serviceVersion: '1.0.0',
|
||||
serviceDomain: 'api.example.com',
|
||||
port: 8080,
|
||||
|
||||
// Add custom routes
|
||||
addCustomRoutes: async (typedserver) => {
|
||||
typedserver.addRoute('/api/status', 'GET', async () => {
|
||||
return new Response(JSON.stringify({ status: 'healthy' }), {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
await serviceServer.start();
|
||||
```
|
||||
|
||||
## 🧩 Architecture Overview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ TypedServer │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
|
||||
│ │ SmartServe │ │ TypedRouter │ │ TypedSocket │ │
|
||||
│ │ (Routing) │ │ (RPC) │ │ (WebSocket) │ │
|
||||
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
|
||||
│ │ │ │ │
|
||||
│ ▼ ▼ ▼ │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ Request Handler Pipeline │ │
|
||||
│ │ 1. Controller Registry (Decorated Routes) │ │
|
||||
│ │ 2. TypedRequest/TypedSocket handlers │ │
|
||||
│ │ 3. Static File Serving │ │
|
||||
│ │ 4. SPA Fallback │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## License and Legal Information
|
||||
|
||||
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file.
|
||||
|
||||
5
readme.todo.md
Normal file
5
readme.todo.md
Normal file
@@ -0,0 +1,5 @@
|
||||
- Wake up the service worker before sending stuff.
|
||||
|
||||
Handle reload properly. Make sure service worker is up.
|
||||
|
||||
Pill handling of service worker status.
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@api.global/typedserver',
|
||||
version: '7.10.0',
|
||||
version: '7.11.1',
|
||||
description: 'A TypeScript-based project for easy serving of static files with support for live reloading, compression, and typed requests.'
|
||||
}
|
||||
|
||||
@@ -137,6 +137,12 @@ export interface IServerOptions {
|
||||
* Security headers configuration (CSP, HSTS, X-Frame-Options, etc.)
|
||||
*/
|
||||
securityHeaders?: ISecurityHeaders;
|
||||
|
||||
/**
|
||||
* Response compression configuration
|
||||
* Set to true for defaults (brotli + gzip), false to disable, or provide detailed config
|
||||
*/
|
||||
compression?: plugins.smartserve.ICompressionConfig | boolean;
|
||||
}
|
||||
|
||||
export type THttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS' | 'ALL';
|
||||
@@ -145,14 +151,6 @@ export interface IRouteHandler {
|
||||
(request: Request): Promise<Response | null>;
|
||||
}
|
||||
|
||||
export interface IRegisteredRoute {
|
||||
pattern: string;
|
||||
regex: RegExp;
|
||||
paramNames: string[];
|
||||
method: THttpMethod;
|
||||
handler: IRouteHandler;
|
||||
}
|
||||
|
||||
export class TypedServer {
|
||||
// instance
|
||||
public options: IServerOptions;
|
||||
@@ -175,9 +173,6 @@ export class TypedServer {
|
||||
// File server for static files
|
||||
private fileServer: plugins.smartserve.FileServer;
|
||||
|
||||
// Custom route handlers (for addRoute API)
|
||||
private customRoutes: IRegisteredRoute[] = [];
|
||||
|
||||
public lastReload: number = Date.now();
|
||||
public ended = false;
|
||||
|
||||
@@ -210,49 +205,18 @@ export class TypedServer {
|
||||
* @param handler - Async function that receives Request and returns Response or null
|
||||
*/
|
||||
public addRoute(path: string, method: THttpMethod, handler: IRouteHandler): void {
|
||||
// Convert Express-style path to regex
|
||||
const paramNames: string[] = [];
|
||||
let regexPattern = path
|
||||
// Handle named parameters :param
|
||||
.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, (_, paramName) => {
|
||||
paramNames.push(paramName);
|
||||
return '([^/]+)';
|
||||
})
|
||||
// Handle wildcard *splat (matches everything including slashes)
|
||||
.replace(/\*([a-zA-Z_][a-zA-Z0-9_]*)/g, (_, paramName) => {
|
||||
paramNames.push(paramName);
|
||||
return '(.*)';
|
||||
// Delegate to smartserve's ControllerRegistry
|
||||
plugins.smartserve.ControllerRegistry.addRoute(path, method, async (ctx: plugins.smartserve.IRequestContext) => {
|
||||
// Convert context to Request for backwards compatibility
|
||||
const request = new Request(ctx.url.toString(), {
|
||||
method: ctx.method,
|
||||
headers: ctx.headers,
|
||||
});
|
||||
|
||||
// Ensure exact match
|
||||
regexPattern = `^${regexPattern}$`;
|
||||
|
||||
this.customRoutes.push({
|
||||
pattern: path,
|
||||
regex: new RegExp(regexPattern),
|
||||
paramNames,
|
||||
method,
|
||||
handler,
|
||||
(request as any).params = ctx.params;
|
||||
return handler(request);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse route parameters from a path using a registered route
|
||||
*/
|
||||
private parseRouteParams(
|
||||
route: IRegisteredRoute,
|
||||
pathname: string
|
||||
): Record<string, string> | null {
|
||||
const match = pathname.match(route.regex);
|
||||
if (!match) return null;
|
||||
|
||||
const params: Record<string, string> = {};
|
||||
route.paramNames.forEach((name, index) => {
|
||||
params[name] = match[index + 1];
|
||||
});
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* inits and starts the server
|
||||
*/
|
||||
@@ -323,6 +287,7 @@ export class TypedServer {
|
||||
const smartServeOptions: plugins.smartserve.ISmartServeOptions = {
|
||||
port,
|
||||
hostname: '0.0.0.0',
|
||||
compression: this.options.compression,
|
||||
tls:
|
||||
this.options.privateKey && this.options.publicKey
|
||||
? {
|
||||
@@ -650,18 +615,6 @@ export class TypedServer {
|
||||
}
|
||||
}
|
||||
|
||||
// Custom routes (registered via addRoute)
|
||||
for (const route of this.customRoutes) {
|
||||
if (route.method === 'ALL' || route.method === method) {
|
||||
const params = this.parseRouteParams(route, path);
|
||||
if (params !== null) {
|
||||
(request as any).params = params;
|
||||
const response = await route.handler(request);
|
||||
if (response) return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HTML injection for reload (if enabled)
|
||||
if (this.options.injectReload && this.options.serveDir) {
|
||||
const response = await this.handleHtmlWithInjection(request);
|
||||
|
||||
@@ -25,6 +25,8 @@ export interface IUtilityWebsiteServerConstructorOptions {
|
||||
port?: number;
|
||||
/** ads.txt entries (only served if configured) */
|
||||
adsTxt?: string[];
|
||||
/** Response compression configuration (default: enabled with brotli + gzip) */
|
||||
compression?: plugins.smartserve.ICompressionConfig | boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,6 +69,9 @@ export class UtilityWebsiteServer {
|
||||
forceSsl: this.options.forceSsl ?? false,
|
||||
securityHeaders: this.options.securityHeaders,
|
||||
|
||||
// Compression
|
||||
compression: this.options.compression,
|
||||
|
||||
// PWA manifest
|
||||
manifest: {
|
||||
name: this.options.domain,
|
||||
|
||||
Reference in New Issue
Block a user