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
package/dist/index.mjs CHANGED
@@ -30,8 +30,9 @@ var DebouncerMap = class {
30
30
  }
31
31
  getDebouncer(key, ms) {
32
32
  let debouncer = this.debouncers.get(key);
33
- if (!debouncer) {
34
- debouncer = createDebouncer(ms ?? this.defaultMs);
33
+ const effectiveMs = ms ?? this.defaultMs;
34
+ if (!debouncer || debouncer.ms !== effectiveMs) {
35
+ debouncer = createDebouncer(effectiveMs);
35
36
  this.debouncers.set(key, debouncer);
36
37
  }
37
38
  return debouncer;
@@ -73,12 +74,18 @@ function createPersistedAtom(config, entityId, storage, options) {
73
74
  if (value !== null && value !== void 0) {
74
75
  setValue(value);
75
76
  }
76
- }).catch(console.error).finally(() => {
77
+ }).catch((err) => {
78
+ console.error(`[bff-store] Failed to load atom "${config.key}":`, err);
79
+ }).finally(() => {
77
80
  setTimeout(() => {
78
81
  const store = getDefaultStore();
79
82
  store.set(loadingAtom, false);
80
83
  }, 0);
81
84
  });
85
+ return () => {
86
+ const debounceKey = `${entityId}:${config.key}`;
87
+ debouncerMap.cancel(debounceKey);
88
+ };
82
89
  };
