@vobs/dict 1.1.0 → 1.2.1
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/index.cjs +531 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +74 -0
- package/dist/index.d.ts +74 -0
- package/dist/index.js +525 -0
- package/dist/index.js.map +1 -0
- package/package.json +25 -6
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Signal } from '@vobs/reactivity';
|
|
2
|
+
import { InjectionKey, VobsPlugin } from '@vobs/vobs';
|
|
3
|
+
|
|
4
|
+
type DictName = string;
|
|
5
|
+
type DictValue = string | number;
|
|
6
|
+
interface DictItem {
|
|
7
|
+
readonly value: DictValue;
|
|
8
|
+
readonly label: string;
|
|
9
|
+
readonly disabled?: boolean;
|
|
10
|
+
readonly [key: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
type DictItems = readonly DictItem[];
|
|
13
|
+
type DictData = Readonly<Record<DictName, DictItems>>;
|
|
14
|
+
type DictLoader = (name: DictName, signal: AbortSignal) => DictItems | PromiseLike<DictItems>;
|
|
15
|
+
interface DictQuery {
|
|
16
|
+
readonly name: DictName;
|
|
17
|
+
readonly items: Signal<DictItems>;
|
|
18
|
+
readonly loading: Signal<boolean>;
|
|
19
|
+
readonly error: Signal<Error | null>;
|
|
20
|
+
readonly updatedAt: Signal<number>;
|
|
21
|
+
load(options?: {
|
|
22
|
+
readonly force?: boolean;
|
|
23
|
+
}): Promise<DictItems>;
|
|
24
|
+
invalidate(): void;
|
|
25
|
+
}
|
|
26
|
+
interface DictDehydratedEntry {
|
|
27
|
+
readonly name: DictName;
|
|
28
|
+
readonly items: DictItems;
|
|
29
|
+
readonly updatedAt: number;
|
|
30
|
+
}
|
|
31
|
+
interface DictDehydratedState {
|
|
32
|
+
readonly version: 1;
|
|
33
|
+
readonly entries: readonly DictDehydratedEntry[];
|
|
34
|
+
}
|
|
35
|
+
interface DictOptions {
|
|
36
|
+
readonly data?: DictData;
|
|
37
|
+
readonly loader?: DictLoader;
|
|
38
|
+
readonly staleTime?: number;
|
|
39
|
+
readonly now?: () => number;
|
|
40
|
+
readonly onError?: (error: DictError, name: DictName) => void;
|
|
41
|
+
}
|
|
42
|
+
interface DictPluginOptions extends DictOptions {
|
|
43
|
+
readonly dict?: DictContext;
|
|
44
|
+
}
|
|
45
|
+
interface DictContext {
|
|
46
|
+
readonly data: Signal<DictData>;
|
|
47
|
+
readonly loading: Signal<boolean>;
|
|
48
|
+
readonly error: Signal<Error | null>;
|
|
49
|
+
readonly staleTime: number;
|
|
50
|
+
get(name: DictName): DictItems;
|
|
51
|
+
find(name: DictName, value: DictValue): DictItem | undefined;
|
|
52
|
+
label(name: DictName, value: DictValue, fallback?: string): string;
|
|
53
|
+
query(name: DictName): DictQuery;
|
|
54
|
+
load(name: DictName, options?: {
|
|
55
|
+
readonly force?: boolean;
|
|
56
|
+
}): Promise<DictItems>;
|
|
57
|
+
set(name: DictName, items: DictItems): void;
|
|
58
|
+
invalidate(name?: DictName): void;
|
|
59
|
+
dehydrate(): DictDehydratedState;
|
|
60
|
+
hydrate(snapshot: unknown): void;
|
|
61
|
+
dispose(): void;
|
|
62
|
+
}
|
|
63
|
+
type DictErrorCode = 'DICT_CONTEXT_MISSING' | 'DICT_CONTEXT_DISPOSED' | 'INVALID_DICT_NAME' | 'INVALID_DICT_ITEMS' | 'DICT_LOADER_MISSING' | 'DICT_LOAD_FAILED' | 'INVALID_DEHYDRATED_STATE';
|
|
64
|
+
declare class DictError extends Error {
|
|
65
|
+
readonly code: DictErrorCode;
|
|
66
|
+
readonly cause: unknown;
|
|
67
|
+
constructor(code: DictErrorCode, message: string, cause?: unknown);
|
|
68
|
+
}
|
|
69
|
+
declare const DICT_KEY: InjectionKey<DictContext>;
|
|
70
|
+
declare function createDict(options?: DictOptions): DictContext;
|
|
71
|
+
declare function dictPlugin(options?: DictPluginOptions): VobsPlugin;
|
|
72
|
+
declare function useDict(): DictContext;
|
|
73
|
+
|
|
74
|
+
export { DICT_KEY, type DictContext, type DictData, type DictDehydratedEntry, type DictDehydratedState, DictError, type DictErrorCode, type DictItem, type DictItems, type DictLoader, type DictName, type DictOptions, type DictPluginOptions, type DictQuery, type DictValue, createDict, dictPlugin, useDict };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Signal } from '@vobs/reactivity';
|
|
2
|
+
import { InjectionKey, VobsPlugin } from '@vobs/vobs';
|
|
3
|
+
|
|
4
|
+
type DictName = string;
|
|
5
|
+
type DictValue = string | number;
|
|
6
|
+
interface DictItem {
|
|
7
|
+
readonly value: DictValue;
|
|
8
|
+
readonly label: string;
|
|
9
|
+
readonly disabled?: boolean;
|
|
10
|
+
readonly [key: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
type DictItems = readonly DictItem[];
|
|
13
|
+
type DictData = Readonly<Record<DictName, DictItems>>;
|
|
14
|
+
type DictLoader = (name: DictName, signal: AbortSignal) => DictItems | PromiseLike<DictItems>;
|
|
15
|
+
interface DictQuery {
|
|
16
|
+
readonly name: DictName;
|
|
17
|
+
readonly items: Signal<DictItems>;
|
|
18
|
+
readonly loading: Signal<boolean>;
|
|
19
|
+
readonly error: Signal<Error | null>;
|
|
20
|
+
readonly updatedAt: Signal<number>;
|
|
21
|
+
load(options?: {
|
|
22
|
+
readonly force?: boolean;
|
|
23
|
+
}): Promise<DictItems>;
|
|
24
|
+
invalidate(): void;
|
|
25
|
+
}
|
|
26
|
+
interface DictDehydratedEntry {
|
|
27
|
+
readonly name: DictName;
|
|
28
|
+
readonly items: DictItems;
|
|
29
|
+
readonly updatedAt: number;
|
|
30
|
+
}
|
|
31
|
+
interface DictDehydratedState {
|
|
32
|
+
readonly version: 1;
|
|
33
|
+
readonly entries: readonly DictDehydratedEntry[];
|
|
34
|
+
}
|
|
35
|
+
interface DictOptions {
|
|
36
|
+
readonly data?: DictData;
|
|
37
|
+
readonly loader?: DictLoader;
|
|
38
|
+
readonly staleTime?: number;
|
|
39
|
+
readonly now?: () => number;
|
|
40
|
+
readonly onError?: (error: DictError, name: DictName) => void;
|
|
41
|
+
}
|
|
42
|
+
interface DictPluginOptions extends DictOptions {
|
|
43
|
+
readonly dict?: DictContext;
|
|
44
|
+
}
|
|
45
|
+
interface DictContext {
|
|
46
|
+
readonly data: Signal<DictData>;
|
|
47
|
+
readonly loading: Signal<boolean>;
|
|
48
|
+
readonly error: Signal<Error | null>;
|
|
49
|
+
readonly staleTime: number;
|
|
50
|
+
get(name: DictName): DictItems;
|
|
51
|
+
find(name: DictName, value: DictValue): DictItem | undefined;
|
|
52
|
+
label(name: DictName, value: DictValue, fallback?: string): string;
|
|
53
|
+
query(name: DictName): DictQuery;
|
|
54
|
+
load(name: DictName, options?: {
|
|
55
|
+
readonly force?: boolean;
|
|
56
|
+
}): Promise<DictItems>;
|
|
57
|
+
set(name: DictName, items: DictItems): void;
|
|
58
|
+
invalidate(name?: DictName): void;
|
|
59
|
+
dehydrate(): DictDehydratedState;
|
|
60
|
+
hydrate(snapshot: unknown): void;
|
|
61
|
+
dispose(): void;
|
|
62
|
+
}
|
|
63
|
+
type DictErrorCode = 'DICT_CONTEXT_MISSING' | 'DICT_CONTEXT_DISPOSED' | 'INVALID_DICT_NAME' | 'INVALID_DICT_ITEMS' | 'DICT_LOADER_MISSING' | 'DICT_LOAD_FAILED' | 'INVALID_DEHYDRATED_STATE';
|
|
64
|
+
declare class DictError extends Error {
|
|
65
|
+
readonly code: DictErrorCode;
|
|
66
|
+
readonly cause: unknown;
|
|
67
|
+
constructor(code: DictErrorCode, message: string, cause?: unknown);
|
|
68
|
+
}
|
|
69
|
+
declare const DICT_KEY: InjectionKey<DictContext>;
|
|
70
|
+
declare function createDict(options?: DictOptions): DictContext;
|
|
71
|
+
declare function dictPlugin(options?: DictPluginOptions): VobsPlugin;
|
|
72
|
+
declare function useDict(): DictContext;
|
|
73
|
+
|
|
74
|
+
export { DICT_KEY, type DictContext, type DictData, type DictDehydratedEntry, type DictDehydratedState, DictError, type DictErrorCode, type DictItem, type DictItems, type DictLoader, type DictName, type DictOptions, type DictPluginOptions, type DictQuery, type DictValue, createDict, dictPlugin, useDict };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
|
|
4
|
+
// packages/reactivity/src/debug.ts
|
|
5
|
+
var activeDebugHooks = null;
|
|
6
|
+
var signalNames = /* @__PURE__ */ new WeakMap();
|
|
7
|
+
function hasDebugHooks() {
|
|
8
|
+
return activeDebugHooks !== null;
|
|
9
|
+
}
|
|
10
|
+
__name(hasDebugHooks, "hasDebugHooks");
|
|
11
|
+
function setSignalDebugName(signal, name) {
|
|
12
|
+
signalNames.set(signal, name);
|
|
13
|
+
invokeDebug("signalNamed", signal, name);
|
|
14
|
+
}
|
|
15
|
+
__name(setSignalDebugName, "setSignalDebugName");
|
|
16
|
+
function invokeDebug(name, ...args) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
__name(invokeDebug, "invokeDebug");
|
|
20
|
+
|
|
21
|
+
// packages/reactivity/src/owner.ts
|
|
22
|
+
var currentOwner = null;
|
|
23
|
+
function getCurrentOwner() {
|
|
24
|
+
return currentOwner;
|
|
25
|
+
}
|
|
26
|
+
__name(getCurrentOwner, "getCurrentOwner");
|
|
27
|
+
function onDispose(cleanup) {
|
|
28
|
+
throw new Error("Vobs: onDispose \u5FC5\u987B\u5728 Owner \u4F5C\u7528\u57DF\u5185\u8C03\u7528");
|
|
29
|
+
}
|
|
30
|
+
__name(onDispose, "onDispose");
|
|
31
|
+
|
|
32
|
+
// packages/reactivity/src/signal.ts
|
|
33
|
+
var currentSubscriber = null;
|
|
34
|
+
function getCurrentSubscriber() {
|
|
35
|
+
return currentSubscriber;
|
|
36
|
+
}
|
|
37
|
+
__name(getCurrentSubscriber, "getCurrentSubscriber");
|
|
38
|
+
function trackDependency(dependency) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
__name(trackDependency, "trackDependency");
|
|
42
|
+
function state(initialValue, debugName) {
|
|
43
|
+
let value = initialValue;
|
|
44
|
+
let disposed = false;
|
|
45
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
46
|
+
const signalInstance = {
|
|
47
|
+
get value() {
|
|
48
|
+
return value;
|
|
49
|
+
},
|
|
50
|
+
set value(nextValue) {
|
|
51
|
+
if (disposed || Object.is(value, nextValue)) return;
|
|
52
|
+
value = nextValue;
|
|
53
|
+
for (const subscriber of [...subscribers]) subscriber.notify();
|
|
54
|
+
},
|
|
55
|
+
unsubscribe(subscriber) {
|
|
56
|
+
subscribers.delete(subscriber);
|
|
57
|
+
},
|
|
58
|
+
// 与 `.value =` 赋值同一条路径:判等短路、debug hook、notify 全部一致。
|
|
59
|
+
// 以闭包实现,可安全地作为回调直接传递(无 this 绑定问题)。
|
|
60
|
+
set(next) {
|
|
61
|
+
signalInstance.value = next;
|
|
62
|
+
},
|
|
63
|
+
dispose() {
|
|
64
|
+
if (disposed) return;
|
|
65
|
+
disposed = true;
|
|
66
|
+
subscribers.clear();
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
if (debugName?.trim()) setSignalDebugName(signalInstance, debugName.trim());
|
|
70
|
+
return signalInstance;
|
|
71
|
+
}
|
|
72
|
+
__name(state, "state");
|
|
73
|
+
|
|
74
|
+
// packages/reactivity/src/scheduler.ts
|
|
75
|
+
var _Scheduler = class _Scheduler {
|
|
76
|
+
constructor() {
|
|
77
|
+
this.dirtyEffects = /* @__PURE__ */ new Set();
|
|
78
|
+
this.lowPriorityEffects = /* @__PURE__ */ new Set();
|
|
79
|
+
// flush 不可重入(flushing 标志保证),缓冲数组可在轮次间安全复用,避免每轮分配。
|
|
80
|
+
this.normalBuffer = [];
|
|
81
|
+
this.lowBuffer = [];
|
|
82
|
+
this.flushing = false;
|
|
83
|
+
this.scheduled = false;
|
|
84
|
+
this.batchDepth = 0;
|
|
85
|
+
}
|
|
86
|
+
schedule(effect2) {
|
|
87
|
+
if (effect2.disposed) return;
|
|
88
|
+
this.dirtyEffects.add(effect2);
|
|
89
|
+
this.lowPriorityEffects.delete(effect2);
|
|
90
|
+
this.ensureScheduled();
|
|
91
|
+
}
|
|
92
|
+
/** Queue an effect behind normal updates while preserving deterministic order. */
|
|
93
|
+
scheduleLow(effect2) {
|
|
94
|
+
if (effect2.disposed) return;
|
|
95
|
+
if (!this.dirtyEffects.has(effect2)) this.lowPriorityEffects.add(effect2);
|
|
96
|
+
this.ensureScheduled();
|
|
97
|
+
}
|
|
98
|
+
ensureScheduled() {
|
|
99
|
+
if (this.batchDepth === 0 && !this.flushing && !this.scheduled) {
|
|
100
|
+
this.scheduled = true;
|
|
101
|
+
queueMicrotask(() => {
|
|
102
|
+
this.scheduled = false;
|
|
103
|
+
this.flush();
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
remove(effect2) {
|
|
108
|
+
this.dirtyEffects.delete(effect2);
|
|
109
|
+
this.lowPriorityEffects.delete(effect2);
|
|
110
|
+
}
|
|
111
|
+
batch(fn) {
|
|
112
|
+
this.batchDepth++;
|
|
113
|
+
try {
|
|
114
|
+
return fn();
|
|
115
|
+
} finally {
|
|
116
|
+
this.batchDepth--;
|
|
117
|
+
if (this.batchDepth === 0) this.flush();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
flush() {
|
|
121
|
+
if (this.flushing || this.batchDepth > 0) return;
|
|
122
|
+
this.flushing = true;
|
|
123
|
+
let rounds = 0;
|
|
124
|
+
let firstError;
|
|
125
|
+
let hasError = false;
|
|
126
|
+
try {
|
|
127
|
+
while (this.dirtyEffects.size > 0 || this.lowPriorityEffects.size > 0) {
|
|
128
|
+
if (++rounds > 100) {
|
|
129
|
+
this.dirtyEffects.clear();
|
|
130
|
+
this.lowPriorityEffects.clear();
|
|
131
|
+
throw new Error("Vobs: \u54CD\u5E94\u5F0F\u66F4\u65B0\u8D85\u8FC7 100 \u8F6E\uFF0C\u53EF\u80FD\u5B58\u5728\u5FAA\u73AF\u4F9D\u8D56");
|
|
132
|
+
}
|
|
133
|
+
this.collectRunnable(this.dirtyEffects, this.normalBuffer);
|
|
134
|
+
this.collectRunnable(this.lowPriorityEffects, this.lowBuffer);
|
|
135
|
+
sortEffects(this.normalBuffer);
|
|
136
|
+
sortEffects(this.lowBuffer);
|
|
137
|
+
for (const effect2 of this.normalBuffer) {
|
|
138
|
+
try {
|
|
139
|
+
effect2.run();
|
|
140
|
+
} catch (error) {
|
|
141
|
+
if (!hasError) {
|
|
142
|
+
firstError = error;
|
|
143
|
+
hasError = true;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
for (const effect2 of this.lowBuffer) {
|
|
148
|
+
try {
|
|
149
|
+
effect2.run();
|
|
150
|
+
} catch (error) {
|
|
151
|
+
if (!hasError) {
|
|
152
|
+
firstError = error;
|
|
153
|
+
hasError = true;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
this.normalBuffer.length = 0;
|
|
158
|
+
this.lowBuffer.length = 0;
|
|
159
|
+
}
|
|
160
|
+
} finally {
|
|
161
|
+
this.normalBuffer.length = 0;
|
|
162
|
+
this.lowBuffer.length = 0;
|
|
163
|
+
this.flushing = false;
|
|
164
|
+
}
|
|
165
|
+
if (hasError) throw firstError;
|
|
166
|
+
}
|
|
167
|
+
/** 收集未 disposed 的 effect 并清空源集合;run() 期间新调度的 effect 留给下一轮。 */
|
|
168
|
+
collectRunnable(source, target) {
|
|
169
|
+
for (const effect2 of source) {
|
|
170
|
+
if (!effect2.disposed) target.push(effect2);
|
|
171
|
+
}
|
|
172
|
+
source.clear();
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
__name(_Scheduler, "Scheduler");
|
|
176
|
+
function sortEffects(effects) {
|
|
177
|
+
if (effects.length > 1) {
|
|
178
|
+
effects.sort((a, b) => b.depth - a.depth || a.order - b.order);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
__name(sortEffects, "sortEffects");
|
|
182
|
+
|
|
183
|
+
// packages/runtime/src/hmr.ts
|
|
184
|
+
var globalTarget = globalThis;
|
|
185
|
+
var hmrGlobal = globalTarget.__VOBS_HMR__ ?? { modules: /* @__PURE__ */ new Map() };
|
|
186
|
+
globalTarget.__VOBS_HMR__ = hmrGlobal;
|
|
187
|
+
|
|
188
|
+
// packages/vobs/src/context.ts
|
|
189
|
+
var ownerProviders = /* @__PURE__ */ new WeakMap();
|
|
190
|
+
function inject(key, fallback) {
|
|
191
|
+
const owner = getCurrentOwner();
|
|
192
|
+
const value = owner ? injectFromOwner(owner, key) : void 0;
|
|
193
|
+
return value === void 0 ? fallback : value;
|
|
194
|
+
}
|
|
195
|
+
__name(inject, "inject");
|
|
196
|
+
function injectFromOwner(owner, key) {
|
|
197
|
+
let current = owner;
|
|
198
|
+
while (current) {
|
|
199
|
+
const providers = ownerProviders.get(current);
|
|
200
|
+
if (providers?.has(key)) return providers.get(key);
|
|
201
|
+
current = current.parent;
|
|
202
|
+
}
|
|
203
|
+
return void 0;
|
|
204
|
+
}
|
|
205
|
+
__name(injectFromOwner, "injectFromOwner");
|
|
206
|
+
|
|
207
|
+
// packages/vobs/src/app.ts
|
|
208
|
+
function createInjectionKey(description) {
|
|
209
|
+
return Symbol(description);
|
|
210
|
+
}
|
|
211
|
+
__name(createInjectionKey, "createInjectionKey");
|
|
212
|
+
|
|
213
|
+
// packages/dict/src/index.ts
|
|
214
|
+
var _DictError = class _DictError extends Error {
|
|
215
|
+
constructor(code, message, cause) {
|
|
216
|
+
super(message);
|
|
217
|
+
this.name = "DictError";
|
|
218
|
+
this.code = code;
|
|
219
|
+
this.cause = cause;
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
__name(_DictError, "DictError");
|
|
223
|
+
var DictError = _DictError;
|
|
224
|
+
var DICT_KEY = createInjectionKey("vobs.dict");
|
|
225
|
+
var EMPTY_ITEMS = Object.freeze([]);
|
|
226
|
+
function createDict(options = {}) {
|
|
227
|
+
const staleTime = validateStaleTime(options.staleTime ?? 5 * 60 * 1e3);
|
|
228
|
+
const now = options.now ?? Date.now;
|
|
229
|
+
const initial = normalizeData(options.data ?? {});
|
|
230
|
+
const data = state(initial);
|
|
231
|
+
const loading = state(false);
|
|
232
|
+
const error = state(null);
|
|
233
|
+
const entries = /* @__PURE__ */ new Map();
|
|
234
|
+
let disposed = false;
|
|
235
|
+
for (const [name, items] of Object.entries(initial)) {
|
|
236
|
+
const entry = createEntry(name, items, now());
|
|
237
|
+
entries.set(name, entry);
|
|
238
|
+
}
|
|
239
|
+
const context = {
|
|
240
|
+
data,
|
|
241
|
+
loading,
|
|
242
|
+
error,
|
|
243
|
+
staleTime,
|
|
244
|
+
get(name) {
|
|
245
|
+
ensureActive();
|
|
246
|
+
return data.value[validateName(name)] ?? EMPTY_ITEMS;
|
|
247
|
+
},
|
|
248
|
+
find(name, value) {
|
|
249
|
+
ensureActive();
|
|
250
|
+
return context.get(name).find((item) => item.value === value);
|
|
251
|
+
},
|
|
252
|
+
label(name, value, fallback) {
|
|
253
|
+
ensureActive();
|
|
254
|
+
return context.find(name, value)?.label ?? fallback ?? String(value);
|
|
255
|
+
},
|
|
256
|
+
query(name) {
|
|
257
|
+
ensureActive();
|
|
258
|
+
return getEntry(validateName(name));
|
|
259
|
+
},
|
|
260
|
+
load(name, loadOptions = {}) {
|
|
261
|
+
ensureActive();
|
|
262
|
+
const entry = getEntry(validateName(name));
|
|
263
|
+
if (!loadOptions.force && isFresh(entry)) return Promise.resolve(entry.items.value);
|
|
264
|
+
if (entry.inFlight) return entry.inFlight;
|
|
265
|
+
if (!options.loader) {
|
|
266
|
+
const loadError = new DictError("DICT_LOADER_MISSING", `Vobs Dict: ${entry.name} \u672A\u914D\u7F6E loader`);
|
|
267
|
+
entry.error.value = loadError;
|
|
268
|
+
error.value = loadError;
|
|
269
|
+
report(loadError, entry.name);
|
|
270
|
+
return Promise.reject(loadError);
|
|
271
|
+
}
|
|
272
|
+
const revision = ++entry.revision;
|
|
273
|
+
const controller = new AbortController();
|
|
274
|
+
entry.controller = controller;
|
|
275
|
+
entry.loading.value = true;
|
|
276
|
+
refreshLoading();
|
|
277
|
+
const request = Promise.resolve().then(() => options.loader(entry.name, controller.signal)).then((items) => {
|
|
278
|
+
if (disposed || revision !== entry.revision) return entry.items.value;
|
|
279
|
+
const normalized = normalizeItems(items);
|
|
280
|
+
commit(entry, normalized, now(), false);
|
|
281
|
+
return normalized;
|
|
282
|
+
}).catch((reason) => {
|
|
283
|
+
if (disposed || revision !== entry.revision || isAbortError(reason)) return entry.items.value;
|
|
284
|
+
const loadError = reason instanceof DictError ? reason : new DictError("DICT_LOAD_FAILED", `Vobs Dict: ${entry.name} \u52A0\u8F7D\u5931\u8D25`, reason);
|
|
285
|
+
entry.error.value = loadError;
|
|
286
|
+
error.value = loadError;
|
|
287
|
+
report(loadError, entry.name);
|
|
288
|
+
throw loadError;
|
|
289
|
+
}).finally(() => {
|
|
290
|
+
if (revision !== entry.revision) return;
|
|
291
|
+
entry.inFlight = null;
|
|
292
|
+
entry.controller = null;
|
|
293
|
+
entry.loading.value = false;
|
|
294
|
+
refreshLoading();
|
|
295
|
+
});
|
|
296
|
+
entry.inFlight = request;
|
|
297
|
+
return request;
|
|
298
|
+
},
|
|
299
|
+
set(name, items) {
|
|
300
|
+
ensureActive();
|
|
301
|
+
commit(getEntry(validateName(name)), normalizeItems(items), now());
|
|
302
|
+
},
|
|
303
|
+
invalidate(name) {
|
|
304
|
+
ensureActive();
|
|
305
|
+
if (name === void 0) {
|
|
306
|
+
for (const entry of entries.values()) invalidateEntry(entry);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
invalidateEntry(getEntry(validateName(name)));
|
|
310
|
+
},
|
|
311
|
+
dehydrate() {
|
|
312
|
+
ensureActive();
|
|
313
|
+
return {
|
|
314
|
+
version: 1,
|
|
315
|
+
entries: Object.freeze([...entries.values()].filter((entry) => entry.updatedAt.value > 0).map((entry) => Object.freeze({
|
|
316
|
+
name: entry.name,
|
|
317
|
+
items: entry.items.value,
|
|
318
|
+
updatedAt: entry.updatedAt.value
|
|
319
|
+
})))
|
|
320
|
+
};
|
|
321
|
+
},
|
|
322
|
+
hydrate(snapshot) {
|
|
323
|
+
ensureActive();
|
|
324
|
+
const restored = parseDehydratedState(snapshot);
|
|
325
|
+
const nextData = {};
|
|
326
|
+
const restoredNames = /* @__PURE__ */ new Set();
|
|
327
|
+
for (const item of restored.entries) {
|
|
328
|
+
const entry = getEntry(item.name);
|
|
329
|
+
entry.revision++;
|
|
330
|
+
entry.controller?.abort();
|
|
331
|
+
entry.controller = null;
|
|
332
|
+
entry.inFlight = null;
|
|
333
|
+
entry.loading.value = false;
|
|
334
|
+
entry.items.value = item.items;
|
|
335
|
+
entry.error.value = null;
|
|
336
|
+
entry.updatedAt.value = item.updatedAt;
|
|
337
|
+
nextData[item.name] = item.items;
|
|
338
|
+
restoredNames.add(item.name);
|
|
339
|
+
}
|
|
340
|
+
for (const [name, entry] of entries) {
|
|
341
|
+
if (restoredNames.has(name)) continue;
|
|
342
|
+
entry.revision++;
|
|
343
|
+
entry.controller?.abort();
|
|
344
|
+
entry.controller = null;
|
|
345
|
+
entry.inFlight = null;
|
|
346
|
+
entry.loading.value = false;
|
|
347
|
+
entry.items.value = EMPTY_ITEMS;
|
|
348
|
+
entry.error.value = null;
|
|
349
|
+
entry.updatedAt.value = 0;
|
|
350
|
+
}
|
|
351
|
+
data.value = Object.freeze(nextData);
|
|
352
|
+
error.value = null;
|
|
353
|
+
refreshLoading();
|
|
354
|
+
},
|
|
355
|
+
dispose() {
|
|
356
|
+
if (disposed) return;
|
|
357
|
+
disposed = true;
|
|
358
|
+
for (const entry of entries.values()) {
|
|
359
|
+
entry.controller?.abort();
|
|
360
|
+
entry.items.dispose();
|
|
361
|
+
entry.loading.dispose();
|
|
362
|
+
entry.error.dispose();
|
|
363
|
+
entry.updatedAt.dispose();
|
|
364
|
+
}
|
|
365
|
+
entries.clear();
|
|
366
|
+
data.dispose();
|
|
367
|
+
loading.dispose();
|
|
368
|
+
error.dispose();
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
return context;
|
|
372
|
+
function getEntry(name) {
|
|
373
|
+
const existing = entries.get(name);
|
|
374
|
+
if (existing) return existing;
|
|
375
|
+
const entry = createEntry(name, data.value[name] ?? EMPTY_ITEMS, 0);
|
|
376
|
+
entries.set(name, entry);
|
|
377
|
+
return entry;
|
|
378
|
+
}
|
|
379
|
+
function createEntry(name, items, updatedAt) {
|
|
380
|
+
const entry = {
|
|
381
|
+
name,
|
|
382
|
+
items: state(items),
|
|
383
|
+
loading: state(false),
|
|
384
|
+
error: state(null),
|
|
385
|
+
updatedAt: state(updatedAt),
|
|
386
|
+
controller: null,
|
|
387
|
+
inFlight: null,
|
|
388
|
+
revision: 0,
|
|
389
|
+
load: /* @__PURE__ */ __name((loadOptions) => context.load(name, loadOptions), "load"),
|
|
390
|
+
invalidate: /* @__PURE__ */ __name(() => context.invalidate(name), "invalidate")
|
|
391
|
+
};
|
|
392
|
+
return entry;
|
|
393
|
+
}
|
|
394
|
+
function commit(entry, items, updatedAt, replaceRequest = true) {
|
|
395
|
+
if (replaceRequest) {
|
|
396
|
+
entry.revision++;
|
|
397
|
+
entry.controller?.abort();
|
|
398
|
+
entry.controller = null;
|
|
399
|
+
entry.inFlight = null;
|
|
400
|
+
}
|
|
401
|
+
entry.items.value = items;
|
|
402
|
+
entry.error.value = null;
|
|
403
|
+
entry.updatedAt.value = updatedAt;
|
|
404
|
+
data.value = Object.freeze({ ...data.value, [entry.name]: items });
|
|
405
|
+
error.value = null;
|
|
406
|
+
refreshLoading();
|
|
407
|
+
}
|
|
408
|
+
function invalidateEntry(entry) {
|
|
409
|
+
entry.revision++;
|
|
410
|
+
entry.controller?.abort();
|
|
411
|
+
entry.controller = null;
|
|
412
|
+
entry.inFlight = null;
|
|
413
|
+
entry.loading.value = false;
|
|
414
|
+
entry.updatedAt.value = 0;
|
|
415
|
+
refreshLoading();
|
|
416
|
+
}
|
|
417
|
+
function isFresh(entry) {
|
|
418
|
+
return entry.updatedAt.value > 0 && now() - entry.updatedAt.value < staleTime;
|
|
419
|
+
}
|
|
420
|
+
function refreshLoading() {
|
|
421
|
+
loading.value = [...entries.values()].some((entry) => entry.loading.value);
|
|
422
|
+
}
|
|
423
|
+
function report(dictError, name) {
|
|
424
|
+
try {
|
|
425
|
+
options.onError?.(dictError, name);
|
|
426
|
+
} catch {
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
function ensureActive() {
|
|
430
|
+
if (disposed) throw new DictError("DICT_CONTEXT_DISPOSED", "Vobs Dict: \u4E0A\u4E0B\u6587\u5DF2\u9500\u6BC1");
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
__name(createDict, "createDict");
|
|
434
|
+
function dictPlugin(options = {}) {
|
|
435
|
+
return {
|
|
436
|
+
name: "@vobs/dict",
|
|
437
|
+
version: "0.1.0",
|
|
438
|
+
install(context) {
|
|
439
|
+
const ownedDict = options.dict ? void 0 : createDict(options);
|
|
440
|
+
context.provide(DICT_KEY, options.dict ?? ownedDict);
|
|
441
|
+
return () => ownedDict?.dispose();
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
__name(dictPlugin, "dictPlugin");
|
|
446
|
+
function useDict() {
|
|
447
|
+
const dict = inject(DICT_KEY);
|
|
448
|
+
if (!dict) throw new DictError("DICT_CONTEXT_MISSING", "Vobs Dict: \u627E\u4E0D\u5230\u4E0A\u4E0B\u6587\uFF0C\u8BF7\u5B89\u88C5 dictPlugin");
|
|
449
|
+
return dict;
|
|
450
|
+
}
|
|
451
|
+
__name(useDict, "useDict");
|
|
452
|
+
function validateName(name) {
|
|
453
|
+
if (typeof name !== "string" || name.trim() === "") {
|
|
454
|
+
throw new DictError("INVALID_DICT_NAME", "Vobs Dict: \u5B57\u5178\u540D\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");
|
|
455
|
+
}
|
|
456
|
+
return name;
|
|
457
|
+
}
|
|
458
|
+
__name(validateName, "validateName");
|
|
459
|
+
function validateStaleTime(value) {
|
|
460
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
461
|
+
throw new DictError("INVALID_DICT_ITEMS", "Vobs Dict: staleTime \u5FC5\u987B\u662F\u5927\u4E8E\u7B49\u4E8E 0 \u7684\u6709\u9650\u6570\u5B57");
|
|
462
|
+
}
|
|
463
|
+
return value;
|
|
464
|
+
}
|
|
465
|
+
__name(validateStaleTime, "validateStaleTime");
|
|
466
|
+
function normalizeData(data) {
|
|
467
|
+
const normalized = {};
|
|
468
|
+
for (const [name, items] of Object.entries(data)) normalized[validateName(name)] = normalizeItems(items);
|
|
469
|
+
return Object.freeze(normalized);
|
|
470
|
+
}
|
|
471
|
+
__name(normalizeData, "normalizeData");
|
|
472
|
+
function normalizeItems(items) {
|
|
473
|
+
if (!Array.isArray(items)) throw new DictError("INVALID_DICT_ITEMS", "Vobs Dict: \u5B57\u5178\u9879\u5FC5\u987B\u662F\u6570\u7EC4");
|
|
474
|
+
return Object.freeze(items.map((item, index) => {
|
|
475
|
+
if (!item || typeof item !== "object" || typeof item.value !== "string" && typeof item.value !== "number" || typeof item.label !== "string") {
|
|
476
|
+
throw new DictError("INVALID_DICT_ITEMS", `Vobs Dict: \u7B2C ${index + 1} \u4E2A\u5B57\u5178\u9879\u5FC5\u987B\u5305\u542B value \u548C label`);
|
|
477
|
+
}
|
|
478
|
+
return Object.freeze({ ...item });
|
|
479
|
+
}));
|
|
480
|
+
}
|
|
481
|
+
__name(normalizeItems, "normalizeItems");
|
|
482
|
+
function parseDehydratedState(snapshot) {
|
|
483
|
+
let value = snapshot;
|
|
484
|
+
if (typeof snapshot === "string") {
|
|
485
|
+
try {
|
|
486
|
+
value = JSON.parse(snapshot);
|
|
487
|
+
} catch {
|
|
488
|
+
throw new DictError("INVALID_DEHYDRATED_STATE", "Vobs Dict: \u521D\u59CB\u72B6\u6001\u4E0D\u662F\u6709\u6548 JSON");
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
if (!value || typeof value !== "object") {
|
|
492
|
+
throw new DictError("INVALID_DEHYDRATED_STATE", "Vobs Dict: \u521D\u59CB\u72B6\u6001\u683C\u5F0F\u65E0\u6548");
|
|
493
|
+
}
|
|
494
|
+
const candidate = value;
|
|
495
|
+
if (candidate.version !== 1 || !Array.isArray(candidate.entries)) {
|
|
496
|
+
throw new DictError("INVALID_DEHYDRATED_STATE", "Vobs Dict: \u521D\u59CB\u72B6\u6001\u7248\u672C\u6216 entries \u65E0\u6548");
|
|
497
|
+
}
|
|
498
|
+
const seen = /* @__PURE__ */ new Set();
|
|
499
|
+
const entries = candidate.entries.map((entry) => {
|
|
500
|
+
if (!entry || typeof entry !== "object") {
|
|
501
|
+
throw new DictError("INVALID_DEHYDRATED_STATE", "Vobs Dict: \u521D\u59CB\u72B6\u6001\u6761\u76EE\u65E0\u6548");
|
|
502
|
+
}
|
|
503
|
+
const name = validateName(entry.name);
|
|
504
|
+
const updatedAt = entry.updatedAt;
|
|
505
|
+
if (seen.has(name) || typeof updatedAt !== "number" || !Number.isFinite(updatedAt) || updatedAt < 0) {
|
|
506
|
+
throw new DictError("INVALID_DEHYDRATED_STATE", "Vobs Dict: \u521D\u59CB\u72B6\u6001\u6761\u76EE\u5B57\u6BB5\u65E0\u6548");
|
|
507
|
+
}
|
|
508
|
+
seen.add(name);
|
|
509
|
+
return Object.freeze({
|
|
510
|
+
name,
|
|
511
|
+
items: normalizeItems(entry.items),
|
|
512
|
+
updatedAt
|
|
513
|
+
});
|
|
514
|
+
});
|
|
515
|
+
return { version: 1, entries: Object.freeze(entries) };
|
|
516
|
+
}
|
|
517
|
+
__name(parseDehydratedState, "parseDehydratedState");
|
|
518
|
+
function isAbortError(value) {
|
|
519
|
+
return Boolean(value) && typeof value === "object" && value.name === "AbortError";
|
|
520
|
+
}
|
|
521
|
+
__name(isAbortError, "isAbortError");
|
|
522
|
+
|
|
523
|
+
export { DICT_KEY, DictError, createDict, dictPlugin, useDict };
|
|
524
|
+
//# sourceMappingURL=index.js.map
|
|
525
|
+
//# sourceMappingURL=index.js.map
|