bff-store 0.1.0 → 0.1.2

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.
Files changed (54) hide show
  1. package/.claude/settings.local.json +14 -1
  2. package/README.md +196 -63
  3. package/dist/cli.js +11 -9
  4. package/dist/index.d.mts +48 -1
  5. package/dist/index.d.ts +48 -1
  6. package/dist/index.mjs +88 -10
  7. package/dist/package.json +3 -6
  8. package/dist/server/entry.d.mts +1 -1
  9. package/dist/server/entry.d.ts +1 -1
  10. package/dist/server/entry.js +31992 -23
  11. package/dist/server/entry.mjs +32016 -13
  12. package/dist/{server-V7WCW4ZB.mjs → server-2WMLXQ23.mjs} +11 -9
  13. package/dist/storage/jsonl-entry.js +1 -1
  14. package/dist/storage/jsonl-entry.mjs +1 -1
  15. package/dist/storage/mongodb-entry.js +31986 -10
  16. package/dist/storage/mongodb-entry.mjs +32008 -8
  17. package/docs/BUG_FIX_MONGODB_CHILD_PROCESS.md +129 -0
  18. package/docs/FRONTEND_BACKEND_ISOLATION.md +150 -0
  19. package/docs/IMPLEMENTATION_OVERVIEW.md +231 -0
  20. package/docs/LOCAL_LINK_STRATEGIES.md +117 -0
  21. package/docs/bugs/01-server-async-startup.high.md +34 -0
  22. package/docs/bugs/03-storage-cache-eviction.md +40 -0
  23. package/docs/bugs/04-http-error-body-loss.md +38 -0
  24. package/docs/bugs/05-debouncer-ms-ignored.md +41 -0
  25. package/docs/bugs/07-jsonl-key-collision.high.md +31 -0
  26. package/docs/bugs/08-dead-code-getStorageForRequest.md +18 -0
  27. package/docs/bugs/09-waitForLoad-infinite.md +37 -0
  28. package/docs/bugs/10-parseBody-silent-failure.md +32 -0
  29. package/docs/bugs/11-mongodb-storage-leak.high.md +35 -0
  30. package/docs/bugs/12-setEntityId-stale-closure.high.md +35 -0
  31. package/docs/bugs/14-dead-code-isServerRunning.md +18 -0
  32. package/docs/bugs/15-unmount-data-loss.md +37 -0
  33. package/docs/bugs/17-unused-parameter.md +26 -0
  34. package/docs/bugs/18-storage-get-silent-error.md +28 -0
  35. package/docs/bugs/README.md +35 -0
  36. package/package.json +3 -6
  37. package/src/atomCreator.ts +10 -2
  38. package/src/createStore.ts +19 -1
  39. package/src/debouncer.ts +5 -2
  40. package/src/environment.ts +11 -0
  41. package/src/index.ts +8 -1
  42. package/src/nodeStore.ts +86 -0
  43. package/src/server/handlers.ts +7 -24
  44. package/src/server/index.ts +0 -4
  45. package/src/storage/adapters/remoteStorage.ts +2 -1
  46. package/src/storage/jsonl.ts +3 -1
  47. package/src/storage/mongodb.ts +6 -6
  48. package/src/storage/transport.ts +24 -3
  49. package/tests/storage/jsonl.test.ts +18 -2
  50. package/tsconfig.json +1 -0
  51. package/docs/BUG_DIAGNOSIS_REMOTE_STORAGE_OPTIONS.md +0 -104
  52. package/docs/BUG_FIX_REMOTE_STORAGE_OPTIONS.md +0 -63
  53. package/docs/BUG_FIX_SESSION_2026-06-03.md +0 -171
  54. /package/docs/{SIDECAR_SERVER.md → BFF_SIDECAR_SERVER.md} +0 -0
@@ -39,7 +39,20 @@
39
39
  "Bash(npm publish:*)",
40
40
  "Bash(rg -rn \"jotai-state-store\" --type-list)",
41
41
  "Bash(rg -rn \"jotai-state-store\" .)",
