@ramathibodi/nuxt-commons 4.0.5 → 4.0.7
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/dist/module.json
CHANGED
|
@@ -106,15 +106,12 @@ watchEffect(() => {
|
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
});
|
|
109
|
-
|
|
109
|
+
const rulesLen = () => props.rules?.length ?? 0;
|
|
110
|
+
watch([datePart, timePart, rulesLen], () => {
|
|
110
111
|
if (!props.readonly) nextTick(() => dateRef.value?.validate());
|
|
111
|
-
}, {
|
|
112
|
-
deep: true
|
|
113
112
|
});
|
|
114
|
-
watch(
|
|
113
|
+
watch([datePart, timePart, rulesLen], () => {
|
|
115
114
|
if (!props.readonly) nextTick(() => timeRef.value?.validate());
|
|
116
|
-
}, {
|
|
117
|
-
deep: true
|
|
118
115
|
});
|
|
119
116
|
watch(() => props.modelValue, () => {
|
|
120
117
|
pauseEmit.value = true;
|
|
@@ -1,14 +1,70 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* useApi wraps authenticated HTTP calls, URL resolution, and optional response caching for runtime services.
|
|
3
|
+
*
|
|
4
|
+
* Caching contract for the `cache` parameter on get / getPromise / post / postPromise:
|
|
5
|
+
*
|
|
6
|
+
* - `false` (default) — no caching, every call hits the network.
|
|
7
|
+
* - `true` — cache the response for 5 minutes (legacy default; preserved
|
|
8
|
+
* for back-compat). Prefer `{ ttlSeconds }` for new code.
|
|
9
|
+
* - `{ ttlSeconds: 5 }` — cache for exactly 5 seconds. PREFERRED form.
|
|
10
|
+
* - `{ ttlMs: 5000 }` — same, expressed in milliseconds.
|
|
11
|
+
* - `number` (legacy) — DEPRECATED. Treated by an undocumented size-based
|
|
12
|
+
* heuristic where values <= 60 are read as MINUTES and
|
|
13
|
+
* values > 60 as SECONDS. Emits a console warning the
|
|
14
|
+
* first time a session sees this form. Migrate to
|
|
15
|
+
* `{ ttlSeconds }` / `{ ttlMs }` to remove the warning
|
|
16
|
+
* and get unambiguous behaviour.
|
|
17
|
+
*
|
|
18
|
+
* Mutations that change resources read by a cached query should call
|
|
19
|
+
* `useApi().invalidate(prefix)` so the next read fetches fresh data. Without that,
|
|
20
|
+
* cached entries stay valid until their TTL expires — `localStorage` survives reloads,
|
|
21
|
+
* so users can see stale empty / outdated lists for the full TTL window even after
|
|
22
|
+
* creating data themselves.
|
|
23
|
+
*
|
|
3
24
|
* This doc block is consumed by vue-docgen for generated API documentation.
|
|
4
25
|
*/
|
|
5
26
|
import type { UseFetchOptions } from 'nuxt/app';
|
|
6
27
|
import type { SearchParameters } from 'ofetch';
|
|
28
|
+
/**
|
|
29
|
+
* Explicit cache options. Use this in new code instead of passing a bare number.
|
|
30
|
+
* - `ttlSeconds`: cache lifetime in seconds. Mutually exclusive with `ttlMs`.
|
|
31
|
+
* - `ttlMs` : cache lifetime in milliseconds. Mutually exclusive with `ttlSeconds`.
|
|
32
|
+
*/
|
|
33
|
+
export type CacheOption = boolean | number | {
|
|
34
|
+
ttlSeconds: number;
|
|
35
|
+
} | {
|
|
36
|
+
ttlMs: number;
|
|
37
|
+
};
|
|
38
|
+
export declare function _resetLegacyHeuristicWarning(): void;
|
|
39
|
+
/**
|
|
40
|
+
* Resolve a cache option to seconds. Pure function, exported for tests.
|
|
41
|
+
*
|
|
42
|
+
* - false / 0 / null / undefined → 0 (no cache)
|
|
43
|
+
* - true → DEFAULT_TTL_SECONDS_FOR_TRUE
|
|
44
|
+
* - { ttlSeconds: n } → n (clamped to >= 0, floored)
|
|
45
|
+
* - { ttlMs: n } → ceil(n / 1000)
|
|
46
|
+
* - number (legacy) → heuristic + warn
|
|
47
|
+
*/
|
|
48
|
+
export declare function resolveCacheTtlSeconds(cache: CacheOption | undefined | null): number;
|
|
49
|
+
/**
|
|
50
|
+
* Drop every `api-cache-*` localStorage entry whose stored URL contains `prefix`.
|
|
51
|
+
* Pass `undefined` (or no arg) to drop ALL api-cache entries.
|
|
52
|
+
*
|
|
53
|
+
* Recommended invalidation path after a mutation:
|
|
54
|
+
*
|
|
55
|
+
* await useApi().postPromise('/model/resource/create', input)
|
|
56
|
+
* useApi().invalidate('/model/resource') // drops list + lookup caches
|
|
57
|
+
*
|
|
58
|
+
* Returns the number of entries removed. Safe to call when the cache is empty.
|
|
59
|
+
*/
|
|
60
|
+
declare function invalidateCache(prefix?: string): number;
|
|
7
61
|
export declare function useApi(): {
|
|
8
62
|
urlBuilder: (url: string | string[]) => string;
|
|
9
|
-
get: <T>(url: string | string[], body?: Record<string, any> | [], params?: SearchParameters, options?: UseFetchOptions<unknown>, cache?:
|
|
10
|
-
getPromise: <T>(url: string | string[], body?: Record<string, any> | [], params?: SearchParameters, options?: UseFetchOptions<unknown>, cache?:
|
|
11
|
-
post: <T>(url: string | string[], body?: Record<string, any> | [], params?: SearchParameters, options?: UseFetchOptions<unknown>, cache?:
|
|
12
|
-
postPromise: <T>(url: string | string[], body?: Record<string, any> | [], params?: SearchParameters, options?: UseFetchOptions<unknown>, cache?:
|
|
63
|
+
get: <T>(url: string | string[], body?: Record<string, any> | [], params?: SearchParameters, options?: UseFetchOptions<unknown>, cache?: CacheOption) => Promise<T>;
|
|
64
|
+
getPromise: <T>(url: string | string[], body?: Record<string, any> | [], params?: SearchParameters, options?: UseFetchOptions<unknown>, cache?: CacheOption) => Promise<T>;
|
|
65
|
+
post: <T>(url: string | string[], body?: Record<string, any> | [], params?: SearchParameters, options?: UseFetchOptions<unknown>, cache?: CacheOption) => Promise<T>;
|
|
66
|
+
postPromise: <T>(url: string | string[], body?: Record<string, any> | [], params?: SearchParameters, options?: UseFetchOptions<unknown>, cache?: CacheOption) => Promise<T>;
|
|
13
67
|
hashKey: (data: any) => Promise<string>;
|
|
68
|
+
invalidate: typeof invalidateCache;
|
|
14
69
|
};
|
|
70
|
+
export {};
|
|
@@ -5,9 +5,65 @@ import { stableStringify } from "../utils/object.js";
|
|
|
5
5
|
import { sha256 } from "../utils/hash.js";
|
|
6
6
|
import { useRuntimeConfig } from "#imports";
|
|
7
7
|
import { useAuthentication } from "../bridges/authentication.js";
|
|
8
|
+
const DEFAULT_TTL_SECONDS_FOR_TRUE = 5 * 60;
|
|
9
|
+
const CACHE_PREFIX = "api-cache-";
|
|
10
|
+
let _legacyHeuristicWarned = false;
|
|
11
|
+
function warnLegacyHeuristic(value, resolvedSeconds) {
|
|
12
|
+
if (_legacyHeuristicWarned) return;
|
|
13
|
+
_legacyHeuristicWarned = true;
|
|
14
|
+
console.warn(
|
|
15
|
+
`[useApi] cache=${value} resolved to ${resolvedSeconds}s via the legacy heuristic (values <=60 are minutes, >60 are seconds). This is ambiguous \u2014 pass { ttlSeconds: N } or { ttlMs: N } instead. See useApi() docblock for details.`
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
export function _resetLegacyHeuristicWarning() {
|
|
19
|
+
_legacyHeuristicWarned = false;
|
|
20
|
+
}
|
|
21
|
+
export function resolveCacheTtlSeconds(cache) {
|
|
22
|
+
if (!cache) return 0;
|
|
23
|
+
if (typeof cache === "boolean") return DEFAULT_TTL_SECONDS_FOR_TRUE;
|
|
24
|
+
if (typeof cache === "number") {
|
|
25
|
+
const n = Math.max(0, Number(cache));
|
|
26
|
+
if (!Number.isFinite(n) || n === 0) return 0;
|
|
27
|
+
const resolved = n > 60 ? n : n * 60;
|
|
28
|
+
warnLegacyHeuristic(n, resolved);
|
|
29
|
+
return resolved;
|
|
30
|
+
}
|
|
31
|
+
if (typeof cache === "object") {
|
|
32
|
+
if ("ttlSeconds" in cache) return Math.max(0, Math.floor(Number(cache.ttlSeconds) || 0));
|
|
33
|
+
if ("ttlMs" in cache) return Math.max(0, Math.ceil((Number(cache.ttlMs) || 0) / 1e3));
|
|
34
|
+
}
|
|
35
|
+
return 0;
|
|
36
|
+
}
|
|
37
|
+
function invalidateCache(prefix) {
|
|
38
|
+
if (typeof window === "undefined" || !window.localStorage) return 0;
|
|
39
|
+
const keys = [];
|
|
40
|
+
for (let i = 0; i < window.localStorage.length; i++) {
|
|
41
|
+
const k = window.localStorage.key(i);
|
|
42
|
+
if (k && k.startsWith(CACHE_PREFIX)) keys.push(k);
|
|
43
|
+
}
|
|
44
|
+
let removed = 0;
|
|
45
|
+
for (const k of keys) {
|
|
46
|
+
if (!prefix) {
|
|
47
|
+
try {
|
|
48
|
+
ls.remove(k);
|
|
49
|
+
removed++;
|
|
50
|
+
} catch {
|
|
51
|
+
}
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
const raw = window.localStorage.getItem(k);
|
|
56
|
+
if (raw && raw.includes(prefix)) {
|
|
57
|
+
ls.remove(k);
|
|
58
|
+
removed++;
|
|
59
|
+
}
|
|
60
|
+
} catch {
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return removed;
|
|
64
|
+
}
|
|
8
65
|
export function useApi() {
|
|
9
66
|
const config = useRuntimeConfig();
|
|
10
|
-
const CACHE_PREFIX = "api-cache-";
|
|
11
67
|
function urlBuilder(url) {
|
|
12
68
|
let returnUrl;
|
|
13
69
|
if (Array.isArray(url)) {
|
|
@@ -64,12 +120,6 @@ export function useApi() {
|
|
|
64
120
|
const jsonString = stableStringify(data);
|
|
65
121
|
return sha256(jsonString);
|
|
66
122
|
}
|
|
67
|
-
function computeTtlSecond(cache) {
|
|
68
|
-
if (!cache) return 0;
|
|
69
|
-
if (typeof cache === "boolean") return 5 * 60;
|
|
70
|
-
let ttlNum = Math.max(0, Number(cache));
|
|
71
|
-
return ttlNum > 60 ? ttlNum : ttlNum * 60;
|
|
72
|
-
}
|
|
73
123
|
function safeGetCache(key) {
|
|
74
124
|
try {
|
|
75
125
|
const v = ls.get(key);
|
|
@@ -80,7 +130,7 @@ export function useApi() {
|
|
|
80
130
|
}
|
|
81
131
|
async function ofetchWithCache(url, method, body, params, options, cache) {
|
|
82
132
|
const builtUrl = urlBuilder(url);
|
|
83
|
-
const ttl =
|
|
133
|
+
const ttl = resolveCacheTtlSeconds(cache);
|
|
84
134
|
if (ttl === 0) {
|
|
85
135
|
return ofetch(builtUrl, optionBuilder(method, body, params, options));
|
|
86
136
|
}
|
|
@@ -94,5 +144,5 @@ export function useApi() {
|
|
|
94
144
|
ls.set(key, result, { ttl });
|
|
95
145
|
return result;
|
|
96
146
|
}
|
|
97
|
-
return { urlBuilder, get, getPromise, post, postPromise, hashKey };
|
|
147
|
+
return { urlBuilder, get, getPromise, post, postPromise, hashKey, invalidate: invalidateCache };
|
|
98
148
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type UseMutationReturn } from '@vue/apollo-composable';
|
|
2
2
|
import { type ClassConstructor } from '../utils/object.js';
|
|
3
|
+
import { type CacheOption } from './api.js';
|
|
3
4
|
declare type VariableOptions = {
|
|
4
5
|
type?: string;
|
|
5
6
|
name?: string;
|
|
@@ -11,7 +12,7 @@ declare type VariableOptions = {
|
|
|
11
12
|
};
|
|
12
13
|
export declare function useGraphQl(): {
|
|
13
14
|
query: <T>(operation: string, fields: Array<string | Object> | ClassConstructor<any>, variables?: VariableOptions, cache?: boolean) => import("@vue/apollo-composable").UseQueryReturn<T, Record<string, never>>;
|
|
14
|
-
queryPromise: <T>(operation: string, fields: Array<string | Object> | ClassConstructor<any>, variables?: VariableOptions, cache?:
|
|
15
|
+
queryPromise: <T>(operation: string, fields: Array<string | Object> | ClassConstructor<any>, variables?: VariableOptions, cache?: CacheOption) => Promise<T>;
|
|
15
16
|
mutation: <T>(operation: string, fields: Array<string | Object> | ClassConstructor<any>, variables?: VariableOptions) => UseMutationReturn<any, any>;
|
|
16
17
|
mutationPromise: <T>(operation: string, fields: Array<string | Object> | ClassConstructor<any>, variables?: VariableOptions) => Promise<T>;
|
|
17
18
|
};
|