@playfast/reform-query-browser 1.0.2 → 1.2.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/README.md CHANGED
@@ -29,8 +29,8 @@ import { BrowserQueryDefaults, localStorageQueryStore } from '@playfast/reform-q
29
29
  // In your app layer, alongside Engine:
30
30
  const base = Layer.mergeAll(
31
31
  Engine,
32
- BrowserQueryDefaults, // refetch on focus + reconnect
33
- localStorageQueryStore, // enables `persist`
32
+ BrowserQueryDefaults, // refetch on focus + reconnect
33
+ localStorageQueryStore, // enables `persist`
34
34
  /* your state + calc layers */
35
35
  )
36
36
 
@@ -39,7 +39,7 @@ AsyncCalc.live(Todos, { query, persist: { key: 'todos' } })
39
39
 
40
40
  // Imperative control anywhere you can run an Effect:
41
41
  runtime.runFork(AsyncCalc.invalidate(Todos)) // mark stale (refetches if active)
42
- runtime.runFork(AsyncCalc.refetch(Todos)) // force a run now
42
+ runtime.runFork(AsyncCalc.refetch(Todos)) // force a run now
43
43
  ```
44
44
 
45
45
  All of `QueryEvents`/`QueryStore` are optional: a calc that uses `persist` with no
@@ -0,0 +1,4 @@
1
+ export { QueryEventsBrowser } from './queryEvents.browser.js';
2
+ export { localStorageQueryStore } from './localStorage.store.js';
3
+ export { BrowserQueryDefaults, RefetchOnFocus, RefetchOnReconnect } from './refetchManagers.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAC1D,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAA;AAC7D,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { QueryEventsBrowser } from './queryEvents.browser.js';
2
+ export { localStorageQueryStore } from './localStorage.store.js';
3
+ export { BrowserQueryDefaults, RefetchOnFocus, RefetchOnReconnect } from './refetchManagers.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAC1D,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAA;AAC7D,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA"}
@@ -0,0 +1,5 @@
1
+ import { QueryStore } from '@playfast/reform/internal';
2
+ import { Layer } from 'effect';
3
+ export declare const makeLocalStorageQueryStore: (storage: Storage) => Layer.Layer<QueryStore>;
4
+ export declare const localStorageQueryStore: Layer.Layer<QueryStore>;
5
+ //# sourceMappingURL=localStorage.store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"localStorage.store.d.ts","sourceRoot":"","sources":["../src/localStorage.store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsC,MAAM,2BAA2B,CAAA;AAC1F,OAAO,EAAU,KAAK,EAAkB,MAAM,QAAQ,CAAA;AA2BtD,eAAO,MAAM,0BAA0B,GAAI,SAAS,OAAO,KAAG,KAAK,CAAC,KAAK,CAAC,UAAU,CACvC,CAAA;AAS7C,eAAO,MAAM,sBAAsB,EAAE,KAAK,CAAC,KAAK,CAAC,UAAU,CAAmC,CAAA"}
@@ -0,0 +1,30 @@
1
+ import { QueryStore, noopQueryStore } from '@playfast/reform/internal';
2
+ import { Effect, Layer, Option, Schema } from 'effect';
3
+ const PREFIX = 'reform-query:';
4
+ const JsonValue = Schema.parseJson(Schema.Unknown);
5
+ // Corrupt/version-skewed entry → None (cache miss); fetch fresh.
6
+ const decodeJson = Schema.decodeUnknownOption(JsonValue);
7
+ const encodeJson = Schema.encodeSync(JsonValue);
8
+ const makeApi = (storage) => {
9
+ return {
10
+ get: (key) => Effect.sync(() => {
11
+ const raw = storage.getItem(PREFIX + key);
12
+ return raw === null ? Option.none() : decodeJson(raw);
13
+ }),
14
+ set: (key, payload) => Effect.sync(() => {
15
+ storage.setItem(PREFIX + key, encodeJson(payload));
16
+ }),
17
+ remove: (key) => Effect.sync(() => {
18
+ storage.removeItem(PREFIX + key);
19
+ }),
20
+ };
21
+ };
22
+ export const makeLocalStorageQueryStore = (storage) => Layer.succeed(QueryStore, makeApi(storage));
23
+ const makeDefaultStore = () => {
24
+ if (typeof window === 'undefined' || typeof window.localStorage === 'undefined') {
25
+ return Layer.succeed(QueryStore, noopQueryStore);
26
+ }
27
+ return makeLocalStorageQueryStore(window.localStorage);
28
+ };
29
+ export const localStorageQueryStore = Layer.suspend(makeDefaultStore);
30
+ //# sourceMappingURL=localStorage.store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"localStorage.store.js","sourceRoot":"","sources":["../src/localStorage.store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,cAAc,EAAE,MAAM,2BAA2B,CAAA;AAC1F,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAEtD,MAAM,MAAM,GAAG,eAAe,CAAA;AAE9B,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;AAClD,iEAAiE;AACjE,MAAM,UAAU,GAAG,MAAM,CAAC,mBAAmB,CAAC,SAAS,CAAC,CAAA;AACxD,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,CAAA;AAE/C,MAAM,OAAO,GAAG,CAAC,OAAgB,EAAiB,EAAE;IAClD,OAAO;QACL,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE,CACX,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE;YACf,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,GAAG,CAAC,CAAA;YACzC,OAAO,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAA;QACvD,CAAC,CAAC;QACJ,GAAG,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,CACpB,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE;YACf,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,GAAG,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC,CAAA;QACpD,CAAC,CAAC;QACJ,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CACd,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE;YACf,OAAO,CAAC,UAAU,CAAC,MAAM,GAAG,GAAG,CAAC,CAAA;QAClC,CAAC,CAAC;KACL,CAAA;AACH,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,OAAgB,EAA2B,EAAE,CACtF,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,CAAA;AAE7C,MAAM,gBAAgB,GAAG,GAA4B,EAAE;IACrD,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,OAAO,MAAM,CAAC,YAAY,KAAK,WAAW,EAAE,CAAC;QAChF,OAAO,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,cAAc,CAAC,CAAA;IAClD,CAAC;IACD,OAAO,0BAA0B,CAAC,MAAM,CAAC,YAAY,CAAC,CAAA;AACxD,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,sBAAsB,GAA4B,KAAK,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAA"}
@@ -0,0 +1,4 @@
1
+ import { QueryEvents } from '@playfast/reform/internal';
2
+ import { Layer } from 'effect';
3
+ export declare const QueryEventsBrowser: Layer.Layer<QueryEvents>;
4
+ //# sourceMappingURL=queryEvents.browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queryEvents.browser.d.ts","sourceRoot":"","sources":["../src/queryEvents.browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAwC,MAAM,2BAA2B,CAAA;AAC7F,OAAO,EAAU,KAAK,EAAE,MAAM,QAAQ,CAAA;AAEtC,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC,KAAK,CAAC,WAAW,CA6CvD,CAAA"}
@@ -0,0 +1,39 @@
1
+ import { QueryEvents, noopQueryEvents } from '@playfast/reform/internal';
2
+ import { Effect, Layer } from 'effect';
3
+ export const QueryEventsBrowser = Layer.scoped(QueryEvents, Effect.gen(function* () {
4
+ if (typeof window === 'undefined') {
5
+ return noopQueryEvents;
6
+ }
7
+ const focusListeners = new Set();
8
+ const onlineListeners = new Set();
9
+ const fire = (listeners) => {
10
+ listeners.forEach((listener) => listener());
11
+ };
12
+ // Focus + tab-visible both fire; ignore hide visibilitychange.
13
+ const onFocus = () => {
14
+ if (document.visibilityState === 'visible') {
15
+ fire(focusListeners);
16
+ }
17
+ };
18
+ const onOnline = () => fire(onlineListeners);
19
+ document.addEventListener('visibilitychange', onFocus);
20
+ window.addEventListener('focus', onFocus);
21
+ window.addEventListener('online', onOnline);
22
+ yield* Effect.addFinalizer(() => Effect.sync(() => {
23
+ document.removeEventListener('visibilitychange', onFocus);
24
+ window.removeEventListener('focus', onFocus);
25
+ window.removeEventListener('online', onOnline);
26
+ }));
27
+ const subscribeTo = (listeners) => (listener) => {
28
+ listeners.add(listener);
29
+ return () => {
30
+ listeners.delete(listener);
31
+ };
32
+ };
33
+ const api = {
34
+ subscribeFocus: subscribeTo(focusListeners),
35
+ subscribeOnline: subscribeTo(onlineListeners),
36
+ };
37
+ return api;
38
+ }));
39
+ //# sourceMappingURL=queryEvents.browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queryEvents.browser.js","sourceRoot":"","sources":["../src/queryEvents.browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAuB,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAC7F,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AAEtC,MAAM,CAAC,MAAM,kBAAkB,GAA6B,KAAK,CAAC,MAAM,CACtE,WAAW,EACX,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;QAClC,OAAO,eAAe,CAAA;IACxB,CAAC;IAED,MAAM,cAAc,GAAG,IAAI,GAAG,EAAc,CAAA;IAC5C,MAAM,eAAe,GAAG,IAAI,GAAG,EAAc,CAAA;IAC7C,MAAM,IAAI,GAAG,CAAC,SAAkC,EAAE,EAAE;QAClD,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAA;IAC7C,CAAC,CAAA;IACD,+DAA+D;IAC/D,MAAM,OAAO,GAAG,GAAG,EAAE;QACnB,IAAI,QAAQ,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;YAC3C,IAAI,CAAC,cAAc,CAAC,CAAA;QACtB,CAAC;IACH,CAAC,CAAA;IACD,MAAM,QAAQ,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,CAAA;IAE5C,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,OAAO,CAAC,CAAA;IACtD,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;IACzC,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;IAC3C,KAAK,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,CAC9B,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE;QACf,QAAQ,CAAC,mBAAmB,CAAC,kBAAkB,EAAE,OAAO,CAAC,CAAA;QACzD,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;QAC5C,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;IAChD,CAAC,CAAC,CACH,CAAA;IAED,MAAM,WAAW,GACf,CAAC,SAA0B,EAAE,EAAE,CAC/B,CAAC,QAAoB,EAAgB,EAAE;QACrC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;QACvB,OAAO,GAAG,EAAE;YACV,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;QAC5B,CAAC,CAAA;IACH,CAAC,CAAA;IACH,MAAM,GAAG,GAAmB;QAC1B,cAAc,EAAE,WAAW,CAAC,cAAc,CAAC;QAC3C,eAAe,EAAE,WAAW,CAAC,eAAe,CAAC;KAC9C,CAAA;IACD,OAAO,GAAG,CAAA;AACZ,CAAC,CAAC,CACH,CAAA"}
@@ -0,0 +1,6 @@
1
+ import { QueryEvents, Queries } from '@playfast/reform/internal';
2
+ import { Layer } from 'effect';
3
+ export declare const RefetchOnFocus: Layer.Layer<never, never, QueryEvents | Queries>;
4
+ export declare const RefetchOnReconnect: Layer.Layer<never, never, QueryEvents | Queries>;
5
+ export declare const BrowserQueryDefaults: Layer.Layer<never, never, Queries>;
6
+ //# sourceMappingURL=refetchManagers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refetchManagers.d.ts","sourceRoot":"","sources":["../src/refetchManagers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAuB,OAAO,EAAE,MAAM,2BAA2B,CAAA;AACrF,OAAO,EAAU,KAAK,EAAE,MAAM,QAAQ,CAAA;AAkBtC,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAE3E,CAAA;AAED,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAE/E,CAAA;AAED,eAAO,MAAM,oBAAoB,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAG3B,CAAA"}
@@ -0,0 +1,16 @@
1
+ import { QueryEvents, Queries } from '@playfast/reform/internal';
2
+ import { Effect, Layer } from 'effect';
3
+ import { QueryEventsBrowser } from './queryEvents.browser.js';
4
+ const onSignal = (pick) => Layer.scopedDiscard(Effect.gen(function* () {
5
+ const events = yield* QueryEvents;
6
+ const queries = yield* Queries;
7
+ const invalidateAll = () => {
8
+ queries.byName.forEach((handle) => handle.invalidate());
9
+ };
10
+ const unsubscribe = pick(events)(invalidateAll);
11
+ yield* Effect.addFinalizer(() => Effect.sync(unsubscribe));
12
+ }));
13
+ export const RefetchOnFocus = onSignal((events) => events.subscribeFocus);
14
+ export const RefetchOnReconnect = onSignal((events) => events.subscribeOnline);
15
+ export const BrowserQueryDefaults = Layer.mergeAll(RefetchOnFocus, RefetchOnReconnect).pipe(Layer.provide(QueryEventsBrowser));
16
+ //# sourceMappingURL=refetchManagers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refetchManagers.js","sourceRoot":"","sources":["../src/refetchManagers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAuB,OAAO,EAAE,MAAM,2BAA2B,CAAA;AACrF,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AACtC,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAE1D,MAAM,QAAQ,GAAG,CACf,IAAsE,EACpB,EAAE,CACpD,KAAK,CAAC,aAAa,CACjB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,WAAW,CAAA;IACjC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,OAAO,CAAA;IAC9B,MAAM,aAAa,GAAG,GAAG,EAAE;QACzB,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,CAAA;IACzD,CAAC,CAAA;IACD,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,aAAa,CAAC,CAAA;IAC/C,KAAK,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAA;AAC5D,CAAC,CAAC,CACH,CAAA;AAEH,MAAM,CAAC,MAAM,cAAc,GAAqD,QAAQ,CACtF,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,cAAc,CAClC,CAAA;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAqD,QAAQ,CAC1F,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,eAAe,CACnC,CAAA;AAED,MAAM,CAAC,MAAM,oBAAoB,GAAuC,KAAK,CAAC,QAAQ,CACpF,cAAc,EACd,kBAAkB,CACnB,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC,CAAA"}
package/package.json CHANGED
@@ -1,51 +1,63 @@
1
1
  {
2
2
  "name": "@playfast/reform-query-browser",
3
- "playbook": "./playbook",
4
- "version": "1.0.2",
5
- "type": "module",
3
+ "version": "1.2.0",
6
4
  "description": "Browser host layers for Reform's AsyncCalc — window focus/online auto-invalidation and localStorage persistence. Keeps reform core DOM-free: implements the optional QueryEvents and QueryStore seams.",
7
5
  "keywords": [
8
- "reform",
9
6
  "effect",
10
- "react-query",
7
+ "layer",
11
8
  "persistence",
9
+ "react-query",
12
10
  "refetch-on-focus",
13
- "layer"
11
+ "reform"
14
12
  ],
13
+ "bugs": {
14
+ "url": "https://github.com/playfast/reform/issues"
15
+ },
15
16
  "license": "MIT",
16
17
  "repository": {
17
18
  "type": "git",
18
19
  "url": "https://github.com/playfast/reform.git",
19
20
  "directory": "packages/reform-query-browser"
20
21
  },
21
- "bugs": {
22
- "url": "https://github.com/playfast/reform/issues"
23
- },
24
- "sideEffects": false,
25
- "exports": {
26
- "./package.json": "./package.json",
27
- ".": "./src/index.ts",
28
- "./*": "./src/*.ts"
29
- },
30
22
  "files": [
23
+ "dist",
31
24
  "src",
32
25
  "README.md"
33
26
  ],
27
+ "type": "module",
28
+ "sideEffects": false,
29
+ "main": "./dist/index.js",
30
+ "types": "./dist/index.d.ts",
31
+ "exports": {
32
+ "./package.json": "./package.json",
33
+ ".": {
34
+ "playfast-src": "./src/index.ts",
35
+ "types": "./dist/index.d.ts",
36
+ "default": "./dist/index.js"
37
+ },
38
+ "./*": {
39
+ "playfast-src": "./src/*.ts",
40
+ "types": "./dist/*.d.ts",
41
+ "default": "./dist/*.js"
42
+ }
43
+ },
44
+ "publishConfig": {
45
+ "access": "public"
46
+ },
34
47
  "scripts": {
35
48
  "clean": "rm -rf dist .tsbuildinfo",
36
49
  "check": "tsc --noEmit",
37
- "build": "tsc -p tsconfig.build.json",
50
+ "build": "rm -rf dist .tsbuildinfo && tsc -p tsconfig.build.json && bun ../../scripts/fix-esm-extensions.ts dist",
38
51
  "test": "vitest run",
39
52
  "test:watch": "vitest",
40
53
  "coverage": "vitest run --coverage",
41
54
  "lint": "oxlint src",
42
- "lint:fix": "oxlint --fix src"
55
+ "lint:fix": "oxlint --fix src",
56
+ "prepack": "bun run build"
43
57
  },
44
58
  "peerDependencies": {
45
- "effect": "*",
46
- "@playfast/reform": "*"
59
+ "@playfast/reform": "*",
60
+ "effect": "*"
47
61
  },
48
- "publishConfig": {
49
- "access": "public"
50
- }
62
+ "playbook": "./playbook"
51
63
  }
@@ -1,4 +1,4 @@
1
- import { QueryStore, type QueryStoreApi, noopQueryStore } from '@playfast/reform'
1
+ import { QueryStore, type QueryStoreApi, noopQueryStore } from '@playfast/reform/internal'
2
2
  import { Effect, Layer, Option, Schema } from 'effect'
3
3
 
4
4
  const PREFIX = 'reform-query:'
@@ -8,25 +8,32 @@ const JsonValue = Schema.parseJson(Schema.Unknown)
8
8
  const decodeJson = Schema.decodeUnknownOption(JsonValue)
9
9
  const encodeJson = Schema.encodeSync(JsonValue)
10
10
 
11
- const makeApi = (): QueryStoreApi => {
12
- if (typeof window === 'undefined' || typeof localStorage === 'undefined') {
13
- return noopQueryStore
14
- }
11
+ const makeApi = (storage: Storage): QueryStoreApi => {
15
12
  return {
16
13
  get: (key) =>
17
14
  Effect.sync(() => {
18
- const raw = localStorage.getItem(PREFIX + key)
15
+ const raw = storage.getItem(PREFIX + key)
19
16
  return raw === null ? Option.none() : decodeJson(raw)
20
17
  }),
21
18
  set: (key, payload) =>
22
19
  Effect.sync(() => {
23
- localStorage.setItem(PREFIX + key, encodeJson(payload))
20
+ storage.setItem(PREFIX + key, encodeJson(payload))
24
21
  }),
25
22
  remove: (key) =>
26
23
  Effect.sync(() => {
27
- localStorage.removeItem(PREFIX + key)
24
+ storage.removeItem(PREFIX + key)
28
25
  }),
29
26
  }
30
27
  }
31
28
 
32
- export const localStorageQueryStore: Layer.Layer<QueryStore> = Layer.succeed(QueryStore, makeApi())
29
+ export const makeLocalStorageQueryStore = (storage: Storage): Layer.Layer<QueryStore> =>
30
+ Layer.succeed(QueryStore, makeApi(storage))
31
+
32
+ const makeDefaultStore = (): Layer.Layer<QueryStore> => {
33
+ if (typeof window === 'undefined' || typeof window.localStorage === 'undefined') {
34
+ return Layer.succeed(QueryStore, noopQueryStore)
35
+ }
36
+ return makeLocalStorageQueryStore(window.localStorage)
37
+ }
38
+
39
+ export const localStorageQueryStore: Layer.Layer<QueryStore> = Layer.suspend(makeDefaultStore)
@@ -1,4 +1,4 @@
1
- import { QueryEvents, type QueryEventsApi, noopQueryEvents } from '@playfast/reform'
1
+ import { QueryEvents, type QueryEventsApi, noopQueryEvents } from '@playfast/reform/internal'
2
2
  import { Effect, Layer } from 'effect'
3
3
 
4
4
  export const QueryEventsBrowser: Layer.Layer<QueryEvents> = Layer.scoped(
@@ -1,4 +1,4 @@
1
- import { QueryEvents, type QueryEventsApi, Queries } from '@playfast/reform'
1
+ import { QueryEvents, type QueryEventsApi, Queries } from '@playfast/reform/internal'
2
2
  import { Effect, Layer } from 'effect'
3
3
  import { QueryEventsBrowser } from './queryEvents.browser'
4
4
 
@@ -4,7 +4,8 @@
4
4
  import { expect, it } from '@effect/vitest'
5
5
  import { AsyncCalc, Engine, State, StateGroup } from '@playfast/reform'
6
6
  import { Duration, Effect, Layer, Schema as S } from 'effect'
7
- import { localStorageQueryStore } from './localStorage.store'
7
+ import { Window } from 'happy-dom'
8
+ import { makeLocalStorageQueryStore } from './localStorage.store'
8
9
  import { QueryEventsBrowser } from './queryEvents.browser'
9
10
  import { RefetchOnFocus } from './refetchManagers'
10
11
 
@@ -49,7 +50,9 @@ it.live('window focus invalidates a live query, so an active reader refetches',
49
50
  })
50
51
 
51
52
  it.live('persist hydrates from localStorage (stale) then writes the settled value through', () => {
52
- localStorage.setItem('reform-query:P', '99') // a previously-persisted value
53
+ const storageWindow = new Window({ url: 'https://reform-query.test/' })
54
+ const storage = storageWindow.localStorage
55
+ storage.setItem('reform-query:P', '99') // a previously-persisted value
53
56
  class Count extends State.make('count', S.Number) {}
54
57
  class Inputs extends StateGroup.make(Count) {}
55
58
  class Q extends AsyncCalc.make('Q', {
@@ -63,7 +66,7 @@ it.live('persist hydrates from localStorage (stale) then writes the settled valu
63
66
  })
64
67
  const App = QLive.pipe(
65
68
  Layer.provideMerge(StateGroup.live(Inputs, { count: 5 })),
66
- Layer.provideMerge(localStorageQueryStore),
69
+ Layer.provideMerge(makeLocalStorageQueryStore(storage)),
67
70
  Layer.provideMerge(Engine),
68
71
  )
69
72
 
@@ -71,8 +74,8 @@ it.live('persist hydrates from localStorage (stale) then writes the settled valu
71
74
  const view = yield* Q.store
72
75
  expect(view.get()).toMatchObject({ _tag: 'Success', value: 99, refetching: true })
73
76
 
74
- yield* waitUntil(() => localStorage.getItem('reform-query:P') === '10')
77
+ yield* waitUntil(() => storage.getItem('reform-query:P') === '10')
75
78
  expect(view.get()).toMatchObject({ _tag: 'Success', value: 10, refetching: false })
76
- expect(localStorage.getItem('reform-query:P')).toBe('10')
77
- }).pipe(Effect.provide(App))
79
+ expect(storage.getItem('reform-query:P')).toBe('10')
80
+ }).pipe(Effect.provide(App), Effect.ensuring(Effect.sync(() => storageWindow.close())))
78
81
  })