joopjs 2.0.5 → 2.1.0
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/.claude/skills/auth.md +235 -0
- package/.claude/skills/banking.md +377 -0
- package/.claude/skills/encryption.md +265 -0
- package/.claude/skills/finance.md +248 -0
- package/.claude/skills/observables.md +270 -0
- package/.claude/skills/security.md +240 -0
- package/.claude/skills/setup.md +196 -0
- package/.cursor/rules/joopjs.mdc +150 -0
- package/.github/copilot-instructions.md +143 -0
- package/.windsurf/rules/joopjs.md +226 -0
- package/CHANGELOG.md +81 -0
- package/README.md +47 -7
- package/ai-rules/AGENTS.md +241 -0
- package/ai-rules/GEMINI.md +183 -0
- package/dist/ai/index.js +15 -3
- package/dist/ai/index.js.map +1 -1
- package/dist/ai/index.mjs +15 -3
- package/dist/ai/index.mjs.map +1 -1
- package/dist/analytics/index.js +10 -2
- package/dist/analytics/index.js.map +1 -1
- package/dist/analytics/index.mjs +10 -2
- package/dist/analytics/index.mjs.map +1 -1
- package/dist/angular/index.d.mts +98 -27
- package/dist/angular/index.d.ts +98 -27
- package/dist/angular/index.js +44 -0
- package/dist/angular/index.js.map +1 -1
- package/dist/angular/index.mjs +39 -1
- package/dist/angular/index.mjs.map +1 -1
- package/dist/api/index.js +15 -3
- package/dist/api/index.js.map +1 -1
- package/dist/api/index.mjs +15 -3
- package/dist/api/index.mjs.map +1 -1
- package/dist/auth/index.js +15 -3
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/index.mjs +15 -3
- package/dist/auth/index.mjs.map +1 -1
- package/dist/banking/index.js +15 -3
- package/dist/banking/index.js.map +1 -1
- package/dist/banking/index.mjs +15 -3
- package/dist/banking/index.mjs.map +1 -1
- package/dist/cache/index.js +15 -3
- package/dist/cache/index.js.map +1 -1
- package/dist/cache/index.mjs +15 -3
- package/dist/cache/index.mjs.map +1 -1
- package/dist/{index-DFqEoX_l.d.ts → consent.service-CIHNtx9h.d.ts} +1 -2
- package/dist/{index-B_ksKpS1.d.mts → consent.service-DQ-JAEJx.d.mts} +1 -2
- package/dist/core/index.d.mts +34 -1
- package/dist/core/index.d.ts +34 -1
- package/dist/core/index.js +56 -5
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +54 -6
- package/dist/core/index.mjs.map +1 -1
- package/dist/deeplink/index.js +15 -3
- package/dist/deeplink/index.js.map +1 -1
- package/dist/deeplink/index.mjs +15 -3
- package/dist/deeplink/index.mjs.map +1 -1
- package/dist/device/index.js +15 -3
- package/dist/device/index.js.map +1 -1
- package/dist/device/index.mjs +15 -3
- package/dist/device/index.mjs.map +1 -1
- package/dist/forms/index.js +15 -3
- package/dist/forms/index.js.map +1 -1
- package/dist/forms/index.mjs +15 -3
- package/dist/forms/index.mjs.map +1 -1
- package/dist/i18n/index.js +15 -3
- package/dist/i18n/index.js.map +1 -1
- package/dist/i18n/index.mjs +15 -3
- package/dist/i18n/index.mjs.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +50 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +50 -8
- package/dist/index.mjs.map +1 -1
- package/dist/{joop-CA3DMeOO.d.ts → joop-Dim2yEKG.d.ts} +1 -1
- package/dist/{joop-Bx7Iwj5p.d.mts → joop-GkQw13f9.d.mts} +1 -1
- package/dist/native-bridge/index.js +10 -2
- package/dist/native-bridge/index.js.map +1 -1
- package/dist/native-bridge/index.mjs +10 -2
- package/dist/native-bridge/index.mjs.map +1 -1
- package/dist/network/index.js +15 -3
- package/dist/network/index.js.map +1 -1
- package/dist/network/index.mjs +15 -3
- package/dist/network/index.mjs.map +1 -1
- package/dist/observability/index.js +15 -3
- package/dist/observability/index.js.map +1 -1
- package/dist/observability/index.mjs +15 -3
- package/dist/observability/index.mjs.map +1 -1
- package/dist/pwa/index.js +15 -3
- package/dist/pwa/index.js.map +1 -1
- package/dist/pwa/index.mjs +15 -3
- package/dist/pwa/index.mjs.map +1 -1
- package/dist/react/index.d.mts +2 -2
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.js +15 -3
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +15 -3
- package/dist/react/index.mjs.map +1 -1
- package/dist/router/index.js +15 -3
- package/dist/router/index.js.map +1 -1
- package/dist/router/index.mjs +15 -3
- package/dist/router/index.mjs.map +1 -1
- package/dist/security/index.js +15 -3
- package/dist/security/index.js.map +1 -1
- package/dist/security/index.mjs +15 -3
- package/dist/security/index.mjs.map +1 -1
- package/dist/session/index.js +15 -3
- package/dist/session/index.js.map +1 -1
- package/dist/session/index.mjs +15 -3
- package/dist/session/index.mjs.map +1 -1
- package/dist/state/index.js +15 -3
- package/dist/state/index.js.map +1 -1
- package/dist/state/index.mjs +15 -3
- package/dist/state/index.mjs.map +1 -1
- package/dist/storage/index.js +15 -3
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/index.mjs +15 -3
- package/dist/storage/index.mjs.map +1 -1
- package/dist/sync/index.js +15 -3
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/index.mjs +15 -3
- package/dist/sync/index.mjs.map +1 -1
- package/dist/theme/index.js +15 -3
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +15 -3
- package/dist/theme/index.mjs.map +1 -1
- package/dist/ui/index.js +15 -3
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/index.mjs +15 -3
- package/dist/ui/index.mjs.map +1 -1
- package/dist/utilities/index.js +46 -4
- package/dist/utilities/index.js.map +1 -1
- package/dist/utilities/index.mjs +46 -4
- package/dist/utilities/index.mjs.map +1 -1
- package/dist/vue/index.d.mts +2 -2
- package/dist/vue/index.d.ts +2 -2
- package/dist/vue/index.js +15 -3
- package/dist/vue/index.js.map +1 -1
- package/dist/vue/index.mjs +15 -3
- package/dist/vue/index.mjs.map +1 -1
- package/dist/workflow/index.js +15 -3
- package/dist/workflow/index.js.map +1 -1
- package/dist/workflow/index.mjs +15 -3
- package/dist/workflow/index.mjs.map +1 -1
- package/package.json +96 -32
- package/scripts/setup-ai.mjs +133 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# /observables — JoopJS Reactive Patterns
|
|
2
|
+
|
|
3
|
+
> Author: Kundan Singh
|
|
4
|
+
|
|
5
|
+
JoopJS has its own lightweight reactive primitives — `JoopSubject` and `JoopBehaviorSubject`. They are **not** RxJS Observables. Never use `.emit()`, `.pipe()`, or RxJS operators.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Core API
|
|
10
|
+
|
|
11
|
+
### JoopSubject — event stream (no initial value)
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { JoopSubject } from 'joopjs';
|
|
15
|
+
|
|
16
|
+
const subject = new JoopSubject<number>();
|
|
17
|
+
|
|
18
|
+
// Subscribe
|
|
19
|
+
const unsub = subject.subscribe((value) => {
|
|
20
|
+
console.log('Got:', value);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Emit
|
|
24
|
+
subject.next(42); // all subscribers receive 42
|
|
25
|
+
|
|
26
|
+
// Unsubscribe
|
|
27
|
+
unsub();
|
|
28
|
+
|
|
29
|
+
// Complete (all subscribers removed)
|
|
30
|
+
subject.complete();
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### JoopBehaviorSubject — holds current value + emits to new subscribers immediately
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { JoopBehaviorSubject } from 'joopjs';
|
|
37
|
+
|
|
38
|
+
const balance = new JoopBehaviorSubject<number>(0); // initial value
|
|
39
|
+
|
|
40
|
+
// New subscriber gets current value immediately
|
|
41
|
+
balance.subscribe(v => console.log('Balance:', v)); // logs 0 right away
|
|
42
|
+
|
|
43
|
+
balance.next(100); // logs 100
|
|
44
|
+
|
|
45
|
+
// Read current value synchronously
|
|
46
|
+
const current = balance.getValue(); // 100
|
|
47
|
+
|
|
48
|
+
// Check if completed
|
|
49
|
+
const done = balance.isCompleted();
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Subscribing to Service Streams
|
|
55
|
+
|
|
56
|
+
Every JoopJS service exposes reactive streams for key state changes. Always use the returned `unsubscribe` function to avoid memory leaks.
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { JoopDigitalWalletService } from 'joopjs';
|
|
60
|
+
const wallet = new JoopDigitalWalletService();
|
|
61
|
+
|
|
62
|
+
// Subscribe to balance changes
|
|
63
|
+
const unsub = wallet.balance$().subscribe(({ walletId, balance }) => {
|
|
64
|
+
updateUI(walletId, balance);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// On component/page destroy
|
|
68
|
+
unsub();
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Common service streams
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
// Auth
|
|
75
|
+
auth.session$().subscribe(session => { /* null when logged out */ });
|
|
76
|
+
|
|
77
|
+
// Wallet
|
|
78
|
+
wallet.balance$().subscribe(({ walletId, balance }) => { });
|
|
79
|
+
|
|
80
|
+
// AML
|
|
81
|
+
aml.alert$().subscribe(alert => { });
|
|
82
|
+
|
|
83
|
+
// Fraud Detection
|
|
84
|
+
fraud.alert$().subscribe(result => { });
|
|
85
|
+
|
|
86
|
+
// Sanctions
|
|
87
|
+
sanctions.hit$().subscribe(hit => { }); // only fires on non-clear results
|
|
88
|
+
|
|
89
|
+
// Risk Engine
|
|
90
|
+
risk.score$().subscribe(({ score, level }) => { });
|
|
91
|
+
|
|
92
|
+
// KYC
|
|
93
|
+
kyc.status$().subscribe(update => { });
|
|
94
|
+
|
|
95
|
+
// MFA
|
|
96
|
+
mfa.status$().subscribe(event => { });
|
|
97
|
+
|
|
98
|
+
// Sessions
|
|
99
|
+
sessions.expired$().subscribe(session => { });
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Common Patterns
|
|
105
|
+
|
|
106
|
+
### React — useEffect subscription
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
import { useEffect, useState } from 'react';
|
|
110
|
+
import { JoopDigitalWalletService } from 'joopjs';
|
|
111
|
+
|
|
112
|
+
const wallet = new JoopDigitalWalletService();
|
|
113
|
+
|
|
114
|
+
function BalanceDisplay({ walletId }: { walletId: string }) {
|
|
115
|
+
const [balance, setBalance] = useState(0);
|
|
116
|
+
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
const unsub = wallet.balance$().subscribe(({ walletId: id, balance }) => {
|
|
119
|
+
if (id === walletId) setBalance(balance);
|
|
120
|
+
});
|
|
121
|
+
return unsub; // cleanup on unmount
|
|
122
|
+
}, [walletId]);
|
|
123
|
+
|
|
124
|
+
return <div>{balance}</div>;
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Angular — async pipe
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
import { Observable } from 'rxjs'; // only needed to bridge to Angular async pipe
|
|
132
|
+
import { JoopDigitalWalletService } from 'joopjs';
|
|
133
|
+
|
|
134
|
+
@Component({ template: `<div>{{ balance$ | async }}</div>` })
|
|
135
|
+
export class BalanceComponent {
|
|
136
|
+
balance$ = new Observable<number>(observer => {
|
|
137
|
+
// bridge JoopSubject → RxJS Observable for async pipe
|
|
138
|
+
const unsub = wallet.balance$().subscribe(({ balance }) => observer.next(balance));
|
|
139
|
+
return () => unsub();
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Vue — ref + onMounted/onUnmounted
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
import { ref, onMounted, onUnmounted } from 'vue';
|
|
148
|
+
import { JoopDigitalWalletService } from 'joopjs';
|
|
149
|
+
|
|
150
|
+
const wallet = new JoopDigitalWalletService();
|
|
151
|
+
|
|
152
|
+
export function useWalletBalance(walletId: string) {
|
|
153
|
+
const balance = ref(0);
|
|
154
|
+
let unsub: () => void;
|
|
155
|
+
|
|
156
|
+
onMounted(() => {
|
|
157
|
+
unsub = wallet.balance$().subscribe(({ walletId: id, b }) => {
|
|
158
|
+
if (id === walletId) balance.value = b;
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
onUnmounted(() => unsub?.());
|
|
163
|
+
return { balance };
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Vanilla TypeScript — manual lifecycle
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
const wallet = new JoopDigitalWalletService();
|
|
171
|
+
const subscriptions: Array<() => void> = [];
|
|
172
|
+
|
|
173
|
+
function init() {
|
|
174
|
+
subscriptions.push(
|
|
175
|
+
wallet.balance$().subscribe(upd => renderBalance(upd.balance))
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function destroy() {
|
|
180
|
+
subscriptions.forEach(unsub => unsub());
|
|
181
|
+
subscriptions.length = 0;
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Creating Your Own Subjects
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
import { JoopSubject, JoopBehaviorSubject } from 'joopjs';
|
|
191
|
+
|
|
192
|
+
class MyPaymentService {
|
|
193
|
+
private _status$ = new JoopBehaviorSubject<'idle' | 'processing' | 'done'>('idle');
|
|
194
|
+
|
|
195
|
+
status$() { return this._status$; } // expose as read-only
|
|
196
|
+
|
|
197
|
+
async processPayment(amount: number) {
|
|
198
|
+
this._status$.next('processing');
|
|
199
|
+
await doPayment(amount);
|
|
200
|
+
this._status$.next('done');
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Error Isolation
|
|
208
|
+
|
|
209
|
+
`JoopSubject.next()` and `JoopBehaviorSubject` replay (the immediate value sent to new subscribers) are **error-isolated**. `next()` snapshots its listener list and wraps each subscriber call in try/catch, so:
|
|
210
|
+
|
|
211
|
+
- One subscriber that throws no longer aborts delivery to the others.
|
|
212
|
+
- A `subscribe()` / `unsubscribe()` triggered mid-emission can't disturb the in-flight notification.
|
|
213
|
+
|
|
214
|
+
Subscriber errors are routed to a handler instead of bubbling out of `next()`. By default the handler is `console.error`. Override it globally with `setSubjectErrorHandler`:
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
import { JoopSubject, setSubjectErrorHandler } from 'joopjs';
|
|
218
|
+
|
|
219
|
+
// Route subscriber errors wherever you want (default: console.error)
|
|
220
|
+
setSubjectErrorHandler((error) => {
|
|
221
|
+
errorReporter.captureException(error);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const subject = new JoopSubject<number>();
|
|
225
|
+
subject.subscribe(() => { throw new Error('boom'); }); // handled, isolated
|
|
226
|
+
subject.subscribe((v) => console.log('still delivered:', v));
|
|
227
|
+
|
|
228
|
+
subject.next(1); // logs 'still delivered: 1' — the throwing subscriber does not block this one
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
This is still **not** RxJS — `.subscribe()` returns an unsubscribe function and there are no operators.
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## Critical Rules
|
|
236
|
+
|
|
237
|
+
| Rule | Correct | Wrong |
|
|
238
|
+
|------|---------|-------|
|
|
239
|
+
| Emit values | `.next(value)` | `.emit(value)` |
|
|
240
|
+
| Read current value | `.getValue()` | `.value` |
|
|
241
|
+
| Stop listening | Call the returned `unsub()` function | `.unsubscribe()` (doesn't exist) |
|
|
242
|
+
| Add listeners | `.subscribe(callback)` | `.addListener()` |
|
|
243
|
+
| One-time listen | Subscribe, call `unsub()` inside the callback | `.once()` (doesn't exist) |
|
|
244
|
+
| Merge/combine | Compose in application code | `.pipe()` (not available) |
|
|
245
|
+
| Check completion | `.isCompleted()` | `.closed` |
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## What Gets Published to npm
|
|
250
|
+
|
|
251
|
+
Controlled by `"files"` in `package.json` — acts as an allowlist. Only these two are included:
|
|
252
|
+
|
|
253
|
+
| Published to npm | Never published |
|
|
254
|
+
|-----------------|----------------|
|
|
255
|
+
| `dist/` — ESM + CJS + `.d.ts` for all 34 sub-paths | `src/` — TypeScript source |
|
|
256
|
+
| `CHANGELOG.md` — release history | `tests/` — test suite |
|
|
257
|
+
| | `scripts/` — release automation |
|
|
258
|
+
| | `playground/` — Vite demo app |
|
|
259
|
+
| | `.claude/` — Claude skills (including this file) |
|
|
260
|
+
| | `.cursor/` — Cursor rules |
|
|
261
|
+
| | `.windsurf/` — Windsurf rules |
|
|
262
|
+
| | `GEMINI.md`, `AGENTS.md` — AI tool instructions |
|
|
263
|
+
| | `tsup.config.ts`, `vitest.config.ts`, `tsconfig.json` |
|
|
264
|
+
|
|
265
|
+
Source code, AI rules, and dev tooling are **never** published to npm.
|
|
266
|
+
|
|
267
|
+
Verify the tarball contents before any publish:
|
|
268
|
+
```bash
|
|
269
|
+
npm pack --dry-run
|
|
270
|
+
```
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# /security — JoopJS Security Services
|
|
2
|
+
|
|
3
|
+
> Author: Kundan Singh
|
|
4
|
+
|
|
5
|
+
All security services are imported from `'joopjs'`. Instantiate as plain singletons.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Sanctions Screening
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { JoopSanctionsScreeningService } from 'joopjs';
|
|
13
|
+
const sanctions = new JoopSanctionsScreeningService();
|
|
14
|
+
|
|
15
|
+
sanctions.loadList('ofac', [
|
|
16
|
+
{ id: 'e-001', name: 'Kim Jong-un', aliases: ['Dear Leader', 'Supreme Leader'],
|
|
17
|
+
type: 'individual', lists: ['ofac', 'un'] },
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
const result = sanctions.screen({ name: 'Kim Jong-un', country: 'KP' });
|
|
21
|
+
// result.status: 'clear' | 'hit' | 'review'
|
|
22
|
+
// result.matches[]: [{ entity, matchType, score, matchedOn }]
|
|
23
|
+
// matchType: 'exact' | 'alias' | 'fuzzy'
|
|
24
|
+
|
|
25
|
+
sanctions.disableList('eu'); sanctions.enableList('eu');
|
|
26
|
+
sanctions.hit$().subscribe(hit => console.log('SANCTIONS HIT:', hit)); // fires only on non-clear
|
|
27
|
+
|
|
28
|
+
// Screen with custom threshold (default 0.85)
|
|
29
|
+
const strict = sanctions.screen({ name: 'John Smith' }, { threshold: 0.95 });
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Key types:** `JoopSanctionsEntity`, `JoopScreeningResult`, `JoopSanctionMatch`, `JoopSanctionsList`, `JoopSanctionMatchType`
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## AML (Anti-Money Laundering)
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { JoopAmlService } from 'joopjs';
|
|
40
|
+
const aml = new JoopAmlService();
|
|
41
|
+
|
|
42
|
+
aml.addRule({ id: 'R01', name: 'Large Cash', type: 'threshold', threshold: 10000, currency: 'USD', action: 'flag', enabled: true });
|
|
43
|
+
|
|
44
|
+
const tx = { id: 'TXN-001', amount: 15000, currency: 'USD', type: 'cash-deposit', userId: 'u-001', timestamp: Date.now() };
|
|
45
|
+
const alert = aml.checkTransaction(tx);
|
|
46
|
+
// alert: null (pass) | JoopAmlAlert { alertId, ruleId, severity, recommendation }
|
|
47
|
+
|
|
48
|
+
aml.flag(tx.id, 'TXN-001', 'manual review required');
|
|
49
|
+
aml.alert$().subscribe(alert => console.log('AML ALERT:', alert)); // reactive
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Risk Engine
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { JoopRiskEngineService } from 'joopjs';
|
|
58
|
+
const risk = new JoopRiskEngineService();
|
|
59
|
+
|
|
60
|
+
risk.addFactor({ key: 'velocityScore', weight: 0.3, transform: (v: number) => Math.min(v / 10, 1) });
|
|
61
|
+
risk.addFactor({ key: 'locationScore', weight: 0.2 });
|
|
62
|
+
risk.addFactor({ key: 'deviceScore', weight: 0.5 });
|
|
63
|
+
|
|
64
|
+
const score = risk.evaluate({ velocityScore: 8, locationScore: 3, deviceScore: 7 });
|
|
65
|
+
// score.total ∈ [0,1], score.level: 'low'|'medium'|'high'|'critical'
|
|
66
|
+
|
|
67
|
+
risk.setThreshold('medium', 0.4); // adjust thresholds
|
|
68
|
+
risk.score$().subscribe(({ score, level }) => { });
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Fraud Detection
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import { JoopFraudDetectionService } from 'joopjs';
|
|
77
|
+
const fraud = new JoopFraudDetectionService();
|
|
78
|
+
|
|
79
|
+
fraud.addRule({ id: 'velocity', name: 'TX Velocity', condition: (ctx) => ctx.txnCount > 5, risk: 40 });
|
|
80
|
+
fraud.addRule({ id: 'geo', name: 'Geo Anomaly', condition: (ctx) => ctx.countries.size > 3, risk: 60 });
|
|
81
|
+
|
|
82
|
+
const result = fraud.assess({ txnCount: 10, countries: new Set(['US', 'NG', 'RU', 'CN']), amount: 5000 });
|
|
83
|
+
// result.risk ∈ [0,100], result.level, result.triggeredRules[]
|
|
84
|
+
|
|
85
|
+
fraud.alert$().subscribe(alert => console.log('FRAUD:', alert));
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Identity Verification (KYC)
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
import { JoopIdentityVerificationService } from 'joopjs';
|
|
94
|
+
const kyc = new JoopIdentityVerificationService();
|
|
95
|
+
|
|
96
|
+
const submission = kyc.submit({ userId: 'u-001', documentType: 'passport', documentNumber: 'P123456', expiryDate: Date.now() + 730 * 86_400_000 });
|
|
97
|
+
kyc.verify(submission.id, 'approved', { notes: 'Document verified' });
|
|
98
|
+
const status = kyc.getStatus('u-001');
|
|
99
|
+
// { verificationStatus: 'verified', riskLevel, kycLevel }
|
|
100
|
+
kyc.status$().subscribe(update => { });
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Behavioral Biometrics
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { JoopBehavioralBiometricsService } from 'joopjs';
|
|
109
|
+
const bio = new JoopBehavioralBiometricsService();
|
|
110
|
+
|
|
111
|
+
bio.startSession('u-001', 'session-1');
|
|
112
|
+
bio.recordEvent('session-1', { type: 'keystroke', key: 'a', duration: 92, interval: 145 });
|
|
113
|
+
bio.recordEvent('session-1', { type: 'mouse', x: 450, y: 300, velocity: 2.3 });
|
|
114
|
+
bio.recordEvent('session-1', { type: 'swipe', direction: 'right', duration: 180, distance: 200 });
|
|
115
|
+
|
|
116
|
+
const analysis = bio.analyzeSession('session-1');
|
|
117
|
+
// analysis.anomalyScore ∈ [0,1], analysis.confidence, analysis.flags[]
|
|
118
|
+
|
|
119
|
+
bio.setBaseline('u-001', { avgKeystrokeDuration: 90, avgMouseVelocity: 2.5 });
|
|
120
|
+
bio.anomaly$().subscribe(({ sessionId, score, flags }) => { });
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Threat Intelligence
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
import { JoopThreatIntelligenceService } from 'joopjs';
|
|
129
|
+
const ti = new JoopThreatIntelligenceService();
|
|
130
|
+
|
|
131
|
+
ti.addIndicator({ type: 'ip', value: '192.168.1.100', threat: 'botnet', severity: 'high', confidence: 0.9 });
|
|
132
|
+
ti.addIndicator({ type: 'domain', value: 'evil.com', threat: 'phishing', severity: 'critical', confidence: 1.0 });
|
|
133
|
+
|
|
134
|
+
const check = ti.checkIp('192.168.1.100');
|
|
135
|
+
// { found: true, threat, severity, confidence } | { found: false }
|
|
136
|
+
|
|
137
|
+
const block = ti.isBlocked('domain', 'evil.com'); // boolean
|
|
138
|
+
ti.blockIndicator('ip', '10.0.0.1');
|
|
139
|
+
ti.alert$().subscribe(threat => { });
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Compliance Policy
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
import { JoopCompliancePolicyService } from 'joopjs';
|
|
148
|
+
const compliance = new JoopCompliancePolicyService();
|
|
149
|
+
|
|
150
|
+
compliance.addPolicy({ id: 'KYC-001', name: 'Customer Verification', jurisdiction: 'US', regulation: 'BSA', requirement: 'Verify all customers above $10k threshold', enabled: true });
|
|
151
|
+
compliance.recordAudit({ policyId: 'KYC-001', action: 'check', outcome: 'pass', details: 'Customer ID verified', performedBy: 'system' });
|
|
152
|
+
|
|
153
|
+
const status = compliance.getPolicyStatus('KYC-001'); // { policy, lastAudit, auditCount, passRate }
|
|
154
|
+
const failures = compliance.getRecentFailures(7 * 86_400_000);
|
|
155
|
+
compliance.violation$().subscribe(v => { });
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Penetration Test Reporter
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
import { JoopPenTestReporterService } from 'joopjs';
|
|
164
|
+
const pentest = new JoopPenTestReporterService();
|
|
165
|
+
|
|
166
|
+
const engagement = pentest.createEngagement({ name: 'Q4 2025 Assessment', target: 'Banking API', scope: ['authentication', 'data validation'] });
|
|
167
|
+
pentest.addFinding(engagement.id, { title: 'SQL Injection in login', severity: 'critical', cvss: 9.1, description: '...', recommendation: 'Use parameterized queries' });
|
|
168
|
+
const report = pentest.generateReport(engagement.id); // { executiveSummary, findings, riskMatrix }
|
|
169
|
+
const stats = pentest.getStats(engagement.id); // { critical, high, medium, low, info }
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Vulnerability Tracker
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
import { JoopVulnerabilityTrackerService } from 'joopjs';
|
|
178
|
+
const vulns = new JoopVulnerabilityTrackerService();
|
|
179
|
+
|
|
180
|
+
vulns.add({ id: 'CVE-2025-001', title: 'Auth bypass', severity: 'high', cvss: 8.5, affectedComponent: 'login-service', status: 'open' });
|
|
181
|
+
vulns.updateStatus('CVE-2025-001', 'patched', 'Applied fix in v1.2.3');
|
|
182
|
+
const open = vulns.getOpen(); // unresolved vulnerabilities
|
|
183
|
+
const overdue = vulns.getOverdue(30 * 86_400_000); // open longer than 30 days
|
|
184
|
+
const stats = vulns.getStats(); // { total, open, patched, bySeverity }
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Security Audit Log
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
import { JoopSecurityAuditService } from 'joopjs';
|
|
193
|
+
const audit = new JoopSecurityAuditService();
|
|
194
|
+
|
|
195
|
+
audit.log({ userId: 'u-001', action: 'login', resource: 'auth', outcome: 'success', ip: '10.0.0.1' });
|
|
196
|
+
audit.log({ userId: 'u-001', action: 'transfer', resource: 'wallet', outcome: 'success', amount: 1000 });
|
|
197
|
+
const logs = audit.query({ userId: 'u-001', from: startTs, to: endTs });
|
|
198
|
+
const failures = audit.getFailures('login', 24 * 3600_000); // login failures in last 24h
|
|
199
|
+
const suspicious = audit.getSuspiciousActivity('u-001'); // multiple failures, unusual hours
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Data Masking
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
import { JoopDataMaskingService } from 'joopjs';
|
|
208
|
+
const masking = new JoopDataMaskingService();
|
|
209
|
+
|
|
210
|
+
const masked = masking.maskPan('4111111111111111'); // '**** **** **** 1111'
|
|
211
|
+
const maskedEmail = masking.maskEmail('user@bank.com'); // 'u***@bank.com'
|
|
212
|
+
const maskedPhone = masking.maskPhone('+1-555-123-4567'); // '+1-555-***-4567'
|
|
213
|
+
masking.addRule({ field: 'ssn', pattern: /^\d{9}$/, mask: (v) => `***-**-${v.slice(-4)}` });
|
|
214
|
+
const obj = masking.maskObject({ name: 'Alice', ssn: '123456789', pan: '4111111111111111' });
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## What Gets Published to npm
|
|
220
|
+
|
|
221
|
+
Controlled by `"files"` in `package.json` — acts as an allowlist. Only these two are included:
|
|
222
|
+
|
|
223
|
+
| Published to npm | Never published |
|
|
224
|
+
|-----------------|----------------|
|
|
225
|
+
| `dist/` — ESM + CJS + `.d.ts` for all 34 sub-paths | `src/` — TypeScript source |
|
|
226
|
+
| `CHANGELOG.md` — release history | `tests/` — test suite |
|
|
227
|
+
| | `scripts/` — release automation |
|
|
228
|
+
| | `playground/` — Vite demo app |
|
|
229
|
+
| | `.claude/` — Claude skills (including this file) |
|
|
230
|
+
| | `.cursor/` — Cursor rules |
|
|
231
|
+
| | `.windsurf/` — Windsurf rules |
|
|
232
|
+
| | `GEMINI.md`, `AGENTS.md` — AI tool instructions |
|
|
233
|
+
| | `tsup.config.ts`, `vitest.config.ts`, `tsconfig.json` |
|
|
234
|
+
|
|
235
|
+
Source code, AI rules, and dev tooling are **never** published to npm.
|
|
236
|
+
|
|
237
|
+
Verify the tarball contents before any publish:
|
|
238
|
+
```bash
|
|
239
|
+
npm pack --dry-run
|
|
240
|
+
```
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# /setup — Install and Configure JoopJS
|
|
2
|
+
|
|
3
|
+
> Author: Kundan Singh
|
|
4
|
+
|
|
5
|
+
Use this skill when a developer needs to add joopjs to a project, bootstrap it, or configure it.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install joopjs
|
|
13
|
+
# or
|
|
14
|
+
yarn add joopjs
|
|
15
|
+
pnpm add joopjs
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
No peer dependencies are required for core features. React / Angular / Vue bindings are optional.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Bootstrap
|
|
23
|
+
|
|
24
|
+
### Plain TypeScript / Vanilla JS
|
|
25
|
+
```ts
|
|
26
|
+
import { createJoop } from 'joopjs';
|
|
27
|
+
|
|
28
|
+
const joop = createJoop({
|
|
29
|
+
env: 'production', // 'development' | 'staging' | 'production'
|
|
30
|
+
appId: 'my-banking-app',
|
|
31
|
+
version: '1.0.0',
|
|
32
|
+
locale: 'en',
|
|
33
|
+
currency: 'USD',
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### React
|
|
38
|
+
```tsx
|
|
39
|
+
// app.tsx
|
|
40
|
+
import { createJoop } from 'joopjs';
|
|
41
|
+
export const joop = createJoop({ env: 'production', appId: 'app' });
|
|
42
|
+
|
|
43
|
+
// Use services as singletons
|
|
44
|
+
import { JoopAuthService } from 'joopjs';
|
|
45
|
+
const auth = new JoopAuthService();
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Angular (standalone)
|
|
49
|
+
```ts
|
|
50
|
+
// main.ts
|
|
51
|
+
import { provideJoopAngular } from 'joopjs/angular';
|
|
52
|
+
import { createJoop } from 'joopjs';
|
|
53
|
+
|
|
54
|
+
bootstrapApplication(AppComponent, {
|
|
55
|
+
providers: [provideJoopAngular(createJoop({ env: 'production', appId: 'app' }))]
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Inject the instance with `injectJoop()` and bridge any JoopJS observable to an Angular signal with `joopSignal()` (auto-teardown via `DestroyRef`):
|
|
60
|
+
```ts
|
|
61
|
+
import { injectJoop, joopSignal } from 'joopjs/angular';
|
|
62
|
+
|
|
63
|
+
const joop = injectJoop();
|
|
64
|
+
const session = joopSignal(auth.session$()); // Angular signal, no manual unsubscribe
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
> Optional peers for the Angular adapter: `@angular/common` and `@angular/router` (only needed if you use the HTTP interceptor / guard). The core SDK never imports a framework.
|
|
68
|
+
|
|
69
|
+
### Angular (NgModule — legacy)
|
|
70
|
+
```ts
|
|
71
|
+
// Deprecated but still functional — prefer provideJoopAngular() above.
|
|
72
|
+
import { JoopModule } from 'joopjs/angular';
|
|
73
|
+
import { createJoop } from 'joopjs';
|
|
74
|
+
|
|
75
|
+
@NgModule({
|
|
76
|
+
imports: [JoopModule.forRoot(createJoop({ env: 'production', appId: 'app' }))]
|
|
77
|
+
})
|
|
78
|
+
export class AppModule {}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Vue
|
|
82
|
+
```ts
|
|
83
|
+
// main.ts
|
|
84
|
+
import { createApp } from 'vue';
|
|
85
|
+
import { JoopVuePlugin } from 'joopjs/vue';
|
|
86
|
+
import { createJoop } from 'joopjs';
|
|
87
|
+
|
|
88
|
+
createApp(App)
|
|
89
|
+
.use(JoopVuePlugin, createJoop({ env: 'production', appId: 'app' }))
|
|
90
|
+
.mount('#app');
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Configuration Reference
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
interface JoopConfig {
|
|
99
|
+
env: 'development' | 'staging' | 'production';
|
|
100
|
+
appId: string;
|
|
101
|
+
version?: string;
|
|
102
|
+
locale?: string; // default: 'en'
|
|
103
|
+
currency?: string; // default: 'USD'
|
|
104
|
+
logLevel?: 'debug' | 'info' | 'warn' | 'error' | 'none';
|
|
105
|
+
apiBaseUrl?: string; // base URL for JoopHttpClient
|
|
106
|
+
timeout?: number; // HTTP timeout ms, default 30000
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Service Instantiation Pattern
|
|
113
|
+
|
|
114
|
+
All services are plain classes — no DI required:
|
|
115
|
+
```ts
|
|
116
|
+
import {
|
|
117
|
+
JoopDigitalWalletService,
|
|
118
|
+
JoopLoanServicingService,
|
|
119
|
+
JoopAmlService,
|
|
120
|
+
} from 'joopjs';
|
|
121
|
+
|
|
122
|
+
// Create once at module/app level (singletons)
|
|
123
|
+
const wallet = new JoopDigitalWalletService();
|
|
124
|
+
const loans = new JoopLoanServicingService();
|
|
125
|
+
const aml = new JoopAmlService();
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Tree-Shakeable Sub-path Imports
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
import { JoopGcmService } from 'joopjs/encryption';
|
|
134
|
+
import { JoopAuthService } from 'joopjs/auth';
|
|
135
|
+
import { JoopCacheService } from 'joopjs/cache';
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Available sub-paths: `encryption`, `auth`, `api`, `core`, `session`, `banking`, `security`, `device`, `observability`, `theme`, `i18n`, `ui`, `native-bridge`, `deeplink`, `cache`, `network`, `analytics`, `validation`, `utilities`, `forms`, `pwa`, `router`, `ai`, `state`, `workers`, `workflow`, `sync`, `platform`, `react`, `angular`, `vue`, `india`.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Environment-Specific Setup
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
// Use JoopConfigService for runtime config
|
|
146
|
+
import { JoopConfigService } from 'joopjs';
|
|
147
|
+
const config = new JoopConfigService();
|
|
148
|
+
config.set('apiBaseUrl', 'https://api.mybank.com');
|
|
149
|
+
config.set('currency', 'AED');
|
|
150
|
+
|
|
151
|
+
// Use JoopEnvironmentService for env detection
|
|
152
|
+
import { JoopEnvironmentService } from 'joopjs';
|
|
153
|
+
const env = new JoopEnvironmentService();
|
|
154
|
+
env.set('production', { debug: false, apiUrl: 'https://api.prod.com' });
|
|
155
|
+
const profile = env.get('production'); // { debug, apiUrl }
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Health Check
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
import { JoopHealthService } from 'joopjs';
|
|
164
|
+
const health = new JoopHealthService();
|
|
165
|
+
health.register('api', async () => {
|
|
166
|
+
const ok = await fetch('/health').then(r => r.ok);
|
|
167
|
+
return { name: 'api', healthy: ok };
|
|
168
|
+
});
|
|
169
|
+
const report = await health.check();
|
|
170
|
+
// { healthy: true, checks: [{ name: 'api', healthy: true }] }
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## What Gets Published to npm
|
|
176
|
+
|
|
177
|
+
Controlled by `"files"` in `package.json` — acts as an allowlist. Only these two are included:
|
|
178
|
+
|
|
179
|
+
| Published to npm | Never published |
|
|
180
|
+
|-----------------|----------------|
|
|
181
|
+
| `dist/` — ESM + CJS + `.d.ts` for all 34 sub-paths | `src/` — TypeScript source |
|
|
182
|
+
| `CHANGELOG.md` — release history | `tests/` — test suite |
|
|
183
|
+
| | `scripts/` — release automation |
|
|
184
|
+
| | `playground/` — Vite demo app |
|
|
185
|
+
| | `.claude/` — Claude skills (including this file) |
|
|
186
|
+
| | `.cursor/` — Cursor rules |
|
|
187
|
+
| | `.windsurf/` — Windsurf rules |
|
|
188
|
+
| | `GEMINI.md`, `AGENTS.md` — AI tool instructions |
|
|
189
|
+
| | `tsup.config.ts`, `vitest.config.ts`, `tsconfig.json` |
|
|
190
|
+
|
|
191
|
+
Source code, AI rules, and dev tooling are **never** published to npm.
|
|
192
|
+
|
|
193
|
+
Verify the tarball contents before any publish:
|
|
194
|
+
```bash
|
|
195
|
+
npm pack --dry-run
|
|
196
|
+
```
|