@relax.js/core 1.0.2 → 1.0.4

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.
Files changed (240) hide show
  1. package/README.md +194 -188
  2. package/dist/DependencyInjection.d.ts +42 -24
  3. package/dist/collections/Index.d.ts +2 -0
  4. package/dist/collections/index.js +1 -1
  5. package/dist/collections/index.js.map +4 -4
  6. package/dist/collections/index.mjs +1 -1
  7. package/dist/collections/index.mjs.map +4 -4
  8. package/dist/di/index.js +1 -1
  9. package/dist/di/index.js.map +3 -3
  10. package/dist/di/index.mjs +1 -1
  11. package/dist/di/index.mjs.map +3 -3
  12. package/dist/errors.d.ts +20 -0
  13. package/dist/forms/FormValidator.d.ts +1 -20
  14. package/dist/forms/ValidationRules.d.ts +2 -0
  15. package/dist/forms/index.js +1 -1
  16. package/dist/forms/index.js.map +4 -4
  17. package/dist/forms/index.mjs +1 -1
  18. package/dist/forms/index.mjs.map +4 -4
  19. package/dist/html/TableRenderer.d.ts +1 -0
  20. package/dist/html/index.js.map +2 -2
  21. package/dist/html/index.mjs.map +2 -2
  22. package/dist/html/template.d.ts +4 -0
  23. package/dist/http/http.d.ts +1 -0
  24. package/dist/http/index.js.map +2 -2
  25. package/dist/http/index.mjs.map +2 -2
  26. package/dist/index.d.ts +0 -2
  27. package/dist/index.js +3 -3
  28. package/dist/index.js.map +4 -4
  29. package/dist/index.mjs +3 -3
  30. package/dist/index.mjs.map +4 -4
  31. package/dist/routing/index.js +3 -3
  32. package/dist/routing/index.js.map +3 -3
  33. package/dist/routing/index.mjs +3 -3
  34. package/dist/routing/index.mjs.map +3 -3
  35. package/dist/routing/routeTargetRegistry.d.ts +1 -0
  36. package/dist/routing/types.d.ts +2 -1
  37. package/dist/templates/NodeTemplate.d.ts +2 -0
  38. package/dist/utils/index.js +1 -1
  39. package/dist/utils/index.js.map +2 -2
  40. package/dist/utils/index.mjs +1 -1
  41. package/dist/utils/index.mjs.map +2 -2
  42. package/docs/Architecture.md +333 -333
  43. package/docs/DependencyInjection.md +277 -237
  44. package/docs/Errors.md +87 -87
  45. package/docs/GettingStarted.md +231 -231
  46. package/docs/Pipes.md +5 -5
  47. package/docs/Translations.md +167 -312
  48. package/docs/WhyRelaxjs.md +336 -336
  49. package/docs/api/.nojekyll +1 -0
  50. package/docs/api/assets/hierarchy.js +1 -0
  51. package/docs/api/assets/highlight.css +120 -0
  52. package/docs/api/assets/icons.js +18 -0
  53. package/docs/api/assets/icons.svg +1 -0
  54. package/docs/api/assets/main.js +60 -0
  55. package/docs/api/assets/navigation.js +1 -0
  56. package/docs/api/assets/search.js +1 -0
  57. package/docs/api/assets/style.css +1633 -0
  58. package/docs/api/classes/http.WebSocketClient.html +26 -0
  59. package/docs/api/classes/i18n.LocaleChangeEvent.html +66 -0
  60. package/docs/api/classes/index.Blueprint.html +3 -0
  61. package/docs/api/classes/index.BoundNode.html +3 -0
  62. package/docs/api/classes/index.DigitsValidation.html +10 -0
  63. package/docs/api/classes/index.FormValidator.html +32 -0
  64. package/docs/api/classes/index.HttpError.html +13 -0
  65. package/docs/api/classes/index.LinkedList.html +26 -0
  66. package/docs/api/classes/index.NavigateRouteEvent.html +76 -0
  67. package/docs/api/classes/index.Node.html +15 -0
  68. package/docs/api/classes/index.PageSelectedEvent.html +61 -0
  69. package/docs/api/classes/index.Pager.html +4 -0
  70. package/docs/api/classes/index.RangeValidation.html +15 -0
  71. package/docs/api/classes/index.RelaxError.html +17 -0
  72. package/docs/api/classes/index.RequiredValidation.html +10 -0
  73. package/docs/api/classes/index.RouteError.html +11 -0
  74. package/docs/api/classes/index.RouteGuardError.html +12 -0
  75. package/docs/api/classes/index.RouteLink.html +779 -0
  76. package/docs/api/classes/index.RouteTarget.html +788 -0
  77. package/docs/api/classes/index.SSEClient.html +13 -0
  78. package/docs/api/classes/index.SSEDataEvent.html +63 -0
  79. package/docs/api/classes/index.ServiceCollection.html +28 -0
  80. package/docs/api/classes/index.ServiceContainer.html +24 -0
  81. package/docs/api/classes/index.SortChangeEvent.html +61 -0
  82. package/docs/api/classes/index.TableRenderer.html +5 -0
  83. package/docs/api/classes/index.TableSorter.html +4 -0
  84. package/docs/api/enums/index.GuardResult.html +9 -0
  85. package/docs/api/functions/elements.formError.html +6 -0
  86. package/docs/api/functions/elements.selectOne.html +6 -0
  87. package/docs/api/functions/i18n.getCurrentLocale.html +3 -0
  88. package/docs/api/functions/i18n.loadNamespace.html +7 -0
  89. package/docs/api/functions/i18n.loadNamespaces.html +6 -0
  90. package/docs/api/functions/i18n.onMissingTranslation.html +7 -0
  91. package/docs/api/functions/i18n.setLocale.html +7 -0
  92. package/docs/api/functions/i18n.setMessageFormatter.html +7 -0
  93. package/docs/api/functions/i18n.t.html +9 -0
  94. package/docs/api/functions/index.BooleanConverter.html +6 -0
  95. package/docs/api/functions/index.ContainerService.html +13 -0
  96. package/docs/api/functions/index.DateConverter.html +11 -0
  97. package/docs/api/functions/index.Inject.html +16 -0
  98. package/docs/api/functions/index.NumberConverter.html +5 -0
  99. package/docs/api/functions/index.RegisterValidator.html +7 -0
  100. package/docs/api/functions/index.applyPipes.html +17 -0
  101. package/docs/api/functions/index.asyncHandler.html +11 -0
  102. package/docs/api/functions/index.capitalizePipe.html +4 -0
  103. package/docs/api/functions/index.clearPendingNavigations.html +1 -0
  104. package/docs/api/functions/index.compileTemplate.html +26 -0
  105. package/docs/api/functions/index.configure.html +5 -0
  106. package/docs/api/functions/index.createBluePrint.html +1 -0
  107. package/docs/api/functions/index.createConverterFromDataType.html +4 -0
  108. package/docs/api/functions/index.createConverterFromInputType.html +5 -0
  109. package/docs/api/functions/index.createPipeRegistry.html +12 -0
  110. package/docs/api/functions/index.currencyPipe.html +9 -0
  111. package/docs/api/functions/index.datePipe.html +9 -0
  112. package/docs/api/functions/index.daysAgoPipe.html +8 -0
  113. package/docs/api/functions/index.defaultPipe.html +5 -0
  114. package/docs/api/functions/index.defineRoutes.html +8 -0
  115. package/docs/api/functions/index.del.html +8 -0
  116. package/docs/api/functions/index.findRouteByName.html +5 -0
  117. package/docs/api/functions/index.findRouteByUrl.html +4 -0
  118. package/docs/api/functions/index.firstPipe.html +4 -0
  119. package/docs/api/functions/index.generateSequentialId.html +21 -0
  120. package/docs/api/functions/index.get.html +9 -0
  121. package/docs/api/functions/index.getDataConverter.html +11 -0
  122. package/docs/api/functions/index.getParentComponent.html +18 -0
  123. package/docs/api/functions/index.getValidator.html +4 -0
  124. package/docs/api/functions/index.html.html +19 -0
  125. package/docs/api/functions/index.joinPipe.html +5 -0
  126. package/docs/api/functions/index.keysPipe.html +4 -0
  127. package/docs/api/functions/index.lastPipe.html +4 -0
  128. package/docs/api/functions/index.lowercasePipe.html +4 -0
  129. package/docs/api/functions/index.mapFormToClass.html +17 -0
  130. package/docs/api/functions/index.matchRoute.html +5 -0
  131. package/docs/api/functions/index.navigate.html +8 -0
  132. package/docs/api/functions/index.onError.html +8 -0
  133. package/docs/api/functions/index.piecesPipe.html +8 -0
  134. package/docs/api/functions/index.post.html +9 -0
  135. package/docs/api/functions/index.printRoutes.html +2 -0
  136. package/docs/api/functions/index.put.html +9 -0
  137. package/docs/api/functions/index.readData.html +17 -0
  138. package/docs/api/functions/index.registerRouteTarget.html +9 -0
  139. package/docs/api/functions/index.reportError.html +10 -0
  140. package/docs/api/functions/index.request.html +8 -0
  141. package/docs/api/functions/index.resolveValue.html +18 -0
  142. package/docs/api/functions/index.setFetch.html +6 -0
  143. package/docs/api/functions/index.setFormData.html +17 -0
  144. package/docs/api/functions/index.shortenPipe.html +5 -0
  145. package/docs/api/functions/index.startRouting.html +6 -0
  146. package/docs/api/functions/index.ternaryPipe.html +6 -0
  147. package/docs/api/functions/index.trimPipe.html +4 -0
  148. package/docs/api/functions/index.unregisterRouteTarget.html +3 -0
  149. package/docs/api/functions/index.uppercasePipe.html +4 -0
  150. package/docs/api/hierarchy.html +1 -0
  151. package/docs/api/index.html +323 -0
  152. package/docs/api/interfaces/http.SimpleDataEvent.html +3 -0
  153. package/docs/api/interfaces/http.WebSocketAbstraction.html +9 -0
  154. package/docs/api/interfaces/http.WebSocketCodec.html +4 -0
  155. package/docs/api/interfaces/http.WebSocketOptions.html +20 -0
  156. package/docs/api/interfaces/index.CompiledTemplate.html +10 -0
  157. package/docs/api/interfaces/index.DataLoader.html +19 -0
  158. package/docs/api/interfaces/index.EngineConfig.html +11 -0
  159. package/docs/api/interfaces/index.ErrorContext.html +4 -0
  160. package/docs/api/interfaces/index.FormReaderOptions.html +8 -0
  161. package/docs/api/interfaces/index.HttpOptions.html +16 -0
  162. package/docs/api/interfaces/index.HttpResponse.html +17 -0
  163. package/docs/api/interfaces/index.LoadRoute.html +7 -0
  164. package/docs/api/interfaces/index.NavigateOptions.html +7 -0
  165. package/docs/api/interfaces/index.PipeRegistry.html +12 -0
  166. package/docs/api/interfaces/index.RegistrationOptions.html +22 -0
  167. package/docs/api/interfaces/index.RenderTemplate.html +7 -0
  168. package/docs/api/interfaces/index.RequestOptions.html +11 -0
  169. package/docs/api/interfaces/index.Routable.html +10 -0
  170. package/docs/api/interfaces/index.Route.html +13 -0
  171. package/docs/api/interfaces/index.RouteGuard.html +2 -0
  172. package/docs/api/interfaces/index.RouteValue.html +6 -0
  173. package/docs/api/interfaces/index.SSEOptions.html +24 -0
  174. package/docs/api/interfaces/index.ValidationContext.html +8 -0
  175. package/docs/api/interfaces/index.ValidatorOptions.html +14 -0
  176. package/docs/api/media/Architecture.md +333 -0
  177. package/docs/api/media/DependencyInjection.md +277 -0
  178. package/docs/api/media/GettingStarted.md +231 -0
  179. package/docs/api/media/HttpClient.md +459 -0
  180. package/docs/api/media/Pipes.md +211 -0
  181. package/docs/api/media/Routing.md +332 -0
  182. package/docs/api/media/WhyRelaxjs.md +336 -0
  183. package/docs/api/media/forms.md +99 -0
  184. package/docs/api/media/html.md +175 -0
  185. package/docs/api/media/i18n.md +354 -0
  186. package/docs/api/media/utilities.md +143 -0
  187. package/docs/api/media/validation.md +351 -0
  188. package/docs/api/modules/collections_Index.html +1 -0
  189. package/docs/api/modules/di.html +1 -0
  190. package/docs/api/modules/elements.html +1 -0
  191. package/docs/api/modules/forms.html +1 -0
  192. package/docs/api/modules/html.html +1 -0
  193. package/docs/api/modules/http.html +1 -0
  194. package/docs/api/modules/i18n.html +1 -0
  195. package/docs/api/modules/index.html +1 -0
  196. package/docs/api/modules/routing.html +1 -0
  197. package/docs/api/modules/utils.html +1 -0
  198. package/docs/api/modules.html +1 -0
  199. package/docs/api/types/http.WebSocketFactory.html +2 -0
  200. package/docs/api/types/i18n.MessageFormatter.html +3 -0
  201. package/docs/api/types/i18n.MissingTranslationHandler.html +1 -0
  202. package/docs/api/types/index.Constructor.html +7 -0
  203. package/docs/api/types/index.ConverterFunc.html +2 -0
  204. package/docs/api/types/index.DataType.html +2 -0
  205. package/docs/api/types/index.InputType.html +2 -0
  206. package/docs/api/types/index.PipeFunction.html +6 -0
  207. package/docs/api/types/index.RouteData.html +1 -0
  208. package/docs/api/types/index.RouteMatchResult.html +9 -0
  209. package/docs/api/types/index.RouteParamType.html +1 -0
  210. package/docs/api/types/index.RouteSegmentType.html +2 -0
  211. package/docs/api/types/index.SSEEventFactory.html +5 -0
  212. package/docs/api/types/index.ServiceScope.html +10 -0
  213. package/docs/api/types/index.SortColumn.html +3 -0
  214. package/docs/api/variables/i18n.formatICU.html +3 -0
  215. package/docs/api/variables/index.container.html +6 -0
  216. package/docs/api/variables/index.defaultPipes.html +6 -0
  217. package/docs/api/variables/index.internalRoutes.html +1 -0
  218. package/docs/api/variables/index.serviceCollection.html +6 -0
  219. package/docs/api.json +93171 -0
  220. package/docs/elements/dom.md +102 -102
  221. package/docs/forms/creating-form-components.md +924 -924
  222. package/docs/forms/form-api.md +94 -94
  223. package/docs/forms/forms.md +99 -99
  224. package/docs/forms/patterns.md +311 -311
  225. package/docs/forms/reading-writing.md +365 -365
  226. package/docs/forms/validation.md +351 -351
  227. package/docs/html/TableRenderer.md +291 -291
  228. package/docs/html/html.md +175 -175
  229. package/docs/html/index.md +54 -54
  230. package/docs/html/template.md +422 -422
  231. package/docs/http/HttpClient.md +459 -459
  232. package/docs/http/ServerSentEvents.md +184 -184
  233. package/docs/http/index.md +109 -109
  234. package/docs/i18n/i18n.md +49 -4
  235. package/docs/i18n/intl-standard.md +178 -178
  236. package/docs/routing/RouteLink.md +98 -98
  237. package/docs/routing/Routing.md +332 -332
  238. package/docs/routing/layouts.md +207 -207
  239. package/docs/utilities.md +143 -143
  240. package/package.json +4 -3