42
- "Bash(xargs sed:*)"
42
+ "Bash(xargs sed:*)",
43
+ "Bash(npx tsx:*)",
44
+ "Bash(npm ls:*)",
45
+ "Bash(perl -e 'alarm 5; exec @ARGV' -- node debug-simple.js)",
46
+ "Bash(echo \"exit: $?\")",
47
+ "Bash([ -d node_modules/mongodb ])",
48
+ "Bash(echo \"---mongodb status: $\\([ -d node_modules/mongodb ] && echo 'INSTALLED' || echo 'NOT INSTALLED'\\)\")",
49
+ "Bash(echo \"---mongodb: $\\([ -d node_modules/mongodb ] && echo 'INSTALLED' || echo 'NOT INSTALLED'\\)\")",
50
+ "Bash(xargs kill:*)",
51
+ "Bash(npm start:*)",
52
+ "Bash(mv 01-server-async-startup.md 01-server-async-startup.high.md)",
53
+ "Bash(mv 07-jsonl-key-collision.md 07-jsonl-key-collision.high.md)",
54
+ "Bash(mv 11-mongodb-storage-leak.md 11-mongodb-storage-leak.high.md)",
55
+ "Bash(mv 12-setEntityId-stale-closure.md 12-setEntityId-stale-closure.high.md)"
43
56
  ]
44
57
  }
45
58
  }
package/README.md CHANGED
@@ -1,42 +1,77 @@
1
1
  # bff-store
2
2
 
3
- A jotai-based state management library with pluggable storage adapters and built-in BFF (Backend for Frontend) for browser/Next.js environments.
3
+ A jotai-based state management library with pluggable storage adapters and a built-in BFF (Backend for Frontend) for browser/Next.js environments.
4
4
 
5
5
  ## Features
6
6
 
7
7
  - **Configuration-driven**: Define multiple states via array configuration
8
- - **Pluggable storage**: Use memory, JSONL files, MongoDB, or remote server
8
+ - **Pluggable storage**: Use memory, JSONL files, MongoDB, or a remote server
9
9
  - **Auto-generated hooks**: React hooks auto-generated from config
10
10
  - **Loading states**: Built-in loading state tracking
11
- - **Debounced saves**: Automatic debouncing for non-critical data
11
+ - **Debounced saves**: Automatic debouncing for non-critical data (800ms default)
12
12
  - **Built-in BFF**: Embedded sidecar API server for browser/Next.js environments
13
+ - **Multi-tenant**: Per-entityId isolation via `setEntityId()`
14
+ - **Unmount cleanup**: Pending writes cancelled on component unmount
15
+ - **Embedded Sidecar**: Like a serverless function that auto-starts on first use — no manual server deployment required
13
16
 
14
17
  ## Architecture
15
18
 
16
19
  ```
17
20
  ┌─────────────────────────────────────────────────────────┐
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
- │ └─────────────────────┘
21
+ │ Your Application
22
+ │ (Next.js / Browser)
23
+
24
+ │ useStore() ──► remoteStorage ──► localhost:3847
25
+ │ │
26
+ │ ▼
27
+ │ ┌─────────────────────┐
28
+ │ │ BFF (Sidecar)
29
+ │ │ │
30
+ │ │ /storage/get/:key
31
+ │ │ /storage/set/:key │
32
+ │ │ /storage/batch-*
33
+ └──────────┬──────────┘
34
+
35
+
36
+ ┌─────────────────────┐
37
+ │ │ JSONL / MongoDB │ │
38
+ │ │ (multi-tenant)
39
+ │ └─────────────────────┘
37
40
  └─────────────────────────────────────────────────────────┘
38
41
  ```
39
42
 
