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 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
@@ -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
+
@@ -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
+ }