package/README.md CHANGED
@@ -1,188 +1,194 @@
1
- # Relaxjs
2
-
3
- **Ship faster with less code.**
4
-
5
- Web Component library with routing, forms, DI, templating, and i18n. No virtual DOM, no build magic, no surprise re-renders.
6
-
7
- ## Why Relaxjs?
8
-
9
- Modern frameworks solve problems you might not have. Relaxjs takes the opposite approach:
10
-
11
- | Framework Approach | Relaxjs Approach |
12
- |-------------------|------------------|
13
- | Virtual DOM diffing | Direct DOM manipulation |
14
- | Reactive state management | Explicit updates |
15
- | Custom template syntax | Standard HTML |
16
- | Framework-specific lifecycle | Native Web Component lifecycle |
17
- | Magic re-renders | You control what updates |
18
- | Custom rendering pipeline | Native async/await everywhere |
19
-
20
- > [Read the detailed comparison](docs/WhyRelaxjs.md)
21
-
22
- **The result:** You always know *when* something ran, *why* it ran, and *what* triggered it.
23
-
24
- ## What Relaxjs Adds
25
-
26
- Web Components give you encapsulation and lifecycle hooks. Relaxjs fills the gaps:
27
-
28
- | Vanilla Web Components | With Relaxjs |
29
- |------------------------|--------------|
30
- | Manual form serialization | `readData(form)` returns typed objects (send it directly to backend) |
31
- | Query string parsing | Named routes with typed parameters |
32
- | DIY validation logic | `FormValidator` with HTML5 integration |
33
- | No component library | Table, Tabs, TreeView, Menu ready to use |
34
- | Manual service wiring | Decorator-based dependency injection |
35
- | Raw fetch boilerplate | Simple HTTP client for backend calls |
36
-
37
- You write less boilerplate while keeping full control.
38
-
39
- ## Installation
40
-
41
- ```bash
42
- npm install relaxjs
43
- ```
44
-
45
- ## Quick Examples
46
-
47
- ### Form Handling
48
-
49
- Read and write form data with automatic type conversion:
50
-
51
- ```typescript
52
- import { setFormData, readData } from 'relaxjs';
53
-
54
- // Populate a form from an object
55
- const user = { name: 'John', email: 'john@example.com', age: 30 };
56
- setFormData(form, user);
57
-
58
- // Read form data back (with types!)
59
- const data = readData(form);
60
- // { name: 'John', email: 'john@example.com', age: 30 }
61
- ```
62
-
63
- Supports nested objects (`user.address.city`), arrays (`tags[]`), and automatic type conversion for numbers, booleans, and dates.
64
-
65
- > [Form utilities docs](docs/forms/forms.md)
66
-
67
- ### Client-Side Routing
68
-
69
- Define routes and let the router handle navigation:
70
-
71
- ```typescript
72
- import { defineRoutes, navigate, startRouting } from 'relaxjs';
73
-
74
- defineRoutes([
75
- { name: 'home', path: '/', componentTagName: 'app-home' },
76
- { name: 'user', path: '/users/:id', componentTagName: 'app-user' },
77
- { name: 'settings', path: '/settings', componentTagName: 'app-settings' }
78
- ]);
79
-
80
- startRouting();
81
-
82
- // Navigate programmatically
83
- navigate('user', { params: { id: '123' } });
84
- ```
85
-
86
- ```html
87
- <r-route-target></r-route-target>
88
- ```
89
-
90
- > [Routing docs](docs/routing/Routing.md)
91
-
92
- ### Form Validation
93
-
94
- HTML5-style validation with custom rules and error summaries:
95
-
96
- ```typescript
97
- import { FormValidator } from 'relaxjs';
98
-
99
- const validator = new FormValidator(form, {
100
- useSummary: true,
101
- submitCallback: () => saveData()
102
- });
103
- ```
104
-
105
- > [Validation docs](docs/forms/validation.md)
106
-
107
- ## What's Included
108
-
109
- | Feature | Description |
110
- |---------|-------------|
111
- | **Form Utilities** | Read/write forms, type conversion, validation |
112
- | **Routing** | Named routes, typed parameters, guards, layouts, multiple targets |
113
- | **HTML Templates** | `html` tagged templates with data binding and in-place updates |
114
- | **HTTP Client** | Type-safe `get`/`post`/`put`/`del` with automatic JWT |
115
- | **WebSocket** | Auto-reconnect, message queuing, typed messages |
116
- | **SSE** | Server-Sent Events dispatched as DOM events |
117
- | **Dependency Injection** | Decorator-based DI with constructor and property injection |
118
- | **i18n** | ICU message format, pluralization, locale-aware formatting |
119
- | **Pipes** | 15 built-in data transformations for templates |
120
-
121
- ## Where Relaxjs Fits
122
-
123
- Relaxjs is a good choice when:
124
-
125
- - You're building a **small-to-medium SPA** where you want direct control over the DOM
126
- - Your team prefers **vanilla Web Components** over framework abstractions
127
- - You want **gradual adoption** - use only the parts you need, no all-or-nothing buy-in
128
- - **Bundle size and simplicity** matter more than ecosystem breadth
129
- - You want to **understand what your code does** - no hidden re-renders, no magic proxies, no compiler transforms
130
-
131
- ## Where Relaxjs Doesn't Fit
132
-
133
- Be honest with yourself - Relaxjs is not the right tool for everything:
134
-
135
- - **Large-scale apps with complex state** - Relaxjs has no reactive state management, no global store, no computed properties. If your UI has dozens of interdependent data flows, you'll be writing a lot of manual update logic.
136
- - **Server-side rendering / static site generation** - Relaxjs is client-only. If you need SEO, fast first-paint from the server, or pre-rendered pages, look at Next.js, Nuxt, or SvelteKit.
137
- - **Big teams that need a large talent pool** - React and Angular developers are everywhere. Finding developers who know Relaxjs (or are willing to learn a small library) is harder.
138
- - **Rich ecosystem needs** - There's no component marketplace, no DevTools extension, no community middleware. You build what you need or use vanilla JS libraries.
139
- - **Mobile / native targets** - No React Native equivalent, no Ionic integration. Relaxjs is for the browser.
140
-
141
- ## How It Compares
142
-
143
- | | Relaxjs | React | Angular | Vue | Svelte |
144
- |---|---|---|---|---|---|
145
- | **Approach** | Web Components | Virtual DOM | Full framework | Virtual DOM | Compiler |
146
- | **Bundle size** | ~20KB gzipped | ~45KB | ~150KB+ | ~33KB | ~2KB runtime |
147
- | **Learning curve** | Low (vanilla TS) | Medium | High | Medium | Low-Medium |
148
- | **State management** | Explicit DOM updates | Hooks / Redux / Zustand | RxJS / Signals | Reactive refs | Stores / runes |
149
- | **Routing** | Built-in (simple) | react-router (separate) | Built-in (full) | vue-router (separate) | SvelteKit |
150
- | **Forms** | Built-in (HTML5 native) | Controlled / uncontrolled | Reactive forms | v-model | bind: |
151
- | **SSR** | No | Yes | Yes | Yes | Yes |
152
- | **Ecosystem** | Small | Massive | Large | Large | Growing |
153
- | **DI** | Built-in | None (Context API) | Built-in | Provide / Inject | None |
154
- | **i18n** | Built-in (ICU) | i18next etc. | Built-in | vue-i18n | i18next etc. |
155
- | **DevTools** | Browser DevTools | React DevTools | Angular DevTools | Vue DevTools | Svelte DevTools |
156
- | **Community size** | Small | Very large | Large | Large | Medium |
157
-
158
- ## Philosophy
159
-
160
- This isn't a framework - it's a library. Use what you need:
161
-
162
- - Need just form handling? Import `setFormData` and `readData`.
163
- - Need routing? Add `defineRoutes` and `r-route-target`.
164
- - Need everything? It's all there.
165
-
166
- No buy-in required. No migration path to worry about. Just Web Components and TypeScript.
167
-
168
- ## Documentation
169
-
170
- - [Why Relaxjs?](docs/WhyRelaxjs.md) - Detailed comparison with frameworks
171
- - [Getting Started](docs/GettingStarted.md) - Progressive adoption guide (7 levels)
172
- - [Architecture](docs/Architecture.md)
173
- - [Form Utilities](docs/forms/forms.md) - Validation, reading/writing, custom form components
174
- - [Routing](docs/routing/Routing.md) - Routes, guards, layouts, navigation
175
- - [HTML Templates](docs/html/html.md) - Tagged templates with data binding
176
- - [HTTP & WebSocket](docs/http/HttpClient.md) - REST calls, WebSocket, SSE
177
- - [Dependency Injection](docs/DependencyInjection.md)
178
- - [i18n](docs/i18n/i18n.md) - Translations, ICU format, locale switching
179
- - [Pipes](docs/Pipes.md) - Data transformations for templates
180
- - [Utilities](docs/utilities.md) - Sequential IDs, LinkedList, helpers
181
-
182
- ## Browser Support
183
-
184
- Works in all browsers that support Web Components (Chrome, Firefox, Safari, Edge).
185
-
186
- ## License
187
-
188
- ISC
1
+ # Relaxjs
2
+
3
+ **Ship faster with less code.**
4
+
5
+ Web Component library with routing, forms, DI, templating, and i18n. No virtual DOM, no build magic, no surprise re-renders.
6
+
7
+ - ~20KB gzipped, one dependency
8
+ - Native Web Components, zero vendor lock-in
9
+ - Use only what you need: forms, routing, DI, i18n are all independent
10
+ - No build step required, no compiler, no CLI
11
+ - Standard HTML, standard DOM, standard async/await
12
+
13
+ ## Why Relaxjs?
14
+
15
+ Modern frameworks solve problems you might not have. Relaxjs takes the opposite approach:
16
+
17
+ | Framework Approach | Relaxjs Approach |
18
+ |-------------------|------------------|
19
+ | Virtual DOM diffing | Direct DOM manipulation |
20
+ | Reactive state management | Explicit updates |
21
+ | Custom template syntax | Standard HTML |
22
+ | Framework-specific lifecycle | Native Web Component lifecycle |
23
+ | Magic re-renders | You control what updates |
24
+ | Custom rendering pipeline | Native async/await everywhere |
25
+
26
+ > [Read the detailed comparison](docs/WhyRelaxjs.md)
27
+
28
+ **The result:** You always know *when* something ran, *why* it ran, and *what* triggered it.
29
+
30
+ ## What Relaxjs Adds
31
+
32
+ Web Components give you encapsulation and lifecycle hooks. Relaxjs fills the gaps:
33
+
34
+ | Vanilla Web Components | With Relaxjs |
35
+ |------------------------|--------------|
36
+ | Manual form serialization | `readData(form)` returns typed objects (send it directly to backend) |
37
+ | Query string parsing | Named routes with typed parameters |
38
+ | DIY validation logic | `FormValidator` with HTML5 integration |
39
+ | No component library | Table, Tabs, TreeView, Menu ready to use |
40
+ | Manual service wiring | Decorator-based dependency injection |
41
+ | Raw fetch boilerplate | Simple HTTP client for backend calls |
42
+
43
+ You write less boilerplate while keeping full control.
44
+
45
+ ## Installation
46
+
47
+ ```bash
48
+ npm install @relax.js/core
49
+ ```
50
+
51
+ ## Quick Examples
52
+
53
+ ### Form Handling
54
+
55
+ Read and write form data with automatic type conversion:
56
+
57
+ ```typescript
58
+ import { setFormData, readData } from '@relax.js/core';
59
+
60
+ // Populate a form from an object
61
+ const user = { name: 'John', email: 'john@example.com', age: 30 };
62
+ setFormData(form, user);
63
+
64
+ // Read form data back (with types!)
65
+ const data = readData(form);
66
+ // { name: 'John', email: 'john@example.com', age: 30 }
67
+ ```
68
+
69
+ Supports nested objects (`user.address.city`), arrays (`tags[]`), and automatic type conversion for numbers, booleans, and dates.
70
+
71
+ > [Form utilities docs](docs/forms/forms.md)
72
+
73
+ ### Client-Side Routing
74
+
75
+ Define routes and let the router handle navigation:
76
+
77
+ ```typescript
78
+ import { defineRoutes, navigate, startRouting } from '@relax.js/core';
79
+
80
+ defineRoutes([
81
+ { name: 'home', path: '/', componentTagName: 'app-home' },
82
+ { name: 'user', path: '/users/:id', componentTagName: 'app-user' },
83
+ { name: 'settings', path: '/settings', componentTagName: 'app-settings' }
84
+ ]);
85
+
86
+ startRouting();
87
+
88
+ // Navigate programmatically
89
+ navigate('user', { params: { id: '123' } });
90
+ ```
91
+
92
+ ```html
93
+ <r-route-target></r-route-target>
94
+ ```
95
+
96
+ > [Routing docs](docs/routing/Routing.md)
97
+
98
+ ### Form Validation
99
+
100
+ HTML5-style validation with custom rules and error summaries:
101
+
102
+ ```typescript
103
+ import { FormValidator } from '@relax.js/core';
104
+
105
+ const validator = new FormValidator(form, {
106
+ useSummary: true,
107
+ submitCallback: () => saveData()
108
+ });
109
+ ```
110
+
111
+ > [Validation docs](docs/forms/validation.md)
112
+
113
+ ## What's Included
114
+
115
+ | Feature | Description |
116
+ |---------|-------------|
117
+ | **Form Utilities** | Read/write forms, type conversion, validation |
118
+ | **Routing** | Named routes, typed parameters, guards, layouts, multiple targets |
119
+ | **HTML Templates** | `html` tagged templates with data binding and in-place updates |
120
+ | **HTTP Client** | Type-safe `get`/`post`/`put`/`del` with automatic JWT |
121
+ | **WebSocket** | Auto-reconnect, message queuing, typed messages |
122
+ | **SSE** | Server-Sent Events dispatched as DOM events |
123
+ | **Dependency Injection** | Decorator-based DI with constructor and property injection |
124
+ | **i18n** | ICU message format, pluralization, locale-aware formatting |
125
+ | **Pipes** | 15 built-in data transformations for templates |
126
+
127
+ ## Where Relaxjs Fits
128
+
129
+ Relaxjs is a good choice when:
130
+
131
+ - You're building a **small-to-medium SPA** where you want direct control over the DOM
132
+ - Your team prefers **vanilla Web Components** over framework abstractions
133
+ - You want **gradual adoption** - use only the parts you need, no all-or-nothing buy-in
134
+ - **Bundle size and simplicity** matter more than ecosystem breadth
135
+ - You want to **understand what your code does** - no hidden re-renders, no magic proxies, no compiler transforms
136
+
137
+ ## Where Relaxjs Doesn't Fit
138
+
139
+ Relaxjs is not the right tool for everything:
140
+
141
+ - **Large-scale apps with complex state** - Relaxjs has no reactive state management, no global store, no computed properties. If your UI has dozens of interdependent data flows, you'll be writing a lot of manual update logic.
142
+ - **Server-side rendering / static site generation** - Relaxjs is client-only. If you need SEO, fast first-paint from the server, or pre-rendered pages, look at Next.js, Nuxt, or SvelteKit.
143
+ - **Big teams that need a large talent pool** - React and Angular developers are everywhere. Finding developers who know Relaxjs (or are willing to learn a small library) is harder.
144
+ - **Rich ecosystem needs** - There's no component marketplace, no DevTools extension, no community middleware. You build what you need or use vanilla JS libraries.
145
+ - **Mobile / native targets** - No React Native equivalent, no Ionic integration. Relaxjs is for the browser.
146
+
147
+ ## How It Compares
148
+
149
+ | | Relaxjs | React | Angular | Vue | Svelte |
150
+ |---|---|---|---|---|---|
151
+ | **Approach** | Web Components | Virtual DOM | Full framework | Virtual DOM | Compiler |
152
+ | **Bundle size** | ~20KB gzipped | ~45KB | ~150KB+ | ~33KB | ~2KB runtime |
153
+ | **Learning curve** | Low (vanilla TS) | Medium | High | Medium | Low-Medium |
154
+ | **State management** | Explicit DOM updates | Hooks / Redux / Zustand | RxJS / Signals | Reactive refs | Stores / runes |
155
+ | **Routing** | Built-in (simple) | react-router (separate) | Built-in (full) | vue-router (separate) | SvelteKit |
156
+ | **Forms** | Built-in (HTML5 native) | Controlled / uncontrolled | Reactive forms | v-model | bind: |
157
+ | **SSR** | No | Yes | Yes | Yes | Yes |
158
+ | **Ecosystem** | Small | Massive | Large | Large | Growing |
159
+ | **DI** | Built-in | None (Context API) | Built-in | Provide / Inject | None |
160
+ | **i18n** | Built-in (ICU) | i18next etc. | Built-in | vue-i18n | i18next etc. |
161
+ | **DevTools** | Browser DevTools | React DevTools | Angular DevTools | Vue DevTools | Svelte DevTools |
162
+ | **Community size** | Small | Very large | Large | Large | Medium |
163
+
164
+ ## Philosophy
165
+
166
+ This isn't a framework - it's a library. Use what you need:
167
+
168
+ - Need just form handling? Import `setFormData` and `readData`.
169
+ - Need routing? Add `defineRoutes` and `r-route-target`.
170
+ - Need everything? It's all there.
171
+
172
+ No buy-in required. No migration path to worry about.
173
+
174
+ ## Documentation
175
+
176
+ - [Why Relaxjs?](docs/WhyRelaxjs.md) - Detailed comparison with frameworks
177
+ - [Getting Started](docs/GettingStarted.md) - Progressive adoption guide (7 levels)
178
+ - [Architecture](docs/Architecture.md)
179
+ - [Form Utilities](docs/forms/forms.md) - Validation, reading/writing, custom form components
180
+ - [Routing](docs/routing/Routing.md) - Routes, guards, layouts, navigation
181
+ - [HTML Templates](docs/html/html.md) - Tagged templates with data binding
182
+ - [HTTP & WebSocket](docs/http/HttpClient.md) - REST calls, WebSocket, SSE
183
+ - [Dependency Injection](docs/DependencyInjection.md)
184
+ - [i18n](docs/i18n/i18n.md) - Translations, ICU format, locale switching
185
+ - [Pipes](docs/Pipes.md) - Data transformations for templates
186
+ - [Utilities](docs/utilities.md) - Sequential IDs, LinkedList, helpers
187
+
188
+ ## Browser Support
189
+
190
+ Works in all browsers that support Web Components (Chrome, Firefox, Safari, Edge).
191
+
192
+ ## License
193
+
194
+ MIT
@@ -65,42 +65,60 @@ export interface RegistrationOptions {
65
65
  properties?: Record<string, string | Constructor>;
66
66
  }
