@ngstato/angular 0.1.2 → 0.1.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.
- package/README.md +6 -0
- package/dist/README.md +394 -0
- package/dist/create-angular-store.d.ts +18 -0
- package/dist/devtools.component.d.ts +38 -0
- package/dist/esm2022/create-angular-store.mjs +133 -0
- package/dist/esm2022/devtools.component.mjs +343 -0
- package/dist/esm2022/index.mjs +8 -0
- package/dist/esm2022/inject-store.mjs +9 -0
- package/dist/esm2022/ngstato-angular.mjs +5 -0
- package/dist/esm2022/provide-ngstato.mjs +22 -0
- package/dist/fesm2022/ngstato-angular.mjs +512 -0
- package/dist/fesm2022/ngstato-angular.mjs.map +1 -0
- package/dist/index.d.ts +5 -50
- package/dist/inject-store.d.ts +2 -0
- package/dist/provide-ngstato.d.ts +8 -0
- package/ng-package.json +8 -0
- package/package.json +23 -21
- package/dist/index.d.mts +0 -50
- package/dist/index.js +0 -542
- package/dist/index.js.map +0 -1
- package/dist/index.mjs +0 -538
- package/dist/index.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -338,6 +338,12 @@ computed: {
|
|
|
338
338
|
|
|
339
339
|
---
|
|
340
340
|
|
|
341
|
+
## Demo live
|
|
342
|
+
|
|
343
|
+
[](https://stackblitz.com/github/becher/ngstato/tree/main/apps/stackblitz-demo)
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
341
347
|
## Roadmap
|
|
342
348
|
|
|
343
349
|
### v0.1 ✅ TERMINÉ
|
package/dist/README.md
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
# ngStato
|
|
2
|
+
|
|
3
|
+
> Stato est une librairie de gestion d'état Angular moderne pour remplacer NgRx complètement, avec une API plus simple, Signals-first, sans RxJS obligatoire.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## La même action. Deux approches.
|
|
14
|
+
|
|
15
|
+
| ❌ NgRx v20 (officiel, MIT) | ✅ ngStato |
|
|
16
|
+
|---|---|
|
|
17
|
+
| `loadStudents: rxMethod<void>(` | `async loadStudents(state) {` |
|
|
18
|
+
| ` pipe(` | ` state.isLoading = true` |
|
|
19
|
+
| ` tap(() => patchState(store, { isLoading: true })),` | ` state.students = await service.getAll()` |
|
|
20
|
+
| ` switchMap(() =>` | ` state.isLoading = false` |
|
|
21
|
+
| ` from(service.getAll()).pipe(` | `}` |
|
|
22
|
+
| ` tapResponse({` | |
|
|
23
|
+
| ` next: (s) => patchState(store, { students: s, isLoading: false }),` | **1 concept : async/await** |
|
|
24
|
+
| ` error: (e) => patchState(store, { error: e.message, isLoading: false })` | **5 lignes** |
|
|
25
|
+
| ` })` | |
|
|
26
|
+
| ` )` | |
|
|
27
|
+
| ` )` | |
|
|
28
|
+
| ` )` | |
|
|
29
|
+
| `)` | |
|
|
30
|
+
| **9 concepts RxJS/NgRx — 14 lignes** | |
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Pourquoi switcher vers ngStato ?
|
|
35
|
+
|
|
36
|
+
**1 concept au lieu de 9 pour écrire une action async**
|
|
37
|
+
NgRx nécessite rxMethod, pipe, tap, switchMap, from, tapResponse, patchState... ngStato nécessite uniquement async/await — natif JavaScript.
|
|
38
|
+
|
|
39
|
+
**2x moins de code pour le même résultat**
|
|
40
|
+
Sur un store CRUD complet (5 actions), NgRx v20 nécessite ~90 lignes. ngStato nécessite ~45 lignes.
|
|
41
|
+
|
|
42
|
+
**DevTools sans extension browser**
|
|
43
|
+
NgRx DevTools nécessite l'extension Chrome Redux DevTools. ngStato intègre ses DevTools directement dans l'app — fonctionnels sur tous les browsers et mobile.
|
|
44
|
+
|
|
45
|
+
**Protection production automatique**
|
|
46
|
+
ngStato utilise `isDevMode()` d'Angular — les DevTools sont physiquement impossibles à activer en prod.
|
|
47
|
+
|
|
48
|
+
**38x plus léger — ~3 KB vs ~50 KB gzip**
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npm install @ngstato/core @ngstato/angular
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
// app.config.ts
|
|
60
|
+
import { provideStato } from '@ngstato/angular'
|
|
61
|
+
import { isDevMode } from '@angular/core'
|
|
62
|
+
|
|
63
|
+
export const appConfig: ApplicationConfig = {
|
|
64
|
+
providers: [
|
|
65
|
+
provideRouter(routes),
|
|
66
|
+
provideStato({
|
|
67
|
+
http: { baseUrl: 'https://api.monapp.com', timeout: 8000 },
|
|
68
|
+
devtools: isDevMode()
|
|
69
|
+
})
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Créer un store
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
// user.store.ts
|
|
80
|
+
import { Injectable, OnDestroy, inject } from '@angular/core'
|
|
81
|
+
import { createStore, http, optimistic, retryable, connectDevTools } from '@ngstato/core'
|
|
82
|
+
import { injectStore } from '@ngstato/angular'
|
|
83
|
+
|
|
84
|
+
function createUserStore() {
|
|
85
|
+
const store = createStore({
|
|
86
|
+
|
|
87
|
+
// State
|
|
88
|
+
users: [] as User[],
|
|
89
|
+
isLoading: false,
|
|
90
|
+
error: null as string | null,
|
|
91
|
+
|
|
92
|
+
// Computed — recalculés automatiquement
|
|
93
|
+
computed: {
|
|
94
|
+
total: (state) => state.users.length,
|
|
95
|
+
admins: (state) => state.users.filter(u => u.role === 'admin')
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
// Actions
|
|
99
|
+
actions: {
|
|
100
|
+
|
|
101
|
+
// Chargement avec retry automatique
|
|
102
|
+
loadUsers: retryable(
|
|
103
|
+
async (state) => {
|
|
104
|
+
state.isLoading = true
|
|
105
|
+
state.users = await http.get('/users')
|
|
106
|
+
state.isLoading = false
|
|
107
|
+
},
|
|
108
|
+
{ attempts: 3, backoff: 'exponential', delay: 1000 }
|
|
109
|
+
),
|
|
110
|
+
|
|
111
|
+
// Suppression avec rollback automatique
|
|
112
|
+
deleteUser: optimistic(
|
|
113
|
+
(state, id: string) => {
|
|
114
|
+
state.users = state.users.filter(u => u.id !== id)
|
|
115
|
+
},
|
|
116
|
+
async (_, id: string) => {
|
|
117
|
+
await http.delete(`/users/${id}`)
|
|
118
|
+
}
|
|
119
|
+
),
|
|
120
|
+
|
|
121
|
+
// Action simple
|
|
122
|
+
async addUser(state, user: UserCreate) {
|
|
123
|
+
const created = await http.post('/users', user)
|
|
124
|
+
state.users = [...state.users, created]
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
// Hooks lifecycle
|
|
129
|
+
hooks: {
|
|
130
|
+
onError: (err, name) => console.error(`[UserStore] ${name}:`, err.message)
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
connectDevTools(store, 'UserStore') // ← une seule ligne
|
|
135
|
+
return store
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@Injectable({ providedIn: 'root' })
|
|
139
|
+
export class UserStore implements OnDestroy {
|
|
140
|
+
private _store = createUserStore()
|
|
141
|
+
|
|
142
|
+
get users() { return this._store.users }
|
|
143
|
+
get isLoading() { return this._store.isLoading }
|
|
144
|
+
get total() { return this._store.total }
|
|
145
|
+
|
|
146
|
+
loadUsers = (...a: any[]) => this._store.loadUsers(...a)
|
|
147
|
+
deleteUser = (...a: any[]) => this._store.deleteUser(...a)
|
|
148
|
+
addUser = (...a: any[]) => this._store.addUser(...a)
|
|
149
|
+
|
|
150
|
+
ngOnDestroy() { this._store.__store__.destroy(this._store) }
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Dans un composant
|
|
154
|
+
@Component({ ... })
|
|
155
|
+
export class UserListComponent {
|
|
156
|
+
store = injectStore(UserStore) // ou inject(UserStore)
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Helpers
|
|
163
|
+
|
|
164
|
+
| Helper | Description | Équivalent NgRx |
|
|
165
|
+
|--------|-------------|-----------------|
|
|
166
|
+
| `abortable()` | Annule la requête précédente automatiquement | switchMap |
|
|
167
|
+
| `debounced()` | Debounce sans RxJS — défaut 300ms | debounceTime |
|
|
168
|
+
| `throttled()` | Throttle sans RxJS | throttleTime |
|
|
169
|
+
| `retryable()` | Retry avec backoff fixe ou exponentiel | retryWhen |
|
|
170
|
+
| `fromStream()` | Écoute Observable/WebSocket/Firebase/Supabase | rxMethod + Effect |
|
|
171
|
+
| `optimistic()` | Update immédiat + rollback automatique si échec | Manuel en NgRx |
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
import { abortable, debounced, throttled, retryable, fromStream, optimistic } from '@ngstato/core'
|
|
175
|
+
|
|
176
|
+
actions: {
|
|
177
|
+
// Annulation auto — comme switchMap
|
|
178
|
+
search: abortable(async (state, q: string, { signal }) => {
|
|
179
|
+
state.results = await fetch(`/api/search?q=${q}`, { signal }).then(r => r.json())
|
|
180
|
+
}),
|
|
181
|
+
|
|
182
|
+
// Debounce 300ms
|
|
183
|
+
filter: debounced((state, q: string) => { state.query = q }, 300),
|
|
184
|
+
|
|
185
|
+
// Retry x3 avec backoff exponentiel
|
|
186
|
+
load: retryable(async (state) => {
|
|
187
|
+
state.data = await http.get('/data')
|
|
188
|
+
}, { attempts: 3, backoff: 'exponential' }),
|
|
189
|
+
|
|
190
|
+
// Realtime WebSocket
|
|
191
|
+
listen: fromStream(
|
|
192
|
+
() => webSocket('wss://api.monapp.com/ws'),
|
|
193
|
+
(state, msg) => { state.messages = [...state.messages, msg] }
|
|
194
|
+
),
|
|
195
|
+
|
|
196
|
+
// Optimistic + rollback auto
|
|
197
|
+
delete: optimistic(
|
|
198
|
+
(state, id) => { state.items = state.items.filter(i => i.id !== id) },
|
|
199
|
+
async (_, id) => { await http.delete(`/items/${id}`) }
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Client HTTP
|
|
207
|
+
|
|
208
|
+
```ts
|
|
209
|
+
import { http } from '@ngstato/core'
|
|
210
|
+
|
|
211
|
+
// Configurer via provideStato() — une seule fois
|
|
212
|
+
provideStato({
|
|
213
|
+
http: {
|
|
214
|
+
baseUrl: 'https://api.monapp.com',
|
|
215
|
+
timeout: 8000,
|
|
216
|
+
headers: { 'X-App-Version': '1.0' },
|
|
217
|
+
auth: () => localStorage.getItem('token')
|
|
218
|
+
}
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
// Utiliser partout dans les actions
|
|
222
|
+
await http.get('/users')
|
|
223
|
+
await http.get('/users', { params: { page: 1, limit: 10 } })
|
|
224
|
+
await http.post('/users', { name: 'Alice' })
|
|
225
|
+
await http.put('/users/1', { name: 'Bob' })
|
|
226
|
+
await http.patch('/users/1', { active: false })
|
|
227
|
+
await http.delete('/users/1')
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## DevTools
|
|
233
|
+
|
|
234
|
+
Panel intégré dans l'app — sans extension browser, sans installation supplémentaire.
|
|
235
|
+
|
|
236
|
+
- Panel déplaçable à la souris
|
|
237
|
+
- Redimensionnable — coin bas-droite
|
|
238
|
+
- Minimisable — bouton ▼/▲
|
|
239
|
+
- Historique des actions avec durées et timestamps
|
|
240
|
+
- Diff Avant/Après pour chaque action
|
|
241
|
+
- Onglet State — state actuel complet
|
|
242
|
+
- **Désactivé automatiquement en production via `isDevMode()`**
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
// app.config.ts
|
|
246
|
+
provideStato({ devtools: isDevMode() })
|
|
247
|
+
|
|
248
|
+
// app.component.ts
|
|
249
|
+
import { StatoDevToolsComponent } from '@ngstato/angular'
|
|
250
|
+
|
|
251
|
+
@Component({
|
|
252
|
+
imports: [RouterOutlet, StatoDevToolsComponent],
|
|
253
|
+
template: `<router-outlet /><stato-devtools />`
|
|
254
|
+
})
|
|
255
|
+
export class AppComponent {}
|
|
256
|
+
|
|
257
|
+
// mon-store.ts
|
|
258
|
+
connectDevTools(store, 'MonStore') // une seule ligne
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
| | NgRx DevTools | ngStato DevTools |
|
|
262
|
+
|---|---|---|
|
|
263
|
+
| Installation | Extension Chrome | Zéro installation |
|
|
264
|
+
| Browser support | Chrome/Firefox | Tous browsers |
|
|
265
|
+
| Mobile | ❌ | ✅ |
|
|
266
|
+
| Désactivé en prod | Manuel | `isDevMode()` auto |
|
|
267
|
+
| State visible en prod | Oui si oubli | Jamais |
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## Guide de migration NgRx → ngStato
|
|
272
|
+
|
|
273
|
+
La migration est progressive — store par store.
|
|
274
|
+
|
|
275
|
+
```ts
|
|
276
|
+
// withState → state initial
|
|
277
|
+
// NgRx
|
|
278
|
+
withState({ users: [] as User[], isLoading: false })
|
|
279
|
+
// ngStato
|
|
280
|
+
users: [] as User[], isLoading: false,
|
|
281
|
+
|
|
282
|
+
// withMethods + rxMethod → actions
|
|
283
|
+
// NgRx
|
|
284
|
+
withMethods((store) => ({
|
|
285
|
+
load: rxMethod<void>(pipe(
|
|
286
|
+
tap(() => patchState(store, { isLoading: true })),
|
|
287
|
+
switchMap(() => from(service.get()).pipe(
|
|
288
|
+
tapResponse({
|
|
289
|
+
next: (d) => patchState(store, { data: d, isLoading: false }),
|
|
290
|
+
error: (e) => patchState(store, { error: e.message })
|
|
291
|
+
})
|
|
292
|
+
))
|
|
293
|
+
))
|
|
294
|
+
}))
|
|
295
|
+
// ngStato
|
|
296
|
+
actions: {
|
|
297
|
+
async load(state) {
|
|
298
|
+
state.isLoading = true
|
|
299
|
+
state.data = await service.get()
|
|
300
|
+
state.isLoading = false
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// withComputed → computed
|
|
305
|
+
// NgRx
|
|
306
|
+
withComputed((store) => ({
|
|
307
|
+
total: computed(() => store.users().length)
|
|
308
|
+
}))
|
|
309
|
+
// ngStato
|
|
310
|
+
computed: {
|
|
311
|
+
total: (state) => state.users.length
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Comparaison NgRx SignalStore v20 vs ngStato
|
|
318
|
+
|
|
319
|
+
| Feature | NgRx SignalStore v20 | ngStato v0.1 |
|
|
320
|
+
|---------|---------------------|--------------|
|
|
321
|
+
| withState | ✅ | ✅ |
|
|
322
|
+
| withMethods / actions | ✅ rxMethod requis | ✅ async/await |
|
|
323
|
+
| withComputed | ✅ | ✅ |
|
|
324
|
+
| patchState | ✅ obligatoire | ✅ state.x = y |
|
|
325
|
+
| provideStore | ✅ | ✅ provideStato() |
|
|
326
|
+
| inject() | ✅ | ✅ injectStore() |
|
|
327
|
+
| onInit / onDestroy | ✅ | ✅ |
|
|
328
|
+
| DevTools | ✅ extension Chrome | ✅ panel intégré |
|
|
329
|
+
| DevTools mobile | ❌ | ✅ |
|
|
330
|
+
| Protection prod | ⚠️ logOnly manuel | ✅ isDevMode() auto |
|
|
331
|
+
| RxJS requis | ✅ obligatoire | ❌ optionnel |
|
|
332
|
+
| Bundle size | ~50 KB gzip | ~3 KB gzip |
|
|
333
|
+
| withProps | ✅ | 🔜 v0.2 |
|
|
334
|
+
| withEntities | ✅ | 🔜 v1.0 |
|
|
335
|
+
| signalStoreFeature() | ✅ | 🔜 v0.4 |
|
|
336
|
+
| Schematics CLI | ✅ | 🔜 v1.0 |
|
|
337
|
+
| ESLint plugin | ✅ | 🔜 v1.0 |
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## Demo live
|
|
342
|
+
|
|
343
|
+
[](https://stackblitz.com/github/becher/ngstato/tree/main/apps/stackblitz-demo)
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
## Roadmap
|
|
348
|
+
|
|
349
|
+
### v0.1 ✅ TERMINÉ
|
|
350
|
+
- `createStore()` — state, actions, computed, hooks
|
|
351
|
+
- `StatoHttp` — GET POST PUT PATCH DELETE avec auth, timeout, params
|
|
352
|
+
- `abortable()`, `debounced()`, `throttled()`, `retryable()`, `fromStream()`, `optimistic()`
|
|
353
|
+
- `@ngstato/angular` — Signals natifs, `provideStato()`, `injectStore()`
|
|
354
|
+
- DevTools — panel déplaçable, redimensionnable, minimisable
|
|
355
|
+
- `connectDevTools()` — connexion automatique store → DevTools
|
|
356
|
+
- Protection prod automatique via `isDevMode()`
|
|
357
|
+
- **144 tests — 100% passing**
|
|
358
|
+
|
|
359
|
+
### v0.2 — Helpers avancés
|
|
360
|
+
- `exclusive()` — = exhaustMap NgRx
|
|
361
|
+
- `queued()` — = concatMap NgRx
|
|
362
|
+
- `store.on()` — réactions inter-stores
|
|
363
|
+
- Testing utilities
|
|
364
|
+
- DevTools time-travel
|
|
365
|
+
|
|
366
|
+
### v0.3 — Persistance
|
|
367
|
+
- `withPersist()` — localStorage / sessionStorage / IndexedDB
|
|
368
|
+
- `withHistory()` — undo/redo
|
|
369
|
+
- SSR ready
|
|
370
|
+
|
|
371
|
+
### v1.0 — Production ready
|
|
372
|
+
- `withEntities()`, Schematics CLI, ESLint plugin
|
|
373
|
+
- Documentation VitePress complète
|
|
374
|
+
- Benchmarks comparatifs
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## Contribuer
|
|
379
|
+
|
|
380
|
+
```bash
|
|
381
|
+
git clone https://github.com/becher/ngstato
|
|
382
|
+
cd ngstato
|
|
383
|
+
pnpm install # Node >= 18, pnpm >= 8
|
|
384
|
+
pnpm build
|
|
385
|
+
pnpm test # 144 tests
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
Convention commits : `feat` / `fix` / `docs` / `test` / `refactor` / `chore`
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## License
|
|
393
|
+
|
|
394
|
+
MIT — Copyright (c) 2025 ngStato
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { OnDestroy } from '@angular/core';
|
|
2
|
+
import type { StatoStoreConfig } from '@ngstato/core';
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
export declare function createAngularStore<S extends object>(config: S & StatoStoreConfig<S>): any;
|
|
5
|
+
export declare class StatoStoreBase implements OnDestroy {
|
|
6
|
+
storeInstance: any;
|
|
7
|
+
initStore<S extends object>(config: S & StatoStoreConfig<S>): void;
|
|
8
|
+
ngOnDestroy(): void;
|
|
9
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<StatoStoreBase, never>;
|
|
10
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<StatoStoreBase>;
|
|
11
|
+
}
|
|
12
|
+
export declare function StatoStore<S extends object>(config: S & StatoStoreConfig<S>): {
|
|
13
|
+
new (): {
|
|
14
|
+
storeInstance: any;
|
|
15
|
+
initStore<S_1 extends object>(config: S_1 & StatoStoreConfig<S_1>): void;
|
|
16
|
+
ngOnDestroy(): void;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { OnInit, OnDestroy } from '@angular/core';
|
|
2
|
+
import type { ActionLog } from '@ngstato/core';
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
export declare class StatoDevToolsComponent implements OnInit, OnDestroy {
|
|
5
|
+
private unsub?;
|
|
6
|
+
isOpen: import("@angular/core").WritableSignal<boolean>;
|
|
7
|
+
isMinimized: import("@angular/core").WritableSignal<boolean>;
|
|
8
|
+
activeTab: import("@angular/core").WritableSignal<"actions" | "state">;
|
|
9
|
+
logs: import("@angular/core").WritableSignal<ActionLog[]>;
|
|
10
|
+
selectedLog: import("@angular/core").WritableSignal<ActionLog>;
|
|
11
|
+
posX: import("@angular/core").WritableSignal<number>;
|
|
12
|
+
posY: import("@angular/core").WritableSignal<number>;
|
|
13
|
+
panelWidth: import("@angular/core").WritableSignal<number>;
|
|
14
|
+
panelHeight: import("@angular/core").WritableSignal<number>;
|
|
15
|
+
private isDragging;
|
|
16
|
+
private isResizing;
|
|
17
|
+
private dragOffsetX;
|
|
18
|
+
private dragOffsetY;
|
|
19
|
+
private startW;
|
|
20
|
+
private startH;
|
|
21
|
+
private startX;
|
|
22
|
+
private startY;
|
|
23
|
+
private boundMouseMove;
|
|
24
|
+
private boundMouseUp;
|
|
25
|
+
ngOnInit(): void;
|
|
26
|
+
ngOnDestroy(): void;
|
|
27
|
+
toggle(): void;
|
|
28
|
+
toggleMinimize(): void;
|
|
29
|
+
clear(): void;
|
|
30
|
+
selectLog(log: ActionLog): void;
|
|
31
|
+
formatTime(iso: string): string;
|
|
32
|
+
onDragStart(e: MouseEvent): void;
|
|
33
|
+
onResizeStart(e: MouseEvent): void;
|
|
34
|
+
onMouseMove(e: MouseEvent): void;
|
|
35
|
+
onMouseUp(): void;
|
|
36
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<StatoDevToolsComponent, never>;
|
|
37
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<StatoDevToolsComponent, "ngstato-devtools", never, {}, {}, never, never, true, never>;
|
|
38
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────
|
|
2
|
+
// @ngstato/angular — createAngularStore()
|
|
3
|
+
// Transforme un ngstato store en store Angular avec Signals
|
|
4
|
+
// ─────────────────────────────────────────────────────
|
|
5
|
+
import { signal, computed, Injectable } from '@angular/core';
|
|
6
|
+
import { createStore } from '@ngstato/core';
|
|
7
|
+
import * as i0 from "@angular/core";
|
|
8
|
+
// ─────────────────────────────────────────────────────
|
|
9
|
+
// FONCTION PRINCIPALE — createAngularStore()
|
|
10
|
+
// ─────────────────────────────────────────────────────
|
|
11
|
+
export function createAngularStore(config) {
|
|
12
|
+
// 1. Créer le store core
|
|
13
|
+
const coreStore = createStore(config);
|
|
14
|
+
// 2. Créer un Signal pour chaque propriété du state
|
|
15
|
+
const signals = {};
|
|
16
|
+
const initialState = coreStore.getState();
|
|
17
|
+
for (const key of Object.keys(initialState)) {
|
|
18
|
+
signals[key] = signal(initialState[key]);
|
|
19
|
+
}
|
|
20
|
+
// 3. Synchroniser les Signals avec le state core
|
|
21
|
+
coreStore.subscribe((newState) => {
|
|
22
|
+
for (const key of Object.keys(newState)) {
|
|
23
|
+
if (signals[key]) {
|
|
24
|
+
signals[key].set(newState[key]);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
// 4. Construire l objet public Angular
|
|
29
|
+
const angularStore = {
|
|
30
|
+
__store__: coreStore.__store__
|
|
31
|
+
};
|
|
32
|
+
// 5. Exposer chaque propriété comme Signal
|
|
33
|
+
for (const key of Object.keys(initialState)) {
|
|
34
|
+
Object.defineProperty(angularStore, key, {
|
|
35
|
+
get: () => signals[key],
|
|
36
|
+
enumerable: true,
|
|
37
|
+
configurable: true
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
// 6. Exposer chaque computed comme Signal computed
|
|
41
|
+
const { computed: computedConfig } = config;
|
|
42
|
+
if (computedConfig) {
|
|
43
|
+
for (const key of Object.keys(computedConfig)) {
|
|
44
|
+
const computedSignal = computed(() => coreStore[key]);
|
|
45
|
+
Object.defineProperty(angularStore, key, {
|
|
46
|
+
get: () => computedSignal,
|
|
47
|
+
enumerable: true,
|
|
48
|
+
configurable: true
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// 7. Exposer chaque action directement
|
|
53
|
+
const { actions } = config;
|
|
54
|
+
if (actions) {
|
|
55
|
+
for (const name of Object.keys(actions)) {
|
|
56
|
+
angularStore[name] = (...args) => coreStore.__store__.dispatch(name, ...args);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// 8. Appeler onInit si défini
|
|
60
|
+
const { hooks } = config;
|
|
61
|
+
if (hooks?.onInit) {
|
|
62
|
+
hooks.onInit(angularStore);
|
|
63
|
+
}
|
|
64
|
+
// 9. Exposer destroy pour le cleanup
|
|
65
|
+
angularStore.__destroy__ = () => {
|
|
66
|
+
coreStore.__store__.destroy(angularStore);
|
|
67
|
+
};
|
|
68
|
+
return angularStore;
|
|
69
|
+
}
|
|
70
|
+
// ─────────────────────────────────────────────────────
|
|
71
|
+
// CLASSE DE BASE — étendue par StatoStore()
|
|
72
|
+
// Publique — pas de membres privés
|
|
73
|
+
// ─────────────────────────────────────────────────────
|
|
74
|
+
export class StatoStoreBase {
|
|
75
|
+
storeInstance;
|
|
76
|
+
initStore(config) {
|
|
77
|
+
this.storeInstance = createAngularStore(config);
|
|
78
|
+
// Copier toutes les propriétés sur this
|
|
79
|
+
for (const key of Object.keys(this.storeInstance)) {
|
|
80
|
+
if (key !== '__store__' && key !== '__destroy__') {
|
|
81
|
+
Object.defineProperty(this, key, {
|
|
82
|
+
get: () => this.storeInstance[key],
|
|
83
|
+
enumerable: true,
|
|
84
|
+
configurable: true
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Copier les actions
|
|
89
|
+
const { actions } = config;
|
|
90
|
+
if (actions) {
|
|
91
|
+
for (const name of Object.keys(actions)) {
|
|
92
|
+
Object.defineProperty(this, name, {
|
|
93
|
+
get: () => this.storeInstance[name],
|
|
94
|
+
enumerable: true,
|
|
95
|
+
configurable: true
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
ngOnDestroy() {
|
|
101
|
+
this.storeInstance?.__destroy__();
|
|
102
|
+
}
|
|
103
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StatoStoreBase, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
104
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StatoStoreBase });
|
|
105
|
+
}
|
|
106
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StatoStoreBase, decorators: [{
|
|
107
|
+
type: Injectable
|
|
108
|
+
}] });
|
|
109
|
+
// ─────────────────────────────────────────────────────
|
|
110
|
+
// FACTORY — StatoStore()
|
|
111
|
+
// Crée un service Angular injectable
|
|
112
|
+
// Usage :
|
|
113
|
+
// export class UserStore extends StatoStore({
|
|
114
|
+
// user: null,
|
|
115
|
+
// actions: { ... }
|
|
116
|
+
// }) {}
|
|
117
|
+
// ─────────────────────────────────────────────────────
|
|
118
|
+
export function StatoStore(config) {
|
|
119
|
+
class ConcreteStore extends StatoStoreBase {
|
|
120
|
+
constructor() {
|
|
121
|
+
super();
|
|
122
|
+
this.initStore(config);
|
|
123
|
+
}
|
|
124
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ConcreteStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
125
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ConcreteStore, providedIn: 'root' });
|
|
126
|
+
}
|
|
127
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ConcreteStore, decorators: [{
|
|
128
|
+
type: Injectable,
|
|
129
|
+
args: [{ providedIn: 'root' }]
|
|
130
|
+
}], ctorParameters: () => [] });
|
|
131
|
+
return ConcreteStore;
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3JlYXRlLWFuZ3VsYXItc3RvcmUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvY3JlYXRlLWFuZ3VsYXItc3RvcmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsd0RBQXdEO0FBQ3hELDBDQUEwQztBQUMxQyw0REFBNEQ7QUFDNUQsd0RBQXdEO0FBRXhELE9BQU8sRUFDTCxNQUFNLEVBQ04sUUFBUSxFQUNSLFVBQVUsRUFHWCxNQUFNLGVBQWUsQ0FBQTtBQUV0QixPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sZUFBZSxDQUFBOztBQUczQyx3REFBd0Q7QUFDeEQsNkNBQTZDO0FBQzdDLHdEQUF3RDtBQUV4RCxNQUFNLFVBQVUsa0JBQWtCLENBQ2hDLE1BQStCO0lBRS9CLHlCQUF5QjtJQUN6QixNQUFNLFNBQVMsR0FBRyxXQUFXLENBQUMsTUFBTSxDQUFDLENBQUE7SUFFckMsb0RBQW9EO0lBQ3BELE1BQU0sT0FBTyxHQUE4QyxFQUFFLENBQUE7SUFDN0QsTUFBTSxZQUFZLEdBQUcsU0FBUyxDQUFDLFFBQVEsRUFBRSxDQUFBO0lBRXpDLEtBQUssTUFBTSxHQUFHLElBQUksTUFBTSxDQUFDLElBQUksQ0FBQyxZQUFzQixDQUFDLEVBQUUsQ0FBQztRQUN0RCxPQUFPLENBQUMsR0FBRyxDQUFDLEdBQUcsTUFBTSxDQUFFLFlBQW9CLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQTtJQUNuRCxDQUFDO0lBRUQsaURBQWlEO0lBQ2pELFNBQVMsQ0FBQyxTQUFTLENBQUMsQ0FBQyxRQUFhLEVBQUUsRUFBRTtRQUNwQyxLQUFLLE1BQU0sR0FBRyxJQUFJLE1BQU0sQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQztZQUN4QyxJQUFJLE9BQU8sQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDO2dCQUNqQixPQUFPLENBQUMsR0FBRyxDQUFDLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFBO1lB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gOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgFxyXG4vLyBAbmdzdGF0by9hbmd1bGFyIOKAlCBjcmVhdGVBbmd1bGFyU3RvcmUoKVxyXG4vLyBUcmFuc2Zvcm1lIHVuIG5nc3RhdG8gc3RvcmUgZW4gc3RvcmUgQW5ndWxhciBhdmVjIFNpZ25hbHNcclxuLy8g4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSAXHJcblxyXG5pbXBvcnQge1xyXG4gIHNpZ25hbCxcclxuICBjb21wdXRlZCxcclxuICBJbmplY3RhYmxlLFxyXG4gIE9uRGVzdHJveSxcclxuICBTaWduYWxcclxufSBmcm9tICdAYW5ndWxhci9jb3JlJ1xyXG5cclxuaW1wb3J0IHsgY3JlYXRlU3RvcmUgfSBmcm9tICdAbmdzdGF0by9jb3JlJ1xyXG5pbXBvcnQgdHlwZSB7IFN0YXRvU3RvcmVDb25maWcgfSBmcm9tICdAbmdzdGF0by9jb3JlJ1xyXG5cclxuLy8g4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSAXHJcbi8vIEZPTkNUSU9OIFBSSU5DSVBBTEUg4oCUIGNyZWF0ZUFuZ3VsYXJTdG9yZSgpXHJcbi8vIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgFxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZUFuZ3VsYXJTdG9yZTxTIGV4dGVuZHMgb2JqZWN0PihcclxuICBjb25maWc6IFMgJiBTdGF0b1N0b3JlQ29uZmlnPFM+XHJcbikge1xyXG4gIC8vIDEuIENyw6llciBsZSBzdG9yZSBjb3JlXHJcbiAgY29uc3QgY29yZVN0b3JlID0gY3JlYXRlU3RvcmUoY29uZmlnKVxyXG5cclxuICAvLyAyLiBDcsOpZXIgdW4gU2lnbmFsIHBvdXIgY2hhcXVlIHByb3ByacOpdMOpIGR1IHN0YXRlXHJcbiAgY29uc3Qgc2lnbmFsczogUmVjb3JkPHN0cmluZywgUmV0dXJuVHlwZTx0eXBlb2Ygc2lnbmFsPj4gPSB7fVxyXG4gIGNvbnN0IGluaXRpYWxTdGF0ZSA9IGNvcmVTdG9yZS5nZXRTdGF0ZSgpXHJcblxyXG4gIGZvciAoY29uc3Qga2V5IG9mIE9iamVjdC5rZXlzKGluaXRpYWxTdGF0ZSBhcyBvYmplY3QpKSB7XHJcbiAgICBzaWduYWxzW2tleV0gPSBzaWduYWwoKGluaXRpYWxTdGF0ZSBhcyBhbnkpW2tleV0pXHJcbiAgfVxyXG5cclxuICAvLyAzLiBTeW5jaHJvbmlzZXIgbGVzIFNpZ25hbHMgYXZlYyBsZSBzdGF0ZSBjb3JlXHJcbiAgY29yZVN0b3JlLnN1YnNjcmliZSgobmV3U3RhdGU6IGFueSkgPT4ge1xyXG4gICAgZm9yIChjb25zdCBrZXkgb2YgT2JqZWN0LmtleXMobmV3U3RhdGUpKSB7XHJcbiAgICAgIGlmIChzaWduYWxzW2tleV0pIHtcclxuICAgICAgICBzaWduYWxzW2tleV0uc2V0KG5ld1N0YXRlW2tleV0pXHJcbiAgICAgIH1cclxuICAgIH1cclxuICB9KVxyXG5cclxuICAvLyA0LiBDb25zdHJ1aXJlIGwgb2JqZXQgcHVibGljIEFuZ3VsYXJcclxuICBjb25zdCBhbmd1bGFyU3RvcmU6IGFueSA9IHtcclxuICAgIF9fc3RvcmVfXzogY29yZVN0b3JlLl9fc3RvcmVfX1xyXG4gIH1cclxuXHJcbiAgLy8gNS4gRXhwb3NlciBjaGFxdWUgcHJvcHJpw6l0w6kgY29tbWUgU2lnbmFsXHJcbiAgZm9yIChjb25zdCBrZXkgb2YgT2JqZWN0LmtleXMoaW5pdGlhbFN0YXRlIGFzIG9iamVjdCkpIHtcclxuICAgIE9iamVjdC5kZWZpbmVQcm9wZXJ0eShhbmd1bGFyU3RvcmUsIGtleSwge1xyXG4gICAgICBnZXQ6ICAgICAgICAgICgpID0+IHNpZ25hbHNba2V5XSxcclxuICAgICAgZW51bWVyYWJsZTogICB0cnVlLFxyXG4gICAgICBjb25maWd1cmFibGU6IHRydWVcclxuICAgIH0pXHJcbiAgfVxyXG5cclxuICAvLyA2LiBFeHBvc2VyIGNoYXF1ZSBjb21wdXRlZCBjb21tZSBTaWduYWwgY29tcHV0ZWRcclxuICBjb25zdCB7IGNvbXB1dGVkOiBjb21wdXRlZENvbmZpZyB9ID0gY29uZmlnIGFzIFN0YXRvU3RvcmVDb25maWc8Uz5cclxuICBpZiAoY29tcHV0ZWRDb25maWcpIHtcclxuICAgIGZvciAoY29uc3Qga2V5IG9mIE9iamVjdC5rZXlzKGNvbXB1dGVkQ29uZmlnKSkge1xyXG4gICAgICBjb25zdCBjb21wdXRlZFNpZ25hbCA9IGNvbXB1dGVkKCgpID0+IGNvcmVTdG9yZVtrZXldKVxyXG4gICAgICBPYmplY3QuZGVmaW5lUHJvcGVydHkoYW5ndWxhclN0b3JlLCBrZXksIHtcclxuICAgICAgICBnZXQ6ICAgICAgICAgICgpID0+IGNvbXB1dGVkU2lnbmFsLFxyXG4gICAgICAgIGVudW1lcmFibGU6ICAgdHJ1ZSxcclxuICAgICAgICBjb25maWd1cmFibGU6IHRydWVcclxuICAgICAgfSlcclxuICAgIH1cclxuICB9XHJcblxyXG4gIC8vIDcuIEV4cG9zZXIgY2hhcXVlIGFjdGlvbiBkaXJlY3RlbWVudFxyXG4gIGNvbnN0IHsgYWN0aW9ucyB9ID0gY29uZmlnIGFzIFN0YXRvU3RvcmVDb25maWc8Uz5cclxuICBpZiAoYWN0aW9ucykge1xyXG4gICAgZm9yIChjb25zdCBuYW1lIG9mIE9iamVjdC5rZXlzKGFjdGlvbnMpKSB7XHJcbiAgICAgIGFuZ3VsYXJTdG9yZVtuYW1lXSA9ICguLi5hcmdzOiB1bmtub3duW10pID0+XHJcbiAgICAgICAgY29yZVN0b3JlLl9fc3RvcmVfXy5kaXNwYXRjaChuYW1lLCAuLi5hcmdzKVxyXG4gICAgfVxyXG4gIH1cclxuXHJcbiAgLy8gOC4gQXBwZWxlciBvbkluaXQgc2kgZMOpZmluaVxyXG4gIGNvbnN0IHsgaG9va3MgfSA9IGNvbmZpZyBhcyBTdGF0b1N0b3JlQ29uZmlnPFM+XHJcbiAgaWYgKGhvb2tzPy5vbkluaXQpIHtcclxuICAgIGhvb2tzLm9uSW5pdChhbmd1bGFyU3RvcmUpXHJcbiAgfVxyXG5cclxuICAvLyA5LiBFeHBvc2VyIGRlc3Ryb3kgcG91ciBsZSBjbGVhbnVwXHJcbiAgYW5ndWxhclN0b3JlLl9fZGVzdHJveV9fID0gKCkgPT4ge1xyXG4gICAgY29yZVN0b3JlLl9fc3RvcmVfXy5kZXN0cm95KGFuZ3VsYXJTdG9yZSlcclxuICB9XHJcblxyXG4gIHJldHVybiBhbmd1bGFyU3RvcmVcclxufVxyXG5cclxuLy8g4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSAXHJcbi8vIENMQVNTRSBERSBCQVNFIOKAlCDDqXRlbmR1ZSBwYXIgU3RhdG9TdG9yZSgpXHJcbi8vIFB1YmxpcXVlIOKAlCBwYXMgZGUgbWVtYnJlcyBwcml2w6lzXHJcbi8vIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgFxyXG5ASW5qZWN0YWJsZSgpXHJcbmV4cG9ydCBjbGFzcyBTdGF0b1N0b3JlQmFzZSBpbXBsZW1lbnRzIE9uRGVzdHJveSB7XHJcbiAgc3RvcmVJbnN0YW5jZTogYW55XHJcblxyXG4gIGluaXRTdG9yZTxTIGV4dGVuZHMgb2JqZWN0Pihjb25maWc6IFMgJiBTdGF0b1N0b3JlQ29uZmlnPFM+KSB7XHJcbiAgICB0aGlzLnN0b3JlSW5zdGFuY2UgPSBjcmVhdGVBbmd1bGFyU3RvcmUoY29uZmlnKVxyXG5cclxuICAgIC8vIENvcGllciB0b3V0ZXMgbGVzIHByb3ByacOpdMOpcyBzdXIgdGhpc1xyXG4gICAgZm9yIChjb25zdCBrZXkgb2YgT2JqZWN0LmtleXModGhpcy5zdG9yZUluc3RhbmNlKSkge1xyXG4gICAgICBpZiAoa2V5ICE9PSAnX19zdG9yZV9fJyAmJiBrZXkgIT09ICdfX2Rlc3Ryb3lfXycpIHtcclxuICAgICAgICBPYmplY3QuZGVmaW5lUHJvcGVydHkodGhpcywga2V5LCB7XHJcbiAgICAgICAgICBnZXQ6ICAgICAgICAgICgpID0+IHRoaXMuc3RvcmVJbnN0YW5jZVtrZXldLFxyXG4gICAgICAgICAgZW51bWVyYWJsZTogICB0cnVlLFxyXG4gICAgICAgICAgY29uZmlndXJhYmxlOiB0cnVlXHJcbiAgICAgICAgfSlcclxuICAgICAgfVxyXG4gICAgfVxyXG5cclxuICAgIC8vIENvcGllciBsZXMgYWN0aW9uc1xyXG4gICAgY29uc3QgeyBhY3Rpb25zIH0gPSBjb25maWcgYXMgU3RhdG9TdG9yZUNvbmZpZzxTPlxyXG4gICAgaWYgKGFjdGlvbnMpIHtcclxuICAgIGZvciAoY29uc3QgbmFtZSBvZiBPYmplY3Qua2V5cyhhY3Rpb25zKSkge1xyXG4gICAgICAgIE9iamVjdC5kZWZpbmVQcm9wZXJ0eSh0aGlzLCBuYW1lLCB7XHJcbiAgICAgICAgZ2V0OiAgICAgICAgICAoKSA9PiB0aGlzLnN0b3JlSW5zdGFuY2VbbmFtZV0sXHJcbiAgICAgICAgZW51bWVyYWJsZTogICB0cnVlLFxyXG4gICAgICAgIGNvbmZpZ3VyYWJsZTogdHJ1ZVxyXG4gICAgICAgIH0pXHJcbiAgICB9XHJcbiAgICB9XHJcbiAgfVxyXG5cclxuICBuZ09uRGVzdHJveSgpIHtcclxuICAgIHRoaXMuc3RvcmVJbnN0YW5jZT8uX19kZXN0cm95X18oKVxyXG4gIH1cclxufVxyXG5cclxuLy8g4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSAXHJcbi8vIEZBQ1RPUlkg4oCUIFN0YXRvU3RvcmUoKVxyXG4vLyBDcsOpZSB1biBzZXJ2aWNlIEFuZ3VsYXIgaW5qZWN0YWJsZVxyXG4vLyBVc2FnZSA6XHJcbi8vICAgZXhwb3J0IGNsYXNzIFVzZXJTdG9yZSBleHRlbmRzIFN0YXRvU3RvcmUoe1xyXG4vLyAgICAgdXNlcjogbnVsbCxcclxuLy8gICAgIGFjdGlvbnM6IHsgLi4uIH1cclxuLy8gICB9KSB7fVxyXG4vLyDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIBcclxuXHJcbmV4cG9ydCBmdW5jdGlvbiBTdGF0b1N0b3JlPFMgZXh0ZW5kcyBvYmplY3Q+KFxyXG4gIGNvbmZpZzogUyAmIFN0YXRvU3RvcmVDb25maWc8Uz5cclxuKSB7XHJcbiAgQEluamVjdGFibGUoeyBwcm92aWRlZEluOiAncm9vdCcgfSlcclxuICBjbGFzcyBDb25jcmV0ZVN0b3JlIGV4dGVuZHMgU3RhdG9TdG9yZUJhc2Uge1xyXG4gICAgY29uc3RydWN0b3IoKSB7XHJcbiAgICAgIHN1cGVyKClcclxuICAgICAgdGhpcy5pbml0U3RvcmUoY29uZmlnKVxyXG4gICAgfVxyXG4gIH1cclxuXHJcbiAgcmV0dXJuIENvbmNyZXRlU3RvcmVcclxufSJdfQ==
|