@playfast/reform-query-browser 1.0.2 → 1.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/README.md +3 -3
- package/package.json +18 -18
- package/src/localStorage.store.ts +16 -9
- package/src/queryEvents.browser.ts +1 -1
- package/src/refetchManagers.ts +1 -1
- package/src/reform-query-browser.test.ts +9 -6
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,
|
|
33
|
-
localStorageQueryStore,
|
|
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))
|
|
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
|
package/package.json
CHANGED
|
@@ -1,36 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playfast/reform-query-browser",
|
|
3
|
-
"
|
|
4
|
-
"version": "1.0.2",
|
|
5
|
-
"type": "module",
|
|
3
|
+
"version": "1.1.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
|
-
"
|
|
7
|
+
"layer",
|
|
11
8
|
"persistence",
|
|
9
|
+
"react-query",
|
|
12
10
|
"refetch-on-focus",
|
|
13
|
-
"
|
|
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
|
-
"
|
|
22
|
-
"
|
|
23
|
-
|
|
22
|
+
"files": [
|
|
23
|
+
"src",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"type": "module",
|
|
24
27
|
"sideEffects": false,
|
|
25
28
|
"exports": {
|
|
26
29
|
"./package.json": "./package.json",
|
|
27
30
|
".": "./src/index.ts",
|
|
28
31
|
"./*": "./src/*.ts"
|
|
29
32
|
},
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
],
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
34
36
|
"scripts": {
|
|
35
37
|
"clean": "rm -rf dist .tsbuildinfo",
|
|
36
38
|
"check": "tsc --noEmit",
|
|
@@ -42,10 +44,8 @@
|
|
|
42
44
|
"lint:fix": "oxlint --fix src"
|
|
43
45
|
},
|
|
44
46
|
"peerDependencies": {
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
+
"@playfast/reform": "*",
|
|
48
|
+
"effect": "*"
|
|
47
49
|
},
|
|
48
|
-
"
|
|
49
|
-
"access": "public"
|
|
50
|
-
}
|
|
50
|
+
"playbook": "./playbook"
|
|
51
51
|
}
|
|
@@ -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 =
|
|
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
|
-
|
|
20
|
+
storage.setItem(PREFIX + key, encodeJson(payload))
|
|
24
21
|
}),
|
|
25
22
|
remove: (key) =>
|
|
26
23
|
Effect.sync(() => {
|
|
27
|
-
|
|
24
|
+
storage.removeItem(PREFIX + key)
|
|
28
25
|
}),
|
|
29
26
|
}
|
|
30
27
|
}
|
|
31
28
|
|
|
32
|
-
export const
|
|
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(
|
package/src/refetchManagers.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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(
|
|
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(() =>
|
|
77
|
+
yield* waitUntil(() => storage.getItem('reform-query:P') === '10')
|
|
75
78
|
expect(view.get()).toMatchObject({ _tag: 'Success', value: 10, refetching: false })
|
|
76
|
-
expect(
|
|
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
|
})
|