67
67
  /**
68
- * Field decorator that collects property injection configuration.
69
- * Updates or creates the properties mapping in registration options.
68
+ * Field decorator that injects a service from the global DI container.
69
+ * The service is resolved when the class instance is created (not at class definition time),
70
+ * so services must be registered before the first instance is created.
70
71
  *
71
- * @example
72
- * @ContainerService({
73
- * inject: [Database],
74
- * properties: {
75
- * logger: Logger, // Inject by type
76
- * audit: 'auditLogger' // Inject by key
77
- * }
78
- * })
79
- * class UserService {
80
- * @Inject(Logger)
81
- * private logger!: Logger;
72
+ * Works with web components regardless of how they are created:
73
+ * - By the browser (HTML parsing): services are resolved during construction
74
+ * - By application code (`document.createElement` or `new`): same behavior
75
+ * - Injected fields are available in `connectedCallback` and all lifecycle methods
82
76
  *
83
- * @Inject('auditLogger')
84
- * private audit!: Logger;
77
+ * @example
78
+ * // Using `@Inject` in a web component
79
+ * class UserPanel extends HTMLElement {
80
+ * @Inject(UserService)
81
+ * private userService!: UserService;
85
82
  *
86
- * constructor(db: Database) {}
83
+ * connectedCallback() {
84
+ * // userService is already resolved and ready to use
85
+ * const user = this.userService.getCurrentUser();
86
+ * this.render(user);
87
+ * }
87
88
  * }
89
+ *
90
+ * @example
91
+ * // Services must be registered before components are created.
92
+ * // In your app entry point (e.g. main.ts):
93
+ * serviceCollection.registerByType(UserService, { inject: [ApiClient] });
94
+ * serviceCollection.registerByType(ApiClient, { inject: [] });
95
+ *
96
+ * // Now components can be created (by browser or code)
97
+ * customElements.define('user-panel', UserPanel);
88
98
  */
