@lacqjs/nuxt-dict 0.0.8 → 0.0.9
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 +1 -1
- package/dist/module.mjs +1 -1
- package/dist/runtime/composables/useDict.js +3 -0
- package/dist/runtime/core/cache/indexeddb-cache.d.ts +51 -28
- package/dist/runtime/core/cache/indexeddb-cache.js +84 -119
- package/dist/runtime/core/define-adapter.d.ts +1 -2
- package/dist/runtime/core/dict-manager.d.ts +7 -3
- package/dist/runtime/core/dict-manager.js +29 -5
- package/dist/runtime/options.js +2 -1
- package/dist/runtime/plugins/dict.js +24 -9
- package/dist/runtime/types/index.d.ts +5 -0
- package/package.json +3 -2
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -7,7 +7,7 @@ export { createDictTranslator } from '../dist/runtime/utils/dict-translator.js';
|
|
|
7
7
|
export { defineDictAdapter } from '../dist/runtime/core/define-adapter.js';
|
|
8
8
|
|
|
9
9
|
const name = "@lacqjs/nuxt-dict";
|
|
10
|
-
const version = "0.0.
|
|
10
|
+
const version = "0.0.9";
|
|
11
11
|
const devDependencies = {
|
|
12
12
|
nuxt: "^4.4.8"};
|
|
13
13
|
const pkg = {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { shallowRef, ref, watch, onMounted, onBeforeUnmount, triggerRef } from "vue";
|
|
2
2
|
import { useNuxtApp } from "#imports";
|
|
3
3
|
import { DEFAULT_STORE_NAME } from "../core/cache/indexeddb-cache.js";
|
|
4
|
+
import { createLogger } from "../utils/logger.js";
|
|
5
|
+
const logger = createLogger("nuxt-dict");
|
|
4
6
|
const activeInstances = /* @__PURE__ */ new Map();
|
|
5
7
|
async function fetchDictData(manager, dictType, storeName, data, loading, error, mode = "load", itemMap) {
|
|
6
8
|
loading.value = true;
|
|
@@ -20,6 +22,7 @@ async function fetchDictData(manager, dictType, storeName, data, loading, error,
|
|
|
20
22
|
}
|
|
21
23
|
}
|
|
22
24
|
} catch (e) {
|
|
25
|
+
logger.debug(`fetchDictData ERROR store=${storeName} type=${dictType} mode=${mode}`, e);
|
|
23
26
|
error.value = e instanceof Error ? e.message : String(e);
|
|
24
27
|
} finally {
|
|
25
28
|
loading.value = false;
|
|
@@ -2,47 +2,70 @@ import type { DictEntry, CacheEntry } from '../../types/index.js';
|
|
|
2
2
|
/** 默认 IndexedDB 对象存储库名称 */
|
|
3
3
|
export declare const DEFAULT_STORE_NAME = "dicts";
|
|
4
4
|
/**
|
|
5
|
-
* IndexedDB
|
|
6
|
-
* 支持客户端离线访问,减少重复网络请求。
|
|
7
|
-
* 版本号改用 localStorage 存储,不再使用 IndexedDB。
|
|
5
|
+
* 基于 Dexie.js 的 IndexedDB 缓存实现(多表方案)。
|
|
8
6
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
7
|
+
* @description 每个字典仓库对应一个独立的 Dexie table(IndexedDB object store),
|
|
8
|
+
* 通过 init(storeNames) 在初始化时一次性声明所有表的 schema,
|
|
9
|
+
* 避免原生 IndexedDB 多次版本升级导致的竞态问题。
|
|
10
|
+
*
|
|
11
|
+
* 数据隔离:各仓库数据物理隔离,在 DevTools Application 面板中独立可见。
|
|
12
|
+
* Schema 管理:使用仓库数量作为版本号,增删仓库时自动升级。
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* const cache = new IndexedDBCache('nuxt-dict')
|
|
16
|
+
* await cache.init(['dicts', 'dicts2', 'dicts3'])
|
|
17
|
+
* await cache.set('dicts', 'gender', 'zh-CN', entry)
|
|
18
|
+
* const result = await cache.get('dicts', 'gender', 'zh-CN')
|
|
13
19
|
*/
|
|
14
20
|
export declare class IndexedDBCache {
|
|
15
|
-
private dbName;
|
|
16
21
|
private db;
|
|
17
|
-
private
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
private pendingStores;
|
|
22
|
+
private dbName;
|
|
23
|
+
/**
|
|
24
|
+
* @param {string} dbName - IndexedDB 数据库名称
|
|
25
|
+
*/
|
|
22
26
|
constructor(dbName: string);
|
|
23
|
-
/** 初始化 IndexedDB,创建默认对象存储库 */
|
|
24
|
-
init(): Promise<void>;
|
|
25
27
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
28
|
+
* 初始化数据库,一次性声明所有仓库对应的 object store。
|
|
29
|
+
*
|
|
30
|
+
* @description 根据传入的仓库名列表声明 Dexie schema,每个仓库一个独立表。
|
|
31
|
+
* 使用仓库数量作为版本号,增减仓库时自动触发 Dexie 的版本升级。
|
|
32
|
+
* 若版本降级(删了仓库)导致 VersionError,自动删除旧库重建。
|
|
33
|
+
*
|
|
34
|
+
* @param {string[]} storeNames - 所有仓库名列表,如 ['dicts', 'dicts2', 'dicts3']
|
|
35
|
+
* @returns {Promise<void>}
|
|
29
36
|
*/
|
|
30
|
-
|
|
37
|
+
init(storeNames: string[]): Promise<void>;
|
|
31
38
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
39
|
+
* 生成缓存键。
|
|
40
|
+
* @param {string} dictType - 字典类型,如 'gender'
|
|
41
|
+
* @param {string} locale - 语言标识,如 'zh-CN'
|
|
42
|
+
* @returns {string} 格式 `${dictType}_${locale}`
|
|
35
43
|
*/
|
|
36
|
-
private doUpgrade;
|
|
37
|
-
/** 生成存储键名:`{dictType}_{locale}` */
|
|
38
44
|
private getStoreKey;
|
|
39
|
-
/**
|
|
45
|
+
/**
|
|
46
|
+
* 从指定仓库读取字典缓存。
|
|
47
|
+
*
|
|
48
|
+
* @param {string} storeName - 仓库名,如 'dicts'
|
|
49
|
+
* @param {string} dictType - 字典类型,如 'gender'
|
|
50
|
+
* @param {string} locale - 语言标识,如 'zh-CN'
|
|
51
|
+
* @returns {Promise<CacheEntry<DictEntry> | null>} 缓存条目,未命中返回 null
|
|
52
|
+
*/
|
|
40
53
|
get(storeName: string, dictType: string, locale: string): Promise<CacheEntry<DictEntry> | null>;
|
|
41
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* 写入缓存条目到指定仓库。
|
|
56
|
+
*
|
|
57
|
+
* @param {string} storeName - 仓库名
|
|
58
|
+
* @param {string} dictType - 字典类型
|
|
59
|
+
* @param {string} locale - 语言标识
|
|
60
|
+
* @param {CacheEntry<DictEntry>} entry - 缓存条目
|
|
61
|
+
* @returns {Promise<void>}
|
|
62
|
+
*/
|
|
42
63
|
set(storeName: string, dictType: string, locale: string, entry: CacheEntry<DictEntry>): Promise<void>;
|
|
43
64
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
65
|
+
* 清空缓存数据。
|
|
66
|
+
*
|
|
67
|
+
* @param {string} [storeName] - 指定仓库名则只清该仓库,不传则清空全部
|
|
68
|
+
* @returns {Promise<void>}
|
|
46
69
|
*/
|
|
47
70
|
clear(storeName?: string): Promise<void>;
|
|
48
71
|
}
|
|
@@ -1,144 +1,109 @@
|
|
|
1
|
+
import Dexie from "dexie";
|
|
2
|
+
import { createLogger } from "../../utils/logger.js";
|
|
1
3
|
export const DEFAULT_STORE_NAME = "dicts";
|
|
4
|
+
const logger = createLogger("nuxt-dict");
|
|
2
5
|
export class IndexedDBCache {
|
|
6
|
+
db;
|
|
3
7
|
dbName;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
upgradeQueue = Promise.resolve();
|
|
8
|
-
/** 累积待创建的 store 名,批量创建以减少升级次数 */
|
|
9
|
-
pendingStores = /* @__PURE__ */ new Set();
|
|
8
|
+
/**
|
|
9
|
+
* @param {string} dbName - IndexedDB 数据库名称
|
|
10
|
+
*/
|
|
10
11
|
constructor(dbName) {
|
|
11
12
|
this.dbName = dbName;
|
|
12
|
-
|
|
13
|
-
/** 初始化 IndexedDB,创建默认对象存储库 */
|
|
14
|
-
init() {
|
|
15
|
-
if (this.db) return Promise.resolve();
|
|
16
|
-
return new Promise((resolve, reject) => {
|
|
17
|
-
const request = indexedDB.open(this.dbName);
|
|
18
|
-
request.addEventListener("upgradeneeded", () => {
|
|
19
|
-
const db = request.result;
|
|
20
|
-
if (!db.objectStoreNames.contains(DEFAULT_STORE_NAME)) {
|
|
21
|
-
db.createObjectStore(DEFAULT_STORE_NAME);
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
request.addEventListener("success", () => {
|
|
25
|
-
this.db = request.result;
|
|
26
|
-
this.version = this.db.version;
|
|
27
|
-
resolve();
|
|
28
|
-
});
|
|
29
|
-
request.addEventListener("error", () => {
|
|
30
|
-
reject(
|
|
31
|
-
new Error("Failed to open IndexedDB: " + (request.error?.message || "unknown error"))
|
|
32
|
-
);
|
|
33
|
-
});
|
|
34
|
-
request.addEventListener("blocked", () => {
|
|
35
|
-
reject(new Error("IndexedDB is blocked by another tab"));
|
|
36
|
-
});
|
|
37
|
-
});
|
|
13
|
+
this.db = new Dexie(dbName);
|
|
38
14
|
}
|
|
39
15
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
16
|
+
* 初始化数据库,一次性声明所有仓库对应的 object store。
|
|
17
|
+
*
|
|
18
|
+
* @description 根据传入的仓库名列表声明 Dexie schema,每个仓库一个独立表。
|
|
19
|
+
* 使用仓库数量作为版本号,增减仓库时自动触发 Dexie 的版本升级。
|
|
20
|
+
* 若版本降级(删了仓库)导致 VersionError,自动删除旧库重建。
|
|
21
|
+
*
|
|
22
|
+
* @param {string[]} storeNames - 所有仓库名列表,如 ['dicts', 'dicts2', 'dicts3']
|
|
23
|
+
* @returns {Promise<void>}
|
|
43
24
|
*/
|
|
44
|
-
async
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
25
|
+
async init(storeNames) {
|
|
26
|
+
const schema = {};
|
|
27
|
+
for (const name of storeNames) {
|
|
28
|
+
schema[name] = "";
|
|
29
|
+
}
|
|
30
|
+
const version = Math.max(storeNames.length, 1);
|
|
31
|
+
this.db.version(version).stores(schema);
|
|
32
|
+
logger.debug(`IndexedDB init: opening "${this.dbName}", version=${version}, stores=[${storeNames.join(", ")}]`);
|
|
33
|
+
try {
|
|
34
|
+
await this.db.open();
|
|
35
|
+
} catch (e) {
|
|
36
|
+
if (e instanceof Error && e.name === "VersionError") {
|
|
37
|
+
logger.debug(`IndexedDB init: VersionError, deleting old database and recreating`);
|
|
38
|
+
this.db.close();
|
|
39
|
+
await Dexie.delete(this.dbName);
|
|
40
|
+
this.db = new Dexie(this.dbName);
|
|
41
|
+
this.db.version(version).stores(schema);
|
|
42
|
+
await this.db.open();
|
|
43
|
+
} else {
|
|
44
|
+
throw e;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
logger.debug(`IndexedDB init: complete, tables=[${this.db.tables.map((t) => t.name).join(", ")}]`);
|
|
50
48
|
}
|
|
51
49
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
50
|
+
* 生成缓存键。
|
|
51
|
+
* @param {string} dictType - 字典类型,如 'gender'
|
|
52
|
+
* @param {string} locale - 语言标识,如 'zh-CN'
|
|
53
|
+
* @returns {string} 格式 `${dictType}_${locale}`
|
|
55
54
|
*/
|
|
56
|
-
async doUpgrade() {
|
|
57
|
-
if (this.pendingStores.size === 0) return;
|
|
58
|
-
const stores = new Set(this.pendingStores);
|
|
59
|
-
this.pendingStores.clear();
|
|
60
|
-
const toCreate = [...stores].filter((s) => !this.db.objectStoreNames.contains(s));
|
|
61
|
-
if (toCreate.length === 0) return;
|
|
62
|
-
this.db.onversionchange = () => {
|
|
63
|
-
this.db.close();
|
|
64
|
-
};
|
|
65
|
-
this.db.close();
|
|
66
|
-
this.version++;
|
|
67
|
-
await new Promise((resolve, reject) => {
|
|
68
|
-
const req = indexedDB.open(this.dbName, this.version);
|
|
69
|
-
req.addEventListener("upgradeneeded", () => {
|
|
70
|
-
const db = req.result;
|
|
71
|
-
for (const name of toCreate) {
|
|
72
|
-
if (!db.objectStoreNames.contains(name)) {
|
|
73
|
-
db.createObjectStore(name);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
req.addEventListener("success", () => {
|
|
78
|
-
this.db = req.result;
|
|
79
|
-
resolve();
|
|
80
|
-
});
|
|
81
|
-
req.addEventListener("error", () => reject(req.error));
|
|
82
|
-
req.addEventListener("blocked", () => {
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
/** 生成存储键名:`{dictType}_{locale}` */
|
|
87
55
|
getStoreKey(dictType, locale) {
|
|
88
56
|
return `${dictType}_${locale}`;
|
|
89
57
|
}
|
|
90
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* 从指定仓库读取字典缓存。
|
|
60
|
+
*
|
|
61
|
+
* @param {string} storeName - 仓库名,如 'dicts'
|
|
62
|
+
* @param {string} dictType - 字典类型,如 'gender'
|
|
63
|
+
* @param {string} locale - 语言标识,如 'zh-CN'
|
|
64
|
+
* @returns {Promise<CacheEntry<DictEntry> | null>} 缓存条目,未命中返回 null
|
|
65
|
+
*/
|
|
91
66
|
async get(storeName, dictType, locale) {
|
|
92
|
-
if (!this.db) return null;
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const request = store.get(this.getStoreKey(dictType, locale));
|
|
99
|
-
request.addEventListener("success", () => resolve(request.result ?? null));
|
|
100
|
-
request.addEventListener("error", () => reject(request.error));
|
|
101
|
-
});
|
|
67
|
+
if (!this.db.isOpen()) return null;
|
|
68
|
+
if (!this.db.tables.some((t) => t.name === storeName)) return null;
|
|
69
|
+
const key = this.getStoreKey(dictType, locale);
|
|
70
|
+
const record = await this.db.table(storeName).get(key);
|
|
71
|
+
logger.debug(`IndexedDB get: store=${storeName} key=${key} hit=${!!record}`);
|
|
72
|
+
return record ?? null;
|
|
102
73
|
}
|
|
103
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* 写入缓存条目到指定仓库。
|
|
76
|
+
*
|
|
77
|
+
* @param {string} storeName - 仓库名
|
|
78
|
+
* @param {string} dictType - 字典类型
|
|
79
|
+
* @param {string} locale - 语言标识
|
|
80
|
+
* @param {CacheEntry<DictEntry>} entry - 缓存条目
|
|
81
|
+
* @returns {Promise<void>}
|
|
82
|
+
*/
|
|
104
83
|
async set(storeName, dictType, locale, entry) {
|
|
105
|
-
if (!this.db) return;
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const store = tx.objectStore(storeName);
|
|
111
|
-
const request = store.put(entry, this.getStoreKey(dictType, locale));
|
|
112
|
-
request.addEventListener("success", () => resolve());
|
|
113
|
-
request.addEventListener("error", () => reject(request.error));
|
|
114
|
-
});
|
|
84
|
+
if (!this.db.isOpen()) return;
|
|
85
|
+
if (!this.db.tables.some((t) => t.name === storeName)) return;
|
|
86
|
+
const key = this.getStoreKey(dictType, locale);
|
|
87
|
+
logger.debug(`IndexedDB set: store=${storeName} key=${key}`);
|
|
88
|
+
await this.db.table(storeName).put(entry, key);
|
|
115
89
|
}
|
|
116
90
|
/**
|
|
117
|
-
*
|
|
118
|
-
*
|
|
91
|
+
* 清空缓存数据。
|
|
92
|
+
*
|
|
93
|
+
* @param {string} [storeName] - 指定仓库名则只清该仓库,不传则清空全部
|
|
94
|
+
* @returns {Promise<void>}
|
|
119
95
|
*/
|
|
120
96
|
async clear(storeName) {
|
|
121
|
-
if (!this.db) return;
|
|
97
|
+
if (!this.db.isOpen()) return;
|
|
122
98
|
if (storeName) {
|
|
123
|
-
if (!this.db.
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
const storeNames = Array.from(this.db.objectStoreNames);
|
|
134
|
-
for (const name of storeNames) {
|
|
135
|
-
await new Promise((resolve, reject) => {
|
|
136
|
-
const tx = this.db.transaction(name, "readwrite");
|
|
137
|
-
const store = tx.objectStore(name);
|
|
138
|
-
const request = store.clear();
|
|
139
|
-
request.addEventListener("success", () => resolve());
|
|
140
|
-
request.addEventListener("error", () => reject(request.error));
|
|
141
|
-
});
|
|
99
|
+
if (!this.db.tables.some((t) => t.name === storeName)) return;
|
|
100
|
+
logger.debug(`IndexedDB clear: store=${storeName}`);
|
|
101
|
+
await this.db.table(storeName).clear();
|
|
102
|
+
} else {
|
|
103
|
+
logger.debug("IndexedDB clear: ALL");
|
|
104
|
+
for (const table of this.db.tables) {
|
|
105
|
+
await table.clear();
|
|
106
|
+
}
|
|
142
107
|
}
|
|
143
108
|
}
|
|
144
109
|
}
|
|
@@ -9,8 +9,7 @@ import type { DictAdapter } from '../types/index.js';
|
|
|
9
9
|
* @returns {DictAdapter} 原样返回传入的适配器对象
|
|
10
10
|
*
|
|
11
11
|
* @example
|
|
12
|
-
* // ~/dict/dict-adapter.ts
|
|
13
|
-
* import { defineDictAdapter } from '@lacqjs/nuxt-dict'
|
|
12
|
+
* // ~/dict/dict-adapter.ts — defineDictAdapter 已自动导入,无需 import
|
|
14
13
|
*
|
|
15
14
|
* export default defineDictAdapter({
|
|
16
15
|
* async fetchDict(storeName, { types, locale }) {
|
|
@@ -20,6 +20,8 @@ export interface DictManagerOptions {
|
|
|
20
20
|
ttl: number;
|
|
21
21
|
/** localStorage 中存储版本号的 key */
|
|
22
22
|
versionStorageKey: string;
|
|
23
|
+
/** 配置为惰性版本检查的仓库集合,不在此集合中的仓库由 initialize() 立即检查 */
|
|
24
|
+
lazyStores: Set<string>;
|
|
23
25
|
}
|
|
24
26
|
/**
|
|
25
27
|
* 字典管理器 —— 核心调度层。
|
|
@@ -46,6 +48,8 @@ export declare class DictManager {
|
|
|
46
48
|
private checkedStores;
|
|
47
49
|
/** 正在进行的版本检查请求,用于去重 */
|
|
48
50
|
private pendingVersionChecks;
|
|
51
|
+
/** 配置为惰性版本检查的仓库集合 */
|
|
52
|
+
private lazyStores;
|
|
49
53
|
locale: import("vue").ShallowRef<string, string>;
|
|
50
54
|
constructor(options: DictManagerOptions);
|
|
51
55
|
/** 构建带存储库命名空间和语言后缀的缓存键 */
|
|
@@ -66,7 +70,7 @@ export declare class DictManager {
|
|
|
66
70
|
*/
|
|
67
71
|
getLocale(): string;
|
|
68
72
|
/**
|
|
69
|
-
*
|
|
73
|
+
* 惰性版本检查(仅 lazy 仓库使用):首次访问该仓库时检查版本变更,按需清理缓存。
|
|
70
74
|
* 并发调用去重 —— 同一仓库的多个 getDict 共享单次版本检查。
|
|
71
75
|
*/
|
|
72
76
|
private ensureVersionChecked;
|
|
@@ -126,8 +130,8 @@ export declare class DictManager {
|
|
|
126
130
|
*/
|
|
127
131
|
private codeMatch;
|
|
128
132
|
/**
|
|
129
|
-
*
|
|
130
|
-
*
|
|
133
|
+
* 初始化管理器:对非 lazy 仓库立即并行检查版本号。
|
|
134
|
+
* lazy 仓库的版本检查延迟到首次 getDict() 调用时惰性触发。
|
|
131
135
|
*
|
|
132
136
|
* @returns {Promise<void>}
|
|
133
137
|
*/
|
|
@@ -2,6 +2,8 @@ import { shallowRef } from "vue";
|
|
|
2
2
|
import { DEFAULT_STORE_NAME } from "./cache/indexeddb-cache.js";
|
|
3
3
|
import { MemoryCache } from "./cache/memory-cache.js";
|
|
4
4
|
import { VersionCheck } from "./cache/version-check.js";
|
|
5
|
+
import { createLogger } from "../utils/logger.js";
|
|
6
|
+
const logger = createLogger("nuxt-dict");
|
|
5
7
|
export class DictManager {
|
|
6
8
|
memoryCache;
|
|
7
9
|
indexedDB;
|
|
@@ -14,12 +16,15 @@ export class DictManager {
|
|
|
14
16
|
checkedStores = /* @__PURE__ */ new Set();
|
|
15
17
|
/** 正在进行的版本检查请求,用于去重 */
|
|
16
18
|
pendingVersionChecks = /* @__PURE__ */ new Map();
|
|
19
|
+
/** 配置为惰性版本检查的仓库集合 */
|
|
20
|
+
lazyStores;
|
|
17
21
|
locale = shallowRef("zh-CN");
|
|
18
22
|
constructor(options) {
|
|
19
23
|
this.memoryCache = new MemoryCache(options.memoryMax, options.ttl);
|
|
20
24
|
this.indexedDB = options.indexedDB;
|
|
21
25
|
this.adapters = options.adapters;
|
|
22
26
|
this.pendingRequests = /* @__PURE__ */ new Map();
|
|
27
|
+
this.lazyStores = options.lazyStores;
|
|
23
28
|
this.versionChecks = /* @__PURE__ */ new Map();
|
|
24
29
|
for (const [storeName, adapter] of options.adapters) {
|
|
25
30
|
const storageKey = storeName === DEFAULT_STORE_NAME ? options.versionStorageKey : `${options.versionStorageKey}__${storeName}`;
|
|
@@ -56,7 +61,7 @@ export class DictManager {
|
|
|
56
61
|
return this.locale.value;
|
|
57
62
|
}
|
|
58
63
|
/**
|
|
59
|
-
*
|
|
64
|
+
* 惰性版本检查(仅 lazy 仓库使用):首次访问该仓库时检查版本变更,按需清理缓存。
|
|
60
65
|
* 并发调用去重 —— 同一仓库的多个 getDict 共享单次版本检查。
|
|
61
66
|
*/
|
|
62
67
|
async ensureVersionChecked(storeName) {
|
|
@@ -79,9 +84,9 @@ export class DictManager {
|
|
|
79
84
|
}
|
|
80
85
|
})() : Promise.resolve();
|
|
81
86
|
this.pendingVersionChecks.set(storeName, promise);
|
|
82
|
-
this.checkedStores.add(storeName);
|
|
83
87
|
try {
|
|
84
88
|
await promise;
|
|
89
|
+
this.checkedStores.add(storeName);
|
|
85
90
|
} finally {
|
|
86
91
|
this.pendingVersionChecks.delete(storeName);
|
|
87
92
|
}
|
|
@@ -95,7 +100,10 @@ export class DictManager {
|
|
|
95
100
|
* @returns {Promise<DictEntry>} 包含 items 和可选 tree 的字典条目
|
|
96
101
|
*/
|
|
97
102
|
async getDict(type, storeName = DEFAULT_STORE_NAME) {
|
|
98
|
-
|
|
103
|
+
logger.debug(`getDict START store=${storeName} type=${type} lazy=${this.lazyStores.has(storeName)}`);
|
|
104
|
+
if (this.lazyStores.has(storeName)) {
|
|
105
|
+
await this.ensureVersionChecked(storeName);
|
|
106
|
+
}
|
|
99
107
|
const key = this.buildKey(type, storeName);
|
|
100
108
|
const memoryEntry = this.memoryCache.get(key);
|
|
101
109
|
if (memoryEntry) {
|
|
@@ -115,7 +123,9 @@ export class DictManager {
|
|
|
115
123
|
}
|
|
116
124
|
/** 执行实际的数据获取与缓存写入 */
|
|
117
125
|
async fetchAndCache(type, storeName, key) {
|
|
126
|
+
logger.debug(`fetchAndCache START store=${storeName} type=${type}`);
|
|
118
127
|
const idbEntry = await this.indexedDB.get(storeName, type, this.locale.value);
|
|
128
|
+
logger.debug(`fetchAndCache IndexedDB result store=${storeName} type=${type} hit=${!!idbEntry}`);
|
|
119
129
|
if (idbEntry) {
|
|
120
130
|
this.memoryCache.set(key, {
|
|
121
131
|
data: idbEntry.data,
|
|
@@ -124,12 +134,14 @@ export class DictManager {
|
|
|
124
134
|
});
|
|
125
135
|
return idbEntry.data;
|
|
126
136
|
}
|
|
137
|
+
logger.debug(`fetchAndCache NETWORK requesting store=${storeName} type=${type}`);
|
|
127
138
|
const adapter = this.getAdapter(storeName);
|
|
128
139
|
const response = await adapter.fetchDict(storeName, {
|
|
129
140
|
types: [type],
|
|
130
141
|
locale: this.locale.value
|
|
131
142
|
});
|
|
132
143
|
const entry = response.data[type];
|
|
144
|
+
logger.debug(`fetchAndCache NETWORK done store=${storeName} type=${type} found=${!!entry} items=${entry?.items?.length}`);
|
|
133
145
|
if (!entry) {
|
|
134
146
|
throw new Error(`Dictionary type "${type}" not found in response`);
|
|
135
147
|
}
|
|
@@ -253,12 +265,24 @@ export class DictManager {
|
|
|
253
265
|
return String(a) === String(b);
|
|
254
266
|
}
|
|
255
267
|
/**
|
|
256
|
-
*
|
|
257
|
-
*
|
|
268
|
+
* 初始化管理器:对非 lazy 仓库立即并行检查版本号。
|
|
269
|
+
* lazy 仓库的版本检查延迟到首次 getDict() 调用时惰性触发。
|
|
258
270
|
*
|
|
259
271
|
* @returns {Promise<void>}
|
|
260
272
|
*/
|
|
261
273
|
async initialize() {
|
|
274
|
+
if (typeof localStorage === "undefined") return;
|
|
275
|
+
const immediateStores = [...this.versionChecks].filter(([name]) => !this.lazyStores.has(name));
|
|
276
|
+
await Promise.all(immediateStores.map(async ([name, vc]) => {
|
|
277
|
+
try {
|
|
278
|
+
const { changed } = await vc.check(name);
|
|
279
|
+
if (changed) {
|
|
280
|
+
await this.invalidateAll(name);
|
|
281
|
+
}
|
|
282
|
+
} catch {
|
|
283
|
+
}
|
|
284
|
+
this.checkedStores.add(name);
|
|
285
|
+
}));
|
|
262
286
|
}
|
|
263
287
|
/**
|
|
264
288
|
* 失效缓存数据。
|
package/dist/runtime/options.js
CHANGED
|
@@ -54,13 +54,26 @@ function resolveClientLocale(options) {
|
|
|
54
54
|
}
|
|
55
55
|
return options.locale.default;
|
|
56
56
|
}
|
|
57
|
-
async function
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
57
|
+
async function initClient(manager, indexedDB, options, logger) {
|
|
58
|
+
if (options.cache.indexedDB.enabled) {
|
|
59
|
+
try {
|
|
60
|
+
const storeNames = [DEFAULT_STORE_NAME, ...Object.keys(options.stores)];
|
|
61
|
+
await indexedDB.init(storeNames);
|
|
62
|
+
} catch (e) {
|
|
63
|
+
logger.warn("IndexedDB init failed:", e);
|
|
64
|
+
}
|
|
63
65
|
}
|
|
66
|
+
await manager.initialize();
|
|
67
|
+
}
|
|
68
|
+
function resolveLazyStores(options) {
|
|
69
|
+
const lazyStores = /* @__PURE__ */ new Set();
|
|
70
|
+
if (options.api.lazy) lazyStores.add(DEFAULT_STORE_NAME);
|
|
71
|
+
for (const [name, config] of Object.entries(options.stores)) {
|
|
72
|
+
if (config.lazy ?? options.api.lazy ?? false) {
|
|
73
|
+
lazyStores.add(name);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return lazyStores;
|
|
64
77
|
}
|
|
65
78
|
async function executePrefetch(manager, types, logger) {
|
|
66
79
|
try {
|
|
@@ -102,12 +115,14 @@ const dictPlugin = defineNuxtPlugin(async (nuxtApp) => {
|
|
|
102
115
|
const logger = createLogger("nuxt-dict", { level: options.logLevel });
|
|
103
116
|
const adapters = createAdapters(options, logger);
|
|
104
117
|
const indexedDB = new IndexedDBCache(options.cache.indexedDB.dbName);
|
|
118
|
+
const lazyStores = resolveLazyStores(options);
|
|
105
119
|
const manager = new DictManager({
|
|
106
120
|
adapters,
|
|
107
121
|
indexedDB,
|
|
108
122
|
memoryMax: options.cache.memoryMax,
|
|
109
123
|
ttl: options.cache.ttl,
|
|
110
|
-
versionStorageKey: options.version.storageKey
|
|
124
|
+
versionStorageKey: options.version.storageKey,
|
|
125
|
+
lazyStores
|
|
111
126
|
});
|
|
112
127
|
const locale = import.meta.server ? resolveServerLocale(options) : resolveClientLocale(options);
|
|
113
128
|
manager.setLocale(locale);
|
|
@@ -120,8 +135,8 @@ const dictPlugin = defineNuxtPlugin(async (nuxtApp) => {
|
|
|
120
135
|
}
|
|
121
136
|
});
|
|
122
137
|
}
|
|
123
|
-
if (import.meta.client
|
|
124
|
-
await
|
|
138
|
+
if (import.meta.client) {
|
|
139
|
+
await initClient(manager, indexedDB, options, logger);
|
|
125
140
|
}
|
|
126
141
|
if (import.meta.server && options.ssr.prefetch.length > 0) {
|
|
127
142
|
await executePrefetch(manager, options.ssr.prefetch, logger);
|
|
@@ -88,6 +88,8 @@ export interface StoreApiOptions {
|
|
|
88
88
|
versionEndpoint?: string;
|
|
89
89
|
/** 自定义字典适配器文件路径(如 '~/dict/dict-adapter'),不传则按约定路径自动发现或使用默认 REST 适配器 */
|
|
90
90
|
adapter?: string;
|
|
91
|
+
/** 是否惰性检查版本号,默认继承全局 `api.lazy`。`true` 时首次 getDict 调用才检查,`false` 时页面加载立即检查 */
|
|
92
|
+
lazy?: boolean;
|
|
91
93
|
}
|
|
92
94
|
/** 模块配置项(用户可传,字段均可选) */
|
|
93
95
|
export interface ModuleOptions {
|
|
@@ -104,6 +106,8 @@ export interface ModuleOptions {
|
|
|
104
106
|
versionEndpoint?: string;
|
|
105
107
|
/** 自定义字典适配器文件路径(如 '~/dict/dict-adapter'),不传则按约定路径自动发现或使用默认 REST 适配器 */
|
|
106
108
|
adapter?: string;
|
|
109
|
+
/** 是否惰性检查版本号,默认 `false`(页面加载时立即检查)。`true` 时首次 getDict 调用才检查 */
|
|
110
|
+
lazy?: boolean;
|
|
107
111
|
};
|
|
108
112
|
cache?: {
|
|
109
113
|
/** 内存缓存最大条目数,默认 `200` */
|
|
@@ -156,6 +160,7 @@ export interface ResolvedModuleOptions {
|
|
|
156
160
|
dictEndpoint: string;
|
|
157
161
|
versionEndpoint: string;
|
|
158
162
|
adapter?: string;
|
|
163
|
+
lazy: boolean;
|
|
159
164
|
};
|
|
160
165
|
cache: {
|
|
161
166
|
memoryMax: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lacqjs/nuxt-dict",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"description": "Nuxt 数据字典模块,提供扁平/树形字典翻译、多语言国际化、三级缓存与 SSR 预取。",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"@lacqjs/nuxt-dict",
|
|
@@ -44,7 +44,8 @@
|
|
|
44
44
|
"@nuxt/kit": "^4.4.8",
|
|
45
45
|
"compare-versions": "^6.1.1",
|
|
46
46
|
"consola": "^3.4.2",
|
|
47
|
-
"defu": "^6.1.7"
|
|
47
|
+
"defu": "^6.1.7",
|
|
48
|
+
"dexie": "^4.4.4"
|
|
48
49
|
},
|
|
49
50
|
"devDependencies": {
|
|
50
51
|
"@nuxt/devtools": "^3.2.4",
|