43
+ ### Data Flow
44
+
45
+ 1. **Init**: `createStore()` creates atoms, `remoteStorage` auto-starts the BFF server
46
+ 2. **Read**: `useStore()` returns from jotai store directly — no I/O
47
+ 3. **Write**: Update local jotai atom first (sync UI response), then debounce-persist to storage
48
+ 4. **Unmount**: Automatically cancel pending writes to prevent writes from destroyed components
49
+
50
+ ## Embedded Sidecar Pattern
51
+
52
+ bff-store borrows the **embedded sidecar** pattern from microservices: the BFF server is co-located with your application process, auto-started on first use, with the lifecycle managed transparently.
53
+
54
+ | Aspect | Classic Microservice Sidecar | bff-store Embedded Sidecar |
55
+ |--------|----------------------------|--------------------------|
56
+ | Deployment | Separate container, same pod | In-process, same Node.js process |
57
+ | Startup | Kubernetes orchestrates | `createStore()` triggers `import('./server')` |
58
+ | Singleton | One per pod | One per process (singleton `startServer`) |
59
+ | State | External DB / volume | JSONL / MongoDB |
60
+ | Scaling | Horizontal pod scaling | Multiple store instances share one server |
61
+ | Cold start | Pod scheduling overhead | Module import + server listen (~100ms) |
62
+
63
+ This gives you the **transparency of serverless** (no manual server deployment) with the **control of a long-running process** (full backend, no 100ms timeout, no cold starts after first invocation).
64
+
65
+ ### How it compares to serverless
66
+
67
+ | | Serverless (Lambda) | bff-store Sidecar |
68
+ |--|---------------------|-------------------|
69
+ | Cold start | 100ms–1s (platform) | ~100ms (import + listen) |
70
+ | State | External KV store (S3/Dynamo) | JSONL / MongoDB (local) |
71
+ | Concurrency | One invocation per instance | All stores share one server |
72
+ | Tenant isolation | Separate functions | Separate collections / directories |
73
+ | Failure mode | Invocation fails | Server restarts, pending writes lost (mitigated by unmount cancel) |
74
+
40
75
  ## Installation
41
76
 
42
77
  ```bash
@@ -47,7 +82,7 @@ pnpm add bff-store
47
82
 
48
83
  ## Client Usage (Browser / Next.js)
49
84
 
50
- BFF Server 会自动启动,客户端只需使用 `remoteStorage()` 并指定存储后端:
85
+ The BFF server starts automatically. Use `remoteStorage()` to specify the storage backend:
51
86
 
52
87
  ```typescript
53
88
  import { createStore, useStore, remoteStorage } from 'bff-store';
@@ -55,9 +90,10 @@ import { createStore, useStore, remoteStorage } from 'bff-store';
55
90
  const config = [
56
91
  { key: 'theme', defaultValue: 'dark' },
57
92
  { key: 'characters', defaultValue: [] },
93
+ { key: 'settings', defaultValue: {}, immediate: true }, // persist immediately, no debounce
58
94
  ] as const;
59
95
 
