@relax.js/core 1.0.4 → 1.0.6
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 +52 -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 +137 -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,333 +0,0 @@
|
|
|
1
|
-
# Architecture Overview
|
|
2
|
-
|
|
3
|
-
Relaxjs is a library for building web applications with Web Components. It emphasizes explicit control, direct DOM manipulation, and predictable behavior.
|
|
4
|
-
|
|
5
|
-
## Philosophy
|
|
6
|
-
|
|
7
|
-
Unlike modern SPA frameworks, Relaxjs takes an explicit approach:
|
|
8
|
-
|
|
9
|
-
- **No virtual DOM**: The real DOM is the source of truth
|
|
10
|
-
- **No hidden reactivity**: Changes happen when you explicitly make them
|
|
11
|
-
- **No synthetic lifecycle**: Uses native Web Component lifecycle hooks
|
|
12
|
-
- **Full developer control**: You always know what triggered what
|
|
13
|
-
|
|
14
|
-
## Core Modules
|
|
15
|
-
|
|
16
|
-
```
|
|
17
|
-
@relax.js/core
|
|
18
|
-
├── Routing # SPA navigation with guards and layouts
|
|
19
|
-
├── Forms # Data binding, validation, type conversion
|
|
20
|
-
├── DI # Dependency injection container
|
|
21
|
-
├── HTTP # Fetch wrapper and WebSocket client
|
|
22
|
-
├── i18n # Internationalization with ICU support
|
|
23
|
-
├── Templates # HTML templating with data binding
|
|
24
|
-
└── Components # Pre-built UI components
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Module Integration
|
|
28
|
-
|
|
29
|
-
### Application Startup
|
|
30
|
-
|
|
31
|
-
```typescript
|
|
32
|
-
import { defineRoutes, startRouting } from '@relax.js/core/routing';
|
|
33
|
-
import { setLocale } from '@relax.js/core/i18n';
|
|
34
|
-
import { serviceCollection } from '@relax.js/core/di';
|
|
35
|
-
|
|
36
|
-
class Application extends HTMLElement {
|
|
37
|
-
async connectedCallback() {
|
|
38
|
-
// 1. Register services
|
|
39
|
-
serviceCollection.registerByType(ApiService, { inject: [] });
|
|
40
|
-
serviceCollection.registerByType(AuthService, { inject: [ApiService] });
|
|
41
|
-
|
|
42
|
-
// 2. Set locale
|
|
43
|
-
await setLocale(navigator.language);
|
|
44
|
-
|
|
45
|
-
// 3. Define routes
|
|
46
|
-
defineRoutes([
|
|
47
|
-
{ name: 'home', path: '/', componentTagName: 'app-home' },
|
|
48
|
-
{ name: 'login', path: '/login', componentTagName: 'login-page' }
|
|
49
|
-
]);
|
|
50
|
-
|
|
51
|
-
// 4. Start routing
|
|
52
|
-
startRouting();
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
customElements.define('app-main', Application);
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
### Component Pattern
|
|
60
|
-
|
|
61
|
-
```typescript
|
|
62
|
-
class UserProfile extends HTMLElement {
|
|
63
|
-
private form: HTMLFormElement;
|
|
64
|
-
private validator: FormValidator;
|
|
65
|
-
|
|
66
|
-
connectedCallback() {
|
|
67
|
-
this.innerHTML = `
|
|
68
|
-
<form>
|
|
69
|
-
<input name="name" required>
|
|
70
|
-
<input name="email" type="email" required>
|
|
71
|
-
<button type="submit">Save</button>
|
|
72
|
-
</form>
|
|
73
|
-
`;
|
|
74
|
-
|
|
75
|
-
this.form = this.querySelector('form')!;
|
|
76
|
-
this.validator = new FormValidator(this.form, {
|
|
77
|
-
submitCallback: () => this.save()
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
this.loadUser();
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
async loadUser() {
|
|
84
|
-
const api = container.resolve(ApiService);
|
|
85
|
-
const user = await api.get('/user/profile');
|
|
86
|
-
setFormData(this.form, user.as<User>());
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
async save() {
|
|
90
|
-
const data = readData(this.form);
|
|
91
|
-
const api = container.resolve(ApiService);
|
|
92
|
-
await api.put('/user/profile', JSON.stringify(data));
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
customElements.define('user-profile', UserProfile);
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
## Data Flow
|
|
100
|
-
|
|
101
|
-
```
|
|
102
|
-
┌─────────────────────────────────────────────────────────┐
|
|
103
|
-
│ User Action │
|
|
104
|
-
└─────────────────────────────────────────────────────────┘
|
|
105
|
-
│
|
|
106
|
-
▼
|
|
107
|
-
┌─────────────────────────────────────────────────────────┐
|
|
108
|
-
│ Event Handler │
|
|
109
|
-
│ │
|
|
110
|
-
│ element.addEventListener('click', (e) => { │
|
|
111
|
-
│ // Explicit action │
|
|
112
|
-
│ }); │
|
|
113
|
-
└─────────────────────────────────────────────────────────┘
|
|
114
|
-
│
|
|
115
|
-
▼
|
|
116
|
-
┌─────────────────────────────────────────────────────────┐
|
|
117
|
-
│ DOM Update │
|
|
118
|
-
│ │
|
|
119
|
-
│ // Direct manipulation │
|
|
120
|
-
│ element.textContent = newValue; │
|
|
121
|
-
│ element.setAttribute('data-state', 'active'); │
|
|
122
|
-
└─────────────────────────────────────────────────────────┘
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
## Routing Architecture
|
|
126
|
-
|
|
127
|
-
```
|
|
128
|
-
┌──────────────┐ navigate() ┌──────────────────┐
|
|
129
|
-
│ User │ ────────────────► │ Router │
|
|
130
|
-
│ Action │ │ │
|
|
131
|
-
└──────────────┘ │ - Match route │
|
|
132
|
-
│ - Check guards │
|
|
133
|
-
│ - Update URL │
|
|
134
|
-
└────────┬─────────┘
|
|
135
|
-
│
|
|
136
|
-
NavigateRouteEvent
|
|
137
|
-
│
|
|
138
|
-
▼
|
|
139
|
-
┌──────────────────┐
|
|
140
|
-
│ r-route-target│
|
|
141
|
-
│ │
|
|
142
|
-
│ - Load component│
|
|
143
|
-
│ - Pass params │
|
|
144
|
-
└──────────────────┘
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
### Multiple Targets
|
|
148
|
-
|
|
149
|
-
```html
|
|
150
|
-
<body>
|
|
151
|
-
<r-route-target>
|
|
152
|
-
<!-- Main content renders here -->
|
|
153
|
-
</r-route-target>
|
|
154
|
-
|
|
155
|
-
<r-route-target name="modal">
|
|
156
|
-
<!-- Modal content renders here -->
|
|
157
|
-
</r-route-target>
|
|
158
|
-
|
|
159
|
-
<r-route-target name="sidebar">
|
|
160
|
-
<!-- Sidebar content renders here -->
|
|
161
|
-
</r-route-target>
|
|
162
|
-
</body>
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
```typescript
|
|
166
|
-
// Navigate to specific target
|
|
167
|
-
navigate('userDetails', { params: { id: '123' }, target: 'modal' });
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
## Form Data Flow
|
|
171
|
-
|
|
172
|
-
```
|
|
173
|
-
┌─────────────┐ setFormData() ┌─────────────────────────────────────┐
|
|
174
|
-
│ Data │ ──────────────────► │ Form Elements & Custom Components │
|
|
175
|
-
│ Object │ │ │
|
|
176
|
-
└─────────────┘ │ <input> <select> <textarea> │
|
|
177
|
-
│ <r-input> <r-checkbox> (FORM API) │
|
|
178
|
-
▲ └────────┬────────────────────────────┘
|
|
179
|
-
│ │
|
|
180
|
-
│ readData() User edits
|
|
181
|
-
│ │
|
|
182
|
-
│ ▼
|
|
183
|
-
┌─────┴───────┐ FormValidator ┌─────────────────┐
|
|
184
|
-
│ Updated │ ◄─────────────────── │ Validation │
|
|
185
|
-
│ Object │ │ │
|
|
186
|
-
└─────────────┘ │ - HTML5 │
|
|
187
|
-
│ - Custom rules │
|
|
188
|
-
│ - FORM API │
|
|
189
|
-
│ components │
|
|
190
|
-
└─────────────────┘
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
### Form Component Support
|
|
194
|
-
|
|
195
|
-
The form utilities support both native HTML form elements and modern form-associated custom elements (those implementing the FORM API with `ElementInternals`). This means:
|
|
196
|
-
|
|
197
|
-
- **Native elements**: `<input>`, `<select>`, `<textarea>` work as expected
|
|
198
|
-
- **Custom components**: Web components like `<r-input>`, `<r-checkbox>`, `<r-select>` that use `ElementInternals` and `formAssociated = true` are fully integrated
|
|
199
|
-
- **No API differences**: Both types are handled identically by `setFormData()`, `readData()`, `mapFormToClass()`, and `FormValidator`
|
|
200
|
-
|
|
201
|
-
## Template Rendering
|
|
202
|
-
|
|
203
|
-
### Static Templates (html)
|
|
204
|
-
|
|
205
|
-
```typescript
|
|
206
|
-
const template = html`
|
|
207
|
-
<div class="card">
|
|
208
|
-
<h2>${'title'}</h2>
|
|
209
|
-
<p>${'description'}</p>
|
|
210
|
-
</div>
|
|
211
|
-
`;
|
|
212
|
-
|
|
213
|
-
// Creates new DOM each time
|
|
214
|
-
const fragment = template({ title: 'Hello', description: 'World' });
|
|
215
|
-
container.appendChild(fragment);
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
### Updateable Templates (html)
|
|
219
|
-
|
|
220
|
-
```typescript
|
|
221
|
-
const template = html`
|
|
222
|
-
<div class="user">
|
|
223
|
-
<span>{{name}}</span>
|
|
224
|
-
<span>{{score|number}}</span>
|
|
225
|
-
</div>
|
|
226
|
-
`;
|
|
227
|
-
|
|
228
|
-
const rendered = template({ name: 'John', score: 42 });
|
|
229
|
-
container.appendChild(rendered.fragment);
|
|
230
|
-
|
|
231
|
-
// Later, update without recreating DOM
|
|
232
|
-
rendered.update({ name: 'Jane', score: 100 });
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
## Service Architecture
|
|
236
|
-
|
|
237
|
-
```
|
|
238
|
-
┌────────────────────────────────────────────────────────┐
|
|
239
|
-
│ ServiceCollection │
|
|
240
|
-
│ │
|
|
241
|
-
│ Register services with metadata: │
|
|
242
|
-
│ - Constructor dependencies │
|
|
243
|
-
│ - Property injections │
|
|
244
|
-
│ - Scope (global/closest) │
|
|
245
|
-
└───────────────────────────┬────────────────────────────┘
|
|
246
|
-
│
|
|
247
|
-
│ Configuration
|
|
248
|
-
▼
|
|
249
|
-
┌────────────────────────────────────────────────────────┐
|
|
250
|
-
│ ServiceContainer │
|
|
251
|
-
│ │
|
|
252
|
-
│ Resolve services: │
|
|
253
|
-
│ 1. Check cache (for singletons) │
|
|
254
|
-
│ 2. Resolve dependencies │
|
|
255
|
-
│ 3. Create instance │
|
|
256
|
-
│ 4. Inject properties │
|
|
257
|
-
│ 5. Cache if global scope │
|
|
258
|
-
└────────────────────────────────────────────────────────┘
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
## File Organization
|
|
262
|
-
|
|
263
|
-
Recommended project structure:
|
|
264
|
-
|
|
265
|
-
```
|
|
266
|
-
src/
|
|
267
|
-
├── components/
|
|
268
|
-
│ ├── forms/ # Form-related components
|
|
269
|
-
│ ├── lists/ # List/table components
|
|
270
|
-
│ └── shared/ # Shared UI components
|
|
271
|
-
├── pages/ # Route components
|
|
272
|
-
├── services/ # Business logic services
|
|
273
|
-
├── guards/ # Route guards
|
|
274
|
-
├── models/ # TypeScript interfaces/types
|
|
275
|
-
└── app.ts # Application entry point
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
## Best Practices
|
|
279
|
-
|
|
280
|
-
1. **Use native HTML when possible**: Don't create components for things HTML already does well
|
|
281
|
-
|
|
282
|
-
2. **Keep components small**: Each component should do one thing
|
|
283
|
-
|
|
284
|
-
3. **Form Components Use FORM API**: When building custom form components, use the HTML Form API with `ElementInternals` and `formAssociated = true`. All RelaxJS form utilities automatically support these components without any special handling:
|
|
285
|
-
|
|
286
|
-
```typescript
|
|
287
|
-
class CustomCheckbox extends HTMLElement {
|
|
288
|
-
static formAssociated = true;
|
|
289
|
-
|
|
290
|
-
private internals: ElementInternals;
|
|
291
|
-
|
|
292
|
-
constructor() {
|
|
293
|
-
super();
|
|
294
|
-
this.internals = this.attachInternals();
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
connectedCallback() {
|
|
298
|
-
const checkbox = document.createElement('input');
|
|
299
|
-
checkbox.type = 'checkbox';
|
|
300
|
-
this.appendChild(checkbox);
|
|
301
|
-
|
|
302
|
-
checkbox.addEventListener('change', () => {
|
|
303
|
-
this.internals.setFormValue(checkbox.checked ? 'on' : '');
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
get checked() { return (this.querySelector('input') as HTMLInputElement).checked; }
|
|
308
|
-
set checked(v) { (this.querySelector('input') as HTMLInputElement).checked = v; }
|
|
309
|
-
|
|
310
|
-
get value() { return this.getAttribute('value') || 'on'; }
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
customElements.define('r-checkbox', CustomCheckbox);
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
Once defined, use it seamlessly with form utilities:
|
|
317
|
-
|
|
318
|
-
```typescript
|
|
319
|
-
const form = document.querySelector('form');
|
|
320
|
-
const data = { termsAccepted: true };
|
|
321
|
-
setFormData(form, data); // Works with <r-checkbox name="termsAccepted"></r-checkbox>
|
|
322
|
-
|
|
323
|
-
const extracted = readData(form); // Extracts values from custom components
|
|
324
|
-
const validator = new FormValidator(form); // Validates custom components
|
|
325
|
-
```
|
|
326
|
-
|
|
327
|
-
3. **Explicit over implicit**: Prefer explicit method calls over magic bindings
|
|
328
|
-
|
|
329
|
-
4. **Type everything**: Use TypeScript interfaces for all data structures
|
|
330
|
-
|
|
331
|
-
5. **CSS variables for theming**: Use semantic variable names
|
|
332
|
-
|
|
333
|
-
6. **Form-associated components**: Use `ElementInternals` for custom form controls
|
|
@@ -1,277 +0,0 @@
|
|
|
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 '@relax.js/core/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 '@relax.js/core/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
|
-
The service is resolved when the instance is created, so the field is available
|
|
123
|
-
in all methods including `connectedCallback` for web components.
|
|
124
|
-
|
|
125
|
-
```typescript
|
|
126
|
-
import { ContainerService, Inject } from '@relax.js/core/di';
|
|
127
|
-
|
|
128
|
-
@ContainerService({ inject: [] })
|
|
129
|
-
class OrderService {
|
|
130
|
-
@Inject(LoggerService)
|
|
131
|
-
private logger!: LoggerService;
|
|
132
|
-
|
|
133
|
-
@Inject('primaryCache') // resolve by string key
|
|
134
|
-
private cache!: CacheService;
|
|
135
|
-
}
|
|
136
|
-
```
|
|
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
|
-
## Using Services in Web Components
|
|
157
|
-
|
|
158
|
-
Web components use `@Inject` to access services. Do not use `@ContainerService` on
|
|
159
|
-
components since the browser creates them with zero constructor arguments.
|
|
160
|
-
|
|
161
|
-
### Property Injection with @Inject
|
|
162
|
-
|
|
163
|
-
```typescript
|
|
164
|
-
import { Inject } from '@relax.js/core/di';
|
|
165
|
-
|
|
166
|
-
class UserPanel extends HTMLElement {
|
|
167
|
-
@Inject(UserService)
|
|
168
|
-
private userService!: UserService;
|
|
169
|
-
|
|
170
|
-
@Inject(LoggerService)
|
|
171
|
-
private logger!: LoggerService;
|
|
172
|
-
|
|
173
|
-
connectedCallback() {
|
|
174
|
-
// Injected fields are resolved and ready to use
|
|
175
|
-
const user = this.userService.getCurrentUser();
|
|
176
|
-
this.logger.log('UserPanel connected');
|
|
177
|
-
this.render(user);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
customElements.define('user-panel', UserPanel);
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
This works the same way regardless of how the component is created:
|
|
185
|
-
|
|
186
|
-
```typescript
|
|
187
|
-
// Created by application code
|
|
188
|
-
document.createElement('user-panel');
|
|
189
|
-
|
|
190
|
-
// Created by the browser from HTML
|
|
191
|
-
// <user-panel></user-panel>
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
### Setup Order
|
|
195
|
-
|
|
196
|
-
Register all services before defining custom elements. Services
|
|
197
|
-
decorated with `@ContainerService` register themselves when imported,
|
|
198
|
-
so import those modules first.
|
|
199
|
-
|
|
200
|
-
```typescript
|
|
201
|
-
// main.ts - application entry point
|
|
202
|
-
|
|
203
|
-
// 1. Import services (registers them via @ContainerService)
|
|
204
|
-
import './services/ApiClient';
|
|
205
|
-
import './services/UserService';
|
|
206
|
-
|
|
207
|
-
// 2. Register any manual services
|
|
208
|
-
serviceCollection.register(ConfigService, {
|
|
209
|
-
inject: [],
|
|
210
|
-
instance: { apiUrl: '/api/v1' },
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
// 3. Define custom elements (components can now resolve services)
|
|
214
|
-
import { UserPanel } from './components/UserPanel';
|
|
215
|
-
customElements.define('user-panel', UserPanel);
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
## Resolving Dependencies
|
|
219
|
-
|
|
220
|
-
```typescript
|
|
221
|
-
// By class
|
|
222
|
-
const service = container.resolve(MyService);
|
|
223
|
-
|
|
224
|
-
// By string key
|
|
225
|
-
const cache = container.resolve<CacheService>('primaryCache');
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
## Error Handling
|
|
229
|
-
|
|
230
|
-
All DI errors go through the global [`onError`](Errors.md) handler, so you can log, display, or suppress them.
|
|
231
|
-
|
|
232
|
-
### Resolution Errors
|
|
233
|
-
|
|
234
|
-
Thrown when `resolve()` cannot find a registered service.
|
|
235
|
-
|
|
236
|
-
| Field | Description |
|
|
237
|
-
|-------|-------------|
|
|
238
|
-
| `service` | The class name or string key that was requested |
|
|
239
|
-
| `registeredTypes` | Array of all registered class names |
|
|
240
|
-
| `registeredKeys` | Array of all registered string keys |
|
|
241
|
-
|
|
242
|
-
```
|
|
243
|
-
RelaxError: Failed to resolve service 'MyService'
|
|
244
|
-
{ service: 'MyService', registeredTypes: ['LoggerService', 'ConfigService'], registeredKeys: ['primaryCache'] }
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
### Registration Errors
|
|
248
|
-
|
|
249
|
-
Thrown during `register()` or `registerByType()` to catch configuration mistakes early.
|
|
250
|
-
|
|
251
|
-
| Error | Context | Cause |
|
|
252
|
-
|-------|---------|-------|
|
|
253
|
-
| Service name collision | `{ service }` | Two different classes registered with the same class name |
|
|
254
|
-
| Service key already registered | `{ key, existingClass, newClass }` | A string key is reused for a different class |
|
|
255
|
-
| Instance and inject conflict | `{ service }` | Both `instance` and non-empty `inject` provided (`inject` is ignored) |
|
|
256
|
-
|
|
257
|
-
### Suppressing Errors
|
|
258
|
-
|
|
259
|
-
```typescript
|
|
260
|
-
import { onError } from '@relax.js/core/utils';
|
|
261
|
-
|
|
262
|
-
onError((error, ctx) => {
|
|
263
|
-
if (error.context.service === 'OptionalPlugin') {
|
|
264
|
-
ctx.suppress();
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
console.error(error.message, error.context);
|
|
268
|
-
});
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
## Best Practices
|
|
272
|
-
|
|
273
|
-
1. **Register early**: Register all services at application startup
|
|
274
|
-
2. **Prefer constructor injection**: More explicit than property injection
|
|
275
|
-
3. **Use global scope for stateless services**: Like loggers, config
|
|
276
|
-
4. **Use closest scope for request-specific data**: Like user context
|
|
277
|
-
5. **Avoid circular dependencies**: Restructure to use events or mediators
|