@relax.js/core 1.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/LICENSE +21 -0
- package/README.md +188 -0
- package/dist/DataLoader.d.ts +51 -0
- package/dist/DependencyInjection.d.ts +271 -0
- package/dist/DependencyInjectionOld.d.ts +35 -0
- package/dist/Metadata.d.ts +8 -0
- package/dist/SequentialId.d.ts +47 -0
- package/dist/_alt/src/MustardEngine.d.ts +30 -0
- package/dist/_alt/src/MustardParser.d.ts +63 -0
- package/dist/_alt/src/MustardParser2.d.ts +35 -0
- package/dist/_alt/src/pipes.d.ts +93 -0
- package/dist/_alt/src/template.d.ts +166 -0
- package/dist/_alt/src/tools.d.ts +4 -0
- package/dist/_alt/tests/pipes.tests.d.ts +1 -0
- package/dist/_alt/tests/template.tests.d.ts +1 -0
- package/dist/_alt/vitest.config.d.ts +2 -0
- package/dist/collections/Index.d.ts +1 -0
- package/dist/collections/LinkedList.d.ts +75 -0
- package/dist/collections/Pager.d.ts +15 -0
- package/dist/collections/index.js +2 -0
- package/dist/collections/index.js.map +7 -0
- package/dist/collections/index.mjs +2 -0
- package/dist/collections/index.mjs.map +7 -0
- package/dist/components/Table.d.ts +13 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.js +128 -0
- package/dist/components/index.js.map +7 -0
- package/dist/components/index.mjs +128 -0
- package/dist/components/index.mjs.map +7 -0
- package/dist/components/lists/Table.d.ts +59 -0
- package/dist/components/lists/TreeView.d.ts +67 -0
- package/dist/components/lists/index.d.ts +2 -0
- package/dist/components/loader.d.ts +60 -0
- package/dist/components/menus/MenuItem.d.ts +30 -0
- package/dist/components/menus/TopMenu.d.ts +16 -0
- package/dist/components/menus/index.d.ts +2 -0
- package/dist/components/panels/tabs.d.ts +15 -0
- package/dist/di/index.d.ts +1 -0
- package/dist/di/index.js +2 -0
- package/dist/di/index.js.map +7 -0
- package/dist/di/index.mjs +2 -0
- package/dist/di/index.mjs.map +7 -0
- package/dist/elements/CopyAttributes.d.ts +2 -0
- package/dist/elements/dom.d.ts +18 -0
- package/dist/elements/index.d.ts +2 -0
- package/dist/elements/index.js +2 -0
- package/dist/elements/index.js.map +7 -0
- package/dist/elements/index.mjs +2 -0
- package/dist/elements/index.mjs.map +7 -0
- package/dist/errors.d.ts +71 -0
- package/dist/forms/FormReader.d.ts +182 -0
- package/dist/forms/FormValidator.d.ts +114 -0
- package/dist/forms/ValidationRules.d.ts +103 -0
- package/dist/forms/index.d.ts +4 -0
- package/dist/forms/index.js +2 -0
- package/dist/forms/index.js.map +7 -0
- package/dist/forms/index.mjs +2 -0
- package/dist/forms/index.mjs.map +7 -0
- package/dist/forms/setFormData.d.ts +49 -0
- package/dist/getParentComponent.d.ts +43 -0
- package/dist/html/TableRenderer.d.ts +44 -0
- package/dist/html/TreeBinder.d.ts +9 -0
- package/dist/html/html.d.ts +55 -0
- package/dist/html/index.d.ts +5 -0
- package/dist/html/index.js +2 -0
- package/dist/html/index.js.map +7 -0
- package/dist/html/index.mjs +2 -0
- package/dist/html/index.mjs.map +7 -0
- package/dist/html/template.d.ts +167 -0
- package/dist/http/ServerSentEvents.d.ts +116 -0
- package/dist/http/SimpleWebSocket.d.ts +153 -0
- package/dist/http/http.d.ts +177 -0
- package/dist/http/index.d.ts +3 -0
- package/dist/http/index.js +2 -0
- package/dist/http/index.js.map +7 -0
- package/dist/http/index.mjs +2 -0
- package/dist/http/index.mjs.map +7 -0
- package/dist/i18n/i18n.d.ts +105 -0
- package/dist/i18n/icu.d.ts +64 -0
- package/dist/i18n/index.d.ts +2 -0
- package/dist/i18n/index.js +2 -0
- package/dist/i18n/index.js.map +7 -0
- package/dist/i18n/index.mjs +2 -0
- package/dist/i18n/index.mjs.map +7 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +7 -0
- package/dist/index.mjs +5 -0
- package/dist/index.mjs.map +7 -0
- package/dist/lib/DataLoader.d.ts +51 -0
- package/dist/lib/DependencyInjection.d.ts +271 -0
- package/dist/lib/InvokeParent.d.ts +10 -0
- package/dist/lib/Pipes.d.ts +236 -0
- package/dist/lib/SequentialId.d.ts +47 -0
- package/dist/lib/collections/Index.d.ts +1 -0
- package/dist/lib/collections/LinkedList.d.ts +75 -0
- package/dist/lib/collections/Pager.d.ts +15 -0
- package/dist/lib/collections/TableRenderer.d.ts +44 -0
- package/dist/lib/di/index.d.ts +1 -0
- package/dist/lib/elements/CopyAttributes.d.ts +2 -0
- package/dist/lib/elements/dom.d.ts +18 -0
- package/dist/lib/elements/index.d.ts +2 -0
- package/dist/lib/errors.d.ts +71 -0
- package/dist/lib/forms/FormReader.d.ts +182 -0
- package/dist/lib/forms/FormValidator.d.ts +114 -0
- package/dist/lib/forms/ValidationRules.d.ts +103 -0
- package/dist/lib/forms/index.d.ts +4 -0
- package/dist/lib/forms/setFormData.d.ts +49 -0
- package/dist/lib/getParentComponent.d.ts +43 -0
- package/dist/lib/html/TableRenderer.d.ts +44 -0
- package/dist/lib/html/TreeBinder.d.ts +9 -0
- package/dist/lib/html/html.d.ts +55 -0
- package/dist/lib/html/html2.d.ts +55 -0
- package/dist/lib/html/index.d.ts +5 -0
- package/dist/lib/html/m.d.ts +167 -0
- package/dist/lib/html/m2.d.ts +8 -0
- package/dist/lib/html/m3.d.ts +0 -0
- package/dist/lib/html/template.d.ts +167 -0
- package/dist/lib/http/HttpClient.d.ts +153 -0
- package/dist/lib/http/ServerSentEvents.d.ts +116 -0
- package/dist/lib/http/SimpleWebSocket.d.ts +153 -0
- package/dist/lib/http/http.d.ts +177 -0
- package/dist/lib/http/index.d.ts +3 -0
- package/dist/lib/i18n/i18n.d.ts +105 -0
- package/dist/lib/i18n/icu.d.ts +64 -0
- package/dist/lib/i18n/index.d.ts +2 -0
- package/dist/lib/index.d.ts +16 -0
- package/dist/lib/routing/NavigateRouteEvent.d.ts +52 -0
- package/dist/lib/routing/RouteLink.d.ts +7 -0
- package/dist/lib/routing/Routing.d.ts +270 -0
- package/dist/lib/routing/RoutingTarget.d.ts +22 -0
- package/dist/lib/routing/index.d.ts +7 -0
- package/dist/lib/routing/navigation.d.ts +70 -0
- package/dist/lib/routing/routeMatching.d.ts +21 -0
- package/dist/lib/routing/routeTargetRegistry.d.ts +23 -0
- package/dist/lib/routing/types.d.ts +130 -0
- package/dist/lib/templates/NodeTemplate.d.ts +38 -0
- package/dist/lib/templates/accessorParser.d.ts +87 -0
- package/dist/lib/templates/parseTemplate.d.ts +6 -0
- package/dist/lib/templates/tokenizer.d.ts +76 -0
- package/dist/lib/tools.d.ts +30 -0
- package/dist/lib/utils/index.d.ts +4 -0
- package/dist/pipes.d.ts +236 -0
- package/dist/routing/NavigateRouteEvent.d.ts +52 -0
- package/dist/routing/RouteLink.d.ts +7 -0
- package/dist/routing/RoutingTarget.d.ts +22 -0
- package/dist/routing/index.d.ts +7 -0
- package/dist/routing/index.js +5 -0
- package/dist/routing/index.js.map +7 -0
- package/dist/routing/index.mjs +5 -0
- package/dist/routing/index.mjs.map +7 -0
- package/dist/routing/navigation.d.ts +70 -0
- package/dist/routing/routeMatching.d.ts +21 -0
- package/dist/routing/routeTargetRegistry.d.ts +23 -0
- package/dist/routing/types.d.ts +130 -0
- package/dist/templates/NodeTemplate.d.ts +38 -0
- package/dist/templates/accessorParser.d.ts +87 -0
- package/dist/templates/parseTemplate.d.ts +6 -0
- package/dist/templates/tokenizer.d.ts +76 -0
- package/dist/tools.d.ts +30 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.js +2 -0
- package/dist/utils/index.js.map +7 -0
- package/dist/utils/index.mjs +2 -0
- package/dist/utils/index.mjs.map +7 -0
- package/docs/Architecture.md +333 -0
- package/docs/DependencyInjection.md +237 -0
- package/docs/Errors.md +87 -0
- package/docs/GettingStarted.md +231 -0
- package/docs/Pipes.md +211 -0
- package/docs/Translations.md +312 -0
- package/docs/WhyRelaxjs.md +336 -0
- package/docs/elements/dom.md +102 -0
- package/docs/forms/creating-form-components.md +924 -0
- package/docs/forms/form-api.md +94 -0
- package/docs/forms/forms.md +99 -0
- package/docs/forms/patterns.md +311 -0
- package/docs/forms/reading-writing.md +365 -0
- package/docs/forms/validation.md +351 -0
- package/docs/html/TableRenderer.md +292 -0
- package/docs/html/html.md +175 -0
- package/docs/html/index.md +54 -0
- package/docs/html/template.md +422 -0
- package/docs/http/HttpClient.md +459 -0
- package/docs/http/ServerSentEvents.md +184 -0
- package/docs/http/index.md +109 -0
- package/docs/i18n/i18n.md +309 -0
- package/docs/i18n/intl-standard.md +178 -0
- package/docs/routing/RouteLink.md +98 -0
- package/docs/routing/Routing.md +332 -0
- package/docs/routing/RoutingTarget.md +136 -0
- package/docs/routing/layouts.md +207 -0
- package/docs/utilities.md +143 -0
- package/package.json +93 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# Dependency Injection
|
|
2
|
+
|
|
3
|
+
A lightweight IoC container for managing service dependencies in your application.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The DI system provides:
|
|
8
|
+
- Constructor injection
|
|
9
|
+
- Property injection
|
|
10
|
+
- Singleton and scoped lifetimes
|
|
11
|
+
- Decorator-based registration
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { ContainerService, container } from 'relaxjs/di';
|
|
17
|
+
|
|
18
|
+
@ContainerService()
|
|
19
|
+
class LoggerService {
|
|
20
|
+
log(message: string) {
|
|
21
|
+
console.log(`[LOG] ${message}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Resolve and use
|
|
26
|
+
const logger = container.resolve(LoggerService);
|
|
27
|
+
logger.log('Hello!');
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Registration
|
|
31
|
+
|
|
32
|
+
Use the `@ContainerService` decorator to register services:
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
@ContainerService()
|
|
36
|
+
class ConfigService {
|
|
37
|
+
apiUrl = '/api/v1';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@ContainerService({ inject: [ConfigService] })
|
|
41
|
+
class ApiClient {
|
|
42
|
+
constructor(private config: ConfigService) {}
|
|
43
|
+
|
|
44
|
+
fetch(path: string) {
|
|
45
|
+
return fetch(this.config.apiUrl + path);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Manual Registration
|
|
51
|
+
|
|
52
|
+
For cases where you can't use decorators (e.g. registering an existing instance):
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { serviceCollection } from 'relaxjs/di';
|
|
56
|
+
|
|
57
|
+
// Register with existing instance
|
|
58
|
+
const config = { apiUrl: '/api' };
|
|
59
|
+
serviceCollection.register(ConfigService, {
|
|
60
|
+
inject: [],
|
|
61
|
+
instance: config
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Register with custom key
|
|
65
|
+
serviceCollection.register(CacheService, {
|
|
66
|
+
key: 'primaryCache',
|
|
67
|
+
scope: 'global',
|
|
68
|
+
inject: []
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Scopes
|
|
73
|
+
|
|
74
|
+
### Global (Singleton)
|
|
75
|
+
|
|
76
|
+
Same instance returned every time:
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
@ContainerService({ scope: 'global' })
|
|
80
|
+
class DatabaseConnection {
|
|
81
|
+
// ...
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const db1 = container.resolve(DatabaseConnection);
|
|
85
|
+
const db2 = container.resolve(DatabaseConnection);
|
|
86
|
+
// db1 === db2
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Closest (Scoped)
|
|
90
|
+
|
|
91
|
+
New instance for each container scope:
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
@ContainerService({ scope: 'closest' })
|
|
95
|
+
class RequestContext {
|
|
96
|
+
// ...
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Constructor Injection
|
|
101
|
+
|
|
102
|
+
Dependencies are passed to the constructor in order:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
@ContainerService({
|
|
106
|
+
inject: [LoggerService, ConfigService, DatabaseConnection]
|
|
107
|
+
})
|
|
108
|
+
class UserRepository {
|
|
109
|
+
constructor(
|
|
110
|
+
private logger: LoggerService,
|
|
111
|
+
private config: ConfigService,
|
|
112
|
+
private db: DatabaseConnection
|
|
113
|
+
) {}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Property Injection
|
|
118
|
+
|
|
119
|
+
### Using the `@Inject` Decorator
|
|
120
|
+
|
|
121
|
+
The `@Inject` decorator resolves a dependency and assigns it to a class field:
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
import { ContainerService, Inject } from 'relaxjs/di';
|
|
125
|
+
|
|
126
|
+
@ContainerService({ inject: [] })
|
|
127
|
+
class OrderService {
|
|
128
|
+
@Inject(LoggerService)
|
|
129
|
+
private logger!: LoggerService;
|
|
130
|
+
|
|
131
|
+
@Inject('primaryCache') // resolve by string key
|
|
132
|
+
private cache!: CacheService;
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`@Inject` resolves the dependency immediately when the class is instantiated, so the field is available in all methods (including the constructor body).
|
|
137
|
+
|
|
138
|
+
### Using the `properties` Option
|
|
139
|
+
|
|
140
|
+
Alternatively, declare property injection in the registration options:
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
@ContainerService({
|
|
144
|
+
inject: [],
|
|
145
|
+
properties: {
|
|
146
|
+
logger: LoggerService,
|
|
147
|
+
cache: 'primaryCache' // string key
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
class OrderService {
|
|
151
|
+
logger!: LoggerService;
|
|
152
|
+
cache!: CacheService;
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Integration with Web Components
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
@ContainerService({ inject: [UserService] })
|
|
160
|
+
class UserProfileComponent extends HTMLElement {
|
|
161
|
+
constructor(private userService: UserService) {
|
|
162
|
+
super();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
connectedCallback() {
|
|
166
|
+
this.loadProfile();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async loadProfile() {
|
|
170
|
+
const user = await this.userService.getCurrentUser();
|
|
171
|
+
this.render(user);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
customElements.define('user-profile', UserProfileComponent);
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Resolving Dependencies
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
// By class
|
|
182
|
+
const service = container.resolve(MyService);
|
|
183
|
+
|
|
184
|
+
// By string key
|
|
185
|
+
const cache = container.resolve<CacheService>('primaryCache');
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Error Handling
|
|
189
|
+
|
|
190
|
+
All DI errors go through the global [`onError`](Errors.md) handler, so you can log, display, or suppress them.
|
|
191
|
+
|
|
192
|
+
### Resolution Errors
|
|
193
|
+
|
|
194
|
+
Thrown when `resolve()` cannot find a registered service.
|
|
195
|
+
|
|
196
|
+
| Field | Description |
|
|
197
|
+
|-------|-------------|
|
|
198
|
+
| `service` | The class name or string key that was requested |
|
|
199
|
+
| `registeredTypes` | Array of all registered class names |
|
|
200
|
+
| `registeredKeys` | Array of all registered string keys |
|
|
201
|
+
|
|
202
|
+
```
|
|
203
|
+
RelaxError: Failed to resolve service 'MyService'
|
|
204
|
+
{ service: 'MyService', registeredTypes: ['LoggerService', 'ConfigService'], registeredKeys: ['primaryCache'] }
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Registration Errors
|
|
208
|
+
|
|
209
|
+
Thrown during `register()` or `registerByType()` to catch configuration mistakes early.
|
|
210
|
+
|
|
211
|
+
| Error | Context | Cause |
|
|
212
|
+
|-------|---------|-------|
|
|
213
|
+
| Service name collision | `{ service }` | Two different classes registered with the same class name |
|
|
214
|
+
| Service key already registered | `{ key, existingClass, newClass }` | A string key is reused for a different class |
|
|
215
|
+
| Instance and inject conflict | `{ service }` | Both `instance` and non-empty `inject` provided (`inject` is ignored) |
|
|
216
|
+
|
|
217
|
+
### Suppressing Errors
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
import { onError } from 'relaxjs/utils';
|
|
221
|
+
|
|
222
|
+
onError((error, ctx) => {
|
|
223
|
+
if (error.context.service === 'OptionalPlugin') {
|
|
224
|
+
ctx.suppress();
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
console.error(error.message, error.context);
|
|
228
|
+
});
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Best Practices
|
|
232
|
+
|
|
233
|
+
1. **Register early**: Register all services at application startup
|
|
234
|
+
2. **Prefer constructor injection**: More explicit than property injection
|
|
235
|
+
3. **Use global scope for stateless services**: Like loggers, config
|
|
236
|
+
4. **Use closest scope for request-specific data**: Like user context
|
|
237
|
+
5. **Avoid circular dependencies**: Restructure to use events or mediators
|
package/docs/Errors.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Error Handling
|
|
2
|
+
|
|
3
|
+
Global error handling for Relaxjs. Intercept errors before they throw, inspect structured context, and decide whether to suppress them.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
By default, Relaxjs throws a `RelaxError` when something goes wrong internally. Register a handler with `onError()` to intercept these errors. The handler receives an `ErrorContext` that lets you control whether the error is thrown.
|
|
8
|
+
|
|
9
|
+
## Quick Start
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import { onError } from 'relaxjs/utils';
|
|
13
|
+
|
|
14
|
+
onError((error, ctx) => {
|
|
15
|
+
console.log(error.message);
|
|
16
|
+
console.log(error.context);
|
|
17
|
+
});
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Errors still throw after the handler runs. Call `ctx.suppress()` to prevent that:
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
onError((error, ctx) => {
|
|
24
|
+
logToService(error.message, error.context);
|
|
25
|
+
showToast(error.message);
|
|
26
|
+
ctx.suppress();
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## RelaxError
|
|
31
|
+
|
|
32
|
+
Extends `Error` with a `context: Record<string, unknown>` field. The context contains specific information from the error source. For example, a routing error includes the route name, component tag, and route data. See each module's documentation for the context fields it provides.
|
|
33
|
+
|
|
34
|
+
## ErrorContext
|
|
35
|
+
|
|
36
|
+
The second argument passed to the handler. Call `suppress()` to prevent the error from being thrown.
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
onError((error, ctx) => {
|
|
40
|
+
if (error.context.route === 'optional-sidebar') {
|
|
41
|
+
ctx.suppress();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
showErrorDialog(error.message);
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
If the handler does not call `suppress()`, the `RelaxError` is thrown after the handler returns.
|
|
50
|
+
|
|
51
|
+
## Using reportError
|
|
52
|
+
|
|
53
|
+
Application code can route errors through the same handler using `reportError()`. It returns the `RelaxError` to throw, or `null` if the handler suppressed it:
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { reportError } from 'relaxjs/utils';
|
|
57
|
+
|
|
58
|
+
const error = reportError('Payment processing failed', {
|
|
59
|
+
orderId: 123,
|
|
60
|
+
provider: 'stripe',
|
|
61
|
+
statusCode: 500,
|
|
62
|
+
});
|
|
63
|
+
if (error) throw error;
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## API Reference
|
|
67
|
+
|
|
68
|
+
### Functions
|
|
69
|
+
|
|
70
|
+
| Function | Description |
|
|
71
|
+
|----------|-------------|
|
|
72
|
+
| `onError(handler)` | Register a global error handler. Replaces any previous handler. |
|
|
73
|
+
| `reportError(message, context)` | Create and report a `RelaxError`. Returns the error to throw, or `null` if suppressed. |
|
|
74
|
+
|
|
75
|
+
### Types
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
interface ErrorContext {
|
|
79
|
+
suppress(): void;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
class RelaxError extends Error {
|
|
83
|
+
context: Record<string, unknown>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
type ErrorHandler = (error: RelaxError, ctx: ErrorContext) => void;
|
|
87
|
+
```
|
|
@@ -0,0 +1,231 @@
|
|
|
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 relaxjs
|
|
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 'relaxjs/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 'relaxjs/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 'relaxjs/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 'relaxjs/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 'relaxjs/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 'relaxjs/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.
|