weifuwu 0.33.11 → 0.35.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 +95 -87
- package/dist/client/app.d.ts +18 -27
- package/dist/client/error-boundary.d.ts +23 -0
- package/dist/client/index.d.ts +25 -233
- package/dist/client/index.js +698 -893
- package/dist/client/middleware/api.d.ts +2 -0
- package/dist/client/middleware/auth.d.ts +9 -60
- package/dist/client/middleware/ws.d.ts +4 -10
- package/dist/client/render.d.ts +17 -0
- package/dist/client/router.d.ts +5 -53
- package/dist/client/types.d.ts +51 -92
- package/dist/client/vnode.d.ts +38 -0
- package/dist/core/router.d.ts +22 -4
- package/dist/index.d.ts +3 -0
- package/dist/index.js +218 -56
- package/dist/middleware/static.d.ts +8 -3
- package/dist/postgres/client.d.ts +1 -1
- package/dist/postgres/index.d.ts +0 -1
- package/dist/postgres/types.d.ts +5 -1
- package/dist/redis/client.d.ts +1 -1
- package/dist/request.d.ts +23 -0
- package/dist/response.d.ts +46 -0
- package/dist/types.d.ts +29 -0
- package/package.json +2 -3
- package/dist/client/form.d.ts +0 -111
- package/dist/client/jsx-runtime.d.ts +0 -481
- package/dist/client/jsx-runtime.js +0 -1073
- package/dist/client/lazy.d.ts +0 -50
- package/dist/client/resource.d.ts +0 -50
- package/dist/client/signal.d.ts +0 -65
- package/dist/postgres/module.d.ts +0 -12
package/dist/client/index.js
CHANGED
|
@@ -1,809 +1,674 @@
|
|
|
1
|
-
// src/client/
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
constructor(value) {
|
|
10
|
-
this.#value = value;
|
|
11
|
-
}
|
|
12
|
-
get value() {
|
|
13
|
-
if (currentEffect) {
|
|
14
|
-
this.#listeners.add(currentEffect);
|
|
15
|
-
currentDeps?.add(this);
|
|
16
|
-
}
|
|
17
|
-
return this.#value;
|
|
18
|
-
}
|
|
19
|
-
set value(v) {
|
|
20
|
-
if (v !== this.#value) {
|
|
21
|
-
this.#value = v;
|
|
22
|
-
this.#notify();
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
/** @internal 移除监听器(由 effect dispose 调用) */
|
|
26
|
-
_removeListener(fn) {
|
|
27
|
-
this.#listeners.delete(fn);
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* 可变更新 — 原地修改信号值并触发通知。
|
|
31
|
-
*
|
|
32
|
-
* 适用于数组/对象等引用类型:无需创建新引用即可触发更新。
|
|
33
|
-
*
|
|
34
|
-
* ```ts
|
|
35
|
-
* const items = signal([1, 2, 3])
|
|
36
|
-
* items.mutate(arr => arr.push(4)) // 数组原地修改
|
|
37
|
-
* // items.value === [1, 2, 3, 4]
|
|
38
|
-
* ```
|
|
39
|
-
*/
|
|
40
|
-
mutate(fn) {
|
|
41
|
-
fn(this.#value);
|
|
42
|
-
this.#notify();
|
|
43
|
-
}
|
|
44
|
-
/** 通知所有监听器 — 批量模式时积攒,否则立即执行 */
|
|
45
|
-
#notify() {
|
|
46
|
-
if (_batchDepth > 0) {
|
|
47
|
-
for (const fn of this.#listeners) _pendingBatch.add(fn);
|
|
48
|
-
} else {
|
|
49
|
-
const fns = [...this.#listeners];
|
|
50
|
-
for (const fn of fns) fn();
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
function signal(initial) {
|
|
55
|
-
return new Signal(initial);
|
|
1
|
+
// src/client/vnode.ts
|
|
2
|
+
var Fragment = /* @__PURE__ */ Symbol("Fragment");
|
|
3
|
+
function jsx(type, props, key) {
|
|
4
|
+
return {
|
|
5
|
+
type,
|
|
6
|
+
props: normalizeProps(props),
|
|
7
|
+
key: key ?? void 0
|
|
8
|
+
};
|
|
56
9
|
}
|
|
57
|
-
|
|
58
|
-
|
|
10
|
+
var jsxs = jsx;
|
|
11
|
+
function jsxDEV(type, props, key) {
|
|
12
|
+
return jsx(type, props, key);
|
|
59
13
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
try {
|
|
70
|
-
fn();
|
|
71
|
-
} finally {
|
|
72
|
-
currentEffect = prevEffect;
|
|
73
|
-
currentDeps = prevDeps;
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
run();
|
|
77
|
-
return () => {
|
|
78
|
-
for (const dep of deps) dep._removeListener(run);
|
|
79
|
-
deps.clear();
|
|
80
|
-
};
|
|
14
|
+
var h = jsx;
|
|
15
|
+
function normalizeProps(props) {
|
|
16
|
+
if (!props) return {};
|
|
17
|
+
const result = {};
|
|
18
|
+
for (const key of Object.keys(props)) {
|
|
19
|
+
if (key === "key") continue;
|
|
20
|
+
result[key] = props[key];
|
|
21
|
+
}
|
|
22
|
+
return result;
|
|
81
23
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
24
|
+
|
|
25
|
+
// src/client/render.ts
|
|
26
|
+
function render(input, ctx) {
|
|
27
|
+
return renderValue(input, ctx);
|
|
28
|
+
}
|
|
29
|
+
function renderValue(v, ctx) {
|
|
30
|
+
if (v == null || typeof v === "boolean") return document.createTextNode("");
|
|
31
|
+
if (typeof v === "string" || typeof v === "number") return document.createTextNode(String(v));
|
|
32
|
+
if (Array.isArray(v)) return renderArray(v, ctx);
|
|
33
|
+
const vnode = v;
|
|
34
|
+
if (vnode.type === Fragment) {
|
|
35
|
+
const frag = document.createDocumentFragment();
|
|
36
|
+
forEach(vnode.props?.children, (child) => frag.appendChild(renderValue(child, ctx)));
|
|
37
|
+
return frag;
|
|
38
|
+
}
|
|
39
|
+
if (typeof vnode.type === "function") {
|
|
40
|
+
return renderComponent(vnode.type, vnode.props, vnode, ctx);
|
|
41
|
+
}
|
|
42
|
+
const el = document.createElement(vnode.type);
|
|
43
|
+
vnode.el = el;
|
|
44
|
+
for (const [key, value] of Object.entries(vnode.props ?? {})) {
|
|
45
|
+
if (key === "children" || key === "key" || key === "ref") continue;
|
|
46
|
+
setProp(el, key, value);
|
|
47
|
+
}
|
|
48
|
+
const flatChildren = flattenChildren(vnode.props?.children);
|
|
49
|
+
for (const child of flatChildren) {
|
|
50
|
+
el.appendChild(renderValue(child, ctx));
|
|
51
|
+
}
|
|
52
|
+
if (vnode.props?.ref) {
|
|
53
|
+
queueMicrotask(() => vnode.props.ref(el));
|
|
54
|
+
}
|
|
55
|
+
return el;
|
|
88
56
|
}
|
|
89
|
-
function
|
|
90
|
-
|
|
57
|
+
function renderComponent(Comp, props, vnode, ctx) {
|
|
58
|
+
const prev$ = vnode._$;
|
|
59
|
+
if (!prev$) vnode._$ = {};
|
|
60
|
+
ctx.ui = ctx.ui ?? {};
|
|
61
|
+
ctx.ui.ready = !!prev$;
|
|
62
|
+
let childVNode;
|
|
91
63
|
try {
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
64
|
+
childVNode = Comp(props, ctx);
|
|
65
|
+
} catch (e) {
|
|
66
|
+
const errHandler = ctx.ui?._errorHandler;
|
|
67
|
+
if (errHandler) {
|
|
68
|
+
errHandler(e);
|
|
69
|
+
childVNode = null;
|
|
70
|
+
} else {
|
|
71
|
+
console.error("Component render error:", e);
|
|
72
|
+
childVNode = null;
|
|
99
73
|
}
|
|
100
74
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const prevDeps = currentDeps;
|
|
105
|
-
currentEffect = null;
|
|
106
|
-
currentDeps = null;
|
|
107
|
-
try {
|
|
108
|
-
return fn();
|
|
109
|
-
} finally {
|
|
110
|
-
currentEffect = prevEffect;
|
|
111
|
-
currentDeps = prevDeps;
|
|
75
|
+
if (childVNode == null) {
|
|
76
|
+
vnode._child = null;
|
|
77
|
+
return document.createTextNode("");
|
|
112
78
|
}
|
|
79
|
+
vnode._child = childVNode;
|
|
80
|
+
return renderValue(childVNode, ctx);
|
|
113
81
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
currentCtx = ctx;
|
|
119
|
-
}
|
|
120
|
-
function onMount(fn) {
|
|
121
|
-
if (_pendingMountQueue) _pendingMountQueue.push(fn);
|
|
82
|
+
function renderArray(arr, ctx) {
|
|
83
|
+
const frag = document.createDocumentFragment();
|
|
84
|
+
for (const item of arr) frag.appendChild(renderValue(item, ctx));
|
|
85
|
+
return frag;
|
|
122
86
|
}
|
|
123
|
-
function
|
|
124
|
-
if (
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
let entry = _entries.get(el);
|
|
131
|
-
if (entry) return entry;
|
|
132
|
-
entry = {
|
|
133
|
-
mounted: document.contains(el),
|
|
134
|
-
observer: null,
|
|
135
|
-
mountFns: [],
|
|
136
|
-
disposeFns: []
|
|
137
|
-
};
|
|
138
|
-
_entries.set(el, entry);
|
|
139
|
-
const obs = new MutationObserver(() => {
|
|
140
|
-
const now = document.contains(el);
|
|
141
|
-
if (now && !entry.mounted) {
|
|
142
|
-
entry.mounted = true;
|
|
143
|
-
const fns = entry.mountFns.slice();
|
|
144
|
-
entry.mountFns = [];
|
|
145
|
-
for (const fn of fns) {
|
|
146
|
-
const dispose = fn();
|
|
147
|
-
if (typeof dispose === "function") {
|
|
148
|
-
entry.disposeFns.push(dispose);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
} else if (!now && entry.mounted) {
|
|
152
|
-
entry.mounted = false;
|
|
153
|
-
for (const fn of entry.disposeFns) fn();
|
|
154
|
-
entry.disposeFns = [];
|
|
155
|
-
entry.mountFns = [];
|
|
156
|
-
obs.disconnect();
|
|
157
|
-
_entries.delete(el);
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
obs.observe(document.body, { childList: true, subtree: true });
|
|
161
|
-
entry.observer = obs;
|
|
162
|
-
return entry;
|
|
87
|
+
function forEach(children, fn) {
|
|
88
|
+
if (children == null) return;
|
|
89
|
+
if (Array.isArray(children)) {
|
|
90
|
+
children.forEach(fn);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
fn(children);
|
|
163
94
|
}
|
|
164
|
-
function
|
|
165
|
-
|
|
166
|
-
if (
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
95
|
+
function flattenChildren(children) {
|
|
96
|
+
if (children == null) return [];
|
|
97
|
+
if (!Array.isArray(children)) return [children];
|
|
98
|
+
const result = [];
|
|
99
|
+
for (const child of children) {
|
|
100
|
+
if (Array.isArray(child)) {
|
|
101
|
+
result.push(...child);
|
|
102
|
+
} else {
|
|
103
|
+
result.push(child);
|
|
170
104
|
}
|
|
171
|
-
} else {
|
|
172
|
-
entry.mountFns.push(fn);
|
|
173
105
|
}
|
|
174
|
-
|
|
175
|
-
function wrap(tagName, setup) {
|
|
176
|
-
return (props, ctx) => {
|
|
177
|
-
const el = document.createElement(tagName);
|
|
178
|
-
_onMountElement(el, () => setup(el, props, ctx));
|
|
179
|
-
return el;
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
function _trackEffect(el, dispose) {
|
|
183
|
-
const entry = _ensure(el);
|
|
184
|
-
entry.disposeFns.push(dispose);
|
|
106
|
+
return result;
|
|
185
107
|
}
|
|
186
108
|
function setProp(el, key, value) {
|
|
187
109
|
if (key === "class" || key === "className") {
|
|
188
|
-
|
|
189
|
-
_trackEffect(el, effect(() => {
|
|
190
|
-
el.className = String(value.value);
|
|
191
|
-
}));
|
|
192
|
-
} else {
|
|
193
|
-
el.className = String(value ?? "");
|
|
194
|
-
}
|
|
110
|
+
el.className = String(value ?? "");
|
|
195
111
|
} else if (key === "style" && typeof value === "object" && value !== null) {
|
|
196
112
|
Object.assign(el.style, value);
|
|
197
113
|
} else if (key.startsWith("on") && typeof value === "function") {
|
|
198
114
|
el.addEventListener(key.slice(2).toLowerCase(), value);
|
|
199
|
-
} else if (key === "
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
if (key === "value" || key === "checked") {
|
|
205
|
-
el[key] = v;
|
|
206
|
-
} else if (v == null || v === false) {
|
|
207
|
-
el.removeAttribute(key);
|
|
208
|
-
} else if (v === true) {
|
|
209
|
-
el.setAttribute(key, "");
|
|
210
|
-
} else {
|
|
211
|
-
el.setAttribute(key, String(v));
|
|
212
|
-
}
|
|
213
|
-
}));
|
|
115
|
+
} else if (key === "value" && (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
|
|
116
|
+
;
|
|
117
|
+
el.value = String(value ?? "");
|
|
118
|
+
} else if (value === true) {
|
|
119
|
+
el.setAttribute(key, "");
|
|
214
120
|
} else if (value != null && value !== false) {
|
|
215
|
-
|
|
216
|
-
else if (key === "value" || key === "checked") el[key] = value;
|
|
217
|
-
else el.setAttribute(key, String(value));
|
|
121
|
+
el.setAttribute(key, String(value));
|
|
218
122
|
}
|
|
219
123
|
}
|
|
220
|
-
function
|
|
221
|
-
if (
|
|
222
|
-
|
|
124
|
+
function patchValue(parent, oldNode, oldInput, newInput, ctx) {
|
|
125
|
+
if (oldInput == null) {
|
|
126
|
+
if (newInput == null) return null;
|
|
127
|
+
const node = renderValue(newInput, ctx);
|
|
128
|
+
if (oldNode && oldNode.parentNode) {
|
|
129
|
+
oldNode.parentNode.insertBefore(node, oldNode);
|
|
130
|
+
} else {
|
|
131
|
+
parent.appendChild(node);
|
|
132
|
+
}
|
|
133
|
+
return node;
|
|
134
|
+
}
|
|
135
|
+
if (newInput == null) {
|
|
136
|
+
if (oldNode) {
|
|
137
|
+
callRef(oldInput, null);
|
|
138
|
+
oldNode.remove();
|
|
139
|
+
}
|
|
223
140
|
return null;
|
|
224
141
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
142
|
+
const oldType = typeOf(oldInput);
|
|
143
|
+
const newType = typeOf(newInput);
|
|
144
|
+
if (oldType !== newType) {
|
|
145
|
+
callRef(oldInput, null);
|
|
146
|
+
const node = renderValue(newInput, ctx);
|
|
147
|
+
if (oldNode?.parentNode) {
|
|
148
|
+
oldNode.parentNode.replaceChild(node, oldNode);
|
|
149
|
+
}
|
|
150
|
+
return node;
|
|
151
|
+
}
|
|
152
|
+
if (newType === "text") {
|
|
153
|
+
if (oldNode && oldNode.textContent !== String(newInput)) {
|
|
154
|
+
oldNode.textContent = String(newInput);
|
|
155
|
+
}
|
|
156
|
+
return oldNode;
|
|
157
|
+
}
|
|
158
|
+
const newV = newInput;
|
|
159
|
+
const oldV = oldInput;
|
|
160
|
+
if (typeof newV.type === "function") {
|
|
161
|
+
const comp = newV.type;
|
|
162
|
+
if (oldV._$) newV._$ = oldV._$;
|
|
163
|
+
ctx.ui = ctx.ui ?? {};
|
|
164
|
+
ctx.ui.ready = !!newV._$;
|
|
165
|
+
const childNew = comp(newV.props, ctx);
|
|
166
|
+
newV._child = childNew;
|
|
167
|
+
return patchValue(parent, oldNode, oldV._child, childNew, ctx);
|
|
168
|
+
}
|
|
169
|
+
if (newV.type === Fragment) {
|
|
170
|
+
patchChildren(parent, oldV, newV, ctx);
|
|
171
|
+
return oldNode;
|
|
172
|
+
}
|
|
173
|
+
if (typeof newV.type === "string") {
|
|
174
|
+
if (oldNode && oldNode.nodeType === 1) {
|
|
175
|
+
patchProps(oldNode, oldV.props, newV.props);
|
|
176
|
+
patchChildren(oldNode, oldV, newV, ctx);
|
|
177
|
+
} else if (oldNode) {
|
|
178
|
+
callRef(oldInput, null);
|
|
179
|
+
const node = renderValue(newInput, ctx);
|
|
180
|
+
oldNode.parentNode?.replaceChild(node, oldNode);
|
|
181
|
+
return node;
|
|
182
|
+
}
|
|
183
|
+
return oldNode;
|
|
184
|
+
}
|
|
185
|
+
if (Array.isArray(newInput)) {
|
|
186
|
+
const oldArr = Array.isArray(oldInput) ? oldInput : [];
|
|
187
|
+
patchSimpleChildren(parent, oldArr, newInput, ctx);
|
|
188
|
+
return oldNode;
|
|
189
|
+
}
|
|
190
|
+
return oldNode;
|
|
191
|
+
}
|
|
192
|
+
function typeOf(input) {
|
|
193
|
+
if (input == null || typeof input === "boolean") return "null";
|
|
194
|
+
if (typeof input === "string" || typeof input === "number") return "text";
|
|
195
|
+
if (Array.isArray(input)) return "array";
|
|
196
|
+
const v = input;
|
|
197
|
+
if (typeof v.type === "function") return `fn:${v.type.name || "anon"}`;
|
|
198
|
+
if (v.type === Fragment) return "fragment";
|
|
199
|
+
if (typeof v.type === "string") return "tag:" + v.type;
|
|
200
|
+
return "unknown";
|
|
201
|
+
}
|
|
202
|
+
function patchProps(el, oldProps, newProps) {
|
|
203
|
+
const oldKeys = oldProps ? Object.keys(oldProps).filter((k) => k !== "children" && k !== "key" && k !== "ref") : [];
|
|
204
|
+
const newKeys = newProps ? Object.keys(newProps).filter((k) => k !== "children" && k !== "key" && k !== "ref") : [];
|
|
205
|
+
for (const key of oldKeys) {
|
|
206
|
+
if (!newKeys.includes(key)) {
|
|
207
|
+
if (key === "value" && (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
|
|
208
|
+
;
|
|
209
|
+
el.value = "";
|
|
210
|
+
} else {
|
|
211
|
+
el.removeAttribute(key === "className" ? "class" : key);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
for (const key of newKeys) {
|
|
216
|
+
const oldVal = oldProps?.[key];
|
|
217
|
+
const newVal = newProps?.[key];
|
|
218
|
+
if (newVal !== oldVal) {
|
|
219
|
+
if (key === "class" || key === "className") {
|
|
220
|
+
el.className = String(newVal ?? "");
|
|
221
|
+
} else if (key === "style" && typeof newVal === "object") {
|
|
222
|
+
Object.assign(el.style, newVal);
|
|
223
|
+
} else if (key.startsWith("on") && typeof newVal === "function") {
|
|
224
|
+
const eventName = key.slice(2).toLowerCase();
|
|
225
|
+
if (typeof oldVal === "function") el.removeEventListener(eventName, oldVal);
|
|
226
|
+
el.addEventListener(eventName, newVal);
|
|
227
|
+
} else if (key === "value" && (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
|
|
228
|
+
;
|
|
229
|
+
el.value = String(newVal ?? "");
|
|
230
|
+
} else if (newVal === true) {
|
|
231
|
+
el.setAttribute(key, "");
|
|
232
|
+
} else if (newVal != null && newVal !== false) {
|
|
233
|
+
el.setAttribute(key, String(newVal));
|
|
234
|
+
} else {
|
|
235
|
+
if (key === "value" && (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
|
|
236
|
+
;
|
|
237
|
+
el.value = "";
|
|
238
|
+
} else {
|
|
239
|
+
el.removeAttribute(key);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
229
243
|
}
|
|
230
|
-
return null;
|
|
231
244
|
}
|
|
232
|
-
function
|
|
233
|
-
if (
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
245
|
+
function getKey(input) {
|
|
246
|
+
if (input == null || typeof input !== "object") return void 0;
|
|
247
|
+
return input.key;
|
|
248
|
+
}
|
|
249
|
+
function patchChildren(parent, oldVNode, newVNode, ctx) {
|
|
250
|
+
const oldChildren = normalize(oldVNode.props?.children);
|
|
251
|
+
const newChildren = normalize(newVNode.props?.children);
|
|
252
|
+
const hasKey = newChildren.some((c) => getKey(c) !== void 0);
|
|
253
|
+
if (hasKey) {
|
|
254
|
+
patchKeyedChildren(parent, oldChildren, newChildren, ctx);
|
|
255
|
+
} else {
|
|
256
|
+
patchSimpleChildren(parent, oldChildren, newChildren, ctx);
|
|
237
257
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
258
|
+
}
|
|
259
|
+
function normalize(children) {
|
|
260
|
+
if (children == null) return [];
|
|
261
|
+
if (!Array.isArray(children)) return [children];
|
|
262
|
+
const result = [];
|
|
263
|
+
for (const child of children) {
|
|
264
|
+
if (Array.isArray(child)) {
|
|
265
|
+
result.push(...child);
|
|
266
|
+
} else {
|
|
267
|
+
result.push(child);
|
|
268
|
+
}
|
|
241
269
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
270
|
+
return result;
|
|
271
|
+
}
|
|
272
|
+
function patchSimpleChildren(parent, oldChildren, newChildren, ctx) {
|
|
273
|
+
const max = Math.max(oldChildren.length, newChildren.length);
|
|
274
|
+
for (let i = 0; i < max; i++) {
|
|
275
|
+
const oldChild = oldChildren[i];
|
|
276
|
+
const newChild = newChildren[i];
|
|
277
|
+
const existingNode = parent.childNodes[i] || null;
|
|
278
|
+
if (oldChild === void 0 && newChild !== void 0) {
|
|
279
|
+
const node = renderValue(newChild, ctx);
|
|
280
|
+
parent.appendChild(node);
|
|
281
|
+
} else if (newChild === void 0) {
|
|
282
|
+
if (existingNode) {
|
|
283
|
+
callRef(oldChild, null);
|
|
284
|
+
existingNode.remove();
|
|
285
|
+
}
|
|
249
286
|
} else {
|
|
250
|
-
|
|
251
|
-
anchor.style.display = "contents";
|
|
252
|
-
_trackEffect(anchor, effect(() => {
|
|
253
|
-
text.textContent = String(child.value);
|
|
254
|
-
}));
|
|
255
|
-
anchor.appendChild(text);
|
|
256
|
-
parent.appendChild(anchor);
|
|
257
|
-
return;
|
|
287
|
+
patchValue(parent, existingNode, oldChild, newChild, ctx);
|
|
258
288
|
}
|
|
259
|
-
parent.appendChild(text);
|
|
260
|
-
return;
|
|
261
289
|
}
|
|
262
|
-
parent.appendChild(document.createTextNode(String(child)));
|
|
263
290
|
}
|
|
264
|
-
function
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
} else {
|
|
289
|
-
entry.mountFns.push(fn);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
for (const fn of _pendingCleanupQueue) {
|
|
293
|
-
entry.disposeFns.push(fn);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
291
|
+
function patchKeyedChildren(parent, oldChildren, newChildren, ctx) {
|
|
292
|
+
const oldKeyMap = /* @__PURE__ */ new Map();
|
|
293
|
+
for (let i = 0; i < oldChildren.length; i++) {
|
|
294
|
+
const key = getKey(oldChildren[i]);
|
|
295
|
+
if (key !== void 0) {
|
|
296
|
+
oldKeyMap.set(key, { vnode: oldChildren[i], node: parent.childNodes[i] || null });
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
const newKeys = newChildren.map((c) => getKey(c));
|
|
300
|
+
for (const key of oldKeyMap.keys()) {
|
|
301
|
+
if (!newKeys.includes(key)) {
|
|
302
|
+
const entry = oldKeyMap.get(key);
|
|
303
|
+
callRef(entry.vnode, null);
|
|
304
|
+
entry.node?.remove();
|
|
305
|
+
oldKeyMap.delete(key);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
for (let i = oldChildren.length - 1; i >= 0; i--) {
|
|
309
|
+
const key = getKey(oldChildren[i]);
|
|
310
|
+
if (key === void 0) {
|
|
311
|
+
const node = parent.childNodes[i];
|
|
312
|
+
if (node) {
|
|
313
|
+
callRef(oldChildren[i], null);
|
|
314
|
+
node.remove();
|
|
296
315
|
}
|
|
297
|
-
_pendingMountQueue = prevMount;
|
|
298
|
-
_pendingCleanupQueue = prevCleanup;
|
|
299
316
|
}
|
|
300
|
-
return result;
|
|
301
317
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
318
|
+
let insertBefore = parent.firstChild;
|
|
319
|
+
for (let i = newChildren.length - 1; i >= 0; i--) {
|
|
320
|
+
const key = newKeys[i];
|
|
321
|
+
const newChild = newChildren[i];
|
|
322
|
+
const oldEntry = key !== void 0 ? oldKeyMap.get(key) : void 0;
|
|
323
|
+
if (oldEntry && oldEntry.node) {
|
|
324
|
+
parent.insertBefore(oldEntry.node, insertBefore);
|
|
325
|
+
insertBefore = oldEntry.node;
|
|
326
|
+
patchValue(parent, oldEntry.node, oldEntry.vnode, newChild, ctx);
|
|
327
|
+
} else {
|
|
328
|
+
const node = renderValue(newChild, ctx);
|
|
329
|
+
parent.insertBefore(node, insertBefore);
|
|
330
|
+
insertBefore = node;
|
|
306
331
|
}
|
|
307
332
|
}
|
|
308
|
-
const childList = children.length > 0 ? children : props?.children != null ? [props.children] : [];
|
|
309
|
-
for (const child of childList) appendChild(el, child);
|
|
310
|
-
return el;
|
|
311
333
|
}
|
|
312
|
-
function
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
const childList = props?.children != null ? Array.isArray(props.children) ? props.children : [props.children] : [];
|
|
320
|
-
const el = document.createElement("div");
|
|
321
|
-
el.style.display = "contents";
|
|
322
|
-
for (const child of childList) appendChild(el, child);
|
|
323
|
-
return el;
|
|
324
|
-
}
|
|
325
|
-
function domMount(root, app) {
|
|
326
|
-
const container = typeof root === "string" ? document.querySelector(root) : root;
|
|
327
|
-
if (!container) throw new Error(`mount target not found: ${root}`);
|
|
328
|
-
container.innerHTML = "";
|
|
329
|
-
container.appendChild(app);
|
|
334
|
+
function callRef(input, el) {
|
|
335
|
+
if (input == null || typeof input !== "object") return;
|
|
336
|
+
const vnode = input;
|
|
337
|
+
if (typeof vnode.props?.ref === "function") {
|
|
338
|
+
vnode.props.ref(el);
|
|
339
|
+
}
|
|
340
|
+
forEach(vnode.props?.children, (child) => callRef(child, el));
|
|
330
341
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
342
|
+
|
|
343
|
+
// src/client/router.ts
|
|
344
|
+
function flattenRoutes(routes, basePath = "", chain = []) {
|
|
345
|
+
const result = [];
|
|
346
|
+
for (const route of routes) {
|
|
347
|
+
const fullPath = joinPaths(basePath, route.path);
|
|
348
|
+
const { re, keys } = compilePath(fullPath);
|
|
349
|
+
const fullChain = [...chain, route];
|
|
350
|
+
result.push({ re, keys, def: route, chain: fullChain });
|
|
351
|
+
if (route.children) {
|
|
352
|
+
result.push(...flattenRoutes(route.children, fullPath, fullChain));
|
|
353
|
+
}
|
|
338
354
|
}
|
|
355
|
+
return result;
|
|
339
356
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
357
|
+
var layoutDepth = /* @__PURE__ */ new WeakMap();
|
|
358
|
+
function joinPaths(a, b) {
|
|
359
|
+
if (!b || b === "/") return a || "/";
|
|
360
|
+
const left = a.endsWith("/") ? a.slice(0, -1) : a;
|
|
361
|
+
const right = b.startsWith("/") ? b : "/" + b;
|
|
362
|
+
return left + right;
|
|
343
363
|
}
|
|
344
|
-
function
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
364
|
+
function compilePath(path) {
|
|
365
|
+
const keys = [];
|
|
366
|
+
const reStr = path.replace(/:(\w+)/g, (_, key) => {
|
|
367
|
+
keys.push(key);
|
|
368
|
+
return "([^/]+)";
|
|
369
|
+
}).replace(/\*/g, ".*");
|
|
370
|
+
return { re: new RegExp(`^${reStr}$`), keys };
|
|
348
371
|
}
|
|
349
|
-
function
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
if (show && children != null) {
|
|
355
|
-
el.appendChild(toNode(children));
|
|
356
|
-
} else if (!show && fallback != null) {
|
|
357
|
-
el.appendChild(toNode(fallback));
|
|
372
|
+
function matchRoute(path, routes) {
|
|
373
|
+
let best = null;
|
|
374
|
+
for (const fr of routes) {
|
|
375
|
+
if (path.match(fr.re)) {
|
|
376
|
+
if (!best || fr.chain.length > best.chain.length) best = fr;
|
|
358
377
|
}
|
|
359
378
|
}
|
|
360
|
-
|
|
361
|
-
const dispose = effect(() => render(Boolean(when.value)));
|
|
362
|
-
_trackEffect(el, dispose);
|
|
363
|
-
} else {
|
|
364
|
-
render(Boolean(when));
|
|
365
|
-
}
|
|
366
|
-
return el;
|
|
379
|
+
return best;
|
|
367
380
|
}
|
|
368
|
-
function
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
function
|
|
372
|
-
if (
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
381
|
+
function router(opts) {
|
|
382
|
+
const flatRoutes = flattenRoutes(opts.routes);
|
|
383
|
+
const mode = opts.mode || "history";
|
|
384
|
+
function getPath() {
|
|
385
|
+
if (mode === "hash") {
|
|
386
|
+
const hash = window.location.hash.replace(/^#/, "") || "/";
|
|
387
|
+
return hash;
|
|
388
|
+
}
|
|
389
|
+
return window.location.pathname;
|
|
390
|
+
}
|
|
391
|
+
function resolve(path) {
|
|
392
|
+
const match = matchRoute(path, flatRoutes);
|
|
393
|
+
if (match) {
|
|
394
|
+
const params = {};
|
|
395
|
+
const m = path.match(match.re);
|
|
396
|
+
if (m) {
|
|
397
|
+
for (let i = 0; i < match.keys.length; i++) {
|
|
398
|
+
params[match.keys[i]] = decodeURIComponent(m[i + 1]);
|
|
399
|
+
}
|
|
382
400
|
}
|
|
383
|
-
return
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
if (k !== null) oldKeyMap.set(k, node);
|
|
390
|
-
}
|
|
391
|
-
const newKeys = [];
|
|
392
|
-
const newItems = [];
|
|
393
|
-
for (let i = 0; i < list.length; i++) {
|
|
394
|
-
newKeys.push(getKey(list[i], i));
|
|
395
|
-
newItems.push(list[i]);
|
|
396
|
-
}
|
|
397
|
-
const removedKeys = new Set(oldKeyMap.keys());
|
|
398
|
-
for (const k of newKeys) removedKeys.delete(k);
|
|
399
|
-
for (const k of removedKeys) {
|
|
400
|
-
const node = oldKeyMap.get(k);
|
|
401
|
-
node.remove();
|
|
402
|
-
oldKeyMap.delete(k);
|
|
401
|
+
return {
|
|
402
|
+
path: match.def.path,
|
|
403
|
+
params,
|
|
404
|
+
query: Object.fromEntries(new URLSearchParams(window.location.search)),
|
|
405
|
+
chain: match.chain
|
|
406
|
+
};
|
|
403
407
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
408
|
+
return { path, params: {}, query: {}, chain: [] };
|
|
409
|
+
}
|
|
410
|
+
return (ctx) => {
|
|
411
|
+
const resolved = resolve(getPath());
|
|
412
|
+
ctx.route = resolved;
|
|
413
|
+
if (!ctx.app) ctx.app = {};
|
|
414
|
+
ctx.app.navigate = (path) => {
|
|
415
|
+
if (mode === "hash") {
|
|
416
|
+
window.location.hash = "#" + path;
|
|
417
|
+
const resolved2 = resolve(path);
|
|
418
|
+
ctx.route = resolved2;
|
|
411
419
|
} else {
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
}
|
|
416
|
-
el.insertBefore(node, insertBefore);
|
|
417
|
-
insertBefore = node;
|
|
420
|
+
window.history.pushState({}, "", path);
|
|
421
|
+
const resolved2 = resolve(path);
|
|
422
|
+
ctx.route = resolved2;
|
|
418
423
|
}
|
|
424
|
+
ctx.ui?.render();
|
|
425
|
+
};
|
|
426
|
+
const onPop = () => {
|
|
427
|
+
const resolved2 = resolve(getPath());
|
|
428
|
+
ctx.route = resolved2;
|
|
429
|
+
ctx.ui?.render();
|
|
430
|
+
};
|
|
431
|
+
window.addEventListener("popstate", onPop);
|
|
432
|
+
if (mode === "hash") {
|
|
433
|
+
window.addEventListener("hashchange", onPop);
|
|
419
434
|
}
|
|
420
|
-
|
|
421
|
-
if (isSignal(each)) {
|
|
422
|
-
const dispose = effect(() => render(each.value));
|
|
423
|
-
_trackEffect(el, dispose);
|
|
424
|
-
} else {
|
|
425
|
-
render(each);
|
|
426
|
-
}
|
|
427
|
-
return el;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
// src/client/types.ts
|
|
431
|
-
function createContext(key) {
|
|
432
|
-
return {
|
|
433
|
-
provide: (ctx, value) => ctx.provide(key, value),
|
|
434
|
-
inject: (ctx) => ctx.inject(key)
|
|
435
|
+
return ctx;
|
|
435
436
|
};
|
|
436
437
|
}
|
|
437
|
-
function
|
|
438
|
-
|
|
438
|
+
function RouteView(_props, ctx) {
|
|
439
|
+
const route = ctx.route;
|
|
440
|
+
if (!route?.chain?.length) return null;
|
|
441
|
+
const ctxAny = ctx;
|
|
442
|
+
const depth = layoutDepth.get(ctxAny) ?? 0;
|
|
443
|
+
if (depth >= route.chain.length) return null;
|
|
444
|
+
const def = route.chain[depth];
|
|
445
|
+
const Comp = def.layout ?? def.component;
|
|
446
|
+
if (!Comp) return null;
|
|
447
|
+
if (def.layout) {
|
|
448
|
+
layoutDepth.set(ctxAny, depth + 1);
|
|
449
|
+
}
|
|
450
|
+
return { type: Comp, props: {}, key: void 0 };
|
|
439
451
|
}
|
|
440
452
|
|
|
441
453
|
// src/client/app.ts
|
|
454
|
+
var mutationMethods = ["push", "pop", "splice", "shift", "unshift", "sort", "reverse"];
|
|
455
|
+
var wrappedCache = /* @__PURE__ */ new WeakMap();
|
|
456
|
+
function wrapDeep(val, dirty) {
|
|
457
|
+
if (val === null || typeof val !== "object") return val;
|
|
458
|
+
if (val instanceof Node) return val;
|
|
459
|
+
if (wrappedCache.has(val)) return wrappedCache.get(val);
|
|
460
|
+
let proxy;
|
|
461
|
+
if (Array.isArray(val)) {
|
|
462
|
+
proxy = new Proxy(val, {
|
|
463
|
+
get(target, key, receiver) {
|
|
464
|
+
const v = Reflect.get(target, key, receiver);
|
|
465
|
+
if (typeof key === "string" && mutationMethods.includes(key) && typeof v === "function") {
|
|
466
|
+
return function(...args) {
|
|
467
|
+
const r = v.apply(target, args);
|
|
468
|
+
dirty();
|
|
469
|
+
return r;
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
return wrapDeep(v, dirty);
|
|
473
|
+
},
|
|
474
|
+
set(target, key, val2) {
|
|
475
|
+
target[key] = wrapDeep(val2, dirty);
|
|
476
|
+
dirty();
|
|
477
|
+
return true;
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
} else {
|
|
481
|
+
proxy = new Proxy(val, {
|
|
482
|
+
get(_target, key, receiver) {
|
|
483
|
+
const v = Reflect.get(_target, key, receiver);
|
|
484
|
+
return wrapDeep(v, dirty);
|
|
485
|
+
},
|
|
486
|
+
set(target, key, val2) {
|
|
487
|
+
target[key] = wrapDeep(val2, dirty);
|
|
488
|
+
dirty();
|
|
489
|
+
return true;
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
wrappedCache.set(val, proxy);
|
|
494
|
+
return proxy;
|
|
495
|
+
}
|
|
442
496
|
function createApp() {
|
|
443
497
|
const middlewares = [];
|
|
444
|
-
|
|
445
|
-
let
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
hash: window.location.hash,
|
|
451
|
-
component: null,
|
|
452
|
-
data: {},
|
|
453
|
-
loading: false
|
|
454
|
-
},
|
|
455
|
-
app: {
|
|
456
|
-
navigate(path) {
|
|
457
|
-
window.history.pushState({}, "", path);
|
|
458
|
-
ctx.route.path = path;
|
|
459
|
-
ctx.route.query = Object.fromEntries(new URLSearchParams(window.location.search));
|
|
460
|
-
ctx.route.hash = window.location.hash;
|
|
461
|
-
const Ctor = window.CustomEvent || CustomEvent;
|
|
462
|
-
window.dispatchEvent(new Ctor("wefu:navigate", { detail: { path } }));
|
|
463
|
-
}
|
|
464
|
-
},
|
|
465
|
-
provide(key, value) {
|
|
466
|
-
provides.set(key, value);
|
|
467
|
-
},
|
|
468
|
-
inject(key) {
|
|
469
|
-
return provides.get(key) ?? null;
|
|
470
|
-
},
|
|
471
|
-
ws: null
|
|
472
|
-
};
|
|
473
|
-
return {
|
|
498
|
+
let ctx = {};
|
|
499
|
+
let container = null;
|
|
500
|
+
let rootComponent = null;
|
|
501
|
+
let oldVNode = null;
|
|
502
|
+
let rendered = false;
|
|
503
|
+
const app = {
|
|
474
504
|
get ctx() {
|
|
475
505
|
return ctx;
|
|
476
506
|
},
|
|
477
507
|
use(mw) {
|
|
478
508
|
middlewares.push(mw);
|
|
479
|
-
return
|
|
509
|
+
return app;
|
|
480
510
|
},
|
|
481
511
|
async mount(rootSelector, RootComponent) {
|
|
512
|
+
rootComponent = RootComponent;
|
|
482
513
|
for (const mw of middlewares) {
|
|
483
514
|
ctx = await mw(ctx);
|
|
484
515
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
setCtx(ctx);
|
|
498
|
-
const vnode = jsx(Component, mergedProps);
|
|
499
|
-
root.appendChild(vnode);
|
|
500
|
-
setCtx(null);
|
|
501
|
-
}
|
|
502
|
-
};
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
// src/client/router.ts
|
|
506
|
-
var CHAIN_KEY = /* @__PURE__ */ Symbol("routeChain");
|
|
507
|
-
var DEPTH_KEY = /* @__PURE__ */ Symbol("routeDepth");
|
|
508
|
-
function router(opts) {
|
|
509
|
-
const mode = opts.mode ?? "hash";
|
|
510
|
-
const flatRoutes = flattenRoutes(opts.routes);
|
|
511
|
-
function matchRoute(path) {
|
|
512
|
-
for (const fr of flatRoutes) {
|
|
513
|
-
const result = path.match(fr.re);
|
|
514
|
-
if (!result) continue;
|
|
515
|
-
const isLeaf = (idx) => idx === fr.chain.length - 1;
|
|
516
|
-
const matchedChain = fr.chain.map((item, idx) => {
|
|
517
|
-
const params = {};
|
|
518
|
-
if (isLeaf(idx)) {
|
|
519
|
-
fr.keys.forEach((k, i) => {
|
|
520
|
-
params[k] = decodeURIComponent(result[i + 1]);
|
|
521
|
-
});
|
|
522
|
-
}
|
|
523
|
-
return { ...item, params };
|
|
524
|
-
});
|
|
525
|
-
return { chain: matchedChain, leafRoute: fr.leafRoute };
|
|
526
|
-
}
|
|
527
|
-
return null;
|
|
528
|
-
}
|
|
529
|
-
function resolve(ctx2, path) {
|
|
530
|
-
const raw = mode === "hash" ? path || "/" : path;
|
|
531
|
-
const [cleanPath, qs] = raw.split("?");
|
|
532
|
-
ctx2.route.query = Object.fromEntries(new URLSearchParams(qs ?? ""));
|
|
533
|
-
ctx2.route.path = cleanPath;
|
|
534
|
-
const matched = matchRoute(cleanPath);
|
|
535
|
-
if (!matched) {
|
|
536
|
-
ctx2.route.component = opts.notFound ?? null;
|
|
537
|
-
ctx2.route.data = {};
|
|
538
|
-
ctx2.route.transition = opts.transition;
|
|
539
|
-
ctx2.route[CHAIN_KEY] = [];
|
|
540
|
-
return;
|
|
541
|
-
}
|
|
542
|
-
const leaf = matched.chain[matched.chain.length - 1];
|
|
543
|
-
ctx2.route.params = { ...leaf.params };
|
|
544
|
-
ctx2.route.component = leaf.component ?? leaf.routeDef.component ?? null;
|
|
545
|
-
ctx2.route.title = matched.leafRoute.title;
|
|
546
|
-
ctx2.route.auth = matched.leafRoute.auth;
|
|
547
|
-
ctx2.route.transition = matched.leafRoute.transition ?? opts.transition;
|
|
548
|
-
if (matched.leafRoute.title) document.title = matched.leafRoute.title;
|
|
549
|
-
ctx2.route[CHAIN_KEY] = matched.chain;
|
|
550
|
-
ctx2.route[DEPTH_KEY] = 0;
|
|
551
|
-
}
|
|
552
|
-
return (ctx) => {
|
|
553
|
-
function emit(path) {
|
|
554
|
-
const Ctor = window.CustomEvent || CustomEvent;
|
|
555
|
-
window.dispatchEvent(new Ctor("wefu:route", { detail: { path } }));
|
|
556
|
-
}
|
|
557
|
-
function navigateAndLoad(path) {
|
|
558
|
-
resolve(ctx, path);
|
|
559
|
-
const leafChain = ctx.route[CHAIN_KEY];
|
|
560
|
-
const leafLoader = leafChain?.[leafChain.length - 1]?.routeDef?.loader;
|
|
561
|
-
if (leafLoader) {
|
|
562
|
-
ctx.route.loading = true;
|
|
563
|
-
emit(ctx.route.path);
|
|
564
|
-
leafLoader(ctx).then((data) => {
|
|
565
|
-
ctx.route.data = data;
|
|
566
|
-
ctx.route.loading = false;
|
|
567
|
-
emit(ctx.route.path);
|
|
568
|
-
}).catch(() => {
|
|
569
|
-
ctx.route.data = {};
|
|
570
|
-
ctx.route.loading = false;
|
|
571
|
-
emit(ctx.route.path);
|
|
516
|
+
const el = typeof rootSelector === "string" ? document.querySelector(rootSelector) : rootSelector;
|
|
517
|
+
if (!el) throw new Error(`mount target not found: ${rootSelector}`);
|
|
518
|
+
container = el;
|
|
519
|
+
container.innerHTML = "";
|
|
520
|
+
let _dirty = false;
|
|
521
|
+
function scheduleRender() {
|
|
522
|
+
if (_dirty) return;
|
|
523
|
+
_dirty = true;
|
|
524
|
+
queueMicrotask(() => {
|
|
525
|
+
if (!_dirty) return;
|
|
526
|
+
_dirty = false;
|
|
527
|
+
ctx.ui.render();
|
|
572
528
|
});
|
|
573
|
-
} else {
|
|
574
|
-
ctx.route.loading = false;
|
|
575
|
-
emit(ctx.route.path);
|
|
576
529
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
ctx.app.navigate = (path) => {
|
|
588
|
-
if (opts.scrollRestoration !== false) {
|
|
589
|
-
scrollPositions.set(window.location.pathname, window.scrollY);
|
|
590
|
-
}
|
|
591
|
-
window.history.pushState({}, "", path);
|
|
592
|
-
navigateAndLoad(path);
|
|
593
|
-
};
|
|
594
|
-
window.addEventListener("popstate", () => {
|
|
595
|
-
if (opts.scrollRestoration !== false) {
|
|
596
|
-
const savedY = scrollPositions.get(window.location.pathname);
|
|
597
|
-
if (savedY !== void 0) {
|
|
598
|
-
requestAnimationFrame(() => window.scrollTo(0, savedY));
|
|
530
|
+
const $target = {};
|
|
531
|
+
ctx.ui = {
|
|
532
|
+
render: () => {
|
|
533
|
+
if (!container || !rootComponent || !oldVNode) return;
|
|
534
|
+
_dirty = false;
|
|
535
|
+
layoutDepth.delete(ctx);
|
|
536
|
+
const newVNode = wrapComponent(rootComponent, ctx);
|
|
537
|
+
const oldNode = container.firstChild;
|
|
538
|
+
if (oldNode) {
|
|
539
|
+
patchValue(container, oldNode, oldVNode, newVNode, ctx);
|
|
599
540
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
for (const route of routes) {
|
|
612
|
-
const fullPath = parentPath + route.path;
|
|
613
|
-
const parts = fullPath.split("/").filter(Boolean);
|
|
614
|
-
const keys = [];
|
|
615
|
-
const reStr = "^/" + parts.map((p) => {
|
|
616
|
-
if (p.startsWith(":")) {
|
|
617
|
-
keys.push(p.slice(1));
|
|
618
|
-
return "([^/]+)";
|
|
619
|
-
}
|
|
620
|
-
return p;
|
|
621
|
-
}).join("/") + "$";
|
|
622
|
-
if (route.children && route.children.length > 0) {
|
|
623
|
-
for (const child of route.children) {
|
|
624
|
-
const childPath = fullPath + child.path;
|
|
625
|
-
const childParts = childPath.split("/").filter(Boolean);
|
|
626
|
-
const childKeys = [];
|
|
627
|
-
const childReStr = "^/" + childParts.map((p) => {
|
|
628
|
-
if (p.startsWith(":")) {
|
|
629
|
-
childKeys.push(p.slice(1));
|
|
630
|
-
return "([^/]+)";
|
|
541
|
+
oldVNode = newVNode;
|
|
542
|
+
},
|
|
543
|
+
/** 极少需要:仅当绕过 Proxy 直接操作底层对象时使用 */
|
|
544
|
+
dirty: () => {
|
|
545
|
+
scheduleRender();
|
|
546
|
+
},
|
|
547
|
+
$: new Proxy($target, {
|
|
548
|
+
set(_target, key, val) {
|
|
549
|
+
_target[key] = wrapDeep(val, scheduleRender);
|
|
550
|
+
scheduleRender();
|
|
551
|
+
return true;
|
|
631
552
|
}
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
}
|
|
645
|
-
} else {
|
|
646
|
-
const chain = [
|
|
647
|
-
{ component: route.component, params: {}, routeDef: route, depth: 0 }
|
|
648
|
-
];
|
|
649
|
-
result.push({
|
|
650
|
-
re: new RegExp(reStr),
|
|
651
|
-
keys,
|
|
652
|
-
chain,
|
|
653
|
-
leafRoute: route
|
|
654
|
-
});
|
|
553
|
+
}),
|
|
554
|
+
ready: false
|
|
555
|
+
};
|
|
556
|
+
oldVNode = wrapComponent(RootComponent, ctx);
|
|
557
|
+
const node = render(oldVNode, ctx);
|
|
558
|
+
if (node instanceof Node) container.appendChild(node);
|
|
559
|
+
_dirty = false;
|
|
560
|
+
rendered = true;
|
|
561
|
+
},
|
|
562
|
+
destroy() {
|
|
563
|
+
if (container) container.innerHTML = "";
|
|
564
|
+
container = null;
|
|
565
|
+
ctx = {};
|
|
655
566
|
}
|
|
656
|
-
}
|
|
657
|
-
return
|
|
567
|
+
};
|
|
568
|
+
return app;
|
|
658
569
|
}
|
|
659
|
-
function
|
|
660
|
-
|
|
661
|
-
let myDepth = null;
|
|
662
|
-
let cachedComp = null;
|
|
663
|
-
function render() {
|
|
664
|
-
const chain = ctx.route[CHAIN_KEY];
|
|
665
|
-
if (myDepth === null) {
|
|
666
|
-
const d = ctx.route[DEPTH_KEY] ?? 0;
|
|
667
|
-
myDepth = d;
|
|
668
|
-
ctx.route[DEPTH_KEY] = myDepth + 1;
|
|
669
|
-
}
|
|
670
|
-
const depth = myDepth;
|
|
671
|
-
if (!chain || depth >= chain.length) {
|
|
672
|
-
if (el.children.length > 0) el.textContent = "";
|
|
673
|
-
cachedComp = null;
|
|
674
|
-
return;
|
|
675
|
-
}
|
|
676
|
-
const item = chain[depth];
|
|
677
|
-
const Comp = item.layout ?? item.component;
|
|
678
|
-
if (!Comp) return;
|
|
679
|
-
if (cachedComp === Comp) return;
|
|
680
|
-
cachedComp = Comp;
|
|
681
|
-
el.textContent = "";
|
|
682
|
-
setCtx(ctx);
|
|
683
|
-
const page = jsx(Comp, {});
|
|
684
|
-
el.appendChild(page);
|
|
685
|
-
setCtx(null);
|
|
686
|
-
}
|
|
687
|
-
render();
|
|
688
|
-
window.addEventListener("wefu:route", render);
|
|
689
|
-
onCleanup(() => window.removeEventListener("wefu:route", render));
|
|
690
|
-
return el;
|
|
570
|
+
function wrapComponent(Comp, _ctx) {
|
|
571
|
+
return { type: Comp, props: {}, key: void 0 };
|
|
691
572
|
}
|
|
692
573
|
|
|
693
|
-
// src/client/
|
|
694
|
-
function
|
|
695
|
-
|
|
696
|
-
let error = null;
|
|
697
|
-
let status = "pending";
|
|
698
|
-
let loadingPromise = null;
|
|
699
|
-
function startLoad() {
|
|
700
|
-
if (status !== "pending") return;
|
|
701
|
-
status = "loading";
|
|
702
|
-
loadingPromise = loader().then((mod) => {
|
|
703
|
-
loaded = "default" in mod ? mod.default : mod;
|
|
704
|
-
status = "loaded";
|
|
705
|
-
}).catch((e) => {
|
|
706
|
-
error = e;
|
|
707
|
-
status = "error";
|
|
708
|
-
});
|
|
709
|
-
}
|
|
710
|
-
return (props, ctx) => {
|
|
711
|
-
if (loaded) {
|
|
712
|
-
return loaded(props, ctx);
|
|
713
|
-
}
|
|
714
|
-
if (error) {
|
|
715
|
-
const ErrorComp = options?.errorFallback;
|
|
716
|
-
if (ErrorComp) return ErrorComp(props, ctx);
|
|
717
|
-
const el2 = document.createElement("div");
|
|
718
|
-
el2.textContent = `\u7EC4\u4EF6\u52A0\u8F7D\u5931\u8D25: ${error.message}`;
|
|
719
|
-
el2.style.cssText = "padding: 1rem; color: #ef4444;";
|
|
720
|
-
return el2;
|
|
721
|
-
}
|
|
722
|
-
if (status === "pending") {
|
|
723
|
-
startLoad();
|
|
724
|
-
}
|
|
725
|
-
if (status === "loading" && loadingPromise) {
|
|
726
|
-
loadingPromise.then(() => {
|
|
727
|
-
setTimeout(() => {
|
|
728
|
-
window.dispatchEvent(new CustomEvent("wefu:lazy-loaded"));
|
|
729
|
-
}, 0);
|
|
730
|
-
});
|
|
731
|
-
}
|
|
732
|
-
const Fallback = options?.fallback;
|
|
733
|
-
if (Fallback) return Fallback(props, ctx);
|
|
734
|
-
const el = document.createElement("div");
|
|
735
|
-
el.textContent = "\u52A0\u8F7D\u4E2D...";
|
|
736
|
-
el.style.cssText = "padding: 1rem; color: #6b7280;";
|
|
737
|
-
return el;
|
|
738
|
-
};
|
|
574
|
+
// src/client/types.ts
|
|
575
|
+
function extendCtx(ctx, fields) {
|
|
576
|
+
return Object.assign(Object.create(ctx), fields);
|
|
739
577
|
}
|
|
740
578
|
|
|
741
579
|
// src/client/middleware/ws.ts
|
|
742
|
-
function ws(
|
|
743
|
-
const wsUrl =
|
|
744
|
-
const reconnectInterval =
|
|
745
|
-
const maxReconnect =
|
|
580
|
+
function ws(options = {}) {
|
|
581
|
+
const wsUrl = options.url ?? "/ws";
|
|
582
|
+
const reconnectInterval = options.reconnectInterval ?? 3e3;
|
|
583
|
+
const maxReconnect = options.maxReconnect ?? 10;
|
|
584
|
+
const pingIntervalMs = options.pingInterval ?? 3e4;
|
|
585
|
+
const pingTimeoutMs = options.pingTimeout ?? 1e4;
|
|
746
586
|
return (ctx) => {
|
|
747
|
-
const isConnected = signal(false);
|
|
748
587
|
const messageHandlers = /* @__PURE__ */ new Set();
|
|
749
588
|
let socket = null;
|
|
750
|
-
let
|
|
589
|
+
let reconnectAttempts = 0;
|
|
751
590
|
let reconnectTimer = null;
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
591
|
+
let pingTimer = null;
|
|
592
|
+
let pingTimeoutTimer = null;
|
|
593
|
+
let destroyed = false;
|
|
594
|
+
const wsClient = {
|
|
595
|
+
isConnected: false,
|
|
596
|
+
onMessage: (fn) => {
|
|
597
|
+
messageHandlers.add(fn);
|
|
598
|
+
return () => {
|
|
599
|
+
messageHandlers.delete(fn);
|
|
600
|
+
};
|
|
601
|
+
},
|
|
602
|
+
send: (msg) => {
|
|
603
|
+
if (socket?.readyState === WebSocket.OPEN) {
|
|
604
|
+
socket.send(JSON.stringify(msg));
|
|
605
|
+
}
|
|
606
|
+
},
|
|
607
|
+
_connect: connect,
|
|
608
|
+
/** 断开连接并清理所有定时器 */
|
|
609
|
+
close: () => {
|
|
610
|
+
destroyed = true;
|
|
611
|
+
clearTimers();
|
|
612
|
+
socket?.close();
|
|
613
|
+
socket = null;
|
|
614
|
+
wsClient.isConnected = false;
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
async function connect() {
|
|
618
|
+
if (destroyed) return;
|
|
755
619
|
try {
|
|
756
|
-
socket = new WebSocket(
|
|
620
|
+
socket = new WebSocket(wsUrl);
|
|
621
|
+
socket.onopen = () => {
|
|
622
|
+
wsClient.isConnected = true;
|
|
623
|
+
reconnectAttempts = 0;
|
|
624
|
+
startPing();
|
|
625
|
+
};
|
|
626
|
+
socket.onmessage = (e) => {
|
|
627
|
+
try {
|
|
628
|
+
const data = JSON.parse(e.data);
|
|
629
|
+
for (const handler of messageHandlers) handler(data);
|
|
630
|
+
} catch {
|
|
631
|
+
}
|
|
632
|
+
resetPingTimeout();
|
|
633
|
+
};
|
|
634
|
+
socket.onclose = () => {
|
|
635
|
+
wsClient.isConnected = false;
|
|
636
|
+
clearTimers();
|
|
637
|
+
if (!destroyed && reconnectAttempts < maxReconnect) {
|
|
638
|
+
reconnectAttempts++;
|
|
639
|
+
reconnectTimer = setTimeout(connect, reconnectInterval * Math.min(reconnectAttempts, 5));
|
|
640
|
+
}
|
|
641
|
+
};
|
|
642
|
+
socket.onerror = () => {
|
|
643
|
+
socket?.close();
|
|
644
|
+
};
|
|
757
645
|
} catch {
|
|
758
|
-
|
|
759
|
-
|
|
646
|
+
if (!destroyed) {
|
|
647
|
+
reconnectTimer = setTimeout(connect, reconnectInterval);
|
|
648
|
+
}
|
|
760
649
|
}
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
const data = JSON.parse(event.data);
|
|
768
|
-
for (const h of messageHandlers) h(data);
|
|
769
|
-
} catch {
|
|
770
|
-
for (const h of messageHandlers) h(event.data);
|
|
650
|
+
}
|
|
651
|
+
function startPing() {
|
|
652
|
+
if (pingIntervalMs <= 0) return;
|
|
653
|
+
pingTimer = setInterval(() => {
|
|
654
|
+
if (socket?.readyState === WebSocket.OPEN) {
|
|
655
|
+
socket.send(JSON.stringify({ type: "ping" }));
|
|
771
656
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
scheduleReconnect();
|
|
777
|
-
};
|
|
778
|
-
socket.onerror = () => {
|
|
779
|
-
socket?.close();
|
|
780
|
-
};
|
|
657
|
+
pingTimeoutTimer = setTimeout(() => {
|
|
658
|
+
socket?.close();
|
|
659
|
+
}, pingTimeoutMs);
|
|
660
|
+
}, pingIntervalMs);
|
|
781
661
|
}
|
|
782
|
-
function
|
|
783
|
-
|
|
784
|
-
reconnectCount++;
|
|
785
|
-
reconnectTimer = setTimeout(connect, reconnectInterval * reconnectCount);
|
|
662
|
+
function resetPingTimeout() {
|
|
663
|
+
clearTimeout(pingTimeoutTimer);
|
|
786
664
|
}
|
|
787
|
-
function
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
console.warn("ws.send: WebSocket not open, readyState:", socket?.readyState);
|
|
792
|
-
}
|
|
665
|
+
function clearTimers() {
|
|
666
|
+
clearInterval(pingTimer);
|
|
667
|
+
clearTimeout(pingTimeoutTimer);
|
|
668
|
+
clearTimeout(reconnectTimer);
|
|
793
669
|
}
|
|
794
670
|
connect();
|
|
795
|
-
return extendCtx(ctx, {
|
|
796
|
-
ws: {
|
|
797
|
-
send,
|
|
798
|
-
onMessage: (handler) => {
|
|
799
|
-
messageHandlers.add(handler);
|
|
800
|
-
return () => messageHandlers.delete(handler);
|
|
801
|
-
},
|
|
802
|
-
get isConnected() {
|
|
803
|
-
return isConnected;
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
});
|
|
671
|
+
return extendCtx(ctx, { ws: wsClient });
|
|
807
672
|
};
|
|
808
673
|
}
|
|
809
674
|
|
|
@@ -815,13 +680,27 @@ function api(options) {
|
|
|
815
680
|
};
|
|
816
681
|
const onRequest = options?.onRequest;
|
|
817
682
|
const onResponse = options?.onResponse;
|
|
683
|
+
const timeoutMs = options?.timeout ?? 0;
|
|
818
684
|
async function request(method, url, body, reqOpts) {
|
|
819
685
|
const fullURL = opts.baseURL + url;
|
|
820
686
|
const init = {
|
|
821
687
|
method,
|
|
822
|
-
headers: { ...opts.headers, ...reqOpts?.headers }
|
|
823
|
-
signal: reqOpts?.signal
|
|
688
|
+
headers: { ...opts.headers, ...reqOpts?.headers }
|
|
824
689
|
};
|
|
690
|
+
const hasTimeout = timeoutMs > 0;
|
|
691
|
+
const hasUserSignal = !!reqOpts?.signal;
|
|
692
|
+
let abortController = null;
|
|
693
|
+
let timer = null;
|
|
694
|
+
if (hasTimeout || hasUserSignal) {
|
|
695
|
+
abortController = new AbortController();
|
|
696
|
+
init.signal = abortController.signal;
|
|
697
|
+
if (hasTimeout) {
|
|
698
|
+
timer = setTimeout(() => abortController.abort(new Error("Request timed out")), timeoutMs);
|
|
699
|
+
}
|
|
700
|
+
if (hasUserSignal) {
|
|
701
|
+
reqOpts.signal.addEventListener("abort", () => abortController.abort());
|
|
702
|
+
}
|
|
703
|
+
}
|
|
825
704
|
if (body !== void 0 && method !== "GET" && method !== "HEAD") {
|
|
826
705
|
init.body = JSON.stringify(body);
|
|
827
706
|
}
|
|
@@ -829,19 +708,27 @@ function api(options) {
|
|
|
829
708
|
if (onRequest) {
|
|
830
709
|
finalReq = onRequest(finalReq);
|
|
831
710
|
}
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
711
|
+
try {
|
|
712
|
+
const res = await fetch(finalReq.url, finalReq.init);
|
|
713
|
+
if (onResponse) {
|
|
714
|
+
return onResponse(res);
|
|
715
|
+
}
|
|
716
|
+
if (!res.ok) {
|
|
717
|
+
const text = await res.text().catch(() => "");
|
|
718
|
+
throw new ApiError(res.status, text || res.statusText);
|
|
719
|
+
}
|
|
720
|
+
const contentLength = res.headers.get("content-length");
|
|
721
|
+
if (res.status === 204 || contentLength === "0") {
|
|
722
|
+
return void 0;
|
|
723
|
+
}
|
|
724
|
+
const ct = (res.headers.get("content-type") || "").toLowerCase();
|
|
725
|
+
if (ct.includes("application/json") || ct.includes("json")) {
|
|
726
|
+
return res.json();
|
|
727
|
+
}
|
|
728
|
+
return res.text();
|
|
729
|
+
} finally {
|
|
730
|
+
if (timer) clearTimeout(timer);
|
|
843
731
|
}
|
|
844
|
-
return res.json();
|
|
845
732
|
}
|
|
846
733
|
return (ctx) => {
|
|
847
734
|
const client = {
|
|
@@ -851,8 +738,7 @@ function api(options) {
|
|
|
851
738
|
patch: (url, body, reqOpts) => request("PATCH", url, body, reqOpts),
|
|
852
739
|
delete: (url, reqOpts) => request("DELETE", url, void 0, reqOpts)
|
|
853
740
|
};
|
|
854
|
-
ctx
|
|
855
|
-
return ctx;
|
|
741
|
+
return extendCtx(ctx, { api: client });
|
|
856
742
|
};
|
|
857
743
|
}
|
|
858
744
|
var ApiError = class extends Error {
|
|
@@ -867,207 +753,126 @@ var ApiError = class extends Error {
|
|
|
867
753
|
};
|
|
868
754
|
|
|
869
755
|
// src/client/middleware/auth.ts
|
|
756
|
+
function decodeJWT(token) {
|
|
757
|
+
try {
|
|
758
|
+
const parts = token.split(".");
|
|
759
|
+
if (parts.length !== 3) return null;
|
|
760
|
+
const payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
761
|
+
return JSON.parse(atob(payload));
|
|
762
|
+
} catch {
|
|
763
|
+
return null;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
function isTokenExpired(token) {
|
|
767
|
+
const payload = decodeJWT(token);
|
|
768
|
+
if (!payload?.exp) return true;
|
|
769
|
+
return payload.exp * 1e3 - 3e4 < Date.now();
|
|
770
|
+
}
|
|
870
771
|
function auth(options) {
|
|
871
772
|
const storage = options?.storage ?? localStorage;
|
|
872
773
|
const tokenKey = options?.tokenKey ?? "weifuwu_token";
|
|
873
774
|
const userKey = options?.userKey ?? "weifuwu_user";
|
|
775
|
+
const refreshTokenKey = options?.refreshTokenKey ?? "weifuwu_refresh";
|
|
776
|
+
const refreshEndpoint = options?.refreshEndpoint ?? "/api/auth/refresh";
|
|
874
777
|
return (ctx) => {
|
|
875
778
|
const savedToken = storage.getItem(tokenKey);
|
|
876
779
|
const savedUserStr = storage.getItem(userKey);
|
|
877
|
-
const token = signal(savedToken);
|
|
878
|
-
const user = signal(
|
|
879
|
-
savedUserStr ? JSON.parse(savedUserStr) : null
|
|
880
|
-
);
|
|
881
|
-
const isLoggedIn = computed(() => token.value !== null);
|
|
882
|
-
const authorizationHeader = computed(() => token.value ? `Bearer ${token.value}` : null);
|
|
883
780
|
const authClient = {
|
|
884
|
-
token,
|
|
885
|
-
user,
|
|
886
|
-
isLoggedIn
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
781
|
+
token: savedToken,
|
|
782
|
+
user: savedUserStr ? JSON.parse(savedUserStr) : null,
|
|
783
|
+
get isLoggedIn() {
|
|
784
|
+
return this.token !== null;
|
|
785
|
+
},
|
|
786
|
+
login(newToken, newUser, refreshToken) {
|
|
787
|
+
authClient.token = newToken;
|
|
788
|
+
authClient.user = newUser;
|
|
891
789
|
storage.setItem(tokenKey, newToken);
|
|
892
790
|
storage.setItem(userKey, JSON.stringify(newUser));
|
|
791
|
+
if (refreshToken) storage.setItem(refreshTokenKey, refreshToken);
|
|
893
792
|
},
|
|
894
793
|
logout() {
|
|
895
|
-
token
|
|
896
|
-
user
|
|
794
|
+
authClient.token = null;
|
|
795
|
+
authClient.user = null;
|
|
897
796
|
storage.removeItem(tokenKey);
|
|
898
797
|
storage.removeItem(userKey);
|
|
798
|
+
storage.removeItem(refreshTokenKey);
|
|
899
799
|
},
|
|
900
800
|
setUser(newUser) {
|
|
901
|
-
user
|
|
801
|
+
authClient.user = newUser;
|
|
902
802
|
storage.setItem(userKey, JSON.stringify(newUser));
|
|
803
|
+
},
|
|
804
|
+
async refresh() {
|
|
805
|
+
const rt = storage.getItem(refreshTokenKey);
|
|
806
|
+
if (!rt) return false;
|
|
807
|
+
try {
|
|
808
|
+
const res = await fetch(refreshEndpoint, {
|
|
809
|
+
method: "POST",
|
|
810
|
+
headers: { "Content-Type": "application/json" },
|
|
811
|
+
body: JSON.stringify({ refreshToken: rt })
|
|
812
|
+
});
|
|
813
|
+
if (!res.ok) {
|
|
814
|
+
authClient.logout();
|
|
815
|
+
return false;
|
|
816
|
+
}
|
|
817
|
+
const data = await res.json();
|
|
818
|
+
authClient.token = data.token;
|
|
819
|
+
storage.setItem(tokenKey, data.token);
|
|
820
|
+
if (data.refreshToken) storage.setItem(refreshTokenKey, data.refreshToken);
|
|
821
|
+
return true;
|
|
822
|
+
} catch {
|
|
823
|
+
return false;
|
|
824
|
+
}
|
|
903
825
|
}
|
|
904
826
|
};
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
// src/client/resource.ts
|
|
911
|
-
function createResource(fetcher, options) {
|
|
912
|
-
const data = signal(options?.initialValue);
|
|
913
|
-
const loading = signal(true);
|
|
914
|
-
const error = signal(void 0);
|
|
915
|
-
let fetchId = 0;
|
|
916
|
-
async function load() {
|
|
917
|
-
const id = ++fetchId;
|
|
918
|
-
loading.value = true;
|
|
919
|
-
error.value = void 0;
|
|
920
|
-
try {
|
|
921
|
-
const result = await fetcher();
|
|
922
|
-
if (id === fetchId) {
|
|
923
|
-
data.value = result;
|
|
924
|
-
loading.value = false;
|
|
925
|
-
}
|
|
926
|
-
} catch (e) {
|
|
927
|
-
if (id === fetchId) {
|
|
928
|
-
error.value = e instanceof Error ? e : new Error(String(e));
|
|
929
|
-
loading.value = false;
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
load();
|
|
934
|
-
const state = {
|
|
935
|
-
data,
|
|
936
|
-
loading,
|
|
937
|
-
error,
|
|
938
|
-
refetch: () => {
|
|
939
|
-
load();
|
|
827
|
+
if (savedToken && isTokenExpired(savedToken)) {
|
|
828
|
+
authClient.refresh().catch(() => {
|
|
829
|
+
});
|
|
940
830
|
}
|
|
831
|
+
return extendCtx(ctx, { auth: authClient });
|
|
941
832
|
};
|
|
942
|
-
return [data, state];
|
|
943
833
|
}
|
|
944
834
|
|
|
945
|
-
// src/client/
|
|
946
|
-
function
|
|
947
|
-
const
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
return
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
newErrors[key] = err;
|
|
970
|
-
if (err !== null) hasError = true;
|
|
971
|
-
}
|
|
972
|
-
errors.value = newErrors;
|
|
973
|
-
return !hasError;
|
|
974
|
-
}
|
|
975
|
-
function handleSubmit(e) {
|
|
976
|
-
e.preventDefault();
|
|
977
|
-
if (submitting.value) return;
|
|
978
|
-
const allTouched = {};
|
|
979
|
-
for (const key of Object.keys(values.value)) {
|
|
980
|
-
allTouched[key] = true;
|
|
981
|
-
}
|
|
982
|
-
touched.value = allTouched;
|
|
983
|
-
if (!validateAllFields()) return;
|
|
984
|
-
if (options.onSubmit) {
|
|
985
|
-
submitting.value = true;
|
|
986
|
-
const result = options.onSubmit({ ...values.value });
|
|
987
|
-
if (result instanceof Promise) {
|
|
988
|
-
result.finally(() => {
|
|
989
|
-
submitting.value = false;
|
|
990
|
-
});
|
|
991
|
-
} else {
|
|
992
|
-
submitting.value = false;
|
|
993
|
-
}
|
|
835
|
+
// src/client/error-boundary.ts
|
|
836
|
+
function ErrorBoundary(props, ctx) {
|
|
837
|
+
const $ = ctx.ui.$;
|
|
838
|
+
if (!("error" in $)) $.error = null;
|
|
839
|
+
if ($.error) {
|
|
840
|
+
const fb = props.fallback;
|
|
841
|
+
if (typeof fb === "function") {
|
|
842
|
+
return fb({ error: $.error });
|
|
843
|
+
}
|
|
844
|
+
return fb ?? null;
|
|
845
|
+
}
|
|
846
|
+
;
|
|
847
|
+
ctx.ui._errorHandler = (err) => {
|
|
848
|
+
$.error = err;
|
|
849
|
+
ctx.ui.dirty();
|
|
850
|
+
};
|
|
851
|
+
try {
|
|
852
|
+
const result = props.children;
|
|
853
|
+
return result ?? null;
|
|
854
|
+
} catch (e) {
|
|
855
|
+
$.error = e;
|
|
856
|
+
const fb = props.fallback;
|
|
857
|
+
if (typeof fb === "function") {
|
|
858
|
+
return fb({ error: e });
|
|
994
859
|
}
|
|
860
|
+
return fb ?? null;
|
|
995
861
|
}
|
|
996
|
-
function field(name) {
|
|
997
|
-
const fieldError = {
|
|
998
|
-
get value() {
|
|
999
|
-
return errors.value[name];
|
|
1000
|
-
}
|
|
1001
|
-
};
|
|
1002
|
-
return {
|
|
1003
|
-
get value() {
|
|
1004
|
-
return String(values.value[name] ?? "");
|
|
1005
|
-
},
|
|
1006
|
-
onInput(e) {
|
|
1007
|
-
const target = e.target;
|
|
1008
|
-
const newVal = target.type === "checkbox" ? target.checked : target.value;
|
|
1009
|
-
const newValues = { ...values.value, [name]: newVal };
|
|
1010
|
-
values.value = newValues;
|
|
1011
|
-
if (touched.value[name]) {
|
|
1012
|
-
const err = validateField(name, newVal);
|
|
1013
|
-
errors.value = { ...errors.value, [name]: err };
|
|
1014
|
-
}
|
|
1015
|
-
},
|
|
1016
|
-
get error() {
|
|
1017
|
-
return fieldError;
|
|
1018
|
-
}
|
|
1019
|
-
};
|
|
1020
|
-
}
|
|
1021
|
-
function setValue(name, value) {
|
|
1022
|
-
values.value = { ...values.value, [name]: value };
|
|
1023
|
-
}
|
|
1024
|
-
function reset() {
|
|
1025
|
-
values.value = { ...options.initial };
|
|
1026
|
-
errors.value = {};
|
|
1027
|
-
touched.value = {};
|
|
1028
|
-
submitting.value = false;
|
|
1029
|
-
}
|
|
1030
|
-
return {
|
|
1031
|
-
values,
|
|
1032
|
-
errors,
|
|
1033
|
-
submitting,
|
|
1034
|
-
touched,
|
|
1035
|
-
handleSubmit,
|
|
1036
|
-
field,
|
|
1037
|
-
setValue,
|
|
1038
|
-
reset,
|
|
1039
|
-
validateAll: validateAllFields
|
|
1040
|
-
};
|
|
1041
862
|
}
|
|
1042
863
|
export {
|
|
1043
864
|
ApiError,
|
|
1044
865
|
ErrorBoundary,
|
|
1045
|
-
For,
|
|
1046
866
|
Fragment,
|
|
1047
867
|
RouteView,
|
|
1048
|
-
Show,
|
|
1049
868
|
api,
|
|
1050
869
|
auth,
|
|
1051
|
-
batch,
|
|
1052
|
-
computed,
|
|
1053
870
|
createApp,
|
|
1054
|
-
createContext,
|
|
1055
|
-
createPortal,
|
|
1056
|
-
createResource,
|
|
1057
|
-
domMount,
|
|
1058
|
-
effect,
|
|
1059
871
|
extendCtx,
|
|
1060
|
-
|
|
872
|
+
h,
|
|
1061
873
|
jsx,
|
|
1062
874
|
jsxDEV,
|
|
1063
875
|
jsxs,
|
|
1064
|
-
lazy,
|
|
1065
|
-
onCleanup,
|
|
1066
|
-
onMount,
|
|
1067
876
|
router,
|
|
1068
|
-
signal,
|
|
1069
|
-
untrack,
|
|
1070
|
-
useForm,
|
|
1071
|
-
wrap,
|
|
1072
877
|
ws
|
|
1073
878
|
};
|