89
99
  export declare function Inject<T extends object>(typeOrKey: Constructor<T> | string): (_: undefined, context: ClassFieldDecoratorContext) => (this: any) => T;
90
100
  /**
91
- * Class decorator that automatically registers a service in the global DI container.
92
- * Use this to declaratively register services without manual registration calls.
101
+ * Class decorator that registers a service in the global DI container.
102
+ * Registration happens at class definition time (when the module loads),
103
+ * so import the module before creating instances that depend on this service.
93
104
  *
94
- * Services are registered at module load time, so ensure this file is imported
95
- * before attempting to resolve the decorated service.
105
+ * For web components: use `@ContainerService` on services, not on the
106
+ * components themselves. Components use `@Inject` to consume services.
96
107
  *
97
108
  * @param options - Registration configuration including scope and dependencies
98
109
  *
99
110
  * @example
100
- * // Simple service with constructor injection
101
- * @ContainerService({ inject: [DatabaseConnection] })
102
- * class UserRepository {
103
- * constructor(private db: DatabaseConnection) {}
111
+ * // Register a service that components can inject
112
+ * @ContainerService({ inject: [ApiClient] })
113
+ * class UserService {
114
+ * constructor(private api: ApiClient) {}
115
+ * getCurrentUser() { return this.api.get('/user'); }
116
+ * }
117
+ *
118
+ * // Component consumes the service
119
+ * class UserPanel extends HTMLElement {
120
+ * @Inject(UserService)
121
+ * private userService!: UserService;
104
122
  * }
105
123
  *
106
124
  * @example
@@ -1 +1,3 @@
1
1
  export { LinkedList, Node } from "./LinkedList";
2
+ export { Pager, PageSelectedEvent } from "./Pager";
3
+ export { type DataLoader } from "./DataLoader";
@@ -1,2 +1,2 @@
1
- var h=Object.defineProperty;var a=Object.getOwnPropertyDescriptor;var _=Object.getOwnPropertyNames;var u=Object.prototype.hasOwnProperty;var o=(s,t)=>{for(var e in t)h(s,e,{get:t[e],enumerable:!0})},f=(s,t,e,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of _(t))!u.call(s,r)&&r!==e&&h(s,r,{get:()=>t[r],enumerable:!(n=a(t,r))||n.enumerable});return s};var v=s=>f(h({},"__esModule",{value:!0}),s);var d={};o(d,{LinkedList:()=>l,Node:()=>i});module.exports=v(d);var i=class{constructor(t,e){this.value=t;this.removeCallback=e;this.next=null;this.prev=null}remove(){this.prev.next=this.next,this.next.prev=this.prev,this.removeCallback()}},l=class{constructor(){this._length=0}addFirst(t){let e=new i(t,()=>this._length--);this._first?(e.next=this._first,this._first.prev=e,this._first=e):(this._first=e,this._last=this._first),this._length++}addLast(t){let e=new i(t,()=>this._length--);this._first?(e.prev=this._last,this._last.next=e,this._last=e):(this._first=e,this._last=this._first),this._length++}removeFirst(){if(!this.first)throw new Error("The list is empty.");let t=this._first.value;return this._first=this._first.next,this._length--,t}removeLast(){if(!this.last)throw new Error("The list is empty.");let t=this._last.value;return this._last=this._last.prev,this._length--,t}get length(){return this._length}get first(){return this._first}get firstValue(){return this._first?.value}get last(){return this._last}get lastValue(){return this._last?.value}};
1
+ var u=Object.defineProperty;var p=Object.getOwnPropertyDescriptor;var d=Object.getOwnPropertyNames;var g=Object.prototype.hasOwnProperty;var v=(i,t)=>{for(var e in t)u(i,e,{get:t[e],enumerable:!0})},f=(i,t,e,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of d(t))!g.call(i,s)&&s!==e&&u(i,s,{get:()=>t[s],enumerable:!(r=p(t,s))||r.enumerable});return i};var _=i=>f(u({},"__esModule",{value:!0}),i);var m={};v(m,{LinkedList:()=>l,Node:()=>n,PageSelectedEvent:()=>a,Pager:()=>o});module.exports=_(m);var n=class{constructor(t,e){this.value=t;this.removeCallback=e;this.next=null;this.prev=null}remove(){this.prev.next=this.next,this.next.prev=this.prev,this.removeCallback()}},l=class{constructor(){this._length=0}addFirst(t){let e=new n(t,()=>this._length--);this._first?(e.next=this._first,this._first.prev=e,this._first=e):(this._first=e,this._last=this._first),this._length++}addLast(t){let e=new n(t,()=>this._length--);this._first?(e.prev=this._last,this._last.next=e,this._last=e):(this._first=e,this._last=this._first),this._length++}removeFirst(){if(!this.first)throw new Error("The list is empty.");let t=this._first.value;return this._first=this._first.next,this._length--,t}removeLast(){if(!this.last)throw new Error("The list is empty.");let t=this._last.value;return this._last=this._last.prev,this._length--,t}get length(){return this._length}get first(){return this._first}get firstValue(){return this._first?.value}get last(){return this._last}get lastValue(){return this._last?.value}};var a=class extends Event{constructor(e){super("pageselected",{bubbles:!0,composed:!0});this.page=e}},o=class{constructor(t,e,r){this.currentPage=1;this.container=t,this.totalCount=e,this.pageSize=r,this.render()}render(){this.container.innerHTML="";let t=Math.max(1,Math.ceil(this.totalCount/this.pageSize)),e=(r,s,c=!1)=>{let h=document.createElement("button");return h.textContent=r,h.disabled=c,h.addEventListener("click",()=>this.selectPage(s)),h};this.container.appendChild(e("Previous",this.currentPage-1,this.currentPage===1));for(let r=1;r<=t;r++){let s=e(r.toString(),r);r===this.currentPage&&s.classList.add("selected"),this.container.appendChild(s)}this.container.appendChild(e("Next",this.currentPage+1,this.currentPage===t))}selectPage(t){let e=Math.max(1,Math.ceil(this.totalCount/this.pageSize));t<1||t>e||t===this.currentPage||(this.currentPage=t,this.render(),this.container.dispatchEvent(new a(this.currentPage)))}update(t){this.totalCount=t;let e=Math.max(1,Math.ceil(this.totalCount/this.pageSize));this.currentPage>e&&(this.currentPage=e),this.render()}getCurrentPage(){return this.currentPage}};
2
2
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../src/collections/Index.ts", "../../src/collections/LinkedList.ts"],
4
- "sourcesContent": ["export {LinkedList, Node} from \"./LinkedList\";", "/**\r\n * A node in the @see LinkedList.\r\n */\r\nexport class Node<T> {\r\n /**\r\n * Next node unless last one.\r\n */\r\n public next: Node<T> | null = null;\r\n /**\r\n * Previous node unless first one.\r\n */\r\n public prev: Node<T> | null = null;\r\n\r\n /**\r\n * Constructor.\r\n * @param value Value contained in the node.\r\n */\r\n constructor(public value: T, private removeCallback: () => void) {}\r\n\r\n /**\r\n * Remove this node.\r\n * Will notify the list of the update to ensure correct element count.\r\n */\r\n remove() {\r\n this.prev.next = this.next;\r\n this.next.prev = this.prev;\r\n this.removeCallback();\r\n }\r\n}\r\n\r\n/**\r\n * A trivial linked list implementation.\r\n */\r\nexport class LinkedList<T> {\r\n private _first?: Node<T>;\r\n private _last?: Node<T>;\r\n private _length = 0;\r\n\r\n /**\r\n * Add a value to the beginning of the list.\r\n * @param value Value that should be contained in the node.\r\n */\r\n addFirst(value: T) {\r\n const newNode = new Node(value, () => this._length--);\r\n if (!this._first) {\r\n this._first = newNode;\r\n this._last = this._first;\r\n } else {\r\n newNode.next = this._first;\r\n this._first.prev = newNode;\r\n this._first = newNode;\r\n }\r\n\r\n this._length++;\r\n }\r\n\r\n /**\r\n * Add a value to the end of the list.\r\n * @param value Value that should be contained in a node.\r\n */\r\n addLast(value: T) {\r\n const newNode = new Node(value, () => this._length--);\r\n if (!this._first) {\r\n this._first = newNode;\r\n this._last = this._first;\r\n } else {\r\n newNode.prev = this._last;\r\n this._last.next = newNode;\r\n this._last = newNode;\r\n }\r\n\r\n this._length++;\r\n }\r\n\r\n /**\r\n * Remove a node from the beginning of the list.\r\n * @returns Value contained in the first node.\r\n */\r\n removeFirst(): T {\r\n if (!this.first) {\r\n throw new Error('The list is empty.');\r\n }\r\n\r\n const value = this._first.value;\r\n this._first = this._first.next;\r\n this._length--;\r\n return value;\r\n }\r\n\r\n /**\r\n * Remove a node from the end of the list.\r\n * @returns Value contained in the last node.\r\n */\r\n removeLast(): T {\r\n if (!this.last) {\r\n throw new Error('The list is empty.');\r\n }\r\n\r\n const value = this._last.value;\r\n this._last = this._last.prev;\r\n this._length--;\r\n return value;\r\n }\r\n\r\n /**\r\n * Number of nodes in the list.\r\n *\r\n * The count works as long as you do not manually remove nodes (by assigning next/prev to the neighbors).\r\n */\r\n get length(): number {\r\n return this._length;\r\n }\r\n\r\n /**\r\n * First ndoe.\r\n */\r\n get first(): Node<T> | undefined {\r\n return this._first;\r\n }\r\n\r\n /**\r\n * Contained value of the first node.\r\n */\r\n get firstValue(): T | undefined {\r\n return this._first?.value;\r\n }\r\n\r\n /**\r\n * Last node.\r\n */\r\n get last(): Node<T> | undefined {\r\n return this._last;\r\n }\r\n\r\n /**\r\n * Contained value of the last node.\r\n */\r\n get lastValue(): T | undefined {\r\n return this._last?.value;\r\n }\r\n}\r\n"],
5
- "mappings": "4ZAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,gBAAAE,EAAA,SAAAC,IAAA,eAAAC,EAAAJ,GCGO,IAAMK,EAAN,KAAc,CAcjB,YAAmBC,EAAkBC,EAA4B,CAA9C,WAAAD,EAAkB,oBAAAC,EAVrC,KAAO,KAAuB,KAI9B,KAAO,KAAuB,IAMoC,CAMlE,QAAS,CACL,KAAK,KAAK,KAAO,KAAK,KACtB,KAAK,KAAK,KAAO,KAAK,KACtB,KAAK,eAAe,CACxB,CACJ,EAKaC,EAAN,KAAoB,CAApB,cAGH,KAAQ,QAAU,EAMlB,SAASF,EAAU,CACf,IAAMG,EAAU,IAAIJ,EAAKC,EAAO,IAAM,KAAK,SAAS,EAC/C,KAAK,QAING,EAAQ,KAAO,KAAK,OACpB,KAAK,OAAO,KAAOA,EACnB,KAAK,OAASA,IALd,KAAK,OAASA,EACd,KAAK,MAAQ,KAAK,QAOtB,KAAK,SACT,CAMA,QAAQH,EAAU,CACd,IAAMG,EAAU,IAAIJ,EAAKC,EAAO,IAAM,KAAK,SAAS,EAC/C,KAAK,QAING,EAAQ,KAAO,KAAK,MACpB,KAAK,MAAM,KAAOA,EAClB,KAAK,MAAQA,IALb,KAAK,OAASA,EACd,KAAK,MAAQ,KAAK,QAOtB,KAAK,SACT,CAMA,aAAiB,CACb,GAAI,CAAC,KAAK,MACN,MAAM,IAAI,MAAM,oBAAoB,EAGxC,IAAMH,EAAQ,KAAK,OAAO,MAC1B,YAAK,OAAS,KAAK,OAAO,KAC1B,KAAK,UACEA,CACX,CAMA,YAAgB,CACZ,GAAI,CAAC,KAAK,KACN,MAAM,IAAI,MAAM,oBAAoB,EAGxC,IAAMA,EAAQ,KAAK,MAAM,MACzB,YAAK,MAAQ,KAAK,MAAM,KACxB,KAAK,UACEA,CACX,CAOA,IAAI,QAAiB,CACjB,OAAO,KAAK,OAChB,CAKA,IAAI,OAA6B,CAC7B,OAAO,KAAK,MAChB,CAKA,IAAI,YAA4B,CAC5B,OAAO,KAAK,QAAQ,KACxB,CAKA,IAAI,MAA4B,CAC5B,OAAO,KAAK,KAChB,CAKA,IAAI,WAA2B,CAC3B,OAAO,KAAK,OAAO,KACvB,CACJ",
6
- "names": ["Index_exports", "__export", "LinkedList", "Node", "__toCommonJS", "Node", "value", "removeCallback", "LinkedList", "newNode"]
3
+ "sources": ["../../src/collections/Index.ts", "../../src/collections/LinkedList.ts", "../../src/collections/Pager.ts"],
4
+ "sourcesContent": ["export {LinkedList, Node} from \"./LinkedList\";\nexport { Pager, PageSelectedEvent } from \"./Pager\";\nexport { type DataLoader } from \"./DataLoader\";", "/**\r\n * A node in the @see LinkedList.\r\n */\r\nexport class Node<T> {\r\n /**\r\n * Next node unless last one.\r\n */\r\n public next: Node<T> | null = null;\r\n /**\r\n * Previous node unless first one.\r\n */\r\n public prev: Node<T> | null = null;\r\n\r\n /**\r\n * Constructor.\r\n * @param value Value contained in the node.\r\n */\r\n constructor(public value: T, private removeCallback: () => void) {}\r\n\r\n /**\r\n * Remove this node.\r\n * Will notify the list of the update to ensure correct element count.\r\n */\r\n remove() {\r\n this.prev.next = this.next;\r\n this.next.prev = this.prev;\r\n this.removeCallback();\r\n }\r\n}\r\n\r\n/**\r\n * A trivial linked list implementation.\r\n */\r\nexport class LinkedList<T> {\r\n private _first?: Node<T>;\r\n private _last?: Node<T>;\r\n private _length = 0;\r\n\r\n /**\r\n * Add a value to the beginning of the list.\r\n * @param value Value that should be contained in the node.\r\n */\r\n addFirst(value: T) {\r\n const newNode = new Node(value, () => this._length--);\r\n if (!this._first) {\r\n this._first = newNode;\r\n this._last = this._first;\r\n } else {\r\n newNode.next = this._first;\r\n this._first.prev = newNode;\r\n this._first = newNode;\r\n }\r\n\r\n this._length++;\r\n }\r\n\r\n /**\r\n * Add a value to the end of the list.\r\n * @param value Value that should be contained in a node.\r\n */\r\n addLast(value: T) {\r\n const newNode = new Node(value, () => this._length--);\r\n if (!this._first) {\r\n this._first = newNode;\r\n this._last = this._first;\r\n } else {\r\n newNode.prev = this._last;\r\n this._last.next = newNode;\r\n this._last = newNode;\r\n }\r\n\r\n this._length++;\r\n }\r\n\r\n /**\r\n * Remove a node from the beginning of the list.\r\n * @returns Value contained in the first node.\r\n */\r\n removeFirst(): T {\r\n if (!this.first) {\r\n throw new Error('The list is empty.');\r\n }\r\n\r\n const value = this._first.value;\r\n this._first = this._first.next;\r\n this._length--;\r\n return value;\r\n }\r\n\r\n /**\r\n * Remove a node from the end of the list.\r\n * @returns Value contained in the last node.\r\n */\r\n removeLast(): T {\r\n if (!this.last) {\r\n throw new Error('The list is empty.');\r\n }\r\n\r\n const value = this._last.value;\r\n this._last = this._last.prev;\r\n this._length--;\r\n return value;\r\n }\r\n\r\n /**\r\n * Number of nodes in the list.\r\n *\r\n * The count works as long as you do not manually remove nodes (by assigning next/prev to the neighbors).\r\n */\r\n get length(): number {\r\n return this._length;\r\n }\r\n\r\n /**\r\n * First ndoe.\r\n */\r\n get first(): Node<T> | undefined {\r\n return this._first;\r\n }\r\n\r\n /**\r\n * Contained value of the first node.\r\n */\r\n get firstValue(): T | undefined {\r\n return this._first?.value;\r\n }\r\n\r\n /**\r\n * Last node.\r\n */\r\n get last(): Node<T> | undefined {\r\n return this._last;\r\n }\r\n\r\n /**\r\n * Contained value of the last node.\r\n */\r\n get lastValue(): T | undefined {\r\n return this._last?.value;\r\n }\r\n}\r\n", "export class PageSelectedEvent extends Event {\r\n constructor(public page: number) {\r\n super('pageselected', {\r\n bubbles: true,\r\n composed: true,\r\n });\r\n }\r\n}\r\n\r\ndeclare global {\r\n interface HTMLElementEventMap {\r\n 'pageselected': PageSelectedEvent;\r\n }\r\n}\r\n\r\nexport class Pager {\r\n private container: HTMLElement;\r\n private totalCount: number;\r\n private pageSize: number;\r\n private currentPage: number = 1;\r\n\r\n constructor(container: HTMLElement, totalCount: number, pageSize: number) {\r\n this.container = container;\r\n this.totalCount = totalCount;\r\n this.pageSize = pageSize;\r\n\r\n this.render();\r\n }\r\n\r\n private render() {\r\n this.container.innerHTML = '';\r\n\r\n const pageCount = Math.max(1, Math.ceil(this.totalCount / this.pageSize));\r\n\r\n const createButton = (label: string, page: number, disabled: boolean = false) => {\r\n const btn = document.createElement('button');\r\n btn.textContent = label;\r\n btn.disabled = disabled;\r\n btn.addEventListener('click', () => this.selectPage(page));\r\n return btn;\r\n };\r\n\r\n this.container.appendChild(\r\n createButton('Previous', this.currentPage - 1, this.currentPage === 1)\r\n );\r\n\r\n for (let i = 1; i <= pageCount; i++) {\r\n const btn = createButton(i.toString(), i);\r\n if (i === this.currentPage) {\r\n btn.classList.add('selected');\r\n }\r\n this.container.appendChild(btn);\r\n }\r\n\r\n this.container.appendChild(\r\n createButton('Next', this.currentPage + 1, this.currentPage === pageCount)\r\n );\r\n }\r\n\r\n private selectPage(page: number) {\r\n const pageCount = Math.max(1, Math.ceil(this.totalCount / this.pageSize));\r\n if (page < 1 || page > pageCount || page === this.currentPage) return;\r\n\r\n this.currentPage = page;\r\n this.render();\r\n\r\n this.container.dispatchEvent(new PageSelectedEvent(this.currentPage));\r\n }\r\n\r\n public update(totalCount: number) {\r\n this.totalCount = totalCount;\r\n const pageCount = Math.max(1, Math.ceil(this.totalCount / this.pageSize));\r\n if (this.currentPage > pageCount) {\r\n this.currentPage = pageCount;\r\n }\r\n this.render();\r\n }\r\n\r\n public getCurrentPage(): number {\r\n return this.currentPage;\r\n }\r\n}\r\n"],
5
+ "mappings": "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",
6
+ "names": ["Index_exports", "__export", "LinkedList", "Node", "PageSelectedEvent", "Pager", "__toCommonJS", "Node", "value", "removeCallback", "LinkedList", "newNode", "PageSelectedEvent", "page", "Pager", "container", "totalCount", "pageSize", "pageCount", "createButton", "label", "disabled", "btn", "i"]
7
7
  }
