@netrojs/fnetro 0.1.5 → 0.2.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 +500 -897
- package/client.ts +220 -200
- package/core.ts +146 -644
- package/dist/client.d.ts +99 -155
- package/dist/client.js +177 -570
- package/dist/core.d.ts +69 -156
- package/dist/core.js +31 -452
- package/dist/server.d.ts +120 -179
- package/dist/server.js +278 -553
- package/package.json +17 -8
- package/server.ts +455 -247
package/dist/client.js
CHANGED
|
@@ -1,419 +1,8 @@
|
|
|
1
1
|
// client.ts
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
useState,
|
|
6
|
-
useEffect,
|
|
7
|
-
useMemo,
|
|
8
|
-
useRef as useHonoRef,
|
|
9
|
-
useSyncExternalStore
|
|
10
|
-
} from "hono/jsx";
|
|
2
|
+
import { createSignal, createMemo, createComponent } from "solid-js";
|
|
3
|
+
import { hydrate } from "solid-js/web";
|
|
11
4
|
|
|
12
5
|
// core.ts
|
|
13
|
-
var RAW = /* @__PURE__ */ Symbol("raw");
|
|
14
|
-
var IS_REACTIVE = /* @__PURE__ */ Symbol("isReactive");
|
|
15
|
-
var IS_READONLY = /* @__PURE__ */ Symbol("isReadonly");
|
|
16
|
-
var IS_REF = /* @__PURE__ */ Symbol("isRef");
|
|
17
|
-
var MARK_RAW = /* @__PURE__ */ Symbol("markRaw");
|
|
18
|
-
var targetMap = /* @__PURE__ */ new WeakMap();
|
|
19
|
-
var activeEffect = null;
|
|
20
|
-
var shouldTrack = true;
|
|
21
|
-
var trackStack = [];
|
|
22
|
-
function pauseTracking() {
|
|
23
|
-
trackStack.push(shouldTrack);
|
|
24
|
-
shouldTrack = false;
|
|
25
|
-
}
|
|
26
|
-
function resetTracking() {
|
|
27
|
-
shouldTrack = trackStack.pop() ?? true;
|
|
28
|
-
}
|
|
29
|
-
function track(target, key) {
|
|
30
|
-
if (!shouldTrack || !activeEffect) return;
|
|
31
|
-
let depsMap = targetMap.get(target);
|
|
32
|
-
if (!depsMap) targetMap.set(target, depsMap = /* @__PURE__ */ new Map());
|
|
33
|
-
let dep = depsMap.get(key);
|
|
34
|
-
if (!dep) depsMap.set(key, dep = /* @__PURE__ */ new Set());
|
|
35
|
-
trackEffect(activeEffect, dep);
|
|
36
|
-
}
|
|
37
|
-
function trackEffect(effect2, dep) {
|
|
38
|
-
if (!dep.has(effect2)) {
|
|
39
|
-
dep.add(effect2);
|
|
40
|
-
effect2.deps.push(dep);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
function trigger(target, key, newVal, oldVal) {
|
|
44
|
-
const depsMap = targetMap.get(target);
|
|
45
|
-
if (!depsMap) return;
|
|
46
|
-
const effects = [];
|
|
47
|
-
const computedEffects = [];
|
|
48
|
-
depsMap.get(key)?.forEach((e) => {
|
|
49
|
-
if (e !== activeEffect) {
|
|
50
|
-
e.computed ? computedEffects.push(e) : effects.push(e);
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
[...computedEffects, ...effects].forEach((e) => {
|
|
54
|
-
if (e.active) e.scheduler ? e.scheduler() : e.run();
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
var ReactiveEffect = class {
|
|
58
|
-
constructor(fn, scheduler, scope) {
|
|
59
|
-
this.fn = fn;
|
|
60
|
-
this.scheduler = scheduler;
|
|
61
|
-
this.scope = scope;
|
|
62
|
-
scope?.effects.push(this);
|
|
63
|
-
}
|
|
64
|
-
deps = [];
|
|
65
|
-
active = true;
|
|
66
|
-
cleanup;
|
|
67
|
-
computed = false;
|
|
68
|
-
run() {
|
|
69
|
-
if (!this.active) return this.fn();
|
|
70
|
-
const prevEffect = activeEffect;
|
|
71
|
-
const prevShouldTrack = shouldTrack;
|
|
72
|
-
shouldTrack = true;
|
|
73
|
-
activeEffect = this;
|
|
74
|
-
this.cleanup?.();
|
|
75
|
-
this.cleanup = void 0;
|
|
76
|
-
this.deps.length = 0;
|
|
77
|
-
try {
|
|
78
|
-
const result = this.fn();
|
|
79
|
-
if (typeof result === "function") this.cleanup = result;
|
|
80
|
-
return result;
|
|
81
|
-
} finally {
|
|
82
|
-
activeEffect = prevEffect;
|
|
83
|
-
shouldTrack = prevShouldTrack;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
stop() {
|
|
87
|
-
if (this.active) {
|
|
88
|
-
cleanupEffect(this);
|
|
89
|
-
this.active = false;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
function cleanupEffect(e) {
|
|
94
|
-
e.deps.forEach((dep) => dep.delete(e));
|
|
95
|
-
e.deps.length = 0;
|
|
96
|
-
}
|
|
97
|
-
var activeScope;
|
|
98
|
-
var EffectScope = class {
|
|
99
|
-
effects = [];
|
|
100
|
-
cleanups = [];
|
|
101
|
-
active = true;
|
|
102
|
-
run(fn) {
|
|
103
|
-
const prev = activeScope;
|
|
104
|
-
activeScope = this;
|
|
105
|
-
try {
|
|
106
|
-
return fn();
|
|
107
|
-
} finally {
|
|
108
|
-
activeScope = prev;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
stop() {
|
|
112
|
-
if (this.active) {
|
|
113
|
-
this.effects.forEach((e) => e.stop());
|
|
114
|
-
this.cleanups.forEach((fn) => fn());
|
|
115
|
-
this.active = false;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
onCleanup(fn) {
|
|
119
|
-
this.cleanups.push(fn);
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
function effectScope() {
|
|
123
|
-
return new EffectScope();
|
|
124
|
-
}
|
|
125
|
-
function effect(fn) {
|
|
126
|
-
const e = new ReactiveEffect(fn, void 0, activeScope);
|
|
127
|
-
e.run();
|
|
128
|
-
return () => e.stop();
|
|
129
|
-
}
|
|
130
|
-
function watchEffect(fn, opts) {
|
|
131
|
-
const e = new ReactiveEffect(fn, void 0, activeScope);
|
|
132
|
-
e.run();
|
|
133
|
-
return () => e.stop();
|
|
134
|
-
}
|
|
135
|
-
var refTarget = /* @__PURE__ */ Symbol("refTarget");
|
|
136
|
-
var RefImpl = class {
|
|
137
|
-
constructor(value, shallow = false) {
|
|
138
|
-
this.shallow = shallow;
|
|
139
|
-
this._value = shallow ? value : toReactive(value);
|
|
140
|
-
}
|
|
141
|
-
[IS_REF] = true;
|
|
142
|
-
_value;
|
|
143
|
-
_subscribers = /* @__PURE__ */ new Set();
|
|
144
|
-
get value() {
|
|
145
|
-
track(this, refTarget);
|
|
146
|
-
this._subscribers.forEach((fn) => {
|
|
147
|
-
});
|
|
148
|
-
return this._value;
|
|
149
|
-
}
|
|
150
|
-
set value(next) {
|
|
151
|
-
const newVal = this.shallow ? next : toReactive(next);
|
|
152
|
-
if (!hasChanged(newVal, this._value)) return;
|
|
153
|
-
this._value = newVal;
|
|
154
|
-
trigger(this, refTarget, newVal, this._value);
|
|
155
|
-
this._subscribers.forEach((fn) => fn());
|
|
156
|
-
}
|
|
157
|
-
/** Subscribe for useSyncExternalStore */
|
|
158
|
-
subscribe(fn) {
|
|
159
|
-
this._subscribers.add(fn);
|
|
160
|
-
return () => this._subscribers.delete(fn);
|
|
161
|
-
}
|
|
162
|
-
peek() {
|
|
163
|
-
return this._value;
|
|
164
|
-
}
|
|
165
|
-
};
|
|
166
|
-
function ref(value) {
|
|
167
|
-
return isRef(value) ? value : new RefImpl(value);
|
|
168
|
-
}
|
|
169
|
-
function shallowRef(value) {
|
|
170
|
-
return new RefImpl(value, true);
|
|
171
|
-
}
|
|
172
|
-
function triggerRef(r) {
|
|
173
|
-
if (r instanceof RefImpl) {
|
|
174
|
-
trigger(r, refTarget);
|
|
175
|
-
r._subscribers.forEach((fn) => fn());
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
function isRef(r) {
|
|
179
|
-
return !!r && typeof r === "object" && r[IS_REF] === true;
|
|
180
|
-
}
|
|
181
|
-
function unref(r) {
|
|
182
|
-
return isRef(r) ? r.value : r;
|
|
183
|
-
}
|
|
184
|
-
function toRef(obj, key) {
|
|
185
|
-
const r = new RefImpl(void 0, false);
|
|
186
|
-
Object.defineProperty(r, "value", {
|
|
187
|
-
get() {
|
|
188
|
-
track(r, refTarget);
|
|
189
|
-
return obj[key];
|
|
190
|
-
},
|
|
191
|
-
set(v) {
|
|
192
|
-
obj[key] = v;
|
|
193
|
-
trigger(r, refTarget, v, obj[key]);
|
|
194
|
-
}
|
|
195
|
-
});
|
|
196
|
-
return r;
|
|
197
|
-
}
|
|
198
|
-
function toRefs(obj) {
|
|
199
|
-
const result = {};
|
|
200
|
-
for (const key in obj) result[key] = toRef(obj, key);
|
|
201
|
-
return result;
|
|
202
|
-
}
|
|
203
|
-
var ComputedRefImpl = class {
|
|
204
|
-
constructor(getter, setter) {
|
|
205
|
-
this.setter = setter;
|
|
206
|
-
this.effect = new ReactiveEffect(getter, () => {
|
|
207
|
-
if (!this._dirty) {
|
|
208
|
-
this._dirty = true;
|
|
209
|
-
trigger(this, refTarget);
|
|
210
|
-
this._subscribers.forEach((fn) => fn());
|
|
211
|
-
}
|
|
212
|
-
}, activeScope);
|
|
213
|
-
this.effect.computed = true;
|
|
214
|
-
}
|
|
215
|
-
[IS_REF] = true;
|
|
216
|
-
effect;
|
|
217
|
-
_value;
|
|
218
|
-
_dirty = true;
|
|
219
|
-
_subscribers = /* @__PURE__ */ new Set();
|
|
220
|
-
get value() {
|
|
221
|
-
track(this, refTarget);
|
|
222
|
-
if (this._dirty) {
|
|
223
|
-
this._dirty = false;
|
|
224
|
-
this._value = this.effect.run();
|
|
225
|
-
}
|
|
226
|
-
return this._value;
|
|
227
|
-
}
|
|
228
|
-
set value(v) {
|
|
229
|
-
this.setter?.(v);
|
|
230
|
-
}
|
|
231
|
-
subscribe(fn) {
|
|
232
|
-
this._subscribers.add(fn);
|
|
233
|
-
return () => this._subscribers.delete(fn);
|
|
234
|
-
}
|
|
235
|
-
peek() {
|
|
236
|
-
return this._value;
|
|
237
|
-
}
|
|
238
|
-
};
|
|
239
|
-
function computed(arg) {
|
|
240
|
-
if (typeof arg === "function") {
|
|
241
|
-
return new ComputedRefImpl(arg);
|
|
242
|
-
}
|
|
243
|
-
return new ComputedRefImpl(arg.get, arg.set);
|
|
244
|
-
}
|
|
245
|
-
var reactiveMap = /* @__PURE__ */ new WeakMap();
|
|
246
|
-
var readonlyMap = /* @__PURE__ */ new WeakMap();
|
|
247
|
-
var shallowReactiveMap = /* @__PURE__ */ new WeakMap();
|
|
248
|
-
function toReactive(value) {
|
|
249
|
-
return value !== null && typeof value === "object" ? reactive(value) : value;
|
|
250
|
-
}
|
|
251
|
-
var arrayInstrumentations = {};
|
|
252
|
-
["includes", "indexOf", "lastIndexOf"].forEach((method) => {
|
|
253
|
-
arrayInstrumentations[method] = function(...args) {
|
|
254
|
-
const arr = toRaw(this);
|
|
255
|
-
for (let i = 0; i < this.length; i++) track(arr, i);
|
|
256
|
-
let res = arr[method](...args);
|
|
257
|
-
if (res === -1 || res === false) res = arr[method](...args.map(toRaw));
|
|
258
|
-
return res;
|
|
259
|
-
};
|
|
260
|
-
});
|
|
261
|
-
["push", "pop", "shift", "unshift", "splice"].forEach((method) => {
|
|
262
|
-
arrayInstrumentations[method] = function(...args) {
|
|
263
|
-
pauseTracking();
|
|
264
|
-
const res = toRaw(this)[method].apply(this, args);
|
|
265
|
-
resetTracking();
|
|
266
|
-
return res;
|
|
267
|
-
};
|
|
268
|
-
});
|
|
269
|
-
function createHandler(shallow = false, readonly2 = false) {
|
|
270
|
-
return {
|
|
271
|
-
get(target, key, receiver) {
|
|
272
|
-
if (key === RAW) return target;
|
|
273
|
-
if (key === IS_REACTIVE) return !readonly2;
|
|
274
|
-
if (key === IS_READONLY) return readonly2;
|
|
275
|
-
if (key === MARK_RAW) return target[MARK_RAW];
|
|
276
|
-
const isArray = Array.isArray(target);
|
|
277
|
-
if (!readonly2 && isArray && hasOwn(arrayInstrumentations, key)) {
|
|
278
|
-
return Reflect.get(arrayInstrumentations, key, receiver);
|
|
279
|
-
}
|
|
280
|
-
const res = Reflect.get(target, key, receiver);
|
|
281
|
-
if (typeof key === "symbol" || key === "__proto__") return res;
|
|
282
|
-
if (!readonly2) track(target, key);
|
|
283
|
-
if (shallow) return res;
|
|
284
|
-
if (isRef(res)) return isArray ? res : res.value;
|
|
285
|
-
return res !== null && typeof res === "object" && !res[MARK_RAW] ? readonly2 ? readonlyProxy(res) : reactive(res) : res;
|
|
286
|
-
},
|
|
287
|
-
set(target, key, value, receiver) {
|
|
288
|
-
if (readonly2) {
|
|
289
|
-
console.warn(`[fnetro] Cannot set "${String(key)}" on readonly object`);
|
|
290
|
-
return true;
|
|
291
|
-
}
|
|
292
|
-
const oldVal = target[key];
|
|
293
|
-
const result = Reflect.set(target, key, value, receiver);
|
|
294
|
-
if (hasChanged(value, oldVal)) trigger(target, key, value, oldVal);
|
|
295
|
-
return result;
|
|
296
|
-
},
|
|
297
|
-
deleteProperty(target, key) {
|
|
298
|
-
if (readonly2) return true;
|
|
299
|
-
const hadKey = hasOwn(target, key);
|
|
300
|
-
const result = Reflect.deleteProperty(target, key);
|
|
301
|
-
if (hadKey && result) trigger(target, key);
|
|
302
|
-
return result;
|
|
303
|
-
},
|
|
304
|
-
has(target, key) {
|
|
305
|
-
const res = Reflect.has(target, key);
|
|
306
|
-
track(target, key);
|
|
307
|
-
return res;
|
|
308
|
-
},
|
|
309
|
-
ownKeys(target) {
|
|
310
|
-
track(target, Array.isArray(target) ? "length" : "__iterate__");
|
|
311
|
-
return Reflect.ownKeys(target);
|
|
312
|
-
}
|
|
313
|
-
};
|
|
314
|
-
}
|
|
315
|
-
function reactive(target) {
|
|
316
|
-
if (isReadonly(target)) return target;
|
|
317
|
-
if (target[MARK_RAW]) return target;
|
|
318
|
-
if (reactiveMap.has(target)) return reactiveMap.get(target);
|
|
319
|
-
const proxy = new Proxy(target, createHandler());
|
|
320
|
-
reactiveMap.set(target, proxy);
|
|
321
|
-
return proxy;
|
|
322
|
-
}
|
|
323
|
-
function shallowReactive(target) {
|
|
324
|
-
if (shallowReactiveMap.has(target)) return shallowReactiveMap.get(target);
|
|
325
|
-
const proxy = new Proxy(target, createHandler(true));
|
|
326
|
-
shallowReactiveMap.set(target, proxy);
|
|
327
|
-
return proxy;
|
|
328
|
-
}
|
|
329
|
-
function readonlyProxy(target) {
|
|
330
|
-
if (readonlyMap.has(target)) return readonlyMap.get(target);
|
|
331
|
-
const proxy = new Proxy(target, createHandler(false, true));
|
|
332
|
-
readonlyMap.set(target, proxy);
|
|
333
|
-
return proxy;
|
|
334
|
-
}
|
|
335
|
-
function readonly(target) {
|
|
336
|
-
return readonlyProxy(target);
|
|
337
|
-
}
|
|
338
|
-
function markRaw(value) {
|
|
339
|
-
;
|
|
340
|
-
value[MARK_RAW] = true;
|
|
341
|
-
return value;
|
|
342
|
-
}
|
|
343
|
-
function toRaw(observed) {
|
|
344
|
-
const raw = observed?.[RAW];
|
|
345
|
-
return raw ? toRaw(raw) : observed;
|
|
346
|
-
}
|
|
347
|
-
function isReactive(value) {
|
|
348
|
-
if (isReadonly(value)) return isReactive(value[RAW]);
|
|
349
|
-
return !!(value && value[IS_REACTIVE]);
|
|
350
|
-
}
|
|
351
|
-
function isReadonly(value) {
|
|
352
|
-
return !!(value && value[IS_READONLY]);
|
|
353
|
-
}
|
|
354
|
-
function traverse(value, seen = /* @__PURE__ */ new Set()) {
|
|
355
|
-
if (!value || typeof value !== "object" || seen.has(value)) return value;
|
|
356
|
-
seen.add(value);
|
|
357
|
-
if (isRef(value)) {
|
|
358
|
-
traverse(value.value, seen);
|
|
359
|
-
return value;
|
|
360
|
-
}
|
|
361
|
-
if (Array.isArray(value)) {
|
|
362
|
-
value.forEach((v) => traverse(v, seen));
|
|
363
|
-
return value;
|
|
364
|
-
}
|
|
365
|
-
for (const key in value) traverse(value[key], seen);
|
|
366
|
-
return value;
|
|
367
|
-
}
|
|
368
|
-
function normalizeSource(src) {
|
|
369
|
-
if (Array.isArray(src)) return () => src.map((s) => isRef(s) ? s.value : s());
|
|
370
|
-
if (isRef(src)) return () => src.value;
|
|
371
|
-
return src;
|
|
372
|
-
}
|
|
373
|
-
function watch(source, cb, opts = {}) {
|
|
374
|
-
const getter = opts.deep ? () => traverse(normalizeSource(source)()) : normalizeSource(source);
|
|
375
|
-
let oldVal = void 0;
|
|
376
|
-
let cleanupFn;
|
|
377
|
-
const cleanup = (fn) => {
|
|
378
|
-
cleanupFn = fn;
|
|
379
|
-
};
|
|
380
|
-
const job = () => {
|
|
381
|
-
if (!effect2.active) return;
|
|
382
|
-
cleanupFn?.();
|
|
383
|
-
cleanupFn = void 0;
|
|
384
|
-
const newVal = effect2.run();
|
|
385
|
-
if (opts.deep || hasChanged(newVal, oldVal)) {
|
|
386
|
-
cb(newVal, oldVal, cleanup);
|
|
387
|
-
oldVal = newVal;
|
|
388
|
-
}
|
|
389
|
-
if (opts.once) effect2.stop();
|
|
390
|
-
};
|
|
391
|
-
const effect2 = new ReactiveEffect(getter, job, activeScope);
|
|
392
|
-
if (opts.immediate) {
|
|
393
|
-
cleanupFn?.();
|
|
394
|
-
cleanupFn = void 0;
|
|
395
|
-
const val = effect2.run();
|
|
396
|
-
cb(val, oldVal, cleanup);
|
|
397
|
-
oldVal = val;
|
|
398
|
-
} else {
|
|
399
|
-
oldVal = effect2.run();
|
|
400
|
-
}
|
|
401
|
-
return () => effect2.stop();
|
|
402
|
-
}
|
|
403
|
-
var __hooks = {
|
|
404
|
-
useValue: (r) => isRef(r) ? r.value : r(),
|
|
405
|
-
useLocalRef: (init) => ref(init),
|
|
406
|
-
useLocalReactive: (init) => reactive(init)
|
|
407
|
-
};
|
|
408
|
-
function use(source) {
|
|
409
|
-
return __hooks.useValue(source);
|
|
410
|
-
}
|
|
411
|
-
function useLocalRef(init) {
|
|
412
|
-
return __hooks.useLocalRef(init);
|
|
413
|
-
}
|
|
414
|
-
function useLocalReactive(init) {
|
|
415
|
-
return __hooks.useLocalReactive(init);
|
|
416
|
-
}
|
|
417
6
|
function definePage(def) {
|
|
418
7
|
return { __type: "page", ...def };
|
|
419
8
|
}
|
|
@@ -423,9 +12,6 @@ function defineGroup(def) {
|
|
|
423
12
|
function defineLayout(Component) {
|
|
424
13
|
return { __type: "layout", Component };
|
|
425
14
|
}
|
|
426
|
-
function defineMiddleware(handler) {
|
|
427
|
-
return { __type: "middleware", handler };
|
|
428
|
-
}
|
|
429
15
|
function defineApiRoute(path, register) {
|
|
430
16
|
return { __type: "api", path, register };
|
|
431
17
|
}
|
|
@@ -443,118 +29,122 @@ function resolveRoutes(routes, options = {}) {
|
|
|
443
29
|
pages.push(...sub.pages);
|
|
444
30
|
apis.push(...sub.apis);
|
|
445
31
|
} else {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
32
|
+
pages.push({
|
|
33
|
+
fullPath: (options.prefix ?? "") + route.path,
|
|
34
|
+
page: route,
|
|
35
|
+
layout: route.layout !== void 0 ? route.layout : options.layout,
|
|
36
|
+
middleware: [...options.middleware ?? [], ...route.middleware ?? []]
|
|
37
|
+
});
|
|
450
38
|
}
|
|
451
39
|
}
|
|
452
40
|
return { pages, apis };
|
|
453
41
|
}
|
|
454
|
-
|
|
455
|
-
var STATE_KEY = "__FNETRO_STATE__";
|
|
456
|
-
var PARAMS_KEY = "__FNETRO_PARAMS__";
|
|
457
|
-
function hasChanged(a, b) {
|
|
458
|
-
return !Object.is(a, b);
|
|
459
|
-
}
|
|
460
|
-
function hasOwn(obj, key) {
|
|
461
|
-
return Object.prototype.hasOwnProperty.call(obj, key);
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
// client.ts
|
|
465
|
-
function clientUseValue(source) {
|
|
466
|
-
if (isRef(source)) {
|
|
467
|
-
return useSyncExternalStore(
|
|
468
|
-
(notify) => source.subscribe(notify),
|
|
469
|
-
() => source.peek?.() ?? source.value
|
|
470
|
-
);
|
|
471
|
-
}
|
|
472
|
-
const c = useMemo(() => computed(source), [source]);
|
|
473
|
-
return useSyncExternalStore(
|
|
474
|
-
(notify) => c.subscribe(notify),
|
|
475
|
-
() => c.peek?.() ?? c.value
|
|
476
|
-
);
|
|
477
|
-
}
|
|
478
|
-
function clientUseLocalRef(init) {
|
|
479
|
-
const stableRef = useHonoRef(null);
|
|
480
|
-
if (stableRef.current === null) stableRef.current = ref(init);
|
|
481
|
-
const r = stableRef.current;
|
|
482
|
-
useSyncExternalStore(
|
|
483
|
-
(notify) => r.subscribe(notify),
|
|
484
|
-
() => r.peek?.() ?? r.value
|
|
485
|
-
);
|
|
486
|
-
return r;
|
|
487
|
-
}
|
|
488
|
-
function clientUseLocalReactive(init) {
|
|
489
|
-
const stableRef = useHonoRef(null);
|
|
490
|
-
if (stableRef.current === null) stableRef.current = reactive(init);
|
|
491
|
-
const proxy = stableRef.current;
|
|
492
|
-
const [tick, setTick] = useState(0);
|
|
493
|
-
useEffect(() => {
|
|
494
|
-
return watchEffect(() => {
|
|
495
|
-
JSON.stringify(proxy);
|
|
496
|
-
setTick((t) => t + 1);
|
|
497
|
-
});
|
|
498
|
-
}, []);
|
|
499
|
-
return proxy;
|
|
500
|
-
}
|
|
501
|
-
Object.assign(__hooks, {
|
|
502
|
-
useValue: clientUseValue,
|
|
503
|
-
useLocalRef: clientUseLocalRef,
|
|
504
|
-
useLocalReactive: clientUseLocalReactive
|
|
505
|
-
});
|
|
506
|
-
function compileRoute(r) {
|
|
42
|
+
function compilePath(path) {
|
|
507
43
|
const keys = [];
|
|
508
|
-
const src =
|
|
44
|
+
const src = path.replace(/\[\.\.\.([^\]]+)\]/g, (_, k) => {
|
|
509
45
|
keys.push(k);
|
|
510
46
|
return "(.*)";
|
|
511
47
|
}).replace(/\[([^\]]+)\]/g, (_, k) => {
|
|
512
48
|
keys.push(k);
|
|
513
49
|
return "([^/]+)";
|
|
514
50
|
}).replace(/\*/g, "(.*)");
|
|
515
|
-
return {
|
|
51
|
+
return { re: new RegExp(`^${src}$`), keys };
|
|
52
|
+
}
|
|
53
|
+
function matchPath(compiled, pathname) {
|
|
54
|
+
const m = pathname.match(compiled.re);
|
|
55
|
+
if (!m) return null;
|
|
56
|
+
const params = {};
|
|
57
|
+
compiled.keys.forEach((k, i) => {
|
|
58
|
+
params[k] = decodeURIComponent(m[i + 1] ?? "");
|
|
59
|
+
});
|
|
60
|
+
return params;
|
|
516
61
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
62
|
+
var SPA_HEADER = "x-fnetro-spa";
|
|
63
|
+
var STATE_KEY = "__FNETRO_STATE__";
|
|
64
|
+
var PARAMS_KEY = "__FNETRO_PARAMS__";
|
|
65
|
+
var SEO_KEY = "__FNETRO_SEO__";
|
|
66
|
+
|
|
67
|
+
// client.ts
|
|
68
|
+
var _routes = [];
|
|
69
|
+
var _appLayout;
|
|
70
|
+
function findRoute(pathname) {
|
|
71
|
+
for (const { route, cp } of _routes) {
|
|
72
|
+
const params = matchPath(cp, pathname);
|
|
73
|
+
if (params !== null) return { route, params };
|
|
527
74
|
}
|
|
528
75
|
return null;
|
|
529
76
|
}
|
|
530
|
-
var
|
|
531
|
-
var
|
|
532
|
-
function
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
77
|
+
var _setNav = null;
|
|
78
|
+
var _mw = [];
|
|
79
|
+
function useClientMiddleware(mw) {
|
|
80
|
+
_mw.push(mw);
|
|
81
|
+
}
|
|
82
|
+
async function runMiddleware(url, done) {
|
|
83
|
+
const chain = [..._mw, async (_u, next) => {
|
|
84
|
+
await done();
|
|
85
|
+
await next();
|
|
86
|
+
}];
|
|
87
|
+
let i = 0;
|
|
88
|
+
const run = async () => {
|
|
89
|
+
const fn = chain[i++];
|
|
90
|
+
if (fn) await fn(url, run);
|
|
91
|
+
};
|
|
92
|
+
await run();
|
|
539
93
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
94
|
+
function setMeta(selector, attr, val) {
|
|
95
|
+
if (!val) {
|
|
96
|
+
document.querySelector(selector)?.remove();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
let el = document.querySelector(selector);
|
|
100
|
+
if (!el) {
|
|
101
|
+
el = document.createElement("meta");
|
|
102
|
+
const m = /\[(\w+[:-]?\w*)="([^"]+)"\]/.exec(selector);
|
|
103
|
+
if (m) el.setAttribute(m[1], m[2]);
|
|
104
|
+
document.head.appendChild(el);
|
|
105
|
+
}
|
|
106
|
+
el.setAttribute(attr, val);
|
|
107
|
+
}
|
|
108
|
+
function syncSEO(seo) {
|
|
109
|
+
if (seo.title) document.title = seo.title;
|
|
110
|
+
setMeta('[name="description"]', "content", seo.description);
|
|
111
|
+
setMeta('[name="keywords"]', "content", seo.keywords);
|
|
112
|
+
setMeta('[name="robots"]', "content", seo.robots);
|
|
113
|
+
setMeta('[name="theme-color"]', "content", seo.themeColor);
|
|
114
|
+
setMeta('[property="og:title"]', "content", seo.ogTitle);
|
|
115
|
+
setMeta('[property="og:description"]', "content", seo.ogDescription);
|
|
116
|
+
setMeta('[property="og:image"]', "content", seo.ogImage);
|
|
117
|
+
setMeta('[property="og:url"]', "content", seo.ogUrl);
|
|
118
|
+
setMeta('[property="og:type"]', "content", seo.ogType);
|
|
119
|
+
setMeta('[name="twitter:card"]', "content", seo.twitterCard);
|
|
120
|
+
setMeta('[name="twitter:title"]', "content", seo.twitterTitle);
|
|
121
|
+
setMeta('[name="twitter:description"]', "content", seo.twitterDescription);
|
|
122
|
+
setMeta('[name="twitter:image"]', "content", seo.twitterImage);
|
|
123
|
+
const canon = seo.canonical;
|
|
124
|
+
let linkEl = document.querySelector('link[rel="canonical"]');
|
|
125
|
+
if (canon) {
|
|
126
|
+
if (!linkEl) {
|
|
127
|
+
linkEl = document.createElement("link");
|
|
128
|
+
linkEl.rel = "canonical";
|
|
129
|
+
document.head.appendChild(linkEl);
|
|
130
|
+
}
|
|
131
|
+
linkEl.href = canon;
|
|
132
|
+
} else {
|
|
133
|
+
linkEl?.remove();
|
|
549
134
|
}
|
|
550
|
-
return prefetchCache.get(url);
|
|
551
135
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
136
|
+
var _cache = /* @__PURE__ */ new Map();
|
|
137
|
+
function fetchPayload(href) {
|
|
138
|
+
if (!_cache.has(href)) {
|
|
139
|
+
_cache.set(
|
|
140
|
+
href,
|
|
141
|
+
fetch(href, { headers: { [SPA_HEADER]: "1" } }).then((r) => {
|
|
142
|
+
if (!r.ok) throw new Error(`${r.status} ${r.statusText}`);
|
|
143
|
+
return r.json();
|
|
144
|
+
})
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
return _cache.get(href);
|
|
558
148
|
}
|
|
559
149
|
async function navigate(to, opts = {}) {
|
|
560
150
|
const u = new URL(to, location.origin);
|
|
@@ -562,35 +152,36 @@ async function navigate(to, opts = {}) {
|
|
|
562
152
|
location.href = to;
|
|
563
153
|
return;
|
|
564
154
|
}
|
|
565
|
-
|
|
566
|
-
const match = matchRoute(compiled, u.pathname);
|
|
567
|
-
if (!match) {
|
|
155
|
+
if (!findRoute(u.pathname)) {
|
|
568
156
|
location.href = to;
|
|
569
157
|
return;
|
|
570
158
|
}
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
159
|
+
await runMiddleware(u.pathname, async () => {
|
|
160
|
+
try {
|
|
161
|
+
const payload = await fetchPayload(u.toString());
|
|
162
|
+
history[opts.replace ? "replaceState" : "pushState"](
|
|
163
|
+
{ url: u.pathname },
|
|
164
|
+
"",
|
|
165
|
+
u.pathname
|
|
166
|
+
);
|
|
167
|
+
if (opts.scroll !== false) window.scrollTo(0, 0);
|
|
168
|
+
_setNav?.({ path: u.pathname, data: payload.state ?? {}, params: payload.params ?? {} });
|
|
169
|
+
syncSEO(payload.seo ?? {});
|
|
170
|
+
} catch (err) {
|
|
171
|
+
console.error("[fnetro] Navigation error:", err);
|
|
172
|
+
location.href = to;
|
|
173
|
+
}
|
|
174
|
+
});
|
|
586
175
|
}
|
|
587
176
|
function prefetch(url) {
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
177
|
+
try {
|
|
178
|
+
const u = new URL(url, location.origin);
|
|
179
|
+
if (u.origin !== location.origin || !findRoute(u.pathname)) return;
|
|
180
|
+
fetchPayload(u.toString());
|
|
181
|
+
} catch {
|
|
182
|
+
}
|
|
592
183
|
}
|
|
593
|
-
function
|
|
184
|
+
function onLinkClick(e) {
|
|
594
185
|
if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
|
|
595
186
|
const a = e.composedPath().find(
|
|
596
187
|
(el) => el instanceof HTMLAnchorElement
|
|
@@ -603,7 +194,7 @@ function interceptClicks(e) {
|
|
|
603
194
|
e.preventDefault();
|
|
604
195
|
navigate(a.href);
|
|
605
196
|
}
|
|
606
|
-
function
|
|
197
|
+
function onLinkHover(e) {
|
|
607
198
|
const a = e.composedPath().find(
|
|
608
199
|
(el) => el instanceof HTMLAnchorElement
|
|
609
200
|
);
|
|
@@ -612,62 +203,78 @@ function interceptHover(e) {
|
|
|
612
203
|
function onPopState() {
|
|
613
204
|
navigate(location.href, { replace: true, scroll: false });
|
|
614
205
|
}
|
|
206
|
+
function AppRoot(props) {
|
|
207
|
+
const [nav, setNav] = createSignal(props.initial);
|
|
208
|
+
_setNav = setNav;
|
|
209
|
+
const view = createMemo(() => {
|
|
210
|
+
const { path, data, params } = nav();
|
|
211
|
+
const m = findRoute(path);
|
|
212
|
+
if (!m) {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
const layout = m.route.layout !== void 0 ? m.route.layout : props.appLayout;
|
|
216
|
+
const pageEl = createComponent(m.route.page.Page, { ...data, url: path, params });
|
|
217
|
+
if (!layout) return pageEl;
|
|
218
|
+
return createComponent(layout.Component, {
|
|
219
|
+
url: path,
|
|
220
|
+
params,
|
|
221
|
+
get children() {
|
|
222
|
+
return pageEl;
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
return view;
|
|
227
|
+
}
|
|
615
228
|
async function boot(options) {
|
|
616
229
|
const { pages } = resolveRoutes(options.routes, {
|
|
617
230
|
layout: options.layout,
|
|
618
231
|
middleware: []
|
|
619
232
|
});
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
currentLayout = options.layout;
|
|
233
|
+
_routes = pages.map((r) => ({ route: r, cp: compilePath(r.fullPath) }));
|
|
234
|
+
_appLayout = options.layout;
|
|
623
235
|
const pathname = location.pathname;
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
console.warn(`[fnetro] No route matched "${pathname}" \u2014 not hydrating`);
|
|
236
|
+
if (!findRoute(pathname)) {
|
|
237
|
+
console.warn(`[fnetro] No route matched "${pathname}" \u2014 skipping hydration`);
|
|
627
238
|
return;
|
|
628
239
|
}
|
|
629
240
|
const stateMap = window[STATE_KEY] ?? {};
|
|
630
241
|
const paramsMap = window[PARAMS_KEY] ?? {};
|
|
631
|
-
const
|
|
632
|
-
|
|
633
|
-
|
|
242
|
+
const seoData = window[SEO_KEY] ?? {};
|
|
243
|
+
const initial = {
|
|
244
|
+
path: pathname,
|
|
245
|
+
data: stateMap[pathname] ?? {},
|
|
246
|
+
params: paramsMap
|
|
247
|
+
};
|
|
248
|
+
const container = document.getElementById("fnetro-app");
|
|
249
|
+
if (!container) {
|
|
250
|
+
console.error("[fnetro] #fnetro-app not found \u2014 aborting hydration");
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
syncSEO(seoData);
|
|
254
|
+
hydrate(
|
|
255
|
+
() => createComponent(AppRoot, { initial, appLayout: _appLayout }),
|
|
256
|
+
container
|
|
257
|
+
);
|
|
258
|
+
document.addEventListener("click", onLinkClick);
|
|
634
259
|
if (options.prefetchOnHover !== false) {
|
|
635
|
-
document.addEventListener("mouseover",
|
|
260
|
+
document.addEventListener("mouseover", onLinkHover);
|
|
636
261
|
}
|
|
637
262
|
window.addEventListener("popstate", onPopState);
|
|
638
|
-
for (const fn of afterNavListeners) await fn(pathname);
|
|
639
263
|
}
|
|
640
264
|
export {
|
|
265
|
+
PARAMS_KEY,
|
|
266
|
+
SEO_KEY,
|
|
267
|
+
SPA_HEADER,
|
|
268
|
+
STATE_KEY,
|
|
641
269
|
boot,
|
|
642
|
-
|
|
270
|
+
compilePath,
|
|
643
271
|
defineApiRoute,
|
|
644
272
|
defineGroup,
|
|
645
273
|
defineLayout,
|
|
646
|
-
defineMiddleware,
|
|
647
274
|
definePage,
|
|
648
|
-
|
|
649
|
-
effectScope,
|
|
650
|
-
isReactive,
|
|
651
|
-
isReadonly,
|
|
652
|
-
isRef,
|
|
653
|
-
markRaw,
|
|
275
|
+
matchPath,
|
|
654
276
|
navigate,
|
|
655
|
-
onAfterNavigate,
|
|
656
|
-
onBeforeNavigate,
|
|
657
277
|
prefetch,
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
ref,
|
|
661
|
-
shallowReactive,
|
|
662
|
-
shallowRef,
|
|
663
|
-
toRaw,
|
|
664
|
-
toRef,
|
|
665
|
-
toRefs,
|
|
666
|
-
triggerRef,
|
|
667
|
-
unref,
|
|
668
|
-
use,
|
|
669
|
-
useLocalReactive,
|
|
670
|
-
useLocalRef,
|
|
671
|
-
watch,
|
|
672
|
-
watchEffect
|
|
278
|
+
resolveRoutes,
|
|
279
|
+
useClientMiddleware
|
|
673
280
|
};
|