@engjts/nexus 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/advanced/playground/generatePlaygroundHTML.d.ts.map +1 -1
- package/dist/advanced/playground/generatePlaygroundHTML.js +107 -0
- package/dist/advanced/playground/generatePlaygroundHTML.js.map +1 -1
- package/dist/advanced/playground/playground.d.ts +19 -0
- package/dist/advanced/playground/playground.d.ts.map +1 -1
- package/dist/advanced/playground/playground.js +70 -0
- package/dist/advanced/playground/playground.js.map +1 -1
- package/dist/advanced/playground/types.d.ts +20 -0
- package/dist/advanced/playground/types.d.ts.map +1 -1
- package/dist/core/application.d.ts +14 -0
- package/dist/core/application.d.ts.map +1 -1
- package/dist/core/application.js +173 -71
- package/dist/core/application.js.map +1 -1
- package/dist/core/context-pool.d.ts +2 -13
- package/dist/core/context-pool.d.ts.map +1 -1
- package/dist/core/context-pool.js +7 -45
- package/dist/core/context-pool.js.map +1 -1
- package/dist/core/context.d.ts +108 -5
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/context.js +449 -53
- package/dist/core/context.js.map +1 -1
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +9 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/middleware.d.ts +6 -0
- package/dist/core/middleware.d.ts.map +1 -1
- package/dist/core/middleware.js +83 -84
- package/dist/core/middleware.js.map +1 -1
- package/dist/core/performance/fast-json.d.ts +149 -0
- package/dist/core/performance/fast-json.d.ts.map +1 -0
- package/dist/core/performance/fast-json.js +473 -0
- package/dist/core/performance/fast-json.js.map +1 -0
- package/dist/core/router/file-router.d.ts +20 -7
- package/dist/core/router/file-router.d.ts.map +1 -1
- package/dist/core/router/file-router.js +41 -13
- package/dist/core/router/file-router.js.map +1 -1
- package/dist/core/router/index.d.ts +6 -0
- package/dist/core/router/index.d.ts.map +1 -1
- package/dist/core/router/index.js +33 -6
- package/dist/core/router/index.js.map +1 -1
- package/dist/core/router/radix-tree.d.ts +4 -1
- package/dist/core/router/radix-tree.d.ts.map +1 -1
- package/dist/core/router/radix-tree.js +7 -3
- package/dist/core/router/radix-tree.js.map +1 -1
- package/dist/core/serializer.d.ts +251 -0
- package/dist/core/serializer.d.ts.map +1 -0
- package/dist/core/serializer.js +290 -0
- package/dist/core/serializer.js.map +1 -0
- package/dist/core/types.d.ts +39 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -2
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- package/documentation/01-getting-started.md +0 -240
- package/documentation/02-context.md +0 -335
- package/documentation/03-routing.md +0 -397
- package/documentation/04-middleware.md +0 -483
- package/documentation/05-validation.md +0 -514
- package/documentation/06-error-handling.md +0 -465
- package/documentation/07-performance.md +0 -364
- package/documentation/08-adapters.md +0 -470
- package/documentation/09-api-reference.md +0 -548
- package/documentation/10-examples.md +0 -582
- package/documentation/11-deployment.md +0 -477
- package/documentation/12-sentry.md +0 -620
- package/documentation/13-sentry-data-storage.md +0 -996
- package/documentation/14-sentry-data-reference.md +0 -457
- package/documentation/15-sentry-summary.md +0 -409
- package/documentation/16-alerts-system.md +0 -745
- package/documentation/17-alert-adapters.md +0 -696
- package/documentation/18-alerts-implementation-summary.md +0 -385
- package/documentation/19-class-based-routing.md +0 -840
- package/documentation/20-websocket-realtime.md +0 -813
- package/documentation/21-cache-system.md +0 -510
- package/documentation/22-job-queue.md +0 -772
- package/documentation/23-sentry-plugin.md +0 -551
- package/documentation/24-testing-utilities.md +0 -1287
- package/documentation/25-api-versioning.md +0 -533
- package/documentation/26-context-store.md +0 -607
- package/documentation/27-dependency-injection.md +0 -329
- package/documentation/28-lifecycle-hooks.md +0 -521
- package/documentation/29-package-structure.md +0 -196
- package/documentation/30-plugin-system.md +0 -414
- package/documentation/31-jwt-authentication.md +0 -597
- package/documentation/32-cli.md +0 -268
- package/documentation/ALERTS-COMPLETE-SUMMARY.md +0 -429
- package/documentation/ALERTS-INDEX.md +0 -330
- package/documentation/ALERTS-QUICK-REFERENCE.md +0 -286
- package/documentation/README.md +0 -178
- package/documentation/index.html +0 -34
- package/modern_framework_paper.md +0 -1870
- package/public/css/style.css +0 -87
- package/public/index.html +0 -34
- package/public/js/app.js +0 -27
- package/src/advanced/cache/InMemoryCacheStore.ts +0 -68
- package/src/advanced/cache/MultiTierCache.ts +0 -194
- package/src/advanced/cache/RedisCacheStore.ts +0 -341
- package/src/advanced/cache/index.ts +0 -5
- package/src/advanced/cache/types.ts +0 -40
- package/src/advanced/graphql/SimpleDataLoader.ts +0 -42
- package/src/advanced/graphql/index.ts +0 -22
- package/src/advanced/graphql/server.ts +0 -252
- package/src/advanced/graphql/types.ts +0 -42
- package/src/advanced/jobs/InMemoryQueueStore.ts +0 -68
- package/src/advanced/jobs/JobQueue.ts +0 -556
- package/src/advanced/jobs/RedisQueueStore.ts +0 -367
- package/src/advanced/jobs/index.ts +0 -5
- package/src/advanced/jobs/types.ts +0 -70
- package/src/advanced/observability/APMManager.ts +0 -163
- package/src/advanced/observability/AlertManager.ts +0 -109
- package/src/advanced/observability/MetricRegistry.ts +0 -151
- package/src/advanced/observability/ObservabilityCenter.ts +0 -304
- package/src/advanced/observability/StructuredLogger.ts +0 -154
- package/src/advanced/observability/TracingManager.ts +0 -117
- package/src/advanced/observability/adapters.ts +0 -304
- package/src/advanced/observability/createObservabilityMiddleware.ts +0 -63
- package/src/advanced/observability/index.ts +0 -11
- package/src/advanced/observability/types.ts +0 -174
- package/src/advanced/playground/extractPathParams.ts +0 -6
- package/src/advanced/playground/generateFieldExample.ts +0 -31
- package/src/advanced/playground/generatePlaygroundHTML.ts +0 -1849
- package/src/advanced/playground/generateSummary.ts +0 -19
- package/src/advanced/playground/getTagFromPath.ts +0 -9
- package/src/advanced/playground/index.ts +0 -8
- package/src/advanced/playground/playground.ts +0 -170
- package/src/advanced/playground/types.ts +0 -20
- package/src/advanced/playground/zodToExample.ts +0 -16
- package/src/advanced/playground/zodToParams.ts +0 -15
- package/src/advanced/postman/buildAuth.ts +0 -31
- package/src/advanced/postman/buildBody.ts +0 -15
- package/src/advanced/postman/buildQueryParams.ts +0 -27
- package/src/advanced/postman/buildRequestItem.ts +0 -36
- package/src/advanced/postman/buildResponses.ts +0 -11
- package/src/advanced/postman/buildUrl.ts +0 -33
- package/src/advanced/postman/capitalize.ts +0 -4
- package/src/advanced/postman/generateCollection.ts +0 -59
- package/src/advanced/postman/generateEnvironment.ts +0 -34
- package/src/advanced/postman/generateExampleFromZod.ts +0 -21
- package/src/advanced/postman/generateFieldExample.ts +0 -45
- package/src/advanced/postman/generateName.ts +0 -20
- package/src/advanced/postman/generateUUID.ts +0 -11
- package/src/advanced/postman/getTagFromPath.ts +0 -10
- package/src/advanced/postman/index.ts +0 -28
- package/src/advanced/postman/postman.ts +0 -156
- package/src/advanced/postman/slugify.ts +0 -7
- package/src/advanced/postman/types.ts +0 -140
- package/src/advanced/realtime/index.ts +0 -18
- package/src/advanced/realtime/websocket.ts +0 -231
- package/src/advanced/sentry/index.ts +0 -1236
- package/src/advanced/sentry/types.ts +0 -355
- package/src/advanced/static/generateDirectoryListing.ts +0 -47
- package/src/advanced/static/generateETag.ts +0 -7
- package/src/advanced/static/getMimeType.ts +0 -9
- package/src/advanced/static/index.ts +0 -32
- package/src/advanced/static/isSafePath.ts +0 -13
- package/src/advanced/static/publicDir.ts +0 -21
- package/src/advanced/static/serveStatic.ts +0 -225
- package/src/advanced/static/spa.ts +0 -24
- package/src/advanced/static/types.ts +0 -159
- package/src/advanced/swagger/SwaggerGenerator.ts +0 -66
- package/src/advanced/swagger/buildOperation.ts +0 -61
- package/src/advanced/swagger/buildParameters.ts +0 -61
- package/src/advanced/swagger/buildRequestBody.ts +0 -21
- package/src/advanced/swagger/buildResponses.ts +0 -54
- package/src/advanced/swagger/capitalize.ts +0 -5
- package/src/advanced/swagger/convertPath.ts +0 -9
- package/src/advanced/swagger/createSwagger.ts +0 -12
- package/src/advanced/swagger/generateOperationId.ts +0 -21
- package/src/advanced/swagger/generateSpec.ts +0 -105
- package/src/advanced/swagger/generateSummary.ts +0 -24
- package/src/advanced/swagger/generateSwaggerUI.ts +0 -70
- package/src/advanced/swagger/generateThemeCss.ts +0 -53
- package/src/advanced/swagger/index.ts +0 -25
- package/src/advanced/swagger/swagger.ts +0 -237
- package/src/advanced/swagger/types.ts +0 -206
- package/src/advanced/swagger/zodFieldToOpenAPI.ts +0 -94
- package/src/advanced/swagger/zodSchemaToOpenAPI.ts +0 -50
- package/src/advanced/swagger/zodToOpenAPI.ts +0 -22
- package/src/advanced/testing/factory.ts +0 -509
- package/src/advanced/testing/harness.ts +0 -612
- package/src/advanced/testing/index.ts +0 -430
- package/src/advanced/testing/load-test.ts +0 -618
- package/src/advanced/testing/mock-server.ts +0 -498
- package/src/advanced/testing/mock.ts +0 -670
- package/src/cli/bin.ts +0 -9
- package/src/cli/cli.ts +0 -158
- package/src/cli/commands/add.ts +0 -178
- package/src/cli/commands/build.ts +0 -73
- package/src/cli/commands/create.ts +0 -166
- package/src/cli/commands/dev.ts +0 -85
- package/src/cli/commands/generate.ts +0 -99
- package/src/cli/commands/help.ts +0 -95
- package/src/cli/commands/init.ts +0 -91
- package/src/cli/commands/version.ts +0 -38
- package/src/cli/index.ts +0 -6
- package/src/cli/templates/generators.ts +0 -359
- package/src/cli/templates/index.ts +0 -680
- package/src/cli/utils/exec.ts +0 -52
- package/src/cli/utils/file-system.ts +0 -78
- package/src/cli/utils/logger.ts +0 -111
- package/src/core/adapter.ts +0 -88
- package/src/core/application.ts +0 -1335
- package/src/core/context-pool.ts +0 -127
- package/src/core/context.ts +0 -412
- package/src/core/index.ts +0 -80
- package/src/core/middleware.ts +0 -262
- package/src/core/performance/buffer-pool.ts +0 -108
- package/src/core/performance/middleware-optimizer.ts +0 -162
- package/src/core/plugin/PluginManager.ts +0 -435
- package/src/core/plugin/builder.ts +0 -358
- package/src/core/plugin/index.ts +0 -50
- package/src/core/plugin/types.ts +0 -214
- package/src/core/router/file-router.ts +0 -594
- package/src/core/router/index.ts +0 -227
- package/src/core/router/radix-tree.ts +0 -226
- package/src/core/store/index.ts +0 -30
- package/src/core/store/registry.ts +0 -178
- package/src/core/store/request-store.ts +0 -240
- package/src/core/store/types.ts +0 -233
- package/src/core/types.ts +0 -574
- package/src/database/adapter.ts +0 -35
- package/src/database/adapters/index.ts +0 -1
- package/src/database/adapters/mysql.ts +0 -669
- package/src/database/database.ts +0 -70
- package/src/database/dialect.ts +0 -388
- package/src/database/index.ts +0 -12
- package/src/database/migrations.ts +0 -86
- package/src/database/optimizer.ts +0 -125
- package/src/database/query-builder.ts +0 -404
- package/src/database/realtime.ts +0 -53
- package/src/database/schema.ts +0 -71
- package/src/database/transactions.ts +0 -56
- package/src/database/types.ts +0 -87
- package/src/deployment/cluster.ts +0 -471
- package/src/deployment/config.ts +0 -454
- package/src/deployment/docker.ts +0 -599
- package/src/deployment/graceful-shutdown.ts +0 -373
- package/src/deployment/index.ts +0 -56
- package/src/index.ts +0 -264
- package/src/security/adapter.ts +0 -318
- package/src/security/auth/JWTPlugin.ts +0 -234
- package/src/security/auth/JWTProvider.ts +0 -316
- package/src/security/auth/adapter.ts +0 -12
- package/src/security/auth/jwt.ts +0 -234
- package/src/security/auth/middleware.ts +0 -188
- package/src/security/csrf.ts +0 -220
- package/src/security/headers.ts +0 -108
- package/src/security/index.ts +0 -60
- package/src/security/rate-limit/adapter.ts +0 -7
- package/src/security/rate-limit/memory.ts +0 -108
- package/src/security/rate-limit/middleware.ts +0 -181
- package/src/security/sanitization.ts +0 -75
- package/src/security/types.ts +0 -240
- package/src/security/utils.ts +0 -52
- package/tsconfig.json +0 -39
|
@@ -1,607 +0,0 @@
|
|
|
1
|
-
# ContextStore & RequestStore - State Management
|
|
2
|
-
|
|
3
|
-
Nexus menyediakan dua jenis state management yang terinspirasi dari Flutter's Provider pattern:
|
|
4
|
-
|
|
5
|
-
| Store Type | Scope | Lifetime | Use Case |
|
|
6
|
-
|------------|-------|----------|----------|
|
|
7
|
-
| **ContextStore** | Global (Singleton) | App lifetime | User session, cache, shared state |
|
|
8
|
-
| **RequestStore** | Per-request | Request only | Form processing, temp calculations |
|
|
9
|
-
|
|
10
|
-
## Konsep Utama
|
|
11
|
-
|
|
12
|
-
```
|
|
13
|
-
┌─────────────────────────────────────────────────────────────────────┐
|
|
14
|
-
│ Application │
|
|
15
|
-
│ ┌────────────────────────────────────────────────────────────┐ │
|
|
16
|
-
│ │ StoreRegistry (Global/Singleton) │ │
|
|
17
|
-
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
|
|
18
|
-
│ │ │ UserStore │ │ CartStore │ │ CacheStore │ │ │
|
|
19
|
-
│ │ │ (persist) │ │ (persist) │ │ (persist) │ │ │
|
|
20
|
-
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
|
|
21
|
-
│ └────────────────────────────────────────────────────────────┘ │
|
|
22
|
-
│ │
|
|
23
|
-
│ ┌────────────────────────────────────────────────────────────┐ │
|
|
24
|
-
│ │ Request 1 Request 2 │ │
|
|
25
|
-
│ │ ┌──────────────┐ ┌──────────────┐ │ │
|
|
26
|
-
│ │ │CheckoutStore │ │CheckoutStore │ │ │
|
|
27
|
-
│ │ │ (disposed) │ │ (disposed) │ │ │
|
|
28
|
-
│ │ └──────────────┘ └──────────────┘ │ │
|
|
29
|
-
│ │ Different instance! Different instance! │ │
|
|
30
|
-
│ └────────────────────────────────────────────────────────────┘ │
|
|
31
|
-
└─────────────────────────────────────────────────────────────────────┘
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
---
|
|
35
|
-
|
|
36
|
-
## Quick Start
|
|
37
|
-
|
|
38
|
-
### ContextStore (Global)
|
|
39
|
-
|
|
40
|
-
```typescript
|
|
41
|
-
import { ContextStore, createApp } from 'nexus-framework';
|
|
42
|
-
|
|
43
|
-
// 1. Define state type
|
|
44
|
-
interface UserState {
|
|
45
|
-
users: User[];
|
|
46
|
-
loading: boolean;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// 2. Create store class
|
|
50
|
-
class UserStore extends ContextStore<UserState> {
|
|
51
|
-
protected initial(): UserState {
|
|
52
|
-
return { users: [], loading: false };
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
async fetchUsers() {
|
|
56
|
-
this.update({ loading: true });
|
|
57
|
-
const users = await api.getUsers();
|
|
58
|
-
this.set({ users, loading: false });
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// 3. Register store
|
|
63
|
-
const app = createApp();
|
|
64
|
-
app.stores([UserStore]);
|
|
65
|
-
|
|
66
|
-
// 4. Access in routes
|
|
67
|
-
app.get('/users', async (ctx) => {
|
|
68
|
-
const userStore = ctx.store(UserStore);
|
|
69
|
-
return { users: userStore.state.users };
|
|
70
|
-
});
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### RequestStore (Per-Request)
|
|
74
|
-
|
|
75
|
-
```typescript
|
|
76
|
-
import { RequestStore } from 'nexus-framework';
|
|
77
|
-
|
|
78
|
-
// 1. Define state type
|
|
79
|
-
interface CheckoutState {
|
|
80
|
-
items: Item[];
|
|
81
|
-
total: number;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// 2. Create store class (NO registration needed!)
|
|
85
|
-
class CheckoutStore extends RequestStore<CheckoutState> {
|
|
86
|
-
protected initial(): CheckoutState {
|
|
87
|
-
return { items: [], total: 0 };
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
addItem(item: Item) {
|
|
91
|
-
const items = [...this.state.items, item];
|
|
92
|
-
const total = items.reduce((sum, i) => sum + i.price, 0);
|
|
93
|
-
this.set({ items, total });
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// 3. Access in routes (auto-created on first access)
|
|
98
|
-
app.post('/checkout', async (ctx) => {
|
|
99
|
-
const checkout = ctx.requestStore(CheckoutStore);
|
|
100
|
-
checkout.addItem(ctx.body.item);
|
|
101
|
-
return { total: checkout.state.total };
|
|
102
|
-
});
|
|
103
|
-
// Store automatically disposed after response!
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
---
|
|
107
|
-
|
|
108
|
-
## ContextStore (Global) - Detail
|
|
109
|
-
|
|
110
|
-
### Kapan Pakai?
|
|
111
|
-
|
|
112
|
-
- ✅ Data yang perlu persist (user session, authentication)
|
|
113
|
-
- ✅ Shared state antar requests (cache, counters)
|
|
114
|
-
- ✅ Real-time features (WebSocket state, SSE)
|
|
115
|
-
- ✅ Background tasks state
|
|
116
|
-
|
|
117
|
-
### API Reference
|
|
118
|
-
|
|
119
|
-
#### Properties
|
|
120
|
-
|
|
121
|
-
| Property | Type | Description |
|
|
122
|
-
|----------|------|-------------|
|
|
123
|
-
| `state` | `Readonly<T>` | Current state (read-only) |
|
|
124
|
-
| `name` | `string` | Store name (default: class name) |
|
|
125
|
-
| `listenerCount` | `number` | Active listeners count |
|
|
126
|
-
| `isInitialized` | `boolean` | Initialization status |
|
|
127
|
-
|
|
128
|
-
#### Methods
|
|
129
|
-
|
|
130
|
-
| Method | Description |
|
|
131
|
-
|--------|-------------|
|
|
132
|
-
| `protected initial(): T` | **Required.** Return initial state |
|
|
133
|
-
| `protected set(state: T)` | Replace entire state |
|
|
134
|
-
| `protected update(partial: Partial<T>)` | Merge partial state |
|
|
135
|
-
| `listen(callback): unsubscribe` | Subscribe to changes |
|
|
136
|
-
| `reset()` | Reset to initial state |
|
|
137
|
-
| `dispose()` | Cleanup store |
|
|
138
|
-
|
|
139
|
-
#### Lifecycle Hooks
|
|
140
|
-
|
|
141
|
-
| Hook | When Called |
|
|
142
|
-
|------|-------------|
|
|
143
|
-
| `onInit()` | After registration |
|
|
144
|
-
| `onDispose()` | Before disposal |
|
|
145
|
-
|
|
146
|
-
### Example: Real-time Counter
|
|
147
|
-
|
|
148
|
-
```typescript
|
|
149
|
-
class CounterStore extends ContextStore<{ count: number }> {
|
|
150
|
-
protected initial() {
|
|
151
|
-
return { count: 0 };
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
increment() {
|
|
155
|
-
this.update({ count: this.state.count + 1 });
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
decrement() {
|
|
159
|
-
this.update({ count: this.state.count - 1 });
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Register
|
|
164
|
-
app.stores([CounterStore]);
|
|
165
|
-
|
|
166
|
-
// Increment from any route
|
|
167
|
-
app.post('/increment', async (ctx) => {
|
|
168
|
-
ctx.store(CounterStore).increment();
|
|
169
|
-
return { count: ctx.store(CounterStore).state.count };
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
// SSE - real-time updates
|
|
173
|
-
app.get('/counter-stream', async (ctx) => {
|
|
174
|
-
const counter = ctx.store(CounterStore);
|
|
175
|
-
|
|
176
|
-
ctx.raw.res.setHeader('Content-Type', 'text/event-stream');
|
|
177
|
-
|
|
178
|
-
const unsubscribe = counter.listen((state) => {
|
|
179
|
-
ctx.raw.res.write(`data: ${JSON.stringify(state)}\n\n`);
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
ctx.raw.req.on('close', unsubscribe);
|
|
183
|
-
return new Promise(() => {}); // Keep alive
|
|
184
|
-
});
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
---
|
|
188
|
-
|
|
189
|
-
## RequestStore (Per-Request) - Detail
|
|
190
|
-
|
|
191
|
-
### Kapan Pakai?
|
|
192
|
-
|
|
193
|
-
- ✅ Form processing (checkout, wizard steps)
|
|
194
|
-
- ✅ Request-specific calculations
|
|
195
|
-
- ✅ Temporary data aggregation
|
|
196
|
-
- ✅ Multi-step operations dalam satu request
|
|
197
|
-
|
|
198
|
-
### API Reference
|
|
199
|
-
|
|
200
|
-
Same as ContextStore, plus:
|
|
201
|
-
|
|
202
|
-
| Property | Type | Description |
|
|
203
|
-
|----------|------|-------------|
|
|
204
|
-
| `requestId` | `string` | Unique ID per request |
|
|
205
|
-
|
|
206
|
-
### Key Differences from ContextStore
|
|
207
|
-
|
|
208
|
-
| Aspect | ContextStore | RequestStore |
|
|
209
|
-
|--------|--------------|--------------|
|
|
210
|
-
| Registration | `app.stores([...])` | **Not needed** |
|
|
211
|
-
| Access | `ctx.store(Class)` | `ctx.requestStore(Class)` |
|
|
212
|
-
| Lifetime | App lifetime | Request only |
|
|
213
|
-
| Instance | Singleton | Fresh per request |
|
|
214
|
-
| Disposal | Manual/shutdown | Auto after response |
|
|
215
|
-
|
|
216
|
-
### Example: Multi-Step Checkout
|
|
217
|
-
|
|
218
|
-
```typescript
|
|
219
|
-
interface CheckoutState {
|
|
220
|
-
items: CartItem[];
|
|
221
|
-
subtotal: number;
|
|
222
|
-
discount: number;
|
|
223
|
-
tax: number;
|
|
224
|
-
total: number;
|
|
225
|
-
step: 'cart' | 'shipping' | 'payment' | 'confirm';
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
class CheckoutStore extends RequestStore<CheckoutState> {
|
|
229
|
-
protected initial(): CheckoutState {
|
|
230
|
-
return {
|
|
231
|
-
items: [],
|
|
232
|
-
subtotal: 0,
|
|
233
|
-
discount: 0,
|
|
234
|
-
tax: 0,
|
|
235
|
-
total: 0,
|
|
236
|
-
step: 'cart'
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
addItem(item: CartItem) {
|
|
241
|
-
const items = [...this.state.items, item];
|
|
242
|
-
this.recalculate(items);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
applyDiscount(code: string) {
|
|
246
|
-
const discount = this.calculateDiscount(code);
|
|
247
|
-
this.recalculate(this.state.items, discount);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
private recalculate(items: CartItem[], discount = this.state.discount) {
|
|
251
|
-
const subtotal = items.reduce((sum, i) => sum + i.price * i.qty, 0);
|
|
252
|
-
const afterDiscount = subtotal - discount;
|
|
253
|
-
const tax = afterDiscount * 0.11;
|
|
254
|
-
const total = afterDiscount + tax;
|
|
255
|
-
|
|
256
|
-
this.set({
|
|
257
|
-
...this.state,
|
|
258
|
-
items,
|
|
259
|
-
subtotal,
|
|
260
|
-
discount,
|
|
261
|
-
tax,
|
|
262
|
-
total
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
nextStep() {
|
|
267
|
-
const steps: CheckoutState['step'][] = ['cart', 'shipping', 'payment', 'confirm'];
|
|
268
|
-
const currentIndex = steps.indexOf(this.state.step);
|
|
269
|
-
if (currentIndex < steps.length - 1) {
|
|
270
|
-
this.update({ step: steps[currentIndex + 1] });
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
private calculateDiscount(code: string): number {
|
|
275
|
-
const discounts: Record<string, number> = {
|
|
276
|
-
'SAVE10': this.state.subtotal * 0.1,
|
|
277
|
-
'SAVE20': this.state.subtotal * 0.2,
|
|
278
|
-
};
|
|
279
|
-
return discounts[code] || 0;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// Usage
|
|
284
|
-
app.post('/checkout/process', async (ctx) => {
|
|
285
|
-
const checkout = ctx.requestStore(CheckoutStore);
|
|
286
|
-
|
|
287
|
-
// Step 1: Add items
|
|
288
|
-
for (const item of ctx.body.items) {
|
|
289
|
-
checkout.addItem(item);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// Step 2: Apply discount
|
|
293
|
-
if (ctx.body.discountCode) {
|
|
294
|
-
checkout.applyDiscount(ctx.body.discountCode);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Step 3: Process each step
|
|
298
|
-
checkout.nextStep(); // cart → shipping
|
|
299
|
-
// validate shipping...
|
|
300
|
-
|
|
301
|
-
checkout.nextStep(); // shipping → payment
|
|
302
|
-
// process payment...
|
|
303
|
-
|
|
304
|
-
checkout.nextStep(); // payment → confirm
|
|
305
|
-
|
|
306
|
-
return {
|
|
307
|
-
requestId: checkout.requestId,
|
|
308
|
-
order: checkout.state
|
|
309
|
-
};
|
|
310
|
-
});
|
|
311
|
-
// CheckoutStore disposed after response - clean slate for next request!
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
### Example: Request Validation Pipeline
|
|
315
|
-
|
|
316
|
-
```typescript
|
|
317
|
-
interface ValidationState {
|
|
318
|
-
errors: string[];
|
|
319
|
-
warnings: string[];
|
|
320
|
-
isValid: boolean;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
class ValidationStore extends RequestStore<ValidationState> {
|
|
324
|
-
protected initial() {
|
|
325
|
-
return { errors: [], warnings: [], isValid: true };
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
addError(msg: string) {
|
|
329
|
-
this.set({
|
|
330
|
-
errors: [...this.state.errors, msg],
|
|
331
|
-
warnings: this.state.warnings,
|
|
332
|
-
isValid: false
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
addWarning(msg: string) {
|
|
337
|
-
this.update({
|
|
338
|
-
warnings: [...this.state.warnings, msg]
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Middleware-like usage
|
|
344
|
-
app.post('/api/users', async (ctx) => {
|
|
345
|
-
const validation = ctx.requestStore(ValidationStore);
|
|
346
|
-
|
|
347
|
-
// Validate email
|
|
348
|
-
if (!ctx.body.email?.includes('@')) {
|
|
349
|
-
validation.addError('Invalid email format');
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// Validate password
|
|
353
|
-
if (ctx.body.password?.length < 8) {
|
|
354
|
-
validation.addError('Password must be at least 8 characters');
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// Check for weak password
|
|
358
|
-
if (ctx.body.password === '12345678') {
|
|
359
|
-
validation.addWarning('Password is too common');
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// Return early if invalid
|
|
363
|
-
if (!validation.state.isValid) {
|
|
364
|
-
return ctx.response.status(400).json({
|
|
365
|
-
success: false,
|
|
366
|
-
errors: validation.state.errors,
|
|
367
|
-
warnings: validation.state.warnings
|
|
368
|
-
});
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// Proceed with user creation...
|
|
372
|
-
return { success: true };
|
|
373
|
-
});
|
|
374
|
-
```
|
|
375
|
-
|
|
376
|
-
---
|
|
377
|
-
|
|
378
|
-
## Using Both Together
|
|
379
|
-
|
|
380
|
-
```typescript
|
|
381
|
-
// Global: User session
|
|
382
|
-
class SessionStore extends ContextStore<{ userId: string | null }> {
|
|
383
|
-
protected initial() { return { userId: null }; }
|
|
384
|
-
|
|
385
|
-
login(userId: string) { this.update({ userId }); }
|
|
386
|
-
logout() { this.update({ userId: null }); }
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// Per-request: Order processing
|
|
390
|
-
class OrderStore extends RequestStore<{ items: Item[]; total: number }> {
|
|
391
|
-
protected initial() { return { items: [], total: 0 }; }
|
|
392
|
-
|
|
393
|
-
addItem(item: Item) {
|
|
394
|
-
const items = [...this.state.items, item];
|
|
395
|
-
this.set({ items, total: items.reduce((s, i) => s + i.price, 0) });
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
app.stores([SessionStore]);
|
|
400
|
-
|
|
401
|
-
app.post('/order', async (ctx) => {
|
|
402
|
-
// Get global session
|
|
403
|
-
const session = ctx.store(SessionStore);
|
|
404
|
-
|
|
405
|
-
if (!session.state.userId) {
|
|
406
|
-
return ctx.response.status(401).json({ error: 'Not logged in' });
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// Get request-scoped order
|
|
410
|
-
const order = ctx.requestStore(OrderStore);
|
|
411
|
-
|
|
412
|
-
for (const item of ctx.body.items) {
|
|
413
|
-
order.addItem(item);
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// Save order with user ID
|
|
417
|
-
await db.orders.create({
|
|
418
|
-
userId: session.state.userId,
|
|
419
|
-
items: order.state.items,
|
|
420
|
-
total: order.state.total
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
return { success: true, total: order.state.total };
|
|
424
|
-
});
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
---
|
|
428
|
-
|
|
429
|
-
## Application Methods
|
|
430
|
-
|
|
431
|
-
### For ContextStore (Global)
|
|
432
|
-
|
|
433
|
-
| Method | Description |
|
|
434
|
-
|--------|-------------|
|
|
435
|
-
| `app.store(StoreClass)` | Register single store |
|
|
436
|
-
| `app.stores(StoreClass[])` | Register multiple stores |
|
|
437
|
-
| `app.getStore(StoreClass)` | Access from app level |
|
|
438
|
-
| `app.getStoreRegistry()` | Get registry |
|
|
439
|
-
|
|
440
|
-
### Context Methods
|
|
441
|
-
|
|
442
|
-
| Method | Description |
|
|
443
|
-
|--------|-------------|
|
|
444
|
-
| `ctx.store(StoreClass)` | Access global store |
|
|
445
|
-
| `ctx.requestStore(StoreClass)` | Access request-scoped store |
|
|
446
|
-
|
|
447
|
-
---
|
|
448
|
-
|
|
449
|
-
## Best Practices
|
|
450
|
-
|
|
451
|
-
### 1. Immutable Updates
|
|
452
|
-
|
|
453
|
-
```typescript
|
|
454
|
-
// ❌ Mutating state directly
|
|
455
|
-
this.state.users.push(user); // Won't trigger listeners!
|
|
456
|
-
|
|
457
|
-
// ✅ Create new reference
|
|
458
|
-
this.set({
|
|
459
|
-
...this.state,
|
|
460
|
-
users: [...this.state.users, user]
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
// ✅ Or use update() for partial
|
|
464
|
-
this.update({
|
|
465
|
-
users: [...this.state.users, user]
|
|
466
|
-
});
|
|
467
|
-
```
|
|
468
|
-
|
|
469
|
-
### 2. Computed Properties
|
|
470
|
-
|
|
471
|
-
```typescript
|
|
472
|
-
class CartStore extends ContextStore<CartState> {
|
|
473
|
-
// Use getters for computed values
|
|
474
|
-
get itemCount(): number {
|
|
475
|
-
return this.state.items.length;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
get totalPrice(): number {
|
|
479
|
-
return this.state.items.reduce((sum, item) => sum + item.price, 0);
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
get isEmpty(): boolean {
|
|
483
|
-
return this.state.items.length === 0;
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
// Usage
|
|
488
|
-
const cart = ctx.store(CartStore);
|
|
489
|
-
console.log(cart.totalPrice); // Computed on access
|
|
490
|
-
```
|
|
491
|
-
|
|
492
|
-
### 3. Async Actions
|
|
493
|
-
|
|
494
|
-
```typescript
|
|
495
|
-
class DataStore extends ContextStore<DataState> {
|
|
496
|
-
async fetchData() {
|
|
497
|
-
// Show loading
|
|
498
|
-
this.update({ loading: true, error: null });
|
|
499
|
-
|
|
500
|
-
try {
|
|
501
|
-
const data = await api.getData();
|
|
502
|
-
this.update({ data, loading: false });
|
|
503
|
-
} catch (error) {
|
|
504
|
-
this.update({
|
|
505
|
-
loading: false,
|
|
506
|
-
error: error.message
|
|
507
|
-
});
|
|
508
|
-
throw error; // Re-throw if needed
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
```
|
|
513
|
-
|
|
514
|
-
### 4. Cleanup Listeners
|
|
515
|
-
|
|
516
|
-
```typescript
|
|
517
|
-
app.get('/events', async (ctx) => {
|
|
518
|
-
const store = ctx.store(MyStore);
|
|
519
|
-
|
|
520
|
-
// ✅ Always cleanup listeners
|
|
521
|
-
const unsubscribe = store.listen((state) => {
|
|
522
|
-
ctx.raw.res.write(`data: ${JSON.stringify(state)}\n\n`);
|
|
523
|
-
});
|
|
524
|
-
|
|
525
|
-
ctx.raw.req.on('close', () => {
|
|
526
|
-
unsubscribe(); // Prevent memory leak!
|
|
527
|
-
});
|
|
528
|
-
});
|
|
529
|
-
```
|
|
530
|
-
|
|
531
|
-
---
|
|
532
|
-
|
|
533
|
-
## Type Definitions
|
|
534
|
-
|
|
535
|
-
```typescript
|
|
536
|
-
// Listener callback
|
|
537
|
-
type StoreListener<T> = (state: T, prevState: T) => void;
|
|
538
|
-
|
|
539
|
-
// Unsubscribe function
|
|
540
|
-
type DisposeCallback = () => void;
|
|
541
|
-
|
|
542
|
-
// Store constructor
|
|
543
|
-
type StoreConstructor<T extends ContextStore<any>> = new (...args: any[]) => T;
|
|
544
|
-
type RequestStoreConstructor<T extends RequestStore<any>> = new (...args: any[]) => T;
|
|
545
|
-
|
|
546
|
-
// Extract state type
|
|
547
|
-
type StateOf<T> = T extends ContextStore<infer S> ? S : never;
|
|
548
|
-
type RequestStateOf<T> = T extends RequestStore<infer S> ? S : never;
|
|
549
|
-
```
|
|
550
|
-
|
|
551
|
-
---
|
|
552
|
-
|
|
553
|
-
## Troubleshooting
|
|
554
|
-
|
|
555
|
-
### "Store not registered"
|
|
556
|
-
|
|
557
|
-
```typescript
|
|
558
|
-
// ❌ Error: Store UserStore is not registered
|
|
559
|
-
ctx.store(UserStore);
|
|
560
|
-
|
|
561
|
-
// ✅ Solution: Register first
|
|
562
|
-
app.stores([UserStore]);
|
|
563
|
-
ctx.store(UserStore); // OK!
|
|
564
|
-
```
|
|
565
|
-
|
|
566
|
-
### "RequestStore still has data"
|
|
567
|
-
|
|
568
|
-
```typescript
|
|
569
|
-
// ❌ Wrong expectation: RequestStore shares data
|
|
570
|
-
app.get('/a', (ctx) => {
|
|
571
|
-
ctx.requestStore(MyStore).setValue('hello');
|
|
572
|
-
});
|
|
573
|
-
app.get('/b', (ctx) => {
|
|
574
|
-
// This is ALWAYS empty - different request!
|
|
575
|
-
ctx.requestStore(MyStore).state.value; // undefined
|
|
576
|
-
});
|
|
577
|
-
|
|
578
|
-
// ✅ Use ContextStore for shared data
|
|
579
|
-
app.stores([MyStore]);
|
|
580
|
-
app.get('/a', (ctx) => ctx.store(MyStore).setValue('hello'));
|
|
581
|
-
app.get('/b', (ctx) => ctx.store(MyStore).state.value); // 'hello'
|
|
582
|
-
```
|
|
583
|
-
|
|
584
|
-
### Memory Leak Warning
|
|
585
|
-
|
|
586
|
-
```typescript
|
|
587
|
-
// ❌ Listener never cleaned up
|
|
588
|
-
store.listen((state) => { ... });
|
|
589
|
-
|
|
590
|
-
// ✅ Always cleanup
|
|
591
|
-
const unsubscribe = store.listen((state) => { ... });
|
|
592
|
-
// Later:
|
|
593
|
-
unsubscribe();
|
|
594
|
-
```
|
|
595
|
-
|
|
596
|
-
---
|
|
597
|
-
|
|
598
|
-
## Migration from Flutter Provider
|
|
599
|
-
|
|
600
|
-
| Flutter | Nexus |
|
|
601
|
-
|---------|-------|
|
|
602
|
-
| `ChangeNotifier` | `ContextStore<T>` / `RequestStore<T>` |
|
|
603
|
-
| `notifyListeners()` | `set()` / `update()` |
|
|
604
|
-
| `Provider.of<T>(context)` | `ctx.store(T)` / `ctx.requestStore(T)` |
|
|
605
|
-
| `Consumer<T>` | `store.listen()` |
|
|
606
|
-
| `MultiProvider` | `app.stores([...])` |
|
|
607
|
-
| Scoped Provider | `RequestStore` |
|