60
- // 使用 MongoDB 存储
96
+ // Using MongoDB
61
97
  const store = createStore('my-app', config, {
62
98
  storage: remoteStorage({
63
99
  backend: 'mongodb',
@@ -66,7 +102,7 @@ const store = createStore('my-app', config, {
66
102
  }),
67
103
  });
68
104
 
69
- // 或使用 JSONL 存储
105
+ // Or using JSONL
70
106
  // const store = createStore('my-app', config, {
71
107
  // storage: remoteStorage({
72
108
  // backend: 'jsonl',
@@ -75,71 +111,127 @@ const store = createStore('my-app', config, {
75
111
  // });
76
112
  ```
77
113
 
78
- ### React 组件中使用
114
+ ### In a React Component
79
115
 
80
116
  ```typescript
81
117
  function App() {
82
- const { theme, setTheme, isLoading } = useStore(store);
118
+ const { theme, setTheme, characters, setCharacters, isLoading } = useStore(store);
83
119
 
84
120
  if (isLoading) return <div>Loading...</div>;
85
121
 
86
- return <input value={theme} onChange={e => setTheme(e.target.value)} />;
122
+ return (
123
+ <input value={theme} onChange={e => setTheme(e.target.value)} />
124
+ );
87
125
  }
88
126
  ```
89
127
 
90
- ### 自定义 BFF 服务器地址
128
+ ### Immediate Persistence (no debounce)
129
+
130
+ For critical data, use `immediate: true`:
91
131
 
92
132
  ```typescript
93
- // 如果 BFF 运行在其他地址
94
- const adapter = remoteStorage({ baseUrl: 'http://localhost:3847' });
95
- const adapter = remoteStorage({ entityId: 'user-123' }); // 多租户支持
133
+ const config = [
134
+ { key: 'autosave', defaultValue: '', immediate: true },
135
+ ] as const;
96
136
  ```
97
137
 
98
- ### Memory Storage (不持久化)
138
+ ### Waiting for the Server
99
139
 
100
- 适用于开发环境或不需要持久化的场景:
140
+ `createStore` is synchronous, but server startup is asynchronous. Optionally wait:
101
141
 
102
142
  ```typescript
103
- import { createStore, useStore, memoryStorage } from 'bff-store';
143
+ import { createStore, waitForServer } from 'bff-store';
104
144
 
105
- const store = createStore('my-app', config, {
106
- storage: memoryStorage(), // 不走 BFF,数据仅存在内存
107
- });
145
+ const store = createStore('my-app', config, { storage: adapter });
146
+ await waitForServer();
108
147
  ```
109
148
 
110
- ## Advanced: 手动启动 BFF Server
149
+ ### Multi-Tenant Switching
111
150
 
112
- 大多数情况不需要手动启动 BFF,它会自动启动。如需手动控制:
151
+ Switch tenants dynamically via `setEntityId`:
113
152
 
114
153
  ```typescript
115
- import { startServer } from 'bff-store/server';
154
+ const adapter = remoteStorage({ entityId: 'tenant-A' });
155
+ const store = createStore('tenant-A', config, { storage: adapter });
116
156
 
117
- await startServer({
118
- port: 3847,
157
+ // Switch tenant
158
+ adapter.setEntityId('tenant-B');
159
+ ```
160
+
161
+ ### Memory Storage (non-persistent)
162
+
163
+ For development or non-persistent use cases:
164
+
165
+ ```typescript
166
+ import { createStore, useStore, memoryStorage } from 'bff-store';
167
+
168
+ const store = createStore('my-app', config, {
169
+ storage: memoryStorage(), // data lives only in memory
119
170
  });
120
171
  ```
121
172
 
122
- ## Node.js 直接使用 Storage Adapters
173
+ ## Node.js Direct Storage Adapter Usage
123
174
 
124
- 如果不通过 BFF,可以直接在 Node.js 中使用存储适配器:
175
+ You can also use storage adapters directly in Node.js without the BFF:
125
176
 
126
177
  ```typescript
127
- import { createStore } from 'bff-store';
178
+ import { createNodeStore, getDefaultStore } from 'bff-store';
128
179
  import { jsonlStorage } from 'bff-store/jsonl';
129
180
  import { mongodbStorage } from 'bff-store/mongodb';
130
181
 
131
- // 使用 JSONL
132
- const adapter = jsonlStorage({ dir: './sessions' });
182
+ // Using JSONL
183
+ const store = createNodeStore('entity-123', [
184
+ { key: 'theme', defaultValue: 'dark' },
185
+ { key: 'count', defaultValue: 0 },
186
+ ], {
187
+ storage: jsonlStorage({ dir: './sessions' }),
188
+ });
133
189
 
134
- // 使用 MongoDB
135
- const adapter = await mongodbStorage({
136
- url: 'mongodb://localhost:27017',
137
- database: 'myapp',
190
+ // Wait for initial load (5s timeout)
191
+ await store.waitForLoad();
192
+
193
+ // Read/write via jotai getDefaultStore()
194
+ const jotai = getDefaultStore();
195
+ jotai.set(store.atoms.theme, 'light'); // auto-debounce persists
196
+ console.log(jotai.get(store.atoms.count));
197
+
198
+ // Or using MongoDB
199
+ const store2 = createNodeStore('entity-456', [
200
+ { key: 'data', defaultValue: null },
201
+ ], {
202
+ storage: await mongodbStorage({
203
+ url: 'mongodb://localhost:27017',
204
+ database: 'myapp',
205
+ }),
138
206
  });
207
+ ```
208
+
209
+ ### Environment Detection
139
210
 
140
- const store = createStore('entity-123', config, { storage: adapter });
211
+ ```typescript
212
+ import { isNode, isBrowser } from 'bff-store';
213
+
214
+ if (isNode()) { /* Node.js */ }
215
+ if (isBrowser()) { /* Browser */ }
141
216
  ```
142
217
 
218
+ ## Storage Backends
219
+
220
+ ### JSONL
221
+
222
+ File format: `{dir}/{entityId}/{encodeURIComponent(key)}.jsonl`
223
+
224
+ Each line is a JSON object with timestamp. `get` reads the last line for the latest value.
225
+
226
+ ### MongoDB
227
+
228
+ Collection name: `state_{entityId}`. Each key uses upsert — only the latest value is kept.
229
+
230
+ > For production, create a compound index on `key + entityId`:
231
+ > ```javascript
232
+ > db.state_<entityId>.createIndex({ key: 1, entityId: 1 }, { unique: true })
233
+ > ```
234
+
143
235
  ## API Endpoints (BFF Server)
144
236
 
145
237
  | Method | Endpoint | Body | Description |
@@ -162,9 +254,18 @@ Creates a store with multiple persisted atoms.
162
254
  - `options.storage`: Storage adapter (required)
163
255
  - `options.debounceMs`: Debounce delay in ms (default: 800)
164
256
 
257
+ ### `waitForServer()`
258
+
259
+ Returns a promise that resolves when the auto-started BFF server is ready. Only meaningful in Node.js with `remoteStorage`.
260
+
261
+ ```typescript
262
+ import { waitForServer } from 'bff-store';
263
+ await waitForServer();
264
+ ```
265
+
165
266
  ### `useStore(store)`
166
267
 
167
- React hook to use the store in components.
268
+ React hook to consume the store in components.
168
269
 
169
270
  Returns: `{ ...data, ...setters, isLoading }`
170
271
 
@@ -172,26 +273,39 @@ Setter names are derived by capitalizing the config key:
172
273
  - `theme` → `setTheme`
173
274
  - `userName` → `setUserName`
174
275
 
276
+ ### `createNodeStore(entityId, config, options)`
277
+
278
+ Node.js environment store with `waitForLoad()`.
279
+
280
+ ```typescript
281
+ const store = createNodeStore('entity-123', config, { storage: adapter });
282
+ await store.waitForLoad(); // 5s timeout
283
+ ```
284
+
175
285
  ### `remoteStorage(options?)`
176
286
 
177
287
  Creates a remote storage adapter for connecting to the BFF server.
178
288
 
179
289
  ```typescript
180
- // 基本用法 - BFF 使用默认存储后端
290
+ // Basic
181
291
  const adapter = remoteStorage();
182
292
 
183
- // 指定使用 MongoDB
293
+ // Use MongoDB
184
294
  const adapter = remoteStorage({
185
295
  backend: 'mongodb',
186
296
  mongoUrl: 'mongodb://user:pass@host:27017',
187
297
  mongoDb: 'myapp',
188
298
  });
189
299
 
190
- // 指定使用 JSONL
300
+ // Use JSONL
191
301
  const adapter = remoteStorage({
192
302
  backend: 'jsonl',
193
303
  jsonlDir: '/tmp/my-app-data',
194
304
  });
305
+
306
+ // Multi-tenant
307
+ const adapter = remoteStorage({ entityId: 'user-123' });
308
+ adapter.setEntityId('user-456'); // switch tenant
195
309
  ```
196
310
 
197
311
  - `options.baseUrl`: BFF server URL (default: `http://localhost:3847`)
@@ -199,24 +313,43 @@ const adapter = remoteStorage({
199
313
  - `options.backend`: Storage backend type `'mongodb'` or `'jsonl'`
200
314
  - `options.mongoUrl`: MongoDB connection URL (required if backend is mongodb)
201
315
  - `options.mongoDb`: MongoDB database name (default: `jotai_state_store`)
202
- - `options.jsonlDir`: JSONL storage directory (required if backend is jsonl)
316
+ - `options.jsonlDir`: JSONL storage directory (default: `./data`)
203
317
 
204
318
  ### `startServer(options)`
205
319
 
206
320
  Starts the BFF server (singleton pattern). Import from `bff-store/server`.
207
321
 
208
- - `options.port`: Server port (default: 3847)
322
+ ```typescript
323
+ import { startServer } from 'bff-store/server';
209
324
 
210
- Note: Storage backend is configured by the client via `remoteStorage()` options, not by the server.
325
+ await startServer({
326
+ port: 3847,
327
+ backend: 'jsonl',
328
+ jsonlDir: './data',
329
+ });
330
+ ```
211
331
 
212
332
  ## Package Exports
213
333
 
214
334
  | Export | Description | Environment |
215
335
  |--------|-------------|-------------|
216
- | `bff-store` | Main entry: createStore, useStore, memoryStorage, remoteStorage | Browser + Node.js |
336
+ | `bff-store` | Main: createStore, useStore, createNodeStore, waitForServer, isNode, isBrowser, memoryStorage, remoteStorage | Browser + Node.js |
217
337
  | `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 |
338
+ | `bff-store/mongodb` | MongoDB storage adapter (async factory) | Node.js only |
339
+ | `bff-store/server` | BFF server: startServer, Router, EntityIdCache | Node.js only |
340
+
341
+ ## Changelog
342
+
343
+ ### v0.1.1 (2026-07-05)
344
+
345
+ - `createStore` with `remote` storage now waits for server startup to complete
346
+ - `waitForServer()` added for explicit server readiness
347
+ - `remoteStorage.setEntityId()` now works correctly (live entityId reference)
348
+ - MongoDB `set` uses upsert mode — no more unbounded document accumulation
349
+ - JSONL key encoding changed to `encodeURIComponent` — prevents `a.b` / `a-b` collision
350
+ - `atom.onMount` unmount cleanup cancels pending debounced writes
351
+ - `waitForLoad()` now has a 5s timeout
352
+ - HTTP error responses now include server-returned error details
220
353
 
221
354
  ## License
222
355
 
package/dist/cli.js CHANGED
@@ -32025,12 +32025,11 @@ async function mongodbStorage(options2) {
32025
32025
  },
32026
32026
  async set(key, value) {
32027
32027
  const collection = getCollection(currentEntityId);
32028
- await collection.insertOne({
32029
- key,
32030
- value,
32031
- timestamp: Date.now(),
32032
- entityId: currentEntityId
32033
- });
32028
+ await collection.updateOne(
32029
+ { key, entityId: currentEntityId },
32030
+ { $set: { value, timestamp: Date.now() } },
32031
+ { upsert: true }
32032
+ );
32034
32033
  },
32035
32034
  async remove(key) {
32036
32035
  const collection = getCollection(currentEntityId);
@@ -32133,7 +32132,7 @@ function jsonlStorage(options2) {
32133
32132
  const baseDir = options2?.dir ?? "./sessions";
32134
32133
  let entityId = "default";
32135
32134
  function getFilePath(eId, key) {
32136
- const safeKey = key.replace(/[^a-zA-Z0-9_]/g, "_");
32135
+ const safeKey = encodeURIComponent(key);
32137
32136
  return path.join(baseDir, eId, `${safeKey}.jsonl`);
32138
32137
  }
32139
32138
  const storage = {
@@ -32281,7 +32280,7 @@ async function getCachedStorage(config, entityId) {
32281
32280
  }
32282
32281
  return entry.adapter.storage;
32283
32282
  }
32284
- if (storageCache.size >= MAX_CACHE_SIZE) {
32283
+ while (storageCache.size >= MAX_CACHE_SIZE && storageCache.size > 0) {
32285
32284
  let oldestKey = null;
32286
32285
  let oldestTime = Infinity;
32287
32286
  for (const [k, entry] of storageCache.entries()) {
@@ -32292,6 +32291,8 @@ async function getCachedStorage(config, entityId) {
32292
32291
  }
32293
32292
  if (oldestKey) {
32294
32293
  storageCache.delete(oldestKey);
32294
+ } else {
32295
+ break;
32295
32296
  }
32296
32297
  }
32297
32298
  let adapter;
@@ -32324,6 +32325,7 @@ function createStorageHandlers(options2) {
32324
32325
  try {
32325
32326
  body = await parseBody(req);
32326
32327
  } catch {
32328
+ console.warn("[bff-store] Failed to parse request body, using URL config only");
32327
32329
  }
32328
32330
  }
32329
32331
  const config = body ? {
@@ -32399,7 +32401,7 @@ function createStorageHandlers(options2) {
32399
32401
  res.writeHead(200);
32400
32402
  res.end(JSON.stringify({ success: true }));
32401
32403
  }
32402
- async function handleHealth(req, res) {
32404
+ async function handleHealth(_req, res) {
32403
32405
  res.setHeader("Content-Type", "application/json");
32404
32406
  res.writeHead(200);
32405
32407
  res.end(JSON.stringify({ status: "ok" }));
package/dist/index.d.mts CHANGED
@@ -79,6 +79,12 @@ declare function createStore(entityId: string, config: AtomConfigs, options?: {
79
79
  storage: StorageAdapter;
80
80
  debounceMs?: number;
81
81
  }): Store;
82
+ /**
83
+ * Wait for the auto-started server to be ready.
84
+ * Only meaningful when using remote storage in Node.js;
85
+ * returns a resolved promise in browser environments.
86
+ */
87
+ declare function waitForServer(): Promise<void> | undefined;
82
88
 
83
89
  /**
84
90
  * React Hook to use a store
@@ -229,4 +235,45 @@ interface RemoteStorageOptions {
229
235
  */
230
236
  declare function remoteStorage(options?: RemoteStorageOptions): StorageAdapter;
231
237
 
232
- export { type AsyncStorageFactory, type AtomConfig, type AtomConfigs, type AtomType, HttpTransport, type MemoryStorageOptions, RestStorageProtocol, type Storage, type StorageAdapter, type StorageFactory, type StorageHttpProtocol, type StorageOptions, type Store, type TransportAdapter, type UseStoreReturn, createMemoryStorage, createPersistedAtom, remoteStorage as createRemoteStorage, createStorageFromTransport, createStorageWithProtocol, createStore, memoryStorage, remoteStorage, useStore };
238
+ /**
239
+ * Environment detection utilities
240
+ */
241
+ declare function isNode(): boolean;
242
+ declare function isBrowser(): boolean;
243
+
244
+ /**
245
+ * Create a store for Node.js environments.
246
+ *
247
+ * @example
248
+ * ```typescript
249
+ * import { jsonlStorage } from 'bff-store/jsonl';
250
+ * import { createNodeStore } from 'bff-store';
251
+ *
252
+ * const store = createNodeStore('entity-123', [
253
+ * { key: 'theme', defaultValue: 'dark' },
254
+ * ], {
255
+ * storage: jsonlStorage({ dir: './sessions' }),
256
+ * });
257
+ *
258
+ * await store.waitForLoad();
259
+ *
260
+ * const jotai = getDefaultStore();
261
+ * jotai.set(store.atoms.theme, 'light');
262
+ * ```
263
+ */
264
+ declare function createNodeStore(entityId: string, config: AtomConfigs, options: {
265
+ storage: StorageAdapter;
266
+ debounceMs?: number;
267
+ }): {
268
+ /** Atoms to use with getDefaultStore().get/set */
269
+ atoms: StoreAtoms;
270
+ /** Loading state atoms */
271
+ loadingAtoms: StoreLoadingAtoms;
272
+ /**
273
+ * Wait for all atoms to finish loading from storage.
274
+ * Call this after creating the store to ensure data is ready.
275
+ */
276
+ waitForLoad(): Promise<void>;
277
+ };
278
+
279
+ export { type AsyncStorageFactory, type AtomConfig, type AtomConfigs, type AtomType, HttpTransport, type MemoryStorageOptions, RestStorageProtocol, type Storage, type StorageAdapter, type StorageFactory, type StorageHttpProtocol, type StorageOptions, type Store, type TransportAdapter, type UseStoreReturn, createMemoryStorage, createNodeStore, createPersistedAtom, remoteStorage as createRemoteStorage, createStorageFromTransport, createStorageWithProtocol, createStore, isBrowser, isNode, memoryStorage, remoteStorage, useStore, waitForServer };
package/dist/index.d.ts CHANGED
@@ -79,6 +79,12 @@ declare function createStore(entityId: string, config: AtomConfigs, options?: {
79
79
  storage: StorageAdapter;
80
80
  debounceMs?: number;
81
81
  }): Store;
82
+ /**
83
+ * Wait for the auto-started server to be ready.
84
+ * Only meaningful when using remote storage in Node.js;
85
+ * returns a resolved promise in browser environments.
86
+ */
87
+ declare function waitForServer(): Promise<void> | undefined;
82
88
 
83
89
  /**
84
90
  * React Hook to use a store
@@ -229,4 +235,45 @@ interface RemoteStorageOptions {
229
235
  */
230
236
  declare function remoteStorage(options?: RemoteStorageOptions): StorageAdapter;
231
237
 
232
- export { type AsyncStorageFactory, type AtomConfig, type AtomConfigs, type AtomType, HttpTransport, type MemoryStorageOptions, RestStorageProtocol, type Storage, type StorageAdapter, type StorageFactory, type StorageHttpProtocol, type StorageOptions, type Store, type TransportAdapter, type UseStoreReturn, createMemoryStorage, createPersistedAtom, remoteStorage as createRemoteStorage, createStorageFromTransport, createStorageWithProtocol, createStore, memoryStorage, remoteStorage, useStore };
238
+ /**
239
+ * Environment detection utilities
240
+ */
241
+ declare function isNode(): boolean;
242
+ declare function isBrowser(): boolean;
243
+
244
+ /**
245
+ * Create a store for Node.js environments.
246
+ *
247
+ * @example
248
+ * ```typescript
249
+ * import { jsonlStorage } from 'bff-store/jsonl';
250
+ * import { createNodeStore } from 'bff-store';
251
+ *
252
+ * const store = createNodeStore('entity-123', [
253
+ * { key: 'theme', defaultValue: 'dark' },
254
+ * ], {
255
+ * storage: jsonlStorage({ dir: './sessions' }),
256
+ * });
257
+ *
258
+ * await store.waitForLoad();
259
+ *
260
+ * const jotai = getDefaultStore();
261
+ * jotai.set(store.atoms.theme, 'light');
262
+ * ```
263
+ */
264
+ declare function createNodeStore(entityId: string, config: AtomConfigs, options: {
265
+ storage: StorageAdapter;
266
+ debounceMs?: number;
267
+ }): {
268
+ /** Atoms to use with getDefaultStore().get/set */
269
+ atoms: StoreAtoms;
270
+ /** Loading state atoms */
271
+ loadingAtoms: StoreLoadingAtoms;
272
+ /**
273
+ * Wait for all atoms to finish loading from storage.
274
+ * Call this after creating the store to ensure data is ready.
275
+ */
276
+ waitForLoad(): Promise<void>;
277
+ };
278
+
279
+ export { type AsyncStorageFactory, type AtomConfig, type AtomConfigs, type AtomType, HttpTransport, type MemoryStorageOptions, RestStorageProtocol, type Storage, type StorageAdapter, type StorageFactory, type StorageHttpProtocol, type StorageOptions, type Store, type TransportAdapter, type UseStoreReturn, createMemoryStorage, createNodeStore, createPersistedAtom, remoteStorage as createRemoteStorage, createStorageFromTransport, createStorageWithProtocol, createStore, isBrowser, isNode, memoryStorage, remoteStorage, useStore, waitForServer };