@ramathibodi/nuxt-commons 4.0.5 → 4.0.6

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
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": "^4.3.1"
6
6
  },
7
- "version": "4.0.5",
7
+ "version": "4.0.6",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
@@ -106,15 +106,11 @@ watchEffect(() => {
106
106
  }
107
107
  }
108
108
  });
109
- watch(computedDateRules, () => {
109
+ watch([datePart, timePart, () => props.rules], () => {
110
110
  if (!props.readonly) nextTick(() => dateRef.value?.validate());
111
- }, {
112
- deep: true
113
111
  });
114
- watch(computedTimeRules, () => {
112
+ watch([datePart, timePart, () => props.rules], () => {
115
113
  if (!props.readonly) nextTick(() => timeRef.value?.validate());
116
- }, {
117
- deep: true
118
114
  });
119
115
  watch(() => props.modelValue, () => {
120
116
  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?: boolean | number) => Promise<T>;
10
- getPromise: <T>(url: string | string[], body?: Record<string, any> | [], params?: SearchParameters, options?: UseFetchOptions<unknown>, cache?: boolean | number) => Promise<T>;
11
- post: <T>(url: string | string[], body?: Record<string, any> | [], params?: SearchParameters, options?: UseFetchOptions<unknown>, cache?: boolean | number) => Promise<T>;
12
- postPromise: <T>(url: string | string[], body?: Record<string, any> | [], params?: SearchParameters, options?: UseFetchOptions<unknown>, cache?: boolean | number) => Promise<T>;
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 = computeTtlSecond(cache);
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?: boolean | number) => Promise<T>;
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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ramathibodi/nuxt-commons",
3
- "version": "4.0.5",
3
+ "version": "4.0.6",
4
4
  "description": "Ramathibodi Nuxt modules for common components",
5
5
  "repository": {
6
6
  "type": "git",