@aegisjsproject/atlas 0.0.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/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ <!-- markdownlint-disable -->
2
+ # Changelog
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [v1.0.0] - 2023-05-31
11
+
12
+ Initial Release
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Chris Zuber
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,338 @@
1
+ # `@aegisjsproject/atlas`
2
+
3
+ A client-side router library using `Navigation` & `URLPattern`
4
+
5
+ [![CodeQL](https://github.com/AegisJSProject/atlas/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/AegisJSProject/atlas/actions/workflows/codeql-analysis.yml)
6
+ ![Node CI](https://github.com/AegisJSProject/atlas/workflows/Node%20CI/badge.svg)
7
+ ![Lint Code Base](https://github.com/AegisJSProject/atlas/workflows/Lint%20Code%20Base/badge.svg)
8
+
9
+ [![GitHub license](https://img.shields.io/github/license/AegisJSProject/atlas.svg)](https://github.com/AegisJSProject/atlas/blob/master/LICENSE)
10
+ [![GitHub last commit](https://img.shields.io/github/last-commit/AegisJSProject/atlas.svg)](https://github.com/AegisJSProject/atlas/commits/master)
11
+ [![GitHub release](https://img.shields.io/github/release/AegisJSProject/atlas?logo=github)](https://github.com/AegisJSProject/atlas/releases)
12
+ [![GitHub Sponsors](https://img.shields.io/github/sponsors/shgysk8zer0?logo=github)](https://github.com/sponsors/shgysk8zer0)
13
+
14
+ [![npm](https://img.shields.io/npm/v/@aegisjsproject/atlas)](https://www.npmjs.com/package/@aegisjsproject/atlas)
15
+ ![node-current](https://img.shields.io/node/v/@aegisjsproject/atlas)
16
+ ![npm bundle size gzipped](https://img.shields.io/bundlephobia/minzip/@aegisjsproject/atlas)
17
+ [![npm](https://img.shields.io/npm/dw/@aegisjsproject/atlas?logo=npm)](https://www.npmjs.com/package/@aegisjsproject/atlas)
18
+
19
+ [![GitHub followers](https://img.shields.io/github/followers/AegisJSProject.svg?style=social)](https://github.com/shgysk8zer0)
20
+ ![GitHub forks](https://img.shields.io/github/forks/AegisJSProject/atlas.svg?style=social)
21
+ ![GitHub stars](https://img.shields.io/github/stars/AegisJSProject/atlas.svg?style=social)
22
+ [![Twitter Follow](https://img.shields.io/twitter/follow/shgysk8zer0.svg?style=social)](https://twitter.com/shgysk8zer0)
23
+
24
+ [![Donate using Liberapay](https://img.shields.io/liberapay/receives/shgysk8zer0.svg?logo=liberapay)](https://liberapay.com/shgysk8zer0/donate "Donate using Liberapay")
25
+ - - -
26
+
27
+ - [Code of Conduct](./.github/CODE_OF_CONDUCT.md)
28
+ - [Contributing](./.github/CONTRIBUTING.md)
29
+ <!-- - [Security Policy](./.github/SECURITY.md) -->
30
+
31
+ ## Overview
32
+
33
+ This router intercepts same-origin navigations and resolves them to registered route modules. Each module can return content in multiple native formats (e.g. `Response`, `Document`, `Element`), allowing flexibility without imposing rendering constraints.
34
+
35
+ Key characteristics:
36
+
37
+ - Native Navigation API (`navigation`)
38
+ - Route-to-module mapping via dynamic `import()`
39
+ - Direct DOM updates (no diffing layer)
40
+ - Supports HTML streaming via `Response`
41
+ - Built-in metadata handling (title, description, styles)
42
+ - Optional preload observation
43
+ - Abort-safe lifecycle with `AbortController` and `DisposableStack`
44
+
45
+ > [!IMPORTANT]
46
+ > This requires the [Navigation API](https://developer.mozilla.org/en-US/docs/Web/API/Navigation_API), which is Baseline 2026.
47
+ > It also creates a [Trusted Types Policy](https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API), where supported,
48
+ > labeled `"aegis-atlas#html"` for handling HTML responses without sanitizer restrictions.
49
+
50
+ > [!TIP]
51
+ > Route module specifiers can use bare specifiers like `@acme/blog`. These can be resolved via an import map, for example:
52
+ >
53
+ > ```html
54
+ > <script type="importmap">
55
+ > {
56
+ > "imports": {
57
+ > "@acme/blog": "https://cdn.example.com/acme-blog/index.js"
58
+ > }
59
+ > }
60
+ > </script>
61
+ > ```
62
+ >
63
+ > This allows modules to be loaded from a CDN without changing route definitions.
64
+ ---
65
+
66
+ ## Installation
67
+
68
+ This module is intended to be used directly in modern browser environments.
69
+
70
+ ```js
71
+ import { init } from '@aegisjsproject/atlas';
72
+ ```
73
+
74
+ No dependencies required.
75
+
76
+ ---
77
+
78
+ ## Core Concepts
79
+
80
+ ### Route Modules
81
+
82
+ Each route resolves to a module with the following shape:
83
+
84
+ ```js
85
+
86
+ export default async function handler(request, context) {
87
+ return new Response('<h1>Hello</h1>', {
88
+ headers: { 'Content-Type': 'text/html' }
89
+ });
90
+ }
91
+
92
+ export const title = 'Page Title';
93
+
94
+ export const description = 'Page description';
95
+
96
+ export const styles = new CSSStyleSheet();
97
+ ```
98
+
99
+ #### Supported Exports
100
+
101
+ - `default` (required)
102
+ - Function: `(Request, RouteContextObject) => HandlerResult`
103
+ - Or static value: `HandlerResult`
104
+ - `title` (optional)
105
+ - `description` (optional)
106
+ - `styles` (optional: `CSSStyleSheet` or array)
107
+
108
+ ---
109
+
110
+ ### Handler Return Types
111
+
112
+ Handlers may return:
113
+ - `Response` (must be `text/html`)
114
+ - `HTMLDocument`
115
+ - `Element`
116
+ - `DocumentFragment`
117
+ - `URL` (triggers navigation)
118
+ ---
119
+
120
+ ### Route Context
121
+
122
+ Each handler receives a `context` object:
123
+
124
+ ```js
125
+ {
126
+
127
+ result, // URLPatternResult
128
+ params, // extracted route params
129
+ stack, // DisposableStack
130
+ controller, // AbortController
131
+ signal, // AbortSignal
132
+ type, // navigation type
133
+ url, // URL instance
134
+ state, // navigation state
135
+ info, // navigation info
136
+ timestamp // performance timestamp
137
+ }
138
+
139
+ ```
140
+
141
+ ---
142
+
143
+ ## Usage
144
+
145
+ ### Initialize Router
146
+
147
+ ```js
148
+
149
+ init({
150
+ '/': '/routes/home.js',
151
+ '/users/:id': '/routes/user.js'
152
+ '/posts/:year(\\d{4})/:month(\\d{2})/:day(\\d{2})/:slug': '@acme/blog',
153
+ '/product/:sku': '@acme/store/product',
154
+ }, {
155
+ root: 'app',
156
+ preload: true
157
+ });
158
+
159
+ ```
160
+
161
+ #### Options
162
+
163
+ - `root`: Element or element ID where content is rendered
164
+ - `preload`: Enable preload observation
165
+ - `signal`: Optional `AbortSignal` for teardown
166
+
167
+ ---
168
+
169
+ ### Navigation Helpers
170
+
171
+ ```js
172
+
173
+ import { navigate, back, forward, reload } from './router.js';
174
+
175
+ navigate('/about');
176
+ back();
177
+ forward();
178
+ reload();
179
+ ```
180
+
181
+ ---
182
+
183
+ ### Navigation Lifecycle
184
+
185
+ Wait for navigation completion:
186
+
187
+ ```js
188
+ import { whenLoaded } from './router.js';
189
+
190
+ await whenLoaded();
191
+ ```
192
+
193
+ ---
194
+
195
+ ## Behavior Details
196
+
197
+ ### Interception Rules
198
+
199
+ Navigation is intercepted only if:
200
+
201
+ - `event.canIntercept` is true
202
+ - URL is same-origin
203
+ - Triggering element does **not** have `.no-router`
204
+
205
+ ---
206
+
207
+ ### Content Handling
208
+
209
+ #### `Response`
210
+
211
+ - Must be `text/html`
212
+ - Parsed via `Document.parseHTMLUnsafe`
213
+ - Re-processed as `HTMLDocument`
214
+
215
+ #### `HTMLDocument`
216
+
217
+ - Updates:
218
+ - `document.title`
219
+ - meta description
220
+ - root content
221
+
222
+ #### `Element` / `DocumentFragment`
223
+
224
+ - Directly replaces root children
225
+
226
+ #### `URL`
227
+
228
+ - Triggers navigation
229
+
230
+ ---
231
+
232
+ ### Root Management
233
+
234
+ ```js
235
+
236
+ setRoot('app');
237
+
238
+ // or
239
+
240
+ setRoot(document.getElementById('app'));
241
+
242
+ ```
243
+
244
+ If root is `<body>`, full body is replaced.
245
+
246
+ If root is an element with `id`, only matching subtree is replaced.
247
+
248
+ ---
249
+
250
+ ### Metadata Updates
251
+
252
+ Route modules can define:
253
+
254
+ - `title` → updates `document.title`
255
+ - `description` → updates all matching meta tags:
256
+ - `name="description"`
257
+ - `og:description`
258
+ - `twitter:description`
259
+ - `styles` → appended to `document.adoptedStyleSheets`
260
+
261
+ ---
262
+
263
+ ### Form Handling
264
+
265
+ - Automatically determines method (`GET` or `POST`)
266
+ - Submits `FormData` when applicable
267
+ - Uses `Request` API for consistency
268
+
269
+ ---
270
+
271
+ ### Abort + Cleanup
272
+
273
+ Each navigation:
274
+
275
+ - Uses `AbortController`
276
+ - Combines signals via `AbortSignal.any`
277
+ - Cleans up via `DisposableStack`
278
+
279
+ Handlers should respect `context.signal` where applicable.
280
+
281
+ ---
282
+
283
+ ### Trusted Types
284
+
285
+ If supported, a Trusted Types policy is used to safely pass HTML into:
286
+
287
+ ```js
288
+
289
+ Document.parseHTMLUnsafe(...)
290
+
291
+ ```
292
+
293
+ This ensures CSP compatibility without stripping critical markup like:
294
+
295
+ - `<iframe>`
296
+ - inline event handlers
297
+ - form attributes
298
+
299
+ ---
300
+
301
+ ## Example Route
302
+
303
+ ```js
304
+
305
+ export default async function(request, { params }) {
306
+ return new Response(`
307
+ <h1>User ${params.id}</h1>
308
+ `, {
309
+ headers: { 'Content-Type': 'text/html' }
310
+ });
311
+ }
312
+
313
+ export const title = 'User Profile';
314
+ export const description = 'User details page';
315
+
316
+ ```
317
+
318
+ ---
319
+
320
+ ## Notes
321
+
322
+ - Only `text/html` responses are supported for `Response`
323
+ - Non-matching routes fall back to `fetch()`
324
+ - Errors during routing are surfaced via `reportError`
325
+ - Designed for modern browsers with Navigation API support
326
+
327
+ ---
328
+
329
+ ## Summary
330
+
331
+ This router provides a low-level, high-control alternative to framework routers by:
332
+
333
+ - Eliminating abstraction layers
334
+ - Leveraging native platform APIs
335
+ - Supporting flexible content types
336
+ - Maintaining strict control over navigation lifecycle
337
+
338
+ Intended for environments where performance, control, and minimal overhead are priorities.