http-request-manager 18.7.19 → 18.7.21
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/ARCHITECTURE.md +483 -0
- package/DATABASE_README.md +1176 -0
- package/HTTP_MANAGER_README.md +579 -0
- package/HTTP_SINGNALS_MANAGER_README.md +654 -0
- package/HTTP_STATE_MANAGER_README.md +948 -0
- package/INTERCEPTOR_README.md +549 -0
- package/LOCAL_STORAGE_README.md +1056 -0
- package/STORE_STATE_MANAGER_README.md +1322 -0
- package/UTILS_README.md +1186 -0
- package/WS_MANAGER_README.md +613 -0
- package/ng-package.json +8 -0
- package/package.json +1 -12
- package/src/lib/http-request-manager.module.ts +132 -0
- package/src/lib/http-request-services-demo/database-data-demo/database-data-demo.component.html +65 -0
- package/src/lib/http-request-services-demo/database-data-demo/database-data-demo.component.scss +0 -0
- package/src/lib/http-request-services-demo/database-data-demo/database-data-demo.component.ts +224 -0
- package/src/lib/http-request-services-demo/http-request-services-demo.component.html +114 -0
- package/src/lib/http-request-services-demo/http-request-services-demo.component.scss +6 -0
- package/src/lib/http-request-services-demo/http-request-services-demo.component.ts +52 -0
- package/src/lib/http-request-services-demo/local-storage-demo/local-storage-demo.component.html +195 -0
- package/src/lib/http-request-services-demo/local-storage-demo/local-storage-demo.component.scss +17 -0
- package/src/lib/http-request-services-demo/local-storage-demo/local-storage-demo.component.ts +206 -0
- package/src/lib/http-request-services-demo/local-storage-signals-demo/local-storage-signals-demo.component.html +200 -0
- package/src/lib/http-request-services-demo/local-storage-signals-demo/local-storage-signals-demo.component.scss +17 -0
- package/src/lib/http-request-services-demo/local-storage-signals-demo/local-storage-signals-demo.component.ts +212 -0
- package/src/lib/http-request-services-demo/request-manager-demo/file-downloader/download-file/download-file.component.html +53 -0
- package/src/lib/http-request-services-demo/request-manager-demo/file-downloader/download-file/download-file.component.scss +60 -0
- package/src/lib/http-request-services-demo/request-manager-demo/file-downloader/download-file/download-file.component.ts +72 -0
- package/src/lib/http-request-services-demo/request-manager-demo/file-downloader/file-download.module.ts +28 -0
- package/src/lib/http-request-services-demo/request-manager-demo/file-downloader/file-downloader.component.html +10 -0
- package/src/lib/http-request-services-demo/request-manager-demo/file-downloader/file-downloader.component.scss +29 -0
- package/src/lib/http-request-services-demo/request-manager-demo/file-downloader/file-downloader.component.ts +100 -0
- package/src/lib/http-request-services-demo/request-manager-demo/file-downloader/models/download-labels-model.ts +22 -0
- package/src/lib/http-request-services-demo/request-manager-demo/file-downloader/spinner/spinner.component.html +8 -0
- package/src/lib/http-request-services-demo/request-manager-demo/file-downloader/spinner/spinner.component.scss +19 -0
- package/src/lib/http-request-services-demo/request-manager-demo/file-downloader/spinner/spinner.component.ts +26 -0
- package/src/lib/http-request-services-demo/request-manager-demo/models/app-session.model.ts +30 -0
- package/src/lib/http-request-services-demo/request-manager-demo/models/app.model.ts +19 -0
- package/src/lib/http-request-services-demo/request-manager-demo/models/get-sample.model.ts +25 -0
- package/src/lib/http-request-services-demo/request-manager-demo/models/sample-ai-prompt.ts +19 -0
- package/src/lib/http-request-services-demo/request-manager-demo/models/sample-client-details.ts +24 -0
- package/src/lib/http-request-services-demo/request-manager-demo/models/sample-client-info.ts +30 -0
- package/src/lib/http-request-services-demo/request-manager-demo/models/sample-client.model.ts +49 -0
- package/src/lib/http-request-services-demo/request-manager-demo/models/sample-mapper-client-info.ts +33 -0
- package/src/lib/http-request-services-demo/request-manager-demo/request-manager-demo.component.html +392 -0
- package/src/lib/http-request-services-demo/request-manager-demo/request-manager-demo.component.scss +24 -0
- package/src/lib/http-request-services-demo/request-manager-demo/request-manager-demo.component.ts +461 -0
- package/src/lib/http-request-services-demo/request-manager-state-demo/request-manager-state-demo.component.html +393 -0
- package/src/lib/http-request-services-demo/request-manager-state-demo/request-manager-state-demo.component.scss +24 -0
- package/src/lib/http-request-services-demo/request-manager-state-demo/request-manager-state-demo.component.ts +421 -0
- package/src/lib/http-request-services-demo/request-manager-state-demo/services/state-manager-demo.service.ts +87 -0
- package/src/lib/http-request-services-demo/request-manager-ws-demo/components/services/state-data-request.service.ts +120 -0
- package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-ai-messaging/ws-ai-messaging.component.css +0 -0
- package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-ai-messaging/ws-ai-messaging.component.html +3 -0
- package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-ai-messaging/ws-ai-messaging.component.ts +16 -0
- package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-chats/ws-chats.component.css +0 -0
- package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-chats/ws-chats.component.html +3 -0
- package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-chats/ws-chats.component.ts +16 -0
- package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-data-control/ws-data-control.component.css +31 -0
- package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-data-control/ws-data-control.component.html +72 -0
- package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-data-control/ws-data-control.component.scss +41 -0
- package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-data-control/ws-data-control.component.spec.ts +205 -0
- package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-data-control/ws-data-control.component.ts +77 -0
- package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-messaging/ws-messaging.component.css +11 -0
- package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-messaging/ws-messaging.component.html +96 -0
- package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-messaging/ws-messaging.component.spec.ts +31 -0
- package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-messaging/ws-messaging.component.ts +229 -0
- package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-notifications/ws-notifications.component.css +30 -0
- package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-notifications/ws-notifications.component.html +172 -0
- package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-notifications/ws-notifications.component.spec.ts +31 -0
- package/src/lib/http-request-services-demo/request-manager-ws-demo/components/ws-notifications/ws-notifications.component.ts +239 -0
- package/src/lib/http-request-services-demo/request-manager-ws-demo/models/oidc-client.model.ts +31 -0
- package/src/lib/http-request-services-demo/request-manager-ws-demo/models/user-data.model.ts +32 -0
- package/src/lib/http-request-services-demo/request-manager-ws-demo/request-manager-ws-demo.component.css +0 -0
- package/src/lib/http-request-services-demo/request-manager-ws-demo/request-manager-ws-demo.component.html +84 -0
- package/src/lib/http-request-services-demo/request-manager-ws-demo/request-manager-ws-demo.component.ts +41 -0
- package/src/lib/http-request-services-demo/request-manager-ws-demo/services/index.ts +3 -0
- package/src/lib/http-request-services-demo/request-manager-ws-demo/services/message-service-demo.service.ts +83 -0
- package/src/lib/http-request-services-demo/request-manager-ws-demo/services/notification-service-demo.service.ts +147 -0
- package/src/lib/http-request-services-demo/request-manager-ws-demo/services/state-service-demo.service.ts +158 -0
- package/src/lib/http-request-services-demo/request-signals-manager-demo/file-downloader/download-file/download-file.component.html +53 -0
- package/src/lib/http-request-services-demo/request-signals-manager-demo/file-downloader/download-file/download-file.component.scss +60 -0
- package/src/lib/http-request-services-demo/request-signals-manager-demo/file-downloader/download-file/download-file.component.ts +72 -0
- package/src/lib/http-request-services-demo/request-signals-manager-demo/file-downloader/file-download.module.ts +28 -0
- package/src/lib/http-request-services-demo/request-signals-manager-demo/file-downloader/file-downloader.component.html +10 -0
- package/src/lib/http-request-services-demo/request-signals-manager-demo/file-downloader/file-downloader.component.scss +29 -0
- package/src/lib/http-request-services-demo/request-signals-manager-demo/file-downloader/file-downloader.component.ts +100 -0
- package/src/lib/http-request-services-demo/request-signals-manager-demo/file-downloader/models/download-labels-model.ts +22 -0
- package/src/lib/http-request-services-demo/request-signals-manager-demo/file-downloader/spinner/spinner.component.html +8 -0
- package/src/lib/http-request-services-demo/request-signals-manager-demo/file-downloader/spinner/spinner.component.scss +19 -0
- package/src/lib/http-request-services-demo/request-signals-manager-demo/file-downloader/spinner/spinner.component.ts +26 -0
- package/src/lib/http-request-services-demo/request-signals-manager-demo/models/app-session.model.ts +30 -0
- package/src/lib/http-request-services-demo/request-signals-manager-demo/models/app.model.ts +19 -0
- package/src/lib/http-request-services-demo/request-signals-manager-demo/models/get-sample.model.ts +25 -0
- package/src/lib/http-request-services-demo/request-signals-manager-demo/models/sample-ai-prompt.ts +19 -0
- package/src/lib/http-request-services-demo/request-signals-manager-demo/models/sample-client-details.ts +24 -0
- package/src/lib/http-request-services-demo/request-signals-manager-demo/models/sample-client-info.ts +30 -0
- package/src/lib/http-request-services-demo/request-signals-manager-demo/models/sample-client.model.ts +49 -0
- package/src/lib/http-request-services-demo/request-signals-manager-demo/models/sample-mapper-client-info.ts +33 -0
- package/src/lib/http-request-services-demo/request-signals-manager-demo/request-signals-manager-demo.component.html +380 -0
- package/src/lib/http-request-services-demo/request-signals-manager-demo/request-signals-manager-demo.component.scss +24 -0
- package/src/lib/http-request-services-demo/request-signals-manager-demo/request-signals-manager-demo.component.ts +410 -0
- package/src/lib/http-request-services-demo/store-state-manager-demo/models/settings.model.ts +28 -0
- package/src/lib/http-request-services-demo/store-state-manager-demo/services/settings-state.service.ts +48 -0
- package/src/lib/http-request-services-demo/store-state-manager-demo/store-state-manager-demo.component.css +0 -0
- package/src/lib/http-request-services-demo/store-state-manager-demo/store-state-manager-demo.component.html +23 -0
- package/src/lib/http-request-services-demo/store-state-manager-demo/store-state-manager-demo.component.ts +36 -0
- package/src/lib/index.ts +3 -0
- package/src/lib/interceptors/credentials.interceptor.ts +16 -0
- package/src/lib/interceptors/index.ts +6 -0
- package/src/lib/interceptors/models/error-settings.model.ts +22 -0
- package/src/lib/interceptors/models/index.ts +2 -0
- package/src/lib/interceptors/proxy-debugger.interceptor.ts +46 -0
- package/src/lib/interceptors/request-error.interceptor.ts +65 -0
- package/src/lib/interceptors/request-header.interceptor.ts +53 -0
- package/src/lib/models/config-http-options.model.ts +42 -0
- package/src/lib/models/config-local-storage-options.model.ts +27 -0
- package/src/lib/models/config-options.model.ts +27 -0
- package/src/lib/models/config-token.model.ts +9 -0
- package/src/lib/models/data-type.enum.ts +5 -0
- package/src/lib/models/database-storage.model.ts +24 -0
- package/src/lib/models/index.ts +12 -0
- package/src/lib/models/retry-options.model.ts +22 -0
- package/src/lib/services/database-manager-service/database.manager.service.ts +262 -0
- package/src/lib/services/database-manager-service/db.storage.service.ts +207 -0
- package/src/lib/services/database-manager-service/index.ts +4 -0
- package/src/lib/services/database-manager-service/models/index.ts +2 -0
- package/src/lib/services/database-manager-service/models/table-schema.ts +33 -0
- package/src/lib/services/index.ts +12 -0
- package/src/lib/services/local-storage-manager-service/index.ts +4 -0
- package/src/lib/services/local-storage-manager-service/local-storage-manager.service.spec.ts +71 -0
- package/src/lib/services/local-storage-manager-service/local-storage-manager.service.ts +426 -0
- package/src/lib/services/local-storage-manager-service/local-storage-signals-manager.service.spec.ts +67 -0
- package/src/lib/services/local-storage-manager-service/local-storage-signals-manager.service.ts +345 -0
- package/src/lib/services/local-storage-manager-service/models/global-store-options.model.ts +30 -0
- package/src/lib/services/local-storage-manager-service/models/index.ts +6 -0
- package/src/lib/services/local-storage-manager-service/models/setting-options.model.ts +35 -0
- package/src/lib/services/local-storage-manager-service/models/storage-data.model.ts +24 -0
- package/src/lib/services/local-storage-manager-service/models/storage-option.model.ts +32 -0
- package/src/lib/services/local-storage-manager-service/models/storage-type.enum.ts +5 -0
- package/src/lib/services/request-manager-services/README.md +268 -0
- package/src/lib/services/request-manager-services/http-manager-signals.service.ts +246 -0
- package/src/lib/services/request-manager-services/http-manager.service.spec.ts +232 -0
- package/src/lib/services/request-manager-services/http-manager.service.ts +274 -0
- package/src/lib/services/request-manager-services/index.ts +8 -0
- package/src/lib/services/request-manager-services/request-signals.service.ts +214 -0
- package/src/lib/services/request-manager-services/request.service.ts +309 -0
- package/src/lib/services/request-manager-services/rxjs-operators/countdown.ts +17 -0
- package/src/lib/services/request-manager-services/rxjs-operators/delay-retry.ts +16 -0
- package/src/lib/services/request-manager-services/rxjs-operators/index.ts +4 -0
- package/src/lib/services/request-manager-services/rxjs-operators/request-polling.ts +35 -0
- package/src/lib/services/request-manager-services/rxjs-operators/request-streaming.ts +436 -0
- package/src/lib/services/request-manager-state-service/http-manager-state.store.ts +1321 -0
- package/src/lib/services/request-manager-state-service/index.ts +3 -0
- package/src/lib/services/request-manager-state-service/models/api-request.model.ts +61 -0
- package/src/lib/services/request-manager-state-service/models/index.ts +6 -0
- package/src/lib/services/request-manager-state-service/models/request-options.model.ts +22 -0
- package/src/lib/services/request-manager-state-service/models/stream-type.enum.ts +13 -0
- package/src/lib/services/request-manager-state-service/models/ws-options.model.ts +39 -0
- package/src/lib/services/store-state-manager-service/index.ts +3 -0
- package/src/lib/services/store-state-manager-service/models/index.ts +2 -0
- package/src/lib/services/store-state-manager-service/models/state-storage-options.model.ts +24 -0
- package/src/lib/services/store-state-manager-service/store-state-manager.service.ts +88 -0
- package/src/lib/services/utils/app.service.spec.ts +25 -0
- package/src/lib/services/utils/app.service.ts +21 -0
- package/src/lib/services/utils/encryption/README.md +79 -0
- package/src/lib/services/utils/encryption/asymmetrical-encryption.service.ts +282 -0
- package/src/lib/services/utils/encryption/encryption-test.service.ts +39 -0
- package/src/lib/services/utils/encryption/index.ts +5 -0
- package/src/lib/services/utils/encryption/random.ts +81 -0
- package/src/lib/services/utils/encryption/symmetrical-encryption.service.ts +93 -0
- package/src/lib/services/utils/headers.service.spec.ts +80 -0
- package/src/lib/services/utils/headers.service.ts +18 -0
- package/src/lib/services/utils/index.ts +7 -0
- package/src/lib/services/utils/object-merger.service.spec.ts +18 -0
- package/src/lib/services/utils/object-merger.service.ts +78 -0
- package/src/lib/services/utils/path-query.service.spec.ts +117 -0
- package/src/lib/services/utils/path-query.service.ts +69 -0
- package/src/lib/services/utils/random-color.utils.ts +83 -0
- package/src/lib/services/utils/utils.service.spec.ts +165 -0
- package/src/lib/services/utils/utils.service.ts +192 -0
- package/src/lib/services/ws-manager-service/index.ts +4 -0
- package/src/lib/services/ws-manager-service/models/channel-info.model.ts +24 -0
- package/src/lib/services/ws-manager-service/models/channel-message-data.model.ts +24 -0
- package/src/lib/services/ws-manager-service/models/channel-message.model.ts +24 -0
- package/src/lib/services/ws-manager-service/models/communication-type.enum.ts +5 -0
- package/src/lib/services/ws-manager-service/models/index.ts +5 -0
- package/src/lib/services/ws-manager-service/models/ws-user.model.ts +38 -0
- package/src/lib/services/ws-manager-service/services/index.ts +3 -0
- package/src/lib/services/ws-manager-service/services/websocket.service.ts +392 -0
- package/src/public-api.ts +14 -0
- package/tsconfig.lib.json +32 -0
- package/tsconfig.lib.prod.json +10 -0
- package/tsconfig.spec.json +14 -0
- package/fesm2022/http-request-manager.mjs +0 -7634
- package/fesm2022/http-request-manager.mjs.map +0 -1
- package/http-request-manager-18.7.19.tgz +0 -0
- package/types/http-request-manager.d.ts +0 -2278
package/UTILS_README.md
ADDED
|
@@ -0,0 +1,1186 @@
|
|
|
1
|
+
# Utils Service
|
|
2
|
+
|
|
3
|
+
The `UtilsService` provides a collection of utility functions for common tasks including JSON handling, type checking, expiration management, conversions, and validation. These utilities simplify everyday development tasks.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This service provides:
|
|
8
|
+
|
|
9
|
+
- **JSON Handling** - Safe JSON parsing and stringification
|
|
10
|
+
- **Type Checking** - Runtime type validation and verification
|
|
11
|
+
- **Expiration Management** - Convert duration strings to epoch timestamps and check expiry
|
|
12
|
+
- **Conversions** - Base32, binary, and other data format conversions
|
|
13
|
+
- **Object Operations** - Deep comparison and property access
|
|
14
|
+
- **Time Utilities** - Date/time calculations and formatting
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { HttpRequestManagerModule } from 'http-request-manager';
|
|
20
|
+
|
|
21
|
+
@NgModule({
|
|
22
|
+
imports: [HttpRequestManagerModule.forRoot({})]
|
|
23
|
+
})
|
|
24
|
+
export class AppModule { }
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Basic Usage
|
|
28
|
+
|
|
29
|
+
### Service Injection
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { Component, inject } from '@angular/core';
|
|
33
|
+
import { UtilsService } from 'http-request-manager';
|
|
34
|
+
|
|
35
|
+
@Component({
|
|
36
|
+
selector: 'app-utils-demo',
|
|
37
|
+
template: `
|
|
38
|
+
<div class="utils-demo">
|
|
39
|
+
<h2>Utils Service Demo</h2>
|
|
40
|
+
|
|
41
|
+
<div class="json-section">
|
|
42
|
+
<h3>JSON Operations</h3>
|
|
43
|
+
<button (click)="testJsonOperations()">Test JSON Operations</button>
|
|
44
|
+
<div *ngIf="jsonResult">
|
|
45
|
+
<p>Original: {{ jsonResult.original }}</p>
|
|
46
|
+
<p>Parsed: {{ jsonResult.parsed | json }}</p>
|
|
47
|
+
<p>Stringified: {{ jsonResult.stringified }}</p>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<div class="type-section">
|
|
52
|
+
<h3>Type Checking</h3>
|
|
53
|
+
<input [(ngModel)]="testValue" placeholder="Enter a value">
|
|
54
|
+
<button (click)="checkTypes()">Check Types</button>
|
|
55
|
+
<div *ngIf="typeResult">
|
|
56
|
+
<p>Is String: {{ typeResult.isString }}</p>
|
|
57
|
+
<p>Is Object: {{ typeResult.isObject }}</p>
|
|
58
|
+
<p>Is JSON: {{ typeResult.isJson }}</p>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<div class="expiry-section">
|
|
63
|
+
<h3>Expiration Management</h3>
|
|
64
|
+
<button (click)="testExpiration()">Test Expiration</button>
|
|
65
|
+
<div *ngIf="expiryResult">
|
|
66
|
+
<p>1 day from now: {{ expiryResult.oneDay | date:'medium' }}</p>
|
|
67
|
+
<p>Current time: {{ expiryResult.current | date:'medium' }}</p>
|
|
68
|
+
<p>Has 1 day expired: {{ expiryResult.hasExpired }}</p>
|
|
69
|
+
<p>Time remaining: {{ expiryResult.timeRemaining }}</p>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<div class="conversion-section">
|
|
74
|
+
<h3>Conversions</h3>
|
|
75
|
+
<button (click)="testConversions()">Test Conversions</button>
|
|
76
|
+
<div *ngIf="conversionResult">
|
|
77
|
+
<p>Base32 to Hex: {{ conversionResult.base32ToHex }}</p>
|
|
78
|
+
<p>Binary to Hex: {{ conversionResult.binaryToHex }}</p>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
`
|
|
83
|
+
})
|
|
84
|
+
export class UtilsDemoComponent {
|
|
85
|
+
private utils = inject(UtilsService);
|
|
86
|
+
|
|
87
|
+
testValue = '';
|
|
88
|
+
jsonResult: any;
|
|
89
|
+
typeResult: any;
|
|
90
|
+
expiryResult: any;
|
|
91
|
+
conversionResult: any;
|
|
92
|
+
|
|
93
|
+
testJsonOperations() {
|
|
94
|
+
const testObj = { name: 'John', age: 30, active: true };
|
|
95
|
+
|
|
96
|
+
this.jsonResult = {
|
|
97
|
+
original: testObj,
|
|
98
|
+
parsed: this.utils.stringToJSON(JSON.stringify(testObj)),
|
|
99
|
+
stringified: this.utils.JSONToString(testObj)
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
checkTypes() {
|
|
104
|
+
this.typeResult = {
|
|
105
|
+
isString: this.utils.isString(this.testValue),
|
|
106
|
+
isObject: this.utils.isObject(this.testValue),
|
|
107
|
+
isJson: this.utils.isJSON(this.testValue)
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
testExpiration() {
|
|
112
|
+
const oneDayFromNow = this.utils.expires('1d');
|
|
113
|
+
const currentTime = Math.floor(Date.now() / 1000);
|
|
114
|
+
|
|
115
|
+
this.expiryResult = {
|
|
116
|
+
oneDay: oneDayFromNow ? new Date(oneDayFromNow * 1000) : null,
|
|
117
|
+
current: new Date(currentTime * 1000),
|
|
118
|
+
hasExpired: this.utils.hasExpired(oneDayFromNow || 0),
|
|
119
|
+
timeRemaining: this.utils.expiresIn(oneDayFromNow || 0)
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
testConversions() {
|
|
124
|
+
this.conversionResult = {
|
|
125
|
+
base32ToHex: this.utils.base32ToHex('JBSWY3DPEHPK3PXP'),
|
|
126
|
+
binaryToHex: this.utils.binaryToHex('11000011100011110000')
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## API Reference
|
|
133
|
+
|
|
134
|
+
### JSON Operations
|
|
135
|
+
|
|
136
|
+
#### JSONToString(value: any): string
|
|
137
|
+
|
|
138
|
+
Safely convert value to JSON string:
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
const obj = { name: 'John', age: 30 };
|
|
142
|
+
const jsonString = this.utils.JSONToString(obj);
|
|
143
|
+
// Returns: '{"name":"John","age":30}'
|
|
144
|
+
|
|
145
|
+
const number = 42;
|
|
146
|
+
const stringValue = this.utils.JSONToString(number);
|
|
147
|
+
// Returns: '42'
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
#### stringToJSON(value: string): any
|
|
151
|
+
|
|
152
|
+
Safely parse JSON string:
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
const jsonString = '{"name":"John","age":30}';
|
|
156
|
+
const obj = this.utils.stringToJSON(jsonString);
|
|
157
|
+
// Returns: { name: 'John', age: 30 }
|
|
158
|
+
|
|
159
|
+
const invalidJson = '{invalid json}';
|
|
160
|
+
const result = this.utils.stringToJSON(invalidJson);
|
|
161
|
+
// Returns: '{invalid json}' (unchanged)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Type Checking
|
|
165
|
+
|
|
166
|
+
#### isString(value: any): boolean
|
|
167
|
+
|
|
168
|
+
Check if value is a string:
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
this.utils.isString('hello'); // true
|
|
172
|
+
this.utils.isString(123); // false
|
|
173
|
+
this.utils.isString(null); // false
|
|
174
|
+
this.utils.isString(undefined); // false
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
#### isObject(value: any): boolean
|
|
178
|
+
|
|
179
|
+
Check if value is an object:
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
this.utils.isObject({}); // true
|
|
183
|
+
this.utils.isObject({ name: 'John' }); // true
|
|
184
|
+
this.utils.isObject(null); // false
|
|
185
|
+
this.utils.isObject([]); // true (arrays are objects)
|
|
186
|
+
this.utils.isObject('string'); // false
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
#### isJSON(value: any): boolean
|
|
190
|
+
|
|
191
|
+
Check if value is valid JSON:
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
this.utils.isJSON('{"name":"John"}'); // true
|
|
195
|
+
this.utils.isJSON('not json'); // false
|
|
196
|
+
this.utils.isJSON(123); // false
|
|
197
|
+
this.utils.isJSON(null); // false
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Property Access
|
|
201
|
+
|
|
202
|
+
#### getValueByProp(obj: any, prop: string): any
|
|
203
|
+
|
|
204
|
+
Get property value by string path:
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
const obj = { user: { name: 'John', settings: { theme: 'dark' } } };
|
|
208
|
+
|
|
209
|
+
this.utils.getValueByProp(obj, 'user.name'); // undefined (doesn't support nesting)
|
|
210
|
+
this.utils.getValueByProp(obj.user, 'name'); // 'John'
|
|
211
|
+
this.utils.getValueByProp(obj.user.settings, 'theme'); // 'dark'
|
|
212
|
+
this.utils.getValueByProp(obj, 'nonexistent'); // false
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Object Comparison
|
|
216
|
+
|
|
217
|
+
#### objectsEqual(x: any, y: any): boolean
|
|
218
|
+
|
|
219
|
+
Deep comparison of two objects:
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
const obj1 = { name: 'John', age: 30, active: true };
|
|
223
|
+
const obj2 = { name: 'John', age: 30, active: true };
|
|
224
|
+
const obj3 = { name: 'John', age: 31, active: true };
|
|
225
|
+
|
|
226
|
+
this.utils.objectsEqual(obj1, obj2); // true
|
|
227
|
+
this.utils.objectsEqual(obj1, obj3); // false
|
|
228
|
+
|
|
229
|
+
// Nested objects
|
|
230
|
+
const nested1 = { user: { name: 'John', settings: { theme: 'dark' } } };
|
|
231
|
+
const nested2 = { user: { name: 'John', settings: { theme: 'dark' } } };
|
|
232
|
+
this.utils.objectsEqual(nested1, nested2); // true
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### HTTP Operations
|
|
236
|
+
|
|
237
|
+
#### getJSON(file: string): Observable<any>
|
|
238
|
+
|
|
239
|
+
Get JSON file from assets:
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
this.utils.getJSON('data/config.json').subscribe(config => {
|
|
243
|
+
console.log('Config loaded:', config);
|
|
244
|
+
});
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Time Properties
|
|
248
|
+
|
|
249
|
+
#### today: number
|
|
250
|
+
|
|
251
|
+
Get current time in milliseconds:
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
const currentTime = this.utils.today;
|
|
255
|
+
console.log('Current time (ms):', currentTime);
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Expiration Management
|
|
259
|
+
|
|
260
|
+
#### expires(str?: string): number | undefined
|
|
261
|
+
|
|
262
|
+
Convert duration string to epoch timestamp:
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
// Time units: y=years, m=months, w=weeks, d=days, hr=hours, mn=minutes, s=seconds
|
|
266
|
+
|
|
267
|
+
this.utils.expires('1d'); // Current time + 1 day
|
|
268
|
+
this.utils.expires('2h'); // Current time + 2 hours
|
|
269
|
+
this.utils.expires('30m'); // Current time + 30 minutes
|
|
270
|
+
this.utils.expires('1w'); // Current time + 1 week
|
|
271
|
+
this.utils.expires('1y'); // Current time + 1 year
|
|
272
|
+
|
|
273
|
+
this.utils.expires(); // undefined (no duration provided)
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
#### hasExpired(expiryDate: number): boolean
|
|
277
|
+
|
|
278
|
+
Check if timestamp has expired:
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
const futureTime = this.utils.expires('1d');
|
|
282
|
+
const pastTime = Math.floor(Date.now() / 1000) - 3600; // 1 hour ago
|
|
283
|
+
|
|
284
|
+
this.utils.hasExpired(futureTime); // false
|
|
285
|
+
this.utils.hasExpired(pastTime); // true
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
#### hasExpiry(setting: any): boolean
|
|
289
|
+
|
|
290
|
+
Check if setting has expiration:
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
const setting1 = { expires: 1234567890 };
|
|
294
|
+
const setting2 = { encrypted: true };
|
|
295
|
+
|
|
296
|
+
this.utils.hasExpiry(setting1); // true
|
|
297
|
+
this.utils.hasExpiry(setting2); // false
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
#### expiresIn(expiryDate: number): string | undefined
|
|
301
|
+
|
|
302
|
+
Get human-readable time remaining:
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
const oneDayFromNow = this.utils.expires('1d');
|
|
306
|
+
const oneHourFromNow = this.utils.expires('1h');
|
|
307
|
+
|
|
308
|
+
console.log(this.utils.expiresIn(oneDayFromNow)); // "1d"
|
|
309
|
+
console.log(this.utils.expiresIn(oneHourFromNow)); // "1h"
|
|
310
|
+
|
|
311
|
+
// For expired dates
|
|
312
|
+
const pastTime = Math.floor(Date.now() / 1000) - 3600;
|
|
313
|
+
console.log(this.utils.expiresIn(pastTime)); // "Expired"
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Conversions
|
|
317
|
+
|
|
318
|
+
#### base32ToHex(base32: string): string
|
|
319
|
+
|
|
320
|
+
Convert Base32 string to hexadecimal:
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
this.utils.base32ToHex('JBSWY3DPEHPK3PXP'); // '48656C6C6F20576F726C64'
|
|
324
|
+
this.utils.base32ToHex('MY'); // '66'
|
|
325
|
+
this.utils.base32ToHex('MZXQ6==='); // '6C'
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
#### binaryToHex(binary: string): string
|
|
329
|
+
|
|
330
|
+
Convert binary string to hexadecimal:
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
this.utils.binaryToHex('11000011100011110000'); // 'C78F0'
|
|
334
|
+
this.utils.binaryToHex('10101010'); // 'AA000000'
|
|
335
|
+
this.utils.binaryToHex('1111'); // 'F000'
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### String Utilities
|
|
339
|
+
|
|
340
|
+
#### lc(str: string): string
|
|
341
|
+
|
|
342
|
+
Convert string to lowercase with special character replacement:
|
|
343
|
+
|
|
344
|
+
```typescript
|
|
345
|
+
this.utils.lc('Hello World!'); // 'helloworld'
|
|
346
|
+
this.utils.lc('Test-Case_String'); // 'testcasestring'
|
|
347
|
+
this.utils.lc('UPPER CASE'); // 'uppercase'
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
## Advanced Examples
|
|
351
|
+
|
|
352
|
+
### Data Validation Service
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
import { Injectable } from '@angular/core';
|
|
356
|
+
import { UtilsService } from 'http-request-manager';
|
|
357
|
+
|
|
358
|
+
interface ValidationResult {
|
|
359
|
+
isValid: boolean;
|
|
360
|
+
errors: string[];
|
|
361
|
+
sanitizedData?: any;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
@Injectable({
|
|
365
|
+
providedIn: 'root'
|
|
366
|
+
})
|
|
367
|
+
export class DataValidationService {
|
|
368
|
+
constructor(private utils: UtilsService) {}
|
|
369
|
+
|
|
370
|
+
validateAndSanitize<T>(data: any, schema: any): ValidationResult {
|
|
371
|
+
const errors: string[] = [];
|
|
372
|
+
let sanitizedData: any = {};
|
|
373
|
+
|
|
374
|
+
try {
|
|
375
|
+
// Check if data is object
|
|
376
|
+
if (!this.utils.isObject(data)) {
|
|
377
|
+
errors.push('Data must be an object');
|
|
378
|
+
return { isValid: false, errors };
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Sanitize each field according to schema
|
|
382
|
+
for (const [field, rules] of Object.entries(schema)) {
|
|
383
|
+
const value = data[field];
|
|
384
|
+
const sanitizedValue = this.sanitizeField(field, value, rules as any);
|
|
385
|
+
|
|
386
|
+
if (sanitizedValue !== undefined) {
|
|
387
|
+
sanitizedData[field] = sanitizedValue;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return {
|
|
392
|
+
isValid: errors.length === 0,
|
|
393
|
+
errors,
|
|
394
|
+
sanitizedData
|
|
395
|
+
};
|
|
396
|
+
} catch (error) {
|
|
397
|
+
errors.push(`Validation error: ${error}`);
|
|
398
|
+
return { isValid: false, errors };
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
private sanitizeField(field: string, value: any, rules: any): any {
|
|
403
|
+
// Handle required fields
|
|
404
|
+
if (rules.required && (value === undefined || value === null)) {
|
|
405
|
+
throw new Error(`Field '${field}' is required`);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (value === undefined || value === null) {
|
|
409
|
+
return rules.default;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Type validation
|
|
413
|
+
switch (rules.type) {
|
|
414
|
+
case 'string':
|
|
415
|
+
if (typeof value !== 'string') {
|
|
416
|
+
throw new Error(`Field '${field}' must be a string`);
|
|
417
|
+
}
|
|
418
|
+
break;
|
|
419
|
+
|
|
420
|
+
case 'number':
|
|
421
|
+
if (typeof value !== 'number' && isNaN(Number(value))) {
|
|
422
|
+
throw new Error(`Field '${field}' must be a number`);
|
|
423
|
+
}
|
|
424
|
+
value = Number(value);
|
|
425
|
+
break;
|
|
426
|
+
|
|
427
|
+
case 'boolean':
|
|
428
|
+
if (typeof value !== 'boolean') {
|
|
429
|
+
throw new Error(`Field '${field}' must be a boolean`);
|
|
430
|
+
}
|
|
431
|
+
break;
|
|
432
|
+
|
|
433
|
+
case 'object':
|
|
434
|
+
if (!this.utils.isObject(value)) {
|
|
435
|
+
throw new Error(`Field '${field}' must be an object`);
|
|
436
|
+
}
|
|
437
|
+
break;
|
|
438
|
+
|
|
439
|
+
case 'json':
|
|
440
|
+
if (!this.utils.isJSON(value)) {
|
|
441
|
+
throw new Error(`Field '${field}' must be valid JSON`);
|
|
442
|
+
}
|
|
443
|
+
value = this.utils.stringToJSON(value);
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// String-specific rules
|
|
448
|
+
if (rules.type === 'string') {
|
|
449
|
+
// Min/max length
|
|
450
|
+
if (rules.minLength && value.length < rules.minLength) {
|
|
451
|
+
throw new Error(`Field '${field}' must be at least ${rules.minLength} characters`);
|
|
452
|
+
}
|
|
453
|
+
if (rules.maxLength && value.length > rules.maxLength) {
|
|
454
|
+
throw new Error(`Field '${field}' must be no more than ${rules.maxLength} characters`);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Pattern matching
|
|
458
|
+
if (rules.pattern && !rules.pattern.test(value)) {
|
|
459
|
+
throw new Error(`Field '${field}' does not match required pattern`);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Sanitization
|
|
463
|
+
if (rules.lowercase) {
|
|
464
|
+
value = value.toLowerCase();
|
|
465
|
+
}
|
|
466
|
+
if (rules.trim) {
|
|
467
|
+
value = value.trim();
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Number-specific rules
|
|
472
|
+
if (rules.type === 'number') {
|
|
473
|
+
if (rules.min !== undefined && value < rules.min) {
|
|
474
|
+
throw new Error(`Field '${field}' must be at least ${rules.min}`);
|
|
475
|
+
}
|
|
476
|
+
if (rules.max !== undefined && value > rules.max) {
|
|
477
|
+
throw new Error(`Field '${field}' must be no more than ${rules.max}`);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Array-specific rules
|
|
482
|
+
if (rules.type === 'array') {
|
|
483
|
+
if (!Array.isArray(value)) {
|
|
484
|
+
throw new Error(`Field '${field}' must be an array`);
|
|
485
|
+
}
|
|
486
|
+
if (rules.minItems && value.length < rules.minItems) {
|
|
487
|
+
throw new Error(`Field '${field}' must have at least ${rules.minItems} items`);
|
|
488
|
+
}
|
|
489
|
+
if (rules.maxItems && value.length > rules.maxItems) {
|
|
490
|
+
throw new Error(`Field '${field}' must have no more than ${rules.maxItems} items`);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
return value;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Predefined schemas
|
|
498
|
+
getUserSchema() {
|
|
499
|
+
return {
|
|
500
|
+
name: {
|
|
501
|
+
type: 'string',
|
|
502
|
+
required: true,
|
|
503
|
+
minLength: 2,
|
|
504
|
+
maxLength: 50,
|
|
505
|
+
pattern: /^[a-zA-Z\s]+$/,
|
|
506
|
+
trim: true
|
|
507
|
+
},
|
|
508
|
+
email: {
|
|
509
|
+
type: 'string',
|
|
510
|
+
required: true,
|
|
511
|
+
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
|
|
512
|
+
lowercase: true,
|
|
513
|
+
trim: true
|
|
514
|
+
},
|
|
515
|
+
age: {
|
|
516
|
+
type: 'number',
|
|
517
|
+
min: 0,
|
|
518
|
+
max: 150
|
|
519
|
+
},
|
|
520
|
+
preferences: {
|
|
521
|
+
type: 'object',
|
|
522
|
+
default: {}
|
|
523
|
+
}
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
getProductSchema() {
|
|
528
|
+
return {
|
|
529
|
+
name: {
|
|
530
|
+
type: 'string',
|
|
531
|
+
required: true,
|
|
532
|
+
minLength: 1,
|
|
533
|
+
maxLength: 100
|
|
534
|
+
},
|
|
535
|
+
price: {
|
|
536
|
+
type: 'number',
|
|
537
|
+
required: true,
|
|
538
|
+
min: 0
|
|
539
|
+
},
|
|
540
|
+
description: {
|
|
541
|
+
type: 'string',
|
|
542
|
+
maxLength: 500,
|
|
543
|
+
trim: true
|
|
544
|
+
},
|
|
545
|
+
tags: {
|
|
546
|
+
type: 'array',
|
|
547
|
+
maxItems: 10,
|
|
548
|
+
default: []
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
### Cache Management Service
|
|
556
|
+
|
|
557
|
+
```typescript
|
|
558
|
+
@Injectable()
|
|
559
|
+
export class CacheManagerService {
|
|
560
|
+
constructor(private utils: UtilsService) {}
|
|
561
|
+
|
|
562
|
+
createCache<T>(key: string, ttl: string, maxSize?: number) {
|
|
563
|
+
return {
|
|
564
|
+
key,
|
|
565
|
+
ttl: this.utils.expires(ttl),
|
|
566
|
+
maxSize,
|
|
567
|
+
data: new Map<string, { value: T; timestamp: number }>(),
|
|
568
|
+
|
|
569
|
+
set(itemKey: string, value: T): void {
|
|
570
|
+
// Clean up expired items
|
|
571
|
+
this.cleanup();
|
|
572
|
+
|
|
573
|
+
// Check size limit
|
|
574
|
+
if (this.maxSize && this.data.size >= this.maxSize) {
|
|
575
|
+
// Remove oldest item
|
|
576
|
+
const oldestKey = this.data.keys().next().value;
|
|
577
|
+
this.data.delete(oldestKey);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
this.data.set(itemKey, {
|
|
581
|
+
value,
|
|
582
|
+
timestamp: Date.now()
|
|
583
|
+
});
|
|
584
|
+
},
|
|
585
|
+
|
|
586
|
+
get(itemKey: string): T | null {
|
|
587
|
+
this.cleanup();
|
|
588
|
+
|
|
589
|
+
const item = this.data.get(itemKey);
|
|
590
|
+
if (item) {
|
|
591
|
+
return item.value;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
return null;
|
|
595
|
+
},
|
|
596
|
+
|
|
597
|
+
has(itemKey: string): boolean {
|
|
598
|
+
this.cleanup();
|
|
599
|
+
return this.data.has(itemKey);
|
|
600
|
+
},
|
|
601
|
+
|
|
602
|
+
delete(itemKey: string): boolean {
|
|
603
|
+
return this.data.delete(itemKey);
|
|
604
|
+
},
|
|
605
|
+
|
|
606
|
+
clear(): void {
|
|
607
|
+
this.data.clear();
|
|
608
|
+
},
|
|
609
|
+
|
|
610
|
+
size(): number {
|
|
611
|
+
this.cleanup();
|
|
612
|
+
return this.data.size;
|
|
613
|
+
},
|
|
614
|
+
|
|
615
|
+
cleanup(): void {
|
|
616
|
+
const now = Date.now();
|
|
617
|
+
const expiredKeys: string[] = [];
|
|
618
|
+
|
|
619
|
+
this.data.forEach((item, key) => {
|
|
620
|
+
if (item.timestamp < this.ttl * 1000) {
|
|
621
|
+
expiredKeys.push(key);
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
expiredKeys.forEach(key => this.data.delete(key));
|
|
626
|
+
},
|
|
627
|
+
|
|
628
|
+
export(): Array<{ key: string; value: T; timestamp: number }> {
|
|
629
|
+
this.cleanup();
|
|
630
|
+
return Array.from(this.data.entries()).map(([key, item]) => ({
|
|
631
|
+
key,
|
|
632
|
+
value: item.value,
|
|
633
|
+
timestamp: item.timestamp
|
|
634
|
+
}));
|
|
635
|
+
},
|
|
636
|
+
|
|
637
|
+
import(items: Array<{ key: string; value: T; timestamp: number }>): void {
|
|
638
|
+
items.forEach(item => {
|
|
639
|
+
if (item.timestamp > this.ttl * 1000) {
|
|
640
|
+
this.data.set(item.key, {
|
|
641
|
+
value: item.value,
|
|
642
|
+
timestamp: item.timestamp
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// Global cache instance
|
|
651
|
+
private globalCache = this.createCache('global-cache', '1h', 100);
|
|
652
|
+
|
|
653
|
+
cacheResult<T>(key: string, fn: () => T, ttl: string = '1h'): T {
|
|
654
|
+
const cached = this.globalCache.get(key);
|
|
655
|
+
if (cached !== null) {
|
|
656
|
+
return cached;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const result = fn();
|
|
660
|
+
this.globalCache.set(key, result);
|
|
661
|
+
return result;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
invalidateCache(key: string): void {
|
|
665
|
+
this.globalCache.delete(key);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
clearCache(): void {
|
|
669
|
+
this.globalCache.clear();
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
### Configuration Manager
|
|
675
|
+
|
|
676
|
+
```typescript
|
|
677
|
+
@Injectable()
|
|
678
|
+
export class ConfigurationManagerService {
|
|
679
|
+
private configs = new Map<string, any>();
|
|
680
|
+
|
|
681
|
+
constructor(private utils: UtilsService) {}
|
|
682
|
+
|
|
683
|
+
loadConfig<T>(configKey: string, defaultValue: T, expiration?: string): T {
|
|
684
|
+
const cached = this.configs.get(configKey);
|
|
685
|
+
|
|
686
|
+
if (cached && !this.isExpired(cached.timestamp, expiration)) {
|
|
687
|
+
return cached.value;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// Load from storage or API
|
|
691
|
+
const value = this.loadConfigValue<T>(configKey, defaultValue);
|
|
692
|
+
|
|
693
|
+
this.configs.set(configKey, {
|
|
694
|
+
value,
|
|
695
|
+
timestamp: Date.now(),
|
|
696
|
+
expiresAt: expiration ? this.utils.expires(expiration) : undefined
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
return value;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
updateConfig<T>(configKey: string, value: T, expiration?: string): void {
|
|
703
|
+
this.configs.set(configKey, {
|
|
704
|
+
value,
|
|
705
|
+
timestamp: Date.now(),
|
|
706
|
+
expiresAt: expiration ? this.utils.expires(expiration) : undefined
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
this.saveConfigValue(configKey, value);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
getConfig<T>(configKey: string): T | null {
|
|
713
|
+
const cached = this.configs.get(configKey);
|
|
714
|
+
if (cached && !this.isExpired(cached.timestamp, undefined)) {
|
|
715
|
+
return cached.value;
|
|
716
|
+
}
|
|
717
|
+
return null;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
invalidateConfig(configKey: string): void {
|
|
721
|
+
this.configs.delete(configKey);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
clearConfigs(): void {
|
|
725
|
+
this.configs.clear();
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
private isExpired(timestamp: number, expiration?: string): boolean {
|
|
729
|
+
if (!expiration) return false;
|
|
730
|
+
|
|
731
|
+
const expiryTime = this.utils.expires(expiration);
|
|
732
|
+
return this.utils.hasExpired(expiryTime || 0);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
private loadConfigValue<T>(configKey: string, defaultValue: T): T {
|
|
736
|
+
try {
|
|
737
|
+
const stored = localStorage.getItem(`config:${configKey}`);
|
|
738
|
+
if (stored) {
|
|
739
|
+
const parsed = JSON.parse(stored);
|
|
740
|
+
return parsed.value || defaultValue;
|
|
741
|
+
}
|
|
742
|
+
} catch (error) {
|
|
743
|
+
console.warn(`Failed to load config ${configKey}:`, error);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
return defaultValue;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
private saveConfigValue(configKey: string, value: any): void {
|
|
750
|
+
try {
|
|
751
|
+
localStorage.setItem(`config:${configKey}`, JSON.stringify({
|
|
752
|
+
value,
|
|
753
|
+
savedAt: new Date().toISOString()
|
|
754
|
+
}));
|
|
755
|
+
} catch (error) {
|
|
756
|
+
console.warn(`Failed to save config ${configKey}:`, error);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// Predefined configurations
|
|
761
|
+
getAppConfig() {
|
|
762
|
+
return this.loadConfig('app-config', {
|
|
763
|
+
version: '1.0.0',
|
|
764
|
+
apiUrl: 'http://localhost:3000',
|
|
765
|
+
features: {
|
|
766
|
+
enableAnalytics: true,
|
|
767
|
+
enableNotifications: true,
|
|
768
|
+
enableOffline: false
|
|
769
|
+
},
|
|
770
|
+
limits: {
|
|
771
|
+
maxFileSize: 10485760, // 10MB
|
|
772
|
+
maxRequestsPerMinute: 100
|
|
773
|
+
}
|
|
774
|
+
}, '1h');
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
getUserPreferences() {
|
|
778
|
+
return this.loadConfig('user-preferences', {
|
|
779
|
+
theme: 'light',
|
|
780
|
+
language: 'en',
|
|
781
|
+
notifications: {
|
|
782
|
+
email: true,
|
|
783
|
+
push: true,
|
|
784
|
+
sms: false
|
|
785
|
+
},
|
|
786
|
+
privacy: {
|
|
787
|
+
analytics: true,
|
|
788
|
+
marketing: false
|
|
789
|
+
}
|
|
790
|
+
}, '30d');
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
getFeatureFlags() {
|
|
794
|
+
return this.loadConfig('feature-flags', {
|
|
795
|
+
newDashboard: false,
|
|
796
|
+
darkMode: true,
|
|
797
|
+
betaFeatures: false,
|
|
798
|
+
experimentalUI: false
|
|
799
|
+
}, '1d');
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
### URL Parameter Parser
|
|
805
|
+
|
|
806
|
+
```typescript
|
|
807
|
+
@Injectable()
|
|
808
|
+
export class URLParserService {
|
|
809
|
+
constructor(private utils: UtilsService) {}
|
|
810
|
+
|
|
811
|
+
parseQueryParams(url: string): Record<string, string> {
|
|
812
|
+
const params: Record<string, string> = {};
|
|
813
|
+
const queryString = url.split('?')[1];
|
|
814
|
+
|
|
815
|
+
if (!queryString) return params;
|
|
816
|
+
|
|
817
|
+
queryString.split('&').forEach(pair => {
|
|
818
|
+
const [key, value] = pair.split('=');
|
|
819
|
+
if (key && value) {
|
|
820
|
+
params[decodeURIComponent(key)] = decodeURIComponent(value);
|
|
821
|
+
}
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
return params;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
buildQueryString(params: Record<string, any>): string {
|
|
828
|
+
const searchParams = new URLSearchParams();
|
|
829
|
+
|
|
830
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
831
|
+
if (value !== null && value !== undefined) {
|
|
832
|
+
if (Array.isArray(value)) {
|
|
833
|
+
value.forEach(item => searchParams.append(key, String(item)));
|
|
834
|
+
} else {
|
|
835
|
+
searchParams.set(key, String(value));
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
return searchParams.toString();
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
extractUrlParams(template: string, actualUrl: string): Record<string, string> {
|
|
844
|
+
const templateParts = template.split('/');
|
|
845
|
+
const actualParts = actualUrl.split('/');
|
|
846
|
+
const params: Record<string, string> = {};
|
|
847
|
+
|
|
848
|
+
if (templateParts.length !== actualParts.length) {
|
|
849
|
+
return params;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
templateParts.forEach((part, index) => {
|
|
853
|
+
if (part.startsWith(':')) {
|
|
854
|
+
const paramName = part.substring(1);
|
|
855
|
+
params[paramName] = actualParts[index];
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
return params;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
buildUrl(template: string, params: Record<string, string>): string {
|
|
863
|
+
let url = template;
|
|
864
|
+
|
|
865
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
866
|
+
url = url.replace(`:${key}`, encodeURIComponent(value));
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
return url;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// URL validation
|
|
873
|
+
isValidUrl(url: string): boolean {
|
|
874
|
+
try {
|
|
875
|
+
new URL(url);
|
|
876
|
+
return true;
|
|
877
|
+
} catch {
|
|
878
|
+
return false;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
isValidEmail(email: string): boolean {
|
|
883
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
884
|
+
return emailRegex.test(email);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
sanitizeUrl(url: string): string {
|
|
888
|
+
// Remove potentially dangerous protocols
|
|
889
|
+
const dangerousProtocols = ['javascript:', 'data:', 'vbscript:'];
|
|
890
|
+
|
|
891
|
+
dangerousProtocols.forEach(protocol => {
|
|
892
|
+
if (url.toLowerCase().startsWith(protocol)) {
|
|
893
|
+
throw new Error(`Dangerous protocol detected: ${protocol}`);
|
|
894
|
+
}
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
return url;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
```
|
|
901
|
+
|
|
902
|
+
## Integration with Other Services
|
|
903
|
+
|
|
904
|
+
### With HTTP State Service
|
|
905
|
+
|
|
906
|
+
```typescript
|
|
907
|
+
@Injectable()
|
|
908
|
+
export class SmartCacheService extends HTTPManagerStateService<any> {
|
|
909
|
+
private utils = inject(UtilsService);
|
|
910
|
+
private cache = new Map<string, { data: any; expiresAt: number }>();
|
|
911
|
+
|
|
912
|
+
constructor() {
|
|
913
|
+
super(
|
|
914
|
+
ApiRequest.adapt({ path: ['data'] }),
|
|
915
|
+
DataType.OBJECT
|
|
916
|
+
);
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
fetchWithCache(key: string, fetchFn: () => any, ttl: string = '1h') {
|
|
920
|
+
// Check cache first
|
|
921
|
+
const cached = this.cache.get(key);
|
|
922
|
+
if (cached && !this.utils.hasExpired(cached.expiresAt)) {
|
|
923
|
+
return of(cached.data);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
// Fetch and cache
|
|
927
|
+
return fetchFn().pipe(
|
|
928
|
+
tap(data => {
|
|
929
|
+
const expiresAt = this.utils.expires(ttl);
|
|
930
|
+
this.cache.set(key, {
|
|
931
|
+
data,
|
|
932
|
+
expiresAt: expiresAt || 0
|
|
933
|
+
});
|
|
934
|
+
})
|
|
935
|
+
);
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
invalidateCache(key: string): void {
|
|
939
|
+
this.cache.delete(key);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
clearExpiredCache(): void {
|
|
943
|
+
const now = Date.now() / 1000;
|
|
944
|
+
const expiredKeys: string[] = [];
|
|
945
|
+
|
|
946
|
+
this.cache.forEach((item, key) => {
|
|
947
|
+
if (item.expiresAt < now) {
|
|
948
|
+
expiredKeys.push(key);
|
|
949
|
+
}
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
expiredKeys.forEach(key => this.cache.delete(key));
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
```
|
|
956
|
+
|
|
957
|
+
### With Local Storage
|
|
958
|
+
|
|
959
|
+
```typescript
|
|
960
|
+
@Injectable()
|
|
961
|
+
export class SmartStorageService {
|
|
962
|
+
private utils = inject(UtilsService);
|
|
963
|
+
private storage = inject(LocalStorageManagerService);
|
|
964
|
+
|
|
965
|
+
setWithExpiration(key: string, value: any, expiration: string): void {
|
|
966
|
+
const expiresAt = this.utils.expires(expiration);
|
|
967
|
+
|
|
968
|
+
this.storage.setItem(key, {
|
|
969
|
+
value,
|
|
970
|
+
expiresAt,
|
|
971
|
+
createdAt: Date.now()
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
getWithExpiration<T>(key: string): T | null {
|
|
976
|
+
const stored = this.storage.getItem(key);
|
|
977
|
+
|
|
978
|
+
if (!stored) return null;
|
|
979
|
+
|
|
980
|
+
if (this.utils.hasExpired(stored.expiresAt)) {
|
|
981
|
+
this.storage.removeItem(key);
|
|
982
|
+
return null;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
return stored.value;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
setIfNewer(key: string, value: any, timestamp: number): boolean {
|
|
989
|
+
const stored = this.storage.getItem(key);
|
|
990
|
+
|
|
991
|
+
if (stored && stored.timestamp > timestamp) {
|
|
992
|
+
return false; // Stored version is newer
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
this.storage.setItem(key, {
|
|
996
|
+
value,
|
|
997
|
+
timestamp,
|
|
998
|
+
updatedAt: Date.now()
|
|
999
|
+
});
|
|
1000
|
+
|
|
1001
|
+
return true;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
```
|
|
1005
|
+
|
|
1006
|
+
## Best Practices
|
|
1007
|
+
|
|
1008
|
+
### 1. Type Safety
|
|
1009
|
+
|
|
1010
|
+
```typescript
|
|
1011
|
+
// ✅ Good - Validate types before operations
|
|
1012
|
+
if (this.utils.isString(value)) {
|
|
1013
|
+
const result = this.utils.lc(value);
|
|
1014
|
+
// Safe to use result as string
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
// ❌ Avoid - No type checking
|
|
1018
|
+
const result = this.utils.lc(value); // Could fail if value is not string
|
|
1019
|
+
```
|
|
1020
|
+
|
|
1021
|
+
### 2. Error Handling
|
|
1022
|
+
|
|
1023
|
+
```typescript
|
|
1024
|
+
// ✅ Good - Handle potential errors
|
|
1025
|
+
try {
|
|
1026
|
+
const parsed = this.utils.stringToJSON(jsonString);
|
|
1027
|
+
// Process parsed data
|
|
1028
|
+
} catch (error) {
|
|
1029
|
+
console.error('Failed to parse JSON:', error);
|
|
1030
|
+
return defaultValue;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
// ❌ Avoid - No error handling
|
|
1034
|
+
const parsed = this.utils.stringToJSON(jsonString); // Could throw
|
|
1035
|
+
```
|
|
1036
|
+
|
|
1037
|
+
### 3. Performance Considerations
|
|
1038
|
+
|
|
1039
|
+
```typescript
|
|
1040
|
+
// ✅ Good - Cache expensive operations
|
|
1041
|
+
private cachedBase32Conversion = new Map<string, string>();
|
|
1042
|
+
|
|
1043
|
+
convertBase32(value: string): string {
|
|
1044
|
+
if (!this.cachedBase32Conversion.has(value)) {
|
|
1045
|
+
this.cachedBase32Conversion.set(value, this.utils.base32ToHex(value));
|
|
1046
|
+
}
|
|
1047
|
+
return this.cachedBase32Conversion.get(value)!;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
// ❌ Avoid - Repeated expensive operations
|
|
1051
|
+
// Converting the same value multiple times without caching
|
|
1052
|
+
```
|
|
1053
|
+
|
|
1054
|
+
### 4. Validation Before Usage
|
|
1055
|
+
|
|
1056
|
+
```typescript
|
|
1057
|
+
// ✅ Good - Validate input before processing
|
|
1058
|
+
validateAndProcess(data: any): any {
|
|
1059
|
+
if (!this.utils.isObject(data)) {
|
|
1060
|
+
throw new Error('Data must be an object');
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
if (!this.utils.isJSON(JSON.stringify(data))) {
|
|
1064
|
+
throw new Error('Data contains invalid JSON');
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
return this.processData(data);
|
|
1068
|
+
}
|
|
1069
|
+
```
|
|
1070
|
+
|
|
1071
|
+
## Performance Optimization
|
|
1072
|
+
|
|
1073
|
+
### Batch Operations
|
|
1074
|
+
|
|
1075
|
+
```typescript
|
|
1076
|
+
@Injectable()
|
|
1077
|
+
export class BatchUtilsService {
|
|
1078
|
+
private utils = inject(UtilsService);
|
|
1079
|
+
|
|
1080
|
+
batchConvertExpirations(durations: string[]): number[] {
|
|
1081
|
+
return durations.map(duration => this.utils.expires(duration) || 0);
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
batchValidateTypes(values: any[]): Array<{value: any; isString: boolean; isObject: boolean}> {
|
|
1085
|
+
return values.map(value => ({
|
|
1086
|
+
value,
|
|
1087
|
+
isString: this.utils.isString(value),
|
|
1088
|
+
isObject: this.utils.isObject(value)
|
|
1089
|
+
}));
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
batchCompareObjects(pairs: Array<{x: any; y: any}>): boolean[] {
|
|
1093
|
+
return pairs.map(pair => this.utils.objectsEqual(pair.x, pair.y));
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
```
|
|
1097
|
+
|
|
1098
|
+
### Memoization
|
|
1099
|
+
|
|
1100
|
+
```typescript
|
|
1101
|
+
@Injectable()
|
|
1102
|
+
export class MemoizedUtilsService {
|
|
1103
|
+
private utils = inject(UtilsService);
|
|
1104
|
+
|
|
1105
|
+
// Memoization cache
|
|
1106
|
+
private stringToJsonCache = new Map<string, any>();
|
|
1107
|
+
private objectComparisonCache = new Map<string, boolean>();
|
|
1108
|
+
|
|
1109
|
+
memoizedStringToJSON(value: string): any {
|
|
1110
|
+
if (this.stringToJsonCache.has(value)) {
|
|
1111
|
+
return this.stringToJsonCache.get(value);
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
const result = this.utils.stringToJSON(value);
|
|
1115
|
+
this.stringToJsonCache.set(value, result);
|
|
1116
|
+
return result;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
memoizedObjectsEqual(x: any, y: any): boolean {
|
|
1120
|
+
const key = JSON.stringify([x, y]);
|
|
1121
|
+
|
|
1122
|
+
if (this.objectComparisonCache.has(key)) {
|
|
1123
|
+
return this.objectComparisonCache.get(key);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
const result = this.utils.objectsEqual(x, y);
|
|
1127
|
+
this.objectComparisonCache.set(key, result);
|
|
1128
|
+
return result;
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
clearCache(): void {
|
|
1132
|
+
this.stringToJsonCache.clear();
|
|
1133
|
+
this.objectComparisonCache.clear();
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
```
|
|
1137
|
+
|
|
1138
|
+
## Troubleshooting
|
|
1139
|
+
|
|
1140
|
+
### Common Issues
|
|
1141
|
+
|
|
1142
|
+
#### 1. JSON Parsing Errors
|
|
1143
|
+
```typescript
|
|
1144
|
+
// ✅ Good - Safe JSON parsing
|
|
1145
|
+
safeJsonParse(jsonString: string, fallback: any = null): any {
|
|
1146
|
+
try {
|
|
1147
|
+
return this.utils.stringToJSON(jsonString);
|
|
1148
|
+
} catch (error) {
|
|
1149
|
+
console.warn('Invalid JSON:', jsonString, error);
|
|
1150
|
+
return fallback;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
// ❌ Avoid - Unsafe parsing
|
|
1155
|
+
const data = JSON.parse(jsonString); // Could throw
|
|
1156
|
+
```
|
|
1157
|
+
|
|
1158
|
+
#### 2. Type Checking Edge Cases
|
|
1159
|
+
```typescript
|
|
1160
|
+
// ✅ Good - Handle edge cases
|
|
1161
|
+
isValidString(value: any): boolean {
|
|
1162
|
+
return this.utils.isString(value) && value.trim().length > 0;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
// ❌ Avoid - Incomplete validation
|
|
1166
|
+
const isString = this.utils.isString(value); // Could be empty string
|
|
1167
|
+
```
|
|
1168
|
+
|
|
1169
|
+
#### 3. Expiration Calculation
|
|
1170
|
+
```typescript
|
|
1171
|
+
// ✅ Good - Handle invalid durations
|
|
1172
|
+
safeExpires(duration: string, fallback: number): number {
|
|
1173
|
+
const expires = this.utils.expires(duration);
|
|
1174
|
+
return expires || fallback;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
// ❌ Avoid - No fallback
|
|
1178
|
+
const expires = this.utils.expires(duration); // Could be undefined
|
|
1179
|
+
```
|
|
1180
|
+
|
|
1181
|
+
## Related Documentation
|
|
1182
|
+
|
|
1183
|
+
- [Local Storage Service](local-storage/README.md)
|
|
1184
|
+
- [HTTP State Service](http-state/README.md)
|
|
1185
|
+
- [Database Manager Service](database/README.md)
|
|
1186
|
+
- [Architecture Overview](../architecture/README.md)
|