@@ -1,2 +1,2 @@
1
- var s=class{constructor(e,t){this.value=e;this.removeCallback=t;this.next=null;this.prev=null}remove(){this.prev.next=this.next,this.next.prev=this.prev,this.removeCallback()}},i=class{constructor(){this._length=0}addFirst(e){let t=new s(e,()=>this._length--);this._first?(t.next=this._first,this._first.prev=t,this._first=t):(this._first=t,this._last=this._first),this._length++}addLast(e){let t=new s(e,()=>this._length--);this._first?(t.prev=this._last,this._last.next=t,this._last=t):(this._first=t,this._last=this._first),this._length++}removeFirst(){if(!this.first)throw new Error("The list is empty.");let e=this._first.value;return this._first=this._first.next,this._length--,e}removeLast(){if(!this.last)throw new Error("The list is empty.");let e=this._last.value;return this._last=this._last.prev,this._length--,e}get length(){return this._length}get first(){return this._first}get firstValue(){return this._first?.value}get last(){return this._last}get lastValue(){return this._last?.value}};export{i as LinkedList,s as Node};
1
+ var i=class{constructor(e,t){this.value=e;this.removeCallback=t;this.next=null;this.prev=null}remove(){this.prev.next=this.next,this.next.prev=this.prev,this.removeCallback()}},l=class{constructor(){this._length=0}addFirst(e){let t=new i(e,()=>this._length--);this._first?(t.next=this._first,this._first.prev=t,this._first=t):(this._first=t,this._last=this._first),this._length++}addLast(e){let t=new i(e,()=>this._length--);this._first?(t.prev=this._last,this._last.next=t,this._last=t):(this._first=t,this._last=this._first),this._length++}removeFirst(){if(!this.first)throw new Error("The list is empty.");let e=this._first.value;return this._first=this._first.next,this._length--,e}removeLast(){if(!this.last)throw new Error("The list is empty.");let e=this._last.value;return this._last=this._last.prev,this._length--,e}get length(){return this._length}get first(){return this._first}get firstValue(){return this._first?.value}get last(){return this._last}get lastValue(){return this._last?.value}};var a=class extends Event{constructor(t){super("pageselected",{bubbles:!0,composed:!0});this.page=t}},o=class{constructor(e,t,r){this.currentPage=1;this.container=e,this.totalCount=t,this.pageSize=r,this.render()}render(){this.container.innerHTML="";let e=Math.max(1,Math.ceil(this.totalCount/this.pageSize)),t=(r,s,u=!1)=>{let n=document.createElement("button");return n.textContent=r,n.disabled=u,n.addEventListener("click",()=>this.selectPage(s)),n};this.container.appendChild(t("Previous",this.currentPage-1,this.currentPage===1));for(let r=1;r<=e;r++){let s=t(r.toString(),r);r===this.currentPage&&s.classList.add("selected"),this.container.appendChild(s)}this.container.appendChild(t("Next",this.currentPage+1,this.currentPage===e))}selectPage(e){let t=Math.max(1,Math.ceil(this.totalCount/this.pageSize));e<1||e>t||e===this.currentPage||(this.currentPage=e,this.render(),this.container.dispatchEvent(new a(this.currentPage)))}update(e){this.totalCount=e;let t=Math.max(1,Math.ceil(this.totalCount/this.pageSize));this.currentPage>t&&(this.currentPage=t),this.render()}getCurrentPage(){return this.currentPage}};export{l as LinkedList,i as Node,a as PageSelectedEvent,o as Pager};
2
2
  //# sourceMappingURL=index.mjs.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../src/collections/LinkedList.ts"],
