@relax.js/core 1.0.4 → 1.0.5
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/dist/DependencyInjection.d.ts +3 -3
- package/dist/collections/LinkedList.d.ts +9 -8
- package/dist/collections/index.js +1 -1
- package/dist/collections/index.js.map +3 -3
- package/dist/collections/index.mjs +1 -1
- package/dist/collections/index.mjs.map +3 -3
- package/dist/di/index.js +1 -1
- package/dist/di/index.js.map +2 -2
- package/dist/di/index.mjs +1 -1
- package/dist/di/index.mjs.map +2 -2
- package/dist/elements/index.js +1 -1
- package/dist/elements/index.js.map +1 -1
- package/dist/forms/FormValidator.d.ts +2 -2
- package/dist/forms/ValidationRules.d.ts +2 -6
- package/dist/forms/index.js +1 -1
- package/dist/forms/index.js.map +3 -3
- package/dist/forms/index.mjs +1 -1
- package/dist/forms/index.mjs.map +3 -3
- package/dist/forms/setFormData.d.ts +39 -1
- package/dist/html/index.js +1 -1
- package/dist/html/index.js.map +3 -3
- package/dist/html/index.mjs +1 -1
- package/dist/html/index.mjs.map +3 -3
- package/dist/http/ServerSentEvents.d.ts +1 -1
- package/dist/http/SimpleWebSocket.d.ts +1 -1
- package/dist/http/index.js +1 -1
- package/dist/http/index.js.map +3 -3
- package/dist/http/index.mjs +1 -1
- package/dist/http/index.mjs.map +3 -3
- package/dist/i18n/icu.d.ts +1 -1
- package/dist/i18n/index.js +1 -1
- package/dist/i18n/index.js.map +2 -2
- package/dist/i18n/index.mjs +1 -1
- package/dist/i18n/index.mjs.map +2 -2
- package/dist/index.js +3 -3
- package/dist/index.js.map +3 -3
- package/dist/index.mjs +3 -3
- package/dist/index.mjs.map +3 -3
- package/dist/routing/NavigateRouteEvent.d.ts +4 -4
- package/dist/routing/index.js +2 -2
- package/dist/routing/index.js.map +3 -3
- package/dist/routing/index.mjs +3 -3
- package/dist/routing/index.mjs.map +3 -3
- package/dist/routing/navigation.d.ts +1 -1
- package/dist/templates/NodeTemplate.d.ts +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +3 -3
- package/dist/utils/index.mjs +1 -1
- package/dist/utils/index.mjs.map +3 -3
- package/docs/GettingStarted.md +7 -0
- package/docs/api.json +34 -12
- package/docs/forms/reading-writing.md +101 -1
- package/docs/setup/bootstrapping.md +154 -0
- package/docs/setup/build-and-deploy.md +183 -0
- package/docs/setup/project-structure.md +170 -0
- package/docs/setup/vite.md +175 -0
- package/package.json +3 -2
- package/docs/api/.nojekyll +0 -1
- package/docs/api/assets/hierarchy.js +0 -1
- package/docs/api/assets/highlight.css +0 -120
- package/docs/api/assets/icons.js +0 -18
- package/docs/api/assets/icons.svg +0 -1
- package/docs/api/assets/main.js +0 -60
- package/docs/api/assets/navigation.js +0 -1
- package/docs/api/assets/search.js +0 -1
- package/docs/api/assets/style.css +0 -1633
- package/docs/api/classes/http.WebSocketClient.html +0 -26
- package/docs/api/classes/i18n.LocaleChangeEvent.html +0 -66
- package/docs/api/classes/index.Blueprint.html +0 -3
- package/docs/api/classes/index.BoundNode.html +0 -3
- package/docs/api/classes/index.DigitsValidation.html +0 -10
- package/docs/api/classes/index.FormValidator.html +0 -32
- package/docs/api/classes/index.HttpError.html +0 -13
- package/docs/api/classes/index.LinkedList.html +0 -26
- package/docs/api/classes/index.NavigateRouteEvent.html +0 -76
- package/docs/api/classes/index.Node.html +0 -15
- package/docs/api/classes/index.PageSelectedEvent.html +0 -61
- package/docs/api/classes/index.Pager.html +0 -4
- package/docs/api/classes/index.RangeValidation.html +0 -15
- package/docs/api/classes/index.RelaxError.html +0 -17
- package/docs/api/classes/index.RequiredValidation.html +0 -10
- package/docs/api/classes/index.RouteError.html +0 -11
- package/docs/api/classes/index.RouteGuardError.html +0 -12
- package/docs/api/classes/index.RouteLink.html +0 -779
- package/docs/api/classes/index.RouteTarget.html +0 -788
- package/docs/api/classes/index.SSEClient.html +0 -13
- package/docs/api/classes/index.SSEDataEvent.html +0 -63
- package/docs/api/classes/index.ServiceCollection.html +0 -28
- package/docs/api/classes/index.ServiceContainer.html +0 -24
- package/docs/api/classes/index.SortChangeEvent.html +0 -61
- package/docs/api/classes/index.TableRenderer.html +0 -5
- package/docs/api/classes/index.TableSorter.html +0 -4
- package/docs/api/enums/index.GuardResult.html +0 -9
- package/docs/api/functions/elements.formError.html +0 -6
- package/docs/api/functions/elements.selectOne.html +0 -6
- package/docs/api/functions/i18n.getCurrentLocale.html +0 -3
- package/docs/api/functions/i18n.loadNamespace.html +0 -7
- package/docs/api/functions/i18n.loadNamespaces.html +0 -6
- package/docs/api/functions/i18n.onMissingTranslation.html +0 -7
- package/docs/api/functions/i18n.setLocale.html +0 -7
- package/docs/api/functions/i18n.setMessageFormatter.html +0 -7
- package/docs/api/functions/i18n.t.html +0 -9
- package/docs/api/functions/index.BooleanConverter.html +0 -6
- package/docs/api/functions/index.ContainerService.html +0 -13
- package/docs/api/functions/index.DateConverter.html +0 -11
- package/docs/api/functions/index.Inject.html +0 -16
- package/docs/api/functions/index.NumberConverter.html +0 -5
- package/docs/api/functions/index.RegisterValidator.html +0 -7
- package/docs/api/functions/index.applyPipes.html +0 -17
- package/docs/api/functions/index.asyncHandler.html +0 -11
- package/docs/api/functions/index.capitalizePipe.html +0 -4
- package/docs/api/functions/index.clearPendingNavigations.html +0 -1
- package/docs/api/functions/index.compileTemplate.html +0 -26
- package/docs/api/functions/index.configure.html +0 -5
- package/docs/api/functions/index.createBluePrint.html +0 -1
- package/docs/api/functions/index.createConverterFromDataType.html +0 -4
- package/docs/api/functions/index.createConverterFromInputType.html +0 -5
- package/docs/api/functions/index.createPipeRegistry.html +0 -12
- package/docs/api/functions/index.currencyPipe.html +0 -9
- package/docs/api/functions/index.datePipe.html +0 -9
- package/docs/api/functions/index.daysAgoPipe.html +0 -8
- package/docs/api/functions/index.defaultPipe.html +0 -5
- package/docs/api/functions/index.defineRoutes.html +0 -8
- package/docs/api/functions/index.del.html +0 -8
- package/docs/api/functions/index.findRouteByName.html +0 -5
- package/docs/api/functions/index.findRouteByUrl.html +0 -4
- package/docs/api/functions/index.firstPipe.html +0 -4
- package/docs/api/functions/index.generateSequentialId.html +0 -21
- package/docs/api/functions/index.get.html +0 -9
- package/docs/api/functions/index.getDataConverter.html +0 -11
- package/docs/api/functions/index.getParentComponent.html +0 -18
- package/docs/api/functions/index.getValidator.html +0 -4
- package/docs/api/functions/index.html.html +0 -19
- package/docs/api/functions/index.joinPipe.html +0 -5
- package/docs/api/functions/index.keysPipe.html +0 -4
- package/docs/api/functions/index.lastPipe.html +0 -4
- package/docs/api/functions/index.lowercasePipe.html +0 -4
- package/docs/api/functions/index.mapFormToClass.html +0 -17
- package/docs/api/functions/index.matchRoute.html +0 -5
- package/docs/api/functions/index.navigate.html +0 -8
- package/docs/api/functions/index.onError.html +0 -8
- package/docs/api/functions/index.piecesPipe.html +0 -8
- package/docs/api/functions/index.post.html +0 -9
- package/docs/api/functions/index.printRoutes.html +0 -2
- package/docs/api/functions/index.put.html +0 -9
- package/docs/api/functions/index.readData.html +0 -17
- package/docs/api/functions/index.registerRouteTarget.html +0 -9
- package/docs/api/functions/index.reportError.html +0 -10
- package/docs/api/functions/index.request.html +0 -8
- package/docs/api/functions/index.resolveValue.html +0 -18
- package/docs/api/functions/index.setFetch.html +0 -6
- package/docs/api/functions/index.setFormData.html +0 -17
- package/docs/api/functions/index.shortenPipe.html +0 -5
- package/docs/api/functions/index.startRouting.html +0 -6
- package/docs/api/functions/index.ternaryPipe.html +0 -6
- package/docs/api/functions/index.trimPipe.html +0 -4
- package/docs/api/functions/index.unregisterRouteTarget.html +0 -3
- package/docs/api/functions/index.uppercasePipe.html +0 -4
- package/docs/api/hierarchy.html +0 -1
- package/docs/api/index.html +0 -323
- package/docs/api/interfaces/http.SimpleDataEvent.html +0 -3
- package/docs/api/interfaces/http.WebSocketAbstraction.html +0 -9
- package/docs/api/interfaces/http.WebSocketCodec.html +0 -4
- package/docs/api/interfaces/http.WebSocketOptions.html +0 -20
- package/docs/api/interfaces/index.CompiledTemplate.html +0 -10
- package/docs/api/interfaces/index.DataLoader.html +0 -19
- package/docs/api/interfaces/index.EngineConfig.html +0 -11
- package/docs/api/interfaces/index.ErrorContext.html +0 -4
- package/docs/api/interfaces/index.FormReaderOptions.html +0 -8
- package/docs/api/interfaces/index.HttpOptions.html +0 -16
- package/docs/api/interfaces/index.HttpResponse.html +0 -17
- package/docs/api/interfaces/index.LoadRoute.html +0 -7
- package/docs/api/interfaces/index.NavigateOptions.html +0 -7
- package/docs/api/interfaces/index.PipeRegistry.html +0 -12
- package/docs/api/interfaces/index.RegistrationOptions.html +0 -22
- package/docs/api/interfaces/index.RenderTemplate.html +0 -7
- package/docs/api/interfaces/index.RequestOptions.html +0 -11
- package/docs/api/interfaces/index.Routable.html +0 -10
- package/docs/api/interfaces/index.Route.html +0 -13
- package/docs/api/interfaces/index.RouteGuard.html +0 -2
- package/docs/api/interfaces/index.RouteValue.html +0 -6
- package/docs/api/interfaces/index.SSEOptions.html +0 -24
- package/docs/api/interfaces/index.ValidationContext.html +0 -8
- package/docs/api/interfaces/index.ValidatorOptions.html +0 -14
- package/docs/api/media/Architecture.md +0 -333
- package/docs/api/media/DependencyInjection.md +0 -277
- package/docs/api/media/GettingStarted.md +0 -231
- package/docs/api/media/HttpClient.md +0 -459
- package/docs/api/media/Pipes.md +0 -211
- package/docs/api/media/Routing.md +0 -332
- package/docs/api/media/WhyRelaxjs.md +0 -336
- package/docs/api/media/forms.md +0 -99
- package/docs/api/media/html.md +0 -175
- package/docs/api/media/i18n.md +0 -354
- package/docs/api/media/utilities.md +0 -143
- package/docs/api/media/validation.md +0 -351
- package/docs/api/modules/collections_Index.html +0 -1
- package/docs/api/modules/di.html +0 -1
- package/docs/api/modules/elements.html +0 -1
- package/docs/api/modules/forms.html +0 -1
- package/docs/api/modules/html.html +0 -1
- package/docs/api/modules/http.html +0 -1
- package/docs/api/modules/i18n.html +0 -1
- package/docs/api/modules/index.html +0 -1
- package/docs/api/modules/routing.html +0 -1
- package/docs/api/modules/utils.html +0 -1
- package/docs/api/modules.html +0 -1
- package/docs/api/types/http.WebSocketFactory.html +0 -2
- package/docs/api/types/i18n.MessageFormatter.html +0 -3
- package/docs/api/types/i18n.MissingTranslationHandler.html +0 -1
- package/docs/api/types/index.Constructor.html +0 -7
- package/docs/api/types/index.ConverterFunc.html +0 -2
- package/docs/api/types/index.DataType.html +0 -2
- package/docs/api/types/index.InputType.html +0 -2
- package/docs/api/types/index.PipeFunction.html +0 -6
- package/docs/api/types/index.RouteData.html +0 -1
- package/docs/api/types/index.RouteMatchResult.html +0 -9
- package/docs/api/types/index.RouteParamType.html +0 -1
- package/docs/api/types/index.RouteSegmentType.html +0 -2
- package/docs/api/types/index.SSEEventFactory.html +0 -5
- package/docs/api/types/index.ServiceScope.html +0 -10
- package/docs/api/types/index.SortColumn.html +0 -3
- package/docs/api/variables/i18n.formatICU.html +0 -3
- package/docs/api/variables/index.container.html +0 -6
- package/docs/api/variables/index.defaultPipes.html +0 -6
- package/docs/api/variables/index.internalRoutes.html +0 -1
- package/docs/api/variables/index.serviceCollection.html +0 -6
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
Getting Started
|
|
2
|
-
===============
|
|
3
|
-
|
|
4
|
-
Relaxjs is designed for gradual adoption. Start with full control using vanilla web components, then progressively add features like templating and routing as your app grows.
|
|
5
|
-
|
|
6
|
-
## Installation
|
|
7
|
-
|
|
8
|
-
```
|
|
9
|
-
npm i -S @relax.js/core
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
## Level 1: Plain Web Components
|
|
15
|
-
|
|
16
|
-
Start with standard custom elements. No Relaxjs features are needed, just your components in HTML.
|
|
17
|
-
|
|
18
|
-
```html
|
|
19
|
-
<!-- index.html -->
|
|
20
|
-
<body>
|
|
21
|
-
<my-header></my-header>
|
|
22
|
-
<my-content></my-content>
|
|
23
|
-
</body>
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
```ts
|
|
27
|
-
// my-header.ts
|
|
28
|
-
export class MyHeader extends HTMLElement {
|
|
29
|
-
connectedCallback() {
|
|
30
|
-
this.innerHTML = `<h1>My App</h1>`;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
customElements.define('my-header', MyHeader);
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
You're in complete control. Relaxjs components like `<r-input>`, `<r-button>`, etc. work alongside your own.
|
|
37
|
-
|
|
38
|
-
---
|
|
39
|
-
|
|
40
|
-
## Level 2: Add Templating with `html()`
|
|
41
|
-
|
|
42
|
-
When your components get complex, use `html()` for reactive templates and data binding.
|
|
43
|
-
|
|
44
|
-
```ts
|
|
45
|
-
import { html } from '@relax.js/core/html';
|
|
46
|
-
|
|
47
|
-
export class UserCard extends HTMLElement {
|
|
48
|
-
private user = { name: 'Alice', role: 'Admin' };
|
|
49
|
-
|
|
50
|
-
connectedCallback() {
|
|
51
|
-
const result = html`
|
|
52
|
-
<div class="card">
|
|
53
|
-
<h2>{{name}}</h2>
|
|
54
|
-
<span>{{role}}</span>
|
|
55
|
-
<button onclick=${() => this.edit()}>Edit</button>
|
|
56
|
-
</div>
|
|
57
|
-
`(this.user);
|
|
58
|
-
this.appendChild(result.fragment);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
private edit() {
|
|
62
|
-
// Handle edit
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
customElements.define('user-card', UserCard);
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
Still no routing. You control navigation yourself.
|
|
69
|
-
|
|
70
|
-
---
|
|
71
|
-
|
|
72
|
-
## Level 3: Manual Navigation
|
|
73
|
-
|
|
74
|
-
Swap content programmatically while staying in full control.
|
|
75
|
-
|
|
76
|
-
```ts
|
|
77
|
-
import { html } from '@relax.js/core/html';
|
|
78
|
-
|
|
79
|
-
export class AppShell extends HTMLElement {
|
|
80
|
-
private main!: HTMLElement;
|
|
81
|
-
|
|
82
|
-
connectedCallback() {
|
|
83
|
-
const result = html`
|
|
84
|
-
<nav>
|
|
85
|
-
<a onclick=${() => this.showMain()}>Home</a>
|
|
86
|
-
<a onclick=${() => this.showSettings()}>Settings</a>
|
|
87
|
-
</nav>
|
|
88
|
-
<main></main>
|
|
89
|
-
`({});
|
|
90
|
-
this.appendChild(result.fragment);
|
|
91
|
-
this.main = this.querySelector('main')!;
|
|
92
|
-
this.main.innerHTML = '<home-page></home-page>';
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
private showMain() {
|
|
96
|
-
this.main.innerHTML = '<home-page></home-page>';
|
|
97
|
-
}
|
|
98
|
-
private showSettings() {
|
|
99
|
-
this.main.innerHTML = '<settings-page></settings-page>';
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
customElements.define('app-shell', AppShell);
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
---
|
|
106
|
-
|
|
107
|
-
## Level 4: Simple Routing
|
|
108
|
-
|
|
109
|
-
When your app has multiple pages and you want URL-based navigation, add routing.
|
|
110
|
-
|
|
111
|
-
```ts
|
|
112
|
-
import { Route, defineRoutes, startRouting } from '@relax.js/core/routing';
|
|
113
|
-
|
|
114
|
-
const routes: Route[] = [
|
|
115
|
-
{ name: 'home', path: '/', componentTagName: 'home-page' },
|
|
116
|
-
{ name: 'settings', path: '/settings', componentTagName: 'settings-page' },
|
|
117
|
-
{ name: 'profile', path: '/profile/:id', componentTagName: 'profile-page' },
|
|
118
|
-
];
|
|
119
|
-
|
|
120
|
-
defineRoutes(routes);
|
|
121
|
-
startRouting();
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
```html
|
|
125
|
-
<!-- index.html -->
|
|
126
|
-
<body>
|
|
127
|
-
<nav>
|
|
128
|
-
<r-link name="home">Home</r-link>
|
|
129
|
-
<r-link name="settings">Settings</r-link>
|
|
130
|
-
<r-link name="profile" param-id="123">Profile</r-link>
|
|
131
|
-
</nav>
|
|
132
|
-
<r-route-target></r-route-target>
|
|
133
|
-
</body>
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
The `<r-link>` component handles navigation by route name. Use `param-*` attributes for route parameters. The `<r-route-target>` renders the matched component.
|
|
137
|
-
|
|
138
|
-
---
|
|
139
|
-
|
|
140
|
-
## Level 5: Programmatic Navigation
|
|
141
|
-
|
|
142
|
-
Use `navigate()` for code-driven navigation.
|
|
143
|
-
|
|
144
|
-
```ts
|
|
145
|
-
import { navigate } from '@relax.js/core/routing';
|
|
146
|
-
|
|
147
|
-
// Navigate by route name with params
|
|
148
|
-
navigate('profile', { params: { id: '123' } });
|
|
149
|
-
|
|
150
|
-
// Navigate by path
|
|
151
|
-
navigate('/settings');
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
---
|
|
155
|
-
|
|
156
|
-
## Level 6: Route Guards
|
|
157
|
-
|
|
158
|
-
Protect routes with guards when you need authentication or authorization.
|
|
159
|
-
|
|
160
|
-
```ts
|
|
161
|
-
// guards.ts
|
|
162
|
-
import { RouteGuard, RouteMatchResult, GuardResult, navigate } from '@relax.js/core/routing';
|
|
163
|
-
|
|
164
|
-
export class AuthGuard implements RouteGuard {
|
|
165
|
-
check(route: RouteMatchResult): GuardResult {
|
|
166
|
-
const token = localStorage.getItem('token');
|
|
167
|
-
if (!token) {
|
|
168
|
-
navigate('login');
|
|
169
|
-
return GuardResult.Stop;
|
|
170
|
-
}
|
|
171
|
-
return GuardResult.Continue;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
```ts
|
|
177
|
-
// app.ts
|
|
178
|
-
import { Route, defineRoutes } from '@relax.js/core/routing';
|
|
179
|
-
import { AuthGuard } from './guards';
|
|
180
|
-
|
|
181
|
-
const authGuard = new AuthGuard();
|
|
182
|
-
|
|
183
|
-
const routes: Route[] = [
|
|
184
|
-
{ name: 'login', path: '/login', componentTagName: 'login-page' },
|
|
185
|
-
{ name: 'home', path: '/', componentTagName: 'home-page', guards: [authGuard] },
|
|
186
|
-
{ name: 'admin', path: '/admin', componentTagName: 'admin-page', guards: [authGuard] },
|
|
187
|
-
];
|
|
188
|
-
|
|
189
|
-
defineRoutes(routes);
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
---
|
|
193
|
-
|
|
194
|
-
## Level 7: Layouts
|
|
195
|
-
|
|
196
|
-
Use different HTML pages for distinct UI structures. Each layout is a separate static HTML file.
|
|
197
|
-
|
|
198
|
-
```
|
|
199
|
-
/index.html <- default layout (authenticated app)
|
|
200
|
-
/public.html <- public layout (login, register)
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
```ts
|
|
204
|
-
const routes: Route[] = [
|
|
205
|
-
// Routes using public.html
|
|
206
|
-
{ name: 'login', path: '/login', componentTagName: 'login-page', layout: 'public' },
|
|
207
|
-
{ name: 'register', path: '/register', componentTagName: 'register-page', layout: 'public' },
|
|
208
|
-
|
|
209
|
-
// Routes using index.html (default)
|
|
210
|
-
{ name: 'dashboard', path: '/', componentTagName: 'dashboard-page' },
|
|
211
|
-
{ name: 'settings', path: '/settings', componentTagName: 'settings-page' },
|
|
212
|
-
];
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
When navigating between layouts, the router redirects to the appropriate HTML file automatically.
|
|
216
|
-
|
|
217
|
-
---
|
|
218
|
-
|
|
219
|
-
## Summary
|
|
220
|
-
|
|
221
|
-
| Level | Features | Use When |
|
|
222
|
-
|-------|----------|----------|
|
|
223
|
-
| 1 | Plain components | Starting out, simple pages |
|
|
224
|
-
| 2 | `html()` templating | Complex component rendering |
|
|
225
|
-
| 3 | Manual navigation | Few views, full control needed |
|
|
226
|
-
| 4 | Basic routing | Multiple pages, URL sync needed |
|
|
227
|
-
| 5 | `navigate()` | Code-driven navigation |
|
|
228
|
-
| 6 | Guards | Auth/access control |
|
|
229
|
-
| 7 | Layouts | Shared UI structure |
|
|
230
|
-
|
|
231
|
-
Start at Level 1 and add features as your app requires them. You're always in control.
|
|
@@ -1,459 +0,0 @@
|
|
|
1
|
-
# HTTP Client
|
|
2
|
-
|
|
3
|
-
Type-safe HTTP module built on fetch() with automatic JWT handling.
|
|
4
|
-
|
|
5
|
-
## Quick Start
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
import { configure, get, post } from '@relax.js/core/http';
|
|
9
|
-
|
|
10
|
-
configure({ baseUrl: '/api/v1' });
|
|
11
|
-
|
|
12
|
-
// GET request
|
|
13
|
-
const response = await get('/users');
|
|
14
|
-
const users = response.as<User[]>();
|
|
15
|
-
|
|
16
|
-
// POST request
|
|
17
|
-
const result = await post('/users', JSON.stringify({ name: 'John' }));
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## Configuration
|
|
21
|
-
|
|
22
|
-
Call `configure()` once at app startup to set defaults for all requests:
|
|
23
|
-
|
|
24
|
-
```typescript
|
|
25
|
-
import { configure } from '@relax.js/core/http';
|
|
26
|
-
|
|
27
|
-
configure({
|
|
28
|
-
baseUrl: '/api/v1',
|
|
29
|
-
contentType: 'application/json',
|
|
30
|
-
bearerTokenName: 'authToken'
|
|
31
|
-
});
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
```typescript
|
|
35
|
-
interface HttpOptions {
|
|
36
|
-
baseUrl?: string; // Base URL prepended to all requests
|
|
37
|
-
contentType?: string; // Default content type (default: 'application/json')
|
|
38
|
-
bearerTokenName?: string; // JWT token key in localStorage (default: 'jwt', null to disable)
|
|
39
|
-
timeout?: number; // Default request timeout in milliseconds
|
|
40
|
-
}
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
### Request Timeouts
|
|
44
|
-
|
|
45
|
-
Set a default timeout for all requests:
|
|
46
|
-
|
|
47
|
-
```typescript
|
|
48
|
-
configure({
|
|
49
|
-
baseUrl: '/api',
|
|
50
|
-
timeout: 10000 // 10 seconds
|
|
51
|
-
});
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
Requests that exceed the timeout are automatically aborted. Per-request signals override the default timeout:
|
|
55
|
-
|
|
56
|
-
```typescript
|
|
57
|
-
// Custom timeout for a slow endpoint
|
|
58
|
-
await get('/reports/generate', null, {
|
|
59
|
-
signal: AbortSignal.timeout(60000)
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
// Manual abort control
|
|
63
|
-
const controller = new AbortController();
|
|
64
|
-
await get('/users', null, { signal: controller.signal });
|
|
65
|
-
controller.abort();
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## HTTP Methods
|
|
69
|
-
|
|
70
|
-
All methods are standalone functions. They return `Promise<HttpResponse>`.
|
|
71
|
-
|
|
72
|
-
### GET
|
|
73
|
-
|
|
74
|
-
```typescript
|
|
75
|
-
import { get } from '@relax.js/core/http';
|
|
76
|
-
|
|
77
|
-
// Simple GET
|
|
78
|
-
const response = await get('/users');
|
|
79
|
-
|
|
80
|
-
// With query parameters
|
|
81
|
-
const filtered = await get('/users', {
|
|
82
|
-
status: 'active',
|
|
83
|
-
role: 'admin'
|
|
84
|
-
});
|
|
85
|
-
// Results in: /api/v1/users?status=active&role=admin
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
### POST
|
|
89
|
-
|
|
90
|
-
```typescript
|
|
91
|
-
import { post } from '@relax.js/core/http';
|
|
92
|
-
|
|
93
|
-
const user = { name: 'John', email: 'john@example.com' };
|
|
94
|
-
const response = await post('/users', JSON.stringify(user));
|
|
95
|
-
|
|
96
|
-
if (response.success) {
|
|
97
|
-
const created = response.as<User>();
|
|
98
|
-
console.log('Created user:', created.id);
|
|
99
|
-
}
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
### PUT
|
|
103
|
-
|
|
104
|
-
```typescript
|
|
105
|
-
import { put } from '@relax.js/core/http';
|
|
106
|
-
|
|
107
|
-
const updates = { name: 'John Updated' };
|
|
108
|
-
const response = await put('/users/123', JSON.stringify(updates));
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
### DELETE
|
|
112
|
-
|
|
113
|
-
The function is named `del` (not `delete`, which is a reserved word):
|
|
114
|
-
|
|
115
|
-
```typescript
|
|
116
|
-
import { del } from '@relax.js/core/http';
|
|
117
|
-
|
|
118
|
-
const response = await del('/users/123');
|
|
119
|
-
if (response.success) {
|
|
120
|
-
console.log('User deleted');
|
|
121
|
-
}
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
### Generic Request
|
|
125
|
-
|
|
126
|
-
Use `request()` for full control over the request:
|
|
127
|
-
|
|
128
|
-
```typescript
|
|
129
|
-
import { request } from '@relax.js/core/http';
|
|
130
|
-
|
|
131
|
-
const response = await request('/users', {
|
|
132
|
-
method: 'POST',
|
|
133
|
-
headers: {
|
|
134
|
-
'Content-Type': 'application/json',
|
|
135
|
-
'X-Custom-Header': 'value'
|
|
136
|
-
},
|
|
137
|
-
body: JSON.stringify(data),
|
|
138
|
-
credentials: 'include'
|
|
139
|
-
});
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
## Response Handling
|
|
143
|
-
|
|
144
|
-
All methods return an `HttpResponse`:
|
|
145
|
-
|
|
146
|
-
```typescript
|
|
147
|
-
interface HttpResponse {
|
|
148
|
-
success: boolean; // true for 2xx responses
|
|
149
|
-
statusCode: number; // HTTP status code
|
|
150
|
-
statusReason: string; // HTTP status text
|
|
151
|
-
contentType: string | null; // Response content type
|
|
152
|
-
body: unknown; // Parsed JSON body (success) or raw text (error)
|
|
153
|
-
charset: string | null; // Response charset
|
|
154
|
-
as<T>(): T; // Type-cast body (throws on error responses)
|
|
155
|
-
}
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
### Type-Safe Responses
|
|
159
|
-
|
|
160
|
-
```typescript
|
|
161
|
-
interface User {
|
|
162
|
-
id: number;
|
|
163
|
-
name: string;
|
|
164
|
-
email: string;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const response = await get('/users/123');
|
|
168
|
-
|
|
169
|
-
if (response.success) {
|
|
170
|
-
const user = response.as<User>();
|
|
171
|
-
displayUser(user);
|
|
172
|
-
} else {
|
|
173
|
-
console.error(`Error ${response.statusCode}: ${response.body}`);
|
|
174
|
-
}
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
### 204 No Content
|
|
178
|
-
|
|
179
|
-
Responses with status 204 return `null` as the body (no JSON parsing attempted).
|
|
180
|
-
|
|
181
|
-
## Authentication
|
|
182
|
-
|
|
183
|
-
JWT tokens are automatically read from localStorage and added as `Authorization: Bearer <token>`:
|
|
184
|
-
|
|
185
|
-
```typescript
|
|
186
|
-
// Login and store token
|
|
187
|
-
const loginResponse = await post('/auth/login', JSON.stringify({
|
|
188
|
-
username: 'user',
|
|
189
|
-
password: 'pass'
|
|
190
|
-
}));
|
|
191
|
-
|
|
192
|
-
if (loginResponse.success) {
|
|
193
|
-
const { token } = loginResponse.as<{ token: string }>();
|
|
194
|
-
localStorage.setItem('jwt', token);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// All subsequent requests include the Authorization header automatically
|
|
198
|
-
const protectedData = await get('/protected/resource');
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
### Disabling Auto-Auth
|
|
202
|
-
|
|
203
|
-
```typescript
|
|
204
|
-
configure({
|
|
205
|
-
baseUrl: '/api/public',
|
|
206
|
-
bearerTokenName: null // Disable JWT handling
|
|
207
|
-
});
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
### Custom Token Name
|
|
211
|
-
|
|
212
|
-
```typescript
|
|
213
|
-
configure({
|
|
214
|
-
bearerTokenName: 'auth_token' // Reads from localStorage.getItem('auth_token')
|
|
215
|
-
});
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
## Error Handling
|
|
219
|
-
|
|
220
|
-
```typescript
|
|
221
|
-
import { get, HttpError } from '@relax.js/core/http';
|
|
222
|
-
|
|
223
|
-
try {
|
|
224
|
-
const response = await get('/users/999');
|
|
225
|
-
|
|
226
|
-
if (!response.success) {
|
|
227
|
-
throw new HttpError(response);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
return response.as<User>();
|
|
231
|
-
} catch (error) {
|
|
232
|
-
if (error instanceof HttpError) {
|
|
233
|
-
console.error(`HTTP ${error.response.statusCode}: ${error.message}`);
|
|
234
|
-
} else {
|
|
235
|
-
console.error('Network error:', error);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
## Testing
|
|
241
|
-
|
|
242
|
-
Replace the global fetch implementation for unit tests:
|
|
243
|
-
|
|
244
|
-
```typescript
|
|
245
|
-
import { setFetch, get, configure } from '@relax.js/core/http';
|
|
246
|
-
|
|
247
|
-
// Mock fetch for tests
|
|
248
|
-
setFetch(async (url, options) => {
|
|
249
|
-
return new Response(JSON.stringify({ id: 1, name: 'Test User' }), {
|
|
250
|
-
status: 200,
|
|
251
|
-
headers: { 'content-type': 'application/json' }
|
|
252
|
-
});
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
configure({ baseUrl: '/api' });
|
|
256
|
-
const response = await get('/users/1');
|
|
257
|
-
const user = response.as<User>();
|
|
258
|
-
// user === { id: 1, name: 'Test User' }
|
|
259
|
-
|
|
260
|
-
// Restore real fetch
|
|
261
|
-
setFetch();
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
## WebSocket Client
|
|
265
|
-
|
|
266
|
-
Type-safe WebSocket client with automatic reconnection and message queuing.
|
|
267
|
-
|
|
268
|
-
```typescript
|
|
269
|
-
import { WebSocketClient } from '@relax.js/core/http';
|
|
270
|
-
|
|
271
|
-
interface ChatMessage {
|
|
272
|
-
user: string;
|
|
273
|
-
text: string;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
const ws = new WebSocketClient<ChatMessage>('wss://chat.example.com', {
|
|
277
|
-
autoReconnect: true,
|
|
278
|
-
reconnectDelay: 1000,
|
|
279
|
-
maxReconnectDelay: 30000,
|
|
280
|
-
onConnect: (socket) => console.log('Connected'),
|
|
281
|
-
onClose: (socket) => console.log('Disconnected')
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
ws.connect();
|
|
285
|
-
|
|
286
|
-
// Send messages (queued automatically if disconnected)
|
|
287
|
-
ws.send({ user: 'John', text: 'Hello!' });
|
|
288
|
-
|
|
289
|
-
// Receive messages
|
|
290
|
-
while (ws.connected) {
|
|
291
|
-
try {
|
|
292
|
-
const message = await ws.receive();
|
|
293
|
-
console.log(`${message.user}: ${message.text}`);
|
|
294
|
-
} catch (error) {
|
|
295
|
-
console.log('Connection closed');
|
|
296
|
-
break;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
ws.disconnect();
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
### Features
|
|
304
|
-
|
|
305
|
-
- **Auto-reconnect**: Automatically reconnects with exponential backoff (enabled by default)
|
|
306
|
-
- **Message queuing**: Messages sent while disconnected are queued and sent on reconnect
|
|
307
|
-
- **Type-safe**: Generic type parameter for message types
|
|
308
|
-
- **JSON by default**: Automatically serializes/deserializes JSON messages
|
|
309
|
-
- **Connection state**: Check `connected` property for current state
|
|
310
|
-
- **Graceful disconnect**: Call `disconnect()` to close without auto-reconnect
|
|
311
|
-
|
|
312
|
-
### WebSocket Options
|
|
313
|
-
|
|
314
|
-
```typescript
|
|
315
|
-
interface WebSocketOptions<TMessage> {
|
|
316
|
-
codec?: WebSocketCodec<TMessage>; // Custom message encoding
|
|
317
|
-
autoReconnect?: boolean; // Auto-reconnect (default: true)
|
|
318
|
-
reconnectDelay?: number; // Initial reconnect delay in ms (default: 1000)
|
|
319
|
-
maxReconnectDelay?: number; // Max reconnect delay in ms (default: 30000)
|
|
320
|
-
onConnect?: (socket: WebSocketClient<TMessage>) => void;
|
|
321
|
-
onClose?: (socket: WebSocketClient<TMessage>) => void;
|
|
322
|
-
}
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
### Exponential Backoff
|
|
326
|
-
|
|
327
|
-
When auto-reconnect is enabled, the client uses exponential backoff:
|
|
328
|
-
|
|
329
|
-
| Attempt | Delay (with defaults) |
|
|
330
|
-
|---------|----------------------|
|
|
331
|
-
| 1 | 1s |
|
|
332
|
-
| 2 | 2s |
|
|
333
|
-
| 3 | 4s |
|
|
334
|
-
| 4 | 8s |
|
|
335
|
-
| 5 | 16s |
|
|
336
|
-
| 6+ | 30s (max) |
|
|
337
|
-
|
|
338
|
-
The delay resets to `reconnectDelay` after a successful connection.
|
|
339
|
-
|
|
340
|
-
### Custom Message Codec
|
|
341
|
-
|
|
342
|
-
```typescript
|
|
343
|
-
interface WebSocketCodec<TMessage> {
|
|
344
|
-
encode(data: TMessage): string | ArrayBufferLike | Blob | ArrayBufferView;
|
|
345
|
-
decode(data: string | ArrayBufferLike | Blob | ArrayBufferView): TMessage;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
const ws = new WebSocketClient<Message>('wss://api.example.com', {
|
|
349
|
-
codec: {
|
|
350
|
-
encode(msg: Message): string {
|
|
351
|
-
return msgpack.encode(msg);
|
|
352
|
-
},
|
|
353
|
-
decode(data: string): Message {
|
|
354
|
-
return msgpack.decode(data);
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
});
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
### Receive Behavior
|
|
361
|
-
|
|
362
|
-
`receive()` returns a Promise that resolves when a message arrives. Only one `receive()` call can be active at a time:
|
|
363
|
-
|
|
364
|
-
```typescript
|
|
365
|
-
// Correct: sequential receives
|
|
366
|
-
const msg1 = await ws.receive();
|
|
367
|
-
const msg2 = await ws.receive();
|
|
368
|
-
|
|
369
|
-
// Error: concurrent receives throw
|
|
370
|
-
const [msg1, msg2] = await Promise.all([ws.receive(), ws.receive()]); // Throws!
|
|
371
|
-
```
|
|
372
|
-
|
|
373
|
-
The promise rejects if the connection closes while waiting:
|
|
374
|
-
|
|
375
|
-
```typescript
|
|
376
|
-
try {
|
|
377
|
-
const message = await ws.receive();
|
|
378
|
-
handleMessage(message);
|
|
379
|
-
} catch (error) {
|
|
380
|
-
// error.message === 'WebSocket connection closed'
|
|
381
|
-
console.log('Connection lost');
|
|
382
|
-
}
|
|
383
|
-
```
|
|
384
|
-
|
|
385
|
-
### Testing WebSocket
|
|
386
|
-
|
|
387
|
-
Pass a factory function instead of a URL:
|
|
388
|
-
|
|
389
|
-
```typescript
|
|
390
|
-
import { WebSocketClient, WebSocketAbstraction, WebSocketFactory } from '@relax.js/core/http';
|
|
391
|
-
|
|
392
|
-
const mockSocket: WebSocketAbstraction = {
|
|
393
|
-
onopen: null,
|
|
394
|
-
onerror: null,
|
|
395
|
-
onclose: null,
|
|
396
|
-
onmessage: null,
|
|
397
|
-
send: vi.fn(),
|
|
398
|
-
close: vi.fn()
|
|
399
|
-
};
|
|
400
|
-
|
|
401
|
-
const factory: WebSocketFactory = () => mockSocket;
|
|
402
|
-
const ws = new WebSocketClient<Message>(factory);
|
|
403
|
-
ws.connect();
|
|
404
|
-
|
|
405
|
-
// Simulate server message
|
|
406
|
-
mockSocket.onmessage?.({ data: '{"text": "hello"}' });
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
## API Reference
|
|
410
|
-
|
|
411
|
-
### Functions
|
|
412
|
-
|
|
413
|
-
| Function | Description |
|
|
414
|
-
|----------|-------------|
|
|
415
|
-
| `configure(options)` | Set module-wide defaults (base URL, content type, JWT) |
|
|
416
|
-
| `get(url, queryString?, options?)` | GET request with optional query parameters |
|
|
417
|
-
| `post(url, body, options?)` | POST request with body |
|
|
418
|
-
| `put(url, body, options?)` | PUT request with body |
|
|
419
|
-
| `del(url, options?)` | DELETE request |
|
|
420
|
-
| `request(url, options?)` | Generic request with full RequestInit options |
|
|
421
|
-
| `setFetch(fn?)` | Replace fetch implementation for testing |
|
|
422
|
-
|
|
423
|
-
### WebSocketClient
|
|
424
|
-
|
|
425
|
-
| Method/Property | Description |
|
|
426
|
-
|-----------------|-------------|
|
|
427
|
-
| `connect()` | Establish WebSocket connection |
|
|
428
|
-
| `disconnect()` | Close connection without auto-reconnect |
|
|
429
|
-
| `send(data)` | Send message (queued if disconnected) |
|
|
430
|
-
| `receive()` | Receive next message (rejects on close) |
|
|
431
|
-
| `connected` | `boolean` - Current connection state |
|
|
432
|
-
|
|
433
|
-
### Exports
|
|
434
|
-
|
|
435
|
-
```typescript
|
|
436
|
-
// HTTP
|
|
437
|
-
import {
|
|
438
|
-
configure,
|
|
439
|
-
get,
|
|
440
|
-
post,
|
|
441
|
-
put,
|
|
442
|
-
del,
|
|
443
|
-
request,
|
|
444
|
-
setFetch,
|
|
445
|
-
HttpOptions,
|
|
446
|
-
HttpResponse,
|
|
447
|
-
HttpError,
|
|
448
|
-
RequestOptions
|
|
449
|
-
} from '@relax.js/core/http';
|
|
450
|
-
|
|
451
|
-
// WebSocket
|
|
452
|
-
import {
|
|
453
|
-
WebSocketClient,
|
|
454
|
-
WebSocketOptions,
|
|
455
|
-
WebSocketCodec,
|
|
456
|
-
WebSocketAbstraction,
|
|
457
|
-
WebSocketFactory
|
|
458
|
-
} from '@relax.js/core/http';
|
|
459
|
-
```
|