@ngstato/angular 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/README.md ADDED
@@ -0,0 +1,388 @@
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
+ ![version](https://img.shields.io/badge/version-0.1.0-blue)
6
+ ![tests](https://img.shields.io/badge/tests-144%20%E2%9C%85-green)
7
+ ![bundle](https://img.shields.io/badge/bundle-~3KB-yellow)
8
+ ![angular](https://img.shields.io/badge/Angular-17%2B-red)
9
+ ![license](https://img.shields.io/badge/license-MIT-lightgrey)
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
+ ## Roadmap
342
+
343
+ ### v0.1 ✅ TERMINÉ
344
+ - `createStore()` — state, actions, computed, hooks
345
+ - `StatoHttp` — GET POST PUT PATCH DELETE avec auth, timeout, params
346
+ - `abortable()`, `debounced()`, `throttled()`, `retryable()`, `fromStream()`, `optimistic()`
347
+ - `@ngstato/angular` — Signals natifs, `provideStato()`, `injectStore()`
348
+ - DevTools — panel déplaçable, redimensionnable, minimisable
349
+ - `connectDevTools()` — connexion automatique store → DevTools
350
+ - Protection prod automatique via `isDevMode()`
351
+ - **144 tests — 100% passing**
352
+
353
+ ### v0.2 — Helpers avancés
354
+ - `exclusive()` — = exhaustMap NgRx
355
+ - `queued()` — = concatMap NgRx
356
+ - `store.on()` — réactions inter-stores
357
+ - Testing utilities
358
+ - DevTools time-travel
359
+
360
+ ### v0.3 — Persistance
361
+ - `withPersist()` — localStorage / sessionStorage / IndexedDB
362
+ - `withHistory()` — undo/redo
363
+ - SSR ready
364
+
365
+ ### v1.0 — Production ready
366
+ - `withEntities()`, Schematics CLI, ESLint plugin
367
+ - Documentation VitePress complète
368
+ - Benchmarks comparatifs
369
+
370
+ ---
371
+
372
+ ## Contribuer
373
+
374
+ ```bash
375
+ git clone https://github.com/becher/ngstato
376
+ cd ngstato
377
+ pnpm install # Node >= 18, pnpm >= 8
378
+ pnpm build
379
+ pnpm test # 144 tests
380
+ ```
381
+
382
+ Convention commits : `feat` / `fix` / `docs` / `test` / `refactor` / `chore`
383
+
384
+ ---
385
+
386
+ ## License
387
+
388
+ 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,39 @@
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 config;
6
+ private unsub?;
7
+ isOpen: import("@angular/core").WritableSignal<boolean>;
8
+ isMinimized: import("@angular/core").WritableSignal<boolean>;
9
+ activeTab: import("@angular/core").WritableSignal<"actions" | "state">;
10
+ logs: import("@angular/core").WritableSignal<ActionLog[]>;
11
+ selectedLog: import("@angular/core").WritableSignal<ActionLog>;
12
+ posX: import("@angular/core").WritableSignal<number>;
13
+ posY: import("@angular/core").WritableSignal<number>;
14
+ panelWidth: import("@angular/core").WritableSignal<number>;
15
+ panelHeight: import("@angular/core").WritableSignal<number>;
16
+ private isDragging;
17
+ private isResizing;
18
+ private dragOffsetX;
19
+ private dragOffsetY;
20
+ private startW;
21
+ private startH;
22
+ private startX;
23
+ private startY;
24
+ private boundMouseMove;
25
+ private boundMouseUp;
26
+ ngOnInit(): void;
27
+ ngOnDestroy(): void;
28
+ toggle(): void;
29
+ toggleMinimize(): void;
30
+ clear(): void;
31
+ selectLog(log: ActionLog): void;
32
+ formatTime(iso: string): string;
33
+ onDragStart(e: MouseEvent): void;
34
+ onResizeStart(e: MouseEvent): void;
35
+ onMouseMove(e: MouseEvent): void;
36
+ onMouseUp(): void;
37
+ static ɵfac: i0.ɵɵFactoryDeclaration<StatoDevToolsComponent, never>;
38
+ static ɵcmp: i0.ɵɵComponentDeclaration<StatoDevToolsComponent, "ngstato-devtools", never, {}, {}, never, never, true, never>;
39
+ }
@@ -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==