4
- "sourcesContent": ["/**\r\n * A node in the @see LinkedList.\r\n */\r\nexport class Node<T> {\r\n /**\r\n * Next node unless last one.\r\n */\r\n public next: Node<T> | null = null;\r\n /**\r\n * Previous node unless first one.\r\n */\r\n public prev: Node<T> | null = null;\r\n\r\n /**\r\n * Constructor.\r\n * @param value Value contained in the node.\r\n */\r\n constructor(public value: T, private removeCallback: () => void) {}\r\n\r\n /**\r\n * Remove this node.\r\n * Will notify the list of the update to ensure correct element count.\r\n */\r\n remove() {\r\n this.prev.next = this.next;\r\n this.next.prev = this.prev;\r\n this.removeCallback();\r\n }\r\n}\r\n\r\n/**\r\n * A trivial linked list implementation.\r\n */\r\nexport class LinkedList<T> {\r\n private _first?: Node<T>;\r\n private _last?: Node<T>;\r\n private _length = 0;\r\n\r\n /**\r\n * Add a value to the beginning of the list.\r\n * @param value Value that should be contained in the node.\r\n */\r\n addFirst(value: T) {\r\n const newNode = new Node(value, () => this._length--);\r\n if (!this._first) {\r\n this._first = newNode;\r\n this._last = this._first;\r\n } else {\r\n newNode.next = this._first;\r\n this._first.prev = newNode;\r\n this._first = newNode;\r\n }\r\n\r\n this._length++;\r\n }\r\n\r\n /**\r\n * Add a value to the end of the list.\r\n * @param value Value that should be contained in a node.\r\n */\r\n addLast(value: T) {\r\n const newNode = new Node(value, () => this._length--);\r\n if (!this._first) {\r\n this._first = newNode;\r\n this._last = this._first;\r\n } else {\r\n newNode.prev = this._last;\r\n this._last.next = newNode;\r\n this._last = newNode;\r\n }\r\n\r\n this._length++;\r\n }\r\n\r\n /**\r\n * Remove a node from the beginning of the list.\r\n * @returns Value contained in the first node.\r\n */\r\n removeFirst(): T {\r\n if (!this.first) {\r\n throw new Error('The list is empty.');\r\n }\r\n\r\n const value = this._first.value;\r\n this._first = this._first.next;\r\n this._length--;\r\n return value;\r\n }\r\n\r\n /**\r\n * Remove a node from the end of the list.\r\n * @returns Value contained in the last node.\r\n */\r\n removeLast(): T {\r\n if (!this.last) {\r\n throw new Error('The list is empty.');\r\n }\r\n\r\n const value = this._last.value;\r\n this._last = this._last.prev;\r\n this._length--;\r\n return value;\r\n }\r\n\r\n /**\r\n * Number of nodes in the list.\r\n *\r\n * The count works as long as you do not manually remove nodes (by assigning next/prev to the neighbors).\r\n */\r\n get length(): number {\r\n return this._length;\r\n }\r\n\r\n /**\r\n * First ndoe.\r\n */\r\n get first(): Node<T> | undefined {\r\n return this._first;\r\n }\r\n\r\n /**\r\n * Contained value of the first node.\r\n */\r\n get firstValue(): T | undefined {\r\n return this._first?.value;\r\n }\r\n\r\n /**\r\n * Last node.\r\n */\r\n get last(): Node<T> | undefined {\r\n return this._last;\r\n }\r\n\r\n /**\r\n * Contained value of the last node.\r\n */\r\n get lastValue(): T | undefined {\r\n return this._last?.value;\r\n }\r\n}\r\n"],
5
- "mappings": "AAGO,IAAMA,EAAN,KAAc,CAcjB,YAAmBC,EAAkBC,EAA4B,CAA9C,WAAAD,EAAkB,oBAAAC,EAVrC,KAAO,KAAuB,KAI9B,KAAO,KAAuB,IAMoC,CAMlE,QAAS,CACL,KAAK,KAAK,KAAO,KAAK,KACtB,KAAK,KAAK,KAAO,KAAK,KACtB,KAAK,eAAe,CACxB,CACJ,EAKaC,EAAN,KAAoB,CAApB,cAGH,KAAQ,QAAU,EAMlB,SAASF,EAAU,CACf,IAAMG,EAAU,IAAIJ,EAAKC,EAAO,IAAM,KAAK,SAAS,EAC/C,KAAK,QAING,EAAQ,KAAO,KAAK,OACpB,KAAK,OAAO,KAAOA,EACnB,KAAK,OAASA,IALd,KAAK,OAASA,EACd,KAAK,MAAQ,KAAK,QAOtB,KAAK,SACT,CAMA,QAAQH,EAAU,CACd,IAAMG,EAAU,IAAIJ,EAAKC,EAAO,IAAM,KAAK,SAAS,EAC/C,KAAK,QAING,EAAQ,KAAO,KAAK,MACpB,KAAK,MAAM,KAAOA,EAClB,KAAK,MAAQA,IALb,KAAK,OAASA,EACd,KAAK,MAAQ,KAAK,QAOtB,KAAK,SACT,CAMA,aAAiB,CACb,GAAI,CAAC,KAAK,MACN,MAAM,IAAI,MAAM,oBAAoB,EAGxC,IAAMH,EAAQ,KAAK,OAAO,MAC1B,YAAK,OAAS,KAAK,OAAO,KAC1B,KAAK,UACEA,CACX,CAMA,YAAgB,CACZ,GAAI,CAAC,KAAK,KACN,MAAM,IAAI,MAAM,oBAAoB,EAGxC,IAAMA,EAAQ,KAAK,MAAM,MACzB,YAAK,MAAQ,KAAK,MAAM,KACxB,KAAK,UACEA,CACX,CAOA,IAAI,QAAiB,CACjB,OAAO,KAAK,OAChB,CAKA,IAAI,OAA6B,CAC7B,OAAO,KAAK,MAChB,CAKA,IAAI,YAA4B,CAC5B,OAAO,KAAK,QAAQ,KACxB,CAKA,IAAI,MAA4B,CAC5B,OAAO,KAAK,KAChB,CAKA,IAAI,WAA2B,CAC3B,OAAO,KAAK,OAAO,KACvB,CACJ",
6
- "names": ["Node", "value", "removeCallback", "LinkedList", "newNode"]
3
+ "sources": ["../../src/collections/LinkedList.ts", "../../src/collections/Pager.ts"],
4
+ "sourcesContent": ["/**\r\n * A node in the @see LinkedList.\r\n */\r\nexport class Node<T> {\r\n /**\r\n * Next node unless last one.\r\n */\r\n public next: Node<T> | null = null;\r\n /**\r\n * Previous node unless first one.\r\n */\r\n public prev: Node<T> | null = null;\r\n\r\n /**\r\n * Constructor.\r\n * @param value Value contained in the node.\r\n */\r\n constructor(public value: T, private removeCallback: () => void) {}\r\n\r\n /**\r\n * Remove this node.\r\n * Will notify the list of the update to ensure correct element count.\r\n */\r\n remove() {\r\n this.prev.next = this.next;\r\n this.next.prev = this.prev;\r\n this.removeCallback();\r\n }\r\n}\r\n\r\n/**\r\n * A trivial linked list implementation.\r\n */\r\nexport class LinkedList<T> {\r\n private _first?: Node<T>;\r\n private _last?: Node<T>;\r\n private _length = 0;\r\n\r\n /**\r\n * Add a value to the beginning of the list.\r\n * @param value Value that should be contained in the node.\r\n */\r\n addFirst(value: T) {\r\n const newNode = new Node(value, () => this._length--);\r\n if (!this._first) {\r\n this._first = newNode;\r\n this._last = this._first;\r\n } else {\r\n newNode.next = this._first;\r\n this._first.prev = newNode;\r\n this._first = newNode;\r\n }\r\n\r\n this._length++;\r\n }\r\n\r\n /**\r\n * Add a value to the end of the list.\r\n * @param value Value that should be contained in a node.\r\n */\r\n addLast(value: T) {\r\n const newNode = new Node(value, () => this._length--);\r\n if (!this._first) {\r\n this._first = newNode;\r\n this._last = this._first;\r\n } else {\r\n newNode.prev = this._last;\r\n this._last.next = newNode;\r\n this._last = newNode;\r\n }\r\n\r\n this._length++;\r\n }\r\n\r\n /**\r\n * Remove a node from the beginning of the list.\r\n * @returns Value contained in the first node.\r\n */\r\n removeFirst(): T {\r\n if (!this.first) {\r\n throw new Error('The list is empty.');\r\n }\r\n\r\n const value = this._first.value;\r\n this._first = this._first.next;\r\n this._length--;\r\n return value;\r\n }\r\n\r\n /**\r\n * Remove a node from the end of the list.\r\n * @returns Value contained in the last node.\r\n */\r\n removeLast(): T {\r\n if (!this.last) {\r\n throw new Error('The list is empty.');\r\n }\r\n\r\n const value = this._last.value;\r\n this._last = this._last.prev;\r\n this._length--;\r\n return value;\r\n }\r\n\r\n /**\r\n * Number of nodes in the list.\r\n *\r\n * The count works as long as you do not manually remove nodes (by assigning next/prev to the neighbors).\r\n */\r\n get length(): number {\r\n return this._length;\r\n }\r\n\r\n /**\r\n * First ndoe.\r\n */\r\n get first(): Node<T> | undefined {\r\n return this._first;\r\n }\r\n\r\n /**\r\n * Contained value of the first node.\r\n */\r\n get firstValue(): T | undefined {\r\n return this._first?.value;\r\n }\r\n\r\n /**\r\n * Last node.\r\n */\r\n get last(): Node<T> | undefined {\r\n return this._last;\r\n }\r\n\r\n /**\r\n * Contained value of the last node.\r\n */\r\n get lastValue(): T | undefined {\r\n return this._last?.value;\r\n }\r\n}\r\n", "export class PageSelectedEvent extends Event {\r\n constructor(public page: number) {\r\n super('pageselected', {\r\n bubbles: true,\r\n composed: true,\r\n });\r\n }\r\n}\r\n\r\ndeclare global {\r\n interface HTMLElementEventMap {\r\n 'pageselected': PageSelectedEvent;\r\n }\r\n}\r\n\r\nexport class Pager {\r\n private container: HTMLElement;\r\n private totalCount: number;\r\n private pageSize: number;\r\n private currentPage: number = 1;\r\n\r\n constructor(container: HTMLElement, totalCount: number, pageSize: number) {\r\n this.container = container;\r\n this.totalCount = totalCount;\r\n this.pageSize = pageSize;\r\n\r\n this.render();\r\n }\r\n\r\n private render() {\r\n this.container.innerHTML = '';\r\n\r\n const pageCount = Math.max(1, Math.ceil(this.totalCount / this.pageSize));\r\n\r\n const createButton = (label: string, page: number, disabled: boolean = false) => {\r\n const btn = document.createElement('button');\r\n btn.textContent = label;\r\n btn.disabled = disabled;\r\n btn.addEventListener('click', () => this.selectPage(page));\r\n return btn;\r\n };\r\n\r\n this.container.appendChild(\r\n createButton('Previous', this.currentPage - 1, this.currentPage === 1)\r\n );\r\n\r\n for (let i = 1; i <= pageCount; i++) {\r\n const btn = createButton(i.toString(), i);\r\n if (i === this.currentPage) {\r\n btn.classList.add('selected');\r\n }\r\n this.container.appendChild(btn);\r\n }\r\n\r\n this.container.appendChild(\r\n createButton('Next', this.currentPage + 1, this.currentPage === pageCount)\r\n );\r\n }\r\n\r\n private selectPage(page: number) {\r\n const pageCount = Math.max(1, Math.ceil(this.totalCount / this.pageSize));\r\n if (page < 1 || page > pageCount || page === this.currentPage) return;\r\n\r\n this.currentPage = page;\r\n this.render();\r\n\r\n this.container.dispatchEvent(new PageSelectedEvent(this.currentPage));\r\n }\r\n\r\n public update(totalCount: number) {\r\n this.totalCount = totalCount;\r\n const pageCount = Math.max(1, Math.ceil(this.totalCount / this.pageSize));\r\n if (this.currentPage > pageCount) {\r\n this.currentPage = pageCount;\r\n }\r\n this.render();\r\n }\r\n\r\n public getCurrentPage(): number {\r\n return this.currentPage;\r\n }\r\n}\r\n"],
5
+ "mappings": "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",
6
+ "names": ["Node", "value", "removeCallback", "LinkedList", "newNode", "PageSelectedEvent", "page", "Pager", "container", "totalCount", "pageSize", "pageCount", "createButton", "label", "disabled", "btn", "i"]
7
7
  }