83
90
  const writeAtom = atom(
84
91
  (get) => get(baseAtom),
@@ -104,12 +111,13 @@ function createPersistedAtom(config, entityId, storage, options) {
104
111
  }
105
112
 
106
113
  // src/createStore.ts
114
+ var serverInitPromise = null;
107
115
  function createStore(entityId, config, options) {
108
116
  const adapter = options?.storage;
109
117
  const debounceMs = options?.debounceMs ?? 800;
110
118
  if (adapter?.name === "remote" && typeof window === "undefined" && typeof process !== "undefined") {
111
- import("./server-V7WCW4ZB.mjs").then(({ startServer }) => {
112
- startServer().catch((err) => {
119
+ import("./server-2WMLXQ23.mjs").then(({ startServer }) => {
120
+ serverInitPromise = startServer().then(() => void 0).catch((err) => {
113
121
  console.error("[bff-store] Failed to auto-start server:", err);
114
122
  });
115
123
  });
@@ -138,6 +146,9 @@ function createStore(entityId, config, options) {
138
146
  loadingAtoms
139
147
  };
140
148
  }
149
+ function waitForServer() {
150
+ return serverInitPromise;
151
+ }
141
152
 
142
153
  // src/useStore.ts
143
154
  import { useAtom } from "jotai";
@@ -224,7 +235,13 @@ var HttpTransport = class {
224
235
  async get(url) {
225
236
  const res = await fetch(url);
226
237
  if (!res.ok) {
227
- throw new Error(`GET ${url} failed: ${res.statusText}`);
238
+ let detail = res.statusText;
239
+ try {
240
+ const body = await res.clone().json();
241
+ detail = body?.error ?? body?.message ?? detail;
242
+ } catch {
243
+ }
244
+ throw new Error(`GET ${url} failed: ${detail}`);
228
245
  }
229
246
  return res.json();
230
247
  }
@@ -235,14 +252,26 @@ var HttpTransport = class {
235
252
  body: JSON.stringify(body)
236
253
  });
237
254
  if (!res.ok) {
238
- throw new Error(`POST ${url} failed: ${res.statusText}`);
255
+ let detail = res.statusText;
256
+ try {
257
+ const errBody = await res.clone().json();
258
+ detail = errBody?.error ?? errBody?.message ?? detail;
259
+ } catch {
260
+ }
261
+ throw new Error(`POST ${url} failed: ${detail}`);
239
262
  }
240
263
  return res.json();
241
264
  }
242
265
  async delete(url) {
243
266
  const res = await fetch(url, { method: "DELETE" });
244
267
  if (!res.ok) {
245
- throw new Error(`DELETE ${url} failed: ${res.statusText}`);
268
+ let detail = res.statusText;
269
+ try {
270
+ const errBody = await res.clone().json();
271
+ detail = errBody?.error ?? errBody?.message ?? detail;
272
+ } catch {
273
+ }
274
+ throw new Error(`DELETE ${url} failed: ${detail}`);
246
275
  }
247
276
  }
248
277
  };
@@ -404,7 +433,7 @@ function remoteStorage(options = {}) {
404
433
  mongoDb: options.mongoDb,
405
434
  jsonlDir: options.jsonlDir
406
435
  };
407
- const protocol = options.protocol ?? new RestStorageProtocol(baseUrl, entityId.current, backendConfig);
436
+ const protocol = options.protocol ?? new RestStorageProtocol(baseUrl, entityId, backendConfig);
408
437
  const storage = createStorageWithProtocol(transport, protocol);
409
438
  const adapter = {
410
439
  storage,
@@ -415,16 +444,65 @@ function remoteStorage(options = {}) {
415
444
  };
416
445
  return adapter;
417
446
  }
447
+
448
+ // src/environment.ts
449
+ function isNode() {
450
+ return typeof window === "undefined" && typeof process !== "undefined";
451
+ }
452
+ function isBrowser() {
453
+ return typeof window !== "undefined";
454
+ }
455
+
456
+ // src/nodeStore.ts
457
+ import { getDefaultStore as getDefaultStore3 } from "jotai";
458
+ function createNodeStore(entityId, config, options) {
459
+ const store = createStore(entityId, config, options);
460
+ const jotaiStore = getDefaultStore3();
461
+ async function waitForLoad(timeoutMs = 5e3) {
462
+ for (const atom2 of Object.values(store.atoms)) {
463
+ jotaiStore.sub(atom2, () => {
464
+ });
465
+ }
466
+ return new Promise((resolve, reject) => {
467
+ const loadingAtoms = Object.values(store.loadingAtoms);
468
+ const stillLoading = () => loadingAtoms.some((atom2) => jotaiStore.get(atom2));
469
+ if (!stillLoading()) {
470
+ resolve();
471
+ return;
472
+ }
473
+ const timeout = setTimeout(() => {
474
+ clearInterval(interval);
475
+ reject(new Error(`waitForLoad timed out after ${timeoutMs}ms`));
476
+ }, timeoutMs);
477
+ const interval = setInterval(() => {
478
+ if (!stillLoading()) {
479
+ clearInterval(interval);
480
+ clearTimeout(timeout);
481
+ resolve();
482
+ }
483
+ }, 10);
484
+ });
485
+ }
486
+ return {
487
+ atoms: store.atoms,
488
+ loadingAtoms: store.loadingAtoms,
489
+ waitForLoad
490
+ };
491
+ }
418
492
  export {
419
493
  HttpTransport,
420
494
  RestStorageProtocol,
421
495
  createMemoryStorage,
496
+ createNodeStore,
422
497
  createPersistedAtom,
423
498
  remoteStorage as createRemoteStorage,
424
499
  createStorageFromTransport,
425
500
  createStorageWithProtocol,
426
501
  createStore,
502
+ isBrowser,
503
+ isNode,
427
504
  memoryStorage,
428
505
  remoteStorage,
429
- useStore
506
+ useStore,
507
+ waitForServer
430
508
  };
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bff-store",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "A jotai-based state management library with pluggable storage adapters",
5
5
  "main": "index.js",
6
6
  "module": "index.mjs",
@@ -8,8 +8,7 @@
8
8
  "exports": {
9
9
  ".": {
10
10
  "types": "./index.d.ts",
11
- "import": "./index.mjs",
12
- "require": "./index.js"
11
+ "import": "./index.mjs"
13
12
  },
14
13
  "./jsonl": {
15
14
  "types": "./storage/jsonl-entry.d.ts",
@@ -50,13 +49,11 @@
50
49
  "@types/react": "^19.2.16",
51
50
  "jotai": "^2.6.0",
52
51
  "jsdom": "^29.1.1",
52
+ "mongodb": "^6.21.0",
53
53
  "react": "^18.2.0",
54
54
  "react-dom": "^18.2.0",
55
55
  "tsup": "^8.0.0",
56
56
  "typescript": "^5.0.0",
57
57
  "vitest": "^1.0.0"
58
- },
59
- "dependencies": {
60
- "mongodb": "^6.21.0"
61
58
  }
62
59
  }
@@ -62,7 +62,7 @@ declare function createStorageHandlers(options: StorageHandlersOptions): {
62
62
  handleDelete: (req: IncomingMessage, res: ServerResponse, params?: Record<string, string>) => Promise<void>;
63
63
  handleBatchGet: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
64
64
  handleBatchSet: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
65
- handleHealth: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
65
+ handleHealth: (_req: IncomingMessage, res: ServerResponse) => Promise<void>;
66
66
  };
67
67
 
68
68
  /**
@@ -62,7 +62,7 @@ declare function createStorageHandlers(options: StorageHandlersOptions): {
62
62
  handleDelete: (req: IncomingMessage, res: ServerResponse, params?: Record<string, string>) => Promise<void>;
63
63
  handleBatchGet: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
64
64
  handleBatchSet: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
65
- handleHealth: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
65
+ handleHealth: (_req: IncomingMessage, res: ServerResponse) => Promise<void>;
66
66
  };
67
67
 
68
68
  /**