first commit
This commit is contained in:
+132
@@ -0,0 +1,132 @@
|
||||
import * as _nuxt_schema from '@nuxt/schema';
|
||||
import { CookiesStorageOptions } from '../dist/nuxt/runtime/storages.js';
|
||||
import { StateTree, PiniaPluginContext } from 'pinia';
|
||||
|
||||
type IsAny<T> = unknown extends T ? ([keyof T] extends [never] ? false : true) : false;
|
||||
type ExcludeArrayKeys<T> = T extends ArrayLike<any> ? Exclude<keyof T, keyof any[]> : keyof T;
|
||||
type PathImpl<T, Key extends keyof T> = Key extends string ? IsAny<T[Key]> extends true ? never : T[Key] extends Record<string, any> ? `${Key}.${PathImpl<T[Key], ExcludeArrayKeys<T[Key]>> & string}` | `${Key}.${ExcludeArrayKeys<T[Key]> & string}` : never : never;
|
||||
type Path<T> = keyof T extends string ? (PathImpl<T, keyof T> | keyof T) extends infer P ? P extends string | keyof T ? P : keyof T : keyof T : never;
|
||||
/**
|
||||
* Synchronous storage based on Web Storage API.
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/Storage
|
||||
*/
|
||||
interface StorageLike {
|
||||
/**
|
||||
* Get a key's value if it exists.
|
||||
*/
|
||||
getItem: (key: string) => string | null;
|
||||
/**
|
||||
* Set a key with a value, or update it if it exists.
|
||||
*/
|
||||
setItem: (key: string, value: string) => void;
|
||||
}
|
||||
/**
|
||||
* Serializer implementation to stringify/parse state.
|
||||
*/
|
||||
interface Serializer {
|
||||
/**
|
||||
* Serialize state into string before storing.
|
||||
* @default JSON.stringify
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
|
||||
*/
|
||||
serialize: (data: StateTree) => string;
|
||||
/**
|
||||
* Deserializes string into state before hydrating.
|
||||
* @default JSON.parse
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
|
||||
*/
|
||||
deserialize: (data: string) => StateTree;
|
||||
}
|
||||
interface Persistence<State extends StateTree = StateTree> {
|
||||
key: string;
|
||||
/**
|
||||
* Log errors in console.
|
||||
* @default false
|
||||
*/
|
||||
debug: boolean;
|
||||
/**
|
||||
* Synchronous storage to persist the state.
|
||||
*/
|
||||
storage: StorageLike;
|
||||
/**
|
||||
* Serializer to serialize/deserialize state into storage.
|
||||
*/
|
||||
serializer: Serializer;
|
||||
/**
|
||||
* Hook called before hydrating store.
|
||||
*/
|
||||
beforeHydrate?: (context: PiniaPluginContext) => void;
|
||||
/**
|
||||
* Hook called after hydrating store.
|
||||
*/
|
||||
afterHydrate?: (context: PiniaPluginContext) => void;
|
||||
/**
|
||||
* Dot-notation paths to pick from state before persisting.
|
||||
*/
|
||||
pick?: Path<State>[] | string[];
|
||||
/**
|
||||
* Dot-notation paths to omit from state before persisting.
|
||||
*/
|
||||
omit?: Path<State>[] | string[];
|
||||
}
|
||||
type PersistenceOptions<State extends StateTree = StateTree> = Partial<Omit<Persistence<State>, 'key'>> & {
|
||||
/**
|
||||
* Storage key to use.
|
||||
* @default $store.id
|
||||
*/
|
||||
key?: ((s: string) => string) | string;
|
||||
};
|
||||
type Persist<State extends StateTree = StateTree> = boolean | PersistenceOptions<State> | PersistenceOptions<State>[];
|
||||
declare module 'pinia' {
|
||||
interface DefineStoreOptionsBase<S extends StateTree, Store> {
|
||||
/**
|
||||
* Persist store in storage
|
||||
* @see https://codeberg.org/praz/pinia-plugin-persistedstate
|
||||
*/
|
||||
persist?: Persist<S>;
|
||||
}
|
||||
interface PiniaCustomProperties {
|
||||
/**
|
||||
* Hydrate store from configured storage
|
||||
* Warning: this is for advances usecases, make sure you know what you're doing
|
||||
*/
|
||||
$hydrate: (opts?: {
|
||||
runHooks?: boolean;
|
||||
}) => void;
|
||||
/**
|
||||
* Persist store into configured storage
|
||||
* Warning: this is for advances usecases, make sure you know what you're doing
|
||||
*/
|
||||
$persist: () => void;
|
||||
}
|
||||
}
|
||||
|
||||
type ModuleOptions = Pick<PersistenceOptions, 'debug'> & {
|
||||
/**
|
||||
* Default storage for persistence. Only accepts presets.
|
||||
*/
|
||||
storage?: 'cookies' | 'localStorage' | 'sessionStorage';
|
||||
/**
|
||||
* Global key template, allow pre/postfixing store keys.
|
||||
* @example 'my-%id-persistence' will yield 'my-<store-id>-persistence'
|
||||
*/
|
||||
key?: `${string}%id${string}`;
|
||||
/**
|
||||
* Options used globally by default cookie storage.
|
||||
* Ignored for other storages.
|
||||
*/
|
||||
cookieOptions?: Omit<CookiesStorageOptions, 'encode' | 'decode'>;
|
||||
/**
|
||||
* Automatically persist all stores with global defaults, opt-out individually.
|
||||
*/
|
||||
auto?: boolean;
|
||||
};
|
||||
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
||||
|
||||
declare module '@nuxt/schema' {
|
||||
interface PublicRuntimeConfig {
|
||||
piniaPluginPersistedstate: ModuleOptions;
|
||||
}
|
||||
}
|
||||
|
||||
export { _default as default };
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "pinia-plugin-persistedstate",
|
||||
"configKey": "piniaPluginPersistedstate",
|
||||
"compatibility": {
|
||||
"nuxt": ">=3.0.0"
|
||||
},
|
||||
"version": "4.7.1",
|
||||
"builder": {
|
||||
"@nuxt/module-builder": "1.0.2",
|
||||
"unbuild": "3.6.0"
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
import { defineNuxtModule, createResolver, useLogger, hasNuxtModule, addImports, addPlugin } from '@nuxt/kit';
|
||||
import { defu } from 'defu';
|
||||
|
||||
const module = defineNuxtModule({
|
||||
meta: {
|
||||
name: "pinia-plugin-persistedstate",
|
||||
configKey: "piniaPluginPersistedstate",
|
||||
compatibility: {
|
||||
nuxt: ">=3.0.0"
|
||||
}
|
||||
},
|
||||
defaults: {},
|
||||
setup(options, nuxt) {
|
||||
const resolver = createResolver(import.meta.url);
|
||||
const logger = useLogger();
|
||||
if (!hasNuxtModule("pinia", nuxt)) {
|
||||
logger.warn("The `@pinia/nuxt` module was not found, `pinia-plugin-persistedstate/nuxt` will not work.");
|
||||
return;
|
||||
}
|
||||
nuxt.options.build.transpile.push(resolver.resolve("./runtime"));
|
||||
nuxt.options.runtimeConfig.public.piniaPluginPersistedstate = defu(nuxt.options.runtimeConfig.public.piniaPluginPersistedstate, options);
|
||||
addImports({
|
||||
name: "storages",
|
||||
from: resolver.resolve("./runtime/storages"),
|
||||
as: "piniaPluginPersistedstate"
|
||||
});
|
||||
addPlugin(resolver.resolve("./runtime/plugin"));
|
||||
}
|
||||
});
|
||||
|
||||
export { module as default };
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import type { PiniaPluginContext } from 'pinia';
|
||||
import type { Persistence, PersistenceOptions } from '../types.js';
|
||||
export declare function parsePersistKey(key: PersistenceOptions['key'], storeId: string): string;
|
||||
export declare function createPersistence(context: PiniaPluginContext, optionsParser: (p: PersistenceOptions) => Persistence, auto: boolean): void;
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
import { deepOmit, deepPick } from "./utils.js";
|
||||
function hydrateStore(store, {
|
||||
storage,
|
||||
serializer,
|
||||
key,
|
||||
debug,
|
||||
pick,
|
||||
omit,
|
||||
beforeHydrate,
|
||||
afterHydrate
|
||||
}, context, runHooks = true) {
|
||||
try {
|
||||
if (runHooks)
|
||||
beforeHydrate?.(context);
|
||||
const fromStorage = storage.getItem(key);
|
||||
if (fromStorage) {
|
||||
const deserialized = serializer.deserialize(fromStorage);
|
||||
const picked = pick ? deepPick(deserialized, pick) : deserialized;
|
||||
const omitted = omit ? deepOmit(picked, omit) : picked;
|
||||
store.$patch(omitted);
|
||||
}
|
||||
if (runHooks)
|
||||
afterHydrate?.(context);
|
||||
} catch (error) {
|
||||
if (debug)
|
||||
console.error("[pinia-plugin-persistedstate]", error);
|
||||
}
|
||||
}
|
||||
function persistState(state, {
|
||||
storage,
|
||||
serializer,
|
||||
key,
|
||||
debug,
|
||||
pick,
|
||||
omit
|
||||
}) {
|
||||
try {
|
||||
const picked = pick ? deepPick(state, pick) : state;
|
||||
const omitted = omit ? deepOmit(picked, omit) : picked;
|
||||
const toStorage = serializer.serialize(omitted);
|
||||
storage.setItem(key, toStorage);
|
||||
} catch (error) {
|
||||
if (debug)
|
||||
console.error("[pinia-plugin-persistedstate]", error);
|
||||
}
|
||||
}
|
||||
export function parsePersistKey(key, storeId) {
|
||||
return typeof key === "function" ? key(storeId) : typeof key === "string" ? key : storeId;
|
||||
}
|
||||
export function createPersistence(context, optionsParser, auto) {
|
||||
const { pinia, store, options: { persist = auto } } = context;
|
||||
if (!persist)
|
||||
return;
|
||||
// v8 ignore if -- @preserve
|
||||
if (!(store.$id in pinia.state.value)) {
|
||||
const originalStore = pinia._s.get(store.$id.replace("__hot:", ""));
|
||||
if (originalStore)
|
||||
void Promise.resolve().then(() => originalStore.$persist());
|
||||
return;
|
||||
}
|
||||
const persistenceOptions = Array.isArray(persist) ? persist : persist === true ? [{}] : [persist];
|
||||
const persistences = persistenceOptions.map(optionsParser);
|
||||
store.$hydrate = ({ runHooks = true } = {}) => {
|
||||
persistences.forEach((p) => {
|
||||
hydrateStore(store, p, context, runHooks);
|
||||
});
|
||||
};
|
||||
store.$persist = () => {
|
||||
persistences.forEach((p) => {
|
||||
persistState(store.$state, p);
|
||||
});
|
||||
};
|
||||
persistences.forEach((p) => {
|
||||
hydrateStore(store, p, context);
|
||||
store.$subscribe(
|
||||
(_mutation, state) => persistState(state, p),
|
||||
{ detached: true }
|
||||
);
|
||||
});
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
|
||||
export default _default;
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import { defineNuxtPlugin, useRuntimeConfig } from "#app";
|
||||
import { createPersistence, parsePersistKey } from "./core.js";
|
||||
import { storages } from "./storages.js";
|
||||
function piniaPlugin(context) {
|
||||
const config = useRuntimeConfig();
|
||||
const options = config.public.piniaPluginPersistedstate;
|
||||
createPersistence(
|
||||
context,
|
||||
(p) => {
|
||||
const persistKey = parsePersistKey(p.key, context.store.$id);
|
||||
return {
|
||||
key: options.key ? options.key.replace(/%id/g, persistKey) : persistKey,
|
||||
debug: p.debug ?? options.debug ?? false,
|
||||
serializer: p.serializer ?? {
|
||||
serialize: (data) => JSON.stringify(data),
|
||||
deserialize: (data) => JSON.parse(data)
|
||||
},
|
||||
storage: p.storage ?? (options.storage ? options.storage === "cookies" ? storages.cookies(options.cookieOptions) : storages[options.storage]() : storages.cookies()),
|
||||
beforeHydrate: p.beforeHydrate,
|
||||
afterHydrate: p.afterHydrate,
|
||||
pick: p.pick,
|
||||
omit: p.omit
|
||||
};
|
||||
},
|
||||
options.auto ?? false
|
||||
);
|
||||
}
|
||||
export default defineNuxtPlugin({
|
||||
name: "pinia-plugin-persistedstate",
|
||||
setup({ $pinia }) {
|
||||
$pinia.use(piniaPlugin);
|
||||
}
|
||||
});
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import type { CookieOptions } from '#app';
|
||||
import type { StorageLike } from '../types.js';
|
||||
export type CookiesStorageOptions = Omit<CookieOptions, 'default' | 'watch' | 'readonly' | 'filter'>;
|
||||
/**
|
||||
* Cookie-based storage. Cookie options can be passed as parameter.
|
||||
* Uses Nuxt's `useCookie` under the hood.
|
||||
*/
|
||||
declare function cookies(options?: CookiesStorageOptions): StorageLike;
|
||||
/**
|
||||
* LocalStorage-based storage.
|
||||
* Warning: only works client-side.
|
||||
*/
|
||||
declare function localStorage(): StorageLike;
|
||||
/**
|
||||
* SessionStorage-based storage.
|
||||
* Warning: only works client-side.
|
||||
*/
|
||||
declare function sessionStorage(): StorageLike;
|
||||
export declare const storages: {
|
||||
cookies: typeof cookies;
|
||||
localStorage: typeof localStorage;
|
||||
sessionStorage: typeof sessionStorage;
|
||||
};
|
||||
export {};
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import { useCookie, useRuntimeConfig } from "#app";
|
||||
function cookies(options) {
|
||||
return {
|
||||
getItem: (key) => useCookie(
|
||||
key,
|
||||
{
|
||||
...options ?? useRuntimeConfig().public.piniaPluginPersistedstate.cookieOptions ?? {},
|
||||
decode: options?.decode ?? decodeURIComponent,
|
||||
readonly: true
|
||||
}
|
||||
).value,
|
||||
setItem: (key, value) => useCookie(
|
||||
key,
|
||||
{
|
||||
...options ?? useRuntimeConfig().public.piniaPluginPersistedstate.cookieOptions ?? {},
|
||||
encode: options?.encode ?? encodeURIComponent
|
||||
}
|
||||
).value = value
|
||||
};
|
||||
}
|
||||
function localStorage() {
|
||||
return {
|
||||
getItem: (key) => import.meta.client ? window.localStorage.getItem(key) : null,
|
||||
setItem: (key, value) => import.meta.client ? window.localStorage.setItem(key, value) : null
|
||||
};
|
||||
}
|
||||
function sessionStorage() {
|
||||
return {
|
||||
getItem: (key) => import.meta.client ? window.sessionStorage.getItem(key) : null,
|
||||
setItem: (key, value) => import.meta.client ? window.sessionStorage.setItem(key, value) : null
|
||||
};
|
||||
}
|
||||
export const storages = {
|
||||
cookies,
|
||||
localStorage,
|
||||
sessionStorage
|
||||
};
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export declare function deepPick(obj: object, paths: Array<string>): {};
|
||||
export declare function deepOmit(obj: object, paths: Array<string>): object;
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
function get(obj, path) {
|
||||
if (obj == null)
|
||||
return void 0;
|
||||
let value = obj;
|
||||
for (let i = 0; i < path.length; i++) {
|
||||
if (value === void 0 || value[path[i]] === void 0)
|
||||
return void 0;
|
||||
if (value === null || value[path[i]] === null)
|
||||
return null;
|
||||
value = value[path[i]];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
function set(obj, value, path) {
|
||||
if (path.length === 0)
|
||||
return value;
|
||||
const idx = path[0];
|
||||
if (path.length > 1) {
|
||||
value = set(
|
||||
typeof obj !== "object" || obj === null || !Object.prototype.hasOwnProperty.call(obj, idx) ? Number.isInteger(Number(path[1])) ? [] : {} : obj[idx],
|
||||
value,
|
||||
Array.prototype.slice.call(path, 1)
|
||||
);
|
||||
}
|
||||
if (Number.isInteger(Number(idx)) && Array.isArray(obj))
|
||||
return obj.slice()[idx];
|
||||
return Object.assign({}, obj, { [idx]: value });
|
||||
}
|
||||
function unset(obj, path) {
|
||||
if (obj == null || path.length === 0)
|
||||
return obj;
|
||||
if (path.length === 1) {
|
||||
if (obj == null)
|
||||
return obj;
|
||||
if (Number.isInteger(path[0]) && Array.isArray(obj))
|
||||
return Array.prototype.slice.call(obj, 0).splice(path[0], 1);
|
||||
const result = {};
|
||||
for (const p in obj)
|
||||
result[p] = obj[p];
|
||||
delete result[path[0]];
|
||||
return result;
|
||||
}
|
||||
if (obj[path[0]] == null) {
|
||||
if (Number.isInteger(path[0]) && Array.isArray(obj))
|
||||
return Array.prototype.concat.call([], obj);
|
||||
const result = {};
|
||||
for (const p in obj)
|
||||
result[p] = obj[p];
|
||||
return result;
|
||||
}
|
||||
return set(
|
||||
obj,
|
||||
unset(
|
||||
obj[path[0]],
|
||||
Array.prototype.slice.call(path, 1)
|
||||
),
|
||||
[path[0]]
|
||||
);
|
||||
}
|
||||
export function deepPick(obj, paths) {
|
||||
return paths.map((p) => p.split(".")).map((p) => [p, get(obj, p)]).filter((t) => t[1] !== void 0).reduce((acc, cur) => set(acc, cur[1], cur[0]), {});
|
||||
}
|
||||
export function deepOmit(obj, paths) {
|
||||
return paths.map((p) => p.split(".")).reduce((acc, cur) => unset(acc, cur), obj);
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import type { NuxtModule } from '@nuxt/schema'
|
||||
|
||||
import type { default as Module } from './module.mjs'
|
||||
|
||||
export type ModuleOptions = typeof Module extends NuxtModule<infer O> ? Partial<O> : Record<string, any>
|
||||
|
||||
export { default } from './module.mjs'
|
||||
Reference in New Issue
Block a user