nuxt-state 0.0.2 → 0.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 +100 -20
- package/dist/module.json +1 -1
- package/dist/module.mjs +9 -2
- package/dist/runtime/app/composables/defineState.js +22 -2
- package/dist/runtime/app/plugins/hydration.d.ts +3 -0
- package/dist/runtime/app/plugins/hydration.js +17 -0
- package/dist/runtime/app/state-registry.d.ts +16 -0
- package/dist/runtime/app/state-registry.js +42 -0
- package/dist/runtime/app/state-snapshot.d.ts +13 -0
- package/dist/runtime/app/state-snapshot.js +74 -0
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -15,8 +15,8 @@ A regular composable runs its factory for every invocation. A composable created
|
|
|
15
15
|
`defineState` runs its factory lazily, once for the current Nuxt application instance,
|
|
16
16
|
and returns that exact result to every caller in that app.
|
|
17
17
|
|
|
18
|
-
This is a working prototype for discussion and possible future contribution to
|
|
19
|
-
|
|
18
|
+
This is a working open-source prototype for discussion and possible future contribution to
|
|
19
|
+
Nuxt. The API is intentionally narrow and the project is not yet presented as production-ready.
|
|
20
20
|
|
|
21
21
|
## Why
|
|
22
22
|
|
|
@@ -41,9 +41,6 @@ export default defineNuxtConfig({
|
|
|
41
41
|
})
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
The package has not been published yet; during development, use this repository as a
|
|
45
|
-
workspace dependency.
|
|
46
|
-
|
|
47
44
|
## Usage
|
|
48
45
|
|
|
49
46
|
Create a state in Nuxt 4's application source directory:
|
|
@@ -93,39 +90,119 @@ refs remain computed refs, reactive objects remain reactive, and functions are u
|
|
|
93
90
|
- The factory is lazy and runs at first use.
|
|
94
91
|
- It runs once per Nuxt app instance.
|
|
95
92
|
- The exact factory result is returned to all callers in that app.
|
|
93
|
+
- The factory runs in a detached Vue effect scope owned by the Nuxt app, so effects and Nuxt
|
|
94
|
+
composables are not disposed with the first consuming component.
|
|
96
95
|
- A module-local `WeakMap` keys instances by `NuxtApp`, providing request isolation while
|
|
97
96
|
allowing old application instances to be garbage-collected.
|
|
98
97
|
- Separate `defineState()` calls have separate closure-owned caches, including calls in
|
|
99
98
|
the same file.
|
|
99
|
+
- Mutable state used during SSR is restored into the client-created refs and reactive proxies
|
|
100
|
+
before Vue hydrates the component tree.
|
|
100
101
|
|
|
101
102
|
Async factories are rejected by TypeScript and guarded at runtime for JavaScript users.
|
|
102
103
|
Expose an async function from synchronous state or use Nuxt's data-fetching APIs instead.
|
|
103
104
|
|
|
104
|
-
## SSR
|
|
105
|
+
## SSR hydration
|
|
106
|
+
|
|
107
|
+
Since v0.1.0, nuxt-state transparently hydrates mutable top-level members returned by the factory.
|
|
108
|
+
Nuxt injects an internal call-site key at build time; the developer-facing call remains exactly
|
|
109
|
+
`defineState(factory)`.
|
|
110
|
+
|
|
111
|
+
On the server, the module captures the final values after rendering in one namespaced Nuxt
|
|
112
|
+
payload entry. On the client, it runs the factory normally and patches those values into the
|
|
113
|
+
new refs and reactive proxies before Vue hydration. The returned object is never replaced, so
|
|
114
|
+
computed refs, functions, watchers, and closures created by the client factory remain wired to
|
|
115
|
+
the hydrated state.
|
|
105
116
|
|
|
106
|
-
|
|
107
|
-
|
|
117
|
+
```ts
|
|
118
|
+
export const useAccount = defineState(() => {
|
|
119
|
+
const count = ref(0)
|
|
120
|
+
const user = reactive({ name: 'Guest', roles: [] as string[] })
|
|
121
|
+
const double = computed(() => count.value * 2)
|
|
122
|
+
const increment = () => count.value++
|
|
123
|
+
|
|
124
|
+
return { count, user, double, increment }
|
|
125
|
+
})
|
|
126
|
+
```
|
|
108
127
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
128
|
+
Nested serializable values inside supported refs and reactive objects are handled by Nuxt's
|
|
129
|
+
payload serializer. Functions, readonly computed refs, and plain runtime objects are recreated
|
|
130
|
+
by the factory rather than serialized. Concurrent SSR requests retain separate Nuxt-app
|
|
131
|
+
registries and cannot share user state.
|
|
132
|
+
|
|
133
|
+
Snapshot discovery is intentionally limited to mutable members exposed by the factory. Reactive
|
|
134
|
+
state that must survive SSR hydration currently needs to be exposed from the `defineState`
|
|
135
|
+
factory. A private ref captured only by a computed value or function is not visible to the
|
|
136
|
+
snapshot layer; if it is mutated during SSR, its client value can differ and cause a hydration
|
|
137
|
+
mismatch. Discovering closure-private Vue state would require a new explicit API, compiler-level
|
|
138
|
+
analysis, or undocumented reactivity inspection, none of which belongs in v0.2.0.
|
|
139
|
+
|
|
140
|
+
`useFetch()` can remain inside a synchronous state factory. Its request caching and payload
|
|
141
|
+
hydration still belong to Nuxt; `nuxt-state` neither replaces nor triggers a second fetch
|
|
142
|
+
mechanism. If multiple sibling SSR components must all render completed data, await the returned
|
|
143
|
+
Nuxt `AsyncData` promise in a parent/page as you would with normal Nuxt data fetching.
|
|
144
|
+
|
|
145
|
+
When a returned `useFetch().data` ref is snapshotted, both Nuxt's data payload and the internal
|
|
146
|
+
state snapshot refer to it. Nuxt's graph serializer preserves the shared object identity, so the
|
|
147
|
+
response body is emitted once rather than copied into the HTML twice. There is still a small
|
|
148
|
+
snapshot-metadata overhead.
|
|
149
|
+
|
|
150
|
+
## Compatibility
|
|
151
|
+
|
|
152
|
+
### Supported
|
|
153
|
+
|
|
154
|
+
- `ref()` and `reactive()`, including nested serializable objects and arrays;
|
|
155
|
+
- `shallowRef()` and `shallowReactive()` with their shallow semantics preserved;
|
|
156
|
+
- `Date`, `Map`, `Set`, shared references, and cyclic graphs supported by Nuxt's payload
|
|
157
|
+
serializer;
|
|
158
|
+
- readonly computed chains and functions as client-recreated runtime state;
|
|
159
|
+
- readonly views when their mutable source is also returned and hydrated;
|
|
160
|
+
- `useFetch()`, `useAsyncData()`, `callOnce()`, `useCookie()`, `useRuntimeConfig()`, `useRoute()`,
|
|
161
|
+
and `useRouter()` in valid Nuxt contexts;
|
|
162
|
+
- first use from plugins, route middleware, layouts, pages, and components;
|
|
163
|
+
- state lifetime across client navigation and repeated component mount/unmount.
|
|
164
|
+
|
|
165
|
+
`useFetch` and `useAsyncData` continue to own their request/payload behavior. nuxt-state's
|
|
166
|
+
snapshot metadata points at the same payload graph rather than serializing response bodies again.
|
|
167
|
+
`callOnce({ mode: 'navigation' })` retains Nuxt's normal per-navigation behavior.
|
|
168
|
+
|
|
169
|
+
### Characterized or limited
|
|
170
|
+
|
|
171
|
+
- A readonly view is not independent mutable state. If its mutable source is private, it follows
|
|
172
|
+
the private-state limitation below.
|
|
173
|
+
- Writable computed refs are not supported hydration state. Vue exposes no public `isComputed`
|
|
174
|
+
check, and a writable computed currently looks like a mutable ref; restoring it invokes its
|
|
175
|
+
setter and may cause side effects. Return and hydrate its source refs instead.
|
|
176
|
+
- Mutable reactive values that must survive SSR hydration need to be reachable through the
|
|
177
|
+
enumerable object returned from the factory.
|
|
178
|
+
- The intended return is a composable-style object. Arbitrary ref, reactive-root, function, or
|
|
179
|
+
primitive returns still share per app, but the current member-based snapshot format does not
|
|
180
|
+
hydrate them.
|
|
181
|
+
- Cycles are supported for Nuxt-serializable object graphs, not arbitrary native resources or
|
|
182
|
+
custom class instances.
|
|
113
183
|
|
|
114
184
|
## Current limitations
|
|
115
185
|
|
|
116
186
|
- Nuxt 4 and Vue 3 only.
|
|
117
187
|
- Synchronous factories only.
|
|
118
188
|
- No persistence or browser-storage integration.
|
|
119
|
-
-
|
|
189
|
+
- Hydration is guaranteed for standard and shallow refs/reactives. `customRef()` and writable
|
|
190
|
+
computed hydration are not supported.
|
|
191
|
+
- Hydrated values must be serializable by Nuxt's payload system; DOM nodes, sockets, functions
|
|
192
|
+
inside refs, symbols, and arbitrary native/class resources are unsupported.
|
|
193
|
+
- Closure-private mutable state is not discoverable; return any ref/reactive value whose SSR
|
|
194
|
+
mutations must hydrate.
|
|
120
195
|
- No Nuxt Layers support yet.
|
|
121
196
|
- State resets when its module is hot-reloaded; HMR preservation is not implemented.
|
|
122
|
-
-
|
|
123
|
-
guaranteed yet.
|
|
197
|
+
- Context-sensitive composables must still be called while normal Nuxt context is available.
|
|
124
198
|
- There is no reset API, keyed/multi-instance state, DevTools integration, or central
|
|
125
|
-
registry.
|
|
199
|
+
user-facing registry.
|
|
200
|
+
- The keyed transform is source-sensitive. The supported path is the module's auto-imported
|
|
201
|
+
`defineState`; a barrel re-export or unrelated manual wrapper is not guaranteed to receive an
|
|
202
|
+
internal hydration key.
|
|
126
203
|
|
|
127
|
-
See [
|
|
128
|
-
|
|
204
|
+
See [the architecture notes](./docs/architecture.md) and [roadmap](./docs/roadmap.md) for
|
|
205
|
+
implementation constraints and deferred work.
|
|
129
206
|
|
|
130
207
|
## Development
|
|
131
208
|
|
|
@@ -134,15 +211,18 @@ Requires Node.js 22+ and pnpm.
|
|
|
134
211
|
```bash
|
|
135
212
|
pnpm install
|
|
136
213
|
pnpm dev
|
|
214
|
+
pnpm fmt:check
|
|
137
215
|
pnpm lint
|
|
138
216
|
pnpm test:types
|
|
139
217
|
pnpm test
|
|
218
|
+
pnpm test:stress
|
|
140
219
|
pnpm prepack
|
|
141
220
|
pnpm dev:build
|
|
142
221
|
```
|
|
143
222
|
|
|
144
|
-
The
|
|
145
|
-
|
|
223
|
+
The browser suite requires Chromium, installed with
|
|
224
|
+
`pnpm exec playwright-core install chromium`. The playground contains two counter components,
|
|
225
|
+
a reactive object example, a nested state, and two independent states exported from one file.
|
|
146
226
|
|
|
147
227
|
## License
|
|
148
228
|
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resolve } from 'node:path';
|
|
2
|
-
import { defineNuxtModule, createResolver, addImports, addImportsDir } from '@nuxt/kit';
|
|
2
|
+
import { defineNuxtModule, createResolver, addImports, addPlugin, addImportsDir } from '@nuxt/kit';
|
|
3
3
|
|
|
4
4
|
const module$1 = defineNuxtModule({
|
|
5
5
|
meta: {
|
|
@@ -11,10 +11,17 @@ const module$1 = defineNuxtModule({
|
|
|
11
11
|
defaults: {},
|
|
12
12
|
setup(_options, nuxt) {
|
|
13
13
|
const resolver = createResolver(import.meta.url);
|
|
14
|
+
const defineStateSource = resolver.resolve("./runtime/app/composables/defineState");
|
|
14
15
|
addImports({
|
|
15
16
|
name: "defineState",
|
|
16
|
-
from:
|
|
17
|
+
from: defineStateSource
|
|
17
18
|
});
|
|
19
|
+
nuxt.options.optimization.keyedComposables.push({
|
|
20
|
+
name: "defineState",
|
|
21
|
+
source: defineStateSource,
|
|
22
|
+
argumentLength: 2
|
|
23
|
+
});
|
|
24
|
+
addPlugin(resolver.resolve("./runtime/app/plugins/hydration"));
|
|
18
25
|
addImportsDir(resolve(nuxt.options.srcDir, "states/**"));
|
|
19
26
|
}
|
|
20
27
|
});
|
|
@@ -1,21 +1,41 @@
|
|
|
1
1
|
import { useNuxtApp } from "#app";
|
|
2
|
+
import { effectScope } from "vue";
|
|
3
|
+
import { registerHydratableState } from "../state-registry.js";
|
|
4
|
+
import { restoreState, snapshotState } from "../state-snapshot.js";
|
|
2
5
|
function isPromiseLike(value) {
|
|
3
6
|
return (typeof value === "object" && value !== null || typeof value === "function") && "then" in value && typeof value.then === "function";
|
|
4
7
|
}
|
|
5
|
-
export function defineState(factory) {
|
|
8
|
+
export function defineState(factory, internalKey) {
|
|
6
9
|
const instances = /* @__PURE__ */ new WeakMap();
|
|
7
10
|
return function useDefinedState() {
|
|
8
11
|
const nuxtApp = useNuxtApp();
|
|
9
12
|
if (instances.has(nuxtApp)) {
|
|
10
13
|
return instances.get(nuxtApp);
|
|
11
14
|
}
|
|
12
|
-
const
|
|
15
|
+
const scope = effectScope(true);
|
|
16
|
+
let instance;
|
|
17
|
+
try {
|
|
18
|
+
instance = scope.run(factory);
|
|
19
|
+
} catch (error) {
|
|
20
|
+
scope.stop();
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
13
23
|
if (isPromiseLike(instance)) {
|
|
24
|
+
scope.stop();
|
|
14
25
|
throw new TypeError(
|
|
15
26
|
"[nuxt-state] State factories must be synchronous. Expose an async function from the state or use Nuxt data-fetching composables instead."
|
|
16
27
|
);
|
|
17
28
|
}
|
|
18
29
|
instances.set(nuxtApp, instance);
|
|
30
|
+
const app = nuxtApp;
|
|
31
|
+
app.vueApp?.onUnmount?.(() => scope.stop());
|
|
32
|
+
if (internalKey) {
|
|
33
|
+
registerHydratableState(nuxtApp, internalKey, {
|
|
34
|
+
snapshot: () => snapshotState(instance),
|
|
35
|
+
restore: (snapshot) => restoreState(instance, snapshot),
|
|
36
|
+
dispose: () => scope.stop()
|
|
37
|
+
});
|
|
38
|
+
}
|
|
19
39
|
return instance;
|
|
20
40
|
};
|
|
21
41
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineNuxtPlugin, useHydration } from "#app";
|
|
2
|
+
import {
|
|
3
|
+
clearPendingStateSnapshots,
|
|
4
|
+
collectStateSnapshots,
|
|
5
|
+
receiveStateSnapshots
|
|
6
|
+
} from "../state-registry.js";
|
|
7
|
+
export const STATE_PAYLOAD_KEY = "__nuxt_state__";
|
|
8
|
+
export default defineNuxtPlugin((nuxtApp) => {
|
|
9
|
+
useHydration(
|
|
10
|
+
STATE_PAYLOAD_KEY,
|
|
11
|
+
() => collectStateSnapshots(nuxtApp),
|
|
12
|
+
(snapshots) => receiveStateSnapshots(nuxtApp, snapshots)
|
|
13
|
+
);
|
|
14
|
+
if (import.meta.client) {
|
|
15
|
+
nuxtApp.hook("app:mounted", () => clearPendingStateSnapshots(nuxtApp));
|
|
16
|
+
}
|
|
17
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type StateHydrationPayload = Record<string, unknown>;
|
|
2
|
+
export interface HydratableStateEntry {
|
|
3
|
+
snapshot: () => unknown;
|
|
4
|
+
restore: (snapshot: unknown) => void;
|
|
5
|
+
dispose?: () => void;
|
|
6
|
+
}
|
|
7
|
+
interface StateRegistry {
|
|
8
|
+
active: Map<string, HydratableStateEntry>;
|
|
9
|
+
hydration: Map<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
export declare function getStateRegistry(nuxtApp: object): StateRegistry;
|
|
12
|
+
export declare function registerHydratableState(nuxtApp: object, key: string, entry: HydratableStateEntry): void;
|
|
13
|
+
export declare function collectStateSnapshots(nuxtApp: object): StateHydrationPayload;
|
|
14
|
+
export declare function receiveStateSnapshots(nuxtApp: object, snapshots: StateHydrationPayload | undefined): void;
|
|
15
|
+
export declare function clearPendingStateSnapshots(nuxtApp: object): void;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const registries = /* @__PURE__ */ new WeakMap();
|
|
2
|
+
export function getStateRegistry(nuxtApp) {
|
|
3
|
+
let registry = registries.get(nuxtApp);
|
|
4
|
+
if (!registry) {
|
|
5
|
+
registry = {
|
|
6
|
+
active: /* @__PURE__ */ new Map(),
|
|
7
|
+
hydration: /* @__PURE__ */ new Map()
|
|
8
|
+
};
|
|
9
|
+
registries.set(nuxtApp, registry);
|
|
10
|
+
}
|
|
11
|
+
return registry;
|
|
12
|
+
}
|
|
13
|
+
export function registerHydratableState(nuxtApp, key, entry) {
|
|
14
|
+
const registry = getStateRegistry(nuxtApp);
|
|
15
|
+
registry.active.get(key)?.dispose?.();
|
|
16
|
+
registry.active.set(key, entry);
|
|
17
|
+
if (registry.hydration.has(key)) {
|
|
18
|
+
const snapshot = registry.hydration.get(key);
|
|
19
|
+
registry.hydration.delete(key);
|
|
20
|
+
entry.restore(snapshot);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function collectStateSnapshots(nuxtApp) {
|
|
24
|
+
const snapshots = {};
|
|
25
|
+
for (const [key, entry] of getStateRegistry(nuxtApp).active) {
|
|
26
|
+
snapshots[key] = entry.snapshot();
|
|
27
|
+
}
|
|
28
|
+
return snapshots;
|
|
29
|
+
}
|
|
30
|
+
export function receiveStateSnapshots(nuxtApp, snapshots) {
|
|
31
|
+
const registry = getStateRegistry(nuxtApp);
|
|
32
|
+
registry.hydration = new Map(Object.entries(snapshots ?? {}));
|
|
33
|
+
for (const [key, entry] of registry.active) {
|
|
34
|
+
if (!registry.hydration.has(key)) continue;
|
|
35
|
+
const snapshot = registry.hydration.get(key);
|
|
36
|
+
registry.hydration.delete(key);
|
|
37
|
+
entry.restore(snapshot);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export function clearPendingStateSnapshots(nuxtApp) {
|
|
41
|
+
getStateRegistry(nuxtApp).hydration.clear();
|
|
42
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
interface RefSnapshot {
|
|
2
|
+
type: 'ref';
|
|
3
|
+
value: unknown;
|
|
4
|
+
}
|
|
5
|
+
interface ReactiveSnapshot {
|
|
6
|
+
type: 'reactive';
|
|
7
|
+
value: unknown;
|
|
8
|
+
}
|
|
9
|
+
type StateSnapshotEntry = RefSnapshot | ReactiveSnapshot;
|
|
10
|
+
export type StateSnapshot = Record<string, StateSnapshotEntry>;
|
|
11
|
+
export declare function snapshotState(state: unknown): StateSnapshot;
|
|
12
|
+
export declare function restoreState(state: unknown, snapshot: unknown): void;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { isReactive, isReadonly, isRef, toRaw } from "vue";
|
|
2
|
+
function isObjectLike(value) {
|
|
3
|
+
return typeof value === "object" && value !== null || typeof value === "function";
|
|
4
|
+
}
|
|
5
|
+
function unwrapReactive(value) {
|
|
6
|
+
return isReactive(value) ? toRaw(value) : value;
|
|
7
|
+
}
|
|
8
|
+
export function snapshotState(state) {
|
|
9
|
+
const snapshot = {};
|
|
10
|
+
if (!isObjectLike(state)) return snapshot;
|
|
11
|
+
for (const [name, value] of Object.entries(state)) {
|
|
12
|
+
if (isRef(value) && !isReadonly(value)) {
|
|
13
|
+
snapshot[name] = {
|
|
14
|
+
type: "ref",
|
|
15
|
+
value: unwrapReactive(value.value)
|
|
16
|
+
};
|
|
17
|
+
} else if (isReactive(value) && !isReadonly(value)) {
|
|
18
|
+
snapshot[name] = {
|
|
19
|
+
type: "reactive",
|
|
20
|
+
value: toRaw(value)
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return snapshot;
|
|
25
|
+
}
|
|
26
|
+
function isPlainRecord(value) {
|
|
27
|
+
return Object.prototype.toString.call(value) === "[object Object]";
|
|
28
|
+
}
|
|
29
|
+
function canPatch(target, source) {
|
|
30
|
+
return Array.isArray(target) && Array.isArray(source) || isPlainRecord(target) && !isRef(target) && isPlainRecord(source) && !isRef(source);
|
|
31
|
+
}
|
|
32
|
+
function isTrackableObject(value) {
|
|
33
|
+
return typeof value === "object" && value !== null;
|
|
34
|
+
}
|
|
35
|
+
function patchValue(target, source, seen) {
|
|
36
|
+
if (!isTrackableObject(source)) return source;
|
|
37
|
+
if (seen.has(source)) return seen.get(source);
|
|
38
|
+
if (!canPatch(target, source)) {
|
|
39
|
+
seen.set(source, source);
|
|
40
|
+
return source;
|
|
41
|
+
}
|
|
42
|
+
seen.set(source, target);
|
|
43
|
+
if (Array.isArray(target) && Array.isArray(source)) {
|
|
44
|
+
for (let index = 0; index < source.length; index++) {
|
|
45
|
+
target[index] = patchValue(target[index], source[index], seen);
|
|
46
|
+
}
|
|
47
|
+
target.length = source.length;
|
|
48
|
+
return target;
|
|
49
|
+
}
|
|
50
|
+
if (!isPlainRecord(target) || !isPlainRecord(source)) return source;
|
|
51
|
+
for (const key of Object.keys(target)) {
|
|
52
|
+
if (!(key in source)) delete target[key];
|
|
53
|
+
}
|
|
54
|
+
for (const [key, value] of Object.entries(source)) {
|
|
55
|
+
target[key] = patchValue(target[key], value, seen);
|
|
56
|
+
}
|
|
57
|
+
return target;
|
|
58
|
+
}
|
|
59
|
+
function isStateSnapshotEntry(value) {
|
|
60
|
+
return isPlainRecord(value) && (value.type === "ref" || value.type === "reactive") && Object.hasOwn(value, "value");
|
|
61
|
+
}
|
|
62
|
+
export function restoreState(state, snapshot) {
|
|
63
|
+
if (!isObjectLike(state) || !isPlainRecord(snapshot)) return;
|
|
64
|
+
const seen = /* @__PURE__ */ new WeakMap();
|
|
65
|
+
for (const [name, entry] of Object.entries(snapshot)) {
|
|
66
|
+
if (!isStateSnapshotEntry(entry)) continue;
|
|
67
|
+
const target = state[name];
|
|
68
|
+
if (entry.type === "ref" && isRef(target) && !isReadonly(target)) {
|
|
69
|
+
target.value = entry.value;
|
|
70
|
+
} else if (entry.type === "reactive" && isReactive(target) && !isReadonly(target)) {
|
|
71
|
+
patchValue(target, entry.value, seen);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-state",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Define shared Nuxt state using the same Composition API you already use in composables.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"composable",
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
"nuxt": "^4.5.2",
|
|
59
59
|
"oxfmt": "^0.65.0",
|
|
60
60
|
"oxlint": "^1.80.0",
|
|
61
|
+
"playwright-core": "^1.62.1",
|
|
61
62
|
"typescript": "^6.0.3",
|
|
62
63
|
"vitest": "^4.1.11",
|
|
63
64
|
"vue": "^3.5.31",
|
|
@@ -75,8 +76,10 @@
|
|
|
75
76
|
"fmt": "oxfmt",
|
|
76
77
|
"fmt:check": "oxfmt --check",
|
|
77
78
|
"test": "vitest run",
|
|
78
|
-
"
|
|
79
|
+
"test:browser": "vitest run test/browser",
|
|
80
|
+
"test:stress": "vitest run --config vitest.stress.config.ts",
|
|
81
|
+
"check": "pnpm run fmt:check && pnpm run lint",
|
|
79
82
|
"test:watch": "vitest",
|
|
80
|
-
"test:types": "vue-tsc --noEmit && pnpm --dir playground exec vue-tsc --noEmit"
|
|
83
|
+
"test:types": "nuxt-module-build prepare && nuxt-module-build build && vue-tsc --noEmit && pnpm --dir playground exec vue-tsc --noEmit"
|
|
81
84
|
}
|
|
82
85
|
}
|