package/dist/di/index.js CHANGED
@@ -1,2 +1,2 @@
1
- var d=Object.defineProperty;var y=Object.getOwnPropertyDescriptor;var f=Object.getOwnPropertyNames;var h=Object.prototype.hasOwnProperty;var x=(n,t)=>{for(var e in t)d(n,e,{get:t[e],enumerable:!0})},C=(n,t,e,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of f(t))!h.call(n,s)&&s!==e&&d(n,s,{get:()=>t[s],enumerable:!(r=y(t,s))||r.enumerable});return n};var T=n=>C(d({},"__esModule",{value:!0}),n);var b={};x(b,{ContainerService:()=>m,Inject:()=>j,ServiceCollection:()=>a,ServiceContainer:()=>l,container:()=>v,serviceCollection:()=>g});module.exports=T(b);var p=class extends Error{constructor(e,r){super(e);this.context=r}},u=null;function o(n,t){let e=new p(n,t);if(u){let r=!1;if(u(e,{suppress(){r=!0}}),r)return null}return e}function j(n){return(t,e)=>{var r=v.resolve(n);return function(){return r}}}function m(n){return t=>{let e=n??{inject:[]};e.key?g.register(t,e):g.registerByType(t,e)}}var c=class{constructor(t,e,r,s={},i,w){this.classConstructor=t;this.scope=e;this.inject=r;this.properties=s;this.key=i;this.instance=w}},a=class{constructor(){this.servicesByKey=new Map;this.servicesByClassName=new Map}register(t,e){this.validateRegistration(t,e);let r=new c(t,e.scope??"global",e.inject,e.properties??{},e.key,e.instance);e.key&&this.servicesByKey.set(e.key,r),this.servicesByClassName.set(t.name,r)}registerByType(t,e){this.checkNameCollision(t),e&&this.validateRegistration(t,e);let r=new c(t,e?.scope,e?.inject??[],e?.properties,e?.key,e?.instance);e?.key&&this.servicesByKey.set(e.key,r),this.servicesByClassName.set(t.name,r)}checkNameCollision(t){let e=this.servicesByClassName.get(t.name);if(e&&e.classConstructor!==t){let r=o("Service name collision: different class registered with same name",{service:t.name});if(r)throw r}}validateRegistration(t,e){if(this.checkNameCollision(t),e.key){let r=this.servicesByKey.get(e.key);if(r&&r.classConstructor!==t){let s=o("Service key already registered to a different class",{key:e.key,existingClass:r.classConstructor.name,newClass:t.name});if(s)throw s}}if(e.instance&&e.inject.length>0){let r=o("Service has both instance and inject (inject will be ignored)",{service:t.name});if(r)throw r}}tryGet(t){return typeof t=="string"?this.servicesByKey.get(t):this.servicesByClassName.get(t.name)}get(t){let e=this.tryGet(t);if(!e){let r=typeof t=="string"?t:t.name,s=o(`Failed to resolve service '${r}'`,{service:r,registeredTypes:Array.from(this.servicesByClassName.keys()),registeredKeys:Array.from(this.servicesByKey.keys())});if(s)throw s}return e}};var l=class{constructor(t){this.serviceCollection=t;this.instances=new Map}resolve(t){let e=typeof t=="string"?t:t.name;if(this.instances.has(e))return this.instances.get(e);let r=this.serviceCollection.get(t);if(!r){let i=o(`Failed to resolve service '${e}'`,{service:e});if(i)throw i;return}if(r.instance){let i=r.instance;return this.injectFields(i,r),this.instances.set(e,i),i}let s=this.createInstance(r);return r.scope==="global"&&this.instances.set(e,s),this.injectFields(s,r),s}createInstance(t){let e=t.classConstructor,r=t.inject.map(s=>this.resolve(s));return new e(...r)}injectFields(t,e){for(let[r,s]of Object.entries(e.properties))t[r]=this.resolve(s)}},g=new a,v=new l(g);
1
+ var d=Object.defineProperty;var y=Object.getOwnPropertyDescriptor;var f=Object.getOwnPropertyNames;var h=Object.prototype.hasOwnProperty;var x=(n,t)=>{for(var e in t)d(n,e,{get:t[e],enumerable:!0})},C=(n,t,e,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of f(t))!h.call(n,s)&&s!==e&&d(n,s,{get:()=>t[s],enumerable:!(r=y(t,s))||r.enumerable});return n};var T=n=>C(d({},"__esModule",{value:!0}),n);var b={};x(b,{ContainerService:()=>j,Inject:()=>m,ServiceCollection:()=>a,ServiceContainer:()=>l,container:()=>v,serviceCollection:()=>g});module.exports=T(b);var u=class extends Error{constructor(e,r){super(e);this.context=r}},p=null;function o(n,t){let e=new u(n,t);if(p){let r=!1;if(p(e,{suppress(){r=!0}}),r)return null}return e}function m(n){return(t,e)=>function(){return v.resolve(n)}}function j(n){return t=>{let e=n??{inject:[]};e.key?g.register(t,e):g.registerByType(t,e)}}var c=class{constructor(t,e,r,s={},i,w){this.classConstructor=t;this.scope=e;this.inject=r;this.properties=s;this.key=i;this.instance=w}},a=class{constructor(){this.servicesByKey=new Map;this.servicesByClassName=new Map}register(t,e){this.validateRegistration(t,e);let r=new c(t,e.scope??"global",e.inject,e.properties??{},e.key,e.instance);e.key&&this.servicesByKey.set(e.key,r),this.servicesByClassName.set(t.name,r)}registerByType(t,e){this.checkNameCollision(t),e&&this.validateRegistration(t,e);let r=new c(t,e?.scope,e?.inject??[],e?.properties,e?.key,e?.instance);e?.key&&this.servicesByKey.set(e.key,r),this.servicesByClassName.set(t.name,r)}checkNameCollision(t){let e=this.servicesByClassName.get(t.name);if(e&&e.classConstructor!==t){let r=o("Service name collision: different class registered with same name",{service:t.name});if(r)throw r}}validateRegistration(t,e){if(this.checkNameCollision(t),e.key){let r=this.servicesByKey.get(e.key);if(r&&r.classConstructor!==t){let s=o("Service key already registered to a different class",{key:e.key,existingClass:r.classConstructor.name,newClass:t.name});if(s)throw s}}if(e.instance&&e.inject.length>0){let r=o("Service has both instance and inject (inject will be ignored)",{service:t.name});if(r)throw r}}tryGet(t){return typeof t=="string"?this.servicesByKey.get(t):this.servicesByClassName.get(t.name)}get(t){let e=this.tryGet(t);if(!e){let r=typeof t=="string"?t:t.name,s=o(`Failed to resolve service '${r}'`,{service:r,registeredTypes:Array.from(this.servicesByClassName.keys()),registeredKeys:Array.from(this.servicesByKey.keys())});if(s)throw s}return e}};var l=class{constructor(t){this.serviceCollection=t;this.instances=new Map}resolve(t){let e=typeof t=="string"?t:t.name;if(this.instances.has(e))return this.instances.get(e);let r=this.serviceCollection.get(t);if(!r){let i=o(`Failed to resolve service '${e}'`,{service:e});if(i)throw i;return}if(r.instance){let i=r.instance;return this.injectFields(i,r),this.instances.set(e,i),i}let s=this.createInstance(r);return r.scope==="global"&&this.instances.set(e,s),this.injectFields(s,r),s}createInstance(t){let e=t.classConstructor,r=t.inject.map(s=>this.resolve(s));return new e(...r)}injectFields(t,e){for(let[r,s]of Object.entries(e.properties))t[r]=this.resolve(s)}},g=new a,v=new l(g);
2
2
  //# sourceMappingURL=index.js.map