bff-store 0.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/settings.local.json +45 -0
- package/CONTEXT.md +53 -0
- package/README.md +223 -0
- package/dist/cli.js +32577 -0
- package/dist/index.d.mts +232 -0
- package/dist/index.d.ts +232 -0
- package/dist/index.mjs +430 -0
- package/dist/package.json +62 -0
- package/dist/server/entry.d.mts +94 -0
- package/dist/server/entry.d.ts +94 -0
- package/dist/server/entry.js +573 -0
- package/dist/server/entry.mjs +533 -0
- package/dist/server-V7WCW4ZB.mjs +530 -0
- package/dist/storage/jsonl-entry.d.mts +42 -0
- package/dist/storage/jsonl-entry.d.ts +42 -0
- package/dist/storage/jsonl-entry.js +112 -0
- package/dist/storage/jsonl-entry.mjs +74 -0
- package/dist/storage/mongodb-entry.d.mts +40 -0
- package/dist/storage/mongodb-entry.d.ts +40 -0
- package/dist/storage/mongodb-entry.js +114 -0
- package/dist/storage/mongodb-entry.mjs +86 -0
- package/docs/BUG_DIAGNOSIS_REMOTE_STORAGE_OPTIONS.md +104 -0
- package/docs/BUG_FIX_REMOTE_STORAGE_OPTIONS.md +63 -0
- package/docs/BUG_FIX_SESSION_2026-06-03.md +171 -0
- package/docs/IMPLEMENTATION.md +333 -0
- package/docs/PLAN.md +153 -0
- package/docs/REMOTE_STORAGE_CONFIG.md +125 -0
- package/docs/SIDECAR_SERVER.md +184 -0
- package/package.json +62 -0
- package/scripts/adapt-dist-package.js +33 -0
- package/src/atomCreator.ts +76 -0
- package/src/createStore.ts +77 -0
- package/src/debouncer.ts +84 -0
- package/src/index.ts +35 -0
- package/src/server/cli.ts +62 -0
- package/src/server/entityIdCache.ts +57 -0
- package/src/server/entry.ts +12 -0
- package/src/server/handlers.ts +271 -0
- package/src/server/index.ts +182 -0
- package/src/server/router.ts +74 -0
- package/src/server.ts +5 -0
- package/src/storage/adapters/remoteStorage.ts +70 -0
- package/src/storage/base.ts +28 -0
- package/src/storage/index.ts +9 -0
- package/src/storage/jsonl-entry.ts +9 -0
- package/src/storage/jsonl.ts +111 -0
- package/src/storage/memory.ts +49 -0
- package/src/storage/mongodb-entry.ts +9 -0
- package/src/storage/mongodb.ts +132 -0
- package/src/storage/protocol.ts +170 -0
- package/src/storage/transport.ts +95 -0
- package/src/types.ts +76 -0
- package/src/useStore.ts +83 -0
- package/tests/atomCreator.test.ts +153 -0
- package/tests/createStore.test.ts +126 -0
- package/tests/debouncer.test.ts +125 -0
- package/tests/server.test.ts +158 -0
- package/tests/storage/jsonl.test.ts +132 -0
- package/tests/storage/memory.test.ts +101 -0
- package/tests/storage/mongodb.test.ts +40 -0
- package/tests/storage/remoteStorage.test.ts +126 -0
- package/tests/useStore.test.tsx +147 -0
- package/tsconfig.json +18 -0
- package/tsup.config.ts +53 -0
- package/vitest.config.ts +14 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(npm install:*)",
|
|
5
|
+
"Bash(npm run:*)",
|
|
6
|
+
"Bash(ls -la *.config.* vitest.config.*)",
|
|
7
|
+
"Bash(npm test:*)",
|
|
8
|
+
"Bash(ls -la vitest.config.*)",
|
|
9
|
+
"Bash(node -e ':*)",
|
|
10
|
+
"Bash(awk NR>=54 && NR<=95:*)",
|
|
11
|
+
"Bash(awk NR>=84 && NR<=131:*)",
|
|
12
|
+
"Bash(awk NR>=1 && NR<=35:*)",
|
|
13
|
+
"Bash(awk NR>=94 && NR<=120:*)",
|
|
14
|
+
"Bash(awk NR>=19 && NR<=65:*)",
|
|
15
|
+
"Bash(grep -l \"handlers\" /Users/Admin/Desktop/jotai-state-store/tests/*.ts)",
|
|
16
|
+
"Bash(xargs ls:*)",
|
|
17
|
+
"Bash(grep:*)",
|
|
18
|
+
"Bash(node:*)",
|
|
19
|
+
"Bash(npx vitest:*)",
|
|
20
|
+
"Bash(npx create-next-app@latest . --typescript --eslint --no-tailwind --app --src-dir --no-import-alias --use-npm)",
|
|
21
|
+
"Bash(npm link:*)",
|
|
22
|
+
"Bash(curl -s http://localhost:3847/health)",
|
|
23
|
+
"Bash(curl:*)",
|
|
24
|
+
"Read(//private/tmp/jotai-state-store-data/**)",
|
|
25
|
+
"Bash(cat /tmp/jotai-state-store-data/test-app/*.jsonl)",
|
|
26
|
+
"Bash(npx tsc:*)",
|
|
27
|
+
"Bash(ls /Users/Admin/Desktop/jotai-state-store/dist/*.d.ts)",
|
|
28
|
+
"Bash(npx tsup:*)",
|
|
29
|
+
"Bash(find /Users/Admin/Desktop/jotai-state-store/tests/next_test/node_modules -name \"package.json\" -path \"*/jotai-state-store/*\" -exec cat {} \\\\;)",
|
|
30
|
+
"Bash(find:*)",
|
|
31
|
+
"Bash(ls /Users/Admin/Desktop/jotai-state-store/dist/*.ts)",
|
|
32
|
+
"Read(//Users/Admin/node_modules/jotai-state-store/dist/**)",
|
|
33
|
+
"Bash(ls:*)",
|
|
34
|
+
"Bash(kill 89616)",
|
|
35
|
+
"Bash(python3 -c 'import urllib.parse; print\\(urllib.parse.quote\\(\"mongodb://admin:715705%40Qc123@localhost:27017/jotai_test?directConnection=true&authSource=admin\"\\)\\)')",
|
|
36
|
+
"Bash(python3 -c 'import urllib.parse.quote\\(\"mongodb://admin:715705%40Qc123@localhost:27017/jotai_test?directConnection=true&authSource=admin\"\\)\\)')",
|
|
37
|
+
"Read(//tmp/**)",
|
|
38
|
+
"Bash(kill 68874)",
|
|
39
|
+
"Bash(npm publish:*)",
|
|
40
|
+
"Bash(rg -rn \"jotai-state-store\" --type-list)",
|
|
41
|
+
"Bash(rg -rn \"jotai-state-store\" .)",
|
|
42
|
+
"Bash(xargs sed:*)"
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
}
|
package/CONTEXT.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Domain Context
|
|
2
|
+
|
|
3
|
+
## Core Concepts
|
|
4
|
+
|
|
5
|
+
### Store (协调者)
|
|
6
|
+
The central coordinator that manages multiple persisted atoms. Created via `createStore()`, it holds:
|
|
7
|
+
- `entityId` - the unique identifier for this store instance
|
|
8
|
+
- `config` - the atom configuration array
|
|
9
|
+
- `atoms` - map of key → WritableAtom
|
|
10
|
+
- `loadingAtoms` - map of key → loading state atom
|
|
11
|
+
|
|
12
|
+
### Atom Config (原子配置)
|
|
13
|
+
Configuration for a single persisted state field:
|
|
14
|
+
- `key` - unique identifier within the store
|
|
15
|
+
- `defaultValue` - initial value before loading from storage
|
|
16
|
+
- `type` - optional type hint ('string' | 'number' | 'boolean' | 'array' | 'object')
|
|
17
|
+
- `immediate` - if true, save immediately without debouncing
|
|
18
|
+
|
|
19
|
+
### Storage Adapter (存储适配器)
|
|
20
|
+
The seam between the store and the underlying persistence layer. Implements the `Storage` interface and provides:
|
|
21
|
+
- `storage` - the Storage implementation
|
|
22
|
+
- `name` - identifier for the adapter type
|
|
23
|
+
- Optional lifecycle methods: `setEntityId()`, `close()`
|
|
24
|
+
|
|
25
|
+
### Storage (存储接口)
|
|
26
|
+
The minimal interface for persistence:
|
|
27
|
+
- `get<T>(key)` - load value by key
|
|
28
|
+
- `set<T>(key, value)` - save value
|
|
29
|
+
- `remove(key)` - delete value
|
|
30
|
+
- Optional: `getMultiple()`, `setMultiple()` for batch operations
|
|
31
|
+
|
|
32
|
+
### Debouncer (防抖器)
|
|
33
|
+
Manages delayed execution of save operations. Prevents excessive writes by:
|
|
34
|
+
- Buffering rapid changes
|
|
35
|
+
- Saving after a quiet period (default 800ms)
|
|
36
|
+
- Supporting immediate mode for critical data
|
|
37
|
+
|
|
38
|
+
## Relationships
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
Store (coordinates)
|
|
42
|
+
├── config: AtomConfig[]
|
|
43
|
+
├── atoms: Map<key, WritableAtom>
|
|
44
|
+
└── storage: StorageAdapter (seam to persistence)
|
|
45
|
+
└── storage: Storage (data interface)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Setter Naming Convention
|
|
49
|
+
|
|
50
|
+
For an atom config with key `theme`:
|
|
51
|
+
- Value access: `store.atoms.theme.get()`
|
|
52
|
+
- Setter name: `setTheme` (derived via capitalize)
|
|
53
|
+
- Via hook: `{ theme, setTheme } = useStore(store)`
|
package/README.md
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# bff-store
|
|
2
|
+
|
|
3
|
+
A jotai-based state management library with pluggable storage adapters and built-in BFF (Backend for Frontend) for browser/Next.js environments.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Configuration-driven**: Define multiple states via array configuration
|
|
8
|
+
- **Pluggable storage**: Use memory, JSONL files, MongoDB, or remote server
|
|
9
|
+
- **Auto-generated hooks**: React hooks auto-generated from config
|
|
10
|
+
- **Loading states**: Built-in loading state tracking
|
|
11
|
+
- **Debounced saves**: Automatic debouncing for non-critical data
|
|
12
|
+
- **Built-in BFF**: Embedded sidecar API server for browser/Next.js environments
|
|
13
|
+
|
|
14
|
+
## Architecture
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
┌─────────────────────────────────────────────────────────┐
|
|
18
|
+
│ Your Application │
|
|
19
|
+
│ (Next.js/Browser) │
|
|
20
|
+
│ │
|
|
21
|
+
│ useStore() ──► remoteStorage ──► localhost:3847 │
|
|
22
|
+
│ │ │
|
|
23
|
+
│ ▼ │
|
|
24
|
+
│ ┌─────────────────────┐ │
|
|
25
|
+
│ │ BFF (Sidecar) │ │
|
|
26
|
+
│ │ bff-store │ │
|
|
27
|
+
│ │ │ │
|
|
28
|
+
│ │ /storage/get/:key │ │
|
|
29
|
+
│ │ /storage/set/:key │ │
|
|
30
|
+
│ │ /storage/delete │ │
|
|
31
|
+
│ └──────────┬──────────┘ │
|
|
32
|
+
│ │ │
|
|
33
|
+
│ ▼ │
|
|
34
|
+
│ ┌─────────────────────┐ │
|
|
35
|
+
│ │ JSONL / MongoDB │ │
|
|
36
|
+
│ └─────────────────────┘ │
|
|
37
|
+
└─────────────────────────────────────────────────────────┘
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm install bff-store
|
|
44
|
+
# or
|
|
45
|
+
pnpm add bff-store
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Client Usage (Browser / Next.js)
|
|
49
|
+
|
|
50
|
+
BFF Server 会自动启动,客户端只需使用 `remoteStorage()` 并指定存储后端:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { createStore, useStore, remoteStorage } from 'bff-store';
|
|
54
|
+
|
|
55
|
+
const config = [
|
|
56
|
+
{ key: 'theme', defaultValue: 'dark' },
|
|
57
|
+
{ key: 'characters', defaultValue: [] },
|
|
58
|
+
] as const;
|
|
59
|
+
|
|
60
|
+
// 使用 MongoDB 存储
|
|
61
|
+
const store = createStore('my-app', config, {
|
|
62
|
+
storage: remoteStorage({
|
|
63
|
+
backend: 'mongodb',
|
|
64
|
+
mongoUrl: 'mongodb://user:pass@host:27017',
|
|
65
|
+
mongoDb: 'myapp',
|
|
66
|
+
}),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// 或使用 JSONL 存储
|
|
70
|
+
// const store = createStore('my-app', config, {
|
|
71
|
+
// storage: remoteStorage({
|
|
72
|
+
// backend: 'jsonl',
|
|
73
|
+
// jsonlDir: '/tmp/my-app-data',
|
|
74
|
+
// }),
|
|
75
|
+
// });
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 在 React 组件中使用
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
function App() {
|
|
82
|
+
const { theme, setTheme, isLoading } = useStore(store);
|
|
83
|
+
|
|
84
|
+
if (isLoading) return <div>Loading...</div>;
|
|
85
|
+
|
|
86
|
+
return <input value={theme} onChange={e => setTheme(e.target.value)} />;
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 自定义 BFF 服务器地址
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
// 如果 BFF 运行在其他地址
|
|
94
|
+
const adapter = remoteStorage({ baseUrl: 'http://localhost:3847' });
|
|
95
|
+
const adapter = remoteStorage({ entityId: 'user-123' }); // 多租户支持
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Memory Storage (不持久化)
|
|
99
|
+
|
|
100
|
+
适用于开发环境或不需要持久化的场景:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { createStore, useStore, memoryStorage } from 'bff-store';
|
|
104
|
+
|
|
105
|
+
const store = createStore('my-app', config, {
|
|
106
|
+
storage: memoryStorage(), // 不走 BFF,数据仅存在内存
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Advanced: 手动启动 BFF Server
|
|
111
|
+
|
|
112
|
+
大多数情况不需要手动启动 BFF,它会自动启动。如需手动控制:
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
import { startServer } from 'bff-store/server';
|
|
116
|
+
|
|
117
|
+
await startServer({
|
|
118
|
+
port: 3847,
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Node.js 直接使用 Storage Adapters
|
|
123
|
+
|
|
124
|
+
如果不通过 BFF,可以直接在 Node.js 中使用存储适配器:
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
import { createStore } from 'bff-store';
|
|
128
|
+
import { jsonlStorage } from 'bff-store/jsonl';
|
|
129
|
+
import { mongodbStorage } from 'bff-store/mongodb';
|
|
130
|
+
|
|
131
|
+
// 使用 JSONL
|
|
132
|
+
const adapter = jsonlStorage({ dir: './sessions' });
|
|
133
|
+
|
|
134
|
+
// 使用 MongoDB
|
|
135
|
+
const adapter = await mongodbStorage({
|
|
136
|
+
url: 'mongodb://localhost:27017',
|
|
137
|
+
database: 'myapp',
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const store = createStore('entity-123', config, { storage: adapter });
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## API Endpoints (BFF Server)
|
|
144
|
+
|
|
145
|
+
| Method | Endpoint | Body | Description |
|
|
146
|
+
|--------|----------|------|-------------|
|
|
147
|
+
| GET | `/storage/get/:key?entityId=x` | - | Get value by key |
|
|
148
|
+
| POST | `/storage/set/:key?entityId=x` | `{ value }` | Set value |
|
|
149
|
+
| DELETE | `/storage/delete/:key?entityId=x` | - | Delete key |
|
|
150
|
+
| POST | `/storage/batch-get?entityId=x` | `{ keys: [] }` | Batch get |
|
|
151
|
+
| POST | `/storage/batch-set?entityId=x` | `{ entries: {} }` | Batch set |
|
|
152
|
+
| GET | `/health` | - | Health check |
|
|
153
|
+
|
|
154
|
+
## API Reference
|
|
155
|
+
|
|
156
|
+
### `createStore(entityId, config, options)`
|
|
157
|
+
|
|
158
|
+
Creates a store with multiple persisted atoms.
|
|
159
|
+
|
|
160
|
+
- `entityId`: Unique identifier for this store instance
|
|
161
|
+
- `config`: Array of atom configurations
|
|
162
|
+
- `options.storage`: Storage adapter (required)
|
|
163
|
+
- `options.debounceMs`: Debounce delay in ms (default: 800)
|
|
164
|
+
|
|
165
|
+
### `useStore(store)`
|
|
166
|
+
|
|
167
|
+
React hook to use the store in components.
|
|
168
|
+
|
|
169
|
+
Returns: `{ ...data, ...setters, isLoading }`
|
|
170
|
+
|
|
171
|
+
Setter names are derived by capitalizing the config key:
|
|
172
|
+
- `theme` → `setTheme`
|
|
173
|
+
- `userName` → `setUserName`
|
|
174
|
+
|
|
175
|
+
### `remoteStorage(options?)`
|
|
176
|
+
|
|
177
|
+
Creates a remote storage adapter for connecting to the BFF server.
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
// 基本用法 - BFF 使用默认存储后端
|
|
181
|
+
const adapter = remoteStorage();
|
|
182
|
+
|
|
183
|
+
// 指定使用 MongoDB
|
|
184
|
+
const adapter = remoteStorage({
|
|
185
|
+
backend: 'mongodb',
|
|
186
|
+
mongoUrl: 'mongodb://user:pass@host:27017',
|
|
187
|
+
mongoDb: 'myapp',
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// 指定使用 JSONL
|
|
191
|
+
const adapter = remoteStorage({
|
|
192
|
+
backend: 'jsonl',
|
|
193
|
+
jsonlDir: '/tmp/my-app-data',
|
|
194
|
+
});
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
- `options.baseUrl`: BFF server URL (default: `http://localhost:3847`)
|
|
198
|
+
- `options.entityId`: Default entityId for all requests
|
|
199
|
+
- `options.backend`: Storage backend type `'mongodb'` or `'jsonl'`
|
|
200
|
+
- `options.mongoUrl`: MongoDB connection URL (required if backend is mongodb)
|
|
201
|
+
- `options.mongoDb`: MongoDB database name (default: `jotai_state_store`)
|
|
202
|
+
- `options.jsonlDir`: JSONL storage directory (required if backend is jsonl)
|
|
203
|
+
|
|
204
|
+
### `startServer(options)`
|
|
205
|
+
|
|
206
|
+
Starts the BFF server (singleton pattern). Import from `bff-store/server`.
|
|
207
|
+
|
|
208
|
+
- `options.port`: Server port (default: 3847)
|
|
209
|
+
|
|
210
|
+
Note: Storage backend is configured by the client via `remoteStorage()` options, not by the server.
|
|
211
|
+
|
|
212
|
+
## Package Exports
|
|
213
|
+
|
|
214
|
+
| Export | Description | Environment |
|
|
215
|
+
|--------|-------------|-------------|
|
|
216
|
+
| `bff-store` | Main entry: createStore, useStore, memoryStorage, remoteStorage | Browser + Node.js |
|
|
217
|
+
| `bff-store/jsonl` | JSONL storage adapter | Node.js only |
|
|
218
|
+
| `bff-store/mongodb` | MongoDB storage adapter | Node.js only |
|
|
219
|
+
| `bff-store/server` | BFF server (startServer, Router, etc.) | Node.js only |
|
|
220
|
+
|
|
221
|
+
## License
|
|
222
|
+
|
|
223
|
+
MIT
|