vue-cacheable-dictionary 1.0.0
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/README.md +80 -0
- package/README.zh-CN.md +81 -0
- package/dist/index.d.ts +112 -0
- package/dist/index.js +141 -0
- package/package.json +60 -0
package/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# vue-cacheable-dictionary
|
|
2
|
+
|
|
3
|
+
[中文 README](./README.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
A small cacheable dictionary loader for Vue 3. It keeps a reactive store, dedupes in-flight requests, and lets you query dictionaries by key from anywhere.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add vue-cacheable-dictionary
|
|
11
|
+
# or
|
|
12
|
+
npm i vue-cacheable-dictionary
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
Create a single instance (usually in a composable/module), provide a `load` function, then query dicts on demand.
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { createDictionaryInstance, type DictItem } from 'vue-cacheable-dictionary'
|
|
21
|
+
|
|
22
|
+
interface MyDictItem extends DictItem {
|
|
23
|
+
color?: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const dict = createDictionaryInstance<MyDictItem>({
|
|
27
|
+
load: async (dicts) => {
|
|
28
|
+
const res = await fetch('/dict', {
|
|
29
|
+
method: 'POST',
|
|
30
|
+
headers: { 'Content-Type': 'application/json' },
|
|
31
|
+
body: JSON.stringify(dicts),
|
|
32
|
+
})
|
|
33
|
+
return res.json() as Promise<Record<string, MyDictItem[]>>
|
|
34
|
+
},
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
export const useDict = dict.getDictData
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
In components:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { computed } from 'vue'
|
|
44
|
+
import { useDict } from './useDict'
|
|
45
|
+
|
|
46
|
+
const store = useDict(['type', 'status'])
|
|
47
|
+
|
|
48
|
+
const statusLabel = computed(() => store.get('status')?.label(1) ?? '')
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## What you get
|
|
52
|
+
|
|
53
|
+
- `createDictionaryInstance(...)`: creates an instance that manages caching and request deduplication.
|
|
54
|
+
- `DictArray`: an array-like wrapper with helpers like `pick`, `omit`, `label`, and `item`.
|
|
55
|
+
- `DictItem` / `DictKey` types for typing your dictionaries.
|
|
56
|
+
|
|
57
|
+
## Notes
|
|
58
|
+
|
|
59
|
+
- Only string keys are sent to `load(...)`. Non-string keys (e.g. `Symbol`) are ignored for remote loading.
|
|
60
|
+
- The store is reactive and backed by a `Map`, so it works naturally in Vue templates/computed.
|
|
61
|
+
|
|
62
|
+
## Development
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pnpm install
|
|
66
|
+
pnpm play
|
|
67
|
+
pnpm test
|
|
68
|
+
pnpm build
|
|
69
|
+
pnpm typecheck
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Contributing
|
|
73
|
+
|
|
74
|
+
- File an issue for bugs/feature requests.
|
|
75
|
+
- Send a PR with focused changes and a clear description.
|
|
76
|
+
- Ensure `pnpm test` and `pnpm typecheck` pass before opening a PR.
|
|
77
|
+
|
|
78
|
+
## License
|
|
79
|
+
|
|
80
|
+
MIT
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# vue-cacheable-dictionary
|
|
2
|
+
|
|
3
|
+
[English README](./README.md)
|
|
4
|
+
|
|
5
|
+
一个面向 Vue 3 的可缓存字典加载器:维护响应式字典仓库、去重并发请求,并支持按 key 随用随取。
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add vue-cacheable-dictionary
|
|
11
|
+
# 或
|
|
12
|
+
npm i vue-cacheable-dictionary
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## 使用
|
|
16
|
+
|
|
17
|
+
通常在一个 composable/模块中创建单例,提供 `load` 方法,然后按需查询字典。
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { createDictionaryInstance, type DictItem } from 'vue-cacheable-dictionary'
|
|
21
|
+
|
|
22
|
+
interface MyDictItem extends DictItem {
|
|
23
|
+
color?: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const dict = createDictionaryInstance<MyDictItem>({
|
|
27
|
+
load: async (dicts) => {
|
|
28
|
+
const res = await fetch('/dict', {
|
|
29
|
+
method: 'POST',
|
|
30
|
+
headers: { 'Content-Type': 'application/json' },
|
|
31
|
+
body: JSON.stringify(dicts),
|
|
32
|
+
})
|
|
33
|
+
return res.json() as Promise<Record<string, MyDictItem[]>>
|
|
34
|
+
},
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
export const useDict = dict.getDictData
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
组件内使用:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { computed } from 'vue'
|
|
44
|
+
import { useDict } from './useDict'
|
|
45
|
+
|
|
46
|
+
const store = useDict(['type', 'status'])
|
|
47
|
+
|
|
48
|
+
const statusLabel = computed(() => store.get('status')?.label(1) ?? '')
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## 你会得到什么
|
|
52
|
+
|
|
53
|
+
- `createDictionaryInstance(...)`:创建实例,负责缓存与请求去重。
|
|
54
|
+
- `DictArray`:类数组封装,提供 `pick` / `omit` / `label` / `item` 等便捷方法。
|
|
55
|
+
- `DictItem` / `DictKey`:字典数据类型定义。
|
|
56
|
+
|
|
57
|
+
## 说明
|
|
58
|
+
|
|
59
|
+
- 只有字符串类型的 key 会传给 `load(...)`;非字符串 key(例如 `Symbol`)会被忽略,不会触发远程请求。
|
|
60
|
+
- 字典仓库底层是 `Map` 且具备响应式能力,可直接用于模板与 computed。
|
|
61
|
+
|
|
62
|
+
## 开发
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pnpm install
|
|
66
|
+
pnpm play
|
|
67
|
+
pnpm test
|
|
68
|
+
pnpm build
|
|
69
|
+
pnpm typecheck
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## 贡献
|
|
73
|
+
|
|
74
|
+
- 通过 Issue 提交 bug/需求。
|
|
75
|
+
- 提交 PR 时保持改动聚焦,并写清楚变更目的与影响范围。
|
|
76
|
+
- 提交前确保 `pnpm test` 与 `pnpm typecheck` 通过。
|
|
77
|
+
|
|
78
|
+
## 许可证
|
|
79
|
+
|
|
80
|
+
MIT
|
|
81
|
+
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import * as vue0 from "vue";
|
|
2
|
+
|
|
3
|
+
//#region src/dict.d.ts
|
|
4
|
+
type DictKey = string | symbol;
|
|
5
|
+
interface DictItem {
|
|
6
|
+
label: string;
|
|
7
|
+
value: any;
|
|
8
|
+
}
|
|
9
|
+
type DictItemValue = DictItem['value'];
|
|
10
|
+
declare class DictArray<D extends DictItem = DictItem> extends Array<D> {
|
|
11
|
+
constructor(dicts: D[]);
|
|
12
|
+
/**
|
|
13
|
+
* Like `omit` in `lodash.js`
|
|
14
|
+
* @param values The values of dict items to omit.
|
|
15
|
+
* @returns DictArray
|
|
16
|
+
*/
|
|
17
|
+
omit(values: DictKey[]): DictArray<D>;
|
|
18
|
+
/**
|
|
19
|
+
* Like `pick` in `lodash.js`
|
|
20
|
+
* @param values The values of dict items.
|
|
21
|
+
* @returns DictArray
|
|
22
|
+
*/
|
|
23
|
+
pick(values: DictKey[]): DictArray<D>;
|
|
24
|
+
/**
|
|
25
|
+
* Get label by value.
|
|
26
|
+
* @param value The value of dict item.
|
|
27
|
+
* @returns The label of dict item.
|
|
28
|
+
*/
|
|
29
|
+
label(value: DictItemValue): string;
|
|
30
|
+
/**
|
|
31
|
+
* Get dict item by value.
|
|
32
|
+
* @param value The value of dict item.
|
|
33
|
+
* @param defaultDict The default dict item.
|
|
34
|
+
* @returns The dict item.
|
|
35
|
+
*/
|
|
36
|
+
item(value: DictItemValue, defaultDict?: D): D | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Convert to plain array.
|
|
39
|
+
* It will lost all methods of DictArray.
|
|
40
|
+
* @returns Plain array.
|
|
41
|
+
*/
|
|
42
|
+
toPlainArray(): D[];
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/store.d.ts
|
|
46
|
+
declare class DictionaryStore<D extends DictItem = DictItem> {
|
|
47
|
+
#private;
|
|
48
|
+
/**
|
|
49
|
+
* A readonly handler.
|
|
50
|
+
* For native invoke on two-direction data binding.
|
|
51
|
+
*/
|
|
52
|
+
readonly store: vue0.ComputedRef<vue0.ShallowReactive<Map<DictKey, DictArray<D>>>>;
|
|
53
|
+
add(key: DictKey, dict: DictArray<D>): void;
|
|
54
|
+
addRaw(key: DictKey, dict: D[]): void;
|
|
55
|
+
addAll(data: Record<DictKey, DictArray<D>>): void;
|
|
56
|
+
delete(key: DictKey): void;
|
|
57
|
+
get(key: DictKey, defaultValue?: DictArray<D>): DictArray<D> | undefined;
|
|
58
|
+
size(): number;
|
|
59
|
+
keys(): DictKey[];
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region src/index.d.ts
|
|
63
|
+
interface DictionaryOptions<D extends DictItem = DictItem> {
|
|
64
|
+
/**
|
|
65
|
+
* Load dictionary data.
|
|
66
|
+
* @param dicts The dicts to load. DictItem keys that are not of type string will be ignored.
|
|
67
|
+
* @returns A promise that resolves to a record of dicts.
|
|
68
|
+
*/
|
|
69
|
+
load: (dicts: string[]) => Promise<Record<string, D[]>>;
|
|
70
|
+
}
|
|
71
|
+
interface DictionaryInstance<D extends DictItem = DictItem> {
|
|
72
|
+
/**
|
|
73
|
+
* Dictionary store.
|
|
74
|
+
* @readonly
|
|
75
|
+
*/
|
|
76
|
+
readonly store: DictionaryStore;
|
|
77
|
+
/**
|
|
78
|
+
* Query queue for dict request.
|
|
79
|
+
* @readonly
|
|
80
|
+
*/
|
|
81
|
+
readonly queue: Set<DictKey>;
|
|
82
|
+
/**
|
|
83
|
+
* Query dictionary data
|
|
84
|
+
*
|
|
85
|
+
* Read from cache first, fetch remotely if not present.
|
|
86
|
+
*
|
|
87
|
+
* @param dicts The dicts to query.
|
|
88
|
+
* @returns The dictionary store.
|
|
89
|
+
* @example
|
|
90
|
+
* const dict = useDict(['type', 'status']);
|
|
91
|
+
* const dict2 = useDict(['type', 'order_type']); // duplicate for test.
|
|
92
|
+
*
|
|
93
|
+
* dict1.get('order_type'); // ✅
|
|
94
|
+
* dict2.get('status'); // ✅
|
|
95
|
+
*/
|
|
96
|
+
getDictData: (dicts?: DictKey[]) => DictionaryStore<D>;
|
|
97
|
+
/**
|
|
98
|
+
* Reload dictionary data.
|
|
99
|
+
*
|
|
100
|
+
* @param dicts The dicts to reload.
|
|
101
|
+
* @returns A promise that resolves to a record of dicts.
|
|
102
|
+
*/
|
|
103
|
+
reloadDictData: (dicts: DictKey[]) => Promise<Record<string, D[]>>;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Create a dictionary instance.
|
|
107
|
+
* @param options The dictionary options.
|
|
108
|
+
* @returns The dictionary instance.
|
|
109
|
+
*/
|
|
110
|
+
declare function createDictionaryInstance<D extends DictItem = DictItem>(options: DictionaryOptions<D>): DictionaryInstance<D>;
|
|
111
|
+
//#endregion
|
|
112
|
+
export { DictArray, type DictItem, type DictKey, createDictionaryInstance };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { difference } from "lodash-es";
|
|
2
|
+
import { computed, shallowReactive } from "vue";
|
|
3
|
+
|
|
4
|
+
//#region src/dict.ts
|
|
5
|
+
var DictArray = class DictArray extends Array {
|
|
6
|
+
constructor(dicts) {
|
|
7
|
+
super();
|
|
8
|
+
for (let i = 0; i < dicts.length; i++) {
|
|
9
|
+
const dict = dicts[i];
|
|
10
|
+
this.push(dict);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Like `omit` in `lodash.js`
|
|
15
|
+
* @param values The values of dict items to omit.
|
|
16
|
+
* @returns DictArray
|
|
17
|
+
*/
|
|
18
|
+
omit(values) {
|
|
19
|
+
return new DictArray(this.filter((item) => !values.includes(item.value)));
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Like `pick` in `lodash.js`
|
|
23
|
+
* @param values The values of dict items.
|
|
24
|
+
* @returns DictArray
|
|
25
|
+
*/
|
|
26
|
+
pick(values) {
|
|
27
|
+
return new DictArray(this.filter((item) => values.includes(item.value)));
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Get label by value.
|
|
31
|
+
* @param value The value of dict item.
|
|
32
|
+
* @returns The label of dict item.
|
|
33
|
+
*/
|
|
34
|
+
label(value) {
|
|
35
|
+
return this.item(value)?.label ?? "";
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Get dict item by value.
|
|
39
|
+
* @param value The value of dict item.
|
|
40
|
+
* @param defaultDict The default dict item.
|
|
41
|
+
* @returns The dict item.
|
|
42
|
+
*/
|
|
43
|
+
item(value, defaultDict) {
|
|
44
|
+
return this.find((item) => item.value === value) ?? defaultDict;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Convert to plain array.
|
|
48
|
+
* It will lost all methods of DictArray.
|
|
49
|
+
* @returns Plain array.
|
|
50
|
+
*/
|
|
51
|
+
toPlainArray() {
|
|
52
|
+
return Array.from(this);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/store.ts
|
|
58
|
+
var DictionaryStore = class {
|
|
59
|
+
#store = shallowReactive(/* @__PURE__ */ new Map());
|
|
60
|
+
/**
|
|
61
|
+
* A readonly handler.
|
|
62
|
+
* For native invoke on two-direction data binding.
|
|
63
|
+
*/
|
|
64
|
+
store = computed(() => this.#store);
|
|
65
|
+
add(key, dict) {
|
|
66
|
+
this.#store.set(key, dict);
|
|
67
|
+
}
|
|
68
|
+
addRaw(key, dict) {
|
|
69
|
+
this.#store.set(key, new DictArray(dict));
|
|
70
|
+
}
|
|
71
|
+
addAll(data) {
|
|
72
|
+
for (const key in data) if (Object.prototype.hasOwnProperty.call(data, key)) this.add(key, data[key]);
|
|
73
|
+
}
|
|
74
|
+
delete(key) {
|
|
75
|
+
this.#store.delete(key);
|
|
76
|
+
}
|
|
77
|
+
get(key, defaultValue) {
|
|
78
|
+
return this.#store.get(key) ?? defaultValue;
|
|
79
|
+
}
|
|
80
|
+
size() {
|
|
81
|
+
return this.#store.size;
|
|
82
|
+
}
|
|
83
|
+
keys() {
|
|
84
|
+
return Array.from(this.#store.keys());
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
//#endregion
|
|
89
|
+
//#region src/index.ts
|
|
90
|
+
/**
|
|
91
|
+
* Create a dictionary instance.
|
|
92
|
+
* @param options The dictionary options.
|
|
93
|
+
* @returns The dictionary instance.
|
|
94
|
+
*/
|
|
95
|
+
function createDictionaryInstance(options) {
|
|
96
|
+
const store = new DictionaryStore();
|
|
97
|
+
/**
|
|
98
|
+
* Query queue for dict request.
|
|
99
|
+
* @readonly
|
|
100
|
+
*/
|
|
101
|
+
const fetchQueueSet = /* @__PURE__ */ new Set();
|
|
102
|
+
function getDictData(dicts) {
|
|
103
|
+
if (!Array.isArray(dicts) || dicts.length === 0) return store;
|
|
104
|
+
const diff = difference(dicts, [...store.keys(), ...fetchQueueSet]);
|
|
105
|
+
if (diff.length > 0) loadDictData(diff);
|
|
106
|
+
return store;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Load dictionary data.
|
|
110
|
+
* @param dicts The dicts to load.
|
|
111
|
+
* @returns A promise that resolves to a record of dicts.
|
|
112
|
+
*/
|
|
113
|
+
function loadDictData(dicts) {
|
|
114
|
+
dicts.forEach((dict) => fetchQueueSet.add(dict));
|
|
115
|
+
const requestDicts = dicts.filter((key) => typeof key === "string");
|
|
116
|
+
return options.load(requestDicts).then((response) => {
|
|
117
|
+
for (const key in response) if (Object.prototype.hasOwnProperty.call(response, key)) store.addRaw(key, response[key]);
|
|
118
|
+
return response;
|
|
119
|
+
}).finally(() => {
|
|
120
|
+
dicts.forEach((dict) => fetchQueueSet.delete(dict));
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Reload dictionary data.
|
|
125
|
+
* @param dicts The dicts to reload.
|
|
126
|
+
* @returns A promise that resolves to a record of dicts.
|
|
127
|
+
*/
|
|
128
|
+
function reloadDictData(dicts) {
|
|
129
|
+
if (dicts && dicts.length > 0) return loadDictData(dicts).then();
|
|
130
|
+
return Promise.resolve({});
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
store,
|
|
134
|
+
queue: fetchQueueSet,
|
|
135
|
+
getDictData,
|
|
136
|
+
reloadDictData
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
//#endregion
|
|
141
|
+
export { DictArray, createDictionaryInstance };
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vue-cacheable-dictionary",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "A cacheable dictionary library for Vue.",
|
|
6
|
+
"author": "Even Wan <me@tuanzi.ink>",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"homepage": "https://github.com/tuanzisama/vue-cacheable-dictionary#readme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/tuanzisama/vue-cacheable-dictionary.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/tuanzisama/vue-cacheable-dictionary/issues"
|
|
15
|
+
},
|
|
16
|
+
"exports": {
|
|
17
|
+
".": "./dist/index.js",
|
|
18
|
+
"./package.json": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
"main": "./dist/index.js",
|
|
21
|
+
"module": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"vue": "^3.0.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/express": "^5.0.6",
|
|
34
|
+
"@types/lodash-es": "^4.17.12",
|
|
35
|
+
"@types/node": "^25.0.3",
|
|
36
|
+
"@vitejs/plugin-vue": "^6.0.3",
|
|
37
|
+
"@vitest/browser-playwright": "^4.0.16",
|
|
38
|
+
"bumpp": "^10.3.2",
|
|
39
|
+
"express": "^5.2.1",
|
|
40
|
+
"playwright": "^1.57.0",
|
|
41
|
+
"tsdown": "^0.18.1",
|
|
42
|
+
"typescript": "^5.9.3",
|
|
43
|
+
"vite": "^7.3.0",
|
|
44
|
+
"vitest": "^4.0.16",
|
|
45
|
+
"vitest-browser-vue": "^2.0.1",
|
|
46
|
+
"vue": "^3.5.25",
|
|
47
|
+
"vue-tsc": "^3.1.8"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"lodash-es": "^4.17.22"
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"build": "tsdown",
|
|
54
|
+
"dev": "tsdown --watch",
|
|
55
|
+
"play": "vite",
|
|
56
|
+
"test": "vitest run",
|
|
57
|
+
"typecheck": "vue-tsc --noEmit",
|
|
58
|
+
"release": "bumpp && pnpm publish"
|
|
59
|
+
}
|
|
60
|
+
}
|