zova-module-a-model 5.0.18 → 5.0.22

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zova-module-a-model",
3
- "version": "5.0.18",
3
+ "version": "5.0.22",
4
4
  "title": "a-model",
5
5
  "zovaModule": {
6
6
  "capabilities": {
@@ -55,5 +55,5 @@
55
55
  "@tanstack/vue-query": "^5.45.0",
56
56
  "localforage": "^1.10.0"
57
57
  },
58
- "gitHead": "38b917802862663cf6c2e6829857de9a2a742dcd"
58
+ "gitHead": "32f556ffbbe6864613db3b5a50adc3fb259411d9"
59
59
  }
@@ -1,12 +1,13 @@
1
1
  import { Query } from '@tanstack/vue-query';
2
- import { BeanModelPersister } from './bean.model.persister.js';
2
+ import { BeanModelLocal } from './bean.model.local.js';
3
+ import { QueryMetaPersisterCookieType } from '../../types.js';
3
4
 
4
- export class BeanModelCookie<TScopeModule = unknown> extends BeanModelPersister<TScopeModule> {
5
+ export class BeanModelCookie<TScopeModule = unknown> extends BeanModelLocal<TScopeModule> {
5
6
  $serializeCookie(obj?: Query) {
6
7
  return String(obj?.state?.data ?? '');
7
8
  }
8
9
 
9
- $deserializeCookie(value?: string) {
10
+ $deserializeCookie(value?: any) {
10
11
  return {
11
12
  state: {
12
13
  data: value,
@@ -27,4 +28,20 @@ export class BeanModelCookie<TScopeModule = unknown> extends BeanModelPersister<
27
28
  buster: this._getPersisterBuster(),
28
29
  };
29
30
  }
31
+
32
+ protected _cookieCoerce(value?: string, cookieType?: QueryMetaPersisterCookieType) {
33
+ if (value === undefined || value === '') return undefined; // string of '' means should delete the cookie
34
+ if (!cookieType || cookieType === 'auto') {
35
+ return value === 'true' ? true : value === 'false' ? false : value;
36
+ } else if (cookieType === 'boolean') {
37
+ return value === 'true' ? true : value === 'false' ? false : Boolean(Number(value));
38
+ } else if (cookieType === 'number') {
39
+ return Number(value);
40
+ } else if (cookieType === 'date') {
41
+ return new Date(value);
42
+ } else if (cookieType === 'string') {
43
+ return value;
44
+ }
45
+ return value;
46
+ }
30
47
  }
@@ -0,0 +1,30 @@
1
+ import { Query } from '@tanstack/vue-query';
2
+ import { BeanModelPersister } from './bean.model.persister.js';
3
+
4
+ export class BeanModelLocal<TScopeModule = unknown> extends BeanModelPersister<TScopeModule> {
5
+ $serializeLocal(obj?: Query) {
6
+ return JSON.stringify(obj?.state?.data);
7
+ }
8
+
9
+ $deserializeLocal(value?: string) {
10
+ return {
11
+ state: {
12
+ data: value ? JSON.parse(value) : undefined,
13
+ dataUpdateCount: 0,
14
+ dataUpdatedAt: Date.now(),
15
+ error: null,
16
+ errorUpdateCount: 0,
17
+ errorUpdatedAt: 0,
18
+ fetchFailureCount: 0,
19
+ fetchFailureReason: null,
20
+ fetchMeta: null,
21
+ isInvalidated: false,
22
+ status: 'success',
23
+ fetchStatus: 'idle',
24
+ },
25
+ queryKey: undefined,
26
+ queryHash: undefined,
27
+ buster: this._getPersisterBuster(),
28
+ };
29
+ }
30
+ }
@@ -1,9 +1,10 @@
1
- import { QueryMetaPersister } from '../../types.js';
1
+ import { QueryMetaPersister, resolveMaxAgeTime } from '../../types.js';
2
2
  import { experimental_createPersister } from '@tanstack/query-persist-client-core';
3
3
  import { Query, QueryKey } from '@tanstack/vue-query';
4
4
  import localforage from 'localforage';
5
5
  import { SymbolBeanFullName } from 'zova';
6
6
  import { BeanModelLast } from './bean.model.last.js';
7
+ import { CookieWrapper } from '../../common/cookieWrapper.js';
7
8
 
8
9
  export class BeanModelPersister<TScopeModule = unknown> extends BeanModelLast<TScopeModule> {
9
10
  $persisterLoad<T>(queryKey: QueryKey): T | undefined {
@@ -11,7 +12,7 @@ export class BeanModelPersister<TScopeModule = unknown> extends BeanModelLast<TS
11
12
  if (!query) return undefined;
12
13
  const options = this._adjustPersisterOptions(query.meta?.persister);
13
14
  if (!options) return undefined;
14
- const storage = this._getPersisterStorage(options);
15
+ const storage = this._getPersisterStorage(options, query);
15
16
  if (!storage) return undefined;
16
17
  const storageKey = this._getPersisterStorageKey(options, query);
17
18
  try {
@@ -21,7 +22,7 @@ export class BeanModelPersister<TScopeModule = unknown> extends BeanModelLast<TS
21
22
 
22
23
  if (persistedQuery.state.dataUpdatedAt) {
23
24
  const queryAge = Date.now() - persistedQuery.state.dataUpdatedAt;
24
- const expired = queryAge > options.maxAge!;
25
+ const expired = queryAge > (resolveMaxAgeTime(options.maxAge, query) ?? Infinity);
25
26
  const busted = persistedQuery.buster !== options.buster;
26
27
  if (expired || busted) {
27
28
  storage.removeItem(storageKey);
@@ -50,7 +51,7 @@ export class BeanModelPersister<TScopeModule = unknown> extends BeanModelLast<TS
50
51
  if (!query) return;
51
52
  const options = this._adjustPersisterOptions(query.meta?.persister);
52
53
  if (!options) return;
53
- const storage = this._getPersisterStorage(options);
54
+ const storage = this._getPersisterStorage(options, query);
54
55
  if (!storage) return;
55
56
  const storageKey = this._getPersisterStorageKey(options, query);
56
57
  const data = options.serialize!({
@@ -74,7 +75,7 @@ export class BeanModelPersister<TScopeModule = unknown> extends BeanModelLast<TS
74
75
  if (!query) return;
75
76
  const options = this._adjustPersisterOptions(query.meta?.persister);
76
77
  if (!options) return;
77
- const storage = this._getPersisterStorage(options);
78
+ const storage = this._getPersisterStorage(options, query);
78
79
  if (!storage) return;
79
80
  const storageKey = this._getPersisterStorageKey(options, query);
80
81
  if (options.sync === true) {
@@ -92,7 +93,7 @@ export class BeanModelPersister<TScopeModule = unknown> extends BeanModelLast<TS
92
93
  if (!options) return undefined;
93
94
  return experimental_createPersister({
94
95
  storage: this._getPersisterStorage(options) as any,
95
- maxAge: options.maxAge,
96
+ maxAge: options.maxAge as number,
96
97
  prefix: options.prefix,
97
98
  buster: options.buster,
98
99
  });
@@ -106,9 +107,7 @@ export class BeanModelPersister<TScopeModule = unknown> extends BeanModelLast<TS
106
107
  options = { ...options };
107
108
  }
108
109
  options.storage = options.storage ?? (options.sync ? 'local' : 'db');
109
- options.maxAge =
110
- options.maxAge ??
111
- (options.sync ? this.scopeSelf.config.persister.sync.maxAge : this.scopeSelf.config.persister.async.maxAge);
110
+ options.maxAge = options.maxAge ?? this.scopeSelf.config.persister.maxAge[options.storage];
112
111
  options.prefix = options.prefix ?? this._getPersisterPrefix();
113
112
  options.buster = options.buster ?? this._getPersisterBuster();
114
113
  options.serialize = options.serialize ?? JSON.stringify;
@@ -117,15 +116,20 @@ export class BeanModelPersister<TScopeModule = unknown> extends BeanModelLast<TS
117
116
  }
118
117
 
119
118
  protected _getPersisterStorageKey(options: QueryMetaPersister, query: Query) {
120
- if (options.storage === 'cookie') return String(query.queryKey[query.queryKey.length - 1]);
119
+ if (['cookie', 'local'].includes(options.storage!)) return String(query.queryKey[query.queryKey.length - 1]);
121
120
  return `${options.prefix}-${query.queryHash}`;
122
121
  }
123
122
 
124
- protected _getPersisterStorage(options?: QueryMetaPersister | boolean) {
123
+ protected _getPersisterStorage(options?: QueryMetaPersister | boolean, query?: Query) {
125
124
  options = this._adjustPersisterOptions(options);
126
125
  if (!options) return undefined;
127
- if (options.storage === 'cookie') return this.app.meta.cookie;
126
+ // cookie
127
+ if (options.storage === 'cookie') return this.bean._newBeanSimple(CookieWrapper, false, options, query);
128
+ // check server
129
+ if (process.env.SERVER) return undefined;
130
+ // local
128
131
  if (options.storage === 'local') return localStorage;
132
+ // db
129
133
  if (options.storage === 'db') return localforage;
130
134
  }
131
135
 
@@ -13,6 +13,7 @@ import { UnwrapNestedRefs } from 'vue';
13
13
  import { useCustomRef } from 'zova';
14
14
  import { DefinedInitialQueryOptions, UndefinedInitialQueryOptions } from '../../common/types.js';
15
15
  import { BeanModelQuery } from './bean.model.query.js';
16
+ import { resolveStaleTime } from '../../types.js';
16
17
 
17
18
  const SymbolUseQueries = Symbol('SymbolUseQueries');
18
19
 
@@ -35,6 +36,19 @@ export class BeanModelUseQuery<TScopeModule = unknown> extends BeanModelQuery<TS
35
36
  const queryKey = this.self._forceQueryKeyPrefix(options.queryKey);
36
37
  const persister = this._createPersister(options.meta?.persister);
37
38
  options = { ...options, queryKey, persister };
39
+ // staleTime
40
+ const sync = typeof options.meta?.persister === 'object' && options.meta?.persister?.sync;
41
+ if (sync !== true) {
42
+ const staleTime = options.staleTime ?? this.scopeSelf.config.query.staleTime.async;
43
+ const queryCache = this.$queryFind({ queryKey });
44
+ const queryCacheExists = queryCache?.state.data !== undefined;
45
+ options.staleTime = query => {
46
+ if (process.env.CLIENT && this.ctx.meta.ssr.isRuntimeSsrPreHydration && queryCacheExists) {
47
+ return resolveStaleTime(this.scopeSelf.config.query.staleTime.ssr, query);
48
+ }
49
+ return resolveStaleTime(staleTime, query);
50
+ };
51
+ }
38
52
  return this.ctx.meta.util.instanceScope(() => {
39
53
  return useQuery(options, queryClient);
40
54
  });
@@ -59,13 +73,28 @@ export class BeanModelUseQuery<TScopeModule = unknown> extends BeanModelQuery<TS
59
73
  TQueryKey extends QueryKey = QueryKey,
60
74
  >(options: UseQueryOptions<TQueryFnData, TError, TData, TQueryFnData, TQueryKey>, queryClient?: QueryClient): TData;
61
75
  $useQueryLocal(options, queryClient) {
62
- options = this.app.meta.util.extend({}, options, {
63
- enabled: false,
64
- staleTime: Infinity,
65
- meta: {
66
- persister: { storage: 'local', sync: true },
76
+ options = this.app.meta.util.extend(
77
+ {
78
+ meta: {
79
+ persister: {
80
+ serialize: (obj?: Query) => {
81
+ return this.$serializeLocal(obj);
82
+ },
83
+ deserialize: (value?: string) => {
84
+ return this.$deserializeLocal(value);
85
+ },
86
+ },
87
+ },
67
88
  },
68
- });
89
+ options,
90
+ {
91
+ enabled: false,
92
+ staleTime: Infinity,
93
+ meta: {
94
+ persister: { storage: 'local', sync: true },
95
+ },
96
+ },
97
+ );
69
98
  const self = this;
70
99
  return useCustomRef(() => {
71
100
  return {
@@ -114,7 +143,8 @@ export class BeanModelUseQuery<TScopeModule = unknown> extends BeanModelQuery<TS
114
143
  return this.$serializeCookie(obj);
115
144
  },
116
145
  deserialize: (value?: string) => {
117
- return this.$deserializeCookie(value);
146
+ const cookieType = options.meta.persister.cookieType;
147
+ return this.$deserializeCookie(this._cookieCoerce(value, cookieType));
118
148
  },
119
149
  },
120
150
  },
@@ -0,0 +1,40 @@
1
+ import { BeanBase, Local } from 'zova';
2
+ import { ScopeModule } from '../resource/this.js';
3
+ import { dehydrate, hydrate, QueryClient, VueQueryPlugin, VueQueryPluginOptions } from '@tanstack/vue-query';
4
+
5
+ @Local()
6
+ export class LocalStorage extends BeanBase<ScopeModule> {
7
+ protected async __init__() {
8
+ // options
9
+ let options = this.scope.config.queryClientConfig.defaultOptions;
10
+ if (process.env.SERVER) {
11
+ options = this.app.meta.util.extend({}, options, {
12
+ queries: { gcTime: Infinity },
13
+ });
14
+ }
15
+ // queryClient
16
+ const queryClient = new QueryClient({
17
+ defaultOptions: options,
18
+ });
19
+ // use plugin
20
+ const vueQueryPluginOptions: VueQueryPluginOptions = { queryClient };
21
+ this.app.vue.use(VueQueryPlugin, vueQueryPluginOptions);
22
+ // onRendered
23
+ if (process.env.SERVER) {
24
+ this.ctx.meta.ssr.context.onRendered(() => {
25
+ this.ctx.meta.ssr.state.query = dehydrate(queryClient, {
26
+ shouldDehydrateMutation: () => {
27
+ return false;
28
+ },
29
+ });
30
+ queryClient.clear();
31
+ });
32
+ }
33
+ // client
34
+ if (process.env.CLIENT && this.ctx.meta.ssr.isRuntimeSsrPreHydration) {
35
+ hydrate(queryClient, this.ctx.meta.ssr.state.query);
36
+ }
37
+ }
38
+
39
+ protected __dispose__() {}
40
+ }
@@ -0,0 +1,36 @@
1
+ import { Query } from '@tanstack/vue-query';
2
+ import { BeanSimple, CookieOptions } from 'zova';
3
+ import { QueryMetaPersister, resolveMaxAgeTime } from '../types.js';
4
+ import { __ThisModule__ } from '../resource/this.js';
5
+
6
+ export class CookieWrapper extends BeanSimple {
7
+ options: QueryMetaPersister;
8
+ query: Query;
9
+
10
+ protected __init__(options: QueryMetaPersister, query: Query) {
11
+ this.options = options;
12
+ this.query = query;
13
+ }
14
+
15
+ getItem(key: string): string | undefined {
16
+ return this.app.meta.cookie.getItem(key);
17
+ }
18
+
19
+ setItem(key: string, value: string): void {
20
+ const configScope = this.bean.scope(__ThisModule__).config;
21
+ const opts: CookieOptions = { ...configScope.persister.cookie.options };
22
+ let maxAge = resolveMaxAgeTime(this.options.maxAge, this.query);
23
+ if (maxAge !== undefined) {
24
+ if (maxAge === Infinity) maxAge = 1000 * 60 * 60 * 24 * 365;
25
+ opts.expires = new Date(Date.now() + maxAge);
26
+ }
27
+ if (!opts.path) {
28
+ opts.path = `/${this.app.config.env.appPublicPath || ''}`;
29
+ }
30
+ this.app.meta.cookie.setItem(key, value, opts);
31
+ }
32
+
33
+ removeItem(key: string): void {
34
+ this.app.meta.cookie.removeItem(key);
35
+ }
36
+ }
@@ -1,5 +1,6 @@
1
- import { DefaultOptions, defaultShouldDehydrateQuery } from '@tanstack/vue-query';
2
- import { ZovaApplication } from 'zova';
1
+ import { DefaultOptions, defaultShouldDehydrateQuery, StaleTime } from '@tanstack/vue-query';
2
+ import { CookieOptions, ZovaApplication } from 'zova';
3
+ import { MaxAgeTime } from '../types.js';
3
4
 
4
5
  const defaultOptions: DefaultOptions = {
5
6
  queries: {
@@ -12,19 +13,31 @@ const defaultOptions: DefaultOptions = {
12
13
  dehydrate: {
13
14
  shouldDehydrateQuery(query) {
14
15
  if (query.meta?.ssr?.dehydrate === false) return false;
16
+ if (typeof query.meta?.persister === 'object' && query.meta?.persister?.sync) return false;
15
17
  return defaultShouldDehydrateQuery(query);
16
18
  },
19
+ shouldDehydrateMutation(_mutation) {
20
+ return false;
21
+ },
17
22
  },
18
23
  };
19
24
 
20
25
  export const config = (_app: ZovaApplication) => {
21
26
  return {
22
27
  persister: {
23
- sync: {
24
- maxAge: Infinity,
28
+ maxAge: {
29
+ cookie: undefined as MaxAgeTime | undefined, // undefined: session cookie
30
+ local: Infinity as MaxAgeTime,
31
+ db: (1000 * 60 * 60 * 24) as number, // 24 hours
32
+ },
33
+ cookie: {
34
+ options: {} as Omit<CookieOptions, 'expires'>,
25
35
  },
26
- async: {
27
- maxAge: 1000 * 60 * 60 * 24, // 24 hours
36
+ },
37
+ query: {
38
+ staleTime: {
39
+ async: 0 as StaleTime,
40
+ ssr: Infinity as StaleTime,
28
41
  },
29
42
  },
30
43
  queryClientConfig: {
package/src/monkey.ts CHANGED
@@ -1,14 +1,14 @@
1
1
  import { BeanBase, BeanContainer, BeanSimple, IMonkeySystem } from 'zova';
2
- import { Storage } from './local/storage.js';
2
+ import { LocalStorage } from './bean/local.storage.js';
3
3
  import { useQueryClient } from '@tanstack/vue-query';
4
4
  import { markRaw } from 'vue';
5
5
 
6
6
  export class Monkey extends BeanSimple implements IMonkeySystem {
7
- storage: Storage;
7
+ storage: LocalStorage;
8
8
 
9
9
  async appInitialize(bean: BeanContainer) {
10
10
  // storage
11
- this.storage = await bean._newBean(Storage, false);
11
+ this.storage = await bean._newBean(LocalStorage, false);
12
12
  }
13
13
  async appInitialized(_bean: BeanContainer) {}
14
14
  async appReady(_bean: BeanContainer) {}
package/src/types.ts CHANGED
@@ -1,10 +1,23 @@
1
1
  import 'zova';
2
- import { DefaultError, useMutation, useQuery, useQueryClient } from '@tanstack/vue-query';
2
+ import {
3
+ DefaultError,
4
+ DehydratedState,
5
+ Query,
6
+ QueryKey,
7
+ StaleTime,
8
+ useMutation,
9
+ useQuery,
10
+ useQueryClient,
11
+ } from '@tanstack/vue-query';
3
12
  import { UnwrapNestedRefs } from 'vue';
4
13
  declare module 'zova' {
5
14
  export interface BeanBase {
6
15
  $queryClient: ReturnType<typeof useQueryClient>;
7
16
  }
17
+
18
+ export interface SSRContextState {
19
+ query: DehydratedState;
20
+ }
8
21
  }
9
22
 
10
23
  declare module '@tanstack/vue-query' {
@@ -29,13 +42,22 @@ export interface QueryMetaSSR {
29
42
 
30
43
  export type QueryMetaPersisterStorage = 'cookie' | 'local' | 'db' | undefined;
31
44
 
45
+ export type QueryMetaPersisterCookieType = 'auto' | 'boolean' | 'number' | 'date' | 'string' | undefined;
46
+
47
+ export type MaxAgeTime<
48
+ TQueryFnData = unknown,
49
+ TError = DefaultError,
50
+ TData = TQueryFnData,
51
+ TQueryKey extends QueryKey = QueryKey,
52
+ > = StaleTime<TQueryFnData, TError, TData, TQueryKey>;
53
+
32
54
  export interface QueryMetaPersister {
33
55
  /** default is false */
34
56
  sync?: boolean;
35
57
  /** default is db if async, local if sync */
36
58
  storage?: QueryMetaPersisterStorage;
37
59
  /** default is 24 hours */
38
- maxAge?: number;
60
+ maxAge?: MaxAgeTime;
39
61
  /**
40
62
  * How to serialize the data to storage.
41
63
  * @default `JSON.stringify`
@@ -48,6 +70,7 @@ export interface QueryMetaPersister {
48
70
  deserialize?: (cachedString: any) => any;
49
71
  prefix?: string;
50
72
  buster?: string;
73
+ cookieType?: QueryMetaPersisterCookieType;
51
74
  }
52
75
 
53
76
  export type DataQuery<TData> = UnwrapNestedRefs<ReturnType<typeof useQuery<TData | undefined, Error | null>>>;
@@ -55,3 +78,27 @@ export type DataQuery<TData> = UnwrapNestedRefs<ReturnType<typeof useQuery<TData
55
78
  export type DataMutation<TData = unknown, TVariables = void, TContext = unknown> = UnwrapNestedRefs<
56
79
  ReturnType<typeof useMutation<TData, DefaultError, TVariables, TContext>>
57
80
  >;
81
+
82
+ export function resolveStaleTime<
83
+ TQueryFnData = unknown,
84
+ TError = DefaultError,
85
+ TData = TQueryFnData,
86
+ TQueryKey extends QueryKey = QueryKey,
87
+ >(
88
+ staleTime: undefined | StaleTime<TQueryFnData, TError, TData, TQueryKey>,
89
+ query: Query<TQueryFnData, TError, TData, TQueryKey>,
90
+ ): number | undefined {
91
+ return typeof staleTime === 'function' ? staleTime(query) : staleTime;
92
+ }
93
+
94
+ export function resolveMaxAgeTime<
95
+ TQueryFnData = unknown,
96
+ TError = DefaultError,
97
+ TData = TQueryFnData,
98
+ TQueryKey extends QueryKey = QueryKey,
99
+ >(
100
+ maxAge: undefined | MaxAgeTime<TQueryFnData, TError, TData, TQueryKey>,
101
+ query: Query<TQueryFnData, TError, TData, TQueryKey>,
102
+ ): number | undefined {
103
+ return typeof maxAge === 'function' ? maxAge(query) : maxAge;
104
+ }
@@ -1,17 +0,0 @@
1
- import { BeanBase, Local } from 'zova';
2
- import { ScopeModule } from '../resource/this.js';
3
- import { VueQueryPlugin, VueQueryPluginOptions } from '@tanstack/vue-query';
4
-
5
- @Local()
6
- export class Storage extends BeanBase<ScopeModule> {
7
- protected async __init__() {
8
- const vueQueryPluginOptions: VueQueryPluginOptions = {
9
- queryClientConfig: {
10
- defaultOptions: this.scope.config.queryClientConfig.defaultOptions,
11
- },
12
- };
13
- this.app.vue.use(VueQueryPlugin, vueQueryPluginOptions);
14
- }
15
-
16
- protected __dispose__() {}
17
- }